Download src/tinyllm/kernels/int8_gemv.py from CoolDip00/tiny-llm: direct link, hf CLI and curl.
- Browser
- Download file 3.97 kB
-
https://huggingface.co/spaces/CoolDip00/tiny-llm/resolve/main/src/tinyllm/kernels/int8_gemv.py
- Command line
-
hf download hf://spaces/CoolDip00/tiny-llm/src/tinyllm/kernels/int8_gemv.py
-
curl -L -o int8_gemv.py https://huggingface.co/spaces/CoolDip00/tiny-llm/resolve/main/src/tinyllm/kernels/int8_gemv.py
3.97 kB
| """Step 4.10b (D-016): INT8 weight-only matmul for decode, reading the int8 weights directly. | |
| The PyTorch path in tinyllm.quant (Int8Linear, backend "torch") runs three kernels per layer: | |
| cast the int8 weight to bf16 (reads 1 byte and writes 2 per weight), matmul (reads those 2 bytes | |
| again), then scale. Here one kernel reads each int8 weight once, converts it in registers (exact: | |
| |q| <= 127), accumulates in fp32 and applies the per-row scale at the end: | |
| y[m, n] = scale[n] * sum_k x[m, k] * q[n, k] x: (M, K) bf16/fp16, q: (N, K) int8 | |
| At decode, M (tokens in the step = batch) is small, so the step is bound by reading q. The tile | |
| over M is padded to 16 rows for tl.dot (tensor cores); the wasted flops are cheap next to the | |
| bytes. Each program owns BLOCK_N output columns and loops over K. Block sizes are autotuned per | |
| (N, K, M bucket) on first use, which must happen outside CUDA-graph capture (GraphDecoder warms up | |
| first). M above MAX_M (prefill) stays on the PyTorch path; see Int8Linear.forward. | |
| """ | |
| from __future__ import annotations | |
| import torch | |
| import triton | |
| import triton.language as tl | |
| from torch import Tensor | |
| MAX_M = 64 # rows per call this kernel takes; more (prefill) is a GEMM, left to cuBLAS | |
| _CONFIGS = [triton.Config({"BLOCK_N": bn, "BLOCK_K": bk}, num_warps=w, num_stages=s) | |
| for bn in (16, 32, 64) for bk in (128, 256) for w in (4,) for s in (3, 4)] | |
| _CONFIGS += [triton.Config({"BLOCK_N": 32, "BLOCK_K": 512}, num_warps=8, num_stages=3), | |
| triton.Config({"BLOCK_N": 64, "BLOCK_K": 128}, num_warps=2, num_stages=4)] | |
| def _int8_matmul_kernel(x_ptr, q_ptr, s_ptr, y_ptr, M, N, K, stride_xm, stride_qn, stride_ym, | |
| BLOCK_M: tl.constexpr, BLOCK_N: tl.constexpr, BLOCK_K: tl.constexpr): | |
| pid = tl.program_id(0) | |
| offs_m = tl.arange(0, BLOCK_M) | |
| offs_n = pid * BLOCK_N + tl.arange(0, BLOCK_N) | |
| m_ok, n_ok = offs_m < M, offs_n < N | |
| acc = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32) | |
| for k0 in range(0, K, BLOCK_K): | |
| offs_k = k0 + tl.arange(0, BLOCK_K) | |
| k_ok = offs_k < K | |
| x = tl.load(x_ptr + offs_m[:, None] * stride_xm + offs_k[None, :], | |
| mask=m_ok[:, None] & k_ok[None, :], other=0.0) | |
| q = tl.load(q_ptr + offs_n[:, None] * stride_qn + offs_k[None, :], | |
| mask=n_ok[:, None] & k_ok[None, :], other=0) | |
| acc = tl.dot(x, tl.trans(q.to(x.dtype)), acc) # int8 -> bf16 is exact | |
| s = tl.load(s_ptr + offs_n, mask=n_ok, other=0.0) | |
| y = acc * s[None, :] | |
| tl.store(y_ptr + offs_m[:, None] * stride_ym + offs_n[None, :], y, mask=m_ok[:, None] & n_ok[None, :]) | |
| def int8_matmul(x: Tensor, q: Tensor, scale: Tensor) -> Tensor: | |
| """x (M, K) bf16/fp16 CUDA, M <= MAX_M; q (N, K) int8; scale (N,) fp32 -> y (M, N) fp32. | |
| Same values as F.linear(x, q.to(x.dtype)) * scale, except that the matmul result isn't rounded | |
| to bf16 before scaling (closer to the fp32 dequantize-then-matmul reference, not further).""" | |
| M, K = x.shape | |
| N = q.shape[0] | |
| if M > MAX_M or q.shape[1] != K or x.dtype not in (torch.bfloat16, torch.float16): | |
| raise ValueError(f"int8_matmul: x {tuple(x.shape)} {x.dtype}, q {tuple(q.shape)}; needs M <= {MAX_M}, " | |
| "matching K, bf16/fp16 x") | |
| x = x if x.stride(1) == 1 else x.contiguous() | |
| q = q if q.stride(1) == 1 else q.contiguous() | |
| y = torch.empty((M, N), device=x.device, dtype=torch.float32) | |
| block_m = max(16, triton.next_power_of_2(M)) | |
| grid = lambda meta: (triton.cdiv(N, meta["BLOCK_N"]),) # noqa: E731 | |
| _int8_matmul_kernel[grid](x, q, scale, y, M, N, K, x.stride(0), q.stride(0), y.stride(0), BLOCK_M=block_m) | |
| return y | |
| def best_configs() -> dict[str, str]: | |
| """The autotuned config per (N, K, BLOCK_M) key so far, for the benchmark JSON.""" | |
| return {str(k): str(v) for k, v in _int8_matmul_kernel.cache.items()} | |