RL Notes

January 29, 2026

RL 是 post-training 里比较重要的一个部分。开个坑,记录下 RL 相关的基础知识。

跑个 demo 看下 RL 的基本概念

RL 本质是在 Actor 和 Env 的持续交互中,通过 action 和 state 变化来不断迭代 policy/reward/critic 等模型。

下面的例子用一个左右移动的场景(目标是移动到 2)来举例(下面代码基本都有注释,highlight 几个点):

  • reward model: 这个场景下 reward 会比较简单,直接通过 state 来判断。为了引入 critic values,这里在 state=2 的 reward 是最大的,但是故意在 state=1 的 reward 设置为最小。所以如果 actor 只关心 reward,那永远走不到 2。
    • reward 分为确定性的 reward 和非确定性的 reward。如何定义好 reward(尤其是过程中的 reward)很关键;
  • critic model:为了避免 actor 只看 reward(短期收益),引入 critic values(长期价值),长期价值本质上是未来所有潜在 reward 的压缩。所以每个 step 的真实价值应该是 reward + gamma * critic-value。
    • critic value 也分两种情况,一种是可枚举的,比如这个场景,我可以遍历所有的情况,然后不断更新 critic value;另一种是不可枚举的,预估的;
  • policy 最后输出的是一个 logprobs,(1, T),T 是 action 的维度,然后加入一定的随机性,这里和 LLM 的 temperature 是类似的。
  • Training 训练过程
    • rollout:actor 每走一步或者几步,产生的 trajectory 可以认为是一次 rollout,包括 state/action/logprobs/reward/critic-values 等多个信息。
    • reward model 一般不参与 RL 训练。
    • critic model 更新:
      • 计算一个 advantage,公式是 adv = step["reward"] + gamma * V[s_next] - V[s]
        • adv > 0:说明 V[s] 估计小了;adv < 0:说明 V[s] 估计大了;
        • 用这个 delta 值去训模型
    • policy model 更新:
      • 对于 policy model,adv 的值表示这个行为应该被鼓励还是抑制,从而影响下次输出的 logprobs
  • 这是一个最简单的 demo,reward/critic/policy 都直接用 python dict 来表示,实际的训练过程,根据不同的方法论会更加的复杂...
import numpy as np
import random

# 环境的描述(-2  2 共有 5 个点)
states = [-2, -1, 0, 1, 2]
# 动作集合(左,不动,右)
actions = [-1, 0, 1]

# 执行 action
def transition(s, a):
    return max(-2, min(2, s + a))

# reward model 定义
def reward(s):
    if s == 2:
        return 10
    if s == 1:
        return -5
    return -1

# policy logits: state -> action logits
policy_logits = {
    s: np.zeros(len(actions)) for s in states
}

def softmax(x):
    e = np.exp(x - np.max(x))
    return e / e.sum()

# policy model, 
def policy(state):
    probs = softmax(policy_logits[state])
    return probs

V = {s: 0.0 for s in states}
gamma = 0.9

trajectory = []

# 一次 rollout
state = 0
for t in range(100):
    probs = policy(state)
    action_idx = np.random.choice(len(actions), p=probs)
    action = actions[action_idx]

    logprob = np.log(probs[action_idx] + 1e-8)

    next_state = transition(state, action)
    r = reward(next_state)

    trajectory.append({
        "state": state,
        "action": action,
        "action_idx": action_idx,
        "reward": r,
        "logprob": logprob
    })

    state = next_state


# 计算 advantage, 作为 policy model 的输入
advantages = []
for step in trajectory:
    s = step["state"]
    a = step["action"]
    s_next = transition(s, a)
    td_target = step["reward"] + gamma * V[s_next]
    advantage = td_target - V[s]
    advantages.append(advantage)

# 利用 advantages 更新 policy model
lr = 0.1
for step, adv in zip(trajectory, advantages):
    s = step["state"]
    a_idx = step["action_idx"]
    logp = step["logprob"]

    # policy gradient:  logπ(a|s) * advantage
    policy_logits[s][a_idx] += lr * adv

# 利用 advantages 更新 critic model
alpha = 0.1
for step, adv in zip(trajectory, advantages):
    s = step["state"]
    V[s] += alpha * adv

for i, step in enumerate(trajectory):
    print(
        f"t={i}, s={step['state']}, a={step['action']}, "
        f"r={step['reward']}, logp={step['logprob']:.3f}, "
        f"adv={advantages[i]:.3f}"
    )
  • 除了上面的 demo 之外,还有一个 reference model,目的是约束,放置模型训歪。不过我现在还没理解这个是否是必要的,如果是为了约束,那在 reward 和 critic model 里也能起到类似的作用,多加一个模型反而让整体复杂度和稳定性风险又高一个量级。
    • Reference 模型可能来自于初始化的 policy model,为了防止 policy model 跑的太偏;(利用 KL 散度来定义 loss)

OpenRLHF / Slime / VeRL

Slime

// TODO 源码解读

OpenTinker(RL-as-a-Service)