Skip to content

vllm.models.hy_v4

HY V4 (hy_v4) model — hardware-isolated entry point.

HY V4 combines three architectural pieces:

  • iHC (independent Hyper-Connections): the single residual stream is replaced by hc_mult parallel residual channels, gated per sub-block.
  • MLA + lightning indexer: multi-head latent attention with an optional DSA-style sparse top-k selection, plus an output gate and a learnable sink.
  • MoE: sigmoid-routed experts with a clamped SwiGLU and shared experts.

The package is organized like vllm.models.deepseek_v32: this module is the only public entry point and dispatches on the current platform, so registry entries never reach into a platform subpackage.

Only NVIDIA is supported for now. The port also drops the reference implementation's HPC/TPCP fusion paths, which depend on infrastructure that does not exist in this tree.

Modules:

Classes:

  • HYV4MTP

    HY V4 MTP draft head.

HYV4MTP

Bases: Module

HY V4 MTP draft head.

Not a pipeline-parallel stage: the draft head always runs on a single rank, matching DeepseekV32MTP / KimiK3MTP / HYV3MTP.

Methods:

Source code in vllm/models/hy_v4/nvidia/mtp.py
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
class HYV4MTP(nn.Module):
    """HY V4 MTP draft head.

    Not a pipeline-parallel stage: the draft head always runs on a single rank,
    matching `DeepseekV32MTP` / `KimiK3MTP` / `HYV3MTP`.
    """

    packed_modules_mapping = {
        "gate_up_proj": ["gate_proj", "up_proj"],
        # MLA runs both latent down-projections as one GEMM.
        "fused_qkv_a_proj": ["q_a_proj", "kv_a_proj_with_mqa"],
        # The indexer fuses wk and weights_proj into one GEMM.
        "wk_weights_proj": ["wk", "weights_proj"],
    }

    def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
        super().__init__()
        self.config = vllm_config.model_config.hf_config
        self.model = HYV4MultiTokenPredictor(
            vllm_config=vllm_config, prefix=maybe_prefix(prefix, "model")
        )
        self.quant_config = self.model.quant_config
        self.sampler = Sampler()

    def set_topk_indices_buffer(self, topk_indices_buffer: torch.Tensor) -> None:
        """Share the target sparse-index buffer with every draft consumer.

        Proposers that walk ``named_modules()`` instead of calling this reach
        the same consumers via ``HYV4MLAAttention.topk_indices_buffer``.
        """
        self.model.topk_indices_buffer = topk_indices_buffer
        for layer in self.model.layers.values():
            self_attn = layer.mtp_block.self_attn
            if not self_attn.is_sparse:
                continue

            indexer = self_attn.indexer
            assert indexer is not None, "Sparse HYV4 MTP attention requires an indexer"
            indexer.topk_indices_buffer = topk_indices_buffer
            indexer.indexer_op.topk_indices_buffer = topk_indices_buffer

            attn_impl = self_attn.mla_attn.impl
            assert hasattr(attn_impl, "topk_indices_buffer"), (
                "Sparse HYV4 MTP attention backend requires a top-k indices buffer"
            )
            attn_impl.topk_indices_buffer = topk_indices_buffer

    def forward(
        self,
        input_ids: torch.Tensor,
        positions: torch.Tensor,
        hidden_states: torch.Tensor,
        intermediate_tensors: IntermediateTensors | None = None,
        inputs_embeds: torch.Tensor | None = None,
        spec_step_idx: int = 0,
    ) -> torch.Tensor:
        del intermediate_tensors  # the MTP head is single-stage
        if (
            self.model.requires_topk_indices_buffer
            and self.model.topk_indices_buffer is None
        ):
            raise RuntimeError(
                "HYV4 sparse MTP requires the target model's top-k indices buffer. "
                "The proposer must call HYV4MTP.set_topk_indices_buffer() before "
                "the first draft forward."
            )
        self.model.spec_step_idx = spec_step_idx
        return self.model(input_ids, positions, hidden_states, inputs_embeds)

    def compute_logits(
        self,
        hidden_states: torch.Tensor,
        spec_step_idx: int = 0,
    ) -> torch.Tensor | None:
        self.model.spec_step_idx = spec_step_idx
        return self.model.compute_logits(hidden_states)

    def sample(
        self,
        logits: torch.Tensor,
        sampling_metadata: SamplingMetadata,
    ) -> SamplerOutput | None:
        return self.sampler(logits, sampling_metadata)

    def _rewrite_spec_layer_name(self, spec_layer: int, name: str) -> str:
        if f"model.layers.{spec_layer}.embed_tokens" in name:
            return "__skip__"
        if f"model.layers.{spec_layer}.shared_head" in name:
            return "__skip__"

        spec_layer_weight_names = ["enorm", "hnorm", "eh_proj", "final_layernorm"]
        spec_layer_weight = any(
            weight_name in name for weight_name in spec_layer_weight_names
        )
        if not spec_layer_weight:
            name = name.replace(
                f"model.layers.{spec_layer}.",
                f"model.layers.{spec_layer}.mtp_block.",
            )
        return name

    def _load_fused_expert_weights(
        self,
        name: str,
        params_dict: dict,
        loaded_weight: torch.Tensor,
        shard_id: str,
        num_experts: int,
    ) -> bool:
        if name not in params_dict:
            return False
        param = params_dict[name]
        weight_loader = typing.cast(Callable[..., bool], param.weight_loader)
        loaded_local_expert = False
        for expert_id in range(num_experts):
            curr_expert_weight = loaded_weight[expert_id]
            success = weight_loader(
                param,
                curr_expert_weight,
                name,
                shard_id,
                expert_id,
                return_success=True,
            )
            if success:
                loaded_local_expert = True
        return loaded_local_expert

    def _load_expert_weight(
        self,
        name: str,
        loaded_weight: torch.Tensor,
        params_dict: dict,
        loaded_params: set[str],
        split_expert_params_mapping: list[tuple[str, str, int, str]],
        fused_expert_param_names: dict[tuple[str, str], str],
        num_experts: int,
    ) -> bool:
        """Load one routed-expert weight in either checkpoint layout.

        Args:
            name: Weight name already rewritten to draft-module naming.
            loaded_weight: The checkpoint tensor.
            params_dict: The draft model's named parameters.
            loaded_params: Set updated with the parameters that received a value.
            split_expert_params_mapping: Mapping for the per-expert layout.
            fused_expert_param_names: ``(mlp_prefix, tag) -> param name`` for the
                all-experts-packed layout.
            num_experts: Total number of routed experts.

        Returns:
            True when the weight was consumed (even if this rank holds none of
            the addressed experts).
        """
        base = name.split(".experts.")[0]
        for ckpt_proj, tag in (
            (".experts.gate_up_proj", "w13_weight"),
            (".experts.down_proj", "w2_weight"),
        ):
            if ckpt_proj not in name:
                continue
            param_base = fused_expert_param_names.get((base, tag))
            if param_base is None:
                return False
            # Keep the checkpoint suffix (e.g. `_scale_inv`) so block-scale
            # tensors land in the scale parameter, not in the weight.
            target = _resolve_fused_expert_param(
                param_base, name.split(ckpt_proj, 1)[1], params_dict
            )
            if target is None:
                return False
            if tag == "w13_weight":
                chunks = loaded_weight.chunk(2, dim=-2)
                loaded_w1 = self._load_fused_expert_weights(
                    target, params_dict, chunks[0], "w1", num_experts
                )
                loaded_w3 = self._load_fused_expert_weights(
                    target, params_dict, chunks[1], "w3", num_experts
                )
                loaded = loaded_w1 and loaded_w3
            else:
                loaded = self._load_fused_expert_weights(
                    target, params_dict, loaded_weight, "w2", num_experts
                )
            if loaded:
                loaded_params.add(target)
            # The weight belongs to the experts either way; never fall through.
            return True

        consumed = False
        for param_name, weight_name, expert_id, shard_id in split_expert_params_mapping:
            if weight_name not in name:
                continue
            consumed = True
            name_mapped = name.replace(weight_name, param_name)
            if name_mapped not in params_dict:
                continue
            param = params_dict[name_mapped]
            weight_loader = typing.cast(Callable[..., bool], param.weight_loader)
            if weight_loader(
                param,
                loaded_weight,
                name_mapped,
                shard_id=shard_id,
                expert_id=expert_id,
                return_success=True,
            ):
                loaded_params.add(name_mapped)
        return consumed

    def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
        params_dict = dict(self.named_parameters())
        pp_missing_layer_names = get_pp_missing_layer_names(self)
        loaded_params: set[str] = set()

        mtp_start = self.config.num_hidden_layers
        shared_weights = {
            "model.embed_tokens.weight": "model.embed_tokens.weight",
            "lm_head.weight": f"model.layers.{mtp_start}.shared_head.head.weight",
        }

        num_experts = getattr(self.config, "n_routed_experts", 0)
        sink_tp_size = get_tensor_model_parallel_world_size()
        sink_tp_rank = get_tensor_model_parallel_rank()
        n_local_head = self.config.num_attention_heads // sink_tp_size
        head_rank_start = n_local_head * sink_tp_rank
        head_rank_end = n_local_head * (sink_tp_rank + 1)

        # Routed-expert weights come in two checkpoint layouts:
        #   split: mlp.experts.<id>.gate_proj.weight
        #   fused: mlp.experts.gate_up_proj (all experts in one tensor)
        # The split layout is resolved through the shared EPLB helper so the
        # target param names stay in sync with the RoutedExperts module layout;
        # the fused layout is resolved from the live parameter names.
        split_expert_params_mapping = fused_moe_make_expert_params_mapping(
            self,
            ckpt_gate_proj_name="gate_proj",
            ckpt_down_proj_name="down_proj",
            ckpt_up_proj_name="up_proj",
            num_experts=num_experts,
        )
        fused_expert_param_names: dict[tuple[str, str], str] = {}
        for param_name in params_dict:
            for tag in ("w13_weight", "w2_weight"):
                if param_name.endswith(tag) and ".experts." in param_name:
                    base = param_name.split(".experts.")[0]
                    fused_expert_param_names[base, tag] = param_name
        stacked_mapping = [
            (".gate_up_proj", ".gate_proj", 0),
            (".gate_up_proj", ".up_proj", 1),
            # MLA runs both latent down-projections as one GEMM.
            (".fused_qkv_a_proj", ".q_a_proj", 0),
            (".fused_qkv_a_proj", ".kv_a_proj_with_mqa", 1),
        ]
        # Sparse DSA draft blocks build an Indexer whose wk / weights_proj are
        # fused into a single MergedColumnParallelLinear (wk_weights_proj).
        indexer_stacked_mapping = [
            (".wk_weights_proj", ".wk", 0),
            (".wk_weights_proj", ".weights_proj", 1),
        ]
        # FP8 indexer wk dequant buffer (weight and scale arrive separately).
        pending_wk_fp8: dict[str, dict[str, torch.Tensor]] = {}

        for name, loaded_weight in weights:
            if name in shared_weights:
                target_name = shared_weights[name]
                if target_name in params_dict:
                    param = params_dict[target_name]
                    weight_loader = getattr(
                        param, "weight_loader", default_weight_loader
                    )
                    weight_loader(param, loaded_weight)
                    loaded_params.add(target_name)
                continue

            spec_layer = None
            if name.startswith("model.mtp_layers."):
                parts = name.split(".")
                if len(parts) > 3 and parts[2].isdigit():
                    spec_layer = mtp_start + int(parts[2])
                    name = name.replace(
                        f"model.mtp_layers.{parts[2]}.",
                        f"model.layers.{spec_layer}.",
                    )
            else:
                spec_layer = _get_spec_layer_idx_from_weight_name(self.config, name)

            if spec_layer is None:
                continue

            name = self._rewrite_spec_layer_name(spec_layer, name)
            if name == "__skip__":
                continue
            name, loaded_weight = _prepare_mtp_fp8_expert_scale(
                self.quant_config, name, loaded_weight
            )

            if "mlp.gate.e_score_correction_bias" in name:
                name = name.replace("gate.e_score_correction_bias", "expert_bias")

            is_loaded = False
            for param_name, weight_name, shard_id in stacked_mapping:
                if weight_name not in name or ".experts." in name:
                    continue
                name_mapped = name.replace(weight_name, param_name)
                if name_mapped not in params_dict:
                    if is_pp_missing_parameter(name_mapped, self):
                        is_loaded = True
                    break
                param = params_dict[name_mapped]
                param.weight_loader(param, loaded_weight, shard_id)
                loaded_params.add(name_mapped)
                is_loaded = True
                break
            if is_loaded:
                continue

            # FP8 indexer wk: dequantize to BF16 and load into the fused
            # wk_weights_proj. PP-aware (skips layers not held by this rank).
            if _try_load_fp8_indexer_wk(
                name,
                loaded_weight,
                pending_wk_fp8,
                params_dict,
                loaded_params,
                pp_missing_layer_names,
            ):
                continue

            # BF16 indexer wk / weights_proj: merge into the fused param.
            is_loaded = False
            for param_name, weight_name, shard_id in indexer_stacked_mapping:
                if weight_name not in name or "wk_weights" in name:
                    continue
                name_mapped = name.replace(weight_name, param_name)
                if name_mapped not in params_dict:
                    if is_pp_missing_parameter(name_mapped, self):
                        is_loaded = True
                    break
                param = params_dict[name_mapped]
                param.weight_loader(param, loaded_weight, shard_id)
                loaded_params.add(name_mapped)
                is_loaded = True
                break
            if is_loaded:
                continue

            is_loaded = False
            if ".experts." in name:
                is_loaded = self._load_expert_weight(
                    name,
                    loaded_weight,
                    params_dict,
                    loaded_params,
                    split_expert_params_mapping,
                    fused_expert_param_names,
                    num_experts,
                )
            if is_loaded:
                continue

            if "learnable_sink_param" in name:
                if name in params_dict:
                    narrow_weight = loaded_weight[head_rank_start:head_rank_end]
                    n = narrow_weight.shape[0]
                    with torch.no_grad():
                        params_dict[name][:n].copy_(narrow_weight)
                    loaded_params.add(name)
                continue

            remapped_name = maybe_remap_kv_scale_name(name, params_dict)
            if remapped_name is None:
                continue
            name = remapped_name
            if name not in params_dict:
                if is_pp_missing_parameter(name, self):
                    continue
                if _should_skip_missing_mtp_scale_param(self.quant_config, name):
                    continue
                logger.warning_once("Skipping unknown MTP weight: %s", name)
                continue

            param = params_dict[name]
            weight_loader = getattr(param, "weight_loader", default_weight_loader)
            weight_loader(param, loaded_weight)
            loaded_params.add(name)

        logger.info_once("HYV4 MTP draft model loaded: %d params", len(loaded_params))
        unassigned_all = sorted(set(params_dict) - loaded_params)
        # KVCacheScaleParameter (``k_scale`` / ``v_scale`` / ``q_scale`` /
        # ``prob_scale``) is created by ``BaseKVCacheMethod.create_weights`` for
        # every Attention layer that runs on an fp8-family quant method, and
        # its -1.0 sentinel is replaced by the runtime default (1.0) in
        # ``process_weights_after_loading`` -- which runs *after* this method.
        # HY V4 checkpoints intentionally omit these (``kv_cache_quant_algo:
        # null``); silence them only when the parameter is actually the
        # sentinel type so a genuine mismatch on a same-named tensor still
        # reaches the warning.
        from vllm.model_executor.layers.quantization.kv_cache import (
            KVCacheScaleParameter,
        )

        unassigned = [
            name
            for name in unassigned_all
            if not isinstance(params_dict.get(name), KVCacheScaleParameter)
        ]
        if unassigned:
            # A draft parameter with no checkpoint source keeps its sentinel
            # init value (FP8 scales start at finfo(float32).min), which
            # silently destroys draft quality instead of failing the load.
            logger.warning(
                "HYV4 MTP draft model: %d parameters received no checkpoint value: %s",
                len(unassigned),
                ", ".join(unassigned),
            )
        return loaded_params

