Rust in Everything
基于 burn 0.22.1

怎么读这一卷

主线位置:三条主线的总览。本页把算子主线、求导主线、训练主线各列一遍,标出每一步落在哪个函数上、哪一页讲它;再讲 Cargo feature 怎么决定这三条线上有哪些层:burn 的 feature 一路传到 burn-dispatch,它的构建脚本发出 backend_enabled、cube_backend、executing_backend 三个 cfg。「一次 matmul」和「一个训练步」用实验把算子主线和训练主线各走一遍。

flowchart TB
  start[从这里开始] --> q{读这一卷为了什么}
  q -->|会用| use1[01 全景]
  use1 --> use2["02 张量 和 07 模块"]
  q -->|读懂实现| all[00 到 11 按顺序]
  q -->|只关心 GPU| gpu["05 融合 再到 06 CubeCL"]
  q -->|自己写一个| hands["04 自动微分 再到 11 动手"]

锁定版本

项本卷说明
burn 工作区0.22.1,tag v0.22.1,commit 907ab111a(2026-10-20)工作区所有 crate 同一个版本号([workspace.package] version = "0.22.1");burn 的 rust-version 是 1.95,edition 2024
cubecl0.11.3,tag v0.11.3,commit b26114988(2026-10-22)burn 工作区写的是 cubecl = "0.11.1",新项目解析到 0.11.3
cubek0.3.1,tag v0.3.1,commit 245851cc(2026-10-20)matmul、reduce、卷积、attention、随机数这些现成的 cube 内核
实验依赖里的 wgpu / gemm / serde / tracing30.0.1 / 0.19.0 / 1.0.229 / 0.1.44锁文件里实际解析到的版本
上一版v0.22.0(2026-10-06)、v0.21.0(2026-05-07)0.22.1 是 0.22.0 之后的补丁版

本卷的版本组合不是挑出来的:一个新 crate 写 burn = { version = "=0.22.1", features = ["flex", "metal"] },cargo 解析出来就是这一组,依赖树里一共 396 个包。源码链接一律指向 tag:https://github.com/tracel-ai/burn/blob/v0.22.1/crates/...、https://github.com/tracel-ai/cubecl/blob/v0.11.3/crates/...、https://github.com/tracel-ai/cubek/blob/v0.3.1/crates/...。burn 仓库没有 CHANGELOG,合并都是 squash,提交标题末尾的 (#NNNN) 就是 PR 号,本卷的「冷知识」都从这里查。

实测平台是 M2 Max、macOS 27、rustc 1.99.0。GPU 只实测了 Metal(经 wgpu);CUDA、ROCm、Vulkan、WebGPU 只读源码,凡是只读了源码的地方都会写明。cpu 这个 feature(cubecl 的 CPU 运行时,经 MLIR / LLVM)本卷没有构建:它的构建脚本要联网下载 LLVM。

0.22 改了什么

0.22 是一次大改。#4717(2026-05-13,首次发布于 v0.22.0-pre.1)的标题是「[Breaking] Remove Tensor backend generic and add high-level Device struct」:

0.210.22
张量类型Tensor<B, const D: usize, K = Float>Tensor<const D: usize, K = Float>
选后端类型:type Backend = Autodiff<Wgpu>值:Device::wgpu(..).autodiff()
模型struct Mlp<B: Backend>,字段 Linear<B>struct Mlp,字段 Linear
多后端并存Router<(Wgpu, Flex)> 这类组合类型一个全局后端 Dispatch,按设备在运行期 match
能不能对非 autodiff 后端调 backward编译期拒绝运行期检查

0.21 的 README 写的是 type Backend = Autodiff<Wgpu>;,并说「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」。用户代码里不再有后端类型参数,后端由张量所在的 Device 决定,所有张量都走 burn-dispatch 的 Dispatch。同一版里 burn-ndarray(#5344)和 burn-tch(#5525)标成弃用,CPU 主推 burn-flex。

网上的大量例子(以及 burn 自己的一些文档注释,比如 Dispatch 上的示例还写着 Tensor::<Dispatch, 2>)是旧写法。0.21 的写法在 0.22.1 下的报错(intro-old,只开 flex):

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}");
}
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);
  |                    +      +

