☰
使用 SGLang Kernel API 日志调试 CUDA 崩溃:从环境变量到 compute-sanitizer 的完整实战指南
2026/10/6 1:46:08 网站建设 项目流程

使用 SGLang Kernel API 日志调试 CUDA 崩溃:从环境变量到 compute-sanitizer 的完整实战指南

【免费下载链接】sglangSGLang is a high-performance serving framework for large language models and multimodal models.项目地址: https://gitcode.com/GitHub_Trending/sg/sglang

导读

CUDA 非法内存访问、device-side assert、NaN/Inf 扩散——这类崩溃的共性是:进程在正常调试输出被刷出之前就中止了,你永远看不到触发崩溃的那批张量长什么样。SGLang 为此内置了一套基于@debug_kernel_api装饰器的内核 API 日志系统:它在内核执行之前捕获输入张量的形状、dtype、设备、连续性乃至数值统计,把"崩溃发生时到底发生了什么"记录下来。本文以该功能为主线,完整覆盖从环境变量启用、四个日志级别、崩溃安全 dump、多进程调试到与 compute-sanitizer / cuda-gdb / printf 组合定位的全流程,并结合 kernel_api_logging.py 等源码说明其底层实现原理。读完本文,你将掌握一套对 LLM 与 Diffusion 模型通用的 CUDA 崩溃排查方法论。

为什么 CUDA 崩溃需要"内核 API 日志"

问题:CUDA 错误(illegal memory access、device-side assert、out-of-bounds、NaN/Inf)往往直接中止进程。标准做法是在代码里手动打印张量,但大多数情况下崩溃发生在你还没来得及加打印的位置,而且普通 stdout 缓冲会在 abort 时丢失。

解决方案:SGLang 的@debug_kernel_api装饰器在执行前记录输入,因此即使程序中止,你依然能看到导致崩溃的调用边界上发生了什么。核心实现位于 python/sglang/kernels/kernel_api_logging.py,其设计参照了 FlashInfer 的 kernel API logging 工具。

日志覆盖范围

当前日志覆盖聚焦于 SGLang 中价值最高的内核边界:

  • 通过register_custom_op(...)注册的自定义算子;
  • 通过register_custom_op_from_extern(...)注册的外部自定义算子(如 FlashInfer 等外部库的内核);
  • LLM 的 attention、linear、quantization 以及多平台 wrapper 入口;
  • Diffusion 的 attention 实现、linear、rotary 和 custom-op wrapper 入口;
  • 部分直接torch.ops.sglang.*热点与模型级 bypass。

这意味着该日志对 LLM 和 Diffusion 内核调试都有用,但它不会自动覆盖仓库中每一个纯 PyTorch 调用。

源码中的接线方式

日志系统通过三条路径接入调用边界,你可以从源码确认其覆盖面:

  1. LLM 自定义算子:register_custom_op最终通过debug_torch_op把日志装饰器挂到torch.ops.sglang.<op>上,见 python/sglang/srt/utils/custom_op.py;外部算子走register_custom_op_from_extern,同样以debug_torch_op(fn, name)收尾(同文件 L328-L337)。
  2. Diffusion 自定义算子:CustomOp基类的forward直接标注@debug_kernel_api,见 python/sglang/multimodal_gen/runtime/layers/custom_op.py;对应的register_custom_op位于 python/sglang/multimodal_gen/runtime/layers/utils.py。
  3. AOT 编译内核:maybe_wrap_debug_kernel提供条件包装(仅在环境变量开启时生效),被 python/sglang/kernels/aot/python/sgl_kernel/init.py 用于批量包装sgl_kernel中的导出函数。

因此,当你通过@register_custom_op注册自己的算子时,它天然处于日志覆盖范围内——这正是后文复现实验的基础。

Step 1:启用 Kernel API 日志

日志由 4 个环境变量控制,其中SGLANG_KERNEL_API_LOGLEVEL决定详细程度,SGLANG_KERNEL_API_LOGDEST决定输出位置。在源码中,这些变量在模块导入时一次性解析(kernel_api_logging.py),因此必须在启动 Python 进程前设置好。

基础日志(仅函数名,Level 1)

export SGLANG_KERNEL_API_LOGLEVEL=1 export SGLANG_KERNEL_API_LOGDEST=stdout python my_script.py

输出示例(真实摘自Qwen/Qwen3-0.6B):

