Download scripts/bitnet_linear.py from CrashOverrideX/Quillan-Ronin: direct link, hf CLI and curl.
- Browser
- Download file 4.37 kB
-
https://huggingface.co/CrashOverrideX/Quillan-Ronin/resolve/main/scripts/bitnet_linear.py
- Command line
-
hf download hf://CrashOverrideX/Quillan-Ronin/scripts/bitnet_linear.py
-
curl -L -o bitnet_linear.py https://huggingface.co/CrashOverrideX/Quillan-Ronin/resolve/main/scripts/bitnet_linear.py
4.37 kB
| """Proper BitNet 1.58b Linear with packed ternary storage. | |
| Storage: 4 ternary values per byte (0.5 bytes/value). | |
| Training: unpacks to fp16, computes, repacks. | |
| Inference: loads only packed ternary (no fp16 copies). | |
| """ | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| def _ternary_quant(w, eps=0.01): | |
| """Quantize weights to {-1, 0, 1} with STE.""" | |
| scale = w.abs().mean().clamp(min=eps) | |
| w_scaled = w / scale | |
| w_q = torch.round(torch.clamp(w_scaled, -1.0, 1.0)) | |
| return w + (w_q * scale - w).detach(), scale | |
| def _pack_ternary(w): | |
| """Pack fp16 ternary {-1,0,1} values into uint8 (4 per byte).""" | |
| w_int = (w.round().clamp(-1, 1) + 1).long() # -1→0, 0→1, 1→2 | |
| flat = w_int.reshape(-1) | |
| pad = (4 - flat.numel() % 4) % 4 | |
| if pad: flat = torch.cat([flat, flat.new_zeros(pad)]) | |
| packed = flat[0::4] | (flat[1::4] << 2) | (flat[2::4] << 4) | (flat[3::4] << 6) | |
| return packed.to(torch.uint8) | |
| def _unpack_ternary(packed, shape, device='cpu'): | |
| """Unpack uint8 to fp16 ternary values.""" | |
| flat = packed.to(torch.uint8) | |
| v0 = (flat >> 0) & 3; v1 = (flat >> 2) & 3 | |
| v2 = (flat >> 4) & 3; v3 = (flat >> 6) & 3 | |
| vals = torch.stack([v0, v1, v2, v3], -1).reshape(-1) | |
| result = torch.where(vals == 0, -1, torch.where(vals == 2, 1, 0)) | |
| total = torch.Size(shape).numel() | |
| return result[:total].reshape(shape).half() | |
| class BitNetLinear(nn.Module): | |
| """BitNet 1.58b Linear with packed ternary storage. | |
| - self.weight: nn.Parameter containing fp16 latent weights (training only) | |
| - self.packed: buffer with packed ternary weights (for saving/loading) | |
| - During training: use self.weight (fp16 latent) via _ternary_quant | |
| - During inference: use self.packed (unpacked on-the-fly) | |
| - Save checkpoint: only save self.packed (not self.weight) | |
| """ | |
| def __init__(self, in_features, out_features, bias=False): | |
| super().__init__() | |
| self.in_features = in_features | |
| self.out_features = out_features | |
| # Latent fp16 weight for gradient accumulation (training only) | |
| self.weight = nn.Parameter(torch.empty(out_features, in_features, dtype=torch.float16)) | |
| nn.init.kaiming_normal_(self.weight, a=0.1) | |
| # Packed ternary storage (for checkpoint save/load) | |
| self.register_buffer('packed', _pack_ternary(self.weight.data)) | |
| if bias: | |
| self.bias = nn.Parameter(torch.zeros(out_features, dtype=torch.float16)) | |
| else: | |
| self.bias = None | |
| def forward(self, x): | |
| """Forward pass with ternary weights and 8-bit activation quantization.""" | |
| if self.training: | |
| # Use fp16 latent weights with ternary STE quantization | |
| w_eff, _ = _ternary_quant(self.weight) | |
| else: | |
| # Use packed ternary (unpack once, cache for subsequent calls) | |
| if not hasattr(self, '_unpacked') or self._unpacked is None: | |
| self._unpacked = _unpack_ternary(self.packed, self.weight.shape, x.device) | |
| w_eff = self._unpacked | |
| # 8-bit activation quantization with STE | |
| x_scale = 127.0 / x.abs().max(dim=-1, keepdim=True).values.clamp(min=0.01) | |
| x_8bit = (x * x_scale).round().clamp(-128, 127) / x_scale | |
| x_q = x + (x_8bit - x).detach() | |
| return F.linear(x_q, w_eff, self.bias) | |
| def pack_weights(self): | |
| """Pack current weights to ternary storage.""" | |
| w_q, scale = _ternary_quant(self.weight.data) | |
| self.packed = _pack_ternary(w_q) | |
| self._unpacked = None | |
| def from_linear(cls, linear, in_features=None, out_features=None): | |
| """Convert a standard Linear or existing BitLinear to BitNetLinear.""" | |
| if in_features is None: in_features = linear.in_features | |
| if out_features is None: out_features = linear.out_features | |
| bl = cls(in_features, out_features, bias=linear.bias is not None) | |
| with torch.no_grad(): | |
| w = linear.weight.data | |
| if w.dtype != torch.float16: w = w.half() | |
| bl.weight.data.copy_(w) | |
| bl.pack_weights() | |
| if bl.bias is not None and linear.bias is not None: | |
| bl.bias.data.copy_(linear.bias.data.half() if linear.bias.dtype != torch.float16 else linear.bias.data) | |
| return bl | |