Skip to content

Enable FlashInfer checkpointing SSU STP no-fallback path - #43439

Draft
danielafrimi wants to merge 6 commits into
vllm-project:mainfrom
danielafrimi:flashinfer-checkpointing-ssu-stp-no-fallback-clean
Draft

danielafrimi wants to merge 6 commits into
vllm-project:mainfrom
danielafrimi:flashinfer-checkpointing-ssu-stp-no-fallback-clean

Conversation

@danielafrimi

Copy link
Copy Markdown
Contributor

Summary

This PR enables the FlashInfer checkpointing SSU path for STP without falling back to the older SSU implementation.

  • Added the checkpointing SSU/STP dispatch path and replay plumbing in ssu_dispatch.py.
  • Updated the Mamba mixer and Mamba utility code so prefill/decode replay uses the checkpointing SSU state layout correctly.
  • Wired the feature through Mamba config, engine args, v1 worker utilities, and the model registrations that use the Mamba/Mamba2 mixer stack.
  • Fixed decode replay behavior for the no-fallback path so the FlashInfer checkpointing SSU kernel is used directly.
  • Added focused coverage for checkpointing SSU/STP behavior in tests/kernels/mamba/test_checkpointing_ssu_stp.py, plus worker utility coverage for the new config/routing.

Validation

  • GSM8K 5-shot, full 1319/1319 sample run, normal CUDA graph, no fallback:
    • strict exact match: 0.9234268385
    • flexible exact match: 0.9302501895
    • artifact: /lustre/fsw/coreai_nvfm_llm/dafrimi/vllm-stp-no-fallback-artifacts/evals/gsm8k-safe-window-normal-cg-full-20260522_000209/nemotron-stp-safe-window-normal-cg/results_2026-05-22T00-55-08.305740.json

For comparison, old FlashInfer STP full runs were in the same band:

  • gsm8k-stp-oldfi-full-conc50-20260520: strict 0.9188779378, flexible 0.9310083397
  • gsm8k-stp-oldfi-full-rerun2-conc50-nobs-20260520: strict 0.9166034875, flexible 0.9302501895

Notes

  • Branch: flashinfer-checkpointing-ssu-stp-no-fallback-clean
  • Head commit: ee869a7e32a205c7ed49ca2461acc8913a1614ac
  • The MTP/new-kernel follow-up work is split onto flashinfer-checkpointing-ssu-mtp-new-kernel.

Daniel Afrimi added 5 commits May 19, 2026 06:42
Signed-off-by: Daniel Afrimi <dafrimi@login-lyris01.lyris.clusters.nvidia.com>
Adds STP replay coverage for non-contiguous state indices, destination slot moves, and a production-shaped reproducer for remaining checkpointing drift. Also avoids the known large-batch checkpointing_ssu drift by advancing STP replay one request at a time.\n\nValidation: python -m pytest tests/kernels/mamba/test_checkpointing_ssu_stp.py -q -> 5 passed, 1 xfailed.\n\nKnown remaining issue: live concurrent four-prompt serving with async scheduling + CUDA graph is still under investigation; full GSM8K has not been rerun on this commit yet.

Signed-off-by: Daniel Afrimi <dafrimi@login-lyris01.lyris.clusters.nvidia.com>
GSM8K limit-50 conc50 with CUDA graph + async: new checkpointing STP 0.92 strict / 0.92 flexible, matching old FI STP baseline 0.92 strict / 0.92 flexible.

Signed-off-by: Daniel Afrimi <dafrimi@login-lyris01.lyris.clusters.nvidia.com>
@mergify mergify Bot added the v1 label May 22, 2026
@mergify

mergify Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @danielafrimi.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

@mergify mergify Bot added the needs-rebase label May 22, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

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.

Code Review

This pull request implements FlashInfer checkpointing Selective State Update (SSU) for Mamba models, introducing a configurable checkpoint_interval and expanding the Mamba KV cache to include replay and tracker tensors. The changes update the MambaMixer2 layer and FlashInferSSUBackend to utilize new FlashInfer kernels, while also extending KVBlockZeroer to support Mamba state. Critical feedback was provided regarding the efficiency of the backend implementation, specifically noting performance bottlenecks caused by manual loops, frequent memory allocations, and torch.cat operations. Furthermore, a potential race condition was identified in the slot-copying logic during CUDA stream capture when source and destination indices overlap.

