Skip to content

Commit e71c34d

Browse files
committed
fix: normalize Qwen3.5 MTP hidden with producer weights
1 parent 6c9e03b commit e71c34d

8 files changed

Lines changed: 144 additions & 10 deletions

File tree

‎lightllm/common/basemodel/basemodel.py‎

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -686,7 +686,9 @@ def prefill_func(input_tensors, _infer_state):
686686

687687
post_output = self.post_infer.token_forward(last_input_embs, infer_state, self.pre_post_weight)
688688
hidden_collector = infer_state.hidden_collector
689-
hidden_collector.add_final_hidden(last_input_embs)
689+
hidden_collector.add_final_hidden(
690+
last_input_embs if post_output.final_hidden is None else post_output.final_hidden
691+
)
690692
model_output = self._create_model_output(post_output, infer_state)
691693
del post_output
692694

@@ -712,7 +714,9 @@ def _token_forward(self, infer_state: InferStateInfo):
712714
last_input_embs, infer_state=infer_state, layer_weight=self.pre_post_weight
713715
)
714716

715-
hidden_collector.add_final_hidden(last_input_embs)
717+
hidden_collector.add_final_hidden(
718+
last_input_embs if post_output.final_hidden is None else post_output.final_hidden
719+
)
716720
model_output = self._create_model_output(post_output, infer_state)
717721
del post_output
718722

@@ -915,8 +919,12 @@ def _overlap_tpsp_context_forward(self, infer_state: InferStateInfo, infer_state
915919
)
916920
g_cache_manager.cache_env_out()
917921

918-
hidden_collector0.add_final_hidden(last_input_embs)
919-
hidden_collector1.add_final_hidden(last_input_embs1)
922+
hidden_collector0.add_final_hidden(
923+
last_input_embs if post_output.final_hidden is None else post_output.final_hidden
924+
)
925+
hidden_collector1.add_final_hidden(
926+
last_input_embs1 if post_output1.final_hidden is None else post_output1.final_hidden
927+
)
920928
model_output = self._create_model_output(post_output, infer_state)
921929
model_output1 = self._create_model_output(post_output1, infer_state1)
922930
del post_output, post_output1
@@ -957,8 +965,12 @@ def _overlap_tpsp_token_forward(self, infer_state: InferStateInfo, infer_state1:
957965
last_input_embs, last_input_embs1, infer_state, infer_state1, self.pre_post_weight
958966
)
959967

960-
hidden_collector0.add_final_hidden(last_input_embs)
961-
hidden_collector1.add_final_hidden(last_input_embs1)
968+
hidden_collector0.add_final_hidden(
969+
last_input_embs if post_output.final_hidden is None else post_output.final_hidden
970+
)
971+
hidden_collector1.add_final_hidden(
972+
last_input_embs1 if post_output1.final_hidden is None else post_output1.final_hidden
973+
)
962974
model_output = self._create_model_output(post_output, infer_state)
963975
model_output1 = self._create_model_output(post_output1, infer_state1)
964976
del post_output, post_output1

‎lightllm/common/basemodel/batch_objs.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,10 +175,11 @@ def unpad_prefill(self, origin_handle_token_num: int) -> "ModelMtpOutputCollecto
175175

176176
@dataclass
177177
class PostLayerOutput:
178-
"""输出层 logits,以及可选的候选列到 token ID 的映射。"""
178+
"""输出层 logits、可选的候选 token ID 映射和供 collector 收集的最终 hidden。"""
179179

180180
logits: torch.Tensor
181181
logits_token_ids: Optional[torch.Tensor] = None
182+
final_hidden: Optional[torch.Tensor] = None
182183

183184

184185
@dataclass
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from lightllm.models.llama.layer_infer.post_layer_infer import LlamaPostLayerInfer
2+
3+
4+
class Qwen35PostLayerInfer(LlamaPostLayerInfer):
5+
def token_forward(self, input_embdings, infer_state, layer_weight):
6+
normed = self._norm(input_embdings, infer_state, layer_weight)
7+
output = super().token_forward(normed, infer_state, layer_weight)
8+
output.final_hidden = normed
9+
return output
10+
11+
def _project_local_logits(self, hidden, token_num, layer_weight, infer_state):
12+
hidden = hidden.permute(1, 0).view(-1, token_num)
13+
return layer_weight.lm_head_weight_(input=hidden, alloc_func=self.alloc_tensor)

