用多核加速批量仿真¶
中级
前置阅读: 批量数据集生成
一行 YAML 配置¶
output:
parallel:
n_jobs: -1 # -1 = 全部 CPU 核
backend: loky # loky | multiprocessing | threading
batch_size: 4 # 每 batch 任务数(loky 默认 auto)
n_jobs 怎么选¶
| 值 | 含义 |
|---|---|
1 |
串行(开发调试时用) |
-1 |
全部逻辑 CPU |
auto |
pylectra 推荐值:min(cpu_count - 1, 16) |
4 |
显式指定 4 个 worker |
经验:
- 16 核以下机器,
-1通常最快 - 16 核以上,
auto(上限 16)避免边际收益递减 - 4 GB / 8 GB 小内存机器:每个 worker 占 ~500 MB;16 核同时跑会爆内存。显式设
n_jobs: 4或8
from pylectra.hardware import recommend_n_jobs, summary
print(summary()) # 看自己机器的硬件
print(recommend_n_jobs()) # 看推荐值
backend 选哪个¶
loky(默认推荐)¶
joblib 的进程池实现,Windows / macOS / Linux 都能用。每个 worker 独立 Python 进程,绕开 GIL,CPU-bound 任务(pylectra 是这类)最快。
multiprocessing¶
Python 标准库 multiprocessing。功能与 loky 类似,但 fork 实现细节有差异。一般别用——loky 修了很多 fork 在 Windows / macOS 下的坑。
threading¶
线程池。受 GIL 限制,对 pylectra 这种 CPU-bound 任务没有加速——即使设 n_jobs=8 也只用 1 个 CPU。
例外:如果你的 worker 调用了释放 GIL 的 C 扩展(numpy 大矩阵 BLAS、scipy 求解器、torch),threading 反而能并行。pylectra 的 ODE 主路径主要是 Python,不要用 threading。
Windows 非 ASCII 用户名问题¶
Windows 中文用户名(如 龙宗加)+ joblib loky 后端 = UnicodeEncodeError。
multiprocessing.resource_tracker 用 ASCII 编码 IPC 消息,遇到 C:\Users\龙宗加\AppData\Local\Temp\... 会炸。
解决:改 JOBLIB_TEMP_FOLDER 到一个全 ASCII 路径:
# Windows cmd
set JOBLIB_TEMP_FOLDER=D:\joblib_tmp
python -m pylectra run examples/batch_case39.yaml
# Windows PowerShell
$env:JOBLIB_TEMP_FOLDER = "D:\joblib_tmp"
# Linux / macOS(一般不会遇到这问题)
export JOBLIB_TEMP_FOLDER=/tmp/joblib_tmp
或在 Python 里:
import os
os.environ["JOBLIB_TEMP_FOLDER"] = r"D:\joblib_tmp"
from pylectra.run import run
run("examples/batch_case39.yaml")
batch_size 调优¶
batch_size 决定每次发给 worker 的任务块大小。
- 默认 /
"auto":joblib 自适应(通常合理) - 太小(如
1):调度开销大、worker 闲置 - 太大(如
100):负载不均(最慢的那个 worker 拖整体)
如果你跑很大批量(10 000+ 样本),手动设 batch_size: 8 或 16 通常比 auto 快 5–10%。
n_jobs vs 内存¶
每个 worker:
- 独立 Python 解释器(~50 MB)
- 复制 numpy 大数组(如 case 数据 ~1 MB)
- joblib 内部 buffers(~10 MB)
- 仿真本身的状态(case 越大占越多,case39 ~30 MB)
case39,n_jobs=8 → 总占 ~250 MB。 case2000+,n_jobs=8 → 可能爆 8 GB。
OOM 救急:
验证并行真在加速¶
跑 5 样本:
# 串行
time python -m pylectra run examples/batch_case39.yaml \
-O 'output={"parallel": {"n_jobs": 1}}'
# 并行
time python -m pylectra run examples/batch_case39.yaml \
-O 'output={"parallel": {"n_jobs": -1}}'
8 核机器上典型差距:串行 ~50 s vs 并行 ~10 s(约 5× 加速;理想 8× 因 joblib 启动 + 写盘 IO)。
在共享集群上¶
学校 / 公司 HPC 集群上,每个 worker 不一定独占一个物理核——其他用户也在抢。
- SLURM / PBS 任务申请
--cpus-per-task=8后,joblibn_jobs=-1看到的是 8 个(不是节点全部) - 集群禁止 fork 时改
backend: threading—— 放弃并行加速,但能避免被 admin 杀掉 dask比 joblib 更适合分布式集群——pylectra 不内建支持,但因为SingleRunner是 picklable,外面套一层 dask 不难
跑大批量 + 持久化重要 worker 信息¶
import joblib
joblib.parallel_config(backend="loky",
n_jobs=8,
temp_folder="/scratch/joblib")
# 之后所有 batch 调用继承这个配置
常见疑问¶
Q:为什么 4 核机 n_jobs=4 只比 n_jobs=2 快 30%?¶
单个仿真本身已经吃了 60% CPU(numpy / scipy 内部 BLAS 多线程)。 关掉 BLAS 的内部多线程能让并行更线性:
export OPENBLAS_NUM_THREADS=1
export MKL_NUM_THREADS=1
python -m pylectra run examples/batch_case39.yaml
Q:进程一直在 "Joblib starting" 卡住¶
第一次启动 loky worker 会 import 整个 pylectra(含所有插件 + numpy / scipy)——10–30 秒正常。 只有第一次慢,后续任务复用同一组 worker。
如果 5 分钟还卡住,可能是某个插件 import 时报错——加 -O 'verbose=2' 看详细日志。
Q:能跨机器分布吗?¶
joblib 不行——本机限定。要跨机器:
- dask.distributed:把 SingleRunner 任务塞进 dask client.submit
- Ray:类似 dask
- Slurm 数组任务:每个 array task 跑一个独立 batch,最后合并 metadata