正在获取所在地区…
正在读取天气
音乐
暂未播放
FlexGen 完全拆解:用线性规划编排 GPU、CPU 与 SSD 上的大模型推理
问题:显存放不下时,为什么“能跑”仍不等于“跑得动”
FlexGen 由 Stanford、UC Berkeley、ETH Zurich 等机构的研究者在 2023 年发表于 ICML。它研究的不是“如何让显存足够时的在线聊天再快一点”,而是一个更受约束的问题:当模型连 CPU 内存都未必放得下、只能借助 SSD 时,怎样用一张 16 GB 的普通 GPU 完成高吞吐生成式推理。
这一定义里有两个容易被忽略的限定。
第一,FlexGen 面向的是吞吐优先、延迟不敏感的离线任务,例如批量评测、文档信息抽取和数据清洗。单个请求等几分钟甚至更久并不理想,但只要整批数据完成得更快,就仍有价值。它不适合要求秒级首 token 的交互式服务。第二,“把张量移到 CPU 或 SSD”只解决容量问题,并不自动解决性能问题。若每生成一个 token 都把数百 GB 权重重新读一遍,系统虽然不会 OOM,却会被 PCIe 或 SSD 带宽彻底锁死。
可以用最简单的下界看清这个矛盾。设一层需要从慢速存储读入的权重为 W 字节,链路有效带宽为 β 字节每秒,一次只服务 B 个序列,那么忽略计算后,每个序列分摊的权重读取时间至少是
tweight per sequence≥BβW.B 越大,同一份权重被更多序列复用,单位序列承担的 I/O 越少。但批量一大,激活与 KV Cache 又会迅速吃掉内存。FlexGen 真正解决的是这个闭环:在 GPU、CPU、SSD 的容量和带宽约束下,联合选择批量大小、执行顺序、张量放置位置和计算位置,而不是孤立地做一次 offload。

图 1 来自 FlexGen 论文。三类张量进入统一调度器后,可以分别放在 GPU 显存、CPU DRAM 或硬盘;KV Cache 还可以先压缩再存储。图很简洁,但它表达了论文最重要的系统观:权重、激活和缓存的生命周期不同,不能再沿用“只卸载权重”的单一策略。
论文以 OPT-175B 为例说明容量压力。FP16 权重约占 325 GB,而 T4 只有 16 GB 显存。更反直觉的是,在批量 b=512、提示长度 s=512、生成长度 n=32 时,KV Cache 峰值约为 1.2 TB,达到权重体积的 3.8 倍。大批量一方面摊薄权重 I/O,另一方面却把缓存推成新的容量瓶颈。因此,批量并不是越大越好,它必须与放置策略一起求解。
FlexGen 给出的答案可以压缩成四点:
- 用之字形块调度让已加载的一层权重连续服务多个 GPU 小批次;
- 分别决定权重、激活和 KV Cache 在三级存储中的比例;
- 把 I/O 与 GPU 计算重叠,并在特定情况下把注意力计算交给 CPU;
- 用代价模型和线性规划寻找高吞吐策略,再用 4-bit 压缩进一步减少 I/O。
这些设计的共同目标不是减少算术量,而是提高每次慢速数据搬运所支撑的有效计算量。
从 Transformer 推理的数据流重新定义卸载
理解调度前,先把一次生成拆成张量生命周期。设批量大小为 b,提示长度为 s,生成 token 数为 n,Transformer 层数为 l,隐藏维度为 h1,MLP 中间维度为 h2。对第 i 层,输入为 Xi,注意力投影权重为 WQi,WKi,WVi,WOi。
在 Prefill 阶段,一次处理全部提示 token:
Ki=XiWKi,Vi=XiWVi,Qi=XiWQi.随后计算注意力与 MLP。这里 Ki,Vi 不会随这一层计算结束而释放,因为 Decode 的每一步都要读取历史键和值。到生成第 t 个 token 时,新隐藏状态记作 Xti,缓存会执行追加:
K1:s+ti=Concat(K1:s+t−1i,XtiWKi),V1:s+ti=Concat(V1:s+t−1i,XtiWVi).当前 query 只有一个位置,却要与长为 s+t 的全部历史 Key 做内积。这造成三类数据截然不同的复用方式。
| 张量 | 大致生命周期 | 主要复用方向 | 卸载难点 |
|---|---|---|---|
| 权重 | 整个任务始终存在 | 同一层、不同序列与 token | 体积固定且很大,反复读取最昂贵 |
| 激活 | 相邻层之间 | 只被下一层消费 | 生命周期短,但大 Prefill 激活仍可观 |
| KV Cache | 从 Prefill 持续到序列结束 | 同一序列后续所有 Decode 步 | 随批量和上下文长度线性增长 |
按论文采用的 FP16 口径,忽略较小的嵌入层,权重字节数近似为
MW=l(8h12+4h1h2).其中 8h12 来自注意力中的四个 h1×h1 矩阵,每个 FP16 元素占 2 字节;4h1h2 来自 MLP 的两个矩阵。KV Cache 峰值近似为
MKV=4blh1(s+n).系数 4 等于 Key 与 Value 两份张量乘以每个 FP16 元素 2 字节。这个式子揭示了系统设计的矛盾:增大 b 能提高权重复用,却按相同比例增大 MKV;延长上下文也不会增加权重,却会持续放大缓存。

