后端
张量那一部分把调用送到 self.storage().matmul(..)。这一部分讲门后:Storage 怎么选后端,CPU 和 Metal 各自怎么把数算出来、什么时候真的算完,以及 trait 里没有的融合算子从哪里来。
代码集中在 candle-core/src/:storage.rs、backend.rs、op.rs(分派与契约);cpu_backend/、cpu/、accelerate.rs(CPU);metal_backend/(Metal 的 BackendStorage 实现);custom_op.rs。Metal 的命令缓冲区、内核加载和 .metal 源码在单独的 crate candle-metal-kernels。CUDA 后端(cuda_backend/、candle-kernels)本卷只读源码、没有实测。
- Storage 与后端 trait:
Storage永远是 Cpu、Cuda、Metal 三个变体,没开的后端换成 dummy 类型;方法先查同设备、同 dtype,再按设备对match;BackendStorage要求Sized,写不成 trait 对象;每个逐元素算子是一个零大小类型,编译期展开,整数 dtype 的一元运算会 panic。 - CPU 后端:
CpuStorage每种 dtype 一个Vec;Map1/Map2把 dtype 分派写一次;逐元素运算单线程;matmul 默认走gemm,开accelerate走sgemm,实测快 3.7 倍,但 f16 乘不了、bf16 两种构建都乘不了;三个线程池读两个不同的环境变量。 - Metal 后端:一个共享的并发计算编码器加自动内存屏障;每 50 次取编码器提交一次命令缓冲区;读回时等的是包含自己那次 blit 的命令缓冲区;缓冲区按 2 的幂分桶复用;
.metal源码运行时编译,GEMM、归约首次各要 4 秒以上,系统缓存命中时只要几毫秒;copy()不复制、批维步长特殊时 matmul 静默算错两个上游 bug。 - 自定义算子:
CustomOp1/2/3每个后端一个前向、加一个用张量运算写的bwd;candle-nn的softmax_last_dim、rms_norm、layer_norm是融合算子但不带反向,梯度在那里静默中断;写一个 CPU + Metal 的 softplus;原地算子拿同一个张量当两个参数会死锁。
flowchart TB t["Tensor 方法"] --> st["Storage 枚举"] st -->|Cpu| cpu["CpuStorage"] st -->|Metal| mtl["MetalStorage"] st -->|Cuda| cu["CudaStorage"] cpu --> g["gemm 或 Accelerate"] mtl --> k["candle-metal-kernels"] k --> q["命令缓冲区"] nn["candle-nn ops"] --> co["CustomOp 前向"] co --> st tr["BackendStorage trait"] -.->|实现| cpu tr -.->|实现| mtl tr -.->|实现| cu
第一页是分派和契约,后两页是契约的两个实测过的实现者,最后一页是契约之外的扩展口。算完之后结果张量怎么记进计算图,见计算图怎么记下来;量化矩阵乘走的是另一套存储 QStorage,见 QTensor 与 QMatMul。
💬 关联讨论
发起讨论加载中...