phanerozoic commited on
Commit
4bca4f5
·
verified ·
1 Parent(s): af194d8

Uploaded using `kernel-builder`.

Browse files
Files changed (45) hide show
  1. build/torch211-cxx11-cu126-x86_64-linux/__init__.py +964 -0
  2. build/torch211-cxx11-cu126-x86_64-linux/_model_as_a_kernel_cuda_e33875a.abi3.so +3 -0
  3. build/torch211-cxx11-cu126-x86_64-linux/_ops.py +9 -0
  4. build/torch211-cxx11-cu126-x86_64-linux/metadata.json +36 -0
  5. build/torch211-cxx11-cu126-x86_64-linux/model_as_a_kernel/__init__.py +26 -0
  6. build/torch211-cxx11-cu128-x86_64-linux/__init__.py +964 -0
  7. build/torch211-cxx11-cu128-x86_64-linux/_model_as_a_kernel_cuda_e33875a.abi3.so +3 -0
  8. build/torch211-cxx11-cu128-x86_64-linux/_ops.py +9 -0
  9. build/torch211-cxx11-cu128-x86_64-linux/metadata.json +38 -0
  10. build/torch211-cxx11-cu128-x86_64-linux/model_as_a_kernel/__init__.py +26 -0
  11. build/torch211-cxx11-cu130-x86_64-linux/__init__.py +964 -0
  12. build/torch211-cxx11-cu130-x86_64-linux/_model_as_a_kernel_cuda_e33875a.abi3.so +3 -0
  13. build/torch211-cxx11-cu130-x86_64-linux/_ops.py +9 -0
  14. build/torch211-cxx11-cu130-x86_64-linux/metadata.json +38 -0
  15. build/torch211-cxx11-cu130-x86_64-linux/model_as_a_kernel/__init__.py +26 -0
  16. build/torch212-cxx11-cu126-x86_64-linux/__init__.py +964 -0
  17. build/torch212-cxx11-cu126-x86_64-linux/_model_as_a_kernel_cuda_e33875a.abi3.so +3 -0
  18. build/torch212-cxx11-cu126-x86_64-linux/_ops.py +9 -0
  19. build/torch212-cxx11-cu126-x86_64-linux/metadata.json +36 -0
  20. build/torch212-cxx11-cu126-x86_64-linux/model_as_a_kernel/__init__.py +26 -0
  21. build/torch212-cxx11-cu130-x86_64-linux/__init__.py +964 -0
  22. build/torch212-cxx11-cu130-x86_64-linux/_model_as_a_kernel_cuda_e33875a.abi3.so +3 -0
  23. build/torch212-cxx11-cu130-x86_64-linux/_ops.py +9 -0
  24. build/torch212-cxx11-cu130-x86_64-linux/metadata.json +38 -0
  25. build/torch212-cxx11-cu130-x86_64-linux/model_as_a_kernel/__init__.py +26 -0
  26. build/torch212-cxx11-cu132-x86_64-linux/__init__.py +964 -0
  27. build/torch212-cxx11-cu132-x86_64-linux/_model_as_a_kernel_cuda_e33875a.abi3.so +3 -0
  28. build/torch212-cxx11-cu132-x86_64-linux/_ops.py +9 -0
  29. build/torch212-cxx11-cu132-x86_64-linux/metadata.json +38 -0
  30. build/torch212-cxx11-cu132-x86_64-linux/model_as_a_kernel/__init__.py +26 -0
  31. build/torch213-cxx11-cu126-x86_64-linux/__init__.py +964 -0
  32. build/torch213-cxx11-cu126-x86_64-linux/_model_as_a_kernel_cuda_e33875a.abi3.so +3 -0
  33. build/torch213-cxx11-cu126-x86_64-linux/_ops.py +9 -0
  34. build/torch213-cxx11-cu126-x86_64-linux/metadata.json +36 -0
  35. build/torch213-cxx11-cu126-x86_64-linux/model_as_a_kernel/__init__.py +26 -0
  36. build/torch213-cxx11-cu130-x86_64-linux/__init__.py +964 -0
  37. build/torch213-cxx11-cu130-x86_64-linux/_model_as_a_kernel_cuda_e33875a.abi3.so +3 -0
  38. build/torch213-cxx11-cu130-x86_64-linux/_ops.py +9 -0
  39. build/torch213-cxx11-cu130-x86_64-linux/metadata.json +38 -0
  40. build/torch213-cxx11-cu130-x86_64-linux/model_as_a_kernel/__init__.py +26 -0
  41. build/torch213-cxx11-cu132-x86_64-linux/__init__.py +964 -0
  42. build/torch213-cxx11-cu132-x86_64-linux/_model_as_a_kernel_cuda_e33875a.abi3.so +3 -0
  43. build/torch213-cxx11-cu132-x86_64-linux/_ops.py +9 -0
  44. build/torch213-cxx11-cu132-x86_64-linux/metadata.json +38 -0
  45. build/torch213-cxx11-cu132-x86_64-linux/model_as_a_kernel/__init__.py +26 -0
build/torch211-cxx11-cu126-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,964 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """model-as-a-kernel: an entire llama-family decode step as one kernel launch.
2
+
3
+ A phase interpreter runs the whole forward pass (embedding, every layer's
4
+ norms, QKV, rope, attention over the KV cache, SwiGLU MLP, the LM head, and
5
+ the greedy argmax) inside a single persistent kernel, with a software grid
6
+ barrier between phases. The in-kernel step loop makes an entire greedy
7
+ generation, prompt consumption included, one kernel launch: each step's
8
+ argmax feeds the next step's embedding read without leaving the device.
9
+ """
10
+ import os
11
+ import struct
12
+ from typing import List, Optional, Sequence
13
+
14
+ import torch
15
+
16
+ try:
17
+ from ._ops import ops
18
+ except ImportError: # local development build (torch.utils.cpp_extension)
19
+ class _LocalOps:
20
+ def __getattr__(self, name):
21
+ return getattr(torch.ops.mak_ext, name)
22
+
23
+ ops = _LocalOps()
24
+
25
+ __all__ = ["MegaModel"]
26
+
27
+ _OP_EMBED, _OP_GEMV, _OP_QKV_POST, _OP_ATTN = 0, 1, 2, 3
28
+ _OP_ARGMAX_PART, _OP_ARGMAX_FIN = 4, 5
29
+ _OP_KV_APPEND, _OP_NORMRES, _OP_PLEMIX = 6, 7, 8
30
+ _OP_GEMV_PLAIN, _OP_NORMB, _OP_GLUB, _OP_ATTNFINB = 9, 10, 11, 12
31
+ _OP_GEMV_Q4 = 13
32
+ _IT_NONE, _IT_RMSNORM, _IT_SWIGLU, _IT_ATTNFIN = 0, 1, 2, 3
33
+ _IT_RMSNORM_G, _IT_GELU_GLU = 4, 5
34
+ _EP_GELU_PLE, _EP_F32_AMAX_CAP = 5, 6
35
+ _ITF_REDUCE, _ITF_WRITEBACK = 8, 16
36
+ _EP_STORE, _EP_RESID, _EP_F32, _EP_F32_AMAX, _EP_PARTIAL = 0, 1, 2, 3, 4
37
+ _NSLICE = 4
38
+ # Widest [B][K] input panel (bf16 elements) a batched projection stages in
39
+ # shared before it spills to the global-scratch path. 16384 (32 KB) keeps
40
+ # two blocks resident per SM; the persistent kernel shares one occupancy
41
+ # across all phases, so a wider panel would drop every phase to one block
42
+ # per SM, which measures slower than reading the input from L2.
43
+ _STAGE_BUDGET = 16384
44
+
45
+
46
+ def _fbits(f: float) -> int:
47
+ return struct.unpack("<i", struct.pack("<f", float(f)))[0]
48
+
49
+
50
+ def _row(op, p1=0, p2=0, p3=0, p4=0, p5=0, p6=0, n=0, k=0, it=0, epi=0,
51
+ hq=0, hkv=0, d=0, f0=0.0, i0=0):
52
+ return [int(op), int(p1), int(p2), int(p3), int(p4), int(p5), int(p6),
53
+ int(n), int(k), int(it), int(epi), int(hq), int(hkv), int(d),
54
+ _fbits(f0), int(i0)]
55
+
56
+
57
+ class MegaModel:
58
+ """A packed llama-family decoder whose decode step is one kernel launch.
59
+
60
+ Supported architecture: RMSNorm decoder blocks with rotary attention
61
+ (GQA, optional Qwen3-style per-head qk RMSNorm), SwiGLU MLP, no attention
62
+ or MLP biases, plain (unscaled) rope, batch size 1. This covers SmolLM2,
63
+ TinyLlama, Qwen3 dense, Llama-class checkpoints without rope scaling,
64
+ and similar models.
65
+ """
66
+
67
+ def __init__(self, weights: dict, config: dict, device="cuda",
68
+ max_seq: int = 4096, max_gen: int = 4096):
69
+ cfg = dict(config)
70
+ self.L = int(cfg["num_hidden_layers"])
71
+ self.hidden = int(cfg["hidden_size"])
72
+ self.Hq = int(cfg["num_attention_heads"])
73
+ self.Hkv = int(cfg.get("num_key_value_heads") or self.Hq)
74
+ self.D = int(cfg.get("head_dim") or self.hidden // self.Hq)
75
+ self.I = int(cfg["intermediate_size"])
76
+ self.V = int(cfg["vocab_size"])
77
+ self.theta = float(cfg.get("rope_theta", 10000.0))
78
+ self.eps = float(cfg.get("rms_norm_eps", 1e-6))
79
+ self.qk_norm = bool(cfg.get("qk_norm", False))
80
+ self.max_seq = int(max_seq)
81
+ self.max_gen = int(max_gen)
82
+ self.device = torch.device(device)
83
+
84
+ if self.D % 2 or self.D > 256:
85
+ raise ValueError("head_dim must be even and <= 256")
86
+ if self.hidden % 8 or self.I % 8 or (self.Hq * self.D) % 8:
87
+ raise ValueError(
88
+ "hidden, intermediate, and Hq*head_dim must be multiples of 8")
89
+ if self.Hq % self.Hkv:
90
+ raise ValueError("num_attention_heads must be divisible by num_key_value_heads")
91
+
92
+ dt = torch.bfloat16
93
+ dev = self.device
94
+
95
+ def pack(t):
96
+ t = t.detach().to(device=dev, dtype=dt).contiguous()
97
+ self._keep.append(t)
98
+ return t
99
+
100
+ # Row-tile-of-8 weight layout [N/8][K/8][8][8] (MAK_TILED=1): the
101
+ # eight warps of a block read one contiguous 4KB stream per step
102
+ # instead of eight 4KB-strided pieces. Measured slower on every
103
+ # card (H200 +15 percent, RTX PRO +9, Ada +14 — per-warp stream
104
+ # locality beats per-block contiguity on all of them), so the
105
+ # default is the plain layout; the flag rides in the program and
106
+ # both layouts compute identical bits.
107
+ env_t = os.environ.get("MAK_TILED", "").strip()
108
+ self.tiled = env_t == "1"
109
+
110
+ def pack_tiled(t):
111
+ t = t.detach().to(device=dev, dtype=dt).contiguous()
112
+ if not self.tiled:
113
+ self._keep.append(t)
114
+ return t
115
+ n, k = t.shape
116
+ assert k % 8 == 0
117
+ n8 = (n + 7) // 8 * 8
118
+ if n8 != n:
119
+ t = torch.cat(
120
+ [t, torch.zeros(n8 - n, k, dtype=dt, device=dev)], 0)
121
+ t = t.view(n8 // 8, 8, k // 8, 8).permute(0, 2, 1, 3).contiguous()
122
+ self._keep.append(t)
123
+ return t
124
+
125
+ self._keep: List[torch.Tensor] = []
126
+ embed = pack(weights["embed"])
127
+ final_norm = pack(weights["norm"])
128
+ lm_head = weights["lm_head"] # packed below (dense-tiled or nf4)
129
+ inv_freq = weights.get("inv_freq")
130
+ if inv_freq is None:
131
+ d_idx = torch.arange(0, self.D, 2, dtype=torch.float32)
132
+ inv_freq = 1.0 / (self.theta ** (d_idx / self.D))
133
+ inv_freq = inv_freq.detach().to(device=dev,
134
+ dtype=torch.float32).contiguous()
135
+ if inv_freq.numel() != self.D // 2:
136
+ raise ValueError("inv_freq length must be head_dim / 2")
137
+ self._keep.append(inv_freq)
138
+ self._invf = inv_freq
139
+
140
+ qdim, kvdim = self.Hq * self.D, self.Hkv * self.D
141
+
142
+ def _is_q4(w):
143
+ return isinstance(w, dict) and w.get("q4")
144
+
145
+ def pack_w(w):
146
+ # nf4-packed weights move to the device intact; dense weights take
147
+ # the usual (optionally row-tiled) bf16 packing.
148
+ if _is_q4(w):
149
+ pk = w["packed"].to(dev).contiguous()
150
+ am = w["absmax"].to(device=dev, dtype=torch.float32).contiguous()
151
+ self._keep.append(pk)
152
+ self._keep.append(am)
153
+ return {"q4": True, "packed": pk, "absmax": am,
154
+ "N": int(w["N"]), "K": int(w["K"])}
155
+ return pack_tiled(w)
156
+
157
+ def _wshape(w, expected):
158
+ got = (int(w["N"]), int(w["K"])) if _is_q4(w) else tuple(w.shape)
159
+ assert got == expected, (got, expected)
160
+
161
+ self._has_q4 = False
162
+ self._layers = []
163
+ for lw in weights["layers"]:
164
+ _wshape(lw["wqkv"], (qdim + 2 * kvdim, self.hidden))
165
+ _wshape(lw["wo"], (self.hidden, qdim))
166
+ _wshape(lw["wgu"], (2 * self.I, self.hidden))
167
+ _wshape(lw["wdown"], (self.hidden, self.I))
168
+ layer = {
169
+ "wqkv": pack_w(lw["wqkv"]), "wo": pack_w(lw["wo"]),
170
+ "wgu": pack_w(lw["wgu"]), "wdown": pack_w(lw["wdown"]),
171
+ "ln1": pack(lw["ln1"]), "ln2": pack(lw["ln2"]),
172
+ "qn": pack(lw["qn"]) if self.qk_norm else None,
173
+ "kn": pack(lw["kn"]) if self.qk_norm else None,
174
+ }
175
+ self._has_q4 |= any(_is_q4(layer[n]) for n in
176
+ ("wqkv", "wo", "wgu", "wdown"))
177
+ self._layers.append(layer)
178
+ assert len(self._layers) == self.L
179
+ assert embed.shape == (self.V, self.hidden)
180
+ lm_head = pack_w(lm_head)
181
+ self._has_q4 |= _is_q4(lm_head)
182
+ if not _is_q4(lm_head):
183
+ assert lm_head.numel() >= self.V * self.hidden # row-tiled, padded
184
+ self._w_embed, self._w_fnorm, self._w_lmhead = (embed, final_norm,
185
+ lm_head)
186
+ self._qdim, self._kvdim = qdim, kvdim
187
+
188
+ # Working buffers. Pointers to these are baked into the program, so
189
+ # they (like the packed weights) live for the model's lifetime.
190
+ self._maxk = max(self.hidden, qdim, self.I)
191
+ # prefill chunk size, bounded by the staging shared-memory budget
192
+ # (16K bf16 elements; the cp.async weight ring rides alongside)
193
+ self._chunk_m = max(1, min(8, 16384 // self._maxk))
194
+ cm = 8 # buffers sized for the maximum chunk
195
+ self._hidden = torch.empty(cm * self.hidden, dtype=dt, device=dev)
196
+ self._qkv = torch.empty(cm * (qdim + 2 * kvdim), dtype=dt, device=dev)
197
+ self._gu = torch.empty(cm * 2 * self.I, dtype=dt, device=dev)
198
+ self._logits = torch.empty(self.V, dtype=torch.float32, device=dev)
199
+ # attention chunk length (kernel maximum 128); shorter chunks give
200
+ # the attention phase more grid parallelism at the cost of more
201
+ # softmax partials to finalize
202
+ env_ch = os.environ.get("MAK_CHUNK", "").strip()
203
+ self._chunk = int(env_ch) if env_ch in ("32", "64", "128") else 128
204
+ self._maxch = (self.max_seq + self._chunk - 1) // self._chunk
205
+ self._partials = torch.empty(
206
+ cm * self.Hq * self._maxch * (self.D + 2), dtype=torch.float32,
207
+ device=dev)
208
+ self._kcache = torch.zeros(self.L, self.Hkv, self.max_seq, self.D,
209
+ dtype=dt, device=dev)
210
+ self._vcache = torch.zeros(self.L, self.Hkv, self.max_seq, self.D,
211
+ dtype=dt, device=dev)
212
+ self._token = torch.zeros(1, dtype=torch.int32, device=dev)
213
+ self._prompt_in = torch.zeros(self.max_seq, dtype=torch.int32,
214
+ device=dev)
215
+ self._tokens_out = torch.zeros(self.max_gen, dtype=torch.int32,
216
+ device=dev)
217
+ self._bar = torch.zeros(34, dtype=torch.int32, device=dev)
218
+
219
+ probe = torch.zeros(1, 16, dtype=torch.int64, device=dev)
220
+ self._nblocks = int(ops.mak_num_blocks(probe, self._maxk))
221
+ self._parts = torch.zeros(self._nblocks, dtype=torch.int64, device=dev)
222
+ try:
223
+ self._bw_per_sm = float(ops.mak_bw_per_sm(probe))
224
+ except (AttributeError, RuntimeError):
225
+ self._bw_per_sm = 0.0
226
+
227
+ self._batch = 0
228
+ self._pos_b = None
229
+ self._kv_bstride = 0
230
+ self._gemma = False
231
+ # transformed-input scratch: the nf4 and wide-batch paths write the
232
+ # dense bf16 input here (one row per active token) for the following
233
+ # plain/quant GEMV; sized for a prefill chunk (up to 8 rows)
234
+ self._xg = torch.empty(cm * self._maxk, dtype=dt, device=dev)
235
+ self._build_programs()
236
+
237
+ # ------------------------------------------------------------------
238
+ def _kvp(self, cache, li: int, b: int) -> int:
239
+ t = cache[li]
240
+ return (t[b] if t.dim() == 4 else t).data_ptr()
241
+
242
+ def _build_rows(self, kv_slice: int = 0, batch: bool = False,
243
+ big: bool = False):
244
+ """Program rows for the llama-family path. kv_slice selects the
245
+ batch slice the KV pointers address (per-sequence prefill);
246
+ batch=True emits the batched-decode tail (per-row fp32 logits and
247
+ explicit argmax phases) instead of the fused LM-head amax. In batch
248
+ mode each projection stays fused (input staged in shared) when its
249
+ [B][K] panel fits the budget; the projections whose K is too wide
250
+ transform into a global scratch and read it with a plain GEMV."""
251
+ _tbit = (1 << 20) if self.tiled else 0 # GEMV weight layout flag
252
+ qdim, kvdim = self._qdim, self._kvdim
253
+ embed, lm_head = self._w_embed, self._w_lmhead
254
+ final_norm = self._w_fnorm
255
+ xg = self._xg.data_ptr()
256
+ B = self._batch if batch else 1
257
+ rows, names = [], []
258
+ staged_elems = [0] # widest [B][K] panel any fused projection stages
259
+
260
+ def fits(K):
261
+ return B * K <= _STAGE_BUDGET
262
+
263
+ def note(K):
264
+ staged_elems[0] = max(staged_elems[0], B * K)
265
+
266
+ def _q4(w):
267
+ return isinstance(w, dict) and w.get("q4")
268
+
269
+ def _wgemv(out, w, N, K, epi, resid):
270
+ # the GEMV after a transform-to-scratch phase: nf4-dequant when
271
+ # the weight is packed, otherwise a plain bf16 GEMV
272
+ if _q4(w):
273
+ rows.append(_row(_OP_GEMV_Q4, p1=xg, p2=w["packed"].data_ptr(),
274
+ p3=out, p5=resid, p6=w["absmax"].data_ptr(),
275
+ n=N, k=K, epi=epi))
276
+ else:
277
+ rows.append(_row(_OP_GEMV_PLAIN, p1=xg, p2=w.data_ptr(),
278
+ p3=out, p5=resid, n=N, k=K, it=_tbit,
279
+ epi=epi))
280
+
281
+ def norm_gemv(name, gamma, w, out, N, K, epi, inp, resid=0,
282
+ variant=_IT_RMSNORM):
283
+ if _q4(w) or (big and not fits(K)):
284
+ rows.append(_row(_OP_NORMB, p1=inp, p2=gamma, p3=xg, k=K,
285
+ it=variant, f0=self.eps))
286
+ names.append(name + ".norm")
287
+ _wgemv(out, w, N, K, epi, resid)
288
+ names.append(name)
289
+ else:
290
+ note(K)
291
+ rows.append(_row(_OP_GEMV, p1=inp, p2=w.data_ptr(), p3=out,
292
+ p4=gamma, p5=resid, n=N, k=K,
293
+ it=variant | _tbit, epi=epi, f0=self.eps))
294
+ names.append(name)
295
+
296
+ def glu_gemv(name, gu, w, out, resid, variant=_IT_SWIGLU):
297
+ if _q4(w) or (big and not fits(self.I)):
298
+ rows.append(_row(_OP_GLUB, p1=gu, p3=xg, k=self.I, it=variant))
299
+ names.append(name + ".glu")
300
+ _wgemv(out, w, self.hidden, self.I, _EP_RESID, resid)
301
+ names.append(name)
302
+ else:
303
+ note(self.I)
304
+ rows.append(_row(_OP_GEMV, p1=gu, p2=w.data_ptr(), p3=out,
305
+ p5=resid, n=self.hidden, k=self.I,
306
+ it=variant | _tbit, epi=_EP_RESID))
307
+ names.append(name)
308
+
309
+ def attn_gemv(name, w, out, resid):
310
+ if _q4(w) or (big and not fits(qdim)):
311
+ rows.append(_row(_OP_ATTNFINB, p1=self._partials.data_ptr(),
312
+ p3=xg, k=qdim, hq=self.Hq,
313
+ hkv=self._chunk << 16, d=self.D,
314
+ i0=self._maxch))
315
+ names.append(name + ".fin")
316
+ _wgemv(out, w, self.hidden, qdim, _EP_RESID, resid)
317
+ names.append(name)
318
+ else:
319
+ note(qdim)
320
+ rows.append(_row(_OP_GEMV, p1=self._partials.data_ptr(),
321
+ p2=w.data_ptr(), p3=out, p5=resid,
322
+ n=self.hidden, k=qdim,
323
+ it=_IT_ATTNFIN | _tbit, epi=_EP_RESID,
324
+ hq=self.Hq, hkv=self._chunk << 16, d=self.D,
325
+ i0=self._maxch))
326
+ names.append(name)
327
+
328
+ rows.append(_row(_OP_EMBED, p2=embed.data_ptr(),
329
+ p3=self._hidden.data_ptr(),
330
+ p4=self._parts.data_ptr(),
331
+ p5=self._prompt_in.data_ptr(),
332
+ p6=self._token.data_ptr(),
333
+ n=self._tokens_out.data_ptr(), k=self.hidden))
334
+ names.append("embed")
335
+ scale = 1.0 / (self.D ** 0.5)
336
+ hid = self._hidden.data_ptr()
337
+ for li, lw in enumerate(self._layers):
338
+ kc = self._kvp(self._kcache, li, kv_slice)
339
+ vc = self._kvp(self._vcache, li, kv_slice)
340
+ norm_gemv(f"L{li}.qkv", lw["ln1"].data_ptr(),
341
+ lw["wqkv"], self._qkv.data_ptr(),
342
+ qdim + 2 * kvdim, self.hidden, _EP_STORE, hid)
343
+ rows.append(_row(6, p1=self._qkv.data_ptr(), p2=kc, p3=vc,
344
+ p6=lw["kn"].data_ptr() if self.qk_norm else 0,
345
+ n=self.max_seq, k=1 if self.qk_norm else 0,
346
+ it=_fbits(self.eps),
347
+ epi=self._invf.data_ptr(),
348
+ hq=self.Hq, hkv=self.Hkv, d=self.D))
349
+ names.append(f"L{li}.kvappend")
350
+ rows.append(_row(_OP_ATTN, p1=self._qkv.data_ptr(), p2=kc,
351
+ p3=vc, p4=self._partials.data_ptr(),
352
+ p5=lw["qn"].data_ptr() if self.qk_norm else 0,
353
+ p6=lw["kn"].data_ptr() if self.qk_norm else 0,
354
+ n=self.max_seq, k=1 if self.qk_norm else 0,
355
+ it=_fbits(self.eps),
356
+ epi=self._invf.data_ptr(),
357
+ hq=self.Hq,
358
+ hkv=self.Hkv | (self._chunk << 16),
359
+ d=self.D, f0=scale, i0=self._maxch))
360
+ names.append(f"L{li}.attn")
361
+ attn_gemv(f"L{li}.o", lw["wo"], hid, hid)
362
+ norm_gemv(f"L{li}.gateup", lw["ln2"].data_ptr(),
363
+ lw["wgu"], self._gu.data_ptr(), 2 * self.I,
364
+ self.hidden, _EP_STORE, hid)
365
+ glu_gemv(f"L{li}.down", self._gu.data_ptr(), lw["wdown"], hid, hid)
366
+ if batch or _q4(lm_head):
367
+ norm_gemv("lm_head", final_norm.data_ptr(), lm_head,
368
+ self._logits.data_ptr(), self.V, self.hidden, _EP_F32,
369
+ hid)
370
+ rows.append(_row(_OP_ARGMAX_PART, p1=self._logits.data_ptr(),
371
+ p3=self._parts.data_ptr(), n=self.V))
372
+ names.append("argmax.part")
373
+ rows.append(_row(_OP_ARGMAX_FIN, p1=self._parts.data_ptr(),
374
+ p3=self._token.data_ptr(),
375
+ p4=self._tokens_out.data_ptr(),
376
+ k=self.max_gen))
377
+ names.append("argmax.fin")
378
+ else:
379
+ rows.append(_row(_OP_GEMV, p1=hid, p2=lm_head.data_ptr(),
380
+ p3=self._logits.data_ptr(),
381
+ p4=final_norm.data_ptr(),
382
+ p6=self._parts.data_ptr(), n=self.V,
383
+ k=self.hidden, it=_IT_RMSNORM | _tbit,
384
+ epi=_EP_F32_AMAX, hkv=1, f0=self.eps))
385
+ names.append("lm_head")
386
+ rows.append(_row(_OP_ARGMAX_FIN, p1=self._parts.data_ptr(),
387
+ p3=self._token.data_ptr(),
388
+ p4=self._tokens_out.data_ptr()))
389
+ names.append("argmax.fin")
390
+ se = max(staged_elems[0], self._maxk)
391
+ return rows, names, se
392
+
393
+ def _build_programs(self):
394
+ dev = self.device
395
+ rows, names, _ = self._build_rows(kv_slice=0, batch=False)
396
+ self._prog = torch.tensor(rows, dtype=torch.int64, device=dev)
397
+ self.phase_names = names
398
+ if self._batch:
399
+ big = self._batch > min(8, 16384 // self._maxk)
400
+ rows, _, se = self._build_rows(kv_slice=0, batch=True, big=big)
401
+ self._prog_batch = torch.tensor(rows, dtype=torch.int64,
402
+ device=dev)
403
+ self._batch_stage_elems = int(se)
404
+ self._progs_prefill = [self._prog]
405
+ for b in range(1, self._batch):
406
+ rows, _, _ = self._build_rows(kv_slice=b, batch=False)
407
+ self._progs_prefill.append(
408
+ torch.tensor(rows, dtype=torch.int64, device=dev))
409
+
410
+ def batch_max(self) -> int:
411
+ """Largest batch size this model supports. Up to 8 sequences stage
412
+ in shared; beyond that the transformed input moves to a global
413
+ scratch, bounded by the kernel's register accumulators."""
414
+ try:
415
+ hard = int(ops.mak_batch_maxb())
416
+ except (AttributeError, RuntimeError):
417
+ hard = 8
418
+ return 1 if self._gemma else hard
419
+
420
+ # ------------------------------------------------------------------
421
+ def enable_batch(self, B: int):
422
+ """Allocate per-sequence KV caches, token buffers, and programs for
423
+ batched decode. B up to 8 stages the input in shared; larger B (up
424
+ to batch_max()) routes the transformed input through a global
425
+ scratch so the batch width is not bounded by the projection size."""
426
+ if self._gemma:
427
+ raise ValueError("batched decode covers the llama-family path")
428
+ maxb = self.batch_max()
429
+ if not 1 <= B <= maxb:
430
+ raise ValueError(f"B must be in [1, {maxb}] for this model")
431
+ if self._batch == B:
432
+ return
433
+ dt, dev = torch.bfloat16, self.device
434
+ self._kcache = torch.zeros(self.L, B, self.Hkv, self.max_seq,
435
+ self.D, dtype=dt, device=dev)
436
+ self._vcache = torch.zeros_like(self._kcache)
437
+ self._token = torch.zeros(B, dtype=torch.int32, device=dev)
438
+ self._tokens_out = torch.zeros(B * self.max_gen, dtype=torch.int32,
439
+ device=dev)
440
+ self._logits = torch.empty(B * self.V, dtype=torch.float32,
441
+ device=dev)
442
+ self._parts = torch.zeros(B * self._nblocks, dtype=torch.int64,
443
+ device=dev)
444
+ self._pos_b = torch.zeros(B, dtype=torch.int32, device=dev)
445
+ # transformed-input scratch: one row per active token, and prefill
446
+ # (during generate_batch) still uses up to chunk_m rows
447
+ self._xg = torch.empty(max(8, B) * self._maxk, dtype=dt, device=dev)
448
+ # working buffers hold one row per active sequence; prefill chunks
449
+ # still use up to chunk_m rows, so keep at least the fused capacity
450
+ nb = max(8, B)
451
+ qkvd = self._qdim + 2 * self._kvdim
452
+ self._hidden = torch.empty(nb * self.hidden, dtype=dt, device=dev)
453
+ self._qkv = torch.empty(nb * qkvd, dtype=dt, device=dev)
454
+ self._gu = torch.empty(nb * 2 * self.I, dtype=dt, device=dev)
455
+ self._partials = torch.empty(
456
+ nb * self.Hq * self._maxch * (self.D + 2), dtype=torch.float32,
457
+ device=dev)
458
+ self._kv_bstride = self.Hkv * self.max_seq * self.D
459
+ self._batch = B
460
+ self._build_programs()
461
+
462
+ def decode_batch(self, tokens: Sequence[int], positions: Sequence[int],
463
+ steps: int = 1) -> torch.Tensor:
464
+ """Batched greedy decode: sequence b consumes tokens[b] at
465
+ positions[b]; `steps` in-kernel steps run with per-sequence argmax
466
+ feedback. Returns the generated tokens [B, steps]; the final
467
+ step's fp32 logits are live in batch_logits()."""
468
+ B = self._batch
469
+ if B < 1:
470
+ raise ValueError("call enable_batch(B) first")
471
+ if len(tokens) != B or len(positions) != B:
472
+ raise ValueError("tokens and positions must have B entries")
473
+ if steps < 1 or steps > self.max_gen:
474
+ raise ValueError("steps out of range")
475
+ if max(positions) + steps > self.max_seq:
476
+ raise ValueError("sequence exceeds max_seq")
477
+ self._token.copy_(torch.tensor(list(tokens), dtype=torch.int32))
478
+ self._pos_b.copy_(torch.tensor(list(positions), dtype=torch.int32))
479
+ ops.mak_run_batch(self._prog_batch, self._bar, self._pos_b, steps,
480
+ 0, self._maxk, self._kv_bstride,
481
+ self._batch_stage_elems)
482
+ return self._tokens_out.view(B, self.max_gen)[:, :steps]
483
+
484
+ def batch_logits(self) -> torch.Tensor:
485
+ """fp32 logits [B, V] of the most recent batched step."""
486
+ return self._logits.view(max(self._batch, 1), self.V)
487
+
488
+ def generate_batch(self, prompts: Sequence[Sequence[int]],
489
+ max_new: int) -> List[List[int]]:
490
+ """Greedy generation for a batch of prompts: per-sequence prefill,
491
+ then one batched decode launch. Each sequence's token stream is
492
+ bit-identical to its own single-sequence generate()."""
493
+ B = len(prompts)
494
+ if max_new < 1 or max_new > self.max_gen:
495
+ raise ValueError("max_new out of range")
496
+ self.enable_batch(B)
497
+ firsts: List[int] = []
498
+ for b, ids in enumerate(prompts):
499
+ P = len(ids)
500
+ if P < 1 or P + max_new > self.max_seq:
501
+ raise ValueError("sequence exceeds max_seq")
502
+ self._prompt_in[:P].copy_(
503
+ torch.tensor(list(ids), dtype=torch.int32))
504
+ ops.mak_run_seq(self._progs_prefill[b], self._bar, 0, P, 1 - P,
505
+ P, self._maxk, self._chunk_m)
506
+ firsts.append(int(self._token[0].item()))
507
+ if max_new == 1:
508
+ return [[t] for t in firsts]
509
+ self._token.copy_(torch.tensor(firsts, dtype=torch.int32))
510
+ self._pos_b.copy_(torch.tensor([len(p) for p in prompts],
511
+ dtype=torch.int32))
512
+ ops.mak_run_batch(self._prog_batch, self._bar, self._pos_b,
513
+ max_new - 1, 0, self._maxk, self._kv_bstride,
514
+ self._batch_stage_elems)
515
+ rest = (self._tokens_out.view(B, self.max_gen)[:, :max_new - 1]
516
+ .cpu().tolist())
517
+ return [[firsts[b]] + rest[b] for b in range(B)]
518
+
519
+ # ------------------------------------------------------------------
520
+ @staticmethod
521
+ def _dense_weight(mod):
522
+ """The effective bf16 weight of a linear layer, dequantizing packed
523
+ quantized layers (bitsandbytes 4-bit and 8-bit) so a quantized
524
+ checkpoint loads directly. Dequantization is exact and does not
525
+ depend on the card, so inference on the result keeps every
526
+ guarantee; the weights materialize to bf16, so the quantized memory
527
+ footprint is not preserved (that needs the in-kernel packed path)."""
528
+ w = mod.weight
529
+ qs = getattr(w, "quant_state", None)
530
+ if qs is not None: # bitsandbytes 4-bit (nf4 / fp4)
531
+ import bitsandbytes as bnb
532
+ return bnb.functional.dequantize_4bit(w.data, qs).to(torch.bfloat16)
533
+ if getattr(w, "SCB", None) is not None or hasattr(mod, "SCB"):
534
+ # bitsandbytes 8-bit (LLM.int8): row scales in SCB, int8 in CB
535
+ scb = w.SCB if getattr(w, "SCB", None) is not None else mod.SCB
536
+ cb = w.data if w.data.dtype == torch.int8 else mod.CB
537
+ return (cb.to(torch.float32)
538
+ * (scb.to(torch.float32) / 127.0).unsqueeze(1)
539
+ ).to(torch.bfloat16)
540
+ return w
541
+
542
+ @staticmethod
543
+ def _q4_parts(mod):
544
+ """(packed [N, K/2] uint8, absmax [N*K/64] fp32) for a bitsandbytes
545
+ nf4 layer whose weights stay packed, else None. Double-quantized
546
+ absmax is materialized to fp32 so the kernel needs one scale array."""
547
+ w = getattr(mod, "weight", None)
548
+ qs = getattr(w, "quant_state", None)
549
+ if qs is None or getattr(qs, "quant_type", None) != "nf4":
550
+ return None
551
+ import bitsandbytes as bnb
552
+ n, k = int(qs.shape[0]), int(qs.shape[1])
553
+ if k % 64:
554
+ return None # kernel assumes 64 | K for per-block absmax
555
+ packed = w.data.reshape(n, k // 2).contiguous()
556
+ if getattr(qs, "nested", False):
557
+ absmax = bnb.functional.dequantize_blockwise(
558
+ qs.absmax, qs.state2) + qs.offset
559
+ else:
560
+ absmax = qs.absmax
561
+ return packed, absmax.float().reshape(-1).contiguous()
562
+
563
+ @classmethod
564
+ def _proj(cls, mods):
565
+ """A projection, concatenated across `mods` (q/k/v or gate/up). Stays
566
+ nf4-packed when every part is nf4, else a dense bf16 tensor."""
567
+ parts = [cls._q4_parts(m) for m in mods]
568
+ if all(p is not None for p in parts):
569
+ packed = torch.cat([p[0] for p in parts], 0)
570
+ absmax = torch.cat([p[1] for p in parts], 0)
571
+ return {"q4": True, "packed": packed, "absmax": absmax,
572
+ "N": packed.shape[0], "K": packed.shape[1] * 2}
573
+ return torch.cat([cls._dense_weight(m) for m in mods], 0)
574
+
575
+ @classmethod
576
+ def from_pretrained(cls, model, device="cuda", max_seq: int = 4096,
577
+ max_gen: int = 4096):
578
+ """Build from a transformers model (object or repo id). A quantized
579
+ checkpoint (bitsandbytes 4-bit/8-bit) is accepted directly; its
580
+ weights are dequantized to bf16 at load."""
581
+ if isinstance(model, str):
582
+ from transformers import AutoModelForCausalLM
583
+ model = AutoModelForCausalLM.from_pretrained(
584
+ model, torch_dtype=torch.bfloat16)
585
+ if "gemma4" in getattr(model.config, "model_type", ""):
586
+ return cls._from_gemma4(model, device, max_seq, max_gen)
587
+ hf = model.config
588
+ dec = model.model
589
+ attn0 = dec.layers[0].self_attn
590
+ qk_norm = hasattr(attn0, "q_norm") and attn0.q_norm is not None
591
+ if getattr(attn0.q_proj, "bias", None) is not None:
592
+ raise ValueError("attention biases are not supported")
593
+
594
+ # rope config across transformers API generations: v5 keeps theta and
595
+ # type in a rope_parameters dict; v4 used a rope_theta attribute and
596
+ # an optional rope_scaling dict.
597
+ rp = getattr(hf, "rope_parameters", None)
598
+ if not isinstance(rp, dict):
599
+ rs = getattr(hf, "rope_scaling", None)
600
+ rp = rs if isinstance(rs, dict) else {}
601
+ theta = rp.get("rope_theta")
602
+ if theta is None:
603
+ theta = getattr(hf, "rope_theta", 10000.0)
604
+ rope_type = rp.get("rope_type", rp.get("type", "default"))
605
+ if rope_type not in ("default", "llama3"):
606
+ raise ValueError(f"rope type {rope_type!r} is not supported")
607
+ # take the frequency table transformers computed (scaling included)
608
+ rot = dec.rotary_emb
609
+ att_scale = float(getattr(rot, "attention_scaling", 1.0))
610
+ if att_scale != 1.0:
611
+ raise ValueError("rope attention_scaling != 1 is not supported")
612
+ inv_freq = rot.inv_freq.detach().float()
613
+
614
+ pj = cls._proj
615
+ layers = []
616
+ for lyr in dec.layers:
617
+ a, m = lyr.self_attn, lyr.mlp
618
+ layers.append({
619
+ "wqkv": pj([a.q_proj, a.k_proj, a.v_proj]),
620
+ "wo": pj([a.o_proj]),
621
+ "wgu": pj([m.gate_proj, m.up_proj]),
622
+ "wdown": pj([m.down_proj]),
623
+ "ln1": lyr.input_layernorm.weight,
624
+ "ln2": lyr.post_attention_layernorm.weight,
625
+ "qn": a.q_norm.weight if qk_norm else None,
626
+ "kn": a.k_norm.weight if qk_norm else None,
627
+ })
628
+ weights = {"embed": dec.embed_tokens.weight, "norm": dec.norm.weight,
629
+ "lm_head": pj([model.lm_head]), "layers": layers,
630
+ "inv_freq": inv_freq}
631
+ config = {
632
+ "num_hidden_layers": hf.num_hidden_layers,
633
+ "hidden_size": hf.hidden_size,
634
+ "num_attention_heads": hf.num_attention_heads,
635
+ "num_key_value_heads": getattr(hf, "num_key_value_heads", None),
636
+ "head_dim": getattr(hf, "head_dim", None),
637
+ "intermediate_size": hf.intermediate_size,
638
+ "vocab_size": hf.vocab_size,
639
+ "rope_theta": float(theta),
640
+ "rms_norm_eps": hf.rms_norm_eps,
641
+ "qk_norm": qk_norm,
642
+ }
643
+ return cls(weights, config, device=device, max_seq=max_seq,
644
+ max_gen=max_gen)
645
+
646
+ # ------------------------------------------------------------------
647
+ @classmethod
648
+ def _from_gemma4(cls, model, device, max_seq, max_gen):
649
+ """gemma-4-E2B-it program: gemma norms (fp32, raw weight),
650
+ gelu-tanh gating, post-sublayer norm phases, per-layer-input
651
+ pathway, sliding windows, shared KV, dual head dims, softcapped
652
+ head. Scope is the E2B text model only."""
653
+ self = cls.__new__(cls)
654
+ cfg = model.config.text_config if hasattr(model.config, "text_config") \
655
+ else model.config
656
+ dec = model.model.language_model if hasattr(model.model, "language_model") \
657
+ else model.model
658
+ assert cfg.model_type == "gemma4_text", cfg.model_type
659
+ assert not cfg.enable_moe_block
660
+
661
+ self.L = int(cfg.num_hidden_layers)
662
+ self.hidden = int(cfg.hidden_size)
663
+ self.Hq = int(cfg.num_attention_heads)
664
+ self.V = int(cfg.vocab_size)
665
+ self.eps = float(cfg.rms_norm_eps)
666
+ self.qk_norm = True
667
+ self.max_seq = int(max_seq)
668
+ self.max_gen = int(max_gen)
669
+ self.device = torch.device(device)
670
+ self.tiled = False
671
+ win = int(cfg.sliding_window)
672
+ cap = float(cfg.final_logit_softcapping)
673
+ PL = int(cfg.hidden_size_per_layer_input)
674
+ dt = torch.bfloat16
675
+ dev = self.device
676
+ self._keep = []
677
+
678
+ def pack(t):
679
+ t = t.detach().to(device=dev, dtype=dt).contiguous()
680
+ self._keep.append(t)
681
+ return t
682
+
683
+ def packf(t):
684
+ t = t.detach().to(device=dev, dtype=torch.float32).contiguous()
685
+ self._keep.append(t)
686
+ return t
687
+
688
+ embed = pack(dec.embed_tokens.weight)
689
+ ple_tab = pack(dec.embed_tokens_per_layer.weight)
690
+ plm_w = pack(dec.per_layer_model_projection.weight)
691
+ pln_w = pack(dec.per_layer_projection_norm.weight)
692
+ fnorm = pack(dec.norm.weight)
693
+ rot = dec.rotary_emb
694
+ invf = {lt: packf(getattr(rot, f"{lt}_inv_freq").float())
695
+ for lt in set(cfg.layer_types)}
696
+ ascale = {lt: float(getattr(rot, f"{lt}_attention_scaling"))
697
+ for lt in set(cfg.layer_types)}
698
+
699
+ layers = []
700
+ for li, lyr in enumerate(dec.layers):
701
+ a = lyr.self_attn
702
+ D_l = int(a.head_dim)
703
+ shared = bool(a.is_kv_shared_layer)
704
+ ent = {
705
+ "D": D_l, "shared": shared,
706
+ "lscale": float(lyr.layer_scalar.float().item()),
707
+ "type": cfg.layer_types[li],
708
+ "I": int(lyr.mlp.intermediate_size),
709
+ "qn": pack(a.q_norm.weight),
710
+ "o": pack(a.o_proj.weight),
711
+ "ln_in": pack(lyr.input_layernorm.weight),
712
+ "ln_pa": pack(lyr.post_attention_layernorm.weight),
713
+ "ln_pf": pack(lyr.pre_feedforward_layernorm.weight),
714
+ "ln_ff": pack(lyr.post_feedforward_layernorm.weight),
715
+ "ln_pl": pack(lyr.post_per_layer_input_norm.weight),
716
+ "plig": pack(lyr.per_layer_input_gate.weight),
717
+ "plpr": pack(lyr.per_layer_projection.weight),
718
+ "wgu": pack(torch.cat([lyr.mlp.gate_proj.weight,
719
+ lyr.mlp.up_proj.weight], 0)),
720
+ "wdn": pack(lyr.mlp.down_proj.weight),
721
+ }
722
+ if shared:
723
+ ent["wqkv"] = pack(a.q_proj.weight)
724
+ ent["kn"] = None
725
+ else:
726
+ ent["wqkv"] = pack(torch.cat(
727
+ [a.q_proj.weight, a.k_proj.weight, a.v_proj.weight], 0))
728
+ ent["kn"] = pack(a.k_norm.weight)
729
+ ent["kc"] = torch.zeros(self.max_seq, D_l, dtype=dt,
730
+ device=dev)
731
+ ent["vc"] = torch.zeros(self.max_seq, D_l, dtype=dt,
732
+ device=dev)
733
+ self._keep += [ent["kc"], ent["vc"]]
734
+ layers.append(ent)
735
+ # shared layers read the last non-shared layer of their type
736
+ src = {}
737
+ for li, ent in enumerate(layers):
738
+ if not ent["shared"]:
739
+ src[ent["type"]] = ent
740
+ for ent in layers:
741
+ if ent["shared"]:
742
+ ent["kc"] = src[ent["type"]]["kc"]
743
+ ent["vc"] = src[ent["type"]]["vc"]
744
+
745
+ maxD = max(e["D"] for e in layers)
746
+ maxI = max(e["I"] for e in layers)
747
+ self._maxk = max(self.hidden, self.Hq * maxD, maxI)
748
+ self._chunk_m = 1
749
+ self._chunk = 128
750
+ self._maxch = (self.max_seq + self._chunk - 1) // self._chunk
751
+ self._hidden = torch.empty(self.hidden, dtype=dt, device=dev)
752
+ self._tmp = torch.empty(self.hidden, dtype=dt, device=dev)
753
+ self._qkv = torch.empty((self.Hq + 2) * maxD, dtype=dt, device=dev)
754
+ self._gu = torch.empty(2 * maxI, dtype=dt, device=dev)
755
+ self._t256 = torch.empty(PL, dtype=dt, device=dev)
756
+ self._ctxraw = torch.empty(self.L * PL, dtype=dt, device=dev)
757
+ self._pletok = torch.empty(self.L * PL, dtype=dt, device=dev)
758
+ self._ple = torch.empty(self.L * PL, dtype=dt, device=dev)
759
+ self._logits = torch.empty(self.V, dtype=torch.float32, device=dev)
760
+ self._partials = torch.empty(
761
+ self.Hq * self._maxch * (maxD + 2), dtype=torch.float32,
762
+ device=dev)
763
+ self._token = torch.zeros(1, dtype=torch.int32, device=dev)
764
+ self._prompt_in = torch.zeros(self.max_seq, dtype=torch.int32,
765
+ device=dev)
766
+ self._tokens_out = torch.zeros(self.max_gen, dtype=torch.int32,
767
+ device=dev)
768
+ self._bar = torch.zeros(34, dtype=torch.int32, device=dev)
769
+ probe = torch.zeros(1, 16, dtype=torch.int64, device=dev)
770
+ self._nblocks = int(ops.mak_num_blocks(probe, self._maxk))
771
+ self._parts = torch.zeros(self._nblocks, dtype=torch.int64,
772
+ device=dev)
773
+ try:
774
+ self._bw_per_sm = float(ops.mak_bw_per_sm(probe))
775
+ except (AttributeError, RuntimeError):
776
+ self._bw_per_sm = 0.0
777
+ self._invf = invf[layers[0]["type"]]
778
+ self.Hkv, self.D, self.I = 1, maxD, maxI
779
+ self.theta = 0.0
780
+
781
+ emb_scale = float(torch.tensor(self.hidden ** 0.5,
782
+ dtype=torch.float32).to(dt))
783
+ ple_scale = float(torch.tensor(PL ** 0.5,
784
+ dtype=torch.float32).to(dt))
785
+ proj_scale = float(torch.tensor(self.hidden ** -0.5,
786
+ dtype=torch.float32))
787
+ hid = self._hidden.data_ptr()
788
+ tmp = self._tmp.data_ptr()
789
+ rows, names = [], []
790
+ rows.append(_row(_OP_EMBED, p1=ple_tab.data_ptr(),
791
+ p2=embed.data_ptr(), p3=hid,
792
+ p4=self._parts.data_ptr(),
793
+ p5=self._prompt_in.data_ptr(),
794
+ p6=self._token.data_ptr(),
795
+ n=self._tokens_out.data_ptr(), k=self.hidden,
796
+ it=self._pletok.data_ptr(),
797
+ hkv=self.L * PL, f0=emb_scale,
798
+ i0=_fbits(ple_scale)))
799
+ names.append("embed")
800
+ rows.append(_row(_OP_GEMV, p1=hid, p2=plm_w.data_ptr(),
801
+ p3=self._ctxraw.data_ptr(), n=self.L * PL,
802
+ k=self.hidden, it=_IT_NONE, epi=_EP_STORE))
803
+ names.append("plm")
804
+ rows.append(_row(8, p1=self._ctxraw.data_ptr(),
805
+ p2=pln_w.data_ptr(), p3=self._ple.data_ptr(),
806
+ p4=self._pletok.data_ptr(), n=self.L, k=PL,
807
+ f0=self.eps, i0=_fbits(proj_scale)))
808
+ names.append("plemix")
809
+
810
+ for li, e in enumerate(layers):
811
+ D_l, qdim = e["D"], self.Hq * e["D"]
812
+ lt = e["type"]
813
+ w_l = win if lt == "sliding_attention" else 0
814
+ asb = _fbits(ascale[lt]) if ascale[lt] != 1.0 else 0
815
+ flags = 1 | 2 | (0 if e["shared"] else 4) | \
816
+ (8 if e["shared"] else 0)
817
+ n_qkv = qdim if e["shared"] else qdim + 2 * D_l
818
+ rows.append(_row(_OP_GEMV, p1=hid, p2=e["wqkv"].data_ptr(),
819
+ p3=self._qkv.data_ptr(),
820
+ p4=e["ln_in"].data_ptr(), n=n_qkv,
821
+ k=self.hidden, it=_IT_RMSNORM_G,
822
+ epi=_EP_STORE, f0=self.eps))
823
+ names.append(f"L{li}.qkv")
824
+ rows.append(_row(_OP_ATTN, p1=self._qkv.data_ptr(),
825
+ p2=e["kc"].data_ptr(), p3=e["vc"].data_ptr(),
826
+ p4=self._partials.data_ptr(),
827
+ p5=e["qn"].data_ptr(),
828
+ p6=e["kn"].data_ptr() if e["kn"] is not None
829
+ else 0,
830
+ n=self.max_seq,
831
+ k=flags | (w_l << 16), it=_fbits(self.eps),
832
+ epi=invf[lt].data_ptr(), hq=self.Hq,
833
+ hkv=1 | (self._chunk << 16), d=D_l, f0=1.0,
834
+ i0=self._maxch | (asb << 32)))
835
+ names.append(f"L{li}.attn")
836
+ rows.append(_row(_OP_GEMV, p1=self._partials.data_ptr(),
837
+ p2=e["o"].data_ptr(), p3=tmp,
838
+ n=self.hidden, k=qdim, it=_IT_ATTNFIN,
839
+ epi=_EP_STORE, hq=self.Hq,
840
+ hkv=self._chunk << 16, d=D_l,
841
+ i0=self._maxch | (w_l << 16)))
842
+ names.append(f"L{li}.o")
843
+ rows.append(_row(7, p1=tmp, p2=e["ln_pa"].data_ptr(), p3=hid,
844
+ n=self.hidden, f0=self.eps, i0=_fbits(1.0)))
845
+ names.append(f"L{li}.nr_attn")
846
+ rows.append(_row(_OP_GEMV, p1=hid, p2=e["wgu"].data_ptr(),
847
+ p3=self._gu.data_ptr(),
848
+ p4=e["ln_pf"].data_ptr(), n=2 * e["I"],
849
+ k=self.hidden, it=_IT_RMSNORM_G,
850
+ epi=_EP_STORE, f0=self.eps))
851
+ names.append(f"L{li}.gateup")
852
+ rows.append(_row(_OP_GEMV, p1=self._gu.data_ptr(),
853
+ p2=e["wdn"].data_ptr(), p3=tmp,
854
+ n=self.hidden, k=e["I"], it=_IT_GELU_GLU,
855
+ epi=_EP_STORE))
856
+ names.append(f"L{li}.down")
857
+ rows.append(_row(7, p1=tmp, p2=e["ln_ff"].data_ptr(), p3=hid,
858
+ n=self.hidden, f0=self.eps, i0=_fbits(1.0)))
859
+ names.append(f"L{li}.nr_ffw")
860
+ rows.append(_row(_OP_GEMV, p1=hid, p2=e["plig"].data_ptr(),
861
+ p3=self._t256.data_ptr(),
862
+ p6=self._ple.data_ptr() + li * PL * 2,
863
+ n=PL, k=self.hidden, it=_IT_NONE,
864
+ epi=_EP_GELU_PLE))
865
+ names.append(f"L{li}.plig")
866
+ rows.append(_row(_OP_GEMV, p1=self._t256.data_ptr(),
867
+ p2=e["plpr"].data_ptr(), p3=tmp,
868
+ n=self.hidden, k=PL, it=_IT_NONE,
869
+ epi=_EP_STORE))
870
+ names.append(f"L{li}.plproj")
871
+ rows.append(_row(7, p1=tmp, p2=e["ln_pl"].data_ptr(), p3=hid,
872
+ n=self.hidden, f0=self.eps,
873
+ i0=_fbits(e["lscale"])))
874
+ names.append(f"L{li}.nr_ple")
875
+
876
+ rows.append(_row(_OP_GEMV, p1=hid, p2=embed.data_ptr(),
877
+ p3=self._logits.data_ptr(),
878
+ p4=fnorm.data_ptr(),
879
+ p6=self._parts.data_ptr(), n=self.V,
880
+ k=self.hidden, it=_IT_RMSNORM_G,
881
+ epi=_EP_F32_AMAX_CAP, hkv=1, f0=self.eps,
882
+ i0=_fbits(cap)))
883
+ names.append("lm_head")
884
+ rows.append(_row(_OP_ARGMAX_FIN, p1=self._parts.data_ptr(),
885
+ p3=self._token.data_ptr(),
886
+ p4=self._tokens_out.data_ptr()))
887
+ names.append("argmax.fin")
888
+ self._prog = torch.tensor(rows, dtype=torch.int64, device=dev)
889
+ self.phase_names = names
890
+ self._gemma = True
891
+ self._batch = 0
892
+ return self
893
+
894
+ # ------------------------------------------------------------------
895
+ def decode_step(self, token: int, pos: int, phased: bool = False):
896
+ """One decode step; returns the live fp32 logits buffer [V]."""
897
+ if pos >= self.max_seq:
898
+ raise ValueError("pos exceeds max_seq")
899
+ self._token.fill_(int(token))
900
+ if phased:
901
+ ops.mak_run_phased(self._prog, pos, self.max_gen - 1, False,
902
+ self._maxk)
903
+ else:
904
+ ops.mak_run(self._prog, self._bar, pos, self.max_gen - 1,
905
+ self._maxk)
906
+ return self._logits[:self.V]
907
+
908
+ def decode_step_timed(self, token: int, pos: int):
909
+ """One phase-per-launch step; returns (logits, per-phase ms)."""
910
+ self._token.fill_(int(token))
911
+ ms = ops.mak_run_phased(self._prog, pos, self.max_gen - 1, True,
912
+ self._maxk)
913
+ return self._logits[:self.V], ms
914
+
915
+ def prefill(self, ids: Sequence[int], phased: bool = False):
916
+ """Consume a prompt; returns the last position's logits. Runs in
917
+ chunks of up to chunk_m tokens with weight reads amortized across
918
+ the chunk (bitwise identical to token-by-token consumption)."""
919
+ P = len(ids)
920
+ if phased:
921
+ for i, t in enumerate(ids):
922
+ self.decode_step(int(t), i, phased=True)
923
+ return self._logits
924
+ self._prompt_in[:P].copy_(torch.tensor(ids, dtype=torch.int32))
925
+ ops.mak_run_seq(self._prog, self._bar, 0, P, 1 - P, P, self._maxk,
926
+ self._chunk_m)
927
+ return self._logits[:self.V]
928
+
929
+ def generate(self, prompt_ids: Sequence[int], max_new: int,
930
+ single_launch: bool = True) -> List[int]:
931
+ """Greedy generation. With single_launch (default) the entire call,
932
+ prompt consumption included, is one kernel launch: the in-kernel step
933
+ loop reads prompt tokens from a staged device buffer, then feeds each
934
+ step's argmax to the next step's embedding read. With
935
+ single_launch=False the same computation runs as one launch per
936
+ token; the two paths produce identical tokens.
937
+ """
938
+ P = len(prompt_ids)
939
+ if max_new < 1 or max_new > self.max_gen:
940
+ raise ValueError("max_new out of range")
941
+ if P < 1 or P + max_new > self.max_seq:
942
+ raise ValueError("sequence exceeds max_seq")
943
+ if single_launch:
944
+ self._prompt_in[:P].copy_(
945
+ torch.tensor(prompt_ids, dtype=torch.int32))
946
+ ops.mak_run_seq(self._prog, self._bar, 0, P + max_new - 1,
947
+ 1 - P, P, self._maxk, self._chunk_m)
948
+ return self._tokens_out[:max_new].cpu().tolist()
949
+ pos = 0
950
+ for t in prompt_ids[:-1]:
951
+ self._token.fill_(int(t))
952
+ ops.mak_run(self._prog, self._bar, pos, self.max_gen - 1,
953
+ self._maxk)
954
+ pos += 1
955
+ self._token.fill_(int(prompt_ids[-1]))
956
+ ops.mak_run(self._prog, self._bar, pos, 0, self._maxk)
957
+ pos += 1
958
+ if max_new > 1:
959
+ ops.mak_run_steps(self._prog, self._bar, pos, max_new - 1, 1,
960
+ self._maxk)
961
+ return self._tokens_out[:max_new].cpu().tolist()
962
+
963
+
964
+
build/torch211-cxx11-cu126-x86_64-linux/_model_as_a_kernel_cuda_e33875a.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6461efe05f2ae10a20c69a3648f9bb7bb7af9c6914b95b00f19c754e83298201
3
+ size 3839520
build/torch211-cxx11-cu126-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _model_as_a_kernel_cuda_e33875a
3
+ ops = torch.ops._model_as_a_kernel_cuda_e33875a
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_model_as_a_kernel_cuda_e33875a::{op_name}"
build/torch211-cxx11-cu126-x86_64-linux/metadata.json ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "model-as-a-kernel",
3
+ "id": "_model_as_a_kernel_cuda_e33875a",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "python-depends": [],
7
+ "backend": {
8
+ "type": "cuda",
9
+ "archs": [
10
+ "8.0",
11
+ "8.6",
12
+ "8.9",
13
+ "9.0"
14
+ ]
15
+ },
16
+ "digest": {
17
+ "algorithm": "sha256",
18
+ "files": {
19
+ "__init__.py": "/YX5/zzAOyJacQ7H+j8zaq+xjWyy8w6r0j+ugE1EUs8=",
20
+ "_model_as_a_kernel_cuda_e33875a.abi3.so": "ZGHv4F8q4Qogxpo2SPm7e7evnGkUuVsA8Zx1ToMpggE=",
21
+ "_ops.py": "Pqj4WKtwPyeDG9grQnzLget4g1jsGUdR7ZMa44e4rhs=",
22
+ "model_as_a_kernel/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
23
+ }
24
+ },
25
+ "provenance": {
26
+ "kernel-builder": {
27
+ "version": "0.17.0-dev0",
28
+ "sha": "05f1ed487f599a320311aa26076ec0ad290c3f68",
29
+ "dirty": false
30
+ },
31
+ "kernel": {
32
+ "sha": "e33875ae4eba42fd136a89c388e806281cd4759c",
33
+ "dirty": false
34
+ }
35
+ }
36
+ }
build/torch211-cxx11-cu126-x86_64-linux/model_as_a_kernel/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import importlib.util
3
+ import sys
4
+ from pathlib import Path
5
+ from types import ModuleType
6
+
7
+
8
+ def _import_from_path(file_path: Path) -> ModuleType:
9
+ # We cannot use the module name as-is, after adding it to `sys.modules`,
10
+ # it would also be used for other imports. So, we make a module name that
11
+ # depends on the path for it to be unique using the hex-encoded hash of
12
+ # the path.
13
+ path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
+ module_name = path_hash
15
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
16
+ if spec is None:
17
+ raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
+ module = importlib.util.module_from_spec(spec)
19
+ if module is None:
20
+ raise ImportError(f"Cannot load module {module_name} from spec")
21
+ sys.modules[module_name] = module
22
+ spec.loader.exec_module(module) # type: ignore
23
+ return module
24
+
25
+
26
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
build/torch211-cxx11-cu128-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,964 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """model-as-a-kernel: an entire llama-family decode step as one kernel launch.
2
+
3
+ A phase interpreter runs the whole forward pass (embedding, every layer's
4
+ norms, QKV, rope, attention over the KV cache, SwiGLU MLP, the LM head, and
5
+ the greedy argmax) inside a single persistent kernel, with a software grid
6
+ barrier between phases. The in-kernel step loop makes an entire greedy
7
+ generation, prompt consumption included, one kernel launch: each step's
8
+ argmax feeds the next step's embedding read without leaving the device.
9
+ """
10
+ import os
11
+ import struct
12
+ from typing import List, Optional, Sequence
13
+
14
+ import torch
15
+
16
+ try:
17
+ from ._ops import ops
18
+ except ImportError: # local development build (torch.utils.cpp_extension)
19
+ class _LocalOps:
20
+ def __getattr__(self, name):
21
+ return getattr(torch.ops.mak_ext, name)
22
+
23
+ ops = _LocalOps()
24
+
25
+ __all__ = ["MegaModel"]
26
+
27
+ _OP_EMBED, _OP_GEMV, _OP_QKV_POST, _OP_ATTN = 0, 1, 2, 3
28
+ _OP_ARGMAX_PART, _OP_ARGMAX_FIN = 4, 5
29
+ _OP_KV_APPEND, _OP_NORMRES, _OP_PLEMIX = 6, 7, 8
30
+ _OP_GEMV_PLAIN, _OP_NORMB, _OP_GLUB, _OP_ATTNFINB = 9, 10, 11, 12
31
+ _OP_GEMV_Q4 = 13
32
+ _IT_NONE, _IT_RMSNORM, _IT_SWIGLU, _IT_ATTNFIN = 0, 1, 2, 3
33
+ _IT_RMSNORM_G, _IT_GELU_GLU = 4, 5
34
+ _EP_GELU_PLE, _EP_F32_AMAX_CAP = 5, 6
35
+ _ITF_REDUCE, _ITF_WRITEBACK = 8, 16
36
+ _EP_STORE, _EP_RESID, _EP_F32, _EP_F32_AMAX, _EP_PARTIAL = 0, 1, 2, 3, 4
37
+ _NSLICE = 4
38
+ # Widest [B][K] input panel (bf16 elements) a batched projection stages in
39
+ # shared before it spills to the global-scratch path. 16384 (32 KB) keeps
40
+ # two blocks resident per SM; the persistent kernel shares one occupancy
41
+ # across all phases, so a wider panel would drop every phase to one block
42
+ # per SM, which measures slower than reading the input from L2.
43
+ _STAGE_BUDGET = 16384
44
+
45
+
46
+ def _fbits(f: float) -> int:
47
+ return struct.unpack("<i", struct.pack("<f", float(f)))[0]
48
+
49
+
50
+ def _row(op, p1=0, p2=0, p3=0, p4=0, p5=0, p6=0, n=0, k=0, it=0, epi=0,
51
+ hq=0, hkv=0, d=0, f0=0.0, i0=0):
52
+ return [int(op), int(p1), int(p2), int(p3), int(p4), int(p5), int(p6),
53
+ int(n), int(k), int(it), int(epi), int(hq), int(hkv), int(d),
54
+ _fbits(f0), int(i0)]
55
+
56
+
57
+ class MegaModel:
58
+ """A packed llama-family decoder whose decode step is one kernel launch.
59
+
60
+ Supported architecture: RMSNorm decoder blocks with rotary attention
61
+ (GQA, optional Qwen3-style per-head qk RMSNorm), SwiGLU MLP, no attention
62
+ or MLP biases, plain (unscaled) rope, batch size 1. This covers SmolLM2,
63
+ TinyLlama, Qwen3 dense, Llama-class checkpoints without rope scaling,
64
+ and similar models.
65
+ """
66
+
67
+ def __init__(self, weights: dict, config: dict, device="cuda",
68
+ max_seq: int = 4096, max_gen: int = 4096):
69
+ cfg = dict(config)
70
+ self.L = int(cfg["num_hidden_layers"])
71
+ self.hidden = int(cfg["hidden_size"])
72
+ self.Hq = int(cfg["num_attention_heads"])
73
+ self.Hkv = int(cfg.get("num_key_value_heads") or self.Hq)
74
+ self.D = int(cfg.get("head_dim") or self.hidden // self.Hq)
75
+ self.I = int(cfg["intermediate_size"])
76
+ self.V = int(cfg["vocab_size"])
77
+ self.theta = float(cfg.get("rope_theta", 10000.0))
78
+ self.eps = float(cfg.get("rms_norm_eps", 1e-6))
79
+ self.qk_norm = bool(cfg.get("qk_norm", False))
80
+ self.max_seq = int(max_seq)
81
+ self.max_gen = int(max_gen)
82
+ self.device = torch.device(device)
83
+
84
+ if self.D % 2 or self.D > 256:
85
+ raise ValueError("head_dim must be even and <= 256")
86
+ if self.hidden % 8 or self.I % 8 or (self.Hq * self.D) % 8:
87
+ raise ValueError(
88
+ "hidden, intermediate, and Hq*head_dim must be multiples of 8")
89
+ if self.Hq % self.Hkv:
90
+ raise ValueError("num_attention_heads must be divisible by num_key_value_heads")
91
+
92
+ dt = torch.bfloat16
93
+ dev = self.device
94
+
95
+ def pack(t):
96
+ t = t.detach().to(device=dev, dtype=dt).contiguous()
97
+ self._keep.append(t)
98
+ return t
99
+
100
+ # Row-tile-of-8 weight layout [N/8][K/8][8][8] (MAK_TILED=1): the
101
+ # eight warps of a block read one contiguous 4KB stream per step
102
+ # instead of eight 4KB-strided pieces. Measured slower on every
103
+ # card (H200 +15 percent, RTX PRO +9, Ada +14 — per-warp stream
104
+ # locality beats per-block contiguity on all of them), so the
105
+ # default is the plain layout; the flag rides in the program and
106
+ # both layouts compute identical bits.
107
+ env_t = os.environ.get("MAK_TILED", "").strip()
108
+ self.tiled = env_t == "1"
109
+
110
+ def pack_tiled(t):
111
+ t = t.detach().to(device=dev, dtype=dt).contiguous()
112
+ if not self.tiled:
113
+ self._keep.append(t)
114
+ return t
115
+ n, k = t.shape
116
+ assert k % 8 == 0
117
+ n8 = (n + 7) // 8 * 8
118
+ if n8 != n:
119
+ t = torch.cat(
120
+ [t, torch.zeros(n8 - n, k, dtype=dt, device=dev)], 0)
121
+ t = t.view(n8 // 8, 8, k // 8, 8).permute(0, 2, 1, 3).contiguous()
122
+ self._keep.append(t)
123
+ return t
124
+
125
+ self._keep: List[torch.Tensor] = []
126
+ embed = pack(weights["embed"])
127
+ final_norm = pack(weights["norm"])
128
+ lm_head = weights["lm_head"] # packed below (dense-tiled or nf4)
129
+ inv_freq = weights.get("inv_freq")
130
+ if inv_freq is None:
131
+ d_idx = torch.arange(0, self.D, 2, dtype=torch.float32)
132
+ inv_freq = 1.0 / (self.theta ** (d_idx / self.D))
133
+ inv_freq = inv_freq.detach().to(device=dev,
134
+ dtype=torch.float32).contiguous()
135
+ if inv_freq.numel() != self.D // 2:
136
+ raise ValueError("inv_freq length must be head_dim / 2")
137
+ self._keep.append(inv_freq)
138
+ self._invf = inv_freq
139
+
140
+ qdim, kvdim = self.Hq * self.D, self.Hkv * self.D
141
+
142
+ def _is_q4(w):
143
+ return isinstance(w, dict) and w.get("q4")
144
+
145
+ def pack_w(w):
146
+ # nf4-packed weights move to the device intact; dense weights take
147
+ # the usual (optionally row-tiled) bf16 packing.
148
+ if _is_q4(w):
149
+ pk = w["packed"].to(dev).contiguous()
150
+ am = w["absmax"].to(device=dev, dtype=torch.float32).contiguous()
151
+ self._keep.append(pk)
152
+ self._keep.append(am)
153
+ return {"q4": True, "packed": pk, "absmax": am,
154
+ "N": int(w["N"]), "K": int(w["K"])}
155
+ return pack_tiled(w)
156
+
157
+ def _wshape(w, expected):
158
+ got = (int(w["N"]), int(w["K"])) if _is_q4(w) else tuple(w.shape)
159
+ assert got == expected, (got, expected)
160
+
161
+ self._has_q4 = False
162
+ self._layers = []
163
+ for lw in weights["layers"]:
164
+ _wshape(lw["wqkv"], (qdim + 2 * kvdim, self.hidden))
165
+ _wshape(lw["wo"], (self.hidden, qdim))
166
+ _wshape(lw["wgu"], (2 * self.I, self.hidden))
167
+ _wshape(lw["wdown"], (self.hidden, self.I))
168
+ layer = {
169
+ "wqkv": pack_w(lw["wqkv"]), "wo": pack_w(lw["wo"]),
170
+ "wgu": pack_w(lw["wgu"]), "wdown": pack_w(lw["wdown"]),
171
+ "ln1": pack(lw["ln1"]), "ln2": pack(lw["ln2"]),
172
+ "qn": pack(lw["qn"]) if self.qk_norm else None,
173
+ "kn": pack(lw["kn"]) if self.qk_norm else None,
174
+ }
175
+ self._has_q4 |= any(_is_q4(layer[n]) for n in
176
+ ("wqkv", "wo", "wgu", "wdown"))
177
+ self._layers.append(layer)
178
+ assert len(self._layers) == self.L
179
+ assert embed.shape == (self.V, self.hidden)
180
+ lm_head = pack_w(lm_head)
181
+ self._has_q4 |= _is_q4(lm_head)
182
+ if not _is_q4(lm_head):
183
+ assert lm_head.numel() >= self.V * self.hidden # row-tiled, padded
184
+ self._w_embed, self._w_fnorm, self._w_lmhead = (embed, final_norm,
185
+ lm_head)
186
+ self._qdim, self._kvdim = qdim, kvdim
187
+
188
+ # Working buffers. Pointers to these are baked into the program, so
189
+ # they (like the packed weights) live for the model's lifetime.
190
+ self._maxk = max(self.hidden, qdim, self.I)
191
+ # prefill chunk size, bounded by the staging shared-memory budget
192
+ # (16K bf16 elements; the cp.async weight ring rides alongside)
193
+ self._chunk_m = max(1, min(8, 16384 // self._maxk))
194
+ cm = 8 # buffers sized for the maximum chunk
195
+ self._hidden = torch.empty(cm * self.hidden, dtype=dt, device=dev)
196
+ self._qkv = torch.empty(cm * (qdim + 2 * kvdim), dtype=dt, device=dev)
197
+ self._gu = torch.empty(cm * 2 * self.I, dtype=dt, device=dev)
198
+ self._logits = torch.empty(self.V, dtype=torch.float32, device=dev)
199
+ # attention chunk length (kernel maximum 128); shorter chunks give
200
+ # the attention phase more grid parallelism at the cost of more
201
+ # softmax partials to finalize
202
+ env_ch = os.environ.get("MAK_CHUNK", "").strip()
203
+ self._chunk = int(env_ch) if env_ch in ("32", "64", "128") else 128
204
+ self._maxch = (self.max_seq + self._chunk - 1) // self._chunk
205
+ self._partials = torch.empty(
206
+ cm * self.Hq * self._maxch * (self.D + 2), dtype=torch.float32,
207
+ device=dev)
208
+ self._kcache = torch.zeros(self.L, self.Hkv, self.max_seq, self.D,
209
+ dtype=dt, device=dev)
210
+ self._vcache = torch.zeros(self.L, self.Hkv, self.max_seq, self.D,
211
+ dtype=dt, device=dev)
212
+ self._token = torch.zeros(1, dtype=torch.int32, device=dev)
213
+ self._prompt_in = torch.zeros(self.max_seq, dtype=torch.int32,
214
+ device=dev)
215
+ self._tokens_out = torch.zeros(self.max_gen, dtype=torch.int32,
216
+ device=dev)
217
+ self._bar = torch.zeros(34, dtype=torch.int32, device=dev)
218
+
219
+ probe = torch.zeros(1, 16, dtype=torch.int64, device=dev)
220
+ self._nblocks = int(ops.mak_num_blocks(probe, self._maxk))
221
+ self._parts = torch.zeros(self._nblocks, dtype=torch.int64, device=dev)
222
+ try:
223
+ self._bw_per_sm = float(ops.mak_bw_per_sm(probe))
224
+ except (AttributeError, RuntimeError):
225
+ self._bw_per_sm = 0.0
226
+
227
+ self._batch = 0
228
+ self._pos_b = None
229
+ self._kv_bstride = 0
230
+ self._gemma = False
231
+ # transformed-input scratch: the nf4 and wide-batch paths write the
232
+ # dense bf16 input here (one row per active token) for the following
233
+ # plain/quant GEMV; sized for a prefill chunk (up to 8 rows)
234
+ self._xg = torch.empty(cm * self._maxk, dtype=dt, device=dev)
235
+ self._build_programs()
236
+
237
+ # ------------------------------------------------------------------
238
+ def _kvp(self, cache, li: int, b: int) -> int:
239
+ t = cache[li]
240
+ return (t[b] if t.dim() == 4 else t).data_ptr()
241
+
242
+ def _build_rows(self, kv_slice: int = 0, batch: bool = False,
243
+ big: bool = False):
244
+ """Program rows for the llama-family path. kv_slice selects the
245
+ batch slice the KV pointers address (per-sequence prefill);
246
+ batch=True emits the batched-decode tail (per-row fp32 logits and
247
+ explicit argmax phases) instead of the fused LM-head amax. In batch
248
+ mode each projection stays fused (input staged in shared) when its
249
+ [B][K] panel fits the budget; the projections whose K is too wide
250
+ transform into a global scratch and read it with a plain GEMV."""
251
+ _tbit = (1 << 20) if self.tiled else 0 # GEMV weight layout flag
252
+ qdim, kvdim = self._qdim, self._kvdim
253
+ embed, lm_head = self._w_embed, self._w_lmhead
254
+ final_norm = self._w_fnorm
255
+ xg = self._xg.data_ptr()
256
+ B = self._batch if batch else 1
257
+ rows, names = [], []
258
+ staged_elems = [0] # widest [B][K] panel any fused projection stages
259
+
260
+ def fits(K):
261
+ return B * K <= _STAGE_BUDGET
262
+
263
+ def note(K):
264
+ staged_elems[0] = max(staged_elems[0], B * K)
265
+
266
+ def _q4(w):
267
+ return isinstance(w, dict) and w.get("q4")
268
+
269
+ def _wgemv(out, w, N, K, epi, resid):
270
+ # the GEMV after a transform-to-scratch phase: nf4-dequant when
271
+ # the weight is packed, otherwise a plain bf16 GEMV
272
+ if _q4(w):
273
+ rows.append(_row(_OP_GEMV_Q4, p1=xg, p2=w["packed"].data_ptr(),
274
+ p3=out, p5=resid, p6=w["absmax"].data_ptr(),
275
+ n=N, k=K, epi=epi))
276
+ else:
277
+ rows.append(_row(_OP_GEMV_PLAIN, p1=xg, p2=w.data_ptr(),
278
+ p3=out, p5=resid, n=N, k=K, it=_tbit,
279
+ epi=epi))
280
+
281
+ def norm_gemv(name, gamma, w, out, N, K, epi, inp, resid=0,
282
+ variant=_IT_RMSNORM):
283
+ if _q4(w) or (big and not fits(K)):
284
+ rows.append(_row(_OP_NORMB, p1=inp, p2=gamma, p3=xg, k=K,
285
+ it=variant, f0=self.eps))
286
+ names.append(name + ".norm")
287
+ _wgemv(out, w, N, K, epi, resid)
288
+ names.append(name)
289
+ else:
290
+ note(K)
291
+ rows.append(_row(_OP_GEMV, p1=inp, p2=w.data_ptr(), p3=out,
292
+ p4=gamma, p5=resid, n=N, k=K,
293
+ it=variant | _tbit, epi=epi, f0=self.eps))
294
+ names.append(name)
295
+
296
+ def glu_gemv(name, gu, w, out, resid, variant=_IT_SWIGLU):
297
+ if _q4(w) or (big and not fits(self.I)):
298
+ rows.append(_row(_OP_GLUB, p1=gu, p3=xg, k=self.I, it=variant))
299
+ names.append(name + ".glu")
300
+ _wgemv(out, w, self.hidden, self.I, _EP_RESID, resid)
301
+ names.append(name)
302
+ else:
303
+ note(self.I)
304
+ rows.append(_row(_OP_GEMV, p1=gu, p2=w.data_ptr(), p3=out,
305
+ p5=resid, n=self.hidden, k=self.I,
306
+ it=variant | _tbit, epi=_EP_RESID))
307
+ names.append(name)
308
+
309
+ def attn_gemv(name, w, out, resid):
310
+ if _q4(w) or (big and not fits(qdim)):
311
+ rows.append(_row(_OP_ATTNFINB, p1=self._partials.data_ptr(),
312
+ p3=xg, k=qdim, hq=self.Hq,
313
+ hkv=self._chunk << 16, d=self.D,
314
+ i0=self._maxch))
315
+ names.append(name + ".fin")
316
+ _wgemv(out, w, self.hidden, qdim, _EP_RESID, resid)
317
+ names.append(name)
318
+ else:
319
+ note(qdim)
320
+ rows.append(_row(_OP_GEMV, p1=self._partials.data_ptr(),
321
+ p2=w.data_ptr(), p3=out, p5=resid,
322
+ n=self.hidden, k=qdim,
323
+ it=_IT_ATTNFIN | _tbit, epi=_EP_RESID,
324
+ hq=self.Hq, hkv=self._chunk << 16, d=self.D,
325
+ i0=self._maxch))
326
+ names.append(name)
327
+
328
+ rows.append(_row(_OP_EMBED, p2=embed.data_ptr(),
329
+ p3=self._hidden.data_ptr(),
330
+ p4=self._parts.data_ptr(),
331
+ p5=self._prompt_in.data_ptr(),
332
+ p6=self._token.data_ptr(),
333
+ n=self._tokens_out.data_ptr(), k=self.hidden))
334
+ names.append("embed")
335
+ scale = 1.0 / (self.D ** 0.5)
336
+ hid = self._hidden.data_ptr()
337
+ for li, lw in enumerate(self._layers):
338
+ kc = self._kvp(self._kcache, li, kv_slice)
339
+ vc = self._kvp(self._vcache, li, kv_slice)
340
+ norm_gemv(f"L{li}.qkv", lw["ln1"].data_ptr(),
341
+ lw["wqkv"], self._qkv.data_ptr(),
342
+ qdim + 2 * kvdim, self.hidden, _EP_STORE, hid)
343
+ rows.append(_row(6, p1=self._qkv.data_ptr(), p2=kc, p3=vc,
344
+ p6=lw["kn"].data_ptr() if self.qk_norm else 0,
345
+ n=self.max_seq, k=1 if self.qk_norm else 0,
346
+ it=_fbits(self.eps),
347
+ epi=self._invf.data_ptr(),
348
+ hq=self.Hq, hkv=self.Hkv, d=self.D))
349
+ names.append(f"L{li}.kvappend")
350
+ rows.append(_row(_OP_ATTN, p1=self._qkv.data_ptr(), p2=kc,
351
+ p3=vc, p4=self._partials.data_ptr(),
352
+ p5=lw["qn"].data_ptr() if self.qk_norm else 0,
353
+ p6=lw["kn"].data_ptr() if self.qk_norm else 0,
354
+ n=self.max_seq, k=1 if self.qk_norm else 0,
355
+ it=_fbits(self.eps),
356
+ epi=self._invf.data_ptr(),
357
+ hq=self.Hq,
358
+ hkv=self.Hkv | (self._chunk << 16),
359
+ d=self.D, f0=scale, i0=self._maxch))
360
+ names.append(f"L{li}.attn")
361
+ attn_gemv(f"L{li}.o", lw["wo"], hid, hid)
362
+ norm_gemv(f"L{li}.gateup", lw["ln2"].data_ptr(),
363
+ lw["wgu"], self._gu.data_ptr(), 2 * self.I,
364
+ self.hidden, _EP_STORE, hid)
365
+ glu_gemv(f"L{li}.down", self._gu.data_ptr(), lw["wdown"], hid, hid)
366
+ if batch or _q4(lm_head):
367
+ norm_gemv("lm_head", final_norm.data_ptr(), lm_head,
368
+ self._logits.data_ptr(), self.V, self.hidden, _EP_F32,
369
+ hid)
370
+ rows.append(_row(_OP_ARGMAX_PART, p1=self._logits.data_ptr(),
371
+ p3=self._parts.data_ptr(), n=self.V))
372
+ names.append("argmax.part")
373
+ rows.append(_row(_OP_ARGMAX_FIN, p1=self._parts.data_ptr(),
374
+ p3=self._token.data_ptr(),
375
+ p4=self._tokens_out.data_ptr(),
376
+ k=self.max_gen))
377
+ names.append("argmax.fin")
378
+ else:
379
+ rows.append(_row(_OP_GEMV, p1=hid, p2=lm_head.data_ptr(),
380
+ p3=self._logits.data_ptr(),
381
+ p4=final_norm.data_ptr(),
382
+ p6=self._parts.data_ptr(), n=self.V,
383
+ k=self.hidden, it=_IT_RMSNORM | _tbit,
384
+ epi=_EP_F32_AMAX, hkv=1, f0=self.eps))
385
+ names.append("lm_head")
386
+ rows.append(_row(_OP_ARGMAX_FIN, p1=self._parts.data_ptr(),
387
+ p3=self._token.data_ptr(),
388
+ p4=self._tokens_out.data_ptr()))
389
+ names.append("argmax.fin")
390
+ se = max(staged_elems[0], self._maxk)
391
+ return rows, names, se
392
+
393
+ def _build_programs(self):
394
+ dev = self.device
395
+ rows, names, _ = self._build_rows(kv_slice=0, batch=False)
396
+ self._prog = torch.tensor(rows, dtype=torch.int64, device=dev)
397
+ self.phase_names = names
398
+ if self._batch:
399
+ big = self._batch > min(8, 16384 // self._maxk)
400
+ rows, _, se = self._build_rows(kv_slice=0, batch=True, big=big)
401
+ self._prog_batch = torch.tensor(rows, dtype=torch.int64,
402
+ device=dev)
403
+ self._batch_stage_elems = int(se)
404
+ self._progs_prefill = [self._prog]
405
+ for b in range(1, self._batch):
406
+ rows, _, _ = self._build_rows(kv_slice=b, batch=False)
407
+ self._progs_prefill.append(
408
+ torch.tensor(rows, dtype=torch.int64, device=dev))
409
+
410
+ def batch_max(self) -> int:
411
+ """Largest batch size this model supports. Up to 8 sequences stage
412
+ in shared; beyond that the transformed input moves to a global
413
+ scratch, bounded by the kernel's register accumulators."""
414
+ try:
415
+ hard = int(ops.mak_batch_maxb())
416
+ except (AttributeError, RuntimeError):
417
+ hard = 8
418
+ return 1 if self._gemma else hard
419
+
420
+ # ------------------------------------------------------------------
421
+ def enable_batch(self, B: int):
422
+ """Allocate per-sequence KV caches, token buffers, and programs for
423
+ batched decode. B up to 8 stages the input in shared; larger B (up
424
+ to batch_max()) routes the transformed input through a global
425
+ scratch so the batch width is not bounded by the projection size."""
426
+ if self._gemma:
427
+ raise ValueError("batched decode covers the llama-family path")
428
+ maxb = self.batch_max()
429
+ if not 1 <= B <= maxb:
430
+ raise ValueError(f"B must be in [1, {maxb}] for this model")
431
+ if self._batch == B:
432
+ return
433
+ dt, dev = torch.bfloat16, self.device
434
+ self._kcache = torch.zeros(self.L, B, self.Hkv, self.max_seq,
435
+ self.D, dtype=dt, device=dev)
436
+ self._vcache = torch.zeros_like(self._kcache)
437
+ self._token = torch.zeros(B, dtype=torch.int32, device=dev)
438
+ self._tokens_out = torch.zeros(B * self.max_gen, dtype=torch.int32,
439
+ device=dev)
440
+ self._logits = torch.empty(B * self.V, dtype=torch.float32,
441
+ device=dev)
442
+ self._parts = torch.zeros(B * self._nblocks, dtype=torch.int64,
443
+ device=dev)
444
+ self._pos_b = torch.zeros(B, dtype=torch.int32, device=dev)
445
+ # transformed-input scratch: one row per active token, and prefill
446
+ # (during generate_batch) still uses up to chunk_m rows
447
+ self._xg = torch.empty(max(8, B) * self._maxk, dtype=dt, device=dev)
448
+ # working buffers hold one row per active sequence; prefill chunks
449
+ # still use up to chunk_m rows, so keep at least the fused capacity
450
+ nb = max(8, B)
451
+ qkvd = self._qdim + 2 * self._kvdim
452
+ self._hidden = torch.empty(nb * self.hidden, dtype=dt, device=dev)
453
+ self._qkv = torch.empty(nb * qkvd, dtype=dt, device=dev)
454
+ self._gu = torch.empty(nb * 2 * self.I, dtype=dt, device=dev)
455
+ self._partials = torch.empty(
456
+ nb * self.Hq * self._maxch * (self.D + 2), dtype=torch.float32,
457
+ device=dev)
458
+ self._kv_bstride = self.Hkv * self.max_seq * self.D
459
+ self._batch = B
460
+ self._build_programs()
461
+
462
+ def decode_batch(self, tokens: Sequence[int], positions: Sequence[int],
463
+ steps: int = 1) -> torch.Tensor:
464
+ """Batched greedy decode: sequence b consumes tokens[b] at
465
+ positions[b]; `steps` in-kernel steps run with per-sequence argmax
466
+ feedback. Returns the generated tokens [B, steps]; the final
467
+ step's fp32 logits are live in batch_logits()."""
468
+ B = self._batch
469
+ if B < 1:
470
+ raise ValueError("call enable_batch(B) first")
471
+ if len(tokens) != B or len(positions) != B:
472
+ raise ValueError("tokens and positions must have B entries")
473
+ if steps < 1 or steps > self.max_gen:
474
+ raise ValueError("steps out of range")
475
+ if max(positions) + steps > self.max_seq:
476
+ raise ValueError("sequence exceeds max_seq")
477
+ self._token.copy_(torch.tensor(list(tokens), dtype=torch.int32))
478
+ self._pos_b.copy_(torch.tensor(list(positions), dtype=torch.int32))
479
+ ops.mak_run_batch(self._prog_batch, self._bar, self._pos_b, steps,
480
+ 0, self._maxk, self._kv_bstride,
481
+ self._batch_stage_elems)
482
+ return self._tokens_out.view(B, self.max_gen)[:, :steps]
483
+
484
+ def batch_logits(self) -> torch.Tensor:
485
+ """fp32 logits [B, V] of the most recent batched step."""
486
+ return self._logits.view(max(self._batch, 1), self.V)
487
+
488
+ def generate_batch(self, prompts: Sequence[Sequence[int]],
489
+ max_new: int) -> List[List[int]]:
490
+ """Greedy generation for a batch of prompts: per-sequence prefill,
491
+ then one batched decode launch. Each sequence's token stream is
492
+ bit-identical to its own single-sequence generate()."""
493
+ B = len(prompts)
494
+ if max_new < 1 or max_new > self.max_gen:
495
+ raise ValueError("max_new out of range")
496
+ self.enable_batch(B)
497
+ firsts: List[int] = []
498
+ for b, ids in enumerate(prompts):
499
+ P = len(ids)
500
+ if P < 1 or P + max_new > self.max_seq:
501
+ raise ValueError("sequence exceeds max_seq")
502
+ self._prompt_in[:P].copy_(
503
+ torch.tensor(list(ids), dtype=torch.int32))
504
+ ops.mak_run_seq(self._progs_prefill[b], self._bar, 0, P, 1 - P,
505
+ P, self._maxk, self._chunk_m)
506
+ firsts.append(int(self._token[0].item()))
507
+ if max_new == 1:
508
+ return [[t] for t in firsts]
509
+ self._token.copy_(torch.tensor(firsts, dtype=torch.int32))
510
+ self._pos_b.copy_(torch.tensor([len(p) for p in prompts],
511
+ dtype=torch.int32))
512
+ ops.mak_run_batch(self._prog_batch, self._bar, self._pos_b,
513
+ max_new - 1, 0, self._maxk, self._kv_bstride,
514
+ self._batch_stage_elems)
515
+ rest = (self._tokens_out.view(B, self.max_gen)[:, :max_new - 1]
516
+ .cpu().tolist())
517
+ return [[firsts[b]] + rest[b] for b in range(B)]
518
+
519
+ # ------------------------------------------------------------------
520
+ @staticmethod
521
+ def _dense_weight(mod):
522
+ """The effective bf16 weight of a linear layer, dequantizing packed
523
+ quantized layers (bitsandbytes 4-bit and 8-bit) so a quantized
524
+ checkpoint loads directly. Dequantization is exact and does not
525
+ depend on the card, so inference on the result keeps every
526
+ guarantee; the weights materialize to bf16, so the quantized memory
527
+ footprint is not preserved (that needs the in-kernel packed path)."""
528
+ w = mod.weight
529
+ qs = getattr(w, "quant_state", None)
530
+ if qs is not None: # bitsandbytes 4-bit (nf4 / fp4)
531
+ import bitsandbytes as bnb
532
+ return bnb.functional.dequantize_4bit(w.data, qs).to(torch.bfloat16)
533
+ if getattr(w, "SCB", None) is not None or hasattr(mod, "SCB"):
534
+ # bitsandbytes 8-bit (LLM.int8): row scales in SCB, int8 in CB
535
+ scb = w.SCB if getattr(w, "SCB", None) is not None else mod.SCB
536
+ cb = w.data if w.data.dtype == torch.int8 else mod.CB
537
+ return (cb.to(torch.float32)
538
+ * (scb.to(torch.float32) / 127.0).unsqueeze(1)
539
+ ).to(torch.bfloat16)
540
+ return w
541
+
542
+ @staticmethod
543
+ def _q4_parts(mod):
544
+ """(packed [N, K/2] uint8, absmax [N*K/64] fp32) for a bitsandbytes
545
+ nf4 layer whose weights stay packed, else None. Double-quantized
546
+ absmax is materialized to fp32 so the kernel needs one scale array."""
547
+ w = getattr(mod, "weight", None)
548
+ qs = getattr(w, "quant_state", None)
549
+ if qs is None or getattr(qs, "quant_type", None) != "nf4":
550
+ return None
551
+ import bitsandbytes as bnb
552
+ n, k = int(qs.shape[0]), int(qs.shape[1])
553
+ if k % 64:
554
+ return None # kernel assumes 64 | K for per-block absmax
555
+ packed = w.data.reshape(n, k // 2).contiguous()
556
+ if getattr(qs, "nested", False):
557
+ absmax = bnb.functional.dequantize_blockwise(
558
+ qs.absmax, qs.state2) + qs.offset
559
+ else:
560
+ absmax = qs.absmax
561
+ return packed, absmax.float().reshape(-1).contiguous()
562
+
563
+ @classmethod
564
+ def _proj(cls, mods):
565
+ """A projection, concatenated across `mods` (q/k/v or gate/up). Stays
566
+ nf4-packed when every part is nf4, else a dense bf16 tensor."""
567
+ parts = [cls._q4_parts(m) for m in mods]
568
+ if all(p is not None for p in parts):
569
+ packed = torch.cat([p[0] for p in parts], 0)
570
+ absmax = torch.cat([p[1] for p in parts], 0)
571
+ return {"q4": True, "packed": packed, "absmax": absmax,
572
+ "N": packed.shape[0], "K": packed.shape[1] * 2}
573
+ return torch.cat([cls._dense_weight(m) for m in mods], 0)
574
+
575
+ @classmethod
576
+ def from_pretrained(cls, model, device="cuda", max_seq: int = 4096,
577
+ max_gen: int = 4096):
578
+ """Build from a transformers model (object or repo id). A quantized
579
+ checkpoint (bitsandbytes 4-bit/8-bit) is accepted directly; its
580
+ weights are dequantized to bf16 at load."""
581
+ if isinstance(model, str):
582
+ from transformers import AutoModelForCausalLM
583
+ model = AutoModelForCausalLM.from_pretrained(
584
+ model, torch_dtype=torch.bfloat16)
585
+ if "gemma4" in getattr(model.config, "model_type", ""):
586
+ return cls._from_gemma4(model, device, max_seq, max_gen)
587
+ hf = model.config
588
+ dec = model.model
589
+ attn0 = dec.layers[0].self_attn
590
+ qk_norm = hasattr(attn0, "q_norm") and attn0.q_norm is not None
591
+ if getattr(attn0.q_proj, "bias", None) is not None:
592
+ raise ValueError("attention biases are not supported")
593
+
594
+ # rope config across transformers API generations: v5 keeps theta and
595
+ # type in a rope_parameters dict; v4 used a rope_theta attribute and
596
+ # an optional rope_scaling dict.
597
+ rp = getattr(hf, "rope_parameters", None)
598
+ if not isinstance(rp, dict):
599
+ rs = getattr(hf, "rope_scaling", None)
600
+ rp = rs if isinstance(rs, dict) else {}
601
+ theta = rp.get("rope_theta")
602
+ if theta is None:
603
+ theta = getattr(hf, "rope_theta", 10000.0)
604
+ rope_type = rp.get("rope_type", rp.get("type", "default"))
605
+ if rope_type not in ("default", "llama3"):
606
+ raise ValueError(f"rope type {rope_type!r} is not supported")
607
+ # take the frequency table transformers computed (scaling included)
608
+ rot = dec.rotary_emb
609
+ att_scale = float(getattr(rot, "attention_scaling", 1.0))
610
+ if att_scale != 1.0:
611
+ raise ValueError("rope attention_scaling != 1 is not supported")
612
+ inv_freq = rot.inv_freq.detach().float()
613
+
614
+ pj = cls._proj
615
+ layers = []
616
+ for lyr in dec.layers:
617
+ a, m = lyr.self_attn, lyr.mlp
618
+ layers.append({
619
+ "wqkv": pj([a.q_proj, a.k_proj, a.v_proj]),
620
+ "wo": pj([a.o_proj]),
621
+ "wgu": pj([m.gate_proj, m.up_proj]),
622
+ "wdown": pj([m.down_proj]),
623
+ "ln1": lyr.input_layernorm.weight,
624
+ "ln2": lyr.post_attention_layernorm.weight,
625
+ "qn": a.q_norm.weight if qk_norm else None,
626
+ "kn": a.k_norm.weight if qk_norm else None,
627
+ })
628
+ weights = {"embed": dec.embed_tokens.weight, "norm": dec.norm.weight,
629
+ "lm_head": pj([model.lm_head]), "layers": layers,
630
+ "inv_freq": inv_freq}
631
+ config = {
632
+ "num_hidden_layers": hf.num_hidden_layers,
633
+ "hidden_size": hf.hidden_size,
634
+ "num_attention_heads": hf.num_attention_heads,
635
+ "num_key_value_heads": getattr(hf, "num_key_value_heads", None),
636
+ "head_dim": getattr(hf, "head_dim", None),
637
+ "intermediate_size": hf.intermediate_size,
638
+ "vocab_size": hf.vocab_size,
639
+ "rope_theta": float(theta),
640
+ "rms_norm_eps": hf.rms_norm_eps,
641
+ "qk_norm": qk_norm,
642
+ }
643
+ return cls(weights, config, device=device, max_seq=max_seq,
644
+ max_gen=max_gen)
645
+
646
+ # ------------------------------------------------------------------
647
+ @classmethod
648
+ def _from_gemma4(cls, model, device, max_seq, max_gen):
649
+ """gemma-4-E2B-it program: gemma norms (fp32, raw weight),
650
+ gelu-tanh gating, post-sublayer norm phases, per-layer-input
651
+ pathway, sliding windows, shared KV, dual head dims, softcapped
652
+ head. Scope is the E2B text model only."""
653
+ self = cls.__new__(cls)
654
+ cfg = model.config.text_config if hasattr(model.config, "text_config") \
655
+ else model.config
656
+ dec = model.model.language_model if hasattr(model.model, "language_model") \
657
+ else model.model
658
+ assert cfg.model_type == "gemma4_text", cfg.model_type
659
+ assert not cfg.enable_moe_block
660
+
661
+ self.L = int(cfg.num_hidden_layers)
662
+ self.hidden = int(cfg.hidden_size)
663
+ self.Hq = int(cfg.num_attention_heads)
664
+ self.V = int(cfg.vocab_size)
665
+ self.eps = float(cfg.rms_norm_eps)
666
+ self.qk_norm = True
667
+ self.max_seq = int(max_seq)
668
+ self.max_gen = int(max_gen)
669
+ self.device = torch.device(device)
670
+ self.tiled = False
671
+ win = int(cfg.sliding_window)
672
+ cap = float(cfg.final_logit_softcapping)
673
+ PL = int(cfg.hidden_size_per_layer_input)
674
+ dt = torch.bfloat16
675
+ dev = self.device
676
+ self._keep = []
677
+
678
+ def pack(t):
679
+ t = t.detach().to(device=dev, dtype=dt).contiguous()
680
+ self._keep.append(t)
681
+ return t
682
+
683
+ def packf(t):
684
+ t = t.detach().to(device=dev, dtype=torch.float32).contiguous()
685
+ self._keep.append(t)
686
+ return t
687
+
688
+ embed = pack(dec.embed_tokens.weight)
689
+ ple_tab = pack(dec.embed_tokens_per_layer.weight)
690
+ plm_w = pack(dec.per_layer_model_projection.weight)
691
+ pln_w = pack(dec.per_layer_projection_norm.weight)
692
+ fnorm = pack(dec.norm.weight)
693
+ rot = dec.rotary_emb
694
+ invf = {lt: packf(getattr(rot, f"{lt}_inv_freq").float())
695
+ for lt in set(cfg.layer_types)}
696
+ ascale = {lt: float(getattr(rot, f"{lt}_attention_scaling"))
697
+ for lt in set(cfg.layer_types)}
698
+
699
+ layers = []
700
+ for li, lyr in enumerate(dec.layers):
701
+ a = lyr.self_attn
702
+ D_l = int(a.head_dim)
703
+ shared = bool(a.is_kv_shared_layer)
704
+ ent = {
705
+ "D": D_l, "shared": shared,
706
+ "lscale": float(lyr.layer_scalar.float().item()),
707
+ "type": cfg.layer_types[li],
708
+ "I": int(lyr.mlp.intermediate_size),
709
+ "qn": pack(a.q_norm.weight),
710
+ "o": pack(a.o_proj.weight),
711
+ "ln_in": pack(lyr.input_layernorm.weight),
712
+ "ln_pa": pack(lyr.post_attention_layernorm.weight),
713
+ "ln_pf": pack(lyr.pre_feedforward_layernorm.weight),
714
+ "ln_ff": pack(lyr.post_feedforward_layernorm.weight),
715
+ "ln_pl": pack(lyr.post_per_layer_input_norm.weight),
716
+ "plig": pack(lyr.per_layer_input_gate.weight),
717
+ "plpr": pack(lyr.per_layer_projection.weight),
718
+ "wgu": pack(torch.cat([lyr.mlp.gate_proj.weight,
719
+ lyr.mlp.up_proj.weight], 0)),
720
+ "wdn": pack(lyr.mlp.down_proj.weight),
721
+ }
722
+ if shared:
723
+ ent["wqkv"] = pack(a.q_proj.weight)
724
+ ent["kn"] = None
725
+ else:
726
+ ent["wqkv"] = pack(torch.cat(
727
+ [a.q_proj.weight, a.k_proj.weight, a.v_proj.weight], 0))
728
+ ent["kn"] = pack(a.k_norm.weight)
729
+ ent["kc"] = torch.zeros(self.max_seq, D_l, dtype=dt,
730
+ device=dev)
731
+ ent["vc"] = torch.zeros(self.max_seq, D_l, dtype=dt,
732
+ device=dev)
733
+ self._keep += [ent["kc"], ent["vc"]]
734
+ layers.append(ent)
735
+ # shared layers read the last non-shared layer of their type
736
+ src = {}
737
+ for li, ent in enumerate(layers):
738
+ if not ent["shared"]:
739
+ src[ent["type"]] = ent
740
+ for ent in layers:
741
+ if ent["shared"]:
742
+ ent["kc"] = src[ent["type"]]["kc"]
743
+ ent["vc"] = src[ent["type"]]["vc"]
744
+
745
+ maxD = max(e["D"] for e in layers)
746
+ maxI = max(e["I"] for e in layers)
747
+ self._maxk = max(self.hidden, self.Hq * maxD, maxI)
748
+ self._chunk_m = 1
749
+ self._chunk = 128
750
+ self._maxch = (self.max_seq + self._chunk - 1) // self._chunk
751
+ self._hidden = torch.empty(self.hidden, dtype=dt, device=dev)
752
+ self._tmp = torch.empty(self.hidden, dtype=dt, device=dev)
753
+ self._qkv = torch.empty((self.Hq + 2) * maxD, dtype=dt, device=dev)
754
+ self._gu = torch.empty(2 * maxI, dtype=dt, device=dev)
755
+ self._t256 = torch.empty(PL, dtype=dt, device=dev)
756
+ self._ctxraw = torch.empty(self.L * PL, dtype=dt, device=dev)
757
+ self._pletok = torch.empty(self.L * PL, dtype=dt, device=dev)
758
+ self._ple = torch.empty(self.L * PL, dtype=dt, device=dev)
759
+ self._logits = torch.empty(self.V, dtype=torch.float32, device=dev)
760
+ self._partials = torch.empty(
761
+ self.Hq * self._maxch * (maxD + 2), dtype=torch.float32,
762
+ device=dev)
763
+ self._token = torch.zeros(1, dtype=torch.int32, device=dev)
764
+ self._prompt_in = torch.zeros(self.max_seq, dtype=torch.int32,
765
+ device=dev)
766
+ self._tokens_out = torch.zeros(self.max_gen, dtype=torch.int32,
767
+ device=dev)
768
+ self._bar = torch.zeros(34, dtype=torch.int32, device=dev)
769
+ probe = torch.zeros(1, 16, dtype=torch.int64, device=dev)
770
+ self._nblocks = int(ops.mak_num_blocks(probe, self._maxk))
771
+ self._parts = torch.zeros(self._nblocks, dtype=torch.int64,
772
+ device=dev)
773
+ try:
774
+ self._bw_per_sm = float(ops.mak_bw_per_sm(probe))
775
+ except (AttributeError, RuntimeError):
776
+ self._bw_per_sm = 0.0
777
+ self._invf = invf[layers[0]["type"]]
778
+ self.Hkv, self.D, self.I = 1, maxD, maxI
779
+ self.theta = 0.0
780
+
781
+ emb_scale = float(torch.tensor(self.hidden ** 0.5,
782
+ dtype=torch.float32).to(dt))
783
+ ple_scale = float(torch.tensor(PL ** 0.5,
784
+ dtype=torch.float32).to(dt))
785
+ proj_scale = float(torch.tensor(self.hidden ** -0.5,
786
+ dtype=torch.float32))
787
+ hid = self._hidden.data_ptr()
788
+ tmp = self._tmp.data_ptr()
789
+ rows, names = [], []
790
+ rows.append(_row(_OP_EMBED, p1=ple_tab.data_ptr(),
791
+ p2=embed.data_ptr(), p3=hid,
792
+ p4=self._parts.data_ptr(),
793
+ p5=self._prompt_in.data_ptr(),
794
+ p6=self._token.data_ptr(),
795
+ n=self._tokens_out.data_ptr(), k=self.hidden,
796
+ it=self._pletok.data_ptr(),
797
+ hkv=self.L * PL, f0=emb_scale,
798
+ i0=_fbits(ple_scale)))
799
+ names.append("embed")
800
+ rows.append(_row(_OP_GEMV, p1=hid, p2=plm_w.data_ptr(),
801
+ p3=self._ctxraw.data_ptr(), n=self.L * PL,
802
+ k=self.hidden, it=_IT_NONE, epi=_EP_STORE))
803
+ names.append("plm")
804
+ rows.append(_row(8, p1=self._ctxraw.data_ptr(),
805
+ p2=pln_w.data_ptr(), p3=self._ple.data_ptr(),
806
+ p4=self._pletok.data_ptr(), n=self.L, k=PL,
807
+ f0=self.eps, i0=_fbits(proj_scale)))
808
+ names.append("plemix")
809
+
810
+ for li, e in enumerate(layers):
811
+ D_l, qdim = e["D"], self.Hq * e["D"]
812
+ lt = e["type"]
813
+ w_l = win if lt == "sliding_attention" else 0
814
+ asb = _fbits(ascale[lt]) if ascale[lt] != 1.0 else 0
815
+ flags = 1 | 2 | (0 if e["shared"] else 4) | \
816
+ (8 if e["shared"] else 0)
817
+ n_qkv = qdim if e["shared"] else qdim + 2 * D_l
818
+ rows.append(_row(_OP_GEMV, p1=hid, p2=e["wqkv"].data_ptr(),
819
+ p3=self._qkv.data_ptr(),
820
+ p4=e["ln_in"].data_ptr(), n=n_qkv,
821
+ k=self.hidden, it=_IT_RMSNORM_G,
822
+ epi=_EP_STORE, f0=self.eps))
823
+ names.append(f"L{li}.qkv")
824
+ rows.append(_row(_OP_ATTN, p1=self._qkv.data_ptr(),
825
+ p2=e["kc"].data_ptr(), p3=e["vc"].data_ptr(),
826
+ p4=self._partials.data_ptr(),
827
+ p5=e["qn"].data_ptr(),
828
+ p6=e["kn"].data_ptr() if e["kn"] is not None
829
+ else 0,
830
+ n=self.max_seq,
831
+ k=flags | (w_l << 16), it=_fbits(self.eps),
832
+ epi=invf[lt].data_ptr(), hq=self.Hq,
833
+ hkv=1 | (self._chunk << 16), d=D_l, f0=1.0,
834
+ i0=self._maxch | (asb << 32)))
835
+ names.append(f"L{li}.attn")
836
+ rows.append(_row(_OP_GEMV, p1=self._partials.data_ptr(),
837
+ p2=e["o"].data_ptr(), p3=tmp,
838
+ n=self.hidden, k=qdim, it=_IT_ATTNFIN,
839
+ epi=_EP_STORE, hq=self.Hq,
840
+ hkv=self._chunk << 16, d=D_l,
841
+ i0=self._maxch | (w_l << 16)))
842
+ names.append(f"L{li}.o")
843
+ rows.append(_row(7, p1=tmp, p2=e["ln_pa"].data_ptr(), p3=hid,
844
+ n=self.hidden, f0=self.eps, i0=_fbits(1.0)))
845
+ names.append(f"L{li}.nr_attn")
846
+ rows.append(_row(_OP_GEMV, p1=hid, p2=e["wgu"].data_ptr(),
847
+ p3=self._gu.data_ptr(),
848
+ p4=e["ln_pf"].data_ptr(), n=2 * e["I"],
849
+ k=self.hidden, it=_IT_RMSNORM_G,
850
+ epi=_EP_STORE, f0=self.eps))
851
+ names.append(f"L{li}.gateup")
852
+ rows.append(_row(_OP_GEMV, p1=self._gu.data_ptr(),
853
+ p2=e["wdn"].data_ptr(), p3=tmp,
854
+ n=self.hidden, k=e["I"], it=_IT_GELU_GLU,
855
+ epi=_EP_STORE))
856
+ names.append(f"L{li}.down")
857
+ rows.append(_row(7, p1=tmp, p2=e["ln_ff"].data_ptr(), p3=hid,
858
+ n=self.hidden, f0=self.eps, i0=_fbits(1.0)))
859
+ names.append(f"L{li}.nr_ffw")
860
+ rows.append(_row(_OP_GEMV, p1=hid, p2=e["plig"].data_ptr(),
861
+ p3=self._t256.data_ptr(),
862
+ p6=self._ple.data_ptr() + li * PL * 2,
863
+ n=PL, k=self.hidden, it=_IT_NONE,
864
+ epi=_EP_GELU_PLE))
865
+ names.append(f"L{li}.plig")
866
+ rows.append(_row(_OP_GEMV, p1=self._t256.data_ptr(),
867
+ p2=e["plpr"].data_ptr(), p3=tmp,
868
+ n=self.hidden, k=PL, it=_IT_NONE,
869
+ epi=_EP_STORE))
870
+ names.append(f"L{li}.plproj")
871
+ rows.append(_row(7, p1=tmp, p2=e["ln_pl"].data_ptr(), p3=hid,
872
+ n=self.hidden, f0=self.eps,
873
+ i0=_fbits(e["lscale"])))
874
+ names.append(f"L{li}.nr_ple")
875
+
876
+ rows.append(_row(_OP_GEMV, p1=hid, p2=embed.data_ptr(),
877
+ p3=self._logits.data_ptr(),
878
+ p4=fnorm.data_ptr(),
879
+ p6=self._parts.data_ptr(), n=self.V,
880
+ k=self.hidden, it=_IT_RMSNORM_G,
881
+ epi=_EP_F32_AMAX_CAP, hkv=1, f0=self.eps,
882
+ i0=_fbits(cap)))
883
+ names.append("lm_head")
884
+ rows.append(_row(_OP_ARGMAX_FIN, p1=self._parts.data_ptr(),
885
+ p3=self._token.data_ptr(),
886
+ p4=self._tokens_out.data_ptr()))
887
+ names.append("argmax.fin")
888
+ self._prog = torch.tensor(rows, dtype=torch.int64, device=dev)
889
+ self.phase_names = names
890
+ self._gemma = True
891
+ self._batch = 0
892
+ return self
893
+
894
+ # ------------------------------------------------------------------
895
+ def decode_step(self, token: int, pos: int, phased: bool = False):
896
+ """One decode step; returns the live fp32 logits buffer [V]."""
897
+ if pos >= self.max_seq:
898
+ raise ValueError("pos exceeds max_seq")
899
+ self._token.fill_(int(token))
900
+ if phased:
901
+ ops.mak_run_phased(self._prog, pos, self.max_gen - 1, False,
902
+ self._maxk)
903
+ else:
904
+ ops.mak_run(self._prog, self._bar, pos, self.max_gen - 1,
905
+ self._maxk)
906
+ return self._logits[:self.V]
907
+
908
+ def decode_step_timed(self, token: int, pos: int):
909
+ """One phase-per-launch step; returns (logits, per-phase ms)."""
910
+ self._token.fill_(int(token))
911
+ ms = ops.mak_run_phased(self._prog, pos, self.max_gen - 1, True,
912
+ self._maxk)
913
+ return self._logits[:self.V], ms
914
+
915
+ def prefill(self, ids: Sequence[int], phased: bool = False):
916
+ """Consume a prompt; returns the last position's logits. Runs in
917
+ chunks of up to chunk_m tokens with weight reads amortized across
918
+ the chunk (bitwise identical to token-by-token consumption)."""
919
+ P = len(ids)
920
+ if phased:
921
+ for i, t in enumerate(ids):
922
+ self.decode_step(int(t), i, phased=True)
923
+ return self._logits
924
+ self._prompt_in[:P].copy_(torch.tensor(ids, dtype=torch.int32))
925
+ ops.mak_run_seq(self._prog, self._bar, 0, P, 1 - P, P, self._maxk,
926
+ self._chunk_m)
927
+ return self._logits[:self.V]
928
+
929
+ def generate(self, prompt_ids: Sequence[int], max_new: int,
930
+ single_launch: bool = True) -> List[int]:
931
+ """Greedy generation. With single_launch (default) the entire call,
932
+ prompt consumption included, is one kernel launch: the in-kernel step
933
+ loop reads prompt tokens from a staged device buffer, then feeds each
934
+ step's argmax to the next step's embedding read. With
935
+ single_launch=False the same computation runs as one launch per
936
+ token; the two paths produce identical tokens.
937
+ """
938
+ P = len(prompt_ids)
939
+ if max_new < 1 or max_new > self.max_gen:
940
+ raise ValueError("max_new out of range")
941
+ if P < 1 or P + max_new > self.max_seq:
942
+ raise ValueError("sequence exceeds max_seq")
943
+ if single_launch:
944
+ self._prompt_in[:P].copy_(
945
+ torch.tensor(prompt_ids, dtype=torch.int32))
946
+ ops.mak_run_seq(self._prog, self._bar, 0, P + max_new - 1,
947
+ 1 - P, P, self._maxk, self._chunk_m)
948
+ return self._tokens_out[:max_new].cpu().tolist()
949
+ pos = 0
950
+ for t in prompt_ids[:-1]:
951
+ self._token.fill_(int(t))
952
+ ops.mak_run(self._prog, self._bar, pos, self.max_gen - 1,
953
+ self._maxk)
954
+ pos += 1
955
+ self._token.fill_(int(prompt_ids[-1]))
956
+ ops.mak_run(self._prog, self._bar, pos, 0, self._maxk)
957
+ pos += 1
958
+ if max_new > 1:
959
+ ops.mak_run_steps(self._prog, self._bar, pos, max_new - 1, 1,
960
+ self._maxk)
961
+ return self._tokens_out[:max_new].cpu().tolist()
962
+
963
+
964
+
build/torch211-cxx11-cu128-x86_64-linux/_model_as_a_kernel_cuda_e33875a.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:bc112475d41da2f89920294225377bf527635de3ba393a1a6e9f4106c0e34ebb
3
+ size 6395056
build/torch211-cxx11-cu128-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _model_as_a_kernel_cuda_e33875a
3
+ ops = torch.ops._model_as_a_kernel_cuda_e33875a
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_model_as_a_kernel_cuda_e33875a::{op_name}"
build/torch211-cxx11-cu128-x86_64-linux/metadata.json ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "model-as-a-kernel",
3
+ "id": "_model_as_a_kernel_cuda_e33875a",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "python-depends": [],
7
+ "backend": {
8
+ "type": "cuda",
9
+ "archs": [
10
+ "10.0",
11
+ "12.0",
12
+ "8.0",
13
+ "8.6",
14
+ "8.9",
15
+ "9.0"
16
+ ]
17
+ },
18
+ "digest": {
19
+ "algorithm": "sha256",
20
+ "files": {
21
+ "__init__.py": "/YX5/zzAOyJacQ7H+j8zaq+xjWyy8w6r0j+ugE1EUs8=",
22
+ "_model_as_a_kernel_cuda_e33875a.abi3.so": "vBEkddQdoviZIClCJTd79SdjXeO6OToabp9BBsDjTrs=",
23
+ "_ops.py": "Pqj4WKtwPyeDG9grQnzLget4g1jsGUdR7ZMa44e4rhs=",
24
+ "model_as_a_kernel/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
25
+ }
26
+ },
27
+ "provenance": {
28
+ "kernel-builder": {
29
+ "version": "0.17.0-dev0",
30
+ "sha": "05f1ed487f599a320311aa26076ec0ad290c3f68",
31
+ "dirty": false
32
+ },
33
+ "kernel": {
34
+ "sha": "e33875ae4eba42fd136a89c388e806281cd4759c",
35
+ "dirty": false
36
+ }
37
+ }
38
+ }
build/torch211-cxx11-cu128-x86_64-linux/model_as_a_kernel/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import importlib.util
3
+ import sys
4
+ from pathlib import Path
5
+ from types import ModuleType
6
+
7
+
8
+ def _import_from_path(file_path: Path) -> ModuleType:
9
+ # We cannot use the module name as-is, after adding it to `sys.modules`,
10
+ # it would also be used for other imports. So, we make a module name that
11
+ # depends on the path for it to be unique using the hex-encoded hash of
12
+ # the path.
13
+ path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
+ module_name = path_hash
15
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
16
+ if spec is None:
17
+ raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
+ module = importlib.util.module_from_spec(spec)
19
+ if module is None:
20
+ raise ImportError(f"Cannot load module {module_name} from spec")
21
+ sys.modules[module_name] = module
22
+ spec.loader.exec_module(module) # type: ignore
23
+ return module
24
+
25
+
26
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
build/torch211-cxx11-cu130-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,964 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """model-as-a-kernel: an entire llama-family decode step as one kernel launch.
2
+
3
+ A phase interpreter runs the whole forward pass (embedding, every layer's
4
+ norms, QKV, rope, attention over the KV cache, SwiGLU MLP, the LM head, and
5
+ the greedy argmax) inside a single persistent kernel, with a software grid
6
+ barrier between phases. The in-kernel step loop makes an entire greedy
7
+ generation, prompt consumption included, one kernel launch: each step's
8
+ argmax feeds the next step's embedding read without leaving the device.
9
+ """
10
+ import os
11
+ import struct
12
+ from typing import List, Optional, Sequence
13
+
14
+ import torch
15
+
16
+ try:
17
+ from ._ops import ops
18
+ except ImportError: # local development build (torch.utils.cpp_extension)
19
+ class _LocalOps:
20
+ def __getattr__(self, name):
21
+ return getattr(torch.ops.mak_ext, name)
22
+
23
+ ops = _LocalOps()
24
+
25
+ __all__ = ["MegaModel"]
26
+
27
+ _OP_EMBED, _OP_GEMV, _OP_QKV_POST, _OP_ATTN = 0, 1, 2, 3
28
+ _OP_ARGMAX_PART, _OP_ARGMAX_FIN = 4, 5
29
+ _OP_KV_APPEND, _OP_NORMRES, _OP_PLEMIX = 6, 7, 8
30
+ _OP_GEMV_PLAIN, _OP_NORMB, _OP_GLUB, _OP_ATTNFINB = 9, 10, 11, 12
31
+ _OP_GEMV_Q4 = 13
32
+ _IT_NONE, _IT_RMSNORM, _IT_SWIGLU, _IT_ATTNFIN = 0, 1, 2, 3
33
+ _IT_RMSNORM_G, _IT_GELU_GLU = 4, 5
34
+ _EP_GELU_PLE, _EP_F32_AMAX_CAP = 5, 6
35
+ _ITF_REDUCE, _ITF_WRITEBACK = 8, 16
36
+ _EP_STORE, _EP_RESID, _EP_F32, _EP_F32_AMAX, _EP_PARTIAL = 0, 1, 2, 3, 4
37
+ _NSLICE = 4
38
+ # Widest [B][K] input panel (bf16 elements) a batched projection stages in
39
+ # shared before it spills to the global-scratch path. 16384 (32 KB) keeps
40
+ # two blocks resident per SM; the persistent kernel shares one occupancy
41
+ # across all phases, so a wider panel would drop every phase to one block
42
+ # per SM, which measures slower than reading the input from L2.
43
+ _STAGE_BUDGET = 16384
44
+
45
+
46
+ def _fbits(f: float) -> int:
47
+ return struct.unpack("<i", struct.pack("<f", float(f)))[0]
48
+
49
+
50
+ def _row(op, p1=0, p2=0, p3=0, p4=0, p5=0, p6=0, n=0, k=0, it=0, epi=0,
51
+ hq=0, hkv=0, d=0, f0=0.0, i0=0):
52
+ return [int(op), int(p1), int(p2), int(p3), int(p4), int(p5), int(p6),
53
+ int(n), int(k), int(it), int(epi), int(hq), int(hkv), int(d),
54
+ _fbits(f0), int(i0)]
55
+
56
+
57
+ class MegaModel:
58
+ """A packed llama-family decoder whose decode step is one kernel launch.
59
+
60
+ Supported architecture: RMSNorm decoder blocks with rotary attention
61
+ (GQA, optional Qwen3-style per-head qk RMSNorm), SwiGLU MLP, no attention
62
+ or MLP biases, plain (unscaled) rope, batch size 1. This covers SmolLM2,
63
+ TinyLlama, Qwen3 dense, Llama-class checkpoints without rope scaling,
64
+ and similar models.
65
+ """
66
+
67
+ def __init__(self, weights: dict, config: dict, device="cuda",
68
+ max_seq: int = 4096, max_gen: int = 4096):
69
+ cfg = dict(config)
70
+ self.L = int(cfg["num_hidden_layers"])
71
+ self.hidden = int(cfg["hidden_size"])
72
+ self.Hq = int(cfg["num_attention_heads"])
73
+ self.Hkv = int(cfg.get("num_key_value_heads") or self.Hq)
74
+ self.D = int(cfg.get("head_dim") or self.hidden // self.Hq)
75
+ self.I = int(cfg["intermediate_size"])
76
+ self.V = int(cfg["vocab_size"])
77
+ self.theta = float(cfg.get("rope_theta", 10000.0))
78
+ self.eps = float(cfg.get("rms_norm_eps", 1e-6))
79
+ self.qk_norm = bool(cfg.get("qk_norm", False))
80
+ self.max_seq = int(max_seq)
81
+ self.max_gen = int(max_gen)
82
+ self.device = torch.device(device)
83
+
84
+ if self.D % 2 or self.D > 256:
85
+ raise ValueError("head_dim must be even and <= 256")
86
+ if self.hidden % 8 or self.I % 8 or (self.Hq * self.D) % 8:
87
+ raise ValueError(
88
+ "hidden, intermediate, and Hq*head_dim must be multiples of 8")
89
+ if self.Hq % self.Hkv:
90
+ raise ValueError("num_attention_heads must be divisible by num_key_value_heads")
91
+
92
+ dt = torch.bfloat16
93
+ dev = self.device
94
+
95
+ def pack(t):
96
+ t = t.detach().to(device=dev, dtype=dt).contiguous()
97
+ self._keep.append(t)
98
+ return t
99
+
100
+ # Row-tile-of-8 weight layout [N/8][K/8][8][8] (MAK_TILED=1): the
101
+ # eight warps of a block read one contiguous 4KB stream per step
102
+ # instead of eight 4KB-strided pieces. Measured slower on every
103
+ # card (H200 +15 percent, RTX PRO +9, Ada +14 — per-warp stream
104
+ # locality beats per-block contiguity on all of them), so the
105
+ # default is the plain layout; the flag rides in the program and
106
+ # both layouts compute identical bits.
107
+ env_t = os.environ.get("MAK_TILED", "").strip()
108
+ self.tiled = env_t == "1"
109
+
110
+ def pack_tiled(t):
111
+ t = t.detach().to(device=dev, dtype=dt).contiguous()
112
+ if not self.tiled:
113
+ self._keep.append(t)
114
+ return t
115
+ n, k = t.shape
116
+ assert k % 8 == 0
117
+ n8 = (n + 7) // 8 * 8
118
+ if n8 != n:
119
+ t = torch.cat(
120
+ [t, torch.zeros(n8 - n, k, dtype=dt, device=dev)], 0)
121
+ t = t.view(n8 // 8, 8, k // 8, 8).permute(0, 2, 1, 3).contiguous()
122
+ self._keep.append(t)
123
+ return t
124
+
125
+ self._keep: List[torch.Tensor] = []
126
+ embed = pack(weights["embed"])
127
+ final_norm = pack(weights["norm"])
128
+ lm_head = weights["lm_head"] # packed below (dense-tiled or nf4)
129
+ inv_freq = weights.get("inv_freq")
130
+ if inv_freq is None:
131
+ d_idx = torch.arange(0, self.D, 2, dtype=torch.float32)
132
+ inv_freq = 1.0 / (self.theta ** (d_idx / self.D))
133
+ inv_freq = inv_freq.detach().to(device=dev,
134
+ dtype=torch.float32).contiguous()
135
+ if inv_freq.numel() != self.D // 2:
136
+ raise ValueError("inv_freq length must be head_dim / 2")
137
+ self._keep.append(inv_freq)
138
+ self._invf = inv_freq
139
+
140
+ qdim, kvdim = self.Hq * self.D, self.Hkv * self.D
141
+
142
+ def _is_q4(w):
143
+ return isinstance(w, dict) and w.get("q4")
144
+
145
+ def pack_w(w):
146
+ # nf4-packed weights move to the device intact; dense weights take
147
+ # the usual (optionally row-tiled) bf16 packing.
148
+ if _is_q4(w):
149
+ pk = w["packed"].to(dev).contiguous()
150
+ am = w["absmax"].to(device=dev, dtype=torch.float32).contiguous()
151
+ self._keep.append(pk)
152
+ self._keep.append(am)
153
+ return {"q4": True, "packed": pk, "absmax": am,
154
+ "N": int(w["N"]), "K": int(w["K"])}
155
+ return pack_tiled(w)
156
+
157
+ def _wshape(w, expected):
158
+ got = (int(w["N"]), int(w["K"])) if _is_q4(w) else tuple(w.shape)
159
+ assert got == expected, (got, expected)
160
+
161
+ self._has_q4 = False
162
+ self._layers = []
163
+ for lw in weights["layers"]:
164
+ _wshape(lw["wqkv"], (qdim + 2 * kvdim, self.hidden))
165
+ _wshape(lw["wo"], (self.hidden, qdim))
166
+ _wshape(lw["wgu"], (2 * self.I, self.hidden))
167
+ _wshape(lw["wdown"], (self.hidden, self.I))
168
+ layer = {
169
+ "wqkv": pack_w(lw["wqkv"]), "wo": pack_w(lw["wo"]),
170
+ "wgu": pack_w(lw["wgu"]), "wdown": pack_w(lw["wdown"]),
171
+ "ln1": pack(lw["ln1"]), "ln2": pack(lw["ln2"]),
172
+ "qn": pack(lw["qn"]) if self.qk_norm else None,
173
+ "kn": pack(lw["kn"]) if self.qk_norm else None,
174
+ }
175
+ self._has_q4 |= any(_is_q4(layer[n]) for n in
176
+ ("wqkv", "wo", "wgu", "wdown"))
177
+ self._layers.append(layer)
178
+ assert len(self._layers) == self.L
179
+ assert embed.shape == (self.V, self.hidden)
180
+ lm_head = pack_w(lm_head)
181
+ self._has_q4 |= _is_q4(lm_head)
182
+ if not _is_q4(lm_head):
183
+ assert lm_head.numel() >= self.V * self.hidden # row-tiled, padded
184
+ self._w_embed, self._w_fnorm, self._w_lmhead = (embed, final_norm,
185
+ lm_head)
186
+ self._qdim, self._kvdim = qdim, kvdim
187
+
188
+ # Working buffers. Pointers to these are baked into the program, so
189
+ # they (like the packed weights) live for the model's lifetime.
190
+ self._maxk = max(self.hidden, qdim, self.I)
191
+ # prefill chunk size, bounded by the staging shared-memory budget
192
+ # (16K bf16 elements; the cp.async weight ring rides alongside)
193
+ self._chunk_m = max(1, min(8, 16384 // self._maxk))
194
+ cm = 8 # buffers sized for the maximum chunk
195
+ self._hidden = torch.empty(cm * self.hidden, dtype=dt, device=dev)
196
+ self._qkv = torch.empty(cm * (qdim + 2 * kvdim), dtype=dt, device=dev)
197
+ self._gu = torch.empty(cm * 2 * self.I, dtype=dt, device=dev)
198
+ self._logits = torch.empty(self.V, dtype=torch.float32, device=dev)
199
+ # attention chunk length (kernel maximum 128); shorter chunks give
200
+ # the attention phase more grid parallelism at the cost of more
201
+ # softmax partials to finalize
202
+ env_ch = os.environ.get("MAK_CHUNK", "").strip()
203
+ self._chunk = int(env_ch) if env_ch in ("32", "64", "128") else 128
204
+ self._maxch = (self.max_seq + self._chunk - 1) // self._chunk
205
+ self._partials = torch.empty(
206
+ cm * self.Hq * self._maxch * (self.D + 2), dtype=torch.float32,
207
+ device=dev)
208
+ self._kcache = torch.zeros(self.L, self.Hkv, self.max_seq, self.D,
209
+ dtype=dt, device=dev)
210
+ self._vcache = torch.zeros(self.L, self.Hkv, self.max_seq, self.D,
211
+ dtype=dt, device=dev)
212
+ self._token = torch.zeros(1, dtype=torch.int32, device=dev)
213
+ self._prompt_in = torch.zeros(self.max_seq, dtype=torch.int32,
214
+ device=dev)
215
+ self._tokens_out = torch.zeros(self.max_gen, dtype=torch.int32,
216
+ device=dev)
217
+ self._bar = torch.zeros(34, dtype=torch.int32, device=dev)
218
+
219
+ probe = torch.zeros(1, 16, dtype=torch.int64, device=dev)
220
+ self._nblocks = int(ops.mak_num_blocks(probe, self._maxk))
221
+ self._parts = torch.zeros(self._nblocks, dtype=torch.int64, device=dev)
222
+ try:
223
+ self._bw_per_sm = float(ops.mak_bw_per_sm(probe))
224
+ except (AttributeError, RuntimeError):
225
+ self._bw_per_sm = 0.0
226
+
227
+ self._batch = 0
228
+ self._pos_b = None
229
+ self._kv_bstride = 0
230
+ self._gemma = False
231
+ # transformed-input scratch: the nf4 and wide-batch paths write the
232
+ # dense bf16 input here (one row per active token) for the following
233
+ # plain/quant GEMV; sized for a prefill chunk (up to 8 rows)
234
+ self._xg = torch.empty(cm * self._maxk, dtype=dt, device=dev)
235
+ self._build_programs()
236
+
237
+ # ------------------------------------------------------------------
238
+ def _kvp(self, cache, li: int, b: int) -> int:
239
+ t = cache[li]
240
+ return (t[b] if t.dim() == 4 else t).data_ptr()
241
+
242
+ def _build_rows(self, kv_slice: int = 0, batch: bool = False,
243
+ big: bool = False):
244
+ """Program rows for the llama-family path. kv_slice selects the
245
+ batch slice the KV pointers address (per-sequence prefill);
246
+ batch=True emits the batched-decode tail (per-row fp32 logits and
247
+ explicit argmax phases) instead of the fused LM-head amax. In batch
248
+ mode each projection stays fused (input staged in shared) when its
249
+ [B][K] panel fits the budget; the projections whose K is too wide
250
+ transform into a global scratch and read it with a plain GEMV."""
251
+ _tbit = (1 << 20) if self.tiled else 0 # GEMV weight layout flag
252
+ qdim, kvdim = self._qdim, self._kvdim
253
+ embed, lm_head = self._w_embed, self._w_lmhead
254
+ final_norm = self._w_fnorm
255
+ xg = self._xg.data_ptr()
256
+ B = self._batch if batch else 1
257
+ rows, names = [], []
258
+ staged_elems = [0] # widest [B][K] panel any fused projection stages
259
+
260
+ def fits(K):
261
+ return B * K <= _STAGE_BUDGET
262
+
263
+ def note(K):
264
+ staged_elems[0] = max(staged_elems[0], B * K)
265
+
266
+ def _q4(w):
267
+ return isinstance(w, dict) and w.get("q4")
268
+
269
+ def _wgemv(out, w, N, K, epi, resid):
270
+ # the GEMV after a transform-to-scratch phase: nf4-dequant when
271
+ # the weight is packed, otherwise a plain bf16 GEMV
272
+ if _q4(w):
273
+ rows.append(_row(_OP_GEMV_Q4, p1=xg, p2=w["packed"].data_ptr(),
274
+ p3=out, p5=resid, p6=w["absmax"].data_ptr(),
275
+ n=N, k=K, epi=epi))
276
+ else:
277
+ rows.append(_row(_OP_GEMV_PLAIN, p1=xg, p2=w.data_ptr(),
278
+ p3=out, p5=resid, n=N, k=K, it=_tbit,
279
+ epi=epi))
280
+
281
+ def norm_gemv(name, gamma, w, out, N, K, epi, inp, resid=0,
282
+ variant=_IT_RMSNORM):
283
+ if _q4(w) or (big and not fits(K)):
284
+ rows.append(_row(_OP_NORMB, p1=inp, p2=gamma, p3=xg, k=K,
285
+ it=variant, f0=self.eps))
286
+ names.append(name + ".norm")
287
+ _wgemv(out, w, N, K, epi, resid)
288
+ names.append(name)
289
+ else:
290
+ note(K)
291
+ rows.append(_row(_OP_GEMV, p1=inp, p2=w.data_ptr(), p3=out,
292
+ p4=gamma, p5=resid, n=N, k=K,
293
+ it=variant | _tbit, epi=epi, f0=self.eps))
294
+ names.append(name)
295
+
296
+ def glu_gemv(name, gu, w, out, resid, variant=_IT_SWIGLU):
297
+ if _q4(w) or (big and not fits(self.I)):
298
+ rows.append(_row(_OP_GLUB, p1=gu, p3=xg, k=self.I, it=variant))
299
+ names.append(name + ".glu")
300
+ _wgemv(out, w, self.hidden, self.I, _EP_RESID, resid)
301
+ names.append(name)
302
+ else:
303
+ note(self.I)
304
+ rows.append(_row(_OP_GEMV, p1=gu, p2=w.data_ptr(), p3=out,
305
+ p5=resid, n=self.hidden, k=self.I,
306
+ it=variant | _tbit, epi=_EP_RESID))
307
+ names.append(name)
308
+
309
+ def attn_gemv(name, w, out, resid):
310
+ if _q4(w) or (big and not fits(qdim)):
311
+ rows.append(_row(_OP_ATTNFINB, p1=self._partials.data_ptr(),
312
+ p3=xg, k=qdim, hq=self.Hq,
313
+ hkv=self._chunk << 16, d=self.D,
314
+ i0=self._maxch))
315
+ names.append(name + ".fin")
316
+ _wgemv(out, w, self.hidden, qdim, _EP_RESID, resid)
317
+ names.append(name)
318
+ else:
319
+ note(qdim)
320
+ rows.append(_row(_OP_GEMV, p1=self._partials.data_ptr(),
321
+ p2=w.data_ptr(), p3=out, p5=resid,
322
+ n=self.hidden, k=qdim,
323
+ it=_IT_ATTNFIN | _tbit, epi=_EP_RESID,
324
+ hq=self.Hq, hkv=self._chunk << 16, d=self.D,
325
+ i0=self._maxch))
326
+ names.append(name)
327
+
328
+ rows.append(_row(_OP_EMBED, p2=embed.data_ptr(),
329
+ p3=self._hidden.data_ptr(),
330
+ p4=self._parts.data_ptr(),
331
+ p5=self._prompt_in.data_ptr(),
332
+ p6=self._token.data_ptr(),
333
+ n=self._tokens_out.data_ptr(), k=self.hidden))
334
+ names.append("embed")
335
+ scale = 1.0 / (self.D ** 0.5)
336
+ hid = self._hidden.data_ptr()
337
+ for li, lw in enumerate(self._layers):
338
+ kc = self._kvp(self._kcache, li, kv_slice)
339
+ vc = self._kvp(self._vcache, li, kv_slice)
340
+ norm_gemv(f"L{li}.qkv", lw["ln1"].data_ptr(),
341
+ lw["wqkv"], self._qkv.data_ptr(),
342
+ qdim + 2 * kvdim, self.hidden, _EP_STORE, hid)
343
+ rows.append(_row(6, p1=self._qkv.data_ptr(), p2=kc, p3=vc,
344
+ p6=lw["kn"].data_ptr() if self.qk_norm else 0,
345
+ n=self.max_seq, k=1 if self.qk_norm else 0,
346
+ it=_fbits(self.eps),
347
+ epi=self._invf.data_ptr(),
348
+ hq=self.Hq, hkv=self.Hkv, d=self.D))
349
+ names.append(f"L{li}.kvappend")
350
+ rows.append(_row(_OP_ATTN, p1=self._qkv.data_ptr(), p2=kc,
351
+ p3=vc, p4=self._partials.data_ptr(),
352
+ p5=lw["qn"].data_ptr() if self.qk_norm else 0,
353
+ p6=lw["kn"].data_ptr() if self.qk_norm else 0,
354
+ n=self.max_seq, k=1 if self.qk_norm else 0,
355
+ it=_fbits(self.eps),
356
+ epi=self._invf.data_ptr(),
357
+ hq=self.Hq,
358
+ hkv=self.Hkv | (self._chunk << 16),
359
+ d=self.D, f0=scale, i0=self._maxch))
360
+ names.append(f"L{li}.attn")
361
+ attn_gemv(f"L{li}.o", lw["wo"], hid, hid)
362
+ norm_gemv(f"L{li}.gateup", lw["ln2"].data_ptr(),
363
+ lw["wgu"], self._gu.data_ptr(), 2 * self.I,
364
+ self.hidden, _EP_STORE, hid)
365
+ glu_gemv(f"L{li}.down", self._gu.data_ptr(), lw["wdown"], hid, hid)
366
+ if batch or _q4(lm_head):
367
+ norm_gemv("lm_head", final_norm.data_ptr(), lm_head,
368
+ self._logits.data_ptr(), self.V, self.hidden, _EP_F32,
369
+ hid)
370
+ rows.append(_row(_OP_ARGMAX_PART, p1=self._logits.data_ptr(),
371
+ p3=self._parts.data_ptr(), n=self.V))
372
+ names.append("argmax.part")
373
+ rows.append(_row(_OP_ARGMAX_FIN, p1=self._parts.data_ptr(),
374
+ p3=self._token.data_ptr(),
375
+ p4=self._tokens_out.data_ptr(),
376
+ k=self.max_gen))
377
+ names.append("argmax.fin")
378
+ else:
379
+ rows.append(_row(_OP_GEMV, p1=hid, p2=lm_head.data_ptr(),
380
+ p3=self._logits.data_ptr(),
381
+ p4=final_norm.data_ptr(),
382
+ p6=self._parts.data_ptr(), n=self.V,
383
+ k=self.hidden, it=_IT_RMSNORM | _tbit,
384
+ epi=_EP_F32_AMAX, hkv=1, f0=self.eps))
385
+ names.append("lm_head")
386
+ rows.append(_row(_OP_ARGMAX_FIN, p1=self._parts.data_ptr(),
387
+ p3=self._token.data_ptr(),
388
+ p4=self._tokens_out.data_ptr()))
389
+ names.append("argmax.fin")
390
+ se = max(staged_elems[0], self._maxk)
391
+ return rows, names, se
392
+
393
+ def _build_programs(self):
394
+ dev = self.device
395
+ rows, names, _ = self._build_rows(kv_slice=0, batch=False)
396
+ self._prog = torch.tensor(rows, dtype=torch.int64, device=dev)
397
+ self.phase_names = names
398
+ if self._batch:
399
+ big = self._batch > min(8, 16384 // self._maxk)
400
+ rows, _, se = self._build_rows(kv_slice=0, batch=True, big=big)
401
+ self._prog_batch = torch.tensor(rows, dtype=torch.int64,
402
+ device=dev)
403
+ self._batch_stage_elems = int(se)
404
+ self._progs_prefill = [self._prog]
405
+ for b in range(1, self._batch):
406
+ rows, _, _ = self._build_rows(kv_slice=b, batch=False)
407
+ self._progs_prefill.append(
408
+ torch.tensor(rows, dtype=torch.int64, device=dev))
409
+
410
+ def batch_max(self) -> int:
411
+ """Largest batch size this model supports. Up to 8 sequences stage
412
+ in shared; beyond that the transformed input moves to a global
413
+ scratch, bounded by the kernel's register accumulators."""
414
+ try:
415
+ hard = int(ops.mak_batch_maxb())
416
+ except (AttributeError, RuntimeError):
417
+ hard = 8
418
+ return 1 if self._gemma else hard
419
+
420
+ # ------------------------------------------------------------------
421
+ def enable_batch(self, B: int):
422
+ """Allocate per-sequence KV caches, token buffers, and programs for
423
+ batched decode. B up to 8 stages the input in shared; larger B (up
424
+ to batch_max()) routes the transformed input through a global
425
+ scratch so the batch width is not bounded by the projection size."""
426
+ if self._gemma:
427
+ raise ValueError("batched decode covers the llama-family path")
428
+ maxb = self.batch_max()
429
+ if not 1 <= B <= maxb:
430
+ raise ValueError(f"B must be in [1, {maxb}] for this model")
431
+ if self._batch == B:
432
+ return
433
+ dt, dev = torch.bfloat16, self.device
434
+ self._kcache = torch.zeros(self.L, B, self.Hkv, self.max_seq,
435
+ self.D, dtype=dt, device=dev)
436
+ self._vcache = torch.zeros_like(self._kcache)
437
+ self._token = torch.zeros(B, dtype=torch.int32, device=dev)
438
+ self._tokens_out = torch.zeros(B * self.max_gen, dtype=torch.int32,
439
+ device=dev)
440
+ self._logits = torch.empty(B * self.V, dtype=torch.float32,
441
+ device=dev)
442
+ self._parts = torch.zeros(B * self._nblocks, dtype=torch.int64,
443
+ device=dev)
444
+ self._pos_b = torch.zeros(B, dtype=torch.int32, device=dev)
445
+ # transformed-input scratch: one row per active token, and prefill
446
+ # (during generate_batch) still uses up to chunk_m rows
447
+ self._xg = torch.empty(max(8, B) * self._maxk, dtype=dt, device=dev)
448
+ # working buffers hold one row per active sequence; prefill chunks
449
+ # still use up to chunk_m rows, so keep at least the fused capacity
450
+ nb = max(8, B)
451
+ qkvd = self._qdim + 2 * self._kvdim
452
+ self._hidden = torch.empty(nb * self.hidden, dtype=dt, device=dev)
453
+ self._qkv = torch.empty(nb * qkvd, dtype=dt, device=dev)
454
+ self._gu = torch.empty(nb * 2 * self.I, dtype=dt, device=dev)
455
+ self._partials = torch.empty(
456
+ nb * self.Hq * self._maxch * (self.D + 2), dtype=torch.float32,
457
+ device=dev)
458
+ self._kv_bstride = self.Hkv * self.max_seq * self.D
459
+ self._batch = B
460
+ self._build_programs()
461
+
462
+ def decode_batch(self, tokens: Sequence[int], positions: Sequence[int],
463
+ steps: int = 1) -> torch.Tensor:
464
+ """Batched greedy decode: sequence b consumes tokens[b] at
465
+ positions[b]; `steps` in-kernel steps run with per-sequence argmax
466
+ feedback. Returns the generated tokens [B, steps]; the final
467
+ step's fp32 logits are live in batch_logits()."""
468
+ B = self._batch
469
+ if B < 1:
470
+ raise ValueError("call enable_batch(B) first")
471
+ if len(tokens) != B or len(positions) != B:
472
+ raise ValueError("tokens and positions must have B entries")
473
+ if steps < 1 or steps > self.max_gen:
474
+ raise ValueError("steps out of range")
475
+ if max(positions) + steps > self.max_seq:
476
+ raise ValueError("sequence exceeds max_seq")
477
+ self._token.copy_(torch.tensor(list(tokens), dtype=torch.int32))
478
+ self._pos_b.copy_(torch.tensor(list(positions), dtype=torch.int32))
479
+ ops.mak_run_batch(self._prog_batch, self._bar, self._pos_b, steps,
480
+ 0, self._maxk, self._kv_bstride,
481
+ self._batch_stage_elems)
482
+ return self._tokens_out.view(B, self.max_gen)[:, :steps]
483
+
484
+ def batch_logits(self) -> torch.Tensor:
485
+ """fp32 logits [B, V] of the most recent batched step."""
486
+ return self._logits.view(max(self._batch, 1), self.V)
487
+
488
+ def generate_batch(self, prompts: Sequence[Sequence[int]],
489
+ max_new: int) -> List[List[int]]:
490
+ """Greedy generation for a batch of prompts: per-sequence prefill,
491
+ then one batched decode launch. Each sequence's token stream is
492
+ bit-identical to its own single-sequence generate()."""
493
+ B = len(prompts)
494
+ if max_new < 1 or max_new > self.max_gen:
495
+ raise ValueError("max_new out of range")
496
+ self.enable_batch(B)
497
+ firsts: List[int] = []
498
+ for b, ids in enumerate(prompts):
499
+ P = len(ids)
500
+ if P < 1 or P + max_new > self.max_seq:
501
+ raise ValueError("sequence exceeds max_seq")
502
+ self._prompt_in[:P].copy_(
503
+ torch.tensor(list(ids), dtype=torch.int32))
504
+ ops.mak_run_seq(self._progs_prefill[b], self._bar, 0, P, 1 - P,
505
+ P, self._maxk, self._chunk_m)
506
+ firsts.append(int(self._token[0].item()))
507
+ if max_new == 1:
508
+ return [[t] for t in firsts]
509
+ self._token.copy_(torch.tensor(firsts, dtype=torch.int32))
510
+ self._pos_b.copy_(torch.tensor([len(p) for p in prompts],
511
+ dtype=torch.int32))
512
+ ops.mak_run_batch(self._prog_batch, self._bar, self._pos_b,
513
+ max_new - 1, 0, self._maxk, self._kv_bstride,
514
+ self._batch_stage_elems)
515
+ rest = (self._tokens_out.view(B, self.max_gen)[:, :max_new - 1]
516
+ .cpu().tolist())
517
+ return [[firsts[b]] + rest[b] for b in range(B)]
518
+
519
+ # ------------------------------------------------------------------
520
+ @staticmethod
521
+ def _dense_weight(mod):
522
+ """The effective bf16 weight of a linear layer, dequantizing packed
523
+ quantized layers (bitsandbytes 4-bit and 8-bit) so a quantized
524
+ checkpoint loads directly. Dequantization is exact and does not
525
+ depend on the card, so inference on the result keeps every
526
+ guarantee; the weights materialize to bf16, so the quantized memory
527
+ footprint is not preserved (that needs the in-kernel packed path)."""
528
+ w = mod.weight
529
+ qs = getattr(w, "quant_state", None)
530
+ if qs is not None: # bitsandbytes 4-bit (nf4 / fp4)
531
+ import bitsandbytes as bnb
532
+ return bnb.functional.dequantize_4bit(w.data, qs).to(torch.bfloat16)
533
+ if getattr(w, "SCB", None) is not None or hasattr(mod, "SCB"):
534
+ # bitsandbytes 8-bit (LLM.int8): row scales in SCB, int8 in CB
535
+ scb = w.SCB if getattr(w, "SCB", None) is not None else mod.SCB
536
+ cb = w.data if w.data.dtype == torch.int8 else mod.CB
537
+ return (cb.to(torch.float32)
538
+ * (scb.to(torch.float32) / 127.0).unsqueeze(1)
539
+ ).to(torch.bfloat16)
540
+ return w
541
+
542
+ @staticmethod
543
+ def _q4_parts(mod):
544
+ """(packed [N, K/2] uint8, absmax [N*K/64] fp32) for a bitsandbytes
545
+ nf4 layer whose weights stay packed, else None. Double-quantized
546
+ absmax is materialized to fp32 so the kernel needs one scale array."""
547
+ w = getattr(mod, "weight", None)
548
+ qs = getattr(w, "quant_state", None)
549
+ if qs is None or getattr(qs, "quant_type", None) != "nf4":
550
+ return None
551
+ import bitsandbytes as bnb
552
+ n, k = int(qs.shape[0]), int(qs.shape[1])
553
+ if k % 64:
554
+ return None # kernel assumes 64 | K for per-block absmax
555
+ packed = w.data.reshape(n, k // 2).contiguous()
556
+ if getattr(qs, "nested", False):
557
+ absmax = bnb.functional.dequantize_blockwise(
558
+ qs.absmax, qs.state2) + qs.offset
559
+ else:
560
+ absmax = qs.absmax
561
+ return packed, absmax.float().reshape(-1).contiguous()
562
+
563
+ @classmethod
564
+ def _proj(cls, mods):
565
+ """A projection, concatenated across `mods` (q/k/v or gate/up). Stays
566
+ nf4-packed when every part is nf4, else a dense bf16 tensor."""
567
+ parts = [cls._q4_parts(m) for m in mods]
568
+ if all(p is not None for p in parts):
569
+ packed = torch.cat([p[0] for p in parts], 0)
570
+ absmax = torch.cat([p[1] for p in parts], 0)
571
+ return {"q4": True, "packed": packed, "absmax": absmax,
572
+ "N": packed.shape[0], "K": packed.shape[1] * 2}
573
+ return torch.cat([cls._dense_weight(m) for m in mods], 0)
574
+
575
+ @classmethod
576
+ def from_pretrained(cls, model, device="cuda", max_seq: int = 4096,
577
+ max_gen: int = 4096):
578
+ """Build from a transformers model (object or repo id). A quantized
579
+ checkpoint (bitsandbytes 4-bit/8-bit) is accepted directly; its
580
+ weights are dequantized to bf16 at load."""
581
+ if isinstance(model, str):
582
+ from transformers import AutoModelForCausalLM
583
+ model = AutoModelForCausalLM.from_pretrained(
584
+ model, torch_dtype=torch.bfloat16)
585
+ if "gemma4" in getattr(model.config, "model_type", ""):
586
+ return cls._from_gemma4(model, device, max_seq, max_gen)
587
+ hf = model.config
588
+ dec = model.model
589
+ attn0 = dec.layers[0].self_attn
590
+ qk_norm = hasattr(attn0, "q_norm") and attn0.q_norm is not None
591
+ if getattr(attn0.q_proj, "bias", None) is not None:
592
+ raise ValueError("attention biases are not supported")
593
+
594
+ # rope config across transformers API generations: v5 keeps theta and
595
+ # type in a rope_parameters dict; v4 used a rope_theta attribute and
596
+ # an optional rope_scaling dict.
597
+ rp = getattr(hf, "rope_parameters", None)
598
+ if not isinstance(rp, dict):
599
+ rs = getattr(hf, "rope_scaling", None)
600
+ rp = rs if isinstance(rs, dict) else {}
601
+ theta = rp.get("rope_theta")
602
+ if theta is None:
603
+ theta = getattr(hf, "rope_theta", 10000.0)
604
+ rope_type = rp.get("rope_type", rp.get("type", "default"))
605
+ if rope_type not in ("default", "llama3"):
606
+ raise ValueError(f"rope type {rope_type!r} is not supported")
607
+ # take the frequency table transformers computed (scaling included)
608
+ rot = dec.rotary_emb
609
+ att_scale = float(getattr(rot, "attention_scaling", 1.0))
610
+ if att_scale != 1.0:
611
+ raise ValueError("rope attention_scaling != 1 is not supported")
612
+ inv_freq = rot.inv_freq.detach().float()
613
+
614
+ pj = cls._proj
615
+ layers = []
616
+ for lyr in dec.layers:
617
+ a, m = lyr.self_attn, lyr.mlp
618
+ layers.append({
619
+ "wqkv": pj([a.q_proj, a.k_proj, a.v_proj]),
620
+ "wo": pj([a.o_proj]),
621
+ "wgu": pj([m.gate_proj, m.up_proj]),
622
+ "wdown": pj([m.down_proj]),
623
+ "ln1": lyr.input_layernorm.weight,
624
+ "ln2": lyr.post_attention_layernorm.weight,
625
+ "qn": a.q_norm.weight if qk_norm else None,
626
+ "kn": a.k_norm.weight if qk_norm else None,
627
+ })
628
+ weights = {"embed": dec.embed_tokens.weight, "norm": dec.norm.weight,
629
+ "lm_head": pj([model.lm_head]), "layers": layers,
630
+ "inv_freq": inv_freq}
631
+ config = {
632
+ "num_hidden_layers": hf.num_hidden_layers,
633
+ "hidden_size": hf.hidden_size,
634
+ "num_attention_heads": hf.num_attention_heads,
635
+ "num_key_value_heads": getattr(hf, "num_key_value_heads", None),
636
+ "head_dim": getattr(hf, "head_dim", None),
637
+ "intermediate_size": hf.intermediate_size,
638
+ "vocab_size": hf.vocab_size,
639
+ "rope_theta": float(theta),
640
+ "rms_norm_eps": hf.rms_norm_eps,
641
+ "qk_norm": qk_norm,
642
+ }
643
+ return cls(weights, config, device=device, max_seq=max_seq,
644
+ max_gen=max_gen)
645
+
646
+ # ------------------------------------------------------------------
647
+ @classmethod
648
+ def _from_gemma4(cls, model, device, max_seq, max_gen):
649
+ """gemma-4-E2B-it program: gemma norms (fp32, raw weight),
650
+ gelu-tanh gating, post-sublayer norm phases, per-layer-input
651
+ pathway, sliding windows, shared KV, dual head dims, softcapped
652
+ head. Scope is the E2B text model only."""
653
+ self = cls.__new__(cls)
654
+ cfg = model.config.text_config if hasattr(model.config, "text_config") \
655
+ else model.config
656
+ dec = model.model.language_model if hasattr(model.model, "language_model") \
657
+ else model.model
658
+ assert cfg.model_type == "gemma4_text", cfg.model_type
659
+ assert not cfg.enable_moe_block
660
+
661
+ self.L = int(cfg.num_hidden_layers)
662
+ self.hidden = int(cfg.hidden_size)
663
+ self.Hq = int(cfg.num_attention_heads)
664
+ self.V = int(cfg.vocab_size)
665
+ self.eps = float(cfg.rms_norm_eps)
666
+ self.qk_norm = True
667
+ self.max_seq = int(max_seq)
668
+ self.max_gen = int(max_gen)
669
+ self.device = torch.device(device)
670
+ self.tiled = False
671
+ win = int(cfg.sliding_window)
672
+ cap = float(cfg.final_logit_softcapping)
673
+ PL = int(cfg.hidden_size_per_layer_input)
674
+ dt = torch.bfloat16
675
+ dev = self.device
676
+ self._keep = []
677
+
678
+ def pack(t):
679
+ t = t.detach().to(device=dev, dtype=dt).contiguous()
680
+ self._keep.append(t)
681
+ return t
682
+
683
+ def packf(t):
684
+ t = t.detach().to(device=dev, dtype=torch.float32).contiguous()
685
+ self._keep.append(t)
686
+ return t
687
+
688
+ embed = pack(dec.embed_tokens.weight)
689
+ ple_tab = pack(dec.embed_tokens_per_layer.weight)
690
+ plm_w = pack(dec.per_layer_model_projection.weight)
691
+ pln_w = pack(dec.per_layer_projection_norm.weight)
692
+ fnorm = pack(dec.norm.weight)
693
+ rot = dec.rotary_emb
694
+ invf = {lt: packf(getattr(rot, f"{lt}_inv_freq").float())
695
+ for lt in set(cfg.layer_types)}
696
+ ascale = {lt: float(getattr(rot, f"{lt}_attention_scaling"))
697
+ for lt in set(cfg.layer_types)}
698
+
699
+ layers = []
700
+ for li, lyr in enumerate(dec.layers):
701
+ a = lyr.self_attn
702
+ D_l = int(a.head_dim)
703
+ shared = bool(a.is_kv_shared_layer)
704
+ ent = {
705
+ "D": D_l, "shared": shared,
706
+ "lscale": float(lyr.layer_scalar.float().item()),
707
+ "type": cfg.layer_types[li],
708
+ "I": int(lyr.mlp.intermediate_size),
709
+ "qn": pack(a.q_norm.weight),
710
+ "o": pack(a.o_proj.weight),
711
+ "ln_in": pack(lyr.input_layernorm.weight),
712
+ "ln_pa": pack(lyr.post_attention_layernorm.weight),
713
+ "ln_pf": pack(lyr.pre_feedforward_layernorm.weight),
714
+ "ln_ff": pack(lyr.post_feedforward_layernorm.weight),
715
+ "ln_pl": pack(lyr.post_per_layer_input_norm.weight),
716
+ "plig": pack(lyr.per_layer_input_gate.weight),
717
+ "plpr": pack(lyr.per_layer_projection.weight),
718
+ "wgu": pack(torch.cat([lyr.mlp.gate_proj.weight,
719
+ lyr.mlp.up_proj.weight], 0)),
720
+ "wdn": pack(lyr.mlp.down_proj.weight),
721
+ }
722
+ if shared:
723
+ ent["wqkv"] = pack(a.q_proj.weight)
724
+ ent["kn"] = None
725
+ else:
726
+ ent["wqkv"] = pack(torch.cat(
727
+ [a.q_proj.weight, a.k_proj.weight, a.v_proj.weight], 0))
728
+ ent["kn"] = pack(a.k_norm.weight)
729
+ ent["kc"] = torch.zeros(self.max_seq, D_l, dtype=dt,
730
+ device=dev)
731
+ ent["vc"] = torch.zeros(self.max_seq, D_l, dtype=dt,
732
+ device=dev)
733
+ self._keep += [ent["kc"], ent["vc"]]
734
+ layers.append(ent)
735
+ # shared layers read the last non-shared layer of their type
736
+ src = {}
737
+ for li, ent in enumerate(layers):
738
+ if not ent["shared"]:
739
+ src[ent["type"]] = ent
740
+ for ent in layers:
741
+ if ent["shared"]:
742
+ ent["kc"] = src[ent["type"]]["kc"]
743
+ ent["vc"] = src[ent["type"]]["vc"]
744
+
745
+ maxD = max(e["D"] for e in layers)
746
+ maxI = max(e["I"] for e in layers)
747
+ self._maxk = max(self.hidden, self.Hq * maxD, maxI)
748
+ self._chunk_m = 1
749
+ self._chunk = 128
750
+ self._maxch = (self.max_seq + self._chunk - 1) // self._chunk
751
+ self._hidden = torch.empty(self.hidden, dtype=dt, device=dev)
752
+ self._tmp = torch.empty(self.hidden, dtype=dt, device=dev)
753
+ self._qkv = torch.empty((self.Hq + 2) * maxD, dtype=dt, device=dev)
754
+ self._gu = torch.empty(2 * maxI, dtype=dt, device=dev)
755
+ self._t256 = torch.empty(PL, dtype=dt, device=dev)
756
+ self._ctxraw = torch.empty(self.L * PL, dtype=dt, device=dev)
757
+ self._pletok = torch.empty(self.L * PL, dtype=dt, device=dev)
758
+ self._ple = torch.empty(self.L * PL, dtype=dt, device=dev)
759
+ self._logits = torch.empty(self.V, dtype=torch.float32, device=dev)
760
+ self._partials = torch.empty(
761
+ self.Hq * self._maxch * (maxD + 2), dtype=torch.float32,
762
+ device=dev)
763
+ self._token = torch.zeros(1, dtype=torch.int32, device=dev)
764
+ self._prompt_in = torch.zeros(self.max_seq, dtype=torch.int32,
765
+ device=dev)
766
+ self._tokens_out = torch.zeros(self.max_gen, dtype=torch.int32,
767
+ device=dev)
768
+ self._bar = torch.zeros(34, dtype=torch.int32, device=dev)
769
+ probe = torch.zeros(1, 16, dtype=torch.int64, device=dev)
770
+ self._nblocks = int(ops.mak_num_blocks(probe, self._maxk))
771
+ self._parts = torch.zeros(self._nblocks, dtype=torch.int64,
772
+ device=dev)
773
+ try:
774
+ self._bw_per_sm = float(ops.mak_bw_per_sm(probe))
775
+ except (AttributeError, RuntimeError):
776
+ self._bw_per_sm = 0.0
777
+ self._invf = invf[layers[0]["type"]]
778
+ self.Hkv, self.D, self.I = 1, maxD, maxI
779
+ self.theta = 0.0
780
+
781
+ emb_scale = float(torch.tensor(self.hidden ** 0.5,
782
+ dtype=torch.float32).to(dt))
783
+ ple_scale = float(torch.tensor(PL ** 0.5,
784
+ dtype=torch.float32).to(dt))
785
+ proj_scale = float(torch.tensor(self.hidden ** -0.5,
786
+ dtype=torch.float32))
787
+ hid = self._hidden.data_ptr()
788
+ tmp = self._tmp.data_ptr()
789
+ rows, names = [], []
790
+ rows.append(_row(_OP_EMBED, p1=ple_tab.data_ptr(),
791
+ p2=embed.data_ptr(), p3=hid,
792
+ p4=self._parts.data_ptr(),
793
+ p5=self._prompt_in.data_ptr(),
794
+ p6=self._token.data_ptr(),
795
+ n=self._tokens_out.data_ptr(), k=self.hidden,
796
+ it=self._pletok.data_ptr(),
797
+ hkv=self.L * PL, f0=emb_scale,
798
+ i0=_fbits(ple_scale)))
799
+ names.append("embed")
800
+ rows.append(_row(_OP_GEMV, p1=hid, p2=plm_w.data_ptr(),
801
+ p3=self._ctxraw.data_ptr(), n=self.L * PL,
802
+ k=self.hidden, it=_IT_NONE, epi=_EP_STORE))
803
+ names.append("plm")
804
+ rows.append(_row(8, p1=self._ctxraw.data_ptr(),
805
+ p2=pln_w.data_ptr(), p3=self._ple.data_ptr(),
806
+ p4=self._pletok.data_ptr(), n=self.L, k=PL,
807
+ f0=self.eps, i0=_fbits(proj_scale)))
808
+ names.append("plemix")
809
+
810
+ for li, e in enumerate(layers):
811
+ D_l, qdim = e["D"], self.Hq * e["D"]
812
+ lt = e["type"]
813
+ w_l = win if lt == "sliding_attention" else 0
814
+ asb = _fbits(ascale[lt]) if ascale[lt] != 1.0 else 0
815
+ flags = 1 | 2 | (0 if e["shared"] else 4) | \
816
+ (8 if e["shared"] else 0)
817
+ n_qkv = qdim if e["shared"] else qdim + 2 * D_l
818
+ rows.append(_row(_OP_GEMV, p1=hid, p2=e["wqkv"].data_ptr(),
819
+ p3=self._qkv.data_ptr(),
820
+ p4=e["ln_in"].data_ptr(), n=n_qkv,
821
+ k=self.hidden, it=_IT_RMSNORM_G,
822
+ epi=_EP_STORE, f0=self.eps))
823
+ names.append(f"L{li}.qkv")
824
+ rows.append(_row(_OP_ATTN, p1=self._qkv.data_ptr(),
825
+ p2=e["kc"].data_ptr(), p3=e["vc"].data_ptr(),
826
+ p4=self._partials.data_ptr(),
827
+ p5=e["qn"].data_ptr(),
828
+ p6=e["kn"].data_ptr() if e["kn"] is not None
829
+ else 0,
830
+ n=self.max_seq,
831
+ k=flags | (w_l << 16), it=_fbits(self.eps),
832
+ epi=invf[lt].data_ptr(), hq=self.Hq,
833
+ hkv=1 | (self._chunk << 16), d=D_l, f0=1.0,
834
+ i0=self._maxch | (asb << 32)))
835
+ names.append(f"L{li}.attn")
836
+ rows.append(_row(_OP_GEMV, p1=self._partials.data_ptr(),
837
+ p2=e["o"].data_ptr(), p3=tmp,
838
+ n=self.hidden, k=qdim, it=_IT_ATTNFIN,
839
+ epi=_EP_STORE, hq=self.Hq,
840
+ hkv=self._chunk << 16, d=D_l,
841
+ i0=self._maxch | (w_l << 16)))
842
+ names.append(f"L{li}.o")
843
+ rows.append(_row(7, p1=tmp, p2=e["ln_pa"].data_ptr(), p3=hid,
844
+ n=self.hidden, f0=self.eps, i0=_fbits(1.0)))
845
+ names.append(f"L{li}.nr_attn")
846
+ rows.append(_row(_OP_GEMV, p1=hid, p2=e["wgu"].data_ptr(),
847
+ p3=self._gu.data_ptr(),
848
+ p4=e["ln_pf"].data_ptr(), n=2 * e["I"],
849
+ k=self.hidden, it=_IT_RMSNORM_G,
850
+ epi=_EP_STORE, f0=self.eps))
851
+ names.append(f"L{li}.gateup")
852
+ rows.append(_row(_OP_GEMV, p1=self._gu.data_ptr(),
853
+ p2=e["wdn"].data_ptr(), p3=tmp,
854
+ n=self.hidden, k=e["I"], it=_IT_GELU_GLU,
855
+ epi=_EP_STORE))
856
+ names.append(f"L{li}.down")
857
+ rows.append(_row(7, p1=tmp, p2=e["ln_ff"].data_ptr(), p3=hid,
858
+ n=self.hidden, f0=self.eps, i0=_fbits(1.0)))
859
+ names.append(f"L{li}.nr_ffw")
860
+ rows.append(_row(_OP_GEMV, p1=hid, p2=e["plig"].data_ptr(),
861
+ p3=self._t256.data_ptr(),
862
+ p6=self._ple.data_ptr() + li * PL * 2,
863
+ n=PL, k=self.hidden, it=_IT_NONE,
864
+ epi=_EP_GELU_PLE))
865
+ names.append(f"L{li}.plig")
866
+ rows.append(_row(_OP_GEMV, p1=self._t256.data_ptr(),
867
+ p2=e["plpr"].data_ptr(), p3=tmp,
868
+ n=self.hidden, k=PL, it=_IT_NONE,
869
+ epi=_EP_STORE))
870
+ names.append(f"L{li}.plproj")
871
+ rows.append(_row(7, p1=tmp, p2=e["ln_pl"].data_ptr(), p3=hid,
872
+ n=self.hidden, f0=self.eps,
873
+ i0=_fbits(e["lscale"])))
874
+ names.append(f"L{li}.nr_ple")
875
+
876
+ rows.append(_row(_OP_GEMV, p1=hid, p2=embed.data_ptr(),
877
+ p3=self._logits.data_ptr(),
878
+ p4=fnorm.data_ptr(),
879
+ p6=self._parts.data_ptr(), n=self.V,
880
+ k=self.hidden, it=_IT_RMSNORM_G,
881
+ epi=_EP_F32_AMAX_CAP, hkv=1, f0=self.eps,
882
+ i0=_fbits(cap)))
883
+ names.append("lm_head")
884
+ rows.append(_row(_OP_ARGMAX_FIN, p1=self._parts.data_ptr(),
885
+ p3=self._token.data_ptr(),
886
+ p4=self._tokens_out.data_ptr()))
887
+ names.append("argmax.fin")
888
+ self._prog = torch.tensor(rows, dtype=torch.int64, device=dev)
889
+ self.phase_names = names
890
+ self._gemma = True
891
+ self._batch = 0
892
+ return self
893
+
894
+ # ------------------------------------------------------------------
895
+ def decode_step(self, token: int, pos: int, phased: bool = False):
896
+ """One decode step; returns the live fp32 logits buffer [V]."""
897
+ if pos >= self.max_seq:
898
+ raise ValueError("pos exceeds max_seq")
899
+ self._token.fill_(int(token))
900
+ if phased:
901
+ ops.mak_run_phased(self._prog, pos, self.max_gen - 1, False,
902
+ self._maxk)
903
+ else:
904
+ ops.mak_run(self._prog, self._bar, pos, self.max_gen - 1,
905
+ self._maxk)
906
+ return self._logits[:self.V]
907
+
908
+ def decode_step_timed(self, token: int, pos: int):
909
+ """One phase-per-launch step; returns (logits, per-phase ms)."""
910
+ self._token.fill_(int(token))
911
+ ms = ops.mak_run_phased(self._prog, pos, self.max_gen - 1, True,
912
+ self._maxk)
913
+ return self._logits[:self.V], ms
914
+
915
+ def prefill(self, ids: Sequence[int], phased: bool = False):
916
+ """Consume a prompt; returns the last position's logits. Runs in
917
+ chunks of up to chunk_m tokens with weight reads amortized across
918
+ the chunk (bitwise identical to token-by-token consumption)."""
919
+ P = len(ids)
920
+ if phased:
921
+ for i, t in enumerate(ids):
922
+ self.decode_step(int(t), i, phased=True)
923
+ return self._logits
924
+ self._prompt_in[:P].copy_(torch.tensor(ids, dtype=torch.int32))
925
+ ops.mak_run_seq(self._prog, self._bar, 0, P, 1 - P, P, self._maxk,
926
+ self._chunk_m)
927
+ return self._logits[:self.V]
928
+
929
+ def generate(self, prompt_ids: Sequence[int], max_new: int,
930
+ single_launch: bool = True) -> List[int]:
931
+ """Greedy generation. With single_launch (default) the entire call,
932
+ prompt consumption included, is one kernel launch: the in-kernel step
933
+ loop reads prompt tokens from a staged device buffer, then feeds each
934
+ step's argmax to the next step's embedding read. With
935
+ single_launch=False the same computation runs as one launch per
936
+ token; the two paths produce identical tokens.
937
+ """
938
+ P = len(prompt_ids)
939
+ if max_new < 1 or max_new > self.max_gen:
940
+ raise ValueError("max_new out of range")
941
+ if P < 1 or P + max_new > self.max_seq:
942
+ raise ValueError("sequence exceeds max_seq")
943
+ if single_launch:
944
+ self._prompt_in[:P].copy_(
945
+ torch.tensor(prompt_ids, dtype=torch.int32))
946
+ ops.mak_run_seq(self._prog, self._bar, 0, P + max_new - 1,
947
+ 1 - P, P, self._maxk, self._chunk_m)
948
+ return self._tokens_out[:max_new].cpu().tolist()
949
+ pos = 0
950
+ for t in prompt_ids[:-1]:
951
+ self._token.fill_(int(t))
952
+ ops.mak_run(self._prog, self._bar, pos, self.max_gen - 1,
953
+ self._maxk)
954
+ pos += 1
955
+ self._token.fill_(int(prompt_ids[-1]))
956
+ ops.mak_run(self._prog, self._bar, pos, 0, self._maxk)
957
+ pos += 1
958
+ if max_new > 1:
959
+ ops.mak_run_steps(self._prog, self._bar, pos, max_new - 1, 1,
960
+ self._maxk)
961
+ return self._tokens_out[:max_new].cpu().tolist()
962
+
963
+
964
+
build/torch211-cxx11-cu130-x86_64-linux/_model_as_a_kernel_cuda_e33875a.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:590bdbf325381981283ce087722012cbe44e97ac57595475fbcb12b043e7a79c
3
+ size 6347568
build/torch211-cxx11-cu130-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _model_as_a_kernel_cuda_e33875a
3
+ ops = torch.ops._model_as_a_kernel_cuda_e33875a
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_model_as_a_kernel_cuda_e33875a::{op_name}"
build/torch211-cxx11-cu130-x86_64-linux/metadata.json ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "model-as-a-kernel",
3
+ "id": "_model_as_a_kernel_cuda_e33875a",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "python-depends": [],
7
+ "backend": {
8
+ "type": "cuda",
9
+ "archs": [
10
+ "10.0",
11
+ "12.0",
12
+ "8.0",
13
+ "8.6",
14
+ "8.9",
15
+ "9.0"
16
+ ]
17
+ },
18
+ "digest": {
19
+ "algorithm": "sha256",
20
+ "files": {
21
+ "__init__.py": "/YX5/zzAOyJacQ7H+j8zaq+xjWyy8w6r0j+ugE1EUs8=",
22
+ "_model_as_a_kernel_cuda_e33875a.abi3.so": "WQvb8yU4GYEoPOCHciASy+ROl6xXWVR1+8sSsEPnp5w=",
23
+ "_ops.py": "Pqj4WKtwPyeDG9grQnzLget4g1jsGUdR7ZMa44e4rhs=",
24
+ "model_as_a_kernel/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
25
+ }
26
+ },
27
+ "provenance": {
28
+ "kernel-builder": {
29
+ "version": "0.17.0-dev0",
30
+ "sha": "05f1ed487f599a320311aa26076ec0ad290c3f68",
31
+ "dirty": false
32
+ },
33
+ "kernel": {
34
+ "sha": "e33875ae4eba42fd136a89c388e806281cd4759c",
35
+ "dirty": false
36
+ }
37
+ }
38
+ }
build/torch211-cxx11-cu130-x86_64-linux/model_as_a_kernel/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import importlib.util
3
+ import sys
4
+ from pathlib import Path
5
+ from types import ModuleType
6
+
7
+
8
+ def _import_from_path(file_path: Path) -> ModuleType:
9
+ # We cannot use the module name as-is, after adding it to `sys.modules`,
10
+ # it would also be used for other imports. So, we make a module name that
11
+ # depends on the path for it to be unique using the hex-encoded hash of
12
+ # the path.
13
+ path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
+ module_name = path_hash
15
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
16
+ if spec is None:
17
+ raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
+ module = importlib.util.module_from_spec(spec)
19
+ if module is None:
20
+ raise ImportError(f"Cannot load module {module_name} from spec")
21
+ sys.modules[module_name] = module
22
+ spec.loader.exec_module(module) # type: ignore
23
+ return module
24
+
25
+
26
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
build/torch212-cxx11-cu126-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,964 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """model-as-a-kernel: an entire llama-family decode step as one kernel launch.
2
+
3
+ A phase interpreter runs the whole forward pass (embedding, every layer's
4
+ norms, QKV, rope, attention over the KV cache, SwiGLU MLP, the LM head, and
5
+ the greedy argmax) inside a single persistent kernel, with a software grid
6
+ barrier between phases. The in-kernel step loop makes an entire greedy
7
+ generation, prompt consumption included, one kernel launch: each step's
8
+ argmax feeds the next step's embedding read without leaving the device.
9
+ """
10
+ import os
11
+ import struct
12
+ from typing import List, Optional, Sequence
13
+
14
+ import torch
15
+
16
+ try:
17
+ from ._ops import ops
18
+ except ImportError: # local development build (torch.utils.cpp_extension)
19
+ class _LocalOps:
20
+ def __getattr__(self, name):
21
+ return getattr(torch.ops.mak_ext, name)
22
+
23
+ ops = _LocalOps()
24
+
25
+ __all__ = ["MegaModel"]
26
+
27
+ _OP_EMBED, _OP_GEMV, _OP_QKV_POST, _OP_ATTN = 0, 1, 2, 3
28
+ _OP_ARGMAX_PART, _OP_ARGMAX_FIN = 4, 5
29
+ _OP_KV_APPEND, _OP_NORMRES, _OP_PLEMIX = 6, 7, 8
30
+ _OP_GEMV_PLAIN, _OP_NORMB, _OP_GLUB, _OP_ATTNFINB = 9, 10, 11, 12
31
+ _OP_GEMV_Q4 = 13
32
+ _IT_NONE, _IT_RMSNORM, _IT_SWIGLU, _IT_ATTNFIN = 0, 1, 2, 3
33
+ _IT_RMSNORM_G, _IT_GELU_GLU = 4, 5
34
+ _EP_GELU_PLE, _EP_F32_AMAX_CAP = 5, 6
35
+ _ITF_REDUCE, _ITF_WRITEBACK = 8, 16
36
+ _EP_STORE, _EP_RESID, _EP_F32, _EP_F32_AMAX, _EP_PARTIAL = 0, 1, 2, 3, 4
37
+ _NSLICE = 4
38
+ # Widest [B][K] input panel (bf16 elements) a batched projection stages in
39
+ # shared before it spills to the global-scratch path. 16384 (32 KB) keeps
40
+ # two blocks resident per SM; the persistent kernel shares one occupancy
41
+ # across all phases, so a wider panel would drop every phase to one block
42
+ # per SM, which measures slower than reading the input from L2.
43
+ _STAGE_BUDGET = 16384
44
+
45
+
46
+ def _fbits(f: float) -> int:
47
+ return struct.unpack("<i", struct.pack("<f", float(f)))[0]
48
+
49
+
50
+ def _row(op, p1=0, p2=0, p3=0, p4=0, p5=0, p6=0, n=0, k=0, it=0, epi=0,
51
+ hq=0, hkv=0, d=0, f0=0.0, i0=0):
52
+ return [int(op), int(p1), int(p2), int(p3), int(p4), int(p5), int(p6),
53
+ int(n), int(k), int(it), int(epi), int(hq), int(hkv), int(d),
54
+ _fbits(f0), int(i0)]
55
+
56
+
57
+ class MegaModel:
58
+ """A packed llama-family decoder whose decode step is one kernel launch.
59
+
60
+ Supported architecture: RMSNorm decoder blocks with rotary attention
61
+ (GQA, optional Qwen3-style per-head qk RMSNorm), SwiGLU MLP, no attention
62
+ or MLP biases, plain (unscaled) rope, batch size 1. This covers SmolLM2,
63
+ TinyLlama, Qwen3 dense, Llama-class checkpoints without rope scaling,
64
+ and similar models.
65
+ """
66
+
67
+ def __init__(self, weights: dict, config: dict, device="cuda",
68
+ max_seq: int = 4096, max_gen: int = 4096):
69
+ cfg = dict(config)
70
+ self.L = int(cfg["num_hidden_layers"])
71
+ self.hidden = int(cfg["hidden_size"])
72
+ self.Hq = int(cfg["num_attention_heads"])
73
+ self.Hkv = int(cfg.get("num_key_value_heads") or self.Hq)
74
+ self.D = int(cfg.get("head_dim") or self.hidden // self.Hq)
75
+ self.I = int(cfg["intermediate_size"])
76
+ self.V = int(cfg["vocab_size"])
77
+ self.theta = float(cfg.get("rope_theta", 10000.0))
78
+ self.eps = float(cfg.get("rms_norm_eps", 1e-6))
79
+ self.qk_norm = bool(cfg.get("qk_norm", False))
80
+ self.max_seq = int(max_seq)
81
+ self.max_gen = int(max_gen)
82
+ self.device = torch.device(device)
83
+
84
+ if self.D % 2 or self.D > 256:
85
+ raise ValueError("head_dim must be even and <= 256")
86
+ if self.hidden % 8 or self.I % 8 or (self.Hq * self.D) % 8:
87
+ raise ValueError(
88
+ "hidden, intermediate, and Hq*head_dim must be multiples of 8")
89
+ if self.Hq % self.Hkv:
90
+ raise ValueError("num_attention_heads must be divisible by num_key_value_heads")
91
+
92
+ dt = torch.bfloat16
93
+ dev = self.device
94
+
95
+ def pack(t):
96
+ t = t.detach().to(device=dev, dtype=dt).contiguous()
97
+ self._keep.append(t)
98
+ return t
99
+
100
+ # Row-tile-of-8 weight layout [N/8][K/8][8][8] (MAK_TILED=1): the
101
+ # eight warps of a block read one contiguous 4KB stream per step
102
+ # instead of eight 4KB-strided pieces. Measured slower on every
103
+ # card (H200 +15 percent, RTX PRO +9, Ada +14 — per-warp stream
104
+ # locality beats per-block contiguity on all of them), so the
105
+ # default is the plain layout; the flag rides in the program and
106
+ # both layouts compute identical bits.
107
+ env_t = os.environ.get("MAK_TILED", "").strip()
108
+ self.tiled = env_t == "1"
109
+
110
+ def pack_tiled(t):
111
+ t = t.detach().to(device=dev, dtype=dt).contiguous()
112
+ if not self.tiled:
113
+ self._keep.append(t)
114
+ return t
115
+ n, k = t.shape
116
+ assert k % 8 == 0
117
+ n8 = (n + 7) // 8 * 8
118
+ if n8 != n:
119
+ t = torch.cat(
120
+ [t, torch.zeros(n8 - n, k, dtype=dt, device=dev)], 0)
121
+ t = t.view(n8 // 8, 8, k // 8, 8).permute(0, 2, 1, 3).contiguous()
122
+ self._keep.append(t)
123
+ return t
124
+
125
+ self._keep: List[torch.Tensor] = []
126
+ embed = pack(weights["embed"])
127
+ final_norm = pack(weights["norm"])
128
+ lm_head = weights["lm_head"] # packed below (dense-tiled or nf4)
129
+ inv_freq = weights.get("inv_freq")
130
+ if inv_freq is None:
131
+ d_idx = torch.arange(0, self.D, 2, dtype=torch.float32)
132
+ inv_freq = 1.0 / (self.theta ** (d_idx / self.D))
133
+ inv_freq = inv_freq.detach().to(device=dev,
134
+ dtype=torch.float32).contiguous()
135
+ if inv_freq.numel() != self.D // 2:
136
+ raise ValueError("inv_freq length must be head_dim / 2")
137
+ self._keep.append(inv_freq)
138
+ self._invf = inv_freq
139
+
140
+ qdim, kvdim = self.Hq * self.D, self.Hkv * self.D
141
+
142
+ def _is_q4(w):
143
+ return isinstance(w, dict) and w.get("q4")
144
+
145
+ def pack_w(w):
146
+ # nf4-packed weights move to the device intact; dense weights take
147
+ # the usual (optionally row-tiled) bf16 packing.
148
+ if _is_q4(w):
149
+ pk = w["packed"].to(dev).contiguous()
150
+ am = w["absmax"].to(device=dev, dtype=torch.float32).contiguous()
151
+ self._keep.append(pk)
152
+ self._keep.append(am)
153
+ return {"q4": True, "packed": pk, "absmax": am,
154
+ "N": int(w["N"]), "K": int(w["K"])}
155
+ return pack_tiled(w)
156
+
157
+ def _wshape(w, expected):
158
+ got = (int(w["N"]), int(w["K"])) if _is_q4(w) else tuple(w.shape)
159
+ assert got == expected, (got, expected)
160
+
161
+ self._has_q4 = False
162
+ self._layers = []
163
+ for lw in weights["layers"]:
164
+ _wshape(lw["wqkv"], (qdim + 2 * kvdim, self.hidden))
165
+ _wshape(lw["wo"], (self.hidden, qdim))
166
+ _wshape(lw["wgu"], (2 * self.I, self.hidden))
167
+ _wshape(lw["wdown"], (self.hidden, self.I))
168
+ layer = {
169
+ "wqkv": pack_w(lw["wqkv"]), "wo": pack_w(lw["wo"]),
170
+ "wgu": pack_w(lw["wgu"]), "wdown": pack_w(lw["wdown"]),
171
+ "ln1": pack(lw["ln1"]), "ln2": pack(lw["ln2"]),
172
+ "qn": pack(lw["qn"]) if self.qk_norm else None,
173
+ "kn": pack(lw["kn"]) if self.qk_norm else None,
174
+ }
175
+ self._has_q4 |= any(_is_q4(layer[n]) for n in
176
+ ("wqkv", "wo", "wgu", "wdown"))
177
+ self._layers.append(layer)
178
+ assert len(self._layers) == self.L
179
+ assert embed.shape == (self.V, self.hidden)
180
+ lm_head = pack_w(lm_head)
181
+ self._has_q4 |= _is_q4(lm_head)
182
+ if not _is_q4(lm_head):
183
+ assert lm_head.numel() >= self.V * self.hidden # row-tiled, padded
184
+ self._w_embed, self._w_fnorm, self._w_lmhead = (embed, final_norm,
185
+ lm_head)
186
+ self._qdim, self._kvdim = qdim, kvdim
187
+
188
+ # Working buffers. Pointers to these are baked into the program, so
189
+ # they (like the packed weights) live for the model's lifetime.
190
+ self._maxk = max(self.hidden, qdim, self.I)
191
+ # prefill chunk size, bounded by the staging shared-memory budget
192
+ # (16K bf16 elements; the cp.async weight ring rides alongside)
193
+ self._chunk_m = max(1, min(8, 16384 // self._maxk))
194
+ cm = 8 # buffers sized for the maximum chunk
195
+ self._hidden = torch.empty(cm * self.hidden, dtype=dt, device=dev)
196
+ self._qkv = torch.empty(cm * (qdim + 2 * kvdim), dtype=dt, device=dev)
197
+ self._gu = torch.empty(cm * 2 * self.I, dtype=dt, device=dev)
198
+ self._logits = torch.empty(self.V, dtype=torch.float32, device=dev)
199
+ # attention chunk length (kernel maximum 128); shorter chunks give
200
+ # the attention phase more grid parallelism at the cost of more
201
+ # softmax partials to finalize
202
+ env_ch = os.environ.get("MAK_CHUNK", "").strip()
203
+ self._chunk = int(env_ch) if env_ch in ("32", "64", "128") else 128
204
+ self._maxch = (self.max_seq + self._chunk - 1) // self._chunk
205
+ self._partials = torch.empty(
206
+ cm * self.Hq * self._maxch * (self.D + 2), dtype=torch.float32,
207
+ device=dev)
208
+ self._kcache = torch.zeros(self.L, self.Hkv, self.max_seq, self.D,
209
+ dtype=dt, device=dev)
210
+ self._vcache = torch.zeros(self.L, self.Hkv, self.max_seq, self.D,
211
+ dtype=dt, device=dev)
212
+ self._token = torch.zeros(1, dtype=torch.int32, device=dev)
213
+ self._prompt_in = torch.zeros(self.max_seq, dtype=torch.int32,
214
+ device=dev)
215
+ self._tokens_out = torch.zeros(self.max_gen, dtype=torch.int32,
216
+ device=dev)
217
+ self._bar = torch.zeros(34, dtype=torch.int32, device=dev)
218
+
219
+ probe = torch.zeros(1, 16, dtype=torch.int64, device=dev)
220
+ self._nblocks = int(ops.mak_num_blocks(probe, self._maxk))
221
+ self._parts = torch.zeros(self._nblocks, dtype=torch.int64, device=dev)
222
+ try:
223
+ self._bw_per_sm = float(ops.mak_bw_per_sm(probe))
224
+ except (AttributeError, RuntimeError):
225
+ self._bw_per_sm = 0.0
226
+
227
+ self._batch = 0
228
+ self._pos_b = None
229
+ self._kv_bstride = 0
230
+ self._gemma = False
231
+ # transformed-input scratch: the nf4 and wide-batch paths write the
232
+ # dense bf16 input here (one row per active token) for the following
233
+ # plain/quant GEMV; sized for a prefill chunk (up to 8 rows)
234
+ self._xg = torch.empty(cm * self._maxk, dtype=dt, device=dev)
235
+ self._build_programs()
236
+
237
+ # ------------------------------------------------------------------
238
+ def _kvp(self, cache, li: int, b: int) -> int:
239
+ t = cache[li]
240
+ return (t[b] if t.dim() == 4 else t).data_ptr()
241
+
242
+ def _build_rows(self, kv_slice: int = 0, batch: bool = False,
243
+ big: bool = False):
244
+ """Program rows for the llama-family path. kv_slice selects the
245
+ batch slice the KV pointers address (per-sequence prefill);
246
+ batch=True emits the batched-decode tail (per-row fp32 logits and
247
+ explicit argmax phases) instead of the fused LM-head amax. In batch
248
+ mode each projection stays fused (input staged in shared) when its
249
+ [B][K] panel fits the budget; the projections whose K is too wide
250
+ transform into a global scratch and read it with a plain GEMV."""
251
+ _tbit = (1 << 20) if self.tiled else 0 # GEMV weight layout flag
252
+ qdim, kvdim = self._qdim, self._kvdim
253
+ embed, lm_head = self._w_embed, self._w_lmhead
254
+ final_norm = self._w_fnorm
255
+ xg = self._xg.data_ptr()
256
+ B = self._batch if batch else 1
257
+ rows, names = [], []
258
+ staged_elems = [0] # widest [B][K] panel any fused projection stages
259
+
260
+ def fits(K):
261
+ return B * K <= _STAGE_BUDGET
262
+
263
+ def note(K):
264
+ staged_elems[0] = max(staged_elems[0], B * K)
265
+
266
+ def _q4(w):
267
+ return isinstance(w, dict) and w.get("q4")
268
+
269
+ def _wgemv(out, w, N, K, epi, resid):
270
+ # the GEMV after a transform-to-scratch phase: nf4-dequant when
271
+ # the weight is packed, otherwise a plain bf16 GEMV
272
+ if _q4(w):
273
+ rows.append(_row(_OP_GEMV_Q4, p1=xg, p2=w["packed"].data_ptr(),
274
+ p3=out, p5=resid, p6=w["absmax"].data_ptr(),
275
+ n=N, k=K, epi=epi))
276
+ else:
277
+ rows.append(_row(_OP_GEMV_PLAIN, p1=xg, p2=w.data_ptr(),
278
+ p3=out, p5=resid, n=N, k=K, it=_tbit,
279
+ epi=epi))
280
+
281
+ def norm_gemv(name, gamma, w, out, N, K, epi, inp, resid=0,
282
+ variant=_IT_RMSNORM):
283
+ if _q4(w) or (big and not fits(K)):
284
+ rows.append(_row(_OP_NORMB, p1=inp, p2=gamma, p3=xg, k=K,
285
+ it=variant, f0=self.eps))
286
+ names.append(name + ".norm")
287
+ _wgemv(out, w, N, K, epi, resid)
288
+ names.append(name)
289
+ else:
290
+ note(K)
291
+ rows.append(_row(_OP_GEMV, p1=inp, p2=w.data_ptr(), p3=out,
292
+ p4=gamma, p5=resid, n=N, k=K,
293
+ it=variant | _tbit, epi=epi, f0=self.eps))
294
+ names.append(name)
295
+
296
+ def glu_gemv(name, gu, w, out, resid, variant=_IT_SWIGLU):
297
+ if _q4(w) or (big and not fits(self.I)):
298
+ rows.append(_row(_OP_GLUB, p1=gu, p3=xg, k=self.I, it=variant))
299
+ names.append(name + ".glu")
300
+ _wgemv(out, w, self.hidden, self.I, _EP_RESID, resid)
301
+ names.append(name)
302
+ else:
303
+ note(self.I)
304
+ rows.append(_row(_OP_GEMV, p1=gu, p2=w.data_ptr(), p3=out,
305
+ p5=resid, n=self.hidden, k=self.I,
306
+ it=variant | _tbit, epi=_EP_RESID))
307
+ names.append(name)
308
+
309
+ def attn_gemv(name, w, out, resid):
310
+ if _q4(w) or (big and not fits(qdim)):
311
+ rows.append(_row(_OP_ATTNFINB, p1=self._partials.data_ptr(),
312
+ p3=xg, k=qdim, hq=self.Hq,
313
+ hkv=self._chunk << 16, d=self.D,
314
+ i0=self._maxch))
315
+ names.append(name + ".fin")
316
+ _wgemv(out, w, self.hidden, qdim, _EP_RESID, resid)
317
+ names.append(name)
318
+ else:
319
+ note(qdim)
320
+ rows.append(_row(_OP_GEMV, p1=self._partials.data_ptr(),
321
+ p2=w.data_ptr(), p3=out, p5=resid,
322
+ n=self.hidden, k=qdim,
323
+ it=_IT_ATTNFIN | _tbit, epi=_EP_RESID,
324
+ hq=self.Hq, hkv=self._chunk << 16, d=self.D,
325
+ i0=self._maxch))
326
+ names.append(name)
327
+
328
+ rows.append(_row(_OP_EMBED, p2=embed.data_ptr(),
329
+ p3=self._hidden.data_ptr(),
330
+ p4=self._parts.data_ptr(),
331
+ p5=self._prompt_in.data_ptr(),
332
+ p6=self._token.data_ptr(),
333
+ n=self._tokens_out.data_ptr(), k=self.hidden))
334
+ names.append("embed")
335
+ scale = 1.0 / (self.D ** 0.5)
336
+ hid = self._hidden.data_ptr()
337
+ for li, lw in enumerate(self._layers):
338
+ kc = self._kvp(self._kcache, li, kv_slice)
339
+ vc = self._kvp(self._vcache, li, kv_slice)
340
+ norm_gemv(f"L{li}.qkv", lw["ln1"].data_ptr(),
341
+ lw["wqkv"], self._qkv.data_ptr(),
342
+ qdim + 2 * kvdim, self.hidden, _EP_STORE, hid)
343
+ rows.append(_row(6, p1=self._qkv.data_ptr(), p2=kc, p3=vc,
344
+ p6=lw["kn"].data_ptr() if self.qk_norm else 0,
345
+ n=self.max_seq, k=1 if self.qk_norm else 0,
346
+ it=_fbits(self.eps),
347
+ epi=self._invf.data_ptr(),
348
+ hq=self.Hq, hkv=self.Hkv, d=self.D))
349
+ names.append(f"L{li}.kvappend")
350
+ rows.append(_row(_OP_ATTN, p1=self._qkv.data_ptr(), p2=kc,
351
+ p3=vc, p4=self._partials.data_ptr(),
352
+ p5=lw["qn"].data_ptr() if self.qk_norm else 0,
353
+ p6=lw["kn"].data_ptr() if self.qk_norm else 0,
354
+ n=self.max_seq, k=1 if self.qk_norm else 0,
355
+ it=_fbits(self.eps),
356
+ epi=self._invf.data_ptr(),
357
+ hq=self.Hq,
358
+ hkv=self.Hkv | (self._chunk << 16),
359
+ d=self.D, f0=scale, i0=self._maxch))
360
+ names.append(f"L{li}.attn")
361
+ attn_gemv(f"L{li}.o", lw["wo"], hid, hid)
362
+ norm_gemv(f"L{li}.gateup", lw["ln2"].data_ptr(),
363
+ lw["wgu"], self._gu.data_ptr(), 2 * self.I,
364
+ self.hidden, _EP_STORE, hid)
365
+ glu_gemv(f"L{li}.down", self._gu.data_ptr(), lw["wdown"], hid, hid)
366
+ if batch or _q4(lm_head):
367
+ norm_gemv("lm_head", final_norm.data_ptr(), lm_head,
368
+ self._logits.data_ptr(), self.V, self.hidden, _EP_F32,
369
+ hid)
370
+ rows.append(_row(_OP_ARGMAX_PART, p1=self._logits.data_ptr(),
371
+ p3=self._parts.data_ptr(), n=self.V))
372
+ names.append("argmax.part")
373
+ rows.append(_row(_OP_ARGMAX_FIN, p1=self._parts.data_ptr(),
374
+ p3=self._token.data_ptr(),
375
+ p4=self._tokens_out.data_ptr(),
376
+ k=self.max_gen))
377
+ names.append("argmax.fin")
378
+ else:
379
+ rows.append(_row(_OP_GEMV, p1=hid, p2=lm_head.data_ptr(),
380
+ p3=self._logits.data_ptr(),
381
+ p4=final_norm.data_ptr(),
382
+ p6=self._parts.data_ptr(), n=self.V,
383
+ k=self.hidden, it=_IT_RMSNORM | _tbit,
384
+ epi=_EP_F32_AMAX, hkv=1, f0=self.eps))
385
+ names.append("lm_head")
386
+ rows.append(_row(_OP_ARGMAX_FIN, p1=self._parts.data_ptr(),
387
+ p3=self._token.data_ptr(),
388
+ p4=self._tokens_out.data_ptr()))
389
+ names.append("argmax.fin")
390
+ se = max(staged_elems[0], self._maxk)
391
+ return rows, names, se
392
+
393
+ def _build_programs(self):
394
+ dev = self.device
395
+ rows, names, _ = self._build_rows(kv_slice=0, batch=False)
396
+ self._prog = torch.tensor(rows, dtype=torch.int64, device=dev)
397
+ self.phase_names = names
398
+ if self._batch:
399
+ big = self._batch > min(8, 16384 // self._maxk)
400
+ rows, _, se = self._build_rows(kv_slice=0, batch=True, big=big)
401
+ self._prog_batch = torch.tensor(rows, dtype=torch.int64,
402
+ device=dev)
403
+ self._batch_stage_elems = int(se)
404
+ self._progs_prefill = [self._prog]
405
+ for b in range(1, self._batch):
406
+ rows, _, _ = self._build_rows(kv_slice=b, batch=False)
407
+ self._progs_prefill.append(
408
+ torch.tensor(rows, dtype=torch.int64, device=dev))
409
+
410
+ def batch_max(self) -> int:
411
+ """Largest batch size this model supports. Up to 8 sequences stage
412
+ in shared; beyond that the transformed input moves to a global
413
+ scratch, bounded by the kernel's register accumulators."""
414
+ try:
415
+ hard = int(ops.mak_batch_maxb())
416
+ except (AttributeError, RuntimeError):
417
+ hard = 8
418
+ return 1 if self._gemma else hard
419
+
420
+ # ------------------------------------------------------------------
421
+ def enable_batch(self, B: int):
422
+ """Allocate per-sequence KV caches, token buffers, and programs for
423
+ batched decode. B up to 8 stages the input in shared; larger B (up
424
+ to batch_max()) routes the transformed input through a global
425
+ scratch so the batch width is not bounded by the projection size."""
426
+ if self._gemma:
427
+ raise ValueError("batched decode covers the llama-family path")
428
+ maxb = self.batch_max()
429
+ if not 1 <= B <= maxb:
430
+ raise ValueError(f"B must be in [1, {maxb}] for this model")
431
+ if self._batch == B:
432
+ return
433
+ dt, dev = torch.bfloat16, self.device
434
+ self._kcache = torch.zeros(self.L, B, self.Hkv, self.max_seq,
435
+ self.D, dtype=dt, device=dev)
436
+ self._vcache = torch.zeros_like(self._kcache)
437
+ self._token = torch.zeros(B, dtype=torch.int32, device=dev)
438
+ self._tokens_out = torch.zeros(B * self.max_gen, dtype=torch.int32,
439
+ device=dev)
440
+ self._logits = torch.empty(B * self.V, dtype=torch.float32,
441
+ device=dev)
442
+ self._parts = torch.zeros(B * self._nblocks, dtype=torch.int64,
443
+ device=dev)
444
+ self._pos_b = torch.zeros(B, dtype=torch.int32, device=dev)
445
+ # transformed-input scratch: one row per active token, and prefill
446
+ # (during generate_batch) still uses up to chunk_m rows
447
+ self._xg = torch.empty(max(8, B) * self._maxk, dtype=dt, device=dev)
448
+ # working buffers hold one row per active sequence; prefill chunks
449
+ # still use up to chunk_m rows, so keep at least the fused capacity
450
+ nb = max(8, B)
451
+ qkvd = self._qdim + 2 * self._kvdim
452
+ self._hidden = torch.empty(nb * self.hidden, dtype=dt, device=dev)
453
+ self._qkv = torch.empty(nb * qkvd, dtype=dt, device=dev)
454
+ self._gu = torch.empty(nb * 2 * self.I, dtype=dt, device=dev)
455
+ self._partials = torch.empty(
456
+ nb * self.Hq * self._maxch * (self.D + 2), dtype=torch.float32,
457
+ device=dev)
458
+ self._kv_bstride = self.Hkv * self.max_seq * self.D
459
+ self._batch = B
460
+ self._build_programs()
461
+
462
+ def decode_batch(self, tokens: Sequence[int], positions: Sequence[int],
463
+ steps: int = 1) -> torch.Tensor:
464
+ """Batched greedy decode: sequence b consumes tokens[b] at
465
+ positions[b]; `steps` in-kernel steps run with per-sequence argmax
466
+ feedback. Returns the generated tokens [B, steps]; the final
467
+ step's fp32 logits are live in batch_logits()."""
468
+ B = self._batch
469
+ if B < 1:
470
+ raise ValueError("call enable_batch(B) first")
471
+ if len(tokens) != B or len(positions) != B:
472
+ raise ValueError("tokens and positions must have B entries")
473
+ if steps < 1 or steps > self.max_gen:
474
+ raise ValueError("steps out of range")
475
+ if max(positions) + steps > self.max_seq:
476
+ raise ValueError("sequence exceeds max_seq")
477
+ self._token.copy_(torch.tensor(list(tokens), dtype=torch.int32))
478
+ self._pos_b.copy_(torch.tensor(list(positions), dtype=torch.int32))
479
+ ops.mak_run_batch(self._prog_batch, self._bar, self._pos_b, steps,
480
+ 0, self._maxk, self._kv_bstride,
481
+ self._batch_stage_elems)
482
+ return self._tokens_out.view(B, self.max_gen)[:, :steps]
483
+
484
+ def batch_logits(self) -> torch.Tensor:
485
+ """fp32 logits [B, V] of the most recent batched step."""
486
+ return self._logits.view(max(self._batch, 1), self.V)
487
+
488
+ def generate_batch(self, prompts: Sequence[Sequence[int]],
489
+ max_new: int) -> List[List[int]]:
490
+ """Greedy generation for a batch of prompts: per-sequence prefill,
491
+ then one batched decode launch. Each sequence's token stream is
492
+ bit-identical to its own single-sequence generate()."""
493
+ B = len(prompts)
494
+ if max_new < 1 or max_new > self.max_gen:
495
+ raise ValueError("max_new out of range")
496
+ self.enable_batch(B)
497
+ firsts: List[int] = []
498
+ for b, ids in enumerate(prompts):
499
+ P = len(ids)
500
+ if P < 1 or P + max_new > self.max_seq:
501
+ raise ValueError("sequence exceeds max_seq")
502
+ self._prompt_in[:P].copy_(
503
+ torch.tensor(list(ids), dtype=torch.int32))
504
+ ops.mak_run_seq(self._progs_prefill[b], self._bar, 0, P, 1 - P,
505
+ P, self._maxk, self._chunk_m)
506
+ firsts.append(int(self._token[0].item()))
507
+ if max_new == 1:
508
+ return [[t] for t in firsts]
509
+ self._token.copy_(torch.tensor(firsts, dtype=torch.int32))
510
+ self._pos_b.copy_(torch.tensor([len(p) for p in prompts],
511
+ dtype=torch.int32))
512
+ ops.mak_run_batch(self._prog_batch, self._bar, self._pos_b,
513
+ max_new - 1, 0, self._maxk, self._kv_bstride,
514
+ self._batch_stage_elems)
515
+ rest = (self._tokens_out.view(B, self.max_gen)[:, :max_new - 1]
516
+ .cpu().tolist())
517
+ return [[firsts[b]] + rest[b] for b in range(B)]
518
+
519
+ # ------------------------------------------------------------------
520
+ @staticmethod
521
+ def _dense_weight(mod):
522
+ """The effective bf16 weight of a linear layer, dequantizing packed
523
+ quantized layers (bitsandbytes 4-bit and 8-bit) so a quantized
524
+ checkpoint loads directly. Dequantization is exact and does not
525
+ depend on the card, so inference on the result keeps every
526
+ guarantee; the weights materialize to bf16, so the quantized memory
527
+ footprint is not preserved (that needs the in-kernel packed path)."""
528
+ w = mod.weight
529
+ qs = getattr(w, "quant_state", None)
530
+ if qs is not None: # bitsandbytes 4-bit (nf4 / fp4)
531
+ import bitsandbytes as bnb
532
+ return bnb.functional.dequantize_4bit(w.data, qs).to(torch.bfloat16)
533
+ if getattr(w, "SCB", None) is not None or hasattr(mod, "SCB"):
534
+ # bitsandbytes 8-bit (LLM.int8): row scales in SCB, int8 in CB
535
+ scb = w.SCB if getattr(w, "SCB", None) is not None else mod.SCB
536
+ cb = w.data if w.data.dtype == torch.int8 else mod.CB
537
+ return (cb.to(torch.float32)
538
+ * (scb.to(torch.float32) / 127.0).unsqueeze(1)
539
+ ).to(torch.bfloat16)
540
+ return w
541
+
542
+ @staticmethod
543
+ def _q4_parts(mod):
544
+ """(packed [N, K/2] uint8, absmax [N*K/64] fp32) for a bitsandbytes
545
+ nf4 layer whose weights stay packed, else None. Double-quantized
546
+ absmax is materialized to fp32 so the kernel needs one scale array."""
547
+ w = getattr(mod, "weight", None)
548
+ qs = getattr(w, "quant_state", None)
549
+ if qs is None or getattr(qs, "quant_type", None) != "nf4":
550
+ return None
551
+ import bitsandbytes as bnb
552
+ n, k = int(qs.shape[0]), int(qs.shape[1])
553
+ if k % 64:
554
+ return None # kernel assumes 64 | K for per-block absmax
555
+ packed = w.data.reshape(n, k // 2).contiguous()
556
+ if getattr(qs, "nested", False):
557
+ absmax = bnb.functional.dequantize_blockwise(
558
+ qs.absmax, qs.state2) + qs.offset
559
+ else:
560
+ absmax = qs.absmax
561
+ return packed, absmax.float().reshape(-1).contiguous()
562
+
563
+ @classmethod
564
+ def _proj(cls, mods):
565
+ """A projection, concatenated across `mods` (q/k/v or gate/up). Stays
566
+ nf4-packed when every part is nf4, else a dense bf16 tensor."""
567
+ parts = [cls._q4_parts(m) for m in mods]
568
+ if all(p is not None for p in parts):
569
+ packed = torch.cat([p[0] for p in parts], 0)
570
+ absmax = torch.cat([p[1] for p in parts], 0)
571
+ return {"q4": True, "packed": packed, "absmax": absmax,
572
+ "N": packed.shape[0], "K": packed.shape[1] * 2}
573
+ return torch.cat([cls._dense_weight(m) for m in mods], 0)
574
+
575
+ @classmethod
576
+ def from_pretrained(cls, model, device="cuda", max_seq: int = 4096,
577
+ max_gen: int = 4096):
578
+ """Build from a transformers model (object or repo id). A quantized
579
+ checkpoint (bitsandbytes 4-bit/8-bit) is accepted directly; its
580
+ weights are dequantized to bf16 at load."""
581
+ if isinstance(model, str):
582
+ from transformers import AutoModelForCausalLM
583
+ model = AutoModelForCausalLM.from_pretrained(
584
+ model, torch_dtype=torch.bfloat16)
585
+ if "gemma4" in getattr(model.config, "model_type", ""):
586
+ return cls._from_gemma4(model, device, max_seq, max_gen)
587
+ hf = model.config
588
+ dec = model.model
589
+ attn0 = dec.layers[0].self_attn
590
+ qk_norm = hasattr(attn0, "q_norm") and attn0.q_norm is not None
591
+ if getattr(attn0.q_proj, "bias", None) is not None:
592
+ raise ValueError("attention biases are not supported")
593
+
594
+ # rope config across transformers API generations: v5 keeps theta and
595
+ # type in a rope_parameters dict; v4 used a rope_theta attribute and
596
+ # an optional rope_scaling dict.
597
+ rp = getattr(hf, "rope_parameters", None)
598
+ if not isinstance(rp, dict):
599
+ rs = getattr(hf, "rope_scaling", None)
600
+ rp = rs if isinstance(rs, dict) else {}
601
+ theta = rp.get("rope_theta")
602
+ if theta is None:
603
+ theta = getattr(hf, "rope_theta", 10000.0)
604
+ rope_type = rp.get("rope_type", rp.get("type", "default"))
605
+ if rope_type not in ("default", "llama3"):
606
+ raise ValueError(f"rope type {rope_type!r} is not supported")
607
+ # take the frequency table transformers computed (scaling included)
608
+ rot = dec.rotary_emb
609
+ att_scale = float(getattr(rot, "attention_scaling", 1.0))
610
+ if att_scale != 1.0:
611
+ raise ValueError("rope attention_scaling != 1 is not supported")
612
+ inv_freq = rot.inv_freq.detach().float()
613
+
614
+ pj = cls._proj
615
+ layers = []
616
+ for lyr in dec.layers:
617
+ a, m = lyr.self_attn, lyr.mlp
618
+ layers.append({
619
+ "wqkv": pj([a.q_proj, a.k_proj, a.v_proj]),
620
+ "wo": pj([a.o_proj]),
621
+ "wgu": pj([m.gate_proj, m.up_proj]),
622
+ "wdown": pj([m.down_proj]),
623
+ "ln1": lyr.input_layernorm.weight,
624
+ "ln2": lyr.post_attention_layernorm.weight,
625
+ "qn": a.q_norm.weight if qk_norm else None,
626
+ "kn": a.k_norm.weight if qk_norm else None,
627
+ })
628
+ weights = {"embed": dec.embed_tokens.weight, "norm": dec.norm.weight,
629
+ "lm_head": pj([model.lm_head]), "layers": layers,
630
+ "inv_freq": inv_freq}
631
+ config = {
632
+ "num_hidden_layers": hf.num_hidden_layers,
633
+ "hidden_size": hf.hidden_size,
634
+ "num_attention_heads": hf.num_attention_heads,
635
+ "num_key_value_heads": getattr(hf, "num_key_value_heads", None),
636
+ "head_dim": getattr(hf, "head_dim", None),
637
+ "intermediate_size": hf.intermediate_size,
638
+ "vocab_size": hf.vocab_size,
639
+ "rope_theta": float(theta),
640
+ "rms_norm_eps": hf.rms_norm_eps,
641
+ "qk_norm": qk_norm,
642
+ }
643
+ return cls(weights, config, device=device, max_seq=max_seq,
644
+ max_gen=max_gen)
645
+
646
+ # ------------------------------------------------------------------
647
+ @classmethod
648
+ def _from_gemma4(cls, model, device, max_seq, max_gen):
649
+ """gemma-4-E2B-it program: gemma norms (fp32, raw weight),
650
+ gelu-tanh gating, post-sublayer norm phases, per-layer-input
651
+ pathway, sliding windows, shared KV, dual head dims, softcapped
652
+ head. Scope is the E2B text model only."""
653
+ self = cls.__new__(cls)
654
+ cfg = model.config.text_config if hasattr(model.config, "text_config") \
655
+ else model.config
656
+ dec = model.model.language_model if hasattr(model.model, "language_model") \
657
+ else model.model
658
+ assert cfg.model_type == "gemma4_text", cfg.model_type
659
+ assert not cfg.enable_moe_block
660
+
661
+ self.L = int(cfg.num_hidden_layers)
662
+ self.hidden = int(cfg.hidden_size)
663
+ self.Hq = int(cfg.num_attention_heads)
664
+ self.V = int(cfg.vocab_size)
665
+ self.eps = float(cfg.rms_norm_eps)
666
+ self.qk_norm = True
667
+ self.max_seq = int(max_seq)
668
+ self.max_gen = int(max_gen)
669
+ self.device = torch.device(device)
670
+ self.tiled = False
671
+ win = int(cfg.sliding_window)
672
+ cap = float(cfg.final_logit_softcapping)
673
+ PL = int(cfg.hidden_size_per_layer_input)
674
+ dt = torch.bfloat16
675
+ dev = self.device
676
+ self._keep = []
677
+
678
+ def pack(t):
679
+ t = t.detach().to(device=dev, dtype=dt).contiguous()
680
+ self._keep.append(t)
681
+ return t
682
+
683
+ def packf(t):
684
+ t = t.detach().to(device=dev, dtype=torch.float32).contiguous()
685
+ self._keep.append(t)
686
+ return t
687
+
688
+ embed = pack(dec.embed_tokens.weight)
689
+ ple_tab = pack(dec.embed_tokens_per_layer.weight)
690
+ plm_w = pack(dec.per_layer_model_projection.weight)
691
+ pln_w = pack(dec.per_layer_projection_norm.weight)
692
+ fnorm = pack(dec.norm.weight)
693
+ rot = dec.rotary_emb
694
+ invf = {lt: packf(getattr(rot, f"{lt}_inv_freq").float())
695
+ for lt in set(cfg.layer_types)}
696
+ ascale = {lt: float(getattr(rot, f"{lt}_attention_scaling"))
697
+ for lt in set(cfg.layer_types)}
698
+
699
+ layers = []
700
+ for li, lyr in enumerate(dec.layers):
701
+ a = lyr.self_attn
702
+ D_l = int(a.head_dim)
703
+ shared = bool(a.is_kv_shared_layer)
704
+ ent = {
705
+ "D": D_l, "shared": shared,
706
+ "lscale": float(lyr.layer_scalar.float().item()),
707
+ "type": cfg.layer_types[li],
708
+ "I": int(lyr.mlp.intermediate_size),
709
+ "qn": pack(a.q_norm.weight),
710
+ "o": pack(a.o_proj.weight),
711
+ "ln_in": pack(lyr.input_layernorm.weight),
712
+ "ln_pa": pack(lyr.post_attention_layernorm.weight),
713
+ "ln_pf": pack(lyr.pre_feedforward_layernorm.weight),
714
+ "ln_ff": pack(lyr.post_feedforward_layernorm.weight),
715
+ "ln_pl": pack(lyr.post_per_layer_input_norm.weight),
716
+ "plig": pack(lyr.per_layer_input_gate.weight),
717
+ "plpr": pack(lyr.per_layer_projection.weight),
718
+ "wgu": pack(torch.cat([lyr.mlp.gate_proj.weight,
719
+ lyr.mlp.up_proj.weight], 0)),
720
+ "wdn": pack(lyr.mlp.down_proj.weight),
721
+ }
722
+ if shared:
723
+ ent["wqkv"] = pack(a.q_proj.weight)
724
+ ent["kn"] = None
725
+ else:
726
+ ent["wqkv"] = pack(torch.cat(
727
+ [a.q_proj.weight, a.k_proj.weight, a.v_proj.weight], 0))
728
+ ent["kn"] = pack(a.k_norm.weight)
729
+ ent["kc"] = torch.zeros(self.max_seq, D_l, dtype=dt,
730
+ device=dev)
731
+ ent["vc"] = torch.zeros(self.max_seq, D_l, dtype=dt,
732
+ device=dev)
733
+ self._keep += [ent["kc"], ent["vc"]]
734
+ layers.append(ent)
735
+ # shared layers read the last non-shared layer of their type
736
+ src = {}
737
+ for li, ent in enumerate(layers):
738
+ if not ent["shared"]:
739
+ src[ent["type"]] = ent
740
+ for ent in layers:
741
+ if ent["shared"]:
742
+ ent["kc"] = src[ent["type"]]["kc"]
743
+ ent["vc"] = src[ent["type"]]["vc"]
744
+
745
+ maxD = max(e["D"] for e in layers)
746
+ maxI = max(e["I"] for e in layers)
747
+ self._maxk = max(self.hidden, self.Hq * maxD, maxI)
748
+ self._chunk_m = 1
749
+ self._chunk = 128
750
+ self._maxch = (self.max_seq + self._chunk - 1) // self._chunk
751
+ self._hidden = torch.empty(self.hidden, dtype=dt, device=dev)
752
+ self._tmp = torch.empty(self.hidden, dtype=dt, device=dev)
753
+ self._qkv = torch.empty((self.Hq + 2) * maxD, dtype=dt, device=dev)
754
+ self._gu = torch.empty(2 * maxI, dtype=dt, device=dev)
755
+ self._t256 = torch.empty(PL, dtype=dt, device=dev)
756
+ self._ctxraw = torch.empty(self.L * PL, dtype=dt, device=dev)
757
+ self._pletok = torch.empty(self.L * PL, dtype=dt, device=dev)
758
+ self._ple = torch.empty(self.L * PL, dtype=dt, device=dev)
759
+ self._logits = torch.empty(self.V, dtype=torch.float32, device=dev)
760
+ self._partials = torch.empty(
761
+ self.Hq * self._maxch * (maxD + 2), dtype=torch.float32,
762
+ device=dev)
763
+ self._token = torch.zeros(1, dtype=torch.int32, device=dev)
764
+ self._prompt_in = torch.zeros(self.max_seq, dtype=torch.int32,
765
+ device=dev)
766
+ self._tokens_out = torch.zeros(self.max_gen, dtype=torch.int32,
767
+ device=dev)
768
+ self._bar = torch.zeros(34, dtype=torch.int32, device=dev)
769
+ probe = torch.zeros(1, 16, dtype=torch.int64, device=dev)
770
+ self._nblocks = int(ops.mak_num_blocks(probe, self._maxk))
771
+ self._parts = torch.zeros(self._nblocks, dtype=torch.int64,
772
+ device=dev)
773
+ try:
774
+ self._bw_per_sm = float(ops.mak_bw_per_sm(probe))
775
+ except (AttributeError, RuntimeError):
776
+ self._bw_per_sm = 0.0
777
+ self._invf = invf[layers[0]["type"]]
778
+ self.Hkv, self.D, self.I = 1, maxD, maxI
779
+ self.theta = 0.0
780
+
781
+ emb_scale = float(torch.tensor(self.hidden ** 0.5,
782
+ dtype=torch.float32).to(dt))
783
+ ple_scale = float(torch.tensor(PL ** 0.5,
784
+ dtype=torch.float32).to(dt))
785
+ proj_scale = float(torch.tensor(self.hidden ** -0.5,
786
+ dtype=torch.float32))
787
+ hid = self._hidden.data_ptr()
788
+ tmp = self._tmp.data_ptr()
789
+ rows, names = [], []
790
+ rows.append(_row(_OP_EMBED, p1=ple_tab.data_ptr(),
791
+ p2=embed.data_ptr(), p3=hid,
792
+ p4=self._parts.data_ptr(),
793
+ p5=self._prompt_in.data_ptr(),
794
+ p6=self._token.data_ptr(),
795
+ n=self._tokens_out.data_ptr(), k=self.hidden,
796
+ it=self._pletok.data_ptr(),
797
+ hkv=self.L * PL, f0=emb_scale,
798
+ i0=_fbits(ple_scale)))
799
+ names.append("embed")
800
+ rows.append(_row(_OP_GEMV, p1=hid, p2=plm_w.data_ptr(),
801
+ p3=self._ctxraw.data_ptr(), n=self.L * PL,
802
+ k=self.hidden, it=_IT_NONE, epi=_EP_STORE))
803
+ names.append("plm")
804
+ rows.append(_row(8, p1=self._ctxraw.data_ptr(),
805
+ p2=pln_w.data_ptr(), p3=self._ple.data_ptr(),
806
+ p4=self._pletok.data_ptr(), n=self.L, k=PL,
807
+ f0=self.eps, i0=_fbits(proj_scale)))
808
+ names.append("plemix")
809
+
810
+ for li, e in enumerate(layers):
811
+ D_l, qdim = e["D"], self.Hq * e["D"]
812
+ lt = e["type"]
813
+ w_l = win if lt == "sliding_attention" else 0
814
+ asb = _fbits(ascale[lt]) if ascale[lt] != 1.0 else 0
815
+ flags = 1 | 2 | (0 if e["shared"] else 4) | \
816
+ (8 if e["shared"] else 0)
817
+ n_qkv = qdim if e["shared"] else qdim + 2 * D_l
818
+ rows.append(_row(_OP_GEMV, p1=hid, p2=e["wqkv"].data_ptr(),
819
+ p3=self._qkv.data_ptr(),
820
+ p4=e["ln_in"].data_ptr(), n=n_qkv,
821
+ k=self.hidden, it=_IT_RMSNORM_G,
822
+ epi=_EP_STORE, f0=self.eps))
823
+ names.append(f"L{li}.qkv")
824
+ rows.append(_row(_OP_ATTN, p1=self._qkv.data_ptr(),
825
+ p2=e["kc"].data_ptr(), p3=e["vc"].data_ptr(),
826
+ p4=self._partials.data_ptr(),
827
+ p5=e["qn"].data_ptr(),
828
+ p6=e["kn"].data_ptr() if e["kn"] is not None
829
+ else 0,
830
+ n=self.max_seq,
831
+ k=flags | (w_l << 16), it=_fbits(self.eps),
832
+ epi=invf[lt].data_ptr(), hq=self.Hq,
833
+ hkv=1 | (self._chunk << 16), d=D_l, f0=1.0,
834
+ i0=self._maxch | (asb << 32)))
835
+ names.append(f"L{li}.attn")
836
+ rows.append(_row(_OP_GEMV, p1=self._partials.data_ptr(),
837
+ p2=e["o"].data_ptr(), p3=tmp,
838
+ n=self.hidden, k=qdim, it=_IT_ATTNFIN,
839
+ epi=_EP_STORE, hq=self.Hq,
840
+ hkv=self._chunk << 16, d=D_l,
841
+ i0=self._maxch | (w_l << 16)))
842
+ names.append(f"L{li}.o")
843
+ rows.append(_row(7, p1=tmp, p2=e["ln_pa"].data_ptr(), p3=hid,
844
+ n=self.hidden, f0=self.eps, i0=_fbits(1.0)))
845
+ names.append(f"L{li}.nr_attn")
846
+ rows.append(_row(_OP_GEMV, p1=hid, p2=e["wgu"].data_ptr(),
847
+ p3=self._gu.data_ptr(),
848
+ p4=e["ln_pf"].data_ptr(), n=2 * e["I"],
849
+ k=self.hidden, it=_IT_RMSNORM_G,
850
+ epi=_EP_STORE, f0=self.eps))
851
+ names.append(f"L{li}.gateup")
852
+ rows.append(_row(_OP_GEMV, p1=self._gu.data_ptr(),
853
+ p2=e["wdn"].data_ptr(), p3=tmp,
854
+ n=self.hidden, k=e["I"], it=_IT_GELU_GLU,
855
+ epi=_EP_STORE))
856
+ names.append(f"L{li}.down")
857
+ rows.append(_row(7, p1=tmp, p2=e["ln_ff"].data_ptr(), p3=hid,
858
+ n=self.hidden, f0=self.eps, i0=_fbits(1.0)))
859
+ names.append(f"L{li}.nr_ffw")
860
+ rows.append(_row(_OP_GEMV, p1=hid, p2=e["plig"].data_ptr(),
861
+ p3=self._t256.data_ptr(),
862
+ p6=self._ple.data_ptr() + li * PL * 2,
863
+ n=PL, k=self.hidden, it=_IT_NONE,
864
+ epi=_EP_GELU_PLE))
865
+ names.append(f"L{li}.plig")
866
+ rows.append(_row(_OP_GEMV, p1=self._t256.data_ptr(),
867
+ p2=e["plpr"].data_ptr(), p3=tmp,
868
+ n=self.hidden, k=PL, it=_IT_NONE,
869
+ epi=_EP_STORE))
870
+ names.append(f"L{li}.plproj")
871
+ rows.append(_row(7, p1=tmp, p2=e["ln_pl"].data_ptr(), p3=hid,
872
+ n=self.hidden, f0=self.eps,
873
+ i0=_fbits(e["lscale"])))
874
+ names.append(f"L{li}.nr_ple")
875
+
876
+ rows.append(_row(_OP_GEMV, p1=hid, p2=embed.data_ptr(),
877
+ p3=self._logits.data_ptr(),
878
+ p4=fnorm.data_ptr(),
879
+ p6=self._parts.data_ptr(), n=self.V,
880
+ k=self.hidden, it=_IT_RMSNORM_G,
881
+ epi=_EP_F32_AMAX_CAP, hkv=1, f0=self.eps,
882
+ i0=_fbits(cap)))
883
+ names.append("lm_head")
884
+ rows.append(_row(_OP_ARGMAX_FIN, p1=self._parts.data_ptr(),
885
+ p3=self._token.data_ptr(),
886
+ p4=self._tokens_out.data_ptr()))
887
+ names.append("argmax.fin")
888
+ self._prog = torch.tensor(rows, dtype=torch.int64, device=dev)
889
+ self.phase_names = names
890
+ self._gemma = True
891
+ self._batch = 0
892
+ return self
893
+
894
+ # ------------------------------------------------------------------
895
+ def decode_step(self, token: int, pos: int, phased: bool = False):
896
+ """One decode step; returns the live fp32 logits buffer [V]."""
897
+ if pos >= self.max_seq:
898
+ raise ValueError("pos exceeds max_seq")
899
+ self._token.fill_(int(token))
900
+ if phased:
901
+ ops.mak_run_phased(self._prog, pos, self.max_gen - 1, False,
902
+ self._maxk)
903
+ else:
904
+ ops.mak_run(self._prog, self._bar, pos, self.max_gen - 1,
905
+ self._maxk)
906
+ return self._logits[:self.V]
907
+
908
+ def decode_step_timed(self, token: int, pos: int):
909
+ """One phase-per-launch step; returns (logits, per-phase ms)."""
910
+ self._token.fill_(int(token))
911
+ ms = ops.mak_run_phased(self._prog, pos, self.max_gen - 1, True,
912
+ self._maxk)
913
+ return self._logits[:self.V], ms
914
+
915
+ def prefill(self, ids: Sequence[int], phased: bool = False):
916
+ """Consume a prompt; returns the last position's logits. Runs in
917
+ chunks of up to chunk_m tokens with weight reads amortized across
918
+ the chunk (bitwise identical to token-by-token consumption)."""
919
+ P = len(ids)
920
+ if phased:
921
+ for i, t in enumerate(ids):
922
+ self.decode_step(int(t), i, phased=True)
923
+ return self._logits
924
+ self._prompt_in[:P].copy_(torch.tensor(ids, dtype=torch.int32))
925
+ ops.mak_run_seq(self._prog, self._bar, 0, P, 1 - P, P, self._maxk,
926
+ self._chunk_m)
927
+ return self._logits[:self.V]
928
+
929
+ def generate(self, prompt_ids: Sequence[int], max_new: int,
930
+ single_launch: bool = True) -> List[int]:
931
+ """Greedy generation. With single_launch (default) the entire call,
932
+ prompt consumption included, is one kernel launch: the in-kernel step
933
+ loop reads prompt tokens from a staged device buffer, then feeds each
934
+ step's argmax to the next step's embedding read. With
935
+ single_launch=False the same computation runs as one launch per
936
+ token; the two paths produce identical tokens.
937
+ """
938
+ P = len(prompt_ids)
939
+ if max_new < 1 or max_new > self.max_gen:
940
+ raise ValueError("max_new out of range")
941
+ if P < 1 or P + max_new > self.max_seq:
942
+ raise ValueError("sequence exceeds max_seq")
943
+ if single_launch:
944
+ self._prompt_in[:P].copy_(
945
+ torch.tensor(prompt_ids, dtype=torch.int32))
946
+ ops.mak_run_seq(self._prog, self._bar, 0, P + max_new - 1,
947
+ 1 - P, P, self._maxk, self._chunk_m)
948
+ return self._tokens_out[:max_new].cpu().tolist()
949
+ pos = 0
950
+ for t in prompt_ids[:-1]:
951
+ self._token.fill_(int(t))
952
+ ops.mak_run(self._prog, self._bar, pos, self.max_gen - 1,
953
+ self._maxk)
954
+ pos += 1
955
+ self._token.fill_(int(prompt_ids[-1]))
956
+ ops.mak_run(self._prog, self._bar, pos, 0, self._maxk)
957
+ pos += 1
958
+ if max_new > 1:
959
+ ops.mak_run_steps(self._prog, self._bar, pos, max_new - 1, 1,
960
+ self._maxk)
961
+ return self._tokens_out[:max_new].cpu().tolist()
962
+
963
+
964
+
build/torch212-cxx11-cu126-x86_64-linux/_model_as_a_kernel_cuda_e33875a.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e7e3d83357592292c3b6901445991763e8e7c61f66373bb2191da1bfd2d6860d
3
+ size 3845320
build/torch212-cxx11-cu126-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _model_as_a_kernel_cuda_e33875a
3
+ ops = torch.ops._model_as_a_kernel_cuda_e33875a
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_model_as_a_kernel_cuda_e33875a::{op_name}"
build/torch212-cxx11-cu126-x86_64-linux/metadata.json ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "model-as-a-kernel",
3
+ "id": "_model_as_a_kernel_cuda_e33875a",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "python-depends": [],
7
+ "backend": {
8
+ "type": "cuda",
9
+ "archs": [
10
+ "8.0",
11
+ "8.6",
12
+ "8.9",
13
+ "9.0"
14
+ ]
15
+ },
16
+ "digest": {
17
+ "algorithm": "sha256",
18
+ "files": {
19
+ "__init__.py": "/YX5/zzAOyJacQ7H+j8zaq+xjWyy8w6r0j+ugE1EUs8=",
20
+ "_model_as_a_kernel_cuda_e33875a.abi3.so": "5+PYM1dZIpLDtpAURZkXY+jnxh9mNzuyGR2hv9LWhg0=",
21
+ "_ops.py": "Pqj4WKtwPyeDG9grQnzLget4g1jsGUdR7ZMa44e4rhs=",
22
+ "model_as_a_kernel/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
23
+ }
24
+ },
25
+ "provenance": {
26
+ "kernel-builder": {
27
+ "version": "0.17.0-dev0",
28
+ "sha": "05f1ed487f599a320311aa26076ec0ad290c3f68",
29
+ "dirty": false
30
+ },
31
+ "kernel": {
32
+ "sha": "e33875ae4eba42fd136a89c388e806281cd4759c",
33
+ "dirty": false
34
+ }
35
+ }
36
+ }
build/torch212-cxx11-cu126-x86_64-linux/model_as_a_kernel/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import importlib.util
3
+ import sys
4
+ from pathlib import Path
5
+ from types import ModuleType
6
+
7
+
8
+ def _import_from_path(file_path: Path) -> ModuleType:
9
+ # We cannot use the module name as-is, after adding it to `sys.modules`,
10
+ # it would also be used for other imports. So, we make a module name that
11
+ # depends on the path for it to be unique using the hex-encoded hash of
12
+ # the path.
13
+ path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
+ module_name = path_hash
15
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
16
+ if spec is None:
17
+ raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
+ module = importlib.util.module_from_spec(spec)
19
+ if module is None:
20
+ raise ImportError(f"Cannot load module {module_name} from spec")
21
+ sys.modules[module_name] = module
22
+ spec.loader.exec_module(module) # type: ignore
23
+ return module
24
+
25
+
26
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
build/torch212-cxx11-cu130-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,964 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """model-as-a-kernel: an entire llama-family decode step as one kernel launch.
2
+
3
+ A phase interpreter runs the whole forward pass (embedding, every layer's
4
+ norms, QKV, rope, attention over the KV cache, SwiGLU MLP, the LM head, and
5
+ the greedy argmax) inside a single persistent kernel, with a software grid
6
+ barrier between phases. The in-kernel step loop makes an entire greedy
7
+ generation, prompt consumption included, one kernel launch: each step's
8
+ argmax feeds the next step's embedding read without leaving the device.
9
+ """
10
+ import os
11
+ import struct
12
+ from typing import List, Optional, Sequence
13
+
14
+ import torch
15
+
16
+ try:
17
+ from ._ops import ops
18
+ except ImportError: # local development build (torch.utils.cpp_extension)
19
+ class _LocalOps:
20
+ def __getattr__(self, name):
21
+ return getattr(torch.ops.mak_ext, name)
22
+
23
+ ops = _LocalOps()
24
+
25
+ __all__ = ["MegaModel"]
26
+
27
+ _OP_EMBED, _OP_GEMV, _OP_QKV_POST, _OP_ATTN = 0, 1, 2, 3
28
+ _OP_ARGMAX_PART, _OP_ARGMAX_FIN = 4, 5
29
+ _OP_KV_APPEND, _OP_NORMRES, _OP_PLEMIX = 6, 7, 8
30
+ _OP_GEMV_PLAIN, _OP_NORMB, _OP_GLUB, _OP_ATTNFINB = 9, 10, 11, 12
31
+ _OP_GEMV_Q4 = 13
32
+ _IT_NONE, _IT_RMSNORM, _IT_SWIGLU, _IT_ATTNFIN = 0, 1, 2, 3
33
+ _IT_RMSNORM_G, _IT_GELU_GLU = 4, 5
34
+ _EP_GELU_PLE, _EP_F32_AMAX_CAP = 5, 6
35
+ _ITF_REDUCE, _ITF_WRITEBACK = 8, 16
36
+ _EP_STORE, _EP_RESID, _EP_F32, _EP_F32_AMAX, _EP_PARTIAL = 0, 1, 2, 3, 4
37
+ _NSLICE = 4
38
+ # Widest [B][K] input panel (bf16 elements) a batched projection stages in
39
+ # shared before it spills to the global-scratch path. 16384 (32 KB) keeps
40
+ # two blocks resident per SM; the persistent kernel shares one occupancy
41
+ # across all phases, so a wider panel would drop every phase to one block
42
+ # per SM, which measures slower than reading the input from L2.
43
+ _STAGE_BUDGET = 16384
44
+
45
+
46
+ def _fbits(f: float) -> int:
47
+ return struct.unpack("<i", struct.pack("<f", float(f)))[0]
48
+
49
+
50
+ def _row(op, p1=0, p2=0, p3=0, p4=0, p5=0, p6=0, n=0, k=0, it=0, epi=0,
51
+ hq=0, hkv=0, d=0, f0=0.0, i0=0):
52
+ return [int(op), int(p1), int(p2), int(p3), int(p4), int(p5), int(p6),
53
+ int(n), int(k), int(it), int(epi), int(hq), int(hkv), int(d),
54
+ _fbits(f0), int(i0)]
55
+
56
+
57
+ class MegaModel:
58
+ """A packed llama-family decoder whose decode step is one kernel launch.
59
+
60
+ Supported architecture: RMSNorm decoder blocks with rotary attention
61
+ (GQA, optional Qwen3-style per-head qk RMSNorm), SwiGLU MLP, no attention
62
+ or MLP biases, plain (unscaled) rope, batch size 1. This covers SmolLM2,
63
+ TinyLlama, Qwen3 dense, Llama-class checkpoints without rope scaling,
64
+ and similar models.
65
+ """
66
+
67
+ def __init__(self, weights: dict, config: dict, device="cuda",
68
+ max_seq: int = 4096, max_gen: int = 4096):
69
+ cfg = dict(config)
70
+ self.L = int(cfg["num_hidden_layers"])
71
+ self.hidden = int(cfg["hidden_size"])
72
+ self.Hq = int(cfg["num_attention_heads"])
73
+ self.Hkv = int(cfg.get("num_key_value_heads") or self.Hq)
74
+ self.D = int(cfg.get("head_dim") or self.hidden // self.Hq)
75
+ self.I = int(cfg["intermediate_size"])
76
+ self.V = int(cfg["vocab_size"])
77
+ self.theta = float(cfg.get("rope_theta", 10000.0))
78
+ self.eps = float(cfg.get("rms_norm_eps", 1e-6))
79
+ self.qk_norm = bool(cfg.get("qk_norm", False))
80
+ self.max_seq = int(max_seq)
81
+ self.max_gen = int(max_gen)
82
+ self.device = torch.device(device)
83
+
84
+ if self.D % 2 or self.D > 256:
85
+ raise ValueError("head_dim must be even and <= 256")
86
+ if self.hidden % 8 or self.I % 8 or (self.Hq * self.D) % 8:
87
+ raise ValueError(
88
+ "hidden, intermediate, and Hq*head_dim must be multiples of 8")
89
+ if self.Hq % self.Hkv:
90
+ raise ValueError("num_attention_heads must be divisible by num_key_value_heads")
91
+
92
+ dt = torch.bfloat16
93
+ dev = self.device
94
+
95
+ def pack(t):
96
+ t = t.detach().to(device=dev, dtype=dt).contiguous()
97
+ self._keep.append(t)
98
+ return t
99
+
100
+ # Row-tile-of-8 weight layout [N/8][K/8][8][8] (MAK_TILED=1): the
101
+ # eight warps of a block read one contiguous 4KB stream per step
102
+ # instead of eight 4KB-strided pieces. Measured slower on every
103
+ # card (H200 +15 percent, RTX PRO +9, Ada +14 — per-warp stream
104
+ # locality beats per-block contiguity on all of them), so the
105
+ # default is the plain layout; the flag rides in the program and
106
+ # both layouts compute identical bits.
107
+ env_t = os.environ.get("MAK_TILED", "").strip()
108
+ self.tiled = env_t == "1"
109
+
110
+ def pack_tiled(t):
111
+ t = t.detach().to(device=dev, dtype=dt).contiguous()
112
+ if not self.tiled:
113
+ self._keep.append(t)
114
+ return t
115
+ n, k = t.shape
116
+ assert k % 8 == 0
117
+ n8 = (n + 7) // 8 * 8
118
+ if n8 != n:
119
+ t = torch.cat(
120
+ [t, torch.zeros(n8 - n, k, dtype=dt, device=dev)], 0)
121
+ t = t.view(n8 // 8, 8, k // 8, 8).permute(0, 2, 1, 3).contiguous()
122
+ self._keep.append(t)
123
+ return t
124
+
125
+ self._keep: List[torch.Tensor] = []
126
+ embed = pack(weights["embed"])
127
+ final_norm = pack(weights["norm"])
128
+ lm_head = weights["lm_head"] # packed below (dense-tiled or nf4)
129
+ inv_freq = weights.get("inv_freq")
130
+ if inv_freq is None:
131
+ d_idx = torch.arange(0, self.D, 2, dtype=torch.float32)
132
+ inv_freq = 1.0 / (self.theta ** (d_idx / self.D))
133
+ inv_freq = inv_freq.detach().to(device=dev,
134
+ dtype=torch.float32).contiguous()
135
+ if inv_freq.numel() != self.D // 2:
136
+ raise ValueError("inv_freq length must be head_dim / 2")
137
+ self._keep.append(inv_freq)
138
+ self._invf = inv_freq
139
+
140
+ qdim, kvdim = self.Hq * self.D, self.Hkv * self.D
141
+
142
+ def _is_q4(w):
143
+ return isinstance(w, dict) and w.get("q4")
144
+
145
+ def pack_w(w):
146
+ # nf4-packed weights move to the device intact; dense weights take
147
+ # the usual (optionally row-tiled) bf16 packing.
148
+ if _is_q4(w):
149
+ pk = w["packed"].to(dev).contiguous()
150
+ am = w["absmax"].to(device=dev, dtype=torch.float32).contiguous()
151
+ self._keep.append(pk)
152
+ self._keep.append(am)
153
+ return {"q4": True, "packed": pk, "absmax": am,
154
+ "N": int(w["N"]), "K": int(w["K"])}
155
+ return pack_tiled(w)
156
+
157
+ def _wshape(w, expected):
158
+ got = (int(w["N"]), int(w["K"])) if _is_q4(w) else tuple(w.shape)
159
+ assert got == expected, (got, expected)
160
+
161
+ self._has_q4 = False
162
+ self._layers = []
163
+ for lw in weights["layers"]:
164
+ _wshape(lw["wqkv"], (qdim + 2 * kvdim, self.hidden))
165
+ _wshape(lw["wo"], (self.hidden, qdim))
166
+ _wshape(lw["wgu"], (2 * self.I, self.hidden))
167
+ _wshape(lw["wdown"], (self.hidden, self.I))
168
+ layer = {
169
+ "wqkv": pack_w(lw["wqkv"]), "wo": pack_w(lw["wo"]),
170
+ "wgu": pack_w(lw["wgu"]), "wdown": pack_w(lw["wdown"]),
171
+ "ln1": pack(lw["ln1"]), "ln2": pack(lw["ln2"]),
172
+ "qn": pack(lw["qn"]) if self.qk_norm else None,
173
+ "kn": pack(lw["kn"]) if self.qk_norm else None,
174
+ }
175
+ self._has_q4 |= any(_is_q4(layer[n]) for n in
176
+ ("wqkv", "wo", "wgu", "wdown"))
177
+ self._layers.append(layer)
178
+ assert len(self._layers) == self.L
179
+ assert embed.shape == (self.V, self.hidden)
180
+ lm_head = pack_w(lm_head)
181
+ self._has_q4 |= _is_q4(lm_head)
182
+ if not _is_q4(lm_head):
183
+ assert lm_head.numel() >= self.V * self.hidden # row-tiled, padded
184
+ self._w_embed, self._w_fnorm, self._w_lmhead = (embed, final_norm,
185
+ lm_head)
186
+ self._qdim, self._kvdim = qdim, kvdim
187
+
188
+ # Working buffers. Pointers to these are baked into the program, so
189
+ # they (like the packed weights) live for the model's lifetime.
190
+ self._maxk = max(self.hidden, qdim, self.I)
191
+ # prefill chunk size, bounded by the staging shared-memory budget
192
+ # (16K bf16 elements; the cp.async weight ring rides alongside)
193
+ self._chunk_m = max(1, min(8, 16384 // self._maxk))
194
+ cm = 8 # buffers sized for the maximum chunk
195
+ self._hidden = torch.empty(cm * self.hidden, dtype=dt, device=dev)
196
+ self._qkv = torch.empty(cm * (qdim + 2 * kvdim), dtype=dt, device=dev)
197
+ self._gu = torch.empty(cm * 2 * self.I, dtype=dt, device=dev)
198
+ self._logits = torch.empty(self.V, dtype=torch.float32, device=dev)
199
+ # attention chunk length (kernel maximum 128); shorter chunks give
200
+ # the attention phase more grid parallelism at the cost of more
201
+ # softmax partials to finalize
202
+ env_ch = os.environ.get("MAK_CHUNK", "").strip()
203
+ self._chunk = int(env_ch) if env_ch in ("32", "64", "128") else 128
204
+ self._maxch = (self.max_seq + self._chunk - 1) // self._chunk
205
+ self._partials = torch.empty(
206
+ cm * self.Hq * self._maxch * (self.D + 2), dtype=torch.float32,
207
+ device=dev)
208
+ self._kcache = torch.zeros(self.L, self.Hkv, self.max_seq, self.D,
209
+ dtype=dt, device=dev)
210
+ self._vcache = torch.zeros(self.L, self.Hkv, self.max_seq, self.D,
211
+ dtype=dt, device=dev)
212
+ self._token = torch.zeros(1, dtype=torch.int32, device=dev)
213
+ self._prompt_in = torch.zeros(self.max_seq, dtype=torch.int32,
214
+ device=dev)
215
+ self._tokens_out = torch.zeros(self.max_gen, dtype=torch.int32,
216
+ device=dev)
217
+ self._bar = torch.zeros(34, dtype=torch.int32, device=dev)
218
+
219
+ probe = torch.zeros(1, 16, dtype=torch.int64, device=dev)
220
+ self._nblocks = int(ops.mak_num_blocks(probe, self._maxk))
221
+ self._parts = torch.zeros(self._nblocks, dtype=torch.int64, device=dev)
222
+ try:
223
+ self._bw_per_sm = float(ops.mak_bw_per_sm(probe))
224
+ except (AttributeError, RuntimeError):
225
+ self._bw_per_sm = 0.0
226
+
227
+ self._batch = 0
228
+ self._pos_b = None
229
+ self._kv_bstride = 0
230
+ self._gemma = False
231
+ # transformed-input scratch: the nf4 and wide-batch paths write the
232
+ # dense bf16 input here (one row per active token) for the following
233
+ # plain/quant GEMV; sized for a prefill chunk (up to 8 rows)
234
+ self._xg = torch.empty(cm * self._maxk, dtype=dt, device=dev)
235
+ self._build_programs()
236
+
237
+ # ------------------------------------------------------------------
238
+ def _kvp(self, cache, li: int, b: int) -> int:
239
+ t = cache[li]
240
+ return (t[b] if t.dim() == 4 else t).data_ptr()
241
+
242
+ def _build_rows(self, kv_slice: int = 0, batch: bool = False,
243
+ big: bool = False):
244
+ """Program rows for the llama-family path. kv_slice selects the
245
+ batch slice the KV pointers address (per-sequence prefill);
246
+ batch=True emits the batched-decode tail (per-row fp32 logits and
247
+ explicit argmax phases) instead of the fused LM-head amax. In batch
248
+ mode each projection stays fused (input staged in shared) when its
249
+ [B][K] panel fits the budget; the projections whose K is too wide
250
+ transform into a global scratch and read it with a plain GEMV."""
251
+ _tbit = (1 << 20) if self.tiled else 0 # GEMV weight layout flag
252
+ qdim, kvdim = self._qdim, self._kvdim
253
+ embed, lm_head = self._w_embed, self._w_lmhead
254
+ final_norm = self._w_fnorm
255
+ xg = self._xg.data_ptr()
256
+ B = self._batch if batch else 1
257
+ rows, names = [], []
258
+ staged_elems = [0] # widest [B][K] panel any fused projection stages
259
+
260
+ def fits(K):
261
+ return B * K <= _STAGE_BUDGET
262
+
263
+ def note(K):
264
+ staged_elems[0] = max(staged_elems[0], B * K)
265
+
266
+ def _q4(w):
267
+ return isinstance(w, dict) and w.get("q4")
268
+
269
+ def _wgemv(out, w, N, K, epi, resid):
270
+ # the GEMV after a transform-to-scratch phase: nf4-dequant when
271
+ # the weight is packed, otherwise a plain bf16 GEMV
272
+ if _q4(w):
273
+ rows.append(_row(_OP_GEMV_Q4, p1=xg, p2=w["packed"].data_ptr(),
274
+ p3=out, p5=resid, p6=w["absmax"].data_ptr(),
275
+ n=N, k=K, epi=epi))
276
+ else:
277
+ rows.append(_row(_OP_GEMV_PLAIN, p1=xg, p2=w.data_ptr(),
278
+ p3=out, p5=resid, n=N, k=K, it=_tbit,
279
+ epi=epi))
280
+
281
+ def norm_gemv(name, gamma, w, out, N, K, epi, inp, resid=0,
282
+ variant=_IT_RMSNORM):
283
+ if _q4(w) or (big and not fits(K)):
284
+ rows.append(_row(_OP_NORMB, p1=inp, p2=gamma, p3=xg, k=K,
285
+ it=variant, f0=self.eps))
286
+ names.append(name + ".norm")
287
+ _wgemv(out, w, N, K, epi, resid)
288
+ names.append(name)
289
+ else:
290
+ note(K)
291
+ rows.append(_row(_OP_GEMV, p1=inp, p2=w.data_ptr(), p3=out,
292
+ p4=gamma, p5=resid, n=N, k=K,
293
+ it=variant | _tbit, epi=epi, f0=self.eps))
294
+ names.append(name)
295
+
296
+ def glu_gemv(name, gu, w, out, resid, variant=_IT_SWIGLU):
297
+ if _q4(w) or (big and not fits(self.I)):
298
+ rows.append(_row(_OP_GLUB, p1=gu, p3=xg, k=self.I, it=variant))
299
+ names.append(name + ".glu")
300
+ _wgemv(out, w, self.hidden, self.I, _EP_RESID, resid)
301
+ names.append(name)
302
+ else:
303
+ note(self.I)
304
+ rows.append(_row(_OP_GEMV, p1=gu, p2=w.data_ptr(), p3=out,
305
+ p5=resid, n=self.hidden, k=self.I,
306
+ it=variant | _tbit, epi=_EP_RESID))
307
+ names.append(name)
308
+
309
+ def attn_gemv(name, w, out, resid):
310
+ if _q4(w) or (big and not fits(qdim)):
311
+ rows.append(_row(_OP_ATTNFINB, p1=self._partials.data_ptr(),
312
+ p3=xg, k=qdim, hq=self.Hq,
313
+ hkv=self._chunk << 16, d=self.D,
314
+ i0=self._maxch))
315
+ names.append(name + ".fin")
316
+ _wgemv(out, w, self.hidden, qdim, _EP_RESID, resid)
317
+ names.append(name)
318
+ else:
319
+ note(qdim)
320
+ rows.append(_row(_OP_GEMV, p1=self._partials.data_ptr(),
321
+ p2=w.data_ptr(), p3=out, p5=resid,
322
+ n=self.hidden, k=qdim,
323
+ it=_IT_ATTNFIN | _tbit, epi=_EP_RESID,
324
+ hq=self.Hq, hkv=self._chunk << 16, d=self.D,
325
+ i0=self._maxch))
326
+ names.append(name)
327
+
328
+ rows.append(_row(_OP_EMBED, p2=embed.data_ptr(),
329
+ p3=self._hidden.data_ptr(),
330
+ p4=self._parts.data_ptr(),
331
+ p5=self._prompt_in.data_ptr(),
332
+ p6=self._token.data_ptr(),
333
+ n=self._tokens_out.data_ptr(), k=self.hidden))
334
+ names.append("embed")
335
+ scale = 1.0 / (self.D ** 0.5)
336
+ hid = self._hidden.data_ptr()
337
+ for li, lw in enumerate(self._layers):
338
+ kc = self._kvp(self._kcache, li, kv_slice)
339
+ vc = self._kvp(self._vcache, li, kv_slice)
340
+ norm_gemv(f"L{li}.qkv", lw["ln1"].data_ptr(),
341
+ lw["wqkv"], self._qkv.data_ptr(),
342
+ qdim + 2 * kvdim, self.hidden, _EP_STORE, hid)
343
+ rows.append(_row(6, p1=self._qkv.data_ptr(), p2=kc, p3=vc,
344
+ p6=lw["kn"].data_ptr() if self.qk_norm else 0,
345
+ n=self.max_seq, k=1 if self.qk_norm else 0,
346
+ it=_fbits(self.eps),
347
+ epi=self._invf.data_ptr(),
348
+ hq=self.Hq, hkv=self.Hkv, d=self.D))
349
+ names.append(f"L{li}.kvappend")
350
+ rows.append(_row(_OP_ATTN, p1=self._qkv.data_ptr(), p2=kc,
351
+ p3=vc, p4=self._partials.data_ptr(),
352
+ p5=lw["qn"].data_ptr() if self.qk_norm else 0,
353
+ p6=lw["kn"].data_ptr() if self.qk_norm else 0,
354
+ n=self.max_seq, k=1 if self.qk_norm else 0,
355
+ it=_fbits(self.eps),
356
+ epi=self._invf.data_ptr(),
357
+ hq=self.Hq,
358
+ hkv=self.Hkv | (self._chunk << 16),
359
+ d=self.D, f0=scale, i0=self._maxch))
360
+ names.append(f"L{li}.attn")
361
+ attn_gemv(f"L{li}.o", lw["wo"], hid, hid)
362
+ norm_gemv(f"L{li}.gateup", lw["ln2"].data_ptr(),
363
+ lw["wgu"], self._gu.data_ptr(), 2 * self.I,
364
+ self.hidden, _EP_STORE, hid)
365
+ glu_gemv(f"L{li}.down", self._gu.data_ptr(), lw["wdown"], hid, hid)
366
+ if batch or _q4(lm_head):
367
+ norm_gemv("lm_head", final_norm.data_ptr(), lm_head,
368
+ self._logits.data_ptr(), self.V, self.hidden, _EP_F32,
369
+ hid)
370
+ rows.append(_row(_OP_ARGMAX_PART, p1=self._logits.data_ptr(),
371
+ p3=self._parts.data_ptr(), n=self.V))
372
+ names.append("argmax.part")
373
+ rows.append(_row(_OP_ARGMAX_FIN, p1=self._parts.data_ptr(),
374
+ p3=self._token.data_ptr(),
375
+ p4=self._tokens_out.data_ptr(),
376
+ k=self.max_gen))
377
+ names.append("argmax.fin")
378
+ else:
379
+ rows.append(_row(_OP_GEMV, p1=hid, p2=lm_head.data_ptr(),
380
+ p3=self._logits.data_ptr(),
381
+ p4=final_norm.data_ptr(),
382
+ p6=self._parts.data_ptr(), n=self.V,
383
+ k=self.hidden, it=_IT_RMSNORM | _tbit,
384
+ epi=_EP_F32_AMAX, hkv=1, f0=self.eps))
385
+ names.append("lm_head")
386
+ rows.append(_row(_OP_ARGMAX_FIN, p1=self._parts.data_ptr(),
387
+ p3=self._token.data_ptr(),
388
+ p4=self._tokens_out.data_ptr()))
389
+ names.append("argmax.fin")
390
+ se = max(staged_elems[0], self._maxk)
391
+ return rows, names, se
392
+
393
+ def _build_programs(self):
394
+ dev = self.device
395
+ rows, names, _ = self._build_rows(kv_slice=0, batch=False)
396
+ self._prog = torch.tensor(rows, dtype=torch.int64, device=dev)
397
+ self.phase_names = names
398
+ if self._batch:
399
+ big = self._batch > min(8, 16384 // self._maxk)
400
+ rows, _, se = self._build_rows(kv_slice=0, batch=True, big=big)
401
+ self._prog_batch = torch.tensor(rows, dtype=torch.int64,
402
+ device=dev)
403
+ self._batch_stage_elems = int(se)
404
+ self._progs_prefill = [self._prog]
405
+ for b in range(1, self._batch):
406
+ rows, _, _ = self._build_rows(kv_slice=b, batch=False)
407
+ self._progs_prefill.append(
408
+ torch.tensor(rows, dtype=torch.int64, device=dev))
409
+
410
+ def batch_max(self) -> int:
411
+ """Largest batch size this model supports. Up to 8 sequences stage
412
+ in shared; beyond that the transformed input moves to a global
413
+ scratch, bounded by the kernel's register accumulators."""
414
+ try:
415
+ hard = int(ops.mak_batch_maxb())
416
+ except (AttributeError, RuntimeError):
417
+ hard = 8
418
+ return 1 if self._gemma else hard
419
+
420
+ # ------------------------------------------------------------------
421
+ def enable_batch(self, B: int):
422
+ """Allocate per-sequence KV caches, token buffers, and programs for
423
+ batched decode. B up to 8 stages the input in shared; larger B (up
424
+ to batch_max()) routes the transformed input through a global
425
+ scratch so the batch width is not bounded by the projection size."""
426
+ if self._gemma:
427
+ raise ValueError("batched decode covers the llama-family path")
428
+ maxb = self.batch_max()
429
+ if not 1 <= B <= maxb:
430
+ raise ValueError(f"B must be in [1, {maxb}] for this model")
431
+ if self._batch == B:
432
+ return
433
+ dt, dev = torch.bfloat16, self.device
434
+ self._kcache = torch.zeros(self.L, B, self.Hkv, self.max_seq,
435
+ self.D, dtype=dt, device=dev)
436
+ self._vcache = torch.zeros_like(self._kcache)
437
+ self._token = torch.zeros(B, dtype=torch.int32, device=dev)
438
+ self._tokens_out = torch.zeros(B * self.max_gen, dtype=torch.int32,
439
+ device=dev)
440
+ self._logits = torch.empty(B * self.V, dtype=torch.float32,
441
+ device=dev)
442
+ self._parts = torch.zeros(B * self._nblocks, dtype=torch.int64,
443
+ device=dev)
444
+ self._pos_b = torch.zeros(B, dtype=torch.int32, device=dev)
445
+ # transformed-input scratch: one row per active token, and prefill
446
+ # (during generate_batch) still uses up to chunk_m rows
447
+ self._xg = torch.empty(max(8, B) * self._maxk, dtype=dt, device=dev)
448
+ # working buffers hold one row per active sequence; prefill chunks
449
+ # still use up to chunk_m rows, so keep at least the fused capacity
450
+ nb = max(8, B)
451
+ qkvd = self._qdim + 2 * self._kvdim
452
+ self._hidden = torch.empty(nb * self.hidden, dtype=dt, device=dev)
453
+ self._qkv = torch.empty(nb * qkvd, dtype=dt, device=dev)
454
+ self._gu = torch.empty(nb * 2 * self.I, dtype=dt, device=dev)
455
+ self._partials = torch.empty(
456
+ nb * self.Hq * self._maxch * (self.D + 2), dtype=torch.float32,
457
+ device=dev)
458
+ self._kv_bstride = self.Hkv * self.max_seq * self.D
459
+ self._batch = B
460
+ self._build_programs()
461
+
462
+ def decode_batch(self, tokens: Sequence[int], positions: Sequence[int],
463
+ steps: int = 1) -> torch.Tensor:
464
+ """Batched greedy decode: sequence b consumes tokens[b] at
465
+ positions[b]; `steps` in-kernel steps run with per-sequence argmax
466
+ feedback. Returns the generated tokens [B, steps]; the final
467
+ step's fp32 logits are live in batch_logits()."""
468
+ B = self._batch
469
+ if B < 1:
470
+ raise ValueError("call enable_batch(B) first")
471
+ if len(tokens) != B or len(positions) != B:
472
+ raise ValueError("tokens and positions must have B entries")
473
+ if steps < 1 or steps > self.max_gen:
474
+ raise ValueError("steps out of range")
475
+ if max(positions) + steps > self.max_seq:
476
+ raise ValueError("sequence exceeds max_seq")
477
+ self._token.copy_(torch.tensor(list(tokens), dtype=torch.int32))
478
+ self._pos_b.copy_(torch.tensor(list(positions), dtype=torch.int32))
479
+ ops.mak_run_batch(self._prog_batch, self._bar, self._pos_b, steps,
480
+ 0, self._maxk, self._kv_bstride,
481
+ self._batch_stage_elems)
482
+ return self._tokens_out.view(B, self.max_gen)[:, :steps]
483
+
484
+ def batch_logits(self) -> torch.Tensor:
485
+ """fp32 logits [B, V] of the most recent batched step."""
486
+ return self._logits.view(max(self._batch, 1), self.V)
487
+
488
+ def generate_batch(self, prompts: Sequence[Sequence[int]],
489
+ max_new: int) -> List[List[int]]:
490
+ """Greedy generation for a batch of prompts: per-sequence prefill,
491
+ then one batched decode launch. Each sequence's token stream is
492
+ bit-identical to its own single-sequence generate()."""
493
+ B = len(prompts)
494
+ if max_new < 1 or max_new > self.max_gen:
495
+ raise ValueError("max_new out of range")
496
+ self.enable_batch(B)
497
+ firsts: List[int] = []
498
+ for b, ids in enumerate(prompts):
499
+ P = len(ids)
500
+ if P < 1 or P + max_new > self.max_seq:
501
+ raise ValueError("sequence exceeds max_seq")
502
+ self._prompt_in[:P].copy_(
503
+ torch.tensor(list(ids), dtype=torch.int32))
504
+ ops.mak_run_seq(self._progs_prefill[b], self._bar, 0, P, 1 - P,
505
+ P, self._maxk, self._chunk_m)
506
+ firsts.append(int(self._token[0].item()))
507
+ if max_new == 1:
508
+ return [[t] for t in firsts]
509
+ self._token.copy_(torch.tensor(firsts, dtype=torch.int32))
510
+ self._pos_b.copy_(torch.tensor([len(p) for p in prompts],
511
+ dtype=torch.int32))
512
+ ops.mak_run_batch(self._prog_batch, self._bar, self._pos_b,
513
+ max_new - 1, 0, self._maxk, self._kv_bstride,
514
+ self._batch_stage_elems)
515
+ rest = (self._tokens_out.view(B, self.max_gen)[:, :max_new - 1]
516
+ .cpu().tolist())
517
+ return [[firsts[b]] + rest[b] for b in range(B)]
518
+
519
+ # ------------------------------------------------------------------
520
+ @staticmethod
521
+ def _dense_weight(mod):
522
+ """The effective bf16 weight of a linear layer, dequantizing packed
523
+ quantized layers (bitsandbytes 4-bit and 8-bit) so a quantized
524
+ checkpoint loads directly. Dequantization is exact and does not
525
+ depend on the card, so inference on the result keeps every
526
+ guarantee; the weights materialize to bf16, so the quantized memory
527
+ footprint is not preserved (that needs the in-kernel packed path)."""
528
+ w = mod.weight
529
+ qs = getattr(w, "quant_state", None)
530
+ if qs is not None: # bitsandbytes 4-bit (nf4 / fp4)
531
+ import bitsandbytes as bnb
532
+ return bnb.functional.dequantize_4bit(w.data, qs).to(torch.bfloat16)
533
+ if getattr(w, "SCB", None) is not None or hasattr(mod, "SCB"):
534
+ # bitsandbytes 8-bit (LLM.int8): row scales in SCB, int8 in CB
535
+ scb = w.SCB if getattr(w, "SCB", None) is not None else mod.SCB
536
+ cb = w.data if w.data.dtype == torch.int8 else mod.CB
537
+ return (cb.to(torch.float32)
538
+ * (scb.to(torch.float32) / 127.0).unsqueeze(1)
539
+ ).to(torch.bfloat16)
540
+ return w
541
+
542
+ @staticmethod
543
+ def _q4_parts(mod):
544
+ """(packed [N, K/2] uint8, absmax [N*K/64] fp32) for a bitsandbytes
545
+ nf4 layer whose weights stay packed, else None. Double-quantized
546
+ absmax is materialized to fp32 so the kernel needs one scale array."""
547
+ w = getattr(mod, "weight", None)
548
+ qs = getattr(w, "quant_state", None)
549
+ if qs is None or getattr(qs, "quant_type", None) != "nf4":
550
+ return None
551
+ import bitsandbytes as bnb
552
+ n, k = int(qs.shape[0]), int(qs.shape[1])
553
+ if k % 64:
554
+ return None # kernel assumes 64 | K for per-block absmax
555
+ packed = w.data.reshape(n, k // 2).contiguous()
556
+ if getattr(qs, "nested", False):
557
+ absmax = bnb.functional.dequantize_blockwise(
558
+ qs.absmax, qs.state2) + qs.offset
559
+ else:
560
+ absmax = qs.absmax
561
+ return packed, absmax.float().reshape(-1).contiguous()
562
+
563
+ @classmethod
564
+ def _proj(cls, mods):
565
+ """A projection, concatenated across `mods` (q/k/v or gate/up). Stays
566
+ nf4-packed when every part is nf4, else a dense bf16 tensor."""
567
+ parts = [cls._q4_parts(m) for m in mods]
568
+ if all(p is not None for p in parts):
569
+ packed = torch.cat([p[0] for p in parts], 0)
570
+ absmax = torch.cat([p[1] for p in parts], 0)
571
+ return {"q4": True, "packed": packed, "absmax": absmax,
572
+ "N": packed.shape[0], "K": packed.shape[1] * 2}
573
+ return torch.cat([cls._dense_weight(m) for m in mods], 0)
574
+
575
+ @classmethod
576
+ def from_pretrained(cls, model, device="cuda", max_seq: int = 4096,
577
+ max_gen: int = 4096):
578
+ """Build from a transformers model (object or repo id). A quantized
579
+ checkpoint (bitsandbytes 4-bit/8-bit) is accepted directly; its
580
+ weights are dequantized to bf16 at load."""
581
+ if isinstance(model, str):
582
+ from transformers import AutoModelForCausalLM
583
+ model = AutoModelForCausalLM.from_pretrained(
584
+ model, torch_dtype=torch.bfloat16)
585
+ if "gemma4" in getattr(model.config, "model_type", ""):
586
+ return cls._from_gemma4(model, device, max_seq, max_gen)
587
+ hf = model.config
588
+ dec = model.model
589
+ attn0 = dec.layers[0].self_attn
590
+ qk_norm = hasattr(attn0, "q_norm") and attn0.q_norm is not None
591
+ if getattr(attn0.q_proj, "bias", None) is not None:
592
+ raise ValueError("attention biases are not supported")
593
+
594
+ # rope config across transformers API generations: v5 keeps theta and
595
+ # type in a rope_parameters dict; v4 used a rope_theta attribute and
596
+ # an optional rope_scaling dict.
597
+ rp = getattr(hf, "rope_parameters", None)
598
+ if not isinstance(rp, dict):
599
+ rs = getattr(hf, "rope_scaling", None)
600
+ rp = rs if isinstance(rs, dict) else {}
601
+ theta = rp.get("rope_theta")
602
+ if theta is None:
603
+ theta = getattr(hf, "rope_theta", 10000.0)
604
+ rope_type = rp.get("rope_type", rp.get("type", "default"))
605
+ if rope_type not in ("default", "llama3"):
606
+ raise ValueError(f"rope type {rope_type!r} is not supported")
607
+ # take the frequency table transformers computed (scaling included)
608
+ rot = dec.rotary_emb
609
+ att_scale = float(getattr(rot, "attention_scaling", 1.0))
610
+ if att_scale != 1.0:
611
+ raise ValueError("rope attention_scaling != 1 is not supported")
612
+ inv_freq = rot.inv_freq.detach().float()
613
+
614
+ pj = cls._proj
615
+ layers = []
616
+ for lyr in dec.layers:
617
+ a, m = lyr.self_attn, lyr.mlp
618
+ layers.append({
619
+ "wqkv": pj([a.q_proj, a.k_proj, a.v_proj]),
620
+ "wo": pj([a.o_proj]),
621
+ "wgu": pj([m.gate_proj, m.up_proj]),
622
+ "wdown": pj([m.down_proj]),
623
+ "ln1": lyr.input_layernorm.weight,
624
+ "ln2": lyr.post_attention_layernorm.weight,
625
+ "qn": a.q_norm.weight if qk_norm else None,
626
+ "kn": a.k_norm.weight if qk_norm else None,
627
+ })
628
+ weights = {"embed": dec.embed_tokens.weight, "norm": dec.norm.weight,
629
+ "lm_head": pj([model.lm_head]), "layers": layers,
630
+ "inv_freq": inv_freq}
631
+ config = {
632
+ "num_hidden_layers": hf.num_hidden_layers,
633
+ "hidden_size": hf.hidden_size,
634
+ "num_attention_heads": hf.num_attention_heads,
635
+ "num_key_value_heads": getattr(hf, "num_key_value_heads", None),
636
+ "head_dim": getattr(hf, "head_dim", None),
637
+ "intermediate_size": hf.intermediate_size,
638
+ "vocab_size": hf.vocab_size,
639
+ "rope_theta": float(theta),
640
+ "rms_norm_eps": hf.rms_norm_eps,
641
+ "qk_norm": qk_norm,
642
+ }
643
+ return cls(weights, config, device=device, max_seq=max_seq,
644
+ max_gen=max_gen)
645
+
646
+ # ------------------------------------------------------------------
647
+ @classmethod
648
+ def _from_gemma4(cls, model, device, max_seq, max_gen):
649
+ """gemma-4-E2B-it program: gemma norms (fp32, raw weight),
650
+ gelu-tanh gating, post-sublayer norm phases, per-layer-input
651
+ pathway, sliding windows, shared KV, dual head dims, softcapped
652
+ head. Scope is the E2B text model only."""
653
+ self = cls.__new__(cls)
654
+ cfg = model.config.text_config if hasattr(model.config, "text_config") \
655
+ else model.config
656
+ dec = model.model.language_model if hasattr(model.model, "language_model") \
657
+ else model.model
658
+ assert cfg.model_type == "gemma4_text", cfg.model_type
659
+ assert not cfg.enable_moe_block
660
+
661
+ self.L = int(cfg.num_hidden_layers)
662
+ self.hidden = int(cfg.hidden_size)
663
+ self.Hq = int(cfg.num_attention_heads)
664
+ self.V = int(cfg.vocab_size)
665
+ self.eps = float(cfg.rms_norm_eps)
666
+ self.qk_norm = True
667
+ self.max_seq = int(max_seq)
668
+ self.max_gen = int(max_gen)
669
+ self.device = torch.device(device)
670
+ self.tiled = False
671
+ win = int(cfg.sliding_window)
672
+ cap = float(cfg.final_logit_softcapping)
673
+ PL = int(cfg.hidden_size_per_layer_input)
674
+ dt = torch.bfloat16
675
+ dev = self.device
676
+ self._keep = []
677
+
678
+ def pack(t):
679
+ t = t.detach().to(device=dev, dtype=dt).contiguous()
680
+ self._keep.append(t)
681
+ return t
682
+
683
+ def packf(t):
684
+ t = t.detach().to(device=dev, dtype=torch.float32).contiguous()
685
+ self._keep.append(t)
686
+ return t
687
+
688
+ embed = pack(dec.embed_tokens.weight)
689
+ ple_tab = pack(dec.embed_tokens_per_layer.weight)
690
+ plm_w = pack(dec.per_layer_model_projection.weight)
691
+ pln_w = pack(dec.per_layer_projection_norm.weight)
692
+ fnorm = pack(dec.norm.weight)
693
+ rot = dec.rotary_emb
694
+ invf = {lt: packf(getattr(rot, f"{lt}_inv_freq").float())
695
+ for lt in set(cfg.layer_types)}
696
+ ascale = {lt: float(getattr(rot, f"{lt}_attention_scaling"))
697
+ for lt in set(cfg.layer_types)}
698
+
699
+ layers = []
700
+ for li, lyr in enumerate(dec.layers):
701
+ a = lyr.self_attn
702
+ D_l = int(a.head_dim)
703
+ shared = bool(a.is_kv_shared_layer)
704
+ ent = {
705
+ "D": D_l, "shared": shared,
706
+ "lscale": float(lyr.layer_scalar.float().item()),
707
+ "type": cfg.layer_types[li],
708
+ "I": int(lyr.mlp.intermediate_size),
709
+ "qn": pack(a.q_norm.weight),
710
+ "o": pack(a.o_proj.weight),
711
+ "ln_in": pack(lyr.input_layernorm.weight),
712
+ "ln_pa": pack(lyr.post_attention_layernorm.weight),
713
+ "ln_pf": pack(lyr.pre_feedforward_layernorm.weight),
714
+ "ln_ff": pack(lyr.post_feedforward_layernorm.weight),
715
+ "ln_pl": pack(lyr.post_per_layer_input_norm.weight),
716
+ "plig": pack(lyr.per_layer_input_gate.weight),
717
+ "plpr": pack(lyr.per_layer_projection.weight),
718
+ "wgu": pack(torch.cat([lyr.mlp.gate_proj.weight,
719
+ lyr.mlp.up_proj.weight], 0)),
720
+ "wdn": pack(lyr.mlp.down_proj.weight),
721
+ }
722
+ if shared:
723
+ ent["wqkv"] = pack(a.q_proj.weight)
724
+ ent["kn"] = None
725
+ else:
726
+ ent["wqkv"] = pack(torch.cat(
727
+ [a.q_proj.weight, a.k_proj.weight, a.v_proj.weight], 0))
728
+ ent["kn"] = pack(a.k_norm.weight)
729
+ ent["kc"] = torch.zeros(self.max_seq, D_l, dtype=dt,
730
+ device=dev)
731
+ ent["vc"] = torch.zeros(self.max_seq, D_l, dtype=dt,
732
+ device=dev)
733
+ self._keep += [ent["kc"], ent["vc"]]
734
+ layers.append(ent)
735
+ # shared layers read the last non-shared layer of their type
736
+ src = {}
737
+ for li, ent in enumerate(layers):
738
+ if not ent["shared"]:
739
+ src[ent["type"]] = ent
740
+ for ent in layers:
741
+ if ent["shared"]:
742
+ ent["kc"] = src[ent["type"]]["kc"]
743
+ ent["vc"] = src[ent["type"]]["vc"]
744
+
745
+ maxD = max(e["D"] for e in layers)
746
+ maxI = max(e["I"] for e in layers)
747
+ self._maxk = max(self.hidden, self.Hq * maxD, maxI)
748
+ self._chunk_m = 1
749
+ self._chunk = 128
750
+ self._maxch = (self.max_seq + self._chunk - 1) // self._chunk
751
+ self._hidden = torch.empty(self.hidden, dtype=dt, device=dev)
752
+ self._tmp = torch.empty(self.hidden, dtype=dt, device=dev)
753
+ self._qkv = torch.empty((self.Hq + 2) * maxD, dtype=dt, device=dev)
754
+ self._gu = torch.empty(2 * maxI, dtype=dt, device=dev)
755
+ self._t256 = torch.empty(PL, dtype=dt, device=dev)
756
+ self._ctxraw = torch.empty(self.L * PL, dtype=dt, device=dev)
757
+ self._pletok = torch.empty(self.L * PL, dtype=dt, device=dev)
758
+ self._ple = torch.empty(self.L * PL, dtype=dt, device=dev)
759
+ self._logits = torch.empty(self.V, dtype=torch.float32, device=dev)
760
+ self._partials = torch.empty(
761
+ self.Hq * self._maxch * (maxD + 2), dtype=torch.float32,
762
+ device=dev)
763
+ self._token = torch.zeros(1, dtype=torch.int32, device=dev)
764
+ self._prompt_in = torch.zeros(self.max_seq, dtype=torch.int32,
765
+ device=dev)
766
+ self._tokens_out = torch.zeros(self.max_gen, dtype=torch.int32,
767
+ device=dev)
768
+ self._bar = torch.zeros(34, dtype=torch.int32, device=dev)
769
+ probe = torch.zeros(1, 16, dtype=torch.int64, device=dev)
770
+ self._nblocks = int(ops.mak_num_blocks(probe, self._maxk))
771
+ self._parts = torch.zeros(self._nblocks, dtype=torch.int64,
772
+ device=dev)
773
+ try:
774
+ self._bw_per_sm = float(ops.mak_bw_per_sm(probe))
775
+ except (AttributeError, RuntimeError):
776
+ self._bw_per_sm = 0.0
777
+ self._invf = invf[layers[0]["type"]]
778
+ self.Hkv, self.D, self.I = 1, maxD, maxI
779
+ self.theta = 0.0
780
+
781
+ emb_scale = float(torch.tensor(self.hidden ** 0.5,
782
+ dtype=torch.float32).to(dt))
783
+ ple_scale = float(torch.tensor(PL ** 0.5,
784
+ dtype=torch.float32).to(dt))
785
+ proj_scale = float(torch.tensor(self.hidden ** -0.5,
786
+ dtype=torch.float32))
787
+ hid = self._hidden.data_ptr()
788
+ tmp = self._tmp.data_ptr()
789
+ rows, names = [], []
790
+ rows.append(_row(_OP_EMBED, p1=ple_tab.data_ptr(),
791
+ p2=embed.data_ptr(), p3=hid,
792
+ p4=self._parts.data_ptr(),
793
+ p5=self._prompt_in.data_ptr(),
794
+ p6=self._token.data_ptr(),
795
+ n=self._tokens_out.data_ptr(), k=self.hidden,
796
+ it=self._pletok.data_ptr(),
797
+ hkv=self.L * PL, f0=emb_scale,
798
+ i0=_fbits(ple_scale)))
799
+ names.append("embed")
800
+ rows.append(_row(_OP_GEMV, p1=hid, p2=plm_w.data_ptr(),
801
+ p3=self._ctxraw.data_ptr(), n=self.L * PL,
802
+ k=self.hidden, it=_IT_NONE, epi=_EP_STORE))
803
+ names.append("plm")
804
+ rows.append(_row(8, p1=self._ctxraw.data_ptr(),
805
+ p2=pln_w.data_ptr(), p3=self._ple.data_ptr(),
806
+ p4=self._pletok.data_ptr(), n=self.L, k=PL,
807
+ f0=self.eps, i0=_fbits(proj_scale)))
808
+ names.append("plemix")
809
+
810
+ for li, e in enumerate(layers):
811
+ D_l, qdim = e["D"], self.Hq * e["D"]
812
+ lt = e["type"]
813
+ w_l = win if lt == "sliding_attention" else 0
814
+ asb = _fbits(ascale[lt]) if ascale[lt] != 1.0 else 0
815
+ flags = 1 | 2 | (0 if e["shared"] else 4) | \
816
+ (8 if e["shared"] else 0)
817
+ n_qkv = qdim if e["shared"] else qdim + 2 * D_l
818
+ rows.append(_row(_OP_GEMV, p1=hid, p2=e["wqkv"].data_ptr(),
819
+ p3=self._qkv.data_ptr(),
820
+ p4=e["ln_in"].data_ptr(), n=n_qkv,
821
+ k=self.hidden, it=_IT_RMSNORM_G,
822
+ epi=_EP_STORE, f0=self.eps))
823
+ names.append(f"L{li}.qkv")
824
+ rows.append(_row(_OP_ATTN, p1=self._qkv.data_ptr(),
825
+ p2=e["kc"].data_ptr(), p3=e["vc"].data_ptr(),
826
+ p4=self._partials.data_ptr(),
827
+ p5=e["qn"].data_ptr(),
828
+ p6=e["kn"].data_ptr() if e["kn"] is not None
829
+ else 0,
830
+ n=self.max_seq,
831
+ k=flags | (w_l << 16), it=_fbits(self.eps),
832
+ epi=invf[lt].data_ptr(), hq=self.Hq,
833
+ hkv=1 | (self._chunk << 16), d=D_l, f0=1.0,
834
+ i0=self._maxch | (asb << 32)))
835
+ names.append(f"L{li}.attn")
836
+ rows.append(_row(_OP_GEMV, p1=self._partials.data_ptr(),
837
+ p2=e["o"].data_ptr(), p3=tmp,
838
+ n=self.hidden, k=qdim, it=_IT_ATTNFIN,
839
+ epi=_EP_STORE, hq=self.Hq,
840
+ hkv=self._chunk << 16, d=D_l,
841
+ i0=self._maxch | (w_l << 16)))
842
+ names.append(f"L{li}.o")
843
+ rows.append(_row(7, p1=tmp, p2=e["ln_pa"].data_ptr(), p3=hid,
844
+ n=self.hidden, f0=self.eps, i0=_fbits(1.0)))
845
+ names.append(f"L{li}.nr_attn")
846
+ rows.append(_row(_OP_GEMV, p1=hid, p2=e["wgu"].data_ptr(),
847
+ p3=self._gu.data_ptr(),
848
+ p4=e["ln_pf"].data_ptr(), n=2 * e["I"],
849
+ k=self.hidden, it=_IT_RMSNORM_G,
850
+ epi=_EP_STORE, f0=self.eps))
851
+ names.append(f"L{li}.gateup")
852
+ rows.append(_row(_OP_GEMV, p1=self._gu.data_ptr(),
853
+ p2=e["wdn"].data_ptr(), p3=tmp,
854
+ n=self.hidden, k=e["I"], it=_IT_GELU_GLU,
855
+ epi=_EP_STORE))
856
+ names.append(f"L{li}.down")
857
+ rows.append(_row(7, p1=tmp, p2=e["ln_ff"].data_ptr(), p3=hid,
858
+ n=self.hidden, f0=self.eps, i0=_fbits(1.0)))
859
+ names.append(f"L{li}.nr_ffw")
860
+ rows.append(_row(_OP_GEMV, p1=hid, p2=e["plig"].data_ptr(),
861
+ p3=self._t256.data_ptr(),
862
+ p6=self._ple.data_ptr() + li * PL * 2,
863
+ n=PL, k=self.hidden, it=_IT_NONE,
864
+ epi=_EP_GELU_PLE))
865
+ names.append(f"L{li}.plig")
866
+ rows.append(_row(_OP_GEMV, p1=self._t256.data_ptr(),
867
+ p2=e["plpr"].data_ptr(), p3=tmp,
868
+ n=self.hidden, k=PL, it=_IT_NONE,
869
+ epi=_EP_STORE))
870
+ names.append(f"L{li}.plproj")
871
+ rows.append(_row(7, p1=tmp, p2=e["ln_pl"].data_ptr(), p3=hid,
872
+ n=self.hidden, f0=self.eps,
873
+ i0=_fbits(e["lscale"])))
874
+ names.append(f"L{li}.nr_ple")
875
+
876
+ rows.append(_row(_OP_GEMV, p1=hid, p2=embed.data_ptr(),
877
+ p3=self._logits.data_ptr(),
878
+ p4=fnorm.data_ptr(),
879
+ p6=self._parts.data_ptr(), n=self.V,
880
+ k=self.hidden, it=_IT_RMSNORM_G,
881
+ epi=_EP_F32_AMAX_CAP, hkv=1, f0=self.eps,
882
+ i0=_fbits(cap)))
883
+ names.append("lm_head")
884
+ rows.append(_row(_OP_ARGMAX_FIN, p1=self._parts.data_ptr(),
885
+ p3=self._token.data_ptr(),
886
+ p4=self._tokens_out.data_ptr()))
887
+ names.append("argmax.fin")
888
+ self._prog = torch.tensor(rows, dtype=torch.int64, device=dev)
889
+ self.phase_names = names
890
+ self._gemma = True
891
+ self._batch = 0
892
+ return self
893
+
894
+ # ------------------------------------------------------------------
895
+ def decode_step(self, token: int, pos: int, phased: bool = False):
896
+ """One decode step; returns the live fp32 logits buffer [V]."""
897
+ if pos >= self.max_seq:
898
+ raise ValueError("pos exceeds max_seq")
899
+ self._token.fill_(int(token))
900
+ if phased:
901
+ ops.mak_run_phased(self._prog, pos, self.max_gen - 1, False,
902
+ self._maxk)
903
+ else:
904
+ ops.mak_run(self._prog, self._bar, pos, self.max_gen - 1,
905
+ self._maxk)
906
+ return self._logits[:self.V]
907
+
908
+ def decode_step_timed(self, token: int, pos: int):
909
+ """One phase-per-launch step; returns (logits, per-phase ms)."""
910
+ self._token.fill_(int(token))
911
+ ms = ops.mak_run_phased(self._prog, pos, self.max_gen - 1, True,
912
+ self._maxk)
913
+ return self._logits[:self.V], ms
914
+
915
+ def prefill(self, ids: Sequence[int], phased: bool = False):
916
+ """Consume a prompt; returns the last position's logits. Runs in
917
+ chunks of up to chunk_m tokens with weight reads amortized across
918
+ the chunk (bitwise identical to token-by-token consumption)."""
919
+ P = len(ids)
920
+ if phased:
921
+ for i, t in enumerate(ids):
922
+ self.decode_step(int(t), i, phased=True)
923
+ return self._logits
924
+ self._prompt_in[:P].copy_(torch.tensor(ids, dtype=torch.int32))
925
+ ops.mak_run_seq(self._prog, self._bar, 0, P, 1 - P, P, self._maxk,
926
+ self._chunk_m)
927
+ return self._logits[:self.V]
928
+
929
+ def generate(self, prompt_ids: Sequence[int], max_new: int,
930
+ single_launch: bool = True) -> List[int]:
931
+ """Greedy generation. With single_launch (default) the entire call,
932
+ prompt consumption included, is one kernel launch: the in-kernel step
933
+ loop reads prompt tokens from a staged device buffer, then feeds each
934
+ step's argmax to the next step's embedding read. With
935
+ single_launch=False the same computation runs as one launch per
936
+ token; the two paths produce identical tokens.
937
+ """
938
+ P = len(prompt_ids)
939
+ if max_new < 1 or max_new > self.max_gen:
940
+ raise ValueError("max_new out of range")
941
+ if P < 1 or P + max_new > self.max_seq:
942
+ raise ValueError("sequence exceeds max_seq")
943
+ if single_launch:
944
+ self._prompt_in[:P].copy_(
945
+ torch.tensor(prompt_ids, dtype=torch.int32))
946
+ ops.mak_run_seq(self._prog, self._bar, 0, P + max_new - 1,
947
+ 1 - P, P, self._maxk, self._chunk_m)
948
+ return self._tokens_out[:max_new].cpu().tolist()
949
+ pos = 0
950
+ for t in prompt_ids[:-1]:
951
+ self._token.fill_(int(t))
952
+ ops.mak_run(self._prog, self._bar, pos, self.max_gen - 1,
953
+ self._maxk)
954
+ pos += 1
955
+ self._token.fill_(int(prompt_ids[-1]))
956
+ ops.mak_run(self._prog, self._bar, pos, 0, self._maxk)
957
+ pos += 1
958
+ if max_new > 1:
959
+ ops.mak_run_steps(self._prog, self._bar, pos, max_new - 1, 1,
960
+ self._maxk)
961
+ return self._tokens_out[:max_new].cpu().tolist()
962
+
963
+
964
+
build/torch212-cxx11-cu130-x86_64-linux/_model_as_a_kernel_cuda_e33875a.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2b82256093271ec83b0ab2150b9bdc2aded273ef1abeb215467793cc5658e970
3
+ size 6354080
build/torch212-cxx11-cu130-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _model_as_a_kernel_cuda_e33875a
3
+ ops = torch.ops._model_as_a_kernel_cuda_e33875a
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_model_as_a_kernel_cuda_e33875a::{op_name}"
build/torch212-cxx11-cu130-x86_64-linux/metadata.json ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "model-as-a-kernel",
3
+ "id": "_model_as_a_kernel_cuda_e33875a",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "python-depends": [],
7
+ "backend": {
8
+ "type": "cuda",
9
+ "archs": [
10
+ "10.0",
11
+ "12.0",
12
+ "8.0",
13
+ "8.6",
14
+ "8.9",
15
+ "9.0"
16
+ ]
17
+ },
18
+ "digest": {
19
+ "algorithm": "sha256",
20
+ "files": {
21
+ "__init__.py": "/YX5/zzAOyJacQ7H+j8zaq+xjWyy8w6r0j+ugE1EUs8=",
22
+ "_model_as_a_kernel_cuda_e33875a.abi3.so": "K4IlYJMnHsg7CrIVC5vcKt7Sc+8avrIVRneTzFZY6XA=",
23
+ "_ops.py": "Pqj4WKtwPyeDG9grQnzLget4g1jsGUdR7ZMa44e4rhs=",
24
+ "model_as_a_kernel/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
25
+ }
26
+ },
27
+ "provenance": {
28
+ "kernel-builder": {
29
+ "version": "0.17.0-dev0",
30
+ "sha": "05f1ed487f599a320311aa26076ec0ad290c3f68",
31
+ "dirty": false
32
+ },
33
+ "kernel": {
34
+ "sha": "e33875ae4eba42fd136a89c388e806281cd4759c",
35
+ "dirty": false
36
+ }
37
+ }
38
+ }
build/torch212-cxx11-cu130-x86_64-linux/model_as_a_kernel/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import importlib.util
3
+ import sys
4
+ from pathlib import Path
5
+ from types import ModuleType
6
+
7
+
8
+ def _import_from_path(file_path: Path) -> ModuleType:
9
+ # We cannot use the module name as-is, after adding it to `sys.modules`,
10
+ # it would also be used for other imports. So, we make a module name that
11
+ # depends on the path for it to be unique using the hex-encoded hash of
12
+ # the path.
13
+ path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
+ module_name = path_hash
15
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
16
+ if spec is None:
17
+ raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
+ module = importlib.util.module_from_spec(spec)
19
+ if module is None:
20
+ raise ImportError(f"Cannot load module {module_name} from spec")
21
+ sys.modules[module_name] = module
22
+ spec.loader.exec_module(module) # type: ignore
23
+ return module
24
+
25
+
26
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
build/torch212-cxx11-cu132-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,964 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """model-as-a-kernel: an entire llama-family decode step as one kernel launch.
2
+
3
+ A phase interpreter runs the whole forward pass (embedding, every layer's
4
+ norms, QKV, rope, attention over the KV cache, SwiGLU MLP, the LM head, and
5
+ the greedy argmax) inside a single persistent kernel, with a software grid
6
+ barrier between phases. The in-kernel step loop makes an entire greedy
7
+ generation, prompt consumption included, one kernel launch: each step's
8
+ argmax feeds the next step's embedding read without leaving the device.
9
+ """
10
+ import os
11
+ import struct
12
+ from typing import List, Optional, Sequence
13
+
14
+ import torch
15
+
16
+ try:
17
+ from ._ops import ops
18
+ except ImportError: # local development build (torch.utils.cpp_extension)
19
+ class _LocalOps:
20
+ def __getattr__(self, name):
21
+ return getattr(torch.ops.mak_ext, name)
22
+
23
+ ops = _LocalOps()
24
+
25
+ __all__ = ["MegaModel"]
26
+
27
+ _OP_EMBED, _OP_GEMV, _OP_QKV_POST, _OP_ATTN = 0, 1, 2, 3
28
+ _OP_ARGMAX_PART, _OP_ARGMAX_FIN = 4, 5
29
+ _OP_KV_APPEND, _OP_NORMRES, _OP_PLEMIX = 6, 7, 8
30
+ _OP_GEMV_PLAIN, _OP_NORMB, _OP_GLUB, _OP_ATTNFINB = 9, 10, 11, 12
31
+ _OP_GEMV_Q4 = 13
32
+ _IT_NONE, _IT_RMSNORM, _IT_SWIGLU, _IT_ATTNFIN = 0, 1, 2, 3
33
+ _IT_RMSNORM_G, _IT_GELU_GLU = 4, 5
34
+ _EP_GELU_PLE, _EP_F32_AMAX_CAP = 5, 6
35
+ _ITF_REDUCE, _ITF_WRITEBACK = 8, 16
36
+ _EP_STORE, _EP_RESID, _EP_F32, _EP_F32_AMAX, _EP_PARTIAL = 0, 1, 2, 3, 4
37
+ _NSLICE = 4
38
+ # Widest [B][K] input panel (bf16 elements) a batched projection stages in
39
+ # shared before it spills to the global-scratch path. 16384 (32 KB) keeps
40
+ # two blocks resident per SM; the persistent kernel shares one occupancy
41
+ # across all phases, so a wider panel would drop every phase to one block
42
+ # per SM, which measures slower than reading the input from L2.
43
+ _STAGE_BUDGET = 16384
44
+
45
+
46
+ def _fbits(f: float) -> int:
47
+ return struct.unpack("<i", struct.pack("<f", float(f)))[0]
48
+
49
+
50
+ def _row(op, p1=0, p2=0, p3=0, p4=0, p5=0, p6=0, n=0, k=0, it=0, epi=0,
51
+ hq=0, hkv=0, d=0, f0=0.0, i0=0):
52
+ return [int(op), int(p1), int(p2), int(p3), int(p4), int(p5), int(p6),
53
+ int(n), int(k), int(it), int(epi), int(hq), int(hkv), int(d),
54
+ _fbits(f0), int(i0)]
55
+
56
+
57
+ class MegaModel:
58
+ """A packed llama-family decoder whose decode step is one kernel launch.
59
+
60
+ Supported architecture: RMSNorm decoder blocks with rotary attention
61
+ (GQA, optional Qwen3-style per-head qk RMSNorm), SwiGLU MLP, no attention
62
+ or MLP biases, plain (unscaled) rope, batch size 1. This covers SmolLM2,
63
+ TinyLlama, Qwen3 dense, Llama-class checkpoints without rope scaling,
64
+ and similar models.
65
+ """
66
+
67
+ def __init__(self, weights: dict, config: dict, device="cuda",
68
+ max_seq: int = 4096, max_gen: int = 4096):
69
+ cfg = dict(config)
70
+ self.L = int(cfg["num_hidden_layers"])
71
+ self.hidden = int(cfg["hidden_size"])
72
+ self.Hq = int(cfg["num_attention_heads"])
73
+ self.Hkv = int(cfg.get("num_key_value_heads") or self.Hq)
74
+ self.D = int(cfg.get("head_dim") or self.hidden // self.Hq)
75
+ self.I = int(cfg["intermediate_size"])
76
+ self.V = int(cfg["vocab_size"])
77
+ self.theta = float(cfg.get("rope_theta", 10000.0))
78
+ self.eps = float(cfg.get("rms_norm_eps", 1e-6))
79
+ self.qk_norm = bool(cfg.get("qk_norm", False))
80
+ self.max_seq = int(max_seq)
81
+ self.max_gen = int(max_gen)
82
+ self.device = torch.device(device)
83
+
84
+ if self.D % 2 or self.D > 256:
85
+ raise ValueError("head_dim must be even and <= 256")
86
+ if self.hidden % 8 or self.I % 8 or (self.Hq * self.D) % 8:
87
+ raise ValueError(
88
+ "hidden, intermediate, and Hq*head_dim must be multiples of 8")
89
+ if self.Hq % self.Hkv:
90
+ raise ValueError("num_attention_heads must be divisible by num_key_value_heads")
91
+
92
+ dt = torch.bfloat16
93
+ dev = self.device
94
+
95
+ def pack(t):
96
+ t = t.detach().to(device=dev, dtype=dt).contiguous()
97
+ self._keep.append(t)
98
+ return t
99
+
100
+ # Row-tile-of-8 weight layout [N/8][K/8][8][8] (MAK_TILED=1): the
101
+ # eight warps of a block read one contiguous 4KB stream per step
102
+ # instead of eight 4KB-strided pieces. Measured slower on every
103
+ # card (H200 +15 percent, RTX PRO +9, Ada +14 — per-warp stream
104
+ # locality beats per-block contiguity on all of them), so the
105
+ # default is the plain layout; the flag rides in the program and
106
+ # both layouts compute identical bits.
107
+ env_t = os.environ.get("MAK_TILED", "").strip()
108
+ self.tiled = env_t == "1"
109
+
110
+ def pack_tiled(t):
111
+ t = t.detach().to(device=dev, dtype=dt).contiguous()
112
+ if not self.tiled:
113
+ self._keep.append(t)
114
+ return t
115
+ n, k = t.shape
116
+ assert k % 8 == 0
117
+ n8 = (n + 7) // 8 * 8
118
+ if n8 != n:
119
+ t = torch.cat(
120
+ [t, torch.zeros(n8 - n, k, dtype=dt, device=dev)], 0)
121
+ t = t.view(n8 // 8, 8, k // 8, 8).permute(0, 2, 1, 3).contiguous()
122
+ self._keep.append(t)
123
+ return t
124
+
125
+ self._keep: List[torch.Tensor] = []
126
+ embed = pack(weights["embed"])
127
+ final_norm = pack(weights["norm"])
128
+ lm_head = weights["lm_head"] # packed below (dense-tiled or nf4)
129
+ inv_freq = weights.get("inv_freq")
130
+ if inv_freq is None:
131
+ d_idx = torch.arange(0, self.D, 2, dtype=torch.float32)
132
+ inv_freq = 1.0 / (self.theta ** (d_idx / self.D))
133
+ inv_freq = inv_freq.detach().to(device=dev,
134
+ dtype=torch.float32).contiguous()
135
+ if inv_freq.numel() != self.D // 2:
136
+ raise ValueError("inv_freq length must be head_dim / 2")
137
+ self._keep.append(inv_freq)
138
+ self._invf = inv_freq
139
+
140
+ qdim, kvdim = self.Hq * self.D, self.Hkv * self.D
141
+
142
+ def _is_q4(w):
143
+ return isinstance(w, dict) and w.get("q4")
144
+
145
+ def pack_w(w):
146
+ # nf4-packed weights move to the device intact; dense weights take
147
+ # the usual (optionally row-tiled) bf16 packing.
148
+ if _is_q4(w):
149
+ pk = w["packed"].to(dev).contiguous()
150
+ am = w["absmax"].to(device=dev, dtype=torch.float32).contiguous()
151
+ self._keep.append(pk)
152
+ self._keep.append(am)
153
+ return {"q4": True, "packed": pk, "absmax": am,
154
+ "N": int(w["N"]), "K": int(w["K"])}
155
+ return pack_tiled(w)
156
+
157
+ def _wshape(w, expected):
158
+ got = (int(w["N"]), int(w["K"])) if _is_q4(w) else tuple(w.shape)
159
+ assert got == expected, (got, expected)
160
+
161
+ self._has_q4 = False
162
+ self._layers = []
163
+ for lw in weights["layers"]:
164
+ _wshape(lw["wqkv"], (qdim + 2 * kvdim, self.hidden))
165
+ _wshape(lw["wo"], (self.hidden, qdim))
166
+ _wshape(lw["wgu"], (2 * self.I, self.hidden))
167
+ _wshape(lw["wdown"], (self.hidden, self.I))
168
+ layer = {
169
+ "wqkv": pack_w(lw["wqkv"]), "wo": pack_w(lw["wo"]),
170
+ "wgu": pack_w(lw["wgu"]), "wdown": pack_w(lw["wdown"]),
171
+ "ln1": pack(lw["ln1"]), "ln2": pack(lw["ln2"]),
172
+ "qn": pack(lw["qn"]) if self.qk_norm else None,
173
+ "kn": pack(lw["kn"]) if self.qk_norm else None,
174
+ }
175
+ self._has_q4 |= any(_is_q4(layer[n]) for n in
176
+ ("wqkv", "wo", "wgu", "wdown"))
177
+ self._layers.append(layer)
178
+ assert len(self._layers) == self.L
179
+ assert embed.shape == (self.V, self.hidden)
180
+ lm_head = pack_w(lm_head)
181
+ self._has_q4 |= _is_q4(lm_head)
182
+ if not _is_q4(lm_head):
183
+ assert lm_head.numel() >= self.V * self.hidden # row-tiled, padded
184
+ self._w_embed, self._w_fnorm, self._w_lmhead = (embed, final_norm,
185
+ lm_head)
186
+ self._qdim, self._kvdim = qdim, kvdim
187
+
188
+ # Working buffers. Pointers to these are baked into the program, so
189
+ # they (like the packed weights) live for the model's lifetime.
190
+ self._maxk = max(self.hidden, qdim, self.I)
191
+ # prefill chunk size, bounded by the staging shared-memory budget
192
+ # (16K bf16 elements; the cp.async weight ring rides alongside)
193
+ self._chunk_m = max(1, min(8, 16384 // self._maxk))
194
+ cm = 8 # buffers sized for the maximum chunk
195
+ self._hidden = torch.empty(cm * self.hidden, dtype=dt, device=dev)
196
+ self._qkv = torch.empty(cm * (qdim + 2 * kvdim), dtype=dt, device=dev)
197
+ self._gu = torch.empty(cm * 2 * self.I, dtype=dt, device=dev)
198
+ self._logits = torch.empty(self.V, dtype=torch.float32, device=dev)
199
+ # attention chunk length (kernel maximum 128); shorter chunks give
200
+ # the attention phase more grid parallelism at the cost of more
201
+ # softmax partials to finalize
202
+ env_ch = os.environ.get("MAK_CHUNK", "").strip()
203
+ self._chunk = int(env_ch) if env_ch in ("32", "64", "128") else 128
204
+ self._maxch = (self.max_seq + self._chunk - 1) // self._chunk
205
+ self._partials = torch.empty(
206
+ cm * self.Hq * self._maxch * (self.D + 2), dtype=torch.float32,
207
+ device=dev)
208
+ self._kcache = torch.zeros(self.L, self.Hkv, self.max_seq, self.D,
209
+ dtype=dt, device=dev)
210
+ self._vcache = torch.zeros(self.L, self.Hkv, self.max_seq, self.D,
211
+ dtype=dt, device=dev)
212
+ self._token = torch.zeros(1, dtype=torch.int32, device=dev)
213
+ self._prompt_in = torch.zeros(self.max_seq, dtype=torch.int32,
214
+ device=dev)
215
+ self._tokens_out = torch.zeros(self.max_gen, dtype=torch.int32,
216
+ device=dev)
217
+ self._bar = torch.zeros(34, dtype=torch.int32, device=dev)
218
+
219
+ probe = torch.zeros(1, 16, dtype=torch.int64, device=dev)
220
+ self._nblocks = int(ops.mak_num_blocks(probe, self._maxk))
221
+ self._parts = torch.zeros(self._nblocks, dtype=torch.int64, device=dev)
222
+ try:
223
+ self._bw_per_sm = float(ops.mak_bw_per_sm(probe))
224
+ except (AttributeError, RuntimeError):
225
+ self._bw_per_sm = 0.0
226
+
227
+ self._batch = 0
228
+ self._pos_b = None
229
+ self._kv_bstride = 0
230
+ self._gemma = False
231
+ # transformed-input scratch: the nf4 and wide-batch paths write the
232
+ # dense bf16 input here (one row per active token) for the following
233
+ # plain/quant GEMV; sized for a prefill chunk (up to 8 rows)
234
+ self._xg = torch.empty(cm * self._maxk, dtype=dt, device=dev)
235
+ self._build_programs()
236
+
237
+ # ------------------------------------------------------------------
238
+ def _kvp(self, cache, li: int, b: int) -> int:
239
+ t = cache[li]
240
+ return (t[b] if t.dim() == 4 else t).data_ptr()
241
+
242
+ def _build_rows(self, kv_slice: int = 0, batch: bool = False,
243
+ big: bool = False):
244
+ """Program rows for the llama-family path. kv_slice selects the
245
+ batch slice the KV pointers address (per-sequence prefill);
246
+ batch=True emits the batched-decode tail (per-row fp32 logits and
247
+ explicit argmax phases) instead of the fused LM-head amax. In batch
248
+ mode each projection stays fused (input staged in shared) when its
249
+ [B][K] panel fits the budget; the projections whose K is too wide
250
+ transform into a global scratch and read it with a plain GEMV."""
251
+ _tbit = (1 << 20) if self.tiled else 0 # GEMV weight layout flag
252
+ qdim, kvdim = self._qdim, self._kvdim
253
+ embed, lm_head = self._w_embed, self._w_lmhead
254
+ final_norm = self._w_fnorm
255
+ xg = self._xg.data_ptr()
256
+ B = self._batch if batch else 1
257
+ rows, names = [], []
258
+ staged_elems = [0] # widest [B][K] panel any fused projection stages
259
+
260
+ def fits(K):
261
+ return B * K <= _STAGE_BUDGET
262
+
263
+ def note(K):
264
+ staged_elems[0] = max(staged_elems[0], B * K)
265
+
266
+ def _q4(w):
267
+ return isinstance(w, dict) and w.get("q4")
268
+
269
+ def _wgemv(out, w, N, K, epi, resid):
270
+ # the GEMV after a transform-to-scratch phase: nf4-dequant when
271
+ # the weight is packed, otherwise a plain bf16 GEMV
272
+ if _q4(w):
273
+ rows.append(_row(_OP_GEMV_Q4, p1=xg, p2=w["packed"].data_ptr(),
274
+ p3=out, p5=resid, p6=w["absmax"].data_ptr(),
275
+ n=N, k=K, epi=epi))
276
+ else:
277
+ rows.append(_row(_OP_GEMV_PLAIN, p1=xg, p2=w.data_ptr(),
278
+ p3=out, p5=resid, n=N, k=K, it=_tbit,
279
+ epi=epi))
280
+
281
+ def norm_gemv(name, gamma, w, out, N, K, epi, inp, resid=0,
282
+ variant=_IT_RMSNORM):
283
+ if _q4(w) or (big and not fits(K)):
284
+ rows.append(_row(_OP_NORMB, p1=inp, p2=gamma, p3=xg, k=K,
285
+ it=variant, f0=self.eps))
286
+ names.append(name + ".norm")
287
+ _wgemv(out, w, N, K, epi, resid)
288
+ names.append(name)
289
+ else:
290
+ note(K)
291
+ rows.append(_row(_OP_GEMV, p1=inp, p2=w.data_ptr(), p3=out,
292
+ p4=gamma, p5=resid, n=N, k=K,
293
+ it=variant | _tbit, epi=epi, f0=self.eps))
294
+ names.append(name)
295
+
296
+ def glu_gemv(name, gu, w, out, resid, variant=_IT_SWIGLU):
297
+ if _q4(w) or (big and not fits(self.I)):
298
+ rows.append(_row(_OP_GLUB, p1=gu, p3=xg, k=self.I, it=variant))
299
+ names.append(name + ".glu")
300
+ _wgemv(out, w, self.hidden, self.I, _EP_RESID, resid)
301
+ names.append(name)
302
+ else:
303
+ note(self.I)
304
+ rows.append(_row(_OP_GEMV, p1=gu, p2=w.data_ptr(), p3=out,
305
+ p5=resid, n=self.hidden, k=self.I,
306
+ it=variant | _tbit, epi=_EP_RESID))
307
+ names.append(name)
308
+
309
+ def attn_gemv(name, w, out, resid):
310
+ if _q4(w) or (big and not fits(qdim)):
311
+ rows.append(_row(_OP_ATTNFINB, p1=self._partials.data_ptr(),
312
+ p3=xg, k=qdim, hq=self.Hq,
313
+ hkv=self._chunk << 16, d=self.D,
314
+ i0=self._maxch))
315
+ names.append(name + ".fin")
316
+ _wgemv(out, w, self.hidden, qdim, _EP_RESID, resid)
317
+ names.append(name)
318
+ else:
319
+ note(qdim)
320
+ rows.append(_row(_OP_GEMV, p1=self._partials.data_ptr(),
321
+ p2=w.data_ptr(), p3=out, p5=resid,
322
+ n=self.hidden, k=qdim,
323
+ it=_IT_ATTNFIN | _tbit, epi=_EP_RESID,
324
+ hq=self.Hq, hkv=self._chunk << 16, d=self.D,
325
+ i0=self._maxch))
326
+ names.append(name)
327
+
328
+ rows.append(_row(_OP_EMBED, p2=embed.data_ptr(),
329
+ p3=self._hidden.data_ptr(),
330
+ p4=self._parts.data_ptr(),
331
+ p5=self._prompt_in.data_ptr(),
332
+ p6=self._token.data_ptr(),
333
+ n=self._tokens_out.data_ptr(), k=self.hidden))
334
+ names.append("embed")
335
+ scale = 1.0 / (self.D ** 0.5)
336
+ hid = self._hidden.data_ptr()
337
+ for li, lw in enumerate(self._layers):
338
+ kc = self._kvp(self._kcache, li, kv_slice)
339
+ vc = self._kvp(self._vcache, li, kv_slice)
340
+ norm_gemv(f"L{li}.qkv", lw["ln1"].data_ptr(),
341
+ lw["wqkv"], self._qkv.data_ptr(),
342
+ qdim + 2 * kvdim, self.hidden, _EP_STORE, hid)
343
+ rows.append(_row(6, p1=self._qkv.data_ptr(), p2=kc, p3=vc,
344
+ p6=lw["kn"].data_ptr() if self.qk_norm else 0,
345
+ n=self.max_seq, k=1 if self.qk_norm else 0,
346
+ it=_fbits(self.eps),
347
+ epi=self._invf.data_ptr(),
348
+ hq=self.Hq, hkv=self.Hkv, d=self.D))
349
+ names.append(f"L{li}.kvappend")
350
+ rows.append(_row(_OP_ATTN, p1=self._qkv.data_ptr(), p2=kc,
351
+ p3=vc, p4=self._partials.data_ptr(),
352
+ p5=lw["qn"].data_ptr() if self.qk_norm else 0,
353
+ p6=lw["kn"].data_ptr() if self.qk_norm else 0,
354
+ n=self.max_seq, k=1 if self.qk_norm else 0,
355
+ it=_fbits(self.eps),
356
+ epi=self._invf.data_ptr(),
357
+ hq=self.Hq,
358
+ hkv=self.Hkv | (self._chunk << 16),
359
+ d=self.D, f0=scale, i0=self._maxch))
360
+ names.append(f"L{li}.attn")
361
+ attn_gemv(f"L{li}.o", lw["wo"], hid, hid)
362
+ norm_gemv(f"L{li}.gateup", lw["ln2"].data_ptr(),
363
+ lw["wgu"], self._gu.data_ptr(), 2 * self.I,
364
+ self.hidden, _EP_STORE, hid)
365
+ glu_gemv(f"L{li}.down", self._gu.data_ptr(), lw["wdown"], hid, hid)
366
+ if batch or _q4(lm_head):
367
+ norm_gemv("lm_head", final_norm.data_ptr(), lm_head,
368
+ self._logits.data_ptr(), self.V, self.hidden, _EP_F32,
369
+ hid)
370
+ rows.append(_row(_OP_ARGMAX_PART, p1=self._logits.data_ptr(),
371
+ p3=self._parts.data_ptr(), n=self.V))
372
+ names.append("argmax.part")
373
+ rows.append(_row(_OP_ARGMAX_FIN, p1=self._parts.data_ptr(),
374
+ p3=self._token.data_ptr(),
375
+ p4=self._tokens_out.data_ptr(),
376
+ k=self.max_gen))
377
+ names.append("argmax.fin")
378
+ else:
379
+ rows.append(_row(_OP_GEMV, p1=hid, p2=lm_head.data_ptr(),
380
+ p3=self._logits.data_ptr(),
381
+ p4=final_norm.data_ptr(),
382
+ p6=self._parts.data_ptr(), n=self.V,
383
+ k=self.hidden, it=_IT_RMSNORM | _tbit,
384
+ epi=_EP_F32_AMAX, hkv=1, f0=self.eps))
385
+ names.append("lm_head")
386
+ rows.append(_row(_OP_ARGMAX_FIN, p1=self._parts.data_ptr(),
387
+ p3=self._token.data_ptr(),
388
+ p4=self._tokens_out.data_ptr()))
389
+ names.append("argmax.fin")
390
+ se = max(staged_elems[0], self._maxk)
391
+ return rows, names, se
392
+
393
+ def _build_programs(self):
394
+ dev = self.device
395
+ rows, names, _ = self._build_rows(kv_slice=0, batch=False)
396
+ self._prog = torch.tensor(rows, dtype=torch.int64, device=dev)
397
+ self.phase_names = names
398
+ if self._batch:
399
+ big = self._batch > min(8, 16384 // self._maxk)
400
+ rows, _, se = self._build_rows(kv_slice=0, batch=True, big=big)
401
+ self._prog_batch = torch.tensor(rows, dtype=torch.int64,
402
+ device=dev)
403
+ self._batch_stage_elems = int(se)
404
+ self._progs_prefill = [self._prog]
405
+ for b in range(1, self._batch):
406
+ rows, _, _ = self._build_rows(kv_slice=b, batch=False)
407
+ self._progs_prefill.append(
408
+ torch.tensor(rows, dtype=torch.int64, device=dev))
409
+
410
+ def batch_max(self) -> int:
411
+ """Largest batch size this model supports. Up to 8 sequences stage
412
+ in shared; beyond that the transformed input moves to a global
413
+ scratch, bounded by the kernel's register accumulators."""
414
+ try:
415
+ hard = int(ops.mak_batch_maxb())
416
+ except (AttributeError, RuntimeError):
417
+ hard = 8
418
+ return 1 if self._gemma else hard
419
+
420
+ # ------------------------------------------------------------------
421
+ def enable_batch(self, B: int):
422
+ """Allocate per-sequence KV caches, token buffers, and programs for
423
+ batched decode. B up to 8 stages the input in shared; larger B (up
424
+ to batch_max()) routes the transformed input through a global
425
+ scratch so the batch width is not bounded by the projection size."""
426
+ if self._gemma:
427
+ raise ValueError("batched decode covers the llama-family path")
428
+ maxb = self.batch_max()
429
+ if not 1 <= B <= maxb:
430
+ raise ValueError(f"B must be in [1, {maxb}] for this model")
431
+ if self._batch == B:
432
+ return
433
+ dt, dev = torch.bfloat16, self.device
434
+ self._kcache = torch.zeros(self.L, B, self.Hkv, self.max_seq,
435
+ self.D, dtype=dt, device=dev)
436
+ self._vcache = torch.zeros_like(self._kcache)
437
+ self._token = torch.zeros(B, dtype=torch.int32, device=dev)
438
+ self._tokens_out = torch.zeros(B * self.max_gen, dtype=torch.int32,
439
+ device=dev)
440
+ self._logits = torch.empty(B * self.V, dtype=torch.float32,
441
+ device=dev)
442
+ self._parts = torch.zeros(B * self._nblocks, dtype=torch.int64,
443
+ device=dev)
444
+ self._pos_b = torch.zeros(B, dtype=torch.int32, device=dev)
445
+ # transformed-input scratch: one row per active token, and prefill
446
+ # (during generate_batch) still uses up to chunk_m rows
447
+ self._xg = torch.empty(max(8, B) * self._maxk, dtype=dt, device=dev)
448
+ # working buffers hold one row per active sequence; prefill chunks
449
+ # still use up to chunk_m rows, so keep at least the fused capacity
450
+ nb = max(8, B)
451
+ qkvd = self._qdim + 2 * self._kvdim
452
+ self._hidden = torch.empty(nb * self.hidden, dtype=dt, device=dev)
453
+ self._qkv = torch.empty(nb * qkvd, dtype=dt, device=dev)
454
+ self._gu = torch.empty(nb * 2 * self.I, dtype=dt, device=dev)
455
+ self._partials = torch.empty(
456
+ nb * self.Hq * self._maxch * (self.D + 2), dtype=torch.float32,
457
+ device=dev)
458
+ self._kv_bstride = self.Hkv * self.max_seq * self.D
459
+ self._batch = B
460
+ self._build_programs()
461
+
462
+ def decode_batch(self, tokens: Sequence[int], positions: Sequence[int],
463
+ steps: int = 1) -> torch.Tensor:
464
+ """Batched greedy decode: sequence b consumes tokens[b] at
465
+ positions[b]; `steps` in-kernel steps run with per-sequence argmax
466
+ feedback. Returns the generated tokens [B, steps]; the final
467
+ step's fp32 logits are live in batch_logits()."""
468
+ B = self._batch
469
+ if B < 1:
470
+ raise ValueError("call enable_batch(B) first")
471
+ if len(tokens) != B or len(positions) != B:
472
+ raise ValueError("tokens and positions must have B entries")
473
+ if steps < 1 or steps > self.max_gen:
474
+ raise ValueError("steps out of range")
475
+ if max(positions) + steps > self.max_seq:
476
+ raise ValueError("sequence exceeds max_seq")
477
+ self._token.copy_(torch.tensor(list(tokens), dtype=torch.int32))
478
+ self._pos_b.copy_(torch.tensor(list(positions), dtype=torch.int32))
479
+ ops.mak_run_batch(self._prog_batch, self._bar, self._pos_b, steps,
480
+ 0, self._maxk, self._kv_bstride,
481
+ self._batch_stage_elems)
482
+ return self._tokens_out.view(B, self.max_gen)[:, :steps]
483
+
484
+ def batch_logits(self) -> torch.Tensor:
485
+ """fp32 logits [B, V] of the most recent batched step."""
486
+ return self._logits.view(max(self._batch, 1), self.V)
487
+
488
+ def generate_batch(self, prompts: Sequence[Sequence[int]],
489
+ max_new: int) -> List[List[int]]:
490
+ """Greedy generation for a batch of prompts: per-sequence prefill,
491
+ then one batched decode launch. Each sequence's token stream is
492
+ bit-identical to its own single-sequence generate()."""
493
+ B = len(prompts)
494
+ if max_new < 1 or max_new > self.max_gen:
495
+ raise ValueError("max_new out of range")
496
+ self.enable_batch(B)
497
+ firsts: List[int] = []
498
+ for b, ids in enumerate(prompts):
499
+ P = len(ids)
500
+ if P < 1 or P + max_new > self.max_seq:
501
+ raise ValueError("sequence exceeds max_seq")
502
+ self._prompt_in[:P].copy_(
503
+ torch.tensor(list(ids), dtype=torch.int32))
504
+ ops.mak_run_seq(self._progs_prefill[b], self._bar, 0, P, 1 - P,
505
+ P, self._maxk, self._chunk_m)
506
+ firsts.append(int(self._token[0].item()))
507
+ if max_new == 1:
508
+ return [[t] for t in firsts]
509
+ self._token.copy_(torch.tensor(firsts, dtype=torch.int32))
510
+ self._pos_b.copy_(torch.tensor([len(p) for p in prompts],
511
+ dtype=torch.int32))
512
+ ops.mak_run_batch(self._prog_batch, self._bar, self._pos_b,
513
+ max_new - 1, 0, self._maxk, self._kv_bstride,
514
+ self._batch_stage_elems)
515
+ rest = (self._tokens_out.view(B, self.max_gen)[:, :max_new - 1]
516
+ .cpu().tolist())
517
+ return [[firsts[b]] + rest[b] for b in range(B)]
518
+
519
+ # ------------------------------------------------------------------
520
+ @staticmethod
521
+ def _dense_weight(mod):
522
+ """The effective bf16 weight of a linear layer, dequantizing packed
523
+ quantized layers (bitsandbytes 4-bit and 8-bit) so a quantized
524
+ checkpoint loads directly. Dequantization is exact and does not
525
+ depend on the card, so inference on the result keeps every
526
+ guarantee; the weights materialize to bf16, so the quantized memory
527
+ footprint is not preserved (that needs the in-kernel packed path)."""
528
+ w = mod.weight
529
+ qs = getattr(w, "quant_state", None)
530
+ if qs is not None: # bitsandbytes 4-bit (nf4 / fp4)
531
+ import bitsandbytes as bnb
532
+ return bnb.functional.dequantize_4bit(w.data, qs).to(torch.bfloat16)
533
+ if getattr(w, "SCB", None) is not None or hasattr(mod, "SCB"):
534
+ # bitsandbytes 8-bit (LLM.int8): row scales in SCB, int8 in CB
535
+ scb = w.SCB if getattr(w, "SCB", None) is not None else mod.SCB
536
+ cb = w.data if w.data.dtype == torch.int8 else mod.CB
537
+ return (cb.to(torch.float32)
538
+ * (scb.to(torch.float32) / 127.0).unsqueeze(1)
539
+ ).to(torch.bfloat16)
540
+ return w
541
+
542
+ @staticmethod
543
+ def _q4_parts(mod):
544
+ """(packed [N, K/2] uint8, absmax [N*K/64] fp32) for a bitsandbytes
545
+ nf4 layer whose weights stay packed, else None. Double-quantized
546
+ absmax is materialized to fp32 so the kernel needs one scale array."""
547
+ w = getattr(mod, "weight", None)
548
+ qs = getattr(w, "quant_state", None)
549
+ if qs is None or getattr(qs, "quant_type", None) != "nf4":
550
+ return None
551
+ import bitsandbytes as bnb
552
+ n, k = int(qs.shape[0]), int(qs.shape[1])
553
+ if k % 64:
554
+ return None # kernel assumes 64 | K for per-block absmax
555
+ packed = w.data.reshape(n, k // 2).contiguous()
556
+ if getattr(qs, "nested", False):
557
+ absmax = bnb.functional.dequantize_blockwise(
558
+ qs.absmax, qs.state2) + qs.offset
559
+ else:
560
+ absmax = qs.absmax
561
+ return packed, absmax.float().reshape(-1).contiguous()
562
+
563
+ @classmethod
564
+ def _proj(cls, mods):
565
+ """A projection, concatenated across `mods` (q/k/v or gate/up). Stays
566
+ nf4-packed when every part is nf4, else a dense bf16 tensor."""
567
+ parts = [cls._q4_parts(m) for m in mods]
568
+ if all(p is not None for p in parts):
569
+ packed = torch.cat([p[0] for p in parts], 0)
570
+ absmax = torch.cat([p[1] for p in parts], 0)
571
+ return {"q4": True, "packed": packed, "absmax": absmax,
572
+ "N": packed.shape[0], "K": packed.shape[1] * 2}
573
+ return torch.cat([cls._dense_weight(m) for m in mods], 0)
574
+
575
+ @classmethod
576
+ def from_pretrained(cls, model, device="cuda", max_seq: int = 4096,
577
+ max_gen: int = 4096):
578
+ """Build from a transformers model (object or repo id). A quantized
579
+ checkpoint (bitsandbytes 4-bit/8-bit) is accepted directly; its
580
+ weights are dequantized to bf16 at load."""
581
+ if isinstance(model, str):
582
+ from transformers import AutoModelForCausalLM
583
+ model = AutoModelForCausalLM.from_pretrained(
584
+ model, torch_dtype=torch.bfloat16)
585
+ if "gemma4" in getattr(model.config, "model_type", ""):
586
+ return cls._from_gemma4(model, device, max_seq, max_gen)
587
+ hf = model.config
588
+ dec = model.model
589
+ attn0 = dec.layers[0].self_attn
590
+ qk_norm = hasattr(attn0, "q_norm") and attn0.q_norm is not None
591
+ if getattr(attn0.q_proj, "bias", None) is not None:
592
+ raise ValueError("attention biases are not supported")
593
+
594
+ # rope config across transformers API generations: v5 keeps theta and
595
+ # type in a rope_parameters dict; v4 used a rope_theta attribute and
596
+ # an optional rope_scaling dict.
597
+ rp = getattr(hf, "rope_parameters", None)
598
+ if not isinstance(rp, dict):
599
+ rs = getattr(hf, "rope_scaling", None)
600
+ rp = rs if isinstance(rs, dict) else {}
601
+ theta = rp.get("rope_theta")
602
+ if theta is None:
603
+ theta = getattr(hf, "rope_theta", 10000.0)
604
+ rope_type = rp.get("rope_type", rp.get("type", "default"))
605
+ if rope_type not in ("default", "llama3"):
606
+ raise ValueError(f"rope type {rope_type!r} is not supported")
607
+ # take the frequency table transformers computed (scaling included)
608
+ rot = dec.rotary_emb
609
+ att_scale = float(getattr(rot, "attention_scaling", 1.0))
610
+ if att_scale != 1.0:
611
+ raise ValueError("rope attention_scaling != 1 is not supported")
612
+ inv_freq = rot.inv_freq.detach().float()
613
+
614
+ pj = cls._proj
615
+ layers = []
616
+ for lyr in dec.layers:
617
+ a, m = lyr.self_attn, lyr.mlp
618
+ layers.append({
619
+ "wqkv": pj([a.q_proj, a.k_proj, a.v_proj]),
620
+ "wo": pj([a.o_proj]),
621
+ "wgu": pj([m.gate_proj, m.up_proj]),
622
+ "wdown": pj([m.down_proj]),
623
+ "ln1": lyr.input_layernorm.weight,
624
+ "ln2": lyr.post_attention_layernorm.weight,
625
+ "qn": a.q_norm.weight if qk_norm else None,
626
+ "kn": a.k_norm.weight if qk_norm else None,
627
+ })
628
+ weights = {"embed": dec.embed_tokens.weight, "norm": dec.norm.weight,
629
+ "lm_head": pj([model.lm_head]), "layers": layers,
630
+ "inv_freq": inv_freq}
631
+ config = {
632
+ "num_hidden_layers": hf.num_hidden_layers,
633
+ "hidden_size": hf.hidden_size,
634
+ "num_attention_heads": hf.num_attention_heads,
635
+ "num_key_value_heads": getattr(hf, "num_key_value_heads", None),
636
+ "head_dim": getattr(hf, "head_dim", None),
637
+ "intermediate_size": hf.intermediate_size,
638
+ "vocab_size": hf.vocab_size,
639
+ "rope_theta": float(theta),
640
+ "rms_norm_eps": hf.rms_norm_eps,
641
+ "qk_norm": qk_norm,
642
+ }
643
+ return cls(weights, config, device=device, max_seq=max_seq,
644
+ max_gen=max_gen)
645
+
646
+ # ------------------------------------------------------------------
647
+ @classmethod
648
+ def _from_gemma4(cls, model, device, max_seq, max_gen):
649
+ """gemma-4-E2B-it program: gemma norms (fp32, raw weight),
650
+ gelu-tanh gating, post-sublayer norm phases, per-layer-input
651
+ pathway, sliding windows, shared KV, dual head dims, softcapped
652
+ head. Scope is the E2B text model only."""
653
+ self = cls.__new__(cls)
654
+ cfg = model.config.text_config if hasattr(model.config, "text_config") \
655
+ else model.config
656
+ dec = model.model.language_model if hasattr(model.model, "language_model") \
657
+ else model.model
658
+ assert cfg.model_type == "gemma4_text", cfg.model_type
659
+ assert not cfg.enable_moe_block
660
+
661
+ self.L = int(cfg.num_hidden_layers)
662
+ self.hidden = int(cfg.hidden_size)
663
+ self.Hq = int(cfg.num_attention_heads)
664
+ self.V = int(cfg.vocab_size)
665
+ self.eps = float(cfg.rms_norm_eps)
666
+ self.qk_norm = True
667
+ self.max_seq = int(max_seq)
668
+ self.max_gen = int(max_gen)
669
+ self.device = torch.device(device)
670
+ self.tiled = False
671
+ win = int(cfg.sliding_window)
672
+ cap = float(cfg.final_logit_softcapping)
673
+ PL = int(cfg.hidden_size_per_layer_input)
674
+ dt = torch.bfloat16
675
+ dev = self.device
676
+ self._keep = []
677
+
678
+ def pack(t):
679
+ t = t.detach().to(device=dev, dtype=dt).contiguous()
680
+ self._keep.append(t)
681
+ return t
682
+
683
+ def packf(t):
684
+ t = t.detach().to(device=dev, dtype=torch.float32).contiguous()
685
+ self._keep.append(t)
686
+ return t
687
+
688
+ embed = pack(dec.embed_tokens.weight)
689
+ ple_tab = pack(dec.embed_tokens_per_layer.weight)
690
+ plm_w = pack(dec.per_layer_model_projection.weight)
691
+ pln_w = pack(dec.per_layer_projection_norm.weight)
692
+ fnorm = pack(dec.norm.weight)
693
+ rot = dec.rotary_emb
694
+ invf = {lt: packf(getattr(rot, f"{lt}_inv_freq").float())
695
+ for lt in set(cfg.layer_types)}
696
+ ascale = {lt: float(getattr(rot, f"{lt}_attention_scaling"))
697
+ for lt in set(cfg.layer_types)}
698
+
699
+ layers = []
700
+ for li, lyr in enumerate(dec.layers):
701
+ a = lyr.self_attn
702
+ D_l = int(a.head_dim)
703
+ shared = bool(a.is_kv_shared_layer)
704
+ ent = {
705
+ "D": D_l, "shared": shared,
706
+ "lscale": float(lyr.layer_scalar.float().item()),
707
+ "type": cfg.layer_types[li],
708
+ "I": int(lyr.mlp.intermediate_size),
709
+ "qn": pack(a.q_norm.weight),
710
+ "o": pack(a.o_proj.weight),
711
+ "ln_in": pack(lyr.input_layernorm.weight),
712
+ "ln_pa": pack(lyr.post_attention_layernorm.weight),
713
+ "ln_pf": pack(lyr.pre_feedforward_layernorm.weight),
714
+ "ln_ff": pack(lyr.post_feedforward_layernorm.weight),
715
+ "ln_pl": pack(lyr.post_per_layer_input_norm.weight),
716
+ "plig": pack(lyr.per_layer_input_gate.weight),
717
+ "plpr": pack(lyr.per_layer_projection.weight),
718
+ "wgu": pack(torch.cat([lyr.mlp.gate_proj.weight,
719
+ lyr.mlp.up_proj.weight], 0)),
720
+ "wdn": pack(lyr.mlp.down_proj.weight),
721
+ }
722
+ if shared:
723
+ ent["wqkv"] = pack(a.q_proj.weight)
724
+ ent["kn"] = None
725
+ else:
726
+ ent["wqkv"] = pack(torch.cat(
727
+ [a.q_proj.weight, a.k_proj.weight, a.v_proj.weight], 0))
728
+ ent["kn"] = pack(a.k_norm.weight)
729
+ ent["kc"] = torch.zeros(self.max_seq, D_l, dtype=dt,
730
+ device=dev)
731
+ ent["vc"] = torch.zeros(self.max_seq, D_l, dtype=dt,
732
+ device=dev)
733
+ self._keep += [ent["kc"], ent["vc"]]
734
+ layers.append(ent)
735
+ # shared layers read the last non-shared layer of their type
736
+ src = {}
737
+ for li, ent in enumerate(layers):
738
+ if not ent["shared"]:
739
+ src[ent["type"]] = ent
740
+ for ent in layers:
741
+ if ent["shared"]:
742
+ ent["kc"] = src[ent["type"]]["kc"]
743
+ ent["vc"] = src[ent["type"]]["vc"]
744
+
745
+ maxD = max(e["D"] for e in layers)
746
+ maxI = max(e["I"] for e in layers)
747
+ self._maxk = max(self.hidden, self.Hq * maxD, maxI)
748
+ self._chunk_m = 1
749
+ self._chunk = 128
750
+ self._maxch = (self.max_seq + self._chunk - 1) // self._chunk
751
+ self._hidden = torch.empty(self.hidden, dtype=dt, device=dev)
752
+ self._tmp = torch.empty(self.hidden, dtype=dt, device=dev)
753
+ self._qkv = torch.empty((self.Hq + 2) * maxD, dtype=dt, device=dev)
754
+ self._gu = torch.empty(2 * maxI, dtype=dt, device=dev)
755
+ self._t256 = torch.empty(PL, dtype=dt, device=dev)
756
+ self._ctxraw = torch.empty(self.L * PL, dtype=dt, device=dev)
757
+ self._pletok = torch.empty(self.L * PL, dtype=dt, device=dev)
758
+ self._ple = torch.empty(self.L * PL, dtype=dt, device=dev)
759
+ self._logits = torch.empty(self.V, dtype=torch.float32, device=dev)
760
+ self._partials = torch.empty(
761
+ self.Hq * self._maxch * (maxD + 2), dtype=torch.float32,
762
+ device=dev)
763
+ self._token = torch.zeros(1, dtype=torch.int32, device=dev)
764
+ self._prompt_in = torch.zeros(self.max_seq, dtype=torch.int32,
765
+ device=dev)
766
+ self._tokens_out = torch.zeros(self.max_gen, dtype=torch.int32,
767
+ device=dev)
768
+ self._bar = torch.zeros(34, dtype=torch.int32, device=dev)
769
+ probe = torch.zeros(1, 16, dtype=torch.int64, device=dev)
770
+ self._nblocks = int(ops.mak_num_blocks(probe, self._maxk))
771
+ self._parts = torch.zeros(self._nblocks, dtype=torch.int64,
772
+ device=dev)
773
+ try:
774
+ self._bw_per_sm = float(ops.mak_bw_per_sm(probe))
775
+ except (AttributeError, RuntimeError):
776
+ self._bw_per_sm = 0.0
777
+ self._invf = invf[layers[0]["type"]]
778
+ self.Hkv, self.D, self.I = 1, maxD, maxI
779
+ self.theta = 0.0
780
+
781
+ emb_scale = float(torch.tensor(self.hidden ** 0.5,
782
+ dtype=torch.float32).to(dt))
783
+ ple_scale = float(torch.tensor(PL ** 0.5,
784
+ dtype=torch.float32).to(dt))
785
+ proj_scale = float(torch.tensor(self.hidden ** -0.5,
786
+ dtype=torch.float32))
787
+ hid = self._hidden.data_ptr()
788
+ tmp = self._tmp.data_ptr()
789
+ rows, names = [], []
790
+ rows.append(_row(_OP_EMBED, p1=ple_tab.data_ptr(),
791
+ p2=embed.data_ptr(), p3=hid,
792
+ p4=self._parts.data_ptr(),
793
+ p5=self._prompt_in.data_ptr(),
794
+ p6=self._token.data_ptr(),
795
+ n=self._tokens_out.data_ptr(), k=self.hidden,
796
+ it=self._pletok.data_ptr(),
797
+ hkv=self.L * PL, f0=emb_scale,
798
+ i0=_fbits(ple_scale)))
799
+ names.append("embed")
800
+ rows.append(_row(_OP_GEMV, p1=hid, p2=plm_w.data_ptr(),
801
+ p3=self._ctxraw.data_ptr(), n=self.L * PL,
802
+ k=self.hidden, it=_IT_NONE, epi=_EP_STORE))
803
+ names.append("plm")
804
+ rows.append(_row(8, p1=self._ctxraw.data_ptr(),
805
+ p2=pln_w.data_ptr(), p3=self._ple.data_ptr(),
806
+ p4=self._pletok.data_ptr(), n=self.L, k=PL,
807
+ f0=self.eps, i0=_fbits(proj_scale)))
808
+ names.append("plemix")
809
+
810
+ for li, e in enumerate(layers):
811
+ D_l, qdim = e["D"], self.Hq * e["D"]
812
+ lt = e["type"]
813
+ w_l = win if lt == "sliding_attention" else 0
814
+ asb = _fbits(ascale[lt]) if ascale[lt] != 1.0 else 0
815
+ flags = 1 | 2 | (0 if e["shared"] else 4) | \
816
+ (8 if e["shared"] else 0)
817
+ n_qkv = qdim if e["shared"] else qdim + 2 * D_l
818
+ rows.append(_row(_OP_GEMV, p1=hid, p2=e["wqkv"].data_ptr(),
819
+ p3=self._qkv.data_ptr(),
820
+ p4=e["ln_in"].data_ptr(), n=n_qkv,
821
+ k=self.hidden, it=_IT_RMSNORM_G,
822
+ epi=_EP_STORE, f0=self.eps))
823
+ names.append(f"L{li}.qkv")
824
+ rows.append(_row(_OP_ATTN, p1=self._qkv.data_ptr(),
825
+ p2=e["kc"].data_ptr(), p3=e["vc"].data_ptr(),
826
+ p4=self._partials.data_ptr(),
827
+ p5=e["qn"].data_ptr(),
828
+ p6=e["kn"].data_ptr() if e["kn"] is not None
829
+ else 0,
830
+ n=self.max_seq,
831
+ k=flags | (w_l << 16), it=_fbits(self.eps),
832
+ epi=invf[lt].data_ptr(), hq=self.Hq,
833
+ hkv=1 | (self._chunk << 16), d=D_l, f0=1.0,
834
+ i0=self._maxch | (asb << 32)))
835
+ names.append(f"L{li}.attn")
836
+ rows.append(_row(_OP_GEMV, p1=self._partials.data_ptr(),
837
+ p2=e["o"].data_ptr(), p3=tmp,
838
+ n=self.hidden, k=qdim, it=_IT_ATTNFIN,
839
+ epi=_EP_STORE, hq=self.Hq,
840
+ hkv=self._chunk << 16, d=D_l,
841
+ i0=self._maxch | (w_l << 16)))
842
+ names.append(f"L{li}.o")
843
+ rows.append(_row(7, p1=tmp, p2=e["ln_pa"].data_ptr(), p3=hid,
844
+ n=self.hidden, f0=self.eps, i0=_fbits(1.0)))
845
+ names.append(f"L{li}.nr_attn")
846
+ rows.append(_row(_OP_GEMV, p1=hid, p2=e["wgu"].data_ptr(),
847
+ p3=self._gu.data_ptr(),
848
+ p4=e["ln_pf"].data_ptr(), n=2 * e["I"],
849
+ k=self.hidden, it=_IT_RMSNORM_G,
850
+ epi=_EP_STORE, f0=self.eps))
851
+ names.append(f"L{li}.gateup")
852
+ rows.append(_row(_OP_GEMV, p1=self._gu.data_ptr(),
853
+ p2=e["wdn"].data_ptr(), p3=tmp,
854
+ n=self.hidden, k=e["I"], it=_IT_GELU_GLU,
855
+ epi=_EP_STORE))
856
+ names.append(f"L{li}.down")
857
+ rows.append(_row(7, p1=tmp, p2=e["ln_ff"].data_ptr(), p3=hid,
858
+ n=self.hidden, f0=self.eps, i0=_fbits(1.0)))
859
+ names.append(f"L{li}.nr_ffw")
860
+ rows.append(_row(_OP_GEMV, p1=hid, p2=e["plig"].data_ptr(),
861
+ p3=self._t256.data_ptr(),
862
+ p6=self._ple.data_ptr() + li * PL * 2,
863
+ n=PL, k=self.hidden, it=_IT_NONE,
864
+ epi=_EP_GELU_PLE))
865
+ names.append(f"L{li}.plig")
866
+ rows.append(_row(_OP_GEMV, p1=self._t256.data_ptr(),
867
+ p2=e["plpr"].data_ptr(), p3=tmp,
868
+ n=self.hidden, k=PL, it=_IT_NONE,
869
+ epi=_EP_STORE))
870
+ names.append(f"L{li}.plproj")
871
+ rows.append(_row(7, p1=tmp, p2=e["ln_pl"].data_ptr(), p3=hid,
872
+ n=self.hidden, f0=self.eps,
873
+ i0=_fbits(e["lscale"])))
874
+ names.append(f"L{li}.nr_ple")
875
+
876
+ rows.append(_row(_OP_GEMV, p1=hid, p2=embed.data_ptr(),
877
+ p3=self._logits.data_ptr(),
878
+ p4=fnorm.data_ptr(),
879
+ p6=self._parts.data_ptr(), n=self.V,
880
+ k=self.hidden, it=_IT_RMSNORM_G,
881
+ epi=_EP_F32_AMAX_CAP, hkv=1, f0=self.eps,
882
+ i0=_fbits(cap)))
883
+ names.append("lm_head")
884
+ rows.append(_row(_OP_ARGMAX_FIN, p1=self._parts.data_ptr(),
885
+ p3=self._token.data_ptr(),
886
+ p4=self._tokens_out.data_ptr()))
887
+ names.append("argmax.fin")
888
+ self._prog = torch.tensor(rows, dtype=torch.int64, device=dev)
889
+ self.phase_names = names
890
+ self._gemma = True
891
+ self._batch = 0
892
+ return self
893
+
894
+ # ------------------------------------------------------------------
895
+ def decode_step(self, token: int, pos: int, phased: bool = False):
896
+ """One decode step; returns the live fp32 logits buffer [V]."""
897
+ if pos >= self.max_seq:
898
+ raise ValueError("pos exceeds max_seq")
899
+ self._token.fill_(int(token))
900
+ if phased:
901
+ ops.mak_run_phased(self._prog, pos, self.max_gen - 1, False,
902
+ self._maxk)
903
+ else:
904
+ ops.mak_run(self._prog, self._bar, pos, self.max_gen - 1,
905
+ self._maxk)
906
+ return self._logits[:self.V]
907
+
908
+ def decode_step_timed(self, token: int, pos: int):
909
+ """One phase-per-launch step; returns (logits, per-phase ms)."""
910
+ self._token.fill_(int(token))
911
+ ms = ops.mak_run_phased(self._prog, pos, self.max_gen - 1, True,
912
+ self._maxk)
913
+ return self._logits[:self.V], ms
914
+
915
+ def prefill(self, ids: Sequence[int], phased: bool = False):
916
+ """Consume a prompt; returns the last position's logits. Runs in
917
+ chunks of up to chunk_m tokens with weight reads amortized across
918
+ the chunk (bitwise identical to token-by-token consumption)."""
919
+ P = len(ids)
920
+ if phased:
921
+ for i, t in enumerate(ids):
922
+ self.decode_step(int(t), i, phased=True)
923
+ return self._logits
924
+ self._prompt_in[:P].copy_(torch.tensor(ids, dtype=torch.int32))
925
+ ops.mak_run_seq(self._prog, self._bar, 0, P, 1 - P, P, self._maxk,
926
+ self._chunk_m)
927
+ return self._logits[:self.V]
928
+
929
+ def generate(self, prompt_ids: Sequence[int], max_new: int,
930
+ single_launch: bool = True) -> List[int]:
931
+ """Greedy generation. With single_launch (default) the entire call,
932
+ prompt consumption included, is one kernel launch: the in-kernel step
933
+ loop reads prompt tokens from a staged device buffer, then feeds each
934
+ step's argmax to the next step's embedding read. With
935
+ single_launch=False the same computation runs as one launch per
936
+ token; the two paths produce identical tokens.
937
+ """
938
+ P = len(prompt_ids)
939
+ if max_new < 1 or max_new > self.max_gen:
940
+ raise ValueError("max_new out of range")
941
+ if P < 1 or P + max_new > self.max_seq:
942
+ raise ValueError("sequence exceeds max_seq")
943
+ if single_launch:
944
+ self._prompt_in[:P].copy_(
945
+ torch.tensor(prompt_ids, dtype=torch.int32))
946
+ ops.mak_run_seq(self._prog, self._bar, 0, P + max_new - 1,
947
+ 1 - P, P, self._maxk, self._chunk_m)
948
+ return self._tokens_out[:max_new].cpu().tolist()
949
+ pos = 0
950
+ for t in prompt_ids[:-1]:
951
+ self._token.fill_(int(t))
952
+ ops.mak_run(self._prog, self._bar, pos, self.max_gen - 1,
953
+ self._maxk)
954
+ pos += 1
955
+ self._token.fill_(int(prompt_ids[-1]))
956
+ ops.mak_run(self._prog, self._bar, pos, 0, self._maxk)
957
+ pos += 1
958
+ if max_new > 1:
959
+ ops.mak_run_steps(self._prog, self._bar, pos, max_new - 1, 1,
960
+ self._maxk)
961
+ return self._tokens_out[:max_new].cpu().tolist()
962
+
963
+
964
+
build/torch212-cxx11-cu132-x86_64-linux/_model_as_a_kernel_cuda_e33875a.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:effc2eb17efb6c24253a3cf8f08442c69792e6dbda74ed6909650ea26dddcf60
3
+ size 6374560
build/torch212-cxx11-cu132-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _model_as_a_kernel_cuda_e33875a
3
+ ops = torch.ops._model_as_a_kernel_cuda_e33875a
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_model_as_a_kernel_cuda_e33875a::{op_name}"
build/torch212-cxx11-cu132-x86_64-linux/metadata.json ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "model-as-a-kernel",
3
+ "id": "_model_as_a_kernel_cuda_e33875a",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "python-depends": [],
7
+ "backend": {
8
+ "type": "cuda",
9
+ "archs": [
10
+ "10.0",
11
+ "12.0",
12
+ "8.0",
13
+ "8.6",
14
+ "8.9",
15
+ "9.0"
16
+ ]
17
+ },
18
+ "digest": {
19
+ "algorithm": "sha256",
20
+ "files": {
21
+ "__init__.py": "/YX5/zzAOyJacQ7H+j8zaq+xjWyy8w6r0j+ugE1EUs8=",
22
+ "_model_as_a_kernel_cuda_e33875a.abi3.so": "7/wusX77bCQlOjz48IRCxpeS5tvadO1pCWUOom3dz2A=",
23
+ "_ops.py": "Pqj4WKtwPyeDG9grQnzLget4g1jsGUdR7ZMa44e4rhs=",
24
+ "model_as_a_kernel/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
25
+ }
26
+ },
27
+ "provenance": {
28
+ "kernel-builder": {
29
+ "version": "0.17.0-dev0",
30
+ "sha": "05f1ed487f599a320311aa26076ec0ad290c3f68",
31
+ "dirty": false
32
+ },
33
+ "kernel": {
34
+ "sha": "e33875ae4eba42fd136a89c388e806281cd4759c",
35
+ "dirty": false
36
+ }
37
+ }
38
+ }
build/torch212-cxx11-cu132-x86_64-linux/model_as_a_kernel/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import importlib.util
3
+ import sys
4
+ from pathlib import Path
5
+ from types import ModuleType
6
+
7
+
8
+ def _import_from_path(file_path: Path) -> ModuleType:
9
+ # We cannot use the module name as-is, after adding it to `sys.modules`,
10
+ # it would also be used for other imports. So, we make a module name that
11
+ # depends on the path for it to be unique using the hex-encoded hash of
12
+ # the path.
13
+ path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
+ module_name = path_hash
15
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
16
+ if spec is None:
17
+ raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
+ module = importlib.util.module_from_spec(spec)
19
+ if module is None:
20
+ raise ImportError(f"Cannot load module {module_name} from spec")
21
+ sys.modules[module_name] = module
22
+ spec.loader.exec_module(module) # type: ignore
23
+ return module
24
+
25
+
26
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
build/torch213-cxx11-cu126-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,964 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """model-as-a-kernel: an entire llama-family decode step as one kernel launch.
2
+
3
+ A phase interpreter runs the whole forward pass (embedding, every layer's
4
+ norms, QKV, rope, attention over the KV cache, SwiGLU MLP, the LM head, and
5
+ the greedy argmax) inside a single persistent kernel, with a software grid
6
+ barrier between phases. The in-kernel step loop makes an entire greedy
7
+ generation, prompt consumption included, one kernel launch: each step's
8
+ argmax feeds the next step's embedding read without leaving the device.
9
+ """
10
+ import os
11
+ import struct
12
+ from typing import List, Optional, Sequence
13
+
14
+ import torch
15
+
16
+ try:
17
+ from ._ops import ops
18
+ except ImportError: # local development build (torch.utils.cpp_extension)
19
+ class _LocalOps:
20
+ def __getattr__(self, name):
21
+ return getattr(torch.ops.mak_ext, name)
22
+
23
+ ops = _LocalOps()
24
+
25
+ __all__ = ["MegaModel"]
26
+
27
+ _OP_EMBED, _OP_GEMV, _OP_QKV_POST, _OP_ATTN = 0, 1, 2, 3
28
+ _OP_ARGMAX_PART, _OP_ARGMAX_FIN = 4, 5
29
+ _OP_KV_APPEND, _OP_NORMRES, _OP_PLEMIX = 6, 7, 8
30
+ _OP_GEMV_PLAIN, _OP_NORMB, _OP_GLUB, _OP_ATTNFINB = 9, 10, 11, 12
31
+ _OP_GEMV_Q4 = 13
32
+ _IT_NONE, _IT_RMSNORM, _IT_SWIGLU, _IT_ATTNFIN = 0, 1, 2, 3
33
+ _IT_RMSNORM_G, _IT_GELU_GLU = 4, 5
34
+ _EP_GELU_PLE, _EP_F32_AMAX_CAP = 5, 6
35
+ _ITF_REDUCE, _ITF_WRITEBACK = 8, 16
36
+ _EP_STORE, _EP_RESID, _EP_F32, _EP_F32_AMAX, _EP_PARTIAL = 0, 1, 2, 3, 4
37
+ _NSLICE = 4
38
+ # Widest [B][K] input panel (bf16 elements) a batched projection stages in
39
+ # shared before it spills to the global-scratch path. 16384 (32 KB) keeps
40
+ # two blocks resident per SM; the persistent kernel shares one occupancy
41
+ # across all phases, so a wider panel would drop every phase to one block
42
+ # per SM, which measures slower than reading the input from L2.
43
+ _STAGE_BUDGET = 16384
44
+
45
+
46
+ def _fbits(f: float) -> int:
47
+ return struct.unpack("<i", struct.pack("<f", float(f)))[0]
48
+
49
+
50
+ def _row(op, p1=0, p2=0, p3=0, p4=0, p5=0, p6=0, n=0, k=0, it=0, epi=0,
51
+ hq=0, hkv=0, d=0, f0=0.0, i0=0):
52
+ return [int(op), int(p1), int(p2), int(p3), int(p4), int(p5), int(p6),
53
+ int(n), int(k), int(it), int(epi), int(hq), int(hkv), int(d),
54
+ _fbits(f0), int(i0)]
55
+
56
+
57
+ class MegaModel:
58
+ """A packed llama-family decoder whose decode step is one kernel launch.
59
+
60
+ Supported architecture: RMSNorm decoder blocks with rotary attention
61
+ (GQA, optional Qwen3-style per-head qk RMSNorm), SwiGLU MLP, no attention
62
+ or MLP biases, plain (unscaled) rope, batch size 1. This covers SmolLM2,
63
+ TinyLlama, Qwen3 dense, Llama-class checkpoints without rope scaling,
64
+ and similar models.
65
+ """
66
+
67
+ def __init__(self, weights: dict, config: dict, device="cuda",
68
+ max_seq: int = 4096, max_gen: int = 4096):
69
+ cfg = dict(config)
70
+ self.L = int(cfg["num_hidden_layers"])
71
+ self.hidden = int(cfg["hidden_size"])
72
+ self.Hq = int(cfg["num_attention_heads"])
73
+ self.Hkv = int(cfg.get("num_key_value_heads") or self.Hq)
74
+ self.D = int(cfg.get("head_dim") or self.hidden // self.Hq)
75
+ self.I = int(cfg["intermediate_size"])
76
+ self.V = int(cfg["vocab_size"])
77
+ self.theta = float(cfg.get("rope_theta", 10000.0))
78
+ self.eps = float(cfg.get("rms_norm_eps", 1e-6))
79
+ self.qk_norm = bool(cfg.get("qk_norm", False))
80
+ self.max_seq = int(max_seq)
81
+ self.max_gen = int(max_gen)
82
+ self.device = torch.device(device)
83
+
84
+ if self.D % 2 or self.D > 256:
85
+ raise ValueError("head_dim must be even and <= 256")
86
+ if self.hidden % 8 or self.I % 8 or (self.Hq * self.D) % 8:
87
+ raise ValueError(
88
+ "hidden, intermediate, and Hq*head_dim must be multiples of 8")
89
+ if self.Hq % self.Hkv:
90
+ raise ValueError("num_attention_heads must be divisible by num_key_value_heads")
91
+
92
+ dt = torch.bfloat16
93
+ dev = self.device
94
+
95
+ def pack(t):
96
+ t = t.detach().to(device=dev, dtype=dt).contiguous()
97
+ self._keep.append(t)
98
+ return t
99
+
100
+ # Row-tile-of-8 weight layout [N/8][K/8][8][8] (MAK_TILED=1): the
101
+ # eight warps of a block read one contiguous 4KB stream per step
102
+ # instead of eight 4KB-strided pieces. Measured slower on every
103
+ # card (H200 +15 percent, RTX PRO +9, Ada +14 — per-warp stream
104
+ # locality beats per-block contiguity on all of them), so the
105
+ # default is the plain layout; the flag rides in the program and
106
+ # both layouts compute identical bits.
107
+ env_t = os.environ.get("MAK_TILED", "").strip()
108
+ self.tiled = env_t == "1"
109
+
110
+ def pack_tiled(t):
111
+ t = t.detach().to(device=dev, dtype=dt).contiguous()
112
+ if not self.tiled:
113
+ self._keep.append(t)
114
+ return t
115
+ n, k = t.shape
116
+ assert k % 8 == 0
117
+ n8 = (n + 7) // 8 * 8
118
+ if n8 != n:
119
+ t = torch.cat(
120
+ [t, torch.zeros(n8 - n, k, dtype=dt, device=dev)], 0)
121
+ t = t.view(n8 // 8, 8, k // 8, 8).permute(0, 2, 1, 3).contiguous()
122
+ self._keep.append(t)
123
+ return t
124
+
125
+ self._keep: List[torch.Tensor] = []
126
+ embed = pack(weights["embed"])
127
+ final_norm = pack(weights["norm"])
128
+ lm_head = weights["lm_head"] # packed below (dense-tiled or nf4)
129
+ inv_freq = weights.get("inv_freq")
130
+ if inv_freq is None:
131
+ d_idx = torch.arange(0, self.D, 2, dtype=torch.float32)
132
+ inv_freq = 1.0 / (self.theta ** (d_idx / self.D))
133
+ inv_freq = inv_freq.detach().to(device=dev,
134
+ dtype=torch.float32).contiguous()
135
+ if inv_freq.numel() != self.D // 2:
136
+ raise ValueError("inv_freq length must be head_dim / 2")
137
+ self._keep.append(inv_freq)
138
+ self._invf = inv_freq
139
+
140
+ qdim, kvdim = self.Hq * self.D, self.Hkv * self.D
141
+
142
+ def _is_q4(w):
143
+ return isinstance(w, dict) and w.get("q4")
144
+
145
+ def pack_w(w):
146
+ # nf4-packed weights move to the device intact; dense weights take
147
+ # the usual (optionally row-tiled) bf16 packing.
148
+ if _is_q4(w):
149
+ pk = w["packed"].to(dev).contiguous()
150
+ am = w["absmax"].to(device=dev, dtype=torch.float32).contiguous()
151
+ self._keep.append(pk)
152
+ self._keep.append(am)
153
+ return {"q4": True, "packed": pk, "absmax": am,
154
+ "N": int(w["N"]), "K": int(w["K"])}
155
+ return pack_tiled(w)
156
+
157
+ def _wshape(w, expected):
158
+ got = (int(w["N"]), int(w["K"])) if _is_q4(w) else tuple(w.shape)
159
+ assert got == expected, (got, expected)
160
+
161
+ self._has_q4 = False
162
+ self._layers = []
163
+ for lw in weights["layers"]:
164
+ _wshape(lw["wqkv"], (qdim + 2 * kvdim, self.hidden))
165
+ _wshape(lw["wo"], (self.hidden, qdim))
166
+ _wshape(lw["wgu"], (2 * self.I, self.hidden))
167
+ _wshape(lw["wdown"], (self.hidden, self.I))
168
+ layer = {
169
+ "wqkv": pack_w(lw["wqkv"]), "wo": pack_w(lw["wo"]),
170
+ "wgu": pack_w(lw["wgu"]), "wdown": pack_w(lw["wdown"]),
171
+ "ln1": pack(lw["ln1"]), "ln2": pack(lw["ln2"]),
172
+ "qn": pack(lw["qn"]) if self.qk_norm else None,
173
+ "kn": pack(lw["kn"]) if self.qk_norm else None,
174
+ }
175
+ self._has_q4 |= any(_is_q4(layer[n]) for n in
176
+ ("wqkv", "wo", "wgu", "wdown"))
177
+ self._layers.append(layer)
178
+ assert len(self._layers) == self.L
179
+ assert embed.shape == (self.V, self.hidden)
180
+ lm_head = pack_w(lm_head)
181
+ self._has_q4 |= _is_q4(lm_head)
182
+ if not _is_q4(lm_head):
183
+ assert lm_head.numel() >= self.V * self.hidden # row-tiled, padded
184
+ self._w_embed, self._w_fnorm, self._w_lmhead = (embed, final_norm,
185
+ lm_head)
186
+ self._qdim, self._kvdim = qdim, kvdim
187
+
188
+ # Working buffers. Pointers to these are baked into the program, so
189
+ # they (like the packed weights) live for the model's lifetime.
190
+ self._maxk = max(self.hidden, qdim, self.I)
191
+ # prefill chunk size, bounded by the staging shared-memory budget
192
+ # (16K bf16 elements; the cp.async weight ring rides alongside)
193
+ self._chunk_m = max(1, min(8, 16384 // self._maxk))
194
+ cm = 8 # buffers sized for the maximum chunk
195
+ self._hidden = torch.empty(cm * self.hidden, dtype=dt, device=dev)
196
+ self._qkv = torch.empty(cm * (qdim + 2 * kvdim), dtype=dt, device=dev)
197
+ self._gu = torch.empty(cm * 2 * self.I, dtype=dt, device=dev)
198
+ self._logits = torch.empty(self.V, dtype=torch.float32, device=dev)
199
+ # attention chunk length (kernel maximum 128); shorter chunks give
200
+ # the attention phase more grid parallelism at the cost of more
201
+ # softmax partials to finalize
202
+ env_ch = os.environ.get("MAK_CHUNK", "").strip()
203
+ self._chunk = int(env_ch) if env_ch in ("32", "64", "128") else 128
204
+ self._maxch = (self.max_seq + self._chunk - 1) // self._chunk
205
+ self._partials = torch.empty(
206
+ cm * self.Hq * self._maxch * (self.D + 2), dtype=torch.float32,
207
+ device=dev)
208
+ self._kcache = torch.zeros(self.L, self.Hkv, self.max_seq, self.D,
209
+ dtype=dt, device=dev)
210
+ self._vcache = torch.zeros(self.L, self.Hkv, self.max_seq, self.D,
211
+ dtype=dt, device=dev)
212
+ self._token = torch.zeros(1, dtype=torch.int32, device=dev)
213
+ self._prompt_in = torch.zeros(self.max_seq, dtype=torch.int32,
214
+ device=dev)
215
+ self._tokens_out = torch.zeros(self.max_gen, dtype=torch.int32,
216
+ device=dev)
217
+ self._bar = torch.zeros(34, dtype=torch.int32, device=dev)
218
+
219
+ probe = torch.zeros(1, 16, dtype=torch.int64, device=dev)
220
+ self._nblocks = int(ops.mak_num_blocks(probe, self._maxk))
221
+ self._parts = torch.zeros(self._nblocks, dtype=torch.int64, device=dev)
222
+ try:
223
+ self._bw_per_sm = float(ops.mak_bw_per_sm(probe))
224
+ except (AttributeError, RuntimeError):
225
+ self._bw_per_sm = 0.0
226
+
227
+ self._batch = 0
228
+ self._pos_b = None
229
+ self._kv_bstride = 0
230
+ self._gemma = False
231
+ # transformed-input scratch: the nf4 and wide-batch paths write the
232
+ # dense bf16 input here (one row per active token) for the following
233
+ # plain/quant GEMV; sized for a prefill chunk (up to 8 rows)
234
+ self._xg = torch.empty(cm * self._maxk, dtype=dt, device=dev)
235
+ self._build_programs()
236
+
237
+ # ------------------------------------------------------------------
238
+ def _kvp(self, cache, li: int, b: int) -> int:
239
+ t = cache[li]
240
+ return (t[b] if t.dim() == 4 else t).data_ptr()
241
+
242
+ def _build_rows(self, kv_slice: int = 0, batch: bool = False,
243
+ big: bool = False):
244
+ """Program rows for the llama-family path. kv_slice selects the
245
+ batch slice the KV pointers address (per-sequence prefill);
246
+ batch=True emits the batched-decode tail (per-row fp32 logits and
247
+ explicit argmax phases) instead of the fused LM-head amax. In batch
248
+ mode each projection stays fused (input staged in shared) when its
249
+ [B][K] panel fits the budget; the projections whose K is too wide
250
+ transform into a global scratch and read it with a plain GEMV."""
251
+ _tbit = (1 << 20) if self.tiled else 0 # GEMV weight layout flag
252
+ qdim, kvdim = self._qdim, self._kvdim
253
+ embed, lm_head = self._w_embed, self._w_lmhead
254
+ final_norm = self._w_fnorm
255
+ xg = self._xg.data_ptr()
256
+ B = self._batch if batch else 1
257
+ rows, names = [], []
258
+ staged_elems = [0] # widest [B][K] panel any fused projection stages
259
+
260
+ def fits(K):
261
+ return B * K <= _STAGE_BUDGET
262
+
263
+ def note(K):
264
+ staged_elems[0] = max(staged_elems[0], B * K)
265
+
266
+ def _q4(w):
267
+ return isinstance(w, dict) and w.get("q4")
268
+
269
+ def _wgemv(out, w, N, K, epi, resid):
270
+ # the GEMV after a transform-to-scratch phase: nf4-dequant when
271
+ # the weight is packed, otherwise a plain bf16 GEMV
272
+ if _q4(w):
273
+ rows.append(_row(_OP_GEMV_Q4, p1=xg, p2=w["packed"].data_ptr(),
274
+ p3=out, p5=resid, p6=w["absmax"].data_ptr(),
275
+ n=N, k=K, epi=epi))
276
+ else:
277
+ rows.append(_row(_OP_GEMV_PLAIN, p1=xg, p2=w.data_ptr(),
278
+ p3=out, p5=resid, n=N, k=K, it=_tbit,
279
+ epi=epi))
280
+
281
+ def norm_gemv(name, gamma, w, out, N, K, epi, inp, resid=0,
282
+ variant=_IT_RMSNORM):
283
+ if _q4(w) or (big and not fits(K)):
284
+ rows.append(_row(_OP_NORMB, p1=inp, p2=gamma, p3=xg, k=K,
285
+ it=variant, f0=self.eps))
286
+ names.append(name + ".norm")
287
+ _wgemv(out, w, N, K, epi, resid)
288
+ names.append(name)
289
+ else:
290
+ note(K)
291
+ rows.append(_row(_OP_GEMV, p1=inp, p2=w.data_ptr(), p3=out,
292
+ p4=gamma, p5=resid, n=N, k=K,
293
+ it=variant | _tbit, epi=epi, f0=self.eps))
294
+ names.append(name)
295
+
296
+ def glu_gemv(name, gu, w, out, resid, variant=_IT_SWIGLU):
297
+ if _q4(w) or (big and not fits(self.I)):
298
+ rows.append(_row(_OP_GLUB, p1=gu, p3=xg, k=self.I, it=variant))
299
+ names.append(name + ".glu")
300
+ _wgemv(out, w, self.hidden, self.I, _EP_RESID, resid)
301
+ names.append(name)
302
+ else:
303
+ note(self.I)
304
+ rows.append(_row(_OP_GEMV, p1=gu, p2=w.data_ptr(), p3=out,
305
+ p5=resid, n=self.hidden, k=self.I,
306
+ it=variant | _tbit, epi=_EP_RESID))
307
+ names.append(name)
308
+
309
+ def attn_gemv(name, w, out, resid):
310
+ if _q4(w) or (big and not fits(qdim)):
311
+ rows.append(_row(_OP_ATTNFINB, p1=self._partials.data_ptr(),
312
+ p3=xg, k=qdim, hq=self.Hq,
313
+ hkv=self._chunk << 16, d=self.D,
314
+ i0=self._maxch))
315
+ names.append(name + ".fin")
316
+ _wgemv(out, w, self.hidden, qdim, _EP_RESID, resid)
317
+ names.append(name)
318
+ else:
319
+ note(qdim)
320
+ rows.append(_row(_OP_GEMV, p1=self._partials.data_ptr(),
321
+ p2=w.data_ptr(), p3=out, p5=resid,
322
+ n=self.hidden, k=qdim,
323
+ it=_IT_ATTNFIN | _tbit, epi=_EP_RESID,
324
+ hq=self.Hq, hkv=self._chunk << 16, d=self.D,
325
+ i0=self._maxch))
326
+ names.append(name)
327
+
328
+ rows.append(_row(_OP_EMBED, p2=embed.data_ptr(),
329
+ p3=self._hidden.data_ptr(),
330
+ p4=self._parts.data_ptr(),
331
+ p5=self._prompt_in.data_ptr(),
332
+ p6=self._token.data_ptr(),
333
+ n=self._tokens_out.data_ptr(), k=self.hidden))
334
+ names.append("embed")
335
+ scale = 1.0 / (self.D ** 0.5)
336
+ hid = self._hidden.data_ptr()
337
+ for li, lw in enumerate(self._layers):
338
+ kc = self._kvp(self._kcache, li, kv_slice)
339
+ vc = self._kvp(self._vcache, li, kv_slice)
340
+ norm_gemv(f"L{li}.qkv", lw["ln1"].data_ptr(),
341
+ lw["wqkv"], self._qkv.data_ptr(),
342
+ qdim + 2 * kvdim, self.hidden, _EP_STORE, hid)
343
+ rows.append(_row(6, p1=self._qkv.data_ptr(), p2=kc, p3=vc,
344
+ p6=lw["kn"].data_ptr() if self.qk_norm else 0,
345
+ n=self.max_seq, k=1 if self.qk_norm else 0,
346
+ it=_fbits(self.eps),
347
+ epi=self._invf.data_ptr(),
348
+ hq=self.Hq, hkv=self.Hkv, d=self.D))
349
+ names.append(f"L{li}.kvappend")
350
+ rows.append(_row(_OP_ATTN, p1=self._qkv.data_ptr(), p2=kc,
351
+ p3=vc, p4=self._partials.data_ptr(),
352
+ p5=lw["qn"].data_ptr() if self.qk_norm else 0,
353
+ p6=lw["kn"].data_ptr() if self.qk_norm else 0,
354
+ n=self.max_seq, k=1 if self.qk_norm else 0,
355
+ it=_fbits(self.eps),
356
+ epi=self._invf.data_ptr(),
357
+ hq=self.Hq,
358
+ hkv=self.Hkv | (self._chunk << 16),
359
+ d=self.D, f0=scale, i0=self._maxch))
360
+ names.append(f"L{li}.attn")
361
+ attn_gemv(f"L{li}.o", lw["wo"], hid, hid)
362
+ norm_gemv(f"L{li}.gateup", lw["ln2"].data_ptr(),
363
+ lw["wgu"], self._gu.data_ptr(), 2 * self.I,
364
+ self.hidden, _EP_STORE, hid)
365
+ glu_gemv(f"L{li}.down", self._gu.data_ptr(), lw["wdown"], hid, hid)
366
+ if batch or _q4(lm_head):
367
+ norm_gemv("lm_head", final_norm.data_ptr(), lm_head,
368
+ self._logits.data_ptr(), self.V, self.hidden, _EP_F32,
369
+ hid)
370
+ rows.append(_row(_OP_ARGMAX_PART, p1=self._logits.data_ptr(),
371
+ p3=self._parts.data_ptr(), n=self.V))
372
+ names.append("argmax.part")
373
+ rows.append(_row(_OP_ARGMAX_FIN, p1=self._parts.data_ptr(),
374
+ p3=self._token.data_ptr(),
375
+ p4=self._tokens_out.data_ptr(),
376
+ k=self.max_gen))
377
+ names.append("argmax.fin")
378
+ else:
379
+ rows.append(_row(_OP_GEMV, p1=hid, p2=lm_head.data_ptr(),
380
+ p3=self._logits.data_ptr(),
381
+ p4=final_norm.data_ptr(),
382
+ p6=self._parts.data_ptr(), n=self.V,
383
+ k=self.hidden, it=_IT_RMSNORM | _tbit,
384
+ epi=_EP_F32_AMAX, hkv=1, f0=self.eps))
385
+ names.append("lm_head")
386
+ rows.append(_row(_OP_ARGMAX_FIN, p1=self._parts.data_ptr(),
387
+ p3=self._token.data_ptr(),
388
+ p4=self._tokens_out.data_ptr()))
389
+ names.append("argmax.fin")
390
+ se = max(staged_elems[0], self._maxk)
391
+ return rows, names, se
392
+
393
+ def _build_programs(self):
394
+ dev = self.device
395
+ rows, names, _ = self._build_rows(kv_slice=0, batch=False)
396
+ self._prog = torch.tensor(rows, dtype=torch.int64, device=dev)
397
+ self.phase_names = names
398
+ if self._batch:
399
+ big = self._batch > min(8, 16384 // self._maxk)
400
+ rows, _, se = self._build_rows(kv_slice=0, batch=True, big=big)
401
+ self._prog_batch = torch.tensor(rows, dtype=torch.int64,
402
+ device=dev)
403
+ self._batch_stage_elems = int(se)
404
+ self._progs_prefill = [self._prog]
405
+ for b in range(1, self._batch):
406
+ rows, _, _ = self._build_rows(kv_slice=b, batch=False)
407
+ self._progs_prefill.append(
408
+ torch.tensor(rows, dtype=torch.int64, device=dev))
409
+
410
+ def batch_max(self) -> int:
411
+ """Largest batch size this model supports. Up to 8 sequences stage
412
+ in shared; beyond that the transformed input moves to a global
413
+ scratch, bounded by the kernel's register accumulators."""
414
+ try:
415
+ hard = int(ops.mak_batch_maxb())
416
+ except (AttributeError, RuntimeError):
417
+ hard = 8
418
+ return 1 if self._gemma else hard
419
+
420
+ # ------------------------------------------------------------------
421
+ def enable_batch(self, B: int):
422
+ """Allocate per-sequence KV caches, token buffers, and programs for
423
+ batched decode. B up to 8 stages the input in shared; larger B (up
424
+ to batch_max()) routes the transformed input through a global
425
+ scratch so the batch width is not bounded by the projection size."""
426
+ if self._gemma:
427
+ raise ValueError("batched decode covers the llama-family path")
428
+ maxb = self.batch_max()
429
+ if not 1 <= B <= maxb:
430
+ raise ValueError(f"B must be in [1, {maxb}] for this model")
431
+ if self._batch == B:
432
+ return
433
+ dt, dev = torch.bfloat16, self.device
434
+ self._kcache = torch.zeros(self.L, B, self.Hkv, self.max_seq,
435
+ self.D, dtype=dt, device=dev)
436
+ self._vcache = torch.zeros_like(self._kcache)
437
+ self._token = torch.zeros(B, dtype=torch.int32, device=dev)
438
+ self._tokens_out = torch.zeros(B * self.max_gen, dtype=torch.int32,
439
+ device=dev)
440
+ self._logits = torch.empty(B * self.V, dtype=torch.float32,
441
+ device=dev)
442
+ self._parts = torch.zeros(B * self._nblocks, dtype=torch.int64,
443
+ device=dev)
444
+ self._pos_b = torch.zeros(B, dtype=torch.int32, device=dev)
445
+ # transformed-input scratch: one row per active token, and prefill
446
+ # (during generate_batch) still uses up to chunk_m rows
447
+ self._xg = torch.empty(max(8, B) * self._maxk, dtype=dt, device=dev)
448
+ # working buffers hold one row per active sequence; prefill chunks
449
+ # still use up to chunk_m rows, so keep at least the fused capacity
450
+ nb = max(8, B)
451
+ qkvd = self._qdim + 2 * self._kvdim
452
+ self._hidden = torch.empty(nb * self.hidden, dtype=dt, device=dev)
453
+ self._qkv = torch.empty(nb * qkvd, dtype=dt, device=dev)
454
+ self._gu = torch.empty(nb * 2 * self.I, dtype=dt, device=dev)
455
+ self._partials = torch.empty(
456
+ nb * self.Hq * self._maxch * (self.D + 2), dtype=torch.float32,
457
+ device=dev)
458
+ self._kv_bstride = self.Hkv * self.max_seq * self.D
459
+ self._batch = B
460
+ self._build_programs()
461
+
462
+ def decode_batch(self, tokens: Sequence[int], positions: Sequence[int],
463
+ steps: int = 1) -> torch.Tensor:
464
+ """Batched greedy decode: sequence b consumes tokens[b] at
465
+ positions[b]; `steps` in-kernel steps run with per-sequence argmax
466
+ feedback. Returns the generated tokens [B, steps]; the final
467
+ step's fp32 logits are live in batch_logits()."""
468
+ B = self._batch
469
+ if B < 1:
470
+ raise ValueError("call enable_batch(B) first")
471
+ if len(tokens) != B or len(positions) != B:
472
+ raise ValueError("tokens and positions must have B entries")
473
+ if steps < 1 or steps > self.max_gen:
474
+ raise ValueError("steps out of range")
475
+ if max(positions) + steps > self.max_seq:
476
+ raise ValueError("sequence exceeds max_seq")
477
+ self._token.copy_(torch.tensor(list(tokens), dtype=torch.int32))
478
+ self._pos_b.copy_(torch.tensor(list(positions), dtype=torch.int32))
479
+ ops.mak_run_batch(self._prog_batch, self._bar, self._pos_b, steps,
480
+ 0, self._maxk, self._kv_bstride,
481
+ self._batch_stage_elems)
482
+ return self._tokens_out.view(B, self.max_gen)[:, :steps]
483
+
484
+ def batch_logits(self) -> torch.Tensor:
485
+ """fp32 logits [B, V] of the most recent batched step."""
486
+ return self._logits.view(max(self._batch, 1), self.V)
487
+
488
+ def generate_batch(self, prompts: Sequence[Sequence[int]],
489
+ max_new: int) -> List[List[int]]:
490
+ """Greedy generation for a batch of prompts: per-sequence prefill,
491
+ then one batched decode launch. Each sequence's token stream is
492
+ bit-identical to its own single-sequence generate()."""
493
+ B = len(prompts)
494
+ if max_new < 1 or max_new > self.max_gen:
495
+ raise ValueError("max_new out of range")
496
+ self.enable_batch(B)
497
+ firsts: List[int] = []
498
+ for b, ids in enumerate(prompts):
499
+ P = len(ids)
500
+ if P < 1 or P + max_new > self.max_seq:
501
+ raise ValueError("sequence exceeds max_seq")
502
+ self._prompt_in[:P].copy_(
503
+ torch.tensor(list(ids), dtype=torch.int32))
504
+ ops.mak_run_seq(self._progs_prefill[b], self._bar, 0, P, 1 - P,
505
+ P, self._maxk, self._chunk_m)
506
+ firsts.append(int(self._token[0].item()))
507
+ if max_new == 1:
508
+ return [[t] for t in firsts]
509
+ self._token.copy_(torch.tensor(firsts, dtype=torch.int32))
510
+ self._pos_b.copy_(torch.tensor([len(p) for p in prompts],
511
+ dtype=torch.int32))
512
+ ops.mak_run_batch(self._prog_batch, self._bar, self._pos_b,
513
+ max_new - 1, 0, self._maxk, self._kv_bstride,
514
+ self._batch_stage_elems)
515
+ rest = (self._tokens_out.view(B, self.max_gen)[:, :max_new - 1]
516
+ .cpu().tolist())
517
+ return [[firsts[b]] + rest[b] for b in range(B)]
518
+
519
+ # ------------------------------------------------------------------
520
+ @staticmethod
521
+ def _dense_weight(mod):
522
+ """The effective bf16 weight of a linear layer, dequantizing packed
523
+ quantized layers (bitsandbytes 4-bit and 8-bit) so a quantized
524
+ checkpoint loads directly. Dequantization is exact and does not
525
+ depend on the card, so inference on the result keeps every
526
+ guarantee; the weights materialize to bf16, so the quantized memory
527
+ footprint is not preserved (that needs the in-kernel packed path)."""
528
+ w = mod.weight
529
+ qs = getattr(w, "quant_state", None)
530
+ if qs is not None: # bitsandbytes 4-bit (nf4 / fp4)
531
+ import bitsandbytes as bnb
532
+ return bnb.functional.dequantize_4bit(w.data, qs).to(torch.bfloat16)
533
+ if getattr(w, "SCB", None) is not None or hasattr(mod, "SCB"):
534
+ # bitsandbytes 8-bit (LLM.int8): row scales in SCB, int8 in CB
535
+ scb = w.SCB if getattr(w, "SCB", None) is not None else mod.SCB
536
+ cb = w.data if w.data.dtype == torch.int8 else mod.CB
537
+ return (cb.to(torch.float32)
538
+ * (scb.to(torch.float32) / 127.0).unsqueeze(1)
539
+ ).to(torch.bfloat16)
540
+ return w
541
+
542
+ @staticmethod
543
+ def _q4_parts(mod):
544
+ """(packed [N, K/2] uint8, absmax [N*K/64] fp32) for a bitsandbytes
545
+ nf4 layer whose weights stay packed, else None. Double-quantized
546
+ absmax is materialized to fp32 so the kernel needs one scale array."""
547
+ w = getattr(mod, "weight", None)
548
+ qs = getattr(w, "quant_state", None)
549
+ if qs is None or getattr(qs, "quant_type", None) != "nf4":
550
+ return None
551
+ import bitsandbytes as bnb
552
+ n, k = int(qs.shape[0]), int(qs.shape[1])
553
+ if k % 64:
554
+ return None # kernel assumes 64 | K for per-block absmax
555
+ packed = w.data.reshape(n, k // 2).contiguous()
556
+ if getattr(qs, "nested", False):
557
+ absmax = bnb.functional.dequantize_blockwise(
558
+ qs.absmax, qs.state2) + qs.offset
559
+ else:
560
+ absmax = qs.absmax
561
+ return packed, absmax.float().reshape(-1).contiguous()
562
+
563
+ @classmethod
564
+ def _proj(cls, mods):
565
+ """A projection, concatenated across `mods` (q/k/v or gate/up). Stays
566
+ nf4-packed when every part is nf4, else a dense bf16 tensor."""
567
+ parts = [cls._q4_parts(m) for m in mods]
568
+ if all(p is not None for p in parts):
569
+ packed = torch.cat([p[0] for p in parts], 0)
570
+ absmax = torch.cat([p[1] for p in parts], 0)
571
+ return {"q4": True, "packed": packed, "absmax": absmax,
572
+ "N": packed.shape[0], "K": packed.shape[1] * 2}
573
+ return torch.cat([cls._dense_weight(m) for m in mods], 0)
574
+
575
+ @classmethod
576
+ def from_pretrained(cls, model, device="cuda", max_seq: int = 4096,
577
+ max_gen: int = 4096):
578
+ """Build from a transformers model (object or repo id). A quantized
579
+ checkpoint (bitsandbytes 4-bit/8-bit) is accepted directly; its
580
+ weights are dequantized to bf16 at load."""
581
+ if isinstance(model, str):
582
+ from transformers import AutoModelForCausalLM
583
+ model = AutoModelForCausalLM.from_pretrained(
584
+ model, torch_dtype=torch.bfloat16)
585
+ if "gemma4" in getattr(model.config, "model_type", ""):
586
+ return cls._from_gemma4(model, device, max_seq, max_gen)
587
+ hf = model.config
588
+ dec = model.model
589
+ attn0 = dec.layers[0].self_attn
590
+ qk_norm = hasattr(attn0, "q_norm") and attn0.q_norm is not None
591
+ if getattr(attn0.q_proj, "bias", None) is not None:
592
+ raise ValueError("attention biases are not supported")
593
+
594
+ # rope config across transformers API generations: v5 keeps theta and
595
+ # type in a rope_parameters dict; v4 used a rope_theta attribute and
596
+ # an optional rope_scaling dict.
597
+ rp = getattr(hf, "rope_parameters", None)
598
+ if not isinstance(rp, dict):
599
+ rs = getattr(hf, "rope_scaling", None)
600
+ rp = rs if isinstance(rs, dict) else {}
601
+ theta = rp.get("rope_theta")
602
+ if theta is None:
603
+ theta = getattr(hf, "rope_theta", 10000.0)
604
+ rope_type = rp.get("rope_type", rp.get("type", "default"))
605
+ if rope_type not in ("default", "llama3"):
606
+ raise ValueError(f"rope type {rope_type!r} is not supported")
607
+ # take the frequency table transformers computed (scaling included)
608
+ rot = dec.rotary_emb
609
+ att_scale = float(getattr(rot, "attention_scaling", 1.0))
610
+ if att_scale != 1.0:
611
+ raise ValueError("rope attention_scaling != 1 is not supported")
612
+ inv_freq = rot.inv_freq.detach().float()
613
+
614
+ pj = cls._proj
615
+ layers = []
616
+ for lyr in dec.layers:
617
+ a, m = lyr.self_attn, lyr.mlp
618
+ layers.append({
619
+ "wqkv": pj([a.q_proj, a.k_proj, a.v_proj]),
620
+ "wo": pj([a.o_proj]),
621
+ "wgu": pj([m.gate_proj, m.up_proj]),
622
+ "wdown": pj([m.down_proj]),
623
+ "ln1": lyr.input_layernorm.weight,
624
+ "ln2": lyr.post_attention_layernorm.weight,
625
+ "qn": a.q_norm.weight if qk_norm else None,
626
+ "kn": a.k_norm.weight if qk_norm else None,
627
+ })
628
+ weights = {"embed": dec.embed_tokens.weight, "norm": dec.norm.weight,
629
+ "lm_head": pj([model.lm_head]), "layers": layers,
630
+ "inv_freq": inv_freq}
631
+ config = {
632
+ "num_hidden_layers": hf.num_hidden_layers,
633
+ "hidden_size": hf.hidden_size,
634
+ "num_attention_heads": hf.num_attention_heads,
635
+ "num_key_value_heads": getattr(hf, "num_key_value_heads", None),
636
+ "head_dim": getattr(hf, "head_dim", None),
637
+ "intermediate_size": hf.intermediate_size,
638
+ "vocab_size": hf.vocab_size,
639
+ "rope_theta": float(theta),
640
+ "rms_norm_eps": hf.rms_norm_eps,
641
+ "qk_norm": qk_norm,
642
+ }
643
+ return cls(weights, config, device=device, max_seq=max_seq,
644
+ max_gen=max_gen)
645
+
646
+ # ------------------------------------------------------------------
647
+ @classmethod
648
+ def _from_gemma4(cls, model, device, max_seq, max_gen):
649
+ """gemma-4-E2B-it program: gemma norms (fp32, raw weight),
650
+ gelu-tanh gating, post-sublayer norm phases, per-layer-input
651
+ pathway, sliding windows, shared KV, dual head dims, softcapped
652
+ head. Scope is the E2B text model only."""
653
+ self = cls.__new__(cls)
654
+ cfg = model.config.text_config if hasattr(model.config, "text_config") \
655
+ else model.config
656
+ dec = model.model.language_model if hasattr(model.model, "language_model") \
657
+ else model.model
658
+ assert cfg.model_type == "gemma4_text", cfg.model_type
659
+ assert not cfg.enable_moe_block
660
+
661
+ self.L = int(cfg.num_hidden_layers)
662
+ self.hidden = int(cfg.hidden_size)
663
+ self.Hq = int(cfg.num_attention_heads)
664
+ self.V = int(cfg.vocab_size)
665
+ self.eps = float(cfg.rms_norm_eps)
666
+ self.qk_norm = True
667
+ self.max_seq = int(max_seq)
668
+ self.max_gen = int(max_gen)
669
+ self.device = torch.device(device)
670
+ self.tiled = False
671
+ win = int(cfg.sliding_window)
672
+ cap = float(cfg.final_logit_softcapping)
673
+ PL = int(cfg.hidden_size_per_layer_input)
674
+ dt = torch.bfloat16
675
+ dev = self.device
676
+ self._keep = []
677
+
678
+ def pack(t):
679
+ t = t.detach().to(device=dev, dtype=dt).contiguous()
680
+ self._keep.append(t)
681
+ return t
682
+
683
+ def packf(t):
684
+ t = t.detach().to(device=dev, dtype=torch.float32).contiguous()
685
+ self._keep.append(t)
686
+ return t
687
+
688
+ embed = pack(dec.embed_tokens.weight)
689
+ ple_tab = pack(dec.embed_tokens_per_layer.weight)
690
+ plm_w = pack(dec.per_layer_model_projection.weight)
691
+ pln_w = pack(dec.per_layer_projection_norm.weight)
692
+ fnorm = pack(dec.norm.weight)
693
+ rot = dec.rotary_emb
694
+ invf = {lt: packf(getattr(rot, f"{lt}_inv_freq").float())
695
+ for lt in set(cfg.layer_types)}
696
+ ascale = {lt: float(getattr(rot, f"{lt}_attention_scaling"))
697
+ for lt in set(cfg.layer_types)}
698
+
699
+ layers = []
700
+ for li, lyr in enumerate(dec.layers):
701
+ a = lyr.self_attn
702
+ D_l = int(a.head_dim)
703
+ shared = bool(a.is_kv_shared_layer)
704
+ ent = {
705
+ "D": D_l, "shared": shared,
706
+ "lscale": float(lyr.layer_scalar.float().item()),
707
+ "type": cfg.layer_types[li],
708
+ "I": int(lyr.mlp.intermediate_size),
709
+ "qn": pack(a.q_norm.weight),
710
+ "o": pack(a.o_proj.weight),
711
+ "ln_in": pack(lyr.input_layernorm.weight),
712
+ "ln_pa": pack(lyr.post_attention_layernorm.weight),
713
+ "ln_pf": pack(lyr.pre_feedforward_layernorm.weight),
714
+ "ln_ff": pack(lyr.post_feedforward_layernorm.weight),
715
+ "ln_pl": pack(lyr.post_per_layer_input_norm.weight),
716
+ "plig": pack(lyr.per_layer_input_gate.weight),
717
+ "plpr": pack(lyr.per_layer_projection.weight),
718
+ "wgu": pack(torch.cat([lyr.mlp.gate_proj.weight,
719
+ lyr.mlp.up_proj.weight], 0)),
720
+ "wdn": pack(lyr.mlp.down_proj.weight),
721
+ }
722
+ if shared:
723
+ ent["wqkv"] = pack(a.q_proj.weight)
724
+ ent["kn"] = None
725
+ else:
726
+ ent["wqkv"] = pack(torch.cat(
727
+ [a.q_proj.weight, a.k_proj.weight, a.v_proj.weight], 0))
728
+ ent["kn"] = pack(a.k_norm.weight)
729
+ ent["kc"] = torch.zeros(self.max_seq, D_l, dtype=dt,
730
+ device=dev)
731
+ ent["vc"] = torch.zeros(self.max_seq, D_l, dtype=dt,
732
+ device=dev)
733
+ self._keep += [ent["kc"], ent["vc"]]
734
+ layers.append(ent)
735
+ # shared layers read the last non-shared layer of their type
736
+ src = {}
737
+ for li, ent in enumerate(layers):
738
+ if not ent["shared"]:
739
+ src[ent["type"]] = ent
740
+ for ent in layers:
741
+ if ent["shared"]:
742
+ ent["kc"] = src[ent["type"]]["kc"]
743
+ ent["vc"] = src[ent["type"]]["vc"]
744
+
745
+ maxD = max(e["D"] for e in layers)
746
+ maxI = max(e["I"] for e in layers)
747
+ self._maxk = max(self.hidden, self.Hq * maxD, maxI)
748
+ self._chunk_m = 1
749
+ self._chunk = 128
750
+ self._maxch = (self.max_seq + self._chunk - 1) // self._chunk
751
+ self._hidden = torch.empty(self.hidden, dtype=dt, device=dev)
752
+ self._tmp = torch.empty(self.hidden, dtype=dt, device=dev)
753
+ self._qkv = torch.empty((self.Hq + 2) * maxD, dtype=dt, device=dev)
754
+ self._gu = torch.empty(2 * maxI, dtype=dt, device=dev)
755
+ self._t256 = torch.empty(PL, dtype=dt, device=dev)
756
+ self._ctxraw = torch.empty(self.L * PL, dtype=dt, device=dev)
757
+ self._pletok = torch.empty(self.L * PL, dtype=dt, device=dev)
758
+ self._ple = torch.empty(self.L * PL, dtype=dt, device=dev)
759
+ self._logits = torch.empty(self.V, dtype=torch.float32, device=dev)
760
+ self._partials = torch.empty(
761
+ self.Hq * self._maxch * (maxD + 2), dtype=torch.float32,
762
+ device=dev)
763
+ self._token = torch.zeros(1, dtype=torch.int32, device=dev)
764
+ self._prompt_in = torch.zeros(self.max_seq, dtype=torch.int32,
765
+ device=dev)
766
+ self._tokens_out = torch.zeros(self.max_gen, dtype=torch.int32,
767
+ device=dev)
768
+ self._bar = torch.zeros(34, dtype=torch.int32, device=dev)
769
+ probe = torch.zeros(1, 16, dtype=torch.int64, device=dev)
770
+ self._nblocks = int(ops.mak_num_blocks(probe, self._maxk))
771
+ self._parts = torch.zeros(self._nblocks, dtype=torch.int64,
772
+ device=dev)
773
+ try:
774
+ self._bw_per_sm = float(ops.mak_bw_per_sm(probe))
775
+ except (AttributeError, RuntimeError):
776
+ self._bw_per_sm = 0.0
777
+ self._invf = invf[layers[0]["type"]]
778
+ self.Hkv, self.D, self.I = 1, maxD, maxI
779
+ self.theta = 0.0
780
+
781
+ emb_scale = float(torch.tensor(self.hidden ** 0.5,
782
+ dtype=torch.float32).to(dt))
783
+ ple_scale = float(torch.tensor(PL ** 0.5,
784
+ dtype=torch.float32).to(dt))
785
+ proj_scale = float(torch.tensor(self.hidden ** -0.5,
786
+ dtype=torch.float32))
787
+ hid = self._hidden.data_ptr()
788
+ tmp = self._tmp.data_ptr()
789
+ rows, names = [], []
790
+ rows.append(_row(_OP_EMBED, p1=ple_tab.data_ptr(),
791
+ p2=embed.data_ptr(), p3=hid,
792
+ p4=self._parts.data_ptr(),
793
+ p5=self._prompt_in.data_ptr(),
794
+ p6=self._token.data_ptr(),
795
+ n=self._tokens_out.data_ptr(), k=self.hidden,
796
+ it=self._pletok.data_ptr(),
797
+ hkv=self.L * PL, f0=emb_scale,
798
+ i0=_fbits(ple_scale)))
799
+ names.append("embed")
800
+ rows.append(_row(_OP_GEMV, p1=hid, p2=plm_w.data_ptr(),
801
+ p3=self._ctxraw.data_ptr(), n=self.L * PL,
802
+ k=self.hidden, it=_IT_NONE, epi=_EP_STORE))
803
+ names.append("plm")
804
+ rows.append(_row(8, p1=self._ctxraw.data_ptr(),
805
+ p2=pln_w.data_ptr(), p3=self._ple.data_ptr(),
806
+ p4=self._pletok.data_ptr(), n=self.L, k=PL,
807
+ f0=self.eps, i0=_fbits(proj_scale)))
808
+ names.append("plemix")
809
+
810
+ for li, e in enumerate(layers):
811
+ D_l, qdim = e["D"], self.Hq * e["D"]
812
+ lt = e["type"]
813
+ w_l = win if lt == "sliding_attention" else 0
814
+ asb = _fbits(ascale[lt]) if ascale[lt] != 1.0 else 0
815
+ flags = 1 | 2 | (0 if e["shared"] else 4) | \
816
+ (8 if e["shared"] else 0)
817
+ n_qkv = qdim if e["shared"] else qdim + 2 * D_l
818
+ rows.append(_row(_OP_GEMV, p1=hid, p2=e["wqkv"].data_ptr(),
819
+ p3=self._qkv.data_ptr(),
820
+ p4=e["ln_in"].data_ptr(), n=n_qkv,
821
+ k=self.hidden, it=_IT_RMSNORM_G,
822
+ epi=_EP_STORE, f0=self.eps))
823
+ names.append(f"L{li}.qkv")
824
+ rows.append(_row(_OP_ATTN, p1=self._qkv.data_ptr(),
825
+ p2=e["kc"].data_ptr(), p3=e["vc"].data_ptr(),
826
+ p4=self._partials.data_ptr(),
827
+ p5=e["qn"].data_ptr(),
828
+ p6=e["kn"].data_ptr() if e["kn"] is not None
829
+ else 0,
830
+ n=self.max_seq,
831
+ k=flags | (w_l << 16), it=_fbits(self.eps),
832
+ epi=invf[lt].data_ptr(), hq=self.Hq,
833
+ hkv=1 | (self._chunk << 16), d=D_l, f0=1.0,
834
+ i0=self._maxch | (asb << 32)))
835
+ names.append(f"L{li}.attn")
836
+ rows.append(_row(_OP_GEMV, p1=self._partials.data_ptr(),
837
+ p2=e["o"].data_ptr(), p3=tmp,
838
+ n=self.hidden, k=qdim, it=_IT_ATTNFIN,
839
+ epi=_EP_STORE, hq=self.Hq,
840
+ hkv=self._chunk << 16, d=D_l,
841
+ i0=self._maxch | (w_l << 16)))
842
+ names.append(f"L{li}.o")
843
+ rows.append(_row(7, p1=tmp, p2=e["ln_pa"].data_ptr(), p3=hid,
844
+ n=self.hidden, f0=self.eps, i0=_fbits(1.0)))
845
+ names.append(f"L{li}.nr_attn")
846
+ rows.append(_row(_OP_GEMV, p1=hid, p2=e["wgu"].data_ptr(),
847
+ p3=self._gu.data_ptr(),
848
+ p4=e["ln_pf"].data_ptr(), n=2 * e["I"],
849
+ k=self.hidden, it=_IT_RMSNORM_G,
850
+ epi=_EP_STORE, f0=self.eps))
851
+ names.append(f"L{li}.gateup")
852
+ rows.append(_row(_OP_GEMV, p1=self._gu.data_ptr(),
853
+ p2=e["wdn"].data_ptr(), p3=tmp,
854
+ n=self.hidden, k=e["I"], it=_IT_GELU_GLU,
855
+ epi=_EP_STORE))
856
+ names.append(f"L{li}.down")
857
+ rows.append(_row(7, p1=tmp, p2=e["ln_ff"].data_ptr(), p3=hid,
858
+ n=self.hidden, f0=self.eps, i0=_fbits(1.0)))
859
+ names.append(f"L{li}.nr_ffw")
860
+ rows.append(_row(_OP_GEMV, p1=hid, p2=e["plig"].data_ptr(),
861
+ p3=self._t256.data_ptr(),
862
+ p6=self._ple.data_ptr() + li * PL * 2,
863
+ n=PL, k=self.hidden, it=_IT_NONE,
864
+ epi=_EP_GELU_PLE))
865
+ names.append(f"L{li}.plig")
866
+ rows.append(_row(_OP_GEMV, p1=self._t256.data_ptr(),
867
+ p2=e["plpr"].data_ptr(), p3=tmp,
868
+ n=self.hidden, k=PL, it=_IT_NONE,
869
+ epi=_EP_STORE))
870
+ names.append(f"L{li}.plproj")
871
+ rows.append(_row(7, p1=tmp, p2=e["ln_pl"].data_ptr(), p3=hid,
872
+ n=self.hidden, f0=self.eps,
873
+ i0=_fbits(e["lscale"])))
874
+ names.append(f"L{li}.nr_ple")
875
+
876
+ rows.append(_row(_OP_GEMV, p1=hid, p2=embed.data_ptr(),
877
+ p3=self._logits.data_ptr(),
878
+ p4=fnorm.data_ptr(),
879
+ p6=self._parts.data_ptr(), n=self.V,
880
+ k=self.hidden, it=_IT_RMSNORM_G,
881
+ epi=_EP_F32_AMAX_CAP, hkv=1, f0=self.eps,
882
+ i0=_fbits(cap)))
883
+ names.append("lm_head")
884
+ rows.append(_row(_OP_ARGMAX_FIN, p1=self._parts.data_ptr(),
885
+ p3=self._token.data_ptr(),
886
+ p4=self._tokens_out.data_ptr()))
887
+ names.append("argmax.fin")
888
+ self._prog = torch.tensor(rows, dtype=torch.int64, device=dev)
889
+ self.phase_names = names
890
+ self._gemma = True
891
+ self._batch = 0
892
+ return self
893
+
894
+ # ------------------------------------------------------------------
895
+ def decode_step(self, token: int, pos: int, phased: bool = False):
896
+ """One decode step; returns the live fp32 logits buffer [V]."""
897
+ if pos >= self.max_seq:
898
+ raise ValueError("pos exceeds max_seq")
899
+ self._token.fill_(int(token))
900
+ if phased:
901
+ ops.mak_run_phased(self._prog, pos, self.max_gen - 1, False,
902
+ self._maxk)
903
+ else:
904
+ ops.mak_run(self._prog, self._bar, pos, self.max_gen - 1,
905
+ self._maxk)
906
+ return self._logits[:self.V]
907
+
908
+ def decode_step_timed(self, token: int, pos: int):
909
+ """One phase-per-launch step; returns (logits, per-phase ms)."""
910
+ self._token.fill_(int(token))
911
+ ms = ops.mak_run_phased(self._prog, pos, self.max_gen - 1, True,
912
+ self._maxk)
913
+ return self._logits[:self.V], ms
914
+
915
+ def prefill(self, ids: Sequence[int], phased: bool = False):
916
+ """Consume a prompt; returns the last position's logits. Runs in
917
+ chunks of up to chunk_m tokens with weight reads amortized across
918
+ the chunk (bitwise identical to token-by-token consumption)."""
919
+ P = len(ids)
920
+ if phased:
921
+ for i, t in enumerate(ids):
922
+ self.decode_step(int(t), i, phased=True)
923
+ return self._logits
924
+ self._prompt_in[:P].copy_(torch.tensor(ids, dtype=torch.int32))
925
+ ops.mak_run_seq(self._prog, self._bar, 0, P, 1 - P, P, self._maxk,
926
+ self._chunk_m)
927
+ return self._logits[:self.V]
928
+
929
+ def generate(self, prompt_ids: Sequence[int], max_new: int,
930
+ single_launch: bool = True) -> List[int]:
931
+ """Greedy generation. With single_launch (default) the entire call,
932
+ prompt consumption included, is one kernel launch: the in-kernel step
933
+ loop reads prompt tokens from a staged device buffer, then feeds each
934
+ step's argmax to the next step's embedding read. With
935
+ single_launch=False the same computation runs as one launch per
936
+ token; the two paths produce identical tokens.
937
+ """
938
+ P = len(prompt_ids)
939
+ if max_new < 1 or max_new > self.max_gen:
940
+ raise ValueError("max_new out of range")
941
+ if P < 1 or P + max_new > self.max_seq:
942
+ raise ValueError("sequence exceeds max_seq")
943
+ if single_launch:
944
+ self._prompt_in[:P].copy_(
945
+ torch.tensor(prompt_ids, dtype=torch.int32))
946
+ ops.mak_run_seq(self._prog, self._bar, 0, P + max_new - 1,
947
+ 1 - P, P, self._maxk, self._chunk_m)
948
+ return self._tokens_out[:max_new].cpu().tolist()
949
+ pos = 0
950
+ for t in prompt_ids[:-1]:
951
+ self._token.fill_(int(t))
952
+ ops.mak_run(self._prog, self._bar, pos, self.max_gen - 1,
953
+ self._maxk)
954
+ pos += 1
955
+ self._token.fill_(int(prompt_ids[-1]))
956
+ ops.mak_run(self._prog, self._bar, pos, 0, self._maxk)
957
+ pos += 1
958
+ if max_new > 1:
959
+ ops.mak_run_steps(self._prog, self._bar, pos, max_new - 1, 1,
960
+ self._maxk)
961
+ return self._tokens_out[:max_new].cpu().tolist()
962
+
963
+
964
+
build/torch213-cxx11-cu126-x86_64-linux/_model_as_a_kernel_cuda_e33875a.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:058f096551b99c0ad1db80dcc08bb6a4a95ec9924515919892a196e8e7ddf3bc
3
+ size 3849856
build/torch213-cxx11-cu126-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _model_as_a_kernel_cuda_e33875a
3
+ ops = torch.ops._model_as_a_kernel_cuda_e33875a
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_model_as_a_kernel_cuda_e33875a::{op_name}"
build/torch213-cxx11-cu126-x86_64-linux/metadata.json ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "model-as-a-kernel",
3
+ "id": "_model_as_a_kernel_cuda_e33875a",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "python-depends": [],
7
+ "backend": {
8
+ "type": "cuda",
9
+ "archs": [
10
+ "8.0",
11
+ "8.6",
12
+ "8.9",
13
+ "9.0"
14
+ ]
15
+ },
16
+ "digest": {
17
+ "algorithm": "sha256",
18
+ "files": {
19
+ "__init__.py": "/YX5/zzAOyJacQ7H+j8zaq+xjWyy8w6r0j+ugE1EUs8=",
20
+ "_model_as_a_kernel_cuda_e33875a.abi3.so": "BY8JZVG5nArR24DcwIu2pKleyZJFFZGYkqGW6Ofd87w=",
21
+ "_ops.py": "Pqj4WKtwPyeDG9grQnzLget4g1jsGUdR7ZMa44e4rhs=",
22
+ "model_as_a_kernel/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
23
+ }
24
+ },
25
+ "provenance": {
26
+ "kernel-builder": {
27
+ "version": "0.17.0-dev0",
28
+ "sha": "05f1ed487f599a320311aa26076ec0ad290c3f68",
29
+ "dirty": false
30
+ },
31
+ "kernel": {
32
+ "sha": "e33875ae4eba42fd136a89c388e806281cd4759c",
33
+ "dirty": false
34
+ }
35
+ }
36
+ }
build/torch213-cxx11-cu126-x86_64-linux/model_as_a_kernel/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import importlib.util
3
+ import sys
4
+ from pathlib import Path
5
+ from types import ModuleType
6
+
7
+
8
+ def _import_from_path(file_path: Path) -> ModuleType:
9
+ # We cannot use the module name as-is, after adding it to `sys.modules`,
10
+ # it would also be used for other imports. So, we make a module name that
11
+ # depends on the path for it to be unique using the hex-encoded hash of
12
+ # the path.
13
+ path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
+ module_name = path_hash
15
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
16
+ if spec is None:
17
+ raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
+ module = importlib.util.module_from_spec(spec)
19
+ if module is None:
20
+ raise ImportError(f"Cannot load module {module_name} from spec")
21
+ sys.modules[module_name] = module
22
+ spec.loader.exec_module(module) # type: ignore
23
+ return module
24
+
25
+
26
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
build/torch213-cxx11-cu130-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,964 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """model-as-a-kernel: an entire llama-family decode step as one kernel launch.
2
+
3
+ A phase interpreter runs the whole forward pass (embedding, every layer's
4
+ norms, QKV, rope, attention over the KV cache, SwiGLU MLP, the LM head, and
5
+ the greedy argmax) inside a single persistent kernel, with a software grid
6
+ barrier between phases. The in-kernel step loop makes an entire greedy
7
+ generation, prompt consumption included, one kernel launch: each step's
8
+ argmax feeds the next step's embedding read without leaving the device.
9
+ """
10
+ import os
11
+ import struct
12
+ from typing import List, Optional, Sequence
13
+
14
+ import torch
15
+
16
+ try:
17
+ from ._ops import ops
18
+ except ImportError: # local development build (torch.utils.cpp_extension)
19
+ class _LocalOps:
20
+ def __getattr__(self, name):
21
+ return getattr(torch.ops.mak_ext, name)
22
+
23
+ ops = _LocalOps()
24
+
25
+ __all__ = ["MegaModel"]
26
+
27
+ _OP_EMBED, _OP_GEMV, _OP_QKV_POST, _OP_ATTN = 0, 1, 2, 3
28
+ _OP_ARGMAX_PART, _OP_ARGMAX_FIN = 4, 5
29
+ _OP_KV_APPEND, _OP_NORMRES, _OP_PLEMIX = 6, 7, 8
30
+ _OP_GEMV_PLAIN, _OP_NORMB, _OP_GLUB, _OP_ATTNFINB = 9, 10, 11, 12
31
+ _OP_GEMV_Q4 = 13
32
+ _IT_NONE, _IT_RMSNORM, _IT_SWIGLU, _IT_ATTNFIN = 0, 1, 2, 3
33
+ _IT_RMSNORM_G, _IT_GELU_GLU = 4, 5
34
+ _EP_GELU_PLE, _EP_F32_AMAX_CAP = 5, 6
35
+ _ITF_REDUCE, _ITF_WRITEBACK = 8, 16
36
+ _EP_STORE, _EP_RESID, _EP_F32, _EP_F32_AMAX, _EP_PARTIAL = 0, 1, 2, 3, 4
37
+ _NSLICE = 4
38
+ # Widest [B][K] input panel (bf16 elements) a batched projection stages in
39
+ # shared before it spills to the global-scratch path. 16384 (32 KB) keeps
40
+ # two blocks resident per SM; the persistent kernel shares one occupancy
41
+ # across all phases, so a wider panel would drop every phase to one block
42
+ # per SM, which measures slower than reading the input from L2.
43
+ _STAGE_BUDGET = 16384
44
+
45
+
46
+ def _fbits(f: float) -> int:
47
+ return struct.unpack("<i", struct.pack("<f", float(f)))[0]
48
+
49
+
50
+ def _row(op, p1=0, p2=0, p3=0, p4=0, p5=0, p6=0, n=0, k=0, it=0, epi=0,
51
+ hq=0, hkv=0, d=0, f0=0.0, i0=0):
52
+ return [int(op), int(p1), int(p2), int(p3), int(p4), int(p5), int(p6),
53
+ int(n), int(k), int(it), int(epi), int(hq), int(hkv), int(d),
54
+ _fbits(f0), int(i0)]
55
+
56
+
57
+ class MegaModel:
58
+ """A packed llama-family decoder whose decode step is one kernel launch.
59
+
60
+ Supported architecture: RMSNorm decoder blocks with rotary attention
61
+ (GQA, optional Qwen3-style per-head qk RMSNorm), SwiGLU MLP, no attention
62
+ or MLP biases, plain (unscaled) rope, batch size 1. This covers SmolLM2,
63
+ TinyLlama, Qwen3 dense, Llama-class checkpoints without rope scaling,
64
+ and similar models.
65
+ """
66
+
67
+ def __init__(self, weights: dict, config: dict, device="cuda",
68
+ max_seq: int = 4096, max_gen: int = 4096):
69
+ cfg = dict(config)
70
+ self.L = int(cfg["num_hidden_layers"])
71
+ self.hidden = int(cfg["hidden_size"])
72
+ self.Hq = int(cfg["num_attention_heads"])
73
+ self.Hkv = int(cfg.get("num_key_value_heads") or self.Hq)
74
+ self.D = int(cfg.get("head_dim") or self.hidden // self.Hq)
75
+ self.I = int(cfg["intermediate_size"])
76
+ self.V = int(cfg["vocab_size"])
77
+ self.theta = float(cfg.get("rope_theta", 10000.0))
78
+ self.eps = float(cfg.get("rms_norm_eps", 1e-6))
79
+ self.qk_norm = bool(cfg.get("qk_norm", False))
80
+ self.max_seq = int(max_seq)
81
+ self.max_gen = int(max_gen)
82
+ self.device = torch.device(device)
83
+
84
+ if self.D % 2 or self.D > 256:
85
+ raise ValueError("head_dim must be even and <= 256")
86
+ if self.hidden % 8 or self.I % 8 or (self.Hq * self.D) % 8:
87
+ raise ValueError(
88
+ "hidden, intermediate, and Hq*head_dim must be multiples of 8")
89
+ if self.Hq % self.Hkv:
90
+ raise ValueError("num_attention_heads must be divisible by num_key_value_heads")
91
+
92
+ dt = torch.bfloat16
93
+ dev = self.device
94
+
95
+ def pack(t):
96
+ t = t.detach().to(device=dev, dtype=dt).contiguous()
97
+ self._keep.append(t)
98
+ return t
99
+
100
+ # Row-tile-of-8 weight layout [N/8][K/8][8][8] (MAK_TILED=1): the
101
+ # eight warps of a block read one contiguous 4KB stream per step
102
+ # instead of eight 4KB-strided pieces. Measured slower on every
103
+ # card (H200 +15 percent, RTX PRO +9, Ada +14 — per-warp stream
104
+ # locality beats per-block contiguity on all of them), so the
105
+ # default is the plain layout; the flag rides in the program and
106
+ # both layouts compute identical bits.
107
+ env_t = os.environ.get("MAK_TILED", "").strip()
108
+ self.tiled = env_t == "1"
109
+
110
+ def pack_tiled(t):
111
+ t = t.detach().to(device=dev, dtype=dt).contiguous()
112
+ if not self.tiled:
113
+ self._keep.append(t)
114
+ return t
115
+ n, k = t.shape
116
+ assert k % 8 == 0
117
+ n8 = (n + 7) // 8 * 8
118
+ if n8 != n:
119
+ t = torch.cat(
120
+ [t, torch.zeros(n8 - n, k, dtype=dt, device=dev)], 0)
121
+ t = t.view(n8 // 8, 8, k // 8, 8).permute(0, 2, 1, 3).contiguous()
122
+ self._keep.append(t)
123
+ return t
124
+
125
+ self._keep: List[torch.Tensor] = []
126
+ embed = pack(weights["embed"])
127
+ final_norm = pack(weights["norm"])
128
+ lm_head = weights["lm_head"] # packed below (dense-tiled or nf4)
129
+ inv_freq = weights.get("inv_freq")
130
+ if inv_freq is None:
131
+ d_idx = torch.arange(0, self.D, 2, dtype=torch.float32)
132
+ inv_freq = 1.0 / (self.theta ** (d_idx / self.D))
133
+ inv_freq = inv_freq.detach().to(device=dev,
134
+ dtype=torch.float32).contiguous()
135
+ if inv_freq.numel() != self.D // 2:
136
+ raise ValueError("inv_freq length must be head_dim / 2")
137
+ self._keep.append(inv_freq)
138
+ self._invf = inv_freq
139
+
140
+ qdim, kvdim = self.Hq * self.D, self.Hkv * self.D
141
+
142
+ def _is_q4(w):
143
+ return isinstance(w, dict) and w.get("q4")
144
+
145
+ def pack_w(w):
146
+ # nf4-packed weights move to the device intact; dense weights take
147
+ # the usual (optionally row-tiled) bf16 packing.
148
+ if _is_q4(w):
149
+ pk = w["packed"].to(dev).contiguous()
150
+ am = w["absmax"].to(device=dev, dtype=torch.float32).contiguous()
151
+ self._keep.append(pk)
152
+ self._keep.append(am)
153
+ return {"q4": True, "packed": pk, "absmax": am,
154
+ "N": int(w["N"]), "K": int(w["K"])}
155
+ return pack_tiled(w)
156
+
157
+ def _wshape(w, expected):
158
+ got = (int(w["N"]), int(w["K"])) if _is_q4(w) else tuple(w.shape)
159
+ assert got == expected, (got, expected)
160
+
161
+ self._has_q4 = False
162
+ self._layers = []
163
+ for lw in weights["layers"]:
164
+ _wshape(lw["wqkv"], (qdim + 2 * kvdim, self.hidden))
165
+ _wshape(lw["wo"], (self.hidden, qdim))
166
+ _wshape(lw["wgu"], (2 * self.I, self.hidden))
167
+ _wshape(lw["wdown"], (self.hidden, self.I))
168
+ layer = {
169
+ "wqkv": pack_w(lw["wqkv"]), "wo": pack_w(lw["wo"]),
170
+ "wgu": pack_w(lw["wgu"]), "wdown": pack_w(lw["wdown"]),
171
+ "ln1": pack(lw["ln1"]), "ln2": pack(lw["ln2"]),
172
+ "qn": pack(lw["qn"]) if self.qk_norm else None,
173
+ "kn": pack(lw["kn"]) if self.qk_norm else None,
174
+ }
175
+ self._has_q4 |= any(_is_q4(layer[n]) for n in
176
+ ("wqkv", "wo", "wgu", "wdown"))
177
+ self._layers.append(layer)
178
+ assert len(self._layers) == self.L
179
+ assert embed.shape == (self.V, self.hidden)
180
+ lm_head = pack_w(lm_head)
181
+ self._has_q4 |= _is_q4(lm_head)
182
+ if not _is_q4(lm_head):
183
+ assert lm_head.numel() >= self.V * self.hidden # row-tiled, padded
184
+ self._w_embed, self._w_fnorm, self._w_lmhead = (embed, final_norm,
185
+ lm_head)
186
+ self._qdim, self._kvdim = qdim, kvdim
187
+
188
+ # Working buffers. Pointers to these are baked into the program, so
189
+ # they (like the packed weights) live for the model's lifetime.
190
+ self._maxk = max(self.hidden, qdim, self.I)
191
+ # prefill chunk size, bounded by the staging shared-memory budget
192
+ # (16K bf16 elements; the cp.async weight ring rides alongside)
193
+ self._chunk_m = max(1, min(8, 16384 // self._maxk))
194
+ cm = 8 # buffers sized for the maximum chunk
195
+ self._hidden = torch.empty(cm * self.hidden, dtype=dt, device=dev)
196
+ self._qkv = torch.empty(cm * (qdim + 2 * kvdim), dtype=dt, device=dev)
197
+ self._gu = torch.empty(cm * 2 * self.I, dtype=dt, device=dev)
198
+ self._logits = torch.empty(self.V, dtype=torch.float32, device=dev)
199
+ # attention chunk length (kernel maximum 128); shorter chunks give
200
+ # the attention phase more grid parallelism at the cost of more
201
+ # softmax partials to finalize
202
+ env_ch = os.environ.get("MAK_CHUNK", "").strip()
203
+ self._chunk = int(env_ch) if env_ch in ("32", "64", "128") else 128
204
+ self._maxch = (self.max_seq + self._chunk - 1) // self._chunk
205
+ self._partials = torch.empty(
206
+ cm * self.Hq * self._maxch * (self.D + 2), dtype=torch.float32,
207
+ device=dev)
208
+ self._kcache = torch.zeros(self.L, self.Hkv, self.max_seq, self.D,
209
+ dtype=dt, device=dev)
210
+ self._vcache = torch.zeros(self.L, self.Hkv, self.max_seq, self.D,
211
+ dtype=dt, device=dev)
212
+ self._token = torch.zeros(1, dtype=torch.int32, device=dev)
213
+ self._prompt_in = torch.zeros(self.max_seq, dtype=torch.int32,
214
+ device=dev)
215
+ self._tokens_out = torch.zeros(self.max_gen, dtype=torch.int32,
216
+ device=dev)
217
+ self._bar = torch.zeros(34, dtype=torch.int32, device=dev)
218
+
219
+ probe = torch.zeros(1, 16, dtype=torch.int64, device=dev)
220
+ self._nblocks = int(ops.mak_num_blocks(probe, self._maxk))
221
+ self._parts = torch.zeros(self._nblocks, dtype=torch.int64, device=dev)
222
+ try:
223
+ self._bw_per_sm = float(ops.mak_bw_per_sm(probe))
224
+ except (AttributeError, RuntimeError):
225
+ self._bw_per_sm = 0.0
226
+
227
+ self._batch = 0
228
+ self._pos_b = None
229
+ self._kv_bstride = 0
230
+ self._gemma = False
231
+ # transformed-input scratch: the nf4 and wide-batch paths write the
232
+ # dense bf16 input here (one row per active token) for the following
233
+ # plain/quant GEMV; sized for a prefill chunk (up to 8 rows)
234
+ self._xg = torch.empty(cm * self._maxk, dtype=dt, device=dev)
235
+ self._build_programs()
236
+
237
+ # ------------------------------------------------------------------
238
+ def _kvp(self, cache, li: int, b: int) -> int:
239
+ t = cache[li]
240
+ return (t[b] if t.dim() == 4 else t).data_ptr()
241
+
242
+ def _build_rows(self, kv_slice: int = 0, batch: bool = False,
243
+ big: bool = False):
244
+ """Program rows for the llama-family path. kv_slice selects the
245
+ batch slice the KV pointers address (per-sequence prefill);
246
+ batch=True emits the batched-decode tail (per-row fp32 logits and
247
+ explicit argmax phases) instead of the fused LM-head amax. In batch
248
+ mode each projection stays fused (input staged in shared) when its
249
+ [B][K] panel fits the budget; the projections whose K is too wide
250
+ transform into a global scratch and read it with a plain GEMV."""
251
+ _tbit = (1 << 20) if self.tiled else 0 # GEMV weight layout flag
252
+ qdim, kvdim = self._qdim, self._kvdim
253
+ embed, lm_head = self._w_embed, self._w_lmhead
254
+ final_norm = self._w_fnorm
255
+ xg = self._xg.data_ptr()
256
+ B = self._batch if batch else 1
257
+ rows, names = [], []
258
+ staged_elems = [0] # widest [B][K] panel any fused projection stages
259
+
260
+ def fits(K):
261
+ return B * K <= _STAGE_BUDGET
262
+
263
+ def note(K):
264
+ staged_elems[0] = max(staged_elems[0], B * K)
265
+
266
+ def _q4(w):
267
+ return isinstance(w, dict) and w.get("q4")
268
+
269
+ def _wgemv(out, w, N, K, epi, resid):
270
+ # the GEMV after a transform-to-scratch phase: nf4-dequant when
271
+ # the weight is packed, otherwise a plain bf16 GEMV
272
+ if _q4(w):
273
+ rows.append(_row(_OP_GEMV_Q4, p1=xg, p2=w["packed"].data_ptr(),
274
+ p3=out, p5=resid, p6=w["absmax"].data_ptr(),
275
+ n=N, k=K, epi=epi))
276
+ else:
277
+ rows.append(_row(_OP_GEMV_PLAIN, p1=xg, p2=w.data_ptr(),
278
+ p3=out, p5=resid, n=N, k=K, it=_tbit,
279
+ epi=epi))
280
+
281
+ def norm_gemv(name, gamma, w, out, N, K, epi, inp, resid=0,
282
+ variant=_IT_RMSNORM):
283
+ if _q4(w) or (big and not fits(K)):
284
+ rows.append(_row(_OP_NORMB, p1=inp, p2=gamma, p3=xg, k=K,
285
+ it=variant, f0=self.eps))
286
+ names.append(name + ".norm")
287
+ _wgemv(out, w, N, K, epi, resid)
288
+ names.append(name)
289
+ else:
290
+ note(K)
291
+ rows.append(_row(_OP_GEMV, p1=inp, p2=w.data_ptr(), p3=out,
292
+ p4=gamma, p5=resid, n=N, k=K,
293
+ it=variant | _tbit, epi=epi, f0=self.eps))
294
+ names.append(name)
295
+
296
+ def glu_gemv(name, gu, w, out, resid, variant=_IT_SWIGLU):
297
+ if _q4(w) or (big and not fits(self.I)):
298
+ rows.append(_row(_OP_GLUB, p1=gu, p3=xg, k=self.I, it=variant))
299
+ names.append(name + ".glu")
300
+ _wgemv(out, w, self.hidden, self.I, _EP_RESID, resid)
301
+ names.append(name)
302
+ else:
303
+ note(self.I)
304
+ rows.append(_row(_OP_GEMV, p1=gu, p2=w.data_ptr(), p3=out,
305
+ p5=resid, n=self.hidden, k=self.I,
306
+ it=variant | _tbit, epi=_EP_RESID))
307
+ names.append(name)
308
+
309
+ def attn_gemv(name, w, out, resid):
310
+ if _q4(w) or (big and not fits(qdim)):
311
+ rows.append(_row(_OP_ATTNFINB, p1=self._partials.data_ptr(),
312
+ p3=xg, k=qdim, hq=self.Hq,
313
+ hkv=self._chunk << 16, d=self.D,
314
+ i0=self._maxch))
315
+ names.append(name + ".fin")
316
+ _wgemv(out, w, self.hidden, qdim, _EP_RESID, resid)
317
+ names.append(name)
318
+ else:
319
+ note(qdim)
320
+ rows.append(_row(_OP_GEMV, p1=self._partials.data_ptr(),
321
+ p2=w.data_ptr(), p3=out, p5=resid,
322
+ n=self.hidden, k=qdim,
323
+ it=_IT_ATTNFIN | _tbit, epi=_EP_RESID,
324
+ hq=self.Hq, hkv=self._chunk << 16, d=self.D,
325
+ i0=self._maxch))
326
+ names.append(name)
327
+
328
+ rows.append(_row(_OP_EMBED, p2=embed.data_ptr(),
329
+ p3=self._hidden.data_ptr(),
330
+ p4=self._parts.data_ptr(),
331
+ p5=self._prompt_in.data_ptr(),
332
+ p6=self._token.data_ptr(),
333
+ n=self._tokens_out.data_ptr(), k=self.hidden))
334
+ names.append("embed")
335
+ scale = 1.0 / (self.D ** 0.5)
336
+ hid = self._hidden.data_ptr()
337
+ for li, lw in enumerate(self._layers):
338
+ kc = self._kvp(self._kcache, li, kv_slice)
339
+ vc = self._kvp(self._vcache, li, kv_slice)
340
+ norm_gemv(f"L{li}.qkv", lw["ln1"].data_ptr(),
341
+ lw["wqkv"], self._qkv.data_ptr(),
342
+ qdim + 2 * kvdim, self.hidden, _EP_STORE, hid)
343
+ rows.append(_row(6, p1=self._qkv.data_ptr(), p2=kc, p3=vc,
344
+ p6=lw["kn"].data_ptr() if self.qk_norm else 0,
345
+ n=self.max_seq, k=1 if self.qk_norm else 0,
346
+ it=_fbits(self.eps),
347
+ epi=self._invf.data_ptr(),
348
+ hq=self.Hq, hkv=self.Hkv, d=self.D))
349
+ names.append(f"L{li}.kvappend")
350
+ rows.append(_row(_OP_ATTN, p1=self._qkv.data_ptr(), p2=kc,
351
+ p3=vc, p4=self._partials.data_ptr(),
352
+ p5=lw["qn"].data_ptr() if self.qk_norm else 0,
353
+ p6=lw["kn"].data_ptr() if self.qk_norm else 0,
354
+ n=self.max_seq, k=1 if self.qk_norm else 0,
355
+ it=_fbits(self.eps),
356
+ epi=self._invf.data_ptr(),
357
+ hq=self.Hq,
358
+ hkv=self.Hkv | (self._chunk << 16),
359
+ d=self.D, f0=scale, i0=self._maxch))
360
+ names.append(f"L{li}.attn")
361
+ attn_gemv(f"L{li}.o", lw["wo"], hid, hid)
362
+ norm_gemv(f"L{li}.gateup", lw["ln2"].data_ptr(),
363
+ lw["wgu"], self._gu.data_ptr(), 2 * self.I,
364
+ self.hidden, _EP_STORE, hid)
365
+ glu_gemv(f"L{li}.down", self._gu.data_ptr(), lw["wdown"], hid, hid)
366
+ if batch or _q4(lm_head):
367
+ norm_gemv("lm_head", final_norm.data_ptr(), lm_head,
368
+ self._logits.data_ptr(), self.V, self.hidden, _EP_F32,
369
+ hid)
370
+ rows.append(_row(_OP_ARGMAX_PART, p1=self._logits.data_ptr(),
371
+ p3=self._parts.data_ptr(), n=self.V))
372
+ names.append("argmax.part")
373
+ rows.append(_row(_OP_ARGMAX_FIN, p1=self._parts.data_ptr(),
374
+ p3=self._token.data_ptr(),
375
+ p4=self._tokens_out.data_ptr(),
376
+ k=self.max_gen))
377
+ names.append("argmax.fin")
378
+ else:
379
+ rows.append(_row(_OP_GEMV, p1=hid, p2=lm_head.data_ptr(),
380
+ p3=self._logits.data_ptr(),
381
+ p4=final_norm.data_ptr(),
382
+ p6=self._parts.data_ptr(), n=self.V,
383
+ k=self.hidden, it=_IT_RMSNORM | _tbit,
384
+ epi=_EP_F32_AMAX, hkv=1, f0=self.eps))
385
+ names.append("lm_head")
386
+ rows.append(_row(_OP_ARGMAX_FIN, p1=self._parts.data_ptr(),
387
+ p3=self._token.data_ptr(),
388
+ p4=self._tokens_out.data_ptr()))
389
+ names.append("argmax.fin")
390
+ se = max(staged_elems[0], self._maxk)
391
+ return rows, names, se
392
+
393
+ def _build_programs(self):
394
+ dev = self.device
395
+ rows, names, _ = self._build_rows(kv_slice=0, batch=False)
396
+ self._prog = torch.tensor(rows, dtype=torch.int64, device=dev)
397
+ self.phase_names = names
398
+ if self._batch:
399
+ big = self._batch > min(8, 16384 // self._maxk)
400
+ rows, _, se = self._build_rows(kv_slice=0, batch=True, big=big)
401
+ self._prog_batch = torch.tensor(rows, dtype=torch.int64,
402
+ device=dev)
403
+ self._batch_stage_elems = int(se)
404
+ self._progs_prefill = [self._prog]
405
+ for b in range(1, self._batch):
406
+ rows, _, _ = self._build_rows(kv_slice=b, batch=False)
407
+ self._progs_prefill.append(
408
+ torch.tensor(rows, dtype=torch.int64, device=dev))
409
+
410
+ def batch_max(self) -> int:
411
+ """Largest batch size this model supports. Up to 8 sequences stage
412
+ in shared; beyond that the transformed input moves to a global
413
+ scratch, bounded by the kernel's register accumulators."""
414
+ try:
415
+ hard = int(ops.mak_batch_maxb())
416
+ except (AttributeError, RuntimeError):
417
+ hard = 8
418
+ return 1 if self._gemma else hard
419
+
420
+ # ------------------------------------------------------------------
421
+ def enable_batch(self, B: int):
422
+ """Allocate per-sequence KV caches, token buffers, and programs for
423
+ batched decode. B up to 8 stages the input in shared; larger B (up
424
+ to batch_max()) routes the transformed input through a global
425
+ scratch so the batch width is not bounded by the projection size."""
426
+ if self._gemma:
427
+ raise ValueError("batched decode covers the llama-family path")
428
+ maxb = self.batch_max()
429
+ if not 1 <= B <= maxb:
430
+ raise ValueError(f"B must be in [1, {maxb}] for this model")
431
+ if self._batch == B:
432
+ return
433
+ dt, dev = torch.bfloat16, self.device
434
+ self._kcache = torch.zeros(self.L, B, self.Hkv, self.max_seq,
435
+ self.D, dtype=dt, device=dev)
436
+ self._vcache = torch.zeros_like(self._kcache)
437
+ self._token = torch.zeros(B, dtype=torch.int32, device=dev)
438
+ self._tokens_out = torch.zeros(B * self.max_gen, dtype=torch.int32,
439
+ device=dev)
440
+ self._logits = torch.empty(B * self.V, dtype=torch.float32,
441
+ device=dev)
442
+ self._parts = torch.zeros(B * self._nblocks, dtype=torch.int64,
443
+ device=dev)
444
+ self._pos_b = torch.zeros(B, dtype=torch.int32, device=dev)
445
+ # transformed-input scratch: one row per active token, and prefill
446
+ # (during generate_batch) still uses up to chunk_m rows
447
+ self._xg = torch.empty(max(8, B) * self._maxk, dtype=dt, device=dev)
448
+ # working buffers hold one row per active sequence; prefill chunks
449
+ # still use up to chunk_m rows, so keep at least the fused capacity
450
+ nb = max(8, B)
451
+ qkvd = self._qdim + 2 * self._kvdim
452
+ self._hidden = torch.empty(nb * self.hidden, dtype=dt, device=dev)
453
+ self._qkv = torch.empty(nb * qkvd, dtype=dt, device=dev)
454
+ self._gu = torch.empty(nb * 2 * self.I, dtype=dt, device=dev)
455
+ self._partials = torch.empty(
456
+ nb * self.Hq * self._maxch * (self.D + 2), dtype=torch.float32,
457
+ device=dev)
458
+ self._kv_bstride = self.Hkv * self.max_seq * self.D
459
+ self._batch = B
460
+ self._build_programs()
461
+
462
+ def decode_batch(self, tokens: Sequence[int], positions: Sequence[int],
463
+ steps: int = 1) -> torch.Tensor:
464
+ """Batched greedy decode: sequence b consumes tokens[b] at
465
+ positions[b]; `steps` in-kernel steps run with per-sequence argmax
466
+ feedback. Returns the generated tokens [B, steps]; the final
467
+ step's fp32 logits are live in batch_logits()."""
468
+ B = self._batch
469
+ if B < 1:
470
+ raise ValueError("call enable_batch(B) first")
471
+ if len(tokens) != B or len(positions) != B:
472
+ raise ValueError("tokens and positions must have B entries")
473
+ if steps < 1 or steps > self.max_gen:
474
+ raise ValueError("steps out of range")
475
+ if max(positions) + steps > self.max_seq:
476
+ raise ValueError("sequence exceeds max_seq")
477
+ self._token.copy_(torch.tensor(list(tokens), dtype=torch.int32))
478
+ self._pos_b.copy_(torch.tensor(list(positions), dtype=torch.int32))
479
+ ops.mak_run_batch(self._prog_batch, self._bar, self._pos_b, steps,
480
+ 0, self._maxk, self._kv_bstride,
481
+ self._batch_stage_elems)
482
+ return self._tokens_out.view(B, self.max_gen)[:, :steps]
483
+
484
+ def batch_logits(self) -> torch.Tensor:
485
+ """fp32 logits [B, V] of the most recent batched step."""
486
+ return self._logits.view(max(self._batch, 1), self.V)
487
+
488
+ def generate_batch(self, prompts: Sequence[Sequence[int]],
489
+ max_new: int) -> List[List[int]]:
490
+ """Greedy generation for a batch of prompts: per-sequence prefill,
491
+ then one batched decode launch. Each sequence's token stream is
492
+ bit-identical to its own single-sequence generate()."""
493
+ B = len(prompts)
494
+ if max_new < 1 or max_new > self.max_gen:
495
+ raise ValueError("max_new out of range")
496
+ self.enable_batch(B)
497
+ firsts: List[int] = []
498
+ for b, ids in enumerate(prompts):
499
+ P = len(ids)
500
+ if P < 1 or P + max_new > self.max_seq:
501
+ raise ValueError("sequence exceeds max_seq")
502
+ self._prompt_in[:P].copy_(
503
+ torch.tensor(list(ids), dtype=torch.int32))
504
+ ops.mak_run_seq(self._progs_prefill[b], self._bar, 0, P, 1 - P,
505
+ P, self._maxk, self._chunk_m)
506
+ firsts.append(int(self._token[0].item()))
507
+ if max_new == 1:
508
+ return [[t] for t in firsts]
509
+ self._token.copy_(torch.tensor(firsts, dtype=torch.int32))
510
+ self._pos_b.copy_(torch.tensor([len(p) for p in prompts],
511
+ dtype=torch.int32))
512
+ ops.mak_run_batch(self._prog_batch, self._bar, self._pos_b,
513
+ max_new - 1, 0, self._maxk, self._kv_bstride,
514
+ self._batch_stage_elems)
515
+ rest = (self._tokens_out.view(B, self.max_gen)[:, :max_new - 1]
516
+ .cpu().tolist())
517
+ return [[firsts[b]] + rest[b] for b in range(B)]
518
+
519
+ # ------------------------------------------------------------------
520
+ @staticmethod
521
+ def _dense_weight(mod):
522
+ """The effective bf16 weight of a linear layer, dequantizing packed
523
+ quantized layers (bitsandbytes 4-bit and 8-bit) so a quantized
524
+ checkpoint loads directly. Dequantization is exact and does not
525
+ depend on the card, so inference on the result keeps every
526
+ guarantee; the weights materialize to bf16, so the quantized memory
527
+ footprint is not preserved (that needs the in-kernel packed path)."""
528
+ w = mod.weight
529
+ qs = getattr(w, "quant_state", None)
530
+ if qs is not None: # bitsandbytes 4-bit (nf4 / fp4)
531
+ import bitsandbytes as bnb
532
+ return bnb.functional.dequantize_4bit(w.data, qs).to(torch.bfloat16)
533
+ if getattr(w, "SCB", None) is not None or hasattr(mod, "SCB"):
534
+ # bitsandbytes 8-bit (LLM.int8): row scales in SCB, int8 in CB
535
+ scb = w.SCB if getattr(w, "SCB", None) is not None else mod.SCB
536
+ cb = w.data if w.data.dtype == torch.int8 else mod.CB
537
+ return (cb.to(torch.float32)
538
+ * (scb.to(torch.float32) / 127.0).unsqueeze(1)
539
+ ).to(torch.bfloat16)
540
+ return w
541
+
542
+ @staticmethod
543
+ def _q4_parts(mod):
544
+ """(packed [N, K/2] uint8, absmax [N*K/64] fp32) for a bitsandbytes
545
+ nf4 layer whose weights stay packed, else None. Double-quantized
546
+ absmax is materialized to fp32 so the kernel needs one scale array."""
547
+ w = getattr(mod, "weight", None)
548
+ qs = getattr(w, "quant_state", None)
549
+ if qs is None or getattr(qs, "quant_type", None) != "nf4":
550
+ return None
551
+ import bitsandbytes as bnb
552
+ n, k = int(qs.shape[0]), int(qs.shape[1])
553
+ if k % 64:
554
+ return None # kernel assumes 64 | K for per-block absmax
555
+ packed = w.data.reshape(n, k // 2).contiguous()
556
+ if getattr(qs, "nested", False):
557
+ absmax = bnb.functional.dequantize_blockwise(
558
+ qs.absmax, qs.state2) + qs.offset
559
+ else:
560
+ absmax = qs.absmax
561
+ return packed, absmax.float().reshape(-1).contiguous()
562
+
563
+ @classmethod
564
+ def _proj(cls, mods):
565
+ """A projection, concatenated across `mods` (q/k/v or gate/up). Stays
566
+ nf4-packed when every part is nf4, else a dense bf16 tensor."""
567
+ parts = [cls._q4_parts(m) for m in mods]
568
+ if all(p is not None for p in parts):
569
+ packed = torch.cat([p[0] for p in parts], 0)
570
+ absmax = torch.cat([p[1] for p in parts], 0)
571
+ return {"q4": True, "packed": packed, "absmax": absmax,
572
+ "N": packed.shape[0], "K": packed.shape[1] * 2}
573
+ return torch.cat([cls._dense_weight(m) for m in mods], 0)
574
+
575
+ @classmethod
576
+ def from_pretrained(cls, model, device="cuda", max_seq: int = 4096,
577
+ max_gen: int = 4096):
578
+ """Build from a transformers model (object or repo id). A quantized
579
+ checkpoint (bitsandbytes 4-bit/8-bit) is accepted directly; its
580
+ weights are dequantized to bf16 at load."""
581
+ if isinstance(model, str):
582
+ from transformers import AutoModelForCausalLM
583
+ model = AutoModelForCausalLM.from_pretrained(
584
+ model, torch_dtype=torch.bfloat16)
585
+ if "gemma4" in getattr(model.config, "model_type", ""):
586
+ return cls._from_gemma4(model, device, max_seq, max_gen)
587
+ hf = model.config
588
+ dec = model.model
589
+ attn0 = dec.layers[0].self_attn
590
+ qk_norm = hasattr(attn0, "q_norm") and attn0.q_norm is not None
591
+ if getattr(attn0.q_proj, "bias", None) is not None:
592
+ raise ValueError("attention biases are not supported")
593
+
594
+ # rope config across transformers API generations: v5 keeps theta and
595
+ # type in a rope_parameters dict; v4 used a rope_theta attribute and
596
+ # an optional rope_scaling dict.
597
+ rp = getattr(hf, "rope_parameters", None)
598
+ if not isinstance(rp, dict):
599
+ rs = getattr(hf, "rope_scaling", None)
600
+ rp = rs if isinstance(rs, dict) else {}
601
+ theta = rp.get("rope_theta")
602
+ if theta is None:
603
+ theta = getattr(hf, "rope_theta", 10000.0)
604
+ rope_type = rp.get("rope_type", rp.get("type", "default"))
605
+ if rope_type not in ("default", "llama3"):
606
+ raise ValueError(f"rope type {rope_type!r} is not supported")
607
+ # take the frequency table transformers computed (scaling included)
608
+ rot = dec.rotary_emb
609
+ att_scale = float(getattr(rot, "attention_scaling", 1.0))
610
+ if att_scale != 1.0:
611
+ raise ValueError("rope attention_scaling != 1 is not supported")
612
+ inv_freq = rot.inv_freq.detach().float()
613
+
614
+ pj = cls._proj
615
+ layers = []
616
+ for lyr in dec.layers:
617
+ a, m = lyr.self_attn, lyr.mlp
618
+ layers.append({
619
+ "wqkv": pj([a.q_proj, a.k_proj, a.v_proj]),
620
+ "wo": pj([a.o_proj]),
621
+ "wgu": pj([m.gate_proj, m.up_proj]),
622
+ "wdown": pj([m.down_proj]),
623
+ "ln1": lyr.input_layernorm.weight,
624
+ "ln2": lyr.post_attention_layernorm.weight,
625
+ "qn": a.q_norm.weight if qk_norm else None,
626
+ "kn": a.k_norm.weight if qk_norm else None,
627
+ })
628
+ weights = {"embed": dec.embed_tokens.weight, "norm": dec.norm.weight,
629
+ "lm_head": pj([model.lm_head]), "layers": layers,
630
+ "inv_freq": inv_freq}
631
+ config = {
632
+ "num_hidden_layers": hf.num_hidden_layers,
633
+ "hidden_size": hf.hidden_size,
634
+ "num_attention_heads": hf.num_attention_heads,
635
+ "num_key_value_heads": getattr(hf, "num_key_value_heads", None),
636
+ "head_dim": getattr(hf, "head_dim", None),
637
+ "intermediate_size": hf.intermediate_size,
638
+ "vocab_size": hf.vocab_size,
639
+ "rope_theta": float(theta),
640
+ "rms_norm_eps": hf.rms_norm_eps,
641
+ "qk_norm": qk_norm,
642
+ }
643
+ return cls(weights, config, device=device, max_seq=max_seq,
644
+ max_gen=max_gen)
645
+
646
+ # ------------------------------------------------------------------
647
+ @classmethod
648
+ def _from_gemma4(cls, model, device, max_seq, max_gen):
649
+ """gemma-4-E2B-it program: gemma norms (fp32, raw weight),
650
+ gelu-tanh gating, post-sublayer norm phases, per-layer-input
651
+ pathway, sliding windows, shared KV, dual head dims, softcapped
652
+ head. Scope is the E2B text model only."""
653
+ self = cls.__new__(cls)
654
+ cfg = model.config.text_config if hasattr(model.config, "text_config") \
655
+ else model.config
656
+ dec = model.model.language_model if hasattr(model.model, "language_model") \
657
+ else model.model
658
+ assert cfg.model_type == "gemma4_text", cfg.model_type
659
+ assert not cfg.enable_moe_block
660
+
661
+ self.L = int(cfg.num_hidden_layers)
662
+ self.hidden = int(cfg.hidden_size)
663
+ self.Hq = int(cfg.num_attention_heads)
664
+ self.V = int(cfg.vocab_size)
665
+ self.eps = float(cfg.rms_norm_eps)
666
+ self.qk_norm = True
667
+ self.max_seq = int(max_seq)
668
+ self.max_gen = int(max_gen)
669
+ self.device = torch.device(device)
670
+ self.tiled = False
671
+ win = int(cfg.sliding_window)
672
+ cap = float(cfg.final_logit_softcapping)
673
+ PL = int(cfg.hidden_size_per_layer_input)
674
+ dt = torch.bfloat16
675
+ dev = self.device
676
+ self._keep = []
677
+
678
+ def pack(t):
679
+ t = t.detach().to(device=dev, dtype=dt).contiguous()
680
+ self._keep.append(t)
681
+ return t
682
+
683
+ def packf(t):
684
+ t = t.detach().to(device=dev, dtype=torch.float32).contiguous()
685
+ self._keep.append(t)
686
+ return t
687
+
688
+ embed = pack(dec.embed_tokens.weight)
689
+ ple_tab = pack(dec.embed_tokens_per_layer.weight)
690
+ plm_w = pack(dec.per_layer_model_projection.weight)
691
+ pln_w = pack(dec.per_layer_projection_norm.weight)
692
+ fnorm = pack(dec.norm.weight)
693
+ rot = dec.rotary_emb
694
+ invf = {lt: packf(getattr(rot, f"{lt}_inv_freq").float())
695
+ for lt in set(cfg.layer_types)}
696
+ ascale = {lt: float(getattr(rot, f"{lt}_attention_scaling"))
697
+ for lt in set(cfg.layer_types)}
698
+
699
+ layers = []
700
+ for li, lyr in enumerate(dec.layers):
701
+ a = lyr.self_attn
702
+ D_l = int(a.head_dim)
703
+ shared = bool(a.is_kv_shared_layer)
704
+ ent = {
705
+ "D": D_l, "shared": shared,
706
+ "lscale": float(lyr.layer_scalar.float().item()),
707
+ "type": cfg.layer_types[li],
708
+ "I": int(lyr.mlp.intermediate_size),
709
+ "qn": pack(a.q_norm.weight),
710
+ "o": pack(a.o_proj.weight),
711
+ "ln_in": pack(lyr.input_layernorm.weight),
712
+ "ln_pa": pack(lyr.post_attention_layernorm.weight),
713
+ "ln_pf": pack(lyr.pre_feedforward_layernorm.weight),
714
+ "ln_ff": pack(lyr.post_feedforward_layernorm.weight),
715
+ "ln_pl": pack(lyr.post_per_layer_input_norm.weight),
716
+ "plig": pack(lyr.per_layer_input_gate.weight),
717
+ "plpr": pack(lyr.per_layer_projection.weight),
718
+ "wgu": pack(torch.cat([lyr.mlp.gate_proj.weight,
719
+ lyr.mlp.up_proj.weight], 0)),
720
+ "wdn": pack(lyr.mlp.down_proj.weight),
721
+ }
722
+ if shared:
723
+ ent["wqkv"] = pack(a.q_proj.weight)
724
+ ent["kn"] = None
725
+ else:
726
+ ent["wqkv"] = pack(torch.cat(
727
+ [a.q_proj.weight, a.k_proj.weight, a.v_proj.weight], 0))
728
+ ent["kn"] = pack(a.k_norm.weight)
729
+ ent["kc"] = torch.zeros(self.max_seq, D_l, dtype=dt,
730
+ device=dev)
731
+ ent["vc"] = torch.zeros(self.max_seq, D_l, dtype=dt,
732
+ device=dev)
733
+ self._keep += [ent["kc"], ent["vc"]]
734
+ layers.append(ent)
735
+ # shared layers read the last non-shared layer of their type
736
+ src = {}
737
+ for li, ent in enumerate(layers):
738
+ if not ent["shared"]:
739
+ src[ent["type"]] = ent
740
+ for ent in layers:
741
+ if ent["shared"]:
742
+ ent["kc"] = src[ent["type"]]["kc"]
743
+ ent["vc"] = src[ent["type"]]["vc"]
744
+
745
+ maxD = max(e["D"] for e in layers)
746
+ maxI = max(e["I"] for e in layers)
747
+ self._maxk = max(self.hidden, self.Hq * maxD, maxI)
748
+ self._chunk_m = 1
749
+ self._chunk = 128
750
+ self._maxch = (self.max_seq + self._chunk - 1) // self._chunk
751
+ self._hidden = torch.empty(self.hidden, dtype=dt, device=dev)
752
+ self._tmp = torch.empty(self.hidden, dtype=dt, device=dev)
753
+ self._qkv = torch.empty((self.Hq + 2) * maxD, dtype=dt, device=dev)
754
+ self._gu = torch.empty(2 * maxI, dtype=dt, device=dev)
755
+ self._t256 = torch.empty(PL, dtype=dt, device=dev)
756
+ self._ctxraw = torch.empty(self.L * PL, dtype=dt, device=dev)
757
+ self._pletok = torch.empty(self.L * PL, dtype=dt, device=dev)
758
+ self._ple = torch.empty(self.L * PL, dtype=dt, device=dev)
759
+ self._logits = torch.empty(self.V, dtype=torch.float32, device=dev)
760
+ self._partials = torch.empty(
761
+ self.Hq * self._maxch * (maxD + 2), dtype=torch.float32,
762
+ device=dev)
763
+ self._token = torch.zeros(1, dtype=torch.int32, device=dev)
764
+ self._prompt_in = torch.zeros(self.max_seq, dtype=torch.int32,
765
+ device=dev)
766
+ self._tokens_out = torch.zeros(self.max_gen, dtype=torch.int32,
767
+ device=dev)
768
+ self._bar = torch.zeros(34, dtype=torch.int32, device=dev)
769
+ probe = torch.zeros(1, 16, dtype=torch.int64, device=dev)
770
+ self._nblocks = int(ops.mak_num_blocks(probe, self._maxk))
771
+ self._parts = torch.zeros(self._nblocks, dtype=torch.int64,
772
+ device=dev)
773
+ try:
774
+ self._bw_per_sm = float(ops.mak_bw_per_sm(probe))
775
+ except (AttributeError, RuntimeError):
776
+ self._bw_per_sm = 0.0
777
+ self._invf = invf[layers[0]["type"]]
778
+ self.Hkv, self.D, self.I = 1, maxD, maxI
779
+ self.theta = 0.0
780
+
781
+ emb_scale = float(torch.tensor(self.hidden ** 0.5,
782
+ dtype=torch.float32).to(dt))
783
+ ple_scale = float(torch.tensor(PL ** 0.5,
784
+ dtype=torch.float32).to(dt))
785
+ proj_scale = float(torch.tensor(self.hidden ** -0.5,
786
+ dtype=torch.float32))
787
+ hid = self._hidden.data_ptr()
788
+ tmp = self._tmp.data_ptr()
789
+ rows, names = [], []
790
+ rows.append(_row(_OP_EMBED, p1=ple_tab.data_ptr(),
791
+ p2=embed.data_ptr(), p3=hid,
792
+ p4=self._parts.data_ptr(),
793
+ p5=self._prompt_in.data_ptr(),
794
+ p6=self._token.data_ptr(),
795
+ n=self._tokens_out.data_ptr(), k=self.hidden,
796
+ it=self._pletok.data_ptr(),
797
+ hkv=self.L * PL, f0=emb_scale,
798
+ i0=_fbits(ple_scale)))
799
+ names.append("embed")
800
+ rows.append(_row(_OP_GEMV, p1=hid, p2=plm_w.data_ptr(),
801
+ p3=self._ctxraw.data_ptr(), n=self.L * PL,
802
+ k=self.hidden, it=_IT_NONE, epi=_EP_STORE))
803
+ names.append("plm")
804
+ rows.append(_row(8, p1=self._ctxraw.data_ptr(),
805
+ p2=pln_w.data_ptr(), p3=self._ple.data_ptr(),
806
+ p4=self._pletok.data_ptr(), n=self.L, k=PL,
807
+ f0=self.eps, i0=_fbits(proj_scale)))
808
+ names.append("plemix")
809
+
810
+ for li, e in enumerate(layers):
811
+ D_l, qdim = e["D"], self.Hq * e["D"]
812
+ lt = e["type"]
813
+ w_l = win if lt == "sliding_attention" else 0
814
+ asb = _fbits(ascale[lt]) if ascale[lt] != 1.0 else 0
815
+ flags = 1 | 2 | (0 if e["shared"] else 4) | \
816
+ (8 if e["shared"] else 0)
817
+ n_qkv = qdim if e["shared"] else qdim + 2 * D_l
818
+ rows.append(_row(_OP_GEMV, p1=hid, p2=e["wqkv"].data_ptr(),
819
+ p3=self._qkv.data_ptr(),
820
+ p4=e["ln_in"].data_ptr(), n=n_qkv,
821
+ k=self.hidden, it=_IT_RMSNORM_G,
822
+ epi=_EP_STORE, f0=self.eps))
823
+ names.append(f"L{li}.qkv")
824
+ rows.append(_row(_OP_ATTN, p1=self._qkv.data_ptr(),
825
+ p2=e["kc"].data_ptr(), p3=e["vc"].data_ptr(),
826
+ p4=self._partials.data_ptr(),
827
+ p5=e["qn"].data_ptr(),
828
+ p6=e["kn"].data_ptr() if e["kn"] is not None
829
+ else 0,
830
+ n=self.max_seq,
831
+ k=flags | (w_l << 16), it=_fbits(self.eps),
832
+ epi=invf[lt].data_ptr(), hq=self.Hq,
833
+ hkv=1 | (self._chunk << 16), d=D_l, f0=1.0,
834
+ i0=self._maxch | (asb << 32)))
835
+ names.append(f"L{li}.attn")
836
+ rows.append(_row(_OP_GEMV, p1=self._partials.data_ptr(),
837
+ p2=e["o"].data_ptr(), p3=tmp,
838
+ n=self.hidden, k=qdim, it=_IT_ATTNFIN,
839
+ epi=_EP_STORE, hq=self.Hq,
840
+ hkv=self._chunk << 16, d=D_l,
841
+ i0=self._maxch | (w_l << 16)))
842
+ names.append(f"L{li}.o")
843
+ rows.append(_row(7, p1=tmp, p2=e["ln_pa"].data_ptr(), p3=hid,
844
+ n=self.hidden, f0=self.eps, i0=_fbits(1.0)))
845
+ names.append(f"L{li}.nr_attn")
846
+ rows.append(_row(_OP_GEMV, p1=hid, p2=e["wgu"].data_ptr(),
847
+ p3=self._gu.data_ptr(),
848
+ p4=e["ln_pf"].data_ptr(), n=2 * e["I"],
849
+ k=self.hidden, it=_IT_RMSNORM_G,
850
+ epi=_EP_STORE, f0=self.eps))
851
+ names.append(f"L{li}.gateup")
852
+ rows.append(_row(_OP_GEMV, p1=self._gu.data_ptr(),
853
+ p2=e["wdn"].data_ptr(), p3=tmp,
854
+ n=self.hidden, k=e["I"], it=_IT_GELU_GLU,
855
+ epi=_EP_STORE))
856
+ names.append(f"L{li}.down")
857
+ rows.append(_row(7, p1=tmp, p2=e["ln_ff"].data_ptr(), p3=hid,
858
+ n=self.hidden, f0=self.eps, i0=_fbits(1.0)))
859
+ names.append(f"L{li}.nr_ffw")
860
+ rows.append(_row(_OP_GEMV, p1=hid, p2=e["plig"].data_ptr(),
861
+ p3=self._t256.data_ptr(),
862
+ p6=self._ple.data_ptr() + li * PL * 2,
863
+ n=PL, k=self.hidden, it=_IT_NONE,
864
+ epi=_EP_GELU_PLE))
865
+ names.append(f"L{li}.plig")
866
+ rows.append(_row(_OP_GEMV, p1=self._t256.data_ptr(),
867
+ p2=e["plpr"].data_ptr(), p3=tmp,
868
+ n=self.hidden, k=PL, it=_IT_NONE,
869
+ epi=_EP_STORE))
870
+ names.append(f"L{li}.plproj")
871
+ rows.append(_row(7, p1=tmp, p2=e["ln_pl"].data_ptr(), p3=hid,
872
+ n=self.hidden, f0=self.eps,
873
+ i0=_fbits(e["lscale"])))
874
+ names.append(f"L{li}.nr_ple")
875
+
876
+ rows.append(_row(_OP_GEMV, p1=hid, p2=embed.data_ptr(),
877
+ p3=self._logits.data_ptr(),
878
+ p4=fnorm.data_ptr(),
879
+ p6=self._parts.data_ptr(), n=self.V,
880
+ k=self.hidden, it=_IT_RMSNORM_G,
881
+ epi=_EP_F32_AMAX_CAP, hkv=1, f0=self.eps,
882
+ i0=_fbits(cap)))
883
+ names.append("lm_head")
884
+ rows.append(_row(_OP_ARGMAX_FIN, p1=self._parts.data_ptr(),
885
+ p3=self._token.data_ptr(),
886
+ p4=self._tokens_out.data_ptr()))
887
+ names.append("argmax.fin")
888
+ self._prog = torch.tensor(rows, dtype=torch.int64, device=dev)
889
+ self.phase_names = names
890
+ self._gemma = True
891
+ self._batch = 0
892
+ return self
893
+
894
+ # ------------------------------------------------------------------
895
+ def decode_step(self, token: int, pos: int, phased: bool = False):
896
+ """One decode step; returns the live fp32 logits buffer [V]."""
897
+ if pos >= self.max_seq:
898
+ raise ValueError("pos exceeds max_seq")
899
+ self._token.fill_(int(token))
900
+ if phased:
901
+ ops.mak_run_phased(self._prog, pos, self.max_gen - 1, False,
902
+ self._maxk)
903
+ else:
904
+ ops.mak_run(self._prog, self._bar, pos, self.max_gen - 1,
905
+ self._maxk)
906
+ return self._logits[:self.V]
907
+
908
+ def decode_step_timed(self, token: int, pos: int):
909
+ """One phase-per-launch step; returns (logits, per-phase ms)."""
910
+ self._token.fill_(int(token))
911
+ ms = ops.mak_run_phased(self._prog, pos, self.max_gen - 1, True,
912
+ self._maxk)
913
+ return self._logits[:self.V], ms
914
+
915
+ def prefill(self, ids: Sequence[int], phased: bool = False):
916
+ """Consume a prompt; returns the last position's logits. Runs in
917
+ chunks of up to chunk_m tokens with weight reads amortized across
918
+ the chunk (bitwise identical to token-by-token consumption)."""
919
+ P = len(ids)
920
+ if phased:
921
+ for i, t in enumerate(ids):
922
+ self.decode_step(int(t), i, phased=True)
923
+ return self._logits
924
+ self._prompt_in[:P].copy_(torch.tensor(ids, dtype=torch.int32))
925
+ ops.mak_run_seq(self._prog, self._bar, 0, P, 1 - P, P, self._maxk,
926
+ self._chunk_m)
927
+ return self._logits[:self.V]
928
+
929
+ def generate(self, prompt_ids: Sequence[int], max_new: int,
930
+ single_launch: bool = True) -> List[int]:
931
+ """Greedy generation. With single_launch (default) the entire call,
932
+ prompt consumption included, is one kernel launch: the in-kernel step
933
+ loop reads prompt tokens from a staged device buffer, then feeds each
934
+ step's argmax to the next step's embedding read. With
935
+ single_launch=False the same computation runs as one launch per
936
+ token; the two paths produce identical tokens.
937
+ """
938
+ P = len(prompt_ids)
939
+ if max_new < 1 or max_new > self.max_gen:
940
+ raise ValueError("max_new out of range")
941
+ if P < 1 or P + max_new > self.max_seq:
942
+ raise ValueError("sequence exceeds max_seq")
943
+ if single_launch:
944
+ self._prompt_in[:P].copy_(
945
+ torch.tensor(prompt_ids, dtype=torch.int32))
946
+ ops.mak_run_seq(self._prog, self._bar, 0, P + max_new - 1,
947
+ 1 - P, P, self._maxk, self._chunk_m)
948
+ return self._tokens_out[:max_new].cpu().tolist()
949
+ pos = 0
950
+ for t in prompt_ids[:-1]:
951
+ self._token.fill_(int(t))
952
+ ops.mak_run(self._prog, self._bar, pos, self.max_gen - 1,
953
+ self._maxk)
954
+ pos += 1
955
+ self._token.fill_(int(prompt_ids[-1]))
956
+ ops.mak_run(self._prog, self._bar, pos, 0, self._maxk)
957
+ pos += 1
958
+ if max_new > 1:
959
+ ops.mak_run_steps(self._prog, self._bar, pos, max_new - 1, 1,
960
+ self._maxk)
961
+ return self._tokens_out[:max_new].cpu().tolist()
962
+
963
+
964
+
build/torch213-cxx11-cu130-x86_64-linux/_model_as_a_kernel_cuda_e33875a.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c23353414f62f5288193b6608c66a56b3b74ed86a4ab404dbea27dd31cd33f63
3
+ size 6358528
build/torch213-cxx11-cu130-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _model_as_a_kernel_cuda_e33875a
3
+ ops = torch.ops._model_as_a_kernel_cuda_e33875a
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_model_as_a_kernel_cuda_e33875a::{op_name}"
build/torch213-cxx11-cu130-x86_64-linux/metadata.json ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "model-as-a-kernel",
3
+ "id": "_model_as_a_kernel_cuda_e33875a",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "python-depends": [],
7
+ "backend": {
8
+ "type": "cuda",
9
+ "archs": [
10
+ "10.0",
11
+ "12.0",
12
+ "8.0",
13
+ "8.6",
14
+ "8.9",
15
+ "9.0"
16
+ ]
17
+ },
18
+ "digest": {
19
+ "algorithm": "sha256",
20
+ "files": {
21
+ "__init__.py": "/YX5/zzAOyJacQ7H+j8zaq+xjWyy8w6r0j+ugE1EUs8=",
22
+ "_model_as_a_kernel_cuda_e33875a.abi3.so": "wjNTQU9i9SiBk7ZgjGalazt07Yakq0BNvqJ90xzTP2M=",
23
+ "_ops.py": "Pqj4WKtwPyeDG9grQnzLget4g1jsGUdR7ZMa44e4rhs=",
24
+ "model_as_a_kernel/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
25
+ }
26
+ },
27
+ "provenance": {
28
+ "kernel-builder": {
29
+ "version": "0.17.0-dev0",
30
+ "sha": "05f1ed487f599a320311aa26076ec0ad290c3f68",
31
+ "dirty": false
32
+ },
33
+ "kernel": {
34
+ "sha": "e33875ae4eba42fd136a89c388e806281cd4759c",
35
+ "dirty": false
36
+ }
37
+ }
38
+ }
build/torch213-cxx11-cu130-x86_64-linux/model_as_a_kernel/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import importlib.util
3
+ import sys
4
+ from pathlib import Path
5
+ from types import ModuleType
6
+
7
+
8
+ def _import_from_path(file_path: Path) -> ModuleType:
9
+ # We cannot use the module name as-is, after adding it to `sys.modules`,
10
+ # it would also be used for other imports. So, we make a module name that
11
+ # depends on the path for it to be unique using the hex-encoded hash of
12
+ # the path.
13
+ path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
+ module_name = path_hash
15
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
16
+ if spec is None:
17
+ raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
+ module = importlib.util.module_from_spec(spec)
19
+ if module is None:
20
+ raise ImportError(f"Cannot load module {module_name} from spec")
21
+ sys.modules[module_name] = module
22
+ spec.loader.exec_module(module) # type: ignore
23
+ return module
24
+
25
+
26
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
build/torch213-cxx11-cu132-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,964 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """model-as-a-kernel: an entire llama-family decode step as one kernel launch.
2
+
3
+ A phase interpreter runs the whole forward pass (embedding, every layer's
4
+ norms, QKV, rope, attention over the KV cache, SwiGLU MLP, the LM head, and
5
+ the greedy argmax) inside a single persistent kernel, with a software grid
6
+ barrier between phases. The in-kernel step loop makes an entire greedy
7
+ generation, prompt consumption included, one kernel launch: each step's
8
+ argmax feeds the next step's embedding read without leaving the device.
9
+ """
10
+ import os
11
+ import struct
12
+ from typing import List, Optional, Sequence
13
+
14
+ import torch
15
+
16
+ try:
17
+ from ._ops import ops
18
+ except ImportError: # local development build (torch.utils.cpp_extension)
19
+ class _LocalOps:
20
+ def __getattr__(self, name):
21
+ return getattr(torch.ops.mak_ext, name)
22
+
23
+ ops = _LocalOps()
24
+
25
+ __all__ = ["MegaModel"]
26
+
27
+ _OP_EMBED, _OP_GEMV, _OP_QKV_POST, _OP_ATTN = 0, 1, 2, 3
28
+ _OP_ARGMAX_PART, _OP_ARGMAX_FIN = 4, 5
29
+ _OP_KV_APPEND, _OP_NORMRES, _OP_PLEMIX = 6, 7, 8
30
+ _OP_GEMV_PLAIN, _OP_NORMB, _OP_GLUB, _OP_ATTNFINB = 9, 10, 11, 12
31
+ _OP_GEMV_Q4 = 13
32
+ _IT_NONE, _IT_RMSNORM, _IT_SWIGLU, _IT_ATTNFIN = 0, 1, 2, 3
33
+ _IT_RMSNORM_G, _IT_GELU_GLU = 4, 5
34
+ _EP_GELU_PLE, _EP_F32_AMAX_CAP = 5, 6
35
+ _ITF_REDUCE, _ITF_WRITEBACK = 8, 16
36
+ _EP_STORE, _EP_RESID, _EP_F32, _EP_F32_AMAX, _EP_PARTIAL = 0, 1, 2, 3, 4
37
+ _NSLICE = 4
38
+ # Widest [B][K] input panel (bf16 elements) a batched projection stages in
39
+ # shared before it spills to the global-scratch path. 16384 (32 KB) keeps
40
+ # two blocks resident per SM; the persistent kernel shares one occupancy
41
+ # across all phases, so a wider panel would drop every phase to one block
42
+ # per SM, which measures slower than reading the input from L2.
43
+ _STAGE_BUDGET = 16384
44
+
45
+
46
+ def _fbits(f: float) -> int:
47
+ return struct.unpack("<i", struct.pack("<f", float(f)))[0]
48
+
49
+
50
+ def _row(op, p1=0, p2=0, p3=0, p4=0, p5=0, p6=0, n=0, k=0, it=0, epi=0,
51
+ hq=0, hkv=0, d=0, f0=0.0, i0=0):
52
+ return [int(op), int(p1), int(p2), int(p3), int(p4), int(p5), int(p6),
53
+ int(n), int(k), int(it), int(epi), int(hq), int(hkv), int(d),
54
+ _fbits(f0), int(i0)]
55
+
56
+
57
+ class MegaModel:
58
+ """A packed llama-family decoder whose decode step is one kernel launch.
59
+
60
+ Supported architecture: RMSNorm decoder blocks with rotary attention
61
+ (GQA, optional Qwen3-style per-head qk RMSNorm), SwiGLU MLP, no attention
62
+ or MLP biases, plain (unscaled) rope, batch size 1. This covers SmolLM2,
63
+ TinyLlama, Qwen3 dense, Llama-class checkpoints without rope scaling,
64
+ and similar models.
65
+ """
66
+
67
+ def __init__(self, weights: dict, config: dict, device="cuda",
68
+ max_seq: int = 4096, max_gen: int = 4096):
69
+ cfg = dict(config)
70
+ self.L = int(cfg["num_hidden_layers"])
71
+ self.hidden = int(cfg["hidden_size"])
72
+ self.Hq = int(cfg["num_attention_heads"])
73
+ self.Hkv = int(cfg.get("num_key_value_heads") or self.Hq)
74
+ self.D = int(cfg.get("head_dim") or self.hidden // self.Hq)
75
+ self.I = int(cfg["intermediate_size"])
76
+ self.V = int(cfg["vocab_size"])
77
+ self.theta = float(cfg.get("rope_theta", 10000.0))
78
+ self.eps = float(cfg.get("rms_norm_eps", 1e-6))
79
+ self.qk_norm = bool(cfg.get("qk_norm", False))
80
+ self.max_seq = int(max_seq)
81
+ self.max_gen = int(max_gen)
82
+ self.device = torch.device(device)
83
+
84
+ if self.D % 2 or self.D > 256:
85
+ raise ValueError("head_dim must be even and <= 256")
86
+ if self.hidden % 8 or self.I % 8 or (self.Hq * self.D) % 8:
87
+ raise ValueError(
88
+ "hidden, intermediate, and Hq*head_dim must be multiples of 8")
89
+ if self.Hq % self.Hkv:
90
+ raise ValueError("num_attention_heads must be divisible by num_key_value_heads")
91
+
92
+ dt = torch.bfloat16
93
+ dev = self.device
94
+
95
+ def pack(t):
96
+ t = t.detach().to(device=dev, dtype=dt).contiguous()
97
+ self._keep.append(t)
98
+ return t
99
+
100
+ # Row-tile-of-8 weight layout [N/8][K/8][8][8] (MAK_TILED=1): the
101
+ # eight warps of a block read one contiguous 4KB stream per step
102
+ # instead of eight 4KB-strided pieces. Measured slower on every
103
+ # card (H200 +15 percent, RTX PRO +9, Ada +14 — per-warp stream
104
+ # locality beats per-block contiguity on all of them), so the
105
+ # default is the plain layout; the flag rides in the program and
106
+ # both layouts compute identical bits.
107
+ env_t = os.environ.get("MAK_TILED", "").strip()
108
+ self.tiled = env_t == "1"
109
+
110
+ def pack_tiled(t):
111
+ t = t.detach().to(device=dev, dtype=dt).contiguous()
112
+ if not self.tiled:
113
+ self._keep.append(t)
114
+ return t
115
+ n, k = t.shape
116
+ assert k % 8 == 0
117
+ n8 = (n + 7) // 8 * 8
118
+ if n8 != n:
119
+ t = torch.cat(
120
+ [t, torch.zeros(n8 - n, k, dtype=dt, device=dev)], 0)
121
+ t = t.view(n8 // 8, 8, k // 8, 8).permute(0, 2, 1, 3).contiguous()
122
+ self._keep.append(t)
123
+ return t
124
+
125
+ self._keep: List[torch.Tensor] = []
126
+ embed = pack(weights["embed"])
127
+ final_norm = pack(weights["norm"])
128
+ lm_head = weights["lm_head"] # packed below (dense-tiled or nf4)
129
+ inv_freq = weights.get("inv_freq")
130
+ if inv_freq is None:
131
+ d_idx = torch.arange(0, self.D, 2, dtype=torch.float32)
132
+ inv_freq = 1.0 / (self.theta ** (d_idx / self.D))
133
+ inv_freq = inv_freq.detach().to(device=dev,
134
+ dtype=torch.float32).contiguous()
135
+ if inv_freq.numel() != self.D // 2:
136
+ raise ValueError("inv_freq length must be head_dim / 2")
137
+ self._keep.append(inv_freq)
138
+ self._invf = inv_freq
139
+
140
+ qdim, kvdim = self.Hq * self.D, self.Hkv * self.D
141
+
142
+ def _is_q4(w):
143
+ return isinstance(w, dict) and w.get("q4")
144
+
145
+ def pack_w(w):
146
+ # nf4-packed weights move to the device intact; dense weights take
147
+ # the usual (optionally row-tiled) bf16 packing.
148
+ if _is_q4(w):
149
+ pk = w["packed"].to(dev).contiguous()
150
+ am = w["absmax"].to(device=dev, dtype=torch.float32).contiguous()
151
+ self._keep.append(pk)
152
+ self._keep.append(am)
153
+ return {"q4": True, "packed": pk, "absmax": am,
154
+ "N": int(w["N"]), "K": int(w["K"])}
155
+ return pack_tiled(w)
156
+
157
+ def _wshape(w, expected):
158
+ got = (int(w["N"]), int(w["K"])) if _is_q4(w) else tuple(w.shape)
159
+ assert got == expected, (got, expected)
160
+
161
+ self._has_q4 = False
162
+ self._layers = []
163
+ for lw in weights["layers"]:
164
+ _wshape(lw["wqkv"], (qdim + 2 * kvdim, self.hidden))
165
+ _wshape(lw["wo"], (self.hidden, qdim))
166
+ _wshape(lw["wgu"], (2 * self.I, self.hidden))
167
+ _wshape(lw["wdown"], (self.hidden, self.I))
168
+ layer = {
169
+ "wqkv": pack_w(lw["wqkv"]), "wo": pack_w(lw["wo"]),
170
+ "wgu": pack_w(lw["wgu"]), "wdown": pack_w(lw["wdown"]),
171
+ "ln1": pack(lw["ln1"]), "ln2": pack(lw["ln2"]),
172
+ "qn": pack(lw["qn"]) if self.qk_norm else None,
173
+ "kn": pack(lw["kn"]) if self.qk_norm else None,
174
+ }
175
+ self._has_q4 |= any(_is_q4(layer[n]) for n in
176
+ ("wqkv", "wo", "wgu", "wdown"))
177
+ self._layers.append(layer)
178
+ assert len(self._layers) == self.L
179
+ assert embed.shape == (self.V, self.hidden)
180
+ lm_head = pack_w(lm_head)
181
+ self._has_q4 |= _is_q4(lm_head)
182
+ if not _is_q4(lm_head):
183
+ assert lm_head.numel() >= self.V * self.hidden # row-tiled, padded
184
+ self._w_embed, self._w_fnorm, self._w_lmhead = (embed, final_norm,
185
+ lm_head)
186
+ self._qdim, self._kvdim = qdim, kvdim
187
+
188
+ # Working buffers. Pointers to these are baked into the program, so
189
+ # they (like the packed weights) live for the model's lifetime.
190
+ self._maxk = max(self.hidden, qdim, self.I)
191
+ # prefill chunk size, bounded by the staging shared-memory budget
192
+ # (16K bf16 elements; the cp.async weight ring rides alongside)
193
+ self._chunk_m = max(1, min(8, 16384 // self._maxk))
194
+ cm = 8 # buffers sized for the maximum chunk
195
+ self._hidden = torch.empty(cm * self.hidden, dtype=dt, device=dev)
196
+ self._qkv = torch.empty(cm * (qdim + 2 * kvdim), dtype=dt, device=dev)
197
+ self._gu = torch.empty(cm * 2 * self.I, dtype=dt, device=dev)
198
+ self._logits = torch.empty(self.V, dtype=torch.float32, device=dev)
199
+ # attention chunk length (kernel maximum 128); shorter chunks give
200
+ # the attention phase more grid parallelism at the cost of more
201
+ # softmax partials to finalize
202
+ env_ch = os.environ.get("MAK_CHUNK", "").strip()
203
+ self._chunk = int(env_ch) if env_ch in ("32", "64", "128") else 128
204
+ self._maxch = (self.max_seq + self._chunk - 1) // self._chunk
205
+ self._partials = torch.empty(
206
+ cm * self.Hq * self._maxch * (self.D + 2), dtype=torch.float32,
207
+ device=dev)
208
+ self._kcache = torch.zeros(self.L, self.Hkv, self.max_seq, self.D,
209
+ dtype=dt, device=dev)
210
+ self._vcache = torch.zeros(self.L, self.Hkv, self.max_seq, self.D,
211
+ dtype=dt, device=dev)
212
+ self._token = torch.zeros(1, dtype=torch.int32, device=dev)
213
+ self._prompt_in = torch.zeros(self.max_seq, dtype=torch.int32,
214
+ device=dev)
215
+ self._tokens_out = torch.zeros(self.max_gen, dtype=torch.int32,
216
+ device=dev)
217
+ self._bar = torch.zeros(34, dtype=torch.int32, device=dev)
218
+
219
+ probe = torch.zeros(1, 16, dtype=torch.int64, device=dev)
220
+ self._nblocks = int(ops.mak_num_blocks(probe, self._maxk))
221
+ self._parts = torch.zeros(self._nblocks, dtype=torch.int64, device=dev)
222
+ try:
223
+ self._bw_per_sm = float(ops.mak_bw_per_sm(probe))
224
+ except (AttributeError, RuntimeError):
225
+ self._bw_per_sm = 0.0
226
+
227
+ self._batch = 0
228
+ self._pos_b = None
229
+ self._kv_bstride = 0
230
+ self._gemma = False
231
+ # transformed-input scratch: the nf4 and wide-batch paths write the
232
+ # dense bf16 input here (one row per active token) for the following
233
+ # plain/quant GEMV; sized for a prefill chunk (up to 8 rows)
234
+ self._xg = torch.empty(cm * self._maxk, dtype=dt, device=dev)
235
+ self._build_programs()
236
+
237
+ # ------------------------------------------------------------------
238
+ def _kvp(self, cache, li: int, b: int) -> int:
239
+ t = cache[li]
240
+ return (t[b] if t.dim() == 4 else t).data_ptr()
241
+
242
+ def _build_rows(self, kv_slice: int = 0, batch: bool = False,
243
+ big: bool = False):
244
+ """Program rows for the llama-family path. kv_slice selects the
245
+ batch slice the KV pointers address (per-sequence prefill);
246
+ batch=True emits the batched-decode tail (per-row fp32 logits and
247
+ explicit argmax phases) instead of the fused LM-head amax. In batch
248
+ mode each projection stays fused (input staged in shared) when its
249
+ [B][K] panel fits the budget; the projections whose K is too wide
250
+ transform into a global scratch and read it with a plain GEMV."""
251
+ _tbit = (1 << 20) if self.tiled else 0 # GEMV weight layout flag
252
+ qdim, kvdim = self._qdim, self._kvdim
253
+ embed, lm_head = self._w_embed, self._w_lmhead
254
+ final_norm = self._w_fnorm
255
+ xg = self._xg.data_ptr()
256
+ B = self._batch if batch else 1
257
+ rows, names = [], []
258
+ staged_elems = [0] # widest [B][K] panel any fused projection stages
259
+
260
+ def fits(K):
261
+ return B * K <= _STAGE_BUDGET
262
+
263
+ def note(K):
264
+ staged_elems[0] = max(staged_elems[0], B * K)
265
+
266
+ def _q4(w):
267
+ return isinstance(w, dict) and w.get("q4")
268
+
269
+ def _wgemv(out, w, N, K, epi, resid):
270
+ # the GEMV after a transform-to-scratch phase: nf4-dequant when
271
+ # the weight is packed, otherwise a plain bf16 GEMV
272
+ if _q4(w):
273
+ rows.append(_row(_OP_GEMV_Q4, p1=xg, p2=w["packed"].data_ptr(),
274
+ p3=out, p5=resid, p6=w["absmax"].data_ptr(),
275
+ n=N, k=K, epi=epi))
276
+ else:
277
+ rows.append(_row(_OP_GEMV_PLAIN, p1=xg, p2=w.data_ptr(),
278
+ p3=out, p5=resid, n=N, k=K, it=_tbit,
279
+ epi=epi))
280
+
281
+ def norm_gemv(name, gamma, w, out, N, K, epi, inp, resid=0,
282
+ variant=_IT_RMSNORM):
283
+ if _q4(w) or (big and not fits(K)):
284
+ rows.append(_row(_OP_NORMB, p1=inp, p2=gamma, p3=xg, k=K,
285
+ it=variant, f0=self.eps))
286
+ names.append(name + ".norm")
287
+ _wgemv(out, w, N, K, epi, resid)
288
+ names.append(name)
289
+ else:
290
+ note(K)
291
+ rows.append(_row(_OP_GEMV, p1=inp, p2=w.data_ptr(), p3=out,
292
+ p4=gamma, p5=resid, n=N, k=K,
293
+ it=variant | _tbit, epi=epi, f0=self.eps))
294
+ names.append(name)
295
+
296
+ def glu_gemv(name, gu, w, out, resid, variant=_IT_SWIGLU):
297
+ if _q4(w) or (big and not fits(self.I)):
298
+ rows.append(_row(_OP_GLUB, p1=gu, p3=xg, k=self.I, it=variant))
299
+ names.append(name + ".glu")
300
+ _wgemv(out, w, self.hidden, self.I, _EP_RESID, resid)
301
+ names.append(name)
302
+ else:
303
+ note(self.I)
304
+ rows.append(_row(_OP_GEMV, p1=gu, p2=w.data_ptr(), p3=out,
305
+ p5=resid, n=self.hidden, k=self.I,
306
+ it=variant | _tbit, epi=_EP_RESID))
307
+ names.append(name)
308
+
309
+ def attn_gemv(name, w, out, resid):
310
+ if _q4(w) or (big and not fits(qdim)):
311
+ rows.append(_row(_OP_ATTNFINB, p1=self._partials.data_ptr(),
312
+ p3=xg, k=qdim, hq=self.Hq,
313
+ hkv=self._chunk << 16, d=self.D,
314
+ i0=self._maxch))
315
+ names.append(name + ".fin")
316
+ _wgemv(out, w, self.hidden, qdim, _EP_RESID, resid)
317
+ names.append(name)
318
+ else:
319
+ note(qdim)
320
+ rows.append(_row(_OP_GEMV, p1=self._partials.data_ptr(),
321
+ p2=w.data_ptr(), p3=out, p5=resid,
322
+ n=self.hidden, k=qdim,
323
+ it=_IT_ATTNFIN | _tbit, epi=_EP_RESID,
324
+ hq=self.Hq, hkv=self._chunk << 16, d=self.D,
325
+ i0=self._maxch))
326
+ names.append(name)
327
+
328
+ rows.append(_row(_OP_EMBED, p2=embed.data_ptr(),
329
+ p3=self._hidden.data_ptr(),
330
+ p4=self._parts.data_ptr(),
331
+ p5=self._prompt_in.data_ptr(),
332
+ p6=self._token.data_ptr(),
333
+ n=self._tokens_out.data_ptr(), k=self.hidden))
334
+ names.append("embed")
335
+ scale = 1.0 / (self.D ** 0.5)
336
+ hid = self._hidden.data_ptr()
337
+ for li, lw in enumerate(self._layers):
338
+ kc = self._kvp(self._kcache, li, kv_slice)
339
+ vc = self._kvp(self._vcache, li, kv_slice)
340
+ norm_gemv(f"L{li}.qkv", lw["ln1"].data_ptr(),
341
+ lw["wqkv"], self._qkv.data_ptr(),
342
+ qdim + 2 * kvdim, self.hidden, _EP_STORE, hid)
343
+ rows.append(_row(6, p1=self._qkv.data_ptr(), p2=kc, p3=vc,
344
+ p6=lw["kn"].data_ptr() if self.qk_norm else 0,
345
+ n=self.max_seq, k=1 if self.qk_norm else 0,
346
+ it=_fbits(self.eps),
347
+ epi=self._invf.data_ptr(),
348
+ hq=self.Hq, hkv=self.Hkv, d=self.D))
349
+ names.append(f"L{li}.kvappend")
350
+ rows.append(_row(_OP_ATTN, p1=self._qkv.data_ptr(), p2=kc,
351
+ p3=vc, p4=self._partials.data_ptr(),
352
+ p5=lw["qn"].data_ptr() if self.qk_norm else 0,
353
+ p6=lw["kn"].data_ptr() if self.qk_norm else 0,
354
+ n=self.max_seq, k=1 if self.qk_norm else 0,
355
+ it=_fbits(self.eps),
356
+ epi=self._invf.data_ptr(),
357
+ hq=self.Hq,
358
+ hkv=self.Hkv | (self._chunk << 16),
359
+ d=self.D, f0=scale, i0=self._maxch))
360
+ names.append(f"L{li}.attn")
361
+ attn_gemv(f"L{li}.o", lw["wo"], hid, hid)
362
+ norm_gemv(f"L{li}.gateup", lw["ln2"].data_ptr(),
363
+ lw["wgu"], self._gu.data_ptr(), 2 * self.I,
364
+ self.hidden, _EP_STORE, hid)
365
+ glu_gemv(f"L{li}.down", self._gu.data_ptr(), lw["wdown"], hid, hid)
366
+ if batch or _q4(lm_head):
367
+ norm_gemv("lm_head", final_norm.data_ptr(), lm_head,
368
+ self._logits.data_ptr(), self.V, self.hidden, _EP_F32,
369
+ hid)
370
+ rows.append(_row(_OP_ARGMAX_PART, p1=self._logits.data_ptr(),
371
+ p3=self._parts.data_ptr(), n=self.V))
372
+ names.append("argmax.part")
373
+ rows.append(_row(_OP_ARGMAX_FIN, p1=self._parts.data_ptr(),
374
+ p3=self._token.data_ptr(),
375
+ p4=self._tokens_out.data_ptr(),
376
+ k=self.max_gen))
377
+ names.append("argmax.fin")
378
+ else:
379
+ rows.append(_row(_OP_GEMV, p1=hid, p2=lm_head.data_ptr(),
380
+ p3=self._logits.data_ptr(),
381
+ p4=final_norm.data_ptr(),
382
+ p6=self._parts.data_ptr(), n=self.V,
383
+ k=self.hidden, it=_IT_RMSNORM | _tbit,
384
+ epi=_EP_F32_AMAX, hkv=1, f0=self.eps))
385
+ names.append("lm_head")
386
+ rows.append(_row(_OP_ARGMAX_FIN, p1=self._parts.data_ptr(),
387
+ p3=self._token.data_ptr(),
388
+ p4=self._tokens_out.data_ptr()))
389
+ names.append("argmax.fin")
390
+ se = max(staged_elems[0], self._maxk)
391
+ return rows, names, se
392
+
393
+ def _build_programs(self):
394
+ dev = self.device
395
+ rows, names, _ = self._build_rows(kv_slice=0, batch=False)
396
+ self._prog = torch.tensor(rows, dtype=torch.int64, device=dev)
397
+ self.phase_names = names
398
+ if self._batch:
399
+ big = self._batch > min(8, 16384 // self._maxk)
400
+ rows, _, se = self._build_rows(kv_slice=0, batch=True, big=big)
401
+ self._prog_batch = torch.tensor(rows, dtype=torch.int64,
402
+ device=dev)
403
+ self._batch_stage_elems = int(se)
404
+ self._progs_prefill = [self._prog]
405
+ for b in range(1, self._batch):
406
+ rows, _, _ = self._build_rows(kv_slice=b, batch=False)
407
+ self._progs_prefill.append(
408
+ torch.tensor(rows, dtype=torch.int64, device=dev))
409
+
410
+ def batch_max(self) -> int:
411
+ """Largest batch size this model supports. Up to 8 sequences stage
412
+ in shared; beyond that the transformed input moves to a global
413
+ scratch, bounded by the kernel's register accumulators."""
414
+ try:
415
+ hard = int(ops.mak_batch_maxb())
416
+ except (AttributeError, RuntimeError):
417
+ hard = 8
418
+ return 1 if self._gemma else hard
419
+
420
+ # ------------------------------------------------------------------
421
+ def enable_batch(self, B: int):
422
+ """Allocate per-sequence KV caches, token buffers, and programs for
423
+ batched decode. B up to 8 stages the input in shared; larger B (up
424
+ to batch_max()) routes the transformed input through a global
425
+ scratch so the batch width is not bounded by the projection size."""
426
+ if self._gemma:
427
+ raise ValueError("batched decode covers the llama-family path")
428
+ maxb = self.batch_max()
429
+ if not 1 <= B <= maxb:
430
+ raise ValueError(f"B must be in [1, {maxb}] for this model")
431
+ if self._batch == B:
432
+ return
433
+ dt, dev = torch.bfloat16, self.device
434
+ self._kcache = torch.zeros(self.L, B, self.Hkv, self.max_seq,
435
+ self.D, dtype=dt, device=dev)
436
+ self._vcache = torch.zeros_like(self._kcache)
437
+ self._token = torch.zeros(B, dtype=torch.int32, device=dev)
438
+ self._tokens_out = torch.zeros(B * self.max_gen, dtype=torch.int32,
439
+ device=dev)
440
+ self._logits = torch.empty(B * self.V, dtype=torch.float32,
441
+ device=dev)
442
+ self._parts = torch.zeros(B * self._nblocks, dtype=torch.int64,
443
+ device=dev)
444
+ self._pos_b = torch.zeros(B, dtype=torch.int32, device=dev)
445
+ # transformed-input scratch: one row per active token, and prefill
446
+ # (during generate_batch) still uses up to chunk_m rows
447
+ self._xg = torch.empty(max(8, B) * self._maxk, dtype=dt, device=dev)
448
+ # working buffers hold one row per active sequence; prefill chunks
449
+ # still use up to chunk_m rows, so keep at least the fused capacity
450
+ nb = max(8, B)
451
+ qkvd = self._qdim + 2 * self._kvdim
452
+ self._hidden = torch.empty(nb * self.hidden, dtype=dt, device=dev)
453
+ self._qkv = torch.empty(nb * qkvd, dtype=dt, device=dev)
454
+ self._gu = torch.empty(nb * 2 * self.I, dtype=dt, device=dev)
455
+ self._partials = torch.empty(
456
+ nb * self.Hq * self._maxch * (self.D + 2), dtype=torch.float32,
457
+ device=dev)
458
+ self._kv_bstride = self.Hkv * self.max_seq * self.D
459
+ self._batch = B
460
+ self._build_programs()
461
+
462
+ def decode_batch(self, tokens: Sequence[int], positions: Sequence[int],
463
+ steps: int = 1) -> torch.Tensor:
464
+ """Batched greedy decode: sequence b consumes tokens[b] at
465
+ positions[b]; `steps` in-kernel steps run with per-sequence argmax
466
+ feedback. Returns the generated tokens [B, steps]; the final
467
+ step's fp32 logits are live in batch_logits()."""
468
+ B = self._batch
469
+ if B < 1:
470
+ raise ValueError("call enable_batch(B) first")
471
+ if len(tokens) != B or len(positions) != B:
472
+ raise ValueError("tokens and positions must have B entries")
473
+ if steps < 1 or steps > self.max_gen:
474
+ raise ValueError("steps out of range")
475
+ if max(positions) + steps > self.max_seq:
476
+ raise ValueError("sequence exceeds max_seq")
477
+ self._token.copy_(torch.tensor(list(tokens), dtype=torch.int32))
478
+ self._pos_b.copy_(torch.tensor(list(positions), dtype=torch.int32))
479
+ ops.mak_run_batch(self._prog_batch, self._bar, self._pos_b, steps,
480
+ 0, self._maxk, self._kv_bstride,
481
+ self._batch_stage_elems)
482
+ return self._tokens_out.view(B, self.max_gen)[:, :steps]
483
+
484
+ def batch_logits(self) -> torch.Tensor:
485
+ """fp32 logits [B, V] of the most recent batched step."""
486
+ return self._logits.view(max(self._batch, 1), self.V)
487
+
488
+ def generate_batch(self, prompts: Sequence[Sequence[int]],
489
+ max_new: int) -> List[List[int]]:
490
+ """Greedy generation for a batch of prompts: per-sequence prefill,
491
+ then one batched decode launch. Each sequence's token stream is
492
+ bit-identical to its own single-sequence generate()."""
493
+ B = len(prompts)
494
+ if max_new < 1 or max_new > self.max_gen:
495
+ raise ValueError("max_new out of range")
496
+ self.enable_batch(B)
497
+ firsts: List[int] = []
498
+ for b, ids in enumerate(prompts):
499
+ P = len(ids)
500
+ if P < 1 or P + max_new > self.max_seq:
501
+ raise ValueError("sequence exceeds max_seq")
502
+ self._prompt_in[:P].copy_(
503
+ torch.tensor(list(ids), dtype=torch.int32))
504
+ ops.mak_run_seq(self._progs_prefill[b], self._bar, 0, P, 1 - P,
505
+ P, self._maxk, self._chunk_m)
506
+ firsts.append(int(self._token[0].item()))
507
+ if max_new == 1:
508
+ return [[t] for t in firsts]
509
+ self._token.copy_(torch.tensor(firsts, dtype=torch.int32))
510
+ self._pos_b.copy_(torch.tensor([len(p) for p in prompts],
511
+ dtype=torch.int32))
512
+ ops.mak_run_batch(self._prog_batch, self._bar, self._pos_b,
513
+ max_new - 1, 0, self._maxk, self._kv_bstride,
514
+ self._batch_stage_elems)
515
+ rest = (self._tokens_out.view(B, self.max_gen)[:, :max_new - 1]
516
+ .cpu().tolist())
517
+ return [[firsts[b]] + rest[b] for b in range(B)]
518
+
519
+ # ------------------------------------------------------------------
520
+ @staticmethod
521
+ def _dense_weight(mod):
522
+ """The effective bf16 weight of a linear layer, dequantizing packed
523
+ quantized layers (bitsandbytes 4-bit and 8-bit) so a quantized
524
+ checkpoint loads directly. Dequantization is exact and does not
525
+ depend on the card, so inference on the result keeps every
526
+ guarantee; the weights materialize to bf16, so the quantized memory
527
+ footprint is not preserved (that needs the in-kernel packed path)."""
528
+ w = mod.weight
529
+ qs = getattr(w, "quant_state", None)
530
+ if qs is not None: # bitsandbytes 4-bit (nf4 / fp4)
531
+ import bitsandbytes as bnb
532
+ return bnb.functional.dequantize_4bit(w.data, qs).to(torch.bfloat16)
533
+ if getattr(w, "SCB", None) is not None or hasattr(mod, "SCB"):
534
+ # bitsandbytes 8-bit (LLM.int8): row scales in SCB, int8 in CB
535
+ scb = w.SCB if getattr(w, "SCB", None) is not None else mod.SCB
536
+ cb = w.data if w.data.dtype == torch.int8 else mod.CB
537
+ return (cb.to(torch.float32)
538
+ * (scb.to(torch.float32) / 127.0).unsqueeze(1)
539
+ ).to(torch.bfloat16)
540
+ return w
541
+
542
+ @staticmethod
543
+ def _q4_parts(mod):
544
+ """(packed [N, K/2] uint8, absmax [N*K/64] fp32) for a bitsandbytes
545
+ nf4 layer whose weights stay packed, else None. Double-quantized
546
+ absmax is materialized to fp32 so the kernel needs one scale array."""
547
+ w = getattr(mod, "weight", None)
548
+ qs = getattr(w, "quant_state", None)
549
+ if qs is None or getattr(qs, "quant_type", None) != "nf4":
550
+ return None
551
+ import bitsandbytes as bnb
552
+ n, k = int(qs.shape[0]), int(qs.shape[1])
553
+ if k % 64:
554
+ return None # kernel assumes 64 | K for per-block absmax
555
+ packed = w.data.reshape(n, k // 2).contiguous()
556
+ if getattr(qs, "nested", False):
557
+ absmax = bnb.functional.dequantize_blockwise(
558
+ qs.absmax, qs.state2) + qs.offset
559
+ else:
560
+ absmax = qs.absmax
561
+ return packed, absmax.float().reshape(-1).contiguous()
562
+
563
+ @classmethod
564
+ def _proj(cls, mods):
565
+ """A projection, concatenated across `mods` (q/k/v or gate/up). Stays
566
+ nf4-packed when every part is nf4, else a dense bf16 tensor."""
567
+ parts = [cls._q4_parts(m) for m in mods]
568
+ if all(p is not None for p in parts):
569
+ packed = torch.cat([p[0] for p in parts], 0)
570
+ absmax = torch.cat([p[1] for p in parts], 0)
571
+ return {"q4": True, "packed": packed, "absmax": absmax,
572
+ "N": packed.shape[0], "K": packed.shape[1] * 2}
573
+ return torch.cat([cls._dense_weight(m) for m in mods], 0)
574
+
575
+ @classmethod
576
+ def from_pretrained(cls, model, device="cuda", max_seq: int = 4096,
577
+ max_gen: int = 4096):
578
+ """Build from a transformers model (object or repo id). A quantized
579
+ checkpoint (bitsandbytes 4-bit/8-bit) is accepted directly; its
580
+ weights are dequantized to bf16 at load."""
581
+ if isinstance(model, str):
582
+ from transformers import AutoModelForCausalLM
583
+ model = AutoModelForCausalLM.from_pretrained(
584
+ model, torch_dtype=torch.bfloat16)
585
+ if "gemma4" in getattr(model.config, "model_type", ""):
586
+ return cls._from_gemma4(model, device, max_seq, max_gen)
587
+ hf = model.config
588
+ dec = model.model
589
+ attn0 = dec.layers[0].self_attn
590
+ qk_norm = hasattr(attn0, "q_norm") and attn0.q_norm is not None
591
+ if getattr(attn0.q_proj, "bias", None) is not None:
592
+ raise ValueError("attention biases are not supported")
593
+
594
+ # rope config across transformers API generations: v5 keeps theta and
595
+ # type in a rope_parameters dict; v4 used a rope_theta attribute and
596
+ # an optional rope_scaling dict.
597
+ rp = getattr(hf, "rope_parameters", None)
598
+ if not isinstance(rp, dict):
599
+ rs = getattr(hf, "rope_scaling", None)
600
+ rp = rs if isinstance(rs, dict) else {}
601
+ theta = rp.get("rope_theta")
602
+ if theta is None:
603
+ theta = getattr(hf, "rope_theta", 10000.0)
604
+ rope_type = rp.get("rope_type", rp.get("type", "default"))
605
+ if rope_type not in ("default", "llama3"):
606
+ raise ValueError(f"rope type {rope_type!r} is not supported")
607
+ # take the frequency table transformers computed (scaling included)
608
+ rot = dec.rotary_emb
609
+ att_scale = float(getattr(rot, "attention_scaling", 1.0))
610
+ if att_scale != 1.0:
611
+ raise ValueError("rope attention_scaling != 1 is not supported")
612
+ inv_freq = rot.inv_freq.detach().float()
613
+
614
+ pj = cls._proj
615
+ layers = []
616
+ for lyr in dec.layers:
617
+ a, m = lyr.self_attn, lyr.mlp
618
+ layers.append({
619
+ "wqkv": pj([a.q_proj, a.k_proj, a.v_proj]),
620
+ "wo": pj([a.o_proj]),
621
+ "wgu": pj([m.gate_proj, m.up_proj]),
622
+ "wdown": pj([m.down_proj]),
623
+ "ln1": lyr.input_layernorm.weight,
624
+ "ln2": lyr.post_attention_layernorm.weight,
625
+ "qn": a.q_norm.weight if qk_norm else None,
626
+ "kn": a.k_norm.weight if qk_norm else None,
627
+ })
628
+ weights = {"embed": dec.embed_tokens.weight, "norm": dec.norm.weight,
629
+ "lm_head": pj([model.lm_head]), "layers": layers,
630
+ "inv_freq": inv_freq}
631
+ config = {
632
+ "num_hidden_layers": hf.num_hidden_layers,
633
+ "hidden_size": hf.hidden_size,
634
+ "num_attention_heads": hf.num_attention_heads,
635
+ "num_key_value_heads": getattr(hf, "num_key_value_heads", None),
636
+ "head_dim": getattr(hf, "head_dim", None),
637
+ "intermediate_size": hf.intermediate_size,
638
+ "vocab_size": hf.vocab_size,
639
+ "rope_theta": float(theta),
640
+ "rms_norm_eps": hf.rms_norm_eps,
641
+ "qk_norm": qk_norm,
642
+ }
643
+ return cls(weights, config, device=device, max_seq=max_seq,
644
+ max_gen=max_gen)
645
+
646
+ # ------------------------------------------------------------------
647
+ @classmethod
648
+ def _from_gemma4(cls, model, device, max_seq, max_gen):
649
+ """gemma-4-E2B-it program: gemma norms (fp32, raw weight),
650
+ gelu-tanh gating, post-sublayer norm phases, per-layer-input
651
+ pathway, sliding windows, shared KV, dual head dims, softcapped
652
+ head. Scope is the E2B text model only."""
653
+ self = cls.__new__(cls)
654
+ cfg = model.config.text_config if hasattr(model.config, "text_config") \
655
+ else model.config
656
+ dec = model.model.language_model if hasattr(model.model, "language_model") \
657
+ else model.model
658
+ assert cfg.model_type == "gemma4_text", cfg.model_type
659
+ assert not cfg.enable_moe_block
660
+
661
+ self.L = int(cfg.num_hidden_layers)
662
+ self.hidden = int(cfg.hidden_size)
663
+ self.Hq = int(cfg.num_attention_heads)
664
+ self.V = int(cfg.vocab_size)
665
+ self.eps = float(cfg.rms_norm_eps)
666
+ self.qk_norm = True
667
+ self.max_seq = int(max_seq)
668
+ self.max_gen = int(max_gen)
669
+ self.device = torch.device(device)
670
+ self.tiled = False
671
+ win = int(cfg.sliding_window)
672
+ cap = float(cfg.final_logit_softcapping)
673
+ PL = int(cfg.hidden_size_per_layer_input)
674
+ dt = torch.bfloat16
675
+ dev = self.device
676
+ self._keep = []
677
+
678
+ def pack(t):
679
+ t = t.detach().to(device=dev, dtype=dt).contiguous()
680
+ self._keep.append(t)
681
+ return t
682
+
683
+ def packf(t):
684
+ t = t.detach().to(device=dev, dtype=torch.float32).contiguous()
685
+ self._keep.append(t)
686
+ return t
687
+
688
+ embed = pack(dec.embed_tokens.weight)
689
+ ple_tab = pack(dec.embed_tokens_per_layer.weight)
690
+ plm_w = pack(dec.per_layer_model_projection.weight)
691
+ pln_w = pack(dec.per_layer_projection_norm.weight)
692
+ fnorm = pack(dec.norm.weight)
693
+ rot = dec.rotary_emb
694
+ invf = {lt: packf(getattr(rot, f"{lt}_inv_freq").float())
695
+ for lt in set(cfg.layer_types)}
696
+ ascale = {lt: float(getattr(rot, f"{lt}_attention_scaling"))
697
+ for lt in set(cfg.layer_types)}
698
+
699
+ layers = []
700
+ for li, lyr in enumerate(dec.layers):
701
+ a = lyr.self_attn
702
+ D_l = int(a.head_dim)
703
+ shared = bool(a.is_kv_shared_layer)
704
+ ent = {
705
+ "D": D_l, "shared": shared,
706
+ "lscale": float(lyr.layer_scalar.float().item()),
707
+ "type": cfg.layer_types[li],
708
+ "I": int(lyr.mlp.intermediate_size),
709
+ "qn": pack(a.q_norm.weight),
710
+ "o": pack(a.o_proj.weight),
711
+ "ln_in": pack(lyr.input_layernorm.weight),
712
+ "ln_pa": pack(lyr.post_attention_layernorm.weight),
713
+ "ln_pf": pack(lyr.pre_feedforward_layernorm.weight),
714
+ "ln_ff": pack(lyr.post_feedforward_layernorm.weight),
715
+ "ln_pl": pack(lyr.post_per_layer_input_norm.weight),
716
+ "plig": pack(lyr.per_layer_input_gate.weight),
717
+ "plpr": pack(lyr.per_layer_projection.weight),
718
+ "wgu": pack(torch.cat([lyr.mlp.gate_proj.weight,
719
+ lyr.mlp.up_proj.weight], 0)),
720
+ "wdn": pack(lyr.mlp.down_proj.weight),
721
+ }
722
+ if shared:
723
+ ent["wqkv"] = pack(a.q_proj.weight)
724
+ ent["kn"] = None
725
+ else:
726
+ ent["wqkv"] = pack(torch.cat(
727
+ [a.q_proj.weight, a.k_proj.weight, a.v_proj.weight], 0))
728
+ ent["kn"] = pack(a.k_norm.weight)
729
+ ent["kc"] = torch.zeros(self.max_seq, D_l, dtype=dt,
730
+ device=dev)
731
+ ent["vc"] = torch.zeros(self.max_seq, D_l, dtype=dt,
732
+ device=dev)
733
+ self._keep += [ent["kc"], ent["vc"]]
734
+ layers.append(ent)
735
+ # shared layers read the last non-shared layer of their type
736
+ src = {}
737
+ for li, ent in enumerate(layers):
738
+ if not ent["shared"]:
739
+ src[ent["type"]] = ent
740
+ for ent in layers:
741
+ if ent["shared"]:
742
+ ent["kc"] = src[ent["type"]]["kc"]
743
+ ent["vc"] = src[ent["type"]]["vc"]
744
+
745
+ maxD = max(e["D"] for e in layers)
746
+ maxI = max(e["I"] for e in layers)
747
+ self._maxk = max(self.hidden, self.Hq * maxD, maxI)
748
+ self._chunk_m = 1
749
+ self._chunk = 128
750
+ self._maxch = (self.max_seq + self._chunk - 1) // self._chunk
751
+ self._hidden = torch.empty(self.hidden, dtype=dt, device=dev)
752
+ self._tmp = torch.empty(self.hidden, dtype=dt, device=dev)
753
+ self._qkv = torch.empty((self.Hq + 2) * maxD, dtype=dt, device=dev)
754
+ self._gu = torch.empty(2 * maxI, dtype=dt, device=dev)
755
+ self._t256 = torch.empty(PL, dtype=dt, device=dev)
756
+ self._ctxraw = torch.empty(self.L * PL, dtype=dt, device=dev)
757
+ self._pletok = torch.empty(self.L * PL, dtype=dt, device=dev)
758
+ self._ple = torch.empty(self.L * PL, dtype=dt, device=dev)
759
+ self._logits = torch.empty(self.V, dtype=torch.float32, device=dev)
760
+ self._partials = torch.empty(
761
+ self.Hq * self._maxch * (maxD + 2), dtype=torch.float32,
762
+ device=dev)
763
+ self._token = torch.zeros(1, dtype=torch.int32, device=dev)
764
+ self._prompt_in = torch.zeros(self.max_seq, dtype=torch.int32,
765
+ device=dev)
766
+ self._tokens_out = torch.zeros(self.max_gen, dtype=torch.int32,
767
+ device=dev)
768
+ self._bar = torch.zeros(34, dtype=torch.int32, device=dev)
769
+ probe = torch.zeros(1, 16, dtype=torch.int64, device=dev)
770
+ self._nblocks = int(ops.mak_num_blocks(probe, self._maxk))
771
+ self._parts = torch.zeros(self._nblocks, dtype=torch.int64,
772
+ device=dev)
773
+ try:
774
+ self._bw_per_sm = float(ops.mak_bw_per_sm(probe))
775
+ except (AttributeError, RuntimeError):
776
+ self._bw_per_sm = 0.0
777
+ self._invf = invf[layers[0]["type"]]
778
+ self.Hkv, self.D, self.I = 1, maxD, maxI
779
+ self.theta = 0.0
780
+
781
+ emb_scale = float(torch.tensor(self.hidden ** 0.5,
782
+ dtype=torch.float32).to(dt))
783
+ ple_scale = float(torch.tensor(PL ** 0.5,
784
+ dtype=torch.float32).to(dt))
785
+ proj_scale = float(torch.tensor(self.hidden ** -0.5,
786
+ dtype=torch.float32))
787
+ hid = self._hidden.data_ptr()
788
+ tmp = self._tmp.data_ptr()
789
+ rows, names = [], []
790
+ rows.append(_row(_OP_EMBED, p1=ple_tab.data_ptr(),
791
+ p2=embed.data_ptr(), p3=hid,
792
+ p4=self._parts.data_ptr(),
793
+ p5=self._prompt_in.data_ptr(),
794
+ p6=self._token.data_ptr(),
795
+ n=self._tokens_out.data_ptr(), k=self.hidden,
796
+ it=self._pletok.data_ptr(),
797
+ hkv=self.L * PL, f0=emb_scale,
798
+ i0=_fbits(ple_scale)))
799
+ names.append("embed")
800
+ rows.append(_row(_OP_GEMV, p1=hid, p2=plm_w.data_ptr(),
801
+ p3=self._ctxraw.data_ptr(), n=self.L * PL,
802
+ k=self.hidden, it=_IT_NONE, epi=_EP_STORE))
803
+ names.append("plm")
804
+ rows.append(_row(8, p1=self._ctxraw.data_ptr(),
805
+ p2=pln_w.data_ptr(), p3=self._ple.data_ptr(),
806
+ p4=self._pletok.data_ptr(), n=self.L, k=PL,
807
+ f0=self.eps, i0=_fbits(proj_scale)))
808
+ names.append("plemix")
809
+
810
+ for li, e in enumerate(layers):
811
+ D_l, qdim = e["D"], self.Hq * e["D"]
812
+ lt = e["type"]
813
+ w_l = win if lt == "sliding_attention" else 0
814
+ asb = _fbits(ascale[lt]) if ascale[lt] != 1.0 else 0
815
+ flags = 1 | 2 | (0 if e["shared"] else 4) | \
816
+ (8 if e["shared"] else 0)
817
+ n_qkv = qdim if e["shared"] else qdim + 2 * D_l
818
+ rows.append(_row(_OP_GEMV, p1=hid, p2=e["wqkv"].data_ptr(),
819
+ p3=self._qkv.data_ptr(),
820
+ p4=e["ln_in"].data_ptr(), n=n_qkv,
821
+ k=self.hidden, it=_IT_RMSNORM_G,
822
+ epi=_EP_STORE, f0=self.eps))
823
+ names.append(f"L{li}.qkv")
824
+ rows.append(_row(_OP_ATTN, p1=self._qkv.data_ptr(),
825
+ p2=e["kc"].data_ptr(), p3=e["vc"].data_ptr(),
826
+ p4=self._partials.data_ptr(),
827
+ p5=e["qn"].data_ptr(),
828
+ p6=e["kn"].data_ptr() if e["kn"] is not None
829
+ else 0,
830
+ n=self.max_seq,
831
+ k=flags | (w_l << 16), it=_fbits(self.eps),
832
+ epi=invf[lt].data_ptr(), hq=self.Hq,
833
+ hkv=1 | (self._chunk << 16), d=D_l, f0=1.0,
834
+ i0=self._maxch | (asb << 32)))
835
+ names.append(f"L{li}.attn")
836
+ rows.append(_row(_OP_GEMV, p1=self._partials.data_ptr(),
837
+ p2=e["o"].data_ptr(), p3=tmp,
838
+ n=self.hidden, k=qdim, it=_IT_ATTNFIN,
839
+ epi=_EP_STORE, hq=self.Hq,
840
+ hkv=self._chunk << 16, d=D_l,
841
+ i0=self._maxch | (w_l << 16)))
842
+ names.append(f"L{li}.o")
843
+ rows.append(_row(7, p1=tmp, p2=e["ln_pa"].data_ptr(), p3=hid,
844
+ n=self.hidden, f0=self.eps, i0=_fbits(1.0)))
845
+ names.append(f"L{li}.nr_attn")
846
+ rows.append(_row(_OP_GEMV, p1=hid, p2=e["wgu"].data_ptr(),
847
+ p3=self._gu.data_ptr(),
848
+ p4=e["ln_pf"].data_ptr(), n=2 * e["I"],
849
+ k=self.hidden, it=_IT_RMSNORM_G,
850
+ epi=_EP_STORE, f0=self.eps))
851
+ names.append(f"L{li}.gateup")
852
+ rows.append(_row(_OP_GEMV, p1=self._gu.data_ptr(),
853
+ p2=e["wdn"].data_ptr(), p3=tmp,
854
+ n=self.hidden, k=e["I"], it=_IT_GELU_GLU,
855
+ epi=_EP_STORE))
856
+ names.append(f"L{li}.down")
857
+ rows.append(_row(7, p1=tmp, p2=e["ln_ff"].data_ptr(), p3=hid,
858
+ n=self.hidden, f0=self.eps, i0=_fbits(1.0)))
859
+ names.append(f"L{li}.nr_ffw")
860
+ rows.append(_row(_OP_GEMV, p1=hid, p2=e["plig"].data_ptr(),
861
+ p3=self._t256.data_ptr(),
862
+ p6=self._ple.data_ptr() + li * PL * 2,
863
+ n=PL, k=self.hidden, it=_IT_NONE,
864
+ epi=_EP_GELU_PLE))
865
+ names.append(f"L{li}.plig")
866
+ rows.append(_row(_OP_GEMV, p1=self._t256.data_ptr(),
867
+ p2=e["plpr"].data_ptr(), p3=tmp,
868
+ n=self.hidden, k=PL, it=_IT_NONE,
869
+ epi=_EP_STORE))
870
+ names.append(f"L{li}.plproj")
871
+ rows.append(_row(7, p1=tmp, p2=e["ln_pl"].data_ptr(), p3=hid,
872
+ n=self.hidden, f0=self.eps,
873
+ i0=_fbits(e["lscale"])))
874
+ names.append(f"L{li}.nr_ple")
875
+
876
+ rows.append(_row(_OP_GEMV, p1=hid, p2=embed.data_ptr(),
877
+ p3=self._logits.data_ptr(),
878
+ p4=fnorm.data_ptr(),
879
+ p6=self._parts.data_ptr(), n=self.V,
880
+ k=self.hidden, it=_IT_RMSNORM_G,
881
+ epi=_EP_F32_AMAX_CAP, hkv=1, f0=self.eps,
882
+ i0=_fbits(cap)))
883
+ names.append("lm_head")
884
+ rows.append(_row(_OP_ARGMAX_FIN, p1=self._parts.data_ptr(),
885
+ p3=self._token.data_ptr(),
886
+ p4=self._tokens_out.data_ptr()))
887
+ names.append("argmax.fin")
888
+ self._prog = torch.tensor(rows, dtype=torch.int64, device=dev)
889
+ self.phase_names = names
890
+ self._gemma = True
891
+ self._batch = 0
892
+ return self
893
+
894
+ # ------------------------------------------------------------------
895
+ def decode_step(self, token: int, pos: int, phased: bool = False):
896
+ """One decode step; returns the live fp32 logits buffer [V]."""
897
+ if pos >= self.max_seq:
898
+ raise ValueError("pos exceeds max_seq")
899
+ self._token.fill_(int(token))
900
+ if phased:
901
+ ops.mak_run_phased(self._prog, pos, self.max_gen - 1, False,
902
+ self._maxk)
903
+ else:
904
+ ops.mak_run(self._prog, self._bar, pos, self.max_gen - 1,
905
+ self._maxk)
906
+ return self._logits[:self.V]
907
+
908
+ def decode_step_timed(self, token: int, pos: int):
909
+ """One phase-per-launch step; returns (logits, per-phase ms)."""
910
+ self._token.fill_(int(token))
911
+ ms = ops.mak_run_phased(self._prog, pos, self.max_gen - 1, True,
912
+ self._maxk)
913
+ return self._logits[:self.V], ms
914
+
915
+ def prefill(self, ids: Sequence[int], phased: bool = False):
916
+ """Consume a prompt; returns the last position's logits. Runs in
917
+ chunks of up to chunk_m tokens with weight reads amortized across
918
+ the chunk (bitwise identical to token-by-token consumption)."""
919
+ P = len(ids)
920
+ if phased:
921
+ for i, t in enumerate(ids):
922
+ self.decode_step(int(t), i, phased=True)
923
+ return self._logits
924
+ self._prompt_in[:P].copy_(torch.tensor(ids, dtype=torch.int32))
925
+ ops.mak_run_seq(self._prog, self._bar, 0, P, 1 - P, P, self._maxk,
926
+ self._chunk_m)
927
+ return self._logits[:self.V]
928
+
929
+ def generate(self, prompt_ids: Sequence[int], max_new: int,
930
+ single_launch: bool = True) -> List[int]:
931
+ """Greedy generation. With single_launch (default) the entire call,
932
+ prompt consumption included, is one kernel launch: the in-kernel step
933
+ loop reads prompt tokens from a staged device buffer, then feeds each
934
+ step's argmax to the next step's embedding read. With
935
+ single_launch=False the same computation runs as one launch per
936
+ token; the two paths produce identical tokens.
937
+ """
938
+ P = len(prompt_ids)
939
+ if max_new < 1 or max_new > self.max_gen:
940
+ raise ValueError("max_new out of range")
941
+ if P < 1 or P + max_new > self.max_seq:
942
+ raise ValueError("sequence exceeds max_seq")
943
+ if single_launch:
944
+ self._prompt_in[:P].copy_(
945
+ torch.tensor(prompt_ids, dtype=torch.int32))
946
+ ops.mak_run_seq(self._prog, self._bar, 0, P + max_new - 1,
947
+ 1 - P, P, self._maxk, self._chunk_m)
948
+ return self._tokens_out[:max_new].cpu().tolist()
949
+ pos = 0
950
+ for t in prompt_ids[:-1]:
951
+ self._token.fill_(int(t))
952
+ ops.mak_run(self._prog, self._bar, pos, self.max_gen - 1,
953
+ self._maxk)
954
+ pos += 1
955
+ self._token.fill_(int(prompt_ids[-1]))
956
+ ops.mak_run(self._prog, self._bar, pos, 0, self._maxk)
957
+ pos += 1
958
+ if max_new > 1:
959
+ ops.mak_run_steps(self._prog, self._bar, pos, max_new - 1, 1,
960
+ self._maxk)
961
+ return self._tokens_out[:max_new].cpu().tolist()
962
+
963
+
964
+
build/torch213-cxx11-cu132-x86_64-linux/_model_as_a_kernel_cuda_e33875a.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c0e8576073d666697bab258e0cc2ff5a784464ee621c1f5ff29403e2c6d987ca
3
+ size 6374912
build/torch213-cxx11-cu132-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _model_as_a_kernel_cuda_e33875a
3
+ ops = torch.ops._model_as_a_kernel_cuda_e33875a
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_model_as_a_kernel_cuda_e33875a::{op_name}"
build/torch213-cxx11-cu132-x86_64-linux/metadata.json ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "model-as-a-kernel",
3
+ "id": "_model_as_a_kernel_cuda_e33875a",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "python-depends": [],
7
+ "backend": {
8
+ "type": "cuda",
9
+ "archs": [
10
+ "10.0",
11
+ "12.0",
12
+ "8.0",
13
+ "8.6",
14
+ "8.9",
15
+ "9.0"
16
+ ]
17
+ },
18
+ "digest": {
19
+ "algorithm": "sha256",
20
+ "files": {
21
+ "__init__.py": "/YX5/zzAOyJacQ7H+j8zaq+xjWyy8w6r0j+ugE1EUs8=",
22
+ "_model_as_a_kernel_cuda_e33875a.abi3.so": "wOhXYHPWZml7qyWODML/WnhEZO5iHB9f8pQD4sbZh8o=",
23
+ "_ops.py": "Pqj4WKtwPyeDG9grQnzLget4g1jsGUdR7ZMa44e4rhs=",
24
+ "model_as_a_kernel/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
25
+ }
26
+ },
27
+ "provenance": {
28
+ "kernel-builder": {
29
+ "version": "0.17.0-dev0",
30
+ "sha": "05f1ed487f599a320311aa26076ec0ad290c3f68",
31
+ "dirty": false
32
+ },
33
+ "kernel": {
34
+ "sha": "e33875ae4eba42fd136a89c388e806281cd4759c",
35
+ "dirty": false
36
+ }
37
+ }
38
+ }
build/torch213-cxx11-cu132-x86_64-linux/model_as_a_kernel/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import importlib.util
3
+ import sys
4
+ from pathlib import Path
5
+ from types import ModuleType
6
+
7
+
8
+ def _import_from_path(file_path: Path) -> ModuleType:
9
+ # We cannot use the module name as-is, after adding it to `sys.modules`,
10
+ # it would also be used for other imports. So, we make a module name that
11
+ # depends on the path for it to be unique using the hex-encoded hash of
12
+ # the path.
13
+ path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
+ module_name = path_hash
15
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
16
+ if spec is None:
17
+ raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
+ module = importlib.util.module_from_spec(spec)
19
+ if module is None:
20
+ raise ImportError(f"Cannot load module {module_name} from spec")
21
+ sys.modules[module_name] = module
22
+ spec.loader.exec_module(module) # type: ignore
23
+ return module
24
+
25
+
26
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))