Rust in Everything
基于 burn 0.22.1

开了 cpu feature,离线构建失败

主线位置:算子主线的最底层还没造出来。cpu 是 cubecl 的 CPU 运行时,和 Metal、CUDA 一样落在 Dispatch 的 Cube 变体下(Dispatch),内核经 MLIR / LLVM 编成本机代码。它需要一份 LLVM,这份 LLVM 是在 cargo build 时从网上下载的。

flowchart TB
  feat["burn cpu feature"] --> cc["burn-cubecl cpu"]
  cc --> ccpu["cubecl-cpu"]
  ccpu --> llvm["cubecl-llvm"]
  llvm --> bun["构建依赖 tracel-llvm-bundler"]
  bun --> sent{"本机已装过 LLVM"}
  sent -->|是| ok["跳过下载"]
  sent -->|否| dl["联网下载 checksum 和 LLVM"]
  dl -->|离线| pnc["build.rs:66 unwrap panic"]

本卷不构建 cpu(写作说明里禁止开启)。本页的报错是本卷实验环境第一次误开 cpu 时记下的结果(2026-10-29,burn 0.22.1,tracel-llvm-bundler 23.1.0-4,离线),没有重跑;其余内容来自源码和 cargo tree。M2 Max、macOS 27、rustc 1.99.0。

现象

依赖写成 burn = { version = "=0.22.1", features = [..., "cpu"] },cargo build --offline 在编译 burn 之前就失败。编辑笔记里记下的是:tracel-llvm-bundler 的构建脚本在 build.rs:66:37 panic,消息是 called Result::unwrap() on an Err value: Io(Kind(UnexpectedEof))。

报错里既没有「LLVM」也没有「网络」。--offline 只管 cargo 自己不访问网络;构建脚本是普通的 Rust 程序,它要不要联网 cargo 管不着。

出处

cpu 这个 feature 沿着下面这条链传下去(每一行都是 Cargo.toml 里的 cpu = [...]):

cratefeature 定义
burncpu = ["burn-core/cpu", ...]
burn-corecpu = ["burn-tensor/cpu", "dep:burn-cpu"]
burn-tensorcpu = ["burn-dispatch/cpu", "cubecl-cpu", "cubecl"]
burn-dispatchcpu = ["cubecl", "burn-cubecl/cpu"]
burn-cubeclcpu = ["cubecl/cpu", "burn-backend/cubecl-cpu"]
cubeclcpu = ["cubecl-cpu"]

cubecl-cpu 依赖 cubecl-llvm,后者把 tracel-llvm-bundler 列为构建依赖(工作区里锁的版本是 23.1.0-4)。这个 crate 不在 burn / cubecl 仓库里,下面引的是 cargo 缓存里 tracel-llvm-bundler-23.1.0-4/build.rs 的原文。

构建脚本的 bundle_cache 先看一个哨兵文件,有就直接返回:

pub fn bundle_cache() -> AnyResult<()> {
    let env = Environment::current();
    let llvm_path = llvm_path()?;

    let sentinel = env.sentinel_path(&llvm_path);
    println!("cargo:rerun-if-changed={}", sentinel.display());

    // 0) Already installed?
    if sentinel.exists() {
        return Ok(());
    }
    // ...
    let skip_download = std::env::var_os("TRACEL_LLVM_BUNDLER_SKIP_CHECKSUM_DOWNLOAD").is_some();

    let checksum_path = env.checksum_cache_path()?;
    if !skip_download {
        download_to_path(&env.checksum_url(), &checksum_path)
            .with_context(|| format!("downloading {}", env.checksum_url()))?;
    }
    // ...
}

没有哨兵就去 GitHub release 下载 checksum 文件,再下载 LLVM 包(macos-AArch64.tar.xz 之类)、校验、解压。下载函数第一行就是那个 unwrap,第 66 行:

/// Download (overwriting) to a path.
fn download_to_path(url: &str, dest: &Path) -> AnyResult<()> {
    let req = ureq::get(url).call().unwrap();
    // ...
}

