☰
手机跑35B大模型:内存墙、量化与端侧推理实战解析
2026/10/1 4:11:38 网站建设 项目流程

“350亿参数,住进一台手机”——这话第一次听像发布会PPT,直到我自己真把模型跑起来才发现,让35B的Transformer住进手机内存,比让它住进机房难了足有一个量级。这里的核心瓶颈不是算力,是“内存墙”:参数要占地方,推理要喂带宽,手机RAM就那么点,闪存还慢。这篇文章我会从参数账目算起,拆解量化、mmap、统一内存、进程保活这些端侧推理的关键技术,再附上我实测两台手机跑35B的真实数据和建议。不管你是在玩llama.cpp还是在研究端侧AI部署,这篇都值得看完。

1. 先算账:350亿参数到底在手机里占多少地方

很多人低估了“35B”这三个数字的分量。在做任何端侧部署之前,得先把内存账单算清楚,不然后面每一步都是瞎调参。

1.1 一张内存账单:权重、KV Cache和运行时

大模型推理时的内存占用分三块:权重、KV Cache、运行时激活值和临时缓冲。权重是大头,KV Cache随着上下文长度线性增长,运行时在batch=1的逐token生成场景占用相对小,但也不能忽略。

以35B参数模型为例,不同精度的权重文件大小如下:

精度每参数字节数权重总大小8GB手机16GB手机24GB手机
FP162约70GB完全没戏完全没戏完全没戏
INT81约35GB没戏没戏没戏
INT60.75约26GB没戏勉强搭配swap没戏
INT40.5约17.5GB没戏勉强搭配swap能完整塞下
INT30.375约13.1GB搭配swap能完整塞下舒服
INT20.25约8.75GB搭配swap舒服舒服

再算KV Cache。假设一个35B模型有大约60层,KV heads走GQA后为8个,head_dim为128,上下文长度设为4096,FP16存储下KV Cache大约是:

2(K和V) × 4096(序列长度) × 60(层数) × 8(KV头数) × 128(维度) × 2(字节) ≈ 536MB

如果上下文拉到8K,这个数字直接翻倍到1GB以上。再叠加运行时激活值、临时buffer、App本身开销,实际上你想跑4096上下文,至少要在“权重+1GB”的额度上再留出500MB甚至更多。

所以最现实的方案是:INT4量化后的模型约17.5GB,加上KV Cache和运行时,总量逼近19GB。这意味着:

  • 24GB内存机型:可以完整装下,运行得比较从容。
  • 16GB内存机型:勉强,必须用更小的量化档、更短的上下文,并依赖系统内存压缩。
  • 8GB内存机型:单靠RAM装不下Q4,必须走闪存流式加载,速度会非常感人,这个后面讲到。

我见过很多人拿了Q4的35B模型就往16GB手机上塞,结果进程直接被系统杀掉。这不是模型问题,是预算问题——你从一开始就该把“权重大小 + KV Cache + 运行时余量”三部分都算进去。

1.2 内存墙其实是两堵墙:容量墙与带宽墙

“内存墙”这个词被滥用了,但真正做端侧部署的人都清楚,它其实是两堵墙。

第一堵是容量墙:模型权重和KV Cache超过了可用内存容量,放不下。前面算的账就是在讲这堵墙。容量墙容易理解,量化、剪枝、蒸馏都是拆这堵墙的手段。

第二堵是带宽墙:即使模型全部驻留在内存里,每个token生成时,处理器都必须把全部权重从内存读取一遍。这是因为Transformer是自回归解码——每生成一个新token,所有层都要重新计算一遍,每一层的权重都要重新被读一次。这个过程不是按需只读一小部分,而是全量扫描。

带宽墙有多狠,算一下就知道。iPhone 15 Pro上的A17 Pro配的是LPDDR5内存,理论峰值带宽大约51.2GB/s。如果35B模型用INT4量化,权重17.5GB,每生成一个token都要把这17.5GB全部读一遍,理想情况下:

17.5GB ÷ 51.2GB/s ≈ 0.34秒/token

这已经是理论极限了,折算下来大约3 tokens/s。但这是纯读取权重的时间,还没算算子、KV Cache读写、缓存未命中、系统其他进程抢带宽。所以实际能跑到2 tokens/s已经非常不错。

