🚨 [Kernels] Refactor all linear attn models & native kernels fallback - #47630
Conversation
|
The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update. |
Cyrilvallez
left a comment
There was a problem hiding this comment.
Very very nice, cannot wait for this PR! Just checked bamba for now to give a few early thoughts!
| # Only kernels can use this shortcircuit, fallback to normal torch otherwise | ||
| if fused_output is not None: | ||
| return fused_output |
There was a problem hiding this comment.
This makes it a bit awkward IMO... Any way to check if the function was found in kernels instead?
There was a problem hiding this comment.
Hard to do because kernels can be swapped at runtime at any time
| ' I will be talking about the importance of the internet in our lives.\nThe internet is a global' | ||
| ], | ||
| ("cuda", 9): [ | ||
| ' I am going to talk about the “Theory of Relativity” by Albert Einstein.\n', |
There was a problem hiding this comment.
Not sure what caused the change on A10 but the output is the same across all devices now so I wouldn't see it as a regression as the outputs are aligned. I assumed some small matmul diff causing slightly different gens previously
|
This comment contains models: ["models/falcon_mamba"] |
|
run-slow: bamba, falcon_h1, falcon_mamba, granitemoehybrid, inkling, jamba, lfm2, lfm2_moe, mamba, mamba2, minimax, nemotron_h, olmo_hybrid, qwen3_5, qwen3_5_moe, qwen3_next |
|
This comment contains models: ["models/bamba", "models/falcon_h1", "models/falcon_mamba", "models/granitemoehybrid", "models/inkling", "models/jamba", "models/lfm2", "models/lfm2_moe", "models/mamba", "models/mamba2", "models/minimax", "models/nemotron_h", "models/olmo_hybrid", "models/qwen3_5", "models/qwen3_5_moe", "models/qwen3_next"] |
Cyrilvallez
left a comment
There was a problem hiding this comment.
All right, very nice work! Still a few important details to fix, but should not be much efforts. Most notably:
- Make sure we align the name of the torch functions and kernels function everywhere, as most are not the same. This makes it much harder to understand what we do. Having the same name makes it clear for anyone that it's supposed to do the same thing in pure torch
- Correctly guard all conv parts with
and not record_pastfor speculative decoding/mtp etc, and be coherent between all our implems
| @use_kernel_func_from_hub_with_fallback( | ||
| "mamba_split_conv1d_scan_combined", | ||
| "mamba_ssm", | ||
| ) | ||
| def mamba2_split_conv1d_scan_combined( | ||
| zxbcdt: torch.Tensor, | ||
| conv1d_weight: torch.Tensor, | ||
| conv1d_bias: torch.Tensor | None, | ||
| dt_bias: torch.Tensor, | ||
| A: torch.Tensor, | ||
| D: torch.Tensor, | ||
| chunk_size: int, | ||
| initial_states: torch.Tensor | None = None, | ||
| dt_limit: tuple[float, float] = (0.0, float("inf")), | ||
| return_final_states: bool = False, | ||
| activation: str = "silu", | ||
| rmsnorm_weight: torch.Tensor | None = None, | ||
| rmsnorm_eps: float = 1e-6, | ||
| outproj_weight: torch.Tensor | None = None, | ||
| outproj_bias: torch.Tensor | None = None, | ||
| headdim: int | None = None, | ||
| ngroups: int = 1, | ||
| norm_before_gate: bool = True, | ||
| **kwargs, | ||
| ): | ||
| return None |
There was a problem hiding this comment.
This is still a bit awkward IMO 🥲 We do need to have all the args and kwargs so that kernels can match the signature right? We cannot simply use mamba2_split_conv1d_scan_combined(*args, **kwargs)?
There was a problem hiding this comment.
Yea not possible because we need to match the kernels signature
| class JambaMambaMixer(MambaMixer): | ||
| class JambaMambaMixer(FalconMambaMixer): |
There was a problem hiding this comment.
Is it really better to switch inheritance? Why do we want to add use_mambapy etc?
There was a problem hiding this comment.
The main goal here is to show that it's nearly 1:1 in the forward minus mamba inner fn not being usable
| q, k, v = torch.split( | ||
| mixed_qkv.transpose(1, 2), | ||
| [self.key_dim, self.key_dim, self.value_dim], | ||
| dim=-1, | ||
| ) |
There was a problem hiding this comment.
Humm actually, is it really better to merge them given that we need to cat and split?
There was a problem hiding this comment.
Pretty sure it's worth it, the conv is a bigger limiting factor than the cat and split
(also just easier for our cache in general)
Kernels] Native fallbackKernels] Refactor all mamba's recurrent part & Native kernels fallback
Kernels] Refactor all mamba's recurrent part & Native kernels fallbackKernels] Refactor all mamba models recurrent part & Native kernels fallback
|
[For maintainers] Suggested jobs to run (before merge) run-slow: bamba, falcon_h1, falcon_mamba, granitemoehybrid, inkling, jamba, lfm2, lfm2_moe, mamba, mamba2, minimax, nemotron_h, olmo_hybrid, qwen3_5, qwen3_5_moe, qwen3_next |
Kernels] Refactor all mamba models recurrent part & Native kernels fallbackKernels] Refactor all linear attn models & native kernels fallback
CI recapDashboard: View test results in Grafana |
Cyrilvallez
left a comment
There was a problem hiding this comment.
Alright, let's go now if you're done with all the changes after offline discussion!! Thanks again, super happy to land it and finally have fully clean mambas! 🤗
…e, adopt standard names - Drop the private RWKV7_WKV_FUNCTIONS registry and its config knob: it duplicated the kernel-substitution mechanism huggingface#47630 just unified across linear-attention models. The torch reference is called directly; an optimised kernel arrives via hub-kernels when one exists, not a per-model registry. The doc section demonstrating the registry goes with it. - Gate generate's input truncation on is_first_iteration instead of the state's existence: a pre-allocated state (the compile contract) or a warm state (a resumed chat turn) silently dropped every prompt token but the last one. Same gate Mamba uses; bare callers keep the old behaviour. - Rename cu_seq_lens to the ecosystem's cu_seq_lens_q, so padding-free collators reach the packed path instead of being swallowed by kwargs. - Refuse Rwkv7Cache.crop() loudly (the state is not invertible), fix keys_to_ignore_at_inference to name the output this model actually has, add the missing explicit models/__init__ re-export, and drop the attentions=None output field and ignored output_attentions parameter. - Tests: generate-with-state equalities, end-to-end left-padded batch generate, compiled decode step over allocate_state, the chunked-prefill mask-slicing contract, a multi-chunk numpy-oracle case; pin the numpy reference to its external sources and the rwkv package version used for the integration expectations; let the shared gradient-checkpointing trainings run via test_all_params_have_gradient. - Docs: compile guidance now reflects measurement (reduce-overhead with an eager prefill; max-autotune measured slower on this kernel chain).
…e, adopt standard names - Drop the private RWKV7_WKV_FUNCTIONS registry and its config knob: it duplicated the kernel-substitution mechanism huggingface#47630 just unified across linear-attention models. The torch reference is called directly; an optimised kernel arrives via hub-kernels when one exists, not a per-model registry. The doc section demonstrating the registry goes with it. - Gate generate's input truncation on is_first_iteration instead of the state's existence: a pre-allocated state (the compile contract) or a warm state (a resumed chat turn) silently dropped every prompt token but the last one. Same gate Mamba uses; bare callers keep the old behaviour. - Rename cu_seq_lens to the ecosystem's cu_seq_lens_q, so padding-free collators reach the packed path instead of being swallowed by kwargs. - Refuse Rwkv7Cache.crop() loudly (the state is not invertible), fix keys_to_ignore_at_inference to name the output this model actually has, add the missing explicit models/__init__ re-export, and drop the attentions=None output field and ignored output_attentions parameter. - Tests: generate-with-state equalities, end-to-end left-padded batch generate, compiled decode step over allocate_state, the chunked-prefill mask-slicing contract, a multi-chunk numpy-oracle case; pin the numpy reference to its external sources and the rwkv package version used for the integration expectations; let the shared gradient-checkpointing trainings run via test_all_params_have_gradient. - Docs: compile guidance now reflects measurement (reduce-overhead with an eager prefill; max-autotune measured slower on this kernel chain).
…e, adopt standard names - Drop the private RWKV7_WKV_FUNCTIONS registry and its config knob: it duplicated the kernel-substitution mechanism huggingface#47630 just unified across linear-attention models. The torch reference is called directly; an optimised kernel arrives via hub-kernels when one exists, not a per-model registry. The doc section demonstrating the registry goes with it. - Gate generate's input truncation on is_first_iteration instead of the state's existence: a pre-allocated state (the compile contract) or a warm state (a resumed chat turn) silently dropped every prompt token but the last one. Same gate Mamba uses; bare callers keep the old behaviour. - Rename cu_seq_lens to the ecosystem's cu_seq_lens_q, so padding-free collators reach the packed path instead of being swallowed by kwargs. - Refuse Rwkv7Cache.crop() loudly (the state is not invertible), fix keys_to_ignore_at_inference to name the output this model actually has, add the missing explicit models/__init__ re-export, and drop the attentions=None output field and ignored output_attentions parameter. - Tests: generate-with-state equalities, end-to-end left-padded batch generate, compiled decode step over allocate_state, the chunked-prefill mask-slicing contract, a multi-chunk numpy-oracle case; pin the numpy reference to its external sources and the rwkv package version used for the integration expectations; let the shared gradient-checkpointing trainings run via test_all_params_have_gradient. - Docs: compile guidance now reflects measurement (reduce-overhead with an eager prefill; max-autotune measured slower on this kernel chain).
#1102) * Vendored FLA: re-resolve kernel closures on modules imported before us Since huggingface/transformers#47630 the gated delta kernels are bound by use_kernel_func_from_hub_with_fallback, which resolves the implementation at decoration time and closes over it. A modeling module imported before fla is live freezes the pure-PyTorch fallback into that closure, and nothing done to fla afterwards can reach it. _repair_already_imported_modeling only rebinds module globals, which these modules no longer have. Re-apply the decorator to the undecorated function once fla is live, rather than writing to cell_contents: the wrapper also closes over the implementation's parameter names, and those differ between the fallback and the kernel, so patching the implementation alone would filter out arguments the kernel needs. * Test the repair against the live fla, not against the fallback A modeling module imported while a real fla install was live froze that install's kernel into the wrapper. UNSLOTH_FORCE_VENDORED_FLA and the Hopper #640 switch then purge the install, but the old check treated any non-fallback implementation as valid and skipped it, so the wrapper kept calling the kernel we replaced the install to avoid, which is the miscompiled backward. Compare against the kernel fla resolves to now instead. When there is no live replacement the rebuild lands on the pure-torch fallback, which is taken deliberately: torch beats calling into a purged install. The kernel-hub tests now skip on a transformers predating #47630, where the repair correctly returns () and there is nothing to patch.
Tl;dr: Allow kernels, og, and torch only to natively coexist along each other. This is also makes kernels as opt in, not as mandatory which caused very weird behavior where it suddenly took precedence even over the og kernels
Model types
Dependent on
Note:
activation=Noneand manually applying the activation...; act(hidden_states)