Rust in Everything
基于 candle-core 0.11.0

源码地图

主线位置:三条主线经过的代码分布在哪些 crate 里。算子主线几乎全在 candle-core,Metal 那一支下到 candle-metal-kernels;求导主线在 candle-core 的 backprop.rs,优化器在 candle-nn;推理主线从 candle-core/src/quantized/ 读 GGUF,经 candle-nn 的层和融合算子,到 candle-transformers 的模型文件。

flowchart TB
  app["应用 crate"] --> tr["candle-transformers 模型"]
  app --> nn["candle-nn 层 优化器"]
  tr --> nn
  nn --> core["candle-core"]
  tr --> core
  core --> mk["candle-metal-kernels"]
  core -.->|cuda| ck["candle-kernels"]
  core -.->|ug| ug["candle-ug"]
  core --> gemm["gemm crate"]
  core --> tok["tokenizers"]
  mk --> objc["objc2-metal"]

行数是 git show 0.11.0:<path> | wc -l 数的行数(含注释和空行),.rs 不含 tests/、benches/、examples/ 目录,但含 src/ 里的 tests.rs,只用来比较各块的大小。

crate 分层

工作区的 members 是 candle-core、candle-nn、candle-transformers、candle-datasets、candle-examples、candle-pyo3、candle-ug、wasm 例子和测试、tensor-tools;candle-metal-kernels、candle-kernels、candle-onnx、candle-flash-attn、candle-flash-attn-v3 在 exclude 里,单独发布,版本号同样是 0.11.0。

crate行数入口做什么讲它的页
candle-core39494src/tensor.rs(3116)、storage.rs、backprop.rs、quantized/张量、布局、dtype 与设备、Storage 和三个后端、自动微分、safetensors / npy / pickle、量化与 GGUF02 张量、03 后端、04 自动微分、06 量化
candle-metal-kernels8198 Rust + 16942 .metalsrc/kernel.rs 的 Kernels、metal/commands.rs、kernels/*.rs、metal_src/*.metalMetal 设备、命令缓冲区、管线缓存的薄封装(经 objc2-metal),每类内核一个 call_* 函数,内核本身是 .metal 源码Metal 后端
candle-kernels799 Rust + 20489 .cu / .cuhbuild.rs、src/lib.rsCUDA 内核;本卷只读源码本页下文
candle-nn9682src/var_builder.rs、ops.rs(1327)、kv_cache.rs、optim.rsVarBuilder、Linear / Embedding / 归一化等层、softmax / rms_norm / rope / sdpa 这些融合算子、KV 缓存、优化器05 神经网络层、优化器与训练循环
candle-transformers83110src/models/(82236)、generation/mod.rs、quantized_nn.rs模型实现、取样、量化版的 Linear / RmsNorm / VarBuilder07 模型
candle-ug14src/lib.rs只是把 ug、ug-cuda、ug-metal 再导出,供 ug feature 用不展开
candle-onnx、candle-pyo3、candle-datasets2634、1976、673—ONNX 求值、Python 绑定、数据集不展开
candle-flash-attn、candle-flash-attn-v31427、1182—CUDA 上的 flash attention 层不展开
candle-examples2702(另有 examples/ 下 35765 行)examples/*/main.rs每个模型一个可执行的例子,quantized、bert 等链接到用到的页

candle-transformers 的体量几乎全是模型:src/models/ 下有 105 个单文件模型和 19 个目录(stable_diffusion、whisper、segment_anything 等),models/mod.rs 声明了 124 个模块,其中 22 个以 quantized_ 开头。本卷只读其中两个:bert.rs(625 行)和 quantized_llama.rs(692 行)。

candle-core/src 按目录分

