[Maintain][Manifest] Add adaptive 2D pooling ops - #1808
Conversation
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
superAngGao
left a comment
There was a problem hiding this comment.
Thanks for adding the adaptive 2D pooling specs. I tested this in the TileOpsGov runner image requested for CI parity:
ghcr.io/tile-ai/tileops-runner:65dbc98-torch2.10
with the source and cache mounts, without installing or upgrading tilelang, torch, or any dependencies. Full manifest validation passes:
python scripts/validate_manifest.py -> All manifest checks passed.
I think there are two manifest/spec issues to fix before landing:
- The adaptive pooling roofline FLOP expression undercounts non-divisible output grids.
The new entries use:
kH = (H_in + out_H - 1) // out_H
kW = (W_in + out_W - 1) // out_W
flops = N * C * out_H * out_W * kH * kW
For PyTorch adaptive pooling, each output bin is based on floor/ceil bin boundaries, so bin sizes vary and may overlap. For the PR's own nondiv-7x7 workload [2, 64, 55, 57] -> [7, 7], I get:
- manifest formula:
451584 - adaptive-bin scan count:
491904 - ratio:
0.918033
So the formula is about 8.2% low for this representative workload. This affects all three new ops: AdaptiveAvgPool2dFwdOp, AdaptiveMaxPool2dFwdOp, and AdaptiveMaxPool2dIndicesFwdOp.
Could we either switch this to an exact adaptive-bin formula/helper, or avoid pairing a non-divisible adaptive workload with this approximate roofline expression?
- The
output_sizeshape rules accept tuple/list forms that PyTorch rejects.
The current rules treat a one-element tuple/list like a square output, but PyTorch only does that for a bare int. In the same runner image:
adaptive_avg_pool2d(x, 6)-> OK, output[N, C, 6, 6]adaptive_avg_pool2d(x, [6])-> RuntimeError:output_size must be 2adaptive_avg_pool2d(x, (6,))-> RuntimeError:output_size must be 2adaptive_avg_pool2d(x, [None, 3])-> OK
So I think the manifest should require tuple/list output_size to have length 2, and reserve the square-output behavior for scalar int only.
One non-blocking note: targeted validate_manifest.py --check-op AdaptiveAvgPool2dFwdOp fails because these are status: spec-only entries and the corresponding Op classes do not exist yet. That matches the manifest design doc behavior for targeted L0-L4 checks, so I am not treating that as a blocker here.
zhen8838
left a comment
There was a problem hiding this comment.
Request changes on head ceed9f7.
- The three adaptive entries still describe only
[N, C, H_in, W_in], while the referenced PyTorch API also accepts unbatched[C, H_in, W_in]. The spec must either include the CHW form or explicitly narrow the supported API before promotion. output_sizeis incomplete and permissive: the parameter type omits scalarNone, and thelen(...) > 1fallback in the shape rules accepts a one-element tuple/list as a square output. Require the exact accepted forms and a two-element tuple/list, matching the runtime contract.- The roofline's
ceil(H_in/H_out)andceil(W_in/W_out)treat all adaptive bins as uniform. Adaptive bins vary and can overlap for non-divisible and expanded outputs, so the current FLOPs are not authoritative. Define the work from the actual bin boundaries before marking these entries implemented.
- roofline: exact adaptive-bin scan formula (bins may overlap on non-divisible grids; ceil approximation was ~8% low on nondiv-7x7) - shape_rules: require tuple/list output_size to have length 2, matching PyTorch (single-element tuple/list is a RuntimeError) - document batched-NCHW declaration and op-layer CHW support
|
@superAngGao Both points verified locally with the same numbers (451584 vs 491904, ratio 0.918). Addressed in 0972fde:
|
|
@zhen8838 Addressed in 0972fde:
|
Align with the torch 2.13 reference docs, which declare output_size: int | None | tuple[int | None, int | None] for both nn.AdaptiveAvgPool2d and nn.AdaptiveMaxPool2d. Scalar None resolves to (None, None), i.e. output equals the input spatial size.
|
@zhen8838 Follow-up on point 2 — you were right about the documented contract: the torch 2.13 reference pages declare One nuance worth recording: the torch 2.10 runtime still raises |
zhen8838
left a comment
There was a problem hiding this comment.
Re-reviewed current head 115007f.
The previous manifest blockers are resolved: output_size now covers scalar None and rejects non-2-element tuple/list forms, and the roofline uses exact adaptive-bin scan counts rather than a uniform ceil-divided window. The manifest remains correctly status: spec-only, with the NCHW pool-family convention and CHW Op-layer support explicitly documented. Manifest, compile-contract, benchmark-contract, pre-commit, actionlint, and gitleaks checks pass; GPU smoke is appropriately skipped for this spec-only diff.
superAngGao
left a comment
There was a problem hiding this comment.
Thanks for the updates. The adaptive-bin math looks correct now: in the official runner image, the new formula evaluates the PR's nondiv-7x7 workload to 491904, matching a direct per-bin enumeration, so the previous undercount is fixed.
I think there is still one blocking issue before this manifest can land:
The inline roofline expressions are not synthesizeable by the current TileOps roofline codegen. The new vars entries reference shape symbols such as N, C, H_in, and W_in directly, e.g. out_H: "H_in if oH is None else oH". However, inline roofline codegen only binds declared tensor inputs, params, elem_bytes, helpers, and earlier vars; it does not automatically materialize shape dimension names as Python locals. In the official runner image, this reproduces with:
from tileops.manifest import load_manifest
from tileops.ops._roofline_codegen import synthesize_eval_roofline
entry = load_manifest()["AdaptiveAvgPool2dFwdOp"]
synthesize_eval_roofline(
"AdaptiveAvgPool2dFwdOp",
roofline=entry["roofline"],
signature=entry["signature"],
)which raises:
ValueError: AdaptiveAvgPool2dFwdOp: roofline.vars['out_H'] references unknown name 'H_in'
This affects all three new adaptive pooling entries. Full manifest validation does not catch it because status: spec-only only gets L0 schema validation, and the roofline design doc explicitly says codegen is the authoritative gate for name/form correctness.
A minimal fix is to define the shape-derived locals explicitly in roofline.vars before the existing adaptive-bin variables:
N: "input.shape[0]"
C: "input.shape[1]"
H_in: "input.shape[2]"
W_in: "input.shape[3]"I tested that shape-binding approach locally in the same runner image with the existing formula body, and synthesize_eval_roofline() then succeeds and returns (491904, 815104) for the nondiv-7x7 avg-pool workload.
Non-blocking note: I also verified the scalar None behavior. The latest PyTorch docs describe scalar None, but the current runner's torch 2.10.0+cu129 still rejects F.adaptive_avg_pool2d(x, None) / F.adaptive_max_pool2d(x, None). I am not blocking on that here if the intended TileOps contract is to follow the newer documented semantics, but the follow-up correctness/reference path should account for that runtime mismatch.
…vars Inline roofline codegen only binds tensor inputs, params, elem_bytes, helpers, and earlier vars; shape dimension names are not materialized as locals when vars is present. Bind N/C/H_in/W_in from input.shape so synthesize_eval_roofline succeeds for all three adaptive entries (nondiv-7x7 evaluates to (491904, 815104) for the avg variant).
|
@superAngGao Confirmed and fixed in 62c1a8b. Reproduced the codegen failure locally with your snippet ( Fix: all three adaptive entries now bind the shape dims explicitly at the top of N: "input.shape[0]"
C: "input.shape[1]"
H_in: "input.shape[2]"
W_in: "input.shape[3]"Verification (tileops-dev, torch 2.10.0+cu129): On the scalar- |
superAngGao
left a comment
There was a problem hiding this comment.
Re-reviewed latest head 62c1a8b. The previous inline roofline codegen blocker is fixed: all three adaptive pool entries now bind N/C/H_in/W_in explicitly, synthesize_eval_roofline() succeeds, and the exact adaptive-bin scan matches direct enumeration for the declared workloads including nondiv-7x7. The spec-only manifest shape and bench_manifest_driven=false usage are consistent with the current repository conventions. Looks good to me.
Ibuki-wind
left a comment
There was a problem hiding this comment.
Overall
Approval is blocked by stale PR metadata and review-reply cleanup required by the repository approval gate.
Cross-cutting concerns
- Rewrite the PR body to record the current final state: it still says the roofline uses one ceil-divided bin size and must be replaced later, but head 62c1a8b already uses the exact adaptive-bin scan. Keep the body concise and add the required pre-commit and test-node-delta verification facts.
- Reduce the four author replies at issue comments 5126310781, 5126311041, 5127183876, and 5127907638 to outcome-only forms such as
Done in <sha>.orWon’t-fix: <one-line reason>.; the current root-cause and design restatements violate the approval-gate reply discipline.
Withdrawn so the PR metadata can be cleaned up directly.
Part of #1799
Summary
spec-onlymanifest entries for adaptive average pooling, adaptive max pooling, and the max-pooling indices variant.output_sizerules, representative workloads, and exact adaptive-bin roofline formulas.Test plan
python scripts/validate_manifest.py