================================================================================ [2026-03-19 00:47:06] SGLang Kernel API Call: RMSNorm.forward ================================================================================ [2026-03-19 00:47:06] SGLang Kernel API Call: sglang.quant_method.UnquantizedLinearMethod.apply ================================================================================ [2026-03-19 00:47:06] SGLang Kernel API Call: sglang.custom_op.fused_inplace_qknorm

注意函数名的三类形态:普通模块方法(RMSNorm.forward)、quant 方法(sglang.quant_method.UnquantizedLinearMethod.apply)和自定义算子(sglang.custom_op.fused_inplace_qknorm)。

详细日志(输入输出带元数据,Level 3)

export SGLANG_KERNEL_API_LOGLEVEL=3 export SGLANG_KERNEL_API_LOGDEST=debug.log python my_script.py

debug.log中的输出示例(真实摘自Qwen/Qwen3-0.6B):

================================================================================ [2026-03-19 00:47:30] SGLang Kernel API Call: sglang.quant_method.UnquantizedLinearMethod.apply Positional input arguments: arg[0]=QKVParallelLinear( repr=QKVParallelLinear(in_features=1024, output_features=4096, bias=False, tp_size=1, gather_output=False) ) arg[1]=Tensor( shape=(1, 1024) dtype=torch.bfloat16 device=cuda:0 requires_grad=False is_contiguous=True ) arg[2]=None Output: return=Tensor( shape=(1, 4096) dtype=torch.bfloat16 device=cuda:0 requires_grad=False is_contiguous=True )

Level 3 已经足够你判断绝大多数形状(shape)、数据类型(dtype)和设备(device)不匹配问题。注意arg[0]是非张量对象(QKVParallelLinear模块实例),序列化器会提取shape/dtype/device属性或截断的repr;is_contiguous一栏则直接反映 stride 连续性,是排查 stride 相关内核 bug 的关键信号。

完整日志(带张量统计,Level 5)

export SGLANG_KERNEL_API_LOGLEVEL=5 export SGLANG_KERNEL_API_LOGDEST=debug.log python my_script.py

额外输出(真实摘自black-forest-labs/FLUX.1-dev):

================================================================================ [2026-03-19 01:00:42] SGLANG Kernel API Call: diffusion.quant_method.UnquantizedLinearMethod.apply Positional input arguments: arg[1]=Tensor( shape=(1, 77, 768) dtype=torch.bfloat16 device=cuda:0 requires_grad=False is_contiguous=True min=-27.250000 max=28.500000 mean=0.011723 nan_count=0 inf_count=0 ) Output: return=Tensor( shape=(1, 77, 2304) dtype=torch.bfloat16 device=cuda:0 requires_grad=False is_contiguous=True min=-8.937500 max=9.375000 mean=0.009460 nan_count=0 inf_count=0 )

Level 5 在 Level 3 基础上追加min/max/mean/nan_count/inf_count。统计计算由_serialize_tensor完成(kernel_api_logging.py):复数张量先取绝对值,整数张量不统计 NaN/Inf,浮点张量则逐项统计。空张量会标记为statistics=[empty tensor]。

崩溃安全 Dump(Level 10)

export SGLANG_KERNEL_API_LOGLEVEL=10 export SGLANG_KERNEL_API_LOGDEST=debug.log export SGLANG_KERNEL_API_DUMP_DIR=/tmp/sglang_kernel_api_dumps python my_script.py

Level 10 在执行前保存输入。若内核崩溃,dump 目录中依然保留输入与异常元数据。真实Qwen/Qwen3-0.6B的 Level 10 dump 布局:

/tmp/sglang_kernel_api_validation/qwen_qwen3_0_6b_level10_dumps /tmp/sglang_kernel_api_validation/qwen_qwen3_0_6b_level10_dumps/20260319_004821_182_pid919286_RotaryEmbedding.forward_call0001 /tmp/sglang_kernel_api_validation/qwen_qwen3_0_6b_level10_dumps/20260319_004821_182_pid919286_RotaryEmbedding.forward_call0001/inputs.pt /tmp/sglang_kernel_api_validation/qwen_qwen3_0_6b_level10_dumps/20260319_004821_182_pid919286_RotaryEmbedding.forward_call0001/metadata.json /tmp/sglang_kernel_api_validation/qwen_qwen3_0_6b_level10_dumps/20260319_004821_182_pid919286_RotaryEmbedding.forward_call0001/outputs.pt

