Download torch-ext/model_as_a_kernel/__init__.py from phanerozoic/model-as-a-kernel: direct link, hf CLI and curl.
- Browser
- Download file 47.1 kB
-
https://huggingface.co/kernels/phanerozoic/model-as-a-kernel/resolve/v1/torch-ext/model_as_a_kernel/__init__.py
- Command line
-
hf download hf://phanerozoic/model-as-a-kernel@v1/torch-ext/model_as_a_kernel/__init__.py
-
curl -L -o __init__.py https://huggingface.co/kernels/phanerozoic/model-as-a-kernel/resolve/v1/torch-ext/model_as_a_kernel/__init__.py
47.1 kB
| """model-as-a-kernel: an entire llama-family decode step as one kernel launch. | |
| A phase interpreter runs the whole forward pass (embedding, every layer's | |
| norms, QKV, rope, attention over the KV cache, SwiGLU MLP, the LM head, and | |
| the greedy argmax) inside a single persistent kernel, with a software grid | |
| barrier between phases. The in-kernel step loop makes an entire greedy | |
| generation, prompt consumption included, one kernel launch: each step's | |
| argmax feeds the next step's embedding read without leaving the device. | |
| """ | |
| import os | |
| import struct | |
| from typing import List, Optional, Sequence | |
| import torch | |
| try: | |
| from ._ops import ops | |
| except ImportError: # local development build (torch.utils.cpp_extension) | |
| class _LocalOps: | |
| def __getattr__(self, name): | |
| return getattr(torch.ops.mak_ext, name) | |
| ops = _LocalOps() | |
| __all__ = ["MegaModel"] | |
| _OP_EMBED, _OP_GEMV, _OP_QKV_POST, _OP_ATTN = 0, 1, 2, 3 | |
| _OP_ARGMAX_PART, _OP_ARGMAX_FIN = 4, 5 | |
| _OP_KV_APPEND, _OP_NORMRES, _OP_PLEMIX = 6, 7, 8 | |
| _OP_GEMV_PLAIN, _OP_NORMB, _OP_GLUB, _OP_ATTNFINB = 9, 10, 11, 12 | |
| _OP_GEMV_Q4 = 13 | |
| _IT_NONE, _IT_RMSNORM, _IT_SWIGLU, _IT_ATTNFIN = 0, 1, 2, 3 | |
| _IT_RMSNORM_G, _IT_GELU_GLU = 4, 5 | |
| _EP_GELU_PLE, _EP_F32_AMAX_CAP = 5, 6 | |
| _ITF_REDUCE, _ITF_WRITEBACK = 8, 16 | |
| _EP_STORE, _EP_RESID, _EP_F32, _EP_F32_AMAX, _EP_PARTIAL = 0, 1, 2, 3, 4 | |
| _NSLICE = 4 | |
| # Widest [B][K] input panel (bf16 elements) a batched projection stages in | |
| # shared before it spills to the global-scratch path. 16384 (32 KB) keeps | |
| # two blocks resident per SM; the persistent kernel shares one occupancy | |
| # across all phases, so a wider panel would drop every phase to one block | |
| # per SM, which measures slower than reading the input from L2. | |
| _STAGE_BUDGET = 16384 | |
| def _fbits(f: float) -> int: | |
| return struct.unpack("<i", struct.pack("<f", float(f)))[0] | |
| def _row(op, p1=0, p2=0, p3=0, p4=0, p5=0, p6=0, n=0, k=0, it=0, epi=0, | |
| hq=0, hkv=0, d=0, f0=0.0, i0=0): | |
| return [int(op), int(p1), int(p2), int(p3), int(p4), int(p5), int(p6), | |
| int(n), int(k), int(it), int(epi), int(hq), int(hkv), int(d), | |
| _fbits(f0), int(i0)] | |
| class MegaModel: | |
| """A packed llama-family decoder whose decode step is one kernel launch. | |
| Supported architecture: RMSNorm decoder blocks with rotary attention | |
| (GQA, optional Qwen3-style per-head qk RMSNorm), SwiGLU MLP, no attention | |
| or MLP biases, plain (unscaled) rope, batch size 1. This covers SmolLM2, | |
| TinyLlama, Qwen3 dense, Llama-class checkpoints without rope scaling, | |
| and similar models. | |
| """ | |
| def __init__(self, weights: dict, config: dict, device="cuda", | |
| max_seq: int = 4096, max_gen: int = 4096): | |
| cfg = dict(config) | |
| self.L = int(cfg["num_hidden_layers"]) | |
| self.hidden = int(cfg["hidden_size"]) | |
| self.Hq = int(cfg["num_attention_heads"]) | |
| self.Hkv = int(cfg.get("num_key_value_heads") or self.Hq) | |
| self.D = int(cfg.get("head_dim") or self.hidden // self.Hq) | |
| self.I = int(cfg["intermediate_size"]) | |
| self.V = int(cfg["vocab_size"]) | |
| self.theta = float(cfg.get("rope_theta", 10000.0)) | |
| self.eps = float(cfg.get("rms_norm_eps", 1e-6)) | |
| self.qk_norm = bool(cfg.get("qk_norm", False)) | |
| self.max_seq = int(max_seq) | |
| self.max_gen = int(max_gen) | |
| self.device = torch.device(device) | |
| if self.D % 2 or self.D > 256: | |
| raise ValueError("head_dim must be even and <= 256") | |
| if self.hidden % 8 or self.I % 8 or (self.Hq * self.D) % 8: | |
| raise ValueError( | |
| "hidden, intermediate, and Hq*head_dim must be multiples of 8") | |
| if self.Hq % self.Hkv: | |
| raise ValueError("num_attention_heads must be divisible by num_key_value_heads") | |
| dt = torch.bfloat16 | |
| dev = self.device | |
| def pack(t): | |
| t = t.detach().to(device=dev, dtype=dt).contiguous() | |
| self._keep.append(t) | |
| return t | |
| # Row-tile-of-8 weight layout [N/8][K/8][8][8] (MAK_TILED=1): the | |
| # eight warps of a block read one contiguous 4KB stream per step | |
| # instead of eight 4KB-strided pieces. Measured slower on every | |
| # card (H200 +15 percent, RTX PRO +9, Ada +14 — per-warp stream | |
| # locality beats per-block contiguity on all of them), so the | |
| # default is the plain layout; the flag rides in the program and | |
| # both layouts compute identical bits. | |
| env_t = os.environ.get("MAK_TILED", "").strip() | |
| self.tiled = env_t == "1" | |
| def pack_tiled(t): | |
| t = t.detach().to(device=dev, dtype=dt).contiguous() | |
| if not self.tiled: | |
| self._keep.append(t) | |
| return t | |
| n, k = t.shape | |
| assert k % 8 == 0 | |
| n8 = (n + 7) // 8 * 8 | |
| if n8 != n: | |
| t = torch.cat( | |
| [t, torch.zeros(n8 - n, k, dtype=dt, device=dev)], 0) | |
| t = t.view(n8 // 8, 8, k // 8, 8).permute(0, 2, 1, 3).contiguous() | |
| self._keep.append(t) | |
| return t | |
| self._keep: List[torch.Tensor] = [] | |
| embed = pack(weights["embed"]) | |
| final_norm = pack(weights["norm"]) | |
| lm_head = weights["lm_head"] # packed below (dense-tiled or nf4) | |
| inv_freq = weights.get("inv_freq") | |
| if inv_freq is None: | |
| d_idx = torch.arange(0, self.D, 2, dtype=torch.float32) | |
| inv_freq = 1.0 / (self.theta ** (d_idx / self.D)) | |
| inv_freq = inv_freq.detach().to(device=dev, | |
| dtype=torch.float32).contiguous() | |
| if inv_freq.numel() != self.D // 2: | |
| raise ValueError("inv_freq length must be head_dim / 2") | |
| self._keep.append(inv_freq) | |
| self._invf = inv_freq | |
| qdim, kvdim = self.Hq * self.D, self.Hkv * self.D | |
| def _is_q4(w): | |
| return isinstance(w, dict) and w.get("q4") | |
| def pack_w(w): | |
| # nf4-packed weights move to the device intact; dense weights take | |
| # the usual (optionally row-tiled) bf16 packing. | |
| if _is_q4(w): | |
| pk = w["packed"].to(dev).contiguous() | |
| am = w["absmax"].to(device=dev, dtype=torch.float32).contiguous() | |
| self._keep.append(pk) | |
| self._keep.append(am) | |
| return {"q4": True, "packed": pk, "absmax": am, | |
| "N": int(w["N"]), "K": int(w["K"])} | |
| return pack_tiled(w) | |
| def _wshape(w, expected): | |
| got = (int(w["N"]), int(w["K"])) if _is_q4(w) else tuple(w.shape) | |
| assert got == expected, (got, expected) | |
| self._has_q4 = False | |
| self._layers = [] | |
| for lw in weights["layers"]: | |
| _wshape(lw["wqkv"], (qdim + 2 * kvdim, self.hidden)) | |
| _wshape(lw["wo"], (self.hidden, qdim)) | |
| _wshape(lw["wgu"], (2 * self.I, self.hidden)) | |
| _wshape(lw["wdown"], (self.hidden, self.I)) | |
| layer = { | |
| "wqkv": pack_w(lw["wqkv"]), "wo": pack_w(lw["wo"]), | |
| "wgu": pack_w(lw["wgu"]), "wdown": pack_w(lw["wdown"]), | |
| "ln1": pack(lw["ln1"]), "ln2": pack(lw["ln2"]), | |
| "qn": pack(lw["qn"]) if self.qk_norm else None, | |
| "kn": pack(lw["kn"]) if self.qk_norm else None, | |
| } | |
| self._has_q4 |= any(_is_q4(layer[n]) for n in | |
| ("wqkv", "wo", "wgu", "wdown")) | |
| self._layers.append(layer) | |
| assert len(self._layers) == self.L | |
| assert embed.shape == (self.V, self.hidden) | |
| lm_head = pack_w(lm_head) | |
| self._has_q4 |= _is_q4(lm_head) | |
| if not _is_q4(lm_head): | |
| assert lm_head.numel() >= self.V * self.hidden # row-tiled, padded | |
| self._w_embed, self._w_fnorm, self._w_lmhead = (embed, final_norm, | |
| lm_head) | |
| self._qdim, self._kvdim = qdim, kvdim | |
| # Working buffers. Pointers to these are baked into the program, so | |
| # they (like the packed weights) live for the model's lifetime. | |
| self._maxk = max(self.hidden, qdim, self.I) | |
| # prefill chunk size, bounded by the staging shared-memory budget | |
| # (16K bf16 elements; the cp.async weight ring rides alongside) | |
| self._chunk_m = max(1, min(8, 16384 // self._maxk)) | |
| cm = 8 # buffers sized for the maximum chunk | |
| self._hidden = torch.empty(cm * self.hidden, dtype=dt, device=dev) | |
| self._qkv = torch.empty(cm * (qdim + 2 * kvdim), dtype=dt, device=dev) | |
| self._gu = torch.empty(cm * 2 * self.I, dtype=dt, device=dev) | |
| self._logits = torch.empty(self.V, dtype=torch.float32, device=dev) | |
| # attention chunk length (kernel maximum 128); shorter chunks give | |
| # the attention phase more grid parallelism at the cost of more | |
| # softmax partials to finalize | |
| env_ch = os.environ.get("MAK_CHUNK", "").strip() | |
| self._chunk = int(env_ch) if env_ch in ("32", "64", "128") else 128 | |
| self._maxch = (self.max_seq + self._chunk - 1) // self._chunk | |
| self._partials = torch.empty( | |
| cm * self.Hq * self._maxch * (self.D + 2), dtype=torch.float32, | |
| device=dev) | |
| self._kcache = torch.zeros(self.L, self.Hkv, self.max_seq, self.D, | |
| dtype=dt, device=dev) | |
| self._vcache = torch.zeros(self.L, self.Hkv, self.max_seq, self.D, | |
| dtype=dt, device=dev) | |
| self._token = torch.zeros(1, dtype=torch.int32, device=dev) | |
| self._prompt_in = torch.zeros(self.max_seq, dtype=torch.int32, | |
| device=dev) | |
| self._tokens_out = torch.zeros(self.max_gen, dtype=torch.int32, | |
| device=dev) | |
| self._bar = torch.zeros(34, dtype=torch.int32, device=dev) | |
| probe = torch.zeros(1, 16, dtype=torch.int64, device=dev) | |
| self._nblocks = int(ops.mak_num_blocks(probe, self._maxk)) | |
| self._parts = torch.zeros(self._nblocks, dtype=torch.int64, device=dev) | |
| try: | |
| self._bw_per_sm = float(ops.mak_bw_per_sm(probe)) | |
| except (AttributeError, RuntimeError): | |
| self._bw_per_sm = 0.0 | |
| self._batch = 0 | |
| self._pos_b = None | |
| self._kv_bstride = 0 | |
| self._gemma = False | |
| # transformed-input scratch: the nf4 and wide-batch paths write the | |
| # dense bf16 input here (one row per active token) for the following | |
| # plain/quant GEMV; sized for a prefill chunk (up to 8 rows) | |
| self._xg = torch.empty(cm * self._maxk, dtype=dt, device=dev) | |
| self._build_programs() | |
| # ------------------------------------------------------------------ | |
| def _kvp(self, cache, li: int, b: int) -> int: | |
| t = cache[li] | |
| return (t[b] if t.dim() == 4 else t).data_ptr() | |
| def _build_rows(self, kv_slice: int = 0, batch: bool = False, | |
| big: bool = False): | |
| """Program rows for the llama-family path. kv_slice selects the | |
| batch slice the KV pointers address (per-sequence prefill); | |
| batch=True emits the batched-decode tail (per-row fp32 logits and | |
| explicit argmax phases) instead of the fused LM-head amax. In batch | |
| mode each projection stays fused (input staged in shared) when its | |
| [B][K] panel fits the budget; the projections whose K is too wide | |
| transform into a global scratch and read it with a plain GEMV.""" | |
| _tbit = (1 << 20) if self.tiled else 0 # GEMV weight layout flag | |
| qdim, kvdim = self._qdim, self._kvdim | |
| embed, lm_head = self._w_embed, self._w_lmhead | |
| final_norm = self._w_fnorm | |
| xg = self._xg.data_ptr() | |
| B = self._batch if batch else 1 | |
| rows, names = [], [] | |
| staged_elems = [0] # widest [B][K] panel any fused projection stages | |
| def fits(K): | |
| return B * K <= _STAGE_BUDGET | |
| def note(K): | |
| staged_elems[0] = max(staged_elems[0], B * K) | |
| def _q4(w): | |
| return isinstance(w, dict) and w.get("q4") | |
| def _wgemv(out, w, N, K, epi, resid): | |
| # the GEMV after a transform-to-scratch phase: nf4-dequant when | |
| # the weight is packed, otherwise a plain bf16 GEMV | |
| if _q4(w): | |
| rows.append(_row(_OP_GEMV_Q4, p1=xg, p2=w["packed"].data_ptr(), | |
| p3=out, p5=resid, p6=w["absmax"].data_ptr(), | |
| n=N, k=K, epi=epi)) | |
| else: | |
| rows.append(_row(_OP_GEMV_PLAIN, p1=xg, p2=w.data_ptr(), | |
| p3=out, p5=resid, n=N, k=K, it=_tbit, | |
| epi=epi)) | |
| def norm_gemv(name, gamma, w, out, N, K, epi, inp, resid=0, | |
| variant=_IT_RMSNORM): | |
| if _q4(w) or (big and not fits(K)): | |
| rows.append(_row(_OP_NORMB, p1=inp, p2=gamma, p3=xg, k=K, | |
| it=variant, f0=self.eps)) | |
| names.append(name + ".norm") | |
| _wgemv(out, w, N, K, epi, resid) | |
| names.append(name) | |
| else: | |
| note(K) | |
| rows.append(_row(_OP_GEMV, p1=inp, p2=w.data_ptr(), p3=out, | |
| p4=gamma, p5=resid, n=N, k=K, | |
| it=variant | _tbit, epi=epi, f0=self.eps)) | |
| names.append(name) | |
| def glu_gemv(name, gu, w, out, resid, variant=_IT_SWIGLU): | |
| if _q4(w) or (big and not fits(self.I)): | |
| rows.append(_row(_OP_GLUB, p1=gu, p3=xg, k=self.I, it=variant)) | |
| names.append(name + ".glu") | |
| _wgemv(out, w, self.hidden, self.I, _EP_RESID, resid) | |
| names.append(name) | |
| else: | |
| note(self.I) | |
| rows.append(_row(_OP_GEMV, p1=gu, p2=w.data_ptr(), p3=out, | |
| p5=resid, n=self.hidden, k=self.I, | |
| it=variant | _tbit, epi=_EP_RESID)) | |
| names.append(name) | |
| def attn_gemv(name, w, out, resid): | |
| if _q4(w) or (big and not fits(qdim)): | |
| rows.append(_row(_OP_ATTNFINB, p1=self._partials.data_ptr(), | |
| p3=xg, k=qdim, hq=self.Hq, | |
| hkv=self._chunk << 16, d=self.D, | |
| i0=self._maxch)) | |
| names.append(name + ".fin") | |
| _wgemv(out, w, self.hidden, qdim, _EP_RESID, resid) | |
| names.append(name) | |
| else: | |
| note(qdim) | |
| rows.append(_row(_OP_GEMV, p1=self._partials.data_ptr(), | |
| p2=w.data_ptr(), p3=out, p5=resid, | |
| n=self.hidden, k=qdim, | |
| it=_IT_ATTNFIN | _tbit, epi=_EP_RESID, | |
| hq=self.Hq, hkv=self._chunk << 16, d=self.D, | |
| i0=self._maxch)) | |
| names.append(name) | |
| rows.append(_row(_OP_EMBED, p2=embed.data_ptr(), | |
| p3=self._hidden.data_ptr(), | |
| p4=self._parts.data_ptr(), | |
| p5=self._prompt_in.data_ptr(), | |
| p6=self._token.data_ptr(), | |
| n=self._tokens_out.data_ptr(), k=self.hidden)) | |
| names.append("embed") | |
| scale = 1.0 / (self.D ** 0.5) | |
| hid = self._hidden.data_ptr() | |
| for li, lw in enumerate(self._layers): | |
| kc = self._kvp(self._kcache, li, kv_slice) | |
| vc = self._kvp(self._vcache, li, kv_slice) | |
| norm_gemv(f"L{li}.qkv", lw["ln1"].data_ptr(), | |
| lw["wqkv"], self._qkv.data_ptr(), | |
| qdim + 2 * kvdim, self.hidden, _EP_STORE, hid) | |
| rows.append(_row(6, p1=self._qkv.data_ptr(), p2=kc, p3=vc, | |
| p6=lw["kn"].data_ptr() if self.qk_norm else 0, | |
| n=self.max_seq, k=1 if self.qk_norm else 0, | |
| it=_fbits(self.eps), | |
| epi=self._invf.data_ptr(), | |
| hq=self.Hq, hkv=self.Hkv, d=self.D)) | |
| names.append(f"L{li}.kvappend") | |
| rows.append(_row(_OP_ATTN, p1=self._qkv.data_ptr(), p2=kc, | |
| p3=vc, p4=self._partials.data_ptr(), | |
| p5=lw["qn"].data_ptr() if self.qk_norm else 0, | |
| p6=lw["kn"].data_ptr() if self.qk_norm else 0, | |
| n=self.max_seq, k=1 if self.qk_norm else 0, | |
| it=_fbits(self.eps), | |
| epi=self._invf.data_ptr(), | |
| hq=self.Hq, | |
| hkv=self.Hkv | (self._chunk << 16), | |
| d=self.D, f0=scale, i0=self._maxch)) | |
| names.append(f"L{li}.attn") | |
| attn_gemv(f"L{li}.o", lw["wo"], hid, hid) | |
| norm_gemv(f"L{li}.gateup", lw["ln2"].data_ptr(), | |
| lw["wgu"], self._gu.data_ptr(), 2 * self.I, | |
| self.hidden, _EP_STORE, hid) | |
| glu_gemv(f"L{li}.down", self._gu.data_ptr(), lw["wdown"], hid, hid) | |
| if batch or _q4(lm_head): | |
| norm_gemv("lm_head", final_norm.data_ptr(), lm_head, | |
| self._logits.data_ptr(), self.V, self.hidden, _EP_F32, | |
| hid) | |
| rows.append(_row(_OP_ARGMAX_PART, p1=self._logits.data_ptr(), | |
| p3=self._parts.data_ptr(), n=self.V)) | |
| names.append("argmax.part") | |
| rows.append(_row(_OP_ARGMAX_FIN, p1=self._parts.data_ptr(), | |
| p3=self._token.data_ptr(), | |
| p4=self._tokens_out.data_ptr(), | |
| k=self.max_gen)) | |
| names.append("argmax.fin") | |
| else: | |
| rows.append(_row(_OP_GEMV, p1=hid, p2=lm_head.data_ptr(), | |
| p3=self._logits.data_ptr(), | |
| p4=final_norm.data_ptr(), | |
| p6=self._parts.data_ptr(), n=self.V, | |
| k=self.hidden, it=_IT_RMSNORM | _tbit, | |
| epi=_EP_F32_AMAX, hkv=1, f0=self.eps)) | |
| names.append("lm_head") | |
| rows.append(_row(_OP_ARGMAX_FIN, p1=self._parts.data_ptr(), | |
| p3=self._token.data_ptr(), | |
| p4=self._tokens_out.data_ptr())) | |
| names.append("argmax.fin") | |
| se = max(staged_elems[0], self._maxk) | |
| return rows, names, se | |
| def _build_programs(self): | |
| dev = self.device | |
| rows, names, _ = self._build_rows(kv_slice=0, batch=False) | |
| self._prog = torch.tensor(rows, dtype=torch.int64, device=dev) | |
| self.phase_names = names | |
| if self._batch: | |
| big = self._batch > min(8, 16384 // self._maxk) | |
| rows, _, se = self._build_rows(kv_slice=0, batch=True, big=big) | |
| self._prog_batch = torch.tensor(rows, dtype=torch.int64, | |
| device=dev) | |
| self._batch_stage_elems = int(se) | |
| self._progs_prefill = [self._prog] | |
| for b in range(1, self._batch): | |
| rows, _, _ = self._build_rows(kv_slice=b, batch=False) | |
| self._progs_prefill.append( | |
| torch.tensor(rows, dtype=torch.int64, device=dev)) | |
| def batch_max(self) -> int: | |
| """Largest batch size this model supports. Up to 8 sequences stage | |
| in shared; beyond that the transformed input moves to a global | |
| scratch, bounded by the kernel's register accumulators.""" | |
| try: | |
| hard = int(ops.mak_batch_maxb()) | |
| except (AttributeError, RuntimeError): | |
| hard = 8 | |
| return 1 if self._gemma else hard | |
| # ------------------------------------------------------------------ | |
| def enable_batch(self, B: int): | |
| """Allocate per-sequence KV caches, token buffers, and programs for | |
| batched decode. B up to 8 stages the input in shared; larger B (up | |
| to batch_max()) routes the transformed input through a global | |
| scratch so the batch width is not bounded by the projection size.""" | |
| if self._gemma: | |
| raise ValueError("batched decode covers the llama-family path") | |
| maxb = self.batch_max() | |
| if not 1 <= B <= maxb: | |
| raise ValueError(f"B must be in [1, {maxb}] for this model") | |
| if self._batch == B: | |
| return | |
| dt, dev = torch.bfloat16, self.device | |
| self._kcache = torch.zeros(self.L, B, self.Hkv, self.max_seq, | |
| self.D, dtype=dt, device=dev) | |
| self._vcache = torch.zeros_like(self._kcache) | |
| self._token = torch.zeros(B, dtype=torch.int32, device=dev) | |
| self._tokens_out = torch.zeros(B * self.max_gen, dtype=torch.int32, | |
| device=dev) | |
| self._logits = torch.empty(B * self.V, dtype=torch.float32, | |
| device=dev) | |
| self._parts = torch.zeros(B * self._nblocks, dtype=torch.int64, | |
| device=dev) | |
| self._pos_b = torch.zeros(B, dtype=torch.int32, device=dev) | |
| # transformed-input scratch: one row per active token, and prefill | |
| # (during generate_batch) still uses up to chunk_m rows | |
| self._xg = torch.empty(max(8, B) * self._maxk, dtype=dt, device=dev) | |
| # working buffers hold one row per active sequence; prefill chunks | |
| # still use up to chunk_m rows, so keep at least the fused capacity | |
| nb = max(8, B) | |
| qkvd = self._qdim + 2 * self._kvdim | |
| self._hidden = torch.empty(nb * self.hidden, dtype=dt, device=dev) | |
| self._qkv = torch.empty(nb * qkvd, dtype=dt, device=dev) | |
| self._gu = torch.empty(nb * 2 * self.I, dtype=dt, device=dev) | |
| self._partials = torch.empty( | |
| nb * self.Hq * self._maxch * (self.D + 2), dtype=torch.float32, | |
| device=dev) | |
| self._kv_bstride = self.Hkv * self.max_seq * self.D | |
| self._batch = B | |
| self._build_programs() | |
| def decode_batch(self, tokens: Sequence[int], positions: Sequence[int], | |
| steps: int = 1) -> torch.Tensor: | |
| """Batched greedy decode: sequence b consumes tokens[b] at | |
| positions[b]; `steps` in-kernel steps run with per-sequence argmax | |
| feedback. Returns the generated tokens [B, steps]; the final | |
| step's fp32 logits are live in batch_logits().""" | |
| B = self._batch | |
| if B < 1: | |
| raise ValueError("call enable_batch(B) first") | |
| if len(tokens) != B or len(positions) != B: | |
| raise ValueError("tokens and positions must have B entries") | |
| if steps < 1 or steps > self.max_gen: | |
| raise ValueError("steps out of range") | |
| if max(positions) + steps > self.max_seq: | |
| raise ValueError("sequence exceeds max_seq") | |
| self._token.copy_(torch.tensor(list(tokens), dtype=torch.int32)) | |
| self._pos_b.copy_(torch.tensor(list(positions), dtype=torch.int32)) | |
| ops.mak_run_batch(self._prog_batch, self._bar, self._pos_b, steps, | |
| 0, self._maxk, self._kv_bstride, | |
| self._batch_stage_elems) | |
| return self._tokens_out.view(B, self.max_gen)[:, :steps] | |
| def batch_logits(self) -> torch.Tensor: | |
| """fp32 logits [B, V] of the most recent batched step.""" | |
| return self._logits.view(max(self._batch, 1), self.V) | |
| def generate_batch(self, prompts: Sequence[Sequence[int]], | |
| max_new: int) -> List[List[int]]: | |
| """Greedy generation for a batch of prompts: per-sequence prefill, | |
| then one batched decode launch. Each sequence's token stream is | |
| bit-identical to its own single-sequence generate().""" | |
| B = len(prompts) | |
| if max_new < 1 or max_new > self.max_gen: | |
| raise ValueError("max_new out of range") | |
| self.enable_batch(B) | |
| firsts: List[int] = [] | |
| for b, ids in enumerate(prompts): | |
| P = len(ids) | |
| if P < 1 or P + max_new > self.max_seq: | |
| raise ValueError("sequence exceeds max_seq") | |
| self._prompt_in[:P].copy_( | |
| torch.tensor(list(ids), dtype=torch.int32)) | |
| ops.mak_run_seq(self._progs_prefill[b], self._bar, 0, P, 1 - P, | |
| P, self._maxk, self._chunk_m) | |
| firsts.append(int(self._token[0].item())) | |
| if max_new == 1: | |
| return [[t] for t in firsts] | |
| self._token.copy_(torch.tensor(firsts, dtype=torch.int32)) | |
| self._pos_b.copy_(torch.tensor([len(p) for p in prompts], | |
| dtype=torch.int32)) | |
| ops.mak_run_batch(self._prog_batch, self._bar, self._pos_b, | |
| max_new - 1, 0, self._maxk, self._kv_bstride, | |
| self._batch_stage_elems) | |
| rest = (self._tokens_out.view(B, self.max_gen)[:, :max_new - 1] | |
| .cpu().tolist()) | |
| return [[firsts[b]] + rest[b] for b in range(B)] | |
| # ------------------------------------------------------------------ | |
| def _dense_weight(mod): | |
| """The effective bf16 weight of a linear layer, dequantizing packed | |
| quantized layers (bitsandbytes 4-bit and 8-bit) so a quantized | |
| checkpoint loads directly. Dequantization is exact and does not | |
| depend on the card, so inference on the result keeps every | |
| guarantee; the weights materialize to bf16, so the quantized memory | |
| footprint is not preserved (that needs the in-kernel packed path).""" | |
| w = mod.weight | |
| qs = getattr(w, "quant_state", None) | |
| if qs is not None: # bitsandbytes 4-bit (nf4 / fp4) | |
| import bitsandbytes as bnb | |
| return bnb.functional.dequantize_4bit(w.data, qs).to(torch.bfloat16) | |
| if getattr(w, "SCB", None) is not None or hasattr(mod, "SCB"): | |
| # bitsandbytes 8-bit (LLM.int8): row scales in SCB, int8 in CB | |
| scb = w.SCB if getattr(w, "SCB", None) is not None else mod.SCB | |
| cb = w.data if w.data.dtype == torch.int8 else mod.CB | |
| return (cb.to(torch.float32) | |
| * (scb.to(torch.float32) / 127.0).unsqueeze(1) | |
| ).to(torch.bfloat16) | |
| return w | |
| def _q4_parts(mod): | |
| """(packed [N, K/2] uint8, absmax [N*K/64] fp32) for a bitsandbytes | |
| nf4 layer whose weights stay packed, else None. Double-quantized | |
| absmax is materialized to fp32 so the kernel needs one scale array.""" | |
| w = getattr(mod, "weight", None) | |
| qs = getattr(w, "quant_state", None) | |
| if qs is None or getattr(qs, "quant_type", None) != "nf4": | |
| return None | |
| import bitsandbytes as bnb | |
| n, k = int(qs.shape[0]), int(qs.shape[1]) | |
| if k % 64: | |
| return None # kernel assumes 64 | K for per-block absmax | |
| packed = w.data.reshape(n, k // 2).contiguous() | |
| if getattr(qs, "nested", False): | |
| absmax = bnb.functional.dequantize_blockwise( | |
| qs.absmax, qs.state2) + qs.offset | |
| else: | |
| absmax = qs.absmax | |
| return packed, absmax.float().reshape(-1).contiguous() | |
| def _proj(cls, mods): | |
| """A projection, concatenated across `mods` (q/k/v or gate/up). Stays | |
| nf4-packed when every part is nf4, else a dense bf16 tensor.""" | |
| parts = [cls._q4_parts(m) for m in mods] | |
| if all(p is not None for p in parts): | |
| packed = torch.cat([p[0] for p in parts], 0) | |
| absmax = torch.cat([p[1] for p in parts], 0) | |
| return {"q4": True, "packed": packed, "absmax": absmax, | |
| "N": packed.shape[0], "K": packed.shape[1] * 2} | |
| return torch.cat([cls._dense_weight(m) for m in mods], 0) | |
| def from_pretrained(cls, model, device="cuda", max_seq: int = 4096, | |
| max_gen: int = 4096): | |
| """Build from a transformers model (object or repo id). A quantized | |
| checkpoint (bitsandbytes 4-bit/8-bit) is accepted directly; its | |
| weights are dequantized to bf16 at load.""" | |
| if isinstance(model, str): | |
| from transformers import AutoModelForCausalLM | |
| model = AutoModelForCausalLM.from_pretrained( | |
| model, torch_dtype=torch.bfloat16) | |
| if "gemma4" in getattr(model.config, "model_type", ""): | |
| return cls._from_gemma4(model, device, max_seq, max_gen) | |
| hf = model.config | |
| dec = model.model | |
| attn0 = dec.layers[0].self_attn | |
| qk_norm = hasattr(attn0, "q_norm") and attn0.q_norm is not None | |
| if getattr(attn0.q_proj, "bias", None) is not None: | |
| raise ValueError("attention biases are not supported") | |
| # rope config across transformers API generations: v5 keeps theta and | |
| # type in a rope_parameters dict; v4 used a rope_theta attribute and | |
| # an optional rope_scaling dict. | |
| rp = getattr(hf, "rope_parameters", None) | |
| if not isinstance(rp, dict): | |
| rs = getattr(hf, "rope_scaling", None) | |
| rp = rs if isinstance(rs, dict) else {} | |
| theta = rp.get("rope_theta") | |
| if theta is None: | |
| theta = getattr(hf, "rope_theta", 10000.0) | |
| rope_type = rp.get("rope_type", rp.get("type", "default")) | |
| if rope_type not in ("default", "llama3"): | |
| raise ValueError(f"rope type {rope_type!r} is not supported") | |
| # take the frequency table transformers computed (scaling included) | |
| rot = dec.rotary_emb | |
| att_scale = float(getattr(rot, "attention_scaling", 1.0)) | |
| if att_scale != 1.0: | |
| raise ValueError("rope attention_scaling != 1 is not supported") | |
| inv_freq = rot.inv_freq.detach().float() | |
| pj = cls._proj | |
| layers = [] | |
| for lyr in dec.layers: | |
| a, m = lyr.self_attn, lyr.mlp | |
| layers.append({ | |
| "wqkv": pj([a.q_proj, a.k_proj, a.v_proj]), | |
| "wo": pj([a.o_proj]), | |
| "wgu": pj([m.gate_proj, m.up_proj]), | |
| "wdown": pj([m.down_proj]), | |
| "ln1": lyr.input_layernorm.weight, | |
| "ln2": lyr.post_attention_layernorm.weight, | |
| "qn": a.q_norm.weight if qk_norm else None, | |
| "kn": a.k_norm.weight if qk_norm else None, | |
| }) | |
| weights = {"embed": dec.embed_tokens.weight, "norm": dec.norm.weight, | |
| "lm_head": pj([model.lm_head]), "layers": layers, | |
| "inv_freq": inv_freq} | |
| config = { | |
| "num_hidden_layers": hf.num_hidden_layers, | |
| "hidden_size": hf.hidden_size, | |
| "num_attention_heads": hf.num_attention_heads, | |
| "num_key_value_heads": getattr(hf, "num_key_value_heads", None), | |
| "head_dim": getattr(hf, "head_dim", None), | |
| "intermediate_size": hf.intermediate_size, | |
| "vocab_size": hf.vocab_size, | |
| "rope_theta": float(theta), | |
| "rms_norm_eps": hf.rms_norm_eps, | |
| "qk_norm": qk_norm, | |
| } | |
| return cls(weights, config, device=device, max_seq=max_seq, | |
| max_gen=max_gen) | |
| # ------------------------------------------------------------------ | |
| def _from_gemma4(cls, model, device, max_seq, max_gen): | |
| """gemma-4-E2B-it program: gemma norms (fp32, raw weight), | |
| gelu-tanh gating, post-sublayer norm phases, per-layer-input | |
| pathway, sliding windows, shared KV, dual head dims, softcapped | |
| head. Scope is the E2B text model only.""" | |
| self = cls.__new__(cls) | |
| cfg = model.config.text_config if hasattr(model.config, "text_config") \ | |
| else model.config | |
| dec = model.model.language_model if hasattr(model.model, "language_model") \ | |
| else model.model | |
| assert cfg.model_type == "gemma4_text", cfg.model_type | |
| assert not cfg.enable_moe_block | |
| self.L = int(cfg.num_hidden_layers) | |
| self.hidden = int(cfg.hidden_size) | |
| self.Hq = int(cfg.num_attention_heads) | |
| self.V = int(cfg.vocab_size) | |
| self.eps = float(cfg.rms_norm_eps) | |
| self.qk_norm = True | |
| self.max_seq = int(max_seq) | |
| self.max_gen = int(max_gen) | |
| self.device = torch.device(device) | |
| self.tiled = False | |
| win = int(cfg.sliding_window) | |
| cap = float(cfg.final_logit_softcapping) | |
| PL = int(cfg.hidden_size_per_layer_input) | |
| dt = torch.bfloat16 | |
| dev = self.device | |
| self._keep = [] | |
| def pack(t): | |
| t = t.detach().to(device=dev, dtype=dt).contiguous() | |
| self._keep.append(t) | |
| return t | |
| def packf(t): | |
| t = t.detach().to(device=dev, dtype=torch.float32).contiguous() | |
| self._keep.append(t) | |
| return t | |
| embed = pack(dec.embed_tokens.weight) | |
| ple_tab = pack(dec.embed_tokens_per_layer.weight) | |
| plm_w = pack(dec.per_layer_model_projection.weight) | |
| pln_w = pack(dec.per_layer_projection_norm.weight) | |
| fnorm = pack(dec.norm.weight) | |
| rot = dec.rotary_emb | |
| invf = {lt: packf(getattr(rot, f"{lt}_inv_freq").float()) | |
| for lt in set(cfg.layer_types)} | |
| ascale = {lt: float(getattr(rot, f"{lt}_attention_scaling")) | |
| for lt in set(cfg.layer_types)} | |
| layers = [] | |
| for li, lyr in enumerate(dec.layers): | |
| a = lyr.self_attn | |
| D_l = int(a.head_dim) | |
| shared = bool(a.is_kv_shared_layer) | |
| ent = { | |
| "D": D_l, "shared": shared, | |
| "lscale": float(lyr.layer_scalar.float().item()), | |
| "type": cfg.layer_types[li], | |
| "I": int(lyr.mlp.intermediate_size), | |
| "qn": pack(a.q_norm.weight), | |
| "o": pack(a.o_proj.weight), | |
| "ln_in": pack(lyr.input_layernorm.weight), | |
| "ln_pa": pack(lyr.post_attention_layernorm.weight), | |
| "ln_pf": pack(lyr.pre_feedforward_layernorm.weight), | |
| "ln_ff": pack(lyr.post_feedforward_layernorm.weight), | |
| "ln_pl": pack(lyr.post_per_layer_input_norm.weight), | |
| "plig": pack(lyr.per_layer_input_gate.weight), | |
| "plpr": pack(lyr.per_layer_projection.weight), | |
| "wgu": pack(torch.cat([lyr.mlp.gate_proj.weight, | |
| lyr.mlp.up_proj.weight], 0)), | |
| "wdn": pack(lyr.mlp.down_proj.weight), | |
| } | |
| if shared: | |
| ent["wqkv"] = pack(a.q_proj.weight) | |
| ent["kn"] = None | |
| else: | |
| ent["wqkv"] = pack(torch.cat( | |
| [a.q_proj.weight, a.k_proj.weight, a.v_proj.weight], 0)) | |
| ent["kn"] = pack(a.k_norm.weight) | |
| ent["kc"] = torch.zeros(self.max_seq, D_l, dtype=dt, | |
| device=dev) | |
| ent["vc"] = torch.zeros(self.max_seq, D_l, dtype=dt, | |
| device=dev) | |
| self._keep += [ent["kc"], ent["vc"]] | |
| layers.append(ent) | |
| # shared layers read the last non-shared layer of their type | |
| src = {} | |
| for li, ent in enumerate(layers): | |
| if not ent["shared"]: | |
| src[ent["type"]] = ent | |
| for ent in layers: | |
| if ent["shared"]: | |
| ent["kc"] = src[ent["type"]]["kc"] | |
| ent["vc"] = src[ent["type"]]["vc"] | |
| maxD = max(e["D"] for e in layers) | |
| maxI = max(e["I"] for e in layers) | |
| self._maxk = max(self.hidden, self.Hq * maxD, maxI) | |
| self._chunk_m = 1 | |
| self._chunk = 128 | |
| self._maxch = (self.max_seq + self._chunk - 1) // self._chunk | |
| self._hidden = torch.empty(self.hidden, dtype=dt, device=dev) | |
| self._tmp = torch.empty(self.hidden, dtype=dt, device=dev) | |
| self._qkv = torch.empty((self.Hq + 2) * maxD, dtype=dt, device=dev) | |
| self._gu = torch.empty(2 * maxI, dtype=dt, device=dev) | |
| self._t256 = torch.empty(PL, dtype=dt, device=dev) | |
| self._ctxraw = torch.empty(self.L * PL, dtype=dt, device=dev) | |
| self._pletok = torch.empty(self.L * PL, dtype=dt, device=dev) | |
| self._ple = torch.empty(self.L * PL, dtype=dt, device=dev) | |
| self._logits = torch.empty(self.V, dtype=torch.float32, device=dev) | |
| self._partials = torch.empty( | |
| self.Hq * self._maxch * (maxD + 2), dtype=torch.float32, | |
| device=dev) | |
| self._token = torch.zeros(1, dtype=torch.int32, device=dev) | |
| self._prompt_in = torch.zeros(self.max_seq, dtype=torch.int32, | |
| device=dev) | |
| self._tokens_out = torch.zeros(self.max_gen, dtype=torch.int32, | |
| device=dev) | |
| self._bar = torch.zeros(34, dtype=torch.int32, device=dev) | |
| probe = torch.zeros(1, 16, dtype=torch.int64, device=dev) | |
| self._nblocks = int(ops.mak_num_blocks(probe, self._maxk)) | |
| self._parts = torch.zeros(self._nblocks, dtype=torch.int64, | |
| device=dev) | |
| try: | |
| self._bw_per_sm = float(ops.mak_bw_per_sm(probe)) | |
| except (AttributeError, RuntimeError): | |
| self._bw_per_sm = 0.0 | |
| self._invf = invf[layers[0]["type"]] | |
| self.Hkv, self.D, self.I = 1, maxD, maxI | |
| self.theta = 0.0 | |
| emb_scale = float(torch.tensor(self.hidden ** 0.5, | |
| dtype=torch.float32).to(dt)) | |
| ple_scale = float(torch.tensor(PL ** 0.5, | |
| dtype=torch.float32).to(dt)) | |
| proj_scale = float(torch.tensor(self.hidden ** -0.5, | |
| dtype=torch.float32)) | |
| hid = self._hidden.data_ptr() | |
| tmp = self._tmp.data_ptr() | |
| rows, names = [], [] | |
| rows.append(_row(_OP_EMBED, p1=ple_tab.data_ptr(), | |
| p2=embed.data_ptr(), p3=hid, | |
| p4=self._parts.data_ptr(), | |
| p5=self._prompt_in.data_ptr(), | |
| p6=self._token.data_ptr(), | |
| n=self._tokens_out.data_ptr(), k=self.hidden, | |
| it=self._pletok.data_ptr(), | |
| hkv=self.L * PL, f0=emb_scale, | |
| i0=_fbits(ple_scale))) | |
| names.append("embed") | |
| rows.append(_row(_OP_GEMV, p1=hid, p2=plm_w.data_ptr(), | |
| p3=self._ctxraw.data_ptr(), n=self.L * PL, | |
| k=self.hidden, it=_IT_NONE, epi=_EP_STORE)) | |
| names.append("plm") | |
| rows.append(_row(8, p1=self._ctxraw.data_ptr(), | |
| p2=pln_w.data_ptr(), p3=self._ple.data_ptr(), | |
| p4=self._pletok.data_ptr(), n=self.L, k=PL, | |
| f0=self.eps, i0=_fbits(proj_scale))) | |
| names.append("plemix") | |
| for li, e in enumerate(layers): | |
| D_l, qdim = e["D"], self.Hq * e["D"] | |
| lt = e["type"] | |
| w_l = win if lt == "sliding_attention" else 0 | |
| asb = _fbits(ascale[lt]) if ascale[lt] != 1.0 else 0 | |
| flags = 1 | 2 | (0 if e["shared"] else 4) | \ | |
| (8 if e["shared"] else 0) | |
| n_qkv = qdim if e["shared"] else qdim + 2 * D_l | |
| rows.append(_row(_OP_GEMV, p1=hid, p2=e["wqkv"].data_ptr(), | |
| p3=self._qkv.data_ptr(), | |
| p4=e["ln_in"].data_ptr(), n=n_qkv, | |
| k=self.hidden, it=_IT_RMSNORM_G, | |
| epi=_EP_STORE, f0=self.eps)) | |
| names.append(f"L{li}.qkv") | |
| rows.append(_row(_OP_ATTN, p1=self._qkv.data_ptr(), | |
| p2=e["kc"].data_ptr(), p3=e["vc"].data_ptr(), | |
| p4=self._partials.data_ptr(), | |
| p5=e["qn"].data_ptr(), | |
| p6=e["kn"].data_ptr() if e["kn"] is not None | |
| else 0, | |
| n=self.max_seq, | |
| k=flags | (w_l << 16), it=_fbits(self.eps), | |
| epi=invf[lt].data_ptr(), hq=self.Hq, | |
| hkv=1 | (self._chunk << 16), d=D_l, f0=1.0, | |
| i0=self._maxch | (asb << 32))) | |
| names.append(f"L{li}.attn") | |
| rows.append(_row(_OP_GEMV, p1=self._partials.data_ptr(), | |
| p2=e["o"].data_ptr(), p3=tmp, | |
| n=self.hidden, k=qdim, it=_IT_ATTNFIN, | |
| epi=_EP_STORE, hq=self.Hq, | |
| hkv=self._chunk << 16, d=D_l, | |
| i0=self._maxch | (w_l << 16))) | |
| names.append(f"L{li}.o") | |
| rows.append(_row(7, p1=tmp, p2=e["ln_pa"].data_ptr(), p3=hid, | |
| n=self.hidden, f0=self.eps, i0=_fbits(1.0))) | |
| names.append(f"L{li}.nr_attn") | |
| rows.append(_row(_OP_GEMV, p1=hid, p2=e["wgu"].data_ptr(), | |
| p3=self._gu.data_ptr(), | |
| p4=e["ln_pf"].data_ptr(), n=2 * e["I"], | |
| k=self.hidden, it=_IT_RMSNORM_G, | |
| epi=_EP_STORE, f0=self.eps)) | |
| names.append(f"L{li}.gateup") | |
| rows.append(_row(_OP_GEMV, p1=self._gu.data_ptr(), | |
| p2=e["wdn"].data_ptr(), p3=tmp, | |
| n=self.hidden, k=e["I"], it=_IT_GELU_GLU, | |
| epi=_EP_STORE)) | |
| names.append(f"L{li}.down") | |
| rows.append(_row(7, p1=tmp, p2=e["ln_ff"].data_ptr(), p3=hid, | |
| n=self.hidden, f0=self.eps, i0=_fbits(1.0))) | |
| names.append(f"L{li}.nr_ffw") | |
| rows.append(_row(_OP_GEMV, p1=hid, p2=e["plig"].data_ptr(), | |
| p3=self._t256.data_ptr(), | |
| p6=self._ple.data_ptr() + li * PL * 2, | |
| n=PL, k=self.hidden, it=_IT_NONE, | |
| epi=_EP_GELU_PLE)) | |
| names.append(f"L{li}.plig") | |
| rows.append(_row(_OP_GEMV, p1=self._t256.data_ptr(), | |
| p2=e["plpr"].data_ptr(), p3=tmp, | |
| n=self.hidden, k=PL, it=_IT_NONE, | |
| epi=_EP_STORE)) | |
| names.append(f"L{li}.plproj") | |
| rows.append(_row(7, p1=tmp, p2=e["ln_pl"].data_ptr(), p3=hid, | |
| n=self.hidden, f0=self.eps, | |
| i0=_fbits(e["lscale"]))) | |
| names.append(f"L{li}.nr_ple") | |
| rows.append(_row(_OP_GEMV, p1=hid, p2=embed.data_ptr(), | |
| p3=self._logits.data_ptr(), | |
| p4=fnorm.data_ptr(), | |
| p6=self._parts.data_ptr(), n=self.V, | |
| k=self.hidden, it=_IT_RMSNORM_G, | |
| epi=_EP_F32_AMAX_CAP, hkv=1, f0=self.eps, | |
| i0=_fbits(cap))) | |
| names.append("lm_head") | |
| rows.append(_row(_OP_ARGMAX_FIN, p1=self._parts.data_ptr(), | |
| p3=self._token.data_ptr(), | |
| p4=self._tokens_out.data_ptr())) | |
| names.append("argmax.fin") | |
| self._prog = torch.tensor(rows, dtype=torch.int64, device=dev) | |
| self.phase_names = names | |
| self._gemma = True | |
| self._batch = 0 | |
| return self | |
| # ------------------------------------------------------------------ | |
| def decode_step(self, token: int, pos: int, phased: bool = False): | |
| """One decode step; returns the live fp32 logits buffer [V].""" | |
| if pos >= self.max_seq: | |
| raise ValueError("pos exceeds max_seq") | |
| self._token.fill_(int(token)) | |
| if phased: | |
| ops.mak_run_phased(self._prog, pos, self.max_gen - 1, False, | |
| self._maxk) | |
| else: | |
| ops.mak_run(self._prog, self._bar, pos, self.max_gen - 1, | |
| self._maxk) | |
| return self._logits[:self.V] | |
| def decode_step_timed(self, token: int, pos: int): | |
| """One phase-per-launch step; returns (logits, per-phase ms).""" | |
| self._token.fill_(int(token)) | |
| ms = ops.mak_run_phased(self._prog, pos, self.max_gen - 1, True, | |
| self._maxk) | |
| return self._logits[:self.V], ms | |
| def prefill(self, ids: Sequence[int], phased: bool = False): | |
| """Consume a prompt; returns the last position's logits. Runs in | |
| chunks of up to chunk_m tokens with weight reads amortized across | |
| the chunk (bitwise identical to token-by-token consumption).""" | |
| P = len(ids) | |
| if phased: | |
| for i, t in enumerate(ids): | |
| self.decode_step(int(t), i, phased=True) | |
| return self._logits | |
| self._prompt_in[:P].copy_(torch.tensor(ids, dtype=torch.int32)) | |
| ops.mak_run_seq(self._prog, self._bar, 0, P, 1 - P, P, self._maxk, | |
| self._chunk_m) | |
| return self._logits[:self.V] | |
| def generate(self, prompt_ids: Sequence[int], max_new: int, | |
| single_launch: bool = True) -> List[int]: | |
| """Greedy generation. With single_launch (default) the entire call, | |
| prompt consumption included, is one kernel launch: the in-kernel step | |
| loop reads prompt tokens from a staged device buffer, then feeds each | |
| step's argmax to the next step's embedding read. With | |
| single_launch=False the same computation runs as one launch per | |
| token; the two paths produce identical tokens. | |
| """ | |
| P = len(prompt_ids) | |
| if max_new < 1 or max_new > self.max_gen: | |
| raise ValueError("max_new out of range") | |
| if P < 1 or P + max_new > self.max_seq: | |
| raise ValueError("sequence exceeds max_seq") | |
| if single_launch: | |
| self._prompt_in[:P].copy_( | |
| torch.tensor(prompt_ids, dtype=torch.int32)) | |
| ops.mak_run_seq(self._prog, self._bar, 0, P + max_new - 1, | |
| 1 - P, P, self._maxk, self._chunk_m) | |
| return self._tokens_out[:max_new].cpu().tolist() | |
| pos = 0 | |
| for t in prompt_ids[:-1]: | |
| self._token.fill_(int(t)) | |
| ops.mak_run(self._prog, self._bar, pos, self.max_gen - 1, | |
| self._maxk) | |
| pos += 1 | |
| self._token.fill_(int(prompt_ids[-1])) | |
| ops.mak_run(self._prog, self._bar, pos, 0, self._maxk) | |
| pos += 1 | |
| if max_new > 1: | |
| ops.mak_run_steps(self._prog, self._bar, pos, max_new - 1, 1, | |
| self._maxk) | |
| return self._tokens_out[:max_new].cpu().tolist() | |