1use std::path::Path;
2use std::str::{FromStr, from_utf8};
3
4use super::graph_state::GraphState;
5use super::ir::{
6 ArgType, Argument, AttributeValue, Attributes, NodeType, RawNode, TensorData, TensorDataExt,
7 TensorType,
8};
9use super::protos::{
10 AttributeProto, NodeProto, TensorProto, ValueInfoProto,
11 attribute_proto::AttributeType,
12 tensor_proto::{DataLocation, DataType as DT},
13 tensor_shape_proto::dimension::Value,
14};
15use crate::external_data::ExternalDataInfo;
16use crate::tensor_store::TensorDataRef;
17
18use burn_tensor::{BoolStore, DType};
19use protobuf::Enum;
20
21pub const DEFAULT_OPSET_VERSION: usize = 16;
25
26#[derive(Debug)]
28pub enum ParseError {
29 VariantNotFound(String),
30}
31
32pub fn sanitize_name(name: &str) -> String {
49 if name.is_empty() {
51 return String::new();
52 }
53
54 let mut result = String::with_capacity(name.len() * 2);
55 let mut prev_is_lower = false;
56 let mut prev_is_underscore = false;
57
58 for (i, c) in name.chars().enumerate() {
59 if c == '_' {
60 if !prev_is_underscore || i == 0 {
62 result.push('_');
63 prev_is_underscore = true;
64 }
65 prev_is_lower = false;
66 } else if c.is_ascii_alphanumeric() {
67 if c.is_ascii_uppercase() && prev_is_lower && !prev_is_underscore {
69 result.push('_');
70 }
71
72 result.push(c.to_ascii_lowercase());
73 prev_is_lower = c.is_ascii_lowercase();
74 prev_is_underscore = false;
75 } else {
76 if !prev_is_underscore && i > 0 {
78 result.push('_');
79 prev_is_underscore = true;
80 }
81 prev_is_lower = false;
82 }
83 }
84
85 while result.ends_with('_') && result.len() > 1 {
87 let check = result.trim_end_matches('_');
89 if check.is_empty() {
90 break;
92 }
93 result.pop();
94 }
95
96 if !result.is_empty() && !result.starts_with(|c: char| c.is_ascii_alphabetic() || c == '_') {
98 result = format!("_{result}");
99 }
100
101 result
102}
103
104pub fn element_type_from_proto(dt_i32: i32) -> Result<DType, String> {
106 match DT::from_i32(dt_i32).ok_or_else(|| format!("unknown dtype {}", dt_i32))? {
107 DT::FLOAT => Ok(DType::F32),
108 DT::DOUBLE => Ok(DType::F64),
109 DT::FLOAT16 => Ok(DType::F16),
110 DT::BFLOAT16 => Ok(DType::BF16),
111 DT::INT64 => Ok(DType::I64),
112 DT::INT32 => Ok(DType::I32),
113 DT::INT16 => Ok(DType::I16),
114 DT::INT8 => Ok(DType::I8),
115 DT::UINT64 => Ok(DType::U64),
116 DT::UINT32 => Ok(DType::U32),
117 DT::UINT16 => Ok(DType::U16),
118 DT::UINT8 => Ok(DType::U8),
119 DT::BOOL => Ok(DType::Bool(BoolStore::Native)),
120 DT::STRING => Err("String tensors not supported".to_string()),
121 other => Err(format!("unsupported dtype {:?}", other)),
122 }
123}
124
125pub fn argument_from_initializer(initializer: &TensorProto) -> (Argument, TensorData) {
132 use crate::ir::ValueSource;
133
134 let name = initializer.name.clone();
135
136 match TensorData::try_from(initializer.clone()) {
138 Ok(td) => {
139 let arg = if td.shape.is_empty() {
140 Argument {
142 name,
143 ty: ArgType::ScalarNative(td.elem_type()),
144 value_source: ValueSource::Constant, value_store: None,
146 }
147 } else {
148 Argument {
149 name,
150 ty: ArgType::Tensor(TensorType {
151 dtype: td.elem_type(),
152 rank: td.shape.len(),
153 static_shape: Some(td.shape.iter().map(|&d| Some(d)).collect()),
154 }),
155 value_source: ValueSource::Constant, value_store: None,
157 }
158 };
159 (arg, td)
160 }
161 Err(orig_err) => {
162 let dims: Vec<i64> = initializer.dims.clone();
164 if dims.iter().any(|&d| d < 0) {
165 panic!(
166 "invalid tensor shape (negative dims) for initializer '{}': {:?}",
167 name, dims
168 );
169 }
170
171 let dim_elems: usize = if dims.is_empty() {
173 1
174 } else {
175 dims.iter().map(|&d| d as usize).product()
176 };
177
178 let payload_len = {
180 let i32n = initializer.int32_data.len();
181 let i64n = initializer.int64_data.len();
182 let f32n = initializer.float_data.len();
183 let f64n = initializer.double_data.len();
184 let sn = initializer.string_data.len();
185 let typed = *[i32n, i64n, f32n, f64n, sn].iter().max().unwrap_or(&0);
186 if typed > 0 {
187 typed
188 } else {
189 if !initializer.raw_data.is_empty() && dim_elems == 1 {
191 1
192 } else {
193 0
194 }
195 }
196 };
197
198 let looks_scalar = dims.is_empty() || (dims.len() == 1 && dims[0] == 1);
200 if looks_scalar && payload_len == 1 {
201 let td = TensorData::try_from(initializer.clone()).unwrap_or_else(|_| {
202 panic!(
203 "failed to decode scalar initializer '{}': dims={:?}",
204 name, dims
205 )
206 });
207 let arg = Argument {
208 name,
209 ty: ArgType::ScalarNative(td.elem_type()),
210 value_source: ValueSource::Constant, value_store: None,
212 };
213 return (arg, td);
214 }
215
216 if dim_elems == 0 && payload_len == 0 && !dims.is_empty() {
218 let dtype = element_type_from_proto(initializer.data_type).unwrap_or_else(|e| {
221 panic!(
222 "unsupported empty-tensor data_type={} for '{}': {}",
223 initializer.data_type, name, e
224 )
225 });
226
227 let shape_usize: Vec<usize> = dims.iter().map(|&d| d as usize).collect();
229
230 let td = match dtype {
231 DType::F16 => TensorData::new(Vec::<half::f16>::new(), shape_usize.clone()),
232 DType::BF16 => TensorData::new(Vec::<half::bf16>::new(), shape_usize.clone()),
233 DType::F32 => TensorData::new(Vec::<f32>::new(), shape_usize.clone()),
234 DType::F64 => TensorData::new(Vec::<f64>::new(), shape_usize.clone()),
235 DType::I8 => TensorData::new(Vec::<i8>::new(), shape_usize.clone()),
236 DType::I16 => TensorData::new(Vec::<i16>::new(), shape_usize.clone()),
237 DType::I32 => TensorData::new(Vec::<i32>::new(), shape_usize.clone()),
238 DType::I64 => TensorData::new(Vec::<i64>::new(), shape_usize.clone()),
239 DType::U8 => TensorData::new(Vec::<u8>::new(), shape_usize.clone()),
240 DType::U16 => TensorData::new(Vec::<u16>::new(), shape_usize.clone()),
241 DType::U32 => TensorData::new(Vec::<u32>::new(), shape_usize.clone()),
242 DType::U64 => TensorData::new(Vec::<u64>::new(), shape_usize.clone()),
243 DType::Bool(_) => TensorData::new(Vec::<bool>::new(), shape_usize.clone()),
244 _ => panic!(
245 "Unsupported dtype {:?} for empty tensor '{}' (data_type={})",
246 dtype, name, initializer.data_type
247 ),
248 };
249
250 let arg = Argument {
251 name,
252 ty: ArgType::Tensor(TensorType {
253 dtype,
254 rank: shape_usize.len(),
255 static_shape: Some(shape_usize.iter().map(|&d| Some(d)).collect()),
256 }),
257 value_source: ValueSource::Constant, value_store: None,
259 };
260 return (arg, td);
261 }
262
263 panic!(
265 "invalid tensor '{}' (dims {:?} => {} elems) with payload {} elems; original error: {:?}",
266 name, dims, dim_elems, payload_len, orig_err
267 );
268 }
269 }
270}
271
272pub fn argument_from_initializer_lazy_with_context(
281 initializer: TensorProto,
282 base_path: Option<&Path>,
283) -> Result<(Argument, TensorDataRef), ParseError> {
284 use crate::ir::ValueSource;
285
286 let name = initializer.name.clone();
287
288 let data_ref = tensor_data_ref_from_proto(initializer, base_path)?;
290
291 let arg = if data_ref.shape().is_empty() {
292 Argument {
294 name,
295 ty: ArgType::ScalarNative(data_ref.dtype()),
296 value_source: ValueSource::Constant,
297 value_store: None,
298 }
299 } else {
300 Argument {
301 name,
302 ty: ArgType::Tensor(TensorType {
303 dtype: data_ref.dtype(),
304 rank: data_ref.shape().len(),
305 static_shape: Some(data_ref.shape().iter().map(|&d| Some(d)).collect()),
306 }),
307 value_source: ValueSource::Constant,
308 value_store: None,
309 }
310 };
311
312 Ok((arg, data_ref))
313}
314
315impl TryFrom<TensorProto> for TensorDataRef {
323 type Error = ParseError;
324
325 fn try_from(tensor: TensorProto) -> Result<TensorDataRef, Self::Error> {
326 tensor_data_ref_from_proto(tensor, None)
327 }
328}
329
330pub fn tensor_data_ref_from_proto(
341 tensor: TensorProto,
342 base_path: Option<&Path>,
343) -> Result<TensorDataRef, ParseError> {
344 let shape = convert_shape(tensor.dims.clone());
345 let elem = element_type_from_proto(tensor.data_type).map_err(ParseError::VariantNotFound)?;
346
347 if tensor.data_location.enum_value() == Ok(DataLocation::EXTERNAL) {
349 return create_external_data_ref(&tensor, base_path, shape, elem);
350 }
351
352 if !tensor.raw_data.is_empty() {
356 match elem {
357 DType::F32
358 | DType::F64
359 | DType::F16
360 | DType::BF16
361 | DType::I64
362 | DType::I32
363 | DType::I16
364 | DType::I8
365 | DType::U64
366 | DType::U32
367 | DType::U16
368 | DType::U8
369 | DType::Bool(_) => Ok(TensorDataRef::new(tensor.raw_data, shape, elem)),
370 _ => Err(ParseError::VariantNotFound(format!(
371 "Unsupported dtype {:?}",
372 elem
373 ))),
374 }
375 } else {
376 let raw_bytes = match elem {
384 DType::F32 => vec_to_bytes(&tensor.float_data),
385 DType::F64 => vec_to_bytes(&tensor.double_data),
386 DType::I64 => vec_to_bytes(&tensor.int64_data),
387 DType::I32 => vec_to_bytes(&tensor.int32_data),
388 DType::I16 => {
389 let data: Vec<i16> = tensor.int32_data.iter().map(|&x| x as i16).collect();
390 vec_to_bytes(&data)
391 }
392 DType::I8 => {
393 let data: Vec<i8> = tensor.int32_data.iter().map(|&x| x as i8).collect();
394 vec_to_bytes(&data)
395 }
396 DType::U64 => vec_to_bytes(&tensor.uint64_data),
397 DType::U32 => {
398 let data: Vec<u32> = tensor.uint64_data.iter().map(|&x| x as u32).collect();
399 vec_to_bytes(&data)
400 }
401 DType::U16 => {
402 let data: Vec<u16> = tensor.int32_data.iter().map(|&x| x as u16).collect();
403 vec_to_bytes(&data)
404 }
405 DType::U8 => {
406 let data: Vec<u8> = tensor.int32_data.iter().map(|&x| x as u8).collect();
407 bytes::Bytes::from(data)
408 }
409 DType::Bool(_) => {
410 let data: Vec<u8> = tensor.int32_data.iter().map(|&x| (x != 0) as u8).collect();
411 bytes::Bytes::from(data)
412 }
413 DType::F16 => {
414 let data: Vec<u16> = tensor.int32_data.iter().map(|&x| x as u16).collect();
416 vec_to_bytes(&data)
417 }
418 DType::BF16 => {
419 let data: Vec<u16> = tensor.int32_data.iter().map(|&x| x as u16).collect();
421 vec_to_bytes(&data)
422 }
423 _ => {
424 return Err(ParseError::VariantNotFound(format!(
425 "empty/unsupported payload for {:?}",
426 elem
427 )));
428 }
429 };
430 Ok(TensorDataRef::new(raw_bytes, shape, elem))
431 }
432}
433
434fn create_external_data_ref(
439 tensor: &TensorProto,
440 base_path: Option<&Path>,
441 shape: Vec<usize>,
442 dtype: DType,
443) -> Result<TensorDataRef, ParseError> {
444 let entries = tensor
446 .external_data
447 .iter()
448 .map(|e| (e.key.as_str(), e.value.as_str()));
449
450 let external_info = ExternalDataInfo::from_proto_entries(entries).map_err(|e| {
451 ParseError::VariantNotFound(format!(
452 "Failed to parse external_data for tensor '{}': {}",
453 tensor.name, e
454 ))
455 })?;
456
457 let base = base_path.ok_or_else(|| {
459 ParseError::VariantNotFound(format!(
460 "Tensor '{}' uses external data but no base_path provided. \
461 External data requires loading from a file path, not bytes.",
462 tensor.name
463 ))
464 })?;
465
466 let file_path = external_info
467 .resolve_path(base)
468 .map_err(ParseError::VariantNotFound)?;
469
470 let length = external_info.length.unwrap_or_else(|| {
473 let num_elements: usize = if shape.is_empty() {
474 1 } else {
476 shape.iter().product()
477 };
478 (num_elements * dtype.size()) as u64
479 });
480
481 log::debug!(
482 "Creating external data ref for '{}': file={}, offset={}, length={}",
483 tensor.name,
484 file_path.display(),
485 external_info.offset,
486 length
487 );
488
489 Ok(TensorDataRef::new_external(
490 file_path,
491 external_info.offset,
492 length,
493 shape,
494 dtype,
495 ))
496}
497
498fn vec_to_bytes<T: bytemuck::Pod>(data: &[T]) -> bytes::Bytes {
500 bytes::Bytes::copy_from_slice(bytemuck::cast_slice(data))
501}
502
503impl TryFrom<TensorProto> for TensorData {
508 type Error = ParseError;
509
510 fn try_from(tensor: TensorProto) -> Result<TensorData, Self::Error> {
511 let data_ref = TensorDataRef::try_from(tensor)?;
512 Ok(data_ref.to_tensor_data())
513 }
514}
515fn convert_vec_tensor_proto(tensors: Vec<TensorProto>) -> Result<Vec<TensorData>, ParseError> {
516 let mut result = Vec::new();
517 for tensor in tensors {
518 result.push(TensorData::try_from(tensor)?);
519 }
520 Ok(result)
521}
522
523impl TryFrom<AttributeProto> for AttributeValue {
525 type Error = ParseError;
526
527 fn try_from(attr: AttributeProto) -> Result<AttributeValue, Self::Error> {
528 let value = match attr.type_.unwrap() {
529 AttributeType::FLOAT => AttributeValue::Float32(attr.f),
530 AttributeType::INT => AttributeValue::Int64(attr.i),
531 AttributeType::STRING => AttributeValue::String(to_string(attr.s)),
532
533 AttributeType::TENSOR => AttributeValue::Tensor(TensorData::try_from(attr.t.unwrap())?),
535
536 AttributeType::GRAPH => {
538 panic!(
542 "Graph attributes should be converted during node processing, not during proto conversion"
543 )
544 }
545 AttributeType::FLOATS => AttributeValue::Float32s(attr.floats),
546 AttributeType::INTS => AttributeValue::Int64s(attr.ints),
547 AttributeType::STRINGS => AttributeValue::Strings(to_string_vec(attr.strings)),
548 AttributeType::TENSORS => {
549 AttributeValue::Tensors(convert_vec_tensor_proto(attr.tensors)?)
550 }
551 AttributeType::GRAPHS => {
552 panic!(
553 "Graphs attributes should be converted during node processing, not during proto conversion"
554 )
555 }
556 attribute_type => {
559 return Err(ParseError::VariantNotFound(format!("{attribute_type:?}")));
560 }
561 };
562
563 Ok(value)
564 }
565}
566
567pub fn convert_vec_attrs_proto(attrs: Vec<AttributeProto>) -> Attributes {
570 let mut result = Attributes::new();
571 for attr in attrs {
572 if let Ok(attr_type) = attr.type_.enum_value()
574 && (attr_type == AttributeType::GRAPH || attr_type == AttributeType::GRAPHS)
575 {
576 continue;
577 }
578 result.insert(attr.name.clone(), AttributeValue::try_from(attr).unwrap());
579 }
580 result
581}
582
583pub fn convert_node_proto(node: &NodeProto, graph_data: &GraphState) -> RawNode {
584 let name = sanitize_name(&node.name);
585
586 let inputs = node.input.iter().map(|x| graph_data.init_in(x)).collect();
587
588 let outputs = node
589 .output
590 .iter()
591 .map(|output_name| {
592 let mut arg = Argument::from_name(sanitize_name(output_name));
594 if let Some(graph_output_type) = graph_data.get_output_type(output_name) {
597 arg.ty = graph_output_type.clone();
598 } else if let Some(value_info_type) = graph_data.get_value_info_type(output_name) {
599 arg.ty = value_info_type.clone();
600 }
601 arg
602 })
603 .collect();
604
605 let attrs = convert_vec_attrs_proto(node.attribute.clone());
606
607 let node_type = NodeType::from_str(&node.op_type).expect("Unknown node type");
608
609 RawNode {
610 node_type,
611 name,
612 inputs,
613 outputs,
614 attrs,
615 }
616}
617
618fn to_string(bytes: bytes::Bytes) -> String {
619 from_utf8(&bytes).unwrap().to_string()
620}
621
622fn to_string_vec(bytes: Vec<bytes::Bytes>) -> Vec<String> {
623 bytes.into_iter().map(to_string).collect()
624}
625
626fn convert_shape(shape: Vec<i64>) -> Vec<usize> {
627 shape.iter().map(|s| *s as usize).collect()
628}
629
630pub fn extract_outer_scope_references(
638 graph_proto: &crate::protos::GraphProto,
639) -> std::collections::HashSet<String> {
640 use std::collections::HashSet;
641
642 let initializer_names: HashSet<String> = graph_proto
644 .initializer
645 .iter()
646 .filter(|i| !i.name.is_empty())
647 .map(|i| sanitize_name(&i.name))
648 .collect();
649
650 let is_outer_scope_input = |name: &str| -> bool {
654 !name.is_empty() && !initializer_names.contains(&sanitize_name(name))
655 };
656
657 let mut defined_names: HashSet<String> = HashSet::new();
659
660 for input in &graph_proto.input {
662 if !input.name.is_empty() && !is_outer_scope_input(&input.name) {
663 defined_names.insert(sanitize_name(&input.name));
664 }
665 }
666
667 for init in &graph_proto.initializer {
669 if !init.name.is_empty() {
670 defined_names.insert(sanitize_name(&init.name));
671 }
672 }
673
674 for node in &graph_proto.node {
676 for output in &node.output {
677 if !output.is_empty() {
678 defined_names.insert(sanitize_name(output));
679 }
680 }
681 }
682
683 let mut referenced_names: HashSet<String> = HashSet::new();
685
686 for input in &graph_proto.input {
688 if is_outer_scope_input(&input.name) {
689 referenced_names.insert(sanitize_name(&input.name));
690 }
691 }
692
693 for node in &graph_proto.node {
695 for input in &node.input {
696 if !input.is_empty() {
697 referenced_names.insert(sanitize_name(input));
698 }
699 }
700
701 let is_loop_or_scan = node.op_type == "Loop" || node.op_type == "Scan";
705
706 for attr in &node.attribute {
707 if let Ok(attr_type) = attr.type_.enum_value() {
708 use crate::protos::attribute_proto::AttributeType;
709 match attr_type {
710 AttributeType::GRAPH => {
711 if let Some(nested_graph) = attr.g.as_ref() {
712 let loop_provided_names: std::collections::HashSet<String> =
719 if is_loop_or_scan && attr.name == "body" {
720 nested_graph
721 .input
722 .iter()
723 .filter(|i| !i.name.is_empty())
724 .map(|i| sanitize_name(&i.name))
725 .collect()
726 } else {
727 std::collections::HashSet::new()
728 };
729
730 let nested_refs = extract_outer_scope_references(nested_graph);
732 for name in nested_refs {
733 if !defined_names.contains(&name)
735 && !loop_provided_names.contains(&name)
736 {
737 referenced_names.insert(name);
738 }
739 }
740 }
741 }
742 AttributeType::GRAPHS => {
743 for nested_graph in &attr.graphs {
744 let nested_refs = extract_outer_scope_references(nested_graph);
745 for name in nested_refs {
746 if !defined_names.contains(&name) {
747 referenced_names.insert(name);
748 }
749 }
750 }
751 }
752 _ => {}
753 }
754 }
755 }
756 }
757
758 for output in &graph_proto.output {
760 if !output.name.is_empty() {
761 referenced_names.insert(sanitize_name(&output.name));
762 }
763 }
764
765 referenced_names
767 .difference(&defined_names)
768 .cloned()
769 .collect()
770}
771
772pub fn extract_node_outer_scope_references(
778 node_proto: &NodeProto,
779) -> std::collections::HashSet<String> {
780 use std::collections::HashSet;
781
782 let mut all_refs: HashSet<String> = HashSet::new();
783
784 for attr in &node_proto.attribute {
785 if let Ok(attr_type) = attr.type_.enum_value() {
786 match attr_type {
787 AttributeType::GRAPH => {
788 if let Some(graph_proto) = attr.g.as_ref() {
789 let refs = extract_outer_scope_references(graph_proto);
790 all_refs.extend(refs);
791 }
792 }
793 AttributeType::GRAPHS => {
794 for graph_proto in &attr.graphs {
795 let refs = extract_outer_scope_references(graph_proto);
796 all_refs.extend(refs);
797 }
798 }
799 _ => {}
800 }
801 }
802 }
803
804 all_refs
805}
806
807pub fn convert_graph_attributes(
818 node_proto: &NodeProto,
819 opset_version: usize,
820 parent_registry: Option<crate::graph_state::NameRegistry>,
821 base_path: Option<&Path>,
822) -> Attributes {
823 use crate::ir::DeferredGraph;
824 use std::sync::Arc;
825
826 let mut result = Attributes::new();
827
828 let name_registry = parent_registry.unwrap_or_default();
831
832 for attr in &node_proto.attribute {
833 if let Ok(attr_type) = attr.type_.enum_value() {
834 match attr_type {
835 AttributeType::GRAPH => {
836 if let Some(graph_proto) = attr.g.as_ref() {
837 let deferred = DeferredGraph {
839 proto: Arc::new(graph_proto.clone()),
840 opset_version,
841 name_registry: Some(name_registry.clone()),
842 base_path: base_path.map(|p| p.to_path_buf()),
843 };
844 result.insert(attr.name.clone(), AttributeValue::DeferredGraph(deferred));
845 }
846 }
847 AttributeType::GRAPHS => {
848 let deferred_graphs: Vec<_> = attr
849 .graphs
850 .iter()
851 .map(|graph_proto| DeferredGraph {
852 proto: Arc::new(graph_proto.clone()),
853 opset_version,
854 name_registry: Some(name_registry.clone()),
855 base_path: base_path.map(|p| p.to_path_buf()),
856 })
857 .collect();
858 result.insert(
859 attr.name.clone(),
860 AttributeValue::DeferredGraphs(deferred_graphs),
861 );
862 }
863 _ => {}
864 }
865 }
866 }
867 result
868}
869
870impl TryFrom<ValueInfoProto> for Argument {
871 type Error = ParseError;
872
873 fn try_from(value: ValueInfoProto) -> Result<Argument, Self::Error> {
874 let name = sanitize_name(&value.name);
875 let proto_type = value
876 .type_
877 .as_ref()
878 .ok_or(ParseError::VariantNotFound("missing type".into()))?;
879
880 if !proto_type.has_tensor_type() {
881 return Err(ParseError::VariantNotFound(format!(
884 "Unsupported argument type: no tensor_type in {:?}",
885 proto_type
886 )));
887 }
888
889 let tensor_proto = proto_type.tensor_type();
890 let elem_type =
891 element_type_from_proto(tensor_proto.elem_type).map_err(ParseError::VariantNotFound)?;
892
893 let ty = if tensor_proto.shape.dim.is_empty() {
894 ArgType::ScalarNative(elem_type)
895 } else {
896 let static_shape: Vec<Option<usize>> = tensor_proto
897 .shape
898 .dim
899 .iter()
900 .map(|d| match &d.value {
901 Some(Value::DimValue(v)) => Some(*v as usize),
902 _ => None,
903 })
904 .collect();
905 let static_shape = Some(static_shape);
906
907 ArgType::Tensor(TensorType {
908 rank: tensor_proto.shape.dim.len(),
909 dtype: elem_type,
910 static_shape,
911 })
912 };
913
914 Ok(Argument {
915 ty,
916 name,
917 value_source: crate::ir::ValueSource::Dynamic, value_store: None,
919 })
920 }
921}
922
923#[cfg(test)]
924mod tests {
925 use super::*;
926
927 #[test]
928 fn test_sanitize_name_basic() {
929 assert_eq!(sanitize_name("valid_name"), "valid_name");
931 assert_eq!(sanitize_name("_underscore"), "_underscore");
932 assert_eq!(sanitize_name("a"), "a");
933
934 assert_eq!(sanitize_name("ValidName123"), "valid_name123");
936 assert_eq!(sanitize_name("MyVariable"), "my_variable");
937 assert_eq!(sanitize_name("HTTPResponse"), "httpresponse");
938 }
939
940 #[test]
941 fn test_sanitize_name_special_chars() {
942 assert_eq!(sanitize_name("input:0"), "input_0");
944 assert_eq!(sanitize_name("layer/weight"), "layer_weight");
945 assert_eq!(sanitize_name("jax2tf/model:0"), "jax2tf_model_0");
946
947 assert_eq!(sanitize_name("bert.encoder.layer"), "bert_encoder_layer");
949 assert_eq!(sanitize_name("layer-norm"), "layer_norm");
950
951 assert_eq!(
953 sanitize_name("jax2tf_rhs_/pjit_silu_/Const_2:0"),
954 "jax2tf_rhs_pjit_silu_const_2_0"
955 );
956 }
957
958 #[test]
959 fn test_sanitize_name_camel_to_snake() {
960 assert_eq!(
962 sanitize_name("onnx__GlobalAveragePool_0"),
963 "onnx_global_average_pool_0"
964 );
965 assert_eq!(sanitize_name("onnx__Gemm_0"), "onnx_gemm_0");
966 assert_eq!(sanitize_name("onnx__Greater_0"), "onnx_greater_0");
967 assert_eq!(sanitize_name("MyClassName"), "my_class_name");
968 assert_eq!(sanitize_name("HTTPSConnection"), "httpsconnection");
969 }
970
971 #[test]
972 fn test_sanitize_name_starts_with_digit() {
973 assert_eq!(sanitize_name("123tensor"), "_123tensor");
974 assert_eq!(sanitize_name("0input"), "_0input");
975 }
976
977 #[test]
978 fn test_sanitize_name_unicode() {
979 assert_eq!(sanitize_name("tensor™"), "tensor");
981 assert_eq!(sanitize_name("input€output"), "input_output");
982 }
983
984 #[test]
985 fn test_sanitize_name_empty_and_edge_cases() {
986 assert_eq!(sanitize_name(""), "");
988
989 assert_eq!(sanitize_name("_"), "_");
990
991 assert_eq!(sanitize_name("___"), "_");
993 assert_eq!(sanitize_name("a__b"), "a_b");
994
995 assert_eq!(sanitize_name(":/:"), "_");
997
998 assert_eq!(sanitize_name("a:::b"), "a_b");
1000
1001 assert_eq!(sanitize_name("name_:"), "name");
1003 }
1004}