model-as-a-kernel v1 source
Browse files- README.md +122 -0
- build.toml +2 -2
README.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
library_name: kernels
|
| 3 |
+
license: apache-2.0
|
| 4 |
+
---
|
| 5 |
+
|
| 6 |
+
# model-as-a-kernel
|
| 7 |
+
|
| 8 |
+
An entire llama-family decode step as one kernel launch, loadable through
|
| 9 |
+
`kernels`. A persistent phase-interpreter kernel executes the whole forward
|
| 10 |
+
pass (embedding read, every layer's RMSNorms, QKV projection, rope, attention
|
| 11 |
+
over the KV cache, SwiGLU MLP, the LM head, and the greedy argmax) inside a
|
| 12 |
+
single launch, crossing a software grid barrier between phases instead of
|
| 13 |
+
returning to the host. Greedy generation is closed-loop on the device: the
|
| 14 |
+
argmax of one launch feeds the embedding read of the next, so a token costs
|
| 15 |
+
exactly one kernel launch, no framework code, no host synchronization, and
|
| 16 |
+
nothing on the CPU between tokens.
|
| 17 |
+
|
| 18 |
+
## How it works
|
| 19 |
+
|
| 20 |
+
At model load the Python side packs the weights (bf16, contiguous) and
|
| 21 |
+
compiles the architecture into a program: an int64 `[n_phases, 16]` tensor
|
| 22 |
+
whose rows name an op (embed, fused rmsnorm+GEMV, qk-norm+rope+cache-append,
|
| 23 |
+
attention, swiglu+GEMV, argmax) and carry its pointers and sizes. One
|
| 24 |
+
persistent kernel interprets the program; all resident blocks execute each
|
| 25 |
+
phase cooperatively, then cross a sense-reversing global barrier. The launch
|
| 26 |
+
geometry comes from the occupancy API, so every block is co-resident and the
|
| 27 |
+
barrier cannot deadlock. A phase-per-launch debug mode runs the identical
|
| 28 |
+
device code one phase at a time; it is bitwise identical to the fused mode by
|
| 29 |
+
construction and gives per-phase cudaEvent timings.
|
| 30 |
+
|
| 31 |
+
Arithmetic reproduces transformers eager bf16 semantics op for op: bf16
|
| 32 |
+
storage with fp32 accumulation and one rounding per op boundary, the RMSNorm
|
| 33 |
+
double rounding (normalize, round, weight multiply, round), fp32 softmax with
|
| 34 |
+
the probabilities cast to bf16 before the PV product, bf16 rope multiplies
|
| 35 |
+
with cos/sin rounded to bf16, and scores rounded to bf16 before and after the
|
| 36 |
+
1/sqrt(D) scaling, as eager attention does. Every reduction uses a fixed
|
| 37 |
+
tree, so the kernel is bitwise deterministic run to run: no atomics touch
|
| 38 |
+
floating-point data anywhere.
|
| 39 |
+
|
| 40 |
+
## Usage
|
| 41 |
+
|
| 42 |
+
```python
|
| 43 |
+
from kernels import get_kernel
|
| 44 |
+
|
| 45 |
+
mak = get_kernel("phanerozoic/model-as-a-kernel", version=1, trust_remote_code=True)
|
| 46 |
+
|
| 47 |
+
mm = mak.MegaModel.from_pretrained("HuggingFaceTB/SmolLM2-135M") # any plain
|
| 48 |
+
# llama-family checkpoint: SmolLM2, TinyLlama, Qwen3 dense, Llama without
|
| 49 |
+
# rope scaling. Pass a transformers model object to reuse loaded weights.
|
| 50 |
+
|
| 51 |
+
tokens = mm.generate(prompt_ids, max_new=128) # one launch per token
|
| 52 |
+
logits = mm.decode_step(token_id, pos) # one launch, fp32 [V] logits
|
| 53 |
+
logits = mm.prefill(prompt_ids) # sequential decode steps
|
| 54 |
+
logits, ms = mm.decode_step_timed(token_id, pos) # per-phase cudaEvent ms
|
| 55 |
+
```
|
| 56 |
+
|
| 57 |
+
`version` selects the release branch; `trust_remote_code` is required by
|
| 58 |
+
`kernels` for publishers without the trusted-publisher mark. `from_pretrained`
|
| 59 |
+
needs `transformers` only if you pass a repo id string.
|
| 60 |
+
|
| 61 |
+
## API
|
| 62 |
+
|
| 63 |
+
| Symbol | Purpose |
|
| 64 |
+
|---|---|
|
| 65 |
+
| `MegaModel.from_pretrained(model_or_id, device, max_seq, max_gen)` | pack a llama-family checkpoint into a program |
|
| 66 |
+
| `MegaModel(weights, config, ...)` | direct construction from tensors |
|
| 67 |
+
| `decode_step(token, pos, phased=False)` | one decode step, returns live fp32 logits `[V]` |
|
| 68 |
+
| `prefill(ids)` | feed a prompt token by token |
|
| 69 |
+
| `generate(prompt_ids, max_new)` | greedy generation, closed loop on device |
|
| 70 |
+
| `decode_step_timed(token, pos)` | phase-per-launch mode with per-phase ms |
|
| 71 |
+
| `ops.mak_run / mak_run_steps / mak_run_phased / mak_num_blocks` | raw program launches |
|
| 72 |
+
|
| 73 |
+
Supported architecture: RMSNorm decoder blocks with rotary attention (GQA,
|
| 74 |
+
optional Qwen3-style per-head qk RMSNorm), SwiGLU MLP, no attention or MLP
|
| 75 |
+
biases, plain unscaled rope, batch size 1, single device. `max_seq` bounds
|
| 76 |
+
the KV cache, `max_gen` the device-side token output buffer.
|
| 77 |
+
|
| 78 |
+
## Correctness
|
| 79 |
+
|
| 80 |
+
bf16 transformers are order-sensitive: transformers itself does not
|
| 81 |
+
reproduce its own logits between a batched forward and cache-stepped decode
|
| 82 |
+
(the two differ by whole bf16 ulps that amplify layer over layer), so network
|
| 83 |
+
outputs are only defined up to that reproduction band, and the correctness
|
| 84 |
+
contract has two parts.
|
| 85 |
+
|
| 86 |
+
Op-level, checked bitwise against eager-semantics replication in torch:
|
| 87 |
+
the embedding read, the fused rmsnorm+GEMV, rope against
|
| 88 |
+
`apply_rotary_pos_emb` (cos/sin tables and rotated K exact), attention at
|
| 89 |
+
S=1, and the layer-0 V cache across all positions are `torch.equal` to the
|
| 90 |
+
reference.
|
| 91 |
+
|
| 92 |
+
Whole-model, on SmolLM2-135M over a 96-position teacher-forced sequence:
|
| 93 |
+
the deviation from cache-stepped transformers eager must sit inside twice
|
| 94 |
+
transformers' own cache-vs-batch band with comparable argmax agreement.
|
| 95 |
+
Measured on RTX 6000 Ada (sm89): transformers self-band 1.94 max-abs with
|
| 96 |
+
95/96 argmax agreement; this kernel vs cache-stepped eager 2.50 max-abs with
|
| 97 |
+
90/96 agreement. The fused single-launch mode, the phase-per-launch mode, and
|
| 98 |
+
repeated runs are mutually bitwise identical.
|
| 99 |
+
|
| 100 |
+
## Measured
|
| 101 |
+
|
| 102 |
+
Greedy decode, SmolLM2-135M bf16, 128 tokens with a 32-token prompt,
|
| 103 |
+
against `transformers` eager `generate` on the same device:
|
| 104 |
+
|
| 105 |
+
| GPU | this kernel | transformers eager | speedup |
|
| 106 |
+
|---|---|---|---|
|
| 107 |
+
| RTX 6000 Ada (sm89) | 378 tok/s | 37 tok/s | 10.3x |
|
| 108 |
+
|
| 109 |
+
Per-phase cudaEvent brackets at position 512 attribute the remaining time to
|
| 110 |
+
the attention phases (76% in phase-per-launch mode), which are latency-bound
|
| 111 |
+
at one block per head; the LM-head GEMV already streams at ~700 GB/s. The
|
| 112 |
+
speedup comes from removing all inter-op framework overhead: at batch 1 a
|
| 113 |
+
135M-parameter step is ~180 launches of microseconds-scale kernels under
|
| 114 |
+
eager execution, and exactly one launch here.
|
| 115 |
+
|
| 116 |
+
## Limits
|
| 117 |
+
|
| 118 |
+
Batch size 1, decode only (prefill runs as sequential decode steps), greedy
|
| 119 |
+
sampling in-kernel (route the logits to an external sampler for anything
|
| 120 |
+
else), llama-family architectures without rope scaling or attention biases,
|
| 121 |
+
one CUDA device at a time. The program bakes device pointers, so the
|
| 122 |
+
`MegaModel` owns its weights and buffers for its lifetime.
|
build.toml
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
[general]
|
| 2 |
-
name = "
|
| 3 |
version = 1
|
| 4 |
edition = 5
|
| 5 |
license = "Apache-2.0"
|
|
@@ -16,7 +16,7 @@ src = [
|
|
| 16 |
"torch-ext/torch_binding.h",
|
| 17 |
]
|
| 18 |
|
| 19 |
-
[kernel.
|
| 20 |
backend = "cuda"
|
| 21 |
cuda-capabilities = ["8.0", "8.6", "8.9", "9.0", "10.0", "12.0"]
|
| 22 |
depends = ["torch"]
|
|
|
|
| 1 |
[general]
|
| 2 |
+
name = "model-as-a-kernel"
|
| 3 |
version = 1
|
| 4 |
edition = 5
|
| 5 |
license = "Apache-2.0"
|
|
|
|
| 16 |
"torch-ext/torch_binding.h",
|
| 17 |
]
|
| 18 |
|
| 19 |
+
[kernel.model-as-a-kernel]
|
| 20 |
backend = "cuda"
|
| 21 |
cuda-capabilities = ["8.0", "8.6", "8.9", "9.0", "10.0", "12.0"]
|
| 22 |
depends = ["torch"]
|