函数本来返回 AnyResult,调用处也写了 with_context,但请求失败在 unwrap 上直接 panic,带 URL 的上下文就没机会出现。消息里的 Io(Kind(UnexpectedEof)) 是离线时 ureq 给出的错误。

几个路径(src/config.rs):

用途位置
解压后的 LLVM 和哨兵 .tracel-llvm-installeddirs::data_local_dir()/tracel/tracel-llvm-23.1.0-4/,macOS 上是 ~/Library/Application Support/tracel/...
下载的压缩包和 checksum~/.cache/tracel/
下载地址https://github.com/tracel-ai/tracel-llvm/releases/download/v23.1.0-4/

那次失败之后,本机这两个目录都已经建出来、但都是空的。

📝NOTE
冷知识:cubecl 的 CPU 运行时曾经只在 Linux 上编译

cubecl 的 #790(「feat: disable CPU backend on non-linux system as LLVM is not yet bundled there」,2025-07,首次发布于 cubecl v0.7.0)把 cubecl-cpu 的依赖改成 [target.'cfg(target_os = "linux")'.dependencies]、lib.rs 加上 #![cfg(target_os = "linux")]:别的系统上还没有打包好的 LLVM。两个月后 #897(「Enable cubecl-cpu on all OSes」,同样首发于 v0.7.0)加上 tracel-llvm 依赖,提交说明里一串「Bump to tracel-llvm 20.1.4-3」「fix caching conflict with bundle install」,从此每个系统都靠构建期下载的 LLVM 包。#1542(v0.11.0-pre.3)又把构建脚本从 cubecl-cpu 挪进新拆出来的 cubecl-llvm,就是现在报错的这一层。

修法

先问自己要的是哪个 CPU 后端:

  • 只是想在 CPU 上跑:用 flex。它是纯 Rust(burn-flex 的 crate 文档写着「Pure Rust (no C dependencies)」),没有构建期下载,Device::flex(),见 Flex:CPU 后端。本卷所有 CPU 实验都用它。
  • 确实要 cubecl 的 CPU 运行时(同一份 #[cube] 内核在 CPU 上跑):在能联网的机器上构建一次,让构建脚本装好 LLVM 并写下哨兵;之后同一台机器离线构建会在第 0 步返回。CI 或离线机器上,把上表的 ~/Library/Application Support/tracel/(Linux 上是 ~/.local/share/tracel/)和 ~/.cache/tracel/ 一起缓存或拷过去。TRACEL_LLVM_BUNDLER_SKIP_CHECKSUM_DOWNLOAD 只跳过 checksum 的下载,它要求 checksum 文件已经在 ~/.cache/tracel/ 里,压缩包缺了照样要下载。

怎么确认没中招

cargo tree -i 看依赖图里有没有这个 crate(本卷的 pa-old 只开 flex):

warning: nothing to print.

To find dependencies that require specific target platforms, try to use option `--target all` first, and then narrow your search scope accordingly.

这条命令加了 --target all,输出说明它不在任何平台的构建里。不要看 Cargo.lock:同一个 pa-old 的 Cargo.lock 里有 name = "tracel-llvm-bundler" 一项,lock 文件会列出可选依赖,开没开要看 cargo tree。如果它出现了,cargo tree -e features -i cubecl-cpu 能找到是谁打开的 cpu(工作区里别的 crate 也可能打开它,cargo 会把 feature 合并)。

小结

  • cpu feature 经 burn-cubecl → cubecl-cpu → cubecl-llvm 引入 tracel-llvm-bundler,它的构建脚本联网下载 LLVM。
  • 离线时 panic 在 build.rs:66 的 ureq::get(url).call().unwrap(),消息只有 Io(Kind(UnexpectedEof)),看不出是在下载。
  • --offline 管不到构建脚本;装过一次后靠 data_local_dir()/tracel/ 下的哨兵跳过下载。
  • 只要 CPU 能跑,用纯 Rust 的 flex。

💬 关联讨论

发起讨论
加载中...