四年前我第一次听说“把几十B参数的大模型塞进4GB显存”这个说法时,第一反应是“这怕不是标题党”。直到后来自己动手调 airllm,用一张老旧的入门级显卡跑通了 70B 级别的开源模型,才明白这件事在工程上是成立的。airllm 的核心思路不是把模型“压缩”得更小,而是让 GPU 不再长期保存整份模型。它依赖 CPU 内存和磁盘换空间,用逐层搬运的方式完成推理,把“显存不够”变成了“速度慢一点”的问题。
这篇文章不打算做成项目文档翻译,我会把自己在实际测试里修正过的理解、跑通过的最小代码、性能瓶颈分析和踩过的坑都写出来。如果你手头只有 6GB、8GB 甚至 4GB 显存的卡,又不想把数据交给云端 API,这篇文章应该能帮你少走很多弯路。
1. 先想清楚:为什么一张平民显卡跑不动大模型
1.1 大模型的体积都藏在哪
很多人以为大模型“大”主要体现在模型文件大小上,其实推理过程中还要额外占三块地方:权重、KV Cache、激活值。
权重是基底体积。一个 70B 模型,用 16 位半精度存储,每个参数要 2 字节,仅模型权重就是 140GB 左右。如果用 8 位量化存储,大约 70GB;用 4 位量化,大约 35GB。这是很多人对“量化”抱有执念的原因,因为权重体积是推理开销里最直观、最可预测的部分。
KV Cache 则随着 token 数量增长。Transformer 生成每个 token 时,都需要把历史 token 的 Key 和 Value 缓存下来。假设你设置了 2048 的上下文窗口,模型有 80 层、隐藏维度 8192,那么每个序列实际占用的 KV Cache 可能轻松超过 10GB。显存小的情况下,KV Cache 往往比权重更先逼疯你。
激活值则是前向计算过程中每一层产生的中间张量。虽然单层激活值不算大,但层数一多、batch 一大,也会变成一个不可忽略的变量。显存容量的算法本质就是:峰值并发时,上面三者同时在 GPU 上占用的字节数,不能超过显卡可用显存。
1.2 显存不够时的常规解法
普通用户遇到显存不足,通常会走四条路。第一条是升级显卡,但对大多数消费级用户来说成本太高;第二条是把模型换小,比如从 70B 换成 7B,但能力和覆盖度也一起缩水;第三条是量化,把 FP16 权重压成 INT4,体积能降到原来的八分之一,但 35GB 依然超过主流 8GB 显卡的容量;第四条是接云端 API,速度和显存问题都解决了,可数据出本地这件事对很多场景是硬伤。
我并不是说这些方案不好。它们各自有明确的适用场景。真正的问题是:当你手里只有一个特定的大模型,又必须本地推理时,以上方案都会在本机和目标模型之间制造新的缺口。
1.3 airllm 的切入点:不做魔改,只做搬运
airllm 走了另一条路:让 GPU 只保留当前正在计算的那一两层,其余所有层都放在 CPU 内存或磁盘上。模型结构完全不改,权重也不重新训练,只是把“计算过程”和“存储位置”解耦。
用生活里的场景类比:办公室里工位有限,不可能让所有人同时坐下。airllm 的做法不是把每个人都压缩成“半个员工”,而是让员工排队,一个人站起来离开工位,下一个人才能坐下干活。只要任务可以被拆成串行步骤,那么物理座位数只要够一个人周转就行。Transformer 的前向传播天然就是这样一种串行结构,每一层必须等上一层算完才能开始,所以逐层卸载在架构上是自洽的。
2. 核心原理:层卸载是怎么把 70B 塞进 4GB 显存的
2.1 从“整层驻留”到“单层租住”
传统推理框架的做法是,加载模型时把全部层放进显存,然后反复使用。显存里同时存着 layer0 到 layer N。airllm 相反,它遵循“用完即走”原则:计算第 k 层时,只把第 k 层权重从 CPU 内存拷贝到 GPU,算完立刻释放,再把第 k+1 层搬进来。
这个设计能成立,依赖一个容易被忽略的事实:Transformer 的层间依赖是严格串行的,计算第 5 层时,根本不需要第 6 层到第 N 层的任何权重。所以 GPU 上真正同时存在的,只有“当前层”和“下一层待预取的层”。显存占用从“全模型大小”变成“单层权重 + 当前激活 + 必要缓存”,哪怕只有 4GB 显存也可以容纳 70B 模型中某一层的计算需求。
2.2 自回归生成中的数据流动
大模型推理可以分成 prefill 和 decode 两个阶段。prefill 处理用户输入的提示词,一次性计算整段文本;decode 逐 token 生成,每生成一个 token 都会重新做一次完整前向传播。
在 airllm 这种方案里,整个过程大致是:模型权重分散存放在 CPU 内存中,GPU 显存里只常驻少量临时缓冲区。运行时把 layer0 的权重从内存搬到显存,计算完成后输出结果直接作为 layer1 的输入;随后释放 layer0 权重,再把 layer1 权重搬进显存,依此类推。每个 token 都会重复整条搬运链。
还要留意 KV Cache 的存取。生成新 token 时,每一层都需要更新 KV Cache。如果缓存保存在 CPU 内存中,那么更新时同样要经历 CPU 和 GPU 之间的拷贝。好在 KV Cache 通常比权重小一个量级,带宽瓶颈主要还在权重搬运上。
2.3 为什么精度几乎无损
很多人以为“显存不够还能跑”一定用了近似计算。airllm 不是这样。GPU 拿到的是原始 FP16 或 INT4 权重,完成的也是完整矩阵乘法,并没有对计算过程做近似。精度损失来源只是你所选权重格式本身的量化误差,跟你把模型直接加载到大显存里跑是一样的。
换句话来说,airllm 不会“降低模型智商”。它只是把整个推理过程的时间拉长,用更长的等待换取同样的输出质量。它的取舍不是“精度换显存”,而是“时间换显存”。这一点让它的定位变得非常清晰。
2.4 一个大胆的预估:速度上限
你可以提前估算一下这种方案的天花板。假设一个 70B 模型使用 INT4 量化,总权重约 35GB。显卡和 CPU 内存之间的实际有效拷贝带宽,按 PCIe 3.0 x16 算大概 12GB/s,按 PCIe 4.0 x16 算大概 20GB/s。那就意味着,每个 token 的前向传播光搬运全量权重就需要 1.7 到 3 秒。计算和搬运如果无法完全重叠,最终延迟只会更高。
这不是说 airllm 慢得不能用,而是说你要对它的性能预期有准确认知。如果你需要每秒几十 token 的实时对话体验,这种方案确实不合适;如果你只是需要一个后台任务慢慢跑,输出一段 300 token 的摘要,等几分钟完全可以接受。
3. 从零跑通 airllm:环境、代码与验证
3.1 硬件底线:显存可以小,内存和通道不能省
我测试时用的显卡显存只有 4GB,但机器上的 CPU 内存有 96GB,磁盘也是 NVMe 接口。最终体验告诉我,airllm 对显存要求很低,对另外三项硬件要求很高:
- CPU 内存:必须能装下整个模型权重外加 KV Cache。70B INT4 模型建议至少 48GB 空闲内存,否则把磁盘当成 swap 用会让速度再降一个等级。
- 磁盘:模型原始文件、量化权重、可能的临时卸载文件都放在 NVMe SSD 上。机械硬盘做卸载区基本不可用,随机读太慢。
- CPU 与 GPU 之间的通道:PCIe 3.0 x16 是底线,PCIe 4.0 更好。带宽直接决定每个 token 的搬运下限。
这里最适合的硬件画像其实是“显卡差但整机内存大”的机器。如果你手里的机器内存只有 16GB,那么跑 70B 级别模型会很吃力,可能只能转向 7B 或 13B 级别。
3.2 安装与模型准备
软件层面,airllm 依赖 Python 环境和常见深度学习运行时。建议新建独立虚拟环境,避免和已有项目冲突。安装包本身很简单:
pip install airllm模型权重需要提前准备好。你可以从开源社区下载对应架构的权重目录,目录里通常会包含一个 config 文件和一个或多个权重分片。airllm 支持直接读取常见权重目录格式,关键是目录路径要放对,不要只给到模型文件那一层。
如果你手里的是原始 FP16 权重,想进一步缩小搬运体积,可以准备量化版本。airllm 方案里量化权重收益非常大,因为重量越小,每次搬运所需时间越短,速度几乎线性提升。但要注意,量化权重也有不同格式,优先选择与运行时兼容性较好的 4-bit 量化格式。
3.3 最小生成代码
初始化并生成文本的调用思路很直接。下面的代码是我测试时的写法,不同版本 API 可能有差异,但结构大致如此:
from airllm import AirLLM model = AirLLM.from_pretrained( "模型权重目录", device_map="auto", offload_path="./offload_weights" ) response = model.generate( "用中文写一段关于周末露营的注意事项:", max_new_tokens=128, do_sample=True, temperature=0.8 ) print(response)代码逻辑就是用from_pretrained加载权重目录,指定offload_path作为额外卸载空间。generate的用法和主流文本生成接口保持一致。第一次运行会有一个初始化过程,大概率需要等待较长时间,因为需要把模型分片信息读入内存并建立索引,这并不代表每次推理都那么慢。
3.4 判断它到底有没有在干活
跑起来后,可以用系统监控工具观察资源占用。你会看到显卡显存占用始终保持在一个很低的数值,比如 2GB、3GB 左右,而 CPU 内存占用接近模型体积。GPU 利用率会呈现“脉冲式”波动:权重搬上来时利用率拉满,搬走时跌到零。
这是正常现象。不要看到 GPU 利用率忽高忽低就以为死机了。airllm 本来就是间歇性使用 GPU,计算密集度不像常驻模型推理那样稳定。判断是否卡死,最直接的方法是看终端是否仍在持续输出 token,而不是看显存曲线是否平滑。
4. 实测经验:速度分布、调参方向与量化效果
4.1 一个 token 的时间都去哪了
为了让速度优化更有方向性,我把一次完整生成拆成了三个阶段:首次初始化、prefill 阶段、decode 阶段。首次初始化主要是加载权重索引和元信息,可能在几十秒到几分钟。prefill 阶段需要处理输入提示词,计算量不算大但也要逐层搬运。decode 阶段是最耗时的,每生成一个 token 都要做一遍完整前向传播,包括全部层权重的换入换出。
实测下来,decode 阶段单 token 延迟可以稳定落在几十秒到几秒的量级。具体数字取决于权重格式、PCIe 带宽和模型层数。如果 CPU 内存频繁触发 swap,延迟还会继续恶化。所以你要么耐心等待,要么从减少搬运量入手。
4.2 让速度提升一个数量级的五个参数
我测试中真正有效果的优化手段,按收益排序如下:
- 权重格式从 FP16 换成 INT4:搬运体积直接降到原来的四分之一,单 token 延迟大幅缩短。这是最值得做的一步。
- 限制
max_new_tokens:它不直接改变单 token 速度,但可以避免无意义的长时间生成。输出 128 token 和输出 512 token 的时间差距就是 4 倍。 - 控制上下文长度:KV Cache 过大会让内存占用暴涨,也会增加传输压力。没有长上下文需求时,不需要设置很大。
- 开启流式输出:让客户端逐 token 拿到结果,主观体验会比干等好很多,虽然总耗时没变。
- 关闭重复采样或调低 batch:在低显存场景里,batch 大于 1 并不会带来同步收益,因为多序列同时推理会产生更多激活和 KV Cache。
4.3 量化、预取与带宽上限的关系
我看过一些评测文章,把量化效果说得神乎其神。实际上量化只是在帮助你突破带宽瓶颈。下面这张表能直观看出变化:
| 权重格式 | 70B 模型体积 | 在 PCIe 4.0 下的理论搬运耗时 |
|---|---|---|
| FP16 | 约 140GB | 7 秒/次 |
| INT8 | 约 70GB | 3.5 秒/次 |
| INT4 | 约 35GB | 1.75 秒/次 |
这里的耗时是指“每个 token 完整搬运一次所有层权重”的下限,还没算计算时间和调度损耗。这也是为什么连接口带宽都成了关键因素。很多支持预取机制的版本,会在计算当前层的同时预取下一层,让传输和计算重叠一部分。否则单 token 延迟还会更接近表中的理论值。
5. 我踩过的五个坑与对应解法
5.1 内存先爆而不是显存先爆
第一次跑的时候,我下意识一直盯着显存占用,结果发现真正告警的是 CPU 内存。airllm 一旦把所有权重加载到内存,内存占用会接近模型体积,KV Cache 还在持续增长。如果机器内存共享给其他应用,很容易在生成到一半时触发系统 OOM。
解决方法是给推理任务预留足够内存,关掉不必要的浏览器标签和后台进程。如果内存实在不够,可以尝试把卸载路径指向 NVMe SSD,让部分分片不常驻内存,但这会进一步降低速度。内存容量是 airllm 场景里最容易翻车的变量。
5.2 长上下文让速度雪崩
我刚开始图省事,把上下文窗口设得很大,想着“反正显存放不下就放内存”。结果发现,随着对话历史变长,每生成一个 token 都要重新搬运和更新大量 KV Cache,速度衰减非常明显。这其实包含两层问题:一层是 KV Cache 体积随序列长度线性增长,另一层是注意力计算本身随序列长度二次增长。
解决思路很简单:对话场景里尽量裁剪历史,摘要之后再重新作为输入。不要无脑拉长上下文,airllm 的设计初衷是“内存换显存”,上下文过长会把这个优势吃掉。
5.3 并发推理是假象
我还试过同时开两个线程生成文本,结果不是并行加速,而是互相拖慢。原因很好理解:两个推理任务轮流抢占 GPU,每次切换都要把对方的权重换出、把自己的权重换入,等于额外引入大量搬运开销。
如果你确实需要处理多个离线文本,正确的做法是串行完成任务,或者把它们合并进同一个输入序列的 batch 里,但 batch 受限于可用显存。低显存硬件上,永远不要期待多进程并发能带来线性加速。
5.4 权重分片和格式不匹配
模型的权重文件常常是多个分片。airllm 读取分片时,如果路径指向不完整,或者分片顺序信息丢失,会出现加载失败或生成结果混乱。还有一次我混合使用了不同精度的权重文件,配置信息和实际权重对不上,导致莫名其妙的数值异常。
解决方式是把权重目录整理干净,只保留对应模型的config和分片文件。优先选择单一格式的量化权重,不要自己手动混拼。如果你是从社区下载的模型,确保下载完整,校验文件大小与源一致。
5.5 瞬时显存 spike 导致的 OOM
airllm 虽然平均显存占用低,但某些阶段会因为预取和注意力计算出现瞬时尖峰。比如 prefill 阶段处理长输入时,激活值会突然增高,让 4GB 卡瞬间溢出。
遇到这种情况,我一般的做法是降低输入长度、减小 batch、关闭过大的预取额度。如果实在压不住,也可以换一张显存稍微大一点的卡来做 prefill,再把 KV Cache 交给另一台机器继续 decode。对于个人玩家来说,更简单的办法是调整输入提示词,减少一次性计算的 token 数量。
6. 到底什么场景适合 airllm,什么场景该放弃
6.1 适合的场景
airllm 最舒服的使用场景是离线批量推理。比如给一批文档生成摘要、对日志做初步分类、批量生成候选文案。这些任务本身就不要求毫秒级响应,单条数据等几分钟完全可接受。
它也很适合强调数据隐私的本地化场景。模型权重放在本机,输入数据不出内网,比调用云端 API 更可控。对于课程设计、个人 Demo、技术验证这类“只要跑通就行”的目标,airllm 能帮你在大显存硬件之外快速验证模型能力。
6.2 不适合的场景
任何需要高并发、低延迟的在线服务都不适合。聊天机器人、客服系统、实时翻译这类场景,用户等 3 秒才出第一个字就会流失。即使你把量化做到极致,PCIe 带宽决定的上限就摆在那里。
如果你没有足够大的 CPU 内存,也不适合。整个模型放进内存都困难的时候,“内存换显存”就变成了“磁盘换显存”,性能会断崖式下降。同样的预算,你可能更应该换一张大显存显卡或者直接租赁云端资源。
6.3 我的个人判断与下一步做法
我自己使用的硬件确实不算好,但 airllm 让我完成了预算内不可能完成的大模型本地推理实验。我最大的体会是:这类方案的价值不在“替代数据中心”,而在“降低尝试门槛”。它把大模型推理拆成了存储层级问题,而不是单纯比拼显存容量。
下一步我会继续从两个方向优化:一个是进一步压权重格式,把单 token 搬运体积降到极限;另一个是把流水线预取调得更激进,让权重搬运和 GPU 计算尽可能重叠。如果你也打算跑同一个方向,建议先拿一个较小的模型验证链路,再用目标模型慢慢压测。