ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

端侧Agent工程化实战:模型量化、推理引擎与记忆系统设计

端侧Agent工程化实战:模型量化、推理引擎与记忆系统设计 我们这个“深入理解端侧 Agent”的系列前两篇更多在讲端侧 Agent 是什么、为什么值得做、底层推理和调度大概怎么运转。到了第三篇按计划该切入工程化了。我把工程化拆成“上”“下”两部分因为一旦从概念走向落地杂事远比想象中多——模型选型、运行时、推理进程、编排并发、记忆存储、技能系统、权限沙箱、故障兜底随便拎出一项都能让团队排查一整天。这篇文章基于我们团队在小尺寸模型、手机和嵌入式设备上落地 Agent 的实际经历谈不上什么标准答案但里面每一个选型理由和踩坑记录都是真实的。如果你正在做端侧 Agent 工程化或者正打算把云端 Agent 往端侧搬这篇文章应该能帮你少走几条弯路。1. 为什么端侧 Agent 的工程化和云端 Agent 完全是两码事1.1 云端 Agent 的三个隐藏前提在端侧首先不成立很多人觉得 Agent 工程化不就是“把 LLM 包一层工具调用再挂上记忆和 Workflow”吗这个思路在云端能跑通是因为云环境有三个默认前提。第一算力几乎无限。云端可以随时拉起一台带 H100 的实例模型的参数规模基本不受限制推理速度不够就加卡内存不够就扩容。第二网络稳定可靠。Agent 调用模型 API、调用云端技能、同步记忆都在一个高带宽低延迟的内网环境里完成。第三模型能力可以随时升级。大模型一更新云端 Agent 的能力立刻水涨船高用户感知不大。端侧这三个前提全都不成立。端侧的算力以 NPU、CPU、GPU 的固定算力为上限内存更是瓶颈很多设备上能分给 Agent 的内存可能只有几百 MB 到 2GB。网络是移动网络或离线场景今天信号好明天进电梯Agent 不能因为网络抖动就罢工。模型一旦部署到设备上迭代周期就变成 OTA 节奏,不可能像云端那样热更新一个大版本。所以我在给新同学讲端侧 Agent 时通常会说云端工程化是“往上堆资源”端侧工程化是“在限制里打磨”两者的思维起点完全不同。如果你把云端的 Agent 框架直接搬到端侧会发现十个功能有七个因为资源或环境约束跑不起来剩下三个跑起来也未必稳。1.2 “本地跑个模型”和“本地有个 Agent”差的不是一星半点我见过不少团队把端侧 Agent 简单理解成“在手机里装一个能对话的大模型 App”。这个理解在 Demo 阶段没问题但距离工程化非常远。跑一个本地模型 App本质是“一次请求-一次推理-一次返回”交互是单轮的模型不保留长期状态App 退出就结束。Agent 则是一个长期存活的智能体它要持续感知环境变化比如用户说“帮我设置半小时后的提醒”“把我正在看的这篇文章存到笔记里”它需要能读取系统状态、调用系统 API、跨应用操作并且把之前的交互内容记下来。这就带来了工程上完全不同的要求感知侧需要事件监听和系统钩子而不只是一个输入框决策侧需要一个常驻的调度核心而不是一次性的推理调用执行侧需要一套可管控的技能调用框架而不是让模型直接拼命令记忆侧需要持久化存储和索引而不是一段聊天记录。拿智能家居场景举例。用户对 Agent 说“我睡觉了”Agent 可能要同时完成关灯、锁门、调空调、设置闹钟四件事。这四件事不是靠一次模型推理就能全部完成的它需要按照依赖关系依次调用不同设备的能力执行过程中还可能遇到某个设备离线需要动态调整方案。这种多步骤、有状态、可恢复的任务流程才是 Agent 工程化的核心而不是“把模型跑在本地”这个动作本身。1.3 混合架构端云协同的边界才是设计的起点讲端侧 Agent并不是说完全不能用云端能力。实际上大多数成熟产品走的是混合架构核心决策和隐私敏感操作放在端侧复杂推理和知识密集型任务放到云端。这个边界怎么划是工程化第一笔要画的线。我的建议是遵循三个原则。第一凡是涉及用户隐私的操作路径尽量端侧闭环。比如读取本地文件、分析相册、处理健康数据这些数据不出设备是最好的设计。第二凡是需要低延迟响应的动作必须端侧兜底。比如语音助手的唤醒词识别、快捷指令的触发不能每次都在云端走一圈再回来那样用户的体感会非常差。第三凡是重推理任务允许云端增强但必须明确降级路径。比如复杂文档总结可以走云端但云端不可用时Agent 要做到不减功能地降低回答质量而不是直接“抱歉我当前无法处理”。举个我们实际遇到的例子。车载场景下用户在地下车库或隧道里没有信号此时 Agent 需要能继续处理导航、音乐、车辆控制等本地任务。我们把导航目的地解析、音乐检索这类依赖知识库的请求设计成“端侧意图识别 云端结果增强”当云端不可用时端侧用本地缓存的历史数据和简化模型完成基本服务。这套降级逻辑如果不在设计初期定下来后期补会非常痛苦因为涉及数据流、模型选择、UI 状态多个层次的改动。所以我把混合架构的边界问题放在工程化第一章节它决定了后续所有模块的资源和约束条件先想清楚再动手比什么都重要。2. 端侧模型量化与推理引擎第一道工程门槛2.1 量化选型内存账先算清楚再谈效果模型是 Agent 的“大脑”但端侧能装下多大的大脑先得算内存账。以 7B 参数的模型为例FP16 精度下权重大小就是 14GB绝大多数端侧设备根本装不下。INT8 量化后权重变成约 7GB仍然偏大。INT4 量化后约 3.5GB加上运行时所需的 KV Cache 和激活值总内存占用大概在 4.5GB 到 5GB 左右这才勉强进入高端手机和平板能承受的范围。我们实际使用中落地优先级一般是1.5B 到 3B 模型跑 INT4/INT8用于车载、智能家居和轻量设备7B 模型跑 INT4用于旗舰手机和专业设备13B 以上模型基本不走端侧纯本地推理除非是特殊定制硬件。但这不代表量化就是“压一下就完事”。量化一定会带来精度损失尤其对函数调用、JSON 输出、工具参数生成这类结构化任务低精度模型经常出现参数名拼错、数值类型错误的问题。我们的做法是为每个目标模型建立一份“量化评测集”里面包含 Agent 典型场景的工具调用用例量化后逐条跑一遍对比成功率。有一次我们在一个 4bit 量化模型上发现工具调用参数的字段错误率高达 12%怎么调提示词都没用最后切换到 6bit 量化才恢复正常。所以别只看内存降低要用任务指标决定精度档位。2.2 推理引擎选型别追求“一个库跑所有硬件”端侧推理引擎的选型很容易进一个误区希望找一个万能引擎一套代码在所有设备上跑。现实是没有这种东西。我列个我们评估过的推理引擎对比你可以参考引擎主要特点适合场景踩过的坑llama.cpp纯 C/C 实现CPU/GPU 都支持社区活跃通用 LLM 推理、跨平台快速验证对 NPU 支持较弱需要自行集成硬件加速ONNX Runtime生态完善模型转换链路成熟已有 PyTorch 模型的团队中高层API某些自定义算子转换失败需要手工回退MNN移动端优化好支持 Android/iOS手机端 Agent 的主力选择之一新模型架构的算子覆盖有滞后NCNN轻量级CPU 优化强嵌入式设备和低功耗硬件对 LLM 支持不如前几个更适合传统 CV 模型TFLite生态稳定硬件委托机制清晰已有 TensorFlow 生态的团队LLM 动态 shape 处理比较繁琐性能上限有限那个“真香”方案其实是组合同一个模型导出多份格式运行时根据设备类型加载。Android 旗舰机走 NPU 加速的格式老设备走 CPU 优化版本嵌入式 Linux 走 llama.cpp。听起来运维复杂实际上用一个统一的模型管理模块就能封装换来的是各设备的稳定性和性能。工程上另一个重要建议是先把流程跑通再优化。不要一开始就扎进 NPU 算子适配里先用 CPU 跑通整套 Agent 流程确认效果和功能没问题再把性能瓶颈用 NPU 加速。我们有个教训第一次做端侧 Agent直接上手调 NPU结果算子对齐花了两周最后发现整体流程里记忆检索的耗时占比比推理还高NPU 优化完全没打在点上。2.3 进程架构不要让模型推理拖死主服务模型推理非常吃资源如果你把推理直接跑在 Agent 主进程里Agent 所在的应用会频繁卡顿甚至被系统杀掉。我们的做法是把推理独立成一个常驻服务进程主进程通过 IPC 和它通信。进程模型设计如下Agent 主进程负责感知、决策、技能调用、记忆管理保持轻量模型推理进程负责加载模型并处理推理请求占资源大户通信层Android 上走 Binder/AIDLLinux 上走 Unix Socket 或共享内存队列。这样做的好处有三个。第一模型加载和卸载不会影响主进程的响应速度第二推理进程崩溃后主进程可以自动拉起Agent 不退出第三主进程可以同时对接多个推理后端比如一个轻量模型做意图识别一个稍大的模型做深度推理互相独立。关于通信层我要提醒一个细节不要用 HTTP 做端侧本机通信。虽然开发最顺手但 HTTP 的序列化开销和端口管理在低功耗场景很吃亏。我们用 Unix Domain Socket 加 protobuf 或者 MessagePack延迟低、占用小、并且天然支持本机多进程通信。3. 编排层工程化并发、任务队列与生命周期3.1 端侧同样有并发压力只是换了一种形式“AI Agent 怎么抗并发”这个问题我之前总觉得云端才需要考虑。后来在手机上做实际产品发现端侧照样有并发只是“并发”的来源不太一样。想象一个真实场景用户戴着耳机说“帮我放一首周杰伦的歌”同时车载系统正在播报路况后台还有一个定时任务在等待触发。这三个事件几乎同时到达 Agent,如果 Agent 只有一个执行通道就必须排队处理还可能互相打断。我们设计的并发模型分三层请求接入层用异步队列接收所有事件避免阻塞系统回调调度层根据任务优先级决定下一个执行哪个任务比如系统安全相关任务优先于娱乐任务执行层使用线程池或协程池跑具体任务但模型推理部分仍然是串行的需要在调度层做合并。这里容易犯的错是让每个请求都独立占用一次模型推理。我们后来做了一个“请求合并”机制如果多个请求在短时间窗口内到达且判断它们可能属于同一个会话意图就合并成一次推理输入交给模型大幅降低推理开销。比如用户连续说“设个八点的闹钟”“明天早上”“顺便提醒我带文件”这三条如果分开推理模型可能无法连贯理解合在一条上下文里反而效果更好。3.2 Agent 任务的生命周期必须用状态机管理Agent 的任务不是一次原子操作它通常要经历多个阶段理解意图、生成计划、逐步执行、中途验证、最终交付。在工程上这种多阶段任务必须用状态机管理否则任何一步异常都找不到责任方。我们定义的核心状态包括Idle等待新任务Planning正在生成执行计划Executing正在调用技能工具WaitingForInput需要用户确认或补充信息Degraded云端不可用或推理质量下降Error任务执行失败Settled任务完成结果已归档。每个状态都绑定超时和重试策略。比如 Planning 阶段超过 5 秒没有产出直接降级为简化规则执行Executing 阶段某个技能调用连续失败两次不再死磕而是进入 Error 并给用户反馈替代方案。状态机的价值在于“资源紧张时可管控”。端侧的内存和算力有限我们设定当系统内存水位超过 80% 时冻结所有低优先级任务状态不是杀死任务而是让它们暂停在 Executing 阶段等资源恢复后从断点继续。如果没有状态机这种暂停/恢复根本做不到你只能硬着头皮跑然后等系统杀进程。3.3 多 Agent 协作与事件总线我之前聊过端侧也可以有多 Agent 协作的场景主 Agent 负责统筹专门 Agent 负责各领域。工程化之后协作方案的取舍会很明显。我们试过两种多 Agent 通信方式。一种是通过共享内存直接读写公共状态实现简单但多个 Agent 并发写同一个状态时排查问题简直灾难。另一种是引入事件总线所有 Agent 只向总线发消息也只从总线订阅自己关心的事件模块之间彻底解耦。我们最终选了第二种。在端侧落地事件总线不需要上什么重量级中间件。Linux 设备上我们用本地 Unix Socket 配一个轻量消息分发器移动端直接用系统提供的本地广播机制。重点是消息格式一定要提前定好 Schema比如任务完成事件必须包含 task_id、状态、耗时、结果摘要字段这样后续无论是做日志还是做并发控制都有依据。多 Agent 编排还有个工程细节是执行顺序的拓扑关系。我们用 DAG 来描述任务依赖主 Agent 根据用户意图动态生成一张子任务图然后按拓扑序执行。某个子任务失败时只重跑它的依赖链而不是整个任务重新来。比如“查找文件并总结摘要”这个任务“查找文件”成功但“总结”失败就只重跑“总结”节点不需要重新找文件。4. 记忆系统存储选型与遗忘策略4.1 短期记忆、长期记忆、向量检索分别怎么落记忆是 Agent 区别于普通聊天机器人的关键但工程上的记忆设计经常被做成“全塞进向量数据库”这是个误区。我们把记忆分成三种分别用不同的存储方案记忆类型内容存储方案说明短期工作记忆当前会话的上下文、临时变量内存缓存限制在几十条消息内超出窗口就做压缩不直接存盘结构化长期记忆用户偏好、实体关系、任务记录SQLite 或类似嵌入式数据库事务支持好查询方便数据可控语义记忆对话历史、文档内容的向量索引sqlite-vec 或 FAISS 本地索引用于模糊检索和语义召回为什么要区分这么细因为端侧存储空间有限向量索引最占空间。一段 1000 字的对话嵌入成向量后光向量就要占用几 KB 到几十 KB还不算原始文本。如果所有记忆都向量化一个用户用三个月存储可能膨胀到几百 MB。所以我们只在需要语义检索的内容上做向量化其他记忆用结构化字段存储比如“用户偏好喜欢早上八点听新闻”直接存成键值对不需要向量化。我们选 SQLite 作为长期记忆的底座理由很简单事务可靠、崩溃恢复机制成熟、单文件易备份。向量检索用 sqlite-vec 这样的扩展直接在同一个库里管理向量和结构化数据省去了维护两套存储的麻烦。4.2 记忆压缩和遗忘不是可选项是容量工序记忆系统的最大工程挑战不是写入而是容量管理。一个长期使用的 Agent如果只存不删任何设备都会被撑爆。我们的遗忘策略是分层的。会话结束后短期记忆先尝试用模型做摘要把细节压缩成几十字的结论存入长期记忆原始对话保留在本地日志里超过保留期后清理。当长期记忆库达到容量水位线的 80%触发“深度压缩”任务对旧记忆做二次归纳把多条相关的条目合并成一条。达到 95% 时启动“强制裁剪”优先删除低价值记忆条目。这个过程中最容易出现的坑是 Agent 自己触发记忆迁移时把时间顺序搞乱。比如整理“上周去杭州出差”的记忆模型可能在总结时输出“下周五有杭州行程”时间错位。我们的解法是任何模型生成的记忆都必须经过“时间字段校验”只允许模型改写内容正文时间戳和实体关系必须从原记录继承。这种机械校验看起来粗暴但非常有效能挡住大部分记忆污染。另外建议给记忆操作设计一个统一接口不管是写入、查询、压缩还是删除都通过同一个模块走。这样后续做权限控制、日志审计和同步上云都会方便很多。如果各模块各自操作数据库后面维护就是一场噩梦。5. 技能系统设计Harness 与 Agent 的边界怎么切5.1 Harness 和 Agent 的区别对工程化意味着什么“harness 和 agent 区别”这个问题被很多人搜过它同时也是工程化的关键议题。Harness 翻译成“工具链”或“脚手架”更准确它规定了 Agent 能在一个什么样的环境里执行动作能调用哪些工具、用什么协议、超时多久、有哪些约束。Agent 则是那个“决定调用哪个工具、怎么调用、调用后怎么办”的决策主体。把这两个概念分开工程上的意义非常直接决策和执行力必须解耦。如果它们耦合Agent 代码里直接写死各种系统操作比如 shell 命令、文件删除、权限修改那这个 Agent 的安全性和可维护性都会迅速失控。而如果 Harness 独立承担“执行沙箱”职责Agent 的每一次动作都要经过 Harness 的校验和授权风险面就能被大幅度收住。我见过一个反面案例某个端侧 Agent 的“打开应用”技能直接由 Agent 主进程 fork 一个 shell 去执行 am start 命令结果一次模型输出格式异常把命令参数拼接错了差点拉起错误的 Activity。后来我们把所有系统技能收编进 Harness由 Harness 做参数白名单校验同样的异常事件再也没出现过。5.2 技能注册、加载与调用协议技能系统要支持“接入标准化、加载按需化、调用可管控”。我们实践下来的标准流程是每个技能是一个独立插件目录包含 manifest 文件和实现代码manifest 里用 JSON Schema 描述技能名称、参数列表、返回结构、权限需求Agent 通过技能注册中心发现可用技能加载时只加载 manifest不加载实现真正调用时才按需冷加载实现代码调用结束后释放资源。这个设计的好处是设备资源占用低而且技能可以独立升级。如果某个技能版本有 bug只需要禁掉该插件的 manifest entry不需要重启 Agent 主进程。调用协议方面不要自己发明一套格式直接用 OpenAPI 或 JSON-RPC 这类成熟规范。我们用 OpenAPI 描述每个技能的 HTTP 映射或者用 JSON-RPC 描述本地进程间调用。模型端通过函数调用Function Calling生成结构化参数Harness 负责把参数映射到具体技能实现。这里有个经验技能的参数定义一定要严格最好每个参数都声明类型、范围、枚举值。因为模型的输出天生“自由奔放”如果技能定义模棱两可模型就会生成千奇百怪的调用。我们甚至要求每个技能必须定义“失败时的替代建议”字段这样技能调用失败时 Agent 可以直接把建议话术回复给用户而不是愣在原地。5.3 权限控制最小权限和人类审批端侧 Agent 直接面对操作系统权限控制做得不好等于把家门钥匙交给一个陌生人。我们的权限模型分三层强制措施。第一进程权限最小化。Agent 相关进程用独立 UID 运行只授予它操作必需的文件夹和设备的权限。Linux 上不要给它 rootAndroid 上只申请用户实际授予的权限不要“为了以后方便”提前申请所有权限。第二敏感技能有人审。凡是涉及支付、短信、删除文件、修改系统设置这类高风险动作Agent 不能自行执行必须通过 UI 弹窗让用户确认。这里“人机共审”不只是产品体验问题从工程上看它也是防止模型误操作的最后一道大坝。第三技能沙箱隔离。如果平台支持把技能实现放在单独的容器或子进程中执行就算技能代码有漏洞也不会影响 Agent 主进程。我们在嵌入式 Linux 上直接给每个技能套了 Linux 权限隔离组效果很好。6. 故障兜底与安全护栏把端侧 Agent 当“真正的服务”来做6.1 崩溃恢复与看门狗模式端侧 Agent 面临一个云上没有的麻烦它寄宿在操作系统里随时可能被系统杀掉。可能是内存不足被 LMK 收走可能是后台任务被系统冻结也可能是设备重启。云端 Agent 崩溃了自动扩容重启就是端侧 Agent 崩溃了用户感知是“这个助理偶尔失灵”。我们做的第一道防线是看门狗机制。独立的监控进程定期检查 Agent 主进程的心跳心跳超时就拉起一个新的主进程。同时主进程在进入关键状态前会持久化一份“任务快照”到本地里面包含当前状态机状态、正在执行的技能 ID、关键上下文摘要。重启后先恢复快照再决定继续执行还是告知用户任务中断。这里有个重要的取舍不是所有任务都值得恢复。如果任务快照显示已经执行了 20 分钟、涉及 15 步操作而且是一个“查询天气”这类轻任务那直接放弃并重新开始反而更合理。我们给每个任务类型定义“可恢复价值评分”只有评分高于阈值的重型任务才做完整恢复轻任务直接清理快照进入 Idle。这能避免恢复机制本身带来新的复杂度。6.2 模型不可靠时的护栏工程模型是概率系统必然有输出格式错误、幻觉、参数越界、死循环这些问题。工程上的态度就是“用护栏层兜住模型的不可靠性”。我们会在推理输出之后和技能执行之前插入一道硬校验Shell 校验输出是否符合 JSON Schema不符合就自动重试一次采用更低温度参数参数校验工具参数是否在合法范围比如音量调成 200% 这种显然越界的情况直接拒绝超时控制在每个技能调用都设置超时防止某个本地操作卡死整个 Agent执行上限给 Agent 的单次任务设定执行步数上限默认 8 步超过就停止并给出阶段性总结而不是无限循环。有人担心这些护栏会限制 Agent 的“智能”。但我的观点是先把确定性管住了再谈智能。用户不会因为 Agent 偶尔更“自由”而原谅它经常出错端侧产品尤其如此。6.3 本地日志与可审计性端侧隐私要求高但日志不是不能做关键是不能像云端那样把所有数据传到服务器。我们的做法是“本地详细日志 用户同意后选择性上传”。本地日志记录尽量全每次事件触发的原始输入、Agent 选中的意图、生成的计划、每次技能调用的技能 ID、参数摘要、返回结果摘要、耗时、状态迁移。这些日志存成环形文件最多保留最近几天的量存储超限就滚动覆盖。这样做的好处是排查问题时不用靠“猜”可以直接回放当时的完整决策链路。有一次用户反馈 Agent “乱操作”我们通过日志发现是模型把“关闭蓝牙”识别成了“关闭网络”立刻定位到意图分类模型的问题并做了针对性微调。如果当时没有审计日志这个决策链信息这个 bug 可能好几天都查不出来。日志字段里我会特别强调一个点必须区分“用户原始输入”和“Agent 的中间解释结果”。这不仅是排查方便也是责任界定的依据如果 Agent 做错了能明确看到是理解错了还是执行错了而不是把责任混在一起。7. 落地优先级三条路线图建议与“下篇”预告工程化讲了一大堆如果团队刚起步我建议按下面的优先级推进先把地基打好再盖楼。第一条先做单机闭环再谈多 Agent 协作。不管最终架构多复杂先把“模型加载 - 意图识别 - 一次技能调用 - 记忆写入 - 状态返回”这条链路完整跑通。这个过程会暴露出大量真实问题模型量化精度够不够、IPC 性能行不行、技能调用稳不稳定。把这些基础问题解决了再上多 Agent、再谈云端协同才不至于一团乱麻。第二条把“结果可预期”放在“能力丰富”之前。端侧 Agent 的第一版能力可以少但每个能力都得稳定。我们内部有个原则功能上线前先在低端设备上做压力测试如果某个功能在两年前的设备上表现不合格宁可推迟发布也不带病上线。用户对端侧 Agent 的容忍度比云产品低很多一次失败就可能卸载。第三条从第一天就预留观测接口。端侧 Agent 的可观测性比云端难做但又是排查问题的命脉。最少要在核心模块加日志埋点、结构化事件上报通道、状态快照持久化。这些观测能力早期可能用不上一旦上线出问题它们就是唯一的救命稻草。下篇我计划重点展开三块一是记忆和技能系统的完整接口设计细节包括消息 Schema 和存储模型二是多设备间的 Agent 状态同步与冲突处理三是在嵌入式设备上做 Agent 的资源调度与功耗优化。这些内容更偏实现层面等我们跑完新一批设备验证再回来填坑。先到这继续干活去了。
返回列表