[Bug已解决] CUDA error- device-side assert triggered 在 model.cuda() 时触发解决方案

[Bug已解决] CUDA error- device-side assert triggered 在 model.cuda() 时触发解决方案
[Bug已解决] CUDA error: device-side assert triggered 在 model.cuda() 时触发解决方案一、现象长什么样你写好了模型准备把它搬到 GPU 上训练model MyModel() model.cuda() # 或 model.to(cuda)结果还没开始跑 forward仅仅在model.cuda()这一步就崩了报CUDA error: device-side assert triggered CUDA kernel errors might be asynchronously reported at some other API call, so the stacktrace below might be incorrect. For debugging consider passing CUDA_LAUNCH_BLOCKING1.并且后面跟着 model.cuda()的栈。 这非常反直觉model.cuda()只是把参数张量从 CPU 拷贝到 GPU又不是执行某个算子的 forward怎么会触发「device-side assert设备端断言」本 issuepytorch/pytorch#37057讨论的就是这类情况——在.cuda()阶段就抛出 device-side assert。 本文聚焦device-side assert 是什么、为什么.cuda()会触发它、怎么定位真正的断言来源、怎么修。二、背景device-side assert 是什么CUDA 里有个机制叫device-side assert在内核里你写assert(cond)或 PyTorch 里很多算子内部有TORCH_CHECK/CUDA_KERNEL_ASSERT当条件不满足时会在 GPU 内核里触发断言导致整个上下文进入错误状态后续所有 CUDA 调用都失败并报CUDA error: device-side assert triggered。 关键特性异步GPU 内核是异步执行的所以断言错误往往不在出错的那一刻报而是在之后某个无关的 CUDA 调用比如下一次.cuda()、下一次loss.backward()才浮现。所以你看到的栈指向model.cuda()未必是真正的错误现场。污染上下文一旦触发当前进程的所有后续 CUDA 调用都会失败必须重启进程才能恢复光torch.cuda.empty_cache()没用。常见根因是越界label 越界分类数不对、index 越界embedding/gather下标超范围、shape 不匹配导致内核访问了非法内存。 PyTorch 的报错提示里那句CUDA kernel errors might be asynchronously reported就是在告诉你栈可能不准要去真正的算子调用处找原因。三、为什么 model.cuda() 也能触发 device-side assertmodel.cuda()本身只是.to(device)的张量拷贝不该触发内核断言。那为什么报错指向它两种情形情形 A异步延迟暴露真正触发断言的是上一次 forward / backward的某个内核比如你之前跑过一次里面的CrossEntropyLoss因为 label 越界在 GPU 上 assert 了。但因为异步错误没在那时报而是「攒着」等到你下一次任意 CUDA 调用——恰好是model.cuda()——才抛出来。于是栈指向了.cuda()但真凶在更早的 forward。情形 Bcuda()内部触发了内核某些模块在.cuda()时会做「权重重排 / 特殊初始化内核」例如带caffe2风格预打包权重的算子某些自定义 CUDA 扩展在to()时触发 shape 相关的内核断言nn.Embedding的权重在.cuda()后被某个 lazy 初始化内核处理。 但更常见仍是情形 A.cuda()只是「背锅」的真正断言在之前某次计算。四、最小可运行复现label 越界触发异步暴露下面演示「越界 label → 异步 → 后续.cuda()报 device-side assert」的经典链路。需要 GPU无 GPU 时我们给出逻辑说明。import os os.environ[CUDA_LAUNCH_BLOCKING] 1 # 关键让错误同步暴露栈才准 import torch import torch.nn as nn def demo_device_side_assert(): if not torch.cuda.is_available(): print([skip] 无 GPU仅说明分类 label 越界会在 GPU 内核触发 assert) return model nn.Linear(10, 5).cuda() # 5 类 crit nn.CrossEntropyLoss() x torch.randn(3, 10, devicecuda) # label 取值 0..4但这里给成 5越界→ 内核里 assert 失败 y torch.tensor([5, 0, 1], devicecuda) try: logits model(x) loss crit(logits, y) # 这里其实已经触发 device-side assert异步 loss.backward() except Exception as e: print(第一次调用异常可能已触发 assert:, type(e).__name__) # 紧接着再任意一次 CUDA 调用错误延迟暴露栈可能指向这里 try: model2 nn.Linear(3, 3).cuda() # 报错可能指向这一行 print(model2 创建成功) except RuntimeError as e: if device-side assert in str(e): print([确认] device-side assert 延迟暴露真凶是上面的越界 label) else: print(其它错误:, e) if __name__ __main__: demo_device_side_assert()要点没设CUDA_LAUNCH_BLOCKING1时错误会延迟到后面任意.cuda()/.to()才抛栈指向那一行极具迷惑性。五、解决方案一CUDA_LAUNCH_BLOCKING1 定位真凶第一步永远是让异步变同步拿到准确栈CUDA_LAUNCH_BLOCKING1 python train.py或在代码最前面import os os.environ[CUDA_LAUNCH_BLOCKING] 1这样 device-side assert 会在真正出错的那次算子调用就抛出栈直接指向根因比如CrossEntropyLoss、embedding、gather而不是迷惑你的model.cuda()。六、解决方案二TORCH_USE_CUDA_DSA 精确定位新版本PyTorch 较新版本支持device-side assert 的精确行号DSA Device Side Assert开启后能告诉你具体哪个文件哪一行触发TORCH_USE_CUDA_DSA1 CUDA_LAUNCH_BLOCKING1 python train.py这会在报错里给出类似assert failed at file xxx.cu:123的信息极大加速定位。注意它会带来额外开销仅调试用。七、解决方案三最常见的根因修复清单device-side assert 九成以上是「越界」。按以下顺序查1. 分类标签越界# 类别数 num_classeslabel 必须在 [0, num_classes-1] num_classes 5 y torch.tensor([5, 0, 1]) # 5 越界 # 修复检查数据加载确保 label 已正确映射 assert y.min() 0 and y.max() num_classes, flabel 越界: {y.min()},{y.max()}2. nn.Embedding 下标越界emb nn.Embedding(num_embeddings1000, embedding_dim16) idx torch.tensor([0, 999, 1000], devicecuda) # 1000 越界合法 0..999 # 修复clamp 或检查词表 idx idx.clamp(0, 999) out emb(idx)3. gather / index_select 下标越界# gather 沿 dim 的下标必须在 [0, dim_size) src torch.randn(3, 5, devicecuda) idx torch.tensor([[0, 5, 2]], devicecuda) # 5 越界列 0..4 idx idx.clamp(0, src.size(1) - 1) out torch.gather(src, 1, idx)4. 形状不匹配导致内核访问非法内存例如view后元素总数变了、或自定义算子输出 shape 算错。用CUDA_LAUNCH_BLOCKING1定位到具体算子核对输入输出 shape。八、解决方案四进程已被污染必须重启一旦 device-side assert 触发当前进程的所有后续 CUDA 调用都会失败torch.cuda.empty_cache()也救不了。所以训练脚本里捕获到device-side assert后应直接退出进程不要尝试继续在 notebook 里要重启 kernel才能恢复 GPU 上下文不要在同一个进程里反复 retry那只会一直报同样的错。import torch import sys try: loss.backward() except RuntimeError as e: if device-side assert in str(e): print(触发 device-side assert进程上下文已污染必须退出重启) sys.exit(1) # notebook 里用退出并重启 kernel九、排查清单model.cuda() 报 device-side assert先同步CUDA_LAUNCH_BLOCKING1重跑看错误栈指向哪个真实算子不是.cuda()。再精确定位TORCH_USE_CUDA_DSA1拿到内核断言行号。查越界label、embedding 下标、gather 下标是否超范围最常见。查 shape最近改动的算子输出 shape 是否算错导致非法内存访问。查自定义 CUDA 扩展自己的.cu/cpp扩展里有无assert或越界访问。重启进程确认是 device-side assert 后退出现有进程/kernel 再重启别在同一上下文硬扛。十、小结CUDA error: device-side assert triggered model.cuda()的核心陷阱是device-side assert 是异步的真凶通常是更早一次 forward / backward 里的越界label 越界、embedding / gather 下标越界、shape 错误错误被「攒着」直到下一次任意 CUDA 调用恰好是model.cuda()或model.to()才抛出于是栈指向了.cuda()极具迷惑性。 排查铁律同步暴露CUDA_LAUNCH_BLOCKING1让错误在真实算子处抛出精确定位TORCH_USE_CUDA_DSA1拿内核断言行号修根因九成是越界逐条核对 label / 下标 / shape重启进程上下文一旦被污染必须退出重启不能在同一进程硬重试。 记住栈里那行model.cuda()几乎从来不是真凶顺着CUDA_LAUNCH_BLOCKING1的栈往回找才是正解。