Rust in Everything
基于 burn 0.22.1

type provided when a constant was expected

主线位置:三条主线的起点之前,代码还没编译过去。0.22 把后端从 Tensor 的类型参数里拿掉(怎么读这一卷 的「0.22 改了什么」),算子主线从 Tensor<D, K> 开始、按设备分派(Dispatch),训练主线上的模块也不再带 B。照 0.21 的教程写,编译器在第一行就拦下。

flowchart TB
  old["0.21 写法 Tensor B D"] --> rustc["rustc 按 Tensor D K 解析"]
  rustc --> e747["E0747 第一个参数要常量"]
  oldm["Mlp B: Backend"] --> e405["E0405 找不到 Backend"]
  oldl["Linear Flex"] --> e107["E0107 多了泛型参数"]
  e747 --> fix["删掉 B,改传 Device"]
  e405 --> fix
  e107 --> fix

本页实验 crate pa-old(只开 flex,debug 构建),平台 M2 Max、macOS 27、rustc 1.99.0。

现象

网上多数例子和 0.21 的 burn book 都是这种写法,后端是 Tensor 的第一个类型参数(src/main.rs):

use burn::backend::Flex;
use burn::tensor::Tensor;

fn main() {
  let device = Default::default();
  let a = Tensor::<Flex, 2>::ones([2, 2], &device);
  println!("{a}");
}
   Compiling pa-old v0.1.0 (~/exp-burn/pa-old)
error[E0747]: type provided when a constant was expected
 --> src/main.rs:6:20
  |
6 |   let a = Tensor::<Flex, 2>::ones([2, 2], &device);
  |                    ^^^^
  |
help: if this generic argument was intended as a const parameter, surround it with braces
  |
6 |   let a = Tensor::<{ Flex }, 2>::ones([2, 2], &device);
  |                    +      +

For more information about this error, try `rustc --explain E0747`.
error: could not compile `pa-old` (bin "pa-old") due to 1 previous error

use burn::backend::Flex 没有报错,Flex 这个类型还在,只是不能放进 Tensor 了。rustc 的 help(加大括号)方向是错的,照做只会换一条错误。

模块是同样的情况。0.21 的 burn book 用 burn::prelude::* 拿到 Backend,模型带一个 B(src/bin/old_module.rs):

use burn::nn::{Linear, LinearConfig};
use burn::prelude::*;

#[derive(Module, Debug)]
struct Mlp<B: Backend> {
  fc: Linear<B>,
}

impl<B: Backend> Mlp<B> {
  fn new(device: &B::Device) -> Self {
    Self { fc: LinearConfig::new(3, 2).init(device) }
  }

  fn forward(&self, x: Tensor<B, 2>) -> Tensor<B, 2> {
    self.fc.forward(x)
  }
}
   Compiling pa-old v0.1.0 (~/exp-burn/pa-old)
error[E0405]: cannot find trait `Backend` in this scope
 --> src/bin/old_module.rs:5:15
  |