图 2 同样来自论文。横向依次展开生成 token,纵向是近似无限的数据集,每个彩色方块代表某个 GPU batch 在某一层上的计算,相同颜色表示共享同一层权重。图中一条合法执行路径必须满足四个约束:左侧依赖先完成;权重、激活和缓存与计算必须位于同一设备;激活保留到下一层消费,KV Cache 保留到序列生成结束;任一时刻的驻留张量不得超过设备容量。
于是,卸载可形式化为受容量约束的计算图遍历:选择以什么顺序访问方块,同时决定访问每个方块前后要搬哪些张量。目标函数是计算时间与数据移动时间共同构成的总执行时间。
FlexGen 的核心:计算图分块与张量生命周期
最直觉的执行顺序是逐行调度:取一个 batch,完成第一个 token 的所有层,再完成下一个 token,直到该 batch 结束,然后换下一批。这种顺序的优点是单批完成得快,序列结束后也能立即释放 KV Cache;它天然偏向低延迟。
问题在于相邻方块通常属于不同层。假设模型权重放在 CPU 或 SSD,每跨过一层就要读入下一层权重;生成下一 token 时,又从第一层重新开始。若生成 n 个 token,整套权重至少被轮转读取 n 次,而且一次加载只服务当前 GPU batch。对 OPT-175B 这类模型,链路输送的字节数比 GPU 能执行的 FLOP 更早成为瓶颈。
另一个极端是逐列调度:加载第 j 层权重后,让数据集中所有序列都通过这一层。它能最大化权重复用,但真实数据集可以无限大,产生的中间激活和 KV Cache 无处存放,系统很快耗尽 CPU 内存与 SSD 空间。
FlexGen 在两个极端之间取一个有限矩形块。一个块包含若干 GPU batch;系统加载某层权重,让块内所有小批次依次完成该层,再转到下一层。抵达 token 边界时,遍历方向反转,形成之字形路径。反转不是数学必需,而是一种减少控制复杂度、便于连续访问相邻边界数据的工程安排。

