
做端侧 Agent 这个系列上一篇我拆的是整体框架写完以后不少朋友来问同一个问题框架看懂了但第一步就卡住了——Agent 的大脑那个 LLM到底怎么放到设备本地跑起来这个问题不解决后面什么工具调用、记忆管理、多设备协同全都是空中楼阁。所以这一篇专门聊端侧 LLM 部署也就是把模型本体装进你的 Mac、Linux 小主机、树莓派或者 RK3588 开发板让它能稳定支撑 Agent 的循环决策。这篇文章适合两类人。一类是想在本地跑一个私人大模型助手、但还没找到入手路径的开发者另一类是已经用上了 Ollama 或 llama.cpp但不知道怎么把模型和 Agent 的工具调用循环真正对接起来的玩家。我会先从“为什么要部署在端侧”讲起再讲模型、推理引擎、量化等级的选型逻辑随后给一个可以直接复现的 Agent 循环示例最后把部署过程中我踩过的坑和排查思路全部倒出来。1. 端侧 LLM 部署到底在解决什么问题1.1 端侧 Agent 为什么需要本地大模型Agent 和聊天机器人最本质的区别在于它不是一个“一次问答”的被动程序而是一个循环感知输入让模型规划下一步调用工具执行动作观察结果再交给模型判断直到任务完成。这个循环每一步都要模型参与而且通常一个小任务也要在模型和工具之间往返 3 到 5 次。如果模型在云端 API 上每次往返都要把上下文重新传输一遍。延迟是叠加的成本也是叠加的。拿一个“帮我查天气并规划周末出行”的任务来说Agent 要先后完成意图识别、工具调用参数抽取、结果总结三次完整生成。云端模式下一次任务平均要消耗几千个 token遇到长上下文场景还要翻倍。对于单次体验无所谓但一旦 Agent 成为日常工具这个账单会让你非常不舒服。端侧部署的核心动机就是把“决策”这个高频动作拉回本地。本地模型不需要 70B 那么大的参数规模它只要具备稳定的指令遵循能力和工具调用能力就能扛起 Agent 的日常循环。我做个不太严谨但很形象的类比云端大模型像你花重金请的外聘专家重大事项值得找它端侧大模型像坐你隔壁的助理普通事情它顺手就办了办不了的再想办法上报。Agent 产品如果把所有琐碎事情都推给外聘专家成本和延迟都会失控。1.2 端侧部署的三个硬边界内存、带宽、功耗部署端侧 LLM 之前要先认清三个绕不开的边界内存、内存带宽和功耗。模型权重是实打实要住进内存的。一个 3B 参数的模型量化到 4bit 之后约占 2GB7B 量化后约 4GB14B 量化后约 8GB。这还不算 KV cache 和运行时开销。所以你手上设备的可用内存直接决定了能跑什么量级的模型。8GB 内存的设备跑 3B 模型比较从容16GB 内存能舒服地跑 7B14B 就建议上 32GB 的机器了。内存带宽决定的是生成速度。大模型推理本质上是反复读取权重做矩阵乘法尤其在 CPU 推理时模型权重有多大每生成一个 token 就要把所有权重读一遍。估算公式很简单理论 token 生成速度约等于“内存带宽 ÷ 模型权重大小”。举个例子一台内存带宽约 25GB/s 的入门设备跑 3B Q4 量化模型权重约 2GB理论极限就是每秒 12 到 13 个 token换成 7B Q4 模型速度会直接掉到每秒 6 个左右。而苹果 M 系列 Mac 的统一内存带宽有 100GB/s 以上所以同样跑 7B 模型体验会好很多。功耗和散热是另一个容易被忽略的问题。CPU 满载推理时耗电明显笔记本风扇狂转、开发板烫手都是常态。如果产品要长期运行更好的选择是 GPU 或 NPU 加速或者干脆选一个算力调度更优秀的推理框架。这也是为什么现在越来越多端侧 AI 硬件不管手机芯片还是嵌入式 SoC都在重点宣传 NPU 的 TOPS 指标。1.3 端侧与云端方案的真实差距诚实地说端侧方案不是来取代云端方案的两者是互补关系。我用一张表把现实差距列出来方便你做技术选型时对号入座维度端侧部署云端 API单次交互延迟毫秒级无网络波动200ms 起步受网络影响大隐私敏感数据不出设备天然安全需要经过服务商离线可用性完全离线可用断网即不可用模型能力上限通常 3B ~ 14B能力有限70B知识面和推理深度更强调用成本硬件成本加电费复用免费按 token 计费高频使用成本高模型更新维护需要自己拉模型、做版本管理厂商自助升级无感端侧真正的价值是“确定性”延迟确定、可用性确定、隐私边界确定。云端的价值是“天花板高”复杂规划、深度知识、多语言理解都更强。一个成熟的 Agent 产品大概率会走混合路线端侧模型做第一层处理遇到高置信度场景自己解决低置信度再升级到云端。后面讲实操时我也会把这个思路带进 Agent 循环里。2. 部署方案的选型模型、推理引擎与量化2.1 模型怎么选3B、7B、14B 的真实差距模型选型是端侧部署里最容易拍脑袋、也最影响体验的一步。我的建议是不要只看参数数量要重点关注两个能力指标指令遵循能力和函数调用能力。Agent 场景下模型的“听话程度”和“调用工具的准确率”比它的知识面更重要。3B 级别是目前手机和嵌入式设备的主力。代表模型有 Qwen2.5-3B-Instruct、Llama 3.2-3B、Phi-3.5-mini、Gemma-2-2B。这个规模的优势是轻、快、内存占用低但劣势也很明显复杂推理能力弱工具调用经常不稳定。比如让一个 3B 模型同时维护三个工具的调用参数它偶尔会漏参数或者编造不存在的字段。7B 到 8B 级别是端侧 Agent 的“甜点位”。代表有 Qwen2.5-7B-Instruct、Llama 3.1-8B、Mistral-7B。这个规模开始具备可靠的函数调用能力指令遵循明显变好知识量也够日常使用。代价是内存占用和推理速度16GB 内存设备才能跑得舒服。如果条件允许优先选 Qwen 系列因为它在函数调用上做过专门优化实测工具调用的成功率明显高于同级别的其他模型。14B 级别是民用端侧设备的上限了代表是 Qwen2.5-14B-Instruct。它的规划能力和角色一致性已经接近商业 API 的基础水平但内存需求大速度也比较感人。除非你用的是 32GB 内存的设备并且对延迟不敏感否则我不建议一上来就挑战 14B。经验是从 3B 或 7B 跑通整个 Agent 流程再根据瓶颈决定是否升级模型而不是反过来。2.2 推理引擎怎么选Ollama、llama.cpp、MLC-LLM模型选好以后就要选一个推理引擎把它跑起来。常见的选择是 Ollama、llama.cpp、MLC-LLM 这三类它们的定位差异其实很大。引擎运行形态量化格式适合场景上手难度Ollama守护进程 CLI OpenAI 兼容 APIGGUF快速验证、本地服务化、Agent 原型低llama.cpp原生 C/C 库可嵌入程序GGUF嵌入式开发、深度定制、资源受限设备中MLC-LLMPython 包 / 移动端 SDK编译后专用格式手机 App、NPU 加速、需要极致性能中高如果只是想把 LLM 快速跑起来然后立刻接 Agent 循环Ollama 是首选。它自带模型管理、进程常驻、OpenAI 兼容接口后面要对接 LangChain 或者自己写代码都很顺手。如果你在开发一个类似树莓派智能音箱、需要把推理逻辑嵌进 C 程序里的产品那直接用 llama.cpp 更合适因为它是 Ollama 底层的同款去掉了一层调度和封装。MLC-LLM 基于 TVM 编译优化能针对具体硬件生成优化代码尤其在移动端 GPU/NPU 上表现突出。如果你的目标平台是手机 App或者要榨干某个 NPU 芯片的算力值得花时间研究它。作为个人开发者做原型我不建议一上来就碰 MLC-LLM它的学习曲线比前两者陡不少。有一点要说清楚Ollama 的底层就是 llama.cpp所以它们推理性能本身没有本质差距。重要的不是“哪个跑得快”而是“哪个能让你更容易把事情做完”。Ollama 的 API 易用性和生态完整度让它成了大多数人的入门起点。2.3 量化等级为什么 q4_K_M 是我的默认选项模型部署绕不开量化。原始权重的精度通常是 16 位浮点一个 3B 模型要占大约 6GB7B 要占约 14GB这在端侧根本没法接受。量化就是把权重从 16bit 压缩到 8bit、4bit 甚至更低换来体积和内存占用的大幅下降代价是精度损失。GGUF 格式是目前端侧最主流的模型格式它的文件名里通常带量化等级标记。我的默认选项是q4_K_M其中 4 代表 4bitK 是 K-quant 量化方法M 是这种量化下质量与体积平衡的最好档位。4bit 量化下3B 模型约 2GB7B 约 4GB内存压力足够低同时质量损失控制在可接受范围内。内存实在紧张就用q3_K_S但模型会开始出现明显的“降智”对质量要求高、设备内存充足就用q5_K_M或q6_K它们离全精度更近速度差异不大主要差在模型文件体积。q8_0和f16我一般不推荐用于端侧 Agent体积大但智能提升有限。你真正需要关注的不只是权重大小还有 KV cache。它和上下文长度直接挂钩上下文越长内存占用越大。经验公式大概是总内存需求 ≈ 量化后权重大小 上下文长度 × 单 token KV cache 大小。一个 8GB 内存设备跑 3B Q4 模型默认 2048 上下文没问题硬拉到 8192 就可能爆内存。后面讲实操时我会具体示范如何调整。3. 实操把 LLM 跑起来并接进 Agent 循环3.1 最小部署用 Ollama 一行命令跑起 3B 模型端侧部署最顺的路径我实测下来还是 Ollama。先装运行时再拉模型最后启动服务三步就能跑通。# macOS / Linux 安装 Ollama curl -fsSL https://ollama.com/install.sh | sh # 拉取一个 3B 级别、q4_K_M 量化的指令模型 ollama pull qwen2.5:3b-instruct-q4_K_M # 启动服务通常安装后会自动启动 ollama serve # 验证模型能正常回答 ollama run qwen2.5:3b-instruct-q4_K_M 用一句话解释什么是 Agent装完以后先确认设备内存余量再决定要不要调整参数。Linux 下用free -h看内存macOS 可以用活动监视器。如果内存紧张优先考虑换更小的模型而不是硬调参数。Ollama 的默认配置偏保守很多设备其实还有性能余量可挖。我建议在启动服务前先设置几个环境变量。OLLAMA_NUM_THREADS控制推理线程数建议设为你 CPU 物理核心数或略低OLLAMA_FLASH_ATTENTION1开启 Flash Attention能显著降低长上下文时的内存占用并略微提速OLLAMA_KV_CACHE_TYPEq8_0把 KV cache 量化到 8bit进一步压缩内存。这三个是我在多数设备上都会开的组合。OLLAMA_FLASH_ATTENTION1和OLLAMA_KV_CACHE_TYPEq8_0这两个参数在短上下文中感知不强但一旦把上下文拉到 4096 以上内存占用的差别就出来了。另外如果你用的是集成显卡或独显Ollama 默认可能只跑 CPU需要手动指定OLLAMA_GPU_LAYERS把部分层扔给 GPU下方 3.4 节我会展开说。3.2 给 Agent 加上函数调用能力Agent 要干活不能只会聊天关键就是让模型能调用你预定义的函数。Ollama 从 0.3.6 版本开始支持 tools接口风格延续了 OpenAI 的 schema迁移成本很低。前提是模型本身得支持函数调用这就是为什么前面反复强调优先选 Qwen 系列。一个标准的工具定义大概是这样的{ type: function, function: { name: get_weather, description: 查询指定城市的天气用于判断穿衣建议, parameters: { type: object, properties: { city: { type: string, description: 城市名称例如北京 } }, required: [city] } } }这里有个关键细节小模型对 description 的依赖远超你的想象。模型不是通过参数名理解工具的而是通过 description 描述来推测“什么时候该用什么工具、参数怎么填”。所以每个工具的 description 一定要写得像给人类同事写备忘一样把使用场景、参数含义、单位都交代清楚。我见过很多人工具调不通不是代码问题纯粹是 description 写得太含糊。3.3 一个可运行的 Agent 循环示例下面这段代码是我在本地跑通的最小 Agent 原型逻辑非常简单把用户输入交给模型模型要么直接回复要么返回一个工具调用请求如果是工具调用就执行并把执行结果以“工具消息”的形式回传给模型让它继续决策。import json import ollama MODEL qwen2.5:3b-instruct-q4_K_M TOOLS [{ type: function, function: { name: get_weather, description: 查询指定城市的天气用于判断穿衣建议, parameters: { type: object, properties: { city: { type: string, description: 城市名称例如北京 } }, required: [city] } } }] def get_weather(city: str) - str: # 原型阶段先用模拟数据生产环境请接真实天气 API return f{city}今天晴气温 22~27 度 def agent_loop(user_input: str, max_steps: int 5) - None: messages [{role: user, content: user_input}] for step in range(max_steps): response ollama.chat( modelMODEL, messagesmessages, toolsTOOLS, options{temperature: 0.7} ) message response[message] if message.get(tool_calls): messages.append(message) for call in message[tool_calls]: fn_name call[function][name] arguments call[function][arguments] print(f- 调用工具: {fn_name}({arguments})) result globals()[fn_name](**arguments) messages.append({ role: tool, content: json.dumps(result) }) else: print(Agent:, message[content]) return if __name__ __main__: agent_loop(我在上海今天需要穿外套吗)这段代码里最核心的是“把工具执行结果当作一条新的对话消息写回 messages”这一步是 Agent 循环能够继续推进的关键。模型看到工具返回的结果后才能综合判断给出最终回答。如果你的 ollama 客户端版本比较旧arguments字段可能返回 JSON 字符串而不是字典遇到这种情况要先用json.loads解析一下再传参。顺带提醒一句示例里用globals()[fn_name]只是图方便生产环境绝对不能用。正确的做法是维护一个显式的工具白名单字典只允许调用你注册过的函数这是 Agent 安全的基础要求之一。本地 Agent 一旦具备执行能力就要在权限边界上多花心思尤其是它能调用文件操作或系统命令的时候。3.4 CPU 推理加速的几个实测有效手段端侧设备普遍没有大显存显卡多数情况下都在 CPU 上跑。CPU 推理很吃参数设置我记录过一组实测同一台 M1 Mac默认设置下跑 3B 模型大约是每秒 18 个 token开启多线程加 Flash Attention 后提升到每秒 27 个左右接近 50% 的涨幅。这里面每一步都有具体作用。第一是线程数。Ollama 默认不一定会用满所有核心手动设置OLLAMA_NUM_THREADS是最直接的收益来源。第二是 Flash Attention它把注意力计算的内存复杂度降低了一个量级特别适合长上下文场景设置方式就是前面说的环境变量。第三是 KV cache 量化OLLAMA_KV_CACHE_TYPEq8_0在牺牲极小精度的情况下大量压缩内存副作用很小但收益明显。如果设备有 GPU无论 Intel 核显、AMD 集显还是 N卡都可以尝试让 Ollama 加载部分层到 GPU。设置OLLAMA_GPU_LAYERS控制卸载到 GPU 的层数通常先试一半比如 32 层的 7B 模型先卸载 16 层然后根据显存余量上下调整。卸载到 GPU 的层数越多推理速度越快但显存不够就会直接崩掉所以要一点点试。还有一个完全免费的性能优化就是降低上下文长度。GChat 接口里有个num_ctx参数默认 2048 或 4096。很多 Agent 任务根本用不了那么长的上下文调低到 1024 甚至 512能明显减少 KV cache 占用也能稍微提速。代价是模型“记忆”变短Agent 循环里如果工具返回内容较长可能会被截断需要自己权衡。4. 部署过程中常见的坑与排查技巧4.1 报错排查速查表端侧部署的坑很多不是踩一次就能记住的。我把最常见的几类问题整理成速查表照着排查能省不少时间。现象可能原因排查思路与解决内存不足进程被杀模型权重 KV cache 超限换小模型缩短 num_ctx开启 KV cache 量化推理速度极慢CPU 线程没跑满设置 OLLAMA_NUM_THREADS检查 CPU 占用率首次加载要等很久冷启动模型从磁盘加载用 keep_alive 参数让模型常驻或启动时预热请求输出是乱码或英文不变中文模型文件损坏或终端编码问题重新 pull 模型检查终端 UTF-8 编码函数调用不生效模型直接乱答模型太小或本身不支持 tools换 Qwen2.5 或 7B 级别模型强化 system prompt调用 OpenAI 兼容接口超时默认 keep_alive 时间太短设置较大的 keep_alive 值并定期发送保活请求“进程被杀”是最常见的。很多朋友拿到设备后满怀期望直接跑 7B 模型结果运行五分钟系统就把它干掉了。排查时先看日志Linux 用dmesg | tail看是不是 OOM Killer 动手macOS 看崩溃报告。如果是优先把模型降到 3B 或者把OLLAMA_KV_CACHE_TYPE设为q8_0比盲目加 swap 更有效。4.2 模型“效果不行”的一次真实排查记录有一次我把 3B 模型跑通后兴冲冲接进 Agent结果模型经常在应该调用工具的时候直接回一句话或者调用完工具后总结得牛头不对马嘴。我一度以为是模型太小差点直接换 7B后来冷静下来逐步排查发现至少有一半问题出在“提示方式”上。第一个问题出在采样温度。我当时用的是默认温度偏高导致模型输出随机性大。把 temperature 从默认值降到 0.6 后工具调用的稳定性明显上升。第二个问题是 system prompt 缺失。Agent 场景下一定要给模型摆明身份和优先级我的写法是“你是一个运行在用户设备上的智能助手。当用户的需求可以通过工具满足时你必须先调用工具根据工具返回结果组织回答禁止在没有工具结果的情况下编造信息。”第三是上下文污染。我在调试时反复把新旧消息混在一起发给模型中间还夹着错误的工具结果模型被绕晕了。后来我在每次调试循环里只保留用户原始意图和最近两轮工具结果效果立刻改善。这个经验很重要端侧小模型的“免疫力”不如云端大模型它经不起混乱上下文的干扰。最后才轮到模型规模的问题。把同样的提示方案挪到 7B 模型上确实更加稳定但 3B 在优化提示后也勉强能用。所以我的建议是别一上来就怪模型先把自己的提示工程和工具定义做扎实再谈升级。4.3 内存与交换分区调优端侧设备的物理内存有限但有些场景确实没法马上换硬件这时系统层面的内存调优能帮你抢出一点空间。Linux 设备上我习惯设置一个 4GB 到 8GB 的 zram 或 swapfile 作为兜底防止偶发内存峰值直接杀死模型进程。macOS 上则尽量关闭不用的 App并留意活动监视器里“内存压力”是否常年处于黄色以上。模型文件放哪里也有讲究。尽量放在 SSD 或 NVMe 上不要图省事放在老旧的 SD 卡里否则每次冷加载都是煎熬加载过程中还容易因为 IO 卡顿导致校验出错。嵌入式设备如果使用 tmpfs 放模型可以加快加载但注意断电即失需要启动时重新加载。监控工具方面运行时用htop看 CPU 和内存用ollama ps查看当前驻留的模型和上下文占用这两个命令足以应对绝大多数排查场景。如果你的设备在长时间运行后内存持续增长别急着怀疑内存泄漏先看一下是不是同时驻留了多个模型。Ollama 默认会把最近用过的模型留在内存里可以通过ollama stop手动释放或者设置OLLAMA_KEEP_ALIVE来调整驻留时长。5. 从原型到产品还要跨过的门槛5.1 搭建一个可用的端侧 Agent 硬件底座原型跑通只是第一步真要把端侧 Agent 做成产品硬件选型会直接决定体验上限。我在不同设备上实测过目前最顺手的三类平台是苹果 M 系列芯片的设备RK3588 系列开发板以及带有 NPU 的手机旗舰芯片平台。苹果芯片优势在统一内存带宽跑 7B 模型依然能保持每秒 25 个 token 以上的流畅体验RK3588 的 NPU 能效比好适合做嵌入式一体机手机侧则受散热约束3B 模型是长期运行的安全线。这里有一个趋势值得关注端侧 AI 硬件正在把 NPU 作为标配未来 3B 模型的部署会越来越省电推理速度会继续提升。到那时候“端侧只适合做玩具”的刻板印象会被打破。但不管硬件多强Agent 产品的工程问题依然是决定成败的关键比如模型如何分发更新、工具运行时如何做权限隔离、多设备之间如何同步会话状态。我个人建议原型阶段先把 Ollama 跑熟产品化阶段再考虑用 llama.cpp 直接把推理引擎嵌入到自己的二进制文件里减少一个外部进程的依赖。5.2 我个人在这条路上的体会部署 LLM 这件事真正难的地方从来不是“把模型放到设备上”而是如何在有限的算力里把 Agent 的决策质量做到可用。我跑了很长时间 3B 模型 工具编排越来越确信一件事一个精心设计工具定义、严格控制提示词、清楚知道自己何时该求助云端的端侧 Agent能赢过一个提示词乱七八糟的云端大模型 Agent。在 Agent 安全上也多说一句。端侧 Agent 虽然数据不出设备隐私风险低但工具执行本身就是新的攻击面。模型可能被用户输入诱导去调用危险工具所以生产环境必须做函数白名单、参数校验和执行沙箱。这个领域的红队研究已经有一段时间了专用名词叫 Agent 投毒核心思想是通过污染记忆或工具描述让模型按照攻击者的意图行事。端侧模型因为部署在个人设备上权限边界更敏感这事真不能等产品上线再补。给还在起步期的朋友一个建议不要一上来就追求 14B 模型或者复杂框架。先拿一个 3B 模型跑通 Agent 最小闭环让模型真正调一次工具、拿一次结果、做一次总结然后你再按照前面说的调优清单逐项优化。这条路我已经替你们趟过一遍了剩下的坑基本都能在上面的速查表里找到答案。