- 人工智能
- 机器学习
- 深度学习
【免费下载链接】framework
A machine learning library with a TypeScript API and Rust backend. CUDA and WebGPU compatibility. Built to understand how ML frameworks and models work internally.
@mni-ml/framework是一个 TypeScript 前端 + Rust 后端的机器学习框架,支持自动微分、Tensor 运算与神经网络训练,并提供了 CPU、CUDA、WebGPU 三种后端。本文将带你拆解它最"硬核"的一部分:如何在运行时用NVRTC现场编译.cu内核文件、注册 GPU 函数,并调用cuBLAS完成矩阵乘法。无论你是框架初学者还是 CUDA 开发爱好者,读完都能看懂一条完整的数据流。
一、整体架构:从 TypeScript 一行代码到 GPU 内核
整个后端的调用链非常清晰(出自 README.md 中的 Backend Architecture):
TypeScript API (tensor.ts, nn.ts, optimizer.ts) └─→ N-API Bridge (lib.rs) ├─→ CPU Backend (纯 Rust 实现) ├─→ CUDA Backend (cudarc + .cu 内核) ← 本文主角 └─→ WebGPU Backend (wgpu + .wgsl 着色器)cpu、cuda、webgpu三个 feature 在编译期互斥,默认是cpu- 依赖声明在 src/native/Cargo.toml:
cudarc = { version = "0.16", features = ["cublas", "nvrtc", "curand"] },一个 crate 同时提供了 cuBLAS 和 NVRTC 两套能力
📌 关键设计:没有预编译好的 fatbin/cubin 文件,所有 CUDA 代码以.cu源码形式存在,在 GPU 首次初始化时现场编译。
二、NVRTC 运行时编译:.cu 源码如何变成 GPU 可执行代码
核心逻辑只有 10 行,位于 src/native/src/device.rs 的compile_and_load方法:
fn compile_and_load(&mut self, name: &str, source: &str, kernel_names: &[&str]) { let ptx = nvrtc::compile_ptx(source) // 1. NVRTC 把 .cu 源码编译成 PTX .unwrap_or_else(|e| panic!("Failed to compile {name}: {e}")); let module = self.ctx.load_module(ptx) // 2. 用 Driver API 加载 PTX 成 CUDA Module .unwrap_or_else(|e| panic!("Failed to load module {name}: {e}")); for &kname in kernel_names { let func = module.load_function(kname); // 3. 取出每个 kernel 函数句柄 self.functions.insert(kname.to_string(), func); // 4. 注册进哈希表 } }分三步理解:
- NVRTC 编译:
nvrtc::compile_ptx(source)等价于命令行里跑nvcc -ptx,把 CUDA C++ 源码编译为 PTX 中间码(可移植的 GPU 汇编),全程在内存中完成,不落盘; - 加载模块:
ctx.load_module(ptx)通过 CUDA Driver API 把 PTX 交给 GPU 驱动,按需 JIT 成当前显卡架构的机器码; - 函数注册:从 Module 中取出每个
__global__函数的句柄(CudaFunction),存进HashMap<String, CudaFunction>,之后按名字即可取出启动。
这种"运行时编译"方案的好处很直接:
- 无需 nvcc 工具链:用户装好 CUDA 运行时即可,不用装完整的 CUDA Toolkit;
- 跨显卡架构自适应:PTX 由驱动 JIT,老卡新卡都能跑;
- 与 WebGPU 后端形成对照:WebGPU 那边用的是
.wgsl着色器同样走运行时编译(见 src/native/src/device.rs 的compile_shader),架构完全对称。
三、一次初始化加载 17 个内核文件
load_all_kernels(src/native/src/device.rs)用include_str!宏把.cu源码直接嵌进二进制,然后在启动时统一编译。完整的内核清单覆盖了训练所需的全部基础算子:
| 内核文件 | 提供的能力(节选) |
|---|---|
| elementwise.cu | add / sub / mul / exp / log / broadcast 等 21 个逐元素运算 |
| activation.cu | GELU、ReLU、Sigmoid 的前向/反向 |
| flash_attention.cu | Flash Attention 前向/反向 |
| conv.cu | conv1d/conv2d 前向与两种反向 |
| layernorm.cu、softmax.cu | LayerNorm、Softmax 前后向 |
| fused_ops.cu | residual+layernorm、bias+GELU 等融合算子 |
| adamw.cu、kv_quant.cu | 优化器步进、KV Cache 量化 |
以 src/native/kernels/elementwise.cu 为例,内核就是标准的 CUDA C++:
extern "C" __global__ void add_f32(float* out, const float* a, const float* b, int n) { int i = blockIdx.x * blockDim.x + threadIdx.x; if (i < n) out[i] = a[i] + b[i]; }注意extern "C"—— 它保证函数名不被 C++ 修饰(name mangling),NVRTC 编译后 Rust 侧才能按字符串"add_f32"精确找到函数。
设备本身用OnceLock实现了懒加载单例(src/native/src/device.rs):第一次调用GpuDevice::instance()时才创建 CUDA Context、默认 Stream 和 cuBLAS 句柄,并触发全部内核编译;后续调用共享同一实例,编译开销只付一次。
四、内核怎么启动:launch_builder 与 LaunchConfig
内核编译好只是第一步,真正让 GPU 干活的是"启动"(launch)。以Tensor.add()背后的 CUDA 路径 src/native/src/ops/elementwise.rs 为例:
let func = dev.get_func("add_f32"); // 按名字取出 kernel 句柄 unsafe { dev.stream.launch_builder(func) .arg(&out_ptr).arg(&a_ptr).arg(&b_ptr) // 参数就是显存指针 .arg(&(n as i32)) .launch(LaunchConfig { grid_dim: ((n + 255) / 256, 1, 1), // 向上取整到 256 线程一组的块数 block_dim: (256, 1, 1), shared_mem_bytes: 0, }) .unwrap(); }- 输入输出都是显存指针(
store.dev_ptr),数据无需在 CPU/GPU 间来回拷贝; grid_dim × block_dim就是 CUDA 经典的"一个线程处理一个元素"的并行模式,块大小固定 256。
五、cuBLAS 调用:matmul API 背后的矩阵乘法
Tensor.matmul()的 CUDA 实现(src/native/src/ops/matmul.rs)没有自己写内核,而是把活交给 NVIDIA 官方库cuBLAS,这是框架性能的关键保障。
5.1 句柄创建
cuBLAS 句柄在设备初始化时创建(src/native/src/device.rs):
let blas = CudaBlas::new(stream.clone()).expect("cuBLAS handle creation failed");注意它复用了 CUDA 默认 Stream,保证矩阵乘与其他内核串行同序执行,无需额外同步。
5.2 列主序陷阱:行主序框架如何用列主序的 cuBLAS
cuBLAS 默认按列主序(column-major)处理矩阵,而框架的 Tensor 是行主序(row-major)。直接传参会得到错误结果,标准解法是"转置技巧":
$$C = A_{M \times K} \cdot B_{K \times N} \iff C^T = B^T A^T$$
于是把 B 当第一个操作数、A 当第二个,并把 m、n 互换。对应 src/native/src/ops/matmul.rs:
// cuBLAS is column-major. For row-major C = A @ B: // pass B as first arg, A as second, with m=N, n=M, k=K. let cfg = GemmConfig { transa: CUBLAS_OP_N, transb: CUBLAS_OP_N, m: n as i32, n: m as i32, k: k as i32, // 维度互换! alpha: 1.0f32, lda: n as i32, ldb: k as i32, beta: 0.0f32, ldc: n as i32, }; dev.blas.gemm(cfg, b_data, a_data, c_data).unwrap(); // 注意参数顺序:B 在前beta: 0.0表示结果直接覆盖 C 而不是累加,输出张量事先用store.zeros在 GPU 上分配好。
5.3 批量矩阵乘法:stride = 0 实现广播
Transformer 的注意力、batch 化的 Linear 层都涉及[..., M, K] × [..., K, N]的批量乘。框架用 cuBLAS 的gemm_strided_batched一次下发(src/native/src/ops/matmul.rs),并藏了一个巧思:
- 当某个操作数 batch 维为 1(需要广播)时,把它的
stride设为0,cuBLAS 会复用同一块矩阵给所有 batch——在 GPU 上免费实现了广播; - 遇到更复杂的多维广播模式则回退到 CPU 路径,保证正确性优先。
六、环境准备:如何装到 CUDA 版本
普通用户完全不用关心编译细节,@mni-ml/framework通过平台包自动分发(npm/linux-x64-gnu-cuda/README.md):
npm install @mni-ml/framework加载器 src/native-loader.ts 会检测系统里是否存在libcuda.so(Linux)或nvcuda.dll(Windows):
- ✅ 检测到 NVIDIA 驱动 → 优先加载
@mni-ml/framework-linux-x64-gnu-cuda(CUDA 版) - ❌ 没有 GPU 驱动 → 依次回退到 WebGPU 版、CPU 版
⚠️ 常见坑:只装了 CUDA Toolkit 却没装显卡驱动,hasCudaDriver检测不到驱动库,会自动降级到 WebGPU/CPU 后端——这是设计预期,不是错误。
如果你是贡献者想从源码构建,项目要求 Rust 工具链 + CUDA Toolkit:
npm run build:native:cuda # 需要 CUDA toolkit七、关键文件索引
| 文件 | 作用 |
|---|---|
| src/native/src/device.rs | NVRTC 编译、模块加载、内核注册与单例管理 |
| src/native/src/ops/matmul.rs | cuBLASgemm/gemm_strided_batched调用 |
| src/native/src/ops/elementwise.rs | 典型内核启动范式 |
| src/native/kernels/ | 17 个.cu内核源码 |
| src/native/Cargo.toml | feature 开关与 cudarc 依赖 |
八、写在最后
@framework 这套方案浓缩了工业级 ML 框架的通用套路:
- 源码即资产:
.cu内核以include_str!嵌入,运行时经 NVRTC → PTX → Driver API 三级流水线加载,免工具链依赖; - 能复用官方库就复用:矩阵乘法这类高度优化的计算交给 cuBLAS,自研内核只补齐 cuBLAS 覆盖不到的部分(激活函数、归一化、融合算子);
- 行主序 × 列主序:用"参数对调 + 维度互换"零拷贝解决,是读懂所有 cuBLAS 封装的钥匙。
如果你想深入 ML 框架内核如何工作,这个"TypeScript 接口 + Rust 后端 + 运行时编译 CUDA"的三层结构,是一个规模可控、值得精读的学习样本 🚀
- 人工智能
- 机器学习
- 深度学习
【免费下载链接】framework
A machine learning library with a TypeScript API and Rust backend. CUDA and WebGPU compatibility. Built to understand how ML frameworks and models work internally.
相关推荐
CUDA 全局内存原子指令与 NVRTC 运行时编译实战:解析 simpleAtomicIntrinsics_nvrtc
CUDA 全局内存原子指令与 NVRTC 运行时编译实战:解析 simpleAtomicIntrinsics_nvrtc 导读 本文以 CUDA Samples
示例工程如何编译 BitNet W2A8 CUDA 内核并用 test.py 运行 GEMV 加速测试?
如何编译 BitNet W2A8 CUDA 内核并用 test.py 运行 GEMV 加速测试? BitNet 仓库的 gpu/ 目录 https://link
人工智能大模型推理引擎本地部署模型量化模型优化旧Mac免费装新macOS:OpenCore Legacy Patcher完整指南
旧Mac免费装新macOS:OpenCore Legacy Patcher完整指南 macOS 软件更新页面那行灰字——"此 Mac 无法升级到最新版本"——看
桌面应用操作系统固件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考