# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Auto-dispatch model factory and parameter utilities.
``AutoModel.from_pretrained`` reads a checkpoint config, picks the right model
class, constructs it, and loads weights — the primary entry point for callers.
Custom model classes can be registered via :func:`register_model` to override
the default :class:`~models.default.modeling_default.CausalLM` for a given
``model_type`` string.
"""
import dataclasses
import logging
import os
from typing import Callable, Dict, Type
import torch.nn as nn
from .checkpoint.checkpoint_utils import load_checkpoint_config_dicts
from .checkpoint.loader import load_weights
from .config import (QUANT_FP16, QUANT_INT4_AWQ, QUANT_INT4_AWQ_MODELOPT,
QUANT_INT4_GPTQ, QUANT_MXFP8, QUANT_NVFP4, ModelConfig,
_is_gemma4_assistant_model_type, make_dflash_draft_config,
make_dspark_draft_config, make_mtp_draft_config,
module_quant_type)
__all__ = [
"AutoModel", "load_model_config", "register_attention_scale_default",
"register_model", "standard_attention_scale", "dtype_summary",
"param_count"
]
AttentionScaleDefault = Callable[[int], float]
_MODEL_REGISTRY: Dict[str, Type[nn.Module]] = {}
_ATTENTION_SCALE_DEFAULT_REGISTRY: Dict[str, AttentionScaleDefault] = {}
def standard_attention_scale(head_dim: int) -> float:
return 1.0 / (float(head_dim)**0.5)
_QWEN3_5_MTP_BASE_MODEL_TYPES = frozenset({
"qwen3_5_text",
"qwen3_5_moe",
"qwen3_5_moe_text",
"qwen3_omni_next_text_moe",
})
_QWEN3_5_MTP_DRAFT_MODEL_TYPES = frozenset({
"qwen3_5_text",
"qwen3_5_moe_text",
"qwen3_omni_next_text_moe",
})
def _is_qwen3_5_mtp_base_supported(model_type: str) -> bool:
return model_type in _QWEN3_5_MTP_BASE_MODEL_TYPES
def _is_qwen3_5_mtp_draft_supported(model_type: str) -> bool:
return model_type in _QWEN3_5_MTP_DRAFT_MODEL_TYPES
_GROUP_SIZE_LM_HEAD_QUANTS = frozenset({
QUANT_INT4_AWQ,
QUANT_INT4_AWQ_MODELOPT,
QUANT_INT4_GPTQ,
QUANT_MXFP8,
QUANT_NVFP4,
})
[docs]
def register_model(model_type: str, model_class: Type[nn.Module],
default_attention_scale: AttentionScaleDefault) -> None:
"""Register *model_class* as the handler for *model_type*.
When :meth:`AutoModel.from_pretrained` encounters a checkpoint whose
``model_type`` field equals *model_type*, it instantiates *model_class*
instead of the built-in :class:`~models.default.modeling_default.CausalLM`.
Args:
model_type: Value of ``model_type`` in the checkpoint ``config.json``.
model_class: ``nn.Module`` subclass; must accept a single
:class:`~config.ModelConfig` as its constructor argument.
default_attention_scale: Function returning this family's default for
a given attention head dimension.
"""
_MODEL_REGISTRY[model_type] = model_class
_ATTENTION_SCALE_DEFAULT_REGISTRY[model_type] = default_attention_scale
def register_attention_scale_default(
model_type: str,
default_attention_scale: AttentionScaleDefault) -> None:
"""Register a default for a model type with special factory dispatch."""
_ATTENTION_SCALE_DEFAULT_REGISTRY[model_type] = default_attention_scale
def load_model_config(model_dir: str) -> ModelConfig:
"""Load ModelConfig using the default declared by its model family."""
default_attention_scale = _default_attention_scale_for_model_dir(model_dir)
return ModelConfig.from_pretrained(model_dir, default_attention_scale)
def _default_attention_scale_for_model_dir(
model_dir: str) -> AttentionScaleDefault:
"""Return the attention-scale default registered by checkpoint model_type."""
root, llm_dict = load_checkpoint_config_dicts(model_dir)
default_attention_scale = standard_attention_scale
for config in (root, llm_dict):
model_type = config.get("model_type")
if model_type in _ATTENTION_SCALE_DEFAULT_REGISTRY:
default_attention_scale = _ATTENTION_SCALE_DEFAULT_REGISTRY[
model_type]
break
return default_attention_scale
[docs]
class AutoModel:
"""HuggingFace-style factory that dispatches on ``model_type``."""
[docs]
@classmethod
def from_pretrained(cls,
model_dir: str,
device: str = "cpu",
key_remap=None,
key_prefix: "str | None" = None,
eagle_base: bool = False,
eagle_draft_dir: "str | None" = None,
reduced_vocab_dir: "str | None" = None,
mtp_base: bool = False,
mtp_tree_base: bool = False,
mtp_draft: bool = False,
tp_size: int = 1,
tp_rank: int = 0,
dflash_base: bool = False,
dflash_tree_base: bool = False,
dflash_draft: bool = False,
dflash_draft_dir: "str | None" = None,
dspark_base: bool = False,
dspark_draft: bool = False,
dspark_draft_dir: "str | None" = None,
gemma4_mtp_base: bool = False,
gemma4_mtp_draft: bool = False,
gemma4_kv_sharing_map: "list[dict] | None" = None,
gemma4_target_kv_cache_quant: "str | None" = None,
num_decoder_layers: "int | None" = None,
extra_configs: "dict | None" = None) -> nn.Module:
"""Construct and load a model from *model_dir*.
Reads ``config.json`` via :class:`~config.ModelConfig`, looks up the
model class in the registry (falling back to the built-in
:class:`~models.default.modeling_default.CausalLM`), instantiates it,
moves it to *device*, and loads safetensors weights.
Args:
model_dir: Local HF checkpoint directory.
device: Target device (e.g. ``"cpu"``, ``"cuda:0"``).
key_remap: Optional callable ``(key: str) -> Optional[str]``.
Passed through to :func:`load_weights` for checkpoint
key remapping (e.g. TTS talker ``codec_embedding``
→ ``embed_tokens``).
key_prefix: Explicit checkpoint key prefix to strip (e.g.
``"talker."``). Passed through to :func:`load_weights`.
eagle_base: When True, export as EAGLE3 base model with extra
tree-attention inputs and hidden_states output.
eagle_draft_dir:
Optional EAGLE3 draft checkpoint directory. Gemma4
EAGLE3 uses it to select the draft-trained target
hidden layers for base hidden_states output.
reduced_vocab_dir:
Optional directory containing ``vocab_map.safetensors``.
mtp_base: When True, export the standard Qwen3.5 text model as
the MTP base variant.
mtp_draft: When True, build the dedicated Qwen3.5 dense MTP
draft model from the base checkpoint config.
tp_size: Tensor-parallel world size. When >1 the config
is reduced to per-rank shapes via
:meth:`ModelConfig.for_rank`, and weights
are sharded on assignment. Default 1 = no TP.
tp_rank: This rank's index in [0, tp_size).
mtp_tree_base: When True, MTP base export adds DDTree parent/depth
metadata inputs for Qwen3.5 hybrid state execution
(MTP tree drafting).
dflash_base: When True, export as DFlash base model.
dflash_tree_base:
When True, add DDTree parent/depth metadata inputs
for Qwen3.5 hybrid state execution.
dflash_draft: When True, build the DFlash draft model.
dflash_draft_dir:
Path to the DFlash draft checkpoint directory.
dspark_base: When True, export as DSpark base model.
dspark_draft: When True, build the DSpark draft backbone model.
dspark_draft_dir:
Path to the DSpark draft checkpoint directory.
gemma4_mtp_base:
Export a Gemma4 target checkpoint as the base
engine for paired Gemma4 MTP.
gemma4_mtp_draft:
Export/load a paired Gemma4 assistant checkpoint.
gemma4_kv_sharing_map:
Validated assistant-layer to target-layer map for
Gemma4 MTP draft runtime config.
gemma4_target_kv_cache_quant:
Target/base KV-cache quantization mode inherited by
Gemma4 MTP draft inputs that alias target KV cache.
num_decoder_layers:
When set, truncate the model to only the first N
decoder layers (few-layer numeric validation).
Only supported for the plain default ``CausalLM``
path (e.g. Qwen3); rejected for eagle/mtp/dflash/dspark
and registered non-default variants. The checkpoint's
extra-layer weights are simply skipped by the loader.
Returns:
Loaded ``nn.Module`` in eval mode.
"""
from .models.default.modeling_default import CausalLM
config = load_model_config(model_dir)
if extra_configs:
for key, value in extra_configs.items():
setattr(config, key, value)
# Qwen3-Omni Next ships both dense and sparse-MoE thinkers under the
# same ``qwen3_omni_next_text`` model_type (the HF config is not
# rewritten for the MoE variant). Detect MoE by ``num_experts > 0`` and
# re-dispatch to the registered ``_moe`` class variant when one exists.
if (getattr(config, "num_experts", 0)
and f"{config.model_type}_moe" in _MODEL_REGISTRY):
config.model_type = f"{config.model_type}_moe"
if eagle_base:
config.eagle_base = True
if eagle_draft_dir:
import json
draft_cfg_path = os.path.join(eagle_draft_dir, "config.json")
if os.path.isfile(draft_cfg_path):
with open(draft_cfg_path) as f:
draft_cfg = json.load(f)
config.eagle3_target_layer_ids = list(
draft_cfg.get("target_layer_ids", []) or [])
if mtp_base or config.mtp_base:
config.mtp_base = True
if gemma4_mtp_base:
config.gemma4_mtp_base = True
if mtp_tree_base:
config.mtp_base = True
config.mtp_tree_base = True
elif config.mtp_tree_base:
config.mtp_base = True
if dflash_base:
config.dflash_base = True
if dflash_tree_base:
config.dflash_base = True
config.dflash_tree_base = True
elif config.dflash_tree_base:
config.dflash_base = True
if config.dflash_base:
# Read target_layer_ids from DFlash draft checkpoint if provided
if not config.dflash_target_layer_ids and dflash_draft_dir:
import json
draft_cfg_path = os.path.join(dflash_draft_dir, "config.json")
if os.path.isfile(draft_cfg_path):
with open(draft_cfg_path) as f:
draft_cfg = json.load(f)
dflash_cfg = draft_cfg.get("dflash_config", {}) or {}
config.dflash_target_layer_ids = dflash_cfg.get(
"target_layer_ids",
draft_cfg.get("target_layer_ids", [1, 8, 15, 22, 29]))
config.dflash_block_size = int(
dflash_cfg.get("block_size",
draft_cfg.get("block_size", 16)))
default_mask_token_id = (4 if str(
draft_cfg.get("model_type", "")).startswith("gemma4")
else 248070)
config.dflash_mask_token_id = int(
dflash_cfg.get(
"mask_token_id",
draft_cfg.get("mask_token_id",
default_mask_token_id)))
if not config.dflash_target_layer_ids:
config.dflash_target_layer_ids = [1, 8, 15, 22, 29]
if dspark_base:
config.dspark_base = True
if not config.dspark_target_layer_ids and dspark_draft_dir:
import json
draft_cfg_path = os.path.join(dspark_draft_dir, "config.json")
if os.path.isfile(draft_cfg_path):
with open(draft_cfg_path) as f:
draft_cfg = json.load(f)
dspark_cfg = draft_cfg.get("dspark_config", {})
config.dspark_target_layer_ids = (
dspark_cfg.get("target_layer_ids")
or draft_cfg.get("target_layer_ids", []))
config.dspark_block_size = int(
dspark_cfg.get("block_size",
draft_cfg.get("block_size", 7)))
default_mask_token_id = (4 if str(
draft_cfg.get("model_type", "")).startswith("gemma4")
else 151669)
config.dspark_mask_token_id = int(
dspark_cfg.get(
"mask_token_id",
draft_cfg.get("mask_token_id",
default_mask_token_id)))
config.dspark_enable_confidence_head = bool(
dspark_cfg.get(
"enable_confidence_head",
draft_cfg.get("enable_confidence_head", False)))
config.dspark_confidence_head_with_markov = bool(
dspark_cfg.get(
"confidence_head_with_markov",
draft_cfg.get("confidence_head_with_markov",
False)))
config.dspark_markov_head_type = str(
dspark_cfg.get("markov_head_type",
draft_cfg.get("markov_head_type", "")))
config.dspark_markov_rank = int(
dspark_cfg.get("markov_rank",
draft_cfg.get("markov_rank", 0)))
if not config.dspark_target_layer_ids:
raise ValueError(
"dspark_base requires DSpark target_layer_ids; pass "
"dspark_draft_dir or set dspark_target_layer_ids.")
if tp_size > 1:
config = config.for_rank(tp_rank, tp_size)
variant = _resolve_model_variant(config,
eagle_base=eagle_base,
mtp_base=config.mtp_base,
mtp_draft=mtp_draft,
dflash_base=config.dflash_base,
dflash_draft=dflash_draft,
dspark_base=config.dspark_base,
dspark_draft=dspark_draft,
gemma4_mtp_base=gemma4_mtp_base,
gemma4_mtp_draft=gemma4_mtp_draft)
# EAGLE3 draft: auto-detect from draft_vocab_size
if variant == "eagle3_draft":
from .models.eagle3.modeling_eagle3_draft import Eagle3DraftModel
model_class = Eagle3DraftModel
# Set up key remapping: midlayer -> layers.0, skip t2d
if key_remap is None:
key_remap = _eagle3_key_remap
elif variant == "mtp_draft":
is_nemotron_h_mtp = config.is_nemotron_h
# TODO: support other model types
if not (is_nemotron_h_mtp
or _is_qwen3_5_mtp_draft_supported(config.model_type)):
raise NotImplementedError(
"MTP draft is only supported for qwen3_5_text / "
"qwen3_5_moe_text / qwen3_omni_next_text_moe / "
f"Nemotron-H checkpoints; got {config.model_type!r}.")
draft_model_type = config.model_type
tie_word_embeddings = config.tie_word_embeddings
config = make_mtp_draft_config(config)
if is_nemotron_h_mtp:
from .models.nemotron_h import NemotronHMtpDraftModel
model_class = NemotronHMtpDraftModel
elif draft_model_type == "qwen3_omni_next_text_moe":
from .models.qwen3_omni_next import \
Qwen3OmniNextMoeMtpDraftModel
model_class = Qwen3OmniNextMoeMtpDraftModel
elif draft_model_type == "qwen3_5_moe_text":
from .models.qwen3_5_moe import Qwen3_5MoeMtpDraftModel
model_class = Qwen3_5MoeMtpDraftModel
else:
from .models.qwen3_5 import Qwen3_5MtpDraftModel
model_class = Qwen3_5MtpDraftModel
if key_remap is None:
# Only borrow the base model's lm_head when the checkpoint
# ships no dedicated ``mtp.lm_head.*`` tensors.
from .checkpoint.loader import iter_checkpoint_keys
share_base_lm_head = not any(
key.startswith("mtp.lm_head.")
for key in iter_checkpoint_keys(model_dir))
def key_remap(key):
return _mtp_key_remap(
key,
tie_word_embeddings=tie_word_embeddings,
share_base_lm_head=share_base_lm_head)
elif variant == "dflash_draft":
if dflash_draft_dir is None:
raise ValueError(
"dflash_draft requires dflash_draft_dir to be set.")
from .models.dflash.modeling_dflash_draft import DFlashDraftModel
base_config = config
base_model_dir = model_dir
base_tie_word_embeddings = base_config.tie_word_embeddings
draft_has_lm_head = _checkpoint_has_dflash_lm_head(
dflash_draft_dir)
config = make_dflash_draft_config(
dflash_draft_dir,
_default_attention_scale_for_model_dir(dflash_draft_dir))
if base_config.model_type == "nemotron_h":
# Nemotron-3.5 target-hidden stays far inside FP16; run fc at the
# checkpoint's native NVFP4 rather than the dense-FP16 + FP32
# projection that guards Qwen3-8B (target-hidden ~abs 2e4).
config.dflash_fc_native_precision = True
config.quant.excluded = [
e for e in config.quant.excluded if e != "fc"
]
if not draft_has_lm_head:
config = _inherit_dflash_lm_head_quant(config, base_config)
model_class = DFlashDraftModel
model_dir = dflash_draft_dir
if key_remap is None:
key_remap = _dflash_key_remap
elif variant == "gemma4_mtp_draft":
if not _is_gemma4_assistant_model_type(config.root_model_type):
raise ValueError(
"Gemma4 MTP draft requires a Gemma4 assistant checkpoint.")
from .models.gemma4 import Gemma4AssistantForCausalLM
config.gemma4_mtp_draft = True
config.shares_target_kv = True
config.has_own_kv_cache = False
config.constant_draft_positions = True
config.returns_feedback_hidden = True
config.assistant_hidden_size = config.hidden_size
config.kv_sharing_map = list(gemma4_kv_sharing_map or [])
if gemma4_target_kv_cache_quant is not None:
config.quant.kv_cache_quant = gemma4_target_kv_cache_quant
model_class = Gemma4AssistantForCausalLM
elif variant == "dspark_draft":
if dspark_draft_dir is None:
raise ValueError(
"dspark_draft requires dspark_draft_dir to be set.")
from .models.dspark.modeling_dspark_draft import DSparkDraftModel
config = make_dspark_draft_config(
dspark_draft_dir,
_default_attention_scale_for_model_dir(dspark_draft_dir))
model_class = DSparkDraftModel
model_dir = dspark_draft_dir
if key_remap is None:
key_remap = _dspark_key_remap
else:
if (variant == "mtp_base" and not config.is_nemotron_h
and not _is_qwen3_5_mtp_base_supported(config.model_type)):
raise NotImplementedError(
"MTP base is only supported for Qwen3.5 (text/MoE) and "
f"Nemotron-H checkpoints; got {config.model_type!r}.")
if variant == "gemma4_mtp_base":
if config.model_type not in ("gemma4", "gemma4_text",
"gemma4_unified",
"gemma4_unified_text"):
raise ValueError(
"Gemma4 MTP base requires a Gemma4 target checkpoint.")
from .models.gemma4 import Gemma4ForCausalLM
config.gemma4_mtp_base = True
model_class = Gemma4ForCausalLM
else:
# DFlash base is supported for both Qwen3.5 hybrid (qwen3_5_text) and
# dense Qwen3 (default CausalLM). Dense models use the Transformer's
# dflash_target_layer_ids parameter to collect target-layer hidden states.
model_class = _MODEL_REGISTRY.get(config.model_type, CausalLM)
# 4-layer numeric validation: truncate to the first N decoder
# layers. The whole pipeline is config-driven (the Transformer builds
# layers from ``config.num_hidden_layers`` / ``config.layer_types``,
# ``onnx_export_spec`` derives the KV / recurrent / conv I/O counts from
# them, the runtime config.json is written from ``model.config``, and the
# loader silently skips checkpoint keys for the dropped layers), so a
# single config override is sufficient and the modeling code needs no
# change. This covers the plain default ``CausalLM`` path (e.g. Qwen3)
# AND registered hybrid base models (Qwen3.5 linear+full / Gated DeltaNet,
# Nemotron-H Mamba), which also build per-layer from ``layer_types``. The
# eagle/mtp/dflash/gemma4-mtp speculative-decoding variants have a
# different per-layer structure and remain out of scope.
if num_decoder_layers is not None:
if (eagle_base or config.eagle_base or mtp_base or config.mtp_base
or dflash_base or config.dflash_base or dspark_base
or config.dspark_base or mtp_draft or dflash_draft
or dspark_draft or config.is_dspark_draft
or gemma4_mtp_base or config.gemma4_mtp_base
or gemma4_mtp_draft or config.gemma4_mtp_draft):
raise NotImplementedError(
"num_decoder_layers cannot be combined with the "
"eagle/mtp/dflash/dspark/gemma4-mtp speculative-decoding variants."
)
if not 1 <= num_decoder_layers <= config.num_hidden_layers:
raise ValueError(
f"num_decoder_layers={num_decoder_layers} out of range "
f"[1, {config.num_hidden_layers}].")
config.num_hidden_layers = num_decoder_layers
# Keep per-layer lists consistent with the truncated layer count.
if config.layer_types:
config.layer_types = config.layer_types[:num_decoder_layers]
logging.getLogger(__name__).info(
"num_decoder_layers: truncated to first %d decoder layers",
num_decoder_layers)
model = model_class(config)
model.to(device)
pre_repack_hook = None
apply_reduced_vocab_after_load = False
if reduced_vocab_dir is not None:
from .vocab_reduction.onnx_export import (
apply_reduced_vocab, load_reduced_vocab_map,
should_apply_reduced_vocab_before_repacking)
vocab_map = load_reduced_vocab_map(reduced_vocab_dir,
vocab_size=config.vocab_size,
device=device)
if should_apply_reduced_vocab_before_repacking(model):
def _apply_pre_repack_reduced_vocab(loaded_model: nn.Module):
apply_reduced_vocab(loaded_model, vocab_map)
loaded_model._reduced_vocab_dir = reduced_vocab_dir
pre_repack_hook = _apply_pre_repack_reduced_vocab
else:
apply_reduced_vocab_after_load = True
if variant == "dflash_draft" and not draft_has_lm_head:
next_pre_repack_hook = pre_repack_hook
def _load_pre_repack_dflash_lm_head(loaded_model: nn.Module):
_load_dflash_lm_head(
loaded_model,
base_model_dir,
device,
tie_word_embeddings=base_tie_word_embeddings)
if next_pre_repack_hook is not None:
next_pre_repack_hook(loaded_model)
pre_repack_hook = _load_pre_repack_dflash_lm_head
load_weights(model,
model_dir,
device=device,
key_remap=key_remap,
key_prefix=key_prefix,
pre_repack_hook=pre_repack_hook,
mapping=config.mapping)
refresh_router_bias = getattr(model, "refresh_fp32_router_bias", None)
if callable(refresh_router_bias):
refresh_router_bias()
if variant == "dflash_draft":
if draft_has_lm_head:
logging.getLogger(__name__).info(
"DFlash lm_head source: draft checkpoint buffers")
if apply_reduced_vocab_after_load:
from .vocab_reduction.onnx_export import \
apply_reduced_vocab_from_dir
apply_reduced_vocab_from_dir(model, reduced_vocab_dir)
# Post-load optimisation: fuse GDN input projections for Qwen3.5 / Qwen3.5-MoE.
if (config.model_type in ("qwen3_5_text", "qwen3_5_moe_text")
and not mtp_draft):
from .models.qwen3_5 import fuse_gdn_input_projections
fuse_gdn_input_projections(model)
# Fuse attention Q/K/V projections into one packed-QKV GEMM where eligible.
from .models.default.modeling_default import fuse_qkv_projections
fuse_qkv_projections(model)
return model
def param_count(model: nn.Module) -> int:
"""Return total parameter element count (trainable and frozen)."""
return sum(p.numel() for p in model.parameters())
def dtype_summary(model: nn.Module) -> Dict[str, int]:
"""Map dtype name -> number of parameter elements."""
out: Dict[str, int] = {}
for p in model.parameters():
name = str(p.dtype).replace("torch.", "")
out[name] = out.get(name, 0) + p.numel()
return dict(sorted(out.items(), key=lambda x: -x[1]))
def _inherit_dflash_lm_head_quant(draft_config: ModelConfig,
base_config: ModelConfig) -> ModelConfig:
"""Make an old DFlash draft build a loadable shared lm_head.
Older DFlash drafts may omit ``lm_head.*`` and reuse the base output head.
When that head is quantized, mirror its layout only for the draft ``lm_head``
so the fallback loader can copy the base sidecar tensors.
"""
lm_head_quant = module_quant_type("lm_head", base_config)
if lm_head_quant == QUANT_FP16:
return draft_config
draft_quant = draft_config.quant
base_quant = base_config.quant
use_base_group_size = lm_head_quant in _GROUP_SIZE_LM_HEAD_QUANTS
if (use_base_group_size and draft_quant.quant_type != QUANT_FP16
and draft_quant.group_size != base_quant.group_size):
raise ValueError(
"DFlash draft cannot share %s base lm_head with a different "
"draft quantization group size." % lm_head_quant)
layer_overrides = dict(draft_quant.layer_overrides)
layer_overrides["lm_head"] = lm_head_quant
excluded = [name for name in draft_quant.excluded if name != "lm_head"]
quant_updates = {
"excluded": excluded,
"layer_overrides": layer_overrides,
}
if use_base_group_size:
quant_updates["group_size"] = base_quant.group_size
if lm_head_quant == QUANT_INT4_GPTQ:
quant_updates[
"gptq_zero_point_offset"] = base_quant.gptq_zero_point_offset
quant = dataclasses.replace(draft_quant, **quant_updates)
return dataclasses.replace(draft_config, quant=quant)
# ---------------------------------------------------------------------------
# EAGLE3 helpers
# ---------------------------------------------------------------------------
def _resolve_model_variant(config: ModelConfig,
*,
eagle_base: bool,
mtp_base: bool,
mtp_draft: bool,
dflash_base: bool = False,
dflash_draft: bool = False,
dspark_base: bool = False,
dspark_draft: bool = False,
gemma4_mtp_base: bool = False,
gemma4_mtp_draft: bool = False) -> str:
"""Resolve the requested model variant while keeping EAGLE3 behavior intact."""
if eagle_base and mtp_base:
raise ValueError("eagle_base and mtp_base cannot both be enabled.")
if eagle_base and mtp_draft:
raise ValueError("eagle_base and mtp_draft cannot both be enabled.")
if mtp_base and mtp_draft:
raise ValueError("mtp_base and mtp_draft cannot both be enabled.")
if dflash_base and dflash_draft:
raise ValueError(
"dflash_base and dflash_draft cannot both be enabled.")
if dspark_base and dspark_draft:
raise ValueError(
"dspark_base and dspark_draft cannot both be enabled.")
if dflash_base and (eagle_base or mtp_base or mtp_draft or dspark_base or
dspark_draft or gemma4_mtp_base or gemma4_mtp_draft):
raise ValueError(
"dflash_base cannot be combined with other spec variants.")
if dflash_draft and (eagle_base or mtp_base or mtp_draft or dspark_base or
dspark_draft or gemma4_mtp_base or gemma4_mtp_draft):
raise ValueError(
"dflash_draft cannot be combined with other spec variants.")
if dspark_base and (eagle_base or mtp_base or mtp_draft or dflash_base or
dflash_draft or gemma4_mtp_base or gemma4_mtp_draft):
raise ValueError(
"dspark_base cannot be combined with other spec variants.")
if dspark_draft and (eagle_base or mtp_base or mtp_draft or dflash_base or
dflash_draft or gemma4_mtp_base or gemma4_mtp_draft):
raise ValueError(
"dspark_draft cannot be combined with other spec variants.")
if gemma4_mtp_base and (eagle_base or mtp_base or mtp_draft or dflash_base
or dflash_draft or dspark_base or dspark_draft):
raise ValueError(
"gemma4_mtp_base cannot be combined with other speculative variants."
)
if gemma4_mtp_draft and (eagle_base or mtp_base or mtp_draft or dflash_base
or dflash_draft or dspark_base or dspark_draft):
raise ValueError(
"gemma4_mtp_draft cannot be combined with other speculative variants."
)
if gemma4_mtp_base and gemma4_mtp_draft:
raise ValueError(
"gemma4_mtp_base and gemma4_mtp_draft cannot both be enabled.")
if config.is_eagle3_draft:
if mtp_base or mtp_draft:
raise ValueError(
"EAGLE3 draft checkpoints cannot be loaded as Qwen3.5 MTP variants."
)
return "eagle3_draft"
if gemma4_mtp_draft:
return "gemma4_mtp_draft"
if dflash_draft:
return "dflash_draft"
if dflash_base:
return "dflash_base"
if dspark_draft:
return "dspark_draft"
if dspark_base:
return "dspark_base"
if mtp_draft:
return "mtp_draft"
if mtp_base:
return "mtp_base"
if gemma4_mtp_base:
return "gemma4_mtp_base"
if eagle_base:
return "eagle_base"
return "llm"
def _eagle3_key_remap(key: str) -> "str | None":
"""Remap EAGLE3 draft checkpoint keys.
Handles all known EAGLE3 draft checkpoint variations:
- ``t2d`` keys are skipped (but ``d2t`` is kept).
- ``target_model.*`` keys are skipped (multi-target training artifact).
- ``midlayer.*`` -> ``layers.0.*``
- ``qkv_proj.{q,k,v}_proj`` -> ``{q,k,v}_proj`` (flatten old pipeline
``EdgeLLMAttention`` wrapper nesting, used by quantized checkpoints).
- ``._pre_quant_scale`` -> ``.pre_quant_scale`` (modelopt internal naming;
normally stripped by ``postprocess_state_dict()`` but not by per-module
export via ``_export_quantized_weight()``).
"""
if "t2d" in key and "d2t" not in key:
return None # skip t2d
if key.startswith("target_model."):
return None # skip multi-target training artifact
key = key.replace("midlayer.", "layers.0.")
key = key.replace("qkv_proj.q_proj", "q_proj")
key = key.replace("qkv_proj.k_proj", "k_proj")
key = key.replace("qkv_proj.v_proj", "v_proj")
key = key.replace("._pre_quant_scale", ".pre_quant_scale")
return key
def _load_dflash_lm_head(model: nn.Module,
base_model_dir: str,
device: str,
*,
tie_word_embeddings: bool = True) -> None:
"""Load the DFlash draft lm_head from the base model checkpoint.
Deterministic source selection (matching MTP pattern):
1. Explicit ``lm_head.weight`` from the base checkpoint.
2. Embedding fallback *only* when ``tie_word_embeddings=True``.
3. Otherwise fail loudly — untied models must not use embeddings.
This helper is used only for old DFlash draft checkpoints that do not carry
``lm_head.*`` tensors. Quantized checkpoints, and dense checkpoints that
explicitly save ``lm_head.weight``, are loaded by the generic checkpoint
loader and must not be overwritten from the base embedding table.
"""
import logging
import os
import torch
from .models.linear import FP16Linear
logger = logging.getLogger(__name__)
lm_head = getattr(model, "lm_head", None)
if lm_head is None:
logger.warning("DFlash draft model has no lm_head; skipping.")
return
from .checkpoint.loader import _build_shard_map
shard_map = _build_shard_map(base_model_dir)
if not isinstance(lm_head, FP16Linear):
_load_dflash_quantized_lm_head(model, lm_head, shard_map,
base_model_dir, device)
return
# --- Determine source key with strict priority ---
lm_head_candidates = [
"lm_head.weight",
"model.lm_head.weight",
"language_model.lm_head.weight",
"model.language_model.lm_head.weight",
]
embed_candidates = [
"model.embed_tokens.weight",
"embed_tokens.weight",
"model.language_model.embed_tokens.weight",
"language_model.model.embed_tokens.weight",
]
# Priority 1: explicit lm_head.weight from base checkpoint
source_key = None
source_type = None
for cand in lm_head_candidates:
if cand in shard_map:
source_key = cand
source_type = "lm_head"
break
# Priority 2: embedding fallback only if tie_word_embeddings
if source_key is None:
if tie_word_embeddings:
for cand in embed_candidates:
if cand in shard_map:
source_key = cand
source_type = "tied_embedding"
break
else:
raise ValueError(
"DFlash lm_head: base model at %s has "
"tie_word_embeddings=False but no lm_head.weight found. "
"Cannot safely fall back to embed_tokens." % base_model_dir)
if source_key is None:
raise ValueError(
"Cannot find lm_head.weight or embed_tokens.weight in "
"base model at %s." % base_model_dir)
shard_path = shard_map[source_key]
if source_type == "tied_embedding":
logger.info("DFlash lm_head source: %s (tied fallback) from %s",
source_key, os.path.basename(shard_path))
else:
logger.info("DFlash lm_head source: %s from %s", source_key,
os.path.basename(shard_path))
source_weight = _read_dflash_checkpoint_tensor(shard_path, source_key,
device)
# --- Copy weight into model's dense lm_head ---
if source_weight.shape != lm_head.weight.shape:
raise ValueError(
f"DFlash lm_head shape mismatch: source={source_weight.shape} "
f"vs lm_head={lm_head.weight.shape}")
with torch.no_grad():
lm_head.weight.copy_(source_weight.to(lm_head.weight.dtype))
def _load_dflash_quantized_lm_head(model: nn.Module, lm_head: nn.Module,
shard_map: Dict[str, str],
base_model_dir: str, device: str) -> None:
"""Load quantized lm_head sidecar tensors shared from the base checkpoint."""
import logging
import os
from .checkpoint.loader import _set_tensor
logger = logging.getLogger(__name__)
source_prefixes = (
"lm_head",
"model.lm_head",
"language_model.lm_head",
"model.language_model.lm_head",
)
target_state = lm_head.state_dict()
missing = []
source_by_target = {}
optional_tensors = {"g_idx", "int4_act_perm", "pre_quant_scale"}
for target_name in target_state:
source_key = next((f"{prefix}.{target_name}"
for prefix in source_prefixes
if f"{prefix}.{target_name}" in shard_map), None)
if source_key is None:
if target_name not in optional_tensors:
missing.append(target_name)
continue
source_by_target[target_name] = source_key
if missing:
raise ValueError(
"DFlash quantized lm_head fallback requires base checkpoint tensors "
"for %s; missing %s in %s." % (", ".join(
target_state.keys()), ", ".join(missing), base_model_dir))
loaded_tensors = []
for target_name, source_key in source_by_target.items():
target_tensor = target_state[target_name]
source_tensor = _read_dflash_checkpoint_tensor(shard_map[source_key],
source_key, device)
source_tensor = _normalize_dflash_lm_head_tensor_shape(
target_name, source_tensor, target_tensor)
loaded_tensors.append((target_name, source_key, source_tensor))
loaded = []
for target_name, source_key, source_tensor in loaded_tensors:
if not _set_tensor(model,
f"lm_head.{target_name}",
source_tensor,
mapping=model.config.mapping):
raise ValueError(
f"Failed to assign DFlash lm_head tensor {target_name!r}.")
loaded.append(source_key)
logger.info(
"DFlash lm_head source: quantized base checkpoint buffers from %s",
", ".join(sorted({os.path.basename(shard_map[key])
for key in loaded})))
def _read_dflash_checkpoint_tensor(shard_path: str, key: str, device: str):
import torch
from safetensors import safe_open
if shard_path.endswith(".bin"):
state = torch.load(shard_path, map_location=device, weights_only=True)
return state[key]
with safe_open(shard_path, framework="pt", device=device) as f:
return f.get_tensor(key)
def _normalize_dflash_lm_head_tensor_shape(target_name: str, source, target):
if source.shape == target.shape:
return source
if source.numel() == 1 and target.numel() == 1:
return source.reshape(target.shape)
raise ValueError(
"DFlash quantized lm_head shape mismatch for %s: source=%s vs "
"lm_head=%s" % (target_name, source.shape, target.shape))
def _checkpoint_has_dflash_lm_head(model_dir: str) -> bool:
"""Return whether a DFlash draft checkpoint owns lm_head tensors."""
from .checkpoint.loader import _build_shard_map
for key in _build_shard_map(model_dir):
mapped = _dflash_key_remap(key)
if mapped is not None and mapped.startswith("lm_head."):
return True
return False
def _dflash_key_remap(key: str) -> "str | None":
"""Remap DFlash draft checkpoint keys."""
if "rotary_emb" in key:
return None
return key
def _dspark_key_remap(key: str) -> "str | None":
"""Remap DSpark draft checkpoint keys for the backbone engine.
Markov and confidence tensors are exported as sidecars rather than baked
into the backbone engine, so the generic loader may skip them.
"""
if "rotary_emb" in key:
return None
return key
def _mtp_key_remap(key: str,
*,
tie_word_embeddings: bool,
share_base_lm_head: bool = False) -> "str | None":
"""Remap MTP checkpoint keys for the draft model.
``share_base_lm_head`` sources the draft head from the base model
(``lm_head.*`` tensors, or the embedding table when tied); enable it only
when the checkpoint has no ``mtp.lm_head.*`` of its own.
"""
if key.startswith("mtp."):
return key[len("mtp."):]
if share_base_lm_head and key.startswith("lm_head."):
return key
if share_base_lm_head and tie_word_embeddings and key in (
"model.embed_tokens.weight",
"model.language_model.embed_tokens.weight"):
return "lm_head.weight"
return None