Repository navigation
feat(kda): add fused speculative decode kernel #4809
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
39bf748
3fe7c6a
040416a
1a63922
5dda39a
609ec78
de01b57
0e280eb
32b1a36
c015bdf
8467255
174e569
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -29,6 +29,7 @@ | |||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| from .api_logging import flashinfer_api | ||||||||||||||||||||||||||||||||
| from .trace.templates.kda import ( | ||||||||||||||||||||||||||||||||
| fused_kda_decode_packed_trace, | ||||||||||||||||||||||||||||||||
| fused_kda_decode_trace, | ||||||||||||||||||||||||||||||||
| packed_kda_decode_trace, | ||||||||||||||||||||||||||||||||
| recurrent_kda_trace, | ||||||||||||||||||||||||||||||||
|
|
@@ -45,6 +46,16 @@ | |||||||||||||||||||||||||||||||
| _FUSED_KDA_DECODE_AVAILABLE = False | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| from .kda_kernels import run_packed_kda_decode as _run_packed_kda_decode | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||
| from .kda_kernels.fused_kda_decode_multitoken import ( | ||||||||||||||||||||||||||||||||
| run_fused_kda_decode_multitoken as _run_fused_kda_decode_packed, | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| _FUSED_KDA_DECODE_PACKED_AVAILABLE = True | ||||||||||||||||||||||||||||||||
| except (ImportError, RuntimeError): | ||||||||||||||||||||||||||||||||
| _run_fused_kda_decode_packed = None | ||||||||||||||||||||||||||||||||
| _FUSED_KDA_DECODE_PACKED_AVAILABLE = False | ||||||||||||||||||||||||||||||||
| from .kda_kernels import run_recurrent_kda as _run_recurrent_kda | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||
|
|
@@ -476,6 +487,151 @@ def fused_kda_decode( | |||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| @flashinfer_api(trace=fused_kda_decode_packed_trace) | ||||||||||||||||||||||||||||||||
| def fused_kda_decode_packed( | ||||||||||||||||||||||||||||||||
| x: torch.Tensor, | ||||||||||||||||||||||||||||||||
| weight: torch.Tensor, | ||||||||||||||||||||||||||||||||
| conv_state: torch.Tensor, | ||||||||||||||||||||||||||||||||
| raw_gate: torch.Tensor, | ||||||||||||||||||||||||||||||||
| raw_beta: torch.Tensor, | ||||||||||||||||||||||||||||||||
| A_log: torch.Tensor, | ||||||||||||||||||||||||||||||||
| dt_bias: torch.Tensor, | ||||||||||||||||||||||||||||||||
| state_indices: torch.Tensor, | ||||||||||||||||||||||||||||||||
| query_start_loc: torch.Tensor, | ||||||||||||||||||||||||||||||||
| num_accepted_tokens: torch.Tensor, | ||||||||||||||||||||||||||||||||
| state: torch.Tensor, | ||||||||||||||||||||||||||||||||
| output_gate: torch.Tensor, | ||||||||||||||||||||||||||||||||
| norm_weight: torch.Tensor, | ||||||||||||||||||||||||||||||||
| lower_bound: Optional[float] = -5.0, | ||||||||||||||||||||||||||||||||
| norm_eps: float = 1e-5, | ||||||||||||||||||||||||||||||||
| output: Optional[torch.Tensor] = None, | ||||||||||||||||||||||||||||||||
| ) -> torch.Tensor: | ||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. API nits: could be fixed before merge here, otherwise could become expensive to clean up after release (we can't change anything about this API after unless its bwd compatible or go through the long process for deprecation). (a) Two parameters are inserted mid-signature, and nothing is keyword-only.
All 16 parameters on the new op are Ask: make (b) The name inverts the modifier order of the op 190 lines above it. (c) The packed facade has no
The concrete consequence lands on the
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I renamed it to packed_fused_kda_decode and moved query_start_loc and num_accepted_tokens behind *, so the shared positional arguments stay aligned with fused_kda_decode. I kept this facade CuTe-only rather than exposing backend=: T>1 exists only in CuTe and Cake doesn't consume packed metadata; the docstring now says that explicitly. I'd add backend selection if Cake gains packed semantics. |
||||||||||||||||||||||||||||||||
| r"""Run packed T>=1 fused KDA with per-token cache checkpoints. | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| Tokens use a packed ragged layout described by ``query_start_loc``. | ||||||||||||||||||||||||||||||||
| ``state_indices`` has shape ``[N, T]``, where T is the maximum verification | ||||||||||||||||||||||||||||||||
| length. ``num_accepted_tokens[n] - 1`` selects the source checkpoint and | ||||||||||||||||||||||||||||||||
| convolution-history offset for sequence ``n``; token ``t`` writes its | ||||||||||||||||||||||||||||||||
| recurrent checkpoint to ``state_indices[n, t]``. The convolution cache is | ||||||||||||||||||||||||||||||||
| one rolling window of length ``T + 2`` in ``state_indices[n, 0]``. | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| T=1 dispatches to :func:`fused_kda_decode`, including its bfloat16-state and | ||||||||||||||||||||||||||||||||
| softplus-gate support. T>1 uses the experimental SM10x packed backend, | ||||||||||||||||||||||||||||||||
| which currently requires FP32 recurrent state and a finite negative | ||||||||||||||||||||||||||||||||
| ``lower_bound``. Both paths require head dimension 128, convolution width | ||||||||||||||||||||||||||||||||
| four, and 12, 24, 32, 48, or 96 heads. Other tensors follow | ||||||||||||||||||||||||||||||||
| :func:`fused_kda_decode`, with the packed row count supplied by ``x``. | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| ``query_start_loc`` must be contiguous int32 of shape ``[N+1]``, start at | ||||||||||||||||||||||||||||||||
| zero, and contain nondecreasing offsets within ``x`` with active lengths no | ||||||||||||||||||||||||||||||||
| larger than T. | ||||||||||||||||||||||||||||||||
| ``num_accepted_tokens`` must be contiguous int32 of shape ``[N]`` with | ||||||||||||||||||||||||||||||||
| values in ``[1, T]``. Active recurrent destinations must be positive cache | ||||||||||||||||||||||||||||||||
| slots and must not alias destinations from another active sequence; | ||||||||||||||||||||||||||||||||
| non-positive or zero-length rows are padding and do not mutate either cache. | ||||||||||||||||||||||||||||||||
| These value constraints are caller-owned so CUDA Graph replay never performs | ||||||||||||||||||||||||||||||||
| a device-to-host validation sync. | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| Args: | ||||||||||||||||||||||||||||||||
| x: | ||||||||||||||||||||||||||||||||
| Packed QKV projection with shape ``[num_rows, 3 * H * 128]`` and | ||||||||||||||||||||||||||||||||
| dtype bfloat16. | ||||||||||||||||||||||||||||||||
| weight: | ||||||||||||||||||||||||||||||||
| Depthwise convolution weights with shape ``[3, 4, H * 128]`` and | ||||||||||||||||||||||||||||||||
| dtype float32. | ||||||||||||||||||||||||||||||||
| conv_state: | ||||||||||||||||||||||||||||||||
| Paged bfloat16 convolution cache. T=1 uses history length three; | ||||||||||||||||||||||||||||||||
| T>1 uses the extended rolling history length ``T + 2``. | ||||||||||||||||||||||||||||||||
| raw_gate: | ||||||||||||||||||||||||||||||||
| Raw recurrence gate with shape ``[1, num_rows, H, 128]`` and dtype | ||||||||||||||||||||||||||||||||
| bfloat16. | ||||||||||||||||||||||||||||||||
| raw_beta: | ||||||||||||||||||||||||||||||||
| Raw delta-rule learning-rate logits with shape | ||||||||||||||||||||||||||||||||
| ``[1, num_rows, H]`` and dtype bfloat16. | ||||||||||||||||||||||||||||||||
| A_log: | ||||||||||||||||||||||||||||||||
| Log decay parameter with ``H`` elements and dtype float32. | ||||||||||||||||||||||||||||||||
| dt_bias: | ||||||||||||||||||||||||||||||||
| Per-channel decay bias with ``H * 128`` elements and dtype float32. | ||||||||||||||||||||||||||||||||
| state_indices: | ||||||||||||||||||||||||||||||||
| Contiguous int32 cache slots with shape ``[N, T]``. For T>1, | ||||||||||||||||||||||||||||||||
| column ``t`` is the recurrent checkpoint destination for token | ||||||||||||||||||||||||||||||||
| ``t``. Column zero also selects the rolling convolution cache. | ||||||||||||||||||||||||||||||||
| query_start_loc: | ||||||||||||||||||||||||||||||||
| Contiguous int32 packed-row offsets with shape ``[N + 1]``. | ||||||||||||||||||||||||||||||||
| num_accepted_tokens: | ||||||||||||||||||||||||||||||||
| Contiguous int32 accepted-token counts with shape ``[N]``. | ||||||||||||||||||||||||||||||||
| state: | ||||||||||||||||||||||||||||||||
| Paged recurrent state with shape ``[num_slots, H, 128, 128]``. | ||||||||||||||||||||||||||||||||
| T=1 accepts float32 or bfloat16; T>1 requires float32. | ||||||||||||||||||||||||||||||||
| output_gate: | ||||||||||||||||||||||||||||||||
| Gated RMSNorm logits with shape ``[num_rows, H, 128]`` or | ||||||||||||||||||||||||||||||||
| ``[1, num_rows, H, 128]`` and dtype bfloat16. | ||||||||||||||||||||||||||||||||
| norm_weight: | ||||||||||||||||||||||||||||||||
| RMSNorm weight with 128 elements and dtype float32. | ||||||||||||||||||||||||||||||||
| lower_bound: | ||||||||||||||||||||||||||||||||
| Negative recurrence-gate lower bound. T=1 also accepts ``None`` | ||||||||||||||||||||||||||||||||
| for the softplus gate; T>1 requires a finite negative value. | ||||||||||||||||||||||||||||||||
| norm_eps: | ||||||||||||||||||||||||||||||||
| Non-negative RMSNorm epsilon. | ||||||||||||||||||||||||||||||||
| output: | ||||||||||||||||||||||||||||||||
| Optional preallocated contiguous bfloat16 output with shape | ||||||||||||||||||||||||||||||||
| ``[1, num_rows, H, 128]``. | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| Returns: | ||||||||||||||||||||||||||||||||
| The packed bfloat16 output with shape ``[1, num_rows, H, 128]``. | ||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||
|
coderabbitai[bot] marked this conversation as resolved.
Resolved
|
||||||||||||||||||||||||||||||||
| if state_indices.ndim == 2 and state_indices.shape[1] == 1: | ||||||||||||||||||||||||||||||||
| if _run_fused_kda_decode is None: | ||||||||||||||||||||||||||||||||
| raise NotImplementedError("fused KDA decode backend is unavailable") | ||||||||||||||||||||||||||||||||
| legacy_state_indices = state_indices[:, 0] | ||||||||||||||||||||||||||||||||
| if x.shape[0] != legacy_state_indices.shape[0]: | ||||||||||||||||||||||||||||||||
| packed_rows = torch.arange( | ||||||||||||||||||||||||||||||||
| x.shape[0], dtype=query_start_loc.dtype, device=x.device | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| sequence_indices = torch.searchsorted( | ||||||||||||||||||||||||||||||||
| query_start_loc[1:], packed_rows, right=True | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| legacy_state_indices = legacy_state_indices.index_select( | ||||||||||||||||||||||||||||||||
| 0, sequence_indices | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The guard at Suggest dropping the guard and always remapping, which is a no-op when the mapping really is the identity, with one null slot appended so out-of-range rows land on the backend's existing null path instead of faulting: legacy_state_indices = torch.cat(
[state_indices[:, 0], state_indices.new_zeros(1)]
).index_select(0, torch.searchsorted(query_start_loc[1:], torch.arange(
x.shape[0], dtype=query_start_loc.dtype, device=x.device), right=True))Both shapes below are ones the Output on a B200 (SM100, cc 10.0): In case B, row 0 is correct and row 1 is wrong, which is what pins the diagnosis to the mapping rather than to the kernel. Repro script (save at the repo root,
|
||||||||||||||||||||||||||||||||
| return _run_fused_kda_decode( | ||||||||||||||||||||||||||||||||
| x=x, | ||||||||||||||||||||||||||||||||
| weight=weight, | ||||||||||||||||||||||||||||||||
| conv_state=conv_state, | ||||||||||||||||||||||||||||||||
| raw_gate=raw_gate, | ||||||||||||||||||||||||||||||||
| raw_beta=raw_beta, | ||||||||||||||||||||||||||||||||
| A_log=A_log, | ||||||||||||||||||||||||||||||||
| dt_bias=dt_bias, | ||||||||||||||||||||||||||||||||
| state_indices=legacy_state_indices, | ||||||||||||||||||||||||||||||||
| state=state, | ||||||||||||||||||||||||||||||||
| output_gate=output_gate, | ||||||||||||||||||||||||||||||||
| norm_weight=norm_weight, | ||||||||||||||||||||||||||||||||
| lower_bound=lower_bound, | ||||||||||||||||||||||||||||||||
| norm_eps=norm_eps, | ||||||||||||||||||||||||||||||||
| output=output, | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
|
coderabbitai[bot] marked this conversation as resolved.
Resolved
|
||||||||||||||||||||||||||||||||
| if _run_fused_kda_decode_packed is None: | ||||||||||||||||||||||||||||||||
| raise NotImplementedError("packed fused KDA decode backend is unavailable") | ||||||||||||||||||||||||||||||||
| return _run_fused_kda_decode_packed( | ||||||||||||||||||||||||||||||||
| x=x, | ||||||||||||||||||||||||||||||||
| weight=weight, | ||||||||||||||||||||||||||||||||
| conv_state=conv_state, | ||||||||||||||||||||||||||||||||
| raw_gate=raw_gate, | ||||||||||||||||||||||||||||||||
| raw_beta=raw_beta, | ||||||||||||||||||||||||||||||||
| A_log=A_log, | ||||||||||||||||||||||||||||||||
| dt_bias=dt_bias, | ||||||||||||||||||||||||||||||||
| state_indices=state_indices, | ||||||||||||||||||||||||||||||||
| query_start_loc=query_start_loc, | ||||||||||||||||||||||||||||||||
| num_accepted_tokens=num_accepted_tokens, | ||||||||||||||||||||||||||||||||
| state=state, | ||||||||||||||||||||||||||||||||
| output_gate=output_gate, | ||||||||||||||||||||||||||||||||
| norm_weight=norm_weight, | ||||||||||||||||||||||||||||||||
| lower_bound=lower_bound, | ||||||||||||||||||||||||||||||||
| norm_eps=norm_eps, | ||||||||||||||||||||||||||||||||
| output=output, | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # Cached trivial metadata for the frozen mode's uniform batched form: the | ||||||||||||||||||||||||||||||||
| # arange cu_seqlens / slot indices are content-stable per (device, B, T), so | ||||||||||||||||||||||||||||||||
| # building them once keeps the hot path allocation- and launch-free (required | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.