atlas-kernels
Role: the bridge between the CUDA source tree and the Rust workspace. Every PTX module every other crate launches is defined here.
Key file: src/lib.rs (hand-written glue, ~50 lines) + build.rs (auto-generates the heavy file).
The trick: auto-generated PTX embedding
atlas-kernels/src/lib.rs ends with a single line:
#![allow(unused)] fn main() { include!(concat!(env!("OUT_DIR"), "/target_ptx.rs")); }
Everything inside target_ptx.rs — per-target PTX byte constants, the ptx_modules() lookup function, the all_ptx_sets() multi-target registry — is produced by build.rs at every Cargo build. You will not find target_ptx.rs in the repository; it is generated fresh into OUT_DIR each time.
The generated file looks roughly like:
#![allow(unused)] fn main() { pub static PTX_GB10_QWEN3_NVFP4: &[PtxModule] = &[ PtxModule { name: "prefill_attn_v47", ptx: include_bytes!("...sm121/prefill_v47.ptx") }, PtxModule { name: "decode_attn", ptx: include_bytes!("...sm121/decode_attn.ptx") }, PtxModule { name: "moe_w4a16", ptx: include_bytes!("...sm121/moe_w4a16.ptx") }, // ~35 modules per target ]; pub static PTX_GB10_QWEN35_NVFP4: &[PtxModule] = &[ /* ... */ ]; pub fn ptx_modules(target: &KernelTarget) -> Option<&'static [PtxModule]> { match (target.arch, target.model, target.quant) { ("sm_121", "qwen3-next-80b-a3b", "nvfp4") => Some(PTX_GB10_QWEN3_NVFP4), ("sm_121", "qwen3.5-35b-a3b", "nvfp4") => Some(PTX_GB10_QWEN35_NVFP4), // ... _ => None, } } }
At runtime, spark-server::main resolves the current model's KernelTarget, calls ptx_modules(&target), and passes the resulting slice to AtlasCudaBackend::new which uploads each PTX module to the GPU via cuModuleLoadData.
What build.rs actually does
- Read
ATLAS_TARGET_*— three env vars (HW,MODEL,QUANT). Wildcards (*) expand to "every matching directory". - Walk
kernels/<hw>/<model>/<quant>/— for each leaf that matches the wildcards, readHARDWARE.toml,MODEL.toml,KERNEL.toml. - Resolve the compiler —
resolve_compute_target(vendor)returns aBox<dyn ComputeTarget>. Today alwaysNvidiaTarget { nvcc }. - Compile every source file — for each
*.cuin the leaf, callcompute_target.compile(src, out, arch, flags). Flags come fromKERNEL.toml'sextra_nvcc_flags = [...]plus the arch-specific ones fromHARDWARE.toml. - Apply module-name overrides —
KERNEL.toml's[modules]section lets kernels with different file stems (e2m1_branchless.cu) expose themselves under shorter module names (e2m1). This is cosmetic but keepsGpuBackend::kernel("e2m1", "convert_f32_to_e2m1")readable at the call site. - Parse
MODEL.toml→SamplingPresets+ModelBehavior— non-kernel metadata that the server consumes directly (defaulttemperature,thinking_budget, etc.). Emitted as Rust constants alongside the PTX. - Write
target_ptx.rs— onePtxModulearray per target, the dispatch match, and aconst ALL_TARGETS: &[KernelTarget]listing everything that got compiled.
The whole phase is idempotent — rerun-if-changed directives on the kernel tree mean cargo only recompiles what changed.
ATLAS_SKIP_BUILD=1 — the escape hatch
On a Linux laptop with no nvcc, the crate would fail to build without this. The escape hatch is a single env var. When set, build.rs:
- Does not invoke any compiler.
- Emits a stub
target_ptx.rswith an emptyALL_TARGETSandptx_modulesreturningNonefor everything. - The crate compiles cleanly.
ci.yml uses this. So does local cargo clippy. The Kernel Dispatch chapter covers the broader flow.
Per-target PTX bytes — sizes and counts
Rough numbers for the default multi-model build at avarok/atlas-gb10:latest:
| Target | # kernels | PTX bytes (approx) |
|---|---|---|
| GB10 / Qwen3.5-35B-A3B / NVFP4 | 35 | ~5.4 MB |
| GB10 / Qwen3-Next-80B-A3B / NVFP4 | 35 | ~5.5 MB |
| GB10 / Qwen3.5-122B-A10B / NVFP4 | 35 | ~5.5 MB |
| GB10 / Nemotron-3-Nano / NVFP4 | 33 | ~4.8 MB |
| GB10 / Nemotron-3-Super / NVFP4 | 33 | ~4.8 MB |
| GB10 / Mistral-Small-4 / NVFP4 | 31 | ~4.2 MB |
| GB10 / MiniMax-M2.7 / NVFP4 | 38 | ~6.1 MB |
| GB10 / Qwen3.6 / FP8 | 34 | ~5.2 MB |
| GB10 / Gemma-4 / NVFP4 (×2 flavors) | 29 | ~4.0 MB ea |
| GB10 / Qwen3-VL / NVFP4 | 40 (incl. ViT) | ~6.6 MB |
Total embedded PTX in the default multi-model binary: ~65 MB. The binary itself lands at ~200 MB in release builds. This is why the Docker image is ~8 GB once you add the CUDA userspace (libcudart, libnvrtc), tokenizer deps, and Ubuntu base — the actual Atlas footprint is small.
What gets added to this crate when you…
- …add a new
(hw, model, quant)leaf? Nothing in theatlas-kernels/src/directory.build.rspicks it up automatically on the nextcargo build. You do need to add a matchingKernelTargetconst inatlas-core::target::KernelTargetso downstream code can refer to it by name. - …add a new kernel to an existing leaf? Drop the
.cuin the leaf directory;build.rspicks it up. If you want a non-stem module name, add an entry to the leaf'sKERNEL.toml. - …add a new hardware vendor? Extend
resolve_compute_target(vendor)inbuild.rsto return your newComputeTargetimpl. Everything else flows from there.
The rest of the kernel-engineering story is in the CUDA Kernel Engineering deep dive.