rl: true per-token entropy bonus (maximize r + beta*H, explicit diversity knob, no KL)
Browse files- scripts/rl.py +15 -3
- src/mxf/config.py +1 -0
scripts/rl.py
CHANGED
|
@@ -174,7 +174,7 @@ def update(actor, opt, submodule, ids, attn, p_len, marker, old_lp, adv, dirs_re
|
|
| 174 |
gen_mask = attn[:, p_len:].bool()
|
| 175 |
total_tok = max(int(gen_mask.sum()), 1)
|
| 176 |
lo, hi = 1 - a.clip_eps, 1 + a.clip_eps
|
| 177 |
-
loss_sum, clipped_tok = 0.0, 0
|
| 178 |
opt.zero_grad(set_to_none=True)
|
| 179 |
for s in range(0, n, a.micro_batch):
|
| 180 |
e = min(s + a.micro_batch, n)
|
|
@@ -183,12 +183,20 @@ def update(actor, opt, submodule, ids, attn, p_len, marker, old_lp, adv, dirs_re
|
|
| 183 |
STEER_COEFF, device, torch.bfloat16)
|
| 184 |
with hooked(submodule, hook):
|
| 185 |
logits = actor(input_ids=b_ids, attention_mask=b_attn).logits[:, p_len - 1 : -1]
|
| 186 |
-
|
| 187 |
del logits
|
|
|
|
| 188 |
m = gen_mask[s:e].to(device)
|
| 189 |
ratio = torch.exp(new_lp - old_lp[s:e].to(device)).clamp(max=a.tis_cap) # TIS, upper only
|
| 190 |
A = adv[s:e, None].to(device)
|
| 191 |
loss = (-torch.minimum(ratio * A, ratio.clamp(lo, hi) * A) * m).sum() / total_tok
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 192 |
loss.backward() # micro-losses share the global normalizer → grads sum correctly
|
| 193 |
loss_sum += loss.item()
|
| 194 |
clipped_tok += int((((ratio < lo) | (ratio > hi)) & m).sum())
|
|
@@ -199,7 +207,8 @@ def update(actor, opt, submodule, ids, attn, p_len, marker, old_lp, adv, dirs_re
|
|
| 199 |
else: # stepping Adam on nan/inf grads corrupts moments AND weights
|
| 200 |
opt.zero_grad(set_to_none=True)
|
| 201 |
print(f"[update] non-finite grad norm ({gn}) — skipping step", flush=True)
|
| 202 |
-
return {"loss": loss_sum, "grad_norm": gn, "clipfrac": clipped_tok / total_tok
|
|
|
|
| 203 |
|
| 204 |
|
| 205 |
def main():
|
|
@@ -226,6 +235,8 @@ def main():
|
|
| 226 |
ap.add_argument("--len-penalty-start", type=int, default=cfg.len_penalty_start)
|
| 227 |
ap.add_argument("--len-penalty-per-tok", type=float, default=cfg.len_penalty_per_tok)
|
| 228 |
ap.add_argument("--no-gates", action="store_true", help="disable fluency/distinct/len shaping")
|
|
|
|
|
|
|
| 229 |
ap.add_argument("--tp", type=int, default=int(os.environ.get("WORLD_SIZE", "1")))
|
| 230 |
ap.add_argument("--vllm-gpu-mem", type=float, default=0.35)
|
| 231 |
ap.add_argument("--vllm-max-len", type=int, default=1024)
|
|
@@ -337,6 +348,7 @@ def main():
|
|
| 337 |
log = {"reward/mean": raw_r.mean().item(), "reward/std": raw_r.std().item(),
|
| 338 |
"reward/max": raw_r.max().item(), "reward/shaped_mean": r.mean().item(),
|
| 339 |
"reward/gate_frac": gate_frac, "ratio/clipfrac": stats["clipfrac"],
|
|
|
|
| 340 |
"loss": stats["loss"], "grad_norm": stats["grad_norm"],
|
| 341 |
"rollout/mean_logp": torch.cat(old_lps).mean().item(),
|
| 342 |
"rollout/len_mean": n_gen / (B * G), "tokens_per_sec": n_gen / secs,
|
|
|
|
| 174 |
gen_mask = attn[:, p_len:].bool()
|
| 175 |
total_tok = max(int(gen_mask.sum()), 1)
|
| 176 |
lo, hi = 1 - a.clip_eps, 1 + a.clip_eps
|
| 177 |
+
loss_sum, clipped_tok, ent_sum = 0.0, 0, 0.0
|
| 178 |
opt.zero_grad(set_to_none=True)
|
| 179 |
for s in range(0, n, a.micro_batch):
|
| 180 |
e = min(s + a.micro_batch, n)
|
|
|
|
| 183 |
STEER_COEFF, device, torch.bfloat16)
|
| 184 |
with hooked(submodule, hook):
|
| 185 |
logits = actor(input_ids=b_ids, attention_mask=b_attn).logits[:, p_len - 1 : -1]
|
| 186 |
+
logp_full = torch.log_softmax(logits.float(), -1)
|
| 187 |
del logits
|
| 188 |
+
new_lp = logp_full.gather(-1, b_ids[:, p_len:, None]).squeeze(-1)
|
| 189 |
m = gen_mask[s:e].to(device)
|
| 190 |
ratio = torch.exp(new_lp - old_lp[s:e].to(device)).clamp(max=a.tis_cap) # TIS, upper only
|
| 191 |
A = adv[s:e, None].to(device)
|
| 192 |
loss = (-torch.minimum(ratio * A, ratio.clamp(lo, hi) * A) * m).sum() / total_tok
|
| 193 |
+
if a.entropy_coef > 0:
|
| 194 |
+
# true per-token entropy (unbiased, logits are already here) — maximize r + β·H(π):
|
| 195 |
+
# keeps the policy stochastic for Bo-N without KL's behavior-anchoring side effect
|
| 196 |
+
ent = -(logp_full.exp() * logp_full).sum(-1)
|
| 197 |
+
ent_sum += float((ent.detach() * m).sum())
|
| 198 |
+
loss = loss - a.entropy_coef * (ent * m).sum() / total_tok
|
| 199 |
+
del logp_full
|
| 200 |
loss.backward() # micro-losses share the global normalizer → grads sum correctly
|
| 201 |
loss_sum += loss.item()
|
| 202 |
clipped_tok += int((((ratio < lo) | (ratio > hi)) & m).sum())
|
|
|
|
| 207 |
else: # stepping Adam on nan/inf grads corrupts moments AND weights
|
| 208 |
opt.zero_grad(set_to_none=True)
|
| 209 |
print(f"[update] non-finite grad norm ({gn}) — skipping step", flush=True)
|
| 210 |
+
return {"loss": loss_sum, "grad_norm": gn, "clipfrac": clipped_tok / total_tok,
|
| 211 |
+
"entropy": ent_sum / total_tok}
|
| 212 |
|
| 213 |
|
| 214 |
def main():
|
|
|
|
| 235 |
ap.add_argument("--len-penalty-start", type=int, default=cfg.len_penalty_start)
|
| 236 |
ap.add_argument("--len-penalty-per-tok", type=float, default=cfg.len_penalty_per_tok)
|
| 237 |
ap.add_argument("--no-gates", action="store_true", help="disable fluency/distinct/len shaping")
|
| 238 |
+
ap.add_argument("--entropy-coef", type=float, default=cfg.entropy_coef,
|
| 239 |
+
help="β for maximize r + β·H(π): direct diversity pressure (Bo-N depends on it)")
|
| 240 |
ap.add_argument("--tp", type=int, default=int(os.environ.get("WORLD_SIZE", "1")))
|
| 241 |
ap.add_argument("--vllm-gpu-mem", type=float, default=0.35)
|
| 242 |
ap.add_argument("--vllm-max-len", type=int, default=1024)
|
|
|
|
| 348 |
log = {"reward/mean": raw_r.mean().item(), "reward/std": raw_r.std().item(),
|
| 349 |
"reward/max": raw_r.max().item(), "reward/shaped_mean": r.mean().item(),
|
| 350 |
"reward/gate_frac": gate_frac, "ratio/clipfrac": stats["clipfrac"],
|
| 351 |
+
"policy/entropy": stats["entropy"],
|
| 352 |
"loss": stats["loss"], "grad_norm": stats["grad_norm"],
|
| 353 |
"rollout/mean_logp": torch.cat(old_lps).mean().item(),
|
| 354 |
"rollout/len_mean": n_gen / (B * G), "tokens_per_sec": n_gen / secs,
|
src/mxf/config.py
CHANGED
|
@@ -68,6 +68,7 @@ class RLConfig:
|
|
| 68 |
lr: float = 1e-6
|
| 69 |
clip_eps: float = 0.2
|
| 70 |
tis_cap: float = 2.0 # TIS upper ratio cap — absorbs residual vLLM/HF kernel mismatch
|
|
|
|
| 71 |
max_new_tokens: int = 96
|
| 72 |
min_new_tokens: int = 16
|
| 73 |
temperature: float = 1.0
|
|
|
|
| 68 |
lr: float = 1e-6
|
| 69 |
clip_eps: float = 0.2
|
| 70 |
tis_cap: float = 2.0 # TIS upper ratio cap — absorbs residual vLLM/HF kernel mismatch
|
| 71 |
+
entropy_coef: float = 0.0 # β in maximize r + β·H(π); explicit diversity knob (no KL)
|
| 72 |
max_new_tokens: int = 96
|
| 73 |
min_new_tokens: int = 16
|
| 74 |
temperature: float = 1.0
|