rvq-codec
Residual vector quantization encode and decode for EnCodec, DAC, and
SNAC-class audio codecs and the audio language models built on them,
loadable through kernels. Decode output is bit-identical to the framework
result (torch.equal), not merely close. The reference baselines are three
framework formulations of the same operation, all measured.
Every audio language model ends at an RVQ decode: a stack of codebook
lookups summed into a waveform latent, run once per frame of generated
audio. Written in framework ops it is Q separate advanced-index gathers,
each materializing an [N, D] tensor, followed by Q-1 adds, and every one
of those intermediates exists only to be summed away. This kernel is a
single traversal that produces exactly the same bytes.
Decode of 30 s of audio on 14 x86 threads, against three framework formulations of the same operation: the one-hot matmul route at 17.5 to 141.7 ms, stack-and-sum at 1.63 to 8.45, a hand-written gather-and-sum at 0.48 to 2.20, and the kernel at 0.33 to 3.22, with output identical to all of them.
Usage
import kernels
rv = kernels.get_kernel("phanerozoic/rvq-codec", version=1, trust_remote_code=True)
q = rv.ResidualVQ(codebooks) # [Q, K, D], or a list of [K, D]
codes = q.encode(x) # [..., D] -> [..., Q] int32
recon = q.decode(codes) # [..., Q] -> [..., D]
recon = q.decode(codes, n_stages=4) # truncate for a lower bitrate
version selects the release branch; trust_remote_code is required by
kernels for publishers without the trusted-publisher mark.
API
| Symbol | Purpose |
|---|---|
ResidualVQ(codebooks) |
plan holding the codebook stack and precomputed norms |
.encode(x, n_stages) |
residual quantization to [..., Q] int32 codes |
.decode(codes, n_stages) |
codes to [..., D]; truncate stages for lower bitrate |
Method
The two directions have opposite structure, and the measurements say so.
Decode is a single traversal: Q codebook lookups accumulated in place, with no intermediate per stage. It is exact, so the output is the framework result to the bit.
Encode's cost is a dense [N, D] x [D, K] product per stage, which BLAS
already runs at hardware peak. A hand-blocked search measured 0.03-0.09x
against it, and transposing the codebook to remove the horizontal reductions
did not help, because the real constraint is codebook reuse across vectors,
which is exactly what a blocked GEMM provides. So the product stays in the
framework and only what follows it is fused: argmin, the gather, and the
residual subtract, which a framework implementation materializes twice. That
took the worst case from 4641 ms to 272 ms. Distances use
||r - c||^2 = ||r||^2 - 2 r.c + ||c||^2, dropping the term constant in k,
with codebook norms precomputed once by the plan.
Measured
Decode of 30 s of audio, 14 threads, x86-64, against three framework formulations of the same operation:
| config | rvq-codec | gather + sum | stack + sum | one-hot matmul |
|---|---|---|---|---|
| EnCodec Q=8 K=1024 D=128 | 0.33 ms | 0.48 ms | 1.78 ms | 17.5 ms |
| SNAC Q=4 K=4096 D=768 | 0.73 ms | 0.76 ms | 1.63 ms | 98.7 ms |
| Mimi Q=32 K=2048 D=256 | 3.22 ms | 2.20 ms | 8.45 ms | 141.7 ms |
The kernel is 44x to 136x the one-hot matmul formulation and 2.2x to 5.5x the stack-and-sum route, while a carefully hand-written gather-and-sum loop is at parity; the kernel's advantage there is that it needs no intermediates and is one call rather than a loop.
Encode, same host, against the framework's matmul formulation:
| config | rvq-codec | cdist | matmul | dist tensor |
|---|---|---|---|---|
| EnCodec Q=8 K=1024 D=128 | 31.6 ms | 24.7 ms | 24.3 ms | 73.7 MB |
| DAC Q=9 K=1024 D=8 | 30.3 ms | 16.9 ms | 22.0 ms | 95.1 MB |
| SNAC Q=4 K=4096 D=768 | 70.5 ms | 67.9 ms | 66.0 ms | 59.0 MB |
| Mimi Q=32 K=2048 D=256 | 224.7 ms | 156.4 ms | 187.1 ms | 393.2 MB |
Encode runs at 0.73-0.94x of the framework matmul formulation while avoiding
the [N, K] distance materialization, which is 59 to 393 MB across the
stage loop at these sizes.
Correctness
Decode output is torch.equal to the framework result at every
configuration measured, not merely close.
Reconstruction error is non-increasing in the number of stages only when each stage's codebook scale tracks the residual it quantizes, which is what training produces. With every stage at the same scale, stage 2 subtracts a code far larger than stage 1's residual and error goes up. That is a property of the codebooks, not of the algorithm, and the test suite pins both behaviors.
Requirements and limits
- CPU,
x86_64-linuxandaarch64-linux, for PyTorch 2.11, 2.12, and 2.13 (six prebuilt variants), each architecture built on its own host and validated against the prebuilt binary. - Encode is at parity with the framework matmul by design; the win there is the removed distance materialization, not throughput.
References
Zeghidour et al., SoundStream (2021); Défossez et al., EnCodec (2022); Kumar et al., DAC (2023); Siuzdak et al., SNAC (2024).
License
Apache-2.0.
- Downloads last month
- -
- OS
- linux
- Arch
- x86_64aarch64
- Kernel Builder
- 19aaa64
