一台手机跑三个模型:端侧分工、精度档位与崩坏判定

我们整理端侧资料时见过一份少见的部署记录:同一台手机上用同一个推理引擎放了三个小模型,按”写作、思辨、技术问答”三种用途分工;后端优先级按 NPU、GPU、CPU 排;三个模型统一保持半精度,并把”精度切到低档”直接指认为输出崩坏的诱因。这份记录没有跑分,也不评价别的引擎与芯片,它给出的是一整套可抄的参数结构。对做端侧交付的人来说,它的价值恰好在这里——手机上的模型工程,难的从来不是”单个模型能不能跑起来”。
一、难点是常驻,不是能跑
单机跑一个零点几 B 或两 B 的模型,今天的公开引擎基本都能做到,社区里也有大量现成示例。真正让工程变难的是三件事同时发生:
- 权重常驻。多个模型都留在内存里,内存水位就是一条长期占用,而不是单次调用的峰值。系统回收、后台被杀、可用内存随使用时间下降,这些都不是模型侧的问题,是驻留策略的问题。
- 加载与切换耗时。如果每次换用途都要重新加载权重,用户感受到的”卡顿”发生在切换那一刻,跟推理速度无关。
- 参数维护成本。三个模型就有三套采样参数、三套系统提示词、三套输出上限,任何一处改了什么,另一处没人记得对齐。
据该部署记录,它的应对思路不是”堆一个更大更强的模型全包”,而是两条:一是让每个模型只管一类活,输出上限按用途分别设(写作放得宽一些,思辨和技术问答收一些);二是用 mmap 之类的方式降低驻留压力,并在长话题结束后主动新开会话,把上下文累积切断。这两条都不是模型能力的优化,而是内存与状态治理的优化——这也是我认为这份记录值得当成方法论来看的原因。
二、按分工挑模型,而不是按能力挑
那份记录里三个模型的规模分别是 2B 与两个 1.5B,来自同一个开放权重家族,且都是量化版本;选型给出了两条依据。
一条是引擎适配成熟度。同一架构在所选引擎上的兼容性被评价为最好,另一系列则记录过思考标签解析出现乱码的问题。这里的重点不是”谁更强”,而是解析层的确定性:一旦模板标签解析不稳,输出会表现为结构性错乱,而不是变慢。
另一条是场景错位。三个模型覆盖的时段与用途错开,避免”多模型同时高负载”,也就避开了单一大模型什么都做但持续发热的形态。把这两条抄回自己项目时,我建议先写成一句话再动手:本项目里最不能出错的是输出结构还是首字延迟?最贵的是内存还是电量?答不清楚的话,很容易退回到”选个最大的”。
多模型分工也不是白得的,它带来两个新问题:一是模型之间会不会给出互相矛盾的回答(写作模型与代码模型对同一事实的表述不一致,用户会当成 bug);二是路由判断放哪一层(在端侧用一个规则或极小的分类器判用途,判错了要能一眼看出来是哪条规则命中的)。这两点在评测里都要有用例,不能只测单模型质量。
三、精度切低换到的往往不是资源,而是崩坏
那份记录里最反直觉的一条是:三个模型全部保持 fp16,不做低精度量化,同时把推理精度参数选到 low 档指认为 token 崩坏的高发诱因——表现为满屏重复字符与乱码。
这件事在工程上的要害是故障形态。慢是能被感知的:用户知道在等,日志里有耗时,监控有阈值。崩坏不是:输出照常流出来,进程不报错,请求成功返回,长度也正常,内容却已经不能看。它更容易被当成”模型能力不行”记进结论里,而不是当成一次可复现的配置事故。
由此得到一条纪律:精度档位是一次行为变更,改完必须跑崩坏判定,而不是改完跑一遍看起来顺手的对话。判定要写成可复现的用例(下面第六节给清单),并且用例集要固定长度约束与采样参数,否则换了温度就等于换了实验条件,前后不可比。
另外要防的是”资源账算错方向”。低精度确实减少权重体积与内存占用,但一旦触发崩坏,用户重试、应用重问、会话加长,实际占用反而上升,而这些消耗会落在”用量”里,看不出根因。我们做端侧成本归因时,会把”同一会话内的重复提问率”单独列一栏,就是为这种情况留口子。
四、后端优先级与线程数是一对联动变量
据该记录,后端优先级按 NPU(走 QNN 接口)、GPU、CPU 的顺序排;而在以 NPU 为主算力时,线程数收敛到 2 到 3。
很多团队的直觉是”线程越多越快”,这在端侧经常是反的。端侧推理的瓶颈常常不在算力分配,而在调度竞争与温控:线程开多了,与前台应用抢同一批大核,系统调度会拉长尾延迟;持续高负载触发降频,前后两次的耗时曲线就不具备可比性了。用很少的线程把请求交给专用算力单元,反而更稳。
这份记录没有给功耗与发热数字,我也不替它补。要拿到自己项目的结论,得把曲线测出来:同一固定提示词、同一后端、连续跑固定次数,记录首字延迟、逐 token 间隔,同时记录机身温度与电量变化。测三件事:后端回退链是否真的按 NPU、GPU、CPU 顺序生效(把首选后端人为关一次),线程数在 1、2、3、4 各档下的尾延迟差别,以及降频发生后的恢复时间。这三条测出来,后端与线程就不是抄别人的数,而是自己环境里的既定配置。
五、核心交付物之一:参数分档与内存驻留治理表
那份记录的参数我原样列在下面。要说明的是,这张表是”某台机型、某个引擎、某三个模型的实测配置”,不是通用推荐值;换机型换引擎之后必须重测。我们把它当成表结构模板使用,把数值当成示例。
| 治理项 | 写作模型 | 思辨与日常推理 | 技术与编码问答 | 落地的纪律 |
|---|---|---|---|---|
| 定位 | 长文主笔 | 日常推理主力 | 技术问答 | 一个模型只做一类事 |
| 规模档 | 2B | 1.5B | 1.5B | 按最小够用选,不按能力上限选 |
| 采样温度 | 0.48 | 0.52 | 0.35 | 发散任务偏松,事实任务偏紧 |
| Top P | 0.88 | 记录未单列 | 记录未单列 | 与温度一起定,不单独调 |
| Min P 过滤 | 开启 | 开启 | 开启 | 三个模型统一 |
| 重复惩罚 | 1.03 至 1.04 | 1.03 至 1.04 | 1.03 至 1.04 | 全档统一,写成常量 |
| 输出上限 | 1024 | 768 | 768 | 上限即成本护栏,按用途分设 |
| 精度 | fp16 | fp16 | fp16 | 低精度档必须跑崩坏用例 |
| 后端优先级 | QNN 优先,其次 GPU,再次 CPU | 同左 | 同左 | 回退链可人为验证 |
| 线程数 | 2 至 3(NPU 主算力) | 同左 | 同左 | 与后端一起定,不单独调 |
参数表之外,还要有驻留与状态这一半,否则参数对了仍然会掉。
| 驻留项 | 做法 | 要留的观测 |
|---|---|---|
| 权重驻留 | 以 mmap 方式映射,降低常驻内存压力 | 每个模型的驻留量与内存水位曲线 |
| 会话长度 | 长话题结束后新开会话,切断上下文累积 | 累积 token 数与内存水位的连带关系 |
| 缓存失效 | 明确失效条件:会话新建、模型切换、参数变更都要清 | 缓存命中时的参数版本号与提示词版本号 |
| 切换开销 | 常驻优先,切换耗时写进体验预算 | 切换一次的墙钟时间 |
| 参数来源 | 全部走配置,不写死在代码里 | 配置项标识、版本、生效时间 |
上下文累积这件事在端侧比在服务端更凶:内存是硬墙,服务端可以靠排队,手机上只能靠丢。丢什么必须是设计过的,我们把它写成配置而不是临时决定。
六、核心交付物之二:崩坏判定用例集与现场验收
崩坏用例的目标不是”看看效果”,是能在换精度、换引擎版本、换后端之后,用同一条判据说”输出还能不能看”。我们沿用四类用例:
- 重复字符压测:固定短输入、固定采样参数、要求生成到输出上限,判据为同一字符或同一短语连续重复超过阈值即记失败。
- 标签与结构完整性:带思考标签、工具调用格式、代码块的样例,判据为标签成对、结构可解析,解析失败直接算崩坏而不是”质量略差”。
- 短输入保真:一两句话的极短输入,判据为用户原文在回答里不缺失。据该记录,1.5B 模型在极短输入下会丢掉输入的起始部分,做法是重写系统提示词,强制不得丢弃、忽略或改写用户输入的任何内容来缓解。这类模板拼接偏差靠模型自述发现不了,只能靠用例,并保留回归样本。
- 长文尾部退化:接近输出上限的长生成,判据为尾部段落仍与开头同题,不出现乱码与语种漂移。
现场验收时,除用例之外还要交付四样东西:崩坏用例的清单与判定阈值、后端与线程的配置及回退验证方法、温度与续航曲线的测量步骤(不给通用数值,只给方法与结论落点)、参数表与模型版本的一致性核对。端云分工也要在验收里讲明白:数据不出设备的硬性要求走端侧,需要跨领域知识或多约束规划的只能上云,离线时的降级形态要提前定义——是”只答本地已有的内容”还是”明确告诉用户等联网”,不许由实现临时决定。
最后是一条我不愿意让项目省略的立场:端侧多模型的收益来自分工,风险来自状态。参数是显性的,谁都能看;驻留、会话长度、缓存失效这些是隐性的,出事时才看得见。显性部分照抄别人的表,隐性部分按自己的机器重测,这才是这份记录对洹拓做端侧交付时的正确用法。