Skip to content

fix(models): dense-cache sliding-window models still degenerate on rolled-over over-window prefill (offset>0 && seq>window) #413

Description

@inureyes

Problem / Background

PR #412 (#408) hardened sliding-window >window single-pass prefill across models. For dense-KVCache sliding-window models (cohere2, gemma3n, olmo3) it routes mask construction through the shared create_sliding_window_prefill_mask(size, sliding_offset, window) helper and slices K/V to the mask's key axis.

That helper only builds the uncapped full windowed mask when size > window && sliding_offset == 0 (a FRESH prefill). For sliding_offset > 0 && seq_len > window (a rolled-over / continued prefill, e.g. a multi-turn continuation chunk longer than the window with prior cache, or a chunked-prefill chunk larger than the window) it falls through to the clamped [seq_len, window] mask.

This was explicitly left out of #412's scope. It is pre-existing (NOT introduced by #412): the behavior is byte-identical to the pre-#412 code path, so this is a latent degeneration, not a regression.

The bug

A dense KVCache retains ALL keys (unlike a RotatingKVCache, which trims to window). With the clamped [seq_len, window] mask, whose tril diagonal window - seq_len < 0, and the model slicing K/V to the trailing window keys, the earliest query rows (logical position < seq_len - window) are stranded with an all--inf row. That produces NaN, which decodes to <pad>.

So a rolled-over over-window prefill on cohere2 / gemma3n / olmo3 degenerates to <pad> / NaN output for the early positions.

Root cause (the asymmetry)

The shared helper's gate size > window && sliding_offset == 0 is NARROWER than the two other windowed-mask gates in the tree:

Models that reach causal_attention's windowed path (e.g. baichuan, gemma4) therefore handle the offset>0 over-window case correctly. The dense-cache models that build the helper mask directly do not, because of the extra sliding_offset == 0 condition.

Proposed Solution

Extend create_sliding_window_prefill_mask (or the dense-cache call sites) to build the correct full windowed mask for the sliding_offset > 0 && seq_len > window case as well: the full [seq_len, seq_len + sliding_offset] windowed-causal mask over all retained keys, mirroring create_causal_mask_with_window_full with a non-zero offset. Keep the dense-cache mask-key-axis slicing consistent (a full mask keeps every key).

Coordinate with #410 (the sliding_prefill_mask consolidation), since both touch this helper and its gate. Verify that gemma3 / gemma4 single-process and the RotatingKVCache models are not affected by the helper change (RotatingKVCache trims to window, so its offset>0 path differs and must not be regressed).

Acceptance Criteria

  • A rolled-over over-window prefill (offset>0, seq>window) on a dense-cache sliding-window model (cohere2 / gemma3n / olmo3) produces coherent output instead of <pad> / NaN.
  • Fresh over-window and within-window paths are unchanged.
  • RotatingKVCache models and gemma3 / gemma4 are not regressed.
  • A regression test at the helper / mask layer covers the offset>0 over-window case.

Technical Considerations

Metadata

Metadata

Assignees

Labels

area:inferenceGeneration, sampling, decoding (incl. speculative, DRY)area:modelsModel architectures, weights, loading, metadatapriority:mediumMedium prioritystatus:doneCompletedtype:bugBug fixes, error corrections, or issue resolutions

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions