[E2E Model Support] Add TileGym kernel integration for LFM2 MoE - #187
[E2E Model Support] Add TileGym kernel integration for LFM2 MoE#187iamanishx wants to merge 4 commits into
Conversation
…fy kernel inventory - causal_conv1d_update_cutile: accept both decode call conventions and return the matching rank. transformers 5.13+ calls it from Lfm2MoeShortConv.forward with a 3D (B, D, 1) tensor and gates the result directly, while 5.10.x calls it from cuda_kernels_forward with Bx.squeeze(-1), a 2D (B, D) tensor. The previous 2D-only unpacking raised ValueError on the 5.13+ path. - Lfm2MoeSparseMoeBlockTileGym: fall back to nn.Linear when Lfm2MoeTopKRouter is not importable. transformers 5.10.x inlines the router inside Lfm2MoeSparseMoeBlock. Both layouts expose the same gate.weight state_dict key of shape (num_experts, hidden_size), so strict loading works either way. - causal_conv1d_prefill: drop the unused sequence-length ct.Constant. It was part of the JIT specialization key, so every distinct prompt length forced a fresh cuTile compile for no benefit. Bounds are already handled by check_bounds on the gathers and scatter. - kernel_definitions: point Definition.reference at pinned transformers permalinks with line anchors and align the reference run signatures with the solution entry points, both required by tests/kernel_inventory. - tests: compute the bf16 update reference in float32 to match the kernel's f32 accumulation, and add coverage for the legacy 2D decode call. Validated on RTX PRO 6000 Blackwell (SM120): tests/ops/test_causal_conv1d_lfm2.py, 17 passed. tests/kernel_inventory -k lfm2, 2 passed. Signed-off-by: iamanishx <manishbiswal754@gmail.com>
Resolve the tests/kernel_inventory/kernel_runtime_utils.py conflict in favour of upstream's refactor: MUTATED_INPUTS_BY_DEFINITION was replaced by the YAML-backed RuntimeInputCatalog, so the dict is gone and the lfm2_moe_causal_conv1d_update in-place conv_state declaration moves to tests/kernel_inventory/runtime_inputs.yaml alongside the equivalent qwen3_5 entry. Signed-off-by: iamanishx <manishbiswal754@gmail.com>
The CI ops job runs pytest with -k test_op, so the previous names were silently deselected. Signed-off-by: iamanishx <manishbiswal754@gmail.com>
|
/ok to test b176ea4 |
|
Hi @iamanishx, thanks a lot for this PR! It's a really thorough end-to-end LFM2-MoE integration. We'll do a full review pass shortly. One thing before we can merge: since this is your first contribution to TileGym, we'll need a signed CLA on file. Per first-time-contributors-cla-required, changes are MIT-licensed and require a signed CLA (LICENSES/CLA.md) emailed to TileGym@nvidia.com. Thanks for understanding! |
sure |
|
@hannahli-nv @lirundong hi, please have a look!! |
Apologies for the delayed response. We’ll update the review comments early next week. Thank you for your understanding regarding the inconvenience. |
Description
Adds end-to-end TileGym support for
LiquidAI/LFM2-8B-A1B. Closes #154.LFM2-8B-A1B is a hybrid MoE model, 8.3B total and 1.5B active parameters. In this
checkpoint the token mixer is a short depthwise causal convolution in 18 of 24 layers
and GQA attention in the other 6, while the channel mixer is a dense SwiGLU MLP in the
first 2 layers and a 32-expert top-4 sparse MoE block in the remaining 22.
TileGym had no causal conv1d kernel for the convolution blocks, so this PR contributes
two new cuTile kernels alongside the model integration, following the layout of
src/tilegym/transformers/qwen3_5/kernels/. The MoE block followsOlmoeSparseMoeBlockTileGym, per the guidance on the issue.Integration.
apply_tilegym_kernel_to_lfm2_moeinmonkey_patch.py, registered aslfm2_moeinMODEL_TYPE_TO_APPLY_TILEGYM_FN:get_apply_rope_func(model="llama")get_rms_norm_module(), operator, ffn, embedding and per-head Q/K normsget_fmha_interface()onALL_ATTENTION_FUNCTIONS["sdpa"]Lfm2MoeSparseMoeBlockTileGymover TileGymfused_moeLfm2MoeMLPTileGymwith fusedsilu_and_muluse_cutile=TrueNew files under
src/tilegym/transformers/lfm2_moe/:kernels/causal_conv1d_prefill.py,kernels/causal_conv1d_update.py, matchingkernel_definitions/andkernel_solutions/pairs, and
modeling_lfm2_moe.py. Also alfm2_moepreset inmodeling/transformers/scripts/benchmark_hf_model.sh, dispatch intilegym_patch.py,tests/ops/test_causal_conv1d_lfm2.py, and the in-placeconv_statedeclaration intests/kernel_inventory/runtime_inputs.yaml.Weight layout and routing, verified against the HF source.
gate_up_projis(E, 2I, H)with gate first then up, confirmed by HF'slinear(x, gate_up_proj[e]).chunk(2, dim=-1), anddown_projis(E, H, I). Both matchwhat
fused_moeexpects, so weights pass through with no merge and no reorder. Routinguses sigmoid rather than softmax. When
use_expert_biasis set the bias only selects thetop-k experts while the gathered weights stay unbiased.
norm_topk_probdivides bysum + 1e-6, thenrouted_scaling_factorscales. Submodule and buffer names areidentical to the stock block, so the HF state dict loads with
strict=True, andLfm2MoeMLPTileGymkeeps thew1,w3,w2names LFM2 uses.transformers compatibility. The LFM2 MoE API differs across releases and both are
handled. On 5.13 and later the conv entry points are module-level functions called from
Lfm2MoeShortConv.forwardwith 3D(B, D, 1)decode tensors, and the router is adedicated
Lfm2MoeTopKRouter. On 5.10.x the call comes fromcuda_kernels_forwardwith2D
Bx.squeeze(-1), and the router is inlined inLfm2MoeSparseMoeBlock. The updatewrapper accepts both ranks and returns the matching rank; the MoE wrapper falls back to
nn.Linear, which exposes the samegate.weightstate dict key.Testing on RTX PRO 6000 Blackwell, SM120, the same compute capability as the RTX 5090
referenced in the issue, with transformers 5.10.2 and torch 2.9.1+cu130:
Kernel coverage: prefill against
F.conv1din float32 and bfloat16 over several channeland sequence sizes, decode update against a torch reference including the in-place state
roll, the legacy 2D decode call, and prefill against token-by-token decode for
consistency. End-to-end generation was run unpatched, patched without conv kernels, and
patched with conv kernels; all three produce coherent output. Outputs are not
bit-identical across configurations, which is expected because sigmoid top-k routing
turns small numeric differences into different expert selections. Patch application was
confirmed on the loaded model: backend
cutile, both conv entry points bound to theTileGym wrappers,
Lfm2MoeSparseMoeBlockTileGym,Lfm2MoeMLPTileGym,_TileRMSNorm,sdpa, 18 of 24 layers on the conv path, and conv update call counts equal to convlayers times decode tokens.
Performance, steady state after two warmup passes at the measured shape, five timed
runs, batch 1, bfloat16. Prefill, single forward pass:
Decode, 210 token prompt, 64 generated tokens: baseline 62.30 tok/s, TileGym without conv
kernels 50.77 tok/s, TileGym with conv kernels 54.28 tok/s. Batch-1 decode is slower than
baseline for both TileGym configurations. That regime sits about 10x off the memory
roofline, roughly 16 ms per token against a 1.7 ms floor for 1.5B active parameters at
1792 GB/s, so it is bound by Python dispatch and per-call fixed costs rather than kernel
throughput;
fused_moepays token-to-expert sorting, alignment and workspace setup onevery call, which amortizes over thousands of tokens but not over one, while the stock
path only touches the four selected experts. The conv kernels recover 6.9 percent of
decode throughput, consistent with 18 of 24 layers using the conv path.
Notes. The conv kernels support batch size 1 and kernel_size 3, which is
conv_L_cachefor this checkpoint; both are asserted. The attention patch registers onALL_ATTENTION_FUNCTIONS["sdpa"]only, matching most existing model patches, so loadingwith
attn_implementation="eager"bypasses FMHA; happy to also patcheagerasgemma3does. The conv patch requires
use_cutile=Truebecause the kernels are cuTile only.Kernel definitions are tagged
status:unverified; happy to flip them once the team's ownvalidation passes.
CI Configuration
Checklist