Skip to content

Load quantized checkpoint weights per component - #733

Open
xiaoyu-work wants to merge 7 commits into
mainfrom
feat/per-component-quantization-unified
Open

Load quantized checkpoint weights per component#733
xiaoyu-work wants to merge 7 commits into
mainfrom
feat/per-component-quantization-unified

Conversation

@xiaoyu-work

@xiaoyu-work xiaoyu-work commented Sep 8, 2026

Copy link
Copy Markdown
Member

Summary

  • Add typed weight records, component-owned bundles, and the Olive/GPTQ/AWQ codec registry.
  • Load existing quantized checkpoint weights using independent component layouts and per-projection HuggingFace source-name overrides and exclusions; do not quantize floating-point checkpoint weights.
  • Preserve Gemma4, Qwen3.5/QMoE, and T5 architecture-specific weight adaptations, tied tables, and component sidecar routing, and validate quantized parameter binding.
  • Preserve default attention head dimensions and KV-head counts when HuggingFace configuration overrides only a subset of layers.
  • Keep Gemma4 per-layer inputs in [batch, sequence, layers, projection] form between embedding and decoder components for OpenVINO; other execution providers retain the flattened layout.
  • Restore the seq2seq, vision-language, and speech independent-component quantization matrix coverage lost during the earlier graph-test file split.

Loading correctness fixes

  • Recognize canonical weights only as complete, shape-matching groups so raw GPTQ/AWQ scales remain available to their codec; validate zero points using each projection's effective symmetry.
  • Normalize quantized embedding tables for two-dimensional GatherBlockQuantized storage rather than treating them as linear projections.
  • Honor Qwen3.5-MoE's model-declared float exclusions and Whisper's declared output head during both graph construction and loading.
  • Preserve the distinction between an absent component mapping (legacy global rules) and an explicitly empty mapping (float components).
  • Defer Gemma4 component packing until projection overrides are resolved. Restore T5 HuggingFace source aliases and separate decoder-stack rules from the independently owned top-level LM head.

Consolidation

Replaces #666, #669, and #670 and consolidates the related development from test/per-component-quantization-stack into this single PR.

