Answer

您询问的是 tie_embeddings_and_output_weights,这是指将输入词嵌入层(embedding)和输出层(lm_head)的权重绑定在一起的机制。

功能概述

在许多语言模型中,输入的词嵌入矩阵和输出的语言模型头(lm_head)可以共享相同的权重矩阵,这样可以减少模型参数量。这个特性通过配置文件中的 tie_word_embeddings 参数控制。

实现方式

1. ParallelLMHead 的 tie_weights 方法

ParallelLMHead 类提供了 tie_weights() 方法来实现权重绑定: 1

这个方法的逻辑是:

  • 对于 GGUF 量化模型,返回原始的 embed_tokens 对象 2
  • 对于其他情况,直接将 lm_head.weight 指向 embed_tokens.weight,实现权重共享 3
2. 在模型初始化中的使用

多个模型在初始化时会根据 config.tie_word_embeddings 决定是否绑定权重:

Granite 模型示例4

Olmo 模型示例5

Llama Eagle 模型示例6

3. 在权重加载时的处理

tie_word_embeddings=True 时,模型在加载权重时会跳过 lm_head.weight,因为它已经与 embed_tokens.weight 共享:

Granite 模型的处理7

Olmo 模型的处理8

MiniCPM3 模型的处理9

4. GGUF 模型的特殊处理

对于 GGUF 量化模型,加载器会检查是否存在 lm_head.weight,如果不存在则自动设置 tie_word_embeddings=True10

配置示例

在模型配置中,tie_word_embeddings 通常在 config.json 中定义:

{
  "tie_word_embeddings": true  // 或 false
}

例如 InternVL 配置默认设置为 False11

优势

权重绑定的主要优势:

  1. 减少参数量:避免存储两份相同大小的权重矩阵
  2. 减少内存占用:特别是对于大词汇表的模型
  3. 可能提高泛化能力:输入和输出共享表示

Notes

不同模型对 tie_word_embeddings 的默认设置不同。例如 Llama 3.2 1B Instruct 设置为 True,而 Llama 3.1 8B Instruct 设置为 False12 在 BailingMoE 模型中,当启用 DP attention 时,lm_head 使用 use_attn_tp_group 参数来控制张量并行组。 13

Wiki pages you might want to explore:

Wiki pages you might want to explore:

Citations

File: python/sglang/srt/layers/vocab_parallel_embedding.py (L560-567)

    def tie_weights(self, embed_tokens: VocabParallelEmbedding):
        """Tie the weights with word embeddings."""
        # GGUF quantized embed_tokens.
        if self.quant_config and self.quant_config.get_name() == "gguf":
            return embed_tokens
        else:
            self.weight = embed_tokens.weight
            return self

File: python/sglang/srt/models/granite.py (L322-329)

        self.lm_head = ParallelLMHead(
            config.vocab_size,
            config.hidden_size,
            quant_config=quant_config,
            prefix=add_prefix("lm_head", prefix),
        )
        if self.config.tie_word_embeddings:
            self.lm_head.tie_weights(self.model.embed_tokens)

File: python/sglang/srt/models/granite.py (L400-404)

            if "lm_head.weight" in name and self.config.tie_word_embeddings:
                # Input and output embeddings are tied, so the output embeddings
                # may not be present in the checkpoint. We assume that the input
                # embeddings are always present in the checkpoint.
                continue

File: python/sglang/srt/models/olmo.py (L308-318)

        if config.tie_word_embeddings:
            self.lm_head = self.model.embed_tokens
        else:
            self.unpadded_vocab_size = config.vocab_size
            self.lm_head = ParallelLMHead(
                self.unpadded_vocab_size,
                config.hidden_size,
                org_num_embeddings=config.vocab_size,
                quant_config=quant_config,
                prefix=add_prefix("lm_head", prefix),
            )

File: python/sglang/srt/models/olmo.py (L355-356)

            if self.config.tie_word_embeddings and "lm_head.weight" in name:
                continue

File: python/sglang/srt/models/llama_eagle.py (L127-137)

        # Llama 3.2 1B Instruct set tie_word_embeddings to True
        # Llama 3.1 8B Instruct set tie_word_embeddings to False
        if self.config.tie_word_embeddings:
            self.lm_head = self.model.embed_tokens
        else:
            self.lm_head = ParallelLMHead(
                getattr(config, "hot_vocab_size", config.vocab_size),
                config.hidden_size,
                quant_config=quant_config,
                prefix=add_prefix("lm_head", prefix),
            )

File: python/sglang/srt/models/minicpm3.py (L470-471)

            if self.config.tie_word_embeddings and "lm_head.weight" in name:
                continue

File: python/sglang/srt/model_loader/loader.py (L1453-1457)

        # we can only know if tie word embeddings after mapping weights
        if "lm_head.weight" in get_gguf_extra_tensor_names(
            local_model_path, gguf_weights_map
        ):
            model_config.hf_config.update({"tie_word_embeddings": True})

