Single-host parallelism
Parallel path search, slice chunks, and matrix multiplication, and the distinction between thread count and actual CPU usage.
On this page
Parallel search in one call
The Light / Heavy compiled library receives the caller's Rayon pool width and schedules parallel searches within one call. No outer seed loop is needed; seed specifies the base seed. See Using Heavy for a single-call example.
For example, RAYON_NUM_THREADS=8 configures eight workers. It does not reserve eight physical cores or set process affinity; actual CPU usage depends on available parallel work, OS scheduling, and other load. Internal start counts and candidate scheduling are not public return fields.
Recommended thread settings
Start Light and Heavy with eight worker threads. To invest more in search when sufficient CPU resources are available, use 16, 24, or 32 threads. The current implementation adds one independent search start for every eight threads:
RAYON_NUM_THREADS |
Independent search starts |
|---|---|
8 |
1 |
16 |
2 |
24 |
3 |
32 |
4 |
Starts use different seeds, run concurrently within one call, and are compared by the current objective. They share the overall pool; they are not two or three sequential rounds, nor is each start pinned to eight cores. seed specifies the base seed, and the library derives the others.
Set thread count before launching the program. For example, with 24 available physical cores:
RAYON_NUM_THREADS=24 python plan.py
Eight is a suggested starting configuration, not a mandatory minimum or the fastest setting for every network. If no count is set, the Rayon runtime determines it; it is not fixed at eight. More starts add search work and may improve path quality, without implying proportional acceleration of an individual search.
Units of parallel work
| Location | Parallel work | Sequential or synchronized work |
|---|---|---|
| Light / Heavy | Independent starts and search tasks within each start | Search dependencies and result aggregation |
| random_greedy | Complete search trials | Greedy steps within one trial |
| bisect | Independent partitioning trials | Parent/child calls within a recursive partition |
| order_dp | Different intervals of the same length | Progression from shorter to longer intervals |
| anneal_paths / treesa_path | Independent search chains | Local rotations within one chain |
| temper_path(s) | Replica-local searches within a round | Adjacent-replica exchange at round end, then the next round |
| Native sliced execution | Groups of slice tasks | Summation within each group and ordered combination of group sums |
| matrixmultiply mt | Threads within one GEMM | Controlled by the optional Cargo feature and thread limit |
For public low-level algorithms, the number of parallel work items bounds stage parallelism. Giving a stage more threads does not mean it continuously occupies that many cores. Standalone low-level calls do not additionally apply the independent-start scheduling rules of Light and Heavy.
Thread pools and nested parallelism
RAYON_NUM_THREADS=8 cargo run --release --bin tnpath -- \
network.json --method auto --preset heavy --seed 0
RAYON_NUM_THREADS=8 MATMUL_NUM_THREADS=1 tnexec \
--net network.json --load-path plan.json
| Control | Scope |
|---|---|
| RAYON_NUM_THREADS | Light / Heavy search, public low-level algorithms, and native slice chunks |
| MATMUL_NUM_THREADS | Threads within a GEMM when the matrixmultiply mt feature is enabled |
| CPU affinity / launcher binding | CPUs on which the process can actually run |
Multithreading both outer Rayon work and inner GEMM may create more runnable threads than available CPUs, causing oversubscription. tnexec sets MATMUL_NUM_THREADS=1 when there is more than one outer slice chunk and the caller has not set it; existing settings are retained. The default Python wheel does not enable the optional matrixmultiply thread pool.
Threads, work, and results
For low-level calls with fixed ntrials, chains, rounds, or moves, extra threads mainly change elapsed time for that fixed work. Light and Heavy may change their search workload with thread settings. Record both elapsed time and path quality when comparing thread counts; do not treat these comparisons as fixed-workload speedup automatically.
| Comparison | What to hold fixed |
|---|---|
| Thread speedup for fixed work | Entry point, trial/chain/round counts, seed, objective, and output validation |
| Light / Heavy with different resources | Record configuration, elapsed time, and final path metrics; differing workloads are not fixed-work strong-scaling tests |
| Results with max_time | External overall timeout, repetitions, and completed work; checkpoint timing depends on execution timing |
| Sliced-execution speedup | Path, sliced legs, dtype, chunk rules, and backend |
Pool width is neither physical-core count nor average CPU usage. Merely reporting RAYON_NUM_THREADS=8 does not establish that a stage used eight physical cores.
Measurements to record
Estimate average stage CPU usage as process CPU time / wall time. A ratio of 2 means an average consumption of two CPU-seconds per elapsed second in that window; it does not by itself identify physical-core usage. Waiting, memory-bandwidth limits, or insufficient work can make the ratio lower than pool width.
- Record CPU affinity and physical-core/SMT mapping, not just logical-thread count.
- Record Rayon worker count and whether matrixmultiply mt is enabled.
- Record process CPU time, wall time, RSS, and measurement start/end points.
- For standalone public low-level algorithms, record trial, chain, replica, or slice-chunk counts. Light / Heavy internal candidate records are not exposed through the public interface.
- Running multiple processes on a shared server can improve throughput; this does not imply a shorter completion time for the same task in one process.