Repository navigation
Conversation
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/24687
Note: Links to docs will display an error until the docs builds have been completed. ✅ You can merge normally! (2 Unrelated Failures)As of commit a8af9ff with merge base 765e973 ( BROKEN TRUNK - The following jobs failed but were present on the merge base:👉 Rebase onto the `viable/strict` branch to avoid these failures
This comment was automatically generated by Dr. CI and updates every 15 minutes. |
|
@psiddh has exported this pull request. If you are a Meta employee, you can view the originating Diff in D128739913. |
This PR needs a
|
There was a problem hiding this comment.
🟡 Changes recommended
A stale attention-sink position shift can cause an out-of-bounds mask fill when starting a new sequence, and the Buck package omits the configuration required by both LFM3 models.
4 open issues
What changed in this PR
Adds LFM3-1.7B and LFM3-1.7B-Base support to ExecuTorch’s Qualcomm static LLM flow, including rolling convolution state shared between prefill and decode.
Changes:
- Adds the LFM3 model, checkpoint converter, configuration, quantization recipe, and numerical tests.
- Integrates convolution-state allocation, binding, and updates into the LLM runner.
- Registers decoder variants and prompt formatting, updates build targets, and separates converted-checkpoint caches.
| File | Description |
|---|---|
extension/android/CMakeLists.txt |
Includes convolution-state support in the Android build. |
examples/qualcomm/oss_scripts/llama/wrappers/llm_wrappers.py |
Integrates LFM3 construction and quantized state I/O. |
examples/qualcomm/oss_scripts/llama/targets.bzl |
Adds LFM3 Python dependencies. |
examples/qualcomm/oss_scripts/llama/static_llm_quant_recipe.py |
Defines the LFM3 quantization recipe. |
examples/qualcomm/oss_scripts/llama/runner/token_generator.h |
Adds decode-side convolution-state members. |
examples/qualcomm/oss_scripts/llama/runner/token_generator.cpp |
Binds and updates convolution state during decoding. |
examples/qualcomm/oss_scripts/llama/runner/runner.h |
Adds decoder versions, state ownership, and position reset. |
examples/qualcomm/oss_scripts/llama/runner/runner.cpp |
Initializes shared convolution state and checks supported modes. |
examples/qualcomm/oss_scripts/llama/runner/prompt_processor.h |
Adds prefill-side convolution-state members. |
examples/qualcomm/oss_scripts/llama/runner/prompt_processor.cpp |
Binds and updates convolution state during prefill. |
examples/qualcomm/oss_scripts/llama/runner/conv_state_manager.h |
Declares the rolling-state manager interface. |
examples/qualcomm/oss_scripts/llama/runner/conv_state_manager.cpp |
Implements state allocation, binding, and row updates. |
examples/qualcomm/oss_scripts/llama/qnn_llama_runner.cpp |
Adds LFM3 chat and base prompt formatting. |
examples/qualcomm/oss_scripts/llama/decoder_constants.py |
Registers LFM3 decoder identifiers. |
examples/qualcomm/oss_scripts/llama/CMakeLists.txt |
Includes the new state manager in runner builds. |
examples/qualcomm/oss_scripts/llama/__init__.py |
Registers LFM3 model configurations. |
examples/qualcomm/oss_scripts/lfm3/test_lfm3_static.py |
Tests chunked inference and annotation matching. |
examples/qualcomm/oss_scripts/lfm3/README.md |
Documents usage and unsupported modes. |
examples/qualcomm/oss_scripts/lfm3/model_wrapper.py |
Implements the static LFM3 decoder and state I/O. |
examples/qualcomm/oss_scripts/lfm3/convert_weights.py |
Converts Hugging Face weights to the static layout. |
examples/qualcomm/oss_scripts/lfm3/config/1_7b_config.json |
Supplies LFM3-1.7B architecture parameters. |
examples/qualcomm/oss_scripts/lfm3/BUCK |
Defines LFM3 library and test targets. |
examples/models/llama/hf_download.py |
Adds converter-specific checkpoint cache suffixes. |
backends/qualcomm/quantizer/custom_annotation.py |
Shares quantization encoding across convolution-state I/O. |
Review effort: Balanced
| void reset() override { | ||
| cur_pos_ = 0; |
| if producer is not None: | ||
| # The new rows' producer owns the observer; it precedes the concat in | ||
| # graph order, so every user of the shared spec can reference it. | ||
| producer.output_qspec = act | ||
| shared = SharedQuantizationSpec(new_rows) |
| "convert_weights.py", | ||
| "model_wrapper.py", | ||
| ], | ||
| base_module = "executorch.examples.qualcomm.oss_scripts.lfm3", |
|
|
||
| ## Text | ||
| ``` | ||
| python examples/qualcomm/oss_scripts/llama/llama.py -b build-android -s ${SERIAL_NUM} -m SM8750 \ |
Summary:
First diff of the stack that enables Liquid AI's LFM3 models on Qualcomm HTP with ExecuTorch. It runs LFM3-1.7B and LFM3-1.7B-Base through the static LLM flow (`llama.py --decoder_model lfm3-1_7b`); the next diff adds LFM3-VL-2B and LFM3-Omni-2B image + text. Generic-path (XNNPACK) support is a follow-up on top and is not needed by these diffs.
The static model (`examples/qualcomm/oss_scripts/lfm3/model_wrapper.py`) keeps the existing smart-mask KV I/O for the attention layers. Each short-conv layer takes its rolling state `[1, conv_L_cache - 1, dim]` as an extra input after the V caches and returns its new conv inputs `[1, ar_len, dim]`; state rows that precede position 0 are masked in the graph, so the runner never needs the quantized zero point. The recipe is 16a4w with 8-bit KV.
On the runner side, the new `ConvStateManager` binds the conv states and outputs as the last method I/O (validated against the method meta), shares them between the prefill and decode graphs, and keeps the last `state_len` valid rows after each step. It is created from the `get_n_conv_layers` metadata, so models without conv layers are unaffected. `PromptProcessor` and `TokenGenerator` bind and update it, and `kLfm3`/`kLfm3Base` add the decoder versions and ChatML template. Lookahead decoding, attention sink and sharding are rejected for conv-state models.
`annotate_conv_state` shares one 16-bit encoding across a conv-state input, the new rows and their concat, because the runner copies output bytes straight into the next call's state buffer. It matches only rank-3 row-wise concats, so the KV-cache concats keep `annotate_kv_8bit`'s encoding; the test covers that.
`download_and_convert_hf_checkpoint` takes a `cache_suffix` so the static converter's cached checkpoint doesn't collide with another converter's for the same repo.
Review order: `runner/conv_state_manager.{h,cpp}`, the processor/runner wiring, `lfm3/model_wrapper.py`, `custom_annotation.py`, then `llm_wrappers.py`.
This diff was authored with Claude Code.
Differential Revision: D128739913
1c1dfb1 to
ec5a0f6
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Resetting the sequence position without clearing the attention-sink shift can produce a negative prefill position that becomes an oversized unsigned mask-fill count, causing an out-of-bounds write.
6 open issues
Reset position_shift_ when starting a new attention-sink sequence Test conv-state encoding equality after recipe quantization · New Validate conv-state tensor rank before indexing its dimensions · New Package the default LFM3 config in the Buck target Test shared state encodings through the real LFM3 recipe Use supported long options in the LFM3 example command
Review effort: Balanced
| gm = torch.export.export(model, inputs, strict=False).module() | ||
| annotate_conv_state(gm) |
| auto info = method_meta.input_tensor_meta(first_input + layer); | ||
| ET_CHECK_MSG(info.ok(), "Failed to get conv state input tensor meta"); | ||
| ET_CHECK_MSG( | ||
| info->sizes()[1] == metadata_.state_len, "Conv state length mismatch"); |
|
@haowhsu-quic @shewu-quic @winskuo-quic Would love your review on this. We're adding support for Liquid AI's LFM3 models and running them end to end on Qualcomm HTP. There are a few fixes I still plan to make on this PR, but I'd like early thoughts on the overall direction first, especially the conv-state handling (ConvStateManager, conv state as extra graph I/O) and the annotate_conv_state quantization choice. Also since proper HF → ExecuTorch QNN flow for this model will take longer, since that design is still in flux, so this goes through the static LLM flow for now. I've run it end to end on HTP (Galaxy S25) and the results look good. |
|
@claude review the PR along with comments |
|
Claude finished @psiddh's task in 5m 57s —— View job PR Review: #24687Summary: The conv-state-as-graph-I/O direction looks workable, but the new global Detailed reviewSummaryThis PR adds LFM3-1.7B and LFM3-1.7B-Base to the QNN static LLM flow, with a rolling short-conv state carried as extra graph I/O and handled by a new Three independent reviewers went through the diff, and I re-read the cited code for every finding below. I also checked each of Copilot's comments:
Backward Compatibility
Infrastructure
Testing
Code Quality
RecommendationRequest Changes. The |
winskuo-quic
left a comment
There was a problem hiding this comment.
@psiddh Thanks a lot for the PR to enable LFM3.
While I am still going through the runner's logic, I have first left some comments for the AoT side, mostly about the file structure since this model seems to follow a different convention.
Please have a look.
| @@ -0,0 +1,225 @@ | |||
| # Copyright (c) Meta Platforms, Inc. and affiliates. | |||
There was a problem hiding this comment.
Instead of creating a testing file for this specific model, could we instead reuse https://github.com/pytorch/executorch/blob/main/backends/qualcomm/tests/test_qnn_delegate.py?
There was a problem hiding this comment.
Done. No model-specific test file anymore. lfm3-1_7b is in TestExampleLLMScript.llm_specs, and the conv-state checks live in test_qnn_delegate.py.
| @@ -0,0 +1,75 @@ | |||
| # Copyright (c) Meta Platforms, Inc. and affiliates. | |||
There was a problem hiding this comment.
I think convert_weights.py are usually placed under examples/models/{MODEL_NAME}.
If there's nothing that is backend specific in this file, maybe we should place it under the examples/models/{MODEL_NAME} folder
There was a problem hiding this comment.
Done, moved to examples/models/lfm3/.
| @@ -0,0 +1,21 @@ | |||
| # LFM3 on Qualcomm HTP | |||
There was a problem hiding this comment.
I think our convention is to provide each model's instruction under executorch/examples/qualcomm/oss_scripts/llama/README.md. Could we reorg this file and merge it into executorch/examples/qualcomm/oss_scripts/llama/README.md?
There was a problem hiding this comment.
Agree! Done, merged into oss_scripts/llama/README.md as an "LFM3 1.7B" entry.
| @@ -0,0 +1,56 @@ | |||
| { | |||
There was a problem hiding this comment.
Ditto. Same issue as convert_weights.py
There was a problem hiding this comment.
Done, moved to examples/models/lfm3/config/.
| "tie_word_embeddings": true, | ||
| "use_sandwich_norm": true, | ||
| "vocab_size": 128000 | ||
| } No newline at end of file |
|
|
||
|
|
||
| @dataclass(init=False, frozen=True) | ||
| class _Lfm3Decoder(LLMModelConfig): |
There was a problem hiding this comment.
I think for consistency, we don't need a base class. We can just have a list of configs for "lfm3-1_7b" and another for "lfm3-1_7b_base"
There was a problem hiding this comment.
Done, replaced with flat configs.
| r3 = False | ||
| quant_recipe = Lfm3QuantRecipe | ||
| # Differs from the generic-path converter for the same repo; keep caches apart. | ||
| converted_checkpoint_suffix = "_qnn_static" |
There was a problem hiding this comment.
I am assuming that this has something to do with placing convert_weights.py under Qualcomm folder. I am curious why LFM3 is different from other models. I believe examples/models/llama/llama_transformer.py should be sharing the same weight naming as static_llama, meaning using the same converted weight cache is safe.
I saw your summary section mentioning about enabling XNNPACK as follow-up. Is this related to the follow-up task?
There was a problem hiding this comment.
Yes, it was related to the XNNPACK follow-up. It's dropped now, along with the hf_download.py change. LFM3 now uses static_llama with the same weight naming as llama_transformer (og for the attention gate; B_proj/C_proj/x_proj/conv/out_proj for the short conv), so there's a single converter in examples/models/lfm3/ and one converted checkpoint. The XNNPACK follow-up reuses it.
| return (h, *out[1:]) | ||
|
|
||
|
|
||
| class Lfm3Model(nn.Module): |
There was a problem hiding this comment.
I think what is in this file highly overlaps with https://github.com/pytorch/executorch/blob/main/examples/qualcomm/oss_scripts/llama/model/static_llama.py. Could we possibly identify the gaps and just use registry. For example, feed forward uses: https://github.com/pytorch/executorch/blob/main/examples/qualcomm/oss_scripts/llama/model/feed_forward.py.
For example, the following is different and some of these could be used with a registry,
- Attention has a sigmoid at the end (3-4 lines).
- Mixed layers
ShortConvcould be registered. - Rope is different
There was a problem hiding this comment.
Done, model_wrapper.py is gone and LFM3 is built from static_llama pieces:
- ShortConv: a ShortConvDecoderLayer registered in a new DECODER_LAYER_REGISTRY under "conv". LlamaModel builds each layer from layer_types, defaulting to LlamaDecoderLayer.
- Feed forward: reuses FeedForward_REGISTRY / feed_forward.py, same as the other models.
- Attention sigmoid gate: a headwise_attn_output_gate option on the existing LlamaAttention (sigmoid(og(x)) per head), not a separate attention class.
- RoPE: uses the existing ROTARY_EMB_REGISTRY["partial"]. The converter permutes q/k for LFM3's partial-RoPE layout, and sliding vs full layers get their own rope_parameters.
Summary: Run LFM3 through shared `static_llama` with rolling convolution state and a converter/config shared with the generic path. Fail closed on missing state annotations and test equal input/output encodings with production `Lfm3QuantRecipe` before runner byte copies. Keep the checks in existing `test_qnn_delegate.py`; fix OSS formatting and the loop-variable capture. `Runner::reset()` now resets position for all non-attention-sink QNN text models, so Android `LlmModule.resetContext()` starts a new conversation. With attention sink it remains a no-op. Unsupported conv-state runner modes return `NotSupported`. Known limitation: a dedicated C++ `ConvStateManager` unit test remains a follow-up. Originally authored with Claude Code; updated with Metamate (agentcloud). Differential Revision: D128739913
ec5a0f6 to
a8af9ff
Compare
|
Thanks @winskuo-quic . All AoT comments are addressed, and the structure changed. LFM3 now goes through the shared static_llama (a short-conv hybrid decoder layer next to the existing ones), so the standalone oss_scripts/lfm3/ folder is gone. |
Summary: Pull Request resolved: pytorch#24687 Run LFM3 through shared `static_llama` with rolling convolution state and a converter/config shared with the generic path. Reject lookahead and attention sink before Python export. Select conv-state I/O by its user-argument/output position, not shape, and remove unused manager accessors. Support single-file and indexed sharded Safetensors checkpoints. An independent tiny HF logits test exercises the shared converter, partial RoPE, gate/norm order and conv tap direction, including the export rewrites. It skips when LFM3 modeling is unavailable and runs against an explicitly selected local HF reference otherwise. `Runner::reset()` resets position for all non-attention-sink QNN text models, so Android `LlmModule.resetContext()` starts a new conversation; attention-sink reset remains a no-op. Unsupported conv-state runner modes return `NotSupported`. Known limitation: a dedicated C++ `ConvStateManager` unit test remains a follow-up. Originally authored with Claude Code; updated with Metamate (agentcloud). Differential Revision: D128739913



Summary:
Run LFM3 through shared
static_llamawith rolling convolution state and a converter/config shared with the generic path. Fail closed on missing state annotations and test equal input/output encodings with productionLfm3QuantRecipebefore runner byte copies. Keep the checks in existingtest_qnn_delegate.py; fix OSS formatting and the loop-variable capture.Runner::reset()now resets position for all non-attention-sink QNN text models, so AndroidLlmModule.resetContext()starts a new conversation. With attention sink it remains a no-op. Unsupported conv-state runner modes returnNotSupported.Known limitation: a dedicated C++
ConvStateManagerunit test remains a follow-up.Originally authored with Claude Code; updated with Metamate (agentcloud).
Differential Revision: D128739913