每个调用对应一个以时间戳_pid_函数名_call序号命名的子目录,内含:

  • inputs.pt:torch.save保存的输入张量字典,key 形如arg_0、arg_1、kwarg_x(容器与嵌套元素会被展开为arg_0_0之类的前缀 key,容器结构记录在metadata.json中);
  • outputs.pt:输出张量(仅当调用成功完成时存在);
  • metadata.json:调用元数据。

真实metadata.json片段:

{ "function_name": "RotaryEmbedding.forward", "timestamp": "20260319_004821_182", "process_id": 919286, "execution_status": "completed", "input_tensor_keys": ["arg_0", "arg_1", "arg_2"], "output_tensor_keys": ["result_0", "result_1"] }

源码层面的执行流是(kernel_api_logging.py):先_dump_function_inputs写入execution_status: "inputs_saved"的元数据与inputs.pt;随后调用被包装函数;成功则_dump_function_outputs把状态改为completed并写入outputs.pt;抛异常则_mark_dump_exception把状态改为exception并记录异常类型与消息。因此你只要看到execution_status: "exception"且缺outputs.pt,就说明崩溃发生在该调用边界内。

使用 Level 10 的注意事项:

  • 若 CUDA graph capture 处于激活状态,张量 dump 会被自动跳过(避免 capture 期间触发 CUDA 错误),此时仍能得到调用日志,但没有inputs.pt/outputs.pt;
  • Level 10 dump 的本质是崩溃安全的调用快照:它始终保留观察到的调用边界,但并非每个方法都能一键回放,因为部分方法依赖未序列化进 dump 的模块状态;
  • 对真实模型的成功路径 Level 10 dump,通常建议在调试运行中临时关闭 CUDA graph 与 piecewise CUDA graph。

Step 2:复现一个 LLM CUDA 崩溃

先用一个最小复现脚本模拟"embedding 索引越界导致 device-side assert"的场景。脚本通过@register_custom_op注册一个会崩溃的自定义算子,从而让崩溃点正好落在日志覆盖边界上:

python3 - <<'PY' from pathlib import Path Path("/tmp/sglang_llm_crash.py").write_text( "import torch\n" "import torch.nn.functional as F\n" "from sglang.srt.utils.custom_op import register_custom_op\n\n" "def _fake_embedding(indices, table):\n" " return torch.empty((*indices.shape, table.shape[-1]), device=table.device, dtype=table.dtype)\n\n" "@register_custom_op(op_name='mock_llm_cuda_crash', fake_impl=_fake_embedding)\n" "def mock_llm_cuda_crash(indices, table):\n" " out = F.embedding(indices, table)\n" " torch.cuda.synchronize()\n" " return out\n\n" "table = torch.randn(4, 8, device='cuda', dtype=torch.float16)\n" "indices = torch.tensor([0, 7], device='cuda', dtype=torch.long)\n" "mock_llm_cuda_crash(indices, table)\n" ) PY SGLANG_KERNEL_API_LOGLEVEL=1 \ SGLANG_KERNEL_API_LOGDEST=/tmp/sglang_llm_level1.log \ python3 /tmp/sglang_llm_crash.py

预期结果:

  • 脚本以 CUDAdevice-side assert退出;
  • 日志中仍保留崩溃前的最后一个 API 边界记录(即sglang.custom_op.mock_llm_cuda_crash)。

这个脚本刻意用indices=[0, 7]配合仅 4 行的table制造越界:F.embedding查表时索引 7 超出词表规模 4,GPU 侧 assert 触发,随后torch.cuda.synchronize()把异步错误同步回主机端。

同一示例换 Level 3:

SGLANG_KERNEL_API_LOGLEVEL=3 \ SGLANG_KERNEL_API_LOGDEST=/tmp/sglang_llm_level3.log \ python3 /tmp/sglang_llm_crash.py

现在日志中会出现崩溃前的张量元数据——你可以直接看到indices的形状与数值来源。

再试 Level 10:

SGLANG_KERNEL_API_LOGLEVEL=10 \ SGLANG_KERNEL_API_LOGDEST=/tmp/sglang_llm_level10.log \ SGLANG_KERNEL_API_DUMP_DIR=/tmp/sglang_llm_level10_dumps \ python3 /tmp/sglang_llm_crash.py

