Skip to content

Stale queue#23

Merged
haizhongzheng merged 3 commits into
mainfrom
stale-queue
Jul 14, 2026
Merged

Stale queue#23
haizhongzheng merged 3 commits into
mainfrom
stale-queue

Conversation

@haizhongzheng

Copy link
Copy Markdown
Member

Title:

feat(dataflow): earliest-deadline-first (EDF) rollout buffer consumption

Body:

Problem: FIFO consumption creates a difficulty bias in staleness drops

Long generations span several weight updates while they are being produced, so
their min_version is already old the moment they land in the rollout buffer.
Consuming in completion order (FIFO) then parks them behind fresher short
samples until they exceed max_staleness and get dropped.

The two effects compound into a systematic bias: the long/hard samples a
model most needs to learn are preferentially discarded
. In our measurements,
FIFO's staleness-dropped samples are 1.3–1.7× longer than the samples it
trains on.

Change: consume by staleness deadline instead of arrival order

The rollout buffer is now a priority queue consumed in ascending min_version
order (earliest deadline first) — the samples closest to expiring are
trained on first, before they cross the staleness bound.

  • New config key dataflow.buffer.queue_order: edf (default) or fifo
    (the historical behavior, kept for comparison).
  • The per-token staleness invariant is unchanged: EDF only reorders which
    buffered samples are consumed first; nothing older than max_staleness is
    ever trained on, and the replay path is untouched.
  • Ties on min_version break by arrival order, so rollout groups stay
    contiguous; unversioned samples are consumed eagerly.
  • New W&B metrics to observe the bias directly: buffer/consumed_len_avg,
    buffer/skipped_stale_len_avg, plus buffer-eviction accounting.
  • Checkpoint compatibility: state_dict round-trips across queue_order
    settings (priorities are recomputed from metadata on load), and legacy
    deque-format checkpoints still load.
  • 14 new unit tests: FIFO parity, EDF ordering/ties, a deterministic
    mixed-length load simulation showing the bias and its fix, and
    state-dict round-trips (astraflow/dataflow/tests/test_rollout_buffer_queue_order.py).

A/B results (Qwen3-4B, M2PO on math, 8×H100)

Controlled pair under real staleness pressure (6 GPUs RaaS vs 2 GPUs FSDP,
max_staleness: 8, ctx 16k / max_new_tokens 14000). Recipes included in
this PR: examples/math/qwen3-4b-m2po-edf/ and .../qwen3-4b-m2po-fifo/.

Overall avg@k across AIME24/25, AMC, Minerva, MATH500:

step edf fifo Δ
0 42.2 43.1
100 57.6 52.5 +5.1
200 59.2 56.9 +2.3
250 59.5 58.1 +1.4
350 58.6 59.6 −1.0 (converged)
  • The @100 gap reproduced across two independent edf runs (57.6 and 57.8).
  • Gains are largest on the hardest sets — @100: AIME25 +9.4, AIME24 +5.4,
    AMC +5.0.
  • Mechanism confirmed by the new metrics: fifo drops are length-biased
    (dropped/consumed length ratio 1.3–1.7×) with 8.3–17.1% drop rate; edf is
    near-fair (~1.1×) at 7.4–9.6%.
  • Net effect: sample efficiency — edf reaches fifo's eval plateau ~150
    steps earlier; with enough steps both arms converge.

Also in this PR

  • docs: installation.md now warns that flash-attn must be installed from
    outside the repo directory — [tool.uv] exclude-dependencies makes
    uv pip install flash-attn a silent no-op inside it, and a leftover
    namespace stub makes import flash_attn falsely succeed. The verify step
    now imports flash_attn_func and checks the installed version.

Compatibility / rollout notes

  • Default changes to edf. Existing YAMLs without queue_order pick it up
    automatically; set dataflow.buffer.queue_order: fifo to restore the old
    behavior exactly.
  • No API changes; config plumbing is additive
    (loader → AgentConfig → AstraFlow → MultiModelDataServing → RolloutBuffer).

FIFO (completion-order) consumption has a difficulty bias: long
generations span several weight versions, so their min_version is old on
arrival, and queueing behind fresher short samples pushes them past
max_staleness — the buffer preferentially expires exactly the long/hard
prompts. Measured under rollout over-provisioning (6 inference GPUs vs 2
trainer GPUs, Qwen3-4B math RL): dropped samples are 1.3-1.7x longer
than consumed ones and 17% of production expires.

Replace the fresh-queue deque with a priority heap ordered by
dataflow.buffer.queue_order:

- "edf" (new default): ascending min_version — consume the sample
  closest to its staleness deadline first. The per-token invariant is
  unchanged (current_version - min_version <= max_staleness for
  everything trained on); ties break by arrival, so rollout groups stay
  contiguous.
- "fifo": arrival order, bit-for-bit the historical behavior, kept for
  comparison runs.

Plumb the option through AgentConfig -> AstraFlow -> data serving ->
RolloutBuffer (per-model override supported), and surface the bias in
metrics: buffer/consumed_len_avg, buffer/skipped_stale_len_avg, plus the
previously computed-but-unreported eviction count. state_dict stays
backward compatible with deque-era checkpoints (priorities are
recomputed from metadata on load, so checkpoints also survive switching
queue_order).

A/B on 8xH100 (only queue_order differs): edf halves the drop rate,
drops length-fairly (1.1x vs 1.7x), trains on ~25% longer samples, and
reaches 57.6 overall avg@k at step 100 vs fifo's 52.5 (+5.1, reproduced
across two independent edf runs; largest gain on the hardest set, AIME25
+9.4). Both arms converge by step 350 — edf is a sample-efficiency win,
reaching fifo's plateau ~150 steps earlier. Covered by 14 new unit tests
including a deterministic mixed-length load simulation asserting the
bias exists under fifo and disappears under edf with no staleness
violations in either mode.
Two Qwen3-4B math-RL recipes identical except
dataflow.buffer.queue_order (and trial_name), for reproducing the
rollout-buffer consumption-order comparison. The 6-RaaS/2-trainer GPU
split deliberately over-provisions rollout so the fresh buffer builds
real staleness pressure; ctx 16k / max_new_tokens 14000 makes
generations span multiple weight versions. READMEs document the setup
and the measured results (edf: +5.1 overall avg@k at step 100, +9.4 on
AIME25, ~150 steps faster to the eval plateau; converged by step 350).
Following the conda install steps verbatim leaves flash-attn 2.8.3
silently uninstalled: pyproject's [tool.uv]
exclude-dependencies=["flash-attn"] (needed so project installs don't
try to resolve/build it) also applies to the explicit
`uv pip install flash-attn` in Step 4 when run from the repo directory —
the command exits 0 without installing anything. The failure is then
masked because flash-attn-4 (sglang's attention backend) leaves a
namespace stub at site-packages/flash_attn/, so `import flash_attn`
appears to work and Step 6's `flash_attn.__version__` check fails with a
confusing AttributeError instead of a clear signal.

Step 4 now cds to /tmp before installing (mirroring the Dockerfile fix
from d192bbe) and explains why; Step 6 verifies by importing
flash_attn_func and reading the wheel metadata version, with the failure
signature and remedy spelled out. Both envs built from these docs on
this cluster had hit the trap unnoticed.
@haizhongzheng haizhongzheng merged commit 9417152 into main Jul 14, 2026
1 check failed
@haizhongzheng haizhongzheng deleted the stale-queue branch July 14, 2026 16:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant