MCPcopy Create free account
hub / github.com/NodeDB-Lab/nodedb / train

Method train

nodedb-codec/src/vector_quant/opq.rs:74–160  ·  view source on GitHub ↗

Train an OPQ codec using the Non-Para OPQ algorithm. Alternates between a codebook step (Lloyd's k-means on the rotated training set) and a Procrustes step (SVD-based rotation update to minimize reconstruction error) for `opq_iters` iterations. - `opq_iters`: number of alternating Procrustes+codebook iterations. - `kmeans_iters`: Lloyd's k-means iterations per subspace per OPQ iter.

(
        vectors: &[&[f32]],
        dim: usize,
        m: usize,
        k: usize,
        opq_iters: usize,
        kmeans_iters: usize,
    )

Source from the content-addressed store, hash-verified

72 /// - `opq_iters`: number of alternating Procrustes+codebook iterations.
73 /// - `kmeans_iters`: Lloyd's k-means iterations per subspace per OPQ iter.
74 pub fn train(
75 vectors: &[&[f32]],
76 dim: usize,
77 m: usize,
78 k: usize,
79 opq_iters: usize,
80 kmeans_iters: usize,
81 ) -> Self {
82 assert!(!vectors.is_empty(), "training set must be non-empty");
83 assert!(dim > 0 && m > 0 && k > 0, "dim/m/k must be positive");
84 assert!(
85 dim.is_multiple_of(m),
86 "dim ({dim}) must be divisible by m ({m})"
87 );
88 let sub_dim = dim / m;
89 let seed = dim as u64 ^ ((m as u64) << 16) ^ ((k as u64) << 32);
90
91 let mut rotation = identity(dim);
92 let mut codebooks: Vec<Vec<Vec<f32>>> = Vec::new();
93
94 let iters = opq_iters.max(1);
95
96 for iter in 0..iters {
97 // Codebook step: train PQ on the current rotated training set.
98 let rotated: Vec<Vec<f32>> =
99 vectors.iter().map(|v| matvec(&rotation, v, dim)).collect();
100 codebooks = train_codebooks(&rotated, m, k, sub_dim, kmeans_iters, seed ^ iter as u64);
101
102 // Procrustes step: find R minimising ‖R·X - Y‖_F where Y is
103 // the dequantized reconstruction of R·X.
104 //
105 // Closed-form solution (Ge et al. CVPR 2013, §3.2):
106 // M = X · Yᵀ (dim × dim)
107 // SVD(M) = U Σ Vᵀ
108 // R_new = V · Uᵀ
109 //
110 // Skip rotation update on the last iteration — codebooks were
111 // already retrained with the current R.
112 if iter + 1 < iters {
113 let n = vectors.len();
114 // Build dim×N matrices X (original) and Y (reconstructed).
115 // DMatrix is column-major; we store column j = vector j.
116 let x_mat = DMatrix::from_fn(dim, n, |row, col| vectors[col][row]);
117 let y_mat = {
118 let recon: Vec<Vec<f32>> = rotated
119 .iter()
120 .map(|rv| {
121 let codes = pq_encode(rv, &codebooks, m, sub_dim);
122 dequantize_codes(&codes, &codebooks)
123 })
124 .collect();
125 DMatrix::from_fn(dim, n, |row, col| recon[col][row])
126 };
127
128 // M = X · Yᵀ (dim × dim)
129 let m_mat = &x_mat * y_mat.transpose();
130
131 // Guard: skip rotation update if M contains NaN (degenerate

Callers

nothing calls this directly

Calls 9

matvecFunction · 0.85
train_codebooksFunction · 0.85
pq_encodeFunction · 0.85
dequantize_codesFunction · 0.85
collectMethod · 0.80
identityFunction · 0.70
iterMethod · 0.45
lenMethod · 0.45
pushMethod · 0.45

Tested by

no test coverage detected