RL 其实很简单
内容提要
作者认为数据库比RL更复杂,因为数据库需维护跨事务、会话、崩溃的状态正确性,而RL链路虽长但近乎无状态。他通过绘制数据流图掌控系统,并详细描述了RL训练环:派发、rollout、凑batch、reward、advantage、训练、权重同步。他强调骨架简单但每跳细节复杂,如轨迹树、版本差、切分等,需先理解整体再深入。
延伸解读
复杂度度量:状态维护而非代码量
作者提出衡量系统复杂度的标准不是代码行数或组件数量,而是需要在并发和故障边界上维护正确的状态数量。数据库因跨事务、会话、崩溃重启的状态一致性而复杂,RL链路虽长但近乎无状态。这提醒我们,评估系统复杂度时,应关注状态管理的难度,而非表面规模。
数据流图:掌控系统的关键
作者接手新系统时,第一件事是绘制数据从入口到出口每一跳的形态、owner和生命周期。他认为真正的优化机会在链路接缝处,即组件交接时传递的内容。通过掌控数据流,他成功将DSA R3的数据流从GB级降至KB级。这启示我们,理解系统全貌比局部优化更重要。
异步RL的版本差与队列深度
异步RL中,样本的版本差(batch_id - version_tag)决定其能否参与训练,而训练队列的深度会放大样本的陈旧度。因此,队列深度不仅是缓冲,更是需要监控的指标。这提醒我们,在异步系统中,需关注样本新鲜度与吞吐量的平衡,避免因队列积压导致训练质量下降。
骨架简单,细节复杂
作者强调RL链路骨架简单,但每一跳细节复杂,如轨迹树分叉、版本差计算、切分与显存管理等。他建议先理解整体骨架,再深入细节,否则容易迷失。这提示我们,面对复杂系统,应先建立全局观,再逐步攻克局部难点,顺序不可颠倒。
Q&A
作者为什么认为数据库比RL更复杂?
作者认为数据库比RL更复杂,因为数据库需要维护跨事务、跨会话、跨进程、跨崩溃重启的状态正确性,而RL链路虽然长但近乎无状态,状态少且每跳基本是纯函数。
作者用什么标准来衡量一个系统的复杂度?
作者用“有多少状态需要在并发和故障边界上被维护正确”来衡量系统复杂度,而不是代码行数或组件数量。
RL训练环中,数据是如何流动的?
RL训练环的数据流是:dataset -> 派发 -> rollout -> 就绪缓冲 -> 凑batch -> reward -> advantage -> training -> 权重同步 -> 回到派发。派发时按在途水位持续滴灌轨迹,rollout一条轨迹跑完就单独回收,凑batch时需先算版本差,reward挂在最后一个token上,advantage从token维回到标量再tile回token维,训练后权重同步回推理引擎。
在RL中,什么是env group、trajectory和leaf trajectory?
env group是dataset中的一行prompt,代表一组要在同一环境设定下反复采样的东西;一个env group按n展开成n条trajectory;一条trajectory是一串多轮交互,如果中途分叉,会被展平成多条leaf trajectory,共享前缀的节点在后面的leaf里loss mask置0。最终喂给训练的batch中,一行是一条leaf trajectory。
在异步RL中,派发是如何工作的?
异步RL中,driver维护一个在途轨迹的目标水位,每次有轨迹跑完或推理实例空出容量,就再派一批出去,让在途数量往目标水位上靠。派发的单位是trajectory,不是env group;每条轨迹在注册进就绪缓冲时打上当时的权重版本号;水位是自适应的,双向调整。
在凑batch时,为什么要先算版本差?
因为凑完一批就要训练,训练完权重版本就往前推一格,那些还在rollout中、版本偏老的轨迹可能超出允许的版本差,导致白跑。所以凑之前要先dry-run,优先消费旧样本,避免浪费。
RL训练中,reward是如何存储的?
reward是每条leaf trajectory生成一个标量r_i,存成token_level_rewards,只放在这条轨迹最后一个有效token的位置上,其余全0。
在RL训练中,advantage是如何计算的?
以GRPO为例,先将token_level_rewards按轨迹求和得到标量s_i,然后按uid分组归一化得到ŝ_i,再tile回token维,最后乘上response_mask。
在RL训练中,数据切分和搬运的七步是什么?
七步包括:1. pad到能整除;2. 按token数重排;3. 切给DP;4. 几百MB的张量通过共享内存池搬运;5. 切micro-batch;6. forward之后按[:, -R-1:-1]切出response段的log prob和entropy;7. loss归一化,确保切分方式变了但数学结果不变。
权重同步时,rollout侧做了哪些动作?
rollout侧的动作包括:pause_generation(暂停调度并撤回在跑请求)、release kv cache(释放KV cache物理显存但保留虚拟地址)、update_weights(更新权重版本)、resume kv cache(重新占回显存)、continue_generation(继续调度)。这些动作全是资源腾挪和请求重排,没有维护跨step的语义状态,且都是幂等或可重来的。