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"
File size: 17,183 Bytes
e5235fb | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 | """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
@dataclass(frozen=True)
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
@torch.compiler.disable
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))
|