‎lightllm/models/qwen3_5/model.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
Qwen35TransformerLayerInfer,
1616
)
1717
from lightllm.models.qwen3_5.infer_struct import Qwen35InferStateInfo
18+
from lightllm.models.qwen3_5.layer_infer.post_layer_infer import Qwen35PostLayerInfer
1819
from lightllm.common.build_utils import repair_config
1920
from lightllm.utils.log_utils import init_logger
2021

@@ -53,6 +54,7 @@ class Qwen3_5TpPartModel(Qwen3NextTpPartModel):
5354
pre_and_post_weight_class = Qwen35PreAndPostLayerWeight
5455

5556
pre_layer_infer_class = Qwen3VLMultimodalPreLayerInfer
57+
post_layer_infer_class = Qwen35PostLayerInfer
5658
transformer_layer_infer_class = Qwen35TransformerLayerInfer
5759

5860
infer_state_class = Qwen35InferStateInfo

‎lightllm/models/qwen3_5_mtp/layer_infer/pre_layer_infer.py‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ def _mtp_fuse(
2323
input_embdings.shape[0] == tgt_embdings.shape[0]
2424
), f"shape {input_embdings.shape} != shape {tgt_embdings.shape}"
2525

26-
layer_weight.main_norm_weight_(input=tgt_embdings, eps=self.eps_, out=tgt_embdings)
2726
layer_weight.enorm_weight_(input=input_embdings, eps=self.eps_, out=input_embdings)
2827
layer_weight.hnorm_weight_(input=tgt_embdings, eps=self.eps_, out=tgt_embdings)
2928
cat_embdings = torch.cat((input_embdings, tgt_embdings), dim=-1)

‎lightllm/models/qwen3_5_mtp/layer_weights/pre_and_post_layer_weight.py‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,4 @@ def __init__(self, data_type, network_config, quant_cfg: Quantcfg):
4242
# Shared with the main Qwen3.5 model, injected by the model class (not loaded here).
4343
self.wte_weight_: EmbeddingWeight = None
4444
self.lm_head_weight_: LMHeadWeight = None
45-
self.main_norm_weight_: NoTpGEMMANormWeight = None
4645
return

‎lightllm/models/qwen3_5_mtp/model.py‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@ def _init_weights(self, start_layer_index=None):
6969
# Shared with the main Qwen3.5 model (mtp_use_dedicated_embeddings: false).
7070
self.pre_post_weight.wte_weight_ = self.main_model.pre_post_weight.wte_weight_
7171
self.pre_post_weight.lm_head_weight_ = self.main_model.pre_post_weight.lm_head_weight_
72-
self.pre_post_weight.main_norm_weight_ = self.main_model.pre_post_weight.final_norm_weight_
7372
return
7473

7574
def _init_infer_layer(self, start_layer_index=None):
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
from types import SimpleNamespace
2+
3+
import torch
4+
5+
from lightllm.common.basemodel.basemodel import TpPartBaseModel
6+
from lightllm.common.basemodel.hidden_collector import FinalHiddenCollector
7+
from lightllm.common.basemodel.layer_weights.meta_weights import NoTpGEMMANormWeight
8+
from lightllm.models.llama.layer_infer import post_layer_infer as llama_post
9+
from lightllm.models.qwen3_5.layer_infer.post_layer_infer import Qwen35PostLayerInfer
10+
from lightllm.models.qwen3_5_mtp.layer_infer.pre_layer_infer import Qwen3_5MTPPreLayerInfer
11+
12+
13+
def test_qwen35_main_to_draft_two_steps(monkeypatch) -> None:
14+
def norm_weight(values):
15+
norm = NoTpGEMMANormWeight.__new__(NoTpGEMMANormWeight)
16+
norm.dim = 3
17+
norm.data_type_ = torch.float32
18+
norm.weight_name = "norm.weight"
19+
norm.weight = torch.empty(3)
20+
norm.load_hf_weights({"norm.weight": torch.tensor(values)})
21+
norm._forward = norm._native_forward
22+
return norm
23+
24+
main_norm = norm_weight([0.5, -0.25, 1.0])
25+
draft_norm = norm_weight([-0.5, 1.0, 0.25])
26+
enorm = norm_weight([0.0, 0.25, -0.25])
27+
hnorm = norm_weight([0.25, -0.5, 0.5])
28+
projection = torch.arange(1, 16, dtype=torch.float32).reshape(5, 3) / 10
29+
fusion = torch.tensor(
30+
[[1.0, 0.0, 0.0, 0.0, 1.0, 0.0], [0.0, 1.0, 0.0, 0.0, 0.0, 1.0], [0.0, 0.0, 1.0, 1.0, 0.0, 0.0]]
31+
)
32+
33+
class LMHead:
34+
vocab_size = 5
35+
36+
def __call__(self, input, alloc_func):
37+
return projection @ input
38+
39+
main_weight = SimpleNamespace(final_norm_weight_=main_norm, lm_head_weight_=LMHead())
40+
draft_weight = SimpleNamespace(
41+
final_norm_weight_=draft_norm,
42+
lm_head_weight_=LMHead(),
43+
enorm_weight_=enorm,
44+
hnorm_weight_=hnorm,
45+
eh_proj_weight_=SimpleNamespace(mm=lambda hidden: hidden @ fusion.T),
46+
)
47+
post = Qwen35PostLayerInfer.__new__(Qwen35PostLayerInfer)
48+
post.eps_ = 1e-6
49+
post.tp_world_size_ = 1
50+
post.alloc_tensor = lambda shape, dtype, **kwargs: torch.empty(shape, dtype=dtype)
51+
pre = Qwen3_5MTPPreLayerInfer.__new__(Qwen3_5MTPPreLayerInfer)
52+
pre.eps_ = post.eps_
53+
model = TpPartBaseModel.__new__(TpPartBaseModel)
54+
model.layers_num = 0
55+
model.post_infer = post
56+
post._tpsp_allgather = lambda input, infer_state: input
57+
58+
def forward(hidden, weight):
59+
model.pre_post_weight = weight
60+
model.pre_infer = SimpleNamespace(
61+
token_forward=lambda *args: hidden,
62+
_tpsp_sp_split=lambda input, infer_state: input,
63+
)
64+
return model._token_forward(state)
65+
66+
state = SimpleNamespace(
67+
is_prefill=False,
68+
is_cuda_graph=False,
69+
input_ids=torch.ones(2, dtype=torch.long),
70+
is_draft_model=False,
71+
batch_size=2,
72+
prompt_logics=None,
73+
hidden_collector=FinalHiddenCollector(),
74+
)
75+
monkeypatch.setattr(
76+
llama_post,
77+
"get_env_start_args",
78+
lambda: SimpleNamespace(target_vocab_topk_sampling=None, draft_vocab_topk_sampling=None),
79+
)
80+
81+
def reference_norm(hidden, weight):
82+
return torch.nn.functional.rms_norm(hidden, (3,), weight.weight, eps=post.eps_)
83+
84+
hidden = torch.tensor([[1.0, 2.0, -3.0], [4.0, -2.0, 1.0]])
85+
expected = reference_norm(hidden, main_norm)
86+
output = forward(hidden, main_weight)
87+
torch.testing.assert_close(output.mtp_collector.spec_hidden, expected)
88+
torch.testing.assert_close(output.logits, expected @ projection.T)
89+
assert state.hidden_collector.final_hidden is None
90+
91+
state.is_draft_model = True
92+
for step in range(2):
93+
embedding = torch.tensor([[2.0, -1.0, 3.0], [-3.0, 2.0, 1.0]]) + step
94+
state.mtp_draft_input_hiddens = output.mtp_collector.spec_hidden
95+
expected_fused = (
96+
torch.cat(
97+
(reference_norm(embedding, enorm), reference_norm(expected, hnorm)),
98+
dim=-1,
99+
)
100+
@ fusion.T
101+
)
102+
fused = pre._mtp_fuse(embedding, state, draft_weight)
103+
torch.testing.assert_close(fused, expected_fused)
104+
expected = reference_norm(expected_fused, draft_norm)
105+
output = forward(fused, draft_weight)
106+
torch.testing.assert_close(output.mtp_collector.spec_hidden, expected)
107+
torch.testing.assert_close(output.logits, expected @ projection.T)
108+
assert state.hidden_collector.final_hidden is None
109+
assert not torch.allclose(expected, reference_norm(expected_fused, main_norm))

0 commit comments

Comments
 (0)