[Model] Add K-EXAONE-2.0-750B-A37B (#50524)

Signed-off-by: lkm2835 <lkm2835@gmail.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>

Kyungmin Lee committed Aug 3, 2026 at 22:33 UTC 9ae11a6b895dea5b2275e0b6ad96327af67d3b0c
6 files changed +161 -11
docs/models/supported_models.md
+1 -1
@@ -364,7 +364,7 @@ th {
364 | `Ernie4_5ForCausalLM` | Ernie4.5 | `baidu/ERNIE-4.5-0.3B-PT`, etc. | ✅︎ | ✅︎ |
365 | `Ernie4_5_MoeForCausalLM` | Ernie4.5MoE | `baidu/ERNIE-4.5-21B-A3B-PT`, `baidu/ERNIE-4.5-300B-A47B-PT`, etc. | ✅︎ | ✅︎ |
366 | `ExaoneForCausalLM` | EXAONE-3 | `LGAI-EXAONE/EXAONE-3.0-7.8B-Instruct`, etc. | ✅︎ | ✅︎ |
367 -| `ExaoneMoEForCausalLM` | K-EXAONE | `LGAI-EXAONE/K-EXAONE-236B-A23B`, etc. | | |
367 +| `ExaoneMoeForCausalLM` | K-EXAONE, K-EXAONE-2 | `LGAI-EXAONE/K-EXAONE-236B-A23B`, `LGAI-EXAONE/K-EXAONE-2.0-750B-A37B`, etc. | | |
368 | `Exaone4ForCausalLM` | EXAONE-4 | `LGAI-EXAONE/EXAONE-4.0-32B`, etc. | ✅︎ | ✅︎ |
369 | `Fairseq2LlamaForCausalLM` | Llama (fairseq2 format) | `mgleize/fairseq2-dummy-Llama-3.2-1B`, etc. | ✅︎ | ✅︎ |
370 | `FalconForCausalLM` | Falcon | `tiiuae/falcon-7b`, `tiiuae/falcon-40b`, `tiiuae/falcon-rw-7b`, etc. | | ✅︎ |
tests/models/registry.py
+1 -1
@@ -267,7 +267,7 @@ _TEXT_GENERATION_EXAMPLE_MODELS = {
267 "LGAI-EXAONE/EXAONE-3.0-7.8B-Instruct", trust_remote_code=True
268 ),
269 "Exaone4ForCausalLM": _HfExamplesInfo("LGAI-EXAONE/EXAONE-4.0-32B"),
270 - "ExaoneMoEForCausalLM": _HfExamplesInfo(
270 + "ExaoneMoeForCausalLM": _HfExamplesInfo(
271 "LGAI-EXAONE/K-EXAONE-236B-A23B", min_transformers_version="5.1.0"
272 ),
273 "Fairseq2LlamaForCausalLM": _HfExamplesInfo("mgleize/fairseq2-dummy-Llama-3.2-1B"),
vllm/model_executor/models/exaone4.py
+5 -2
@@ -31,7 +31,7 @@ from transformers import Exaone4Config
31 from vllm.compilation.decorators import support_torch_compile
32 from vllm.config import CacheConfig, VllmConfig
33 from vllm.distributed import get_pp_group, get_tensor_model_parallel_world_size
34 -from vllm.model_executor.layers.activation import SiluAndMul
34 +from vllm.model_executor.layers.activation import SiluAndMul, SiluAndMulWithClamp
35 from vllm.model_executor.layers.attention import Attention
36 from vllm.model_executor.layers.layernorm import RMSNorm
37 from vllm.model_executor.layers.linear import (
@@ -70,6 +70,7 @@ class Exaone4GatedMLP(nn.Module):
70 quant_config: QuantizationConfig | None = None,
71 reduce_results: bool = True,
72 bias: bool = False,
73 + swiglu_limit: float | None = None,
74 prefix: str = "",
75 use_data_parallel: bool = False,
76 ) -> None:
@@ -95,7 +96,9 @@ class Exaone4GatedMLP(nn.Module):
96 raise ValueError(
97 f"Unsupported activation: {hidden_act}. Only silu is supported for now."
98 )
98 - self.act_fn = SiluAndMul()
99 + self.act_fn = (
100 + SiluAndMul() if swiglu_limit is None else SiluAndMulWithClamp(swiglu_limit)
101 + )
102
103 def forward(self, x):
104 gate_up, _ = self.gate_up_proj(x)
vllm/model_executor/models/exaone_moe.py
+152 -5
@@ -29,19 +29,25 @@ from vllm.distributed import (
29 get_pp_group,
30 get_tensor_model_parallel_world_size,
31 )
32 +from vllm.model_executor.layers.attention import Attention
33 from vllm.model_executor.layers.fused_moe import FusedMoEFactory
34 from vllm.model_executor.layers.layernorm import RMSNorm
34 -from vllm.model_executor.layers.linear import ReplicatedLinear
35 +from vllm.model_executor.layers.linear import (
36 + QKVParallelLinear,
37 + ReplicatedLinear,
38 + RowParallelLinear,
39 +)
40 from vllm.model_executor.layers.logits_processor import LogitsProcessor
41 from vllm.model_executor.layers.quantization import QuantizationConfig
42 +from vllm.model_executor.layers.rotary_embedding import get_rope
43 from vllm.model_executor.layers.vocab_parallel_embedding import (
44 DEFAULT_VOCAB_PADDING_SIZE,
45 ParallelLMHead,
46 VocabParallelEmbedding,
47 )
48 from vllm.sequence import IntermediateTensors
49 +from vllm.transformers_utils.config import set_default_rope_theta
50
44 -from .exaone4 import Exaone4Attention as ExaoneMoeAttention
51 from .exaone4 import Exaone4GatedMLP as ExaoneMoeGatedMLP
52 from .interfaces import SupportsLoRA, SupportsPP
53 from .utils import (
@@ -59,6 +65,7 @@ class ExaoneMoe(nn.Module):
65 def __init__(
66 self,
67 config: PretrainedConfig,
68 + swiglu_limit: float | None = None,
69 quant_config: QuantizationConfig | None = None,
70 prefix: str = "",
71 enable_eplb: bool = False,
@@ -119,6 +126,7 @@ class ExaoneMoe(nn.Module):
126 hidden_act=config.hidden_act,
127 quant_config=quant_config,
128 reduce_results=False,
129 + swiglu_limit=swiglu_limit,
130 prefix=f"{prefix}.shared_experts",
131 )
132 else:
@@ -133,6 +141,8 @@ class ExaoneMoe(nn.Module):
141 intermediate_size=config.moe_intermediate_size,
142 renormalize=config.norm_topk_prob,
143 quant_config=quant_config,
144 + activation="silu",
145 + swiglu_limit=swiglu_limit,
146 use_grouped_topk=True,
147 num_expert_group=config.n_group,
148 topk_group=config.topk_group,
@@ -157,13 +167,138 @@ class ExaoneMoe(nn.Module):
167 return final_hidden_states.view(orig_shape)
168
169
170 +class ExaoneMoeAttention(nn.Module):
171 + def __init__(
172 + self,
173 + config,
174 + hidden_size: int,
175 + num_heads: int,
176 + num_kv_heads: int,
177 + max_position_embeddings: int = 8192,
178 + quant_config: QuantizationConfig | None = None,
179 + bias: bool = False,
180 + cache_config: CacheConfig | None = None,
181 + prefix: str = "",
182 + is_mtp: bool = False,
183 + ) -> None:
184 + super().__init__()
185 + self.hidden_size = hidden_size
186 + tp_size = get_tensor_model_parallel_world_size()
187 + self.total_num_heads = num_heads
188 + assert self.total_num_heads % tp_size == 0
189 + self.num_heads = self.total_num_heads // tp_size
190 + self.total_num_kv_heads = num_kv_heads
191 + if self.total_num_kv_heads >= tp_size:
192 + # Number of KV heads is greater than TP size, so we partition
193 + # the KV heads across multiple tensor parallel GPUs.
194 + assert self.total_num_kv_heads % tp_size == 0
195 + else:
196 + # Number of KV heads is less than TP size, so we replicate
197 + # the KV heads across multiple tensor parallel GPUs.
198 + assert tp_size % self.total_num_kv_heads == 0
199 + self.num_kv_heads = max(1, self.total_num_kv_heads // tp_size)
200 + # MistralConfig has an optional head_dim introduced by Mistral-Nemo
201 + self.head_dim = getattr(config, "head_dim", None)
202 + if self.head_dim is None:
203 + self.head_dim = self.hidden_size // self.total_num_heads
204 + self.q_size = self.num_heads * self.head_dim
205 + self.kv_size = self.num_kv_heads * self.head_dim
206 + self.scaling = self.head_dim**-0.5
207 + self.max_position_embeddings = max_position_embeddings
208 +
209 + self.qkv_proj = QKVParallelLinear(
210 + hidden_size=hidden_size,
211 + head_size=self.head_dim,
212 + total_num_heads=self.total_num_heads,
213 + total_num_kv_heads=self.total_num_kv_heads,
214 + bias=bias,
215 + quant_config=quant_config,
216 + prefix=f"{prefix}.qkv_proj",
217 + )
218 +
219 + self.o_proj = RowParallelLinear(
220 + input_size=self.total_num_heads * self.head_dim,
221 + output_size=hidden_size,
222 + bias=bias,
223 + quant_config=quant_config,
224 + prefix=f"{prefix}.o_proj",
225 + )
226 +
227 + self.q_norm = RMSNorm(self.head_dim, eps=config.rms_norm_eps)
228 + self.k_norm = RMSNorm(self.head_dim, eps=config.rms_norm_eps)
229 +
230 + is_neox_style = True
231 + if quant_config is not None and quant_config.get_name() == "gguf":
232 + is_neox_style = False
233 +
234 + layer_idx = extract_layer_index(prefix)
235 +
236 + if config.sliding_windows is not None:
237 + self.sliding_window_size = config.sliding_windows[layer_idx]
238 +
239 + if config.layer_types[layer_idx] == "full_attention":
240 + self.sliding_window_size = None
241 +
242 + if is_mtp:
243 + self.sliding_window = (
244 + config.mtp_layer_types[layer_idx] == "sliding_attention"
245 + )
246 + if config.mtp_sliding_windows is not None:
247 + self.sliding_window_size = config.mtp_sliding_windows[layer_idx]
248 +
249 + if config.mtp_layer_types[layer_idx] == "full_attention":
250 + self.sliding_window_size = None
251 +
252 + # apply rotary embeddings to every layer in full attention models
253 + self.apply_rope_all_layers = "sliding_attention" not in config.layer_types
254 +
255 + set_default_rope_theta(config, default_theta=1000000)
256 + self.rotary_emb = get_rope(
257 + self.head_dim,
258 + max_position=max_position_embeddings,
259 + rope_parameters=config.rope_parameters,
260 + is_neox_style=is_neox_style,
261 + )
262 + self.attn = Attention(
263 + self.num_heads,
264 + self.head_dim,
265 + self.scaling,
266 + num_kv_heads=self.num_kv_heads,
267 + cache_config=cache_config,
268 + quant_config=quant_config,
269 + per_layer_sliding_window=self.sliding_window_size,
270 + prefix=f"{prefix}.attn",
271 + )
272 +
273 + def forward(
274 + self,
275 + positions: torch.Tensor,
276 + hidden_states: torch.Tensor,
277 + ) -> torch.Tensor:
278 + qkv, _ = self.qkv_proj(hidden_states)
279 + q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1)
280 +
281 + q = q.unflatten(-1, (self.num_heads, self.head_dim))
282 + q = self.q_norm(q)
283 + q = q.flatten(-2, -1)
284 + k = k.unflatten(-1, (self.num_kv_heads, self.head_dim))
285 + k = self.k_norm(k)
286 + k = k.flatten(-2, -1)
287 +
288 + if self.sliding_window_size or self.apply_rope_all_layers:
289 + q, k = self.rotary_emb(positions, q, k)
290 + attn_output = self.attn(q, k, v)
291 + output, _ = self.o_proj(attn_output)
292 + return output
293 +
294 +
295 class ExaoneMoeDecoderLayer(nn.Module):
296 def __init__(
297 self,
298 config: PretrainedConfig,
299 cache_config: CacheConfig | None = None,
300 quant_config: QuantizationConfig | None = None,
166 - mtp_layer: bool = None,
301 + is_mtp: bool = None,
302 prefix: str = "",
303 ) -> None:
304 super().__init__()
@@ -187,17 +322,29 @@ class ExaoneMoeDecoderLayer(nn.Module):
322 bias=attention_bias,
323 cache_config=cache_config,
324 prefix=f"{prefix}.self_attn",
325 + is_mtp=is_mtp,
326 )
327
192 - if config.is_moe_layer[layer_idx] and not mtp_layer:
328 + swiglu_limits = getattr(config, "swiglu_limits", None)
329 + if swiglu_limits is not None:
330 + swiglu_limit = swiglu_limits[layer_idx]
331 + swiglu_limit = swiglu_limit if swiglu_limit > 0 else None
332 + else:
333 + swiglu_limit = None
334 +
335 + if config.mlp_layer_types[layer_idx] == "sparse" and not is_mtp:
336 self.mlp = ExaoneMoe(
194 - config=config, quant_config=quant_config, prefix=f"{prefix}.mlp"
337 + config=config,
338 + swiglu_limit=swiglu_limit,
339 + quant_config=quant_config,
340 + prefix=f"{prefix}.mlp",
341 )
342 else:
343 self.mlp = ExaoneMoeGatedMLP(
344 hidden_size=self.hidden_size,
345 intermediate_size=config.intermediate_size,
346 hidden_act=config.hidden_act,
347 + swiglu_limit=swiglu_limit,
348 quant_config=quant_config,
349 bias=getattr(config, "mlp_bias", False),
350 prefix=f"{prefix}.mlp",
vllm/model_executor/models/exaone_moe_mtp.py
+1 -1
@@ -81,7 +81,7 @@ class ExaoneMoeMultiTokenPredictor(nn.Module):
81 vllm_config.model_config.hf_config,
82 quant_config=quant_config,
83 prefix=f"{prefix}.layers.{idx}",
84 - mtp_layer=True,
84 + is_mtp=True,
85 )
86 for idx in range(self.num_mtp_layers)
87 )
vllm/model_executor/models/registry.py
+1 -1
@@ -97,7 +97,7 @@ _TEXT_GENERATION_MODELS = {
97 "Ernie4_5_MoeForCausalLM": ("ernie45_moe", "Ernie4_5_MoeForCausalLM"),
98 "ExaoneForCausalLM": ("exaone", "ExaoneForCausalLM"),
99 "Exaone4ForCausalLM": ("exaone4", "Exaone4ForCausalLM"),
100 - "ExaoneMoEForCausalLM": ("exaone_moe", "ExaoneMoeForCausalLM"),
100 + "ExaoneMoeForCausalLM": ("exaone_moe", "ExaoneMoeForCausalLM"),
101 "Fairseq2LlamaForCausalLM": ("fairseq2_llama", "Fairseq2LlamaForCausalLM"),
102 "FalconForCausalLM": ("falcon", "FalconForCausalLM"),
103 "FalconMambaForCausalLM": ("mamba", "MambaForCausalLM"),