Skip to content

Commit 40bacb4

Browse files
Fix DeepSeek-V3 checkpoint export compatibility (flagos-ai#1202)
## Summary - Add an explicit `--skip-mtp` conversion option so DeepSeek-V3/Moonlight checkpoints can be exported to HF implementations that only contain the main LM layers. - Keep loader and saver MTP handling consistent when MTP is skipped, avoiding unconsumed queue messages and missing HF MTP layer lookups. - Align DeepSeek-V3 HF config export with current Megatron argument names and true vocabulary size handling. - Guard Engram-only checkpoint fields for non-Engram checkpoints and remove unsupported legacy Megatron conversion arguments. ## Validation - `PYTHONPYCACHEPREFIX=/private/tmp/flagscale_pycache python3 -m py_compile tools/checkpoint/convert.py tools/checkpoint/utils.py tools/checkpoint/loader_mcore.py tools/checkpoint/loader_transformers.py tools/checkpoint/saver_mcore.py tools/checkpoint/saver_transformers.py tools/checkpoint/deepseek_v3/args.py` - `git diff --check`
1 parent 5c04b69 commit 40bacb4

11 files changed

Lines changed: 462 additions & 235 deletions

File tree

tools/checkpoint/convert.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,14 @@ def main():
6464
parser.add_argument(
6565
"--max-queue-size", type=int, default=50, help="Maximum number of tensors in the queue"
6666
)
67+
parser.add_argument(
68+
"--skip-mtp",
69+
action="store_true",
70+
help=(
71+
"Skip Multi-Token Prediction (MTP) modules during conversion. "
72+
"Use this when the target implementation only contains the main LM layers."
73+
),
74+
)
6775

6876
extend_cases = [["mistral", "mixtral"]]
6977

tools/checkpoint/deepseek_v3/args.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def load_args_hf2mg(args):
2323
args.swiglu = True if hidden_act == "silu" else False
2424
args.max_position_embeddings = deepseek_v3_args["max_position_embeddings"]
2525
args.init_method_std = deepseek_v3_args["initializer_range"]
26-
args.norm_epsilon = deepseek_v3_args["rms_norm_eps"]
26+
args.layernorm_epsilon = deepseek_v3_args["rms_norm_eps"]
2727
args.untie_embeddings_and_output_weights = not deepseek_v3_args["tie_word_embeddings"]
2828
args.rotary_base = deepseek_v3_args["rope_theta"]
2929
args.disable_bias_linear = not deepseek_v3_args["attention_bias"]
@@ -93,13 +93,14 @@ def load_args_hf2mg(args):
9393
def save_args_mg2hf(args):
9494
first_k_dense_replace = args.moe_layer_freq.index(1)
9595
seq_aux = True if args.moe_router_load_balancing_type == "seq_aux_loss" else False
96+
mtp_num_layers = getattr(args, "mtp_num_layers", 0) or 0
9697
config = DeepseekV3Config(
9798
vocab_size=args.vocab_size,
9899
hidden_size=args.hidden_size,
99100
intermediate_size=args.ffn_hidden_size,
100101
moe_intermediate_size=args.moe_ffn_hidden_size,
101102
num_hidden_layers=args.num_layers,
102-
num_nextn_predict_layers=args.mtp_num_layers,
103+
num_nextn_predict_layers=mtp_num_layers,
103104
num_attention_heads=args.num_attention_heads,
104105
num_key_value_heads=args.num_query_groups,
105106
n_shared_experts=args.moe_shared_expert_intermediate_size // args.moe_ffn_hidden_size,
@@ -118,7 +119,7 @@ def save_args_mg2hf(args):
118119
seq_aux=seq_aux,
119120
max_position_embeddings=args.max_position_embeddings,
120121
initializer_range=args.init_method_std,
121-
rms_norm_eps=args.norm_epsilon,
122+
rms_norm_eps=args.layernorm_epsilon,
122123
tie_word_embeddings=not args.untie_embeddings_and_output_weights,
123124
rope_theta=args.rotary_base,
124125
attention_dropout=args.attention_dropout,

tools/checkpoint/deepseek_v3/ckpt.py

Lines changed: 105 additions & 111 deletions
Large diffs are not rendered by default.

tools/checkpoint/loader_mcore.py

Lines changed: 61 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,18 @@ def _load_checkpoint(queue, args):
4646
from megatron.training.checkpointing import load_args_from_checkpoint, load_checkpoint
4747
from megatron.legacy.model import module
4848
from megatron.core import mpu
49-
from megatron.legacy import fused_kernels
5049
from megatron.core.tensor_parallel.random import (
5150
get_cuda_rng_tracker, _DATA_PARALLEL_RNG_TRACKER_NAME,
5251
_EXPERT_PARALLEL_RNG_TRACKER_NAME, _MODEL_PARALLEL_RNG_TRACKER_NAME
5352
)
54-
from tools.checkpoint.utils import _ConverterFakeProcessGroup
53+
from tools.checkpoint.utils import (
54+
_ConverterFakeProcessGroup,
55+
get_expert_model_parallel_rank,
56+
get_expert_tensor_parallel_rank,
57+
get_mcore_model_parallel_size,
58+
get_tensor_model_parallel_rank,
59+
validate_mcore_parallel_size,
60+
)
5561
except ModuleNotFoundError:
5662
print("Unable to import Megatron, please specify the path to Megatron using --megatron-path. Exiting.")
5763
queue.put("exit")
@@ -78,7 +84,6 @@ def queue_put(name, msg):
7884
'--no-masked-softmax-fusion',
7985
'--no-bias-gelu-fusion',
8086
'--no-bias-dropout-fusion',
81-
'--no-async-tensor-model-parallel-allreduce',
8287
'--use-cpu-initialization',
8388
'--micro-batch-size', '1',
8489
'--no-load-optim',
@@ -125,20 +130,27 @@ def _set_arg(arg_name):
125130
_set_arg("hetero_pipeline_layer_split")
126131

127132
# for engram
128-
_set_arg("use_engram")
129-
_set_arg("engram_layer_ids")
130-
_set_arg("engram_hc_mult")
131-
_set_arg("engram_kernel_size")
132-
_set_arg("engram_pad_id")
133-
_set_arg("engram_seed")
134-
_set_arg("engram_vocab_size")
135-
_set_arg("engram_tokenizer_name_or_path")
136-
_set_arg("max_ngram_size")
137-
_set_arg("n_embed_per_ngram")
138-
_set_arg("n_head_per_ngram")
139-
setattr(margs, "vocab_size", args.true_vocab_size)
140-
engram_tokenizer_path_ckpt = getattr(checkpoint_args, "engram_tokenizer_name_or_path", None)
141-
setattr(margs, "engram_tokenizer_name_or_path", os.path.join(root_path, engram_tokenizer_path_ckpt))
133+
if getattr(checkpoint_args, "use_engram", False):
134+
_set_arg("use_engram")
135+
_set_arg("engram_layer_ids")
136+
_set_arg("engram_hc_mult")
137+
_set_arg("engram_kernel_size")
138+
_set_arg("engram_pad_id")
139+
_set_arg("engram_seed")
140+
_set_arg("engram_vocab_size")
141+
_set_arg("engram_tokenizer_name_or_path")
142+
_set_arg("max_ngram_size")
143+
_set_arg("n_embed_per_ngram")
144+
_set_arg("n_head_per_ngram")
145+
if args.true_vocab_size is not None:
146+
setattr(margs, "vocab_size", args.true_vocab_size)
147+
engram_tokenizer_path_ckpt = getattr(checkpoint_args, "engram_tokenizer_name_or_path", None)
148+
if engram_tokenizer_path_ckpt and not os.path.isabs(engram_tokenizer_path_ckpt):
149+
engram_tokenizer_path_ckpt = os.path.join(root_path, engram_tokenizer_path_ckpt)
150+
setattr(margs, "engram_tokenizer_name_or_path", engram_tokenizer_path_ckpt)
151+
else:
152+
setattr(margs, "use_engram", False)
153+
setattr(margs, "engram_layer_ids", [])
142154

143155
# for hetero
144156
if margs.hetero_process_meshes is not None:
@@ -148,7 +160,7 @@ def _set_arg(arg_name):
148160

149161
# Arguments do sanity checks on the world size, but we don't care,
150162
# so trick it into thinking we are plenty of processes
151-
margs.world_size = margs.tensor_model_parallel_size * margs.pipeline_model_parallel_size * margs.expert_model_parallel_size
163+
margs.world_size = get_mcore_model_parallel_size(margs) * margs.pipeline_model_parallel_size
152164

153165
# Explicitly copy data types from checkpoint.
154166
margs.fp16 = checkpoint_args.fp16
@@ -170,6 +182,7 @@ def _set_arg(arg_name):
170182

171183
print("*"*20 + "validate loader arguments" + "*"*20)
172184
margs = validate_args(margs)
185+
validate_mcore_parallel_size(margs)
173186

174187
def check_for_arg(arg_name, default=None):
175188
if getattr(margs, arg_name, None) is None:
@@ -224,14 +237,20 @@ def check_for_arg(arg_name, default=None):
224237
tp_size = margs.tensor_model_parallel_size
225238
pp_size = margs.pipeline_model_parallel_size
226239
ep_size = margs.expert_model_parallel_size
240+
etp_size = margs.expert_tensor_parallel_size
241+
mcore_model_parallel_size = get_mcore_model_parallel_size(margs)
227242
vp_size = margs.virtual_pipeline_model_parallel_size or 1
228243
mpu.set_tensor_model_parallel_world_size(tp_size)
229244
mpu.set_pipeline_model_parallel_world_size(pp_size)
230245
mpu.set_expert_model_parallel_world_size(ep_size)
246+
if hasattr(mpu, "set_expert_tensor_parallel_world_size"):
247+
mpu.set_expert_tensor_parallel_world_size(etp_size)
231248
mpu.set_virtual_pipeline_model_parallel_world_size(margs.virtual_pipeline_model_parallel_size)
232249
mpu.set_tensor_model_parallel_rank(0)
233250
mpu.set_pipeline_model_parallel_rank(0)
234251
mpu.set_expert_model_parallel_rank(0)
252+
if hasattr(mpu, "set_expert_tensor_parallel_rank"):
253+
mpu.set_expert_tensor_parallel_rank(0)
235254
mpu.set_virtual_pipeline_model_parallel_rank(0)
236255
# For backward compatibility during local parallel states refactoring
237256
fake_tp_group = _ConverterFakeProcessGroup(size=tp_size)
@@ -242,10 +261,10 @@ def check_for_arg(arg_name, default=None):
242261
fake_pp_group = _ConverterFakeProcessGroup(size=margs.pipeline_model_parallel_size)
243262
fake_cp_group = _ConverterFakeProcessGroup(size=margs.context_parallel_size)
244263
fake_dp_group = _ConverterFakeProcessGroup(size=margs.data_parallel_size)
245-
fake_etp_group = _ConverterFakeProcessGroup(size=margs.expert_tensor_parallel_size)
246-
edp_parallel_size = margs.tensor_model_parallel_size * margs.context_parallel_size // (margs.expert_tensor_parallel_size * margs.expert_model_parallel_size)
264+
fake_etp_group = _ConverterFakeProcessGroup(size=etp_size)
265+
edp_parallel_size = mcore_model_parallel_size // (etp_size * ep_size)
247266
fake_edp_group = _ConverterFakeProcessGroup(size=edp_parallel_size)
248-
fake_etp_ep_group = _ConverterFakeProcessGroup(size=margs.expert_tensor_parallel_size*margs.expert_model_parallel_size)
267+
fake_etp_ep_group = _ConverterFakeProcessGroup(size=etp_size * ep_size)
249268
fake_tcp_group = _ConverterFakeProcessGroup(size=margs.tensor_model_parallel_size*margs.context_parallel_size)
250269
mpu._PIPELINE_MODEL_PARALLEL_GROUP = fake_pp_group
251270
mpu._CONTEXT_PARALLEL_GROUP = fake_cp_group
@@ -254,14 +273,11 @@ def check_for_arg(arg_name, default=None):
254273
mpu._EXPERT_DATA_PARALLEL_GROUP = fake_edp_group
255274
mpu._EXPERT_TENSOR_AND_MODEL_PARALLEL_GROUP = fake_etp_ep_group
256275
mpu._TENSOR_AND_CONTEXT_PARALLEL_GROUP = fake_tcp_group
257-
mpu._EXPERT_TENSOR_PARALLEL_GROUP = fake_tp_group
276+
mpu._EXPERT_TENSOR_PARALLEL_GROUP = fake_etp_group
258277
mpu._DATA_PARALLEL_GROUP_WITH_CP = fake_dp_group
259278
mpu._INTRA_PARTIAL_DATA_PARALLEL_GROUP_WITH_CP = fake_dp_group
260279
mpu._LAST_RANK_WHEN_USING_PIPELINE = pp_size - 1
261280

262-
# fused kernel
263-
fused_kernels.load(margs)
264-
265281
# random
266282
CUDA_RNG_STATE_TRACKER = get_cuda_rng_tracker()
267283
torch.cuda.manual_seed(42)
@@ -291,6 +307,7 @@ def check_for_arg(arg_name, default=None):
291307
md.previous_tensor_parallel_size = margs.tensor_model_parallel_size
292308
md.previous_pipeline_parallel_size = margs.pipeline_model_parallel_size
293309
md.previous_expert_parallel_size = margs.expert_model_parallel_size
310+
md.previous_expert_tensor_parallel_size = margs.expert_tensor_parallel_size
294311
md.previous_decoder_first_pipeline_num_layers = margs.decoder_first_pipeline_num_layers
295312
md.true_vocab_size = args.true_vocab_size # true (non-padded) vocab size
296313
md.make_vocab_size_divisible_by = margs.make_vocab_size_divisible_by
@@ -302,16 +319,22 @@ def get_models(count, dtype):
302319
# for one pp stage
303320
nonlocal consumed_train_samples
304321
nonlocal consumed_valid_samples
305-
tp_size = margs.tensor_model_parallel_size
306322
pp_size = margs.pipeline_model_parallel_size
307323
vp_size = margs.virtual_pipeline_model_parallel_size or 1
308324

309325
models = [[] for _ in range(vp_size)]
310326
for rank_id in range(count):
311-
tp_rank = rank_id % tp_size
312-
ep_rank = rank_id // tp_size
327+
tp_rank = get_tensor_model_parallel_rank(rank_id, margs)
328+
ep_rank = get_expert_model_parallel_rank(rank_id, margs)
329+
etp_rank = get_expert_tensor_parallel_rank(rank_id, margs)
313330
mpu.set_tensor_model_parallel_rank(tp_rank)
314331
mpu.set_expert_model_parallel_rank(ep_rank)
332+
if hasattr(mpu, "set_expert_tensor_parallel_rank"):
333+
mpu.set_expert_tensor_parallel_rank(etp_rank)
334+
fake_tp_group.set_rank(tp_rank)
335+
fake_ep_group.set_rank(ep_rank)
336+
fake_etp_group.set_rank(etp_rank)
337+
fake_etp_ep_group.set_rank(ep_rank * etp_size + etp_rank)
315338
if pp_size > 1 and vp_size > 1:
316339
model_ = []
317340
for vp_rank in range(vp_size):
@@ -353,7 +376,7 @@ def get_models(count, dtype):
353376
mpu.set_pipeline_model_parallel_rank(0)
354377
fake_pp_group = _ConverterFakeProcessGroup(rank=0, size=pp_size)
355378
mpu._PIPELINE_MODEL_PARALLEL_GROUP = fake_pp_group
356-
all_models = [get_models(tp_size * ep_size, margs.params_dtype)]
379+
all_models = [get_models(mcore_model_parallel_size, margs.params_dtype)]
357380
models = all_models[0][0] # pp0vpp0
358381

359382
md.consumed_train_samples = consumed_train_samples
@@ -379,15 +402,17 @@ def get_models(count, dtype):
379402
mpu._PIPELINE_MODEL_PARALLEL_GROUP = fake_pp_group
380403

381404
if pp_rank > 0 and vp_rank == 0:
382-
all_models.append(get_models(tp_size * ep_size, margs.params_dtype))
405+
all_models.append(get_models(mcore_model_parallel_size, margs.params_dtype))
383406

384407
models = all_models[pp_rank][vp_rank]
385408
for layer_id in range(len(models[0].decoder.layers)):
386409
message = dict()
387410
margs.total_layer_num = total_layer_num
388411

389412
engram_layer_id = total_layer_num # get_global_layer_id
390-
if margs.use_engram and engram_layer_id in margs.engram_layer_ids:
413+
if getattr(margs, "use_engram", False) and engram_layer_id in getattr(
414+
margs, "engram_layer_ids", []
415+
):
391416
ckpt_plugin.get_engram_ckpt(message, models, engram_layer_id, margs)
392417

393418
ckpt_plugin.get_attn_ckpt(message, models, layer_id, margs)
@@ -406,9 +431,11 @@ def get_models(count, dtype):
406431
ckpt_plugin.get_output_layer_ckpt(message, models, margs)
407432
queue_put("output layer", message)
408433

409-
message = dict()
410-
if margs.mtp_num_layers:
411-
for mtp_layer_id in range(margs.mtp_num_layers):
434+
mtp_num_layers = getattr(margs, "mtp_num_layers", 0)
435+
if getattr(args, "skip_mtp", False):
436+
mtp_num_layers = 0
437+
if mtp_num_layers:
438+
for mtp_layer_id in range(mtp_num_layers):
412439
message = dict()
413440
ckpt_plugin.get_mtp_ckpt(message, models, mtp_layer_id, margs)
414441
queue_put(f"mtp module {mtp_layer_id}", message)

tools/checkpoint/loader_transformers.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ def queue_put(name, msg):
7373
'--no-masked-softmax-fusion',
7474
'--no-bias-gelu-fusion',
7575
'--no-bias-dropout-fusion',
76-
'--no-async-tensor-model-parallel-allreduce',
7776
'--use-cpu-initialization',
7877
'--micro-batch-size', '1',
7978
'--no-load-optim',
@@ -192,9 +191,11 @@ def check_for_arg(arg_name, default=None):
192191
message = {"weight": hf_model.lm_head.weight.data}
193192
queue_put("output layer", message)
194193

195-
message = dict()
196-
if margs.mtp_num_layers:
197-
for mtp_layer_id in range(margs.mtp_num_layers):
194+
mtp_num_layers = getattr(margs, "mtp_num_layers", 0)
195+
if getattr(args, "skip_mtp", False):
196+
mtp_num_layers = 0
197+
if mtp_num_layers:
198+
for mtp_layer_id in range(mtp_num_layers):
198199
message = dict()
199200
ckpt_plugin.get_hf_mtp_ckpt(message, hf_model, mtp_layer_id, margs)
200201
queue_put(f"mtp module {mtp_layer_id}", message)

0 commit comments

Comments
 (0)