块文件(行数)讲它的页
张量与布局tensor.rs(3116)、tensor_cat.rs(304)、layout.rs(289)、shape.rs(634)、strided_index.rs(100)、indexer.rs(257)、display.rs(558)02 张量
dtype、设备、错误dtype.rs(295)、dummy_dtype.rs(268)、device.rs(518)、error.rs(394)DType 与 Device、错误
分派与算子定义storage.rs(849)、backend.rs(174)、op.rs(1204)、custom_op.rs(488)、dummy_metal_backend.rs(280)、dummy_cuda_backend.rs(317)Storage 与后端 trait、自定义算子
CPU 后端cpu_backend/(4108:mod.rs 3327、utils.rs 363、conv2d.rs 418)、cpu/(1715:neon.rs、avx.rs、simd128.rs、kernels.rs、erf.rs)、accelerate.rs(476)、mkl.rs(418)CPU 后端
Metal 后端metal_backend/(2864:mod.rs 2378、device.rs 486)Metal 后端
CUDA 后端cuda_backend/(4046),只读源码—
自动微分backprop.rs(809)、variable.rs(152)04 自动微分
量化与 GGUFquantized/(10730:k_quants.rs 2841、neon.rs 1327、cuda.rs 1130、mod.rs 1043、avx.rs 669、gguf_file.rs 638、metal.rs 502、fast_mmq.rs 400、fast_mmvq.rs 323、tokenizer.rs 324 等)06 量化
序列化safetensors.rs(652)、pickle.rs(841)、npy.rs(475)safetensors 与 mmap、VarBuilder
其他conv.rs(387)、sort.rs(294)、streaming.rs(208)、nditer.rs(319)、utils.rs(424,线程数、has_accelerate 等)用到时链接

量化一块比整个 Metal 后端大三倍多,和 README 里「Quantization support using the llama.cpp quantized types」对得上:量化点积在 CPU 上有标量(k_quants.rs)、NEON、AVX、wasm SIMD 几份实现,Metal 和 CUDA 各有一份内核。

candle-metal-kernels:Rust 一半,.metal 一半

Rust 这一半(不含 tests.rs 是 5745 行)分三层:

  • metal/:对 objc2-metal 的薄封装。Device、CommandBuffer、ComputeCommandEncoder、Fence、ResidencySet,以及管理当前命令缓冲区的 Commands(commands.rs,389 行)。
  • kernel.rs:Kernels 两张表,一张按源文件存编译好的库,一张按(内核名,函数常量)存计算管线。
  • kernels/:每类内核一个文件,call_mlx_gemm、call_quantized_matmul_mv_t、call_sdpa_vector 这样的函数负责取管线、设参数、算网格大小、编码进命令缓冲区。

.metal 这一半在 src/metal_src/ 下,17 个文件 16942 行。最大的是 quantized.metal(7741)、scaled_dot_product_attention.metal(2401)、reduce.metal(1554)、mlx_gemm.metal(1483);gemm、sdpa、sort 的内核从 MLX 移植过来,文件里留着 // ============ "mlx/backend/metal/kernels/utils.h" 这样的分段注释。

它们不是在构建时编译的。source.rs 把 16 个文件逐个 include_str! 成 &'static str 常量,和 Source 枚举一一对应:

pub const AFFINE: &str = include_str!("metal_src/affine.metal");
pub const BINARY: &str = include_str!("metal_src/binary.metal");
// ...
pub const MLX_GEMM: &str = include_str!("metal_src/mlx_gemm.metal");
// ...
pub const SDPA: &str = include_str!("metal_src/scaled_dot_product_attention.metal");

第 17 个文件 utils.metal 在 tag 上没有被任何地方引用;affine.metal、binary.metal、cast.metal、unary.metal 开头各有一段 // Utils,把要用的工具函数写在自己文件里。

第一次用到某个 Source 里的内核时,Kernels::load_library 拿着这段文本调 newLibraryWithSource:options:error:,在运行期把 MSL 编译成库;同一个源文件里的第一个内核付编译的钱,之后同一进程里只是查表。load_pipeline_with_constants 再从库里取函数、建计算管线,按(名字,函数常量)缓存。编译选项由 get_compile_options 决定:macOS 15 起默认 MTLMathMode::Fast,环境变量 CANDLE_METAL_ENABLE_FAST_MATH 可以关掉。进程之外,macOS 在用户缓存目录里还有一份编译缓存;冷热两种情况各要多久、这层缓存怎么工作、命令缓冲区怎么攒和提交,见 Metal 后端,一次生成里每个库的编译时间见生成一个 token。

