【问题标题】:Parallel processing in R with H2O在 R 中使用 H2O 进行并行处理
【发布时间】:2023-03-15 13:04:02
【问题描述】:

我正在设置一段代码,以使用foreach 并行处理我的数据中 N 组的一些计算。

我有一个涉及调用 h2o.gbm 的计算。

在我当前的顺序设置中,我使用了大约 70% 的 RAM。

如何在并行代码中正确设置 h2o.init()?我担心使用多核时可能会耗尽内存。

我的 Windows 10 机器有 12 个内核和 128GB 的​​ RAM。

这样的伪代码会起作用吗?

library(foreach)
library(doParallel)

#setup parallel backend to use 12 processors
cl<-makeCluster(12)
registerDoParallel(cl)

#loop
df4 <-foreach(i = as.numeric(seq(1,999)), .combine=rbind) %dopar% {
  df4 <- data.frame()
  #bunch of computations
  h2o.init(nthreads=1, max_mem_size="10G")
  gbm <- h2o.gbm(train_some_model)
  df4 <- data.frame(someoutput)
   }

fwrite(df4, append=TRUE)

stopCluster(cl)

【问题讨论】:

  • 这是两种并行计算模式,显式和隐式。可以参考博客here
  • 谢谢@Patric。你对这个问题有什么建议?显式还是隐式?
  • 我想你想混合这两种模式。如果不是真的需要,尤其是在本地机器上,我不建议这样做,因为它会导致潜在的功能和性能问题,并且很难调试。
  • 而且隐式解决方案将非常容易和稳定,即使它可能无法达到最高性能。我建议你先试试。
  • @Patric 你也愿意写答案吗?

标签: r memory foreach parallel-processing h2o


【解决方案1】:

根据 Erin LeDell 的回答,我只想补充一点,在许多情况下,一个不错的实用解决方案可能介于 #1 和 #3 之间。为了提高 CPU 利用率并仍然节省 RAM,您可以并行使用多个 H2O 实例,但它们每个都可以使用多个内核,而与仅使用一个内核运行更多实例相比,它们不会有太大的性能损失。

我在 36 核服务器上使用相对较小的 40MB 数据集(240K 行,22 列)进行了实验。

  • 案例 1:使用所有 36 个内核 (nthreads=36) 估计 120 个 GBM 模型(默认 超参数)顺序。

  • 案例 2:使用 foreach 在这台机器上运行 4 个 H2O 实例,每个 使用 9 个核心依次估计 30 个 GBM 默认模型(总计 = 120 个估计)。

  • 案例 3:使用 foreach 在这台机器上运行 12 个 H2O 实例,每个 使用 3 个核心依次估计 10 个 GBM 默认模型(总计 = 120 个估计)。

在此数据集上使用 36 个内核来估计单个 GBM 模型的效率非常低。案例 1 中的 CPU 利用率大幅上升,但平均低于 50%。因此,一次使用多个 H2O 实例肯定会有所收获。

  • 运行时案例 1:264 秒
  • 运行时案例 2:132 秒
  • 运行时案例 3:130 秒

鉴于从 4 个 H2O 实例到 12 个 H2O 实例的微小改进,我什至没有运行 36 个 H2O 实例,每个实例都使用一个内核并行运行。

【讨论】:

    【解决方案2】:

    您的代码当前设置方式不是最佳选择。我了解您正在尝试做什么——并行执行一堆 GBM(每个都在一个单核 H2O 集群上),这样您就可以最大限度地利用机器上 12 个内核的 CPU 使用率。但是,您的代码将尝试在同一单核 H2O 集群上并行运行 foreach 循环中的所有 GBM。您一次只能从一个 R 实例连接到一个 H2O 集群,但是 foreach 循环将创建一个新的 R 实例。

    与 R 中的大多数机器学习算法不同,H2O 算法都支持多核,因此训练过程已经在算法级别并行化,无需像 foreach 这样的并行 R 包.

    您有几个选择(#1 或 #3 可能是最好的):

    1. 在脚本顶部设置 h2o.init(nthreads = -1) 以使用所有 12 个内核。将foreach() 循环更改为常规循环,并按顺序训练每个 GBM(在不同的数据分区上)。尽管不同的 GBM 是按顺序训练的,但每个单个 GBM 将在 H2O 集群中完全并行化。
    2. 在脚本顶部设置h2o.init(nthreads = -1),但保留foreach() 循环。这应该一次运行所有 GBM,每个 GBM 在所有内核上并行化。这可能会使 H2O 集群不堪重负(这实际上并不是 H2O 的使用方式)并且可能比 #1 慢一些,但是如果不知道数据的大小和分区的数量就很难说想继续训练。如果您已经将 70% 的 RAM 用于单个 GBM,那么这可能不是最佳选择。
    3. 您可以更新您的代码以执行以下操作(最类似于您的原始脚本)。这将保留您的 foreach 循环,在您机器上的不同端口创建一个新的 1 核 H2O 集群。见下文。

    更新的 R 代码示例,它使用 iris 数据集并以 data.frame 的形式返回 iris 的预测类:

    library(foreach)
    library(doParallel)
    library(h2o)
    h2o.shutdown(prompt = FALSE)
    
    #setup parallel backend to use 12 processors
    cl <- makeCluster(12)
    registerDoParallel(cl)
    
    #loop
    df4 <- foreach(i = seq(20), .combine=rbind) %dopar% {
      library(h2o)
      port <- 54321 + 3*i
      print(paste0("http://localhost:", port))
      h2o.init(nthreads = 1, max_mem_size = "1G", port = port)
      df4 <- data.frame()
      data(iris)
      data <- as.h2o(iris)
      ss <- h2o.splitFrame(data)
      gbm <- h2o.gbm(x = 1:4, y = "Species", training_frame = ss[[1]])
      df4 <- as.data.frame(h2o.predict(gbm, ss[[2]]))[,1]
    }
    

    为了判断哪个选项最好,我会尝试在几个数据分区(可能是 10 到 100 个)上运行它,看看哪种方法的扩展性最好。如果您的训练数据很小,则 #3 可能会比 #1 更快,但总的来说,我认为 #1 可能是最具可扩展性/稳定性的解决方案。

    【讨论】:

    • 既然doParallel中的每个worker都是一个独立的进程,那这段代码不是会创建12个独立的h2o实例并且每个进程只能连接到该进程中创建的h2o实例吗?
    • 我会在少数组上做一个介于 1 和 2 之间的基准测试,以尝试估计哪个组在整个组中效果最好。由于 GBM 模型可能很大,我担心您可能会用完 RAM,因此我还建议将每个 GBM 保存到磁盘并在循环内从 H2O 集群中删除模型和相关的训练集。
    • @mauna 你提出了一个很好的观点。我已经尝试过了,它似乎确实有效,所以我将把我的#3 选项更新为这个选项(而不是使用 bash 脚本自动生成一堆 R 脚本,然后单独启动它们)。
    • 另外,我认为选项#3 只能在 Windows 平台上按预期工作,因为类 UNIX 系统使用 fork(),因此将与父进程共享资源。你怎么看?
    • @mauna 从理论上讲,每个解决方案都会有所帮助。但在实践中,它非常依赖硬件和问题。我非常同意@Erin,#1 is probably the most scalable/stable solution
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-02-13
    • 2015-03-26
    • 1970-01-01
    • 2011-11-06
    • 1970-01-01
    • 2011-11-12
    • 2018-09-01
    相关资源
    最近更新 更多