端侧LLM部署全攻略:从量化选型到Agent工具调用
最近在把端侧 Agent 从纸面方案真正跑到本地设备上最大的感受是端侧 LLM 部署这一关直接决定了一个 Agent 项目能不能继续往下走。端侧 Agent 不等于在手机上套一个聊天框它要的是推理、规划、工具调用全链路都跑在本地而这一切的地基就是先把一个能稳定运行的 LLM 放到有限的硬件资源里。这篇文章是“深入理解端侧 Agent”系列的第二篇我会围绕“端侧 LLM 部署”展开把模型选型、量化、推理框架、硬件适配、Agent 循环接线这几块串起来结合我在 RK3588、Jetson Orin 和普通 PC 上的实测结果给准备自己搭端侧 Agent 的朋友一套可以直接照抄的路径。如果你已经在云端跑过大模型服务再转头做端侧会明显感觉到这不是“缩小版云端部署”这么简单。云端可以开 A100 堆显存端侧只有 8GB 内存、一个 NPU 或者 CPU甚至连 CUDA 都没有。决定技术方案的往往不是“哪个框架最强”而是“这台设备到底能容纳多大模型、跑多快”。这篇文章我不会推一个万能方案只把端侧部署的决策逻辑和实操细节讲透让你在换设备、换模型时也能自己判断。1. 端侧 Agent 与端侧 LLM 部署先想清楚要解决什么问题1.1 为什么不是继续用云端 API很多人做 Agent 的第一反应是接云端大模型 API这在原型阶段确实最省事。但一旦想做成真正常驻运行的硬件产品几个问题就会冒出来第一是延迟不可控Agent 决策一次就要一两秒再叠加网络波动交互体验会很差第二是断网不可用工业巡检、车机、户外设备这种场景不可能一直在线第三是数据隐私用户的语音、照片、传感器数据传到远端很多场景过不了合规和用户信任这一关。端侧 Agent 的核心思路是让 LLM 推理至少跑在端侧同时把工具调用、状态记录、规则判断这些逻辑也放在本地。需要说明的是“端侧 Agent”不一定是 100% 端侧它也可以是混合架构——本地跑一个小模型承接高频和敏感请求云端兜底复杂推理Agent 自己根据任务难度决定走哪条路。这个分层很重要能帮你在成本和体验之间找到平衡。从部署角度看端侧 LLM 是 Agent 的“大脑”但大脑不是全部。一个完整的端侧 Agent 通常包含输入模块语音识别、视觉、文本、LLM 决策模块、工具执行模块读传感器、查本地文件、调云端业务 API、记忆模块。LLM 部署得好不好直接影响决策质量但整个系统的成败往往是工具链和消息循环有没有接对。所以我建议不要一上来就研究复杂 Agent 框架先把一个 LLM 在端侧跑通、并能稳定调用工具后面再往上加框架会顺手得多。1.2 端侧部署的真正瓶颈内存带宽与上下文很多人以为端侧部署的瓶颈是“算力不够”实际经验告诉我端侧最缺的是内存带宽而不是 TOPS。LLM 在自回归生成时每生成一个 token都需要把全部模型权重从内存读一遍所以单 token 生成速度的上限约等于内存带宽除以模型文件大小。用理论值估算一下一个 7B 模型用 Q4 量化后大约是 4GB 权重如果设备的双通道 DDR5 按 60GB/s 算理想情况下每秒最多生成约 15 个 token。跑 3B 模型约 2GB这个数字会到 30 左右。所以“7B 模型一定比 3B 强”在端侧并不成立如果你的设备带宽只有 40GB/s硬上 7B 会让 Agent 每一步都像网络卡顿一样用户根本等不住。上下文长度带来的内存开销也不能忽视。模型跑起来后KV Cache 会随着对话轮数变大。上下文从 2048 提到 8192KV Cache 在 7B 模型上可能多占几百 MB 到 1GB对只有 8GB 内存的设备来说是非常大的变动。部署时不能只看模型文件大小要把上下文长度、量化方式、运行框架的额外开销一起算进去。2. 模型选型和量化第一步不是装框架而是选模型2.1 端侧模型底盘从 1B 到 8B部署之前先定任务再选模型。如果 Agent 只需要做简单的意图判断、文本摘要、固定流程问答1B~3B 的模型完全够用如果要做多轮对话加工具调用3B~8B 才比较稳。我实测下来1.5B 模型做简单指令还可以但一旦同时给四五个工具它经常编造不存在的参数甚至输出一堆无法解析的伪 JSON3B 以上会明显改善7B 在工具调用成功率上又高一个档次代价是速度更慢。中文场景优先考虑 Qwen 系列指令跟随和工具调用能力在小模型里比较靠前英文场景可以看 Llama 3.2 1B/3B 和 Phi-3 系列要跑多模态视觉 Agent就去看 Qwen2-VL 或者 MiniCPM-V 这类带视觉编码器的模型。我这里做的不是拉踩是端侧项目里最符合“稳”这个字的组合。选择时还要看模型是否原生支持 function calling。有些模型经过指令微调后可以用“输出 JSON”的方式调用工具但原生支持工具调用的模型在格式稳定性上会高很多。Qwen2.5 系列和 Llama 3.x 系列基本都原生支持工具调用Phi-3 的调用格式则需要额外适配。这点在选型时比榜单分数更重要。2.2 量化不是可选项GGUF、AWQ 与精度取舍端侧几乎必须量化不是“可选项”。最常见的做法是用 GGUF 格式加 Q4_K_M 量化这是 llama.cpp/Ollama 生态的事实标准。Q4 能把 7B 模型的权重从约 14GBFP16压到约 4.3GB内存和加载压力都小很多质量损失在多数场景下能接受。如果对精度比较敏感可以升到 Q5_K_M体积和速度的代价都不大Q8_0 质量更接近原始权重但体积基本到 7~8GB端侧通常不划算。AWQ 和 GPTQ 是另一条路通常配合 vLLM、TensorRT-LLM 使用在 NVIDIA GPU 上表现不错但在纯 CPU、NPU 设备上支持度不如 GGUF。所以我给端侧初学者的建议是先无脑跑 Q4_K_M实测效果不行再升 Q5_K_M不要一上来追求 Q8 或者 FP16跑不动的话什么精度都白搭。如何在 Ollama 里指定量化版Ollama 标签体系里常见qwen2.5:3b默认就是 Q4 量化也可以用ollama pull qwen2.5:7b-instruct-q5_K_M这种方式拉指定 tag。如果要用 llama.cpp 手动跑从模型社区下载 GGUF 文件之后直接加载即可不需要自己转格式除非你要把 safetensors 转成 GGUF那才需要调用转换脚本。2.3 模型选型速查表我在下面列了一个“先别折腾直接用这个组合”的参考表基于常见设备和预算整理量化方式默认 Q4_K_M。场景推荐模型量化后体积内存占用参考适用设备轻量问答、意图识别Qwen2.5-1.5B约 1GB2GB 以内树莓派 5、手机、低端盒子多轮对话 小规模工具调用Qwen2.5-3B / Llama 3.2-3B约 2GB4GB 左右RK3588、8GB Jetson、手机复杂工具调用、中文 AgentQwen2.5-7B / GLM-4-9B约 4.5~6GB8GB 起步Jetson Orin、高配开发板、PC英文摘要、英语 AgentLlama 3.2-3B / Phi-3.5-mini约 2GB4GB 左右主流端侧设备视觉 文本多模态Qwen2-VL-2B / MiniCPM-V 2.6约 3~8GB8GB 起步Jetson Orin、大内存 RK3588注意“内存占用参考”不是只算模型权重还包括 KV Cache、系统、Agent 服务进程。8GB 设备跑 7B 模型最好提前把其他大进程清掉否则很容易 OOM。3. 推理框架与硬件匹配几个主流方案的对比3.1 Ollama 与 llama.cpp入门最快的两兄弟Ollama 是最适合端侧 Agent 起步的推理框架。它把模型管理、量化、服务化封装得很好安装完直接ollama pull拉模型然后ollama serve起一个 OpenAI 兼容的 HTTP 服务。Agent 程序只需要把请求发给http://127.0.0.1:11434/v1完全不用管底层加载逻辑。它的缺点是想深度调优比较难例如你想自定义采样器、做模型微调后的特殊处理还是会受限。llama.cpp 是整个 GGUF 生态的底层引擎可定制性高支持 CUDA、Vulkan、Metal 多种后端也自带llama-server服务端。如果要在 Jetson 上跑且想用 GPU我一般直接用 llama.cpp 编译一个带 CUDA 的后端比套 Ollama 更可控。缺点是所有编译、参数配置都要自己来对不太熟 CMake 的新手有门槛。我的选择逻辑是第一次验证模型效果用 Ollama五分钟跑通后续性能优化或者要上 Jetson CUDA再切到 llama.cpp。不要在原型阶段就开始做底层优化容易浪费时间。3.2 RK3588、Jetson Orin 与手机 SoC 上的差异不同硬件对部署方案的影响比框架还大。RK3588 是很多端侧盒子、开发板的首选CPU 强、内存可到 32GB但它的 NPU 在 LLM 加速上支持有限。我实测在 RK3588 上直接跑 llama.cpp 的 CPU 版3B 模型能有 8~12 token/s7B 模型掉到 3~5 token/s而且散热差了还会再降。如果你想压榨 RK3588核弹级方案是走 RKLLM 工具链把模型转成 NPU 能跑的格式但转换工具链只支持部分模型架构而且算子优化要花不少时间。Jetson Orin 系列是另一条线因为带 CUDAllama.cpp 可以直接用 GPU 跑。Orin Nano 8GB 跑 7B Q4 我见过 15~25 token/sOrin NX 16GB 可以到 25~40 token/s比 RK3588 舒服很多。缺点是价格高、功耗也偏高不是所有产品都承受得起。手机 SoC 则是另一个战场高通、联发科都有各自的 AI 加速库配合 ExecuTorch、MLC-LLM、TFLite 这类移动端框架可以做得很轻但工程复杂度显著上升适合有移动端开发资源的团队。所以我常跟朋友说选硬件之前先算一笔账——你的产品是插电还是电池供电能不能接受 15W 功耗目标用户最常卡在内存还是网络这些问题比选框架更早决定成败。3.3 API 兼容层把推理服务转成 OpenAI 风格接口无论用 Ollama 还是 llama.cpp我都建议把推理服务抽象成 OpenAI 兼容接口。这么做的好处是Agent 代码只需要写一遍以后换框架、换设备只需改base_url。Ollama 的/v1/chat/completions、llama.cpp 的llama-server的/v1/chat/completions、MLC-LLM 也提供 OpenAI 风格接口生态里已经把这个当成默认规范了。代码里最简单的连接方式是这样from openai import OpenAI client OpenAI( base_urlhttp://127.0.0.1:11434/v1, api_keyollama, # 本地服务不会校验 key传什么都行 ) resp client.chat.completions.create( modelqwen2.5:3b, messages[{role: user, content: 用一句话介绍你自己}], temperature0.3, ) print(resp.choices[0].message.content)只要把模型名和本地地址换成对应推理服务的这段代码在各种端侧设备上都能复用。这也是做 Agent 项目最重要的“可移植性”思维模型可以换、设备可以换但上层业务逻辑稳定不动。4. 实操在端侧跑通一个能自动决策的 Agent4.1 环境准备与最小的 LLM 服务我以 RK3588 开发板为例装好 Ubuntu 之后先装 Ollama。这里强调一下网上curl -fsSL https://ollama.com/install.sh | sh这种方式最省事如果你对管道执行脚本不放心可以手动从官网下载压缩包解压只是多几步环境变量配置。# 安装 Ollama curl -fsSL https://ollama.com/install.sh | sh # 拉取一个 3B 模型默认 Q4 量化 ollama pull qwen2.5:3b # 启动服务并监听局域网 OLLAMA_HOST0.0.0.0:11434 ollama serveJetson Orin 如果想用 GPU 加速更推荐直接编译 llama.cppgit clone https://github.com/ggerganov/llama.cpp cd llama.cpp cmake -B build -DGGML_CUDAON -DCMAKE_BUILD_TYPERelease cmake --build build --config Release -j $(nproc)编译完成后把 GGUF 模型放到任意目录用llama-server启动一个 OpenAI 兼容接口。命令大致是./build/bin/llama-server -m /path/to/qwen2.5-7b-instruct-q4_k_m.gguf -c 4096 --port 8080启动完成后先用 curl 验证服务是否正常。这一步能帮你把“模型加载问题”和“Agent 代码问题”彻底隔离开curl http://127.0.0.1:11434/v1/chat/completions \ -H Content-Type: application/json \ -d { model: qwen2.5:3b, messages: [{role: user, content: 你好}], stream: false }如果返回正常 JSON说明 LLM 服务已就绪接下来就可以接 Agent 逻辑了。4.2 工具调用Function Calling的完整链路Agent 和普通聊天最大的区别就是会调用工具。我通常把 Agent 决策流程拆成四步LLM 根据用户输入判断“需不需要工具” → 返回一个结构化的 tool_calls 请求 → Agent 执行对应工具并把结果回填到消息列表 → LLM 结合工具结果生成最终回答。为了让模型稳定输出“想调用哪个工具”我们需要在请求里附带tools字段。这是一个标准的 OpenAI 风格工具描述我在端侧项目里常写类似这样的定义tools [ { type: function, function: { name: get_weather, description: 查询某个城市当天的天气情况返回天气状态和温度, parameters: { type: object, properties: { city: {type: string, description: 城市名例如北京、上海} }, required: [city] } } }, { type: function, function: { name: get_current_time, description: 获取当前系统时间返回年月日和时分秒, parameters: {type: object, properties: {}} } } ]工具描述写得越明确模型就越少乱开会话。比如get_weather的 description 里写了“返回天气状态和温度”模型就能知道这个工具是拿实时信息的。如果工具描述模糊它会倾向于自己瞎编天气这是端侧 Agent 最常见的失败原因之一。4.3 全套 Agent 循环代码下面是一套可直接运行的 Agent 循环我用 requests 而不是 OpenAI SDK这样在无外网依赖的纯内网环境里也能跑也方便你看清协议细节。import json import time import requests MODEL qwen2.5:3b OLLAMA_BASE http://127.0.0.1:11434/v1 def get_current_time(): return time.ctime() def get_weather(city): # 这里只是演示正常应该查本地缓存或云端 API return {city: city, weather: 晴, temperature: 24} def run_tool(name, args): if name get_current_time: return get_current_time() if name get_weather: return get_weather(args.get(city, 未知城市)) return {error: ftool not found: {name}} def ask_agent(user_text): messages [ {role: system, content: 你是一个运行在本地设备上的助手。当用户需要实时信息时应该先调用工具获取数据不要凭空编造。工具结果可能是一个 JSON 字符串你需要把它整理成自然语言回复。}, {role: user, content: user_text}, ] for step in range(8): # 限制最大轮数防止循环失控 payload { model: MODEL, messages: messages, tools: tools, temperature: 0.3, max_tokens: 512, stream: False, } resp requests.post(f{OLLAMA_BASE}/chat/completions, jsonpayload, timeout60) resp.raise_for_status() data resp.json() msg data[choices][0][message] # 先把 assistant 消息追加进历史保证上下文连续 messages.append({ role: assistant, content: msg.get(content) or , tool_calls: msg.get(tool_calls), }) # 如果没有工具调用说明 LLM 已给出最终回答 if not msg.get(tool_calls): return msg.get(content) or # 否则逐个执行工具把结果回填 for tc in msg[tool_calls]: fn tc[function] try: args json.loads(fn.get(arguments) or {}) except json.JSONDecodeError: args {_parse_error: fn.get(arguments)} result run_tool(fn[name], args) messages.append({ role: tool, tool_call_id: tc.get(id, fstep-{step}), content: json.dumps(result, ensure_asciiFalse), }) return 已超过最大轮数请稍后再试 if __name__ __main__: print(ask_agent(现在几点了)) print(ask_agent(帮我查一下杭州的天气))这段代码踩中了我前面说的所有环节工具定义、assistant 消息回放、工具结果回填、轮数上限。唯一要注意的是“tool_call_id” 必须和模型返回的id一致否则有些服务端会拒绝消息序列。Ollama 大部分情况会给你一个 id万一没有我就用step-N兜底。4.4 参数调整对 Agent 行为的影响同样的模型、同样的工具定义参数设得不对Agent 表现会天差地别。我在端侧项目里的默认值如下你可以照着先跑再按需调整temperature 设 0.2~0.4。Agent 决策场景要可控性优先太高会让它随机乱调工具。max_tokens 单次设 256~512 就够。工具调用阶段用不到长文本输出答非所问的长篇大论反而是负担。num_ctx上下文窗口设 4096 起步最多 8192再大内存吃不住速度也掉得厉害。stream 在工具调用循环里建议关掉等拿到完整结构再处理会省很多麻烦如果是面向用户打字机效果只在最终回答阶段开启。如果模型对 function calling 支持不好可以退而求其次用 JSON mode在 system prompt 里强制要求模型只输出 JSON然后自己解析action和params。这个方法“能用但不舒服”因为你需要处理各种格式异常。所以我通常把原生 tool calling 当作第一选项JSON mode 只做 fallback。5. 端侧 Agent 部署中的经典坑与排查方法5.1 模型加载慢、OOM、卡死端侧部署最常见的坑就是内存不够。你看到 8GB 开发板以为能跑 7B 模型结果系统占 1.5GB、Agent 服务占 0.5GB、推理框架再占一堆KV Cache 一涨进程直接被 OOM Killer 干掉。我排查的第一条命令永远是free -h先看剩余内存再看是不是已经触发过 OOM。如果内存吃紧优先级是这样的先砍上下文长度从 8192 降到 4096 试试再换更激进的量化Q5 改 Q4最后才考虑换更小模型。不要一生气直接买更大内存的主板很多情况下是 KV Cache 和框架开销在作怪缩小上下文就立竿见影。模型首次加载慢也很常见。Ollama 或 llama.cpp 冷启动需要把几个 GB 的权重从磁盘读到内存如果用的是 TF 卡或机械盘会非常慢。解决方式是让服务常驻不要每请求一次就启动一次Ollama 可以设置keep_alive例如在请求里带keep_alive: 5m或者在 Evironment 里调大默认保活时间。如果首 token 延迟是产品痛点尽量用 NVMe 或者至少是高素质 eMMC。5.2 工具调用失效与循环失控我踩过最深的坑是 Agent 陷入死循环它一遍遍调用同一个工具不把工具结果当回事或者把返回的 JSON 当作要执行的指令。这种问题的根源通常是工具结果没被正确放回消息历史或者模型没有足够强的“停止能力”。解法有三个层面第一代码层面限制最大轮数我一般设 6~8 轮到顶直接返回第二prompt 层面明确“如果工具返回结果为空或报错就如实告诉用户不要重复调用”第三工具层面做结果校验例如天气工具查询失败时不要把原始异常堆栈丢给模型而应该返回一个干净的{error: 查询失败}模型更容易理解。另一个典型问题是模型串工具参数。其实就是函数名和 description 写得太差或者一个模型一次被塞了十几个工具。端侧模型没有云端那么大容量工具数量控制在 5~8 个以内名字要短、含义要唯一。工具多了正确率下降得非常快。5.3 实测速度参考与性能预期下面是几组实测参考不是基准测试只是我手头设备和社区同规格设备上的常见水平。速度会受散热、固件、量化策略影响建议拿到设备后自己在目标模型上跑一遍。设备推理引擎模型实测参考RK358832GB 内存llama.cpp CPUQwen2.5-3B Q4_K_M8~12 token/sRK358832GB 内存llama.cpp CPUQwen2.5-7B Q4_K_M3~5 token/sJetson Orin Nano 8GBllama.cpp CUDAQwen2.5-7B Q4_K_M15~25 token/sJetson Orin NX 16GBllama.cpp CUDAQwen2.5-7B Q4_K_M25~40 token/sApple M116GBOllama MetalLlama 3.2-3B Q4_K_M20~30 token/s看到这些数字你就明白为什么端侧 Agent 设计时最好把“一次决策需要的 token 数”压到最低。工具调用轮数多、系统提示词长、上下文回放过长都会显著拉慢每个请求。所以我在端侧项目里习惯于精简 system prompt把历史消息裁剪到最近几轮而不是把所有对话都塞进去。5.4 端侧 Agent 部署问题速查表症状可能原因处置方法首次启动极慢模型从磁盘冷加载服务常驻、用 SSD、调大 keep_alive进程被 kill内存不足降上下文长度、换更小模型、清理后台进程中文输出乱码英文 tokenizer 模型换 Qwen 等中文优化模型并在 system 里指定中文回复工具调用格式错模型不支持原生 function calling换支持工具调用的模型或降 temperature 后重试同一工具反复调用工具结果未回填 / prompt 缺停止指令检查 tool_call_id 和消息顺序增加轮数上限Jetson 编译报错CUDA 环境不匹配用 JetPack 统一版本再编译确认 DD 架构设置生成内容重复温度太低或上下文被污染清空历史、低温度会导致重复时适度提高到 0.55.5 影响范围哪些真实场景值得端侧 Agent 落地聊完部署技巧再回到业务视角。端侧 Agent 目前最适合三类场景一是隐私敏感型医疗、金融、办公设备上的数据处理不出终端本地模型 本地工具循环天然有优势二是弱网/无网环境工业巡检、仓储 AGV、车载语音断网也要能干活三是对延迟要求苛刻的实时交互例如语音助手和机器人本地推理省掉了往返云端的 300ms~1s。这些场景共同点是“物理世界交互”多于“纯文本生成”所以 Agent 是否好用很大程度取决于工具链和硬件配套是否完整。只部署一个 LLM 是不够的还得把麦克风、摄像头、传感器、本地数据库接好。这也是我把这篇文章重心放在“LLM 部署 工具循环”而不是“聊天机器人”的原因。端侧 Agent 的竞争点不在模型本身而在系统整合的稳定度。个人建议是先用一个成熟的小模型在目标设备上把完整链路跑通再根据真实数据决定是升模型还是优化工具。端侧部署这件事很多问题只有真机通电跑起来才能发现纸面推演再合理也不如一次free -h来得实在。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →