Generate your first kernel
With your reference machine set up, generating a kernel is four steps: start a runner on the target machine, then export, generate, and inspect from the reference machine.
graph LR
A[1. Start a runner] --> B[2. Export] --> C[3. Generate] --> D[4. Inspect]
Run them yourself below, or skip to the agent and describe the job in plain English.
We'll use this W4A16 weight-only quantized GEMM throughout — a naive reference (it unpacks the 4-bit weights to a full matrix and runs torch.matmul) whose whole point is to be fused, so it's the perfect first model. A model file needs a Model class plus two factories — get_inputs() returning the example forward inputs and get_init_inputs() returning the constructor args (Model(M, N, K) here). Save it as source.py:
source.py — W4A16 weight-only quantized GEMM
# source.py
from __future__ import annotations
import torch
import torch.nn as nn
GROUP_SIZE = 128
def _pack_int4(w_q: torch.Tensor) -> torch.Tensor:
"""Pack (K, N) uint8 in [0,15] into (K//2, N) uint8 (even rows low nibble, odd rows high)."""
K, N = w_q.shape
assert K % 2 == 0
lo = w_q[0::2].to(torch.uint8) & 0xF
hi = w_q[1::2].to(torch.uint8) & 0xF
return (lo | (hi << 4)).contiguous()
def _unpack_int4(w_packed: torch.Tensor, K: int) -> torch.Tensor:
"""Unpack (K//2, N) uint8 -> (K, N) uint8 in [0,15]."""
Kh, N = w_packed.shape
assert Kh * 2 == K
out = torch.empty((K, N), dtype=torch.uint8, device=w_packed.device)
out[0::2] = w_packed & 0xF
out[1::2] = (w_packed >> 4) & 0xF
return out
class Model(nn.Module):
"""W4A16 GEMM: y = x @ dequant(w_q, scales, zeros)."""
def __init__(self, M: int, N: int, K: int, group_size: int = GROUP_SIZE):
super().__init__()
assert K % group_size == 0, "K must be divisible by group_size"
assert K % 2 == 0, "K must be even (int4 packing)"
self.M, self.N, self.K = M, N, K
self.group_size = group_size
n_groups = K // group_size
torch.manual_seed(0xC0DE ^ (M * 1315423911 + N * 2654435761 + K))
w_full = torch.randn(K, N, dtype=torch.float32) * 0.02
w_g = w_full.view(n_groups, group_size, N)
w_min = w_g.min(dim=1, keepdim=True).values
w_max = w_g.max(dim=1, keepdim=True).values
scales = (w_max - w_min).clamp_min(1e-8) / 15.0
zeros = (-w_min / scales).round().clamp(0, 15)
w_q = ((w_g / scales) + zeros).round().clamp(0, 15).to(torch.uint8).view(K, N)
self.register_buffer("w_q", _pack_int4(w_q)) # uint8, (K//2, N)
self.register_buffer("scales", scales.squeeze(1).to(torch.bfloat16)) # bf16, (n_groups, N)
self.register_buffer("zeros", zeros.squeeze(1).to(torch.bfloat16)) # bf16, (n_groups, N)
def forward(self, x: torch.Tensor) -> torch.Tensor:
K = self.K
w_unpacked = _unpack_int4(self.w_q, K).to(torch.bfloat16)
scales = self.scales.repeat_interleave(self.group_size, dim=0)
zeros = self.zeros.repeat_interleave(self.group_size, dim=0)
w_bf = (w_unpacked - zeros) * scales
return x.to(torch.bfloat16) @ w_bf
M = 1
N = 12288
K = 4096
def get_inputs():
return [torch.randn(M, K, dtype=torch.bfloat16)]
def get_init_inputs():
return [M, N, K]
Step 1 — Start a runner
A kernel is scored on the target GPU, not in the cloud — so you first put an eval worker there: a yasp-eval container that detects the hardware, registers with the Eval router, and then benchmarks every candidate kernel KernelGen routes to it. You deploy it once; it stays up and serves all your generations.
Deploy one on the target — see Gaia workers (single host with Docker, or Helm for a Kubernetes cluster). Then confirm it registered and note its GPU Name — the value you pass to --gpu-name in step 3:
yasp-toolkit eval workers
Step 2 — Export the reference
On the reference machine, turn source.py into the reference.pt2 the generator consumes — a torch.export program with its weights bundled:
yasp-toolkit kernelgen export \
--input source.py \
--output build/source.pt2
export traces the model with torch.export in inference mode, decomposes it to Core ATen IR, and saves it with the weights bundled. It creates build/ itself. See kernelgen export.
Model won't export?
If export can't trace your model, send the exact error (and your source.py if you can share it) to support@yasp.ai.
Step 3 — Generate
One command creates the task, uploads source.pt2, runs the generation, and downloads the result. Use your worker's GPU Name from step 1:
export GPU="<GPU Name from `yasp-toolkit eval workers`>"
yasp-toolkit kernelgen compile \
--reference build/source.pt2 \
--gpu-name "$GPU" \
--output build/module_source.py \
--report build/report.json
The task id prints immediately, then a progress bar streams the upload and a spinner tracks the generation. On success build/module_source.py holds the optimized kernel and build/report.json the compilation report. See kernelgen compile for --config tuning and routing options.
Generation failed?
Check the job's state with yasp-toolkit kernelgen get <TASK_ID> --wait (the task id is printed when the task starts) — it polls until the job reaches a terminal state and exits non-zero if it failed. Send the id and the exact error to support@yasp.ai.
Step 4 — Inspect
Read the generated kernel and its report:
cat build/module_source.py # the optimized, fused kernel source
cat build/report.json # timing, accuracy, and what the generator changed
report.json records how the generated kernel scored on the worker's GPU against the reference; module_source.py holds the generated source — the W4A16 unpack and the GEMM fused instead of materializing the full bf16 weight matrix.
Prefer the agent?
The same four steps, by conversation. Launch the shell and describe the job:
yasp
A startup check verifies your environment first; then the bundled skills handle the rest — e.g. "Deploy an eval worker to gpu-lab-03, then generate a fused kernel from source.py and show me the speedup." It deploys the worker, exports the reference, runs the end-to-end generation, and reports how the kernel scored.
Where to next?
Your own models follow the exact same four steps — point export at a different source.py and reuse the running worker for every generation. From here:
- Gaia workers overview — deploy and manage the eval workers that back generation.
kernelgenreference —export,compile, andgetin one place.evalreference — score candidate kernels against a reference witheval compare, and inspect evaluators.- CLI reference — every
yaspandyasp-toolkitcommand, flag by flag. Session management (yasp --list,--resume) lives on theyasppage.