zeppelin_embed/lifecycle/
rescored_scan.rs1#[derive(Clone, Copy, Debug, Eq, PartialEq)]
9pub struct ScanRescoreOptions {
10 oversample: usize,
11 max_candidates_per_segment: usize,
12}
13
14impl ScanRescoreOptions {
15 pub const fn new(
17 oversample: usize,
18 max_candidates_per_segment: usize,
19 ) -> Result<Self, crate::quant::RescoreError> {
20 if oversample == 0 {
21 return Err(crate::quant::RescoreError::ZeroOversample);
22 }
23 if max_candidates_per_segment == 0 {
24 return Err(crate::quant::RescoreError::ZeroCandidateLimit);
25 }
26 Ok(Self {
27 oversample,
28 max_candidates_per_segment,
29 })
30 }
31
32 #[must_use]
34 pub const fn oversample(self) -> usize {
35 self.oversample
36 }
37
38 #[must_use]
40 pub const fn max_candidates_per_segment(self) -> usize {
41 self.max_candidates_per_segment
42 }
43
44 pub(crate) fn frontier(self, k: usize, eligible: u64) -> Result<usize, super::QueryError> {
45 let requested = k
46 .checked_mul(self.oversample)
47 .ok_or_else(|| rescore_error(crate::quant::RescoreError::ArithmeticOverflow))?;
48 let eligible = usize::try_from(eligible)
49 .map_err(|_| rescore_error(crate::quant::RescoreError::ArithmeticOverflow))?;
50 let frontier = requested.min(eligible);
51 self.check_count(frontier)?;
52 Ok(frontier)
53 }
54
55 fn check_count(self, requested: usize) -> Result<(), super::QueryError> {
56 if requested > self.max_candidates_per_segment {
57 return Err(rescore_error(
58 crate::quant::RescoreError::CandidateLimitExceeded {
59 requested,
60 maximum: self.max_candidates_per_segment,
61 },
62 ));
63 }
64 Ok(())
65 }
66}
67
68fn rescore_error(error: crate::quant::RescoreError) -> super::QueryError {
69 super::QueryError::Scan(crate::scan::ScanError::Rescore(error))
70}
71
72pub(crate) fn accumulate(
73 total: &mut crate::diag::ScanRescoreCounters,
74 next: crate::diag::ScanRescoreCounters,
75) -> Result<(), super::QueryError> {
76 let add = |a: u64, b: u64| {
77 a.checked_add(b).ok_or(super::QueryError::Scan(
78 crate::scan::ScanError::ArithmeticOverflow,
79 ))
80 };
81 total.coarse_rows = add(total.coarse_rows, next.coarse_rows)?;
82 total.eligible_rows = add(total.eligible_rows, next.eligible_rows)?;
83 total.candidates_rescored = add(total.candidates_rescored, next.candidates_rescored)?;
84 total.coarse_bytes = add(total.coarse_bytes, next.coarse_bytes)?;
85 total.rescore_bytes = add(total.rescore_bytes, next.rescore_bytes)?;
86 Ok(())
87}
88
89pub(crate) trait RescoreCandidate {
91 fn row_id(&self) -> usize;
92 fn score(&self) -> f32;
93 fn set_score(&mut self, score: f32);
94}
95
96impl RescoreCandidate for crate::scan::ScanCandidate {
97 fn row_id(&self) -> usize {
98 self.row_id
99 }
100 fn score(&self) -> f32 {
101 self.score
102 }
103 fn set_score(&mut self, score: f32) {
104 self.score = score;
105 }
106}
107
108#[allow(clippy::too_many_arguments)]
112pub(crate) fn rescore<C: RescoreCandidate>(
113 candidates: &mut Vec<C>,
114 stats: &mut crate::scan::ScanStats,
115 vectors: &[f32],
116 query: &[f32],
117 k: usize,
118 eligible: u64,
119 options: ScanRescoreOptions,
120 cancellation: &super::QueryCancellation<'_>,
121 accounting: &std::sync::Arc<super::stats::Accounting>,
122 memory: &mut super::ExactScanMemory,
123) -> Result<(crate::diag::ScanRescoreCounters, bool, Option<f32>), super::QueryError> {
124 use super::{
125 QueryError, StoreError,
126 stats::{Accounted, AllocationComponent},
127 };
128 use crate::scan::ScanError;
129 cancellation.check_graph().map_err(super::map_scan_error)?;
130 let dimension = query.len();
131 if dimension == 0 {
132 return Err(QueryError::Scan(ScanError::ZeroDimension));
133 }
134 if !vectors.len().is_multiple_of(dimension) {
135 return Err(QueryError::Scan(ScanError::RowDataLength {
136 dimension,
137 actual: vectors.len(),
138 }));
139 }
140 let count = candidates.len();
141 options.check_count(count)?;
142 memory
143 .candidates
144 .set(
145 candidates
146 .capacity()
147 .checked_mul(std::mem::size_of::<C>())
148 .ok_or(QueryError::Scan(ScanError::ArithmeticOverflow))?,
149 )
150 .map_err(QueryError::Store)?;
151 let mut rows = Accounted::try_with_capacity(accounting, count, AllocationComponent::Temporary)
152 .map_err(QueryError::Store)?;
153 for (position, candidate) in candidates.iter().enumerate() {
154 if position.is_multiple_of(64) {
155 cancellation.check_graph().map_err(super::map_scan_error)?;
156 }
157 let start = candidate
158 .row_id()
159 .checked_mul(dimension)
160 .ok_or(QueryError::Scan(ScanError::ArithmeticOverflow))?;
161 let end = start
162 .checked_add(dimension)
163 .ok_or(QueryError::Scan(ScanError::ArithmeticOverflow))?;
164 let vector = vectors.get(start..end).ok_or_else(|| {
165 rescore_error(crate::quant::RescoreError::CandidateRowOutOfRange {
166 position,
167 row_index: candidate.row_id(),
168 row_count: vectors.len() / dimension,
169 })
170 })?;
171 rows.push((candidate.row_id(), vector))
172 .map_err(QueryError::Store)?;
173 }
174 let mut position = 0;
175 let mut worst: Option<f32> = None;
176 let mut narrowing: Option<(usize, f64)> = None;
177 let scored = crate::quant::exact_squared_l2_with_sink(
178 query,
179 rows.iter().copied(),
180 |_, checkpoint| {
181 if checkpoint {
182 cancellation.check_graph().map_err(super::map_scan_error)?;
183 }
184 Ok(())
185 },
186 |row_id, exact| {
187 let candidate = candidates
188 .get_mut(position)
189 .ok_or(QueryError::Scan(ScanError::ArithmeticOverflow))?;
190 position += 1;
191 let score = exact as f32;
192 if !score.is_finite() {
193 if narrowing.is_none_or(|(row, previous)| {
194 exact.total_cmp(&previous).is_gt()
195 || (exact.total_cmp(&previous).is_eq() && row_id < row)
196 }) {
197 narrowing = Some((row_id, exact));
198 }
199 } else {
200 candidate.set_score(score);
201 if worst.is_none_or(|previous| score.total_cmp(&previous).is_lt()) {
202 worst = Some(score);
203 }
204 }
205 Ok(())
206 },
207 )
208 .map_err(|error| match error {
209 crate::quant::RescoreCheckError::Rescore(error) => super::map_l2_rescore_error(error),
210 crate::quant::RescoreCheckError::Check(error) => error,
211 })?;
212 cancellation.check_graph().map_err(super::map_scan_error)?;
213 if let Some((row_id, _)) = narrowing {
214 return Err(QueryError::Scan(ScanError::NonFiniteScore { row_id }));
215 }
216 let scored =
217 u64::try_from(scored).map_err(|_| QueryError::Scan(ScanError::ArithmeticOverflow))?;
218 let coordinates = scored
219 .checked_mul(dimension as u64)
220 .ok_or(QueryError::Scan(ScanError::ArithmeticOverflow))?;
221 let bytes = coordinates
222 .checked_mul(4)
223 .ok_or(QueryError::Scan(ScanError::ArithmeticOverflow))?;
224 let counters = crate::diag::ScanRescoreCounters {
225 coarse_rows: stats.dims_touched / dimension as u64,
226 eligible_rows: eligible,
227 candidates_rescored: scored,
228 coarse_bytes: stats.bytes_read,
229 rescore_bytes: bytes,
230 };
231 stats.dims_touched = stats
232 .dims_touched
233 .checked_add(coordinates)
234 .ok_or(QueryError::Scan(ScanError::ArithmeticOverflow))?;
235 stats.bytes_read = stats
236 .bytes_read
237 .checked_add(bytes)
238 .ok_or(QueryError::Scan(ScanError::ArithmeticOverflow))?;
239 let caller = std::thread::current().id();
240 if scored > 0 && !stats.worker_thread_ids.contains(&caller) {
241 let needed = stats
242 .worker_thread_ids
243 .len()
244 .checked_add(1)
245 .ok_or(QueryError::Scan(ScanError::ArithmeticOverflow))?;
246 memory
247 .worker_ids
248 .set(
249 needed
250 .max(stats.worker_thread_ids.capacity())
251 .checked_mul(std::mem::size_of::<std::thread::ThreadId>())
252 .ok_or(QueryError::Scan(ScanError::ArithmeticOverflow))?,
253 )
254 .map_err(QueryError::Store)?;
255 stats.worker_thread_ids.try_reserve_exact(1).map_err(|_| {
256 QueryError::Store(StoreError::AllocationFailed {
257 needed: memory.worker_ids.bytes(),
258 component: "rescore worker IDs",
259 })
260 })?;
261 stats.worker_thread_ids.push(caller);
262 }
263 stats.threads_used = stats.worker_thread_ids.len();
264 let exhaustive = scored == eligible;
265
266 candidates.sort_unstable_by(|a, b| {
267 b.score()
268 .total_cmp(&a.score())
269 .then(a.row_id().cmp(&b.row_id()))
270 });
271 crate::scan::truncate_to_k_with_score_ties(candidates, k, |candidate| candidate.score());
272 Ok((counters, exhaustive, if exhaustive { worst } else { None }))
273}