1. 为什么端侧 Agent 非得先啃下"端侧 LLM"这块硬骨头
1.1 云端方案在 Agent 场景的三处"掉链子"
先说我自己的结论:端侧 Agent 的体验瓶颈,从来不在 Agent 框架本身,而在模型能不能留在本地。
很多团队一开始是云端方案——把 LLM 放服务器上,端上只做采集和展示。跑通 Demo 很容易,但只要一上真机,问题就来了。第一是延迟。Agent 不是单轮问答,它是一连串的"理解意图→调用工具→看结果→再推理"循环。云端一次往返 2 到 5 秒,在一个 5 步工具调用链里就是 10 到 25 秒。用户等的不是"一个回答",而是一个流程,体感完全不一样。
第二是隐私。Agent 要干活就得访问大量上下文:聊天记录、通讯录、本地文档、摄像头画面、位置信息。这些数据全推到云端,用户的信任成本很高,在医疗、财务这些场景基本是红线。我见过不止一个产品,就是因为"数据必须留在本地"这一条,直接从云端方案转做端侧。
第三是成本和可用性。Agent 的多轮工具调用会把 token 消耗放大好几倍,生产环境跑一个月账单会很可观。而断网或弱网环境,云端方案直接不可用——地下车库的网络信号、高铁隧道里的弱网,Agent 一分钱都干不了。
1.2 端侧 LLM 在 Agent 里的定位
一个容易走极端的误区:大家总觉得端侧方案要取代云端方案。我的看法是:端侧 LLM 不是来"替代"的,是来"兜底"的。
实践里最划算的做法是混合架构——端侧模型负责低延迟、高隐私、轻量级的子任务(意图识别、敏感信息过滤、本地知识检索、简单工具调用、离线模式兜底),云端大模型负责重推理(复杂逻辑、跨领域知识、代码生成、高质量长文)。端侧能 pass 就 pass,端侧搞不定再上云。
这里有三个判断标准可以抄作业:
- 延迟敏感度:这个任务用户能等多久?交互式操作超过 1 秒体感就明显变差,必须端侧。
- 数据敏感度:内容是否涉及个人隐私或业务机密?是就端侧。
- 调用成本:Agent 单轮任务会展开多少次模型调用?次数越多,云端成本越失控,端侧越划算。
端侧模型只要满足这三个标准里的任意一条,就有充分的部署理由。
1.3 读者范围与前置基础
这篇文章是系列第二篇,默认你已经理解端侧 Agent 的基本概念(Agent 循环、工具调用、记忆机制),但不需要有端侧模型部署经验。我会从选型、量化的原理一路讲到引擎部署和真实踩坑,覆盖以下这些实际关注点:端侧 AI 硬件选型(RK3588、Jetson Orin)、本地部署热门模型(开源 Qwen 系列、DeepSeek 蒸馏模型、Llama 3.2 小模型)、Ollama / llama.cpp 落地方法,以及推理引擎(llama.cpp / ONNX Runtime / MLC-LLM)的边界。
2. 部署前的选择题:算力盘点、内存账本与模型挑选
2.1 主流端侧硬件的算力分布
先把"端侧硬件"这四个字落到具体设备上。根据我的实测和社区数据,端侧部署的硬件大致可以分三档:
| 设备类型 | 代表设备 | 可用内存 | 算力画像 | 能跑的模型上限 |
|---|---|---|---|---|
| 手机 SoC | 骁龙 8 Gen 3、天玑 9300、A17 Pro | 8~16GB(设备总内存) | NPU 算力强但生态碎片化 | 3B~4B INT4 流畅,7B INT4 勉强 |
| 开发板 | NVIDIA Jetson Orin Nano/NX、RK3588 | 8~32GB | Jetson 自带 CUDA 生态成熟,RK3588 靠 CPU/NPU | 7B~14B INT4(Jetson),7B 以下(RK3588) |
| 迷你主机/笔记本 | Mac mini M1/M2、NUC、老台式机 | 16~64GB | 内存带宽大,CPU 推理为主,统一内存对 LLM 极度友好 | 7B~14B INT4 流畅 |
一个关键认知是:在这个场景里,决定"能跑多大模型"的往往不是算力(TOPS/TFLOPS),而是内存容量和内存带宽。LLM 推理是内存带宽密集型的活儿,参数要从内存搬到计算单元,内存带宽越大,每秒吐出来的 token 越多。M 系列芯片的 MacBook 之所以适合跑本地模型,统一内存架构功不可没。反过来,很多 SoC 标称高 TOPS,但内存带宽不够,实测速度并没有广告那么漂亮。
2.2 先算内存账:能装多大模型,真不是看参数量的简单算术
这句话几乎所有上过手的人都踩过:以为 7B 模型就只需要 7GB 内存,结果一跑内存爆了。
部署一个模型的真实内存需求,粗略等于三层总和:
- 权重内存:取决于参数量和量化精度。FP16 大概是 2 字节/参数,INT4 量化后大约是 0.5~0.6 字节/参数。7B 模型 FP16 需要约 14GB,INT4 只需要约 4GB。
- KV Cache:推理时缓存历史 token 的 Key/Value。以 Qwen2.5-7B 为例,约 28 层 Transformer、隐藏维度 3584,FP16 下每 token 的 KV 开销大约 196KB,4K 上下文就要约 784MB。上下文开得越长,KV 越大。
- 系统与运行时开销:Python 运行环境、推理框架、输入输出的中间张量,保守预留 0.5~1GB。
所以一个 7B INT4 模型、4K 上下文窗口,总共需要大约 4GB(权重)+ 0.8GB(KV)+ 0.5GB(运行时)≈ 5.5GB 内存。想跑 32K 长上下文,KV Cache 会飙到好几 GB,对端侧设备来说就非常吃力了。
这里有个小技巧:KV Cache 也可以量化,很多推理引擎支持 KV 量化到 8bit 甚至 4bit,能省掉一半以上的 KV 内存,对端侧的长上下文场景几乎属于必开项。
2.3 模型这么挑:中文场景、指令跟随、函数调用缺一不可
模型选型上,我会先明确一个观点:不建议直接拿原版基座模型部署。端侧 Agent 需要的是指令跟随能力强、最好原生支持工具调用的小模型,基座模型需要自己调提示词来"驾驭",在端侧这么紧巴巴的算力下,效果很难保证。
目前我实测下来值得优先考虑的几条线:
- Qwen2.5 系列(0.5B / 1.5B / 3B / 7B):中文场景的默认首选。中文 token 效率高(同样的中文内容,token 消耗比 Llama 少很多,约省一半),指令跟随和函数调用原生支持,而且 3B 和 7B 都有 INT4 量化之后的可用效果。
- Llama 3.2(1B / 3B):英文场景友好,生态完善,函数调用能力需要配合提示词模板做适配。
- Phi-3-mini / Phi-3.5-mini:微软的小模型,逻辑推理在同体量里相对强,适合端侧做决策类子任务。
- DeepSeek-R1-Distill-Qwen 系列(1.5B / 7B):蒸馏后的推理模型,适合 Agent 里的"慢思考"环节,比如做复杂计划拆分,但生成速度偏慢,端侧部署时上下文窗口别开太大。
选型口诀:先看语言(中文就 Qwen),再看生态(GPU 设备优先能跑 CUDA 的),最后看体量(内存余量决定上限)。
3. 量化这条"减重手术":GGUF、Q4_K_M 与精度损失的真实体验
3.1 量化到底做了什么:FP16 到 INT4 的数值游戏
部署端侧模型,量化是绕不开的一步。它的本质很简单:LLM 在推理和训练时通常用 FP16 或 BF16 浮点数存权重和激活值,每个参数占 2 字节。量化就是把浮点数近似成低比特整数(比如 INT8 占 1 字节、INT4 只占半字节),让模型体积缩小、内存占用降低、推理速度变快。
GGUF 是目前端侧最主流的量化模型格式(Ollama 和 llama.cpp 生态都认这个格式),格式内部就声明了模型结构、分词器、超参数和权重数据。你在 Ollama 的模型库里看到q4_K_M、q8_0、f16这些后缀,指的就是不同的量化方式和档位。
各个档位的含义大概是这样:
f16:未量化,体积最大,精度最高。q8_0:8 位量化,精度损失极小,体积约降一半。q6_K:6 位量化,端侧偏保守但效果好的选择。q5_K_M:5 位混合量化,接近 q8 的品质,体积更小。q4_K_M:4 位混合量化,端侧部署的黄金档位。q3_K:3 位量化,体积最小但掉点明显,不推荐直接做 Agent。
K_M里的 M 表示混合精度——不是所有层都一刀切用同一位数量化,而是对关键的张量(如注意力层的部分权重)保留更高精度,对不敏感的权重用低精度,在体积和效果之间做平衡。
3.2 别被"性能几乎无损"骗了:量化敏感度因模型而异
社区里经常有人说"4bit 量化几乎无损",这话要打个大大的折扣。
「无损」通常指的是在标准评测集(比如 MMLU、C-Eval、困惑度)上的分数差异很小,一般能控制在 1~2 个百分点内。但评测集覆盖不了所有真实任务。在端侧 Agent 的实际应用中,我踩到过几个典型的量化后问题:
- 结构化输出变差:某些模型 q4 量化后,回
{"action": ...}这种 JSON 时更容易出现多了一个花括号、少了一个引号的问题。这个问题对 Agent 是致命的,因为解析失败直接打断工具调用链。 - 函数调用参数填错:量化会让模型对某些参数理解出现偏差,比如温度参数填了字符串而不是数字,工具名张冠李戴。
- 长上下文能力退化:低比特量化在长上下文的注意力计算上更容易产生累积误差,导致 8K 以上上下文时,模型开始"遗忘"早期信息。
所以我的建议是:端侧 Agent 部署至少用 q4_K_M,如果内存有余量,直接上 q5_K_M。对结构化输出要求很高的时候,试着对比 q4 和 q5 的 JSON 出错率,这个差异通常在 q5 就明显变小了。千万不要为了省 0.5GB 内存硬上 q3。
3.3 KV Cache 量化:容易被忽略但很关键的一环
前面算内存账的时候提到,KV Cache 在长上下文时会吃掉大量内存。端侧如果跑 8K、16K 上下文,KV 内存往往比权重还难绷。大部分推理引擎(llama.cpp 系列)里可以单独设置 KV Cache 量化精度,一般有f16、q8_0、q4_0几个选项。
我对 KV 量化的实测经验是:q8_0对最终生成质量影响很小,但能显著降低长上下文内存压力;q4_0会损失一些细节,Agent 场景下可能让模型在长对话里记错用户偏好,所以一般只建议在内存实在不够时用。当你看到某个端侧部署教程说"单条内存跑 7B 16K 上下文",多半就是开了 KV 量化。
4. 推理引擎选型与部署实操:从模型文件到 Agent API 的全链路
4.1 三条路线:Ollama、llama.cpp、MLC-LLM
选推理引擎,本质上是在选"你愿意为可控性付出多少工程成本"。
- Ollama:最省事。一条命令拉模型,一条命令起服务,自动兼容 OpenAI 的 API 格式。适合快速验证、中小规模部署、以及不想碰底层优化的团队。
- llama.cpp:最极客。纯 C/C++ 实现,CPU 推理优化到位,支持 GPU 加速,量化、KV 缓存、上下文长度、批处理大小全都能手动调。适合需要精细控制、或最终要嵌入到 C++/Go/Rust 等后端里跑的场景。
- MLC-LLM / ONNX Runtime:面向移动端深度优化,有能力做到 iOS/Android 嵌入式集成。MLC-LLM 基于 TVM 编译器,能把模型编译成针对特定硬件优化的机器码,适配 NPU/DSP 的能力最强,但开发和调试成本也最高。
我的默认选择是:开发调试用 Ollama,生产嵌入用 llama.cpp。不是 Ollama 不行,而是生产环境里你往往需要把推理引擎作为库嵌进业务代码,llama.cpp 的 C 接口和绑定更从容;Ollama 更适合做独立服务,尤其是快速起步和内部工具链。
4.2 Ollama 快速通道:拉模型、跑服务、暴露 API
Ollama 路径几乎是复制粘贴就能跑通的。
第一步,安装。macOS 直接brew install ollama,Linux 直接跑安装脚本就好,Windows 下载安装包双击。
第二步,选模型并拉取。比如我默认拉一个 Qwen2.5 3B 的 q4 量化版:
ollama pull qwen2.5:3b-instruct-q4_K_M第三步,启动服务(Ollama 默认后台 API 端口 11434):
ollama serve这时候模型就可以直接用了。最简单的方式:命令行交互运行。
ollama run qwen2.5:3b-instruct-q4_K_M但 Agent 要用的往往是 API。Ollama 从 0.1.25+ 版本开始提供 OpenAI 兼容端点/v1/chat/completions,这意味着任何按 OpenAI SDK 写的 Agent 代码,只要把 base_url 改成http://localhost:11434/v1就能直接跑:
curl http://localhost:11434/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "qwen2.5:3b-instruct-q4_K_M", "messages": [{"role": "user", "content": "帮我查一下今天的待办事项"}], "temperature": 0.7 }'如果你想让模型只做服务端调用、不占用命令行交互,ollama serve是和终端粘着的,生产环境建议用systemd或pm2托管。
4.3 llama.cpp 进阶:量化转换、llama-server、自定义参数
当你需要更精细的控制,llama.cpp 是必经之路。
第一步,编译。llama.cpp 的构建系统是 CMake,支持 CPU 和 CUDA 后端:
git clone https://github.com/ggerganov/llama.cpp cd llama.cpp cmake -B build -DCMAKE_BUILD_TYPE=Release -DGGML_CUDA=ON cmake --build build --config Release -j $(nproc)第二步,把模型转换成 GGUF 格式并量化。如果你直接拿到的 HF 权重是原版 FP16,需要先转换再量化。但更省事的方式是直接从社区下载已经量化好的 GGUF 文件,比如Qwen2.5-7B-Instruct-GGUF里的q4_K_M.gguf。
如果想自己动手量化,两步命令大概是这样的:
# 把 HF 权重转成 FP16 的 GGUF python3 convert_hf_to_gguf.py ./Qwen2.5-7B-Instruct --outfile qwen2.5-7b-f16.gguf # 把 FP16 GGUF 量化成 q4_K_M ./build/bin/llama-quantize qwen2.5-7b-f16.gguf qwen2.5-7b-q4_K_M.gguf q4_K_M第三步,起服务。llama.cpp 的llama-server同样暴露 OpenAI 兼容 API,参数比 Ollama 细很多:
./build/bin/llama-server \ -m ./qwen2.5-7b-q4_K_M.gguf \ -c 8192 \ --host 0.0.0.0 \ --port 8080 \ --parallel 4 \ --no-webui解释几个重要参数:-c是上下文窗口长度,端侧建议先 4096 跑通再上调;--parallel是同时服务的请求数,Agent 场景一般 2~4 就够,开太多会互相抢内存;--no-webui是关闭自带网页界面,纯 API 输出,适合生产环境。
4.4 引擎选型对比表
| 维度 | Ollama | llama.cpp | MLC-LLM / ONNX Runtime |
|---|---|---|---|
| 上手成本 | 极低 | 中 | 高 |
| 嵌入式集成 | 独立服务为主 | 可作库嵌入 | 专为移动端设计 |
| 可控性 | 低 | 高 | 极高 |
| 移动端生态 | 弱 | 中 | 强(Android/iOS) |
| 适用场景 | 快速验证/内部工具/边端盒子 | 生产服务/边缘盒/自定义调优 | 手机端原生产品 |
选型逻辑就一句话:项目纵深越大,越值得往 llama.cpp 或者 MLC 走。只做验证和原型,Ollama 完全够用。
5. 让端侧模型真正具备 Agent 能力:工具调用、结构化输出与记忆管理
5.1 端侧模型的工具调用:别迷信"原生支持"
模型说"支持 function calling"是一回事,模型在端侧量化后表现稳定是另一回事。
Qwen2.5 系列的原生工具调用能力在开源模型里属于第一梯队。Ollama 里可以直接把工具定义传给/v1/chat/completions接口,模型会在需要时返回一个包含函数名和参数的结构化请求。下面这个例子是让端侧模型决定"要调用哪个本地命令":
import requests def call_llm_with_tools(messages, tools): resp = requests.post( "http://localhost:11434/v1/chat/completions", json={ "model": "qwen2.5:3b-instruct-q4_K_M", "messages": messages, "tools": tools, "temperature": 0.3, }, ) return resp.json() tools = [ { "type": "function", "function": { "name": "search_local_notes", "description": "在本地笔记库中检索相关文档", "parameters": { "type": "object", "properties": { "query": { "type": "string", "description": "检索关键词" }, "top_k": { "type": "integer", "description": "返回条数" } }, "required": ["query"] } } } ]但这里有几个容易踩的坑:
坑一:工具数量别贪多。端侧模型对工具定义数量的包容度比云端小很多。实测 Qwen2.5-3B 在 5 个工具以内表现稳定,增加到 10 个以上时,参数混淆和忘记工具名的情况明显增多。工具定义应该精简,能合并就合并。
坑二:参数用英文 key,值用中文没关系。模型对英文的query、top_k这类字段名理解更稳,但对中文值很友好。反过来,如果你把字段名定义成中文,部分模型会直接翻车。
坑三:温度要调低。Agent 场景下生成结构(工具调用/JSON)比创造更重要,温度建议设置在 0.2~0.4 之间,默认 0.7 在端侧容易生成多余文字破坏 JSON。
5.2 结构化输出 JSON 的落地细节
Agent 内部有大量的结构化数据交换,解析失败是整个链路最常见的故障点。我的经验是:不要把 JSON 输出完全交给模型的"自觉",要同时加三重保险。
第一重:系统提示词里明确要求格式。比如:
你必须严格只输出一个 JSON 对象,不要包含任何其他文字、解释或 markdown 代码块。第二重:对模型输出做容错解析。使用json.loads之前先清理输出,去掉可能包裹的```json前缀和尾随说明。
第三重:提供"失败就再生成一次"的重试回路。Agent 循环里,解析失败不是终止,而是作为错误信息回填给模型,让它重新生成正确的 JSON。
另外,如果目标是让 Agent 生成稳定 JSON,最可靠的方法不是让模型直接输出 JSON 字符串,而是让它通过工具调用返回结构化参数。因为工具调用本身的输出格式由推理引擎和模型共同保证,比裸输出 JSON 稳得多。
5.3 上下文窗口、记忆与 KV Cache 的工程权衡
端侧 Agent 的记忆管理,说白了就是在有限的上下文窗口里做取舍。
上下文窗口开得越大,KV Cache 内存占用增长,推理速度下降。实测同一个 7B q4 模型,4K 上下文时每秒约 25 token,开到 16K 上下文,速度会掉到 15 token 秒左右,而且首 token 延迟也会增加。
端侧 Agent 的记忆策略我建议分三层:
- 短期记忆:保留最近几轮原始对话,控制在上下文窗口的 1/2 以内,给工具调用结果留空间。
- 长期记忆:用本地向量库存历史关键信息,对话开始前做一次检索,把 top-k 相关内容塞进上下文。这样比把全部历史塞进去省几十倍 token。
- 摘要记忆:当上下文快满时,让端侧模型自己对前面的对话做摘要,把摘要和历史轮次做替换。成本低但有效。
向量检索本身在端侧也很友好:sqlite-vec或轻量Chroma本地跑bge-small-zh-v1.5这类嵌入模型完全没问题,知识库化的 Agent 也就是这么搭起来的。
6. 端侧知识库 Agent 实战:一个可以照抄的完整案例
6.1 方案架构与硬件
这是我在一台 Jetson Orin NX 16GB 开发板上跑通的案例,目标是做一个完全离线、针对个人技术笔记的问答 Agent:用户提问,Agent 检索本地笔记,让模型基于检索结果作答,并能在多轮对话里保持上下文。
硬件:Jetson Orin NX 16GB(JetPack 5.1.2),外接一块 USB SSD 存模型和向量库。 模型:Qwen2.5-7B-Instruct q4_K_M(约 4.7GB),嵌入模型 bge-small-zh-v1.5(约 120MB)。 推理引擎:llama.cpp(CUDA 后端),上下文 8192,KV Cache q8 量化。 向量库:Chroma(本地持久化),距离算法余弦相似度。
整体架构分三块:llama.cpp 起 OpenAI 兼容 API,Python 侧接openai库跑 Agent 循环,检索端用 Chroma 把笔记切片向量化后做 top-5 召回。
6.2 分步实现:嵌入、检索、生成
第一步,笔记切片入库。这一步的重点是切片大小。切太大(比如 2000 字),检索噪声高;切太小(比如 100 字),语义不完整。我实测 500 字左右重叠 50 字的效果最好。
from chromadb import Client from chromadb.utils import embedding_functions # 用本地 bge 模型做 embedding,完全离线 ef = embedding_functions.ONNXMiniLMEmbeddingFunction( model_name="bge-small-zh-v1.5", download_root="./models", ) client = Client() collection = client.get_or_create_collection(name="notes", embedding_function=ef) # 笔记切片 chunks = split_markdown("my_notes.md", chunk_size=500, overlap=50) collection.add( ids=[f"chunk_{i}" for i in range(len(chunks))], documents=chunks, metadatas=[{"source": "my_notes.md"} for _ in chunks], )第二步,Agent 推理循环。核心是:检索增强的 query 构造 + 工具调用兜底。
from openai import OpenAI client = OpenAI(base_url="http://127.0.0.1:8080/v1", api_key="not-needed") def retrieve(query): # 检索 top-5 相关笔记片段 results = collection.query(query_texts=[query], n_results=5) return "\n---\n".join(results["documents"][0]) def run_agent(user_query, history): # 先检索填充上下文 context = retrieve(user_query) messages = history + [ {"role": "system", "content": f"你是离线知识库助手,请基于以下资料回答问题:\n\n{context}"}, {"role": "user", "content": user_query}, ] for _ in range(3): # 重试上限 resp = client.chat.completions.create( model="qwen2.5-7b-instruct", messages=messages, temperature=0.3, max_tokens=1024, ) return resp.choices[0].message.content第三步,多轮记忆。每轮对话结束后,把用户输入、Agent 输出追加到 history,并检查上下文长度。如果接近 4096 token,就对最早几轮调一次摘要压缩。
6.3 真实效果与调优过程
跑通的初始版本犯了一个低级错误:把整篇笔记的全文都塞给模型做生成,6K 多字的笔记,一次retrieval没做,模型生成答案时哀嚎"内容太长记不住",输出完全不可用。改成检索增强后效果立刻有了质的提升。
实测数据(Jetson Orin NX 16GB,CUDA 后端):
- 模型加载时间:约 6 秒。
- 首 token 延迟:约 1.2 秒(带检索上下文 1500 token 左右)。
- 生成速度:约 32 token/s。
- 单轮问答总耗时:2~4 秒。
和云端方案对比,这个速度谈不上惊艳,但胜在完全离线、零调用成本,而且数据不出设备。对真正的端侧 Agent 来说,"2 秒内能给我一个靠谱的答案,而且不泄露笔记内容"比"0.5 秒但数据得传到云端"更值钱。
7. 实测性能与踩坑记录:别让理论部署死在细节上
7.1 不同设备上的实测数据
下面这组数据是我在不同设备上用同一个 Qwen2.5-7B-Instruct q4_K_M 测出来的,供做选型参考(上下文 4096,温度 0.3,batch 大小默认):
| 设备 | 推理后端 | 内存余量 | 生成速度 | 首 token 延迟 | 备注 |
|---|---|---|---|---|---|
| Jetson Orin NX 16GB | CUDA | 充足 | 30~35 tok/s | ~1s | 最省心的选择 |
| Jetson Orin Nano 8GB | CUDA | 紧张 | 12~18 tok/s | ~1.5s | 要关并行,KV 缓存降 q8 |
| RK3588 开发板 16GB | CPU | 紧张 | 4~6 tok/s | ~3s | 跑 7B 偏慢,建议换 3B |
| Mac mini M1 16GB | Metal | 充足 | 15~20 tok/s | ~1s | 内存带宽是优势 |
| 骁龙 8 Gen 3 手机(16G 版) | 混合 | 受限 | 20~30 tok/s | ~0.5s | 受发热和功耗限制大 |
一个反复出现的问题:速度测试看起来都很美好,但跑 Agent 多轮工具调用时,速度会明显劣化。原因是每轮工具调用都会把上一次的结果追加进上下文,上下文边长之后,预填充阶段的耗时与 token 数成正比,导致"越用越慢"。解决思路是定期裁剪对话历史,或用摘要压缩旧对话,把上下文维持在 2~4K 左右的舒适区间。
7.2 高频踩坑清单与解决
坑一:NPU 从未真正启用。很多开发板标称 NPU,但 llama.cpp/Ollama 默认后端根本不调它。你以为在用 NPU,其实一直在用 CPU 慢慢跑。排查方法很简单,看日志输出里的后端标识,Jetson 平台看是不是ggml_cuda,没有就说明压根没加速。如果要用 NPU,就得老老实实走 MLC-LLM 或厂商的 SDK 做适配,工程量大增。我的建议是:在 Jetson 上优先依赖 CUDA,不要指望 NPU。
坑二:上下文开太大导致"假死"。我见过有人在 14GB 内存的机器上强行开 32K 上下文跑 7B q4,结果模型加载成功了,一推理就段错误崩溃。原因是 KV Cache 内存计算错误,实际占用已经超过设备总内存。解决方法是先按 2.2 节的内存账本算一遍,再实际把-c参数降到 8192 以下跑通全程。
坑三:中文 token 效率问题被忽略。同样的中文对话,Llama 3.2 的 token 消耗可能比 Qwen 多 50% 以上。token 消耗越多,单次请求的预填充耗时越长,KV 缓存压力越大。如果你的 Agent 主要面向中文用户,选型阶段就应该把中文 token 效率纳入考量,Qwen 系在这个维度有明显的天然优势。
坑四:工具调用引发无限递归。Agent 拿到工具权限后,可能会因为一个失败的调用结果反复重试,甚至让工具调用工具,直接打满推理引擎的并发队列。给端侧 Agent 配工具时,一定要加两个保险:单次任务最大工具调用次数(我一般设为 5),以及无进展重试上限。这对安全性和资源管理都很重要,端侧设备本来就资源有限,一个失控循环足以让整机卡死。
坑五:服务进程内存只涨不降。长时间运行的 llama-server 服务,内存占用会随并发请求累积缓慢上涨。初期没注意,跑了三天后占满内存被系统 OOM。这个问题的应对就是写一个简单的内存监控脚本,超过阈值自动重启服务。作为端侧进程管家的常规操作,我还会在启动命令里加上--mlock尝试锁内存,避免被 swap 拖慢,但前提是内存确实够用。
7.3 优化方向:从跑通到跑好
如果你已经能跑通端侧 LLM,下一步优化可以从这三个方向下手。
第一,预填充和生成分开调优。llama.cpp 里--batch-size控制预填充批大小,增大它(比如 512)能显著降低首 token 延迟;生成阶段则受上下文长度和并发数影响,减少并发能提升单请求速度。
第二,模型做任务裁剪。如果 Agent 只需要做工具调用和检索问答,可以微调一个专用小模型,相比通用 7B 模型,速度和稳定性都更好。不过微调成本较高,前期先靠提示词优化兜底。
第三,混合调度。端侧模型负责简单任务和隐私敏感任务,云端大模型负责高难度的复杂推理。把两套路由逻辑写进 Agent 框架,让模型自己判断"这个问题我能不能搞定,搞不定就上云",实际体验会远超纯端侧方案。
端侧 LLM 部署这件事,坑很多,但收益也很实在:离线可用、隐私可控、成本可控。把上面这一整套流程走一遍,你基本就有了在任何一台像样的端侧设备上部署 Agent 模型的能力。至于模型选 3B 还是 7B、上 q4 还是 q5,归根结底取决于你的设备内存、响应速度需求和任务复杂度,建议都实际跑一轮再定,不要纯看参数表拍脑袋。