| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | |
| 4 | from types import SimpleNamespace |
| 5 | |
| 6 | import pytest |
| 7 | import torch |
| 8 | |
| 9 | from vllm.models.deepseek_v4.amd import dspark as dspark_module |
| 10 | from vllm.models.deepseek_v4.amd.dspark import DSparkDeepseekV4ForCausalLM |
| 11 | |
| 12 | |
| 13 | def _make_uninitialized_model(confidence_head): |
| 14 | model = DSparkDeepseekV4ForCausalLM.__new__(DSparkDeepseekV4ForCausalLM) |
| 15 | object.__setattr__( |
| 16 | model, |
| 17 | "model", |
| 18 | SimpleNamespace(confidence_head=confidence_head), |
| 19 | ) |
| 20 | return model |
| 21 | |
| 22 | |
| 23 | def _prepare_loader_model(model, named_parameters): |
| 24 | object.__setattr__( |
| 25 | model, |
| 26 | "config", |
| 27 | SimpleNamespace( |
| 28 | n_routed_experts=1, |
| 29 | expert_dtype="fp4", |
| 30 | num_attention_heads=1, |
| 31 | ), |
| 32 | ) |
| 33 | object.__setattr__(model, "named_parameters", lambda: named_parameters) |
| 34 | |
| 35 | |
| 36 | def _disable_distributed_loader_paths(monkeypatch: pytest.MonkeyPatch): |
| 37 | monkeypatch.setattr( |
| 38 | dspark_module, |
| 39 | "fused_moe_make_expert_params_mapping", |
| 40 | lambda *args, **kwargs: [], |
| 41 | ) |
| 42 | monkeypatch.setattr( |
| 43 | dspark_module, "get_tensor_model_parallel_world_size", lambda: 1 |
| 44 | ) |
| 45 | monkeypatch.setattr(dspark_module, "get_tensor_model_parallel_rank", lambda: 0) |
| 46 | |
| 47 | |
| 48 | @pytest.mark.cpu_test |
| 49 | def test_deepseek_v4_rocm_dspark_maps_enabled_confidence_head(): |
| 50 | model = _make_uninitialized_model(object()) |
| 51 | |
| 52 | assert ( |
| 53 | model._remap_dspark_name("mtp.2.confidence_head.proj.weight") |
| 54 | == "model.confidence_head.proj.weight" |
| 55 | ) |
| 56 | |
| 57 | |
| 58 | @pytest.mark.cpu_test |
| 59 | def test_deepseek_v4_rocm_dspark_skips_disabled_confidence_head(): |
| 60 | model = _make_uninitialized_model(None) |
| 61 | |
| 62 | assert model._remap_dspark_name("mtp.2.confidence_head.proj.weight") is None |
| 63 | |
| 64 | |
| 65 | @pytest.mark.cpu_test |
| 66 | def test_deepseek_v4_rocm_dspark_disables_unloaded_confidence_head( |
| 67 | monkeypatch: pytest.MonkeyPatch, |
| 68 | ): |
| 69 | model = _make_uninitialized_model(object()) |
| 70 | _prepare_loader_model(model, []) |
| 71 | _disable_distributed_loader_paths(monkeypatch) |
| 72 | |
| 73 | assert model.load_weights([]) == set() |
| 74 | assert model.model.confidence_head is None |
| 75 | |
| 76 | |
| 77 | @pytest.mark.cpu_test |
| 78 | def test_deepseek_v4_rocm_dspark_loads_complete_confidence_head( |
| 79 | monkeypatch: pytest.MonkeyPatch, |
| 80 | ): |
| 81 | class FakeParameter: |
| 82 | loaded_weight = None |
| 83 | |
| 84 | def weight_loader(self, param, loaded_weight): |
| 85 | assert param is self |
| 86 | self.loaded_weight = loaded_weight |
| 87 | |
| 88 | confidence_head = object() |
| 89 | parameter = FakeParameter() |
| 90 | model = _make_uninitialized_model(confidence_head) |
| 91 | _prepare_loader_model( |
| 92 | model, |
| 93 | [("model.confidence_head.proj.weight", parameter)], |
| 94 | ) |
| 95 | _disable_distributed_loader_paths(monkeypatch) |
| 96 | loaded_weight = torch.tensor([[1.0, 2.0]]) |
| 97 | |
| 98 | loaded = model.load_weights([("mtp.2.confidence_head.proj.weight", loaded_weight)]) |
| 99 | |
| 100 | assert loaded == {"model.confidence_head.proj.weight"} |
| 101 | assert parameter.loaded_weight is loaded_weight |
| 102 | assert model.model.confidence_head is confidence_head |
| 103 | |
| 104 | |
| 105 | @pytest.mark.cpu_test |
| 106 | def test_deepseek_v4_rocm_dspark_confidence_is_probability(): |
| 107 | class ConfidenceHead: |
| 108 | def __call__(self, head_hidden, markov_embed): |
| 109 | return (head_hidden[:, 0] + markov_embed[:, 0]).float() |
| 110 | |
| 111 | model = _make_uninitialized_model(ConfidenceHead()) |
| 112 | head_hidden = torch.tensor([[0.0], [1.0]], dtype=torch.bfloat16) |
| 113 | markov_embed = torch.tensor([[0.0], [-2.0]], dtype=torch.bfloat16) |
| 114 | |
| 115 | confidence = model.compute_confidence(head_hidden, markov_embed) |
| 116 | |
| 117 | torch.testing.assert_close( |
| 118 | confidence, |
| 119 | torch.sigmoid(torch.tensor([0.0, -1.0])), |
| 120 | ) |
| 121 | assert torch.all((confidence >= 0) & (confidence <= 1)) |
| 122 | |
| 123 | |
| 124 | @pytest.mark.cpu_test |
| 125 | def test_deepseek_v4_rocm_dspark_confidence_requires_a_head(): |
| 126 | model = _make_uninitialized_model(None) |
| 127 | empty = torch.zeros((1, 1)) |
| 128 | |
| 129 | with pytest.raises(RuntimeError, match="confidence_head"): |
| 130 | model.compute_confidence(empty, empty) |