Skip to content

[ET-VK] Add dynamic-shape resize to q8ta ops#20312

Merged
meta-codesync[bot] merged 4 commits into
gh/SS-JIA/558/basefrom
gh/SS-JIA/558/head
Jun 18, 2026
Merged

[ET-VK] Add dynamic-shape resize to q8ta ops#20312
meta-codesync[bot] merged 4 commits into
gh/SS-JIA/558/basefrom
gh/SS-JIA/558/head

Conversation

@SS-JIA

@SS-JIA SS-JIA commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Stack from ghstack (oldest at bottom):

The q8ta (quantized int8) op DynamicDispatchNodes were constructed with an empty resize-args list and no resize function, so their output tensors were never virtual_resized on trigger_resize(). On a dynamic-shape graph this froze the q8ta outputs at the build-time upper-bound shape — the same failure mode the fp32 ops already avoid. Concretely, in a quantized Vulkan-delegated graph the terminal pointwise conv produces the graph output, so a smaller input (e.g. 238 rows fed into a graph allocated at the 241-row upper bound) left stale rows that propagate downstream, where GroupNorm's global per-group statistics smear them across the whole tensor.

Add resize functions across the q8ta op family, each matching that op's output-shape semantics (mirroring the corresponding fp32 op's resize):

  • q8ta_conv2d / q8ta_conv2d_dw: output H/W recomputed from the input via calc_out_sizes_hw.
  • q8ta_conv2d_pw: 1x1 conv preserves spatial dims (out H/W == in H/W).
  • q8ta_conv2d_transposed: transposed output formula via calc_out_sizes_hw(transposed=true) (threads output_padding through the dispatch, which was previously dropped).
  • q8ta im2col scratch: flattened-window K from channels/kernel/groups, H_out/W_out from the current input.
  • q8ta_linear: [*input.shape[:-1], out_features].
  • q8ta binary: broadcast(in_a, in_b).
  • q8ta quantize / dequantize: elementwise, output shape == input shape.

The quantized conv/quant path now honors dynamic input shapes like the fp32 path.

Differential Revision: D108788845

[ghstack-poisoned]
@pytorch-bot

pytorch-bot Bot commented Jun 16, 2026

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/20312

Note: Links to docs will display an error until the docs builds have been completed.

❌ 6 New Failures, 1 Unrelated Failure

As of commit 8530ff7 with merge base 0eb8247 (image):

NEW FAILURES - The following jobs have failed:

BROKEN TRUNK - The following job failed but were present on the merge base:

👉 Rebase onto the `viable/strict` branch to avoid these failures

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jun 16, 2026
@linux-foundation-easycla

linux-foundation-easycla Bot commented Jun 16, 2026

Copy link
Copy Markdown

CLA Missing ID

@github-actions

Copy link
Copy Markdown

This PR needs a release notes: label

If your change should be included in the release notes (i.e. would users of this library care about this change?), please use a label starting with release notes:. This helps us keep track and include your important work in the next release notes.

To add a label, you can comment to pytorchbot, for example
@pytorchbot label "release notes: none"

For more information, see
https://github.com/pytorch/pytorch/wiki/PyTorch-AutoLabel-Bot#why-categorize-for-release-notes-and-how-does-it-work.

[ghstack-poisoned]
SS-JIA pushed a commit that referenced this pull request Jun 17, 2026
Pull Request resolved: #20312

The q8ta (quantized int8) op `DynamicDispatchNode`s were constructed with an empty resize-args list and no resize function, so their output tensors were never `virtual_resize`d on `trigger_resize()`. On a dynamic-shape graph this froze the q8ta outputs at the build-time upper-bound shape — the same failure mode the fp32 ops already avoid. Concretely, in a quantized Vulkan-delegated graph the terminal pointwise conv produces the graph output, so a smaller input (e.g. 238 rows fed into a graph allocated at the 241-row upper bound) left stale rows that propagate downstream, where GroupNorm's global per-group statistics smear them across the whole tensor.

Add resize functions across the q8ta op family, each matching that op's output-shape semantics (mirroring the corresponding fp32 op's resize):
- `q8ta_conv2d` / `q8ta_conv2d_dw`: output H/W recomputed from the input via `calc_out_sizes_hw`.
- `q8ta_conv2d_pw`: 1x1 conv preserves spatial dims (out H/W == in H/W).
- `q8ta_conv2d_transposed`: transposed output formula via `calc_out_sizes_hw(transposed=true)` (threads `output_padding` through the dispatch, which was previously dropped).
- `q8ta` im2col scratch: flattened-window `K` from channels/kernel/groups, `H_out`/`W_out` from the current input.
- `q8ta_linear`: `[*input.shape[:-1], out_features]`.
- `q8ta` binary: `broadcast(in_a, in_b)`.
- `q8ta` quantize / dequantize: elementwise, output shape == input shape.

