use crate::infer::GraphExt as _;
use crate::ops::upsample::InterpMode;
use crate::{Graph, NodeId, Op};
impl Graph {
pub fn conv3d(
&mut self,
input: NodeId,
weight: NodeId,
stride: [usize; 3],
padding: [usize; 3],
dilation: [usize; 3],
groups: usize,
) -> NodeId {
let in_s = self.node(input).shape.clone();
let w_s = self.node(weight).shape.clone();
assert_eq!(in_s.rank(), 5, "conv3d: input must be [N, C_in, D, H, W]");
assert_eq!(
w_s.rank(),
5,
"conv3d: weight must be [C_out, C_in/groups, kD, kH, kW]"
);
let ks = [
w_s.dim(2).unwrap_static(),
w_s.dim(3).unwrap_static(),
w_s.dim(4).unwrap_static(),
];
let out =
crate::shape::conv3d_output_shape(&in_s, &w_s, ks, stride, padding, dilation, groups)
.expect("conv3d shape inference");
self.push(
Op::Conv3d {
stride,
padding,
dilation,
groups,
},
vec![input, weight],
out,
None,
)
}
#[allow(clippy::too_many_arguments)]
pub fn conv_transpose3d(
&mut self,
input: NodeId,
weight: NodeId,
stride: [usize; 3],
padding: [usize; 3],
dilation: [usize; 3],
output_padding: [usize; 3],
groups: usize,
) -> NodeId {
let in_s = self.node(input).shape.clone();
let w_s = self.node(weight).shape.clone();
assert_eq!(
in_s.rank(),
5,
"conv_transpose3d: input must be [N, C_in, D, H, W]"
);
assert_eq!(
w_s.rank(),
5,
"conv_transpose3d: weight must be [C_in, C_out/groups, kD, kH, kW]"
);
let ks = [
w_s.dim(2).unwrap_static(),
w_s.dim(3).unwrap_static(),
w_s.dim(4).unwrap_static(),
];
let out = crate::shape::conv_transpose3d_output_shape(
&in_s,
&w_s,
ks,
stride,
padding,
dilation,
output_padding,
groups,
)
.expect("conv_transpose3d shape inference");
self.push(
Op::ConvTranspose3d {
stride,
padding,
dilation,
output_padding,
groups,
},
vec![input, weight],
out,
None,
)
}
pub fn interpolate3d(
&mut self,
x: NodeId,
out_dhw: [usize; 3],
mode: InterpMode,
align_corners: bool,
) -> NodeId {
let xs = self.shape(x).clone();
assert_eq!(xs.rank(), 5, "interpolate3d: input must be [N, C, D, H, W]");
assert!(
out_dhw.iter().all(|&l| l > 0),
"interpolate3d: out_dhw must be positive"
);
let mut y = x;
for (i, &out_len) in out_dhw.iter().enumerate() {
y = self.resample_axis(y, 2 + i, out_len, mode, align_corners);
}
y
}
fn resample_axis(
&mut self,
x: NodeId,
axis: usize,
out_len: usize,
mode: InterpMode,
align_corners: bool,
) -> NodeId {
let xs = self.shape(x).clone();
let rank = xs.rank();
let in_len = xs.dim(axis).unwrap_static();
if in_len == out_len {
return x;
}
let mut perm: Vec<usize> = (0..rank).filter(|&d| d != axis).collect();
perm.push(axis);
let xp = self.transpose_(x, perm.clone());
let ps = self.shape(xp).clone();
let batch: usize = (0..rank - 1).map(|d| ps.dim(d).unwrap_static()).product();
let w = interp_matrix(in_len, out_len, mode, align_corners);
let w_node = self.const_f32_tensor(w, &[in_len, out_len]);
let x2 = self.reshape_(xp, vec![batch as i64, in_len as i64]);
let y2 = self.mm(x2, w_node);
let mut out_perm_dims: Vec<i64> = (0..rank - 1)
.map(|d| ps.dim(d).unwrap_static() as i64)
.collect();
out_perm_dims.push(out_len as i64);
let yp = self.reshape_(y2, out_perm_dims);
let mut inv = vec![0usize; rank];
for (new_pos, &old_axis) in perm.iter().enumerate() {
inv[old_axis] = new_pos;
}
self.transpose_(yp, inv)
}
}
fn interp_matrix(l_in: usize, l_out: usize, mode: InterpMode, align_corners: bool) -> Vec<f32> {
let mut w = vec![0f32; l_in * l_out];
for j in 0..l_out {
let pos = if align_corners {
if l_out == 1 {
0.0
} else {
j as f32 * (l_in as f32 - 1.0) / (l_out as f32 - 1.0)
}
} else {
let p = (j as f32 + 0.5) * (l_in as f32) / (l_out as f32) - 0.5;
p.clamp(0.0, l_in as f32 - 1.0)
};
match mode {
InterpMode::Nearest => {
let i = (pos + 0.5).floor() as usize;
let i = i.min(l_in - 1);
w[i * l_out + j] = 1.0;
}
InterpMode::Linear => {
let i0 = pos.floor() as usize;
let i0 = i0.min(l_in - 1);
let i1 = (i0 + 1).min(l_in - 1);
let frac = pos - i0 as f32;
w[i0 * l_out + j] += 1.0 - frac;
w[i1 * l_out + j] += frac;
}
}
}
w
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{DType, Shape};
fn dims(g: &Graph, id: NodeId) -> Vec<usize> {
g.shape(id)
.dims()
.iter()
.map(|d| d.unwrap_static())
.collect()
}
#[test]
fn conv3d_output_shape_matches_formula() {
let mut g = Graph::new("conv3d");
let x = g.input("x", Shape::new(&[1, 2, 8, 8, 8], DType::F32));
let w = g.param("w", Shape::new(&[4, 2, 3, 3, 3], DType::F32));
let y = g.conv3d(x, w, [1, 1, 1], [1, 1, 1], [1, 1, 1], 1);
assert_eq!(dims(&g, y), vec![1, 4, 8, 8, 8]);
}
#[test]
fn conv_transpose3d_upsamples_by_stride() {
let mut g = Graph::new("ct3d");
let x = g.input("x", Shape::new(&[1, 3, 4, 4, 4], DType::F32));
let w = g.param("w", Shape::new(&[3, 5, 2, 2, 2], DType::F32));
let y = g.conv_transpose3d(x, w, [2, 2, 2], [0, 0, 0], [1, 1, 1], [0, 0, 0], 1);
assert_eq!(dims(&g, y), vec![1, 5, 8, 8, 8]);
}
#[test]
fn interpolate3d_shape() {
let mut g = Graph::new("interp3d");
let x = g.input("x", Shape::new(&[1, 2, 2, 3, 4], DType::F32));
let y = g.interpolate3d(x, [4, 6, 8], InterpMode::Linear, false);
assert_eq!(dims(&g, y), vec![1, 2, 4, 6, 8]);
}
}