高通骁龙8 Gen 3用LPDDR5X,峰值带宽约68GB/s,理论极限能到4 tokens/s;天玑9300用LPDDR5X-9600,带宽约77GB/s,理论能到4.5 tokens/s。这些数字就是35B模型在手机上的天花板,跟算力关系不大,纯粹是内存带宽锁死的。

把带宽墙和生活类比一下:你从仓库(内存)往工位(计算单元)搬零件,工位处理每个零件需要1秒,但仓库每秒只能递出10个零件。就算工位再快,产出也被仓库递件速度卡住。模型的“计算单元”早就不是瓶颈,瓶颈在内存这个“递件窗口”。

这一点决定了策略:想要提高手机上的生成速度,要么减小权重体积(量化、蒸馏、MoE),要么提高有效带宽(更好的内存标准、更高效的缓存利用),光堆算力毫无意义。

1.3 为什么手机不能像电脑那样随意换页

有人会问:电脑内存不够还可以靠虚拟内存swap到硬盘,手机为什么不行?

技术上手机也有类似机制。Linux内核支持mmap缺页换入,iOS在内存压力下也会把页面丢弃并重新从闪存读回。但问题在于手机闪存的随机读取性能远不如PC的NVMe SSD,而且手机上App的存储配额、文件系统权限、进程冻结机制都让“换页”变得极不可控。

PC上你swap到NVMe SSD,顺序读取能到2-3GB/s,随机读取的IOPS也有几十万。手机闪存顺序读取确实不差,但随机读取性能差很多。LLM推理时的权重访问是高度迭代的——每一层按顺序扫过去,每次解码都扫一遍,这意味着如果你的模型文件大于可用RAM,系统会反复换页。闪存的写放大、磨损、功耗和发热都会成为问题。

更麻烦的是手机的内存回收策略。Android的Low Memory Killer(LMK)会根据进程优先级杀进程,你的大内存推理App往往就是第一个被杀的对象。iOS的Jetsam机制更严,后台App的页面会被优先清空。

所以结论很直接:如果模型权重超过手机物理内存的60%,这趟“住进手机”的路就会很艰难。要么压缩权重,要么接受慢速,要么干脆换一台更大内存的设备。

2. 量化是入场券:真4位和假4位

既然FP16的35B模型有70GB,那一切的前提就是把权重大幅度压缩。量化是目前唯一在手机端“真能跑”的方案,但它不是无代价的。

2.1 怎么把70GB压到17.5GB:分组量化与混合精度

量化的核心思路,是把每个浮点权重从FP16的16位,压到INT4的4位,用更少的比特存储同样的信息。但直接每个权重单独映射到4位会带来严重的精度损失,所以现代量化都采用“分组缩放”策略。

以llama.cpp里常用的Q4_K_M为例:权重矩阵会被切成若干group,每个group通常是128个权重值。group内部共享一个缩放因子(scale)和零点偏移(zero point)。量化时,把这128个浮点值先归一化到某个范围,再映射到16个离散整数档位(4位能表示0-15)。解码时,根据scale把整数还原成接近原始浮点的值。

这样做的意义在于:相邻权重往往数值范围接近,shared scale能保留group内的相对精度,而不像全局scale那样让小数部分失真。实验结果也表明,group size越小,精度越高,但存储scale的开销也越大。group=128是一个内存开销和精度的平衡点。

Q4_K_M里的“_M”指的是混合精度。具体来说,模型的某些敏感层(比如attention的key、value投影层)用更高的精度(如Q6),而FFN部分用Q4。这是因为attention对数值误差更敏感,如果KV投影层的量化误差过大,模型的上下文理解和长文本表现会明显退化。K_M这种混合策略,用大约多1-2%的存储开销,换回了接近Q5甚至Q6的生成质量,性价比很高。

还有一个常见概念是“激活值量化”。权重可以离线量化,但激活值只能在运行时算,所以端侧推理框架通常保留FP16的激活值,这被称为“W4A16”(权重4位、激活16位)。跟训练时的“INT8全量化”不同,推理侧W4A16是主流,因为端侧不需要反向传播,只关心激活值精度对生成的影响。

2.2 量化档位怎么选:Q8到Q2的实测取舍

不同量化档位的取舍,本质是质量与体积的交换。以35B模型为例,我在测试里常用这些档位:

量化档位近似体积能跑的内存质量表现
Q8_0约35GB40GB以上接近原版,几乎无损
Q6_K约26GB32GB以上质量很好,日常够用
Q5_K_M约23GB24GB以上质量良好
Q4_K_M约18GB24GB勉强/16GB需要swap质量可接受,端侧主力档位
Q3_K_M约14GB16GB能跑质量明显下降,复杂任务拉胯
Q2_K约9.5GB8GB+swap能跑质量大幅下降,算术和逻辑易出错

