Spaces:
Paused
Paused
Download tests/test_actquant.py from kharki/mn-zerogpu: direct link, hf CLI and curl.
- Browser
- Download file 2.59 kB
-
https://huggingface.co/spaces/kharki/mn-zerogpu/resolve/main/tests/test_actquant.py
- Command line
-
hf download hf://spaces/kharki/mn-zerogpu/tests/test_actquant.py
-
curl -L -o test_actquant.py https://huggingface.co/spaces/kharki/mn-zerogpu/resolve/main/tests/test_actquant.py
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) | |