1. 为什么端侧Agent的落地顺序,是把模型先“请”到设备上
我最初做端侧 Agent 的时候,思路特别天真:规划逻辑、工具列表、记忆模块都写好了,模型直接接云端 API 就行。真正把手上一整套 Agent 代码往 RK3588 开发板上搬的时候才发现,最卡壳的部分根本不是 Agent 代码,而是给 Agent 当大脑的模型在设备上站不住脚。这篇是该系列的第二篇,重点补上端侧 LLM 部署这块:模型为什么必须跑在设备上、硬件怎么选、量化是怎么回事、部署框架怎么搭、模型起来之后又该怎么和 Agent 框架顺畅对接。
先说你最该想明白的一件事:端侧 Agent 不是简单地把云端接口“降级”到本地,而是要把整套推理链路重新设计一遍。云端 API 的延迟、隐私、成本和并发问题,在端侧全部变成了“模型能不能跑、跑得好不好”的问题。
1.1 云上 Agent 的三笔账
Agent 的核心动作是“感知—规划—行动—观察”,这个循环里每一步都可能触发一次大模型调用。云端 API 看起来方便,但算起账来很吓人。
第一笔是延迟账。每次请求从设备到机房,再返回结果,一个 RTT 就要几十到几百毫秒。Agent 如果连续调用三次模型,用户感知到的就是“卡了三拍”。我实测过一些开放平台的接口,高峰期单次聊天补全能到 3 秒以上,这在工具调用频繁的 Agent 场景里基本没法用。
第二笔是隐私账。端侧 Agent 面对的多是家庭、车载、工位这类环境,摄像头画面、麦克风录音、传感器数据和执行器状态,全都往云端送,很多场景根本过不了关。端侧部署的意义不是省几毛钱成本,而是让数据留在本机,推理结果也只在本机产生。
第三笔是成本与并发账。云端 API 按 token 计费,长期跑一个 24 小时待机的 Agent,费用会累积得非常快。更头疼的是并发限制:一个账号在同一时间能发起的请求数量有限,“AI Agent 怎么扛并发”这个问题,在云端经常要靠买配额、做排队来解决。放到端侧就不一样了,一台板子跑模型,并发能力由自己的内存和带宽决定,边际成本几乎为零。
三笔账算完,结论很明确:只要硬件能扛住,模型就必须端侧化。
1.2 端侧 LLM 与云端 LLM 各自该管什么
端侧 Agent 不意味着所有推理都压在本地。经过几个项目折腾,我最推荐的是“端侧为主、云端兜底”的混合架构。
端侧 LLM 负责高频、低延迟、隐私敏感的任务:意图识别、指令解析、工具调用参数抽取、简单状态判断、短对话回复。这些任务单个看都不难,但架不住调用频率高,放到本地能极大提升响应速度。
云端 LLM 负责低频、重计算、需要大知识的任务:长文档总结、复杂多跳推理、跨领域知识问答。这类任务一天可能就发生几次,牺牲一点延迟和隐私换取能力上限,完全值得。
关键是 Agent 里要有一个“升级策略”。我在项目里通常会让端侧模型先给出一个结论和置信度,置信度低于阈值的时候再转发到云端,而不是每句话都云端兜底。这样既保住了体验,也不会因为端侧模型能力弱而把整个 Agent 搞成“弱智”。
1.3 部署边界要提前想清楚
动手之前,先列一张边界清单:
- 模型最大能到多少参数,取决于设备内存和带宽。8GB 设备跑 7B Q4 很紧,16GB 设备能舒服一点,32GB 设备才有资格谈 14B。
- 上下文窗口要留多少。Agent 每轮工具调用通常要消耗 500 到 2000 token,加上系统提示和工具定义,平均一轮轻松破 4000 token。
- 功耗和持续运行时长。如果设备靠电池供电,连续推理会迅速耗尽电量,这时候就得趁机限制每小时的 LLM 调用次数。
- 并发数预期。一台设备可能同时服务家里两位家庭成员的两个 Agent 会话,那就要按 2 路并发的标准来评估模型速度和内存占用。
这些边界不提前定,后面选硬件、选模型、调量化全部会拍脑袋。
2. 硬件这关:RK3588、Jetson Orin 和树莓派的真实算力边界
端侧 LLM 硬件选型,网上参数满天飞,真正跑起来完全是另一回事。我先后在树莓派 5、RK3588 开发板、Jetson Orin Nano 上部署过同一套 Agent,感受差异非常大。
2.1 三块主控的账面数据和实测感受
先给一张基于我实际部署经验的粗略对比表,方便你建立直觉:
| 平台 | 常见内存 | 内存带宽(量级) | 加速单元 | 实际能流畅跑的模型规模 |
|---|---|---|---|---|
| 树莓派 5 | 8GB / 16GB | 10~20GB/s | 无 GPU | 1.5B~3B Q4 |
| RK3588 开发板 | 8GB / 16GB / 32GB | 20~35GB/s | 6TOPS NPU | 3B 很稳,7B Q4 可日常用 |
| Jetson Orin Nano / NX | 8GB / 16GB | 60~70GB/s | Ampere 架构 GPU | 8B Q4 流畅,14B Q4 可跑 |
树莓派 5 在我这里基本只能当“演示机”。跑 1.5B 模型还凑合,一旦上 3B Q4,生成速度掉到每秒钟几个 token,Agent 做个三步工具调用能等上半分钟,耐心再好也扛不住。
RK3588 是我目前的长期主力。16GB 版本跑 Qwen2.5-3B 量化版非常稳,7B Q4 也能跑,但速度明显下降,需要配合散热和调参。它的 NPU 标称 6TOPS,看着很唬人,但实际跑 Transformer 模型时,算子支持和工具链仍然比较折腾,多数时候我直接用 CPU 跑,反而省心。
Jetson Orin 系列是体验最好的。CUDA 生态成熟,软件栈完善,8B 模型量化后在 Nano 上能到每秒 15 token 以上,跑 14B 也不会直接崩,只是需要把上下文窗口调小。缺点只有一个:贵,而且开发板功耗比 RK3588 高不少。
2.2 算的不是 TOPS,而是内存带宽
这是个特别容易踩的坑。很多人选型先看“算力多少 TOPS”,但对于 LLM 推理,内存带宽才是真正的瓶颈。
原因在于 Transformer 解码阶段每生成一个 token,都要把整个模型权重从内存里读一遍。假设一个 3B 模型量化成 INT4 后权重约 2GB,内存带宽是 20GB/s,那么理论上每生成一个 token 至少需要 2GB 除以 20GB/s,等于 0.1 秒,生成速度上限就是 10 token/s。再强的算力,只要带宽不够,也只能干等数据从内存搬到计算单元。
RK3588 的 NPU 标称算力高,但很多端侧 NPU 对 LLM 常用算子的支持不完整,有些算子会回退到 CPU。结果就是你买的“6TOPS”根本没用在刀刃上,最后全靠 CPU 和内存硬扛。所以我选硬件时第一眼永远看内存容量和带宽,TOPS 只当参考。
2.3 我的选型建议
我会按这三个档位来给建议:
- 预算有限、喜欢折腾、想跑视觉加 Agent 的,选 RK3588 开发板 16GB 版本。不要买 8GB 版本,8GB 会经常处于“差一点”的尴尬状态。
- 想把 Agent 长期稳定运行、不想在软件栈上花太多时间的,直接上 Jetson Orin Nano 或 NX。哪怕内存小一点,GPU 带来的生成速度和生态优势非常明显。
- 树莓派更适合做“端侧传感器节点”,比如接摄像头、麦克风、传感器,把数据预处理完再交给另一端侧的模型节点,而不是让它自己扛 LLM。
还有一个容易忽略的点:开发板的存储介质。模型文件动辄几个 GB,频繁读取会显著缩短 TF 卡寿命。有条件一定要把系统和模型放在 SSD 或者可靠的 eMMC 上,TF 卡最多放缓存数据。
3. 模型选型和量化:从 DeepSeek 蒸馏版到 3B 级模型怎么挑
硬件定了,下一步就是选模型。端侧选模型的核心逻辑和云端完全不同:云端看模型能力上限,端侧看“内存装不装得下”和“工具调用稳不稳”。
3.1 先搞定“内存装不装得下”的账
有一个很简单的估算方法:模型权重大约占多少,KV cache 要占多少。
FP16 精度下,1B 参数约占 2GB,3B 约占 6GB,7B 约占 14GB,14B 约占 28GB。这个内存占用在端侧根本不可接受,所以量化是关键。INT4 量化后,权重缩到约原来的四分之一:3B 约 2GB,7B 约 4.5GB,14B 约 9GB。
除了权重,还要给 KV cache 留空间。8K 上下文、3B 模型大概需要几百 MB 到 1GB 的 KV cache,具体取决于层数和注意力头数。所以一台 8GB 设备跑 7B Q4 往往会因为 KV cache 耗尽而换页,体验很差;跑 3B Q4 才能留下足够余量。
最近 DeepSeek 本地部署热度很高,很多人问 8GB 设备能不能跑。确实能跑,但跑的是量化后的蒸馏版,比如 DeepSeek-R1-Distill-Qwen-7B,Q4_K_M 后约 4.7GB,8GB 设备还得把上下文窗口收到 4K 左右才安全。如果你想要更稳定的 Agent 体验,我会优先建议 3B 或 4B 级别的模型,把容量留给上下文和并发。
3.2 模型选择看工具调用能力,别只看榜单
Agent 场景里,模型能不能稳定输出合规的“工具调用 JSON”,比“数学推理排行榜第几名”重要得多。
以我的实测经验:
- Qwen2.5 系列 3B 和 7B 对 function calling 的原生支持很完整,中文场景下输出稳定,是端侧 Agent 的首选。
- Llama 3.2 3B 英文表现不错,工具调用也能用,但中文指令跟随会弱一截。
- Phi-3.5 Mini 代码能力强,但工具调用格式偶发漂移,需要做兜底解析。
- Mistral Nemo 12B 能力更强,但 12B 对硬件要求高,一般得 Jetson 级别才跑得舒服。
挑选模型时看公开榜单不是不可以,但端侧场景一定要带着自己的工具定义和真实 prompt 去测。同一个模型,在通用问答榜上的分数很高,遇到工具调用时却可能输出非法 JSON,这种例子我见过不止一次。
3.3 量化原理:GGUF、AWQ 与 GPTQ 怎么选
量化就是把模型权重从高精度压缩到低精度,让权重体积更小、读取更快。FP16 每个权重占 2 字节,INT4 只占 0.5 字节,内存占用砍掉 75%,带宽压力也同步下降,代价是精度损失和输出稳定性下降。
端侧部署最常见的三类量化方式:
- GGUF 是 llama.cpp 生态的格式,支持 K-quants 这类分组量化,在 CPU 上表现好,兼容性极高。RK3588 和树莓派首选。
- AWQ 和 GPTQ 主要面向 GPU,Jetson Orin 上用这两种配合 TensorRT 或推理引擎效果更好,能获得比 CPU 更低的延迟。
- 动态量化(如 bitsandbytes)适合快速实验,但部署到生产环境时我更倾向离线量化好的 GGUF 或 AWQ,启动速度和稳定性更好。
量化等级上,INT8 几乎无损,但体积还是太大;INT4 是当前端侧的甜蜜点;INT3 或更低就不建议了,输出质量崩得厉害,Agent 工具调用会频繁出错。
特别提醒一点:量化对工具调用的影响往往比想象中严重。INT4 模型参数被近似压缩,生成 logits 的微小漂移可能导致输出 JSON 不合法,或者工具名的 token 概率被压过去。如果 Agent 高度依赖结构化输出,宁可选择 Q6_K 或 Q8_0,也不要一味追求最小体积。
4. 部署实操:在 RK3588 上用 Ollama 和 llama.cpp 把模型跑起来
选完硬件和模型,真正动手部署时,第一步是选推理框架。框架选得对不对,直接影响后续 Agent 对接的顺利程度。
4.1 推理框架怎么选,先想好你有多爱折腾
我常用的三条路线:
- Ollama:安装最简单,自带 OpenAI 兼容 API,内置模型管理和并发调度,适合快速验证和大多数 Agent 项目。
- llama.cpp:可定制性最强,能够控制线程数、KV cache、prefix cache、内存锁等细节,适合需要深度调优的场景。
- llama-cpp-python:把 llama.cpp 编译成 Python 包,适合想直接把模型嵌进 Python Agent 进程、不走 HTTP 的开发者。
对大多数第一次部署的人,我的建议是先走 Ollama,把链路跑通后再决定要不要换 llama.cpp。别一上来就编译源码,否则环境问题会把你劝退。
4.2 Ollama 路线:最快要一条命令跑起来
在 RK3588 的 Debian/Ubuntu 系统上,安装 Ollama 很简单:
curl -fsSL https://ollama.com/install.sh | sh装完以后先检查内存是否足够:
free -h然后拉取模型并运行:
ollama pull qwen2.5:3b ollama run qwen2.5:3b这里有几个我踩过的细节:
- 如果只打算通过 API 调用,
ollama run主要是为了验证模型能正常出字。真正给 Agent 用,要确认ollama serve在后台跑着。 - 默认端口是 11434,OpenAI 兼容接口是
http://127.0.0.1:11434/v1。 - 想要并发调高一点,启动服务前设置环境变量:
OLLAMA_NUM_PARALLEL=2 OLLAMA_KEEP_ALIVE=30m ollama serve。KEEP_ALIVE表示模型在空闲时驻留内存的时间,调长一点能避免每次请求都重新加载模型。
我在 RK3588 上实测,Qwen2.5-3B 的 INT4 版本用 Ollama CPU 推理,生成速度大约在每秒 8 到 12 token 之间。这个速度对大部分 Agent 工具调用场景是够用的,因为工具调用往往只需要生成几十个 token,真正耗时的是模型加载和首 token 时间。
4.3 llama.cpp 路线:把每一环握在自己手里
当你开始在意 KV cache、前缀缓存、线程绑定这些细节时,就该切换到 llama.cpp 了。
先构建:
sudo apt update sudo apt install -y build-essential cmake git git clone https://github.com/ggerganov/llama.cpp.git cd llama.cpp cmake -B build -DCMAKE_BUILD_TYPE=Release cmake --build build --config Release -j $(nproc)接着下载对应模型的 GGUF 文件。注意从官方发布页或镜像站拿文件,同时下载 sha256 校验文件,防止文件不完整。模型文件建议放在/opt/models这类独立目录,别丢在用户目录里。
启动 OpenAI 兼容的推理服务:
./build/bin/llama-server \ -m /opt/models/qwen2.5-3b-instruct-q4_k_m.gguf \ --host 0.0.0.0 \ --port 8080 \ -c 8192 \ --mlock--mlock会把模型权重锁在内存里,避免被 swap 出去。RK3588 的内存如果足够,强烈建议加这个参数,否则系统内存一紧张,模型文件被换到交换分区,延迟会暴涨到不可用。
服务起来之后,用 curl 验证:
curl http://127.0.0.1:8080/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "qwen2.5", "messages": [{"role": "user", "content": "1+1=?"}], "max_tokens": 64 }'4.4 服务化对接 Agent 框架
端侧模型部署完,Agent 框架接入的工作量非常小。绝大多数 Agent 框架都支持自定义 OpenAI 兼容 base URL,你只需要把原先指向云端的地址改成http://127.0.0.1:8080/v1或http://127.0.0.1:11434/v1。
一个最小对接示例:
from openai import OpenAI client = OpenAI( base_url="http://127.0.0.1:8080/v1", api_key="not-needed", ) resp = client.chat.completions.create( model="qwen2.5", messages=[ {"role": "system", "content": "你是家庭助理 Agent。"}, {"role": "user", "content": "打开客厅灯"}, ], ) print(resp.choices[0].message.content)设备上的推理服务默认监听 127.0.0.1,如果 Agent 跑在同一台设备,完全不要改成 0.0.0.0。只有当你把 Agent 核心进程放在另一台电脑、模型跑在开发板上时才需要监听局域网地址,但务必隔离开公网访问,端口不要直接暴露到公网。多台端侧设备同时提供模型服务时,我还会在前面加一层 LLM 网关做路由,统一管理请求和故障切换。
5. 让端侧 LLM 会“用工具”:函数调用与 Agent 编排的实操细节
模型跑起来了,Agent 才能真正开始干活的环节是“让模型学会在合适的时候调用工具”。这个环节出问题最多,也最考验基本功。
5.1 工具调用在模型眼里只是“带格式约束的文本生成”
工具调用没有玄学。把每个工具的调用方式写成 JSON Schema,放进请求的tools参数里,模型根据用户意图输出一个表示“我要调用某个函数”的 JSON。
我常用的一个最小工具定义长这样:
{ "type": "function", "function": { "name": "control_light", "description": "控制指定位置的灯光开关", "parameters": { "type": "object", "properties": { "device_id": {"type": "string", "description": "灯的设备编号"}, "action": {"type": "string", "enum": ["on", "off"]} }, "required": ["device_id", "action"] } } }模型会输出类似这样的结构化结果:
{ "name": "control_light", "arguments": { "device_id": "livingroom-main", "action": "off" } }Agent 框架拿到这个 JSON 后,负责执行对应函数,再把执行结果作为一条观察消息塞回上下文,模型基于新的状态规划下一步动作。整个循环的关键是“工具的 Schema 足够简单明确”。越复杂、字段越多,端侧小模型越容易出错。
有一个我在设计工具时一直用的思路,对应理解大模型 token 的三个点:系统提示词和角色设定是 key,它告诉模型“我是谁”;用户的请求是 query,它告诉模型“我在找什么”;工具定义和上下文内容才是 value,它提供了“我能提供什么”。工具定义写得好不好,直接决定了模型能不能在 value 里检索到正确的调用方式。
5.2 报错“provider rejected the request schema or tool payload”的排查链路
我遇到的最高频报错,就是请求里带 tools 参数时,本地推理服务直接返回类似“llm request failed: provider rejected the request schema or tool payload”的错误。第一次遇到时我以为是模型能力问题,后来发现十有八九是 schema 格式问题。
完整排查链路我建议你这么走:
- 先用最简单的不带 tools 的请求确认模型可以正常对话,排除模型加载、显存、服务崩溃的问题。
- 加上一个最简单的工具再请求,逐步确认是哪个字段触发了拒绝。
- 检查 Schema 格式。最常见的坑有三个:“required”被写成了布尔值而不是数组;“parameters”顶级类型写错;“type” 写了
"function"却缺了内层function对象。这些在云端模型那里可能被宽松兼容,端侧引擎校验更严格,直接拒绝。 - 确认工具描述里没有引擎不支持的字段,比如把
"default": null写在非标准位置。 - 如果本地引擎本身不支持结构化工具参数,比如某些 GGUF 模型没有原生 function calling,那么请求里带 tools 就会被拒绝。这时候要么换一个原生支持工具调用的模型,要么暂时关闭 tools,把工具说明用自然语言写进系统提示词,要求模型输出固定 JSON,再在 Agent 后端做解析和校验。
- 打开推理服务日志,看拒绝发生的位置,到底是 HTTP 层拒的还是模型层拒的,这能省很多排查时间。
把工具调用请求和日志完整存下来,是排这类问题的唯一捷径。我一般会把每次请求的 tools schema 存成带版本的配置文件,而不是硬编码在 Agent 代码里,改起来方便,出问题也能对照排查。
5.3 上下文窗口与记忆管理:端侧 Agent 最容易翻车的地方
端侧模型上下文窗口通常就 8K 左右,KV cache 还占用设备内存,所以上下文管理比云端严格得多。
我现在的做法是:
- 把系统提示词、角色设定、工具定义放在最前面,而且保持稳定不变。这样推理引擎的 prefix cache 才能命中,重复请求更快。
- 会话历史只保留最近 N 轮,更早的直接裁剪。裁剪前如果还有重要信息,用模型生成一段摘要塞回系统提示词。
- 长时记忆交给向量库或内存数据库,需要时按语义检索回填,而不是把全部历史都塞进上下文。
- 每一轮请求前都做 token 计数,接近上限时强制触发一次压缩摘要。很多端侧 Agent 跑几天之后突然变慢、报错“out of memory”,十有八九就是上下文撑爆了 KV cache。
当工具数量超过 10 个时,不要再把所有工具定义一次全塞给模型。我会先用一个小模型做意图分类,只把相关的一两个工具定义传给主模型,准确率反而会提升,上下文占用也会降下来。
6. 延迟、并发与上下文窗口:端侧 Agent 上线前最该盯的三个指标
模型能调用工具、Agent 能跑通,这只是开始。上线之后你会面对更现实的问题:延迟是否可接受、并发会不会拖垮设备、上下文会不会把内存塞满。这三个指标我建议在部署第一天就盯上。
6.1 端侧模型的“快”怎么量化:TTFT 与 TPS
很多人判断模型快不快只看“每秒生成多少 token”,也就是 TPS。但在 Agent 场景里,TTFT(首 token 延迟)更重要。
工具调用很多时候只生成十几二十个 token,TPS 再高,如果首 token 要等两三秒,整体体验还是很差。TTFT 受到模型加载、KV cache 分配、前缀命中等因素影响,和单纯推理速度是两个维度。
我常用的测速方式:
curl -w '总耗时: %{time_total}s\n' -s \ -H "Content-Type: application/json" \ -d '{ "model": "qwen2.5", "messages": [{"role": "user", "content": "用一句话介绍自己"}], "max_tokens": 64 }' \ http://127.0.0.1:8080/v1/chat/completions | tail -1更精细一点就写脚本读取响应里的usage.completion_tokens,除以总耗时,得到真实 TPS。我实测下来的量级参考:
- RK3588 跑 3B Q4:TTFT 约 0.5 到 2 秒,TPS 约 8 到 12。
- RK3588 跑 7B Q4:TPS 约 4 到 6,肉眼可见地慢。
- Jetson Orin Nano 跑 8B Q4:TPS 约 15 到 25,体验明显好一个档次。
想优化 TPS,优先加内存带宽,其次是看是否用了--mlock、是否设置-t线程数、散热是否到位。想优化 TTFT,优先做前缀缓存和 KV cache 复用,这些比盲目换大模型有效得多。
6.2 单板并发:一台设备扛几个 Agent
端侧设备不是只能同时服务一个请求。我实际测试下来,RK3588 跑 3B Q4,用 Ollama 设OLLAMA_NUM_PARALLEL=2,同时服务两个 Agent 会话是可行的,单请求时间会变慢一些,但整体吞吐更高。
如果一台设备要跑多个独立 Agent 实例,比如监控、门禁、语音助手各一个,我建议这样配置:
- 优先保证每个 Agent 实例的上下文独立,内存别都堆在 KV cache 上。
- 把并发数从 2 起步,观察 TPS 下降到不能接受时再调低。
- 如果某个 Agent 是无人值守的持续任务,尽量错峰运行,不要让多个重任务同时触发大段生成。
当你有好几台设备,或者一台设备跑不过来时,可以考虑在端侧集群前面加一个 LLM 网关,统一接收 Agent 请求、按设备负载做路由,这也是“LLM 网关”在端侧场景里比较务实的用法。它不负责模型推理,只负责调度和容错。
6.3 散热、存储与无人值守
这部分是很多人忽略的坑,但往往决定项目能不能长期运行。
RK3588 这类开发板长时间连续推理,发热非常明显。如果不加散热片或风扇,ARM 核心会快速降频,TPS 直接掉一半。我在机箱里加了一个主动散热风扇后,生成速度稳定了很多。
监控温度很简单:
sudo apt install lm-sensors sensors设备无人值守时,我还建议加一个简单的看门狗脚本,检测到模型服务进程退出就自动拉起,保证 Agent 不会因为一次偶发崩溃就彻底失联。
存储方面,尽量把模型文件放在 SSD 或 eMMC 上,不要放 TF 卡。一个大模型文件几个 GB,连续推理时反复读取,TF 卡撑不了太久。日志和中间缓存可以写到内存盘,减少闪存磨损。
供电也要单独说一句。开发板跑满负载时电流需求比待机高很多,别用电脑 USB 口随便供电,用额定电流足够的外置电源,否则会出现奇怪的重启和推理中断。
最后再分享一点个人体会
端侧 LLM 部署这件事,本质上是在“能力上限”和“硬件成本”之间找一个可接受的平衡点。端侧 Agent 不应该追求让模型无所不能,而是把高频动作做得又快又稳,把搞不定的活交给云端兜底。我刚接触的时候踩过不少坑,比如一上来就在 8GB 设备上挑战 14B 模型,结果卡到怀疑人生;又比如工具调用报错时拼命换模型,最后发现只是 schema 里一个字段写错了。如果你的模型请求莫名其妙报错,先别怀疑硬件,打开请求日志看一眼 tools 参数,一半问题都在那里。这个系列之后我还会继续写工具调用解析和记忆机制的落地细节,到时候再聊。