Skip to content

experiment: support rust kernels - #724

Draft
drbh wants to merge 2 commits into
mainfrom
support-rust-kernels
Draft

experiment: support rust kernels#724
drbh wants to merge 2 commits into
mainfrom
support-rust-kernels

Conversation

@drbh

@drbh drbh commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

this pr explores extending the kernel builder to support kernels written in rust. at the moment kernels are expected to be written a cpp or pure python. this pr updates the build process to:

  • add two new backends rust-cpu and rust-cuda
  • enable defining kernels written in rust in the build.toml
  • update cmake build step to compile rust with cargo
  • add cuda-oxide to rust build env

the goal of this experiment is to explore the recently released https://github.com/NVlabs/cuda-oxide library as a backend for hf kernels so developers would have the option to author kernels purely in rust.

additionally this change set is used in a small proof of concept rust impl of flash attn2 using cuda-oxide and tvmffi bindings here https://github.com/drbh/flash-attn2-oxide

the published result can be seen https://huggingface.co/kernels/drbh/flash-attn2-oxide and can be run in comparison to the reference kernel

# /// script
# requires-python = ">=3.13"
# dependencies = [
#     "apache-tvm-ffi>=0.1.12",
#     "kernels>=0.16.0",
#     "numpy>=2.5.1",
#     "torch>=2.13.0",
# ]
# ///
import math

import torch
from kernels import get_kernel

oxide = get_kernel("drbh/flash-attn2-oxide", version=1, trust_remote_code=True)
reference = get_kernel("kernels-community/flash-attn2", version=2)

torch.manual_seed(0)
b, s, h, d = 2, 128, 8, 64
q = torch.randn(b, s, h, d, device="cuda", dtype=torch.float16)
k = torch.randn(b, s, h, d, device="cuda", dtype=torch.float16)
v = torch.randn(b, s, h, d, device="cuda", dtype=torch.float16)
scale = 1.0 / math.sqrt(d)

for causal in (False, True):
    # fwd is positional: (q, k, v, alibi_slopes, softmax_scale, is_causal,
    # window_size_left, window_size_right, softcap)
    out, lse = oxide.fwd(q, k, v, None, scale, causal, -1, -1, 0.0)
    ref = reference.flash_attn_func(
        q, k, v, dropout_p=0.0, softmax_scale=scale, causal=causal
    )
    ref = ref[0] if isinstance(ref, (tuple, list)) else ref

    max_diff = (out.float() - ref.float()).abs().max().item()
    ok = torch.allclose(out.float(), ref.float(), atol=2e-3, rtol=2.5e-2)
    print(f"causal={causal!s:<5} max|diff|={max_diff:.2e} {'ok' if ok else 'MISMATCH'}")
    assert ok

then run

uv run example/compare.py

and it outputs

causal=False max|diff|=4.88e-04 ok
causal=True  max|diff|=4.88e-04 ok

Warning

this work is an experiment, and may change or be closed at any point in time

@HuggingFaceDocBuilderDev

Copy link
Copy Markdown

The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update.

@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown

Coverage report — kernels/

Measured on: Python 3.10 / Torch 2.13.0.
Other CI configurations are not included in this number.
Hardware-gated code paths (ROCm/XPU/NPU/Darwin/Windows) are excluded or unreachable on the Linux+CUDA runner.

Total coverage: 84.3% — threshold: 80% — ✅

Per-file breakdown
Name Stmts Miss Cover Missing
src/kernels/__init__.py 13 0 100%
src/kernels/_system.py 6 1 83% 10
src/kernels/_versions.py 63 7 89% 46, 49, 52-53, 56-57, 100
src/kernels/backends.py 212 62 71% 40, 44, 48-51, 68, 90, 108, 117, 121, 125-127, 148, 157, 161, 165-167, 188, 199, 201, 208-211, 224, 228, 232-252, 260, 283-303
src/kernels/compat.py 8 1 88% 5
src/kernels/deps.py 58 4 93% 59-60, 101, 104
src/kernels/hf_hub.py 62 5 92% 18, 20, 114, 136-137
src/kernels/importer.py 48 3 94% 106, 110, 113
src/kernels/install.py 50 5 90% 78, 116-117, 155, 173
src/kernels/layer/__init__.py 6 0 100%
src/kernels/layer/_interval_tree.py 103 4 96% 23, 52, 147, 150
src/kernels/layer/device.py 48 14 71% 42, 47-49, 91, 96-98, 101, 149, 152, 155-157
src/kernels/layer/func.py 82 7 91% 83, 113, 185, 303, 309, 322, 340
src/kernels/layer/globals.py 5 0 100%
src/kernels/layer/kernelize.py 74 8 89% 255, 281, 289-290, 296, 300, 316-318
src/kernels/layer/layer.py 211 16 92% 169, 212, 218, 231, 339, 419-420, 432, 441, 449, 460, 489, 493, 506, 559, 589
src/kernels/layer/mode.py 14 0 100%
src/kernels/layer/repos.py 144 42 71% 27, 33, 36-43, 63-64, 70, 73-76, 90, 94, 103-104, 110, 113-116, 123-124, 130, 133-136, 143-144, 150, 153-156, 163-164, 170, 173-176, 257
src/kernels/load.py 74 9 88% 218, 224, 230-231, 252-264
src/kernels/locking.py 108 70 35% 42-109, 113-136, 144, 148-155, 159-169, 173-180
src/kernels/status.py 49 2 96% 23, 81
src/kernels/variants.py 278 22 92% 64, 95, 116, 146, 255-256, 298-301, 303, 387-394, 400-406, 437-443, 455-461, 611-613
src/kernels/verify.py 88 1 99% 32
TOTAL 1804 283 84%

