1use sim_kernel::{Cx, Error, Result, Symbol};
12
13use super::{
14 execution::{TensorExecError, TensorMeta, TensorOp, TensorRequest, execute_tensor_request},
15 execution_math_support::{
16 ProductSpec, float_output_dtype, matches_tensor_transcendental, matmul_output_shape,
17 norm_value, reduce_min_max, reduce_sum, reduction_output_dtype, reduction_pair_dtype,
18 scalar_tensor, sum_products, tensor_from_cells, transcendental_cell,
19 },
20 value::Tensor,
21};
22
23pub fn sum_op_symbol() -> Symbol {
25 Symbol::qualified("tensor", "op/sum")
26}
27
28pub fn min_op_symbol() -> Symbol {
30 Symbol::qualified("tensor", "op/min")
31}
32
33pub fn max_op_symbol() -> Symbol {
35 Symbol::qualified("tensor", "op/max")
36}
37
38pub fn norm_op_symbol() -> Symbol {
40 Symbol::qualified("tensor", "op/norm")
41}
42
43pub fn transpose_exec_op_symbol() -> Symbol {
45 Symbol::qualified("tensor", "op/transpose")
46}
47
48pub fn dot_op_symbol() -> Symbol {
50 Symbol::qualified("tensor", "op/dot")
51}
52
53pub fn matmul_exec_op_symbol() -> Symbol {
55 Symbol::qualified("tensor", "op/matmul")
56}
57
58pub fn sqrt_op_symbol() -> Symbol {
60 Symbol::qualified("tensor", "op/sqrt")
61}
62
63pub fn exp_op_symbol() -> Symbol {
65 Symbol::qualified("tensor", "op/exp")
66}
67
68pub fn sin_op_symbol() -> Symbol {
70 Symbol::qualified("tensor", "op/sin")
71}
72
73pub fn cos_op_symbol() -> Symbol {
75 Symbol::qualified("tensor", "op/cos")
76}
77
78pub fn tensor_executor_math_op_symbols() -> Vec<Symbol> {
80 vec![
81 sum_op_symbol(),
82 min_op_symbol(),
83 max_op_symbol(),
84 norm_op_symbol(),
85 transpose_exec_op_symbol(),
86 dot_op_symbol(),
87 matmul_exec_op_symbol(),
88 sqrt_op_symbol(),
89 exp_op_symbol(),
90 sin_op_symbol(),
91 cos_op_symbol(),
92 ]
93}
94
95pub(crate) fn is_tensor_executor_math_op(symbol: &Symbol) -> bool {
96 tensor_executor_math_op_symbols()
97 .iter()
98 .any(|candidate| candidate == symbol)
99}
100
101pub fn execute_tensor_reduction(cx: &mut Cx, operator: Symbol, tensor: &Tensor) -> Result<Tensor> {
103 let output_dtype = reduction_output_dtype(tensor);
104 let op = TensorOp::without_attributes(cx, operator)?;
105 execute_tensor_request(
106 cx,
107 TensorRequest::new(
108 op,
109 vec![tensor.clone()],
110 TensorMeta::new(Vec::new(), output_dtype),
111 ),
112 )
113}
114
115pub fn execute_tensor_norm(cx: &mut Cx, tensor: &Tensor) -> Result<Tensor> {
117 let op = TensorOp::without_attributes(cx, norm_op_symbol())?;
118 execute_tensor_request(
119 cx,
120 TensorRequest::new(
121 op,
122 vec![tensor.clone()],
123 TensorMeta::new(Vec::new(), float_output_dtype(tensor)),
124 ),
125 )
126}
127
128pub fn execute_tensor_transpose(cx: &mut Cx, tensor: &Tensor) -> Result<Tensor> {
130 let [rows, cols] = tensor.shape() else {
131 return Err(Error::Eval("transpose expects a rank-2 tensor".to_owned()));
132 };
133 let op = TensorOp::without_attributes(cx, transpose_exec_op_symbol())?;
134 execute_tensor_request(
135 cx,
136 TensorRequest::new(
137 op,
138 vec![tensor.clone()],
139 TensorMeta::new(vec![*cols, *rows], tensor.dtype().clone()),
140 ),
141 )
142}
143
144pub fn execute_tensor_dot(cx: &mut Cx, left: &Tensor, right: &Tensor) -> Result<Tensor> {
146 if left.shape().len() != 1 || right.shape().len() != 1 {
147 return Err(Error::Eval("dot expects two rank-1 tensors".to_owned()));
148 }
149 if left.shape() != right.shape() {
150 return Err(Error::Eval(
151 "dot expects vectors with matching lengths".to_owned(),
152 ));
153 }
154 let op = TensorOp::without_attributes(cx, dot_op_symbol())?;
155 execute_tensor_request(
156 cx,
157 TensorRequest::new(
158 op,
159 vec![left.clone(), right.clone()],
160 TensorMeta::new(Vec::new(), reduction_pair_dtype(left, right)),
161 ),
162 )
163}
164
165pub fn execute_tensor_matmul(cx: &mut Cx, left: &Tensor, right: &Tensor) -> Result<Tensor> {
167 let shape = matmul_output_shape(left.shape(), right.shape())?;
168 let op = TensorOp::without_attributes(cx, matmul_exec_op_symbol())?;
169 execute_tensor_request(
170 cx,
171 TensorRequest::new(
172 op,
173 vec![left.clone(), right.clone()],
174 TensorMeta::new(shape, reduction_pair_dtype(left, right)),
175 ),
176 )
177}
178
179pub fn execute_tensor_transcendental(
181 cx: &mut Cx,
182 operator: Symbol,
183 tensor: &Tensor,
184) -> Result<Tensor> {
185 if !matches_tensor_transcendental(&operator) {
186 return Err(Error::Eval(format!(
187 "unsupported tensor transcendental operation {operator}"
188 )));
189 }
190 let op = TensorOp::without_attributes(cx, operator)?;
191 execute_tensor_request(
192 cx,
193 TensorRequest::new(
194 op,
195 vec![tensor.clone()],
196 TensorMeta::new(tensor.shape().to_vec(), float_output_dtype(tensor)),
197 ),
198 )
199}
200
201pub(crate) fn execute_tensor_math_request(
202 cx: &mut Cx,
203 request: &TensorRequest,
204) -> std::result::Result<Tensor, TensorExecError> {
205 let operation = &request.operation.symbol;
206 if *operation == sum_op_symbol()
207 || *operation == min_op_symbol()
208 || *operation == max_op_symbol()
209 {
210 execute_reduction_request(cx, request)
211 } else if *operation == norm_op_symbol() {
212 execute_norm_request(cx, request)
213 } else if *operation == transpose_exec_op_symbol() {
214 execute_transpose_request(cx, request)
215 } else if *operation == dot_op_symbol() {
216 execute_dot_request(cx, request)
217 } else if *operation == matmul_exec_op_symbol() {
218 execute_matmul_request(cx, request)
219 } else if matches_tensor_transcendental(operation) {
220 execute_transcendental_request(cx, request)
221 } else {
222 Err(TensorExecError::unsupported(
223 operation.clone(),
224 "unknown tensor math operation",
225 ))
226 }
227}
228
229fn execute_reduction_request(
230 cx: &mut Cx,
231 request: &TensorRequest,
232) -> std::result::Result<Tensor, TensorExecError> {
233 let [tensor] = request.inputs.as_ref() else {
234 return Err(TensorExecError::invalid(
235 "tensor reduction expects exactly one tensor input",
236 ));
237 };
238 let value = if request.operation.symbol == sum_op_symbol() {
239 reduce_sum(cx, tensor)?
240 } else if request.operation.symbol == min_op_symbol() {
241 reduce_min_max(cx, tensor, false)?
242 } else {
243 reduce_min_max(cx, tensor, true)?
244 };
245 scalar_tensor(cx, request.output.dtype().clone(), value)
246}
247
248fn execute_norm_request(
249 cx: &mut Cx,
250 request: &TensorRequest,
251) -> std::result::Result<Tensor, TensorExecError> {
252 let [tensor] = request.inputs.as_ref() else {
253 return Err(TensorExecError::invalid(
254 "tensor norm expects exactly one tensor input",
255 ));
256 };
257 let value = norm_value(cx, tensor)?;
258 scalar_tensor(cx, request.output.dtype().clone(), value)
259}
260
261fn execute_transpose_request(
262 cx: &mut Cx,
263 request: &TensorRequest,
264) -> std::result::Result<Tensor, TensorExecError> {
265 let [tensor] = request.inputs.as_ref() else {
266 return Err(TensorExecError::invalid(
267 "transpose expects exactly one tensor input",
268 ));
269 };
270 let [rows, cols] = tensor.shape() else {
271 return Err(TensorExecError::invalid("transpose expects rank-2 input"));
272 };
273 let mut out = Vec::with_capacity(tensor.len());
274 for col in 0..*cols {
275 for row in 0..*rows {
276 out.push(
277 tensor
278 .cell(row * cols + col)
279 .map_err(TensorExecError::from)?,
280 );
281 }
282 }
283 tensor_from_cells(
284 cx,
285 request.output.shape().to_vec(),
286 request.output.dtype().clone(),
287 out,
288 )
289}
290
291fn execute_dot_request(
292 cx: &mut Cx,
293 request: &TensorRequest,
294) -> std::result::Result<Tensor, TensorExecError> {
295 let [left, right] = request.inputs.as_ref() else {
296 return Err(TensorExecError::invalid(
297 "dot expects exactly two tensor inputs",
298 ));
299 };
300 if left.shape().len() != 1 || right.shape().len() != 1 || left.shape() != right.shape() {
301 return Err(TensorExecError::invalid(
302 "dot expects matching rank-1 tensor inputs",
303 ));
304 }
305 let value = sum_products(
306 cx,
307 ProductSpec {
308 left,
309 right,
310 left_start: 0,
311 right_start: 0,
312 count: left.shape()[0],
313 left_stride: 1,
314 right_stride: 1,
315 },
316 )?;
317 scalar_tensor(cx, request.output.dtype().clone(), value)
318}
319
320fn execute_matmul_request(
321 cx: &mut Cx,
322 request: &TensorRequest,
323) -> std::result::Result<Tensor, TensorExecError> {
324 let [left, right] = request.inputs.as_ref() else {
325 return Err(TensorExecError::invalid(
326 "matmul expects exactly two tensor inputs",
327 ));
328 };
329 let out_shape =
330 matmul_output_shape(left.shape(), right.shape()).map_err(TensorExecError::from)?;
331 if out_shape != request.output.shape() {
332 return Err(TensorExecError::shape(format!(
333 "matmul output shape {:?} did not match {:?}",
334 out_shape,
335 request.output.shape()
336 )));
337 }
338 match (left.shape(), right.shape()) {
339 ([n], [m]) if n == m => {
340 let value = sum_products(
341 cx,
342 ProductSpec {
343 left,
344 right,
345 left_start: 0,
346 right_start: 0,
347 count: *n,
348 left_stride: 1,
349 right_stride: 1,
350 },
351 )?;
352 scalar_tensor(cx, request.output.dtype().clone(), value)
353 }
354 ([rows, inner_left], [inner_right, cols]) if inner_left == inner_right => {
355 let mut out = Vec::with_capacity(rows * cols);
356 for row in 0..*rows {
357 for col in 0..*cols {
358 out.push(sum_products(
359 cx,
360 ProductSpec {
361 left,
362 right,
363 left_start: row * inner_left,
364 right_start: col,
365 count: *inner_left,
366 left_stride: 1,
367 right_stride: *cols,
368 },
369 )?);
370 }
371 }
372 tensor_from_cells(cx, out_shape, request.output.dtype().clone(), out)
373 }
374 ([rows, inner_left], [inner_right]) if inner_left == inner_right => {
375 let mut out = Vec::with_capacity(*rows);
376 for row in 0..*rows {
377 out.push(sum_products(
378 cx,
379 ProductSpec {
380 left,
381 right,
382 left_start: row * inner_left,
383 right_start: 0,
384 count: *inner_left,
385 left_stride: 1,
386 right_stride: 1,
387 },
388 )?);
389 }
390 tensor_from_cells(cx, out_shape, request.output.dtype().clone(), out)
391 }
392 ([inner_left], [inner_right, cols]) if inner_left == inner_right => {
393 let mut out = Vec::with_capacity(*cols);
394 for col in 0..*cols {
395 out.push(sum_products(
396 cx,
397 ProductSpec {
398 left,
399 right,
400 left_start: 0,
401 right_start: col,
402 count: *inner_left,
403 left_stride: 1,
404 right_stride: *cols,
405 },
406 )?);
407 }
408 tensor_from_cells(cx, out_shape, request.output.dtype().clone(), out)
409 }
410 _ => Err(TensorExecError::invalid(
411 "matmul supports rank-1 and rank-2 tensors with matching inner dimensions",
412 )),
413 }
414}
415
416fn execute_transcendental_request(
417 cx: &mut Cx,
418 request: &TensorRequest,
419) -> std::result::Result<Tensor, TensorExecError> {
420 let [tensor] = request.inputs.as_ref() else {
421 return Err(TensorExecError::invalid(
422 "transcendental expects exactly one tensor input",
423 ));
424 };
425 let cells = tensor.cells().map_err(TensorExecError::from)?;
426 let mut out = Vec::with_capacity(cells.len());
427 for cell in cells.iter() {
428 out.push(transcendental_cell(
429 cx,
430 cell,
431 &request.operation.symbol,
432 request.output.dtype(),
433 )?);
434 }
435 tensor_from_cells(
436 cx,
437 request.output.shape().to_vec(),
438 request.output.dtype().clone(),
439 out,
440 )
441}