Skip to content

Commit 855fe5c

Browse files
authored
Remove pad and identity rewrite (#1432)
## Summary Remove two CGC compatibility workarounds following upstream IX fixes already included in the current compiler version (`35c01c26`). | Removed workaround | Upstream issue | Fix | |---|---|---| | `eliminate-identity` | microsoft/ix#1198 — ONNX Identity fails to lower to Foundry and DXCGC | microsoft/ix#1201 adds native Identity lowering. | | Pad-specific constant folding (`fold_constant_pad_pads`) and the `fold-constant-pad-pads` compatibility alias | microsoft/ix#1199 — Support constant folding of ONNX Pad parameters before lowering | microsoft/ix#1206 improves Cast/Pad chain support, static shape inference, and lowering type compatibility. | ## Changes - Remove the Identity rewrite implementation, capability registration, and public export. - Remove the Pad-specific folding prepass and its dedicated dead-producer cleanup. - Remove the `fold-constant-pad-pads` alias and public function export. - Update documentation and tests to reflect the retired rules. - Preserve `cgc-constant-folding` and its shared integer/boolean expression evaluator. General folding remains necessary for static shape chains and continues to fold Pad parameters in graphs containing `Shape`. - Leave compiler options, including the default-enabled topological sorting, unchanged. ## Validation - 203 affected unit tests passed. - All five models that previously used the Pad-specific prepass retained identical Pad parameter values. - GPU outputs before and after Pad-prepass removal were elementwise identical across three input samples per model. - The three SOD models required topological sorting to be disabled for the GPU comparison because the current wheel does not yet include the fix for microsoft/ix#1207. Their default-path conversion failure remains unchanged. - End-to-end SOD validation through JSON config, build, perf, and eval is still in progress.
1 parent 0091a66 commit 855fe5c

6 files changed

Lines changed: 42 additions & 629 deletions

File tree

docs/commands/optimize.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,10 +135,9 @@ the shared pattern package does not re-export these backend-specific patterns.
135135
|------------|--------------------------|
136136
| `normalize-int32-dq` | Normalize initializer-backed INT32 `DequantizeLinear` in the standard domain (opset >= 10) and `com.microsoft` (opset 1): omit immutable all-zero scalar/singleton zero points and clone singleton scales as scalars. Preserve shared initializers and domains; skip overridable parameters, per-axis vectors, unsupported attributes and nonlocal inputs. Handles nested graphs, not local functions. Enabled by CGC build configuration, disabled by default elsewhere. |
137137
| `deduplicate-opset-imports` | Remove repeated model-level opset declarations with identical domain and version, retaining the first declaration and domain order. Reject conflicting versions for the same domain. Run before operator rewrites and opset upgrades; do not alter graph content, local functions, or the retained versions. |
138-
| `eliminate-identity` | Remove safe internal tensor Identity aliases. Additionally replace top-level standard-domain FP32 graph-output Identities with same-shape Reshape when input/output types match exactly, all dimensions are positive static integers, opset >= 5, and the model has no subgraphs. Preserve output names/order, annotated aliases, unknown or conflicting types, scalar/dynamic/zero-size outputs and protected captures. Does not rewrite local functions. Workaround for [microsoft/ix#1198](https://github.com/microsoft/ix/issues/1198). |
139138
| `gridsample-to-gather` | Decompose 2D `GridSample` with linear interpolation and zero padding into four `GatherND` reads, bounds masks and weighted sums. Supports both `align_corners` settings, FP16/FP32 IO and dynamic batch, with known positive channel, input spatial and grid spatial dimensions. Rank-3 indices contain explicit batch and spatial coordinates; `batch_dims=0` avoids the ORT symbolic shape inference defect tracked in [onnxruntime#24206](https://github.com/microsoft/onnxruntime/pull/24206). Batch coordinates are generated dynamically and shared across the four reads; sampled values are reshaped back to the grid layout. FP16 interpolation is computed in FP32 and cast back. Requires opset >= 16 (`bilinear` before opset 20); other modes are unchanged. Enabled by CGC builds, disabled in ordinary optimization. Floating-point rounding may differ from native sampling. |
140139
| `omit-empty-resize-inputs` | Replace statically empty Resize ROI/scales with omitted inputs. Do not rely on graph-input defaults or rewrite crop-and-resize semantics. Requires opset 13; upgrade older matching models using ONNX version conversion. |
141-
| `cgc-constant-folding` | Fill FoundryToolbox constant-folding gaps without an ORT Session. Fold standard `Pad.pads` constant integer chains; in graphs containing `Shape`, also fold statically known selected dimensions and bounded constant integer/boolean expressions (`Gather`, `Concat`, `Reshape`, `Slice`, `Transpose`, `Squeeze`, `Unsqueeze`, integer `Cast`, `ConstantOfShape`, arithmetic, `Equal`, `Where`). Iterate with shape inference, up to 32 rounds. Requires opset >= 11. Only the main graph is rewritten; preserve tensor names for shared uses and subgraph captures. Runtime floating-point computations and unresolved dimensions remain unchanged. This rule does not freeze inputs: specialize dimensions before optimization when needed; later Foundry `freeze-dims` does not retroactively affect this rule. Limits: 128 dependency values per traversal, 65,536 elements per operation and 1,048,576 cached elements per round. Enabled by CGC builds; disabled in ordinary optimization. `fold-constant-pad-pads` remains a compatibility alias. |
140+
| `cgc-constant-folding` | Fill FoundryToolbox constant-folding gaps without an ORT Session. In graphs containing `Shape`, fold statically known selected dimensions and bounded constant integer/boolean expressions (`Gather`, `Concat`, `Reshape`, `Slice`, `Transpose`, `Squeeze`, `Unsqueeze`, integer `Cast`, `ConstantOfShape`, arithmetic, `Equal`, `Where`). Iterate with shape inference, up to 32 rounds. Requires opset >= 11. Only the main graph is rewritten; preserve tensor names for shared uses and subgraph captures. Graphs without `Shape`, runtime floating-point computations and unresolved dimensions remain unchanged. This rule does not freeze inputs: specialize dimensions before optimization when needed; later Foundry `freeze-dims` does not retroactively affect this rule. Limits: 128 dependency values per traversal, 65,536 elements per operation and 1,048,576 cached elements per round. Enabled by CGC builds; disabled in ordinary optimization. |
142141
| `resize-tf-half-pixel-for-nn-to-asymmetric` | Change only the coordinate mode for nearest/floor Resize with static, non-overridable, positive integer scales. Dynamic/fractional scales and sizes-based inference are outside this rule. |
143142
| `approximate-cubic-resize-with-linear` | **Lossy**, explicit cubic-to-linear approximation. Excludes antialiasing, outside exclusion, and crop-and-resize semantics. Prints a warning when applied. |
144143
| `gathernd-to-reshape` | Replace GatherND only when data/indices/output ranks are not all equal and static, non-overridable int64 indices visit every input slice exactly once in storage order. Require positive static data dimensions; support batch dimensions, multi-coordinate indices, and equivalent negative indices. Dynamic data shapes or indices, overridable defaults, empty tensors, partial selection, repetition, and reordering are outside this rule. |

src/winml/modelkit/optim/pipes/cgir_rewrite_rules.py

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
ResizeWithTfHalfPixelForNNPattern,
3030
cgc_constant_folding,
3131
deduplicate_opset_imports,
32-
eliminate_identity,
3332
normalize_int32_dq,
3433
)
3534
from ..registry import BoolCapability, CapabilityCategory
@@ -73,26 +72,10 @@ class CGIRModelRewriteRule:
7372
default=False,
7473
)
7574

76-
ELIMINATE_IDENTITY = BoolCapability(
77-
name="eliminate-identity",
78-
ort_name=None,
79-
description="Eliminate internal tensor Identity aliases without changing graph IO for CGIR",
80-
category=CapabilityCategory.REWRITE,
81-
default=False,
82-
)
83-
84-
FOLD_CONSTANT_PAD_PADS = BoolCapability(
85-
name="fold-constant-pad-pads",
86-
ort_name=None,
87-
description="Alias for cgc-constant-folding",
88-
category=CapabilityCategory.REWRITE,
89-
default=False,
90-
)
91-
9275
CGC_CONSTANT_FOLDING = BoolCapability(
9376
name="cgc-constant-folding",
9477
ort_name=None,
95-
description="Fill FoundryToolbox folding gaps for Pad parameters and static shape subgraphs",
78+
description="Fill FoundryToolbox folding gaps for static shape subgraphs",
9679
category=CapabilityCategory.REWRITE,
9780
default=False,
9881
)
@@ -169,7 +152,6 @@ class CGIRModelRewriteRule:
169152
CGIRModelRewriteRule(
170153
capability=CGC_CONSTANT_FOLDING,
171154
transform=cgc_constant_folding,
172-
aliases=(FOLD_CONSTANT_PAD_PADS,),
173155
),
174156
CGIRModelRewriteRule(
175157
capability=DEDUPLICATE_OPSET_IMPORTS,
@@ -214,10 +196,6 @@ class CGIRModelRewriteRule:
214196
target=MatMulDFTPattern,
215197
minimum_opset=17,
216198
),
217-
CGIRModelRewriteRule(
218-
capability=ELIMINATE_IDENTITY,
219-
transform=eliminate_identity,
220-
),
221199
CGIRRewriteRule(
222200
capability=GRIDSAMPLE_TO_GATHER,
223201
source=LinearGridSamplePattern,
@@ -240,8 +218,6 @@ class CGIRModelRewriteRule:
240218
"CGIR_REWRITE_RULES",
241219
"DEDUPLICATE_OPSET_IMPORTS",
242220
"DFT_TO_MATMUL",
243-
"ELIMINATE_IDENTITY",
244-
"FOLD_CONSTANT_PAD_PADS",
245221
"GATHERND_TO_RESHAPE",
246222
"GRIDSAMPLE_TO_GATHER",
247223
"OMIT_EMPTY_RESIZE_INPUTS",

src/winml/modelkit/pattern/cgc/__init__.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@
44
# --------------------------------------------------------------------------
55
"""Opt-in CGC compatibility patterns and model metadata rewrites."""
66

7-
from .cgc_constant_folding import cgc_constant_folding, fold_constant_pad_pads
7+
from .cgc_constant_folding import cgc_constant_folding
88
from .dft_patterns import DFTWithStaticParametersPattern, MatMulDFTPattern
99
from .dq_rewrites import normalize_int32_dq
1010
from .gathernd_patterns import GatherNDWithIdentityIndicesPattern, ReshapedGatherNDPattern
1111
from .gridsample_patterns import GatherLinearGridSamplePattern, LinearGridSamplePattern
12-
from .identity_rewrites import eliminate_identity
1312
from .opset_rewrites import deduplicate_opset_imports
1413
from .prelu_patterns import ExpandedPReluPattern, PReluWithFiniteSlopePattern
1514
from .resize_patterns import (
@@ -39,7 +38,5 @@
3938
"ResizeWithTfHalfPixelForNNPattern",
4039
"cgc_constant_folding",
4140
"deduplicate_opset_imports",
42-
"eliminate_identity",
43-
"fold_constant_pad_pads",
4441
"normalize_int32_dq",
4542
]

src/winml/modelkit/pattern/cgc/cgc_constant_folding.py

Lines changed: 10 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# Licensed under the MIT License.
44
# --------------------------------------------------------------------------
55

6-
"""Bounded constant folding for CGC Pad parameters and static shape subgraphs.
6+
"""Bounded constant folding for CGC static shape subgraphs.
77
88
FoundryToolbox currently lacks some constant folding needed by ONNX lowering.
99
These rewrites fill that gap without an ORT Session or execution-provider graph
@@ -14,13 +14,10 @@
1414

1515
import logging
1616
import math
17-
from collections import Counter, deque
1817
from typing import cast
1918

2019
import numpy as np
2120
from onnx import (
22-
AttributeProto,
23-
GraphProto,
2421
ModelProto,
2522
TensorProto,
2623
ValueInfoProto,
@@ -62,7 +59,6 @@ def __init__(self, model: ModelProto, *, static_shapes: bool = False) -> None:
6259
self.initializers = {value.name: value for value in model.graph.initializer}
6360
self.inputs = {value.name for value in model.graph.input}
6461
self.values: dict[str, np.ndarray] = {}
65-
self.evaluated: set[str] = set()
6662
self.cached_elements = 0
6763

6864
def tensor(self, value: TensorProto) -> np.ndarray:
@@ -153,15 +149,14 @@ def evaluate(self, name: str, visited: set[str], active: set[str]) -> np.ndarray
153149
raise ValueError("Only integer Cast targets are supported")
154150
fragment = helper.make_model(
155151
helper.make_graph(
156-
[node], "constant_pad_parameter", [],
152+
[node], "constant_parameter", [],
157153
[ValueInfoProto(name=name)],
158154
[numpy_helper.from_array(value, key) for key, value in inputs.items()],
159155
),
160156
opset_imports=list(self.model.opset_import),
161157
ir_version=self.model.ir_version,
162158
)
163159
result = cast("list[np.ndarray]", ReferenceEvaluator(fragment).run(None, {}))[0]
164-
self.evaluated.add(name)
165160
if result.dtype.kind not in "iub" or result.size > _MAX_ELEMENTS:
166161
raise ValueError("Constant result exceeds supported type or size")
167162
if self.cached_elements + result.size > _MAX_CACHED_ELEMENTS:
@@ -173,150 +168,22 @@ def evaluate(self, name: str, visited: set[str], active: set[str]) -> np.ndarray
173168
active.remove(name)
174169

175170

176-
def _referenced_names(graph: GraphProto) -> list[str]:
177-
names = [value.name for value in graph.output]
178-
for annotation in graph.quantization_annotation:
179-
names.append(annotation.tensor_name)
180-
names.extend(item.value for item in annotation.quant_parameter_tensor_names)
181-
for node in graph.node:
182-
names.extend(name for name in node.input if name)
183-
for attribute in node.attribute:
184-
if attribute.type == AttributeProto.GRAPH:
185-
names.extend(_referenced_names(attribute.g))
186-
elif attribute.type == AttributeProto.GRAPHS:
187-
for child in attribute.graphs:
188-
names.extend(_referenced_names(child))
189-
return names
190-
191-
192-
def fold_constant_pad_pads(model: ModelProto) -> ModelProto:
193-
"""Fold constant integer Pad widths without specializing runtime input shapes.
194-
195-
Only the main graph is rewritten. Nested graph captures and quantization
196-
annotations conservatively protect shared producers from removal.
197-
"""
198-
versions = [item.version for item in model.opset_import if item.domain in {"", "ai.onnx"}]
199-
if not versions or len(set(versions)) != 1 or versions[0] < 11:
200-
return model
201-
candidates = [
202-
(index, node) for index, node in enumerate(model.graph.node)
203-
if node.domain in {"", "ai.onnx"} and node.op_type == "Pad"
204-
and len(node.input) >= 2 and node.input[1]
205-
]
206-
if not candidates:
207-
return model
208-
evaluator = _ConstantParameters(model)
209-
types = {
210-
value.name: value.type for value in
211-
[*model.graph.input, *model.graph.value_info, *model.graph.output]
212-
}
213-
replacements: dict[int, np.ndarray] = {}
214-
selected_dependencies: set[str] = set()
215-
for index, node in candidates:
216-
producer = evaluator.producers.get(node.input[1])
217-
if producer is None or producer.op_type == "Constant":
218-
continue
219-
try:
220-
visited: set[str] = set()
221-
pads = evaluator.evaluate(node.input[1], visited, set())
222-
if pads.dtype != np.int64 or pads.ndim != 1 or pads.size % 2:
223-
continue
224-
tensor_type = types.get(node.input[0])
225-
rank = (
226-
len(tensor_type.tensor_type.shape.dim)
227-
if tensor_type is not None and tensor_type.tensor_type.HasField("shape") else None
228-
)
229-
if len(node.input) > 3 and node.input[3]:
230-
if versions[0] < 18:
231-
continue
232-
axes = evaluator.evaluate(node.input[3], visited, set())
233-
if axes.ndim != 1 or axes.dtype not in {np.dtype("int32"), np.dtype("int64")}:
234-
continue
235-
if pads.size != 2 * axes.size:
236-
continue
237-
if rank is not None:
238-
if np.any(axes < -rank) or np.any(axes >= rank):
239-
continue
240-
if len({int(axis) % rank for axis in axes}) != axes.size:
241-
continue
242-
elif rank is not None and pads.size != 2 * rank:
243-
continue
244-
replacements[index] = pads
245-
selected_dependencies.update(visited)
246-
except (
247-
ValueError, KeyError, TypeError, IndexError, StopIteration,
248-
NotImplementedError, OverflowError,
249-
):
250-
logger.debug("Pad constant parameter is not foldable: %s", node.name, exc_info=True)
251-
if not replacements:
252-
return model
253-
254-
rewritten = ModelProto()
255-
rewritten.CopyFrom(model)
256-
used_names = set(evaluator.producers) | set(evaluator.initializers) | evaluator.inputs
257-
used_names.update(_referenced_names(model.graph))
258-
used_names.update(value.name for value in model.graph.value_info)
259-
constants = []
260-
folded_names: dict[str, str] = {}
261-
for index, value in replacements.items():
262-
node = rewritten.graph.node[index]
263-
source = node.input[1]
264-
if source not in folded_names:
265-
name = source + "_folded_pads"
266-
while name in used_names:
267-
name += "_"
268-
used_names.add(name)
269-
folded_names[source] = name
270-
constants.append(helper.make_node(
271-
"Constant", [], [name], value=numpy_helper.from_array(value),
272-
))
273-
node.input[1] = folded_names[source]
274-
275-
references = Counter(_referenced_names(rewritten.graph))
276-
removable = {
277-
node.output[0]: node for node in rewritten.graph.node
278-
if len(node.output) == 1
279-
and node.output[0] in evaluator.evaluated & selected_dependencies
280-
}
281-
pending = deque(name for name in removable if not references[name])
282-
removed: set[str] = set()
283-
while pending:
284-
name = pending.popleft()
285-
if name in removed:
286-
continue
287-
removed.add(name)
288-
for source in removable[name].input:
289-
references[source] -= 1
290-
if source in removable and not references[source]:
291-
pending.append(source)
292-
nodes = [node for node in rewritten.graph.node if not any(n in removed for n in node.output)]
293-
del rewritten.graph.node[:]
294-
rewritten.graph.node.extend([*constants, *nodes])
295-
infos = [value for value in rewritten.graph.value_info if value.name not in removed]
296-
del rewritten.graph.value_info[:]
297-
rewritten.graph.value_info.extend(infos)
298-
logger.info("Folded constant pads for %d Pad node(s)", len(replacements))
299-
return rewritten
300-
301-
302171
def cgc_constant_folding(model: ModelProto) -> ModelProto:
303-
"""Fill FoundryToolbox constant-folding gaps for Pad and static Shape chains.
172+
"""Fill FoundryToolbox constant-folding gaps for static Shape chains.
304173
305-
Only the main graph is changed. Pad widths use the existing bounded folder;
306-
graphs containing Shape also fold bounded integer/boolean constant chains
174+
Only the main graph is changed. Graphs containing Shape fold bounded integer/boolean chains
307175
to a fixed point with shape inference. This never freezes symbolic input
308176
dimensions: callers must specialize inputs explicitly before this rule when
309177
required. Casts of runtime data, including floating-point outputs, remain.
310178
"""
311-
prepared = fold_constant_pad_pads(model)
312179
if not any(node.op_type == "Shape" and node.domain in {"", "ai.onnx"}
313-
for node in prepared.graph.node):
314-
return prepared
315-
versions = {item.version for item in prepared.opset_import if item.domain in {"", "ai.onnx"}}
180+
for node in model.graph.node):
181+
return model
182+
versions = {item.version for item in model.opset_import if item.domain in {"", "ai.onnx"}}
316183
if len(versions) != 1 or next(iter(versions)) < 11:
317-
return prepared
184+
return model
318185
rewritten = ModelProto()
319-
rewritten.CopyFrom(prepared)
186+
rewritten.CopyFrom(model)
320187
changed = False
321188
for _iteration in range(32):
322189
for value_info in rewritten.graph.value_info:
@@ -339,7 +206,7 @@ def cgc_constant_folding(model: ModelProto) -> ModelProto:
339206
))
340207
folded += 1
341208
if not folded:
342-
return rewritten if changed else prepared
209+
return rewritten if changed else model
343210
changed = True
344211
logger.info("CGC constant folding: folded %d shape/integer node(s)", folded)
345212
logger.warning("CGC constant folding reached the 32-round limit; retaining partial folding")

0 commit comments

Comments
 (0)