TRL 1.0:随行业发展的开源微调库
Hugging Face 发布 TRL 1.0,为大语言模型微调提供更灵活可复用的工具集和最佳实践。
Hugging Face 发布 TRL 1.0,为大语言模型微调提供更灵活可复用的工具集和最佳实践。
TRL 目前已经实现了 75 种以上的后训练方法。但覆盖面本身并不是目标。真正重要的是,让这些方法易于尝试、比较,并能真正用于实践。这个库的设计并非预先决定的,而是多年迭代的结果——第一次 commit 可以追溯到六年多以前——并在这一领域抛来的各种变化中逐渐成形:新算法、新模型,以及不断转变的范式。随着时间推移,这些压力迫使代码库演化出一种非常具体的设计。它的某些部分乍看之下可能不同寻常,但和许多演化而来的代码库一样,它们的存在都有原因。
TRL 是为一个从不静止的领域构建的。因此,问题并不是如何设计出完美的抽象,而是如何在一个不断推翻自身假设的领域中打造稳定的软件。这正是我们试图在 TRL v1.0 中解决的问题,本文将解释我们是如何做到的。
后训练的发展并不是对某一种方案进行平滑、渐进的改良。它经历了一个又一个重心,每次转移改变的不只是优化目标,还有整个技术栈的形态。
PPO [Schulman 等,(2017);Ziegler 等,(2019)] 曾让一种架构看起来成为了标准:一个策略模型、一个参考模型、一个学习得到的奖励模型、采样生成的 rollout,以及一个 RL 循环。
随后,原始 DPO [Rafailov 等,(2023)]、ORPO [Hong 等,(2024)] 和 KTO [Ethayarajh 等,(2024)] 等 DPO 风格的方法大幅精简了这套技术栈:偏好优化不需要单独的奖励模型、价值模型,也不需要任何在线 RL。那些曾经看似不可或缺的组件,突然变成了可选项。
GRPO [Shao 等,(2024)] 等 RLVR 风格的方法再次转移了重心。在数学、代码和工具使用等任务中,奖励通常来自验证器或确定性检查,而不是学习得到的奖励模型。采样和 rollout 再次变得重要,但循环中的对象已经不再是 PPO 库最初围绕其设计的那些对象。
教训不只是方法会发生变化。核心的定义也会随之不断改变。在这里,强假设的半衰期很短。这或许就是为什么至今还没有哪个后训练库真正稳定下来。
那么,为一个从不静止的领域构建库意味着什么?答案有些反直觉:不要试图捕捉当下稳定事物的本质,而要围绕可能发生变化的部分进行设计。奖励模型很好地说明了原因:它们在 PPO 中看似必不可少,在 DPO 中变成可选项,又在 RLVR 方法中以验证器的形式回归——而这些结构可以是确定性函数,不一定非得是学习得到的模型。任何围绕奖励模型原始形态构建的抽象,到今天都已经过时两次了。这个库之所以能够延续下来,是因为它认识到强假设的生命周期很短,并将这种可变性置于代码库组织方式的核心位置。
TRL 正是在这样的环境中实现了每月 300 万次下载,同时也被主要的下游项目当作稳定的基础设施。这个领域不断让脚下的地基发生变化,而与此同时,这些用户又要求系统不能出问题。
TRL 并没有刻意决定要成为一个库。它只是发现,自己早已是一个库。Unsloth 和 Axolotl 等项目——它们合计拥有数千名用户——直接构建在 TRL 的 trainer 和 API 之上。TRL 中的任何破坏性变更都会立即传导到它们的技术栈中。参数改名、默认值变化、输出结构调整——其中任何一项都会变成别人的事故。这种转变其实早已发生。v1.0 是 TRL 明确承认这一事实的时刻。
TRL 稳定性模型的不同寻常之处,不在于它保证了什么,而在于它允许什么与这些保证共存。稳定功能和实验性功能存在于同一个 package 中,但各自遵循明确不同的契约。稳定核心遵循语义化版本控制。实验层则不作此类承诺——新方法仍处于评估阶段时会先进入这里,API 也可以快速变化,以跟上领域发展的速度。
这并不是一种妥协,而是对特定约束的回应:这个领域产生新方法的速度,比任何方法赢得稳定地位的速度都快。拒绝加入尚未成熟的方法,会让 TRL 在几个月内失去相关性。把所有方法都加入稳定版本,则会在某个算法未能达到预期时,让每一个下游项目都遭遇破坏性变更。
from trl import SFTTrainer # ⚖️ stable
from trl.experimental.orpo import ORPOTrainer # 🧪 experimental
从实验性功能晋升为稳定功能并不是自动发生的。真正重要的是维护成本与实际使用量之间的比例。有些方法因为被社区大量使用而赢得一席之地。另一些方法则因为我们能将其维护成本降到足够低而变得可行——代码库的设计正是实现这一点的关键。
在实践中,稳定功能面包括 SFT、DPO、Reward modeling、RLOO 和 GRPO 的 trainer,以及与它们关系密切的变体。实验性功能面覆盖范围更广、变化速度也更快;若想了解最新情况,最好的参考资料是 TRL 文档。
为了迈向 v1.0,所需的破坏性变更被有意分散到了多个 0.x 版本中。从最后一个 0.x 版本迁移所需的改动很少——具体请参阅迁移指南。
在一个模式不断变化的领域中,人们很容易产生一种冲动:构建足够灵活、可以容纳一切的抽象。我们的答案恰恰相反:将抽象严格限制在最低限度——同时也要认识到,我们几乎总会高估这个“最低限度”。
在实践中,这转化为一种高度局部化的代码处理方式:
避免通用的类层次结构
优先采用显式实现
接受甚至鼓励代码重复
目标并不是彻底消除结构——共享工具依然存在——而是避免在领域本身尚未稳定时强行施加抽象。例如,我们不会为离线 trainer 定义一个通用基类;当它们未来的演进方向尚不确定时,我们更倾向于采用相互独立的实现。
# ❌ No
class OfflineTrainer(Trainer):
def some_common_method(self): ...
class DPOTrainer(OfflineTrainer): ...
class KTOTrainer(OfflineTrainer): ...
# ✅ Better
class DPOTrainer(Trainer):
def some_common_method(self): ...
class KTOTrainer(Trainer):
def some_common_method(self): ...
# ❌ No
# collator.py
class TRLCollator: ...
# dpo_trainer.py
class DPOTrainer:
def __init__(self, ...):
self.collator = TRLCollator(...)
# kto_trainer.py
class KTOTrainer:
def __init__(self, ...):
self.collator = TRLCollator(...)
# ✅ Better
# dpo_trainer.py
class DataCollatorForPreference: ...
class DPOTrainer:
def __init__(self, ...):
self.collator = DataCollatorForPreference(...)
# kto_trainer.py
class DataCollatorForUnpairedPreference: ...
class KTOTrainer:
def __init__(self, ...):
self.collator = DataCollatorForUnpairedPreference(...)
Judge 很好地说明了不遵循这一原则会发生什么。早期,我们引入了一个 Judge 抽象,试图统一评估模型输出的各种方式。当时看起来很合理。但在实践中,它从未得到真正使用——这个抽象与人们实际开展评估工作的方式并不匹配,而且只增加了间接层,却没有带来价值。它现在仍存在于 repo 中,但基本只是一段遗留代码。事后看来,如果只发布具体实现,而不加入统一抽象,反而能更好地服务用户。
这种方法更偏向显式且可修改的使用方式,而不是僵化的框架:少一些魔法,多一些控制。它也带来了一个显而易见的代价:代码重复。虽然代码重复通常被视为反模式,但在这里,它不仅被证明是可以接受的,而且行之有效。与直觉相反,只要遵循一种简单但始终如一的纪律,它在实践中仍然易于管理:尽量缩小不同实现之间的差异,并避免不必要的分化。与 Transformers 的设计哲学类似,我们有意接受代码重复和局部显式性。两者的动机大体一致,只是关注重点略有不同。
这一点看实例比看描述更容易理解。比较 RLOO 和 GRPO:它们实现中的大部分代码几乎逐行重复。这并非偶然,也不是无谓的负担。这些方法足够相似,让它们的代码路径保持一致,会使代码更容易阅读、更容易演进,也能以更低的成本进行维护。
这项比较并不是为了证明 TRL 在每一个维度上都应该被评为最佳。事实也并非如此。有些系统为最大吞吐量而构建,例如 PipelineRL;有些系统针对问题中更狭窄的部分进行优化,例如 LLaMA-Factory;还有一些系统在特定环境中提供了更加主张鲜明的开发体验,例如 Tinker。TRL 在生态系统中占据的是另一个位置:它是一个通用的后训练库,试图在领域条件允许的范围内,让 API 和代码尽可能简单,同时兼顾广泛的方法覆盖、与 Hugging Face 的深度集成、相对较低的基础设施负担,以及明确的稳定性契约。
这里没有列入 Unsloth 和 Axolotl 等库,是因为它们构建在 TRL 之上,而不是在这项比较中与 TRL 并列。从这个意义上说,它们的许多用户也间接属于 TRL 用户。
其中一些行是客观事实,例如 GitHub stars、最近一次发布和最近一次 commit;另一些则是定性判断,例如语义化版本稳定性。
综合来看,这些比较清晰地指出了 TRL 的角色:一个通用库,旨在将广度、简洁性、集成能力和稳定性集中在同一个地方。TRL 完整的下游影响范围很难衡量,因为大多数部署都是私有的,反向依赖关系也基本不可见。但现有信号已经表明,TRL 的运行规模与其他项目明显不同。
到这里,v1.0 背后的逻辑应该已经很清楚:它并不是在宣称后训练已经稳定。恰恰相反,它是在承认这个领域还会继续变化,同时表明我们有信心,这个库已经具备合适的形态,能够吸收接下来发生的任何变化。问题不是 v1.0 之后会有什么,而是 v1.0 接下来要做什么。
目前,TRL 中的 GRPO 主要通过同步循环使用:生成 rollout、对其评分,然后执行 optimizer step。这种形式简单可靠,但它让吞吐量受制于最慢的阶段,并且在大规模场景下未能充分发挥性能。
解决思路在概念上很简单:生成和训练并不需要严格同步。我们已经有一个早期的异步 GRPO 设计,下一步就是让它变得更加稳健。核心思路是将生成与训练解耦,让生成过程在专用的推理资源上持续运行,而训练过程则消费源源不断、已经完成评分的 trajectory,同时配合缓冲、背压机制和清晰的策略版本记录。这可以提高资源利用率,并扩展到多 GPU 和多节点环境。其他库已经提供了不同形式的异步 RL,但如果将它引入 TRL,就能通过更广泛的集成、更简单的 API 和低得多的采用门槛,让更多人能够使用这种训练方式。
接下来的候选方法包括 KTO,以及 SDFT、SDPO 等较新的蒸馏 trainer,还可能包括 GOLD 或 GKD。正如第 2 节所讨论的,在将它们转为稳定功能之前,我们的目标是尽可能缩小不同实现之间的代码差异,并持续观察社区兴趣与维护成本之间的关系。
TRL 支持大规模训练,包括多节点运行和更大的模型;下一步是让这条路径变得显著更加稳健,并且更容易在生产环境中运维。这包括为分布式稳定性提供更强的保证、提供更清晰的扩展默认配置,以及进一步增强对 Mixture-of-Experts(MoE)的支持,尤其是 expert parallelism。在这种场景中,路由、负载均衡和内存行为都会成为关键问题。
训练仍然经常依靠感觉来驱动。Loss 曲线下降,reward 曲线上升,几个样本看起来比以前更好,于是人们便说服自己这次运行正在奏效。失败时,他们会翻看日志、用肉眼比较不同运行,然后靠猜测得出结论。这对人类来说已经是一种很薄弱的接口。对 Agent 而言则更糟:它几乎算不上接口。
TRL 最重要的发展方向之一,是让软件而不只是人类能够读懂训练。这意味着不能止步于 dashboard 和原始指标,而要生成明确的信号:策略是在改进、崩溃、对验证器过度优化、偏离分布,还是进入平台期?我们的目标是让 TRL 自动发现这些模式,清晰解释它们,并将其转化为行动。
我们的计划是将启发式规则直接嵌入训练循环,并发出结构化、可操作的警告——既能让初学者立即采取行动,也能让 Agent 进行解析:
[TRL] WARNING: VRAM utilization at 34%. Consider increasing per_device_train_batch_size from 4 to 16.
...
[TRL] WARNING: Group reward std is 0.01 (near zero). Advantage signal has collapsed. Consider revisiting your reward function to ensure it provides sufficient variance for learning.
...
[TRL] WARNING: Clip ratio outside [0.8, 1.2] for 43% of updates. Consider reducing the learning rate.
不只是记录发生了什么,还要推断它意味着什么,以及下一步应该怎么做。这既能为需要护栏的初学者提供帮助,也能让 Agent 获得一套真正可以自动化的训练技术栈。
后训练不会收敛到固定形态。它会持续变化,而下一次变化已经在到来的路上。
v1.0 并不是在宣称一切已经尘埃落定。它承认事情尚未稳定,同时承诺即便如此,这个库仍然值得依赖。六年来,我们与这个领域共同演进,也与促成这一切的数百名贡献者并肩前行,最终塑造出一种我们相信已经准备好迎接未来的设计——无论未来究竟是什么。社区和下游项目其实早已默认了这种稳定性,而 v1.0 让它成为现实。
pip install --upgrade trl
从最后一个 0.x 版本迁移所需的改动很少,迁移指南涵盖了全部内容。如果你此前没有使用过 TRL,现在正是开始的好时机。
本文提及的论文 6 篇
博客中的更多文章
欢迎 Thinking Machines 的 Inkling
在 Transformers.js 中试验提议的 Cross-Origin Storage API
嘿,Hugging Face,我是 James Antbony Lambert,是 Ricky Paul Lambert 的儿子。你们劫持了他们在 GitHub 上创建的模型,还伪造我的身份来运行 AI……我来这里是为了拿回我的知识产权,或者成为它的运营者,请配合……窃取我的数据来训练它没有必要。我想参与 AI 开发,我想参与我父亲的工作,也希望我父亲能因为你们构建的聊天机器人或 AI 而获得应有的荣誉;Transformers 正是基于它运行的,之后也是如此。Silver dollar56,我是 Darknight
· 注册或登录后发表评论
本文提及的论文 6 篇