@@ -513,7 +513,10 @@ def tosa_support_factory(
513513 if additional_positive_checks :
514514 positive_checks .extend (additional_positive_checks )
515515 negative_checks = _negative_checks (
516- tosa_spec , exported_program , reporter , additional_checks
516+ tosa_spec ,
517+ exported_program ,
518+ reporter ,
519+ additional_checks ,
517520 )
518521
519522 # An op must be accepted by at least one postitive check, and not rejected by any
@@ -530,8 +533,44 @@ def tosa_support_factory(
530533 return any_chain (* additional_positive_overrides or (), default_checks )
531534
532535
536+ def _has_symbolic_shape (node : fx .Node ) -> bool :
537+ val = node .meta .get ("val" )
538+ vals = val if isinstance (val , (list , tuple )) else (val ,)
539+ for node_val in vals :
540+ if isinstance (node_val , torch .SymInt ):
541+ return True
542+
543+ shape = getattr (node_val , "shape" , None )
544+ if shape is not None and any (isinstance (dim , torch .SymInt ) for dim in shape ):
545+ return True
546+
547+ return False
548+
549+
533550class SymbolicShapeSupportCheck (OperatorSupportBase ):
534- """Reject symbolic tensor shapes for specs without the shape extension."""
551+ """Reject symbolic shape constructs that require the TOSA shape
552+ extension.
553+ """
554+
555+ _SYMBOLIC_SPATIAL_DIM_TARGETS = (
556+ exir_ops .edge .aten .convolution .default ,
557+ exir_ops .edge .aten .avg_pool2d .default ,
558+ exir_ops .edge .aten .max_pool2d .default ,
559+ exir_ops .edge .aten .max_pool2d_with_indices .default ,
560+ exir_ops .edge .aten ._adaptive_avg_pool2d .default ,
561+ torch .ops .aten .conv_transpose2d .input ,
562+ )
563+ _SYMBOLIC_MEAN_TARGETS = (
564+ exir_ops .edge .aten .mean .dim ,
565+ exir_ops .edge .aten .mean .default ,
566+ )
567+ _SYMBOLIC_VIEW_SHAPE_TARGETS = (
568+ exir_ops .edge .aten .squeeze_copy .dim ,
569+ exir_ops .edge .aten .squeeze_copy .dims ,
570+ exir_ops .edge .aten .unsqueeze_copy .default ,
571+ )
572+ _SYMBOLIC_PAD_TARGETS = (exir_ops .edge .aten .constant_pad_nd .default ,)
573+ _SYMBOLIC_SLICE_TARGETS = (exir_ops .edge .aten .slice_copy .Tensor ,)
535574
536575 def __init__ (self , reporter : WhyNoPartitionReporter ):
537576 """Initialize the check with a reporter.
@@ -543,88 +582,147 @@ def __init__(self, reporter: WhyNoPartitionReporter):
543582 self .reporter = reporter
544583
545584 @staticmethod
546- def _has_symbolic_shape (node : fx .Node ) -> bool :
547- val = node .meta .get ("val" )
548- vals = val if isinstance (val , (list , tuple )) else (val ,)
549- for node_val in vals :
550- if isinstance (node_val , torch .SymInt ):
585+ def _has_symbolic_shape_argument (arg : object ) -> bool :
586+ if isinstance (arg , torch .SymInt ):
587+ return True
588+
589+ if isinstance (arg , fx .Node ):
590+ return SymbolicShapeSupportCheck ._has_symbolic_shape_argument (
591+ arg .meta .get ("val" )
592+ )
593+
594+ if isinstance (arg , (list , tuple )):
595+ return any (
596+ SymbolicShapeSupportCheck ._has_symbolic_shape_argument (item )
597+ for item in arg
598+ )
599+
600+ return False
601+
602+ @staticmethod
603+ def _get_mean_reduction_dims (node : fx .Node , input_rank : int ) -> tuple [int , ...]:
604+ if node .target == exir_ops .edge .aten .mean .default :
605+ return tuple (range (input_rank ))
606+
607+ dims = node .kwargs .get ("dim" , node .args [1 ] if len (node .args ) > 1 else None )
608+ if dims is None :
609+ return tuple (range (input_rank ))
610+ if isinstance (dims , int ):
611+ return (dims % input_rank ,)
612+ return tuple (dim % input_rank for dim in typing .cast (Sequence [int ], dims ))
613+
614+ def _has_unsupported_symbolic_tensor_shape (self , node : fx .Node ) -> bool :
615+ if node .target not in (
616+ * self ._SYMBOLIC_SPATIAL_DIM_TARGETS ,
617+ * self ._SYMBOLIC_MEAN_TARGETS ,
618+ * self ._SYMBOLIC_VIEW_SHAPE_TARGETS ,
619+ * self ._SYMBOLIC_PAD_TARGETS ,
620+ * self ._SYMBOLIC_SLICE_TARGETS ,
621+ ):
622+ return False
623+ if not node .all_input_nodes :
624+ return False
625+
626+ input_node = node .all_input_nodes [0 ]
627+ input_fake_tensor = get_first_fake_tensor (input_node )
628+ if not any (isinstance (s , torch .SymInt ) for s in input_fake_tensor .shape ):
629+ return False
630+
631+ if node .target in self ._SYMBOLIC_SPATIAL_DIM_TARGETS :
632+ if any (isinstance (s , torch .SymInt ) for s in input_fake_tensor .shape [2 :]):
633+ self .reporter .report_reject (node , "Symbolic spatial dims unsupported" )
551634 return True
552635
553- shape = getattr (node_val , "shape" , None )
554- if shape is not None and any (
555- isinstance (dim , torch .SymInt ) for dim in shape
636+ if node .target in self ._SYMBOLIC_MEAN_TARGETS :
637+ if any (
638+ isinstance (input_fake_tensor .shape [dim ], torch .SymInt )
639+ for dim in self ._get_mean_reduction_dims (
640+ node , len (input_fake_tensor .shape )
641+ )
556642 ):
643+ self .reporter .report_reject (node , "Symbolic mean dims unsupported" )
557644 return True
558645
646+ if node .target in self ._SYMBOLIC_VIEW_SHAPE_TARGETS :
647+ self .reporter .report_reject (node , "Symbolic view dims unsupported" )
648+ return True
649+
650+ if node .target in self ._SYMBOLIC_PAD_TARGETS :
651+ self .reporter .report_reject (node , "Symbolic pad dims unsupported" )
652+ return True
653+
654+ if node .target in self ._SYMBOLIC_SLICE_TARGETS :
655+ self .reporter .report_reject (node , "Symbolic slices unsupported" )
656+ return True
657+
559658 return False
560659
561- def _partition_dynamic_upmsample_nearest2d (self , node : fx .Node ) -> bool :
562- """Check if the node is an upsample_nearest2d with symbolic shapes.
660+ def is_node_supported (
661+ self , submodules : typing .Mapping [str , torch .nn .Module ], node : fx .Node
662+ ) -> bool :
663+ """Return False for symbolic shape uses needing shape extension.
664+
665+ Without TOSA shape extension, symbolic input/output tensor dimensions
666+ are generally allowed because they are tensor metadata. Symbolic shape
667+ arguments and known shape-materialization edge cases are rejected.
563668
564669 Args:
670+ submodules (typing.Mapping[str, torch.nn.Module]): Exported modules.
565671 node (fx.Node): FX node to check.
566672
567673 Returns:
568- bool: True if the node is an upsample_nearest2d with symbolic
569- shapes; otherwise, False.
674+ bool: False if rejected by constraints; otherwise, True.
570675
571676 """
572- if node .target != exir_ops .edge .aten .upsample_nearest2d .vec :
573- return False
677+ del submodules
678+ if node .op in ("placeholder" , "output" ):
679+ return True
680+ if node .op == "call_function" and node .target in (* Q_OPS , * DQ_OPS ):
681+ return True
574682
575- try :
576- input_tensor = get_first_fake_tensor (node .all_input_nodes [0 ])
577- output_tensor = get_first_fake_tensor (node )
578- except Exception as exc :
683+ if self ._has_symbolic_shape_argument (node .args ):
579684 self .reporter .report_reject (
580685 node ,
581- f"upsample_nearest2d symbolic shapes need tensor metadata: { exc } " ,
686+ "Node has symbolic shape arguments, has the TOSA spec shape extension support? " ,
582687 )
583688 return False
584689
585- input_size_xy = input_tensor .shape [2 :4 ]
586- output_size_xy = output_tensor .shape [2 :4 ]
587- if len (input_size_xy ) != 2 or len (output_size_xy ) != 2 :
588- self .reporter .report_reject (
589- node , "upsample_nearest2d expects 2D spatial input/output."
590- )
690+ if self ._has_unsupported_symbolic_tensor_shape (node ):
591691 return False
592692
593693 return True
594694
595- def is_node_supported (
596- self , submodules : typing .Mapping [str , torch .nn .Module ], node : fx .Node
597- ) -> bool :
598- """Return False for nodes with symbolic tensor input or output shapes.
599695
600- Dynamic shapes require the TOSA shape extension. Reject nodes with
601- symbolic tensor dimensions before partitioning when the active spec
602- does not enable that extension.
696+ class CheckResolvedTensorShapes (OperatorSupportBase ):
697+ """Reject nodes with unresolved tensor input or output shapes."""
603698
604- Args:
605- submodules (typing.Mapping[str, torch.nn.Module]): Exported modules.
606- node (fx.Node): FX node to check.
699+ def __init__ (self , reporter : WhyNoPartitionReporter ):
700+ """Initialize the check with a reporter.
607701
608- Returns :
609- bool: False if rejected by constraints; otherwise, True .
702+ Args :
703+ reporter (WhyNoPartitionReporter): Reporter for rejection reasons .
610704
611705 """
706+ self .reporter = reporter
707+
708+ def is_node_supported (
709+ self , submodules : typing .Mapping [str , torch .nn .Module ], node : fx .Node
710+ ) -> bool :
711+ """Return False when the node depends on unresolved tensor shapes."""
712+ del submodules
612713 if node .op in ("placeholder" , "output" ):
613714 return True
614715 if node .op == "call_function" and node .target in (* Q_OPS , * DQ_OPS ):
615716 return True
616717
617- if self . _has_symbolic_shape (node ) or any (
618- self . _has_symbolic_shape (input_node ) for input_node in node .all_input_nodes
718+ if _has_symbolic_shape (node ) or any (
719+ _has_symbolic_shape (input_node ) for input_node in node .all_input_nodes
619720 ):
620- if node .target == exir_ops .edge .aten .upsample_nearest2d .vec :
621- return self ._partition_dynamic_upmsample_nearest2d (node )
622- else :
623- self .reporter .report_reject (
624- node ,
625- "Node has symbolic shape, has the TOSA spec shape extension support?" ,
626- )
627- return False
721+ self .reporter .report_reject (
722+ node ,
723+ "Node has unresolved tensor shapes, which are not supported by this target." ,
724+ )
725+ return False
628726
629727 return True
630728
0 commit comments