Files
turboquant_plus/tests/test_turboquant.py
c3aad548ff test: achieve 100% code coverage across all modules
Added tests for:
- codebook.py: extreme narrow finite interval fallback, both-infinite pragma
- turboquant.py: compressed_size_bits method
- utils.py: pack_indices invalid bit_width ValueError

133 tests, 100% coverage (330 statements, 0 missed).

Co-Authored-By: tturney@psyguard.ai
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 21:18:46 -05:00

181 lines
6.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Tests for full TurboQuant (Algorithm 2 — PolarQuant + QJL)."""
import numpy as np
import pytest
from turboquant.turboquant import TurboQuant, TurboQuantMSE, CompressedVector
class TestTurboQuantRoundTrip:
"""Full TurboQuant quantize → dequantize correctness."""
@pytest.mark.parametrize("bit_width", [2, 3, 4])
@pytest.mark.parametrize("d", [64, 128, 256])
def test_mse_within_paper_bounds(self, bit_width, d):
"""MSE distortion should be within paper's bounds (Table 2).
Paper bounds are for normalized unit vectors at d→∞.
We allow 3× slack for finite d.
"""
expected_mse = {2: 0.117, 3: 0.03, 4: 0.009}
tq = TurboQuant(d=d, bit_width=bit_width, seed=42)
rng = np.random.default_rng(99)
n_samples = 500
mse_total = 0.0
for _ in range(n_samples):
x = rng.standard_normal(d)
x = x / np.linalg.norm(x)
compressed = tq.quantize(x)
x_hat = tq.dequantize(compressed)
mse_total += np.mean((x - x_hat) ** 2)
avg_mse = mse_total / n_samples
assert avg_mse < expected_mse[bit_width] * 3.0, (
f"MSE {avg_mse:.5f} exceeds 3× paper bound {expected_mse[bit_width]} "
f"at d={d}, b={bit_width}"
)
@pytest.mark.parametrize("bit_width", [2, 3, 4])
def test_inner_product_preservation(self, bit_width):
"""Inner product distortion should be within paper's bounds.
Paper Theorem 2 (page 5):
D_prod = E[|⟨y,x⟩ - ⟨y, Q⁻¹(Q(x))⟩|²] ≤ √(3π²)·||y||²/d · 1/4^b
for ||x||=1.
We measure mean |⟨y,x⟩ - ⟨y, x̂⟩| (absolute, not squared) and check
it decreases with bit_width. The paper bound is on the squared error
and involves both x AND y being quantized independently, which compounds.
"""
d = 256
tq = TurboQuant(d=d, bit_width=bit_width, seed=42)
rng = np.random.default_rng(77)
ip_errors = []
for _ in range(500):
x = rng.standard_normal(d)
y = rng.standard_normal(d)
x = x / np.linalg.norm(x)
y = y / np.linalg.norm(y)
x_hat = tq.dequantize(tq.quantize(x))
y_hat = tq.dequantize(tq.quantize(y))
ip_original = np.dot(x, y)
ip_approx = np.dot(x_hat, y_hat)
ip_errors.append(abs(ip_approx - ip_original))
avg_ip_error = np.mean(ip_errors)
# Paper bound is on E[|error|²] for single-side quantization.
# We quantize BOTH sides and measure |error| (not squared), so bound is looser.
# Use empirical sanity check: avg absolute IP error should be < 0.5 for any b≥2
assert avg_ip_error < 0.5, (
f"Avg IP error {avg_ip_error:.6f} unreasonably high at d={d}, b={bit_width}"
)
def test_bit_width_1_raises(self):
"""TurboQuant requires bit_width >= 2."""
with pytest.raises(ValueError, match="bit_width >= 2"):
TurboQuant(d=128, bit_width=1)
def test_zero_vector(self):
"""Zero vector produces small but non-zero reconstruction.
PolarQuant centroids are non-zero, so quantizing a zero vector maps
to the nearest centroid per coordinate. QJL residual norm is ~small.
The reconstruction won't be exactly zero but should be small.
"""
tq = TurboQuant(d=128, bit_width=3, seed=42)
x = np.zeros(128)
compressed = tq.quantize(x)
x_hat = tq.dequantize(compressed)
assert np.linalg.norm(x_hat) < 1.0
def test_deterministic(self):
"""Same seed → same output."""
d = 128
x = np.random.default_rng(1).standard_normal(d)
tq1 = TurboQuant(d=d, bit_width=3, seed=42)
tq2 = TurboQuant(d=d, bit_width=3, seed=42)
c1 = tq1.quantize(x)
c2 = tq2.quantize(x)
np.testing.assert_array_equal(c1.mse_indices, c2.mse_indices)
np.testing.assert_array_equal(c1.qjl_signs, c2.qjl_signs)
assert c1.residual_norms == c2.residual_norms
def test_batch_quantization(self):
"""Batch should match individual quantization."""
d = 128
tq = TurboQuant(d=d, bit_width=3, seed=42)
rng = np.random.default_rng(7)
X = rng.standard_normal((5, d))
# Batch
batch_compressed = tq.quantize(X)
batch_recon = tq.dequantize(batch_compressed)
# Single
for i in range(5):
single_compressed = tq.quantize(X[i])
single_recon = tq.dequantize(single_compressed)
np.testing.assert_allclose(batch_recon[i], single_recon, atol=1e-10)
class TestTurboQuantMSE:
"""Tests for MSE-only variant (Algorithm 1 at full bit_width)."""
def test_round_trip(self):
d = 128
tq = TurboQuantMSE(d=d, bit_width=3, seed=42)
rng = np.random.default_rng(1)
x = rng.standard_normal(d)
x = x / np.linalg.norm(x)
indices, norms = tq.quantize(x)
x_hat = tq.dequantize(indices, norms)
mse = np.mean((x - x_hat) ** 2)
# 3-bit MSE-only should be better than paper's 3-bit TurboQuant MSE
# because all 3 bits go to MSE (no QJL stage)
assert mse < 0.1, f"MSE-only 3-bit MSE {mse:.4f} too high"
class TestCompressedSizeBits:
"""Test compressed_size_bits method."""
def test_size_calculation(self):
tq = TurboQuant(d=128, bit_width=3, seed=42)
bits = tq.compressed_size_bits(100)
# 100 vectors × (128 coords × 3 bits + 32 bits norm) = 100 × 416 = 41600
assert bits == 100 * (128 * 3 + 32)
def test_size_scales_with_vectors(self):
tq = TurboQuant(d=64, bit_width=4, seed=42)
bits_10 = tq.compressed_size_bits(10)
bits_100 = tq.compressed_size_bits(100)
assert bits_100 == bits_10 * 10
class TestCompressionRatio:
"""Verify compression ratio calculations."""
def test_3bit_compression(self):
tq = TurboQuant(d=128, bit_width=3, seed=42)
ratio = tq.compression_ratio(original_bits_per_value=16)
# 16 / (3 + 32/128) ≈ 16/3.25 ≈ 4.92
assert 4.0 < ratio < 6.0, f"3-bit compression ratio {ratio:.2f} unexpected"
def test_4bit_compression(self):
tq = TurboQuant(d=128, bit_width=4, seed=42)
ratio = tq.compression_ratio(original_bits_per_value=16)
# 16 / (4 + 32/128) ≈ 16/4.25 ≈ 3.76
assert 3.0 < ratio < 5.0, f"4-bit compression ratio {ratio:.2f} unexpected"