1use anyhow::{Context, Result};
2use candle_core::Tensor;
3
4pub fn linear(x: &Tensor, w: &Tensor, b: Option<&Tensor>) -> Result<Tensor> {
7 let dims = x.dims().to_vec();
8 let d_in = *dims.last().context("linear: input has no dims")?;
9 let lead: usize = dims[..dims.len() - 1].iter().product();
10 let x2 = x.reshape((lead, d_in))?;
11 let y2 = x2.matmul(&w.t()?)?;
12 let d_out = w.dim(0)?;
13 let mut out_dims = dims[..dims.len() - 1].to_vec();
14 out_dims.push(d_out);
15 let y = y2.reshape(out_dims)?;
16 match b {
17 Some(b) => Ok(y.broadcast_add(b)?),
18 None => Ok(y),
19 }
20}
21
22pub fn linear_nobias(x: &Tensor, w: &Tensor) -> Result<Tensor> {
23 linear(x, w, None)
24}
25
26pub fn linear_bias(x: &Tensor, w: &Tensor, b: &Tensor) -> Result<Tensor> {
27 linear(x, w, Some(b))
28}
29
30#[cfg(test)]
31mod tests {
32 use super::*;
33 use candle_core::Device;
34
35 #[test]
36 fn matches_manual_matmul_for_3d_input() {
37 let device = Device::Cpu;
38 let x = Tensor::from_vec((0..24u32).map(|v| v as f32).collect(), (2, 3, 4), &device).unwrap();
39 let w = Tensor::from_vec((0..8u32).map(|v| v as f32 * 0.5).collect(), (2, 4), &device).unwrap();
40 let b = Tensor::from_vec(vec![1.0f32, -1.0], (2,), &device).unwrap();
41
42 let got = linear(&x, &w, Some(&b)).unwrap();
43 assert_eq!(got.dims(), &[2, 3, 2]);
44
45 let x2 = x.reshape((6, 4)).unwrap();
47 let want2 = x2.matmul(&w.t().unwrap()).unwrap().broadcast_add(&b).unwrap();
48 let want = want2.reshape((2, 3, 2)).unwrap();
49
50 let got_v: Vec<f32> = got.flatten_all().unwrap().to_vec1().unwrap();
51 let want_v: Vec<f32> = want.flatten_all().unwrap().to_vec1().unwrap();
52 assert_eq!(got_v, want_v);
53 }
54
55 #[test]
56 fn nobias_matches_direct_matmul_for_2d_input() {
57 let device = Device::Cpu;
58 let x = Tensor::from_vec((0..12u32).map(|v| v as f32).collect(), (3, 4), &device).unwrap();
59 let w = Tensor::from_vec((0..8u32).map(|v| v as f32).collect(), (2, 4), &device).unwrap();
60
61 let got = linear_nobias(&x, &w).unwrap();
62 let want = x.matmul(&w.t().unwrap()).unwrap();
63
64 let got_v: Vec<f32> = got.flatten_all().unwrap().to_vec1().unwrap();
65 let want_v: Vec<f32> = want.flatten_all().unwrap().to_vec1().unwrap();
66 assert_eq!(got_v, want_v);
67 }
68}