forked from ccf-ai-infra/TileOPs-Metax
7 Commits
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
6e0b507b42
|
[Refactor][Workloads] Align workloads/ops/ naming with tileops/ops/ layout (#939)
## Summary
Align `workloads/ops/` file naming and directory structure 1:1 with the
post-#928 `tileops/ops/` layout. Pure file-move/rename refactor — no
workload logic changes.
- Drop `_fwd` suffix from mamba, attention, deltanet, and gated_deltanet
workloads
- Rename `mean_pooling_ops.py` to `mean_pooling.py`
- Move attention workloads into `workloads/ops/attention/` subpackage
- Update all imports across the repo
Closes #931
## Test plan
- [x] **AC-1**: All `from workloads.ops.<name>` imports updated for
moved/renamed files — verified by importing all 20 renamed/moved
workload classes and grep confirming zero old import paths remain
- [x] **AC-2**: `python -c "from workloads.ops.attention import ..."`
resolves for all 15 moved attention workload classes
- [x] **AC-3**: Full test suite passes — 2361 passed, 22 skipped, 0
failed (233.76s at commit
|
|
|
|
978615d1b3
|
[Refactor][Benchmark] Detach benchmarks from tests via workloads layer (#787)
## Summary Repo-wide refactor to fully detach `benchmarks/` from `tests/` by introducing a shared `workloads/` layer. Resolves #783. Case study documented in #789. ### Problem `benchmarks/` had 81 `tests.*` imports across 67 files, plus 42 `self.test.*` accesses. Benchmark code broke when test-only code changed — the wrong dependency direction. ### Solution Introduce `workloads/` as a neutral shared layer owning only workload parameters, `gen_inputs()`, and fixture metadata. Tests and benchmarks both import from `workloads/` but never from each other. ### Architecture ``` workloads/base.py → WorkloadBase (gen_inputs only), FixtureMeta, FixtureBase workloads/ops/*.py → 55 concrete workload classes (params + gen_inputs) tests/test_base.py → TestBase(WorkloadBase) + @abstractmethod ref_program + check() tests/ops/*.py → Test classes inherit (Workload, TestBase), define ref_program locally benchmarks/benchmark.py → BenchmarkBase(workload: WorkloadBase), self.workload benchmarks/ops/*.py → Import from workloads.ops/*, define baselines locally ``` ### Trust model - `workloads/` contains NO reference implementations, NO correctness logic, NO assertion/tolerance code - `ref_program()` is `@abstractmethod` on `TestBase`, defined independently in each test class - Benchmark baselines are independent local copies — no shared oracle surface between tests and benchmarks - `workloads/` ships in the wheel with lazy `pytest` import (no undeclared runtime dependency) ## Changes **189 files changed** (+5329 / -3732) | Category | Count | Description | |----------|-------|-------------| | `workloads/` (new) | 58 files | `WorkloadBase`, `FixtureMeta`, `FixtureBase`, 55 workload classes | | `benchmarks/` | 73 files | All imports migrated from `tests.*` → `workloads.*`; `self.test` → `self.workload`; local baseline copies | | `tests/` | 58 files | Workload params extracted to `workloads/`; `ref_program` kept local; `TestBase` enforces abstract contract | | `pyproject.toml` | 1 file | `workloads` included in wheel package discovery | **Zero changes** to `tileops/ops/`, `tileops/kernels/`, or `tileops/ops_manifest.yaml`. ## Acceptance criteria - [x] `rg -n "from tests\.|import tests\." benchmarks` → no matches - [x] `rg -n "self\.test\." benchmarks` → no matches - [x] `benchmarks/benchmark.py` does not import or reference `TestBase` - [x] `BenchmarkBase` stores `self.workload`, not `self.test` - [x] `workloads/base.py` exports `WorkloadBase`, `FixtureMeta`, `FixtureBase` - [x] `tests/test_base.py` no longer defines `FixtureMeta` or `FixtureBase` - [x] `workloads/` contains no correctness-only methods or assertion/tolerance logic - [x] No shared oracle surface — reference functions duplicated independently - [x] Op implementation files identical to upstream/main (pure refactor) - [x] Manifest unchanged from upstream/main - [x] Test coverage preserved (fp32/fp16/bf16, 1D-4D, all original params) - [x] Representative benchmark smoke runs produce numeric output - [x] Representative correctness smoke tests pass ## Test plan - [x] Structural audit: zero `tests.*` imports in `benchmarks/` - [x] Structural audit: zero `self.test` in `benchmarks/` - [x] `pytest --collect-only benchmarks` — 1004 tests collected, 0 import errors - [x] `pytest tests/ops/test_softmax.py -m smoke` — 15 passed - [x] `pytest tests/ops/test_activation.py -m smoke` — 20 passed - [x] `pytest benchmarks/ops/bench_softmax.py -m smoke` — passed with numeric output - [x] `pytest benchmarks/ops/bench_activation.py -m smoke` — passed with numeric output - [x] Wheel build: `workloads` importable without pytest installed 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
|
|
|
81c0eafb99
|
[Perf][Elementwise] Optimize binary max/min kernels to close bandwidth gap with PyTorch (#539)
## Summary
- Add `register_copy` strategy to `BinaryKernel` with vectorized 128-bit
fragment loads (`uint4`) for same-shape inputs, achieving 2-3x bandwidth
improvement on complex op_funcs (maximum/minimum)
- Simplify `maximum`/`minimum` `op_func` from 5-node comparison chain
with copysign to `T.max`/`T.min` + `isnan` guards, reducing IR node
count
- Add `autotune_configs` to `BinaryKernel` with 9 configurations
(threads in {128, 256, 512} x num_per_thread in {2, 4, 8}) for
fp16/bf16/fp32 and 6 for fp8
- Cache compiled kernel to eliminate JIT lookup overhead on each
`forward()` call
Closes #512
## Benchmark
### Environment
- **GPU**: NVIDIA H200
- **PyTorch**: 2.9.1+cu128
- **CUDA**: 12.8
- **dtype**: float16
- **Measurement**: 1000 iterations, CUDA event timing, 50-iteration
warmup
### Before (main branch)
| op | shape | TileOPs (TB/s) | PyTorch (TB/s) | ratio |
|---|---|---|---|---|
| maximum | (1024, 4096) | 1.18 | 1.83 | 65% |
| maximum | (1024, 10240) | 1.32 | 2.79 | 47% |
| maximum | (1024, 20480) | 1.42 | 3.37 | 42% |
| minimum | (1024, 4096) | 1.18 | 1.83 | 65% |
| minimum | (1024, 10240) | 1.31 | 2.76 | 48% |
| minimum | (1024, 20480) | 1.42 | 3.32 | 43% |
### After (this PR, independent H200 re-measurement by Reviewer)
| op | shape | TileOPs (TB/s) | PyTorch (TB/s) | ratio |
|---|---|---|---|---|
| maximum | (1024, 4096) | 2.67 | 1.83 | **146%** |
| maximum | (1024, 10240) | 3.36 | 2.77 | **121%** |
| maximum | (1024, 20480) | 3.79 | 3.35 | **113%** |
| minimum | (1024, 4096) | 2.66 | 1.90 | **140%** |
| minimum | (1024, 10240) | 3.36 | 2.72 | **123%** |
| minimum | (1024, 20480) | 3.79 | 3.35 | **113%** |
All target shapes exceed the 80% acceptance threshold, with large shapes
exceeding PyTorch bandwidth.
### Kernel-level (bypassing Op layer Python overhead)
| op | shape | TileOPs (TB/s) | PyTorch (TB/s) | ratio |
|---|---|---|---|---|
| maximum | (1024, 4096) | 2.64 | 4.10 | 64% |
| maximum | (1024, 10240) | 3.55 | 3.52 | **101%** |
| maximum | (1024, 20480) | 3.84 | 3.82 | **101%** |
## Test plan
- [x] 123 unit tests pass (`tests/ops/test_binary_arith.py` +
`tests/test_elementwise_strategy_bench.py`)
- [x] NaN propagation tests pass: `test_maximum_nan_propagation`,
`test_minimum_nan_propagation` (16 tests)
- [x] Signed-zero semantics tests pass: `test_maximum_signed_zero`,
`test_minimum_signed_zero` (16 tests)
- [x] Autotune config validation:
`test_binary_kernel_has_autotune_configs`,
`test_binary_kernel_autotune_configs_distinct`
- [x] 6 benchmark runs pass on H200
## Acceptance Criteria
| AC | Description | Status |
|---|---|---|
| AC-1 | Modified files pass unit tests | Pass (123/123) |
| AC-2 | maximum/minimum >= 80% PyTorch BW on target shapes | Pass
(113-146%) |
| AC-3 | Before/after benchmark comparison in PR | Pass (see above) |
| AC-4 | No regression in signed-zero/NaN tests | Pass (16/16) |
| AC-5 | autotune_configs with >= 3 configs | Pass (9 configs for
fp16/bf16/fp32) |
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
|
|
|
d9b83d0456
|
[Fix][Elementwise] Correct maximum/minimum signed-zero tie-break (#508)
Closes #469 ## Summary Fix `maximum` / `minimum` elementwise ops to match PyTorch / IEEE 754 signed-zero semantics: - `maximum(+0.0, -0.0)` now correctly returns `+0.0` (was returning `-0.0`) - `minimum(-0.0, +0.0)` now correctly returns `-0.0` (was returning `+0.0`) The fix adds a tie-break path using `copysign` comparison that activates only when `a == b`, preserving the existing NaN-propagation behavior from PR #463. **Dtype support matrix:** | dtype | supported | |-------|-----------| | float16 | yes | | bfloat16 | yes | | float32 | yes | ## Structural Compliance All checks passed. ## Test plan - [x] `maximum(+0.0, -0.0)` returns `+0.0` for fp16, bf16, fp32 - [x] `minimum(-0.0, +0.0)` returns `-0.0` for fp16, bf16, fp32 - [x] NaN propagation behavior from PR #463 is preserved (not regressed) - [x] 12 new signed-zero regression tests added and passing - [x] No performance regression — tie-break path only activates on `a == b` ## Benchmark **Environment**: H200 GPU, CUDA 12.8, PyTorch 2.9.1+cu128, TileLang 0.1.8 Before/after comparison (`main` vs this branch, fp16): | Op | Shape | Before Latency (ms) | Before BW (TB/s) | After Latency (ms) | After BW (TB/s) | Delta | |----|-------|---------------------|-------------------|---------------------|-------------------|-------| | maximum | (1024, 4096) | 0.02 | 1.18 | 0.02 | 1.18 | 0% | | maximum | (1024, 10240) | 0.05 | 1.33 | 0.05 | 1.32 | -0.8% | | maximum | (1024, 20480) | 0.09 | 1.43 | 0.09 | 1.42 | -0.7% | | minimum | (1024, 4096) | 0.02 | 1.19 | 0.02 | 1.18 | -0.8% | | minimum | (1024, 10240) | 0.05 | 1.32 | 0.05 | 1.31 | -0.8% | | minimum | (1024, 20480) | 0.09 | 1.43 | 0.09 | 1.42 | -0.7% | **Takeaways:** - No measurable regression — deltas are within noise margin (<1%) - The `copysign`-based tie-break only activates when `a == b`, which is rare in real workloads - Existing gap vs PyTorch baseline (42–65% efficiency) is a pre-existing issue tracked in #512 **Benchmark command:** ```bash PYTHONPATH="$PWD" python -m pytest benchmarks/ops/bench_binary_elementwise.py -v -k "maximum or minimum" ``` ## Regression - 101 tests pass (12 signed-zero + 10 NaN propagation + existing suite) - Lint clean (`pre-commit run --all-files` passes) - Signed-zero fix does not alter NaN-propagation codepath — verified by `test_maximum_signed_zero_with_nan` and `test_minimum_signed_zero_with_nan` --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
|
|
|
783fbe5f7c
|
[Feat][Elementwise] Register elementwise ops for torch.compile (#482)
## Summary Register all elementwise ops for `torch.compile` via `@torch.library.custom_op` + `register_fake`. Three factory functions handle 55 template ops; independent ops use hand-written registration. After this change, `Kernel.forward` switches from `self.kernel(x)` to `type(self)._wrapped(...)`. Closes #440 ## Changes - **`tileops/ops/elementwise.py`** — Add `_register_unary_custom_op`, `_register_binary_custom_op`, `_register_fused_gated_custom_op` factory functions; `_wrapped` binding on all kernel classes at module load time; forward path switch to use `_wrapped`; `_OP_REGISTRY` as `WeakValueDictionary` to avoid leaks - **`tileops/kernels/elementwise.py`** — Fix `RemainderKernel` and `FloorDivideKernel` fp16 precision: promote division to fp32 before `T.floor` to avoid rounding near integer quotients - **`tests/test_elementwise_compile.py`** — 60 torch.compile correctness tests covering every registered op (unary, binary, comparison, logical, bitwise, fused gated) - **`benchmarks/ops/bench_compile_elementwise.py`** — New benchmark: eager vs torch.compile latency and bandwidth ## Test plan - [x] **AC-1**: All 55 elementwise ops work under `torch.compile` — full-matrix smoke with `fullgraph=True`; 60 pytest cases passed - [x] **AC-2**: `register_fake` returns correct shape and dtype (including `torch.bool` for comparison) — verified via dtype/shape assertions - [x] **AC-3**: Eager mode L1 tests still pass — 196 passed across binary_arith, unary_math, fused_gated, special_elementwise, bitwise suites - [x] **AC-4**: Import time measured and acceptable — average 160.60ms (max 162.43ms), well below 500ms constraint ## Benchmark Environment: Torch 2.9.1+cu128, CUDA 12.8, NVIDIA H200, Driver 575.57.08 ### Unary Ops (fp16, latency us + bandwidth TB/s) | Op | N | Eager (us) | Compile (us) | Overhead | Eager BW (TB/s) | Compile BW (TB/s) | |---|---|---|---|---|---|---| | relu | 262,144 | 2.3 | 2.1 | 0.92x | 0.47 | 0.51 | | relu | 1,048,576 | 3.1 | 3.0 | 0.95x | 1.34 | 1.41 | | relu | 4,000,000 | 6.4 | 6.2 | 0.97x | 2.50 | 2.58 | | exp | 262,144 | 2.3 | 2.1 | 0.92x | 0.46 | 0.50 | | exp | 1,048,576 | 3.2 | 3.0 | 0.95x | 1.33 | 1.40 | | exp | 4,000,000 | 6.4 | 6.2 | 0.97x | 2.48 | 2.57 | | gelu | 262,144 | 2.4 | 2.2 | 0.93x | 0.44 | 0.47 | | gelu | 1,048,576 | 3.5 | 3.4 | 0.96x | 1.20 | 1.25 | | gelu | 4,000,000 | 8.1 | 8.0 | 0.99x | 1.99 | 2.01 | | silu | 262,144 | 2.4 | 2.2 | 0.92x | 0.44 | 0.48 | | silu | 1,048,576 | 3.4 | 3.2 | 0.95x | 1.23 | 1.29 | | silu | 4,000,000 | 7.9 | 7.7 | 0.97x | 2.01 | 2.07 | | abs | 262,144 | 2.2 | 2.1 | 0.92x | 0.47 | 0.51 | | abs | 1,048,576 | 3.1 | 3.0 | 0.94x | 1.33 | 1.41 | | abs | 4,000,000 | 6.4 | 6.2 | 0.97x | 2.50 | 2.57 | | sqrt | 262,144 | 2.4 | 2.3 | 0.94x | 0.43 | 0.46 | | sqrt | 1,048,576 | 3.2 | 3.0 | 0.95x | 1.31 | 1.38 | | sqrt | 4,000,000 | 6.5 | 6.2 | 0.97x | 2.48 | 2.56 | | log | 262,144 | 2.4 | 2.2 | 0.92x | 0.45 | 0.48 | | log | 1,048,576 | 3.4 | 3.2 | 0.94x | 1.24 | 1.31 | | log | 4,000,000 | 7.9 | 7.7 | 0.98x | 2.03 | 2.06 | | tanh | 262,144 | 2.3 | 2.1 | 0.92x | 0.45 | 0.49 | | tanh | 1,048,576 | 3.2 | 3.0 | 0.95x | 1.32 | 1.40 | | tanh | 4,000,000 | 6.5 | 6.3 | 0.97x | 2.44 | 2.53 | ### Binary Ops (fp16, latency us + bandwidth TB/s) | Op | Shape (MxN) | Eager (us) | Compile (us) | Overhead | Eager BW (TB/s) | Compile BW (TB/s) | |---|---|---|---|---|---|---| | add | 1024x4096 | 8.9 | 8.7 | 0.98x | 2.84 | 2.90 | | add | 1024x10240 | 18.4 | 18.2 | 0.99x | 3.41 | 3.46 | | add | 1024x20480 | 33.1 | 32.9 | 0.99x | 3.80 | 3.82 | | mul | 1024x4096 | 8.9 | 8.6 | 0.98x | 2.84 | 2.91 | | mul | 1024x10240 | 18.4 | 18.2 | 0.99x | 3.42 | 3.46 | | mul | 1024x20480 | 33.1 | 32.9 | 0.99x | 3.80 | 3.83 | | sub | 1024x4096 | 8.8 | 8.7 | 0.98x | 2.84 | 2.91 | | sub | 1024x10240 | 18.4 | 18.2 | 0.99x | 3.42 | 3.46 | | sub | 1024x20480 | 33.1 | 32.9 | 0.99x | 3.80 | 3.83 | | div | 1024x4096 | 9.1 | 8.8 | 0.98x | 2.77 | 2.84 | | div | 1024x10240 | 18.2 | 18.0 | 0.99x | 3.45 | 3.49 | | div | 1024x20480 | 33.2 | 32.8 | 0.99x | 3.80 | 3.83 | | remainder | 1024x4096 | 9.5 | 9.3 | 0.98x | 2.65 | 2.71 | | remainder | 1024x10240 | 19.3 | 19.0 | 0.99x | 3.27 | 3.31 | | remainder | 1024x20480 | 34.6 | 34.3 | 0.99x | 3.64 | 3.67 | | floor_divide | 1024x4096 | 9.5 | 9.3 | 0.98x | 2.65 | 2.71 | | floor_divide | 1024x10240 | 19.2 | 19.0 | 0.99x | 3.28 | 3.31 | | floor_divide | 1024x20480 | 34.6 | 34.2 | 0.99x | 3.64 | 3.68 | | pow | 1024x4096 | 16.2 | 16.1 | 0.99x | 1.55 | 1.56 | | pow | 1024x10240 | 35.4 | 35.4 | 1.00x | 1.78 | 1.78 | | pow | 1024x20480 | 67.2 | 67.2 | 1.00x | 1.87 | 1.87 | | maximum | 1024x4096 | 20.0 | 19.7 | 0.98x | 1.26 | 1.28 | | maximum | 1024x10240 | 44.7 | 44.2 | 0.99x | 1.41 | 1.42 | | maximum | 1024x20480 | 81.8 | 81.3 | 0.99x | 1.54 | 1.55 | | eq | 1024x4096 | 19.0 | 18.8 | 0.99x | 1.10 | 1.11 | | eq | 1024x10240 | 40.7 | 40.4 | 0.99x | 1.29 | 1.30 | | eq | 1024x20480 | 85.2 | 84.9 | 1.00x | 1.23 | 1.24 | ### Fused Gated Ops (fp16, latency us + bandwidth TB/s) | Op | Shape (MxN) | Eager (us) | Compile (us) | Overhead | Eager BW (TB/s) | Compile BW (TB/s) | |---|---|---|---|---|---|---| | silu_and_mul | 1024x4096 | 9.8 | 9.5 | 0.97x | 2.57 | 2.65 | | silu_and_mul | 1024x10240 | 20.8 | 20.5 | 0.99x | 3.03 | 3.06 | | silu_and_mul | 1024x20480 | 37.6 | 37.2 | 0.99x | 3.35 | 3.38 | | gelu_and_mul | 1024x4096 | 10.6 | 10.3 | 0.97x | 2.38 | 2.45 | | gelu_and_mul | 1024x10240 | 22.2 | 22.0 | 0.99x | 2.83 | 2.86 | | gelu_and_mul | 1024x20480 | 39.4 | 39.2 | 1.00x | 3.20 | 3.21 | | gelu_tanh_and_mul | 1024x4096 | 9.1 | 8.8 | 0.97x | 2.77 | 2.85 | | gelu_tanh_and_mul | 1024x10240 | 18.1 | 17.9 | 0.99x | 3.48 | 3.52 | | gelu_tanh_and_mul | 1024x20480 | 32.9 | 32.7 | 0.99x | 3.82 | 3.84 | ### Takeaways - **Zero overhead**: `torch.compile` adds no runtime cost — compiled paths are consistently 1-8% **faster** than eager due to eliminated Python dispatch overhead - **Unary ops**: 3-8% faster under compile at small sizes (262K elements), converging to ~1-3% at large sizes (4M) - **Binary/fused ops**: 1-2% faster under compile across all DNN-realistic shapes - **Compute-bound ops** (pow, maximum, eq): negligible difference as expected — kernel time dominates 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
|
|
|
d820ab34f3
|
[Feat][Elementwise] Add BinaryKernel + FusedGatedKernel template ops (22 ops) (#463)
## Summary Implements 22 elementwise operator subclasses using the template base classes from #436: - **20 BinaryKernel ops**: arithmetic (Sub, Mul, Div, Remainder, FloorDivide, Pow, Maximum, Minimum, Lerp), comparison (Eq, Ne, Gt, Lt, Ge, Le), logical (LogicalAnd, LogicalOr), bitwise (BitwiseAnd, BitwiseOr, BitwiseXor) - **2 FusedGatedKernel ops**: GeluAndMul, GeluTanhAndMul - NaN propagation fix for MaximumKernel/MinimumKernel (round 1) Closes #438 ## Dtype Support Matrix | Op Family | Supported input dtypes | Output dtype | Semantics | |---|---|---|---| | `sub`, `mul` | validated in this PR on `float16`, `bfloat16`, `float32` | same as input | PyTorch-style elementwise arithmetic with broadcast | | `div`, `remainder`, `pow`, `floor_divide`, `lerp`, `maximum`, `minimum` | `float16`, `bfloat16`, `float32` | same as input | PyTorch-aligned float arithmetic; unsupported integer dtypes are rejected at construction time | | `eq`, `ne`, `gt`, `lt`, `ge`, `le` | `float16`, `bfloat16`, `float32` | `torch.bool` | Kernel emits int8 1/0 flags, Op layer casts to bool | | `logical_and`, `logical_or` | `bool`, `uint8`, `int8`, `int16`, `int32`, `int64`, `float16`, `bfloat16`, `float32` | `torch.bool` | Non-zero truthiness, matching `torch.logical_*` semantics | | `bitwise_and`, `bitwise_or`, `bitwise_xor` | `uint8`, `int8`, `int16`, `int32`, `int64` | same as input | PyTorch-style bitwise semantics; float dtypes rejected | | `gelu_and_mul`, `gelu_tanh_and_mul` | `float16`, `bfloat16`, `float32` | same as input | Fused gated activation on `(M, 2N) -> (M, N)` | ## Test plan - [x] **AC1**: 20 BinaryKernel + 2 FusedGatedKernel subclasses implemented - [x] **AC2**: Comparison ops output torch.bool - [x] **AC3**: `__all__` updated in both elementwise.py files - [x] **AC4**: L1: 22 ops pass (fp16, same-shape) - [x] **AC5**: L3: 60 broadcast tests pass (bias-add, row, scalar patterns x 20 ops) - [x] **AC6**: L4: 6 edge cases pass (fp32, 4K) - [x] **AC7**: dtype rejection/runtime mismatch paths covered for the float-only binary and fused-gated contracts - [x] **AC8**: `__init__.py` not modified **Test results**: 196/196 passed, 0 failed ## Benchmark Environment: Torch 2.9.1+cu128, CUDA 12.8, NVIDIA H200, Driver 575.57.08 Shapes: (tokens=1024, hidden_dim) - DNN-realistic hidden dimensions ### Binary Arithmetic (fp16, bandwidth TB/s) | Op | Shape (MxN) | TileOPs | PyTorch | Speedup | |---|---|---|---|---| | sub | 1024x4096 | 1.97 | 1.84 | 1.07x | | sub | 1024x10240 | 2.73 | 2.77 | 0.99x | | sub | 1024x20480 | 3.37 | 3.37 | 1.00x | | mul | 1024x4096 | 1.87 | 1.83 | 1.02x | | mul | 1024x10240 | 2.82 | 2.83 | 0.99x | | mul | 1024x20480 | 3.40 | 3.40 | 1.00x | | div | 1024x4096 | 1.82 | 1.87 | 0.98x | | div | 1024x10240 | 2.72 | 2.59 | 1.05x | | div | 1024x20480 | 3.36 | 3.31 | 1.02x | | remainder | 1024x4096 | 1.81 | 1.61 | 1.13x | | remainder | 1024x10240 | 2.73 | 2.40 | 1.14x | | remainder | 1024x20480 | 3.35 | 2.82 | 1.19x | | pow | 1024x4096 | 1.05 | 1.02 | 1.02x | | pow | 1024x10240 | 1.29 | 1.26 | 1.02x | | pow | 1024x20480 | 1.42 | 1.39 | 1.02x | | floor_divide | 1024x4096 | 1.92 | 0.82 | 2.33x | | floor_divide | 1024x10240 | 2.74 | 0.98 | 2.79x | | floor_divide | 1024x20480 | 3.41 | 1.05 | 3.25x | | lerp | 1024x4096 | 1.86 | 1.85 | 1.00x | | lerp | 1024x10240 | 2.73 | 2.81 | 0.97x | | lerp | 1024x20480 | 3.36 | 3.42 | 0.98x | | maximum | 1024x4096 | 1.01 | 1.82 | 0.56x | | maximum | 1024x10240 | 1.22 | 2.71 | 0.45x | | maximum | 1024x20480 | 1.40 | 3.38 | 0.41x | | minimum | 1024x4096 | 1.02 | 1.89 | 0.54x | | minimum | 1024x10240 | 1.24 | 2.70 | 0.46x | | minimum | 1024x20480 | 1.38 | 3.34 | 0.41x | ### Comparison (fp16, bandwidth TB/s) | Op | Shape (MxN) | TileOPs | PyTorch | Speedup | |---|---|---|---|---| | eq | 1024x4096 | 0.78 | 1.72 | 0.45x | | eq | 1024x10240 | 1.02 | 2.49 | 0.41x | | eq | 1024x20480 | 1.05 | 3.17 | 0.33x | | ne | 1024x4096 | 0.79 | 1.65 | 0.48x | | ne | 1024x10240 | 1.02 | 2.56 | 0.40x | | gt | 1024x4096 | 0.78 | 1.72 | 0.45x | | gt | 1024x10240 | 1.02 | 2.48 | 0.41x | | lt | 1024x4096 | 0.79 | 1.72 | 0.46x | | lt | 1024x10240 | 1.02 | 2.55 | 0.40x | | ge | 1024x4096 | 0.78 | 1.72 | 0.45x | | ge | 1024x10240 | 1.02 | 2.56 | 0.40x | | le | 1024x4096 | 0.79 | 1.64 | 0.48x | | le | 1024x10240 | 1.02 | 2.55 | 0.40x | ### Logical (fp16 input, bandwidth TB/s) | Op | Shape (MxN) | TileOPs | PyTorch | Speedup | |---|---|---|---|---| | logical_and | 1024x4096 | 0.78 | 2.17 | 0.36x | | logical_and | 1024x10240 | 1.02 | 3.37 | 0.30x | | logical_or | 1024x4096 | 0.85 | 2.05 | 0.41x | | logical_or | 1024x10240 | 1.15 | 3.41 | 0.34x | ### Bitwise (int32, bandwidth TB/s) | Op | Shape (MxN) | TileOPs | PyTorch | Speedup | |---|---|---|---|---| | bitwise_and | 1024x4096 | 2.59 | 2.55 | 1.01x | | bitwise_and | 1024x10240 | 3.31 | 3.42 | 0.97x | | bitwise_or | 1024x4096 | 2.51 | 2.54 | 0.99x | | bitwise_or | 1024x10240 | 3.30 | 3.41 | 0.97x | | bitwise_xor | 1024x4096 | 2.51 | 2.61 | 0.96x | | bitwise_xor | 1024x10240 | 3.37 | 3.36 | 1.00x | ### Fused Gated (fp16, bandwidth TB/s) | Op | Shape (MxN) | TileOPs | PyTorch | Speedup | |---|---|---|---|---| | gelu_and_mul | 1024x4096 | 1.61 | 0.63 | 2.53x | | gelu_and_mul | 1024x10240 | 2.27 | 0.77 | 2.96x | | gelu_and_mul | 1024x20480 | 2.75 | 0.84 | 3.29x | | gelu_tanh_and_mul | 1024x4096 | 1.87 | 0.66 | 2.82x | | gelu_tanh_and_mul | 1024x10240 | 2.76 | 0.81 | 3.41x | | gelu_tanh_and_mul | 1024x20480 | 3.36 | 0.89 | 3.79x | ### Broadcast: Bias-Add Pattern (fp16, bandwidth TB/s) | Op | a_shape | b_shape | TileOPs | PyTorch | Speedup | |---|---|---|---|---|---| | sub | 1024x4096 | 1x4096 | 1.53 | 0.87 | 1.75x | | sub | 1024x10240 | 1x10240 | 2.53 | 1.14 | 2.22x | | sub | 1024x20480 | 1x20480 | 3.11 | 1.27 | 2.45x | | mul | 1024x4096 | 1x4096 | 1.54 | 0.88 | 1.75x | | mul | 1024x10240 | 1x10240 | 2.42 | 1.12 | 2.16x | | mul | 1024x20480 | 1x20480 | 3.18 | 1.28 | 2.47x | | div | 1024x4096 | 1x4096 | 1.47 | 0.82 | 1.80x | | div | 1024x10240 | 1x10240 | 2.37 | 1.06 | 2.24x | | div | 1024x20480 | 1x20480 | 2.82 | 1.16 | 2.43x | ### Takeaways - **Broadcast (bias-add)**: 1.75-2.47x faster (stride-based codegen beats PyTorch broadcast) - **Fused gated**: 2.5-3.8x faster (kernel fusion eliminates intermediate writes) - **floor_divide**: 2.3-3.3x faster (TileLang codegen advantage) - **remainder**: 1.1-1.2x faster - **Binary arith** (sub/mul/div/lerp/pow): ~parity with PyTorch at DNN-realistic sizes - **Bitwise**: ~parity (0.96-1.01x) - **Comparison**: ~0.3-0.5x slower (int8 intermediate + bool cast overhead; optimization opportunity) - **Logical**: ~0.3-0.4x slower (int8 intermediate + bool cast; optimization opportunity) - **Maximum/minimum**: ~0.4-0.6x slower (T.isnan NaN propagation overhead; optimization opportunity) ## Changes | File | Description | |---|---| | `tileops/kernels/elementwise.py` | 20 BinaryKernel + 2 FusedGatedKernel subclasses | | `tileops/ops/elementwise.py` | 22 matching Op subclasses | | `tests/ops/test_binary_arith.py` | Arithmetic smoke + broadcast + edge case tests | | `tests/ops/test_comparison.py` | Comparison smoke + broadcast + bool output tests | | `tests/ops/test_logical.py` | Logical op smoke + broadcast tests | | `tests/ops/test_bitwise.py` | Bitwise op smoke + broadcast tests | | `tests/ops/test_fused_gated.py` | Fused gated op tests | | `benchmarks/ops/bench_binary_elementwise.py` | Benchmark: same-shape + broadcast (DNN-realistic 2D shapes) | ## Follow-up Issue The next issue that should be taken immediately after this PR is: - #440: add `torch.compile` / fake-tensor support for the shared elementwise template via `custom_op` registration --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
|
|
|
ec595f03ca
|
[Feat][Elementwise] Add template infrastructure: base classes, smoke tests, and strategy benchmark (#453)
## Summary Build the complete template infrastructure for 66 elementwise ops: - **3 kernel base classes** — `UnaryKernel`, `BinaryKernel`, `FusedGatedKernel` with strategy support (`direct`, `explicit_parallel`, `register_copy`) - **3 op base classes** — `UnaryOp`, `BinaryOp`, `FusedGatedOp` with `coalesce_broadcast_dims` utility for stride-based broadcast - **3 concrete example ops** — `relu`, `add`, `silu_and_mul` (one per template family) - **Strategy benchmark** — determines `DEFAULT_STRATEGY` per kernel type with bandwidth rationale Closes #436 ## Test plan - [x] AC1: UnaryKernel, BinaryKernel, FusedGatedKernel implemented with all strategies - [x] AC2: `coalesce_broadcast_dims` handles all 6+ broadcast patterns correctly (6 unit tests) - [x] AC3: relu, add, silu_and_mul pass L1 correctness tests (14 configs total) - [x] AC4: 4 broadcast patterns pass L3 tests (bias-add, row, scalar, interleaved) - [x] AC5: Strategy benchmark run; DEFAULT_STRATEGY updated with rationale - [x] AC6: `__init__.py` re-exports base classes only (not concrete ops) **29 tests pass, 14 benchmarks pass.** ## Benchmark ### Unary (relu) Strategy | strategy | bandwidth (TB/s) | |---|---| | register_copy | 2.40 | | direct | 1.04 | | explicit_parallel | 0.86 | ### Binary (add) Strategy | strategy | bandwidth (TB/s) | |---|---| | explicit_parallel | 2.62 | | direct | 1.42 | **Decision**: Unary `DEFAULT_STRATEGY = register_copy`, Binary `DEFAULT_STRATEGY = explicit_parallel`. ## Files changed (9 files, +1177 lines) | File | Description | |---|---| | `tileops/kernels/elementwise.py` | Kernel base classes + strategy factory | | `tileops/ops/elementwise.py` | Op base classes + coalesce_broadcast_dims | | `tileops/kernels/__init__.py` | Re-export base kernel classes | | `tileops/ops/__init__.py` | Re-export base op classes | | `tests/ops/test_activation.py` | Unary op tests (relu) | | `tests/ops/test_binary_arith.py` | Binary op + broadcast tests (add) | | `tests/ops/test_fused_gated.py` | Fused gated op tests (silu_and_mul) | | `benchmarks/ops/bench_activation.py` | Unary strategy benchmark | | `benchmarks/ops/bench_binary_arith.py` | Binary strategy benchmark | --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |