端侧 Agent 的话题继续。上一篇聊了端侧 Agent 为什么值得跑到本地设备上这一篇直接进部署实操。先说清楚一件事这里的“部署”不是把模型文件塞进设备就完事而是把“端侧 LLM 推理引擎 工具调用能力 Agent 编排”这一整套东西真正跑起来并且跑得稳、跑得久、跑得省内存。这篇主要面向想在 Jetson Orin、Mac、树莓派或者 x86 小主机上做端侧 Agent 的开发者。你不需要是算法工程师但至少要知道 Linux 基本命令、会装依赖。文章里我会给出具体的选型思路、参数计算过程、实际启动命令以及我在各种设备上踩过的坑。内容会比较长建议收藏后按章节跳着看。1. 先把问题定义清楚端侧 LLM 部署到底在解决什么在选工具和跑命令之前值得花两分钟想清楚一个核心问题你为什么要做端侧部署如果只是好奇“能不能跑”那下载 Ollama、拉个模型、curl 一下就够了。但如果你是想做一个真正可用的端侧 Agent那么部署的目标不只是“能出字”而是要让模型在受限算力下持续稳定地完成多轮推理、工具调用和状态管理。这两件事的难度差着数量级。1.1 为什么非要端侧不可很多人的第一反应是云端 API 那么成熟为什么要折腾端侧这背后有几层现实原因。延迟是最直接的一层。端侧模型推理省去了网络请求时间7B 模型在 Jetson Orin 上跑 30 tokens/s 左右虽然比不上云端旗舰 API 的速度但少了一次 50 到 200 毫秒的“出网旅行”人机交互的体感反而更紧凑。更重要的是Agent 的多轮工具调用会频繁依赖 LLM 做决策——如果每轮都走云端延迟会累加得很难看。隐私和离线能力是第二层。医疗场景、个人助理、工业现场都要求数据不出设备。比如一个采集现场数据的 Agent把用户输入的 token 送到云端做意图识别这在合规上基本走不通。端侧 LLM 的意义不是“比云端聪明”而是在数据边界内提供一个可用的推理大脑。成本是第三层也是最容易被忽略的。端侧 Agent 的典型形态是“一个设备一个 Agent”如果用云端 API 做一轮带有工具调用的多步任务很容易消耗几千个 token跑上一个月累计成本未必比买一块 Jetson 便宜。这也是为什么很多企业的私有化部署最终都走向了“本地模型 本地服务”的路线。但端侧部署不是没有代价。它的天花板很明确内存有限、算力有限、散热有限。所以你在部署阶段做的所有取舍本质上都是在跟这“三个有限”做博弈。1.2 部署不是下载模型你需要的是整套运行时这是我见过最多的误区很多人以为“部署 LLM”等于把模型文件跑起来。实际上一个可用的端侧 Agent 运行时至少由六个部分组成。第一是模型本身也就是量化后的权重文件。第二是推理引擎负责把权重真正“跑起来”常见的有 llama.cpp、Ollama、MLC-LLM。第三是模型服务层向外提供 API让 Agent 编排层不用直接和推理引擎的底层接口打交道。第四是上下文管理器负责多轮对话的截断、压缩和记忆策略这一层在长任务 Agent 里特别重要。第五是工具调用协议让模型可以输出结构化的 function call 参数。第六是 Agent 编排层也就是决定“下一步该做什么”的循环逻辑。很多人把第一步当成了全部结果模型跑起来之后发现自己只是拿到一个高级聊天机器人离“Agent”还差着十万八千里。这篇后续的实操部分我会把这六层串起来展示。1.3 硬件选型内存定上限算力定体验功耗定形态在动手之前先判断你手上的设备适合什么规格的模型。这里有三条硬规则内存决定了你能装多大模型算力决定了你会不会等得心焦功耗和散热决定了它能不能持续工作而不降频。举个例子。树莓派 5 有 8GB 内存理论上能塞下一个 7B 的 Q4 量化模型但实际推理速度只有个位数 token/s做个玩具可以做产品你会崩溃。Jetson Orin Nano 8GB 同样是 8GB 内存但因为有 GPU 的 tensor core 加速跑 7B Q4 大概能到 15 到 25 tokens/s体验完全不一样。MacBook 的 Apple Silicon 带了大的统一内存带宽跑 7B Q4 甚至 14B Q4 都比较从容。我按不同类型设备整理了一张参考表数值范围来自我的实测和社区帖子汇总你可以根据手头预算对号入座设备类型内存适合模型规模参考速度Q4适合场景树莓派 58GB1.5B~3B5~15 tok/s原型验证、教学实验Jetson Orin Nano 8GB8GB3B~7B10~25 tok/s边缘 Agent 原型Jetson Orin 16GB16GB7B~14B20~40 tok/s产品级端侧 AgentJetson Orin NX/AGX 32GB32GB14B~32B15~30 tok/s复杂任务 AgentMacBook M2/M316GB7B~14B20~40 tok/s开发调试、本地办公x86 小主机 独显16GB7B~32B由显卡决定私有化部署选型时优先看内存再看算力。我的经验是如果内存不够塞模型一切都白搭如果内存刚好够但算力拉胯你还能靠小模型、长上下文、异步任务来补救。2. 推理引擎怎么选llama.cpp、Ollama、MLC-LLM、vLLM 横评确定硬件之后第二步是选推理引擎。这个选择决定了你能用什么格式的模型、能达到什么性能、能不能支持工具调用。市面上主流方案有四个llama.cpp、Ollama、MLC-LLM、vLLM。它们定位各不相同适合的场景也完全不一样。2.1 llama.cpp嵌入式设备的万金油llama.cpp 是最值得优先了解的引擎。它是纯 C/C 实现依赖极少编译后产物很小能在 CPU、GPU、Apple Silicon 等各类设备上跑。它对内存的利用做到了极致——通过内存映射的方式直接加载模型文件不显式一次性把全部权重读进内存这使得在低内存设备上“硬塞”大模型成为可能。llama.cpp 自带的 llama-server 提供 OpenAI 兼容 API还支持 quantized KV Cache、grammar 约束和 Function Calling。如果你要做产品化部署我强烈建议直接深入它因为它的可控性最强你可以精确指定 GPU 层数、上下文长度、并发数、采样参数不会像某些封装框架那样藏黑盒。它的缺点是上手门槛稍高需要自己编译、自己找模型文件、自己管理服务进程。但编译本身不难两条命令就能完成后面我会给完整步骤。2.2 Ollama原型验证效率最高生产环境要谨慎Ollama 的优点非常明显一条命令拉模型一条命令起服务还自动管理模型文件路径不用自己操心量化格式。它的 API 也是 OpenAI 兼容的对新版本还支持 tools 参数这意味着你可以在十分钟内跑通“对话 工具调用”的最小链路。对于刚入门的开发者Ollama 几乎是最省心的方案。但用久了你会发现它的问题并发请求时性能表现不稳定底层参数难以精细调优内置的服务管理逻辑也不够灵活。它在 Jetson 这类设备上跑倒是很方便只需要额外设置一个环境变量来启动 CUDA 版本。我的建议是原型验证用 Ollama一旦你想深入控制性能和行为就迁移到 llama.cpp。2.3 MLC-LLM 与 vLLM两条路的分化MLC-LLM 走的是 TVM 编译路线它把模型编译成特定硬件上的高质量代码对 AMD、Apple Silicon 和一些国产芯片的支持做得很好。如果你手上的硬件不是 NVIDIA 也不是 Apple或者你想压榨出最后一丁点性能MLC 值得研究。代价是学习曲线陡峭编译时间动辄半小时以上调试也不如 llama.cpp 直观。vLLM 走的是另一条路它主打高并发和吞吐优化使用 PagedAttention 技术把 KV Cache 分页管理并支持 Continuous Batching让多请求共享一次推理过程。这在云端推理里是刚需但端侧 Agent 通常是单用户场景并发量并不高所以 vLLM 的优势体现不出来。除非你在 32GB 的 Orin AGX 上做多路并发任务调度否则我不建议优先考虑。2.4 我目前版本的选型结论综合性能和易用性我的选择是日常开发用 Ollama 快速验证正式部署用 llama.cpp 的 llama-server。MLC-LLM 作为备选只在需要适配特殊硬件时启用。vLLM 暂时不考虑。这个结论不是绝对的。如果你做的是云边协同架构Orin 只负责推理云端负责 Agent 编排那么 vLLM 可能在接入层更顺手。但如果你和我一样想把 Agent 完全跑在端侧llama.cpp 的轻量和可控是最大的优势。3. 模型选型与参数计算量化、KV Cache 和 Token 速率推理引擎定了接下来是模型选型。选择标准可以用一句话概括在“能力够用”和“资源够用”之间找平衡。端侧模型不是越大越好因为模型越大量化损失越难控制推理速度越慢KV Cache 占用越大。我见过很多人直接拉一个 32B 模型塞进 Orin 32GB发现上下文一长就 OOM最后只能退回 7B。3.1 量化档位怎么理解4-bit 和 8-bit 差多少先说量化。模型权重默认是 FP16 或 BF16每个参数占 2 字节。一个 7B 模型权重部分就有约 14GB这直接劝退了大多数端侧设备。量化就是把权重从高精度压缩到低精度比如 4-bit 量化后每个参数只占 0.5 字节7B 模型权重缩到约 4GB 左右。但量化不是均匀压缩。llama.cpp 里经常看到 Q4_K_M、Q5_K_S、Q8_0 这些命名K 代表 K-quant这是它设计的一套针对不同权重分布的非均匀量化方案能对 outlier 权重做特殊保护。Q4_K_M 的 M 代表 Medium是质量和体积兼顾的版本Q4_0 是基础版体积小但损失略大Q8_0 是 8-bit 量化体积接近原始一半精度损失很小。在实践中我的经验是7B 及以下模型优先选 Q4_K_M推理速度和完善程度最好如果任务涉及数学、代码、工具调用且你发现 Q4 的模型经常“逻辑飘”试一次 Q6_K体积大个 1GB 左右但效果提升很明显。8-bit 量化在端侧通常没必要除非你内存非常充裕。3.2 KV Cache 才是内存里的隐藏大户很多人只算模型权重大小结果发现模型加载没问题但对话一长就内存吃紧。问题出在 KV Cache模型在推理过程中需要缓存历史 token 的 Key 和 Value 张量以便后续 token 计算注意力。它的体积和“输入长度”线性相关是端侧内存的重要消耗者。计算 KV Cache 有一个公式可以自己动手估算KV Cache 显存 ≈ 2 × 层数 × 上下文长度 × KV 头数 × 头维度 × 字节数 / 示例精度以 Qwen2.5-7B-Instruct 为例它大约有 28 层、8 个 KV 头、每个头维度 128。如果我们用 FP16 存储 KV Cache即每值 2 字节那么在 2048 上下文的限制下 2 × 28 × 2048 × 8 × 128 × 2 ≈ 235MB。 这个量还行。但如果上下文拉长到 8192就变成约 940MB拉到 32768直接逼近 3.75GB。这就是为什么我反复提醒不要盲目调大 --ctx-size它比模型权重更容易吃掉内存。好在新版 llama.cpp 支持 KV Cache 量化可以让 KV 从 FP16 降到 8-bit 甚至 4-bit内存减半甚至更多。如果你需要长上下文一定要开 KV Cache 量化否则 16GB 的设备跑 7B 模型上下文稍微一长就濒临 OOM。3.3 Token 速率的下限和体验阈值决定端侧 Agent 好不好用的不只是模型参数和内存还有 Token 生成速率。速率取决于硬件算力、模型规模、量化档位和上下文长度。实测下来Jetson Orin 16GB 跑 7B Q4生成速度在 20~35 tokens/s 之间Jetson Orin Nano 8GB 会掉到 15~25树莓派跑 7B 就只剩 3~8 tokens/s几乎没有实用价值。对于 Agent 场景这个速率意味着什么呢一个典型的工具调用推理模型需要输出包括思维链和参数在内的约 300~800 token。以 25 tokens/s 计算光生成就要 12 到 32 秒。如果任务需要多步工具调用累计下来可能就是一分多钟。所以单看“能不能生成”不如看“能不能在可接受时间内完成任务”。做产品的时候我通常会给自己定一个接受线交互式任务要求 30 tokens/s异步后台任务可以放宽到 10 tokens/s。低于 10用户会明显感觉卡顿。这也解释了为什么端侧 Agent 更适合后台静默任务而不是人机高频对话。4. 实操在 Jetson Orin 上跑通一个带工具调用的 Agent理论部分够多了下面直接上实操。以 Jetson Orin 系列为例完整的流程包括环境准备、模型选择、服务启动、Function Calling 配置和 Agent 循环落地。4.1 环境准备与部署方式选择先准备系统环境。如果你用的是 Orin 开发套件JetPack 6.0 以上自带 Ubuntu 环境和 CUDA 驱动这一步通常不需要额外处理。然后安装构建 llama.cpp 所需的依赖sudo apt update sudo apt install build-essential cmake git git clone https://github.com/ggml-org/llama.cpp cd llama.cpp cmake -B build -DGGML_CUDAON cmake --build build --config Release -j $(nproc)这里关键参数是-DGGML_CUDAON它让推理在 Orin 的 GPU 上执行而不是全部在内置 CPU 上慢慢算。编译大概几分钟完成后llama-server会出现在build/bin/下。如果你不想编译也可以直接用 Ollamacurl -fsSL https://ollama.com/install.sh | sh然后在运行 Ollama 前设置一个环境变量让它使用 CUDA 版本的底层库OLLAMA_LLM_LIBRARYcuda_v12 ollama serve两种方式我都用过。Ollama 更适合快速起步llama.cpp 更适合产品化控制。下面以 llama.cpp 的 llama-server 为主线继续。4.2 模型选择与拉取接下来下载模型。对于通用工具调用型 Agent我的首选是 Qwen2.5-7B-Instruct 的 GGUF 文件它原生支持 Function Calling工具格式遵循 ChatML在端侧表现很稳。如果你的设备只有 8GB 内存降级到 Qwen2.5-3B-Instruct 或 Llama-3.2-3B-Instruct。下载模型可以到 Hugging Face或者本地直接执行huggingface-cli download Qwen/Qwen2.5-7B-Instruct-GGUF qwen2.5-7b-instruct-q4_k_m.gguf --local-dir /models没有 HF CLI 的话直接用 wget 也可以。下载完先检查一下文件大小7B Q4_K_M 应该在 4GB 左右。如果文件只有 2GB说明下载不完整加载时会报错。如果你跑的是推理类任务可以试试 DeepSeek-R1-Distill-Qwen-7B 的 GGUF 版但要注意 R1 系列模型倾向输出长思考链在端侧会把上下文和生成时间消耗得飞快。工具调用频率高的 Agent我建议还是用 Qwen 这类擅长指令跟随的模型。4.3 服务启动参数逐个说明模型文件就位后启动服务./build/bin/llama-server \ --model /models/qwen2.5-7b-instruct-q4_k_m.gguf \ --host 0.0.0.0 \ --port 8080 \ --n-gpu-layers 999 \ --ctx-size 8192 \ --parallel 4这里每个参数我单独解释一下。--n-gpu-layers 999表示尽可能把模型层都放到 GPU 上如果你的设备显存不足可以逐个减少这个数让部分层留在 CPU 上但速度会明显下降。--ctx-size 8192是上下文长度8K 对大多数 Agent 任务够用。--parallel 4表示同时最多处理 4 个请求并发的 KV Cache 会按这个数翻倍内存紧张时改为 1。启动后 curl 试一下curl http://127.0.0.1:8080/v1/chat/completions \ -H Content-Type: application/json \ -d {model:local,messages:[{role:user,content:你好}]}如果返回正常 JSON说明推理链路已经通了。4.4 Function Calling让大模型真正拥有“动手能力”到这里你已经有了一台会聊天的端侧 LLM但 Agent 需要的是“会动手”。这时需要启用 Function Calling。llama-server 的 OpenAI 兼容接口支持tools参数你只要在请求里带上函数定义模型就会在需要时返回结构化调用参数而不是立刻回复普通文本。一个最简单的天气查询工具定义长这样{ type: function, function: { name: get_weather, description: 查询指定城市当天的天气情况参数 city 为中文城市名。, parameters: { type: object, properties: { city: { type: string } }, required: [city] } } }发送请求时把它放在tools字段里同时将tool_choice设为auto。模型如果认为需要查询天气返回的message会带上tool_calls字段里面是函数名和参数 JSON。你的 Agent 循环解析这个字段、执行对应函数、把结果作为role: tool的消息回填给模型模型就能继续推理。这里有一个很关键的经验函数描述和参数说明要写得非常清楚尤其是“这个函数是干什么的”和“参数取值的边界”。你写“查询指定城市的天气”模型就能正确理解你写“天气查询”模型就可能在参数里混入描述性的自然语言。用行业的话说描述决定了 query 在参数空间里的相似度描述越规范模型越不容易在意图匹配环节跑偏。4.5 一个 60 行左右的 Agent 循环现在把上面的内容串成一个小型 Agent。下面这个 Python 循环代码可以直接跑思路是把用户输入和工具列表发给模型如果返回文本就结束如果返回 tool_calls就执行工具、回填结果然后继续循环。import json import requests API_URL http://127.0.0.1:8080/v1/chat/completions def call_llm(messages, toolsNone): payload {model: local, messages: messages} if tools: payload[tools] tools resp requests.post(API_URL, jsonpayload) return resp.json()[choices][0][message] def run_agent(user_input, tools, tool_handlers, max_steps8): messages [ {role: system, content: 你是一个贴心的助理必要时使用工具完成任务。}, {role: user, content: user_input} ] for _ in range(max_steps): msg call_llm(messages, tools) if not msg.get(tool_calls): return msg[content] messages.append(msg) for tc in msg[tool_calls]: fn_name tc[function][name] fn_args json.loads(tc[function][arguments]) result tool_handlers[fn_name](**fn_args) messages.append({ role: tool, tool_call_id: tc[id], content: str(result) }) return 达到最大步数任务未完成这段代码里tool_handlers是一个 Python 字典键是工具名值是真实函数。你自己扩展时只需增加新的函数定义和对应的 handler 即可。注意每次循环都要把上一次的tool_calls消息完整回传否则模型会丢失上下文。4.6 接入更完整的 Agent 框架时要注意什么上面的循环是最小可运行版本真实产品里你还得考虑记忆管理、任务队列、错误重试和并发控制。如果你要接入较完整的 Agent 框架比如 LangGraph 或 Semantic Kernel需要确认它对“端侧模型”的假设。很多框架默认模型延迟极低、可无限制重试实际端侧模型做不到。我的建议是框架可以接但要绕开它的云端假设。例如把工具调用结果缓存起来避免同类问题反复请求模型把任务切成小块让模型一次只决策一个动作而不是甩给它一个超级复杂的 system prompt设置超时和重试上限防止模型生成不合法 JSON 时死循环。这些比选框架本身重要得多。5. 常见问题与排查实录端侧部署的坑我几乎都踩过一遍。下面这些问题如果你也遇到了对照着排查会省很多时间。5.1 内存没爆速度却骤降swap 在拖后腿表现是模型刚开始正常多轮对话后速度从 30 tokens/s 掉到个位数。排查下来往往不是算力问题而是系统开始大量使用 swap。当你把 --ctx-size 或 --parallel 调大内存占用超过物理内存后Linux 会大量触发交换推理进程反复从 swap 读数据速度自然雪崩。解决思路一是减少并行数把 --parallel 设为 1二是开 KV Cache 量化三是检查系统 swap 配置如果 swap 设得过大模型权重会被换出需要适当调低 swappiness。另外可以用free -h来实时看内存占用不要在内存剩余不足 2GB 时强行拉长上下文。5.2 输出速度还行但首字延迟高得离谱如果生成速度稳定但用户问一句要等好几秒才出第一个字问题通常出在 prompt 处理和 GPU offload 上。检查两件事确认--n-gpu-layers足够大让模型完整加载进 GPU确认长 prompt 下启用了 flash attention可以通过--flash-attn参数开启。还有一个容易被忽略的点tools 定义很长时每次请求都会把工具清单塞进 prompt模型需要先处理这些 token 才能开始回答。建议把工具的 description 写精炼避免无意义的冗长。5.3 工具调用时灵时不灵这是 Agent 场景里最恼人的问题。排查时先确认模型是否走正了 ChatML 格式。Qwen 模型对系统提示和用户消息的分隔符很敏感如果消息拼错格式工具调用的概率会显著下降。再确认采样参数。temperature 过高会让模型“发散”工具名都拼错建议工具调用场景把 temperature 固定在 0.3 以下。还有一点容易踩工具参数的类型严格性如果你定义 required 字段为 string但实际传了数字模型有时会把12345和12345搞混导致后端解析失败。解决方式是在 handler 里做宽容类型转换。5.4 单路表现稳定多路并发直接雪崩端侧设备的算力决定它不适合多路并发。--parallel 开大后内存和算力都会被瓜分反而每个请求都变慢。如果你确实需要并发访问正确做法是外层加一个任务队列把并发请求削峰转成串行执行或者用一台低配服务器做接入层把推理压力平均分到多台端侧设备。进一步说“AI Agent 怎么扛并发”这个问题在端侧架构里的答案是Agent 本体不扛并发扛并发的是接入层。端侧 Agent 适合用单实例、多任务队列的方式消化请求而不是像云端那样靠 Continuous Batching 堆吞吐。6. 一些最后想说的在整个部署过程中我最大的感受是端侧 LLM 部署与其说是“技术问题”不如说是“取舍问题”。它不会给你一台无限算力的机器而是逼你在模型能力、速度和资源占用之间反复权衡。前面提到的模型选型、量化档位、KV Cache 大小、上下文长度每一样都是一次取舍。如果你打算把这个方案产品化最后再分享一个小建议不要追求“一台设备跑所有任务”。你可以根据任务复杂度拆成两个模型——一个 0.5B 或 1.5B 的轻量模型做意图识别和粗分类另一个 7B 模型只在需要工具调用和深度推理时才被唤醒。这种“大小模型分诊”的设计往往比单纯堆一个大模型更实用也能让端侧设备跑得更从容。下一篇文章我大概率会聊聊端侧 Agent 的记忆管理和长任务编排那是在模型能稳定跑起来之后真正拉开体验差距的地方。