_load_expert_weight(name, loaded_weight, params_dict, loaded_params, split_expert_params_mapping, fused_expert_param_names, num_experts)

Load one routed-expert weight in either checkpoint layout.

Parameters:

  • name

    (str) –

    Weight name already rewritten to draft-module naming.

  • loaded_weight

    (Tensor) –

    The checkpoint tensor.

  • params_dict

    (dict) –

    The draft model's named parameters.

  • loaded_params

    (set[str]) –

    Set updated with the parameters that received a value.

  • split_expert_params_mapping

    (list[tuple[str, str, int, str]]) –

    Mapping for the per-expert layout.

  • fused_expert_param_names

    (dict[tuple[str, str], str]) –

    (mlp_prefix, tag) -> param name for the all-experts-packed layout.

  • num_experts

    (int) –

    Total number of routed experts.

Returns:

  • bool

    True when the weight was consumed (even if this rank holds none of

  • bool

    the addressed experts).

Source code in vllm/models/hy_v4/nvidia/mtp.py
def _load_expert_weight(
    self,
    name: str,
    loaded_weight: torch.Tensor,
    params_dict: dict,
    loaded_params: set[str],
    split_expert_params_mapping: list[tuple[str, str, int, str]],
    fused_expert_param_names: dict[tuple[str, str], str],
    num_experts: int,
) -> bool:
    """Load one routed-expert weight in either checkpoint layout.

    Args:
        name: Weight name already rewritten to draft-module naming.
        loaded_weight: The checkpoint tensor.
        params_dict: The draft model's named parameters.
        loaded_params: Set updated with the parameters that received a value.
        split_expert_params_mapping: Mapping for the per-expert layout.
        fused_expert_param_names: ``(mlp_prefix, tag) -> param name`` for the
            all-experts-packed layout.
        num_experts: Total number of routed experts.

    Returns:
        True when the weight was consumed (even if this rank holds none of
        the addressed experts).
    """
    base = name.split(".experts.")[0]
    for ckpt_proj, tag in (
        (".experts.gate_up_proj", "w13_weight"),
        (".experts.down_proj", "w2_weight"),
    ):
        if ckpt_proj not in name:
            continue
        param_base = fused_expert_param_names.get((base, tag))
        if param_base is None:
            return False
        # Keep the checkpoint suffix (e.g. `_scale_inv`) so block-scale
        # tensors land in the scale parameter, not in the weight.
        target = _resolve_fused_expert_param(
            param_base, name.split(ckpt_proj, 1)[1], params_dict
        )
        if target is None:
            return False
        if tag == "w13_weight":
            chunks = loaded_weight.chunk(2, dim=-2)
            loaded_w1 = self._load_fused_expert_weights(
                target, params_dict, chunks[0], "w1", num_experts
            )
            loaded_w3 = self._load_fused_expert_weights(
                target, params_dict, chunks[1], "w3", num_experts
            )
            loaded = loaded_w1 and loaded_w3
        else:
            loaded = self._load_fused_expert_weights(
                target, params_dict, loaded_weight, "w2", num_experts
            )
        if loaded:
            loaded_params.add(target)
        # The weight belongs to the experts either way; never fall through.
        return True

    consumed = False
    for param_name, weight_name, expert_id, shard_id in split_expert_params_mapping:
        if weight_name not in name:
            continue
        consumed = True
        name_mapped = name.replace(weight_name, param_name)
        if name_mapped not in params_dict:
            continue
        param = params_dict[name_mapped]
        weight_loader = typing.cast(Callable[..., bool], param.weight_loader)
        if weight_loader(
            param,
            loaded_weight,
            name_mapped,
            shard_id=shard_id,
            expert_id=expert_id,
            return_success=True,
        ):
            loaded_params.add(name_mapped)
    return consumed

