use std::rc::Rc;
use crate::{DType, Engine, Error, Matrix, Result, matrix as core_matrix};
use super::super::{Module, ModuleRegistry, NamedBuffer, NamedStateU32, matrix, random};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct VectorQuantizerConfig {
pub num_codes: usize,
pub code_dim: usize,
pub commitment_beta: f32,
pub ema_decay: f32,
pub ema_epsilon: f32,
pub dead_threshold: f32,
pub normalize_codes: bool,
}
impl Default for VectorQuantizerConfig {
fn default() -> Self {
Self {
num_codes: 256,
code_dim: 64,
commitment_beta: 0.25,
ema_decay: 0.99,
ema_epsilon: 1e-5,
dead_threshold: 1.0,
normalize_codes: false,
}
}
}
pub struct VqResult {
pub quantized: Matrix,
pub indices: Matrix,
pub commitment_loss: Matrix,
}
pub struct VectorQuantizer {
config: VectorQuantizerConfig,
codebook: NamedBuffer,
embed_sum: NamedBuffer,
cluster_size: NamedBuffer,
ema_step: NamedStateU32,
registry: ModuleRegistry,
}
impl VectorQuantizer {
pub fn with_seed(engine: &Engine, config: VectorQuantizerConfig, seed: u64) -> Result<Self> {
validate_config(config)?;
let count = config
.num_codes
.checked_mul(config.code_dim)
.ok_or_else(|| Error::invalid_argument("VQ codebook size overflows usize"))?;
let denominator = config
.num_codes
.checked_add(config.code_dim)
.ok_or_else(|| Error::invalid_argument("VQ Glorot extent overflows usize"))?;
let limit = (6.0_f32 / denominator as f32).sqrt();
let values = random::symmetric_uniform(count, limit, seed);
let codebook = Matrix::from_f32(engine, [config.num_codes, config.code_dim], &values)?;
let embed_sum = Matrix::from_f32(engine, [config.num_codes, config.code_dim], &values)?;
let cluster_size = Matrix::from_f32(engine, [config.num_codes], &vec![1.0; config.num_codes])?;
Self::from_state(config, codebook, embed_sum, cluster_size)
}
pub fn new(engine: &Engine, config: VectorQuantizerConfig) -> Result<Self> {
Self::with_seed(engine, config, 0)
}
fn from_state(
config: VectorQuantizerConfig,
codebook: Matrix,
embed_sum: Matrix,
cluster_size: Matrix,
) -> Result<Self> {
validate_config(config)?;
let state_shape = [config.num_codes, config.code_dim];
if codebook.shape() != state_shape
|| embed_sum.shape() != state_shape
|| cluster_size.shape() != [config.num_codes]
|| [codebook.dtype(), embed_sum.dtype(), cluster_size.dtype()]
.into_iter()
.any(|dtype| dtype != DType::F32)
|| !codebook.engine_handle().same_as(embed_sum.engine_handle())
|| !codebook
.engine_handle()
.same_as(cluster_size.engine_handle())
{
return Err(Error::invalid_argument(
"VQ state must match the configured same-engine FP32 codebook geometry",
));
}
let mut registry = ModuleRegistry::new();
registry.register_buffer("codebook", codebook, true)?;
registry.register_buffer("embed_sum", embed_sum, true)?;
registry.register_buffer("cluster_size", cluster_size, true)?;
let ema_step = registry.register_state_u32("ema_step", 0)?;
let codebook = registry
.buffer_handle("codebook")
.ok_or_else(|| Error::internal("VQ codebook registration was lost"))?;
let embed_sum = registry
.buffer_handle("embed_sum")
.ok_or_else(|| Error::internal("VQ embed-sum registration was lost"))?;
let cluster_size = registry
.buffer_handle("cluster_size")
.ok_or_else(|| Error::internal("VQ cluster-size registration was lost"))?;
Ok(Self {
config,
codebook,
embed_sum,
cluster_size,
ema_step,
registry,
})
}
pub fn quantize(&self, latent: &Matrix) -> Result<VqResult> {
let assignment = matrix::vq_assign(latent, &self.codebook.data())?;
let detached_delta = matrix::detach(&core_matrix::sub(&assignment.quantized, latent)?)?;
let quantized = core_matrix::add(latent, &detached_delta)?;
let difference = core_matrix::sub(latent, &assignment.quantized)?;
let squared = core_matrix::mul(&difference, &difference)?;
let total = core_matrix::reshape(&core_matrix::sum(&squared, -1)?, [])?;
let mean = core_matrix::scale(&total, 1.0 / squared.num_elements() as f32)?;
let commitment_loss = core_matrix::scale(&mean, self.config.commitment_beta)?;
Ok(VqResult {
quantized,
indices: assignment.indices,
commitment_loss,
})
}
pub fn ema_update(&self, latent: &Matrix, indices: &Matrix) -> Result<()> {
let step = self.ema_step.value();
let state = matrix::vq_ema_update(
latent,
indices,
&self.embed_sum.data(),
&self.cluster_size.data(),
&self.codebook.data(),
self.config.ema_decay,
self.config.ema_epsilon,
self.config.dead_threshold,
step,
self.config.normalize_codes,
)?;
self.embed_sum.replace_data(state.embed_sum)?;
self.cluster_size.replace_data(state.cluster_size)?;
self.codebook.replace_data(state.codebook)?;
self.ema_step.replace_value(step.wrapping_add(1));
Ok(())
}
pub fn lookup(&self, indices: &Matrix) -> Result<Matrix> {
matrix::vq_lookup(&self.codebook.data(), indices)
}
pub fn seed(&self, latents: &Matrix) -> Result<()> {
let [rows, code_dim] = latents.shape() else {
return Err(Error::invalid_argument(
"VQ seed latents must have shape [N,D]",
));
};
let codebook = self.codebook.data();
if latents.dtype() != DType::F32
|| *rows < self.config.num_codes
|| self.config.num_codes > 512
|| *code_dim != self.config.code_dim
|| !latents.engine_handle().same_as(codebook.engine_handle())
{
return Err(Error::invalid_argument(
"VQ seed requires same-engine FP32 [N,D] latents with N >= K",
));
}
let squared = core_matrix::mul(latents, latents)?;
let norms = core_matrix::sum(&squared, 1)?.reshape([*rows])?;
let selected = core_matrix::top_k(
&norms,
i32::try_from(self.config.num_codes)
.map_err(|_| Error::invalid_argument("VQ seed code count exceeds i32"))?,
-1,
)?;
let seeded = matrix::vq_lookup(latents, &selected.indices)?;
let embed_sum = core_matrix::copy(&seeded)?;
let cluster_size = Matrix::from_slice_handle(
latents.engine_handle(),
vec![self.config.num_codes],
&vec![1.0_f32; self.config.num_codes],
)?;
self.codebook.replace_data(seeded)?;
self.embed_sum.replace_data(embed_sum)?;
self.cluster_size.replace_data(cluster_size)?;
latents.engine_handle().checkpoint(false)?.wait()
}
pub fn codebook(&self) -> Matrix {
self.codebook.data()
}
pub const fn config(&self) -> &VectorQuantizerConfig {
&self.config
}
pub fn ema_step(&self) -> u32 {
self.ema_step.value()
}
}
impl Module for VectorQuantizer {
fn forward(&self, input: &Matrix) -> Result<Matrix> {
Ok(self.quantize(input)?.quantized)
}
fn registry(&self) -> &ModuleRegistry {
&self.registry
}
}
pub struct ResidualVqResult {
pub quantized: Matrix,
pub indices: Vec<Matrix>,
pub residuals: Vec<Matrix>,
pub commitment_loss: Matrix,
}
pub struct ResidualVectorQuantizer {
config: VectorQuantizerConfig,
levels: Vec<Rc<VectorQuantizer>>,
registry: ModuleRegistry,
}
impl ResidualVectorQuantizer {
pub fn with_seed(
engine: &Engine,
config: VectorQuantizerConfig,
num_levels: usize,
seed: u64,
) -> Result<Self> {
if num_levels == 0 {
return Err(Error::invalid_argument(
"residual VQ requires at least one level",
));
}
let mut registry = ModuleRegistry::new();
let mut levels = Vec::with_capacity(num_levels);
for level in 0..num_levels {
let quantizer = Rc::new(VectorQuantizer::with_seed(
engine,
config,
seed.wrapping_add(level as u64),
)?);
registry.register_module(format!("level{level}"), quantizer.clone())?;
levels.push(quantizer);
}
Ok(Self {
config,
levels,
registry,
})
}
pub fn new(engine: &Engine, config: VectorQuantizerConfig, num_levels: usize) -> Result<Self> {
Self::with_seed(engine, config, num_levels, 0)
}
pub fn quantize(&self, latent: &Matrix) -> Result<ResidualVqResult> {
let mut residual = latent.clone();
let mut total = None;
let mut indices = Vec::with_capacity(self.levels.len());
let mut residuals = Vec::with_capacity(self.levels.len());
for level in &self.levels {
let assignment = matrix::vq_assign(&residual, &level.codebook())?;
residuals.push(residual.clone());
indices.push(assignment.indices);
total = Some(match total {
Some(previous) => core_matrix::add(&previous, &assignment.quantized)?,
None => assignment.quantized.clone(),
});
residual = core_matrix::sub(&residual, &assignment.quantized)?;
}
let total = total.ok_or_else(|| Error::internal("residual VQ lost all levels"))?;
let quantized = core_matrix::add(latent, &matrix::detach(&core_matrix::sub(&total, latent)?)?)?;
let difference = core_matrix::sub(latent, &total)?;
let squared = core_matrix::mul(&difference, &difference)?;
let total = core_matrix::reshape(&core_matrix::sum(&squared, -1)?, [])?;
let commitment_loss = core_matrix::scale(
&total,
self.config.commitment_beta / squared.num_elements() as f32,
)?;
Ok(ResidualVqResult {
quantized,
indices,
residuals,
commitment_loss,
})
}
pub fn ema_update(&self, result: &ResidualVqResult) -> Result<()> {
if result.indices.len() != self.levels.len() || result.residuals.len() != self.levels.len() {
return Err(Error::invalid_argument(
"residual VQ result level count does not match module",
));
}
for ((level, residual), indices) in self
.levels
.iter()
.zip(&result.residuals)
.zip(&result.indices)
{
level.ema_update(residual, indices)?;
}
Ok(())
}
pub fn lookup(&self, indices: &[Matrix]) -> Result<Matrix> {
if indices.is_empty() || indices.len() > self.levels.len() {
return Err(Error::invalid_argument(
"residual VQ lookup requires one through num_levels token matrices",
));
}
let mut total = self.levels[0].lookup(&indices[0])?;
for (level, level_indices) in self.levels.iter().zip(indices).skip(1) {
let codes = level.lookup(level_indices)?;
if codes.shape() != total.shape() {
return Err(Error::invalid_argument(
"residual VQ token shapes must match",
));
}
total = core_matrix::add(&total, &codes)?;
}
Ok(total)
}
pub fn seed(&self, latents: &Matrix) -> Result<()> {
let mut residual = latents.clone();
for level in &self.levels {
level.seed(&residual)?;
let assignment = matrix::vq_assign(&residual, &level.codebook())?;
residual = core_matrix::sub(&residual, &assignment.quantized)?;
}
Ok(())
}
pub fn num_levels(&self) -> usize {
self.levels.len()
}
pub fn level(&self, index: usize) -> Option<&VectorQuantizer> {
self.levels.get(index).map(Rc::as_ref)
}
}
impl Module for ResidualVectorQuantizer {
fn forward(&self, input: &Matrix) -> Result<Matrix> {
Ok(self.quantize(input)?.quantized)
}
fn registry(&self) -> &ModuleRegistry {
&self.registry
}
}
fn validate_config(config: VectorQuantizerConfig) -> Result<()> {
if config.num_codes == 0
|| config.code_dim == 0
|| !config.commitment_beta.is_finite()
|| config.commitment_beta < 0.0
|| !config.ema_decay.is_finite()
|| !(0.0..=1.0).contains(&config.ema_decay)
|| !config.ema_epsilon.is_finite()
|| config.ema_epsilon <= 0.0
|| !config.dead_threshold.is_finite()
|| config.dead_threshold < 0.0
{
return Err(Error::invalid_argument(
"invalid vector-quantizer configuration",
));
}
Ok(())
}