Metadata-Version: 2.4
Name: tiny-turboquant
Version: 0.13.3
Summary: Pip-installable compressed KV serving backend and diagnostics for PyTorch
Author: Pradeep Boopathy
License-Expression: MIT
Project-URL: Homepage, https://github.com/pradeepboopathy/tiny-turboquant
Project-URL: Repository, https://github.com/pradeepboopathy/tiny-turboquant
Project-URL: Issues, https://github.com/pradeepboopathy/tiny-turboquant/issues
Keywords: quantization,kv-cache,llm,compression,vector-search,pytorch,rag,transformers
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: torch>=2.1
Requires-Dist: numpy>=1.24
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: build>=1.2; extra == "dev"
Requires-Dist: twine>=5.0; extra == "dev"
Requires-Dist: ruff>=0.5; extra == "dev"
Provides-Extra: demos
Requires-Dist: matplotlib>=3.7; extra == "demos"
Requires-Dist: faiss-cpu>=1.7.4; extra == "demos"
Requires-Dist: sentence-transformers>=2.6; extra == "demos"
Provides-Extra: hf
Requires-Dist: transformers>=4.40; extra == "hf"
Requires-Dist: accelerate>=0.30; extra == "hf"
Requires-Dist: safetensors>=0.4; extra == "hf"
Provides-Extra: server
Requires-Dist: transformers>=4.40; extra == "server"
Requires-Dist: accelerate>=0.30; extra == "server"
Requires-Dist: safetensors>=0.4; extra == "server"
Provides-Extra: llm
Requires-Dist: transformers>=4.40; extra == "llm"
Requires-Dist: accelerate>=0.30; extra == "llm"
Requires-Dist: safetensors>=0.4; extra == "llm"
Provides-Extra: all
Requires-Dist: matplotlib>=3.7; extra == "all"
Requires-Dist: faiss-cpu>=1.7.4; extra == "all"
Requires-Dist: sentence-transformers>=2.6; extra == "all"
Requires-Dist: transformers>=4.40; extra == "all"
Requires-Dist: accelerate>=0.30; extra == "all"
Requires-Dist: safetensors>=0.4; extra == "all"
Dynamic: license-file

# tiny-turboquant

`tiny-turboquant` is a pip-installable compressed KV-cache serving backend foundation for Hugging Face decoder models.

The package now has two layers:

1. **Runtime path**: run generation while storing the KV cache compressed between decode steps.
2. **Diagnostic path**: scan real model KV tensors and validate which K/V policies are safe.

The main direction is no longer “add more benchmarks.” The main direction is:

```text
serve first
validate second
scan third
experimental last
```

## Current status

