1use crate::{JitError, JitResult, TensorRef};
7use std::collections::HashMap;
8use std::sync::{Arc, RwLock};
9use torsh_core::DType;
10
11pub type CustomOpFn = Box<dyn Fn(&[TensorRef]) -> JitResult<Vec<TensorRef>> + Send + Sync>;
13
14pub type ShapeInferenceFn = Box<dyn Fn(&[Vec<usize>]) -> JitResult<Vec<Vec<usize>>> + Send + Sync>;
16
17pub type GradientFn =
19 Box<dyn Fn(&[TensorRef], &[TensorRef]) -> JitResult<Vec<TensorRef>> + Send + Sync>;
20
21pub type TypeValidatorFn = Box<dyn Fn(&[TensorRef]) -> JitResult<()> + Send + Sync>;
23
24pub type MemoryOptimizerFn = Box<dyn Fn(&[TensorRef]) -> JitResult<MemoryLayout> + Send + Sync>;
26
27#[derive(Debug, Clone)]
29pub struct PerformanceHints {
30 pub complexity: ComplexityClass,
32
33 pub memory_pattern: MemoryAccessPattern,
35
36 pub vectorizable: bool,
38
39 pub parallelizable: bool,
41
42 pub min_efficient_size: Option<usize>,
44
45 pub cache_friendly: bool,
47
48 pub supports_inplace: bool,
50}
51
52#[derive(Debug, Clone)]
54pub enum ComplexityClass {
55 Constant, Linear, LinearLogN, Quadratic, Cubic, Exponential, Custom(String), }
63
64#[derive(Debug, Clone)]
66pub enum MemoryAccessPattern {
67 Sequential, Random, Strided, Blocked, Broadcast, }
73
74#[derive(Debug, Clone)]
76pub struct MemoryLayout {
77 pub alignment: Option<usize>,
79
80 pub strides: Option<Vec<usize>>,
82
83 pub contiguous: bool,
85
86 pub pool_hint: Option<String>,
88}
89
90#[derive(Debug, Clone)]
92pub struct FusionInfo {
93 pub fusable_with: Vec<String>,
95
96 pub non_fusable_with: Vec<String>,
98
99 pub fusion_barrier: bool,
101
102 pub fusion_priority: i32,
104
105 pub is_elementwise: bool,
107
108 pub is_reduction: bool,
110}
111
112impl Default for PerformanceHints {
113 fn default() -> Self {
114 Self {
115 complexity: ComplexityClass::Linear,
116 memory_pattern: MemoryAccessPattern::Sequential,
117 vectorizable: false,
118 parallelizable: false,
119 min_efficient_size: None,
120 cache_friendly: true,
121 supports_inplace: false,
122 }
123 }
124}
125
126impl Default for MemoryLayout {
127 fn default() -> Self {
128 Self {
129 alignment: None,
130 strides: None,
131 contiguous: true,
132 pool_hint: None,
133 }
134 }
135}
136
137impl Default for FusionInfo {
138 fn default() -> Self {
139 Self {
140 fusable_with: Vec::new(),
141 non_fusable_with: Vec::new(),
142 fusion_barrier: false,
143 fusion_priority: 0,
144 is_elementwise: false,
145 is_reduction: false,
146 }
147 }
148}
149
150pub struct CustomOperator {
152 pub name: String,
154
155 pub namespace: String,
157
158 pub qualified_name: String,
160
161 pub forward_fn: CustomOpFn,
163
164 pub shape_fn: Option<ShapeInferenceFn>,
166
167 pub gradient_fn: Option<GradientFn>,
169
170 pub input_specs: Vec<ArgumentSpec>,
172
173 pub output_specs: Vec<ArgumentSpec>,
175
176 pub is_differentiable: bool,
178
179 pub metadata: HashMap<String, String>,
181
182 pub performance_hints: PerformanceHints,
184
185 pub type_validator: Option<TypeValidatorFn>,
187
188 pub memory_optimizer: Option<MemoryOptimizerFn>,
190
191 pub backend_impls: HashMap<String, CustomOpFn>,
193
194 pub fusion_info: FusionInfo,
196}
197
198#[derive(Debug, Clone)]
200pub struct ArgumentSpec {
201 pub name: String,
203
204 pub dtype: Option<DType>,
206
207 pub shape: Option<Vec<Option<usize>>>,
209
210 pub optional: bool,
212
213 pub default_value: Option<TensorRef>,
215}
216
217lazy_static::lazy_static! {
218 static ref CUSTOM_OP_REGISTRY: Arc<RwLock<CustomOpRegistry>> =
219 Arc::new(RwLock::new(CustomOpRegistry::new()));
220}
221
222pub struct CustomOpRegistry {
224 operators: HashMap<String, Arc<CustomOperator>>,
226
227 namespaces: HashMap<String, Vec<String>>,
229}
230
231impl CustomOpRegistry {
232 fn new() -> Self {
234 Self {
235 operators: HashMap::new(),
236 namespaces: HashMap::new(),
237 }
238 }
239
240 pub fn register(&mut self, op: CustomOperator) -> JitResult<()> {
242 let qualified_name = op.qualified_name.clone();
243 let namespace = op.namespace.clone();
244 let name = op.name.clone();
245
246 if self.operators.contains_key(&qualified_name) {
248 return Err(JitError::RuntimeError(format!(
249 "Operator {} already registered",
250 qualified_name
251 )));
252 }
253
254 self.operators.insert(qualified_name, Arc::new(op));
256
257 self.namespaces.entry(namespace).or_default().push(name);
259
260 Ok(())
261 }
262
263 pub fn get(&self, qualified_name: &str) -> Option<Arc<CustomOperator>> {
265 self.operators.get(qualified_name).cloned()
266 }
267
268 pub fn list_namespace(&self, namespace: &str) -> Vec<String> {
270 self.namespaces.get(namespace).cloned().unwrap_or_default()
271 }
272
273 pub fn list_all(&self) -> Vec<String> {
275 self.operators.keys().cloned().collect()
276 }
277}
278
279pub struct CustomOpBuilder {
281 name: String,
282 namespace: String,
283 forward_fn: Option<CustomOpFn>,
284 shape_fn: Option<ShapeInferenceFn>,
285 gradient_fn: Option<GradientFn>,
286 input_specs: Vec<ArgumentSpec>,
287 output_specs: Vec<ArgumentSpec>,
288 is_differentiable: bool,
289 metadata: HashMap<String, String>,
290 performance_hints: PerformanceHints,
291 type_validator: Option<TypeValidatorFn>,
292 memory_optimizer: Option<MemoryOptimizerFn>,
293 backend_impls: HashMap<String, CustomOpFn>,
294 fusion_info: FusionInfo,
295}
296
297impl CustomOpBuilder {
298 pub fn new(name: impl Into<String>) -> Self {
300 Self {
301 name: name.into(),
302 namespace: "user".to_string(),
303 forward_fn: None,
304 shape_fn: None,
305 gradient_fn: None,
306 input_specs: Vec::new(),
307 output_specs: Vec::new(),
308 is_differentiable: false,
309 metadata: HashMap::new(),
310 performance_hints: PerformanceHints::default(),
311 type_validator: None,
312 memory_optimizer: None,
313 backend_impls: HashMap::new(),
314 fusion_info: FusionInfo::default(),
315 }
316 }
317
318 pub fn namespace(mut self, namespace: impl Into<String>) -> Self {
320 self.namespace = namespace.into();
321 self
322 }
323
324 pub fn forward<F>(mut self, f: F) -> Self
326 where
327 F: Fn(&[TensorRef]) -> JitResult<Vec<TensorRef>> + Send + Sync + 'static,
328 {
329 self.forward_fn = Some(Box::new(f));
330 self
331 }
332
333 pub fn shape_inference<F>(mut self, f: F) -> Self
335 where
336 F: Fn(&[Vec<usize>]) -> JitResult<Vec<Vec<usize>>> + Send + Sync + 'static,
337 {
338 self.shape_fn = Some(Box::new(f));
339 self
340 }
341
342 pub fn gradient<F>(mut self, f: F) -> Self
344 where
345 F: Fn(&[TensorRef], &[TensorRef]) -> JitResult<Vec<TensorRef>> + Send + Sync + 'static,
346 {
347 self.gradient_fn = Some(Box::new(f));
348 self.is_differentiable = true;
349 self
350 }
351
352 pub fn input(mut self, name: impl Into<String>, dtype: Option<DType>) -> Self {
354 self.input_specs.push(ArgumentSpec {
355 name: name.into(),
356 dtype,
357 shape: None,
358 optional: false,
359 default_value: None,
360 });
361 self
362 }
363
364 pub fn output(mut self, name: impl Into<String>, dtype: Option<DType>) -> Self {
366 self.output_specs.push(ArgumentSpec {
367 name: name.into(),
368 dtype,
369 shape: None,
370 optional: false,
371 default_value: None,
372 });
373 self
374 }
375
376 pub fn metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
378 self.metadata.insert(key.into(), value.into());
379 self
380 }
381
382 pub fn performance_hints(mut self, hints: PerformanceHints) -> Self {
384 self.performance_hints = hints;
385 self
386 }
387
388 pub fn complexity(mut self, complexity: ComplexityClass) -> Self {
390 self.performance_hints.complexity = complexity;
391 self
392 }
393
394 pub fn vectorizable(mut self, vectorizable: bool) -> Self {
396 self.performance_hints.vectorizable = vectorizable;
397 self
398 }
399
400 pub fn parallelizable(mut self, parallelizable: bool) -> Self {
402 self.performance_hints.parallelizable = parallelizable;
403 self
404 }
405
406 pub fn memory_pattern(mut self, pattern: MemoryAccessPattern) -> Self {
408 self.performance_hints.memory_pattern = pattern;
409 self
410 }
411
412 pub fn supports_inplace(mut self, inplace: bool) -> Self {
414 self.performance_hints.supports_inplace = inplace;
415 self
416 }
417
418 pub fn type_validator<F>(mut self, validator: F) -> Self
420 where
421 F: Fn(&[TensorRef]) -> JitResult<()> + Send + Sync + 'static,
422 {
423 self.type_validator = Some(Box::new(validator));
424 self
425 }
426
427 pub fn memory_optimizer<F>(mut self, optimizer: F) -> Self
429 where
430 F: Fn(&[TensorRef]) -> JitResult<MemoryLayout> + Send + Sync + 'static,
431 {
432 self.memory_optimizer = Some(Box::new(optimizer));
433 self
434 }
435
436 pub fn backend_impl<F>(mut self, backend: impl Into<String>, implementation: F) -> Self
438 where
439 F: Fn(&[TensorRef]) -> JitResult<Vec<TensorRef>> + Send + Sync + 'static,
440 {
441 self.backend_impls
442 .insert(backend.into(), Box::new(implementation));
443 self
444 }
445
446 pub fn elementwise(mut self, elementwise: bool) -> Self {
448 self.fusion_info.is_elementwise = elementwise;
449 self
450 }
451
452 pub fn reduction(mut self, reduction: bool) -> Self {
454 self.fusion_info.is_reduction = reduction;
455 self
456 }
457
458 pub fn fusion_priority(mut self, priority: i32) -> Self {
460 self.fusion_info.fusion_priority = priority;
461 self
462 }
463
464 pub fn fusable_with(mut self, ops: Vec<String>) -> Self {
466 self.fusion_info.fusable_with.extend(ops);
467 self
468 }
469
470 pub fn non_fusable_with(mut self, ops: Vec<String>) -> Self {
472 self.fusion_info.non_fusable_with.extend(ops);
473 self
474 }
475
476 pub fn fusion_barrier(mut self, barrier: bool) -> Self {
478 self.fusion_info.fusion_barrier = barrier;
479 self
480 }
481
482 pub fn build(self) -> JitResult<()> {
484 let forward_fn = self
485 .forward_fn
486 .ok_or_else(|| JitError::RuntimeError("Forward function not specified".to_string()))?;
487
488 let qualified_name = format!("{}::{}", self.namespace, self.name);
489
490 let op = CustomOperator {
491 name: self.name,
492 namespace: self.namespace,
493 qualified_name,
494 forward_fn,
495 shape_fn: self.shape_fn,
496 gradient_fn: self.gradient_fn,
497 input_specs: self.input_specs,
498 output_specs: self.output_specs,
499 is_differentiable: self.is_differentiable,
500 metadata: self.metadata,
501 performance_hints: self.performance_hints,
502 type_validator: self.type_validator,
503 memory_optimizer: self.memory_optimizer,
504 backend_impls: self.backend_impls,
505 fusion_info: self.fusion_info,
506 };
507
508 register_custom_op(op)
509 }
510}
511
512pub fn register_custom_op(op: CustomOperator) -> JitResult<()> {
514 let mut registry = CUSTOM_OP_REGISTRY
515 .write()
516 .map_err(|_| JitError::RuntimeError("Failed to acquire registry lock".to_string()))?;
517 registry.register(op)
518}
519
520pub fn get_custom_op(qualified_name: &str) -> Option<Arc<CustomOperator>> {
522 CUSTOM_OP_REGISTRY.read().ok()?.get(qualified_name)
523}
524
525pub fn list_custom_ops() -> Vec<String> {
527 CUSTOM_OP_REGISTRY
528 .read()
529 .map(|r| r.list_all())
530 .unwrap_or_default()
531}
532
533pub fn list_ops_in_namespace(namespace: &str) -> Vec<String> {
535 CUSTOM_OP_REGISTRY
536 .read()
537 .map(|r| r.list_namespace(namespace))
538 .unwrap_or_default()
539}
540
541#[macro_export]
543macro_rules! register_op {
544 ($name:expr, $forward:expr) => {
545 $crate::custom_ops::CustomOpBuilder::new($name)
546 .forward($forward)
547 .build()
548 };
549
550 ($name:expr, $forward:expr, $gradient:expr) => {
551 $crate::custom_ops::CustomOpBuilder::new($name)
552 .forward($forward)
553 .gradient($gradient)
554 .build()
555 };
556}
557
558pub mod examples {
560 use super::*;
561
562 pub fn register_example_ops() -> JitResult<()> {
564 CustomOpBuilder::new("relu6")
566 .namespace("torsh")
567 .forward(|inputs| {
568 if inputs.is_empty() {
569 return Err(JitError::RuntimeError("ReLU6 requires 1 input".to_string()));
570 }
571
572 let input = &inputs[0];
573 let mut output = input.clone();
574
575 for val in &mut output.data {
577 *val = val.clamp(0.0, 6.0);
578 }
579
580 Ok(vec![output])
581 })
582 .shape_inference(|shapes| {
583 if shapes.is_empty() {
584 return Err(JitError::RuntimeError(
585 "ReLU6 requires 1 input shape".to_string(),
586 ));
587 }
588 Ok(vec![shapes[0].clone()])
589 })
590 .gradient(|inputs, grad_outputs| {
591 let input = &inputs[0];
592 let grad_output = &grad_outputs[0];
593 let mut grad_input = grad_output.clone();
594
595 for (i, &val) in input.data.iter().enumerate() {
597 if val <= 0.0 || val >= 6.0 {
598 grad_input.data[i] = 0.0;
599 }
600 }
601
602 Ok(vec![grad_input])
603 })
604 .input("input", Some(DType::F32))
605 .output("output", Some(DType::F32))
606 .metadata("description", "ReLU with upper bound of 6")
607 .build()?;
608
609 CustomOpBuilder::new("gelu_approx")
611 .namespace("torsh")
612 .forward(|inputs| {
613 if inputs.is_empty() {
614 return Err(JitError::RuntimeError("GELU requires 1 input".to_string()));
615 }
616
617 let input = &inputs[0];
618 let mut output = input.clone();
619
620 for (i, &x) in input.data.iter().enumerate() {
622 let sigmoid = 1.0 / (1.0 + (-1.702 * x).exp());
623 output.data[i] = x * sigmoid;
624 }
625
626 Ok(vec![output])
627 })
628 .shape_inference(|shapes| {
629 if shapes.is_empty() {
630 return Err(JitError::RuntimeError(
631 "GELU requires 1 input shape".to_string(),
632 ));
633 }
634 Ok(vec![shapes[0].clone()])
635 })
636 .input("input", Some(DType::F32))
637 .output("output", Some(DType::F32))
638 .metadata("description", "Gaussian Error Linear Unit approximation")
639 .build()?;
640
641 Ok(())
642 }
643}
644
645pub mod jit_integration {
647 use super::*;
648 use crate::graph::Operation;
649
650 pub fn is_custom_op(op: &Operation) -> bool {
652 matches!(op, Operation::Custom(_))
653 }
654
655 pub fn execute_custom_op(op_name: &str, inputs: &[TensorRef]) -> JitResult<Vec<TensorRef>> {
657 let op = get_custom_op(op_name).ok_or_else(|| {
658 JitError::RuntimeError(format!("Custom operator {} not found", op_name))
659 })?;
660
661 if inputs.len() != op.input_specs.len() {
663 return Err(JitError::RuntimeError(format!(
664 "Expected {} inputs, got {}",
665 op.input_specs.len(),
666 inputs.len()
667 )));
668 }
669
670 (op.forward_fn)(inputs)
672 }
673
674 pub fn infer_custom_op_shapes(
676 op_name: &str,
677 input_shapes: &[Vec<usize>],
678 ) -> JitResult<Vec<Vec<usize>>> {
679 let op = get_custom_op(op_name).ok_or_else(|| {
680 JitError::RuntimeError(format!("Custom operator {} not found", op_name))
681 })?;
682
683 if let Some(shape_fn) = &op.shape_fn {
684 (shape_fn)(input_shapes)
685 } else {
686 if input_shapes.is_empty() {
688 Ok(vec![])
689 } else {
690 Ok(vec![input_shapes[0].clone()])
691 }
692 }
693 }
694}
695
696pub mod profiling {
698 use super::*;
699 use std::collections::HashMap;
700 use std::sync::{Arc, Mutex};
701 use std::time::{Duration, Instant};
702
703 #[derive(Debug, Clone)]
705 pub struct OperatorMetrics {
706 pub op_name: String,
707 pub total_executions: u64,
708 pub total_time: Duration,
709 pub average_time: Duration,
710 pub min_time: Duration,
711 pub max_time: Duration,
712 pub memory_usage: Option<usize>,
713 pub cache_hits: u64,
714 pub cache_misses: u64,
715 }
716
717 lazy_static::lazy_static! {
718 pub static ref PROFILER: Arc<Mutex<CustomOpProfiler>> = Arc::new(Mutex::new(CustomOpProfiler::new()));
719 }
720
721 pub struct CustomOpProfiler {
723 metrics: HashMap<String, OperatorMetrics>,
724 enabled: bool,
725 }
726
727 impl CustomOpProfiler {
728 fn new() -> Self {
729 Self {
730 metrics: HashMap::new(),
731 enabled: true,
732 }
733 }
734
735 pub fn record_execution(
736 &mut self,
737 op_name: &str,
738 duration: Duration,
739 memory_usage: Option<usize>,
740 ) {
741 if !self.enabled {
742 return;
743 }
744
745 let metrics =
746 self.metrics
747 .entry(op_name.to_string())
748 .or_insert_with(|| OperatorMetrics {
749 op_name: op_name.to_string(),
750 total_executions: 0,
751 total_time: Duration::ZERO,
752 average_time: Duration::ZERO,
753 min_time: Duration::MAX,
754 max_time: Duration::ZERO,
755 memory_usage,
756 cache_hits: 0,
757 cache_misses: 0,
758 });
759
760 metrics.total_executions += 1;
761 metrics.total_time += duration;
762 metrics.average_time = metrics.total_time / metrics.total_executions as u32;
763 metrics.min_time = metrics.min_time.min(duration);
764 metrics.max_time = metrics.max_time.max(duration);
765
766 if let Some(mem) = memory_usage {
767 metrics.memory_usage = Some(mem);
768 }
769 }
770
771 pub fn record_cache_hit(&mut self, op_name: &str) {
772 if let Some(metrics) = self.metrics.get_mut(op_name) {
773 metrics.cache_hits += 1;
774 }
775 }
776
777 pub fn record_cache_miss(&mut self, op_name: &str) {
778 if let Some(metrics) = self.metrics.get_mut(op_name) {
779 metrics.cache_misses += 1;
780 }
781 }
782
783 pub fn get_metrics(&self, op_name: &str) -> Option<OperatorMetrics> {
784 self.metrics.get(op_name).cloned()
785 }
786
787 pub fn get_all_metrics(&self) -> Vec<OperatorMetrics> {
788 self.metrics.values().cloned().collect()
789 }
790
791 pub fn reset(&mut self) {
792 self.metrics.clear();
793 }
794
795 pub fn enable(&mut self, enabled: bool) {
796 self.enabled = enabled;
797 }
798 }
799
800 pub fn execute_with_profiling(
802 op_name: &str,
803 inputs: &[TensorRef],
804 ) -> JitResult<Vec<TensorRef>> {
805 let start = Instant::now();
806 let result = jit_integration::execute_custom_op(op_name, inputs);
807 let duration = start.elapsed();
808
809 let memory_usage = inputs
811 .iter()
812 .map(|t| t.data.len() * std::mem::size_of::<f32>())
813 .sum();
814
815 if let Ok(mut profiler) = PROFILER.lock() {
816 profiler.record_execution(op_name, duration, Some(memory_usage));
817 }
818
819 result
820 }
821
822 pub fn get_operator_metrics(op_name: &str) -> Option<OperatorMetrics> {
824 PROFILER.lock().ok()?.get_metrics(op_name)
825 }
826
827 pub fn get_all_metrics() -> Vec<OperatorMetrics> {
829 PROFILER
830 .lock()
831 .map(|p| p.get_all_metrics())
832 .unwrap_or_default()
833 }
834
835 pub fn reset_profiling() {
837 if let Ok(mut profiler) = PROFILER.lock() {
838 profiler.reset();
839 }
840 }
841
842 pub fn enable_profiling(enabled: bool) {
844 if let Ok(mut profiler) = PROFILER.lock() {
845 profiler.enable(enabled);
846 }
847 }
848}
849
850pub mod caching {
852 use super::*;
853 use std::collections::HashMap;
854 use std::hash::{Hash, Hasher};
855 use std::sync::{Arc, RwLock};
856
857 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
859 pub struct CacheKey {
860 pub op_name: String,
861 pub input_hashes: Vec<u64>,
862 pub input_shapes: Vec<Vec<usize>>,
863 }
864
865 #[derive(Debug, Clone)]
867 pub struct CacheEntry {
868 pub result: Vec<TensorRef>,
869 pub timestamp: std::time::SystemTime,
870 pub hit_count: u64,
871 }
872
873 lazy_static::lazy_static! {
874 static ref CACHE: Arc<RwLock<CustomOpCache>> = Arc::new(RwLock::new(CustomOpCache::new()));
875 }
876
877 pub struct CustomOpCache {
879 entries: HashMap<CacheKey, CacheEntry>,
880 max_size: usize,
881 enabled: bool,
882 }
883
884 impl CustomOpCache {
885 fn new() -> Self {
886 Self {
887 entries: HashMap::new(),
888 max_size: 1000,
889 enabled: true,
890 }
891 }
892
893 pub fn get(&mut self, key: &CacheKey) -> Option<Vec<TensorRef>> {
894 if !self.enabled {
895 return None;
896 }
897
898 if let Some(entry) = self.entries.get_mut(key) {
899 entry.hit_count += 1;
900 Some(entry.result.clone())
901 } else {
902 None
903 }
904 }
905
906 pub fn insert(&mut self, key: CacheKey, result: Vec<TensorRef>) {
907 if !self.enabled {
908 return;
909 }
910
911 if self.entries.len() >= self.max_size {
913 self.evict_oldest();
914 }
915
916 let entry = CacheEntry {
917 result,
918 timestamp: std::time::SystemTime::now(),
919 hit_count: 0,
920 };
921
922 self.entries.insert(key, entry);
923 }
924
925 fn evict_oldest(&mut self) {
926 if let Some((oldest_key, _)) =
927 self.entries.iter().min_by_key(|(_, entry)| entry.timestamp)
928 {
929 let oldest_key = oldest_key.clone();
930 self.entries.remove(&oldest_key);
931 }
932 }
933
934 pub fn clear(&mut self) {
935 self.entries.clear();
936 }
937
938 pub fn set_max_size(&mut self, size: usize) {
939 self.max_size = size;
940 while self.entries.len() > self.max_size {
941 self.evict_oldest();
942 }
943 }
944
945 pub fn enable(&mut self, enabled: bool) {
946 self.enabled = enabled;
947 }
948
949 pub fn stats(&self) -> (usize, usize) {
950 (self.entries.len(), self.max_size)
951 }
952 }
953
954 fn hash_tensor(tensor: &TensorRef) -> u64 {
956 let mut hasher = std::collections::hash_map::DefaultHasher::new();
957
958 tensor.data.len().hash(&mut hasher);
960 for (i, &val) in tensor.data.iter().enumerate() {
961 if i >= 16 {
962 break;
963 } val.to_bits().hash(&mut hasher);
965 }
966
967 hasher.finish()
968 }
969
970 fn create_cache_key(op_name: &str, inputs: &[TensorRef]) -> CacheKey {
972 let input_hashes = inputs.iter().map(hash_tensor).collect();
973 let input_shapes = inputs.iter().map(|t| vec![t.data.len()]).collect(); CacheKey {
976 op_name: op_name.to_string(),
977 input_hashes,
978 input_shapes,
979 }
980 }
981
982 pub fn execute_with_caching(op_name: &str, inputs: &[TensorRef]) -> JitResult<Vec<TensorRef>> {
984 let cache_key = create_cache_key(op_name, inputs);
985
986 if let Ok(mut cache) = CACHE.write() {
988 if let Some(cached_result) = cache.get(&cache_key) {
989 if let Ok(mut profiler) = profiling::PROFILER.lock() {
990 profiler.record_cache_hit(op_name);
991 }
992 return Ok(cached_result);
993 }
994 }
995
996 let result = jit_integration::execute_custom_op(op_name, inputs)?;
998
999 if let Ok(mut cache) = CACHE.write() {
1001 cache.insert(cache_key, result.clone());
1002 if let Ok(mut profiler) = profiling::PROFILER.lock() {
1003 profiler.record_cache_miss(op_name);
1004 }
1005 }
1006
1007 Ok(result)
1008 }
1009
1010 pub fn clear_cache() {
1012 if let Ok(mut cache) = CACHE.write() {
1013 cache.clear();
1014 }
1015 }
1016
1017 pub fn set_cache_size(size: usize) {
1019 if let Ok(mut cache) = CACHE.write() {
1020 cache.set_max_size(size);
1021 }
1022 }
1023
1024 pub fn enable_caching(enabled: bool) {
1026 if let Ok(mut cache) = CACHE.write() {
1027 cache.enable(enabled);
1028 }
1029 }
1030
1031 pub fn get_cache_stats() -> (usize, usize) {
1033 CACHE.read().map(|c| c.stats()).unwrap_or((0, 0))
1034 }
1035}
1036
1037pub mod advanced_execution {
1039 use super::*;
1040 use crate::fusion::FusionStrategy;
1041
1042 #[derive(Debug, Clone)]
1044 pub struct ExecutionContext {
1045 pub backend: String,
1046 pub device_id: Option<usize>,
1047 pub use_cache: bool,
1048 pub use_profiling: bool,
1049 pub fusion_strategy: Option<FusionStrategy>,
1050 pub optimization_level: u8,
1051 }
1052
1053 impl Default for ExecutionContext {
1054 fn default() -> Self {
1055 Self {
1056 backend: "cpu".to_string(),
1057 device_id: None,
1058 use_cache: true,
1059 use_profiling: true,
1060 fusion_strategy: Some(FusionStrategy::Default),
1061 optimization_level: 2,
1062 }
1063 }
1064 }
1065
1066 pub struct CustomOpExecutor {
1068 context: ExecutionContext,
1069 }
1070
1071 impl CustomOpExecutor {
1072 pub fn new(context: ExecutionContext) -> Self {
1074 Self { context }
1075 }
1076
1077 pub fn execute(&self, op_name: &str, inputs: &[TensorRef]) -> JitResult<Vec<TensorRef>> {
1079 let op = get_custom_op(op_name).ok_or_else(|| {
1081 JitError::RuntimeError(format!("Custom operator {} not found", op_name))
1082 })?;
1083
1084 if let Some(validator) = &op.type_validator {
1086 validator(inputs)?;
1087 }
1088
1089 let result = if self.context.use_cache {
1091 caching::execute_with_caching(op_name, inputs)?
1092 } else if self.context.use_profiling {
1093 profiling::execute_with_profiling(op_name, inputs)?
1094 } else {
1095 jit_integration::execute_custom_op(op_name, inputs)?
1096 };
1097
1098 if let Some(optimizer) = &op.memory_optimizer {
1100 let _layout = optimizer(inputs)?;
1101 }
1103
1104 Ok(result)
1105 }
1106
1107 pub fn execute_with_backend(
1109 &self,
1110 op_name: &str,
1111 inputs: &[TensorRef],
1112 ) -> JitResult<Vec<TensorRef>> {
1113 let op = get_custom_op(op_name).ok_or_else(|| {
1114 JitError::RuntimeError(format!("Custom operator {} not found", op_name))
1115 })?;
1116
1117 if let Some(backend_impl) = op.backend_impls.get(&self.context.backend) {
1119 return backend_impl(inputs);
1120 }
1121
1122 self.execute(op_name, inputs)
1124 }
1125
1126 pub fn can_fuse_ops(&self, op_names: &[String]) -> bool {
1128 for (i, op_name) in op_names.iter().enumerate() {
1129 if let Some(op) = get_custom_op(op_name) {
1130 if op.fusion_info.fusion_barrier {
1132 return false;
1133 }
1134
1135 if i + 1 < op_names.len() {
1137 let next_op_name = &op_names[i + 1];
1138 if op.fusion_info.non_fusable_with.contains(next_op_name) {
1139 return false;
1140 }
1141 }
1142 }
1143 }
1144 true
1145 }
1146
1147 pub fn execute_fused(
1149 &self,
1150 op_names: &[String],
1151 inputs: &[TensorRef],
1152 ) -> JitResult<Vec<TensorRef>> {
1153 if !self.can_fuse_ops(op_names) {
1154 return Err(JitError::FusionError(
1155 "Operations cannot be fused".to_string(),
1156 ));
1157 }
1158
1159 let mut current_inputs = inputs.to_vec();
1161
1162 for op_name in op_names {
1163 current_inputs = self.execute(op_name, ¤t_inputs)?;
1164 }
1165
1166 Ok(current_inputs)
1167 }
1168
1169 pub fn set_context(&mut self, context: ExecutionContext) {
1171 self.context = context;
1172 }
1173
1174 pub fn context(&self) -> &ExecutionContext {
1176 &self.context
1177 }
1178 }
1179
1180 pub fn create_executor() -> CustomOpExecutor {
1182 CustomOpExecutor::new(ExecutionContext::default())
1183 }
1184
1185 pub fn create_executor_with_backend(backend: &str) -> CustomOpExecutor {
1187 let mut context = ExecutionContext::default();
1188 context.backend = backend.to_string();
1189 CustomOpExecutor::new(context)
1190 }
1191}
1192
1193#[cfg(test)]
1194mod tests {
1195 use super::*;
1196
1197 #[test]
1198 fn test_custom_op_builder() {
1199 let result = CustomOpBuilder::new("test_op")
1200 .namespace("test")
1201 .forward(|inputs| Ok(inputs.to_vec()))
1202 .input("x", Some(DType::F32))
1203 .output("y", Some(DType::F32))
1204 .build();
1205
1206 assert!(result.is_ok());
1207
1208 let ops = list_ops_in_namespace("test");
1210 assert!(ops.contains(&"test_op".to_string()));
1211 }
1212
1213 #[test]
1214 fn test_example_ops() {
1215 let result = examples::register_example_ops();
1216 assert!(result.is_ok());
1217
1218 let torsh_ops = list_ops_in_namespace("torsh");
1220 assert!(torsh_ops.contains(&"relu6".to_string()));
1221 assert!(torsh_ops.contains(&"gelu_approx".to_string()));
1222 }
1223
1224 #[test]
1225 fn test_custom_op_execution() {
1226 let registration_result = CustomOpBuilder::new("double")
1228 .namespace("test")
1229 .input("input", None)
1230 .forward(|inputs| {
1231 let mut output = inputs[0].clone();
1232 for val in &mut output.data {
1233 *val *= 2.0;
1234 }
1235 Ok(vec![output])
1236 })
1237 .build();
1238
1239 assert!(
1240 registration_result.is_ok(),
1241 "Failed to register custom op: {:?}",
1242 registration_result.err()
1243 );
1244
1245 let input = TensorRef {
1247 data: vec![1.0, 2.0, 3.0],
1248 };
1249 let result = jit_integration::execute_custom_op("test::double", &[input]);
1250
1251 if result.is_err() {
1252 println!("Error executing custom op: {:?}", result.as_ref().err());
1253 println!(
1254 "Available ops in 'test' namespace: {:?}",
1255 list_ops_in_namespace("test")
1256 );
1257 }
1258
1259 assert!(
1260 result.is_ok(),
1261 "Custom op execution failed: {:?}",
1262 result.as_ref().err()
1263 );
1264 let outputs = result.unwrap();
1265 assert_eq!(outputs[0].data, vec![2.0, 4.0, 6.0]);
1266 }
1267}