Uploaded using `kernel-builder`.
Browse files- build/torch211-cxx11-cu126-x86_64-linux/__init__.py +964 -0
- build/torch211-cxx11-cu126-x86_64-linux/_model_as_a_kernel_cuda_e33875a.abi3.so +3 -0
- build/torch211-cxx11-cu126-x86_64-linux/_ops.py +9 -0
- build/torch211-cxx11-cu126-x86_64-linux/metadata.json +36 -0
- build/torch211-cxx11-cu126-x86_64-linux/model_as_a_kernel/__init__.py +26 -0
- build/torch211-cxx11-cu128-x86_64-linux/__init__.py +964 -0
- build/torch211-cxx11-cu128-x86_64-linux/_model_as_a_kernel_cuda_e33875a.abi3.so +3 -0
- build/torch211-cxx11-cu128-x86_64-linux/_ops.py +9 -0
- build/torch211-cxx11-cu128-x86_64-linux/metadata.json +38 -0
- build/torch211-cxx11-cu128-x86_64-linux/model_as_a_kernel/__init__.py +26 -0
- build/torch211-cxx11-cu130-x86_64-linux/__init__.py +964 -0
- build/torch211-cxx11-cu130-x86_64-linux/_model_as_a_kernel_cuda_e33875a.abi3.so +3 -0
- build/torch211-cxx11-cu130-x86_64-linux/_ops.py +9 -0
- build/torch211-cxx11-cu130-x86_64-linux/metadata.json +38 -0
- build/torch211-cxx11-cu130-x86_64-linux/model_as_a_kernel/__init__.py +26 -0
- build/torch212-cxx11-cu126-x86_64-linux/__init__.py +964 -0
- build/torch212-cxx11-cu126-x86_64-linux/_model_as_a_kernel_cuda_e33875a.abi3.so +3 -0
- build/torch212-cxx11-cu126-x86_64-linux/_ops.py +9 -0
- build/torch212-cxx11-cu126-x86_64-linux/metadata.json +36 -0
- build/torch212-cxx11-cu126-x86_64-linux/model_as_a_kernel/__init__.py +26 -0
- build/torch212-cxx11-cu130-x86_64-linux/__init__.py +964 -0
- build/torch212-cxx11-cu130-x86_64-linux/_model_as_a_kernel_cuda_e33875a.abi3.so +3 -0
- build/torch212-cxx11-cu130-x86_64-linux/_ops.py +9 -0
- build/torch212-cxx11-cu130-x86_64-linux/metadata.json +38 -0
- build/torch212-cxx11-cu130-x86_64-linux/model_as_a_kernel/__init__.py +26 -0
- build/torch212-cxx11-cu132-x86_64-linux/__init__.py +964 -0
- build/torch212-cxx11-cu132-x86_64-linux/_model_as_a_kernel_cuda_e33875a.abi3.so +3 -0
- build/torch212-cxx11-cu132-x86_64-linux/_ops.py +9 -0
- build/torch212-cxx11-cu132-x86_64-linux/metadata.json +38 -0
- build/torch212-cxx11-cu132-x86_64-linux/model_as_a_kernel/__init__.py +26 -0
- build/torch213-cxx11-cu126-x86_64-linux/__init__.py +964 -0
- build/torch213-cxx11-cu126-x86_64-linux/_model_as_a_kernel_cuda_e33875a.abi3.so +3 -0
- build/torch213-cxx11-cu126-x86_64-linux/_ops.py +9 -0
- build/torch213-cxx11-cu126-x86_64-linux/metadata.json +36 -0
- build/torch213-cxx11-cu126-x86_64-linux/model_as_a_kernel/__init__.py +26 -0
- build/torch213-cxx11-cu130-x86_64-linux/__init__.py +964 -0
- build/torch213-cxx11-cu130-x86_64-linux/_model_as_a_kernel_cuda_e33875a.abi3.so +3 -0
- build/torch213-cxx11-cu130-x86_64-linux/_ops.py +9 -0
- build/torch213-cxx11-cu130-x86_64-linux/metadata.json +38 -0
- build/torch213-cxx11-cu130-x86_64-linux/model_as_a_kernel/__init__.py +26 -0
- build/torch213-cxx11-cu132-x86_64-linux/__init__.py +964 -0
- build/torch213-cxx11-cu132-x86_64-linux/_model_as_a_kernel_cuda_e33875a.abi3.so +3 -0
- build/torch213-cxx11-cu132-x86_64-linux/_ops.py +9 -0
- build/torch213-cxx11-cu132-x86_64-linux/metadata.json +38 -0
- 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")))
|