一个很反直觉的现象正在发生:Claude、ChatGPT 这类顶级大模型,训练和推理都依赖大规模 GPU 集群,通常需要整座数据中心来支撑;而另一些自然语言能力同样可用的模型,却能完整地塞进智能手机,离线运行、随时响应。最近 Show HN 上这类"跑在手机上的 LLM"项目并不少见,很多开发者看到它们的第一反应是:这到底有什么价值?
答案是,价值不在参数规模,而在工程优化。真正值得关注的是,端侧大模型把推理成本降了几个数量级,同时把数据隐私、响应延迟和可用性这三个云端推理长期绕不开的问题,第一次拉到了一个普通用户能直接感知的位置。这篇文章不打算评论"端侧会取代云端"这种大而空的趋势,而是想用一个真实的部署路径,说明从 HuggingFace 模型到手机可推理的小模型,中间到底发生了什么,哪些环节决定成败,以及你在自己项目里该怎么落地。
如果你正在做 AI 应用,或者只是在纠结"为什么有些模型能跑电脑,有些必须联网",这篇内容会给你一个从原理到实操的完整视角。读完后,你会掌握一套把 1B 到 3B 级别小模型导出、量化并部署到本地推理环境的通用方法,也知道在真实产品中哪些坑最值得提前避开。
1. 这篇文章真正要解决的问题
很多开发者在第一次接触"手机端大模型"时,会下意识地问三个问题。
第一个问题是:手机的内存和算力这么有限,凭什么能运行动辄几十亿参数的模型?这里需要重新理解参数与推理成本的关系。一个模型能不能跑在端侧,决定性因素不是"参数量听起来有多大",而是"单位 token 输出的计算量、内存占用和访存带宽"是否被优化到设备可承受范围。2B 参数的模型如果不做量化,FP16 权重就是 4GB 左右,手机跑不动;压缩成 INT4 后,权重降到 1GB 左右,配合 6GB 以上内存的设备,就有了可用的基础。
第二个问题是:这种模型的回答质量能不能达到 ChatGPT 的水平?更稳妥的判断是,通用能力确实有明显差距,但任务越垂直、场景越封闭,差距越小。如果你要的是"根据本地笔记生成周报总结"或"帮用户理解一段对话的情绪",一个 1.5B 甚至 800M 参数量的端侧模型完全够用。反过来,如果任务需要长程推理、复杂代码生成、多轮记忆,端侧模型会很快暴露上下文窗口和世界知识的短板。
第三个问题是:端侧推理和云端推理在工程上有什么区别?传统云端服务把模型放在 GPU 集群,你只需要发送文本、接收文本;但端侧模型需要你关心内存峰值、手机发热、系统后台回收、NPU 算子支持范围、模型更新策略等一系列问题。这意味着部署范式从"调用 API"变成了"打包运行时、精细控制资源"。
这篇文章真正想解决的问题,就是把这些工程问题拆开讲清楚:为什么端侧模型可行,什么样的任务适合端侧,以及如何把一个开源模型用 ONNX Runtime 跑起来,并最终为手机端集成做好准备。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 端侧 SLM 的核心概念与可行原因
2.1 为什么大模型不能简单往手机里塞
先看一个数字关系:一个 7B 参数的模型,FP16 精度下权重就需要约 14GB 内存,这还不算 KV Cache、临时激活值和其他推理依赖。常规手机内存 8GB 到 16GB,系统本身还要占用 3GB 到 6GB。所以未经压缩的大模型,在内存端就过不了关。
如果强行把模型放到磁盘并做内存映射,又会遇到另一个问题——带宽。大模型推理是典型的访存密集场景,每输出一个 token,都要把全部权重从内存搬运到计算单元。假设 7B 模型用 4-bit 量化后是 3.5GB,手机内存带宽 25GB/s 左右,理论上一秒最多输出约 7 个 token,实际体验会非常卡。这也是为什么端侧模型通常选择 1B 到 3B 级别,而不是 7B 以上。
2.2 SLM 能跑起来的四个支点
把模型塞进手机,靠的是四个技术支点协同工作,而不是某单一魔法。
第一是参数压缩。INT8 量化把权重精度从 FP16 压缩一半,INT4 再压缩到原来的四分之一。最常用的方式是 PTQ(训练后量化),用少量校准数据确定每个权重或每组权重的取值范围,然后映射到低精度整数。做得好时,2B 模型压缩到 1GB 以内,质量损失可以控制得很低。
第二是注意力机制的缓存策略。传统自注意力需要把历史 token 的 Key 和 Value 全部保留,长对话时 KV Cache 会持续增长。端侧推理引擎通常采用两种手段:限定上下文窗口,比如 2048 或 4096 个 token;或者使用滑动窗口注意力,只保留最近 N 个 token 的 KV 信息。这两招能控制推理过程中的显存和内存峰值。
第三是静态度图优化。桌面端 PyTorch 推理灵活,但开销大;移动端通常使用 ONNX Runtime、Core ML、TFLite 这类静态图引擎,先把模型的计算图固定下来,提前完成算子融合、内存复用和常量折叠,推理时少做很多动态判断。
第四是端侧专用硬件。Apple Silicon 的 Neural Engine、高通的 Hexagon NPU、各家 SoC 里的独立 NPU,都在为特定算子提供加速。不过 NPU 的算子覆盖范围通常不完整,所以工程上常见的做法是"多头并跑":NPU 跑支持好的算子,CPU 和 GPU 兜底。这里的隐性成本是硬件适配,而不是模型本身。
2.3 一个被很多教程忽略的约束
端侧模型能跑,不代表它容易跑。真正容易被忽略的问题是算子兼容性。同一个 Transformer 结构,在 PyTorch 里跑得好好的,导出到 ONNX 后,某个自定义 Attention 算子可能没有可用的内核,或者 NPU 驱动根本不认。尽头是下一行日志,而不是模型代码。这也是我建议一切从标准算子开始的原因:先跑通标准实现,再引入自定义优化,否则排查成本会成倍上升。
3. 传统云端方案与端侧方案的真实差距
3.1 从架构层面看变化
传统云端 LLM 应用的架构往往是:移动端 App 通过 HTTPS 调用 API Gateway,再进入统一推理服务,最后转发到 GPU 集群;鉴权、限流、上下文管理都在服务端完成。引入端侧 SLM 后,架构会变成两层推理:本地优先,云端兜底。用户请求先走手机本地推理,遇到无法回答或需要联网知识的问题时,再把请求转发到云端模型。
这不是简单"把模型换个地方跑",而是把决策逻辑从服务端挪到了客户端。你会发现成本模型、更新方式和监控粒度全部要变。成本模型上,端侧让推理的边际成本趋近于零,但增加了包体积和分发成本;更新方式上,模型不能像 API 那样一键热更新,通常要做版本管理和灰度发布;监控粒度上,云端可以拿到每条 prompt 和 response,端侧却只能拿到用户主动上报的日志,数据回传策略必须重新设计。
3.2 从用户感知层面看体验
另一个维度是体验。端侧模型几乎没有"首字延迟",因为不需要网络往返;模型是本地文件,所以完全离线可用,飞机模式和弱网环境下不再掉线;对话状态可以全程留在手机本地,隐私模型从"服务商承诺不保存"变成了"数据物理上不出设备"。
但代价也很实在。端侧模型的知识截止时间取决于打包日期,收集不了实时信息;回答质量和多轮连贯性受上下文窗口限制;每次应用启动后,模型冷加载需要时间;持续对话时手机发热和耗电明显。所以端侧和云端不是替代关系,而是互补。更现实的规划是:把高频、短上下文、隐私敏感的任务放本地,把复杂推理、实时检索增强、超大上下文任务留在云端。
3.3 从工程协作变化看团队分工
传统 AI 功能团队通常由服务端开发负责模型 API 集成,客户端开发只负责展示。引入端侧模型后,客户端团队必须理解模型运行时的生命周期、内存管理、推理线程调度,甚至要参与模型量化效果的测试。模型侧新增了模型压缩、端到端测试、AB 实验的职责。换句话说,团队里要多一种"端侧推理工程师"的能力,这对组织结构不敏感的公司来说,是比技术选型更难的一关。
4. 环境准备与前置条件
4.1 硬件与操作系统
理解端侧推理全流程,一台有至少 16GB 内存的 Mac 或 Linux 机器就够了。Windows 也可以,但下文命令以 macOS/Linux 为主。手机端验证可以选择任意一台 Android 或 iOS 设备,但本文不会直接编译到手机,而是先用电脑跑通 ONNX 模型,再把它作为手机工程的模型输入。
4.2 Python 环境
版本建议 Python 3.10 以上,实际版本以 ONNX Runtime 和 transformers 的兼容要求为准。这里使用虚拟环境,避免污染系统 Python。
bash复制mkdir -p local-llm-demo && cd local-llm-demo
python -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install transformers torch onnxruntime optimum[onnxruntime]
如果机器上没有安装 PyTorch,第一条命令会比较耗时。导出模型需要 torch,但最终推理阶段可以不依赖它。需要提示的是,huggingface_hub 会自动下载模型,如果网络访问 HuggingFace 受限,建议预先设置镜像源,这不是教程核心,不再展开。
4.3 模型选择建议
本文示例使用 1B 到 3B 级别的指令微调模型。你可以选择 Qwen2.5-1.5B-Instruct、Gemma-2-2B、Phi-3-mini 等公开权重中的任意一个,只要它是 causal LM 并带 instruct 版本。不同模型的 tokenizer 和特殊 token 不完全一样,但导出与推理的代码结构基本一致。
如果你不确定选哪个,优先选自己实际业务场景里更常见的 tokenizer 词表,以及社区讨论较多的版本。中文项目建议优先考虑通义系列小模型,英文项目优先考虑 Gemma 或 Phi 系列。
5. 完整示例:从开源模型到端侧推理
5.1 第一步:导出 ONNX 模型
核心目标是把 PyTorch 权重转成静态计算图。这里使用 HuggingFace Optimum 的 ONNX Runtime 集成,导出过程会自动处理 KV Cache 和 past_key_values 的图结构。
python复制# 文件路径:local-llm-demo/export_onnx.py
from transformers import AutoTokenizer
from optimum.onnxruntime import ORTModelForCausalLM
# 换成你要部署的模型,优先选择指令微调版本
model_id = "Qwen/Qwen2.5-1.5B-Instruct"
save_dir = "./onnx_model"
model = ORTModelForCausalLM.from_pretrained(model_id, export=True)
tokenizer = AutoTokenizer.from_pretrained(model_id)
model.save_pretrained(save_dir)
tokenizer.save_pretrained(save_dir)
print("ONNX model saved to", save_dir)
运行方式:
bash复制python export_onnx.py
这一步如果报内存不足,是因为导出时 PyTorch 模型仍然以 FP16 或 FP32 加载,可以先换更小模型,或确保设备剩余内存充足。导出完成后,onnx_model 目录下会多出 model.onnx、config.json 和 tokenizer 相关文件。
5.2 第二步:动态量化压缩权重
ONNX 推理支持多种量化方案。对于端侧部署,优先考虑 INT8 动态量化,它不需要复杂的校准数据集,代码量短,而且对精度影响相对可控。真正追求极致压缩时,再考虑 INT4 静态量化和更专业的量化工具。
python复制# 文件路径:local-llm-demo/quantize_onnx.py
from onnxruntime.quantization import quantize_dynamic, QuantType
model_input = "./onnx_model/model.onnx"
model_output = "./onnx_quantized/model_int8.onnx"
quantize_dynamic(
model_input=model_input,
model_output=model_output,
per_channel=True,
weight_type=QuantType.QInt8,
)
print("Quantized model saved to", model_output)
运行方式:
bash复制mkdir -p onnx_quantized
python quantize_onnx.py
量化时需要注意,不同版本的 onnxruntime 对 per_channel 和 weight_type 的支持度不完全一样。如果 per_channel 报错,可以先去掉该参数;如果 QInt8 不可用,可以回退到 QUInt8。核心原则是:先跑通默认参数,再根据自身设备的 NPU 支持情况做调整。
5.3 第三步:本地推理验证
量化后的模型必须先在电脑上验证效果,再进入移动端。这里仍然用 Optimum 加载量化后的 ONNX 目录,它会自动读取同目录下的 tokenizer 和配置。
python复制# 文件路径:local-llm-demo/infer_onnx.py
from optimum.onnxruntime import ORTModelForCausalLM
from transformers import AutoTokenizer
model_path = "./onnx_quantized"
model = ORTModelForCausalLM.from_pretrained(model_path)
tokenizer = AutoTokenizer.from_pretrained(model_path)
prompt = "请用一句话解释动态规划。"
inputs = tokenizer(prompt, return_tensors="pt")
outputs = model.generate(
**inputs,
max_new_tokens=128,
do_sample=True,
temperature=0.7,
)
response = tokenizer.decode(outputs[0], skip_special_tokens=True)
print(response)
运行方式:
bash复制python infer_onnx.py
如果模型本身的 tokenizer 需要 chat template,直接用 apply_chat_template 会更稳妥。这里为保持示例简短,直接传入纯文本。你只需要知道一点:量化模型的中间检查点,是手机端项目接入前最重要的一道质量验证关,不能跳过。
5.4 第四步:移动端接入的准备思路
把同一个 ONNX 模型搬到手机,不会用到 Python 代码,而是会用到 Android 上的 ONNX Runtime Mobile 或 iOS 上的 Core ML 转换工具。这一步的通用流程是:先为移动 CPU/NPU 选择合适的内置算子,再把 ONNX 模型转成移动运行时的格式,最后在真实设备上测试首 token 延迟和内存峰值。这里不展开完整工程代码,是因为各厂商 SDK 更新频繁,直接贴一版很可能过期。
如果你想快速体验,最简单的路径是:在手机上安装一个支持本地 ONNX 模型的开源聊天 App 或 demo 工程,把自己量化好的 model_int8.onnx 放入模型目录,再修改应用内模型路径和 prompt 模板。这种"模型验证优先、代码集成其次"的顺序,能帮你避免在移动端代码还没写完时,才发现模型本身有问题。
6. 运行结果与效果验证
6.1 如何判断导出与量化成功
运行 export_onnx.py 后,终端出现 ONNX model saved to ./onnx_model 并不代表一切正常,还要检查目录中是否有 model.onnx 文件,文件大小接近原始模型大小。运行 quantize_onnx.py 后,model_int8.onnx 文件应明显小于 model.onnx,通常能缩小到原模型的 30% 到 50%。如果文件大小没有变化,说明量化没有真正生效,优先检查 onnxruntime 版本和日志。
6.2 如何判断推理正确
运行 infer_onnx.py,如果输出是通顺且有语义的中文回答,说明模型结构、tokenizer、量化流程都没问题。如果输出乱码,优先检查量化权重和 tokenizer 路径。如果输出空字符串,优先检查 max_new_tokens 和输入长度是否超限。如果回答全篇重复,可以降低 temperature 或适当提高 repetition_penalty。
6.3 如何在手机端做验收
手机端验收至少要记录三个指标:模型加载时间、首 token 时间、稳态内存占用。首 token 时间在 500ms 以内,体验是"明显快于云端";到了 2s 以上,用户会认为卡顿。稳定的内存占用则要控制在系统可用内存的合理范围内,否则 App 容易被系统后台回收。实际测试时,不是看模型文件大小,而是看运行态内存,因为 KV Cache 会随对话长度变化,这是最容易与本地小模型产生认知偏差的地方。
7. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 导出 ONNX 时内存不足 | PyTorch 模型以 FP32/FP16 全精度加载 | 查看 PyTorch 进程内存占用 | 换更小模型,或增加系统 swap |
| 量化后模型文件大小没变化 | 量化版本 API 不生效或算子不支持 | 重新运行脚本,打开量化日志 | 升级 onnxruntime,调整 weight_type |
| 推理输出乱码 | tokenizer 与模型不匹配 | 检查模型目录里的 tokenizer 文件 | 重新保存 tokenizer,确认同源模型 |
| 输出整段重复 | 采样温度过高或 no_repeat_ngram 未设置 | 减小 temperature,加入 repetition_penalty |
使用更稳定的 beam search 或调整采样参数 |
| 手机推理异常卡顿 | 模型未量化或 NPU 算子未生效 | 查看推理时 CPU/GPU/NPU 调度日志 | 换用移动端专用运行时并显式指定设备 |
| 多轮对话后内存持续上涨 | KV Cache 未做限制 | 观察对话轮次与内存曲线 | 限制上下文窗口,使用滑动窗口注意力 |
第一行是新手最常遇到的场景:电脑内存 16GB,加载一个小模型导出也会因权重和计算图同时占用而失败。解决办法不是硬扛,而是换一个更小的模型,或者分阶段导出。
8. 最佳实践与工程建议
8.1 模型选型先定任务边界
不要一开始就看谁跑分高,而是先定义清楚"哪些功能必须本地化"。我把任务分成三类:高频短文本任务,比如摘要、分类、意图识别;隐私敏感任务,比如邮件草稿、健康记录、本地笔记关联推荐;离线必需任务,比如飞行模式下的翻译和辅助输入。这三类最适合端侧。复杂代码生成、长文档问答、实时知识检索,仍然更适合云端。
8.2 量化精度按版本管理
模型一旦发布到用户手机上,更新成本比 API 高得多。因此建议把量化模型作为一种"可回滚的产物"来管理:每个版本对应明确的基座模型版本、量化配置、验证结果和回滚路径。上线前用一组固定的 prompt 测试集跑回归,确认关键任务质量没有明显回退。
8.3 本地优先、云端兜底,而不是二选一
最实用的产品架构是双路推理。第一次启动时先加载小模型,所有请求走本地;当本地模型置信度低,或用户明确要求联网搜索时,再自动请求云端。这个"置信度阈值"需要结合模型输出的 logits 特征做统计,不能简单依赖关键词命中。
8.4 设置严格的上下文窗口和降级策略
对话类应用最容易让端侧模型超载。即使模型支持 4096 token 上下文,长时间多轮聊天后内存峰值也很大。工程上建议采用:滚动清空或摘要压缩,把早期轮次压缩成一段摘要;流式输出与停止机制,用户停止输入时及时取消推理任务;内存水位告警,接近阈值时自动切换云端或提示用户。
8.5 测试必须覆盖真机温度与后台恢复
电脑上跑通只是起点。手机端的真实致命问题往往是发热和后台恢复。推理 5 到 10 分钟后,局部温度升高,系统会主动降频,推理速度会断崖式下降。另一个问题是 App 切到后台再回来,模型内存被系统回收,重新加载可能造成秒级卡顿。建议在测试集中增加"连续 20 轮对话"和"切后台 10 分钟再恢复"两个用例,这两个用例能过滤掉大量纸面可行、实际不可用的方案。
8.6 日志上报要克制,且必须获得用户授权
端侧模型优势之一是隐私,日志策略不能反过来破坏这个卖点。默认情况下,prompt 和 response 不应离开设备;只有用户主动选择"帮助改进"时,才上报脱敏后的统计指标。这个设定不仅是为了合规,也直接影响用户对产品的信任度。
9. 总结与后续学习方向
回到开头那句话:Claude 和 ChatGPT 确实需要数据中心,但这不代表所有 AI 能力都必须依赖数据中心。用 1B 到 3B 级小模型配合量化和端侧推理,你能把不少高频、隐私敏感、低延迟要求的任务直接放进用户设备里,换来的是更低的成本、更快的响应和更强的安全感。
本文真正讲清楚了四个关键点:端侧模型为什么能跑,靠的不是参数魔法而是量化、缓存、静态度图和硬件加速协同;端侧与云端不是替代关系,而是互补关系;从 HuggingFace 模型到 ONNX 量化模型再到移动端集成,是一个可复现的工程流程;真实项目里最需要关注的是内存峰值、发热、上下文管理和模型更新策略,而不是模型参数量本身。
下一步实践路径很清晰:先选定一个你业务里最常用的小型指令模型,按文中三份脚本跑通导出、量化和本地推理;然后拿起手机,把它放进一个支持本地 ONNX 模型的开源客户端,跑一遍多轮对话和后台恢复测试;最后记录首 token 时间、内存峰值和温度变化,再判断这个方案是否适合你的产品。
不要急着给所有用户推端侧模型。更稳妥的做法是,先在灰度用户里跑一套双路架构,用真实数据比较端侧和云端的满意度、成本和失败率。等到数据证明端侧体验真的达标,你自然会得出一个比今天任何趋势判断都准确的结论。
