feat(weight-free): weight-free ONNX export for causal LMs (dynamo path) - #1
Open
amarquic wants to merge 26 commits into
Open
feat(weight-free): weight-free ONNX export for causal LMs (dynamo path)#1amarquic wants to merge 26 commits into
amarquic wants to merge 26 commits into
Conversation
…sal LMs New files: - exporter/weight_free.py: core weight-free dynamo export — builds meta model, traces with FakeTensors, promotes ONNX initializers to external data, builds weight_spec.json mapping ONNX names to safetensors checkpoint keys - exporter/weight_spec.py: WeightSpec format — describes external weight files and tensor→checkpoint-key mappings consumed by the QAIC compiler - exporter/checkpoint_transforms.py: checkpoint preparation pipeline — DtypeConversionCheckpointTransform, MoEExpertStackingCheckpointTransform (stacks per-expert tensors into batched layout), and GptOssMxfp4ExpertDequantSplitCheckpointTransform (dequant MXFP4 experts) - exporter/__init__.py: export new checkpoint transform classes - scripts/preprocess_moe_checkpoint.py: standalone MoE checkpoint prep script - examples/text_generation/weight_free_export_verify.py: e2e weight-free export verification (export → ORT → parity check) - examples/text_generation/weight_free_export_simple.py: minimal weight-free export example for causal LMs Signed-off-by: amarshar <amarshar@qti.qualcomm.com> Signed-off-by: Amar <amarshar@qti.qualcomm.com>
- gemma.py, gemma2.py: align cos/sin device for dynamo tracing - glm4_moe.py: __qeff_init__ uses meta-device guard for weight-free; register gate_proj/up_proj/down_proj_t directly on self.experts; remove get_all_projections() helper; fix rotary_dim computation - granite.py: full_like for causal mask; device alignment - granitemoe.py: weight-free subfunction export support - grok1.py: replace bool-mask expert loop with gather-BMM pattern; add __qeff_init__ stacking per-expert weights; meta-device guard - llama.py: fix decoder layer for dynamo — buffers, nested compile regions, rotary signature alignment - phi.py, phi3.py: QEffPhi3MLP to avoid SplitToSequence from chunk(2); device alignment fixes - qwen2.py: cos/sin device alignment - qwen3.py: extend qeff_apply_rotary_pos_emb signature and align cos/sin - qwen3_moe.py: weight-free dynamo export; expert weight stacking with meta-device guard; RAM reduction via view aliases (no clone) Signed-off-by: amarshar <amarshar@qti.qualcomm.com> Signed-off-by: Amar <amarshar@qti.qualcomm.com>
…xport() - _export(): add use_weight_free_export flag; pop mla_absorption/ prefill_seq_len/ctx_len from export_kwargs; resolve weight_spec_path; guard _model_offloaded_check() — skip for meta-device weight-free models; set up tmp_onnx_dir; route to export_weight_free_onnx() when flag is set; use effective_transform_owner so weight-free model's transform list applies; embed weight_spec JSON in ONNX metadata; add finally cleanup of tmp dir; create symlink to prepared checkpoint directory after export - get_onnx_path(): add use_weight_free_export and mla_absorption params; pass use_weight_free_export through to _export() kwargs - _compile(): add use_weight_free_export param; pass through to get_onnx_path() Signed-off-by: amarshar <amarshar@qti.qualcomm.com> Signed-off-by: Amar <amarshar@qti.qualcomm.com>
…path Rewrites SplitToSequence→Split, CastLike→Cast, and aten_split/aten_getitem call patterns emitted by the dynamo exporter. Required for models using .chunk() (e.g. Phi3 SwiGLU MLP) that produce QAIC-unsupported ops. Signed-off-by: amarshar <amarshar@qti.qualcomm.com> Signed-off-by: Amar <amarshar@qti.qualcomm.com>
…orCausalLM - Add Any import; add checkpoint transform imports - Add _checkpoint_transforms to QEFFAutoModelForCausalLM with GptOssMxfp4ExpertDequantSplitCheckpointTransform, MoEExpertStackingCheckpointTransform, DtypeConversionCheckpointTransform in priority order - Set _onnx_transforms = [RewriteUnsupportedOpsTransform] - Add use_weight_free_export param to export() and compile(); pass it through to _export() so the weight-free path is accessible via the standard compile(use_weight_free_export=True) call - Build dynamic_shapes for weight-free path same as dynamo path Signed-off-by: amarshar <amarshar@qti.qualcomm.com> Signed-off-by: Amar <amarshar@qti.qualcomm.com>
…eight-free - Add device=position_ids.device to all torch.arange calls in cache update methods so meta-tensor device matches during weight-free subfunction export - Replace torch.tensor(0.0, dtype=float32) with torch.zeros_like() in invalid-mask branches for correct shape inference under FakeTensor tracing - Add QEffHybridChunkedCache utility methods needed for weight-free export Signed-off-by: amarshar <amarshar@qti.qualcomm.com> Signed-off-by: Amar <amarshar@qti.qualcomm.com>
- ctx_scatter_gather.py, ctx_scatter_gather_cb.py: add no-op backward() stubs to all CtxScatter/Gather autograd Functions so torch.export can trace through the autograd graph structure during dynamo export - rms_norm.py: replace QEffGemmaRMSNorm.__qeff_init__ weight mutation with a runtime forward() that applies weight + 1.0 via select_interface; the __qeff_init__ copy_ fails on meta-device during weight-free export since meta tensors cannot hold data Signed-off-by: amarshar <amarshar@qti.qualcomm.com> Signed-off-by: Amar <amarshar@qti.qualcomm.com>
…dynamo path - _setup_onnx_subfunctions: apply torch patches only on TorchScript path (not dynamo); resolve target_classnames from get_submodules_for_export at the call site for dynamo vs inside the function for TorchScript - _cleanup_onnx_subfunctions: undo torch patches only when they were applied - _RetainedState rename block: skip for dynamo path so PreserveNestedCacheRetainedStateTransform can locate dangling outputs Signed-off-by: amarshar <amarshar@qti.qualcomm.com> Signed-off-by: Amar <amarshar@qti.qualcomm.com>
…gions - temporarily_enable_nested_compile_regions: wraps selected module forward() methods with @nested_compile_region at export time so dynamo materializes repeated decoder block functions (used by weight-free subfunction export) - temporarily_disable_nested_compile_regions: unwraps nested_compile_region- decorated forwards before export to avoid premature subgraph boundaries (used by weight-free non-subfunction path) Signed-off-by: amarshar <amarshar@qti.qualcomm.com> Signed-off-by: Amar <amarshar@qti.qualcomm.com>
ctx_indices is (batch, 1, ctx_len) — use dim -1 not dim 1 to get ctx_len. Signed-off-by: amarshar <amarshar@qti.qualcomm.com> Signed-off-by: Amar <amarshar@qti.qualcomm.com>
Signed-off-by: amarshar <amarshar@qti.qualcomm.com> Signed-off-by: Amar <amarshar@qti.qualcomm.com>
…o path get_sampling_inputs_and_outputs now accepts and populates dynamic_shapes alongside dynamic_axes, building Dim entries for each sampler input so the dynamo export path gets consistent symbolic shapes for samplers. Signed-off-by: amarshar <amarshar@qti.qualcomm.com> Signed-off-by: Amar <amarshar@qti.qualcomm.com>
…ee export - Add Phi3MLP → QEffPhi3MLP to KVCacheTransform: QEffPhi3MLP wraps the MLP to avoid SplitToSequence from chunk(2) in dynamo ONNX, which QAIC does not support - Wire QEffGrok1MoeBlock.__qeff_init__ into KVCacheExternalModuleMapperTransform: stacks per-expert weights during init so the gather-BMM forward works correctly for weight-free export Signed-off-by: amarshar <amarshar@qti.qualcomm.com> Signed-off-by: Amar <amarshar@qti.qualcomm.com>
In this branch the function lives in compile/mdp_generator.py not utils/_utils.py — importing it via utils/__init__.py caused ImportError. Signed-off-by: amarshar <amarshar@qti.qualcomm.com> Signed-off-by: Amar <amarshar@qti.qualcomm.com>
…calls Prevents meta/cpu device mismatch during weight-free export tracing. Signed-off-by: amarshar <amarshar@qti.qualcomm.com> Signed-off-by: Amar <amarshar@qti.qualcomm.com>
Signed-off-by: amarshar <amarshar@qti.qualcomm.com> Signed-off-by: Amar <amarshar@qti.qualcomm.com>
…arse_moe key mapping checkpoint_transforms.py: - Add _convert_bin_to_safetensors(): auto-converts .bin checkpoints to safetensors on first use via save_pretrained(safe_serialization=True); idempotent - CheckpointTransformPipeline.apply(): detect .bin files and convert before transform - MoEExpertStackingCheckpointTransform: extend EXPERT_RE and _LayerStacker.add() to handle Mixtral w1/w3/w2 naming (w1→gate, w3→up, w2→down) weight_free.py: - _resolve_checkpoint_dir(): fall back to downloading .bin files when no safetensors exist on HuggingFace Hub (e.g. GPT-J-6B); CheckpointTransformPipeline auto-converts - _find_checkpoint_key(): add lookup 5 to bridge mlp↔block_sparse_moe rename — transformers 5.x renamed the MoE block attribute, ONNX uses 'mlp' but Mixtral checkpoint has 'block_sparse_moe' Signed-off-by: amarshar <amarshar@qti.qualcomm.com> Signed-off-by: Amar <amarshar@qti.qualcomm.com>
…, Mistral, Mixtral, OLMo2 falcon/modeling_falcon.py: - QEffFalconModel.__qeff_init__: meta device guard for sin/cos — skip storing as nn.Parameter on meta device to avoid meta tensor ONNX initializer failure gpt_oss/modeling_gpt_oss.py: - qeff_apply_rotary_pos_emb: add device/dtype alignment for weight-free tracing - ctx_indices/short_read_idx: add device=position_ids.device to torch.arange granitemoe/modeling_granitemoe.py: - QEffGraniteMoeAttention.__qeff_init__: set num_heads/num_key_value_heads as instance attributes — required by @nested_compile_region inside decoder layer - past_key_value_update: fix 3-tuple unpack to 4-tuple - eager_attention_forward: use full_like for causal mask to avoid config.torch_dtype - QEffGraniteMoeTopKGating: drop .to(dtype) after softmax (SoftMax+Cast → QAIC crash) - QEffGraniteMoeDecoderLayer: return hidden_states directly instead of tuple mistral/modeling_mistral.py: - qeff_apply_rotary_pos_emb: add device/dtype alignment mixtral_moe/modeling_mixtral.py: - QEffMixtralExperts.__qeff_init__: meta device guard for gate/up/down projections - QEffMixtralModel.__qeff_init__: drop attention_scaling multiply (creates new tensor that becomes top-level ONNX initializer; direct reference avoids it) - QeffMixtralDecoderLayer: add @torch.compiler.nested_compile_region - QEffMixtralExperts: add to KVCacheTransform so __qeff_init__ is called - eager_attention_forward: use full_like for causal mask olmo2/modeling_olmo2.py: - qeff_apply_rotary_pos_emb: add device/dtype alignment pytorch_transforms.py: - Wire QEffMixtralExperts.__qeff_init__ (MixtralExperts→QEffMixtralExperts mapping) - Wire QEffGraniteMoeAttention.__qeff_init__ (already mapped, __qeff_init__ added) Signed-off-by: amarshar <amarshar@qti.qualcomm.com> Signed-off-by: Amar <amarshar@qti.qualcomm.com>
examples/text_generation/weight_free_export_verify.py: - Single-model smoke-test: weight-free dynamo export → compile → QPC inference - --no_weight_free: regular from_pretrained + dynamo path - --layers: override num_hidden_layers for fast testing - Config loaded without trust_remote_code first (fixes Falcon legacy config) examples/text_generation/continuous_batching_weightfree.py: - Single-model weight-free export with continuous batching (full_batch_size) - Same config fix for legacy models like Falcon examples/performance/compute_context_length/weight_free_ccl_verify.py: - Single-model CCL verification: compile with comp_ctx_lengths_prefill/decode - Prints specializations.json to confirm CCL slots were compiled - Default: ctx_len=4096, CCL=[1024,2048,4096], weight-free + dynamo Signed-off-by: amarshar <amarshar@qti.qualcomm.com> Signed-off-by: Amar <amarshar@qti.qualcomm.com>
amarquic
force-pushed
the
enable_weightfree_for_causallm
branch
from
July 13, 2026 07:45
b5dca6a to
03ce067
Compare
…fter rebase The rebase auto-merge dropped the if not use_dynamo: guard introduced in Sharvari's d6f55bf commit. Restore it so TorchScript patches are not applied on the dynamo export path, and restore use_dynamo in the state dict so _cleanup_onnx_subfunctions can check it. Signed-off-by: Amar <amarshar@qti.qualcomm.com>
- Attention softmax: remove .to(query.dtype) — emits SoftMax->Cast in the subfunction ONNX which triggers optimizeSoftmax assertion in QAIC compiler - Router softmax: replace torch.softmax with manual Exp/Sum/Div — with num_local_experts=8, top_k_gates has 8 Einsum consumers; optimizeSoftmax asserts when a SoftMax node has more than one consumer Signed-off-by: Amar <amarshar@qti.qualcomm.com>
- cache_utils.py: restore missing return k_out, v_out in QEffHybridCache.update() and QEffSlidingWindowCache.update(); remove orphan method blocks that were left floating inside wrong classes after rebase - exporter/__init__.py: use explicit re-export syntax (X as X) for F401 - checkpoint_transforms.py, glm4_moe, grok1, preprocess_moe_checkpoint.py: rename ambiguous variable I to ffn_dim / hidden_dim (E741) - gemma, gemma2, qwen3: remove unused mask_value assignment (F841) - Run ruff format across all 39 changed files Signed-off-by: Amar <amarshar@qti.qualcomm.com>
Creates tests/dynamo/ following the same structure as PR quic#1174: - conftest.py: markers, dynamo_workdir fixture, xdist-safe result capture, HTML/JSON report generation - utils/hash_manager.py: blake2b-keyed workdirs (collision-safe under -n auto) - utils/hardware_manager.py: file-lock QAIC device pool for HW lanes - utils/report_generator.py: HTML + JSON coverage matrix output Extends DynamoModelSpec in model_registry.py with two new flags: weight_free_supported - True for all validated causal LM architectures weight_free_cb_supported - True for all architectures where CB is also verified Both flags False for VLM text-side models (out of scope for this PR) Adds tests/dynamo/causal_lm/test_weight_free.py with 4 CPU-only lanes: 1. test_weight_free_export - meta-device export produces ONNX + weight_spec.json 2. test_weight_free_no_embedded_weights - ONNX has no large embedded initializers 3. test_weight_free_spec_coverage - all spec keys resolve in prepared_checkpoint/ 4. test_weight_free_cb_export - continuous_batching=True export produces artifacts All lanes are @pytest.mark.dynamo_export (CPU, no QAIC hardware) and @pytest.mark.regular. Run with: pytest tests/dynamo/causal_lm/test_weight_free.py -m 'dynamo_export' -n auto Signed-off-by: Amar <amarshar@qti.qualcomm.com>
…+ cleanup - exporter/weight_free/transforms.py: add MoEFusedExpertSplitCheckpointTransform for MoE checkpoints that already store experts in fused/stacked form (gate_up_proj [E,2I,H] + down_proj [E,H,I]). Splits them into the derived layout that QEff ONNX initializers expect (gate_proj/up_proj/down_proj_t). Registered in pipeline between MoEExpertStackingCheckpointTransform (old per-expert format) and DtypeConversionCheckpointTransform (dense fallback). - modeling_auto.py: register MoEFusedExpertSplitCheckpointTransform in QEFFAutoModelForCausalLM._checkpoint_transforms pipeline - customop: remove backward() stubs from CtxScatter/Gather — not needed for dynamo weight-free export (verified by test) - exporter/weight_free/core.py: fix sys.path for scripts/memory_profiling (extra .parent for new subdirectory depth) - docs/source/weight_free_export.md: add weight-free export documentation - examples/text_generation/weight_free/: update docstring paths and add __init__.py Signed-off-by: Amar <amarshar@qti.qualcomm.com>
smedhe
force-pushed
the
enable_dynamo_for_causallm
branch
3 times, most recently
from
July 16, 2026 13:42
b24dc14 to
f4e951b
Compare
Qwen3-MoE stores the router weight as mlp.router.weight in the checkpoint but the QEff wrapper accesses it as mlp.gate.weight. Add lookup strategy 6 to _find_checkpoint_key to handle this rename in both directions so the weight is correctly promoted to an external reference instead of remaining as an embedded meta tensor that causes SerdeError. Signed-off-by: Amar <amarshar@qti.qualcomm.com>
…pipeline - core.py: use config.dtype (not hardcoded float32) for meta model cast and checkpoint conversion; getattr fallback handles dtype=None configs - core.py: propagate enable_proxy flag through _build_meta_qeff_model - cache_utils.py: use v_out.dtype instead of torch.float32 for invalid-mask zeros_like in 5 KV cache update paths — avoids fp32↔bf16 dtype mismatch - modeling_llama.py: use attn_weights.dtype for masked_fill and softmax upcast instead of hardcoded float32 - export_compile_infer.py: add --dtype flag (default float16) to control model weight dtype; add enable_proxy=False - modeling_qeff.py: skip SplitTensorsTransform for weight-free export Signed-off-by: Amar <amarshar@qti.qualcomm.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR adds weight-free ONNX export support for causal LMs on top of Sharvari's dynamo export branch.
Weight-free export exports the ONNX graph with no embedded weights. Weights are stored in the original safetensors checkpoint and loaded by the QAIC compiler at compile time via
weight_spec.json. This enables:Key changes
New infrastructure:
exporter/weight_free.py— core weight-free dynamo export: meta model trace, promotes initializers to external data, buildsweight_spec.jsonexporter/weight_spec.py— WeightSpec format: maps ONNX initializer names → safetensors checkpoint keysexporter/checkpoint_transforms.py— checkpoint preparation pipeline: MoE expert stacking, dtype conversion, MXFP4 dequant, auto .bin→safetensors conversionBase class wiring:
base/modeling_qeff.py—_export():use_weight_free_exportflag, meta device guard, tmp ONNX path, weight_spec embedding in ONNX metadatatransformers/models/modeling_auto.py—QEFFAutoModelForCausalLM:_checkpoint_transforms, CCL Dim fix,use_weight_free_exportthrough compile chainModel-level fixes for dynamo tracing:
cache_utils.py—device=position_ids.deviceon alltorch.arangecallsInfrastructure fixes:
base/onnx_transforms.py—RewriteUnsupportedOpsTransform(SplitToSequence→Split, CastLike→Cast)utils/export_utils.py—_RetainedStaterename guard for dynamo pathutils/torch_patches.py—temporarily_enable/disable_nested_compile_regionscustomop/— backward stubs,CtxGatherCBshape fix,GemmaRMSNormruntime offsetExamples:
examples/text_generation/weight_free_export_verify.py— single-model smoke-testexamples/text_generation/continuous_batching_weightfree.py— weight-free + CBexamples/performance/compute_context_length/weight_free_ccl_verify.py— weight-free + CCLValidated models
Qwen3-30B-A3B, Qwen3-MoE, GPT-OSS, GPT2, Gemma-2-2B, Gemma-7B, Llama-3.2-1B, Phi3, GLM4-MoE
Usage
Test commands run