开了 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 = [...]):
| crate | feature 定义 |
|---|---|
burn | cpu = ["burn-core/cpu", ...] |
burn-core | cpu = ["burn-tensor/cpu", "dep:burn-cpu"] |
burn-tensor | cpu = ["burn-dispatch/cpu", "cubecl-cpu", "cubecl"] |
burn-dispatch | cpu = ["cubecl", "burn-cubecl/cpu"] |
burn-cubecl | cpu = ["cubecl/cpu", "burn-backend/cubecl-cpu"] |
cubecl | cpu = ["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-installed | dirs::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/ |
那次失败之后,本机这两个目录都已经建出来、但都是空的。
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 合并)。
小结
cpufeature 经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。