feat: support GLM-4.7-Flash (MLA + MoE) on Ascend NPU - #403
Conversation
Documentation build overview
47 files changed ·
|
There was a problem hiding this comment.
Code Review
This pull request introduces support for GLM-4.7-Flash (a model featuring Multi-Head Latent Attention and Mixture of Experts) by adding model configurations, a training run script, and a Megatron bridge implementation with Multi-Token Prediction (MTP) support on Ascend NPUs. The review feedback highlights several critical Bash scripting improvements, including quoting array expansions and wildcards to prevent globbing, replacing an overly aggressive pkill -9 python command, and addressing an undefined ${MTP_ARGS[@]} variable. Additionally, the reviewer points out opportunities to simplify the Python bridge code by removing redundant conditional checks and dead code branches, as the bridge is exclusively used for MLA-based models.
| NHEADS=20 | ||
|
|
||
| MODEL_ARGS=( | ||
| --moe-layer-freq [0]*$N_DENSE_LAYERS+[1]*$N_MOE_LAYERS |
There was a problem hiding this comment.
| ${MODEL_ARGS[@]} \ | ||
| ${CKPT_ARGS[@]} \ | ||
| ${ROLLOUT_ARGS[@]} \ | ||
| ${OPTIMIZER_ARGS[@]} \ | ||
| ${GRPO_ARGS[@]} \ | ||
| ${PERF_ARGS[@]} \ | ||
| ${EVAL_ARGS[@]} \ | ||
| ${VLLM_ARGS[@]} \ | ||
| ${MISC_ARGS[@]} \ | ||
| ${MTP_ARGS[@]} |
There was a problem hiding this comment.
在展开 Bash 数组时如果不使用双引号(例如 ${MODEL_ARGS[@]}),会导致 Shell 对展开后的元素进行单词拆分(word splitting)和路径名扩展(globbing)。这会导致像 --moe-layer-freq [0]*1+[1]*46 这样的元素在当前工作目录下存在匹配文件时被错误展开,从而导致参数解析错误。\n\n请始终使用双引号展开数组:"${ARRAY[@]}"。\n\n另外,${MTP_ARGS[@]} 在此处被引用,但该变量在脚本或引入的模型配置中从未被定义。建议将其移除或进行定义。
| ${MODEL_ARGS[@]} \ | |
| ${CKPT_ARGS[@]} \ | |
| ${ROLLOUT_ARGS[@]} \ | |
| ${OPTIMIZER_ARGS[@]} \ | |
| ${GRPO_ARGS[@]} \ | |
| ${PERF_ARGS[@]} \ | |
| ${EVAL_ARGS[@]} \ | |
| ${VLLM_ARGS[@]} \ | |
| ${MISC_ARGS[@]} \ | |
| ${MTP_ARGS[@]} | |
| "${MODEL_ARGS[@]}" \ | |
| "${CKPT_ARGS[@]}" \ | |
| "${ROLLOUT_ARGS[@]}" \ | |
| "${OPTIMIZER_ARGS[@]}" \ | |
| "${GRPO_ARGS[@]}" \ | |
| "${PERF_ARGS[@]}" \ | |
| "${EVAL_ARGS[@]}" \ | |
| "${VLLM_ARGS[@]}" \ | |
| "${MISC_ARGS[@]}" |
| sleep 3 | ||
| pkill -9 ray | ||
| pkill -9 redis-server | ||
| pkill -9 python |
| is_mla = "q_lora_rank" in provider_kwargs | ||
| if is_mla: | ||
| provider_class = self.PROVIDER_CLASS if self.PROVIDER_CLASS is not None else MLAModelProvider | ||
| else: | ||
| for key in ["q_lora_rank", "kv_lora_rank", "qk_head_dim", "qk_pos_emb_head_dim", "v_head_dim"]: | ||
| provider_kwargs.pop(key, None) | ||
| provider_class = self.PROVIDER_CLASS if self.PROVIDER_CLASS is not None else GPTModelProvider | ||
| provider = provider_class(**provider_kwargs) |
There was a problem hiding this comment.
| # Detect MLA model (e.g. GLM-4.7-Flash) for conditional mapping | ||
| is_mla_mapping = hasattr(self, "_hf_config") and hasattr(self._hf_config, "q_lora_rank") | ||
|
|
||
| # For MLA models, override q_layernorm mapping from q_norm to q_a_layernorm | ||
| if is_mla_mapping: | ||
| layer_specific_mappings[ | ||
| "decoder.layers.*.self_attention.q_layernorm.weight" | ||
| ] = "model.layers.*.self_attn.q_a_layernorm.weight" |
There was a problem hiding this comment.
由于此 bridge 专用于 Glm4MoeLiteForCausalLM(始终是 MLA 模型),因此当 self._hf_config 存在时,is_mla_mapping 始终为 True。我们可以通过在 layer_specific_mappings 中直接将 q_layernorm 映射到 q_a_layernorm.weight 来简化映射注册,并移除冗余的条件判断。
| # Detect MLA model (e.g. GLM-4.7-Flash) for conditional mapping | |
| is_mla_mapping = hasattr(self, "_hf_config") and hasattr(self._hf_config, "q_lora_rank") | |
| # For MLA models, override q_layernorm mapping from q_norm to q_a_layernorm | |
| if is_mla_mapping: | |
| layer_specific_mappings[ | |
| "decoder.layers.*.self_attention.q_layernorm.weight" | |
| ] = "model.layers.*.self_attn.q_a_layernorm.weight" | |
| # GLM-4.7-Flash is always an MLA model, so we map q_layernorm to q_a_layernorm directly | |
| layer_specific_mappings[ | |
| "decoder.layers.*.self_attention.q_layernorm.weight" | |
| ] = "model.layers.*.self_attn.q_a_layernorm.weight" |
| # MLA models (e.g. GLM-4.7-Flash): MTP transformer layer reuses the | ||
| # last normal layer spec (MLA), so map the individual Q/KV down/up | ||
| # projections instead of a fused QKV. | ||
| mapping_list.extend( | ||
| [ | ||
| AutoMapping( | ||
| megatron_param=f"mtp.layers.{mtp_layer}.transformer_layer.self_attention.linear_q_down_proj.weight", | ||
| hf_param=f"model.layers.{mtp_layer + num_transformer_layers}.self_attn.q_a_proj.weight", | ||
| ), | ||
| AutoMapping( | ||
| megatron_param=f"mtp.layers.{mtp_layer}.transformer_layer.self_attention.linear_q_up_proj.weight", | ||
| hf_param=f"model.layers.{mtp_layer + num_transformer_layers}.self_attn.q_b_proj.weight", | ||
| ), | ||
| AutoMapping( | ||
| megatron_param=f"mtp.layers.{mtp_layer}.transformer_layer.self_attention.linear_kv_down_proj.weight", | ||
| hf_param=f"model.layers.{mtp_layer + num_transformer_layers}.self_attn.kv_a_proj_with_mqa.weight", | ||
| ), | ||
| AutoMapping( | ||
| megatron_param=f"mtp.layers.{mtp_layer}.transformer_layer.self_attention.linear_kv_up_proj.weight", | ||
| hf_param=f"model.layers.{mtp_layer + num_transformer_layers}.self_attn.kv_b_proj.weight", | ||
| ), | ||
| AutoMapping( | ||
| megatron_param=f"mtp.layers.{mtp_layer}.transformer_layer.self_attention.linear_q_up_proj.layer_norm_weight", | ||
| hf_param=f"model.layers.{mtp_layer + num_transformer_layers}.self_attn.q_a_layernorm.weight", | ||
| ), | ||
| AutoMapping( | ||
| megatron_param=f"mtp.layers.{mtp_layer}.transformer_layer.self_attention.linear_kv_up_proj.layer_norm_weight", | ||
| hf_param=f"model.layers.{mtp_layer + num_transformer_layers}.self_attn.kv_a_layernorm.weight", | ||
| ), | ||
| ] | ||
| ) | ||
| else: | ||
| # Non-MLA models (GLM-4.5): fused QKV projection | ||
| mapping_list.extend( | ||
| [ | ||
| QKVMapping( | ||
| megatron_param=f"mtp.layers.{mtp_layer}.transformer_layer.self_attention.linear_qkv.weight", | ||
| q=f"model.layers.{mtp_layer + num_transformer_layers}.self_attn.q_proj.weight", | ||
| k=f"model.layers.{mtp_layer + num_transformer_layers}.self_attn.k_proj.weight", | ||
| v=f"model.layers.{mtp_layer + num_transformer_layers}.self_attn.v_proj.weight", | ||
| ), | ||
| QKVMapping( | ||
| megatron_param=f"mtp.layers.{mtp_layer}.transformer_layer.self_attention.linear_qkv.bias", | ||
| q=f"model.layers.{mtp_layer + num_transformer_layers}.self_attn.q_proj.bias", | ||
| k=f"model.layers.{mtp_layer + num_transformer_layers}.self_attn.k_proj.bias", | ||
| v=f"model.layers.{mtp_layer + num_transformer_layers}.self_attn.v_proj.bias", | ||
| ), | ||
| ] | ||
| ) | ||
| # MTP transformer layer MLP mappings (shared by both MLA and non-MLA) |
There was a problem hiding this comment.
由于当 self._hf_config 存在时 is_mla_mapping 始终为 True(且如果 self._hf_config 缺失,已在第 248 行提前返回),因此第 319-336 行的 else 分支是永远无法执行的死代码。\n\n建议移除冗余的 if is_mla_mapping: 判断和死代码分支,以简化代码结构。
# MLA models (e.g. GLM-4.7-Flash): MTP transformer layer reuses the
# last normal layer spec (MLA), so map the individual Q/KV down/up
# projections instead of a fused QKV.
mapping_list.extend(
[
AutoMapping(
megatron_param=f"mtp.layers.{mtp_layer}.transformer_layer.self_attention.linear_q_down_proj.weight",
hf_param=f"model.layers.{mtp_layer + num_transformer_layers}.self_attn.q_a_proj.weight",
),
AutoMapping(
megatron_param=f"mtp.layers.{mtp_layer}.transformer_layer.self_attention.linear_q_up_proj.weight",
hf_param=f"model.layers.{mtp_layer + num_transformer_layers}.self_attn.q_b_proj.weight",
),
AutoMapping(
megatron_param=f"mtp.layers.{mtp_layer}.transformer_layer.self_attention.linear_kv_down_proj.weight",
hf_param=f"model.layers.{mtp_layer + num_transformer_layers}.self_attn.kv_a_proj_with_mqa.weight",
),
AutoMapping(
megatron_param=f"mtp.layers.{mtp_layer}.transformer_layer.self_attention.linear_kv_up_proj.weight",
hf_param=f"model.layers.{mtp_layer + num_transformer_layers}.self_attn.kv_b_proj.weight",
),
AutoMapping(
megatron_param=f"mtp.layers.{mtp_layer}.transformer_layer.self_attention.linear_q_up_proj.layer_norm_weight",
hf_param=f"model.layers.{mtp_layer + num_transformer_layers}.self_attn.q_a_layernorm.weight",
),
AutoMapping(
megatron_param=f"mtp.layers.{mtp_layer}.transformer_layer.self_attention.linear_kv_up_proj.layer_norm_weight",
hf_param=f"model.layers.{mtp_layer + num_transformer_layers}.self_attn.kv_a_layernorm.weight",
),
]
)cbe27d8 to
d139290
Compare
|
|
||
| # launch the master node of ray in container | ||
| export MASTER_ADDR=${MASTER_ADDR:-"127.0.0.1"} | ||
| ray start --head --node-ip-address ${MASTER_ADDR} --num-gpus 16 --disable-usage-stats --dashboard-host=0.0.0.0 --dashboard-port=8265 |
There was a problem hiding this comment.
ray start --num-gpus 16 advertises GPU capacity on an NPU job. On Ascend, vime schedules NPU bundles (see launch.py: --num-gpus 0 --resources '{"NPU": 16}'). Please align with the existing NPU launch convention (cf. run-qwen3-30B-A3B-npu.sh / launch.py).
|
DCO is failing: both commits are missing |
| provider.moe_shared_expert_overlap = True | ||
| provider.moe_token_dispatcher_type = "alltoall" | ||
| provider.moe_router_load_balancing_type = "seq_aux_loss" | ||
| provider.moe_router_pre_softmax = False |
There was a problem hiding this comment.
provider.moe_router_pre_softmax = False conflicts with --moe-router-pre-softmax in the model script and with NVIDIA's GLM47FlashBridge (True). Please align provider defaults with the training CLI / official bridge to avoid router behavior mismatch during HF bridge load.
92bff48 to
fb0cd01
Compare
Meihan-chen
left a comment
There was a problem hiding this comment.
Please rebase onto the latest ascend branch and add a new GLM test to validate the integration and prevent regressions,the new end-to-end GLM path is not covered by NPU CI.
a09cc77 to
1b57fcd
Compare
Add the GLM47MTPBridge megatron.bridge plugin for GLM-4.7-Flash (Glm4MoeLiteForCausalLM): MLA (Multi-Head Latent Attention) + GLM-style MoE with MTP support on Ascend 910B NPU. The bridge overrides provider_bridge/mapping_registry with the MLA-aware versions (always individual MLA projections + q_a_layernorm, never fused QKV) while reusing GLM45Bridge's MTP plumbing and fused-expert handling. A runtime monkey-patch (_patch_glm_mtp_graph_friendly, env-gated by VIME_PATCH_GLM_MTP_GRAPH=1) rewrites the MTP drafter's bool-mask index assignment as torch.where so it is NPU cudagraph-capturable (aclnnNonzeroV2 otherwise fails under graph capture). Register a new end-to-end GLM NPU smoke test (tests/test_glm4.7_30B_A3B_npu.py) in the Buildkite NPU CI smk suite to validate the MLA + MoE + MTP integration and prevent regressions. The test loads HF weights via bridge mode, mirrors scripts/run-glm4.7-30B-A3B-npu.sh (TP=4/EP=8, MTP speculative decoding under cudagraph), and exercises the GLM MTP graph patch. Signed-off-by: louyujing <louyujing@huawei.com>
1b57fcd to
cc7cd48
Compare
The CI image pins Megatron-Bridge at commit 7f0fb345, whose
GLM45Bridge._uses_fused_experts reaches self.hf_pretrained.state.source
via _hf_source_and_keys. On the peft adapter export path
(build_adapter_conversion_tasks -> mapping_registry) self.hf_pretrained
is a config-only object with no `state` attribute, so the inherited
helper raises AttributeError and update_weights crashes before any
weight is written back to vLLM.
The same path also reads self.hf_config (property), which the peft path
never populates, so MTP mappings were silently skipped ("No HF config
found").
Override _uses_fused_experts / _hf_expert_suffix with revision-safe
versions that read HF keys through getattr + try/except and fall back to
the documented GLM fused-expert default (True, no .weight suffix) when
no HF state is available. Add _glm_hf_config to locate the config across
both the _hf_config attribute (newer revisions) and the hf_config
property (7f0fb345), and use it for the MTP layer lookup.
Signed-off-by: louyujing <louyujing@huawei.com>
GLM-4.7-Flash HuggingFace checkpoints ship per-expert weights (experts.<n>.gate_proj / up_proj / down_proj), not fused tensors (gate_up_proj / down_proj). The config-only fallback in _uses_fused_experts previously defaulted to True (inherited from the GLM-4.5 assumption), which generated fused-expert mappings and raised KeyError: 'model.layers.*.mlp.experts.gate_up_proj' during weight load. Default to False on the config-only path so the per-expert mapping branch is selected. When HF state keys are available, key-based detection still overrides either default. Verified end-to-end on CI-aligned image (Megatron-Bridge 7f0fb345): update_weights + rollout + train step + second update_weights all succeed (Job succeeded). Signed-off-by: louyujing <louyujing@huawei.com>
…patch Replace the runtime monkey-patch (_patch_glm_mtp_graph_friendly in update_weight_from_tensor.py, env-gated by VIME_PATCH_GLM_MTP_GRAPH=1) with a static patch in docker/npu_patch/vllm.patch that rewrites the same line in vllm's Glm4MoeLiteMultiTokenPredictorLayer.forward: `inputs_embeds[positions == 0] = 0` -> a cudagraph-friendly `torch.where`. The patch is applied at image build time, so the runtime hook, its env gate, and the VIME_PATCH_GLM_MTP_GRAPH export in the run script / e2e test are no longer needed. Behaviour is unchanged: the MTP drafter's bool-mask index assignment (aclnnNonzeroV2) is replaced by an element-wise torch.where so it is NPU cudagraph-capturable. Verified end-to-end on CI-aligned image (Megatron-Bridge 7f0fb345): Job succeeded with the vllm patch applied and the runtime patch removed. Signed-off-by: louyujing <louyujing@huawei.com>
| --rollout-num-gpus-per-engine 4 | ||
| --vllm-gpu-memory-utilization 0.7 | ||
| --vllm-cudagraph-capture-sizes 1 2 4 8 $(seq 16 8 256) | ||
| --vllm-speculative-config '{"method":"mtp","num_speculative_tokens":1}' |
There was a problem hiding this comment.
Just curious, what’s the benefit of enabling MTP here? If it’s optional, maybe we could get the model support in first and add MTP for a follow-up?
There was a problem hiding this comment.
With the main sync in #413 changing the model-loading and weight-sync paths quite a bit, how about focusing on basic GLM-4.7-Flash support here and adding MTP after the sync lands? That should help avoid having to adapt and validate the MTP path twice.
There was a problem hiding this comment.
From our testing, the benefits and adaptation cost of MTP are quite clear: on the inference side, with spec=1 configuration, rollout end-to-end latency is reduced by ~12%, the pos0 acceptance rate reaches ~78%, and the gains from speculative decoding are clearly realized. On the training side, MTP loss aligns perfectly with the convergence pace of the main task; the reward curve overlaps with the non-MTP baseline (both converge from 0.52 to 0.73), so MTP does not degrade training performance. In terms of adaptation cost, training and inference share the same set of checkpoints with no extra weight files required. Code changes are confined to the mapping_registry in the bridge plugin, adding only about 60 lines of MTP layer mapping logic with zero intrusion into the main framework path. MTP is fully optional: when disabled, simply remove MTP_ARGS and vllm-speculative-config related settings, and the model will run normally and complete RL convergence. All things considered, I recommend including MTP in this PR.
The colocate mode (train + rollout sharing the same NPUs) was inherited from test_qwen3_30B_A3B_npu.py, but the GLM-4.7 run script (run-glm4.7-30B-A3B-npu.sh) uses dedicated NPUs for train (8) and rollout (8) on a 16-NPU node. Under colocate, CI observed MTP loss 3.12 (>1.0 threshold) and 0% speculative acceptance, indicating MTP weight sync/alignment is broken in colocate mode. Dropping --colocate matches the run script's dedicated-NPU layout and restores healthy MTP loss (~0.66, well under the 1.0 CI gate). Signed-off-by: louyujing <louyujing@huawei.com>
Overview
Adds training/inference support for the GLM-4.7-Flash model on Ascend 910B NPU. GLM-4.7-Flash uses DeepSeek-V3-style Multi-Head Latent Attention (MLA) plus a GLM-style MoE (64 routed experts + 1 shared expert, sigmoid router with expert bias), and supports Multi-Token Prediction (MTP). This PR registers the corresponding megatron bridge, adds the model-argument and run scripts, and fixes an MTP drafter compatibility issue under NPU cudagraph capture.
Changes
New bridge plugin
vime_plugins/megatron_bridge/glm4moe_lite.py: Adds the megatron bridge forGlm4MoeLiteForCausalLM. It subclassesGLM45Bridgeto reuse its MTP plumbing (num_nextn_predict_layersmapping loop, fused-expert handling, etc.) and only overridesprovider_bridgeandmapping_registryfor the MLA model. MindSpeed TE module types are registered via the publicAutoMapping.register_module_typeAPI through_register_mindspeed_te_module_types(), so weight loading works on NPU without manually patching the container'sparam_mapping.py. Non-MLA branches that are always dead code for this model (is_mla/is_mla_mappingare always true) have been removed, along with the now-unusedQKVMapping/GPTModelProviderimports.vime_plugins/megatron_bridge/__init__.py: Imports and registers the new bridge.MTP cudagraph compatibility fix
docker/npu_patch/vllm.patch: Adds a static patch to vLLM's upstream Glm4MoeLiteMultiTokenPredictorLayer.forward that rewrites the bool-mask index assignment (inputs_embeds[positions == 0] = 0) into the equivalent element-wise torch.where. The original form routes to aclnnNonzeroV2 (a data-dependent-shape op), which fails with stream is captured under NPU cudagraph capture. After the rewrite, the MTP drafter runs under cudagraph on Ascend. The patch is applied at image build time (reconciled at runtime via series.conf), so no runtime hook or env gate is needed. The change is scoped to the GLM MTP class and is a no-op for non-GLM models.Run scripts
scripts/models/glm4.7-30B-A3B-npu.sh: Model argument definitions for GLM-4.7-Flash 30B-A3B (MoE config, MLA q/kv lora rank, qk/v head dim, etc.).scripts/run-glm4.7-30B-A3B-npu.sh: NPU training entry script (GRPO, 16 cards on a single node); enables MTP training and vLLM MTP speculative decoding, and setsVIME_PATCH_GLM_MTP_GRAPH=1.Usage
Weight and data paths can be overridden via the
DATA_ROOTenvironment variable.Testing