1use std::{fmt, sync::Arc};
4
5use sim_kernel::{
6 CapabilityName, ClassRef, Cx, DefaultFactory, Error, Factory, Object, Result, Symbol, Value,
7};
8
9use super::{
10 canonical_ops::{
11 canonical_tensor_op_symbols, execute_canonical_request, is_canonical_tensor_op,
12 },
13 cast::cast_tensor,
14 elementwise::{
15 execute_elementwise_binary_request, execute_elementwise_unary_request,
16 is_elementwise_binary_op, is_elementwise_unary_op, tensor_elementwise_op_symbols,
17 },
18 execution_ops::{
19 execute_tensor_math_request, is_tensor_executor_math_op, tensor_executor_math_op_symbols,
20 },
21 value::{Tensor, build_tensor_value, tensor_value_ref},
22};
23
24pub fn tensor_executor_symbol() -> Symbol {
26 Symbol::qualified("tensor", "executor")
27}
28
29pub fn tensor_site_symbol() -> Symbol {
31 Symbol::new("site/tensor")
32}
33
34pub fn tensor_execute_capability() -> CapabilityName {
37 CapabilityName::new("tensor.execute")
38}
39
40pub fn active_tensor_executor(cx: &Cx) -> Option<Arc<dyn TensorExecutor>> {
42 cx.env().get(&tensor_executor_symbol()).and_then(|value| {
43 value
44 .object()
45 .downcast_ref::<TensorExecutorBinding>()
46 .map(TensorExecutorBinding::executor)
47 })
48}
49
50pub fn tensor_op_symbol() -> Symbol {
52 Symbol::qualified("tensor", "op/tensor")
53}
54
55pub fn scalar_op_symbol() -> Symbol {
57 Symbol::qualified("tensor", "op/scalar")
58}
59
60pub fn vec_op_symbol() -> Symbol {
62 Symbol::qualified("tensor", "op/vec")
63}
64
65pub fn mat_op_symbol() -> Symbol {
67 Symbol::qualified("tensor", "op/mat")
68}
69
70pub fn index_op_symbol() -> Symbol {
72 Symbol::qualified("tensor", "op/index")
73}
74
75pub fn reshape_op_symbol() -> Symbol {
77 Symbol::qualified("tensor", "op/reshape")
78}
79
80pub fn slice_op_symbol() -> Symbol {
82 Symbol::qualified("tensor", "op/slice")
83}
84
85pub fn map_op_symbol() -> Symbol {
87 Symbol::qualified("tensor", "op/map")
88}
89
90pub fn cast_op_symbol() -> Symbol {
92 Symbol::qualified("tensor", "op/cast")
93}
94
95#[derive(Clone, Debug, PartialEq, Eq)]
97pub struct TensorMeta {
98 shape: Arc<[usize]>,
99 dtype: Symbol,
100}
101
102impl TensorMeta {
103 pub fn new(shape: Vec<usize>, dtype: Symbol) -> Self {
105 Self {
106 shape: shape.into(),
107 dtype,
108 }
109 }
110
111 pub fn from_tensor(tensor: &Tensor) -> Self {
113 Self::new(tensor.shape().to_vec(), tensor.dtype().clone())
114 }
115
116 pub fn shape(&self) -> &[usize] {
118 &self.shape
119 }
120
121 pub fn dtype(&self) -> &Symbol {
123 &self.dtype
124 }
125}
126
127#[derive(Clone, Debug)]
129pub struct TensorOp {
130 pub symbol: Symbol,
132 pub attributes: Value,
134}
135
136impl TensorOp {
137 pub fn new(symbol: Symbol, attributes: Value) -> Self {
139 Self { symbol, attributes }
140 }
141
142 pub fn without_attributes(cx: &mut Cx, symbol: Symbol) -> Result<Self> {
144 Ok(Self::new(symbol, cx.factory().nil()?))
145 }
146}
147
148#[derive(Clone)]
150pub struct TensorRequest {
151 pub operation: TensorOp,
153 pub inputs: Arc<[Tensor]>,
155 pub output: TensorMeta,
157}
158
159impl TensorRequest {
160 pub fn new(operation: TensorOp, inputs: Vec<Tensor>, output: TensorMeta) -> Self {
162 Self {
163 operation,
164 inputs: inputs.into(),
165 output,
166 }
167 }
168}
169
170#[derive(Clone)]
172pub enum TensorExecution {
173 Complete(Tensor),
175 Unsupported {
177 reason: Arc<str>,
179 },
180}
181
182#[derive(Clone, Debug, PartialEq, Eq)]
184pub struct TensorExecutorCard {
185 pub symbol: Symbol,
187 pub provider: String,
189 pub locality: Symbol,
191 pub operations: Arc<[Symbol]>,
193 pub device_capability: Option<CapabilityName>,
195}
196
197impl TensorExecutorCard {
198 pub fn new(
200 symbol: Symbol,
201 provider: impl Into<String>,
202 locality: Symbol,
203 operations: Vec<Symbol>,
204 device_capability: Option<CapabilityName>,
205 ) -> Self {
206 Self {
207 symbol,
208 provider: provider.into(),
209 locality,
210 operations: operations.into(),
211 device_capability,
212 }
213 }
214}
215
216#[derive(Clone, Debug, PartialEq, Eq)]
218pub struct SubmissionEvidence {
219 pub executor: Symbol,
221 pub accepted: usize,
223}
224
225impl SubmissionEvidence {
226 pub fn new(executor: Symbol, accepted: usize) -> Self {
228 Self { executor, accepted }
229 }
230}
231
232#[derive(Clone, Debug, PartialEq, Eq)]
234pub enum TensorExecError {
235 CapabilityDenied {
237 capability: CapabilityName,
239 },
240 InvalidRequest {
242 message: Arc<str>,
244 },
245 Unsupported {
247 operation: Symbol,
249 reason: Arc<str>,
251 },
252 Shape {
254 message: Arc<str>,
256 },
257 Eval {
259 message: Arc<str>,
261 },
262}
263
264impl TensorExecError {
265 pub(crate) fn invalid(message: impl Into<Arc<str>>) -> Self {
266 Self::InvalidRequest {
267 message: message.into(),
268 }
269 }
270
271 pub(crate) fn shape(message: impl Into<Arc<str>>) -> Self {
272 Self::Shape {
273 message: message.into(),
274 }
275 }
276
277 pub(crate) fn unsupported(operation: Symbol, reason: impl Into<Arc<str>>) -> Self {
278 Self::Unsupported {
279 operation,
280 reason: reason.into(),
281 }
282 }
283}
284
285impl fmt::Display for TensorExecError {
286 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
287 match self {
288 Self::CapabilityDenied { capability } => {
289 write!(f, "capability denied: {capability}")
290 }
291 Self::InvalidRequest { message } => f.write_str(message),
292 Self::Unsupported { operation, reason } => {
293 write!(f, "unsupported tensor operation {operation}: {reason}")
294 }
295 Self::Shape { message } => f.write_str(message),
296 Self::Eval { message } => f.write_str(message),
297 }
298 }
299}
300
301impl std::error::Error for TensorExecError {}
302
303impl From<Error> for TensorExecError {
304 fn from(error: Error) -> Self {
305 match error {
306 Error::CapabilityDenied { capability } => Self::CapabilityDenied { capability },
307 Error::WrongShape { diagnostics, .. } => {
308 let message = diagnostics
309 .first()
310 .map(|diagnostic| diagnostic.message.clone())
311 .unwrap_or_else(|| "tensor result shape check failed".to_owned());
312 Self::Shape {
313 message: Arc::from(message),
314 }
315 }
316 other => Self::Eval {
317 message: Arc::from(other.to_string()),
318 },
319 }
320 }
321}
322
323impl From<TensorExecError> for Error {
324 fn from(error: TensorExecError) -> Self {
325 match error {
326 TensorExecError::CapabilityDenied { capability } => {
327 Error::CapabilityDenied { capability }
328 }
329 other => Error::Eval(other.to_string()),
330 }
331 }
332}
333
334pub trait TensorExecutor: Send + Sync + 'static {
336 fn card(&self) -> TensorExecutorCard;
338
339 fn execute(
341 &self,
342 cx: &mut Cx,
343 request: TensorRequest,
344 ) -> std::result::Result<TensorExecution, TensorExecError>;
345
346 fn flush(&self) -> std::result::Result<SubmissionEvidence, TensorExecError>;
348}
349
350pub fn execute_tensor_request(cx: &mut Cx, request: TensorRequest) -> Result<Tensor> {
352 let operation = request.operation.symbol.clone();
353 let executor = active_tensor_executor(cx).unwrap_or_else(|| Arc::new(CpuTensorExecutor::new()));
354 match executor.execute(cx, request).map_err(Error::from)? {
355 TensorExecution::Complete(tensor) => Ok(tensor),
356 TensorExecution::Unsupported { reason } => {
357 Err(Error::from(TensorExecError::unsupported(operation, reason)))
358 }
359 }
360}
361
362pub(crate) fn tensor_executor_value(executor: Arc<dyn TensorExecutor>) -> Result<Value> {
363 DefaultFactory.opaque(Arc::new(TensorExecutorBinding { executor }))
364}
365
366struct TensorExecutorBinding {
367 executor: Arc<dyn TensorExecutor>,
368}
369
370impl TensorExecutorBinding {
371 fn executor(&self) -> Arc<dyn TensorExecutor> {
372 self.executor.clone()
373 }
374}
375
376impl Object for TensorExecutorBinding {
377 fn display(&self, _cx: &mut Cx) -> Result<String> {
378 let card = self.executor.card();
379 Ok(format!("#<tensor-executor {}>", card.symbol))
380 }
381
382 fn as_any(&self) -> &dyn std::any::Any {
383 self
384 }
385}
386
387impl sim_kernel::ObjectCompat for TensorExecutorBinding {
388 fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
389 if let Some(value) = cx
390 .registry()
391 .class_by_symbol(&Symbol::qualified("core", "Function"))
392 {
393 return Ok(value.clone());
394 }
395 DefaultFactory.class_stub(
396 sim_kernel::CORE_FUNCTION_CLASS_ID,
397 Symbol::qualified("core", "Function"),
398 )
399 }
400}
401
402#[derive(Clone, Debug, Default)]
404pub struct CpuTensorExecutor;
405
406impl CpuTensorExecutor {
407 pub fn new() -> Self {
409 Self
410 }
411}
412
413impl TensorExecutor for CpuTensorExecutor {
414 fn card(&self) -> TensorExecutorCard {
415 TensorExecutorCard::new(
416 Symbol::qualified("tensor", "executor/cpu"),
417 "cpu",
418 Symbol::qualified("core", "local-fabric"),
419 vec![
420 tensor_op_symbol(),
421 scalar_op_symbol(),
422 vec_op_symbol(),
423 mat_op_symbol(),
424 reshape_op_symbol(),
425 cast_op_symbol(),
426 ]
427 .into_iter()
428 .chain(tensor_elementwise_op_symbols())
429 .chain(tensor_executor_math_op_symbols())
430 .chain(canonical_tensor_op_symbols())
431 .collect(),
432 None,
433 )
434 }
435
436 fn execute(
437 &self,
438 cx: &mut Cx,
439 request: TensorRequest,
440 ) -> std::result::Result<TensorExecution, TensorExecError> {
441 let operation = request.operation.symbol.clone();
442 let result = if operation == tensor_op_symbol() || operation == vec_op_symbol() {
443 execute_tensor(cx, &request)?
444 } else if operation == scalar_op_symbol() {
445 execute_scalar(&request)?
446 } else if operation == mat_op_symbol() {
447 execute_mat(cx, &request)?
448 } else if operation == reshape_op_symbol() {
449 execute_reshape(cx, &request)?
450 } else if operation == cast_op_symbol() {
451 execute_cast(&request)?
452 } else if operation == index_op_symbol() {
453 return Err(TensorExecError::unsupported(
454 operation,
455 "index returns a scalar value, not a tensor",
456 ));
457 } else if is_elementwise_binary_op(&operation) {
458 execute_elementwise_binary_request(cx, &request)?
459 } else if is_elementwise_unary_op(&operation) {
460 execute_elementwise_unary_request(cx, &request)?
461 } else if is_tensor_executor_math_op(&operation) {
462 execute_tensor_math_request(cx, &request)?
463 } else if is_canonical_tensor_op(&operation) {
464 execute_canonical_request(cx, &request)?
465 } else {
466 return Ok(TensorExecution::Unsupported {
467 reason: Arc::from("unknown tensor operation"),
468 });
469 };
470 check_output(&request.output, &result)?;
471 Ok(TensorExecution::Complete(result))
472 }
473
474 fn flush(&self) -> std::result::Result<SubmissionEvidence, TensorExecError> {
475 Ok(SubmissionEvidence::new(
476 Symbol::qualified("tensor", "executor/cpu"),
477 0,
478 ))
479 }
480}
481
482impl Object for CpuTensorExecutor {
483 fn display(&self, _cx: &mut Cx) -> Result<String> {
484 Ok("#<tensor-executor cpu>".to_owned())
485 }
486
487 fn as_any(&self) -> &dyn std::any::Any {
488 self
489 }
490}
491
492impl sim_kernel::ObjectCompat for CpuTensorExecutor {
493 fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
494 if let Some(value) = cx
495 .registry()
496 .class_by_symbol(&Symbol::qualified("core", "Function"))
497 {
498 return Ok(value.clone());
499 }
500 DefaultFactory.class_stub(
501 sim_kernel::CORE_FUNCTION_CLASS_ID,
502 Symbol::qualified("core", "Function"),
503 )
504 }
505}
506
507fn execute_tensor(
508 cx: &mut Cx,
509 request: &TensorRequest,
510) -> std::result::Result<Tensor, TensorExecError> {
511 let cells = request
512 .inputs
513 .iter()
514 .map(|tensor| {
515 if tensor.rank() == 0 {
516 tensor.cell(0)
517 } else {
518 Err(Error::Eval(
519 "tensor op/tensor expects scalar tensor inputs as cells".to_owned(),
520 ))
521 }
522 })
523 .collect::<Result<Vec<_>>>()
524 .map_err(TensorExecError::from)?;
525 build_tensor_value(
526 cx,
527 request.output.shape().to_vec(),
528 Some(request.output.dtype().clone()),
529 cells,
530 )
531 .map_err(TensorExecError::from)
532 .and_then(|value| tensor_from_value(&value))
533}
534
535fn execute_scalar(request: &TensorRequest) -> std::result::Result<Tensor, TensorExecError> {
536 let [tensor] = request.inputs.as_ref() else {
537 return Err(TensorExecError::invalid(
538 "scalar operation expects exactly one tensor input",
539 ));
540 };
541 if tensor.rank() != 0 {
542 return Err(TensorExecError::invalid(
543 "scalar operation expects a rank-0 tensor input",
544 ));
545 }
546 Ok(tensor.clone())
547}
548
549fn execute_mat(
550 cx: &mut Cx,
551 request: &TensorRequest,
552) -> std::result::Result<Tensor, TensorExecError> {
553 if request.output.shape().len() != 2 {
554 return Err(TensorExecError::invalid(
555 "matrix operation expects rank-2 output metadata",
556 ));
557 }
558 let row_width = request.output.shape()[1];
559 let mut cells = Vec::new();
560 for row in request.inputs.iter() {
561 if row.shape() != [row_width] {
562 return Err(TensorExecError::invalid(
563 "matrix operation inputs must be rank-1 rows matching output width",
564 ));
565 }
566 cells.extend(row.cells().map_err(TensorExecError::from)?.iter().cloned());
567 }
568 build_tensor_value(
569 cx,
570 request.output.shape().to_vec(),
571 Some(request.output.dtype().clone()),
572 cells,
573 )
574 .map_err(TensorExecError::from)
575 .and_then(|value| tensor_from_value(&value))
576}
577
578fn execute_reshape(
579 cx: &mut Cx,
580 request: &TensorRequest,
581) -> std::result::Result<Tensor, TensorExecError> {
582 let [tensor] = request.inputs.as_ref() else {
583 return Err(TensorExecError::invalid(
584 "reshape operation expects exactly one tensor input",
585 ));
586 };
587 build_tensor_value(
588 cx,
589 request.output.shape().to_vec(),
590 Some(request.output.dtype().clone()),
591 tensor
592 .cells()
593 .map_err(TensorExecError::from)?
594 .iter()
595 .cloned()
596 .collect(),
597 )
598 .map_err(TensorExecError::from)
599 .and_then(|value| tensor_from_value(&value))
600}
601
602fn execute_cast(request: &TensorRequest) -> std::result::Result<Tensor, TensorExecError> {
603 let [tensor] = request.inputs.as_ref() else {
604 return Err(TensorExecError::invalid(
605 "cast operation expects exactly one tensor input",
606 ));
607 };
608 cast_tensor(tensor, request.output.dtype().clone()).map_err(TensorExecError::from)
609}
610
611fn tensor_from_value(value: &Value) -> std::result::Result<Tensor, TensorExecError> {
612 tensor_value_ref(value)
613 .cloned()
614 .ok_or_else(|| TensorExecError::invalid("tensor executor produced a non-tensor value"))
615}
616
617fn check_output(
618 expected: &TensorMeta,
619 result: &Tensor,
620) -> std::result::Result<(), TensorExecError> {
621 if expected.shape() != result.shape() {
622 return Err(TensorExecError::shape(format!(
623 "tensor result shape {:?} did not match {:?}",
624 result.shape(),
625 expected.shape()
626 )));
627 }
628 if expected.dtype() != result.dtype() {
629 return Err(TensorExecError::shape(format!(
630 "tensor result dtype {} did not match {}",
631 result.dtype(),
632 expected.dtype()
633 )));
634 }
635 Ok(())
636}