the trait bound X: Module is not satisfied
主线位置:训练主线的起点,
#[derive(Module)]为结构体生成遍历、搬设备、valid/train的代码。宏只看字段的类型名来决定一个字段是子模块、常量还是跳过,规则见 Module 派生 的「字段:子模块、常量和 #[module(skip)]」。
flowchart TB
field["字段 pooling: Pooling"] --> name{"类型名是常量吗"}
name -->|"usize f32 String 等"| constant["常量"]
name -->|"写了 module skip"| skip["跳过"]
name -->|"其他一律"| sub["子模块"]
sub --> need["要求 Module 和 ModuleDisplay"]
need --> e0277["E0277"]
实验 crate exp-burn/pb-pit,bin skip_err、clone_err(故意编译失败)和 skip_ok,debug,M2 Max、macOS 27、rustc 1.99.0。
现象
一个分类头,带一个选择池化方式的枚举:
#[derive(Clone, Debug)]
enum Pooling {
Mean,
Max,
}
#[derive(Module, Debug)]
struct Head {
linear: Linear,
pooling: Pooling,
}
cargo build --bin skip_err(节选,三个错误里的第一个和结尾):
error[E0277]: the trait bound `Pooling: burn::module::Module` is not satisfied
--> src/bin/skip_err.rs:10:10
|
10 | #[derive(Module, Debug)]
| ^^^^^^ unsatisfied trait bound
|
help: the trait `burn::module::Module` is not implemented for `Pooling`
--> src/bin/skip_err.rs:5:1
|
5 | enum Pooling {
| ^^^^^^^^^^^^
= note: If `Pooling` is used as configuration or runtime state in a `#[derive(Module)]` type, mark that field with `#[module(skip)]`.
= help: the following other types implement trait `burn::module::Module`:
(L0, L1)
(L0, L1, L2)
...
and 124 others
= note: this error originates in the derive macro `Module` (in Nightly builds, run with -Z macro-backtrace for more info)
...
error: could not compile `pb-pit` (bin "skip_err") due to 10 previous errors
另外两个错误分别指向字段那一行(同样是 Module)和 ModuleDisplay,每个都带同一句 note。rustc 说有 10 个错误,只展开了 3 个,其余是重复的。
0.21 的代码升级到 0.22 时最容易遇到它:那时字段类型里没有 B 的自定义类型会被自动当成配置,0.22 没有后端参数了,这条判断不复存在(见 type provided when a constant was expected)。
另一个顺手加上去就会报错的写法:
#[derive(Module, Clone, Debug)]
struct Head {
linear: Linear,
}
error[E0119]: conflicting implementations of trait `std::clone::Clone` for type `Head`
--> src/bin/clone_err.rs:4:18
|
4 | #[derive(Module, Clone, Debug)]
| ------ ^^^^^ conflicting implementation for `Head`
| |
| first implementation here
For more information about this error, try `rustc --explain E0119`.
error: could not compile `pb-pit` (bin "clone_err") due to 1 previous error
出处
派生宏的 parse_module_field_type 按类型路径的名字分类:bool、整数、浮点、String 和由它们组成的容器算常量,写了 #[module(skip)] 的跳过,其余全部当子模块,生成的代码对它调用 Module 和 ModuleDisplay 的方法。宏在展开时拿不到 trait 实现的信息,只能这样按名字分;Pooling 不在常量名单里,就成了「子模块」,编译器随后发现它没有实现 Module。
那句 note 来自 Module 和 ModuleDisplay 上的 #[diagnostic::on_unimplemented],0.22.1 才加上(Module 派生 的冷知识);0.22.0 只报 trait 不满足。
E0119 的原因:Module 要求 Clone,派生宏自己生成了一个 impl Clone(codegen.rs,结构体的实现在 codegen_struct.rs 的 gen_clone),再写 derive(Clone) 就是两个实现。
修法
字段上标 #[module(skip)],Pooling 只需要 Clone + Debug + Send(bin skip_ok):
#[derive(Module, Debug)]
struct Head {
linear: Linear,
#[module(skip)]
pooling: Pooling,
}
impl Head {
fn forward(&self, x: Tensor<3>) -> Tensor<2> {
let pooled = match self.pooling {
Pooling::Mean => x.mean_dim(1),
Pooling::Max => x.max_dim(1),
};
self.linear.forward(pooled.squeeze_dim(1))
}
}
fn main() {
let device = Device::flex();
let head = Head { linear: LinearConfig::new(4, 2).init(&device), pooling: Pooling::Max };
let out = head.forward(Tensor::<3>::ones([3, 5, 4], &device));
println!("params {}, out {:?}", head.num_params(), out.dims());
// the skipped field survives the module transforms, untouched
let moved = head.valid().to_device(&Device::flex().autodiff());
println!("after valid().to_device(..): pooling = {:?}", moved.pooling);
println!("{moved}");
}
params 10, out [3, 2]
after valid().to_device(..): pooling = Max
Head {
linear: Linear {d_input: 4, d_output: 2, bias: true, params: 10}
pooling: Max
params: 10
}
参数个数只算 Linear 的 10 个;valid()、to_device 之后 pooling 原样保留,显示时用它的 Debug。跳过的字段也不进保存的记录:要随模型一起保存的配置,放进模型的 Config(见 Config),加载时由配置重建。
E0119 的修法是删掉 Clone,#[derive(Module, Debug)] 就够了。
怎么确认没中招
- 0.21 的代码升级时,把每个
#[derive(Module)]结构体里不是子模块、不是Param、也不是基本类型的字段都列出来,逐个决定是#[module(skip)]还是挪到Config。 {model}打印的模块树里,跳过的字段显示成Debug的样子,子模块显示参数个数,一眼能看出宏把哪个字段当成了什么。
小结
- 0.22 的
#[derive(Module)]按类型名分字段,认不出的一律当子模块;配置枚举、运行状态要#[module(skip)]。 - 0.22.1 的错误信息里有一句
note直接提示加#[module(skip)]。 derive(Module)自带Clone,再写derive(Clone)是 E0119。