如果您明确选择了快速(Rust代码)的标记化程序,那么您可能有自己的理由。在处理大型数据集时,基于Rust的标记化程序可以更快地处理数据,并且可以通过在标记化程序创建期间设置"use_fast"选项来明确调用它们。现在几乎所有的HF模型都带有此选项。尽管从警告消息中不太明显,TOKENIZERS_PARALLELISM是一个环境变量而不是标记化程序超参数。将其设置为False就可以解决问题,但正如上面的一些评论所示,人们对此变化的影响存在困惑。例如,这是否会影响模型级别的并行性?让我们看看Rust代码,以了解默认行为以及如果关闭它会发生什么来解决问题。
https://docs.rs/tokenizers/latest/src/tokenizers/utils/parallelism.rs.html
在大多数情况下,我们(终端用户)不会显式地将TOKENIZERS_PARALLELISM设置为True或False。对于所有这些情况,分词器代码都会假定它为TRUE。我们可以通过将其设置为False来显式禁用它。但是,您可以看到,在这种情况下,代码使迭代器序列化。即使您不将此环境变量设置为False,执行代码本身也会在遇到Python forks时这样做(这就是导致首先显示警告的原因)。我们能避免这种情况吗?
让我们退一步看看警告本身。
"当前进程在使用并行处理后被分叉。禁用并行处理以避免死锁...要禁用此警告,您可以:-如果可能,请避免在分叉之前使用tokenizers
- 显式设置环境变量TOKENIZERS_PARALLELISM =(true | false)"
只有使用HF的FastTokenizers时才会出现这种情况,因为它们在Rust中进行并行处理。在这种情况下,当我们通过Python中的多进程派生进程时会发生冲突。派生发生是因为我们将开始在train()方法中循环数据加载器(num_workers>0)。这种组合被认为是不安全的,如果遇到这种情况,标记器会关闭自身的并行性以避免死锁。当我们在这里谈论并行性时,我们严格指的是标记器代码,而不是其他任何东西。换句话说,我们仅影响将输入文本数据转换为标记的代码部分(例如使用tokenizer.encode_plus或任何其他函数)。因此,这不应影响使用利用多个GPU核心的num_workers并行线程的数据加载器函数等。我们如何知道这一点呢?好吧,我们可以尝试在dataset get_item函数中添加一个5秒的延迟和一个打印语句,然后通过循环不同值的num_workers来查看自己。当num_workers = 0时,主进程完成繁重的工作,获取之间有5秒的间隔。当num_workers = 1时,会发生分叉,我们会得到关于并行性的上述警告,并且由于主进程不参与数据提取,因此我们仍然在获取之间获得5秒的间隔。从num_workers>2开始,在5秒的间隔内根据num_workers获取多个提取。
事实上,解决上述警告的简单方法可能是在数据加载器定义中将num_workers = 0。如果num_workers为0,则没有Python fork,主进程本身执行所有数据提取操作。这种方法有效,我们能够充分利用快速分词器的优势,但牺牲了Python端的并行处理。考虑到数据加载器通过在主机(CPU)上并行预取批次以供GPU执行而以并行模式最佳工作,这通常不是一个好的选择。
如果我们设置TOKENIZERS_PARALLELISM = true会发生什么?在最新版本的Pytorch,transformers,tokenisers等中,如果您这样做,然后尝试使用num_workers> 0在数据加载器中进行训练,您的训练将冻结,没有任何错误或警告消息。事实上,这个问题激励我发布这个答案,因为我无法找到解决那个训练冻结问题的方法。根本原因实际上是数据加载器在这种情况下失败,由于上述冲突(它拒绝“fork”因为害怕死锁)。
所以回到我们的核心问题,似乎基于RUST的并行性与我们在Python中进行的fork存在冲突。然而,这可以通过简单地在训练调用之前(即在数据加载器被使用之前)删除所有对分词器的使用来轻松解决。很多时候,我们可能会使用分词器来查看令牌化输出等,只需删除所有这样的分词器调用,让train()函数循环成为分词器第一次被访问的时间。这个简单的修复使得RUST并行化发生在Python fork之后,这应该可以工作。
另一种选择是预先将您的数据转换为令牌,并将它们存储在字典中。然后,您的数据集不应该使用分词器,但在运行时只需调用dict(key),其中key是索引。这样就避免了冲突。警告仍然存在,但您不再在训练过程中使用分词器(注意,在这种情况下为了节省空间,避免在令牌化期间进行填充,并稍后使用collate_fn添加)。
话虽如此,Rust分词器实现速度非常快,即使在分词器内部调用序列化选项(即自动禁用分词器的并行性)时也通常不重要。它仍然击败了传统的分词器。
在大多数情况下,可以忽略警告并在执行期间禁用标记化程序并行处理... 或从一开始就明确将TOKENIZERS_PARALLELISM设置为False。在极少数情况下,速度至关重要,可以探索上述建议的选项之一。