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 Pool,
717 GroupedMatMul,
718 DequantGroupedMatMul,
719 DequantMoEWeights,
720 ScatterAdd,
721 LoraMatMul,
722 DequantMatMul,
723 ScaledMatMul,
724 ScaledQuantize,
725 ScaledQuantScale,
726 ScaledDequantize,
727 SelectiveScan,
728 GatedDeltaNet,
729 Lstm,
730 Gru,
731 Rnn,
732 Mamba2,
733 FusedSwiGLU,
734 FusedMatMulBiasAct,
735 FusedResidualLN,
736 FusedResidualRmsNorm,
737 FusedAttentionBlock,
738 ReluBackward,
743 ActivationBackward,
744 FakeQuantize,
745 FakeQuantizeBackward,
746 FakeQuantizeLSQ,
748 FakeQuantizeLSQBackwardX,
749 FakeQuantizeLSQBackwardScale,
750 MaxPool2dBackward,
751 Conv2dBackwardInput,
752 Conv2dBackwardWeight,
753 SoftmaxCrossEntropy,
754 SoftmaxCrossEntropyWithLogits,
755 SoftmaxCrossEntropyBackward,
756 AttentionBackward,
757 LayerNormBackwardInput,
758 LayerNormBackwardGamma,
759 BatchNormInferenceBackwardInput,
760 BatchNormInferenceBackwardGamma,
761 BatchNormInferenceBackwardBeta,
762 GroupNormBackwardInput,
764 GroupNormBackwardGamma,
765 GroupNormBackwardBeta,
766 RmsNormBackwardInput,
767 RmsNormBackwardGamma,
768 RmsNormBackwardBeta,
769 RopeBackward,
770 CumsumBackward,
771 GatherBackward,
772 GaussianSplatRender,
774 GaussianSplatRenderBackward,
775 GaussianSplatPrepare,
776 GaussianSplatRasterize,
777 Custom,
781 CustomFn,
785 Fft,
789 FftButterflyStage,
790 LogMel,
791 LogMelBackward,
792 WelchPeaks,
793 ComplexNormSq,
798 ComplexNormSqBackward,
799 Conjugate,
800 ]
801 };
802
803 impl Backend for CpuBackend {
804 fn supported_ops(&self) -> &'static [rlx_ir::OpKind] {
805 CPU_SUPPORTED_OPS
806 }
807
808 fn compile(&self, graph: Graph, options: &CompileOptions) -> Box<dyn ExecutableGraph> {
809 use rlx_opt::pass::Pass as _;
810 static ONNX_KERNELS: std::sync::Once = std::sync::Once::new();
811 ONNX_KERNELS.call_once(rlx_cpu::onnx_ref::register_onnx_reference_kernels);
812 let graph = rlx_opt::LowerControlFlow.run(graph);
818 if let Err(errors) = rlx_opt::legalize_for_backend(&graph, CPU_SUPPORTED_OPS) {
822 panic!("{}", rlx_opt::format_legalize_error("cpu", &errors));
823 }
824 let policy = options.policy.clone();
825 let _precision = options.precision;
826 let cfg = rlx_cpu::config::RuntimeConfig::global();
827
828 let graph = crate::precompile::precompile_cleanup(graph, options);
829
830 let mut compile_opts = options.clone();
832 compile_opts.arena_alignment = cfg.arena_alignment;
833 let compile_result = crate::stages::compile_graph_stages_for_backend(
834 rlx_driver::Device::Cpu,
835 graph,
836 &compile_opts,
837 CPU_SUPPORTED_OPS,
838 );
839 crate::stages::maybe_log_fusion(&compile_result.fusion);
840 let fused = compile_result.lir.into_graph();
841
842 let fused = match policy {
845 Some(p) => rlx_opt::AutoMixedPrecision::new(p).run(fused),
846 None => fused,
847 };
848
849 let io_manifest = cpu_low_precision::IoDtypeManifest::from_graph(&fused);
850 let exec_graph = if cpu_low_precision::needs_f32_exec(&fused) {
851 cpu_low_precision::promote_to_f32(fused)
852 } else {
853 fused
854 };
855
856 let plan = memory::plan_memory_aligned(&exec_graph, cfg.arena_alignment);
858 if cfg.verbose >= 1 {
859 eprintln!(
860 "[rlx] arena: {} bytes, {} buffers, alignment: {}",
861 plan.arena_size,
862 plan.assignments.len(),
863 cfg.arena_alignment
864 );
865 }
866 Box::new(build_cpu_executable(
867 exec_graph,
868 plan,
869 io_manifest,
870 options.rng,
871 ))
872 }
873
874 fn compile_lir(
875 &self,
876 lir: LirModule,
877 options: &CompileOptions,
878 ) -> Box<dyn ExecutableGraph> {
879 let alignment = lir.buffers.alignment.max(options.arena_alignment);
880 let mut graph = lir.into_graph();
881 {
882 use rlx_opt::pass::Pass as _;
883 graph = rlx_opt::LegalizeBroadcast.run(graph);
884 }
885 if let Some(p) = options.policy.clone() {
886 use rlx_opt::pass::Pass;
887 graph = rlx_opt::AutoMixedPrecision::new(p).run(graph);
888 }
889 let io_manifest = cpu_low_precision::IoDtypeManifest::from_graph(&graph);
890 let promote = cpu_low_precision::needs_f32_exec(&graph);
891 let exec_graph = if promote {
892 cpu_low_precision::promote_to_f32(graph)
893 } else {
894 graph
895 };
896 let plan = memory::plan_memory_aligned(&exec_graph, alignment);
899 let cfg = rlx_cpu::config::RuntimeConfig::global();
900 if cfg.verbose >= 1 {
901 eprintln!(
902 "[rlx] compile_lir: arena {} bytes ({} buffers, alignment {})",
903 plan.arena_size,
904 plan.assignments.len(),
905 alignment,
906 );
907 }
908 Box::new(build_cpu_executable(
909 exec_graph,
910 plan,
911 io_manifest,
912 options.rng,
913 ))
914 }
915 }
916
917 fn build_cpu_executable(
918 graph: Graph,
919 plan: MemoryPlan,
920 io_manifest: cpu_low_precision::IoDtypeManifest,
921 rng: rlx_ir::RngOptions,
922 ) -> CpuExecutable {
923 let mut arena = Arena::from_plan(plan);
924 let mut input_ids = HashMap::new();
925 let mut param_ids = HashMap::new();
926 let mut node_dtypes: HashMap<NodeId, DType> = HashMap::new();
927 for node in graph.nodes() {
928 node_dtypes.insert(node.id, node.shape.dtype());
929 match &node.op {
930 Op::Input { name } => {
931 input_ids.insert(name.clone(), node.id);
932 }
933 Op::Param { name } => {
934 param_ids.insert(name.clone(), node.id);
935 }
936 _ => {}
937 }
938 }
939
940 let schedule = thunk::compile_thunks_with_rng(&graph, &arena, rng);
941
942 let mut input_slots = Vec::new();
943 for node in graph.nodes() {
944 if let Op::Input { name } = &node.op {
945 let off = arena.byte_offset(node.id);
946 let len = node.shape.num_elements().unwrap_or(0);
947 input_slots.push((name.clone(), off, len, node.shape.dtype()));
948 }
949 }
950
951 let output_slots: Vec<(usize, usize)> = graph
952 .outputs
953 .iter()
954 .map(|&id| {
955 let off = arena.byte_offset(id);
956 let len = graph.node(id).shape.num_elements().unwrap_or(0);
957 (off, len)
958 })
959 .collect();
960
961 for node in graph.nodes() {
962 if let Op::Constant { data } = &node.op
963 && arena.has_buffer(node.id)
964 && !data.is_empty()
965 {
966 match node.shape.dtype() {
967 DType::F64
974 | DType::F16
975 | DType::BF16
976 | DType::I64
977 | DType::I32
978 | DType::U32 => {
979 let off = arena.byte_offset(node.id);
980 let buf = arena.raw_buf_mut();
981 let n = buf.len().saturating_sub(off).min(data.len());
982 buf[off..off + n].copy_from_slice(&data[..n]);
983 }
984 _ => {
985 let buf = arena.slice_mut(node.id);
986 let n_floats = data.len() / 4;
987 let n = buf.len().min(n_floats);
988 for i in 0..n {
989 let bytes = [
990 data[i * 4],
991 data[i * 4 + 1],
992 data[i * 4 + 2],
993 data[i * 4 + 3],
994 ];
995 buf[i] = f32::from_le_bytes(bytes);
996 }
997 }
998 }
999 }
1000 }
1001
1002 CpuExecutable {
1003 graph,
1004 arena,
1005 input_ids,
1006 param_ids,
1007 node_dtypes,
1008 io_manifest,
1009 schedule,
1010 input_slots,
1011 output_slots,
1012 handles: HashMap::new(),
1013 active_extent: None,
1014 moe_resident: None,
1015 moe_resident_layers: None,
1016 moe_topk_capture: None,
1017 baseline_written: false,
1018 }
1019 }
1020
1021 #[derive(Clone)]
1022 struct CpuExecutable {
1023 graph: Graph,
1024 arena: Arena,
1025 input_ids: HashMap<String, NodeId>,
1026 param_ids: HashMap<String, NodeId>,
1027 node_dtypes: HashMap<NodeId, DType>,
1030 io_manifest: cpu_low_precision::IoDtypeManifest,
1032 schedule: thunk::ThunkSchedule,
1033 input_slots: Vec<(String, usize, usize, DType)>,
1035 output_slots: Vec<(usize, usize)>,
1037 handles: HashMap<String, Vec<f32>>,
1042 active_extent: Option<(usize, usize)>,
1048 moe_resident: Option<std::sync::Arc<[bool]>>,
1049 moe_resident_layers: Option<std::sync::Arc<Vec<std::sync::Arc<[bool]>>>>,
1050 moe_topk_capture: Option<std::sync::Arc<rlx_cpu::moe_topk_capture::MoeTopkCapture>>,
1051 baseline_written: bool,
1057 }
1058
1059 unsafe impl Send for CpuExecutable {}
1060
1061 impl CpuExecutable {
1062 fn write_input(&mut self, id: NodeId, data: &[f32]) {
1064 let dtype = self.node_dtypes.get(&id).copied().unwrap_or(DType::F32);
1065 let off = self.arena.byte_offset(id);
1066 let buf = self.arena.raw_buf_mut();
1067 let elem_size = dtype.size_bytes();
1068 let max_elems = (buf.len() - off) / elem_size;
1069 unsafe {
1070 write_typed_from_f32(buf.as_mut_ptr().add(off), dtype, data, max_elems);
1071 }
1072 }
1073
1074 fn read_output(&self, id: NodeId) -> Vec<f32> {
1076 let dtype = self.node_dtypes.get(&id).copied().unwrap_or(DType::F32);
1077 let off = self.arena.byte_offset(id);
1078 let buf = self.arena.raw_buf();
1079 let n_elems = self.graph.node(id).shape.num_elements().unwrap_or(0);
1080 unsafe { read_typed_to_f32(buf.as_ptr().add(off), dtype, n_elems) }
1081 }
1082 }
1083
1084 impl ExecutableGraph for CpuExecutable {
1085 fn clone_box(&self) -> Box<dyn ExecutableGraph> {
1086 Box::new(self.clone())
1087 }
1088 fn set_param(&mut self, name: &str, data: &[f32]) {
1089 if let Some(&id) = self.param_ids.get(name)
1094 && self.arena.has_buffer(id)
1095 {
1096 let dtype = self.node_dtypes.get(&id).copied().unwrap_or(DType::F32);
1097 let off = self.arena.byte_offset(id);
1098 let buf = self.arena.raw_buf_mut();
1099 let elem_size = dtype.size_bytes();
1100 let max_elems = (buf.len() - off) / elem_size;
1101 unsafe {
1102 write_typed_from_f32(buf.as_mut_ptr().add(off), dtype, data, max_elems);
1103 }
1104 }
1105 }
1106
1107 fn run(&mut self, inputs: &[(&str, &[f32])]) -> Vec<Vec<f32>> {
1108 self.restore_arena_baseline();
1109 let handle_names: Vec<String> = self.handles.keys().cloned().collect();
1112 for name in &handle_names {
1113 if let Some(&id) = self.input_ids.get(name)
1114 && self.arena.has_buffer(id)
1115 {
1116 let data = self.handles.get(name).cloned().unwrap_or_default();
1117 self.write_input(id, &data);
1118 }
1119 }
1120 for &(name, data) in inputs {
1122 if let Some(&id) = self.input_ids.get(name)
1123 && self.arena.has_buffer(id)
1124 {
1125 self.write_input(id, data);
1126 }
1127 }
1128
1129 let active_used = if let Some((actual, upper)) = self.active_extent {
1134 thunk::execute_thunks_active(
1135 &self.schedule,
1136 self.arena.raw_buf_mut(),
1137 actual,
1138 upper,
1139 )
1140 } else {
1141 false
1142 };
1143 if !active_used {
1144 thunk::execute_thunks(&self.schedule, self.arena.raw_buf_mut());
1146 }
1147
1148 for (idx, &out_id) in self.graph.outputs.iter().enumerate() {
1152 let name = format!("out{idx}");
1153 if self.handles.contains_key(&name) {
1154 let v = self.read_output(out_id);
1155 self.handles.insert(name, v);
1156 }
1157 }
1158
1159 self.graph
1160 .outputs
1161 .iter()
1162 .map(|&out_id| self.read_output(out_id))
1163 .collect()
1164 }
1165
1166 fn run_raw(&mut self, inputs: &[(&str, &[f32])]) -> Vec<(*const f32, usize)> {
1167 self.restore_arena_baseline();
1168 for &(name, data) in inputs {
1170 if let Some(&id) = self.input_ids.get(name)
1171 && self.arena.has_buffer(id)
1172 {
1173 self.write_input(id, data);
1174 }
1175 }
1176 thunk::execute_thunks(&self.schedule, self.arena.raw_buf_mut());
1177 self.graph
1181 .outputs
1182 .iter()
1183 .map(|&out_id| {
1184 let (ptr, len) = self.arena.raw_ptr(out_id);
1185 (ptr as *const f32, len)
1186 })
1187 .collect()
1188 }
1189
1190 fn run_slots(&mut self, inputs: &[&[f32]]) -> &[(usize, usize)] {
1194 self.restore_arena_baseline();
1195 let buf = self.arena.raw_buf_mut();
1196 for (i, &data) in inputs.iter().enumerate() {
1197 if i < self.input_slots.len() {
1198 let (_, off, max_len, dtype) = &self.input_slots[i];
1199 unsafe {
1200 write_typed_from_f32(buf.as_mut_ptr().add(*off), *dtype, data, *max_len);
1201 }
1202 }
1203 }
1204 thunk::execute_thunks(&self.schedule, self.arena.raw_buf_mut());
1205 &self.output_slots
1206 }
1207
1208 fn arena_ptr(&self) -> *const u8 {
1209 self.arena.raw_buf_mut_ptr()
1210 }
1211
1212 fn bind_handle(&mut self, name: &str, data: &[f32]) -> bool {
1213 self.handles.insert(name.to_string(), data.to_vec());
1218 true
1219 }
1220
1221 fn read_handle(&self, name: &str) -> Option<Vec<f32>> {
1222 self.handles.get(name).cloned()
1223 }
1224
1225 fn set_active_extent(&mut self, extent: Option<(usize, usize)>) {
1226 self.active_extent = extent;
1227 }
1228
1229 fn set_rng(&mut self, rng: rlx_ir::RngOptions) {
1230 *self.schedule.rng.write().unwrap() = rng;
1231 }
1232
1233 fn rng(&self) -> rlx_ir::RngOptions {
1234 *self.schedule.rng.read().unwrap()
1235 }
1236
1237 fn set_moe_resident_experts(&mut self, mask: &[bool]) {
1238 self.moe_resident_layers = None;
1239 self.schedule.moe_resident_layers = None;
1240 self.moe_resident = Some(Arc::from(mask));
1241 self.schedule.moe_resident = self.moe_resident.clone();
1242 }
1243
1244 fn set_moe_resident_experts_per_layer(&mut self, masks: &[&[bool]]) {
1245 self.moe_resident = None;
1246 self.schedule.moe_resident = None;
1247 let layers: Vec<Arc<[bool]>> = masks.iter().map(|m| Arc::from(*m)).collect();
1248 let arc = Arc::new(layers);
1249 self.moe_resident_layers = Some(arc.clone());
1250 self.schedule.moe_resident_layers = Some(arc);
1251 }
1252
1253 fn enable_moe_topk_capture(&mut self, num_experts: usize) -> bool {
1254 let cap = rlx_cpu::moe_topk_capture::MoeTopkCapture::new(num_experts);
1255 self.moe_topk_capture = Some(cap.clone());
1256 self.schedule.moe_topk_capture = Some(cap);
1257 true
1258 }
1259
1260 fn take_moe_topk_capture(&mut self) -> Option<Vec<Vec<u32>>> {
1261 let cap = self.moe_topk_capture.as_ref()?;
1262 let layers = cap.take_layers();
1263 if layers.is_empty() {
1264 None
1265 } else {
1266 Some(layers)
1267 }
1268 }
1269
1270 fn take_moe_residency_stats(&mut self) -> Option<crate::MoeResidencyStats> {
1271 rlx_cpu::moe_residency::take_last_forward_stats()
1272 }
1273
1274 fn set_param_typed(&mut self, name: &str, data: &[u8], dtype: rlx_ir::DType) {
1280 if matches!(dtype, DType::F64 | DType::I64 | DType::I32 | DType::U32) {
1281 self.set_param_bytes(name, data, dtype);
1282 return;
1283 }
1284 if matches!(dtype, DType::U8 | DType::I8) {
1288 self.set_param_bytes(name, data, dtype);
1289 return;
1290 }
1291 if dtype == DType::F32 {
1292 let n = data.len() / 4;
1293 let s = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const f32, n) };
1294 self.set_param(name, s);
1295 } else {
1296 let f32_buf = super::widen_bytes_to_f32(data, dtype);
1297 self.set_param(name, &f32_buf);
1298 }
1299 }
1300
1301 fn run_typed(
1313 &mut self,
1314 inputs: &[(&str, &[u8], rlx_ir::DType)],
1315 ) -> Vec<(Vec<u8>, rlx_ir::DType)> {
1316 let all_f64 = !inputs.is_empty() && inputs.iter().all(|(_, _, dt)| *dt == DType::F64);
1321
1322 if all_f64 {
1323 for (name, data, _) in inputs {
1324 if let Some(&id) = self.input_ids.get(*name) {
1325 if !self.arena.has_buffer(id) {
1326 continue;
1327 }
1328 let off = self.arena.byte_offset(id);
1329 let buf = self.arena.raw_buf_mut();
1330 let n = data.len();
1331 debug_assert!(
1332 off + n <= buf.len(),
1333 "run_typed: input '{name}' overflows arena slot"
1334 );
1335 buf[off..off + n].copy_from_slice(data);
1336 }
1337 }
1338 thunk::execute_thunks(&self.schedule, self.arena.raw_buf_mut());
1339 } else {
1340 let mut f32_owned: Vec<(String, Vec<f32>)> = Vec::new();
1345 for (name, data, dt) in inputs {
1346 let direct = matches!(
1347 *dt,
1348 DType::F64 | DType::I32 | DType::I64 | DType::U32 | DType::C64
1349 );
1350 if direct {
1351 if let Some(&id) = self.input_ids.get(*name) {
1352 if !self.arena.has_buffer(id) {
1353 continue;
1354 }
1355 let off = self.arena.byte_offset(id);
1356 let buf = self.arena.raw_buf_mut();
1357 buf[off..off + data.len()].copy_from_slice(data);
1358 }
1359 } else {
1360 let v = super::widen_bytes_to_f32(data, *dt);
1361 f32_owned.push((name.to_string(), v));
1362 }
1363 }
1364 for (name, data) in &f32_owned {
1365 if let Some(&id) = self.input_ids.get(name.as_str()) {
1366 if self.arena.has_buffer(id) {
1367 self.write_input(id, data);
1368 }
1369 }
1370 }
1371 let active_used = if let Some((actual, upper)) = self.active_extent {
1372 thunk::execute_thunks_active(
1373 &self.schedule,
1374 self.arena.raw_buf_mut(),
1375 actual,
1376 upper,
1377 )
1378 } else {
1379 false
1380 };
1381 if !active_used {
1382 thunk::execute_thunks(&self.schedule, self.arena.raw_buf_mut());
1383 }
1384 }
1385
1386 self.graph
1388 .outputs
1389 .iter()
1390 .enumerate()
1391 .map(|(idx, &id)| {
1392 let exec_dtype = self.graph.node(id).shape.dtype();
1393 let declared = self.io_manifest.output_dtype(idx, exec_dtype);
1394 if matches!(
1395 exec_dtype,
1396 DType::F64
1397 | DType::F16
1398 | DType::BF16
1399 | DType::I32
1400 | DType::I64
1401 | DType::U32
1402 | DType::C64
1403 ) {
1404 let n_elems = self.graph.node(id).shape.num_elements().unwrap_or(0);
1405 let n_bytes = n_elems * exec_dtype.size_bytes();
1406 let off = self.arena.byte_offset(id);
1407 let bytes = self.arena.raw_buf()[off..off + n_bytes].to_vec();
1408 return (bytes, declared);
1409 }
1410 let f32_vals = self.read_output(id);
1411 if declared != exec_dtype {
1412 return (super::narrow_f32_to_bytes(&f32_vals, declared), declared);
1413 }
1414 let bytes = f32_vals.iter().flat_map(|v| v.to_le_bytes()).collect();
1415 (bytes, declared)
1416 })
1417 .collect()
1418 }
1419 }
1420
1421 impl CpuExecutable {
1422 fn restore_arena_baseline(&mut self) {
1432 let persistent: std::collections::HashSet<NodeId> = {
1434 let mut s: std::collections::HashSet<NodeId> =
1435 self.param_ids.values().copied().collect();
1436 for node in self.graph.nodes() {
1437 if matches!(node.op, Op::Constant { .. }) {
1438 s.insert(node.id);
1439 }
1440 }
1441 s
1442 };
1443
1444 if !self.baseline_written {
1447 let constants: Vec<(NodeId, DType, Vec<u8>)> = self
1448 .graph
1449 .nodes()
1450 .iter()
1451 .filter_map(|node| {
1452 if let Op::Constant { data } = &node.op
1453 && self.arena.has_buffer(node.id)
1454 && !data.is_empty()
1455 {
1456 Some((node.id, node.shape.dtype(), data.clone()))
1457 } else {
1458 None
1459 }
1460 })
1461 .collect();
1462 for (id, dtype, data) in constants {
1463 self.write_constant_to_arena(id, dtype, &data);
1464 }
1465 self.baseline_written = true;
1466 }
1467
1468 let mut keep: Vec<(usize, usize)> = self
1479 .graph
1480 .nodes()
1481 .iter()
1482 .filter_map(|node| {
1483 let id = node.id;
1484 if !persistent.contains(&id) || !self.arena.has_buffer(id) {
1485 return None;
1486 }
1487 let dtype = self.node_dtypes.get(&id).copied().unwrap_or(DType::F32);
1488 let nbytes = node.shape.num_elements().unwrap_or(0) * dtype.size_bytes();
1489 let off = self.arena.byte_offset(id);
1490 Some((off, off + nbytes))
1491 })
1492 .collect();
1493 keep.sort_unstable();
1494
1495 let buf = self.arena.raw_buf_mut();
1496 let len = buf.len();
1497 let mut cursor = 0usize;
1498 for (start, end) in keep {
1499 let start = start.min(len);
1500 if cursor < start {
1501 buf[cursor..start].fill(0);
1502 }
1503 cursor = cursor.max(end.min(len));
1504 }
1505 if cursor < len {
1506 buf[cursor..len].fill(0);
1507 }
1508 }
1509
1510 fn write_constant_to_arena(&mut self, id: NodeId, dtype: DType, data: &[u8]) {
1511 match dtype {
1512 DType::F64 | DType::F16 | DType::BF16 | DType::U8 | DType::I8 => {
1513 let off = self.arena.byte_offset(id);
1514 let buf = self.arena.raw_buf_mut();
1515 let n = buf.len().saturating_sub(off).min(data.len());
1516 buf[off..off + n].copy_from_slice(&data[..n]);
1517 }
1518 _ => {
1519 let buf = self.arena.slice_mut(id);
1520 let n_floats = data.len() / 4;
1521 let n = buf.len().min(n_floats);
1522 for i in 0..n {
1523 let bytes = [
1524 data[i * 4],
1525 data[i * 4 + 1],
1526 data[i * 4 + 2],
1527 data[i * 4 + 3],
1528 ];
1529 buf[i] = f32::from_le_bytes(bytes);
1530 }
1531 }
1532 }
1533 }
1534
1535 fn set_param_bytes(&mut self, name: &str, data: &[u8], _dtype: rlx_ir::DType) {
1541 self.write_param_bytes_to_arena(name, data);
1543 }
1544
1545 fn write_param_bytes_to_arena(&mut self, name: &str, data: &[u8]) {
1546 if let Some(&id) = self.param_ids.get(name)
1547 && self.arena.has_buffer(id)
1548 {
1549 let off = self.arena.byte_offset(id);
1550 let buf = self.arena.raw_buf_mut();
1551 debug_assert!(
1552 off + data.len() <= buf.len(),
1553 "set_param_bytes: '{name}' would overflow arena slot"
1554 );
1555 buf[off..off + data.len()].copy_from_slice(data);
1556 }
1557 }
1558 }
1559}
1560
1561#[cfg(feature = "gpu")]
1566pub mod wgpu_backend {
1567 use super::*;
1568 use rlx_ir::OpKind;
1569 use rlx_wgpu::backend::WgpuExecutable;
1570
1571 pub struct WgpuBackend;
1572
1573 const WGPU_SUPPORTED_OPS: &[OpKind] = &[
1579 OpKind::Input,
1580 OpKind::Param,
1581 OpKind::Constant,
1582 OpKind::Activation,
1583 OpKind::Cast,
1584 OpKind::StopGradient,
1585 OpKind::Binary,
1586 OpKind::Compare,
1587 OpKind::Where,
1588 OpKind::Fma,
1589 OpKind::ElementwiseRegion,
1590 OpKind::TransformRegion,
1591 OpKind::BatchElementwiseRegion,
1592 OpKind::MatMul,
1593 OpKind::DotGeneral,
1594 OpKind::LayerNorm,
1595 OpKind::LayerNorm2d,
1596 OpKind::GroupNorm,
1597 OpKind::ResizeNearest2x,
1598 OpKind::RmsNorm,
1599 OpKind::Attention,
1600 OpKind::AttentionBackward,
1601 OpKind::RmsNormBackwardInput,
1602 OpKind::RmsNormBackwardGamma,
1603 OpKind::RmsNormBackwardBeta,
1604 OpKind::LayerNormBackwardInput,
1611 OpKind::LayerNormBackwardGamma,
1612 OpKind::RopeBackward,
1613 OpKind::CumsumBackward,
1614 OpKind::GatherBackward,
1615 OpKind::Rope,
1616 OpKind::Reshape,
1617 OpKind::Transpose,
1618 OpKind::Narrow,
1619 OpKind::Concat,
1620 OpKind::Expand,
1621 OpKind::Gather,
1622 OpKind::Reverse,
1623 OpKind::Reduce,
1624 OpKind::Softmax,
1625 OpKind::SoftmaxCrossEntropy,
1626 OpKind::ArgMax,
1627 OpKind::ArgMin,
1628 OpKind::Cumsum,
1629 OpKind::TopK,
1630 OpKind::Sample,
1631 OpKind::Conv,
1632 OpKind::Im2Col,
1633 OpKind::Pool,
1634 OpKind::GroupedMatMul,
1635 OpKind::DequantGroupedMatMul,
1636 OpKind::DequantMoEWeights,
1637 OpKind::ScatterAdd,
1638 OpKind::SelectiveScan,
1639 OpKind::Lstm,
1640 OpKind::Gru,
1641 OpKind::Rnn,
1642 OpKind::Mamba2,
1643 OpKind::ConvTranspose2d,
1645 OpKind::Conv3d,
1647 OpKind::ConvTranspose3d,
1648 OpKind::DequantMatMul,
1649 OpKind::FusedMatMulBiasAct,
1650 OpKind::FusedResidualLN,
1651 OpKind::FusedResidualRmsNorm,
1652 OpKind::FusedSwiGLU,
1653 OpKind::FusedAttentionBlock,
1654 OpKind::FusedTransformerLayer,
1655 OpKind::Fft,
1661 OpKind::Scan,
1665 OpKind::LogMel,
1666 OpKind::LogMelBackward,
1667 OpKind::WelchPeaks,
1668 OpKind::GaussianSplatRender,
1670 OpKind::GaussianSplatRenderBackward,
1671 OpKind::GaussianSplatPrepare,
1672 OpKind::GaussianSplatRasterize,
1673 OpKind::Custom,
1674 OpKind::RngNormal,
1675 OpKind::RngUniform,
1676 ];
1678
1679 impl Backend for WgpuBackend {
1680 fn supported_ops(&self) -> &'static [OpKind] {
1681 WGPU_SUPPORTED_OPS
1682 }
1683
1684 fn compile(&self, graph: Graph, options: &CompileOptions) -> Box<dyn ExecutableGraph> {
1685 use rlx_opt::pass::Pass as _;
1686 let graph = rlx_opt::LowerControlFlow.run(graph);
1687 let graph = rlx_opt::legalize_or_rewrite_for_backend(graph, WGPU_SUPPORTED_OPS)
1688 .unwrap_or_else(|errors| {
1689 panic!("{}", rlx_opt::format_legalize_error("wgpu", &errors));
1690 });
1691 let graph = crate::precompile::precompile_cleanup(graph, options);
1692 let graph = rlx_opt::LegalizeBroadcast.run(graph);
1696 let compile_result = crate::stages::compile_graph_stages_for_backend(
1705 rlx_driver::Device::Gpu,
1706 graph,
1707 options,
1708 WGPU_SUPPORTED_OPS,
1709 );
1710 crate::stages::maybe_log_fusion(&compile_result.fusion);
1711 let graph = compile_result.lir.into_graph();
1712 let graph = match options.policy.clone() {
1713 Some(p) => rlx_opt::AutoMixedPrecision::new(p).run(graph),
1714 None => graph,
1715 };
1716 let (graph, io_manifest) = cpu_low_precision::prepare_f32_exec_graph(graph);
1717 Box::new(WgpuExecutableWrapper {
1718 inner: WgpuExecutable::compile_rng(graph, options.rng),
1719 io_manifest,
1720 })
1721 }
1722
1723 fn compile_lir(
1724 &self,
1725 lir: LirModule,
1726 options: &CompileOptions,
1727 ) -> Box<dyn ExecutableGraph> {
1728 use rlx_opt::pass::Pass as _;
1729 let graph = rlx_opt::LegalizeBroadcast.run(lir.into_graph());
1732 let graph = prepare_fused_graph(graph, options, WGPU_SUPPORTED_OPS, "wgpu");
1733 let (graph, io_manifest) = cpu_low_precision::prepare_f32_exec_graph(graph);
1734 Box::new(WgpuExecutableWrapper {
1735 inner: WgpuExecutable::compile_rng(graph, options.rng),
1736 io_manifest,
1737 })
1738 }
1739 }
1740
1741 struct WgpuExecutableWrapper {
1742 inner: WgpuExecutable,
1743 io_manifest: cpu_low_precision::IoDtypeManifest,
1744 }
1745
1746 unsafe impl Send for WgpuExecutableWrapper {}
1747
1748 impl ExecutableGraph for WgpuExecutableWrapper {
1749 fn set_param(&mut self, name: &str, data: &[f32]) {
1750 self.inner.set_param(name, data);
1751 }
1752 fn run(&mut self, inputs: &[(&str, &[f32])]) -> Vec<Vec<f32>> {
1753 self.inner.run(inputs)
1754 }
1755 fn run_read_outputs(
1756 &mut self,
1757 inputs: &[(&str, &[f32])],
1758 read_indices: Option<&[usize]>,
1759 ) -> Vec<Vec<f32>> {
1760 self.inner.run_read_outputs(inputs, read_indices)
1761 }
1762 fn bind_gpu_handle(&mut self, name: &str, data: &[f32]) -> bool {
1763 self.inner.bind_gpu_handle(name, data)
1764 }
1765 fn has_gpu_handle(&self, name: &str) -> bool {
1766 self.inner.has_gpu_handle(name)
1767 }
1768 fn set_gpu_handle_feed(&mut self, handle_name: &str, output_index: usize) -> bool {
1769 self.inner.set_gpu_handle_feed(handle_name, output_index);
1770 true
1771 }
1772 fn read_gpu_handle(&self, name: &str) -> Option<Vec<f32>> {
1773 self.inner.read_gpu_handle(name)
1774 }
1775 fn set_active_extent(&mut self, extent: Option<(usize, usize)>) {
1776 self.inner.set_active_extent(extent);
1777 }
1778
1779 fn set_rng(&mut self, rng: rlx_ir::RngOptions) {
1780 self.inner.set_rng(rng);
1781 }
1782
1783 fn rng(&self) -> rlx_ir::RngOptions {
1784 self.inner.rng()
1785 }
1786
1787 fn set_param_typed(&mut self, name: &str, data: &[u8], dtype: rlx_ir::DType) {
1790 match dtype {
1791 rlx_ir::DType::U8 | rlx_ir::DType::I8 => {
1792 self.inner.set_param_bytes(name, data);
1793 }
1794 rlx_ir::DType::F32 => {
1795 let n = data.len() / 4;
1796 let f32_slice =
1797 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const f32, n) };
1798 self.inner.set_param(name, f32_slice);
1799 }
1800 rlx_ir::DType::F16 => {
1801 let n = data.len() / 2;
1802 let f16_slice =
1803 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const half::f16, n) };
1804 let f32: Vec<f32> = f16_slice.iter().map(|h| h.to_f32()).collect();
1805 self.inner.set_param(name, &f32);
1806 }
1807 rlx_ir::DType::BF16 => {
1808 let n = data.len() / 2;
1809 let bf16_slice = unsafe {
1810 std::slice::from_raw_parts(data.as_ptr() as *const half::bf16, n)
1811 };
1812 let f32: Vec<f32> = bf16_slice.iter().map(|h| h.to_f32()).collect();
1813 self.inner.set_param(name, &f32);
1814 }
1815 other => panic!(
1816 "rlx-wgpu set_param_typed: dtype {other:?} unsupported \
1817 (F32, F16, BF16 only — wgpu arena is f32-uniform)"
1818 ),
1819 }
1820 }
1821
1822 fn run_typed(
1825 &mut self,
1826 inputs: &[(&str, &[u8], rlx_ir::DType)],
1827 ) -> Vec<(Vec<u8>, rlx_ir::DType)> {
1828 let mut owned: Vec<(String, Vec<f32>)> = Vec::with_capacity(inputs.len());
1829 for (name, data, dt) in inputs {
1830 let v: Vec<f32> = match *dt {
1831 rlx_ir::DType::F32 => {
1832 let n = data.len() / 4;
1833 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const f32, n) }
1834 .to_vec()
1835 }
1836 rlx_ir::DType::F16 => {
1837 let n = data.len() / 2;
1838 let s = unsafe {
1839 std::slice::from_raw_parts(data.as_ptr() as *const half::f16, n)
1840 };
1841 s.iter().map(|h| h.to_f32()).collect()
1842 }
1843 rlx_ir::DType::BF16 => {
1844 let n = data.len() / 2;
1845 let s = unsafe {
1846 std::slice::from_raw_parts(data.as_ptr() as *const half::bf16, n)
1847 };
1848 s.iter().map(|h| h.to_f32()).collect()
1849 }
1850 rlx_ir::DType::I64 => {
1854 let n = data.len() / 8;
1855 let s =
1856 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const i64, n) };
1857 s.iter().map(|&x| x as f32).collect()
1858 }
1859 rlx_ir::DType::I32 => {
1860 let n = data.len() / 4;
1861 let s =
1862 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const i32, n) };
1863 s.iter().map(|&x| x as f32).collect()
1864 }
1865 rlx_ir::DType::U8 | rlx_ir::DType::I8 | rlx_ir::DType::Bool => {
1866 data.iter().map(|&b| b as f32).collect()
1867 }
1868 other => {
1869 panic!("rlx-wgpu run_typed: input '{name}' dtype {other:?} unsupported")
1870 }
1871 };
1872 owned.push((name.to_string(), v));
1873 }
1874 let refs: Vec<(&str, &[f32])> = owned
1875 .iter()
1876 .map(|(n, d)| (n.as_str(), d.as_slice()))
1877 .collect();
1878 let dtypes =
1879 super::declared_output_dtypes(&self.io_manifest, self.inner.output_dtypes());
1880 let outs = self.inner.run(&refs);
1881 outs.into_iter()
1882 .zip(
1883 dtypes
1884 .into_iter()
1885 .chain(std::iter::repeat(rlx_ir::DType::F32)),
1886 )
1887 .map(|(v, dt)| (narrow_to_dtype(&v, dt), dt))
1888 .collect()
1889 }
1890
1891 fn clone_box(&self) -> Box<dyn ExecutableGraph> {
1892 Box::new(WgpuExecutableWrapper {
1893 inner: self.inner.clone_for_cache(),
1894 io_manifest: self.io_manifest.clone(),
1895 })
1896 }
1897 }
1898
1899 fn narrow_to_dtype(v: &[f32], dt: rlx_ir::DType) -> Vec<u8> {
1905 use rlx_ir::DType;
1906 match dt {
1907 DType::F32 => {
1908 let mut bytes = Vec::with_capacity(v.len() * 4);
1909 for &x in v {
1910 bytes.extend_from_slice(&x.to_le_bytes());
1911 }
1912 bytes
1913 }
1914 DType::F16 => {
1915 let mut bytes = Vec::with_capacity(v.len() * 2);
1916 for &x in v {
1917 bytes.extend_from_slice(&half::f16::from_f32(x).to_le_bytes());
1918 }
1919 bytes
1920 }
1921 DType::BF16 => {
1922 let mut bytes = Vec::with_capacity(v.len() * 2);
1923 for &x in v {
1924 bytes.extend_from_slice(&half::bf16::from_f32(x).to_le_bytes());
1925 }
1926 bytes
1927 }
1928 DType::F64 => {
1929 let mut bytes = Vec::with_capacity(v.len() * 8);
1930 for &x in v {
1931 bytes.extend_from_slice(&(x as f64).to_le_bytes());
1932 }
1933 bytes
1934 }
1935 DType::I8 => v.iter().map(|&x| x as i8 as u8).collect(),
1936 DType::U8 => v.iter().map(|&x| x as u8).collect(),
1937 DType::I16 => {
1938 let mut bytes = Vec::with_capacity(v.len() * 2);
1939 for &x in v {
1940 bytes.extend_from_slice(&(x as i16).to_le_bytes());
1941 }
1942 bytes
1943 }
1944 DType::I32 => {
1945 let mut bytes = Vec::with_capacity(v.len() * 4);
1946 for &x in v {
1947 bytes.extend_from_slice(&(x as i32).to_le_bytes());
1948 }
1949 bytes
1950 }
1951 DType::U32 => {
1952 let mut bytes = Vec::with_capacity(v.len() * 4);
1953 for &x in v {
1954 bytes.extend_from_slice(&(x as u32).to_le_bytes());
1955 }
1956 bytes
1957 }
1958 DType::I64 => {
1959 let mut bytes = Vec::with_capacity(v.len() * 8);
1960 for &x in v {
1961 bytes.extend_from_slice(&(x as i64).to_le_bytes());
1962 }
1963 bytes
1964 }
1965 DType::Bool => v
1966 .iter()
1967 .map(|&x| if x != 0.0 { 1u8 } else { 0u8 })
1968 .collect(),
1969 DType::C64 => {
1976 let mut bytes = Vec::with_capacity(v.len() * 4);
1977 for &x in v {
1978 bytes.extend_from_slice(&x.to_le_bytes());
1979 }
1980 bytes
1981 }
1982 }
1983 }
1984}
1985
1986#[cfg(feature = "vulkan")]
1989pub mod vulkan_backend {
1990 use super::*;
1991 use rlx_ir::OpKind;
1992 use rlx_vulkan::backend::VulkanExecutable;
1993
1994 pub struct VulkanBackend;
1995
1996 impl Backend for VulkanBackend {
1997 fn supported_ops(&self) -> &'static [OpKind] {
1998 rlx_vulkan::backend::SUPPORTED_OPS
1999 }
2000
2001 fn compile(&self, graph: Graph, options: &CompileOptions) -> Box<dyn ExecutableGraph> {
2002 Box::new(VulkanExecutableWrapper {
2007 inner: VulkanExecutable::compile_rng(graph, options.rng),
2008 })
2009 }
2010 }
2011
2012 struct VulkanExecutableWrapper {
2013 inner: VulkanExecutable,
2014 }
2015
2016 unsafe impl Send for VulkanExecutableWrapper {}
2017
2018 impl ExecutableGraph for VulkanExecutableWrapper {
2019 fn set_param(&mut self, name: &str, data: &[f32]) {
2020 self.inner.set_param(name, data);
2021 }
2022
2023 fn run(&mut self, inputs: &[(&str, &[f32])]) -> Vec<Vec<f32>> {
2024 self.inner.run(inputs)
2025 }
2026
2027 fn run_read_outputs(
2028 &mut self,
2029 inputs: &[(&str, &[f32])],
2030 read_indices: Option<&[usize]>,
2031 ) -> Vec<Vec<f32>> {
2032 self.inner.run_read_outputs(inputs, read_indices)
2033 }
2034
2035 fn set_active_extent(&mut self, extent: Option<(usize, usize)>) {
2036 self.inner.set_active_extent(extent);
2037 }
2038
2039 fn set_rng(&mut self, rng: rlx_ir::RngOptions) {
2040 self.inner.set_rng(rng);
2041 }
2042
2043 fn rng(&self) -> rlx_ir::RngOptions {
2044 self.inner.rng()
2045 }
2046
2047 fn bind_gpu_handle(&mut self, name: &str, data: &[f32]) -> bool {
2048 self.inner.bind_gpu_handle(name, data)
2049 }
2050
2051 fn has_gpu_handle(&self, name: &str) -> bool {
2052 self.inner.has_gpu_handle(name)
2053 }
2054
2055 fn set_gpu_handle_feed(&mut self, handle_name: &str, output_index: usize) -> bool {
2056 self.inner.set_gpu_handle_feed(handle_name, output_index);
2057 true
2058 }
2059
2060 fn read_gpu_handle(&self, name: &str) -> Option<Vec<f32>> {
2061 self.inner.read_gpu_handle(name)
2062 }
2063
2064 fn register_kv_row_feed(&mut self, handle_name: &str, output_index: usize) -> bool {
2065 self.inner.register_kv_row_feed(handle_name, output_index);
2066 true
2067 }
2068
2069 fn feed_kv_row(&mut self, src_row: usize, dst_row: usize, row_elems: usize) -> bool {
2070 self.inner.feed_kv_row(src_row, dst_row, row_elems);
2071 true
2072 }
2073
2074 fn read_output_row(
2075 &self,
2076 out_idx: usize,
2077 row: usize,
2078 row_inner: usize,
2079 ) -> Option<Vec<f32>> {
2080 self.inner.read_output_row(out_idx, row, row_inner)
2081 }
2082
2083 fn read_gpu_handle_row(
2084 &self,
2085 name: &str,
2086 row: usize,
2087 row_inner: usize,
2088 ) -> Option<Vec<f32>> {
2089 self.inner.read_gpu_handle_row(name, row, row_inner)
2090 }
2091
2092 fn set_param_typed(&mut self, name: &str, data: &[u8], dtype: rlx_ir::DType) {
2094 match dtype {
2095 rlx_ir::DType::U8 | rlx_ir::DType::I8 => self.inner.set_param_bytes(name, data),
2096 rlx_ir::DType::F32 => {
2097 let n = data.len() / 4;
2098 let s = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const f32, n) };
2099 self.inner.set_param(name, s);
2100 }
2101 other => {
2102 let f = super::widen_bytes_to_f32(data, other);
2103 self.inner.set_param(name, &f);
2104 }
2105 }
2106 }
2107
2108 fn run_typed(
2111 &mut self,
2112 inputs: &[(&str, &[u8], rlx_ir::DType)],
2113 ) -> Vec<(Vec<u8>, rlx_ir::DType)> {
2114 let mut owned: Vec<(String, Vec<f32>)> = Vec::with_capacity(inputs.len());
2115 for (name, data, dt) in inputs {
2116 let v = if *dt == rlx_ir::DType::F32 {
2117 let n = data.len() / 4;
2118 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const f32, n) }.to_vec()
2119 } else {
2120 super::widen_bytes_to_f32(data, *dt)
2121 };
2122 owned.push((name.to_string(), v));
2123 }
2124 let refs: Vec<(&str, &[f32])> = owned
2125 .iter()
2126 .map(|(n, d)| (n.as_str(), d.as_slice()))
2127 .collect();
2128 let dtypes = self.inner.output_dtypes();
2129 let outs = self.inner.run(&refs);
2130 outs.into_iter()
2131 .zip(
2132 dtypes
2133 .into_iter()
2134 .chain(std::iter::repeat(rlx_ir::DType::F32)),
2135 )
2136 .map(|(v, dt)| (super::narrow_f32_to_bytes(&v, dt), dt))
2137 .collect()
2138 }
2139
2140 fn clone_box(&self) -> Box<dyn ExecutableGraph> {
2141 Box::new(VulkanExecutableWrapper {
2142 inner: self.inner.clone_for_cache(),
2143 })
2144 }
2145 }
2146}
2147
2148#[cfg(feature = "oneapi")]
2151pub mod oneapi_backend {
2152 use super::*;
2153 use rlx_ir::OpKind;
2154 use rlx_oneapi::backend::OneApiExecutable;
2155
2156 pub struct OneApiBackend;
2157
2158 impl Backend for OneApiBackend {
2159 fn supported_ops(&self) -> &'static [OpKind] {
2160 rlx_oneapi::backend::SUPPORTED_OPS
2161 }
2162
2163 fn compile(&self, graph: Graph, options: &CompileOptions) -> Box<dyn ExecutableGraph> {
2164 Box::new(OneApiExecutableWrapper {
2168 inner: OneApiExecutable::compile_rng(graph, options.rng),
2169 })
2170 }
2171 }
2172
2173 struct OneApiExecutableWrapper {
2174 inner: OneApiExecutable,
2175 }
2176
2177 unsafe impl Send for OneApiExecutableWrapper {}
2178
2179 impl ExecutableGraph for OneApiExecutableWrapper {
2180 fn set_param(&mut self, name: &str, data: &[f32]) {
2181 self.inner.set_param(name, data);
2182 }
2183
2184 fn run(&mut self, inputs: &[(&str, &[f32])]) -> Vec<Vec<f32>> {
2185 self.inner.run(inputs)
2186 }
2187
2188 fn run_read_outputs(
2189 &mut self,
2190 inputs: &[(&str, &[f32])],
2191 read_indices: Option<&[usize]>,
2192 ) -> Vec<Vec<f32>> {
2193 self.inner.run_read_outputs(inputs, read_indices)
2194 }
2195
2196 fn set_active_extent(&mut self, extent: Option<(usize, usize)>) {
2197 self.inner.set_active_extent(extent);
2198 }
2199
2200 fn set_rng(&mut self, rng: rlx_ir::RngOptions) {
2201 self.inner.set_rng(rng);
2202 }
2203
2204 fn rng(&self) -> rlx_ir::RngOptions {
2205 self.inner.rng()
2206 }
2207
2208 fn set_param_typed(&mut self, name: &str, data: &[u8], dtype: rlx_ir::DType) {
2210 match dtype {
2211 rlx_ir::DType::U8 | rlx_ir::DType::I8 => self.inner.set_param_bytes(name, data),
2212 rlx_ir::DType::F32 => {
2213 let n = data.len() / 4;
2214 let s = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const f32, n) };
2215 self.inner.set_param(name, s);
2216 }
2217 other => {
2218 let f = super::widen_bytes_to_f32(data, other);
2219 self.inner.set_param(name, &f);
2220 }
2221 }
2222 }
2223
2224 fn run_typed(
2227 &mut self,
2228 inputs: &[(&str, &[u8], rlx_ir::DType)],
2229 ) -> Vec<(Vec<u8>, rlx_ir::DType)> {
2230 let mut owned: Vec<(String, Vec<f32>)> = Vec::with_capacity(inputs.len());
2231 for (name, data, dt) in inputs {
2232 let v = if *dt == rlx_ir::DType::F32 {
2233 let n = data.len() / 4;
2234 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const f32, n) }.to_vec()
2235 } else {
2236 super::widen_bytes_to_f32(data, *dt)
2237 };
2238 owned.push((name.to_string(), v));
2239 }
2240 let refs: Vec<(&str, &[f32])> = owned
2241 .iter()
2242 .map(|(n, d)| (n.as_str(), d.as_slice()))
2243 .collect();
2244 let dtypes = self.inner.output_dtypes();
2245 let outs = self.inner.run(&refs);
2246 outs.into_iter()
2247 .zip(
2248 dtypes
2249 .into_iter()
2250 .chain(std::iter::repeat(rlx_ir::DType::F32)),
2251 )
2252 .map(|(v, dt)| (super::narrow_f32_to_bytes(&v, dt), dt))
2253 .collect()
2254 }
2255
2256 fn clone_box(&self) -> Box<dyn ExecutableGraph> {
2257 Box::new(OneApiExecutableWrapper {
2258 inner: self.inner.clone_for_cache(),
2259 })
2260 }
2261 }
2262}
2263
2264#[cfg(all(feature = "mlx", rlx_mlx_host))]
2267pub mod mlx_backend {
2268 use super::*;
2269 use rlx_mlx::MlxExecutable;
2270
2271 pub struct MlxBackend;
2272
2273 const MLX_SUPPORTED_OPS: &[rlx_ir::OpKind] = {
2283 use rlx_ir::OpKind::*;
2284 &[
2285 Input,
2286 Param,
2287 Constant,
2288 Activation,
2289 Cast,
2290 StopGradient,
2291 Binary,
2292 Compare,
2293 Where,
2294 ElementwiseRegion,
2295 TransformRegion,
2296 BatchElementwiseRegion,
2297 MatMul,
2298 DotGeneral,
2299 DenseSolve,
2300 BatchedDenseSolve,
2301 LayerNorm,
2302 LayerNorm2d,
2303 GroupNorm,
2304 ResizeNearest2x,
2305 RmsNorm,
2306 Attention,
2307 Rope,
2308 Reshape,
2309 Transpose,
2310 Narrow,
2311 Concat,
2312 Expand,
2313 Gather,
2314 Reverse,
2315 Reduce,
2316 Softmax,
2317 Cumsum,
2318 ArgMax,
2319 ArgMin,
2320 TopK,
2321 RngNormal,
2322 RngUniform,
2323 Sample,
2324 Conv,
2325 Im2Col,
2326 ConvTranspose2d,
2327 Pool,
2328 GroupedMatMul,
2329 DequantGroupedMatMul,
2330 DequantMoEWeights,
2331 ScatterAdd,
2332 LoraMatMul,
2333 DequantMatMul,
2334 SelectiveScan,
2335 GatedDeltaNet,
2336 FusedSwiGLU,
2337 FusedMatMulBiasAct,
2338 FusedResidualLN,
2339 FusedResidualRmsNorm,
2340 FusedAttentionBlock,
2341 FusedTransformerLayer,
2342 If,
2343 While,
2344 Scan,
2349 ScanBackward,
2350 ScanBackwardXs,
2351 ReluBackward,
2354 ActivationBackward,
2355 SoftmaxCrossEntropy,
2356 SoftmaxCrossEntropyWithLogits,
2357 SoftmaxCrossEntropyBackward,
2358 AttentionBackward,
2359 LayerNormBackwardInput,
2360 LayerNormBackwardGamma,
2361 GroupNormBackwardInput,
2364 GroupNormBackwardGamma,
2365 GroupNormBackwardBeta,
2366 Conv2dBackwardInput,
2371 Conv2dBackwardWeight,
2372 MaxPool2dBackward,
2376 FakeQuantize,
2381 FakeQuantizeBackward,
2382 Custom,
2387 Fft,
2388 LogMel,
2389 LogMelBackward,
2390 WelchPeaks,
2391 GaussianSplatRender,
2392 GaussianSplatRenderBackward,
2393 ]
2396 };
2397
2398 impl Backend for MlxBackend {
2399 fn supported_ops(&self) -> &'static [rlx_ir::OpKind] {
2400 MLX_SUPPORTED_OPS
2401 }
2402
2403 fn compile(&self, graph: Graph, options: &CompileOptions) -> Box<dyn ExecutableGraph> {
2404 let compile_result = crate::stages::compile_graph_stages_for_backend(
2405 rlx_driver::Device::Mlx,
2406 graph,
2407 options,
2408 MLX_SUPPORTED_OPS,
2409 );
2410 crate::stages::maybe_log_fusion(&compile_result.fusion);
2411 self.compile_lir(compile_result.lir, options)
2412 }
2413
2414 fn compile_lir(
2415 &self,
2416 lir: LirModule,
2417 options: &CompileOptions,
2418 ) -> Box<dyn ExecutableGraph> {
2419 use rlx_opt::pass::Pass as _;
2420 let mut graph = lir.into_graph();
2421 graph = rlx_opt::LowerControlFlow.run(graph);
2422 let graph = prepare_fused_graph(graph, options, MLX_SUPPORTED_OPS, "mlx");
2423 Box::new(build_mlx_executable(graph, options.rng))
2424 }
2425 }
2426
2427 fn build_mlx_executable(graph: Graph, rng: rlx_ir::RngOptions) -> MlxExecutableWrapper {
2428 let (graph, io_manifest) = cpu_low_precision::prepare_f32_exec_graph(graph);
2429 let mode = mlx_mode_from_env();
2430 let mut exe = MlxExecutable::compile_from_fused_with_rng(graph, mode, rng);
2431 if mode == rlx_mlx::lower::MlxMode::Compiled {
2432 if let Err(e) = exe.warm_compile() {
2433 eprintln!(
2434 "[rlx-runtime] MLX warm_compile failed ({e}); first run will pay the trace cost"
2435 );
2436 }
2437 }
2438 MlxExecutableWrapper {
2439 inner: exe,
2440 io_manifest,
2441 }
2442 }
2443
2444 fn mlx_mode_from_env() -> rlx_mlx::lower::MlxMode {
2445 match rlx_ir::env::var("RLX_MLX_MODE").as_deref() {
2446 Some(s) if s.eq_ignore_ascii_case("eager") => rlx_mlx::lower::MlxMode::Eager,
2447 Some(s) if s.eq_ignore_ascii_case("lazy") => rlx_mlx::lower::MlxMode::Lazy,
2448 Some(s) if s.eq_ignore_ascii_case("compiled") => rlx_mlx::lower::MlxMode::Compiled,
2449 _ => rlx_mlx::lower::MlxMode::Compiled,
2450 }
2451 }
2452
2453 struct MlxExecutableWrapper {
2454 inner: MlxExecutable,
2455 io_manifest: cpu_low_precision::IoDtypeManifest,
2456 }
2457
2458 unsafe impl Send for MlxExecutableWrapper {}
2459
2460 impl ExecutableGraph for MlxExecutableWrapper {
2461 fn set_param(&mut self, name: &str, data: &[f32]) {
2462 self.inner.set_param(name, data);
2463 }
2464 fn run(&mut self, inputs: &[(&str, &[f32])]) -> Vec<Vec<f32>> {
2465 self.inner.run(inputs)
2466 }
2467 fn run_read_outputs(
2468 &mut self,
2469 inputs: &[(&str, &[f32])],
2470 read_indices: Option<&[usize]>,
2471 ) -> Vec<Vec<f32>> {
2472 self.inner
2473 .run_read_outputs(inputs, read_indices)
2474 .unwrap_or_else(|e| panic!("MLX run_read_outputs failed: {e}"))
2475 }
2476 fn run_slots(&mut self, inputs: &[&[f32]]) -> &[(usize, usize)] {
2477 self.inner.run_slots(inputs)
2478 }
2479 fn arena_ptr(&self) -> *const u8 {
2480 self.inner.arena_ptr()
2481 }
2482 fn commit_no_wait(&mut self, inputs: &[(&str, &[f32])]) {
2483 self.inner.commit_no_wait(inputs);
2484 }
2485 fn sync_pending(&mut self) {
2486 self.inner.sync_pending();
2487 }
2488 fn run_pipelined(&mut self, input_sets: &[Vec<(&str, &[f32])>]) -> Vec<Vec<Vec<f32>>> {
2489 self.inner.run_pipelined(input_sets)
2490 }
2491 fn bind_handle(&mut self, name: &str, data: &[f32]) -> bool {
2492 self.inner.bind_handle(name, data)
2493 }
2494 fn read_handle(&self, name: &str) -> Option<Vec<f32>> {
2495 self.inner.read_handle(name)
2496 }
2497 fn bind_gpu_handle(&mut self, name: &str, data: &[f32]) -> bool {
2498 self.inner.bind_gpu_handle(name, data).is_ok()
2499 }
2500 fn has_gpu_handle(&self, name: &str) -> bool {
2501 self.inner.has_gpu_handle(name)
2502 }
2503 fn set_gpu_handle_feed(&mut self, handle_name: &str, output_index: usize) -> bool {
2504 self.inner.set_gpu_handle_feed(handle_name, output_index);
2505 true
2506 }
2507 fn register_kv_row_feed(&mut self, handle_name: &str, output_index: usize) -> bool {
2508 self.inner.register_kv_row_feed(handle_name, output_index);
2509 true
2510 }
2511 fn feed_kv_row(&mut self, src_row: usize, dst_row: usize, row_elems: usize) -> bool {
2512 self.inner.feed_kv_row(src_row, dst_row, row_elems).is_ok()
2513 }
2514 fn read_gpu_handle(&self, name: &str) -> Option<Vec<f32>> {
2515 self.inner.read_gpu_handle(name).ok()
2516 }
2517 fn run_feed_gpu_handle(
2518 &mut self,
2519 inputs: &[(&str, &[f32])],
2520 handle_name: &str,
2521 output_index: usize,
2522 ) -> Option<Vec<f32>> {
2523 self.inner
2524 .run_feed_gpu(inputs, handle_name, output_index)
2525 .ok()
2526 }
2527 fn set_param_typed(&mut self, name: &str, data: &[u8], dtype: rlx_ir::DType) {
2528 self.inner.set_param_typed(name, data, dtype);
2529 }
2530 fn run_typed(
2531 &mut self,
2532 inputs: &[(&str, &[u8], rlx_ir::DType)],
2533 ) -> Vec<(Vec<u8>, rlx_ir::DType)> {
2534 self.inner.run_typed(inputs)
2535 }
2536 fn set_active_extent(&mut self, extent: Option<(usize, usize)>) {
2537 self.inner.set_active_extent(extent);
2538 }
2539
2540 fn set_rng(&mut self, rng: rlx_ir::RngOptions) {
2541 self.inner.set_rng(rng);
2542 }
2543
2544 fn rng(&self) -> rlx_ir::RngOptions {
2545 self.inner.rng()
2546 }
2547
2548 fn copy_params_from(&mut self, src: &dyn ExecutableGraph) -> bool {
2549 let Some(src_any) = src.executable_as_any() else {
2550 return false;
2551 };
2552 let Some(src_wrap) = src_any.downcast_ref::<MlxExecutableWrapper>() else {
2553 return false;
2554 };
2555 let Some(dst_any) = self.executable_as_any_mut() else {
2556 return false;
2557 };
2558 let Some(dst_wrap) = dst_any.downcast_mut::<MlxExecutableWrapper>() else {
2559 return false;
2560 };
2561 dst_wrap.inner.copy_params_from(&src_wrap.inner)
2562 }
2563
2564 fn executable_as_any(&self) -> Option<&dyn std::any::Any> {
2565 Some(self)
2566 }
2567
2568 fn executable_as_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
2569 Some(self)
2570 }
2571
2572 fn clone_box(&self) -> Box<dyn ExecutableGraph> {
2573 Box::new(MlxExecutableWrapper {
2574 inner: self.inner.clone_for_cache(),
2575 io_manifest: self.io_manifest.clone(),
2576 })
2577 }
2578 }
2579}
2580
2581pub(crate) const COREML_SUPPORTED_OPS: &[rlx_ir::OpKind] = {
2588 use rlx_ir::OpKind::*;
2589 &[
2590 Input,
2591 Param,
2592 Constant,
2593 Activation,
2594 Cast,
2595 Binary,
2596 MatMul,
2597 LayerNorm,
2598 RmsNorm,
2599 Reduce,
2600 Softmax,
2601 Reshape,
2602 Transpose,
2603 Narrow,
2604 Concat,
2605 Gather,
2606 Rope,
2607 Attention,
2608 FusedAttentionBlock,
2613 Compare,
2614 Where,
2615 Expand,
2616 Cumsum,
2617 ScatterAdd,
2618 BatchNormInference,
2619 GroupNorm,
2620 LayerNorm2d,
2621 LoraMatMul,
2622 Conv,
2623 ConvTranspose2d,
2624 Pool,
2625 TopK,
2626 AxialRope2d,
2627 ResizeNearest2x,
2628 StopGradient,
2629 GroupedMatMul,
2630 DequantMatMul,
2631 DequantMoEWeights,
2632 DequantGroupedMatMul,
2633 Quantize,
2634 Dequantize,
2635 SelectiveScan,
2636 GatedDeltaNet,
2637 ArgMax,
2638 ArgMin,
2639 Reverse,
2640 Fft,
2641 LogMel,
2642 Sample,
2643 RngNormal,
2644 Lstm,
2645 Scan,
2648 Gru,
2649 Rnn,
2650 Mamba2,
2651 WelchPeaks,
2652 Custom,
2653 ]
2654};
2655
2656#[allow(dead_code)] pub(crate) const COREML_BACKWARD_OPS: &[rlx_ir::OpKind] = {
2675 use rlx_ir::OpKind::*;
2676 &[
2677 ReluBackward,
2678 ActivationBackward,
2679 LayerNormBackwardInput,
2680 LayerNormBackwardGamma,
2681 GroupNormBackwardInput,
2682 GroupNormBackwardGamma,
2683 GroupNormBackwardBeta,
2684 BatchNormInferenceBackwardInput,
2685 BatchNormInferenceBackwardGamma,
2686 BatchNormInferenceBackwardBeta,
2687 RopeBackward,
2688 AttentionBackward,
2689 SoftmaxCrossEntropyBackward,
2690 CumsumBackward,
2691 GatherBackward,
2692 FakeQuantizeBackward,
2693 ]
2694};
2695
2696#[allow(dead_code)] pub(crate) const COREML_NATIVE_BACKWARD_OPS: &[rlx_ir::OpKind] = {
2730 use rlx_ir::OpKind::*;
2731 &[
2732 RmsNormBackwardInput,
2733 RmsNormBackwardGamma,
2734 RmsNormBackwardBeta,
2735 LayerNormBackwardInput,
2736 LayerNormBackwardGamma,
2737 GroupNormBackwardInput,
2738 GroupNormBackwardGamma,
2739 GroupNormBackwardBeta,
2740 MaxPool2dBackward,
2741 Conv2dBackwardInput,
2742 Conv2dBackwardWeight,
2743 AttentionBackward,
2744 SoftmaxCrossEntropyWithLogits,
2750 SoftmaxCrossEntropyBackward,
2751 ]
2752};
2753
2754#[cfg(feature = "training")]
2765pub(crate) const COREML_SUPPORTED_OPS_TRAINING: [rlx_ir::OpKind;
2766 COREML_SUPPORTED_OPS.len() + COREML_NATIVE_BACKWARD_OPS.len()] = {
2767 let mut arr =
2768 [rlx_ir::OpKind::Input; COREML_SUPPORTED_OPS.len() + COREML_NATIVE_BACKWARD_OPS.len()];
2769 let mut i = 0;
2770 while i < COREML_SUPPORTED_OPS.len() {
2771 arr[i] = COREML_SUPPORTED_OPS[i];
2772 i += 1;
2773 }
2774 let mut j = 0;
2775 while j < COREML_NATIVE_BACKWARD_OPS.len() {
2776 arr[COREML_SUPPORTED_OPS.len() + j] = COREML_NATIVE_BACKWARD_OPS[j];
2777 j += 1;
2778 }
2779 arr
2780};
2781
2782#[cfg(all(
2790 feature = "coreml",
2791 target_vendor = "apple",
2792 not(target_os = "watchos")
2793))]
2794pub mod coreml_backend {
2795 use super::*;
2796 use crate::Precision;
2797 use rlx_coreml::{CoremlExecutable, default_lower_options};
2798
2799 pub struct CoremlBackend;
2800
2801 impl Backend for CoremlBackend {
2802 fn supported_ops(&self) -> &'static [rlx_ir::OpKind] {
2803 #[cfg(feature = "training")]
2809 {
2810 &super::COREML_SUPPORTED_OPS_TRAINING
2811 }
2812 #[cfg(not(feature = "training"))]
2813 {
2814 super::COREML_SUPPORTED_OPS
2815 }
2816 }
2817
2818 fn compile(&self, graph: Graph, options: &CompileOptions) -> Box<dyn ExecutableGraph> {
2819 let graph = rlx_opt::legalize_or_rewrite_for_backend(graph, self.supported_ops())
2823 .unwrap_or_else(|errors| {
2824 panic!("{}", rlx_opt::format_legalize_error("coreml", &errors));
2825 });
2826 let (graph, mut lower_opts) = match options.policy.clone() {
2833 Some(policy) => {
2834 use rlx_opt::pass::Pass as _;
2835 let g = rlx_opt::AutoMixedPrecision::new(policy).run(graph);
2836 let opts = default_lower_options(&g);
2837 (g, opts)
2838 }
2839 None => {
2840 let mut opts = default_lower_options(&graph);
2841 if options.precision == Precision::F16 {
2843 opts.float_dtype = rlx_ir::DType::F16;
2844 }
2845 (graph, opts)
2846 }
2847 };
2848 if let Some(binding) = &options.dim_binding {
2849 let _ = binding;
2850 lower_opts.flexible_inputs = false;
2851 }
2852 Box::new(CoremlExecutableWrapper {
2853 inner: CoremlExecutable::compile_with_lower_opts(graph, lower_opts),
2854 })
2855 }
2856
2857 fn compile_lir(
2858 &self,
2859 lir: LirModule,
2860 options: &CompileOptions,
2861 ) -> Box<dyn ExecutableGraph> {
2862 self.compile(lir.into_graph(), options)
2865 }
2866 }
2867
2868 struct CoremlExecutableWrapper {
2869 inner: CoremlExecutable,
2870 }
2871
2872 unsafe impl Send for CoremlExecutableWrapper {}
2874
2875 impl ExecutableGraph for CoremlExecutableWrapper {
2876 fn set_param(&mut self, name: &str, data: &[f32]) {
2877 self.inner.set_param(name, data);
2878 }
2879
2880 fn set_param_typed(&mut self, name: &str, data: &[u8], dtype: rlx_ir::DType) {
2881 self.inner.set_param_typed(name, data, dtype);
2884 }
2885
2886 fn finalize_params(&mut self) {
2887 self.inner
2888 .finalize()
2889 .unwrap_or_else(|e| panic!("CoreML finalize failed: {e}"));
2890 }
2891
2892 fn run(&mut self, inputs: &[(&str, &[f32])]) -> Vec<Vec<f32>> {
2893 self.inner
2894 .run(inputs)
2895 .unwrap_or_else(|e| panic!("CoreML run failed: {e}"))
2896 }
2897
2898 fn clone_box(&self) -> Box<dyn ExecutableGraph> {
2899 Box::new(CoremlExecutableWrapper {
2900 inner: self.inner.clone_for_cache(),
2901 })
2902 }
2903
2904 fn run_typed(
2905 &mut self,
2906 inputs: &[(&str, &[u8], rlx_ir::DType)],
2907 ) -> Vec<(Vec<u8>, rlx_ir::DType)> {
2908 use rlx_ir::DType;
2909 let owned: Vec<(String, Vec<f32>)> = inputs
2912 .iter()
2913 .map(|(name, data, dt)| {
2914 let v: Vec<f32> = match dt {
2915 DType::I64 => data
2916 .chunks_exact(8)
2917 .map(|c| i64::from_le_bytes(c.try_into().unwrap()) as f32)
2918 .collect(),
2919 DType::I32 => data
2920 .chunks_exact(4)
2921 .map(|c| i32::from_le_bytes(c.try_into().unwrap()) as f32)
2922 .collect(),
2923 DType::U8 | DType::Bool => data.iter().map(|&b| b as f32).collect(),
2924 _ => super::widen_bytes_to_f32(data, *dt),
2925 };
2926 (name.to_string(), v)
2927 })
2928 .collect();
2929 let refs: Vec<(&str, &[f32])> = owned
2930 .iter()
2931 .map(|(n, d)| (n.as_str(), d.as_slice()))
2932 .collect();
2933 self.run(&refs)
2934 .into_iter()
2935 .map(|v| {
2936 let bytes: Vec<u8> = v.iter().flat_map(|f| f.to_le_bytes()).collect();
2937 (bytes, DType::F32)
2938 })
2939 .collect()
2940 }
2941 }
2942}
2943
2944#[cfg(all(feature = "metal", target_vendor = "apple", not(target_os = "watchos")))]
2945pub mod metal_backend {
2946 use super::*;
2947 use rlx_metal::backend::MetalExecutable;
2948
2949 pub struct MetalBackend;
2950
2951 const METAL_SUPPORTED_OPS: &[rlx_ir::OpKind] = {
2965 use rlx_ir::OpKind::*;
2966 &[
2967 Input,
2968 Param,
2969 Constant,
2970 Activation,
2971 Cast,
2972 StopGradient,
2973 Binary,
2974 Compare,
2975 Where,
2976 Fma,
2977 ElementwiseRegion,
2978 TransformRegion,
2979 BatchElementwiseRegion,
2980 MatMul,
2981 ScaledMatMul,
2982 ScaledQuantize,
2983 ScaledQuantScale,
2984 ScaledDequantize,
2985 DotGeneral,
2986 LayerNorm,
2987 LayerNorm2d,
2988 GroupNorm,
2989 RmsNorm,
2990 ResizeNearest2x,
2991 AxialRope2d,
2992 Attention,
2993 AttentionBackward,
2994 RmsNormBackwardInput,
2995 RmsNormBackwardGamma,
2996 RmsNormBackwardBeta,
2997 RopeBackward,
2998 Cumsum,
2999 CumsumBackward,
3000 GatherBackward,
3001 Conv2dBackwardInput,
3002 Conv2dBackwardWeight,
3003 MaxPool2dBackward,
3004 Rope,
3005 Reshape,
3006 Transpose,
3007 Narrow,
3008 Concat,
3009 Expand,
3010 Gather,
3011 Reverse,
3012 Reduce,
3013 Softmax,
3014 SoftmaxCrossEntropy,
3015 SoftmaxCrossEntropyWithLogits,
3016 SoftmaxCrossEntropyBackward,
3017 ArgMax,
3018 ArgMin,
3019 TopK,
3020 Sample,
3021 RngNormal,
3022 RngUniform,
3023 Conv,
3024 Im2Col,
3025 ConvTranspose2d,
3026 Pool,
3027 GroupedMatMul,
3028 DequantGroupedMatMul,
3029 DequantMoEWeights,
3030 ScatterAdd,
3031 DequantMatMul,
3032 GatedDeltaNet,
3033 SelectiveScan,
3034 Lstm,
3035 Gru,
3036 Rnn,
3037 Mamba2,
3038 FusedSwiGLU,
3039 FusedMatMulBiasAct,
3040 FusedResidualLN,
3041 FusedResidualRmsNorm,
3042 FusedAttentionBlock,
3048 Custom,
3054 Fft,
3060 Scan,
3064 LogMel,
3065 LogMelBackward,
3066 WelchPeaks,
3067 GaussianSplatRender,
3069 GaussianSplatRenderBackward,
3070 GaussianSplatPrepare,
3071 GaussianSplatRasterize,
3072 ]
3073 };
3074
3075 impl Backend for MetalBackend {
3076 fn supported_ops(&self) -> &'static [rlx_ir::OpKind] {
3077 METAL_SUPPORTED_OPS
3078 }
3079
3080 fn compile(&self, graph: Graph, options: &CompileOptions) -> Box<dyn ExecutableGraph> {
3081 use rlx_opt::pass::Pass as _;
3082 let graph = rlx_opt::LowerControlFlow.run(graph);
3086 let dispatch = options.kernel_dispatch;
3087 let graph = rlx_opt::legalize_or_rewrite_for_backend_with_config(
3088 graph,
3089 METAL_SUPPORTED_OPS,
3090 dispatch,
3091 )
3092 .unwrap_or_else(|errors| {
3093 panic!("{}", rlx_opt::format_legalize_error("metal", &errors));
3094 });
3095 let graph = crate::precompile::precompile_cleanup(graph, options);
3096
3097 let (graph, io_manifest) = cpu_low_precision::prepare_f32_exec_graph(graph);
3100 Box::new(MetalExecutableWrapper {
3101 inner: MetalExecutable::compile_with_policy(
3102 graph,
3103 options.policy.clone(),
3104 Some(METAL_SUPPORTED_OPS),
3105 options.rng,
3106 ),
3107 io_manifest,
3108 })
3109 }
3110
3111 fn compile_lir(
3112 &self,
3113 lir: LirModule,
3114 options: &CompileOptions,
3115 ) -> Box<dyn ExecutableGraph> {
3116 use rlx_opt::pass::Pass as _;
3117 let mut graph = lir.into_graph();
3118 graph = rlx_opt::LowerControlFlow.run(graph);
3119 let dispatch = options.kernel_dispatch;
3120 let mut graph = rlx_opt::legalize_or_rewrite_for_backend_with_config(
3121 graph,
3122 METAL_SUPPORTED_OPS,
3123 dispatch,
3124 )
3125 .unwrap_or_else(|errors| {
3126 panic!("{}", rlx_opt::format_legalize_error("metal", &errors));
3127 });
3128 graph = crate::precompile::precompile_cleanup(graph, options);
3129 let (graph, io_manifest) = cpu_low_precision::prepare_f32_exec_graph(graph);
3130 Box::new(MetalExecutableWrapper {
3131 inner: MetalExecutable::compile_from_fused(
3132 graph,
3133 options.policy.clone(),
3134 Some(METAL_SUPPORTED_OPS),
3135 options.rng,
3136 ),
3137 io_manifest,
3138 })
3139 }
3140 }
3141
3142 struct MetalExecutableWrapper {
3143 inner: MetalExecutable,
3144 io_manifest: cpu_low_precision::IoDtypeManifest,
3145 }
3146
3147 unsafe impl Send for MetalExecutableWrapper {}
3148
3149 impl ExecutableGraph for MetalExecutableWrapper {
3150 fn set_param(&mut self, name: &str, data: &[f32]) {
3151 self.inner.set_param(name, data);
3152 }
3153
3154 fn finalize_params(&mut self) {
3155 self.inner.preload_qmatmul_weights();
3156 }
3157
3158 fn run(&mut self, inputs: &[(&str, &[f32])]) -> Vec<Vec<f32>> {
3159 self.inner.run(inputs)
3160 }
3161 fn run_read_outputs(
3162 &mut self,
3163 inputs: &[(&str, &[f32])],
3164 read_indices: Option<&[usize]>,
3165 ) -> Vec<Vec<f32>> {
3166 self.inner.run_read_outputs(inputs, read_indices)
3167 }
3168 fn bind_gpu_handle(&mut self, name: &str, data: &[f32]) -> bool {
3169 self.inner.bind_gpu_handle(name, data)
3170 }
3171 fn has_gpu_handle(&self, name: &str) -> bool {
3172 self.inner.has_gpu_handle(name)
3173 }
3174 fn set_gpu_handle_feed(&mut self, handle_name: &str, output_index: usize) -> bool {
3175 self.inner.set_gpu_handle_feed(handle_name, output_index);
3176 true
3177 }
3178 fn read_gpu_handle(&self, name: &str) -> Option<Vec<f32>> {
3179 self.inner.read_gpu_handle(name)
3180 }
3181 fn register_kv_row_feed(&mut self, handle_name: &str, output_index: usize) -> bool {
3182 self.inner.register_kv_row_feed(handle_name, output_index);
3183 true
3184 }
3185 fn feed_kv_row(&mut self, src_row: usize, dst_row: usize, row_elems: usize) -> bool {
3186 self.inner.feed_kv_row(src_row, dst_row, row_elems);
3187 true
3188 }
3189 fn read_output_row(
3190 &self,
3191 out_idx: usize,
3192 row: usize,
3193 row_inner: usize,
3194 ) -> Option<Vec<f32>> {
3195 Some(self.inner.read_graph_output_row(out_idx, row, row_inner))
3196 }
3197 fn run_slots(&mut self, inputs: &[&[f32]]) -> &[(usize, usize)] {
3198 self.inner.run_slots(inputs)
3199 }
3200 fn arena_ptr(&self) -> *const u8 {
3201 self.inner.arena_ptr()
3202 }
3203 fn commit_no_wait(&mut self, inputs: &[(&str, &[f32])]) {
3204 self.inner.commit_no_wait(inputs);
3205 }
3206 fn sync_pending(&mut self) {
3207 self.inner.sync_pending();
3208 }
3209 fn run_pipelined(&mut self, input_sets: &[Vec<(&str, &[f32])>]) -> Vec<Vec<Vec<f32>>> {
3210 self.inner.run_pipelined(input_sets)
3211 }
3212 fn set_active_extent(&mut self, extent: Option<(usize, usize)>) {
3213 self.inner.set_active_extent(extent);
3214 }
3215
3216 fn set_rng(&mut self, rng: rlx_ir::RngOptions) {
3217 self.inner.set_rng(rng);
3218 }
3219
3220 fn rng(&self) -> rlx_ir::RngOptions {
3221 self.inner.rng()
3222 }
3223
3224 fn set_param_typed(&mut self, name: &str, data: &[u8], dtype: rlx_ir::DType) {
3230 if matches!(
3231 dtype,
3232 rlx_ir::DType::U8
3233 | rlx_ir::DType::I8
3234 | rlx_ir::DType::I32
3235 | rlx_ir::DType::I64
3236 | rlx_ir::DType::U32
3237 | rlx_ir::DType::F64
3238 ) {
3239 self.inner.set_param_bytes(name, data);
3240 return;
3241 }
3242 if dtype == rlx_ir::DType::F32 {
3243 let n = data.len() / 4;
3244 let s = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const f32, n) };
3245 self.inner.set_param(name, s);
3246 } else {
3247 let f32_buf = super::widen_bytes_to_f32(data, dtype);
3248 self.inner.set_param(name, &f32_buf);
3249 }
3250 }
3251
3252 fn run_typed(
3256 &mut self,
3257 inputs: &[(&str, &[u8], rlx_ir::DType)],
3258 ) -> Vec<(Vec<u8>, rlx_ir::DType)> {
3259 self.inner.run_typed(inputs)
3260 }
3261
3262 fn copy_params_from(&mut self, src: &dyn ExecutableGraph) -> bool {
3263 let Some(src_any) = src.executable_as_any() else {
3264 return false;
3265 };
3266 let Some(src_wrap) = src_any.downcast_ref::<MetalExecutableWrapper>() else {
3267 return false;
3268 };
3269 let Some(dst_any) = self.executable_as_any_mut() else {
3270 return false;
3271 };
3272 let Some(dst_wrap) = dst_any.downcast_mut::<MetalExecutableWrapper>() else {
3273 return false;
3274 };
3275 dst_wrap.inner.copy_params_from(&src_wrap.inner)
3276 }
3277
3278 fn executable_as_any(&self) -> Option<&dyn std::any::Any> {
3279 Some(self)
3280 }
3281
3282 fn executable_as_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
3283 Some(self)
3284 }
3285
3286 fn clone_box(&self) -> Box<dyn ExecutableGraph> {
3287 Box::new(MetalExecutableWrapper {
3288 inner: self.inner.clone_for_cache(),
3289 io_manifest: self.io_manifest.clone(),
3290 })
3291 }
3292 }
3293}
3294
3295#[cfg(feature = "cuda")]
3298pub mod cuda_backend {
3299 use super::*;
3300 use rlx_cuda::backend::CudaExecutable;
3301
3302 pub struct CudaBackend;
3303
3304 const CUDA_SUPPORTED_OPS: &[rlx_ir::OpKind] = {
3313 use rlx_ir::OpKind::*;
3314 &[
3315 Input,
3316 Param,
3317 Constant,
3318 Activation,
3319 Cast,
3320 StopGradient,
3321 Binary,
3322 Compare,
3323 Where,
3324 ElementwiseRegion,
3325 TransformRegion,
3326 BatchElementwiseRegion,
3327 MatMul,
3328 ScaledMatMul,
3329 ScaledQuantize,
3330 ScaledQuantScale,
3331 ScaledDequantize,
3332 DotGeneral,
3333 LayerNorm,
3334 LayerNorm2d,
3335 GroupNorm,
3336 ResizeNearest2x,
3337 AxialRope2d,
3338 Reverse,
3339 ArgMax,
3340 ArgMin,
3341 RmsNorm,
3342 Attention,
3343 AttentionBackward,
3344 RmsNormBackwardInput,
3345 RmsNormBackwardGamma,
3346 RmsNormBackwardBeta,
3347 RopeBackward,
3348 CumsumBackward,
3349 GatherBackward,
3350 Conv2dBackwardInput,
3351 Conv2dBackwardWeight,
3352 MaxPool2dBackward,
3353 Rope,
3354 Reshape,
3355 Transpose,
3356 Narrow,
3357 Concat,
3358 Expand,
3359 Gather,
3360 Reduce,
3361 Softmax,
3362 Cumsum,
3363 TopK,
3364 Sample,
3365 Conv,
3366 ConvTranspose2d,
3367 Pool,
3368 GroupedMatMul,
3369 DequantGroupedMatMul,
3370 DequantMoEWeights,
3371 ScatterAdd,
3372 DequantMatMul,
3373 SelectiveScan,
3374 Lstm,
3375 Scan,
3378 FusedMatMulBiasAct,
3379 FusedResidualLN,
3380 FusedResidualRmsNorm,
3381 FusedAttentionBlock,
3385 GaussianSplatRender,
3386 GaussianSplatRenderBackward,
3387 GaussianSplatPrepare,
3388 GaussianSplatRasterize,
3389 Custom,
3390 Fft,
3391 LogMel,
3392 LogMelBackward,
3393 WelchPeaks,
3394 Im2Col,
3395 RngNormal,
3396 RngUniform,
3397 ]
3398 };
3399
3400 impl Backend for CudaBackend {
3401 fn supported_ops(&self) -> &'static [rlx_ir::OpKind] {
3402 CUDA_SUPPORTED_OPS
3403 }
3404
3405 fn compile(&self, graph: Graph, options: &CompileOptions) -> Box<dyn ExecutableGraph> {
3406 use rlx_opt::pass::Pass as _;
3407 let graph = rlx_cuda::unfuse::unfuse(graph);
3410 let graph = rlx_opt::legalize_or_rewrite_for_backend(graph, CUDA_SUPPORTED_OPS)
3411 .unwrap_or_else(|errors| {
3412 panic!("{}", rlx_opt::format_legalize_error("cuda", &errors));
3413 });
3414 let graph = crate::precompile::precompile_cleanup(graph, options);
3415 let graph = rlx_opt::LegalizeBroadcast.run(graph);
3417 let compile_result = crate::stages::compile_graph_stages_for_backend(
3419 rlx_driver::Device::Cuda,
3420 graph,
3421 options,
3422 CUDA_SUPPORTED_OPS,
3423 );
3424 crate::stages::maybe_log_fusion(&compile_result.fusion);
3425 let graph = compile_result.lir.into_graph();
3426 let graph = match options.policy.clone() {
3427 Some(p) => rlx_opt::AutoMixedPrecision::new(p).run(graph),
3428 None => graph,
3429 };
3430 let (graph, io_manifest) = cpu_low_precision::prepare_f32_exec_graph(graph);
3431 Box::new(CudaExecutableWrapper {
3432 inner: CudaExecutable::compile_rng(graph, options.rng),
3433 io_manifest,
3434 })
3435 }
3436
3437 fn compile_lir(
3438 &self,
3439 lir: LirModule,
3440 options: &CompileOptions,
3441 ) -> Box<dyn ExecutableGraph> {
3442 use rlx_opt::pass::Pass as _;
3443 let graph = rlx_opt::LegalizeBroadcast.run(lir.into_graph());
3444 let (graph, io_manifest) =
3445 cpu_low_precision::prepare_f32_exec_graph(prepare_fused_graph(
3446 rlx_cuda::unfuse::unfuse(graph),
3447 options,
3448 CUDA_SUPPORTED_OPS,
3449 "cuda",
3450 ));
3451 Box::new(CudaExecutableWrapper {
3452 inner: CudaExecutable::compile_rng(graph, options.rng),
3453 io_manifest,
3454 })
3455 }
3456 }
3457
3458 struct CudaExecutableWrapper {
3459 inner: CudaExecutable,
3460 io_manifest: cpu_low_precision::IoDtypeManifest,
3461 }
3462
3463 unsafe impl Send for CudaExecutableWrapper {}
3468
3469 impl ExecutableGraph for CudaExecutableWrapper {
3470 fn set_param(&mut self, name: &str, data: &[f32]) {
3471 self.inner.set_param(name, data);
3472 }
3473 fn run(&mut self, inputs: &[(&str, &[f32])]) -> Vec<Vec<f32>> {
3474 self.inner.run(inputs)
3475 }
3476 fn run_read_outputs(
3477 &mut self,
3478 inputs: &[(&str, &[f32])],
3479 read_indices: Option<&[usize]>,
3480 ) -> Vec<Vec<f32>> {
3481 self.inner.run_read_outputs(inputs, read_indices)
3482 }
3483 fn bind_gpu_handle(&mut self, name: &str, data: &[f32]) -> bool {
3484 self.inner.bind_gpu_handle(name, data)
3485 }
3486 fn has_gpu_handle(&self, name: &str) -> bool {
3487 self.inner.has_gpu_handle(name)
3488 }
3489 fn set_gpu_handle_feed(&mut self, handle_name: &str, output_index: usize) -> bool {
3490 self.inner.set_gpu_handle_feed(handle_name, output_index);
3491 true
3492 }
3493 fn read_gpu_handle(&self, name: &str) -> Option<Vec<f32>> {
3494 self.inner.read_gpu_handle(name)
3495 }
3496 fn register_kv_row_feed(&mut self, handle_name: &str, output_index: usize) -> bool {
3497 self.inner.register_kv_row_feed(handle_name, output_index);
3498 true
3499 }
3500 fn feed_kv_row(&mut self, src_row: usize, dst_row: usize, row_elems: usize) -> bool {
3501 self.inner.feed_kv_row(src_row, dst_row, row_elems);
3502 true
3503 }
3504 fn read_output_row(
3505 &self,
3506 out_idx: usize,
3507 row: usize,
3508 row_inner: usize,
3509 ) -> Option<Vec<f32>> {
3510 self.inner.read_output_row(out_idx, row, row_inner)
3511 }
3512 fn read_gpu_handle_row(
3513 &self,
3514 name: &str,
3515 row: usize,
3516 row_inner: usize,
3517 ) -> Option<Vec<f32>> {
3518 self.inner.read_gpu_handle_row(name, row, row_inner)
3519 }
3520 fn prepare_resident_gpu_handle(&mut self, name: &str) -> bool {
3521 self.inner.prepare_resident_gpu_handle(name)
3522 }
3523 fn stage_bound_gpu_handles_to_arena(&mut self) {
3524 self.inner.stage_bound_gpu_handles_to_arena();
3525 }
3526 fn seed_resident_kv_prefix_from(
3527 &mut self,
3528 src: &dyn ExecutableGraph,
3529 prefix_tokens: usize,
3530 outgoing_upper: usize,
3531 kv_dim: usize,
3532 n_layers: usize,
3533 ) -> bool {
3534 let Some(dst_exe) = self.cuda_executable_for_kv_seed() else {
3535 return false;
3536 };
3537 let Some(src_exe) = src.cuda_executable_for_kv_seed_ref() else {
3538 return false;
3539 };
3540 dst_exe.seed_resident_kv_prefix_from(
3541 src_exe,
3542 prefix_tokens,
3543 outgoing_upper,
3544 kv_dim,
3545 n_layers,
3546 )
3547 }
3548 fn copy_resident_kv_rows_from(
3549 &mut self,
3550 src: &dyn ExecutableGraph,
3551 from_row: usize,
3552 to_row: usize,
3553 outgoing_upper: usize,
3554 kv_dim: usize,
3555 n_layers: usize,
3556 ) -> bool {
3557 let Some(dst_exe) = self.cuda_executable_for_kv_seed() else {
3558 return false;
3559 };
3560 let Some(src_exe) = src.cuda_executable_for_kv_seed_ref() else {
3561 return false;
3562 };
3563 dst_exe.copy_resident_kv_rows_from(
3564 src_exe,
3565 from_row,
3566 to_row,
3567 outgoing_upper,
3568 kv_dim,
3569 n_layers,
3570 )
3571 }
3572 fn cuda_executable_for_kv_seed(
3573 &mut self,
3574 ) -> Option<&mut rlx_cuda::backend::CudaExecutable> {
3575 Some(&mut self.inner)
3576 }
3577 fn cuda_executable_for_kv_seed_ref(&self) -> Option<&rlx_cuda::backend::CudaExecutable> {
3578 Some(&self.inner)
3579 }
3580 fn set_active_extent(&mut self, extent: Option<(usize, usize)>) {
3581 self.inner.set_active_extent(extent);
3582 }
3583
3584 fn set_rng(&mut self, rng: rlx_ir::RngOptions) {
3585 self.inner.set_rng(rng);
3586 }
3587
3588 fn rng(&self) -> rlx_ir::RngOptions {
3589 self.inner.rng()
3590 }
3591
3592 fn run_slots(&mut self, inputs: &[&[f32]]) -> &[(usize, usize)] {
3593 self.inner.run_slots(inputs)
3594 }
3595
3596 fn arena_ptr(&self) -> *const u8 {
3597 self.inner.arena_ptr()
3598 }
3599
3600 fn set_param_typed(&mut self, name: &str, data: &[u8], dtype: rlx_ir::DType) {
3605 if matches!(dtype, rlx_ir::DType::U8 | rlx_ir::DType::I8) {
3606 self.inner.set_param_bytes(name, data);
3607 return;
3608 }
3609 if dtype == rlx_ir::DType::F32 {
3610 let n = data.len() / 4;
3611 let s = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const f32, n) };
3612 self.inner.set_param(name, s);
3613 } else {
3614 let f32_buf = super::widen_bytes_to_f32(data, dtype);
3615 self.inner.set_param(name, &f32_buf);
3616 }
3617 }
3618
3619 fn run_typed(
3622 &mut self,
3623 inputs: &[(&str, &[u8], rlx_ir::DType)],
3624 ) -> Vec<(Vec<u8>, rlx_ir::DType)> {
3625 let mut owned: Vec<(String, Vec<f32>)> = Vec::with_capacity(inputs.len());
3626 for (name, data, dt) in inputs {
3627 let v = super::widen_bytes_to_f32(data, *dt);
3628 owned.push((name.to_string(), v));
3629 }
3630 let refs: Vec<(&str, &[f32])> = owned
3631 .iter()
3632 .map(|(n, d)| (n.as_str(), d.as_slice()))
3633 .collect();
3634 let dtypes =
3635 super::declared_output_dtypes(&self.io_manifest, self.inner.output_dtypes());
3636 let outs = self.inner.run(&refs);
3637 outs.into_iter()
3638 .zip(
3639 dtypes
3640 .into_iter()
3641 .chain(std::iter::repeat(rlx_ir::DType::F32)),
3642 )
3643 .map(|(v, dt)| (super::narrow_f32_to_bytes(&v, dt), dt))
3644 .collect()
3645 }
3646
3647 fn clone_box(&self) -> Box<dyn ExecutableGraph> {
3648 Box::new(CudaExecutableWrapper {
3649 inner: self.inner.clone_for_cache(),
3650 io_manifest: self.io_manifest.clone(),
3651 })
3652 }
3653 }
3654}
3655
3656#[cfg(feature = "rocm")]
3659pub mod rocm_backend {
3660 use super::*;
3661 use rlx_rocm::backend::RocmExecutable;
3662
3663 pub struct RocmBackend;
3664
3665 const ROCM_SUPPORTED_OPS: &[rlx_ir::OpKind] = {
3668 use rlx_ir::OpKind::*;
3669 &[
3670 Input,
3671 Param,
3672 Constant,
3673 Activation,
3674 Cast,
3675 StopGradient,
3676 Binary,
3677 Compare,
3678 Where,
3679 ElementwiseRegion,
3680 TransformRegion,
3681 BatchElementwiseRegion,
3682 MatMul,
3683 ScaledMatMul,
3684 ScaledQuantize,
3685 ScaledQuantScale,
3686 ScaledDequantize,
3687 DotGeneral,
3688 LayerNorm,
3689 LayerNorm2d,
3690 GroupNorm,
3691 ResizeNearest2x,
3692 AxialRope2d,
3693 Reverse,
3694 ArgMax,
3695 ArgMin,
3696 RmsNorm,
3697 Attention,
3698 AttentionBackward,
3699 RmsNormBackwardInput,
3700 RmsNormBackwardGamma,
3701 RmsNormBackwardBeta,
3702 RopeBackward,
3703 CumsumBackward,
3704 GatherBackward,
3705 Rope,
3706 Reshape,
3707 Transpose,
3708 Narrow,
3709 Concat,
3710 Expand,
3711 Gather,
3712 Reduce,
3713 Softmax,
3714 Cumsum,
3715 TopK,
3716 Sample,
3717 Conv,
3718 ConvTranspose2d,
3719 Pool,
3720 GroupedMatMul,
3721 DequantGroupedMatMul,
3722 DequantMoEWeights,
3723 ScatterAdd,
3724 DequantMatMul,
3725 SelectiveScan,
3726 Lstm,
3727 Scan,
3730 FusedMatMulBiasAct,
3731 FusedResidualLN,
3732 FusedResidualRmsNorm,
3733 FusedAttentionBlock,
3737 GaussianSplatRender,
3738 GaussianSplatRenderBackward,
3739 GaussianSplatPrepare,
3740 GaussianSplatRasterize,
3741 Custom,
3742 Fft,
3743 LogMel,
3744 LogMelBackward,
3745 WelchPeaks,
3746 Im2Col,
3747 RngNormal,
3748 RngUniform,
3749 ]
3750 };
3751
3752 impl Backend for RocmBackend {
3753 fn supported_ops(&self) -> &'static [rlx_ir::OpKind] {
3754 ROCM_SUPPORTED_OPS
3755 }
3756
3757 fn compile(&self, graph: Graph, options: &CompileOptions) -> Box<dyn ExecutableGraph> {
3758 use rlx_opt::pass::Pass as _;
3759 let graph = rlx_rocm::unfuse::unfuse(graph);
3760 let graph = rlx_opt::legalize_or_rewrite_for_backend(graph, ROCM_SUPPORTED_OPS)
3761 .unwrap_or_else(|errors| {
3762 panic!("{}", rlx_opt::format_legalize_error("rocm", &errors));
3763 });
3764 let graph = crate::precompile::precompile_cleanup(graph, options);
3765 let graph = rlx_opt::LegalizeBroadcast.run(graph);
3766 let compile_result = crate::stages::compile_graph_stages_for_backend(
3767 rlx_driver::Device::Rocm,
3768 graph,
3769 options,
3770 ROCM_SUPPORTED_OPS,
3771 );
3772 crate::stages::maybe_log_fusion(&compile_result.fusion);
3773 let graph = compile_result.lir.into_graph();
3774 let graph = match options.policy.clone() {
3775 Some(p) => rlx_opt::AutoMixedPrecision::new(p).run(graph),
3776 None => graph,
3777 };
3778 let (graph, io_manifest) = cpu_low_precision::prepare_f32_exec_graph(graph);
3779 Box::new(RocmExecutableWrapper {
3780 inner: RocmExecutable::compile_rng(graph, options.rng),
3781 io_manifest,
3782 })
3783 }
3784
3785 fn compile_lir(
3786 &self,
3787 lir: LirModule,
3788 options: &CompileOptions,
3789 ) -> Box<dyn ExecutableGraph> {
3790 let (graph, io_manifest) =
3791 cpu_low_precision::prepare_f32_exec_graph(prepare_fused_graph(
3792 rlx_rocm::unfuse::unfuse(lir.into_graph()),
3793 options,
3794 ROCM_SUPPORTED_OPS,
3795 "rocm",
3796 ));
3797 Box::new(RocmExecutableWrapper {
3798 inner: RocmExecutable::compile_rng(graph, options.rng),
3799 io_manifest,
3800 })
3801 }
3802 }
3803
3804 struct RocmExecutableWrapper {
3805 inner: RocmExecutable,
3806 io_manifest: cpu_low_precision::IoDtypeManifest,
3807 }
3808
3809 unsafe impl Send for RocmExecutableWrapper {}
3813
3814 impl ExecutableGraph for RocmExecutableWrapper {
3815 fn set_param(&mut self, name: &str, data: &[f32]) {
3816 self.inner.set_param(name, data);
3817 }
3818 fn run(&mut self, inputs: &[(&str, &[f32])]) -> Vec<Vec<f32>> {
3819 self.inner.run(inputs)
3820 }
3821 fn run_read_outputs(
3822 &mut self,
3823 inputs: &[(&str, &[f32])],
3824 read_indices: Option<&[usize]>,
3825 ) -> Vec<Vec<f32>> {
3826 self.inner.run_read_outputs(inputs, read_indices)
3827 }
3828 fn bind_gpu_handle(&mut self, name: &str, data: &[f32]) -> bool {
3829 self.inner.bind_gpu_handle(name, data)
3830 }
3831 fn has_gpu_handle(&self, name: &str) -> bool {
3832 self.inner.has_gpu_handle(name)
3833 }
3834 fn set_gpu_handle_feed(&mut self, handle_name: &str, output_index: usize) -> bool {
3835 self.inner.set_gpu_handle_feed(handle_name, output_index);
3836 true
3837 }
3838 fn read_gpu_handle(&self, name: &str) -> Option<Vec<f32>> {
3839 self.inner.read_gpu_handle(name)
3840 }
3841 fn run_slots(&mut self, inputs: &[&[f32]]) -> &[(usize, usize)] {
3842 self.inner.run_slots(inputs)
3843 }
3844 fn arena_ptr(&self) -> *const u8 {
3845 self.inner.arena_ptr()
3846 }
3847 fn set_active_extent(&mut self, extent: Option<(usize, usize)>) {
3848 self.inner.set_active_extent(extent);
3849 }
3850
3851 fn set_rng(&mut self, rng: rlx_ir::RngOptions) {
3852 self.inner.set_rng(rng);
3853 }
3854
3855 fn rng(&self) -> rlx_ir::RngOptions {
3856 self.inner.rng()
3857 }
3858
3859 fn set_param_typed(&mut self, name: &str, data: &[u8], dtype: rlx_ir::DType) {
3864 if matches!(dtype, rlx_ir::DType::U8 | rlx_ir::DType::I8) {
3865 self.inner.set_param_bytes(name, data);
3866 return;
3867 }
3868 if dtype == rlx_ir::DType::F32 {
3869 let n = data.len() / 4;
3870 let s = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const f32, n) };
3871 self.inner.set_param(name, s);
3872 } else {
3873 let f32_buf = super::widen_bytes_to_f32(data, dtype);
3874 self.inner.set_param(name, &f32_buf);
3875 }
3876 }
3877
3878 fn run_typed(
3881 &mut self,
3882 inputs: &[(&str, &[u8], rlx_ir::DType)],
3883 ) -> Vec<(Vec<u8>, rlx_ir::DType)> {
3884 let mut owned: Vec<(String, Vec<f32>)> = Vec::with_capacity(inputs.len());
3885 for (name, data, dt) in inputs {
3886 let v = super::widen_bytes_to_f32(data, *dt);
3887 owned.push((name.to_string(), v));
3888 }
3889 let refs: Vec<(&str, &[f32])> = owned
3890 .iter()
3891 .map(|(n, d)| (n.as_str(), d.as_slice()))
3892 .collect();
3893 let dtypes =
3894 super::declared_output_dtypes(&self.io_manifest, self.inner.output_dtypes());
3895 let outs = self.inner.run(&refs);
3896 outs.into_iter()
3897 .zip(
3898 dtypes
3899 .into_iter()
3900 .chain(std::iter::repeat(rlx_ir::DType::F32)),
3901 )
3902 .map(|(v, dt)| (super::narrow_f32_to_bytes(&v, dt), dt))
3903 .collect()
3904 }
3905
3906 fn clone_box(&self) -> Box<dyn ExecutableGraph> {
3907 Box::new(RocmExecutableWrapper {
3908 inner: self.inner.clone_for_cache(),
3909 io_manifest: self.io_manifest.clone(),
3910 })
3911 }
3912 }
3913}
3914
3915#[cfg(feature = "tpu")]
3918pub mod tpu_backend {
3919 use super::*;
3920 use rlx_tpu::TpuExecutable;
3921
3922 pub struct TpuBackend;
3923
3924 const TPU_SUPPORTED_OPS: &[rlx_ir::OpKind] = {
3932 use rlx_ir::OpKind::*;
3933 &[
3934 Input,
3935 Param,
3936 Constant,
3937 Activation,
3938 Cast,
3939 StopGradient,
3940 Binary,
3941 Compare,
3942 Where,
3943 ElementwiseRegion,
3944 TransformRegion,
3945 BatchElementwiseRegion,
3946 MatMul,
3947 DotGeneral,
3948 LayerNorm,
3949 RmsNorm,
3950 Attention,
3951 Rope,
3952 Reshape,
3953 Transpose,
3954 Narrow,
3955 Concat,
3956 Expand,
3957 Gather,
3958 Reduce,
3959 Softmax,
3960 Cumsum,
3961 TopK,
3962 Sample,
3963 Conv,
3964 Pool,
3965 GroupedMatMul,
3966 DequantGroupedMatMul,
3967 DequantMoEWeights,
3968 ScatterAdd,
3969 DequantMatMul,
3970 SelectiveScan,
3971 QMatMul,
3973 QConv2d,
3974 Quantize,
3975 Dequantize,
3976 FusedMatMulBiasAct,
3977 FusedResidualLN,
3978 FusedResidualRmsNorm,
3979 FusedAttentionBlock,
3982 Fft,
3983 LogMel,
3984 LogMelBackward,
3985 WelchPeaks,
3986 RngNormal,
3987 RngUniform,
3988 ]
3990 };
3991
3992 impl Backend for TpuBackend {
3993 fn supported_ops(&self) -> &'static [rlx_ir::OpKind] {
3994 TPU_SUPPORTED_OPS
3995 }
3996
3997 fn compile(&self, graph: Graph, options: &CompileOptions) -> Box<dyn ExecutableGraph> {
3998 let graph = rlx_opt::legalize_or_rewrite_for_backend_with_config(
3999 graph,
4000 TPU_SUPPORTED_OPS,
4001 options.kernel_dispatch,
4002 )
4003 .unwrap_or_else(|errors| {
4004 panic!("{}", rlx_opt::format_legalize_error("tpu", &errors));
4005 });
4006 use rlx_opt::pass::Pass as _;
4022 let policy = options
4023 .policy
4024 .clone()
4025 .unwrap_or(rlx_opt::PrecisionPolicy::AutoMixedBf16);
4026 let graph = rlx_opt::AutoMixedPrecision::new(policy).run(graph);
4027 let _ = options.dce;
4028 let _ = options.constant_folding;
4029 Box::new(TpuExecutableWrapper {
4030 inner: TpuExecutable::compile_rng_with_param_bytes(
4031 graph,
4032 options.rng,
4033 options.quant_param_bindings.as_ref(),
4034 ),
4035 })
4036 }
4037 }
4038
4039 struct TpuExecutableWrapper {
4040 inner: TpuExecutable,
4041 }
4042
4043 unsafe impl Send for TpuExecutableWrapper {}
4047
4048 impl ExecutableGraph for TpuExecutableWrapper {
4049 fn set_param(&mut self, name: &str, data: &[f32]) {
4050 self.inner.set_param(name, data);
4051 }
4052 fn run(&mut self, inputs: &[(&str, &[f32])]) -> Vec<Vec<f32>> {
4053 self.inner.run(inputs)
4054 }
4055
4056 fn set_param_typed(&mut self, name: &str, data: &[u8], dtype: rlx_ir::DType) {
4061 if dtype == rlx_ir::DType::F32 {
4062 let n = data.len() / 4;
4063 let s = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const f32, n) };
4064 self.inner.set_param(name, s);
4065 } else {
4066 let f32_buf = super::widen_bytes_to_f32(data, dtype);
4067 self.inner.set_param(name, &f32_buf);
4068 }
4069 }
4070
4071 fn run_typed(
4072 &mut self,
4073 inputs: &[(&str, &[u8], rlx_ir::DType)],
4074 ) -> Vec<(Vec<u8>, rlx_ir::DType)> {
4075 let mut owned: Vec<(String, Vec<f32>)> = Vec::with_capacity(inputs.len());
4076 for (name, data, dt) in inputs {
4077 let v = super::widen_bytes_to_f32(data, *dt);
4078 owned.push((name.to_string(), v));
4079 }
4080 let refs: Vec<(&str, &[f32])> = owned
4081 .iter()
4082 .map(|(n, d)| (n.as_str(), d.as_slice()))
4083 .collect();
4084 let dtypes = self.inner.output_dtypes();
4085 let outs = self.inner.run(&refs);
4086 outs.into_iter()
4087 .zip(
4088 dtypes
4089 .into_iter()
4090 .chain(std::iter::repeat(rlx_ir::DType::F32)),
4091 )
4092 .map(|(v, dt)| (super::narrow_f32_to_bytes(&v, dt), dt))
4093 .collect()
4094 }
4095
4096 fn clone_box(&self) -> Box<dyn ExecutableGraph> {
4097 Box::new(TpuExecutableWrapper {
4098 inner: self.inner.clone_for_cache(),
4099 })
4100 }
4101 }
4102}
4103
4104#[cfg(feature = "qnn")]
4109pub mod qnn_backend {
4110 use super::*;
4111 use rlx_qnn::runtime::QnnExecutable;
4112
4113 pub struct QnnBackend;
4114
4115 impl Backend for QnnBackend {
4116 fn compile(&self, graph: Graph, _options: &CompileOptions) -> Box<dyn ExecutableGraph> {
4123 let exec = QnnExecutable::compile_graph(&graph)
4124 .unwrap_or_else(|e| panic!("rlx-qnn compile failed: {e}"));
4125 Box::new(QnnExecutableWrapper { inner: exec })
4126 }
4127
4128 fn compile_lir(
4129 &self,
4130 lir: LirModule,
4131 options: &CompileOptions,
4132 ) -> Box<dyn ExecutableGraph> {
4133 self.compile(lir.into_graph(), options)
4136 }
4137 }
4138
4139 struct QnnExecutableWrapper {
4140 inner: QnnExecutable,
4141 }
4142
4143 impl ExecutableGraph for QnnExecutableWrapper {
4144 fn set_param(&mut self, name: &str, data: &[f32]) {
4145 self.inner.set_param(name, data);
4148 }
4149
4150 fn run(&mut self, inputs: &[(&str, &[f32])]) -> Vec<Vec<f32>> {
4151 self.inner
4152 .run(inputs)
4153 .unwrap_or_else(|e| panic!("rlx-qnn run failed: {e}"))
4154 }
4155 }
4156}