1use aria_kernel::{hadamard_blocked_vec, linear, matmul_dispatch, EngineError, SimdMode};
4use std::sync::Arc;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum DType {
8 F32,
9 F16,
10 U8,
11}
12
13#[derive(Clone)]
15pub enum TensorBuf {
16 External(Arc<[u8]>),
17 Owned(Vec<u8>),
18}
19
20impl TensorBuf {
21 pub fn as_slice(&self) -> &[u8] {
22 match self {
23 Self::External(a) => a.as_ref(),
24 Self::Owned(v) => v.as_slice(),
25 }
26 }
27}
28
29#[derive(Clone)]
30pub struct TensorView {
31 pub dtype: DType,
32 pub shape: Vec<usize>,
33 pub strides: Vec<usize>,
34 pub buf: TensorBuf,
35 pub offset: usize,
36 pub len: usize,
37}
38
39impl TensorView {
40 pub fn from_f32(data: Vec<f32>, shape: Vec<usize>) -> Self {
41 let nbytes = data.len() * 4;
42 let mut bytes = Vec::with_capacity(nbytes);
43 for v in data {
44 bytes.extend_from_slice(&v.to_le_bytes());
45 }
46 let strides = row_major_strides(&shape, 4);
47 Self {
48 dtype: DType::F32,
49 shape,
50 strides,
51 buf: TensorBuf::Owned(bytes),
52 offset: 0,
53 len: nbytes,
54 }
55 }
56
57 pub fn from_external(bytes: Arc<[u8]>, dtype: DType, shape: Vec<usize>, offset: usize, len: usize) -> Self {
58 let elem = match dtype {
59 DType::F32 => 4,
60 DType::F16 => 2,
61 DType::U8 => 1,
62 };
63 let strides = row_major_strides(&shape, elem);
64 Self {
65 dtype,
66 shape,
67 strides,
68 buf: TensorBuf::External(bytes),
69 offset,
70 len,
71 }
72 }
73
74 pub fn as_f32_slice(&self) -> Result<&[f32], EngineError> {
75 if self.dtype != DType::F32 {
76 return Err(EngineError::ShapeMismatch("expected f32 tensor".into()));
77 }
78 let bytes = &self.buf.as_slice()[self.offset..self.offset + self.len];
79 if !bytes.len().is_multiple_of(4) {
80 return Err(EngineError::Format("f32 byte length not aligned".into()));
81 }
82 let ptr = bytes.as_ptr() as *const f32;
83 Ok(unsafe { std::slice::from_raw_parts(ptr, bytes.len() / 4) })
84 }
85
86 pub fn to_f32_vec(&self) -> Result<Vec<f32>, EngineError> {
87 Ok(self.as_f32_slice()?.to_vec())
88 }
89}
90
91fn row_major_strides(shape: &[usize], elem: usize) -> Vec<usize> {
92 let mut strides = vec![0; shape.len()];
93 let mut acc = elem;
94 for i in (0..shape.len()).rev() {
95 strides[i] = acc;
96 acc *= shape[i].max(1);
97 }
98 strides
99}
100
101#[derive(Default)]
102pub struct BufferPool {
103 buffers: Vec<Vec<u8>>,
104}
105
106impl BufferPool {
107 pub fn new() -> Self {
108 Self::default()
109 }
110
111 pub fn alloc(&mut self, nbytes: usize) -> &mut [u8] {
112 self.buffers.push(vec![0u8; nbytes]);
113 self.buffers.last_mut().unwrap().as_mut_slice()
114 }
115
116 pub fn reuse_count(&self) -> usize {
117 self.buffers.len()
118 }
119}
120
121#[derive(Debug, Clone)]
122pub enum Op {
123 Linear { out_f: usize, in_f: usize },
125 MatMul {
127 a_rows: usize,
128 a_cols: usize,
129 b_rows: usize,
130 b_cols: usize,
131 },
132 HdmLinear {
134 out_f: usize,
135 in_f: usize,
136 hadamard_seed: Option<i64>,
137 },
138}
139
140pub struct Node {
141 pub op: Op,
142 pub inputs: Vec<usize>,
143 pub output: usize,
144}
145
146pub struct Graph {
147 pub tensors: Vec<Option<TensorView>>,
148 pub nodes: Vec<Node>,
149 pub mode: SimdMode,
150}
151
152impl Graph {
153 pub fn new(mode: SimdMode) -> Self {
154 Self {
155 tensors: Vec::new(),
156 nodes: Vec::new(),
157 mode,
158 }
159 }
160
161 pub fn push_tensor(&mut self, t: TensorView) -> usize {
162 let id = self.tensors.len();
163 self.tensors.push(Some(t));
164 id
165 }
166
167 pub fn add_node(&mut self, node: Node) {
168 self.nodes.push(node);
169 }
170
171 pub fn execute(&mut self, _pool: &mut BufferPool) -> Result<(), EngineError> {
172 for node in &self.nodes {
173 match &node.op {
174 Op::Linear { out_f, in_f } => {
175 let x = self.tensors[node.inputs[0]]
176 .as_ref()
177 .ok_or_else(|| EngineError::Format("missing input tensor".into()))?
178 .to_f32_vec()?;
179 let w = self.tensors[node.inputs[1]]
180 .as_ref()
181 .ok_or_else(|| EngineError::Format("missing weight tensor".into()))?
182 .to_f32_vec()?;
183 let y = linear(&x, &w, *out_f, *in_f)?;
184 let shape = if x.len() == *in_f {
185 vec![*out_f]
186 } else {
187 vec![x.len() / *in_f, *out_f]
188 };
189 self.tensors[node.output] = Some(TensorView::from_f32(y, shape));
190 }
191 Op::HdmLinear {
192 out_f,
193 in_f,
194 hadamard_seed,
195 } => {
196 let x = self.tensors[node.inputs[0]]
197 .as_ref()
198 .ok_or_else(|| EngineError::Format("missing input tensor".into()))?
199 .to_f32_vec()?;
200 let w = self.tensors[node.inputs[1]]
201 .as_ref()
202 .ok_or_else(|| EngineError::Format("missing weight tensor".into()))?
203 .to_f32_vec()?;
204 let mut y = linear(&x, &w, *out_f, *in_f)?;
206 let batch = y.len() / *out_f;
207 for b in 0..batch {
208 let sl = b * *out_f..(b + 1) * *out_f;
209 hadamard_blocked_vec(&mut y[sl], *hadamard_seed, true)?;
210 }
211 let shape = if x.len() == *in_f {
212 vec![*out_f]
213 } else {
214 vec![batch, *out_f]
215 };
216 self.tensors[node.output] = Some(TensorView::from_f32(y, shape));
217 }
218 Op::MatMul {
219 a_rows,
220 a_cols,
221 b_rows,
222 b_cols,
223 } => {
224 let a = self.tensors[node.inputs[0]]
225 .as_ref()
226 .ok_or_else(|| EngineError::Format("missing A".into()))?
227 .to_f32_vec()?;
228 let b = self.tensors[node.inputs[1]]
229 .as_ref()
230 .ok_or_else(|| EngineError::Format("missing B".into()))?
231 .to_f32_vec()?;
232 let c = matmul_dispatch(
233 &a, *a_rows, *a_cols, &b, *b_rows, *b_cols, self.mode,
234 )?;
235 self.tensors[node.output] =
236 Some(TensorView::from_f32(c, vec![*a_rows, *b_cols]));
237 }
238 }
239 }
240 Ok(())
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247
248 fn run_hdm(
249 w_rot: Vec<f32>,
250 x: Vec<f32>,
251 out_f: usize,
252 in_f: usize,
253 seed: Option<i64>,
254 ) -> Vec<f32> {
255 let batch = x.len() / in_f;
256 let mut g = Graph::new(SimdMode::Scalar);
257 let xi = g.push_tensor(TensorView::from_f32(
258 x,
259 if batch == 1 {
260 vec![in_f]
261 } else {
262 vec![batch, in_f]
263 },
264 ));
265 let wi = g.push_tensor(TensorView::from_f32(w_rot, vec![out_f, in_f]));
266 let yi = g.push_tensor(TensorView::from_f32(
267 vec![0.0; batch * out_f],
268 if batch == 1 {
269 vec![out_f]
270 } else {
271 vec![batch, out_f]
272 },
273 ));
274 g.add_node(Node {
275 op: Op::HdmLinear {
276 out_f,
277 in_f,
278 hadamard_seed: seed,
279 },
280 inputs: vec![xi, wi],
281 output: yi,
282 });
283 g.execute(&mut BufferPool::new()).unwrap();
284 g.tensors[yi].as_ref().unwrap().to_f32_vec().unwrap()
285 }
286
287 #[test]
288 fn hdm_linear_matches_orig_weight() {
289 use aria_kernel::hadamard_blocked_rows;
290 let out_f = 10usize;
291 let in_f = 4usize;
292 let seed = Some(3i64);
293 let mut w_orig: Vec<f32> = (0..out_f * in_f)
294 .map(|i| (i as f32) * 0.07 - 0.3)
295 .collect();
296 let x = vec![0.5f32, -0.2, 0.1, 0.3];
297 let y_ref = linear(&x, &w_orig, out_f, in_f).unwrap();
298
299 hadamard_blocked_rows(&mut w_orig, out_f, in_f, seed, false).unwrap();
301 let y = run_hdm(w_orig, x, out_f, in_f, seed);
302 assert_eq!(y.len(), y_ref.len());
303 for (a, b) in y.iter().zip(y_ref.iter()) {
304 assert!((a - b).abs() < 1e-4, "{a} vs {b}");
305 }
306 }
307
308 #[test]
309 fn hdm_linear_batch_and_unsigned() {
310 use aria_kernel::hadamard_blocked_rows;
311 let out_f = 10usize;
312 let in_f = 3usize;
313 let mut w: Vec<f32> = (0..out_f * in_f)
314 .map(|i| (i as f32) * 0.05 - 0.2)
315 .collect();
316 let x = vec![
317 0.2f32, -0.1, 0.4, -0.3, 0.5, 0.1, ];
320 let y_ref = linear(&x, &w, out_f, in_f).unwrap();
321 hadamard_blocked_rows(&mut w, out_f, in_f, None, false).unwrap();
322 let y = run_hdm(w, x, out_f, in_f, None);
323 assert_eq!(y.len(), 2 * out_f);
324 for (a, b) in y.iter().zip(y_ref.iter()) {
325 assert!((a - b).abs() < 1e-4, "{a} vs {b}");
326 }
327 }
328
329 #[test]
330 fn hdm_linear_shape_err() {
331 let mut g = Graph::new(SimdMode::Scalar);
332 let x = g.push_tensor(TensorView::from_f32(vec![1.0, 2.0], vec![2]));
333 let w = g.push_tensor(TensorView::from_f32(vec![1.0; 6], vec![3, 2]));
334 let y = g.push_tensor(TensorView::from_f32(vec![0.0; 3], vec![3]));
335 g.add_node(Node {
336 op: Op::HdmLinear {
337 out_f: 3,
338 in_f: 3, hadamard_seed: Some(0),
340 },
341 inputs: vec![x, w],
342 output: y,
343 });
344 let err = g.execute(&mut BufferPool::new()).unwrap_err();
345 assert!(matches!(err, EngineError::ShapeMismatch(_)));
346 }
347
348 #[test]
349 fn linear_dispatch() {
350 let mut g = Graph::new(SimdMode::Scalar);
351 let x = g.push_tensor(TensorView::from_f32(vec![1.0, 0.0], vec![2]));
352 let w = g.push_tensor(TensorView::from_f32(vec![1.0, 0.0, 0.0, 1.0], vec![2, 2]));
354 let y = g.push_tensor(TensorView::from_f32(vec![0.0, 0.0], vec![2]));
355 g.add_node(Node {
356 op: Op::Linear { out_f: 2, in_f: 2 },
357 inputs: vec![x, w],
358 output: y,
359 });
360 let mut pool = BufferPool::new();
361 g.execute(&mut pool).unwrap();
362 let out = g.tensors[y].as_ref().unwrap().to_f32_vec().unwrap();
363 assert_eq!(out, vec![1.0, 0.0]);
364 }
365
366 #[test]
367 fn external_zero_copy() {
368 let bytes: Arc<[u8]> = Arc::from([0u8, 0, 0x80, 0x3f].as_slice()); let t = TensorView::from_external(bytes, DType::F32, vec![1], 0, 4);
370 assert_eq!(t.to_f32_vec().unwrap(), vec![1.0]);
371 }
372
373 #[test]
374 fn matmul_shape_err() {
375 let mut g = Graph::new(SimdMode::Scalar);
376 let a = g.push_tensor(TensorView::from_f32(vec![1.0], vec![1, 1]));
377 let b = g.push_tensor(TensorView::from_f32(vec![1.0, 2.0], vec![2, 1]));
378 let c = g.push_tensor(TensorView::from_f32(vec![0.0], vec![1, 1]));
379 g.add_node(Node {
380 op: Op::MatMul {
381 a_rows: 1,
382 a_cols: 1,
383 b_rows: 2,
384 b_cols: 1,
385 },
386 inputs: vec![a, b],
387 output: c,
388 });
389 let err = g.execute(&mut BufferPool::new()).unwrap_err();
390 assert!(matches!(err, EngineError::ShapeMismatch(_)));
391 }
392}