---
title: "单机并行"
description: "说明路径搜索、切片任务分块和矩阵乘法的并行方式，以及线程数与实际 CPU 使用量的区别。"
eyebrow: "并行与分布式"
---

## 一次调用中的并行搜索 {#auto-starts}

Light / Heavy 的编译库接收调用方的 Rayon 线程池宽度，并在一次调用内安排并行搜索。无需在调用外再写种子循环；`seed` 指定本次搜索的基础种子。[使用 Heavy](/docs/tutorial-heavy#run)给出单次调用示例。

例如 `RAYON_NUM_THREADS=8` 设置 8 个工作线程。它不预留 8 个物理核，也不固定进程 affinity；实际 CPU 使用量由可并行任务、系统调度和其他负载共同决定。内部起点数与候选调度不是公开返回字段。

## 推荐线程配置 {#recommended-threads}

Light 和 Heavy 建议从 **8 个工作线程**开始。需要增加搜索投入且有足够 CPU 资源时，可使用 16、24 或 32 个线程。当前实现按每 8 个线程增加一个独立搜索起点：

| `RAYON_NUM_THREADS` | 独立搜索起点数 |
| --- | --- |
| `8` | 1 |
| `16` | 2 |
| `24` | 3 |
| `32` | 4 |

各起点使用不同种子，由一次调用并行调度，最后按本次优化目标选择结果。它们共享总线程池，不是顺序执行两轮或三轮，也不为每个起点固定绑定 8 个核心。基础种子通过 `seed` 指定，其余种子由库派生。

在程序启动前设置线程数。例如，有 24 个可用物理核心时，可设置：

```bash
RAYON_NUM_THREADS=24 python plan.py
```

8 是建议的起始配置，不是强制的最小值，也不是对所有网络都最快的配置。未显式设置线程数时，由 [Rayon 运行时](https://docs.rs/rayon/latest/rayon/struct.ThreadPoolBuilder.html#method.num_threads)决定，不固定为 8。更多起点增加搜索工作量，可能改善路径质量，不表示单次搜索按比例加速。

## 并行单位 {#units}

| 位置 | 可并行工作 | 仍需顺序或同步的部分 |
| --- | --- | --- |
| Light / Heavy | 多个独立起点及每个起点内的搜索任务 | 搜索过程中的依赖与结果汇总 |
| random\_greedy | 多次完整搜索 | 单次搜索中的贪心步骤 |
| bisect | 多次独立划分 | 一次递归划分的父子调用 |
| order\_dp | 长度相同的不同区间 | 区间长度从小到大推进 |
| anneal\_paths / treesa\_path | 多条独立搜索链 | 单条链内的局部旋转 |
| temper\_path(s) | 同一轮中各副本的局部搜索 | 轮末相邻副本交换，以及下一轮的开始 |
| 原生切片执行 | 多组切片任务 | 每组内部的求和顺序，以及各组部分和的合并顺序 |
| matrixmultiply mt | 单个 GEMM 内部线程 | 由可选 Cargo feature 和线程上限控制 |

对公开低层算法，并行对象的数量给出阶段并行度上限；给一个阶段更多线程，不代表它能持续占用同样数量的 CPU 核。单独调用低层算法时，不会额外采用 Light 和 Heavy 的独立起点调度规则。

## 线程池与嵌套并行 {#thread-controls}

```bash
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
```

| 控制项 | 作用域 |
| --- | --- |
| RAYON\_NUM\_THREADS | Light / Heavy 路径搜索、公开低层算法和原生切片任务分块 |
| MATMUL\_NUM\_THREADS | 启用 matrixmultiply `mt` feature 时的单个 GEMM 内线程 |
| CPU affinity / launcher binding | 进程实际可以运行在哪些 CPU 上 |

外层 Rayon 与内层 GEMM 同时使用多线程，可能使可运行线程数超过可用 CPU 数量，产生过量订阅（oversubscription）。`tnexec` 在外层切片分块多于 1、且调用者未设置 `MATMUL_NUM_THREADS` 时，将它设为 1；已有设置不会被覆盖。默认 Python wheel 不启用 matrixmultiply 的可选线程池。

## 线程数、工作量和结果 {#work-vs-threads}

对低层固定 `ntrials`、`chains`、`rounds` 或 `moves` 的调用，增加线程主要改变完成这些工作所需的实际时间。Light 和 Heavy 的搜索工作量可能随线程设置变化，因此比较不同线程数时，应同时记录实际耗时与路径质量，不能直接视为固定工作量的线程加速。

| 比较问题 | 需要固定的量 |
| --- | --- |
| 固定工作量的线程加速 | 同一入口、trial/chain/round 数、seed、objective 和输出验证 |
| 不同资源配置下的 Light / Heavy 结果 | 记录配置、实际耗时和最终路径指标；工作量不同时，不能作为固定工作量的强缩放测试 |
| 设置 max\_time 后的结果 | 外部总时间上限、重复次数和实际完成工作量；检查点触发时间会受运行时序影响 |
| 切片执行加速 | 同一 path、sliced legs、dtype、chunk 规则和后端 |

线程池宽度不是物理核数，也不是实际平均用核。只写 `RAYON_NUM_THREADS=8` 不能证明阶段使用了 8 个物理核。

## 并行运行时需要记录的信息 {#measurement}

阶段平均 CPU 使用量可以用 `process CPU time / wall time` 估计。例如比值为 2 表示该窗口内平均每秒消耗 2 个 CPU 秒，不能单独证明使用了多少物理核。等待、内存带宽瓶颈或任务不足都会使该比值低于线程池宽度。

- 记录 CPU affinity、物理核与 SMT 映射，而不只记录逻辑线程数。
- 记录 Rayon worker count，以及是否启用 matrixmultiply mt。
- 记录进程 CPU 时间、墙钟时间、RSS，以及测量的起止时刻。
- 单独测试公开低层算法时，记录 trial、chain、replica 或 slice chunk 的数量；Light / Heavy 的内部候选记录不通过公开接口返回。
- 共享服务器上同时运行多个进程，可以提高单位时间内完成的任务数；这不等于缩短单个进程完成同一任务所需的时间。