Updated by the Test kernels workflow on commit d4bfd09481df7803c8dc31464720e81b4615b415.

@danieldk
danieldk self-requested a review August 20, 2026 15:59
Comment on lines +370 to +373
The crate is built by cargo as a staticlib and whole-archive linked into the
extension, so it must export the `__tvm_ffi_*` symbols itself. Rust kernels
currently require the `[tvm-ffi]` framework, and the project root must include
a `Cargo.lock`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to be sure: Rust doesn't mark the linkage of any symbols to evade dlopen's RTLD_LOCAL right? I wouldn't expect so, but just to be sure.

depends = []
src = ["Cargo.toml", "Cargo.lock", "src"]
device-manifest = "kernels/Cargo.toml"
ptx-dir = "kernels-ptx"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth exposing this? Maybe we should just use a standard directory?

dsl = "cuda-oxide"
depends = []
src = ["Cargo.toml", "Cargo.lock", "src"]
device-manifest = "kernels/Cargo.toml"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does device-manifest mean here?

backend = "cuda"
dsl = "cuda-oxide"
depends = []
src = ["Cargo.toml", "Cargo.lock", "src"]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

src? Don't we always explicitly specify all files? I'm surprised that this works?

}
}

fn main() {}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm surprised that this is not built as a lib?

Comment on lines +144 to +149
cudaOxideSrc = pkgs.fetchFromGitHub {
owner = "drbh";
repo = "cuda-oxide";
rev = "bc5f33bb1d556671c35e475439bb821d25caa2cb";
hash = "sha256-ZIpS0XFDI9yDSXk33OyQ3mGB5gRhE7tldmmex226tLA=";
};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably be in the overlay, maybe in an attr-set in there.

Comment on lines +151 to +176
cudaOxideRust =
if rust-bin != null then
rust-bin.fromRustupToolchainFile "${cudaOxideSrc}/rust-toolchain.toml"
else
rustc;
cudaOxideRustPlatform = pkgs.makeRustPlatform {
cargo = cudaOxideRust;
rustc = cudaOxideRust;
};
cudaOxideBackend = cudaOxideRustPlatform.buildRustPackage {
pname = "rustc-codegen-cuda";
version = "0.2.1";
src = cudaOxideSrc;
sourceRoot = "source/crates/rustc-codegen-cuda";
cargoLock = {
lockFile = "${cudaOxideSrc}/crates/rustc-codegen-cuda/Cargo.lock";
allowBuiltinFetchGit = true;
};
buildInputs = with pkgs; [
libffi
libxml2
zstd
zlib
];
doCheck = false;
};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should probably be factored out somewhere as an mk.* function.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading the rest of the file, maybe we should create a separate arch-rust.nix, at least for now?

Comment on lines +214 to +221
cargoDeps =
if hasRustKernels then
rustPlatform.importCargoLock {
lockFile = src + "/Cargo.lock";
allowBuiltinFetchGit = true;
}
else
null;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's good to have this in the environment for C++ builds (even if set to null).

Comment on lines +258 to +261
preInstallCheck = lib.optionalString (hasRustKernels && cudaSupport) ''
addToSearchPath LD_LIBRARY_PATH "${lib.getOutput "stubs" cudaPackages.cuda_cudart}/lib/stubs"
export LD_LIBRARY_PATH
'';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LD_LIBRARY_PATH inside a derivation typically hides another issue.

Comment on lines +275 to +276
if rust-bin != null && builtins.pathExists (src + "/rust-toolchain.toml") then
[ (rust-bin.fromRustupToolchainFile (src + "/rust-toolchain.toml")) ]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to allow custom toolchains? Also, it seems that it can differ from cuda-oxide?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants