nxpu_backend_samsung/
lib.rs1use 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#[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}