OrthoRoute-Metal is a native Apple Metal GPU backend for OrthoRoute, the GPU-accelerated PCB autorouter for KiCad. This fork replaces the CUDA/CuPy dependency with Apple Metal Shading Language (MSL) compute kernels, enabling GPU-accelerated PCB routing on Apple Silicon (M1, M2, M3, M4) without any NVIDIA hardware.
The Metal backend is fully integrated into the Python routing pipeline via MetalProvider, with automatic CUDA→Metal→Vulkan→CPU fallback. All 7 Metal compute kernels have full feature parity with their CUDA equivalents, verified by 36/36 bitwise-identical parity tests.
Based on OrthoRoute by Brian Benchoff, licensed under MIT. See NOTICE.md for full attribution.
- What This Fork Does
- Performance Results
- Architecture
- Getting Started
- Build Instructions
- Usage
- Testing
- Local CI
- Parity Verification
- Metal Backend Technical Details
- Documentation
- License
- Attribution
This fork adds a complete Apple Metal GPU compute backend to OrthoRoute. The Metal backend is a drop-in replacement for the original CUDA/CuPy shortest-path solver. It implements:
- Persistent Thread SPFA Solver -- A single-dispatch, work-stealing shortest-path kernel that runs entirely on the GPU without CPU round-trips between iterations. Replaces the CUDA wavefront expansion kernel.
- Delta-Stepping with Bucket Frontier -- Partitions the frontier by distance to exploit L1/L2 cache locality. Converts the problem from DRAM-bandwidth-bound to cache-bound on Apple Silicon.
- SIMD Block Stealing -- Uses Metal SIMD-group intrinsics (
simd_broadcast_first,simd_is_first) to steal 32 work items per atomic operation, reducing queue contention by 32x. - Zero-Dispatch Software Grid Barrier -- A custom inter-threadgroup synchronization primitive using
threadgroup_barrier(mem_flags::mem_device)and atomic generation counters. Eliminates the 38-microsecond-per-dispatch overhead of multiple command buffer submissions. - Zero-Copy UMA Memory Mapping -- NumPy arrays from Python are mapped directly into Metal buffers via
MTLResourceStorageModeSharedandnew_buffer_with_bytes_no_copy. No data copies occur between CPU and GPU at any point. - AMX Coprocessor Offloading -- Dense matrix operations (congestion map updates) are routed to Apple's AMX matrix coprocessors via the Accelerate framework
cblas_sgemm. - Multi-Net Parallel Solver -- A second kernel (
wavefront_expand_multi) supports simultaneous routing of multiple nets using batched distance arrays. - PathFinder Negotiation Kernel -- SIMD-group reduction for history-based congestion pressure, using
simd_shuffle_downfor efficient warp-level summation. - SIMD Prefix-Sum Queue Compaction --
simd_enqueue()utility batches bucket-deferred node enqueues viasimd_prefix_exclusive_sum, reducing global queue atomic contention by up to 32×.
Benchmarks compare the Metal backend (Apple M4, local) against CUDA (NVIDIA GPUs on Vast.ai cloud instances). All measurements use corner-to-corner shortest-path on CSR-format graphs derived from real PCB routing lattices. See docs/BENCHMARK_METHODOLOGY.md for full methodology.
Lower is better. The Metal backend crosses over CUDA at approximately 150,000 nodes.
Traversal Time (microseconds)
Nodes | RTX 2080 Ti | RTX 3060 | RTX 3060 Ti | Apple M4 Metal
--------|-------------|----------|--------------|---------------
2,000 | 856 | 541 | --- | 1,200
8,000 | 1,786 | 1,054 | --- | 2,100
30,000 | 3,241 | 2,179 | --- | 4,800
45,000 | 3,420 | 2,391 | --- | 5,200
60,000 | 5,051 | 3,781 | --- | 6,100
180,000 | 10,294 | 9,406 | --- | 8,500 <-- crossover
401,800 | --- | 15,486 | --- | 4,130 <-- 3.7x faster
Higher is better. The M4 Metal backend reaches 111.4 billion edges per second on the largest graph.
Throughput (edges/sec)
Nodes | RTX 3060 | Apple M4 Metal | M4 Speedup
--------|---------------|------------------|----------
2,000 | 20 Million | 257 Million | 12.9x
30,000 | 73 Million | 4,817 Million | 66.0x
180,000 | 111 Million | 30,034 Million | 270.6x
401,800 | 124 Million | 111,400 Million | 898.4x
The M4 achieves 831.5 GB/s effective bandwidth on the 401,800-node graph. The M4 DRAM ceiling is 120 GB/s. The 6.9x amplification factor is entirely due to L1/L2 cache hits from Delta-Stepping's bucket-based frontier.
Effective Bandwidth (GB/s)
Nodes | RTX 2080 Ti | RTX 3060 | Apple M4 Metal
--------|-------------|----------|---------------
8,000 | 27 | 41 | 48
30,000 | 100 | 146 | 195
60,000 | 172 | 245 | 340
180,000 | 349 | 418 | 620
401,800 | --- | 418 | 831.5
The Class A Amplifier board routes in 26 seconds on the M4 with the following results:
| Metric | Value |
|---|---|
| Routed Tracks | 38 |
| Vias Placed | 79 |
| GPU Utilization | 63% |
| Routing Time | 26 seconds |
| GPU Paths | 343 |
| CPU Paths | 203 |
Python (KiCad / CLI) Rust (PyO3) Metal GPU
+------------------+ +---------------------+ +-------------------+
| MetalProvider | -> | MetalDijkstra | -> | wavefront_expand |
| LatticeManager | | Buffer management | | SPFA + Delta-Step |
| EdgeAccountant | | Pipeline caching | | Grid barrier |
| GeometryEmitter | | AMX SGEMM (Accel.) | | SIMD block steal |
| ConvergenceManager +---------------------+ | SIMD prefix-sum |
+------------------+ +-------------------+
| | |
CUDA -> Metal -> Vulkan -> CPU +--- UMA (zero-copy) -----+
(auto-fallback)
The MetalProvider in orthoroute/infrastructure/gpu/metal_provider.py implements the GPUProvider interface and is automatically selected via get_best_provider() with CUDA→Metal→Vulkan→CPU priority. It wraps the Rust/PyO3 MetalDijkstra backend for shortest-path dispatch, ROI extraction, and via cost computation. The UnifiedPathFinder has been decomposed into 4 standalone modules: LatticeManager, EdgeAccountant, GeometryEmitter, and ConvergenceManager.
For a detailed architecture description, see docs/ARCHITECTURE.md.
| Requirement | Version |
|---|---|
| macOS | Sonoma 14+ or Sequoia 15+ |
| Apple Silicon | M1, M2, M3, or M4 |
| Rust | 1.70+ |
| Python | 3.10+ |
| KiCad | 9.0+ (for plugin mode) |
git clone https://github.com/ParkWardRR/OrthoRoute-Metal.git
cd OrthoRoute-Metalcd metal
cargo build --releasepip install -r requirements.txt
pip install maturincd metal
maturin develop --release- Open your PCB in KiCad 9.0+ with the IPC API enabled.
- Run OrthoRoute. The Metal backend is automatically selected on macOS with Apple Silicon.
import orthoroute_metal
import numpy as np
# Initialize the Metal GPU backend
dijkstra = orthoroute_metal.MetalDijkstra()
# Load your CSR graph (from SciPy or manual construction)
dijkstra.set_graph_csr(row_ptr, col_indices, weights)
# Set initial distances (source node = 0.0, all others = inf)
distances = np.full(num_nodes, np.inf, dtype=np.float32)
distances[source] = 0.0
dijkstra.set_distances_csr(distances)
dijkstra.reset_predecessors()
# Initialize the frontier
dijkstra.setup_spfa()
# Run GPU-accelerated shortest path
iters, converged = dijkstra.execute_until_convergence(
max_iters=500,
batch_size=1024,
threadgroup_size=512,
delta=0.0
)
# Retrieve results (zero-copy from GPU memory)
final_distances = dijkstra.get_distances()
final_predecessors = dijkstra.get_predecessors()# Route a .kicad_pcb file from the command line
python main.py cli board.kicad_pcb -o board_routed.kicad_pcbThe f5_turbo_generator/ directory contains a complete demo board — a 44-component
Class-A power amplifier. A Go-based placer generates the .kicad_pcb from a SKiDL netlist:
cd f5_turbo_generator
go build -o place_f5_turbo cmd/place_f5_turbo.go
./place_f5_turbo # → f5_turbo_v2.kicad_pcb
kicad-cli pcb export pdf f5_turbo_v2.kicad_pcb -o f5_turbo_v2.pdf \
--layers F.Cu,B.Cu,F.SilkS,F.Fab,Edge.Cuts --mode-singleSee f5_turbo_generator/README.md for details.
OrthoRoute supports headless routing via .ORP export files. See the upstream
OrthoRoute documentation for details
on the headless workflow.
import orthoroute_metal
import numpy as np
# Accelerate framework SGEMM (routed to AMX coprocessors)
orthoroute_metal.amx_sgemm_py(
m=1024, n=1024, k=1024,
alpha=1.0,
a_array=np.random.randn(1024*1024).astype(np.float32),
b_array=np.random.randn(1024*1024).astype(np.float32),
beta=0.0,
c_array=np.zeros(1024*1024, dtype=np.float32)
)OrthoRoute-Metal includes a comprehensive test suite with 29 test files and 529 tests (13 skipped — Qt) covering core infrastructure, domain models, GPU backend integration, and KiCad integration.
python -m pytest tests/ -v
# 529 passed, 13 skipped in 0.76s| Area | Test File(s) |
|---|---|
| Lattice construction | test_lattice.py |
| CSR graph integrity | test_csr_graph.py |
| Via pooling accounting | test_via_accounting.py |
| Board/layer analysis | test_board_analyzer.py, test_layer_analyzer.py |
| Domain models | test_domain_models.py |
| DRC constraints | test_drc_checker.py |
| Serialization (ORP/ORS) | test_serialization.py |
| CPU fallback | test_cpu_fallback.py |
| Configuration | test_config.py |
| Spatial hash | test_spatial_hash.py |
| Grid/real global grid | test_grid.py, test_real_global_grid.py |
| Data structures | test_data_structures.py |
| Parameter derivation | test_parameter_derivation.py |
| Portal escape | test_portal_escape.py, test_portal_escape_advanced.py |
| Pad mapping | test_pad_mapping.py, test_pad_mapping_advanced.py |
| GPU/CPU parity | test_gpu_cpu_parity.py, test_gpu_cpu_parity_advanced.py |
| PathFinder convergence | test_convergence.py, test_pathfinder_convergence.py |
| Performance benchmarks | test_performance.py, test_benchmarks.py |
| Regression suite | test_regression.py |
| KiCad end-to-end | test_kicad_e2e.py |
| KiCad file parsing | test_kicad_file_parser.py |
| KiCad geometry | test_kicad_geometry.py |
| KiCad serialization | test_kicad_serialization.py |
| KiCad layers/colors | test_kicad_layers.py |
| CUDA ↔ Metal parity | 36/36 golden tensor tests |
The project uses a local CI pipeline via OrbStack (not GitHub Actions).
# Run the full CI pipeline locally
./ci/run.shThe pipeline runs inside a Docker container and executes linting, type checking, and the full test suite.
The Metal backend produces identical outputs to the CUDA reference across all tested graph sizes. 36 out of 36 parity tests pass.
| Verification | Status |
|---|---|
| Distances (bitwise float32 match) | PASS (36/36) |
| Paths (identical node sequences) | PASS (36/36) |
| Reachable nodes (exact count match) | PASS (36/36) |
Graph sizes tested: 2,000 / 8,000 / 30,000 / 45,000 / 60,000 / 180,000 nodes.
CUDA reference outputs were captured on an RTX 2080 Ti (Vast.ai, $0.083/hr) and stored as golden tensors.
| Kernel | Purpose | Threading Model |
|---|---|---|
wavefront_expand_all |
Persistent SPFA with Delta-Stepping | 8,192 threads, SIMD block stealing |
wavefront_expand_multi |
Multi-net parallel SPFA | 2D grid (nodes x nets) |
spfa_setup_kernel |
Frontier initialization from distance array | 1 thread per node |
clear_counters |
Queue state reset between iterations | Single thread |
negotiation_kernel |
PathFinder history pressure reduction | SIMD shuffle down |
roi_extractor_mixin |
Region-of-interest distance extraction | 1 thread per node |
via_kernels |
Via cost initialization | 1 thread per via |
Metal MSL does not support atomic_fetch_min for floating-point types. A compare-and-swap (CAS) loop is used:
inline void atomic_fetch_min_float(device atomic_uint* dest, float val) {
uint old_val_uint = atomic_load_explicit(dest, memory_order_relaxed);
float old_val = as_type<float>(old_val_uint);
while (val < old_val) {
uint desired = as_type<uint>(val);
if (atomic_compare_exchange_weak_explicit(
dest, &old_val_uint, desired,
memory_order_relaxed, memory_order_relaxed)) {
break;
}
old_val = as_type<float>(old_val_uint);
}
}Apple Silicon GPU schedulers do not preempt compute threadgroups. The persistent grid must not exceed the hardware's concurrent execution capacity. On the M4 (10-core GPU), the safe limit is 16 threadgroups of 512 threads (8,192 total). Exceeding this causes an irrecoverable OS watchdog timeout.
A software grid barrier using device atomic_uint and threadgroup_barrier(mem_flags::mem_device) synchronizes all threadgroups between SPFA iterations without returning to the CPU. The full SSSP computation executes inside a single commandBuffer.commit() call.
| Document | Description |
|---|---|
| ARCHITECTURE.md | Three-layer architecture overview |
| BENCHMARK_METHODOLOGY.md | Hardware, graphs, measurement protocol |
| coordinate_system.md | How (x, y, z) maps to mm and layers |
| pathfinder_algorithm.md | PathFinder algorithm deep-dive |
| metal_kernel_internals.md | MSL code walkthrough |
| api_reference.md | Python API reference |
| tuning_guide.md | PathFinder parameter tuning |
| barrel_conflicts_explained.md | Via barrel conflict analysis |
| cloud_gpu_setup.md | Vast.ai cloud GPU setup |
| congestion_ratio.md | Routability prediction metric |
| contributing.md | Contributor guide |
| layer_compaction.md | Post-routing layer minimization |
| plugin_manager_integration.md | KiCad PCM integration |
| ORP_ORS_file_formats.md | File format specification |
| CHANGELOG.md | Version history |
| f5_turbo_generator/README.md | Demo board generator (Go) |
| NOTICE.md | Upstream attribution and license |
| Upstream OrthoRoute | Original CUDA-based OrthoRoute |
The Metal backend (all files under metal/) is licensed under the Blue Oak Model License 1.0.0. See LICENSE.md.
The upstream OrthoRoute code is licensed under the MIT License by Brian Benchoff. See NOTICE.md for the full upstream license text.
This project is a fork of OrthoRoute by Brian Benchoff. OrthoRoute is a GPU-accelerated PCB autorouter for KiCad that implements the PathFinder negotiation-based routing algorithm with CUDA/CuPy GPU acceleration.
The Metal backend was developed independently to bring GPU-accelerated PCB routing to Apple Silicon hardware. The PathFinder algorithm, board parsing, net ordering, KiCad IPC integration, and visualization code originate from the upstream OrthoRoute project.
- McMurchie, L. and Ebeling, C. "PathFinder: A Negotiation-Based Performance-Driven Router for FPGAs." ACM/SIGDA FPGA, 1995.
- Meyer, U. and Sanders, P. "Delta-Stepping: A Parallelizable Shortest Path Algorithm." Journal of Algorithms, 2003.
- Apple. "Metal Shading Language Specification." Version 3.2.
- bbenchoff. OrthoRoute Build Log.