我个人的实践建议是:

  • 24GB内存机型,优先Q4_K_M,这是速度、质量、内存三方权衡下的甜点档。
  • 16GB内存机型,Q4_K_M大概率在长上下文时被杀,建议Q3_K_M,同时把上下文压到2048。
  • 8GB内存机型,别贪,Q2_K配swap,或者干脆换小模型。

量化的代价在具体任务上体现得很明显。我拿同样35B模型分别做Q4_K_M和Q2_K实测,中文常识问答时Q4的表现接近未量化版本,而Q2_K经常在“9.11和9.8谁大”这种问题上翻车,代码生成的错误率也明显更高。你要是有中文长文本或数学推理需求,务必留足内存上Q4,别为了塞进手机强行Q2。

2.3 量化的代价:为什么手机上35B可能干不过蒸馏小模型

这是很多人的认知盲区。手机上的“35B Q2_K”和云端的“35B FP16”完全不是同一个东西。Q2_K只有约9.5GB,质量损失非常大,在很多实际任务上可能还不如一个7B的Q4。

我做一个直观对比:Qwen2.5-7B-Instruct用Q4_K_M,文件约4.4GB,在手机上能跑12-15 tokens/s。Llama-3.1-35B用Q2_K,在8GB手机上只能跑1-2 tokens/s,质量还时好时坏。如果你是做客服问答、资料摘要这种实际业务,我会毫不犹豫选7B Q4,而不是35B Q2。

所以“参数越大越聪明”是有前提的——量化损失可以抹平几个B的参数优势。这也是为什么端侧AI最终还是要走“大模型蒸馏成小模型”的路,而不是把大模型硬塞进手机。后面第5节还会细说。

3. 住进手机以后:mmap、统一内存和进程保命

权重量化完,只是拿到了“入场券”。真正住进手机,还要面对操作系统这一层。这部分的经验,是我跑了无数次“进程被杀”以后总结出来的。

3.1 mmap:让闪存排队假装内存

llama.cpp加载GGUF模型时默认使用mmap,而不是一次性把整个文件读进内存。mmap的作用是把磁盘上的模型文件映射到进程的虚拟地址空间,实际数据按需加载——用到了哪个页面,内核才从磁盘读哪个页面。

这个机制在PC上很成熟,但手机上完全不一样。PC的NVMe SSD快,内存不够时换页也能支撑。手机的闪存虽然顺序读取速度不差,但端侧LLM推理的权重访问是反复顺序扫描,每一层权重都要完整过一遍。如果模型文件大于物理内存,mmap的页面会在每次扫描时反复缺页、换入、丢弃、再缺页,性能可能跌到0.5 tokens/s以下。

所以mmap是一把双刃剑:

  • 好处:不用一次性把所有权重加载进内存,进程启动快,内存占用低。
  • 坏处:内存不够时,每次解码都会触发大量磁盘换页,速度急剧下降。

我在8GB的iPhone上跑35B Q2_K时,明显感受到“首token要等半天,后续token等得更久”的换页地狱。一个非常实用的排查方法是:如果生成的每token延迟呈现锯齿状波动,时快时慢,那就是页面换入的典型症状。

相应地,llama.cpp有个--mlock参数,作用是把权重页面锁进物理内存,防止被换出。PC上这招很好用,但手机上内存本来就紧,mlock只会让你更快触发OOM后被系统杀掉。我在手机端从来没有成功用过mlock跑35B级别模型,不建议尝试。

3.2 统一内存和GPU分片:为什么Apple Silicon占便宜

手机SoC和PC的一个大区别在于:GPU和CPU共享同一块物理内存,这在硬件上天然是“统一内存”。但不同平台的软件栈差距很大。

Apple的Metal后端在llama.cpp里非常成熟,GPU可以直接访问模型权重和KV Cache,不需要CPU中转拷贝。这也是为什么同样跑7B模型,A17 Pro的Metal后端往往能跑出比一些Android旗舰更稳的速度。

Android这边虽然GPU也共享RAM,但Vulkan驱动的成熟度参差不齐。Adreno的驱动在高端机上表现不错,Mali的驱动就看机型和驱动版本了。我实测过同一台手机用CPU跑和Vulkan跑,结果经常是CPU路径更稳定——Vulkan编译着色器以后有加速,但不同opition下反而更慢,这是很常见的坑。

llama.cpp的-ngl参数控制多少层offload到GPU。在手机上,这个参数不是越大越好,因为GPU和CPU共享内存,你offload的层数越多,系统内存占用越大。通常我建议先试-ngl 20到40,观察内存压力和一个token生成速度,再逐步增加。

还有一个容易踩的坑是:别在手机上用--tensor-split这类参数硬拆。手机没有多GPU,拆了反而会让每个设备之间的同步开销吃掉所有收益。

3.3 后台进程与LMK:安卓的生死局

这部分的知识是我用“被杀了无数次”换来的。

Android的Low Memory Killer会根据oom_score_adj给每个进程打分,内存不足时优先杀掉分数高的进程。一个占用大量内存的本地LLM推理进程,在系统看来就是一个完美的“牺牲品”。你切到别的App发个消息,回来发现模型已经没了,需要重新加载——这种事我踩了太多次。

实用的应对策略:

  • 在系统设置里为推理App关闭电池优化,并手动锁定后台任务。
  • 如果用的是Termux跑llama.cpp,在系统应用管理里把Termux设为“不受限制”。
  • 尽量把模型文件放在App自己的沙盒目录,而不是公共存储目录,避免文件系统权限问题导致mmap失败。
  • 不要追求过长的上下文。4096和8192之间,KV Cache的差距可能直接决定你是否触发LMK。

iOS这边虽然Jetsam机制更严格,但用LLMFarm这类原生App时,模型加载后只要不切到重度App,通常能稳定跑完一次对话。iOS不支持像Android那样手动调LMK白名单,所以唯一的办法就是控制模型体积、控制上下文长度,让进程的内存画像保持在安全线以下。

4. 实测日志:两种“跑起来”的姿势

理论讲再多,不如看真实跑起来的样子。我分别用两台设备做了35B模型的端侧推理测试,涵盖了“内存不够硬撑”和“内存够用舒服跑”两种典型场景。

4.1 姿势一:8GB iPhone强行streaming 35B Q2

测试设备是一台8GB内存的iPhone 15 Pro,模型是Llama-3.1-35B-Instruct的Q2_K GGUF,文件大小约9.8GB,用的是LLMFarm加载,Metal后端。

启动加载过程花了大概40秒,因为9.8GB文件超过物理内存,首token延迟长达20多秒。生成阶段稳定下来后,速度在0.8-1.3 tokens/s之间波动。内存压力大时,系统会让App的页面频繁换出,速度会掉到0.5 tokens/s以下。

体验上,这基本属于“能跑但没法用”的范畴:问一个简单问题,等半分钟才看到第一个字开始蹦,然后每个字间隔一秒多。我把上下文从4096降到2048,速度略有提升,但依然达不到2 tokens/s。A17 Pro的LPDDR5带宽51.2GB/s在这个场景下完全被换页拖垮。

4.2 姿势二:24GB安卓把Q4整个塞进内存

第二台测试设备是24GB内存的骁龙8 Gen 3机型,模型是同样的Llama-3.1-35B-Instruct,但换成了Q4_K_M,GGUF约18.5GB。用llama.cpp的Android构建跑,开启Vulkan后端,-ngl设为30,上下文4096。

模型加载大约12秒(18.5GB全部进入内存),首token延迟约3秒,之后稳定在2.8-3.4 tokens/s之间。这个速度虽然比不上云端,但已经属于“能忍受”的范畴:等一小会儿,能看到完整生成的句子,用来写邮件草稿、做简单代码补全都行。

发热和降频是另一个影响因素。连续生成2分钟后机身明显发烫,A17 Pro和骁龙8 Gen 3都会降频,导致tokens/s下降10-20%。我后来加了一个散热背夹,速度波动明显改善。

4.3 性能数据汇总与选择建议

设备模型量化档权重大小首token延迟稳定生成速度体验评价
iPhone 15 Pro 8GBQ2_K约9.8GB20秒以上0.8-1.3 tokens/s能跑但基本没法用
骁龙8 Gen 3 24GBQ4_K_M约18.5GB约3秒2.8-3.4 tokens/s勉强可用

我个人的结论是:35B模型在手机上“能跑”早就不是问题,问题是“跑到什么速度才算能用”。如果你只有8GB内存的设备,老老实实跑7B-14B级别模型,体验会好很多。如果非要体验35B,请选24GB以上内存的旗舰机,并接受每秒2-3个token的现实。

