uqa_storage/diskann_index/format/
layout.rs1use super::{invalid, NODE_HEADER_BYTES, PAGE_PAYLOAD_BYTES};
8use crate::StorageBackendResult;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub struct DiskANNNodeLayout {
12 pub(super) dimensions: u32,
13 pub(super) max_degree: usize,
14 pub(super) node_count: u64,
15 pub(super) slot_bytes: usize,
16 nodes_per_page: u32,
17 fragments: u32,
18 page_count: u64,
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub struct DiskANNNodeAddress {
23 pub first_page: u64,
24 pub slot: u32,
25 pub fragments: u32,
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub struct DiskANNPageShape {
30 pub first_node: u64,
31 pub slots: u32,
32 pub fragment_index: u32,
33 pub fragments: u32,
34 pub payload_bytes: u32,
35}
36
37impl DiskANNNodeLayout {
38 pub fn new(dimensions: u32, max_degree: usize, node_count: u64) -> StorageBackendResult<Self> {
39 if dimensions == 0 || max_degree < 2 {
40 return Err(invalid(
41 "require positive dimensions and maximum degree at least two",
42 ));
43 }
44 let dimensions_usize =
45 usize::try_from(dimensions).map_err(|_| invalid("dimension range"))?;
46 let slot_bytes = max_degree
47 .checked_mul(8)
48 .and_then(|neighbors| {
49 dimensions_usize
50 .checked_mul(4)
51 .and_then(|vectors| vectors.checked_add(neighbors))
52 })
53 .and_then(|body| body.checked_add(NODE_HEADER_BYTES))
54 .ok_or_else(|| invalid("node slot size overflow"))?;
55 std::alloc::Layout::array::<u8>(slot_bytes)
56 .map_err(|_| invalid("node slot exceeds addressable memory"))?;
57 let fragments = u32::try_from(slot_bytes.div_ceil(PAGE_PAYLOAD_BYTES))
58 .map_err(|_| invalid("fragment count exceeds format"))?;
59 let nodes_per_page = (PAGE_PAYLOAD_BYTES / slot_bytes) as u32;
60 let page_count = if nodes_per_page == 0 {
61 node_count
62 .checked_mul(u64::from(fragments))
63 .ok_or_else(|| invalid("page count overflow"))?
64 } else {
65 node_count.div_ceil(u64::from(nodes_per_page))
66 };
67 Ok(Self {
68 dimensions,
69 max_degree,
70 node_count,
71 slot_bytes,
72 nodes_per_page,
73 fragments,
74 page_count,
75 })
76 }
77
78 pub fn dimensions(self) -> u32 {
79 self.dimensions
80 }
81 pub fn max_degree(self) -> usize {
82 self.max_degree
83 }
84 pub fn node_count(self) -> u64 {
85 self.node_count
86 }
87 pub fn slot_bytes(self) -> usize {
88 self.slot_bytes
89 }
90 pub fn page_count(self) -> u64 {
91 self.page_count
92 }
93
94 pub fn node_address(self, node: u64) -> StorageBackendResult<DiskANNNodeAddress> {
95 if node >= self.node_count {
96 return Err(invalid("node ID outside generation"));
97 }
98 if self.nodes_per_page > 0 {
99 let count = u64::from(self.nodes_per_page);
100 Ok(DiskANNNodeAddress {
101 first_page: node / count,
102 slot: (node % count) as u32,
103 fragments: 1,
104 })
105 } else {
106 Ok(DiskANNNodeAddress {
107 first_page: node * u64::from(self.fragments),
108 slot: 0,
109 fragments: self.fragments,
110 })
111 }
112 }
113
114 pub fn page_shape(self, page: u64) -> StorageBackendResult<DiskANNPageShape> {
115 if page >= self.page_count {
116 return Err(invalid("page ID outside generation"));
117 }
118 if self.nodes_per_page > 0 {
119 let first_node = page * u64::from(self.nodes_per_page);
120 let slots = (self.node_count - first_node).min(u64::from(self.nodes_per_page)) as u32;
121 Ok(DiskANNPageShape {
122 first_node,
123 slots,
124 fragment_index: 0,
125 fragments: 1,
126 payload_bytes: (slots as usize * self.slot_bytes) as u32,
127 })
128 } else {
129 let fragments = u64::from(self.fragments);
130 let fragment_index = (page % fragments) as u32;
131 let offset = fragment_index as usize * PAGE_PAYLOAD_BYTES;
132 Ok(DiskANNPageShape {
133 first_node: page / fragments,
134 slots: 1,
135 fragment_index,
136 fragments: self.fragments,
137 payload_bytes: (self.slot_bytes - offset).min(PAGE_PAYLOAD_BYTES) as u32,
138 })
139 }
140 }
141}