set_topk_indices_buffer(topk_indices_buffer)

Share the target sparse-index buffer with every draft consumer.

Proposers that walk named_modules() instead of calling this reach the same consumers via HYV4MLAAttention.topk_indices_buffer.

Source code in vllm/models/hy_v4/nvidia/mtp.py
def set_topk_indices_buffer(self, topk_indices_buffer: torch.Tensor) -> None:
    """Share the target sparse-index buffer with every draft consumer.

    Proposers that walk ``named_modules()`` instead of calling this reach
    the same consumers via ``HYV4MLAAttention.topk_indices_buffer``.
    """
    self.model.topk_indices_buffer = topk_indices_buffer
    for layer in self.model.layers.values():
        self_attn = layer.mtp_block.self_attn
        if not self_attn.is_sparse:
            continue

        indexer = self_attn.indexer
        assert indexer is not None, "Sparse HYV4 MTP attention requires an indexer"
        indexer.topk_indices_buffer = topk_indices_buffer
        indexer.indexer_op.topk_indices_buffer = topk_indices_buffer

        attn_impl = self_attn.mla_attn.impl
        assert hasattr(attn_impl, "topk_indices_buffer"), (
            "Sparse HYV4 MTP attention backend requires a top-k indices buffer"
        )
        attn_impl.topk_indices_buffer = topk_indices_buffer