Burn/Wgpu 在 WASM 运行 MobileNetV2 时为何出现 condvar panic?
仅凭这些日志,无法认定 WASM + WebGPU 不支持 MobileNetV2。目前可以确定的是,执行路径进入了不支持条件变量等待的 Rust 实现。具体是哪个 Burn 或底层依赖调用触发了等待,还需要完整调用栈和依赖版本才能判断。
Burn 的 Wgpu 后端支持在兼容的浏览器和 WASM 运行环境中使用 WebGPU,但不代表所有版本中的模型执行路径都可用。SqueezeNet 成功而 MobileNetV2 失败,说明两者触发的执行路径有差异,不能直接据此认定某种模型结构不受支持。Wgpu 后端文档
两条 panic 分别说明什么?
condvar wait not supported 配合 condvar/no_threads.rs 路径,表明代码调用了当前目标实现不支持的条件变量等待,并没有指出具体的模型算子。
cannot recursively acquire mutex 表明互斥锁实现检测到了重复获取锁。它可能是独立问题,也可能发生在前一次 panic 后的错误处理过程中。应保留浏览器控制台中最早的 panic 及其完整调用栈,不能只凭两条错误文本认定它们有相同根因。
给外层函数加上 async,或使用 self.inference(...).await,都不会自动把内部的同步阻塞调用变成异步调用。
先定位失败阶段
现有函数包含后端初始化、模型构造和预热推理三个阶段。先在每个阶段前后添加日志,确定最后成功执行的位置。
例如,将模型构造和预热部分暂时改成:
log::info!("Before Model::new");
let model = Model::new(&device);
log::info!("After Model::new");
self.model = ModelType::WithWgpuBackend(model);
log::info!("Before warmup inference");
let _ = self.inference(&[0.0; HEIGHT * WIDTH * CHANNELS]).await;
log::info!("After warmup inference");
这段代码只用于缩小范围。如果 inference 返回 Result,正式代码还应处理错误,避免 let _ = ... 丢弃普通错误信息。处理 Result 本身不能修复内部 panic。
如果错误出现在预热阶段,继续在 inference 内部区分输入构造、模型前向计算和输出数据读取。如果后端采用延迟执行,错误可能直到读取输出时才暴露,最后一条日志不一定直接对应有问题的算子。
检查是否同步读取了 GPU 数据
优先检查 inference、输出后处理及生成的模型代码中是否存在:
into_data()、to_data()或into_scalar()等同步读取操作。block_on一类同步等待 Future 的调用。- 显式设备同步,或依赖内部的阻塞等待。
Burn 文档说明,部分数据读取和同步操作会触发后端同步;Tensor API 也指出,同步读回在不受支持的平台上可能 panic。因此,同步读回值得优先检查,但目前提供的代码没有展示相关调用,尚不能确认它就是根因。Burn 异步执行说明、Tensor 数据读取接口
如果所用版本提供异步读回接口,应使用对应接口,并将异步过程传递到调用方。例如,在已有输出 Tensor 的位置:
let data = output.into_data_async().await;
output 表示已有的模型输出,data 的具体类型和错误处理方式应以项目锁定版本的 API 为准。不要用 block_on 包装这个异步读取,否则仍可能进入同步等待路径。into_data_async 文档
如果等待发生在框架内部的模型执行阶段,仅修改最终输出的读取方式可能无效,还需要根据调用栈继续定位。
对照依赖版本和后端配置
排查时应记录 Rust 工具链、WASM 编译目标、Burn 相关依赖的实际版本、Cargo features 和浏览器版本。依赖版本以 Cargo.lock 中解析出的版本为准,并让 ONNX 导入生成的代码与运行时使用兼容的依赖组合。
如果所用版本启用了 fusion,可以按该版本支持的配置方式,比较启用和关闭 fusion 的结果。burn-wgpu 0.22.1 文档提示,WASM 环境有时需要关闭这一功能。这可以作为排查依据,但不能直接套用到版本未知的项目,也不能保证解决本例。Wgpu 的 fusion 说明
如果关闭 fusion 后能够运行,只能说明故障范围进一步缩小到了相关执行路径,仍需通过最小复现确定具体原因。
初始化标志也有一个潜在问题
代码在 init_setup_async(...).await 完成前,就通过 compare_exchange 将 WGPU_INITIALIZED 设置为 true。
如果其他模型实例在初始化结束前进入该函数,就可能跳过初始化并提前使用后端。单个实例上的 &mut self 不能协调其他实例对全局状态的访问。
如果应用确实存在并发调用,应让后续调用等待同一个初始化任务完成,并区分“未初始化”“初始化中”和“已就绪”。单纯更改原子内存序不能解决这个时序问题。目前没有证据表明它导致了这次 panic,应将它作为单独的代码隐患处理。
如何继续缩小到模型或算子问题?
保留可运行的 SqueezeNet 作为对照,再用 MobileNetV2 的较小子图复现,并在读取中间结果时使用所用版本支持的异步方式。只有定位到具体节点、输入形状和失败调用,才能判断是算子实现、执行优化、同步机制还是其他后端问题。
在提供的条件下,NdArray 是已经观察到可运行的临时兜底方案。WebGPU 路径的确定修复仍取决于首个 panic 的调用栈、inference 实现、生成的模型代码及实际依赖版本,不能仅凭当前日志给出保证有效的补丁。
备注:内容仅供参考。