此时你应该看到:

  • 一条sglang.custom_op.mock_llm_cuda_crash的日志条目;
  • 一个包含inputs.pt的 dump 目录;
  • metadata.json显示execution_status: "exception";
  • 没有outputs.pt,因为内核在产生输出前就崩溃了。

Step 3:复现一个 Diffusion CUDA 崩溃

Diffusion 侧的自定义算子注册入口不同,位于sglang.multimodal_gen.runtime.layers.utils,其余套路完全一致:

python3 - <<'PY' from pathlib import Path Path("/tmp/sglang_diffusion_crash.py").write_text( "import torch\n" "import torch.nn.functional as F\n" "from sglang.multimodal_gen.runtime.layers.utils import register_custom_op\n\n" "def _fake_embedding(positions, cache):\n" " return torch.empty((*positions.shape, cache.shape[-1]), device=cache.device, dtype=cache.dtype)\n\n" "@register_custom_op(op_name='mock_diffusion_cuda_crash', fake_impl=_fake_embedding)\n" "def mock_diffusion_cuda_crash(positions, cache):\n" " out = F.embedding(positions, cache)\n" " torch.cuda.synchronize()\n" " return out\n\n" "cache = torch.randn(4, 64, device='cuda', dtype=torch.float16)\n" "positions = torch.tensor([0, 9], device='cuda', dtype=torch.long)\n" "mock_diffusion_cuda_crash(positions, cache)\n" ) PY SGLANG_KERNEL_API_LOGLEVEL=1 \ SGLANG_KERNEL_API_LOGDEST=/tmp/sglang_diffusion_level1.log \ python3 /tmp/sglang_diffusion_crash.py

同样依次尝试 Level 3 与 Level 10:

SGLANG_KERNEL_API_LOGLEVEL=3 \ SGLANG_KERNEL_API_LOGDEST=/tmp/sglang_diffusion_level3.log \ python3 /tmp/sglang_diffusion_crash.py SGLANG_KERNEL_API_LOGLEVEL=10 \ SGLANG_KERNEL_API_LOGDEST=/tmp/sglang_diffusion_level10.log \ SGLANG_KERNEL_API_DUMP_DIR=/tmp/sglang_diffusion_level10_dumps \ python3 /tmp/sglang_diffusion_crash.py

如果你的本地环境存在无关的 FlashInfer 导入问题,请先在上层 shell 中解决再运行示例;示例本身不会设置任何FLASHINFER_*环境变量。

Step 4:多进程调试

多 GPU 或 worker 进程场景下,多个 rank 会把日志写到同一个文件互相覆盖。SGLANG_KERNEL_API_LOGDEST和SGLANG_KERNEL_API_DUMP_DIR都支持%i占位符,它在进程内被替换为os.getpid()(见 kernel_api_logging.py 的_str_with_pid):

export SGLANG_KERNEL_API_LOGLEVEL=3 export SGLANG_KERNEL_API_LOGDEST=debug_rank_%i.log torchrun --nproc_per_node=4 my_script.py

这会生成彼此独立的日志文件,例如debug_rank_12345.log、debug_rank_12346.log、debug_rank_12347.log、debug_rank_12348.log。

真实的多进程示例来自一次 2-GPUQwen/Qwen2.5-0.5B-Instruct运行:

/tmp/sglang_kernel_api_validation_multi/qwen_qwen2_5_0_5b_instruct_level3_950201.log /tmp/sglang_kernel_api_validation_multi/qwen_qwen2_5_0_5b_instruct_level3_950349.log /tmp/sglang_kernel_api_validation_multi/qwen_qwen2_5_0_5b_instruct_level3_950350.log /tmp/sglang_kernel_api_validation_multi/qwen_qwen2_5_0_5b_instruct_level3_950351.log

Level 10 的 dump 目录也应同样处理:

export SGLANG_KERNEL_API_LOGLEVEL=10 export SGLANG_KERNEL_API_LOGDEST=debug_rank_%i.log export SGLANG_KERNEL_API_DUMP_DIR=/tmp/sglang_kernel_api_dumps_%i

这样可避免多个 rank 写入同一棵 dump 目录树。

Step 5:过滤 Level 10 Dump

Level 10 全量 dump 可能过于嘈杂。此时用通配符限制 dump 范围——SGLANG_KERNEL_API_DUMP_INCLUDE/SGLANG_KERNEL_API_DUMP_EXCLUDE采用 shell 风格通配匹配(fnmatch),且支持逗号分隔的多模式(源码解析见 kernel_api_logging.py 与_should_dump_function,同文件 L122-L131):

