|
| 1 | +# CUDA Core Dump:调试内存访问问题的有效工具 |
| 2 | + |
| 3 | +> 英文原稿转载自 [blog.vllm.ai](https://blog.vllm.ai/2025/08/11/cuda-debugging.html) |
| 4 | +
|
| 5 | +你是否曾经在开发 CUDA kernel 时,测试经常遇到非法内存访问(简称 IMA),却不知道该如何调试?在开发 vLLM(一个高性能的 LLM 推理引擎)时,我们一次又一次地体会到了这种痛苦。 |
| 6 | + |
| 7 | +如果你是曾经遇到过此类问题的开发者,那么这篇博客就是为你准备的!我们将揭示一些在 vLLM 中用于调试复杂问题(如 IMA)的高级调试技巧。 |
| 8 | + |
| 9 | +例如,这是一个来自 PyTorch 的错误信息: |
| 10 | + |
| 11 | +```text |
| 12 | +RuntimeError: CUDA error: an illegal memory access was encountered |
| 13 | +CUDA kernel errors might be asynchronously reported at some other API call, so the stacktrace below might be incorrect. |
| 14 | +For debugging consider passing CUDA_LAUNCH_BLOCKING=1 |
| 15 | +Compile with `TORCH_USE_CUDA_DSA` to enable device-side assertions. |
| 16 | +``` |
| 17 | + |
| 18 | +棘手之处在于:CUDA kernel 错误可能在其他 API 调用中被异步报告,因此下面的堆栈跟踪可能是错误的。根据我们的经验,这类异常的 Python 堆栈跟踪基本 **总是错误的,几乎没什么用** 。为了解决这个问题,错误信息建议在运行代码时添加 `CUDA_LAUNCH_BLOCKING=1`。然而,这里仍然有两个问题: |
| 19 | + |
| 20 | +1. 很多人使用 `kernel<<<>>>` 语法启动 CUDA kernel 时,并不会检查 kernel 启动状态,例如[这段代码](https://github.com/pytorch/pytorch/blob/5e320eea665f773b78f6d3bfdbb1898b8e09e051/aten/src/ATen/native/cuda/SortStable.cu#L117)。在这种情况下,即便加了 `CUDA_LAUNCH_BLOCKING=1`,依然无法定位到出错的 kernel。 |
| 21 | +2. 如果非法内存访问发生在 CUDA graph 中的某个 kernel 内,即使加了 `CUDA_LAUNCH_BLOCKING=1`,我们也只能看到在启动 CUDA graph 时出现了问题,依然无法精准定位到出错的具体 kernel。 |
| 22 | + |
| 23 | +要精确定位这类问题,我们需要在非法内存访问发生的那一刻立刻做出响应。当然,这不是用户直接能做到的——它必须由 CUDA 驱动本身提供支持。 |
| 24 | + |
| 25 | +[CUDA core dump 功能](https://docs.nvidia.com/cuda/cuda-gdb/index.html#gpu-core-dump-support)正是为此设计的。它允许 CUDA 驱动在发生非法内存访问时转储 GPU 状态,用户之后可以分析该状态,从而找出是哪个 kernel 引发了问题,以及具体的非法访问是什么。 |
| 26 | + |
| 27 | +## 什么是 Core Dump? |
| 28 | + |
| 29 | +GPU 本质上是一个高度并行的处理器,其中的许多概念在 CPU 上都有对应。 |
| 30 | + |
| 31 | +[Core dump](https://en.wikipedia.org/wiki/Core_dump) 是由 CPU 与操作系统共同提供的功能。当程序在执行过程中崩溃时,操作系统可以记录程序的内存数据、运行状态等信息,以便后续分析与调试。程序崩溃是一个硬件级概念,当 CPU 在执行某条指令时遇到错误,会进入 `trap` 状态,此时操作系统接管程序并执行相应的异常处理过程(默认情况下,这会直接终止程序,但也可以配置生成 core dump 以便分析。例如,通过 `ulimit -c 1` 可启用 core dump 生成,通过 `echo "core.%e.%p" > /proc/sys/kernel/core_pattern` 可指定 core dump 文件路径)。 |
| 32 | + |
| 33 | +类似地,GPU 上的 core dump 功能需要 GPU 硬件与 GPU 驱动的协作。当 GPU 上的某个线程在执行中崩溃时,GPU 硬件需要触发异常并将其传递给 GPU 驱动,由驱动立即处理该异常。然而,据[论坛讨论](https://forums.developer.nvidia.com/t/difference-in-error-handling-between-driver-api-and-runtime-api/336389)所述,GPU 驱动在处理异常时的默认行为是将当前 CUDA 上下文标记为不可用,而不是终止程序。 |
| 34 | + |
| 35 | +## 如何启用 CUDA Core Dump |
| 36 | + |
| 37 | +启用 CUDA core dump 非常简单,只需设置 `CUDA_ENABLE_COREDUMP_ON_EXCEPTION=1` 环境变量即可。不过,为了更顺畅的使用体验,你还应额外设置几个环境变量: |
| 38 | + |
| 39 | +1. 默认情况下,CUDA core dump 会将转储文件保存在当前目录,并且不会输出文件路径。你可以启用 `CUDA_COREDUMP_SHOW_PROGRESS=1` 环境变量来显示 core dump 过程的进度与细节。最重要的是,它会在过程结束后显示 core dump 文件的路径,方便后续调试与分析。 |
| 40 | +2. 许多任务运行在容器内,当任务失败时,容器会被销毁,从而无法保留 core dump 文件。在这种情况下,你可以通过 `CUDA_COREDUMP_FILE` 环境变量指定 core dump 文件的路径模板。例如,可以将其保存到持久化存储目录:`CUDA_COREDUMP_FILE="/persistent_dir/cuda_coredump_%h.%p.%t"`,其中 `%h` 表示主机名,`%p` 表示进程 ID,`%t` 表示转储时间戳。 |
| 41 | +3. 默认情况下,core dump 会保存整个 GPU 上下文。对于像大模型推理这样几乎占满 GPU 显存的程序来说,完整 core dump 会非常庞大(数百 GiB 数据)。你可以通过设置 `CUDA_COREDUMP_GENERATION_FLAGS='skip_nonrelocated_elf_images,skip_global_memory,skip_shared_memory,skip_local_memory'` 跳过保存 GPU 显存、共享内存和本地内存,从而大幅减小 core dump 文件体积。 |
| 42 | + |
| 43 | +文档中还提到,将 `skip_abort` 添加到 `CUDA_COREDUMP_GENERATION_FLAGS` 中,可以在 core dump 完成后防止 CPU 进程中止,这样 CPU 进程可以记录自己的错误堆栈,提供更多调试信息。但实验表明,这个功能存在一个严重[缺陷](https://forums.developer.nvidia.com/t/cuda-core-dump-with-skip-abort-will-ignore-an-illegal-memory-access-error/341802/3),可能导致 GPU 上的非法内存访问错误被忽略。此时,后续代码可能继续运行,但程序的内存数据已经被破坏。对于训练任务来说,这是完全不可接受的;对于推理任务,也是不安全的。因此,这个功能并不可靠,不推荐使用。 |
| 44 | + |
| 45 | +另外,文档称启用 `CUDA_ENABLE_COREDUMP_ON_EXCEPTION=1` 不仅会启用 CUDA core dump,还会默认生成 CPU core dump。但实际上,我们发现 CPU core dump 中几乎没有有用信息,而且难以分析。 |
| 46 | + |
| 47 | +如果你想进行实时调试,也可以启用 `CUDA_DEVICE_WAITS_ON_EXCEPTION=1` 环境变量。它不会使用 core dump,而是在异常发生时立刻暂停 GPU 执行,并挂起等待用户附加调试器(如 cuda-gdb)检查 GPU 状态,此时完整的 GPU 内存仍然保留。不过,这种方式自动化程度较低,需要更多人工干预。 |
| 48 | + |
| 49 | +总结来说,推荐使用以下组合环境变量启用 CUDA core dump: |
| 50 | + |
| 51 | +``` |
| 52 | +CUDA_ENABLE_COREDUMP_ON_EXCEPTION=1 CUDA_COREDUMP_SHOW_PROGRESS=1 CUDA_COREDUMP_GENERATION_FLAGS='skip_nonrelocated_elf_images,skip_global_memory,skip_shared_memory,skip_local_memory' CUDA_COREDUMP_FILE="/persistent_dir/cuda_coredump_%h.%p.%t" |
| 53 | +``` |
| 54 | + |
| 55 | +## 使用 CUDA Core Dump 的示例 |
| 56 | + |
| 57 | +### 调试不当的 Kernel 启动 |
| 58 | + |
| 59 | +```cpp |
| 60 | +// test.cu |
| 61 | +#include <cuda_runtime.h> |
| 62 | +#include <stdio.h> |
| 63 | +#include <stdlib.h> |
| 64 | + |
| 65 | +// CUDA 错误检测宏 |
| 66 | +#define cuda_check(call) do { \ |
| 67 | + cudaError_t err = call; \ |
| 68 | + if (err != cudaSuccess) { \ |
| 69 | + printf("CUDA Error at %s:%d - %s: %s\n", __FILE__, __LINE__, #call, cudaGetErrorString(err)); \ |
| 70 | + exit(EXIT_FAILURE); \ |
| 71 | + } \ |
| 72 | +} while(0) |
| 73 | + |
| 74 | +// 具有非法内存访问的 Kernel — 访问越界 |
| 75 | +__global__ void illegalMemoryAccessKernel(int* data, int size) { |
| 76 | + int idx = blockIdx.x * blockDim.x + threadIdx.x; |
| 77 | + |
| 78 | + // 这会导致非法内存访问 — 访问超分配范围 |
| 79 | + // 我们分配了 'size' 元素,却访问到 size * 2 |
| 80 | + if (idx < size * 2) { // 访问两倍长度区域 |
| 81 | + data[idx - 1000000000] = idx; // 对 idx == 0 时将引发越界访问 |
| 82 | + } |
| 83 | +} |
| 84 | + |
| 85 | +// 正常的 Kernel — 访问安全 |
| 86 | +__global__ void normalKernel(int* data, int size) { |
| 87 | + int idx = blockIdx.x * blockDim.x + threadIdx.x; |
| 88 | + |
| 89 | + if (idx < size) { |
| 90 | + data[idx] = idx; |
| 91 | + } |
| 92 | +} |
| 93 | + |
| 94 | +int main() { |
| 95 | + printf("CUDA Illegal Memory Access Test\n"); |
| 96 | + printf("===============================\n\n"); |
| 97 | + |
| 98 | + int size = 100; |
| 99 | + int* h_data = (int*)malloc(size * sizeof(int)); |
| 100 | + int* d_data; |
| 101 | + |
| 102 | + // 初始化主机内存 |
| 103 | + for (int i = 0; i < size; i++) { |
| 104 | + h_data[i] = 0; |
| 105 | + } |
| 106 | + |
| 107 | + // 分配设备内存 |
| 108 | + cuda_check(cudaMalloc(&d_data, (unsigned long long)(size) * sizeof(int))); |
| 109 | + cuda_check(cudaMemcpy(d_data, h_data, size * sizeof(int), cudaMemcpyHostToDevice)); |
| 110 | + |
| 111 | + // 启动具有非法访问的 kernel |
| 112 | + int blockSize = 256; |
| 113 | + int numBlocks = (size + blockSize - 1) / blockSize; |
| 114 | + |
| 115 | + printf("Launching kernel with out-of-bounds access...\n"); |
| 116 | + illegalMemoryAccessKernel<<<numBlocks, blockSize>>>(d_data, size); |
| 117 | + |
| 118 | + normalKernel<<<numBlocks, blockSize>>>(d_data, size); |
| 119 | + |
| 120 | + cuda_check(cudaMemcpy(h_data, d_data, size * sizeof(int), cudaMemcpyDeviceToHost)); |
| 121 | + for (int i = 0; i < 5; i++) { |
| 122 | + printf("%d ", h_data[i]); |
| 123 | + } |
| 124 | + printf("\n"); |
| 125 | + |
| 126 | + // 同步以捕捉运行时错误 |
| 127 | + cuda_check(cudaDeviceSynchronize()); |
| 128 | + |
| 129 | + printf("Test completed.\n"); |
| 130 | + |
| 131 | + // 清理 |
| 132 | + cuda_check(cudaFree(d_data)); |
| 133 | + free(h_data); |
| 134 | + |
| 135 | + return 0; |
| 136 | +} |
| 137 | +``` |
| 138 | +
|
| 139 | +这段代码顺序启动两个 kernel(`illegalMemoryAccessKernel` 和 `normalKernel`)。正常运行时,你会看到类似这样的错误提示: |
| 140 | +
|
| 141 | +``` |
| 142 | +CUDA Error at test.cu:62 - cudaMemcpy(h_data, d_data, size * sizeof(int), cudaMemcpyDeviceToHost): an illegal memory access was encountered |
| 143 | +``` |
| 144 | +
|
| 145 | +错误只会在 `cudaMemcpy` 返回值中检测到。即使设置了 `CUDA_LAUNCH_BLOCKING=1`,仍然无法识别是哪一个 kernel 出错。 |
| 146 | +
|
| 147 | +通过添加 CUDA core dump 相关环境变量,可以观察到如下信息: |
| 148 | +
|
| 149 | +``` |
| 150 | +[00:40:46.606413] coredump: SM 123/132 has finished state collection |
| 151 | +[00:40:46.606419] coredump: SM 124/132 has finished state collection |
| 152 | +[00:40:46.611453] coredump: Detected an exception of type CUDBG_EXCEPTION_WARP_ILLEGAL_ADDRESS (14) |
| 153 | +[00:40:46.611458] coredump: - Device: 0 |
| 154 | +[00:40:46.611460] coredump: - SM: 124 |
| 155 | +[00:40:46.611462] coredump: - Warp: exception was detected after the warp has exited |
| 156 | +[00:40:46.611465] coredump: - PC 0x7f31abb9f6d0 |
| 157 | +[00:40:46.611467] coredump: SM 125/132 has finished state collection |
| 158 | + |
| 159 | +[00:40:46.806153] coredump: Writing ELF file to /tmp/cuda_coredump_xxx.1799919.1754898045 |
| 160 | + |
| 161 | +[1] 1799919 IOT instruction (core dumped) CUDA_ENABLE_COREDUMP_ON_EXCEPTION=1 CUDA_COREDUMP_SHOW_PROGRESS=1 = = ./test3 |
| 162 | +``` |
| 163 | +
|
| 164 | +当 GPU 线程触发非法内存访问时,CPU 立即生成 core dump 文件并触发 CPU 异常,直接终止程序。这时我们获得了 core dump 文件 `/tmp/cuda_coredump_xxx.1799919.1754898045`。可以使用 `cuda-gdb` 打开它: |
| 165 | +
|
| 166 | +```bash |
| 167 | +$ cuda-gdb |
| 168 | +(cuda-gdb) target cudacore /tmp/cuda_coredump_xxx.1799919.1754898045 |
| 169 | +Opening GPU coredump: /tmp/cuda_coredump_xxx.1799919.1754898045 |
| 170 | +
|
| 171 | +CUDA Exception: Warp Illegal Address |
| 172 | +The exception was triggered at PC 0x7f31abb9f6d0 illegalMemoryAccessKernel(int*, int) |
| 173 | +[Current focus set to CUDA kernel 0, grid 1, block (0,0,0), thread (0,0,0), device 0, sm 124, warp 0, lane 0] |
| 174 | +#0 0x00007f31abb9f6e0 in illegalMemoryAccessKernel(int*, int)<<<(1,1,1),(256,1,1)>>> () |
| 175 | +``` |
| 176 | + |
| 177 | +我们可以清晰看到,异常由 `illegalMemoryAccessKernel` 引发,定位在 kernel 0、grid 1、block (0,0,0)、thread (0,0,0)、device 0、sm 124、warp 0、lane 0。 |
| 178 | + |
| 179 | +### 调试 CUDA Graph 中的 Kernel 异常 |
| 180 | + |
| 181 | +以下是一个更复杂的例子:在 CUDA graph 中插入一个非法访问 kernel: |
| 182 | + |
| 183 | +```python |
| 184 | +# core_dump.py |
| 185 | +import torch |
| 186 | +import torch.nn as nn |
| 187 | + |
| 188 | +from dataclasses import dataclass |
| 189 | + |
| 190 | +@dataclass |
| 191 | +class CupyWrapper: |
| 192 | + data_ptr: int |
| 193 | + size_in_bytes: int |
| 194 | + |
| 195 | + @property |
| 196 | + def __cuda_array_interface__(self): |
| 197 | + return { |
| 198 | + "shape": (self.size_in_bytes,), |
| 199 | + "typestr": '|u1', |
| 200 | + "data": (self.data_ptr, False), |
| 201 | + "version": 3, |
| 202 | + } |
| 203 | + |
| 204 | +def from_buffer(data_ptr: int, size_in_bytes: int) -> torch.Tensor: |
| 205 | + out = torch.as_tensor(CupyWrapper(data_ptr, size_in_bytes)) |
| 206 | + assert data_ptr == out.data_ptr(), "not zero-copy convert, something must be wrong!" |
| 207 | + return out |
| 208 | + |
| 209 | + |
| 210 | +class NeuralNetwork(nn.Module): |
| 211 | + def __init__(self): |
| 212 | + super(NeuralNetwork, self).__init__() |
| 213 | + # 第 1 层: [B, 10] -> [B, 20] + ReLU 激活 |
| 214 | + self.layer1 = nn.Linear(10, 20) |
| 215 | + self.relu = nn.ReLU() |
| 216 | + # 第 2 层: [B, 20] -> [B, 30] |
| 217 | + self.layer2 = nn.Linear(20, 30) |
| 218 | + self.num_called = 0 |
| 219 | + |
| 220 | + def forward(self, x): |
| 221 | + # 输入形状: [B,10] |
| 222 | + x = self.layer1(x) |
| 223 | + x = self.relu(x) |
| 224 | + self.num_called += 1 |
| 225 | + if self.num_called > 1: |
| 226 | + y = from_buffer(x.data_ptr(), x.numel() * 1024 * 1024) |
| 227 | + # 会触发非法内存访问 |
| 228 | + y.fill_(1) |
| 229 | + x = self.layer2(x) |
| 230 | + return x |
| 231 | + |
| 232 | + |
| 233 | +# 使用示例 |
| 234 | +if __name__ == "__main__": |
| 235 | + device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') |
| 236 | + print(f"Using device: {device}") |
| 237 | + |
| 238 | + model = NeuralNetwork().to(device) |
| 239 | + batch_size = 4 |
| 240 | + input_tensor = torch.randn(batch_size, 10).to(device) |
| 241 | + |
| 242 | + print(f"Input shape: {input_tensor.shape}") |
| 243 | + print(f"Input device: {input_tensor.device}") |
| 244 | + |
| 245 | + with torch.no_grad(): |
| 246 | + output = model(input_tensor) # 先 warmup |
| 247 | + g = torch.cuda.CUDAGraph() |
| 248 | + with torch.cuda.graph(g): |
| 249 | + output = model(input_tensor) # 捕获 graph |
| 250 | + g.replay() # 重放 graph |
| 251 | + |
| 252 | + print(f"Output shape: {output.shape}") |
| 253 | + print(f"Output device: {output.device}") |
| 254 | + print(f"Output: {output.sum()}") |
| 255 | + |
| 256 | + print("\nModel architecture:") |
| 257 | + print(model) |
| 258 | + |
| 259 | + total_params = sum(p.numel() for p in model.parameters()) |
| 260 | + print(f"\nTotal parameters: {total_params}") |
| 261 | + print(f"Model device: {next(model.parameters()).device}") |
| 262 | +``` |
| 263 | + |
| 264 | +直接执行会报如下错误: |
| 265 | + |
| 266 | +```text |
| 267 | +Using device: cuda |
| 268 | +Input shape: torch.Size([4, 10]) |
| 269 | +Input device: cuda:0 |
| 270 | +Output shape: torch.Size([4, 30]) |
| 271 | +Output device: cuda:0 |
| 272 | +Traceback (most recent call last): |
| 273 | + File "core_dump.py", line 76, in <module> |
| 274 | + print(f"Output: {output.sum()}") |
| 275 | +RuntimeError: CUDA error: an illegal memory access was encountered |
| 276 | +... |
| 277 | +``` |
| 278 | + |
| 279 | +输出在 `output.sum()` 时才触发,因为此处进行了设备同步,但我们仍不知道哪个 kernel 触发了 IMA。 |
| 280 | + |
| 281 | +添加 `CUDA_LAUNCH_BLOCKING=1` 后,错误信息变为: |
| 282 | + |
| 283 | +```text |
| 284 | +... |
| 285 | +File "core_dump.py", line 71, in <module> |
| 286 | + g.replay() |
| 287 | +RuntimeError: CUDA error: an illegal memory access was encountered |
| 288 | +... |
| 289 | +``` |
| 290 | + |
| 291 | +这让我们推断异常发生在 CUDA graph 内的某个 kernel,但仍无法得知具体是哪一个。 |
| 292 | + |
| 293 | +再加上 core dump 的环境变量组合,我们就可以轻松定位: |
| 294 | + |
| 295 | +```text |
| 296 | +(cuda-gdb) target cudacore /tmp/cuda_coredump_flow-matic.1929094.1754901120 |
| 297 | +Opening GPU coredump: /tmp/cuda_coredump_flow-matic.1929094.1754901120 |
| 298 | +
|
| 299 | +CUDA Exception: Warp Illegal Address |
| 300 | +The exception was triggered at PC 0x7fc2afba5e30 void at::native::vectorized_elementwise_kernel<4, at::native::FillFunctor<unsigned char>, std::array<char*, 1ul> >(int, at::native::FillFunctor<unsigned char>, std::array<char*, 1ul>) |
| 301 | +[Current focus set to CUDA kernel 0, grid 9, block (17454,0,0), thread (0,0,0), device 0, sm 0, warp 1, lane 0] |
| 302 | +#0 0x00007fc2afba5e70 in void at::native::vectorized_elementwise_kernel<4, at::native::FillFunctor<unsigned char>, std::array<char*, 1ul> >(int, at::native::FillFunctor<unsigned char>, std::array<char*, 1ul>)<<<(40960,1,1),(128,1,1)>>> () |
| 303 | +``` |
| 304 | + |
| 305 | +很明显,这是一段 `fill` 函数,grid size 达到 `40960`,说明 `y = from_buffer(x.data_ptr(), x.numel() * 1024 * 1024); y.fill_(1);` 无视了 x 的真实长度,扩大了 1 百万倍然后填充,因而触发了 IMA 异常。 |
| 306 | + |
| 307 | +在部分 GPU 上,这一行可能报 `invalid argument` 而非 IMA,因为 grid size 超出最大限制。在此情况下,CUDA core dump 不会被触发,需要将扩容比例(`1024 * 1024`)适当调低,以免超过限制。 |
| 308 | + |
| 309 | +## 限制与注意事项 |
| 310 | + |
| 311 | +1. 理论上,CUDA core dump 应能捕获 GPU 某个线程引发的各种异常。但实际上,在某些 GPU 和驱动版本上,诸如“在全局/共享地址空间执行操作不支持”等异常可能无法触发 core dump。幸运的是, **非法内存访问** 通常能可靠触发 core dump,满足大多数调试需求。 |
| 312 | +2. 对于硬件相关错误(如“通过 nvlink 非法访问跨 GPU 的内存”或硬件故障),这些并非由具体线程引发,无法归因某个 GPU 线程,不会触发 core dump。 |
| 313 | +3. 通过 driver API 使用不当造成的错误属于[非粘性错误](https://forums.developer.nvidia.com/t/difference-in-error-handling-between-driver-api-and-runtime-api/336389),与 GPU 本身无关,会在 driver API 层面报告,同样不会触发 core dump。例如 `cudaMalloc` 时显存不足,不会生成 core dump。 |
| 314 | +4. 在涉及多 GPU 通信的分布式程序中,通常会通过映射将其他 GPU 的内存映射到当前 GPU。如果另一个 GPU 上的程序退出,映射就失效,再访问它会触发 IMA,但这属于分布式关机期间的特定现象,不属于典型 IMA 问题。使用 core dump 时要区分这种“假阳性”。 |
| 315 | +5. 启用 CUDA core dump 会对 CUDA kernel 性能有轻微影响(因为每个线程退出时需要检查错误并记录信息),因此不建议在生产环境开启。建议仅在可靠复现 IMA 错误以进行调试时使用。 |
| 316 | + |
| 317 | +## 总结 |
| 318 | + |
| 319 | +本文解析了 CUDA core dump 的原理与使用场景。这种调试方式对定位不正确的 kernel 启动与 CUDA graph 内的 kernel 异常特别有效,是调试 `illegal memory access` 及更多问题的强大工具。 |
| 320 | + |
| 321 | +例如,我们近期在 vLLM 中使用此技术调试了一个复杂的 IMA 问题(详见 [相关 PR](https://github.com/vllm-project/vllm/pull/22593))。基本情况是,我们为 MRope 添加了一个 triton kernel,但该 kernel 隐含假设 `head_size == rotary_dim`(即完整 Rope)。当 `head_size != rotary_dim`(部分 Rope)时,该 kernel 会触发 IMA,这在新 GLM-4.5V 模型中发生。若无 CUDA core dump,错误被误报为 `Failed: Cuda error /workspace/csrc/custom_all_reduce.cuh:453 'an illegal memory access was encountered'`,极具误导性。利用 core dump,我们轻松定位问题至 MRope kernel 并修复。请注意,对于更复杂的 IMA 问题,我们仍需以最小可复现示例隔离 kernel,然后结合如 [Compute Sanitizer](https://docs.nvidia.com/compute-sanitizer/ComputeSanitizer/index.html#memcheck-tool) 的专用工具进一步调查。 |
| 322 | + |
| 323 | +vLLM 项目旨在为所有人提供简单、高效、低成本的 LLM 推理服务,而易于调试也是重要组成部分。我们将持续分享更多调试技巧与方法,共同构建强大的 LLM 推理生态。如你有 vLLM 使用或调试经验,欢迎提交 PR 分享(地址在[博客仓库](https://github.com/vllm-project/vllm-project.github.io))。 |
| 324 | + |
| 325 | +## 致谢 |
| 326 | + |
| 327 | +感谢 Ze Long、Vikram Sharma Mailthody、Jeremy Iverson、Sandarbh Jain(来自 NVIDIA)给予的有益讨论。感谢 Red Hat 的 Lucas Wilkinson 帮助润色初稿。 |
0 commit comments