Skip to content

Safe eviction defaults (#140), GRPO single-epoch cache reuse, HELMET GRPO driver - #142

Open
mmjerge wants to merge 15 commits into
awslabs:mainfrom
mmjerge:grpo-upstream
Open

Safe eviction defaults (#140), GRPO single-epoch cache reuse, HELMET GRPO driver#142
mmjerge wants to merge 15 commits into
awslabs:mainfrom
mmjerge:grpo-upstream

Conversation

@mmjerge

@mmjerge mmjerge commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #140 (safe eviction defaults), improves the standalone GRPO loop from #128
(single-epoch cache reuse, completion mask, an alignment fix, and a GPU
inference-mode fix), adds sm_86 to the FlashInfer build, and adds a GRPO
training driver for HELMET tasks plus profiling tooling.

Note on the first commit: 99c6369 is #141 (@vihangp) applied verbatim —
included so this branch is self-consistent and testable (the GRPO
measurements depend on it). Once #141 merges, a rebase makes that commit
vanish; happy to rebase on request.

Fix #140: safe eviction defaults (attention sinks + grace period)

Evicting KV caches produced garbage generation (immediate EOS) on real models
whenever the prompt exceeded cache_length. Instrumentation showed the
eviction/attention mechanics are exact (cached keys/values match a dense
reference gathered at token_positions() exactly; the attention output matches
a manual masked-softmax reference to ~1e-7; a 1-layer model shows no divergence
at all). The cause is attention-sink eviction: trained LLMs put large
attention mass on the first tokens (BOS / template header) and use them as
no-op sinks (Xiao et al., StreamingLLM); evicting them collapses generation.
Toy random models have no sink structure, which is why unit tests pass.

KVCacheFactory now injects safe defaults unless the caller specifies them
(explicit values, including 0, are respected; test_utils.create_kv_cache
pins them to 0 so unit tests keep exercising raw mechanics):

  • lastrec: init_grace_tokens = min(16, cache_length // 8)
  • h2o / h2o-vlen / qh2o / qh2o-vlen: grace_period = cache_length // 16
    (h2o-orig excluded — it rejects grace_period)

Measured (Qwen2.5-0.5B-Instruct, A10G, bf16, ~2k-token prompts, n=40,
substring exact match; before the fix every evicting config scored 0.000):

task dense (full) h2o @1024 lastrec @512 lastrec @1024
recency (answer = last record) 0.925 0.875 0.900 0.900
needle_first 0.700 0.200 0.000 0.000
needle_last 0.550 0.150 0.125 0.225

Recency recovers to within noise of dense — generation through an evicting
cache is functional again — while the needle rows now reflect genuine eviction
lossiness (the expected quality frontier) instead of the collapse.

Grace slots reduce a cache's forward capacity, so chunk sizes are capped at
cache_length - grace where applicable (rl/grpo/loop.py, examples).

GRPO improvements (on top of #128)

  • Single-epoch cache reuse: GRPOLossHeadModel.set_batch makes
    old_logps optional; when omitted (single-epoch GRPO), the old log-prob is
    policy_logp.detach() from the gradient pass's own forward, so the
    importance ratio is exactly 1 and the separate scoring forward pass is
    eliminated (mirrors TRL's num_iterations=1 behaviour). grpo_step gains
    rescore_old_logps (multi-epoch / A-B) and profile (per-phase timings).
  • Alignment fix: scoring/gradient passes receive full_ids[:, :-1], so
    completion token p is scored against the logits at p-1 (was off by one).
  • Completion mask: padding after an early stop no longer contributes to
    the loss or its normalization.
  • GPU inference-mode fix: cache buffers lazily move to the compute device
    on the first forward; under torch.inference_mode() (rollout or eval
    generation) they became inference tensors that the training pass could not
    update in place ("Inplace update to inference tensor outside
    InferenceMode"). batched_generate_fn / generate_completions gain
    no_inference_mode and the GRPO paths use it; eval/generation-only callers
    keep the default (faster) inference mode. GPU-only failure; on CPU buffers
    are already on-device.
  • With Apply final norm ln_f and logit softcapping before head in LongContextInference #141 applied, the rollout-vs-training-forward log-prob skew on a real
    model drops from ~4.8 to ~0.13 (bf16 noise).

Tooling

  • build_ext.py: add sm_86 (A10G / A10 / RTX 30xx) + PTX fallback; kernels
    previously targeted only sm_80 / sm_90.
  • examples/grpo_helmet.py: GRPO training on HELMET datasets via
    load_helmet_dev_eval, reward = sub_exact_match (same metric as the eval
    harness), periodic held-out eval, JSONL metrics. Validated end-to-end on
    HELMET nq @ 8k (prompts ~7k tokens, H2O cache 4096, real eviction).
  • examples/grpo_profile.py (completion-length sweep, optimized-vs-rescore),
    examples/grpo_context_sweep.py (dense-vs-H2O memory/time across context;
    on a 24 GB A10G dense OOMs at >=24k while H2O stays ~flat, see
    docs/GRPO_CONTEXT_SCALING.md), examples/longqa_task.py /
    longqa_quality.py (controlled eviction-quality probes used for Evicting KV cache corrupts generation (garbage output once prompt > cache_length) #140).

Testing


By submitting this pull request, I confirm that you can use, modify, copy, and
redistribute this contribution, under the terms of your choice.

@mmjerge

mmjerge commented Jul 31, 2026

Copy link
Copy Markdown
Contributor Author

End-to-end results: GRPO on HELMET through an evicting sparse cache

Ran the full pipeline in this PR on HELMET nq (8k bucket; real RAG-QA prompts, ~7k tokens avg), Qwen2.5-0.5B-Instruct, single A10G (24 GB), bf16, eager SDPA. Two GRPO arms, identical except the KV cache: H2O @ 4096 slots (actively evicting during rollouts and the gradient pass) vs dense (full attention). Reward = sub_exact_match (round 1) / max(EM, token-F1) partial credit (round 2), same metric family as the eval harness.

Cross-evaluation: training cache × inference cache (n=100 held-out records)

checkpoint ↓ · eval cache → dense (EM / F1) H2O@4096 (EM / F1)
base (untrained) 0.330 / 0.178 0.320 / 0.142
GRPO-trained under H2O 0.350 / 0.181 0.340 / 0.144
GRPO-trained under dense 0.340 / 0.180 0.300 / 0.132

Findings

  1. Sparse inference is essentially free on this task. Base model under H2O@4096 vs dense: 0.320 vs 0.330 EM (~1pp, within noise at n=100). With the eviction defaults from this PR in place, a cache at ~55% of the prompt length costs no measurable quality on real long-context QA. (Before the Evicting KV cache corrupts generation (garbage output once prompt > cache_length) #140 fix, every evicting configuration scored 0.000.)
  2. Training under the sparse cache does not damage the policy. All checkpoints are within noise of each other under matched eval conditions; the H2O-trained policy is indistinguishable from the dense-trained one.
  3. Cross-eval matters. Per-arm evals (each arm under its own cache, small n) had suggested a large H2O-vs-dense gap (0.292 vs 0.417); under matched inference and n=100 that gap disappears entirely — it was inference-cache conflation plus small-sample noise.

Training dynamics / footprint

  • GRPO steps through the evicting cache run stably end-to-end (rollout, scoring-free single-epoch path, chunked backward): ~13–24 s/step at ~7k-token prompts on the A10G, ~8 GB peak — roughly half the dense footprint, consistent with docs/GRPO_CONTEXT_SCALING.md (dense OOMs at ≥24k ctx on 24 GB while H2O stays flat).
  • The F1 partial-credit reward raised the fraction of steps carrying nonzero group-relative advantage ("signal rate") to ~0.7–0.8, from mostly-degenerate groups under binary EM.
  • Caveat: at lr 1e-6, ≤300 steps, one prompt × 8 rollouts per update, neither arm materially improves over base yet — the runs above validate stability and parity, not learning gains. A hotter run (lr ~5e-6, 500+ steps, accumulated prompts, multiple seeds) is the follow-up for the "H2O tracks dense improvement" curve.

Repro: examples/grpo_helmet.py (arms) and examples/grpo_helmet_crosseval.py (matrix), both in this PR.

@mmjerge

mmjerge commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up: the round-2 arms (F1 partial-credit reward, group size 8, 300 steps) cross-evaluate the same way (n=100):

checkpoint ↓ · eval cache → dense (EM / F1) H2O@4096 (EM / F1)
GRPO-trained under H2O (v2) 0.340 / 0.180 0.330 / 0.143
GRPO-trained under dense (v2) 0.330 / 0.179 0.300 / 0.139

Consistent with round 1: all cells within noise of the untrained base (0.330 / 0.320), sparse-inference cost ~1–3pp EM. The parity conclusions hold; the "learning gains" run (higher lr, more steps/prompts, multiple seeds) remains future work.

@mmjerge

mmjerge commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

Hot-run results: learning through the evicting cache is real (with caveats)

Second round with lr 5e-6, 8 rollouts x 2 accumulated prompts x 400 updates (gradient accumulation added in this PR), F1 partial-credit reward, single seed, n=100 cross-eval.

nq @ 8k (EM / F1):

checkpoint ↓ · eval → dense H2O@4096
base 0.330 / 0.178 0.320 / 0.142
GRPO-trained under H2O 0.450 / 0.166 0.420 / 0.167
GRPO-trained under dense 0.270 / 0.397 0.220 / 0.396

hotpot_qa @ 8k:

checkpoint ↓ · eval → dense H2O@4096
base 0.290 / 0.145 0.180 / 0.109
GRPO-trained under H2O 0.300 / 0.316 0.230 / 0.271
GRPO-trained under dense 0.410 / 0.436 0.320 / 0.390

Findings:

  1. Training through the evicting H2O cache produces real gains: +12pp EM on nq (0.33 -> 0.45), the strongest EM result in the matrix.
  2. Task dependence: on hotpot the dense arm gains more (+12pp vs +1pp EM), and H2O inference costs ~11pp EM on hotpot vs ~1pp on nq -- multi-hop tasks with dispersed evidence are where eviction hurts, both at inference and during training rollouts.
  3. Reward-shaping artifact in one arm: the dense/nq arm shows train reward rising (0.70) while eval EM collapses (0.44 -> 0.12) and F1 doubles -- the max(EM, token-F1) partial credit appears to have been gamed. Sample inspection is underway; the next round separates the reward (EM + small F1 tiebreaker) from the eval metric.
  4. Statistics: single seed, n=100 (SE ~5pp); the +/-12pp deltas are ~2-sigma. Seed replications are queued.

@mseeger

mseeger commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

@mmjerge I am aware of attention sink. And indeed, when I evaluated a pre-trained checkpoint with the lastrec policy, I did not get good results.

This is also why I implemented smart-lastrec.

I am OK with have a default grace period with lastrec of 1 (or a small number).

But I do not see why this would affect H2O (say). If the model uses the first token for attention, H2O would keep it in the cache (as it is heavily used).

In the pre-trained checkpoint evals, I did not see bad results just because of there being no grace period, at least not for H2O.

@mmjerge

mmjerge commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

Correction on the "reward hacking" hypothesis, after inspecting generations + seed-1 replication.

Sample generations (distinct NQ questions, dense eval cache):

  • base: verbose scaffolded answers — "The last time the Philadelphia Eagles played the New England Patriots was in Super Bowl LII, which took place..."
  • h2o-trained: same verbose style, largely intact.
  • dense-trained: terse answers — "Super Bowl LII in 2017.", "Mr Carson.", "March 10, 2017."

So the dense arm did not become verbose — the F1 partial credit taught it to strip scaffolding (terse answers score higher token-F1). The EM collapse is substantially a metric-style artifact: HELMET NQ targets often include leading prepositions ("in Super Bowl LII", "on October 21, 2016"), and sub_exact_match requires the target as a substring. The base/h2o style ("...was in Super Bowl LII...") contains it; the terse style ("Super Bowl LII in 2017") does not, even when semantically correct. Some genuine wrong answers are also present in all arms.

Seed-1 replication (nq, n=100) reproduces the seed-0 pattern: h2o-trained 0.44/0.45 EM (dense/h2o eval) vs base 0.33; dense-trained 0.31/0.29. So the pattern is robust across 2 seeds, but the safe interpretation is:

  • Robust claim: GRPO through an actively evicting H2O cache trains stably and moves the policy as much as dense training does — at ~half the memory. No evidence sparse-cache training damages learning.
  • Not yet safe: "H2O-trained beats dense-trained" on EM — the delta is confounded by answer-style/metric interaction (both arms shifted style under the F1 reward; the dense arm shifted further per update).
  • Fix for the next round: reward = EM + small F1 tiebreaker (e.g. EM + 0.2*F1) so style stays anchored to the eval metric, and report both metrics.

hotpot seed-1 arms are still running; will append when complete.

@mmjerge

mmjerge commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

@mseeger Agreed — I've replied in detail on #140 (with an isolated example), and updated this PR accordingly:

  • The factory comment now states the two mechanisms separately: init_grace_tokens for lastrec is about attention sinks (your point — H2O is not affected by sink eviction, since keep_initial_fraction + accumulated scores retain the initial tokens); grace_period for the H2O family is about fresh tokens having the lowest accumulated scores, i.e. the recently-read tail being evicted under pressure — the case the H2OKVCache docstring already anticipates.
  • If you'd prefer to keep H2O's grace_period default at 0 (no behavior change) and only default init_grace_tokens for lastrec, I'm happy to trim the PR to that — the measured data for the H2O case is in Evicting KV cache corrupts generation (garbage output once prompt > cache_length) #140 so the tradeoff is documented either way.

Also pushed since your comment: an em_f1 reward option for the HELMET driver (EM + 0.2*F1 — keeps the reward anchored to the eval metric after we caught the F1-only reward drifting answer style; details in the thread above).

mmjerge added 9 commits August 3, 2026 10:44
Evicting KV caches produced garbage generation (immediate EOS) on real models
whenever the prompt exceeded cache_length. The root cause is attention-sink
eviction, not incorrect attention math: trained LLMs place large attention mass
on the first few tokens (BOS / chat-template header) and use them as no-op
sinks (Xiao et al., StreamingLLM). Evicting them makes the softmax redistribute
that mass onto content tokens, which collapses generation. Toy random models
have no sink structure, which is why unit tests passed.

Instrumentation confirmed the mechanics are exact: on a partial-write chunk the
cached keys/values match a dense reference gathered at token_positions()
exactly, and the attention output matches a manual masked-softmax reference to
~1e-7. A 1-layer model shows no divergence; differences appear only once hidden
states compound across layers, which is expected lossiness.

KVCacheFactory now injects safe defaults unless the caller specifies them
(explicit values, including 0, are respected):

- lastrec: init_grace_tokens = min(16, cache_length // 8)
- h2o / h2o-vlen / qh2o / qh2o-vlen: grace_period = cache_length // 16
  (h2o-orig excluded; it rejects grace_period)

Measured on Qwen2.5-0.5B (A10G, retrieval with the answer in the last record,
evicting cache at cache_length=1024): lastrec 0.00 -> 0.90, which equals the
no-eviction baseline; h2o 0.00 -> 0.70.

test_utils.create_kv_cache pins the protections to 0 so unit tests keep
exercising the raw eviction mechanics.
Builds on the standalone GRPO loop from awslabs#128.

Correctness
- Alignment fix: the scoring/gradient passes are now fed full_ids[:, :-1], so
  completion token p is scored against the logits at position p-1 (next-token
  alignment). Previously they received the full sequence, i.e. an off-by-one.
- Completion mask: padding after an early stop no longer contributes to the
  loss, and num_target_entries normalizes by the real token count.

Single-epoch cache reuse (the optimization)
- GRPOLossHeadModel.set_batch now takes advantages first, with old_logps
  optional. When old_logps is None (single-epoch GRPO, the default), the old
  log-prob is policy_logp.detach() from the gradient pass's own forward, so the
  importance ratio is exactly 1 and the separate scoring forward pass is
  eliminated. This is what TRL does for num_iterations=1. Pass old_logps
  explicitly for multi-epoch updates.
- grpo_step gains rescore_old_logps (recompute old log-probs via a separate
  compute_logprobs pass, for multi-epoch or A/B comparison) and profile
  (per-phase timings: gen_time_ms / score_time_ms / grad_time_ms).

Generation
- batched_generate_fn gains opt-in return_logprobs, yielding
  (tokens, logprobs, active_mask) so a rollout can capture sampling log-probs
  and a padding mask without a second pass. Default behaviour is unchanged, so
  the evaluation harness is unaffected.
- batched_generate_fn gains no_inference_mode. The KV cache lazily moves its
  buffers to the compute device on the first forward; when that forward ran
  under torch.inference_mode() (the rollout), the buffers became inference
  tensors that the following gradient pass could not update in place, raising
  "Inplace update to inference tensor outside InferenceMode". The GRPO rollout
  now runs under torch.no_grad(); eval and generation-only callers keep
  inference mode. GPU-only failure (on CPU the buffers are already on-device).
- rollout.generate_completions_with_logprobs: rollout generation that also
  returns per-token log-probs and the completion mask.

Tooling
- build_ext.py: add sm_86 (A10G / A10 / RTX 30xx) and a PTX JIT fallback; the
  FlashInfer kernels previously targeted only sm_80 / sm_90.
- examples/grpo_qwen.py: runnable GRPO demo with flags for both paths and
  per-phase profiling.
- examples/grpo_profile.py: real-model completion-length sweep comparing the
  optimized and rescore paths (timings, peak memory, throughput).
- examples/grpo_context_sweep.py: dense vs H2O across context length (peak
  memory and step time, with graceful OOM handling).
- examples/longqa_task.py, examples/longqa_quality.py: controlled long-context
  key-value retrieval task and a dense-vs-sparse quality comparison, used to
  investigate awslabs#140.
- docs/GRPO_CONTEXT_SCALING.md: measured dense-vs-H2O context scaling on an
  A10G (H2O stays flat at ~14.9 GB from 2k to 32k while dense grows and OOMs at
  >=24k), with methodology and caveats.
…ing; add 2x2 cross-eval (train-cache x eval-cache)
@mmjerge

mmjerge commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto main now that #141 is merged — the carried copy of that commit dropped out as promised; this PR is now only the eviction defaults, GRPO changes, and tooling. Tests re-verified after rebase (test/rl, test_long_context, kvcache).

…nchanged

Maintainer decision on awslabs#140: keep defaults simple -- change only lastrec
(init_grace_tokens, attention-sink rationale) and leave the H2O family's
grace_period at 0; the recent-tail effect is application-level tuning (and
normalize_scores=True is an alternative mitigation).

The H2O grace_period our experiments rely on moves into the example/RL
configs explicitly (grpo_helmet, crosseval, longqa_quality).
@mmjerge

mmjerge commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

Seed-1 replication complete (same recipe: lr 5e-6, group 8 x 2 accumulated prompts x 400 updates, F1 reward, n=100 cross-eval). Both seeds agree on the direction of every cell.

hotpot_qa @ 8k, seed 1 (EM / F1; seed-0 in parentheses):

checkpoint ↓ · eval → dense H2O@4096
base 0.290 / 0.145 0.180 / 0.109
H2O-trained s1 0.280 / 0.122 (0.300 / 0.316) 0.290 / 0.120 (0.230 / 0.271)
dense-trained s1 0.380 / 0.381 (0.410 / 0.436) 0.330 / 0.335 (0.320 / 0.390)

Two-seed summary across both tasks (EM under dense eval, vs base):

arm nq hotpot_qa
H2O-trained +12pp / +11pp (0.45, 0.44) ~flat (0.30, 0.28)
dense-trained −6pp / −2pp (0.27, 0.31) +12pp / +9pp (0.41, 0.38)

Consistent, replicated pattern: each arm learns where its rollouts see the evidence. On single-hop nq, the H2O arm improves robustly (+11-12pp both seeds) while the dense arm's EM drop is the answer-style artifact documented above. On multi-hop hotpot — where H2O inference itself costs ~11pp (dispersed evidence gets evicted) — the dense arm improves (+9-12pp) while the H2O arm stays ~flat under dense eval (though it does gain under matched H2O inference: 0.18 -> 0.23/0.29 across seeds).

Takeaways: (1) GRPO through an actively evicting cache trains stably and can deliver real gains (nq), at ~half the memory; (2) on dispersed-evidence tasks the eviction-induced information loss in rollouts limits the learning signal — the same regime where H2O inference costs accuracy; (3) the F1-only reward's style drift is confirmed across seeds — subsequent runs use the EM-anchored em_f1 reward added in this PR.

@mmjerge

mmjerge commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Consolidated results: algorithms x caches x tasks (final for this round)

All: Qwen2.5-0.5B-Instruct, HELMET @ 8k (~7k-token prompts), single A10G, 400 updates (x2 accumulated prompts), lr 5e-6, n=100 cross-eval. "H2O" = h2o-torch-quantized8 @ 4096 slots (actively evicting; grace_period=cl/16 set at application level per #140 discussion). EM / F1 shown as dense-eval | H2O-eval.

nq @ 8k (base: 0.33/0.18 | 0.32/0.14):

method (training cache) dense-eval H2O-eval
SFT (H2O) 0.16 / 0.32 0.19 / 0.30
SFT (dense) 0.16 / 0.35 0.23 / 0.36
GRPO em_f1 (H2O) 0.28 / 0.33 0.26 / 0.32
GRPO em_f1 (dense) 0.29 / 0.39 0.29 / 0.42
RLOO em_f1 (H2O) 0.43 / 0.15 0.42 / 0.12

hotpot_qa @ 8k (base: 0.29/0.15 | 0.18/0.11):

method (training cache) dense-eval H2O-eval
SFT (H2O) 0.31 / 0.39 0.28 / 0.39
SFT (dense) 0.37 / 0.43 0.31 / 0.40
GRPO em_f1 (H2O) 0.38 / 0.44 0.33 / 0.40
GRPO em_f1 (dense) 0.42 / 0.46 0.39 / 0.43

Findings

  1. Training through the actively evicting cache works across all three algorithms (GRPO, RLOO, SFT) — stable end-to-end, ~half the dense memory footprint.
  2. RL > SFT on both tasks (hotpot EM 0.42 vs 0.37; nq 0.29-0.43 vs 0.16).
  3. Sparse-trained ~ dense-trained: GRPO H2O arm within 1-4pp EM of dense (nq 0.28 vs 0.29; hotpot 0.38 vs 0.42), and the RLOO H2O arm is the best nq result overall (0.43 EM) — a sparse-trained model beating every dense-trained one.
  4. RLOO's nq gain is the most credible EM improvement of the campaign: unlike earlier arms, its F1 stays at base level (0.15 vs 0.18), i.e. the answer style did not drift — it gets more answers right within the base model's own style. (RLOO also showed the highest signal rate, 0.93-0.94: leave-one-out advantages don't divide by group std, so small reward spreads still carry gradient.)
  5. Training shrinks the sparse-inference penalty: base loses 11pp EM under H2O eval on hotpot; trained models lose 3-5pp.
  6. Caveats: nq's substring-EM remains style-sensitive (SFT/GRPO F1-heavy arms drop nq EM while F1 rises), single seed for round-3/SFT/RLOO (the round-2 pattern replicated across 2 seeds), 0.5B model, 2 tasks.

RLOO mode and the SFT driver live on stacked branches (rl-rloo, sft-helmet) to keep this PR's scope as reviewed; happy to open follow-up PRs once this one lands.

@mseeger

mseeger commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Hello, I am a bit confused about this PR. Maybe it mixes two things?

  • Change of certain defaults in order to avoid poor behavior of lastrec
  • New code for the RL effort

Could you split the two? As I said, I'd like to be conservative on the defaults, but I am more than happy with new code on the RL front, if this helps.

@mseeger

mseeger commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Please also try to document findings in markdown files checked in. The PR messages will be lost in the end, or very hard to find.

mmjerge added 2 commits August 6, 2026 13:41
Restores keys_values/kvcache/factory.py and test_utils.py to main; the
lastrec attention-sink default now lives in a separate PR so this one is
RL-only. Application-level grace settings in the GRPO/eval drivers are
unaffected (they pass grace_period explicitly).
Consolidates the PR-thread results into docs/GRPO_HELMET_RESULTS.md:
round-1 parity cross-eval, round-2 learning gains + the answer-style/
metric artifact analysis and seed replication, and the round-3
algorithms x caches x tasks matrix (SFT / GRPO / RLOO), with caveats
and repro commands.
@mmjerge

mmjerge commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

@mseeger Both points addressed:

  1. Split done. The eviction defaults now live in Fix #140: factory default init_grace_tokens for lastrec (attention sinks) #144 (lastrec-only, conservative scope as you preferred); this PR no longer touches factory.py / test_utils.py and is RL-only (GRPO loop/loss/rollout, generation support, HELMET drivers, docs). The two PRs are independent — the RL drivers set grace at application level, so neither depends on the other's merge order.

  2. Findings checked in. docs/GRPO_HELMET_RESULTS.md (in this PR) consolidates the whole campaign from the thread above: the round-1 parity cross-eval, the round-2 learning gains with the answer-style/metric artifact analysis and seed replication, the round-3 algorithms × caches × tasks matrix (SFT/GRPO/RLOO), caveats, and repro commands.

Re-verified after the split: test/rl (19 passed), test/test_long_context.py + test/generate (17 passed), test/kvcache (956 passed on #144).

records[:n] in file order covered only 17 distinct questions at n=100
on nq (the eval split holds ~6 context variants per question), inflating
variance well past the nominal-n SE. Select one instance per question
first, then second variants, etc. At n=100 on nq this now covers all
100 eval questions.
Documents the unseeded-eval-split finding (awslabs#145) and its impact on the
published tables (17 distinct questions at n=100; wider error bars), and
adds the first corrected numbers: a base-model H2O budget sweep under
the seeded split with full question coverage. Headline: sparse inference
costs ~10pp EM on nq at 4096 slots (not ~1pp), ~7pp on hotpot, with a
cliff at 1024 slots. Full-matrix re-baselining queued.
Comment thread keys_values/generate/base.py Outdated
)


def _sampled_token_logprobs(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is specific to the RL work, it should move to keys_values/rl. Otherwise, you'd have to argue why it is more generally needed (I do not get it from the docstring).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed — it's RL-specific. Moved it to keys_values/rl/grpo/rollout.py as sampled_token_logprobs (next to the rollout code that captures old-policy log-probs). The generic generation loop lazy-imports it inside the return_logprobs branch to avoid a generate ↔ rl import cycle. Done in the latest push.

Comment thread build_ext.py Outdated
"-U__CUDA_NO_HALF2_OPERATORS__",
"-gencode=arch=compute_80,code=sm_80",
"-gencode=arch=compute_90,code=sm_90",
# SASS for the common data-center / workstation GPUs, plus

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain why this change is needed? This is a script needed to build the FlashInfer stuff by Vihang.

  • Do you need this for the different architectures?
  • If so, did you test this on these?
  • This will not break the current setup?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right that this doesn't belong in the RL PR — I've reverted build_ext.py to match main in the latest push, so this PR no longer touches Vihang's build script.

For context on what it was: I'd added sm_86 (A10G/A10/RTX-30xx) plus a PTX fallback line, because the A10G is the box I run the GRPO experiments on and the stock sm_80+sm_90 build won't load FlashInfer there. The retained sm_80/sm_90 SASS lines were untouched, so it wouldn't have broken the A100/H100 setup — but it was orthogonal to RL and I hadn't tested it beyond the A10G, so it's better out of scope here. If it's useful I'll open a separate one-line PR against main for the A10G arch, with you/Vihang as reviewers.

…ch change

- sampled_token_logprobs moved from generate/base.py to rl/grpo/rollout.py
  (RL-specific; generate loop lazy-imports it to avoid an import cycle)
- build_ext.py reverted to match main (sm_86/PTX arch change was orthogonal
  to the RL scope of this PR; will propose separately if wanted)
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.

Evicting KV cache corrupts generation (garbage output once prompt > cache_length)

2 participants