MTP support in per-layer fused export - #2259
Draft
Fridah-nv wants to merge 1 commit into
Draft
Conversation
The refusal said MTP exclusions and orphaned weights are applied after calibration, when every shard is already written. The first half stopped being true once the prefixes were derived from the checkpoint index before calibration: the pre-quantize exclusion loop already leaves MTP modules unquantized, and finalize() already calls _add_mtp_exclusions. That leaves the orphans -- MTP tensors with no slot in state_dict(), which the separate-file conventions produce. load_mtp_weights only fills existing slots and returns the rest, so it is safe to run before quantize; per-layer export now does that and stashes the leftovers on the model under MTP_EXTRA_STATE_ATTR, which finalize() feeds into its existing extra_state_dict path. The stash exists because calibration owns the finalize() call, so hf_ptq cannot pass them as an argument -- the same reason _mtp_layer_prefixes is already carried that way. The blanket refusal is replaced by a narrow one: if the post-calibration load finds tensors that were not staged, the run still fails, because the shards are written by then and they cannot be added. Prototype: covered by an inlined-convention test that fails without the stash pickup. The separate-file conventions (GLM-4.7 standalone mtp.safetensors, Qwen3-Next indexed tail shard) have no local fixture and are unverified. Signed-off-by: Fridah-nv <201670829+Fridah-nv@users.noreply.github.com>
|
Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually. Contributors can view more details about this message here. |
Contributor
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueComment |
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.
What does this PR do?
Type of change: new feature
Stacked on #2136, which refuses MTP models outright. This lifts that refusal.
Why the refusal existed, and what changed. It said MTP exclusions and orphaned MTP
weights are applied after calibration, by which point per-layer export has already written
every shard and the quant config. Two thirds of that stopped being true:
hf_ptq.py, which appends{"quantizer_name": "*<prefix>*", "enable": False}toquant_cfg. feat(export): export each decoder layer as layerwise calibration finishes it #2136 already derivesthose prefixes from the checkpoint index before calibration, so MTP modules are simply
never quantized.
finalize()calls_add_mtp_exclusions.model.layers.{N}, GLM-5.1 / DeepSeek-V3) are returned byget_homogeneous_hf_decoder_layerslike any other decoder layer, so they already gettheir own shard — unquantized, because they are excluded.
That leaves orphans: MTP tensors with no slot in
model.state_dict(), which theseparate-file conventions produce.
load_mtp_weights()only fills existing slots and handsthe rest back, so it is safe to run before
mtq.quantize. Per-layer export now does thatand stashes the leftovers on the model under
MTP_EXTRA_STATE_ATTR;finalize()feeds theminto the
extra_state_dictpath it already had.The stash exists because calibration owns the
finalize()call, sohf_ptqcannot passthem as an argument.
_mtp_layer_prefixesis already carried across that same boundary thesame way, so this follows an existing convention rather than inventing one.
The blanket refusal becomes a narrow one: if the post-calibration load finds keys that were
not staged, the run still fails, because the shards are written by then and nothing can be
added to them.
Usage
No new flags. An MTP checkpoint with
layerwise.export_dirset now exports instead ofraising
NotImplementedError.Testing
tests/gpu/torch/export/test_layerwise_export.py— 25 passed. One new test pins thatstashed orphans reach the tail shard and the index; it fails with
mtp.layers.0.weight missing from the exported checkpointwhen the stash pickup is stubbedout, so it is not vacuous.
tests/unit/recipe282 ·tests/unit/torch/export172 ·tests/examples/hf_ptq36 ·pre-commit clean.
Draft, because the coverage is narrower than the feature. The test exercises orphan
delivery, which is the mechanism this PR changes. It does not exercise the four conventions
load_mtp_weightssupports — inlined (GLM-5.1, DeepSeek-V3), standalonemtp.safetensors(GLM-4.7), indexed
mtp.*tail shard (Qwen3-Next) — none of which has a local fixture.Two risks I have not been able to close:
load_mtp_weightsbefore quantization may split in-slot vs orphan differentlythan running it after, since
model.state_dict()changes once quantizers are inserted.The late guard compares key sets precisely because of this, but a real MTP model should
confirm the split is what we expect.
that path is reasoned about, not observed.
A full export on a real MTP checkpoint is the missing step before this leaves draft.
Before your PR is "Ready for review"
layerwise.export_dirset,which is opt-in. Non-layerwise MTP export is untouched.
guidance in
CONTRIBUTING.md: N/Aare unverified (see Testing).
layerwise.export_direntry and listsMTP as refused; it needs amending once merge order is settled.
Additional Information
Depends on #2136 and must not merge before it. #2136's refusal test and its real-checkpoint
verification both assert that MTP is refused, so whichever lands second needs them
reconciled.