多GPU运行HuggingFace Dataset.map设with_rank=True时rank参数为None
问题描述
在多GPU环境下对HuggingFace Dataset调用Dataset.map()方法时,参照官方指引编写如下代码:
def translate(example, rank, model): os.environ["CUDA_VISIBLE_DEVICES"] = str(rank % torch.cuda.device_count()) example['premise'] = model.translate(example['premise']) return example ds_map = ds.map(translate, with_rank=True, fn_kwargs= {'model':model})
代码运行时触发如下报错:
TypeError: unsupported operand type(s) for %: 'NoneType' and 'int'
报错指向映射函数translate内接收到的rank参数值为None,需要对应的问题排查指引与可落地方案。
问题原因
with_rank=True仅在Dataset.map()启用多进程并行(即显式传入num_proc参数且值大于1)时,才会向处理函数传入非空的进程rank值。未设置num_proc时,map()默认单进程运行,不会传入rank参数,因此函数接收到的rank为None,触发类型错误。
除此之外原代码还有两个隐性问题:
- 在映射函数执行阶段设置
CUDA_VISIBLE_DEVICES无效:该环境变量必须在进程启动后、CUDA上下文初始化前设置才会生效,函数执行时CUDA上下文大概率已经完成初始化,修改环境变量无法改变当前进程的可见GPU。 - 通过
fn_kwargs传入主进程加载的模型存在兼容风险:主进程加载的模型绑定了主进程的CUDA上下文,多进程场景下拷贝到子进程使用时极易触发CUDA设备不匹配、显存泄漏甚至段错误。
排查与修复步骤
- 首先补全
num_proc参数,将值设置为可用GPU的总数量,让rank参数可以正常传入:
import os import torch from datasets import load_dataset # 先统计可用GPU数量 num_gpus = torch.cuda.device_count() # 错误写法:缺少num_proc,导致rank为None # ds_map = ds.map(translate, with_rank=True, fn_kwargs= {'model':model}) # 正确基础配置 ds_map = ds.map( translate, with_rank=True, num_proc=num_gpus, # 必须显式指定,开启多进程后with_rank才会生效 fn_kwargs={"model": model} )
- 重构GPU绑定与模型加载逻辑,通过
map()的initializer参数在子进程启动阶段完成GPU绑定和模型初始化,从根源规避CUDA上下文冲突问题:
def process_initializer(rank): # 子进程启动后第一时间设置可见GPU,此时CUDA尚未初始化,配置可正常生效 os.environ["CUDA_VISIBLE_DEVICES"] = str(rank) global proc_model # 每个子进程独立加载模型到当前绑定的GPU,避免跨进程CUDA上下文冲突 proc_model = load_your_translation_model().to("cuda:0") def translate(example): example["premise"] = proc_model.translate(example["premise"]) return example if __name__ == "__main__": num_gpus = torch.cuda.device_count() ds = load_dataset("your_dataset_name", split="train") # 多进程处理逻辑必须放在main作用域下,避免子进程递归启动 ds_map = ds.map( translate, num_proc=num_gpus, with_rank=True, initializer=process_initializer, # 每个子进程启动时先执行初始化函数 )
- 兼容项排查:Windows系统默认使用
spawn多进程启动模式,对CUDA多进程支持较差,建议优先在Linux环境下运行上述多GPU处理逻辑;如果必须在Windows运行,需要确保所有CUDA相关导入、模型加载逻辑都放在初始化函数或子进程作用域内,不要在主进程提前初始化CUDA上下文。
内容的提问来源于stack exchange,提问作者KoNull
相关产品推荐
相关产品推荐

