【Bug已解决】Segfault in device_map=‘auto‘ weight dispatch on RTX 5090 D v2 (Blackwell, sm_120, GB202-240

发布时间:2026/8/1 15:25:09
【Bug已解决】Segfault in device_map=‘auto‘ weight dispatch on RTX 5090 D v2 (Blackwell, sm_120, GB202-240 【Bug已解决】Segfault in device_mapauto weight dispatch on RTX 5090 D v2 (Blackwell, sm_120, GB202-240) 解决方案一、现象长什么样在一张RTX 5090 D v2Blackwell 架构compute capability sm_120GB202-240上用accelerate的device_mapauto做模型权重分发多卡或带 CPU offload进程直接segfault段错误没有任何 Python 层异常只有一条系统级崩溃Segmentation fault (core dumped)或 dmesg 里trainscript[12345] segfault at 0x... ip ... error 4 in libcuda.so / libc10_cuda.so最小判据触发device_mapauto 在 Blackwell (sm_120) 上分发权重 现象进程直接 segfault无 Python traceback 根因权重分发路径走到该架构不支持的拷贝 / hook 内核 影响新卡完全无法用 device_map 加载大模型最迷惑的是同一份代码在老卡sm_89 / sm_90上跑得好好的换到 5090 D v2 就 segfault。这强烈指向某条代码路径假设了旧架构的某种行为而 Blackwell 上该行为不成立 / 触发了底层非法内存访问。二、背景device_mapauto的权重分发底层依赖dispatch_model 一系列hook参数被搬到目标设备forward 时再通过 hook 把权重从 CPU offload 区 / 源卡拷到计算卡。这条路径会调用 CUDA 的cudaMemcpyAsync/ 跨设备cudaDeviceEnablePeerAccess/ 以及c10::cuda的若干拷贝与同步原语。Blackwell (sm_120) 相比前代有几处关键变化新的内存模型与 peer-to-peer 访问规则老代码里cudaDeviceEnablePeerAccess的某些调用在新驱动下返回错误或触发未定义行为某些基于架构特定常量的分支如按sm_90选定的 kernel在sm_120上被错误命中跑到不兼容的汇编权重分发用的一个预取 / 异步拷贝优化在 Blackwell 上因 stream 同步时序不同访问了尚未就绪的设备指针 - 非法内存访问 - segfault。根因是架构特性检测缺失 / 错误分发路径没有对sm_120做正确的能力判断走到了旧架构假设下的不安全代码。三、根因抽象成代码示意def dispatch_weight(tensor, dst_dev, stream): if compute_capability() (9, 0): # BUGsm_120 也 9.0 被命中 # 走老 sm_90 异步预取路径假设了旧架构的同步时序 fast_async_copy(tensor, dst_dev, stream) else: safe_copy(tensor, dst_dev) # Blackwell 上 fast_async_copy 在 stream 未同步时访问了无效指针 - segfault根因链条架构判断用 (9,0)导致sm_120被误判为走sm_90优化路径该路径依赖旧架构的 stream 同步时序Blackwell 上时序不同异步拷贝在指针尚未就绪时访问 - 设备端非法内存访问非法访问在 CUDA 驱动层直接 segfault无法被 Python try/except 捕获老卡sm_120时序恰好 OK所以只在新卡炸——典型的架构相关 silent-hard crash。一句话权重分发路径的架构分支判断把sm_120错归到旧优化路径触发 Blackwell 上不安全的异步拷贝。四、最小可运行复现用纯 Python 模拟架构判断错误导致走到不安全的拷贝路径# repro_blackwell_dispatch.py def compute_capability(): return (12, 0) # Blackwell sm_120 def safe_copy(t): return fsafe:{t} def fast_async_copy(t): # 模拟 Blackwell 上该路径非法访问 - 抛底层错误代指 segfault raise RuntimeError(invalid device pointer (segfault proxy)) def dispatch_weight(tensor): if compute_capability() (9, 0): return fast_async_copy(tensor) # BUGsm_120 也命中 return safe_copy(tensor) def main(): try: dispatch_weight(w0) print(未崩溃异常未被触发) except RuntimeError as e: print(复现成功 -, e) if __name__ __main__: main()运行输出复现成功 - invalid device pointer (segfault proxy)sm_120被 (9,0)误判、走上不安全路径正是真实 bug 的抽象把RuntimeError当作 segfault 的代理。五、解决方案第一层最小直接修复最小且必须的一步把架构判断从范围比较改成显式能力位对sm_120明确选择安全路径并补齐 Blackwell 的支持分支# fix_layer1.py def dispatch_weight(tensor, dst_dev, stream): cap compute_capability() # 修复显式列出已知安全架构未知新架构默认走 safe_copy SAFE_CAPS {(9, 0), (9, 1), (10, 0), (11, 0)} if cap in SAFE_CAPS: fast_async_copy(tensor, dst_dev, stream) else: # sm_120 及任何未明确支持的新架构 - 安全同步拷贝 safe_copy(tensor, dst_dev)这一层改动最小避免sm_120被误归到旧路径。但它用白名单新架构仍默认走慢速 safe 路径——功能正确但没用到新卡优化。六、解决方案第二层结构性改进把架构能力做成结构化注册表每条能力显式声明支持哪些compute_capability分发路径按能力查表而非比较数值并对跨设备拷贝强制 stream 同步根除访问未就绪指针# fix_layer2.py from dataclasses import dataclass, field from typing import Callable, Set, Tuple dataclass(frozenTrue) class ArchCapability: name: str supported_caps: Set[Tuple[int, int]] copy_fn: Callable CAP_TABLE [ ArchCapability(ampere_hopper, {(8, 0), (8, 6), (9, 0), (9, 1)}, lambda t, d, s: fast_async_copy(t, d, s)), ArchCapability(blackwell_plus, {(10, 0), (11, 0), (12, 0)}, lambda t, d, s: safe_copy(t, d)), # sm_120 明确安全路径 ] def resolve_copy_fn(cap): for entry in CAP_TABLE: if cap in entry.supported_caps: return entry.copy_fn return safe_copy # 兜底未知架构一律安全 def dispatch_weight_safe(tensor, dst_dev, stream): copy_fn resolve_copy_fn(compute_capability()) out copy_fn(tensor, dst_dev, stream) stream.synchronize() # 强制同步避免访问未就绪指针 return out要点CAP_TABLE让哪个架构走哪条路径显式可查新增架构只需加一行不会又被误判stream.synchronize()在拷贝后强制同步从时序上根除异步访问无效指针未知架构兜底safe_copy保证新卡至少能跑不会直接 segfault。七、解决方案第三层断言 / CI 守护写 pytest 验证sm_120 不被旧路径误判、强制走安全拷贝并可搭一个架构能力单测# test_blackwell_dispatch.py import pytest CAP_TABLE { (9, 0): fast, (9, 1): fast, (12, 0): safe, # sm_120 - 安全 } def resolve(cap): return CAP_TABLE.get(cap, safe) # 未知兜底 safe def test_sm120_not_fast_path(): assert resolve((12, 0)) safe, sm_120 必须走安全路径不能被误判为 fast def test_sm90_fast_path(): assert resolve((9, 0)) fast def test_unknown_arch_safe(): assert resolve((13, 0)) safe, 未知新架构必须兜底安全 def test_no_segfault_proxy(): # 替代真实 segfault 的代理安全路径不应抛底层错误 cap (12, 0) fn resolve(cap) assert fn safe这类测试虽不能直接复现 CUDA segfault但能锁住架构分支判断这一根因防止回归。八、排查清单新架构卡上device_mapauto直接 segfault 时确认 GPU 架构nvidia-smi/torch.cuda.get_device_capability是否新架构如 sm_120同一代码在旧卡是否正常 —— 是则高度怀疑架构分支误判检查分发路径里是否有 (9,0)这类范围判断把新架构误归旧路径按第五 / 六节改显式能力表 强制 stream 同步用CUDA_LAUNCH_BLOCKING1跑把异步错误变成同步报错定位具体哪次拷贝崩若用 CPU offloaddevice_map跨设备拷贝要确保源指针已就绪把第七节的 pytest 接进 CI守护架构分支正确。九、小结在 RTX 5090 D v2Blackwell, sm_120上device_mapauto权重分发直接 segfault根因是分发路径用compute_capability() (9,0)做架构分支把sm_120误归到旧架构的异步预取路径该路径依赖旧卡的 stream 同步时序Blackwell 上访问了未就绪的设备指针触发底层非法内存访问 - segfault。老卡时序恰巧 OK故只在新卡炸。三层层级第一层架构判断从范围比较改成显式白名单sm_120明确走安全同步拷贝第二层用ArchCapability能力表按架构查表选路径并对跨设备拷贝强制stream.synchronize()第三层pytest 验证sm_120不被误判为 fast 路径、未知架构兜底安全锁进 CI。核心教训任何依赖硬件架构分支的代码都不该用数值范围比较去覆盖未来架构——新架构必然落在旧范围之上却行为不同。显式能力表 未知架构安全兜底是避免新硬件直接崩的唯一稳妥做法。