The branch starts directly from main at a471d3416e9c52d5508d28730962e18c8a1053d0. The design (#664) and canonical component manifests (#665) are already merged and are not separate dependencies of this PR.

This includes all seven previously uncommitted Gemma4/config/EP files from the old development worktree, not just the three original PR heads. The committed old feature stack was also compared with main plus this PR: production feature areas were retained, and its missing graph-layout matrix tests were restored in the current split test modules.

Review and CI follow-up

Addressed all eight inline review threads, including duplicate/already-resolved reports, in 190eb8a5:

  • Literal module rules now respect path boundaries; the compatibility normalizer rejects a missing task clearly. Redundant Protocol statements and alias parentheses are removed.
  • Fixed the Gemma3 split-tied loading regression, including its previously floating-point scaled embedding. A shared scaled quantized embedding preserves Gemma4 behavior, and actual embedding execution plus unsupported-tie guards are covered.
  • Corrected the LLaVA fixture's missing vision_model namespace without relaxing target validation.
  • Updated the stale GGUF integration expectation to the export contract already established by Decouple export from downstream runtime capability gates #688; tokenizer-identity rejection and full logit/generation comparisons remain intact.

The original full L4/L5 jobs hit their explicit one-hour limits after recording failures. Those failed case IDs also occur on the exact base a471d341, but cancellation lost exception details, so they are not claimed to be infrastructure-only or fixed. No model selection, tolerance, or timeout was weakened. Isolated GPT-2 diagnostics at the original head each executed and passed exactly one selected case: L4 https://github.com/onnxruntime/mobius/actions/runs/34308638240 and L5 https://github.com/onnxruntime/mobius/actions/runs/34308638242 . These single-case successes do not certify the all-model golden jobs.

Fresh full CI for the published follow-up: https://github.com/onnxruntime/mobius/actions/runs/34309655380 .

Validation

  • 874 passed, 16 skipped, 4 deselected in the focused loading/config/model/CLI/graph suite, including the repaired CI selectors and scaled quantized embedding execution.
  • 1 passed for the real pinned smollm2-135m-instruct-f16 GGUF CLI export/reload, full-logit, and generation integration case.
  • Ruff lint and formatting passed for the changed Python files. Independent review of this follow-up found no significant issues.
  • Runs used the repository-required onnx-ir API through isolated dependencies; the shared environment was not modified.
  • The original seven-file pending patch remains consolidated in this PR. Local evidence does not claim that the all-model golden jobs or physical NPU execution are green.

Consolidate the typed weight records, per-component loader, and composite model adapters from #666, #669, and #670 into one change based on current main. The design and canonical component manifests are already merged through #664 and #665.

Preserve independent component layouts, source-name exclusions, Olive/GPTQ/AWQ sidecar normalization, Gemma4/Qwen3.5/T5 model adaptations, and quantized parameter binding validation. Clean up import, type-annotation, and formatting issues left by the earlier stacked-branch conflict resolutions.

Signed-off-by: Xiaoyu Zhang <xiaoyuzhang@microsoft.com>
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

Performance Comparison

Comparing a471d34030cee6

Model Metric Baseline Current Delta
bert (feature-extraction) model_size_bytes 359 KB 359 KB +0.0%
bert (feature-extraction) num_nodes 68 68 +0.0%
falcon model_size_bytes 364 KB 364 KB +0.0%
falcon num_nodes 66 66 +0.0%
gemma2 model_size_bytes 428 KB 428 KB +0.0%
gemma2 num_nodes 105 105 +0.0%
gpt2 model_size_bytes 324 KB 324 KB +0.0%
gpt2 num_nodes 54 54 +0.0%
llama model_size_bytes 425 KB 425 KB +0.0%
llama num_nodes 60 60 +0.0%
llama (static-cache) model_size_bytes 425 KB 425 KB +0.0%
llama (static-cache) num_nodes 56 56 +0.0%
mamba (ssm-text-generation) model_size_bytes 296 KB 296 KB +0.0%
mamba (ssm-text-generation) num_nodes 94 94 +0.0%
phi3 model_size_bytes 421 KB 421 KB +0.0%
phi3 num_nodes 58 58 +0.0%
phi3 (static-cache) model_size_bytes 421 KB 421 KB +0.0%
phi3 (static-cache) num_nodes 54 54 +0.0%
qwen2 model_size_bytes 425 KB 425 KB +0.0%
qwen2 num_nodes 60 60 +0.0%
qwen2 (static-cache) model_size_bytes 425 KB 425 KB +0.0%
qwen2 (static-cache) num_nodes 56 56 +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 265 265 +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 127 127 +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 450 450 +0.0%
t5 (seq2seq) model_size_bytes 836 KB 836 KB +0.0%
t5 (seq2seq) num_nodes 176 176 +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.

Comment thread src/mobius/weights/_adapters.py Fixed
Comment thread src/mobius/weights/_codecs.py Fixed
Comment thread src/mobius/weights/_codecs.py Fixed

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.

🟡 Changes recommended

There are a few correctness/maintenance issues in the updated component-quantization plumbing (including an optional-arg path that will fail at runtime and redundant/accidental code) that should be fixed before approval.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR upgrades Mobius’s quantized-checkpoint loading pipeline to be component-aware, introducing typed logical weight records and a codec registry to normalize existing Olive/GPTQ/AWQ packed sidecars per component (without quantizing float checkpoints), while preserving model-specific weight adaptation behavior and validating that quantized graph parameters are actually bound before publishing.

Changes:

  • Add mobius.weights typed abstractions (records/bundles), a quantization codec registry, and an explicit model-weight adapter boundary.
  • Switch Transformers + CLI build paths to route weights through the adapter + per-component normalization, then enforce a quantized-parameter binding gate.
  • Update per-component quantization/config plumbing and extend Gemma4/Qwen3.5/QMoE/T5 handling + tests to keep graph layouts and loaded packed weights aligned.
File summaries
File Description
tests/build_graph/_support.py Adds helper to assign per-component quantization layouts in graph tests.
src/mobius/weights/_records.py Introduces typed logical weight records (FloatWeight, PackedWeight, WeightRecord, WeightBundle).
src/mobius/weights/_records_test.py Unit tests for typed weight records and bundle invariants.
src/mobius/weights/_codecs.py Adds codec interface + registry and a legacy affine codec wrapper (Olive/GPTQ/AWQ).
src/mobius/weights/_codecs_test.py Tests for codec grouping/validation/normalization behavior.
src/mobius/weights/_adapters.py Adds explicit model adapter boundary with legacy preprocess_weights fallback.
src/mobius/weights/_adapters_test.py Tests adapter precedence and legacy hook compatibility.
src/mobius/weights/init.py Exposes new typed weight/codec/adapter APIs from the mobius.weights package.
src/mobius/models/t5.py Applies per-component quantization plans to encoder vs decoder construction.
src/mobius/models/qwen35.py Routes decoder quantization through component plans and aligns VL decoder source paths.
src/mobius/models/qwen35_test.py Adds regression test for plan-only quantization alignment with QMoE packing.
src/mobius/models/gemma4.py Extends component quantization resolution and adds HF module alias metadata for routing/exclusions.
src/mobius/models/gemma4_test.py Adds broader component-plan tests: regex exclusions, layout-per-component, routing, embedding sidecars.
src/mobius/integrations/transformers/_builder.py Switches loading to use component manifests + adapters + per-component normalization + binding validation.
src/mobius/integrations/transformers/_builder_test.py Adjusts test config to reflect per-component layouts and stripping behavior.
src/mobius/components/_moe.py Ensures QMoE quantization compatibility uses decoder component quantization when present.
src/mobius/_configs/_quantization.py Reworks module-plan rules (modules_to_not_convert, overrides) and regex validation.
src/mobius/_configs/_base.py Clarifies component-quantization extraction semantics and config comments.
src/mobius/_configs_test.py Updates tests for new module-rule matching and component quantization parsing behavior.
src/mobius/_component_quantization.py Major refactor: manifest-driven component configuration, packed-sidecar routing/normalization, binding validation.
src/mobius/_component_quantization_test.py Updates/expands tests for authoritative component plans, routing, and binding validation.
src/mobius/_component_manifest.py Allows manifest resolution without requiring model_type to be non-null.
src/mobius/_builder.py Threads a resolved ComponentManifest through build to keep graph+weight routing aligned.
src/mobius/main.py Updates CLI build/load path to use adapter + component normalization + binding validation.
CHANGELOG.md Updates Unreleased notes to describe per-component quantized checkpoint loading behavior.
Review details
  • Files reviewed: 25/25 changed files
  • Comments generated: 4
  • Review effort level: Lite

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

Comment thread src/mobius/_component_quantization.py Outdated
Comment thread src/mobius/_configs/_quantization.py Outdated
Comment thread src/mobius/_component_quantization.py Outdated
Comment thread src/mobius/models/gemma4.py
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

🏗️ Architecture Diff

Comparing a471d34030cee6

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)

Bring all seven pending source changes from the old per-component quantization stack into the unified PR instead of leaving real feature work on a separate branch.

Keep sparse per-layer overrides from replacing default head dimensions and KV-head counts. Preserve a layered per-layer-input ABI between Gemma4 embedding and decoder exports for OpenVINO while retaining the flattened layout for other execution providers. Include the accompanying regressions and document the component contract.

Signed-off-by: Xiaoyu Zhang <xiaoyuzhang@microsoft.com>
Carry forward the seq2seq, vision-language, and speech component-layout matrix tests from the old integration branch. These tests were lost when the monolithic graph test file was split during earlier stacked-branch conflict resolutions, leaving their shared configuration helper unused.

Reuse the shared helper in the current split test modules so independent component bit widths and group sizes remain covered by the unified PR.

Signed-off-by: Xiaoyu Zhang <xiaoyuzhang@microsoft.com>
Resolve graph layout and checkpoint normalization with the same per-module
policy, including HuggingFace source aliases, model float exclusions and
output heads. Preserve raw sidecars until the effective layout is known,
and distinguish Gather embedding tables from MatMul weight layouts.

Keep legacy absent-component configuration working, defer Gemma4 component
normalization, and restore exact T5 source ownership without applying decoder
stack rules to a separately owned LM head. Add packed-value loading regressions
for mixed components, overrides, symmetry, and protected output heads.