图 3 来自论文。上半部分的逐行调度沿每一行横向走完;下半部分的红色箭头在有限高度的块内纵向扫过,使同色方块共享一次权重驻留。这里需要区分两个批量概念:
- GPU batch size(gbs)是单次矩阵运算真正送入 GPU 的序列数,决定单个 Kernel 的形状和显存临时空间;
- block size(bls)是一次权重驻留期间累计处理的序列数,等于 gbs 与块内 GPU batch 数的乘积,也称有效批量。
例如论文在 OPT-175B 的无压缩策略中使用 gbs=32、块内 8 个 GPU batch,因此 bls=256。GPU 并不需要同时容纳 256 条序列的全部工作集,却能让一次权重加载为 256 条序列服务。它把“计算批量”和“权重复用批量”解耦了。
块也不能无限加高。生成过程中的 KV Cache 长期存活,块内序列越多,缓存占用越大。用 w 表示一层权重大小,只考虑 CPU 交换时,论文给出的峰值近似为
Mpeak=w+2h1bls+4h1blsl(s+n).三项分别对应当前层权重、活动激活和整个块累积的 KV Cache。若可用 CPU 内存为 MC,则块大小受到
bls≤2h1+4h1l(s+n)MC−w约束。这解释了为什么 FlexGen 还必须卸载缓存、压缩缓存,并搜索张量放置比例:块调度只是创造了权重复用机会,容量规划才决定这个机会能放大到多少。
论文附录还构造了理论上 I/O 最优的对角块调度,但其状态管理与实现更复杂。最终系统采用之字形块调度,并证明其 I/O 复杂度不超过最优解的 2 倍。这个保证不是说运行时间最多慢 2 倍,因为实际还有计算、并发和碎片等因素;它只限定了理想化模型中的数据移动复杂度。
用线性规划搜索卸载策略
有了块调度,仍然存在一个很大的策略空间。FlexGen 用 11 个变量描述一项策略:bls、gbs,以及三类张量在三个设备上的放置比例。
p=(bls,gbs,wg,wc,wd,hg,hc,hd,cg,cc,cd).下标 g,c,d 分别代表 GPU、CPU 和 disk;w 表示权重,h 表示隐藏状态或激活,c 表示 KV Cache。每类张量的比例必须守恒:
wg+wc+wd=1,hg+hc+hd=1,cg+cc+cd=1.权重采用层粒度分区,激活和 KV Cache 采用张量粒度分区。若权重也切到元素粒度,放置更灵活,却会引入大量小传输、元数据与拼接开销;若激活和缓存只按整层放置,又很难精确填满设备空余容量。这是一个典型的“控制开销与搜索自由度”折中。
从硬件画像得到每层时间
FlexGen 先在目标机器上测量 PCIe、SSD 读写带宽,以及 GPU GEMM、GPU BMM 和 CPU 计算吞吐。对一个块的一层 Prefill,系统把时间分成五条可能并行的路径:
Tpre=max(tc→gp,tg→cp,td→cp,tc→dp,tcompp).Decode 的平均每层时间同理:
Tgen=max(tc→gg,tg→cg,td→cg,tc→dg,tcompg).上标 p 与 g 分别表示 Prefill 和 generation。这里取最大值而不是求和,是因为模型假设各方向 I/O 与计算能够理想重叠;最慢的流水阶段决定稳态时间。整个块经过 l 层并生成 n 个 token,总时间估计为
T=lTpre+l(n−1)Tgen.为什么是 n−1?论文把产生第一个输出 token 的提示处理计入 Prefill,剩余输出才走逐 token Decode。不同系统对 Prefill/Decode 边界的计时定义可能不同,复现实验时必须统一这一口径。
以 Decode 阶段从 SSD 到 CPU 的读取为例,时间近似为
td→cg=βd→c1[wd(8h12+4h1h2)+4cdbls(s+2n)h1+2hdblsh1].βd→c 是 SSD 到 CPU 的有效带宽。方括号三项分别是该层权重、平均长度下的 KV Cache 和激活字节数。s+n/2 用生成期间的平均缓存长度近似从 s 增长到 s+n 的过程。这个公式清楚展示了不同决策的代价:降低 wd 能少读权重,降低 cd 能少读随批量和长度增长的缓存,降低 hd 能少搬短生命周期激活。
为什么固定批量后能变成线性规划
FlexGen 并未把所有变量一起交给一个通用非线性求解器。它先枚举少量 (bls,gbs) 组合;固定二者后,传输字节数、计算拆分和设备峰值内存都能写成放置比例的线性函数。max 也可通过辅助变量线性化:令 z 不小于五条路径的每一条,再最小化 z。
优化目标是最小化每条序列的时间,也就是最大化吞吐:
pmins.t.blsTMgpeak(p)≤Cg,Mcpeak(p)≤Cc,Mdpeak(p)≤Cd,wg+wc+wd=1,hg+hc+hd=1,cg+cc+cd=1.Cg,Cc,Cd 是三级存储容量。注意,优化器最小化的是 T/bls,并非单个请求的 T。某个策略让整块延迟变长,却把块大小扩大得更多,吞吐仍可能上升。这正是 FlexGen 与在线服务调度目标最本质的区别。
线性模型也有明确误差来源。真实 PCIe 和 SSD 带宽随请求大小变化;页锁定内存、CUDA 工作区、文件映射和碎片会挤占理论容量;多条 DMA 通路也未必能完全并行。论文用分段拟合与正则项改善硬件画像,求解后仍可能因 OOM 手工微调。更重要的是,归档的官方仓库说明自动策略优化器并未发布,命令行中的放置比例通常要参考基准策略后人工试验。因此,“论文有 LP”不等于开源代码会自动为任意机器找到最优参数。
三条关键数据通路如何重叠
块调度把一次权重加载摊给多个小批次,但若所有动作仍串行执行,SSD、PCIe、CPU 和 GPU 会轮流空闲。FlexGen 用多个 CPU 线程和 CUDA stream 维护并发流水。对当前层 j、当前小批次 k,稳态迭代可以抽象为:
1并行发起:2 预取下一层 j+1 的权重3 写回上一批 k-1 的激活与 KV Cache4 读入下一批 k+1 的激活与 KV Cache5 在当前层 j 上计算当前批 k6等待本轮依赖完成,然后推进流水这些动作访问不同方向或设备资源,所以有重叠机会:磁盘把下一层权重读入 CPU,PCIe 把当前需要的数据传入 GPU,GPU 同时计算已经就绪的 batch,另一路再把已消费的激活写回。稳态吞吐由最慢阶段决定;但流水的启动和排空仍有气泡,小块或短生成时尤其明显。
为什么有时应该在 CPU 上算注意力
通常 GPU 比 CPU 快得多,把计算移到 CPU 看似反常。关键在于 Decode 注意力常常受数据移动而非 FLOP 限制。若 KV Cache 已放在 CPU,把它搬到 GPU 只为做一次单 query 注意力,需要传输的缓存量近似为
MKV,move=4bsh1,而把当前隐藏状态送到 CPU 只需近似
Mact,move=4bh1.两者相差约 s 倍。对于 s=512 的长提示,避免搬运整段 KV Cache 可能比使用更快的 GPU 算术单元重要。因此,当某部分缓存驻留在 CPU 时,FlexGen 允许 CPU 直接计算相应注意力,再把小得多的结果送回 GPU。
这不是“CPU 注意力总比 GPU 好”。它只在缓存本来就在 CPU、PCIe 搬运成为瓶颈、CPU 还有可用算力时成立。若 KV Cache 已驻留 GPU,或现代 GPU 端注意力 Kernel 能直接利用高带宽显存,委托 CPU 反而会增加同步和低效计算。启用 4-bit 压缩后,论文还关闭了 CPU delegation,因为 CPU 上的量化与反量化开销会抵消省下的传输。
SSD 不是更慢的显存
论文测试机器的 SSD 读带宽约 2 GB/s、写带宽约 1 GB/s。它与 GPU HBM 相差数个数量级,因此 SSD 的正确角色是“容量最后防线”,而不是频繁随机访问的缓存。FlexGen 为磁盘张量创建文件并映射到虚拟内存,依靠大块顺序访问与预取获得有效带宽。
若 325 GB 权重每个 Decode 步都完整从 2 GB/s SSD 读取,仅理想顺序读就需要约 162.5 秒;任何 Kernel 优化都无法补回这个数量级。块调度的意义正在于让这 162.5 秒对应数百条序列,而不是一条序列。4-bit 压缩若还能让权重完全进入 208 GB CPU 内存,就会直接消灭最慢的 SSD 权重通路,收益远大于“字节数减少四倍”这一表面描述。
权重压缩与 KV Cache 压缩
FlexGen 的量化目标与常见低比特 GEMM 不同。GPTQ、AWQ 或专用 W4A16 Kernel 关注的是用低比特权重直接加速矩阵乘;FlexGen 首先关注存储容量和 I/O 字节数,计算前会把数据还原为 FP16。关于直接低比特计算的路线,可参考 Marlin 的 W4A16 Kernel 拆解;两者优化的瓶颈并不相同。
它采用细粒度、分组非对称量化。对一组连续的 g 个值,先取组内最小值 xmin 与最大值 xmax,再映射到 b 位无符号整数:
q(x)=round(xmax−xminx−xmin(2b−1)).反量化为
x=xmin+2b−1q(x)(xmax−xmin).这里 xmin 相当于零点对应的偏移,(xmax−xmin)/(2b−1) 是缩放因子。分组越小,每组动态范围越贴近局部分布,量化误差通常越低;代价是每组都要保存尺度与最小值,元数据比例和量化 Kernel 开销更高。
论文对权重与 KV Cache 都使用 4-bit、组大小 64,不需要重新训练或校准。权重沿输出通道维分组,KV Cache 沿隐藏维分组。这一选择兼顾局部数值范围与运行时连续访问。量化后的张量在 CPU 或磁盘中存储,使用前反量化成 FP16,因此其主要收益是:
- 权重更可能从 SSD 层提升到 CPU 层,跨越一次数量级更大的带宽差;
- 相同 CPU/SSD 容量能容纳更大的有效批量;
- KV Cache 的传输字节减少,使长上下文 Decode 的 I/O 流水更容易被计算覆盖。
论文还实验了 Top-K 稀疏注意力:先根据 Key 计算注意力分数,只加载得分最高的 10% Value。它减少了 Value Cache I/O,但仍需访问 Key 才能选出 Top-K,而且“10%”是固定实验策略,并不保证对其他模型、层和任务都安全。FlexGen 的核心贡献并不依赖这项近似;把它视为系统可插入的一种推理近似更准确。
准确率实验使用 LAMBADA 与 WikiText。OPT-175B 的 LAMBADA 准确率从 FP16 的 0.758 变为 4-bit 的 0.756;WikiText 困惑度从 10.82 变为 10.94。加入 10% Value 稀疏后,这两项仍为 0.756 与 10.94。论文也报告 3-bit 无法维持准确率。这里的结论应限定为论文所测 OPT 模型和任务,不能外推为所有模型都能无校准地使用相同 4-bit KV 格式。后来 KIVI 等工作进一步研究了 Key 与 Value 的分布差异,可参考 KIVI 的非对称 2-bit KV Cache 量化。
从算法到工程实现
论文中的比例变量落到开源实现后,是一个明确的 Policy 对象。命令行参数 --percent 接收六个数字,依次表示权重在 GPU、CPU 的百分比,KV Cache 在 GPU、CPU 的百分比,以及激活在 GPU、CPU 的百分比;每一类剩余的比例自动归入磁盘。例如:
1python3 -m flexllmgen.flex_opt \2 --model facebook/opt-30b \3 --percent 20 80 0 100 0 100 \4 --gpu-batch-size 48 \5 --num-gpu-batches 3这个策略表示:20% 权重放 GPU、80% 放 CPU;KV Cache 全放 CPU;激活也全放 CPU;有效批量是 48×3=144。这里只用于解释参数结构,并非对任意 30B 模型与机器都适用的通用配置。模型结构、上下文长度、CPU 内存、PCIe 代际和实际可用带宽改变后,安全比例也会改变。
设备抽象与异步复制
运行时把 GPU、CPU 和磁盘包装成统一设备,并让张量知道自己的 home device。磁盘张量不是某种 CUDA 可直接计算的对象,而是由 NumPy open_memmap 创建的文件映射;真正计算前仍要经过 SSD 到 CPU,再经过 CPU 到 GPU。分层抽象的价值是把张量放置与算子逻辑分开,代价则是必须显式管理异步复制、缓冲区所有权与生命周期。
源码为权重加载、缓存加载和缓存写回分别创建 CUDA stream。理想流水中,当前 stream 执行层计算时,其他 stream 可以预取下一层或搬运相邻 batch 的状态。但正确性依赖事件同步:下一层不能在权重复制完成前读取;一个缓存缓冲区也不能在前一计算仍使用它时被覆盖。FlexGen 的 block 循环实质上既是调度顺序,也是环形缓冲区的安全协议。
CPU 侧还要处理两种内存。普通 pageable 内存容量压力较低,但 CUDA 传输通常需要额外 staging;pinned memory 能提高异步 PCIe 复制效率,却不能被操作系统换出,而且锁页本身带来显著额外占用。官方说明在 OOM 时可关闭权重 pinning,能减少约 20% 或更多 CPU 内存,但速度会下降。这说明“CPU 内存还有 10 GB 空余”不代表策略一定可运行,页锁定副本和框架工作区也必须计入余量。
一个层级流水的伪代码
下面的伪代码省略了事件与边界条件,但能展示控制结构:
1for token_step in generation:2 for layer in transformer_layers:3 for micro_batch in block:4 async_load(weight[layer + 1])5 async_store(activation[layer, micro_batch - 1])6 async_store(kv[layer, micro_batch - 1])7 async_load(activation[layer, micro_batch + 1])8 async_load(kv[layer, micro_batch + 1])9 compute(layer, micro_batch)10 synchronize_required_events()外层 token 循环决定自回归依赖,中层 layer 循环保持 Transformer 拓扑正确,内层 micro-batch 循环提供权重复用与流水并发。把 micro-batch 循环放到最内层很关键:同一层权重在它期间保持热驻留。逐请求卸载通常把 layer 或 token 作为最内层,因而失去这次复用。
多 GPU 为什么可能出现超线性 Decode 扩展
FlexGen 选择流水并行,把 l 层平均切给 m 个 GPU,每个 stage 只负责约 l/m 层,再在 stage 间传激活。相比张量并行频繁进行 collective,流水并行通信粒度较粗,更符合吞吐优先目标。
论文报告四张分布式 T4 上,OPT-175B Decode 吞吐从单卡 0.83 token/s 提升到 3.86 token/s,略高于线性四倍。原因并不是 GPU 凭空多出算力,而是每个节点分到的权重和缓存压力降低后,策略可能从 SSD 卸载跨越到 CPU 卸载,或使用更大的 batch。资源翻倍触发了离散的存储层级变化,所以性能曲线不是线性的。不过包含 Prefill 的总生成吞吐只从 0.69 提升到 2.33 token/s,因为短输出下 Prefill 流水气泡占比仍高。
实验结果该怎样解读
论文主实验在 Google Cloud 的单张 NVIDIA T4 16 GB、208 GB CPU DRAM 和 1.5 TB NVMe SSD 上完成,SSD 读写带宽约为 2 GB/s 与 1 GB/s。模型为 OPT-6.7B、30B 和 175B;合成输入统一 padding 到 512 或 1024 token,每条提示生成 32 token。指标定义为
generation throughput=tprefill+tdecodebn.这个指标把整个 batch 的 Prefill 与 Decode 都计入分母,不能与只统计 Decode token/s、只报单请求速度或排除排队时间的结果直接比较。
最大吞吐不是单请求速度
输入长度 512 时,论文报告如下结果:
| 系统 | OPT-6.7B | OPT-30B | OPT-175B |
|---|---|---|---|
| Hugging Face Accelerate | 25.12 | 0.62 | 0.01 |
| DeepSpeed ZeRO-Inference | 9.28 | 0.60 | 0.01 |
| Petals | 8.25 | 2.84 | 0.08 |
| FlexGen | 25.26 | 7.32 | 0.69 |
| FlexGen,4-bit 压缩 | 29.12 | 8.70 | 1.12 |
单位均为生成 token/s。OPT-175B 的 0.69 token/s 来自 32×8=256 的有效批量,基线最大批量只有 1 或 2;压缩策略用有效批量 144 达到 1.12 token/s,并因压缩后权重可以驻留 CPU 而绕开 SSD。这里批量 144 反而小于无压缩的 256,却更快,说明决定性能的不是批量单变量,而是是否跨过了存储层级边界。
论文在吞吐基准中使用 dummy weights,只在准确率实验中使用真实权重。这不会改变同尺寸矩阵运算和数据移动的字节数,但意味着吞吐表并未同时验证模型输出正确性;正确解读是“系统执行路径的性能测量”加上独立的“量化准确率测量”,而不是一项端到端业务质量基准。

