0
mirror of https://github.com/bbenchoff/OrthoRoute.git synced 2026-08-24 14:47:01 +00:00
Files
OrthoRoute/docs/optimization/optimization_baseline_2026-04-03.md
OpenFixture Developer e836eb1eaf Add automated optimization workflow with smoke test validation
- Create scripts/analyze_log.py: Standalone log parser for routing metrics extraction and golden comparison
- Create scripts/optimize_and_validate.ps1: PowerShell automation wrapper for full optimization cycle
- Create scripts/README.md: Comprehensive documentation for automation scripts
- Create tests/regression/smoke_metrics.json: Golden thresholds for smoke test (100 nets, 4 layers)
- Create docs/optimization/optimization_workflow.md: Complete optimization workflow guide (800+ lines)
- Create docs/optimization/baseline_template.md: Standardized template for future optimization baselines
- Extend launch_kicad_debug.ps1: Add -RunValidation flag for post-KiCad smoke test
- Extend copy_to_kicad.ps1: Add -Validate switch for deployment validation
- Update optimization docs: Add workflow references to QUICK_REF, README, golden_result_2026-04-10
- Update historical baselines: Add workflow tool references to 2026-04-03, 04-05, 04-08

Workflow enables fast iterative optimization: make change → sync → smoke test (30s) → analyze → commit
Exit codes: 0=PASS, 1=FAIL routing, 2=WARN regression, 3=ERROR environment
Smoke test becomes primary validation checkpoint (middle complexity: 100 nets vs 512 backplane)
2026-04-12 20:10:06 +02:00

