Skip to content

[Feat][Pool] Implement adaptive 2D pooling ops - #1809

Open
RMLYC wants to merge 18 commits into
tile-ai:mainfrom
RMLYC:feat/pool/adaptive-pool2d
Open

[Feat][Pool] Implement adaptive 2D pooling ops#1809
RMLYC wants to merge 18 commits into
tile-ai:mainfrom
RMLYC:feat/pool/adaptive-pool2d

Conversation

@RMLYC

@RMLYC RMLYC commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Closes #1799

Adds AdaptiveAvgPool2dFwdOp, AdaptiveMaxPool2dFwdOp and the return_indices=True variant AdaptiveMaxPool2dIndicesFwdOp in TileLang, matching PyTorch for CHW/NCHW, integer or two-element output_size, per-dimension None, non-divisible bins, expanded outputs, max ties and int64 indices. The three manifest entries move to implemented.

Rebased onto the main that moved input construction into workloads/, so the new code follows that shape: workloads/pool.py owns AdaptivePool2dWorkload and both stages compose it; the scalar-None normalisation is one property there rather than one per ref_program; the bin arithmetic and static bin extent, previously identical in three kernel bodies, live in pool/common.py.

Benchmark

H200 @ 1500 MHz, torch 2.10.0+cu129. Latency in ms, global 1x1 / SPP 6x6 / non-divisible 7x7 BF16.

op TileOPs PyTorch
AdaptiveAvg 0.0034 / 0.0058 / 0.0070 0.0077 / 0.0111 / 0.0096
AdaptiveMax 0.0033 / 0.0058 / 0.0069 0.0459 / 0.0147 / 0.0119
AdaptiveMax+indices 0.0137 / 0.0203 / 0.0173 0.0458 / 0.0148 / 0.0120

The indices variant is behind PyTorch on the two non-global shapes.

Validation

pytest tests/ops/test_pool.py benchmarks/ops/bench_pool.py 307 passed, adaptive latencies as above; validate_manifest.py --strict and per-op; the four structural gates; pre-commit run --all-files; ruff check .. The extracted bin extent was checked against the original expression over all 16384 axis pairs up to 128.

@RMLYC
RMLYC requested a review from a team July 30, 2026 02:29
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@github-actions github-actions Bot added the feature New feature or new operator label Jul 30, 2026
@RMLYC RMLYC added the all-ai-powered Produced entirely by automated contributors label Jul 30, 2026
@RMLYC
RMLYC marked this pull request as draft July 30, 2026 08:09
@RMLYC
RMLYC marked this pull request as ready for review July 31, 2026 06:15
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@RMLYC
RMLYC force-pushed the feat/pool/adaptive-pool2d branch from 67c1b2e to d8b083d Compare July 31, 2026 06:33
@RMLYC RMLYC assigned zhen8838 and superAngGao and unassigned zhen8838 and superAngGao Jul 31, 2026
@RMLYC
RMLYC requested review from superAngGao and zhen8838 July 31, 2026 08:38
@lcy-seso
lcy-seso force-pushed the feat/pool/adaptive-pool2d branch 3 times, most recently from 815f565 to bd419c4 Compare August 1, 2026 04:13
Rebased onto a main that moved input construction into workloads/. Git
reported no conflict because the adaptive classes are new, but two gates
did: the benchmark and the test each defined gen_inputs for the same NCHW
tensor, and the test spelled it gen_inputs(*shape), which is not the
WorkloadBase contract.

workloads/pool.py now owns AdaptivePool2dWorkload; both stages compose it
and keep only their own oracle. The scalar-None normalisation torch 2.10
requires was written out in both ref_programs and is now one property on
the workload.

The bin arithmetic was identical in three kernel bodies, comment included,
as was the static bin extent. Both move to pool/common.py. They are
evaluated while tracing, so the emitted TIR is unchanged -- 259 tests and
48 benchmarks pass with latencies matching the previous run.

Drops the Adaptive 2D pool protocol section from ops-design.md, leaving
that file untouched by this branch. The base class docstring already
carried the flow, the playbook is otherwise generic with no per-family
section, and both the rule it stated and the pointer to where family-base
process lives were already there: Family-Base Refactoring says a family
base must not normalize genuine per-op differences, and Out of Scope
already routes family protocol variables to the appendix.
@lcy-seso
lcy-seso force-pushed the feat/pool/adaptive-pool2d branch from bd419c4 to 20465ba Compare August 1, 2026 04:21
… int32

The three kernel classes were the same 61 lines three times over -- dtype
gate, config space, argument marshalling -- differing only in which builder
and which registered op they call. They now subclass one base and bind those
two callables. The three torch.library registrations stay separate: they need
distinct names and are what torch.compile resolves against.

With one copy left, the indices kernel's cost shows. It carried the flat
index as int64 through the reduction, so every recorded maximum dragged
64-bit div/mod into the update path. The plane is h_in * w_in, known while
tracing, so the internal state is int32 whenever that fits and only the
stored index is int64, which is what PyTorch returns.

  indices, tileops latency in ms   before   after
  global 1x1                       0.0138   0.0137
  SPP 6x6                          0.0204   0.0158
  non-divisible 7x7                0.0174   0.0164

Matching the prediction: the global case constant-folds the index expression
and has nothing to save, the other two do. 259 tests pass, so the indices
still agree with PyTorch element for element.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

all-ai-powered Produced entirely by automated contributors feature New feature or new operator

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT][POOL] add adaptive 2d pooling operators

4 participants