use std::alloc::Layout;
use std::borrow::Cow;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicBool, Ordering};
use crate::common::counter::hardware_counter::HardwareCounterCell;
use crate::common::fs::atomic_save_json;
use crate::common::mmap::MmapFlusher;
use crate::common::typelevel::True;
use crate::common::types::PointOffsetType;
use fs_err as fs;
use serde::{Deserialize, Serialize};
use crate::quantization::EncodingError;
use crate::quantization::encoded_storage::{EncodedStorage, EncodedStorageBuilder};
use crate::quantization::encoded_vectors::{
DistanceType, EncodedVectors, VectorParameters, validate_vector_parameters,
};
use crate::quantization::quantile::{find_min_max_from_iter, find_quantile_interval};
pub const ALIGNMENT: usize = 16;
const ADDITIONAL_CONSTANT_SIZE: usize = std::mem::size_of::<f32>();
#[derive(Clone, PartialEq, Debug)]
pub enum ScalarQuantizationMethod {
Int8,
}
pub struct EncodedVectorsU8<TStorage: EncodedStorage> {
encoded_vectors: TStorage,
metadata: Metadata,
metadata_path: Option<PathBuf>,
}
pub struct EncodedQueryU8 {
offset: f32,
encoded_query: Vec<u8>,
}
#[derive(Serialize, Deserialize)]
#[serde(untagged)]
enum Metadata {
Int8(MetadataInt8),
}
impl Metadata {
pub fn vector_parameters(&self) -> &VectorParameters {
match self {
Metadata::Int8(meta) => &meta.vector_parameters,
}
}
pub fn actual_dim(&self) -> usize {
match self {
Metadata::Int8(meta) => meta.actual_dim,
}
}
pub fn postprocess_score(&self, score: f32, query_offset: f32, vector_offset: f32) -> f32 {
match self {
Metadata::Int8(metadata) => {
metadata.postprocess_score(score, query_offset, vector_offset)
}
}
}
pub fn postprocess_internal_score(
&self,
score: f32,
query_offset: f32,
vector_offset: f32,
) -> f32 {
match self {
Metadata::Int8(metadata) => {
metadata.postprocess_internal_score(score, query_offset, vector_offset)
}
}
}
}
#[derive(Serialize, Deserialize)]
struct MetadataInt8 {
actual_dim: usize,
alpha: f32,
offset: f32,
multiplier: f32,
vector_parameters: VectorParameters,
}
impl MetadataInt8 {
#[inline]
pub fn encode_value(&self, value: f32) -> u8 {
let i = (value - self.offset) / self.alpha;
i.clamp(0.0, 127.0).round() as u8
}
#[inline]
fn postprocess_score(&self, score: f32, query_offset: f32, vector_offset: f32) -> f32 {
self.multiplier * score + query_offset + vector_offset
}
#[inline]
fn postprocess_internal_score(
&self,
score: f32,
vector_1_offset: f32,
vector_2_offset: f32,
) -> f32 {
let query_offset = vector_1_offset - self.get_shift();
self.postprocess_score(score, query_offset, vector_2_offset)
}
fn get_shift(&self) -> f32 {
let shift = match self.vector_parameters.distance_type {
DistanceType::Dot | DistanceType::Cosine | DistanceType::L2 => {
self.actual_dim as f32 * self.offset * self.offset
}
DistanceType::L1 => 0.0,
};
if self.vector_parameters.invert {
-shift
} else {
shift
}
}
}
impl<TStorage: EncodedStorage> EncodedVectorsU8<TStorage> {
pub fn storage(&self) -> &TStorage {
&self.encoded_vectors
}
#[allow(clippy::too_many_arguments)]
pub fn encode<'a>(
orig_data: impl Iterator<Item = impl AsRef<[f32]> + 'a> + Clone,
mut storage_builder: impl EncodedStorageBuilder<Storage = TStorage>,
vector_parameters: &VectorParameters,
count: usize,
quantile: Option<f32>,
method: ScalarQuantizationMethod,
meta_path: Option<&Path>,
stopped: &AtomicBool,
) -> Result<Self, EncodingError> {
assert_eq!(method, ScalarQuantizationMethod::Int8);
let actual_dim = get_actual_dim(vector_parameters);
if count == 0 {
let metadata = Metadata::Int8(MetadataInt8 {
actual_dim,
alpha: 0.0,
offset: 0.0,
multiplier: 0.0,
vector_parameters: *vector_parameters,
});
if let Some(meta_path) = meta_path {
meta_path
.parent()
.ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Path must have a parent directory",
)
})
.and_then(fs::create_dir_all)
.map_err(|e| {
EncodingError::EncodingError(format!(
"Failed to create metadata directory: {e}",
))
})?;
atomic_save_json(meta_path, &metadata).map_err(|e| {
EncodingError::EncodingError(format!("Failed to save metadata: {e}",))
})?;
}
return Ok(EncodedVectorsU8 {
encoded_vectors: storage_builder.build().map_err(|e| {
EncodingError::EncodingError(format!("Failed to build storage: {e}",))
})?,
metadata,
metadata_path: meta_path.map(PathBuf::from),
});
}
debug_assert!(validate_vector_parameters(orig_data.clone(), vector_parameters).is_ok());
let (alpha, offset) = Self::find_alpha_offset_size_dim(orig_data.clone());
let (alpha, offset) = if let Some(quantile) = quantile {
if let Some((min, max)) = find_quantile_interval(
orig_data.clone(),
vector_parameters.dim,
count,
quantile,
stopped,
)? {
Self::alpha_offset_from_min_max(min, max)
} else {
(alpha, offset)
}
} else {
(alpha, offset)
};
let multiplier = match vector_parameters.distance_type {
DistanceType::Dot | DistanceType::Cosine => alpha * alpha,
DistanceType::L1 => alpha,
DistanceType::L2 => -2.0 * alpha * alpha,
};
let multiplier = if vector_parameters.invert {
-multiplier
} else {
multiplier
};
let metadata = MetadataInt8 {
actual_dim,
alpha,
offset,
multiplier,
vector_parameters: *vector_parameters,
};
for vector in orig_data {
if stopped.load(Ordering::Relaxed) {
return Err(EncodingError::Stopped);
}
let mut encoded_vector = Vec::with_capacity(actual_dim + ADDITIONAL_CONSTANT_SIZE);
encoded_vector.extend_from_slice(&f32::default().to_ne_bytes());
for &value in vector.as_ref() {
let encoded = metadata.encode_value(value);
encoded_vector.push(encoded);
}
if !vector_parameters.dim.is_multiple_of(ALIGNMENT) {
for _ in 0..(ALIGNMENT - vector_parameters.dim % ALIGNMENT) {
let placeholder = match vector_parameters.distance_type {
DistanceType::Dot | DistanceType::Cosine => 0.0,
DistanceType::L1 | DistanceType::L2 => offset,
};
let encoded = metadata.encode_value(placeholder);
encoded_vector.push(encoded);
}
}
let vector_offset = match vector_parameters.distance_type {
DistanceType::Dot | DistanceType::Cosine => {
let elements_sum = encoded_vector.iter().map(|&x| f32::from(x)).sum::<f32>();
elements_sum * alpha * offset
}
DistanceType::L1 => 0.0,
DistanceType::L2 => {
let elements_sqr_sum = encoded_vector
.iter()
.map(|&x| f32::from(x) * f32::from(x))
.sum::<f32>();
elements_sqr_sum * alpha * alpha
}
};
let vector_offset = if vector_parameters.invert {
-vector_offset
} else {
vector_offset
};
let vector_offset = metadata.get_shift() + vector_offset;
encoded_vector[0..ADDITIONAL_CONSTANT_SIZE]
.copy_from_slice(&vector_offset.to_ne_bytes());
storage_builder
.push_vector_data(&encoded_vector)
.map_err(|e| {
EncodingError::EncodingError(format!("Failed to push encoded vector: {e}",))
})?;
}
let encoded_vectors = storage_builder
.build()
.map_err(|e| EncodingError::EncodingError(format!("Failed to build storage: {e}",)))?;
let metadata = Metadata::Int8(metadata);
if let Some(meta_path) = meta_path {
meta_path
.parent()
.ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Path must have a parent directory",
)
})
.and_then(fs::create_dir_all)
.map_err(|e| {
EncodingError::EncodingError(format!(
"Failed to create metadata directory: {e}",
))
})?;
atomic_save_json(meta_path, &metadata).map_err(|e| {
EncodingError::EncodingError(format!("Failed to save metadata: {e}",))
})?;
}
Ok(EncodedVectorsU8 {
encoded_vectors,
metadata,
metadata_path: meta_path.map(PathBuf::from),
})
}
pub fn load(encoded_vectors: TStorage, meta_path: &Path) -> std::io::Result<Self> {
let contents = fs::read_to_string(meta_path)?;
let metadata: Metadata = serde_json::from_str(&contents)?;
let result = Self {
encoded_vectors,
metadata,
metadata_path: Some(meta_path.to_path_buf()),
};
Ok(result)
}
pub fn score_point_simple(&self, query: &EncodedQueryU8, bytes: &[u8]) -> f32 {
match &self.metadata {
Metadata::Int8(metadata) => {
let (vector_offset, v_ptr) = Self::parse_vec_data(bytes);
let q_ptr = query.encoded_query.as_ptr();
let score = match metadata.vector_parameters.distance_type {
DistanceType::Dot | DistanceType::Cosine | DistanceType::L2 => {
impl_score_dot(q_ptr, v_ptr, metadata.actual_dim)
}
DistanceType::L1 => impl_score_l1(q_ptr, v_ptr, metadata.actual_dim),
};
self.metadata
.postprocess_score(score as f32, query.offset, vector_offset)
}
}
}
pub fn score_point_simple_internal(&self, i: PointOffsetType, j: PointOffsetType) -> f32 {
match &self.metadata {
Metadata::Int8(metadata) => {
let (query_offset, q_ptr) = self.get_vec_ptr(i);
let (vector_offset, v_ptr) = self.get_vec_ptr(j);
let score = match metadata.vector_parameters.distance_type {
DistanceType::Dot | DistanceType::Cosine | DistanceType::L2 => {
impl_score_dot(q_ptr, v_ptr, metadata.actual_dim)
}
DistanceType::L1 => impl_score_l1(q_ptr, v_ptr, metadata.actual_dim),
};
self.metadata
.postprocess_internal_score(score as f32, query_offset, vector_offset)
}
}
}
#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
pub fn score_point_neon(&self, query: &EncodedQueryU8, bytes: &[u8]) -> f32 {
match &self.metadata {
Metadata::Int8(metadata) => {
let (vector_offset, v_ptr) = Self::parse_vec_data(bytes);
let q_ptr = query.encoded_query.as_ptr();
let score = match metadata.vector_parameters.distance_type {
DistanceType::Dot | DistanceType::Cosine | DistanceType::L2 => unsafe {
impl_score_dot_neon(q_ptr, v_ptr, metadata.actual_dim as u32)
},
DistanceType::L1 => unsafe {
impl_score_l1_neon(q_ptr, v_ptr, metadata.actual_dim as u32)
},
};
self.metadata
.postprocess_score(score, query.offset, vector_offset)
}
}
}
#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
pub fn score_point_neon_internal(&self, i: PointOffsetType, j: PointOffsetType) -> f32 {
match &self.metadata {
Metadata::Int8(metadata) => {
let (query_offset, q_ptr) = self.get_vec_ptr(i);
let (vector_offset, v_ptr) = self.get_vec_ptr(j);
let score = match metadata.vector_parameters.distance_type {
DistanceType::Dot | DistanceType::Cosine | DistanceType::L2 => unsafe {
impl_score_dot_neon(q_ptr, v_ptr, metadata.actual_dim as u32)
},
DistanceType::L1 => unsafe {
impl_score_l1_neon(q_ptr, v_ptr, metadata.actual_dim as u32)
},
};
self.metadata
.postprocess_internal_score(score, query_offset, vector_offset)
}
}
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub fn score_point_sse(&self, query: &EncodedQueryU8, bytes: &[u8]) -> f32 {
match &self.metadata {
Metadata::Int8(metadata) => {
let (vector_offset, v_ptr) = Self::parse_vec_data(bytes);
let q_ptr = query.encoded_query.as_ptr();
let score = match metadata.vector_parameters.distance_type {
DistanceType::Dot | DistanceType::Cosine | DistanceType::L2 => unsafe {
impl_score_dot_sse(q_ptr, v_ptr, metadata.actual_dim as u32)
},
DistanceType::L1 => unsafe {
impl_score_l1_sse(q_ptr, v_ptr, metadata.actual_dim as u32)
},
};
self.metadata
.postprocess_score(score, query.offset, vector_offset)
}
}
}
#[cfg(target_arch = "x86_64")]
pub fn score_point_sse_internal(&self, i: PointOffsetType, j: PointOffsetType) -> f32 {
match &self.metadata {
Metadata::Int8(metadata) => {
let (query_offset, q_ptr) = self.get_vec_ptr(i);
let (vector_offset, v_ptr) = self.get_vec_ptr(j);
let score = match metadata.vector_parameters.distance_type {
DistanceType::Dot | DistanceType::Cosine | DistanceType::L2 => unsafe {
impl_score_dot_sse(q_ptr, v_ptr, metadata.actual_dim as u32)
},
DistanceType::L1 => unsafe {
impl_score_l1_sse(q_ptr, v_ptr, metadata.actual_dim as u32)
},
};
self.metadata
.postprocess_internal_score(score, query_offset, vector_offset)
}
}
}
#[cfg(target_arch = "x86_64")]
pub fn score_point_avx(&self, query: &EncodedQueryU8, bytes: &[u8]) -> f32 {
match &self.metadata {
Metadata::Int8(metadata) => {
let (vector_offset, v_ptr) = Self::parse_vec_data(bytes);
let q_ptr = query.encoded_query.as_ptr();
let score = match metadata.vector_parameters.distance_type {
DistanceType::Dot | DistanceType::Cosine | DistanceType::L2 => unsafe {
impl_score_dot_avx(q_ptr, v_ptr, metadata.actual_dim as u32)
},
DistanceType::L1 => unsafe {
impl_score_l1_avx(q_ptr, v_ptr, metadata.actual_dim as u32)
},
};
self.metadata
.postprocess_score(score, query.offset, vector_offset)
}
}
}
#[cfg(target_arch = "x86_64")]
pub fn score_point_avx_internal(&self, i: PointOffsetType, j: PointOffsetType) -> f32 {
match &self.metadata {
Metadata::Int8(metadata) => {
let (query_offset, q_ptr) = self.get_vec_ptr(i);
let (vector_offset, v_ptr) = self.get_vec_ptr(j);
let score = match metadata.vector_parameters.distance_type {
DistanceType::Dot | DistanceType::Cosine | DistanceType::L2 => unsafe {
impl_score_dot_avx(q_ptr, v_ptr, metadata.actual_dim as u32)
},
DistanceType::L1 => unsafe {
impl_score_l1_avx(q_ptr, v_ptr, metadata.actual_dim as u32)
},
};
self.metadata
.postprocess_internal_score(score, query_offset, vector_offset)
}
}
}
fn find_alpha_offset_size_dim<'a>(
orig_data: impl Iterator<Item = impl AsRef<[f32]> + 'a> + Clone,
) -> (f32, f32) {
let (min, max) = find_min_max_from_iter(orig_data);
Self::alpha_offset_from_min_max(min, max)
}
fn alpha_offset_from_min_max(min: f32, max: f32) -> (f32, f32) {
let alpha = (max - min) / 127.0;
let offset = min;
(alpha, offset)
}
#[inline]
fn parse_vec_data(data: &[u8]) -> (f32, *const u8) {
debug_assert!(data.len() >= ADDITIONAL_CONSTANT_SIZE);
unsafe {
let offset = data.as_ptr().cast::<f32>().read_unaligned();
let v_ptr = data.as_ptr().add(ADDITIONAL_CONSTANT_SIZE);
(offset, v_ptr)
}
}
#[inline]
fn get_vec_ptr(&self, i: PointOffsetType) -> (f32, *const u8) {
let data = self.encoded_vectors.get_vector_data(i);
Self::parse_vec_data(&data)
}
pub fn get_quantized_vector(&self, i: PointOffsetType) -> Cow<'_, [u8]> {
self.encoded_vectors.get_vector_data(i)
}
pub fn layout(&self) -> Layout {
Layout::from_size_align(self.quantized_vector_size(), align_of::<u8>()).unwrap()
}
pub fn get_quantized_vector_offset_and_code(&self, i: PointOffsetType) -> (f32, &[u8]) {
let (offset, v_ptr) = self.get_vec_ptr(i);
let vector_data_size = self.metadata.actual_dim();
let code = unsafe { std::slice::from_raw_parts(v_ptr, vector_data_size) };
(offset, code)
}
pub fn get_multiplier(&self) -> f32 {
match &self.metadata {
Metadata::Int8(meta) => meta.multiplier,
}
}
pub fn get_shift(&self) -> f32 {
match &self.metadata {
Metadata::Int8(metadata) => metadata.get_shift(),
}
}
fn encode_int8_query(metadata: &MetadataInt8, query: &[f32]) -> EncodedQueryU8 {
let dim = query.len();
let mut query: Vec<_> = query.iter().map(|&v| metadata.encode_value(v)).collect();
if !dim.is_multiple_of(ALIGNMENT) {
for _ in 0..(ALIGNMENT - dim % ALIGNMENT) {
let placeholder = match metadata.vector_parameters.distance_type {
DistanceType::Dot | DistanceType::Cosine => 0.0,
DistanceType::L1 | DistanceType::L2 => metadata.offset,
};
let encoded = metadata.encode_value(placeholder);
query.push(encoded);
}
}
let offset = match metadata.vector_parameters.distance_type {
DistanceType::Dot | DistanceType::Cosine => {
let query_elements_sum = query.iter().map(|&x| f32::from(x)).sum::<f32>();
query_elements_sum * metadata.alpha * metadata.offset
}
DistanceType::L1 => 0.0,
DistanceType::L2 => {
let query_elements_sqr_sum = query
.iter()
.map(|&x| f32::from(x) * f32::from(x))
.sum::<f32>();
query_elements_sqr_sum * metadata.alpha * metadata.alpha
}
};
let offset = if metadata.vector_parameters.invert {
-offset
} else {
offset
};
EncodedQueryU8 {
offset,
encoded_query: query,
}
}
}
pub fn get_actual_dim(vector_parameters: &VectorParameters) -> usize {
vector_parameters.dim + (ALIGNMENT - vector_parameters.dim % ALIGNMENT) % ALIGNMENT
}
pub fn get_quantized_vector_size(vector_parameters: &VectorParameters) -> usize {
let actual_dim = get_actual_dim(vector_parameters);
actual_dim + ADDITIONAL_CONSTANT_SIZE
}
impl<TStorage: EncodedStorage> EncodedVectors for EncodedVectorsU8<TStorage> {
type EncodedQuery = EncodedQueryU8;
fn is_in_ram_or_mmap() -> bool {
TStorage::is_in_ram_or_mmap()
}
fn is_on_disk(&self) -> bool {
self.encoded_vectors.is_on_disk()
}
fn encode_query(&self, query: &[f32]) -> EncodedQueryU8 {
match &self.metadata {
Metadata::Int8(meta) => Self::encode_int8_query(meta, query),
}
}
fn iter_batch(
&self,
offsets: &[PointOffsetType],
) -> impl Iterator<Item = (usize, Cow<'_, [u8]>)> {
self.encoded_vectors.iter_batch(offsets)
}
fn score(
&self,
query: &Self::EncodedQuery,
encoded_vector: &[u8],
hw_counter: &HardwareCounterCell,
) -> f32 {
self.score_bytes(True, query, encoded_vector, hw_counter)
}
fn score_point(
&self,
query: &EncodedQueryU8,
i: PointOffsetType,
hw_counter: &HardwareCounterCell,
) -> f32 {
let bytes = self.encoded_vectors.get_vector_data(i);
self.score_bytes(True, query, &bytes, hw_counter)
}
fn score_internal(
&self,
i: PointOffsetType,
j: PointOffsetType,
hw_counter: &HardwareCounterCell,
) -> f32 {
hw_counter
.cpu_counter()
.incr_delta(self.metadata.vector_parameters().dim);
hw_counter
.vector_io_read()
.incr_delta(self.metadata.vector_parameters().dim * 2);
#[cfg(target_arch = "x86_64")]
if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
return self.score_point_avx_internal(i, j);
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
if is_x86_feature_detected!("sse4.1") {
return self.score_point_sse_internal(i, j);
}
#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
if std::arch::is_aarch64_feature_detected!("neon") {
return self.score_point_neon_internal(i, j);
}
self.score_point_simple_internal(i, j)
}
fn quantized_vector_size(&self) -> usize {
match &self.metadata {
Metadata::Int8(_) => self.metadata.actual_dim() + ADDITIONAL_CONSTANT_SIZE,
}
}
fn encode_internal_vector(&self, id: PointOffsetType) -> Option<EncodedQueryU8> {
match &self.metadata {
Metadata::Int8(metadata) => {
let (vector_offset, q_ptr) = self.get_vec_ptr(id);
let query_offset = vector_offset - metadata.get_shift();
Some(EncodedQueryU8 {
offset: query_offset,
encoded_query: unsafe {
std::slice::from_raw_parts(q_ptr, metadata.actual_dim).to_vec()
},
})
}
}
}
fn upsert_vector(
&mut self,
_id: PointOffsetType,
_vector: &[f32],
_hw_counter: &HardwareCounterCell,
) -> std::io::Result<()> {
debug_assert!(false, "SQ does not support upsert_vector",);
Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"SQ does not support upsert_vector",
))
}
fn vectors_count(&self) -> usize {
self.encoded_vectors.vectors_count()
}
fn flusher(&self) -> MmapFlusher {
self.encoded_vectors.flusher()
}
fn files(&self) -> Vec<PathBuf> {
let mut files = self.encoded_vectors.files();
if let Some(meta_path) = &self.metadata_path {
files.push(meta_path.clone());
}
files
}
fn immutable_files(&self) -> Vec<PathBuf> {
let mut files = self.encoded_vectors.immutable_files();
if let Some(meta_path) = &self.metadata_path {
files.push(meta_path.clone());
}
files
}
fn heap_size_bytes(&self) -> usize {
let Self {
encoded_vectors,
metadata: _,
metadata_path: _,
} = self;
encoded_vectors.heap_size_bytes()
}
type SupportsBytes = True;
fn score_bytes(
&self,
_: Self::SupportsBytes,
query: &Self::EncodedQuery,
bytes: &[u8],
hw_counter: &HardwareCounterCell,
) -> f32 {
hw_counter
.cpu_counter()
.incr_delta(self.metadata.vector_parameters().dim);
debug_assert!(bytes.len() >= ADDITIONAL_CONSTANT_SIZE + self.metadata.actual_dim());
#[cfg(target_arch = "x86_64")]
if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
return self.score_point_avx(query, bytes);
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
if is_x86_feature_detected!("sse4.1") {
return self.score_point_sse(query, bytes);
}
#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
if std::arch::is_aarch64_feature_detected!("neon") {
return self.score_point_neon(query, bytes);
}
self.score_point_simple(query, bytes)
}
}
fn impl_score_dot(q_ptr: *const u8, v_ptr: *const u8, actual_dim: usize) -> i32 {
unsafe {
let mut score = 0i32;
for i in 0..actual_dim {
score += i32::from(*q_ptr.add(i)) * i32::from(*v_ptr.add(i));
}
score
}
}
fn impl_score_l1(q_ptr: *const u8, v_ptr: *const u8, actual_dim: usize) -> i32 {
unsafe {
let mut score = 0i32;
for i in 0..actual_dim {
score += i32::from(*q_ptr.add(i)).abs_diff(i32::from(*v_ptr.add(i))) as i32;
}
score
}
}
#[cfg(target_arch = "x86_64")]
unsafe extern "C" {
fn impl_score_dot_avx(query_ptr: *const u8, vector_ptr: *const u8, dim: u32) -> f32;
fn impl_score_l1_avx(query_ptr: *const u8, vector_ptr: *const u8, dim: u32) -> f32;
fn impl_score_dot_sse(query_ptr: *const u8, vector_ptr: *const u8, dim: u32) -> f32;
fn impl_score_l1_sse(query_ptr: *const u8, vector_ptr: *const u8, dim: u32) -> f32;
}
#[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
unsafe extern "C" {
fn impl_score_dot_neon(query_ptr: *const u8, vector_ptr: *const u8, dim: u32) -> f32;
fn impl_score_l1_neon(query_ptr: *const u8, vector_ptr: *const u8, dim: u32) -> f32;
}