charent/ChatLM-mini-Chinese

多卡情况下,同一份数据集会加载多次吗

shinerdeng opened this issue · 6 comments

非常感谢您贡献如此完整的代码!
请教一个问题,单机多卡环境,我在dataset去除shuffer,get_item打印返回的prompt,发现不同的进程会打印相同的prompt,是不是就代表开了几个GPU,同一条数据就会重复训练多次。batch_size=12,num_work=4
启动命令accelerate launch --multi_gpu --num_processes 2 pre_train.py
image
image

不同的进程代表不同的GPU,不同GPU打印的prompt应该是不一样的。

如果你使用MyDataset的话,多GPU的情况下需要设置keep_in_memory=True,否则分布式数据分发不会生效。你拉一下最新的代码,已经强制校验了,pre_train.py改用huggingface datasets了。

多GPU下,__getitem__方法通过index去获取那一条数据,迭代器yield是为单GPU大数据集设计的。

image
试了可以分片了。是accelerate 帮我拆分了index吧。
多进程情况下,self.data = parquet_table.to_pandas()这行是不是执行了多次,导致同一个文件在内存里保存了多份?

是,accelerate自动拆分index。

是的,多进程情况下,内存中是会有多份(Python多进程的锅),可以先将文本tokenize转换为uint16的token_id用保存(词表小于65535可用uint16,否则用uint32),再读取进来,能省不少内存。我当时没这样做是为了方便debug😂。

如果还想要多进程继续减少内存使用,可以考虑将数据保存为内存映射文件.arrow,而不是.parquet,但是读取会消耗硬盘随机读取的时间。不想手错繁琐的.arrow文件读写,可以直接使用huggingface的datasets库(你拉最新代码pre_train.py是用的就是datasets库了)。如果是小数据集(或者内存可以放下)建议放在内存,加快读取速度,datasets库的load_dataset方法keep_in_memory参数决定是否将数据集放入内存。

感谢您的建议。目前是在单机多卡环境,把大文件直接拆成和gpu数量一样,按rank各加载各的。这样一换环境就要重新生成文件不灵活。考虑到内存不是无限的而训练集可能超大,能不能不save和load模型,动态切换dataset呢,比如训练到一定程度就加载下一个文件。。这样对内存就没那么多要求了。

可以做到动态切换dataset,不过得自己手搓了,但是往各个机器拷贝数据集的步骤少不了。

一个不成熟的想法:先把数据集分割为很多文件(也可以是很多个不同数据集),可以参考 MyDataset的写法,继承Dataset,然后重写__getitem__方法,根据传进来的index决定去加载某个文件(dataloader不能打乱数据,index是按顺序传进来的),比如index在[1000, 2000]加载B数据集并放在内存中,之后index在[1000, 2000]就直接读内存的数据,如果不在[1000, 2000]就加载另外一个数据集进来。分桶的概念,做个index、rank和桶的映射关系表就好了。

训练到一定程度就加载下一个文件,得自己手搓训练的for循环(huggingface的trainer callback也行)+Sampler(用来接收切换数据集的信号,并传递到getitem方法实现数据集切换)吧,比如根据损失、ppl情况切换数据集。

谢谢,官方没提供这样的例子的话手搓确实比较麻烦。