ruda_test_utils/test_tensor/
host_data.rs1use ruda_kernel::dsl as kernel_dsl;
2use ruda_kernel::dsl::RudaElement;
3use ruda_test_runtime::TestRuntime;
4use ruda_kernel::dsl::client::ComputeClient;
5use ruda_kernel::dsl::prelude::RudaPrimitive;
6use ruda_kernel::library::tensor::TensorHandle;
7use ruda_kernel::dsl::zspace::Shape;
8use ruda_kernel::dsl::zspace::Strides;
9
10use crate::test_tensor::{cast::copy_casted, strides::physical_extent};
11
12#[derive(Debug, Clone)]
13pub struct HostData {
14 pub data: HostDataVec,
15 pub shape: Shape,
16 pub strides: Strides,
17}
18
19#[derive(Eq, PartialEq, PartialOrd, Clone, Copy, Debug)]
20pub enum HostDataType {
21 F32,
22 I32,
23 Bool,
24}
25
26#[derive(Clone, Debug)]
27pub enum HostDataVec {
28 F32(Vec<f32>),
29 I32(Vec<i32>),
30 Bool(Vec<bool>),
31}
32
33impl HostDataVec {
34 pub fn get_f32(&self, i: usize) -> f32 {
35 match self {
36 HostDataVec::F32(items) => items[i],
37 _ => panic!("Can't get as f32"),
38 }
39 }
40
41 pub fn get_bool(&self, i: usize) -> bool {
42 match self {
43 HostDataVec::Bool(items) => items[i],
44 _ => panic!("Can't get as bool"),
45 }
46 }
47
48 pub fn get_i32(&self, i: usize) -> i32 {
49 match self {
50 HostDataVec::I32(items) => items[i],
51 _ => panic!("Can't get as i32"),
52 }
53 }
54
55 pub fn try_get_f32(&self, i: usize) -> Option<f32> {
56 match self {
57 HostDataVec::F32(items) => items.get(i).copied(),
58 _ => None,
59 }
60 }
61
62 pub fn try_get_i32(&self, i: usize) -> Option<i32> {
63 match self {
64 HostDataVec::I32(items) => items.get(i).copied(),
65 _ => None,
66 }
67 }
68
69 pub fn try_get_bool(&self, i: usize) -> Option<bool> {
70 match self {
71 HostDataVec::Bool(items) => items.get(i).copied(),
72 _ => None,
73 }
74 }
75}
76
77impl HostData {
78 pub fn from_tensor_handle(
79 client: &ComputeClient<TestRuntime>,
80 mut tensor_handle: TensorHandle<TestRuntime>,
81 host_data_type: HostDataType,
82 ) -> Self {
83 let shape = tensor_handle.shape().clone();
84 let strides = tensor_handle.strides().clone();
85
86 let physical_len = physical_extent(&shape, &strides);
93 tensor_handle.metadata.shape = Shape::from(vec![physical_len]);
94 tensor_handle.metadata.strides = Strides::new(&[1]);
95
96 let data = match host_data_type {
97 HostDataType::F32 => {
98 let handle = copy_casted(
99 client,
100 tensor_handle,
101 f32::as_type_native_unchecked().storage_type(),
102 );
103 let data = f32::from_bytes(
104 &client.read_one_unchecked_tensor(handle.into_copy_descriptor()),
105 )
106 .to_owned();
107
108 HostDataVec::F32(data)
109 }
110 HostDataType::I32 => {
111 let handle = copy_casted(
112 client,
113 tensor_handle,
114 i32::as_type_native_unchecked().storage_type(),
115 );
116 let data = i32::from_bytes(
117 &client.read_one_unchecked_tensor(handle.into_copy_descriptor()),
118 )
119 .to_owned();
120
121 HostDataVec::I32(data)
122 }
123 HostDataType::Bool => {
124 let handle = copy_casted(
125 client,
126 tensor_handle,
127 u32::as_type_native_unchecked().storage_type(),
128 );
129 let data = u32::from_bytes(
130 &client.read_one_unchecked_tensor(handle.into_copy_descriptor()),
131 )
132 .to_owned();
133
134 HostDataVec::Bool(data.iter().map(|&x| x > 0).collect())
135 }
136 };
137
138 Self {
139 data,
140 shape,
141 strides,
142 }
143 }
144
145 pub fn get_f32(&self, index: &[usize]) -> f32 {
146 self.data.get_f32(self.strided_index(index))
147 }
148
149 pub fn get_bool(&self, index: &[usize]) -> bool {
150 self.data.get_bool(self.strided_index(index))
151 }
152
153 pub fn get_i32(&self, index: &[usize]) -> i32 {
154 self.data.get_i32(self.strided_index(index))
155 }
156
157 pub fn try_get_f32(&self, index: &[usize]) -> Option<f32> {
160 self.data.try_get_f32(self.strided_index(index))
161 }
162
163 pub fn try_get_i32(&self, index: &[usize]) -> Option<i32> {
164 self.data.try_get_i32(self.strided_index(index))
165 }
166
167 pub fn try_get_bool(&self, index: &[usize]) -> Option<bool> {
168 self.data.try_get_bool(self.strided_index(index))
169 }
170
171 pub fn iter_indices(&self) -> impl Iterator<Item = Vec<usize>> + '_ {
176 IndexIter::new(self.shape.as_slice().to_vec())
177 }
178
179 pub fn iter_indexed_f32(&self) -> impl Iterator<Item = (Vec<usize>, f32)> + '_ {
182 self.iter_indices().map(move |idx| {
183 let v = self.get_f32(&idx);
184 (idx, v)
185 })
186 }
187
188 pub fn iter_indexed_i32(&self) -> impl Iterator<Item = (Vec<usize>, i32)> + '_ {
191 self.iter_indices().map(move |idx| {
192 let v = self.get_i32(&idx);
193 (idx, v)
194 })
195 }
196
197 pub fn iter_indexed_bool(&self) -> impl Iterator<Item = (Vec<usize>, bool)> + '_ {
200 self.iter_indices().map(move |idx| {
201 let v = self.get_bool(&idx);
202 (idx, v)
203 })
204 }
205
206 fn strided_index(&self, index: &[usize]) -> usize {
207 let mut i = 0usize;
208 for (d, idx) in index.iter().enumerate() {
209 i += idx * self.strides[d];
210 }
211 i
212 }
213
214 pub fn pretty_print(&self) -> String {
221 self.pretty_print_filtered(None)
222 }
223
224 pub fn pretty_print_slice<I>(&self, filter: I) -> String
230 where
231 I: IntoIterator,
232 I::Item: Into<crate::DimFilter>,
233 {
234 let f: crate::TensorFilter = filter.into_iter().map(Into::into).collect();
235 assert_eq!(
236 f.len(),
237 self.shape.rank(),
238 "pretty_print_slice: filter rank ({}) must match tensor rank ({})",
239 f.len(),
240 self.shape.rank(),
241 );
242 self.pretty_print_filtered(Some(f))
243 }
244
245 fn pretty_print_filtered(&self, filter: Option<crate::TensorFilter>) -> String {
246 let rank = self.shape.rank();
247 match rank {
248 0 => String::new(),
249 1 => {
250 let col_filter = filter.as_ref().and_then(|f| f.first());
252 let cols = axis_indices(col_filter, self.shape[0]);
253 let rows = vec![0usize];
254 pretty_print_table(&rows, &cols, |_row_label, col_label| {
255 self.cell_string(self.strided_index(&[col_label]))
256 })
257 }
258 2 => {
259 let row_filter = filter.as_ref().and_then(|f| f.first());
262 let col_filter = filter.as_ref().and_then(|f| f.get(1));
263 let rows = axis_indices(row_filter, self.shape[0]);
264 let cols = axis_indices(col_filter, self.shape[1]);
265 pretty_print_table(&rows, &cols, |row_label, col_label| {
266 self.cell_string(self.strided_index(&[row_label, col_label]))
267 })
268 }
269 _ => self.print_higher_rank(filter.as_ref()),
270 }
271 }
272
273 fn cell_string(&self, idx: usize) -> String {
274 match &self.data {
275 HostDataVec::I32(_) => self.data.get_i32(idx).to_string(),
276 HostDataVec::F32(_) => format!("{:.3}", self.data.get_f32(idx)),
277 HostDataVec::Bool(_) => self.data.get_bool(idx).to_string(),
278 }
279 }
280
281 fn print_higher_rank(&self, filter: Option<&crate::TensorFilter>) -> String {
282 let rank = self.shape.rank();
283 let leading_dims = rank - 2;
284 let row_dim = self.shape[rank - 2];
285 let col_dim = self.shape[rank - 1];
286
287 let row_filter = filter.and_then(|f| f.get(rank - 2));
289 let col_filter = filter.and_then(|f| f.get(rank - 1));
290 let row_indices = axis_indices(row_filter, row_dim);
291 let col_indices = axis_indices(col_filter, col_dim);
292
293 let mut out = String::new();
294 let mut leading = vec![0usize; leading_dims];
295
296 loop {
298 let print_this = match filter {
299 None => true,
300 Some(f) => leading_indices_match(&leading, f),
301 };
302
303 if print_this {
304 if !out.is_empty() {
305 out.push('\n');
306 }
307 out.push_str(&format!("{}:\n", format_leading_label(&leading, rank)));
308
309 let table = pretty_print_table(&row_indices, &col_indices, |row, col| {
310 let mut full = leading.clone();
311 full.push(row);
312 full.push(col);
313 self.cell_string(self.strided_index(&full))
314 });
315 out.push_str(&table);
316 }
317
318 if !increment_lex(&mut leading, &self.shape.as_slice()[..leading_dims]) {
320 break;
321 }
322 }
323
324 out
325 }
326}
327
328pub fn pretty_print_zip(tensors: &[&HostData]) -> String {
329 assert!(!tensors.is_empty(), "Need at least one tensor");
330
331 let dims = tensors[0].shape.as_slice();
332
333 for t in tensors {
334 assert_eq!(t.shape.as_slice(), dims, "All tensors must have same shape");
335 }
336
337 let rank = tensors[0].shape.rank();
338
339 let cell = |full: &[usize]| -> String {
340 let mut parts = Vec::with_capacity(tensors.len());
341 for t in tensors {
342 let idx = t.strided_index(full);
343 parts.push(t.cell_string(idx));
344 }
345 parts.join("/")
346 };
347
348 match rank {
349 0 => String::new(),
350 1 => {
351 let cols: Vec<usize> = (0..dims[0]).collect();
352 pretty_print_table(&[0], &cols, |_, col| cell(&[col]))
353 }
354 2 => {
355 let rows: Vec<usize> = (0..dims[0]).collect();
356 let cols: Vec<usize> = (0..dims[1]).collect();
357 pretty_print_table(&rows, &cols, |row, col| cell(&[row, col]))
358 }
359 _ => {
360 let leading_dims = rank - 2;
361 let rows: Vec<usize> = (0..dims[rank - 2]).collect();
362 let cols: Vec<usize> = (0..dims[rank - 1]).collect();
363 let mut out = String::new();
364 let mut leading = vec![0usize; leading_dims];
365 loop {
366 if !out.is_empty() {
367 out.push('\n');
368 }
369 out.push_str(&format!("{}:\n", format_leading_label(&leading, rank)));
370 let table = pretty_print_table(&rows, &cols, |row, col| {
371 let mut full = leading.clone();
372 full.push(row);
373 full.push(col);
374 cell(&full)
375 });
376 out.push_str(&table);
377
378 if !increment_lex(&mut leading, &dims[..leading_dims]) {
379 break;
380 }
381 }
382 out
383 }
384 }
385}
386
387fn leading_indices_match(leading: &[usize], filter: &crate::TensorFilter) -> bool {
391 use crate::DimFilter::*;
392 for (dim, &idx) in leading.iter().enumerate() {
393 let f = filter.get(dim).unwrap_or(&Any);
394 match f {
395 Any => {}
396 Exact(v) => {
397 if idx != *v {
398 return false;
399 }
400 }
401 Range { start, end } => {
402 if idx < *start || idx > *end {
403 return false;
404 }
405 }
406 }
407 }
408 true
409}
410
411fn increment_lex(idx: &mut [usize], bounds: &[usize]) -> bool {
414 if idx.is_empty() {
415 return false;
416 }
417 for d in (0..idx.len()).rev() {
418 idx[d] += 1;
419 if idx[d] < bounds[d] {
420 return true;
421 }
422 idx[d] = 0;
423 }
424 false
425}
426
427struct IndexIter {
431 shape: Vec<usize>,
432 next: Option<Vec<usize>>,
433}
434
435impl IndexIter {
436 fn new(shape: Vec<usize>) -> Self {
437 let next = if shape.contains(&0) {
439 None
440 } else {
441 Some(vec![0; shape.len()])
442 };
443 Self { shape, next }
444 }
445}
446
447impl Iterator for IndexIter {
448 type Item = Vec<usize>;
449
450 fn next(&mut self) -> Option<Self::Item> {
451 let current = self.next.clone()?;
452
453 let mut tentative = current.clone();
457 if !increment_lex(&mut tentative, &self.shape) {
458 self.next = None;
459 } else {
460 self.next = Some(tentative);
461 }
462
463 Some(current)
464 }
465}
466
467fn format_leading_label(leading: &[usize], rank: usize) -> String {
468 let mut parts: Vec<String> = leading.iter().map(|i| i.to_string()).collect();
469 for _ in 0..(rank - leading.len()) {
472 parts.push("*".to_string());
473 }
474 format!("[{}]", parts.join(", "))
475}
476
477fn axis_indices(f: Option<&crate::DimFilter>, dim_size: usize) -> Vec<usize> {
481 use crate::DimFilter::*;
482 match f {
483 None | Some(Any) => (0..dim_size).collect(),
484 Some(Exact(v)) => {
485 if *v < dim_size {
486 vec![*v]
487 } else {
488 Vec::new()
489 }
490 }
491 Some(Range { start, end }) => {
492 if *start >= dim_size {
493 Vec::new()
494 } else {
495 (*start..=(*end).min(dim_size.saturating_sub(1))).collect()
496 }
497 }
498 }
499}
500
501fn pretty_print_table<F>(rows: &[usize], cols: &[usize], mut cell: F) -> String
502where
503 F: FnMut(usize, usize) -> String,
504{
505 let mut max_width = 0;
506
507 for &r in rows {
508 for &c in cols {
509 max_width = max_width.max(cell(r, c).len());
510 }
511 }
512
513 let label_width = cols.iter().map(|c| c.to_string().len()).max().unwrap_or(0);
516 max_width = max_width.max(label_width).max(2);
517
518 let row_label_width = rows
519 .iter()
520 .map(|r| r.to_string().len())
521 .max()
522 .unwrap_or(0)
523 .max(3);
524
525 let mut s = String::new();
526
527 s.push_str(&format!("{:>width$} |", "", width = row_label_width));
529 for &col in cols {
530 s.push_str(&format!(" {:>width$}", col, width = max_width));
531 }
532 s.push('\n');
533
534 s.push_str(&"-".repeat(row_label_width + 1));
536 s.push('+');
537 for _ in cols {
538 s.push_str(&"-".repeat(max_width + 1));
539 }
540 s.push('\n');
541
542 for &row in rows {
544 s.push_str(&format!("{:>width$} |", row, width = row_label_width));
545
546 for &col in cols {
547 let value = cell(row, col);
548 s.push_str(&format!(" {:>width$}", value, width = max_width));
549 }
550
551 s.push('\n');
552 }
553
554 s
555}