1use std::{
2 cmp::Ordering,
3 hash::{Hash, Hasher},
4};
5
6use query_planning::plan::{
7 VectorAlgorithmParams, VectorBruteForceKnnParams, VectorCagraBuildAlgo, VectorCagraParams, VectorCagraSearchAlgo,
8 VectorDistanceMetric, VectorIvfFlatParams, VectorIvfPqCodebookKind, VectorIvfPqParams, VectorKMeansInit,
9 VectorKMeansMetric, VectorKMeansParams, VectorPairwiseDistanceParams, VectorPcaParams, VectorPcaSolver,
10 VectorRelationBinding, VectorRowIdBinding, VectorTopKParams,
11};
12use serde_json::{Map, Value};
13
14use super::{
15 CuvsOperation, CuvsRelationRole, DenseVectorBinding, canonicalize_cuvs_options,
16 error::{ERRORS, invalid_option},
17};
18use crate::{
19 error::Result,
20 gpu_functions::dense_relation::{CanonicalOptions, DenseRelationArgument, DenseRelationFunction, LabelColumn},
21};
22
23#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
24pub struct CuvsVectorRelationBinding {
26 relation_role: CuvsRelationRole,
27 id: String,
28 vector: DenseVectorBinding,
29}
30
31impl CuvsVectorRelationBinding {
32 pub fn relation_role(&self) -> CuvsRelationRole {
34 self.relation_role
35 }
36
37 pub fn id(&self) -> &str {
39 &self.id
40 }
41
42 pub fn vector(&self) -> &DenseVectorBinding {
44 &self.vector
45 }
46}
47
48#[derive(Debug, Clone)]
52pub struct CuvsValidatedOptions {
53 canonical_json: String,
54 value_options: Map<String, Value>,
55 bindings: Vec<CuvsVectorRelationBinding>,
56 params: VectorAlgorithmParams,
57}
58
59impl CuvsValidatedOptions {
60 pub fn canonical_json(&self) -> &str {
62 &self.canonical_json
63 }
64
65 pub fn bindings(&self) -> &[CuvsVectorRelationBinding] {
67 &self.bindings
68 }
69
70 pub fn params(&self) -> &VectorAlgorithmParams {
73 &self.params
74 }
75
76 pub(super) fn value_options(&self) -> &Map<String, Value> {
78 &self.value_options
79 }
80}
81
82impl PartialEq for CuvsValidatedOptions {
84 fn eq(&self, other: &Self) -> bool {
85 self.canonical_json == other.canonical_json
86 }
87}
88
89impl Eq for CuvsValidatedOptions {}
90
91impl Hash for CuvsValidatedOptions {
92 fn hash<H: Hasher>(&self, state: &mut H) {
93 self.canonical_json.hash(state);
94 }
95}
96
97impl PartialOrd for CuvsValidatedOptions {
98 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
99 Some(self.cmp(other))
100 }
101}
102
103impl Ord for CuvsValidatedOptions {
104 fn cmp(&self, other: &Self) -> Ordering {
105 self.canonical_json.cmp(&other.canonical_json)
106 }
107}
108
109pub(super) fn validate_options(operation: CuvsOperation, raw: &str) -> Result<CuvsValidatedOptions> {
110 let canonical = canonicalize_cuvs_options(operation, raw)
111 .map_err(|error| invalid_option(operation, error.code(), error.argument(), error.message()))?;
112 let options = dense_relation_function(operation).canonical_options(&canonical);
113 let bindings = operation
114 .descriptor()
115 .relation_roles
116 .iter()
117 .map(|&relation_role| {
118 let binding = options.relation_binding(relation_role.as_str())?;
119 Ok(CuvsVectorRelationBinding { relation_role, id: binding.id, vector: binding.vector })
120 })
121 .collect::<Result<Vec<_>>>()?;
122 let params = algorithm_params(operation, &options, &bindings)?;
123 let value_options = options.value_options(operation.descriptor().options);
124 Ok(CuvsValidatedOptions { canonical_json: Value::Object(canonical).to_string(), value_options, bindings, params })
125}
126
127const DISTANCE_METRICS: [(&str, VectorDistanceMetric); 4] = [
128 ("l2_expanded", VectorDistanceMetric::L2Expanded),
129 ("l2_sqrt_expanded", VectorDistanceMetric::L2SqrtExpanded),
130 ("cosine", VectorDistanceMetric::CosineExpanded),
131 ("inner_product", VectorDistanceMetric::InnerProduct),
132];
133
134fn algorithm_params(
135 operation: CuvsOperation,
136 options: &CanonicalOptions<'_>,
137 bindings: &[CuvsVectorRelationBinding],
138) -> Result<VectorAlgorithmParams> {
139 let relation = |ordinal: usize| {
140 let binding = &bindings[ordinal];
141 let row_id = VectorRowIdBinding::column(binding.id());
142 match binding.vector() {
143 DenseVectorBinding::Columns(columns) => {
144 VectorRelationBinding::new(ordinal, row_id, columns.iter().cloned())
145 },
146 DenseVectorBinding::VectorColumn(column) => {
147 VectorRelationBinding::new_vector_column(ordinal, row_id, column.clone())
148 },
149 }
150 };
151 Ok(match operation {
152 CuvsOperation::BruteForceKnn => VectorAlgorithmParams::BruteForceKnn(VectorBruteForceKnnParams::new(
153 relation(0),
154 relation(1),
155 options.usize("k")?,
156 options.choice("metric", &DISTANCE_METRICS)?,
157 )),
158 CuvsOperation::IvfFlat => {
159 let mut params = VectorIvfFlatParams::new(
160 relation(0),
161 relation(1),
162 options.usize("k")?,
163 options.choice("metric", &DISTANCE_METRICS)?,
164 options.u32("n_lists")?,
165 );
166 params.n_probes = options.u32("n_probes")?;
167 params.kmeans_n_iters = options.u32("kmeans_n_iters")?;
168 params.kmeans_trainset_fraction = options.f64("kmeans_trainset_fraction")?;
169 VectorAlgorithmParams::IvfFlat(params)
170 },
171 CuvsOperation::IvfPq => {
172 let mut params = VectorIvfPqParams::new(
173 relation(0),
174 relation(1),
175 options.usize("k")?,
176 options.choice("metric", &DISTANCE_METRICS)?,
177 options.u32("n_lists")?,
178 );
179 params.n_probes = options.u32("n_probes")?;
180 params.kmeans_n_iters = options.u32("kmeans_n_iters")?;
181 params.kmeans_trainset_fraction = options.f64("kmeans_trainset_fraction")?;
182 params.pq_dim = options.u32("pq_dim")?;
183 params.pq_bits = options.u32("pq_bits")?;
184 params.codebook_kind = options.choice(
185 "codebook_kind",
186 &[
187 ("per_subspace", VectorIvfPqCodebookKind::PerSubspace),
188 ("per_cluster", VectorIvfPqCodebookKind::PerCluster),
189 ],
190 )?;
191 VectorAlgorithmParams::IvfPq(params)
192 },
193 CuvsOperation::Cagra => {
194 let mut params = VectorCagraParams::new(
195 relation(0),
196 relation(1),
197 options.usize("k")?,
198 options.choice("metric", &DISTANCE_METRICS)?,
199 );
200 params.graph_degree = options.u32("graph_degree")?;
201 params.intermediate_graph_degree = options.u32("intermediate_graph_degree")?;
202 params.build_algo = options.choice(
203 "build_algo",
204 &[("ivf_pq", VectorCagraBuildAlgo::IvfPq), ("nn_descent", VectorCagraBuildAlgo::NnDescent)],
205 )?;
206 params.itopk_size = options.u32("itopk_size")?;
207 params.search_width = options.u32("search_width")?;
208 params.max_iterations = options.u32("max_iterations")?;
209 params.search_algo = options.choice(
210 "search_algo",
211 &[
212 ("auto", VectorCagraSearchAlgo::Auto),
213 ("single_cta", VectorCagraSearchAlgo::SingleCta),
214 ("multi_cta", VectorCagraSearchAlgo::MultiCta),
215 ("multi_kernel", VectorCagraSearchAlgo::MultiKernel),
216 ],
217 )?;
218 VectorAlgorithmParams::Cagra(params)
219 },
220 CuvsOperation::PairwiseDistance => VectorAlgorithmParams::PairwiseDistance(VectorPairwiseDistanceParams {
221 x: relation(0),
222 y: relation(1),
223 metric: options.choice("metric", &DISTANCE_METRICS)?,
224 }),
225 CuvsOperation::TopK => VectorAlgorithmParams::TopK(VectorTopKParams {
226 input: relation(0),
227 k: options.usize("k")?,
228 select_min: options.choice("select", &[("min", true), ("max", false)])?,
229 }),
230 CuvsOperation::KMeans => {
231 let mut params = VectorKMeansParams::new(relation(0), options.usize("n_clusters")?);
232 params.metric = options.choice(
233 "metric",
234 &[
235 ("l2_expanded", VectorKMeansMetric::L2Expanded),
236 ("l2_sqrt_expanded", VectorKMeansMetric::L2SqrtExpanded),
237 ],
238 )?;
239 params.max_iterations = options.u32("max_iter")?;
240 params.tolerance = options.f64("tol")?;
241 params.n_init = options.u32("n_init")?;
242 params.init = options.choice(
243 "init",
244 &[("kmeans++", VectorKMeansInit::KMeansPlusPlus), ("random", VectorKMeansInit::Random)],
245 )?;
246 VectorAlgorithmParams::KMeans(params)
247 },
248 CuvsOperation::Pca => {
249 let mut params = VectorPcaParams::new(relation(0), options.usize("n_components")?);
250 params.whiten = options.bool("whiten")?;
251 params.solver = options.choice(
252 "solver",
253 &[("cov_eig_dq", VectorPcaSolver::CovEigDq), ("cov_eig_jacobi", VectorPcaSolver::CovEigJacobi)],
254 )?;
255 params.tolerance = options.f64("tol")?;
256 params.n_iterations = options.u32("n_iterations")?;
257 params.flip_signs_based_on_u = options.bool("flip_signs_based_on_u")?;
258 VectorAlgorithmParams::Pca(params)
259 },
260 })
261}
262
263pub(super) fn dense_relation_function(operation: CuvsOperation) -> DenseRelationFunction {
266 let descriptor = operation.descriptor();
267 DenseRelationFunction {
268 errors: &ERRORS,
269 sql_function_name: descriptor.sql_function_name,
270 relations: descriptor
271 .relation_roles
272 .iter()
273 .map(|role| DenseRelationArgument { role: role.as_str(), label: LabelColumn::Feature })
274 .collect(),
275 options: descriptor.options,
276 }
277}