Как настроить кластер doSNOW и SOCK с помощью планировщика Torque/MOAB?
В продолжение /questions/42225063/razreshit-kazhdomu-rabotniku-registrirovatsya-i-rasprostranyat-podzadachi-drugim-rabotnikam, как лучше всего подключаться doSNOW и SOCK кластеризовать в Torque/MOAB планировщик, чтобы избежать сродства процессора во внутреннем параллельном цикле, который обрабатывает некоторую часть кода внешнего параллельного цикла?
Исходя из ответа Стива на этот вопрос, базовый код без вмешательства в планировщик может быть:
library(doSNOW)
hosts <- c('host-1', 'host-2')
cl <- makeSOCKcluster(hosts)
registerDoSNOW(cl)
r <- foreach(i=1:4, .packages='doMC') %dopar% {
registerDoMC(2)
foreach(j=1:8, .combine='c') %dopar% {
i * j
}
}
stopCluster(cl)
1 ответ
Torque всегда создает файл, содержащий имена узлов, которые были выделены для вашей работы Moab, и передает путь этого файла к вашей работе через PBS_NODEFILE
переменная окружения. Имена узлов могут быть перечислены несколько раз, чтобы указать, что он выделил несколько ядер для вашей работы на этом узле. В этом случае мы хотим запустить работника кластера для каждого уникального имени узла в PBS_NODEFILE
, но следите за количеством распределенных ядер на каждом из этих узлов, чтобы мы могли указать правильное количество ядер при регистрации doMC
,
Вот функция, которая читает PBS_NODEFILE
и возвращает фрейм данных с выделенной информацией об узле:
getnodes <- function() {
f <- Sys.getenv('PBS_NODEFILE')
x <- if (nzchar(f)) readLines(f) else rep('localhost', 3)
as.data.frame(table(x), stringsAsFactors=FALSE)
}
Возвращенный фрейм данных содержит столбец с именем "x" из имен узлов и столбец с именем "Freq" соответствующих подсчетов ядер.
Это упрощает создание и регистрацию кластера SOCK с одним рабочим на каждый уникальный узел:
nodes <- getnodes()
cl <- makeSOCKcluster(nodes$x)
registerDoSNOW(cl)
Теперь мы можем легко выполнить foreach
Цикл с одной задачей на одного работника, но не так просто передать правильное количество выделенных ядер каждому из этих работников, не завися от некоторых деталей реализации обоих snow
а также doSNOW
в частности, касающиеся осуществления clusterApplyLB
функция используется doSNOW
, Конечно, это легко, если вам случится знать, что количество распределенных ядер одинаково на каждом узле, но сложнее, если вы хотите общее решение проблемы.
Одно (не очень элегантное) общее решение состоит в том, чтобы назначить количество распределенных ядер глобальной переменной для каждого из работников через снег. clusterApply
функция:
setcores <- function(cl, nodes) {
f <- function(cores) assign('allocated.cores', cores, pos=.GlobalEnv)
clusterApply(cl, nodes$Freq, f)
}
setcores(cl, nodes)
Это гарантирует, что значение переменной "alloc.cores" для каждого из рабочих равно числу раз, когда этот узел появился в PBS_NODEFILE
,
Теперь мы можем использовать эту глобальную переменную при регистрации doMC
:
r <- foreach(i=seq_along(nodes$x), .packages='doMC') %dopar% {
registerDoMC(allocated.cores)
foreach(j=1:allocated.cores, .combine='c') %dopar% {
i * j
}
}
Вот пример сценария задания, который можно использовать для выполнения этого сценария R:
#!/bin/sh
#PBS -l nodes=4:ppn=8
cd "$PBS_O_WORKDIR"
R --slave -f hybridSOCK.R
Когда это подается через qsub
Команда R скрипт создаст кластер SOCK с четырьмя рабочими, и каждый из этих рабочих выполнит внутренний foreach
цикл с использованием 8 ядер. Но поскольку код R является общим, он должен делать правильные вещи независимо от ресурсов, запрашиваемых через qsub
,