如何调试 vLLM-torch.compile 集成¶
简要说明
- 使用 tlparse 获取 torch.compile 日志。请在提交错误报告或寻求支持时附上这些日志。
- vLLM-torch.compile 集成由多个部分组成。vLLM 提供了关闭每个部分的标志:
| 在线标志 | 离线标志 | 结果 |
|---|---|---|
| --enforce-eager | enforce_eager=True | 关闭 torch.compile 和 CUDAGraphs |
| -cc.mode=0 | mode=CompilationMode.NONE | 仅关闭 torch.compile |
| -cc.cudagraph_mode=NONE | compilation_config=CompilationConfig(cudagraph_mode=CUDAGraphMode.NONE) | 仅关闭 CUDAGraphs |
| -cc.backend=eager | compilation_config=CompilationConfig(backend='eager') | 关闭 TorchInductor |
vLLM-torch.compile 概述¶
为了提高性能,vLLM 利用 torch.compile 和 CUDAGraphs 进行加速。torch.compile 为 PyTorch 代码生成优化内核,而 CUDAGraphs 则消除了开销。最显著的一点是,vLLM-compile 并不是简单的 torch.compile,它是使用 PyTorch 内部编译 API 构建的自定义编译器。
- 对于给定的模型,我们通过 TorchDynamo 执行全图捕获,该捕获对批处理大小(token 数量)是动态的。
- 随后,vLLM 可选择性地拆分和/或专门化该图,并使用 TorchInductor 将每个图编译为已编译的制品(artifact)。此步骤可能使用 vLLM 自定义的 Inductor 传递来进一步优化图。
- 已编译的制品会被保存到 vLLM 的编译缓存中,以便将来加载。
- vLLM 应用 CUDAGraphs 以减少 CPU 开销。
以上四个步骤中的任何一步都可能出错。当出现问题时,请尝试隔离出故障的子系统——这将允许您在保持可靠性目标的同时,关闭尽可能少的部分,从而最大限度地减少对性能的影响。同时,这也将有助于我们在您提交错误报告时进行排查。
有关设计的更多详细信息,请参阅以下资源:
使用 tlparse¶
使用 tlparse 查看 torch.compile 日志。这些日志显示了编译过程的所有阶段,包括 torch.compile 生成的融合内核(fused kernels)。
安装 tlparse
要启用 torch.compile 日志,可以设置环境变量 TORCH_TRACE=<dir>。在跟踪期间,该目录下会为每个 rank 创建一个文件,每个文件都包含编译期间的制品。如果可以,建议在提交错误报告时附上这些日志文件——它们非常有帮助。
用法(离线推理)
用法(服务)
给定其中一个日志文件,tlparse 命令会输出一些 HTML 文件(例如放入 ./tl_out/index.html)。打开它即可查看日志,效果类似于以下内容。
关闭 vLLM-torch.compile 集成¶
传入 --enforce-eager 以关闭 vLLM-torch.compile 集成,并完全以 eager 模式运行。这包括关闭 CUDAGraphs。
若仅需关闭 torch.compile,请在编译配置中传入 mode = NONE。(-cc 是 --compilation_config 的缩写)
# Offline
from vllm.config.compilation import CompilationConfig, CompilationMode
LLM(model, compilation_config=CompilationConfig(mode=CompilationMode.NONE))
若仅需关闭 CUDAGraphs,请传入 cudagraph_mode = NONE。
# Offline
from vllm.config.compilation import CompilationConfig, CUDAGraphMode
LLM(model, compilation_config=CompilationConfig(cudagraph_mode=CUDAGraphMode.NONE))
调试 TorchDynamo¶
vLLM 要求模型代码能够通过 TorchDynamo(torch.compile 的前端)捕获为全图。TorchDynamo 并不支持所有的 Python 特性。如果无法支持某个特性(有时称为图中断,即 graph break),它会报错(在 fullgraph 模式下)。
如果您遇到图中断,请在 pytorch/pytorch 中提交议题,以便 PyTorch 开发人员可以优先处理。然后,请尽最大努力重写代码以避免图中断。有关详细信息,请参阅此 Dynamo 指南。
调试动态形状全图捕获¶
vLLM 要求模型的向前传播能够捕获为对批处理大小(即 token 数量)动态的全图。默认情况下,它会将此图编译为一个制品,并将其用于所有批处理大小。
如果您的代码无法通过动态形状捕获,您可能会看到静默的不正确结果、明显的错误或 CUDA 非法内存访问。例如,以下代码无法捕获为单个图:
这个问题很容易诊断。使用 tlparse 并点击 compilation_metrics:它会告诉您关于批处理大小的符号约束。如果存在任何限制批处理大小的约束,那么就说明有问题。
要避免这种情况,请采取以下任一方式:
- 避免根据 token 数量进行分支判断
- 将分支逻辑包装在自定义算子中。TorchDynamo 不会追踪进入自定义算子。
调试约束冲突与动态形状守卫问题¶
动态形状守卫(guards)是 Dynamo 守卫的一个特定类别。它们是 torch.compile 附加到动态维度(如 seq_len)上的约束,以确保已编译的制品保持有效。这些守卫通常出现在框架代码、自定义传递或用户代码根据动态形状值进行分支时。
示例
这会创建一个守卫 x > 10 或 x <= 10,具体取决于所追踪的路径。
vLLM 的假设:vLLM 假设 torch.compile 添加的所有守卫都是可以安全删除的,且不会将编译后的图限制为特定的输入形状。当此假设被违背时,会导致用户需要调试的问题。表明此假设被违背的副作用包括运行时错误或 ConstraintViolationErrors。
如果动态形状被限制为单个值,则会抛出 ConstraintViolationErrors。如果您遇到约束冲突错误或怀疑动态形状守卫被错误添加,可以使用更严格的动态形状模式来帮助调试:
# Online - using unbacked mode
vllm serve meta-llama/Llama-3.2-1B -cc.dynamic_shapes_config.type=unbacked
# Online - using backed_size_oblivious mode
vllm serve meta-llama/Llama-3.2-1B -cc.dynamic_shapes_config.type=backed_size_oblivious
# Offline - using unbacked mode
from vllm.config.compilation import CompilationConfig, DynamicShapesConfig, DynamicShapesType
LLM(model, compilation_config=CompilationConfig(
dynamic_shapes_config=DynamicShapesConfig(type=DynamicShapesType.UNBACKED)
))
# Offline - using backed_size_oblivious mode
from vllm.config.compilation import CompilationConfig, DynamicShapesConfig, DynamicShapesType
LLM(model, compilation_config=CompilationConfig(
dynamic_shapes_config=DynamicShapesConfig(type=DynamicShapesType.BACKED_SIZE_OBLIVIOUS)
))
这些模式更严格,减少或消除了对动态形状守卫的需求,从而有助于隔离问题。
unbacked:使用不支持守卫的 unbacked symints,更容易识别守卫被错误添加的位置。backed_size_oblivious:使用一种对守卫更加严格的模式。
有关动态形状模式的更多详细信息,请参阅 动态形状与 vLLM 守卫删除。
打印守卫¶
要查看编译期间添加的所有守卫,可以使用 TORCH_LOGS=+dynamic。
在日志中查找 [guard added] 以了解守卫被添加的位置。这可以帮助您识别哪些操作导致了守卫被错误地添加。
调试 TorchInductor¶
TorchInductor 获取捕获的图并将其编译为可能调用一个或多个 Triton 内核的 Python 代码。在极少数(但令人遗憾的)情况下,它可能会产生不正确的 Triton 内核。这可能表现为静默的不正确结果、CUDA 非法内存访问或明显的错误。
Inductor 运行时断言¶
默认情况下(在 torch < 2.12 时),vLLM 会禁用 Inductor 的运行时断言(assert_size_stride, assert_alignment),以避免在大型模型上每次前向传播约 2ms 的开销。设置 VLLM_LOGGING_LEVEL=DEBUG 会自动重新启用它们,以便调试会话获得完整的形状/步长(shape/stride)验证。
您也可以通过 --compilation-config 显式覆盖它们。
vllm serve <model> -cc.inductor_compile_config='{"size_asserts": true, "alignment_asserts": true, "scalar_asserts": true}'
在 torch >= 2.12 版本中,PyTorch 使用了高效的“仅断言一次”策略,这些标志不再被 vLLM 抑制。
要调试是否是 TorchInductor 导致的问题,可以通过在编译配置中传入 backend='eager' 来禁用它。
如果是 Inductor 的问题,请向 PyTorch 提交错误报告。如果您喜欢探索,可以调试 Inductor 输出代码中的 Triton 内核(可以通过 tlparse 定位这些代码)。
您也可以使用 TORCH_LOGS=output_code <command> 来打印 Inductor 输出代码。
可编辑的 TorchInductor 代码¶
通过设置 VLLM_COMPILE_CACHE_SAVE_FORMAT=unpacked 或传入 -cc.compile_cache_save_format=unpacked,可以编辑运行的 TorchInductor 代码。默认值为 binary,这意味着它不可编辑。
这是一个非常有用的技巧:您可以在输出代码中设置断点(例如 torch.distributed.breakpoint())和添加打印语句。
调试 vLLM-compile 缓存¶
vLLM 为 torch.compile 制品构建了自己的缓存。其思想是制品可以编译一次,然后在后续重用。这是在 torch.compile 的编译器缓存 之上的一个层级。
虽然 torch.compile 的编译器缓存非常稳定,但 vLLM 的编译器缓存有时并不准确。您可以通过设置 VLLM_DISABLE_COMPILE_CACHE=1 来禁用它。
您也可以手动删除此缓存。
- 使用
rm -rf ~/.cache/vllm删除 vLLM 的编译缓存(请查看日志确认位置是否更改)。 - 使用
rm -rf /tmp/torchinductor_$(whoami)删除 torch.compile 的内置缓存。
vLLM 的缓存是缓存键到编译制品的映射。vLLM 通过组合多个因素(例如配置标志和模型名称)来计算缓存键。如果 vLLM 的编译缓存出错,通常意味着缺少了某个因素。请参阅此示例,了解 vLLM 如何计算缓存键的一部分。
vLLM 的编译缓存要求被编译的代码最终是可序列化的。如果不是,则会在保存时报错。通常解决方法是:
- 重写不可序列化的部分(这可能比较困难,因为目前很难判断哪些是可序列化的,哪些不是)。
- 提交错误报告。
- 通过设置
VLLM_DISABLE_COMPILE_CACHE=1来忽略错误(注意,这会使服务器冷启动变慢)。
调试 CUDAGraphs¶
CUDAGraphs 是一项功能,允许:
- 将启动一个或多个 CUDA 内核的可调用对象捕获到 CUDAGraph 中。
- 重放 CUDAGraph。
被捕获的 CUDAGraph 包含了捕获过程中使用的所有内存。重放 CUDAGraph 时,会读写完全相同的内存区域。
这导致了一些限制:
- 为了在新数据上使用 CUDAGraphs,您需要将数据复制到 CUDAGraph 正在读取的缓冲区中。
- CUDAGraphs 仅捕获 CUDA 内核,不捕获 CPU 上执行的工作。
vLLM 使用原始 CUDAGraphs API,如果使用不当,是不安全的。
若仅需关闭 CUDAGraphs,请传入 cudagraph_mode = NONE。