图 4 来自论文。横轴是完成整个块的总延迟,纵轴是生成吞吐,两者都采用对数刻度。橙色 FlexGen 与蓝色压缩版通过增加有效批量向右上移动:单块更慢,但单位时间完成的 token 更多。绿色 DeepSpeed 与红色 Accelerate 很快受 OOM 限制,无法继续通过扩大批量换吞吐。蓝色虚线并非在所有延迟点都胜过橙线;压缩有反量化开销,只有当减少 I/O 或跨越容量边界的收益更大时才占优。
消融实验揭示了真正瓶颈
在提示长度 512 的 OPT-175B 上,完整无压缩系统为 0.69 token/s。取消策略搜索后降到 0.27;取消 I/O 重叠后为 0.59;取消 CPU 计算后为 0.62;完全禁用磁盘则 OOM;把 DeepSpeed 的放置策略放进 FlexGen 运行时只有 0.01。由此可见,最大的增益不是某一个 CUDA Kernel,而是让有效批量、缓存放置和权重复用形成一致策略。
OPT-30B 的情况不同:完整系统为 7.32 token/s,取消 CPU 计算后降到 4.03,影响比 OPT-175B 更显著。原因是 30B 策略把权重放在 GPU 与 CPU、缓存放 CPU,CPU 就地注意力能省下大量 PCIe 传输;175B 还受到磁盘权重通路支配,优化其中一条次要路径的相对收益更小。消融结果不能脱离具体策略和瓶颈层级来比较。
实验数字为何不能直接套到今天的模型
T4、OPT 和 2023 年软件栈提供了清晰的受限资源案例,却与现代部署有多处差异:
- GQA 或 MQA 会显著减少 KV Cache 头数,改变 MKV 与权重 I/O 的平衡;
- MoE 的总权重很大,但每 token 只激活部分专家,卸载策略还要考虑路由与专家热度;
- PCIe 5.0、CXL、更快 NVMe 与更大主存会移动各层级的临界点;
- FlashAttention、分页 KV 与融合低比特 Kernel 会改变 GPU 计算时间和临时内存;
- 真实请求长度不齐,padding、提前结束和到达时间波动都会破坏静态大块的整齐流水。
因此,FlexGen 最耐久的价值是调度方法与代价建模思路,而不是表中某个固定 token/s。
与其他推理内存技术的边界
FlexGen 常与 PagedAttention、统一内存或权重量化混在一起讨论,但它们解决的不是同一层问题。
| 技术 | 主要问题 | 核心动作 | 典型目标 |
|---|---|---|---|
| FlexGen | 总工作集超过 GPU,甚至超过 CPU | 跨 GPU/CPU/SSD 放置并重排计算 | 离线大批量吞吐 |
| PagedAttention | GPU 内 KV Cache 的碎片与动态增长 | 用逻辑块表映射物理 KV block | 在线动态批处理 |
| vAttention | 分页寻址给 Kernel 带来侵入性 | 预留连续虚拟地址,按需映射物理页 | 保留连续 Kernel 接口 |
| 权重量化 Kernel | 权重带宽与 Tensor Core 利用 | 低比特存储并在计算路径解码 | GPU 内直接加速 GEMM |
| P/D 分离 | Prefill 与 Decode 资源特征冲突 | 将两阶段放到不同实例并传 KV | 集群级 SLO 与 goodput |
PagedAttention 假设 KV Cache 主要仍由 GPU 管理,重点消除预留和碎片浪费;FlexGen 则允许缓存整体落到 CPU 或 SSD,重点减少跨层级数据移动。两者可以组合:上层策略决定哪些缓存页在哪种设备,下层分页器管理某一设备内部的物理分配,但组合后代价模型必须纳入页粒度传输和命中率。
vAttention 用 CUDA 虚拟内存把逻辑连续与物理按需分配结合,目标仍是让高性能连续注意力 Kernel 易于使用。它不解决权重比整机 DRAM 还大的情形,也不会自动安排 SSD 预取。
与 DistServe 的 Prefill/Decode 分离 相比,FlexGen 在单机或少量 GPU 内把慢速存储带宽摊给离线大批量;DistServe 在集群中隔离两阶段干扰,优化满足延迟 SLO 的 goodput。前者愿意牺牲单请求延迟,后者恰恰需要控制 TTFT 与 TPOT。
它也不同于操作系统透明统一内存。透明迁移按缺页发生,系统很难保证大模型层级访问与预取顺序,错误的逐页抖动会毁掉带宽。FlexGen 知道下一层、下一批和下一 token 的依赖,能显式发起大块预取并构造稳定流水。这体现了领域专用运行时相对通用虚拟内存的优势。
局限、适用场景与部署判断
FlexGen 最适合同时满足以下条件的任务:模型无法放入 GPU;输入可以积累成大批量;单条结果无需立即返回;CPU DRAM 或本地 NVMe 容量充足;工作负载的长度分布相对可预测。批量评测、离线标注、固定语料抽取和夜间数据处理都符合这一画像。
若是低并发聊天、流式 Agent 或严格 TPOT 服务,FlexGen 通常不是合适选择。权重与缓存跨 PCIe/SSD 往返会把单请求延迟拉得很高,之字形块还必须等待足够多输入才能形成有效复用。论文官方仓库也明确说明,小 batch 时它会显著慢于权重完整驻留高性能 GPU 的系统。
部署前可以按以下顺序判断,而不是直接复制论文参数。
- 先算容量账。按真实模型精度计算权重,按层数、KV 头数、head dimension、上下文和并发计算 KV Cache;为 CUDA workspace、页锁定缓冲和碎片预留空间。
- 再量带宽。分别实测 SSD 顺序读写、CPU 内存带宽和双向 PCIe,不能用厂商峰值替代端到端有效值。
- 找主导字节。若权重搬运占主导,增大 block size 或压缩权重;若缓存搬运占主导,减少并发、量化 KV 或让 CPU 就地算注意力;若计算占主导,卸载调度不会制造额外 FLOP。
- 检查延迟预算。通过 T/bls 优化出的吞吐策略可能让单块 T 非常大,应先设定可接受的最大完成时间,再搜索可行 Pareto 点。
- 用真实长度回放。长度不齐会导致 padding 浪费和块内提前结束,静态合成序列的最优策略可能失效。
还要注意四个系统性局限。
其一,论文的线性模型假设较理想的并发。SSD、CPU 内存与 PCIe 往往共享 NUMA、IOMMU 或内存控制器资源,多个方向同时工作时的带宽之和并不独立。其二,磁盘耐久性与热节流未进入目标函数,长时间大流量写回会影响消费级 SSD。其三,公开实现已在 2024 年归档,主要围绕 OPT,不能视为持续适配新架构的生产框架。其四,压缩准确率只覆盖有限模型和数据集,现代长上下文、代码或多模态任务需要重新校验。
FlexGen 仍给出一个很有价值的架构原则:当性能由数据移动主导时,应优化“每搬一个字节完成多少有用 token”,而不是只盯着某个算子的 TFLOPS。大 batch、块调度、张量放置、计算委托和量化都是这个原则在不同层面的实现。
小结
FlexGen 把显存不足问题提升成一个异构内存调度问题。它先把自回归推理展开为 batch、layer 与 token 三维计算图,再用之字形块调度让同一层权重服务多个 GPU 小批次;随后分别规划权重、激活和 KV Cache 在 GPU、CPU、SSD 的位置,并用线性代价模型在容量约束下优化每序列时间。
它最关键的三个洞察是:
- 权重 I/O 必须靠有效批量摊薄,计算 batch 与复用 block 应当解耦;
- 数据在哪里,有时比哪种处理器算得快更重要,CPU 就地注意力就是典型例子;
- 压缩的最大收益可能不是低比特算术,而是让工作集从 SSD 跨越到 CPU 或从 CPU 跨越到 GPU。
FlexGen 并没有把廉价单卡变成低延迟的高端集群。它用更长的单块延迟换取更高的离线吞吐,并清楚标出了这项交换成立的条件。也正因如此,它是理解大模型推理中“计算、容量、带宽、批量”四者如何耦合的一篇经典系统工作。
参考资料
- FlexGen: High-Throughput Generative Inference of Large Language Models with a Single GPU(ICML 2023)
- FlexGen arXiv 论文与 HTML 全文
- FlexLLMGen 官方开源仓库
- DeepSpeed Inference: Enabling Efficient Inference of Transformer Models at Unprecedented Scale
- OPT: Open Pre-trained Transformer Language Models
- Petals: Collaborative Inference and Fine-tuning of Large Models
- Hugging Face Accelerate:Big Model Inference 官方文档
- KIVI: A Tuning-Free Asymmetric 2bit Quantization for KV Cache
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!
评论区
分享你的想法,与大家交流讨论
正在获取所在地区…
正在读取天气
音乐
暂未播放