use burn::backend::Flex 本身仍然能编译,Flex 这个类型还在,只是不能再放进 Tensor 的参数里。rustc 的建议(加大括号)是错的方向。

三条主线

全卷沿三条线展开,每页开头都会标出本页在哪条主线上。

算子主线:一次 a.matmul(b)

flowchart TB
  user["Tensor matmul 泛型方法"] --> num["Float 的 Numeric matmul"]
  num --> disp["Dispatch float_matmul"]
  disp -->|设备是 autodiff| ad["Autodiff 记节点"]
  disp -->|Flex 设备| flex["Flex 的 gemm"]
  ad --> fus["Fusion 记 OperationIr"]
  disp -->|GPU 设备| fus
  fus -->|into_data 时| drain["融合流 drain"]
  drain --> cube["CubeBackend matmul"]
  cube --> tune["autotune 选 cubek 内核"]
  tune --> launch["cubecl 编译并提交"]
  launch --> read["异步读回"]
步骤源码讲它的页
1. Tensor::matmul 先 TensorCheck::matmul 查形状,再调 K::matmul(self.primitive, other.primitive);泛型方法只做检查和形状计算burn-tensor/src/tensor/api/numeric.rsTensor 与 Kind、检查与报错
2. Float 的 Numeric::matmul 不泛型,只经手 BridgeTensor:拆出种类,两个都是 Float 就调 Dispatch::float_matmulburn-tensor/src/bridge/ops/float.rsBridgeTensor 与 *_impl
3. Dispatch::float_matmul 的函数体只写 B::float_matmul(lhs, rhs),#[backend_dispatch] 把它展开成按 DispatchTensor 种类和设备的 matchburn-dispatch/src/ops/tensor.rs、macros.rsDispatch
4. 设备是 .autodiff() 的,先进 Autodiff::float_matmul:登记一个 Matmul 反向步骤,再调内层后端burn-autodiff/src/ops/tensor.rs一个算子的反向
5. GPU 设备上内层是 Fusion<CubeBackend>:Fusion::float_matmul 把调用记成 OperationIr::Float(.., FloatOperationIr::Matmul(..)),GlobalFusionClient::register 送进流,立刻返回burn-fusion/src/ops/tensor.rs、client.rsFusion 装饰器、burn-ir
6. 读数据时(into_data)FusionServer::drain_stream 把流里攒下的操作切块、能融合的生成融合内核,不能融合的逐个执行burn-fusion/src/server.rs、stream/、burn-cubecl-fusion融合搜索、生成融合内核
7. matmul 落到 CubeBackend::float_matmul → kernel::matmul::matmul → matmul_autotune,autotune 在 cubek 的一组 matmul 内核里挑最快的burn-cubecl/src/kernel/matmul/burn-cubecl 后端、autotune
8. cubek 的内核是 #[cube] 函数,cubecl 把它展开成 IR、在 Metal 上编成 MSL,Client::launch 提交cubek cubek-matmul、cubecl cubecl-runtime、cubecl-wgpu#[cube] 内核、运行时与 ComputeClient、wgpu 与 Metal
9. into_data 内部是一个 async fn float_into_data,同步 API 用 cubecl 的 block_on(futures_lite)等它burn-cubecl/src/ops/tensor.rs、cubecl cubecl-environment/src/future/base.rsBackend trait
CPU:同一个调用在 Flex 设备上直接进 Flex::float_matmul → matmul::matmul → gemm crate,返回时已经算完burn-flex/src/ops/matmul.rsFlex:CPU 后端

第 5 步和第 6 步的分界值得记住:在 GPU 上,a.matmul(b) 返回时什么都没算,内核是在之后的 into_data(或 sync)里、在 cubecl 起的另一个线程上提交的。实验见一次 matmul。

求导主线:一次 loss.backward()

flowchart TB
  dev["Device autodiff"] --> leaf["require_grad 的叶子"]
  leaf --> fwd["每个 op 登记 Backward 步骤"]
  fwd --> bw["Tensor backward"]
  bw --> server["AutodiffServer backward"]
  server --> topo["收集步骤 按序执行"]
  topo --> grads["Gradients 按节点 id"]
  grads --> gp["GradientsParams 按参数 id"]
