Text Generation
GGUF
llama.cpp
pocketpal
on-device
Mixture of Experts
multilingual
quantization
distillation
1-bit
2-bit
3-bit
conversational
Instructions to use RegalInvader/Marco-Nano-Instruct-GGUF with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- llama.cpp
How to use RegalInvader/Marco-Nano-Instruct-GGUF with llama.cpp:
Install (macOS, Linux)
curl -LsSf https://llama.app/install.sh | sh # Start a local OpenAI-compatible server with a web UI: llama serve -hf RegalInvader/Marco-Nano-Instruct-GGUF:TQ1_0 # Run inference directly in the terminal: llama cli -hf RegalInvader/Marco-Nano-Instruct-GGUF:TQ1_0
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf RegalInvader/Marco-Nano-Instruct-GGUF:TQ1_0 # Run inference directly in the terminal: llama cli -hf RegalInvader/Marco-Nano-Instruct-GGUF:TQ1_0
Build from source code
git clone https://github.com/ggml-org/llama.cpp.git cd llama.cpp cmake -B build cmake --build build -j --target llama-app # Start a local OpenAI-compatible server with a web UI: ./build/bin/llama serve -hf RegalInvader/Marco-Nano-Instruct-GGUF:TQ1_0 # Run inference directly in the terminal: ./build/bin/llama cli -hf RegalInvader/Marco-Nano-Instruct-GGUF:TQ1_0
Use Docker
docker model run hf.co/RegalInvader/Marco-Nano-Instruct-GGUF:TQ1_0
- Llama new
- LM Studio
- Jan
- vLLM
How to use RegalInvader/Marco-Nano-Instruct-GGUF with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "RegalInvader/Marco-Nano-Instruct-GGUF" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "RegalInvader/Marco-Nano-Instruct-GGUF", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/RegalInvader/Marco-Nano-Instruct-GGUF:TQ1_0
- Ollama
How to use RegalInvader/Marco-Nano-Instruct-GGUF with Ollama:
ollama run hf.co/RegalInvader/Marco-Nano-Instruct-GGUF:TQ1_0
- Unsloth Desktop
- Pi
How to use RegalInvader/Marco-Nano-Instruct-GGUF with Pi:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf RegalInvader/Marco-Nano-Instruct-GGUF:TQ1_0
Configure the model in Pi
# Install Pi: npm install -g @earendil-works/pi-coding-agent # Add to ~/.pi/agent/models.json: { "providers": { "llama-cpp": { "baseUrl": "http://localhost:8080/v1", "api": "openai-completions", "apiKey": "none", "models": [ { "id": "RegalInvader/Marco-Nano-Instruct-GGUF:TQ1_0" } ] } } }Run Pi
# Start Pi in your project directory: pi
- Docker Model Runner
How to use RegalInvader/Marco-Nano-Instruct-GGUF with Docker Model Runner:
docker model run hf.co/RegalInvader/Marco-Nano-Instruct-GGUF:TQ1_0
- Lemonade
How to use RegalInvader/Marco-Nano-Instruct-GGUF with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull RegalInvader/Marco-Nano-Instruct-GGUF:TQ1_0
Run and chat with the model
lemonade run user.Marco-Nano-Instruct-GGUF-TQ1_0
List all available models
lemonade list
- Hermes Agent
How to use RegalInvader/Marco-Nano-Instruct-GGUF with Hermes Agent:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf RegalInvader/Marco-Nano-Instruct-GGUF:TQ1_0
Configure Hermes
# Install Hermes: curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash hermes setup # Point Hermes at the local server: hermes config set model.provider custom hermes config set model.base_url http://127.0.0.1:8080/v1 hermes config set model.default RegalInvader/Marco-Nano-Instruct-GGUF:TQ1_0
Run Hermes
hermes
- Atomic Chat
- OpenClaw
How to use RegalInvader/Marco-Nano-Instruct-GGUF with OpenClaw:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf RegalInvader/Marco-Nano-Instruct-GGUF:TQ1_0
Configure OpenClaw
# Install OpenClaw: npm install -g openclaw@latest # Register the local server and set it as the default model: openclaw onboard --non-interactive --mode local \ --auth-choice custom-api-key \ --custom-base-url http://127.0.0.1:8080/v1 \ --custom-model-id "RegalInvader/Marco-Nano-Instruct-GGUF:TQ1_0" \ --custom-provider-id llama-cpp \ --custom-compatibility openai \ --custom-text-input \ --accept-risk \ --skip-health
Run OpenClaw
openclaw agent --local --agent main --message "Hello from Hugging Face"
Download code/src/lowbit/quant/codebook.py from RegalInvader/Marco-Nano-Instruct-GGUF: direct link, hf CLI and curl.
- Browser
- Download file 17.2 kB
-
https://huggingface.co/RegalInvader/Marco-Nano-Instruct-GGUF/resolve/main/code/src/lowbit/quant/codebook.py
- Command line
-
hf download hf://RegalInvader/Marco-Nano-Instruct-GGUF/code/src/lowbit/quant/codebook.py
-
curl -L -o codebook.py https://huggingface.co/RegalInvader/Marco-Nano-Instruct-GGUF/resolve/main/code/src/lowbit/quant/codebook.py
17.2 kB
| """D-58 · llama.cpp's codebook formats as trainable students, started from llama.cpp's own files. | |
| A student starts from llama.cpp's imatrix quantisation of the teacher (`scripts/ptq_baseline.py`): its expert | |
| tensors are read from that GGUF as raw blocks and kept. Every format here stores each block as | |
| weight = eff(d, s) · v | |
| where v is a small signed integer per weight (a codebook entry times its sign: the grid value of IQ2/IQ3, the | |
| IQ4_NL table value), d is the block's fp16 scale and s its sub-blocks' integer scales: | |
| format block sub eff s range bytes (d at bytes 0-1 in every format) | |
| IQ2_XXS 256 32 d·(0.5 + s)·0.25 0..15 66 s: top 4 bits of each sub-block's 2nd uint32 | |
| IQ2_XS 256 16 d·(0.5 + s)·0.25 0..15 74 s: 16 nibbles at bytes 66-73 | |
| IQ2_S 256 16 d·(0.5 + s)·0.25 0..15 82 s: 16 nibbles at bytes 74-81 | |
| IQ3_XXS 256 32 d·(0.5 + s)·0.5 0..15 98 s: top 4 bits of the uint32s at bytes 66-97 | |
| IQ3_S 256 32 d·(1 + 2s) 0..15 110 s: 8 nibbles at bytes 106-109 | |
| IQ4_XS 256 32 d·(s − 32) 0..63 136 s: 4 low bits at bytes 4-7, 2 high at bytes 2-3 | |
| IQ4_NL 32 32 d — 18 (no sub-scale) | |
| (ggml-quants.c's dequantize_row_*; gguf-py's `dequantize_blocks` are the reference the tests compare against.) | |
| The codes — which codebook entry and which signs — are the bytes other than d and s. They are kept unchanged | |
| ("frozen codes"): training moves d and s only, on their own grids, plus the bf16 attention and norms. The export | |
| writes the new d and s into the original blocks, so an untrained student exports byte-identical to llama.cpp's | |
| file, and every exported block is one llama.cpp wrote except for its scales. | |
| Trainable parameters per tensor, both fp32: `dlog` per block (d = d0·exp(dlog), rounded to fp16 in the forward) | |
| and `slat` per sub-block (s = round(slat) clamped to the range). The gradient reaches both through the plain | |
| straight-through estimator, like the latents of `quant.ternary`. v·eff is exact in fp32 (d: 11 significant bits, | |
| the s factor ≤ 7, v ≤ 7), as ggml computes it; the forward then rounds to bf16 like every other student. | |
| """ | |
| from __future__ import annotations | |
| from dataclasses import dataclass | |
| import numpy as np | |
| import torch | |
| class Format: | |
| name: str | |
| block: int # weights per block (one fp16 d) | |
| sub: int # weights per sub-scale (= block when there is none) | |
| nbytes: int # bytes per block | |
| a: float # eff = d·a·(s + b); IQ4_NL has no s (eff = d) | |
| b: float | |
| s_max: int # s in 0..s_max | |
| neutral_s: int # the s that makes eff = d·unit when decoding v (exact) | |
| unit: float # a·(neutral_s + b) | |
| FORMATS = { | |
| "IQ2_XXS": Format("IQ2_XXS", 256, 32, 66, 0.25, 0.5, 15, 0, 0.125), | |
| "IQ2_XS": Format("IQ2_XS", 256, 16, 74, 0.25, 0.5, 15, 0, 0.125), | |
| "IQ2_S": Format("IQ2_S", 256, 16, 82, 0.25, 0.5, 15, 0, 0.125), | |
| "IQ3_XXS": Format("IQ3_XXS", 256, 32, 98, 0.5, 0.5, 15, 0, 0.25), | |
| "IQ3_S": Format("IQ3_S", 256, 32, 110, 2.0, 0.5, 15, 0, 1.0), | |
| "IQ4_XS": Format("IQ4_XS", 256, 32, 136, 1.0, -32.0, 63, 33, 1.0), | |
| "IQ4_NL": Format("IQ4_NL", 32, 32, 18, 1.0, 0.0, 0, 0, 1.0), | |
| } | |
| # ---------------------------------------------------------------- block fields (numpy, uint8 [n, nbytes]) | |
| def get_d(blocks: np.ndarray) -> np.ndarray: | |
| return blocks[:, :2].copy().view(np.float16)[:, 0] | |
| def set_d(blocks: np.ndarray, d: np.ndarray) -> None: | |
| blocks[:, :2] = np.ascontiguousarray(d.astype(np.float16)).reshape(-1, 1).view(np.uint8) | |
| def _nibbles(b: np.ndarray) -> np.ndarray: | |
| return np.stack([b & 15, b >> 4], -1).reshape(b.shape[0], -1) | |
| def _pack_nibbles(s: np.ndarray) -> np.ndarray: | |
| s = s.astype(np.uint8).reshape(s.shape[0], -1, 2) | |
| return s[..., 0] | (s[..., 1] << 4) | |
| def _u32(blocks: np.ndarray, lo: int, hi: int) -> np.ndarray: | |
| return blocks[:, lo:hi].copy().view("<u4") | |
| def _set_u32_top4(blocks: np.ndarray, lo: int, hi: int, s: np.ndarray) -> None: | |
| w = _u32(blocks, lo, hi) | |
| w = (w & np.uint32(0x0FFFFFFF)) | (s.astype(np.uint32) << np.uint32(28)) | |
| blocks[:, lo:hi] = w.view(np.uint8) | |
| def get_s(fmt: Format, blocks: np.ndarray) -> np.ndarray: | |
| """The sub-block scales, uint8 [n, block/sub] (IQ4_NL: an empty [n, 0]).""" | |
| n = fmt.name | |
| if n == "IQ2_XXS": | |
| return (_u32(blocks, 2, 66)[:, 1::2] >> 28).astype(np.uint8) | |
| if n == "IQ3_XXS": | |
| return (_u32(blocks, 66, 98) >> 28).astype(np.uint8) | |
| if n == "IQ2_XS": | |
| return _nibbles(blocks[:, 66:74]) | |
| if n == "IQ2_S": | |
| return _nibbles(blocks[:, 74:82]) | |
| if n == "IQ3_S": | |
| return _nibbles(blocks[:, 106:110]) | |
| if n == "IQ4_XS": | |
| lo = _nibbles(blocks[:, 4:8]) | |
| hi = blocks[:, 2:4].copy().view("<u2")[:, :1] >> (2 * np.arange(8, dtype=np.uint16)) | |
| return (lo | ((hi & 3).astype(np.uint8) << 4)).astype(np.uint8) | |
| if n == "IQ4_NL": | |
| return np.zeros((blocks.shape[0], 0), np.uint8) | |
| raise ValueError(n) | |
| def set_s(fmt: Format, blocks: np.ndarray, s: np.ndarray) -> None: | |
| n = fmt.name | |
| if n == "IQ2_XXS": | |
| w = _u32(blocks, 2, 66) | |
| w[:, 1::2] = (w[:, 1::2] & np.uint32(0x0FFFFFFF)) | (s.astype(np.uint32) << np.uint32(28)) | |
| blocks[:, 2:66] = w.view(np.uint8) | |
| elif n == "IQ3_XXS": | |
| _set_u32_top4(blocks, 66, 98, s) | |
| elif n == "IQ2_XS": | |
| blocks[:, 66:74] = _pack_nibbles(s) | |
| elif n == "IQ2_S": | |
| blocks[:, 74:82] = _pack_nibbles(s) | |
| elif n == "IQ3_S": | |
| blocks[:, 106:110] = _pack_nibbles(s) | |
| elif n == "IQ4_XS": | |
| s = s.astype(np.uint16) | |
| blocks[:, 4:8] = _pack_nibbles(s & 15) | |
| hi = ((s >> 4) << (2 * np.arange(8, dtype=np.uint16))).sum(-1).astype("<u2") | |
| blocks[:, 2:4] = hi.reshape(-1, 1).view(np.uint8) | |
| elif n != "IQ4_NL": | |
| raise ValueError(n) | |
| def eff_np(fmt: Format, d: np.ndarray, s: np.ndarray) -> np.ndarray: | |
| """fp32 effective scale per sub-block [n, block/sub], as ggml computes it.""" | |
| d = d.astype(np.float32).reshape(-1, 1) | |
| if fmt.name == "IQ4_NL": | |
| return d | |
| if fmt.name == "IQ4_XS": | |
| return d * (s.astype(np.float32) - np.float32(32)) | |
| if fmt.name == "IQ3_S": | |
| return d * (np.float32(1) + np.float32(2) * s.astype(np.float32)) | |
| return d * (np.float32(0.5) + s.astype(np.float32)) * np.float32(fmt.a) | |
| def values(fmt: Format, blocks: np.ndarray, gguf) -> np.ndarray: | |
| """int8 [n, block]: each weight's v (codebook entry × sign), decoded by gguf-py with d = 1 and a neutral s.""" | |
| b = blocks.copy() | |
| set_d(b, np.ones(b.shape[0], np.float32)) | |
| set_s(fmt, b, np.full((b.shape[0], fmt.block // fmt.sub if fmt.name != "IQ4_NL" else 0), fmt.neutral_s)) | |
| v = gguf.quants.dequantize(b, getattr(gguf.GGMLQuantizationType, fmt.name)).reshape(b.shape[0], fmt.block) | |
| v = v / np.float32(fmt.unit) | |
| q = np.rint(v) | |
| if not np.array_equal(q, v) or np.abs(q).max() > 127: | |
| raise ValueError(f"{fmt.name}: decoded values are not small integers") | |
| return q.astype(np.int8) | |
| # ---------------------------------------------------------------- the trainable form (torch) | |
| def round_fp16(x: torch.Tensor) -> torch.Tensor: | |
| """x rounded to fp16 (nearest even), as fp32, with the plain STE.""" | |
| return x + (x.half().float() - x).detach() | |
| def round_s(x: torch.Tensor, s_max: int) -> torch.Tensor: | |
| return x + (x.round().clamp(0, s_max) - x).detach() | |
| def eff_torch(fmt: Format, d: torch.Tensor, s: torch.Tensor) -> torch.Tensor: | |
| """d [..., nblk], s [..., nblk·block/sub] (both already on their grids) → eff per sub-block, fp32.""" | |
| k = fmt.block // fmt.sub | |
| dd = d.unsqueeze(-1).expand(*d.shape, k).flatten(-2) | |
| if fmt.name == "IQ4_NL": | |
| return dd | |
| if fmt.name == "IQ4_XS": | |
| return dd * (s - 32.0) | |
| if fmt.name == "IQ3_S": | |
| return dd * (1.0 + 2.0 * s) | |
| return dd * (0.5 + s) * fmt.a | |
| def weight(fmt: Format, v: torch.Tensor, d0: torch.Tensor, dlog: torch.Tensor, slat: torch.Tensor | None | |
| ) -> torch.Tensor: | |
| """The student's fp32 weight [..., C] from int8 v [..., C], d0 / dlog [..., C/block], slat [..., C/sub].""" | |
| d = round_fp16(d0 * torch.exp(dlog)) | |
| s = round_s(slat, fmt.s_max) if slat is not None else None | |
| e = eff_torch(fmt, d, s) | |
| return (v.float().unflatten(-1, (-1, fmt.sub)) * e.unsqueeze(-1)).flatten(-2) | |
| def trained_fields(fmt: Format, d0: torch.Tensor, dlog: torch.Tensor, slat: torch.Tensor | None | |
| ) -> tuple[np.ndarray, np.ndarray]: | |
| """(fp16 d [..., nblk], uint8 s [..., nsub]) exactly as `weight` uses them.""" | |
| d = (d0 * torch.exp(dlog)).half().cpu().numpy() | |
| s = (slat.round().clamp(0, fmt.s_max).to(torch.uint8).cpu().numpy() if slat is not None | |
| else np.zeros((*d.shape[:-1], 0), np.uint8)) | |
| return d, s | |
| def dequant(fmt: Format, blocks: np.ndarray, v: np.ndarray) -> np.ndarray: | |
| """fp32 weights [..., cols] from blocks [..., nblk, nbytes] and their v [..., cols] (no gguf-py needed).""" | |
| flat = blocks.reshape(-1, fmt.nbytes) | |
| e = eff_np(fmt, get_d(flat), get_s(fmt, flat)).reshape(*v.shape[:-1], -1) | |
| return (v.astype(np.float32).reshape(*v.shape[:-1], -1, fmt.sub) * e[..., None]).reshape(v.shape) | |
| def write_blocks(fmt: Format, blocks: np.ndarray, d: np.ndarray, s: np.ndarray) -> np.ndarray: | |
| """A copy of `blocks` [..., nblk, nbytes] with d [..., nblk] and s [..., nsub] written in.""" | |
| out = blocks.reshape(-1, fmt.nbytes).copy() | |
| set_d(out, d.reshape(-1)) | |
| if fmt.name != "IQ4_NL": | |
| set_s(fmt, out, s.reshape(out.shape[0], -1)) | |
| return out.reshape(blocks.shape) | |
| # ---------------------------------------------------------------- free codes (D-58's second mode) | |
| # | |
| # The codes become trainable too: an fp32 latent per weight, as in `quant.ternary`, starting at llama.cpp's | |
| # dequantised weights. The forward divides the latent by its sub-block's eff, finds the nearest codebook entry for | |
| # each group of k magnitudes (Euclidean, exact fp32), takes the latent's signs, and multiplies back by eff; the | |
| # backward is the plain STE to the latent and the exact gradient to eff (so d and s still train). At the start the | |
| # latent is exactly eff·entry·sign, so every group finds its own entry at distance 0 and the student is llama.cpp's | |
| # file. Formats whose 8 signs are stored in 7 bits (IQ2_XXS, IQ2_XS, IQ3_XXS: an even count of negatives) flip | |
| # the sign of the smallest magnitude in an 8-weight group with an odd count, as llama.cpp's quantiser does. | |
| # Grids come from gguf-py at import (`scripts/cb_import.py` writes grids.safetensors), so training needs no gguf-py. | |
| GROUP = {"IQ3_S": 4, "IQ2_XXS": 8, "IQ4_NL": 1} | |
| EVEN_SIGNS = ("IQ2_XXS",) | |
| _CHUNK = 1 << 20 # groups per nearest-entry matmul ([chunk, entries] fp32 scores) | |
| def _nearest(a: torch.Tensor, grid: torch.Tensor) -> torch.Tensor: | |
| """Index of the nearest grid row (L2) for each row of a [N, k]; grid [M, k] fp32. Ties: the lowest index.""" | |
| half = 0.5 * (grid * grid).sum(-1) | |
| out = torch.empty(a.shape[0], dtype=torch.int64, device=a.device) | |
| for i in range(0, a.shape[0], _CHUNK): | |
| out[i:i + _CHUNK] = (a[i:i + _CHUNK] @ grid.T - half).argmax(-1) | |
| return out | |
| _NEIGHBOURS = 16 | |
| _TABLES: dict = {} | |
| def _lattice(grid: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]: | |
| """(the grid's sorted coordinate values [L], for every point of values^k the indices of its 16 nearest grid | |
| entries [L^k, 16], nearest first), as llama.cpp's quantisers search: round each magnitude to the nearest | |
| coordinate value, then look only at that lattice point's neighbours. Cached per grid tensor.""" | |
| key = (grid.data_ptr(), grid.device) | |
| if key not in _TABLES: | |
| vals = torch.unique(grid) | |
| k = grid.shape[1] | |
| pts = torch.cartesian_prod(*[vals] * k).reshape(-1, k).float() # point p = sum r_i·L^(k-1-i) | |
| d2 = ((pts[:, None, :] - grid[None]) ** 2).sum(-1) | |
| _TABLES[key] = (vals, d2.topk(min(_NEIGHBOURS, grid.shape[0]), dim=-1, largest=False).indices) | |
| return _TABLES[key] | |
| def _nearest_lattice(a: torch.Tensor, grid: torch.Tensor) -> torch.Tensor: | |
| """Nearest grid row for each row of a [N, k] among its rounded lattice point's neighbours (exact for a on the | |
| grid: the point is its own nearest neighbour). Ties: the neighbour listed first.""" | |
| vals, table = _lattice(grid) | |
| L, k = vals.numel(), a.shape[1] | |
| mid = (vals[1:] + vals[:-1]) / 2 | |
| place = L ** torch.arange(k - 1, -1, -1, device=a.device) | |
| out = torch.empty(a.shape[0], dtype=torch.int64, device=a.device) | |
| for i in range(0, a.shape[0], _CHUNK): | |
| c = a[i:i + _CHUNK] | |
| p = (torch.bucketize(c, mid) * place).sum(-1) | |
| cand = table[p] # [n, 16] | |
| d2 = ((c[:, None, :] - grid[cand]) ** 2).sum(-1) | |
| out[i:i + _CHUNK] = cand.gather(1, d2.argmin(-1, keepdim=True))[:, 0] | |
| return out | |
| def free_codes(fmt: Format, x: torch.Tensor, grid: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]: | |
| """x [..., C] (latent / eff, fp32) → (idx [..., C/k] int64, signed values [..., C] fp32) on the format's codebook. | |
| IQ4_NL's grid is its 16 values [16, 1], searched per weight (exactly, by the midpoints) with signs included.""" | |
| if fmt.name == "IQ4_NL": | |
| g = grid[:, 0] | |
| idx = torch.bucketize(x, (g[1:] + g[:-1]) / 2) # ties go to the lower value, as `_nearest`'s argmax | |
| return idx, g[idx] | |
| k = GROUP[fmt.name] | |
| neg = x < 0 | |
| if fmt.name in EVEN_SIGNS: # an odd count of negatives in an 8-group: flip the smallest magnitude | |
| g8 = x.abs().unflatten(-1, (-1, 8)) | |
| odd = (neg.unflatten(-1, (-1, 8)).sum(-1) % 2) == 1 | |
| flip = torch.nn.functional.one_hot(g8.argmin(-1), 8).bool() & odd.unsqueeze(-1) | |
| neg = neg ^ flip.flatten(-2) | |
| a = x.abs().reshape(-1, k) | |
| idx = _nearest_lattice(a, grid) | |
| vals = grid[idx].reshape(x.shape) | |
| return idx.reshape(*x.shape[:-1], -1), torch.where(neg, -vals, vals) | |
| def weight_free(fmt: Format, latent: torch.Tensor, d0: torch.Tensor, dlog: torch.Tensor, | |
| slat: torch.Tensor | None, grid: torch.Tensor) -> torch.Tensor: | |
| """The free-code student's fp32 weight [..., C]: eff · (nearest entry × sign), STE to the latent.""" | |
| d = round_fp16(d0 * torch.exp(dlog)) | |
| s = round_s(slat, fmt.s_max) if slat is not None else None | |
| e = eff_torch(fmt, d, s).unsqueeze(-1) # [..., nsub, 1] | |
| lat = latent.unflatten(-1, (-1, fmt.sub)) | |
| with torch.no_grad(): | |
| _, vals = free_codes(fmt, (lat / e.detach()).flatten(-2), grid) | |
| w = e * vals.unflatten(-1, (-1, fmt.sub)) # gradient to eff (d, s) through the chosen values | |
| return (w + (lat - lat.detach())).flatten(-2) # and the plain STE to the latent | |
| def encode(fmt: Format, idx: torch.Tensor, vals: torch.Tensor, d: np.ndarray, s: np.ndarray) -> np.ndarray: | |
| """Blocks uint8 [..., nblk, nbytes] from codebook indices, signed values (for the signs), d and s.""" | |
| lead = vals.shape[:-1] | |
| n = int(np.prod(lead)) * (vals.shape[-1] // fmt.block) | |
| neg = (vals < 0).reshape(n, fmt.block).cpu().numpy() | |
| out = np.zeros((n, fmt.nbytes), np.uint8) | |
| set_d(out, d.reshape(-1)) | |
| if fmt.name == "IQ4_NL": | |
| q = idx.reshape(n, 32).cpu().numpy().astype(np.uint8) | |
| out[:, 2:18] = q[:, :16] | (q[:, 16:] << 4) | |
| return out.reshape(*lead, -1, fmt.nbytes) | |
| gi = idx.reshape(n, -1).cpu().numpy() | |
| if fmt.name == "IQ3_S": | |
| out[:, 2:66] = (gi & 0xFF).astype(np.uint8) | |
| out[:, 66:74] = np.packbits(((gi >> 8) & 1).astype(np.uint8), axis=-1, bitorder="little") | |
| out[:, 74:106] = np.packbits(neg.astype(np.uint8), axis=-1, bitorder="little") | |
| elif fmt.name == "IQ2_XXS": | |
| pat = np.packbits(neg.astype(np.uint8).reshape(n, 32, 8), axis=-1, bitorder="little")[..., 0] # [n, 32] | |
| if (np.unpackbits(pat[..., None], axis=-1).sum(-1) % 2).any(): | |
| raise ValueError("IQ2_XXS sign groups must have an even count of negatives") | |
| sidx = (pat & 0x7F).astype(np.uint32).reshape(n, 8, 4) | |
| aux1 = (sidx << np.array([0, 7, 14, 21], np.uint32)).sum(-1).astype(np.uint32) # [n, 8] | |
| aux0 = gi.astype(np.uint8).reshape(n, 8, 4).copy().view("<u4")[..., 0] | |
| out[:, 2:66] = np.stack([aux0, aux1], -1).astype("<u4").view(np.uint8).reshape(n, 64) | |
| else: | |
| raise NotImplementedError(f"free codes for {fmt.name}") | |
| set_s(fmt, out, s.reshape(n, -1)) | |
| return out.reshape(*lead, -1, fmt.nbytes) | |
| def grid_from_gguf(fmt: Format, gguf) -> torch.Tensor: | |
| """The format's codebook as fp32 [entries, k] (IQ4_NL and IQ4_XS: their 16 values as [16, 1]).""" | |
| if fmt.name in ("IQ4_NL", "IQ4_XS"): | |
| from lowbit.quant.ternary import IQ4NL_VALUES | |
| return torch.tensor(IQ4NL_VALUES, dtype=torch.float32)[:, None] | |
| cls = getattr(gguf.quants, fmt.name) | |
| cls.init_grid() | |
| return torch.from_numpy(np.ascontiguousarray(cls.grid.reshape(cls.grid_shape), dtype=np.float32)) | |