mlr包在HPC多核心运行时内存耗尽及可复现性问题咨询
解决mlr在Torque HPC上的并行问题:内存耗尽与复现性
让我来帮你拆解和解决这两个头疼的问题——我在Torque集群上用mlr做并行调优时也踩过类似的坑。
先搞清楚问题根源
1. Multicore模式下内存耗尽的原因
Torque这类集群调度系统的核心逻辑是:它会给你的作业分配固定数量的CPU核心(通过PBS脚本里的nodes=1:ppn=N参数),但parallel::detectCores()在这种环境下会返回整个服务器刀片的总核心数(你的XL230a Gen9有超多核心),而不是Torque给你分配的配额。这就导致mlr/parallelMap启动了远超作业允许的线程数,最终把内存吃光。
2. Socket模式无法复现结果的原因
Socket模式的每个进程都是独立启动的,不像Multicore那样通过fork继承父进程的RNG状态。你现在的代码里每次循环都硬设set.seed(1),反而会打乱并行RNG的正确传播,导致结果不可复现。
针对性解决方案
方案1:修复Multicore模式的内存耗尽问题
不要用detectCores(),而是从Torque的环境变量里获取真实分配的核心数:
- Torque会通过
PBS_NUM_PPN(单节点核心数)和PBS_NUM_NODES(节点数)传递配额,总核心数就是两者的乘积 - 加个兜底逻辑,在非集群环境(比如本地测试)下退回到
detectCores()
修改并行启动的代码:
# 从Torque环境变量读取分配的核心数 num_cores <- as.integer(Sys.getenv("PBS_NUM_PPN")) * as.integer(Sys.getenv("PBS_NUM_NODES")) # 兜底:如果环境变量不存在(本地测试),用detectCores if (is.na(num_cores) || num_cores < 1) { num_cores <- parallel::detectCores() } # 启动多核心并行,建议关闭预调度避免一次性fork太多进程 parallelStartMulticore(num_cores, mc.preschedule = FALSE)
另外,一定要在你的PBS作业脚本里明确指定核心数,比如:
#PBS -l nodes=1:ppn=8 # 申请1个节点,8个核心 #PBS -l mem=16G # 配套申请足够的内存
方案2:让Socket模式的结果可复现
要正确设置并行RNG的种子传播,步骤如下:
- 在启动并行之前设置全局种子,用L'Ecuyer的并行RNG算法(适合多进程)
- 在
parallelStartSocket里指定同一个种子,让所有子进程继承正确的RNG状态 - 去掉循环内部的
set.seed(1)——这会让所有迭代用同一个种子,反而破坏复现性
修改后的完整代码示例:
library(mlr) library(parallelMap) # 全局设置并行RNG种子,用L'Ecuyer算法 set.seed(123, kind = "L'Ecuyer-CMRG") M <- data.frame(x = runif(1e2), y = as.factor(rnorm(1e2) > 0)) # 获取Torque分配的核心数 num_cores <- as.integer(Sys.getenv("PBS_NUM_PPN")) * as.integer(Sys.getenv("PBS_NUM_NODES")) if (is.na(num_cores) || num_cores < 1) { num_cores <- parallel::detectCores() } # 启动Socket并行,指定种子 parallelStartSocket(num_cores, seed = 123) plyr::l_ply(seq(100), function(x) { message("Iteration number: ", x) tsk <- makeClassifTask(data = M, target = "y") num_ps <- makeParamSet( makeIntegerParam("ntree", lower = 10, upper = 50), makeIntegerParam("nodesize", lower = 1, upper = 5) ) ctrl <- makeTuneControlGrid(resolution = 2L, tune.threshold = TRUE) lrn <- makeLearner("classif.randomForest", predict.type = "prob") rdesc <- makeResampleDesc("CV", iters = 2L, stratify = TRUE) res <- tuneParams( lrn, task = tsk, resampling = rdesc, par.set = num_ps, measures = list(auc), control = ctrl) lrn.optim <- setHyperPars(lrn, par.vals = res$x) m <- train(lrn.optim, tsk) pred_rf <- predict(m, newdata = M) pred_rf } ) parallelStop()
额外提醒:你也可以在tuneParams的控制参数里指定种子,进一步确保调优过程的复现性:
ctrl <- makeTuneControlGrid(resolution = 2L, tune.threshold = TRUE, seed = 123)
额外注意事项
- Packrat同步:确保你的packrat环境在所有集群节点上完全一致,版本不同的依赖包可能会导致奇怪的问题
- Torque资源限制:在PBS脚本里一定要明确内存配额,避免作业因为内存不足被杀死
- 避免嵌套并行:mlr的
tuneParams本身已经是并行的,不要在循环里再套其他并行逻辑,否则会加剧资源消耗
内容的提问来源于stack exchange,提问作者AnonQuest
相关产品推荐
相关产品推荐