export SGLANG_KERNEL_API_LOGLEVEL=10 export SGLANG_KERNEL_API_LOGDEST=debug.log export SGLANG_KERNEL_API_DUMP_DIR=/tmp/sglang_kernel_api_dumps export SGLANG_KERNEL_API_DUMP_INCLUDE='sglang.custom_op.*' export SGLANG_KERNEL_API_DUMP_EXCLUDE='*.fake_impl'

匹配规则:若设置了 INCLUDE,函数名必须命中至少一个模式才 dump;若设置了 EXCLUDE,命中任意一个模式即跳过。

Step 6:常见 CUDA 错误及排查要点

非法内存访问或 Device-Side Assert

典型报错:

RuntimeError: CUDA error: an illegal memory access was encountered torch.AcceleratorError: CUDA error: device-side assert triggered

排查命令:

export SGLANG_KERNEL_API_LOGLEVEL=3

在日志中检查:

  • 张量形状(shape)
  • 张量 dtype
  • CUDA 与 CPU 的设备放置
  • stride / 连续性(is_contiguous)
  • 是否存在"只记录到输入、没有输出"的调用——这是崩溃点的直接标志

典型的形状不匹配模式:

SGLANG Kernel API Call: ... arg[0]=Tensor(shape=(..., 128), ...) # 期望的维度 arg[1]=Tensor(shape=(..., 64), ...) # 不匹配

这类现象通常指向 head-dim、hidden-dim 或 cache 布局不一致,而不是随机的 CUDA 故障。

NaN 或 Inf

排查命令:

export SGLANG_KERNEL_API_LOGLEVEL=5

检查字段:min、max、mean、nan_count、inf_count。

典型坏数据模式:

