Ch29: 端侧大语言模型——推理引擎的范式转移
Part 6: 前沿挑战与进阶 前置章节:Ch28: 十大核心技术难点与三层矛盾 后续章节:Ch30: 学习路线图与导读总结
端侧LLM的出现不是对传统推理引擎的简单扩展,而是一次范式转移。本章将深入分析CV推理与LLM推理的根本差异,横向对比四大端侧LLM方案,剖析核心技术挑战,以及探讨这个方向对ncnn等传统框架的影响和未来可能的发展路径。
CV推理与LLM推理的根本差异
在深入具体方案之前,必须理解一个根本性的转变:端侧LLM不是”跑在手机上的大模型”这么简单,它代表了一种完全不同的推理模式。
无状态 vs 有状态
传统CV推理(图像分类、目标检测、语义分割)是无状态的。给定一张输入图片,模型从前到后跑一遍,输出结果,释放所有中间数据。下一张图片的推理与上一张完全独立——没有”记忆”,没有”上下文”。这个特性让推理引擎可以大胆地在推理完成后释放所有中间内存(ncnn的Light Mode正是利用了这一点)。
LLM推理是有状态的。在生成第100个token时,模型需要”记住”前99个token的信息——具体而言,是Transformer每一层的Key和Value矩阵(KV-Cache)。这个状态随着生成过程线性增长,不能在推理”完成后”释放(因为推理永远没有”完成”,直到生成结束标记或达到长度限制)。
这个差异对推理引擎的影响是全方位的:内存管理从”峰值固定”变为”持续增长”;调度从”一次性执行”变为”循环迭代”;并行策略从”层间pipeline”变为”注意力头间并行”。
计算密集 vs 内存带宽密集
CV推理的核心计算是卷积——大量的乘加运算(MAC),计算与访存比(算术强度)高。这意味着CPU/GPU的FLOPS利用率可以很高,性能瓶颈通常在计算单元而非内存带宽。优化方向是:Winograd减少乘法次数、im2col+GEMM利用矩阵乘法硬件、合理分块让数据留在Cache中。
LLM推理,特别是Decode阶段,是严重的内存带宽密集型。生成每个token时,模型需要读取全部权重(约3.5GB for 7B INT4),但只做一个token的计算(相当于batch_size=1的矩阵-向量乘法)。算术强度极低——每从内存读取一个权重值,只做一次乘加运算。这意味着:无论CPU/GPU的计算能力多强,性能瓶颈几乎永远在内存带宽。
以数字说明:假设一台手机的内存带宽为50 GB/s,7B INT4模型权重约3.5GB。纯带宽限制下,每token的理论最短延迟 = 3.5GB / 50 GB/s = 70ms,即约14 tokens/s。实际上加上KV-Cache读取和其他overhead,通常在8-15 tokens/s之间。增加CPU/GPU的FLOPS对这个数字几乎没有帮助。
峰值固定 vs 动态累积
CV推理的内存使用模式是:加载模型(固定占用)→ 推理时产生中间激活(峰值固定,由最大的层决定)→ 推理完成释放。整个过程的内存占用像一个”凸包”——开始上升,到达峰值,然后下降,峰值位置和大小在编译时就可以精确计算。
LLM推理的内存使用模式是:加载模型(固定占用)→ Prefill阶段产生初始KV-Cache(大量但一次性)→ Decode阶段KV-Cache持续增长(每生成一个token增加一行Key和一行Value)。内存占用像一条单调递增的阶梯——永远不会自动下降,除非显式清除会话。这种”只增不减”的模式对移动端的内存管理是极大挑战。
四大端侧LLM方案横评
MNN-LLM:内建推理引擎方案
定位:阿里MNN团队将LLM推理能力直接集成到MNN框架内部。
架构设计:MNN-LLM构建在MNN的通用推理引擎之上,复用了MNN已有的后端调度(CPU/GPU/NPU)、内存管理和算子库。LLM特有的组件包括:Tokenizer集成、KV-Cache管理模块、采样策略(temperature/top-p/top-k)、以及对话模板处理。
量化方案:支持INT8/INT4权重量化,KV-Cache可选INT8量化。使用自研的量化工具链,支持GPTQ/AWQ格式的预量化模型导入。
优势:
- 与MNN生态深度集成,一个框架同时处理CV和NLP任务
- 可以利用MNN已有的多后端调度能力(GPU加速Prefill阶段)
- 工程质量稳定(阿里内部大规模验证)
- 支持Android/iOS/桌面全平台
劣势:
- LLM相关优化起步晚于llama.cpp
- 社区活跃度相对较低
- 文档和教程相对不足
llama.cpp:社区标杆
定位:Georgi Gerganov发起的纯C/C++项目,从零构建的LLM推理实现。
架构设计:llama.cpp不基于任何已有推理框架,而是专门为LLM推理设计的轻量运行时。核心组件ggml是一个极简的张量计算库(类似于简化版的numpy,但用C写成),上层的llama.cpp在其上实现了完整的Transformer推理流程。
GGUF格式:llama.cpp定义了自己的模型格式GGUF(GPT-Generated Unified Format),直接将量化权重、tokenizer信息、模型元数据打包在一个文件中。GGUF已成为社区分享量化模型的事实标准——HuggingFace上大量模型都提供GGUF格式下载。
量化方案:ggml支持极其丰富的量化格式——Q2_K、Q3_K_S/M/L、Q4_0/Q4_1/Q4_K_S/M、Q5_0/Q5_1/Q5_K_S/M、Q6_K、Q8_0等数十种。每种格式在bit数、量化粒度(per-tensor/per-row/per-block)和额外参数(是否有min值、是否有super-block)上各不相同。这种极致的精细度让用户可以根据设备能力精确选择”精度-内存-速度”的平衡点。
优势:
- 社区极其活跃(GitHub 60k+ stars,数百贡献者)
- 模型支持最广(几乎所有主流LLM架构都有人适配)
- 量化方案最丰富
- 跨平台性极佳(ARM/x86/Metal/CUDA/Vulkan/SYCL)
- 代码简洁可读,学习门槛相对低
劣势:
- 不是通用推理框架,只做LLM
- 代码风格偏”个人项目”,架构设计不如企业级框架严谨
- GPU后端性能相对MNN/专用框架有差距
- 不做图优化(没有算子融合等)
MediaPipe LLM:Google的端侧方案
定位:Google将LLM推理能力集成到MediaPipe框架中,面向Android和Web开发者。
架构设计:MediaPipe LLM inference API基于TensorFlow Lite运行时,通过TFLite的GPU Delegate和NNAPI Delegate利用硬件加速。Google对特定模型(Gemma系列、Phi系列)做了深度优化。
量化方案:使用TFLite的量化工具链,支持INT8和INT4(通过AI Edge量化方法)。Google还利用自己的TPU/GPU资源做更精细的量化校准。
优势:
- 与Android生态深度集成(Android Studio直接支持)
- Google对Gemma模型做了极致的端侧优化
- Web平台支持(通过WebGPU/WebGL)
- 企业级稳定性和文档质量
劣势:
- 模型支持有限(主要是Google自家模型)
- 开放性不如llama.cpp(定制能力受限)
- 依赖TFLite生态(有一定历史包袱)
- 非Google模型的优化深度不足
MLX:Apple Silicon原生方案
定位:Apple机器学习研究团队推出的框架,专门为Apple Silicon(M系列芯片和A系列芯片)设计。
架构设计:MLX的设计哲学是”像NumPy一样简单,像PyTorch一样灵活,像Metal一样快”。它直接使用Metal Performance Shaders(MPS)和Apple AMX(Advanced Matrix Extensions)硬件加速,完全绕过OpenCL/Vulkan等通用GPU API。统一内存架构(UMA)是其核心优势——CPU和GPU共享同一块物理内存,不需要数据拷贝。
量化方案:MLX支持INT4/INT8量化,使用Apple内部优化的量化kernel。由于UMA的存在,量化权重可以被CPU和GPU无缝共享,不存在跨设备传输开销。
优势:
- 在Apple设备上性能极致(充分利用UMA + Metal + AMX)
- 开发体验极佳(Python API,NumPy语法)
- 惰性求值+自动微分(可以做fine-tuning)
- 社区增长迅速(Apple官方维护)
劣势:
- 完全绑定Apple平台(无法在Android/Windows上运行)
- 移动端(iPhone/iPad)支持仍在早期
- 生态相对封闭
- 不适合需要跨平台的产品应用
四方案对比总结
| 维度 | MNN-LLM | llama.cpp | MediaPipe LLM | MLX |
|---|---|---|---|---|
| 定位 | 通用框架+LLM扩展 | LLM专用轻量运行时 | Android/Web LLM SDK | Apple Silicon原生 |
| 平台 | 全平台 | 全平台 | Android/Web/iOS | Apple Only |
| 模型格式 | MNN格式 | GGUF | TFLite | MLX格式/safetensors |
| 量化丰富度 | 中等 | 极高 | 中等 | 中等 |
| GPU加速 | OpenCL/Metal/Vulkan | Metal/Vulkan/CUDA | GPU Delegate | Metal/AMX |
| CV+LLM一体化 | 支持 | 不支持 | 部分支持 | 支持 |
| 社区活跃度 | 中等 | 极高 | 中等 | 高 |
| 学习门槛 | 中等 | 较低 | 较低 | 低(Python) |
| 性能(Apple) | 良好 | 良好 | 良好 | 最优 |
| 性能(Android) | 优秀 | 良好 | 优秀 | N/A |
核心技术挑战深度剖析
挑战一:内存墙——当权重占满了所有空间
端侧LLM面临的第一个物理限制是:模型权重本身就快要占满设备可用内存。
以7B参数模型为例:FP16存储需要14GB,INT8需要7GB,INT4需要3.5GB。即使用最激进的INT4量化,3.5GB的权重在一台8GB手机上仍然占据了约45%的总内存(考虑到系统和其他应用占2-3GB,实际可用约5GB)。再加上KV-Cache和激活内存,可用空间所剩无几。
mmap(内存映射) 是当前最实用的应对策略。通过mmap,模型文件不需要完整加载到物理内存——操作系统按需调入需要的页面。在iOS上这特别有效,因为iOS的虚拟内存管理允许mmap的页面在内存压力下被直接丢弃(因为可以从文件重新读取),不算入应用的”脏内存”配额。llama.cpp和MNN-LLM都支持mmap加载模型。
分层加载(Layer-wise Loading) 是另一种思路:不把整个模型常驻内存,而是按需加载当前层的权重,计算完立即释放。这在Flash Storage足够快的设备上可行(NVMe SSD的读取带宽可以达到数GB/s),但引入了IO等待延迟。目前这更多是一种理论可能性,实际产品中采用的不多,因为反复的IO调度开销和碎片化读取会显著降低throughput。
模型蒸馏与缩小从模型侧解决问题:使用更小的模型(1-3B参数)在移动端可以运行得更从容。Apple Intelligence使用约3B参数的模型;Google的Gemma 2B专门为端侧场景设计。但更小的模型意味着能力上限更低——这是能力与资源的根本trade-off。
挑战二:Prefill与Decode的双阶段特性
LLM推理分为两个性质截然不同的阶段:
Prefill(预填充)阶段:处理用户输入的所有token(可能是一个完整的prompt,几十到几千token)。这个阶段的计算特征类似于batch GEMM——每一层同时处理所有输入token的Key/Query/Value计算。由于batch较大,计算密集度较高(矩阵-矩阵乘法),GPU加速效果显著。Prefill阶段结束时,所有输入token的KV-Cache被一次性生成。
Decode(解码)阶段:逐个生成输出token。每次只处理一个新token(batch_size=1),需要读取所有之前token的KV-Cache来计算注意力。这个阶段的计算特征是矩阵-向量乘法(权重矩阵 x 单个token向量),算术强度极低,完全受内存带宽限制。
双阶段特性对推理引擎的挑战:
两个阶段对硬件的需求完全不同——Prefill适合GPU(高并行度),Decode适合高带宽低延迟的CPU/NPU(因为是带宽限制而非计算限制)。理想情况下,引擎应该为两个阶段分别调度到最合适的硬件,但这引入了阶段切换时的数据迁移开销。
MNN-LLM的做法是将Prefill放在GPU(OpenCL/Metal)执行,Decode放在CPU执行,KV-Cache存储在CPU内存中(GPU阶段写入,CPU阶段读取)。这种异构调度策略在Apple设备上效果最好(UMA下CPU/GPU共享内存,切换无需数据拷贝)。
llama.cpp在Apple设备上利用Metal加速Prefill和部分Decode计算,但整体策略偏向CPU(利用ARM NEON/dotprod指令)。其设计哲学是”简单可靠优先”——不做复杂的跨设备调度,而是在单设备上极致优化。
挑战三:KV-Cache的管理艺术
KV-Cache是端侧LLM最独特的内存管理挑战。它的特征是:
- 只增不减:正常推理过程中KV-Cache只会增长,不会收缩
- 访问模式固定:Decode时每次都要读取完整的KV-Cache(所有层、所有历史token)
- 大小可预测:给定模型架构和序列长度,KV-Cache大小可以精确计算
以LLaMA-7B为例计算KV-Cache大小:
- 32层,每层有32个注意力头,每头维度128
- Key和Value各需要:layers x heads x head_dim x seq_len x precision
- FP16精度,2048 token:32 x 32 x 128 x 2048 x 2 bytes x 2(K+V) = 约1GB
- INT8精度,2048 token:约512MB
- 实际优化后(group query attention等)通常在200-500MB之间
固定窗口策略(Sliding Window)是最简单的上限控制手段。将KV-Cache限制为最近N个token(如1024),超出窗口的历史KV直接丢弃。Mistral 7B模型原生设计了Sliding Window Attention,窗口大小4096。这种策略的内存是O(window_size)而非O(total_tokens),永远不会超限。但对需要远程依赖的任务(如长文档总结、多轮长对话)效果受损。
KV-Cache量化是更精细的策略。观察发现,KV-Cache中的数值分布通常比较集中(特别是经过Layer Norm之后),用INT8甚至INT4量化的精度损失可控。MNN-LLM支持KV-Cache的INT8量化,将KV-Cache内存需求减半,对生成质量的影响在大多数任务上几乎不可感知。
稀疏/选择性Cache是更激进的方案。核心观察是:在注意力计算中,大多数token的贡献是微乎其微的(attention权重接近0),只有少数”关键token”的attention权重较大。如果能识别并只保留这些关键token的KV,就能大幅减少Cache大小。H2O(Heavy Hitter Oracle)方法通过累积attention score来判断token的”重要性”,保留top-k重要token和最近的若干token,其余丢弃。
PagedAttention的端侧简化:服务器端的PagedAttention(vLLM提出)主要解决多请求batch调度时的内存碎片问题。在端侧(通常只有单请求),PagedAttention的batch调度优势不存在,但其”分页管理”的思想仍有价值——可以减少预分配导致的内存浪费(不需要一开始就为最大上下文长度分配所有内存)。
挑战四:Decode阶段的带宽利用优化
由于Decode阶段完全受内存带宽限制,优化的核心方向是”如何更高效地利用每一字节的带宽”。
权重预取(Prefetching):在CPU计算当前层时,通过DMA或软件预取将下一层的权重从主内存搬到Cache中。如果预取与计算重叠得当,可以隐藏内存延迟。llama.cpp在其ARM NEON kernel中大量使用__builtin_prefetch实现这一策略。
分组量化(Group Quantization)的带宽效率:INT4分组量化中,每组32/64/128个权重共享一组scale+zero_point。在Decode时,读取权重后需要先解量化(乘以scale+加zero_point)再做计算。解量化的计算开销相对于内存读取是”免费的”(计算单元闲着等带宽),但量化参数本身(scale/zero_point)也占带宽。因此,组越大(如128),量化参数占比越小,带宽效率越高;但组越大,量化精度越低。最优组大小是带宽效率与精度的平衡点。
混合精度计算:在带宽限制下,减少读取数据量是最直接的加速手段。ARM dot product指令(SDOT/UDOT)可以在一条指令中完成4个INT8乘加,配合INT4权重(解包为INT8后计算),实现”4bit存储+8bit计算”的组合——读取带宽减半,计算精度可接受。
与ncnn的关系:为什么不做LLM
ncnn不支持LLM推理,这不是偶然的疏忽,而是架构设计的必然结果。理解这个”不支持”的原因,恰恰能加深对ncnn设计哲学和LLM推理特殊性的理解。
无状态设计的冲突
ncnn的核心设计假设是无状态推理——创建Extractor、设置输入、执行推理、获取输出、销毁Extractor。每次推理是独立的,Extractor不保留上次推理的任何中间结果。这与LLM的有状态推理(必须保留KV-Cache跨多次推理调用)存在根本性冲突。
虽然可以在ncnn之上手动管理KV-Cache(作为外部状态传入传出),但这意味着:KV-Cache的内存管理完全在ncnn视野之外,ncnn的内存优化(Light Mode等)无法覆盖这块最大的内存消耗者。这使得”在ncnn上跑LLM”变成了”在ncnn之外重写了大部分LLM推理逻辑,只用ncnn做矩阵乘法”,失去了使用框架的意义。
动态序列长度的挑战
LLM的序列长度在推理过程中持续增长——第1次Decode时KV-Cache长度为N,第2次为N+1,第3次为N+2…这意味着注意力计算的矩阵尺寸在每步都变化。在ncnn的静态Shape设计下,每步都需要重新分配/调整内存,效率极低。
算子缺失
LLM需要的核心算子(RoPE旋转位置编码、Group Query Attention、RMS Norm、SiLU激活等)在ncnn中要么不存在,要么实现不针对LLM场景优化。特别是Attention算子——在LLM Decode阶段需要高效处理”一个Query向量与N个Key向量的点积”(Nx1矩阵-向量乘,而非标准的矩阵-矩阵乘),这种特殊的计算模式需要专门的kernel实现。
ncnn的未来可能性
尽管当前ncnn不支持LLM,但这不意味着永远不会支持。社区中已有人讨论为ncnn添加有状态推理支持的可能性。关键的设计决策是:是在ncnn内部原生支持KV-Cache管理(需要大幅修改核心架构),还是在ncnn之上提供一个轻量的LLM运行时封装(保持ncnn核心不变,用外部组件管理状态)。后者的可能性更大,因为它不破坏ncnn的核心设计原则。
未来方向
方向一:更高效的模型架构
当前端侧LLM的困境很大程度上源于Transformer架构本身的特性(O(n)状态存储、O(n^2)注意力计算)。新兴的架构正在尝试从根本上解决这些问题:
状态空间模型(SSM):Mamba架构使用选择性状态空间机制替代注意力,将推理时的状态大小从O(n)降到O(1)——无论处理多长的序列,状态大小恒定。这从根本上消除了KV-Cache膨胀问题。Mamba-2/Jamba等模型展示了SSM在语言建模上接近Transformer的能力,同时推理效率大幅提升。
混合架构:实际趋势是混合使用Attention和SSM/Linear Attention——在需要精确远程依赖的层使用标准Attention(保留KV-Cache),在其他层使用SSM或Linear Attention(无需KV-Cache)。这让KV-Cache的大小从”所有层x所有token”减少到”部分层x所有token”,在能力和效率之间找到新的平衡点。
线性注意力(Linear Attention):通过修改注意力计算的数学形式(去掉softmax的指数运算),使得注意力计算可以递推进行——每个新token只需常数时间更新状态,而非扫描所有历史token。RWKV系列是这个方向的代表,已经展示了在端侧可行的性能。
方向二:硬件协同设计
当前的移动SoC并非为LLM推理设计。未来的硬件演进可能包括:
更大的片上内存/eDRAM:Apple M系列芯片已经展示了大容量统一内存(16GB-192GB)的威力。如果这个趋势延伸到移动端(iPhone 16 Pro已有8GB),端侧LLM的内存压力会逐渐缓解。
专用矩阵单元:Apple AMX、Qualcomm Hexagon Tensor Processor等专用矩阵加速单元能显著提升GEMM/GEMV性能。未来这些单元可能进一步优化低比特计算(原生INT4/INT3 MAC),直接在硬件层面支持极低精度推理。
智能内存控制器:能否设计一种内存控制器,理解”模型推理”的访存模式(顺序扫描权重、随机访问KV-Cache),并据此优化预取策略和页面调度?这是”领域特定内存系统”的思路。
方向三:端云协同推理
纯端侧推理和纯云端推理各有局限。端云协同是务实的中间路线:
推测性解码(Speculative Decoding)的端云分工:端侧运行小模型(1-3B)作为”草稿模型”快速生成候选token序列,云端大模型验证并修正。这大幅减少了云端请求次数(只需验证而非逐token生成),同时保持了大模型的输出质量。延迟体验接近纯端侧(用户看到的是小模型的生成速度),质量接近纯云端。
上下文缓存+端侧续写:将对话的长期记忆(压缩后的KV-Cache)存储在云端,端侧只保留最近几轮对话的KV-Cache。当需要引用远期记忆时,从云端拉取相关片段。这解决了端侧KV-Cache无限增长的问题。
隐私分级处理:敏感内容(个人信息、金融数据)纯端侧处理,非敏感内容(通用知识问答)走云端。推理引擎需要在运行时判断内容敏感度并动态切换路由。
方向四:编译器优化的新机会
LLM推理的规律性(固定的Transformer Block重复堆叠、可预测的访存模式)为编译器优化提供了新机会:
全图kernel融合:将整个Transformer Block(LayerNorm → QKV Projection → Attention → FFN → Residual)融合为一个mega-kernel,消除所有中间内存写回。这在GPU上尤其有效,因为GPU的kernel launch开销在Decode阶段(计算量小、launch频繁)中占比显著。
自动向量化与调度:Decode阶段的计算模式高度规则(每步只处理一个token的全模型前向),编译器可以进行全局数据流分析,自动确定最优的并行策略和数据布局。
离线生成执行计划:由于LLM的计算图在推理过程中不变(只是序列维度增长),可以在部署时生成一个包含所有kernel和内存分配的”执行脚本”,运行时零overhead地按脚本执行。
方向五:多模态端侧推理
端侧LLM的下一步不是”更大的语言模型”,而是”多模态理解与生成”:
视觉-语言模型(VLM)端侧化:LLaVA、Phi-3-Vision等VLM模型已经展示了”看图说话”的能力。将这些模型部署到端侧,意味着推理引擎需要同时处理:图像编码器(CV推理,计算密集)→ 投影层 → 语言模型(LLM推理,带宽密集),两种完全不同的计算模式在同一次推理中交替出现。
语音端侧处理:Whisper端侧化(语音→文本)+ 端侧LLM(文本→回复)+ TTS(文本→语音),构成完整的端侧语音助手流水线。这要求推理引擎支持多个模型的并行/流水线执行,以及模型间的高效数据传递。
本章小结
端侧LLM对移动端推理引擎的冲击是全方位的:
从设计假设层面:无状态→有状态、计算密集→带宽密集、峰值固定→动态累积——传统框架的三大基础假设被同时颠覆。
从技术挑战层面:内存墙、KV-Cache管理、双阶段调度、带宽利用优化——每一个都需要专门的解决方案,不能简单复用CV推理的技术栈。
从生态格局层面:llama.cpp以社区力量建立了事实标准(GGUF格式),MNN-LLM代表了通用框架的LLM扩展路线,MediaPipe LLM代表了平台厂商的官方方案,MLX代表了硬件-软件协同设计的方向。四种路线并存竞争,尚未出现统一标准。
对ncnn的启示:ncnn当前不支持LLM是架构设计的必然结果,但这并非死局。无论是在ncnn之上构建轻量LLM运行时,还是借鉴llama.cpp的设计在ncnn生态内新建一个项目,都是可行的方向。核心决策是:是否要为了LLM改变ncnn”轻量、无状态、极简”的DNA。
端侧LLM的竞赛还处于非常早期——硬件能力、模型架构、推理技术都在快速演进。今天的方案在两年后可能已经过时。但理解了上述根本性差异和核心挑战,读者就能在快速变化的技术浪潮中把握住不变的本质,而不会迷失在层出不穷的具体方案中。
导读目录:README.md 上一章:Ch28: 十大核心技术难点与三层矛盾 下一章:Ch30: 学习路线图与导读总结