Revert "Remove native Hunyuan V1 and VL implementations" (#53296)
Signed-off-by: Kevin Luu <51931015+khluu@users.noreply.github.com> Co-authored-by: Andreas Karatzas <akaratza@amd.com>
Kevin H. Luu committed
Aug 21, 2026 at 13:39 UTC
e3f60265032e7c1e158a749d82a8c4dad62f941a
7 files changed
+2382
-9
docs/models/supported_models.md
+3
-3
@@ -409,6 +409,8 @@ th {
409
| `GraniteSWAForCausalLM` | Granite SWA | `ibm-granite/granite-swash-2b` | ✅︎ | ✅︎ |
410
| `GritLM` | GritLM | `parasail-ai/GritLM-7B-vllm`. | ✅︎ | ✅︎ |
411
| `HrmTextForCausalLM` | HRM-Text | `sapientinc/HRM-Text-1B`, etc. | | |
412
+| `HunYuanDenseV1ForCausalLM` | Hunyuan Dense | `tencent/Hunyuan-7B-Instruct` | ✅︎ | ✅︎ |
413
+| `HunYuanMoEV1ForCausalLM` | Hunyuan-A13B | `tencent/Hunyuan-A13B-Instruct`, `tencent/Hunyuan-A13B-Pretrain`, `tencent/Hunyuan-A13B-Instruct-FP8`, etc. | ✅︎ | ✅︎ |
414
| `HYV3ForCausalLM` | HY3 | `tencent/Hy3-preview-Base`, `tencent/Hy3-preview` | ✅︎ | ✅︎ |
415
| `HyperCLOVAXForCausalLM` | HyperCLOVAX-SEED-Think-14B | `naver-hyperclovax/HyperCLOVAX-SEED-Think-14B` | ✅︎ | ✅︎ |
416
| `InternLM2ForCausalLM` | InternLM2 | `internlm/internlm2-7b`, `internlm/internlm2-chat-7b`, etc. | ✅︎ | ✅︎ |
@@ -476,8 +478,6 @@ Some models are supported only via the [Transformers modeling backend](#transfor
478
| Architecture | Models | Example HF Models | [LoRA](../features/lora.md) | [PP](../serving/parallelism_scaling.md) |
479
| ------------ | ------ | ----------------- | -------------------- | ------------------------- |
480
| `GPTBigCodeForCausalLM` | StarCoder, SantaCoder, WizardCoder | `bigcode/starcoder`, `bigcode/gpt_bigcode-santacoder`, `WizardLM/WizardCoder-15B-V1.0`, etc. | ✅︎ | |
479
-| `HunYuanDenseV1ForCausalLM` | Hunyuan Dense | `tencent/Hunyuan-7B-Instruct` | ✅︎ | ✅︎ |
480
-| `HunYuanMoEV1ForCausalLM` | Hunyuan-A13B | `tencent/Hunyuan-A13B-Instruct`, `tencent/Hunyuan-A13B-Pretrain`, `tencent/Hunyuan-A13B-Instruct-FP8`, etc. | ✅︎ | ✅︎ |
481
| `OlmoForCausalLM` | OLMo | `allenai/OLMo-1B-hf`, `allenai/OLMo-7B-hf`, etc. | ✅︎ | ✅︎ |
482
| `Olmo2ForCausalLM` | OLMo2 | `allenai/OLMo-2-0425-1B`, etc. | ✅︎ | ✅︎ |
483
| `SmolLM3ForCausalLM` | SmolLM3 | `HuggingFaceTB/SmolLM3-3B` | ✅︎ | ✅︎ |
@@ -552,6 +552,7 @@ These models primarily accept the [`LLM.generate`](./generative_models.md#llmgen
552
| `HCXVisionForCausalLM` | HyperCLOVAX-SEED-Vision-Instruct-3B | T + I<sup>+</sup> + V<sup>+</sup> | `naver-hyperclovax/HyperCLOVAX-SEED-Vision-Instruct-3B` | | |
553
| `HCXVisionV2ForCausalLM` | HyperCLOVAX-SEED-Think-32B | T + I<sup>+</sup> + V<sup>+</sup> | `naver-hyperclovax/HyperCLOVAX-SEED-Think-32B` | | |
554
| `H2OVLChatModel` | H2OVL | T + I<sup>E+</sup> | `h2oai/h2ovl-mississippi-800m`, `h2oai/h2ovl-mississippi-2b`, etc. | ✅︎ | ✅︎ |
555
+| `HunYuanVLForConditionalGeneration` | HunyuanOCR | T + I<sup>E+</sup> | `tencent/HunyuanOCR`, etc. | ✅︎ | ✅︎ |
556
| `Idefics3ForConditionalGeneration` | Idefics3 | T + I | `HuggingFaceM4/Idefics3-8B-Llama3`, etc. | ✅︎ | |
557
| `IsaacForConditionalGeneration` | Isaac | T + I<sup>+</sup> | `PerceptronAI/Isaac-0.1` | ✅︎ | ✅︎ |
558
| `InternS1ForConditionalGeneration` | Intern-S1 | T + I<sup>E+</sup> + V<sup>E+</sup> | `internlm/Intern-S1`, `internlm/Intern-S1-mini`, etc. | ✅︎ | ✅︎ |
@@ -628,7 +629,6 @@ Some models are supported only via the [Transformers modeling backend](#transfor
629
| Architecture | Models | Inputs | Example HF Models | [LoRA](../features/lora.md) | [PP](../serving/parallelism_scaling.md) |
630
| ------------ | ------ | ------ | ----------------- | --------------------------- | --------------------------------------- |
631
| `Emu3ForConditionalGeneration` | Emu3 | T + I<sup>+</sup> | `BAAI/Emu3-Chat-hf` | ✅︎ | ✅︎ |
631
-| `HunYuanVLForConditionalGeneration` | HunyuanOCR | T + I<sup>E+</sup> | `tencent/HunyuanOCR`, etc. | ✅︎ | ✅︎ |
632
| `VibeVoiceAsrForConditionalGeneration` | VibeVoice-ASR | T + A<sup>+</sup> | `microsoft/VibeVoice-ASR-HF` | ✅︎ | ✅︎ |
633
634
<sup>^</sup> You need to set the architecture name via `--hf-overrides` to match the one in vLLM.</br>
vllm/model_executor/models/hunyuan_v1.py
new
+1038
@@ -0,0 +1,1038 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+
4
+# coding=utf-8
5
+# Copyright 2024 The HunYuan team.
6
+# Copyright 2023 The vLLM team.
7
+# Copyright 2022 EleutherAI and the HuggingFace Inc. team. All rights reserved.
8
+#
9
+# This code is based on EleutherAI's GPT-NeoX library and the GPT-NeoX
10
+# and OPT implementations in this library. It has been modified from its
11
+# original forms to accommodate minor architectural differences compared
12
+# to GPT-NeoX and OPT used by the Meta AI team that trained the model.
13
+#
14
+# Licensed under the Apache License, Version 2.0 (the "License");
15
+# you may not use this file except in compliance with the License.
16
+# You may obtain a copy of the License at
17
+#
18
+# http://www.apache.org/licenses/LICENSE-2.0
19
+#
20
+# Unless required by applicable law or agreed to in writing, software
21
+# distributed under the License is distributed on an "AS IS" BASIS,
22
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23
+# See the License for the specific language governing permissions and
24
+# limitations under the License.
25
+"""Inference-only HunYuan model compatible with HuggingFace weights."""
26
+
27
+import typing
28
+from collections.abc import Callable, Iterable
29
+from itertools import islice
30
+
31
+import regex as re
32
+import torch
33
+from torch import nn
34
+from transformers import PretrainedConfig
35
+
36
+from vllm.compilation.decorators import support_torch_compile
37
+from vllm.config import CacheConfig, VllmConfig, get_current_vllm_config
38
+from vllm.distributed import (
39
+ get_ep_group,
40
+ get_pp_group,
41
+ get_tensor_model_parallel_world_size,
42
+)
43
+from vllm.model_executor.layers.activation import SiluAndMul
44
+from vllm.model_executor.layers.attention import Attention
45
+from vllm.model_executor.layers.fused_moe import (
46
+ FusedMoEFactory,
47
+ fused_moe_make_expert_params_mapping,
48
+)
49
+from vllm.model_executor.layers.layernorm import RMSNorm
50
+from vllm.model_executor.layers.linear import (
51
+ ColumnParallelLinear,
52
+ MergedColumnParallelLinear,
53
+ QKVParallelLinear,
54
+ ReplicatedLinear,
55
+ RowParallelLinear,
56
+)
57
+from vllm.model_executor.layers.logits_processor import LogitsProcessor
58
+from vllm.model_executor.layers.quantization import QuantizationConfig
59
+from vllm.model_executor.layers.rotary_embedding import get_rope
60
+from vllm.model_executor.layers.vocab_parallel_embedding import (
61
+ ParallelLMHead,
62
+ VocabParallelEmbedding,
63
+)
64
+from vllm.model_executor.model_loader.weight_utils import (
65
+ default_weight_loader,
66
+ maybe_remap_kv_scale_name,
67
+)
68
+from vllm.sequence import IntermediateTensors
69
+from vllm.v1.attention.backend import AttentionType
70
+
71
+from .interfaces import (
72
+ EagleModelMixin,
73
+ MixtureOfExperts,
74
+ SupportsEagle,
75
+ SupportsEagle3,
76
+ SupportsLoRA,
77
+ SupportsPP,
78
+)
79
+from .utils import (
80
+ AutoWeightsLoader,
81
+ PPMissingLayer,
82
+ is_pp_missing_parameter,
83
+ make_layers,
84
+ maybe_prefix,
85
+)
86
+
87
+
88
+def _is_moe(config: PretrainedConfig) -> bool:
89
+ num_experts = getattr(config, "num_experts", None)
90
+ if isinstance(num_experts, int):
91
+ return num_experts > 1
92
+ if isinstance(num_experts, list) and num_experts:
93
+ # Ensure all elements are integers before calling max.
94
+ if all(isinstance(e, int) for e in num_experts):
95
+ return max(num_experts) > 1
96
+ else:
97
+ return False
98
+ return False
99
+
100
+
101
+def _get_cla_factor(config: PretrainedConfig) -> int:
102
+ if not getattr(config, "use_cla", False):
103
+ return 1
104
+ return getattr(config, "cla_share_factor", 1)
105
+
106
+
107
+class HunYuanMLP(nn.Module):
108
+ def __init__(
109
+ self,
110
+ hidden_size: int,
111
+ intermediate_size: int,
112
+ hidden_act: str,
113
+ quant_config: QuantizationConfig | None = None,
114
+ bias: bool = False,
115
+ prefix: str = "",
116
+ reduce_results: bool = True,
117
+ ) -> None:
118
+ super().__init__()
119
+ self.gate_up_proj = MergedColumnParallelLinear(
120
+ input_size=hidden_size,
121
+ output_sizes=[intermediate_size] * 2,
122
+ bias=bias,
123
+ quant_config=quant_config,
124
+ prefix=f"{prefix}.gate_up_proj",
125
+ )
126
+ self.down_proj = RowParallelLinear(
127
+ input_size=intermediate_size,
128
+ output_size=hidden_size,
129
+ bias=bias,
130
+ quant_config=quant_config,
131
+ prefix=f"{prefix}.down_proj",
132
+ reduce_results=reduce_results,
133
+ )
134
+ if hidden_act != "silu":
135
+ raise ValueError(
136
+ f"Unsupported activation: {hidden_act}. Only silu is supported for now."
137
+ )
138
+ self.act_fn = SiluAndMul()
139
+
140
+ def forward(self, x):
141
+ gate_up, _ = self.gate_up_proj(x)
142
+ x = self.act_fn(gate_up)
143
+ x, _ = self.down_proj(x)
144
+ return x
145
+
146
+
147
+class HunYuanAttention(nn.Module):
148
+ def __init__(
149
+ self,
150
+ config: PretrainedConfig,
151
+ hidden_size: int,
152
+ num_heads: int,
153
+ num_kv_heads: int,
154
+ max_position_embeddings: int = 8192,
155
+ quant_config: QuantizationConfig | None = None,
156
+ bias: bool = False,
157
+ cache_config: CacheConfig | None = None,
158
+ prefix: str = "",
159
+ layer_id: int = -1,
160
+ ) -> None:
161
+ super().__init__()
162
+ self.hidden_size = hidden_size
163
+ tp_size = get_tensor_model_parallel_world_size()
164
+ self.total_num_heads = num_heads
165
+ assert self.total_num_heads % tp_size == 0
166
+ self.num_heads = self.total_num_heads // tp_size
167
+ self.total_num_kv_heads = num_kv_heads
168
+ if self.total_num_kv_heads >= tp_size:
169
+ # Number of KV heads is greater than TP size, so we partition
170
+ # the KV heads across multiple tensor parallel GPUs.
171
+ assert self.total_num_kv_heads % tp_size == 0
172
+ else:
173
+ # Number of KV heads is less than TP size, so we replicate
174
+ # the KV heads across multiple tensor parallel GPUs.
175
+ assert tp_size % self.total_num_kv_heads == 0
176
+ self.num_kv_heads = max(1, self.total_num_kv_heads // tp_size)
177
+
178
+ if hasattr(config, "head_dim") and config.head_dim:
179
+ self.head_dim = config.head_dim
180
+ elif hasattr(config, "attention_head_dim"):
181
+ self.head_dim = config.attention_head_dim
182
+ else:
183
+ self.head_dim = self.hidden_size // self.total_num_heads
184
+ self.q_size = self.num_heads * self.head_dim
185
+ self.kv_size = self.num_kv_heads * self.head_dim
186
+ self.scaling = self.head_dim**-0.5
187
+ self.max_position_embeddings = max_position_embeddings
188
+ self.use_qk_norm = getattr(config, "use_qk_norm", False)
189
+ self.layer_id = layer_id
190
+
191
+ self.qkv_proj = QKVParallelLinear(
192
+ hidden_size=hidden_size,
193
+ head_size=self.head_dim,
194
+ total_num_heads=self.total_num_heads,
195
+ total_num_kv_heads=self.total_num_kv_heads,
196
+ bias=bias,
197
+ quant_config=quant_config,
198
+ prefix=f"{prefix}.qkv_proj",
199
+ )
200
+
201
+ self.o_proj = RowParallelLinear(
202
+ input_size=self.total_num_heads * self.head_dim,
203
+ output_size=hidden_size,
204
+ bias=bias,
205
+ quant_config=quant_config,
206
+ prefix=f"{prefix}.o_proj",
207
+ )
208
+
209
+ self.rotary_emb = get_rope(
210
+ self.head_dim,
211
+ max_position=max_position_embeddings,
212
+ rope_parameters=config.rope_parameters,
213
+ is_neox_style=True,
214
+ )
215
+ self.attn = Attention(
216
+ self.num_heads,
217
+ self.head_dim,
218
+ self.scaling,
219
+ num_kv_heads=self.num_kv_heads,
220
+ cache_config=cache_config,
221
+ quant_config=quant_config,
222
+ prefix=f"{prefix}.attn",
223
+ )
224
+
225
+ if self.use_qk_norm:
226
+ self.query_layernorm = RMSNorm(self.head_dim, eps=config.rms_norm_eps)
227
+ self.key_layernorm = RMSNorm(self.head_dim, eps=config.rms_norm_eps)
228
+
229
+ def forward(
230
+ self,
231
+ positions: torch.Tensor,
232
+ hidden_states: torch.Tensor,
233
+ kv_states: tuple[torch.Tensor] | None = None,
234
+ ) -> torch.Tensor:
235
+ qkv, _ = self.qkv_proj(hidden_states)
236
+ q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1)
237
+ q, k = self.rotary_emb(positions, q, k)
238
+ ori_k = k
239
+ if self.use_qk_norm:
240
+ q = self.query_layernorm(
241
+ q.view(-1, self.num_heads, self.head_dim),
242
+ )
243
+ k = self.key_layernorm(
244
+ k.view(-1, self.num_kv_heads, self.head_dim),
245
+ )
246
+
247
+ attn_output = self.attn(q, k, v)
248
+ # For o_proj
249
+ attn_output = attn_output.view(q.shape[0], -1)
250
+ output, _ = self.o_proj(attn_output)
251
+ return output, (ori_k, v)
252
+
253
+
254
+class HunYuanCrossAttention(nn.Module):
255
+ def __init__(
256
+ self,
257
+ config: PretrainedConfig,
258
+ hidden_size: int,
259
+ num_heads: int,
260
+ num_kv_heads: int,
261
+ max_position_embeddings: int = 8192,
262
+ quant_config: QuantizationConfig | None = None,
263
+ bias: bool = False,
264
+ cache_config: CacheConfig | None = None,
265
+ prefix: str = "",
266
+ layer_id: int = -1,
267
+ ) -> None:
268
+ super().__init__()
269
+ self.hidden_size = hidden_size
270
+ tp_size = get_tensor_model_parallel_world_size()
271
+ self.total_num_heads = num_heads
272
+ assert self.total_num_heads % tp_size == 0
273
+ self.num_heads = self.total_num_heads // tp_size
274
+ self.total_num_kv_heads = num_kv_heads
275
+ if self.total_num_kv_heads >= tp_size:
276
+ # Number of KV heads is greater than TP size, so we partition
277
+ # the KV heads across multiple tensor parallel GPUs.
278
+ assert self.total_num_kv_heads % tp_size == 0
279
+ else:
280
+ # Number of KV heads is less than TP size, so we replicate
281
+ # the KV heads across multiple tensor parallel GPUs.
282
+ assert tp_size % self.total_num_kv_heads == 0
283
+ self.num_kv_heads = max(1, self.total_num_kv_heads // tp_size)
284
+ # MistralConfig has an optional head_dim introduced by Mistral-Nemo
285
+ if hasattr(config, "head_dim"):
286
+ self.head_dim = config.head_dim
287
+ elif hasattr(config, "attention_head_dim"):
288
+ self.head_dim = config.attention_head_dim
289
+ else:
290
+ self.head_dim = self.hidden_size // self.total_num_heads
291
+ self.q_size = self.num_heads * self.head_dim
292
+ self.kv_size = self.num_kv_heads * self.head_dim
293
+ self.scaling = self.head_dim**-0.5
294
+ self.max_position_embeddings = max_position_embeddings
295
+ self.use_qk_norm = getattr(config, "use_qk_norm", False)
296
+ self.layer_id = layer_id
297
+
298
+ self.q_proj = ColumnParallelLinear(
299
+ hidden_size,
300
+ hidden_size,
301
+ bias=bias,
302
+ quant_config=quant_config,
303
+ prefix=f"{prefix}.q_proj",
304
+ )
305
+
306
+ self.o_proj = RowParallelLinear(
307
+ input_size=self.total_num_heads * self.head_dim,
308
+ output_size=hidden_size,
309
+ bias=bias,
310
+ quant_config=quant_config,
311
+ prefix=f"{prefix}.o_proj",
312
+ )
313
+
314
+ self.rotary_emb = get_rope(
315
+ self.head_dim,
316
+ max_position=max_position_embeddings,
317
+ rope_parameters=config.rope_parameters,
318
+ is_neox_style=True,
319
+ )
320
+ self.attn = Attention(
321
+ self.num_heads,
322
+ self.head_dim,
323
+ self.scaling,
324
+ num_kv_heads=self.num_kv_heads,
325
+ cache_config=cache_config,
326
+ quant_config=quant_config,
327
+ prefix=f"{prefix}.attn",
328
+ attn_type=AttentionType.ENCODER_DECODER,
329
+ )
330
+
331
+ if self.use_qk_norm:
332
+ self.query_layernorm = RMSNorm(self.head_dim, eps=config.rms_norm_eps)
333
+ self.key_layernorm = RMSNorm(self.head_dim, eps=config.rms_norm_eps)
334
+
335
+ def forward(
336
+ self,
337
+ positions: torch.Tensor,
338
+ hidden_states: torch.Tensor,
339
+ kv_states: tuple[torch.Tensor] | None = None,
340
+ ) -> torch.Tensor:
341
+ assert kv_states is not None
342
+ ori_k, v = kv_states # use last layer kv,
343
+ k = ori_k
344
+ q, _ = self.q_proj(hidden_states)
345
+ k_tmp = torch.empty_like(k) # Todo: reduant rotary embedding
346
+ q, _ = self.rotary_emb(positions, q, k_tmp)
347
+ if self.use_qk_norm:
348
+ q = self.query_layernorm(
349
+ q.view(-1, self.num_heads, self.head_dim),
350
+ )
351
+ k = self.key_layernorm(
352
+ k.view(-1, self.num_kv_heads, self.head_dim),
353
+ )
354
+
355
+ attn_output = self.attn(q, k, v)
356
+ # For o_proj
357
+ attn_output = attn_output.view(q.shape[0], -1)
358
+ output, _ = self.o_proj(attn_output)
359
+ return output, (ori_k, v)
360
+
361
+
362
+class HunYuanSparseMoeBlock(nn.Module):
363
+ def __init__(
364
+ self,
365
+ config: PretrainedConfig,
366
+ quant_config: QuantizationConfig | None = None,
367
+ layer_id: int = -1,
368
+ prefix: str = "",
369
+ enable_eplb: bool = False,
370
+ ):
371
+ super().__init__()
372
+ self.tp_size = get_tensor_model_parallel_world_size()
373
+
374
+ self.ep_group = get_ep_group().device_group
375
+ self.ep_size = self.ep_group.size()
376
+ self.n_routed_experts = config.num_experts
377
+
378
+ if self.tp_size > config.num_experts:
379
+ raise ValueError(
380
+ f"Tensor parallel size {self.tp_size} is greater than "
381
+ f"the number of experts {config.num_experts}."
382
+ )
383
+
384
+ # Get layer_id topk if config.moe_topk is a list
385
+ if isinstance(config.moe_topk, list):
386
+ assert layer_id >= 0
387
+ assert len(config.moe_topk) > layer_id
388
+ top_k = config.moe_topk[layer_id]
389
+ else:
390
+ top_k = config.moe_topk
391
+
392
+ # If it is moe, moe_intermediate_size is preferred
393
+ intermediate_size = config.intermediate_size
394
+ if config.moe_intermediate_size is not None:
395
+ intermediate_size = (
396
+ config.moe_intermediate_size
397
+ if isinstance(config.moe_intermediate_size, int)
398
+ else config.moe_intermediate_size[layer_id]
399
+ )
400
+
401
+ # Load balancing settings.
402
+ vllm_config = get_current_vllm_config()
403
+ eplb_config = vllm_config.parallel_config.eplb_config
404
+ self.enable_eplb = enable_eplb
405
+
406
+ self.n_logical_experts = self.n_routed_experts
407
+ self.n_redundant_experts = eplb_config.num_redundant_experts
408
+ self.n_physical_experts = self.n_logical_experts + self.n_redundant_experts
409
+ self.n_local_physical_experts = self.n_physical_experts // self.ep_size
410
+ self.gate = ReplicatedLinear(
411
+ config.hidden_size,
412
+ config.num_experts,
413
+ bias=False,
414
+ quant_config=None,
415
+ prefix=f"{prefix}.gate",
416
+ )
417
+ if config.use_mixed_mlp_moe > 0:
418
+ # Get layer_id num_shared_expert if config.num_shared_expert is
419
+ # a list.
420
+ if isinstance(config.num_shared_expert, list):
421
+ assert layer_id >= 0
422
+ assert len(config.num_shared_expert) > layer_id
423
+ num_shared_expert = config.num_shared_expert[layer_id]
424
+ else:
425
+ num_shared_expert = config.num_shared_expert
426
+
427
+ self.shared_mlp = HunYuanMLP(
428
+ hidden_size=config.hidden_size,
429
+ intermediate_size=config.intermediate_size * num_shared_expert,
430
+ hidden_act=config.hidden_act,
431
+ quant_config=quant_config,
432
+ reduce_results=False,
433
+ prefix=f"{prefix}.shared_mlp",
434
+ )
435
+ else:
436
+ self.shared_mlp = None
437
+
438
+ self.experts = FusedMoEFactory(
439
+ shared_experts=self.shared_mlp,
440
+ num_experts=self.n_routed_experts,
441
+ top_k=top_k,
442
+ hidden_size=config.hidden_size,
443
+ intermediate_size=intermediate_size,
444
+ renormalize=top_k > 1,
445
+ quant_config=quant_config,
446
+ prefix=f"{prefix}.experts",
447
+ enable_eplb=self.enable_eplb,
448
+ num_redundant_experts=self.n_redundant_experts,
449
+ )
450
+
451
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
452
+ # NOTE: hidden_states can have either 1D or 2D shape.
453
+ orig_shape = hidden_states.shape
454
+ hidden_dim = hidden_states.shape[-1]
455
+ hidden_states = hidden_states.view(-1, hidden_dim)
456
+
457
+ # router_logits: (num_tokens, n_experts)
458
+ router_logits, _ = self.gate(hidden_states)
459
+ final_hidden_states = self.experts(
460
+ hidden_states=hidden_states, router_logits=router_logits
461
+ )
462
+
463
+ return final_hidden_states.view(orig_shape)
464
+
465
+
466
+class HunYuanDecoderLayer(nn.Module):
467
+ def __init__(
468
+ self,
469
+ config: PretrainedConfig,
470
+ cache_config: CacheConfig | None = None,
471
+ quant_config: QuantizationConfig | None = None,
472
+ prefix: str = "",
473
+ layer_id: int = -1,
474
+ enable_eplb: bool = False,
475
+ ) -> None:
476
+ super().__init__()
477
+ assert layer_id >= 0
478
+ self.layer_id = layer_id
479
+ self.hidden_size = config.hidden_size
480
+ self.intermediate_size = (
481
+ config.intermediate_size
482
+ if isinstance(config.intermediate_size, int)
483
+ else config.intermediate_size[layer_id]
484
+ )
485
+ max_position_embeddings = getattr(config, "max_position_embeddings", 8192)
486
+ attention_bias = getattr(config, "attention_bias", False) or getattr(
487
+ config, "bias", False
488
+ )
489
+ cla_factor = _get_cla_factor(config)
490
+ attention_type = (
491
+ AttentionType.ENCODER_DECODER
492
+ if layer_id >= 0 and layer_id % cla_factor != 0
493
+ else AttentionType.DECODER
494
+ )
495
+ if attention_type == AttentionType.DECODER:
496
+ self.self_attn = HunYuanAttention(
497
+ config=config,
498
+ hidden_size=self.hidden_size,
499
+ num_heads=config.num_attention_heads,
500
+ num_kv_heads=getattr(
501
+ config, "num_key_value_heads", config.num_attention_heads
502
+ ),
503
+ max_position_embeddings=max_position_embeddings,
504
+ quant_config=quant_config,
505
+ bias=attention_bias,
506
+ cache_config=cache_config,
507
+ prefix=f"{prefix}.self_attn",
508
+ layer_id=layer_id,
509
+ )
510
+ elif attention_type == AttentionType.ENCODER_DECODER:
511
+ self.self_attn = HunYuanCrossAttention(
512
+ config=config,
513
+ hidden_size=self.hidden_size,
514
+ num_heads=config.num_attention_heads,
515
+ num_kv_heads=getattr(
516
+ config, "num_key_value_heads", config.num_attention_heads
517
+ ),
518
+ max_position_embeddings=max_position_embeddings,
519
+ quant_config=quant_config,
520
+ bias=attention_bias,
521
+ cache_config=cache_config,
522
+ prefix=f"{prefix}.self_attn",
523
+ layer_id=layer_id,
524
+ )
525
+ else:
526
+ raise RuntimeError(f"Unsupported attention type: {attention_type}")
527
+
528
+ if _is_moe(config):
529
+ self.mlp = HunYuanSparseMoeBlock(
530
+ config=config,
531
+ quant_config=quant_config,
532
+ layer_id=layer_id,
533
+ prefix=f"{prefix}.mlp",
534
+ enable_eplb=enable_eplb,
535
+ )
536
+ else:
537
+ self.mlp = HunYuanMLP(
538
+ hidden_size=self.hidden_size,
539
+ intermediate_size=self.intermediate_size,
540
+ hidden_act=config.hidden_act,
541
+ quant_config=quant_config,
542
+ bias=getattr(config, "mlp_bias", False),
543
+ prefix=f"{prefix}.mlp",
544
+ )
545
+
546
+ self.input_layernorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
547
+ self.post_attention_layernorm = RMSNorm(
548
+ config.hidden_size, eps=config.rms_norm_eps
549
+ )
550
+
551
+ def forward(
552
+ self,
553
+ positions: torch.Tensor,
554
+ hidden_states: torch.Tensor,
555
+ residual: torch.Tensor | None,
556
+ kv_states: tuple[torch.Tensor] | None = None,
557
+ ) -> tuple[torch.Tensor, torch.Tensor]:
558
+ # Self Attention
559
+ if residual is None:
560
+ residual = hidden_states
561
+ hidden_states = self.input_layernorm(hidden_states)
562
+ else:
563
+ hidden_states, residual = self.input_layernorm(hidden_states, residual)
564
+ hidden_states, ori_kv_states = self.self_attn(
565
+ positions=positions,
566
+ hidden_states=hidden_states,
567
+ kv_states=kv_states,
568
+ )
569
+
570
+ # Fully Connected
571
+ hidden_states, residual = self.post_attention_layernorm(hidden_states, residual)
572
+ hidden_states = self.mlp(hidden_states)
573
+ return hidden_states, residual, ori_kv_states
574
+
575
+
576
+@support_torch_compile(
577
+ dynamic_arg_dims={
578
+ "input_ids": 0,
579
+ # positions is of shape (xd, seq_len) if xdrope is enabled for hunyuan-vl,
580
+ # otherwise (seq_len, ).
581
+ "positions": -1,
582
+ "intermediate_tensors": 0,
583
+ "inputs_embeds": 0,
584
+ }
585
+)
586
+class HunYuanModel(nn.Module, EagleModelMixin):
587
+ def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
588
+ super().__init__()
589
+
590
+ config = vllm_config.model_config.hf_config
591
+ cache_config = vllm_config.cache_config
592
+ quant_config = vllm_config.quant_config
593
+
594
+ eplb_config = vllm_config.parallel_config.eplb_config
595
+ enable_eplb = vllm_config.parallel_config.enable_eplb
596
+ self.num_redundant_experts = eplb_config.num_redundant_experts
597
+
598
+ self.config = config
599
+ self.quant_config = quant_config
600
+
601
+ self.vocab_size = config.vocab_size
602
+
603
+ if get_pp_group().is_first_rank or (
604
+ config.tie_word_embeddings and get_pp_group().is_last_rank
605
+ ):
606
+ self.embed_tokens = VocabParallelEmbedding(
607
+ self.vocab_size,
608
+ config.hidden_size,
609
+ quant_config=quant_config,
610
+ )
611
+ else:
612
+ self.embed_tokens = PPMissingLayer()
613
+ self.start_layer, self.end_layer, self.layers = make_layers(
614
+ config.num_hidden_layers,
615
+ lambda prefix: HunYuanDecoderLayer(
616
+ config=config,
617
+ layer_id=int(prefix.split(".")[-1]),
618
+ cache_config=cache_config,
619
+ quant_config=quant_config,
620
+ prefix=prefix,
621
+ enable_eplb=enable_eplb,
622
+ ),
623
+ prefix=f"{prefix}.layers",
624
+ )
625
+ if get_pp_group().is_last_rank:
626
+ self.norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
627
+ else:
628
+ self.norm = PPMissingLayer()
629
+
630
+ def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
631
+ return self.embed_tokens(input_ids)
632
+
633
+ def forward(
634
+ self,
635
+ input_ids: torch.Tensor | None,
636
+ positions: torch.Tensor,
637
+ intermediate_tensors: IntermediateTensors | None,
638
+ inputs_embeds: torch.Tensor | None = None,
639
+ ) -> torch.Tensor | IntermediateTensors:
640
+ if get_pp_group().is_first_rank:
641
+ if inputs_embeds is not None:
642
+ hidden_states = inputs_embeds
643
+ else:
644
+ hidden_states = self.embed_input_ids(input_ids)
645
+ residual = None
646
+ else:
647
+ assert intermediate_tensors is not None
648
+ hidden_states = intermediate_tensors["hidden_states"]
649
+ residual = intermediate_tensors["residual"]
650
+
651
+ cla_factor = _get_cla_factor(self.config)
652
+ prev_kv_states = None
653
+ aux_hidden_states = self._maybe_add_hidden_state([], 0, hidden_states, residual)
654
+ for i, layer in enumerate(
655
+ islice(self.layers, self.start_layer, self.end_layer)
656
+ ):
657
+ hidden_states, residual, kv_states = layer(
658
+ positions,
659
+ hidden_states,
660
+ residual,
661
+ prev_kv_states,
662
+ )
663
+
664
+ if getattr(self.config, "use_cla", False) and i % cla_factor == 0:
665
+ prev_kv_states = kv_states
666
+ else:
667
+ prev_kv_states = None
668
+
669
+ self._maybe_add_hidden_state(
670
+ aux_hidden_states, i + 1, hidden_states, residual
671
+ )
672
+
673
+ if not get_pp_group().is_last_rank:
674
+ return IntermediateTensors(
675
+ {"hidden_states": hidden_states, "residual": residual}
676
+ )
677
+
678
+ hidden_states, _ = self.norm(hidden_states, residual)
679
+
680
+ if len(aux_hidden_states) > 0:
681
+ return hidden_states, aux_hidden_states
682
+ return hidden_states
683
+
684
+ def _split_qkv_weight(self, qkv: torch.Tensor):
685
+ num_attention_heads = self.config.num_attention_heads
686
+ num_kv_heads = getattr(
687
+ self.config, "num_key_value_heads", self.config.num_attention_heads
688
+ )
689
+ num_key_value_groups = num_attention_heads // num_kv_heads
690
+ hidden_size = self.config.hidden_size
691
+
692
+ if hasattr(self.config, "head_dim"):
693
+ attention_head_dim = self.config.head_dim
694
+ elif hasattr(self.config, "attention_head_dim"):
695
+ attention_head_dim = self.config.attention_head_dim
696
+ else:
697
+ attention_head_dim = self.config.hidden_size // num_attention_heads
698
+
699
+ qkv = qkv.reshape(
700
+ num_kv_heads, num_key_value_groups + 2, attention_head_dim, hidden_size
701
+ )
702
+ q, k, v = torch.split(qkv, (num_key_value_groups, 1, 1), dim=1)
703
+ q = q.reshape(-1, hidden_size)
704
+ k = k.reshape(-1, hidden_size)
705
+ v = v.reshape(-1, hidden_size)
706
+ return torch.concat((q, k, v))
707
+
708
+ def get_expert_mapping(self) -> list[tuple[str, str, int, str]]:
709
+ if _is_moe(self.config):
710
+ # Params for weights, fp8 weight scales, fp8 activation scales
711
+ # (param_name, weight_name, expert_id, shard_id)
712
+ return fused_moe_make_expert_params_mapping(
713
+ self,
714
+ ckpt_gate_proj_name="gate_proj",
715
+ ckpt_down_proj_name="down_proj",
716
+ ckpt_up_proj_name="up_proj",
717
+ num_experts=self.config.num_experts,
718
+ num_redundant_experts=self.num_redundant_experts,
719
+ )
720
+ else:
721
+ return []
722
+
723
+ def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]):
724
+ cla_factor = _get_cla_factor(self.config)
725
+ stacked_params_mapping = [
726
+ # (param_name, shard_name, shard_id)
727
+ (".qkv_proj", ".q_proj", "q"),
728
+ (".qkv_proj", ".k_proj", "k"),
729
+ (".qkv_proj", ".v_proj", "v"),
730
+ (".gate_up_proj", ".gate_proj", 0),
731
+ (".gate_up_proj", ".up_proj", 1),
732
+ ]
733
+
734
+ num_attention_heads = self.config.num_attention_heads
735
+ num_kv_heads = getattr(
736
+ self.config, "num_key_value_heads", self.config.num_attention_heads
737
+ )
738
+ split_params_mapping = [
739
+ (".gate_up_proj", ".gate_and_up_proj", 2, [(1, 1), (0, 1)], None),
740
+ (
741
+ ".qkv_proj",
742
+ ".qkv_proj",
743
+ num_attention_heads + num_kv_heads * 2,
744
+ [("q", num_attention_heads), ("k", num_kv_heads), ("v", num_kv_heads)],
745
+ self._split_qkv_weight,
746
+ ),
747
+ ]
748
+
749
+ params_dict = dict(self.named_parameters())
750
+ loaded_params: set[str] = set()
751
+ expert_params_mapping = self.get_expert_mapping()
752
+ for name, loaded_weight in weights:
753
+ if "rotary_emb.inv_freq" in name:
754
+ continue
755
+ if "gate_proj_bias" in name:
756
+ name = name.replace("gate_proj_bias", "gate_proj.bias")
757
+ if "up_proj_bias" in name:
758
+ name = name.replace("up_proj_bias", "up_proj.bias")
759
+ if "rotary_emb.cos_cached" in name or "rotary_emb.sin_cached" in name:
760
+ # Models trained using ColossalAI may include these tensors in
761
+ # the checkpoint. Skip them.
762
+ continue
763
+ # With tie_word_embeddings, we can skip lm_head.weight
764
+ # The weight might appear unnecessarily in the files if the model is
765
+ # processed with quantization, LoRA, fine-tuning, etc.
766
+ if self.config.tie_word_embeddings and "lm_head.weight" in name:
767
+ continue
768
+
769
+ is_found = False
770
+ for param_name, weight_name, shard_id in stacked_params_mapping:
771
+ if weight_name not in name:
772
+ continue
773
+ if "mlp.experts" in name:
774
+ continue
775
+ # cross layer only have q_proj, skip qkv pack
776
+ if weight_name == ".q_proj":
777
+ match = re.search(r"layers\.\d+", name)
778
+ if match:
779
+ layer_id = int(match.group(0).split(".")[-1])
780
+ if cla_factor > 1 and layer_id % cla_factor != 0:
781
+ continue
782
+ name = name.replace(weight_name, param_name)
783
+ # Skip loading extra bias for GPTQ models.
784
+ if name.endswith(".bias") and name not in params_dict:
785
+ continue
786
+
787
+ if is_pp_missing_parameter(name, self):
788
+ continue
789
+
790
+ param = params_dict[name]
791
+ weight_loader = param.weight_loader
792
+ weight_loader(param, loaded_weight, shard_id)
793
+ loaded_params.add(name)
794
+ is_found = True
795
+ break
796
+ if is_found:
797
+ continue
798
+
799
+ for (
800
+ param_name,
801
+ weight_name,
802
+ den,
803
+ split_param,
804
+ func,
805
+ ) in split_params_mapping:
806
+ if weight_name not in name:
807
+ continue
808
+ name = name.replace(weight_name, param_name)
809
+ # Skip loading extra bias for GPTQ models.
810
+ if name.endswith(".bias") and name not in params_dict:
811
+ continue
812
+
813
+ if is_pp_missing_parameter(name, self):
814
+ continue
815
+
816
+ assert loaded_weight.shape[0] % den == 0
817
+ units = loaded_weight.shape[0] // den
818
+
819
+ param = params_dict[name]
820
+ weight_loader = param.weight_loader
821
+ offset = 0
822
+ for shard_id, num in split_param:
823
+ new_offset = offset + num * units
824
+ if func:
825
+ weight_loader(
826
+ param, func(loaded_weight)[offset:new_offset], shard_id
827
+ )
828
+ else:
829
+ weight_loader(param, loaded_weight[offset:new_offset], shard_id)
830
+ offset = new_offset
831
+
832
+ break
833
+ else:
834
+ # Skip loading extra bias for GPTQ models.
835
+ if name.endswith(".bias") and name not in params_dict:
836
+ continue
837
+ is_expert_weight = False
838
+ for mapping in expert_params_mapping:
839
+ param_name, weight_name, expert_id, shard_id = mapping
840
+ if weight_name not in name:
841
+ continue
842
+ # this is an expert weight and should not be
843
+ # attempted to load as other weights later
844
+ is_expert_weight = True
845
+
846
+ # Do not modify `name` since the loop may continue here
847
+ # Instead, create a new variable
848
+ name_mapped = name.replace(weight_name, param_name)
849
+ if is_pp_missing_parameter(name_mapped, self):
850
+ continue
851
+ param = params_dict[name_mapped]
852
+ # We should ask the weight loader to return success or not
853
+ # here since otherwise we may skip experts with other
854
+ # available replicas.
855
+ weight_loader = typing.cast(
856
+ Callable[..., bool], param.weight_loader
857
+ )
858
+ success = weight_loader(
859
+ param,
860
+ loaded_weight,
861
+ name_mapped,
862
+ shard_id=shard_id,
863
+ expert_id=expert_id,
864
+ return_success=True,
865
+ )
866
+ if success:
867
+ name = name_mapped
868
+ break
869
+ else:
870
+ if is_expert_weight:
871
+ # We've checked that this is an expert weight
872
+ # However it's not mapped locally to this rank
873
+ # So we simply skip it
874
+ continue
875
+ # Remapping the name of FP8 kv-scale.
876
+ name = maybe_remap_kv_scale_name(name, params_dict)
877
+ if name is None:
878
+ continue
879
+
880
+ if is_pp_missing_parameter(name, self):
881
+ continue
882
+
883
+ if "mlp.gate.wg." in name:
884
+ name = name.replace("wg.", "")
885
+
886
+ param = params_dict[name]
887
+ weight_loader = getattr(
888
+ param, "weight_loader", default_weight_loader
889
+ )
890
+ weight_loader(param, loaded_weight)
891
+ loaded_params.add(name)
892
+ return loaded_params
893
+
894
+
895
+class HunyuanV1ModelBase(
896
+ nn.Module, SupportsLoRA, SupportsPP, SupportsEagle, SupportsEagle3
897
+):
898
+ packed_modules_mapping = {
899
+ "qkv_proj": [
900
+ "q_proj",
901
+ "k_proj",
902
+ "v_proj",
903
+ ],
904
+ "gate_up_proj": [
905
+ "gate_proj",
906
+ "up_proj",
907
+ ],
908
+ }
909
+
910
+ def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
911
+ super().__init__()
912
+
913
+ config = vllm_config.model_config.hf_config
914
+ quant_config = vllm_config.quant_config
915
+ self.config = config
916
+ self.quant_config = quant_config
917
+
918
+ self.model = HunYuanModel(
919
+ vllm_config=vllm_config,
920
+ prefix=maybe_prefix(prefix, "model"),
921
+ )
922
+ if get_pp_group().is_last_rank:
923
+ self.lm_head = ParallelLMHead(
924
+ config.vocab_size,
925
+ config.hidden_size,
926
+ quant_config=quant_config,
927
+ prefix=maybe_prefix(prefix, "lm_head"),
928
+ )
929
+ if config.tie_word_embeddings:
930
+ self.lm_head = self.lm_head.tie_weights(self.model.embed_tokens)
931
+
932
+ logit_scale = getattr(config, "logit_scale", 1.0)
933
+ self.logits_processor = LogitsProcessor(
934
+ config.vocab_size, scale=logit_scale
935
+ )
936
+ else:
937
+ self.lm_head = PPMissingLayer()
938
+
939
+ def forward(
940
+ self,
941
+ input_ids: torch.Tensor | None,
942
+ positions: torch.Tensor,
943
+ intermediate_tensors: IntermediateTensors | None = None,
944
+ inputs_embeds: torch.Tensor | None = None,
945
+ ) -> torch.Tensor | IntermediateTensors:
946
+ model_output = self.model(
947
+ input_ids, positions, intermediate_tensors, inputs_embeds
948
+ )
949
+ return model_output
950
+
951
+ def compute_logits(
952
+ self,
953
+ hidden_states: torch.Tensor,
954
+ ) -> torch.Tensor | None:
955
+ logits = self.logits_processor(self.lm_head, hidden_states)
956
+ return logits
957
+
958
+ def make_empty_intermediate_tensors(
959
+ self, batch_size: int, dtype: torch.dtype, device: torch.device
960
+ ) -> IntermediateTensors:
961
+ return IntermediateTensors(
962
+ {
963
+ "hidden_states": torch.zeros(
964
+ (batch_size, self.config.hidden_size), dtype=dtype, device=device
965
+ ),
966
+ "residual": torch.zeros(
967
+ (batch_size, self.config.hidden_size), dtype=dtype, device=device
968
+ ),
969
+ }
970
+ )
971
+
972
+ def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
973
+ loader = AutoWeightsLoader(self)
974
+ return loader.load_weights(weights)
975
+
976
+ def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
977
+ return self.model.embed_input_ids(input_ids)
978
+
979
+
980
+class HunYuanMoEV1Base(HunyuanV1ModelBase, MixtureOfExperts):
981
+ def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
982
+ super().__init__(vllm_config=vllm_config, prefix=prefix)
983
+
984
+ # Set MoE hyperparameters
985
+ self.num_expert_groups = 1
986
+ self.moe_layers = []
987
+ example_layer = None
988
+ for layer in self.model.layers:
989
+ if isinstance(layer, PPMissingLayer):
990
+ continue
991
+
992
+ assert isinstance(layer, HunYuanDecoderLayer)
993
+ if isinstance(layer.mlp, HunYuanSparseMoeBlock):
994
+ example_layer = layer.mlp
995
+ self.moe_layers.append(layer.mlp.experts)
996
+
997
+ if example_layer is None:
998
+ raise RuntimeError("No HunYuanMoE layer found in model.layers.")
999
+
1000
+ self.num_moe_layers = len(self.moe_layers)
1001
+ self.num_logical_experts = example_layer.n_logical_experts
1002
+ self.num_physical_experts = example_layer.n_physical_experts
1003
+ self.num_local_physical_experts = example_layer.n_local_physical_experts
1004
+ self.num_routed_experts = example_layer.n_routed_experts
1005
+ self.num_redundant_experts = example_layer.n_redundant_experts
1006
+
1007
+ def update_physical_experts_metadata(
1008
+ self,
1009
+ num_physical_experts: int,
1010
+ num_local_physical_experts: int,
1011
+ ) -> None:
1012
+ assert self.num_local_physical_experts == num_local_physical_experts
1013
+ self.num_physical_experts = num_physical_experts
1014
+ self.num_local_physical_experts = num_local_physical_experts
1015
+ self.num_redundant_experts = num_physical_experts - self.num_logical_experts
1016
+ for layer in self.model.layers:
1017
+ if isinstance(layer.mlp, HunYuanSparseMoeBlock):
1018
+ moe = layer.mlp
1019
+ moe.n_local_physical_experts = num_local_physical_experts
1020
+ moe.n_physical_experts = num_physical_experts
1021
+ moe.n_redundant_experts = self.num_redundant_experts
1022
+ moe.experts.update_expert_map()
1023
+
1024
+ def get_expert_mapping(self) -> list[tuple[str, str, int, str]]:
1025
+ return self.model.get_expert_mapping()
1026
+
1027
+
1028
+class HunYuanDenseV1Base(HunyuanV1ModelBase):
1029
+ def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
1030
+ super().__init__(vllm_config=vllm_config, prefix=prefix)
1031
+
1032
+
1033
+class HunYuanDenseV1ForCausalLM(HunYuanDenseV1Base):
1034
+ pass
1035
+
1036
+
1037
+class HunYuanMoEV1ForCausalLM(HunYuanMoEV1Base):
1038
+ pass
vllm/model_executor/models/hunyuan_vision.py
new
+1047
@@ -0,0 +1,1047 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+
4
+# coding=utf-8
5
+# Copyright 2025 The HunYuan team.
6
+# Copyright 2025 The vLLM team.
7
+# Copyright 2025 EleutherAI and the HuggingFace Inc. team. All rights reserved.
8
+#
9
+# This code is based on EleutherAI's GPT-NeoX library and the GPT-NeoX
10
+# and OPT implementations in this library. It has been modified from its
11
+# original forms to accommodate minor architectural differences compared
12
+# to GPT-NeoX and OPT used by the Meta AI team that trained the model.
13
+#
14
+# Licensed under the Apache License, Version 2.0 (the "License");
15
+# you may not use this file except in compliance with the License.
16
+# You may obtain a copy of the License at
17
+#
18
+# http://www.apache.org/licenses/LICENSE-2.0
19
+#
20
+# Unless required by applicable law or agreed to in writing, software
21
+# distributed under the License is distributed on an "AS IS" BASIS,
22
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23
+# See the License for the specific language governing permissions and
24
+# limitations under the License.
25
+"""Inference-only HunYuan-VL model compatible with HuggingFace weights."""
26
+
27
+from collections.abc import Callable, Iterable, Mapping, Sequence
28
+from functools import partial
29
+from typing import Annotated, Any, Literal, TypeAlias
30
+
31
+import torch
32
+import torch.nn as nn
33
+import torch.nn.functional as F
34
+from transformers import BatchFeature, HunYuanVLProcessor
35
+from transformers.models.hunyuan_vl.image_processing_hunyuan_vl import (
36
+ HunYuanVLImageProcessor,
37
+ smart_resize,
38
+)
39
+
40
+from vllm.config import VllmConfig
41
+from vllm.config.multimodal import BaseDummyOptions
42
+from vllm.distributed import parallel_state
43
+from vllm.distributed import utils as dist_utils
44
+from vllm.inputs import ModalityData, MultiModalDataDict
45
+from vllm.logger import init_logger
46
+from vllm.model_executor.layers.activation import get_act_fn
47
+from vllm.model_executor.layers.attention import MMEncoderAttention
48
+from vllm.model_executor.layers.layernorm import RMSNorm
49
+from vllm.model_executor.layers.linear import (
50
+ ColumnParallelLinear,
51
+ QKVParallelLinear,
52
+ RowParallelLinear,
53
+)
54
+from vllm.model_executor.layers.quantization import QuantizationConfig
55
+from vllm.model_executor.models.module_mapping import MultiModelKeys
56
+from vllm.multimodal import MULTIMODAL_REGISTRY
57
+from vllm.multimodal.inputs import (
58
+ ImageItem,
59
+ MultiModalFeatureSpec,
60
+ MultiModalFieldConfig,
61
+ MultiModalKwargsItems,
62
+)
63
+from vllm.multimodal.parse import (
64
+ DictEmbeddingItems,
65
+ ImageSize,
66
+ ModalityDataItems,
67
+ MultiModalDataItems,
68
+ MultiModalDataParser,
69
+)
70
+from vllm.multimodal.processing import (
71
+ BaseDummyInputsBuilder,
72
+ BaseMultiModalProcessor,
73
+ BaseProcessingInfo,
74
+ PromptReplacement,
75
+ PromptUpdate,
76
+ PromptUpdateDetails,
77
+)
78
+from vllm.sequence import IntermediateTensors
79
+from vllm.transformers_utils.configs.hunyuan_vl import (
80
+ HunYuanVLConfig,
81
+ HunYuanVLVisionConfig,
82
+)
83
+from vllm.utils.tensor_schema import TensorSchema, TensorShape
84
+
85
+from .interfaces import (
86
+ MultiModalEmbeddings,
87
+ SupportsEagle,
88
+ SupportsEagle3,
89
+ SupportsLoRA,
90
+ SupportsMultiModal,
91
+ SupportsPP,
92
+ SupportsQuant,
93
+ SupportsXDRoPE,
94
+)
95
+from .utils import (
96
+ AutoWeightsLoader,
97
+ WeightsMapper,
98
+ init_vllm_registered_model,
99
+ maybe_prefix,
100
+)
101
+from .vision import is_vit_use_data_parallel
102
+
103
+logger = init_logger(__name__)
104
+
105
+# === Vision Inputs === #
106
+
107
+
108
+class HunYuanVLImagePixelInputs(TensorSchema):
109
+ """
110
+ Dimensions:
111
+ - np: Number of patches
112
+ - ni: Number of images
113
+ - cps: Number of channels * patch_size * patch_size
114
+ """
115
+
116
+ type: Literal["pixel_values"]
117
+
118
+ pixel_values: Annotated[
119
+ torch.Tensor,
120
+ TensorShape("np", "cps"),
121
+ ]
122
+
123
+ image_grid_thw: Annotated[
124
+ torch.Tensor,
125
+ TensorShape("ni", 3),
126
+ ]
127
+
128
+
129
+class HunYuanVLImageEmbeddingInputs(TensorSchema):
130
+ """
131
+ Dimensions:
132
+ - nf: Number of image features
133
+ - hs: Hidden size
134
+ - ni: Number of images
135
+ """
136
+
137
+ type: Literal["image_embeds"]
138
+
139
+ image_embeds: Annotated[
140
+ torch.Tensor,
141
+ TensorShape("nf", "hs"),
142
+ ]
143
+
144
+ image_grid_thw: Annotated[
145
+ torch.Tensor,
146
+ TensorShape("ni", 3),
147
+ ]
148
+
149
+
150
+HunYuanVLImageInputs: TypeAlias = (
151
+ HunYuanVLImagePixelInputs | HunYuanVLImageEmbeddingInputs
152
+)
153
+
154
+# === Vision Encoder === #
155
+
156
+
157
+class HunYuanVisionMLP(nn.Module):
158
+ def __init__(
159
+ self,
160
+ in_features: int,
161
+ hidden_features: int,
162
+ bias: bool = True,
163
+ act_fn: Callable[[torch.Tensor], torch.Tensor] = F.gelu,
164
+ quant_config: QuantizationConfig | None = None,
165
+ prefix: str = "",
166
+ ):
167
+ super().__init__()
168
+ use_data_parallel = is_vit_use_data_parallel()
169
+ self.dense_h_to_4h = ColumnParallelLinear(
170
+ in_features,
171
+ hidden_features,
172
+ bias=bias,
173
+ quant_config=quant_config,
174
+ prefix=f"{prefix}.dense_h_to_4h",
175
+ disable_tp=use_data_parallel,
176
+ )
177
+ self.dense_4h_to_h = RowParallelLinear(
178
+ hidden_features,
179
+ in_features,
180
+ bias=bias,
181
+ quant_config=quant_config,
182
+ prefix=f"{prefix}.dense_4h_to_h",
183
+ disable_tp=use_data_parallel,
184
+ )
185
+ self.act_fn = act_fn
186
+
187
+ def forward(self, x: torch.Tensor):
188
+ x_up, _ = self.dense_h_to_4h(x)
189
+ x_down, _ = self.dense_4h_to_h(self.act_fn(x_up))
190
+ return x_down
191
+
192
+
193
+class HunYuanVisionAttention(nn.Module):
194
+ def __init__(
195
+ self,
196
+ embed_dim: int,
197
+ num_heads: int,
198
+ projection_size: int,
199
+ quant_config: QuantizationConfig | None = None,
200
+ prefix: str = "",
201
+ ) -> None:
202
+ super().__init__()
203
+ # Per attention head and per partition values.
204
+ use_data_parallel = is_vit_use_data_parallel()
205
+ self.tp_size = (
206
+ 1
207
+ if use_data_parallel
208
+ else parallel_state.get_tensor_model_parallel_world_size()
209
+ )
210
+ self.hidden_size_per_attention_head = dist_utils.divide(
211
+ projection_size, num_heads
212
+ )
213
+ self.num_attention_heads_per_partition = dist_utils.divide(
214
+ num_heads, self.tp_size
215
+ )
216
+
217
+ self.qkv = QKVParallelLinear(
218
+ hidden_size=embed_dim,
219
+ head_size=self.hidden_size_per_attention_head,
220
+ total_num_heads=num_heads,
221
+ total_num_kv_heads=num_heads,
222
+ bias=True,
223
+ quant_config=quant_config,
224
+ prefix=f"{prefix}.qkv",
225
+ disable_tp=use_data_parallel,
226
+ )
227
+
228
+ self.o_proj = RowParallelLinear(
229
+ input_size=projection_size,
230
+ output_size=embed_dim,
231
+ quant_config=quant_config,
232
+ prefix=f"{prefix}.o_proj",
233
+ disable_tp=use_data_parallel,
234
+ )
235
+
236
+ self.scale = self.hidden_size_per_attention_head**-0.5
237
+ self.attn = MMEncoderAttention(
238
+ self.num_attention_heads_per_partition,
239
+ self.hidden_size_per_attention_head,
240
+ self.scale,
241
+ prefix=f"{prefix}.attn",
242
+ )
243
+
244
+ def forward(
245
+ self,
246
+ x: torch.Tensor,
247
+ ) -> torch.Tensor:
248
+ qkv, _ = self.qkv(x)
249
+ q, k, v = qkv.chunk(3, dim=-1)
250
+ out = self.attn(q, k, v)
251
+ output, _ = self.o_proj(out)
252
+ return output
253
+
254
+
255
+class HunYuanVisionBlock(nn.Module):
256
+ def __init__(
257
+ self,
258
+ dim: int,
259
+ num_heads: int,
260
+ mlp_hidden_dim: int,
261
+ act_fn: Callable[[torch.Tensor], torch.Tensor] = F.gelu,
262
+ norm_layer: Callable[[int], nn.Module] | None = None,
263
+ quant_config: QuantizationConfig | None = None,
264
+ prefix: str = "",
265
+ ) -> None:
266
+ super().__init__()
267
+ if norm_layer is None:
268
+ norm_layer = partial(nn.LayerNorm, eps=1e-6)
269
+ self.input_layernorm = norm_layer(dim)
270
+ self.post_attention_layernorm = norm_layer(dim)
271
+ self.self_attn = HunYuanVisionAttention(
272
+ embed_dim=dim,
273
+ num_heads=num_heads,
274
+ projection_size=dim,
275
+ quant_config=quant_config,
276
+ prefix=f"{prefix}.self_attn",
277
+ )
278
+ self.mlp = HunYuanVisionMLP(
279
+ dim,
280
+ mlp_hidden_dim,
281
+ act_fn=act_fn,
282
+ bias=True,
283
+ quant_config=quant_config,
284
+ prefix=f"{prefix}.mlp",
285
+ )
286
+
287
+ def forward(
288
+ self,
289
+ x: torch.Tensor,
290
+ ) -> torch.Tensor:
291
+ x = x + self.self_attn(self.input_layernorm(x))
292
+ x = x + self.mlp(self.post_attention_layernorm(x))
293
+ return x
294
+
295
+
296
+class HunYuanVisionPatchEmbed(nn.Module):
297
+ def __init__(self, config: HunYuanVLVisionConfig):
298
+ super().__init__()
299
+
300
+ self.config = config
301
+ self.embed_dim = config.hidden_size
302
+ self.patch_size = config.patch_size
303
+ self.num_channels = config.num_channels
304
+ self.spatial_merge_size = config.spatial_merge_size
305
+ self.interpolate_mode = config.interpolate_mode
306
+
307
+ self.patch_embedding = nn.Conv2d(
308
+ in_channels=config.num_channels,
309
+ out_channels=self.embed_dim,
310
+ kernel_size=self.patch_size,
311
+ stride=self.patch_size,
312
+ bias=True,
313
+ )
314
+
315
+ self.max_num_patches = (config.max_image_size // self.patch_size) ** 2
316
+
317
+ self.num_positions = self.max_num_patches + 1
318
+ self.position_edge = int(self.num_positions**0.5)
319
+ # first token is cls token, skip it
320
+ self.position_embedding = nn.Embedding(self.num_positions, self.embed_dim)
321
+
322
+ self.patch_pos_embed = None
323
+
324
+ def forward(
325
+ self, pixel_values: torch.Tensor, grid_thw: list[list[int]]
326
+ ) -> torch.Tensor:
327
+ num_patches = pixel_values.size(0)
328
+ pixel_values = pixel_values.reshape(
329
+ num_patches, self.num_channels, self.patch_size, self.patch_size
330
+ )
331
+
332
+ patch_embeds = self.patch_embedding(pixel_values)
333
+ patch_embeds = patch_embeds.squeeze(-1).squeeze(-1).unsqueeze(0)
334
+
335
+ if self.patch_pos_embed is None:
336
+ patch_pos_shape = (
337
+ 1,
338
+ self.position_edge,
339
+ self.position_edge,
340
+ self.embed_dim,
341
+ )
342
+ self.patch_pos_embed = (
343
+ self.position_embedding.weight[1:, :]
344
+ .reshape(patch_pos_shape)
345
+ .permute(0, 3, 1, 2)
346
+ .float()
347
+ )
348
+
349
+ patch_pos_embed_list = []
350
+ for grid in grid_thw:
351
+ _, h0, w0 = grid
352
+ # we add a small number to avoid floating point error in the interpolation
353
+ # see discussion at https://github.com/facebookresearch/dino/issues/8
354
+ h0, w0 = h0 + 0.1, w0 + 0.1
355
+ patch_pos_embed = nn.functional.interpolate(
356
+ self.patch_pos_embed,
357
+ scale_factor=(h0 / self.position_edge, w0 / self.position_edge),
358
+ mode=self.interpolate_mode,
359
+ align_corners=False,
360
+ )
361
+
362
+ patch_pos_embed = (
363
+ patch_pos_embed.reshape(self.embed_dim, -1)
364
+ .transpose(0, 1)
365
+ .unsqueeze(0)
366
+ .to(patch_embeds.dtype)
367
+ )
368
+ patch_pos_embed_list.append(patch_pos_embed)
369
+
370
+ patch_pos_embed = torch.cat(patch_pos_embed_list, dim=1)
371
+ embeddings = patch_embeds + patch_pos_embed
372
+
373
+ return embeddings
374
+
375
+
376
+class HunYuanVisionPatchMerger(nn.Module):
377
+ def __init__(
378
+ self,
379
+ in_channels,
380
+ out_channels,
381
+ spatial_merge_size=2,
382
+ rms_norm_eps=1e-5,
383
+ prefix="",
384
+ ):
385
+ super().__init__()
386
+ self.spatial_merge_size = spatial_merge_size
387
+ embed_std = out_channels**-0.5
388
+
389
+ self.proj = nn.Sequential(
390
+ nn.Conv2d(
391
+ in_channels,
392
+ in_channels * 2,
393
+ kernel_size=spatial_merge_size,
394
+ stride=spatial_merge_size,
395
+ ),
396
+ nn.GELU(),
397
+ nn.Conv2d(in_channels * 2, in_channels * 4, kernel_size=1),
398
+ )
399
+ self.mlp = nn.Linear(in_channels * 4, out_channels)
400
+
401
+ self.image_newline = nn.Parameter(torch.randn(in_channels * 4) * embed_std)
402
+ self.image_begin = nn.Parameter(torch.randn(out_channels) * embed_std)
403
+ self.image_end = nn.Parameter(torch.randn(out_channels) * embed_std)
404
+ self.image_sep = nn.Parameter(torch.randn(out_channels) * embed_std)
405
+
406
+ self.before_rms = RMSNorm(in_channels, eps=rms_norm_eps)
407
+ self.after_rms = RMSNorm(out_channels, eps=rms_norm_eps)
408
+
409
+ def forward(self, x, size=(16, 16)):
410
+ x = self.before_rms(x)
411
+
412
+ h, w = size
413
+ dtype = x.dtype
414
+ x = x.permute(0, 2, 1).reshape(x.shape[0], -1, h, w)
415
+
416
+ x = self.proj(x) # b,c,h,w
417
+ b, c, h, w = x.shape
418
+ x = torch.cat(
419
+ [x, self.image_newline.reshape(1, c, 1, 1).expand(b, c, h, 1).to(dtype)],
420
+ dim=-1,
421
+ )
422
+ x = x.reshape(b, c, -1).permute(0, 2, 1)
423
+ x = self.mlp(x)
424
+
425
+ begin = self.image_begin.reshape(1, 1, -1).expand(b, 1, x.shape[-1]).to(dtype)
426
+ end = self.image_end.reshape(1, 1, -1).expand(b, 1, x.shape[-1]).to(dtype)
427
+ x = torch.cat([begin, x, end], dim=1)
428
+
429
+ return self.after_rms(x)
430
+
431
+
432
+class HunYuanVisionTransformer(nn.Module):
433
+ hf_to_vllm_mapper = WeightsMapper(
434
+ orig_to_new_stacked={
435
+ ".q_proj": (".qkv", "q"),
436
+ ".k_proj": (".qkv", "k"),
437
+ ".v_proj": (".qkv", "v"),
438
+ }
439
+ )
440
+
441
+ def __init__(
442
+ self,
443
+ vision_config: HunYuanVLVisionConfig,
444
+ quant_config: QuantizationConfig | None = None,
445
+ prefix: str = "",
446
+ ) -> None:
447
+ super().__init__()
448
+
449
+ num_hidden_layers = vision_config.num_hidden_layers
450
+ self.hidden_size = vision_config.hidden_size
451
+ self.num_heads = vision_config.num_attention_heads
452
+ self.spatial_merge_size = vision_config.spatial_merge_size
453
+
454
+ from vllm.compilation.backends import set_model_tag
455
+
456
+ with set_model_tag("HunYuanVisionPatchEmbed"):
457
+ self.embeddings = HunYuanVisionPatchEmbed(vision_config)
458
+
459
+ norm_layer = partial(nn.LayerNorm, eps=vision_config.rms_norm_eps)
460
+
461
+ with set_model_tag("HunYuanVisionBlock"):
462
+ self.layers = nn.ModuleList(
463
+ [
464
+ HunYuanVisionBlock(
465
+ dim=vision_config.hidden_size,
466
+ num_heads=vision_config.num_attention_heads,
467
+ mlp_hidden_dim=vision_config.intermediate_size,
468
+ act_fn=get_act_fn(vision_config.hidden_act),
469
+ norm_layer=norm_layer,
470
+ quant_config=quant_config,
471
+ prefix=f"{prefix}.layers.{layer_idx}",
472
+ )
473
+ for layer_idx in range(num_hidden_layers)
474
+ ]
475
+ )
476
+
477
+ with set_model_tag("HunYuanVisionPatchMerger"):
478
+ self.perceive = HunYuanVisionPatchMerger(
479
+ vision_config.hidden_size,
480
+ vision_config.out_hidden_size,
481
+ spatial_merge_size=vision_config.spatial_merge_size,
482
+ rms_norm_eps=vision_config.rms_norm_eps,
483
+ prefix=f"{prefix}.perceive",
484
+ )
485
+
486
+ @property
487
+ def dtype(self) -> torch.dtype:
488
+ return self.embeddings.patch_embedding.weight.dtype
489
+
490
+ @property
491
+ def device(self) -> torch.device:
492
+ return self.embeddings.patch_embedding.weight.device
493
+
494
+ def forward(
495
+ self,
496
+ x: torch.Tensor,
497
+ grid_thw: list[list[int]],
498
+ ) -> torch.Tensor:
499
+ # patchify
500
+ seq_len = x.size(0)
501
+ cu_seqlens: list = [0]
502
+
503
+ hidden_states = x.to(device=self.device, dtype=self.dtype)
504
+ # embeddings = patch_embeds + patch_pos_embed
505
+ hidden_states = self.embeddings(hidden_states, grid_thw)
506
+
507
+ for t, h, w in grid_thw:
508
+ t, h, w = int(t), int(h), int(w)
509
+ cu_seqlens.append(h * w)
510
+
511
+ cu_seqlens = torch.tensor(cu_seqlens, dtype=torch.int32)
512
+ cu_seqlens = torch.cumsum(cu_seqlens, dim=0, dtype=torch.int32)
513
+
514
+ cu_seqlens = cu_seqlens.to(device=self.device, non_blocking=True)
515
+
516
+ hidden_states = hidden_states.reshape(seq_len, -1)
517
+ hidden_states = hidden_states.unsqueeze(0)
518
+
519
+ # build per-image lengths once
520
+ split_lengths = [int(h) * int(w) for (_, h, w) in grid_thw]
521
+ for layer in self.layers:
522
+ # hidden_states: (1, T_total, D)
523
+ parts = hidden_states.split(split_lengths, dim=1) # list of (1, L_i, D)
524
+ parts = [layer(p) for p in parts]
525
+ hidden_states = torch.cat(parts, dim=1)
526
+
527
+ # adapter
528
+ split_lengths = (cu_seqlens[1:] - cu_seqlens[:-1]).tolist()
529
+ split_items = hidden_states.split(split_lengths, dim=1)
530
+ image_embeds_list = []
531
+ for grid, split_item in zip(grid_thw, split_items):
532
+ image_embeds_list.append(
533
+ self.perceive(split_item.contiguous(), size=grid[1:]).squeeze(0)
534
+ )
535
+
536
+ return image_embeds_list
537
+
538
+ def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
539
+ loader = AutoWeightsLoader(self)
540
+ return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
541
+
542
+
543
+def _hunyuan_vl_field_config(hf_inputs: Mapping[str, torch.Tensor]):
544
+ image_grid_thw = hf_inputs.get("image_grid_thw", torch.empty((0, 3)))
545
+ image_grid_sizes = image_grid_thw.prod(-1)
546
+ return dict(
547
+ pixel_values=MultiModalFieldConfig.flat_from_sizes("image", image_grid_sizes),
548
+ image_embeds=MultiModalFieldConfig.flat_from_sizes("image", image_grid_sizes),
549
+ image_grid_thw=MultiModalFieldConfig.batched("image", keep_on_cpu=True),
550
+ )
551
+
552
+
553
+class HunYuanVLMultiModalDataParser(MultiModalDataParser):
554
+ # The patch grid is what sizes the placeholder range.
555
+ embedding_fields = {
556
+ "image": {"image_embeds": "values", "image_grid_thw": "metadata"},
557
+ }
558
+
559
+ def _parse_image_data(
560
+ self,
561
+ data: dict[str, torch.Tensor] | ModalityData[ImageItem],
562
+ ) -> ModalityDataItems[Any, Any] | None:
563
+ if isinstance(data, dict):
564
+ required, optional = self.embedding_field_sets("image")
565
+ return DictEmbeddingItems(
566
+ data,
567
+ modality="image",
568
+ required_fields=required,
569
+ optional_fields=optional,
570
+ fields_factory=_hunyuan_vl_field_config,
571
+ )
572
+
573
+ return super()._parse_image_data(data)
574
+
575
+
576
+class HunYuanVLProcessingInfo(BaseProcessingInfo):
577
+ def get_hf_config(self):
578
+ return self.ctx.get_hf_config(HunYuanVLConfig)
579
+
580
+ def get_hf_processor(
581
+ self,
582
+ **kwargs: object,
583
+ ) -> HunYuanVLProcessor:
584
+ # transformers>=5.13 replaced `use_fast` with `backend`; pin the
585
+ # PIL backend to match the released HunyuanOCR checkpoint packing.
586
+ kwargs.pop("use_fast", None)
587
+ kwargs.setdefault("backend", "pil")
588
+ return self.ctx.get_hf_processor(
589
+ HunYuanVLProcessor,
590
+ **kwargs,
591
+ )
592
+
593
+ def get_image_processor(
594
+ self,
595
+ **kwargs: object,
596
+ ) -> HunYuanVLImageProcessor:
597
+ return self.get_hf_processor(**kwargs).image_processor
598
+
599
+ def get_data_parser(self):
600
+ return HunYuanVLMultiModalDataParser(
601
+ expected_hidden_size=self._get_expected_hidden_size(),
602
+ allow_missing_mm_embeddings=self.allow_missing_mm_embeddings,
603
+ )
604
+
605
+ def get_supported_mm_limits(self) -> Mapping[str, int | None]:
606
+ return {"image": None}
607
+
608
+ def get_mm_max_tokens_per_item(
609
+ self,
610
+ seq_len: int,
611
+ mm_counts: Mapping[str, int],
612
+ ) -> Mapping[str, int]:
613
+ max_image_tokens = self.get_max_image_tokens()
614
+ # TODO: support video
615
+ max_video_tokens = 0
616
+ return {"image": max_image_tokens, "video": max_video_tokens}
617
+
618
+ def _get_vision_info(
619
+ self,
620
+ *,
621
+ image_width: int,
622
+ image_height: int,
623
+ num_frames: int = 1,
624
+ do_resize: bool = True,
625
+ image_processor: HunYuanVLImageProcessor,
626
+ mm_kwargs: Mapping[str, object],
627
+ ) -> tuple[ImageSize, int]:
628
+ hf_config = self.get_hf_config()
629
+ vision_config = hf_config.vision_config
630
+ patch_size = vision_config.patch_size
631
+ spatial_merge_size = vision_config.spatial_merge_size
632
+
633
+ mm_kwargs = self.ctx.get_merged_mm_kwargs(mm_kwargs)
634
+ size = image_processor.size
635
+ if override_size := mm_kwargs.get("size"):
636
+ size = size | override_size
637
+ if (override_min_pixels := mm_kwargs.get("min_pixels")) is not None:
638
+ size = size | {"shortest_edge": override_min_pixels}
639
+ if (override_max_pixels := mm_kwargs.get("max_pixels")) is not None:
640
+ size = size | {"longest_edge": override_max_pixels}
641
+
642
+ if do_resize:
643
+ resized_height, resized_width = smart_resize(
644
+ height=image_height,
645
+ width=image_width,
646
+ factor=patch_size * spatial_merge_size,
647
+ min_pixels=size["shortest_edge"],
648
+ max_pixels=size["longest_edge"],
649
+ )
650
+ preprocessed_size = ImageSize(width=resized_width, height=resized_height)
651
+ else:
652
+ preprocessed_size = ImageSize(width=image_width, height=image_height)
653
+
654
+ grid_t = 1
655
+ grid_h = preprocessed_size.height // patch_size
656
+ grid_w = preprocessed_size.width // patch_size
657
+
658
+ num_vision_tokens = (
659
+ grid_t * grid_h // spatial_merge_size * (grid_w // spatial_merge_size + 1)
660
+ + 2
661
+ )
662
+
663
+ return preprocessed_size, num_vision_tokens
664
+
665
+ def get_num_image_tokens(
666
+ self,
667
+ *,
668
+ image_width: int,
669
+ image_height: int,
670
+ image_processor: HunYuanVLImageProcessor,
671
+ mm_kwargs: Mapping[str, object],
672
+ ) -> int:
673
+ _, num_image_tokens = self._get_vision_info(
674
+ image_width=image_width,
675
+ image_height=image_height,
676
+ image_processor=image_processor,
677
+ mm_kwargs=mm_kwargs,
678
+ )
679
+ return num_image_tokens
680
+
681
+ def get_image_size_with_most_features(self) -> ImageSize:
682
+ image_processor = self.get_image_processor()
683
+
684
+ max_image_size, _ = self._get_vision_info(
685
+ image_width=512,
686
+ image_height=8192,
687
+ image_processor=image_processor,
688
+ mm_kwargs={},
689
+ )
690
+ return max_image_size
691
+
692
+ def get_max_image_tokens(self) -> int:
693
+ image_processor = self.get_image_processor()
694
+ target_width, target_height = self.get_image_size_with_most_features()
695
+
696
+ return self.get_num_image_tokens(
697
+ image_width=target_width,
698
+ image_height=target_height,
699
+ image_processor=image_processor,
700
+ mm_kwargs={},
701
+ )
702
+
703
+
704
+class HunYuanVLDummyInputsBuilder(BaseDummyInputsBuilder[HunYuanVLProcessingInfo]):
705
+ def get_dummy_text(self, mm_counts: Mapping[str, int]) -> str:
706
+ num_images = mm_counts.get("image", 0)
707
+
708
+ hf_processor = self.info.get_hf_processor(typ=HunYuanVLProcessor)
709
+ image_placeholder = (
710
+ f"{hf_processor.image_start_token}{hf_processor.image_token}"
711
+ f"{hf_processor.image_end_token}"
712
+ )
713
+
714
+ return image_placeholder * num_images
715
+
716
+ def get_dummy_mm_data(
717
+ self,
718
+ seq_len: int,
719
+ mm_counts: Mapping[str, int],
720
+ mm_options: Mapping[str, BaseDummyOptions],
721
+ ) -> MultiModalDataDict:
722
+ num_images = mm_counts.get("image", 1)
723
+
724
+ target_width, target_height = self.info.get_image_size_with_most_features()
725
+
726
+ return {
727
+ "image": self._get_dummy_images(
728
+ width=target_width, height=target_height, num_images=num_images
729
+ ),
730
+ }
731
+
732
+
733
+class HunYuanVLMultiModalProcessor(BaseMultiModalProcessor[HunYuanVLProcessingInfo]):
734
+ def _call_hf_processor(
735
+ self,
736
+ prompt: str,
737
+ mm_data: Mapping[str, object],
738
+ mm_kwargs: Mapping[str, object],
739
+ ) -> BatchFeature:
740
+ hf_processor = self.info.get_hf_processor(**mm_kwargs)
741
+ # HunYuanVLProcessor requires image placeholders wrapped with start/end tokens.
742
+ if mm_data.get("images") is not None and prompt:
743
+ img_tok = hf_processor.image_token
744
+ wrapped = (
745
+ f"{hf_processor.image_start_token}{img_tok}"
746
+ f"{hf_processor.image_end_token}"
747
+ )
748
+ if img_tok in prompt and wrapped not in prompt:
749
+ prompt = prompt.replace(img_tok, wrapped)
750
+ return self.info.ctx.call_hf_processor(
751
+ hf_processor,
752
+ dict(text=prompt, **mm_data),
753
+ mm_kwargs,
754
+ )
755
+
756
+ def _get_prompt_updates(
757
+ self,
758
+ mm_items: MultiModalDataItems,
759
+ hf_processor_mm_kwargs: Mapping[str, Any],
760
+ out_mm_kwargs: MultiModalKwargsItems,
761
+ ) -> Sequence[PromptUpdate]:
762
+ hf_processor = self.info.get_hf_processor(**hf_processor_mm_kwargs)
763
+ image_processor = self.info.get_image_processor(**hf_processor_mm_kwargs)
764
+
765
+ token_ids = {
766
+ "image": hf_processor.image_token_id,
767
+ "image_start": hf_processor.image_start_token_id,
768
+ "image_end": hf_processor.image_end_token_id,
769
+ }
770
+
771
+ merge_size = image_processor.merge_size
772
+
773
+ def get_replacement_hunyuan_vl(item_idx: int, modality: str):
774
+ out_item = out_mm_kwargs[modality][item_idx]
775
+ grid_thw = out_item[f"{modality}_grid_thw"].data
776
+ assert isinstance(grid_thw, torch.Tensor)
777
+
778
+ _, grid_h, grid_w = grid_thw
779
+ num_tokens = (int(grid_h) // merge_size) * (
780
+ int(grid_w) // merge_size + 1
781
+ ) + 2
782
+ tokens = (
783
+ [token_ids[f"{modality}_start"]]
784
+ + [token_ids[modality]] * num_tokens
785
+ + [token_ids[f"{modality}_end"]]
786
+ )
787
+ return PromptUpdateDetails.select_token_id(tokens, token_ids[modality])
788
+
789
+ return [
790
+ PromptReplacement(
791
+ modality=modality,
792
+ target=[
793
+ token_ids[f"{modality}_start"],
794
+ token_ids[modality],
795
+ token_ids[f"{modality}_end"],
796
+ ],
797
+ replacement=partial(get_replacement_hunyuan_vl, modality=modality),
798
+ )
799
+ for modality in ("image",)
800
+ ]
801
+
802
+ def _get_mm_fields_config(
803
+ self,
804
+ hf_inputs: BatchFeature,
805
+ hf_processor_mm_kwargs: Mapping[str, object],
806
+ ) -> Mapping[str, MultiModalFieldConfig]:
807
+ return _hunyuan_vl_field_config(hf_inputs)
808
+
809
+
810
+@MULTIMODAL_REGISTRY.register_processor(
811
+ HunYuanVLMultiModalProcessor,
812
+ info=HunYuanVLProcessingInfo,
813
+ dummy_inputs=HunYuanVLDummyInputsBuilder,
814
+)
815
+class HunYuanVLForConditionalGeneration(
816
+ nn.Module,
817
+ SupportsMultiModal,
818
+ SupportsLoRA,
819
+ SupportsPP,
820
+ SupportsQuant,
821
+ SupportsXDRoPE,
822
+ SupportsEagle,
823
+ SupportsEagle3,
824
+):
825
+ # To ensure correct weight loading and mapping.
826
+ hf_to_vllm_mapper = WeightsMapper(
827
+ orig_to_new_prefix={
828
+ # mapping for new names in checkpoint saved after transformers v4.52
829
+ "vit.vit.": "visual.",
830
+ "vit.": "visual.",
831
+ "model.": "language_model.model.",
832
+ }
833
+ )
834
+
835
+ supports_encoder_tp_data = True
836
+
837
+ def get_xdrope_input_positions(
838
+ self,
839
+ input_tokens: list[int],
840
+ mm_features: list[MultiModalFeatureSpec],
841
+ ) -> torch.Tensor:
842
+ kwargs = MultiModalFeatureSpec.gather_kwargs(
843
+ mm_features,
844
+ {"image_grid_thw"},
845
+ )
846
+ image_grid_thw = [item.tolist() for item in kwargs.get("image_grid_thw", [])]
847
+
848
+ hf_config = self.config
849
+ image_start_token_id = hf_config.image_start_token_id
850
+ spatial_merge_size = hf_config.vision_config.spatial_merge_size
851
+ xd_num = len(hf_config.rope_scaling["xdrope_section"])
852
+
853
+ input_tokens_tensor = torch.tensor(input_tokens)
854
+ image_start_indices = torch.argwhere(
855
+ input_tokens_tensor == image_start_token_id
856
+ ).squeeze(1)
857
+
858
+ p_index = torch.arange(len(input_tokens_tensor))
859
+ w_index = torch.arange(len(input_tokens_tensor))
860
+ h_index = torch.arange(len(input_tokens_tensor))
861
+ t_index = torch.arange(len(input_tokens_tensor))
862
+ for image_index in range(len(image_start_indices)):
863
+ # +1 : first image_token, +2: for xdrope positions
864
+ pos = image_start_indices[image_index] + 2
865
+ t, h, w = image_grid_thw[image_index]
866
+ _, llm_grid_h, llm_grid_w = (
867
+ t,
868
+ h // spatial_merge_size,
869
+ w // spatial_merge_size,
870
+ )
871
+
872
+ token_num = (llm_grid_w + 1) * llm_grid_h
873
+ w_index[pos : pos + token_num].copy_(
874
+ torch.arange(0, llm_grid_w + 1)
875
+ .reshape(1, -1)
876
+ .expand(llm_grid_h, -1)
877
+ .reshape(-1)
878
+ )
879
+ h_index[pos : pos + token_num].copy_(
880
+ torch.arange(0, llm_grid_h)
881
+ .reshape(-1, 1)
882
+ .expand(-1, llm_grid_w + 1)
883
+ .reshape(-1)
884
+ )
885
+ t_index[pos : pos + token_num] = image_index
886
+
887
+ if xd_num == 4:
888
+ llm_positions = torch.stack([p_index, w_index, h_index, t_index])
889
+ elif xd_num == 3:
890
+ llm_positions = torch.stack([w_index, h_index, t_index])
891
+
892
+ return llm_positions
893
+
894
+ @classmethod
895
+ def get_placeholder_str(cls, modality: str, i: int) -> str | None:
896
+ if modality.startswith("image"):
897
+ return "<|hy_place▁holder▁no▁100|><|hy_place▁holder▁no▁102|><|hy_place▁holder▁no▁101|>" # noqa: E501
898
+
899
+ raise ValueError("Only image modality is supported")
900
+
901
+ def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
902
+ super().__init__()
903
+ config: HunYuanVLConfig = vllm_config.model_config.hf_config
904
+
905
+ self.config = config
906
+
907
+ with self._mark_tower_model(vllm_config, {"image"}):
908
+ self.visual = HunYuanVisionTransformer(
909
+ config.vision_config,
910
+ quant_config=vllm_config.quant_config,
911
+ prefix=maybe_prefix(prefix, "visual"),
912
+ )
913
+
914
+ with self._mark_language_model(vllm_config):
915
+ self.language_model = init_vllm_registered_model(
916
+ vllm_config=vllm_config,
917
+ prefix=maybe_prefix(prefix, "language_model.model"),
918
+ architectures=[
919
+ "HunYuanDenseV1ForCausalLM",
920
+ "HunYuanMoEV1ForCausalLM",
921
+ ],
922
+ )
923
+
924
+ self.make_empty_intermediate_tensors = (
925
+ self.language_model.make_empty_intermediate_tensors
926
+ )
927
+
928
+ def _parse_and_validate_image_input(
929
+ self, **kwargs: object
930
+ ) -> HunYuanVLImageInputs | None:
931
+ pixel_values = kwargs.pop("pixel_values", None)
932
+ image_embeds = kwargs.pop("image_embeds", None)
933
+ image_grid_thw = kwargs.pop("image_grid_thw", None)
934
+
935
+ if pixel_values is None and image_embeds is None:
936
+ return None
937
+
938
+ # TODO: refine
939
+ if isinstance(pixel_values, list):
940
+ pixel_values = torch.cat(pixel_values, dim=0)
941
+ if len(pixel_values.shape) == 3:
942
+ last_dim = pixel_values.shape[-1]
943
+ pixel_values = pixel_values.reshape(-1, last_dim)
944
+ image_grid_thw = image_grid_thw.reshape(-1, 3)
945
+
946
+ if pixel_values is not None:
947
+ return HunYuanVLImagePixelInputs(
948
+ type="pixel_values",
949
+ pixel_values=pixel_values,
950
+ image_grid_thw=image_grid_thw,
951
+ )
952
+
953
+ if image_embeds is not None:
954
+ return HunYuanVLImageEmbeddingInputs(
955
+ type="image_embeds",
956
+ image_embeds=image_embeds,
957
+ image_grid_thw=image_grid_thw,
958
+ )
959
+
960
+ def _process_image_input(
961
+ self, image_input: HunYuanVLImageInputs
962
+ ) -> tuple[torch.Tensor, ...]:
963
+ grid_thw = image_input["image_grid_thw"]
964
+ assert grid_thw.ndim == 2
965
+ grid_thw_list = grid_thw.tolist()
966
+
967
+ if image_input["type"] == "image_embeds":
968
+ image_embeds = image_input["image_embeds"].type(self.visual.dtype)
969
+ else:
970
+ pixel_values = image_input["pixel_values"]
971
+
972
+ # TODO: use_data_parallel (split image_embeds in visual)
973
+ image_embeds = self.visual(pixel_values, grid_thw=grid_thw_list)
974
+
975
+ return image_embeds
976
+
977
+ def _parse_and_validate_multimodal_inputs(self, **kwargs: object) -> dict:
978
+ mm_input_by_modality = {}
979
+
980
+ # Preserve the order of modalities if there are multiple of them
981
+ # from the order of kwargs.
982
+ for input_key in kwargs:
983
+ if (
984
+ input_key in ("pixel_values", "image_embeds")
985
+ and "image" not in mm_input_by_modality
986
+ ):
987
+ mm_input_by_modality["image"] = self._parse_and_validate_image_input(
988
+ **kwargs
989
+ )
990
+ return mm_input_by_modality
991
+
992
+ def embed_multimodal(self, **kwargs: object) -> MultiModalEmbeddings:
993
+ mm_input_by_modality = self._parse_and_validate_multimodal_inputs(**kwargs)
994
+ if not mm_input_by_modality:
995
+ return []
996
+
997
+ # The result multimodal_embeddings is tuple of tensors, with each
998
+ # tensor correspoending to a multimodal data item (image or video).
999
+ multimodal_embeddings: tuple[torch.Tensor, ...] = ()
1000
+
1001
+ # NOTE: It is important to iterate over the keys in this dictionary
1002
+ # to preserve the order of the modalities.
1003
+ for modality in mm_input_by_modality:
1004
+ multimodal_input = mm_input_by_modality[modality]
1005
+ if modality == "image":
1006
+ image_embeddings = self._process_image_input(multimodal_input)
1007
+ multimodal_embeddings += tuple(image_embeddings)
1008
+ return multimodal_embeddings
1009
+
1010
+ def forward(
1011
+ self,
1012
+ input_ids: torch.Tensor | None,
1013
+ positions: torch.Tensor,
1014
+ intermediate_tensors: IntermediateTensors | None,
1015
+ inputs_embeds: torch.Tensor | None,
1016
+ **kwargs: object,
1017
+ ) -> torch.Tensor | IntermediateTensors:
1018
+ if intermediate_tensors is not None:
1019
+ inputs_embeds = None
1020
+
1021
+ hidden_states = self.language_model(
1022
+ input_ids=input_ids,
1023
+ positions=positions,
1024
+ intermediate_tensors=intermediate_tensors,
1025
+ inputs_embeds=inputs_embeds,
1026
+ )
1027
+ return hidden_states
1028
+
1029
+ def compute_logits(
1030
+ self,
1031
+ hidden_states: torch.Tensor,
1032
+ ) -> torch.Tensor | None:
1033
+ return self.language_model.compute_logits(hidden_states)
1034
+
1035
+ def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
1036
+ loader = AutoWeightsLoader(self)
1037
+ return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
1038
+
1039
+ def get_mm_mapping(self) -> MultiModelKeys:
1040
+ """
1041
+ Get the module prefix in multimodal models
1042
+ """
1043
+ return MultiModelKeys.from_string_field(
1044
+ language_model="language_model.model",
1045
+ connector="visual.perceive",
1046
+ tower_model="visual",
1047
+ )
vllm/model_executor/models/registry.py
+6
-6
@@ -131,6 +131,8 @@ _TEXT_GENERATION_MODELS = {
131
"GraniteSWAForCausalLM": ("granite", "GraniteForCausalLM"),
132
"GritLM": ("gritlm", "GritLM"),
133
"HrmTextForCausalLM": ("hrm_text", "HrmTextForCausalLM"),
134
+ "HunYuanMoEV1ForCausalLM": ("hunyuan_v1", "HunYuanMoEV1ForCausalLM"),
135
+ "HunYuanDenseV1ForCausalLM": ("hunyuan_v1", "HunYuanDenseV1ForCausalLM"),
136
"HYV3ForCausalLM": ("hy_v3", "HYV3ForCausalLM"),
137
"HCXVisionForCausalLM": ("hyperclovax_vision", "HCXVisionForCausalLM"),
138
"HCXVisionV2ForCausalLM": ("hyperclovax_vision_v2", "HCXVisionV2ForCausalLM"),
@@ -439,6 +441,10 @@ _MULTIMODAL_MODELS = {
441
"Granite4VisionForConditionalGeneration",
442
),
443
"H2OVLChatModel": ("h2ovl", "H2OVLChatModel"),
444
+ "HunYuanVLForConditionalGeneration": (
445
+ "hunyuan_vision",
446
+ "HunYuanVLForConditionalGeneration",
447
+ ),
448
"InternVLChatModel": ("internvl", "InternVLChatModel"),
449
"InternS1ForConditionalGeneration": (
450
"interns1",
@@ -694,8 +700,6 @@ _SPECULATIVE_DECODING_MODELS = {
700
_TRANSFORMERS_SUPPORTED_MODELS = {
701
# Text generation models
702
"GPTBigCodeForCausalLM": ("transformers", "TransformersForCausalLM"),
697
- "HunYuanDenseV1ForCausalLM": ("transformers", "TransformersForCausalLM"),
698
- "HunYuanMoEV1ForCausalLM": ("transformers", "TransformersMoEForCausalLM"),
703
"OlmoForCausalLM": ("transformers", "TransformersForCausalLM"),
704
"Olmo2ForCausalLM": ("transformers", "TransformersForCausalLM"),
705
"SmolLM3ForCausalLM": ("transformers", "TransformersForCausalLM"),
@@ -706,10 +710,6 @@ _TRANSFORMERS_SUPPORTED_MODELS = {
710
"transformers",
711
"TransformersMultiModalForCausalLM",
712
),
709
- "HunYuanVLForConditionalGeneration": (
710
- "transformers",
711
- "TransformersMultiModalForCausalLM",
712
- ),
713
"VibeVoiceAsrForConditionalGeneration": (
714
"transformers",
715
"TransformersMultiModalForCausalLM",
vllm/transformers_utils/config.py
+1
@@ -95,6 +95,7 @@ _CONFIG_REGISTRY: dict[str, type[PretrainedConfig]] = LazyConfigDict(
95
granite4_vision="Granite4VisionConfig",
96
hyperclovax="HyperCLOVAXConfig",
97
hyperclovax_vlm="HCXVisionConfig",
98
+ hunyuan_vl="HunYuanVLConfig",
99
hy_v3="HYV3Config",
100
isaac="IsaacConfig",
101
kimi_k2="DeepseekV3Config", # Kimi K2 uses same architecture as DeepSeek V3
vllm/transformers_utils/configs/__init__.py
+6
@@ -44,6 +44,9 @@ _CLASS_TO_MODULE: dict[str, str] = {
44
"FunAudioChatConfig": "vllm.transformers_utils.configs.funaudiochat",
45
"FunAudioChatAudioEncoderConfig": "vllm.transformers_utils.configs.funaudiochat",
46
"Granite4VisionConfig": "vllm.transformers_utils.configs.granite4_vision",
47
+ "HunYuanVLConfig": "vllm.transformers_utils.configs.hunyuan_vl",
48
+ "HunYuanVLTextConfig": "vllm.transformers_utils.configs.hunyuan_vl",
49
+ "HunYuanVLVisionConfig": "vllm.transformers_utils.configs.hunyuan_vl",
50
"HCXVisionConfig": "vllm.transformers_utils.configs.hyperclovax",
51
"HYV3Config": "vllm.transformers_utils.configs.hy_v3",
52
"HyperCLOVAXConfig": "vllm.transformers_utils.configs.hyperclovax",
@@ -138,6 +141,9 @@ __all__ = [
141
"FunAudioChatConfig",
142
"FunAudioChatAudioEncoderConfig",
143
"Granite4VisionConfig",
144
+ "HunYuanVLConfig",
145
+ "HunYuanVLTextConfig",
146
+ "HunYuanVLVisionConfig",
147
"HCXVisionConfig",
148
"HYV3Config",
149
"HyperCLOVAXConfig",
vllm/transformers_utils/configs/hunyuan_vl.py
new
+281
@@ -0,0 +1,281 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+
4
+# adapted from https://github.com/ManaEstras/transformers/blob/v4.57.1.hyvl/src/transformers/models/hunyuan_vl/configuration_hunyuan_vl.py
5
+
6
+from transformers import PretrainedConfig
7
+
8
+
9
+class HunYuanVLVisionConfig(PretrainedConfig):
10
+ model_type = "hunyuan_vl"
11
+ base_config_key = "vision_config"
12
+
13
+ def __init__(
14
+ self,
15
+ hidden_act="gelu",
16
+ hidden_size=1152,
17
+ intermediate_size=4304,
18
+ interpolate_mode="bilinear",
19
+ rms_norm_eps=1e-05,
20
+ learnable_mlp_pooling_size=0,
21
+ num_attention_heads=16,
22
+ num_key_value_heads=None,
23
+ num_channels=3,
24
+ num_hidden_layers=27,
25
+ out_hidden_size=4096,
26
+ patch_size=16,
27
+ remove_prenorm=True,
28
+ spatial_merge_size=2,
29
+ temporal_patch_size=1,
30
+ resize_resolution=2048,
31
+ img_max_token_num=4096,
32
+ max_image_size=2048,
33
+ video_max_image_size=768,
34
+ video_min_image_size=256,
35
+ min_image_size=512,
36
+ anyres_vit_max_image_size=2048,
37
+ max_vit_seq_len=16384,
38
+ text_hidden_size=3072,
39
+ **kwargs,
40
+ ):
41
+ super().__init__(**kwargs)
42
+
43
+ self.hidden_act = hidden_act
44
+ self.hidden_size = hidden_size
45
+ self.intermediate_size = intermediate_size
46
+ self.interpolate_mode = interpolate_mode
47
+ self.learnable_mlp_pooling_size = learnable_mlp_pooling_size
48
+ self.num_attention_heads = num_attention_heads
49
+ if not num_key_value_heads:
50
+ self.num_key_value_heads = num_attention_heads
51
+ else:
52
+ self.num_key_value_heads = num_key_value_heads
53
+ self.num_channels = num_channels
54
+ self.num_hidden_layers = num_hidden_layers
55
+ self.out_hidden_size = out_hidden_size
56
+ self.patch_size = patch_size
57
+ self.remove_prenorm = remove_prenorm
58
+ self.spatial_merge_size = spatial_merge_size
59
+ self.temporal_patch_size = temporal_patch_size
60
+ self.rms_norm_eps = rms_norm_eps
61
+
62
+ self.resize_resolution = resize_resolution
63
+ self.img_max_token_num = img_max_token_num
64
+ self.max_image_size = max_image_size
65
+ self.min_image_size = min_image_size
66
+ self.video_max_image_size = video_max_image_size
67
+ self.video_min_image_size = video_min_image_size
68
+ self.anyres_vit_max_image_size = anyres_vit_max_image_size
69
+ self.max_vit_seq_len = max_vit_seq_len
70
+ self.text_hidden_size = text_hidden_size
71
+
72
+
73
+class HunYuanVLTextConfig(PretrainedConfig):
74
+ r"""
75
+ This is the configuration class to store the configuration of a [`HunYuanVLTextConfig`]. It is used to instantiate an
76
+ HunYuan model according to the specified arguments, defining the model architecture. Instantiating a configuration
77
+ with the defaults will yield a similar configuration to that of the HunYuan-7B.
78
+ Hunyuan-7B-Instruct [tencent/Hunyuan-7B-Instruct](https://huggingface.co/tencent/Hunyuan-7B-Instruct).
79
+
80
+ Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the
81
+ documentation from [`PretrainedConfig`] for more information.
82
+
83
+
84
+ Args:
85
+ vocab_size (`int`, *optional*, defaults to 290943):
86
+ Vocabulary size of the HunYuan model. Defines the number of different tokens that can be represented by the
87
+ `inputs_ids` passed when calling [`HunYuanVLTextConfig`]
88
+ hidden_size (`int`, *optional*, defaults to 4096):
89
+ Dimension of the hidden representations.
90
+ intermediate_size (`int`, *optional*, defaults to 11008):
91
+ Dimension of the MLP representations or shared MLP representations.
92
+ num_hidden_layers (`int`, *optional*, defaults to 32):
93
+ Number of hidden layers in the Transformer decoder.
94
+ num_attention_heads (`int`, *optional*, defaults to 32):
95
+ Number of attention heads for each attention layer in the Transformer decoder.
96
+ num_key_value_heads (`int`, *optional*):
97
+ This is the number of key_value heads that should be used to implement Grouped Query Attention. If
98
+ `num_key_value_heads=num_attention_heads`, the model will use Multi Head Attention (MHA), if
99
+ `num_key_value_heads=1 the model will use Multi Query Attention (MQA) otherwise GQA is used. When
100
+ converting a multi-head checkpoint to a GQA checkpoint, each group key and value head should be constructed
101
+ by meanpooling all the original heads within that group. For more details checkout [this
102
+ paper](https://huggingface.co/papers/2305.13245). If it is not specified, will default to
103
+ `num_attention_heads`.
104
+ hidden_act (`str` or `function`, *optional*, defaults to `"silu"`):
105
+ The non-linear activation function (function or string) in the decoder.
106
+ max_position_embeddings (`int`, *optional*, defaults to 2048):
107
+ The maximum sequence length that this model might ever be used with.
108
+ initializer_range (`float`, *optional*, defaults to 0.02):
109
+ The standard deviation of the truncated_normal_initializer for initializing all weight matrices.
110
+ rms_norm_eps (`float`, *optional*, defaults to 1e-05):
111
+ The epsilon used by the rms normalization layers.
112
+ use_cache (`bool`, *optional*, defaults to `True`):
113
+ Whether or not the model should return the last key/values attentions (not used by all models). Only
114
+ relevant if `config.is_decoder=True`.
115
+ pad_token_id (`int`, *optional*, defaults to 0):
116
+ Padding token id.
117
+ bos_token_id (`int`, *optional*, defaults to 1):
118
+ Beginning of stream token id.
119
+ eos_token_id (`int`, *optional*, defaults to 2):
120
+ End of stream token id.
121
+ eod_token_id (int, *optional*, defaults to 3):
122
+ Token ID representing the end-of-document marker. Used to indicate the termination of a text sequence.
123
+ Example: In multi-document processing, this token helps the model distinguish between separate documents.
124
+ pretraining_tp (`int`, *optional*, defaults to 1):
125
+ Experimental feature. Tensor parallelism rank used during pretraining. Please refer to [this
126
+ document](https://huggingface.co/docs/transformers/parallelism) to understand more about it. This value is
127
+ necessary to ensure exact reproducibility of the pretraining results. Please refer to [this
128
+ issue](https://github.com/pytorch/pytorch/issues/76232).
129
+ tie_word_embeddings (`bool`, *optional*, defaults to `False`):
130
+ Whether to tie weight embeddings
131
+ rope_theta (`float`, *optional*, defaults to 10000.0):
132
+ The base period of the RoPE embeddings.
133
+ rope_scaling (`Dict`, *optional*):
134
+ Dictionary containing the scaling configuration for the RoPE embeddings. Currently supports two scaling
135
+ strategies: linear and dynamic. Their scaling factor must be a float greater than 1. The expected format is
136
+ `{"type": strategy name, "factor": scaling factor}`. When using this flag, don't update
137
+ `max_position_embeddings` to the expected new maximum. See the following thread for more information on how
138
+ these scaling strategies behave:
139
+ https://www.reddit.com/r/LocalLLaMA/comments/14mrgpr/dynamically_scaled_rope_further_increases/. This is an
140
+ experimental feature, subject to breaking API changes in future versions.
141
+ attention_bias (`bool`, defaults to `False`, *optional*, defaults to `False`):
142
+ Whether to use a bias in the query, key, value and output projection layers during self-attention.
143
+ attention_dropout (`float`, *optional*, defaults to 0.0):
144
+ The dropout ratio for the attention probabilities.
145
+ head_dim (`int`, *optional*, defaults to 128):
146
+ The attention head dimension.
147
+ """ # noqa: E501
148
+
149
+ model_type = "hunyuan_vl_text"
150
+ keys_to_ignore_at_inference = ["past_key_values"]
151
+
152
+ def __init__(
153
+ self,
154
+ vocab_size=290943,
155
+ hidden_size=4096,
156
+ intermediate_size: int = 11008,
157
+ num_hidden_layers=32,
158
+ num_attention_heads=32,
159
+ num_key_value_heads=None,
160
+ hidden_act="silu",
161
+ max_position_embeddings=2048,
162
+ initializer_range=0.02,
163
+ rms_norm_eps=1e-5,
164
+ use_cache=True,
165
+ pad_token_id=0,
166
+ bos_token_id=1,
167
+ eos_token_id=2,
168
+ eod_token_id=3,
169
+ pretraining_tp=1,
170
+ tie_word_embeddings=False,
171
+ rope_theta=10000.0,
172
+ rope_scaling=None,
173
+ attention_bias=False,
174
+ attention_dropout=0.0,
175
+ head_dim=None,
176
+ **kwargs,
177
+ ):
178
+ self.vocab_size = vocab_size
179
+ self.max_position_embeddings = max_position_embeddings
180
+ self.hidden_size = hidden_size
181
+ self.intermediate_size = intermediate_size
182
+ self.num_hidden_layers = num_hidden_layers
183
+ self.num_attention_heads = num_attention_heads
184
+ self.head_dim = head_dim
185
+ # for backward compatibility
186
+ if num_key_value_heads is None:
187
+ num_key_value_heads = num_attention_heads
188
+
189
+ self.num_key_value_heads = num_key_value_heads
190
+ self.hidden_act = hidden_act
191
+ self.initializer_range = initializer_range
192
+ self.rms_norm_eps = rms_norm_eps
193
+ self.pretraining_tp = pretraining_tp
194
+ self.use_cache = use_cache
195
+ self.rope_theta = rope_theta
196
+ self.rope_scaling = rope_scaling
197
+ self.attention_bias = attention_bias
198
+ self.attention_dropout = attention_dropout
199
+
200
+ super().__init__(
201
+ pad_token_id=pad_token_id,
202
+ bos_token_id=bos_token_id,
203
+ eos_token_id=eos_token_id,
204
+ tie_word_embeddings=tie_word_embeddings,
205
+ **kwargs,
206
+ )
207
+
208
+
209
+class HunYuanVLConfig(PretrainedConfig):
210
+ model_type = "hunyuan_vl"
211
+ sub_configs = {
212
+ "vision_config": HunYuanVLVisionConfig,
213
+ "text_config": HunYuanVLTextConfig,
214
+ }
215
+ keys_to_ignore_at_inference = ["past_key_values"]
216
+
217
+ def __init__(
218
+ self,
219
+ text_config=None,
220
+ vision_config=None,
221
+ im_start_id=120118,
222
+ im_end_id=120119,
223
+ image_token_id=120120,
224
+ im_newline_id=120121,
225
+ video_start_id=120122,
226
+ video_end_id=120123,
227
+ **kwargs,
228
+ ):
229
+ # We need to init super() here so that it does not reset values
230
+ # that are in text config to the BaseClass defaults. The Base
231
+ # config has many text related defaults and not all defaults are
232
+ # same as for `HunYuanVLTextConfig`.
233
+ super().__init__(**kwargs)
234
+
235
+ if isinstance(vision_config, dict):
236
+ self.vision_config = self.sub_configs["vision_config"](**vision_config)
237
+ elif vision_config is None:
238
+ self.vision_config = self.sub_configs["vision_config"]()
239
+
240
+ if isinstance(text_config, dict):
241
+ self.text_config = self.sub_configs["text_config"](**text_config)
242
+ elif text_config is None:
243
+ # For BC use all kwargs to init `TextConfig`
244
+ self.text_config = self.sub_configs["text_config"](**kwargs)
245
+
246
+ self.image_token_id = image_token_id
247
+ self.im_start_id = im_start_id
248
+ self.im_end_id = im_end_id
249
+ self.im_newline_id = im_newline_id
250
+ self.video_start_id = video_start_id
251
+ self.video_end_id = video_end_id
252
+
253
+ self.vision_config.text_hidden_size = self.text_config.hidden_size
254
+
255
+ # Attention implementation to use. It sets it recursively on sub-configs
256
+ # so we call it again in the end.
257
+ self._attn_implementation = kwargs.pop("attn_implementation", None)
258
+
259
+ def __setattr__(self, key, value):
260
+ if (
261
+ (text_config := super().__getattribute__("__dict__").get("text_config"))
262
+ is not None
263
+ and key not in ["dtype", "_attn_implementation_internal"]
264
+ and key in text_config.__dict__
265
+ ):
266
+ setattr(text_config, key, value)
267
+ else:
268
+ super().__setattr__(key, value)
269
+
270
+ def __getattribute__(self, key):
271
+ if "text_config" in super().__getattribute__("__dict__") and key not in [
272
+ "_name_or_path",
273
+ "model_type",
274
+ "dtype",
275
+ "_attn_implementation_internal",
276
+ ]:
277
+ text_config = super().__getattribute__("text_config")
278
+ if key in text_config.__dict__:
279
+ return getattr(text_config, key)
280
+
281
+ return super().__getattribute__(key)