onnx_runtime_loader/
weights.rs1use std::collections::HashMap;
10use std::fs::File;
11use std::path::{Path, PathBuf};
12
13use memmap2::Mmap;
14use onnx_runtime_ir::{DataType, TensorData, ValueId, WeightRef};
15
16use crate::proto::onnx::{tensor_proto, ModelProto, TensorProto};
17use crate::{pathsafe::guarded_join, LoaderError};
18
19#[derive(Debug, Default)]
22pub struct WeightStore {
23 pub weights: HashMap<ValueId, WeightRef>,
25 mmaps: HashMap<PathBuf, Mmap>,
27}
28
29impl WeightStore {
30 pub fn new() -> Self {
32 Self::default()
33 }
34
35 pub fn bytes<'a>(&'a self, weight: &'a WeightRef) -> Option<&'a [u8]> {
42 match weight {
43 WeightRef::Inline(t) => Some(&t.data),
44 WeightRef::External {
45 path,
46 offset,
47 length,
48 ..
49 } => {
50 let mmap = self.mmaps.get(path)?;
51 mmap.get(*offset..offset.checked_add(*length)?)
52 }
53 }
54 }
55
56 fn mmap_file(&mut self, path: &Path) -> Result<(), LoaderError> {
57 if self.mmaps.contains_key(path) {
58 return Ok(());
59 }
60 let file = File::open(path).map_err(|_| LoaderError::ExternalDataNotFound {
61 path: path.to_path_buf(),
62 })?;
63 let mmap = unsafe { Mmap::map(&file) }.map_err(|e| LoaderError::Mmap(e.to_string()))?;
68 self.mmaps.insert(path.to_path_buf(), mmap);
69 Ok(())
70 }
71}
72
73pub fn load_weights(
77 model: &ModelProto,
78 model_dir: &Path,
79 name_map: &HashMap<String, ValueId>,
80) -> Result<WeightStore, LoaderError> {
81 let mut store = WeightStore::new();
82 let Some(graph) = model.graph.as_ref() else {
83 return Ok(store);
84 };
85
86 for init in &graph.initializer {
87 let Some(&vid) = name_map.get(&init.name) else {
88 continue;
92 };
93 let weight = resolve_initializer(&mut store, init, model_dir)?;
94 store.weights.insert(vid, weight);
95 }
96
97 Ok(store)
98}
99
100fn resolve_initializer(
101 store: &mut WeightStore,
102 init: &TensorProto,
103 model_dir: &Path,
104) -> Result<WeightRef, LoaderError> {
105 let dtype = DataType::from_onnx(init.data_type).ok_or_else(|| {
106 LoaderError::UnsupportedDataType {
107 raw: init.data_type,
108 context: format!("initializer {:?}", init.name),
109 }
110 })?;
111 let dims: Vec<usize> = init.dims.iter().map(|&d| d.max(0) as usize).collect();
112
113 if init.data_location == tensor_proto::DataLocation::External as i32 {
114 let mut location = None;
115 let mut offset: usize = 0;
116 let mut length: Option<usize> = None;
117 for kv in &init.external_data {
118 match kv.key.as_str() {
119 "location" => location = Some(kv.value.clone()),
120 "offset" => offset = kv.value.parse().unwrap_or(0),
121 "length" => length = kv.value.parse().ok(),
122 _ => {}
123 }
124 }
125 let location = location.ok_or_else(|| {
126 LoaderError::GraphBuild(format!(
127 "external initializer {:?} missing 'location'",
128 init.name
129 ))
130 })?;
131 let path = resolve_external_path(model_dir, &location)?;
132 store.mmap_file(&path)?;
133 let numel: usize = dims.iter().product();
134 let length = length.unwrap_or_else(|| dtype.storage_bytes(numel));
135 if let Some(mmap) = store.mmaps.get(&path) {
138 let end = offset.checked_add(length);
139 if end.is_none_or(|e| e > mmap.len()) {
140 return Err(LoaderError::Mmap(format!(
141 "external initializer {:?}: window [{offset}, {:?}) exceeds file {} ({} bytes)",
142 init.name,
143 end,
144 path.display(),
145 mmap.len()
146 )));
147 }
148 }
149 Ok(WeightRef::External {
150 path,
151 offset,
152 length,
153 dtype,
154 dims,
155 })
156 } else {
157 let data = tensor_data_from_proto(init, dtype, &dims)?;
158 Ok(WeightRef::Inline(data))
159 }
160}
161
162fn resolve_external_path(model_dir: &Path, location: &str) -> Result<PathBuf, LoaderError> {
165 guarded_join(model_dir, location).map_err(|reason| LoaderError::ExternalDataPath {
166 path: location.to_string(),
167 reason,
168 })
169}
170
171pub(crate) fn tensor_data_from_proto(
174 proto: &TensorProto,
175 dtype: DataType,
176 dims: &[usize],
177) -> Result<TensorData, LoaderError> {
178 let mut td = TensorData::from_raw(dtype, dims.to_vec(), Vec::new());
179 if !proto.name.is_empty() {
180 td.name = Some(proto.name.clone());
181 }
182
183 if dtype == DataType::String {
184 td.strings = proto
185 .string_data
186 .iter()
187 .map(|b| String::from_utf8_lossy(b).into_owned())
188 .collect();
189 return Ok(td);
190 }
191
192 if !proto.raw_data.is_empty() {
194 td.data = proto.raw_data.clone();
195 return Ok(td);
196 }
197
198 td.data = match dtype {
200 DataType::Float32 => proto
201 .float_data
202 .iter()
203 .flat_map(|v| v.to_le_bytes())
204 .collect(),
205 DataType::Float64 => proto
206 .double_data
207 .iter()
208 .flat_map(|v| v.to_le_bytes())
209 .collect(),
210 DataType::Int64 => proto
211 .int64_data
212 .iter()
213 .flat_map(|v| v.to_le_bytes())
214 .collect(),
215 DataType::Uint64 | DataType::Uint32 => proto
216 .uint64_data
217 .iter()
218 .flat_map(|v| match dtype {
219 DataType::Uint32 => (*v as u32).to_le_bytes().to_vec(),
220 _ => v.to_le_bytes().to_vec(),
221 })
222 .collect(),
223 DataType::Int32 => proto
224 .int32_data
225 .iter()
226 .flat_map(|v| v.to_le_bytes())
227 .collect(),
228 DataType::Int16 | DataType::Uint16 | DataType::Float16 | DataType::BFloat16 => proto
230 .int32_data
231 .iter()
232 .flat_map(|v| (*v as u16).to_le_bytes())
233 .collect(),
234 DataType::Int8 | DataType::Uint8 | DataType::Bool => {
235 proto.int32_data.iter().map(|v| *v as u8).collect()
236 }
237 _ => Vec::new(),
238 };
239 Ok(td)
240}