端侧LLM部署实战:从模型量化到Agent工具调用的完整链路
1. 端侧 Agent 为什么必须过 LLM 部署这一关上一篇文章聊了端侧 Agent 的整体架构也就是感知-决策-行动的闭环怎么在边缘设备上跑通。这一篇讲一个更现实的环节模型选好了、Agent 框架也定了怎么把大模型本身部署到端侧设备上让它在没有公网、没有线电、算力还紧巴巴的环境里老实干活。很多人对部署的理解停留在把模型下下来。实际上端侧 LLM 部署是一个从模型选择、格式转换、量化压缩、推理引擎适配到工具调用接口封装的完整链路。任何一个环节掉链子Agent 就废了。我见过不少团队在服务端跑 LLM 跑得挺顺一到 Jetson、RK3588 这类设备上就翻车原因不外乎三个模型选得过大、格式没选对、推理框架和硬件不匹配。这篇文章的目标读者有两类一类是做端侧 AI 硬件产品、想把 Agent 能力装进设备的工程师另一类是个人开发者想在笔记本、迷你主机或者开发板上跑通一个本地 Agent。无论哪种本质问题都是一样的——如何在有限的算力和内存里把模型跑出够用的速度和够稳的行为。所谓够用我的经验是端侧 Agent 任务生成速度能到 10 token/s 以上单轮延迟在 3 秒以内基本就能形成可用的交互感。再慢用户就等不下去了再快就要加钱换硬件了。部署的目标就是在这个区间里抠出最优解。2. 硬件底牌与模型选型先把家底盘清楚2.1 端侧硬件的算力坐标聊部署之前先得弄清手头的硬件是什么水平。端侧设备五花八门从手机到开发板到迷你工控机算力差异极大。但只要抓住两个关键指标就能判断它能跑多大模型内存容量和内存带宽。内存容量决定模型能不能装下内存带宽决定生成速度的上限。为什么这么说因为 LLM 推理是极度依赖权重复用的每生成一个 token都需要把模型的全部权重至少是参与计算的那部分从内存搬到计算单元。这个过程受内存带宽限制而不是纯算力。这也是为什么同样的 GPU显存带宽高的跑 LLM 更快。我给出一个粗糙但实用的估算公式理论最大速度 ≈ 内存带宽 ÷ 模型权重大小比如 Jetson Orin NX 16GB 版本内存带宽约 100GB/s如果跑一个 7B 参数的模型量化到 4bit 后权重约为 4.5GB那么理论最高约 22 token/s。实测能跑到 15-18 token/s 就不错了因为还有 KV Cache、系统开销和推理框架的损耗。如果你选的是 13B 模型同样 4bit 量化后权重约 8GB速度直接砍半这还没有算内存装不装得下的问题。2.2 关键约束内存带宽决定体验下限我用一个生活化类比解释一下内存带宽就像水管口径模型权重就像要搬运的水。口径不变水越多搬完一缸水的时间就越长。LLM 每生成一个字都要搬一遍全缸水所以模型越大、水管越细吐字越慢。实际选硬件时我的建议是列一张表把设备的内存容量、带宽、功耗和价格放一起看硬件平台内存容量内存带宽适合部署的模型规模实测参考速度Jetson Orin NX 16GB16GB约100GB/s7B-8B Q4量化15-20 token/sJetson Orin 64GB64GB约200GB/s13B-14B Q4量化20-30 token/sRK3588 开发板8-32GB约17-25GB/s3B-4B Q4量化5-12 token/s苹果 M 系列芯片16-64GB 统一内存100-400GB/s7B-32B 量化20-60 token/s普通 PC 消费级 GPU8-24GB 显存300GB/s以上7B-13B 量化30-80 token/s注意上面是跑得动和跑多快的关系还没提能不能跑得久。端侧设备往往是电池供电功耗墙也可能卡你。Jetson Orin 满载 40W如果你做的是移动设备这功耗根本扛不住。所以部署之前先想清楚产品形态是插电的桌面设备、是电池供电的手持设备、还是开发板级别的原型机这会直接影响你能选的模型上限。2.3 模型选型的平衡点硬件盘清楚之后模型选型就简单了。我的经验法则是在设备能承载的最大模型里选一个能力与延迟平衡最好的。目前端侧能跑的主流模型无外乎这几档1B-3B 档Qwen2.5-3B、Phi-3.5-mini、SmolLM 系列。适合 RK3588、手机、树莓派级别的设备。能力偏弱但胜在速度快、内存占用低。做简单的意图识别、工具调用编排没问题复杂推理就吃力了。7B-8B 档Qwen2.5-7B、LLaMA-3.2-8B、Mistral-7B。这是端侧 Agent 的甜点区。能力明显上一个台阶工具调用稳定性也更好Jetson Orin 16GB 级别就能流畅跑起来。13B-14B 档Qwen2.5-14B、Phi-3.5-medium。需要 64GB 级别的大内存设备通常只有高端 Wind 终端、Orin 64GB 这类硬件才带得动。选定模型之后还要面对一个绕不开的问题量化。4bit 量化是目前端侧部署的默认起点Q4_K_M 这个档位在速度和精度之间最均衡。5bit 和 8bit 精度更好但内存占用和速度都要打折。我的建议是原型阶段先用 Q4_K_M 跑通再在精度和工作台上调优。因为实际部署中同一个模型 Q4 和 Q8 的差距远没有参数表里看起来那么大工具调用类任务的感知差异尤其小。3. 模型格式与转换链路从 HuggingFace 权重到能跑的引擎3.1 为什么 GGUF 成了端侧事实标准选好模型之后下一个问题是模型从 huggingface 下下来是一堆 .bin/.safetensors 文件怎么变成能部署的资源答案目前几乎只有一个GGUF 格式。GGUF 是 llama.cpp 社区推出的模型格式本质上是一种针对 CPU 和边缘设备优化的紧凑二进制格式。它把模型结构、词汇表、tokenizer 配置、元数据和权重打包在一个文件里并且天然支持分片量化。你只需要一个 GGUF 文件就能跑一个模型对文件系统的压力也小很多。为什么 GGUF 能赢因为它的设计目标就是能跑就行不需要依赖分布式框架不需要复杂的图编译甚至在 CPU 上都能跑出可用的速度。这让它成了 Ollama、llama.cpp、LM Studio 等绝大多数本地推理工具的共同底座。相比老一代的 pt、safetensors 格式GGUF 对端侧的适配几乎是无脑的。3.2 格式转换实操如果你下到的模型不是 GGUF 格式最常见的方式是用 llama.cpp 提供的转换脚本。我直接给一套我在跑的流程# 1. 克隆 llama.cpp 并安装依赖 git clone https://github.com/ggerganov/llama.cpp cd llama.cpp pip install -r requirements.txt # 2. 转换原始权重到 FP16 GGUF python convert_hf_to_gguf.py \ --outfile qwen2.5-7b-fp16.gguf \ --outtype f16 \ /path/to/Qwen2.5-7B-Instruct # 3. 量化到 4bitQ4_K_M 是通用推荐档 ./llama-quantize \ qwen2.5-7b-fp16.gguf \ qwen2.5-7b-instruct-q4_k_m.gguf \ Q4_K_M这套流程跑完之后你会得到一个几 GB 的单文件模型。这里有几个细节值得注意转换前先检查模型目录里有没有 tokenizer.model 或 tokenizer.json这个文件如果缺失GGUF 转换会失败。转换脚本对模型结构有要求的比如自定义 Attention 结构的模型可能需要改脚本。目前主流开源模型都能直接转遇到报错先查模型是否包含完整的配置文件。量化质量取决于原模型权重和量化档位不建议对已经量化的模型再量化会损失精度。如果不想手动转换另一个更偷懒的办法是直接用 Ollama 拉取现成的 GGUF 模型。Ollama 的模型库里有大量预置的 GGUF 模型一条ollama pull qwen2.5:7b就能拿到。对于个人开发者来说Ollama 的体验更友好但对于深度定制部署我还是建议自己掌握转换链路因为生产环境往往需要裁剪 tokenizer、压缩 metadata甚至做自定义量化。3.3 其他格式ONNX 与移动端引擎GGUF 不是唯一选择。在特定场景下ONNX 和移动端推理引擎也有存在价值。ONNX 是一个跨框架的中间表示格式最大的好处是可以用 ONNX Runtime 在 GPU、CPU、NPU 上统一部署。如果你的端侧设备有 NPU比如瑞芯微 RK3588 的 6 TOPS NPU就想办法把模型导出为 ONNX再用 RKNN 工具链转换成 NPU 可直接加载的格式。RK3588 上跑 YOLOv8 这类视觉模型NPU 加速效果非常明显。但 LLM 不一样——LLM 的算子非常复杂NPU 加速效果往往不如 GPU 直观很多场景下 CPU 反而更稳定。Apple 生态用 Core MLAndroid 生态用 MNN、NCNN这些都是把 LLM 塞进移动 App 的选项。但它们的共性问题是对模型结构支持不全配置成本高。我的态度是手机端做 Agent 优先走 MLC-LLM 或者 Ollama 的移动端方案除非你有专门的算子开发能力否则不要自己用 ONNX 去填坑。4. 推理框架选型与关键参数4.1 主流框架横向对比部署落地时推理框架是绕不开的一层。我的判断标准就三条硬件适配度、部署复杂度、对工具调用的支持度。按这个标准分一下主流的端侧推理框架框架适用场景优势痛点llama.cpp一切 CPU/GPU 端侧场景轻量、跨平台、GGUF 原生支持对其他模型格式支持弱需要手动编译Ollama个人开发者、快速原型一条命令拉起模型自带 API定制化能力弱内部细节黑盒MLC LLM手机端、WebGPU 端支持 TS/JS 端统一运行时配置繁琐对低配设备支持一般ONNX Runtime带 NPU 的工业设备算子全、有 NPU 加速潜力LLM 算子适配成本高vLLM / TensorRT-LLM不严格算端侧吞吐高依赖 NVIDIA 生态不适用于边缘如果你做的产品是 Jetson 工控机这类有 NVIDIA 的硬件TensorRT-LLM 值得深入研究但它的学习曲线陡峭对量化、插件的管理都比 llama.cpp 复杂。我个人的路线是快速原型先用 Ollama进入产品化阶段直接迁移到 llama.cpp 自编译版本这样能精确控制模型加载方式和内存占用。4.2 上下文长度与 KV Cache 的内存估算很多人部署 LLM 只看模型权重大小忽略了 KV Cache 才是耗内存的大头。KV Cache 是推理过程中需要缓存的历史 token 的 Key 和 Value 向量上下文越长缓存越大。一个粗估算公式KV Cache 大小 ≈ 2 × 层数 × 隐藏维度 × 上下文长度 × 2字节FP16或者1字节INT8拿 Qwen2.5-7B 举例层数 28隐藏维度 3584。如果开 8K 上下文FP16 缓存约为 2 × 28 × 3584 × 8192 × 2 ≈ 3.2GB。你看模型权重才 4.5GBKV Cache 一开快赶上权重了。这也是为什么端侧部署必须控制上下文长度——很多时候你以为 OOM 是模型太大其实是 KV Cache 爆了。我的建议是端侧 Agent 场景先用 4K-8K 上下文起步。Agent 的核心对话往往不需要超长上下文而且长上下文对模型注意力计算和缓存都有额外压力。等你会用外部记忆比如向量数据库做检索增强以后模型本身的短上下文已经足够覆盖大多数工具调用场景。4.3 核心启动参数解读无论用 llama.cpp 还是 Ollama这些参数你都躲不开--ctx-size 8192 # 上下文长度 --batch-size 512 # 预填充批次大小越大预填充越快但耗内存 --n-gpu-layers 30 # 把多少层放到 GPU 上其余留在 CPU --threads 8 # CPU 线程数不要超过物理核心数 --mlock # 锁定内存防止系统把模型权重换出 --no-mmap # 如果需要精确控制内存映射关闭 mmap --grammar file.gbnf # 约束输出格式端侧 Agent 的救命稻草这里最影响体验的参数是n-gpu-layers。Jetson 上我会把能放的层都放到 GPU 里因为 CPU 和 GPU 之间搬参数的速度是瓶颈。但注意如果 GPU 内存不够还硬塞层数会触发内存换页速度断崖式下跌。正确做法是逐步调大n-gpu-layers找到一个刚好塞满显存又不溢出的临界值。批量大小也值得注意。预填充阶段就是把整个 Prompt 吃进去的阶段batch-size 越大预填充越快但这部分峰值内存非常凶。有些设备预填充占了大量内存导致生成阶段又慢又不稳我遇到这种情况第一反应就是把 batch-size 降到 256 或 128。5. 从会说话到会干活把 LLM 变成可行动作的 Agent5.1 工具调用的三条实现路线端侧 Agent 和普通大模型聊天最大的区别是什么是工具调用。没有工具调用LLM 就只是个闲聊模型不能触发传感器、不能调 API、不能控制设备。端侧部署的后半程基本就是在和工具调用稳定性搏斗。三条路线复杂度递增第一prompt 式工具描述。把工具列表写在系统提示词里让模型输出一段 JSON然后用正则或简单解析提取参数。实现成本最低但模型经常输出不合法 JSON或者字段名搞错只能说能用稳定性欠佳。第二结构化输出约束。利用 llama.cpp 的 grammar 功能或者框架内置的 JSON schema 约束强制模型输出合法的 JSON且字段完全符合定义。我在端侧生产环境用的就是这条路线比 prompt 式可靠太多。llama.cpp 的--grammar参数可以传入 GBNF 语法文件内置的json_schema_to_gbnf转换工具可以直接把 tool 定义的 JSON Schema 转成约束语法模型吐出来的结构就一定是可解析的。第三框架级 function calling。Ollama 在/chatAPI 里支持tools参数LLaMA 3.1 之后一些模型原生支持|tool_call|特殊 token。这条路线的优点是框架和模型原生配合行为稳定缺点是对模型选择有要求不一定每个 GGUF 都带完整的工具调用能力。5.2 json_schema 约束输出的实操示例拿 Ollama 举例它的 tools 参数直接吃 JSON Schemacurl -X POST http://localhost:11434/api/chat \ -H Content-Type: application/json \ -d { model: qwen2.5:7b, messages: [{role: user, content: 帮我控制灯光亮度调到80%}], tools: [{ type: function, function: { name: set_light_brightness, description: 设置灯光亮度, parameters: { type: object, properties: { brightness: {type: integer, minimum: 0, maximum: 100} }, required: [brightness] } } }] }返回里会多一个tool_calls字段里面是模型决定的动作。你拿到这个结果之后在自己的代码里调度设备控制逻辑就行。如果是 llama.cpp 自部署我建议直接开启--jinja新版 llama.cpp 支持模型自带的 prompt 模板然后用社区推荐的 JSON 约束方式避免在 prompt 里反复强调请输出 JSON模型自己就会遵循模板生成工具调用结构。5.3 端侧 Agent 运行时架构一个能真正落地使用的端侧 Agent部署好模型只是第一步还需要把几个模块串起来调度层负责接收用户输入并将历史会话、工具定义、约束规则组装成最终 prompt。模型推理层负责加载 GGUF、执行推理、返回 token 流或工具调用结果。工具执行层把模型输出的结构化动作映射到真实设备控制调 GPIO、走 MQTT、发 HTTP 请求。结果回灌层把工具执行结果拼回上下文让模型继续决策。这四层串起来之后你得到的才不是一个能聊天的模型而是一个能根据自然语言指令操作设备的 Agent。我在 Jetso n Orin 上的实际部署经验是模型层单独起一个进程或者一个 Docker 容器用 HTTP 接口和调度层通信。这样模型崩溃不会拖垮整个 Agent 系统也方便后续做模型热切换。内存吃紧的设备上调度层和推理层分进程会多占几百兆但对于可维护性来说这很划算。6. 常见问题与排查技巧实录6.1 模型加载失败或 OOM这是端侧部署最高发的故障。排查顺序我建议是这样先看模型文件本身有没有损坏GGUF 文件跑llama-cli --model xxx.gguf直接测试如果本地命令行能跑说明文件没问题。再看内存原因——free -g看一下物理内存余量nvidia-smi看显存占用。端侧设备内存往往被系统、其他进程占掉一截你以为 16GB 可用实际能分给模型的可能只有 10GB。OOM 的应对手段按优先级排列缩小上下文长度4096 降到 2048 常常立竿见影用更小的量化档位Q4 换 Q3或者选更小的模型调整n-gpu-layers把一部分层放回 CPU关掉no-mmap启用内存映射让系统帮忙分批加载6.2 推理速度慢到无法忍受排查方向也是排列组合首先确认是不是n-gpu-layers没配好模型权重全在 CPU 上跑。Jetson 上最常见的问题就是 CPU 和 GPU 之间数据搬运卡死。其次看是不是上下文虫洞context 开得太大每一轮生成的 KV Cache 都巨大不断挤占内存带宽。再一个坑是 CPU 线程没调到位。RK3588 是大小核架构如果只用了小核心推理速度直接减半。用lscpu确认物理核心数在 llama.cpp 里把--threads设为物理核心数不是逻辑线程数并给进程设置亲和性绑定到大核。还有一个容易被忽视的问题功耗模式。开发板默认可能是节能模式CPU/GPU 频率被压着。把电源模式切到性能档比如 Jetson 的nvpmodel -m 0推理速度能提升 30% 以上。6.3 工具调用格式错乱模型输出了一堆字就是不出结构化 JSON或者 JSON 字段不对。端侧模型的能力比云端大模型差这个问题尤其常见。我的处理套路是三层模型层检查模型是否原生支持工具调用。Qwen 系列对工具调用的支持是明确训练的比通用模型稳定得多优先选这种。约束层必须启用 grammar 或 JSON schema 约束别靠 prompt 软约束。框架层把工具调用的解析失败改成重试而不是直接报错。模型这次输出了brightness: 90, mode:没闭合重试一次往往就好。我在 RK3588 上试过用 Qwen2.5-3B 做工具调用不启用约束时成功率只有六七成启用 JSON schema 约束后一下跳到九成以上。所以我对每一位做端侧 Agent 的朋友都强调约束输出不是优化项是必选项。6.4 量化后精度明显下降遇到这个问题先判断是不是量化档位选低了。实战中 Q4_K_M 和 Q8_0 在多数 Agent 任务上差别不大但如果是数学推理、代码生成这类对数值敏感的任务量化掉点会明显。此时可以考虑对关键模块单独做高精度量化或者直接换同系列更大参数的模型但保持 Q4——比如从 3B Q8 换到 7B Q4能力反而更强。还有一类精度下降是 tokenizer 问题少数 GGUF 转换后 tokenizer 出了问题模型输出带上奇怪的字符而且会影响工具调用格式。这种情况重转一次模型重点检查词汇表设置用脚本里--vocab-type参数匹配源模型的 tokenizer。6.5 排查速查表症状第一步动作后续手段加载即崩溃命令行直接跑模型文件重转模型、检查分片完整性运行中 OOM缩小 ctx-size换 Q3 量化、部分层回 CPU生成速度暴跌看 KV Cache 占用降上下文、关长对话记忆输出非 JSON开启 grammar 约束换模型、加解析重试工具字段错乱检查模型是否原生支持 function calling换 Qwen 系、简化工具定义卡在预填充阶段降低 batch-size关闭并发请求、增大内存锁发热严重被降频改功耗模式为持久性能档加散热、限制并发这张表我是在多轮部署实践中沉淀下来的不敢说覆盖所有问题但能解决八成以上的现场故障。7. 写在最后的部署心得做端侧 LLM 部署做到现在我最大的体会是瓶颈往往不在模型本身而在于系统的取舍能力。你选什么模型、用什么量化、开多长的上下文、如何约束输出每一个决策都互相牵制没有全局最优解只有针对你的设备、功耗、延迟、成本四要素的动态平衡。最后分享一个小技巧部署完成之后别急着把模型长期跑在最高档先做一个小时的稳定性压力测试。来回切功能和工具把上下文拉到接近上限看设备温度曲线和内存占用曲线。很多端侧 Agent 翻车都不是因为理论速度不够而是因为长时间运行后升温降频、内存碎片积累导致的越跑越慢、最终卡死。所以一定要给系统留出 20% 以上的内存余量给散热留出设计余量。等你把这一步也跑稳了端侧 LLM 部署这关才算真正过去。