超参数搜索并行训练简记

之前研究了一下 xgboost 超参数搜索的训练问题。有很多收获,也留下了很多问题,这里简单记录一下

我不管原理,纯粹当调库小鬼。观察发现 xgboost 训练初期显存消耗有一个 peak,之后下降进入平稳期。调参时主要关注了 boosting 树的数量,每棵树的深度,每棵树使用的 sample data 比例,sample feature 比例,learning rate。应当要加正则化的,由于我是让 ai 帮我生成的代码,ai 没加正则化,我就以为没有这个接口

  1. 迭代模型训练方法,目的是在“保证精度、不爆显存”的情况下,训练的时间尽量短。这里不考虑精度。

    进行一次超参数搜索的训练,总的计算量是一定的,那么我们希望计算的平均速度尽量快,那么时间就会少。我起初把 GPU 的使用效率和显存使用效率混为一谈,这是错误的。我现在的理解是 nvidia-smi 后的 Volatile GPU-Util 参数代表了过去一段时间 cuda core 的使用效率。

    对比多线程和多进程两种并行策略(使用 threadpool 和 processpool),我发现多进程方法,设置并发数为 2 就能保证计算效率,但是显存不会占很满;而多线程方法开并发数为 6 可以把显存几乎占满[1],但是 volatile GPU util 的均值就小 10%。这个例子说明开大并发数、显存几乎占满并不一定代表减少计算时间。

  2. 由于最初被 AI 误导,在提升并发数和提升显存占用率上花费了很多时间,得到了一些实践结果记录如下:

    • 多用 torch.cuda.empty_cache() 来清空显存中的缓存。一开始以为数据矩阵的显存开销是 8k mib,后来发现是 torch.tensor.matmul 中间变量消耗大量显存,清理 cache 后 train_X 在 GPU 上占 <400 mib,着实震撼。

      当我认为数据矩阵开销是 8k mib 时,我想既然超参数搜索的 train 一样,没必要 copy,我让他们访问显卡上相同的内存不就行了吗? xgboost 训练需要把 X,y 构建成 dmatrix,按照我对 python 内存管理的理解,我把 dmatrix 传给函数,就可以避免每次训练重复构建同样的 dmatrix。但是这个对象不能作为 threadpoolexecutor 和 processpoolexecutor 中能传递的参数,于是 ai 帮我实现了一个方法,这个方法基于多进程的并行,它在显存开销上的表现不如直接用 xgboost.train。白忙活了。

      我猜多线程并行能少 copy 几次 X,y 来实现了显存的节约,但是跑得慢了[2],遗憾离场

    • 在上游的训练参数相同的情况下,使用原生 xgboost.train 的 api,比使用 sklearn 中的 model.fit api 要节省一部分显存,原因不明。这部分多余的开销在多进程训练中 max_worker 很大时(如 40)会有较大影响。

    • xgboost api 可以设置 nthread 或者 n_jobs,它们的变化影响不明,只知道对平稳态的显存开销似乎影响有限。

[1] 这里说的几乎占满是指 peak 不爆。我设计了一些简单的花活,比如多线程同时 peak 不如轮流加入训练,每次放两个进程 peak,剩下的进程 sleep 若干时间。这个花活抛砖引玉,如果我知道每个 mission 过多久能到 peak,peak 的开销是多少,可以设计一个在开始训练时 query 一下当时的显存开销,再做决定。

[2] 我的分析是 python 的线程池管理并没有那么出色?每个线程访问 train_X 时都得给它上 mutex,于是进程之间互相阻塞,耗时较高。c++ 中有一种先进的 shared_lock,运行读的 thread 同时运行,但是会阻塞写入的进程,建议 concurrent 开发者快速整改速速引入。

posted @ 2025-04-07 11:41  yspm  阅读(90)  评论(2)    收藏  举报