1use crate::CompileOptions;
23use rlx_ir::Graph;
24use rlx_ir::hir::HirModule;
25use rlx_ir::lir::LirModule;
26use std::collections::HashMap;
27use std::sync::Arc;
28
29use crate::cpu_low_precision;
30
31#[allow(dead_code)]
38pub(crate) fn widen_bytes_to_f32(data: &[u8], dtype: rlx_ir::DType) -> Vec<f32> {
39 use rlx_ir::DType;
40 match dtype {
41 DType::F32 => {
42 let n = data.len() / 4;
43 let s = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const f32, n) };
44 s.to_vec()
45 }
46 DType::F16 => {
47 let n = data.len() / 2;
48 let s = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const half::f16, n) };
49 s.iter().map(|h| h.to_f32()).collect()
50 }
51 DType::BF16 => {
52 let n = data.len() / 2;
53 let s = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const half::bf16, n) };
54 s.iter().map(|h| h.to_f32()).collect()
55 }
56 other => panic!(
57 "widen_bytes_to_f32: dtype {other:?} unsupported on f32-arena backends \
58 (only F32/F16/BF16 are accepted on the host I/O surface)"
59 ),
60 }
61}
62
63#[allow(dead_code)]
68pub(crate) fn narrow_f32_to_bytes(v: &[f32], dt: rlx_ir::DType) -> Vec<u8> {
69 use rlx_ir::DType;
70 match dt {
71 DType::F32 => {
72 let mut bytes = Vec::with_capacity(v.len() * 4);
73 for &x in v {
74 bytes.extend_from_slice(&x.to_le_bytes());
75 }
76 bytes
77 }
78 DType::F16 => {
79 let mut bytes = Vec::with_capacity(v.len() * 2);
80 for &x in v {
81 bytes.extend_from_slice(&half::f16::from_f32(x).to_le_bytes());
82 }
83 bytes
84 }
85 DType::BF16 => {
86 let mut bytes = Vec::with_capacity(v.len() * 2);
87 for &x in v {
88 bytes.extend_from_slice(&half::bf16::from_f32(x).to_le_bytes());
89 }
90 bytes
91 }
92 DType::F64 => {
93 let mut bytes = Vec::with_capacity(v.len() * 8);
94 for &x in v {
95 bytes.extend_from_slice(&(x as f64).to_le_bytes());
96 }
97 bytes
98 }
99 DType::I8 => v.iter().map(|&x| x as i8 as u8).collect(),
100 DType::U8 => v.iter().map(|&x| x as u8).collect(),
101 DType::I16 => {
102 let mut bytes = Vec::with_capacity(v.len() * 2);
103 for &x in v {
104 bytes.extend_from_slice(&(x as i16).to_le_bytes());
105 }
106 bytes
107 }
108 DType::I32 => {
109 let mut bytes = Vec::with_capacity(v.len() * 4);
110 for &x in v {
111 bytes.extend_from_slice(&(x as i32).to_le_bytes());
112 }
113 bytes
114 }
115 DType::U32 => {
116 let mut bytes = Vec::with_capacity(v.len() * 4);
117 for &x in v {
118 bytes.extend_from_slice(&(x as u32).to_le_bytes());
119 }
120 bytes
121 }
122 DType::I64 => {
123 let mut bytes = Vec::with_capacity(v.len() * 8);
124 for &x in v {
125 bytes.extend_from_slice(&(x as i64).to_le_bytes());
126 }
127 bytes
128 }
129 DType::Bool => v
130 .iter()
131 .map(|&x| if x != 0.0 { 1u8 } else { 0u8 })
132 .collect(),
133 DType::C64 => {
134 let mut bytes = Vec::with_capacity(v.len() * 8);
138 for &x in v {
139 bytes.extend_from_slice(&x.to_le_bytes());
140 bytes.extend_from_slice(&0.0_f32.to_le_bytes());
141 }
142 bytes
143 }
144 }
145}
146
147pub trait ExecutableGraph: Send {
149 fn set_param(&mut self, name: &str, data: &[f32]);
151
152 fn finalize_params(&mut self) {}
155
156 fn clone_box(&self) -> Box<dyn ExecutableGraph> {
163 panic!("clone_box not implemented for this backend");
164 }
165
166 fn run(&mut self, inputs: &[(&str, &[f32])]) -> Vec<Vec<f32>>;
168
169 fn run_read_outputs(
172 &mut self,
173 inputs: &[(&str, &[f32])],
174 read_indices: Option<&[usize]>,
175 ) -> Vec<Vec<f32>> {
176 match read_indices {
177 None => self.run(inputs),
178 Some(ix) => {
179 let all = self.run(inputs);
182 ix.iter().filter_map(|&i| all.get(i).cloned()).collect()
183 }
184 }
185 }
186
187 fn run_raw(&mut self, inputs: &[(&str, &[f32])]) -> Vec<(*const f32, usize)> {
189 let vecs = self.run(inputs);
190 vecs.iter().map(|v| (v.as_ptr(), v.len())).collect()
191 }
192
193 fn run_slots(&mut self, _inputs: &[&[f32]]) -> &[(usize, usize)] {
196 &[] }
198
199 fn arena_ptr(&self) -> *const u8 {
201 std::ptr::null()
202 }
203
204 fn set_active_extent(&mut self, extent: Option<(usize, usize)>) {
221 let _ = extent;
222 }
223
224 fn set_rng(&mut self, rng: rlx_ir::RngOptions) {
226 let _ = rng;
227 }
228
229 fn rng(&self) -> rlx_ir::RngOptions {
231 rlx_ir::RngOptions::default()
232 }
233
234 fn set_moe_resident_experts(&mut self, _mask: &[bool]) {}
236
237 fn set_moe_resident_experts_per_layer(&mut self, _masks: &[&[bool]]) {}
239
240 fn enable_moe_topk_capture(&mut self, _num_experts: usize) -> bool {
242 false
243 }
244
245 fn take_moe_topk_capture(&mut self) -> Option<Vec<Vec<u32>>> {
247 None
248 }
249
250 fn take_moe_residency_stats(&mut self) -> Option<crate::MoeResidencyStats> {
252 None
253 }
254
255 fn bind_handle(&mut self, _name: &str, _data: &[f32]) -> bool {
259 false
260 }
261
262 fn read_handle(&self, _name: &str) -> Option<Vec<f32>> {
264 None
265 }
266
267 fn bind_gpu_handle(&mut self, _name: &str, _data: &[f32]) -> bool {
269 false
270 }
271
272 fn has_gpu_handle(&self, _name: &str) -> bool {
273 false
274 }
275
276 fn set_gpu_handle_feed(&mut self, _handle_name: &str, _output_index: usize) -> bool {
277 false
278 }
279
280 fn read_gpu_handle(&self, _name: &str) -> Option<Vec<f32>> {
281 None
282 }
283
284 fn read_gpu_handle_row(&self, _name: &str, _row: usize, _row_inner: usize) -> Option<Vec<f32>> {
286 None
287 }
288
289 fn register_kv_row_feed(&mut self, _handle_name: &str, _output_index: usize) -> bool {
293 false
294 }
295
296 fn feed_kv_row(&mut self, _src_row: usize, _dst_row: usize, _row_elems: usize) -> bool {
301 false
302 }
303
304 fn prepare_resident_gpu_handle(&mut self, _name: &str) -> bool {
306 false
307 }
308
309 fn stage_bound_gpu_handles_to_arena(&mut self) {}
311
312 fn seed_resident_kv_prefix_from(
315 &mut self,
316 _src: &dyn ExecutableGraph,
317 _prefix_tokens: usize,
318 _outgoing_upper: usize,
319 _kv_dim: usize,
320 _n_layers: usize,
321 ) -> bool {
322 false
323 }
324
325 fn copy_resident_kv_rows_from(
327 &mut self,
328 _src: &dyn ExecutableGraph,
329 _from_row: usize,
330 _to_row: usize,
331 _outgoing_upper: usize,
332 _kv_dim: usize,
333 _n_layers: usize,
334 ) -> bool {
335 false
336 }
337
338 fn copy_params_from(&mut self, src: &dyn ExecutableGraph) -> bool {
341 let _ = src;
342 false
343 }
344
345 fn executable_as_any(&self) -> Option<&dyn std::any::Any> {
347 None
348 }
349
350 fn executable_as_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
352 None
353 }
354
355 #[cfg(feature = "cuda")]
357 fn cuda_executable_for_kv_seed(&mut self) -> Option<&mut rlx_cuda::backend::CudaExecutable> {
358 let _ = self;
359 None
360 }
361
362 #[cfg(feature = "cuda")]
364 fn cuda_executable_for_kv_seed_ref(&self) -> Option<&rlx_cuda::backend::CudaExecutable> {
365 None
366 }
367
368 fn read_output_row(&self, _out_idx: usize, _row: usize, _row_inner: usize) -> Option<Vec<f32>> {
371 None
372 }
373
374 fn run_feed_gpu_handle(
376 &mut self,
377 inputs: &[(&str, &[f32])],
378 _handle_name: &str,
379 _output_index: usize,
380 ) -> Option<Vec<f32>> {
381 let _ = inputs;
382 None
383 }
384
385 fn commit_no_wait(&mut self, inputs: &[(&str, &[f32])]) {
400 let _ = self.run(inputs);
401 }
402
403 fn sync_pending(&mut self) {}
406
407 fn run_pipelined(&mut self, input_sets: &[Vec<(&str, &[f32])>]) -> Vec<Vec<Vec<f32>>> {
416 input_sets.iter().map(|inputs| self.run(inputs)).collect()
417 }
418
419 fn set_param_typed(&mut self, name: &str, data: &[u8], dtype: rlx_ir::DType) {
432 if dtype != rlx_ir::DType::F32 {
433 panic!(
434 "backend's default set_param_typed only handles F32; \
435 got {dtype:?}. Override on the backend for typed support."
436 );
437 }
438 if !data.len().is_multiple_of(4) {
439 panic!(
440 "set_param_typed F32: data length {} not a multiple of 4",
441 data.len()
442 );
443 }
444 let n = data.len() / 4;
449 let f32_slice = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const f32, n) };
450 self.set_param(name, f32_slice);
451 }
452
453 fn run_typed(
457 &mut self,
458 inputs: &[(&str, &[u8], rlx_ir::DType)],
459 ) -> Vec<(Vec<u8>, rlx_ir::DType)> {
460 let mut owned: Vec<(String, Vec<f32>)> = Vec::with_capacity(inputs.len());
463 for (name, data, dt) in inputs {
464 if *dt != rlx_ir::DType::F32 {
465 panic!(
466 "backend's default run_typed only handles F32 inputs; \
467 got {dt:?} for input '{name}'"
468 );
469 }
470 if data.len() % 4 != 0 {
471 panic!(
472 "run_typed F32 input '{name}': len {} not multiple of 4",
473 data.len()
474 );
475 }
476 let n = data.len() / 4;
477 let v: Vec<f32> =
478 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const f32, n) }.to_vec();
479 owned.push((name.to_string(), v));
480 }
481 let refs: Vec<(&str, &[f32])> = owned
482 .iter()
483 .map(|(n, d)| (n.as_str(), d.as_slice()))
484 .collect();
485 let outs = self.run(&refs);
486 outs.into_iter()
487 .map(|v| {
488 let bytes =
489 unsafe { std::slice::from_raw_parts(v.as_ptr() as *const u8, v.len() * 4) }
490 .to_vec();
491 (bytes, rlx_ir::DType::F32)
492 })
493 .collect()
494 }
495}
496
497pub trait Backend: Send + Sync {
507 fn compile(&self, graph: Graph, options: &CompileOptions) -> Box<dyn ExecutableGraph>;
509
510 fn compile_lir(&self, lir: LirModule, options: &CompileOptions) -> Box<dyn ExecutableGraph> {
514 self.compile(lir.into_graph(), options)
515 }
516
517 fn compile_hir(
519 &self,
520 hir: HirModule,
521 device: rlx_driver::Device,
522 options: &CompileOptions,
523 ) -> Result<Box<dyn ExecutableGraph>, rlx_ir::hir::LowerError> {
524 let result = crate::stages::compile_hir_stages(device, hir, options)?;
525 crate::stages::maybe_log_fusion(&result.fusion);
526 Ok(self.compile_lir(result.lir, options))
527 }
528
529 fn compile_module(
531 &self,
532 module: rlx_ir::GraphModule,
533 device: rlx_driver::Device,
534 options: &CompileOptions,
535 ) -> Result<Box<dyn ExecutableGraph>, rlx_ir::hir::LowerError> {
536 let result = crate::stages::compile_module_stages(device, module, options)?;
537 crate::stages::maybe_log_fusion(&result.fusion);
538 Ok(self.compile_lir(result.lir, options))
539 }
540
541 fn supported_ops(&self) -> &'static [rlx_ir::OpKind] {
548 &[]
549 }
550}
551
552#[allow(dead_code)]
555fn prepare_fused_graph(
556 graph: Graph,
557 options: &CompileOptions,
558 supported_ops: &[rlx_ir::OpKind],
559 backend_name: &str,
560) -> Graph {
561 let (mut graph, report) = rlx_opt::prepare_graph_for_backend_with_report(
562 graph,
563 backend_name,
564 supported_ops,
565 options.kernel_dispatch,
566 );
567 rlx_opt::maybe_log_dispatch_report(&report);
568 if !report.compile_ready {
569 panic!(
570 "{}\n{}",
571 rlx_opt::format_legalize_error(backend_name, &report.still_unsupported),
572 rlx_opt::format_dispatch_report(&report)
573 );
574 }
575 graph = crate::precompile::post_fusion_cleanup(graph, options);
576 if let Some(p) = options.policy.clone() {
577 use rlx_opt::pass::Pass as _;
578 graph = rlx_opt::AutoMixedPrecision::new(p).run(graph);
579 }
580 graph
581}
582
583#[allow(dead_code)]
584fn declared_output_dtypes(
585 manifest: &cpu_low_precision::IoDtypeManifest,
586 exec_dtypes: Vec<rlx_ir::DType>,
587) -> Vec<rlx_ir::DType> {
588 exec_dtypes
589 .into_iter()
590 .enumerate()
591 .map(|(i, exec)| manifest.output_dtype(i, exec))
592 .collect()
593}
594
595pub fn compile(backend: &dyn Backend, graph: Graph) -> Box<dyn ExecutableGraph> {
603 backend.compile(graph, &CompileOptions::default())
604}
605
606pub fn compile_hir(
608 backend: &dyn Backend,
609 hir: HirModule,
610 device: rlx_driver::Device,
611 options: &CompileOptions,
612) -> Result<Box<dyn ExecutableGraph>, rlx_ir::hir::LowerError> {
613 backend.compile_hir(hir, device, options)
614}
615
616pub fn compile_module(
618 backend: &dyn Backend,
619 module: rlx_ir::GraphModule,
620 device: rlx_driver::Device,
621 options: &CompileOptions,
622) -> Result<Box<dyn ExecutableGraph>, rlx_ir::hir::LowerError> {
623 backend.compile_module(module, device, options)
624}
625
626pub fn compile_with_precision(
628 backend: &dyn Backend,
629 graph: Graph,
630 precision: crate::Precision,
631) -> Box<dyn ExecutableGraph> {
632 backend.compile(graph, &CompileOptions::new().precision(precision))
633}
634
635fn _legacy_apply_policy(graph: Graph, policy: Option<rlx_opt::PrecisionPolicy>) -> Graph {
640 use rlx_opt::pass::Pass as _;
641 match policy {
642 Some(p) => rlx_opt::AutoMixedPrecision::new(p).run(graph),
643 None => graph,
644 }
645}
646
647#[cfg(feature = "cpu")]
650pub mod cpu_backend {
651 use super::*;
652 use rlx_cpu::{arena::Arena, thunk};
653 use rlx_ir::{DType, NodeId, Op};
654 use rlx_opt::memory::{self, MemoryPlan};
655 use rlx_driver::arena::{read_typed_to_f32, write_typed_from_f32};
658
659 pub struct CpuBackend;
660
661 const CPU_SUPPORTED_OPS: &[rlx_ir::OpKind] = {
668 use rlx_ir::OpKind::*;
669 &[
670 Input,
671 Param,
672 Constant,
673 Activation,
674 Cast,
675 StopGradient,
676 Binary,
677 Compare,
678 Where,
679 Fma,
680 ElementwiseRegion,
681 MatMul,
682 DotGeneral,
683 DenseSolve,
684 BatchedDenseSolve,
685 Scan,
686 ScanBackward,
687 ScanBackwardXs,
688 LayerNorm,
689 LayerNorm2d,
690 GroupNorm,
691 BatchNormInference,
692 RmsNorm,
693 ResizeNearest2x,
694 AxialRope2d,
695 Attention,
696 Rope,
697 Reshape,
698 Transpose,
699 Narrow,
700 Concat,
701 Expand,
702 Gather,
703 Reverse,
704 Reduce,
705 Softmax,
706 Cumsum,
707 ArgMax,
708 ArgMin,
709 TopK,
710 Sample,
711 RngNormal,
712 RngUniform,
713 Conv,
714 Im2Col,
715 ConvTranspose2d,
716 Conv3d,
717 ConvTranspose3d,
718 Pool,
719 GroupedMatMul,
720 DequantGroupedMatMul,
721 DequantMoEWeights,
722 ScatterAdd,
723 LoraMatMul,
724 DequantMatMul,
725 ScaledMatMul,
726 ScaledQuantize,
727 ScaledQuantScale,
728 ScaledDequantize,
729 SelectiveScan,
730 GatedDeltaNet,
731 Lstm,
732 Gru,
733 Rnn,
734 Mamba2,
735 FusedSwiGLU,
736 FusedMatMulBiasAct,
737 FusedResidualLN,
738 FusedResidualRmsNorm,
739 FusedAttentionBlock,
740 ReluBackward,
745 ActivationBackward,
746 FakeQuantize,
747 FakeQuantizeBackward,
748 FakeQuantizeLSQ,
750 FakeQuantizeLSQBackwardX,
751 FakeQuantizeLSQBackwardScale,
752 MaxPool2dBackward,
753 Conv2dBackwardInput,
754 Conv2dBackwardWeight,
755 SoftmaxCrossEntropy,
756 SoftmaxCrossEntropyWithLogits,
757 SoftmaxCrossEntropyBackward,
758 AttentionBackward,
759 LayerNormBackwardInput,
760 LayerNormBackwardGamma,
761 BatchNormInferenceBackwardInput,
762 BatchNormInferenceBackwardGamma,
763 BatchNormInferenceBackwardBeta,
764 GroupNormBackwardInput,
766 GroupNormBackwardGamma,
767 GroupNormBackwardBeta,
768 RmsNormBackwardInput,
769 RmsNormBackwardGamma,
770 RmsNormBackwardBeta,
771 RopeBackward,
772 CumsumBackward,
773 GatherBackward,
774 GaussianSplatRender,
776 GaussianSplatRenderBackward,
777 GaussianSplatPrepare,
778 GaussianSplatRasterize,
779 Custom,
783 CustomFn,
787 Fft,
791 FftButterflyStage,
792 LogMel,
793 LogMelBackward,
794 WelchPeaks,
795 ComplexNormSq,
800 ComplexNormSqBackward,
801 Conjugate,
802 ]
803 };
804
805 impl Backend for CpuBackend {
806 fn supported_ops(&self) -> &'static [rlx_ir::OpKind] {
807 CPU_SUPPORTED_OPS
808 }
809
810 fn compile(&self, graph: Graph, options: &CompileOptions) -> Box<dyn ExecutableGraph> {
811 use rlx_opt::pass::Pass as _;
812 static ONNX_KERNELS: std::sync::Once = std::sync::Once::new();
813 ONNX_KERNELS.call_once(rlx_cpu::onnx_ref::register_onnx_reference_kernels);
814 let graph = rlx_opt::LowerControlFlow.run(graph);
820 if let Err(errors) = rlx_opt::legalize_for_backend(&graph, CPU_SUPPORTED_OPS) {
824 panic!("{}", rlx_opt::format_legalize_error("cpu", &errors));
825 }
826 let policy = options.policy.clone();
827 let _precision = options.precision;
828 let cfg = rlx_cpu::config::RuntimeConfig::global();
829
830 let graph = crate::precompile::precompile_cleanup(graph, options);
831
832 let mut compile_opts = options.clone();
834 compile_opts.arena_alignment = cfg.arena_alignment;
835 let compile_result = crate::stages::compile_graph_stages_for_backend(
836 rlx_driver::Device::Cpu,
837 graph,
838 &compile_opts,
839 CPU_SUPPORTED_OPS,
840 );
841 crate::stages::maybe_log_fusion(&compile_result.fusion);
842 let fused = compile_result.lir.into_graph();
843
844 let fused = match policy {
847 Some(p) => rlx_opt::AutoMixedPrecision::new(p).run(fused),
848 None => fused,
849 };
850
851 let io_manifest = cpu_low_precision::IoDtypeManifest::from_graph(&fused);
852 let exec_graph = if cpu_low_precision::needs_f32_exec(&fused) {
853 cpu_low_precision::promote_to_f32(fused)
854 } else {
855 fused
856 };
857
858 let plan = memory::plan_memory_aligned(&exec_graph, cfg.arena_alignment);
860 if cfg.verbose >= 1 {
861 eprintln!(
862 "[rlx] arena: {} bytes, {} buffers, alignment: {}",
863 plan.arena_size,
864 plan.assignments.len(),
865 cfg.arena_alignment
866 );
867 }
868 Box::new(build_cpu_executable(
869 exec_graph,
870 plan,
871 io_manifest,
872 options.rng,
873 ))
874 }
875
876 fn compile_lir(
877 &self,
878 lir: LirModule,
879 options: &CompileOptions,
880 ) -> Box<dyn ExecutableGraph> {
881 let alignment = lir.buffers.alignment.max(options.arena_alignment);
882 let mut graph = lir.into_graph();
883 {
884 use rlx_opt::pass::Pass as _;
885 graph = rlx_opt::LegalizeBroadcast.run(graph);
886 }
887 if let Some(p) = options.policy.clone() {
888 use rlx_opt::pass::Pass;
889 graph = rlx_opt::AutoMixedPrecision::new(p).run(graph);
890 }
891 let io_manifest = cpu_low_precision::IoDtypeManifest::from_graph(&graph);
892 let promote = cpu_low_precision::needs_f32_exec(&graph);
893 let exec_graph = if promote {
894 cpu_low_precision::promote_to_f32(graph)
895 } else {
896 graph
897 };
898 let plan = memory::plan_memory_aligned(&exec_graph, alignment);
901 let cfg = rlx_cpu::config::RuntimeConfig::global();
902 if cfg.verbose >= 1 {
903 eprintln!(
904 "[rlx] compile_lir: arena {} bytes ({} buffers, alignment {})",
905 plan.arena_size,
906 plan.assignments.len(),
907 alignment,
908 );
909 }
910 Box::new(build_cpu_executable(
911 exec_graph,
912 plan,
913 io_manifest,
914 options.rng,
915 ))
916 }
917 }
918
919 fn build_cpu_executable(
920 graph: Graph,
921 plan: MemoryPlan,
922 io_manifest: cpu_low_precision::IoDtypeManifest,
923 rng: rlx_ir::RngOptions,
924 ) -> CpuExecutable {
925 let mut arena = Arena::from_plan(plan);
926 let mut input_ids = HashMap::new();
927 let mut param_ids = HashMap::new();
928 let mut node_dtypes: HashMap<NodeId, DType> = HashMap::new();
929 for node in graph.nodes() {
930 node_dtypes.insert(node.id, node.shape.dtype());
931 match &node.op {
932 Op::Input { name } => {
933 input_ids.insert(name.clone(), node.id);
934 }
935 Op::Param { name } => {
936 param_ids.insert(name.clone(), node.id);
937 }
938 _ => {}
939 }
940 }
941
942 let schedule = thunk::compile_thunks_with_rng(&graph, &arena, rng);
943
944 let mut input_slots = Vec::new();
945 for node in graph.nodes() {
946 if let Op::Input { name } = &node.op {
947 let off = arena.byte_offset(node.id);
948 let len = node.shape.num_elements().unwrap_or(0);
949 input_slots.push((name.clone(), off, len, node.shape.dtype()));
950 }
951 }
952
953 let output_slots: Vec<(usize, usize)> = graph
954 .outputs
955 .iter()
956 .map(|&id| {
957 let off = arena.byte_offset(id);
958 let len = graph.node(id).shape.num_elements().unwrap_or(0);
959 (off, len)
960 })
961 .collect();
962
963 for node in graph.nodes() {
964 if let Op::Constant { data } = &node.op
965 && arena.has_buffer(node.id)
966 && !data.is_empty()
967 {
968 match node.shape.dtype() {
969 DType::F64
976 | DType::F16
977 | DType::BF16
978 | DType::I64
979 | DType::I32
980 | DType::U32 => {
981 let off = arena.byte_offset(node.id);
982 let buf = arena.raw_buf_mut();
983 let n = buf.len().saturating_sub(off).min(data.len());
984 buf[off..off + n].copy_from_slice(&data[..n]);
985 }
986 _ => {
987 let buf = arena.slice_mut(node.id);
988 let n_floats = data.len() / 4;
989 let n = buf.len().min(n_floats);
990 for i in 0..n {
991 let bytes = [
992 data[i * 4],
993 data[i * 4 + 1],
994 data[i * 4 + 2],
995 data[i * 4 + 3],
996 ];
997 buf[i] = f32::from_le_bytes(bytes);
998 }
999 }
1000 }
1001 }
1002 }
1003
1004 CpuExecutable {
1005 graph,
1006 arena,
1007 input_ids,
1008 param_ids,
1009 node_dtypes,
1010 io_manifest,
1011 schedule,
1012 input_slots,
1013 output_slots,
1014 handles: HashMap::new(),
1015 active_extent: None,
1016 moe_resident: None,
1017 moe_resident_layers: None,
1018 moe_topk_capture: None,
1019 baseline_written: false,
1020 }
1021 }
1022
1023 #[derive(Clone)]
1024 struct CpuExecutable {
1025 graph: Graph,
1026 arena: Arena,
1027 input_ids: HashMap<String, NodeId>,
1028 param_ids: HashMap<String, NodeId>,
1029 node_dtypes: HashMap<NodeId, DType>,
1032 io_manifest: cpu_low_precision::IoDtypeManifest,
1034 schedule: thunk::ThunkSchedule,
1035 input_slots: Vec<(String, usize, usize, DType)>,
1037 output_slots: Vec<(usize, usize)>,
1039 handles: HashMap<String, Vec<f32>>,
1044 active_extent: Option<(usize, usize)>,
1050 moe_resident: Option<std::sync::Arc<[bool]>>,
1051 moe_resident_layers: Option<std::sync::Arc<Vec<std::sync::Arc<[bool]>>>>,
1052 moe_topk_capture: Option<std::sync::Arc<rlx_cpu::moe_topk_capture::MoeTopkCapture>>,
1053 baseline_written: bool,
1059 }
1060
1061 unsafe impl Send for CpuExecutable {}
1062
1063 impl CpuExecutable {
1064 fn write_input(&mut self, id: NodeId, data: &[f32]) {
1066 let dtype = self.node_dtypes.get(&id).copied().unwrap_or(DType::F32);
1067 let off = self.arena.byte_offset(id);
1068 let buf = self.arena.raw_buf_mut();
1069 let elem_size = dtype.size_bytes();
1070 let max_elems = (buf.len() - off) / elem_size;
1071 unsafe {
1072 write_typed_from_f32(buf.as_mut_ptr().add(off), dtype, data, max_elems);
1073 }
1074 }
1075
1076 fn read_output(&self, id: NodeId) -> Vec<f32> {
1078 let dtype = self.node_dtypes.get(&id).copied().unwrap_or(DType::F32);
1079 let off = self.arena.byte_offset(id);
1080 let buf = self.arena.raw_buf();
1081 let n_elems = self.graph.node(id).shape.num_elements().unwrap_or(0);
1082 unsafe { read_typed_to_f32(buf.as_ptr().add(off), dtype, n_elems) }
1083 }
1084 }
1085
1086 impl ExecutableGraph for CpuExecutable {
1087 fn clone_box(&self) -> Box<dyn ExecutableGraph> {
1088 Box::new(self.clone())
1089 }
1090 fn set_param(&mut self, name: &str, data: &[f32]) {
1091 if let Some(&id) = self.param_ids.get(name)
1096 && self.arena.has_buffer(id)
1097 {
1098 let dtype = self.node_dtypes.get(&id).copied().unwrap_or(DType::F32);
1099 let off = self.arena.byte_offset(id);
1100 let buf = self.arena.raw_buf_mut();
1101 let elem_size = dtype.size_bytes();
1102 let max_elems = (buf.len() - off) / elem_size;
1103 unsafe {
1104 write_typed_from_f32(buf.as_mut_ptr().add(off), dtype, data, max_elems);
1105 }
1106 }
1107 }
1108
1109 fn run(&mut self, inputs: &[(&str, &[f32])]) -> Vec<Vec<f32>> {
1110 self.restore_arena_baseline();
1111 let handle_names: Vec<String> = self.handles.keys().cloned().collect();
1114 for name in &handle_names {
1115 if let Some(&id) = self.input_ids.get(name)
1116 && self.arena.has_buffer(id)
1117 {
1118 let data = self.handles.get(name).cloned().unwrap_or_default();
1119 self.write_input(id, &data);
1120 }
1121 }
1122 for &(name, data) in inputs {
1124 if let Some(&id) = self.input_ids.get(name)
1125 && self.arena.has_buffer(id)
1126 {
1127 self.write_input(id, data);
1128 }
1129 }
1130
1131 let active_used = if let Some((actual, upper)) = self.active_extent {
1136 thunk::execute_thunks_active(
1137 &self.schedule,
1138 self.arena.raw_buf_mut(),
1139 actual,
1140 upper,
1141 )
1142 } else {
1143 false
1144 };
1145 if !active_used {
1146 thunk::execute_thunks(&self.schedule, self.arena.raw_buf_mut());
1148 }
1149
1150 for (idx, &out_id) in self.graph.outputs.iter().enumerate() {
1154 let name = format!("out{idx}");
1155 if self.handles.contains_key(&name) {
1156 let v = self.read_output(out_id);
1157 self.handles.insert(name, v);
1158 }
1159 }
1160
1161 self.graph
1162 .outputs
1163 .iter()
1164 .map(|&out_id| self.read_output(out_id))
1165 .collect()
1166 }
1167
1168 fn run_raw(&mut self, inputs: &[(&str, &[f32])]) -> Vec<(*const f32, usize)> {
1169 self.restore_arena_baseline();
1170 for &(name, data) in inputs {
1172 if let Some(&id) = self.input_ids.get(name)
1173 && self.arena.has_buffer(id)
1174 {
1175 self.write_input(id, data);
1176 }
1177 }
1178 thunk::execute_thunks(&self.schedule, self.arena.raw_buf_mut());
1179 self.graph
1183 .outputs
1184 .iter()
1185 .map(|&out_id| {
1186 let (ptr, len) = self.arena.raw_ptr(out_id);
1187 (ptr as *const f32, len)
1188 })
1189 .collect()
1190 }
1191
1192 fn run_slots(&mut self, inputs: &[&[f32]]) -> &[(usize, usize)] {
1196 self.restore_arena_baseline();
1197 let buf = self.arena.raw_buf_mut();
1198 for (i, &data) in inputs.iter().enumerate() {
1199 if i < self.input_slots.len() {
1200 let (_, off, max_len, dtype) = &self.input_slots[i];
1201 unsafe {
1202 write_typed_from_f32(buf.as_mut_ptr().add(*off), *dtype, data, *max_len);
1203 }
1204 }
1205 }
1206 thunk::execute_thunks(&self.schedule, self.arena.raw_buf_mut());
1207 &self.output_slots
1208 }
1209
1210 fn arena_ptr(&self) -> *const u8 {
1211 self.arena.raw_buf_mut_ptr()
1212 }
1213
1214 fn bind_handle(&mut self, name: &str, data: &[f32]) -> bool {
1215 self.handles.insert(name.to_string(), data.to_vec());
1220 true
1221 }
1222
1223 fn read_handle(&self, name: &str) -> Option<Vec<f32>> {
1224 self.handles.get(name).cloned()
1225 }
1226
1227 fn set_active_extent(&mut self, extent: Option<(usize, usize)>) {
1228 self.active_extent = extent;
1229 }
1230
1231 fn set_rng(&mut self, rng: rlx_ir::RngOptions) {
1232 *self.schedule.rng.write().unwrap() = rng;
1233 }
1234
1235 fn rng(&self) -> rlx_ir::RngOptions {
1236 *self.schedule.rng.read().unwrap()
1237 }
1238
1239 fn set_moe_resident_experts(&mut self, mask: &[bool]) {
1240 self.moe_resident_layers = None;
1241 self.schedule.moe_resident_layers = None;
1242 self.moe_resident = Some(Arc::from(mask));
1243 self.schedule.moe_resident = self.moe_resident.clone();
1244 }
1245
1246 fn set_moe_resident_experts_per_layer(&mut self, masks: &[&[bool]]) {
1247 self.moe_resident = None;
1248 self.schedule.moe_resident = None;
1249 let layers: Vec<Arc<[bool]>> = masks.iter().map(|m| Arc::from(*m)).collect();
1250 let arc = Arc::new(layers);
1251 self.moe_resident_layers = Some(arc.clone());
1252 self.schedule.moe_resident_layers = Some(arc);
1253 }
1254
1255 fn enable_moe_topk_capture(&mut self, num_experts: usize) -> bool {
1256 let cap = rlx_cpu::moe_topk_capture::MoeTopkCapture::new(num_experts);
1257 self.moe_topk_capture = Some(cap.clone());
1258 self.schedule.moe_topk_capture = Some(cap);
1259 true
1260 }
1261
1262 fn take_moe_topk_capture(&mut self) -> Option<Vec<Vec<u32>>> {
1263 let cap = self.moe_topk_capture.as_ref()?;
1264 let layers = cap.take_layers();
1265 if layers.is_empty() {
1266 None
1267 } else {
1268 Some(layers)
1269 }
1270 }
1271
1272 fn take_moe_residency_stats(&mut self) -> Option<crate::MoeResidencyStats> {
1273 rlx_cpu::moe_residency::take_last_forward_stats()
1274 }
1275
1276 fn set_param_typed(&mut self, name: &str, data: &[u8], dtype: rlx_ir::DType) {
1282 if matches!(dtype, DType::F64 | DType::I64 | DType::I32 | DType::U32) {
1283 self.set_param_bytes(name, data, dtype);
1284 return;
1285 }
1286 if matches!(dtype, DType::U8 | DType::I8) {
1290 self.set_param_bytes(name, data, dtype);
1291 return;
1292 }
1293 if dtype == DType::F32 {
1294 let n = data.len() / 4;
1295 let s = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const f32, n) };
1296 self.set_param(name, s);
1297 } else {
1298 let f32_buf = super::widen_bytes_to_f32(data, dtype);
1299 self.set_param(name, &f32_buf);
1300 }
1301 }
1302
1303 fn run_typed(
1315 &mut self,
1316 inputs: &[(&str, &[u8], rlx_ir::DType)],
1317 ) -> Vec<(Vec<u8>, rlx_ir::DType)> {
1318 let all_f64 = !inputs.is_empty() && inputs.iter().all(|(_, _, dt)| *dt == DType::F64);
1323
1324 if all_f64 {
1325 for (name, data, _) in inputs {
1326 if let Some(&id) = self.input_ids.get(*name) {
1327 if !self.arena.has_buffer(id) {
1328 continue;
1329 }
1330 let off = self.arena.byte_offset(id);
1331 let buf = self.arena.raw_buf_mut();
1332 let n = data.len();
1333 debug_assert!(
1334 off + n <= buf.len(),
1335 "run_typed: input '{name}' overflows arena slot"
1336 );
1337 buf[off..off + n].copy_from_slice(data);
1338 }
1339 }
1340 thunk::execute_thunks(&self.schedule, self.arena.raw_buf_mut());
1341 } else {
1342 let mut f32_owned: Vec<(String, Vec<f32>)> = Vec::new();
1347 for (name, data, dt) in inputs {
1348 let direct = matches!(
1349 *dt,
1350 DType::F64 | DType::I32 | DType::I64 | DType::U32 | DType::C64
1351 );
1352 if direct {
1353 if let Some(&id) = self.input_ids.get(*name) {
1354 if !self.arena.has_buffer(id) {
1355 continue;
1356 }
1357 let off = self.arena.byte_offset(id);
1358 let buf = self.arena.raw_buf_mut();
1359 buf[off..off + data.len()].copy_from_slice(data);
1360 }
1361 } else {
1362 let v = super::widen_bytes_to_f32(data, *dt);
1363 f32_owned.push((name.to_string(), v));
1364 }
1365 }
1366 for (name, data) in &f32_owned {
1367 if let Some(&id) = self.input_ids.get(name.as_str()) {
1368 if self.arena.has_buffer(id) {
1369 self.write_input(id, data);
1370 }
1371 }
1372 }
1373 let active_used = if let Some((actual, upper)) = self.active_extent {
1374 thunk::execute_thunks_active(
1375 &self.schedule,
1376 self.arena.raw_buf_mut(),
1377 actual,
1378 upper,
1379 )
1380 } else {
1381 false
1382 };
1383 if !active_used {
1384 thunk::execute_thunks(&self.schedule, self.arena.raw_buf_mut());
1385 }
1386 }
1387
1388 self.graph
1390 .outputs
1391 .iter()
1392 .enumerate()
1393 .map(|(idx, &id)| {
1394 let exec_dtype = self.graph.node(id).shape.dtype();
1395 let declared = self.io_manifest.output_dtype(idx, exec_dtype);
1396 if matches!(
1397 exec_dtype,
1398 DType::F64
1399 | DType::F16
1400 | DType::BF16
1401 | DType::I32
1402 | DType::I64
1403 | DType::U32
1404 | DType::C64
1405 ) {
1406 let n_elems = self.graph.node(id).shape.num_elements().unwrap_or(0);
1407 let n_bytes = n_elems * exec_dtype.size_bytes();
1408 let off = self.arena.byte_offset(id);
1409 let bytes = self.arena.raw_buf()[off..off + n_bytes].to_vec();
1410 return (bytes, declared);
1411 }
1412 let f32_vals = self.read_output(id);
1413 if declared != exec_dtype {
1414 return (super::narrow_f32_to_bytes(&f32_vals, declared), declared);
1415 }
1416 let bytes = f32_vals.iter().flat_map(|v| v.to_le_bytes()).collect();
1417 (bytes, declared)
1418 })
1419 .collect()
1420 }
1421 }
1422
1423 impl CpuExecutable {
1424 fn restore_arena_baseline(&mut self) {
1434 let persistent: std::collections::HashSet<NodeId> = {
1436 let mut s: std::collections::HashSet<NodeId> =
1437 self.param_ids.values().copied().collect();
1438 for node in self.graph.nodes() {
1439 if matches!(node.op, Op::Constant { .. }) {
1440 s.insert(node.id);
1441 }
1442 }
1443 s
1444 };
1445
1446 if !self.baseline_written {
1449 let constants: Vec<(NodeId, DType, Vec<u8>)> = self
1450 .graph
1451 .nodes()
1452 .iter()
1453 .filter_map(|node| {
1454 if let Op::Constant { data } = &node.op
1455 && self.arena.has_buffer(node.id)
1456 && !data.is_empty()
1457 {
1458 Some((node.id, node.shape.dtype(), data.clone()))
1459 } else {
1460 None
1461 }
1462 })
1463 .collect();
1464 for (id, dtype, data) in constants {
1465 self.write_constant_to_arena(id, dtype, &data);
1466 }
1467 self.baseline_written = true;
1468 }
1469
1470 let mut keep: Vec<(usize, usize)> = self
1481 .graph
1482 .nodes()
1483 .iter()
1484 .filter_map(|node| {
1485 let id = node.id;
1486 if !persistent.contains(&id) || !self.arena.has_buffer(id) {
1487 return None;
1488 }
1489 let dtype = self.node_dtypes.get(&id).copied().unwrap_or(DType::F32);
1490 let nbytes = node.shape.num_elements().unwrap_or(0) * dtype.size_bytes();
1491 let off = self.arena.byte_offset(id);
1492 Some((off, off + nbytes))
1493 })
1494 .collect();
1495 keep.sort_unstable();
1496
1497 let buf = self.arena.raw_buf_mut();
1498 let len = buf.len();
1499 let mut cursor = 0usize;
1500 for (start, end) in keep {
1501 let start = start.min(len);
1502 if cursor < start {
1503 buf[cursor..start].fill(0);
1504 }
1505 cursor = cursor.max(end.min(len));
1506 }
1507 if cursor < len {
1508 buf[cursor..len].fill(0);
1509 }
1510 }
1511
1512 fn write_constant_to_arena(&mut self, id: NodeId, dtype: DType, data: &[u8]) {
1513 match dtype {
1514 DType::F64 | DType::F16 | DType::BF16 | DType::U8 | DType::I8 => {
1515 let off = self.arena.byte_offset(id);
1516 let buf = self.arena.raw_buf_mut();
1517 let n = buf.len().saturating_sub(off).min(data.len());
1518 buf[off..off + n].copy_from_slice(&data[..n]);
1519 }
1520 _ => {
1521 let buf = self.arena.slice_mut(id);
1522 let n_floats = data.len() / 4;
1523 let n = buf.len().min(n_floats);
1524 for i in 0..n {
1525 let bytes = [
1526 data[i * 4],
1527 data[i * 4 + 1],
1528 data[i * 4 + 2],
1529 data[i * 4 + 3],
1530 ];
1531 buf[i] = f32::from_le_bytes(bytes);
1532 }
1533 }
1534 }
1535 }
1536
1537 fn set_param_bytes(&mut self, name: &str, data: &[u8], _dtype: rlx_ir::DType) {
1543 self.write_param_bytes_to_arena(name, data);
1545 }
1546
1547 fn write_param_bytes_to_arena(&mut self, name: &str, data: &[u8]) {
1548 if let Some(&id) = self.param_ids.get(name)
1549 && self.arena.has_buffer(id)
1550 {
1551 let off = self.arena.byte_offset(id);
1552 let buf = self.arena.raw_buf_mut();
1553 debug_assert!(
1554 off + data.len() <= buf.len(),
1555 "set_param_bytes: '{name}' would overflow arena slot"
1556 );
1557 buf[off..off + data.len()].copy_from_slice(data);
1558 }
1559 }
1560 }
1561}
1562
1563#[cfg(feature = "gpu")]
1568pub mod wgpu_backend {
1569 use super::*;
1570 use rlx_ir::OpKind;
1571 use rlx_wgpu::backend::WgpuExecutable;
1572
1573 pub struct WgpuBackend;
1574
1575 const WGPU_SUPPORTED_OPS: &[OpKind] = &[
1581 OpKind::Input,
1582 OpKind::Param,
1583 OpKind::Constant,
1584 OpKind::Activation,
1585 OpKind::Cast,
1586 OpKind::StopGradient,
1587 OpKind::Binary,
1588 OpKind::Compare,
1589 OpKind::Where,
1590 OpKind::Fma,
1591 OpKind::ElementwiseRegion,
1592 OpKind::TransformRegion,
1593 OpKind::BatchElementwiseRegion,
1594 OpKind::MatMul,
1595 OpKind::DotGeneral,
1596 OpKind::LayerNorm,
1597 OpKind::LayerNorm2d,
1598 OpKind::GroupNorm,
1599 OpKind::ResizeNearest2x,
1600 OpKind::RmsNorm,
1601 OpKind::Attention,
1602 OpKind::AttentionBackward,
1603 OpKind::RmsNormBackwardInput,
1604 OpKind::RmsNormBackwardGamma,
1605 OpKind::RmsNormBackwardBeta,
1606 OpKind::LayerNormBackwardInput,
1613 OpKind::LayerNormBackwardGamma,
1614 OpKind::RopeBackward,
1615 OpKind::CumsumBackward,
1616 OpKind::GatherBackward,
1617 OpKind::Rope,
1618 OpKind::Reshape,
1619 OpKind::Transpose,
1620 OpKind::Narrow,
1621 OpKind::Concat,
1622 OpKind::Expand,
1623 OpKind::Gather,
1624 OpKind::Reverse,
1625 OpKind::Reduce,
1626 OpKind::Softmax,
1627 OpKind::SoftmaxCrossEntropy,
1628 OpKind::ArgMax,
1629 OpKind::ArgMin,
1630 OpKind::Cumsum,
1631 OpKind::TopK,
1632 OpKind::Sample,
1633 OpKind::Conv,
1634 OpKind::Im2Col,
1635 OpKind::Pool,
1636 OpKind::GroupedMatMul,
1637 OpKind::DequantGroupedMatMul,
1638 OpKind::DequantMoEWeights,
1639 OpKind::ScatterAdd,
1640 OpKind::SelectiveScan,
1641 OpKind::Lstm,
1642 OpKind::Gru,
1643 OpKind::Rnn,
1644 OpKind::Mamba2,
1645 OpKind::ConvTranspose2d,
1647 OpKind::Conv3d,
1649 OpKind::ConvTranspose3d,
1650 OpKind::DequantMatMul,
1651 OpKind::FusedMatMulBiasAct,
1652 OpKind::FusedResidualLN,
1653 OpKind::FusedResidualRmsNorm,
1654 OpKind::FusedSwiGLU,
1655 OpKind::FusedAttentionBlock,
1656 OpKind::FusedTransformerLayer,
1657 OpKind::Fft,
1663 OpKind::Scan,
1667 OpKind::LogMel,
1668 OpKind::LogMelBackward,
1669 OpKind::WelchPeaks,
1670 OpKind::GaussianSplatRender,
1672 OpKind::GaussianSplatRenderBackward,
1673 OpKind::GaussianSplatPrepare,
1674 OpKind::GaussianSplatRasterize,
1675 OpKind::Custom,
1676 OpKind::RngNormal,
1677 OpKind::RngUniform,
1678 ];
1680
1681 impl Backend for WgpuBackend {
1682 fn supported_ops(&self) -> &'static [OpKind] {
1683 WGPU_SUPPORTED_OPS
1684 }
1685
1686 fn compile(&self, graph: Graph, options: &CompileOptions) -> Box<dyn ExecutableGraph> {
1687 use rlx_opt::pass::Pass as _;
1688 let graph = rlx_opt::LowerControlFlow.run(graph);
1689 let graph = rlx_opt::legalize_or_rewrite_for_backend(graph, WGPU_SUPPORTED_OPS)
1690 .unwrap_or_else(|errors| {
1691 panic!("{}", rlx_opt::format_legalize_error("wgpu", &errors));
1692 });
1693 let graph = crate::precompile::precompile_cleanup(graph, options);
1694 let graph = rlx_opt::LegalizeBroadcast.run(graph);
1698 let compile_result = crate::stages::compile_graph_stages_for_backend(
1707 rlx_driver::Device::Gpu,
1708 graph,
1709 options,
1710 WGPU_SUPPORTED_OPS,
1711 );
1712 crate::stages::maybe_log_fusion(&compile_result.fusion);
1713 let graph = compile_result.lir.into_graph();
1714 let graph = match options.policy.clone() {
1715 Some(p) => rlx_opt::AutoMixedPrecision::new(p).run(graph),
1716 None => graph,
1717 };
1718 let (graph, io_manifest) = cpu_low_precision::prepare_f32_exec_graph(graph);
1719 Box::new(WgpuExecutableWrapper {
1720 inner: WgpuExecutable::compile_rng(graph, options.rng),
1721 io_manifest,
1722 })
1723 }
1724
1725 fn compile_lir(
1726 &self,
1727 lir: LirModule,
1728 options: &CompileOptions,
1729 ) -> Box<dyn ExecutableGraph> {
1730 use rlx_opt::pass::Pass as _;
1731 let graph = rlx_opt::LegalizeBroadcast.run(lir.into_graph());
1734 let graph = prepare_fused_graph(graph, options, WGPU_SUPPORTED_OPS, "wgpu");
1735 let (graph, io_manifest) = cpu_low_precision::prepare_f32_exec_graph(graph);
1736 Box::new(WgpuExecutableWrapper {
1737 inner: WgpuExecutable::compile_rng(graph, options.rng),
1738 io_manifest,
1739 })
1740 }
1741 }
1742
1743 struct WgpuExecutableWrapper {
1744 inner: WgpuExecutable,
1745 io_manifest: cpu_low_precision::IoDtypeManifest,
1746 }
1747
1748 unsafe impl Send for WgpuExecutableWrapper {}
1749
1750 impl ExecutableGraph for WgpuExecutableWrapper {
1751 fn set_param(&mut self, name: &str, data: &[f32]) {
1752 self.inner.set_param(name, data);
1753 }
1754 fn run(&mut self, inputs: &[(&str, &[f32])]) -> Vec<Vec<f32>> {
1755 self.inner.run(inputs)
1756 }
1757 fn run_read_outputs(
1758 &mut self,
1759 inputs: &[(&str, &[f32])],
1760 read_indices: Option<&[usize]>,
1761 ) -> Vec<Vec<f32>> {
1762 self.inner.run_read_outputs(inputs, read_indices)
1763 }
1764 fn bind_gpu_handle(&mut self, name: &str, data: &[f32]) -> bool {
1765 self.inner.bind_gpu_handle(name, data)
1766 }
1767 fn has_gpu_handle(&self, name: &str) -> bool {
1768 self.inner.has_gpu_handle(name)
1769 }
1770 fn set_gpu_handle_feed(&mut self, handle_name: &str, output_index: usize) -> bool {
1771 self.inner.set_gpu_handle_feed(handle_name, output_index);
1772 true
1773 }
1774 fn read_gpu_handle(&self, name: &str) -> Option<Vec<f32>> {
1775 self.inner.read_gpu_handle(name)
1776 }
1777 fn set_active_extent(&mut self, extent: Option<(usize, usize)>) {
1778 self.inner.set_active_extent(extent);
1779 }
1780
1781 fn set_rng(&mut self, rng: rlx_ir::RngOptions) {
1782 self.inner.set_rng(rng);
1783 }
1784
1785 fn rng(&self) -> rlx_ir::RngOptions {
1786 self.inner.rng()
1787 }
1788
1789 fn set_param_typed(&mut self, name: &str, data: &[u8], dtype: rlx_ir::DType) {
1792 match dtype {
1793 rlx_ir::DType::U8 | rlx_ir::DType::I8 => {
1794 self.inner.set_param_bytes(name, data);
1795 }
1796 rlx_ir::DType::F32 => {
1797 let n = data.len() / 4;
1798 let f32_slice =
1799 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const f32, n) };
1800 self.inner.set_param(name, f32_slice);
1801 }
1802 rlx_ir::DType::F16 => {
1803 let n = data.len() / 2;
1804 let f16_slice =
1805 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const half::f16, n) };
1806 let f32: Vec<f32> = f16_slice.iter().map(|h| h.to_f32()).collect();
1807 self.inner.set_param(name, &f32);
1808 }
1809 rlx_ir::DType::BF16 => {
1810 let n = data.len() / 2;
1811 let bf16_slice = unsafe {
1812 std::slice::from_raw_parts(data.as_ptr() as *const half::bf16, n)
1813 };
1814 let f32: Vec<f32> = bf16_slice.iter().map(|h| h.to_f32()).collect();
1815 self.inner.set_param(name, &f32);
1816 }
1817 other => panic!(
1818 "rlx-wgpu set_param_typed: dtype {other:?} unsupported \
1819 (F32, F16, BF16 only — wgpu arena is f32-uniform)"
1820 ),
1821 }
1822 }
1823
1824 fn run_typed(
1827 &mut self,
1828 inputs: &[(&str, &[u8], rlx_ir::DType)],
1829 ) -> Vec<(Vec<u8>, rlx_ir::DType)> {
1830 let mut owned: Vec<(String, Vec<f32>)> = Vec::with_capacity(inputs.len());
1831 for (name, data, dt) in inputs {
1832 let v: Vec<f32> = match *dt {
1833 rlx_ir::DType::F32 => {
1834 let n = data.len() / 4;
1835 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const f32, n) }
1836 .to_vec()
1837 }
1838 rlx_ir::DType::F16 => {
1839 let n = data.len() / 2;
1840 let s = unsafe {
1841 std::slice::from_raw_parts(data.as_ptr() as *const half::f16, n)
1842 };
1843 s.iter().map(|h| h.to_f32()).collect()
1844 }
1845 rlx_ir::DType::BF16 => {
1846 let n = data.len() / 2;
1847 let s = unsafe {
1848 std::slice::from_raw_parts(data.as_ptr() as *const half::bf16, n)
1849 };
1850 s.iter().map(|h| h.to_f32()).collect()
1851 }
1852 rlx_ir::DType::I64 => {
1856 let n = data.len() / 8;
1857 let s =
1858 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const i64, n) };
1859 s.iter().map(|&x| x as f32).collect()
1860 }
1861 rlx_ir::DType::I32 => {
1862 let n = data.len() / 4;
1863 let s =
1864 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const i32, n) };
1865 s.iter().map(|&x| x as f32).collect()
1866 }
1867 rlx_ir::DType::U8 | rlx_ir::DType::I8 | rlx_ir::DType::Bool => {
1868 data.iter().map(|&b| b as f32).collect()
1869 }
1870 other => {
1871 panic!("rlx-wgpu run_typed: input '{name}' dtype {other:?} unsupported")
1872 }
1873 };
1874 owned.push((name.to_string(), v));
1875 }
1876 let refs: Vec<(&str, &[f32])> = owned
1877 .iter()
1878 .map(|(n, d)| (n.as_str(), d.as_slice()))
1879 .collect();
1880 let dtypes =
1881 super::declared_output_dtypes(&self.io_manifest, self.inner.output_dtypes());
1882 let outs = self.inner.run(&refs);
1883 outs.into_iter()
1884 .zip(
1885 dtypes
1886 .into_iter()
1887 .chain(std::iter::repeat(rlx_ir::DType::F32)),
1888 )
1889 .map(|(v, dt)| (narrow_to_dtype(&v, dt), dt))
1890 .collect()
1891 }
1892
1893 fn clone_box(&self) -> Box<dyn ExecutableGraph> {
1894 Box::new(WgpuExecutableWrapper {
1895 inner: self.inner.clone_for_cache(),
1896 io_manifest: self.io_manifest.clone(),
1897 })
1898 }
1899 }
1900
1901 fn narrow_to_dtype(v: &[f32], dt: rlx_ir::DType) -> Vec<u8> {
1907 use rlx_ir::DType;
1908 match dt {
1909 DType::F32 => {
1910 let mut bytes = Vec::with_capacity(v.len() * 4);
1911 for &x in v {
1912 bytes.extend_from_slice(&x.to_le_bytes());
1913 }
1914 bytes
1915 }
1916 DType::F16 => {
1917 let mut bytes = Vec::with_capacity(v.len() * 2);
1918 for &x in v {
1919 bytes.extend_from_slice(&half::f16::from_f32(x).to_le_bytes());
1920 }
1921 bytes
1922 }
1923 DType::BF16 => {
1924 let mut bytes = Vec::with_capacity(v.len() * 2);
1925 for &x in v {
1926 bytes.extend_from_slice(&half::bf16::from_f32(x).to_le_bytes());
1927 }
1928 bytes
1929 }
1930 DType::F64 => {
1931 let mut bytes = Vec::with_capacity(v.len() * 8);
1932 for &x in v {
1933 bytes.extend_from_slice(&(x as f64).to_le_bytes());
1934 }
1935 bytes
1936 }
1937 DType::I8 => v.iter().map(|&x| x as i8 as u8).collect(),
1938 DType::U8 => v.iter().map(|&x| x as u8).collect(),
1939 DType::I16 => {
1940 let mut bytes = Vec::with_capacity(v.len() * 2);
1941 for &x in v {
1942 bytes.extend_from_slice(&(x as i16).to_le_bytes());
1943 }
1944 bytes
1945 }
1946 DType::I32 => {
1947 let mut bytes = Vec::with_capacity(v.len() * 4);
1948 for &x in v {
1949 bytes.extend_from_slice(&(x as i32).to_le_bytes());
1950 }
1951 bytes
1952 }
1953 DType::U32 => {
1954 let mut bytes = Vec::with_capacity(v.len() * 4);
1955 for &x in v {
1956 bytes.extend_from_slice(&(x as u32).to_le_bytes());
1957 }
1958 bytes
1959 }
1960 DType::I64 => {
1961 let mut bytes = Vec::with_capacity(v.len() * 8);
1962 for &x in v {
1963 bytes.extend_from_slice(&(x as i64).to_le_bytes());
1964 }
1965 bytes
1966 }
1967 DType::Bool => v
1968 .iter()
1969 .map(|&x| if x != 0.0 { 1u8 } else { 0u8 })
1970 .collect(),
1971 DType::C64 => {
1978 let mut bytes = Vec::with_capacity(v.len() * 4);
1979 for &x in v {
1980 bytes.extend_from_slice(&x.to_le_bytes());
1981 }
1982 bytes
1983 }
1984 }
1985 }
1986}
1987
1988#[cfg(feature = "vulkan")]
1991pub mod vulkan_backend {
1992 use super::*;
1993 use rlx_ir::OpKind;
1994 use rlx_vulkan::backend::VulkanExecutable;
1995
1996 pub struct VulkanBackend;
1997
1998 impl Backend for VulkanBackend {
1999 fn supported_ops(&self) -> &'static [OpKind] {
2000 rlx_vulkan::backend::SUPPORTED_OPS
2001 }
2002
2003 fn compile(&self, graph: Graph, options: &CompileOptions) -> Box<dyn ExecutableGraph> {
2004 Box::new(VulkanExecutableWrapper {
2009 inner: VulkanExecutable::compile_rng(graph, options.rng),
2010 })
2011 }
2012 }
2013
2014 struct VulkanExecutableWrapper {
2015 inner: VulkanExecutable,
2016 }
2017
2018 unsafe impl Send for VulkanExecutableWrapper {}
2019
2020 impl ExecutableGraph for VulkanExecutableWrapper {
2021 fn set_param(&mut self, name: &str, data: &[f32]) {
2022 self.inner.set_param(name, data);
2023 }
2024
2025 fn run(&mut self, inputs: &[(&str, &[f32])]) -> Vec<Vec<f32>> {
2026 self.inner.run(inputs)
2027 }
2028
2029 fn run_read_outputs(
2030 &mut self,
2031 inputs: &[(&str, &[f32])],
2032 read_indices: Option<&[usize]>,
2033 ) -> Vec<Vec<f32>> {
2034 self.inner.run_read_outputs(inputs, read_indices)
2035 }
2036
2037 fn set_active_extent(&mut self, extent: Option<(usize, usize)>) {
2038 self.inner.set_active_extent(extent);
2039 }
2040
2041 fn set_rng(&mut self, rng: rlx_ir::RngOptions) {
2042 self.inner.set_rng(rng);
2043 }
2044
2045 fn rng(&self) -> rlx_ir::RngOptions {
2046 self.inner.rng()
2047 }
2048
2049 fn bind_gpu_handle(&mut self, name: &str, data: &[f32]) -> bool {
2050 self.inner.bind_gpu_handle(name, data)
2051 }
2052
2053 fn has_gpu_handle(&self, name: &str) -> bool {
2054 self.inner.has_gpu_handle(name)
2055 }
2056
2057 fn set_gpu_handle_feed(&mut self, handle_name: &str, output_index: usize) -> bool {
2058 self.inner.set_gpu_handle_feed(handle_name, output_index);
2059 true
2060 }
2061
2062 fn read_gpu_handle(&self, name: &str) -> Option<Vec<f32>> {
2063 self.inner.read_gpu_handle(name)
2064 }
2065
2066 fn register_kv_row_feed(&mut self, handle_name: &str, output_index: usize) -> bool {
2067 self.inner.register_kv_row_feed(handle_name, output_index);
2068 true
2069 }
2070
2071 fn feed_kv_row(&mut self, src_row: usize, dst_row: usize, row_elems: usize) -> bool {
2072 self.inner.feed_kv_row(src_row, dst_row, row_elems);
2073 true
2074 }
2075
2076 fn read_output_row(
2077 &self,
2078 out_idx: usize,
2079 row: usize,
2080 row_inner: usize,
2081 ) -> Option<Vec<f32>> {
2082 self.inner.read_output_row(out_idx, row, row_inner)
2083 }
2084
2085 fn read_gpu_handle_row(
2086 &self,
2087 name: &str,
2088 row: usize,
2089 row_inner: usize,
2090 ) -> Option<Vec<f32>> {
2091 self.inner.read_gpu_handle_row(name, row, row_inner)
2092 }
2093
2094 fn set_param_typed(&mut self, name: &str, data: &[u8], dtype: rlx_ir::DType) {
2096 match dtype {
2097 rlx_ir::DType::U8 | rlx_ir::DType::I8 => self.inner.set_param_bytes(name, data),
2098 rlx_ir::DType::F32 => {
2099 let n = data.len() / 4;
2100 let s = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const f32, n) };
2101 self.inner.set_param(name, s);
2102 }
2103 other => {
2104 let f = super::widen_bytes_to_f32(data, other);
2105 self.inner.set_param(name, &f);
2106 }
2107 }
2108 }
2109
2110 fn run_typed(
2113 &mut self,
2114 inputs: &[(&str, &[u8], rlx_ir::DType)],
2115 ) -> Vec<(Vec<u8>, rlx_ir::DType)> {
2116 let mut owned: Vec<(String, Vec<f32>)> = Vec::with_capacity(inputs.len());
2117 for (name, data, dt) in inputs {
2118 let v = if *dt == rlx_ir::DType::F32 {
2119 let n = data.len() / 4;
2120 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const f32, n) }.to_vec()
2121 } else {
2122 super::widen_bytes_to_f32(data, *dt)
2123 };
2124 owned.push((name.to_string(), v));
2125 }
2126 let refs: Vec<(&str, &[f32])> = owned
2127 .iter()
2128 .map(|(n, d)| (n.as_str(), d.as_slice()))
2129 .collect();
2130 let dtypes = self.inner.output_dtypes();
2131 let outs = self.inner.run(&refs);
2132 outs.into_iter()
2133 .zip(
2134 dtypes
2135 .into_iter()
2136 .chain(std::iter::repeat(rlx_ir::DType::F32)),
2137 )
2138 .map(|(v, dt)| (super::narrow_f32_to_bytes(&v, dt), dt))
2139 .collect()
2140 }
2141
2142 fn clone_box(&self) -> Box<dyn ExecutableGraph> {
2143 Box::new(VulkanExecutableWrapper {
2144 inner: self.inner.clone_for_cache(),
2145 })
2146 }
2147 }
2148}
2149
2150#[cfg(feature = "oneapi")]
2153pub mod oneapi_backend {
2154 use super::*;
2155 use rlx_ir::OpKind;
2156 use rlx_oneapi::backend::OneApiExecutable;
2157
2158 pub struct OneApiBackend;
2159
2160 impl Backend for OneApiBackend {
2161 fn supported_ops(&self) -> &'static [OpKind] {
2162 rlx_oneapi::backend::SUPPORTED_OPS
2163 }
2164
2165 fn compile(&self, graph: Graph, options: &CompileOptions) -> Box<dyn ExecutableGraph> {
2166 Box::new(OneApiExecutableWrapper {
2170 inner: OneApiExecutable::compile_rng(graph, options.rng),
2171 })
2172 }
2173 }
2174
2175 struct OneApiExecutableWrapper {
2176 inner: OneApiExecutable,
2177 }
2178
2179 unsafe impl Send for OneApiExecutableWrapper {}
2180
2181 impl ExecutableGraph for OneApiExecutableWrapper {
2182 fn set_param(&mut self, name: &str, data: &[f32]) {
2183 self.inner.set_param(name, data);
2184 }
2185
2186 fn run(&mut self, inputs: &[(&str, &[f32])]) -> Vec<Vec<f32>> {
2187 self.inner.run(inputs)
2188 }
2189
2190 fn run_read_outputs(
2191 &mut self,
2192 inputs: &[(&str, &[f32])],
2193 read_indices: Option<&[usize]>,
2194 ) -> Vec<Vec<f32>> {
2195 self.inner.run_read_outputs(inputs, read_indices)
2196 }
2197
2198 fn set_active_extent(&mut self, extent: Option<(usize, usize)>) {
2199 self.inner.set_active_extent(extent);
2200 }
2201
2202 fn set_rng(&mut self, rng: rlx_ir::RngOptions) {
2203 self.inner.set_rng(rng);
2204 }
2205
2206 fn rng(&self) -> rlx_ir::RngOptions {
2207 self.inner.rng()
2208 }
2209
2210 fn set_param_typed(&mut self, name: &str, data: &[u8], dtype: rlx_ir::DType) {
2212 match dtype {
2213 rlx_ir::DType::U8 | rlx_ir::DType::I8 => self.inner.set_param_bytes(name, data),
2214 rlx_ir::DType::F32 => {
2215 let n = data.len() / 4;
2216 let s = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const f32, n) };
2217 self.inner.set_param(name, s);
2218 }
2219 other => {
2220 let f = super::widen_bytes_to_f32(data, other);
2221 self.inner.set_param(name, &f);
2222 }
2223 }
2224 }
2225
2226 fn run_typed(
2229 &mut self,
2230 inputs: &[(&str, &[u8], rlx_ir::DType)],
2231 ) -> Vec<(Vec<u8>, rlx_ir::DType)> {
2232 let mut owned: Vec<(String, Vec<f32>)> = Vec::with_capacity(inputs.len());
2233 for (name, data, dt) in inputs {
2234 let v = if *dt == rlx_ir::DType::F32 {
2235 let n = data.len() / 4;
2236 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const f32, n) }.to_vec()
2237 } else {
2238 super::widen_bytes_to_f32(data, *dt)
2239 };
2240 owned.push((name.to_string(), v));
2241 }
2242 let refs: Vec<(&str, &[f32])> = owned
2243 .iter()
2244 .map(|(n, d)| (n.as_str(), d.as_slice()))
2245 .collect();
2246 let dtypes = self.inner.output_dtypes();
2247 let outs = self.inner.run(&refs);
2248 outs.into_iter()
2249 .zip(
2250 dtypes
2251 .into_iter()
2252 .chain(std::iter::repeat(rlx_ir::DType::F32)),
2253 )
2254 .map(|(v, dt)| (super::narrow_f32_to_bytes(&v, dt), dt))
2255 .collect()
2256 }
2257
2258 fn clone_box(&self) -> Box<dyn ExecutableGraph> {
2259 Box::new(OneApiExecutableWrapper {
2260 inner: self.inner.clone_for_cache(),
2261 })
2262 }
2263 }
2264}
2265
2266#[cfg(all(feature = "mlx", rlx_mlx_host))]
2269pub mod mlx_backend {
2270 use super::*;
2271 use rlx_mlx::MlxExecutable;
2272
2273 pub struct MlxBackend;
2274
2275 const MLX_SUPPORTED_OPS: &[rlx_ir::OpKind] = {
2285 use rlx_ir::OpKind::*;
2286 &[
2287 Input,
2288 Param,
2289 Constant,
2290 Activation,
2291 Cast,
2292 StopGradient,
2293 Binary,
2294 Compare,
2295 Where,
2296 ElementwiseRegion,
2297 TransformRegion,
2298 BatchElementwiseRegion,
2299 MatMul,
2300 DotGeneral,
2301 DenseSolve,
2302 BatchedDenseSolve,
2303 LayerNorm,
2304 LayerNorm2d,
2305 GroupNorm,
2306 ResizeNearest2x,
2307 RmsNorm,
2308 Attention,
2309 Rope,
2310 Reshape,
2311 Transpose,
2312 Narrow,
2313 Concat,
2314 Expand,
2315 Gather,
2316 Reverse,
2317 Reduce,
2318 Softmax,
2319 Cumsum,
2320 ArgMax,
2321 ArgMin,
2322 TopK,
2323 RngNormal,
2324 RngUniform,
2325 Sample,
2326 Conv,
2327 Im2Col,
2328 ConvTranspose2d,
2329 Pool,
2330 GroupedMatMul,
2331 DequantGroupedMatMul,
2332 DequantMoEWeights,
2333 ScatterAdd,
2334 LoraMatMul,
2335 DequantMatMul,
2336 SelectiveScan,
2337 GatedDeltaNet,
2338 FusedSwiGLU,
2339 FusedMatMulBiasAct,
2340 FusedResidualLN,
2341 FusedResidualRmsNorm,
2342 FusedAttentionBlock,
2343 FusedTransformerLayer,
2344 If,
2345 While,
2346 Scan,
2351 ScanBackward,
2352 ScanBackwardXs,
2353 ReluBackward,
2356 ActivationBackward,
2357 SoftmaxCrossEntropy,
2358 SoftmaxCrossEntropyWithLogits,
2359 SoftmaxCrossEntropyBackward,
2360 AttentionBackward,
2361 LayerNormBackwardInput,
2362 LayerNormBackwardGamma,
2363 GroupNormBackwardInput,
2366 GroupNormBackwardGamma,
2367 GroupNormBackwardBeta,
2368 Conv2dBackwardInput,
2373 Conv2dBackwardWeight,
2374 MaxPool2dBackward,
2378 FakeQuantize,
2383 FakeQuantizeBackward,
2384 Custom,
2389 Fft,
2390 LogMel,
2391 LogMelBackward,
2392 WelchPeaks,
2393 GaussianSplatRender,
2394 GaussianSplatRenderBackward,
2395 ]
2398 };
2399
2400 impl Backend for MlxBackend {
2401 fn supported_ops(&self) -> &'static [rlx_ir::OpKind] {
2402 MLX_SUPPORTED_OPS
2403 }
2404
2405 fn compile(&self, graph: Graph, options: &CompileOptions) -> Box<dyn ExecutableGraph> {
2406 let compile_result = crate::stages::compile_graph_stages_for_backend(
2407 rlx_driver::Device::Mlx,
2408 graph,
2409 options,
2410 MLX_SUPPORTED_OPS,
2411 );
2412 crate::stages::maybe_log_fusion(&compile_result.fusion);
2413 self.compile_lir(compile_result.lir, options)
2414 }
2415
2416 fn compile_lir(
2417 &self,
2418 lir: LirModule,
2419 options: &CompileOptions,
2420 ) -> Box<dyn ExecutableGraph> {
2421 use rlx_opt::pass::Pass as _;
2422 let mut graph = lir.into_graph();
2423 graph = rlx_opt::LowerControlFlow.run(graph);
2424 let graph = prepare_fused_graph(graph, options, MLX_SUPPORTED_OPS, "mlx");
2425 Box::new(build_mlx_executable(graph, options.rng))
2426 }
2427 }
2428
2429 fn build_mlx_executable(graph: Graph, rng: rlx_ir::RngOptions) -> MlxExecutableWrapper {
2430 let (graph, io_manifest) = cpu_low_precision::prepare_f32_exec_graph(graph);
2431 let mode = mlx_mode_from_env();
2432 let mut exe = MlxExecutable::compile_from_fused_with_rng(graph, mode, rng);
2433 if mode == rlx_mlx::lower::MlxMode::Compiled {
2434 if let Err(e) = exe.warm_compile() {
2435 eprintln!(
2436 "[rlx-runtime] MLX warm_compile failed ({e}); first run will pay the trace cost"
2437 );
2438 }
2439 }
2440 MlxExecutableWrapper {
2441 inner: exe,
2442 io_manifest,
2443 }
2444 }
2445
2446 fn mlx_mode_from_env() -> rlx_mlx::lower::MlxMode {
2447 match rlx_ir::env::var("RLX_MLX_MODE").as_deref() {
2448 Some(s) if s.eq_ignore_ascii_case("eager") => rlx_mlx::lower::MlxMode::Eager,
2449 Some(s) if s.eq_ignore_ascii_case("lazy") => rlx_mlx::lower::MlxMode::Lazy,
2450 Some(s) if s.eq_ignore_ascii_case("compiled") => rlx_mlx::lower::MlxMode::Compiled,
2451 _ => rlx_mlx::lower::MlxMode::Compiled,
2452 }
2453 }
2454
2455 struct MlxExecutableWrapper {
2456 inner: MlxExecutable,
2457 io_manifest: cpu_low_precision::IoDtypeManifest,
2458 }
2459
2460 unsafe impl Send for MlxExecutableWrapper {}
2461
2462 impl ExecutableGraph for MlxExecutableWrapper {
2463 fn set_param(&mut self, name: &str, data: &[f32]) {
2464 self.inner.set_param(name, data);
2465 }
2466 fn run(&mut self, inputs: &[(&str, &[f32])]) -> Vec<Vec<f32>> {
2467 self.inner.run(inputs)
2468 }
2469 fn run_read_outputs(
2470 &mut self,
2471 inputs: &[(&str, &[f32])],
2472 read_indices: Option<&[usize]>,
2473 ) -> Vec<Vec<f32>> {
2474 self.inner
2475 .run_read_outputs(inputs, read_indices)
2476 .unwrap_or_else(|e| panic!("MLX run_read_outputs failed: {e}"))
2477 }
2478 fn run_slots(&mut self, inputs: &[&[f32]]) -> &[(usize, usize)] {
2479 self.inner.run_slots(inputs)
2480 }
2481 fn arena_ptr(&self) -> *const u8 {
2482 self.inner.arena_ptr()
2483 }
2484 fn commit_no_wait(&mut self, inputs: &[(&str, &[f32])]) {
2485 self.inner.commit_no_wait(inputs);
2486 }
2487 fn sync_pending(&mut self) {
2488 self.inner.sync_pending();
2489 }
2490 fn run_pipelined(&mut self, input_sets: &[Vec<(&str, &[f32])>]) -> Vec<Vec<Vec<f32>>> {
2491 self.inner.run_pipelined(input_sets)
2492 }
2493 fn bind_handle(&mut self, name: &str, data: &[f32]) -> bool {
2494 self.inner.bind_handle(name, data)
2495 }
2496 fn read_handle(&self, name: &str) -> Option<Vec<f32>> {
2497 self.inner.read_handle(name)
2498 }
2499 fn bind_gpu_handle(&mut self, name: &str, data: &[f32]) -> bool {
2500 self.inner.bind_gpu_handle(name, data).is_ok()
2501 }
2502 fn has_gpu_handle(&self, name: &str) -> bool {
2503 self.inner.has_gpu_handle(name)
2504 }
2505 fn set_gpu_handle_feed(&mut self, handle_name: &str, output_index: usize) -> bool {
2506 self.inner.set_gpu_handle_feed(handle_name, output_index);
2507 true
2508 }
2509 fn register_kv_row_feed(&mut self, handle_name: &str, output_index: usize) -> bool {
2510 self.inner.register_kv_row_feed(handle_name, output_index);
2511 true
2512 }
2513 fn feed_kv_row(&mut self, src_row: usize, dst_row: usize, row_elems: usize) -> bool {
2514 self.inner.feed_kv_row(src_row, dst_row, row_elems).is_ok()
2515 }
2516 fn read_gpu_handle(&self, name: &str) -> Option<Vec<f32>> {
2517 self.inner.read_gpu_handle(name).ok()
2518 }
2519 fn run_feed_gpu_handle(
2520 &mut self,
2521 inputs: &[(&str, &[f32])],
2522 handle_name: &str,
2523 output_index: usize,
2524 ) -> Option<Vec<f32>> {
2525 self.inner
2526 .run_feed_gpu(inputs, handle_name, output_index)
2527 .ok()
2528 }
2529 fn set_param_typed(&mut self, name: &str, data: &[u8], dtype: rlx_ir::DType) {
2530 self.inner.set_param_typed(name, data, dtype);
2531 }
2532 fn run_typed(
2533 &mut self,
2534 inputs: &[(&str, &[u8], rlx_ir::DType)],
2535 ) -> Vec<(Vec<u8>, rlx_ir::DType)> {
2536 self.inner.run_typed(inputs)
2537 }
2538 fn set_active_extent(&mut self, extent: Option<(usize, usize)>) {
2539 self.inner.set_active_extent(extent);
2540 }
2541
2542 fn set_rng(&mut self, rng: rlx_ir::RngOptions) {
2543 self.inner.set_rng(rng);
2544 }
2545
2546 fn rng(&self) -> rlx_ir::RngOptions {
2547 self.inner.rng()
2548 }
2549
2550 fn copy_params_from(&mut self, src: &dyn ExecutableGraph) -> bool {
2551 let Some(src_any) = src.executable_as_any() else {
2552 return false;
2553 };
2554 let Some(src_wrap) = src_any.downcast_ref::<MlxExecutableWrapper>() else {
2555 return false;
2556 };
2557 let Some(dst_any) = self.executable_as_any_mut() else {
2558 return false;
2559 };
2560 let Some(dst_wrap) = dst_any.downcast_mut::<MlxExecutableWrapper>() else {
2561 return false;
2562 };
2563 dst_wrap.inner.copy_params_from(&src_wrap.inner)
2564 }
2565
2566 fn executable_as_any(&self) -> Option<&dyn std::any::Any> {
2567 Some(self)
2568 }
2569
2570 fn executable_as_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
2571 Some(self)
2572 }
2573
2574 fn clone_box(&self) -> Box<dyn ExecutableGraph> {
2575 Box::new(MlxExecutableWrapper {
2576 inner: self.inner.clone_for_cache(),
2577 io_manifest: self.io_manifest.clone(),
2578 })
2579 }
2580 }
2581}
2582
2583pub(crate) const COREML_SUPPORTED_OPS: &[rlx_ir::OpKind] = {
2590 use rlx_ir::OpKind::*;
2591 &[
2592 Input,
2593 Param,
2594 Constant,
2595 Activation,
2596 Cast,
2597 Binary,
2598 MatMul,
2599 LayerNorm,
2600 RmsNorm,
2601 Reduce,
2602 Softmax,
2603 Reshape,
2604 Transpose,
2605 Narrow,
2606 Concat,
2607 Gather,
2608 Rope,
2609 Attention,
2610 FusedAttentionBlock,
2615 Compare,
2616 Where,
2617 Expand,
2618 Cumsum,
2619 ScatterAdd,
2620 BatchNormInference,
2621 GroupNorm,
2622 LayerNorm2d,
2623 LoraMatMul,
2624 Conv,
2625 ConvTranspose2d,
2626 Pool,
2627 TopK,
2628 AxialRope2d,
2629 ResizeNearest2x,
2630 StopGradient,
2631 GroupedMatMul,
2632 DequantMatMul,
2633 DequantMoEWeights,
2634 DequantGroupedMatMul,
2635 Quantize,
2636 Dequantize,
2637 SelectiveScan,
2638 GatedDeltaNet,
2639 ArgMax,
2640 ArgMin,
2641 Reverse,
2642 Fft,
2643 LogMel,
2644 Sample,
2645 RngNormal,
2646 Lstm,
2647 Scan,
2650 Gru,
2651 Rnn,
2652 Mamba2,
2653 WelchPeaks,
2654 Custom,
2655 ]
2656};
2657
2658#[allow(dead_code)] pub(crate) const COREML_BACKWARD_OPS: &[rlx_ir::OpKind] = {
2677 use rlx_ir::OpKind::*;
2678 &[
2679 ReluBackward,
2680 ActivationBackward,
2681 LayerNormBackwardInput,
2682 LayerNormBackwardGamma,
2683 GroupNormBackwardInput,
2684 GroupNormBackwardGamma,
2685 GroupNormBackwardBeta,
2686 BatchNormInferenceBackwardInput,
2687 BatchNormInferenceBackwardGamma,
2688 BatchNormInferenceBackwardBeta,
2689 RopeBackward,
2690 AttentionBackward,
2691 SoftmaxCrossEntropyBackward,
2692 CumsumBackward,
2693 GatherBackward,
2694 FakeQuantizeBackward,
2695 ]
2696};
2697
2698#[allow(dead_code)] pub(crate) const COREML_NATIVE_BACKWARD_OPS: &[rlx_ir::OpKind] = {
2732 use rlx_ir::OpKind::*;
2733 &[
2734 RmsNormBackwardInput,
2735 RmsNormBackwardGamma,
2736 RmsNormBackwardBeta,
2737 LayerNormBackwardInput,
2738 LayerNormBackwardGamma,
2739 GroupNormBackwardInput,
2740 GroupNormBackwardGamma,
2741 GroupNormBackwardBeta,
2742 MaxPool2dBackward,
2743 Conv2dBackwardInput,
2744 Conv2dBackwardWeight,
2745 AttentionBackward,
2746 SoftmaxCrossEntropyWithLogits,
2752 SoftmaxCrossEntropyBackward,
2753 ]
2754};
2755
2756#[cfg(feature = "training")]
2767pub(crate) const COREML_SUPPORTED_OPS_TRAINING: [rlx_ir::OpKind;
2768 COREML_SUPPORTED_OPS.len() + COREML_NATIVE_BACKWARD_OPS.len()] = {
2769 let mut arr =
2770 [rlx_ir::OpKind::Input; COREML_SUPPORTED_OPS.len() + COREML_NATIVE_BACKWARD_OPS.len()];
2771 let mut i = 0;
2772 while i < COREML_SUPPORTED_OPS.len() {
2773 arr[i] = COREML_SUPPORTED_OPS[i];
2774 i += 1;
2775 }
2776 let mut j = 0;
2777 while j < COREML_NATIVE_BACKWARD_OPS.len() {
2778 arr[COREML_SUPPORTED_OPS.len() + j] = COREML_NATIVE_BACKWARD_OPS[j];
2779 j += 1;
2780 }
2781 arr
2782};
2783
2784#[cfg(all(
2792 feature = "coreml",
2793 target_vendor = "apple",
2794 not(target_os = "watchos")
2795))]
2796pub mod coreml_backend {
2797 use super::*;
2798 use crate::Precision;
2799 use rlx_coreml::{CoremlExecutable, default_lower_options};
2800
2801 pub struct CoremlBackend;
2802
2803 impl Backend for CoremlBackend {
2804 fn supported_ops(&self) -> &'static [rlx_ir::OpKind] {
2805 #[cfg(feature = "training")]
2811 {
2812 &super::COREML_SUPPORTED_OPS_TRAINING
2813 }
2814 #[cfg(not(feature = "training"))]
2815 {
2816 super::COREML_SUPPORTED_OPS
2817 }
2818 }
2819
2820 fn compile(&self, graph: Graph, options: &CompileOptions) -> Box<dyn ExecutableGraph> {
2821 let graph = rlx_opt::legalize_or_rewrite_for_backend(graph, self.supported_ops())
2825 .unwrap_or_else(|errors| {
2826 panic!("{}", rlx_opt::format_legalize_error("coreml", &errors));
2827 });
2828 let (graph, mut lower_opts) = match options.policy.clone() {
2835 Some(policy) => {
2836 use rlx_opt::pass::Pass as _;
2837 let g = rlx_opt::AutoMixedPrecision::new(policy).run(graph);
2838 let opts = default_lower_options(&g);
2839 (g, opts)
2840 }
2841 None => {
2842 let mut opts = default_lower_options(&graph);
2843 if options.precision == Precision::F16 {
2845 opts.float_dtype = rlx_ir::DType::F16;
2846 }
2847 (graph, opts)
2848 }
2849 };
2850 if let Some(binding) = &options.dim_binding {
2851 let _ = binding;
2852 lower_opts.flexible_inputs = false;
2853 }
2854 Box::new(CoremlExecutableWrapper {
2855 inner: CoremlExecutable::compile_with_lower_opts(graph, lower_opts),
2856 })
2857 }
2858
2859 fn compile_lir(
2860 &self,
2861 lir: LirModule,
2862 options: &CompileOptions,
2863 ) -> Box<dyn ExecutableGraph> {
2864 self.compile(lir.into_graph(), options)
2867 }
2868 }
2869
2870 struct CoremlExecutableWrapper {
2871 inner: CoremlExecutable,
2872 }
2873
2874 unsafe impl Send for CoremlExecutableWrapper {}
2876
2877 impl ExecutableGraph for CoremlExecutableWrapper {
2878 fn set_param(&mut self, name: &str, data: &[f32]) {
2879 self.inner.set_param(name, data);
2880 }
2881
2882 fn set_param_typed(&mut self, name: &str, data: &[u8], dtype: rlx_ir::DType) {
2883 self.inner.set_param_typed(name, data, dtype);
2886 }
2887
2888 fn finalize_params(&mut self) {
2889 self.inner
2890 .finalize()
2891 .unwrap_or_else(|e| panic!("CoreML finalize failed: {e}"));
2892 }
2893
2894 fn run(&mut self, inputs: &[(&str, &[f32])]) -> Vec<Vec<f32>> {
2895 self.inner
2896 .run(inputs)
2897 .unwrap_or_else(|e| panic!("CoreML run failed: {e}"))
2898 }
2899
2900 fn clone_box(&self) -> Box<dyn ExecutableGraph> {
2901 Box::new(CoremlExecutableWrapper {
2902 inner: self.inner.clone_for_cache(),
2903 })
2904 }
2905
2906 fn run_typed(
2907 &mut self,
2908 inputs: &[(&str, &[u8], rlx_ir::DType)],
2909 ) -> Vec<(Vec<u8>, rlx_ir::DType)> {
2910 use rlx_ir::DType;
2911 let owned: Vec<(String, Vec<f32>)> = inputs
2914 .iter()
2915 .map(|(name, data, dt)| {
2916 let v: Vec<f32> = match dt {
2917 DType::I64 => data
2918 .chunks_exact(8)
2919 .map(|c| i64::from_le_bytes(c.try_into().unwrap()) as f32)
2920 .collect(),
2921 DType::I32 => data
2922 .chunks_exact(4)
2923 .map(|c| i32::from_le_bytes(c.try_into().unwrap()) as f32)
2924 .collect(),
2925 DType::U8 | DType::Bool => data.iter().map(|&b| b as f32).collect(),
2926 _ => super::widen_bytes_to_f32(data, *dt),
2927 };
2928 (name.to_string(), v)
2929 })
2930 .collect();
2931 let refs: Vec<(&str, &[f32])> = owned
2932 .iter()
2933 .map(|(n, d)| (n.as_str(), d.as_slice()))
2934 .collect();
2935 self.run(&refs)
2936 .into_iter()
2937 .map(|v| {
2938 let bytes: Vec<u8> = v.iter().flat_map(|f| f.to_le_bytes()).collect();
2939 (bytes, DType::F32)
2940 })
2941 .collect()
2942 }
2943 }
2944}
2945
2946#[cfg(all(feature = "metal", target_vendor = "apple", not(target_os = "watchos")))]
2947pub mod metal_backend {
2948 use super::*;
2949 use rlx_metal::backend::MetalExecutable;
2950
2951 pub struct MetalBackend;
2952
2953 const METAL_SUPPORTED_OPS: &[rlx_ir::OpKind] = {
2967 use rlx_ir::OpKind::*;
2968 &[
2969 Input,
2970 Param,
2971 Constant,
2972 Activation,
2973 Cast,
2974 StopGradient,
2975 Binary,
2976 Compare,
2977 Where,
2978 Fma,
2979 ElementwiseRegion,
2980 TransformRegion,
2981 BatchElementwiseRegion,
2982 MatMul,
2983 ScaledMatMul,
2984 ScaledQuantize,
2985 ScaledQuantScale,
2986 ScaledDequantize,
2987 DotGeneral,
2988 LayerNorm,
2989 LayerNorm2d,
2990 GroupNorm,
2991 RmsNorm,
2992 ResizeNearest2x,
2993 AxialRope2d,
2994 Attention,
2995 AttentionBackward,
2996 RmsNormBackwardInput,
2997 RmsNormBackwardGamma,
2998 RmsNormBackwardBeta,
2999 RopeBackward,
3000 Cumsum,
3001 CumsumBackward,
3002 GatherBackward,
3003 Conv2dBackwardInput,
3004 Conv2dBackwardWeight,
3005 MaxPool2dBackward,
3006 Rope,
3007 Reshape,
3008 Transpose,
3009 Narrow,
3010 Concat,
3011 Expand,
3012 Gather,
3013 Reverse,
3014 Reduce,
3015 Softmax,
3016 SoftmaxCrossEntropy,
3017 SoftmaxCrossEntropyWithLogits,
3018 SoftmaxCrossEntropyBackward,
3019 ArgMax,
3020 ArgMin,
3021 TopK,
3022 Sample,
3023 RngNormal,
3024 RngUniform,
3025 Conv,
3026 Im2Col,
3027 ConvTranspose2d,
3028 Pool,
3029 GroupedMatMul,
3030 DequantGroupedMatMul,
3031 DequantMoEWeights,
3032 ScatterAdd,
3033 DequantMatMul,
3034 GatedDeltaNet,
3035 SelectiveScan,
3036 Lstm,
3037 Gru,
3038 Rnn,
3039 Mamba2,
3040 FusedSwiGLU,
3041 FusedMatMulBiasAct,
3042 FusedResidualLN,
3043 FusedResidualRmsNorm,
3044 FusedAttentionBlock,
3050 Custom,
3056 Fft,
3062 Scan,
3066 LogMel,
3067 LogMelBackward,
3068 WelchPeaks,
3069 GaussianSplatRender,
3071 GaussianSplatRenderBackward,
3072 GaussianSplatPrepare,
3073 GaussianSplatRasterize,
3074 ]
3075 };
3076
3077 impl Backend for MetalBackend {
3078 fn supported_ops(&self) -> &'static [rlx_ir::OpKind] {
3079 METAL_SUPPORTED_OPS
3080 }
3081
3082 fn compile(&self, graph: Graph, options: &CompileOptions) -> Box<dyn ExecutableGraph> {
3083 use rlx_opt::pass::Pass as _;
3084 let graph = rlx_opt::LowerControlFlow.run(graph);
3088 let dispatch = options.kernel_dispatch;
3089 let graph = rlx_opt::legalize_or_rewrite_for_backend_with_config(
3090 graph,
3091 METAL_SUPPORTED_OPS,
3092 dispatch,
3093 )
3094 .unwrap_or_else(|errors| {
3095 panic!("{}", rlx_opt::format_legalize_error("metal", &errors));
3096 });
3097 let graph = crate::precompile::precompile_cleanup(graph, options);
3098
3099 let (graph, io_manifest) = cpu_low_precision::prepare_f32_exec_graph(graph);
3102 Box::new(MetalExecutableWrapper {
3103 inner: MetalExecutable::compile_with_policy(
3104 graph,
3105 options.policy.clone(),
3106 Some(METAL_SUPPORTED_OPS),
3107 options.rng,
3108 ),
3109 io_manifest,
3110 })
3111 }
3112
3113 fn compile_lir(
3114 &self,
3115 lir: LirModule,
3116 options: &CompileOptions,
3117 ) -> Box<dyn ExecutableGraph> {
3118 use rlx_opt::pass::Pass as _;
3119 let mut graph = lir.into_graph();
3120 graph = rlx_opt::LowerControlFlow.run(graph);
3121 let dispatch = options.kernel_dispatch;
3122 let mut graph = rlx_opt::legalize_or_rewrite_for_backend_with_config(
3123 graph,
3124 METAL_SUPPORTED_OPS,
3125 dispatch,
3126 )
3127 .unwrap_or_else(|errors| {
3128 panic!("{}", rlx_opt::format_legalize_error("metal", &errors));
3129 });
3130 graph = crate::precompile::precompile_cleanup(graph, options);
3131 let (graph, io_manifest) = cpu_low_precision::prepare_f32_exec_graph(graph);
3132 Box::new(MetalExecutableWrapper {
3133 inner: MetalExecutable::compile_from_fused(
3134 graph,
3135 options.policy.clone(),
3136 Some(METAL_SUPPORTED_OPS),
3137 options.rng,
3138 ),
3139 io_manifest,
3140 })
3141 }
3142 }
3143
3144 struct MetalExecutableWrapper {
3145 inner: MetalExecutable,
3146 io_manifest: cpu_low_precision::IoDtypeManifest,
3147 }
3148
3149 unsafe impl Send for MetalExecutableWrapper {}
3150
3151 impl ExecutableGraph for MetalExecutableWrapper {
3152 fn set_param(&mut self, name: &str, data: &[f32]) {
3153 self.inner.set_param(name, data);
3154 }
3155
3156 fn finalize_params(&mut self) {
3157 self.inner.preload_qmatmul_weights();
3158 }
3159
3160 fn run(&mut self, inputs: &[(&str, &[f32])]) -> Vec<Vec<f32>> {
3161 self.inner.run(inputs)
3162 }
3163 fn run_read_outputs(
3164 &mut self,
3165 inputs: &[(&str, &[f32])],
3166 read_indices: Option<&[usize]>,
3167 ) -> Vec<Vec<f32>> {
3168 self.inner.run_read_outputs(inputs, read_indices)
3169 }
3170 fn bind_gpu_handle(&mut self, name: &str, data: &[f32]) -> bool {
3171 self.inner.bind_gpu_handle(name, data)
3172 }
3173 fn has_gpu_handle(&self, name: &str) -> bool {
3174 self.inner.has_gpu_handle(name)
3175 }
3176 fn set_gpu_handle_feed(&mut self, handle_name: &str, output_index: usize) -> bool {
3177 self.inner.set_gpu_handle_feed(handle_name, output_index);
3178 true
3179 }
3180 fn read_gpu_handle(&self, name: &str) -> Option<Vec<f32>> {
3181 self.inner.read_gpu_handle(name)
3182 }
3183 fn register_kv_row_feed(&mut self, handle_name: &str, output_index: usize) -> bool {
3184 self.inner.register_kv_row_feed(handle_name, output_index);
3185 true
3186 }
3187 fn feed_kv_row(&mut self, src_row: usize, dst_row: usize, row_elems: usize) -> bool {
3188 self.inner.feed_kv_row(src_row, dst_row, row_elems);
3189 true
3190 }
3191 fn read_output_row(
3192 &self,
3193 out_idx: usize,
3194 row: usize,
3195 row_inner: usize,
3196 ) -> Option<Vec<f32>> {
3197 Some(self.inner.read_graph_output_row(out_idx, row, row_inner))
3198 }
3199 fn run_slots(&mut self, inputs: &[&[f32]]) -> &[(usize, usize)] {
3200 self.inner.run_slots(inputs)
3201 }
3202 fn arena_ptr(&self) -> *const u8 {
3203 self.inner.arena_ptr()
3204 }
3205 fn commit_no_wait(&mut self, inputs: &[(&str, &[f32])]) {
3206 self.inner.commit_no_wait(inputs);
3207 }
3208 fn sync_pending(&mut self) {
3209 self.inner.sync_pending();
3210 }
3211 fn run_pipelined(&mut self, input_sets: &[Vec<(&str, &[f32])>]) -> Vec<Vec<Vec<f32>>> {
3212 self.inner.run_pipelined(input_sets)
3213 }
3214 fn set_active_extent(&mut self, extent: Option<(usize, usize)>) {
3215 self.inner.set_active_extent(extent);
3216 }
3217
3218 fn set_rng(&mut self, rng: rlx_ir::RngOptions) {
3219 self.inner.set_rng(rng);
3220 }
3221
3222 fn rng(&self) -> rlx_ir::RngOptions {
3223 self.inner.rng()
3224 }
3225
3226 fn set_param_typed(&mut self, name: &str, data: &[u8], dtype: rlx_ir::DType) {
3232 if matches!(
3233 dtype,
3234 rlx_ir::DType::U8
3235 | rlx_ir::DType::I8
3236 | rlx_ir::DType::I32
3237 | rlx_ir::DType::I64
3238 | rlx_ir::DType::U32
3239 | rlx_ir::DType::F64
3240 ) {
3241 self.inner.set_param_bytes(name, data);
3242 return;
3243 }
3244 if dtype == rlx_ir::DType::F32 {
3245 let n = data.len() / 4;
3246 let s = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const f32, n) };
3247 self.inner.set_param(name, s);
3248 } else {
3249 let f32_buf = super::widen_bytes_to_f32(data, dtype);
3250 self.inner.set_param(name, &f32_buf);
3251 }
3252 }
3253
3254 fn run_typed(
3258 &mut self,
3259 inputs: &[(&str, &[u8], rlx_ir::DType)],
3260 ) -> Vec<(Vec<u8>, rlx_ir::DType)> {
3261 self.inner.run_typed(inputs)
3262 }
3263
3264 fn copy_params_from(&mut self, src: &dyn ExecutableGraph) -> bool {
3265 let Some(src_any) = src.executable_as_any() else {
3266 return false;
3267 };
3268 let Some(src_wrap) = src_any.downcast_ref::<MetalExecutableWrapper>() else {
3269 return false;
3270 };
3271 let Some(dst_any) = self.executable_as_any_mut() else {
3272 return false;
3273 };
3274 let Some(dst_wrap) = dst_any.downcast_mut::<MetalExecutableWrapper>() else {
3275 return false;
3276 };
3277 dst_wrap.inner.copy_params_from(&src_wrap.inner)
3278 }
3279
3280 fn executable_as_any(&self) -> Option<&dyn std::any::Any> {
3281 Some(self)
3282 }
3283
3284 fn executable_as_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
3285 Some(self)
3286 }
3287
3288 fn clone_box(&self) -> Box<dyn ExecutableGraph> {
3289 Box::new(MetalExecutableWrapper {
3290 inner: self.inner.clone_for_cache(),
3291 io_manifest: self.io_manifest.clone(),
3292 })
3293 }
3294 }
3295}
3296
3297#[cfg(feature = "cuda")]
3300pub mod cuda_backend {
3301 use super::*;
3302 use rlx_cuda::backend::CudaExecutable;
3303
3304 pub struct CudaBackend;
3305
3306 const CUDA_SUPPORTED_OPS: &[rlx_ir::OpKind] = {
3315 use rlx_ir::OpKind::*;
3316 &[
3317 Input,
3318 Param,
3319 Constant,
3320 Activation,
3321 Cast,
3322 StopGradient,
3323 Binary,
3324 Compare,
3325 Where,
3326 ElementwiseRegion,
3327 TransformRegion,
3328 BatchElementwiseRegion,
3329 MatMul,
3330 ScaledMatMul,
3331 ScaledQuantize,
3332 ScaledQuantScale,
3333 ScaledDequantize,
3334 DotGeneral,
3335 LayerNorm,
3336 LayerNorm2d,
3337 GroupNorm,
3338 ResizeNearest2x,
3339 AxialRope2d,
3340 Reverse,
3341 ArgMax,
3342 ArgMin,
3343 RmsNorm,
3344 Attention,
3345 AttentionBackward,
3346 RmsNormBackwardInput,
3347 RmsNormBackwardGamma,
3348 RmsNormBackwardBeta,
3349 RopeBackward,
3350 CumsumBackward,
3351 GatherBackward,
3352 Conv2dBackwardInput,
3353 Conv2dBackwardWeight,
3354 MaxPool2dBackward,
3355 Rope,
3356 Reshape,
3357 Transpose,
3358 Narrow,
3359 Concat,
3360 Expand,
3361 Gather,
3362 Reduce,
3363 Softmax,
3364 Cumsum,
3365 TopK,
3366 Sample,
3367 Conv,
3368 ConvTranspose2d,
3369 Pool,
3370 GroupedMatMul,
3371 DequantGroupedMatMul,
3372 DequantMoEWeights,
3373 ScatterAdd,
3374 DequantMatMul,
3375 SelectiveScan,
3376 Lstm,
3377 Scan,
3380 FusedMatMulBiasAct,
3381 FusedResidualLN,
3382 FusedResidualRmsNorm,
3383 FusedAttentionBlock,
3387 GaussianSplatRender,
3388 GaussianSplatRenderBackward,
3389 GaussianSplatPrepare,
3390 GaussianSplatRasterize,
3391 Custom,
3392 Fft,
3393 LogMel,
3394 LogMelBackward,
3395 WelchPeaks,
3396 Im2Col,
3397 RngNormal,
3398 RngUniform,
3399 ]
3400 };
3401
3402 impl Backend for CudaBackend {
3403 fn supported_ops(&self) -> &'static [rlx_ir::OpKind] {
3404 CUDA_SUPPORTED_OPS
3405 }
3406
3407 fn compile(&self, graph: Graph, options: &CompileOptions) -> Box<dyn ExecutableGraph> {
3408 use rlx_opt::pass::Pass as _;
3409 let graph = rlx_cuda::unfuse::unfuse(graph);
3412 let graph = rlx_opt::legalize_or_rewrite_for_backend(graph, CUDA_SUPPORTED_OPS)
3413 .unwrap_or_else(|errors| {
3414 panic!("{}", rlx_opt::format_legalize_error("cuda", &errors));
3415 });
3416 let graph = crate::precompile::precompile_cleanup(graph, options);
3417 let graph = rlx_opt::LegalizeBroadcast.run(graph);
3419 let compile_result = crate::stages::compile_graph_stages_for_backend(
3421 rlx_driver::Device::Cuda,
3422 graph,
3423 options,
3424 CUDA_SUPPORTED_OPS,
3425 );
3426 crate::stages::maybe_log_fusion(&compile_result.fusion);
3427 let graph = compile_result.lir.into_graph();
3428 let graph = match options.policy.clone() {
3429 Some(p) => rlx_opt::AutoMixedPrecision::new(p).run(graph),
3430 None => graph,
3431 };
3432 let (graph, io_manifest) = cpu_low_precision::prepare_f32_exec_graph(graph);
3433 Box::new(CudaExecutableWrapper {
3434 inner: CudaExecutable::compile_rng(graph, options.rng),
3435 io_manifest,
3436 })
3437 }
3438
3439 fn compile_lir(
3440 &self,
3441 lir: LirModule,
3442 options: &CompileOptions,
3443 ) -> Box<dyn ExecutableGraph> {
3444 use rlx_opt::pass::Pass as _;
3445 let graph = rlx_opt::LegalizeBroadcast.run(lir.into_graph());
3446 let (graph, io_manifest) =
3447 cpu_low_precision::prepare_f32_exec_graph(prepare_fused_graph(
3448 rlx_cuda::unfuse::unfuse(graph),
3449 options,
3450 CUDA_SUPPORTED_OPS,
3451 "cuda",
3452 ));
3453 Box::new(CudaExecutableWrapper {
3454 inner: CudaExecutable::compile_rng(graph, options.rng),
3455 io_manifest,
3456 })
3457 }
3458 }
3459
3460 struct CudaExecutableWrapper {
3461 inner: CudaExecutable,
3462 io_manifest: cpu_low_precision::IoDtypeManifest,
3463 }
3464
3465 unsafe impl Send for CudaExecutableWrapper {}
3470
3471 impl ExecutableGraph for CudaExecutableWrapper {
3472 fn set_param(&mut self, name: &str, data: &[f32]) {
3473 self.inner.set_param(name, data);
3474 }
3475 fn run(&mut self, inputs: &[(&str, &[f32])]) -> Vec<Vec<f32>> {
3476 self.inner.run(inputs)
3477 }
3478 fn run_read_outputs(
3479 &mut self,
3480 inputs: &[(&str, &[f32])],
3481 read_indices: Option<&[usize]>,
3482 ) -> Vec<Vec<f32>> {
3483 self.inner.run_read_outputs(inputs, read_indices)
3484 }
3485 fn bind_gpu_handle(&mut self, name: &str, data: &[f32]) -> bool {
3486 self.inner.bind_gpu_handle(name, data)
3487 }
3488 fn has_gpu_handle(&self, name: &str) -> bool {
3489 self.inner.has_gpu_handle(name)
3490 }
3491 fn set_gpu_handle_feed(&mut self, handle_name: &str, output_index: usize) -> bool {
3492 self.inner.set_gpu_handle_feed(handle_name, output_index);
3493 true
3494 }
3495 fn read_gpu_handle(&self, name: &str) -> Option<Vec<f32>> {
3496 self.inner.read_gpu_handle(name)
3497 }
3498 fn register_kv_row_feed(&mut self, handle_name: &str, output_index: usize) -> bool {
3499 self.inner.register_kv_row_feed(handle_name, output_index);
3500 true
3501 }
3502 fn feed_kv_row(&mut self, src_row: usize, dst_row: usize, row_elems: usize) -> bool {
3503 self.inner.feed_kv_row(src_row, dst_row, row_elems);
3504 true
3505 }
3506 fn read_output_row(
3507 &self,
3508 out_idx: usize,
3509 row: usize,
3510 row_inner: usize,
3511 ) -> Option<Vec<f32>> {
3512 self.inner.read_output_row(out_idx, row, row_inner)
3513 }
3514 fn read_gpu_handle_row(
3515 &self,
3516 name: &str,
3517 row: usize,
3518 row_inner: usize,
3519 ) -> Option<Vec<f32>> {
3520 self.inner.read_gpu_handle_row(name, row, row_inner)
3521 }
3522 fn prepare_resident_gpu_handle(&mut self, name: &str) -> bool {
3523 self.inner.prepare_resident_gpu_handle(name)
3524 }
3525 fn stage_bound_gpu_handles_to_arena(&mut self) {
3526 self.inner.stage_bound_gpu_handles_to_arena();
3527 }
3528 fn seed_resident_kv_prefix_from(
3529 &mut self,
3530 src: &dyn ExecutableGraph,
3531 prefix_tokens: usize,
3532 outgoing_upper: usize,
3533 kv_dim: usize,
3534 n_layers: usize,
3535 ) -> bool {
3536 let Some(dst_exe) = self.cuda_executable_for_kv_seed() else {
3537 return false;
3538 };
3539 let Some(src_exe) = src.cuda_executable_for_kv_seed_ref() else {
3540 return false;
3541 };
3542 dst_exe.seed_resident_kv_prefix_from(
3543 src_exe,
3544 prefix_tokens,
3545 outgoing_upper,
3546 kv_dim,
3547 n_layers,
3548 )
3549 }
3550 fn copy_resident_kv_rows_from(
3551 &mut self,
3552 src: &dyn ExecutableGraph,
3553 from_row: usize,
3554 to_row: usize,
3555 outgoing_upper: usize,
3556 kv_dim: usize,
3557 n_layers: usize,
3558 ) -> bool {
3559 let Some(dst_exe) = self.cuda_executable_for_kv_seed() else {
3560 return false;
3561 };
3562 let Some(src_exe) = src.cuda_executable_for_kv_seed_ref() else {
3563 return false;
3564 };
3565 dst_exe.copy_resident_kv_rows_from(
3566 src_exe,
3567 from_row,
3568 to_row,
3569 outgoing_upper,
3570 kv_dim,
3571 n_layers,
3572 )
3573 }
3574 fn cuda_executable_for_kv_seed(
3575 &mut self,
3576 ) -> Option<&mut rlx_cuda::backend::CudaExecutable> {
3577 Some(&mut self.inner)
3578 }
3579 fn cuda_executable_for_kv_seed_ref(&self) -> Option<&rlx_cuda::backend::CudaExecutable> {
3580 Some(&self.inner)
3581 }
3582 fn set_active_extent(&mut self, extent: Option<(usize, usize)>) {
3583 self.inner.set_active_extent(extent);
3584 }
3585
3586 fn set_rng(&mut self, rng: rlx_ir::RngOptions) {
3587 self.inner.set_rng(rng);
3588 }
3589
3590 fn rng(&self) -> rlx_ir::RngOptions {
3591 self.inner.rng()
3592 }
3593
3594 fn run_slots(&mut self, inputs: &[&[f32]]) -> &[(usize, usize)] {
3595 self.inner.run_slots(inputs)
3596 }
3597
3598 fn arena_ptr(&self) -> *const u8 {
3599 self.inner.arena_ptr()
3600 }
3601
3602 fn set_param_typed(&mut self, name: &str, data: &[u8], dtype: rlx_ir::DType) {
3607 if matches!(dtype, rlx_ir::DType::U8 | rlx_ir::DType::I8) {
3608 self.inner.set_param_bytes(name, data);
3609 return;
3610 }
3611 if dtype == rlx_ir::DType::F32 {
3612 let n = data.len() / 4;
3613 let s = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const f32, n) };
3614 self.inner.set_param(name, s);
3615 } else {
3616 let f32_buf = super::widen_bytes_to_f32(data, dtype);
3617 self.inner.set_param(name, &f32_buf);
3618 }
3619 }
3620
3621 fn run_typed(
3624 &mut self,
3625 inputs: &[(&str, &[u8], rlx_ir::DType)],
3626 ) -> Vec<(Vec<u8>, rlx_ir::DType)> {
3627 let mut owned: Vec<(String, Vec<f32>)> = Vec::with_capacity(inputs.len());
3628 for (name, data, dt) in inputs {
3629 let v = super::widen_bytes_to_f32(data, *dt);
3630 owned.push((name.to_string(), v));
3631 }
3632 let refs: Vec<(&str, &[f32])> = owned
3633 .iter()
3634 .map(|(n, d)| (n.as_str(), d.as_slice()))
3635 .collect();
3636 let dtypes =
3637 super::declared_output_dtypes(&self.io_manifest, self.inner.output_dtypes());
3638 let outs = self.inner.run(&refs);
3639 outs.into_iter()
3640 .zip(
3641 dtypes
3642 .into_iter()
3643 .chain(std::iter::repeat(rlx_ir::DType::F32)),
3644 )
3645 .map(|(v, dt)| (super::narrow_f32_to_bytes(&v, dt), dt))
3646 .collect()
3647 }
3648
3649 fn clone_box(&self) -> Box<dyn ExecutableGraph> {
3650 Box::new(CudaExecutableWrapper {
3651 inner: self.inner.clone_for_cache(),
3652 io_manifest: self.io_manifest.clone(),
3653 })
3654 }
3655 }
3656}
3657
3658#[cfg(feature = "rocm")]
3661pub mod rocm_backend {
3662 use super::*;
3663 use rlx_rocm::backend::RocmExecutable;
3664
3665 pub struct RocmBackend;
3666
3667 const ROCM_SUPPORTED_OPS: &[rlx_ir::OpKind] = {
3670 use rlx_ir::OpKind::*;
3671 &[
3672 Input,
3673 Param,
3674 Constant,
3675 Activation,
3676 Cast,
3677 StopGradient,
3678 Binary,
3679 Compare,
3680 Where,
3681 ElementwiseRegion,
3682 TransformRegion,
3683 BatchElementwiseRegion,
3684 MatMul,
3685 ScaledMatMul,
3686 ScaledQuantize,
3687 ScaledQuantScale,
3688 ScaledDequantize,
3689 DotGeneral,
3690 LayerNorm,
3691 LayerNorm2d,
3692 GroupNorm,
3693 ResizeNearest2x,
3694 AxialRope2d,
3695 Reverse,
3696 ArgMax,
3697 ArgMin,
3698 RmsNorm,
3699 Attention,
3700 AttentionBackward,
3701 RmsNormBackwardInput,
3702 RmsNormBackwardGamma,
3703 RmsNormBackwardBeta,
3704 RopeBackward,
3705 CumsumBackward,
3706 GatherBackward,
3707 Rope,
3708 Reshape,
3709 Transpose,
3710 Narrow,
3711 Concat,
3712 Expand,
3713 Gather,
3714 Reduce,
3715 Softmax,
3716 Cumsum,
3717 TopK,
3718 Sample,
3719 Conv,
3720 ConvTranspose2d,
3721 Pool,
3722 GroupedMatMul,
3723 DequantGroupedMatMul,
3724 DequantMoEWeights,
3725 ScatterAdd,
3726 DequantMatMul,
3727 SelectiveScan,
3728 Lstm,
3729 Scan,
3732 FusedMatMulBiasAct,
3733 FusedResidualLN,
3734 FusedResidualRmsNorm,
3735 FusedAttentionBlock,
3739 GaussianSplatRender,
3740 GaussianSplatRenderBackward,
3741 GaussianSplatPrepare,
3742 GaussianSplatRasterize,
3743 Custom,
3744 Fft,
3745 LogMel,
3746 LogMelBackward,
3747 WelchPeaks,
3748 Im2Col,
3749 RngNormal,
3750 RngUniform,
3751 ]
3752 };
3753
3754 impl Backend for RocmBackend {
3755 fn supported_ops(&self) -> &'static [rlx_ir::OpKind] {
3756 ROCM_SUPPORTED_OPS
3757 }
3758
3759 fn compile(&self, graph: Graph, options: &CompileOptions) -> Box<dyn ExecutableGraph> {
3760 use rlx_opt::pass::Pass as _;
3761 let graph = rlx_rocm::unfuse::unfuse(graph);
3762 let graph = rlx_opt::legalize_or_rewrite_for_backend(graph, ROCM_SUPPORTED_OPS)
3763 .unwrap_or_else(|errors| {
3764 panic!("{}", rlx_opt::format_legalize_error("rocm", &errors));
3765 });
3766 let graph = crate::precompile::precompile_cleanup(graph, options);
3767 let graph = rlx_opt::LegalizeBroadcast.run(graph);
3768 let compile_result = crate::stages::compile_graph_stages_for_backend(
3769 rlx_driver::Device::Rocm,
3770 graph,
3771 options,
3772 ROCM_SUPPORTED_OPS,
3773 );
3774 crate::stages::maybe_log_fusion(&compile_result.fusion);
3775 let graph = compile_result.lir.into_graph();
3776 let graph = match options.policy.clone() {
3777 Some(p) => rlx_opt::AutoMixedPrecision::new(p).run(graph),
3778 None => graph,
3779 };
3780 let (graph, io_manifest) = cpu_low_precision::prepare_f32_exec_graph(graph);
3781 Box::new(RocmExecutableWrapper {
3782 inner: RocmExecutable::compile_rng(graph, options.rng),
3783 io_manifest,
3784 })
3785 }
3786
3787 fn compile_lir(
3788 &self,
3789 lir: LirModule,
3790 options: &CompileOptions,
3791 ) -> Box<dyn ExecutableGraph> {
3792 let (graph, io_manifest) =
3793 cpu_low_precision::prepare_f32_exec_graph(prepare_fused_graph(
3794 rlx_rocm::unfuse::unfuse(lir.into_graph()),
3795 options,
3796 ROCM_SUPPORTED_OPS,
3797 "rocm",
3798 ));
3799 Box::new(RocmExecutableWrapper {
3800 inner: RocmExecutable::compile_rng(graph, options.rng),
3801 io_manifest,
3802 })
3803 }
3804 }
3805
3806 struct RocmExecutableWrapper {
3807 inner: RocmExecutable,
3808 io_manifest: cpu_low_precision::IoDtypeManifest,
3809 }
3810
3811 unsafe impl Send for RocmExecutableWrapper {}
3815
3816 impl ExecutableGraph for RocmExecutableWrapper {
3817 fn set_param(&mut self, name: &str, data: &[f32]) {
3818 self.inner.set_param(name, data);
3819 }
3820 fn run(&mut self, inputs: &[(&str, &[f32])]) -> Vec<Vec<f32>> {
3821 self.inner.run(inputs)
3822 }
3823 fn run_read_outputs(
3824 &mut self,
3825 inputs: &[(&str, &[f32])],
3826 read_indices: Option<&[usize]>,
3827 ) -> Vec<Vec<f32>> {
3828 self.inner.run_read_outputs(inputs, read_indices)
3829 }
3830 fn bind_gpu_handle(&mut self, name: &str, data: &[f32]) -> bool {
3831 self.inner.bind_gpu_handle(name, data)
3832 }
3833 fn has_gpu_handle(&self, name: &str) -> bool {
3834 self.inner.has_gpu_handle(name)
3835 }
3836 fn set_gpu_handle_feed(&mut self, handle_name: &str, output_index: usize) -> bool {
3837 self.inner.set_gpu_handle_feed(handle_name, output_index);
3838 true
3839 }
3840 fn read_gpu_handle(&self, name: &str) -> Option<Vec<f32>> {
3841 self.inner.read_gpu_handle(name)
3842 }
3843 fn run_slots(&mut self, inputs: &[&[f32]]) -> &[(usize, usize)] {
3844 self.inner.run_slots(inputs)
3845 }
3846 fn arena_ptr(&self) -> *const u8 {
3847 self.inner.arena_ptr()
3848 }
3849 fn set_active_extent(&mut self, extent: Option<(usize, usize)>) {
3850 self.inner.set_active_extent(extent);
3851 }
3852
3853 fn set_rng(&mut self, rng: rlx_ir::RngOptions) {
3854 self.inner.set_rng(rng);
3855 }
3856
3857 fn rng(&self) -> rlx_ir::RngOptions {
3858 self.inner.rng()
3859 }
3860
3861 fn set_param_typed(&mut self, name: &str, data: &[u8], dtype: rlx_ir::DType) {
3866 if matches!(dtype, rlx_ir::DType::U8 | rlx_ir::DType::I8) {
3867 self.inner.set_param_bytes(name, data);
3868 return;
3869 }
3870 if dtype == rlx_ir::DType::F32 {
3871 let n = data.len() / 4;
3872 let s = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const f32, n) };
3873 self.inner.set_param(name, s);
3874 } else {
3875 let f32_buf = super::widen_bytes_to_f32(data, dtype);
3876 self.inner.set_param(name, &f32_buf);
3877 }
3878 }
3879
3880 fn run_typed(
3883 &mut self,
3884 inputs: &[(&str, &[u8], rlx_ir::DType)],
3885 ) -> Vec<(Vec<u8>, rlx_ir::DType)> {
3886 let mut owned: Vec<(String, Vec<f32>)> = Vec::with_capacity(inputs.len());
3887 for (name, data, dt) in inputs {
3888 let v = super::widen_bytes_to_f32(data, *dt);
3889 owned.push((name.to_string(), v));
3890 }
3891 let refs: Vec<(&str, &[f32])> = owned
3892 .iter()
3893 .map(|(n, d)| (n.as_str(), d.as_slice()))
3894 .collect();
3895 let dtypes =
3896 super::declared_output_dtypes(&self.io_manifest, self.inner.output_dtypes());
3897 let outs = self.inner.run(&refs);
3898 outs.into_iter()
3899 .zip(
3900 dtypes
3901 .into_iter()
3902 .chain(std::iter::repeat(rlx_ir::DType::F32)),
3903 )
3904 .map(|(v, dt)| (super::narrow_f32_to_bytes(&v, dt), dt))
3905 .collect()
3906 }
3907
3908 fn clone_box(&self) -> Box<dyn ExecutableGraph> {
3909 Box::new(RocmExecutableWrapper {
3910 inner: self.inner.clone_for_cache(),
3911 io_manifest: self.io_manifest.clone(),
3912 })
3913 }
3914 }
3915}
3916
3917#[cfg(feature = "tpu")]
3920pub mod tpu_backend {
3921 use super::*;
3922 use rlx_tpu::TpuExecutable;
3923
3924 pub struct TpuBackend;
3925
3926 const TPU_SUPPORTED_OPS: &[rlx_ir::OpKind] = {
3934 use rlx_ir::OpKind::*;
3935 &[
3936 Input,
3937 Param,
3938 Constant,
3939 Activation,
3940 Cast,
3941 StopGradient,
3942 Binary,
3943 Compare,
3944 Where,
3945 ElementwiseRegion,
3946 TransformRegion,
3947 BatchElementwiseRegion,
3948 MatMul,
3949 DotGeneral,
3950 LayerNorm,
3951 RmsNorm,
3952 Attention,
3953 Rope,
3954 Reshape,
3955 Transpose,
3956 Narrow,
3957 Concat,
3958 Expand,
3959 Gather,
3960 Reduce,
3961 Softmax,
3962 Cumsum,
3963 TopK,
3964 Sample,
3965 Conv,
3966 Pool,
3967 GroupedMatMul,
3968 DequantGroupedMatMul,
3969 DequantMoEWeights,
3970 ScatterAdd,
3971 DequantMatMul,
3972 SelectiveScan,
3973 QMatMul,
3975 QConv2d,
3976 Quantize,
3977 Dequantize,
3978 FusedMatMulBiasAct,
3979 FusedResidualLN,
3980 FusedResidualRmsNorm,
3981 FusedAttentionBlock,
3984 Fft,
3985 LogMel,
3986 LogMelBackward,
3987 WelchPeaks,
3988 RngNormal,
3989 RngUniform,
3990 ]
3992 };
3993
3994 impl Backend for TpuBackend {
3995 fn supported_ops(&self) -> &'static [rlx_ir::OpKind] {
3996 TPU_SUPPORTED_OPS
3997 }
3998
3999 fn compile(&self, graph: Graph, options: &CompileOptions) -> Box<dyn ExecutableGraph> {
4000 let graph = rlx_opt::legalize_or_rewrite_for_backend_with_config(
4001 graph,
4002 TPU_SUPPORTED_OPS,
4003 options.kernel_dispatch,
4004 )
4005 .unwrap_or_else(|errors| {
4006 panic!("{}", rlx_opt::format_legalize_error("tpu", &errors));
4007 });
4008 use rlx_opt::pass::Pass as _;
4024 let policy = options
4025 .policy
4026 .clone()
4027 .unwrap_or(rlx_opt::PrecisionPolicy::AutoMixedBf16);
4028 let graph = rlx_opt::AutoMixedPrecision::new(policy).run(graph);
4029 let _ = options.dce;
4030 let _ = options.constant_folding;
4031 Box::new(TpuExecutableWrapper {
4032 inner: TpuExecutable::compile_rng_with_param_bytes(
4033 graph,
4034 options.rng,
4035 options.quant_param_bindings.as_ref(),
4036 ),
4037 })
4038 }
4039 }
4040
4041 struct TpuExecutableWrapper {
4042 inner: TpuExecutable,
4043 }
4044
4045 unsafe impl Send for TpuExecutableWrapper {}
4049
4050 impl ExecutableGraph for TpuExecutableWrapper {
4051 fn set_param(&mut self, name: &str, data: &[f32]) {
4052 self.inner.set_param(name, data);
4053 }
4054 fn run(&mut self, inputs: &[(&str, &[f32])]) -> Vec<Vec<f32>> {
4055 self.inner.run(inputs)
4056 }
4057
4058 fn set_param_typed(&mut self, name: &str, data: &[u8], dtype: rlx_ir::DType) {
4063 if dtype == rlx_ir::DType::F32 {
4064 let n = data.len() / 4;
4065 let s = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const f32, n) };
4066 self.inner.set_param(name, s);
4067 } else {
4068 let f32_buf = super::widen_bytes_to_f32(data, dtype);
4069 self.inner.set_param(name, &f32_buf);
4070 }
4071 }
4072
4073 fn run_typed(
4074 &mut self,
4075 inputs: &[(&str, &[u8], rlx_ir::DType)],
4076 ) -> Vec<(Vec<u8>, rlx_ir::DType)> {
4077 let mut owned: Vec<(String, Vec<f32>)> = Vec::with_capacity(inputs.len());
4078 for (name, data, dt) in inputs {
4079 let v = super::widen_bytes_to_f32(data, *dt);
4080 owned.push((name.to_string(), v));
4081 }
4082 let refs: Vec<(&str, &[f32])> = owned
4083 .iter()
4084 .map(|(n, d)| (n.as_str(), d.as_slice()))
4085 .collect();
4086 let dtypes = self.inner.output_dtypes();
4087 let outs = self.inner.run(&refs);
4088 outs.into_iter()
4089 .zip(
4090 dtypes
4091 .into_iter()
4092 .chain(std::iter::repeat(rlx_ir::DType::F32)),
4093 )
4094 .map(|(v, dt)| (super::narrow_f32_to_bytes(&v, dt), dt))
4095 .collect()
4096 }
4097
4098 fn clone_box(&self) -> Box<dyn ExecutableGraph> {
4099 Box::new(TpuExecutableWrapper {
4100 inner: self.inner.clone_for_cache(),
4101 })
4102 }
4103 }
4104}
4105
4106#[cfg(feature = "qnn")]
4111pub mod qnn_backend {
4112 use super::*;
4113 use rlx_qnn::runtime::QnnExecutable;
4114
4115 pub struct QnnBackend;
4116
4117 impl Backend for QnnBackend {
4118 fn compile(&self, graph: Graph, _options: &CompileOptions) -> Box<dyn ExecutableGraph> {
4125 let exec = QnnExecutable::compile_graph(&graph)
4126 .unwrap_or_else(|e| panic!("rlx-qnn compile failed: {e}"));
4127 Box::new(QnnExecutableWrapper { inner: exec })
4128 }
4129
4130 fn compile_lir(
4131 &self,
4132 lir: LirModule,
4133 options: &CompileOptions,
4134 ) -> Box<dyn ExecutableGraph> {
4135 self.compile(lir.into_graph(), options)
4138 }
4139 }
4140
4141 struct QnnExecutableWrapper {
4142 inner: QnnExecutable,
4143 }
4144
4145 impl ExecutableGraph for QnnExecutableWrapper {
4146 fn set_param(&mut self, name: &str, data: &[f32]) {
4147 self.inner.set_param(name, data);
4150 }
4151
4152 fn run(&mut self, inputs: &[(&str, &[f32])]) -> Vec<Vec<f32>> {
4153 self.inner
4154 .run(inputs)
4155 .unwrap_or_else(|e| panic!("rlx-qnn run failed: {e}"))
4156 }
4157 }
4158}