-
Notifications
You must be signed in to change notification settings - Fork 54
Merge Main (PR #204) and Complete Opset Versioning Implementation #216
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feature/op_version
Are you sure you want to change the base?
Conversation
Replace dictionary-based registration with module namespace lookup: - Direct attribute lookup in module namespace (preferred) - Legacy custom_op dictionary support (backward compatibility) - Domain aliasing (e.g., onnx.brevitas -> qonnx.custom_op.general) - Runtime op registration via add_op_to_domain() - No expensive fallbacks (removed case-insensitive matching, full module scan) - Clear, actionable error messages Benefits: - Better IDE support with direct imports - Cleaner, more Pythonic API - O(1) lookup performance - Predictable behavior - External packages work via domain-based imports (e.g., finn.custom_op.fpgadataflow)
- Replace eager module inspection with on-demand discovery - Add RLock for thread-safe registry access - Cache discovered ops in _OP_REGISTRY dict - Remove get_ops_in_domain() and add_op_to_domain() - Privatize registry state variables
Replace hard-coded domain string checks with registry-based lookups. Add is_custom_op() function to registry.py that checks if a domain or specific (domain, op_type) combination exists via registry lookup and module import fallback. Changes: - Add is_custom_op(domain, op_type=None) to custom_op/registry.py - Deprecate hasCustomOp in favor of is_custom_op - Replace is_finn_op implementation with deprecation wrapper - Update all internal uses across 7 files: * modelwrapper.py: get_finn_nodes/get_non_finn_nodes * onnx_exec.py: execute_node domain check * infer_shapes.py: _hide_finn_ops and assertion * infer_data_layouts.py: _dims_to_layout and _infer_node_data_layout * infer_datatypes.py: _infer_node_datatype is_custom_op handles empty domains (standard ONNX) and provides precise op checking when op_type is specified.
…egistration Namespace based customop registration
Replace manual dict registration with automatic class discovery and version resolution. CustomOps now declare op_version attribute and are auto-registered via module imports. - Add op_version attribute to all CustomOp base and subclasses - Implement nested registry structure (domain->op_type->version) - Support "since version" semantics for automatic version resolution - Replace is_finn_op() with is_custom_op() from registry - Add registry API (add_op_to_domain, get_supported_versions, is_custom_op) - Simplify domain __init__.py files to use __all__ exports - Use lazy loading via __all__ for performance
…e inference CustomOp version is now determined solely by _vN class name suffix: - No suffix defaults to version 1 - _vN suffix indicates version N - Removes redundant op_version class attribute across all CustomOp implementations - Updates registry to extract version from class names only - Adds backward compatibility for legacy custom_op dict pattern - Simplifies version discovery logic with custom_op dict fallback
maltanar
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Please address the in-line comments about the versioning for channels-last ops
- Ensure that the linter/pre-commit hook is run, I see that it makes changes to formatting when I run it on my side
- If the intention was to replace all calls to ´is_finn_op´ with ´is_custom_op´ there is still calls to the former in ´test_channelslast.py´
- Ensure that all tests pass, I am seeing failures:
ERROR tests/transformation/test_channelslast.py - AttributeError: module 'qonnx.custom_op.channels_last' has no attribute 'custom_op'
ERROR tests/transformation/test_channelslast_eltwise.py - AttributeError: module 'qonnx.custom_op.channels_last' has no attribute 'custom_op'
ERROR tests/transformation/test_channelslast_residual.py - AttributeError: module 'qonnx.custom_op.channels_last' has no attribute 'custom_op'
FAILED tests/custom_op/test_attr.py::test_attr - AttributeError: module 'qonnx.custom_op.general' has no attribute 'custom_op'
| "Return whether given op_type string is a QONNX or FINN custom op" | ||
| return op_type.startswith("finn") or op_type.startswith("qonnx.custom_op") or op_type.startswith("onnx.brevitas") | ||
| """Deprecated: Use is_custom_op from qonnx.custom_op.registry instead. | ||
| Return whether given op_type string is a QONNX or FINN custom op. | ||
| This function uses hard-coded string matching and will be removed in QONNX v1.0. | ||
| Use the registry-based is_custom_op for better accuracy and extensibility. | ||
| """ | ||
| import warnings | ||
| warnings.warn( | ||
| "is_finn_op is deprecated and will be removed in QONNX v1.0. " | ||
| "Use 'from qonnx.custom_op.registry import is_custom_op' instead.", | ||
| DeprecationWarning, | ||
| stacklevel=2 | ||
| ) | ||
| from qonnx.custom_op.registry import is_custom_op | ||
| return is_custom_op(op_type) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
more of a note-to-self really, but this might break finn-examples in the future which manually incorporates parts of the qonnx source tree for data packing. if it does, more files from the qonnx src tree may have to be added there.
| # channels-last ops are defined by the underlying ONNX standard op | ||
| # thus, we can define them for any version of the original op | ||
| # so we emulate a custom op dictionary that mimics the support for any | ||
| # {ChannelsLastOp}_vX instead of hardcoding what versions are supported | ||
|
|
||
|
|
||
| class ChannelsLastCustomOpDict(dict): | ||
| def __init__(self): | ||
| self._custom_ops = {"Conv": Conv, "MaxPool": MaxPool, "BatchNormalization": BatchNormalization} | ||
|
|
||
| def __getitem__(self, key): | ||
| base_key = key.split("_v")[0] # Extract base key (e.g., Conv from Conv_v13) | ||
| if base_key in self._custom_ops: | ||
| return self._custom_ops[base_key] | ||
| raise KeyError(f"Channels-last CustomOp '{key}' not found.") | ||
|
|
||
| def __contains__(self, key): | ||
| base_key = key.split("_v")[0] | ||
| return base_key in self._custom_ops | ||
|
|
||
| def keys(self): | ||
| return self._custom_ops.keys() | ||
|
|
||
|
|
||
| custom_op = ChannelsLastCustomOpDict() | ||
| __all__ = ["Conv", "MaxPool", "BatchNormalization"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this does not respect the semantics (channels-last ops being implicitly defined for any version of the standard op) in the deleted code, instead all channels-last ops will now be registered as v1, meaning that they will instantiate ai.onnx v1 standard nodes under the hood. can you address this?
if this is tricky to fix in the new system, one band-aid we can apply is instead registering these channels-last ops as v11 which is the current preferred ONNX opset version. in this way they would do something slightly more reasonable.
|
To move this along I have started addressing these issues myself in #217 . |
Summary
Extends the namespace-based registry (merged in PR #204) with full opset versioning support, enabling ONNX-compliant "since version" semantics for operator evolution.
Building on Main
PR #204 introduced the foundational registry infrastructure: namespace-based registration, domain aliases, thread safety, lazy loading, and
__all__-based discovery. What was missing: version awareness. Main's registry uses 2D keys(domain, op_type)and cannot distinguish between operator versions.This PR adds the version dimension, transforming the registry from
(domain, op_type) → classtodomain → op_type → version → class.Key Additions
1. Version-Aware Registry
Three-dimensional structure:
Automatic version inference from class names:
IntQuant→ version 1 (default)IntQuant_v2→ version 2 (strips_vNsuffix)"Since version" resolution:
opset_import: {"qonnx.custom_op.general": 3}[1, 2, 5]2. ModelWrapper Integration
Three new methods enable opset-aware model manipulation:
Models now automatically use the correct operator version based on their
opset_importdeclarations.3. Registry Introspection API
4. Performance Optimizations
__all__filtering5. Execution Engine Updates
onnx_exec.pyuses registry checks instead of string pattern matching6. Code Cleanup
Eliminated manual
custom_opdictionaries in favor of__all__exports for automatic discovery. Deprecatedis_finn_op()with clear migration path tois_custom_op().Migration Guide
CustomOp registration:
Domain checking:
Testing
Added comprehensive test coverage in
test_customop_version.pyfor version resolution, "since version" semantics, fallback behavior, and version gaps. Updated existing tests for opset-aware execution and new ModelWrapper API.Backward Compatibility
Three-tier discovery fallback ensures existing code continues to work:
__all__exports (preferred, modern)custom_opdict (external packages)All of main's capabilities preserved. No breaking changes.