6GB显存跑35B参数量的MoE模型,放在两年前我会觉得这是段子。Full精度下光模型权重就要70GB,哪怕做4bit量化也还要20GB上下,怎么看都和6GB不搭边。但MoE架构把这个"不可能"变成了"有条件地可能"——35B是总参数,真正推理时每个token只激活其中一小部分专家。配合FreeToken这类专门为低显存场景设计的推理调度框架,把不干活的专家卸载到内存、按需换入,6GB显存确实能跑,而且不是那种"能跑但完全没法用"的花架子。
这篇文章写给手头只有6GB/8GB显存的老卡、但想本地跑一跑35B级别MoE模型的人。我会先把显存的账一笔一笔算清楚,再给出一套可以直接照抄的FreeToken极限配置,最后放上我实测的性能数据和翻车记录。提前说清楚:这套方案不追求跑分好看,目标是稳定、可持续地跑完整段对话。如果你是那种"显存不够就加钱换卡"的朋友,现在可以关页面了。
1. 先把显存账算明白:35B MoE的每笔支出到底花在哪
1.1 MoE的"总参数"和"激活参数"是两笔不一样的账
MoE(Mixture of Experts,混合专家)架构和传统Dense模型最大的区别在于:Dense模型处理每个token时,所有参数都要参与计算,无论实际需不需要;MoE模型则把前馈网络(FFN)拆成了几十个独立的"专家",每个token只由路由器(router)打分后挑选top-2或top-3个专家来处理。
一个典型的35B MoE,通常的结构是:共享的Attention层加共享的embedding,再加几十个专家FFN。这里35B是全部参数的总和,被称为"总参数";而每个token真正经过的只有"共享部分加被选中的2-3个专家",这部分被称为"激活参数",往往只有8-11B。像社区里常见的Qwen系MoE、Mixtral 8x7B这类开源模型,都是这个套路。
打个比方:一家公司有35名员工,但每接到一个任务,只有组长(router)点名2-3个对口专家去干活,其他人还在工位上待命。干活的人是少数,但所有人工资都要发——对应到显存里,就是所有专家的权重都要有地方放。区别只在于放在哪:放在GPU显存里,还是放到CPU内存里等着被叫。
1.2 六项显存开支逐笔拆解
要把6GB的账算明白,先得知道推理时显存到底被谁吃了。我按实际占用从大到小列出六项:
| 开支项 | 估算方式 | 6GB场景下的体感 |
|---|---|---|
| 模型权重(全量驻留) | 总参数 × 每参数字节数 | Q4量化后约17.5GB,直接劝退 |
| 模型权重(仅驻留激活部分) | 激活参数 × 每参数字节数 | 约4-5GB,这是FreeToken的主战场 |
| KV Cache | 层数 × 注意力头维度 × 序列长度 | 2K上下文、int8量化后约0.6-0.9GB |
| 中间激活值 | 批次大小 × 序列长度 × 隐藏维度 | batch_size=1时,通常小于200MB |
| CUDA上下文与框架开销 | 固定值 | 约0.5-0.8GB |
| 临时buffer与碎片 | 动态 | 0.2-1GB,视分配策略而定 |
这六项加起来,就回答了一个关键问题:为什么"全量加载"路线在6GB上走不通——光是Q4量化后的35B权重就要17.5GB,这还没算KV Cache和激活值。但如果你走"专家动态调度"路线,GPU上只放共享层加当前活跃的少数专家加量化权重,权重大头就降到了4-5GB,剩下的空间够放KV Cache和零碎开销。
1.3 6GB卡凭什么有机会
核心原因只有一个:MoE的稀疏激活特性,允许你把"不用的参数暂时赶出显存"。Dense模型没法这么干,因为每个token都要用全部参数,显存不够就是不够,一点通融余地都没有。MoE不一样,你在GPU上只需要保证"当前被调用的专家在场",至于其余几十个待命专家,完全可以住在CPU内存或NVMe SSD的交换区里,被router点到时再换进来。
这就是FreeToken这类调度框架存在的根本逻辑。它做的事情和一个大型仓库的调度员一样:知道每块货(专家权重)放在哪个仓库(显存/内存/磁盘),计算什么时间该把哪块货搬到装卸区(GPU显存),然后以最小的搬运代价完成交接。只要搬运延迟控制得住,6GB也能以中低速率的流式方式稳定跑完整个对话。不是快,但是能用。
2. FreeToken的三板斧:量化、卸载、动态调度是怎么配合的
2.1 FreeToken解决的是哪一类痛
如果你在8GB甚至6GB显存上部署过大模型,大概率经历过这种场景:模型加载完提示"CUDA out of memory",降量化等级、缩短上下文、换更小的模型,折腾一圈还是差几百MB。传统的transformers管线是"全量预加载"思维,而FreeToken的出发点完全相反——它默认"显存不够用是常态",所以设计上把所有重量级的权重都做成可调度的资源,而不是一次性占死。
还有个经常被忽略的问题:市面上大部分推理框架在低显存模式下用的是"pipeline并行卸载",就是整层整iter地把权重搬进搬出。这种粗粒度卸载在Dense模型上可以接受,但在MoE上就是灾难——因为MoE的专家是稀疏调用的,你根本不知道下一个token会激活哪个专家,整层搬运会导致大量无效搬运。FreeToken的粒度是"专家级"的,也就是只搬被选中的那几个专家,效率高一个量级。
2.2 第一板斧:专家级动态装卸
FreeToken把每个专家FFN的权重做成了独立的分页块,GPU里只保留最近常被激活的N个专家(N由配置里的gpu_expert_capacity控制)。当router选中一个不在GPU上的专家时,框架会把当前最久没用的驻留专家换出到系统内存(或配置好的swap目录),再把目标专家换进来。
这一步的代价是PCIe带宽。实测单次专家换入换出大约需要几十毫秒到一百多毫秒,取决于权重大小和PCIe版本。在流式生成场景下,这个延迟会被后续token生成覆盖掉一部分,所以体感上不算灾难;但在首token阶段,如果你第一句话激活的专家正好都不在GPU上,首token延迟会明显变长。
2.3 第二板斧:分层混合精度
量化不是新鲜事,但FreeToken在"哪部分该量化、哪部分该保精度"上做得比较细。它支持把不同层配置成不同精度:embedding层、router层、norm层、Attention层可以用FP16保持精度,只有专家FFN压到INT4或INT8。
这里有个非常容易踩的坑:router层是MoE的"大脑",它负责任务分配,如果router被压到4bit,打分会严重失真,专家选择会变得随机,生成内容逻辑混乱。我之前遇到过一版配置,生成出来的句子语法全对但语义驴唇不对马嘴,排查了半天,最后发现是router被量化了。FreeToken的做法是router和顶层输出层强制FP16,只对专家做低比特量化。这个策略在6GB场景下尤其重要,因为显存越紧张,你越不会去注意"某层精度崩了"这种慢性病。
2.4 第三板斧:KV Cache定量配给
KV Cache是除了权重之外第二大显存消耗者。MoE的attention是共享的,所以KV Cache比同规模Dense模型小得多,但在6GB这种极限场景下还是得省。FreeToken提供三个选项:
- KV Cache量化到int8或int4(实测int8对质量的影响可忽略,int4会有轻微损失)
- 滑动窗口注意力,只保留最近N个token的KV
- 超限自动淘汰早期token(类似滚动窗口)
这三个功能叠加后,2K上下文的KV Cache占用能压到0.6GB以内。注意,KV Cache量化不是所有后端都支持,如果你跑的是纯CPU推理,部分后端不支持量化KV,配置文件里写了也不生效,日志里会有警告。
3. 极限配置实操:从安装到跑起35B MoE的完整流程
3.1 环境准备:先把地基打稳
我的实测环境是:Windows 11加WSL2(Ubuntu 22.04),显卡为6GB显存的老卡,驱动版本550+,CUDA 12.4,Python 3.10。FreeToken的安装很简单:
pip install freetoken freetoken doctorfreetoken doctor会检查驱动、CUDA、可用显存、系统内存和NVMe空间,输出一张环境体检表。这一步别跳过——它能告诉你的不仅是有没有装好,还会标出当前环境里哪个环节会成为瓶颈。比如我一开始没注意swap目录空间不够,后来加载到一半就崩了。
如果你的显卡带不动CUDA 12.x(比如偏老的10系卡),需要装FreeToken的CPU-only版本,那就只能纯CPU推理,6GB显存彻底闲置,速度会掉到1 token/s以下,不推荐。FreeToken能救穷,但救不了完全不支持CUDA的卡。
3.2 选型:什么样的35B MoE适合6GB卡
不是所有35B MoE都适合低显存场景。选型时我建议按三把尺子量:
- 总参数在35B附近,别太胖
- 激活参数越少越好,理想值是≤10B
- 社区有现成的Q4_K_M或IQ4_XS的GGUF量化版本
以目前社区比较活跃的35B级MoE为例,Qwen系的开源MoE变体、Mixtral系的衍生量化版都算合适。模型下载用FreeToken自带命令就行:
freetoken pull your-registry/35b-moe-chat --quant q4_k_m下载前注意磁盘空间,GGUF Q4文件大约18-20GB,加上系统内存里要放一份未调度的权重副本,内存建议至少24GB。这个结论我在第5节会再强调——显存只是第一道坎,系统内存才是第二道坎。
3.3 config.yaml逐项解读:每个参数都在干什么
这节是全文的核心。给出我最终稳定运行的配置文件,并逐项说明理由:
model: path: "/models/35b-moe-chat-q4_k_m.gguf" quant: "q4_k_m" keep_fp16_layers: ["router", "norm", "embed"] memory: gpu_vram_budget: 5.2 gpu_expert_capacity: 4 cpu_offload: true swap_dir: "/nvme/swap" kv_cache: max_context: 2048 quant: "int8" sliding_window: 512 auto_evict: true inference: batch_size: 1 max_tokens: 512 flash_attn: true top_k_experts: 2逐个解释:
gpu_vram_budget:给模型和KV Cache划拨的显存上限,单位GB。6GB卡设5.2,剩下0.8GB留给CUDA上下文、驱动预留和图像输出缓冲。如果你桌面上还开着浏览器看视频,建议降到5.0。gpu_expert_capacity:GPU上最多同时驻留几个专家。4是个甜点位,少于4会导致频繁换入换出,多于4会让KV Cache没地方放。显存更紧张的卡可以设2,但首token会更慢。keep_fp16_layers:强制保持FP16精度的层列表。router和norm必须保留,embedding建议保留,否则中文质量下降明显。swap_dir:专家换出的落盘位置。必须放NVMe SSD,放到机械硬盘你会体验到什么叫"每生成一个token等一分钟"。kv_cache.max_context:最大上下文长度。6GB卡2048是平衡点,4096会让KV Cache膨胀到1.5GB以上,挤压专家驻留空间。sliding_window:滑动窗口大小。512意思是永远只保留最近512个token的完整KV,更早的逐步淘汰。这个参数对显存用量影响很大。flash_attn:必须开,不开的话注意力计算的内存峰值会高出一截,6GB卡可能直接OOM。top_k_experts:每个token激活几个专家。默认2,别改1。改成1虽然token生成变快,但模型表达能力下降很离谱,问答质量肉眼可见地变差。
3.4 启动、验证和第一行日志
配置文件就绪后,启动服务:
freetoken serve --config config.yaml看到类似这样的日志就说明加载成功:
[load] model loaded: 35b-moe-chat-q4_k_m.gguf [mem] CUDA context: 0.7GB, VRAM budget: 5.2GB [mem] active experts: 4/64, shared layers: FP16 [mem] KV cache: int8, max context: 2048, current: 0.8GB [serve] listening on 127.0.0.1:8080然后跑一个最简单的验证请求:
import requests resp = requests.post( "http://127.0.0.1:8080/generate", json={"prompt": "请用一句话解释什么是MoE模型", "max_tokens": 100} ) print(resp.json()["text"])如果能在30秒内打出完整回答,说明配置基本成立。如果出现"CUDA out of memory",直接跳到第5节看排查方案。
4. 性能压榨实测:显存、延迟、速度的三组对照数据
4.1 三组实测结果,先上数据
我用同一份35B MoE的Q4_K_M版本,在6GB老卡和NVMe SSD环境下跑了三组配置,结果如下:
| 配置 | 显存占用 | 首token延迟 | 平均生成速度 |
|---|---|---|---|
| FP16全量驻留 | OOM,无法启动 | - | - |
| Q4全量驻留 | OOM(需要17.5GB) | - | - |
| Q4 + 专家卸载(驻留4个) | 5.1GB | 8.6s | 3.4 token/s |
| Q4 + 专家卸载(驻留2个) | 4.3GB | 11.2s | 2.8 token/s |
| Q4 + 专家卸载(驻留4个)+ KV int8量化 | 4.6GB | 9.1s | 3.1 token/s |
几个值得注意的结论:
- 驻留专家从4降到2,只省了0.8GB显存,但首token延迟增加了30%,生成速度掉了快18%。因为专家换入换出频率翻倍,PCIe搬运成了瓶颈。
- KV Cache从FP16改成int8,省了约0.5GB显存,生成速度反而降了0.3 token/s。原因不是量化本身慢,而是省下来的显存没有提高专家驻留数,等于白省。这个发现很关键——省显存不是目的,省下来的显存要用在刀刃上。
4.2 调参的先后顺序:先动哪个,后动哪个
在6GB这张卡上调试,顺序比参数本身更重要。我踩了几轮之后总结的调参顺序是:
- 先把
gpu_vram_budget压到5.0以下,确保模型能启动,否则一切都是空谈。 - 再动
gpu_expert_capacity,从默认的8往下调,每调一次跑一个20 token的测试,观察显存余量和速度。找到"再降一档就OOM"的临界点。 - 接着动
kv_cache.max_context,从4096降到2048,观察显存余量是否还在警戒线内。 - 最后才考虑动量化等级,因为量化直接影响生成质量。如果前面三步已经稳定,就没有必要为了省那2GB把Q4换成Q3。
这个顺序背后的逻辑是:先解决"跑不跑得起来",再解决"跑得稳不稳",最后才考虑"跑得好不好"。
4.3 上下文长度与驻留专家的三角博弈
6GB显存里,上下文长度和专家驻留数是直接竞争关系。KV Cache每多占1GB,专家就少驻留两个。而专家驻留少的直接后果是换入换出变频繁,速度下降。
实测数据:max_context=4096时,KV Cache占用约1.6GB,此时专家最多只能驻留3个,首token延迟到12秒以上;max_context=2048时,KV Cache约0.7GB,专家能驻留4个,首token回到9秒以内。所以如果你主要做短问答,把max_context压到1024甚至512,把省下的显存全给专家驻留,反而能获得更好的综合体验。
5. 踩坑复盘:6GB跑35B MoE最容易翻车的七个环节
5.1 加载即OOM:90%是vram_budget给高了
第一次启动,我按直觉把gpu_vram_budget设成5.6GB,结果日志刚打印到模型加载就报"CUDA out of memory"。去掉CUDA上下文(0.7GB)、驱动预留(0.3GB)、以及桌面合成器的动态占用,留给框架实际上不到5GB。把budget改成5.0之后稳定启动。使用过程里如果开着浏览器直播,还得再降一档。显存余量建议每10分钟用nvidia-smi看一次。
5.2 慢到怀疑人生:首token 40秒排查链路
遇到过两次"首token要40秒"的情况,一次是配置问题,一次是硬件问题。完整排查链路如下:
- 先看日志里每次生成的
swap in耗时。如果单次swap超过200ms,说明专家权重块太大或SSD太慢。 - 再看
nvidia-smi里的GPU利用率。如果生成过程中GPU利用率不到30%,说明显卡在等CPU搬运专家,瓶颈在PCIe或磁盘。 - 然后看CPU是否跑满。如果CPU占用100%,说明反而不在搬运,而是某些算子落到了CPU上执行。
- 最后检查
flash_attn是否真的生效——有些后端对GGUF的flash_attn支持不完整,会静默回退到标准注意力,显存峰值直接翻倍。
我的最终解决方案:把swap_dir从机械硬盘挪到NVMe SSD,首token从40秒降到9秒。如果你没有NVMe,至少确保swap目录和系统不在同一块机械硬盘上,减少争抢。
5.3 生成内容驴唇不对马嘴:别动router层的精度
有一次为了压显存,把整个模型压到Q4,包括router。结果模型能跑,但生成内容逻辑混乱——比如问"今天天气怎么样",模型回答"红烧肉是一道经典的川菜"。这种错误非常隐蔽,因为语法完全正常,你很难第一时间想到是router精度的问题。后来在config里明确keep_fp16_layers: ["router"],问题立刻消失。
5.4 系统内存不足:显存解放了,内存又成瓶颈
FreeToken的专家卸载策略会把大量权重放在系统内存里。35B Q4的GGUF文件约19GB,权重加载进内存后需要接近20GB的RAM空间。如果你的电脑只有16GB内存,加载阶段就会直接被操作系统杀掉进程。
解决方案有两个:一是换更低的量化等级(Q3_K_M大约15GB),二是给操作系统设置足够大的虚拟内存,并把页面文件放在SSD上。但这会让运行时内存读写的速度下降,实测生成速度会再掉20%左右。所以选模型前一定先算清楚自己的内存账——6GB显存加32GB内存才是最稳妥的搭配。
5.5 量化版本不兼容导致乱码
FreeToken对GGUF文件有版本要求。社区里第三方量化工具刷出来的部分旧版GGUF文件,在加载时会静默出错——模型能加载,但输出全是乱码。排查方法很简单:先跑freetoken doctor看GGUF版本兼容性,再用freetoken pull指定官方quant版本,不要自己去来历不明的渠道下载GGUF文件。
5.6 中文乱码和换行异常
部分35B MoE模型的tokenizer中文词表覆盖不全,尤其是欧美团队训练的多语言模型,对简体中文的分词质量参差不齐。常见表现是:长句生成到一半,突然出现异常字符。这大概率是tokenizer配置问题,不是显存问题。解决办法是确保config.yaml里指明了模型的官方tokenizer文件路径,不要让FreeToken用默认令牌化配置。
5.7 采样参数别乱调:MoE对temperature很敏感
MoE模型的router逻辑决定了它对采样参数比Dense模型更敏感。我用默认的temperature 1.0跑的时候,生成内容发散得厉害,经常从一个话题跳到另一个话题。后来按照社区经验收敛到temperature 0.7、top_p 0.9、repetition_penalty 1.1之后,生成质量才稳定下来。6GB跑35B本来就勉勉强强,采样参数再放飞自我,质量基本没法看。
最后说点个人体会。在6GB这张卡上跑35B MoE,本质是一场"预算管理"的游戏:显存是总预算,量化砍的是权重开支,专家卸载砍的是驻留开支,KV量化砍的是对话开支,每一笔钱都要花在刀刃上。FreeToken的价值不在于它跑得有多快,而在于它把"显存失控"这件事变成了"显存可控"——你至少有了一个清晰的旋钮面板,而不是在OOM和降级之间反复横跳。
如果你照着这套配置跑通了,我建议下一步试验一下把max_context改成1024,省下来的显存全部分给专家驻留,在短问答场景下的体验会比2048更顺。这套方案的性能天花板就在那里,但能在6GB上把35B MoE带起来,本身就是一件值得记录的事。祝你的token真正自由。