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.
- turboquant_llm-0.1.0/.gitignore +11 -0
- turboquant_llm-0.1.0/PKG-INFO +120 -0
- turboquant_llm-0.1.0/README.md +93 -0
- turboquant_llm-0.1.0/pyproject.toml +52 -0
- turboquant_llm-0.1.0/src/turboquant_llm/PACKAGE_ORIGIN.txt +3 -0
- turboquant_llm-0.1.0/src/turboquant_llm/__init__.py +11 -0
- turboquant_llm-0.1.0/src/turboquant_llm/compressor.py +96 -0
- turboquant_llm-0.1.0/src/turboquant_llm/config.py +22 -0
- turboquant_llm-0.1.0/src/turboquant_llm/py.typed +0 -0
- turboquant_llm-0.1.0/src/turboquant_llm/rotation.py +14 -0
- turboquant_llm-0.1.0/src/turboquant_llm/scalar_quant.py +33 -0
- turboquant_llm-0.1.0/tests/test_compressor.py +28 -0
|
@@ -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,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)
|