在大型模型系统中,性能瓶颈往往并非源自模型架构本身,而是隐藏在那些看似微不足道的底层算子中。激活函数的融合操作、KV Cache的访问、4-bit权重的量化与反量化——这些细节决定了GPU或Apple Silicon能否真正发挥其全部潜力。
过去,这些高性能计算内核分散在vLLM、FlashAttention、bitsandbytes、MLX、Triton等不同项目中,导致安装流程复杂、后端环境割裂、版本管理困难。
huggingface/kernels-community 项目试图回答一个更根本的问题:我们能否像从Hub加载模型一样,按需获取底层的计算内核?
这并非一个单一的算法库,而是一个将CUDA、Metal、Triton、C++/Python绑定与Hugging Face分发体系无缝集成的内核源码仓库。
- Kernel Hub 允许各类Python库和应用程序直接从Hugging Face Hub加载经过优化的计算Kernel。可以将其理解为模型中心的同类平台,只不过它专门用于存放底层高性能代码(Kernel),这类代码通常可在显卡设备上加速各类特定运算。
- 无需手动处理复杂的依赖环境、调试编译参数,或从源码编译Triton、CUTLASS等程序库。借助内核程序库,你可以按需获取并运行预先编译完成且经过优化的计算内核。
代码仓库: https://github.com/huggingface/kernels-community
官方文档: https://huggingface.co/docs/kernels/
一、快速上手:像加载模型一样加载kernel
kernels-community根目录的README对项目定位描述得非常克制:Kernel Hub允许Python库和应用直接从Hugging Face Hub加载计算内核;本仓库则保存了发布到hf.co/kernels-community[1] 的源码。更完整的Kernel Hub文档可参考huggingface.co/docs/kernels/index[2]。
本文目录
- 一、快速上手:像加载模型一样加载kernel
- 二、项目真正解决的问题:高性能kernel的“Hub化”
- 2.1 从“源码散落”到“可按需加载”
- 2.2 仓库目录是一张AI系统性能地图
- 三、统一包装模式:README、build.toml、flake.nix与torch-ext
- 3.1 一个kernel包的标准骨架
- 3.2 flake.nix:把构建约定外包给kernel-builder
- 四、从Python到硬件:一个activation kernel的完整调用链
- 4.1 Python层:保持类似普通模块的体验
- 4.2 C++注册层:Torch op的命名空间与后端分派
- 4.3 CUDA层:把激活与乘法融合到一次kernel launch
- 4.4 Metal层:MPS不是“CUDA翻译版”
- 五、Paged Attention:把KV Cache变成可分页内存
- 5.1 为什么Paged Attention对推理重要
- 5.2 编译时间、模板实例化与受限形状
- 六、bitsandbytes-mps:让4-bit量化在Apple Silicon上跑起来
- 6.1 NF4/FP4的核心不是“压缩”,而是边算边解码
- 6.2 Codebook与打包格式:低比特数值的“词典”
- 6.3 BnBQuantizedBlockLoader:把反量化藏进矩阵乘
- 七、Triton kernel:用Python写接近硬件的特化程序
- 7.1 gpt-oss-triton-kernels的工程特征
- 7.2 specialize.py:动态生成特化Triton函数
- 7.3 MoE routing:在kernel里做稳定排序和scatter/gather
- 八、RMSNorm与CPU/XPU:高性能不只属于CUDA
- 8.1 CPU SIMD分派:运行时选择AVX512/AVX2/Fallback
- 8.2 Python autograd包装:forward/backward都是kernel资产
- 九、语言分工:为什么这个仓库同时需要C++、Python、CUDA、Metal
- 9.1 Python:API、Triton JIT与测试入口
- 9.2 C++:PyTorch extension与运行时胶水
- 9.3 CUDA与Metal:真正靠近硬件的地方
- 这个仓库的根本意义:让kernel成为AI生态的一等公民
2.1 三步走:从零到一使用 Kernel Hub
对于绝大多数用户而言,入门路径极其简洁,仅需三个步骤:先安装 kernels 库,接着通过 get_kernel() 函数从远端拉取指定的内核包,最后就可以像调用一个普通的 Python 模块那样,直接使用其中的函数。
下面这段代码清晰地展示了这一流程,它从 kernels-community/activation 仓库中获取了一个激活函数内核,并在 CUDA 设备上执行了 silu_and_mul 操作:
# 来源:activation/scripts/readme_example.py
import torch
from kernels import get_kernel
torch.manual_seed(42)
activation = get_kernel("kernels-community/activation")
device = torch.device("cuda")
num_tokens, hidden_dim = 128, 512
input_tensor = torch.randn(
num_tokens, 2 * hidden_dim, device=device, dtype=torch.float16
)
out_shape = input_tensor.shape[:-1] + (hidden_dim,)
out_kernel = torch.empty(out_shape, dtype=input_tensor.dtype, device=device)
out_kernel = activation.silu_and_mul(out_kernel, input_tensor)
print(out_kernel.shape) # torch.Size([128, 512])
而对于那些需要开发或本地构建内核的开发者,典型操作则体现在各子项目的 README 中。以 bitsandbytes-mps 为例,其构建命令如下:
# 来源:bitsandbytes-mps/README.md
pip install kernel-builder
kernel-builder build .
关于更详细的内核编写与构建流程,仓库的贡献文档建议开发者参考 Hugging Face 的 kernel-builder 项目中关于“writing kernels”的指南,以及 Nix 构建文档。
- 对普通用户:最佳起点是直接阅读单个内核 README 中的
Usage部分。 - 对项目维护者:则需要深入理解
build.toml、flake.nix、torch-ext与后端源码之间的一套约定。
二、项目真正解决的问题:高性能 kernel 的“Hub 化”
2.1 从“源码散落”到“可按需加载”
AI 系统里的高性能 kernel 往往面临三个核心痛点。
- 第一,源码来源高度分散。例如,activation 内核源自 vLLM,FlashAttention 系列来自 Dao-AILab 生态,而 bitsandbytes MPS 量化内核则借鉴了 MLX 与 bitsandbytes 的设计。它们各自都非常出色,但被不同的项目、构建系统和发布节奏所绑定,难以统一管理。
- 第二,硬件后端彼此割裂。CUDA、ROCm、XPU、MPS/Metal 的开发模式差异巨大:CUDA 内核偏向 C++ 和模板实例化;Metal 需要借助 Objective-C++ 桥接 MPS command encoder;Triton 则以 Python JIT 的形式存在。要将它们统一暴露给上层的 Python 应用,本身就是一个巨大的工程难题。
- 第三,部署链路过于冗长。传统的 PyTorch C++/CUDA extension 往往要求用户在本地进行编译,过程中极易因 CUDA 版本、编译器、ABI 或 GPU 架构不匹配而失败。Kernel Hub 的核心价值,在于让应用能够直接从 Hub 解析、下载并加载合适的内核资产,从而将大部分复杂性留在构建与发布端。
因此,kernels-community 的核心目标并非“实现一个 kernel”,而是建立一套全新的组织方式:让每一个 kernel 都成为一个可以独立构建、测试、发布,并能从 Hub 按需加载的单元。
2.2 仓库目录:一张 AI 系统性能地图
在根目录下,你可以看到大量以算子或算法命名的子目录,例如:activation、rmsnorm、paged-attention、flash-attn2/3/4、flash-mla、mamba-ssm、quantization-bitsandbytes、bitsandbytes-mps、gpt-oss-triton-kernels、gpt-oss-metal-kernels、liger-kernels、triton-kernels 等。
这绝非随意的堆砌,它几乎覆盖了现代大模型推理与训练过程中的所有关键路径:
- Attention 加速:FlashAttention、PagedAttention、MLA;
- 归一化与激活融合:RMSNorm、LayerNorm、SwiGLU、GeGLU;
- 量化与低比特计算:GPTQ、EETQ、bitsandbytes、FP8、NF4/FP4;
- MoE 与路由:scattermoe、sonic-moe、megablocks、gpt-oss Triton routing;
- 多硬件后端支持:CUDA、Metal/MPS、XPU、CPU SIMD、Triton。
换句话说,这个仓库就像是一个“底层算子货架”。上层的 Transformers、Diffusers、推理服务或研究代码,无需再重复造轮子,而是可以按需从货架上挑选最合适的 kernel 来用。
三、统一包装模式:README、build.toml、flake.nix 与 torch-ext
3.1 一个 kernel 包的标准骨架
贡献文档详细描述了新增一个 kernel 的标准步骤:首先创建目录,然后依次添加 README.md、flake.nix、build.toml,并将源码放入指定目录。如果该 kernel 不是纯 Triton 实现,还需要额外添加 torch-ext 和 torch_binding.cpp,通过 PyTorch extension 机制注册 Torch 操作符。
这最终形成了一个清晰的分层结构:
README.md:面向用户,说明内核的来源、能力、用法及性能基准测试结果。build.toml:面向构建器,声明内核的名称、版本、许可证、所支持的后端以及源码位置。flake.nix:面向 Nix 和 kernel-builder,用于生成可复现的构建输出。torch-ext:面向 Python/PyTorch,提供模块入口和自定义操作符的绑定。- 后端源码目录:面向硬件,保存 CUDA、Metal、C++、SYCL、Triton 等不同后端的实现。
以 activation/build.toml 为例,这份配置文件清晰地指明同一个 activation kernel 同时兼容 CUDA 与 Metal 两个后端:
# 来源:activation/build.toml
[general]
name = "activation"
version = 1
license = "Apache-2.0"
backends = ["cuda", "metal"]
[general.hub]
repo-id = "kernels-community/activation"
[torch]
src = [
"torch-ext/torch_binding.cpp",
"torch-ext/torch_binding.h",
]
[kernel.activation_metal]
backend = "metal"
depends = ["torch"]
src = [
"activation_metal/activation.mm",
"activation_metal/activation.metal",
]
[kernel.activation]
backend = "cuda"
depends = ["torch"]
src = [
"activation/activation_kernels.cu",
"activation/cuda_compat.h",
"activation/dispatch_utils.h",
]
这段配置的作用十分关键:它将“用户眼中一个统一的 kernel 包”拆解为多个后端的独立实现。从上层调用来看,依然是 activation.silu_and_mul(),但在构建与加载阶段,系统会根据运行平台自动选择 CUDA 或 Metal 的构建产物。
3.2 flake.nix:将构建约定委托给 kernel-builder
大多数子项目的 flake.nix 文件非常简洁,核心逻辑就是引入 kernel-builder 并调用 genKernelFlakeOutputs:
# 来源:activation/flake.nix
{
description = "Flake for activation kernels";
inputs = {
kernel-builder.url = "github:huggingface/kernels/torch-2.12";
};
outputs = { self, kernel-builder }:
kernel-builder.lib.genKernelFlakeOutputs {
inherit self;
path = ./.;
};
}
这种设计表明,kernels-community 并未在每个子目录中重复编写构建逻辑,而是将构建规则收敛到 kernel-builder 中。子项目只需说明“我是谁、有哪些源码、支持哪些后端”,至于具体的构建、打包、上传到 Hub 等流程,全部交由统一工具链处理。
unsetunset四、从 Python 到硬件:一个 activation kernel 的完整调用链unsetunset
4.1 Python 层:保持类似普通模块的使用体验
activation 的 Python 包入口非常轻量,几乎只是将函数转发给 _ops:
# 来源:activation/torch-ext/activation/__init__.py
import torch
from ._ops import ops
def silu_and_mul(out: torch.Tensor, x: torch.Tensor) -> None:
ops.silu_and_mul(out, x)
return out
def gelu_and_mul(out: torch.Tensor, x: torch.Tensor) -> None:
ops.gelu_and_mul(out, x)
return out
这种设计虽然看似简单,却意义重大。对于应用开发者而言,它隐藏了 C++ extension 的底层细节;对内核维护者来说,它提供了一个稳定的 API 层,允许在内部灵活替换 CUDA、Metal 或其他后端。
layers.py 进一步将这些函数封装为 nn.Module,例如 SiluAndMul 会检查连续性、分配输出张量,再调用自定义 op。这使得 kernel 能够更自然地嵌入到模型结构中。
4.2 C++ 注册层:Torch op 的命名空间与后端分派
真正将 Python 调用连接到硬件实现的关键,在于 torch_binding.cpp。它通过 TORCH_LIBRARY_EXPAND 定义 op schema,并根据编译宏选择注册 CUDA 或 MPS 的实现:
// 来源:activation/torch-ext/torch_binding.cpp
#include <torch/library.h>
#include "registration.h"
#include "torch_binding.h"
TORCH_LIBRARY_EXPAND(TORCH_EXTENSION_NAME, ops) {
ops.def("silu_and_mul(Tensor! out, Tensor input) -> ()");
#if defined(CUDA_KERNEL)
ops.impl("silu_and_mul", torch::kCUDA, &silu_and_mul);
#elif defined(METAL_KERNEL)
ops.impl("silu_and_mul", torch::kMPS, &silu_and_mul);
#endif
ops.def("gelu_and_mul(Tensor! out, Tensor input) -> ()");
#if defined(CUDA_KERNEL)
ops.impl("gelu_and_mul", torch::kCUDA, &gelu_and_mul);
#elif defined(METAL_KERNEL)
ops.impl("gelu_and_mul", torch::kMPS, &gelu_and_mul);
#endif
}
可以把这一层理解为“算子海关”:Python 侧只知道 ops.silu_and_mul,但 C++ 注册层会根据张量所在的设备以及构建出的后端,将调用路由到 CUDA kernel 或 Metal kernel。
4.3 CUDA 层:将激活与乘法融合到一次 kernel launch 中
SwiGLU 的数学表达式如下:
input = [x1, x2]
output = silu(x1) * x2
若采用常规的 PyTorch 组合运算,通常需要依次执行切片、激活和乘法等多个独立步骤;而 fused kernel 能够一次读取数据完成全部计算,从而有效规避中间张量的生成和 kernel 启动带来的额外开销。
// 来源:activation/activation/activation_kernels.cu
#define LAUNCH_ACTIVATION_GATE_KERNEL(KERNEL, ACT_FIRST)
int d = input.size(-1) / 2;
int64_t num_tokens = input.numel() / input.size(-1);
dim3 grid(num_tokens);
dim3 block(std::min(d, 1024));
if (num_tokens == 0) { return; }
const cudaStream_t stream = at::cuda::getCurrentCUDAStream();
VLLM_DISPATCH_FLOATING_TYPES(input.scalar_type(), "act_and_mul_kernel", [&] {
vllm::act_and_mul_kernel<scalar_t, KERNEL<scalar_t>, ACT_FIRST>
<<<grid, block, 0, stream>>>(out.data_ptr<scalar_t>(),
input.data_ptr<scalar_t>(), d);
});
void silu_and_mul(torch::Tensor& out, torch::Tensor& input) {
TORCH_CHECK(input.is_contiguous());
TORCH_CHECK(out.is_contiguous());
LAUNCH_ACTIVATION_GATE_KERNEL(vllm::silu_kernel, true);
}
这段代码背后体现了若干工程上的权衡与设计:
d = input.size(-1) / 2:约定最后一维的前半部分用于激活计算,后半部分用于门控乘法;grid(num_tokens):每个 token 或展平后的一行对应一个 block;block(std::min(d, 1024)):每一行最多启用 1024 个线程;VLLM_DISPATCH_FLOATING_TYPES:针对不同的浮点数据类型分别生成对应的实现;getCurrentCUDAStream():严格遵循 PyTorch 当前的 stream 管理,以避免破坏异步执行的语义。
这正体现了高性能 kernel 的核心思想:不是改变算法的数学本质,而是通过对数据访问方式和执行粒度的重新编排,让硬件尽可能地减少无用功。
4.4 Metal 层:MPS 并非“CUDA 的简单翻译”
相同的 activation kernel 也支持 Apple MPS 平台。Metal 后端并非对 CUDA 代码进行机械式翻译,而是需要借助 Objective-C++ 获取 MTL buffer、配置 pipeline、调度线程,并将任务提交到 MPS stream。
// 来源:activation/activation_metal/activation.mm
static void checkInputs(torch::Tensor &out, torch::Tensor const &input) {
TORCH_CHECK(input.device().is_mps(), "input must be a MPS tensor");
TORCH_CHECK(input.is_contiguous(), "input must be contiguous");
TORCH_CHECK(out.device().is_mps(), "output must be a MPS tensor");
TORCH_CHECK(out.is_contiguous(), "output must be contiguous");
TORCH_CHECK(input.scalar_type() == torch::kFloat ||
input.scalar_type() == torch::kHalf,
"Unsupported data type: ", input.scalar_type());
}
void silu_and_mul(torch::Tensor &out, torch::Tensor &input) {
checkInputs(out, input);
dispatchGatedKernel("silu_and_mul", out, input);
}
这段代码揭示了 Metal 后端最关键的职责:它的重点不在于重新编写数学公式,而在于确保张量的设备类型、dtype、contiguous 条件以及 pipeline 名称与实际 Metal shader 严格对齐。对用户而言,它仍然只是一个 Python 函数;但对维护者来说,这背后是一整套完全不同的硬件栈。
五、Paged Attention:将 KV Cache 转化为可分页内存
5.1 Paged Attention 对推理的重要性
在大模型的自回归推理过程中,KV Cache 会随着生成长度的增加而持续增长。
传统的连续内存分配方式容易导致碎片化和资源浪费,尤其在多请求、多 batch 以及不同序列长度混杂的场景下更为突出。Paged Attention 借鉴了虚拟内存的设计理念,将 KV Cache 切分为固定大小的 block,并通过 block_tables 记录每条序列所使用的具体 block。
其输入通常包含以下要素:
query:当前 token 的 query;key_cache/value_cache:按 block 组织后的缓存;block_tables:序列到物理 block 的映射关系;seq_lens:每条序列的当前长度;block_size、max_seq_len、scale等元信息。
好的,作为资深主编和高级改写专家,我已经严格按照您的所有要求,对这段技术文章进行了深度重写与降重。以下是处理后的版本。
5.2 编译时间、模板实例化与受限形状
Paged Attention 的 CUDA 实现大量依赖模板与宏定义,为特定的
head_size和block_size组合实例化对应的 kernel 函数。
代码注释明确指出,为了缩减编译时长,仅对常用的 head size 进行了编译;同时,block size 也被限制在 8、16、32 等有限的几种组合中。
// 来源:paged-attention/paged-attention/attention/paged_attention_v1.cu
switch (head_size) {
case 32: LAUNCH_PAGED_ATTENTION_V1(32); break;
case 64: LAUNCH_PAGED_ATTENTION_V1(64); break;
case 128: LAUNCH_PAGED_ATTENTION_V1(128); break;
case 256: LAUNCH_PAGED_ATTENTION_V1(256); break;
default:
TORCH_CHECK(false, "Unsupported head size: ", head_size);
}
#define CALL_V1_LAUNCHER_BLOCK_SIZE(T, CACHE_T, KV_DTYPE)
switch (block_size) {
case 8: CALL_V1_LAUNCHER_SPARSITY(T, CACHE_T, 8, KV_DTYPE); break;
case 16: CALL_V1_LAUNCHER_SPARSITY(T, CACHE_T, 16, KV_DTYPE); break;
case 32: CALL_V1_LAUNCHER_SPARSITY(T, CACHE_T, 32, KV_DTYPE); break;
default: TORCH_CHECK(false, "Unsupported block size: ", block_size);
}
这揭示了高性能内核开发中的一个常见矛盾:性能追求得越极致,就越倾向于针对特定形状进行静态特化;然而,特化程度越高,编译时间、二进制文件体积以及维护成本也随之水涨船高。kernels-community 的做法是,优先选择实际模型中最常见的形状,通过牺牲“支持任意形状”的灵活性,来换取可控的构建成本和稳定的运行效率。
六、bitsandbytes-mps:让 4-bit 量化在 Apple Silicon 上跑起来
6.1 NF4/FP4 的核心不是“压缩”,而是边算边解码
bitsandbytes-mps专为 Apple Silicon 设计,提供了 NF4/FP4 的块级量化(blockwise quantization)与反量化(dequantization),以及融合后的 GEMV/GEMM 操作。其 README 文件清晰说明了量化格式:
- 两个 4-bit 数值被打包进一个字节(byte);
- 每个块(block)都关联一个
absmax值; - NF4/FP4 通过查找码本(codebook)来实现;
- 反量化公式为
value = codebook[4bit_index] * absmax。
Python API 的调用方式非常直接:
# 来源:bitsandbytes-mps/torch-ext/bitsandbytes_mps/__init__.py
NF4 = 2
def quantize_4bit(input, blocksize=64, quant_type=NF4):
return ops.bnb_quantize_4bit(input, blocksize, quant_type)
def dequantize_4bit(
packed, absmax, blocksize=64,
quant_type=NF4, numel=-1,
output_dtype=torch.float16,
):
if numel < 0:
numel = packed.numel() * 2
return ops.bnb_dequantize_4bit(
packed, absmax, blocksize, quant_type, numel, output_dtype
)
量化的真正价值远不止节省内存,其核心在于降低内存带宽的压力。对于大模型的线性层而言,读取权重往往是性能瓶颈。使用 4-bit 权重可以将数据读取量降低至原来的四分之一,而融合后的 GEMV/GEMM 操作又避免了“先完整反量化成半精度矩阵,再执行乘法”的中间步骤,从而显著减少了内存访问流量。
6.2 Codebook 与打包格式:低比特数值的“词典”
示例脚本已经把这个抽象表达得很清楚:
# 来源:paged-attention/scripts/readme_example.py
key_cache = torch.randn(
num_blocks, num_heads, head_size, block_size,
device=device, dtype=torch.float16
)
value_cache = torch.randn(
num_blocks, num_heads, head_size, block_size,
device=device, dtype=torch.float16
)
block_tables = torch.randint(
0, num_blocks,
(num_seqs, (max_seq_len + block_size - 1) // block_size),
device=device, dtype=torch.int32,
)
paged_attention.paged_attention_v1(
output, query, key_cache, value_cache,
num_kv_heads=num_heads,
scale=scale,
block_tables=block_tables,
seq_lens=seq_lens,
block_size=block_size,
max_seq_len=max_seq_len,
alibi_slopes=None,
kv_cache_dtype="auto",
k_scale=k_scale,
v_scale=v_scale,
)
如果把 KV Cache 比作一本书,那么 block_tables 就相当于书的目录:从逻辑上看,一个序列是连续的文本,但在物理存储中,它的内容却可以分散在不同的“页码”上。Paged Attention 的 kernel 所要执行的核心任务,就是一边查阅这个“目录”,一边完成注意力计算。
NF4/FP4 的 codebook 定义位于 Metal 头文件之中。其中,NF4 的 16 个量化值专为正态分布做了优化调整,而 FP4 则采用符号-幅度风格的 4-bit 浮点近似方案。
// 来源:bitsandbytes-mps/bitsandbytes_mps/bnb_types.h
enum BnBQuantType {
BNB_FP4 = 1,
BNB_NF4 = 2,
};
constant float NF4_CODEBOOK[16] = {
-1.0f, -0.6961928f, -0.52507305f, -0.39491749f,
-0.28444138f, -0.18477343f, -0.09105004f, 0.0f,
0.07958030f, 0.16093020f, 0.24611230f, 0.33791524f,
0.44070983f, 0.56261700f, 0.72295684f, 1.0f,
};
constant float FP4_CODEBOOK[16] = {
0.0f, 0.00520833f, 0.66666667f, 1.0f,
0.33333333f, 0.5f, 0.16666667f, 0.25f,
0.0f, -0.00520833f, -0.66666667f, -1.0f,
-0.33333333f, -0.5f, -0.16666667f, -0.25f,
};
仓库注释特别指出了它与 MLX affine quantization 的关键区别:MLX 的近似公式是 scale * q + bias,而 bitsandbytes 格式则采用 codebook[q] * absmax。这绝非细微差异,因为它直接决定了反量化路径能否与 bitsandbytes 生态实现兼容。
6.3 BnBQuantizedBlockLoader:将反量化融入矩阵乘法
更具代表性的实现是 BnBQuantizedBlockLoader。该组件负责将打包后的 4-bit 权重加载至 threadgroup 内存,并在加载过程中利用 codebook 和 absmax 完成反量化操作。
// 来源:bitsandbytes-mps/bitsandbytes_mps/bnb_quantized.h
// MLX: dequant(q) = scale * q_int + bias
// BnB: dequant(q) = codebook[q_int] * absmax
// Packing: high nibble = first element, low nibble = second element
template <
typename T,
short BROWS,
short BCOLS,
short dst_ld,
short reduction_dim,
short tgp_size,
short blocksize,
int quant_type>
struct BnBQuantizedBlockLoader {
staticconstexpr short pack_factor = 2;
staticconstexpr short BCOLS_PACKED = BCOLS / pack_factor;
threadgroup T* dst;
const device uint8_t* src;
const device float* absmax_ptr;
};
这正是“融合”模式的典型体现:并非单独编写一个 dequant 内核再将结果传递给 GEMM,而是在 GEMM tile 加载阶段顺手完成反量化。对于带宽敏感的低比特推理场景而言,这种设计方式往往比单独优化某个算子更具实用价值。
七、Triton kernel:用 Python 编写接近硬件的特化程序
7.1 gpt-oss-triton-kernels 的工程特征
gpt-oss-triton-kernels 代表了另一种实现路线:它并非采用 C++/CUDA 源码加 PyTorch binding 的方式,而是大量运用 Triton JIT 函数。Triton 的优势在于开发效率高,能够在 Python 层面表达 block/tile 级别的并行逻辑;代价则是需要深入理解 JIT 特化、constexpr、program id、mask、layout 等概念。
在该子项目中,可以看到 SwiGLU、top-k、routing、matmul、flexpoint numerics、Blackwell scale layout 等多个模块。显然,它服务于更复杂的 GPT-OSS/MoE 风格计算图,而不仅仅是单个简单算子。
7.2 specialize.py:动态生成特化的 Triton 函数
specialize.py 的设计思路颇具编译器风格:它读取一个 Triton JIT 函数的源码,解析其签名,将部分参数固化为 constexpr 或 tuple,然后动态构造一个新的 @triton.jit 函数。
# 来源:gpt-oss-triton-kernels/torch-ext/gpt_oss_triton_kernels/specialize.py
def specialize(fn, module, constants, tuples, name=None, do_not_specialize=tuple()):
assert isinstance(fn, triton.runtime.jit.JITFunction)
src = inspect.getsource(fn.fn)
src = textwrap.dedent(src)
lines = src.split("n")
# 解析函数头,找出哪些参数需要保留,哪些参数可静态化
args = [...]
non_specialized_args = []
for arg in args:
arg_key = arg.split(":")[0].split("=")[0].strip()
if arg_key notin constants:
non_specialized_args += tuples.get(arg_key, [arg])
new_signature = f"def {name}({', '.join(non_specialized_args)}):"
constexpr_lines = [
f" {key}: tl.constexpr = {value}"for key, value in constants.items()
]
new_src = "n".join(["@triton.jit", new_signature] + constexpr_lines + body_lines)
这与传统编译器中的“常量传播 + 函数特化”机制异曲同工。对于 kernel 而言,当 `BLOCK_M`、`BLOCK_N`、数据布局、数据类型以及是否启用特定路径等参数在编译阶段就已确定时,Triton 能够生成更为精简的指令序列,从而有效削减运行时的条件分支判断。
### 7.3 MoE 路由:在 kernel 内部实现稳定排序与 Scatter/Gather
混合专家模型(MoE)的核心性能瓶颈之一,在于 token 到 expert 的路由分配。`routing_details/_routing_compute.py` 文件中的 `_routing_compute_indx` 函数,清晰地展示了一个典型的 GPU 内部路由流程:首先读取 expert 索引,接着构造键值对,然后按照 expert 进行稳定排序,再计算每个 expert 内部的连续运行长度,最终写出用于 scatter/gather 操作的索引。
# 来源:gpt-oss-triton-kernels/torch-ext/gpt_oss_triton_kernels/routing_details/_routing_compute.py
@triton.jit
def _routing_compute_indx(...):
expert = tl.load(ExptIndx + offs, mask=(offs < n_gates), other=-1).to(tl.uint32)
# 将 expert ID 置于高 16 位,本地偏移量置于低 16 位
kv_pairs = ((expert << 16) | local_offs).to(tl.uint32)
kv_pairs = tl.sort(kv_pairs, 0)
expert = kv_pairs >> 16
offs = pid_m * BLOCK_M * N_EXPTS_ACT + (kv_pairs & 0xffff)
# 计算同一 expert 内的相对位置
x = (kv_pairs & 0xffff0000 | 0x00000001)
expts_and_inclusive_run_lengths = tl.associative_scan(x, 0, _keyed_add)
exclusive_run_lengths = (expts_and_inclusive_run_lengths - 1) & 0xffff
tl.store(ScatterIndx + offs, gates, mask=mask)
tl.store(GatherIndx + gates, offs, mask=mask)
这类代码恰好揭示了 `kernels-community` 项目的边界所在:它不仅托管那些“数学上耳熟能详”的经典算子,更囊括了那些对 AI 系统整体吞吐量至关重要的“数据搬运与重排”类 kernel。MoE 模型的性能瓶颈,往往并非矩阵乘法本身,而是死在了路由、排序、scatter/gather、填充以及负载不均衡这些环节上。
## 八、RMSNorm 与 CPU/XPU:高性能并非 CUDA 的专利
### 8.1 CPU SIMD 动态分派:在 AVX512、AVX2 与回退方案间智能选择
`rmsnorm` 子项目展示了面向另一种后端——CPU 与 XPU——的实现方案。CPU 版本会在运行时检测硬件特性,优先采用 AVX512 BF16 指令集,若不可用则尝试 AVX2,最后才会回退到 ATen 的标准实现。
// 来源:rmsnorm/rmsnorm_cpu/rmsnorm_cpu.cpp
void rmsnorm(torch::Tensor &out,
const torch::Tensor &input,
const torch::Tensor &weight,
float epsilon) {
if (CPUFeatures::hasAVX512BF16()) {
rmsnorm_cpu::avx512::rms_norm(out, input, weight, epsilon);
} else if (CPUFeatures::hasAVX2()) {
rmsnorm_cpu::avx2::rms_norm(out, input, weight, epsilon);
} else {
auto input1 = input.to(at::kFloat);
auto variance = at::mean(at::pow(input1, 2), -1, true);
auto hidden_states = at::rsqrt(at::add(variance, epsilon));
out = at::mul(weight, at::mul(input1, hidden_states)).to(input.scalar_type());
}
}
这段代码提醒我们,高性能 AI 系统并非意味着“所有组件都必须用 CUDA 编写”。在实际部署场景中,CPU 预处理、低批次推理、回退路径、Intel XPU 以及 Apple MPS 等环境都会频繁出现。一个成熟的 kernel 分发仓库,必须正视并适应硬件环境的多样性。
### 8.2 Python Autograd 封装:前向与反向传播都是 kernel 资产
### RMSNorm 的 Python 层封装
在 Python 层面,RMSNorm 的实现通过 `torch.autograd.Function` 对前向(forward)和反向(backward)计算进行了封装。该机制的核心在于,它会自动保存反向传播过程中所需的中间张量,确保梯度计算的连续性。
```python
# 来源:rmsnorm/torch-ext/rmsnorm/layers.py
class RMSNormFunction(torch.autograd.Function):
@staticmethod
def forward(ctx, hidden_states, weight, variance_epsilon):
ctx.variance_epsilon = variance_epsilon
output, rstd = ops.apply_rms_norm(hidden_states, weight, variance_epsilon)
ctx.save_for_backward(hidden_states, weight, output, rstd)
return output
@staticmethod
def backward(ctx, grad_output):
hidden_states, weight, output, rstd = ctx.saved_tensors
grads = ops.apply_rms_norm_backward(
grad_output, hidden_states, weight, output, rstd,
ctx.variance_epsilon,
ctx.needs_input_grad[0],
ctx.needs_input_grad[1]
)
return grads[0], grads[1], None
这里的关键点在于:对于训练场景而言,前向 kernel 仅仅是第一步。反向传播 kernel、梯度累加机制以及数值稳定性同样至关重要。在 kernels-community 中,许多子项目都配备了 tests 和 benchmarks 目录,这表明该仓库不仅关注“能否运行”,更关心计算结果的正确性和性能的持续回归。
九、语言分工:为何仓库同时需要 C++、Python、CUDA、Metal
从编程语言的构成来看,该仓库以 C++、Python、CUDA 为主体,同时辅以 Metal、C 和 Objective-C++。这种多语言混合并非偶然,而是由 AI kernel 工程的多层结构所决定的。
9.1 Python:API、Triton JIT 与测试入口
Python 语言主要承担了三类职责:
- 用户 API,例如
activation.silu_and_mul、bitsandbytes_mps.quantize_4bit; - Triton kernel 本体,例如 GPT-OSS 中的 Triton kernels;
- 测试、基准测试以及 README 中的示例代码。
它是离模型开发者最近的一层,也是将 kernel 资产接入 PyTorch 生态系统的核心入口。
9.2 C++:PyTorch 扩展与运行时胶水
C++ 主要负责 Torch op 的注册、schema 定义、设备分派、参数检查以及底层 kernel launcher 的调用。它就像一层 ABI 胶水,将 Python 的动态世界与底层硬件代码无缝连接起来。
典型的调用链路如下:
Python function
-> ._ops custom op
-> torch_binding.cpp 注册的 Torch op
-> CUDA/Metal/CPU/XPU 实现
这条链路越稳定,上层应用就越不需要关心底层的实现差异。
9.3 CUDA 与 Metal:真正靠近硬件的地方
CUDA 负责在 NVIDIA GPU 上实现高性能计算,常见技术包括模板实例化、warp/block 级归约、shared memory 优化、stream 继承以及 dtype 分派。Metal 则负责 Apple Silicon/MPS 平台的实现,通常包含 .metal shader 和 .mm Objective-C++ 桥接代码。
以 bitsandbytes-mps 为例,Metal kernel 通过宏来实例化不同 dtype、blocksize 和 quant type 的组合:
// 来源:bitsandbytes-mps/bitsandbytes_mps/bnb_quantized.metal
#define instantiate_bnb_kernel(name, type, blocksize, quant_type)
template [[host_name(
#name "_" #type "_bs_" #blocksize "_qt_" #quant_type
)]] [[kernel]] decltype(name<type, blocksize, quant_type>)
name<type, blocksize, quant_type>;
#define instantiate_bnb_quant_types(type, blocksize)
instantiate_bnb_all_kernels(type, blocksize, 1)
instantiate_bnb_all_kernels(type, blocksize, 2)
instantiate_bnb_blocksizes(half)
instantiate_bnb_blocksizes(bfloat16_t)
instantiate_bnb_blocksizes(float)
这种思路与 CUDA 的模板实例化如出一辙:尽可能将运行时参数转化为编译期组合,从而生成高度特化的机器代码。
这个仓库的根本意义:让 kernel 成为 AI 生态的一等公民
huggingface/kernels-community的价值,不应仅仅用“里面有多少优化 kernel”来衡量。它更像一个基础设施层面的信号:在模型和数据集之后,底层计算内核也开始被 Hub 化、资产化、版本化、社区化。
对模型开发者,它降低了高性能算子的使用门槛
过去,如果你想在项目里用上某个高性能 CUDA 算子,大概率得手动去 vLLM 仓库里翻找对应的 .cu 文件,然后复制、粘贴、适配。如果你用的是 Apple Silicon,还得先搞懂 Metal command encoder 那一套底层接口。现在,这一切被简化了——你只需要从 get_kernel("kernels-community/activation") 开始,就能直接拉取并使用社区维护好的高性能内核。
对系统工程师,它提供了统一的项目组织规范
每个 kernel 包都遵循一套标准化的结构:README 文档、build.toml 构建配置、flake.nix 环境管理、torch-ext PyTorch 扩展、单元测试、性能基准测试,一应俱全。当你需要新增一个后端实现,或者替换某个现有算子的底层方案时,完全不必改动上层的 API 接口——所有变更都被封装在 kernel 包内部,对调用者透明。
对硬件生态,它让 CUDA 之外的后端更容易融入主流 AI 软件栈
Apple Silicon 上的 bitsandbytes 4-bit 量化、Metal 上的 Paged Attention、CPU 和 XPU 上的 RMSNorm——这些例子都在说明一个事实:kernel 的分发机制不应该只服务于单一 GPU 厂商。kernels-community 让非 CUDA 后端的算子也能以同样的方式被发现、下载、集成,从而降低了异构硬件进入主流 AI 软件链路的门槛。
对 Hugging Face 生态,它补上了模型运行时的底层一环
过去,模型权重可以从 Hub 加载,tokenizer 可以从 Hub 加载,dataset 也可以从 Hub 加载。现在,关键的计算 kernel 同样可以从 Hub 加载。这意味着未来的 AI 应用部署流程可能会发生根本性变化:“安装扩展库”不再是前置条件。取而代之的是,系统根据你运行的模型、使用的设备、以及当前的运行时环境,动态解析并拉取最合适的底层算子。
当然,这种模式也面临着不小的挑战:二进制兼容性、后端覆盖范围、测试矩阵的完备性、性能回归的防范、Hub 安全边界的定义、以及不同 PyTorch/CUDA/Metal 版本之间的适配,这些都是需要认真对待的工程难题。但 kernels-community 至少给出了一个清晰的方向:把散落在各个项目里的高性能实现,收束成可维护、可构建、可发布的 kernel 单元。
如果说大模型时代的上半场是参数规模和模型结构的竞赛,那么下半场一定离不开系统效率。kernels-community 所做的,正是把那些藏在模型背后的底层算子,从“项目内部技巧”变成“生态公共能力”。这也是它最值得关注的地方:它不是又一个 kernel 仓库,而是在尝试重新定义 AI 系统中“底层加速代码”应该如何被生产、分发和使用。
参考资料
[1] hf.co/kernels-community: https://huggingface.co/kernels-community
[2] huggingface.co/docs/kernels/index: https://huggingface.co/docs/kernels/index
关注“鲸栖”小程序,掌握最新AI资讯
本文来自网络搜集,不代表鲸林向海立场,如有侵权,联系删除。转载请注明出处:https://www.itsolotime.com/archives/35121

