Skip to content

Fix eager experts for DeepSeekV2 - #43288

Merged
vasqu merged 4 commits into
mainfrom
fix_deepseek_v2_experts
Jan 15, 2026
Merged

Fix eager experts for DeepSeekV2#43288
vasqu merged 4 commits into
mainfrom
fix_deepseek_v2_experts

Conversation

@Rocketknight1

Copy link
Copy Markdown
Member

The eager expert computation for DeepSeekV2 inherited from Qwen2MoE, but they pass expert weights differently. Qwen2MoE selects experts before forward(), it passes top_k_weights with shape (num_tokens, num_top_k). DeepSeekV2 does not, so top_k_weights has shape (num_tokens, num_experts). The result is that DeepSeekV2 selected incorrect experts when _experts_implementation == "eager" and the output was garbage.

This PR overrides the forward() method for DeepSeek so we select the right experts!

cc @IlyasMoutawwakil, fixes #43224

@Rocketknight1
Rocketknight1 marked this pull request as ready for review January 14, 2026 14:50
@HuggingFaceDocBuilderDev

Copy link
Copy Markdown

The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update.

@IlyasMoutawwakil

Copy link
Copy Markdown
Member

thanks @Rocketknight1 i was surprised as well when writing batched_mm and grouped_mm that top_k_weights can have two possible shapes while the eager path seemed very standardized across MoEs. so i added support for both shapes without touching the eager path (assuming that it works fine somehow 😅).

@Rocketknight1

Copy link
Copy Markdown
Member Author

Yeah, I'm not sure how this ever worked! I wonder how long this bug has existed for

@IlyasMoutawwakil

Copy link
Copy Markdown
Member

not for long, i see it was introduced in #42456

@Rocketknight1

Copy link
Copy Markdown
Member Author

cc @ydshieh more flaky tests here, I think possibly Hub issues again?

@Rocketknight1

Copy link
Copy Markdown
Member Author

run-slow: deepseek_v2

@github-actions

Copy link
Copy Markdown
Contributor

This comment contains run-slow, running the specified jobs:

models: ["models/deepseek_v2"]
quantizations: []

@github-actions

Copy link
Copy Markdown
Contributor

CI Results

Workflow Run ⚙️

✅ No failing test specific to this PR 🎉 !

@Rocketknight1

Copy link
Copy Markdown
Member Author

@IlyasMoutawwakil tests are green! Can you approve it so I can merge?

super().__init__(config)
self.num_experts = config.n_routed_experts

def forward(

@vasqu vasqu Jan 14, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I have to say that this is likely the wrong fix and will make things incompatible with things like fp8, we should change the gates instead.

Essentially, this scatter changes the shapes

topk_weight = torch.zeros_like(router_logits).scatter_(1, topk_idx, topk_weight)

We should simply remove that line and everything else should be resolved then (without these changes here)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch ! standardizing the router's outputs would definitely make things easier

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, agreed! I wasn't too familiar with the code and your fix seems like a better one

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand how the change breaks FP8 compatibility, though? If you look at the modeling file, it only updates one slice

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the weights are exchanged as is and the forward needs to be overwritten --> it has to expect the same input as the base version in the modeling code. FP8 currently assumes the version where we do not use the scatter

Reference:

# We follow the mixtral "eager" moe implementation at
# https://github.com/huggingface/transformers/blob/457048fbfdba9a7dee8bd03328c62f49e57b95f9/src/transformers/models/mixtral/modular_mixtral.py#L148
# The core changes in this FP8 version should only relate to how we call the linear projections
def forward(
self,
hidden_states: torch.Tensor,
top_k_index: torch.Tensor,
top_k_weights: torch.Tensor,
) -> torch.Tensor:
final_hidden_states = torch.zeros_like(hidden_states)
with torch.no_grad():
expert_mask = torch.nn.functional.one_hot(top_k_index, num_classes=self.num_experts)
expert_mask = expert_mask.permute(2, 1, 0)
expert_hit = torch.greater(expert_mask.sum(dim=(-1, -2)), 0).nonzero()
for expert_idx in expert_hit:
expert_idx = expert_idx[0]
if expert_idx == self.num_experts:
continue
top_k_pos, token_idx = torch.where(expert_mask[expert_idx])
current_state = hidden_states[token_idx]
gate, up = self.linear(
current_state, self.gate_up_proj[expert_idx], self.gate_up_proj_scale_inv[expert_idx]
).chunk(2, dim=-1)
current_hidden_states = self.act_fn(gate) * up
current_hidden_states = self.linear(
current_hidden_states, self.down_proj[expert_idx], self.down_proj_scale_inv[expert_idx]
)
routing_weights = top_k_weights[token_idx, top_k_pos, None]
current_hidden_states = current_hidden_states * routing_weights.to(current_hidden_states.dtype)
final_hidden_states.index_add_(0, token_idx, current_hidden_states.to(final_hidden_states.dtype))
return final_hidden_states

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so with this deepseekv2 will have its top_k_weights with shape (num_tokens, num_top_k) ? like mixtral, qwen moe, etc

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, exactly

@github-actions

Copy link
Copy Markdown
Contributor

[For maintainers] Suggested jobs to run (before merge)

run-slow: deepseek_v2

@Rocketknight1

Copy link
Copy Markdown
Member Author

run-slow: deepseek_v2

@github-actions

Copy link
Copy Markdown
Contributor

This comment contains run-slow, running the specified jobs:

models: ["models/deepseek_v2"]
quantizations: []

@Rocketknight1

Copy link
Copy Markdown
Member Author

@vasqu confirmed in local testing that your fix also recovers the correct output!

@github-actions

Copy link
Copy Markdown
Contributor

CI Results

Workflow Run ⚙️

✅ No failing test specific to this PR 🎉 !

@vasqu vasqu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you :)

@IlyasMoutawwakil IlyasMoutawwakil left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM ! thanks !

@vasqu
vasqu merged commit 396caf5 into main Jan 15, 2026
21 checks passed
@vasqu
vasqu deleted the fix_deepseek_v2_experts branch January 15, 2026 15:48
@IlyasMoutawwakil IlyasMoutawwakil mentioned this pull request Jan 23, 2026
5 tasks
SangbumChoi pushed a commit to SangbumChoi/transformers that referenced this pull request Jan 23, 2026
* Fix eager experts for DeepSeekV2

* Use @vasqu's fix instead

* Propagate modular
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DeepSeek-V2-Lite-Chat repetitive generation when _experts_implementation="eager"

4 participants