我在verl(一个RL后训练框架)里沉浸了数月,复刻了它,然后停止。写下了内部机制、复刻所需的工具开销以及一个棘手的NCCL错误。

Reddit r/LocalLLaMA 新闻

摘要

深入探讨字节跳动verl强化学习后训练框架的内部机制,包括编排、单控制器模式以及一个棘手的NCCL错误修复。作者分享了复刻该框架和构建自定义工具的经验教训。

我不确定是否应该在这里发布这篇文章,但我朋友说很多研究者会浏览这个子版块,这篇文章可能对他们有帮助。我想它也可能帮助那些在家中尝试动手的人。我不知道这里有多少人在做后训练,但我确实看到有人发布蒸馏、微调、数据集和基准测试等内容,所以我觉得你可能会感兴趣。 为了提供背景,我从事Agent和工具使用能力的后训练工作。前一阵子,我几乎全身心投入在字节跳动的RL后训练框架verl中,持续了几个月。我阅读了大部分源码,吸收了几乎所有的知识。在与它一起工作的过程中,我开始想要一个“更好”的版本,一个更适合我的开发体验的版本。于是我对它进行了复刻(非公开,后来我放弃了),试图让它变得更好(在我看来)。虽然我推送了很多修复,并围绕它构建了工具,但到了某个时候,我不得不停下来,这让我心里空落落的,最终我把整个过程写了下来。作为对它的告别,同时也为了从中获得我所学到的所有知识和技能。 这是一篇对实际运行RLHF循环的部分的仔细解读,加上复刻带来的一些工程问题(不过没什么大不了的),以及一个我至今仍有点自豪的调试故事。 以下是博客文章内容的快速浏览: - 编排层的内部机制:从每个阶段(rollout、reward、advantage、update)传递的数据结构(DataProto),以及API名称中未警告的陷阱。在其底层,还有一个半完成的向纯TensorDict的迁移。 - 单控制器模式:一个驱动进程持有调度,通过一个“魔法属性”分发系统将工作分发给GPU工作节点。这个模式很难缠,我花了很长时间才搞明白,但现在理解了,就觉得非常自然,并且帮助我以极大的信心和轻松感来构建自己的编排层小包。 - 资源池和协同定位,以及actor、critic、rollout和reference角色如何被融合到每个GPU上的单个Ray actor中。然后我简要谈谈复刻所需的工具,因为这仍然是verl的一个问题,而且我认为他们不太可能修复它,或者至少修复起来会很麻烦,因为他们需要支持这么多不同的架构等等。但主要问题是打包泄漏:torch不在核心依赖中,一个版本约束被复制粘贴了三次,requirements.txt和setup.py对所需依赖存在分歧,一个未维护的包仍然在活跃代码路径中被导入,其测试被跳过。还有,测试没有标准化,我浪费了很多精力试图让测试套件干净整洁,甚至构建了一个GPU感知的测试调度器,将测试装箱到空闲的卡片上,而不是让一些GPU闲置。我还加了一个小彩蛋,因为我看到同事遇到了一个NCCL问题。一个多GPU测试挂起,没有错误,没有超时,没有崩溃。CPU屏障通过了,但第一个NCCL集合操作挂起了。原因是NCCL为它的bootstrap套接字选择了一个绑定的网络接口,其IP无法路由回自身,因此rank 0在一个没有其他进程能到达的地址上监听。修复方法是一个环境变量(单节点上设置为`NCCL_SOCKET_IFNAME=lo`)。找到这个原因需要拆解TCPStore、Gloo和NCCL层,并阅读NCCL自己的调试输出。 但是,正如你所料,我停止了,因为我关心的每个重构(对编排层而言,如果你读了博客就会明白有太多间接层和魔法,很难理解,而且我认为它不是一个好的开发基础)都必须跟上几乎每天都在发布变化的框架,保持同步的成本超过了工作本身。我现在正在构建自己的小型爱好编排层。显然,这类知识可能会过时,但我认为编排部分作为基础原理是值得了解和理解的,而且无论实现如何,我觉得抽象层或多或少都是一样的,除非你改变范式(例如从单控制器到SPMD)。而且我认为,如果你有兴趣为verl做贡献,你将从这篇博客中获得很好的想法,但我不确定他们是否会接受这些领域的贡献。 好了,抱歉啰嗦了,哈哈,这是完整文章:[https://reinforcedknowledge.com/posts/verl-retrospective/](https://reinforcedknowledge.com/posts/verl-retrospective/)
查看原文

相似文章