TurboQuant
Spiral TurboQuant expressions.
from spiral import expressions as se
# Split each 384-d vector into a 256-d and a 128-d block, then store it compressed.
table.write({"id": ids, "emb": se.TurboQuant(emb, bit_width=8, block_sizes=[256, 128])})
se.turboquant.encode(t["emb"]) # FixedSizeList<f_, dim> -> Extension<TurboQuant>
se.turboquant.decode(t["emb"]) # Extension<TurboQuant> -> FixedSizeList<f_, dim> (lossy)TurboQuant is a lossy scalar-quantizer vector compression scheme (arXiv:2504.19874) for
high-dimensional float embeddings. The chosen (bit_width, seed, num_rounds, block_sizes, dimensions) is baked into the column’s type and re-checked on every subsequent write, so it is
fixed for the lifetime of the column.
Inputs must be FixedSizeList<f_, dim> with dim >= 64 and non-nullable float elements (f16,
f32, or f64).
TurboQuant
def TurboQuant(value: Any,
*,
bit_width: int = 8,
seed: int = 42,
num_rounds: int = 3,
block_sizes: list[int] | None = None) -> AnyMark a column to be TurboQuant-encoded at write time.
Returns a cheap marker; the orthogonal transform and quantization run at write time. See the overview above for the configuration contract.
Examples
import numpy as np
import pyarrow as pa
import spiral.expressions as se
emb = pa.array(np.random.randn(1000, 384).tolist(), pa.list_(pa.float32(), 384))
ids = list(range(1000))
# The default single block pads each 384-d vector up to the next power of two (512).
table.write({"id": ids, "emb": se.TurboQuant(emb, bit_width=8)})
# Or split each 384-d vector into a 256-d and a 128-d block (no padding):
table_blocks.write({"id": ids, "emb": se.TurboQuant(emb, bit_width=8, block_sizes=[256, 128])})Arguments:
value- Apa.ArrayofFixedSizeList<f_, dim>withdim >= 64and non-nullable float elements (f16,f32, orf64).bit_width- Bits per quantized coordinate (1-8).seed- Seeds the orthogonal random transform. Any value works.num_rounds- Number of transform rounds (must be > 0). Note that the default of 3 is probably sufficient, and higher values rarely improve reconstruction.block_sizes- Optional power-of-two block decomposition. Each block must be a power of two>= 64, and the blocks must sum to at leastdim. When omitted, the encoder uses a single block ofdimrounded up to a power of two.
encode
def encode(expr: ExprLike,
*,
bit_width: int = 8,
seed: int = 42,
num_rounds: int = 3,
block_sizes: list[int] | None = None) -> ExprEncode a FixedSizeList<f_, dim> column into Extension<TurboQuant>.
Examples
import spiral.expressions as se
sp.scan({"emb": se.turboquant.encode(t["emb"], bit_width=8)}).to_table()
# Or split each 384-d vector into a 256-d and a 128-d block (no padding):
sp.scan({"emb": se.turboquant.encode(t["emb"], block_sizes=[256, 128])}).to_table()Arguments:
expr- AFixedSizeList<f_, dim>column to compress.bit_width- Bits per quantized coordinate (1-8). Lower bit widths get better compression at the cost of larger reconstruction error. The default of 8 yields near-lossless roundtrip for typical embedding workloads.seed- Seeds the orthogonal random transform. Any value works.num_rounds- Number of transform rounds (must be > 0). Note that the default of 3 is probably sufficient, and higher values rarely improve reconstruction.block_sizes- Optional power-of-two block decomposition. Each block must be a power of two>= 64, and the blocks must sum to at leastdim. When omitted, the encoder uses a single block ofdimrounded up to a power of two.
Returns:
An expression producing an Extension<TurboQuant> column. The chosen config and the
input’s element type / dimension are recorded in the column’s type.
decode
def decode(expr: ExprLike) -> ExprDecode an Extension<TurboQuant> column back to FixedSizeList<f_, dim>.
Lossy: this does not round-trip with encode. The decoded vectors stay close to the originals
(well within typical recall tolerances for ANN workloads at default 8-bit), but individual
coordinates can drift by the quantization step.
Examples
import spiral.expressions as se
# `table["emb"]` was stored TurboQuant-encoded above, so reading it back decodes to vectors.
sp.scan({"emb": se.turboquant.decode(table["emb"])}).to_table()Arguments:
expr- AnExtension<TurboQuant>column.
Returns:
An expression producing a FixedSizeList<f_, dim> column whose dim and element type
come from the input’s TurboQuant metadata.