Spaces:
Paused
Paused
Download src/memory_native/actquant.py from kharki/mn-zerogpu: direct link, hf CLI and curl.
- Browser
- Download file 3.1 kB
-
https://huggingface.co/spaces/kharki/mn-zerogpu/resolve/main/src/memory_native/actquant.py
- Command line
-
hf download hf://spaces/kharki/mn-zerogpu/src/memory_native/actquant.py
-
curl -L -o actquant.py https://huggingface.co/spaces/kharki/mn-zerogpu/resolve/main/src/memory_native/actquant.py
3.1 kB
| """Unbiased low-bit activation quantization — the activation-memory lever for the counter update. | |
| The counter update needs grad_w = Delta^T X. It does NOT need X exactly: if X is replaced by an | |
| *unbiased* low-bit quantization Q(X) with E[Q(X) | X] = X, then | |
| E[ Delta^T Q(X) | X, Delta ] = Delta^T X, | |
| so the update stays unbiased (it only gains variance). Storing Q(X) at b bits instead of fp X | |
| shrinks the saved-activation memory the backward needs. This module provides per-row symmetric | |
| stochastic quantization (the unbiasedness comes from stochastic rounding) and an effective-bits | |
| accounting that includes the per-row scale overhead. | |
| """ | |
| from __future__ import annotations | |
| import torch | |
| __all__ = ["stochastic_quantize", "quantize_codes", "dequantize_codes", "effective_bits", | |
| "pack_int4", "unpack_int4"] | |
| def pack_int4(codes: torch.Tensor) -> torch.Tensor: | |
| """Pack signed 4-bit codes (values in [-8,7]) two per byte -> uint8, ~half the bytes. | |
| Returns a flat uint8 tensor; the caller keeps the element count to unpack.""" | |
| flat = codes.reshape(-1).to(torch.int64) | |
| nib = (flat + 8).clamp_(0, 15) # signed [-8,7] -> [0,15] | |
| if nib.numel() % 2: | |
| nib = torch.cat([nib, nib.new_zeros(1)]) | |
| lo = nib[0::2] | |
| hi = nib[1::2] | |
| return (lo | (hi << 4)).to(torch.uint8) | |
| def unpack_int4(packed: torch.Tensor, n: int) -> torch.Tensor: | |
| """Inverse of pack_int4. Returns a flat int8 tensor of length n with values in [-8,7].""" | |
| p = packed.to(torch.int64) | |
| lo = (p & 0x0F) - 8 | |
| hi = ((p >> 4) & 0x0F) - 8 | |
| out = torch.stack([lo, hi], dim=1).reshape(-1)[:n] | |
| return out.to(torch.int8) | |
| def _levels(bits: int) -> int: | |
| # signed symmetric: codes in [-levels, levels], levels = 2^(bits-1) - 1 | |
| return (1 << (bits - 1)) - 1 | |
| def quantize_codes(x: torch.Tensor, bits: int, dim: int = -1): | |
| """Per-row symmetric stochastic quantization. Returns (codes int, scale fp) with | |
| codes in [-levels, levels] and E[codes * scale | x] = x. Storing codes (b bits) + one | |
| scale per row is the low-bit saved activation.""" | |
| levels = _levels(bits) | |
| amax = x.abs().amax(dim=dim, keepdim=True).clamp_min(1e-12) | |
| scale = amax / levels | |
| y = x / scale | |
| floor = torch.floor(y) | |
| codes = floor + (torch.rand_like(y) < (y - floor)).to(y.dtype) # stochastic round (unbiased) | |
| codes = codes.clamp_(-levels, levels) | |
| return codes, scale | |
| def dequantize_codes(codes: torch.Tensor, scale: torch.Tensor) -> torch.Tensor: | |
| return codes * scale | |
| def stochastic_quantize(x: torch.Tensor, bits: int, dim: int = -1) -> torch.Tensor: | |
| """Convenience: round-trip x through unbiased b-bit quantization, returning the dequantized | |
| tensor (same shape/dtype as x). E[stochastic_quantize(x) | x] = x.""" | |
| codes, scale = quantize_codes(x, bits, dim) | |
| return dequantize_codes(codes, scale).to(x.dtype) | |
| def effective_bits(bits: int, row_len: int, scale_bits: int = 16) -> float: | |
| """Bits per element including the amortized per-row scale (one scale_bits value per row).""" | |
| return bits + scale_bits / float(row_len) | |