The quantized conv/quant path now honors dynamic input shapes like the fp32 path.
ghstack-source-id: 394207699
@exported-using-ghexport

Differential Revision: [D108788845](https://our.internmc.facebook.com/intern/diff/D108788845/)
SS-JIA pushed a commit that referenced this pull request Jun 17, 2026
Pull Request resolved: #20312

The q8ta (quantized int8) op `DynamicDispatchNode`s were constructed with an empty resize-args list and no resize function, so their output tensors were never `virtual_resize`d on `trigger_resize()`. On a dynamic-shape graph this froze the q8ta outputs at the build-time upper-bound shape — the same failure mode the fp32 ops already avoid. Concretely, in a quantized Vulkan-delegated graph the terminal pointwise conv produces the graph output, so a smaller input (e.g. 238 rows fed into a graph allocated at the 241-row upper bound) left stale rows that propagate downstream, where GroupNorm's global per-group statistics smear them across the whole tensor.

Add resize functions across the q8ta op family, each matching that op's output-shape semantics (mirroring the corresponding fp32 op's resize):
- `q8ta_conv2d` / `q8ta_conv2d_dw`: output H/W recomputed from the input via `calc_out_sizes_hw`.
- `q8ta_conv2d_pw`: 1x1 conv preserves spatial dims (out H/W == in H/W).
- `q8ta_conv2d_transposed`: transposed output formula via `calc_out_sizes_hw(transposed=true)` (threads `output_padding` through the dispatch, which was previously dropped).
- `q8ta` im2col scratch: flattened-window `K` from channels/kernel/groups, `H_out`/`W_out` from the current input.
- `q8ta_linear`: `[*input.shape[:-1], out_features]`.
- `q8ta` binary: `broadcast(in_a, in_b)`.
- `q8ta` quantize / dequantize: elementwise, output shape == input shape.

The quantized conv/quant path now honors dynamic input shapes like the fp32 path.
ghstack-source-id: 394212019
@exported-using-ghexport

Differential Revision: [D108788845](https://our.internmc.facebook.com/intern/diff/D108788845/)
[ghstack-poisoned]
SS-JIA pushed a commit that referenced this pull request Jun 17, 2026
Pull Request resolved: #20312

The q8ta (quantized int8) op `DynamicDispatchNode`s were constructed with an empty resize-args list and no resize function, so their output tensors were never `virtual_resize`d on `trigger_resize()`. On a dynamic-shape graph this froze the q8ta outputs at the build-time upper-bound shape — the same failure mode the fp32 ops already avoid. Concretely, in a quantized Vulkan-delegated graph the terminal pointwise conv produces the graph output, so a smaller input (e.g. 238 rows fed into a graph allocated at the 241-row upper bound) left stale rows that propagate downstream, where GroupNorm's global per-group statistics smear them across the whole tensor.

Add resize functions across the q8ta op family, each matching that op's output-shape semantics (mirroring the corresponding fp32 op's resize):
- `q8ta_conv2d` / `q8ta_conv2d_dw`: output H/W recomputed from the input via `calc_out_sizes_hw`.
- `q8ta_conv2d_pw`: 1x1 conv preserves spatial dims (out H/W == in H/W).
- `q8ta_conv2d_transposed`: transposed output formula via `calc_out_sizes_hw(transposed=true)` (threads `output_padding` through the dispatch, which was previously dropped).
- `q8ta` im2col scratch: flattened-window `K` from channels/kernel/groups, `H_out`/`W_out` from the current input.
- `q8ta_linear`: `[*input.shape[:-1], out_features]`.
- `q8ta` binary: `broadcast(in_a, in_b)`.
- `q8ta` quantize / dequantize: elementwise, output shape == input shape.

The quantized conv/quant path now honors dynamic input shapes like the fp32 path.
ghstack-source-id: 394414502
@exported-using-ghexport