File: python/sglang/srt/configs/internvl.py (L347-349)

        # By default, we use tie_word_embeddings=False for models of all sizes.
        self.tie_word_embeddings = False
        self.llm_config.tie_word_embeddings = self.tie_word_embeddings

File: python/sglang/srt/models/bailing_moe.py (L816-827)

        # tie_word_embeddings为true,复用tie_word_embeddings,反之是独立的
        if config.tie_word_embeddings:
            self.lm_head = self.model.word_embeddings
        else:
            # TODO something wrong with ParallelLMHead with DP attention enabled
            self.lm_head = ParallelLMHead(
                config.vocab_size,
                config.hidden_size,
                quant_config=quant_config,
                prefix=add_prefix("lm_head", prefix),
                use_attn_tp_group=get_global_server_args().enable_dp_lm_head,
            )

Answer

您问的是 mcore_adapter/src/mcore_adapter/models/converter/dist_converter.py:621-622 这段代码的含义 1

代码含义

这段代码的逻辑是:

if self.revert and name == MCORE_LM_HEAD:
    return None  # don't need a duplicate lm head

“revert 模式” 指的是将分布式并行的模型权重转换回单机 HuggingFace 格式的过程 2 。这个转换器有两个方向:

  1. 正向转换(revert=False:从 HF 格式转换为 Megatron-Core 分布式格式 2
  2. 反向转换(revert=True:从 Megatron-Core 分布式格式转换回 HF 格式 2

为什么跳过 lm_head

当满足以下条件时,会跳过 lm_head 权重:

  1. 使用 tie_embeddings_and_output_weights=True:这个配置表示 lm_headword_embeddings 共享权重 3
  2. 使用 pipeline parallelism:模型被切分到多个 pipeline stage 4
  3. 在 pipeline 最后一个 stagelm_headword_embeddings 都在最后一个 stage 5
  4. 正在进行 revert 转换:从分布式格式转回 HF 格式 6

在这种情况下,返回 None 意味着不需要单独保存 lm_head 权重,因为它和 word_embeddings 是同一份权重 7

完整的权重共享逻辑

在同一个函数中,您可以看到完整的处理逻辑 8

  • 正向转换时(not self.revert:将 word_embeddings 的权重加载到 lm_head 9
  • 反向转换时(self.revert:跳过 lm_head,避免重复保存 1

与您的问题的关系

这段代码是在模型保存/转换时使用的,而不是在权重更新时使用的 10 。您遇到的 lm_head 权重未更新问题,更可能是因为:

  1. 权重更新时序问题:在 dummy 模式下,lm_head 初始化为随机权重,但权重更新可能没有覆盖到它
  2. 参数名称不匹配:训练端发送的参数名称和推理端期望的参数名称不一致
  3. 权重共享配置不一致:训练端和推理端对 tie_embeddings_and_output_weights 的配置不同

Notes

  • MCORE_LM_HEADMCORE_WORD_EMBEDDING 是常量,分别定义为 "output_layer.weight""embedding.word_embeddings.weight" 11
  • 这个转换器主要用于 checkpoint 的保存和加载,例如在 convert_checkpoint_to_hf 函数中 12
  • 权重共享是 Qwen 等模型的常见配置,可以减少参数量 3
  • 如果您想调试权重更新问题,建议在 update_parameter_in_bucket 方法中添加日志,查看哪些参数被成功加载

Citations

File: mcore_adapter/src/mcore_adapter/models/converter/dist_converter.py (L33-34)

MCORE_WORD_EMBEDDING = "embedding.word_embeddings.weight"
MCORE_LM_HEAD = "output_layer.weight"

File: mcore_adapter/src/mcore_adapter/models/converter/dist_converter.py (L214-214)

        revert: bool = False,

File: mcore_adapter/src/mcore_adapter/models/converter/dist_converter.py (L616-624)

        if (
            self.mca_config.tie_embeddings_and_output_weights
            and self.mca_config.pipeline_model_parallel_size > 1
            and self.is_pipeline_last_stage()
        ):
            if self.revert and name == MCORE_LM_HEAD:
                return None  # don't need a duplicate lm head
            elif not self.revert and name == MCORE_WORD_EMBEDDING:
                name = MCORE_LM_HEAD  # load word embedding weight to lm head

File: mcore_adapter/src/mcore_adapter/models/converter/post_converter.py (L55-55)

        mca_named_weights = dist_reverter(mca_name, weights)

File: mcore_adapter/src/mcore_adapter/models/converter/post_converter.py (L79-81)

def convert_checkpoint_to_hf(
    model_name_or_path: str, save_directory: str, torch_dtype: Optional["torch.dtype"] = None, verbose: bool = True
):

Answer

您问的是当 Pipeline Parallelism (PP) size 为 1tie_embeddings_and_output_weights=True 时的情况

关键区别

当 PP size 为 1 时,权重转换逻辑会有所不同。让我们看看 dist_convert 方法中的条件判断 1

if (
    self.mca_config.tie_embeddings_and_output_weights
    and self.mca_config.pipeline_model_parallel_size > 1  # 注意这个条件
    and self.is_pipeline_last_stage()
):
    if self.revert and name == MCORE_LM_HEAD:
        return None  # don't need a duplicate lm head
    elif not self.revert and name == MCORE_WORD_EMBEDDING:
        name = MCORE_LM_HEAD  # load word embedding weight to lm head

关键点:这个特殊处理逻辑只在 pipeline_model_parallel_size > 1 时才会触发 2

PP size = 1 的行为

当 PP size 为 1 时:

  1. 不会跳过 lm_head 权重:因为条件 pipeline_model_parallel_size > 1 不满足,所以不会执行 return None 的逻辑 2
  2. lm_headword_embeddings 仍然共享权重:虽然不会跳过转换,但在模型配置层面,它们仍然是同一份权重 3
  3. 权重更新应该正常工作:因为没有特殊的跳过逻辑,lm_head 权重应该能够正常接收更新

为什么 PP > 1 时需要特殊处理

在 Pipeline Parallelism 中,word_embeddings 通常在第一个 stage,而 lm_head 在最后一个 stage。但当 tie_embeddings_and_output_weights=True 时,word_embeddings 也需要在最后一个 stage 存在(用作 lm_head4

if self.name_match(weight_name, self.config.pre_process_weights):
    # mtp and tie_embeddings_and_output_weights use embedding weights in last stage
    if weight_name == MCORE_WORD_EMBEDDING and (
        self.mca_config.mtp_num_layers or self.mca_config.tie_embeddings_and_output_weights
    ):
        if self.is_pipeline_last_stage():
            return True

这就是为什么在 PP > 1 时需要特殊处理:避免在保存 checkpoint 时重复保存同一份权重 5

对您问题的影响

如果您的配置是 PP size = 1tie_embeddings_and_output_weights=True

  1. lm_head 权重应该能正常更新:不会触发跳过逻辑
  2. 如果仍然出现乱码:问题可能不在于这个转换逻辑,而在于:
    • 权重更新的时序问题(第一次推理在权重更新之前)
    • SGLang 在 dummy 模式下的初始化问题
    • 参数名称映射问题

建议

对于 PP size = 1 的情况,建议:

  1. 检查权重更新日志:确认 lm_head 是否在 load_weights 调用中
  2. 对比参数名称:确认训练端和推理端的参数名称一致
  3. 如果问题仍然存在:考虑使用 load_format="auto" 而不是 dummy

Notes

  • PP size = 1 是最简单的情况,不涉及跨 stage 的权重共享问题
  • 在 PP size = 1 时,所有层都在同一个 stage,is_pipeline_first_stage()is_pipeline_last_stage() 都返回 True 6
  • 如果您使用的是 Qwen 模型,tie_embeddings_and_output_weights 默认为 True 3
  • 这个转换逻辑主要影响 checkpoint 的保存和加载,对于运行时的权重更新,应该通过 update_parameter_in_bucket 方法处理

Citations

File: mcore_adapter/src/mcore_adapter/models/converter/dist_converter.py (L266-272)

                # mtp and tie_embeddings_and_output_weights use embedding weights in last stage
                if weight_name == MCORE_WORD_EMBEDDING and (
                    self.mca_config.mtp_num_layers or self.mca_config.tie_embeddings_and_output_weights
                ):
                    if self.is_pipeline_last_stage():
                        return True
                return self.is_pipeline_first_stage()

File: mcore_adapter/src/mcore_adapter/models/converter/dist_converter.py (L295-303)

    def is_pipeline_last_stage(self):
        return self.pipeline_model_parallel_rank == (
            self.mca_config.pipeline_model_parallel_size - 1
        ) and self.virtual_pipeline_model_parallel_rank == (
            (self.mca_config.virtual_pipeline_model_parallel_size or 1) - 1
        )

    def is_pipeline_first_stage(self):
        return self.pipeline_model_parallel_rank == 0 and self.virtual_pipeline_model_parallel_rank == 0

File: mcore_adapter/src/mcore_adapter/models/converter/dist_converter.py (L616-624)

        if (
            self.mca_config.tie_embeddings_and_output_weights
            and self.mca_config.pipeline_model_parallel_size > 1
            and self.is_pipeline_last_stage()
        ):
            if self.revert and name == MCORE_LM_HEAD:
                return None  # don't need a duplicate lm head
            elif not self.revert and name == MCORE_WORD_EMBEDDING:
                name = MCORE_LM_HEAD  # load word embedding weight to lm head

File: mcore_adapter/src/mcore_adapter/models/model_config.py (L177-180)

    tie_embeddings_and_output_weights: bool = field(
        default=False,
        metadata={"help": "Untie embeddings and output weights."},
    )
Logo

更多推荐