-
Notifications
You must be signed in to change notification settings - Fork 515
[OMNIML-5613] Quantize ResNet residual adds in torch ONNX example #2024
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: main
Are you sure you want to change the base?
Changes from all commits
664a7a3
28b2912
c15755c
d2a78de
77f2433
7cf4bf5
9cbcbbd
552ac54
edc43d4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,6 +28,7 @@ | |
| import onnx_graphsurgeon as gs | ||
| from onnx.helper import get_attribute_value | ||
| from onnx_graphsurgeon import Constant, Node, Variable | ||
| from onnxconverter_common.float16 import convert_np_to_float16 | ||
|
|
||
| from modelopt.onnx.logging_config import logger | ||
|
|
||
|
|
@@ -1467,13 +1468,17 @@ def fold_q_fp16_to_fp32_casts(onnx_model: onnx.ModelProto) -> onnx.ModelProto: | |
| for inp in node.input: | ||
| consumer_map.setdefault(inp, []).append(node) | ||
| initializers = {init.name: init for init in onnx_model.graph.initializer} | ||
| tensor_types = _build_tensor_type_map(onnx_model) | ||
|
|
||
| to_remove = [] | ||
| for node in onnx_model.graph.node: | ||
| if node.op_type != "Cast": | ||
| continue | ||
| cast_to = next((a.i for a in node.attribute if a.name == "to"), None) | ||
| if cast_to != onnx.TensorProto.FLOAT: | ||
| if ( | ||
| cast_to != onnx.TensorProto.FLOAT | ||
| or tensor_types.get(node.input[0]) != onnx.TensorProto.FLOAT16 | ||
| ): | ||
|
Comment on lines
+1471
to
+1481
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win Cast inputs without a type entry now silently skip folding.
🤖 Prompt for AI Agents |
||
| continue | ||
| consumers = consumer_map.get(node.output[0], []) | ||
| if not consumers or not all(c.op_type in _Q_OPS for c in consumers): | ||
|
|
@@ -1492,6 +1497,53 @@ def fold_q_fp16_to_fp32_casts(onnx_model: onnx.ModelProto) -> onnx.ModelProto: | |
| return onnx_model | ||
|
|
||
|
|
||
| def _convert_q_data_initializers_to_fp16(onnx_model: onnx.ModelProto) -> onnx.ModelProto: | ||
| """Convert FP32 initializer data inputs after Q/DQ scales have been normalized to FP16.""" | ||
| if get_opset_version(onnx_model) < BASE_MIN_OPSET: | ||
| return onnx_model | ||
|
|
||
| consumers: dict[str, list[tuple[onnx.NodeProto, int]]] = defaultdict(list) | ||
| for node in onnx_model.graph.node: | ||
| for index, input_name in enumerate(node.input): | ||
| consumers[input_name].append((node, index)) | ||
|
|
||
| initializers = {initializer.name: initializer for initializer in onnx_model.graph.initializer} | ||
| tensor_types = _build_tensor_type_map(onnx_model) | ||
|
|
||
| for name, initializer in list(initializers.items()): | ||
| if initializer.data_type != onnx.TensorProto.FLOAT: | ||
| continue | ||
|
|
||
| initializer_consumers = consumers.get(name, []) | ||
| q_consumers = [ | ||
| node for node, index in initializer_consumers if index == 0 and node.op_type in _Q_OPS | ||
| ] | ||
| if not q_consumers: | ||
| continue | ||
|
|
||
| for q_node in q_consumers: | ||
| scale_type = tensor_types.get(q_node.input[1]) if len(q_node.input) >= 2 else None | ||
| if scale_type != onnx.TensorProto.FLOAT16: | ||
| raise ValueError("Q scales must be FP16 before converting Q data initializers") | ||
|
|
||
| fp16_initializer = onnx.numpy_helper.from_array( | ||
| convert_np_to_float16(onnx.numpy_helper.to_array(initializer)), initializer.name | ||
| ) | ||
| if len(q_consumers) == len(initializer_consumers): | ||
| initializer.CopyFrom(fp16_initializer) | ||
| continue | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| fp16_initializer.name = f"{initializer.name}_fp16_q" | ||
| while fp16_initializer.name in initializers: | ||
| fp16_initializer.name += "_" | ||
| onnx_model.graph.initializer.append(fp16_initializer) | ||
| initializers[fp16_initializer.name] = fp16_initializer | ||
| for node in q_consumers: | ||
| node.input[0] = fp16_initializer.name | ||
|
|
||
| return onnx_model | ||
|
|
||
|
|
||
| def _is_foldable_constant_cast_pattern(model: onnx.ModelProto, node: onnx.NodeProto) -> bool: | ||
| """Check if a Constant -> Cast pattern can be folded.""" | ||
| assert node.op_type == "Cast" | ||
|
|
||
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.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Keep the input-stem skip ResNet-specific.
This skips FP8 weight DQ for any direct RGB Conv, while the Torch-side exclusion only applies to
timm.models.resnet.ResNet. A non-ResNet model with a direct three-channel input can therefore retain FP16 weights even though this exporter normally restores Conv weight FP8 DQ. Propagate an explicit ResNet/export marker into this pass, or otherwise avoid using RGB topology as the model-family discriminator.🤖 Prompt for AI Agents