blob: e5f279626e5a42229686842652e48100d3e01e3c [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use crate::distance::MetricType;
use crate::hnsw::{HnswBuildParams, HnswGraph};
use crate::io::{PreadCursor, SeekRead, SeekWrite};
use roaring::RoaringTreemap;
use std::io;
pub(crate) fn validate_search_inputs(
queries: &[f32],
nq: usize,
d: usize,
k: usize,
nprobe: usize,
) -> io::Result<()> {
if nq == 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"nq must be greater than 0",
));
}
let expected_query_len = nq.checked_mul(d).ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidInput,
"nq * dimension overflows usize",
)
})?;
if queries.len() != expected_query_len {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!(
"queries length {} does not match nq * dimension {}",
queries.len(),
expected_query_len
),
));
}
if k == 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"k must be greater than 0",
));
}
if nprobe == 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"nprobe must be greater than 0",
));
}
Ok(())
}
pub(crate) fn validate_reserved_zero(bytes: &[u8], format_name: &str) -> io::Result<()> {
if bytes.iter().any(|&byte| byte != 0) {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("{} reserved bytes must be zero", format_name),
));
}
Ok(())
}
const HNSW_GRAPH_MAGIC: u32 = 0x48574752; // "HWGR"
const HNSW_GRAPH_VERSION: u32 = 1;
const HNSW_GRAPH_FLAG_DELTA_VARINT: u32 = 1 << 0;
const HNSW_GRAPH_REQUIRED_FLAGS: u32 = HNSW_GRAPH_FLAG_DELTA_VARINT;
const HNSW_GRAPH_SUPPORTED_FLAGS: u32 = HNSW_GRAPH_REQUIRED_FLAGS;
pub(crate) fn encode_delta_varint_ids(ids: &[i64]) -> (i64, Vec<u8>) {
if ids.is_empty() {
return (0, Vec::new());
}
let base = ids[0];
let mut buf = Vec::with_capacity(ids.len() * 2);
let mut prev = base;
for &id in ids {
let delta = (id as u64).wrapping_sub(prev as u64);
write_u64_varint(&mut buf, delta);
prev = id;
}
(base, buf)
}
pub(crate) fn decode_delta_varint_ids(base: i64, buf: &[u8], count: usize) -> io::Result<Vec<i64>> {
let mut ids = Vec::with_capacity(count);
let mut pos = 0;
let mut current = base as u64;
let mut prev_signed = base;
for _ in 0..count {
let delta = read_u64_varint(buf, &mut pos)?;
current = current.wrapping_add(delta);
let id = current as i64;
if id < prev_signed {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"decoded ID sequence is not monotonically non-decreasing",
));
}
prev_signed = id;
ids.push(id);
}
if pos != buf.len() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"trailing bytes in delta-varint ID section",
));
}
Ok(ids)
}
pub(crate) fn encode_graph(graph: Option<&HnswGraph>) -> io::Result<Vec<u8>> {
let Some(graph) = graph else {
return Ok(Vec::new());
};
let mut buf = Vec::new();
write_u32_fixed(&mut buf, HNSW_GRAPH_MAGIC)?;
write_u32_fixed(&mut buf, HNSW_GRAPH_VERSION)?;
write_u32_fixed(&mut buf, HNSW_GRAPH_REQUIRED_FLAGS)?;
write_u32_varint(&mut buf, graph.len())?;
write_u32_varint(&mut buf, graph.entry_point())?;
write_u32_varint(&mut buf, graph.max_observed_level())?;
for &level in graph.levels() {
write_u32_varint(&mut buf, level)?;
}
for node_levels in graph.neighbors() {
for level_neighbors in node_levels {
write_u32_varint(&mut buf, level_neighbors.len())?;
let mut sorted_neighbors = level_neighbors.clone();
sorted_neighbors.sort_unstable();
let mut previous = 0usize;
for neighbor in sorted_neighbors {
let delta = neighbor.checked_sub(previous).ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidInput,
"graph neighbor delta underflow",
)
})?;
write_u32_varint(&mut buf, delta)?;
previous = neighbor;
}
}
}
Ok(buf)
}
#[cfg(test)]
pub(crate) fn encode_graph_u32_for_size_estimate(graph: &HnswGraph) -> io::Result<Vec<u8>> {
let mut buf = Vec::new();
write_u32_fixed(&mut buf, checked_u32_value(graph.len())?)?;
write_u32_fixed(&mut buf, checked_u32_value(graph.entry_point())?)?;
write_u32_fixed(&mut buf, checked_u32_value(graph.max_observed_level())?)?;
for &level in graph.levels() {
write_u32_fixed(&mut buf, checked_u32_value(level)?)?;
}
for node_levels in graph.neighbors() {
for level_neighbors in node_levels {
write_u32_fixed(&mut buf, checked_u32_value(level_neighbors.len())?)?;
for &neighbor in level_neighbors {
write_u32_fixed(&mut buf, checked_u32_value(neighbor)?)?;
}
}
}
Ok(buf)
}
pub(crate) fn decode_graph(
bytes: &[u8],
vectors: Vec<f32>,
count: usize,
d: usize,
metric: MetricType,
hnsw_params: HnswBuildParams,
) -> io::Result<Option<HnswGraph>> {
if bytes.is_empty() {
return Ok(None);
}
let mut pos = 0usize;
let graph_magic = read_u32_fixed(bytes, &mut pos)?;
if graph_magic != HNSW_GRAPH_MAGIC {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("Invalid HNSW graph magic: 0x{:08X}", graph_magic),
));
}
let graph_version = read_u32_fixed(bytes, &mut pos)?;
if graph_version != HNSW_GRAPH_VERSION {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("Unsupported HNSW graph version: {}", graph_version),
));
}
let graph_flags = read_u32_fixed(bytes, &mut pos)?;
let unknown_graph_flags = graph_flags & !HNSW_GRAPH_SUPPORTED_FLAGS;
if unknown_graph_flags != 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"Unsupported HNSW graph flags: 0x{:08X}",
unknown_graph_flags
),
));
}
if graph_flags & HNSW_GRAPH_REQUIRED_FLAGS != HNSW_GRAPH_REQUIRED_FLAGS {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"HNSW graph v1 requires delta-varint adjacency encoding",
));
}
let graph_count = read_u32_varint(bytes, &mut pos)? as usize;
if graph_count != count {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"graph count {} does not match list count {}",
graph_count, count
),
));
}
let entry_point = read_u32_varint(bytes, &mut pos)? as usize;
let max_observed_level = read_u32_varint(bytes, &mut pos)? as usize;
let mut levels = Vec::with_capacity(count);
for node in 0..count {
let level = read_u32_varint(bytes, &mut pos)? as usize;
if level >= hnsw_params.max_level {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"graph node {} level {} exceeds max_level {}",
node,
level,
hnsw_params.max_level - 1
),
));
}
levels.push(level);
}
let mut neighbors = Vec::with_capacity(count);
for (node, &level) in levels.iter().enumerate() {
let mut node_levels = Vec::with_capacity(level + 1);
for graph_level in 0..=level {
let degree = read_u32_varint(bytes, &mut pos)? as usize;
let max_degree = if graph_level == 0 {
hnsw_params.m.saturating_mul(2)
} else {
hnsw_params.m
};
if degree > max_degree {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"graph node {} degree {} at level {} exceeds max degree {}",
node, degree, graph_level, max_degree
),
));
}
let mut level_neighbors = Vec::with_capacity(degree);
let mut previous = 0usize;
for _ in 0..degree {
let delta = read_u32_varint(bytes, &mut pos)? as usize;
let neighbor = previous.checked_add(delta).ok_or_else(|| {
io::Error::new(io::ErrorKind::InvalidData, "graph neighbor id overflow")
})?;
if neighbor > u32::MAX as usize {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("graph neighbor id {} is out of range", neighbor),
));
}
level_neighbors.push(neighbor);
previous = neighbor;
}
node_levels.push(level_neighbors);
}
neighbors.push(node_levels);
}
if pos != bytes.len() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"trailing bytes in HNSW graph section",
));
}
Ok(Some(HnswGraph::from_parts(
vectors,
count,
d,
metric,
levels,
neighbors,
entry_point,
max_observed_level,
hnsw_params,
)?))
}
fn checked_u32_value(value: usize) -> io::Result<u32> {
if value > u32::MAX as usize {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("value {} exceeds u32 limit", value),
));
}
Ok(value as u32)
}
fn write_u32_fixed(buf: &mut Vec<u8>, value: u32) -> io::Result<()> {
buf.extend_from_slice(&value.to_le_bytes());
Ok(())
}
fn read_u32_fixed(bytes: &[u8], pos: &mut usize) -> io::Result<u32> {
let end = pos.checked_add(4).ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidData,
"graph section position overflow",
)
})?;
if end > bytes.len() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"truncated HNSW graph section",
));
}
let value = u32::from_le_bytes(bytes[*pos..end].try_into().unwrap());
*pos = end;
Ok(value)
}
fn write_u32_varint(buf: &mut Vec<u8>, value: usize) -> io::Result<()> {
write_u64_varint(buf, checked_u32_value(value)? as u64);
Ok(())
}
fn write_u64_varint(buf: &mut Vec<u8>, mut value: u64) {
while value >= 0x80 {
buf.push((value as u8 & 0x7f) | 0x80);
value >>= 7;
}
buf.push(value as u8);
}
fn read_u32_varint(bytes: &[u8], pos: &mut usize) -> io::Result<u32> {
let value = read_u64_varint(bytes, pos)?;
u32::try_from(value).map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("HNSW graph varint value {} exceeds u32 limit", value),
)
})
}
fn read_u64_varint(bytes: &[u8], pos: &mut usize) -> io::Result<u64> {
let mut value = 0u64;
let mut shift = 0u32;
for _ in 0..10 {
if *pos >= bytes.len() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"truncated HNSW graph varint",
));
}
let byte = bytes[*pos];
*pos += 1;
if shift == 63 && (byte & 0x7e) != 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"HNSW graph varint exceeds u64 limit",
));
}
value |= ((byte & 0x7f) as u64) << shift;
if byte & 0x80 == 0 {
return Ok(value);
}
shift += 7;
}
Err(io::Error::new(
io::ErrorKind::InvalidData,
"HNSW graph varint exceeds u64 limit",
))
}
pub(crate) fn write_u32_le(out: &mut dyn SeekWrite, v: u32) -> io::Result<()> {
out.write_all(&v.to_le_bytes())
}
pub(crate) fn write_i32_le(out: &mut dyn SeekWrite, v: i32) -> io::Result<()> {
out.write_all(&v.to_le_bytes())
}
pub(crate) fn write_i64_le(out: &mut dyn SeekWrite, v: i64) -> io::Result<()> {
out.write_all(&v.to_le_bytes())
}
pub(crate) fn write_f32_slice(out: &mut dyn SeekWrite, data: &[f32]) -> io::Result<()> {
let bytes: Vec<u8> = data.iter().flat_map(|f| f.to_le_bytes()).collect();
out.write_all(&bytes)
}
pub(crate) fn read_u32_le<R: SeekRead + ?Sized>(
reader: &mut PreadCursor<'_, R>,
) -> io::Result<u32> {
let mut buf = [0u8; 4];
reader.read_exact(&mut buf)?;
Ok(u32::from_le_bytes(buf))
}
pub(crate) fn read_i32_le<R: SeekRead + ?Sized>(
reader: &mut PreadCursor<'_, R>,
) -> io::Result<i32> {
let mut buf = [0u8; 4];
reader.read_exact(&mut buf)?;
Ok(i32::from_le_bytes(buf))
}
pub(crate) fn read_i64_le<R: SeekRead + ?Sized>(
reader: &mut PreadCursor<'_, R>,
) -> io::Result<i64> {
let mut buf = [0u8; 8];
reader.read_exact(&mut buf)?;
Ok(i64::from_le_bytes(buf))
}
pub(crate) fn read_f32_vec<R: SeekRead + ?Sized>(
reader: &mut PreadCursor<'_, R>,
count: usize,
) -> io::Result<Vec<f32>> {
let byte_len = count.checked_mul(4).ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidData,
"f32 section byte length overflow",
)
})?;
let mut buf = vec![0u8; byte_len];
reader.read_exact(&mut buf)?;
bytes_to_f32_vec(&buf)
}
pub(crate) fn bytes_to_f32_vec(bytes: &[u8]) -> io::Result<Vec<f32>> {
if !bytes.len().is_multiple_of(4) {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"f32 byte section is not 4-byte aligned",
));
}
Ok(bytes
.chunks_exact(4)
.map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
.collect())
}
pub(crate) fn validate_positive_i32(val: i32, field: &str) -> io::Result<i32> {
if val <= 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("invalid header field {}: {} (must be positive)", field, val),
));
}
Ok(val)
}
pub(crate) fn usize_to_i32(value: usize, field: &str) -> io::Result<i32> {
if value > i32::MAX as usize {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("{} exceeds i32 length limit: {}", field, value),
));
}
Ok(value as i32)
}
pub(crate) fn usize_to_i64(value: usize, field: &str) -> io::Result<i64> {
if value > i64::MAX as usize {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("{} exceeds i64 length limit: {}", field, value),
));
}
Ok(value as i64)
}
pub(crate) fn u64_to_i64(value: u64, field: &str) -> io::Result<i64> {
if value > i64::MAX as u64 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("{} exceeds i64 offset limit: {}", field, value),
));
}
Ok(value as i64)
}
const MAX_SECTION_ELEMENTS: usize = 1 << 30;
pub(crate) fn checked_section_size(a: usize, b: usize) -> io::Result<usize> {
let result = a
.checked_mul(b)
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "section size overflow"))?;
if result > MAX_SECTION_ELEMENTS {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"section size {} exceeds maximum {}",
result, MAX_SECTION_ELEMENTS
),
));
}
Ok(result)
}
pub(crate) fn checked_list_offset(offset: i64, list_id: usize) -> io::Result<u64> {
if offset < 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("negative list offset {} at list {}", offset, list_id),
));
}
Ok(offset as u64)
}
pub(crate) fn checked_list_bytes(count: usize, bytes_per_entry: usize) -> io::Result<usize> {
count
.checked_mul(bytes_per_entry)
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "list byte size overflow"))
}
pub(crate) fn decode_roaring_filter(bytes: &[u8]) -> io::Result<RoaringTreemap> {
RoaringTreemap::deserialize_from(bytes).map_err(|e| {
io::Error::new(
io::ErrorKind::InvalidInput,
format!("invalid RoaringTreemap filter: {}", e),
)
})
}