Skip to content

Add LiquidAI LFM2.5 hybrid model support - #462

Open
justinchuby wants to merge 5 commits into
mainfrom
justinchuby-add-lfm2-5-support
Open

Add LiquidAI LFM2.5 hybrid model support#462
justinchuby wants to merge 5 commits into
mainfrom
justinchuby-add-lfm2-5-support

Conversation

@justinchuby

@justinchuby justinchuby commented Aug 7, 2026

Copy link
Copy Markdown
Member

Summary

  • add lfm2 support for LiquidAI/LFM2.5-230M, following upstream Transformers
  • implement reusable double-gated depthwise short convolution with the K-wide ORT GenAI recurrent cache contract
  • wire mixed convolution/GQA layers, per-head QK RMSNorm, RoPE, SwiGLU weight mapping, tied embeddings, registry/config extraction, and hybrid cache I/O
  • match Transformers' mandatory fp32 RMS variance accumulation for decoder, final, and QK norms
  • simplify the convolution graph to consume only required K-1 history, use equal Split, and replace dynamic length arithmetic with negative Slice indices
  • add ORT GenAI config/runtime coverage plus representative L1, real-checkpoint L2, synthetic/real-weight L3, and pinned L4/L5 coverage

Upstream references:

Final validation

Hardware: NVIDIA RTX A1000 (8 GB), driver 573.44, CUDA 12.8. Restored global ONNX Runtime GPU 1.28.0 reports TensorRT/CUDA/CPU; actual LFM2 sessions report ['CUDAExecutionProvider', 'CPUExecutionProvider'].

lintrunner -a
# ok No lint issues.

$env:MOBIUS_TEST_DEVICE='cuda'
$env:MOBIUS_ORT_LOWER_OPSET_FOR_EP='1'
python -m pytest tests/integration_test.py -m integration -k 'lfm2.5-230m' -q --tb=short -n 0
# 3 passed, 122 deselected (CUDA prefill, cached decode, 20-token generation)

python -m pytest scripts/detect_affected_models_test.py -q --tb=short -n 0
# 61 passed

python -m pytest src/mobius/components/_short_conv_test.py tests/build_graph_test.py tests/weight_alignment_test.py tests/synthetic_parity_test.py -k lfm2 -q --tb=short
# 6 passed, 1780 deselected

python -m pytest tests/e2e_golden_test.py -m 'golden or generation' --models lfm2 -q --tb=short -n 0
# 2 passed, 375 skipped, 2 deselected

Restored-ORT-1.28 parity harness (Once upon a time):

Comparison Prefill max / mean abs Top-1 Greedy generation
fp32 ORT CPU vs ORT CUDA 0.0262401 / 0.00278723 exact exact 20/20
fp16 ORT CPU vs ORT CUDA 0.0976562 / 0.00972997 exact exact 20/20
bf16 ORT CUDA vs fp32 ORT CUDA 0.500275 / 0.0534421 exact exact 20/20

All CUDA outputs were finite. CUDA-resident Hugging Face reference validation after the fp32 RMSNorm fix also produced exact 20/20 generation for fp32, fp16, and bf16.

Optimized graph audit

  • raw graph before optimizer: 892 nodes
  • final weighted fp32 graph: 619 nodes, 0 Identity, 17 Transpose
  • final weighted CUDA fp16 graph: 686 nodes, 0 Identity, 17 Transpose
  • restored ORT 1.28 CUDA profile: 665 node events; 655 CUDA, 10 CPU shape/index metadata, 0 Memcpy
  • optimizer folds 82 weight transposes; remaining 17 are required data-layout/LM-head operations
  • short convolution reuses public Linear and INT64_MAX, uses equal Split, retains only required current-mask/K-1-history/K-wide-state slices, and has no duplicated dynamic length arithmetic

CI scoping fix

The first two L4/L5 attempts were externally terminated mid-pytest because tests/_test_configs.py forced run_all=true, causing each GPU job to run the entire golden catalog. detect_affected_models now keeps standalone test-config and unmapped-task changes conservative (run_all=true) while allowing a real model implementation plus its test config to use import-graph scoping. The full PR diff now resolves to:

{"affected": ["lfm2"], "run_all": false}

This is covered by 61 detector tests, including config-only, model+config, and unmapped-task+config cases.

Quality checklist

  • architecture/config/weight mapping and hybrid cache behavior
  • representative L1, real-checkpoint L2, synthetic/real-weight L3, pinned L4/L5
  • ORT GenAI config, CPU model load, and deterministic generation
  • actual restored ORT 1.28 CUDA EP prefill, cached decode, and deterministic generation
  • fp32/fp16 CPU-vs-CUDA plus bf16 CUDA comparisons
  • optimized graph/profile audit with no Identity or Memcpy nodes
  • exact full lintrunner -a and targeted tests

Remaining infrastructure limitations

  • Foundry Local and Olive are not installed in this environment; those deployment/quantization smoke tests remain deferred.

