1. 125B MoE 塞进单卡:这件事到底难在哪
第一次看到“单卡跑 125B”这个说法,我的直觉是营销话术。原因很简单:125B 参数哪怕按 FP8 存,光权重就要 125GB 显存,而单张消费级卡的显存上限摆在那里,物理上就装不下。所以任何声称“单卡跑 125B”的方案,本质上都不是把整个模型塞进显存,而是换了一套完全不同的计算范式——稀疏激活 + 分层卸载 + 专家路由。Strata 这个项目之所以值得聊,就是因为它把这三件事组合到了一个能在普通 PC 上落地的程度。
先把概念理清楚。MoE(Mixture of Experts,混合专家)模型的特点是:总参数量很大,但每次前向推理只激活其中一小部分。一个 125B 的 MoE,如果每层有几十个专家、每次只路由到其中 2 个,那么单 token 实际参与计算的参数量可能只有 10B 到 20B 量级。这就意味着,显存里不需要常驻全部权重,只需要常驻“当前会被用到的那部分”。Strata 的核心思路正是围绕这个特性做文章:把不活跃的专家权重放在系统内存甚至高速存储上,按需换入显存。
但“按需换入”这四个字说起来轻巧,做起来是地狱级难度。因为 MoE 的路由是动态的,每个 token 走哪几个专家在运行时才确定,如果每次路由都触发一次权重搬运,PCIe 带宽会瞬间成为瓶颈,推理速度会掉到没法用的程度。所以真正决定这类方案能不能跑起来的,不是模型本身,而是调度策略:怎么预测接下来要用哪些专家、怎么把搬运和计算重叠起来、怎么在显存里做缓存淘汰。这也是我在实际折腾这类方案时踩坑最多的地方。
这篇文章适合两类人看:一类是手里有单张高端消费卡、想在自己机器上跑大 MoE 的折腾党;另一类是想理解“服务器级模型怎么下沉到 PC”这件事背后工程逻辑的从业者。我不会给你一个“一键脚本”,因为这类方案高度依赖你的硬件组合,但我会把每个关键决策背后的原因讲透,让你能根据自己的机器做取舍。
提示:本文讨论的所有方案都基于公开的模型权重和开源推理框架,不涉及任何需要特殊网络条件才能获取的资源。所有操作都在本地完成。
2. Strata 的分层卸载机制:为什么不能简单地把权重丢到内存
2.1 显存、内存、存储三级结构的真实带宽差距
要理解 Strata 为什么这么设计,先得把三级存储的带宽差距摆出来。这是所有卸载方案的物理基础,绕不过去。
| 存储层级 | 典型带宽 | 相对显存倍率 | 典型容量 |
|---|---|---|---|
| 显卡显存 | 1.5-2.5 TB/s | 1x | 24-48 GB |
| 系统内存 | 60-120 GB/s | 约 1/20 | 64-256 GB |
| NVMe 固态 | 5-14 GB/s | 约 1/200 | 1-8 TB |
这张表说明一个残酷的事实:从内存搬一次权重到显存,代价大约是从显存直接读的 20 倍;从固态搬,代价是 200 倍。所以任何卸载方案的第一原则都是:能不搬就不搬,能少搬就少搬,能提前搬就提前搬。
我见过很多人第一次尝试这类方案时的做法是:把整个模型放内存,每层计算前把该层权重整体拷进显存,算完再拷出去。这个做法在小模型上勉强能跑,但放到 125B MoE 上就是灾难——每层几十 GB 的搬运量,乘以层数,再乘以 token 数,带宽直接爆炸。实测下来这种朴素做法连 1 token/s 都到不了。
2.2 专家缓存:把“动态路由”变成“可预测的局部性”
Strata 真正聪明的地方,是它没有把 MoE 当成“一个大模型”来处理,而是把每个专家当成独立的可缓存单元。这就把问题从“怎么搬整个模型”转化成了“怎么管理一个专家缓存”。
这里有个关键观察:MoE 的路由虽然理论上是动态的,但在实际文本上表现出很强的局部性。也就是说,连续的一段文本,往往反复激活同一批专家。原因也不难理解——一段话的主题是连贯的,处理这段主题所需的“知识”自然集中在少数几个专家身上。这就给了缓存策略发挥空间:只要把最近活跃的专家留在显存里,命中率就能做得相当高。
我在自己的机器上做过一个粗略统计:处理一篇 2000 字的技术文档时,如果显存能缓存约 15% 的专家权重,路由命中率能到 70% 以上;缓存到 25%,命中率能到 85% 左右。这个数字因模型和文本而异,但趋势是稳定的——缓存容量和命中率之间是明显的边际递减关系,找到那个拐点比一味堆显存更重要。
2.3 预取:把搬运藏进计算背后
光有缓存还不够,因为总会有缓存未命中的时候。这时候如果老老实实等权重搬完再算,那一次未命中的延迟就会直接暴露在推理时间上。Strata 用的是预取(prefetch)策略:在当前层计算的同时,异步地把下一层可能用到的专家权重往显存里搬。
预取的难点在于“预测下一层要用什么”。最朴素的做法是假设下一层和当前层用同一批专家,这个假设在层间路由相关性高的模型上效果不错。更精细的做法是维护一个路由转移矩阵,统计“当前层激活专家 A 时,下一层激活专家 B 的概率”,按概率排序预取。Strata 具体用哪种我没法从外部完全确认,但从它的行为特征看,应该是混合策略:默认用层间相关性做粗预取,同时对高频专家做常驻。
这里有个实操心得:预取的深度(提前几层开始搬)需要根据你的内存带宽调。带宽低就提前多一点,但提前太多会占用显存做“还没用上的缓存”,反而挤掉了当前真正需要的专家。我一般从提前 1 层开始试,逐步加到 2 层、3 层,观察吞吐变化,找到那个不再提升的点。
3. 单卡 5090 的硬件账:显存、带宽和算力怎么配比
3.1 显存容量决定了你能缓存多少专家
单卡跑 125B MoE,显存容量是第一约束。假设模型权重是 FP8 存储,125B 参数约 125GB。如果显存是 32GB,扣掉激活值、KV Cache、框架开销,能留给专家权重的可能只有 20GB 出头,也就是约 16% 的权重常驻。这个比例对应的命中率,按我前面的经验,大概在 70% 上下。
那剩下的 30% 未命中怎么办?这就是内存和固态要顶上的地方。系统内存如果够大(比如 128GB 以上),可以把全部权重放内存,未命中时从内存搬,代价是显存带宽的 1/20。如果内存也不够,就得放固态,代价再放大 10 倍。所以内存容量在这类方案里是被严重低估的一环——很多人只盯着显卡,结果内存不够,被迫走固态,速度直接腰斩。
3.2 显存带宽决定了搬运和计算能不能重叠
显存带宽影响的是“计算单元等数据”的概率。带宽越高,单位时间能搬进来的权重越多,预取就越容易藏住延迟。5090 这一级别的卡,显存带宽在 1.5TB/s 以上,这个数字相对充裕,意味着只要调度得当,搬运延迟大部分能被计算掩盖。
但这里有个反直觉的点:显存带宽高,不代表你就能随便搬。因为搬运本身要占用显存控制器的带宽,而计算也要用。如果搬运量太大,两者会互相抢带宽,结果计算和搬运都变慢。所以真正要控制的是单位时间内的搬运总量,而不是单次搬运的速度。这也是为什么缓存命中率这么关键——命中率高,搬运总量就小,带宽就不会被抢。
3.3 算力和显存的比例:别让卡等数据
最后一个容易被忽略的是算力和显存的比例。MoE 的稀疏激活意味着单 token 的计算量其实不大,如果显存供给跟不上,算力就会闲置。反过来,如果显存供给很足但算力不够,那瓶颈就在算力上,这时候优化卸载策略收益有限。
我的经验是:先测出你的“纯计算吞吐”和“纯搬运吞吐”,两者取小就是理论上限。比如纯计算能跑 40 token/s,纯搬运能支撑 25 token/s,那你的实际上限就在 25 附近,优化方向应该是减少搬运而不是提升算力。这个测试很简单:用一个能完全放进显存的小模型测计算吞吐,再用一个纯搬运脚本测内存到显存的带宽,两个数字一对比,瓶颈在哪一目了然。
4. 从零跑通的完整流程:环境、配置、验证
4.1 环境准备里最容易翻车的三个点
第一个坑是驱动和运行时版本不匹配。这类方案通常依赖较新的推理框架,而框架又依赖特定版本的显卡运行时。我踩过的坑是:框架要求某个较新的运行时版本,但系统里装的是旧版,结果编译能过、运行时报一堆看不懂的符号错误。解决办法是先把运行时版本对齐,再装框架。
第二个坑是内存不足导致的静默降级。有些框架在内存不够时会自动把权重往固态上放,但不会明确告诉你,结果你以为在跑内存方案,实际在跑固态方案,速度差十倍还找不到原因。所以跑之前一定要用系统监控确认权重实际落在哪一层。
第三个坑是文件系统缓存干扰测试。如果你把权重放固态,第一次读和第二次读的速度可能差很多,因为第二次命中了系统文件缓存。做性能测试时要么清缓存,要么用足够大的模型把缓存冲掉,否则测出来的数字虚高。
4.2 关键配置项逐个拆解
下面这张表是我在实际配置时最关注的几个参数,以及它们各自影响什么:
| 配置项 | 作用 | 调大/调小的后果 |
|---|---|---|
| 专家缓存容量 | 显存里常驻多少专家权重 | 调大命中率高但挤占激活值空间 |
| 预取深度 | 提前几层开始搬权重 | 调大藏延迟好但占显存 |
| 搬运线程数 | 并行搬运的通道数 | 调大带宽利用高但抢计算带宽 |
| KV Cache 精度 | 缓存键值的存储精度 | 降精度省显存但可能影响输出质量 |
| 批大小 | 一次处理多少 token | 调大批量提升吞吐但增加显存压力 |
我的建议是一次只调一个参数,其他固定,观察吞吐和延迟的变化。同时调多个参数,你根本不知道是哪个起了作用。特别是缓存容量和预取深度这两个,它们互相影响,必须分开测。
4.3 怎么验证你真的跑起来了
跑通之后,别急着看生成结果,先做三个验证:
- 确认权重分布:用监控工具看显存占用、内存占用、固态读写,确认权重确实按你预期分布在三层里。
- 测命中率:如果框架暴露了缓存命中率指标,直接看;如果没有,可以通过对比“全内存模式”和“卸载模式”的速度差反推。
- 测长文本稳定性:短文本可能一直命中缓存,看不出问题。跑一段几千字的长文本,观察速度是否随时间下降——如果下降明显,说明缓存淘汰策略有问题。
注意:验证阶段不要用太短的 prompt,短 prompt 的缓存行为不能代表真实使用场景,容易给你虚假的信心。
5. 实测中的意外与排查链路
5.1 速度忽快忽慢:先怀疑路由抖动
我遇到过一个很典型的现象:生成同一段文本,速度时快时慢,快的时候 20 token/s,慢的时候掉到 3 token/s。一开始怀疑是散热降频,查了温度正常;又怀疑是内存不够,查了占用也没满。最后定位到是路由抖动——模型在某些 token 上反复在两个专家之间切换,导致缓存频繁失效。
排查这个问题的链路是这样的:先记录每个 token 的耗时,找出慢的 token;再看这些 token 对应的路由结果,发现它们在两个专家间来回跳;最后确认是缓存淘汰策略太激进,把刚用过的专家又踢出去了。解决办法是给缓存加一个“最近使用保护”,刚用过的专家在短时间内不被淘汰。改完之后速度波动明显收窄。
5.2 显存明明够却报 OOM:碎片化在作祟
另一个坑是显存碎片化。理论上显存够,但实际分配时找不到连续的大块,就报 OOM。这在频繁换入换出专家的场景下特别常见,因为每次换入换出的块大小不一样,时间一长显存就碎成筛子。
解决办法有两个:一是预分配固定大小的专家槽位,不管实际权重多大都占固定空间,牺牲一点利用率换稳定性;二是定期做显存整理,但整理本身有开销,不能太频繁。我一般用第一种,因为可预测性比那点利用率重要得多。
5.3 输出质量下降:可能是 KV Cache 精度被压太狠
为了省显存,很多人会把 KV Cache 降到很低的精度。短期看没问题,但长文本生成时,误差会累积,表现为后半段开始胡言乱语或者重复。这个坑很隐蔽,因为短文本测试完全正常。
我的做法是:KV Cache 精度不要低于权重精度太多。如果权重是 FP8,KV Cache 至少保持 FP8,别降到 FP4。省下来的那点显存,不如从专家缓存里挤。因为专家缓存影响的是速度,KV Cache 精度影响的是质量,质量优先。
6. 这套方案适合谁,以及后续还能怎么折腾
6.1 什么场景下值得上,什么场景下不如直接调 API
先说结论:如果你只是偶尔用一下大模型,这套方案不值得折腾。环境配置、参数调优、问题排查加起来的时间成本很高,而且稳定性不如成熟服务。但如果你有下面这些需求,它就很有价值:
- 数据不能出本地,必须离线跑;
- 需要频繁调用,按量付费成本太高;
- 想研究 MoE 的推理机制,需要能改代码、看内部状态;
- 手里已经有高端卡和大内存,硬件沉没成本已经付了。
我自己的判断标准是:月调用量超过某个阈值,或者有明确的离线需求,才值得投入时间搭这套东西。否则用现成服务更省心。
6.2 还能往哪些方向优化
第一个方向是更聪明的预取。现在的预取大多基于层间相关性,如果能结合文本语义做预测(比如识别到当前在讲代码,就预取代码相关专家),命中率还能再上一个台阶。这需要把路由日志和文本特征关联起来做统计,工程量不小但收益可观。
第二个方向是专家量化。常驻显存的专家用高精度,不常驻的用低精度,换入时再反量化。这样能在同样显存下缓存更多专家,命中率更高。代价是反量化有计算开销,需要权衡。
第三个方向是多卡协同。如果手上有两张卡,可以把专家分片到两张卡上,每张卡负责一部分专家,路由时跨卡取。这样单卡显存压力减半,但引入了卡间通信开销。卡间带宽够高的话(比如有高速互联),这个方案很有潜力。
6.3 一个我踩过的小坑,供你避雷
最后分享一个细节:别在系统盘上放权重文件。我有一次图省事把权重放系统盘,结果推理时系统盘被读写打满,整个系统卡到鼠标都动不了。后来换成独立的固态盘,问题消失。原因是系统盘同时要处理操作系统的大量小文件读写,和权重的大块顺序读抢 IO,互相拖累。这个坑不大,但踩一次能记一辈子。
另外,如果你打算长期跑这套方案,建议给权重盘单独做散热。大块顺序读会让固态盘持续高温,温度上来之后会降速,速度曲线会变得很难看。加个散热片,成本很低,效果立竿见影。