步骤源码讲它的页
1. Device::autodiff 把设备包成 DispatchDevice::Autodiff,在它上面建的浮点张量是 AutodiffTensor:内层原语加一个图节点burn-tensor/src/device.rs、burn-autodiff/src/tensor.rsAutodiff 装饰器
2. 每个可求导的 op 定义一个实现 Backward 的步骤,prepare 之后由 OpsKind 分成 Tracked / UnTracked,只在有父节点要梯度时保存状态burn-autodiff/src/ops/一个算子的反向
3. Tensor::backward → AutodiffServer::backward:从根节点收集步骤、按顺序执行,用完的节点清掉burn-autodiff/src/runtime/、graph/traversal.rsbackward 与 Gradients
4. 梯度累加进 Gradients,按节点 id 存;x.grad(&grads) 按 id 取burn-autodiff/src/grads.rsbackward 与 Gradients
5. 想用重算换内存时,Device::gradient_checkpointing() 换成平衡检查点策略,部分 op 只记怎么重算burn-autodiff/src/checkpoint/梯度检查点
6. GradientsParams::from_grads 遍历模块,把节点 id 上的梯度换成参数 id 上的梯度burn-optim/src/optim/grads.rs优化器

训练主线:一个 step

flowchart TB
  derive["derive Module 生成 visit 和 map"] --> fwd["forward 走算子主线"]
  fwd --> loss["loss backward 走求导主线"]
  loss --> gp["GradientsParams from_grads"]
  gp --> step["ModuleOptimizer step"]
  step --> mapper["按参数 id 取梯度和状态"]
  mapper --> newp["换上新的 Param"]
  newp --> learner["Learner 包住循环"]
步骤源码讲它的页
1. #[derive(Module)] 为结构体生成 Module 的 visit / map 等遍历;参数是 Param<T>,带一个 ParamIdburn-derive/src/module/、burn-core/src/module/Module 派生、Config
2. forward 是普通方法,Linear 这些模块调张量算子,走算子主线burn-nn/src/modules/nn 模块
3. loss.backward() 走求导主线,GradientsParams::from_grads 换成参数 idburn-optim/src/optim/grads.rs优化器
4. ModuleOptimizer::step 用一个 ModuleMapper 遍历模块,map_float 里按参数 id 取梯度和状态,调 Adam::step 算出新张量burn-optim/src/optim/module/、adam.rs优化器
5. 存取模型:into_record / save_file,burn-store 的 burnpack、safetensors、PyTorch 导入burn-core/src/store/、burn-store/src/存储与加载
6. Learner 把上面的循环、数据加载、指标和 checkpoint 包起来burn-train/src/learner/Learner 与数据

手写一遍这五步、不用 Learner 的实验见一个训练步。

feature 与 cfg

burn 的 feature

crates/burn/Cargo.toml 的 [features] 把每个 feature 转给 burn-core,burn-core 转给 burn-tensor,burn-tensor 转给 burn-dispatch,后者才真正决定依赖哪些后端 crate。和本卷有关的:

feature默认打开后多了什么讲它的页
std是标准库;关掉是 no_std + alloc(只有 Flex 支持)源码地图
optim是burn-optim,并且打开 autodiff优化器
autodiff是(经 optim)burn-dispatch 依赖 burn-autodiff,Device::autodiff() 可用Autodiff 装饰器
flex否纯 Rust 的 CPU 后端 burn-flex,Device::flex()Flex:CPU 后端
wgpu否cubecl 的 wgpu 运行时,Device::wgpu(..),图形 API 由平台决定wgpu 与 Metal
metal / vulkan / webgpu否都隐含 wgpu,再固定一种 API:Device::metal(..) 等。metal 在 cubecl 里是 metal = ["wgpu-msl"],走 wgpu 加 MSL 编译器,不是 cubecl-metalwgpu 与 Metal
cuda / rocm否cubecl 的 CUDA / HIP 运行时(本卷只读源码)源码地图
cpu否cubecl 的 CPU 运行时(MLIR / LLVM),构建脚本联网下载 LLVM,本卷不构建—
ndarray / tch否弃用的两个后端Flex:CPU 后端
fusion否,但见下文打开 burn-fusion,cubecl 后端套上 FusionFusion 装饰器
autotune否,但见下文cubecl 后端第一次遇到某个形状时跑一遍候选内核挑最快的autotune
persistence否显式要求 cubecl 的持久缓存;见下文,cubecl 默认已开autotune
train否burn-train(Learner、指标),连带 dataset 和 optimLearner 与数据
store / safetensors / pytorch否burn-store 和两种导入格式存储与加载
tracing否各后端编进 tracing 的 span(from_data、into_data、内核提交、内存池),见 tracing 卷一次 matmul

