芯片圈的营销数字,向来比芯片本身还要先到。这一次,“第六代骁龙8”还没正式亮相,关于它的描述已经把“prefill +80%”这组数字推到了风口浪尖。看到这组数字,第一反应不该是“卧槽好强”,而应该是“这是怎么算出来的”。因为在大模型推理里,prefill 和 decode 是两条完全不同的性能路径,把其中一条单独拎出来提升 80%,背后涉及的架构改动可能完全不是一回事。这篇文章就来做一次纯粹的架构拆解:从 LLM 推理的底层逻辑出发,把 prefill 的性能瓶颈、可能的架构优化点,以及端侧 AI 算力宣传里那些容易被忽略的“水分”一次性说透。适合正在做端侧模型部署的工程师、想搞明白手机 AI 性能指标的用户,还有所有对“AI 跑在手机里”这件事感兴趣的人。
1. prefill 为什么是端侧 AI 的“隐形瓶颈”
1.1 prefill 和 decode:LLM 推理的两个世界
要拆解“prefill +80%”,得先搞清楚 prefill 在模型推理里到底是个什么角色。大模型生成一段文字,不是一次性把整段话“吐”出来的,而是分成两个完全不同的阶段:第一个阶段叫 prefill,把用户输入的全部 token 一次性喂给模型,并行计算每一层的自注意力,生成一个完整的 KV cache(键值缓存);第二个阶段叫 decode,拿着这个 KV cache,逐个 token 地预测下一个词,每生成一个 token 都要重复一遍“取缓存、算注意力、预测”的循环。
这两个阶段的硬件行为截然不同。prefill 是典型的“计算密集+并行密集”任务:一次性处理 2000 个 token,等效于一次超大矩阵乘,能把计算单元喂饱,算力利用率可以拉得很高。decode 则是典型的“访存密集”任务:每一步只算一个 token,矩阵乘变成矩阵向量乘,真正干活的乘加运算不多,绝大部分时间都花在把权重和 KV cache 从内存里搬进搬出上。
拿生活里的例子打比方:prefill 好比一次性把一卡车快递全部卸下来分拣,考验的是场地吞吐和分拣线的处理能力;decode 好比分拣完之后,一件一件地往不同的派送点送,每送一件都要重新查一次地址簿,真正走路的时间比查地址簿的时间少得多。所以,decode 阶段的核心瓶颈从来不是“算得快不快”,而是“内存带宽够不够宽”。
1.2 端侧场景中,prefill 的问题被放大了
在云端数据中心的场景里,prefill 通常只占请求处理时间里比较小的一部分,甚至可以通过专用的 prefill 节点、动态批处理来优化。但在端侧,prefill 的问题会被放大很多倍。
端侧跑大模型,典型的输入是几百到几千个 token。比如你让手机里的助手读一篇长文档、总结聊天记录,输入可能轻松超过 2000 token。这时候 prefill 不再是“启动一瞬间”的问题,而是用户能直观感知的卡顿:点击发送之后,可能要等好几秒,屏幕上的光标才转完。这个等待时间就是首 token 延迟(TTFT),它几乎完全由 prefill 速度决定。
还有一个容易被忽略的问题:端侧没有独立的 prefill 节点和 decode 节点,没有“调度器”可以把不同用户的请求分批塞进同一个 prefill 周期里。单个请求的 prefill 就是全部,算力再强,如果内存带宽跟不上大块数据的搬运,prefill 时长一样下不来。另外,KV cache 是 prefill 阶段生成的,它的质量和大小直接决定后续 decode 阶段能跑多快。prefill 如果“算得潦草”,KV cache 就得花更大空间补偿,decode 阶段的缓存搬运压力也跟着上去。
所以,如果“prefill +80%”是真实的、可感知的提升,它绝不只是一颗 NPU 的乘法器变多了那么简单。它一定意味着整个内存子系统、数据流架构和模型执行方式都动了手术。
2. 从架构看,prefill 能快 80% 到底来自哪里
2.1 张量引擎的实壮:MAC 阵列和数据流
先看点最直接的:NPU 里负责大量乘加运算的硬件单元。绝大多数 NPU 的核心是一大片 MAC 阵列(乘法累加单元阵列),按照脉动阵列或者类 SIMD 的方式组织。决定 prefill 峰值算力的核心变量,就是这片 MAC 阵列的总宽度:阵列越宽,每个时钟周期能执行的乘加次数越多。
把 MAC 数量扩大,峰值 INT8 算力(TOPS)会跟着线性上涨。但 prefill 不是单纯堆 MAC 就能跑满的。一个大模型 Transformer 层的计算,本质是把输入序列的 token 向量和巨大的权重矩阵做乘法。这个矩阵大得远超 NPU 的片上缓存,所以必须把矩阵“分块”,一块一块地从 DRAM 搬到片上 SRAM,算完再搬下一块。这个搬运动作如果调度不好,MAC 阵列有大量时间在“空转等数据”。所以第六代如果真把 prefill 提了 80%,大概率不只是“加了乘法器”,而是把数据流结构改了——比如增加了片上 SRAM 的容量和 bank 数量,让每一块权重都能更快到位;再比如把 MAC 阵列的互联拓扑从“一维链式”改成“二维网状”,减少数据和权重在阵列内部广播的延迟。
一个很值得注意的方向是“稀疏化”。Transformer 的权重矩阵在做完剪枝之后,会有大量的零值;如果硬件能识别这些零并跳过,等效算力就能在“峰值不变”的情况下实际提升不少。苹果的 ANE、高通的 Hexagon NPU 这几代都在往结构化稀疏的方向走,但稀疏带来的加速往往是“纸面更美”。是否能让软件栈真的把稀疏格式变成 prefill 的提速,取决于编译器能否在运行时快速把权重压缩成适合硬件啃的格式,这是一个非常大的工程挑战。
2.2 系统级协作:内存带宽与异构调度
prefill 提升 80% 的另一条重要路径,在 NPU 之外。骁龙这种 SoC,从来不是单靠一个 NPU 在打仗,而是 CPU、GPU、NPU、DSP 多核协同。prefill 这种大块并行计算,其实非常适合拆给 GPU 的通用计算单元来做,而 NPU 在量化推理上有能效优势。一个合理的架构是:prefill 阶段把适合并行矩阵乘的部分分给 GPU,把量化、稀疏、KV cache 压缩这类适合专用引擎的部分留给 NPU,实现“两条腿一起跑”。
更关键的是内存带宽。即便 MAC 阵列翻倍,如果内存带宽不跟着涨,prefill 照样会被卡死在数据搬运上。这里可以做一个简单的估算:假设第五代平台的内存带宽是 70GB/s 级别,跑一个 7B 模型、2000 token 的 prefill,需要读取权重的总量大概是 14GB(按 8bit 权重算),光权重搬运就需要 0.2 秒;如果权重用 4bit,数据量减半到 7GB,搬运时间降到 0.1 秒。把内存带宽升级 80%,意味着同样数据量的搬运时间能再压缩近一半。所以,可预见的硬件迭代里,LPDDR5X 向 LPDDR6 的带宽升级,可能才是“prefill +80%”真正的原动力,而不是 NPU 那一排排加法器。
还有一个容易被忽视的系统级设计是“一致性缓存”。prefill 阶段很多中间结果(尤其是激活值)不用立刻写回 DRAM,如果片上缓存能把这些数据“扣住”供下一层计算复用,就能省掉大量的 DRAM 往返。骁龙之前就在宣传自己的“智能缓存”机制,第六代如果把缓存一致性协议做得更好,prefill 实际的等效带宽还能再高一大截。
2.3 注意力路径的显式加速
Transformer 的 prefill 计算,除了大矩阵乘,还有一大块是自注意力(Self-Attention)。注意力机制需要对输入序列的每个位置计算与其他所有位置的相似度,这个计算复杂度随序列长度的平方增长。2000 个 token 的输入,要算 400 万个位置对。这个计算和矩阵乘不一样,它的访存模式是“不规则的”:每个位置的数据都要和其他所有位置的数据比较。
业界过去几年在 GPU 上通过 FlashAttention 这类 kernel 来优化:把注意力计算分块,让每个小块的数据尽量留在片上,减少 DRAM 访存。这个优化思路移植到端侧 NPU 上,效果会非常显著。如果第六代骁龙在 NPU 里加入了专门针对注意力计算的硬件路径——比如支持更灵活的块间数据交换、为 attention score 计算提供专门的快速单元——prefill 对这种平方级计算量的任务就能实现远超 80% 的提升。反过来,如果原厂的软件库没有把 FlashAttention 式的分块策略做进 NPU 驱动里,哪怕硬件支持,提升也出不来。
KV cache 的压缩也是这条路径上的重头戏。prefill 生成 KV cache,decode 阶段要反复把 KV cache 从内存里搬出来。KV cache 用 4bit 量化,搬运量立即减半;用“KV cache 低秩分解”,还能在不明显损失质量的情况下进一步压缩。这类优化对 prefill 的“附属收益”是:prefill 阶段生成、写回 KV cache 的耗时降低,同时 decode 阶段读取缓存的带宽压力也同步减小。如果第六代在硬件上直接支持 KV cache 的量化再写入,prefill 的端到端体验提升就远比 80% 的纸面数字更明显。
3. 端侧 AI 的“算力骗局”:TOPS 只是一个开始
3.1 TOPS 是怎么被算出来的
聊完 prefill,再来聊一个更扎心的话题:“算力”这个数字,到底有多少水分。我们在发布会和参数表上看到的 TOPS,是一个“理论峰值”,它的计算公式很简单:TOPS = 频率(MHz)× MAC数目 × 2 × 精度系数 ÷ 1 万亿。注意这个“精度系数”:同一颗芯片,跑 FP16 算力可能是 30 TOPS,跑 INT8 算力可能标到 60 TOPS,跑 INT4 甚至能标到 120 TOPS。厂商喜欢往大了标,于是你看到的就是“第六代骁龙8 算力破 xxx TOPS”这种大数字。
但问题在于:大模型推理里,绝大部分真实负载并不等于干净的 INT8 矩阵乘。Embedding 层、LayerNorm、Softmax、残差连接,这些算子要么是访存密集,要么是特殊函数计算,要么是混合精度;真正能跑满稀疏 INT8 MAC 阵列的部分,可能只占整个推理时间的 40%。也就是说,哪怕纸面 TOPS 翻倍,端到端推理速度翻倍也是做梦。这个比例,在短输入、长生成的场景里尤为明显——decode 阶段根本没多少矩阵乘可算,TOPS 再高也只能看着内存带宽干瞪眼。
还有频率的问题。TOPS 是按“最大频率”算的,但移动端 SoC 不可能长时间跑在最高频,否则发热和耗电直接就让手机变成暖手宝。真实场景下,NPU 可能运行在标称频率的 70% 甚至更低,算力直接打七折。所以,看到 TOPS 数字的第一反应,应该是去问:“这个数字是在什么精度下测的?什么频率下测的?连续跑 10 分钟还能保持吗?”
3.2 真实算力:利用率、带宽和功耗的三角关系
真正决定端侧大模型跑得快不快的,不是一个 TOPS 峰值,而是一个三角关系:NPU 的实际利用率、内存带宽、功耗墙。这三者相互制约,缺一不可。
利用率:一个模型在图编译阶段,如果算子切分不合理、数据布局转换频繁,NPU 利用率可能只有 30%~50%。也就是一颗标称 60 TOPS 的 NPU,实际干活的只有 20~30 TOPS。利用率上不去,再大的峰值也是摆设。移动端这个问题尤其严重,因为端侧框架(比如 Qualcomm AI Engine 的 Runtime、TFLite、MLC 等)对动态形状的支持参差不齐,模型一旦有动态维度(比如变长输入),编译器会自动加一堆 padding 和拷贝,利用率直接崩。
带宽:decode 阶段尤其依赖带宽。一个直观的估算:假设一个 7B 模型用 4bit 权重部署,权重总量约 3.5GB,内存带宽 80GB/s,那么 decode 的“理论最高速度”就是每秒约 23 个 token(80GB/s ÷ 3.5GB ≈ 23 token/s)。这个数字看起来还行,但如果 prefill 阶段把带宽也占了,decode 速度还要往下掉。所以很多端侧实测里,模型标称“每秒 20 token”,实际边跑边聊只有 10 token 出头,就是因为带宽被 prefill 和系统其他组件抢走了。
功耗墙:端侧 SoC 的 NPU 峰值功耗不可能像 GPU 那样动辄几百瓦,手机里 NPU 持续跑大模型,整机功耗必须控制在 5W 以内,否则续航和发热都扛不住。性能调校的第一步往往不是“怎么跑得快”,而是“怎么巧妙地降频但看起来没降太多”,这也是端侧和云端最本质的区别之一。
所以,当你下次听到“prefill +80%”这类数字,请自动在脑内补一句:“在什么样的模型大小、量化精度、序列长度和功耗约束下测出来的?”如果这些条件都没说,这组数字只能当作“可能性的上限”,而不是“一定能拿到的收益”。
4. 端侧大模型部署的正确打开方式
4.1 用指标说话:不要只看 TOPS
既然 TOPS 不可全信,部署端侧模型时应该盯哪些指标?我的经验是,至少要看四个:首 token 延迟(TTFT)、解码速度(token/s)、峰值内存占用、能效(token/J)。这四个指标合在一起,才能反映用户在真实交互中的感受。
TTFT 对应 prefill 体验。会话式 AI 应用里,用户发出指令后会立刻期待回应,TTFT 超过 2 秒,体验就崩了。优化 TTFT 时,压缩输入上下文长度、减少 padding、把输入拆分给 GPU 和 NPU 并行处理,都是立竿见影的手段。解码速度对应生成流畅度,理论上人眼的阅读速度大约是 4~8 token/s,超过这个值,生成速度再快用户也感知不到。但别为了追求速度把量化精度压得太狠,4bit 相对于 8bit 的模型质量损失在小模型上相当明显,尤其是长尾词、代码、中文等复杂场景。
峰值内存占用决定了模型能不能跑在手机里。一个 7B FP16 模型光权重就要 14GB,手机肯定装不下;4bit 量化后约 3.5GB,加上激活值、KV cache、运行时内存,总占用 5GB 左右,这对 12GB 内存的手机来说是可接受的。部署前一定要实测内存峰值,很多模型在加载阶段出现“离奇崩溃”,罪魁祸首就是内存预算没算对。
能效(token/J)是端侧独有的核心指标。因为同样的 token/s,跑在 3W 功耗和跑在 6W 功耗,完全是两种手机体验。能效差的部署方案,跑十分钟就触发温控降频,性能断崖式下跌。用能效作为“锚指标”,能有效避免掉入“峰值性能很高但实际高不过三分钟”的陷阱。
4.2 从架构到配置:端侧部署的优化清单
基于实际部署踩坑经验,我整理了一份实用的优化清单,按优先级排列:
| 优化项 | 做法 | 预期收益 | 坑点 |
|---|---|---|---|
| 权重量化 | 从 8bit 起步,优先用 GPTQ、AWQ 等感知量化方案 | 内存占用降低 50%~75%,带宽压力同步下降 | 直接跑 naive 4bit 量化,模型质量崩得快 |
| KV cache 量化 | 4bit KV cache 或低秩投影压缩 | decode 带宽显著改善,长上下文场景收益大 | 某些框架不支持,需要手动改内核 |
| 输入裁剪 | 上线前处理压缩上下文,截断最老的历史消息 | TTFT 显著下降 | 长对话场景语义可能丢失 |
| 算子融合 | 把 LayerNorm+残差+激活融合成一个算子 | 减少多次中间内存读写 | 不同框架支持度不一 |
| 异构调度 | prefill 分到 GPU,decode 留在 NPU | 端到端延迟降低 | 跨硬件切换有延迟,需要缓存池 |
| 多线程降级 | 禁用大核,只用中核跑 | 功耗下降,避开温控墙 | 长文本生成变慢 |
| 批大小调节 | 端侧尽量不要批处理,保持 batch=1 | 内存峰值可控 | 某些框架隐式做了动态批处理 |
第一项和第二项是最基础但也最见效的。做完这两项,一个 7B 模型从“勉强能装进内存”变成“能正常聊几十轮”,体验是完全不同的。第三项到第七项更像“进阶调校”,取决于应用的形态:如果是文档助手类应用,输入裁剪最重要;如果是实时聊天助手,算子和异构调度权重更高。
4.3 部署流程中的真实成功路径
分享一条我实测过比较顺的部署路径。第一步,选模型。不要一上来就上 13B 或者 MoE 大模型,先在 7B 以下挑一个在端侧数据上表现不差的基座模型。第二步,做量化。用 AWQ 做 4bit 权重量化,用 GPTQ 做对照,选困惑度损失小于 2% 的那版。第三步,跑 benchmark。用同一段文本测 TTFT、token/s、内存峰值和功耗,把这些数据记录成基线。第四步,针对瓶颈做定向优化:如果 TTFT 太长,检查是不是 prefill kernel 没用上 FlashAttention;如果 decode 慢,检查是不是带宽被打满,试着把权重再做一层混合精度拆分。
这条路径的精髓是“先测量、再优化”。端侧部署最忌讳的是凭感觉调参,因为 NPU 和 CPU 之间的性能差异在不同机型、不同驱动版本、不同系统负载下会剧烈变化。没有基线数据,你根本不知道改动到底是变快还是变慢。
5. 常见问题与排查技巧实录
5.1 问题:prefill 指标看着很高,端到端还是很慢
很多人拿着厂商的 prefill benchmark 数据来问我:官方说 prefill 提升了 80%,我这实测怎么没感觉?这个问题我几乎每次都要解释一遍。厂商的 prefill 测试,通常用的是固定形状的输入(比如统一 512 token、batch=1),而且只测 NPU 的单次算子执行时间,不包含调度开销、数据拷贝和进程切换。而真实应用里,prefill 的输入长度是变化的,框架要动态分配 buffer、做形状推断、编译或查找 kernel,这些耗时都可能远超 NPU 本身的执行时间。
排查思路是这样:第一步,用 profiler 分开统计“框架调度时间”和“NPU 执行时间”。如果框架调度时间占了总耗时的 60% 以上,问题不在硬件算力,而在软件栈。第二步,看数据拷贝时长。端侧框架经常在 CPU 内存和 NPU 内存之间搬运数据,一个 2000 token 的输入 embedding,如果每次前向都搬一遍,耗时非常可观。解决办法是把 embedding 层留在 CPU 侧,只把 transformer 主体放到 NPU 上跑。第三步,检查是否每次请求都触发了重新编译。某些运行时会按输入长度做 kernel 缓存,第一次跑某个形状会触发“冷启动”编译,这个时间可能超过 500ms。解决办法是在应用启动阶段做一次“预热推理”,把常见形状的 kernel 都先编译缓存起来。
5.2 问题:decode 速度上不去,加了 TOPS 也没用
这是另一个高频率问题。decode 是访存密集任务,它的速度上限就是内存带宽除以每 token 需要搬运的数据量。你算一下:7B 模型 4bit 权重约 3.5GB,内存带宽 70GB/s,理论上限 20 token/s。怎么优化?不是堆算力,而是减少每 token 搬运的数据量:把模型蒸馏到更小的参数量、把权重切分成“高频头”和“低频头”,高频头用更少 bit、低频头再压得更狠,或者直接换用更低比特的 KV cache。
很多时候,decode 慢的另一个原因是锁频。高通平台的 CPU 黑名单机制里,如果某个进程在高负载下把整机功耗顶到温控阈值,NPU 和 CPU 会被“协同降频”,decode 速度跟着雪崩式下跌。这时候需要在代码里主动做功耗控制:把模型推理的线程绑定到中核,避免大核短时间冲太高,导致整体降频后性能反而更差。这个操作看似“反直觉”,但在移动端实测里,主动降频有时能比硬冲高负载获得更稳定的平均帧率。
5.3 端侧 AI 部署避坑速查
把印象深刻的坑整理成表,供大家直接抄作业:
| 坑 | 现象 | 解法 |
|---|---|---|
| 框架版本混用 | 同一个量化模型,在不同版本上精度表现不一致 | 锁定 runtime 版本,量化方案和推理框架必须同版本验证 |
| 内存泄漏 | 多轮对话后内存缓慢上涨,最后 OOM | 用 Android Studio Memory Profiler 压测 50 轮以上对话 |
| 动态形状 | 不同输入长度反复触发重新编译 | 静态化输入长度(pad 到固定值),或初始化时预热 |
| 大核抢占 | 主线程在 UI 上做推理,卡成 PPT | 推理线程移到独立线程,设置 RT 优先级。 |
| 误用 FP32 | 某些算子悄悄回退到 FP32,导致内存翻倍 | 用算子 fallback 日志检查哪些 op 被回退了 |
| 温度过高 | 连续交互 10 分钟,速度降一半 | 监控 SoC 温度,在 70°C 前主动降载 |
这些坑每一个都踩得人很痛,尤其“动态形状”和“大核抢占”,属于那种不 profiler 根本发现不了的问题。建议在项目一启动就把 profiler 和功耗表接到 CI 里,每个版本对比 TTFT、decode 速度和温度曲线,一旦有回归立刻报警。
6. 一些题外话和最后一个小技巧
我自己的切身体会:端侧 AI 这两年最大的进步,不是某颗芯片的 TOPS 翻了几倍,而是“在手机里跑大模型”这件事从实验走向了常态。但越是这样,越要保持清醒。那些光鲜的“+80%”“xx TOPS”背后,往往藏着精度、频率、利用率、功耗之间的一系列权衡。真正决定实际体验的,不是发布会上的大数字,而是你拿到真机后,跑一个真实应用测出的 TTFT 和 token/s。
最后分享一个很实用的小技巧:判断一台设备的端侧 AI 能力强不强,不要看 TOPS,看它跑一个固定模型(比如 llama-3.2-3b 的 4bit 量化版)时的 TTFT 和稳定 decode 速度。这两个数据,能打败 90% 的营销话术。把这两个数据当作基准线记录在案,每次换设备、换驱动版本都重测一遍,你就能建立属于自己的“真实算力表”。这份表,比厂商的任何一张 PPT 都可靠。