| // 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), |
| ) |
| }) |
| } |