urna_runtime/
mmap_file.rs1use std::path::Path;
6
7use memmap2::Mmap;
8use urna_format::layout::{
9 SECTION_BM25_INDEX, SECTION_CHUNK_IDS, SECTION_CHUNKS_ORIGINAL_SPANS, SECTION_EMBEDDINGS,
10 SECTION_EMBEDDINGS_FP, SECTION_GRAPH_ADJACENCY, SECTION_HNSW_INDEX,
11};
12use urna_format::reader::{UrnaView, validate_slab_values};
13use urna_format::sections::{
14 BlobRefRecord, OriginalSpan, decode_chunk_ids, decode_chunks_original_spans,
15};
16
17use crate::ann;
18use crate::bm25;
19use crate::dtype::DType;
20use crate::error::RuntimeError;
21use crate::graph::CsrIndex;
22use crate::materialize::PackedVectors;
23use crate::rerank::FpSlab;
24use crate::simd::{self, SimdBackend};
25
26pub struct MmapUrnaFile {
27 pub(crate) _mmap: Mmap,
28 pub(crate) embedding_dim: usize,
29 pub(crate) n_embeddings: usize,
30 pub(crate) dtype: DType,
31 pub(crate) embeddings_offset: usize,
33 pub(crate) embeddings_size: usize,
35 pub(crate) embeddings_fp: Option<FpSlab>,
38 pub(crate) chunk_ids: Vec<String>,
39 pub(crate) spans: Vec<OriginalSpan>,
40 pub(crate) embedding_model: String,
41 pub(crate) model_hash: String,
45 pub(crate) file_hash: String,
46 pub(crate) content_hash: String,
47 pub(crate) ann_index: Option<ann::HnswIndex>,
50 pub(crate) bm25_index: Option<bm25::Bm25Index>,
52 pub(crate) graph_index: Option<CsrIndex>,
56 pub(crate) blob_refs: Option<Vec<BlobRefRecord>>,
60 pub(crate) blob_data: Option<crate::blobs::OpenBlobData>,
63 pub(crate) spaces: Option<Vec<crate::spaces::OpenSpace>>,
67 pub(crate) declared_index_type: String,
70 pub(crate) declared_score_type: String,
71}
72
73impl MmapUrnaFile {
74 pub fn open(path: &Path) -> Result<Self, RuntimeError> {
75 let file = std::fs::File::open(path)?;
76 let mmap = unsafe { Mmap::map(&file)? };
83 let view = UrnaView::from_bytes(&mmap)?;
84 view.validate_embeddings_values()?;
85
86 let dim = view.header.embedding_dim as usize;
87 let n = view.header.n_embeddings as usize;
88 let dtype = DType::from_str(&view.manifest.dtype)?;
89
90 let entry = view.entry(SECTION_EMBEDDINGS)?;
91 let embeddings_offset = entry.offset as usize;
92 let embeddings_size = entry.size as usize;
93
94 let embeddings_fp = FpSlab::detect(&view.section_table, n, dim)?;
96 if embeddings_fp.is_some() {
97 let e = view.entry(SECTION_EMBEDDINGS_FP)?;
99 let fp = view.get_section_data(SECTION_EMBEDDINGS_FP)?;
100 validate_slab_values(e.encoding, fp, n, dim)?;
101 }
102
103 let chunk_ids = decode_chunk_ids(&view.decoded_section(SECTION_CHUNK_IDS)?, n)?;
105 let mut spans =
106 decode_chunks_original_spans(&view.decoded_section(SECTION_CHUNKS_ORIGINAL_SPANS)?, n)?;
107
108 let ann_index = if view
112 .section_table
113 .iter()
114 .any(|e| e.section_id == SECTION_HNSW_INDEX)
115 {
116 let bytes = view.decoded_section(SECTION_HNSW_INDEX)?;
117 let mut idx = ann::HnswIndex::from_bytes(&bytes, n, dim)?;
118 let emb_bytes = view.get_section_data(SECTION_EMBEDDINGS)?;
119 let store = PackedVectors::from_section(&view.manifest.dtype, emb_bytes, n, dim)?;
122 idx.attach_store(store);
123 Some(idx)
124 } else {
125 None
126 };
127
128 let bm25_index = if view
129 .section_table
130 .iter()
131 .any(|e| e.section_id == SECTION_BM25_INDEX)
132 {
133 let bytes = view.decoded_section(SECTION_BM25_INDEX)?;
134 Some(bm25::Bm25Index::from_bytes(&bytes)?)
135 } else {
136 None
137 };
138
139 let graph_present = view
144 .manifest
145 .capabilities_ext
146 .as_ref()
147 .and_then(|e| e.graph_present)
148 .unwrap_or(false);
149 let graph_index = if graph_present
150 && view
151 .section_table
152 .iter()
153 .any(|e| e.section_id == SECTION_GRAPH_ADJACENCY)
154 {
155 let bytes = view.decoded_section(SECTION_GRAPH_ADJACENCY)?;
156 Some(CsrIndex::from_bytes(&bytes, n)?)
157 } else {
158 None
159 };
160
161 let blob_refs = crate::blobs::open_blob_sections(&view, &mut spans)?;
165 let blob_data = match &blob_refs {
166 Some(refs) => crate::blobs::open_blob_data(&view, refs.len())?,
167 None => None,
168 };
169
170 let spaces = crate::spaces::open_space_sections(&view)?;
173 let embedding_model = view.manifest.embedding_model.clone();
174 let model_hash = view.manifest.model_hash.clone();
175 let declared_index_type = view.manifest.index_type.clone();
176 let declared_score_type = view.manifest.score_type.clone();
177 let file_hash = view.file_hash_hex();
178 let content_hash = view.content_hash_hex()?;
179 drop(view);
180
181 Ok(Self {
182 _mmap: mmap,
183 embedding_dim: dim,
184 n_embeddings: n,
185 dtype,
186 embeddings_offset,
187 embeddings_size,
188 embeddings_fp,
189 chunk_ids,
190 spans,
191 embedding_model,
192 model_hash,
193 file_hash,
194 content_hash,
195 ann_index,
196 bm25_index,
197 graph_index,
198 blob_refs,
199 blob_data,
200 spaces,
201 declared_index_type,
202 declared_score_type,
203 })
204 }
205
206 pub fn embedding_dim(&self) -> usize {
207 self.embedding_dim
208 }
209 pub fn n_embeddings(&self) -> usize {
210 self.n_embeddings
211 }
212 pub fn dtype(&self) -> DType {
213 self.dtype
214 }
215 pub fn file_hash(&self) -> &str {
216 &self.file_hash
217 }
218 pub fn content_hash(&self) -> &str {
219 &self.content_hash
220 }
221 pub fn simd_backend(&self) -> SimdBackend {
222 simd::detect_backend()
223 }
224 pub fn declared_index_type(&self) -> &str {
225 &self.declared_index_type
226 }
227 pub fn model_hash(&self) -> &str {
230 &self.model_hash
231 }
232 pub fn declared_score_type(&self) -> &str {
233 &self.declared_score_type
234 }
235 pub fn has_ann(&self) -> bool {
236 self.ann_index.is_some()
237 }
238 pub fn has_bm25(&self) -> bool {
239 self.bm25_index.is_some()
240 }
241 pub fn has_graph(&self) -> bool {
242 self.graph_index.is_some()
243 }
244 pub fn has_blobs(&self) -> bool {
245 self.blob_refs.is_some()
246 }
247 pub fn blob_refs(&self) -> Option<&[BlobRefRecord]> {
251 self.blob_refs.as_deref()
252 }
253 pub fn has_spaces(&self) -> bool {
254 self.spaces.is_some()
255 }
256 pub fn space_names(&self) -> Vec<&str> {
259 self.spaces
260 .as_deref()
261 .unwrap_or(&[])
262 .iter()
263 .map(|s| s.entry.name.as_str())
264 .collect()
265 }
266 pub(crate) fn space(&self, name: &str) -> Option<(&crate::spaces::OpenSpace, &[u8])> {
268 let s = self
269 .spaces
270 .as_deref()?
271 .iter()
272 .find(|s| s.entry.name == name)?;
273 Some((s, &self._mmap[s.offset..s.offset + s.size]))
274 }
275
276 pub fn revalidate(&self) -> Result<(), RuntimeError> {
281 let view = UrnaView::from_bytes(&self._mmap)?;
282 view.validate_embeddings_values()?;
283 let _ = view.search_contract()?;
284 Ok(())
285 }
286
287 pub(crate) fn embeddings_bytes(&self) -> &[u8] {
288 &self._mmap[self.embeddings_offset..self.embeddings_offset + self.embeddings_size]
289 }
290
291 pub(crate) fn embeddings_fp_slab(&self) -> Option<(&[u8], DType)> {
295 self.embeddings_fp
296 .map(|fp| (&self._mmap[fp.offset..fp.offset + fp.size], fp.dtype))
297 }
298}