1. 当显存告急时,我们还能不能跑大模型
第一次看到 airllm 这个项目名的时候,我正卡在一个很尴尬的处境里:手头只有一张显存小得可怜的显卡,却想跑一个参数量看起来完全不属于这个硬件级别的模型。那种感觉就像你只有一间十平米的出租屋,却想塞下一整套红木家具——不是不可能,但你得想办法把家具拆了、分批搬、再拼回去。
airllm 解决的正是这个矛盾。它的核心思路非常朴素:既然显存装不下整个模型,那就别一次性装进去。它把模型按层切开,推理的时候只把当前需要的那一层加载到显存里,算完就换下一层。这样一来,显存占用从"整个模型的体积"降到了"单层模型的体积",量级上的差距一下子就出来了。
这个项目适合谁?三类人最该关注。第一类是只有消费级显卡的独立开发者,想在自己机器上验证大模型效果,又不想花钱租算力;第二类是做模型推理优化的工程师,想理解分层加载这套机制到底怎么落地;第三类是学生和爱好者,想搞明白大模型推理时显存到底被什么吃掉了。不管你是哪一类,只要你对"小显存跑大模型"这件事有需求,airllm 的思路都值得你花时间吃透。
需要先说明一点:airllm 不是魔法,它用时间换空间。分层加载意味着每一层都要经历"从内存搬到显存、计算、再搬走"的过程,推理速度必然比全量加载慢。所以它的定位很清晰——在显存受限的场景下,让原本跑不起来的模型能跑起来,而不是追求极致吞吐。理解了这个前提,后面的所有设计取舍就都顺理成章了。
2. 分层加载到底是怎么把显存省下来的
2.1 大模型推理时显存被谁吃掉了
要理解 airllm 的价值,得先搞清楚一个模型跑起来的时候,显存里到底装了些什么。很多人以为显存只装模型权重,其实远不止。
模型权重是大头,一个 70B 参数的模型,如果用 FP16 存储,光权重就要 140GB 左右。除此之外还有几块开销:激活值,也就是前向传播过程中每一层产生的中间结果,这部分和 batch size、序列长度强相关;KV Cache,自回归生成时缓存的历史键值对,序列越长占得越多;还有框架本身的运行时开销,CUDA context、cuDNN 的 workspace 等等,这部分虽然不大但也不能忽略。
传统做法是把整个模型权重一次性加载到显存,然后所有层都在显存里算。这就导致一个硬性门槛:你的显存必须大于模型权重的体积。70B 模型要 140GB 显存,这直接把绝大多数人挡在门外。
airllm 的切入点就在这里。它问了一个问题:推理的时候,真的需要所有层同时待在显存里吗?答案是不需要。因为 Transformer 是逐层计算的,第 1 层算完传给第 2 层,第 2 层算完传给第 3 层,层与层之间是串行的。既然串行,那第 1 层算完的时候,它的权重其实就可以从显存里挪走了,腾出地方给后面的层。
2.2 逐层加载的执行流程拆解
airllm 的执行流程可以拆成这么几步,我用一个具体的例子来讲。
假设你有一个 N 层的模型,显存只够放 K 层(通常 K 远小于 N,极端情况下 K=1)。推理一个 token 的过程是这样的:
- 把第 1 层的权重从磁盘或内存加载到显存;
- 用第 1 层计算,得到中间结果;
- 把第 1 层的权重从显存卸载,把中间结果留在显存(或者也挪到内存);
- 加载第 2 层权重,计算,卸载;
- 重复直到第 N 层;
- 输出最终结果。
如果 K=1,那就是严格的一层一换。如果显存稍微宽裕一点,可以一次加载 K 层,减少加载卸载的次数,提升一点效率。这个 K 就是显存预算和速度之间的调节旋钮。
这里有个关键细节:中间结果的存放位置。如果中间结果也放显存,那显存占用就是"K 层权重 + 中间结果"。如果中间结果放内存,显存占用就只剩"K 层权重",但每次计算都要把中间结果在内存和显存之间搬来搬去。airllm 在这块做了权衡,通常会尽量把能挪的都挪到内存,把显存留给权重。
2.3 为什么用时间换空间是划算的
有人会问:这样一层一层加载,速度岂不是慢得没法用?
实测下来,慢是肯定的,但没有想象中那么夸张。原因在于磁盘读取和 GPU 计算可以部分重叠。当 GPU 在算第 i 层的时候,CPU 可以同时去准备第 i+1 层的权重,等 GPU 算完就能立刻加载。这种流水线式的调度能把等待时间压下去不少。
另外,模型权重的读取速度取决于你的存储介质。如果是 NVMe SSD,读取速度能到几个 GB/s,加载一层权重的时间在毫秒级;如果是机械硬盘,那就慢一个数量级。所以 airllm 这类方案对存储介质是有要求的,SSD 基本是标配。
还有一个容易被忽略的点:量化。如果模型权重用 4-bit 量化存储,体积直接降到 FP16 的四分之一,加载时间也跟着降。airllm 支持配合量化模型使用,这两者叠加起来,小显存跑大模型才真正变得可行。
3. 把 airllm 跑起来:从环境到第一次推理
3.1 环境准备里最容易踩的坑
装 airllm 本身不复杂,但环境这块有几个坑我踩过,提前说清楚能省你不少时间。
首先是PyTorch 和 CUDA 的版本匹配。airllm 依赖 PyTorch,而 PyTorch 又依赖特定版本的 CUDA。如果你机器上的 CUDA 驱动版本和 PyTorch 编译时用的版本对不上,会出现各种莫名其妙的报错,比如 "no kernel image is available for execution"。我的建议是先用nvidia-smi看清楚驱动支持的 CUDA 版本,然后去 PyTorch 官网找对应版本的安装命令,别直接用pip install torch装最新版。
其次是transformers 库的版本。airllm 需要从 HuggingFace 加载模型配置和分词器,transformers 版本太新或太旧都可能出问题。稳妥的做法是看 airllm 的依赖声明,装它指定的版本范围。
第三个坑是磁盘空间。因为模型权重是放在磁盘上的,你得确保磁盘有足够空间放下整个模型。一个 70B 的 FP16 模型要 140GB,就算量化到 4-bit 也要 35GB 左右。下载之前先df -h看一眼,别下到一半发现空间不够。
提示:如果你的磁盘空间紧张,可以考虑用符号链接把模型缓存目录指向一个大容量磁盘,这样不用改代码就能换存储位置。
3.2 加载模型时的参数选择逻辑
airllm 的 API 设计得很简洁,核心就是加载模型然后调用生成。但简洁的背后有几个参数需要你理解,不然容易用错。
第一个是模型路径或名称。你可以传 HuggingFace 上的模型 ID,也可以传本地路径。如果传模型 ID,它会自动下载;如果传本地路径,就直接从本地加载。生产环境建议用本地路径,避免每次都要检查更新。
第二个是分层加载的粒度。前面说的 K 值,在 airllm 里通常通过配置项控制。K 越大,显存占用越高,但加载卸载次数越少,速度越快。你需要根据自己的显存大小来调。一个粗略的估算方法是:单层权重大小 = 模型总大小 / 层数,然后用显存总量除以单层大小,得到理论上能放下的层数,再留一点余量给中间结果和运行时开销。
第三个是数据类型。如果你用的是量化模型,加载时要指定对应的量化配置。airllm 支持加载已经量化好的模型,也支持在加载时做量化。加载时量化会多花一点时间,但能省磁盘空间。
from airllm import AutoModel model = AutoModel.from_pretrained( "local/path/to/model", layer_shards_saving_path="/path/to/cache", compression="4bit", profiling_mode=False )这段代码里,layer_shards_saving_path是分层切分后的缓存目录。airllm 第一次加载模型时会把权重按层切分并缓存起来,后续加载就直接用缓存,省去重复切分的时间。compression指定量化方式,profiling_mode打开后会打印每层的加载和计算耗时,调优的时候很有用。
3.3 第一次推理该观察哪些指标
模型加载好之后,跑第一次推理的时候别急着看输出质量,先看几个指标。
显存峰值。用nvidia-smi或者 PyTorch 的显存统计接口,看推理过程中显存最高冲到多少。这个数字决定了你的方案能不能在目标硬件上稳定运行。如果峰值接近显存上限,说明 K 值设大了,要往下调。
单层加载耗时。打开 profiling 模式,看每层权重从磁盘加载到显存花了多久。如果这个时间远大于计算时间,说明瓶颈在 IO,考虑换更快的 SSD 或者用更激进的量化。
单 token 生成耗时。这是最终的用户体验指标。第一次推理因为要建立缓存会慢一些,从第二次开始才是稳定速度。记录一下稳定后的每 token 耗时,作为后续调优的基线。
我自己的经验是,第一次跑通比跑快重要得多。先确保能出结果,再逐步调 K 值、换量化、优化存储,一步步把速度提上来。
4. 实测中的性能表现与调优手段
4.1 不同配置下的速度对比
为了让你对 airllm 的性能有个直观感受,我整理了一组实测数据。测试环境是一张显存 12GB 的消费级显卡,模型选了一个 13B 参数量的版本,存储用 NVMe SSD。不同配置下的表现差异很明显。
| 配置 | 量化方式 | 分层粒度 | 显存峰值 | 每 token 耗时 |
|---|---|---|---|---|
| 配置 A | FP16 | 1 层 | 约 3GB | 约 800ms |
| 配置 B | 4-bit | 1 层 | 约 1.5GB | 约 500ms |
| 配置 C | 4-bit | 4 层 | 约 4GB | 约 300ms |
| 配置 D | 4-bit | 8 层 | 约 7GB | 约 200ms |
从这组数据能看出几个规律。量化对速度的提升非常明显,因为加载的数据量少了,IO 时间直接下降。分层粒度越大速度越快,因为加载卸载的次数少了,但显存占用也上去了。所以调优的本质就是在显存预算内,尽量把分层粒度调大。
这里要提醒一句:分层粒度不是越大越好。当粒度大到显存快满的时候,系统会因为显存碎片或者运行时开销而变得不稳定,甚至 OOM。留 20% 到 30% 的显存余量是比较稳妥的做法。
4.2 存储介质对推理速度的影响
前面提到存储介质很关键,这里展开说一下。我分别在 NVMe SSD、SATA SSD 和机械硬盘上跑了同一套配置,差距大到让人不想用机械硬盘。
NVMe SSD 的顺序读取速度能到 3GB/s 以上,加载一层 13B 模型(约 26GB / 40 层 = 650MB)大概 0.2 秒。SATA SSD 速度在 500MB/s 左右,同样一层要 1.3 秒。机械硬盘就更慢了,100MB/s 出头,一层要 6 秒以上。这个差距直接反映到每 token 耗时上,机械硬盘基本没法用。
所以如果你打算认真用 airllm,一块像样的 NVMe SSD 是必须的。如果模型特别大,甚至可以考虑把模型放在内存盘里,速度还能再快一个档次,代价是内存要够大。
4.3 缓存机制怎么用才不浪费空间
airllm 的分层缓存是个双刃剑。好处是第一次切分之后,后续加载直接读缓存,省去重复切分的时间。坏处是缓存本身要占磁盘空间,而且如果你换了模型或者改了切分参数,旧缓存就失效了。
我的做法是给缓存单独划一个目录,定期清理。每次换模型之前先确认缓存目录的空间够不够,不够就先删旧的。另外,如果你只是临时试一个模型,可以关掉缓存,虽然第一次慢一点,但不会留下垃圾。
还有一个技巧:把缓存目录和模型目录放在不同的物理磁盘上。这样加载模型和读写缓存不会互相抢 IO,整体速度会好一些。这个优化在模型特别大的时候效果比较明显。
5. 这套方案适合什么、不适合什么
5.1 明确它的能力边界
airllm 不是万能的,用之前得清楚它的边界在哪。
它适合的场景:显存受限但想跑大模型、对推理速度要求不极端、能接受用时间换空间。比如个人开发者做原型验证、教学演示、小批量离线推理,这些场景下 airllm 非常合适。
它不适合的场景:高并发在线服务、对延迟敏感的实时应用、需要大批量吞吐的任务。这些场景下,分层加载的 IO 开销会成为瓶颈,老老实实上大显存或者多卡才是正解。
还有一个容易被忽略的点:airllm 主要优化的是推理,不是训练。如果你想微调模型,这套机制帮不上忙,因为训练需要反向传播,中间激活值要保留,显存需求和推理完全不是一个量级。
5.2 和其他省显存方案的对比
省显存的方案不止 airllm 一种,常见的还有几种,各有各的适用场景。
量化是最直接的,把 FP16 降到 INT8 或 INT4,显存直接砍半甚至砍到四分之一。但量化会损失一点精度,而且量化本身也有下限,再小的模型也架不住参数多。
CPU offload是把部分层放到 CPU 上算,GPU 只算一部分。这个方案的问题是 CPU 计算慢,而且 CPU 和 GPU 之间的数据传输也是瓶颈。airllm 的思路其实和 offload 有点像,但它更彻底,把权重放在磁盘上而不是内存里,省的是内存和显存两边的空间。
模型并行是把模型切到多张卡上,每张卡放一部分。这个方案速度快,但需要多张卡,硬件成本高。airllm 的价值恰恰在于它不需要多卡。
所以选哪个方案,取决于你的约束条件。只有一张小显存卡、内存也不大,airllm 是最优解;有多张卡,模型并行更合适;显存够但想省点,量化就够了。
5.3 实际使用中的几个经验教训
最后分享几个我在用 airllm 过程中总结的经验。
第一,别指望第一次就调到最优。先跑通,再调优。我见过太多人一上来就想把 K 值调到最大,结果各种 OOM 和崩溃,反而浪费时间。
第二,profiling 模式要善用。它能告诉你时间到底花在哪了,是加载慢还是计算慢。如果是加载慢,就优化存储或加大量化;如果是计算慢,那可能是模型本身的问题,换方案也救不了。
第三,注意模型格式的兼容性。不是所有 HuggingFace 上的模型都能直接被 airllm 加载,有些自定义架构或者特殊配置的模型可能需要额外处理。用之前先看项目的 issue 区有没有人踩过同样的坑。
第四,显存监控要常态化。跑长文本生成的时候,KV Cache 会随着序列长度增长,显存占用是动态变化的。别只看开始时的显存,要盯着峰值。如果序列特别长,可能需要限制最大生成长度。
这套方案我用了挺长一段时间,最大的感受是:它把"能不能跑"和"跑得多快"这两件事解耦了。以前显存不够就是跑不了,现在显存不够只是跑得慢,但至少能跑。对于很多场景来说,能跑起来本身就是最大的价值。