Implement INT4 group quantization, nibble packing and a dequantizing matmul
hardPremium edge casesimplement
int4_matmulBefore you code
Two signed 4-bit values occupy one byte. Predict which intermediate representation you need before multiplying them by a floating-point scale.
Review the prerequisite conceptThe concept page shows its own access level. The statement and visible tests below are free.
Write the three pieces of a weight-only INT4 path.
quantize_int4(w, group=128) -> (packed, scales, K)
w [N, K] float32
scales [N, G] float16, G = ceil(K / group), scale = absmax of the group / 7
packed [N, ceil(K / 2)] uint8: q = round(w / scale) clipped to [-8, 7], as a
two's-complement nibble; element 2j in the low nibble of byte j
(an odd K pads one zero nibble; a group of zeros gets scale 0)
unpack_int4(packed, K) -> int8 [N, K], sign-extended
int4_matmul(x, packed, scales, K, group=128) -> float32 [M, N]
y = x @ dequantize(W).T, computed one group at a time: multiply x by the group's
integer weights first, then apply the group's scale to each output column
The visible tests cover the round-trip error and the byte layout. The full suite adds shapes with a partial last group and an odd K, a group of zeros, sign extension of -8, the matmul against a full dequantization, and the storage size.
Visible tests run for everyone; the edge cases and the solution are Premium.
The visible tests
import numpy as np
def test_round_trip_error_stays_within_half_a_step(ns):
rng = np.random.default_rng(0)
w = rng.normal(size=(8, 256)).astype(np.float32)
packed, scale, K = ns["quantize_int4"](w, 128)
q = np.asarray(ns["unpack_int4"](packed, K), dtype=np.float32)
s = np.repeat(np.asarray(scale, np.float32), 128, axis=1)[:, :K]
err = np.abs(q * s - w)
assert (err <= s * 0.5 + 1e-3 * s + 1e-6).all(), f"worst error {err.max():.4f} exceeds half a quantization step"
def test_nibble_layout_is_even_low_odd_high(ns):
w = np.array([[1.0, -1.0, 7.0, -7.0]], dtype=np.float32) # absmax 7 -> scale 1
packed, scale, K = ns["quantize_int4"](w, 4)
assert float(np.asarray(scale)[0, 0]) == 1.0, f"absmax 7 over 7 is scale 1, got {np.asarray(scale)[0, 0]}"
got = [int(b) for b in np.asarray(packed)[0]]
assert got == [0xF1, 0x97], f"expected bytes [0xF1, 0x97] (1 low, -1 high; 7 low, -7 high), got {[hex(b) for b in got]}"
The reasoning behind this component: the companion page.