默认 feature 里没有任何执行后端。crates/burn/src/lib.rs 写得很直接:「Default features include std and optim (and therefore autodiff), but no execution backend.」只写 burn = "0.22.1" 能编译,但 Device::default() 会 panic,见 Device。

表里 fusion 和 autotune 标的是「否,但见下文」:burn 自己的默认 feature 列表里没有它们,可只要默认 feature 开着,它们就会被打开。默认 feature 沿 burn-core/default → burn-tensor/default → burn-dispatch/default(含 burn-cubecl?/default)传到 burn-cubecl,而 burn-cubecl 的默认 feature 是 autotune、std、fusion。用 cargo tree -e features 对三种写法看 burn-cubecl 最终打开了哪些 feature:

## features = ["metal"]
burn-cubecl feature "autotune" ... "fusion" "metal" "std" "wgpu"     依赖树里 burn-fusion: 有
## default-features = false, features = ["std", "metal"]
burn-cubecl feature "metal" "std" "wgpu"                             依赖树里 burn-fusion: 无
## default-features = false, features = ["std", "metal", "fusion", "autotune"]
burn-cubecl feature "autotune" ... "fusion" "metal" "std" "wgpu"     依赖树里 burn-fusion: 有

(每行把同一 crate 的 feature 合并到一行,... 处省略了 burn-cubecl-fusion、burn-fusion、default 这几个由 fusion 带出来的项。)关掉默认 feature 的同时,融合和 autotune 也悄悄关了:用第二种写法在 Metal 设备上打印 Backend::name,得到的是 dispatch<cubecl<wgpu<msl>>>,比默认写法的 dispatch<fusion<cubecl<wgpu<msl>>>> 少了 fusion 一层(打印方法见一次 matmul)。

📝NOTE
没有蠢问题

问: 我只写了 features = ["metal"],没写 fusion,为什么一次 matmul 里打出来的后端名是 dispatch<fusion<cubecl<wgpu<msl>>>>?

答: 因为 fusion 是 burn-cubecl 的默认 feature,而 burn 的默认 feature 会一路把 burn-cubecl?/default 打开(? 表示「如果这个可选依赖被别的 feature 拉进来了」)。burn 的 fusion feature 的作用是在关掉默认 feature 时把它补回来。cubecl 的持久缓存同理:cubecl-runtime 的默认 feature 里就有 persistence,所以不开 burn/persistence,autotune 的结果照样写到磁盘上(位置见一次 matmul)。

burn-dispatch 的三个 cfg

feature 一路传下来,最终由 burn-dispatch/build.rs 归并成三个 cfg:

    let cube = cuda || rocm || cpu || metal || vulkan || webgpu || wgpu;
    let local = cube || flex || ndarray || tch;
    let executing = local || remote;

    // Backend-free builds expose tensor/model APIs without installing an execution backend.
    if executing || capture {
        println!("cargo::rustc-cfg=backend_enabled");
    }

    // Every cubecl-backed feature selects the same backend type, since the device says which
    // runtime, so they share one variant under one cfg rather than a seven-way list at each use.
    if cube {
        println!("cargo::rustc-cfg=cube_backend");
    }

    // Capture records operations but never executes them.
    if executing {
        println!("cargo::rustc-cfg=executing_backend");
    }
