Project: Two-Tier Analytical Performance Bound Model for Triton/Ascend NPU
Spec: .omc/specs/performance_bound_model.md + .omc/specs/implementation_and_paper_plan.md
Plan: .omc/plans/a0_perf_bound_model.md
Detail log: .omc/plans/a0_progress.md
| Stage | Scope | Timeline | Status |
|---|---|---|---|
| A.0 | Python perfbound/ package scaffold (M1–M6 stubs + models) |
Wk 1–7 | ✅ Complete |
| A.1 | M1 Calibration — AscendC microbench suite on 910B3 | Wk 1–2 | ✅ Complete — 16 P0 constants measured + wired into model (40/40 tests) |
| A.2 | M2 DSL Extractor — symbolic affine recovery from TTIR | Wk 2–3 | ✅ Complete — C++ MLIR pass + 10 reference kernels verified (63/63 tests) |
| A.3 | M3 HIVM Extractor — C++ JSON round-trip verified | Wk 3–5 | ✅ Complete — DES schema fix, MTE byte aggregation, handoff tracing (108/108 tests) |
| A.4 | M4 Two Analytical Models — units discipline + compute_bounds driver | Wk 5–7 | ✅ Complete — flops consumption, distinct-edge serialization, 144/144 tests |
| A.5 | M5 Combiner — Gap 1/2/4 wired, Gap 3 from real handoffs | Wk 7 | ✅ Complete — combiner, 5-way attribution, two-limit, real-kernel milestone (committed dffda87) |
| A.6 | M6 Validation Harness — measurement + counterfactual | Wk 6–9 | ✅ Complete — A.6.1 measurement (174d0ab) + A.6.2 counterfactual/remote runner; live 910B3 run done (real T_measured, evidence .omc/research/hw_runs/) |
| A.7 | Two-limit computation (T_bound_HIVM vs T_bound_DSL) |
Wk 8 | ✅ Complete — delivered within A.5; author_headroom now populated from real msprof data |
| A.8 | End-to-end pipeline verified on ≥1 real kernel | Wk 9 | 🟡 Mostly done — mixed-CV pipeline green; chunk_kda compiles+runs on CANN 9.0.0 and is measured on hardware (bishengir beta.2 crash resolved); remaining gap is offline des.json extraction for chunk_kda (needs NPU host) |
| Part B | Experiments, paper writing, iterate calibration | Wk 9–12 | ⛔ Not started |
A.5 → A.6.2 landed since the snapshot below. The "What's done" list covers A.0–A.4; see the A.5 and A.6 completion sections lower down for the combiner, attribution, two-limit, and validation harness. Full
perfbound/suite: 317 passed, 3 skipped, 2 xfailed.Hardware milestone (2026-06-10): the remote 910B3 is wired (
scripts/remote_bench.py, ssh host910B3, CANN 9.0.0, condatriton_hxl). chunk_kda compiles and runs on the device (the bishengirConvertLinalgRToBinarycrash was a CANN 9.0.0-beta.2 bug, fixed in the 9.0.0 release). It was profiled under msprof: T_measured ≈ 104.3 ms, T_bound (HBM floor) ≈ 1.39 ms → soundness PASS (75× tightness), author_headroom ≈ 102.9 ms — the first real, non-synthetic three-level measurement. Real-data parser bug fixed (MIX_AIC task type). Evidence + reproduce steps:.omc/research/hw_runs/RESULTS.md; CI guard:tests/perfbound/test_chunk_kda_hw_validation.py. The 2 remaining xfails are the local milestone compile tests, which now xfail only because WSL has no NPU device (not the compiler bug).
perfbound/package (28 files, pure Python, zero MLIR dependency)- M1:
CalibrationDB,CalibrationConstant(value ± CI, source, n_runs), full JSON ser/de - M2:
GridInfo,DSLExtractor(C++ MLIR pass + affine idiom recovery),grid_idioms.py(1D/2D templates),mlir_parser.py(subprocess wrapper), 10 reference kernels verified - M3: JSON loaders for both C++ emits,
OpRecord+HandoffRecord, pipe→component classification, eligibility oracle,repeat/maskfields, C++ DES graph schema fix (operationscanonical), MTE byte aggregation bugfix, handoff tracing - M4:
compute_boundsdriver,BoundPieces, units discipline (flops for FLOP/us, bytes for B/us), distinct-edge serialization, grid floor explicit total_work parameter, component model weighted-harmonic mean - M5:
T_bound = max(T_grid_floor, T_core_floor) + T_serial_irreducible, 5-way attribution,_wire_gapsconnecting Gap 1/2/4 helpers, binding tier detection, text+JSON report - M6: Stubs with correct
NotImplementedError(hardware-gated)
- M1:
- C++ emitter layer (committed):
emitDESGraph()JSON,emitDependencyGraphJSON(),PipelineAnalysisPasswiring - A.1 calibration (
calib_910b3_v1.json): 16 P0 constants, n=45 each, all CI < 2.5%- Cube FP16/INT8/BF16: ~5.16 TFLOPS/core
- BW GM→UB / UB→GM: ~87 GB/s; GM→L1: ~141 GB/s; L1→L0A: ~452 GB/s
- Vector add/mul/max/min: 14.6–16.2 GFLOPS; transcendentals: 3.3 GFLOPS
- Mandatory handoff cost: 7621 ± 82 cycles (~4.1 µs at 1.85 GHz)
- Tests: 144/144 passing (26 A.1 + 18 A.2 + 47 A.3 + 53 A.4)
| Item | Blocker | Priority |
|---|---|---|
✅ RESOLVED — was a CANN 9.0.0-beta.2 bug; chunk_kda compiles+runs on CANN 9.0.0 release (.omc/research/hw_runs/) |
— | |
✅ DONE — real T_measured + author_headroom from msprof; soundness PASS (test_chunk_kda_hw_validation.py) |
— | |
| Offline des.json for chunk_kda | Needs tritonsim-hivm --triton-script on an NPU host (WSL has no NPU; remote 910B3 is aarch64, the x86-64 binary can't run there) |
P2 |
| Counterfactual delta on hardware | Mechanism offline-tested + remote wiring fixed; one live edit→recompile→delta run still pending | P2 |
Gap 4 from C++ emitter repeat/mask |
C++ emitter still defaults these (model reads them; emitter doesn't populate yet) | P2 |
| Scalar throughput (Vector/20 proxy) | Needs real Scalar calibration (B.4) before trusting as a lower bound | P2 |
Closed since A.4: Gap 3 (avoidable serialization, A.5) · Two-limit / A.7 (two_limit.py, A.5) · M6 measurement + counterfactual harness (A.6.1/A.6.2).
| Item | Closed by |
|---|---|
| C++/Python DES graph schema mismatch | A.3 |
| MTE byte-vs-element aggregation bug | A.3 |
Missing transfer metadata (transfer_sizes, transfer_alignments) |
A.3 |
Non-deterministic C++ output paths (pipeline_dep_graph.json) |
A.3 |
| Units discipline (flops vs elements) | A.4 |
| Grid floor bypass in bound_from_extract | A.4 |
| T_serial_irreducible flat-cost vs Σ over distinct edges | A.4 |
| Step | Status |
|---|---|
C++ ExtractTTIRInfoPass (--extract-ttir-info) |
✅ Done — walks AST, emits JSON (grid_axes, persistent_loops, tensor_ptr_shapes, has_dot) |
perfbound/extract/mlir_parser.py |
✅ Done — subprocess wrapper, brace-counted JSON extraction |
dsl_extractor.py refactor (no regex) |
✅ Done — _extract_from_ttir() uses parse_ttir(), _persistent_kernel_info() round-robin |
grid_idioms.py Bug 1 fix |
✅ Done — parents[2] correct, ascend_910b3.json loads |
| 10 reference kernel parametrized suite | ✅ Done — K1–K10, occupancy/lb/tile_assignment/buffer_ok verified |
| Code review (7 findings) | ✅ All resolved — F1 NameError, F2 pytestmark, F3 paths, F4 C++ filter, F5 inline TTIR, F6 ifdef, F7 dead elif |
| Step | Status |
|---|---|
| C++ DES graph schema fix | ✅ Done — emitDESGraph() emits schema_version: "a3_hivm_des_v1" and start_cycle/end_cycle per op |
| C++ deterministic output paths | ✅ Done — desGraphFile and dependencyGraphFile options, removed hard-coded cwd writes |
| Python MTE byte aggregation | ✅ Done — load_hivm_desgraph() uses bytes not elements for MTE ops |
| Python handoff tracing | ✅ Done — canonical compute-to-compute tracing through MTE intermediaries |
| Python transfer metadata | ✅ Done — transfer_sizes, transfer_alignments, memory space normalization |
| Python required field validation | ✅ Done — validates id, name, pipe on load |
| 22 HIVM extractor tests | ✅ Done — DES schema, field validation, MTE byte aggregation, transfer metadata, handoffs |
| 22 eligibility oracle tests | ✅ Done — Matmul→Cube, elementwise→Vector, i32 compare→Scalar, Gap 1 analysis |
| 3 CLI integration tests (xfail) | ✅ Done — proper xfail on broken fixture |
| Step | Status |
|---|---|
| Component model flops consumption | ✅ Done — uses op.flops for FLOP/us rate, not op.elements |
| Grid model explicit total_work | ✅ Done — caller-supplied total_work parameter matching i_binding units |
| Serialization distinct-edge dedup | ✅ Done — sums over DISTINCT mandatory edges by component pair |
| compute_bounds driver | ✅ Done — picks (i_binding, total_work) consistently (memory-bound vs compute-bound) |
| bound_from_extract wiring | ✅ Done — routes through compute_bounds, no bypass |
| 53 model tests | ✅ Done — FlashAttention golden test, grid floor unit tests, serialization dedup tests, compute_bounds unit-selection |
Detail log: .omc/plans/a6_progress.md · Blocker scoping: .omc/plans/a6_2_blockers_scope.md
M6 splits into measurement (A.6.1) and counterfactual (A.6.2); both software-complete. The only remaining work is hardware/compiler-gated (a live 910B3 run + the CANN compiler bug), tracked by xfail/spike tests.
| Part | Step | Status |
|---|---|---|
| A.6.1 | msprof CSV parser (AiCore filter, invocation grouping, max-per-invocation, warmup discard, median) | ✅ Done (13 tests) |
| A.6.1 | Tri-state harness (ValidationStatus; infra errors excluded from soundness) |
✅ Done (7 tests) |
| A.6.1 | Three-level reachability report (HIVM/DSL/measured + provenance, component match) | ✅ Done (11 tests) |
| A.6.2 | HIVM edit primitives + no-op guards + extract-reversibility check | ✅ Done (23 tests) |
| A.6.2 | Output correctness verification (numpy allclose) | ✅ Done (10 tests) |
| A.6.2 | Counterfactual orchestration (edit→compile→verify→delta, tri-state infra) | ✅ Done (19 tests) |
| A.6.2 | Fallback-kernel counterfactual (decoupled from chunk_kda compiler bug) | ✅ Done (10 tests) |
| A.6.2 | On-device Triton kernel launcher (load→run→dump .npy) |
✅ Done (13 tests) |
| A.6.2 | Remote 910B3 runner (sync/recompile/msprof/fetch, host config, CANN preamble) | ✅ Done (18 tests) |
| A.6 | chunk_kda compile + dump-survives spike | ⛔ xfail — bishengir ConvertLinalgRToBinary crash (CANN 9.0.0-beta.2) |
| Step | Status |
|---|---|
T_bound = max(T_grid_floor, T_core_floor) + T_serial_irreducible |
✅ Done |
| Five-way attribution (grid + gap1/2/3/4), Gap 3 from real handoff classification | ✅ Done |
| Binding tier/component detection + text/JSON report | ✅ Done |
Two-limit (T_bound_HIVM vs T_bound_DSL) + reachability hierarchy (A.7) |
✅ Done — two_limit.py |
| Real-kernel milestone (mixed-CV pipeline end-to-end) | ✅ Done |
A.5 scope: Complete the M5 combiner module with full five-way attribution wired from real data:
- Gap 3 (avoidable serialization) computation from handoff classification
- Gap 1/2/4 helpers verified on real HIVM extracts
- End-to-end bound computation on ≥1 real kernel
- Text+JSON report generation with binding tier/component identification
| Step | Status |
|---|---|
C++ ExtractTTIRInfoPass (--extract-ttir-info) |
✅ Done — walks AST, emits JSON (grid_axes, persistent_loops, tensor_ptr_shapes, has_dot) |
perfbound/extract/mlir_parser.py |
✅ Done — subprocess wrapper, brace-counted JSON extraction |
dsl_extractor.py refactor (no regex) |
✅ Done — _extract_from_ttir() uses parse_ttir(), _persistent_kernel_info() round-robin |
grid_idioms.py Bug 1 fix |
✅ Done — parents[2] correct, ascend_910b3.json loads |
| 10 reference kernel parametrized suite | ✅ Done — K1–K10, occupancy/lb/tile_assignment/buffer_ok verified |
| Code review (7 findings) | ✅ All resolved — F1 NameError, F2 pytestmark, F3 paths, F4 C++ filter, F5 inline TTIR, F6 ifdef, F7 dead elif |
A.3 scope: Verify the C++ emitDESGraph() + emitDependencyGraphJSON() JSON round-trip end-to-end on a real kernel. Python loaders exist (hivm_extractor.py, OpRecord, HandoffRecord) but the C++ JSON output has not been validated against the Python schema.
Closure targets (from open-questions.md §A.3):
- DES graph schema compatibility — canonicalize C++/Python contract, required per-op fields, schema version
- Deterministic artifact handling — remove hard-coded
pipeline_dep_graph.json/trace writes; usetmp_pathin tests - Tier 2 metadata completeness —
O_prec, transfer size/alignment, unit assignment, repeat/mask defaults populated from real JSON - Semantic eligibility oracle — connect TTIR/Linalg semantics to realized HIVM unit assignment; flag Scalar fallback
Open item — L2 cache BW model (deferred to A.5/A.7):
- Real kernels have partial L2 reuse; pure HBM BW is conservative (sound) but loose for tight bounds
- Future:
BW_eff = h × BW_l2 + (1-h) × BW_hbmwhereh = f(working_set / L2_size) - Tracked in
open-questions.md §Open Item — L2 Cache Bandwidth Model
T_bound = max(T_grid_floor, T_core_floor) + T_serial_irreducible
# Tier 1: Grid floor (A.4)
T_grid_floor = total_work * redundancy / (n_cores * occupancy * load_balance * i_binding)
- Memory-bound: i_binding = BW (B/us), total_work = Σ MTE bytes
- Compute-bound: i_binding = Cube/Vector throughput (FLOP/us), total_work = Σ flops
# Tier 2: Component floor (A.4)
T_core_floor = max_c(O_c / I_c)
I_c = Σ O_prec / Σ (O_prec / P_prec) [weighted harmonic mean over ops in component c]
# Serialization (A.4)
T_serial_irreducible = |E_mandatory| * (mandatory_handoff_cycles / clock_freq)
where E_mandatory = set of DISTINCT (producer_component, consumer_component) edges
# Five-way attribution (diagnostic, not part of bound):
grid = T_grid_floor / T_bound
gap1 = wrong-unit placement (eligible set vs realized unit)
gap2 = coalescing loss (actual pkt BW vs ideal large-pkt BW)
gap3 = avoidable serialization (non-mandatory handoffs)
gap4 = intra-unit exec inefficiency (repeat > 1, masked lanes)