Implement the hybrid double-gated short-convolution and QK-normalized GQA architecture, including recurrent cache and ORT GenAI wiring. Add synthetic and real-weight parity, pinned L4/L5 goldens, CLI/runtime coverage, and dtype validation.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
@justinchuby
justinchuby requested review from a team and a lite review from Copilot August 7, 2026 23:51
Comment thread src/mobius/components/_short_conv.py Fixed
Comment thread src/mobius/models/lfm2.py Fixed
Comment thread src/mobius/models/lfm2.py Fixed
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown

Performance Comparison

Comparing 9567144f56dc22

Model Metric Baseline Current Delta
bert (feature-extraction) model_size_bytes 359 KB 359 KB +0.0%
bert (feature-extraction) num_nodes 60 60 +0.0%
falcon model_size_bytes 364 KB 364 KB +0.0%
falcon num_nodes 68 68 +0.0%
gemma2 model_size_bytes 428 KB 428 KB +0.0%
gemma2 num_nodes 107 107 +0.0%
gpt2 model_size_bytes 388 KB 388 KB +0.0%
gpt2 num_nodes 54 54 +0.0%
llama model_size_bytes 425 KB 425 KB +0.0%
llama num_nodes 62 62 +0.0%
llama (static-cache) model_size_bytes 425 KB 425 KB +0.0%
llama (static-cache) num_nodes 58 58 +0.0%
mamba (ssm-text-generation) model_size_bytes 296 KB 296 KB +0.0%
mamba (ssm-text-generation) num_nodes 98 98 +0.0%
phi3 model_size_bytes 421 KB 421 KB +0.0%
phi3 num_nodes 60 60 +0.0%
phi3 (static-cache) model_size_bytes 421 KB 421 KB +0.0%
phi3 (static-cache) num_nodes 56 56 +0.0%
qwen2 model_size_bytes 425 KB 425 KB +0.0%
qwen2 num_nodes 62 62 +0.0%
qwen2 (static-cache) model_size_bytes 425 KB 425 KB +0.0%
qwen2 (static-cache) num_nodes 58 58 +0.0%
qwen3_5_moe (hybrid-text-generation) model_size_bytes 506 KB 506 KB +0.0%
qwen3_5_moe (hybrid-text-generation) num_nodes 275 275 +0.0%
qwen3_5_text (hybrid-text-generation) model_size_bytes 458 KB 458 KB +0.0%
qwen3_5_text (hybrid-text-generation) num_nodes 129 129 +0.0%
qwen3_5_vl (hybrid-qwen-vl) model_size_bytes 977 KB 977 KB +0.0%
qwen3_5_vl (hybrid-qwen-vl) num_nodes 431 431 +0.0%
t5 (seq2seq) model_size_bytes 836 KB 836 KB +0.0%
t5 (seq2seq) num_nodes 166 166 +0.0%
whisper (speech-to-text) model_size_bytes 1008 KB 1008 KB +0.0%
whisper (speech-to-text) num_nodes 128 128 +0.0%

No performance regressions.

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown

🏗️ Architecture Diff

Comparing 9567144f56dc22

Model Sub-model Changes Status
bert (feature-extraction) model 0
falcon model 0
gemma2 model 0
gemma4 (gemma4) decoder 0
gemma4 (gemma4) embedding 0
gemma4 (gemma4) vision_encoder 0
gemma4_text model 0
gpt2 model 0
llama model 0
llama (static-cache) model 0
mamba (ssm-text-generation) model 0
phi3 model 0
phi3 (static-cache) model 0
qwen model 0
qwen (static-cache) model 0
qwen2 model 0
qwen2 (static-cache) model 0
qwen2_moe model 0
qwen2_moe (static-cache) model 0
qwen3 model 0
qwen3 (static-cache) model 0
qwen3_5_moe (hybrid-text-generation) model 0
qwen3_5_text (hybrid-text-generation) model 0
qwen3_5_vl (hybrid-qwen-vl) decoder 0
qwen3_5_vl (hybrid-qwen-vl) embedding 0
qwen3_5_vl (hybrid-qwen-vl) vision_encoder 0
qwen3_moe model 0
qwen3_moe (static-cache) model 0
qwen3_next (hybrid-text-generation) model 0
t5 (seq2seq) decoder 0
t5 (seq2seq) encoder 0
whisper (speech-to-text) decoder 0
whisper (speech-to-text) encoder 0

No architecture changes detected.


Legend: ⚪ No change · 🔵 Minor (attrs/inits) · 🟡 Moderate (nodes added/removed) · 🔴 Major (interface changed)

Copilot AI 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.

Pull request overview

Adds first-class support for LiquidAI’s LFM2.5 hybrid Conv+GQA architecture to mobius, including its hybrid cache contract and ORT GenAI packaging metadata, plus multi-tier validation coverage and goldens.

Changes:

  • Introduces an lfm2 model implementation with mixed short-convolution and full-attention layers, plus weight key remapping for upstream naming differences.
  • Adds a reusable double-gated depthwise short convolution component with a full-kernel recurrent cache (aligned to ORT GenAI’s LFM2 cache contract).
  • Extends test/config/registry + ORT GenAI config generation to recognize LFM2 hybrid cache I/O and validate deterministic generation.

Reviewed changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated no comments.

Show a summary per file
File Description
tests/synthetic_parity_test.py Adds HF config translation for lfm2 and filters conv-specific config keys for synthetic parity.
tests/ort_genai_test.py Adds an integration test that exports LFM2.5 and validates deterministic ORT GenAI generation.
tests/integration_test.py Extends feed construction to handle per-layer conv_state inputs/outputs for conv layers.
tests/build_graph_test.py Verifies hybrid-cache output naming for conv layers (present.{i}.conv_state).
tests/_test_configs.py Adds a representative tiny lfm2 config for graph-build/unit coverage.
testdata/golden/causal-lm/lfm2_5-230m.json Adds pinned golden logits/top-k snapshot for LFM2.5-230M.
testdata/golden/causal-lm/lfm2_5-230m_generation.json Adds pinned deterministic 20-token generation golden for LFM2.5-230M.
testdata/cases/causal-lm/lfm2_5-230m.yaml Adds an L4/L5 real-checkpoint case pinned to a specific HF revision.
src/mobius/tasks/_cache_utils.py Updates hybrid cache input shapes for conv layers to store a full kernel-wide window.
src/mobius/models/lfm2.py Implements Lfm2CausalLMModel + mixed conv/attention decoder layers and projection key remapping.
src/mobius/models/init.py Exports Lfm2CausalLMModel.
src/mobius/integrations/ort_genai/genai_config.py Adds LFM2-specific decoder metadata (layer_types/conv cache) and disables share-buffer mode.
src/mobius/integrations/ort_genai/genai_config_test.py Tests that LFM2 genai_config declares hybrid conv cache fields correctly.
src/mobius/integrations/ort_genai/auto_export.py Maps mobius lfm2 to ORT GenAI model type lfm2.
src/mobius/components/_short_conv.py Adds GatedShortConv implementing the double-gated depthwise causal short-conv with recurrent state.
src/mobius/components/_short_conv_test.py Adds a unit test to ensure the short-conv component builds a stateful graph and exposes expected params.
src/mobius/components/init.py Exports GatedShortConv via the public components API.
src/mobius/_testing/torch_reference.py Extends HF reference execution to support opaque hybrid Cache objects for conv/hybrid models.
src/mobius/_registry.py Registers lfm2 model type, test model id, and dashboard family/category mappings.
src/mobius/_configs/_base.py Adds short-conv config fields and extracts them from HF configs (conv_L_cache/conv_bias), plus lfm2 defaults.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Copilot AI 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.

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated 1 comment.

Suppressed comments (2)

testdata/cases/causal-lm/lfm2_5-230m.yaml:3

  • This SHA does not actually pin the L4/L5 run: GoldenTestCase.revision is parsed, but tests/e2e_golden_test.py::_build_model_package calls build(case.model_id, ...) without using it, and build() has no revision parameter. The golden tests therefore load the repository's current default revision and can drift from these committed logits/tokens. Resolve this revision to a local snapshot before building (or thread a revision argument through build) so the advertised pinned coverage is real.
revision: "13a53837c4906b4f7405932532ba85d182bb013b"

tests/ort_genai_test.py:233

  • This deterministic exact-token test resolves both weights and tokenizer artifacts from the Hub's current default revision, so an upstream update can invalidate the hard-coded sequence. Use the same pinned checkpoint revision as the golden case and pass its local snapshot to both build and write_ort_genai_config.
    model_id = "LiquidAI/LFM2.5-230M"
    pkg = build(model_id, dtype="f32", load_weights=True)
    output_dir = str(tmp_path / "lfm2")
    pkg.save(output_dir)
    write_ort_genai_config(pkg, output_dir, hf_model_id=model_id)

Comment on lines +713 to +714
short_conv_kernel=getattr(config, "conv_L_cache", 3),
short_conv_bias=getattr(config, "conv_bias", False),
justinchuby and others added 3 commits August 7, 2026 17:41
Compute LFM2 RMS variance in float32 before casting back for gamma, matching Transformers for decoder, final, and QK norms. This restores deterministic fp16 CUDA generation parity.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
Use only the K-1 cached values required by causal convolution so Conv emits exactly the current token span. Replace dynamic length arithmetic with equivalent negative slices and equal splitting, removing redundant shape, subtraction, slicing, and constant nodes while preserving the K-wide ORT GenAI cache contract.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
Use the public INT64_MAX component constant for all LFM2 short-convolution slices, removing duplicated magic bounds identified during the optimized-graph audit.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
@justinchuby

Copy link
Copy Markdown
Member Author

Can you set stash type for the RMS norm op? We would prefer fused ops.

Keep config-only changes conservative, but let new model implementations use import-graph affected-model selection. This prevents LFM2 PRs from running the entire GPU golden catalog while preserving run-all behavior for standalone test config and unmapped task changes.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Justin Chu <justinchu@microsoft.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants