Skip to content

GRPO: ValidTokenCount reads 0 for a full step while Losses accumulate — per-micro vtc semantics + rare last-micro corruption #74

Description

@flaviusburca

Symptom

Rare (3 occurrences over ~150 GRPO steps across two engine builds): a training step logs vtc=0 with hundreds of micro-batches, producing garbage step metrics (masked 2384-3052%, kl 35-42, loss 42-59) and exploded pre-clip grad norms (0.77-0.96). The zero-lr guard from #72 neutralizes it (policy untouched), but the root cause is open.

Established by investigation (2026-08-04)

  1. ValidTokenCount is per-micro in the GRPO path: forward_for_grpo unconditionally zeroes Losses/ValidTokenCount/CorrectCount every micro (dsl_model_execution.cpp:795). The trainer's step-end get_valid_token_count(0) therefore returns the last micro's count, not a step total. Verified: a healthy step logged vtc=136 and its last micro's loss-mask count is exactly 136 (decoded from the transport batch).
  2. On corrupted steps the CE kernels did run and write Losses (logged loss ≈ raw -logprob sum over one micro's ~125-170 valid tokens), while vtc read 0 — same kernels write both (fused_lm_head_loss.cpp:417-441), so a stuck kv_sweep skip (fused_lm_head_loss.cpp:242) is ruled out (it would zero both).
  3. Data ruled out: both preserved batches decode cleanly; no micro has a zero loss-mask; last micros carry 125-167 valid tokens (rollouts_step73_FORENSIC, rollouts_step82_FORENSIC in the pool-D output dir, plus the cap3-era step-14 batch).
  4. dispatch_pp_apply_optimizer's host overwrite clamps to ≥1 — ruled out.

Remaining candidates

  • The last micro's chunked_cross_entropy_forward/fused_cross_entropy_forward contributing 0 to the vtc atomic while writing losses (kernel-internal accumulation edge, possibly interacting with lmhead_chunks nano-slicing or the chunked phase-A/B schedule).
  • A post-backward re-zeroing of ValidTokenCount racing the step-end read (any path that calls fill_zero on run-state loss buffers between the last backward_grpo and the read).

Next step

Deterministic replay: feed a forensic batch through a single-GPU debug trainer (same config: fp8-hybrid, sequence_chunks 4, lmhead_chunks 12, single-sample bins) with SUROGATE_CHUNK_TRACE=1 and a vtc D2H readback after each micro — bisects kernel-edge vs post-hoc-zeroing in one run. Batches and configs preserved; replay harness is the open work.

Also worth considering

Step-end consumers treating vtc as a step total (metric normalization; global_norm_sqrt token scale) may deserve an explicit step-accumulated counter regardless of this bug — the per-micro semantics make every step-level metric a last-micro sample.

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions