From 92957809e473e73f12c5eb9622936d177cfa3f93 Mon Sep 17 00:00:00 2001 From: Linxiushen <1787979356@qq.com> Date: Mon, 21 Sep 2026 14:49:07 +0800 Subject: [PATCH] fix: import GLM5 fused experts under transformers>=5.13 transformers 5.13 renamed GlmMoeDsaNaiveMoe to GlmMoeDsaExperts (same class body). angelslim/models/llm/glm5_1.py imported the old name unconditionally and angelslim/models/llm/__init__.py imports GLM5_1 eagerly, so with any transformers release from 5.13 up to the declared <6.0 ceiling every CLI and `from angelslim import Engine` failed at import time, even for non-GLM models. Fall back to the new name when the old one is missing, and add a test that the adapter imports and that GlmExpertsWithLinear reproduces the fused experts module's forward on the installed transformers. Fixes #382 --- angelslim/models/llm/glm5_1.py | 10 ++++- tests/test_glm5_experts_compat.py | 71 +++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 tests/test_glm5_experts_compat.py diff --git a/angelslim/models/llm/glm5_1.py b/angelslim/models/llm/glm5_1.py index d8eca745..29c4ef95 100644 --- a/angelslim/models/llm/glm5_1.py +++ b/angelslim/models/llm/glm5_1.py @@ -38,7 +38,15 @@ import torch.distributed as dist import torch.nn as nn from transformers import AutoConfig, AutoModelForCausalLM, AutoTokenizer -from transformers.models.glm_moe_dsa.modeling_glm_moe_dsa import GlmMoeDsaNaiveMoe + +try: + from transformers.models.glm_moe_dsa.modeling_glm_moe_dsa import GlmMoeDsaNaiveMoe +except ImportError: + # transformers>=5.13 renamed the fused-experts module to GlmMoeDsaExperts; + # the class body is unchanged (3-D gate_up_proj / down_proj, same forward). + from transformers.models.glm_moe_dsa.modeling_glm_moe_dsa import ( + GlmMoeDsaExperts as GlmMoeDsaNaiveMoe, + ) from ...compressor.quant.core import PTQSaveVllmHF from ...utils.utils import find_parent_layer_and_sub_name, print_info diff --git a/tests/test_glm5_experts_compat.py b/tests/test_glm5_experts_compat.py new file mode 100644 index 00000000..1f2c0cab --- /dev/null +++ b/tests/test_glm5_experts_compat.py @@ -0,0 +1,71 @@ +# Copyright 2025 Tencent Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""GLM5 adapter must import and wrap the fused experts module on every +transformers release in the supported range: the class was renamed from +``GlmMoeDsaNaiveMoe`` to ``GlmMoeDsaExperts`` in transformers 5.13.""" + +import importlib + +import pytest +import torch +from transformers.models.glm_moe_dsa import modeling_glm_moe_dsa + +from angelslim.models.llm import glm5_1 + + +def _fused_experts_class(): + return getattr( + modeling_glm_moe_dsa, + "GlmMoeDsaNaiveMoe", + getattr(modeling_glm_moe_dsa, "GlmMoeDsaExperts", None), + ) + + +def test_glm5_module_imports_and_resolves_fused_experts_class(): + assert importlib.import_module("angelslim.models.llm") is not None + assert glm5_1.GlmMoeDsaNaiveMoe is _fused_experts_class() + + +def test_experts_with_linear_matches_fused_experts_forward(): + from transformers.models.glm_moe_dsa.configuration_glm_moe_dsa import ( + GlmMoeDsaConfig, + ) + + torch.manual_seed(0) + config = GlmMoeDsaConfig( + hidden_size=8, moe_intermediate_size=6, num_local_experts=4, hidden_act="silu" + ) + fused = _fused_experts_class()(config) + with torch.no_grad(): + fused.gate_up_proj.normal_() + fused.down_proj.normal_() + assert glm5_1._is_glm_naive_moe(fused) + + linearized = glm5_1.GlmExpertsWithLinear(fused) + + tokens, top_k = 5, 2 + hidden_states = torch.randn(tokens, config.hidden_size) + top_k_index = torch.stack( + [torch.randperm(config.num_local_experts)[:top_k] for _ in range(tokens)] + ) + top_k_weights = torch.rand(tokens, top_k) + with torch.no_grad(): + expected = fused(hidden_states, top_k_index, top_k_weights) + actual = linearized(hidden_states, top_k_index, top_k_weights) + torch.testing.assert_close(actual, expected) + + +if __name__ == "__main__": + pytest.main([__file__])