Skip to Content
Python APIse.turboquant

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) -> Any

Mark 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 - A pa.Array of FixedSizeList<f_, dim> with dim >= 64 and non-nullable float elements (f16, f32, or f64).
  • 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 least dim. When omitted, the encoder uses a single block of dim rounded 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) -> Expr

Encode 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 - A FixedSizeList<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 least dim. When omitted, the encoder uses a single block of dim rounded 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) -> Expr

Decode 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 - An Extension<TurboQuant> column.

Returns:

An expression producing a FixedSizeList<f_, dim> column whose dim and element type come from the input’s TurboQuant metadata.

Last updated on