cfg什么时候有在源码里管什么
cube_backend七个 cubecl feature 任一DispatchDevice::Cube、DispatchTensorKind::Cube 这些变体,backends::Cube 的再导出。七种 GPU / CPU 运行时共用这一个变体,运行时是哪一种由设备值决定
backend_enabled有任何能执行的后端,或者开了 capture没有它时 NoBackend(一个含 Infallible 的结构体)占位,生成的 match 分支变成发散的 never.unreachable(),lib.rs 顶部压掉一串 unused 警告
executing_backend有能执行的后端(本地或 remote)capture 只记录不执行;只开 capture 时有 backend_enabled 而没有 executing_backend

这三个 cfg 只对 burn-dispatch 这一个 crate 生效(构建脚本发的 rustc-cfg 不会传给依赖它的 crate)。过程宏 crate burn-backend-extension 的后端目录 BACKENDS 把 "cube_backend"、"feature = \"flex\"" 这些写成字符串,展开成 [Cube, cube_backend] 这样的列表交给 burn-dispatch 里的 macro_rules!,生成的每个分支带一个 #[cfg(..)];Dispatch 怎么用它们生成 match,见 Dispatch。三种实验配置下构建脚本的输出(target/debug/build/burn-dispatch-*/output,只留 rustc-cfg 行):

# features = ["flex", "metal"]
cargo::rustc-cfg=backend_enabled
cargo::rustc-cfg=cube_backend
cargo::rustc-cfg=executing_backend

# features = ["flex"]
cargo::rustc-cfg=backend_enabled
cargo::rustc-cfg=executing_backend

# 默认 feature(没有后端)
(没有 rustc-cfg 行,只有三行 rustc-check-cfg)

没选后端时编出来的是一个「只有 API、不能执行」的 burn,Device::default() 在运行期 panic;0.22.0 之前它会悄悄编进 Flex,这段来历见 Dispatch 里讲 NoBackend 的一节。

cube_backend 也是 0.22 才有的:#5556(首次发布于 v0.22.0-pre.4)把 cubecl 的运行时从类型里拿掉(「runtime erasure」)之后,CUDA、Metal、Vulkan 这些不再是各自的后端类型,于是归并成一个 cfg。0.21 时 burn-dispatch 里 metal、vulkan、webgpu 还是互斥的三个后端,见源码地图。

阅读路线

前置知识

  • Rust 的 trait、关联类型、const 泛型:Tensor<const D: usize, K> 的秩是 const 参数,后端接口是一组 trait,见 Backend trait。
  • 一点深度学习:张量、矩阵乘、反向传播(backpropagation)、优化器。不需要会 PyTorch,但会的话对照起来更快,见 burn 解决什么问题。
  • async:GPU 读回是 async fn,同步 API 在里面 block_on,future 的基本概念见 tokio 卷(burn 本身不依赖 tokio)。
  • serde:Config 和模型记录走 serde,见 serde 卷。
  • tracing:tracing feature 打开的是 span,要配 subscriber 才看得到,见 tracing 卷。

小结

  • 全卷对着 burn 0.22.1、cubecl 0.11.3、cubek 0.3.1 写,这是新项目写 burn = "=0.22.1" 时解析出来的组合;GPU 只实测 Metal。
  • 0.22 去掉了 Tensor 的后端类型参数(#4717):后端由 Device 的值决定,所有调用经过全局后端 Dispatch;backward 的合法性从编译期检查变成运行期检查。
  • 算子主线:Tensor::matmul → 不泛型的 Numeric::matmul → Dispatch 的 match →(Autodiff)→ Fusion 记 IR → 读数据时 drain → CubeBackend + autotune + cubek 内核 → cubecl 编译提交;CPU 上落到 Flex 的 gemm。
  • 求导主线:Device::autodiff → 每个 op 登记 Backward 步骤 → AutodiffServer::backward 按序执行 → Gradients → GradientsParams。
  • 训练主线:derive(Module) → forward → backward → ModuleOptimizer::step 按参数 id 更新 → Learner。
  • 默认 feature 不含后端;但只要默认 feature 开着,cubecl 后端就自带 fusion 和 autotune。feature 最终在 burn-dispatch 里归并成 backend_enabled、cube_backend、executing_backend 三个 cfg。

💬 关联讨论

发起讨论
加载中...