1use crate::DType;
23use smallvec::SmallVec;
24
25#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28pub enum Dim {
29 Static(usize),
31 Dynamic(u32),
34}
35
36impl Dim {
37 pub fn unwrap_static(self) -> usize {
38 match self {
39 Self::Static(n) => n,
40 Self::Dynamic(s) => panic!("expected static dim, got dynamic symbol {s}"),
41 }
42 }
43
44 pub fn is_static(self) -> bool {
45 matches!(self, Self::Static(_))
46 }
47}
48
49impl From<usize> for Dim {
50 fn from(n: usize) -> Self {
51 Self::Static(n)
52 }
53}
54
55impl std::fmt::Display for Dim {
56 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 match self {
58 Self::Static(n) => write!(f, "{n}"),
59 Self::Dynamic(s) => write!(f, "?{s}"),
60 }
61 }
62}
63
64#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
68#[derive(Debug, Clone, PartialEq, Eq, Hash)]
69pub struct Shape {
70 dims: SmallVec<[Dim; 4]>,
71 dtype: DType,
72}
73
74impl Shape {
75 pub fn new(dims: &[usize], dtype: DType) -> Self {
77 Self {
78 dims: dims.iter().map(|&d| Dim::Static(d)).collect(),
79 dtype,
80 }
81 }
82
83 pub fn from_dims(dims: &[Dim], dtype: DType) -> Self {
85 Self {
86 dims: dims.into(),
87 dtype,
88 }
89 }
90
91 pub fn scalar(dtype: DType) -> Self {
93 Self {
94 dims: SmallVec::new(),
95 dtype,
96 }
97 }
98
99 pub fn rank(&self) -> usize {
100 self.dims.len()
101 }
102 pub fn dtype(&self) -> DType {
103 self.dtype
104 }
105 pub fn dims(&self) -> &[Dim] {
106 &self.dims
107 }
108 pub fn dim(&self, i: usize) -> Dim {
109 self.dims.get(i).copied().unwrap_or_else(|| {
110 let dims: Vec<_> = self.dims.iter().map(|d| d.unwrap_static()).collect();
111 panic!(
112 "Shape::dim({i}) out of bounds for rank {} dims={dims:?}",
113 self.rank()
114 );
115 })
116 }
117
118 pub fn dynamic_symbols(&self) -> Vec<u32> {
121 let mut syms: Vec<u32> = self
122 .dims
123 .iter()
124 .filter_map(|d| match d {
125 Dim::Dynamic(s) => Some(*s),
126 _ => None,
127 })
128 .collect();
129 syms.sort();
130 syms.dedup();
131 syms
132 }
133
134 pub fn bind(&self, bindings: &DimBinding) -> Self {
139 let dims = self
140 .dims
141 .iter()
142 .map(|d| match d {
143 Dim::Dynamic(s) => match bindings.get(*s) {
144 Some(n) => Dim::Static(n),
145 None => *d,
146 },
147 _ => *d,
148 })
149 .collect();
150 Self {
151 dims,
152 dtype: self.dtype,
153 }
154 }
155
156 pub fn num_elements(&self) -> Option<usize> {
158 let mut total = 1usize;
159 for d in &self.dims {
160 match d {
161 Dim::Static(n) => total = total.checked_mul(*n)?,
162 Dim::Dynamic(_) => return None,
163 }
164 }
165 Some(total)
166 }
167
168 pub fn size_bytes(&self) -> Option<usize> {
170 self.num_elements().map(|n| n * self.dtype.size_bytes())
171 }
172
173 pub fn is_static(&self) -> bool {
175 self.dims.iter().all(|d| d.is_static())
176 }
177
178 pub fn with_dim(mut self, axis: usize, dim: Dim) -> Self {
180 self.dims[axis] = dim;
181 self
182 }
183
184 pub fn with_dtype(mut self, dtype: DType) -> Self {
186 self.dtype = dtype;
187 self
188 }
189
190 pub fn broadcast_with(&self, other: &Shape) -> Result<Shape, String> {
192 broadcast(self, other)
193 }
194}
195
196pub fn ada_modulation_lead_pack(x_dims: &[usize], mod_dims_in: &[usize]) -> [u32; 17] {
204 let rank = x_dims.len();
205 assert!(rank >= 1, "AdaLayerNorm/GatedResidual: rank ≥ 1");
206 let lead = rank - 1;
207 assert!(
208 lead <= 8,
209 "AdaLayerNorm/GatedResidual: at most 8 leading dims"
210 );
211 let pad = rank
212 .checked_sub(mod_dims_in.len())
213 .expect("modulation rank exceeds x");
214 let mut mod_dims = vec![1usize; rank];
215 mod_dims[pad..].copy_from_slice(mod_dims_in);
216 let mut pack = [0u32; 17];
217 pack[0] = lead as u32;
218 for j in 0..lead {
219 pack[1 + j] = x_dims[j] as u32;
220 pack[9 + j] = mod_dims[j] as u32;
221 }
222 pack
223}
224
225pub fn ada_modulation_launch(x_dims: &[usize], mod_dims: &[usize]) -> (u32, u32) {
229 assert!(!x_dims.is_empty() && !mod_dims.is_empty());
230 let xr = x_dims.len() - 1;
231 let mr = mod_dims.len() - 1;
232 let mut seq = 1u32;
233 let mut mods = 1u32;
234 for i in 0..xr {
235 let xd = x_dims[i] as u32;
236 let md = if i + mr >= xr {
237 mod_dims[i - (xr - mr)] as u32
238 } else {
239 1
240 };
241 if md == 1 && xd > 1 {
242 seq = seq.saturating_mul(xd);
243 } else {
244 mods = mods.saturating_mul(xd.max(1));
245 }
246 }
247 (mods.max(1), seq.max(1))
248}
249
250pub fn broadcast(a: &Shape, b: &Shape) -> Result<Shape, String> {
254 let max_rank = a.rank().max(b.rank());
255 let mut dims = SmallVec::new();
256 for i in 0..max_rank {
257 let ad = if i < max_rank - a.rank() {
258 Dim::Static(1)
259 } else {
260 a.dims[i - (max_rank - a.rank())]
261 };
262 let bd = if i < max_rank - b.rank() {
263 Dim::Static(1)
264 } else {
265 b.dims[i - (max_rank - b.rank())]
266 };
267 let d = broadcast_dim(ad, bd)?;
268 dims.push(d);
269 }
270 Ok(Shape {
271 dims,
272 dtype: a.dtype,
273 })
274}
275
276fn broadcast_dim(a: Dim, b: Dim) -> Result<Dim, String> {
277 match (a, b) {
278 (Dim::Static(1), d) | (d, Dim::Static(1)) => Ok(d),
279 (Dim::Static(x), Dim::Static(y)) if x == y => Ok(Dim::Static(x)),
280 (Dim::Static(x), Dim::Static(y)) => Err(format!("cannot broadcast {x} with {y}")),
281 (Dim::Dynamic(s), Dim::Dynamic(t)) if s == t => Ok(Dim::Dynamic(s)),
282 (Dim::Dynamic(_), _) | (_, Dim::Dynamic(_)) => Ok(a), }
284}
285
286pub fn matmul_shape(lhs: &Shape, rhs: &Shape) -> Result<Shape, String> {
288 if lhs.rank() < 2 || rhs.rank() < 2 {
289 return Err(format!(
290 "matmul requires rank >= 2, got {} and {}",
291 lhs.rank(),
292 rhs.rank()
293 ));
294 }
295 let m = lhs.dims[lhs.rank() - 2];
296 let k1 = lhs.dims[lhs.rank() - 1];
297 let k2 = rhs.dims[rhs.rank() - 2];
298 let n = rhs.dims[rhs.rank() - 1];
299
300 match (k1, k2) {
302 (Dim::Static(a), Dim::Static(b)) if a != b => {
303 return Err(format!("matmul K mismatch: {a} vs {b}"));
304 }
305 (Dim::Dynamic(s), Dim::Dynamic(t)) if s != t => {
306 return Err(format!("matmul K mismatch: ?{s} vs ?{t}"));
307 }
308 _ => {}
309 }
310
311 let lhs_batch = &lhs.dims[..lhs.rank() - 2];
313 let rhs_batch = &rhs.dims[..rhs.rank() - 2];
314 let batch_a = Shape::from_dims(lhs_batch, lhs.dtype);
315 let batch_b = Shape::from_dims(rhs_batch, rhs.dtype);
316 let batch = if lhs_batch.is_empty() && rhs_batch.is_empty() {
317 SmallVec::new()
318 } else if lhs_batch.is_empty() {
319 rhs_batch.into()
320 } else if rhs_batch.is_empty() {
321 lhs_batch.into()
322 } else {
323 broadcast(&batch_a, &batch_b)?.dims.clone()
324 };
325
326 let mut dims = batch;
327 dims.push(m);
328 dims.push(n);
329 Ok(Shape {
330 dims,
331 dtype: lhs.dtype,
332 })
333}
334
335pub fn expand_shape(input: &Shape, target: &[i64]) -> Result<Shape, String> {
337 if target.iter().any(|&d| d < 0) {
338 return Err("expand target has negative dim".into());
339 }
340 let target_s = Shape::new(
341 &target.iter().map(|&d| d as usize).collect::<Vec<_>>(),
342 input.dtype(),
343 );
344 broadcast(input, &target_s)
345}
346
347pub fn binary_shape(lhs: &Shape, rhs: &Shape) -> Result<Shape, String> {
349 broadcast(lhs, rhs)
350}
351
352pub fn unary_shape(input: &Shape) -> Shape {
354 input.clone()
355}
356
357pub fn cast_shape(input: &Shape, to: DType) -> Shape {
359 input.clone().with_dtype(to)
360}
361
362pub fn compare_shape(lhs: &Shape, rhs: &Shape) -> Result<Shape, String> {
364 Ok(broadcast(lhs, rhs)?.with_dtype(DType::Bool))
365}
366
367pub fn reduce_shape(input: &Shape, axes: &[usize], keep_dim: bool) -> Result<Shape, String> {
369 let mut dims = SmallVec::new();
370 for (i, &d) in input.dims.iter().enumerate() {
371 if axes.contains(&i) {
372 if keep_dim {
373 dims.push(Dim::Static(1));
374 }
375 } else {
376 dims.push(d);
377 }
378 }
379 Ok(Shape {
380 dims,
381 dtype: input.dtype,
382 })
383}
384
385pub fn softmax_shape(input: &Shape) -> Shape {
387 input.clone()
388}
389
390pub fn transpose_shape(input: &Shape, perm: &[usize]) -> Result<Shape, String> {
392 if perm.len() != input.rank() {
393 return Err(format!("perm len {} != rank {}", perm.len(), input.rank()));
394 }
395 let dims: SmallVec<[Dim; 4]> = perm.iter().map(|&i| input.dims[i]).collect();
396 Ok(Shape {
397 dims,
398 dtype: input.dtype,
399 })
400}
401
402pub fn narrow_shape(input: &Shape, axis: usize, len: usize) -> Result<Shape, String> {
404 if axis >= input.rank() {
405 return Err(format!("axis {axis} >= rank {}", input.rank()));
406 }
407 Ok(input.clone().with_dim(axis, Dim::Static(len)))
408}
409
410pub fn concat_shape(inputs: &[&Shape], axis: usize) -> Result<Shape, String> {
412 if inputs.is_empty() {
413 return Err("concat: no inputs".into());
414 }
415 let base = inputs[0];
416 let mut static_sum = 0usize;
417 let mut dyn_sym: Option<u32> = None;
418 for s in inputs {
419 if s.rank() == 0 {
420 return Err("concat: input has rank 0".into());
421 }
422 if s.rank() != base.rank() {
423 return Err(format!(
424 "concat: rank mismatch {} vs {}",
425 s.rank(),
426 base.rank()
427 ));
428 }
429 let ax = axis.min(s.rank().saturating_sub(1));
430 match s.dims[ax] {
431 Dim::Static(n) => static_sum += n,
432 Dim::Dynamic(sym) => {
433 if let Some(prev) = dyn_sym {
434 if prev != sym {
435 return Err(format!(
436 "concat: mismatched dynamic symbols {prev} vs {sym} on axis {axis}"
437 ));
438 }
439 }
440 dyn_sym = Some(sym);
441 }
442 }
443 }
444 let out_dim = match dyn_sym {
445 None => Dim::Static(static_sum),
446 Some(sym) if static_sum == 0 => Dim::Dynamic(sym),
447 Some(sym) => {
448 let _ = static_sum;
451 Dim::Dynamic(sym)
452 }
453 };
454 let out_axis = axis.min(base.rank().saturating_sub(1));
455 Ok(base.clone().with_dim(out_axis, out_dim))
456}
457
458pub fn gather_shape(table: &Shape, indices: &Shape, axis: usize) -> Result<Shape, String> {
460 if axis >= table.rank() {
461 return Err(format!("gather: axis {axis} >= rank {}", table.rank()));
462 }
463 let mut dims: SmallVec<[Dim; 4]> = table.dims[..axis].iter().copied().collect();
469 dims.extend(indices.dims.iter().copied());
470 for i in (axis + 1)..table.rank() {
471 dims.push(table.dims[i]);
472 }
473 Ok(Shape {
474 dims,
475 dtype: table.dtype,
476 })
477}
478
479pub fn reshape_shape(input: &Shape, new_shape: &[i64]) -> Result<Shape, String> {
481 let neg_count = new_shape.iter().filter(|&&d| d == -1).count();
482 if neg_count > 1 {
483 return Err("reshape: at most one -1".into());
484 }
485
486 if input.is_static() {
487 let known_product: i64 = new_shape.iter().filter(|&&d| d != -1).product();
494 let mut dims = SmallVec::new();
495 for &d in new_shape {
496 if d == -1 {
497 let total = input
498 .num_elements()
499 .ok_or_else(|| "reshape: input has dynamic dims".to_string())?;
500 let inferred = total as i64 / known_product.max(1);
501 dims.push(Dim::Static(inferred as usize));
502 } else if d < 0 {
503 return Err(format!("reshape: invalid dim {d}"));
504 } else {
505 dims.push(Dim::Static(d as usize));
506 }
507 }
508 return Ok(Shape {
509 dims,
510 dtype: input.dtype,
511 });
512 }
513
514 let dyn_syms = input.dynamic_symbols();
517 let neg_idx = new_shape.iter().position(|&d| d == -1);
518 let mut out_dims: SmallVec<[Dim; 4]> = SmallVec::new();
519 for (i, &d) in new_shape.iter().enumerate() {
520 if Some(i) == neg_idx {
521 continue;
522 }
523 if d < 0 {
524 return Err(format!("reshape: invalid dim {d}"));
525 }
526 out_dims.push(Dim::Static(d as usize));
527 }
528 if let Some(ni) = neg_idx {
529 let inferred = if dyn_syms.len() == 1 {
530 Dim::Dynamic(dyn_syms[0])
531 } else if dyn_syms.is_empty() {
532 return Err("reshape: cannot infer -1 on static input".into());
533 } else {
534 Dim::Dynamic(crate::dynamic::sym::ROWS)
535 };
536 out_dims.insert(ni, inferred);
537 }
538 Ok(Shape {
539 dims: out_dims,
540 dtype: input.dtype,
541 })
542}
543
544pub fn leading_flatten_fused_shape(input: &Shape) -> Option<Shape> {
546 if input.rank() < 2 {
547 return None;
548 }
549 let Dim::Static(h) = input.dim(input.rank() - 1) else {
550 return None;
551 };
552 let leading = &input.dims()[..input.rank() - 1];
553 let lead_dim = if leading.iter().all(|d| d.is_static()) {
554 Dim::Static(leading.iter().map(|d| d.unwrap_static()).product::<usize>())
555 } else {
556 let mut syms: Vec<u32> = leading
557 .iter()
558 .filter_map(|d| match d {
559 Dim::Dynamic(s) => Some(*s),
560 _ => None,
561 })
562 .collect();
563 syms.sort();
564 syms.dedup();
565 match syms.len() {
566 0 => Dim::Static(leading.iter().map(|d| d.unwrap_static()).product::<usize>()),
567 1 => Dim::Dynamic(syms[0]),
568 _ => Dim::Dynamic(crate::dynamic::sym::ROWS),
569 }
570 };
571 Some(Shape::from_dims(&[lead_dim, Dim::Static(h)], input.dtype()))
572}
573
574pub fn leading_flatten_shape(input: &Shape, new_shape: &[i64]) -> Option<Shape> {
576 if new_shape.len() != 2 {
577 return None;
578 }
579 let flat = leading_flatten_fused_shape(input)?;
580 let Dim::Static(h) = input.dim(input.rank() - 1) else {
581 return None;
582 };
583 if new_shape[1] as usize != h {
584 return None;
585 }
586 match flat.dim(0) {
587 Dim::Static(lead) if new_shape[0] as usize == lead => Some(flat),
588 Dim::Dynamic(_) if new_shape[0] == -1 => Some(flat),
589 _ => None,
590 }
591}
592
593pub fn attention_shape(q: &Shape) -> Shape {
595 q.clone()
596}
597
598impl std::fmt::Display for Shape {
599 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
600 write!(f, "[")?;
601 for (i, d) in self.dims.iter().enumerate() {
602 if i > 0 {
603 write!(f, ", ")?;
604 }
605 write!(f, "{d}")?;
606 }
607 write!(f, "] {}", self.dtype)
608 }
609}
610
611pub fn conv2d_spatial_output(
613 in_size: usize,
614 kernel: usize,
615 stride: usize,
616 padding: usize,
617 dilation: usize,
618) -> usize {
619 let dil_k = dilation.saturating_mul(kernel.saturating_sub(1));
620 (in_size + 2 * padding)
621 .saturating_sub(dil_k)
622 .saturating_sub(1)
623 / stride
624 + 1
625}
626
627pub fn conv_transpose2d_spatial_output(
629 in_size: usize,
630 kernel: usize,
631 stride: usize,
632 padding: usize,
633 dilation: usize,
634 output_padding: usize,
635) -> usize {
636 let dil_k = dilation.saturating_mul(kernel.saturating_sub(1));
637 (in_size - 1) * stride + output_padding + dil_k - 2 * padding + 1
638}
639
640pub fn conv2d_output_shape(
642 input: &Shape,
643 weight: &Shape,
644 kernel_size: [usize; 2],
645 stride: [usize; 2],
646 padding: [usize; 2],
647 dilation: [usize; 2],
648 groups: usize,
649) -> Result<Shape, String> {
650 if input.rank() != 4 || weight.rank() != 4 {
651 return Err("conv2d requires NCHW input and 4-D weight".into());
652 }
653 let n = input.dim(0);
654 let c_in = input.dim(1).unwrap_static();
655 let h = input.dim(2).unwrap_static();
656 let w = input.dim(3).unwrap_static();
657 let c_out = weight.dim(0).unwrap_static();
658 let w_cin = weight.dim(1).unwrap_static();
659 if w_cin * groups != c_in {
660 return Err(format!(
661 "conv2d weight C_in/g={w_cin} * groups={groups} != input C={c_in}"
662 ));
663 }
664 let h_out = conv2d_spatial_output(h, kernel_size[0], stride[0], padding[0], dilation[0]);
665 let w_out = conv2d_spatial_output(w, kernel_size[1], stride[1], padding[1], dilation[1]);
666 Ok(Shape::from_dims(
667 &[
668 n,
669 Dim::Static(c_out),
670 Dim::Static(h_out),
671 Dim::Static(w_out),
672 ],
673 input.dtype(),
674 ))
675}
676
677pub fn im2col_output_shape(
680 input: &Shape,
681 kernel_size: [usize; 2],
682 stride: [usize; 2],
683 padding: [usize; 2],
684 dilation: [usize; 2],
685) -> Result<Shape, String> {
686 if input.rank() != 4 {
687 return Err("im2col requires NCHW input".into());
688 }
689 let c_in = input.dim(1).unwrap_static();
690 let h = input.dim(2).unwrap_static();
691 let w = input.dim(3).unwrap_static();
692 let kh = kernel_size[0];
693 let kw = kernel_size[1];
694 let h_out = conv2d_spatial_output(h, kh, stride[0], padding[0], dilation[0]);
695 let w_out = conv2d_spatial_output(w, kw, stride[1], padding[1], dilation[1]);
696 let k = c_in * kh * kw;
697 let spatial = h_out * w_out;
698 let m = match input.dim(0) {
699 Dim::Static(n) => Dim::Static(n * spatial),
700 Dim::Dynamic(crate::dynamic::sym::BATCH) | Dim::Dynamic(crate::dynamic::sym::ROWS) => {
701 Dim::Dynamic(crate::dynamic::sym::ROWS)
702 }
703 Dim::Dynamic(_) => Dim::Dynamic(crate::dynamic::sym::ROWS),
704 };
705 Ok(Shape::from_dims(&[m, Dim::Static(k)], input.dtype()))
706}
707
708pub fn conv_transpose2d_output_shape(
710 input: &Shape,
711 weight: &Shape,
712 kernel_size: [usize; 2],
713 stride: [usize; 2],
714 padding: [usize; 2],
715 dilation: [usize; 2],
716 output_padding: [usize; 2],
717 groups: usize,
718) -> Result<Shape, String> {
719 if input.rank() != 4 || weight.rank() != 4 {
720 return Err("conv_transpose2d requires NCHW input and 4-D weight".into());
721 }
722 let n = input.dim(0);
724 let c_in = input.dim(1).unwrap_static();
725 let h = input.dim(2).unwrap_static();
726 let w = input.dim(3).unwrap_static();
727 let w_cin = weight.dim(0).unwrap_static();
728 let c_out_per_g = weight.dim(1).unwrap_static();
729 if w_cin != c_in {
730 return Err(format!(
731 "conv_transpose2d weight C_in={w_cin} != input C={c_in}"
732 ));
733 }
734 let h_out = conv_transpose2d_spatial_output(
735 h,
736 kernel_size[0],
737 stride[0],
738 padding[0],
739 dilation[0],
740 output_padding[0],
741 );
742 let w_out = conv_transpose2d_spatial_output(
743 w,
744 kernel_size[1],
745 stride[1],
746 padding[1],
747 dilation[1],
748 output_padding[1],
749 );
750 Ok(Shape::from_dims(
751 &[
752 n,
753 Dim::Static(c_out_per_g * groups),
754 Dim::Static(h_out),
755 Dim::Static(w_out),
756 ],
757 input.dtype(),
758 ))
759}
760
761#[allow(clippy::too_many_arguments)]
764pub fn conv3d_output_shape(
765 input: &Shape,
766 weight: &Shape,
767 kernel_size: [usize; 3],
768 stride: [usize; 3],
769 padding: [usize; 3],
770 dilation: [usize; 3],
771 groups: usize,
772) -> Result<Shape, String> {
773 if input.rank() != 5 || weight.rank() != 5 {
774 return Err("conv3d requires NCDHW input and 5-D weight".into());
775 }
776 let n = input.dim(0);
777 let c_in = input.dim(1).unwrap_static();
778 let d = input.dim(2).unwrap_static();
779 let h = input.dim(3).unwrap_static();
780 let w = input.dim(4).unwrap_static();
781 let c_out = weight.dim(0).unwrap_static();
782 let w_cin = weight.dim(1).unwrap_static();
783 if w_cin * groups != c_in {
784 return Err(format!(
785 "conv3d weight C_in/g={w_cin} * groups={groups} != input C={c_in}"
786 ));
787 }
788 let d_out = conv2d_spatial_output(d, kernel_size[0], stride[0], padding[0], dilation[0]);
789 let h_out = conv2d_spatial_output(h, kernel_size[1], stride[1], padding[1], dilation[1]);
790 let w_out = conv2d_spatial_output(w, kernel_size[2], stride[2], padding[2], dilation[2]);
791 Ok(Shape::from_dims(
792 &[
793 n,
794 Dim::Static(c_out),
795 Dim::Static(d_out),
796 Dim::Static(h_out),
797 Dim::Static(w_out),
798 ],
799 input.dtype(),
800 ))
801}
802
803#[allow(clippy::too_many_arguments)]
805pub fn conv_transpose3d_output_shape(
806 input: &Shape,
807 weight: &Shape,
808 kernel_size: [usize; 3],
809 stride: [usize; 3],
810 padding: [usize; 3],
811 dilation: [usize; 3],
812 output_padding: [usize; 3],
813 groups: usize,
814) -> Result<Shape, String> {
815 if input.rank() != 5 || weight.rank() != 5 {
816 return Err("conv_transpose3d requires NCDHW input and 5-D weight".into());
817 }
818 let n = input.dim(0);
819 let c_in = input.dim(1).unwrap_static();
820 let d = input.dim(2).unwrap_static();
821 let h = input.dim(3).unwrap_static();
822 let w = input.dim(4).unwrap_static();
823 let w_cin = weight.dim(0).unwrap_static();
824 let c_out_per_g = weight.dim(1).unwrap_static();
825 if w_cin != c_in {
826 return Err(format!(
827 "conv_transpose3d weight C_in={w_cin} != input C={c_in}"
828 ));
829 }
830 let d_out = conv_transpose2d_spatial_output(
831 d,
832 kernel_size[0],
833 stride[0],
834 padding[0],
835 dilation[0],
836 output_padding[0],
837 );
838 let h_out = conv_transpose2d_spatial_output(
839 h,
840 kernel_size[1],
841 stride[1],
842 padding[1],
843 dilation[1],
844 output_padding[1],
845 );
846 let w_out = conv_transpose2d_spatial_output(
847 w,
848 kernel_size[2],
849 stride[2],
850 padding[2],
851 dilation[2],
852 output_padding[2],
853 );
854 Ok(Shape::from_dims(
855 &[
856 n,
857 Dim::Static(c_out_per_g * groups),
858 Dim::Static(d_out),
859 Dim::Static(h_out),
860 Dim::Static(w_out),
861 ],
862 input.dtype(),
863 ))
864}
865
866#[cfg(test)]
867mod tests {
868 use super::*;
869
870 #[test]
871 fn static_shape() {
872 let s = Shape::new(&[4, 15, 384], DType::F32);
873 assert_eq!(s.rank(), 3);
874 assert_eq!(s.num_elements(), Some(4 * 15 * 384));
875 assert_eq!(s.size_bytes(), Some(4 * 15 * 384 * 4));
876 assert!(s.is_static());
877 assert_eq!(format!("{s}"), "[4, 15, 384] f32");
878 }
879
880 #[test]
883 fn broadcast_same() {
884 let a = Shape::new(&[4, 15, 384], DType::F32);
885 let r = broadcast(&a, &a).unwrap();
886 assert_eq!(r.dims(), a.dims());
887 }
888
889 #[test]
890 fn broadcast_bias() {
891 let a = Shape::new(&[4, 15, 384], DType::F32);
892 let b = Shape::new(&[384], DType::F32);
893 let r = broadcast(&a, &b).unwrap();
894 assert_eq!(r, Shape::new(&[4, 15, 384], DType::F32));
895 }
896
897 #[test]
898 fn broadcast_scalar() {
899 let a = Shape::new(&[4, 15, 384], DType::F32);
900 let b = Shape::scalar(DType::F32);
901 let r = broadcast(&a, &b).unwrap();
902 assert_eq!(r, a);
903 }
904
905 #[test]
906 fn broadcast_mismatch() {
907 let a = Shape::new(&[4, 15, 384], DType::F32);
908 let b = Shape::new(&[4, 15, 256], DType::F32);
909 assert!(broadcast(&a, &b).is_err());
910 }
911
912 #[test]
913 fn matmul_basic() {
914 let a = Shape::new(&[4, 15, 384], DType::F32);
915 let b = Shape::new(&[384, 1536], DType::F32);
916 let r = matmul_shape(&a, &b).unwrap();
917 assert_eq!(r, Shape::new(&[4, 15, 1536], DType::F32));
918 }
919
920 #[test]
921 fn matmul_batched() {
922 let a = Shape::new(&[4, 15, 384], DType::F32);
923 let b = Shape::new(&[4, 384, 1536], DType::F32);
924 let r = matmul_shape(&a, &b).unwrap();
925 assert_eq!(r, Shape::new(&[4, 15, 1536], DType::F32));
926 }
927
928 #[test]
929 fn matmul_k_mismatch() {
930 let a = Shape::new(&[4, 15, 384], DType::F32);
931 let b = Shape::new(&[512, 1536], DType::F32);
932 assert!(matmul_shape(&a, &b).is_err());
933 }
934
935 #[test]
936 fn reduce_keepdim() {
937 let a = Shape::new(&[4, 15, 384], DType::F32);
938 let r = reduce_shape(&a, &[2], true).unwrap();
939 assert_eq!(r, Shape::new(&[4, 15, 1], DType::F32));
940 }
941
942 #[test]
943 fn reduce_no_keepdim() {
944 let a = Shape::new(&[4, 15, 384], DType::F32);
945 let r = reduce_shape(&a, &[2], false).unwrap();
946 assert_eq!(r, Shape::new(&[4, 15], DType::F32));
947 }
948
949 #[test]
950 fn concat_basic() {
951 let a = Shape::new(&[4, 15, 384], DType::F32);
952 let b = Shape::new(&[4, 15, 384], DType::F32);
953 let r = concat_shape(&[&a, &b], 2).unwrap();
954 assert_eq!(r, Shape::new(&[4, 15, 768], DType::F32));
955 }
956
957 #[test]
958 fn gather_embedding() {
959 let table = Shape::new(&[30522, 384], DType::F32);
960 let indices = Shape::new(&[4, 15], DType::I64);
961 let r = gather_shape(&table, &indices, 0).unwrap();
962 assert_eq!(
963 r,
964 Shape::from_dims(
965 &[Dim::Static(4), Dim::Static(15), Dim::Static(384)],
966 DType::F32
967 )
968 );
969 }
970
971 #[test]
972 fn gather_nonzero_axis_keeps_prefix() {
973 let table = Shape::new(&[4, 3], DType::F32);
977 let indices = Shape::new(&[6], DType::I64);
978 let r = gather_shape(&table, &indices, 1).unwrap();
979 assert_eq!(r, Shape::new(&[4, 6], DType::F32));
980 let table = Shape::new(&[2, 5, 7], DType::F32);
982 let indices = Shape::new(&[3, 4], DType::I64);
983 let r = gather_shape(&table, &indices, 1).unwrap();
984 assert_eq!(r, Shape::new(&[2, 3, 4, 7], DType::F32));
985 }
986
987 #[test]
988 fn reshape_with_neg1() {
989 let a = Shape::new(&[4, 15, 384], DType::F32);
990 let r = reshape_shape(&a, &[60, -1]).unwrap();
991 assert_eq!(r, Shape::new(&[60, 384], DType::F32));
992 }
993
994 #[test]
995 fn transpose_basic() {
996 let a = Shape::new(&[4, 15, 384], DType::F32);
997 let r = transpose_shape(&a, &[0, 2, 1]).unwrap();
998 assert_eq!(r, Shape::new(&[4, 384, 15], DType::F32));
999 }
1000
1001 #[test]
1002 fn narrow_basic() {
1003 let a = Shape::new(&[4, 15, 1152], DType::F32);
1004 let r = narrow_shape(&a, 2, 384).unwrap();
1005 assert_eq!(r, Shape::new(&[4, 15, 384], DType::F32));
1006 }
1007
1008 #[test]
1009 fn compare_bool_output() {
1010 let a = Shape::new(&[4, 15], DType::F32);
1011 let b = Shape::new(&[4, 15], DType::F32);
1012 let r = compare_shape(&a, &b).unwrap();
1013 assert_eq!(r.dtype(), DType::Bool);
1014 assert_eq!(r.rank(), 2);
1015 }
1016
1017 #[test]
1020 fn dynamic_shape() {
1021 let s = Shape::from_dims(
1022 &[Dim::Dynamic(0), Dim::Dynamic(1), Dim::Static(384)],
1023 DType::F32,
1024 );
1025 assert_eq!(s.rank(), 3);
1026 assert_eq!(s.num_elements(), None);
1027 assert!(!s.is_static());
1028 assert_eq!(format!("{s}"), "[?0, ?1, 384] f32");
1029 }
1030
1031 #[test]
1032 fn dynamic_symbols_lists_distinct_dims() {
1033 let s = Shape::from_dims(
1034 &[
1035 Dim::Dynamic(1),
1036 Dim::Static(384),
1037 Dim::Dynamic(0),
1038 Dim::Dynamic(1),
1039 ],
1040 DType::F32,
1041 );
1042 assert_eq!(s.dynamic_symbols(), vec![0, 1]);
1043 }
1044
1045 #[test]
1046 fn bind_specializes_known_symbols() {
1047 let s = Shape::from_dims(
1048 &[Dim::Dynamic(0), Dim::Dynamic(1), Dim::Static(384)],
1049 DType::F32,
1050 );
1051 let mut b = DimBinding::new();
1052 b.set(0, 8);
1053 b.set(1, 64);
1054 let s2 = s.bind(&b);
1055 assert!(s2.is_static());
1056 assert_eq!(s2.num_elements(), Some(8 * 64 * 384));
1057 }
1058
1059 #[test]
1060 fn bind_leaves_unknown_symbols_alone() {
1061 let s = Shape::from_dims(&[Dim::Dynamic(0), Dim::Dynamic(99)], DType::F32);
1062 let mut b = DimBinding::new();
1063 b.set(0, 4);
1064 let s2 = s.bind(&b);
1065 assert!(!s2.is_static()); assert_eq!(s2.dynamic_symbols(), vec![99]);
1067 }
1068}
1069
1070#[derive(Debug, Clone, Default)]
1073pub struct DimBinding {
1074 map: std::collections::HashMap<u32, usize>,
1075}
1076
1077impl DimBinding {
1078 pub fn new() -> Self {
1079 Self::default()
1080 }
1081 pub fn set(&mut self, symbol: u32, size: usize) -> Option<usize> {
1082 self.map.insert(symbol, size)
1083 }
1084 pub fn get(&self, symbol: u32) -> Option<usize> {
1085 self.map.get(&symbol).copied()
1086 }
1087 pub fn is_empty(&self) -> bool {
1088 self.map.is_empty()
1089 }
1090 pub fn len(&self) -> usize {
1091 self.map.len()
1092 }
1093 pub fn iter(&self) -> impl Iterator<Item = (u32, usize)> + '_ {
1094 self.map.iter().map(|(&s, &n)| (s, n))
1095 }
1096}