turboquant-llm 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,11 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .pytest_cache/
4
+ .ruff_cache/
5
+ .mypy_cache/
6
+ dist/
7
+ build/
8
+ *.egg-info/
9
+ .venv/
10
+ venv/
11
+ .env
@@ -0,0 +1,120 @@
1
+ Metadata-Version: 2.4
2
+ Name: turboquant-llm
3
+ Version: 0.1.0
4
+ Summary: Reference implementation of TurboQuant-style vector/KV quantization (rotation + scalar quant + QJL residual).
5
+ Project-URL: Homepage, https://github.com/PestanaRobson/turboquant_llm
6
+ Project-URL: Documentation, https://github.com/PestanaRobson/turboquant_llm#readme
7
+ Project-URL: Repository, https://github.com/PestanaRobson/turboquant_llm
8
+ Project-URL: Issues, https://github.com/PestanaRobson/turboquant_llm/issues
9
+ License-Expression: Apache-2.0
10
+ Keywords: kv-cache,llm,quantization,turboquant,vector-quantization
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: License :: OSI Approved :: Apache Software License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
19
+ Requires-Python: >=3.10
20
+ Requires-Dist: numpy>=1.22
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest>=7.0; extra == 'dev'
23
+ Requires-Dist: ruff>=0.4; extra == 'dev'
24
+ Provides-Extra: torch
25
+ Requires-Dist: torch>=2.0; extra == 'torch'
26
+ Description-Content-Type: text/markdown
27
+
28
+ # turboquant_llm
29
+
30
+ Implementação de **referência** em NumPy do fluxo estilo **TurboQuant**: rotação ortogonal → quantização escalar → estágio **QJL** (sinais 1-bit da projeção JL do resíduo). Serve para experimentos, testes e desenho de API; **não** substitui kernels CUDA/Triton em produção.
31
+
32
+ No PyPI o nome do pacote é **`turboquant-llm`** (hífen); em código use **`import turboquant_llm`**. Código-fonte: [github.com/PestanaRobson/turboquant_llm](https://github.com/PestanaRobson/turboquant_llm).
33
+
34
+ > Os nomes `turboquant` e `turboquant-kv` já existem no PyPI como outros projetos.
35
+
36
+ ## Por que TurboQuant importa (o ganho real)
37
+
38
+ Em LLMs com **contexto longo**, o **cache KV** (chaves e valores guardados por token para não recalcular atenção) domina **memória** e **largura de banda** entre HBM e compute — é um gargalo clássico de servir modelo grande com muitos tokens.
39
+
40
+ O método **TurboQuant** ([paper](https://arxiv.org/abs/2504.19874), [blog Google Research](https://research.google/blog/turboquant-redefining-ai-efficiency-with-extreme-compression/)) foi desenhado para **comprimir vetores de alta dimensão** usados nesse caminho (em especial **K/V** e produtos internos em atenção) com quantização **online** e **sem calibração em dataset** (“data-oblivious”): não exige coletar estatísticas do seu domínio para treinar codebooks.
41
+
42
+ **Resultados reportados pelo Google** (ambiente e modelo nos artigos; números indicativos, não garantidos para o seu hardware ou checkpoint):
43
+
44
+ | Eixo | O que se ganha (ordem de grandeza) |
45
+ |------|-------------------------------------|
46
+ | **Memória do KV** | Redução da ordem de **~6×** no footprint do cache KV em relação a representações densas não comprimidas nos experimentos citados. |
47
+ | **Velocidade da atenção** | Até **~8×** de aceleração no cálculo de **logits de atenção** vs. chaves em precisão total (ex.: FP32), em GPU **H100** e implementação otimizada (baseline JAX no blog). |
48
+ | **Qualidade** | No paper: **~3,5 bits por canal** com neutralidade de qualidade forte; **~2,5** com degradação marginal. No blog: quantização do KV na ordem de **~3 bits** mantendo desempenho em benchmarks longos (LongBench, needle-in-haystack, etc.). |
49
+ | **Overhead de quantização** | Evita o custo de armazenar **constantes de quantização completas por bloco** como em muitos esquemas clássicos — parte central do argumento de eficiência do TurboQuant. |
50
+
51
+ **O que *esta* biblioteca faz:** reproduz a **lógica** (rotação + quantização + estágio tipo QJL) para pesquisa e integração futura. **Não** reproduz sozinha os números de H100 nem a memória real do servidor — isso exige **kernels GPU** e encaixe no **motor de inferência** (vLLM, TensorRT-LLM, etc.). Os ganhos acima são o **alvo** do algoritmo publicado; o caminho até lá é integração nativa no stack de KV.
52
+
53
+ ## Instalação
54
+
55
+ ```bash
56
+ pip install turboquant-llm
57
+ ```
58
+
59
+ Opcional (integrações futuras com modelos):
60
+
61
+ ```bash
62
+ pip install turboquant-llm[torch]
63
+ ```
64
+
65
+ ## Uso rápido
66
+
67
+ ```python
68
+ import numpy as np
69
+ from turboquant_llm import TurboQuantCompressor, TurboQuantConfig
70
+
71
+ dim = 128
72
+ x = np.random.default_rng(0).standard_normal((10, dim))
73
+
74
+ comp = TurboQuantCompressor(
75
+ dim,
76
+ TurboQuantConfig(bits_main=4, qjl_projection_dim=64, seed=0),
77
+ )
78
+ batch = comp.compress(x)
79
+ x_hat = comp.reconstruct_first_stage(batch) # ignora o estágio QJL no resíduo
80
+ ```
81
+
82
+ ## Publicar no PyPI
83
+
84
+ Repositório: [PestanaRobson/turboquant_llm](https://github.com/PestanaRobson/turboquant_llm). Envie o conteúdo deste diretório para o branch principal (`git push`).
85
+
86
+ 1. Com [API token](https://pypi.org/manage/account/token/) do PyPI:
87
+
88
+ ```bash
89
+ python -m pip install build twine
90
+ python -m build
91
+ python -m twine upload dist/*
92
+ ```
93
+
94
+ Teste antes no [TestPyPI](https://test.pypi.org/).
95
+
96
+ ## Gemma 4 e Qwen 3.x: melhor caminho com este tipo de quantização
97
+
98
+ TurboQuant (no paper e no blog do Google Research) ataca sobretudo **memória e custo do cache KV** e produtos internos em atenção — não é só “exportar pesos em INT4”.
99
+
100
+ 1. **Hoje (ecossistema Hugging Face / vLLM)**
101
+ Integração “de verdade” costuma exigir **suporte no motor de inferência** (vLLM, SGLang, TensorRT-LLM, etc.) ou um **módulo de atenção customizado** que, a cada passo, comprima/descomprima **K/V por cabeça** com a mesma rotação e parâmetros fixos por modelo/camada.
102
+
103
+ 2. **Gemma (ex. Gemma 3 / família Gemma no Hub)** e **Qwen3**
104
+ - Confirme `hidden_size`, **GQA** (`num_key_value_heads`), `head_dim` no `config.json` do checkpoint.
105
+ - A lib aqui opera em vetores de tamanho `dim` (= `head_dim` por cabeça, ou blocos que vocês definirem).
106
+ - Caminho pragmático: usar este pacote para **benchmark offline** (distorsão, memória simulada); em seguida portar o núcleo para **PyTorch custom op** ou **Triton** no caminho de escrita/leitura do KV.
107
+
108
+ 3. **Ordem sugerida**
109
+ - Prototipar com esta API em **uma camada** ou tensor KV sintético.
110
+ - Medir qualidade (long context / needle) com `bits_main` ~3–4 como no material de referência.
111
+ - Só então acoplar ao servidor (vLLM plugin / patch) para latência real.
112
+
113
+ ## Licença
114
+
115
+ Apache-2.0
116
+
117
+ ## Referências
118
+
119
+ - [TurboQuant (arXiv)](https://arxiv.org/abs/2504.19874)
120
+ - [Google Research blog](https://research.google/blog/turboquant-redefining-ai-efficiency-with-extreme-compression/)
@@ -0,0 +1,93 @@
1
+ # turboquant_llm
2
+
3
+ Implementação de **referência** em NumPy do fluxo estilo **TurboQuant**: rotação ortogonal → quantização escalar → estágio **QJL** (sinais 1-bit da projeção JL do resíduo). Serve para experimentos, testes e desenho de API; **não** substitui kernels CUDA/Triton em produção.
4
+
5
+ No PyPI o nome do pacote é **`turboquant-llm`** (hífen); em código use **`import turboquant_llm`**. Código-fonte: [github.com/PestanaRobson/turboquant_llm](https://github.com/PestanaRobson/turboquant_llm).
6
+
7
+ > Os nomes `turboquant` e `turboquant-kv` já existem no PyPI como outros projetos.
8
+
9
+ ## Por que TurboQuant importa (o ganho real)
10
+
11
+ Em LLMs com **contexto longo**, o **cache KV** (chaves e valores guardados por token para não recalcular atenção) domina **memória** e **largura de banda** entre HBM e compute — é um gargalo clássico de servir modelo grande com muitos tokens.
12
+
13
+ O método **TurboQuant** ([paper](https://arxiv.org/abs/2504.19874), [blog Google Research](https://research.google/blog/turboquant-redefining-ai-efficiency-with-extreme-compression/)) foi desenhado para **comprimir vetores de alta dimensão** usados nesse caminho (em especial **K/V** e produtos internos em atenção) com quantização **online** e **sem calibração em dataset** (“data-oblivious”): não exige coletar estatísticas do seu domínio para treinar codebooks.
14
+
15
+ **Resultados reportados pelo Google** (ambiente e modelo nos artigos; números indicativos, não garantidos para o seu hardware ou checkpoint):
16
+
17
+ | Eixo | O que se ganha (ordem de grandeza) |
18
+ |------|-------------------------------------|
19
+ | **Memória do KV** | Redução da ordem de **~6×** no footprint do cache KV em relação a representações densas não comprimidas nos experimentos citados. |
20
+ | **Velocidade da atenção** | Até **~8×** de aceleração no cálculo de **logits de atenção** vs. chaves em precisão total (ex.: FP32), em GPU **H100** e implementação otimizada (baseline JAX no blog). |
21
+ | **Qualidade** | No paper: **~3,5 bits por canal** com neutralidade de qualidade forte; **~2,5** com degradação marginal. No blog: quantização do KV na ordem de **~3 bits** mantendo desempenho em benchmarks longos (LongBench, needle-in-haystack, etc.). |
22
+ | **Overhead de quantização** | Evita o custo de armazenar **constantes de quantização completas por bloco** como em muitos esquemas clássicos — parte central do argumento de eficiência do TurboQuant. |
23
+
24
+ **O que *esta* biblioteca faz:** reproduz a **lógica** (rotação + quantização + estágio tipo QJL) para pesquisa e integração futura. **Não** reproduz sozinha os números de H100 nem a memória real do servidor — isso exige **kernels GPU** e encaixe no **motor de inferência** (vLLM, TensorRT-LLM, etc.). Os ganhos acima são o **alvo** do algoritmo publicado; o caminho até lá é integração nativa no stack de KV.
25
+
26
+ ## Instalação
27
+
28
+ ```bash
29
+ pip install turboquant-llm
30
+ ```
31
+
32
+ Opcional (integrações futuras com modelos):
33
+
34
+ ```bash
35
+ pip install turboquant-llm[torch]
36
+ ```
37
+
38
+ ## Uso rápido
39
+
40
+ ```python
41
+ import numpy as np
42
+ from turboquant_llm import TurboQuantCompressor, TurboQuantConfig
43
+
44
+ dim = 128
45
+ x = np.random.default_rng(0).standard_normal((10, dim))
46
+
47
+ comp = TurboQuantCompressor(
48
+ dim,
49
+ TurboQuantConfig(bits_main=4, qjl_projection_dim=64, seed=0),
50
+ )
51
+ batch = comp.compress(x)
52
+ x_hat = comp.reconstruct_first_stage(batch) # ignora o estágio QJL no resíduo
53
+ ```
54
+
55
+ ## Publicar no PyPI
56
+
57
+ Repositório: [PestanaRobson/turboquant_llm](https://github.com/PestanaRobson/turboquant_llm). Envie o conteúdo deste diretório para o branch principal (`git push`).
58
+
59
+ 1. Com [API token](https://pypi.org/manage/account/token/) do PyPI:
60
+
61
+ ```bash
62
+ python -m pip install build twine
63
+ python -m build
64
+ python -m twine upload dist/*
65
+ ```
66
+
67
+ Teste antes no [TestPyPI](https://test.pypi.org/).
68
+
69
+ ## Gemma 4 e Qwen 3.x: melhor caminho com este tipo de quantização
70
+
71
+ TurboQuant (no paper e no blog do Google Research) ataca sobretudo **memória e custo do cache KV** e produtos internos em atenção — não é só “exportar pesos em INT4”.
72
+
73
+ 1. **Hoje (ecossistema Hugging Face / vLLM)**
74
+ Integração “de verdade” costuma exigir **suporte no motor de inferência** (vLLM, SGLang, TensorRT-LLM, etc.) ou um **módulo de atenção customizado** que, a cada passo, comprima/descomprima **K/V por cabeça** com a mesma rotação e parâmetros fixos por modelo/camada.
75
+
76
+ 2. **Gemma (ex. Gemma 3 / família Gemma no Hub)** e **Qwen3**
77
+ - Confirme `hidden_size`, **GQA** (`num_key_value_heads`), `head_dim` no `config.json` do checkpoint.
78
+ - A lib aqui opera em vetores de tamanho `dim` (= `head_dim` por cabeça, ou blocos que vocês definirem).
79
+ - Caminho pragmático: usar este pacote para **benchmark offline** (distorsão, memória simulada); em seguida portar o núcleo para **PyTorch custom op** ou **Triton** no caminho de escrita/leitura do KV.
80
+
81
+ 3. **Ordem sugerida**
82
+ - Prototipar com esta API em **uma camada** ou tensor KV sintético.
83
+ - Medir qualidade (long context / needle) com `bits_main` ~3–4 como no material de referência.
84
+ - Só então acoplar ao servidor (vLLM plugin / patch) para latência real.
85
+
86
+ ## Licença
87
+
88
+ Apache-2.0
89
+
90
+ ## Referências
91
+
92
+ - [TurboQuant (arXiv)](https://arxiv.org/abs/2504.19874)
93
+ - [Google Research blog](https://research.google/blog/turboquant-redefining-ai-efficiency-with-extreme-compression/)
@@ -0,0 +1,52 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "turboquant-llm"
7
+ version = "0.1.0"
8
+ description = "Reference implementation of TurboQuant-style vector/KV quantization (rotation + scalar quant + QJL residual)."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "Apache-2.0"
12
+ keywords = ["quantization", "llm", "kv-cache", "turboquant", "vector-quantization"]
13
+ classifiers = [
14
+ "Development Status :: 3 - Alpha",
15
+ "Intended Audience :: Science/Research",
16
+ "License :: OSI Approved :: Apache Software License",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.10",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
22
+ ]
23
+ dependencies = [
24
+ "numpy>=1.22",
25
+ ]
26
+
27
+ [project.optional-dependencies]
28
+ torch = ["torch>=2.0"]
29
+ dev = [
30
+ "pytest>=7.0",
31
+ "ruff>=0.4",
32
+ ]
33
+
34
+ [project.urls]
35
+ Homepage = "https://github.com/PestanaRobson/turboquant_llm"
36
+ Documentation = "https://github.com/PestanaRobson/turboquant_llm#readme"
37
+ Repository = "https://github.com/PestanaRobson/turboquant_llm"
38
+ Issues = "https://github.com/PestanaRobson/turboquant_llm/issues"
39
+
40
+ [tool.hatch.build.targets.sdist]
41
+ include = ["/src", "/tests", "/README.md"]
42
+
43
+ [tool.hatch.build.targets.wheel]
44
+ packages = ["src/turboquant_llm"]
45
+
46
+ [tool.pytest.ini_options]
47
+ testpaths = ["tests"]
48
+ pythonpath = ["src"]
49
+
50
+ [tool.ruff]
51
+ line-length = 100
52
+ target-version = "py310"
@@ -0,0 +1,3 @@
1
+ Nota interna (não é documentação voltada ao usuário; pode ser removida do wheel em builds futuros se preferir).
2
+
3
+ Histórico: o trabalho foi iniciado no contexto Pestana Labs. O nome de distribuição e a marca voltados ao público são turboquant-llm / import turboquant_llm.
@@ -0,0 +1,11 @@
1
+ """TurboQuant-style vector quantization (reference implementation)."""
2
+
3
+ from turboquant_llm.compressor import CompressedBatch, TurboQuantCompressor
4
+ from turboquant_llm.config import TurboQuantConfig
5
+
6
+ __all__ = [
7
+ "CompressedBatch",
8
+ "TurboQuantCompressor",
9
+ "TurboQuantConfig",
10
+ ]
11
+ __version__ = "0.1.0"
@@ -0,0 +1,96 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ import numpy as np
6
+
7
+ from turboquant_llm.config import TurboQuantConfig
8
+ from turboquant_llm.rotation import random_orthogonal_matrix
9
+ from turboquant_llm.scalar_quant import dequantize_global, quantize_global
10
+
11
+
12
+ @dataclass
13
+ class CompressedBatch:
14
+ """Compressed representation for a batch of row vectors ``x`` with shape ``(n, dim)``."""
15
+
16
+ # Stage 1: rotated + scalar-quantized
17
+ codes_main: np.ndarray
18
+ scale_main: float
19
+ amin_main: float
20
+ amax_main: float
21
+ # Stage 2: QJL signs on residual (columns are projection dimensions)
22
+ qjl_signs: np.ndarray | None
23
+ # Fixed per compressor instance (not stored per batch in a real system — kept for round-trip tests)
24
+ rotation: np.ndarray
25
+ jl_matrix: np.ndarray
26
+
27
+
28
+ class TurboQuantCompressor:
29
+ """Reference TurboQuant-style pipeline: rotate → scalar quantize → 1-bit JL signs on residual.
30
+
31
+ This is intended for experiments and API design, not a drop-in production KV kernel.
32
+ Shapes: ``x`` is ``(..., dim)``; the last dimension must match ``dim``.
33
+ """
34
+
35
+ def __init__(self, dim: int, config: TurboQuantConfig | None = None) -> None:
36
+ if dim < 2:
37
+ raise ValueError("dim must be >= 2")
38
+ self.dim = dim
39
+ self.config = config or TurboQuantConfig()
40
+ rng = np.random.default_rng(self.config.seed)
41
+ self._R = random_orthogonal_matrix(dim, rng)
42
+ k = self.config.qjl_projection_dim
43
+ if k > 0:
44
+ # Typical JL scaling: entries ~ N(0, 1/dim)
45
+ self._J = rng.standard_normal((k, dim)) / np.sqrt(dim)
46
+ else:
47
+ self._J = np.zeros((0, dim), dtype=np.float64)
48
+
49
+ @property
50
+ def rotation(self) -> np.ndarray:
51
+ return self._R
52
+
53
+ @property
54
+ def jl_matrix(self) -> np.ndarray:
55
+ return self._J
56
+
57
+ def compress(self, x: np.ndarray) -> CompressedBatch:
58
+ x = np.asarray(x, dtype=np.float64)
59
+ if x.shape[-1] != self.dim:
60
+ raise ValueError(f"last dimension must be {self.dim}, got {x.shape[-1]}")
61
+ flat = x.reshape(-1, self.dim)
62
+ # Row vectors: x_rot = x @ R (maps through orthonormal columns of R)
63
+ xr = flat @ self._R
64
+
65
+ codes, sc, lo, hi = quantize_global(xr, bits=self.config.bits_main)
66
+ recon = dequantize_global(codes, sc, lo, hi)
67
+ residual = xr - recon
68
+
69
+ if self._J.shape[0] == 0:
70
+ signs = None
71
+ else:
72
+ # (n, k) — store 1-bit signs of JL projection of residual rows
73
+ proj = residual @ self._J.T
74
+ signs = (proj >= 0).astype(np.uint8)
75
+
76
+ return CompressedBatch(
77
+ codes_main=codes.reshape(x.shape),
78
+ scale_main=sc,
79
+ amin_main=lo,
80
+ amax_main=hi,
81
+ qjl_signs=signs.reshape(x.shape[:-1] + (self._J.shape[0],)) if signs is not None else None,
82
+ rotation=self._R,
83
+ jl_matrix=self._J,
84
+ )
85
+
86
+ def reconstruct_first_stage(self, batch: CompressedBatch) -> np.ndarray:
87
+ """Reconstruct after rotation + inverse quant (ignores QJL residual)."""
88
+ recon_flat = dequantize_global(
89
+ batch.codes_main.reshape(-1, self.dim),
90
+ batch.scale_main,
91
+ batch.amin_main,
92
+ batch.amax_main,
93
+ )
94
+ xr_hat = recon_flat
95
+ x_hat = xr_hat @ self._R.T
96
+ return x_hat.reshape(batch.codes_main.shape)
@@ -0,0 +1,22 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+
6
+ @dataclass(frozen=True)
7
+ class TurboQuantConfig:
8
+ """Hyperparameters for the reference compressor (aligned with the public TurboQuant narrative).
9
+
10
+ - ``bits_main``: scalar quantization after rotation (e.g. 3–4 for strong compression).
11
+ - ``qjl_bits``: extra sign bits from the JL projection of the *residual* (QJL-style stage).
12
+ """
13
+
14
+ bits_main: int = 4
15
+ qjl_projection_dim: int = 64
16
+ seed: int = 0
17
+
18
+ def __post_init__(self) -> None:
19
+ if self.bits_main < 1:
20
+ raise ValueError("bits_main must be >= 1")
21
+ if self.qjl_projection_dim < 0:
22
+ raise ValueError("qjl_projection_dim must be >= 0")
File without changes
@@ -0,0 +1,14 @@
1
+ from __future__ import annotations
2
+
3
+ import numpy as np
4
+
5
+
6
+ def random_orthogonal_matrix(dim: int, rng: np.random.Generator) -> np.ndarray:
7
+ """Haar-random orthogonal matrix via QR (columns orthonormal). Shape (dim, dim)."""
8
+ a = rng.standard_normal((dim, dim))
9
+ q, _ = np.linalg.qr(a, mode="complete")
10
+ # QR is unique up to sign; stabilize column signs for reproducibility across platforms
11
+ s = np.sign(np.diag(q))
12
+ s[s == 0] = 1.0
13
+ q = q * s
14
+ return q.astype(np.float64, copy=False)
@@ -0,0 +1,33 @@
1
+ from __future__ import annotations
2
+
3
+ import numpy as np
4
+
5
+
6
+ def quantize_global(
7
+ x: np.ndarray,
8
+ *,
9
+ bits: int,
10
+ ) -> tuple[np.ndarray, float, float, float]:
11
+ """Min/max uniform quantization over all elements of ``x``.
12
+
13
+ Integer codes are in ``[0, 2**bits - 1]``. Reconstruction:
14
+ ``x_hat = amin + codes * scale``.
15
+ """
16
+ if bits < 1:
17
+ raise ValueError("bits must be >= 1")
18
+ n_levels = 2**bits
19
+ eps = 1e-8
20
+ amin = float(np.min(x))
21
+ amax = float(np.max(x))
22
+ if amax - amin < eps:
23
+ scale = 1.0
24
+ codes = np.zeros(x.shape, dtype=np.int64)
25
+ return codes, scale, amin, amax
26
+ scale = (amax - amin) / (n_levels - 1)
27
+ codes = np.rint((x - amin) / scale).clip(0, n_levels - 1).astype(np.int64)
28
+ return codes, scale, amin, amax
29
+
30
+
31
+ def dequantize_global(codes: np.ndarray, scale: float, amin: float, amax: float) -> np.ndarray:
32
+ _ = amax # kept for API symmetry / future per-channel max
33
+ return amin + codes.astype(np.float64) * scale
@@ -0,0 +1,28 @@
1
+ from __future__ import annotations
2
+
3
+ import numpy as np
4
+
5
+ from turboquant_llm import TurboQuantCompressor, TurboQuantConfig
6
+
7
+
8
+ def test_compress_roundtrip_first_stage_small_dim() -> None:
9
+ dim = 16
10
+ rng = np.random.default_rng(42)
11
+ x = rng.standard_normal((4, dim))
12
+ cfg = TurboQuantConfig(bits_main=8, qjl_projection_dim=0, seed=0)
13
+ comp = TurboQuantCompressor(dim, cfg)
14
+ batch = comp.compress(x)
15
+ x_hat = comp.reconstruct_first_stage(batch)
16
+ err = np.linalg.norm(x - x_hat) / (np.linalg.norm(x) + 1e-8)
17
+ assert err < 0.15
18
+
19
+
20
+ def test_qjl_signs_shape() -> None:
21
+ dim = 32
22
+ k = 12
23
+ x = np.random.default_rng(0).standard_normal((2, 3, dim))
24
+ cfg = TurboQuantConfig(bits_main=4, qjl_projection_dim=k, seed=1)
25
+ comp = TurboQuantCompressor(dim, cfg)
26
+ b = comp.compress(x)
27
+ assert b.qjl_signs is not None
28
+ assert b.qjl_signs.shape == (2, 3, k)