CUDA 走的是另一条路。candle-kernels/build.rs 用 cudaforge 在构建时调 nvcc:11 个通用的 .cu(affine、binary、reduce、quantized 等)编成 PTX 文本,由 src/ptx.rs include_str! 进来,运行时交给驱动加载;MoE 和 GGUF 量化 matmul 的 15 个 .cu 直接编成静态库 libmoe.a 链接进去。所以开 cuda 的构建机上要有 CUDA 工具链,Metal 则不需要任何额外的构建工具。这些只读了源码,没有实测。

依赖树:只要 candle-core 会拉进什么

实验 crate intro-tree 只写 candle-core = "0.11",不开任何 feature。cargo tree -e normal 去重之后,除了它自己有 124 个包(含 candle-core)。其中 40 个只经 tokenizers 进来:tokenizers 0.22.2、onig / onig_sys(C 写的 Oniguruma 正则库,构建时用 cc 编译)、esaxx-rs、derive_builder 一族、regex、serde_json 等。--prune tokenizers 之后剩 84 个。

开 metal 和 accelerate 后是 137 个(intro-mm 去掉它自己的 anyhow):多出来的 13 个是 candle-metal-kernels、objc2 一族、block2、dispatch2、candle-metal-kernels 用的 tracing 一族,以及 accelerate-src。

📝NOTE
冷知识:candle-core 从 0.10.0 起依赖 tokenizers

candle-core 的 quantized/tokenizer.rs 定义了 TokenizerFromGguf 这个 trait,给 tokenizers::Tokenizer 实现之后,可以从 GGUF 元数据(tokenizer.ggml.tokens、merges 等)直接构造分词器,不用另找 tokenizer.json。它来自 #3245「feat: allow tokenizer to load from GGUF metadata」(2026-02-19,首次发布于 0.10.0):新增 324 行的 tokenizer.rs 和一个 gguf-tokenizer 例子,同时在 candle-core/Cargo.toml 的普通依赖里加了一行 tokenizers = { workspace = true, features = ["onig"] },没有放在 feature 后面。在这之前,candle-core/Cargo.toml 从 2023 年 7 月的 fdb1acd2「Move llama in a cargo-examples directory.」删掉 tokenizers 之后就一直没有它。

一个多月后 #3414「fix: exclude tokenizers dependency on wasm32 targets」(2026-03-29,同样首次发布于 0.10.0)补了一刀,提交说明写着「The tokenizers crate depends on onig_sys (native regex C library) which cannot compile for wasm32 targets.」,于是依赖和模块声明都挪到了 cfg(not(target_arch = "wasm32")) 后面。现在的样子就是:非 wasm 平台上,只要依赖 candle-core,就会编译一个 C 正则库,上面那 40 个包都由此而来。

小结

  • candle-core 约 3.9 万行,是三条主线的主体;candle-nn 约 1 万行,层、融合算子、KV 缓存、优化器;candle-transformers 8.3 万行,几乎全是模型,models/mod.rs 声明 124 个模块。
  • candle-core/src 里量化与 GGUF 最大(10730 行),其次是 CPU 后端、tensor.rs、CUDA 后端和 Metal 后端。
  • candle-metal-kernels 一半是 objc2-metal 的薄封装和 call_* 函数,一半是 16942 行 .metal;16 个 .metal 文件 include_str! 进二进制,第一次用到时 newLibraryWithSource 编译,按源文件和管线两级缓存在进程里;utils.metal 没被引用。
  • CUDA 内核由 build.rs 在构建时用 nvcc 编成 PTX 和一个静态库,本卷没有实测。
  • 只依赖 candle-core 就有 124 个包,其中 40 个经 0.10.0 加进来的 tokenizers。

💬 关联讨论

发起讨论
加载中...