1616
1717#pragma once
1818
19+ #include < cstddef>
1920#include < cstdint>
2021#include < lumina/distance/Metric.h>
2122#include < lumina/distance/MetricDistance.h>
23+ #include < lumina/distance/encode_space/EncodedRowSource.h>
2224#include < lumina/distance/encode_space/EncodingTypes.h>
2325#include < span>
26+ #include < type_traits>
27+ #include < utility>
2428
2529namespace lumina ::dist {
2630
31+ // Encoded Distance Interface (CPO)
32+ //
33+ // This file defines the core abstraction for computing distances between a floating-point query
34+ // and encoded (compressed/quantized) vectors.
35+ //
36+ // Workflow Overview:
37+ // 1. Prepare: One-time processing of the query for a specific encoding (e.g., building
38+ // lookup tables for PQ or pre-calculating distance components). This amortizes the cost
39+ // of complex setup across many distance evaluations.
40+ // 2. Evaluate: Use the prepared state to compute distances for many rows.
41+ // - BatchEval: Best for contiguous scans (dense layout).
42+ // - GatherEval: Best for graph/IVF traversal where candidate rows are scattered.
43+ //
44+ // Design Goals:
45+ // - Performance: Enables SIMD/ISA-specific optimizations (like AVX-512 gather) via TagInvoke.
46+ // - Decoupling: Uses `EncodedRowSource` to abstract memory layout (flat, strided, or aux-data)
47+ // away from the distance logic.
48+ // - Stability: The API remains constant even if the underlying encoding format changes.
49+
2750struct PrepareTag {
51+ /* *
52+ * @brief Transforms a raw query into a "Prepared State" optimized for a specific encoding.
53+ *
54+ * Why use this?
55+ * For many encodings (like Product Quantization), calculating a distance requires expensive
56+ * per-query setup (e.g., pre-computing 256 distances to codebook centroids). 'Prepare'
57+ * ensures this work is done only once per search.
58+ *
59+ * @param m The Metric (e.g., MetricL2, MetricIP).
60+ * @param e The Encoding (e.g., SQ8, PQ).
61+ * @param q The raw floating-point query vector.
62+ * @param ctx Additional encoding-specific resources (e.g., codebooks, headers).
63+ * @return An opaque state object (S) to be passed to Eval/Gather functions.
64+ */
2865 template <class M , class E , class ... Ctx>
2966 requires TagInvocable<PrepareTag, M, E, std::span<const float >, Ctx&&...>
3067 constexpr auto operator ()(const M& m, const E& e, std::span<const float > q, Ctx&&... ctx) const
3168 noexcept (noexcept (TagInvoke(std::declval<PrepareTag>(), m, e, q, std::forward<Ctx>(ctx)...)))
3269 -> TagInvokeResult<PrepareTag, M, E, std::span<const float>, Ctx&&...>
3370 {
34- return TagInvoke (*this , m, e, q, ctx...);
71+ return TagInvoke (*this , m, e, q, std::forward<Ctx>( ctx) ...);
3572 }
3673};
3774inline constexpr PrepareTag Prepare {};
3875
3976struct EvalEncodedTag {
77+ /* *
78+ * @brief Computes distance for a single encoded row.
79+ *
80+ * Typically used within a loop when manual flow control is needed. However, for
81+ * performance-critical code, BatchEval or GatherEval are usually preferred as they
82+ * allow the implementation to use SIMD more effectively.
83+ *
84+ * @param s The prepared state from Prepare().
85+ * @param r A view to a single encoded row (e.g., encode_space::EncodedRow).
86+ */
4087 template <class M , class E , class S , class R >
4188 requires TagInvocable<EvalEncodedTag, M, E, const S&, const R&>
4289 constexpr auto operator ()(const M& m, const E& e, const S& s, const R& r) const
@@ -49,6 +96,15 @@ struct EvalEncodedTag {
4996inline constexpr EvalEncodedTag EvalEncoded {};
5097
5198struct BatchEvalEncodedTag {
99+ /* *
100+ * @brief Computes distances for a contiguous block of encoded rows.
101+ *
102+ * Optimized for exhaustive scans. The implementation can assume rows are adjacent in memory,
103+ * allowing for efficient linear prefetching and unrolled SIMD processing.
104+ *
105+ * @param b Description of the contiguous batch (base pointer + count).
106+ * @param out Pointer to output float array (must hold at least b.n elements).
107+ */
52108 template <class M , class E , class S >
53109 requires TagInvocable<BatchEvalEncodedTag, M, E, const S&, const encode_space::EncodedBatch&, float *>
54110 constexpr void operator ()(const M& m, const E& e, const S& s, const encode_space::EncodedBatch& b, float * out) const
@@ -59,4 +115,98 @@ struct BatchEvalEncodedTag {
59115};
60116inline constexpr BatchEvalEncodedTag BatchEvalEncoded {};
61117
118+ struct GatherEvalEncodedTag {
119+ /* *
120+ * @brief Computes distances for a non-contiguous set of row IDs.
121+ *
122+ * The primary workhorse for graph-based or IVF search. Given a list of candidate IDs,
123+ * this function "gathers" the encoded data and computes distances.
124+ *
125+ * Performance Note: Specializations of this tag often use ISA-specific instructions
126+ * (e.g., VPGATHERDD) or software pipelining to hide memory latency during random access.
127+ *
128+ * @param data The data source (models EncodedRowSource) providing mapping from ID to memory.
129+ * @param rowIds The list of logical row IDs to evaluate.
130+ * @param results Output span for distances (must match rowIds size).
131+ */
132+ template <class M , class E , class S >
133+ requires TagInvocable<GatherEvalEncodedTag, M, E, const S&, const std::byte*, std::span<const uint64_t >,
134+ std::span<float >>
135+ constexpr void operator ()(const M& m, const E& e, const S& s, const std::byte* recordsBase,
136+ std::span<const uint64_t > rowIds, std::span<float > results) const
137+ noexcept (noexcept (TagInvoke(std::declval<GatherEvalEncodedTag>(), m, e, s, recordsBase, rowIds, results)))
138+ {
139+ TagInvoke (*this , m, e, s, recordsBase, rowIds, results);
140+ }
141+
142+ template <class M , class E , class S , class DataSource >
143+ requires (encode_space::EncodedRowSource<std::remove_cvref_t <DataSource>> &&
144+ TagInvocable<GatherEvalEncodedTag, M, E, const S&, const DataSource&, std::span<const uint64_t >,
145+ std::span<float >>)
146+ constexpr void operator ()(const M& m, const E& e, const S& s, const DataSource& data,
147+ std::span<const uint64_t > rowIds, std::span<float > results) const
148+ noexcept (noexcept (TagInvoke(std::declval<GatherEvalEncodedTag>(), m, e, s, data, rowIds, results)))
149+ {
150+ TagInvoke (*this , m, e, s, data, rowIds, results);
151+ }
152+
153+ template <class M , class E , class S , class DataSource >
154+ requires (encode_space::EncodedRowSource<std::remove_cvref_t <DataSource>> &&
155+ !TagInvocable<GatherEvalEncodedTag, M, E, const S&, const DataSource&, std::span<const uint64_t >,
156+ std::span<float >>)
157+ constexpr void operator ()(const M& m, const E& e, const S& s, const DataSource& data,
158+ std::span<const uint64_t > rowIds, std::span<float > results) const
159+ noexcept (noexcept (encode_space::GetEncodedRow(data, uint64_t {})) && noexcept (
160+ EvalEncoded (m, e, s, encode_space::GetEncodedRow(data, uint64_t {}))))
161+ {
162+ for (std::size_t i = 0 ; i < rowIds.size (); ++i) {
163+ const auto row = encode_space::GetEncodedRow (data, rowIds[i]);
164+ results[i] = EvalEncoded (m, e, s, row);
165+ }
166+ }
167+ };
168+ inline constexpr GatherEvalEncodedTag GatherEvalEncoded {};
169+
170+ struct GatherEvalEncodedWithLowerBoundsTag {
171+ /* *
172+ * @brief Evaluates distances AND returns a lower-bound estimate for each row.
173+ *
174+ * This is an optimization for multi-stage search (e.g., DiskANN).
175+ *
176+ * Lower bounds (D_lb) guarantee that D_true >= D_lb. In search algorithms, if D_lb is
177+ * already greater than the current search radius, we can skip further processing of this
178+ * candidate.
179+ *
180+ * Note: Not all encodings support lower bounds. If unsupported, use the standard GatherEvalEncoded.
181+ *
182+ * @param results Output span for the (potentially approximate) distances.
183+ * @param lowerBounds Output span for the lower-bound estimates.
184+ */
185+ template <class M , class E , class S >
186+ requires TagInvocable<GatherEvalEncodedWithLowerBoundsTag, M, E, const S&, const std::byte*,
187+ std::span<const uint64_t >, std::span<float >, std::span<float >>
188+ constexpr void operator ()(const M& m, const E& e, const S& s, const std::byte* recordsBase,
189+ std::span<const uint64_t > rowIds, std::span<float > results,
190+ std::span<float > lowerBounds) const
191+ noexcept (noexcept (TagInvoke(std::declval<GatherEvalEncodedWithLowerBoundsTag>(), m, e, s, recordsBase, rowIds,
192+ results, lowerBounds)))
193+ {
194+ TagInvoke (*this , m, e, s, recordsBase, rowIds, results, lowerBounds);
195+ }
196+
197+ template <class M , class E , class S , class DataSource >
198+ requires (encode_space::EncodedRowSource<std::remove_cvref_t <DataSource>> &&
199+ TagInvocable<GatherEvalEncodedWithLowerBoundsTag, M, E, const S&, const DataSource&,
200+ std::span<const uint64_t >, std::span<float >, std::span<float >>)
201+ constexpr void operator ()(const M& m, const E& e, const S& s, const DataSource& data,
202+ std::span<const uint64_t > rowIds, std::span<float > results,
203+ std::span<float > lowerBounds) const
204+ noexcept (noexcept (TagInvoke(std::declval<GatherEvalEncodedWithLowerBoundsTag>(), m, e, s, data, rowIds, results,
205+ lowerBounds)))
206+ {
207+ TagInvoke (*this , m, e, s, data, rowIds, results, lowerBounds);
208+ }
209+ };
210+ inline constexpr GatherEvalEncodedWithLowerBoundsTag GatherEvalEncodedWithLowerBounds {};
211+
62212} // namespace lumina::dist
0 commit comments