Skip to content

Conversation

@willg-nv
Copy link

@willg-nv willg-nv commented Dec 17, 2025

What does this PR do?

Type of change: new feature

Overview: This PR integrates an automatical QDQ placment tool into ModelOpt.

This PR is the 1/4 parts of the change, it contains the following changes:

  1. Defines common types: Region, RegionType, Error types
  2. Defines InsertionPoints (the logical localtion to place QDQ pairs), InsertionScheme (a set of insertion points)
  3. Unit tests for new types

Part 1: #701
Part 2: #702
Part 3: #703
Part 4: #704

Usage

        # Region type usage:
        region = Region(region_id=1, level=0, region_type=RegionType.LEAF)
        assert region.get_id() == 1
        assert region.get_level() == 0
        region.add_node(1) # 1 is the index of ONNX graph node
        ...

        point = NodeInputInsertionPoint(node_index=0, input_index=2)
        assert point.node_index == 0 # relative node index in region
        assert point.input_index == 2 # relative input tensor index in specific node
        resolved = point.resolve(region, graph)
        ...

Testing

Implement unit tests, all tests could get passed.

Before your PR is "Ready for review"

  • Make sure you read and follow Contributor guidelines and your commits are signed.
  • Is this change backward compatible?: Yes
  • Did you write any new necessary tests?: Yes
  • Did you add or update any necessary documentation?: No, document change will be included in part 4.
  • Did you update Changelog?: No, this could be done when all parts of the change are merged.

Additional Information

@willg-nv willg-nv requested a review from a team as a code owner December 17, 2025 06:18
@willg-nv willg-nv requested a review from gcunhase December 17, 2025 06:18
@copy-pr-bot
Copy link

copy-pr-bot bot commented Dec 17, 2025

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@willg-nv willg-nv force-pushed the dev-willg-integrate-auto-qdq-placement-part1 branch from 9c53783 to f872e70 Compare December 19, 2025 05:32
@willg-nv
Copy link
Author

Hi @gcunhase, could you help me review this PR? thanks!

self.children.append(child)
child.set_parent(self)

def _is_descendant_of(self, potential_ancestor: "Region") -> bool:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a handy util. Do we want to keep this public?

"""
return self.nodes

def get_all_nodes_recursive(self, _visited: set[int] | None = None) -> set[int]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can be renamed to get_region_nodes_and_descendants()

Comment on lines +277 to +280
# Detect cycles
if self.id in _visited:
logger.warning(f"Cycle detected in region {self.id} during node traversal")
return set()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add_child() alaready prevents adding cycles in the graph. So I think this check is redundant.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this check could help avoid infinite loop when graph editing encountered cycle is unexpectedly. I think this lines could be replaced with an assert statement which could make debug easier.

logger.warning(f"Cycle detected in region {self.id} during node traversal")
return set()

_visited.add(self.id)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can remove _visited in that case too.

Comment on lines +297 to +299
# Detect cycles
if self.id in _visited:
return False
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

visited and cycle detection logic can be removed from here too.

Comment on lines +338 to +376
def run_tests():
"""Run all insertion point and scheme tests."""
print("=" * 70)
print("Autotuner Insertion Points & Schemes Test Suite")
print("=" * 70)

# Create test suite
loader = unittest.TestLoader()
suite = unittest.TestSuite()

# Add test classes
suite.addTests(loader.loadTestsFromTestCase(TestNodeInputInsertionPoint))
suite.addTests(loader.loadTestsFromTestCase(TestRegionOutputInsertionPoint))
suite.addTests(loader.loadTestsFromTestCase(TestChildRegionInputInsertionPoint))
suite.addTests(loader.loadTestsFromTestCase(TestInsertionScheme))

# Run with verbose output
runner = unittest.TextTestRunner(verbosity=2)
result = runner.run(suite)

# Summary
print("\n" + "=" * 70)
print("Test Summary")
print("=" * 70)
print(f"Tests run: {result.testsRun}")
print(f"Successes: {result.testsRun - len(result.failures) - len(result.errors)}")
print(f"Failures: {len(result.failures)}")
print(f"Errors: {len(result.errors)}")

if result.wasSuccessful():
print("\n✓ All insertion point and scheme tests passed!")
return 0
else:
print("\n✗ Some tests failed")
return 1


if __name__ == "__main__":
sys.exit(run_tests())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this boilerplate code. You can refer to the other tests to see how they are called with pytest.

assert region.get_type() == RegionType.LEAF
assert region.get_parent() is None
assert len(region.get_children()) == 0
print("✓ LEAF region creation")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the print statements from this file too

class TestRegion(unittest.TestCase):
"""Test Region class functionality."""

def test_leaf_region_creation(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The three region creation tests are almost identical. It would be great if you could parametrize them.

Comment on lines +129 to +145
def test_is_leaf(self):
"""Test checking if region is LEAF type."""
leaf = Region(region_id=1, level=0, region_type=RegionType.LEAF)
composite = Region(region_id=2, level=1, region_type=RegionType.COMPOSITE)

assert leaf.get_type() == RegionType.LEAF
assert composite.get_type() != RegionType.LEAF
print("✓ Region LEAF type check")

def test_is_composite(self):
"""Test checking if region is COMPOSITE type."""
leaf = Region(region_id=1, level=0, region_type=RegionType.LEAF)
composite = Region(region_id=2, level=1, region_type=RegionType.COMPOSITE)

assert leaf.get_type() != RegionType.COMPOSITE
assert composite.get_type() == RegionType.COMPOSITE
print("✓ Region COMPOSITE type check")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two tests can also be combined.

Comment on lines +189 to +219
def run_tests():
"""Run all Region tests."""
print("=" * 70)
print("Region Class Test Suite")
print("=" * 70)

loader = unittest.TestLoader()
suite = unittest.TestSuite()
suite.addTests(loader.loadTestsFromTestCase(TestRegion))

runner = unittest.TextTestRunner(verbosity=2)
result = runner.run(suite)

print("\n" + "=" * 70)
print("Test Summary")
print("=" * 70)
print(f"Tests run: {result.testsRun}")
print(f"Successes: {result.testsRun - len(result.failures) - len(result.errors)}")
print(f"Failures: {len(result.failures)}")
print(f"Errors: {len(result.errors)}")

if result.wasSuccessful():
print("\n✓ All Region tests passed!")
return 0
else:
print("\n✗ Some tests failed")
return 1


if __name__ == "__main__":
sys.exit(run_tests())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This boilerplate code can be removed too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants