Files
wehub-resource-sync a203934033
Lint test / lint (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:34:58 +08:00

296 lines
13 KiB
Python

# Copyright (c) ModelScope Contributors. All rights reserved.
from dataclasses import fields
from mcore_bridge import ModelConfig
from mcore_bridge import get_mcore_model as _get_mcore_model
from mcore_bridge import hf_to_mcore_config
from transformers.utils import is_torch_npu_available
from typing import Any, Generator, Optional, Tuple
from swift.utils import get_logger
logger = get_logger()
def _check_attention_backend(args, config):
"""Validate attention backend compatibility with configuration."""
attention_backend = config.attention_backend.name
if attention_backend == 'flash' and config.softmax_type == 'learnable':
raise ValueError(f'Attention backend "{attention_backend}" does not support learnable softmax_type.')
def _check_padding_free(args, config):
"""Validate and adjust padding_free setting based on configuration constraints."""
if not args.padding_free:
return
attention_backend = config.attention_backend.name
message = None
if config.experimental_attention_variant == 'dsa':
message = 'DSA is not supported in padding-free mode'
elif attention_backend == 'unfused':
message = f'Attention backend "{attention_backend}" is not supported in padding-free mode'
if message:
logger.warning(f'{message}. Setting args.padding_free to False.')
args.padding_free = False
def get_mcore_model_config(args, hf_config):
kwargs = hf_to_mcore_config(hf_config)
kwargs['mcore_model_type'] = args.megatron_model_meta.model_type
kwargs['hf_config'] = hf_config
for f in fields(ModelConfig):
key, value = f.name, getattr(args, f.name, None)
if value is None or isinstance(value, (list, tuple)) and len(value) == 0:
continue
kwargs[key] = value
if args.task_type == 'seq_cls':
args.problem_type = args.problem_type or getattr(hf_config, 'problem_type', None)
logger.info(f'args.problem_type: {args.problem_type}')
kwargs['params_dtype'] = args.torch_dtype
kwargs['num_layers_in_first_pipeline_stage'] = args.decoder_first_pipeline_num_layers
kwargs['num_layers_in_last_pipeline_stage'] = args.decoder_last_pipeline_num_layers
kwargs['fp4_param'] = args.fp4_param_gather
kwargs['fp8_param'] = args.fp8_param_gather
swiglu = kwargs.get('swiglu', True)
add_bias_linear = kwargs.get('add_bias_linear', False)
num_moe_experts = kwargs.get('num_moe_experts', None)
position_embedding_type = kwargs.get('position_embedding_type', 'rope')
if position_embedding_type != 'rope':
kwargs['apply_rope_fusion'] = False
if not swiglu and not add_bias_linear:
kwargs['bias_activation_fusion'] = False
if add_bias_linear and num_moe_experts and args.moe_grouped_gemm:
kwargs['bias_dropout_fusion'] = False
if num_moe_experts is None:
kwargs['expert_model_parallel_size'] = 1
kwargs['expert_tensor_parallel_size'] = 1
if args.router_replay_mode != 'disabled':
kwargs['moe_enable_routing_replay'] = True
if args.megatron_extra_kwargs:
kwargs.update(args.megatron_extra_kwargs)
config = ModelConfig(**kwargs)
if is_torch_npu_available() and getattr(args, 'attention_backend', 'flash') != 'local':
setattr(config, 'use_flash_attn', True)
_check_attention_backend(args, config)
_check_padding_free(args, config)
return config
class MegatronBridgeBackend:
"""Adapter for NVIDIA ``megatron.bridge.AutoBridge``.
Limitations:
- LoRA / PEFT loading is not yet supported.
- MLLM is not yet supported
- FP8 export is not yet supported.
"""
def __init__(self, auto_bridge: Any, hf_config: Optional[Any] = None):
self._bridge = auto_bridge
self._hf_config = hf_config
@classmethod
def from_hf_config(cls, hf_config) -> 'MegatronBridgeBackend':
from megatron.bridge.models.conversion.auto_bridge import AutoBridge
return cls(AutoBridge.from_hf_config(hf_config), hf_config)
def load_weights(self, models, hf_model_dir, peft_format=False, adapter_name='default', converter=None):
if peft_format:
raise NotImplementedError('LoRA loading via megatron-bridge backend is not yet supported. '
'Please use bridge_backend="mcore-bridge" for LoRA training.')
if converter is not None:
logger.warning('converter is not supported by megatron-bridge backend, ignoring.')
self._bridge.load_hf_weights(models, hf_path=hf_model_dir)
def export_weights(self,
models,
target_device=None,
only_master_rank=False,
peft_format=False,
adapter_name='default',
converter=None,
tqdm_desc='Exporting: ',
disable_tqdm=True,
_is_saving=False) -> Generator[Tuple[str, 'torch.Tensor'], None, None]:
if peft_format:
raise NotImplementedError('LoRA export via megatron-bridge backend is not yet supported. '
'Please use bridge_backend="mcore-bridge" for LoRA training.')
cpu = (target_device == 'cpu')
for weight_tuple in self._bridge.export_hf_weights(models, cpu=cpu):
key = weight_tuple.param_name
tensor = weight_tuple.weight
if converter is not None and tensor is not None:
kv = converter(key, tensor)
if kv is None:
continue
key, tensor = kv
yield key, tensor
def save_weights(self,
models,
output_dir,
peft_format=False,
max_shard_size='5GB',
args=None,
processor=None) -> None:
if peft_format:
raise NotImplementedError('LoRA saving via megatron-bridge backend is not yet supported. '
'Please use bridge_backend="mcore-bridge" for LoRA training.')
# 1. Save weights via megatron-bridge (safetensors format)
self._bridge.save_hf_weights(models, path=output_dir)
# 2. Save HF config and tokenizer on rank 0.
# We use the original HF config (not the one reconstructed by megatron-bridge)
# because the bridge's config-only path may drop fields like num_attention_heads.
import torch.distributed as dist
is_master = (not dist.is_initialized()) or dist.get_rank() == 0
if is_master and args is not None and self._hf_config is not None:
from copy import deepcopy
from swift.model import save_checkpoint
from swift.utils import HfConfigFactory
hf_config = deepcopy(self._hf_config)
llm_config = HfConfigFactory.get_text_config(hf_config)
# MTP: write back num_nextn_predict_layers
mtp_num_layers = getattr(args, 'mtp_num_layers', None)
if mtp_num_layers:
for key in ['num_nextn_predict_layers', 'mtp_num_hidden_layers']:
if hasattr(llm_config, key):
setattr(llm_config, key, mtp_num_layers)
break
else:
llm_config.num_nextn_predict_layers = mtp_num_layers
HfConfigFactory.del_config_attr(hf_config, 'quantization_config')
# FP8: write back quantization_config
expert_dtype = None
fp8_format = getattr(args, 'fp8_format', None)
fp8_recipe = getattr(args, 'fp8_recipe', 'delayed')
fp8_param = getattr(args, 'fp8_param_gather', False)
if fp8_format is not None and fp8_recipe == 'blockwise' and fp8_param:
from transformers.utils.quantization_config import FineGrainedFP8Config
hf_config.quantization_config = FineGrainedFP8Config()
expert_dtype = 'fp8'
if getattr(args, 'model_type', None) == 'deepseek_v4':
HfConfigFactory.set_config_attr(hf_config, 'expert_dtype', expert_dtype)
hf_config.save_pretrained(output_dir)
if processor is not None:
additional_saved_files = getattr(getattr(processor, 'model_meta', None), 'additional_saved_files', None)
save_checkpoint(
None,
processor,
output_dir,
model_dirs=[args.model_dir],
additional_saved_files=additional_saved_files)
if dist.is_initialized():
dist.barrier()
def get_mcore_model(args, hf_config):
bridge_backend = args.bridge_backend
if bridge_backend == 'megatron-bridge':
return _get_megatron_bridge_model(args, hf_config)
config = get_mcore_model_config(args, hf_config)
models = _get_mcore_model(config)
return models
def _get_megatron_bridge_model(args, hf_config):
import dataclasses
backend = MegatronBridgeBackend.from_hf_config(hf_config)
auto_bridge = backend._bridge
# Validate model support via AutoBridge.supports()
from megatron.bridge.models.conversion.auto_bridge import AutoBridge
if not AutoBridge.supports(hf_config):
raise ValueError(f'Model {getattr(hf_config, "model_type", "unknown")} is not supported by '
f'megatron-bridge. Please use bridge_backend="mcore-bridge" or check '
f'AutoBridge.list_supported_models() for supported architectures.')
# --- Step 1: Get provider (GPTModelProvider, which extends TransformerConfig) ---
provider = auto_bridge.to_megatron_provider(load_weights=False)
# --- Step 2: Build overrides from args ---
# Auto-match: iterate over provider's dataclass fields and pick up matching args fields.
# This mirrors mcore-bridge's get_mcore_model_config which does:
# for f in fields(ModelConfig): kwargs[f.name] = getattr(args, f.name, None)
overrides = {}
provider_fields = {f.name for f in dataclasses.fields(provider)}
for field_name in provider_fields:
value = getattr(args, field_name, None)
if value is None or (isinstance(value, (list, tuple)) and len(value) == 0):
continue
overrides[field_name] = value
# Explicit field name mappings (args name → provider field name)
explicit_mappings = {
'decoder_first_pipeline_num_layers': 'num_layers_in_first_pipeline_stage',
'decoder_last_pipeline_num_layers': 'num_layers_in_last_pipeline_stage',
}
for args_key, provider_key in explicit_mappings.items():
value = getattr(args, args_key, None)
if value is not None and provider_key in provider_fields:
overrides[provider_key] = value
# dtype
dtype = getattr(args, 'torch_dtype', None)
# MoE: if no experts, force EP/ETP to 1
# num_moe_experts comes from HF config (parsed by AutoBridge into provider),
# not from args — so check provider too.
num_moe_experts = overrides.get('num_moe_experts') or getattr(provider, 'num_moe_experts', None)
if num_moe_experts is None:
overrides['expert_model_parallel_size'] = 1
overrides['expert_tensor_parallel_size'] = 1
# Router replay
if getattr(args, 'router_replay_mode', 'disabled') != 'disabled':
if 'moe_enable_routing_replay' in provider_fields:
overrides['moe_enable_routing_replay'] = True
# megatron_extra_kwargs (user-specified raw overrides)
if getattr(args, 'megatron_extra_kwargs', None):
overrides.update(args.megatron_extra_kwargs)
# padding_free requires variable_seq_lengths=True so that RotaryEmbedding
# generates freqs matching the actual packed sequence length (cu_seqlens[-1])
# instead of the fixed seq_length. Without this, mcore-bridge's patcher
# use_batched_rope check fails and falls back to the original
# _apply_rotary_pos_emb_thd which calls torch.split on a padded tensor.
if getattr(args, 'padding_free', False) and 'variable_seq_lengths' in provider_fields:
overrides['variable_seq_lengths'] = True
# --- Step 3: Apply overrides and finalize ---
provider.apply_overrides_and_finalize(dtype=dtype, overrides=overrides)
import torch.nn.functional as F
provider.swiglu = (provider.gated_linear_unit and provider.activation_func is F.silu)
# --- Step 4: Create raw models (no DDP/Float16 wrapping) ---
# swift's wrap_model handles DDP/Float16 wrapping with the correct DDP config from args.
models = provider.provide_distributed_model(
wrap_with_ddp=False,
mixed_precision_wrapper=None,
use_cpu_initialization=getattr(args, 'use_cpu_initialization', False),
)
if not isinstance(models, list):
models = [models]
# --- Step 5: Attach backend to model.config.bridge ---
for model in models:
model.config.bridge = backend
logger.info('Created Megatron model via megatron-bridge backend')
return models