`v0.13.3` is a follow-on validation patch that adds measured Kaggle T4 numbers for the three "last-N" policies shipped in v0.13.2 (`fp16k-int4v-last8-fp16`, `q8k-int4v-last8-q8v`, `fp16k-int4v-boundary-last`). All 24 cells in the sweep produced bit-exact greedy generation; see the [last-N section](#last-n-policy-validation-v0133) below.

`v0.13.2` is the dense-shadow speed-foundation release for the compressed-KV backend:

```text
Hugging Face model -> prefill -> keep fp16 active shadow for decode -> rebuild/report compressed KV store
```

The default `dense-shadow` mode keeps Hugging Face decode close to the stock fp16 path while producing compressed KV storage reports. This is useful for validating compressed-at-rest KV policies, but it is **not** active compressed attention and it is **not** a production speedup claim.

For lower active KV residency, use `--cache-mode compressed`. That mode materializes from compressed storage each step and is expected to be much slower with stock Hugging Face attention.

## What this package does now

- Runs a Hugging Face causal LM through a tiny compressed-KV generation engine.
- Supports two cache modes: `dense-shadow` for fast active decode with compressed-at-rest reporting, and `compressed` for storage-only step-by-step materialization.
- Stores/report `past_key_values` in compressed form and separates compressed-at-rest savings from active decode memory.
- Supports quality-stable KV policies such as `fp16k-q8v` and `boundary-fp16-fp16k-q8v`, plus more aggressive experimental policies.
- Provides an OpenAI-compatible local HTTP server.
- Provides baseline-vs-compressed generation validation with token-match, first-divergence, top-token, and optional logit-delta metrics.
- Keeps the previous real-KV scanner for policy discovery.
- Exposes experimental Sparse-V gating for benchmark experiments; it reports whether SDPA calls were actually intercepted.

## What this package does not claim yet

- It does not claim production vLLM speedup.
- It does not claim llama.cpp parity.
- It does not claim fused compressed attention.
- It does not claim TurboQuant+ kernel parity.
- It does not claim that low-bit V is safe for every model.

The current backend stores KV compressed, then dequantizes before calling the stock Hugging Face attention path. Fused dequantization plus attention is a later milestone.

## Install

Base package:

```bash
pip install tiny-turboquant
```

For Hugging Face serving:

```bash
pip install "tiny-turboquant[hf]"
```

For server usage:

```bash
pip install "tiny-turboquant[server]"
```

For development:

```bash
pip install "tiny-turboquant[dev]"
```

## Quick start

### 1. List available KV policies

```bash
tiny-tq bench --list-policies
```

Current policies:

| Policy | Meaning |
|---|---|
| `fp16` | Dense Hugging Face KV cache baseline |
| `fp16k-q8v` | Quality-first compressed policy: keep K dense, store V as q8 |
| `boundary-fp16-fp16k-q8v` | Boundary-protected V-only compression: first/last 2 layers fp16; middle layers fp16 K + q8 V |
| `boundary-fp16-q8k-q8v` | First/last 2 layers fp16; middle layers q8 K + q8 V |
| `q8k-q8v` | More aggressive q8 K + q8 V storage; can diverge on Qwen-style models |
| `fp16k-int4v` | K fp16, V int4 with boundary protection; experimental |
| `q8k-int4v` | q8 keys, int4 values, boundary-layer protection; experimental |
| `fp16k-int4v-last8-fp16` | int4 V except last 8 layers kept fp16 K/V; experimental last-N protection |
| `q8k-int4v-last8-q8v` | q8 K + int4 V except last 8 layers q8 K/V; experimental last-N protection |
| `fp16k-int4v-boundary-last` | boundary + last-N protected int4 V policy; experimental |
| `q8k-turbo4v` | serving-facing alias for q8 keys + 4-bit value storage |

Start with `fp16k-q8v`. Recent Qwen validation showed it exact-matched a 64-token prompt while saving about 25% KV memory. Use boundary policies or `q8k-q8v` only after validation.

### 2. Run one compressed-KV generation benchmark

```bash
tiny-tq bench \
  --model Qwen/Qwen2.5-0.5B-Instruct \
  --kv-policy fp16k-q8v \
  --prompt "Explain why KV-cache memory grows during long-context decoding." \
  --max-new-tokens 64
```

The output includes:

```text
input_tokens
output_tokens
kv_policy
elapsed_seconds
tokens_per_second
cache_report.compressed_storage_bytes
cache_report.dense_fp16_bytes
cache_report.memory_saved_pct
cache_report.compressed_at_rest_saved_pct
cache_report.active_decode_memory_saved_pct
cache_report.cache_mode
```

### 3. Run the deterministic self-test

Before testing compression, verify that the backend loop is deterministic when both sides use dense KV:

```bash
tiny-tq validate \
  --self-test \
  --model Qwen/Qwen2.5-0.5B-Instruct \
  --prompt "Answer with one word only. Which city is the capital of France?" \
  --max-new-tokens 5 \
  --quality-threshold 1.0
```

Expected verdict: `exact-match`. If this fails, debug the decode loop before testing compression.

### 4. Compare dense KV against compressed KV

```bash
tiny-tq validate \
  --model Qwen/Qwen2.5-0.5B-Instruct \
  --baseline fp16 \
  --candidate fp16k-q8v \
  --prompt "Give a short explanation of KV-cache compression." \
  --max-new-tokens 48 \
  --quality-threshold 0.95 \
  --report-json validate_qwen_safe.json \
  --report-md validate_qwen_safe.md
```

The validation report compares:

```text
baseline generated text
candidate generated text
same prefix token count
first divergence position
token match ratio
prefix match ratio
top-1 token agreement per decode step
optional logit deltas with --collect-logits
baseline cache report
candidate cache report
policy summary
quality verdict
```

For deeper quality debugging:

```bash
tiny-tq validate \
  --model Qwen/Qwen2.5-0.5B-Instruct \
  --baseline fp16 \
  --candidate fp16k-q8v \
  --prompt-file prompts/long_prompt.txt \
  --max-new-tokens 64 \
  --collect-logits \
  --report-json validate_long_safe.json \
  --report-md validate_long_safe.md
```

`--collect-logits` is useful for debugging but can use much more memory because full vocabulary logits are retained for each compared decode step.

Use `--no-per-step` when you want compact validation JSON without the full per-step trace. Summary metrics are still computed.

### 5. Compare multiple policies

Use this after the self-test. It runs several candidate policies against one fp16 baseline and recommends the exact-match compressed policy with the highest memory saving.

```bash
tiny-tq compare-policies \
  --model Qwen/Qwen2.5-0.5B-Instruct \
  --prompt "Explain KV-cache compression in simple terms." \
  --max-new-tokens 64 \
  --summary-only \
  --report-json policy_matrix.json \
  --report-md policy_matrix.md
```

Default policies compared:

```text
fp16
fp16k-q8v
boundary-fp16-fp16k-q8v
boundary-fp16-q8k-q8v
q8k-q8v
q8k-int4v
fp16k-int4v-last8-fp16
q8k-int4v-last8-q8v
fp16k-int4v-boundary-last
```

Recommendation rule:

```text
Choose the exact-match compressed policy with the highest memory saving.
If no compressed policy exact-matches, keep `fp16` as the recommendation and report the best risky compressed candidate separately.
```

### 6. Run the local OpenAI-compatible server

```bash
tiny-tq serve \
  --model Qwen/Qwen2.5-0.5B-Instruct \
  --kv-policy fp16k-q8v \
  --host 127.0.0.1 \
  --port 8000 \
  --max-new-tokens 128
```

Health check:

```bash
curl http://127.0.0.1:8000/health
```

Chat completion:

```bash
curl http://127.0.0.1:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "Qwen/Qwen2.5-0.5B-Instruct",
    "messages": [
      {"role": "user", "content": "Explain KV-cache compression in two sentences."}
    ],
    "max_tokens": 64
  }'
```

Completion:

```bash
curl http://127.0.0.1:8000/v1/completions \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "KV-cache compression helps because",
    "max_tokens": 64
  }'
```

## Python API

```python
from tiny_turboquant.backends.hf import TinyTurboHFEngine

engine = TinyTurboHFEngine(
    "Qwen/Qwen2.5-0.5B-Instruct",
    kv_policy="fp16k-q8v",
    device="auto",
    dtype="auto",
).load()

result = engine.generate(
    "Explain KV-cache compression in simple terms.",
    max_new_tokens=64,
)

print(result.generated_text)
print(result.cache_report)
```

## v0.13.2 speed-foundation release

`validate` reports whether the compressed backend preserves generation behavior, not just whether it saves memory. `compare-policies` compares multiple policies and selects the best exact-match compressed policy. If no compressed policy exact-matches, `fp16` remains the recommendation and the best risky compressed candidate is reported separately. The default compressed policy is `fp16k-q8v`.

Key fields:

| Field | Meaning |
|---|---|
| `quality_verdict` | `exact-match`, `near-stable`, `prefix-stable-then-diverged`, or `diverged` |
| `comparison.first_divergence_index` | First generated token position where baseline and candidate split |
| `comparison.token_match_ratio` | Same-token ratio across aligned generated tokens |
| `comparison.prefix_match_ratio` | Same-prefix length divided by shorter output length |
| `step_comparison.top1_match_ratio` | Agreement between the baseline and candidate top-1 decode choice |
| `logit_comparison` | Optional full-logit delta metrics when `--collect-logits` is used |
| `policy_summary` | Memory saving, compression ratio, and candidate/baseline throughput ratio |
| `policy_recommendation` | Rule-based status, cause hint, and next policy to try |
| `best_risky_policy` | Best compressed policy by token match when no compressed policy exact-matches |
| `unsafe_policies` | Compressed policies that changed the generated token path |

This is still not a quality guarantee. It is a controlled backend validation harness. Perplexity, NIAH-style tests, and fused-kernel throughput come later.

## Measured throughput and quality (v0.13.2)

Sweep across Qwen2.5-1.5B-Instruct and Qwen2.5-3B-Instruct on Kaggle T4, prompts of ~1K / 2K / 4K tokens, 64 decode tokens, greedy. fp16 baseline = 1.00.

**Throughput ratio vs fp16 (active decode):**

| model       | seq | fp16k-q8v | boundary-fp16-fp16k-q8v | boundary-fp16-q8k-q8v | q8k-q8v | q8k-int4v |
|---          |---  |---        |---                      |---                    |---      |---        |
| Qwen2.5-1.5B | 1K | 1.07      | 1.16                    | 1.08                  | 1.06    | 1.10      |
| Qwen2.5-1.5B | 2K | 0.95      | 1.01                    | 0.90                  | 0.88    | 0.91      |
| Qwen2.5-1.5B | 4K | 0.93      | 0.95                    | 0.85                  | 0.82    | 0.88      |
| Qwen2.5-3B   | 1K | 1.12      | 1.15                    | 1.10                  | 1.07    | 1.10      |
| Qwen2.5-3B   | 2K | 0.96      | 0.98                    | 0.90                  | 0.88    | 0.92      |
| Qwen2.5-3B   | 4K | 0.92      | 0.93                    | 0.85                  | 0.84    | 0.88      |

**Storage saving and quality:**

| policy                    | KV bytes saved | quality (greedy, all cells) |
|---                        |---             |---                          |
| fp16k-q8v                 | 22.7%          | exact-token match           |
| boundary-fp16-fp16k-q8v   | 19.4–20.1%     | exact-token match           |
| boundary-fp16-q8k-q8v     | 38.8–40.3%     | exact-token match           |
| q8k-q8v                   | 45.3%          | exact-token match           |
| q8k-int4v                 | 56.0–56.4%     | exact-token match           |

Every cell in the matrix produced bit-exact greedy generation against the fp16 baseline. The minimum throughput ratio anywhere is **0.82**; the recommended `fp16k-q8v` default holds **≥0.92** at every (model, seq-len) tested. The 1K rows showing ratios above 1.0 are most likely warmup variance on short runs and should not be read as a "faster than fp16" claim.

### What the savings mean

The compressed KV store is reported every step, but during active decode the engine holds a fp16 "dense shadow" of the cache (`dense_shadow_during_decode: true` in `cache_report`) so the model runs against its own fp16 past at full speed. The compressed store is rebuilt once at end of decode for accurate `memory_saved_pct`. Memory savings therefore apply **at rest** — between requests, on KV swap-out, in cross-tenant pooling, and for cache serialization — not to peak active-decode resident memory. For workloads that actually pay the KV-storage bill (multi-tenant serving, long-context KV swap), the saving is real; for single-stream peak-memory budgeting, plan around fp16.

To force the slow per-step dequant path (drops shadow, lets you measure the storage-only mode):

```
tiny-tq bench --kv-policy fp16k-q8v --cache-mode compressed ...
```

## Last-N policy validation (v0.13.3)

v0.13.2 shipped three "last-N" policies that preserve the most recent 8 tokens at higher precision (motivated by the observation that recent KVs dominate near-term attention). v0.13.3 measures them on the same matrix (Qwen2.5-{1.5B,3B} on Kaggle T4, ~1K/2K/4K prompts from WikiText-103, 64 decode tokens, greedy). fp16 baseline = 1.00.

**Throughput ratio vs fp16:**

| model        | seq | fp16k-int4v-last8-fp16 | q8k-int4v-last8-q8v | fp16k-int4v-boundary-last |
|---           |---  |---                     |---                  |---                        |
| Qwen2.5-1.5B | 1K  | 1.08                   | 1.02                | 1.13                      |
| Qwen2.5-1.5B | 2K  | 1.07                   | 0.97                | 1.08                      |
| Qwen2.5-1.5B | 4K  | 1.01                   | 0.88                | 1.00                      |
| Qwen2.5-3B   | 1K  | 1.16                   | 1.11                | 1.26                      |
| Qwen2.5-3B   | 2K  | 1.03                   | 0.93                | 1.03                      |
| Qwen2.5-3B   | 4K  | 0.98                   | 0.89                | 0.97                      |

**Storage saving and quality:**

| policy                       | KV bytes saved | quality (greedy, all cells) |
|---                           |---             |---                          |
| fp16k-int4v-last8-fp16       | 25.1–27.3%     | exact-token match           |
| q8k-int4v-last8-q8v          | 54.2–55.0%     | exact-token match           |
| fp16k-int4v-boundary-last    | 29.1–30.4%     | exact-token match           |

All 24 cells produced bit-exact greedy output. `fp16k-int4v-boundary-last` is the new sweet spot when you want both extra recent-token protection (last-8 fp16) and the boundary-fp16 prefix anchor on top of int4 values: it stays within 0.97×–1.26× of fp16 throughput while saving ~30% of KV bytes. The q8k variant trades ~5–12% throughput for the headline ~55% saving and is the right choice when KV bytes — not active speed — is the limit. Numbers may differ slightly from the v0.13.3 table above because the prompts are drawn from a different corpus (WikiText-103 here vs the v0.13.3 internal prompt set); the ratios and exact-match behavior are what generalize.

## How the v0.13 backend works

The backend has two explicit cache modes.

### `dense-shadow` mode — default

This is the speed-friendly validation mode used for the v0.13.3 throughput table.

```text
1. Run the prompt through the model with use_cache=True.
2. Keep the model's fp16 past_key_values as the active decode shadow.
3. Run token-by-token generation using the dense Hugging Face cache path.
4. Rebuild/report the compressed KV store at the end of decode.
5. Report both compressed-at-rest savings and active decode memory fields.
```

This mode validates compressed KV storage policies without paying per-step dequantization cost. It is fast, but active decode still holds a fp16 shadow cache. Its memory savings apply to compressed-at-rest use cases such as cache swap-out, cache serialization, pooling, and post-decode storage.

### `compressed` mode

This is the lower-active-storage experimental path.

```text
1. Run the prompt through the model with use_cache=True.
2. Compress the full prefill past_key_values into TinyTurboKVCache.
3. Before each next-token forward, materialize the model cache from compressed storage.
4. Compress/append the new KV rows when possible.
5. Repeat until max_new_tokens or EOS.
```

This mode proves the compressed cache can drive generation, but it is slow with stock Hugging Face attention because it still materializes dense tensors before attention. Fused compressed attention is the later v0.14 direction.

Use:

```bash
tiny-tq bench --kv-policy fp16k-q8v --cache-mode compressed ...
```

## Why the default is now quality-first

Real backend validation showed that `q8k-q8v` can save about 50% KV memory but still diverge early on Qwen-style generation. The quality-stable default therefore protects attention routing first:

```text
first: fp16 vs fp16 self-test
then: fp16k-q8v as current compressed default
more aggressive: boundary-fp16-q8k-q8v
risky until validated: q8k-q8v, q8k-int4v
```

The key rule is simple: K controls attention routing, so keep K fp16 until a model-specific validation report proves K compression is acceptable.

The package keeps `real-model-kv-scan` so policy decisions can be measured instead of guessed.

Example scan:

```bash
tiny-tq real-model-kv-scan \
  --preset safe \
  --model Qwen/Qwen2.5-0.5B-Instruct \
  --prompt "Long context text..." \
  --max-prompt-tokens 256 \
  --page-size 64 \
  --summary-table \
  --report-json qwen_scan.json \
  --report-md qwen_scan.md
```

## CLI surface

Stable commands:

```text
tiny-tq serve
tiny-tq bench
tiny-tq validate
tiny-tq real-model-kv-scan
tiny-tq kv-estimate
tiny-tq version
```

Older research commands are still present for compatibility, but they should not define the public product direction. A future cleanup release will move old experiments under an experimental namespace.

## Backend roadmap

### v0.13.x

- Add fused dequantization plus attention experiments.
- Add long-context sparse-V prototype.
- Add perplexity and prompt-suite validation.

### v0.14.x

- Explore vLLM adapter.
- Explore llama.cpp/GGUF bridge or export path.
- Keep Python package install simple while making native acceleration optional.

## Development

```bash
git clone https://github.com/pradeepboopathy/tiny-turboquant
cd tiny-turboquant
pip install -e ".[dev,hf]"
pytest
```

Build:

```bash
python -m build
python -m twine check dist/*
```

Upload:

```bash
python -m twine upload dist/*
```

## Design rule

`tiny-turboquant` should be a clean compressed-KV backend, not a pile of experiments.

Experiments are allowed internally. The public path should remain:

```text
serve -> bench -> validate -> scan
```