Signed-off-by: Xiaoyu Zhang <xiaoyuzhang@microsoft.com>
Comment thread src/mobius/weights/_codecs.py Fixed
Match literal quantization rules on module-path boundaries and require an
explicit task for compatibility normalization. Remove redundant Protocol
placeholders and simplify Gemma4 source aliases.

Use a shared scaled quantized embedding for Gemma3 component plans while
preserving Gemma4 behavior. Normalize materialized split tied tables against
supported targets, retain unsafe-tie rejection, and cover actual embedding
execution as well as effective head-layout mismatches.

Correct the LLaVA vision fixture namespace and update the stale GGUF runtime
admission expectation without weakening tokenizer identity or numerical
parity checks. The focused suite and pinned SmolLM2 GGUF integration pass.

Signed-off-by: Xiaoyu Zhang <xiaoyuzhang@microsoft.com>

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.

Note

Copilot was unable to run its full agentic suite in this review.

Pull request overview

Copilot reviewed 41 out of 41 changed files in this pull request and generated 5 comments.

Suppressed comments (1)

tests/build_graph/_support.py:1

  • This test helper assigns layouts based on enumerate(task.model_roles.items()), so the chosen (bits, group_size) depends on the insertion order of model_roles. If model_roles differs across tasks or is edited, tests can become brittle/non-obvious.\n\nSuggestion (optional): iterate in a stable order (e.g., for index, component in enumerate(sorted(task.model_roles))) or explicitly define the component order you want to exercise, so layout assignment is deterministic and resilient to unrelated dictionary edits.

Comment thread src/mobius/_component_manifest.py
Comment thread src/mobius/_component_quantization.py
Comment thread src/mobius/_configs/_quantization.py
Comment thread src/mobius/_configs/_quantization.py
Comment thread src/mobius/components/_quantized_linear.py
Resolve dynamic source hooks from an explicit or HuggingFace config model type, and skip them when neither exists instead of passing an empty identifier. Keep static sources authoritative for classes without dynamic hooks.

Remove the unused model-type argument from config-based alias hooks so hand-built T5 configs retain their layer mappings. Cover missing identifiers, explicit type precedence, and static/config-based metadata preservation, and document the hook contract.

Signed-off-by: Xiaoyu Zhang <xiaoyuzhang@microsoft.com>

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.

🟡 Changes recommended

Projection overrides, explicit float MoE gates, binding coverage, and the documented adapter contract remain inconsistent.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

src/mobius/weights/_adapters.py:41

  • The public adapter protocol does not match the component-owned typed pipeline documented in docs/design/per-component-quantization-loading.md:373-402. It receives the entire raw state dict before routing/grouping instead of a ComponentDescriptor and WeightBundle, so an implementation cannot enforce single-component ownership and can still consume or normalize unrelated sidecars. Either invoke adapters per routed bundle after grouping, or explicitly revise the design and PR API contract as a transitional raw-state adapter.
  • Files reviewed: 41/41 changed files
  • Comments generated: 3
  • Review effort level: Balanced

Comment thread src/mobius/_component_quantization.py
Comment thread src/mobius/_component_quantization.py Outdated
Comment thread src/mobius/models/qwen35.py

@titaiwangms titaiwangms 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.

Full-team review of ad666a825b9becf297ae350b5a98f05dc2ae831a (readability, correctness, adversarial, semantic-invariant, and integration passes).

