phanerozoic commited on
Commit
91ea520
·
verified ·
1 Parent(s): 83ae879
Files changed (1) hide show
  1. README.md +80 -0
README.md ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ tags:
4
+ - kernel
5
+ - audio
6
+ - codec
7
+ - quantization
8
+ - cpu
9
+ ---
10
+
11
+ # rvq-codec
12
+
13
+ Residual vector quantization encode and decode, for EnCodec/DAC/SNAC-class audio
14
+ codecs and the audio language models built on them.
15
+
16
+ ```python
17
+ import kernels
18
+
19
+ rv = kernels.get_kernel("phanerozoic/rvq-codec", version=1, trust_remote_code=True)
20
+
21
+ q = rv.ResidualVQ(codebooks) # [Q, K, D], or a list of [K, D]
22
+ codes = q.encode(x) # [..., D] -> [..., Q] int32
23
+ recon = q.decode(codes) # [..., Q] -> [..., D]
24
+ recon = q.decode(codes, n_stages=4) # truncate for a lower bitrate
25
+ ```
26
+
27
+ ## Where the win is, and where it isn't
28
+
29
+ The two directions have opposite structure, and the measurements say so.
30
+
31
+ **Decode is 43-349x and bitwise exact.** Written in framework ops it is Q
32
+ separate advanced-index gathers, each materializing an `[N, D]` tensor, followed
33
+ by Q-1 adds. Every one of those intermediates exists only to be summed away.
34
+ Fused, it is a single traversal.
35
+
36
+ **Encode is at parity, and should be.** Its cost is a dense `[N, D] x [D, K]`
37
+ product per stage, which BLAS already runs at hardware peak. A hand-blocked
38
+ search measured 0.03-0.09x against it; transposing the codebook to remove the
39
+ horizontal reductions did not help, because the real constraint is codebook
40
+ reuse across vectors, which is exactly what a blocked GEMM provides. So the
41
+ product stays in the framework and only what follows it is fused: argmin, the
42
+ gather, and the residual subtract, which a framework implementation
43
+ materializes twice. That took the worst case from 4641 ms to 272 ms.
44
+
45
+ Distances use `||r - c||² = ||r||² - 2 r·c + ||c||²`, dropping the term constant
46
+ in k, with the codebook norms precomputed once by the plan.
47
+
48
+ ## Measured
49
+
50
+ Sized after real codecs, 30 s of audio, x86-64 (i9-12900H, 14 threads).
51
+
52
+ | config | decode | framework | speedup |
53
+ |---|---:|---:|---:|
54
+ | EnCodec Q=8 K=1024 D=128 | 0.26 ms | 92.00 ms | 349x |
55
+ | SNAC Q=4 K=4096 D=768 | 1.10 ms | 47.96 ms | 43x |
56
+ | Mimi Q=32 K=2048 D=256 | 4.23 ms | 374.02 ms | 88x |
57
+
58
+ Decode output is identical to the framework result, not merely close.
59
+
60
+ Encode runs at 0.78-0.93x of the framework matmul formulation while avoiding the
61
+ `[N, K]` distance materialization, which is 74-393 MB across the stage loop at
62
+ these sizes.
63
+
64
+ ## Notes on codebooks
65
+
66
+ Reconstruction error is non-increasing in the number of stages only when each
67
+ stage's codebook scale tracks the residual it quantizes, which is what training
68
+ produces. With every stage at the same scale, stage 2 subtracts a code far
69
+ larger than stage 1's residual and error goes up. That is a property of the
70
+ codebooks, not of the algorithm, and the test suite pins both behaviors.
71
+
72
+ ## Backends
73
+
74
+ Published binaries are `x86_64-linux`, for PyTorch 2.11, 2.12, and 2.13.
75
+
76
+ Correctness and the aarch64 timings above were measured by building this same
77
+ source on aarch64 (Cortex-A76) rather than from a prebuilt variant; the kernel
78
+ builder only emits variants for the host it runs on, and the build host here is
79
+ x86-64. `load_local.load()` in the repo JIT-builds the source, which is how
80
+ aarch64 was validated and how it can be used there today.