Comment on lines +538 to +593
if use_grouped_checkpointing:
for group_idx in range(n_groups):
group_head_start = group_idx * heads_per_group
group_head_end = group_head_start + heads_per_group
group_slice = slice(group_idx, group_idx + 1)
old_B_snapshot = old_B[:, :, :, group_slice, :].contiguous()
old_B_group = torch.empty_like(old_B_snapshot)
for head_start in range(group_head_start, group_head_end):
head_end = head_start + 1
head_slice = slice(head_start, head_end)
state_group = state[:, head_slice].contiguous()
old_x_group = old_x[:, :, head_slice].contiguous()
old_B_group.copy_(old_B_snapshot)
old_dt_group = old_dt[:, :, head_slice, :].contiguous()
old_cumAdt_group = old_cumAdt[
:, :, head_slice, :
].contiguous()
out_group = torch.empty_like(x_ckpt[:, :, head_slice, :])
self._checkpointing_kernel(
state_group,
old_x_group,
old_B_group,
old_dt_group,
old_cumAdt_group,
cache_buf_idx,
prev_num_accepted_tokens,
x_ckpt[:, :, head_slice, :].contiguous(),
dt_ckpt[:, :, head_slice, :],
A[head_slice],
B_ckpt[:, :, group_slice, :].contiguous(),
C_ckpt[:, :, group_slice, :].contiguous(),
out_group,
D=D[head_slice] if D is not None else None,
z=(
z_ckpt[:, :, head_slice, :].contiguous()
if z_ckpt is not None
else None
),
dt_bias=(
dt_bias[head_slice] if dt_bias is not None else None
),
dt_softplus=dt_softplus,
state_batch_indices=kernel_state_indices,
pad_slot_id=null_block_id,
rand_seed=rand_seed,
philox_rounds=self._mamba_config.stochastic_rounding_philox_rounds
or 10,
cu_seqlens=ckpt_cu_seqlens,
max_seqlen=kernel_max_seqlen,
)
state[:, head_slice].copy_(state_group)
old_x[:, :, head_slice].copy_(old_x_group)
old_dt[:, :, head_slice, :].copy_(old_dt_group)
old_cumAdt[:, :, head_slice, :].copy_(old_cumAdt_group)
out_ckpt[:, :, head_slice, :].copy_(out_group)
old_B[:, :, :, group_slice, :].copy_(old_B_group)

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.

high

The manual loop over groups and heads in _run_checkpointing_kernel is highly inefficient. It performs multiple .contiguous() calls on the state tensor (which copies the entire state for all cache slots) and allocates new tensors using torch.empty_like inside the nested loop. For a model with 128 heads, this results in 128 kernel launches and 128 sets of large memory allocations/copies per layer per step. This will significantly degrade performance and increase memory pressure. The logic should be refactored to use a single kernel launch or at least avoid per-head allocations and full-state copies.

Comment on lines +686 to +690
x_chunk = torch.cat(x_parts, dim=0)
dt_chunk = torch.cat(dt_parts, dim=0)
B_chunk = torch.cat(B_parts, dim=0)
C_chunk = torch.cat(C_parts, dim=0)
z_chunk = torch.cat(z_parts, dim=0) if z_parts is not None else None

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.

high

Using torch.cat inside the chunking loop for varlen sequences is inefficient as it creates new tensors and involves additional memory copies on every chunk iteration. This can be a performance bottleneck for long sequences or large batches. Consider using pre-allocated buffers or refactoring the kernel to handle varlen sequences more natively without concatenation.

Comment on lines +921 to +932
_copy_checkpointing_slots_kernel[
(n_slots, triton.cdiv(slot_size, block))
](
tensor,
src_indices,
dst_indices,
slot_size,
slot_stride,
pad_slot_id,
BLOCK=block,
)
continue

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.

high

The _copy_checkpointing_slots_kernel is used when no scratch buffer is available, which occurs during CUDA stream capture (as seen in _get_copy_scratch at line 977). This kernel performs a direct batched copy that is unsafe when source and destination indices overlap (e.g., moving state from slot A to B while another move goes from B to C). Since Triton does not guarantee the execution order of programs, this can lead to race conditions where a slot is overwritten before its original value is read. To ensure correctness during CUDA graph capture, the scratch buffer should be pre-allocated during the warmup/profile phase so it is available during capture.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant