1use std::borrow::Cow;
2
3use crate::internal::*;
4use crate::ops::matmul::de_block_quant::BlockQuantTransform;
5use std::fmt::Debug;
6
7use tract_data::TractResult;
8
9use crate::floats::FloatPrecisionTranslator;
10use crate::ops::nn::TypedModel;
11
12#[macro_export]
13macro_rules! rule_if {
14 ($cond:expr) => {
15 if !$cond {
16 return Ok(None);
17 }
18 };
19}
20
21#[macro_export]
22macro_rules! rule_if_let {
23 ($pat:pat = $expr:expr) => {
24 let $pat = $expr else {
25 return Ok(None);
26 };
27 };
28}
29
30#[macro_export]
31macro_rules! rule_if_some {
32 ($pat:pat = $expr:expr) => {
33 let Some($pat) = $expr else {
34 return Ok(None);
35 };
36 };
37}
38
39#[derive(Debug, Clone, Default)]
44pub struct NodeFilter {
45 pub include: Option<Vec<String>>,
46 pub exclude: Option<Vec<String>>,
47}
48
49impl NodeFilter {
50 pub fn matches(&self, name: &str) -> bool {
52 let dominated = match &self.include {
53 Some(patterns) => patterns.iter().any(|p| name.contains(p)),
54 None => true,
55 };
56 if !dominated {
57 return false;
58 }
59 match &self.exclude {
60 Some(patterns) => !patterns.iter().any(|p| name.contains(p)),
61 None => true,
62 }
63 }
64
65 pub fn is_pass_through(&self) -> bool {
67 self.include.is_none() && self.exclude.is_none()
68 }
69}
70
71pub fn parse_legacy_filter(filter: Option<&str>) -> TractResult<NodeFilter> {
73 let Some(filter) = filter.filter(|f| !f.is_empty()) else {
74 return Ok(NodeFilter::default());
75 };
76 if let Some(patterns) = filter.strip_prefix("!=") {
77 let patterns = patterns.split(',').map(|it| it.trim().to_string()).collect();
78 Ok(NodeFilter { exclude: Some(patterns), ..Default::default() })
79 } else if let Some(patterns) = filter.strip_prefix("==") {
80 let patterns = patterns.split(',').map(|it| it.trim().to_string()).collect();
81 Ok(NodeFilter { include: Some(patterns), ..Default::default() })
82 } else {
83 Ok(NodeFilter::default())
84 }
85}
86
87pub fn build_float_translator(
90 from_dt: DatumType,
91 to_dt: DatumType,
92 filter: NodeFilter,
93) -> Box<dyn ModelTransform> {
94 if filter.is_pass_through() {
95 return Box::new(FloatPrecisionTranslator::new(from_dt, to_dt));
96 }
97 Box::new(FloatPrecisionTranslator::with_filter(from_dt, to_dt, move |node| {
98 filter.matches(&node.name)
99 }))
100}
101
102pub trait ModelTransform: Debug {
103 fn name(&self) -> StaticName;
104 fn transform(&self, model: &mut TypedModel) -> TractResult<()>;
105 fn transform_into(&self, mut model: TypedModel) -> TractResult<TypedModel> {
106 self.transform(&mut model)?;
107 Ok(model)
108 }
109}
110
111#[derive(Debug, Default, serde::Deserialize)]
113pub struct FloatTranslatorConfig {
114 #[serde(default)]
116 pub filter: Option<String>,
117 #[serde(default)]
119 pub include: Option<Vec<String>>,
120 #[serde(default)]
122 pub exclude: Option<Vec<String>>,
123}
124
125impl FloatTranslatorConfig {
126 pub fn into_node_filter(self) -> TractResult<NodeFilter> {
127 if self.include.is_some() || self.exclude.is_some() {
128 Ok(NodeFilter { include: self.include, exclude: self.exclude })
129 } else {
130 parse_legacy_filter(self.filter.as_deref())
131 }
132 }
133}
134
135#[derive(Debug, serde::Deserialize)]
137pub struct FloatPrecisionConfig {
138 pub from: String,
139 pub to: String,
140 #[serde(default)]
142 pub include: Option<Vec<String>>,
143 #[serde(default)]
145 pub exclude: Option<Vec<String>>,
146}
147
148pub struct ModelTransformFactory {
149 pub name: &'static str,
150 pub build_default: fn() -> TractResult<Box<dyn ModelTransform>>,
152 pub build: fn(&mut dyn erased_serde::Deserializer) -> TractResult<Box<dyn ModelTransform>>,
154}
155
156inventory::collect!(ModelTransformFactory);
157
158#[macro_export]
159macro_rules! register_simple_model_transform {
160 ($name: expr, $type: expr) => {
161 $crate::internal::inventory::submit! {
162 $crate::transform::ModelTransformFactory {
163 name: $name,
164 build_default: || Ok(Box::new($type)),
165 build: |_de| Ok(Box::new($type)),
166 }
167 }
168 };
169}
170
171#[macro_export]
172macro_rules! register_model_transform {
173 ($name:expr, $config:ty, $builder:expr) => {
174 $crate::internal::inventory::submit! {
175 $crate::transform::ModelTransformFactory {
176 name: $name,
177 build_default: || {
178 let config = <$config>::default();
179 let builder: fn($config) -> $crate::prelude::TractResult<Box<dyn $crate::transform::ModelTransform>> = $builder;
180 builder(config)
181 },
182 build: |de: &mut dyn erased_serde::Deserializer| {
183 let config: $config = erased_serde::deserialize(de)
184 .map_err(|e| $crate::internal::anyhow!("deserializing transform config: {e}"))?;
185 let builder: fn($config) -> $crate::prelude::TractResult<Box<dyn $crate::transform::ModelTransform>> = $builder;
186 builder(config)
187 },
188 }
189 }
190 };
191}
192
193pub fn split_spec(spec: &str) -> (Cow<'_, str>, &str) {
195 if let Some(pos) = spec.find('(') {
196 (Cow::Borrowed(&spec[..pos]), &spec[pos..])
197 } else if spec.contains('-') {
198 (Cow::Owned(spec.replace('-', "_")), "")
200 } else {
201 (Cow::Borrowed(spec), "")
202 }
203}
204
205pub fn get_transform(name: &str) -> TractResult<Option<Box<dyn ModelTransform>>> {
207 let (name, _) = split_spec(name);
208 for factory in inventory::iter::<ModelTransformFactory>() {
209 if factory.name == &*name {
210 return Ok(Some((factory.build_default)()?));
211 }
212 }
213 Ok(None)
214}
215
216pub fn get_transform_with_params(
218 name: &str,
219 de: &mut dyn erased_serde::Deserializer,
220) -> TractResult<Option<Box<dyn ModelTransform>>> {
221 for factory in inventory::iter::<ModelTransformFactory>() {
222 if factory.name == name {
223 return Ok(Some((factory.build)(de)?));
224 }
225 }
226 Ok(None)
227}
228
229#[derive(Debug, serde::Deserialize)]
232#[serde(untagged)]
233pub enum SymbolValueSpec {
234 Int(i64),
235 Expr(String),
236}
237
238#[derive(Debug, Default, serde::Deserialize)]
239pub struct SetSymbolsConfig {
240 pub values: std::collections::HashMap<String, SymbolValueSpec>,
241}
242
243#[derive(Debug)]
244struct SetSymbolsTransform(SetSymbolsConfig);
245
246impl ModelTransform for SetSymbolsTransform {
247 fn name(&self) -> StaticName {
248 "set_symbols".into()
249 }
250
251 fn transform(&self, model: &mut TypedModel) -> TractResult<()> {
252 let mut subs = std::collections::HashMap::new();
253 for (k, spec) in &self.0.values {
254 let sym = model.symbols.sym(k);
255 let dim = match spec {
256 SymbolValueSpec::Int(v) => TDim::Val(*v),
257 SymbolValueSpec::Expr(s) => model
258 .symbols
259 .parse_tdim(s)
260 .with_context(|| format!("Parsing TDim expression {s:?} for symbol {k}"))?,
261 };
262 subs.insert(sym, dim);
263 }
264 *model = model.set_symbols(&subs)?;
265 Ok(())
266 }
267}
268
269register_model_transform!("set_symbols", SetSymbolsConfig, |config| Ok(Box::new(
270 SetSymbolsTransform(config)
271)));
272
273#[derive(Debug)]
285struct ForceScanExternalState;
286
287impl ModelTransform for ForceScanExternalState {
288 fn name(&self) -> StaticName {
289 "force_scan_external_state".into()
290 }
291
292 fn transform(&self, model: &mut TypedModel) -> TractResult<()> {
293 use crate::ops::scan::Scan;
294 for node in &mut model.nodes {
295 if let Some(scan) = node.op_as_mut::<Scan>() {
296 scan.external_state = true;
297 }
298 }
299 Ok(())
300 }
301}
302
303register_simple_model_transform!("force_scan_external_state", ForceScanExternalState);
304
305#[derive(Debug)]
331struct ExportScanState;
332
333impl ModelTransform for ExportScanState {
334 fn name(&self) -> StaticName {
335 "export_scan_state".into()
336 }
337
338 fn transform(&self, model: &mut TypedModel) -> TractResult<()> {
339 use crate::ops::konst::Const;
340 use crate::ops::scan::{InputMapping, Scan};
341
342 let scans: Vec<usize> =
343 model.nodes().iter().filter(|n| n.op_is::<Scan>()).map(|n| n.id).collect();
344
345 for id in scans {
346 let eligible = {
347 let inputs = model.node_input_facts(id)?;
348 let scan = model.node(id).op_as::<Scan>().unwrap();
349 scan.skip == 0 && scan.iteration_count(&inputs).map(|i| i.is_one()).unwrap_or(false)
350 };
351 if !eligible {
352 continue;
353 }
354
355 let scan = model.node(id).op_as::<Scan>().unwrap();
356 let Some(state_output) =
357 scan.output_mapping.iter().find(|om| om.state).and_then(|om| om.scan.map(|s| s.0))
358 else {
359 continue;
360 };
361 let seeded: Vec<usize> = scan
362 .input_mapping
363 .iter()
364 .enumerate()
365 .filter(|(_, im)| matches!(im, InputMapping::State))
366 .map(|(slot, _)| slot)
367 .filter(|&slot| model.node(model.node(id).inputs[slot].node).op_is::<Const>())
368 .collect();
369 if seeded.is_empty() {
370 continue;
371 }
372
373 for slot in seeded {
374 let fact = model.outlet_fact(model.node(id).inputs[slot])?.clone().without_value();
375 let name = format!("{}.state_in", model.node(id).name);
376 let source = model.add_source(name, fact)?;
377 model.add_edge(source, InletId::new(id, slot))?;
378 }
379 model.outputs.push(OutletId::new(id, state_output));
380 model.node_mut(id).op_as_mut::<Scan>().unwrap().external_state = true;
381 }
382 Ok(())
383 }
384}
385
386register_simple_model_transform!("export_scan_state", ExportScanState);
387
388register_simple_model_transform!("block_quant", BlockQuantTransform);
389
390#[derive(Debug, serde::Deserialize, Default)]
391pub struct SelectOutputsConfig {
392 pub outputs: Vec<String>,
393}
394
395#[derive(Debug)]
396struct SelectOutputsTransform(SelectOutputsConfig);
397
398impl ModelTransform for SelectOutputsTransform {
399 fn name(&self) -> StaticName {
400 "select_outputs".into()
401 }
402
403 fn transform(&self, model: &mut TypedModel) -> TractResult<()> {
404 model.select_outputs_by_name(self.0.outputs.iter())
405 }
406}
407
408register_model_transform!("select_outputs", SelectOutputsConfig, |config| Ok(Box::new(
409 SelectOutputsTransform(config)
410)));
411
412#[derive(Debug, serde::Deserialize, Default)]
413pub struct SelectInputsConfig {
414 pub inputs: Vec<String>,
415}
416
417#[derive(Debug)]
418struct SelectInputsTransform(SelectInputsConfig);
419
420impl ModelTransform for SelectInputsTransform {
421 fn name(&self) -> StaticName {
422 "select_inputs".into()
423 }
424
425 fn transform(&self, model: &mut TypedModel) -> TractResult<()> {
426 model.select_inputs_by_name(self.0.inputs.iter())
427 }
428}
429
430register_model_transform!("select_inputs", SelectInputsConfig, |config| Ok(Box::new(
431 SelectInputsTransform(config)
432)));
433
434inventory::submit! {
435 ModelTransformFactory {
436 name: "f32_to_f16",
437 build_default: || Ok(build_float_translator(DatumType::F32, DatumType::F16, NodeFilter::default())),
438 build: |de| {
439 let config: FloatTranslatorConfig = erased_serde::deserialize(de)
440 .map_err(|e| anyhow::anyhow!("deserializing f32_to_f16 config: {e}"))?;
441 Ok(build_float_translator(DatumType::F32, DatumType::F16, config.into_node_filter()?))
442 },
443 }
444}
445
446inventory::submit! {
447 ModelTransformFactory {
448 name: "f16_to_f32",
449 build_default: || Ok(build_float_translator(DatumType::F16, DatumType::F32, NodeFilter::default())),
450 build: |de| {
451 let config: FloatTranslatorConfig = erased_serde::deserialize(de)
452 .map_err(|e| anyhow::anyhow!("deserializing f16_to_f32 config: {e}"))?;
453 Ok(build_float_translator(DatumType::F16, DatumType::F32, config.into_node_filter()?))
454 },
455 }
456}
457
458inventory::submit! {
459 ModelTransformFactory {
460 name: "float_precision",
461 build_default: || {
462 anyhow::bail!("float_precision transform requires 'from' and 'to' parameters")
463 },
464 build: |de| {
465 let config: FloatPrecisionConfig = erased_serde::deserialize(de)
466 .map_err(|e| anyhow::anyhow!("deserializing float_precision config: {e}"))?;
467 let from_dt: DatumType = config.from.parse()
468 .map_err(|e| anyhow::anyhow!("parsing 'from' datum type: {e}"))?;
469 let to_dt: DatumType = config.to.parse()
470 .map_err(|e| anyhow::anyhow!("parsing 'to' datum type: {e}"))?;
471 let filter = NodeFilter { include: config.include, exclude: config.exclude };
472 Ok(build_float_translator(from_dt, to_dt, filter))
473 },
474 }
475}