
最近把 AI Agent 挪到手机本地跑时我遇到的不只是“模型能不能装下”的问题更多是推理链路被拉长以后暴露出的稳定性与速度问题。Agent 不是一句聊天它要做意图识别、工具调用、结果回填、继续推理每一步都可能因为模型架构、上下文长度、量化精度和手机内存带宽而失败。这篇内容围绕 Liquid AI 的 LFM 2.5 展开整理了一套从架构理解到手机端实测的思路代码部分按可复现的方式给出适合正在做端侧 Agent 选型或本地模型部署的同学。本文不会只堆概念也不会只放几个跑分截图。重点包含LFM 2.5 和传统 Transformer 的差异、为什么 Agent 多轮场景对 KV Cache 和内存带宽更敏感、手机端如何搭一个 Agent 调度循环、怎样量化每一轮工具调用耗时以及常见报错和工程化建议。整个流程可以在无服务器的情况下完成数据也留在设备本地。1. 背景为什么要在手机上本地跑 AI Agent1.1 AI Agent 和普通聊天机器人的差别AI Agent 通常可以理解成一个“能使用工具的大模型系统”。普通的聊天机器人只会回答问题而 AI Agent 会拆解任务、决定调用哪些工具、查看工具返回结果、继续推理最后输出最终答案。一个常见的链路是用户输入任务。模型判断需要调用工具。模型输出结构化工具参数。Agent 系统执行工具。工具结果返回给模型。模型根据结果生成下一轮内容。整个过程是循环式的。与单轮问答相比Agent 对模型有三个额外要求能保持多轮状态不会在长对话后丢失任务目标。能稳定输出结构化内容尤其是 JSON 格式的工具调用参数。推理延迟不能太高否则用户每等一次工具调用都会感觉明显卡顿。如果把这些 Agent 场景放在云端通常有较强的 GPU 支撑问题还不算突出。一旦放到手机本地模型体积、推理速度、内存峰值和散热条件都会成为约束。1.2 手机本地跑 AI Agent 的价值把 Agent 放到手机本地不只是为了“离线可用”。第一隐私性更强。聊天记录、文件摘要、设备状态等敏感信息不需要发送到云端处理。第二可用性更稳。没有网络时仍然可以执行本地任务比如本机文件整理、日程摘要、关键词搜索。第三边际成本更低。服务端大模型的调用费用会随次数增长本地推理只要设备更新时产生一次性成本。不过本地运行也要付出代价。手机 SoC 的内存带宽、电池容量和散热能力远不如服务器同时端侧推理引擎对很多新模型的算子支持滞后。所以选型时不能只看模型效果还要看“架构是否适合端侧落地”。1.3 LFM 2.5 是什么LFM 是 Liquid Foundation Model 的缩写也就是 Liquid AI 发布的系列基础模型。LFM 2.5 是当前较新的版本周期从官方发布方向看它更强调在边缘设备、开发板、笔记本和手机上运行而不是一味追求超大参数量。很多关于 LFM 2.5 的讨论会集中在“非 Transformer 架构”上。传统大模型默认使用 Transformer 结构LFM 2.5 则采取了不同的网络设计目的是降低自回归生成阶段的内存和计算开销。对于手机端 Agent 这种“需要长上下文 多次工具调用”的场景来说这方向有天然吸引力。需要注意不同型号、不同量化版本的 LFM 2.5 在手机上的实际表现差异很大。具体参数规模、上下文长度、推荐推理框架还是要以官方发布信息为准。本文的重点不是罗列官网数据而是跟踪理解这些模型设计如何影响 Agent 真实体验的链路。1.4 为什么架构选择决定端侧 Agent 体验“架构”这个词在热词里高频出现但从端侧 Agent 视角看真正重要的是三件事模型执行一次推理需要多少内存。上下文增长后内存和计算是否快速膨胀。解码阶段生成每个 token 的耗时是否稳定。Transformer 的优势是训练生态成熟、并行性好但在生成阶段有一个比较大的负担每处理一个 token都要保存历史 token 的 Key 和 Value也就是 KV Cache。这个缓存会随上下文长度线性增长。Agent 多轮工具调用会把很长一段历史塞给模型导致 KV Cache 越占越多进而让手机内存快速见顶。而 LFM 2.5 这类非 Transformer 架构会在设计层面对这部分开销做优化减少随序列增长带来的内存压力。这样在手机本地跑 Agent 时才不会出现“聊几轮之后程序被系统杀后台”的尴尬。2. 实测环境准备2.1 软硬件建议手机本地跑大模型环境差异很大。本文示例的结构不依赖特定手机型号但建议满足以下条件否则体验会比较吃力项目建议要求说明操作系统Android 12 及以上对 Termux/容器支持相对稳定内存8GB RAM 起步3B 级模型 系统驻留需要较大余量CPU骁龙 8 系 / 天玑 旗舰或同级低端 CPU 会非常慢Python版本3.10Agent 调度代码使用 Python推理后端视模型官方支持情况优先选择兼容 OpenAI 接口的本地服务不满足高位条件也能跑通流程只是模型要换更小的量化版本或者把上下文限制得更短。2.2 选型原则模型服务与 Agent 逻辑分层实测过程中最重要的一条工程原则是不要把模型加载、采样参数、Token 解析和 Agent 调度逻辑搅拌在一起。我更建议分成两层模型服务层负责加载 LFM 2.5、完成推理、暴露 HTTP 接口。Agent 调度层负责维护会话、决定是否调用工具、解析工具结果。这样做的原因是 LFM 2.5 的端侧推理工具迭代非常快。如果将来官方发布新版本 runtime你只需要替换模型服务层不需要重写 Agent 业务逻辑。2.3 项目目录结构准备一个干净目录例如mobile-agentmobile-agent/ ├── agent_core.py # Agent 调度主循环 ├── tools.py # 工具函数 ├── prompts.py # 提示词模板 ├── benchmark.py # 性能测试脚本 └── models/ └── README.md # 记录模型下载来源与量化方式可以通过 Cloud Codes 这类云端开发环境管理代码写好后同步到手机也可以直接在手机 Termux 中用编辑器编写。两者不影响测试结果。3. LFM 2.5 架构与性能核心原理3.1 传统 Transformer 的 KV Cache 瓶颈要理解 LFM 2.5 为什么值得关注先要理解 Transformer 在生成阶段的问题。Transformer 解码时注意力机制需要计算当前 token 与之前所有 token 的相关性。为了避免每生成一个新 token 都重新计算全部历史推理框架会把历史的 Key 和 Value 缓存下来这就是 KV Cache。KV Cache 的优点很明显节省重复计算。缺点也很明显内存占用随序列长度线性增长。对于普通聊天上下文通常只有几千 token影响不大。但 Agent 场景不同。假设用户说一个问题模型调用一次工具工具返回结果后为了继续决策Agent 必须把原任务、中间推理、工具结果全部放回上下文。十轮工具调用之后历史可能轻松上万 token。如果模型是 7B 或者 13B加上大 KV Cache手机内存就吃不消了。这也是 Agent 开发者经常遇到的现象前几轮响应正常越往后越慢甚至直接 OOM。这通常不是代码问题而是模型架构在长上下文中存在内存扩展缺陷。3.2 LFM 2.5 的非 Transformer 设计方向LFM 2.5 之所以在端侧被频繁讨论是因为它在设计上与纯 Transformer 不同。官方描述中它属于非 Transformer 架构内部采用 LFM Block 这种更接近状态空间模型/线性递归机制的形态。这句话翻译成实际效果就是模型处理序列时不需要把全部历史 K 和 V 都显式缓存下来复杂度更接近线性而不是随 token 数快速膨胀。这带来几个连锁好处长上下文推理对内存增长更友好。手机端多轮 Agent 会话不容易出现后段内存暴涨。解码阶段更少的缓存读取量可能带来更稳定的 token 生成速度。我不是说 LFM 2.5 是目前手机端唯一的正确选择而是在选型时它的架构特点恰好命中 Agent 多轮长上下文的痛点。3.3 解码速度受什么限制在展开实测之前可以先做一个估算。自回归模型每生成一个 token理论上都要把权重从内存搬到计算单元。这个环节非常依赖内存带宽。有一个近似观点解码速度的上限约等于“内存带宽 / 每 token 读取的模型字节数”。所以决定手机端速度的主要因素不是“TOPS 算力”而是模型权重大小。内存带宽。量化方式。算子实现效率。上下文缓存读取量。对比 DNN、CNN、Transformer 等不同架构时计算模式和内存访问模式完全不同。手机端 NPU 通常擅长卷积类算子而大模型中的矩阵乘、注意力算子是否能高效映射到对应硬件取决于引擎优化程度。“大量使用算子对硬件性能的挑战”这句话放在这里再合适不过模型效果好不代表手机端算子库已经适配好。3.4 性能指标怎么选在调研阶段很多人只看一个“每秒生成 token 数”也就是 decode 速度。但 Agent 场景更复杂建议至少关注以下指标Prefill 耗时把用户 prompt 和全部历史一次性处理的时间。首 token 延迟模型开始说出第一个 token 的时间。Decode 速度生成后续 token 的速度。有效工具调用率模型输出的 JSON 能被正确解析并执行的比例。上下文增长后的延迟衰减率从 2K 上下文增加到 8K整体延迟是否明显劣化。如果只看单论解码速度会忽略 Agent 场景里“长历史重新 prefill”的隐患。4. 手机本地跑 AI Agent 实战流程接下来用一个最小可运行的 Agent 示例演示如何在手机本地完成模型推理、工具调用和性能记录。4.1 启动本地模型服务模型服务部分不绑定某一种引擎。原因很简单LFM 2.5 的开发版和开源社区支持度随时间变化硬写某一个命令很快过时。只要你的模型服务能暴露一个 HTTP 接口后面的 Agent 代码都可以复用。以兼容 OpenAI Chat 接口的本地服务为例启动后通常这样测试curl http://127.0.0.1:8080/v1/chat/completions \ -H Content-Type: application/json \ -d { model: lfm-2.5-3b, messages: [{role: user, content: 你好请用一句话介绍自己}], max_tokens: 128 }如果接口返回正常的choices内容说明模型服务已经可用。如果使用的是官方专用推理工具只要把输入输出转换为相同格式即可。为了安全起见建议先用小模型或本地已有模型跑通这个流程再替换成 LFM 2.5 量化版本。4.2 编写工具函数工具函数保存在tools.py中。这里实现三个典型能力计算器、天气查询、便签记录。# -*- coding: utf-8 -*- # 文件路径mobile-agent/tools.py import ast import datetime import operator import os _WEATHER { 北京: 晴25 度, 上海: 小雨18 度, 广州: 多云29 度, } _SAFE_OPERATORS { ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, ast.Div: operator.truediv, ast.Mod: operator.mod, ast.Pow: operator.pow, } def _safe_eval(expr: str): 只允许数字和四则运算避免 eval 注入风险。 node ast.parse(expr, modeeval) if not all( isinstance(item, (ast.Expression, ast.Constant, ast.BinOp, ast.Load, ast.operator, ast.unaryop, ast.USub, ast.UAdd)) for item in ast.walk(node) ): raise ValueError(只支持基本算术表达式) def _eval(node): if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)): return node.value if isinstance(node, ast.BinOp): return _SAFE_OPERATORS[type(node.op)](_eval(node.left), _eval(node.right)) if isinstance(node, ast.UnaryOp) and isinstance(node.op, (ast.USub, ast.UAdd)): operand _eval(node.operand) return -operand if isinstance(node.op, ast.USub) else operand raise ValueError(不支持的运算) return _eval(node) def calculate(expression: str): 计算表达式。 result _safe_eval(expression) return {expression: expression, result: result} def get_weather(city: str): 查询天气演示用静态数据。 return {city: city, weather: _WEATHER.get(city, 未收录该城市数据)} def save_memo(content: str): 把内容追加到本地文件模拟便签能力。 path os.path.join(os.path.dirname(__file__), memo.txt) with open(path, a, encodingutf-8) as f: f.write(f[{datetime.datetime.now()}] {content}\n) return {saved: True, file: path} TOOLS { calculate: {desc: 计算数学表达式, func: calculate}, get_weather: {desc: 查询城市天气, func: get_weather}, save_memo: {desc: 保存便签, func: save_memo}, }安全提醒我这里做了一个“只允许基础 AST 运算节点”的简化白名单生产环境如果要支持更复杂的计算还需要补充更多边界检查。不要把eval裸用于用户输入。4.3 编写提示词模板模型服务允许传入多个消息但为了让工具调用更稳定我建议在系统提示词中显式说明输出格式。# -*- coding: utf-8 -*- # 文件路径mobile-agent/prompts.py import json SYSTEM_PROMPT 你是一个运行在手机本地的 AI Agent。 你的职责是根据用户任务按需调用工具并通过观察结果完成最终回答。 可用工具如下 {tools_desc} 输出格式要求 1. 如果需要调用工具请输出一个 JSON 对象格式为 {{thought: 简短思考, action: 工具名, args: {{参数名: 参数值}}}} 2. 如果已经拿到工具结果可以结束任务请以普通文本回答用户。 3. 每次只能调用一个工具不要再没有工具结果时直接编造结果。 注意必须输出完整 JSON不要包含多余 Markdown 代码块标记。 def get_tools_desc(): from tools import TOOLS lines [] for name, meta in TOOLS.items(): lines.append(f- {name}: {meta[desc]}) return \n.join(lines) def build_system_prompt(): return _replace_placeholder(SYSTEM_PROMPT, {tools_desc: get_tools_desc()}) def _replace_placeholder(text, mapping): for key, value in mapping.items(): text text.replace({ key }, value) return text提示词看似简单却是决定 Agent 是否稳定输出结构的关键。如果模型服务支持原生 Function Calling也可以把 tools 按要求传给接口但提示词方案至少能做到跨引擎通用。4.4 编写 Agent 调度主循环核心逻辑在agent_core.py中。它负责把用户消息发给本地模型拿到回复后尝试解析 JSON如果能解析出action就执行对应工具再把工具结果追加到对话中继续下一轮如果没有 action则直接返回最终答案。# -*- coding: utf-8 -*- # 文件路径mobile-agent/agent_core.py import json import re import requests from prompts import build_system_prompt from tools import TOOLS BASE_URL http://127.0.0.1:8080/v1/chat/completions MODEL_NAME lfm-2.5-3b MAX_STEPS 6 def _parse_action(text: str): 从模型输出中提取 JSON。优先原样解析失败再剥离代码块。 text text.strip() try: return json.loads(text) except json.JSONDecodeError: pass match re.search(r(?:json)?\s*(.*?), text, re.S) if not match: return None try: return json.loads(match.group(1)) except json.JSONDecodeError: return None def _chat(messages, temperature0.2, max_tokens512): payload { model: MODEL_NAME, messages: messages, temperature: temperature, max_tokens: max_tokens, } resp requests.post(BASE_URL, jsonpayload, timeout180) resp.raise_for_status() data resp.json() content data[choices][0][message][content] usage data.get(usage, {}) return content, usage def _run_tool(action, args): if action not in TOOLS: # 静默忽略不存在工具改成错误反馈给模型 return {error: f未知工具: {action}} return TOOLS[action][func](**args) def run_agent(user_input: str): messages [{role: system, content: build_system_prompt()}] messages.append({role: user, content: user_input}) for step in range(MAX_STEPS): print(f\n[Step {step 1}] 正在请求本地模型...) content, usage _chat(messages) parsed _parse_action(content) if parsed is None: # 模型直接回答Agent 结束 return content, step 1 action parsed.get(action) args parsed.get(args) or {} print(f[Tool Call] action{action}, args{args}) observation _run_tool(action, args) # 把模型工具调用和工具结果加入上下文 messages.append({role: assistant, content: content}) messages.append({ role: user, content: f工具返回结果如下请基于结果继续处理\n{json.dumps(observation, ensure_asciiFalse)} }) return 步骤过多任务未完成, MAX_STEPS if __name__ __main__: user_task 北京和上海哪个更冷请用工具算一下温差然后保存便签。 answer, used_steps run_agent(user_task) print(\n Agent 最终回答 ) print(answer) print(f\n实际步数: {used_steps})这段代码有几个值得注意的地方。第一我把“模型输出 JSON”和“工具执行”放到了一个循环里每轮都会把 assistant 消息和工具结果作为新的 user 消息追加进去保证模型能看到自己的推理过程和工具输出。第二我设置了MAX_STEPS避免模型陷入无限工具调用。第三解析 JSON 时兼容了带 Markdown 代码块的情况因为很多本地模型经常会习惯性包裹代码块。如果你使用的模型服务支持原生 Function Calling可以在此基础上改成传tools参数并解析tool_calls字段。核心调度思路仍然一致。4.5 运行与验证在手机 Termux 或本地终端中运行pip install requests python agent_core.py如果模型服务正常可能看到类似下面的过程文本会因模型版本不同而不同[Step 1] 正在请求本地模型... [Tool Call] actionget_weather, args{city: 北京} [Step 2] 正在请求本地模型... [Tool Call] actionget_weather, args{city: 上海} [Step 3] 正在请求本地模型... [Tool Call] actioncalculate, args{expression: 25-18} [Step 4] 正在请求本地模型... Agent 最终回答 北京目前 25 度上海 18 度温差 7 度。已经把这条结果保存为便签。注意这里的城市名称、温度数字都是演示样例真实运行要看模型是否从天气工具获取到结果。如果模型最终没有输出有效 JSON而是在第一步直接回答用户任务说明模型没有理解系统提示中的输出协议。这时可以增加 few-shot 示例。降低 temperature。使用更明确的工具名和参数说明。换成支持工具调用的后端接口。4.6 性能回收脚本在 Agent 场景里仅靠体感判断速度并不可靠。建议在流程中记录每个步骤耗时。下面是一个简单脚本# -*- coding: utf-8 -*- # 文件路径mobile-agent/benchmark.py import json import time import requests from agent_core import _chat BASE_URL http://127.0.0.1:8080/v1/chat/completions MODEL_NAME lfm-2.5-3b def build_contexts(): 构造不同长度的测试上下文方便观察长上下文的性能变化。 for n in [1, 4, 8, 16]: messages [{role: system, content: 你是测试助理。}] for i in range(n): messages.append({role: user, content: f这是第 {i 1} 条测试消息用于填充上下文。}) messages.append({role: assistant, content: f收到第 {i 1} 条。}) messages.append({role: user, content: 请用一个词回答完成了吗}) yield n, messages for turn, messages in build_contexts(): start time.time() content, usage _chat(messages, max_tokens64) elapsed time.time() - start completion_tokens usage.get(completion_tokens, 0) print(json.dumps({ turn: turn, elapsed_sec: round(elapsed, 3), completion_tokens: completion_tokens, speed_token_per_sec: round(completion_tokens / elapsed, 2) if completion_tokens else None, answer: content[:20] }, ensure_asciiFalse))此脚本会在 1 轮/4 轮/8 轮/16 轮上下文中分别请求一次模型并记录整体耗时和生成速度。它的意义不是测官方 benchmark而是帮你观察上下文变长之后你的手机本地模型是否出现明显劣化。5. 常见问题与排查思路手机本地跑 Agent 的坑非常多下面按实际出现频率整理了一些问题现象常见原因解决思路模型服务启动后直接崩溃内存不足或量化不匹配换更小量化版本减少max_tokens与上下文长度Agent 不调用工具直接回答提示词协议不够明确加入 few-shot降低温度改为原生 Function Calling返回 JSON 总是带 Markdown 代码块模型对齐不充分在解析层用正则剥离代码块或继续修正提示词多轮后响应越来越慢上下文太长KV Cache 压力大使用长上下文友好的非 Transformer 架构模型或开启自动摘要工具参数解析错误工具描述与模型理解不一致参数名必须清晰避免歧义手机发热严重CPU 长时间满载限制线程数给手机散热必要时换 NPU 后端下载或同步模型失败工具版本问题或网络不可用确认文件完整通过只传输正式渠道的模型包同一任务多次结果不同采样温度过高调低 temperature设置随机种子如果后端支持遇到 Agent 调度问题不要一上来就重新训练或换模型先做最小复现只测一条工具调用把模型输出打印出来确认是“模型输出格式问题”还是“执行层解析问题”。大多数情况都出在提示词模板而不是模型本身。6. Agent 手机端化的最佳实践与工程建议6.1 不要把所有历史都无脑塞进上下文Agent 多轮交互很容易把上下文撑得很长。一个实用做法是维护一个消息窗口只保留最近几轮关键内容同时把早期结论提炼成摘要。实现思路def compress_if_too_long(messages, max_length8192): total sum(len(m.get(content, )) for m in messages) if total max_length: return messages # 保留 system 和最近的约 6 条消息其余丢弃或摘要 header [messages[0]] tail messages[-6:] return header [{role: system, content: 较早的历史已省略。}] tail这样做会牺牲一部分历史但能够避免内存和时延失控。LFM 2.5 这类长上下文友好模型可以减少压缩频率但不能完全取消。6.2 用标准化工具协议降低集成成本如果你的 Agent 工具会超过一到两个建议尽早统一工具描述格式甚至接入 MCP 等开放工具协议。这样每个工具只需要按照固定 schema 暴露输入输出Agent 调度层和模型服务层都不需要为每种工具写特判。工具描述写得越清晰模型输出越准。例如天气工具不要写“给我天气”而是写清楚{ name: get_weather, desc: 根据城市中文名查询实时天气信息。城市必须是完整名称例如北京、上海。, parameters: { city: string, 必填 } }6.3 把性能测试纳入日常回归Agent 代码改几个字可能会让模型调用工具的稳定性发生变化。建议准备一批固定测试用例每次调整提示词或模型后都跑一遍记录工具调用成功率。完成单个任务的平均步数。平均总耗时。上下文峰值长度。是否出现 JSON 解析失败。只有把这些数据量化后才能理性判断“LFM 2.5 在某个架构版本下是否变好”。6.4 安全边界本地 Agent 的工具授权要克制手机本地 Agent 可以访问通讯录、文件、摄像头等工具执行前必须做权限控制。最佳实践如下涉及读取隐私数据或修改系统数据的工具必须先经过用户确认。工具执行环境尽量使用独立子进程避免模型输出内容直接传给 shell。不要使用os.system(user_input)这类危险方法。工具返回值过滤掉不必要的敏感信息。把本地 Agent 当作“能访问部分设备能力”的代理而不是“可执行任意 shell 命令”的 root。6.5 多设备部署时要考虑硬件差异把同样的 LFM 2.5 模型分别部署在高通旗舰和入门机上结果可能完全不同。不要因为一台手机跑得顺就假定所有用户都顺。建议在架构层面预留CPU 线程数配置项。上下文长度配置项。模型量化级别配置项。工具列表开关。这样即使手机性能不足也可以通过关闭非必要工具、缩短上下文来保住主要功能。7. 总结与下一步围绕“LFM 2.5 在手机本地跑 AI Agent”这个主题这篇文章主要讲了三件事一是 Agent 对模型上下文和多轮稳定性要求更高不只看解码速度二是 LFM 2.5 这类非 Transformer 架构在长上下文和端侧资源受限场景中值得关注三是实现层面要尽可能把模型服务和 Agent 逻辑解耦并用可量化的方式记录工具调用耗时。如果你现在想动手验证可以按以下顺序进行先找一个能跑 LFM 2.5 量化版的本地推理工具开放一个 HTTP 接口再用文中的agent_core.py跑通“天气查询 温差计算 便签保存”这个最小任务最后用benchmark.py分别记录 4 轮、8 轮、16 轮上下文下的耗时数据分析和纯 Transformer 模型在相同手机上是否存在差异。除了模型本身下一步还可以继续深入量化优化、端侧 NPU 算子适配、Function Calling 协议、MCP 工具标准化以及 Agent 日志追踪。把这些工程问题逐一补齐后才能真正让 AI Agent 成为手机里的可靠助手而不是一个偶尔能用的技术 demo。