Как настроить кластер 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,

Другие вопросы по тегам