mn-zerogpu / tests /test_actquant.py
kharki's picture
Upload folder using huggingface_hub
6f75f79 verified
Raw History Blame Contribute Delete
2.59 kB
import math
import torch
from memory_native import RMSCounterLinear
from memory_native.actquant import (
effective_bits,
pack_int4,
quantize_codes,
stochastic_quantize,
unpack_int4,
)
def test_pack_int4_roundtrip_and_halves_bytes():
torch.manual_seed(0)
codes = torch.randint(-7, 8, (133,), dtype=torch.int8) # odd length on purpose
packed = pack_int4(codes)
assert packed.dtype == torch.uint8
assert packed.numel() == (codes.numel() + 1) // 2 # ~0.5 byte/elem
back = unpack_int4(packed, codes.numel())
assert torch.equal(back, codes)
def test_quantizer_is_unbiased():
torch.manual_seed(0)
x = torch.randn(500, 32)
# average many stochastic quantizations -> should converge to x (E[Q(x)|x]=x). 2000 draws put
# the Monte-Carlo noise floor at ~2.3e-3, so a 5e-3 bound actually bites: a small systematic
# bias (e.g. a rounding-direction or clamp error) would push this well past it. (The old 0.02
# bound was ~5x the noise floor and would have passed a biased quantizer.)
avg = torch.stack([stochastic_quantize(x, 4) for _ in range(2000)]).mean(0)
assert (avg - x).abs().mean().item() < 5e-3
def test_codes_fit_bit_width():
torch.manual_seed(0)
x = torch.randn(8, 64)
for bits in (3, 4, 8):
codes, scale = quantize_codes(x, bits)
lim = (1 << (bits - 1)) - 1
assert int(codes.abs().max()) <= lim
assert scale.shape == (8, 1)
def test_effective_bits_includes_scale():
# one fp16 scale amortized over a row of length 32 -> +0.5 bit/elem
assert abs(effective_bits(4, 32) - 4.5) < 1e-9
def test_low_bit_saved_activation_still_recovers_teacher():
"""The counter update needs only E[Q(x)|x]=x, so int8/4/3 saved activations must train
as well as fp (the activation-memory lever from the deep-v2 research)."""
def recover(bits):
torch.manual_seed(0)
n, N, C = 16, 256, 11
ts = 0.25
base = math.sqrt(3.0 / (2.0 * n))
tw = torch.randint(-1, 2, (n, n)).float()
x = torch.randn(N, n)
y = x @ (ts * tw).t()
lay = RMSCounterLinear(n, n, C=C, lr=0.005, lr_scale=0.0,
init_gain=ts / base, act_save_bits=bits).train()
for _ in range(400):
((lay(x) - y) ** 2).mean().backward()
with torch.no_grad():
return ((lay(x) - y) ** 2).mean().item()
fp = recover(0)
for bits in (8, 4, 3):
mse = recover(bits)
assert mse < 5e-3, (bits, mse)
assert mse <= fp + 2e-3, (bits, mse, fp)