batched decode; batch-invariance gates
Browse files- README.md +5 -3
- mak_cuda/megakernel.cu +73 -0
- tests/test_mak.py +35 -0
- torch-ext/model_as_a_kernel/__init__.py +141 -43
README.md
CHANGED
|
@@ -179,6 +179,8 @@ to 1673 at batch 16 (6.5x aggregate).
|
|
| 179 |
|
| 180 |
One CUDA device, greedy sampling in-kernel (route the fp32 logits to an
|
| 181 |
external sampler for anything else). Llama-family architectures without
|
| 182 |
-
rope attention scaling or attention biases.
|
| 183 |
-
|
| 184 |
-
|
|
|
|
|
|
|
|
|
| 179 |
|
| 180 |
One CUDA device, greedy sampling in-kernel (route the fp32 logits to an
|
| 181 |
external sampler for anything else). Llama-family architectures without
|
| 182 |
+
rope attention scaling or attention biases. Weights are dense bf16 or
|
| 183 |
+
bitsandbytes nf4 (4-bit checkpoints load with their weights kept packed
|
| 184 |
+
and dequantized in the kernel). Prompts prefill in chunks of at most eight
|
| 185 |
+
tokens. The program bakes device pointers, so the `MegaModel` owns its
|
| 186 |
+
weights and buffers for its lifetime.
|
mak_cuda/megakernel.cu
CHANGED
|
@@ -55,8 +55,20 @@ enum PhaseOp : int {
|
|
| 55 |
OP_NORMB = 10, // RMSNorm (llama or gemma) [B][K] -> scratch
|
| 56 |
OP_GLUB = 11, // SwiGLU / gelu-glu gating [B][2K] -> scratch [B][K]
|
| 57 |
OP_ATTNFINB = 12, // finalize split-S attention [B] -> scratch [B][qdim]
|
|
|
|
| 58 |
};
|
| 59 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
enum InputTransform : int {
|
| 61 |
IT_NONE = 0,
|
| 62 |
IT_RMSNORM = 1,
|
|
@@ -1326,6 +1338,66 @@ __device__ void ph_gemv_plain(const long long* P, int M, int rs) {
|
|
| 1326 |
}
|
| 1327 |
}
|
| 1328 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1329 |
template <int NB>
|
| 1330 |
__device__ __forceinline__ void dispatch_phase(const long long* P, int pos,
|
| 1331 |
int M, int step_slot,
|
|
@@ -1343,6 +1415,7 @@ __device__ __forceinline__ void dispatch_phase(const long long* P, int pos,
|
|
| 1343 |
case OP_NORMRES: ph_normres(P); break;
|
| 1344 |
case OP_PLEMIX: ph_plemix(P); break;
|
| 1345 |
case OP_GEMV_PLAIN: ph_gemv_plain<NB>(P, M, ring_stages); break;
|
|
|
|
| 1346 |
case OP_NORMB: ph_normb(P, M); break;
|
| 1347 |
case OP_GLUB: ph_glub(P, M); break;
|
| 1348 |
case OP_ATTNFINB: ph_attnfinb(P, M, pos_b, step); break;
|
|
|
|
| 55 |
OP_NORMB = 10, // RMSNorm (llama or gemma) [B][K] -> scratch
|
| 56 |
OP_GLUB = 11, // SwiGLU / gelu-glu gating [B][2K] -> scratch [B][K]
|
| 57 |
OP_ATTNFINB = 12, // finalize split-S attention [B] -> scratch [B][qdim]
|
| 58 |
+
OP_GEMV_Q4 = 13, // GEMV with nf4-packed weights, dequantized in-kernel
|
| 59 |
};
|
| 60 |
|
| 61 |
+
// NF4 codebook (bitsandbytes): the 16 quantile levels a 4-bit index maps
|
| 62 |
+
// to. Dequantized weight = code[nibble] * per-block absmax, rounded to
|
| 63 |
+
// bf16; the lookup is exact and the multiply is IEEE, so it reproduces on
|
| 64 |
+
// every card.
|
| 65 |
+
__device__ __constant__ float NF4_CODE[16] = {
|
| 66 |
+
-1.0f, -0.6961928009986877f, -0.5250730514526367f,
|
| 67 |
+
-0.39491748809814453f, -0.28444138169288635f, -0.18477343022823334f,
|
| 68 |
+
-0.09105003625154495f, 0.0f, 0.07958029955625534f, 0.16093020141124725f,
|
| 69 |
+
0.24611230194568634f, 0.33791524171829224f, 0.44070982933044434f,
|
| 70 |
+
0.5626170039176941f, 0.7229568362236023f, 1.0f};
|
| 71 |
+
|
| 72 |
enum InputTransform : int {
|
| 73 |
IT_NONE = 0,
|
| 74 |
IT_RMSNORM = 1,
|
|
|
|
| 1338 |
}
|
| 1339 |
}
|
| 1340 |
|
| 1341 |
+
// GEMV with nf4-packed weights [N][K/2] (two 4-bit indices per byte, row
|
| 1342 |
+
// major) and per-64-element fp32 absmax. Each lane dequantizes its own
|
| 1343 |
+
// weight tile (code[nibble] * absmax, rounded to bf16) and accumulates in
|
| 1344 |
+
// the exact order of the dense path, so the result is bit-identical to a
|
| 1345 |
+
// dense GEMV over the same dequantized weights and reproduces on every
|
| 1346 |
+
// card. Input is dense bf16 in a global buffer (a transform phase writes
|
| 1347 |
+
// it). Weights stay packed, so the quantized memory footprint is kept.
|
| 1348 |
+
template <int NB>
|
| 1349 |
+
__device__ void ph_gemv_q4(const long long* P, int M) {
|
| 1350 |
+
const unsigned char* W = reinterpret_cast<const unsigned char*>(P[2]);
|
| 1351 |
+
const float* absmax = reinterpret_cast<const float*>(P[6]);
|
| 1352 |
+
const bf16* resid = reinterpret_cast<const bf16*>(P[5]);
|
| 1353 |
+
const bf16* xin = reinterpret_cast<const bf16*>(P[1]);
|
| 1354 |
+
const long long N = P[7], K = P[8];
|
| 1355 |
+
const int epi = (int)P[10];
|
| 1356 |
+
const int lane = threadIdx.x & 31;
|
| 1357 |
+
const long long gwarp =
|
| 1358 |
+
((long long)blockIdx.x * blockDim.x + threadIdx.x) >> 5;
|
| 1359 |
+
const long long nwarps = ((long long)gridDim.x * blockDim.x) >> 5;
|
| 1360 |
+
const long long kbase = (long long)lane * 8;
|
| 1361 |
+
const long long ntiles = (kbase < K) ? (K - kbase + 255) / 256 : 0;
|
| 1362 |
+
const long long Kh = K >> 1; // packed bytes per row
|
| 1363 |
+
|
| 1364 |
+
for (long long row = gwarp; row < N; row += nwarps) {
|
| 1365 |
+
float acc[NB];
|
| 1366 |
+
#pragma unroll
|
| 1367 |
+
for (int m = 0; m < NB; ++m) acc[m] = 0.f;
|
| 1368 |
+
for (long long t = 0; t < ntiles; ++t) {
|
| 1369 |
+
const long long k = kbase + t * 256;
|
| 1370 |
+
const unsigned int pw =
|
| 1371 |
+
*reinterpret_cast<const unsigned int*>(W + row * Kh + (k >> 1));
|
| 1372 |
+
const float am = absmax[(row * K + k) >> 6];
|
| 1373 |
+
float w8[8];
|
| 1374 |
+
#pragma unroll
|
| 1375 |
+
for (int b = 0; b < 4; ++b) {
|
| 1376 |
+
const unsigned int by = (pw >> (b * 8)) & 0xFFu;
|
| 1377 |
+
// bitsandbytes packs the earlier element in the high nibble
|
| 1378 |
+
w8[2 * b] = bf2f(f2bf(NF4_CODE[by >> 4] * am));
|
| 1379 |
+
w8[2 * b + 1] = bf2f(f2bf(NF4_CODE[by & 0xF] * am));
|
| 1380 |
+
}
|
| 1381 |
+
#pragma unroll
|
| 1382 |
+
for (int m = 0; m < NB; ++m) {
|
| 1383 |
+
if (m < M) {
|
| 1384 |
+
const bf16* xp = xin + (long long)m * K + k;
|
| 1385 |
+
#pragma unroll
|
| 1386 |
+
for (int j = 0; j < 8; ++j)
|
| 1387 |
+
acc[m] = fmaf(w8[j], bf2f(xp[j]), acc[m]);
|
| 1388 |
+
}
|
| 1389 |
+
}
|
| 1390 |
+
}
|
| 1391 |
+
#pragma unroll
|
| 1392 |
+
for (int m = 0; m < NB; ++m) {
|
| 1393 |
+
if (m < M) {
|
| 1394 |
+
const float a = warp_sum(acc[m]);
|
| 1395 |
+
if (lane == 0) gemv_store(P, epi, N, resid, m, row, a);
|
| 1396 |
+
}
|
| 1397 |
+
}
|
| 1398 |
+
}
|
| 1399 |
+
}
|
| 1400 |
+
|
| 1401 |
template <int NB>
|
| 1402 |
__device__ __forceinline__ void dispatch_phase(const long long* P, int pos,
|
| 1403 |
int M, int step_slot,
|
|
|
|
| 1415 |
case OP_NORMRES: ph_normres(P); break;
|
| 1416 |
case OP_PLEMIX: ph_plemix(P); break;
|
| 1417 |
case OP_GEMV_PLAIN: ph_gemv_plain<NB>(P, M, ring_stages); break;
|
| 1418 |
+
case OP_GEMV_Q4: ph_gemv_q4<NB>(P, M); break;
|
| 1419 |
case OP_NORMB: ph_normb(P, M); break;
|
| 1420 |
case OP_GLUB: ph_glub(P, M); break;
|
| 1421 |
case OP_ATTNFINB: ph_attnfinb(P, M, pos_b, step); break;
|
tests/test_mak.py
CHANGED
|
@@ -212,6 +212,41 @@ def test_batch_invariance(models):
|
|
| 212 |
assert all(perm[j] == solo[order[j]] for j in range(B))
|
| 213 |
|
| 214 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 215 |
@requires_cuda
|
| 216 |
@pytest.mark.parametrize("model_id", ["Qwen/Qwen3-0.6B",
|
| 217 |
"TinyLlama/TinyLlama-1.1B-Chat-v1.0"])
|
|
|
|
| 212 |
assert all(perm[j] == solo[order[j]] for j in range(B))
|
| 213 |
|
| 214 |
|
| 215 |
+
@requires_cuda
|
| 216 |
+
def test_nf4_packed_matches_dequant():
|
| 217 |
+
"""A bitsandbytes nf4 checkpoint loads with weights kept packed and
|
| 218 |
+
dequantized in the kernel, bitwise identical to the same weights
|
| 219 |
+
dequantized to bf16, and deterministic."""
|
| 220 |
+
bnb = pytest.importorskip("bitsandbytes")
|
| 221 |
+
transformers = pytest.importorskip("transformers")
|
| 222 |
+
from transformers import BitsAndBytesConfig, AutoModelForCausalLM
|
| 223 |
+
mid = "HuggingFaceTB/SmolLM2-135M"
|
| 224 |
+
try:
|
| 225 |
+
q = AutoModelForCausalLM.from_pretrained(
|
| 226 |
+
mid, quantization_config=BitsAndBytesConfig(
|
| 227 |
+
load_in_4bit=True, bnb_4bit_quant_type="nf4",
|
| 228 |
+
bnb_4bit_compute_dtype=torch.bfloat16,
|
| 229 |
+
bnb_4bit_use_double_quant=False))
|
| 230 |
+
except Exception as e:
|
| 231 |
+
pytest.skip(f"bitsandbytes 4-bit unavailable here: {e}")
|
| 232 |
+
mm = mak.MegaModel.from_pretrained(q, max_seq=512, max_gen=64)
|
| 233 |
+
assert mm._has_q4 and mm._layers[0]["wdown"]["packed"].dtype == torch.uint8
|
| 234 |
+
ref = AutoModelForCausalLM.from_pretrained(mid, dtype=torch.bfloat16)
|
| 235 |
+
for name, mod in q.named_modules():
|
| 236 |
+
if isinstance(mod, bnb.nn.Linear4bit):
|
| 237 |
+
w = bnb.functional.dequantize_4bit(mod.weight.data,
|
| 238 |
+
mod.weight.quant_state)
|
| 239 |
+
ref.get_submodule(name).weight.data = \
|
| 240 |
+
w.to(torch.bfloat16).cpu().clone()
|
| 241 |
+
mr = mak.MegaModel.from_pretrained(ref.cuda().eval(), max_seq=512,
|
| 242 |
+
max_gen=64)
|
| 243 |
+
prompt = [5, 9, 13, 21, 33, 41, 40, 32]
|
| 244 |
+
assert torch.equal(mm.decode_step(prompt[0], 0),
|
| 245 |
+
mr.decode_step(prompt[0], 0))
|
| 246 |
+
g = mm.generate(prompt, 32)
|
| 247 |
+
assert g == mr.generate(prompt, 32) == mm.generate(prompt, 32)
|
| 248 |
+
|
| 249 |
+
|
| 250 |
@requires_cuda
|
| 251 |
@pytest.mark.parametrize("model_id", ["Qwen/Qwen3-0.6B",
|
| 252 |
"TinyLlama/TinyLlama-1.1B-Chat-v1.0"])
|
torch-ext/model_as_a_kernel/__init__.py
CHANGED
|
@@ -28,6 +28,7 @@ _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 |
_IT_NONE, _IT_RMSNORM, _IT_SWIGLU, _IT_ATTNFIN = 0, 1, 2, 3
|
| 32 |
_IT_RMSNORM_G, _IT_GELU_GLU = 4, 5
|
| 33 |
_EP_GELU_PLE, _EP_F32_AMAX_CAP = 5, 6
|
|
@@ -124,7 +125,7 @@ class MegaModel:
|
|
| 124 |
self._keep: List[torch.Tensor] = []
|
| 125 |
embed = pack(weights["embed"])
|
| 126 |
final_norm = pack(weights["norm"])
|
| 127 |
-
lm_head =
|
| 128 |
inv_freq = weights.get("inv_freq")
|
| 129 |
if inv_freq is None:
|
| 130 |
d_idx = torch.arange(0, self.D, 2, dtype=torch.float32)
|
|
@@ -137,24 +138,49 @@ class MegaModel:
|
|
| 137 |
self._invf = inv_freq
|
| 138 |
|
| 139 |
qdim, kvdim = self.Hq * self.D, self.Hkv * self.D
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
self._layers = []
|
| 141 |
for lw in weights["layers"]:
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
layer = {
|
| 147 |
-
"wqkv":
|
| 148 |
-
"wgu":
|
| 149 |
-
"wdown": pack_tiled(lw["wdown"]),
|
| 150 |
"ln1": pack(lw["ln1"]), "ln2": pack(lw["ln2"]),
|
| 151 |
"qn": pack(lw["qn"]) if self.qk_norm else None,
|
| 152 |
"kn": pack(lw["kn"]) if self.qk_norm else None,
|
| 153 |
}
|
|
|
|
|
|
|
| 154 |
self._layers.append(layer)
|
| 155 |
assert len(self._layers) == self.L
|
| 156 |
assert embed.shape == (self.V, self.hidden)
|
| 157 |
-
|
|
|
|
|
|
|
|
|
|
| 158 |
self._w_embed, self._w_fnorm, self._w_lmhead = (embed, final_norm,
|
| 159 |
lm_head)
|
| 160 |
self._qdim, self._kvdim = qdim, kvdim
|
|
@@ -202,6 +228,10 @@ class MegaModel:
|
|
| 202 |
self._pos_b = None
|
| 203 |
self._kv_bstride = 0
|
| 204 |
self._gemma = False
|
|
|
|
|
|
|
|
|
|
|
|
|
| 205 |
self._build_programs()
|
| 206 |
|
| 207 |
# ------------------------------------------------------------------
|
|
@@ -222,7 +252,7 @@ class MegaModel:
|
|
| 222 |
qdim, kvdim = self._qdim, self._kvdim
|
| 223 |
embed, lm_head = self._w_embed, self._w_lmhead
|
| 224 |
final_norm = self._w_fnorm
|
| 225 |
-
xg = self._xg.data_ptr()
|
| 226 |
B = self._batch if batch else 1
|
| 227 |
rows, names = [], []
|
| 228 |
staged_elems = [0] # widest [B][K] panel any fused projection stages
|
|
@@ -233,52 +263,63 @@ class MegaModel:
|
|
| 233 |
def note(K):
|
| 234 |
staged_elems[0] = max(staged_elems[0], B * K)
|
| 235 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 236 |
def norm_gemv(name, gamma, w, out, N, K, epi, inp, resid=0,
|
| 237 |
variant=_IT_RMSNORM):
|
| 238 |
-
if big and not fits(K):
|
| 239 |
rows.append(_row(_OP_NORMB, p1=inp, p2=gamma, p3=xg, k=K,
|
| 240 |
it=variant, f0=self.eps))
|
| 241 |
names.append(name + ".norm")
|
| 242 |
-
|
| 243 |
-
n=N, k=K, it=_tbit, epi=epi))
|
| 244 |
names.append(name)
|
| 245 |
else:
|
| 246 |
note(K)
|
| 247 |
-
rows.append(_row(_OP_GEMV, p1=inp, p2=w, p3=out,
|
| 248 |
-
p5=resid, n=N, k=K,
|
| 249 |
-
epi=epi, f0=self.eps))
|
| 250 |
names.append(name)
|
| 251 |
|
| 252 |
def glu_gemv(name, gu, w, out, resid, variant=_IT_SWIGLU):
|
| 253 |
-
if big and not fits(self.I):
|
| 254 |
rows.append(_row(_OP_GLUB, p1=gu, p3=xg, k=self.I, it=variant))
|
| 255 |
names.append(name + ".glu")
|
| 256 |
-
|
| 257 |
-
n=self.hidden, k=self.I, it=_tbit,
|
| 258 |
-
epi=_EP_RESID))
|
| 259 |
names.append(name)
|
| 260 |
else:
|
| 261 |
note(self.I)
|
| 262 |
-
rows.append(_row(_OP_GEMV, p1=gu, p2=w, p3=out,
|
| 263 |
-
n=self.hidden, k=self.I,
|
| 264 |
it=variant | _tbit, epi=_EP_RESID))
|
| 265 |
names.append(name)
|
| 266 |
|
| 267 |
def attn_gemv(name, w, out, resid):
|
| 268 |
-
if big and not fits(qdim):
|
| 269 |
rows.append(_row(_OP_ATTNFINB, p1=self._partials.data_ptr(),
|
| 270 |
p3=xg, k=qdim, hq=self.Hq,
|
| 271 |
hkv=self._chunk << 16, d=self.D,
|
| 272 |
i0=self._maxch))
|
| 273 |
names.append(name + ".fin")
|
| 274 |
-
|
| 275 |
-
n=self.hidden, k=qdim, it=_tbit,
|
| 276 |
-
epi=_EP_RESID))
|
| 277 |
names.append(name)
|
| 278 |
else:
|
| 279 |
note(qdim)
|
| 280 |
rows.append(_row(_OP_GEMV, p1=self._partials.data_ptr(),
|
| 281 |
-
p2=w, p3=out, p5=resid,
|
|
|
|
| 282 |
it=_IT_ATTNFIN | _tbit, epi=_EP_RESID,
|
| 283 |
hq=self.Hq, hkv=self._chunk << 16, d=self.D,
|
| 284 |
i0=self._maxch))
|
|
@@ -297,7 +338,7 @@ class MegaModel:
|
|
| 297 |
kc = self._kvp(self._kcache, li, kv_slice)
|
| 298 |
vc = self._kvp(self._vcache, li, kv_slice)
|
| 299 |
norm_gemv(f"L{li}.qkv", lw["ln1"].data_ptr(),
|
| 300 |
-
lw["wqkv"]
|
| 301 |
qdim + 2 * kvdim, self.hidden, _EP_STORE, hid)
|
| 302 |
rows.append(_row(6, p1=self._qkv.data_ptr(), p2=kc, p3=vc,
|
| 303 |
p6=lw["kn"].data_ptr() if self.qk_norm else 0,
|
|
@@ -317,14 +358,13 @@ class MegaModel:
|
|
| 317 |
hkv=self.Hkv | (self._chunk << 16),
|
| 318 |
d=self.D, f0=scale, i0=self._maxch))
|
| 319 |
names.append(f"L{li}.attn")
|
| 320 |
-
attn_gemv(f"L{li}.o", lw["wo"]
|
| 321 |
norm_gemv(f"L{li}.gateup", lw["ln2"].data_ptr(),
|
| 322 |
-
lw["wgu"]
|
| 323 |
self.hidden, _EP_STORE, hid)
|
| 324 |
-
glu_gemv(f"L{li}.down", self._gu.data_ptr(),
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
norm_gemv("lm_head", final_norm.data_ptr(), lm_head.data_ptr(),
|
| 328 |
self._logits.data_ptr(), self.V, self.hidden, _EP_F32,
|
| 329 |
hid)
|
| 330 |
rows.append(_row(_OP_ARGMAX_PART, p1=self._logits.data_ptr(),
|
|
@@ -402,8 +442,9 @@ class MegaModel:
|
|
| 402 |
self._parts = torch.zeros(B * self._nblocks, dtype=torch.int64,
|
| 403 |
device=dev)
|
| 404 |
self._pos_b = torch.zeros(B, dtype=torch.int32, device=dev)
|
| 405 |
-
# transformed-input scratch
|
| 406 |
-
|
|
|
|
| 407 |
# working buffers hold one row per active sequence; prefill chunks
|
| 408 |
# still use up to chunk_m rows, so keep at least the fused capacity
|
| 409 |
nb = max(8, B)
|
|
@@ -476,10 +517,67 @@ class MegaModel:
|
|
| 476 |
return [[firsts[b]] + rest[b] for b in range(B)]
|
| 477 |
|
| 478 |
# ------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 479 |
@classmethod
|
| 480 |
def from_pretrained(cls, model, device="cuda", max_seq: int = 4096,
|
| 481 |
max_gen: int = 4096):
|
| 482 |
-
"""Build from a transformers model (object or repo id).
|
|
|
|
|
|
|
| 483 |
if isinstance(model, str):
|
| 484 |
from transformers import AutoModelForCausalLM
|
| 485 |
model = AutoModelForCausalLM.from_pretrained(
|
|
@@ -513,22 +611,22 @@ class MegaModel:
|
|
| 513 |
raise ValueError("rope attention_scaling != 1 is not supported")
|
| 514 |
inv_freq = rot.inv_freq.detach().float()
|
| 515 |
|
|
|
|
| 516 |
layers = []
|
| 517 |
for lyr in dec.layers:
|
| 518 |
a, m = lyr.self_attn, lyr.mlp
|
| 519 |
layers.append({
|
| 520 |
-
"wqkv":
|
| 521 |
-
|
| 522 |
-
"
|
| 523 |
-
"
|
| 524 |
-
"wdown": m.down_proj.weight,
|
| 525 |
"ln1": lyr.input_layernorm.weight,
|
| 526 |
"ln2": lyr.post_attention_layernorm.weight,
|
| 527 |
"qn": a.q_norm.weight if qk_norm else None,
|
| 528 |
"kn": a.k_norm.weight if qk_norm else None,
|
| 529 |
})
|
| 530 |
weights = {"embed": dec.embed_tokens.weight, "norm": dec.norm.weight,
|
| 531 |
-
"lm_head": model.lm_head
|
| 532 |
"inv_freq": inv_freq}
|
| 533 |
config = {
|
| 534 |
"num_hidden_layers": hf.num_hidden_layers,
|
|
|
|
| 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
|
|
|
|
| 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)
|
|
|
|
| 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
|
|
|
|
| 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 |
# ------------------------------------------------------------------
|
|
|
|
| 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
|
|
|
|
| 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))
|
|
|
|
| 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,
|
|
|
|
| 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(),
|
|
|
|
| 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)
|
|
|
|
| 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(
|
|
|
|
| 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,
|