vyre_libs/nn/linear/inner/
builder.rs1use vyre::ir::{DataType, Program};
4
5use crate::{
6 builder::{check_tensors, BuildOptions},
7 region::tag_program,
8 tensor_ref::{TensorRef, TensorRefError},
9 MatmulBias,
10};
11
12use super::tiled::{linear_tiled, LINEAR_TILED_MIN_WORK, LINEAR_TILED_TILE};
13
14pub(super) const LINEAR_OP_ID: &str = "vyre-libs::nn::linear";
15#[derive(Debug, Clone)]
17pub struct Linear {
18 x: TensorRef,
19 w: TensorRef,
20 b: TensorRef,
21 out: TensorRef,
22 options: BuildOptions,
23}
24
25impl Linear {
26 #[must_use]
28 pub fn new(x: TensorRef, w: TensorRef, b: TensorRef, out: TensorRef) -> Self {
29 Self {
30 x,
31 w,
32 b,
33 out,
34 options: BuildOptions::default(),
35 }
36 }
37
38 pub fn build(self) -> Result<Program, TensorRefError> {
45 check_tensors(
46 LINEAR_OP_ID,
47 &[
48 (&self.x, DataType::U32),
49 (&self.w, DataType::U32),
50 (&self.b, DataType::U32),
51 (&self.out, DataType::U32),
52 ],
53 )?;
54 let x_shape = self.x.shape.as_ref();
55 let w_shape = self.w.shape.as_ref();
56 let b_shape = self.b.shape.as_ref();
57 let out_shape = self.out.shape.as_ref();
58 let expected_w = match x_shape {
59 [in_dim] => match out_shape {
60 [out_dim] => vec![*in_dim, *out_dim],
61 _ => vec![],
62 },
63 _ => vec![],
64 };
65 if w_shape != expected_w.as_slice() {
66 return Err(TensorRefError::ShapeMismatch {
67 name: self.w.name_str().to_string(),
68 found: self.w.shape.to_vec(),
69 expected: expected_w,
70 op: LINEAR_OP_ID,
71 });
72 }
73 if b_shape != out_shape {
74 return Err(TensorRefError::ShapeMismatch {
75 name: self.b.name_str().to_string(),
76 found: self.b.shape.to_vec(),
77 expected: self.out.shape.to_vec(),
78 op: LINEAR_OP_ID,
79 });
80 }
81 let &[in_dim] = x_shape else {
82 return Err(TensorRefError::ShapeMismatch {
83 name: self.x.name_str().to_string(),
84 found: self.x.shape.to_vec(),
85 expected: vec![1],
86 op: LINEAR_OP_ID,
87 });
88 };
89 let &[out_dim] = out_shape else {
90 return Err(TensorRefError::ShapeMismatch {
91 name: self.out.name_str().to_string(),
92 found: self.out.shape.to_vec(),
93 expected: vec![1],
94 op: LINEAR_OP_ID,
95 });
96 };
97 if in_dim == 0 {
98 return Err(TensorRefError::ShapeMismatch {
99 name: self.x.name_str().to_string(),
100 found: self.x.shape.to_vec(),
101 expected: vec![1],
102 op: LINEAR_OP_ID,
103 });
104 }
105 if out_dim == 0 {
106 return Err(TensorRefError::ShapeMismatch {
107 name: self.out.name_str().to_string(),
108 found: self.out.shape.to_vec(),
109 expected: vec![1],
110 op: LINEAR_OP_ID,
111 });
112 }
113 build_linear_program(
114 self.x.name_str(),
115 self.w.name_str(),
116 self.b.name_str(),
117 self.out.name_str(),
118 in_dim,
119 out_dim,
120 self.options,
121 )
122 .map_err(|_| TensorRefError::ElementCountOverflow {
123 name: self.w.name_str().to_string(),
124 shape: self.w.shape.to_vec(),
125 })
126 }
127}
128
129crate::builder::impl_cat_a_builder_options!(Linear);
130
131pub fn linear(
140 x: &str,
141 w: &str,
142 b: &str,
143 out: &str,
144 in_dim: u32,
145 out_dim: u32,
146) -> Result<Program, String> {
147 if in_dim
148 .checked_mul(out_dim)
149 .is_some_and(|work| work >= LINEAR_TILED_MIN_WORK)
150 {
151 return linear_tiled(x, w, b, out, in_dim, out_dim, LINEAR_TILED_TILE);
152 }
153
154 Linear::new(
155 TensorRef::u32_1d(x, in_dim),
156 TensorRef::u32_2d(w, in_dim, out_dim),
157 TensorRef::u32_1d(b, out_dim),
158 TensorRef::u32_1d(out, out_dim),
159 )
160 .build()
161 .map_err(|error| format!("Fix: {LINEAR_OP_ID} build failed: {error}"))
162}
163
164fn build_linear_program(
165 x: &str,
166 w: &str,
167 b: &str,
168 out: &str,
169 in_dim: u32,
170 out_dim: u32,
171 options: BuildOptions,
172) -> Result<Program, String> {
173 in_dim.checked_mul(out_dim).ok_or_else(|| {
174 "Fix: linear in_dim*out_dim overflows u32; reduce dimensions.".to_string()
175 })?;
176 let mut builder = MatmulBias::new(
177 TensorRef::u32_2d(x, 1, in_dim),
178 TensorRef::u32_2d(w, in_dim, out_dim),
179 TensorRef::u32_1d(b, out_dim),
180 TensorRef::u32_2d(out, 1, out_dim),
181 );
182 if let Some(workgroup_size) = options.workgroup_size {
183 builder = builder.with_workgroup_size(workgroup_size);
184 }
185 let program = builder
186 .build()
187 .map_err(|error| format!("Fix: linear matmul_bias build failed: {error}"))?;
188 Ok(tag_program(
189 options.region_generator.unwrap_or(LINEAR_OP_ID),
190 program,
191 ))
192}