Skip to content

[REFACTOR][OPS] split tileops/ops/elementwise.py into per-op modules (W3-03) #1378

@lcy-seso

Description

@lcy-seso

Description

Symptom / Motivation

tileops/ops/elementwise.py is a 2681-line monolith hosting:

  • 3 umbrella classes (UnaryOp, BinaryOp, FusedGatedOp at lines 668 / 769 / 866)
  • ~60 per-op *FwdOp classes (24 unary_math + 16 unary_activation + 24 binary + multi-input)

The reduction and normalization families already live as directories (tileops/ops/reduction/, tileops/ops/norm/) with one module per op. Elementwise is the only family where the W2 alignment landed without splitting the host file.

Drift cost: any per-op edit forces every reviewer of elementwise.py to scroll past hundreds of unrelated classes. New scaffolds keep landing in the monolith because that's where their siblings are.

Downstream importers reference per-op classes by their flat path:

  • tileops/kernels/__init__.py:39 imports BinaryKernel, FusedGatedKernel, UnaryKernel from tileops/kernels/elementwise.py (mirror split needed)
  • tileops/ops/moe/experts/nopad.py:10 imports SiluAndMulFwdOp from tileops.ops.elementwise
  • tests/ops/test_elementwise_fp8.py imports several *FwdOp classes from tileops.ops.elementwise

These imports MUST keep working: re-export from the new package's __init__.py so the public path tileops.ops.elementwise.<OpName> is preserved.

Root Cause Analysis

N/A — historical: ops were originally siblings under one umbrella; split deferred until W2 stabilized class shapes.

Related Files

  • tileops/ops/elementwise.py (split source)
  • tileops/ops/__init__.py (re-exports)
  • tileops/kernels/elementwise.py + tileops/kernels/__init__.py (mirror, if W2 hasn't already split kernel side)
  • tileops/ops/moe/experts/nopad.py, tests/ops/test_elementwise_fp8.py — verify imports still resolve
  • .claude/rules/code-style.md__init__.py MUST have explicit __all__ and from .module import Symbol re-exports

Goal

tileops/ops/elementwise.py is replaced by a tileops/ops/elementwise/ package with one module per op (or per tight cluster — e.g. all clamp variants in one file is acceptable). The package __init__.py re-exports every previously-public symbol; downstream imports of tileops.ops.elementwise.<OpName> continue to work without code changes elsewhere.

Plan

  1. Create tileops/ops/elementwise/ directory with __init__.py.
  2. Move each *FwdOp class to its own module (file names per .claude/rules/code-style.md: lowercase + underscores, multi-letter abbreviations stay lowercase). Group tightly-coupled variants (e.g. clamp.py for the four Clamp ops) when they share helpers.
  3. Move the umbrella classes UnaryOp, BinaryOp, FusedGatedOp to _base.py inside the package.
  4. __init__.py re-exports every public name with explicit __all__ and from .module import Symbol form (no star imports).
  5. Delete the old tileops/ops/elementwise.py.
  6. Run grep -rn "from tileops.ops.elementwise import\|from .elementwise import" across tileops/ tests/ benchmarks/ and confirm every match still resolves.
  7. Run pytest tests/ops/test_elementwise* and the elementwise bench smoke gate.

Constraints

  • MUST NOT change op runtime behavior. Pure file-layout move.
  • MUST NOT change any class signature, docstring (beyond moving), or kernel binding.
  • MUST preserve every public import path: from tileops.ops.elementwise import <Symbol> must keep working for every symbol that was importable before.
  • MUST keep umbrella classes (UnaryOp, BinaryOp, FusedGatedOp) accessible at the same path.
  • MUST follow .claude/rules/code-style.md: explicit __all__, no file-level lint suppressions, intra-package relative imports, cross-package absolute.
  • Out of scope: renaming any op class, renaming the manifest entry, splitting kernels (already done — verify, don't redo).

Acceptance Criteria

  • AC-1: Modified files pass unit tests (pytest tests/ops/).
  • AC-2: tileops/ops/elementwise.py no longer exists; tileops/ops/elementwise/ is a package with __init__.py.
  • AC-3: python -c "from tileops.ops.elementwise import UnaryOp, BinaryOp, FusedGatedOp" succeeds.
  • AC-4: python -c "from tileops.ops.elementwise import SiluAndMulFwdOp, ReluFwdOp, AddFwdOp, ExpFwdOp, DivFwdOp" succeeds (the imports referenced in current downstream code).
  • AC-5: tests/ops/test_elementwise_fp8.py passes without modification to its from tileops.ops.elementwise import … lines.
  • AC-6: grep -nE "^# ruff:|^# flake8:" tileops/ops/elementwise/ returns nothing (no file-level suppressions introduced).
  • AC-7: [META][OPS] Align elementwise / reduction / normalization ops to new Op-layer design + PyTorch API #1142 tracker row W3-03 flipped 🔲 → 🟢.

Metadata

Metadata

Assignees

Labels

choreMaintenance and housekeepingnightshiftPickable by foundry nightshift mode — fully autonomous agent development, no human approval gaterefactorCode restructuring without behavior change

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions