Skip to main content

nxpu_backend_samsung/
lib.rs

1//! Samsung Exynos NPU backend for NxPU.
2//!
3//! Delegates compilation to the ONNX backend with Samsung Exynos NPU-specific
4//! validation and ONE toolchain compilation hints.
5
6use nxpu_analysis::analyze;
7use nxpu_backend_core::{
8    Backend, BackendError, BackendOptions, BackendOutput, Diagnostic, DiagnosticLevel, Precision,
9    PrecisionPolicy, validate_patterns,
10};
11use nxpu_backend_onnx::OnnxBackend;
12use nxpu_ir::Module;
13
14mod support;
15
16use support::SamsungNpuSupport;
17
18/// Samsung Exynos NPU backend with ONE toolchain hints.
19#[derive(Debug)]
20pub struct SamsungBackend;
21
22impl Backend for SamsungBackend {
23    fn name(&self) -> &str {
24        "Samsung Exynos NPU"
25    }
26
27    fn targets(&self) -> &[&str] {
28        &["samsung", "exynos"]
29    }
30
31    fn preferred_precision(&self) -> Precision {
32        Precision::F16
33    }
34
35    fn compile(
36        &self,
37        module: &Module,
38        opts: &BackendOptions,
39    ) -> Result<BackendOutput, BackendError> {
40        let mut op_names = Vec::new();
41        for (i, ep) in module.entry_points.iter().enumerate() {
42            match analyze::classify_entry_point(module, i) {
43                Ok(pattern) => op_names.extend(analyze::pattern_op_names(&pattern)),
44                Err(e) => {
45                    return Err(BackendError::Unsupported(format!(
46                        "entry point '{}': {e}",
47                        ep.name
48                    )));
49                }
50            }
51        }
52
53        let precision = resolve_precision(opts, self.preferred_precision());
54        let op_refs: Vec<&str> = op_names.iter().map(|s| s.as_str()).collect();
55        let mut diagnostics = validate_patterns(&SamsungNpuSupport, &op_refs, precision);
56
57        let mut output = OnnxBackend.compile(module, opts)?;
58        diagnostics.extend(output.diagnostics);
59
60        diagnostics.push(Diagnostic {
61            level: DiagnosticLevel::Info,
62            message: "To import for Exynos NPU: one-import-onnx -i output.onnx -o model.circle"
63                .into(),
64        });
65        diagnostics.push(Diagnostic {
66            level: DiagnosticLevel::Info,
67            message: "To compile: one-codegen -b npu model.circle -o model.tvn".into(),
68        });
69        diagnostics.push(Diagnostic {
70            level: DiagnosticLevel::Info,
71            message:
72                "For quantization: one-quantize -i model.circle -o model_q.circle --input_dtype float32 --quantized_dtype uint8"
73                    .into(),
74        });
75
76        output.diagnostics = diagnostics;
77        Ok(output)
78    }
79}
80
81fn resolve_precision(opts: &BackendOptions, preferred: Precision) -> Precision {
82    match opts.precision {
83        PrecisionPolicy::Explicit(p) => p,
84        PrecisionPolicy::Auto => preferred,
85        PrecisionPolicy::Keep => Precision::F32,
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92    use nxpu_backend_core::{BackendOptions, OutputContent};
93
94    #[test]
95    fn backend_metadata() {
96        let backend = SamsungBackend;
97        assert_eq!(backend.name(), "Samsung Exynos NPU");
98        assert!(backend.targets().contains(&"samsung"));
99        assert!(backend.targets().contains(&"exynos"));
100        assert_eq!(backend.preferred_precision(), Precision::F16);
101    }
102
103    #[test]
104    fn compile_matmul_with_one_hints() {
105        let source = std::fs::read_to_string(concat!(
106            env!("CARGO_MANIFEST_DIR"),
107            "/../../examples/matmul.wgsl"
108        ))
109        .unwrap();
110        let module = nxpu_parser::parse(&source).unwrap();
111
112        let output = SamsungBackend
113            .compile(&module, &BackendOptions::default())
114            .unwrap();
115        assert_eq!(output.files.len(), 1);
116        assert_eq!(output.files[0].name, "output.onnx");
117        assert!(matches!(output.files[0].content, OutputContent::Binary(_)));
118
119        let messages: Vec<&str> = output
120            .diagnostics
121            .iter()
122            .map(|d| d.message.as_str())
123            .collect();
124        assert!(messages.iter().any(|m| m.contains("one-import-onnx")));
125        assert!(messages.iter().any(|m| m.contains("one-codegen")));
126    }
127
128    fn load_and_compile(example: &str, opts: &BackendOptions) -> BackendOutput {
129        let source = std::fs::read_to_string(format!(
130            "{}/../../examples/{example}.wgsl",
131            env!("CARGO_MANIFEST_DIR")
132        ))
133        .unwrap();
134        let module = nxpu_parser::parse(&source).unwrap();
135        SamsungBackend.compile(&module, opts).unwrap()
136    }
137
138    #[test]
139    fn compile_conv2d() {
140        let output = load_and_compile("conv2d", &BackendOptions::default());
141        assert_ne!(output.files.len(), 0);
142        for file in &output.files {
143            assert_ne!(file.content.len(), 0);
144        }
145    }
146
147    #[test]
148    fn compile_relu() {
149        let output = load_and_compile("relu", &BackendOptions::default());
150        assert_ne!(output.files.len(), 0);
151        for file in &output.files {
152            assert_ne!(file.content.len(), 0);
153        }
154    }
155
156    #[test]
157    fn compile_attention() {
158        let output = load_and_compile("attention", &BackendOptions::default());
159        assert_ne!(output.files.len(), 0);
160        for file in &output.files {
161            assert_ne!(file.content.len(), 0);
162        }
163    }
164
165    #[test]
166    fn resolve_precision_explicit_and_keep() {
167        let explicit_opts = BackendOptions {
168            precision: PrecisionPolicy::Explicit(Precision::F16),
169            ..BackendOptions::default()
170        };
171        assert_eq!(
172            resolve_precision(&explicit_opts, Precision::Int8),
173            Precision::F16
174        );
175
176        let keep_opts = BackendOptions {
177            precision: PrecisionPolicy::Keep,
178            ..BackendOptions::default()
179        };
180        assert_eq!(
181            resolve_precision(&keep_opts, Precision::Int8),
182            Precision::F32
183        );
184    }
185
186    #[test]
187    fn all_three_one_diagnostics() {
188        let output = load_and_compile("matmul", &BackendOptions::default());
189        let messages: Vec<&str> = output
190            .diagnostics
191            .iter()
192            .map(|d| d.message.as_str())
193            .collect();
194        assert!(messages.iter().any(|m| m.contains("one-import-onnx")));
195        assert!(messages.iter().any(|m| m.contains("one-codegen")));
196        assert!(messages.iter().any(|m| m.contains("one-quantize")));
197    }
198}