5. 还能更快吗:投机解码、KV Cache与模型精简

跑通只是第一步,能不能让体验从“痛苦”变成“凑合”,才是真正决定端侧大模型能否落地的关键。

5.1 投机解码:小模型打草稿,大模型批改

投机解码的思路是:先用一个很小的模型(比如3B)快速生成4-8个候选token,然后把候选token序列交给35B大模型一次性验证。大模型确认哪些token是对的,对的直接采纳,错的就从第一个错的位置重新生成。

为什么这个策略适合手机?因为大模型的decode瓶颈是内存带宽,逐token生成要一次次扫描全部权重。投机解码通过batch验证,让一次权重扫描能“批发”出多个token,相当于把内存带宽的利用率拉高了。实测中,如果小模型和大模型能力差距不大,投机解码能把端侧生成速度提升1.5到2倍。

llama.cpp已经内置了投机解码支持,在服务器参数里加上--speculative模型路径和草稿模型参数即可。手机上内存更紧张,但这个思路依然是当前效能提升空间最明显的方案。需要注意:小模型不能太“笨”,否则它生成的候选token大部分被大模型否决,反而增加无效开销。

5.2 KV Cache也能压缩

KV Cache在长上下文下会占几百MB甚至1GB以上。对手机这种寸土寸金的环境,KV Cache量化是性价比很高的一步。

llama.cpp提供--cache-type-k和--cache-type-v参数,把KV Cache从FP16压到Q8_0甚至Q4_0。实测在35B模型、4096上下文场景下,KV Cache量化能省下200-300MB内存,质量损失在大多数任务中几乎不可感知。MLC-LLM等框架也支持FP8的KV Cache。

另外,对话轮次多的场景建议定期清空KV Cache或做context compaction。自己写客户端时,多轮对话的上下文会越积越长,KV Cache会持续增长直到把内存吃光。这算是我在实践中踩过最深的坑之一——明明权重没动,内存却随着对话轮次一路涨到被系统杀掉。

5.3 真正的终局:MoE与蒸馏

就算量化再激进、投机解码再高效,把35B Dense模型全量塞进手机,依然是一件“性价比很低”的事。端侧AI真正的大方向是两个:MoE架构和蒸馏小模型。

MoE(混合专家)模型的总参数量可能很大,但每次推理只激活其中一小部分专家。比如Mixtral 8x7B总参数约47B,但每个token只激活约12B参数;Qwen3-30B-A3B总参数30B,激活参数只有3B。这类模型特别适合端侧:总容量决定了知识量,激活参数决定了推理时的实际内存带宽开销。做端侧部署选型时,优先看激活参数量而不是总参数量。

蒸馏则更直接:把35B模型的知识“压缩”到3B-7B的小模型里。Llama-3.2-3B、Qwen2.5-3B这类模型在手机上能跑到15-20 tokens/s,很多NLP任务的效果已经够用。对绝大多数实际场景来说,一个流畅运行的3B蒸馏模型,比一个卡成PPT的35B硬塞模型更有价值。

5.4 实用建议:手机跑35B之前先想清楚场景

最后给准备自己动手折腾的朋友几条实操建议:

  1. 先量化再跑:直接用FP16的35B模型在手机上跑,等于自杀。至少转成GGUF并量化到Q4_K_M再谈其他。
  2. 控制上下文长度:手机端别贪4096以上,KV Cache和内存是线性关系。
  3. 先跑通再优化:首token延迟高,先看是加载问题还是PreFill问题;稳定速度差,先看是不是触发了换页。
  4. 用框架现成方案:iOS直接用LLMFarm,Android直接用llama.cpp或Ollama,不要从零造轮子。MLC-LLM也可以,但配置门槛高一些。
  5. 散热别忽视:持续推理发热降频会让tokens/s再掉20%,风扇或散热背夹不是智商税。

我在实际折腾中最大的体会是:参数数量的执念并没有意义,“模型多大能在手机上跑多快”才是真正要回答的问题。350亿参数住进手机这件事,技术上确实做到了,但目前它是一件“为了证明能跑而跑”的事。真要天天用,我反而建议你把目光从小参数版本开始,把速度和稳定性做扎实,再考虑怎么往上够更大的模型。端侧推理的乐趣本来就该在“折腾”这个过程里,而不在最终那个炫酷的数字上。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询