Differential Revision: [D108788845](https://our.internmc.facebook.com/intern/diff/D108788845/)
[ghstack-poisoned]
SS-JIA pushed a commit that referenced this pull request Jun 17, 2026
Pull Request resolved: #20312

The q8ta (quantized int8) op `DynamicDispatchNode`s were constructed with an empty resize-args list and no resize function, so their output tensors were never `virtual_resize`d on `trigger_resize()`. On a dynamic-shape graph this froze the q8ta outputs at the build-time upper-bound shape — the same failure mode the fp32 ops already avoid. Concretely, in a quantized Vulkan-delegated graph the terminal pointwise conv produces the graph output, so a smaller input (e.g. 238 rows fed into a graph allocated at the 241-row upper bound) left stale rows that propagate downstream, where GroupNorm's global per-group statistics smear them across the whole tensor.

Add resize functions across the q8ta op family, each matching that op's output-shape semantics (mirroring the corresponding fp32 op's resize):
- `q8ta_conv2d` / `q8ta_conv2d_dw`: output H/W recomputed from the input via `calc_out_sizes_hw`.
- `q8ta_conv2d_pw`: 1x1 conv preserves spatial dims (out H/W == in H/W).
- `q8ta_conv2d_transposed`: transposed output formula via `calc_out_sizes_hw(transposed=true)` (threads `output_padding` through the dispatch, which was previously dropped).
- `q8ta` im2col scratch: flattened-window `K` from channels/kernel/groups, `H_out`/`W_out` from the current input.
- `q8ta_linear`: `[*input.shape[:-1], out_features]`.
- `q8ta` binary: `broadcast(in_a, in_b)`.
- `q8ta` quantize / dequantize: elementwise, output shape == input shape.

The quantized conv/quant path now honors dynamic input shapes like the fp32 path.
ghstack-source-id: 394480015
@exported-using-ghexport

Differential Revision: [D108788845](https://our.internmc.facebook.com/intern/diff/D108788845/)
@SS-JIA

SS-JIA commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

@claude Review this PR

@claude

claude Bot commented Jun 17, 2026

Copy link
Copy Markdown

Claude finished @SS-JIA's task in 8m 59s —— View job


PR Review: Add dynamic-shape resize to q8ta ops

  • Read the full diff against base
  • Review each q8ta op's resize function for correctness
  • Check resize-args / shape-semantics consistency with fp32 ops
  • Review test utility additions
  • Post review feedback

Reviewed against origin/gh/SS-JIA/558/base. This is a solid, well-reasoned fix. The root cause (q8ta DynamicDispatchNodes built with empty resize-args and nullptr resize fn → outputs frozen at build-time upper bound) is clearly identified, and each resize function correctly mirrors its op's output-shape semantics. The commentary is excellent and the force_resize test mechanism is a clever way to exercise every resize formula across the swept shapes.

I verified the supporting infra and ArgGroup layouts, and the resize logic checks out:

  • Binary (Q8taBinary.cpp:127): ArgGroup is {{out, kWrite}, {{in_a, in_b}, kRead}}, so args.at(1).refs.at(0/1) correctly reaches in_a/in_b. Matches fp32 resize_binary_op_node. ✅
  • Linear (Q8taLinear.cpp:150-156): args.at(1).refs.at(0) is packed_int8_input; keeps last dim (out_features), takes leading dims from input. ✅
  • Quant/dequant (Q8taQuantizeDequantize.cpp:87): args.at(1).refs.at(0) is the fp/int8 input; elementwise copy is correct. ✅
  • Conv / DW / PW-im2col / transposed: calc_out_sizes_hw H/W recompute from current input, tail dims overwritten, N/C preserved — consistent with the fp32 resize_conv2d_node. The transposed path correctly threads the previously-dropped output_padding into the transposed=true formula. ✅
  • PW standalone vs PW im2col split via conv_input == kDummyValueRef is a clean dispatch; the standalone path's stride-1/pad-0 assumption matches the op contract (Q8taConv2dPW.cpp:372). ✅
  • im2col scratch (Q8taConv2dIm2Col.cpp:137-149): K/W recompute exactly mirrors calculate_q8ta_im2col_sizes, and recomputing from the original input (not the conv output, which may still be frozen during resize) is the right call and well-documented. ✅

A few minor, non-blocking notes:

1. The {stride, padding, dilation, dilation} filler arg is obscure. In the non-transposed conv/DW/im2col/PW resize calls, the 4th slot of calc_out_sizes_hw is the ceil_mode position, and dilation (a non-bool IntList) is passed purely to resolve val_is_bool(...)false. The comment at Q8taConv2d.cpp:249-251 explains it, but reusing dilation reads as if dilation matters twice. If calc_out_sizes_hw's val_is_bool check handles kDummyValueRef gracefully, passing kDummyValueRef in that slot would express intent more clearly. Worth a quick check rather than a required change.

2. Resize-function duplication. resize_q8ta_conv2d_node, resize_q8ta_conv2d_dw_node, and resize_q8ta_conv2d_pw_im2col_node are nearly identical (same calc_out_sizes_hw call + tail-dim overwrite). Per the repo's "no duplicated logic" preference, these could collapse into one shared helper taking the resize_args. That said, the fp32 side has similar per-op resize fns, so matching that pattern is defensible — flagging only for consideration.

3. force_resize default-on is a repo-wide test behavior change. TestCase::reset() defaults force_resize_ = true (utils.h:667), so all ~24 custom_ops test files now run every DynamicDispatchNode's resize fn on each execute. This is good (it surfaces latent resize bugs), but it means a pre-existing buggy/missing resize in an unrelated op could now show up as a failure here. Worth confirming the full custom_ops suite is green, not just the q8ta tests. The opt-out path (set_force_resize(false)) is documented well for the intentionally-non-shape-preserving cases.

4. CI failures appear unrelated. The Dr. CI failures (arm-tosa, qnn-llama, lora-multimethod, voxtral) are infra/docker exit-code failures and SQNR jobs on other backends — none touch Vulkan q8ta. Confirm the Vulkan custom_ops job specifically passed.

No correctness issues found. Nice fix.
· branch gh/SS-JIA/558/head

@meta-codesync meta-codesync Bot merged commit e751675 into gh/SS-JIA/558/base Jun 18, 2026
174 of 182 checks passed
@meta-codesync meta-codesync Bot deleted the gh/SS-JIA/558/head branch June 18, 2026 01:52
@meta-codesync meta-codesync Bot temporarily deployed to cherry-pick-bot June 18, 2026 01:52 Inactive
SS-JIA pushed a commit that referenced this pull request Jun 18, 2026
Pull Request resolved: #20312

The q8ta (quantized int8) op `DynamicDispatchNode`s were constructed with an empty resize-args list and no resize function, so their output tensors were never `virtual_resize`d on `trigger_resize()`. On a dynamic-shape graph this froze the q8ta outputs at the build-time upper-bound shape — the same failure mode the fp32 ops already avoid. Concretely, in a quantized Vulkan-delegated graph the terminal pointwise conv produces the graph output, so a smaller input (e.g. 238 rows fed into a graph allocated at the 241-row upper bound) left stale rows that propagate downstream, where GroupNorm's global per-group statistics smear them across the whole tensor.

Add resize functions across the q8ta op family, each matching that op's output-shape semantics (mirroring the corresponding fp32 op's resize):
- `q8ta_conv2d` / `q8ta_conv2d_dw`: output H/W recomputed from the input via `calc_out_sizes_hw`.
- `q8ta_conv2d_pw`: 1x1 conv preserves spatial dims (out H/W == in H/W).
- `q8ta_conv2d_transposed`: transposed output formula via `calc_out_sizes_hw(transposed=true)` (threads `output_padding` through the dispatch, which was previously dropped).
- `q8ta` im2col scratch: flattened-window `K` from channels/kernel/groups, `H_out`/`W_out` from the current input.
- `q8ta_linear`: `[*input.shape[:-1], out_features]`.
- `q8ta` binary: `broadcast(in_a, in_b)`.
- `q8ta` quantize / dequantize: elementwise, output shape == input shape.

