Skip to main content

export-hf-model-extension

Onboards and exports new or custom Hugging Face (or Diffusers/PyTorch) model architectures in `litert_torch.generative.export_hf` using pluggable `ModelExtension` classes (built-in under `model_ext/<arch>/` or out-of-tree via `--extension_modules`). Use when supporting a new `model_type` in `export_hf` or `litert_lm`, inspecting a checkpoint with `model_ext/inspector.py`, patching RMSNorm/SwiGLU/ShortConv/Attention/RoPE for `torch.export`, configuring fused weights or `LlmMetadata`, or verifying extensions with `model_ext/testing.py`. Don't use for generic non-generative PyTorch-to-TFLite conversion or standalone post-export equivalence evaluation.

Source facts

Repository
google-ai-edge/litert-torch
Last source activity
October 31, 2026 at 00:20
Detected SKILL.md language
English
Stars
1,108
Forks
194

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
100 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
export-hf-model-extension
description
Onboards and exports new or custom Hugging Face (or Diffusers/PyTorch) model architectures in `litert_torch.generative.export_hf` using pluggable `ModelExtension` classes (built-in under `model_ext/<arch>/` or out-of-tree via `--extension_modules`). Use when supporting a new `model_type` in `export_hf` or `litert_lm`, inspecting a checkpoint with `model_ext/inspector.py`, patching RMSNorm/SwiGLU/ShortConv/Attention/RoPE for `torch.export`, configuring fused weights or `LlmMetadata`, or verifying extensions with `model_ext/testing.py`. Don't use for generic non-generative PyTorch-to-TFLite conversion or standalone post-export equivalence evaluation.
# Onboarding New Model Architectures in `export_hf` (`ModelExtension`) Use the pluggable `ModelExtension` framework (`litert_torch.generative.export_hf.model_ext`) to support new or custom Hugging Face (`trust_remote_code`) checkpoints in `export_hf` and `litert_lm` without modifying core `litert_torch` or checkpoint files. --- ## 1. Standard 4-Step Workflow ### Step 1: Run the Architecture Inspector (`model_ext/inspector.py`) Always inspect the checkpoint or Hugging Face repo ID first. The inspector shrinks the config to a tiny CPU model, checks `.safetensors` prefixes, RMSNorm/MLP/Attention/ShortConv/RoPE modules, runs a `<5s` `torch.export` dry-run, and prints a starter `ModelExtension` skeleton: ```bash bazel run \ //third_party/py/litert_torch/generative/export_hf/model_ext:inspector -- \ --model=/path/to/checkpoint \ --trust_remote_code ``` Or via Python: ```bash python -m litert_torch.generative.export_hf.model_ext.inspector \ --model=/path/to/checkpoint \ --trust_remote_code ``` ### Step 2: Author the `ModelExtension` Subclass `extension.ModelExtension` (either in an out-of-tree `.py` file passed via `--extension_modules=/path/to/my_ext.py` or built-in under `model_ext/<arch>/extension.py`) and set `model_types = ('<model_type>',)`. ### Step 3: Fast Inner-Loop Verification (`<2s` on CPU) Before running a multi-minute full checkpoint export, verify your extension on a tiny model (2–3 layers, `vocab_size=256`, `dtype=torch.float32`) using `model_ext/testing.py`: 1. **`testing.assert_patch_numerical_parity(ext, tiny_model, ...)`**: Verifies eager numerical parity before vs. during `patch_model` and asserts that all submodule classes and `.forward` methods are cleanly restored on exit. 2. **`testing.assert_tiny_prefill_decode_torch_export(ext, tiny_model, ...)`**: Verifies `torch.export.export` graph capture across `prefill_*` and `decode` (and `RoPEEmbedder` when `split_cache=True`). 3. **`testing.assert_v2_weights_resolvable(ext, tiny_model, ...)`**: Saves the unpatched tiny model as safetensors, applies `patch_model` with all fusion / composite flags on, and verifies Converter V2's `HFCheckpointWeightsLoader` resolves every patched parameter (shape and value). Run this whenever your patches fuse, rename, or add parameters (see gotcha F). ### Step 4: Full Export & `litert_lm` Verification * **From source (`bazel run`)**: ```bash bazel run \ //third_party/py/litert_torch/generative/export_hf:export_main -- \ --model=/path/to/checkpoint \ --output_dir="$HOME/export_hf_test_models/outputs/my_model" \ --extension_modules=/path/to/my_extension.py \ --trust_remote_code=True \ --bundle_litert_lm=True ``` * **Python / Installed Package CLI**: Run `litert-torch export_hf ...` or `python -m litert_torch.generative.export_hf.export_main ...` (do **not** run `python -m litert_torch.generative.export_hf.export`, which has no `__main__` block). --- ## 2. LiteRT-Torch Composite Ops & Import Cheat-Sheet Do not guess import paths or function signatures for LiteRT composites: | Operator | Import Path | Exact Call Signature & Contract | | :--- | :--- | :--- | | **RMSNorm (`odml.rms_norm`)** | `from litert_torch.generative.layers import normalization` | `normalization.rms_norm_with_hlfb(hidden_states, weight, eps, torch.ones((hidden_size,), dtype=torch.float32, device=hidden_states.device))` — requires **4 positional arguments** (note the 4th `ones` tensor). Lives in `generative.layers`, **not** `experimental.composites`. | | **SwiGLU (`odml.swiglu`)** | `from litert_torch.generative.export_hf.experimental.composites import swiglu` | `swiglu.apply_swiglu(gate_up)` — takes concatenated `[gate, up]` tensor (`[B, T, 2 * D_ff]`) from `gate_up_proj(x)` and returns `[B, T, D_ff]` **before** `down_proj` / `w2`. | | **ShortConv (`odml.short_conv`)** | `from litert_torch.generative.export_hf.experimental.composites import short_conv` | `short_conv.apply_short_conv_step(in_proj_out, conv_state, conv_weight, conv_bias, num_valid_tokens, conv_L_cache)` (composite) or `short_conv.short_conv(...)` (eager fallback) — takes `in_proj_out = self.in_proj(hidden_states)` (`[B, T, 3 * D]`), **not** `hidden_states` (`[B, T, D]`), and returns `(y, new_conv_state)` **before** `self.out_proj(y)`. | | **Attention (`ALL_ATTENTION_FUNCTIONS`)** | `from litert_torch.generative.export_hf.core import attention as _` | Dispatch via `modeling_utils.ALL_ATTENTION_FUNCTIONS[config._attn_implementation]`. Pass `past_key_value=past_key_values` and `sliding_window=self.sliding_window`. | --- ## 3. Critical Architecture Gotchas (From Real Failure Modes) ### A. Post-SDPA `attn_output` Shape Contract (`[B, T, N, H]` vs. `[B, 1, N*H]`) `lrt_transposed_attention` returns `attn_output` with shape `[B, T, num_heads, head_dim]` on CPU/prefill, whereas the `gpu_sdpa` decode composite returns `[B, 1, num_heads * head_dim]`. * If an architecture applies per-head operations after SDPA (such as `LFM3`'s head-wise sigmoid gate `torch.sigmoid(self.gate_proj(hidden_states)).unsqueeze(-1)`), always normalize the shape first: ```python attn_output = attn_output.reshape(*input_shape, self.num_heads, self.head_dim) attn_output = (attn_output * gate).reshape(*input_shape, -1).contiguous() ``` ### B. Hybrid Architectures (`"conv"` + `"sliding_attention"` + `"full_attention"`) 1. **Mask dictionary keys**: `exportable_module.adapt_inputs` passes `attention_mask = {'full_attention': ..., 'sliding_attention': ...}` when `sliding_window` is set. If `config.layer_types` also contains `'conv'` (or `'linear_attention'`), use `causal_mask_mapping.get(layer_type)` or `causal_mask_mapping.setdefault('conv', None)` in the model's `forward` so indexing `causal_mask_mapping[layer_type]` does not raise `KeyError: 'conv'`. 2. **Forwarding `valid_mask` (`**kwargs`) to `ShortConv`**: `LiteRTExportableModuleForDecoderOnlyLMPrefill` passes `valid_mask` via `**kwargs` so `ShortConv` layers mask out right-padded prefill tokens before updating `past_key_values.layers[layer_idx].conv_states`. Ensure `DecoderLayer.forward` forwards `**kwargs` to `self.conv(...)`. 3. **Shrinking hybrid configs for tiny tests**: Do not blindly slice `config.layer_types[:2]` if the first two layers have the same type (e.g., `LFM3-1.7B` has `['conv', 'conv']` at layers 0–1). Include at least one of each distinct layer type (e.g. `['conv', 'sliding_attention', 'full_attention']`), and clamp `pad_token_id` / `bos_token_id` / `eos_token_id` to `< vocab_size` when shrinking `vocab_size` so `nn.Embedding(..., padding_idx=pad_token_id)` does not fail. ### C. `trust_remote_code` Dynamic Module Lookup & Converter V2 (`--use_v2`) When Hugging Face loads a checkpoint with `trust_remote_code=True`, the modeling module is registered in `sys.modules` under `transformers_modules.<repo>.modeling_<arch>`, not `transformers.models.<arch>`. * Scan `sys.modules` to locate the module for `patch_context`, `patch_model`, and `get_hf_model_cls`: ```python def _find_modeling_module() -> Any | None: for mod_name, mod in list(sys.modules.items()): if mod_name.endswith('modeling_my_arch') and hasattr(mod, 'MyArchForCausalLM'): return mod return None ``` * Return `mod.MyArchForCausalLM` from `get_hf_model_cls` so Converter V2 (`use_v2=True` meta-device `from_config`) can instantiate the model directly. * For Converter V2 weight names (fused projections such as `w1` / `w3`), see gotcha F; it applies to all models, not only `trust_remote_code` ones. ### D. `sliding_window_ring_buffer_size` Flag Coupling When using `LiteRTLMCache` (`split_cache=False`), setting `sliding_window_ring_buffer_size=<W>` requires `apply_gpu_composites=True` and `use_bool_mask=True` (`extra_kwargs={'use_bool_mask': True}`). Without `apply_gpu_composites=True`, `_update_kv_sliding_impl` returns a `(cache_past, cache_slice)` tuple of length `W + T` while `local_mask` has length `W`, causing a dimension broadcast error (`W` vs `W + 1`) in decode. ### E. `bfloat16` Config Default in `assert_patch_numerical_parity` Many `config.json` files specify `"dtype": "bfloat16"`. Calling `AutoModelForCausalLM.from_config(cfg)` initializes weights in `bfloat16`, where multi-layer rounding differences are `~1e-2` vs. `atol=1e-4`. Always test parity in `torch.float32` (`assert_patch_numerical_parity` automatically casts `bfloat16`/`float16` models to `float32` when `cast_to_float32=True`). ### F. Converter V2 (`--use_v2`) Weight Streaming: `KeyError ... not found in weights_loader` With `--use_v2=True` and real weights, the model is traced on `meta` and every parameter of the *patched* model is streamed by name from the checkpoint via `HFCheckpointWeightsLoader`. A patch that fuses or renames parameters therefore breaks export with `KeyError: Parameter 'model.model.layers.0.<...>.weight' not found in weights_loader` during `Save Params Bin`, unless each patched parameter is: 1. an unchanged checkpoint key (keep retained submodules under their checkpoint names, e.g. LFM2's `FusedLfm2MLP` keeps `self.w2`), 2. covered by `fused_weight_specs` (built-ins only cover `gate_up_proj <- (gate_proj, up_proj)` and `qkv_proj <- (q_proj, k_proj[, v_proj])`; e.g. LFM2 declares `extension.FusedWeightSpec('gate_up_proj', ('w1', 'w3'), dim=0)`; the fused submodule must sit under the same parent as its parts), or 3. rebuilt by `resolve_checkpoint_tensor`. Verification traps: * `--use_random_weights=True` skips the weights loader entirely, so a passing random-weights V2 export proves nothing about weight mapping. * Fused names only exist when their flags are on (`--fuse_qkv`, `--fuse_gate_up`, `--use_swiglu_composite`, `--use_qkv_norm_rope_composite`, or `--apply_gpu_composites`, which implies them). Test V2 with these flags. * The full export fails only after tracing and lowering, and reports just the first missing parameter. Prefer `testing.assert_v2_weights_resolvable`, which checks all parameters with all flags on in `<2s`.
View on GitHub