524 lines
20 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# OrthoRoute Performance Baseline & Optimization Roadmap
**Date**: April 3, 2026
**Version**: OrthoRoute v0.2.0
**Test Board**: TestBackplane.kicad_pcb
**Purpose**: Establish performance baseline for future optimization work
> **Update (April 12, 2026):** Automated optimization workflow now available!
> See [optimization_workflow.md](optimization_workflow.md) for complete guide.
> Use `.\scripts\optimize_and_validate.ps1` for automated testing and validation.
---
## Executive Summary
Successfully routed a **18-layer backplane** with **512 nets**, **1,604 pads** in **44.23 minutes** initially, reduced to **25 minutes** after persistent kernel optimization.
**Run history:**
| Run | Date | Change | Total Time | Iter avg | Result | Notes |
|-----|------|--------|------------|----------|--------|-------|
| 1 | Apr 3 | Baseline — multi-launch kernel | 47.9 min | ~39s | 512/512 ✓ | 74 iters |
| 2 | Apr 3 | Persistent CUDA kernel enabled | **25 min** | ~20s | 512/512 ✓ | 70 iters, **2× gain** |
| 3 | Apr 3 | `cp.scatter_add` vectorized bitmap loops | ~25 min | ~22s | 512/512 ✓ | Bug fix run — `cp.scatter_add` doesn't exist, fixed to `cupyx.scatter_add` + bitmap OR corruption fix |
| 4 | Apr 3 | GPU-resident `node_owner_gpu` + zero-upload bitmap | ~25 min | ~22s | 512/512 ✓ | **No measurable improvement** — bottleneck was not bitmap upload |
**Current bottleneck**: Unknown — per-net wall time is ~90130ms but GPU kernel time is only 422ms. The ~70100ms gap remains after eliminating bitmap loops and bitmap upload. Likely candidates: Python path reconstruction overhead, `_path_to_edges()`, `commit_path()`, or per-net accounting in Python.
**What did NOT help**:
- Vectorizing `int(seed)` loops → fixed correctness (bug), no measurable perf gain
- Building bitmap on GPU (`node_owner_gpu`) → no measurable speedup (upload was not the bottleneck)
See [OPTIMIZATION_QUICK_REF.md](OPTIMIZATION_QUICK_REF.md) for current priority table.
**Initial Finding (corrected)**: `_rebuild_via_usage_from_committed()` was believed to be the #1 target.
**Live-run correction (April 3, 2026)**: After profiling 40 iterations and 3,311 GPU paths, the via rebuild consumes **~1% of routing time**. The real bottleneck was the **MULTI-LAUNCH Python→CUDA loop** — now replaced by the persistent kernel. New bottleneck is Python bitmap construction (~50ms/net).
---
## Test Board Characteristics
### Board Complexity
- **Layers**: 32 copper layers
- **Nets**: 512 nets to route
- **Pads**: 3,200 pads
- **Via Pairs**: 870 via pairs
- **Design Type**: High-density backplane
### Routing Environment
- **Grid Resolution**: Manhattan lattice (configured in `orthoroute.json`)
- **Keepout Areas**: Multiple rule areas with track/via constraints
- **Algorithm**: PathFinder negotiated congestion routing
- **Convergence**: 64 iterations to zero overuse
### Final Results
- **Success Rate**: 512/512 nets routed (100%)
- **Overuse**: Zero final overuse
- **Barrel Conflicts**: 310 remaining (acceptable)
- **Output**: 4,118 tracks + 2,585 vias
---
## Performance Baseline (Total: 44.23 minutes)
### Phase Breakdown
#### 1. Initialization Phase (~25 seconds)
| Operation | Time | Frequency | Notes |
|-----------|------|-----------|-------|
| `initialize_graph` | 20.9s | Once | CSR graph construction |
| `finalize` | 3.95s | Once | Route commitment |
| `_apply_keepout_obstacles` | 0.15ms | Once | Keepout enforcement (highly optimized) |
#### 2. Routing Phase (~39 minutes for 64 iterations)
**Per-Iteration Costs**:
| Operation | Count | Min | Max | Avg | Total | % of Routing |
|-----------|-------|-----|-----|-----|-------|--------------|
| `_rebuild_via_usage_from_committed` | 64 | 0.37ms | 485ms | 347ms | 22.2s | ~13.9% |
| `_apply_via_pooling_penalties` | 64 | 2.58ms | 101ms | 25.5ms | 1.6s | ~1.0% |
**Note**: The above percentages are conservative estimates based on profiled operations. The majority of routing time (85%+) is spent in **unprofiled PathFinder algorithm code** within `unified_pathfinder.py`.
#### 3. Visualization & Output (~4 minutes)
- **Iteration Visualizations**: 66 PNG files generated (debug_output/)
- **KiCad Export**: 4,118 tracks + 2,585 vias written to board
- **Commit to KiCad**: ~40 seconds
---
## Performance Bottlenecks (Prioritized)
> Updated April 3, 2026 — persistent kernel active.
| Priority | Target | Measured Cost | % of per-net | Status |
|----------|--------|--------------|--------------|--------|
| ✅ **DONE** | MULTI-LAUNCH Python→CUDA overhead | ~980s / run | was ~95% | **Fixed — persistent kernel** |
| ✅ **DONE (bug fix)** | `int(seed)` loops — bitmap/frontier init | ~50ms/net estimated | n/a | Fixed correctness; **no perf gain measured** |
| ✅ **DONE (no gain)** | GPU-resident `node_owner_gpu` + zero-upload bitmap | ~56KB/net upload | estimated <5ms | Built, deployed **no measurable improvement** |
| 🔴 **#1 (unresolved)** | Unknown ~70100ms gap per net | ~90130ms wall vs 422ms GPU | **~80%** | Not yet identified profiling needed |
| 🟡 **#2** | `initialize_graph()` | 20.9s once | one-time | GPU-accelerated CSR build |
| 🟢 **#3 (was #1)** | `_rebuild_via_usage_from_committed()` | ~11s / run | **~1%** | Already incremental; low priority |
---
### 🔴 Priority 1 (NEW): GPU MULTI-LAUNCH Overhead
**Measured (40 iterations, 3,311 paths):**
```
GPU hardware: RTX Turing (compute capability 75)
4.3 GB total VRAM / 3.0 GB free at start
14,281,664 edges, 446,472 nodes
Per-net timing:
GPU Dijkstra kernel (MULTI-LAUNCH loop): ~16ms (5%)
Python ↔ CUDA launch overhead: ~296ms (95%)
Total per net: ~312ms
Projection (full run):
3,311 paths × 312ms = 1,032s routing time (so far, 40 iters)
If persistent kernel: 3,311 × 16ms = ~53s → 20× speedup
```
**Root cause**: The MULTI-LAUNCH model runs a Python `for` loop (~150 iterations per net)
where each loop iteration launches a CUDA kernel and syncs back to Python to check
termination. This creates ~150 round-trips per net between CPU and GPU.
The PERSISTENT KERNEL is already compiled (confirmed from logs):
```
[CUDA] Compiled PERSISTENT KERNEL (P1-6: device-side queues, eliminates launch overhead!)
```
But every routing call logs:
```
[GPU-SEEDS] Using MULTI-LAUNCH kernel (Python loop)
```
meaning the selection logic never takes the persistent path.
**How to fix** (`cuda_dijkstra.py`):
1. Find the condition that selects between MULTI-LAUNCH and PERSISTENT KERNEL
2. Determine why the persistent path is not activated (flag not set? size threshold? bug?)
3. Wire in the persistent kernel for the main routing loop
4. Validate: routing quality must be identical, per-net total time should drop from ~312ms to ~30-50ms
**Estimated impact**: **10-20× routing speedup**; full run 44 min 3-5 min
---
### 🟡 Priority 2: Graph Initialization (20.9s one-time)
**Function**: `initialize_graph()`
**Impact**: Medium - one-time cost but significant for large boards
**Current**: CSR sparse matrix construction
**Optimization Opportunities**:
1. **GPU Acceleration**: Check if CSR construction can use CuPy
2. **Parallel Construction**: Build edge arrays in parallel
3. **Memory Allocation**: Pre-allocate arrays if sizes are known
4. **Profiling Needed**: Break down 20s into sub-operations
**Estimated Impact**: 30-40% reduction **~6-8s savings**
---
### 🟢 Priority 3: Via Usage Rebuild (~11s total, was Priority 1)
**Function**: `_rebuild_via_usage_from_committed()`
**Revised assessment**: After live profiling, this is ~1% of runtime not worth further
optimization until the kernel overhead (#1) is resolved.
**Current state (April 3, 2026)**:
- Incremental implementation deployed (40% threshold to full rebuild)
- 40 iters measured: 28 calls, 275ms avg, 11s total
- Full rebuild fires nearly every iteration (dirty set > threshold in early convergence)
- Incremental path activates only after routing stabilizes (~iter 30+)
**Action**: Defer. Revisit after persistent kernel is activated.
---
### ⚪ Not a Bottleneck: Keepout Obstacles (0.15ms)
**Status**: ✅ Already highly optimized (vectorized NumPy ray-casting)
**Action**: No optimization needed
---
### 🟢 Priority 3: Finalization (3.95s one-time)
**Function**: `finalize()`
**Impact**: Low - one-time cost, smaller magnitude
**Optimization Opportunities**:
1. **Analyze what finalize() does** - may already be optimized
2. **Lower priority than iterative operations**
**Estimated Impact**: 25-30% reduction → **~1s savings**
---
### ⚪ Not a Bottleneck: Keepout Obstacles (0.15ms)
**Function**: `_apply_keepout_obstacles()`
**Status**: ✅ Already highly optimized (vectorized NumPy ray-casting)
**Action**: No optimization needed
### ⚪ Not a Bottleneck: Finalization (3.95s one-time)
**Function**: `finalize()`
**Impact**: Low — one-time cost, smaller magnitude. Defer.
---
## GPU Performance Analysis — Live Run (April 3, 2026)
Collected from a run started at 15:04:39, observed through 40 completed iterations (3,311 paths routed, log ~612 MB, run still in progress at time of writing).
### Hardware
- GPU: RTX (CUDA compute capability 75 = Turing)
- VRAM: 4.3 GB total, 3.0 GB free at start
- Graph size: 446,472 nodes, 14,281,664 edges
- Graph sort: 1.0s (GPU radix sort, one-time, 14M edges/sec)
### Compiled Kernels (all confirmed present in logs)
| Kernel | Description |
|--------|-------------|
| ACTIVE-LIST | Sparse wavefront — "446,472× fewer memory accesses" |
| MULTI-LAUNCH | Python loop drives wavefront — **current bottleneck** |
| PERSISTENT KERNEL | Device-side queues, eliminates Python launch loop — **compiled, not activated** |
| COMPACTION | GPU-side frontier compaction, no host sync |
| ACCOUNTANT | GPU-side history/present/cost updates |
| GPU BACKTRACE | Eliminates 256 MB parent/dist CPU transfer |
| RR-WAVEFRONT | Round-robin layer preference |
| DELTA-STEPPING | Bucket assignment |
### Timing Measurements (3,311 paths)
| Metric | Value |
|--------|-------|
| GPU kernel time (CUDA Dijkstra only) | 15.8ms avg |
| Total per-net time (kernel + Python overhead) | 312ms avg |
| Python/CUDA launch overhead | **~296ms per net (95%)** |
| Total routing time (40 iters) | 1,032s |
| Via rebuild (`_rebuild_via_usage_from_committed`) | 275ms avg / 11s total (1%) |
### What 296ms of overhead consists of (inferred)
The `[GPU-SEEDS] Using MULTI-LAUNCH kernel (Python loop)` log line appears for every path.
The Python loop runs ~150 iterations per net (seen: 121161 iters), and each iteration:
1. Calls `rr_wavefront_kernel` (JITTER + ROUNDROBIN parameters built in Python)
2. Reads frontier size back to CPU to check termination
3. Launches ACTIVE-LIST kernel with updated params
Each CPU↔GPU round-trip adds ~1-2ms latency on PCIe, giving 150 × ~2ms ≈ 300ms.
### The PERSISTENT KERNEL path
The persistent kernel (`[CUDA] Compiled PERSISTENT KERNEL (P1-6: device-side queues,
eliminates launch overhead!)`) stores the work queue on-device. Python launches it once,
the kernel loops internally until convergence or timeout, then returns. This replaces the
150-iteration Python loop with a single launch.
**Expected result**: per-net time drops from ~312ms → ~30-50ms (kernel time + one-time setup).
### Next step
In `cuda_dijkstra.py`, find the condition:
```python
logger.info("[GPU-SEEDS] Using MULTI-LAUNCH kernel (Python loop)")
```
and understand why the persistent kernel branch is not taken. The fix is likely a flag,
a size threshold, or a missing feature flag (e.g., `ROUNDROBIN` or `JITTER` not yet
ported to the persistent kernel variant).
---
## Unprofiled Algorithm Code (Largely Resolved)
**Update (April 3, 2026)**: The "unprofiled 2,316s" mystery is now explained.
95% of routing time is the MULTI-LAUNCH Python→CUDA overhead (see GPU analysis above).
The algorithm itself (Dijkstra wavefront) takes ~16ms per path on GPU — it is fast.
There is no hidden algorithm bottleneck to uncover with more `@profile_time` decorators.
**Remaining unknowns**: The `_route_all` batch dispatch loop and ROI construction overhead.
These are likely small (<5% each) relative to the kernel overhead, but can be confirmed
by adding `@profile_time` to `_route_all` and `_build_roi`.
**Action Required (revised)**: Instead of adding profile decorators to routing functions,
focus on activating the PERSISTENT KERNEL in `cuda_dijkstra.py`.
---
## Log File Analysis
- **Log Size (baseline)**: 799.9 MB for 44-minute run
- **Root Cause** (fixed April 3, 2026): 160 `logger.info` calls with no verbosity gate
- **Fix applied**: 18 INFOWARNING, 81 INFODEBUG, 1 WARNINGDEBUG 100 calls reclassified
- **Expected**: ~25-40 MB after re-test (re-run needed to confirm)
- See [CURRENT_LOGGING_REVIEW.md](CURRENT_LOGGING_REVIEW.md) for full analysis
---
## Routing Algorithm Behavior (Observed)
### Convergence Pattern
- **Total Iterations**: 64
- **Strategy**: PathFinder negotiated congestion
- **Exclusion/Retry**: Algorithm temporarily excludes difficult nets, retries every 10 iterations
- **Final State**: Zero overuse after 64 iterations
### Temporary Net Exclusions (Normal Behavior)
The algorithm correctly uses exclusion/retry strategy:
- Iteration 26: Excluded 1 net (`B03B07_005`) after 5 failed attempts
- Iteration 30: **RETRY** - gave 3 excluded nets another chance
- Iteration 34: Excluded 2 nets (`B00B04_010`, `B01B03_002`)
- Iteration 40: **RETRY** - gave 2 excluded nets another chance
- Iteration 45: Excluded 3 nets
- Iteration 50: **RETRY** - gave 3 excluded nets another chance
- **Final Result**: All 512 nets successfully routed
**This is correct behavior** - not errors or failures.
---
## System Health Status ✅
### Error Analysis
- **Errors Found**: 0
- **Critical Issues**: 0
- **Exceptions**: 0
- **Traceback**: 0
### Warnings Analysis
- **Total Warnings**: 12 (all temporary net exclusions - expected)
- **Severity**: Low (normal PathFinder algorithm behavior)
### Routing Quality
- **Completion**: 512/512 nets (100%)
- **Overuse**: 0 (perfect convergence)
- **Barrel Conflicts**: 310 (acceptable)
- **Keepout Violations**: 0 (enforced correctly)
**Conclusion**: System is stable and healthy. Ready for optimization work.
---
## Optimization Strategy & Roadmap
### Phase 1: Instrumentation (COMPLETE)
1. **Logging reclassification** 100 calls reclassified (18 INFOWARNING, 81 INFODEBUG)
2. **`@profile_time` decorator** added and exported from `shared/utils/performance_utils.py`
3. **`_rebuild_via_usage_from_committed` profiled** confirmed 1% of runtime
4. **GPU timing profiled** MULTI-LAUNCH overhead identified as #1 bottleneck
---
### Phase 2: PERSISTENT KERNEL Activation (HIGH PRIORITY)
**Goal**: Eliminate PythonCUDA launch loop overhead (95% of routing time)
**Target file**: `orthoroute/algorithms/manhattan/pathfinder/cuda_dijkstra.py`
1. **Find kernel selection logic** search for `MULTI-LAUNCH` selection condition
2. **Identify why persistent path is skipped** flag? threshold? missing JITTER/RR support?
3. **Port JITTER + ROUNDROBIN to persistent kernel** (if not already done)
4. **Activate persistent kernel** for ROI some node threshold
5. **Validate routing quality** zero overuse must be maintained
6. **Benchmark** expect per-net time to drop from ~312ms ~30-50ms
**Target**: 44 min 3-5 min (**10-20× speedup**)
---
### Phase 3: Graph Initialization Optimization
**Goal**: Reduce 20.9s to ~13-15s (30-40% improvement)
1. **Profile `initialize_graph()` sub-operations**
2. **Investigate GPU-accelerated CSR construction**
3. **Parallel edge array building**
4. **Memory pre-allocation**
**Target**: 6-8s savings (only meaningful once kernel overhead is resolved)
---
### Phase 4: Via Rebuild (Deferred)
**Status**: Incremental implementation already deployed (see `_rebuild_via_usage_from_committed`).
At 1% of runtime, further optimization is not worthwhile until Phase 2 is complete.
---
### Success Metrics (Revised)
**Performance Targets**:
- **Phase 2** (persistent kernel): 44 min **3-5 min** (10-20× faster)
- **Phase 3** (init): no change to routing time, saves ~6-8s startup
- **Phase 4** (via rebuild): deferred
**Quality Requirements** (Must Maintain):
- 100% routing success rate (512/512 nets)
- Zero final overuse
- All keepout constraints enforced
- Correct barrel conflict detection
---
## Test Environment
### Hardware
- **OS**: Windows (OneDrive-enabled)
- **Python**: 3.11+
- **GPU**: NVIDIA with CUDA (CuPy available)
- **Memory**: Sufficient for 799MB logs + GPU operations
### Software Stack
- **OrthoRoute**: v0.2.0
- **KiCad**: 9.0+ (IPC API enabled)
- **Dependencies**: NumPy, CuPy, PyQt6
- **Profiling**: `@profile_time` decorator (performance_utils.py)
### Development Tools
- **VS Code**: Dual workspace (source + deployed plugin)
- **Specialized Agents**:
- Refactoring Agent (`.github/agents/refactoring.agent.md`)
- GPU Performance Agent (`.github/agents/gpu-performance.agent.md`)
- Architecture Reviewer (`.github/agents/architecture-review.agent.md`)
- Testing Agent (`.github/agents/testing.agent.md`)
---
## Quick Start for Optimization Work
### 1. Run with DEBUG_LEVEL=4
```powershell
# Set environment variable
$env:ORTHOROUTE_DEBUG_LEVEL=4
# Run plugin with detailed logging
python main.py plugin --no-gui
```
### 2. Analyze New Profile Data
```powershell
# Extract profile timings
Get-Content logs/latest.log | Select-String -Pattern "\[PROFILE\]"
# Count iterations
Get-Content logs/latest.log | Select-String -Pattern "\[ITER" | Measure-Object
```
### 3. Compare Performance
```powershell
# Log file size
(Get-Item logs/latest.log).Length / 1MB
# Total execution time (check log timestamps)
```
### 4. Use Specialized Agents
```markdown
# For algorithm optimization
@GPU Performance Agent - analyze via rebuild performance
# For refactoring unified_pathfinder.py
@Refactoring Agent - extract function X from unified_pathfinder
# For validation
@Architecture Reviewer - check dependencies in optimization branch
# For testing
@Testing Agent - create characterization tests for via rebuild
```
---
## References
### Documentation
- [Algorithm Architecture](../algorithm_architecture.md) - Deep dive into PathFinder
- [DEBUG_LEVEL System Guide](debug_level_system.md) - Complete logging guide
- [Performance Profiling Example](../performance_profiling_example.md) - @profile_time usage
- [Tuning Guide](../tuning_guide.md) - Parameter optimization
### Code Files
- **Main Router**: `orthoroute/algorithms/manhattan/unified_pathfinder.py` (3,936 lines)
- **Profiling Utils**: `orthoroute/shared/utils/performance_utils.py`
- **DEBUG_LEVEL Core**: `orthoroute/shared/utils/debug_levels.py`
- **Migration Examples**: `docs/optimization/examples/debug_level_migration_unified_pathfinder.py`
### Test Data
- **Test Board**: `TestBoards/TestBackplane.kicad_pcb`
- **Baseline Log**: `logs/run_20260403_100453.log` (799.9 MB)
- **Iteration Visualizations**: `debug_output/run_20260403_100514/` (66 PNGs)
- **KiCad Export**: `debug_output/kicad_export_20260403_104820.json`
---
## Notes & Observations
### Design Complexity is Adequate
The TestBackplane board with **512 nets** and **32 layers** provides excellent test coverage:
- Complex enough to show real performance issues
- Large enough for meaningful optimization gains
- Diverse net topology (short/long, simple/complex)
- Sufficient iteration count (64) for statistical analysis
### GPU Utilization Unknown
- Profile data doesn't indicate CPU vs GPU execution
- Need to verify which operations are GPU-accelerated
- Consult GPU Performance Agent for optimization guidance
### Log Verbosity is Critical
- 799 MB logs make analysis impossible
- DEBUG_LEVEL system implementation was essential
- Future optimization work depends on Level 4 logging
### PathFinder Algorithm is Mature
- Zero errors in complex routing proves stability
- Exclusion/retry strategy works correctly
- Ready for performance optimization without correctness concerns
---
## Baseline Established: Ready for Optimization ✅
This document establishes the performance baseline for OrthoRoute on a production-representative test case. All measurements are reproducible, system health is confirmed, and optimization targets are identified.
**Next Immediate Action**: Migrate `unified_pathfinder.py` to DEBUG_LEVEL system to enable detailed algorithm profiling.
---
**Document Version**: 1.0
**Last Updated**: April 3, 2026
**Author**: Performance Analysis
**Status**: Baseline Established