Tensor( ... min=-1234567.000000 # 数值异常偏大 max=9876543.000000 # 数值异常偏大 mean=nan # 已出现 NaN nan_count=128 # 找到 NaN inf_count=0 # 此处尚无 Inf )

这通常意味着坏值在进入崩溃内核之前就已经存在——你需要沿调用链向前追溯,找到第一个产生坏值的位置,而不是盯着崩溃点本身。

显存不足(Out of Memory)

排查命令:

export SGLANG_KERNEL_API_LOGLEVEL=3

检查:

  • 异常大的张量形状
  • batch size
  • 序列长度
  • Diffusion 场景下的帧数或图像分辨率

同时确认是否存在"本应是 per-token / per-frame 的张量,意外变成了 full-sequence / full-image 尺寸"的情况。

典型坏模式:

Tensor( shape=(1024, 8192, 128, 128) # 尺寸过大 ... )

示例:从日志中定位形状 Bug

假设失败调用日志如下:

[2026-03-19 00:47:30] SGLang Kernel API Call: RotaryEmbedding.forward Positional input arguments: arg[0]=Tensor(shape=(1, 8), dtype=torch.int64, ...) arg[1]=Tensor(shape=(1, 8, 8, 256), dtype=torch.bfloat16, ...) # query 正常 arg[2]=Tensor(shape=(1, 8, 4, 64), dtype=torch.bfloat16, ...) # key 的 head_dim 不匹配

你能得出什么结论:

  • positions 看起来合理;
  • query 看起来合理;
  • key 的最后一维与预期的 rotary/head 维度不一致。

这通常意味着 bug 出在 projection 布局、head 打包或 cache 格式上,而不是 rotary 内核本身。

Step 7:与 compute-sanitizer 组合使用

对于更隐蔽的越界写等问题,把内核 API 日志与 CUDA 内存检查工具结合:

export SGLANG_KERNEL_API_LOGLEVEL=3 export SGLANG_KERNEL_API_LOGDEST=debug.log compute-sanitizer --tool memcheck python3 /tmp/sglang_llm_crash.py

用debug.log查看到达崩溃 API 边界的确切输入。

典型compute-sanitizer输出:

========= COMPUTE-SANITIZER ========= Invalid __global__ write of size 4 bytes ========= at 0x1234 in SomeKernel ========= by thread (256,0,0) in block (10,0,0) ========= Address 0x... is out of bounds

工作流是:用 sanitizer 输出定位崩溃的内核,用debug.log定位到达该边界之前的张量,两者交叉得到完整证据链。

如果需要更同步的主机端错误上报,可以单独把CUDA_LAUNCH_BLOCKING=1作为后续对照实验。它不属于默认工作流的一部分,因为改变执行时序可能掩盖与并发相关的问题。

Step 8:与 cuda-gdb 组合使用

需要栈回溯而非仅内存诊断时:

export SGLANG_KERNEL_API_LOGLEVEL=3 export SGLANG_KERNEL_API_LOGDEST=debug.log cuda-gdb --args python3 /tmp/sglang_llm_crash.py

在cuda-gdb内部:

(cuda-gdb) run (cuda-gdb) where

然后将回溯与debug.log相互对照:gdb 告诉你崩溃发生在哪个内核、哪个指令,日志告诉你到达该内核时输入张量是什么。

Step 9:内核级 printf 调试

当你拥有 CUDA 内核源码时,printf()仍是缩小坏索引、坏启动几何或状态传播问题范围的有效手段。

基本模式:

__global__ void MyKernel(const float* input, float* output, int n) { int idx = blockIdx.x * blockDim.x + threadIdx.x; if (threadIdx.x == 0 && blockIdx.x == 0) { printf("n=%d input0=%f\n", n, input[0]); } if (idx < n) { output[idx] = input[idx] * 2.0f; } }

内核启动后强制刷新输出:

my_kernel(...) torch.cuda.synchronize()

printf输出是异步的,不 synchronize 就可能丢失。

Warp 专用内核:选择正确的打印线程

问题:

  • threadIdx.x == 0只打印 block 中第一个 warp 的信息;
  • 对 warp-specialized 内核,这往往错过真正出错的 warp 或 specialization 组。

更好的模式(每个 warp 的第一个 lane 打印):

__global__ void WarpSpecializedKernel(...) { // 示例:每个 warp 的第一个 lane if ((threadIdx.x % 32) == 0) { printf("warp=%d\n", threadIdx.x / 32); } }

或者,如果内核按更大的 specialization 组组织,改为每组打印一次而非每 block 一次。

常见错误(只有 warp 0 打印):

// 只有 warp 0 会打印 if (threadIdx.x == 0) { printf("warp=%d\n", threadIdx.x / 32); }

快速参考

内核类型打印条件说明
简单内核threadIdx.x == 0每 block 一个线程通常足够
Warp 专用内核每个 warp 选一个代表 lane例如threadIdx.x % 32 == 0
组专用内核每组选一个代表 lane依据内核的调度布局选择

其他内核调试手段

assert(value >= 0.0f && "value must be non-negative"); static_assert(BLOCK_SIZE % 32 == 0, "BLOCK_SIZE must be warp aligned");

static_assert能在编译期捕获 warp 对齐等结构性问题,与运行期断言互补。

环境变量参考

变量取值说明
SGLANG_KERNEL_API_LOGLEVEL0关闭日志(默认值)
1仅函数名
3输入输出 + 元数据
5Level 3 + 张量统计(min/max/mean/nan/inf)
10Level 5 + 崩溃安全张量 dump
SGLANG_KERNEL_API_LOGDESTstdout输出到 stdout
stderr输出到 stderr
<path>输出到文件
log_%i.txt%i展开为进程 ID
SGLANG_KERNEL_API_DUMP_DIR<path>Level 10 dump 目录(同样支持%i)
SGLANG_KERNEL_API_DUMP_INCLUDE通配符列表仅 dump 匹配的 API
SGLANG_KERNEL_API_DUMP_EXCLUDE通配符列表跳过匹配的 API

日志系统的几个实现细节

  • 零开销设计:日志级别为 0 时,debug_kernel_api直接返回原函数,装饰器不会给热路径增加任何运行时开销(kernel_api_logging.py);maybe_wrap_debug_kernel也在环境变量为 0 时直接透传原函数(debug_utils.py)。
  • 防重复包装:包装函数带有_debug_kernel_wrapped标记,重复装饰会被安全跳过(kernel_api_logging.py)。
  • torch.compile 兼容:torch.compiler.is_compiling()为真时 wrapper 直接透传原函数,避免干扰图编译(同文件 L418-L420)。
  • 统计跳过:CUDA graph capture 期间 Level 5 统计被有意跳过(标记为statistics=[skipped: CUDA graph capture in progress]),Level 10 dump 同样跳过(Tensor dump skipped: CUDA graph capture in progress),因为统计与 dump 都需要同步/拷贝到 CPU,在 capture 期间不允许。
  • 函数名推断:默认函数名由__qualname__与模块名组合而成,并剥离sglang./sgl_kernel.前缀,得到sglang.quant_method.UnquantizedLinearMethod.apply这类紧凑名称;也支持op_name显式覆盖(同文件 L367-L384)。

最佳实践

1. 从 Level 3 开始

export SGLANG_KERNEL_API_LOGLEVEL=3

Level 3 通常足以捕获错误的形状、dtype 和设备放置。

2. 数值问题用 Level 5

export SGLANG_KERNEL_API_LOGLEVEL=5

怀疑 NaN/Inf 时使用。

3. 崩溃复现用 Level 10

export SGLANG_KERNEL_API_LOGLEVEL=10

进程在你能检查实时张量之前就崩溃时,这是最有用的模式。

配套建议:

  • 需要真实模型运行的成功路径输入/输出 dump 时,临时为该调试会话关闭 CUDA graph;
  • Level 10 过于嘈杂时,配合SGLANG_KERNEL_API_DUMP_INCLUDE/SGLANG_KERNEL_API_DUMP_EXCLUDE精确圈定 API,而不是对所有覆盖的 API 全量 dump。

4. 崩溃时写文件而非 stdout

export SGLANG_KERNEL_API_LOGDEST=crash.log

进程中止时,文件日志比 stdout 更安全。

5. 生产环境关闭日志

unset SGLANG_KERNEL_API_LOGLEVEL

关闭时装饰器返回原始可调用对象,不增加运行时日志开销(见上文"零开销设计")。

故障排查

没有日志输出

依次检查:

  1. echo $SGLANG_KERNEL_API_LOGLEVEL—— 确认级别不为 0;
  2. echo $SGLANG_KERNEL_API_LOGDEST—— 确认输出目标;
  3. 失败路径是否经过受覆盖的 API 边界——纯 PyTorch 调用默认不在覆盖范围内。

输出太多

降低级别:

export SGLANG_KERNEL_API_LOGLEVEL=3

CUDA Graph Capture 期间统计被跳过

如果看到:

statistics=[skipped: CUDA graph capture in progress]

这是预期行为:Level 5 统计在 CUDA graph capture 期间被有意跳过,以避免同步副作用。

CUDA Graph Capture 期间张量 dump 被跳过

如果看到:

Tensor dump skipped: CUDA graph capture in progress

同样是预期行为:Level 10 dump 需要把张量拷贝到 CPU,这在 CUDA graph capture 期间不允许。

总结:一套完整的崩溃排查工作流

把以上步骤串成一条可复用的排查链路:

  1. 首次崩溃:SGLANG_KERNEL_API_LOGLEVEL=3+LOGDEST=file,重跑,从日志找"有输入无输出"的边界;
  2. 数值异常:升级到LOGLEVEL=5,检查 min/max/mean/nan/inf,判断坏值来源;
  3. 需要回放现场:升级到LOGLEVEL=10+DUMP_DIR,拿到inputs.pt/metadata.json(execution_status: "exception"即崩溃点证据);
  4. 多进程:日志与 dump 路径加%i按 PID 隔离;
  5. 越界写等内存问题:叠加compute-sanitizer --tool memcheck定位内核,用日志定位输入;
  6. 需要栈回溯:叠加cuda-gdb,where后与日志交叉比对;
  7. 内核自持:在自定义 CUDA 内核中按 warp/组选择代表线程printf,配合torch.cuda.synchronize()与assert/static_assert。

这套方法论的依据都落在仓库中:日志核心实现见 python/sglang/kernels/kernel_api_logging.py,LLM 侧接入见 python/sglang/srt/utils/custom_op.py,Diffusion 侧接入见 python/sglang/multimodal_gen/runtime/layers/custom_op.py 与 python/sglang/multimodal_gen/runtime/layers/utils.py,AOT 内核条件包装见 python/sglang/kernels/aot/python/sgl_kernel/debug_utils.py。下次再遇到"莫名其妙"的 CUDA 崩溃,先开日志,再谈猜测。

【免费下载链接】sglangSGLang is a high-performance serving framework for large language models and multimodal models.项目地址: https://gitcode.com/GitHub_Trending/sg/sglang

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询