5 | struct Mlp<B: Backend> {
  |               ^^^^^^^ not found in this scope

error[E0405]: cannot find trait `Backend` in this scope
 --> src/bin/old_module.rs:9:9
  |
9 | impl<B: Backend> Mlp<B> {
  |         ^^^^^^^ not found in this scope

For more information about this error, try `rustc --explain E0405`.
error: could not compile `pa-old` (bin "old_module") due to 2 previous errors

只报了 Backend,后面 Tensor<B, 2> 的问题被这两条挡住还没露出来。把后端写死成 Linear<Flex> 也不行(src/bin/old_linear.rs):

error[E0107]: struct takes 0 generic arguments but 1 generic argument was supplied
  --> src/bin/old_linear.rs:7:7
   |
 7 |   fc: Linear<Flex>,
   |       ^^^^^^------ help: remove the unnecessary generics
   |       |
   |       expected 0 generic arguments
   |
note: struct defined here, with 0 generic parameters
  --> ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/burn-nn-0.22.1/src/modules/linear.rs:48:12
   |
48 | pub struct Linear {
   |            ^^^^^^

出处

0.22.1 的 Tensor 是 Tensor<const D: usize, K = Float>:第一个参数是常量秩。rustc 看到 Flex 出现在常量位置,就猜你想写一个常量表达式,于是建议加大括号。后端改由张量所在的 Device 决定,运行期经 Dispatch 分派,见 Tensor 与 Kind 和 Device。

prelude 里的 Backend 也是同一个 PR 拿掉的。0.21 的 burn-core prelude 是:

        tensor::{
            Bool, Device, ElementConversion, Float, Int, Shape, SliceArg, Tensor, TensorData,
            backend::Backend, cast::ToElement, s,
        },

0.22.1 的 prelude 里没有了 backend::Backend,多了 DeviceIndex、DeviceKind、assert_shape。git log -S 指向 #4717(「[Breaking] Remove Tensor backend generic and add high-level Device struct」)。Linear 等 burn-nn 模块同样没有泛型参数,#[derive(Module)] 生成的代码也不再要 B(Module 派生)。

旧写法不只在网上。burn 自己有几处文档注释还停在 0.21:

位置内容
burn-flex/src/lib.rs:21let tensor: Tensor<Flex, 2> = Tensor::from_data(...)
burn-dispatch/src/backend.rs:163Tensor::<Dispatch, 2>::zeros([128, 128], &device)
burn-backend/src/backend/device.rs:186Tensor::<B, 2>::zeros([2, 3], &device)

三处都在标了 ignore 的代码块里,doctest 不编译它们,所以改 API 时没有被发现。

修法

删掉所有后端类型参数;设备从 B::Device 换成 burn::tensor::Device,后端在构造设备时选(src/bin/new_tensor.rs、src/bin/new_module.rs):

use burn::tensor::{Device, Tensor};

fn main() {
  let device = Device::flex();
  let a = Tensor::<2>::ones([2, 2], &device);
  println!("{a}");
}
use burn::module::Module;
use burn::nn::{Linear, LinearConfig};
use burn::tensor::{Device, Tensor};

#[derive(Module, Debug)]
struct Mlp {
  fc: Linear,
}

impl Mlp {
  fn new(device: &Device) -> Self {
    Self { fc: LinearConfig::new(3, 2).init(device) }
  }

  fn forward(&self, x: Tensor<2>) -> Tensor<2> {
    self.fc.forward(x)
  }
}

fn main() {
  let device = Device::flex();
  let mlp = Mlp::new(&device);
  let x = Tensor::<2>::ones([4, 3], &device);
  let y = mlp.forward(x);
  println!("y dims {:?} on {:?}", y.dims(), y.device());
  println!("params {}", mlp.num_params());
}
Tensor {
  data:
[[1.0, 1.0],
 [1.0, 1.0]],
  shape:  [2, 2],
  device:  Device<Flex(Cpu)>,
  kind:  "Float",
  dtype:  "f32",
}
y dims [4, 2] on Device<Flex(Cpu)>
params 8

对照表:

0.210.22.1
Tensor<B, D, K>Tensor<D, K>
struct Mlp<B: Backend>、Linear<B>struct Mlp、Linear
device: &B::Devicedevice: &Device
type B = Autodiff<Wgpu>Device::wgpu(..).autodiff()(Autodiff 装饰器)
Default::default() 推断出后端的设备显式 Device::flex() / Device::metal(..);Device::default() 按 feature 选,见 No execution backend is enabled
📝NOTE
没有蠢问题

问: 删掉 B 之后,模型代码就不能在编译期区分「能求导」和「不能求导」了,这不是退步吗?

答: 是有意的取舍。0.21 的 README 说把后端放进类型里「it is impossible to make the mistake of calling backward on a model that runs on a backend that does not support autodiff」;0.22.1 的 README 改成「backward() checks graph participation at runtime」。换来的是同一个模型类型能在运行期换设备、下游不用为每个后端单态化一遍(编译时间的理由见 BridgeTensor 与 *_impl)。代价是一部分错误从编译期挪到了运行期(比如在不带 .autodiff() 的设备上调 backward),两边的权衡见设计取舍。

怎么确认没中招

  • 在代码里搜 <B: Backend>、<B, 、B::Device、AutodiffBackend,全部改掉;不要照 rustc 的「surround it with braces」去改。
  • 看 Cargo.lock 里 burn 的版本,再看你参照的例子写于哪个版本:带 B 的都是 0.21 及更早。
  • burn 自己的文档注释也可能是旧的:以 tag 里的类型定义为准(Tensor 的定义在 burn-tensor/src/tensor/api/base.rs)。

小结

  • 0.22 起 Tensor 的第一个参数是秩,Tensor<Flex, 2> 报 E0747,rustc 的加括号建议是误导。
  • prelude 里没有 Backend 了(E0405),Linear 等模块不带泛型(E0107)。
  • 修法是删掉所有 B,用 Device 值选后端:Device::flex()、Device::metal(..)、.autodiff()。
  • burn-flex、burn-dispatch、burn-backend 里还有旧写法的文档示例,放在 ignore 块里,doctest 发现不了。

💬 关联讨论

发起讨论
加载中...