From 7e284c112c9752f1877bdfc3984f78d4e100f505 Mon Sep 17 00:00:00 2001 From: hsz06 <1261081994@qq.com> Date: Mon, 29 Sep 2025 17:54:37 +0800 Subject: [PATCH] new file: paddlenlp/transformers/convert/doc/README.md new file: paddlenlp/transformers/convert/doc/llama_analysis.md new file: paddlenlp/transformers/convert/doc/process.png new file: paddlenlp/transformers/convert/doc/qwen2_analysis.md new file: "paddlenlp/transformers/convert/doc/\351\241\271\347\233\256\346\212\245\345\221\212\357\274\232\351\243\236\346\241\250PaddleNLP-\345\211\215\346\262\277\346\250\241\345\236\213\346\250\241\345\235\227\345\214\226\350\256\276\350\256\241.md" new file: paddlenlp/transformers/convert/main.py new file: paddlenlp/transformers/convert/test_ability/test_base/test_modeling.py new file: paddlenlp/transformers/convert/test_ability/test_paralle/compare_torch_with_paddle.py new file: paddlenlp/transformers/convert/until/collect_import_modeling.py new file: paddlenlp/transformers/convert/until/rename_identifiers.py new file: paddlenlp/transformers/convert/until/rewrite_child_classes.py modified: paddlenlp/transformers/qwen2/configuration.py new file: paddlenlp/transformers/qwen2/modeling_qwen2.py new file: paddlenlp/transformers/qwen2/modular_qwen2.py --- paddlenlp/transformers/convert/doc/README.md | 268 ++ .../convert/doc/llama_analysis.md | 60 + .../transformers/convert/doc/process.png | Bin 0 -> 322796 bytes .../convert/doc/qwen2_analysis.md | 87 + ...27\345\214\226\350\256\276\350\256\241.md" | 150 ++ paddlenlp/transformers/convert/main.py | 158 ++ .../test_ability/test_base/test_modeling.py | 45 + .../test_paralle/compare_torch_with_paddle.py | 50 + .../convert/until/collect_import_modeling.py | 259 ++ .../convert/until/rename_identifiers.py | 109 + .../convert/until/rewrite_child_classes.py | 649 +++++ paddlenlp/transformers/qwen2/configuration.py | 51 +- .../transformers/qwen2/modeling_qwen2.py | 2243 +++++++++++++++++ paddlenlp/transformers/qwen2/modular_qwen2.py | 1313 ++++++++++ 14 files changed, 5441 insertions(+), 1 deletion(-) create mode 100644 paddlenlp/transformers/convert/doc/README.md create mode 100644 paddlenlp/transformers/convert/doc/llama_analysis.md create mode 100644 paddlenlp/transformers/convert/doc/process.png create mode 100644 paddlenlp/transformers/convert/doc/qwen2_analysis.md create mode 100644 "paddlenlp/transformers/convert/doc/\351\241\271\347\233\256\346\212\245\345\221\212\357\274\232\351\243\236\346\241\250PaddleNLP-\345\211\215\346\262\277\346\250\241\345\236\213\346\250\241\345\235\227\345\214\226\350\256\276\350\256\241.md" create mode 100644 paddlenlp/transformers/convert/main.py create mode 100644 paddlenlp/transformers/convert/test_ability/test_base/test_modeling.py create mode 100644 paddlenlp/transformers/convert/test_ability/test_paralle/compare_torch_with_paddle.py create mode 100644 paddlenlp/transformers/convert/until/collect_import_modeling.py create mode 100644 paddlenlp/transformers/convert/until/rename_identifiers.py create mode 100644 paddlenlp/transformers/convert/until/rewrite_child_classes.py create mode 100644 paddlenlp/transformers/qwen2/modeling_qwen2.py create mode 100644 paddlenlp/transformers/qwen2/modular_qwen2.py diff --git a/paddlenlp/transformers/convert/doc/README.md b/paddlenlp/transformers/convert/doc/README.md new file mode 100644 index 000000000000..8623ddff561c --- /dev/null +++ b/paddlenlp/transformers/convert/doc/README.md @@ -0,0 +1,268 @@ + + +## 使用方法: + +### 1构建modular__**.py文件 + +#### 1.1分析基础模型 + +​ 在开始构建 `modular_xxx.py`文件之前,首先需要深入分析要基于的**基础模型**。这个基础模型通常是 paddle/ Transformers 库中已有的成熟模型。 + +##### 1.1.1 选择合适的基础模型 + +**选择标准:** + +- **架构相似性**:新模型与基础模型的架构应尽可能相似 +- **任务类型**:基础模型应支持相同的任务(如文本生成、分类等) +- **代码质量**:选择代码结构清晰、文档完善的模型 + +**常见基础模型选择:** + +``` +# 基于BERT架构的模型 +基础模型:BertModel, RobertaModel, DebertaModel + +# 基于GPT架构的模型 +基础模型:GPT2Model, LlamaModel, GPTNeoXModel + +# 基于Encoder-Decoder架构的模型 +基础模型:T5Model, BartModel, PegasusModel +``` + +##### 1.1.2 分析基础模型的关键组件 + +对于选定的基础模型,需要分析其核心组件: + +###### **1. 配置文件 (`configuration_xxx.py`)** + +``` +# 分析配置参数 +# 关注:hidden_size, num_attention_heads, num_hidden_layers, +# vocab_size, max_position_embeddings 等关键参数 +``` + +###### **2. 模型架构 (`modeling_xxx.py`)** + +``` +# 分析模型类结构 +import inspect +from transformers import BertModel + +# 查看类的方法和属性 +print(inspect.getmembers(BertModel, predicate=inspect.ismethod)) +# 重点关注:__init__, forward, 以及其他关键方法 +``` + +##### 1.1.3 识别需要修改的部分 + +基于分析结果,确定哪些部分需要自定义: + +| 组件 | 是否需要修改 | 修改原因 | +| :--------------- | :----------- | :------------------------- | +| **配置参数** | ✅ 通常需要 | 调整模型尺寸、注意力头数等 | +| **前向传播逻辑** | ✅ 通常需要 | 适配新的架构变化 | +| **注意力机制** | ⚠️ 可能需要 | 如果使用不同的注意力机制 | +| **位置编码** | ⚠️ 可能需要 | 如果使用不同的位置编码方案 | +| **输出头** | ✅ 通常需要 | 适配不同的任务需求 | +| **初始化方法** | ⚠️ 可能需要 | 如果使用不同的初始化策略 | + +#### 1.2编写modular文件结构 + +​ 在完成基础模型分析后,您需要创建一个结构清晰、符合规范的 `modular_xxx.py`文件。这个文件是代码生成器的模板,其结构直接决定了最终输出的 `modeling_xxx.py`文件的质量。 + +##### 1.2.1 文件基本结构 + +一个标准的 `modular_xxx.py`文件应包含以下部分,按顺序排列: + +``` +# coding=utf-8 +# 版权声明 (可选) +""" 新模型的简要文档字符串 (可选) """ + +# 1. 导入部分 +from typing import Optional, Tuple, Union + +import torch +import torch.utils.checkpoint +from torch import nn +from torch.nn import CrossEntropyLoss + +# 从基础模型导入必要的组件 +from transformers.models.llama.modeling_llama import ( + LlamaConfig, + LlamaModel, + LlamaForCausalLM, + LlamaDecoderLayer, + # ... 其他需要继承或引用的组件 +) +from transformers import PreTrainedModel, PreTrainedTokenizerBase +from transformers.utils import logging + + +logger = logging.get_logger(__name__) + +# 3. 注意力机制 (如果需要自定义) +class MyNewAttention(nn.Module): + """自定义注意力机制""" + def __init__(self, config: MyNewModelConfig): + super().__init__() + # 实现自定义注意力逻辑 + pass + + def forward(self, hidden_states, attention_mask=None): + # 实现前向传播 + pass + + +# 4. 解码器层 (如果需要修改层结构) +class MyNewDecoderLayer(LlamaDecoderLayer): + """ + 自定义解码器层,继承自LlamaDecoderLayer + 重写需要修改的方法 + """ + def __init__(self, config: MyNewModelConfig): + super().__init__(config) + # 替换或修改注意力机制 + if config.use_custom_attention: + self.self_attn = MyNewAttention(config) + + def forward(self, hidden_states, attention_mask=None): + # 可以完全重写或部分修改父类逻辑 + if self.config.use_custom_attention: + # 自定义逻辑 + return self._custom_forward(hidden_states, attention_mask) + else: + # 回退到父类逻辑 + return super().forward(hidden_states, attention_mask) + + def _custom_forward(self, hidden_states, attention_mask): + """自定义前向传播实现""" + pass + + +# 5. 主模型类 +class MyNewModel(LlamaModel): + """ + 我的新模型主类,继承自LlamaModel + 通常需要重写 __init__ 和 forward 方法 + """ + def __init__(self, config: MyNewModelConfig): + super().__init__(config) + # 替换解码器层 + self.layers = nn.ModuleList([ + MyNewDecoderLayer(config) for _ in range(config.num_hidden_layers) + ]) + # 其他自定义初始化 + self.custom_layer = nn.Linear(config.hidden_size, config.custom_param) + + def forward(self, input_ids, attention_mask=None): + # 调用父类获取基础输出 + super().forward(input_ids, attention_mask) + + # 添加自定义处理 + hidden_states = outputs[0] + custom_output = self.custom_layer(hidden_states) + + # 返回修改后的输出 + return (custom_output,) + outputs[1:] + + +# 6. 任务特定模型 (如用于因果语言建模) +class MyNewForCausalLM(LlamaForCausalLM): + """ + 用于因果语言建模的我的新模型 + """ + def __init__(self, config: MyNewModelConfig): + super().__init__(config) + # 替换主模型 + self.model = MyNewModel(config) + + def forward(self, input_ids, attention_mask=None, labels=None): + # 可以完全重写或扩展父类逻辑 + outputs = self.model(input_ids, attention_mask=attention_mask) + + # 计算损失等 + loss = None + if labels is not None: + # 计算损失逻辑 + pass + + return {"loss": loss, "logits": outputs[0]} + + +# 8. 更新 __all__ 列表,声明哪些类应该被导出 +__all__ = [ + "MyNewModelConfig", + "MyNewModel", + "MyNewForCausalLM", + "MyNewDecoderLayer", +] +``` + +##### 1.2.2 关键编写原则 + +**清晰的继承关系** + +``` +# ✅ 正确:明确继承关系 +class MyNewModel(LlamaModel): + pass + +# ❌ 避免:直接继承过于通用的基类 +class MyNewModel(PreTrainedModel): + pass # 这会导致需要实现大量抽象方法 +``` + +**最小化重写** + +``` +# ✅ 正确:只重写需要修改的方法 +class MyNewDecoderLayer(LlamaDecoderLayer): + def __init__(self, config): + super().__init__(config) # 先调用父类初始化 + # 只修改需要定制的部分 + if config.use_custom_attention: + self.self_attn = CustomAttention(config) + +# ❌ 避免:完全重写整个类,除非必要 +``` + +**保持接口一致性**: + +``` +def forward(self, input_ids, attention_mask=None, **kwargs): + # 处理自定义逻辑 + result = custom_processing(input_ids) + # 调用父类实现剩余逻辑 + super().forward(result, attention_mask, **kwargs) +``` + +**充分利用现有组件**: + +``` +# ✅ 正确:复用基础模型的组件 +from transformers.models.llama.modeling_llama import ( + LlamaRMSNorm, + LlamaRotaryEmbedding, + apply_rotary_pos_emb, +) +``` + +### 2.**执行转换命令** + +通过一个用户主脚本main来驱动整个流程。其标准使用方式如下: + +``` +#自动查找各个模型文件下的modular__**.py模块化构建代码,执行转换生成modeling__***.py文件 +python main.py +``` + +### **自动化处理流水线** + + + +### 最终输出 + +最终,在模型对应的目录下(如 `src/transformers/models/qwen2/`)会生成目标文件: + +- **`modeling_qwen2.py`**:**这是唯一的输出文件,也是最终成果。** 它包含了:**模型架构**(如 `Qwen2Model`, `Qwen2ForCausalLM`)**内联的配置类**(如 `Qwen2Config`)**所有相关的函数、工具类和常量****正确的导入语句**(只导入标准库或 transformers 的通用组件)**文件顶部的警告注释**:明确告知开发者此文件为自动生成,不可手动编辑。 \ No newline at end of file diff --git a/paddlenlp/transformers/convert/doc/llama_analysis.md b/paddlenlp/transformers/convert/doc/llama_analysis.md new file mode 100644 index 000000000000..603e95d422e3 --- /dev/null +++ b/paddlenlp/transformers/convert/doc/llama_analysis.md @@ -0,0 +1,60 @@ +以下是针对Llama模型架构中各组件的功能解析,按模块分类说明其核心作用: + +------ + +### **核心工具函数** + +| 函数/常量 | 作用 | 与Qwen2的差异 | +| :----------------------------: | :-----------------------------------: | :--------------------: | +| `swiglu` | 实现SwiGLU激活函数:`x * silu(gate)` | Qwen2使用标准GLU | +| `rms_norm_fused` | 启用融合的RMSNorm计算(CUDA优化) | 实现相同但配置参数不同 | +| `__all__` | 定义模块的公开接口 | - | +| `_get_interleave` | 生成交错注意力头索引(用于长序列) | Llama特有 | +| `build_alibi_tensor` | 构建ALiBi位置偏置张量(相对位置编码) | Qwen2未使用 | +| `get_triangle_upper_mask` | 生成因果上三角掩码 | 实现逻辑相同 | +| `assign_kv_heads` | 分配KV头的索引(支持GQA/MQA) | - | +| `parallel_matmul` | 并行矩阵乘法(张量并行) | - | +| `scaled_dot_product_attention` | 核心注意力计算 | Llama支持更多掩码类型 | +| `_make_causal_mask` | 动态生成因果掩码(考虑padding) | Qwen2更简化 | + +### **归一化层** + +| 类/函数 | 作用 | 差异点 | +| :------------: | :-------------------: | :-------------: | +| `LlamaRMSNorm` | 带融合优化的RMS归一化 | 与Qwen2实现相同 | + +### **位置编码(核心差异)** + +| 类 | 作用 | 特性 | +| :-------------------------------------: | :------------------------: | :---------------: | +| `LlamaRotaryEmbedding` | 基础RoPE实现 | - | +| `LlamaLinearScalingRotaryEmbedding` | 线性缩放RoPE(扩展上下文) | Qwen2无此变体 | +| `LlamaNTKScalingRotaryEmbedding` | NTK-aware缩放RoPE | 动态调整高频/低频 | +| `LlamaDynamicNTKScalingRotaryEmbedding` | 动态NTK缩放(训练自适应) | Llama特有 | +| `Llama3RotaryEmbedding` | Llama3专用RoPE | 改进的旋转策略 | + +### **前馈网络** + +| 类 | 作用 | 差异 | +| :--------: | :-----------------: | :------------: | +| `LlamaMLP` | 使用SwiGLU的门控FFN | Qwen2用普通GLU | + +### **注意力机制** + +| 类 | 核心改进 | 说明 | +| :-----------------: | :----------------------------------------: | :------------: | +| `LlamaAttention` | - 多版本RoPE支持 - ALiBi融合 - 动态NTK缩放 | 比Qwen2更复杂 | +| `LlamaDecoderLayer` | 深度优化层实现 | 支持梯度检查点 | + +### **预训练基础** + +| 类 | 关键功能 | 扩展性 | +| :--------------------: | :--------------------------: | :-----------: | +| `LlamaPretrainedModel` | - 多设备加载 - FLOPs计算工具 | 比Qwen2更完善 | + +### **任务模块** + +| 类 | 用途 | 特色 | +| :----------------: | :------------: | :---------------: | +| `LlamaForCausalLM` | 语言建模 | 支持静态图导出 | +| `ConcatMaskedLoss` | 多任务损失合并 | 处理padding的梯度 | \ No newline at end of file diff --git a/paddlenlp/transformers/convert/doc/process.png b/paddlenlp/transformers/convert/doc/process.png new file mode 100644 index 0000000000000000000000000000000000000000..317248b7d0406592beedf57657a725dfb71314c3 GIT binary patch literal 322796 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%0A@o;^rkoXAet`5?<|fF8ZNykRd3Xw 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

VQ&HnjK&eHJlnN82q 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ⅈozG1Y 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 literal 0 HcmV?d00001 diff --git a/paddlenlp/transformers/convert/doc/qwen2_analysis.md b/paddlenlp/transformers/convert/doc/qwen2_analysis.md new file mode 100644 index 000000000000..4ac53f797ee7 --- /dev/null +++ b/paddlenlp/transformers/convert/doc/qwen2_analysis.md @@ -0,0 +1,87 @@ +![img](https://i-blog.csdnimg.cn/blog_migrate/4757ad5c98d4344547227fc52684bac1.png) + +是Qwen2模型中各组件和函数的详细作用说明,按模块分类整理: + +### **核心工具函数** + +| 函数/常量 | 作用 | +| :----------------------------: | :------------------------------------------------------: | +| `__all__` | 定义模块的公开接口,控制`from module import *`时的可见性 | +| `get_triangle_upper_mask` | 生成上三角因果注意力掩码(防止未来信息泄露) | +| `assign_kv_heads` | 分配Key/Value头的索引(用于GQA/MQA) | +| `parallel_matmul` | 并行矩阵乘法(支持张量并行) | +| `scaled_dot_product_attention` | 实现缩放点积注意力核心计算 | +| `masked_fill` | 按掩码填充张量(如将padding位置设为负无穷) | +| `is_casual_mask` | 判断是否为因果注意力掩码 | +| `_make_causal_mask` | 创建因果注意力掩码(考虑padding) | +| `_expand_2d_mask` | 将2D掩码扩展为4D(适配多头注意力) | +| `repeat_kv` | 重复Key/Value头(用于GQA/MQA) | + +### **归一化层** + +| 类/函数 | 作用 | +| :------------: | :------------------------------: | +| `Qwen2RMSNorm` | **RMS归一化层**(替代LayerNorm) | + +### **位置编码** + +| 类/函数 | 作用 | +| :----------------------: | :--------------------------------: | +| `Qwen2RotaryEmbedding` | **旋转位置编码(RoPE)** | +| - `rotate_half` | 旋转向量的后半部分(RoPE核心操作) | +| - `apply_rotary_pos_emb` | 将旋转位置编码应用到注意力分数 | + +### **前馈网络** + +| 类/函数 | 作用 | +| :--------: | :---------------------------: | +| `Qwen2MLP` | **门控线性单元(GLU)前馈网络** | + +### **注意力机制** + +| 类/函数 | 作用 | +| :-----------------: | :------------------------------------------------: | +| `Qwen2Attention` | **多头注意力机制** | +| - `__init__` | 初始化Q/K/V投影层、输出层和RoPE | +| - `forward` | 处理输入序列,计算注意力分数并聚合值向量 | +| `Qwen2DecoderLayer` | **Transformer解码层** | +| - `__init__` | 组合自注意力层和前馈网络 | +| - `forward` | 执行:`LN -> Attention -> Add -> LN -> MLP -> Add` | + +### **预训练基础** + +| 类/函数 | 作用 | +| :--------------------: | :--------------------------------: | +| `Qwen2PretrainedModel` | **预训练模型基类** | +| - `config_class` | 关联的配置类(Qwen2Config) | +| - `_get_name_mappings` | 定义参数名称映射(用于加载检查点) | +| - `_init_weights` | 参数初始化策略 | +| - `_get_model_flops` | 计算模型FLOPs | + +### **主干模型** + +| 类/函数 | 作用 | +| :---------------------------------: | :-----------------------: | +| `Qwen2Model` | **模型主干架构** | +| - `_prepare_decoder_attention_mask` | 生成解码器掩码 | +| - `forward` | 执行完整的Transformer堆栈 | +| `Qwen2ForCausalLM` | **因果语言模型** | +| - `prepare_inputs_for_generation` | 处理生成时的输入格式 | +| - `forward` | 计算语言建模损失 | + +### **任务特定头部** + +| 类 | 作用 | +| :------------------------------: | :----------------------: | +| `Qwen2LMHead` | 语言模型头部(词表投影) | +| `Qwen2ForSequenceClassification` | 序列分类任务适配 | +| `Qwen2ForTokenClassification` | 标记分类任务适配 | +| `Qwen2SentenceEmbedding` | 句子向量提取 | + +### **训练相关** + +| 类/函数 | 作用 | +| :-------------------------: | :------------------------: | +| `Qwen2PretrainingCriterion` | 预训练损失计算 | +| `recompute_training_full` | 激活重计算策略 | +| `create_custom_forward` | 为梯度检查点创建自定义前向 | diff --git "a/paddlenlp/transformers/convert/doc/\351\241\271\347\233\256\346\212\245\345\221\212\357\274\232\351\243\236\346\241\250PaddleNLP-\345\211\215\346\262\277\346\250\241\345\236\213\346\250\241\345\235\227\345\214\226\350\256\276\350\256\241.md" "b/paddlenlp/transformers/convert/doc/\351\241\271\347\233\256\346\212\245\345\221\212\357\274\232\351\243\236\346\241\250PaddleNLP-\345\211\215\346\262\277\346\250\241\345\236\213\346\250\241\345\235\227\345\214\226\350\256\276\350\256\241.md" new file mode 100644 index 000000000000..97f0e1382d41 --- /dev/null +++ "b/paddlenlp/transformers/convert/doc/\351\241\271\347\233\256\346\212\245\345\221\212\357\274\232\351\243\236\346\241\250PaddleNLP-\345\211\215\346\262\277\346\250\241\345\236\213\346\250\241\345\235\227\345\214\226\350\256\276\350\256\241.md" @@ -0,0 +1,150 @@ +## 项目报告:飞桨PaddleNLP-前沿模型模块化设计 + +### 项目信息 + +* 项目名称:飞桨PaddleNLP-前沿模型模块化设计 +* 方案描述: + * 文件解析与并行处理:自动查找并并行处理 modular_*.py 文件。 + * 转换流程 (run_converter):封装了单个模块化文件到独立模型文件的完整转换逻辑,包括动态确定模型名称和输出路径、收集并展开导入、重写子类并生成中间文件、移除导入并重写、全局重命名以及最终文件写入和临时文件清理。 + * 辅助工具 (until 目录):包含 collect_import_modeling.py、rename_identifiers.py 和 rewrite_child_classes.py 等脚本,用于支持转换流程中的导入处理、标识符重命名和子类重写。 + * modular_qwen2.py:作为 convert 工具的输入,该文件继承了 Llama 模型的许多组件,并进行了 Qwen2 特有的修改和优化,如 Qwen2RMSNorm、Qwen2RotaryEmbedding、Qwen2MLP 和 Qwen2Attention。它还包含了大量与 PaddlePaddle 分布式训练(如张量并行、序列并行、重计算)和性能优化(如 Flash Attention、融合操作)相关的导入和逻辑。 + * configuration.py:定义了 Qwen2Config 类,存储 Qwen2 模型的所有配置参数,包括词汇表大小、隐藏层维度、注意力头数量、激活函数、最大位置嵌入长度等,并支持滑动窗口注意力等高级特性。 + * modeling__qwen2.py:是经过 convert 工具处理后生成的最终模型文件,包含了 Qwen2 模型的完整实现,包括核心模型类、组件实现、辅助函数以及分布式训练和性能优化相关的代码。 + +* 时间规划: + + * 需求分析与方案设计(7月1日-7月15日) + + 详细调研和对比分析至少两种主流LLM(如Llama系列、Qwen系列)的架构细节、实现差异 和共通之处。 设计LLM的模块化组件体系,明确各模块的功能边界、输入输出接口、可配置参数以及模块 间的依赖关系。制定基于libcst的源码分析策略,确定需要识别的代码模式和转换规则。 初步规划模型并行能力的模块化方案和自动化集成思路 + + * 模块化核心功能开发(7月15日-8月15日) + + 利用libcst等工具,开发源码分析和转换工具的原型,能够解析现有模型代码并提取关键 结构信息,或根据配置生成初步的模块化代码片段。 搭建单元测试和集成测试框架,确保各模块和工具的正确性。 + + * Qwen2模型自动化构造(8月15日-9月7日) + + 以Llama模型结构为蓝本,利用阶段二开发的工具和模块库,自动化生成Qwen2模型的完整结构代码 + + * 精度对齐及模型并行能力验证(9月7日-9月21日) + + 对生成的Qwen2模型进行细致的功能测试和精度验证,通过在测试数据上与手动实现的 Qwen2模型进行效果对比,确保数值精度对齐。 + + * 文档撰写与项目总结(9月21日-9月30日) + + 编写详细的设计文档、用户手册、上手教程以及最佳实践案例。 整理项目代码,按照PaddleNLP社区规范准备Pull Request,将核心成果贡献给社区。完成项目总结报告。 + +### 项目进度 + +* 已完成工作: + + 对照项目申请书的方案,我完成了预定任务,主要工作成果如下: + + * 核心转换流水线 + - 实现了`run_converter()`函数,执行完整的三阶段转换流程 + - 支持并行处理多个模型文件转换 + - 自动生成带警告标识的输出文件 + * 导入扩展系统 + - 实现`expand_modeling_imports()`函数进行递归依赖解析 + - 支持模块化导入的自动展开和集成 + * 标识符重命名系统 + - 实现`rename_identifiers()`函数进行智能重命名 + - 支持大小写保持的标识符转换 + * 类重写系统 + - 实现完整的类重写工具,支持继承关系扁平化 + - 集成依赖分析和合并引擎 + * 以Llama为蓝本的Qwen2模型自动化生成 + * 实现了基于llama的modular__qwen2.py + * 通过转换系统自动生成完整的Qwen2模型实现 + * 对生成的模型进行了精度验证和并行能力验证 + * 与原代码进行精度对齐 + * 进行了并行能力验证 + * 项目文档 + * 转换工具的使用方法 + * 模块化构建的流程 + * 精度及并行能力验证报告 + +* 遇到的问题以及解决方案 + + * Import导入项收集的复杂性问题 + + 存在的难点: + + - (1)重复导入识别:同一个模块可能通过不同路径被多次导入,需要去重处理 + - (2)导入格式多样性:存在相对导入(`..modeling`)、绝对导入等多种格式,解析复杂 + - (3)循环依赖检测:模块间可能存在相互依赖,导致无限递归 + - (4)无效导入过滤:需要区分真正的modeling导入和其他类型的导入 + + 针对以上问题,我提出了分层过滤解决方案: + + - (1)专门的导入收集器: collect_import_modeling实现`ModelingImportCollector`专门识别包含"modeling"关键字的导入语句 + - (2)路径标准化处理: collect_import_modeling通过`resolve_file_path()`函数统一处理相对导入路径转换 + - (3)循环依赖避免机制: collect_import_modeling使用`seen`集合记录已处理的依赖项,防止无限递归 + - (4)严格模式过滤:filter_specific_modeling_imports()`只保留严格符合相对导入模式的modeling导入 + + * 大规模文件处理效率 + + 存在的难点: + + - (1)单线程处理效率低:大量模型文件的串行处理耗时过长 + - (2)内存占用过高:同时加载多个大型模型文件导致内存压力 + + 针对以上问题,我构建了并行处理架构: + + - (1)多进程并行化: main.py使用`multiprocessing.Pool`实现多进程并行转换,动态调整工作进程数量 + - (2)临时文件管理: main.py:65-68 处理完成后自动清理临时文件,减少内存占用 + + * 标识符重命名一致性 + + 存在的难点: + + - (1)大小写风格保持:需要保持原代码的命名风格(如llama→qwen2, Llama→Qwen2, LLAMA→QWEN2) + - (2)误替换风险:字符串级别的替换容易产生误替换和语法错误 + - (3)冲突检测复杂:需要避免与已存在的标识符产生命名冲突 + + 针对以上问题,我开发了AST级别的智能重命名系统: + + - (1)大小写保持算法: rename_identifiers.py通过`_case_preserving_replace()`方法检测原标识符的大小写模式并应用到目标名称 + - (2)AST精确转换: rename_identifiers.py 使用`GenericRenamerTransformer`在AST层面进行精确替换,避免误替换 + - (3)智能冲突检测: rewrite_child_classes.py维护`existing_names`集合检测命名冲突,只注入不冲突的依赖项 + + * 注入依赖时的位置问题 + + 存在的难点: + + - (1)依赖注入顺序混乱:不同类型的依赖(方法、类)混合注入导致代码结构不清晰 + - (2)父子类位置关系错误:子类可能在父类定义之前被注入,导致引用错误 + - (3)代码可读性差:依赖项随意插入破坏了代码的逻辑结构和可维护性 + + 针对以上问题,我实现了智能的分层注入策略: + + - (1)依赖类型分类注入: 系统将注入的依赖分为方法和类两类,方法优先注入在imports之后,类按依赖关系分层注入 + - (2)父子类依赖关系排序: 通过分析类的继承关系,将有父类依赖的类和无父类依赖的类分开处理,确保父类先于子类定义 + - (3)动态位置插入机制: 在遍历主逻辑时,当遇到父类定义后立即插入其对应的子类,保证依赖关系的正确性和代码的逻辑连贯性 + +* 测试用例 + + * 模型转换正确性验证 + + 测试用例的核心是验证从`modular_qwen2.py`转换生成的`modeling_qwen2.py`的功能正确性 + + * 双模型对比测试:加载原始的modular_qwen2模型和转换后的modeling_qwen2模型进行数值对比 + * 精度验证:使用相同的输入数据,两个模型输出的数值使用`numpy.allclose`进行数值对比,相对容差`rtol=1e-5`,绝对容差`atol=1e-3`;转换后的模型与原模型输出相同,模型转换正确。 + + * 精度对齐与并行能力验证 + + 测试用例的核心是验证从`modular_qwen2.py`转换生成的`modeling_qwen2.py`的并行能力正确性 + + - 分布式训练兼容性:创建分布式并行环境,并使用paddle.distributed.launch进行启动运行,在张量并行度为2的配置下,转换后模型与原模型输出相对容差`rtol=1e-5`,绝对容差`atol=1e-3` + - 对于相同的输入产生了完全相同的输出,分布式能力验证成功。 + +* 后续工作安排 + + * 对于大规模文件的处理依赖 + + 对于多文件建立依赖关系图,从底层文件一次向上开始转换 + + * 完善pre-commit,实现自动化转换模块化文件并进行验证 + + * 扩展测试用例覆盖,完善精度对齐和并行能力验证文档中的测试场景 + + * 优化基础模型的构建,真正把基础模型标准化,模块化 \ No newline at end of file diff --git a/paddlenlp/transformers/convert/main.py b/paddlenlp/transformers/convert/main.py new file mode 100644 index 000000000000..9e66d9647abd --- /dev/null +++ b/paddlenlp/transformers/convert/main.py @@ -0,0 +1,158 @@ +import argparse +import glob +import os +import multiprocessing as mp +from pathlib import Path +import re + +from until.collect_import_modeling import expand_modeling_imports,remove_imports_and_rewrite,save_results_to_txt +from until.rewrite_child_classes import rewrite_child_classes +from until.rename_identifiers import rename_identifiers + +AUTO_GENERATED_MESSAGE = """# 🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨 +# This file was automatically generated from {relative_path}. +# Do NOT edit this file manually as any edits will be overwritten by the generation of +# the file from the modular. If any change should be done, please apply the change to the +# {short_name} file directly. One of our CI enforces this. +# 🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨 +""" + +# --- 核心转换逻辑封装 --- +def run_converter(file_to_parse_str: str): + """ + 对单个 modular 文件执行完整的转换流程。 + 这个函数是并行处理的基本单元。 + """ + print(file_to_parse_str) + file_to_parse = Path(file_to_parse_str) + + # --- 动态确定模型名称和输出路径 --- + # 假设:目标模型名可以从文件名推断,例如 "modular_qwen2.py" -> "qwen2" + to_name = file_to_parse.stem.replace("modular_", "") + # 假设:源模型名在模块化文件中有定义或约定俗成(这里我们先硬编码为 "llama" 作为示例) + # 一个更健壮的实现会从 file_to_parse 文件内容中解析出 from_name + + # 输出文件将与输入文件在同一目录下,例如 "modeling_qwen2.py" + output_file = file_to_parse.parent / f"modeling_{to_name}.py" + temp_merged_file = file_to_parse.parent / (output_file.stem + "_temp_merged.py") + + print(f"--- 开始转换: {to_name} ---") + print(f" 输入文件: '{file_to_parse}'") + print(f" 最终输出: '{output_file}'") + + # 步骤 1: 收集并展开 import + expanded_code , dependencies,from_name= expand_modeling_imports(file_to_parse) + #save_results_to_txt(dependencies, "modeling_imports_dependencies.txt") + #save_results_to_txt(expanded_code, "modeling_imports_results.txt") + #print(from_name) + # 步骤 2: 重写子类并生成中间文件 + relative_path = re.search( + r"(transformers/.*|examples/.*)", os.path.abspath(file_to_parse).replace("\\", "/") + ).group(1) + formatted_message=AUTO_GENERATED_MESSAGE.format(relative_path=relative_path, short_name=os.path.basename(relative_path)) + rewrite_child_classes(expanded_code, file_to_parse,formatted_message, temp_merged_file,rename_map={ + "llama": "qwen2" # 只需要提供小写形式! + }) + remove_imports_and_rewrite(temp_merged_file) + # 步骤 3: 全局重命名 + try: + merged_code = temp_merged_file.read_text(encoding="utf-8") + final_code = rename_identifiers(merged_code, from_name, to_name) + output_file.write_text(final_code, encoding="utf-8") + print(f" ✅ 转换成功,最终代码已写入 '{output_file}'。") + except FileNotFoundError: + print(f" ❌ [错误] 找不到中间文件 '{temp_merged_file}',无法进行重命名。") + finally: + # 清理临时文件 + if temp_merged_file.exists(): + temp_merged_file.unlink() + + print(f"--- 转换结束: {to_name} ---\n") + + +# --- 主执行逻辑 --- +def main(): + parser = argparse.ArgumentParser( + description="将模块化的模型定义文件(modular_*.py)转换为独立的模型文件(modeling_*.py)。" + ) + parser.add_argument( + "files", + nargs="*", + help="要转换的模块化文件列表(可选的位置参数)。", + ) + parser.add_argument( + "--files-to-parse", "-f", + dest="files_to_parse", # 明确指定存储的目的地 + default=[], # 默认值改为空列表 + nargs="+", + help="要转换的模块化文件列表。可使用 'all' 或 'examples' 关键字。", + ) + parser.add_argument( + "--num_workers", "-w", + default=-1, + type=int, + help="使用的进程数。默认为 -1,代表使用所有 CPU核心。", + ) + args = parser.parse_args() + + # 合并位置参数和可选参数,以可选参数优先 + files_to_parse = args.files_to_parse if args.files_to_parse else args.files + if not files_to_parse: + files_to_parse = ["all"] # 如果未提供任何文件,则默认为 'all' + + num_workers = mp.cpu_count() if args.num_workers == -1 else args.num_workers + + # --- 解析文件路径 --- + print(">>> 正在解析需要转换的文件...") + if files_to_parse == ["all"]: + from pathlib import Path + # 确定项目根目录 + SCRIPT_DIR = Path(__file__).resolve().parent + PROJECT_ROOT = SCRIPT_DIR.parent.parent.parent + # 使用绝对路径进行搜索 + search_path = PROJECT_ROOT / "paddleformers/transformers/**/modular_*.py" + files_to_parse = glob.glob(str(search_path), recursive=True) + elif files_to_parse == ["examples"]: + # 查找所有 examples 目录下的 modular 文件 + files_to_parse = glob.glob("examples/**/modular_*.py", recursive=True) + else: + # 将模型简称(如 qwen2)解析为完整路径 + resolved_files = [] + for model_name in files_to_parse: + if not os.path.exists(model_name): + # 尝试在 models 目录下构建路径 + full_path = os.path.join("src", "transformers", "models", model_name, f"modular_{model_name}.py") + if not os.path.isfile(full_path): + # 如果找不到,尝试在 examples 目录下构建 + full_path = os.path.join("examples", "modular-transformers", f"modular_{model_name}.py") + + if not os.path.isfile(full_path): + raise ValueError(f"无法为 '{model_name}' 找到模块化文件。请提供完整路径或确认文件名正确。") + resolved_files.append(full_path) + else: + resolved_files.append(model_name) + files_to_parse = resolved_files + + if not files_to_parse: + print("未找到任何需要转换的文件。") + return + + print(f"发现 {len(files_to_parse)} 个文件待处理。") + + + ordered_files= [files_to_parse] + print(ordered_files) + + # --- 按依赖顺序并行处理 --- + for i, dependency_level_files in enumerate(ordered_files): + print(f"\n>>> 开始处理依赖层级 {i+1}/{len(ordered_files)} ({len(dependency_level_files)} 个文件)...") + workers = min(num_workers, len(dependency_level_files)) + if workers > 0: + with mp.Pool(workers) as pool: + pool.map(run_converter, dependency_level_files) + + print("\n--- 所有转换任务已完成 ---") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/paddlenlp/transformers/convert/test_ability/test_base/test_modeling.py b/paddlenlp/transformers/convert/test_ability/test_base/test_modeling.py new file mode 100644 index 000000000000..0b1cdf229b4b --- /dev/null +++ b/paddlenlp/transformers/convert/test_ability/test_base/test_modeling.py @@ -0,0 +1,45 @@ +"""模型组网正确性验证 +【基本流程】 + +定义原模型,加载权重,固定seed,基于numpy生成随机数,转换为PyTorch可以处理的tensor,送入网络,获取输出。 + +定义模块化转换后modeling模型,加载权重,固定seed,基于numpy生成随机数,转换为PaddlePaddle可以处理的tensor,送入网络,获取输出。 + +排查diff,小于阈值,即可完成自测。 +""" +import numpy as np +import paddle +from paddleformers.transformers.qwen2 import Qwen2Config +from paddleformers.transformers.qwen2.modeling import Qwen2ForCausalLM +from paddleformers.transformers import Qwen2Config as Qwen2Config_hf +from paddleformers.transformers import Qwen2ForCausalLM as Qwen2ForCausalLM_hf +#from paddleformers.transformers.qwen2.test_model_expanded import Qwen2ForCausalLM as Qwen2ForCausalLM_hf + + + +def eval_model_convert(): + paddle_input_ids = paddle.to_tensor([[0, 345, 232, 328, 740, 140, 1695, 69, 6078, 1588, 2]]) + torch_input_ids = paddle.to_tensor([[0, 345, 232, 328, 740, 140, 1695, 69, 6078, 1588, 2]]) + + # paddle model + paddle_ckpt_path = "Qwen/Qwen2-0.5B" + config_paddle = Qwen2Config.from_pretrained(paddle_ckpt_path) + model_paddle = Qwen2ForCausalLM.from_pretrained(paddle_ckpt_path, config=config_paddle, dtype="float32") + + # torch model + + torch_ckpt_path = "Qwen/Qwen2-0.5B" + config_torch = Qwen2Config_hf.from_pretrained(torch_ckpt_path) + config_torch.dtype = "float32" + model_torch = Qwen2ForCausalLM_hf.from_pretrained(torch_ckpt_path, config=config_torch, dtype="float32") + + model_paddle.eval() + model_torch.eval() + + out_paddle = model_paddle(paddle_input_ids)[0] + out_torch = model_torch(torch_input_ids, return_dict=False)[0] + print(out_paddle) + print(out_torch) + assert np.allclose(out_paddle.numpy(), out_torch.detach().numpy(), rtol=1e-5, atol=1e-3) + +eval_model_convert() \ No newline at end of file diff --git a/paddlenlp/transformers/convert/test_ability/test_paralle/compare_torch_with_paddle.py b/paddlenlp/transformers/convert/test_ability/test_paralle/compare_torch_with_paddle.py new file mode 100644 index 000000000000..f8414ba936d6 --- /dev/null +++ b/paddlenlp/transformers/convert/test_ability/test_paralle/compare_torch_with_paddle.py @@ -0,0 +1,50 @@ +import numpy as np +import paddle +from paddle.distributed import fleet +from paddleformers.transformers.qwen2 import Qwen2Config +from paddleformers.transformers.qwen2.modeling import Qwen2ForCausalLM +from paddleformers.transformers import Qwen2Config as Qwen2Config_hf +from paddleformers.transformers import Qwen2ForCausalLM as Qwen2ForCausalLM_hf + +def eval_model_convert_parallel(mp_degree=1): + paddle_input_ids = paddle.to_tensor([[0, 345, 232, 328, 740, 140, 1695, 69, 6078, 1588, 2]]) + torch_input_ids = paddle.to_tensor([[0, 345, 232, 328, 740, 140, 1695, 69, 6078, 1588, 2]]) + + strategy = fleet.DistributedStrategy() + strategy.hybrid_configs = { + "dp_degree": 1, + "mp_degree": mp_degree, + "pp_degree": 1, + "sharding_degree": 1, + } + fleet.init(is_collective=True, strategy=strategy) + hcg = fleet.get_hybrid_communicate_group() + + # paddle model + paddle_ckpt_path = "Qwen/Qwen2-0.5B" + config_paddle = Qwen2Config.from_pretrained(paddle_ckpt_path) + config_paddle.tensor_parallel_degree = hcg.get_model_parallel_world_size() + config_paddle.tensor_parallel_rank = hcg.get_model_parallel_rank() + config_paddle.tensor_parallel_output = False + model_paddle = Qwen2ForCausalLM.from_pretrained(paddle_ckpt_path, config=config_paddle, dtype="float32") + + # torch model + torch_ckpt_path = "Qwen/Qwen2-0.5B" + config_torch = Qwen2Config_hf.from_pretrained(torch_ckpt_path) + config_torch = Qwen2Config.from_pretrained(paddle_ckpt_path) + config_torch.tensor_parallel_degree = hcg.get_model_parallel_world_size() + config_torch.tensor_parallel_rank = hcg.get_model_parallel_rank() + config_torch.tensor_parallel_output = False + model_torch = Qwen2ForCausalLM_hf.from_pretrained(torch_ckpt_path, config=config_torch, dtype="float32") + + model_paddle.eval() + model_torch.eval() + + # 手动验证 + out_paddle = model_paddle(paddle_input_ids)[0] + out_torch = model_torch(torch_input_ids)[0] + print(out_paddle) + print(out_torch) + assert np.allclose(out_paddle.numpy(), out_torch.detach().numpy(), rtol=1e-5, atol=1e-4) + +eval_model_convert_parallel(mp_degree=2) \ No newline at end of file diff --git a/paddlenlp/transformers/convert/until/collect_import_modeling.py b/paddlenlp/transformers/convert/until/collect_import_modeling.py new file mode 100644 index 000000000000..892bfa8e3f93 --- /dev/null +++ b/paddlenlp/transformers/convert/until/collect_import_modeling.py @@ -0,0 +1,259 @@ +import libcst as cst +import os +from pathlib import Path +from typing import Dict, Set, Union, List, Tuple + +# ============================================================================== +# 以下所有函数和类均保持您提供的原始版本,没有任何改动 +# ============================================================================== +def get_unique_module_names(imports_dict: Dict[str, str]) -> Set[str]: + """ + 从字典的值中提取出所有唯一的、纯净的模块名。 + 它会移除前缀 '..' 和末尾的 '.'。 + """ + unique_names = set() + + for prefix_value in imports_dict.values(): + temp_name = prefix_value + + # 1. 移除开头的 '..' + if temp_name.startswith(".."): + temp_name = temp_name[2:] + + # 2. 移除末尾的 '.' + final_name = temp_name.rstrip('.') + + # 3. 将最终结果添加到集合中,自动保证唯一性 + if final_name: + unique_names.add(final_name) + + return unique_names +def get_full_name(node: Union[cst.Name, cst.Attribute, cst.ImportFrom]) -> str: + if isinstance(node, cst.Name): + return node.value + elif isinstance(node, cst.Attribute): + return get_full_name(node.value) + "." + node.attr.value + elif isinstance(node, cst.ImportFrom): + module_parts = [] + if node.relative: + module_parts.append("." * len(node.relative)) + if node.module: + module_parts.append(get_full_name(node.module)) + return "".join(module_parts) + else: + return "" + +class ModelingImportCollector(cst.CSTVisitor): + def __init__(self): + self.imports: Dict[str, str] = {} # name -> module_path + self.prefixes_before_modeling: Dict[str, str] = {} + def visit_ImportFrom(self, node: cst.ImportFrom) -> None: + modname = get_full_name(node) + if "modeling" in modname: + modeling_index = modname.find("modeling") + prefix = modname[:modeling_index] + for alias in node.names: + name_in_scope = alias.evaluated_name + self.imports[alias.evaluated_name] = modname + self.prefixes_before_modeling[name_in_scope] = prefix + +class DependencyCollector(cst.CSTVisitor): + def __init__(self): + self.names: Set[str] = set() + def visit_Name(self, node: cst.Name) -> None: + self.names.add(node.value) + +class ModuleInfoCollector(cst.CSTVisitor): + def __init__(self): + self.defs: Dict[str, Union[cst.ClassDef, cst.FunctionDef, cst.Assign]] = {} + self.imports: Dict[str, Union[cst.Import, cst.ImportFrom]] = {} + self.class_stack: List[str] = [] + def visit_ClassDef(self, node: cst.ClassDef) -> None: + self.defs[node.name.value] = node + self.class_stack.append(node.name.value) + def leave_ClassDef(self, original_node: cst.ClassDef) -> None: + self.class_stack.pop() + def visit_FunctionDef(self, node: cst.FunctionDef) -> None: + if not self.class_stack: + self.defs[node.name.value] = node + else: + fullname = ".".join(self.class_stack + [node.name.value]) + self.defs[fullname] = node + def visit_Assign(self, node: cst.Assign) -> None: + if not self.class_stack: + for target_wrapper in node.targets: + if isinstance(target_wrapper.target, cst.Name): + self.defs[target_wrapper.target.value] = node + def visit_Import(self, node: cst.Import) -> None: + for alias in node.names: + name_in_scope = alias.asname.name.value if alias.asname else alias.name.value + self.imports[name_in_scope] = node + def visit_ImportFrom(self, node: cst.ImportFrom) -> None: + for alias in node.names: + name_in_scope = alias.asname.name.value if alias.asname else alias.name.value + self.imports[name_in_scope] = node + +def parse_file(file_path: str) -> Tuple[Dict, Dict, cst.Module]: + with open(file_path, "r", encoding="utf-8") as f: + code = f.read() + module = cst.parse_module(code) + collector = ModuleInfoCollector() + module.visit(collector) + return collector.defs, collector.imports, module + +def collect_recursive( + name: str, defs: Dict[str, cst.CSTNode], imports: Dict[str, cst.CSTNode], + seen: Set[str], module: cst.Module, +) -> Tuple[Dict[str, str], Set[str], Dict[str, List[str]]]: + if name in seen or name not in defs: + return {}, set(), {} + seen.add(name) + node = defs[name] + dependencies = {name: []} + dep_collector = DependencyCollector() + node.visit(dep_collector) + results = {name: module.code_for_node(node)} + collected_imports = set() + for dep in dep_collector.names: + if dep in defs and dep not in seen: + dep_results, dep_imports , dep_deps = collect_recursive(dep, defs, imports, seen, module) + results.update(dep_results) + collected_imports.update(dep_imports) + dependencies.update(dep_deps) + dependencies[name].append(dep) # 记录依赖关系 A -> B + elif dep in imports: + import_node = imports[dep] + import_code = module.code_for_node(import_node) + collected_imports.add(import_code) + dependencies[name].append(dep) + return results, collected_imports, dependencies + +def resolve_file_path(current_file: str, modpath: str) -> Path: + dots = len(modpath) - len(modpath.lstrip(".")) + parts = modpath.lstrip(".").split(".") + cur_dir = Path(current_file).parent + for _ in range(dots - 1): + cur_dir = cur_dir.parent + file_path = cur_dir.joinpath(*parts).with_suffix(".py") + return file_path if file_path.exists() else None + +def expand_modeling_imports(file_path: str) -> Dict[str, str]: + with open(file_path, "r", encoding="utf-8") as f: + code = f.read() + module = cst.parse_module(code) + imp_collector = ModelingImportCollector() + module.visit(imp_collector) + expanded_defs = {} + all_imports = set() + seen = set() + dependencies = {} + for name, modpath in imp_collector.imports.items(): + target_file = resolve_file_path(file_path, modpath) + if not target_file: continue + defs, imports, parsed_module = parse_file(str(target_file)) + if name in defs: + new_defs, new_imports, new_deps = collect_recursive(name, defs, imports, seen, parsed_module) + expanded_defs.update(new_defs) + all_imports.update(new_imports) + dependencies.update(new_deps) + expanded = {} + for i, import_code in enumerate(sorted(list(all_imports))): + expanded[f"__import_{i}__"] = import_code + expanded.update(expanded_defs) + unique_modules = get_unique_module_names(imp_collector.prefixes_before_modeling) + return expanded, dependencies,unique_modules # 返回代码和依赖关系 + +def save_results_to_txt(result: Dict[str, str], output_file: str): + imports_to_write = [] + defs_to_write = {} + for key, value in result.items(): + if key.startswith("__import_"): + imports_to_write.append(value) + else: + defs_to_write[key] = value + with open(output_file, "w", encoding="utf-8") as f: + if imports_to_write: + f.write("### === Imports === ###\n") + for imp in imports_to_write: + f.write(f"{imp}\n") + f.write("\n" + "="*50 + "\n\n") + if defs_to_write: + f.write("### === Definitions === ###\n") + for k, v in sorted(defs_to_write.items()): + f.write(f"=== {k} ===\n") + f.write(f"{v}\n\n") + +# ============================================================================== +# ### NEW ### 以下是为“文件重写”这一新增功能而添加的全新、独立的模块 +# ============================================================================== + +class ModelingImportNodeCollector(cst.CSTVisitor): + """一个专门用于收集待删除 import 节点的新 Visitor。""" + def __init__(self): + self.nodes_to_remove: Set[cst.ImportFrom] = set() + + def visit_ImportFrom(self, node: cst.ImportFrom) -> None: + modname = get_full_name(node) + if "modeling" in modname: + self.nodes_to_remove.add(node) + +class ImportRemover(cst.CSTTransformer): + """一个独立的转换器,用于从语法树中删除指定的import节点。""" + def __init__(self, nodes_to_remove: Set[cst.ImportFrom]): + self.nodes_to_remove = nodes_to_remove + + def leave_ImportFrom( + self, original_node: cst.ImportFrom, updated_node: cst.ImportFrom + ) -> Union[cst.ImportFrom, cst.RemovalSentinel]: + if original_node in self.nodes_to_remove: + return cst.RemoveFromParent() + return updated_node + +def remove_imports_and_rewrite(file_path: str): + """ + 一个独立的函数,封装了文件读取、收集待删除节点、转换和重写的操作。 + """ + # 1. 再次读取和解析文件,以启动独立的重写流程 + with open(file_path, "r", encoding="utf-8") as f: + code = f.read() + module = cst.parse_module(code) + + # 2. 收集需要删除的节点 + node_collector = ModelingImportNodeCollector() + module.visit(node_collector) + + nodes_to_remove = node_collector.nodes_to_remove + if not nodes_to_remove: + print(f"No 'modeling' imports found in '{file_path}' to remove.") + return + + # 3. 使用转换器生成修改后的代码 + print(f"Removing {len(nodes_to_remove)} 'modeling' import(s) from '{file_path}'...") + remover = ImportRemover(nodes_to_remove) + modified_tree = module.visit(remover) + + # 4. 将修改后的代码写回原文件 + with open(file_path, "w", encoding="utf-8") as f: + f.write(modified_tree.code) + print("File rewrite complete.") + + +# ============================================================================== +# ### MODIFIED ### 主程序块现在按顺序执行两个功能 +# ============================================================================== + +if __name__ == "__main__": + file_to_parse = "/home/hsz/PaddleFormers/PaddleFormers/paddleformers/transformers/convert/example/test_model.py" + output_filename = "modeling_imports_results.txt" + + # --- 步骤 1: 执行完整的原有功能 --- + # 调用函数,其接口和返回值完全没有改变 + # 同时也修正了之前版本中解包错误的bug + combined_results = expand_modeling_imports(file_to_parse) + + # 保存结果,完成原有任务 + save_results_to_txt(combined_results, output_filename) + print(f"Code extraction complete. Results saved to {output_filename}") + + # --- 步骤 2: 在原有功能完成后,独立执行新增的功能 --- + remove_imports_and_rewrite(file_to_parse) \ No newline at end of file diff --git a/paddlenlp/transformers/convert/until/rename_identifiers.py b/paddlenlp/transformers/convert/until/rename_identifiers.py new file mode 100644 index 000000000000..c00440976a90 --- /dev/null +++ b/paddlenlp/transformers/convert/until/rename_identifiers.py @@ -0,0 +1,109 @@ +import libcst as cst +from libcst import CSTTransformer +import re +import os +from typing import Set, List, Union + +class GenericRenamerTransformer(CSTTransformer): + """ + 一个通用的CST转换器,用于安全地将代码中的标识符从多个源名称替换为同一个目标名称, + 并能智能地保留原始名称的大小写风格。 + """ + def __init__(self, from_names: Union[Set[str], List[str]], to_name: str): + """ + Args: + from_names: 要被替换的源名称集合或列表 (例如 {'t5', 'llama', 'utils'})。 + to_name: 用于替换的目标名称 (例如 'qwen2')。 + """ + self.to_name = to_name + + # 1. 构建一个包含所有源名称的正则表达式 | (OR 逻辑) + # - 使用 re.escape() 确保特殊字符被正确处理。 + # - 使用 | 符号连接所有名称,实现多选一匹配。 + # - 确保列表非空 + if not from_names: + raise ValueError("from_names 列表不能为空。") + + escaped_names = [re.escape(name) for name in from_names] + pattern = "|".join(escaped_names) + + # 2. 编译一个不区分大小写 (re.IGNORECASE) 的正则表达式 + self.regex = re.compile(pattern, re.IGNORECASE) + + def _case_preserving_replace(self, match: re.Match) -> str: + """ + 这是一个自定义的替换函数,它根据匹配到的字符串的大小写风格, + 来决定 to_name 应该使用哪种大小写形式。 + """ + found_str = match.group(0) + # 如果找到的是全大写 (e.g., LLAMA) + if found_str.isupper(): + return self.to_name.upper() + # 如果找到的是首字母大写 (e.g., Llama) + if found_str.istitle(): + return self.to_name.title() + # 默认情况,包括全小写 (e.g., llama),返回全小写 + return self.to_name.lower() + + def leave_Name( + self, original_node: cst.Name, updated_node: cst.Name + ) -> cst.Name: + """ + 当访问离开一个名称节点时,使用正则表达式和自定义替换函数执行重命名。 + """ + # 使用 regex.sub() 和我们的自定义函数来进行替换 + new_name_str = self.regex.sub(self._case_preserving_replace, updated_node.value) + + # 仅在名称确实发生改变时才创建一个新节点 + if new_name_str != updated_node.value: + if not new_name_str.isidentifier(): + original_name = original_node.value + # 警告,而不是跳过,因为这在依赖于上下文的重命名中可能是允许的。 + # 但对于 cst.Name 节点,它必须是有效标识符。 + print(f"警告:尝试将 '{original_name}' 重命名为无效标识符 '{new_name_str}'。跳过此重命名。") + return updated_node + return updated_node.with_changes(value=new_name_str) + + return updated_node + +def rename_identifiers(source_code: str, from_names: Union[Set[str], List[str]], to_name: str) -> str: + """ + 接收一段Python源代码,将其中的所有 from_names 相关标识符安全地重命名为 to_name。 + + Args: + source_code: 包含Python代码的字符串。 + from_names: 要被替换的源名称集合或列表 (例如 {"t5", "llama"})。 + to_name: 用于替换的目标名称 (例如 "qwen2")。 + + Returns: + 重构后的Python代码字符串。 + """ + try: + module = cst.parse_module(source_code) + transformer = GenericRenamerTransformer(from_names, to_name) + modified_module = module.visit(transformer) + return modified_module.code + except cst.ParserSyntaxError as e: + print(f"Error: Failed to parse the source code. {e}") + return source_code + except ValueError as e: + print(f"Error in rename process: {e}") + return source_code + +# --- 示例用法 --- +# source_code = """ +# class LlamaModel(T5Model): +# def forward(self, input_ids): +# return self.llama_layer(input_ids) +# LLAMA_CONFIG = 1 +# """ +# from_list = ['llama', 't5'] +# to_name = 'qwen2' + +# new_code = rename_identifiers(source_code, from_list, to_name) +# print(new_code) +# # 预期输出: +# # class Qwen2Model(Qwen2Model): +# # def forward(self, input_ids): +# # return self.qwen2_layer(input_ids) +# # QWEN2_CONFIG = 1 \ No newline at end of file diff --git a/paddlenlp/transformers/convert/until/rewrite_child_classes.py b/paddlenlp/transformers/convert/until/rewrite_child_classes.py new file mode 100644 index 000000000000..f5aca4ee72f2 --- /dev/null +++ b/paddlenlp/transformers/convert/until/rewrite_child_classes.py @@ -0,0 +1,649 @@ +import libcst as cst +from typing import Dict, Optional, List, Set, Union +from libcst import matchers as m +import builtins +import os + +# ============================================================================== +# SECTION 1: 智能类合并引擎 +# ============================================================================== + + +def get_node_code(node: cst.CSTNode) -> str: + """辅助函数,用于获取CST节点的代码字符串,以便比较。""" + return cst.Module(body=[node]).code.strip() + +def merge_parameters( + child_params: cst.Parameters, parent_params: cst.Parameters +) -> cst.Parameters: + """智能合并两个方法的参数列表。""" + child_param_map = {p.name.value: p for p in child_params.params} + + insertion_point = len(child_params.params) + for i, p in enumerate(child_params.params): + if p.star: + insertion_point = i + break + + new_params_from_parent = [] + for p in parent_params.params: + if p.name.value not in child_param_map and p.default is not None: + new_params_from_parent.append(p) + + final_params_list = list(child_params.params) + final_params_list[insertion_point:insertion_point] = new_params_from_parent + + return child_params.with_changes(params=tuple(final_params_list)) + +def _get_class_var_names(class_body: list) -> set: + """从类的 body 中提取所有类变量的名称。""" + var_names = set() + for stmt in class_body: + if m.matches(stmt, m.SimpleStatementLine(body=[m.Assign()])): + assign_node = stmt.body[0] + for target in assign_node.targets: + if isinstance(target.target, cst.Name): + var_names.add(target.target.value) + return var_names + +def merge_parent_class_final( + child_class: cst.ClassDef, parent_class: cst.ClassDef +) -> cst.ClassDef: + """ + 类合并主函数(最终智能版): + - 智能展开super()调用,避免代码冗余。 + - 智能合并方法的参数列表,防止运行时错误。 + - 正确处理类变量和未覆盖方法的继承。 + """ + child_body_list = list(child_class.body.body) + parent_body_map = { + stmt.name.value: stmt + for stmt in parent_class.body.body + if hasattr(stmt, 'name') and isinstance(stmt.name, cst.Name) + } + + final_body = list(child_body_list) + + # 1. 处理被子类覆盖的方法 (包括 __init__) + for i, child_stmt in enumerate(child_body_list): + if not isinstance(child_stmt, cst.FunctionDef): + continue + + method_name = child_stmt.name.value + parent_method = parent_body_map.get(method_name) + + if not parent_method or not isinstance(parent_method, cst.FunctionDef): + continue + + # 1a. 智能展开 super() + child_method_body = list(child_stmt.body.body) + parent_method_body = list(parent_method.body.body) + + super_call_index = -1 + for j, stmt in enumerate(child_method_body): + if m.matches(stmt, m.SimpleStatementLine(body=[m.Expr(value=m.Call(func=m.Attribute(value=m.Call(func=m.Name("super")))))]) ) \ + or m.matches(stmt, m.Return(value=m.Call(func=m.Attribute(value=m.Call(func=m.Name("super")))))): + super_call_index = j + break + + new_method_body_stmts = child_method_body + if super_call_index != -1: + child_prefix_stmts = child_method_body[:super_call_index] + child_suffix_stmts = child_method_body[super_call_index + 1:] + child_prefix_codes = [get_node_code(s) for s in child_prefix_stmts] + + divergence_index = 0 + for k, parent_stmt in enumerate(parent_method_body): + if k < len(child_prefix_codes) and get_node_code(parent_stmt) == child_prefix_codes[k]: + divergence_index += 1 + else: + break + + parent_suffix_stmts = parent_method_body[divergence_index:] + new_method_body_stmts = child_prefix_stmts + parent_suffix_stmts + child_suffix_stmts + + # 1b. 合并参数列表 + new_params = merge_parameters(child_stmt.params, parent_method.params) + + # 1c. 创建最终的方法节点 + new_body_block = child_stmt.body.with_changes(body=tuple(new_method_body_stmts)) + final_method = child_stmt.with_changes(body=new_body_block, params=new_params) + + final_body[i] = final_method + + # 2. 添加父类中未被覆盖的成员 + child_member_names = {stmt.name.value for stmt in final_body if hasattr(stmt, 'name')} + child_class_var_names = _get_class_var_names(final_body) + + for parent_stmt in parent_class.body.body: + if hasattr(parent_stmt, 'name') and parent_stmt.name.value in child_member_names: + continue + + if m.matches(parent_stmt, m.SimpleStatementLine(body=[m.Assign()])): + parent_var_names = _get_class_var_names([parent_stmt]) + if not parent_var_names.isdisjoint(child_class_var_names): + continue + + final_body.append(parent_stmt) + + # 3. 清理 pass 语句 + pass_matcher = m.SimpleStatementLine(body=[m.Pass()]) + non_pass_statements = [stmt for stmt in final_body if not m.matches(stmt, pass_matcher)] + + if not non_pass_statements: + cleaned_body = (cst.SimpleStatementLine(body=(cst.Pass(),)),) + else: + cleaned_body = tuple(non_pass_statements) + + # 4. 返回最终结果 + return child_class.with_changes( + bases=parent_class.bases, + body=child_class.body.with_changes(body=cleaned_body) + ) + +# ============================================================================== +# SECTION 2:代码重构工具框架 (已集成新逻辑) +# ============================================================================== + +class ComprehensiveRenamer(cst.CSTTransformer): + """智能、大小写敏感地重命名所有匹配的名称。""" + def __init__(self, rename_map: Dict[str, str]): + self.rename_pairs = [] + for from_sub, to_sub in rename_map.items(): + self.rename_pairs.append((from_sub.lower(), to_sub.lower())) + self.rename_pairs.append((from_sub.capitalize(), to_sub.capitalize())) + self.rename_pairs.append((from_sub.upper(), to_sub.upper())) + self.rename_pairs.sort(key=lambda x: len(x[0]), reverse=True) + + def leave_Name(self, original_node: cst.Name, updated_node: cst.Name) -> cst.Name: + for from_name, to_name in self.rename_pairs: + if from_name in original_node.value: + new_value = original_node.value.replace(from_name, to_name) + return updated_node.with_changes(value=new_value) + return updated_node + +def get_base_class_name(base: cst.BaseExpression) -> Optional[str]: + """提取基类名称。""" + if isinstance(base, cst.Name): + return base.value + elif isinstance(base, cst.Attribute): + parts = [] + node = base + while isinstance(node, cst.Attribute): + parts.append(node.attr.value) + node = node.value + if isinstance(node, cst.Name): + parts.append(node.value) + return ".".join(reversed(parts)) + return None + +def find_class_in_source(module_node: cst.Module) -> Optional[cst.ClassDef]: + """从模块节点中提取第一个类定义。""" + for node in module_node.body: + if isinstance(node, cst.ClassDef): + return node + return None + +class DependencyVisitor(cst.CSTVisitor): + """扫描代码以查找所有潜在的外部引用。""" + def __init__(self): + self.scopes: List[Set[str]] = [set()] + self.dependencies: Set[str] = set() + self.builtins = set(dir(builtins)) + + def visit_FunctionDef(self, node: cst.FunctionDef) -> None: + param_names = {p.name.value for p in node.params.params} + self.scopes.append(param_names) + + def leave_FunctionDef(self, original_node: cst.FunctionDef) -> None: + self.scopes.pop() + + def visit_Assign(self, node: cst.Assign) -> None: + for target in node.targets: + if isinstance(target.target, cst.Name): + self.scopes[-1].add(target.target.value) + + def visit_Name(self, node: cst.Name) -> None: + is_local = any(node.value in scope for scope in self.scopes) + if not is_local and node.value not in self.builtins: + self.dependencies.add(node.value) + +def find_usage_dependencies(node: Union[cst.ClassDef, cst.FunctionDef], expanded: Dict[str, str]) -> Set[str]: + """分析节点的CST,找出其使用到的其他实体。""" + visitor = DependencyVisitor() + node.visit(visitor) + return {dep for dep in visitor.dependencies if dep in expanded} + +def get_full_name(node: Union[cst.Name, cst.Attribute, cst.ImportFrom]) -> str: + """ + 从CST节点递归获取完整名称,如 a.b.c 或 ..a.b + """ + if isinstance(node, cst.Name): + return node.value + elif isinstance(node, cst.Attribute): + # 递归获取基础部分 (a.b) + base_name = get_full_name(node.value) + # 拼接当前属性 (.c) + return f"{base_name}.{node.attr.value}" if base_name else node.attr.value + elif isinstance(node, cst.ImportFrom): + # 处理 from ... import ... 语句的模块路径 + module_parts = [] + if node.relative: + module_parts.append("." * len(node.relative)) + if node.module: + module_parts.append(get_full_name(node.module)) + return "".join(module_parts) + return "" + +def filter_specific_modeling_imports( + import_nodes: Union[Dict[str, cst.BaseSmallStatement], List[cst.BaseSmallStatement]] +) -> Dict[str, cst.BaseSmallStatement]: + """ + 【修正版】只移除严格符合 `from ..***.modeling import ...` 模式的导入。 + + 这个版本可以智能处理输入是字典或列表的情况,并且总是返回一个字典。 + """ + kept_imports_dict: Dict[str, cst.BaseSmallStatement] = {} + + # 【核心修正】: 检查输入类型,并确保我们总是遍历 CST 节点 + nodes_to_iterate = [] + if isinstance(import_nodes, dict): + # 如果输入是字典,我们只关心它的值(CST 节点) + nodes_to_iterate = list(import_nodes.values()) + elif isinstance(import_nodes, list): + # 如果输入已经是列表,直接使用 + nodes_to_iterate = import_nodes + + for node in nodes_to_iterate: + should_keep = True + + if isinstance(node, cst.ImportFrom): + is_two_dots_relative = node.relative and len(node.relative) == 2 + + if is_two_dots_relative: + module_path = get_full_name(node.module) if node.module else "" + + if module_path.endswith(".modeling"): + should_keep = False + + if should_keep: + kept_imports_dict[get_node_code(node)] = node + + return kept_imports_dict + +class EntityFinder(cst.CSTVisitor): + """ + A visitor to find the first ClassDef or FunctionDef node in a CST. + """ + def __init__(self): + self.found_node = None + + def visit_ClassDef(self, node: cst.ClassDef) -> bool: + # Found a class, store it and stop searching + if self.found_node is None: + self.found_node = node + return False # Return False to stop traversing deeper + + def visit_FunctionDef(self, node: cst.FunctionDef) -> bool: + # Found a function, store it and stop searching + if self.found_node is None: + self.found_node = node + return False # Return False to stop traversing deeper + +def find_entity_in_source(source_cst_node: cst.Module) -> Optional[cst.CSTNode]: + """ + Parses a CST module to find the first class or function definition. + + Args: + source_cst_node: The parsed Concrete Syntax Tree of the source file. + + Returns: + The found ClassDef or FunctionDef node, or None if not found. + """ + if not isinstance(source_cst_node, cst.Module): + # Ensure we have a valid CST to visit + return None + + finder = EntityFinder() + source_cst_node.visit(finder) + return finder.found_node + +def rewrite_child_classes( + expanded: Dict[str, str], + target_file: str, + template_comment: str, + output_file: str, + rename_map: Optional[Dict[str, str]] = None +): + """完整的类重写工具 (已集成VFinal版合并引擎)。""" + if rename_map is None: rename_map = {} + + # --- 阶段一 & 二:解析代码 --- + print("阶段一:正在预解析所有父类代码...") + parsed_expanded: Dict[str, cst.Module] = {} + imports_to_inject: Dict[str, cst.BaseSmallStatement] = {} + for name, source in expanded.items(): + try: + module_node = cst.parse_module(source) + parsed_expanded[name] = module_node + for node in module_node.body: + if m.matches(node, m.SimpleStatementLine(body=[m.Import() | m.ImportFrom()])): + imports_to_inject[module_node.code_for_node(node)] = node + except Exception as e: + print(f"警告:预解析 {name} 失败: {e}") + + print("\n阶段二:正在分析目标文件...") + with open(target_file, "r", encoding="utf-8") as f: + module = cst.parse_module(f.read()) + + imports_from_target: Dict[str, cst.SimpleStatementLine] = {} + body_statements: List[cst.BaseStatement] = [] + for stmt in module.body: + # 匹配导入语句 + if m.matches(stmt, m.SimpleStatementLine(body=[m.Import() | m.ImportFrom()])): + imports_from_target[module.code_for_node(stmt)] = stmt + + # 匹配 try-except 块(通常用于可选导入) + elif isinstance(stmt, cst.Try): + imports_from_target[module.code_for_node(stmt)] = stmt + + # 匹配 __all__ 定义 + elif m.matches(stmt, m.SimpleStatementLine(body=[m.Assign(targets=[m.AssignTarget(target=m.Name("__all__"))])])): + imports_from_target[module.code_for_node(stmt)] = stmt + + # 其他语句放入主体 + else: + body_statements.append(stmt) + imports_from_target=filter_specific_modeling_imports(imports_from_target) + # --- 阶段三 & 四:依赖分析与合并 --- + nodes_to_inject: Dict[str, Union[cst.ClassDef, cst.FunctionDef]] = {} + existing_names: Set[str] = {stmt.name.value for stmt in body_statements if hasattr(stmt, 'name')} + visiting: Set[str] = set() + + def collect_dependencies(name: str): + # 1. 边界检查 (完全不变) + # 无论是类还是函数,这些检查(是否已解析、已收集、已存在、正在访问)都同样适用。 + if name not in parsed_expanded or name in nodes_to_inject or name in existing_names or name in visiting: + return + + # 2. 查找实体节点 (需要泛化) + # find_entity_in_source 现在可以返回 ClassDef 或 FunctionDef 节点。 + entity_node = find_entity_in_source(parsed_expanded[name]) + if not entity_node: + return + + # 3. 标记正在访问 (完全不变) + visiting.add(name) + + # 4. 处理类特有的依赖:继承 (只对类执行) + # 如果实体是类,才处理其父类依赖。函数没有继承,会自然跳过此块。 + if isinstance(entity_node, cst.ClassDef): + for base in entity_node.bases: + if base_name := get_base_class_name(base.value): + collect_dependencies(base_name) + + # 5. 处理通用依赖:使用关系 (对类和函数都执行) + # 这里的 `find_usage_dependencies` 函数也必须是通用的, + # 它需要能解析类和函数体内的依赖。 + # - 对于类: 查找成员变量的类型注解等。 + # - 对于函数: 查找参数的类型注解、返回值的类型注解、函数体内调用的其他函数、实例化的类等。 + for dep_name in find_usage_dependencies(entity_node, expanded): + collect_dependencies(dep_name) + + # 6. 完成处理,加入结果集 (完全不变) + # 无论是类还是函数,都在其所有依赖项被处理完毕后,才将自身加入结果集。 + visiting.remove(name) + nodes_to_inject[name] = entity_node + print("\n阶段三:正在进行全局依赖扫描...") + for stmt in body_statements: + if isinstance(stmt, cst.ClassDef): + for base in stmt.bases: + if base_name := get_base_class_name(base.value): + collect_dependencies(base_name) + for dep_name in find_usage_dependencies(stmt, expanded): + collect_dependencies(dep_name) + + print("\n阶段四:正在执行类合并操作...") + processed_body_statements = [] + merged_parents: Set[str] = set() + for stmt in body_statements: + if isinstance(stmt, cst.ClassDef) and stmt.bases: + if base_name := get_base_class_name(stmt.bases[0].value): + if base_name in parsed_expanded: + parent_module = parsed_expanded[base_name] + if parent_class_node := find_class_in_source(parent_module): + print(f" > 正在合并 {base_name} -> {stmt.name.value}...") + # <<<--- ★★★核心修改点:调用新的合并函数★★★ + stmt = merge_parent_class_final(stmt, parent_class_node) + merged_parents.add(base_name) + processed_body_statements.append(stmt) + + # --- 阶段五:按正确顺序重新组装文件 --- + print("\n阶段五:正在生成最终文件...") + + nodes_to_inject_after_merge = {k: v for k, v in nodes_to_inject.items() if k not in merged_parents} + main_defined_names = {stmt.name.value for stmt in processed_body_statements if hasattr(stmt, 'name')} + + print(" > 正在应用智能重命名规则并检测冲突...") + final_nodes_to_inject = {} + renamer = ComprehensiveRenamer(rename_map) + + for original_name, node in nodes_to_inject_after_merge.items(): + renamed_node = node.visit(renamer) + new_name = renamed_node.name.value + if new_name in main_defined_names: + print(f" - 检测到主代码中已存在 '{new_name}',将跳过注入 '{original_name}'") + continue + print(f" - 正在处理依赖 '{original_name}'...") + final_nodes_to_inject[new_name] = renamed_node + + final_imports = {**imports_from_target, **imports_to_inject} + new_body = [] + new_header = [] + #加转换注释 + for line in template_comment.splitlines(): + stripped_line = line.strip() + if stripped_line: + comment_node = cst.Comment(stripped_line) + new_header.append(cst.EmptyLine( + comment=comment_node, + indent=True, + whitespace=cst.SimpleWhitespace(value="") + )) + for item in module.header: + if isinstance(item, cst.EmptyLine) and item.comment: + new_header.append(item) + elif isinstance(item, cst.TrailingWhitespace) and item.comment: + new_header.append(item) + + if final_imports: + unique_imports = {module.code_for_node(n): n for n in final_imports.values()} + new_body.extend(unique_imports.values()) + + injected_items = sorted(final_nodes_to_inject.values(), key=lambda n: n.name.value) + # 2. 分类依赖项:方法和类 + methods_to_inject = [] + classes_to_inject = [] + for node in injected_items: + if isinstance(node, cst.FunctionDef): + print(node.name.value) + methods_to_inject.append(node) + elif isinstance(node, cst.ClassDef): + classes_to_inject.append(node) + else: + print(f"警告:遇到未知类型的节点,无法分类: {type(node.name.value)}") + # 3. 注入方法(放在 imports 之后,主逻辑之前) + if methods_to_inject: + new_body.extend([cst.EmptyLine(), cst.EmptyLine(comment=cst.Comment("# --- Injected Methods ---"))]) + new_body.extend(methods_to_inject) + # 4. 处理类的注入顺序 + # 分组:有父类在主逻辑中的类 vs 没有的 + classes_with_parent_in_main = [] + classes_without_parent_in_main = [] + if classes_to_inject: + # 获取主逻辑中的所有类名 + main_classes = {stmt.name.value for stmt in processed_body_statements if isinstance(stmt, cst.ClassDef)} + + + + for cls_node in classes_to_inject: + has_parent_in_main = False + if isinstance(cls_node, cst.ClassDef) and cls_node.bases: + for base in cls_node.bases: + if base_name := get_base_class_name(base.value): + if base_name in main_classes: + has_parent_in_main = True + break + + if has_parent_in_main: + classes_with_parent_in_main.append(cls_node) + else: + classes_without_parent_in_main.append(cls_node) + + # 4.1 先注入没有父类依赖的类(放在 imports 之后) + if classes_without_parent_in_main: + new_body.extend([cst.EmptyLine(), cst.EmptyLine(comment=cst.Comment("# --- Injected Classes ---"))]) + new_body.extend(classes_without_parent_in_main) + + + # 4. 动态遍历主逻辑,在父类定义后插入其子类 + if processed_body_statements: + # 4.1 收集所有主逻辑的类名 + classes_with_parent_in_main = { + cls for cls in classes_with_parent_in_main + if isinstance(cls, cst.ClassDef) + } + + # 4.2 按顺序处理主逻辑的语句 + for stmt in processed_body_statements: + new_body.append(stmt) + + # 如果是类定义,检查是否有子类需要注入 + if isinstance(stmt, cst.ClassDef): + parent_name = stmt.name.value + # 查找依赖此父类的子类 + child_classes = [ + cls for cls in classes_with_parent_in_main + if any( + get_base_class_name(base.value) == parent_name + for base in cls.bases + ) + ] + # 注入子类 + if child_classes: + new_body.extend([ + cst.EmptyLine(), + cst.EmptyLine(comment=cst.Comment(f"# --- Children of {parent_name} ---")), + *child_classes + ]) + # 从待注入列表中移除已处理的子类 + classes_with_parent_in_main = [ + cls for cls in classes_with_parent_in_main + if cls not in child_classes + ] + +# 5. 注入剩余未处理的依赖主逻辑的类(可能是跨文件的依赖) + if classes_with_parent_in_main: + new_body.extend([cst.EmptyLine(), cst.EmptyLine(comment=cst.Comment("# --- Remaining Injected Child Classes ---"))]) + new_body.extend(classes_with_parent_in_main) + + """ + if injected_items: + new_body.extend([cst.EmptyLine(), cst.EmptyLine(comment=cst.Comment("# --- Injected Dependencies ---"))]) + new_body.extend(injected_items) + + if processed_body_statements: + new_body.extend([cst.EmptyLine(), cst.EmptyLine(comment=cst.Comment("# --- Main Application Logic ---"))]) + new_body.extend(processed_body_statements) + """ + new_module = module.with_changes( + header=tuple(new_header), # 使用新的头部注释 + body=tuple(new_body) # 使用新的主体内容 +) + with open(output_file, "w", encoding="utf-8") as f: + f.write(new_module.code) + + print(f"\n成功生成合并后的文件: {output_file}") + +# ============================================================================== +# SECTION 3: 演示 +# ============================================================================== +if __name__ == "__main__": + + # --- 步骤1: 准备演示环境 --- + # 创建一个虚拟的 child_class.py 文件供脚本读取 + child_class_content = """ +class MyChildClass(ParentClass): + def __init__(self, config, child_param): + # 与父类重复的语句 + if config.flag: + self.param1 = config.param1 + else: + self.param1 = config.default_param1 + + # 调用super + super().__init__(config) + + # 新增的属性和逻辑 + self.child_param = child_param + print("Child class logic executed.") + + def child_method(self): + return "子类方法" +""" + with open("child_class.py", "w", encoding="utf-8") as f: + f.write(child_class_content) + + # --- 步骤2: 定义父类和祖父类源代码 --- + expanded_parents = { + "ParentClass": ''' +class ParentClass(GrandParentClass): + def __init__(self, config): + # 条件语句 + if config.flag: + self.param1 = config.param1 + else: + self.param1 = config.default_param1 + + # 循环语句 + for i in range(5): + self.param2 = i + + # 方法调用 + self.initialize(config) + + # super调用(指向祖父类) + super().__init__() + + def initialize(self, config): + self.param3 = config.param3 + + def parent_method(self): + return "父类方法" +''', + "GrandParentClass": ''' +class GrandParentClass: + def __init__(self): + self.grand_param = "祖父参数" + + def grand_method(self): + return "祖父方法" +''' + } + + # --- 步骤3: 运行重写工具 --- + print("--- 开始运行代码重写工具 ---") + rewrite_child_classes( + expanded=expanded_parents, + target_file="child_class.py", + output_file="merged_class.py" + ) + + # --- 步骤4: 打印结果 --- + print("\n--- 查看生成的 merged_class.py 文件 ---") + with open("merged_class.py", "r", encoding="utf-8") as f: + print(f.read()) + + # --- 步骤5: 清理 --- + os.remove("child_class.py") + os.remove("merged_class.py") \ No newline at end of file diff --git a/paddlenlp/transformers/qwen2/configuration.py b/paddlenlp/transformers/qwen2/configuration.py index c076857647ce..19fdcfdf12aa 100644 --- a/paddlenlp/transformers/qwen2/configuration.py +++ b/paddlenlp/transformers/qwen2/configuration.py @@ -16,10 +16,53 @@ from ..configuration_utils import PretrainedConfig + __all__ = [ + "QWEN2_PRETRAINED_INIT_CONFIGURATION", "Qwen2Config", + "QWEN2_PRETRAINED_RESOURCE_FILES_MAP", ] - +QWEN2_PRETRAINED_INIT_CONFIGURATION = { + # Hypothetical model weights (tiny-random-llama & micro-random-llama) for test only + "__internal_testing__/micro-random-llama": { + "architectures": ["LlamaForCausalLM"], + "hidden_size": 64, + "initializer_range": 0.02, + "intermediate_size": 1000, + "max_position_embeddings": 2048, + "model_type": "llama", + "num_attention_heads": 8, + "num_hidden_layers": 1, + "rms_norm_eps": 1e-06, + "vocab_size": 32000, + "bos_token_id": 1, + "eos_token_id": 2, + "pad_token_id": 0, + }, + "__internal_testing__/tiny-random-llama": { + "architectures": ["LlamaForCausalLM"], + "hidden_size": 768, + "initializer_range": 0.02, + "intermediate_size": 11008, + "max_position_embeddings": 2048, + "model_type": "llama", + "num_attention_heads": 8, + "num_hidden_layers": 2, + "rms_norm_eps": 1e-06, + "vocab_size": 32000, + "bos_token_id": 1, + "eos_token_id": 2, + "pad_token_id": 0, + }, +} + +# Hypothetical model weights (tiny-random-llama) for test only +QWEN2_PRETRAINED_RESOURCE_FILES_MAP = { + "model_state": { + "__internal_testing__/micro-random-llama": "https://bj.bcebos.com/paddlenlp/models/community/__internal_testing__/micro-random-llama/model_state.pdparams", + "__internal_testing__/tiny-random-llama": "https://bj.bcebos.com/paddlenlp/models/community/__internal_testing__/tiny-random-llama/model_state.pdparams", + }, +} class Qwen2Config(PretrainedConfig): r""" @@ -113,6 +156,9 @@ def __init__( use_sliding_window=False, sliding_window=4096, max_window_layers=28, + use_flash_attention_for_generation=False, + alibi=False, + use_last_token_for_generation=False, attention_bias=True, attention_dropout=0.0, rope_scaling_factor=1.0, @@ -153,7 +199,10 @@ def __init__( self.bos_token_id = bos_token_id self.eos_token_id = eos_token_id self.dpo_config = dpo_config + self.use_flash_attention_for_generation = use_flash_attention_for_generation + self.alibi = alibi self.use_fused_head_and_loss_fn = use_fused_head_and_loss_fn + self.use_last_token_for_generation = use_last_token_for_generation super().__init__( pad_token_id=pad_token_id, diff --git a/paddlenlp/transformers/qwen2/modeling_qwen2.py b/paddlenlp/transformers/qwen2/modeling_qwen2.py new file mode 100644 index 000000000000..4a38139353e7 --- /dev/null +++ b/paddlenlp/transformers/qwen2/modeling_qwen2.py @@ -0,0 +1,2243 @@ +# 🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨 +# This file was automatically generated from transformers/qwen2/modular_qwen2.py. +# Do NOT edit this file manually as any edits will be overwritten by the generation of +# the file from the modular. If any change should be done, please apply the change to the +# modular_qwen2.py file directly. One of our CI enforces this. +# 🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨 +# Copyright (c) 2024 PaddlePaddle Authors. All Rights Reserved. +# Copyright 2024 The Qwen team, Alibaba Group and the HuggingFace Inc. team. All rights reserved. +# +# This code ijins based on EleutherAI's GPT-NeoX library and the GPT-NeoX +# and OPT implementations in this library. It has been modified from its +# original forms to accommodate minor architectural differences compared +# to GPT-NeoX and OPT used by the Meta AI team that trained the model. +# +# 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. +from __future__ import annotations + +import math +import warnings +from functools import partial +from typing import Dict, List, Optional, Tuple, Union + +import paddle +import paddle.distributed as dist +import paddle.distributed.fleet.meta_parallel as mpu +import paddle.nn.functional as F +from paddle import Tensor, nn +from paddle.distributed import fleet +from paddle.distributed.fleet.meta_parallel import get_rng_state_tracker +from paddle.distributed.fleet.recompute.recompute import recompute + +from ...utils.tools import get_env_device +from .. import linear_utils +from ..activations import ACT2FN +from ..contrastive_loss import SimpleContrastiveLoss +from ..conversion_utils import StateDictNameMapping, init_name_mappings +from ..embedding_utils import dist_gather_tensor_with_gradient +from ..linear_utils import Linear +from ..model_outputs import ( + BaseModelOutputWithPast, + CausalLMOutputWithPast, + SequenceClassifierOutputWithPast, + TokenClassifierOutput, +) +from ..model_utils import PretrainedModel, register_base_model +from ..refined_recompute import ( + RRColumnParallelLinear, + RRColumnSequenceParallelLinear, + RRRowParallelLinear, + RRRowSequenceParallelLinear, + get_skip_recompute_ops, +) +from ..refined_recompute import recompute as rr_recompute +from ..utils import caculate_llm_per_token_flops, logger +from .configuration import Qwen2Config + + +try: + from paddle.incubate.nn.functional import fused_rotary_position_embedding +except ImportError: + fused_rotary_position_embedding = None + +try: + from paddle.distributed.fleet.utils.sequence_parallel_utils import ( + GatherOp, + ScatterOp, + mark_as_sequence_parallel_parameter, + ) +except: + pass + +try: + from paddle.nn.functional.flash_attention import flash_attention +except: + flash_attention = None +__all__ = [ + "Qwen2Model", + "Qwen2PretrainedModel", + "Qwen2ForCausalLM", + "Qwen2PretrainingCriterion", + "Qwen2ForSequenceClassification", + "Qwen2ForTokenClassification", + "Qwen2SentenceEmbedding", +] +from . import fusion_ops +from ...utils.log import logger +from ...utils.tools import get_env_device +from ..conversion_utils import split_or_fuse_func +from ..conversion_utils import split_or_merge_func +from ..long_sequence_strategies import LongSequenceStrategies +from ..model_outputs import ( + BaseModelOutputWithPastAndCrossAttentions, + CausalLMOutputWithCrossAttentions, +) +from ..segment_parallel_utils import ReshardLayer +from ..utils import caculate_llm_per_token_flops +from .configuration import ( + QWEN2_PRETRAINED_INIT_CONFIGURATION, + QWEN2_PRETRAINED_RESOURCE_FILES_MAP, + Qwen2Config, +) +from paddle.autograd import PyLayer +from paddle.distributed.fleet.utils.sequence_parallel_utils import ( + GatherOp, + ScatterOp, + mark_as_sequence_parallel_parameter, +) +from paddle.incubate.nn.functional import fused_rotary_position_embedding +from paddle.incubate.nn.functional import swiglu +from paddle.nn.functional.flash_attention import flash_attention +from typing import Optional, Tuple +import math +import numpy as np +import os +import paddle + +# --- Injected Methods --- +def _expand_2d_mask(mask, dtype, tgt_length): + """ + Expands attention_mask from `[batch_size, src_length]` to `[batch_size, 1, tgt_length, src_length]`. + """ + batch_size, src_length = mask.shape[0], mask.shape[-1] + tgt_length = tgt_length if tgt_length is not None else src_length + + if get_env_device() == "npu" or get_env_device() == "mlu": + mask = mask[:, None, None, :].astype(dtype) + else: + mask = mask[:, None, None, :].astype("bool") + mask.stop_gradient = True + expanded_mask = mask.expand([batch_size, 1, tgt_length, src_length]) + + return expanded_mask +def _get_interleave(n): + def _get_interleave_power_of_2(n): + start = 2 ** (-(2 ** -(np.log2(n) - 3))) + ratio = start + return [start * ratio**i for i in range(n)] + + if np.log2(n).is_integer(): + return _get_interleave_power_of_2(n) + else: + closest_power_of_2 = int(2 ** np.floor(np.log2(n))) + return ( + _get_interleave_power_of_2(closest_power_of_2) + + _get_interleave(2 * closest_power_of_2)[0::2][: n - closest_power_of_2] + ) +def _get_interleave_power_of_2(n): + start = 2 ** (-(2 ** -(np.log2(n) - 3))) + ratio = start + return [start * ratio**i for i in range(n)] +def _make_causal_mask(input_ids_shape, past_key_values_length): + """ + Make casual mask used for self-attention + """ + batch_size, target_length = input_ids_shape # target_length: seq_len + + if get_env_device() in ["npu", "mlu", "intel_hpu"]: + mask = paddle.tril(paddle.ones((target_length, target_length))).astype("int32") + else: + mask = paddle.tril(paddle.ones((target_length, target_length), dtype="bool")) + + if past_key_values_length > 0: + # [tgt_len, tgt_len + past_len] + mask = paddle.concat([paddle.ones([target_length, past_key_values_length], dtype="bool"), mask], axis=-1) + + # [bs, 1, tgt_len, tgt_len + past_len] + return mask[None, None, :, :].expand([batch_size, 1, target_length, target_length + past_key_values_length]) +def apply_rotary_pos_emb(q, k, cos, sin, position_ids): + + if position_ids is None: + # Note: Only for LlamaForCausalLMPipe model pretraining + cos = cos[:, : q.shape[1], :, :] # [bs, seq_len, 1, dim] + sin = sin[:, : q.shape[1], :, :] # [bs, seq_len, 1, dim] + else: + cos = cos.squeeze(axis=[0, 2]) # [seq_len, dim] + sin = sin.squeeze(axis=[0, 2]) # [seq_len, dim] + cos = cos[position_ids].unsqueeze(2) # [bs, seq_len, 1, dim] + sin = sin[position_ids].unsqueeze(2) # [bs, seq_len, 1, dim] + q_embed = (q * cos) + (rotate_half(q) * sin) + k_embed = (k * cos) + (rotate_half(k) * sin) + return q_embed, k_embed +def assign_kv_heads(num_kv_heads: int, num_gpus: int): + # Initialize the assignment list + """ + Assign kv heads to different GPUs in the Tensor Parallel Setup + + Examples: + assign_kv_heads(num_kv_heads=1, num_gpus=2): [[0], [0]] + assign_kv_heads(num_kv_heads=2, num_gpus=2): [[0], [1]] + assign_kv_heads(num_kv_heads=4, num_gpus=2): [[0,1], [2,3]] + assign_kv_heads(num_kv_heads=1, num_gpus=4): [[0],[0],[0],[0]] + assign_kv_heads(num_kv_heads=2, num_gpus=4): [[0],[0],[1],[1]] + assign_kv_heads(num_kv_heads=4, num_gpus=4): [[0],[1],[2],[3]] + """ + assignment_list = [[] for _ in range(num_gpus)] + # Case 1: more heads than cards + if num_kv_heads > num_gpus: + num_heads_per_card = num_kv_heads // num_gpus + for i in range(num_gpus): + for j in range(num_heads_per_card): + assignment_list[i].append(i * num_heads_per_card + j) + # Case 2: more cards than heads. each card get only 1 head. + else: + num_card_per_heads = num_gpus // num_kv_heads + for i in range(num_kv_heads): + for j in range(num_card_per_heads): + assignment_list[i * num_card_per_heads + j].append(i) + return assignment_list +def build_alibi_tensor( + bool_attention_mask: Tensor, num_heads: int, dtype: paddle.dtype, tensor_parallel_degree=1 +) -> Tensor: + batch_size, seq_length = bool_attention_mask.shape[0], bool_attention_mask.shape[-1] + slopes = paddle.to_tensor(_get_interleave(num_heads), dtype="float32") + alibi = slopes.unsqueeze(axis=[1, 2]) * paddle.arange(seq_length, dtype="float32").unsqueeze(axis=[0, 1]).expand( + [num_heads, -1, -1] + ) + alibi = alibi.reshape(shape=(1, num_heads, 1, seq_length)).expand([batch_size, -1, -1, -1]) + return paddle.cast(alibi, dtype) +def get_use_casual_mask(): + """Get the value of the 'USE_CASUAL_MASK' environment variable.""" + return os.getenv("USE_CASUAL_MASK", "False") == "True" +def is_casual_mask(attention_mask): + """ + Upper triangular of attention_mask equals to attention_mask is casual + """ + return (paddle.triu(attention_mask) == attention_mask).all().item() +def parallel_matmul(x: Tensor, y: Tensor, transpose_y=False, tensor_parallel_output=True): + is_fleet_init = True + tensor_parallel_degree = 1 + try: + hcg = fleet.get_hybrid_communicate_group() + model_parallel_group = hcg.get_model_parallel_group() + tensor_parallel_degree = hcg.get_model_parallel_world_size() + except: + is_fleet_init = False + + if paddle.in_dynamic_mode(): + y_is_distributed = y.is_distributed + else: + y_is_distributed = tensor_parallel_degree > 1 + + if is_fleet_init and tensor_parallel_degree > 1 and y_is_distributed: + # if not running under distributed.launch, it will raise AttributeError: 'Fleet' object has no attribute '_hcg' + input_parallel = paddle.distributed.collective._c_identity(x, group=model_parallel_group) + logits = paddle.matmul(input_parallel, y, transpose_y=transpose_y) + + if tensor_parallel_output: + return logits + + return paddle.distributed.collective._c_concat(logits, group=model_parallel_group) + + else: + logits = paddle.matmul(x, y, transpose_y=transpose_y) + return logits +def repeat_kv(hidden_states: paddle.Tensor, n_rep: int) -> paddle.Tensor: + """ + This is the equivalent of paddle.repeat_interleave(hidden_states, n_rep, axis=1). The hidden states go from (batch, + num_key_value_heads, seqlen, head_dim) to (batch, num_attention_heads, seqlen, head_dim) + """ + batch, slen, num_key_value_heads, head_dim = hidden_states.shape + if n_rep == 1: + return hidden_states + + hidden_states = hidden_states.unsqueeze(-2).tile([1, 1, 1, n_rep, 1]) + return hidden_states.reshape([batch, slen, num_key_value_heads * n_rep, head_dim]) +def rotate_half(x): + """Rotates half the hidden dims of the input.""" + x1 = x[..., : x.shape[-1] // 2] + x2 = x[..., x.shape[-1] // 2 :] + return paddle.concat([-x2, x1], axis=-1) # shape is the same as x +def swiglu(x, y=None): + if y is None: + x, y = paddle.chunk(x, chunks=2, axis=-1) + return F.silu(x) * y + +# --- Injected Classes --- +class ConcatMaskedLoss(PyLayer): + @staticmethod + def forward(ctx, inp, axis, group): + inputs = [] + paddle.distributed.all_gather(inputs, inp, group=group) + with paddle.no_grad(): + cat = paddle.concat(inputs, axis=axis) + ctx.args_axis = axis + ctx.args_group = group + return cat + + @staticmethod + def backward(ctx, grad): + axis = ctx.args_axis + group = ctx.args_group + with paddle.no_grad(): + grads = paddle.split(grad, paddle.distributed.get_world_size(group), axis=axis) + grad = grads[paddle.distributed.get_rank(group)] + return grad +"""Paddle Qwen2 model.""" + +def get_triangle_upper_mask(x, mask=None): + if mask is not None: + return mask + # [bsz, n_head, q_len, kv_seq_len] + shape = x.shape + # [bsz, 1, q_len, kv_seq_len] + shape[1] = 1 + mask = paddle.full(shape, paddle.finfo(x.dtype).min, dtype=x.dtype) + mask = paddle.triu(mask, diagonal=1) + mask.stop_gradient = True + return mask + +def scaled_dot_product_attention( + query_states, + config, + key_states, + value_states, + attention_mask, + output_attentions, + attn_mask_startend_row_indices=None, + training=True, + sequence_parallel=False, + skip_recompute=False, +): + bsz, q_len, num_heads, head_dim = query_states.shape + _, kv_seq_len, _, _ = value_states.shape + + if config.use_flash_attention and flash_attention: + # Paddle Flash Attention input [ bz, seqlen, nhead, head_dim] + # Torch Flash Attention input [ bz, nhead, seqlen, head_dim] + + return fusion_ops.fusion_flash_attention( + query_states, + config, + key_states, + value_states, + attention_mask, + output_attentions, + attn_mask_startend_row_indices=attn_mask_startend_row_indices, + sequence_parallel=sequence_parallel, + skip_recompute=skip_recompute, + ) + else: + # [ bz, seqlen, nhead, head_dim] -> [bs, nhead, seq_len, head_dim] + query_states = paddle.transpose(query_states, [0, 2, 1, 3]) + # merge with the next transpose + key_states = paddle.transpose(key_states, [0, 2, 1, 3]) + value_states = paddle.transpose(value_states, [0, 2, 1, 3]) + + # Add pre divided factor to fix nan under float16. + if paddle.in_dynamic_mode() and query_states.dtype == paddle.float16: + pre_divided_factor = 32 + else: + pre_divided_factor = 1 + + attn_weights = paddle.matmul( + query_states / (math.sqrt(head_dim) * pre_divided_factor), key_states.transpose([0, 1, 3, 2]) + ) + + if attn_weights.shape != [bsz, num_heads, q_len, kv_seq_len]: + raise ValueError( + f"Attention weights should be of shape {(bsz, num_heads, q_len, kv_seq_len)}, but is" + f" {attn_weights.shape}" + ) + + if attention_mask is None: + attention_mask = get_triangle_upper_mask(attn_weights) + + attention_mask = attention_mask.reshape([bsz, 1, q_len, kv_seq_len]) + if attention_mask.shape != [bsz, 1, q_len, kv_seq_len]: + raise ValueError( + f"Attention mask should be of shape {(bsz, 1, q_len, kv_seq_len)}, but is {attention_mask.shape}" + ) + + attn_weights = attn_weights + attention_mask + + if not paddle.in_dynamic_mode(): + attn_weights = F.softmax(attn_weights * pre_divided_factor, axis=-1, dtype="float32").astype( + query_states.dtype + ) + else: + with paddle.amp.auto_cast(False): + attn_weights = F.softmax( + attn_weights.astype("float32") * pre_divided_factor, axis=-1, dtype="float32" + ).astype(query_states.dtype) + + attn_weights = F.dropout(attn_weights, p=config.attention_dropout, training=training) + + attn_output = paddle.matmul(attn_weights, value_states) + attn_output = attn_output.transpose([0, 2, 1, 3]) + + if sequence_parallel: + attn_output = attn_output.reshape([bsz * q_len, head_dim * num_heads]) + else: + attn_output = attn_output.reshape([bsz, q_len, head_dim * num_heads]) + return (attn_output, attn_weights) if output_attentions else attn_output + + +class Qwen2RMSNorm(nn.Layer): + """Qwen2的RMSNorm,继承自LlamaRMSNorm""" + def __init__(self, config: Qwen2Config): + super().__init__() + self.hidden_size = config.hidden_size + self.weight = paddle.create_parameter( + shape=[self.hidden_size], + dtype=paddle.get_default_dtype(), + default_initializer=nn.initializer.Constant(1.0), + ) + self.variance_epsilon = config.rms_norm_eps + self.config = config + + if config.sequence_parallel: + mark_as_sequence_parallel_parameter(self.weight) + + def forward(self, hidden_states): + if self.config.use_fused_rms_norm: + return fusion_ops.fusion_rms_norm(hidden_states, self.weight, self.variance_epsilon) + + if paddle.in_dynamic_mode(): + with paddle.amp.auto_cast(False): + # hidden_states = hidden_states.astype("float32") + # variance = hidden_states.pow(2).mean(-1, keepdim=True) + variance = hidden_states.astype("float32").pow(2).mean(-1, keepdim=True) + hidden_states = paddle.rsqrt(variance + self.variance_epsilon) * hidden_states + else: + hidden_states = hidden_states.astype("float32") + variance = hidden_states.pow(2).mean(-1, keepdim=True) + hidden_states = paddle.rsqrt(variance + self.variance_epsilon) * hidden_states + + if self.weight.dtype in [paddle.float16, paddle.bfloat16]: + hidden_states = paddle.cast(hidden_states, self.weight.dtype) + return hidden_states * self.weight +class Qwen2RotaryEmbedding(nn.Layer): + def __init__(self, dim, max_position_embeddings=2048, base=10000): + super().__init__() + self.dim = dim + self.max_position_embeddings = max_position_embeddings + self.base = base + # [dim / 2] + self.inv_freq = 1.0 / (self.base ** (paddle.cast(paddle.arange(0, self.dim, 2), dtype="float32") / self.dim)) + self._set_cos_sin_cache(seq_len=max_position_embeddings) + def _set_cos_sin_cache(self, seq_len): + self.max_seq_len_cached = seq_len + if self.inv_freq.dtype != paddle.float32: + self.inv_freq = 1.0 / ( + self.base ** (paddle.cast(paddle.arange(0, self.dim, 2), dtype="float32") / self.dim) + ) + # [seq_len] + t = paddle.arange(seq_len, dtype="float32") + # [seq_len, dim/2] + if get_env_device() == "intel_hpu": + # fallback einsum to intel Gaudi TPC since MME doesn't support FP32 + freqs = t.unsqueeze(1) * self.inv_freq.unsqueeze(0) + else: + freqs = paddle.einsum("i,j->ij", t, self.inv_freq) + # Different from paper, but it uses a different permutation in order to obtain the same calculation + # [seq_len, dim] + emb = paddle.concat([freqs, freqs], axis=-1) + # [1, seqlen, 1, dim] + self.cos_cached = emb.cos()[None, :, None, :] + self.sin_cached = emb.sin()[None, :, None, :] + self.cos_sin_table = None if get_env_device() != "gcu" else paddle.concat([freqs.cos(), freqs.sin()], axis=-1) + def forward(self, x, seq_len=None): + if seq_len > self.max_seq_len_cached: + self._set_cos_sin_cache(seq_len) + # x: [bs, num_attention_heads, seq_len, head_size] + if self.cos_cached.dtype != x.dtype and get_env_device() == "intel_hpu": + self.cos_cached = self.cos_cached.cast(x.dtype) + self.sin_cached = self.sin_cached.cast(x.dtype) + cos = self.cos_cached[:, :seq_len, :, :] + sin = self.sin_cached[:, :seq_len, :, :] + return ( + cos.cast(x.dtype) if cos.dtype != x.dtype else cos, + sin.cast(x.dtype) if sin.dtype != x.dtype else sin, + ) + + def get_fused_cos_sin(self, x, seq_len=None): + if self.cos_sin_table is not None and self.cos_sin_table.dtype != x.dtype: + return self.cos_sin_table.cast(x.dtype) + else: + return self.cos_sin_table + +# --- Children of Qwen2RotaryEmbedding --- +class Qwen23RotaryEmbedding(Qwen2RotaryEmbedding): + def __init__( + self, + dim, + max_position_embeddings=8192, + base=500000, + factor=8.0, + low_freq_factor=1.0, + high_freq_factor=4.0, + original_max_position_embeddings=8192, + ): + self.factor = factor + self.low_freq_factor = low_freq_factor + self.high_freq_factor = high_freq_factor + self.original_max_position_embeddings = original_max_position_embeddings + super().__init__(dim, max_position_embeddings, base) + + def _set_cos_sin_cache(self, seq_len): + low_freq_wavelen = self.original_max_position_embeddings / self.low_freq_factor + high_freq_wavelen = self.original_max_position_embeddings / self.high_freq_factor + new_freqs = [] + for freq in self.inv_freq: + wavelen = 2 * math.pi / freq + if wavelen < high_freq_wavelen: + new_freqs.append(freq) + elif wavelen > low_freq_wavelen: + new_freqs.append(freq / self.factor) + else: + assert low_freq_wavelen != high_freq_wavelen + smooth = (self.original_max_position_embeddings / wavelen - self.low_freq_factor) / ( + self.high_freq_factor - self.low_freq_factor + ) + new_freqs.append((1 - smooth) * freq / self.factor + smooth * freq) + self.inv_freq = paddle.to_tensor(new_freqs, dtype=self.inv_freq.dtype) + super()._set_cos_sin_cache(seq_len=seq_len) +class Qwen2DynamicNTKScalingRotaryEmbedding(Qwen2RotaryEmbedding): + """LlamaRotaryEmbedding extended with Dynamic NTK scaling. https://www.reddit.com/r/LocalLLaMA/comments/14mrgpr/dynamically_scaled_rope_further_increases/""" + + def __init__(self, dim, max_position_embeddings=2048, base=10000, scaling_factor=1.0): + self.scaling_factor = scaling_factor + super().__init__(dim, max_position_embeddings, base) + + def _scale_cos_sin(self, seq_len): + # [seq_len] + t = paddle.arange(seq_len, dtype="float32") + # [seq_len, dim/2] + alpha = (self.scaling_factor * seq_len / self.max_position_embeddings) - (self.scaling_factor - 1) + base = self.base * alpha ** (self.dim / (self.dim - 2)) + inv_freq = 1.0 / (base ** (paddle.cast(paddle.arange(0, self.dim, 2), dtype="float32") / self.dim)) + freqs = paddle.einsum("i,j->ij", t, inv_freq) + # Different from paper, but it uses a different permutation in order to obtain the same calculation + # [seq_len, dim] + emb = paddle.concat([freqs, freqs], axis=-1) + # [1, seqlen, 1, dim] + scale_cos = emb.cos()[None, :, None, :] + scale_sin = emb.sin()[None, :, None, :] + scale_cos_sin = None if get_env_device() != "gcu" else paddle.concat([freqs.cos(), freqs.sin()], axis=-1) + return scale_cos, scale_sin, scale_cos_sin + + def forward(self, x, seq_len=None): + # x: [bs, num_attention_heads, seq_len, head_size] + if seq_len > self.max_position_embeddings: + scale_cos, scale_sin, _ = self._scale_cos_sin(seq_len=seq_len) + else: + scale_cos, scale_sin = self.cos_cached, self.sin_cached + cos = scale_cos[:, :seq_len, :, ...] + sin = scale_sin[:, :seq_len, :, ...] + return ( + cos.cast(x.dtype) if cos.dtype != x.dtype else cos, + sin.cast(x.dtype) if sin.dtype != x.dtype else sin, + ) + + def get_fused_cos_sin(self, x, seq_len=None): + if seq_len > self.max_position_embeddings: + _, _, scale_cos_sin = self._scale_cos_sin(seq_len=seq_len) + else: + scale_cos_sin = self.cos_sin_table + if scale_cos_sin is not None and scale_cos_sin.dtype != x.dtype: + return scale_cos_sin.cast(x.dtype) + else: + return scale_cos_sin +class Qwen2LinearScalingRotaryEmbedding(Qwen2RotaryEmbedding): + def __init__(self, dim, max_position_embeddings=2048, base=10000, scaling_factor=1.0): + self.scaling_factor = scaling_factor + super().__init__(dim, max_position_embeddings * scaling_factor, base) + + def _set_cos_sin_cache(self, seq_len): + self.max_seq_len_cached = seq_len + # [seq_len] + t = paddle.arange(seq_len, dtype="float32") + t = t / self.scaling_factor + # [seq_len, dim/2] + if get_env_device() == "intel_hpu": + # fallback einsum to intel Gaudi TPC since MME doesn't support FP32 + freqs = t.unsqueeze(1) * self.inv_freq.unsqueeze(0) + else: + freqs = paddle.einsum("i,j->ij", t, self.inv_freq) + # Different from paper, but it uses a different permutation in order to obtain the same calculation + # [seq_len, dim] + emb = paddle.concat([freqs, freqs], axis=-1) + # [1, seqlen, 1, dim] + self.cos_cached = emb.cos()[None, :, None, :] + self.sin_cached = emb.sin()[None, :, None, :] + self.cos_sin_table = None if get_env_device() != "gcu" else paddle.concat([freqs.cos(), freqs.sin()], axis=-1) +class Qwen2NTKScalingRotaryEmbedding(Qwen2RotaryEmbedding): + """LlamaRotaryEmbedding extended with NTK scaling. https://www.reddit.com/r/LocalLLaMA/comments/14lz7j5/ntkaware_scaled_rope_allows_llama_models_to_have/""" + + def __init__(self, dim, max_position_embeddings=2048, base=10000, scaling_factor=1.0): + base = base * scaling_factor ** (dim / (dim - 2)) + self.scaling_factor = scaling_factor + super().__init__(dim, max_position_embeddings * scaling_factor, base) + +class Qwen2MLP(nn.Layer): + """Qwen2的MLP,继承自LlamaMLP""" + def __init__(self, config: Qwen2Config,is_shared=False, skip_recompute_ops=None): + super().__init__() + if skip_recompute_ops is None: + skip_recompute_ops = {} + self.skip_recompute_ops = skip_recompute_ops + self.hidden_size = config.hidden_size + self.intermediate_size = config.intermediate_size + self.tensor_parallel_degree = config.tensor_parallel_degree + self.fuse_attention_ffn = config.fuse_attention_ffn + + if config.sequence_parallel: + ColumnParallelLinear = linear_utils.ColumnSequenceParallelLinear + RowParallelLinear = linear_utils.RowSequenceParallelLinear + + # NOTE: refined_recompute is only supported when `recompute_use_reentrant=False` + if config.recompute and not config.recompute_use_reentrant: + if skip_recompute_ops.get("mlp_column_ln", False): + ColumnParallelLinear = RRColumnSequenceParallelLinear + if skip_recompute_ops.get("mlp_row_ln", False): + RowParallelLinear = RRRowSequenceParallelLinear + else: + ColumnParallelLinear = linear_utils.ColumnParallelLinear + RowParallelLinear = linear_utils.RowParallelLinear + + # NOTE: refined_recompute is only supported when `recompute_use_reentrant=False` + if config.recompute and not config.recompute_use_reentrant: + if skip_recompute_ops.get("mlp_column_ln", False): + ColumnParallelLinear = RRColumnParallelLinear + if skip_recompute_ops.get("mlp_row_ln", False): + RowParallelLinear = RRRowParallelLinear + + if config.tensor_parallel_degree > 1: + if config.fuse_attention_ffn: + self.gate_up_fused_proj = ColumnParallelLinear( + self.hidden_size, + self.intermediate_size * 2, + gather_output=False, + has_bias=False, + ) + else: + self.gate_proj = ColumnParallelLinear( + self.hidden_size, + self.intermediate_size, + gather_output=False, + has_bias=False, + ) + self.up_proj = ColumnParallelLinear( + self.hidden_size, + self.intermediate_size, + gather_output=False, + has_bias=False, + ) + + self.down_proj = RowParallelLinear( + self.intermediate_size, + self.hidden_size, + input_is_parallel=True, + has_bias=False, + ) + else: + if config.fuse_attention_ffn: + self.gate_up_fused_proj = Linear(self.hidden_size, self.intermediate_size * 2, bias_attr=False) + else: + self.gate_proj = Linear(self.hidden_size, self.intermediate_size, bias_attr=False) + self.up_proj = Linear(self.hidden_size, self.intermediate_size, bias_attr=False) + + self.down_proj = Linear(self.intermediate_size, self.hidden_size, bias_attr=False) + if config.hidden_act == "silu": + self.act_fn = fusion_ops.swiglu + self.fuse_swiglu = True + else: + self.act_fn = ACT2FN[config.hidden_act] + self.fuse_swiglu = False + # Qwen2的MLP结构与Llama相同,但使用不同的配置 + def forward(self, x): + if self.fuse_attention_ffn: + x = self.gate_up_fused_proj(x) + if self.fuse_swiglu: + y = None + else: + x, y = x.chunk(2, axis=-1) + else: + x, y = self.gate_proj(x), self.up_proj(x) + + if self.fuse_swiglu: + x = self.act_fn(x, y) + else: + x = self.act_fn(x) * y + + return self.down_proj(x) + +class Qwen2Attention(nn.Layer): + """ + Multi-headed attention from 'Attention Is All You Need' paper. Modified to use sliding window attention: Longformer + and "Generating Long Sequences with Sparse Transformers". + """ + + def __init__(self, config: Qwen2Config, layerwise_recompute: bool = True, skip_recompute_ops=None): + super().__init__() + if skip_recompute_ops is None: + skip_recompute_ops = {} + self.config = config + self.skip_recompute_ops = skip_recompute_ops + self.hidden_size = config.hidden_size + self.num_heads = config.num_attention_heads + self.num_attention_heads = config.num_attention_heads + + self.head_dim = getattr(config, "head_dim", config.hidden_size // config.num_attention_heads) + + self.num_key_value_heads = config.num_key_value_heads + assert config.num_attention_heads // config.num_key_value_heads + self.num_key_value_groups = config.num_attention_heads // config.num_key_value_heads + self.gqa_or_mqa = config.num_attention_heads != config.num_key_value_heads + self.max_position_embeddings = config.max_position_embeddings + self.rope_theta = config.rope_theta + self.is_causal = True + self.attention_dropout = config.attention_dropout + + # self.seq_length = config.seq_length + self.sequence_parallel = config.sequence_parallel + self.has_bias = config.attention_bias + self.fuse_attention_qkv = config.fuse_attention_qkv + + # Note that we will actually perform a recompute only if both enable_recompute and layerwise_recompute are set to True + # Enable_recompute defaults to False and is controlled by Trainer + self.enable_recompute = False + self.layerwise_recompute = layerwise_recompute + self.recompute_granularity = config.recompute_granularity + if config.tensor_parallel_degree > 1: + assert ( + self.num_heads % config.tensor_parallel_degree == 0 + ), f"num_heads: {self.num_heads}, tensor_parallel_degree: {config.tensor_parallel_degree}" + self.num_heads = self.num_heads // config.tensor_parallel_degree + + assert ( + self.num_key_value_heads % config.tensor_parallel_degree == 0 + ), f"num_key_value_heads: {self.num_key_value_heads}, tensor_parallel_degree: {config.tensor_parallel_degree}" + self.num_key_value_heads = self.num_key_value_heads // config.tensor_parallel_degree + + self.use_fused_rope = config.use_fused_rope + if self.use_fused_rope: + if get_env_device() not in ["gpu", "xpu"] or fused_rotary_position_embedding is None: + warnings.warn( + "Enable fuse rope in the config, but fuse rope is not available. " + "Will disable fuse rope. Try using latest gpu version of Paddle." + ) + self.use_fused_rope = False + + if config.sequence_parallel: + ColumnParallelLinear = linear_utils.ColumnSequenceParallelLinear + RowParallelLinear = linear_utils.RowSequenceParallelLinear + + # NOTE: refined_recompute is only supported when `recompute_use_reentrant=False` + if config.recompute and not config.recompute_use_reentrant: + if skip_recompute_ops.get("attention_column_ln", False): + ColumnParallelLinear = RRColumnSequenceParallelLinear + if skip_recompute_ops.get("attention_row_ln", False): + RowParallelLinear = RRRowSequenceParallelLinear + else: + ColumnParallelLinear = linear_utils.ColumnParallelLinear + RowParallelLinear = linear_utils.RowParallelLinear + + # NOTE: refined_recompute is only supported when `recompute_use_reentrant=False` + if config.recompute and not config.recompute_use_reentrant: + if skip_recompute_ops.get("attention_column_ln", False): + ColumnParallelLinear = RRColumnParallelLinear + if skip_recompute_ops.get("attention_row_ln", False): + RowParallelLinear = RRRowParallelLinear + + if config.tensor_parallel_degree > 1: + if self.fuse_attention_qkv: + self.qkv_proj = ColumnParallelLinear( + self.hidden_size, + self.num_attention_heads * self.head_dim + 2 * self.config.num_key_value_heads * self.head_dim, + has_bias=self.has_bias, + gather_output=False, + ) + else: + self.q_proj = ColumnParallelLinear( + self.hidden_size, + self.num_attention_heads * self.head_dim, + has_bias=self.has_bias, + gather_output=False, + ) + self.k_proj = ColumnParallelLinear(self.hidden_size, self.config.num_key_value_heads * self.head_dim, has_bias=self.has_bias, gather_output=False) # fmt:skip + self.v_proj = ColumnParallelLinear(self.hidden_size, self.config.num_key_value_heads * self.head_dim, has_bias=self.has_bias, gather_output=False) # fmt:skip + self.o_proj = RowParallelLinear(self.hidden_size, self.hidden_size, has_bias=False, input_is_parallel=True) + else: + if self.fuse_attention_qkv: + self.qkv_proj = Linear( + self.hidden_size, + self.num_attention_heads * self.head_dim + 2 * self.config.num_key_value_heads * self.head_dim, + ) + else: + self.q_proj = Linear( + self.hidden_size, self.num_attention_heads * self.head_dim, bias_attr=self.has_bias + ) + self.k_proj = Linear( + self.hidden_size, self.config.num_key_value_heads * self.head_dim, bias_attr=self.has_bias + ) + self.v_proj = Linear( + self.hidden_size, self.config.num_key_value_heads * self.head_dim, bias_attr=self.has_bias + ) + self.o_proj = Linear(self.num_attention_heads * self.head_dim, self.hidden_size, bias_attr=False) + + self.rotary_emb = Qwen2RotaryEmbedding( + self.head_dim, + max_position_embeddings=self.max_position_embeddings, + base=self.rope_theta, + ) + + self.attn_func = scaled_dot_product_attention + + # NOTE: refined_recompute is only supported when `recompute_use_reentrant=False` + if config.recompute and not config.recompute_use_reentrant and skip_recompute_ops.get("flash_attn", False): + self.attn_func = partial(scaled_dot_product_attention, skip_recompute=True) + + def forward( + self, + hidden_states, + position_ids: Optional[Tuple[paddle.Tensor]] = None, + past_key_value: Optional[Tuple[paddle.Tensor]] = None, + attention_mask: Optional[paddle.Tensor] = None, + output_attentions: bool = False, + use_cache: bool = False, + attn_mask_startend_row_indices: Optional[paddle.Tensor] = None, + batch_size: Optional[int] = None, + **kwargs, + ) -> Tuple[paddle.Tensor, Optional[paddle.Tensor], Optional[Tuple[paddle.Tensor]]]: + """Input shape: Batch x Time x Channel""" + # [bs, seq_len, num_head * head_dim] -> [seq_len / n, bs, num_head * head_dim] (n is model parallelism) + + if self.fuse_attention_qkv: + mix_layer = self.qkv_proj(hidden_states) + if self.sequence_parallel: + target_shape = [ + batch_size, + -1, + self.num_key_value_heads, + (self.num_key_value_groups + 2) * self.head_dim, + ] + else: + target_shape = [0, 0, self.num_key_value_heads, (self.num_key_value_groups + 2) * self.head_dim] + mix_layer = paddle.reshape_(mix_layer, target_shape) + query_states, key_states, value_states = paddle.split( + mix_layer, + num_or_sections=[self.num_key_value_groups * self.head_dim, self.head_dim, self.head_dim], + axis=-1, + ) + if self.gqa_or_mqa: + query_states = paddle.reshape_(query_states, [0, 0, self.num_heads, self.head_dim]) + else: + query_states = self.q_proj(hidden_states) + key_states = self.k_proj(hidden_states) + value_states = self.v_proj(hidden_states) + + if self.sequence_parallel: + target_query_shape = [batch_size, -1, self.num_heads, self.head_dim] + target_key_value_shape = [batch_size, -1, self.num_key_value_heads, self.head_dim] + else: + target_query_shape = [0, 0, self.num_heads, self.head_dim] + target_key_value_shape = [0, 0, self.num_key_value_heads, self.head_dim] + query_states = query_states.reshape(shape=target_query_shape) + key_states = key_states.reshape(shape=target_key_value_shape) + value_states = value_states.reshape(shape=target_key_value_shape) + + if position_ids is not None and not self.use_fused_rope: + kv_seq_len = position_ids.max().item() + 1 + else: + kv_seq_len = key_states.shape[-3] + if past_key_value is not None: + kv_seq_len += past_key_value[0].shape[-3] + if self.use_fused_rope: + assert past_key_value is None, "fuse rotary not support cache kv for now" + cos, sin = self.rotary_emb(value_states, seq_len=kv_seq_len) + query_states, key_states, _ = fused_rotary_position_embedding( + query_states, + key_states, + v=None, + sin=sin, + cos=cos, + position_ids=position_ids, + use_neox_rotary_style=False, + ) + else: + cos, sin = self.rotary_emb(value_states, seq_len=kv_seq_len) + query_states, key_states = apply_rotary_pos_emb(query_states, key_states, cos, sin, position_ids) + + # [bs, seq_len, num_head, head_dim] + if past_key_value is not None: + key_states = paddle.concat([past_key_value[0], key_states], axis=1) + value_states = paddle.concat([past_key_value[1], value_states], axis=1) + past_key_value = (key_states, value_states) if use_cache else None + + # TODO(wj-Mcat): use broadcast strategy when n_kv_heads = 1 + # repeat k/v heads if n_kv_heads < n_heads + paddle_version = float(paddle.__version__[:3]) + if not self.config.use_flash_attention or ((paddle_version != 0.0) and (paddle_version <= 2.6)): + key_states = repeat_kv(key_states, self.num_key_value_groups) + value_states = repeat_kv(value_states, self.num_key_value_groups) + + has_gradient = not (query_states.stop_gradient and key_states.stop_gradient and value_states.stop_gradient) + if ( + self.enable_recompute + and self.layerwise_recompute + and has_gradient + and self.recompute_granularity == "core_attn" + ): + recompute_fn = rr_recompute if any(self.skip_recompute_ops.values()) else recompute + outputs = recompute_fn( + self.attn_func, + query_states, + self.config, + key_states, + value_states, + attention_mask, + output_attentions, + attn_mask_startend_row_indices=attn_mask_startend_row_indices, + training=self.training, + sequence_parallel=self.sequence_parallel, + use_reentrant=self.config.recompute_use_reentrant, + ) + else: + outputs = self.attn_func( + query_states, + self.config, + key_states, + value_states, + attention_mask, + output_attentions, + attn_mask_startend_row_indices=attn_mask_startend_row_indices, + training=self.training, + sequence_parallel=self.sequence_parallel, + ) + if output_attentions: + attn_output, attn_weights = outputs + else: + attn_output = outputs + + # if sequence_parallel is true, out shape are [q_len / n, bs, num_head * head_dim] + # else their shape are [bs, q_len, num_head * head_dim], n is mp parallelism. + attn_output = self.o_proj(attn_output) + + if not output_attentions: + attn_weights = None + + outputs = (attn_output,) + + if output_attentions: + outputs += (attn_weights,) + + if use_cache: + outputs += (past_key_value,) + + if type(outputs) is tuple and len(outputs) == 1: + outputs = outputs[0] + + return outputs + + +class Qwen2DecoderLayer(nn.Layer): + """Qwen2的解码器层,继承自LlamaDecoderLayer""" + def __init__(self, config: Qwen2Config, layerwise_recompute: bool = False, skip_recompute_ops=None): + super().__init__() + self.config = config + if skip_recompute_ops is None: + skip_recompute_ops = {} + self.skip_recompute_ops = skip_recompute_ops + self.hidden_size = config.hidden_size + self.self_attn = Qwen2Attention(config, layerwise_recompute, skip_recompute_ops=skip_recompute_ops) + self.mlp = Qwen2MLP(config, skip_recompute_ops=skip_recompute_ops) + self.input_layernorm = Qwen2RMSNorm(config) + self.post_attention_layernorm = Qwen2RMSNorm(config) + self.sequence_parallel = config.sequence_parallel + # Note that we will actually perform a recompute only if both enable_recompute and layerwise_recompute are set to True + # Enable_recompute defaults to False and is controlled by Trainer + self.enable_recompute = False + self.layerwise_recompute = layerwise_recompute + self.recompute_granularity = config.recompute_granularity + def forward( + self, + hidden_states: paddle.Tensor, + position_ids: Optional[paddle.Tensor] = None, + attention_mask: Optional[paddle.Tensor] = None, + output_attentions: Optional[bool] = False, + past_key_value: Optional[Tuple[paddle.Tensor]] = None, + use_cache: Optional[bool] = False, + attn_mask_startend_row_indices: Optional[paddle.Tensor] = None, + batch_size: Optional[int] = None, + alibi: Optional[paddle.Tensor] = None, + npu_is_casual: bool = False, + **kwargs, + ) -> Tuple[paddle.Tensor, Optional[Tuple[paddle.Tensor, paddle.Tensor]]]: + """ + Args: + hidden_states (`paddle.Tensor`): input to the layer of shape `(batch, seq_len, embed_dim)` + attention_mask (`paddle.Tensor`, *optional*): attention mask of size + `(batch, sequence_length)` where padding elements are indicated by 0. + output_attentions (`bool`, *optional*): + Whether or not to return the attentions tensors of all attention layers. See `attentions` under + returned tensors for more detail. + use_cache (`bool`, *optional*): + If set to `True`, `past_key_values` key value states are returned and can be used to speed up decoding + (see `past_key_values`). + past_key_value (`Tuple(paddle.Tensor)`, *optional*): cached past key and value projection states + """ + + # [bs * seq_len, embed_dim] -> [seq_len * bs / n, embed_dim] (sequence_parallel) + residual = hidden_states + + hidden_states = self.input_layernorm(hidden_states) + + # Self Attention + has_gradient = not hidden_states.stop_gradient + if ( + self.enable_recompute + and self.layerwise_recompute + and has_gradient + and self.recompute_granularity == "full_attn" + ): + recompute_fn = rr_recompute if any(self.skip_recompute_ops.values()) else recompute + outputs = recompute_fn( + self.self_attn, + hidden_states, + position_ids, + past_key_value, + attention_mask, + output_attentions, + use_cache, + attn_mask_startend_row_indices, + batch_size, + use_reentrant=self.config.recompute_use_reentrant, + ) + else: + outputs = self.self_attn( + hidden_states, + position_ids, + past_key_value, + attention_mask, + output_attentions, + use_cache, + attn_mask_startend_row_indices=attn_mask_startend_row_indices, + batch_size=batch_size, + ) + + if type(outputs) is tuple: + hidden_states = outputs[0] + else: + hidden_states = outputs + + if output_attentions: + self_attn_weights = outputs[1] + + if use_cache: + present_key_value = outputs[2 if output_attentions else 1] + + hidden_states = residual + hidden_states + + # Fully Connected + residual = hidden_states + hidden_states = self.post_attention_layernorm(hidden_states) + hidden_states = self.mlp(hidden_states) + + hidden_states = residual + hidden_states + + outputs = (hidden_states,) + + if output_attentions: + outputs += (self_attn_weights,) + + if use_cache: + outputs += (present_key_value,) + + if type(outputs) is tuple and len(outputs) == 1: + outputs = outputs[0] + + return outputs + + +class Qwen2PretrainedModel(PretrainedModel): + """Qwen2预训练模型基类,继承自LlamaPretrainedModel""" + config_class = Qwen2Config + base_model_prefix = "qwen2" + _keys_to_ignore_on_load_unexpected = [r"self_attn.rotary_emb.inv_freq"] + @classmethod + def _get_name_mappings(cls, config: Qwen2Config) -> list[StateDictNameMapping]: + mappings: list[StateDictNameMapping] = [] + model_mappings = [ + ["embed_tokens.weight"], + ["norm.weight"], + ] + for layer_index in range(config.num_hidden_layers): + layer_mappings = [ + [f"layers.{layer_index}.self_attn.q_proj.weight", None, "transpose"], + [f"layers.{layer_index}.self_attn.k_proj.weight", None, "transpose"], + [f"layers.{layer_index}.self_attn.v_proj.weight", None, "transpose"], + [f"layers.{layer_index}.self_attn.q_proj.bias", None], + [f"layers.{layer_index}.self_attn.k_proj.bias", None], + [f"layers.{layer_index}.self_attn.v_proj.bias", None], + [f"layers.{layer_index}.self_attn.o_proj.weight", None, "transpose"], + [f"layers.{layer_index}.mlp.up_proj.weight", None, "transpose"], + [f"layers.{layer_index}.mlp.gate_proj.weight", None, "transpose"], + [f"layers.{layer_index}.mlp.down_proj.weight", None, "transpose"], + [f"layers.{layer_index}.self_attn.rotary_emb.inv_freq"], + [f"layers.{layer_index}.input_layernorm.weight"], + [f"layers.{layer_index}.post_attention_layernorm.weight"], + ] + model_mappings.extend(layer_mappings) + + init_name_mappings(mappings=model_mappings) + # base-model prefix "Qwen2MoEModel" + if "Qwen2Model" not in config.architectures: + for mapping in model_mappings: + mapping[0] = "model." + mapping[0] + mapping[1] = "qwen2." + mapping[1] + if not config.tie_word_embeddings: + model_mappings.append(["lm_head.weight", "lm_head.weight", "transpose"]) + + mappings = [StateDictNameMapping(*mapping, index=index) for index, mapping in enumerate(model_mappings)] + return mappings + + @classmethod + def _get_tensor_parallel_mappings(cls, config: Qwen2Config, is_split=True): + + from ..conversion_utils import split_or_merge_func + + fn = split_or_merge_func( + is_split=is_split, + tensor_parallel_degree=config.tensor_parallel_degree, + tensor_parallel_rank=config.tensor_parallel_rank, + num_attention_heads=config.num_attention_heads, + ) + + def get_tensor_parallel_split_mappings(num_layers): + final_actions = {} + + base_actions = { + # Row Linear + "embed_tokens.weight": partial(fn, is_column=False), + "layers.0.self_attn.o_proj.weight": partial(fn, is_column=False), + "layers.0.mlp.down_proj.weight": partial(fn, is_column=False), + } + + if config.tie_word_embeddings: + base_actions["lm_head.weight"] = partial(fn, is_column=False) + else: + base_actions["lm_head.weight"] = partial(fn, is_column=True) + + if not config.vocab_size % config.tensor_parallel_degree == 0: + base_actions.pop("lm_head.weight") + base_actions.pop("embed_tokens.weight") + # Column Linear + if config.fuse_attention_qkv: + base_actions["layers.0.self_attn.qkv_proj.weight"] = partial(fn, is_column=True) + base_actions["layers.0.self_attn.qkv_proj.bias"] = partial(fn, is_column=True) + else: + base_actions["layers.0.self_attn.q_proj.weight"] = partial(fn, is_column=True) + base_actions["layers.0.self_attn.q_proj.bias"] = partial(fn, is_column=True) + # if we have enough num_key_value_heads to split, then split it. + if config.num_key_value_heads % config.tensor_parallel_degree == 0: + base_actions["layers.0.self_attn.k_proj.weight"] = partial(fn, is_column=True) + base_actions["layers.0.self_attn.v_proj.weight"] = partial(fn, is_column=True) + base_actions["layers.0.self_attn.k_proj.bias"] = partial(fn, is_column=True) + base_actions["layers.0.self_attn.v_proj.bias"] = partial(fn, is_column=True) + + if config.fuse_attention_ffn: + base_actions["layers.0.mlp.gate_up_fused_proj.weight"] = partial( + fn, is_column=True, is_naive_2fuse=True + ) + else: + base_actions["layers.0.mlp.gate_proj.weight"] = partial(fn, is_column=True) + base_actions["layers.0.mlp.up_proj.weight"] = partial(fn, is_column=True) + + for key, action in base_actions.items(): + if "layers.0." in key: + for i in range(num_layers): + final_actions[key.replace("layers.0.", f"layers.{i}.")] = action + final_actions[key] = action + + return final_actions + + mappings = get_tensor_parallel_split_mappings(config.num_hidden_layers) + + return mappings + @classmethod + def _get_fuse_or_split_param_mappings(cls, config: Qwen2Config, is_fuse=False): + # return parameter fuse utils + from ..conversion_utils import split_or_fuse_func + + fn = split_or_fuse_func(is_fuse=is_fuse) + + # last key is fused key, other keys are to be fused. + fuse_qkv_keys = [ + ( + "layers.0.self_attn.q_proj.weight", + "layers.0.self_attn.k_proj.weight", + "layers.0.self_attn.v_proj.weight", + "layers.0.self_attn.qkv_proj.weight", + ), + ( + "layers.0.self_attn.q_proj.bias", + "layers.0.self_attn.k_proj.bias", + "layers.0.self_attn.v_proj.bias", + "layers.0.self_attn.qkv_proj.bias", + ), + ] + + fuse_gate_up_keys = ( + "layers.0.mlp.gate_proj.weight", + "layers.0.mlp.up_proj.weight", + "layers.0.mlp.gate_up_fused_proj.weight", + ) + num_heads = config.num_attention_heads + num_key_value_heads = getattr(config, "num_key_value_heads", num_heads) + fuse_attention_qkv = getattr(config, "fuse_attention_qkv", False) + fuse_attention_ffn = getattr(config, "fuse_attention_ffn", False) + + final_actions = {} + if is_fuse: + if fuse_attention_qkv: + for i in range(config.num_hidden_layers): + for fuse_keys in fuse_qkv_keys: + keys = tuple([key.replace("layers.0.", f"layers.{i}.") for key in fuse_keys]) + final_actions[keys] = partial( + fn, is_qkv=True, num_heads=num_heads, num_key_value_heads=num_key_value_heads + ) + if fuse_attention_ffn: + for i in range(config.num_hidden_layers): + keys = tuple([key.replace("layers.0.", f"layers.{i}.") for key in fuse_gate_up_keys]) + final_actions[keys] = fn + else: + if not fuse_attention_qkv: + for i in range(config.num_hidden_layers): + for fuse_keys in fuse_qkv_keys: + keys = tuple([key.replace("layers.0.", f"layers.{i}.") for key in fuse_keys]) + final_actions[keys] = partial( + fn, split_nums=3, is_qkv=True, num_heads=num_heads, num_key_value_heads=num_key_value_heads + ) + if not fuse_attention_ffn: + for i in range(config.num_hidden_layers): + keys = tuple([key.replace("layers.0.", f"layers.{i}.") for key in fuse_gate_up_keys]) + final_actions[keys] = partial(fn, split_nums=2) + return final_actions + + def _get_model_flops(self): + if hasattr(self.config, "seq_length"): + seq_length = self.config.seq_length + else: + seq_length = 2048 + + return caculate_llm_per_token_flops( + hidden_size=self.config.hidden_size, + intermediate_size=self.config.intermediate_size, + layer_num=self.config.num_hidden_layers, + vocab_size=self.config.vocab_size, + seq_length=seq_length, + recompute=False, + ) + + def _get_hardware_flops(self): + if hasattr(self.config, "seq_length"): + seq_length = self.config.seq_length + else: + seq_length = 2048 + + return caculate_llm_per_token_flops( + hidden_size=self.config.hidden_size, + intermediate_size=self.config.intermediate_size, + layer_num=self.config.num_hidden_layers, + vocab_size=self.config.vocab_size, + seq_length=seq_length, + recompute=self.config.recompute, + recompute_granularity=self.config.recompute_granularity, + ) + pretrained_init_configuration = QWEN2_PRETRAINED_INIT_CONFIGURATION + pretrained_resource_files_map = QWEN2_PRETRAINED_RESOURCE_FILES_MAP + + def _init_weights(self, layer): + """Initialization hook""" + if self.config.tensor_parallel_degree > 1: + rng_tracker = get_rng_state_tracker().rng_state + if isinstance( + layer, + ( + nn.Linear, + nn.Embedding, + mpu.VocabParallelEmbedding, + mpu.RowParallelLinear, + mpu.ColumnParallelLinear, + linear_utils.RowSequenceParallelLinear, + linear_utils.ColumnSequenceParallelLinear, + Qwen2LMHead, + ), + ): + # In the dygraph mode, use the `set_value` to reset the parameter directly, + # and reset the `state_dict` to update parameter in static mode. + if isinstance(layer.weight, paddle.Tensor): + if layer.weight.is_distributed: + with rng_tracker(): + layer.weight.set_value( + paddle.tensor.normal( + mean=0.0, + std=self.config.initializer_range + if hasattr(self.config, "initializer_range") + else self.qwen2.config.initializer_range, + shape=layer.weight.shape, + ) + ) + else: + layer.weight.set_value( + paddle.tensor.normal( + mean=0.0, + std=self.config.initializer_range + if hasattr(self.config, "initializer_range") + else self.qwen2.config.initializer_range, + shape=layer.weight.shape, + ) + ) + # Layer.apply is DFS https://github.com/PaddlePaddle/Paddle/blob/a6f5021fcc58b21f4414bae6bf4731ef6971582c/python/paddle/nn/layer/layers.py#L527-L530 + # sublayer is init first + # scale RowParallelLinear weight + with paddle.no_grad(): + if isinstance(layer, Qwen2MLP): + factor = 1 / math.sqrt(2 * self.config.num_hidden_layers) + layer.down_proj.weight.scale_(factor) + if isinstance(layer, Qwen2Attention): + factor = 1 / math.sqrt(2 * self.config.num_hidden_layers) + layer.o_proj.weight.scale_(factor) + + +@register_base_model +class Qwen2Model(Qwen2PretrainedModel): + """Qwen2模型,继承自LlamaModel""" + def __init__(self, config: Qwen2Config): + super().__init__(config) + self.vocab_size = config.vocab_size + self.hidden_size = config.hidden_size + self.sequence_parallel = config.sequence_parallel + self.recompute_granularity = config.recompute_granularity + self.no_recompute_layers = config.no_recompute_layers if config.no_recompute_layers is not None else [] + self.config = config + + # Recompute defaults to False and is controlled by Trainer + self.enable_recompute = False + if config.tensor_parallel_degree > 1 and config.vocab_size % config.tensor_parallel_degree == 0: + self.embed_tokens = mpu.VocabParallelEmbedding( + self.vocab_size, + self.hidden_size, + weight_attr=paddle.ParamAttr(initializer=nn.initializer.XavierNormal()), + ) + else: + self.embed_tokens = nn.Embedding( + self.vocab_size, + self.hidden_size, + ) + + self.layers = nn.LayerList( + [ + Qwen2DecoderLayer( + config=config, + layerwise_recompute=layer_idx not in self.no_recompute_layers, + skip_recompute_ops=get_skip_recompute_ops(config, layer_idx), + ) + for layer_idx in range(config.num_hidden_layers) + ] + ) + self.norm = Qwen2RMSNorm(config) + + self.gradient_checkpointing = False + self.padding_idx = config.pad_token_id + @paddle.jit.not_to_static + def recompute_training_full( + self, + layer_module: nn.Layer, + hidden_states: Tensor, + position_ids: Optional[Tensor], + attention_mask: Tensor, + output_attentions: bool, + past_key_value: Tensor, + use_cache: bool, + attn_mask_startend_row_indices=None, + batch_size: int = None, + alibi=None, + ): + def create_custom_forward(module): + def custom_forward(*inputs): + return module(*inputs) + + return custom_forward + + recompute_fn = rr_recompute if any(layer_module.skip_recompute_ops.values()) else recompute + hidden_states = recompute_fn( + create_custom_forward(layer_module), + hidden_states, + position_ids, + attention_mask, + output_attentions, + past_key_value, + use_cache, + attn_mask_startend_row_indices, + batch_size, + use_reentrant=self.config.recompute_use_reentrant, + ) + + return hidden_states + def forward( + self, + input_ids: paddle.Tensor = None, + position_ids: Optional[paddle.Tensor] = None, + attention_mask: Optional[paddle.Tensor] = None, + inputs_embeds: Optional[paddle.Tensor] = None, + use_cache: Optional[bool] = None, + past_key_values: Optional[List[paddle.Tensor]] = None, + output_attentions: Optional[bool] = None, + output_hidden_states: Optional[bool] = None, + return_dict: Optional[bool] = None, + attn_mask_startend_row_indices=None, + ) -> Union[Tuple, BaseModelOutputWithPast]: + if self.sequence_parallel and use_cache: + raise ValueError("We currently only support sequence parallel without cache.") + + output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions + output_hidden_states = ( + output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states + ) + use_cache = use_cache if use_cache is not None else self.config.use_cache + + return_dict = return_dict if return_dict is not None else self.config.use_return_dict + + # retrieve input_ids and inputs_embeds + if input_ids is not None and inputs_embeds is not None: + raise ValueError("You cannot specify both decoder_input_ids and decoder_inputs_embeds at the same time") + elif input_ids is not None: + batch_size, seq_length = input_ids.shape + elif inputs_embeds is not None: + batch_size, seq_length, _ = inputs_embeds.shape + else: + raise ValueError("You have to specify either decoder_input_ids or decoder_inputs_embeds") + + if past_key_values is None: + past_key_values = tuple([None] * len(self.layers)) + # NOTE: to make cache can be clear in-time + past_key_values = list(past_key_values) + + seq_length_with_past = seq_length + cache_length = 0 + if past_key_values[0] is not None: + cache_length = past_key_values[0][0].shape[1] + seq_length_with_past += cache_length + if inputs_embeds is None: + inputs_embeds = self.embed_tokens(input_ids) + + if self.sequence_parallel: + # [bs, seq_len, num_head * head_dim] -> [bs * seq_len, num_head * head_dim] + bs, seq_len, hidden_size = inputs_embeds.shape + inputs_embeds = paddle.reshape_(inputs_embeds, [bs * seq_len, hidden_size]) + # [seq_len * bs / n, num_head * head_dim] (n is mp parallelism) + inputs_embeds = ScatterOp.apply(inputs_embeds) + + if self.config.context_parallel_degree > 1 and (attention_mask is not None or self.config.alibi): + raise NotImplementedError("Ring FlashAttention doesn't support attention_mask or alibi") + + # embed positions + if self.config.use_flash_attention_for_generation: + attention_mask = None + elif attn_mask_startend_row_indices is None and attention_mask is None: + # [bs, seq_len] + attention_mask = paddle.ones((batch_size, seq_length_with_past), dtype=paddle.bool) + if attn_mask_startend_row_indices is None and self.config.alibi: + if self.config.use_long_sequence_strategies: + alibi_layer = LongSequenceStrategies.build_long_sequence_strategy( + self.config.long_sequence_strategy_type, + self.config.long_sequence_strategy_name, + **self.config.long_sequence_init_args, + ) + alibi = alibi_layer(attention_mask, self.config.num_attention_heads, dtype=inputs_embeds.dtype) + else: + alibi = build_alibi_tensor(attention_mask, self.config.num_attention_heads, dtype=inputs_embeds.dtype) + if self.config.tensor_parallel_degree > 1: + block_size = self.config.num_attention_heads // self.config.tensor_parallel_degree + alibi = alibi[ + :, + self.config.tensor_parallel_rank + * block_size : (self.config.tensor_parallel_rank + 1) + * block_size, + ] + alibi = alibi.reshape([batch_size * block_size, 1, seq_length_with_past]) + else: + alibi = alibi.reshape([batch_size * self.config.num_attention_heads, 1, seq_length_with_past]) + else: + alibi = None + + if position_ids is None: + position_ids = paddle.arange(seq_length, dtype="int64").expand((batch_size, seq_length)) + + use_casual_mask = get_use_casual_mask() and not self.config.alibi + + if self.config.use_flash_attention_for_generation or use_casual_mask: + attention_mask = None + elif attn_mask_startend_row_indices is None: + attention_mask = self._prepare_decoder_attention_mask( + attention_mask, (batch_size, seq_length), cache_length, inputs_embeds.dtype + ) # [bs, 1, seq_len, seq_len] + + is_casual = False + + if ( + attn_mask_startend_row_indices is None + and self.config.use_flash_attention + and get_env_device() not in ["gcu", "intel_hpu"] + ): + if self.config.use_flash_attention_for_generation or use_casual_mask: + is_casual = True + else: + is_casual = is_casual_mask(attention_mask) + if get_env_device() not in ["npu", "mlu"]: + if is_casual and alibi is None: + attention_mask = None + else: + attention_mask = None if attention_mask is None else attention_mask.astype("bool") + hidden_states = inputs_embeds + # decoder layers + all_hidden_states = () if output_hidden_states else None + all_self_attns = () if output_attentions else None + next_decoder_cache = () if use_cache else None + + for idx, (decoder_layer) in enumerate(self.layers): + if output_hidden_states: + all_hidden_states += (hidden_states,) + past_key_value = past_key_values[idx] if past_key_values is not None else None + + has_gradient = not hidden_states.stop_gradient + if ( + self.enable_recompute + and idx not in self.no_recompute_layers + and has_gradient + and self.recompute_granularity == "full" + ): + layer_outputs = self.recompute_training_full( + decoder_layer, + hidden_states, + position_ids, + attention_mask, + output_attentions, + past_key_value, + use_cache, + alibi=alibi, + attn_mask_startend_row_indices=attn_mask_startend_row_indices, + ) + else: + layer_outputs = decoder_layer( + hidden_states, + position_ids, + attention_mask, + output_attentions, + past_key_value, + use_cache, + alibi=alibi, + attn_mask_startend_row_indices=attn_mask_startend_row_indices, + npu_is_casual=is_casual, + ) + + # NOTE: clear outdate cache after it has been used for memory saving + past_key_value = past_key_values[idx] = None + if type(layer_outputs) is tuple: + hidden_states = layer_outputs[0] + else: + hidden_states = layer_outputs + + if output_attentions: + all_self_attns += (layer_outputs[1],) + + if use_cache: + next_decoder_cache += (layer_outputs[2 if output_attentions else 1],) + + if self.config.use_last_token_for_generation: + hidden_states = paddle.unsqueeze(hidden_states[:, -1, :], 1) + + hidden_states = self.norm(hidden_states) + + # add hidden states from the last decoder layer + if output_hidden_states: + all_hidden_states += (hidden_states,) + + next_cache = next_decoder_cache if use_cache else None + + if not return_dict: + return tuple(v for v in [hidden_states, next_cache, all_hidden_states, all_self_attns] if v is not None) + return BaseModelOutputWithPastAndCrossAttentions( + last_hidden_state=hidden_states, + past_key_values=next_cache, + hidden_states=all_hidden_states, + attentions=all_self_attns, + cross_attentions=None, + ) + """ + Transformer decoder consisting of *config.num_hidden_layers* layers. Each layer is a [`LlamaDecoderLayer`] + Args: + config: LlamaConfig + """ + + def get_input_embeddings(self): + return self.embed_tokens + + def set_input_embeddings(self, value): + self.embed_tokens = value + + @staticmethod + def _prepare_decoder_attention_mask(attention_mask, input_shape, past_key_values_length, dtype): + if attention_mask is not None: + # [bsz, seq_len] -> [bsz, 1, tgt_seq_len, src_seq_len] + if len(attention_mask.shape) == 2: + expanded_attn_mask = _expand_2d_mask(attention_mask, dtype, tgt_length=input_shape[-1]) + # For decoding phase in generation, seq_length = 1, we don't need to add causal mask + if input_shape[-1] > 1: + combined_attention_mask = _make_causal_mask( + input_shape, past_key_values_length=past_key_values_length + ) + if get_env_device() in ["npu", "mlu", "intel_hpu"]: + expanded_attn_mask = expanded_attn_mask.astype("bool") & combined_attention_mask.astype("bool") + else: + expanded_attn_mask = expanded_attn_mask & combined_attention_mask + # [bsz, seq_len, seq_len] -> [bsz, 1, seq_len, seq_len] + elif len(attention_mask.shape) == 3: + expanded_attn_mask = attention_mask.unsqueeze(1).astype("bool") + # if attention_mask is already 4-D, do nothing + else: + expanded_attn_mask = attention_mask + else: + expanded_attn_mask = _make_causal_mask(input_shape, past_key_values_length=past_key_values_length) + # Convert bool attention_mask to float attention mask, which will be added to attention_scores later + if get_env_device() in ["npu", "mlu", "intel_hpu"]: + x = paddle.to_tensor(0.0, dtype="float32") + y = paddle.to_tensor(paddle.finfo(dtype).min, dtype="float32") + expanded_attn_mask = paddle.where(expanded_attn_mask.cast("bool"), x, y).astype(dtype) + elif get_env_device() == "xpu": + x = paddle.to_tensor(0.0, dtype="float32") + y = paddle.to_tensor(-1.7005809656952787e38, dtype="float32") + expanded_attn_mask = paddle.where(expanded_attn_mask.cast("bool"), x, y) + elif get_env_device() == "gcu": + min_val = paddle.finfo(dtype).min + x = paddle.to_tensor(0.0, dtype=dtype) + y = paddle.to_tensor(min_val, dtype=dtype) + expanded_attn_mask = paddle.where(expanded_attn_mask.cast("bool"), x, y).astype(dtype) + else: + expanded_attn_mask = paddle.where(expanded_attn_mask.cast("bool"), 0.0, paddle.finfo(dtype).min) + expanded_attn_mask = expanded_attn_mask.astype(dtype) + return expanded_attn_mask +class Qwen2PretrainingCriterion(paddle.nn.Layer): + """Qwen2的预训练损失计算,继承自LlamaPretrainingCriterion""" + def __init__(self, config: Qwen2Config): + + super(Qwen2PretrainingCriterion, self).__init__() + self.ignore_index = getattr(config, "ignore_index", -100) + self.config = config + self.enable_parallel_cross_entropy = ( + config.tensor_parallel_degree > 1 + and config.vocab_size % config.tensor_parallel_degree == 0 + and config.tensor_parallel_output + ) + + if self.enable_parallel_cross_entropy: # and False: # and lm_head is distributed + self.loss_func = mpu.ParallelCrossEntropy(ignore_index=self.ignore_index) + else: + self.loss_func = paddle.nn.CrossEntropyLoss(reduction="none", ignore_index=self.ignore_index) + self.enable_parallel_cross_entropy = config.tensor_parallel_degree > 1 and config.tensor_parallel_output + """ + Criterion for Llama. + It calculates the final loss. + """ + + def forward(self, prediction_scores, masked_lm_labels): + if self.enable_parallel_cross_entropy: + if prediction_scores.shape[-1] == self.config.vocab_size: + warnings.warn( + f"enable_parallel_cross_entropy, the vocab_size should be splited: {prediction_scores.shape[-1]}, {self.config.vocab_size}" + ) + self.loss_func = paddle.nn.CrossEntropyLoss(reduction="none", ignore_index=self.ignore_index) + + with paddle.amp.auto_cast(False): + masked_lm_loss = self.loss_func(prediction_scores.astype("float32"), masked_lm_labels.unsqueeze(2)) + + if self.config.sep_parallel_degree > 1 or self.config.context_parallel_degree > 1: + _hcg = fleet.get_hybrid_communicate_group() + masked_lm_loss = ConcatMaskedLoss.apply(masked_lm_loss, axis=1, group=_hcg.get_sep_parallel_group()) + # skip ignore_index which loss == 0 + # masked_lm_loss = masked_lm_loss[masked_lm_loss > 0] + # loss = paddle.mean(masked_lm_loss) + binary_sequence = paddle.where( + masked_lm_loss > 0, paddle.ones_like(masked_lm_loss), paddle.zeros_like(masked_lm_loss) + ) + count = paddle.sum(binary_sequence) + if count == 0: + loss = paddle.sum(masked_lm_loss * binary_sequence) + else: + loss = paddle.sum(masked_lm_loss * binary_sequence) / count + + return loss + +class Qwen2LMHead(nn.Layer): + """Qwen2的语言模型头,继承自LlamaLMHead""" + def __init__(self, config: Qwen2Config, embedding_weights=None, transpose_y=False): + super(Qwen2LMHead, self).__init__() + self.config = config + if config.tensor_parallel_degree > 1 and config.vocab_size % config.tensor_parallel_degree == 0: + vocab_size = config.vocab_size // config.tensor_parallel_degree + else: + vocab_size = config.vocab_size + + self.transpose_y = transpose_y + if transpose_y: + if embedding_weights is not None: + self.weight = embedding_weights + else: + self.weight = self.create_parameter( + shape=[vocab_size, config.hidden_size], + dtype=paddle.get_default_dtype(), + ) + else: + if vocab_size != config.vocab_size: + with get_rng_state_tracker().rng_state(): + self.weight = self.create_parameter( + shape=[config.hidden_size, vocab_size], + dtype=paddle.get_default_dtype(), + ) + else: + self.weight = self.create_parameter( + shape=[config.hidden_size, vocab_size], + dtype=paddle.get_default_dtype(), + ) + # Must set distributed attr for Tensor Parallel ! + self.weight.is_distributed = True if (vocab_size != config.vocab_size) else False + if self.weight.is_distributed: + # for tie_word_embeddings + self.weight.split_axis = 0 if self.transpose_y else 1 + if get_env_device() == "xpu": + try: + from paddle_xpu.layers.nn import ( # noqa: F401 + parallel_matmul as xpu_parallel_matmul, + ) + + self.xpu_parallel_matmul = xpu_parallel_matmul() + except ImportError: + self.xpu_parallel_matmul = None + def forward(self, hidden_states, tensor_parallel_output=None, batch_size=None): + if self.config.sequence_parallel: + hidden_states = GatherOp.apply(hidden_states) + hidden_states = paddle.reshape_(hidden_states, [batch_size, -1, self.config.hidden_size]) + + if tensor_parallel_output is None: + tensor_parallel_output = self.config.tensor_parallel_output + + logits = parallel_matmul( + hidden_states, self.weight, transpose_y=self.transpose_y, tensor_parallel_output=tensor_parallel_output + ) + return logits +class Qwen2ForCausalLM(Qwen2PretrainedModel): + """用于因果语言建模的Qwen2模型,继承自LlamaForCausalLM""" + def __init__(self, config: Qwen2Config): + super().__init__(config) + self.config = config + + self.qwen2 = Qwen2Model(config) + if config.tie_word_embeddings: + self.lm_head = Qwen2LMHead(config, embedding_weights=self.qwen2.embed_tokens.weight, transpose_y=True) + self.tie_weights() + else: + self.lm_head = Qwen2LMHead(config) + self.criterion = Qwen2PretrainingCriterion(config) + @staticmethod + def update_model_kwargs_for_generation(outputs, model_kwargs, is_encoder_decoder=False): + # update cache + if isinstance(outputs, tuple) and len(outputs) > 1 and not isinstance(outputs[1], paddle.Tensor): + model_kwargs["past_key_values"] = outputs[1] + + if isinstance(outputs, CausalLMOutputWithPast) and "past_key_values" in outputs: + model_kwargs["past_key_values"] = outputs.past_key_values + + # update position_ids + if "position_ids" in model_kwargs and model_kwargs["position_ids"] is not None: + position_ids = model_kwargs["position_ids"] + model_kwargs["position_ids"] = paddle.concat([position_ids, position_ids[..., -1:] + 1], axis=-1) + + if not is_encoder_decoder and "attention_mask" in model_kwargs: + # TODO: support attention mask for other models + attention_mask = model_kwargs["attention_mask"] + if len(attention_mask.shape) == 2: + model_kwargs["attention_mask"] = paddle.concat( + [attention_mask, paddle.ones([attention_mask.shape[0], 1], dtype=attention_mask.dtype)], + axis=-1, + ) + elif len(attention_mask.shape) == 4: + model_kwargs["attention_mask"] = paddle.concat( + [attention_mask, paddle.ones([*attention_mask.shape[:3], 1], dtype=attention_mask.dtype)], + axis=-1, + )[:, :, -1:, :] + + return model_kwargs + + def forward( + self, + input_ids: paddle.Tensor = None, + position_ids: Optional[paddle.Tensor] = None, + attention_mask: Optional[paddle.Tensor] = None, + inputs_embeds: Optional[paddle.Tensor] = None, + labels: Optional[paddle.Tensor] = None, + use_cache: Optional[bool] = None, + past_key_values: Optional[List[paddle.Tensor]] = None, + output_attentions: Optional[bool] = None, + output_hidden_states: Optional[bool] = None, + return_dict: Optional[bool] = None, + attn_mask_startend_row_indices=None, + ) -> Union[Tuple, CausalLMOutputWithPast]: + r""" + Args: + labels (`paddle.Tensor` of shape `(batch_size, sequence_length)`, *optional*): + Labels for computing the masked language modeling loss. Indices should either be in `[0, ..., + config.vocab_size]` or -100 (see `input_ids` docstring). Tokens with indices set to `-100` are ignored + (masked), the loss is only computed for the tokens with labels in `[0, ..., config.vocab_size]`. + + Returns: + + Example: + + ```python + >>> from transformers import AutoTokenizer, Qwen2ForCausalLM + + >>> model = Qwen2ForCausalLM.from_pretrained(PATH_TO_CONVERTED_WEIGHTS) + >>> tokenizer = AutoTokenizer.from_pretrained(PATH_TO_CONVERTED_TOKENIZER) + + >>> prompt = "Hey, are you conscious? Can you talk to me?" + >>> inputs = tokenizer(prompt, return_tensors="pt") + + >>> # Generate + >>> generate_ids = model.generate(inputs.input_ids, max_length=30) + >>> tokenizer.batch_decode(generate_ids, skip_special_tokens=True, clean_up_tokenization_spaces=False)[0] + "Hey, are you conscious? Can you talk to me?\nI'm not conscious, but I can talk to you." + ```""" + + output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions + output_hidden_states = ( + output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states + ) + return_dict = return_dict if return_dict is not None else self.config.use_return_dict + + if attn_mask_startend_row_indices is not None and attention_mask is not None: + logger.warning( + "You have provided both attn_mask_startend_row_indices and attention_mask. " + "The attn_mask_startend_row_indices will be used." + ) + attention_mask = None + + if input_ids is not None and inputs_embeds is not None: + raise ValueError("You cannot specify both decoder_input_ids and decoder_inputs_embeds at the same time") + elif input_ids is not None: + batch_size = input_ids.shape[0] + elif inputs_embeds is not None: + batch_size = inputs_embeds.shape[0] + else: + raise ValueError("You have to specify either decoder_input_ids or decoder_inputs_embeds") + + # decoder outputs consists of (dec_features, layer_state, dec_hidden, dec_attn) + outputs = self.qwen2( + input_ids=input_ids, + position_ids=position_ids, + attention_mask=attention_mask, + inputs_embeds=inputs_embeds, + use_cache=use_cache, + past_key_values=past_key_values, + output_attentions=output_attentions, + output_hidden_states=output_hidden_states, + return_dict=return_dict, + attn_mask_startend_row_indices=attn_mask_startend_row_indices, + ) + + hidden_states = outputs[0] + + # add this for fused_head_and_loss_fn + if self.config.use_fused_head_and_loss_fn and self.training: + if self.config.tensor_parallel_degree > 1 and self.config.sequence_parallel: + hidden_states = GatherOp.apply(hidden_states) + hidden_states = hidden_states.reshape( + [ + batch_size, + -1, + hidden_states.shape[-1], + ] + ) + return hidden_states, self.lm_head.weight, None, self.lm_head.transpose_y + + # if labels is None,means we need full output, instead of tensor_parallel_output + # tensor_parallel_output is together with ParallelCrossEntropy + tensor_parallel_output = self.config.tensor_parallel_output and self.config.tensor_parallel_degree > 1 + + if labels is not None and self.config.use_fused_linear_cross_entropy: + from paddlenlp_kernel.triton.cut_cross_entropy import linear_cross_entropy + + assert ( + self.config.tensor_parallel_degree <= 1 + ), "The argument `use_fused_linear_cross_entropy` is imcompatiable with tensor parallel " + + masked_lm_loss = linear_cross_entropy(hidden_states, self.lm_head.weight, targets=labels) + + binary_sequence = paddle.where( + masked_lm_loss > 0, paddle.ones_like(masked_lm_loss), paddle.zeros_like(masked_lm_loss) + ) + count = paddle.sum(binary_sequence) + if count == 0: + loss = paddle.sum(masked_lm_loss * binary_sequence) + else: + loss = paddle.sum(masked_lm_loss * binary_sequence) / count + logits = None + else: + logits = self.lm_head(hidden_states, tensor_parallel_output=tensor_parallel_output, batch_size=batch_size) + + loss = None + if labels is not None: + loss = self.criterion(logits, labels) + + if not return_dict: + output = (logits,) + outputs[1:] + return (loss,) + output if loss is not None else output + + return CausalLMOutputWithPast( + loss=loss, + logits=logits, + past_key_values=outputs.past_key_values, + hidden_states=outputs.hidden_states, + attentions=outputs.attentions, + ) + enable_to_static_method = True + _tied_weights_keys = ["lm_head.weight"] + + def get_input_embeddings(self): + return self.qwen2.embed_tokens + + def set_input_embeddings(self, value): + self.qwen2.embed_tokens = value + + def get_output_embeddings(self): + return self.lm_head + + def set_output_embeddings(self, new_embeddings): + self.lm_head = new_embeddings + + def set_decoder(self, decoder): + self.qwen2 = decoder + + def get_decoder(self): + return self.qwen2 + + def prepare_inputs_for_generation( + self, input_ids, use_cache=False, past_key_values=None, inputs_embeds=None, **kwargs + ): + batch_size, seq_length = input_ids.shape + position_ids = kwargs.get("position_ids", paddle.arange(seq_length).expand((batch_size, seq_length))) + attention_mask = kwargs.get("attention_mask", None) + if past_key_values: + input_ids = input_ids[:, -1].unsqueeze(axis=-1) + position_ids = position_ids[:, -1].unsqueeze(-1) + + # if `inputs_embeds` are passed, we only want to use them in the 1st generation step + if inputs_embeds is not None and past_key_values is None: + model_inputs = {"inputs_embeds": inputs_embeds} + else: + model_inputs = {"input_ids": input_ids} + + model_inputs.update( + { + "position_ids": position_ids, + "past_key_values": past_key_values, + "use_cache": use_cache, + "attention_mask": attention_mask, + } + ) + return model_inputs + + def _get_model_inputs_spec(self, dtype: str): + return { + "input_ids": paddle.static.InputSpec(shape=[None, None], dtype="int64"), + "attention_mask": paddle.static.InputSpec(shape=[None, None], dtype="int64"), + "position_ids": paddle.static.InputSpec(shape=[None, None], dtype="int64"), + } + + +class Qwen2ForSequenceClassification(Qwen2PretrainedModel): + def __init__(self, config: Qwen2Config): + super().__init__(config) + self.num_labels = config.num_labels + self.qwen2 = Qwen2Model(config) + self.score = Linear(config.hidden_size, self.num_labels, bias_attr=False) + + def get_input_embeddings(self): + return self.qwen2.embed_tokens + + def set_input_embeddings(self, value): + self.qwen2.embed_tokens = value + + def forward( + self, + input_ids: paddle.Tensor = None, + position_ids: Optional[paddle.Tensor] = None, + attention_mask: Optional[paddle.Tensor] = None, + inputs_embeds: Optional[paddle.Tensor] = None, + past_key_values: Optional[List[paddle.Tensor]] = None, + labels: Optional[paddle.Tensor] = None, + use_cache: Optional[bool] = None, + output_attentions: Optional[bool] = None, + output_hidden_states: Optional[bool] = None, + return_dict: Optional[bool] = None, + ) -> Union[Tuple, SequenceClassifierOutputWithPast]: + r""" + labels (`paddle.Tensor` of shape `(batch_size,)`, *optional*): + Labels for computing the sequence classification/regression loss. Indices should be in `[0, ..., + config.num_labels - 1]`. If `config.num_labels == 1` a regression loss is computed (Mean-Square loss), If + `config.num_labels > 1` a classification loss is computed (Cross-Entropy). + """ + return_dict = return_dict if return_dict is not None else self.config.use_return_dict + + transformer_outputs = self.qwen2( + input_ids, + attention_mask=attention_mask, + position_ids=position_ids, + past_key_values=past_key_values, + inputs_embeds=inputs_embeds, + use_cache=use_cache, + output_attentions=output_attentions, + output_hidden_states=output_hidden_states, + return_dict=return_dict, + ) + hidden_states = transformer_outputs[0] + logits = self.score(hidden_states) + + if input_ids is not None: + batch_size = input_ids.shape[0] + else: + batch_size = inputs_embeds.shape[0] + + if self.config.pad_token_id is None and batch_size != 1: + raise ValueError("Cannot handle batch sizes > 1 if no padding token is defined.") + if self.config.pad_token_id is None: + sequence_lengths = -1 + else: + if input_ids is not None: + # if no pad token found, use modulo instead of reverse indexing for ONNX compatibility + sequence_lengths = paddle.equal(input_ids, self.config.pad_token_id).astype("int32").argmax(-1) - 1 + sequence_lengths = sequence_lengths % input_ids.shape[-1] + sequence_lengths = sequence_lengths + else: + sequence_lengths = -1 + + # pooled_logits = logits[paddle.arange(batch_size), sequence_lengths] + pooled_logits = logits.gather_nd(paddle.stack([paddle.arange(logits.shape[0]), sequence_lengths], axis=-1)) + + loss = None + if labels is not None: + if self.config.problem_type is None: + if self.num_labels == 1: + self.config.problem_type = "regression" + elif self.num_labels > 1 and (labels.dtype == paddle.int64 or labels.dtype == paddle.int32): + self.config.problem_type = "single_label_classification" + else: + self.config.problem_type = "multi_label_classification" + + if self.config.problem_type == "regression": + loss_fct = nn.MSELoss() + if self.num_labels == 1: + loss = loss_fct(pooled_logits.squeeze(), labels.squeeze()) + else: + loss = loss_fct(pooled_logits, labels) + elif self.config.problem_type == "single_label_classification": + loss_fct = nn.CrossEntropyLoss() + loss = loss_fct(pooled_logits.reshape([-1, self.num_labels]), labels.reshape([-1])) + elif self.config.problem_type == "multi_label_classification": + loss_fct = nn.BCEWithLogitsLoss() + loss = loss_fct(pooled_logits, labels) + if not return_dict: + output = (pooled_logits,) + transformer_outputs[1:] + return ((loss,) + output) if loss is not None else output + + return SequenceClassifierOutputWithPast( + loss=loss, + logits=pooled_logits, + past_key_values=transformer_outputs.past_key_values, + hidden_states=transformer_outputs.hidden_states, + attentions=transformer_outputs.attentions, + ) + + +# Copied from transformers.models.llama.modeling_llama.LlamaForTokenClassification with Llama->Qwen2, LLAMA->QWEN2 +class Qwen2ForTokenClassification(Qwen2PretrainedModel): + def __init__(self, config: Qwen2Config): + super().__init__(config) + self.num_labels = config.num_labels + self.qwen2 = Qwen2Model(config) + if getattr(config, "classifier_dropout", None) is not None: + classifier_dropout = config.classifier_dropout + elif getattr(config, "hidden_dropout", None) is not None: + classifier_dropout = config.hidden_dropout + else: + classifier_dropout = 0.1 + self.dropout = nn.Dropout(classifier_dropout) + self.score = Linear(config.hidden_size, config.num_labels) + + def get_input_embeddings(self): + return self.qwen2.embed_tokens + + def set_input_embeddings(self, value): + self.qwen2.embed_tokens = value + + def forward( + self, + input_ids: paddle.Tensor = None, + attention_mask: Optional[paddle.Tensor] = None, + position_ids: Optional[paddle.Tensor] = None, + past_key_values: Optional[List[paddle.Tensor]] = None, + inputs_embeds: Optional[paddle.Tensor] = None, + labels: Optional[paddle.Tensor] = None, + use_cache: Optional[bool] = None, + output_attentions: Optional[bool] = None, + output_hidden_states: Optional[bool] = None, + return_dict: Optional[bool] = None, + ) -> Union[Tuple, SequenceClassifierOutputWithPast]: + r""" + labels (`paddle.Tensor` of shape `(batch_size,)`, *optional*): + Labels for computing the sequence classification/regression loss. Indices should be in `[0, ..., + config.num_labels - 1]`. If `config.num_labels == 1` a regression loss is computed (Mean-Square loss), If + `config.num_labels > 1` a classification loss is computed (Cross-Entropy). + """ + return_dict = return_dict if return_dict is not None else self.config.use_return_dict + + outputs = self.qwen2( + input_ids, + attention_mask=attention_mask, + position_ids=position_ids, + past_key_values=past_key_values, + inputs_embeds=inputs_embeds, + use_cache=use_cache, + output_attentions=output_attentions, + output_hidden_states=output_hidden_states, + return_dict=return_dict, + ) + sequence_output = outputs[0] + sequence_output = self.dropout(sequence_output) + logits = self.score(sequence_output) + + loss = None + if labels is not None: + loss_fct = nn.CrossEntropyLoss() + loss = loss_fct(logits.reshape([-1, self.num_labels]), labels.reshape([-1])) + + if not return_dict: + output = (logits,) + outputs[2:] + return ((loss,) + output) if loss is not None else output + + return TokenClassifierOutput( + loss=loss, + logits=logits, + hidden_states=outputs.hidden_states, + attentions=outputs.attentions, + ) + + +class Qwen2SentenceEmbedding(Qwen2PretrainedModel): + def __init__( + self, + config: Qwen2Config, + embedding_temperature: float = 0.02, + ): + """Qwen2SentenceEmbedding + For getting larger batch_size, we use tensor parallel to get larger batch_size. + + Args: + config (Qwen2Config): _description_ + model (Qwen2Model): _description_ + embedding_temperature (float, optional): _description_. Defaults to 0.02. + """ + super(Qwen2SentenceEmbedding, self).__init__(config) + self.config = config + self.qwen2 = Qwen2Model(config) + self.in_batch_negative_loss = SimpleContrastiveLoss(embedding_temperature) + self.world_size = dist.get_world_size() + self.process_rank = dist.get_rank() + self.embedding_negatives_cross_device = config.embedding_negatives_cross_device + if self.world_size <= 1: + self.embedding_negatives_cross_device = False + + def forward( + self, + query: Optional[Dict[str, paddle.Tensor]] = None, + passages: Optional[Dict[str, paddle.Tensor]] = None, + return_encode=False, + ): + """forward""" + q_reps = self.encode(**query) + p_reps = self.encode(**passages) + + q_reps = nn.functional.normalize(q_reps, axis=-1) + p_reps = nn.functional.normalize(p_reps, axis=-1) + + if return_encode: + return q_reps, p_reps + + if self.embedding_negatives_cross_device: + q_reps = dist_gather_tensor_with_gradient(q_reps) + p_reps = dist_gather_tensor_with_gradient(p_reps) + + loss = self.in_batch_negative_loss(q_reps, p_reps) + return loss + + def encode( + self, + input_ids, + position_ids=None, + embedding_indices=None, + attention_mask=None, + output_attentions=False, + output_hidden_states=False, + return_dict=False, + **kwargs, + ): + """encode""" + input_type = type(input_ids) + outputs = self.qwen2( + input_ids, + position_ids=position_ids, + attention_mask=attention_mask, + output_attentions=output_attentions, + output_hidden_states=output_hidden_states, + return_dict=return_dict, + **kwargs, + ) + if isinstance(outputs, input_type): + hidden_states = outputs + else: + hidden_states = outputs[0] + last_hidden_states = hidden_states.gather_nd(embedding_indices) + return last_hidden_states + diff --git a/paddlenlp/transformers/qwen2/modular_qwen2.py b/paddlenlp/transformers/qwen2/modular_qwen2.py new file mode 100644 index 000000000000..f0e2ddcf52ae --- /dev/null +++ b/paddlenlp/transformers/qwen2/modular_qwen2.py @@ -0,0 +1,1313 @@ +# Copyright (c) 2024 PaddlePaddle Authors. All Rights Reserved. +# Copyright 2024 The Qwen team, Alibaba Group and the HuggingFace Inc. team. All rights reserved. +# +# This code ijins based on EleutherAI's GPT-NeoX library and the GPT-NeoX +# and OPT implementations in this library. It has been modified from its +# original forms to accommodate minor architectural differences compared +# to GPT-NeoX and OPT used by the Meta AI team that trained the model. +# +# 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. +"""Paddle Qwen2 model.""" +from __future__ import annotations + +import math +import warnings +from functools import partial +from typing import Dict, List, Optional, Tuple, Union + +import paddle +import paddle.distributed as dist +import paddle.distributed.fleet.meta_parallel as mpu +import paddle.nn.functional as F +from paddle import Tensor, nn +from paddle.distributed import fleet +from paddle.distributed.fleet.meta_parallel import get_rng_state_tracker +from paddle.distributed.fleet.recompute.recompute import recompute + +from ...utils.tools import get_env_device +from .. import linear_utils +from ..activations import ACT2FN +from ..contrastive_loss import SimpleContrastiveLoss +from ..conversion_utils import StateDictNameMapping, init_name_mappings +from ..embedding_utils import dist_gather_tensor_with_gradient +from ..linear_utils import Linear +from ..model_outputs import ( + BaseModelOutputWithPast, + CausalLMOutputWithPast, + SequenceClassifierOutputWithPast, + TokenClassifierOutput, +) +from ..model_utils import PretrainedModel, register_base_model +from ..refined_recompute import ( + RRColumnParallelLinear, + RRColumnSequenceParallelLinear, + RRRowParallelLinear, + RRRowSequenceParallelLinear, + get_skip_recompute_ops, +) +from ..refined_recompute import recompute as rr_recompute +from ..utils import caculate_llm_per_token_flops, logger +from .configuration import Qwen2Config +from ..llama.modeling import ( + LlamaPretrainedModel, + LlamaModel, + LlamaForCausalLM, + LlamaMLP, + LlamaDecoderLayer, + LlamaRMSNorm, + LlamaRotaryEmbedding, + LlamaPretrainingCriterion, + LlamaLMHead, + fusion_ops, +) +from ..llama.modeling import apply_rotary_pos_emb, repeat_kv + + +try: + from paddle.incubate.nn.functional import fused_rotary_position_embedding +except ImportError: + fused_rotary_position_embedding = None + +try: + from paddle.distributed.fleet.utils.sequence_parallel_utils import ( + GatherOp, + ScatterOp, + mark_as_sequence_parallel_parameter, + ) +except: + pass + +try: + from paddle.nn.functional.flash_attention import flash_attention +except: + flash_attention = None +__all__ = [ + "Qwen2Model", + "Qwen2PretrainedModel", + "Qwen2ForCausalLM", + "Qwen2PretrainingCriterion", + "Qwen2ForSequenceClassification", + "Qwen2ForTokenClassification", + "Qwen2SentenceEmbedding", +] + +def get_triangle_upper_mask(x, mask=None): + if mask is not None: + return mask + # [bsz, n_head, q_len, kv_seq_len] + shape = x.shape + # [bsz, 1, q_len, kv_seq_len] + shape[1] = 1 + mask = paddle.full(shape, paddle.finfo(x.dtype).min, dtype=x.dtype) + mask = paddle.triu(mask, diagonal=1) + mask.stop_gradient = True + return mask + +def scaled_dot_product_attention( + query_states, + config, + key_states, + value_states, + attention_mask, + output_attentions, + attn_mask_startend_row_indices=None, + training=True, + sequence_parallel=False, + skip_recompute=False, +): + bsz, q_len, num_heads, head_dim = query_states.shape + _, kv_seq_len, _, _ = value_states.shape + + if config.use_flash_attention and flash_attention: + # Paddle Flash Attention input [ bz, seqlen, nhead, head_dim] + # Torch Flash Attention input [ bz, nhead, seqlen, head_dim] + + return fusion_ops.fusion_flash_attention( + query_states, + config, + key_states, + value_states, + attention_mask, + output_attentions, + attn_mask_startend_row_indices=attn_mask_startend_row_indices, + sequence_parallel=sequence_parallel, + skip_recompute=skip_recompute, + ) + else: + # [ bz, seqlen, nhead, head_dim] -> [bs, nhead, seq_len, head_dim] + query_states = paddle.transpose(query_states, [0, 2, 1, 3]) + # merge with the next transpose + key_states = paddle.transpose(key_states, [0, 2, 1, 3]) + value_states = paddle.transpose(value_states, [0, 2, 1, 3]) + + # Add pre divided factor to fix nan under float16. + if paddle.in_dynamic_mode() and query_states.dtype == paddle.float16: + pre_divided_factor = 32 + else: + pre_divided_factor = 1 + + attn_weights = paddle.matmul( + query_states / (math.sqrt(head_dim) * pre_divided_factor), key_states.transpose([0, 1, 3, 2]) + ) + + if attn_weights.shape != [bsz, num_heads, q_len, kv_seq_len]: + raise ValueError( + f"Attention weights should be of shape {(bsz, num_heads, q_len, kv_seq_len)}, but is" + f" {attn_weights.shape}" + ) + + if attention_mask is None: + attention_mask = get_triangle_upper_mask(attn_weights) + + attention_mask = attention_mask.reshape([bsz, 1, q_len, kv_seq_len]) + if attention_mask.shape != [bsz, 1, q_len, kv_seq_len]: + raise ValueError( + f"Attention mask should be of shape {(bsz, 1, q_len, kv_seq_len)}, but is {attention_mask.shape}" + ) + + attn_weights = attn_weights + attention_mask + + if not paddle.in_dynamic_mode(): + attn_weights = F.softmax(attn_weights * pre_divided_factor, axis=-1, dtype="float32").astype( + query_states.dtype + ) + else: + with paddle.amp.auto_cast(False): + attn_weights = F.softmax( + attn_weights.astype("float32") * pre_divided_factor, axis=-1, dtype="float32" + ).astype(query_states.dtype) + + attn_weights = F.dropout(attn_weights, p=config.attention_dropout, training=training) + + attn_output = paddle.matmul(attn_weights, value_states) + attn_output = attn_output.transpose([0, 2, 1, 3]) + + if sequence_parallel: + attn_output = attn_output.reshape([bsz * q_len, head_dim * num_heads]) + else: + attn_output = attn_output.reshape([bsz, q_len, head_dim * num_heads]) + return (attn_output, attn_weights) if output_attentions else attn_output + + +class Qwen2RMSNorm(LlamaRMSNorm): + """Qwen2的RMSNorm,继承自LlamaRMSNorm""" + def __init__(self, config: Qwen2Config): + super().__init__(config) +class Qwen2RotaryEmbedding(LlamaRotaryEmbedding): + def __init__(self, dim, max_position_embeddings=2048, base=10000): + super().__init__(dim, max_position_embeddings, base) + def _set_cos_sin_cache(self, seq_len): + self.max_seq_len_cached = seq_len + if self.inv_freq.dtype != paddle.float32: + self.inv_freq = 1.0 / ( + self.base ** (paddle.cast(paddle.arange(0, self.dim, 2), dtype="float32") / self.dim) + ) + super()._set_cos_sin_cache(seq_len) + def forward(self, x, seq_len=None): + if seq_len > self.max_seq_len_cached: + self._set_cos_sin_cache(seq_len) + super().forward(x, seq_len) + +class Qwen2MLP(LlamaMLP): + """Qwen2的MLP,继承自LlamaMLP""" + def __init__(self, config: Qwen2Config,is_shared=False, skip_recompute_ops=None): + super().__init__(config) + if config.hidden_act == "silu": + self.act_fn = fusion_ops.swiglu + self.fuse_swiglu = True + else: + self.act_fn = ACT2FN[config.hidden_act] + self.fuse_swiglu = False + # Qwen2的MLP结构与Llama相同,但使用不同的配置 + def forward(self, x): + if self.fuse_attention_ffn: + x = self.gate_up_fused_proj(x) + if self.fuse_swiglu: + y = None + else: + x, y = x.chunk(2, axis=-1) + else: + x, y = self.gate_proj(x), self.up_proj(x) + + if self.fuse_swiglu: + x = self.act_fn(x, y) + else: + x = self.act_fn(x) * y + + return self.down_proj(x) + +class Qwen2Attention(nn.Layer): + """ + Multi-headed attention from 'Attention Is All You Need' paper. Modified to use sliding window attention: Longformer + and "Generating Long Sequences with Sparse Transformers". + """ + + def __init__(self, config: Qwen2Config, layerwise_recompute: bool = True, skip_recompute_ops=None): + super().__init__() + if skip_recompute_ops is None: + skip_recompute_ops = {} + self.config = config + self.skip_recompute_ops = skip_recompute_ops + self.hidden_size = config.hidden_size + self.num_heads = config.num_attention_heads + self.num_attention_heads = config.num_attention_heads + + self.head_dim = getattr(config, "head_dim", config.hidden_size // config.num_attention_heads) + + self.num_key_value_heads = config.num_key_value_heads + assert config.num_attention_heads // config.num_key_value_heads + self.num_key_value_groups = config.num_attention_heads // config.num_key_value_heads + self.gqa_or_mqa = config.num_attention_heads != config.num_key_value_heads + self.max_position_embeddings = config.max_position_embeddings + self.rope_theta = config.rope_theta + self.is_causal = True + self.attention_dropout = config.attention_dropout + + # self.seq_length = config.seq_length + self.sequence_parallel = config.sequence_parallel + self.has_bias = config.attention_bias + self.fuse_attention_qkv = config.fuse_attention_qkv + + # Note that we will actually perform a recompute only if both enable_recompute and layerwise_recompute are set to True + # Enable_recompute defaults to False and is controlled by Trainer + self.enable_recompute = False + self.layerwise_recompute = layerwise_recompute + self.recompute_granularity = config.recompute_granularity + if config.tensor_parallel_degree > 1: + assert ( + self.num_heads % config.tensor_parallel_degree == 0 + ), f"num_heads: {self.num_heads}, tensor_parallel_degree: {config.tensor_parallel_degree}" + self.num_heads = self.num_heads // config.tensor_parallel_degree + + assert ( + self.num_key_value_heads % config.tensor_parallel_degree == 0 + ), f"num_key_value_heads: {self.num_key_value_heads}, tensor_parallel_degree: {config.tensor_parallel_degree}" + self.num_key_value_heads = self.num_key_value_heads // config.tensor_parallel_degree + + self.use_fused_rope = config.use_fused_rope + if self.use_fused_rope: + if get_env_device() not in ["gpu", "xpu"] or fused_rotary_position_embedding is None: + warnings.warn( + "Enable fuse rope in the config, but fuse rope is not available. " + "Will disable fuse rope. Try using latest gpu version of Paddle." + ) + self.use_fused_rope = False + + if config.sequence_parallel: + ColumnParallelLinear = linear_utils.ColumnSequenceParallelLinear + RowParallelLinear = linear_utils.RowSequenceParallelLinear + + # NOTE: refined_recompute is only supported when `recompute_use_reentrant=False` + if config.recompute and not config.recompute_use_reentrant: + if skip_recompute_ops.get("attention_column_ln", False): + ColumnParallelLinear = RRColumnSequenceParallelLinear + if skip_recompute_ops.get("attention_row_ln", False): + RowParallelLinear = RRRowSequenceParallelLinear + else: + ColumnParallelLinear = linear_utils.ColumnParallelLinear + RowParallelLinear = linear_utils.RowParallelLinear + + # NOTE: refined_recompute is only supported when `recompute_use_reentrant=False` + if config.recompute and not config.recompute_use_reentrant: + if skip_recompute_ops.get("attention_column_ln", False): + ColumnParallelLinear = RRColumnParallelLinear + if skip_recompute_ops.get("attention_row_ln", False): + RowParallelLinear = RRRowParallelLinear + + if config.tensor_parallel_degree > 1: + if self.fuse_attention_qkv: + self.qkv_proj = ColumnParallelLinear( + self.hidden_size, + self.num_attention_heads * self.head_dim + 2 * self.config.num_key_value_heads * self.head_dim, + has_bias=self.has_bias, + gather_output=False, + ) + else: + self.q_proj = ColumnParallelLinear( + self.hidden_size, + self.num_attention_heads * self.head_dim, + has_bias=self.has_bias, + gather_output=False, + ) + self.k_proj = ColumnParallelLinear(self.hidden_size, self.config.num_key_value_heads * self.head_dim, has_bias=self.has_bias, gather_output=False) # fmt:skip + self.v_proj = ColumnParallelLinear(self.hidden_size, self.config.num_key_value_heads * self.head_dim, has_bias=self.has_bias, gather_output=False) # fmt:skip + self.o_proj = RowParallelLinear(self.hidden_size, self.hidden_size, has_bias=False, input_is_parallel=True) + else: + if self.fuse_attention_qkv: + self.qkv_proj = Linear( + self.hidden_size, + self.num_attention_heads * self.head_dim + 2 * self.config.num_key_value_heads * self.head_dim, + ) + else: + self.q_proj = Linear( + self.hidden_size, self.num_attention_heads * self.head_dim, bias_attr=self.has_bias + ) + self.k_proj = Linear( + self.hidden_size, self.config.num_key_value_heads * self.head_dim, bias_attr=self.has_bias + ) + self.v_proj = Linear( + self.hidden_size, self.config.num_key_value_heads * self.head_dim, bias_attr=self.has_bias + ) + self.o_proj = Linear(self.num_attention_heads * self.head_dim, self.hidden_size, bias_attr=False) + + self.rotary_emb = Qwen2RotaryEmbedding( + self.head_dim, + max_position_embeddings=self.max_position_embeddings, + base=self.rope_theta, + ) + + self.attn_func = scaled_dot_product_attention + + # NOTE: refined_recompute is only supported when `recompute_use_reentrant=False` + if config.recompute and not config.recompute_use_reentrant and skip_recompute_ops.get("flash_attn", False): + self.attn_func = partial(scaled_dot_product_attention, skip_recompute=True) + + def forward( + self, + hidden_states, + position_ids: Optional[Tuple[paddle.Tensor]] = None, + past_key_value: Optional[Tuple[paddle.Tensor]] = None, + attention_mask: Optional[paddle.Tensor] = None, + output_attentions: bool = False, + use_cache: bool = False, + attn_mask_startend_row_indices: Optional[paddle.Tensor] = None, + batch_size: Optional[int] = None, + **kwargs, + ) -> Tuple[paddle.Tensor, Optional[paddle.Tensor], Optional[Tuple[paddle.Tensor]]]: + """Input shape: Batch x Time x Channel""" + # [bs, seq_len, num_head * head_dim] -> [seq_len / n, bs, num_head * head_dim] (n is model parallelism) + + if self.fuse_attention_qkv: + mix_layer = self.qkv_proj(hidden_states) + if self.sequence_parallel: + target_shape = [ + batch_size, + -1, + self.num_key_value_heads, + (self.num_key_value_groups + 2) * self.head_dim, + ] + else: + target_shape = [0, 0, self.num_key_value_heads, (self.num_key_value_groups + 2) * self.head_dim] + mix_layer = paddle.reshape_(mix_layer, target_shape) + query_states, key_states, value_states = paddle.split( + mix_layer, + num_or_sections=[self.num_key_value_groups * self.head_dim, self.head_dim, self.head_dim], + axis=-1, + ) + if self.gqa_or_mqa: + query_states = paddle.reshape_(query_states, [0, 0, self.num_heads, self.head_dim]) + else: + query_states = self.q_proj(hidden_states) + key_states = self.k_proj(hidden_states) + value_states = self.v_proj(hidden_states) + + if self.sequence_parallel: + target_query_shape = [batch_size, -1, self.num_heads, self.head_dim] + target_key_value_shape = [batch_size, -1, self.num_key_value_heads, self.head_dim] + else: + target_query_shape = [0, 0, self.num_heads, self.head_dim] + target_key_value_shape = [0, 0, self.num_key_value_heads, self.head_dim] + query_states = query_states.reshape(shape=target_query_shape) + key_states = key_states.reshape(shape=target_key_value_shape) + value_states = value_states.reshape(shape=target_key_value_shape) + + if position_ids is not None and not self.use_fused_rope: + kv_seq_len = position_ids.max().item() + 1 + else: + kv_seq_len = key_states.shape[-3] + if past_key_value is not None: + kv_seq_len += past_key_value[0].shape[-3] + if self.use_fused_rope: + assert past_key_value is None, "fuse rotary not support cache kv for now" + cos, sin = self.rotary_emb(value_states, seq_len=kv_seq_len) + query_states, key_states, _ = fused_rotary_position_embedding( + query_states, + key_states, + v=None, + sin=sin, + cos=cos, + position_ids=position_ids, + use_neox_rotary_style=False, + ) + else: + cos, sin = self.rotary_emb(value_states, seq_len=kv_seq_len) + query_states, key_states = apply_rotary_pos_emb(query_states, key_states, cos, sin, position_ids) + + # [bs, seq_len, num_head, head_dim] + if past_key_value is not None: + key_states = paddle.concat([past_key_value[0], key_states], axis=1) + value_states = paddle.concat([past_key_value[1], value_states], axis=1) + past_key_value = (key_states, value_states) if use_cache else None + + # TODO(wj-Mcat): use broadcast strategy when n_kv_heads = 1 + # repeat k/v heads if n_kv_heads < n_heads + paddle_version = float(paddle.__version__[:3]) + if not self.config.use_flash_attention or ((paddle_version != 0.0) and (paddle_version <= 2.6)): + key_states = repeat_kv(key_states, self.num_key_value_groups) + value_states = repeat_kv(value_states, self.num_key_value_groups) + + has_gradient = not (query_states.stop_gradient and key_states.stop_gradient and value_states.stop_gradient) + if ( + self.enable_recompute + and self.layerwise_recompute + and has_gradient + and self.recompute_granularity == "core_attn" + ): + recompute_fn = rr_recompute if any(self.skip_recompute_ops.values()) else recompute + outputs = recompute_fn( + self.attn_func, + query_states, + self.config, + key_states, + value_states, + attention_mask, + output_attentions, + attn_mask_startend_row_indices=attn_mask_startend_row_indices, + training=self.training, + sequence_parallel=self.sequence_parallel, + use_reentrant=self.config.recompute_use_reentrant, + ) + else: + outputs = self.attn_func( + query_states, + self.config, + key_states, + value_states, + attention_mask, + output_attentions, + attn_mask_startend_row_indices=attn_mask_startend_row_indices, + training=self.training, + sequence_parallel=self.sequence_parallel, + ) + if output_attentions: + attn_output, attn_weights = outputs + else: + attn_output = outputs + + # if sequence_parallel is true, out shape are [q_len / n, bs, num_head * head_dim] + # else their shape are [bs, q_len, num_head * head_dim], n is mp parallelism. + attn_output = self.o_proj(attn_output) + + if not output_attentions: + attn_weights = None + + outputs = (attn_output,) + + if output_attentions: + outputs += (attn_weights,) + + if use_cache: + outputs += (past_key_value,) + + if type(outputs) is tuple and len(outputs) == 1: + outputs = outputs[0] + + return outputs + + +class Qwen2DecoderLayer(LlamaDecoderLayer): + """Qwen2的解码器层,继承自LlamaDecoderLayer""" + def __init__(self, config: Qwen2Config, layerwise_recompute: bool = False, skip_recompute_ops=None): + super().__init__(config) + def forward( + self, + hidden_states: paddle.Tensor, + position_ids: Optional[paddle.Tensor] = None, + attention_mask: Optional[paddle.Tensor] = None, + output_attentions: Optional[bool] = False, + past_key_value: Optional[Tuple[paddle.Tensor]] = None, + use_cache: Optional[bool] = False, + attn_mask_startend_row_indices: Optional[paddle.Tensor] = None, + batch_size: Optional[int] = None, + **kwargs, + ) -> Tuple[paddle.Tensor, Optional[Tuple[paddle.Tensor, paddle.Tensor]]]: + """ + Args: + hidden_states (`paddle.Tensor`): input to the layer of shape `(batch, seq_len, embed_dim)` + attention_mask (`paddle.Tensor`, *optional*): attention mask of size + `(batch, sequence_length)` where padding elements are indicated by 0. + output_attentions (`bool`, *optional*): + Whether or not to return the attentions tensors of all attention layers. See `attentions` under + returned tensors for more detail. + use_cache (`bool`, *optional*): + If set to `True`, `past_key_values` key value states are returned and can be used to speed up decoding + (see `past_key_values`). + past_key_value (`Tuple(paddle.Tensor)`, *optional*): cached past key and value projection states + """ + + # [bs * seq_len, embed_dim] -> [seq_len * bs / n, embed_dim] (sequence_parallel) + residual = hidden_states + + hidden_states = self.input_layernorm(hidden_states) + + # Self Attention + has_gradient = not hidden_states.stop_gradient + if ( + self.enable_recompute + and self.layerwise_recompute + and has_gradient + and self.recompute_granularity == "full_attn" + ): + recompute_fn = rr_recompute if any(self.skip_recompute_ops.values()) else recompute + outputs = recompute_fn( + self.self_attn, + hidden_states, + position_ids, + past_key_value, + attention_mask, + output_attentions, + use_cache, + attn_mask_startend_row_indices, + batch_size, + use_reentrant=self.config.recompute_use_reentrant, + ) + else: + outputs = self.self_attn( + hidden_states, + position_ids, + past_key_value, + attention_mask, + output_attentions, + use_cache, + attn_mask_startend_row_indices=attn_mask_startend_row_indices, + batch_size=batch_size, + ) + + if type(outputs) is tuple: + hidden_states = outputs[0] + else: + hidden_states = outputs + + if output_attentions: + self_attn_weights = outputs[1] + + if use_cache: + present_key_value = outputs[2 if output_attentions else 1] + + hidden_states = residual + hidden_states + + # Fully Connected + residual = hidden_states + hidden_states = self.post_attention_layernorm(hidden_states) + hidden_states = self.mlp(hidden_states) + + hidden_states = residual + hidden_states + + outputs = (hidden_states,) + + if output_attentions: + outputs += (self_attn_weights,) + + if use_cache: + outputs += (present_key_value,) + + if type(outputs) is tuple and len(outputs) == 1: + outputs = outputs[0] + + return outputs + + +class Qwen2PretrainedModel(LlamaPretrainedModel): + """Qwen2预训练模型基类,继承自LlamaPretrainedModel""" + config_class = Qwen2Config + base_model_prefix = "qwen2" + _keys_to_ignore_on_load_unexpected = [r"self_attn.rotary_emb.inv_freq"] + @classmethod + def _get_name_mappings(cls, config: Qwen2Config) -> list[StateDictNameMapping]: + mappings: list[StateDictNameMapping] = [] + model_mappings = [ + ["embed_tokens.weight"], + ["norm.weight"], + ] + for layer_index in range(config.num_hidden_layers): + layer_mappings = [ + [f"layers.{layer_index}.self_attn.q_proj.weight", None, "transpose"], + [f"layers.{layer_index}.self_attn.k_proj.weight", None, "transpose"], + [f"layers.{layer_index}.self_attn.v_proj.weight", None, "transpose"], + [f"layers.{layer_index}.self_attn.q_proj.bias", None], + [f"layers.{layer_index}.self_attn.k_proj.bias", None], + [f"layers.{layer_index}.self_attn.v_proj.bias", None], + [f"layers.{layer_index}.self_attn.o_proj.weight", None, "transpose"], + [f"layers.{layer_index}.mlp.up_proj.weight", None, "transpose"], + [f"layers.{layer_index}.mlp.gate_proj.weight", None, "transpose"], + [f"layers.{layer_index}.mlp.down_proj.weight", None, "transpose"], + [f"layers.{layer_index}.self_attn.rotary_emb.inv_freq"], + [f"layers.{layer_index}.input_layernorm.weight"], + [f"layers.{layer_index}.post_attention_layernorm.weight"], + ] + model_mappings.extend(layer_mappings) + + init_name_mappings(mappings=model_mappings) + # base-model prefix "Qwen2MoEModel" + if "Qwen2Model" not in config.architectures: + for mapping in model_mappings: + mapping[0] = "model." + mapping[0] + mapping[1] = "qwen2." + mapping[1] + if not config.tie_word_embeddings: + model_mappings.append(["lm_head.weight", "lm_head.weight", "transpose"]) + + mappings = [StateDictNameMapping(*mapping, index=index) for index, mapping in enumerate(model_mappings)] + return mappings + + @classmethod + def _get_tensor_parallel_mappings(cls, config: Qwen2Config, is_split=True): + + from ..conversion_utils import split_or_merge_func + + fn = split_or_merge_func( + is_split=is_split, + tensor_parallel_degree=config.tensor_parallel_degree, + tensor_parallel_rank=config.tensor_parallel_rank, + num_attention_heads=config.num_attention_heads, + ) + + def get_tensor_parallel_split_mappings(num_layers): + final_actions = {} + + base_actions = { + # Row Linear + "embed_tokens.weight": partial(fn, is_column=False), + "layers.0.self_attn.o_proj.weight": partial(fn, is_column=False), + "layers.0.mlp.down_proj.weight": partial(fn, is_column=False), + } + + if config.tie_word_embeddings: + base_actions["lm_head.weight"] = partial(fn, is_column=False) + else: + base_actions["lm_head.weight"] = partial(fn, is_column=True) + + if not config.vocab_size % config.tensor_parallel_degree == 0: + base_actions.pop("lm_head.weight") + base_actions.pop("embed_tokens.weight") + # Column Linear + if config.fuse_attention_qkv: + base_actions["layers.0.self_attn.qkv_proj.weight"] = partial(fn, is_column=True) + base_actions["layers.0.self_attn.qkv_proj.bias"] = partial(fn, is_column=True) + else: + base_actions["layers.0.self_attn.q_proj.weight"] = partial(fn, is_column=True) + base_actions["layers.0.self_attn.q_proj.bias"] = partial(fn, is_column=True) + # if we have enough num_key_value_heads to split, then split it. + if config.num_key_value_heads % config.tensor_parallel_degree == 0: + base_actions["layers.0.self_attn.k_proj.weight"] = partial(fn, is_column=True) + base_actions["layers.0.self_attn.v_proj.weight"] = partial(fn, is_column=True) + base_actions["layers.0.self_attn.k_proj.bias"] = partial(fn, is_column=True) + base_actions["layers.0.self_attn.v_proj.bias"] = partial(fn, is_column=True) + + if config.fuse_attention_ffn: + base_actions["layers.0.mlp.gate_up_fused_proj.weight"] = partial( + fn, is_column=True, is_naive_2fuse=True + ) + else: + base_actions["layers.0.mlp.gate_proj.weight"] = partial(fn, is_column=True) + base_actions["layers.0.mlp.up_proj.weight"] = partial(fn, is_column=True) + + for key, action in base_actions.items(): + if "layers.0." in key: + for i in range(num_layers): + final_actions[key.replace("layers.0.", f"layers.{i}.")] = action + final_actions[key] = action + + return final_actions + + mappings = get_tensor_parallel_split_mappings(config.num_hidden_layers) + + return mappings + @classmethod + def _get_fuse_or_split_param_mappings(cls, config: Qwen2Config, is_fuse=False): + # return parameter fuse utils + from ..conversion_utils import split_or_fuse_func + + fn = split_or_fuse_func(is_fuse=is_fuse) + + # last key is fused key, other keys are to be fused. + fuse_qkv_keys = [ + ( + "layers.0.self_attn.q_proj.weight", + "layers.0.self_attn.k_proj.weight", + "layers.0.self_attn.v_proj.weight", + "layers.0.self_attn.qkv_proj.weight", + ), + ( + "layers.0.self_attn.q_proj.bias", + "layers.0.self_attn.k_proj.bias", + "layers.0.self_attn.v_proj.bias", + "layers.0.self_attn.qkv_proj.bias", + ), + ] + + fuse_gate_up_keys = ( + "layers.0.mlp.gate_proj.weight", + "layers.0.mlp.up_proj.weight", + "layers.0.mlp.gate_up_fused_proj.weight", + ) + num_heads = config.num_attention_heads + num_key_value_heads = getattr(config, "num_key_value_heads", num_heads) + fuse_attention_qkv = getattr(config, "fuse_attention_qkv", False) + fuse_attention_ffn = getattr(config, "fuse_attention_ffn", False) + + final_actions = {} + if is_fuse: + if fuse_attention_qkv: + for i in range(config.num_hidden_layers): + for fuse_keys in fuse_qkv_keys: + keys = tuple([key.replace("layers.0.", f"layers.{i}.") for key in fuse_keys]) + final_actions[keys] = partial( + fn, is_qkv=True, num_heads=num_heads, num_key_value_heads=num_key_value_heads + ) + if fuse_attention_ffn: + for i in range(config.num_hidden_layers): + keys = tuple([key.replace("layers.0.", f"layers.{i}.") for key in fuse_gate_up_keys]) + final_actions[keys] = fn + else: + if not fuse_attention_qkv: + for i in range(config.num_hidden_layers): + for fuse_keys in fuse_qkv_keys: + keys = tuple([key.replace("layers.0.", f"layers.{i}.") for key in fuse_keys]) + final_actions[keys] = partial( + fn, split_nums=3, is_qkv=True, num_heads=num_heads, num_key_value_heads=num_key_value_heads + ) + if not fuse_attention_ffn: + for i in range(config.num_hidden_layers): + keys = tuple([key.replace("layers.0.", f"layers.{i}.") for key in fuse_gate_up_keys]) + final_actions[keys] = partial(fn, split_nums=2) + return final_actions + + def _get_model_flops(self): + if hasattr(self.config, "seq_length"): + seq_length = self.config.seq_length + else: + seq_length = 2048 + + return caculate_llm_per_token_flops( + hidden_size=self.config.hidden_size, + intermediate_size=self.config.intermediate_size, + layer_num=self.config.num_hidden_layers, + vocab_size=self.config.vocab_size, + seq_length=seq_length, + recompute=False, + ) + + def _get_hardware_flops(self): + if hasattr(self.config, "seq_length"): + seq_length = self.config.seq_length + else: + seq_length = 2048 + + return caculate_llm_per_token_flops( + hidden_size=self.config.hidden_size, + intermediate_size=self.config.intermediate_size, + layer_num=self.config.num_hidden_layers, + vocab_size=self.config.vocab_size, + seq_length=seq_length, + recompute=self.config.recompute, + recompute_granularity=self.config.recompute_granularity, + ) + + +@register_base_model +class Qwen2Model(LlamaModel): + """Qwen2模型,继承自LlamaModel""" + def __init__(self, config: Qwen2Config): + super().__init__(config) + self.padding_idx = config.pad_token_id + @paddle.jit.not_to_static + def recompute_training_full( + self, + layer_module: nn.Layer, + hidden_states: Tensor, + position_ids: Optional[Tensor], + attention_mask: Tensor, + output_attentions: bool, + past_key_value: Tensor, + use_cache: bool, + attn_mask_startend_row_indices=None, + batch_size: int = None, + ): + def create_custom_forward(module): + def custom_forward(*inputs): + return module(*inputs) + + return custom_forward + + recompute_fn = rr_recompute if any(layer_module.skip_recompute_ops.values()) else recompute + hidden_states = recompute_fn( + create_custom_forward(layer_module), + hidden_states, + position_ids, + attention_mask, + output_attentions, + past_key_value, + use_cache, + attn_mask_startend_row_indices, + batch_size, + use_reentrant=self.config.recompute_use_reentrant, + ) + + return hidden_states + def forward( + self, + input_ids: paddle.Tensor = None, + position_ids: Optional[paddle.Tensor] = None, + attention_mask: Optional[paddle.Tensor] = None, + inputs_embeds: Optional[paddle.Tensor] = None, + use_cache: Optional[bool] = None, + past_key_values: Optional[List[paddle.Tensor]] = None, + output_attentions: Optional[bool] = None, + output_hidden_states: Optional[bool] = None, + return_dict: Optional[bool] = None, + attn_mask_startend_row_indices=None, + ) -> Union[Tuple, BaseModelOutputWithPast]: + super().forward() +class Qwen2PretrainingCriterion(LlamaPretrainingCriterion): + """Qwen2的预训练损失计算,继承自LlamaPretrainingCriterion""" + def __init__(self, config: Qwen2Config): + super().__init__(config) + self.enable_parallel_cross_entropy = config.tensor_parallel_degree > 1 and config.tensor_parallel_output + +class Qwen2LMHead(LlamaLMHead): + """Qwen2的语言模型头,继承自LlamaLMHead""" + def __init__(self, config: Qwen2Config): + super().__init__(config) + def forward(self, hidden_states, tensor_parallel_output=None, batch_size=None): + if self.config.sequence_parallel: + hidden_states = GatherOp.apply(hidden_states) + hidden_states = paddle.reshape_(hidden_states, [batch_size, -1, self.config.hidden_size]) + + if tensor_parallel_output is None: + tensor_parallel_output = self.config.tensor_parallel_output + + logits = parallel_matmul( + hidden_states, self.weight, transpose_y=self.transpose_y, tensor_parallel_output=tensor_parallel_output + ) + return logits +class Qwen2ForCausalLM(LlamaForCausalLM): + """用于因果语言建模的Qwen2模型,继承自LlamaForCausalLM""" + def __init__(self, config: Qwen2Config): + super().__init__(config) + @staticmethod + def update_model_kwargs_for_generation(outputs, model_kwargs, is_encoder_decoder=False): + # update cache + if isinstance(outputs, tuple) and len(outputs) > 1 and not isinstance(outputs[1], paddle.Tensor): + model_kwargs["past_key_values"] = outputs[1] + + if isinstance(outputs, CausalLMOutputWithPast) and "past_key_values" in outputs: + model_kwargs["past_key_values"] = outputs.past_key_values + + # update position_ids + if "position_ids" in model_kwargs and model_kwargs["position_ids"] is not None: + position_ids = model_kwargs["position_ids"] + model_kwargs["position_ids"] = paddle.concat([position_ids, position_ids[..., -1:] + 1], axis=-1) + + if not is_encoder_decoder and "attention_mask" in model_kwargs: + # TODO: support attention mask for other models + attention_mask = model_kwargs["attention_mask"] + if len(attention_mask.shape) == 2: + model_kwargs["attention_mask"] = paddle.concat( + [attention_mask, paddle.ones([attention_mask.shape[0], 1], dtype=attention_mask.dtype)], + axis=-1, + ) + elif len(attention_mask.shape) == 4: + model_kwargs["attention_mask"] = paddle.concat( + [attention_mask, paddle.ones([*attention_mask.shape[:3], 1], dtype=attention_mask.dtype)], + axis=-1, + )[:, :, -1:, :] + + return model_kwargs + + def forward( + self, + input_ids: paddle.Tensor = None, + position_ids: Optional[paddle.Tensor] = None, + attention_mask: Optional[paddle.Tensor] = None, + inputs_embeds: Optional[paddle.Tensor] = None, + labels: Optional[paddle.Tensor] = None, + use_cache: Optional[bool] = None, + past_key_values: Optional[List[paddle.Tensor]] = None, + output_attentions: Optional[bool] = None, + output_hidden_states: Optional[bool] = None, + return_dict: Optional[bool] = None, + attn_mask_startend_row_indices=None, + ) -> Union[Tuple, CausalLMOutputWithPast]: + r""" + Args: + labels (`paddle.Tensor` of shape `(batch_size, sequence_length)`, *optional*): + Labels for computing the masked language modeling loss. Indices should either be in `[0, ..., + config.vocab_size]` or -100 (see `input_ids` docstring). Tokens with indices set to `-100` are ignored + (masked), the loss is only computed for the tokens with labels in `[0, ..., config.vocab_size]`. + + Returns: + + Example: + + ```python + >>> from transformers import AutoTokenizer, Qwen2ForCausalLM + + >>> model = Qwen2ForCausalLM.from_pretrained(PATH_TO_CONVERTED_WEIGHTS) + >>> tokenizer = AutoTokenizer.from_pretrained(PATH_TO_CONVERTED_TOKENIZER) + + >>> prompt = "Hey, are you conscious? Can you talk to me?" + >>> inputs = tokenizer(prompt, return_tensors="pt") + + >>> # Generate + >>> generate_ids = model.generate(inputs.input_ids, max_length=30) + >>> tokenizer.batch_decode(generate_ids, skip_special_tokens=True, clean_up_tokenization_spaces=False)[0] + "Hey, are you conscious? Can you talk to me?\nI'm not conscious, but I can talk to you." + ```""" + + output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions + output_hidden_states = ( + output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states + ) + return_dict = return_dict if return_dict is not None else self.config.use_return_dict + + if attn_mask_startend_row_indices is not None and attention_mask is not None: + logger.warning( + "You have provided both attn_mask_startend_row_indices and attention_mask. " + "The attn_mask_startend_row_indices will be used." + ) + attention_mask = None + + if input_ids is not None and inputs_embeds is not None: + raise ValueError("You cannot specify both decoder_input_ids and decoder_inputs_embeds at the same time") + elif input_ids is not None: + batch_size = input_ids.shape[0] + elif inputs_embeds is not None: + batch_size = inputs_embeds.shape[0] + else: + raise ValueError("You have to specify either decoder_input_ids or decoder_inputs_embeds") + + # decoder outputs consists of (dec_features, layer_state, dec_hidden, dec_attn) + outputs = self.qwen2( + input_ids=input_ids, + position_ids=position_ids, + attention_mask=attention_mask, + inputs_embeds=inputs_embeds, + use_cache=use_cache, + past_key_values=past_key_values, + output_attentions=output_attentions, + output_hidden_states=output_hidden_states, + return_dict=return_dict, + attn_mask_startend_row_indices=attn_mask_startend_row_indices, + ) + + hidden_states = outputs[0] + + # add this for fused_head_and_loss_fn + if self.config.use_fused_head_and_loss_fn and self.training: + if self.config.tensor_parallel_degree > 1 and self.config.sequence_parallel: + hidden_states = GatherOp.apply(hidden_states) + hidden_states = hidden_states.reshape( + [ + batch_size, + -1, + hidden_states.shape[-1], + ] + ) + return hidden_states, self.lm_head.weight, None, self.lm_head.transpose_y + + # if labels is None,means we need full output, instead of tensor_parallel_output + # tensor_parallel_output is together with ParallelCrossEntropy + tensor_parallel_output = self.config.tensor_parallel_output and self.config.tensor_parallel_degree > 1 + + if labels is not None and self.config.use_fused_linear_cross_entropy: + from paddlenlp_kernel.triton.cut_cross_entropy import linear_cross_entropy + + assert ( + self.config.tensor_parallel_degree <= 1 + ), "The argument `use_fused_linear_cross_entropy` is imcompatiable with tensor parallel " + + masked_lm_loss = linear_cross_entropy(hidden_states, self.lm_head.weight, targets=labels) + + binary_sequence = paddle.where( + masked_lm_loss > 0, paddle.ones_like(masked_lm_loss), paddle.zeros_like(masked_lm_loss) + ) + count = paddle.sum(binary_sequence) + if count == 0: + loss = paddle.sum(masked_lm_loss * binary_sequence) + else: + loss = paddle.sum(masked_lm_loss * binary_sequence) / count + logits = None + else: + logits = self.lm_head(hidden_states, tensor_parallel_output=tensor_parallel_output, batch_size=batch_size) + + loss = None + if labels is not None: + loss = self.criterion(logits, labels) + + if not return_dict: + output = (logits,) + outputs[1:] + return (loss,) + output if loss is not None else output + + return CausalLMOutputWithPast( + loss=loss, + logits=logits, + past_key_values=outputs.past_key_values, + hidden_states=outputs.hidden_states, + attentions=outputs.attentions, + ) + + +class Qwen2ForSequenceClassification(Qwen2PretrainedModel): + def __init__(self, config: Qwen2Config): + super().__init__(config) + self.num_labels = config.num_labels + self.qwen2 = Qwen2Model(config) + self.score = Linear(config.hidden_size, self.num_labels, bias_attr=False) + + def get_input_embeddings(self): + return self.qwen2.embed_tokens + + def set_input_embeddings(self, value): + self.qwen2.embed_tokens = value + + def forward( + self, + input_ids: paddle.Tensor = None, + position_ids: Optional[paddle.Tensor] = None, + attention_mask: Optional[paddle.Tensor] = None, + inputs_embeds: Optional[paddle.Tensor] = None, + past_key_values: Optional[List[paddle.Tensor]] = None, + labels: Optional[paddle.Tensor] = None, + use_cache: Optional[bool] = None, + output_attentions: Optional[bool] = None, + output_hidden_states: Optional[bool] = None, + return_dict: Optional[bool] = None, + ) -> Union[Tuple, SequenceClassifierOutputWithPast]: + r""" + labels (`paddle.Tensor` of shape `(batch_size,)`, *optional*): + Labels for computing the sequence classification/regression loss. Indices should be in `[0, ..., + config.num_labels - 1]`. If `config.num_labels == 1` a regression loss is computed (Mean-Square loss), If + `config.num_labels > 1` a classification loss is computed (Cross-Entropy). + """ + return_dict = return_dict if return_dict is not None else self.config.use_return_dict + + transformer_outputs = self.qwen2( + input_ids, + attention_mask=attention_mask, + position_ids=position_ids, + past_key_values=past_key_values, + inputs_embeds=inputs_embeds, + use_cache=use_cache, + output_attentions=output_attentions, + output_hidden_states=output_hidden_states, + return_dict=return_dict, + ) + hidden_states = transformer_outputs[0] + logits = self.score(hidden_states) + + if input_ids is not None: + batch_size = input_ids.shape[0] + else: + batch_size = inputs_embeds.shape[0] + + if self.config.pad_token_id is None and batch_size != 1: + raise ValueError("Cannot handle batch sizes > 1 if no padding token is defined.") + if self.config.pad_token_id is None: + sequence_lengths = -1 + else: + if input_ids is not None: + # if no pad token found, use modulo instead of reverse indexing for ONNX compatibility + sequence_lengths = paddle.equal(input_ids, self.config.pad_token_id).astype("int32").argmax(-1) - 1 + sequence_lengths = sequence_lengths % input_ids.shape[-1] + sequence_lengths = sequence_lengths + else: + sequence_lengths = -1 + + # pooled_logits = logits[paddle.arange(batch_size), sequence_lengths] + pooled_logits = logits.gather_nd(paddle.stack([paddle.arange(logits.shape[0]), sequence_lengths], axis=-1)) + + loss = None + if labels is not None: + if self.config.problem_type is None: + if self.num_labels == 1: + self.config.problem_type = "regression" + elif self.num_labels > 1 and (labels.dtype == paddle.int64 or labels.dtype == paddle.int32): + self.config.problem_type = "single_label_classification" + else: + self.config.problem_type = "multi_label_classification" + + if self.config.problem_type == "regression": + loss_fct = nn.MSELoss() + if self.num_labels == 1: + loss = loss_fct(pooled_logits.squeeze(), labels.squeeze()) + else: + loss = loss_fct(pooled_logits, labels) + elif self.config.problem_type == "single_label_classification": + loss_fct = nn.CrossEntropyLoss() + loss = loss_fct(pooled_logits.reshape([-1, self.num_labels]), labels.reshape([-1])) + elif self.config.problem_type == "multi_label_classification": + loss_fct = nn.BCEWithLogitsLoss() + loss = loss_fct(pooled_logits, labels) + if not return_dict: + output = (pooled_logits,) + transformer_outputs[1:] + return ((loss,) + output) if loss is not None else output + + return SequenceClassifierOutputWithPast( + loss=loss, + logits=pooled_logits, + past_key_values=transformer_outputs.past_key_values, + hidden_states=transformer_outputs.hidden_states, + attentions=transformer_outputs.attentions, + ) + + +# Copied from transformers.models.llama.modeling_llama.LlamaForTokenClassification with Llama->Qwen2, LLAMA->QWEN2 +class Qwen2ForTokenClassification(Qwen2PretrainedModel): + def __init__(self, config: Qwen2Config): + super().__init__(config) + self.num_labels = config.num_labels + self.qwen2 = Qwen2Model(config) + if getattr(config, "classifier_dropout", None) is not None: + classifier_dropout = config.classifier_dropout + elif getattr(config, "hidden_dropout", None) is not None: + classifier_dropout = config.hidden_dropout + else: + classifier_dropout = 0.1 + self.dropout = nn.Dropout(classifier_dropout) + self.score = Linear(config.hidden_size, config.num_labels) + + def get_input_embeddings(self): + return self.qwen2.embed_tokens + + def set_input_embeddings(self, value): + self.qwen2.embed_tokens = value + + def forward( + self, + input_ids: paddle.Tensor = None, + attention_mask: Optional[paddle.Tensor] = None, + position_ids: Optional[paddle.Tensor] = None, + past_key_values: Optional[List[paddle.Tensor]] = None, + inputs_embeds: Optional[paddle.Tensor] = None, + labels: Optional[paddle.Tensor] = None, + use_cache: Optional[bool] = None, + output_attentions: Optional[bool] = None, + output_hidden_states: Optional[bool] = None, + return_dict: Optional[bool] = None, + ) -> Union[Tuple, SequenceClassifierOutputWithPast]: + r""" + labels (`paddle.Tensor` of shape `(batch_size,)`, *optional*): + Labels for computing the sequence classification/regression loss. Indices should be in `[0, ..., + config.num_labels - 1]`. If `config.num_labels == 1` a regression loss is computed (Mean-Square loss), If + `config.num_labels > 1` a classification loss is computed (Cross-Entropy). + """ + return_dict = return_dict if return_dict is not None else self.config.use_return_dict + + outputs = self.qwen2( + input_ids, + attention_mask=attention_mask, + position_ids=position_ids, + past_key_values=past_key_values, + inputs_embeds=inputs_embeds, + use_cache=use_cache, + output_attentions=output_attentions, + output_hidden_states=output_hidden_states, + return_dict=return_dict, + ) + sequence_output = outputs[0] + sequence_output = self.dropout(sequence_output) + logits = self.score(sequence_output) + + loss = None + if labels is not None: + loss_fct = nn.CrossEntropyLoss() + loss = loss_fct(logits.reshape([-1, self.num_labels]), labels.reshape([-1])) + + if not return_dict: + output = (logits,) + outputs[2:] + return ((loss,) + output) if loss is not None else output + + return TokenClassifierOutput( + loss=loss, + logits=logits, + hidden_states=outputs.hidden_states, + attentions=outputs.attentions, + ) + + +class Qwen2SentenceEmbedding(Qwen2PretrainedModel): + def __init__( + self, + config: Qwen2Config, + embedding_temperature: float = 0.02, + ): + """Qwen2SentenceEmbedding + For getting larger batch_size, we use tensor parallel to get larger batch_size. + + Args: + config (Qwen2Config): _description_ + model (Qwen2Model): _description_ + embedding_temperature (float, optional): _description_. Defaults to 0.02. + """ + super(Qwen2SentenceEmbedding, self).__init__(config) + self.config = config + self.qwen2 = Qwen2Model(config) + self.in_batch_negative_loss = SimpleContrastiveLoss(embedding_temperature) + self.world_size = dist.get_world_size() + self.process_rank = dist.get_rank() + self.embedding_negatives_cross_device = config.embedding_negatives_cross_device + if self.world_size <= 1: + self.embedding_negatives_cross_device = False + + def forward( + self, + query: Optional[Dict[str, paddle.Tensor]] = None, + passages: Optional[Dict[str, paddle.Tensor]] = None, + return_encode=False, + ): + """forward""" + q_reps = self.encode(**query) + p_reps = self.encode(**passages) + + q_reps = nn.functional.normalize(q_reps, axis=-1) + p_reps = nn.functional.normalize(p_reps, axis=-1) + + if return_encode: + return q_reps, p_reps + + if self.embedding_negatives_cross_device: + q_reps = dist_gather_tensor_with_gradient(q_reps) + p_reps = dist_gather_tensor_with_gradient(p_reps) + + loss = self.in_batch_negative_loss(q_reps, p_reps) + return loss + + def encode( + self, + input_ids, + position_ids=None, + embedding_indices=None, + attention_mask=None, + output_attentions=False, + output_hidden_states=False, + return_dict=False, + **kwargs, + ): + """encode""" + input_type = type(input_ids) + outputs = self.qwen2( + input_ids, + position_ids=position_ids, + attention_mask=attention_mask, + output_attentions=output_attentions, + output_hidden_states=output_hidden_states, + return_dict=return_dict, + **kwargs, + ) + if isinstance(outputs, input_type): + hidden_states = outputs + else: + hidden_states = outputs[0] + last_hidden_states = hidden_states.gather_nd(embedding_indices) + return last_hidden_states +