vllm 的 cpu offload 参数卸载逻辑代码简析
Moreality's Blog最近在看 vllm 的代码,cpu offloading 这部分它的实现还是比较简单的,这里简单记录一下。
由于大模型的参数量实在很大,所以如果想在单机上运行一般都需要跑量化蒸馏后的模型,但是有时又不想牺牲模型质量,于是CPU/SSD 卸载成为一种折衷方案,通过增加推理时间来降低内存需求。
vllm 也实现了一个简单的 cpu offload 的机制,可以通过 --cpu-offload-gb 启用。
这里发现他的实现还是比较简单的,主要就是通过这个 PR ,添加了一个 func 叫 maybe_offload_to_gpu:
这个函数只有一个地方调用了:
cpu offload 的整个流程可以概括为:
- 将传入的
cpu_offload_gb读取为_CPU_OFFLOAD_MAX_BYTES。 - 然后在构建 Module 时对每个 Layer 里面的参数从前往后依次塞到 CPU 的 PIN_MEMORY 上,并累加参数大小 (
_CPU_OFFLOAD_BYTES += p.data.numel() * p.data.element_size(), 即参数数量 x 字节大小) 到_CPU_OFFLOAD_BYTES,直到超过用户配置的可 offload 大小。 - 替换对应 Module 的
forward函数,新的 forward 函数和原来的区别就是:在每次 forward 的时候,将 CPU 上的参数复制到 GPU 上,计算完后再释放
一些可能的理解难点
这里的 forward 替换设计挺有意思:
- (
original_forward = module.forward):保存模块原始的前向传播函数到变量original_forward,为后续恢复原始实现做准备。 - (在
forward函数内的module.forward = original_forward):在自定义的forward函数内部首行,当函数被调用时,立即将模块的前向传播方法恢复为原始方法。这一步确保functional_call能够使用原始的前向传播逻辑,避免递归调用。 - (在
forward函数内末尾的module.forward = forward):前向传播完成后,再次将模块的前向传播方法设置回自定义的forward函数,确保下次调用时仍能触发这个包含参数加载逻辑的自定义函数。 - (函数末尾的
module.forward = forward):为了确保每次都能 hook 到 cpu -> gpu 这部分的逻辑 (参考原 PR 的解释:https://github.com/vllm-project/vllm/pull/6496/files#r1682069375)
这里的核心是使用 functional_call 配合临时的 device_state,实现参数从 CPU 到 GPU 的动态加载。初看可能会疑惑为什么没有将参数从 GPU 释放回 CPU 的逻辑,其实这是因为 device_state 是个局部变量,函数执行完毕后其引用的 GPU 张量会自动被 Python 的垃圾回收机制释放。而原始参数依然保存在 CPU 内存中,从而实现了内存优化的目的。
Generated by RSStT. The copyright belongs to the original author.