端侧 Agent 系列终于写到第二篇了。前一篇我们在讨论“端侧 Agent 整体架构”的时候很多人第一反应就是架构再漂亮模型跑不动等于零。而真正动手部署端侧 LLM 时你会发现问题不是“跑不跑得动”而是“怎么在很有限的资源里让模型愿意守规矩地当一个 Agent”——模型要会调工具、吐结构化 JSON、记住多轮上下文而不是单纯能聊天。这篇我就从 Agent 的实际需求出发把端侧 LLM 部署从硬件选型、模型压缩、推理框架到最终跑通一个工具调用循环整个链路都拆开讲一遍适合手里有 RK3588、Jetson Orin 或者树莓派想做端侧 AI 产品的工程师参考。1.1 为什么端侧部署的难点不是“跑得动”而是“当 Agent 用”先摆一个多数人踩过的误区把“模型能在板子上生成文字”等同于“模型部署完成”。在纯聊天场景下哪怕 7B 模型只有 10 token/s很多用户也能凑合着用。但 Agent 场景完全不同它要求模型在几轮消息中完成意图理解、工具选型、参数填槽、结果归纳每一步都依赖工具调用和上下文记忆。Context 和 Tool Calling 这两个能力是端侧 Agent 对模型部署提出的两个硬指标。上下文长短决定 Agent 能“记住”多少环境信息与中间状态工具调用质量决定 Agent 能不能把你的传感器读数、数据库查询、机械臂动作真正解释为正确的操作。所以你在做端侧部署第一步就必须想清楚计划跑多大的模型以适配目标板子的内存带宽这个模型是否支持 tool calling不是所有模型都有可靠的函数调用能力选用的推理框架是否支持 JSON Mode、Grammar 约束这些功能。很多开源模型在服务器上跑得好好的一放到端侧就暴露了工具调用格式混乱、输出夹杂废话的问题。这不是模型本身差而是你没给它设置足够强的输出约束。而好的端侧部署从选模型那一刻起就在为“让模型成为一个守规矩的 Agent”做准备。1.2 端侧 LLM 部署的四个核心约束体积、内存、带宽、功耗端侧部署和云上部署最大的区别是资源不是“可扩展”的。你没法临时加一张 A100唯一能做的就是在一个限定好的资源盒子里精打细算。我的经验里约束条件按优先级排序是这样的模型体积决定能不能放进设备可用存储和内存峰值内存权重加 KV Cache 加临时推理中间态必须在物理内存以内内存带宽决定每秒钟能喂多少 token 给计算单元直接影响生成速度功耗与散热长时间推理发热严重端侧设备很容易因为温度降频甚至直接死机。这四个约束之间还会互相打架。例如一个 7B 模型用 Q4 量化后权重约 4.4GB看起来放进 8GB 内存的开发板没问题但一旦你想把上下文拉到 32KKV Cache 就会多占 2GB 以上系统再占掉 1GB内存直接爆掉。所以说做端侧 LLM 部署不是一个“下载个模型跑起来”的事情而是在一开始就要把内存账、带宽账、上下文预算算得清清楚楚。2. 硬件平台选型先算内存带宽重点放在推理速度上2.1 主流端侧平台的真实差异对比现在做端侧 LLM绕不开的几个平台是 Jetson Orin 系列、RK3588、树莓派 5以及 Apple Silicon 这一类自带统一内存的电脑。它们之间的真实差异不能只看 TOPS还要看内存带宽和生态成熟度。平台内存 / 带宽NPU/GPU 算力常见可跑模型规模部署特征Jetson Orin Nano Super8GB LPDDR5约 102GB/sGPU 102 TOPS稀疏3B–7B Q4CUDA 生态llama.cpp/Ollama 支持好Jetson Orin NX16GB LPDDR5约 204GB/sGPU 100 TOPS稀疏7B–14B Q4可以跑更大模型但整机成本高RK3588最高 32GB LPDDR4x约 68GB/s6 TOPS NPU1.5B–3B Q4低功耗NPU 路线要专用工具链树莓派 58/16GB LPDDR4x约 23GB/s无独立 NPU0.5B–1.5B Q4适合验证思路不适合产品级 AgentApple Silicon 丐版 M 系列8–16GB 统一内存约 100–200GB/sGPU 强7B–13B Q4Metal 后端成熟开发调试非常方便一张表看下来很多人会盯着算力选型但我要提醒一句LLM 推理是“访存密集型”任务模型权重要在每个 token 生成时整个走一遍内存所以内存带宽比算力更接近硬瓶颈。比如树莓派 5 跑 1.5B Q4理论上内存带宽只有 23GB/s模型体积 1GB也就每秒 20 个 token 上下实际还会打折这就是为什么在树莓派上跑大模型体验很差。2.2 用带宽粗略估算推理速度这条经验我在实际项目里反复验证过非常实用。推理速度的最高上限 ≈ 内存带宽 ÷ 模型体积。举个实际例子Jetson Orin Nano Super 的带宽是 102GB/s加载一份 4.4GB 的 7B Q4 模型最高也就是每秒 23 个 token 的生成上限再算上解码重叠损耗实际能跑到 12–18 token/s 就算不错。同样是这台板子换 2GB 的 3B Q4 模型理论上限就有 50 token/s 以上体感明显流畅一个量级。这也是为什么很多端侧 Agent 产品最终选择 3B 而不是 7B。Agent 场景对首包响应延迟和连续多轮调用的稳定性要求很高10 token/s 的回复速度会让人的使用耐心急剧下降。如果你不是非用 7B 不可3B 加充分优化的工具调用约束用户体验往往比硬上 7B 更好。2.3 NPU 不一定是最优选RK3588 自带 6 TOPS 的 NPU听起来很香但实际走一遍你就会发现NPU 能支持的模型格式和算子非常特定官方 RKLLM 工具链目前对模型版本、量化格式的要求都很严格不是随便拿一个 Hugging Face 模型就能直接塞进去。很多团队在 RK3588 上反复调 NPU 推理最后反而退回 CPU 跑小模型。我的建议是开案之前先确认工具链现状。如果你要做高能效比的量产设备且模型范围比较固定RK3588 的 NPU 路线值得投入因为一旦调通功耗和速度都很漂亮如果只是想快速验证 Agent 产品逻辑先用 CPU llama.cpp 在板子上跑通再去考虑 NPU 迁移这是投入产出比最高的路径。3. 模型压缩与量化选型模型大小和任务能力要打配合3.1 量化的基本原理从 FP16 到 INT4/INT8在端侧部署里模型压缩的核心手段就是量化。通俗说量化就是把模型参数从高精度浮点数压缩到低精度整数。一个 7B 参数的模型FP16 格式占 14GBINT8 大约 7GBINT4 大约 3.5–4.4GB体积缩了近四倍。但量化不是无损的。尤其是很多 Agent 任务里需要复杂的数学计算、多步推理、长 JSON 输出过度量化会让模型输出质量明显下滑。我通常建议端侧 Agent 场景用 Q4_K_M 或 Q5_K_M 这一档体积和质量的平衡点最好Q8 虽然质量更稳但体积偏大对带宽不划算Q2/Q3 基本不建议工具调用能力会明显衰减。3.2 GGUF 格式到底是什么说到端侧部署一定会碰到 GGUF 这个词。它是 llama.cpp 生态定义的模型存储格式把模型权重、分词器、特殊 token、超参信息都打包在一个文件里还支持在文件头做不同精度的张量化。GGUF 的核心价值不在于它是“一种格式”而在于它让端侧部署可以按内存情况灵活加载不同量化档位的同一个模型。实际使用 GGUF 时有几个容易踩的坑同一个模型会有 x.k_quant、x.exl2、x.awq 等不同衍生版本先从官方 GGUF 版本开始下载后先看文件大小确认是否与量化档位匹配避免下错大文件下载中断可能造成模型文件损坏最好校验 sha256。3.3 KV Cache 的账上下文长度决定一半内存占用Agent 场景对上下文的要求很高因为多轮工具调用会把大量历史消息、系统提示和环境数据都塞进上下文。很多人选模型时只算了权重体积忽略了 KV Cache 占用。KV Cache 本质上就是 Transformer 在推理过程中不断缓存的键值状态它让模型不需要每次重算前面的 token但也意味着上下文越长缓存越大。一个 7B 模型的 KV Cache大约每 token 占用几十到上百 KB具体大小要看层数、注意力头数、是否分组查询注意力。跑 4096 上下文大概占 300–500MB跑 32K 则要 2GB 以上对端侧设备是绝对不可忽视的量级。所以部署前必须把内存预算这样做总内存需求 ≈ 模型权重大小 KV Cache 推理运行时开销 系统占用。举个例子8GB 的 Jetson Orin Nano Super系统加 CUDA 上下文约占 1.5GB7B Q4 权重约 4.4GB剩 2GB 左右。这时 4096 的 KV Cache 已经算顶格了想跑 32K 就必须换更小的模型或降上下文。这也是我们在端侧 Agent 里经常采用“本地小上下文 外部短期记忆”方案的根本原因。3.4 Token 视角下的注意力机制Key、Query、Value做端侧 Agent 部署时我会经常想到“token 三个点”Key 代表“我是谁”Query 代表“我在找什么”Value 代表“我能提供什么”。这本来是大模型注意力机制里最基础的原理但在 Agent 场景里它其实可以理解成一种设计哲学。端侧 Agent 的每一条输入 token 都应该明确自己的角色系统指令定义 Key 和 Value 的边界用户传来的目标构成 Query工具返回的内容则是一批新的 Value。想清楚这个你就知道为什么端侧 Agent 的系统提示词不能随便写为什么工具描述不能含糊因为模型在生成时就是靠 Key 去匹配 Query、再提取 Value 的。部署优化做到最后往往不是榨干算力而是把信息组织得更清晰。4. 推理框架与运行方案从 llama.cpp 到平台级工具链4.1 llama.cpp 与 Ollama通用生态的中坚力量目前端侧 LLM 部署最常见的基础设施就是 llama.cpp。它最大的优势是跨平台、支持 CPU/CUDA/Metal/Vulkan 等后端而且自带了 llama-server 这个 OpenAI 兼容 API 服务端非常契合 Agent 开发。Ollama 则是把 llama.cpp 包了一层更友好的使用界面一条命令就能拉模型、起服务适合快速验证。但我自己的项目里一旦要接入复杂的 JSON Schema、调并发参数、做精细的内存控制还是会回到 llama.cpp 原生部署因为可调参数更直接依赖更少。4.2 平台级工具链RKLLM 与 ONNX Runtime如果你要在 RK3588 上充分使用 NPU就绕不开 RKLLM 工具链交互入口是一个叫 RKLLM 的 SDK可以在板载的 NPU 上运行量化后的模型。它对模型型号有要求需要先在 PC 端做模型转换转换工具链对 Python 环境和依赖版本比较敏感新手在这一步特别容易卡住。ONNX Runtime 则更像一个“中间层”它能把模型转成 ONNX 格式在不同设备上调用不同执行后端适合做工程化部署。但 ONNX Runtime 的生态对生成式 LLM 的支持不如 llama.cpp 那么“开箱即用”如果是做 Agent 产品我更推荐走 llama.cpp 先把流程跑通。4.3 选框架时容易被忽略的四个能力点很多人选框架只看“能不能推理”忽略了 Agent 开发最依赖的周边能力。我总结过以下四个必须关注的点OpenAI 兼容接口能不能直接接入现有的 Agent SDKJSON Schema 约束能不能强制模型输出合法 JSON上下文管理能不能动态拼 prompt、控制最大上下文并发服务能力能不能同时服务多个 Agent 通道。llama.cpp 的 llama-server 对以上四点覆盖得都不错单进程多并发、工具调用、JSON 输出约束都支持所以如果你不想折腾太多用它做端侧 Agent 底座是个省心的选择。5. 实操在 Jetson Orin 上跑通一个带工具调用的端侧 Agent5.1 环境准备与编译 llama.cpp我以 Jetson Orin Nano Super 为例系统装的是 Ubuntu 20.04。先安装基础依赖再编译 llama.cpp 并启用 CUDA 后端。sudo apt update sudo apt install -y git cmake build-essential git clone https://github.com/ggml-org/llama.cpp cd llama.cpp cmake -B build -DGGML_CUDAON -DCMAKE_CUDA_ARCHITECTURES87 cmake --build build --config Release -j 4这里的CMAKE_CUDA_ARCHITECTURES87很关键。Jetson Orin 是 Ampere 架构Compute Capability 为 8.7如果你用的是 Xavier应该改成 7.2。不指定架构编译出来的内核可能跑不起来或者性能很糟糕。5.2 下载并启动模型服务模型我建议先用 Qwen2.5 3B Instruct 的 GGUF 版本体积小工具调用能力在端侧模型里算很稳的适合先把链路跑通。# 示例下载 3B Q4_K_M 模型文件 wget https://huggingface.co/Qwen/Qwen2.5-3B-Instruct-GGUF/resolve/main/qwen2.5-3b-instruct-q4_k_m.gguf # 启动 llama-server暴露 8080 端口 ./build/bin/llama-server \ -m qwen2.5-3b-instruct-q4_k_m.gguf \ --host 0.0.0.0 \ --port 8080 \ -c 8192 \ --jinja \ --parallel 2--jinja是让服务端使用模型自带的 Chat Template对 Qwen 这类需要特殊模板的模型很关键。启动后可以用curl http://localhost:8080/v1/models验证服务是否正常。5.3 Agent 工具调用循环的编写服务起来之后就可以在 Python 侧写 Agent 主循环了。核心流程是把用户意图发给模型同时注册工具描述模型返回需要调用的工具名和参数Agent 执行工具把结果回传给模型继续生成。下面是简化版示例。import requests API_URL http://localhost:8080/v1/chat/completions tools [ { type: function, function: { name: get_room_temperature, description: 读取某个房间的当前温度, parameters: { type: object, properties: { room: {type: string, description: 房间名例如 living_room} }, required: [room] } } } ] messages [ {role: system, content: 你是家庭环境控制助手根据用户问题调用可用工具并给出简洁回答。}, {role: user, content: 客厅现在多少度} ] resp requests.post(API_URL, json{ model: qwen2.5-3b, messages: messages, tools: tools, tool_choice: auto }).json() print(resp[choices][0][message])如果模型判断需要调用工具返回的消息里会带着tool_calls字段里面包含函数名和参数。你解析出参数、执行本地传感器查询后把结果作为role: tool的消息追加进messages再重新请求一次模型就会给出最终答案。这套循环就是端侧 Agent 最基本的“计划-调用-总结”骨架。5.4 用 JSON Schema 进一步约束输出工具调用虽然能返回结构化参数但面对一些不标准的用户输入模型可能多出废话或者参数格式不完整。这时可以强制模型走 JSON 输出模式。resp requests.post(API_URL, json{ model: qwen2.5-3b, messages: messages, response_format: { type: json_object, schema: { type: object, properties: { action: {type: string}, params: {type: object} }, required: [action, params] } } }).json()有了 schema 约束模型生成的 JSON 就会稳定很多。端侧 Agent 在低算力环境下特别需要这种“强约束少犯错”的设计思路因为每纠正一次输出就多消耗一轮上下文和好几秒时间。5.5 把并发放进 Agent 编排端侧设备的硬件资源有限很多人问“一个 Agent 怎么扛并发”。我的经验是不要按云端思路做高并发而是用有限的--parallel参数开两到三个推理槽位再在上层用 Agent 调度器把请求排成队列。并发过高会导致每个请求的首 token 延迟急剧恶化反而给用户的体感更差。宁可排队也要保证每个请求的生成速度稳定。6. 常见问题与排查实录6.1 现场排查对照表这部分直接把我在真实部署中遇到的典型问题列出来方便你对着查。现象直接原因解决方式模型服务启动后提示 CUDA out of memory权重 KV Cache 超内存降上下文长度或换更小量化/更小模型编译后的 llama.cpp 运行报 no kernel image availableCUDA 架构没配对按设备 Compute Capability 设置 CMAKE_CUDA_ARCHITECTURES首次请求很慢后续正常模型权重还没完全进显存/热点缓存启动后先预热请求或调整 mmap 行为模型输出大量非 JSON 内容缺少 JSON Schema 约束打开response_format或 grammar工具调用返回参数缺字段Chat Template 未启用启动参数加--jinjaRK3588 NPU 转换失败模型版本不被工具链支持换官方支持列表内的模型版本或暂时 CPU 推理长对话跑着跑着掉速严重上下文接近上限KV Cache 翻涌做消息裁剪或摘要压缩6.2 Agent 两个典型 Bug循环调用与格式漂移第一个是“无限工具调用循环”。模型调用完一个工具拿着错误结果再次调同一个工具反复空转。根因往往是工具返回结果不满足模型预期或者系统提示里没有“结果无效时主动告知用户”这类兜底指令。我会在循环里加一个最大迭代次数的保险丝比如同一轮任务最多调 5 次工具超时就转成普通文本回复。第二个是“格式漂移”。模型刚开始输出标准 JSON几轮对话之后就出现多余注释或截断。这种问题在长上下文中更常出现除了靠 schema 约束还可以审视上下文里是否混入太多历史脏数据。端侧 Agent 对上下文整洁度的要求非常高我一般会在每轮工具执行后将超大返回结果做压缩摘要而不是把原始结果原封不动塞回上下文。6.3 运行时还有哪些“隐形杀手”散热。Jetson 长时间跑模型会触发热保护频率下降生成速度从 20 token/s 掉到 8 token/s 很常见。量产设备必须测持续推理温度。存储。GGUF 模型在存储空间不足的文件系统上会表现异常比如file not found或训练中途闪退先df -h检查空间。功耗。端侧 Agent 不只是在推理时耗电传感器、网络和工具执行也在耗电。整机续航必须按“模型运行 环境采集”综合计算不能只看模型功耗。7. 端侧 Agent 部署之外的长远考虑7.1 本地知识库与记忆扩展端侧 Agent 要真正好用不能只靠单次模型推理还要有“记忆”。现在比较好看的做法是本地向量库加轻量 embedding 模型把工具执行结果、用户偏好、环境历史都存成向量每次进入 Agent 循环前检索最相关的几条拼接进提示词。这比一味加长上下文更节能也更可控。长上下文会成倍吃掉内存和带宽而检索式记忆只消耗固定的拼装成本。在端侧部署里这种“分层记忆”策略几乎是必选题。7.2 从 demo 到量产的路径差异很多人可能在开发板上几天就调通了演示版本但往量产走就会碰壁。量产要考虑模型固化、启动时间、差分升级、异常恢复。我的建议是两种路线择优如果设备配置较高且需要灵活换模型用 llama.cpp 这类通用运行时如果设备量很大且模型固定就该评估 RKLLM/NPU 推理方案把延迟和功耗压到极限。当然这条路刚走出第一步后续其实还能延伸出很多分支比如把视觉模型和语言模型在端侧组合成多模态 Agent比如在 Agent 循环里加入更完善的沙箱安全机制。关键是要先打好端侧 LLM 部署这个地基模型选得合适、内存算得明白、推理框架用得顺手后面一切 Agent 能力才有机会在设备上真正落地。