The typed records/codec boundary and final binding check are strong improvements, but I found four correctness issues that should be addressed before merge:

  1. Every plain Embedding in a quantized component is now treated as a token table.
    src/mobius/_component_quantization.py:346-366 rewrites any ordinary Embedding when quantize_embeddings=True. The previous implementation restricted this to known token-table names, and this PR removes the test that ensured positional embeddings remained float. Components such as speech decoders can own both token and learned positional embeddings; a positional table may therefore become GatherBlockQuantized even though the checkpoint contains only its float weight, leaving packed inputs unbound. Please make this decision manifest/source-name driven (or otherwise restore a token-table-only gate) and add a non-token embedding regression test.

  2. Qwen3.5 component adapters still normalize ordinary decoder sidecars before per-projection overrides are resolved.
    src/mobius/models/qwen35.py:582-588, :702-722, and :988-1005 call preprocess_quantized_weights with a decoder-wide configuration. QuantizationConfig.for_source_paths() (src/mobius/_configs/_quantization.py:256-266) resolves the source roots and strips the remaining module rules. A leaf override such as model.language_model.layers.0.self_attn.q_proj can therefore build an overridden graph through the generic per-module rewrite while the adapter has already packed its weight with the component default. The shared loader sees canonical keys and cannot correct the layout. Please keep non-expert sidecars raw in component mode, as Gemma4 does, and limit the adapter to rename/tie/QMoE-specific transformations.

  3. The graph and weight paths disagree on when legacy global module rules are active.
    configure_component_quantization() gates them on manifest.names == ("model",) (src/mobius/_component_quantization.py:428-434), while normalize_component_quantized_weights() gates them on len(component_names) == 1 (:610-616). Single-component tasks named decoder, vision_encoder, etc. can consequently normalize with exclusions/overrides that were never applied to graph construction. Please use one shared predicate based on the same manifest/package ownership contract.

  4. Binding validation skips that newly supported legacy module-rule path.
    validate_quantized_component_bindings() returns whenever component_quantization is None (src/mobius/_component_quantization.py:740-746), even though configure/normalize explicitly support a root quantization containing exclusions or overrides. Missing packed weights/scales can therefore survive the loader and fail only during session creation or inference. The validator can safely scan emitted quantized ops regardless of whether the plan came from the component mapping or the root config.

Additional issues:

  • normalize_component_quantized_weights() skips codec grouping when there is no raw qweight (src/mobius/_component_quantization.py:628-632), and its final residual check covers only qweight keys (:728-730). An unrouted/orphan *_scales, *_qzeros, .scales, or .qzeros entry is silently accepted. Validate all packed sidecar suffixes and add a scales-only regression test.
  • The design contract says QuantizationCodec.normalize(...) -> WeightRecord, while the implemented protocol returns dict[str, torch.Tensor] (docs/design/per-component-quantization-loading.md, codec section vs. src/mobius/weights/_codecs.py:174-180). Please update the design snippet so future codecs implement the actual interface.

Positive notes: complete canonical-group detection is substantially safer than suffix-only detection; zero-point validation occurs after effective override resolution; and CLI/Transformers now share the same manifest → adapter → codec → binding pipeline.

Preserve positional embeddings and architecture-specific float shared gates when applying component plans. Use the complete manifest to keep legacy module-rule activation consistent between graph construction and normalization.

Defer ordinary packed sidecars until per-projection layouts are resolved, while retaining QMoE expert packing and float table tying. Reject orphan sidecars without rejecting complete canonical affine or QMoE groups, and validate bound quantized inputs for legacy root configurations.

Add regression coverage for graph layouts and loaded tensor values, and clarify the implemented codec and compatibility-adapter contracts.

Signed-off-by: Xiaoyu Zhang <xiaoyuzhang@microsoft.com>

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.

🟡 Changes recommended

GPTQ activation-order metadata is silently bypassed, and invalid embedding layouts can produce graphs that contradict the requested component plan.

Get a fresh assessment by requesting another Copilot review.

Review details
  • Files reviewed: 45/45 changed files
  • Comments generated: 2
  • Review effort level: Balanced

key for key in state_dict if key.endswith(("_qweight", ".qweight"))
)
for qweight_key in qweight_keys:
logical_name, scales_key, _, zero_points_key = self._logical_name(qweight_key)
Comment on lines +356 to +364
if isinstance(child, Embedding) and type(child).forward is Embedding.forward:
embedding_dim = int(child.weight.shape[1])
if (
quantization.quantize_embeddings
and any(
name.rsplit(".", 1)[-1] in _TOKEN_EMBEDDING_NAMES for name in source_names
)
and embedding_dim % quantization.group_size == 0
):
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.

4 participants