
1. 端侧 LLM 部署到底在解决什么问题1.1 从云端 API 到本地推理的动机转变过去两年大部分 Agent 项目都是把 LLM 推理放在云端本地只做编排和工具调用。这个模式在原型阶段非常舒服一行 API key 就能跑起来模型能力也强。但一旦进入真实场景问题就集中爆发了。第一是延迟不可控一次请求往返少则几百毫秒多则几秒Agent 的多轮循环会把这个延迟放大到用户无法接受的程度。第二是隐私和合规很多行业的数据根本不允许出本地设备。第三是成本Agent 的特点是 token 消耗量远超普通对话一个任务跑下来动辄几万 token长期算下来账单非常可观。端侧 LLM 部署就是把推理这件事从远端搬到本地设备上让模型权重和推理引擎都跑在用户手里的硬件上。它解决的核心问题可以归纳成三条低延迟的本地推理、数据不出设备、边际成本趋近于零。这三条恰好是 Agent 从 demo 走向产品的关键门槛。1.2 端侧 Agent 对 LLM 的特殊要求端侧 Agent 和单纯的端侧聊天不是一回事。聊天场景下用户能容忍一两秒的等待输出质量优先。但 Agent 场景下LLM 是决策中枢它要反复做规划、选工具、解析结果、判断是否继续循环。这意味着几个硬性要求首 token 延迟要低因为 Agent 每轮都要等模型开口才能决定下一步结构化输出要稳工具调用本质上是让模型吐出 JSON格式错了整个链路就断了上下文要能压缩Agent 的历史会越滚越长端侧显存根本扛不住无限增长推理要可中断用户点了停止或者超时得能立刻掐掉。这四点决定了端侧 LLM 部署不是简单地把模型下载下来跑起来而是要围绕 Agent 的调用模式做针对性优化。1.3 适合谁来参考这套方案如果你正在做这几类事情这篇内容会对你有直接帮助一是想把 Agent 跑在本地设备上比如工控机、边缘盒子、带 NPU 的开发板二是已经在用云端 API但被延迟或成本逼着想下沉到端侧三是做嵌入式 AI 产品需要评估某块板子到底能不能扛住某个尺寸的模型。我会尽量把选型逻辑、量化取舍、实测数据都摊开讲让你能直接抄作业也能理解每一步为什么这么做。2. 端侧 LLM 部署的整体架构与选型思路2.1 端侧推理的软件栈分层端侧 LLM 部署的软件栈大致可以分成四层从下往上依次是硬件层、推理引擎层、模型层、Agent 编排层。很多人一上来就纠结选哪个模型其实顺序反了应该先看硬件能提供什么算力再决定引擎最后才是模型。硬件层决定了你的算力上限和内存带宽。CPU 推理慢但通用GPU 推理快但功耗高NPU 能效比最好但生态碎片化严重。推理引擎层负责把模型图编译成硬件能执行的算子常见的有 llama.cpp、MNN、NCNN、TensorRT、ONNX Runtime 等。模型层就是权重本身尺寸从 0.5B 到 14B 不等。Agent 编排层则是跑在推理之上的业务逻辑负责 prompt 组装、工具调用解析、循环控制。这个分层的关键在于每一层都会对上一层形成约束。比如你选了一块只有 8GB 内存的板子那模型层最多只能上 7B 的 4bit 量化版本引擎层就得选内存占用小的方案Agent 层的上下文预算也得相应收紧。2.2 硬件平台的横向对比端侧硬件大致分几类我按实际项目里常见的几种做个对比。平台类型代表芯片内存带宽典型算力适合模型尺寸功耗桌面 GPURTX 系列高很强7B-32B高边缘 GPU 模组Jetson Orin中高强3B-13B中ARM SoC NPURK3588中中1B-7B低纯 CPUx86 服务器中弱1B-3B中手机 SoC骁龙/天玑中中1B-4B极低选型的核心不是看峰值算力而是看内存带宽。LLM 推理是典型的 memory-bound 任务每生成一个 token 都要把整个模型权重过一遍。所以带宽决定了你的 token 生成速度上限算力反而没那么关键。这也是为什么很多标称几十 TOPS 的 NPU实际跑 LLM 还不如一块带宽更高的 GPU。2.3 推理引擎的选择逻辑推理引擎的选择要匹配你的硬件和模型格式。我列几个主流方案的实际使用感受llama.cpp通用性最强CPU、CUDA、Metal、部分 NPU 后端都支持GGUF 量化格式生态成熟。适合快速验证和 CPU 场景缺点是 NPU 支持还在完善中。MNN阿里出品对 ARM 和移动端优化很好支持多种量化适合手机和 ARM 板子。TensorRT-LLMNVIDIA 平台上的性能王者但只支持自家 GPU编译流程偏重。ONNX Runtime跨平台好但 LLM 场景下的算子融合和量化支持不如专用引擎。厂商自带工具链比如某些 NPU 的官方编译器性能往往最好但绑定硬件、学习成本高。我的经验是先用 llama.cpp 把流程跑通确认模型和 prompt 没问题再针对目标硬件换专用引擎做性能优化。不要一上来就啃厂商工具链调试成本会让你怀疑人生。2.4 模型尺寸与量化的取舍模型尺寸的选择本质是质量、速度、内存三者的三角权衡。这里有个粗略的估算公式模型显存占用约等于参数量乘以每参数字节数。FP16 是 2 字节INT8 是 1 字节INT4 是 0.5 字节。一个 7B 模型FP16 要 14GBINT8 要 7GBINT4 只要 3.5GB 左右再加上 KV cache 和运行时开销。量化不是免费的午餐。INT8 通常掉点很小几乎无感INT4 在 7B 以上模型上还能接受但小模型上掉点明显再往下的 3bit、2bit 就要谨慎了Agent 场景对指令遵循要求高量化过度会让模型开始胡说八道工具调用格式直接崩掉。提示Agent 场景下我建议量化等级不要低于 INT4且优先选经过指令微调的量化版本而不是拿基座模型直接量化。3. 核心实操从零把 LLM 跑在端侧设备上3.1 环境准备与依赖安装假设我们以一块 ARM 开发板加 llama.cpp 为例这是最容易复现的组合。先确认系统信息包括架构、内存、是否有可用的加速后端。uname -m free -h cat /proc/cpuinfo | grep -i model name | head -1如果是 aarch64 架构直接源码编译 llama.cpp 是最稳的因为预编译包不一定带对应的后端。git clone https://github.com/ggerganov/llama.cpp cd llama.cpp cmake -B build -DCMAKE_BUILD_TYPERelease cmake --build build --config Release -j$(nproc)编译完成后build/bin下会有llama-cli、llama-server等可执行文件。这里有个坑如果板子支持 NEON 指令集编译时会自动开启速度提升明显但如果你的板子有 NPUllama.cpp 默认是用不上的需要额外配置后端这部分后面单独讲。3.2 模型下载与量化格式选择模型权重建议从可信来源获取优先选 GGUF 格式因为它是 llama.cpp 的原生格式元数据里已经包含了量化信息和 chat template。下载时注意区分几个关键后缀Q4_K_M4bit 量化K 表示使用了 k-quant 方法M 是中等粒度是质量和体积的平衡点我最常用这个Q5_K_M5bit质量更好体积大一些Q8_08bit接近无损但体积大Q3_K_S3bit 小粒度体积最小质量损失明显只在极端受限时用。下载命令很简单但要注意校验文件完整性断点续传后文件损坏是常见问题。# 以某个 7B 模型的 Q4_K_M 版本为例 wget -c 模型下载地址 # 校验大小和哈希 ls -lh model.gguf sha256sum model.gguf3.3 首次推理与参数调优先跑一次最基础的推理确认模型能加载、能输出。./build/bin/llama-cli \ -m model.gguf \ -p 你好请用一句话介绍你自己 \ -n 128 \ -t 4 \ --temp 0.7几个关键参数需要根据硬件调整-t线程数一般设成物理核心数超线程反而会拖慢-n生成的最大 token 数Agent 场景下要设小一点避免模型啰嗦-c上下文长度默认可能是 512Agent 场景要开到 4096 甚至 8192但会吃内存--temp温度工具调用场景建议调到 0.1 到 0.3降低随机性。实测下来一块 8 核 ARM 板子跑 7B Q4_K_M生成速度大概在 3 到 6 token/s首 token 延迟 1 到 2 秒。这个速度做聊天勉强做 Agent 就偏慢了所以要么换更小的模型要么上 NPU 加速。3.4 用 llama-server 暴露 OpenAI 兼容接口Agent 编排层通常期望一个 HTTP 接口llama.cpp 自带的llama-server正好提供 OpenAI 兼容的 API这样你的 Agent 代码几乎不用改就能从云端切到本地。./build/bin/llama-server \ -m model.gguf \ -c 4096 \ -t 4 \ --host 0.0.0.0 \ --port 8080 \ -np 2-np是并行请求数Agent 如果有并发工具调用这个值要调大但每个 slot 都会占用一份 KV cache内存要算够。启动后可以用 curl 测试curl http://localhost:8080/v1/chat/completions \ -H Content-Type: application/json \ -d { model: local, messages: [{role: user, content: 返回一个 JSON包含 name 和 age 两个字段}], temperature: 0.1 }如果返回的 JSON 格式稳定说明这个模型可以胜任 Agent 的结构化输出任务。3.5 NPU 加速的接入方式如果板子带 NPU比如 RK3588 这类纯 CPU 推理就浪费了。接入 NPU 一般有两条路一是用厂商提供的转换工具把模型转成 NPU 能吃的格式二是用支持该 NPU 后端的推理框架。转换流程通常是原始模型转 ONNX再用量化工具转成定点模型最后编译成 NPU 的二进制。这个过程坑比较多我总结几个关键点算子支持是最大障碍很多 LLM 里的自定义算子 NPU 不认需要替换或回退到 CPU量化校准集要选得贴近实际输入分布否则精度掉得厉害NPU 的驱动和固件版本要和工具链匹配版本错位会导致加载失败。建议先在厂商的示例模型上跑通全流程再换自己的模型。4. Agent 场景下的端侧 LLM 工程化要点4.1 上下文管理与 KV Cache 优化Agent 的上下文会随着工具调用不断增长端侧内存有限必须做主动管理。几个实用手段滑动窗口只保留最近 N 轮对话老的直接丢弃简单粗暴但有效摘要压缩把历史对话用模型自己总结成一段短文本替换掉原始记录KV cache 量化llama.cpp 支持把 KV cache 也量化成 8bit 或 4bit能省一半以上内存前缀缓存复用system prompt 和工具定义这些固定部分可以缓存起来避免重复计算。KV cache 的量化和权重量化是两回事很多人只量化了权重却忘了 KV cache结果上下文一长就 OOM。开启方式是在启动参数里加--cache-type-k q8_0 --cache-type-v q8_0实测内存能降 40% 左右质量影响很小。4.2 结构化输出与工具调用稳定性Agent 的工具调用依赖模型输出合法 JSON。端侧小模型在这方面天然弱于大模型需要额外手段兜底。我的做法是三层防护第一层用 grammar 约束llama.cpp 支持 GBNF 语法可以强制模型只能输出符合 JSON schema 的内容第二层做输出解析容错遇到不合法 JSON 时尝试修复比如补全括号、去掉多余文本第三层是重试机制连续失败就换更简单的 prompt 重新问。GBNF 的写法不难本质是定义一套文法规则。比如要模型输出{tool: ..., args: {...}}就写对应的规则文件启动时用--grammar-file指定。这样模型在采样阶段就被限制在合法 token 上格式错误率能降到接近零。4.3 并发与资源隔离端侧设备资源紧张Agent 如果有并发需求必须做好隔离。llama-server 的多 slot 机制是共享同一个模型实例的好处是省内存坏处是一个慢请求会拖累其他请求。如果并发量不大多 slot 够用如果要求严格隔离就得起多个进程每个进程独立加载模型但内存会成倍增长。注意端侧设备上不要盲目追求高并发先算清楚内存账。一个 7B Q4 模型加 4K 上下文的 KV cache单实例大概占 5 到 6GB两个实例就是 12GB很多板子直接爆了。4.4 监控与降级策略端侧部署必须考虑异常情况模型加载失败、推理超时、内存不足。我的做法是加一层健康检查定期 ping 推理服务连续失败就切到降级模式。降级可以是换更小的模型也可以是直接返回预设的兜底回复保证 Agent 不会整个卡死。监控指标重点看三个首 token 延迟、token 生成速度、内存占用。这三个指标一旦恶化往往预示着上下文太长或者有内存泄漏。可以在 Agent 编排层记录每次调用的耗时和 token 数积累一段时间就能看出瓶颈在哪。5. 常见问题排查与避坑经验5.1 模型加载失败与内存不足最常见的报错是加载模型时提示内存分配失败。排查顺序是先看模型文件大小和可用内存是否匹配再看是否开了太多 slot 或上下文太长。有个容易忽略的点是某些系统会把文件缓存算进可用内存实际可用内存比free显示的少。可以用--no-mmap参数禁用内存映射强制把模型读进物理内存虽然加载慢但更可控。5.2 推理速度异常缓慢速度慢的原因通常有几个线程数设错了、没开硬件加速、模型量化等级太高。先用-t调整线程数试试一般设成物理核心数。如果板子有 GPU 或 NPU确认编译时是否开启了对应后端。还有一种情况是散热降频跑久了芯片过热自动降频速度会断崖式下跌加个散热片往往能解决。5.3 输出乱码或重复输出乱码多半是 chat template 没配对。不同模型的对话格式不一样GGUF 文件里通常带了 template但如果手动指定 prompt 就可能错位。重复输出则是采样参数问题温度太低或者重复惩罚没设好。可以加--repeat-penalty 1.1缓解。5.4 工具调用格式不稳定这是 Agent 场景最头疼的问题。小模型经常把 JSON 写坏或者把工具名写错。除了前面说的 grammar 约束还可以在 prompt 里给一两个 few-shot 示例效果立竿见影。另外工具定义不要太多端侧模型一次能记住的工具数量有限超过 5 个就容易混淆建议按场景动态加载工具集。问题现象可能原因排查方向解决手段加载失败内存不足检查可用内存与模型大小降量化等级、减上下文、关 mmap速度慢线程/后端配置确认线程数和加速后端调线程、开 GPU/NPU、加散热输出乱码template 错位核对模型对话格式用内置 template、检查 promptJSON 格式错采样随机性看温度和 grammar降温度、加 grammar、few-shot上下文溢出KV cache 太大看上下文长度和并发数开 KV 量化、滑动窗口、减 slot5.5 几个我踩过的坑第一个坑是盲目追求大模型。一开始总想着上 13B结果板子跑不动折腾半天还是回到 7B。端侧场景下模型能不能稳定跑起来比它有多聪明更重要。第二个坑是忽略 chat template手动拼 prompt 导致模型答非所问后来老老实实用 GGUF 自带的 template 就正常了。第三个坑是没做上下文压缩Agent 跑十几轮之后内存直接爆掉加了滑动窗口才稳住。6. 性能实测与扩展方向6.1 一组可参考的实测数据我在一块 8 核 ARM 板子加 16GB 内存上做了几组测试模型是 7B 的 Q4_K_M 版本上下文 4096线程数 8。纯 CPU 推理下首 token 延迟约 1.5 秒生成速度约 4.5 token/s。开启 KV cache 8bit 量化后内存占用从 6.2GB 降到 4.1GB速度基本不变。换成 3B 模型后生成速度提升到 11 token/s 左右首 token 延迟降到 0.6 秒这个速度做 Agent 就比较舒服了。如果板子有 NPU同样的 7B 模型经过转换后生成速度能到 15 到 20 token/s但转换和调试成本不低。所以我的建议是如果 CPU 速度能接受就别折腾 NPU如果 CPU 实在扛不住再考虑 NPU 路线。6.2 后续可以扩展的方向端侧 LLM 部署跑通之后还有几个方向值得深入。一是多模型协同用小模型做路由和简单决策大模型只在关键步骤调用平衡速度和能力。二是模型热更新支持在不重启服务的情况下切换模型版本。三是和 RAG 结合把本地知识库的检索结果喂给端侧模型弥补小模型知识不足的问题。这几个方向我在后续项目里会陆续实践有新的心得再分享。最后分享一个小技巧端侧部署调试阶段一定要把推理服务的日志级别调高把每次请求的 prompt、输出、耗时都记下来。很多问题光看现象猜不出来翻日志一眼就能定位。这个习惯帮我省了大量排查时间。