1use rmpv::Value;
13
14use crate::error::{Error, Result};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18#[allow(missing_docs)]
19pub enum Element {
20 F16,
21 F32,
22 F64,
23 I8,
24 I16,
25 I32,
26 I64,
27 U8,
28 U16,
29 U32,
30 U64,
31}
32
33impl Element {
34 fn width(self) -> usize {
35 match self {
36 Self::I8 | Self::U8 => 1,
37 Self::F16 | Self::I16 | Self::U16 => 2,
38 Self::F32 | Self::I32 | Self::U32 => 4,
39 Self::F64 | Self::I64 | Self::U64 => 8,
40 }
41 }
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46enum ByteOrder {
47 Little,
48 Big,
49}
50
51#[derive(Debug, Clone, PartialEq, Eq)]
53pub struct RawArray {
54 pub element: Element,
56 pub shape: Vec<usize>,
58 order: ByteOrder,
59 data: Vec<u8>,
60}
61
62fn parse_dtype(descr: &str) -> Result<(ByteOrder, Element)> {
63 let (order_char, kind) = descr
64 .split_at_checked(1)
65 .ok_or_else(|| Error::decode(format!("empty numpy dtype descriptor: {descr:?}")))?;
66 let order = match order_char {
67 ">" => ByteOrder::Big,
68 "<" | "|" | "=" => ByteOrder::Little,
71 _ => {
72 return Err(Error::decode(format!(
73 "unsupported numpy byte-order marker in {descr:?}"
74 )));
75 }
76 };
77 let element = match kind {
78 "f2" => Element::F16,
79 "f4" => Element::F32,
80 "f8" => Element::F64,
81 "i1" => Element::I8,
82 "i2" => Element::I16,
83 "i4" => Element::I32,
84 "i8" => Element::I64,
85 "u1" => Element::U8,
86 "u2" => Element::U16,
87 "u4" => Element::U32,
88 "u8" => Element::U64,
89 other => return Err(Error::decode(format!("unsupported numpy dtype {other:?}"))),
90 };
91 Ok((order, element))
92}
93
94fn key_str(key: &Value) -> Option<&str> {
96 match key {
97 Value::String(text) => text.as_str(),
98 Value::Binary(bytes) => std::str::from_utf8(bytes).ok(),
99 _ => None,
100 }
101}
102
103fn lookup<'a>(entries: &'a [(Value, Value)], name: &str) -> Option<&'a Value> {
104 entries
105 .iter()
106 .find(|(key, _)| key_str(key) == Some(name))
107 .map(|(_, value)| value)
108}
109
110fn as_bytes(value: &Value) -> Option<&[u8]> {
111 match value {
112 Value::Binary(bytes) => Some(bytes),
113 Value::String(text) => text.as_bytes().into(),
114 _ => None,
115 }
116}
117
118fn as_text(value: &Value) -> Option<&str> {
119 match value {
120 Value::String(text) => text.as_str(),
121 Value::Binary(bytes) => std::str::from_utf8(bytes).ok(),
122 _ => None,
123 }
124}
125
126pub fn is_array(value: &Value) -> bool {
128 matches!(value, Value::Map(entries) if lookup(entries, "nd").is_some_and(|nd| nd.as_bool() == Some(true)))
129}
130
131pub fn decode(value: &Value) -> Result<RawArray> {
133 let Value::Map(entries) = value else {
134 return Err(Error::decode("expected a msgpack map for a numpy array"));
135 };
136 if lookup(entries, "nd").and_then(Value::as_bool) != Some(true) {
137 return Err(Error::decode(
138 "msgpack map is not a numpy array (missing nd: true)",
139 ));
140 }
141
142 let descr = lookup(entries, "type")
143 .and_then(as_text)
144 .ok_or_else(|| Error::decode("numpy array is missing its dtype descriptor"))?;
145 let (order, element) = parse_dtype(descr)?;
146
147 let shape: Vec<usize> = match lookup(entries, "shape") {
148 Some(Value::Array(dims)) => dims
149 .iter()
150 .map(|dim| {
151 dim.as_u64().map(|value| value as usize).ok_or_else(|| {
152 Error::decode("numpy array shape contains a non-integer dimension")
153 })
154 })
155 .collect::<Result<_>>()?,
156 _ => return Err(Error::decode("numpy array is missing its shape")),
157 };
158
159 let data = lookup(entries, "data")
160 .and_then(as_bytes)
161 .ok_or_else(|| Error::decode("numpy array is missing its data buffer"))?;
162
163 let expected = shape.iter().product::<usize>() * element.width();
164 if data.len() != expected {
165 return Err(Error::decode(format!(
166 "numpy array buffer is {} bytes, but shape {shape:?} of {descr} needs {expected}",
167 data.len()
168 )));
169 }
170
171 Ok(RawArray {
172 element,
173 shape,
174 order,
175 data: data.to_vec(),
176 })
177}
178
179macro_rules! read_elements {
180 ($self:expr, $ty:ty, $convert:expr) => {{
181 let width = std::mem::size_of::<$ty>();
182 $self
183 .data
184 .chunks_exact(width)
185 .map(|chunk| {
186 let raw: [u8; std::mem::size_of::<$ty>()] =
187 chunk.try_into().expect("chunks_exact width");
188 let value = match $self.order {
189 ByteOrder::Little => <$ty>::from_le_bytes(raw),
190 ByteOrder::Big => <$ty>::from_be_bytes(raw),
191 };
192 #[allow(clippy::redundant_closure_call)]
193 $convert(value)
194 })
195 .collect()
196 }};
197}
198
199impl RawArray {
200 pub fn len(&self) -> usize {
202 self.shape.iter().product()
203 }
204
205 pub fn is_empty(&self) -> bool {
207 self.len() == 0
208 }
209
210 pub fn to_f32(&self) -> Vec<f32> {
215 match self.element {
216 Element::F16 => read_elements!(self, u16, |bits| half::f16::from_bits(bits).to_f32()),
217 Element::F32 => read_elements!(self, f32, |value| value),
218 Element::F64 => read_elements!(self, f64, |value: f64| value as f32),
219 Element::I8 => self
220 .data
221 .iter()
222 .map(|byte| f32::from(*byte as i8))
223 .collect(),
224 Element::I16 => read_elements!(self, i16, f32::from),
225 Element::I32 => read_elements!(self, i32, |value: i32| value as f32),
226 Element::I64 => read_elements!(self, i64, |value: i64| value as f32),
227 Element::U8 => self.data.iter().map(|byte| f32::from(*byte)).collect(),
228 Element::U16 => read_elements!(self, u16, f32::from),
229 Element::U32 => read_elements!(self, u32, |value: u32| value as f32),
230 Element::U64 => read_elements!(self, u64, |value: u64| value as f32),
231 }
232 }
233
234 pub fn to_f16(&self) -> Vec<half::f16> {
236 match self.element {
237 Element::F16 => read_elements!(self, u16, half::f16::from_bits),
238 _ => self.to_f32().into_iter().map(half::f16::from_f32).collect(),
239 }
240 }
241
242 pub fn to_u32(&self) -> Result<Vec<u32>> {
244 let values: Vec<i64> = match self.element {
245 Element::I8 => self
246 .data
247 .iter()
248 .map(|byte| i64::from(*byte as i8))
249 .collect(),
250 Element::I16 => read_elements!(self, i16, i64::from),
251 Element::I32 => read_elements!(self, i32, i64::from),
252 Element::I64 => read_elements!(self, i64, |value| value),
253 Element::U8 => self.data.iter().map(|byte| i64::from(*byte)).collect(),
254 Element::U16 => read_elements!(self, u16, i64::from),
255 Element::U32 => read_elements!(self, u32, i64::from),
256 Element::U64 => read_elements!(self, u64, |value: u64| value as i64),
257 Element::F16 | Element::F32 | Element::F64 => {
258 return Err(Error::decode(
259 "sparse indices arrived as a floating-point array",
260 ));
261 }
262 };
263 values
264 .into_iter()
265 .map(|value| {
266 u32::try_from(value)
267 .map_err(|_| Error::decode(format!("sparse index {value} is out of range")))
268 })
269 .collect()
270 }
271
272 pub fn rows_f32(&self) -> Result<Vec<Vec<f32>>> {
274 let cols = self.cols()?;
275 Ok(self.to_f32().chunks(cols).map(<[f32]>::to_vec).collect())
276 }
277
278 pub fn rows_f16(&self) -> Result<Vec<Vec<half::f16>>> {
280 let cols = self.cols()?;
281 Ok(self
282 .to_f16()
283 .chunks(cols)
284 .map(<[half::f16]>::to_vec)
285 .collect())
286 }
287
288 fn cols(&self) -> Result<usize> {
289 match self.shape.as_slice() {
290 [_, cols] if *cols > 0 => Ok(*cols),
291 [_, _] => Ok(1),
292 other => Err(Error::decode(format!(
293 "expected a 2-D array, got shape {other:?}"
294 ))),
295 }
296 }
297}
298
299#[cfg(test)]
300pub(crate) mod fixtures {
301 use super::*;
302
303 pub(crate) fn f32_array(shape: &[usize], values: &[f32]) -> Value {
305 raw_array(
306 "<f4",
307 shape,
308 values.iter().flat_map(|v| v.to_le_bytes()).collect(),
309 )
310 }
311
312 pub(crate) fn f16_array(shape: &[usize], values: &[f32]) -> Value {
313 raw_array(
314 "<f2",
315 shape,
316 values
317 .iter()
318 .flat_map(|v| half::f16::from_f32(*v).to_bits().to_le_bytes())
319 .collect(),
320 )
321 }
322
323 pub(crate) fn i32_array(shape: &[usize], values: &[i32]) -> Value {
324 raw_array(
325 "<i4",
326 shape,
327 values.iter().flat_map(|v| v.to_le_bytes()).collect(),
328 )
329 }
330
331 pub(crate) fn raw_array(descr: &str, shape: &[usize], data: Vec<u8>) -> Value {
332 Value::Map(vec![
333 (Value::Binary(b"nd".to_vec()), Value::Boolean(true)),
334 (Value::Binary(b"type".to_vec()), Value::String(descr.into())),
335 (Value::Binary(b"kind".to_vec()), Value::Binary(Vec::new())),
336 (
337 Value::Binary(b"shape".to_vec()),
338 Value::Array(shape.iter().map(|dim| Value::from(*dim as u64)).collect()),
339 ),
340 (Value::Binary(b"data".to_vec()), Value::Binary(data)),
341 ])
342 }
343}
344
345#[cfg(test)]
346mod tests {
347 #![allow(clippy::float_cmp)]
349
350 use super::*;
351 use fixtures::*;
352
353 #[test]
354 fn decodes_a_dense_f32_vector() {
355 let value = f32_array(&[3], &[0.5, -1.5, 2.0]);
356 assert!(is_array(&value));
357 let array = decode(&value).unwrap();
358 assert_eq!(array.element, Element::F32);
359 assert_eq!(array.shape, vec![3]);
360 assert_eq!(array.to_f32(), vec![0.5, -1.5, 2.0]);
361 }
362
363 #[test]
364 fn decodes_f16_without_losing_the_wire_precision() {
365 let value = f16_array(&[2, 2], &[1.0, 0.25, -3.5, 0.1]);
366 let array = decode(&value).unwrap();
367 assert_eq!(array.element, Element::F16);
368 let rows = array.rows_f16().unwrap();
369 assert_eq!(rows.len(), 2);
370 assert_eq!(rows[0][1], half::f16::from_f32(0.25));
371 assert_eq!(rows[1][1], half::f16::from_f32(0.1));
374 assert_eq!(array.rows_f32().unwrap()[0][0], 1.0);
375 }
376
377 #[test]
378 fn decodes_sparse_indices() {
379 let array = decode(&i32_array(&[4], &[3, 17, 900, 0])).unwrap();
380 assert_eq!(array.to_u32().unwrap(), vec![3, 17, 900, 0]);
381 }
382
383 #[test]
384 fn rejects_negative_sparse_indices() {
385 let array = decode(&i32_array(&[2], &[-1, 4])).unwrap();
386 assert!(array.to_u32().is_err());
387 }
388
389 #[test]
390 fn rejects_float_arrays_as_indices() {
391 let array = decode(&f32_array(&[2], &[1.0, 2.0])).unwrap();
392 assert!(array.to_u32().is_err());
393 }
394
395 #[test]
396 fn honours_big_endian_buffers() {
397 let data: Vec<u8> = [1.5f32, -2.0]
398 .iter()
399 .flat_map(|v| v.to_be_bytes())
400 .collect();
401 let array = decode(&raw_array(">f4", &[2], data)).unwrap();
402 assert_eq!(array.to_f32(), vec![1.5, -2.0]);
403 }
404
405 #[test]
406 fn widens_quantized_int8_embeddings() {
407 let array = decode(&raw_array("|i1", &[3], vec![0xff, 0x01, 0x80])).unwrap();
408 assert_eq!(array.to_f32(), vec![-1.0, 1.0, -128.0]);
409 }
410
411 #[test]
412 fn rejects_a_buffer_that_does_not_match_its_shape() {
413 let truncated = raw_array("<f4", &[4], vec![0; 8]);
414 let err = decode(&truncated).unwrap_err();
415 assert!(err.to_string().contains("needs 16"), "{err}");
416 }
417
418 #[test]
419 fn rejects_unsupported_dtypes_and_non_arrays() {
420 assert!(decode(&raw_array("<c8", &[1], vec![0; 8])).is_err());
421 assert!(decode(&Value::Nil).is_err());
422 assert!(!is_array(&Value::Map(vec![(
423 Value::String("dense".into()),
424 Value::Nil
425 )])));
426 }
427
428 #[test]
429 fn accepts_string_keys_as_well_as_binary_keys() {
430 let value = Value::Map(vec![
431 (Value::String("nd".into()), Value::Boolean(true)),
432 (Value::String("type".into()), Value::String("<f4".into())),
433 (
434 Value::String("shape".into()),
435 Value::Array(vec![Value::from(2u64)]),
436 ),
437 (
438 Value::String("data".into()),
439 Value::Binary([1.0f32, 2.0].iter().flat_map(|v| v.to_le_bytes()).collect()),
440 ),
441 ]);
442 assert_eq!(decode(&value).unwrap().to_f32(), vec![1.0, 2.0]);
443 }
444
445 #[test]
446 fn round_trips_through_real_msgpack_bytes() {
447 let packed = rmp_serde::to_vec(&f32_array(&[2, 2], &[1.0, 2.0, 3.0, 4.0])).unwrap();
448 let value: Value = rmp_serde::from_slice(&packed).unwrap();
449 let array = decode(&value).unwrap();
450 assert_eq!(
451 array.rows_f32().unwrap(),
452 vec![vec![1.0, 2.0], vec![3.0, 4.0]]
453 );
454 }
455}