The quantized conv/quant path now honors dynamic input shapes like the fp32 path.
ghstack-source-id: 394480015
@exported-using-ghexport

Differential Revision: [D108788845](https://our.internmc.facebook.com/intern/diff/D108788845/)
SS-JIA pushed a commit that referenced this pull request Jun 18, 2026
Pull Request resolved: #20312

The q8ta (quantized int8) op `DynamicDispatchNode`s were constructed with an empty resize-args list and no resize function, so their output tensors were never `virtual_resize`d on `trigger_resize()`. On a dynamic-shape graph this froze the q8ta outputs at the build-time upper-bound shape — the same failure mode the fp32 ops already avoid. Concretely, in a quantized Vulkan-delegated graph the terminal pointwise conv produces the graph output, so a smaller input (e.g. 238 rows fed into a graph allocated at the 241-row upper bound) left stale rows that propagate downstream, where GroupNorm's global per-group statistics smear them across the whole tensor.

Add resize functions across the q8ta op family, each matching that op's output-shape semantics (mirroring the corresponding fp32 op's resize):
- `q8ta_conv2d` / `q8ta_conv2d_dw`: output H/W recomputed from the input via `calc_out_sizes_hw`.
- `q8ta_conv2d_pw`: 1x1 conv preserves spatial dims (out H/W == in H/W).
- `q8ta_conv2d_transposed`: transposed output formula via `calc_out_sizes_hw(transposed=true)` (threads `output_padding` through the dispatch, which was previously dropped).
- `q8ta` im2col scratch: flattened-window `K` from channels/kernel/groups, `H_out`/`W_out` from the current input.
- `q8ta_linear`: `[*input.shape[:-1], out_features]`.
- `q8ta` binary: `broadcast(in_a, in_b)`.
- `q8ta` quantize / dequantize: elementwise, output shape == input shape.

The quantized conv/quant path now honors dynamic input shapes like the fp32 path.
ghstack-source-id: 394480015
@exported-using-ghexport

Differential Revision: [D108788845](https://our.internmc.facebook.com/intern/diff/D108788845/)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. meta-exported

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants