lijn14 commited on
Commit ·
c1a46f7
1
Parent(s): 70e674c
创建工程
Browse files- .gitignore +48 -0
- README.md +74 -1
- TASK_ASSIGNMENT.md +414 -0
- configs/deepspeed_config.json +28 -0
- configs/default_config.yaml +182 -0
- pytest.ini +6 -0
- requirements.txt +35 -0
- scripts/evaluate.py +50 -0
- scripts/run_experiments.py +113 -0
- scripts/train.py +76 -0
- scripts/translate.py +82 -0
- scripts/visualize.py +86 -0
- setup.py +21 -0
- src/easytranslate/__init__.py +3 -0
- src/easytranslate/evaluation/__init__.py +16 -0
- src/easytranslate/evaluation/decoding.py +129 -0
- src/easytranslate/evaluation/evaluator.py +76 -0
- src/easytranslate/evaluation/metrics.py +109 -0
- src/easytranslate/model/__init__.py +20 -0
- src/easytranslate/model/attention.py +106 -0
- src/easytranslate/model/decoder.py +86 -0
- src/easytranslate/model/encoder.py +84 -0
- src/easytranslate/model/finetune.py +95 -0
- src/easytranslate/model/positional.py +97 -0
- src/easytranslate/model/transformer.py +157 -0
- src/easytranslate/training/__init__.py +12 -0
- src/easytranslate/training/loss.py +57 -0
- src/easytranslate/training/optimizer.py +76 -0
- src/easytranslate/training/trainer.py +186 -0
- src/easytranslate/utils/__init__.py +7 -0
- src/easytranslate/utils/config.py +51 -0
- src/easytranslate/utils/logging.py +26 -0
- src/easytranslate/utils/seed.py +23 -0
- tests/test_data.py +72 -0
- tests/test_evaluation.py +54 -0
- tests/test_model.py +60 -0
- tests/test_training.py +46 -0
.gitignore
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Byte-compiled / optimized / DLL files
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*$py.class
|
| 5 |
+
|
| 6 |
+
# Distribution
|
| 7 |
+
dist/
|
| 8 |
+
build/
|
| 9 |
+
*.egg-info/
|
| 10 |
+
*.egg
|
| 11 |
+
|
| 12 |
+
# Virtual environments
|
| 13 |
+
.venv/
|
| 14 |
+
venv/
|
| 15 |
+
env/
|
| 16 |
+
|
| 17 |
+
# IDE
|
| 18 |
+
.idea/
|
| 19 |
+
.vscode/
|
| 20 |
+
*.swp
|
| 21 |
+
*.swo
|
| 22 |
+
|
| 23 |
+
# Data
|
| 24 |
+
data/
|
| 25 |
+
*.tsv
|
| 26 |
+
*.csv
|
| 27 |
+
|
| 28 |
+
# Checkpoints & Outputs
|
| 29 |
+
checkpoints/
|
| 30 |
+
outputs/
|
| 31 |
+
logs/
|
| 32 |
+
wandb/
|
| 33 |
+
|
| 34 |
+
# OS
|
| 35 |
+
.DS_Store
|
| 36 |
+
Thumbs.db
|
| 37 |
+
|
| 38 |
+
# Models
|
| 39 |
+
*.pt
|
| 40 |
+
*.pth
|
| 41 |
+
*.bin
|
| 42 |
+
*.safetensors
|
| 43 |
+
*.onnx
|
| 44 |
+
|
| 45 |
+
# Tokenizer artifacts
|
| 46 |
+
*.model
|
| 47 |
+
*.vocab
|
| 48 |
+
tokenizer.json
|
README.md
CHANGED
|
@@ -1,4 +1,77 @@
|
|
| 1 |
---
|
| 2 |
license: mit
|
| 3 |
---
|
| 4 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
license: mit
|
| 3 |
---
|
| 4 |
+
|
| 5 |
+
# EasyTranslate: 基于 Transformer 的英中翻译系统
|
| 6 |
+
|
| 7 |
+
> 研究生 NLP 期末项目 — 使用前沿技术实现高质量英文到中文机器翻译
|
| 8 |
+
|
| 9 |
+
## 亮点
|
| 10 |
+
|
| 11 |
+
- **Pre-LayerNorm Transformer**: 从零构建,训练更稳定
|
| 12 |
+
- **RoPE 旋转位置编码**: 替代传统正弦编码,更好捕获相对位置信息
|
| 13 |
+
- **Flash Attention 2**: 利用 PyTorch 2.0+ 原生支持,训练显著加速
|
| 14 |
+
- **NLLB 预训练微调**: 基于 Meta NLLB-200 模型,支持 LoRA 参数高效微调
|
| 15 |
+
- **完整评估体系**: SacreBLEU + COMET + chrF++ 多维度评估
|
| 16 |
+
- **6 组消融实验**: 系统验证各前沿组件的效果
|
| 17 |
+
|
| 18 |
+
## 快速开始
|
| 19 |
+
|
| 20 |
+
```bash
|
| 21 |
+
# 安装
|
| 22 |
+
conda create -n easytranslate python=3.11 && conda activate easytranslate
|
| 23 |
+
pip install torch --index-url https://download.pytorch.org/whl/cu121
|
| 24 |
+
pip install -r requirements.txt && pip install -e .
|
| 25 |
+
|
| 26 |
+
# 从头训练 Transformer
|
| 27 |
+
python scripts/train.py --config configs/default_config.yaml
|
| 28 |
+
|
| 29 |
+
# 微调 NLLB + LoRA
|
| 30 |
+
python scripts/train.py --config configs/default_config.yaml model.type=finetune_nllb
|
| 31 |
+
|
| 32 |
+
# 评估
|
| 33 |
+
python scripts/evaluate.py --checkpoint checkpoints/best_model.pt
|
| 34 |
+
|
| 35 |
+
# 交互翻译
|
| 36 |
+
python scripts/translate.py --checkpoint checkpoints/best_model.pt
|
| 37 |
+
```
|
| 38 |
+
|
| 39 |
+
## 项目结构
|
| 40 |
+
|
| 41 |
+
```
|
| 42 |
+
src/easytranslate/
|
| 43 |
+
├── data/ # 数据加载、分词、预处理、动态批处理
|
| 44 |
+
├── model/ # Transformer (Encoder-Decoder) + 注意力 + 位置编码 + LoRA
|
| 45 |
+
├── training/ # 训练循环、优化器、损失函数、分布式训练
|
| 46 |
+
├── evaluation/ # BLEU/COMET 指标、Beam Search 解码、评估器
|
| 47 |
+
└── utils/ # 配置管理、日志、随机种子
|
| 48 |
+
```
|
| 49 |
+
|
| 50 |
+
## 技术架构
|
| 51 |
+
|
| 52 |
+
```
|
| 53 |
+
英文输入 → BPE Tokenizer → Source Embedding + RoPE
|
| 54 |
+
→ Transformer Encoder (6 layers, Flash Attention, Pre-LN)
|
| 55 |
+
→ Transformer Decoder (6 layers, Causal Mask, Cross-Attention)
|
| 56 |
+
→ Linear Projection → Beam Search → 中文输出
|
| 57 |
+
```
|
| 58 |
+
|
| 59 |
+
## 团队分工
|
| 60 |
+
|
| 61 |
+
详见 [TASK_ASSIGNMENT.md](TASK_ASSIGNMENT.md)
|
| 62 |
+
|
| 63 |
+
| 角色 | 负责模块 | 核心任务 |
|
| 64 |
+
|------|----------|----------|
|
| 65 |
+
| Person A | `data/` | 数据集加载、BPE 分词器、预处理流水线 |
|
| 66 |
+
| Person B | `model/` | Transformer 架构、Flash Attention、RoPE、LoRA |
|
| 67 |
+
| Person C | `training/` | 训练循环、混合精度、梯度累积、早停 |
|
| 68 |
+
| Person D | `evaluation/` | BLEU/COMET 评估、Beam Search、推理服务 |
|
| 69 |
+
| Person E | `utils/` + 实验 | 工具模块、消融实验、可视化、报告 |
|
| 70 |
+
|
| 71 |
+
## 依赖
|
| 72 |
+
|
| 73 |
+
- Python >= 3.10
|
| 74 |
+
- PyTorch >= 2.1 (Flash Attention 支持)
|
| 75 |
+
- HuggingFace Transformers / Datasets / Tokenizers
|
| 76 |
+
- PEFT (LoRA)
|
| 77 |
+
- SacreBLEU, COMET (评估)
|
TASK_ASSIGNMENT.md
ADDED
|
@@ -0,0 +1,414 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# EasyTranslate 任务分工文档
|
| 2 |
+
|
| 3 |
+
## 项目概述
|
| 4 |
+
|
| 5 |
+
**项目名称**: EasyTranslate — 基于 Transformer 的英中翻译系统
|
| 6 |
+
**课程**: 研究生 NLP 期末作业
|
| 7 |
+
**团队规模**: 5 人
|
| 8 |
+
**技术栈**: PyTorch + HuggingFace Transformers + Flash Attention 2 + RoPE + LoRA
|
| 9 |
+
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
## 📂 项目结构
|
| 13 |
+
|
| 14 |
+
```
|
| 15 |
+
UCAS-EasyTranslate/
|
| 16 |
+
├── configs/
|
| 17 |
+
│ ├── default_config.yaml # 主配置文件
|
| 18 |
+
│ └── deepspeed_config.json # DeepSpeed 分布式训练配置
|
| 19 |
+
├── src/easytranslate/
|
| 20 |
+
│ ├── data/ # [Person A] 数据模块
|
| 21 |
+
│ │ ├── dataset.py # 数据集加载
|
| 22 |
+
│ │ ├── tokenizer.py # 分词器
|
| 23 |
+
│ │ ├── preprocessing.py # 数据预处理
|
| 24 |
+
│ │ └── collator.py # 数据整理 & 动态批处理
|
| 25 |
+
│ ├── model/ # [Person B] 模型模块
|
| 26 |
+
│ │ ├── transformer.py # Transformer 主模型
|
| 27 |
+
│ │ ├── encoder.py # 编码器
|
| 28 |
+
│ │ ├── decoder.py # 解码器
|
| 29 |
+
│ │ ├── attention.py # 注意力机制 (标准 + Flash Attention)
|
| 30 |
+
│ │ ├── positional.py # 位置编码 (Sinusoidal + RoPE)
|
| 31 |
+
│ │ └── finetune.py # 预训练模型微调 (NLLB + LoRA)
|
| 32 |
+
│ ├── training/ # [Person C] 训练模块
|
| 33 |
+
│ │ ├── trainer.py # 训练器 (完整训练循环)
|
| 34 |
+
│ │ ├── optimizer.py # 优化器 & 学习率调度
|
| 35 |
+
│ │ └── loss.py # 损失函数 (标签平滑)
|
| 36 |
+
│ ├── evaluation/ # [Person D] 评估模块
|
| 37 |
+
│ │ ├── metrics.py # 评估指标 (BLEU, COMET, chrF)
|
| 38 |
+
│ │ ├── decoding.py # 解码策略 (贪心, Beam Search, 采样)
|
| 39 |
+
│ │ └── evaluator.py # 评估器
|
| 40 |
+
│ └── utils/ # [Person E] 工具模块
|
| 41 |
+
│ ├── config.py # 配置管理
|
| 42 |
+
│ ├── seed.py # 随机种子
|
| 43 |
+
│ └── logging.py # 日志管理
|
| 44 |
+
├── scripts/
|
| 45 |
+
│ ├── train.py # 训练入口
|
| 46 |
+
│ ├── evaluate.py # 评估入口
|
| 47 |
+
│ ├── translate.py # 翻译推理 (CLI + Web UI)
|
| 48 |
+
│ ├── run_experiments.py # [Person E] 实验运行器
|
| 49 |
+
│ └── visualize.py # [Person E] 可视化分析
|
| 50 |
+
├── tests/
|
| 51 |
+
│ ├── test_data.py # 数据模块测试
|
| 52 |
+
│ ├── test_model.py # 模型模块测试
|
| 53 |
+
│ ├── test_training.py # 训练模块测试
|
| 54 |
+
│ └── test_evaluation.py # 评估模块测试
|
| 55 |
+
├── requirements.txt
|
| 56 |
+
├── setup.py
|
| 57 |
+
└── README.md
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
---
|
| 61 |
+
|
| 62 |
+
## 👥 人员分工
|
| 63 |
+
|
| 64 |
+
### Person A — 数据工程师
|
| 65 |
+
|
| 66 |
+
**负责文件**: `src/easytranslate/data/` 目录下所有文件
|
| 67 |
+
**预计工作量**: ~800 行代码
|
| 68 |
+
**截止日期建议**: 第 1-2 周
|
| 69 |
+
|
| 70 |
+
#### 核心任务
|
| 71 |
+
|
| 72 |
+
| 优先级 | 文件 | 任务 | 要点 |
|
| 73 |
+
|--------|------|------|------|
|
| 74 |
+
| P0 | `dataset.py` | `TranslationDataset.__getitem__` | 实现 tokenize + padding + teacher forcing 输入构造 |
|
| 75 |
+
| P0 | `dataset.py` | `load_wmt_dataset` | 使用 HuggingFace datasets 加载 WMT19 zh-en |
|
| 76 |
+
| P0 | `tokenizer.py` | `TokenizerWrapper` | 统一分词器接口 (encode/decode/vocab_size/special_tokens) |
|
| 77 |
+
| P0 | `tokenizer.py` | `train_bpe_tokenizer` | 使用 HuggingFace tokenizers 库训练 BPE |
|
| 78 |
+
| P0 | `tokenizer.py` | `build_tokenizer` | 根据配置构建分词器 |
|
| 79 |
+
| P1 | `preprocessing.py` | `clean_text` | Unicode 标准化 + 控制字符去除 |
|
| 80 |
+
| P1 | `preprocessing.py` | `filter_by_length` | 按长度和长度比过滤 |
|
| 81 |
+
| P1 | `preprocessing.py` | `preprocess_pipeline` | 完整预处理流水线 |
|
| 82 |
+
| P1 | `collator.py` | `TranslationCollator` | batch padding + mask 生成 |
|
| 83 |
+
| P2 | `collator.py` | `DynamicBatchSampler` | 按 token 数动态构建 batch |
|
| 84 |
+
| P2 | `dataset.py` | `load_opus_dataset` | OPUS-100 数据集加载 |
|
| 85 |
+
| P2 | `dataset.py` | `load_custom_dataset` | 自定义语料加载 |
|
| 86 |
+
|
| 87 |
+
#### 技术参考
|
| 88 |
+
- HuggingFace datasets: https://huggingface.co/docs/datasets
|
| 89 |
+
- HuggingFace tokenizers: https://huggingface.co/docs/tokenizers
|
| 90 |
+
- WMT19 zh-en: `datasets.load_dataset("wmt19", "zh-en")`
|
| 91 |
+
|
| 92 |
+
#### 验收标准
|
| 93 |
+
- [ ] `pytest tests/test_data.py` 全部通过
|
| 94 |
+
- [ ] 能够成功加载 WMT19 数据集并完成预处理
|
| 95 |
+
- [ ] BPE 分词器训练成功,encode/decode 往返一致
|
| 96 |
+
- [ ] DataLoader 能正常迭代,batch 维度正确
|
| 97 |
+
|
| 98 |
+
---
|
| 99 |
+
|
| 100 |
+
### Person B — 模型架构师
|
| 101 |
+
|
| 102 |
+
**负责文件**: `src/easytranslate/model/` 目录下所有文件
|
| 103 |
+
**预计工作量**: ~1000 行代码
|
| 104 |
+
**截止日期建议**: 第 1-2 周
|
| 105 |
+
|
| 106 |
+
#### 核心任务
|
| 107 |
+
|
| 108 |
+
| 优先级 | 文件 | 任务 | 要点 |
|
| 109 |
+
|--------|------|------|------|
|
| 110 |
+
| P0 | `attention.py` | `MultiHeadAttention` | 标准多头注意力: QKV 投影 + scaled dot-product + mask |
|
| 111 |
+
| P0 | `attention.py` | `FlashMultiHeadAttention` | 使用 F.scaled_dot_product_attention (PyTorch 2.0+) |
|
| 112 |
+
| P0 | `positional.py` | `SinusoidalPositionalEncoding` | 经典正弦余弦位置编码 |
|
| 113 |
+
| P0 | `positional.py` | `RotaryPositionalEmbedding` | RoPE 旋转位置编码 (前沿技术!) |
|
| 114 |
+
| P0 | `encoder.py` | `TransformerEncoderLayer/Encoder` | Pre-LayerNorm Encoder |
|
| 115 |
+
| P0 | `decoder.py` | `TransformerDecoderLayer/Decoder` | Pre-LayerNorm Decoder (self-attn + cross-attn + FFN) |
|
| 116 |
+
| P0 | `transformer.py` | `TransformerTranslationModel` | 完整 Enc-Dec 模型: embedding + encoder + decoder + projection |
|
| 117 |
+
| P1 | `transformer.py` | `encode / decode_step` | 推理用的编码和单步解码 |
|
| 118 |
+
| P1 | `finetune.py` | `load_pretrained_model` | 加载 NLLB/mBART 预训练模型 |
|
| 119 |
+
| P1 | `finetune.py` | `setup_lora` | 使用 PEFT 库配置 LoRA 微调 |
|
| 120 |
+
|
| 121 |
+
#### 关键技术点
|
| 122 |
+
|
| 123 |
+
1. **Pre-LayerNorm** (比 Post-LN 训练更稳定):
|
| 124 |
+
```
|
| 125 |
+
x → LayerNorm → Attention → Add(x) → LayerNorm → FFN → Add
|
| 126 |
+
```
|
| 127 |
+
|
| 128 |
+
2. **RoPE 旋转位置编码** (LLaMA/GPT-NeoX 使用):
|
| 129 |
+
```python
|
| 130 |
+
q' = q * cos(θ) + rotate_half(q) * sin(θ)
|
| 131 |
+
k' = k * cos(θ) + rotate_half(k) * sin(θ)
|
| 132 |
+
# attention(q', k') 自动编码相对位置信息
|
| 133 |
+
```
|
| 134 |
+
|
| 135 |
+
3. **Flash Attention 2** (PyTorch 2.0+ 原生支持):
|
| 136 |
+
```python
|
| 137 |
+
F.scaled_dot_product_attention(Q, K, V, attn_mask, dropout_p, is_causal)
|
| 138 |
+
```
|
| 139 |
+
|
| 140 |
+
4. **LoRA 微调** (参数高效):
|
| 141 |
+
```python
|
| 142 |
+
from peft import LoraConfig, get_peft_model, TaskType
|
| 143 |
+
config = LoraConfig(r=16, lora_alpha=32, target_modules=["q_proj", "v_proj"], task_type=TaskType.SEQ_2_SEQ_LM)
|
| 144 |
+
model = get_peft_model(model, config)
|
| 145 |
+
```
|
| 146 |
+
|
| 147 |
+
#### 验收标准
|
| 148 |
+
- [ ] `pytest tests/test_model.py` 全部通过
|
| 149 |
+
- [ ] 模型前向传播输出维度正确: `[B, T, vocab_size]`
|
| 150 |
+
- [ ] Flash Attention 和标准 Attention 输出一致
|
| 151 |
+
- [ ] RoPE 编码能正确应用到 Q, K
|
| 152 |
+
- [ ] LoRA 模型可训练参数量远小于全量参数
|
| 153 |
+
|
| 154 |
+
---
|
| 155 |
+
|
| 156 |
+
### Person C — 训练工程师
|
| 157 |
+
|
| 158 |
+
**负责文件**: `src/easytranslate/training/` 目录下所有文件
|
| 159 |
+
**预计工作量**: ~700 行代码
|
| 160 |
+
**截止日期建议**: 第 2-3 周 (依赖 Person A, B)
|
| 161 |
+
|
| 162 |
+
#### 核心任务
|
| 163 |
+
|
| 164 |
+
| 优先级 | 文件 | 任务 | 要点 |
|
| 165 |
+
|--------|------|------|------|
|
| 166 |
+
| P0 | `loss.py` | `LabelSmoothedCrossEntropyLoss` | 带标签平滑的交叉熵损失 + 忽略 padding |
|
| 167 |
+
| P0 | `optimizer.py` | `build_optimizer` | 构建 AdamW,支持参数分组 |
|
| 168 |
+
| P0 | `optimizer.py` | `build_scheduler` | Cosine with warmup / Inverse sqrt 调度器 |
|
| 169 |
+
| P0 | `trainer.py` | `Trainer.__init__` | 初始化训练环境 (设备, 精度, 分布式) |
|
| 170 |
+
| P0 | `trainer.py` | `_train_one_epoch` | 单 epoch 训练循环 (混合精度 + 梯度累积) |
|
| 171 |
+
| P0 | `trainer.py` | `_validate` | 验证循环 |
|
| 172 |
+
| P1 | `trainer.py` | `train` | 主训练循环 (多 epoch) |
|
| 173 |
+
| P1 | `trainer.py` | `_save/_load_checkpoint` | 检查点保存和加载 |
|
| 174 |
+
| P1 | `trainer.py` | `_should_early_stop` | 早停逻辑 |
|
| 175 |
+
| P2 | `trainer.py` | `_setup_distributed` | DDP/FSDP/DeepSpeed 分布式训练 |
|
| 176 |
+
| P2 | `trainer.py` | `_log_metrics` | TensorBoard/WandB 日志 |
|
| 177 |
+
|
| 178 |
+
#### 关键技术点
|
| 179 |
+
|
| 180 |
+
1. **混合精度训练**:
|
| 181 |
+
```python
|
| 182 |
+
scaler = torch.amp.GradScaler('cuda')
|
| 183 |
+
with torch.amp.autocast('cuda', dtype=torch.float16):
|
| 184 |
+
logits = model(src, tgt)
|
| 185 |
+
loss = criterion(logits, labels)
|
| 186 |
+
scaler.scale(loss).backward()
|
| 187 |
+
scaler.step(optimizer)
|
| 188 |
+
scaler.update()
|
| 189 |
+
```
|
| 190 |
+
|
| 191 |
+
2. **梯度累积**:
|
| 192 |
+
```python
|
| 193 |
+
loss = loss / gradient_accumulation_steps
|
| 194 |
+
loss.backward()
|
| 195 |
+
if (step + 1) % gradient_accumulation_steps == 0:
|
| 196 |
+
optimizer.step()
|
| 197 |
+
optimizer.zero_grad()
|
| 198 |
+
```
|
| 199 |
+
|
| 200 |
+
3. **标签平滑** (smoothing=0.1):
|
| 201 |
+
```
|
| 202 |
+
对于 target token: prob = 1 - smoothing = 0.9
|
| 203 |
+
对于其他 token: prob = smoothing / (V - 1) ≈ 0.000003
|
| 204 |
+
```
|
| 205 |
+
|
| 206 |
+
#### 验收标准
|
| 207 |
+
- [ ] `pytest tests/test_training.py` 全部通过
|
| 208 |
+
- [ ] 能在小数据集上完成完整训练流程
|
| 209 |
+
- [ ] Loss 能正常下降
|
| 210 |
+
- [ ] 检查点能正确保存和加载
|
| 211 |
+
- [ ] 早停机制正常工作
|
| 212 |
+
|
| 213 |
+
---
|
| 214 |
+
|
| 215 |
+
### Person D — 评估与推理工程师
|
| 216 |
+
|
| 217 |
+
**负责文件**: `src/easytranslate/evaluation/` + `scripts/evaluate.py` + `scripts/translate.py`
|
| 218 |
+
**预计工作量**: ~800 行代码
|
| 219 |
+
**截止日期建议**: 第 2-3 周 (依赖 Person B)
|
| 220 |
+
|
| 221 |
+
#### 核心任务
|
| 222 |
+
|
| 223 |
+
| 优先级 | 文件 | 任务 | 要点 |
|
| 224 |
+
|--------|------|------|------|
|
| 225 |
+
| P0 | `metrics.py` | `compute_bleu` | SacreBLEU (tokenize="zh" 对中文分词) |
|
| 226 |
+
| P0 | `metrics.py` | `compute_comet` | COMET 神经网络评估指标 |
|
| 227 |
+
| P0 | `metrics.py` | `compute_chrf` / `compute_ter` | chrF++ 和 TER 指标 |
|
| 228 |
+
| P0 | `decoding.py` | `greedy_decode` | 贪心解码 (逐 token argmax) |
|
| 229 |
+
| P0 | `decoding.py` | `beam_search_decode` | 束搜索 (最关键的解码算法!) |
|
| 230 |
+
| P1 | `decoding.py` | `sample_decode` | 采样解码 (temperature + top-k + top-p) |
|
| 231 |
+
| P1 | `evaluator.py` | `Evaluator` | 统一评估接口 |
|
| 232 |
+
| P1 | `scripts/evaluate.py` | 评估入口脚本 | 加载模型 → 评估 → 输出结果 |
|
| 233 |
+
| P2 | `scripts/translate.py` | 交互翻译 | CLI 交互 + 文件翻译 |
|
| 234 |
+
| P2 | `scripts/translate.py` | `launch_web_ui` | Gradio Web 界面 |
|
| 235 |
+
|
| 236 |
+
#### 关键技术点
|
| 237 |
+
|
| 238 |
+
1. **Beam Search** (翻译最核心的算法):
|
| 239 |
+
```
|
| 240 |
+
维护 beam_size 个候选序列
|
| 241 |
+
每步扩展所有候选 → 选 top-k → 继续
|
| 242 |
+
完成后按 score / length^penalty 排序
|
| 243 |
+
```
|
| 244 |
+
|
| 245 |
+
2. **SacreBLEU** (标准化评估):
|
| 246 |
+
```python
|
| 247 |
+
import sacrebleu
|
| 248 |
+
bleu = sacrebleu.corpus_bleu(hypotheses, [references], tokenize="zh")
|
| 249 |
+
```
|
| 250 |
+
|
| 251 |
+
3. **COMET** (最准确的评估指标):
|
| 252 |
+
```python
|
| 253 |
+
from comet import download_model, load_from_checkpoint
|
| 254 |
+
model = load_from_checkpoint(download_model("Unbabel/wmt22-comet-da"))
|
| 255 |
+
data = [{"src": s, "mt": h, "ref": r} for s, h, r in zip(sources, hyps, refs)]
|
| 256 |
+
output = model.predict(data)
|
| 257 |
+
```
|
| 258 |
+
|
| 259 |
+
#### 验收标准
|
| 260 |
+
- [ ] `pytest tests/test_evaluation.py` 全部通过
|
| 261 |
+
- [ ] BLEU 计算结果与 sacrebleu CLI 一致
|
| 262 |
+
- [ ] Beam search 翻译质量 ≥ Greedy
|
| 263 |
+
- [ ] 能完成端到端评估流程
|
| 264 |
+
- [ ] 交互翻译模式正常工作
|
| 265 |
+
|
| 266 |
+
---
|
| 267 |
+
|
| 268 |
+
### Person E — 实验与报告
|
| 269 |
+
|
| 270 |
+
**负责文件**: `src/easytranslate/utils/` + `scripts/run_experiments.py` + `scripts/visualize.py` + 实验报告
|
| 271 |
+
**预计工作量**: ~600 行代码 + 实验报告
|
| 272 |
+
**截止日期建议**: 第 1 周 (utils) + 第 3-4 周 (实验)
|
| 273 |
+
|
| 274 |
+
#### 核心任务
|
| 275 |
+
|
| 276 |
+
| 优先级 | 文件 | 任务 | 要点 |
|
| 277 |
+
|--------|------|------|------|
|
| 278 |
+
| P0 | `utils/config.py` | 配置管理 | OmegaConf 加载 + CLI 覆盖 |
|
| 279 |
+
| P0 | `utils/seed.py` | 随机种子 | 全局种子设置 (可复现) |
|
| 280 |
+
| P0 | `utils/logging.py` | 日志系统 | Rich 美化 + 文件日志 |
|
| 281 |
+
| P1 | `scripts/run_experiments.py` | 实验运行器 | 自动化运行 6 组消融实验 |
|
| 282 |
+
| P1 | `scripts/visualize.py` | 训练曲线 | Loss/BLEU/LR 曲线绘制 |
|
| 283 |
+
| P1 | `scripts/visualize.py` | 实验对比 | 柱状图 + 表格对比 |
|
| 284 |
+
| P2 | `scripts/visualize.py` | 注意力可视化 | Cross-attention 热力图 |
|
| 285 |
+
| P2 | `scripts/visualize.py` | 翻译样例 | 好坏案例展示 |
|
| 286 |
+
| P0 | — | 实验报告 | 撰写完整实验报告 |
|
| 287 |
+
|
| 288 |
+
#### 实验设计 (6 组消融实验)
|
| 289 |
+
|
| 290 |
+
| 实验编号 | 实验名称 | 关键变量 | 目的 |
|
| 291 |
+
|----------|----------|----------|------|
|
| 292 |
+
| Exp1 | Baseline Transformer | 标准 6 层, d=512, 正弦位置编码 | 基线 |
|
| 293 |
+
| Exp2 | + RoPE | 替换正弦编码为 RoPE | 验证旋转位置编码效果 |
|
| 294 |
+
| Exp3 | + Flash Attention | 使用 Flash Attention 2 | 验证训练加速效果 |
|
| 295 |
+
| Exp4 | Full (RoPE + Flash) | RoPE + Flash Attention | 完整前沿方案 |
|
| 296 |
+
| Exp5 | NLLB + LoRA | 预训练 NLLB-600M + LoRA r=16 | 预训练微调效果 |
|
| 297 |
+
| Exp6 | NLLB Full FT | 预训练 NLLB-600M 全量微调 | 微调上界 |
|
| 298 |
+
|
| 299 |
+
#### 报告结构建议
|
| 300 |
+
1. **摘要**: 项目目标、方法、主要结果
|
| 301 |
+
2. **引言**: 机器翻译背景、Transformer 发展、研究动机
|
| 302 |
+
3. **相关工作**: Transformer、NLLB、LoRA、Flash Attention、RoPE
|
| 303 |
+
4. **方法**:
|
| 304 |
+
- 模型架构 (附架构图)
|
| 305 |
+
- 训练策略 (混合精度、标签平滑、调度器)
|
| 306 |
+
- 预训练微调方案 (NLLB + LoRA)
|
| 307 |
+
5. **实验**:
|
| 308 |
+
- 数据集 (WMT19 zh-en)
|
| 309 |
+
- 实验设置 (超参数表)
|
| 310 |
+
- 实验结果 (BLEU/COMET/chrF 表格)
|
| 311 |
+
- 消融分析 (各组件贡献)
|
| 312 |
+
- 训练效率对比 (Flash Attention 加速比)
|
| 313 |
+
6. **分析**:
|
| 314 |
+
- 注意力可视化
|
| 315 |
+
- 翻译案例分析
|
| 316 |
+
- 错误分析
|
| 317 |
+
7. **结论与展望**
|
| 318 |
+
|
| 319 |
+
#### 验收标准
|
| 320 |
+
- [ ] utils 模块功能正常
|
| 321 |
+
- [ ] 6 组实验能自动化运行
|
| 322 |
+
- [ ] 生成完整的可视化图表
|
| 323 |
+
- [ ] 实验报告完整,图表清晰
|
| 324 |
+
|
| 325 |
+
---
|
| 326 |
+
|
| 327 |
+
## 🗓️ 时间线
|
| 328 |
+
|
| 329 |
+
```
|
| 330 |
+
第 1 周: Person A (数据) + Person B (模型) + Person E (utils) 并行开发
|
| 331 |
+
↓
|
| 332 |
+
第 2 周: Person C (训练, 依赖 A+B) + Person D (评估, 依赖 B) 开始
|
| 333 |
+
Person A, B 完善和联调
|
| 334 |
+
↓
|
| 335 |
+
第 3 周: 全体联调 → scripts/train.py 整合
|
| 336 |
+
Person E 开始跑实验
|
| 337 |
+
↓
|
| 338 |
+
第 4 周: Person E 完成实验 + 报告
|
| 339 |
+
全体 Review + 优化
|
| 340 |
+
```
|
| 341 |
+
|
| 342 |
+
## 🔌 模块接口约定
|
| 343 |
+
|
| 344 |
+
### 分词器接口 (Person A 定义, Person B/C/D 使用)
|
| 345 |
+
```python
|
| 346 |
+
tokenizer.encode(text: str) -> list[int]
|
| 347 |
+
tokenizer.decode(ids: list[int]) -> str
|
| 348 |
+
tokenizer.vocab_size -> int
|
| 349 |
+
tokenizer.pad_id -> int
|
| 350 |
+
tokenizer.bos_id -> int
|
| 351 |
+
tokenizer.eos_id -> int
|
| 352 |
+
```
|
| 353 |
+
|
| 354 |
+
### 模型接口 (Person B 定义, Person C/D 使用)
|
| 355 |
+
```python
|
| 356 |
+
# 训练
|
| 357 |
+
logits = model(src_ids, tgt_input_ids, src_padding_mask, tgt_padding_mask)
|
| 358 |
+
# logits: [B, T, vocab_size]
|
| 359 |
+
|
| 360 |
+
# 推理
|
| 361 |
+
encoder_output = model.encode(src_ids, src_padding_mask)
|
| 362 |
+
next_logits = model.decode_step(tgt_input_ids, encoder_output, src_padding_mask)
|
| 363 |
+
```
|
| 364 |
+
|
| 365 |
+
### 数据 Batch 格式 (Person A 定义, Person C 使用)
|
| 366 |
+
```python
|
| 367 |
+
batch = {
|
| 368 |
+
"src_ids": Tensor[B, S],
|
| 369 |
+
"tgt_input_ids": Tensor[B, T],
|
| 370 |
+
"labels": Tensor[B, T],
|
| 371 |
+
"src_padding_mask": BoolTensor[B, S],
|
| 372 |
+
"tgt_padding_mask": BoolTensor[B, T],
|
| 373 |
+
}
|
| 374 |
+
```
|
| 375 |
+
|
| 376 |
+
### 评估接口 (Person D 定义, Person C/E 使用)
|
| 377 |
+
```python
|
| 378 |
+
evaluator = Evaluator(model, tokenizer, config)
|
| 379 |
+
results = evaluator.evaluate(dataloader)
|
| 380 |
+
# results: {"bleu": 25.6, "comet": 0.82, "chrf": 45.3, "ter": 55.2}
|
| 381 |
+
```
|
| 382 |
+
|
| 383 |
+
---
|
| 384 |
+
|
| 385 |
+
## ⚙️ 开发环境
|
| 386 |
+
|
| 387 |
+
```bash
|
| 388 |
+
# 1. 创建虚拟环境
|
| 389 |
+
conda create -n easytranslate python=3.11
|
| 390 |
+
conda activate easytranslate
|
| 391 |
+
|
| 392 |
+
# 2. 安装 PyTorch (CUDA 12.1)
|
| 393 |
+
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
|
| 394 |
+
|
| 395 |
+
# 3. 安装项目依赖
|
| 396 |
+
pip install -r requirements.txt
|
| 397 |
+
|
| 398 |
+
# 4. 安装项目 (开发模式)
|
| 399 |
+
pip install -e .
|
| 400 |
+
|
| 401 |
+
# 5. 运行测试
|
| 402 |
+
pytest tests/ -v
|
| 403 |
+
```
|
| 404 |
+
|
| 405 |
+
---
|
| 406 |
+
|
| 407 |
+
## ✅ 最终交付清单
|
| 408 |
+
|
| 409 |
+
- [ ] 代码: 所有 `TODO: Person X` 均已实现
|
| 410 |
+
- [ ] 测试: `pytest tests/` 全部通过
|
| 411 |
+
- [ ] 训练: 至少完成 Exp1 (基线) 和 Exp5 (NLLB+LoRA) 两组实验
|
| 412 |
+
- [ ] 评估: 在 WMT19 测试集上报告 BLEU / COMET 分数
|
| 413 |
+
- [ ] 报告: 完整实验报告 (含图表)
|
| 414 |
+
- [ ] 演示: 交互翻译 Demo 可运行
|
configs/deepspeed_config.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"fp16": {
|
| 3 |
+
"enabled": true,
|
| 4 |
+
"loss_scale": 0,
|
| 5 |
+
"loss_scale_window": 1000,
|
| 6 |
+
"initial_scale_power": 16,
|
| 7 |
+
"hysteresis": 2,
|
| 8 |
+
"min_loss_scale": 1
|
| 9 |
+
},
|
| 10 |
+
"zero_optimization": {
|
| 11 |
+
"stage": 2,
|
| 12 |
+
"offload_optimizer": {
|
| 13 |
+
"device": "cpu",
|
| 14 |
+
"pin_memory": true
|
| 15 |
+
},
|
| 16 |
+
"allgather_partitions": true,
|
| 17 |
+
"allgather_bucket_size": 2e8,
|
| 18 |
+
"overlap_comm": true,
|
| 19 |
+
"reduce_scatter": true,
|
| 20 |
+
"reduce_bucket_size": 2e8,
|
| 21 |
+
"contiguous_gradients": true
|
| 22 |
+
},
|
| 23 |
+
"gradient_accumulation_steps": 4,
|
| 24 |
+
"gradient_clipping": 1.0,
|
| 25 |
+
"train_batch_size": "auto",
|
| 26 |
+
"train_micro_batch_size_per_gpu": "auto",
|
| 27 |
+
"wall_clock_breakdown": false
|
| 28 |
+
}
|
configs/default_config.yaml
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ============================================================================
|
| 2 |
+
# EasyTranslate 默认配置文件
|
| 3 |
+
# 基于 Transformer 架构的英中翻译模型
|
| 4 |
+
# ============================================================================
|
| 5 |
+
|
| 6 |
+
# ---------- 模型配置 ----------
|
| 7 |
+
model:
|
| 8 |
+
# 模型类型: "transformer_scratch" | "finetune_nllb" | "finetune_mbart"
|
| 9 |
+
type: "transformer_scratch"
|
| 10 |
+
|
| 11 |
+
# === Transformer from scratch 配置 ===
|
| 12 |
+
transformer:
|
| 13 |
+
d_model: 512 # 模型维度
|
| 14 |
+
nhead: 8 # 多头注意力头数
|
| 15 |
+
num_encoder_layers: 6 # 编码器层数
|
| 16 |
+
num_decoder_layers: 6 # 解码器层数
|
| 17 |
+
dim_feedforward: 2048 # FFN 中间维度
|
| 18 |
+
dropout: 0.1 # Dropout 概率
|
| 19 |
+
activation: "gelu" # 激活函数: "relu" | "gelu"
|
| 20 |
+
max_seq_len: 512 # 最大序列长度
|
| 21 |
+
use_flash_attention: true # 是否使用 Flash Attention 2
|
| 22 |
+
use_rotary_embedding: true # 是否使用旋转位置编码 (RoPE)
|
| 23 |
+
pre_norm: true # Pre-LayerNorm (更稳定)
|
| 24 |
+
|
| 25 |
+
# === 预训练模型微调配置 ===
|
| 26 |
+
pretrained:
|
| 27 |
+
model_name: "facebook/nllb-200-distilled-600M" # HuggingFace 模型名
|
| 28 |
+
src_lang: "eng_Latn" # 源语言代码
|
| 29 |
+
tgt_lang: "zho_Hans" # 目标语言代码
|
| 30 |
+
use_lora: true # 是否使用 LoRA 微调
|
| 31 |
+
lora:
|
| 32 |
+
r: 16 # LoRA 秩
|
| 33 |
+
alpha: 32 # LoRA alpha
|
| 34 |
+
dropout: 0.05 # LoRA dropout
|
| 35 |
+
target_modules: # LoRA 目标模块
|
| 36 |
+
- "q_proj"
|
| 37 |
+
- "v_proj"
|
| 38 |
+
- "k_proj"
|
| 39 |
+
- "o_proj"
|
| 40 |
+
|
| 41 |
+
# ---------- 分词器配置 ----------
|
| 42 |
+
tokenizer:
|
| 43 |
+
# 分词器类型: "bpe" | "sentencepiece" | "pretrained"
|
| 44 |
+
type: "bpe"
|
| 45 |
+
vocab_size: 32000 # 词汇表大小
|
| 46 |
+
min_frequency: 2 # 最小词频
|
| 47 |
+
special_tokens:
|
| 48 |
+
pad: "<pad>"
|
| 49 |
+
unk: "<unk>"
|
| 50 |
+
bos: "<s>"
|
| 51 |
+
eos: "</s>"
|
| 52 |
+
max_length: 512 # 最大 token 长度
|
| 53 |
+
|
| 54 |
+
# ---------- 数据配置 ----------
|
| 55 |
+
data:
|
| 56 |
+
# 数据集: "wmt" | "opus" | "custom"
|
| 57 |
+
dataset_name: "wmt"
|
| 58 |
+
wmt:
|
| 59 |
+
year: "19" # WMT 年份
|
| 60 |
+
language_pair: "zh-en" # 语言对
|
| 61 |
+
opus:
|
| 62 |
+
subset: "UNPC" # OPUS 子集名
|
| 63 |
+
custom:
|
| 64 |
+
train_src: "data/train.en" # 自定义训练集源语言
|
| 65 |
+
train_tgt: "data/train.zh"
|
| 66 |
+
val_src: "data/val.en"
|
| 67 |
+
val_tgt: "data/val.zh"
|
| 68 |
+
test_src: "data/test.en"
|
| 69 |
+
test_tgt: "data/test.zh"
|
| 70 |
+
|
| 71 |
+
# 数据处理
|
| 72 |
+
preprocessing:
|
| 73 |
+
lowercase_src: false # 源语言是否小写化
|
| 74 |
+
remove_punctuation: false # 是否去除标点
|
| 75 |
+
max_src_len: 256 # 源语言最大长度
|
| 76 |
+
max_tgt_len: 256 # 目标语言最大长度
|
| 77 |
+
filter_by_length: true # 是否按长度过滤
|
| 78 |
+
length_ratio_threshold: 3.0 # 长度比阈值
|
| 79 |
+
|
| 80 |
+
# DataLoader
|
| 81 |
+
dataloader:
|
| 82 |
+
batch_size: 32
|
| 83 |
+
num_workers: 4
|
| 84 |
+
pin_memory: true
|
| 85 |
+
dynamic_batching: true # 动态 batch (按 token 数)
|
| 86 |
+
max_tokens_per_batch: 8192 # 动态 batch 最大 token 数
|
| 87 |
+
|
| 88 |
+
# ---------- 训练配置 ----------
|
| 89 |
+
training:
|
| 90 |
+
# 基础训练参数
|
| 91 |
+
epochs: 30
|
| 92 |
+
max_steps: -1 # -1 表示按 epoch 训练
|
| 93 |
+
gradient_accumulation_steps: 4
|
| 94 |
+
fp16: true # 混合精度训练
|
| 95 |
+
bf16: false # BF16 (A100+)
|
| 96 |
+
gradient_checkpointing: false
|
| 97 |
+
|
| 98 |
+
# 优化器
|
| 99 |
+
optimizer:
|
| 100 |
+
type: "adamw" # "adam" | "adamw" | "adafactor"
|
| 101 |
+
lr: 3.0e-4
|
| 102 |
+
weight_decay: 0.01
|
| 103 |
+
betas: [0.9, 0.98]
|
| 104 |
+
eps: 1.0e-8
|
| 105 |
+
|
| 106 |
+
# 学习率调度
|
| 107 |
+
scheduler:
|
| 108 |
+
type: "cosine_with_warmup" # "cosine_with_warmup" | "inverse_sqrt" | "linear"
|
| 109 |
+
warmup_steps: 4000
|
| 110 |
+
min_lr: 1.0e-6
|
| 111 |
+
|
| 112 |
+
# 正则化
|
| 113 |
+
regularization:
|
| 114 |
+
label_smoothing: 0.1 # 标签平滑
|
| 115 |
+
dropout: 0.1
|
| 116 |
+
|
| 117 |
+
# 检查点
|
| 118 |
+
checkpoint:
|
| 119 |
+
save_dir: "checkpoints/"
|
| 120 |
+
save_every_n_steps: 5000
|
| 121 |
+
save_best: true # 保存最佳模型
|
| 122 |
+
metric_for_best: "bleu" # 选择最佳模型的指标
|
| 123 |
+
max_checkpoints: 5 # 最大保存检查点数
|
| 124 |
+
|
| 125 |
+
# 早停
|
| 126 |
+
early_stopping:
|
| 127 |
+
enabled: true
|
| 128 |
+
patience: 5
|
| 129 |
+
min_delta: 0.1
|
| 130 |
+
|
| 131 |
+
# 分布式训练
|
| 132 |
+
distributed:
|
| 133 |
+
strategy: "ddp" # "ddp" | "fsdp" | "deepspeed"
|
| 134 |
+
deepspeed_config: "configs/deepspeed_config.json"
|
| 135 |
+
|
| 136 |
+
# ---------- 评估配置 ----------
|
| 137 |
+
evaluation:
|
| 138 |
+
# 评估指标
|
| 139 |
+
metrics:
|
| 140 |
+
- "bleu" # SacreBLEU
|
| 141 |
+
- "comet" # COMET (神经网络指标)
|
| 142 |
+
- "chrf" # chrF++
|
| 143 |
+
- "ter" # TER
|
| 144 |
+
|
| 145 |
+
# 解码策略
|
| 146 |
+
decoding:
|
| 147 |
+
strategy: "beam_search" # "greedy" | "beam_search" | "sampling"
|
| 148 |
+
beam_size: 5
|
| 149 |
+
length_penalty: 1.0
|
| 150 |
+
no_repeat_ngram_size: 3
|
| 151 |
+
max_decode_len: 256
|
| 152 |
+
|
| 153 |
+
# Sampling 参数
|
| 154 |
+
sampling:
|
| 155 |
+
temperature: 0.7
|
| 156 |
+
top_k: 50
|
| 157 |
+
top_p: 0.9
|
| 158 |
+
|
| 159 |
+
# 评估频率
|
| 160 |
+
eval_every_n_steps: 1000
|
| 161 |
+
eval_on_epoch_end: true
|
| 162 |
+
|
| 163 |
+
# ---------- 日志配置 ----------
|
| 164 |
+
logging:
|
| 165 |
+
# 日志工具: "wandb" | "tensorboard" | "both"
|
| 166 |
+
backend: "tensorboard"
|
| 167 |
+
project_name: "EasyTranslate"
|
| 168 |
+
log_every_n_steps: 100
|
| 169 |
+
log_dir: "logs/"
|
| 170 |
+
|
| 171 |
+
# ---------- 推理/部署配置 ----------
|
| 172 |
+
inference:
|
| 173 |
+
model_path: "checkpoints/best_model"
|
| 174 |
+
device: "cuda"
|
| 175 |
+
batch_size: 16
|
| 176 |
+
quantization: null # null | "int8" | "int4"
|
| 177 |
+
|
| 178 |
+
# ---------- 实验配置 ----------
|
| 179 |
+
experiment:
|
| 180 |
+
seed: 42
|
| 181 |
+
name: "baseline"
|
| 182 |
+
output_dir: "outputs/"
|
pytest.ini
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[pytest]
|
| 2 |
+
testpaths = tests
|
| 3 |
+
python_files = test_*.py
|
| 4 |
+
python_functions = test_*
|
| 5 |
+
python_classes = Test*
|
| 6 |
+
addopts = -v --tb=short
|
requirements.txt
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Core
|
| 2 |
+
torch>=2.1.0
|
| 3 |
+
transformers>=4.36.0
|
| 4 |
+
datasets>=2.16.0
|
| 5 |
+
tokenizers>=0.15.0
|
| 6 |
+
sentencepiece>=0.1.99
|
| 7 |
+
accelerate>=0.25.0
|
| 8 |
+
|
| 9 |
+
# Training
|
| 10 |
+
deepspeed>=0.12.0
|
| 11 |
+
bitsandbytes>=0.41.0
|
| 12 |
+
peft>=0.7.0
|
| 13 |
+
wandb>=0.16.0
|
| 14 |
+
tensorboard>=2.15.0
|
| 15 |
+
|
| 16 |
+
# Evaluation
|
| 17 |
+
sacrebleu>=2.4.0
|
| 18 |
+
unbabel-comet>=2.2.0
|
| 19 |
+
rouge-score>=0.1.2
|
| 20 |
+
nltk>=3.8.0
|
| 21 |
+
|
| 22 |
+
# Utilities
|
| 23 |
+
numpy>=1.24.0
|
| 24 |
+
pandas>=2.0.0
|
| 25 |
+
tqdm>=4.66.0
|
| 26 |
+
pyyaml>=6.0.0
|
| 27 |
+
omegaconf>=2.3.0
|
| 28 |
+
rich>=13.0.0
|
| 29 |
+
matplotlib>=3.8.0
|
| 30 |
+
seaborn>=0.13.0
|
| 31 |
+
|
| 32 |
+
# Serving (optional)
|
| 33 |
+
fastapi>=0.108.0
|
| 34 |
+
uvicorn>=0.25.0
|
| 35 |
+
gradio>=4.10.0
|
scripts/evaluate.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
评估入口脚本
|
| 3 |
+
|
| 4 |
+
使用方式:
|
| 5 |
+
# 在测试集上评估
|
| 6 |
+
python scripts/evaluate.py --config configs/default_config.yaml --checkpoint checkpoints/best_model.pt
|
| 7 |
+
|
| 8 |
+
# 指定解码策略
|
| 9 |
+
python scripts/evaluate.py --checkpoint checkpoints/best_model.pt evaluation.decoding.strategy=beam_search evaluation.decoding.beam_size=10
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
import argparse
|
| 13 |
+
import sys
|
| 14 |
+
from pathlib import Path
|
| 15 |
+
|
| 16 |
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "src"))
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def parse_args():
|
| 20 |
+
parser = argparse.ArgumentParser(description="EasyTranslate Evaluation")
|
| 21 |
+
parser.add_argument("--config", type=str, default="configs/default_config.yaml")
|
| 22 |
+
parser.add_argument("--checkpoint", type=str, required=True, help="模型检查点路径")
|
| 23 |
+
parser.add_argument("--output", type=str, default="outputs/evaluation_results.json", help="结果保存路径")
|
| 24 |
+
args, unknown = parser.parse_known_args()
|
| 25 |
+
return args, unknown
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def main():
|
| 29 |
+
"""
|
| 30 |
+
评估主流程。
|
| 31 |
+
|
| 32 |
+
TODO [Person D]:
|
| 33 |
+
1. 加载配置和检查点
|
| 34 |
+
2. 重建模型并加载权重
|
| 35 |
+
3. 加载测试数据
|
| 36 |
+
4. 构建 Evaluator
|
| 37 |
+
5. 运行评估
|
| 38 |
+
6. 打印和保存结果
|
| 39 |
+
"""
|
| 40 |
+
args, cli_overrides = parse_args()
|
| 41 |
+
|
| 42 |
+
print("=" * 60)
|
| 43 |
+
print(" EasyTranslate - Evaluation")
|
| 44 |
+
print("=" * 60)
|
| 45 |
+
|
| 46 |
+
raise NotImplementedError("TODO: Person D 实现评估主流程")
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
if __name__ == "__main__":
|
| 50 |
+
main()
|
scripts/run_experiments.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
实验运行脚本 — Person E 负责
|
| 3 |
+
|
| 4 |
+
自动化运行消融实验,比较不同配置的效果。
|
| 5 |
+
|
| 6 |
+
使用方式:
|
| 7 |
+
python scripts/run_experiments.py
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
import sys
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "src"))
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
# 实验配置列表
|
| 17 |
+
EXPERIMENTS = [
|
| 18 |
+
{
|
| 19 |
+
"name": "exp1_baseline_transformer",
|
| 20 |
+
"description": "基线: 从头训练标准 Transformer (6层, d=512)",
|
| 21 |
+
"overrides": {
|
| 22 |
+
"model.type": "transformer_scratch",
|
| 23 |
+
"model.transformer.num_encoder_layers": 6,
|
| 24 |
+
"model.transformer.num_decoder_layers": 6,
|
| 25 |
+
"model.transformer.d_model": 512,
|
| 26 |
+
"model.transformer.use_flash_attention": False,
|
| 27 |
+
"model.transformer.use_rotary_embedding": False,
|
| 28 |
+
},
|
| 29 |
+
},
|
| 30 |
+
{
|
| 31 |
+
"name": "exp2_transformer_rope",
|
| 32 |
+
"description": "消融: Transformer + RoPE 位置编码",
|
| 33 |
+
"overrides": {
|
| 34 |
+
"model.type": "transformer_scratch",
|
| 35 |
+
"model.transformer.use_rotary_embedding": True,
|
| 36 |
+
},
|
| 37 |
+
},
|
| 38 |
+
{
|
| 39 |
+
"name": "exp3_transformer_flash_attn",
|
| 40 |
+
"description": "消融: Transformer + Flash Attention 2",
|
| 41 |
+
"overrides": {
|
| 42 |
+
"model.type": "transformer_scratch",
|
| 43 |
+
"model.transformer.use_flash_attention": True,
|
| 44 |
+
},
|
| 45 |
+
},
|
| 46 |
+
{
|
| 47 |
+
"name": "exp4_transformer_full",
|
| 48 |
+
"description": "完整: Transformer + RoPE + Flash Attention",
|
| 49 |
+
"overrides": {
|
| 50 |
+
"model.type": "transformer_scratch",
|
| 51 |
+
"model.transformer.use_flash_attention": True,
|
| 52 |
+
"model.transformer.use_rotary_embedding": True,
|
| 53 |
+
},
|
| 54 |
+
},
|
| 55 |
+
{
|
| 56 |
+
"name": "exp5_nllb_lora",
|
| 57 |
+
"description": "预训练微调: NLLB-600M + LoRA",
|
| 58 |
+
"overrides": {
|
| 59 |
+
"model.type": "finetune_nllb",
|
| 60 |
+
"model.pretrained.use_lora": True,
|
| 61 |
+
"model.pretrained.lora.r": 16,
|
| 62 |
+
},
|
| 63 |
+
},
|
| 64 |
+
{
|
| 65 |
+
"name": "exp6_nllb_full_finetune",
|
| 66 |
+
"description": "预训练全量微调: NLLB-600M",
|
| 67 |
+
"overrides": {
|
| 68 |
+
"model.type": "finetune_nllb",
|
| 69 |
+
"model.pretrained.use_lora": False,
|
| 70 |
+
},
|
| 71 |
+
},
|
| 72 |
+
]
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def run_single_experiment(exp_config: dict):
|
| 76 |
+
"""
|
| 77 |
+
运行单个实验。
|
| 78 |
+
|
| 79 |
+
TODO [Person E]:
|
| 80 |
+
1. 从 base config 创建实验配置
|
| 81 |
+
2. 应用 overrides
|
| 82 |
+
3. 设置 experiment.name 和 output_dir
|
| 83 |
+
4. 调用训练流程
|
| 84 |
+
5. 在测试集上评估
|
| 85 |
+
6. 保存实验结果
|
| 86 |
+
"""
|
| 87 |
+
raise NotImplementedError("TODO: Person E 实现 run_single_experiment")
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def main():
|
| 91 |
+
"""
|
| 92 |
+
运行所有实验。
|
| 93 |
+
|
| 94 |
+
TODO [Person E]:
|
| 95 |
+
1. 遍历 EXPERIMENTS 列表
|
| 96 |
+
2. 对每个实验调用 run_single_experiment
|
| 97 |
+
3. 收集所有实验结果
|
| 98 |
+
4. 生成对比表格
|
| 99 |
+
5. 保存汇总报告
|
| 100 |
+
"""
|
| 101 |
+
print("=" * 60)
|
| 102 |
+
print(" EasyTranslate - Experiment Runner")
|
| 103 |
+
print("=" * 60)
|
| 104 |
+
|
| 105 |
+
for exp in EXPERIMENTS:
|
| 106 |
+
print(f"\n>>> Running: {exp['name']} - {exp['description']}")
|
| 107 |
+
# run_single_experiment(exp)
|
| 108 |
+
|
| 109 |
+
raise NotImplementedError("TODO: Person E 实现实验运行主流程")
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
if __name__ == "__main__":
|
| 113 |
+
main()
|
scripts/train.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
训练入口脚本
|
| 3 |
+
|
| 4 |
+
使用方式:
|
| 5 |
+
# 从头训练 Transformer
|
| 6 |
+
python scripts/train.py --config configs/default_config.yaml
|
| 7 |
+
|
| 8 |
+
# 微调 NLLB
|
| 9 |
+
python scripts/train.py --config configs/default_config.yaml model.type=finetune_nllb
|
| 10 |
+
|
| 11 |
+
# 命令行覆盖参数
|
| 12 |
+
python scripts/train.py --config configs/default_config.yaml training.optimizer.lr=1e-4
|
| 13 |
+
|
| 14 |
+
# 分布式训练
|
| 15 |
+
torchrun --nproc_per_node=4 scripts/train.py --config configs/default_config.yaml
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
import argparse
|
| 19 |
+
import sys
|
| 20 |
+
from pathlib import Path
|
| 21 |
+
|
| 22 |
+
# 将 src 目录加入 Python path
|
| 23 |
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "src"))
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def parse_args():
|
| 27 |
+
parser = argparse.ArgumentParser(description="EasyTranslate Training")
|
| 28 |
+
parser.add_argument("--config", type=str, default="configs/default_config.yaml", help="配置文件路径")
|
| 29 |
+
parser.add_argument("--resume", type=str, default=None, help="检查点路径 (断点续训)")
|
| 30 |
+
args, unknown = parser.parse_known_args()
|
| 31 |
+
return args, unknown
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def main():
|
| 35 |
+
"""
|
| 36 |
+
训练主流程。
|
| 37 |
+
|
| 38 |
+
TODO [整合阶段 - 所有人协作]:
|
| 39 |
+
1. 解析命令行参数
|
| 40 |
+
2. 加载配置 (config_from_cli)
|
| 41 |
+
3. set_seed
|
| 42 |
+
4. setup_logging
|
| 43 |
+
5. 根据 model.type 选择训练模式:
|
| 44 |
+
|
| 45 |
+
A) model.type == "transformer_scratch":
|
| 46 |
+
a. 加载数据集 (load_wmt_dataset / load_opus_dataset)
|
| 47 |
+
b. 预处理 (preprocess_pipeline)
|
| 48 |
+
c. 训练分词器 (train_bpe_tokenizer) 或加载已有分词器
|
| 49 |
+
d. 构建 TranslationDataset + DataLoader
|
| 50 |
+
e. 构建 TransformerTranslationModel
|
| 51 |
+
f. 构建 Trainer 并开始训练
|
| 52 |
+
|
| 53 |
+
B) model.type == "finetune_nllb" / "finetune_mbart":
|
| 54 |
+
a. 加载预训练模型和分词器 (load_pretrained_model)
|
| 55 |
+
b. 配置 LoRA (setup_lora)
|
| 56 |
+
c. 加载数据集,使用预训练分词器处理
|
| 57 |
+
d. 构建 Trainer 并开始训练
|
| 58 |
+
|
| 59 |
+
6. 训练完成后保存最终模型
|
| 60 |
+
7. 在测试集上评估
|
| 61 |
+
"""
|
| 62 |
+
args, cli_overrides = parse_args()
|
| 63 |
+
|
| 64 |
+
print("=" * 60)
|
| 65 |
+
print(" EasyTranslate - English to Chinese Translation")
|
| 66 |
+
print("=" * 60)
|
| 67 |
+
|
| 68 |
+
# TODO: 实现训练主流程
|
| 69 |
+
raise NotImplementedError(
|
| 70 |
+
"TODO: 整合阶段实现训练主流程\n"
|
| 71 |
+
"请在所有模块完成后,协作完成此脚本"
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
if __name__ == "__main__":
|
| 76 |
+
main()
|
scripts/translate.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
交互式翻译推理脚本
|
| 3 |
+
|
| 4 |
+
使用方式:
|
| 5 |
+
# 命令行交互翻译
|
| 6 |
+
python scripts/translate.py --checkpoint checkpoints/best_model.pt
|
| 7 |
+
|
| 8 |
+
# 翻译文件
|
| 9 |
+
python scripts/translate.py --checkpoint checkpoints/best_model.pt --input input.txt --output output.txt
|
| 10 |
+
|
| 11 |
+
# 启动 Gradio Web UI
|
| 12 |
+
python scripts/translate.py --checkpoint checkpoints/best_model.pt --web
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
import argparse
|
| 16 |
+
import sys
|
| 17 |
+
from pathlib import Path
|
| 18 |
+
|
| 19 |
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "src"))
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def parse_args():
|
| 23 |
+
parser = argparse.ArgumentParser(description="EasyTranslate Inference")
|
| 24 |
+
parser.add_argument("--config", type=str, default="configs/default_config.yaml")
|
| 25 |
+
parser.add_argument("--checkpoint", type=str, required=True)
|
| 26 |
+
parser.add_argument("--input", type=str, default=None, help="输入文件路径")
|
| 27 |
+
parser.add_argument("--output", type=str, default=None, help="输出文件路径")
|
| 28 |
+
parser.add_argument("--web", action="store_true", help="启动 Gradio Web UI")
|
| 29 |
+
return parser.parse_args()
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def interactive_translate(evaluator):
|
| 33 |
+
"""
|
| 34 |
+
命令行交互翻译。
|
| 35 |
+
|
| 36 |
+
TODO [Person D]:
|
| 37 |
+
1. 循环读取用户输入
|
| 38 |
+
2. 调用 evaluator.translate_single()
|
| 39 |
+
3. 打印翻译结果
|
| 40 |
+
4. 输入 'quit' 退出
|
| 41 |
+
"""
|
| 42 |
+
raise NotImplementedError("TODO: Person D 实现 interactive_translate")
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def translate_file(evaluator, input_path: str, output_path: str):
|
| 46 |
+
"""
|
| 47 |
+
文件翻译。
|
| 48 |
+
|
| 49 |
+
TODO [Person D]:
|
| 50 |
+
1. 读取输入文件 (一行一句)
|
| 51 |
+
2. 批量翻译
|
| 52 |
+
3. 将结果写入输出文件
|
| 53 |
+
"""
|
| 54 |
+
raise NotImplementedError("TODO: Person D 实现 translate_file")
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def launch_web_ui(evaluator):
|
| 58 |
+
"""
|
| 59 |
+
启动 Gradio Web UI。
|
| 60 |
+
|
| 61 |
+
TODO [Person D]:
|
| 62 |
+
1. 创建 Gradio Interface
|
| 63 |
+
2. 输入: 英文文本框
|
| 64 |
+
3. 输出: 中文翻译结果
|
| 65 |
+
4. 调用 evaluator.translate_single()
|
| 66 |
+
"""
|
| 67 |
+
raise NotImplementedError("TODO: Person D 实现 launch_web_ui")
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def main():
|
| 71 |
+
args = parse_args()
|
| 72 |
+
|
| 73 |
+
print("=" * 60)
|
| 74 |
+
print(" EasyTranslate - Translation")
|
| 75 |
+
print("=" * 60)
|
| 76 |
+
|
| 77 |
+
# TODO: 加载模型、构建 evaluator,然后根据参数选择模式
|
| 78 |
+
raise NotImplementedError("TODO: 实现推理主流程")
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
if __name__ == "__main__":
|
| 82 |
+
main()
|
scripts/visualize.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
可视化与分析脚本 — Person E 负责
|
| 3 |
+
|
| 4 |
+
功能:
|
| 5 |
+
1. 训练曲线绘制 (loss, BLEU, learning rate)
|
| 6 |
+
2. 实验结果对比图
|
| 7 |
+
3. 注意力权重可视化
|
| 8 |
+
4. 翻译样例展示
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
import sys
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
|
| 14 |
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "src"))
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def plot_training_curves(log_dir: str, output_path: str = "outputs/training_curves.png"):
|
| 18 |
+
"""
|
| 19 |
+
绘制训练曲线。
|
| 20 |
+
|
| 21 |
+
TODO [Person E]:
|
| 22 |
+
1. 从 TensorBoard 日志或 CSV 读取训练数据
|
| 23 |
+
2. 绘制子图:
|
| 24 |
+
- Train Loss vs Steps
|
| 25 |
+
- Val Loss vs Steps
|
| 26 |
+
- BLEU vs Epochs
|
| 27 |
+
- Learning Rate vs Steps
|
| 28 |
+
3. 保存图片
|
| 29 |
+
"""
|
| 30 |
+
raise NotImplementedError("TODO: Person E 实现 plot_training_curves")
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def plot_experiment_comparison(results_dir: str, output_path: str = "outputs/experiment_comparison.png"):
|
| 34 |
+
"""
|
| 35 |
+
绘制实验对比图。
|
| 36 |
+
|
| 37 |
+
TODO [Person E]:
|
| 38 |
+
1. 读取所有实验的评估结果
|
| 39 |
+
2. 绘制柱状图: BLEU / COMET / chrF 对比
|
| 40 |
+
3. 绘制表格: 所有指标汇总
|
| 41 |
+
4. 保存图片
|
| 42 |
+
"""
|
| 43 |
+
raise NotImplementedError("TODO: Person E 实现 plot_experiment_comparison")
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def visualize_attention(
|
| 47 |
+
model,
|
| 48 |
+
src_text: str,
|
| 49 |
+
tgt_text: str,
|
| 50 |
+
tokenizer,
|
| 51 |
+
output_path: str = "outputs/attention_map.png",
|
| 52 |
+
):
|
| 53 |
+
"""
|
| 54 |
+
注意力权重可视化。
|
| 55 |
+
|
| 56 |
+
TODO [Person E]:
|
| 57 |
+
1. 获取模型的 encoder self-attention 和 cross-attention 权重
|
| 58 |
+
2. 绘制热力图 (matplotlib / seaborn)
|
| 59 |
+
3. x 轴: 源语言 tokens, y 轴: 目标语言 tokens
|
| 60 |
+
4. 支持多头注意力的分别可视化和平均可视化
|
| 61 |
+
"""
|
| 62 |
+
raise NotImplementedError("TODO: Person E 实现 visualize_attention")
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def generate_translation_examples(
|
| 66 |
+
evaluator,
|
| 67 |
+
test_pairs: list[tuple[str, str]],
|
| 68 |
+
output_path: str = "outputs/translation_examples.md",
|
| 69 |
+
):
|
| 70 |
+
"""
|
| 71 |
+
生成翻译样例展示。
|
| 72 |
+
|
| 73 |
+
TODO [Person E]:
|
| 74 |
+
1. 翻译测试样例
|
| 75 |
+
2. 生成 Markdown 格式的对比表格:
|
| 76 |
+
| 源文 (英) | 参考翻译 (中) | 模型翻译 (中) | BLEU |
|
| 77 |
+
3. 包含好的和差的翻译案例
|
| 78 |
+
4. 保存为 Markdown 文件
|
| 79 |
+
"""
|
| 80 |
+
raise NotImplementedError("TODO: Person E 实现 generate_translation_examples")
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
if __name__ == "__main__":
|
| 84 |
+
print("请指定要运行的可视化任务")
|
| 85 |
+
print(" python scripts/visualize.py --task training_curves --log_dir logs/")
|
| 86 |
+
print(" python scripts/visualize.py --task comparison --results_dir outputs/")
|
setup.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from setuptools import setup, find_packages
|
| 2 |
+
|
| 3 |
+
setup(
|
| 4 |
+
name="easytranslate",
|
| 5 |
+
version="0.1.0",
|
| 6 |
+
description="Transformer-based English-to-Chinese Translation Model",
|
| 7 |
+
packages=find_packages(where="src"),
|
| 8 |
+
package_dir={"": "src"},
|
| 9 |
+
python_requires=">=3.10",
|
| 10 |
+
install_requires=[
|
| 11 |
+
"torch>=2.1.0",
|
| 12 |
+
"transformers>=4.36.0",
|
| 13 |
+
"datasets>=2.16.0",
|
| 14 |
+
"sentencepiece>=0.1.99",
|
| 15 |
+
"accelerate>=0.25.0",
|
| 16 |
+
"sacrebleu>=2.4.0",
|
| 17 |
+
"omegaconf>=2.3.0",
|
| 18 |
+
"rich>=13.0.0",
|
| 19 |
+
"tqdm>=4.66.0",
|
| 20 |
+
],
|
| 21 |
+
)
|
src/easytranslate/__init__.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""EasyTranslate: Transformer-based English-to-Chinese Translation."""
|
| 2 |
+
|
| 3 |
+
__version__ = "0.1.0"
|
src/easytranslate/evaluation/__init__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""评估与推理模块 (Person D 负责)"""
|
| 2 |
+
|
| 3 |
+
from easytranslate.evaluation.metrics import compute_bleu, compute_comet, compute_chrf, compute_all_metrics
|
| 4 |
+
from easytranslate.evaluation.decoding import greedy_decode, beam_search_decode, sample_decode
|
| 5 |
+
from easytranslate.evaluation.evaluator import Evaluator
|
| 6 |
+
|
| 7 |
+
__all__ = [
|
| 8 |
+
"compute_bleu",
|
| 9 |
+
"compute_comet",
|
| 10 |
+
"compute_chrf",
|
| 11 |
+
"compute_all_metrics",
|
| 12 |
+
"greedy_decode",
|
| 13 |
+
"beam_search_decode",
|
| 14 |
+
"sample_decode",
|
| 15 |
+
"Evaluator",
|
| 16 |
+
]
|
src/easytranslate/evaluation/decoding.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
解码策略模块 — Person D 负责实现
|
| 3 |
+
|
| 4 |
+
功能要求:
|
| 5 |
+
1. greedy_decode: 贪心解码
|
| 6 |
+
2. beam_search_decode: 束搜索解码
|
| 7 |
+
3. sample_decode: 采样解码 (temperature, top-k, top-p)
|
| 8 |
+
|
| 9 |
+
技术要点:
|
| 10 |
+
- Beam Search 是翻译任务最常用的解码策略
|
| 11 |
+
- 需要高效处理批量解码
|
| 12 |
+
- 支持长度惩罚 (length penalty) 和重复惩罚 (no_repeat_ngram)
|
| 13 |
+
- 对于预训练模型,可以直接使用 model.generate()
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
from __future__ import annotations
|
| 17 |
+
|
| 18 |
+
import logging
|
| 19 |
+
from typing import Optional
|
| 20 |
+
|
| 21 |
+
import torch
|
| 22 |
+
import torch.nn as nn
|
| 23 |
+
import torch.nn.functional as F
|
| 24 |
+
|
| 25 |
+
logger = logging.getLogger(__name__)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
@torch.no_grad()
|
| 29 |
+
def greedy_decode(
|
| 30 |
+
model: nn.Module,
|
| 31 |
+
src_ids: torch.Tensor, # [B, S]
|
| 32 |
+
src_padding_mask: torch.BoolTensor, # [B, S]
|
| 33 |
+
bos_id: int,
|
| 34 |
+
eos_id: int,
|
| 35 |
+
max_len: int = 256,
|
| 36 |
+
) -> torch.Tensor:
|
| 37 |
+
"""
|
| 38 |
+
贪心解码。
|
| 39 |
+
|
| 40 |
+
TODO [Person D]: 实现以下逻辑:
|
| 41 |
+
1. encoder_output = model.encode(src_ids, src_padding_mask)
|
| 42 |
+
2. 初始化 decoder input: [B, 1] 全为 bos_id
|
| 43 |
+
3. for step in range(max_len):
|
| 44 |
+
a. logits = model.decode_step(decoder_input, encoder_output, src_padding_mask)
|
| 45 |
+
b. next_token = logits.argmax(dim=-1)
|
| 46 |
+
c. decoder_input = concat(decoder_input, next_token)
|
| 47 |
+
d. 如果所有序列都生成了 eos_id,则提前终止
|
| 48 |
+
4. 返回生成的 token ids [B, T]
|
| 49 |
+
"""
|
| 50 |
+
raise NotImplementedError("TODO: Person D 实现 greedy_decode")
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
@torch.no_grad()
|
| 54 |
+
def beam_search_decode(
|
| 55 |
+
model: nn.Module,
|
| 56 |
+
src_ids: torch.Tensor, # [B, S]
|
| 57 |
+
src_padding_mask: torch.BoolTensor,
|
| 58 |
+
bos_id: int,
|
| 59 |
+
eos_id: int,
|
| 60 |
+
beam_size: int = 5,
|
| 61 |
+
max_len: int = 256,
|
| 62 |
+
length_penalty: float = 1.0,
|
| 63 |
+
no_repeat_ngram_size: int = 0,
|
| 64 |
+
) -> torch.Tensor:
|
| 65 |
+
"""
|
| 66 |
+
束搜索解码。
|
| 67 |
+
|
| 68 |
+
TODO [Person D]: 实现以下逻辑:
|
| 69 |
+
1. encoder_output = model.encode(src_ids, src_padding_mask)
|
| 70 |
+
2. 将 encoder_output 扩展为 beam_size 份: [B*beam, S, D]
|
| 71 |
+
3. 初始化 beam:
|
| 72 |
+
- beam_scores: [B, beam_size] 初始为 0
|
| 73 |
+
- beam_tokens: [B, beam_size, 1] 初始为 bos_id
|
| 74 |
+
4. for step in range(max_len):
|
| 75 |
+
a. 对每个 beam 计算 logits
|
| 76 |
+
b. log_probs = log_softmax(logits)
|
| 77 |
+
c. (可选) 应用 no_repeat_ngram 约束
|
| 78 |
+
d. scores = beam_scores + log_probs
|
| 79 |
+
e. 选择 top-k candidates (k = beam_size)
|
| 80 |
+
f. 更新 beam_tokens 和 beam_scores
|
| 81 |
+
g. 将已完成的 beam 移到 finished pool
|
| 82 |
+
5. 对 finished beams 应用 length_penalty:
|
| 83 |
+
score = score / (length ^ length_penalty)
|
| 84 |
+
6. 选择得分最高的序列
|
| 85 |
+
7. 返回最佳翻译 [B, T]
|
| 86 |
+
|
| 87 |
+
这是翻译任务最关键的解码算法,请仔细实现。
|
| 88 |
+
"""
|
| 89 |
+
raise NotImplementedError("TODO: Person D 实现 beam_search_decode")
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
@torch.no_grad()
|
| 93 |
+
def sample_decode(
|
| 94 |
+
model: nn.Module,
|
| 95 |
+
src_ids: torch.Tensor,
|
| 96 |
+
src_padding_mask: torch.BoolTensor,
|
| 97 |
+
bos_id: int,
|
| 98 |
+
eos_id: int,
|
| 99 |
+
max_len: int = 256,
|
| 100 |
+
temperature: float = 1.0,
|
| 101 |
+
top_k: int = 0,
|
| 102 |
+
top_p: float = 1.0,
|
| 103 |
+
) -> torch.Tensor:
|
| 104 |
+
"""
|
| 105 |
+
采样解码 (支持 temperature, top-k, top-p/nucleus sampling)。
|
| 106 |
+
|
| 107 |
+
TODO [Person D]: 实现以下逻辑:
|
| 108 |
+
1. 与贪心解码类似,但每步采样而非取 argmax
|
| 109 |
+
2. 应用 temperature: logits = logits / temperature
|
| 110 |
+
3. 应用 top-k: 只保留概率最高的 k 个 token
|
| 111 |
+
4. 应用 top-p (nucleus): 只保留累积概率达到 p 的 token
|
| 112 |
+
5. 从过滤后的分布中采样: torch.multinomial
|
| 113 |
+
"""
|
| 114 |
+
raise NotImplementedError("TODO: Person D 实现 sample_decode")
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def _apply_no_repeat_ngram(
|
| 118 |
+
logits: torch.Tensor,
|
| 119 |
+
generated_tokens: torch.Tensor,
|
| 120 |
+
ngram_size: int,
|
| 121 |
+
) -> torch.Tensor:
|
| 122 |
+
"""
|
| 123 |
+
防止生成重复的 n-gram。
|
| 124 |
+
|
| 125 |
+
TODO [Person D]:
|
| 126 |
+
1. 从 generated_tokens 中提取所有已出现的 (ngram_size-1)-gram
|
| 127 |
+
2. 对于每个可能导致重复 ngram 的 next token,将其 logits 设为 -inf
|
| 128 |
+
"""
|
| 129 |
+
raise NotImplementedError("TODO: Person D 实现 _apply_no_repeat_ngram")
|
src/easytranslate/evaluation/evaluator.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
评估器模块 — Person D 负责实现
|
| 3 |
+
|
| 4 |
+
功能要求:
|
| 5 |
+
将解码和评估指标整合为统一的评估接口。
|
| 6 |
+
|
| 7 |
+
使用方法:
|
| 8 |
+
evaluator = Evaluator(model, tokenizer, config)
|
| 9 |
+
results = evaluator.evaluate(test_loader)
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
from __future__ import annotations
|
| 13 |
+
|
| 14 |
+
import logging
|
| 15 |
+
from typing import Optional
|
| 16 |
+
|
| 17 |
+
import torch
|
| 18 |
+
import torch.nn as nn
|
| 19 |
+
from torch.utils.data import DataLoader
|
| 20 |
+
from tqdm import tqdm
|
| 21 |
+
|
| 22 |
+
from easytranslate.evaluation.metrics import compute_all_metrics
|
| 23 |
+
from easytranslate.evaluation.decoding import greedy_decode, beam_search_decode, sample_decode
|
| 24 |
+
|
| 25 |
+
logger = logging.getLogger(__name__)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
class Evaluator:
|
| 29 |
+
"""
|
| 30 |
+
翻译模型评估器。
|
| 31 |
+
|
| 32 |
+
TODO [Person D]: 实现以下方法。
|
| 33 |
+
"""
|
| 34 |
+
|
| 35 |
+
def __init__(self, model: nn.Module, tokenizer, config: dict):
|
| 36 |
+
"""
|
| 37 |
+
TODO [Person D]:
|
| 38 |
+
1. 保存 model, tokenizer, config
|
| 39 |
+
2. 从 config 读取解码策略和评估指标配置
|
| 40 |
+
3. 根据策略选择解码函数
|
| 41 |
+
"""
|
| 42 |
+
raise NotImplementedError("TODO: Person D 实现 Evaluator.__init__")
|
| 43 |
+
|
| 44 |
+
def evaluate(
|
| 45 |
+
self,
|
| 46 |
+
dataloader: DataLoader,
|
| 47 |
+
src_texts: Optional[list[str]] = None,
|
| 48 |
+
ref_texts: Optional[list[str]] = None,
|
| 49 |
+
) -> dict:
|
| 50 |
+
"""
|
| 51 |
+
在给定数据上进行评估。
|
| 52 |
+
|
| 53 |
+
TODO [Person D]: 实现以下逻辑:
|
| 54 |
+
1. model.eval()
|
| 55 |
+
2. 遍历 dataloader,使用选定的解码策略生成翻译
|
| 56 |
+
3. 将生成的 token ids 解码为文本
|
| 57 |
+
4. 调用 compute_all_metrics 计算指标
|
| 58 |
+
5. 返回评估结果 dict
|
| 59 |
+
"""
|
| 60 |
+
raise NotImplementedError("TODO: Person D 实现 evaluate")
|
| 61 |
+
|
| 62 |
+
def translate(self, texts: list[str]) -> list[str]:
|
| 63 |
+
"""
|
| 64 |
+
翻译一批文本。
|
| 65 |
+
|
| 66 |
+
TODO [Person D]:
|
| 67 |
+
1. tokenize 输入文本
|
| 68 |
+
2. 调用解码函数生成翻译
|
| 69 |
+
3. 解码为文本
|
| 70 |
+
4. 返回翻译结果列表
|
| 71 |
+
"""
|
| 72 |
+
raise NotImplementedError("TODO: Person D 实现 translate")
|
| 73 |
+
|
| 74 |
+
def translate_single(self, text: str) -> str:
|
| 75 |
+
"""翻译单条文本。"""
|
| 76 |
+
return self.translate([text])[0]
|
src/easytranslate/evaluation/metrics.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
评估指标模块 — Person D 负责实现
|
| 3 |
+
|
| 4 |
+
功能要求:
|
| 5 |
+
1. compute_bleu: 计算 SacreBLEU 分数
|
| 6 |
+
2. compute_comet: 计算 COMET 分数 (神经网络评估指标)
|
| 7 |
+
3. compute_chrf: 计算 chrF++ 分数
|
| 8 |
+
4. compute_ter: 计算 TER (Translation Edit Rate)
|
| 9 |
+
5. compute_all_metrics: 计算所有指标
|
| 10 |
+
|
| 11 |
+
技术要点:
|
| 12 |
+
- SacreBLEU: 标准化的 BLEU 实现,结果可复现
|
| 13 |
+
- COMET: 基于预训练语言模型的评估指标,与人类评价相关性最高
|
| 14 |
+
- chrF++: 基于字符 n-gram 的 F-score,对中文尤其有用
|
| 15 |
+
- TER: 编辑距离,衡量翻译后编辑量
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
from __future__ import annotations
|
| 19 |
+
|
| 20 |
+
import logging
|
| 21 |
+
from typing import Optional
|
| 22 |
+
|
| 23 |
+
logger = logging.getLogger(__name__)
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def compute_bleu(
|
| 27 |
+
hypotheses: list[str],
|
| 28 |
+
references: list[str],
|
| 29 |
+
tokenize: str = "zh",
|
| 30 |
+
) -> dict:
|
| 31 |
+
"""
|
| 32 |
+
计算 SacreBLEU 分数。
|
| 33 |
+
|
| 34 |
+
TODO [Person D]: 实现以下逻辑:
|
| 35 |
+
1. 使用 sacrebleu.corpus_bleu(hypotheses, [references], tokenize=tokenize)
|
| 36 |
+
2. tokenize="zh" 对中文进行字符级别分词
|
| 37 |
+
3. 返回 {"bleu": score, "bleu_1": ..., "bleu_2": ..., "bleu_3": ..., "bleu_4": ..., "bp": ...}
|
| 38 |
+
|
| 39 |
+
注意: references 需要包装为 list of list (支持多参考)
|
| 40 |
+
"""
|
| 41 |
+
raise NotImplementedError("TODO: Person D 实现 compute_bleu")
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def compute_comet(
|
| 45 |
+
sources: list[str],
|
| 46 |
+
hypotheses: list[str],
|
| 47 |
+
references: list[str],
|
| 48 |
+
model_name: str = "Unbabel/wmt22-comet-da",
|
| 49 |
+
batch_size: int = 16,
|
| 50 |
+
gpus: int = 1,
|
| 51 |
+
) -> dict:
|
| 52 |
+
"""
|
| 53 |
+
计算 COMET 分数。
|
| 54 |
+
|
| 55 |
+
TODO [Person D]: 实现以下逻辑:
|
| 56 |
+
1. 加载 COMET 模型: comet.download_model(model_name)
|
| 57 |
+
2. 构建输入数据: [{"src": s, "mt": h, "ref": r} for s, h, r in zip(...)]
|
| 58 |
+
3. 调用 model.predict(data, batch_size, gpus)
|
| 59 |
+
4. 返回 {"comet": system_score, "comet_scores": segment_scores}
|
| 60 |
+
|
| 61 |
+
COMET 需要源语言、翻译结果和参考翻译三者。
|
| 62 |
+
"""
|
| 63 |
+
raise NotImplementedError("TODO: Person D 实现 compute_comet")
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def compute_chrf(
|
| 67 |
+
hypotheses: list[str],
|
| 68 |
+
references: list[str],
|
| 69 |
+
) -> dict:
|
| 70 |
+
"""
|
| 71 |
+
计算 chrF++ 分数。
|
| 72 |
+
|
| 73 |
+
TODO [Person D]:
|
| 74 |
+
1. 使用 sacrebleu.corpus_chrf(hypotheses, [references])
|
| 75 |
+
2. 返回 {"chrf": score}
|
| 76 |
+
"""
|
| 77 |
+
raise NotImplementedError("TODO: Person D 实现 compute_chrf")
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def compute_ter(
|
| 81 |
+
hypotheses: list[str],
|
| 82 |
+
references: list[str],
|
| 83 |
+
) -> dict:
|
| 84 |
+
"""
|
| 85 |
+
计算 TER 分数。
|
| 86 |
+
|
| 87 |
+
TODO [Person D]:
|
| 88 |
+
1. 使用 sacrebleu.corpus_ter(hypotheses, [references])
|
| 89 |
+
2. 返回 {"ter": score}
|
| 90 |
+
"""
|
| 91 |
+
raise NotImplementedError("TODO: Person D 实现 compute_ter")
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
def compute_all_metrics(
|
| 95 |
+
sources: list[str],
|
| 96 |
+
hypotheses: list[str],
|
| 97 |
+
references: list[str],
|
| 98 |
+
metrics: list[str] = ["bleu", "comet", "chrf", "ter"],
|
| 99 |
+
) -> dict:
|
| 100 |
+
"""
|
| 101 |
+
计算所有指定的评估指标。
|
| 102 |
+
|
| 103 |
+
TODO [Person D]:
|
| 104 |
+
1. 遍历 metrics 列表
|
| 105 |
+
2. 调用对应的计算函数
|
| 106 |
+
3. 合并结果并返回
|
| 107 |
+
4. 记录每个指标的计算时间
|
| 108 |
+
"""
|
| 109 |
+
raise NotImplementedError("TODO: Person D 实现 compute_all_metrics")
|
src/easytranslate/model/__init__.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""模型架构模块 (Person B 负责)"""
|
| 2 |
+
|
| 3 |
+
from easytranslate.model.transformer import TransformerTranslationModel
|
| 4 |
+
from easytranslate.model.encoder import TransformerEncoder
|
| 5 |
+
from easytranslate.model.decoder import TransformerDecoder
|
| 6 |
+
from easytranslate.model.attention import MultiHeadAttention, FlashMultiHeadAttention
|
| 7 |
+
from easytranslate.model.positional import SinusoidalPositionalEncoding, RotaryPositionalEmbedding
|
| 8 |
+
from easytranslate.model.finetune import load_pretrained_model, setup_lora
|
| 9 |
+
|
| 10 |
+
__all__ = [
|
| 11 |
+
"TransformerTranslationModel",
|
| 12 |
+
"TransformerEncoder",
|
| 13 |
+
"TransformerDecoder",
|
| 14 |
+
"MultiHeadAttention",
|
| 15 |
+
"FlashMultiHeadAttention",
|
| 16 |
+
"SinusoidalPositionalEncoding",
|
| 17 |
+
"RotaryPositionalEmbedding",
|
| 18 |
+
"load_pretrained_model",
|
| 19 |
+
"setup_lora",
|
| 20 |
+
]
|
src/easytranslate/model/attention.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
注意力机制模块 — Person B 负责实现
|
| 3 |
+
|
| 4 |
+
包含:
|
| 5 |
+
1. MultiHeadAttention: 标准多头注意力 (支持 RoPE)
|
| 6 |
+
2. FlashMultiHeadAttention: Flash Attention 2 加速版本
|
| 7 |
+
|
| 8 |
+
技术要点:
|
| 9 |
+
- Scaled Dot-Product Attention
|
| 10 |
+
- 支持 key_padding_mask 和 attn_mask
|
| 11 |
+
- Flash Attention 2 使用 torch.nn.functional.scaled_dot_product_attention
|
| 12 |
+
- RoPE 旋转位置编码集成
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
from __future__ import annotations
|
| 16 |
+
|
| 17 |
+
import math
|
| 18 |
+
from typing import Optional
|
| 19 |
+
|
| 20 |
+
import torch
|
| 21 |
+
import torch.nn as nn
|
| 22 |
+
import torch.nn.functional as F
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class MultiHeadAttention(nn.Module):
|
| 26 |
+
"""
|
| 27 |
+
标准多头注意力机制。
|
| 28 |
+
|
| 29 |
+
TODO [Person B]: 实现以下内容:
|
| 30 |
+
|
| 31 |
+
__init__:
|
| 32 |
+
1. Q, K, V 线性投影: nn.Linear(d_model, d_model)
|
| 33 |
+
2. 输出投影: nn.Linear(d_model, d_model)
|
| 34 |
+
3. Dropout
|
| 35 |
+
|
| 36 |
+
forward(query, key, value, key_padding_mask=None, attn_mask=None):
|
| 37 |
+
1. 线性投影 Q, K, V
|
| 38 |
+
2. reshape 为 [B, nhead, L, d_k]
|
| 39 |
+
3. (可选) 应用 RoPE 旋转位置编码
|
| 40 |
+
4. 计算 attention scores: QK^T / sqrt(d_k)
|
| 41 |
+
5. 应用 masks (padding mask + causal mask)
|
| 42 |
+
6. Softmax + Dropout
|
| 43 |
+
7. 加权求和 V
|
| 44 |
+
8. reshape 回 [B, L, d_model]
|
| 45 |
+
9. 输出投影
|
| 46 |
+
"""
|
| 47 |
+
|
| 48 |
+
def __init__(
|
| 49 |
+
self,
|
| 50 |
+
d_model: int = 512,
|
| 51 |
+
nhead: int = 8,
|
| 52 |
+
dropout: float = 0.1,
|
| 53 |
+
use_rotary_embedding: bool = False,
|
| 54 |
+
):
|
| 55 |
+
super().__init__()
|
| 56 |
+
assert d_model % nhead == 0, "d_model 必须能被 nhead 整除"
|
| 57 |
+
self.d_model = d_model
|
| 58 |
+
self.nhead = nhead
|
| 59 |
+
self.d_k = d_model // nhead
|
| 60 |
+
|
| 61 |
+
raise NotImplementedError("TODO: Person B 实现 MultiHeadAttention.__init__")
|
| 62 |
+
|
| 63 |
+
def forward(
|
| 64 |
+
self,
|
| 65 |
+
query: torch.Tensor, # [B, L_q, D]
|
| 66 |
+
key: torch.Tensor, # [B, L_k, D]
|
| 67 |
+
value: torch.Tensor, # [B, L_v, D]
|
| 68 |
+
key_padding_mask: Optional[torch.BoolTensor] = None, # [B, L_k]
|
| 69 |
+
attn_mask: Optional[torch.Tensor] = None, # [L_q, L_k]
|
| 70 |
+
) -> torch.Tensor:
|
| 71 |
+
raise NotImplementedError("TODO: Person B 实现 MultiHeadAttention.forward")
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
class FlashMultiHeadAttention(nn.Module):
|
| 75 |
+
"""
|
| 76 |
+
Flash Attention 2 加速的多头注意力。
|
| 77 |
+
|
| 78 |
+
TODO [Person B]: 使用 PyTorch 2.0+ 的 F.scaled_dot_product_attention 实现:
|
| 79 |
+
1. 与 MultiHeadAttention 结构相同
|
| 80 |
+
2. 在 forward 中使用 F.scaled_dot_product_attention(Q, K, V, attn_mask, dropout, is_causal)
|
| 81 |
+
3. 会自动选择最优的 attention kernel (Flash Attention / Memory-Efficient Attention)
|
| 82 |
+
|
| 83 |
+
注意:
|
| 84 |
+
- 需要 PyTorch >= 2.0
|
| 85 |
+
- is_causal=True 时自动生成因果掩码,不需要手动传入 attn_mask
|
| 86 |
+
"""
|
| 87 |
+
|
| 88 |
+
def __init__(
|
| 89 |
+
self,
|
| 90 |
+
d_model: int = 512,
|
| 91 |
+
nhead: int = 8,
|
| 92 |
+
dropout: float = 0.1,
|
| 93 |
+
use_rotary_embedding: bool = False,
|
| 94 |
+
):
|
| 95 |
+
super().__init__()
|
| 96 |
+
raise NotImplementedError("TODO: Person B 实现 FlashMultiHeadAttention.__init__")
|
| 97 |
+
|
| 98 |
+
def forward(
|
| 99 |
+
self,
|
| 100 |
+
query: torch.Tensor,
|
| 101 |
+
key: torch.Tensor,
|
| 102 |
+
value: torch.Tensor,
|
| 103 |
+
key_padding_mask: Optional[torch.BoolTensor] = None,
|
| 104 |
+
is_causal: bool = False,
|
| 105 |
+
) -> torch.Tensor:
|
| 106 |
+
raise NotImplementedError("TODO: Person B 实现 FlashMultiHeadAttention.forward")
|
src/easytranslate/model/decoder.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Transformer Decoder 模块 — Person B 负责实现
|
| 3 |
+
|
| 4 |
+
包含:
|
| 5 |
+
- TransformerDecoderLayer: 单层解码器
|
| 6 |
+
- TransformerDecoder: 多层解码器堆叠
|
| 7 |
+
|
| 8 |
+
架构 (Pre-LayerNorm):
|
| 9 |
+
x → LN → Masked Self-Attention → Residual
|
| 10 |
+
→ LN → Cross-Attention → Residual
|
| 11 |
+
→ LN → FFN → Residual
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
from __future__ import annotations
|
| 15 |
+
|
| 16 |
+
import torch
|
| 17 |
+
import torch.nn as nn
|
| 18 |
+
from typing import Optional
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class TransformerDecoderLayer(nn.Module):
|
| 22 |
+
"""
|
| 23 |
+
单层 Transformer Decoder。
|
| 24 |
+
|
| 25 |
+
TODO [Person B]: 实现以下组件:
|
| 26 |
+
1. Masked Self-Attention (因果掩码,防止看到未来)
|
| 27 |
+
2. Cross-Attention (decoder 查询 encoder 输出)
|
| 28 |
+
3. Feed-Forward Network
|
| 29 |
+
4. 三个 LayerNorm
|
| 30 |
+
5. Residual connections + Dropout
|
| 31 |
+
"""
|
| 32 |
+
|
| 33 |
+
def __init__(
|
| 34 |
+
self,
|
| 35 |
+
d_model: int = 512,
|
| 36 |
+
nhead: int = 8,
|
| 37 |
+
dim_feedforward: int = 2048,
|
| 38 |
+
dropout: float = 0.1,
|
| 39 |
+
activation: str = "gelu",
|
| 40 |
+
use_flash_attention: bool = True,
|
| 41 |
+
use_rotary_embedding: bool = True,
|
| 42 |
+
pre_norm: bool = True,
|
| 43 |
+
):
|
| 44 |
+
super().__init__()
|
| 45 |
+
raise NotImplementedError("TODO: Person B 实现 TransformerDecoderLayer.__init__")
|
| 46 |
+
|
| 47 |
+
def forward(
|
| 48 |
+
self,
|
| 49 |
+
tgt: torch.Tensor, # [B, T, D]
|
| 50 |
+
memory: torch.Tensor, # [B, S, D] (encoder output)
|
| 51 |
+
tgt_mask: Optional[torch.Tensor] = None, # [T, T] causal mask
|
| 52 |
+
memory_key_padding_mask: Optional[torch.BoolTensor] = None, # [B, S]
|
| 53 |
+
tgt_key_padding_mask: Optional[torch.BoolTensor] = None, # [B, T]
|
| 54 |
+
) -> torch.Tensor:
|
| 55 |
+
"""
|
| 56 |
+
TODO [Person B]: Pre-LayerNorm 前向传播:
|
| 57 |
+
1. Masked Self-Attention with causal mask
|
| 58 |
+
2. Cross-Attention with encoder output
|
| 59 |
+
3. FFN
|
| 60 |
+
每步都有 residual connection 和 dropout
|
| 61 |
+
"""
|
| 62 |
+
raise NotImplementedError("TODO: Person B 实现 TransformerDecoderLayer.forward")
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
class TransformerDecoder(nn.Module):
|
| 66 |
+
"""
|
| 67 |
+
多层 Transformer Decoder。
|
| 68 |
+
|
| 69 |
+
TODO [Person B]:
|
| 70 |
+
1. 堆叠 N 个 TransformerDecoderLayer
|
| 71 |
+
2. 最终加一个 LayerNorm
|
| 72 |
+
"""
|
| 73 |
+
|
| 74 |
+
def __init__(self, decoder_layer: TransformerDecoderLayer, num_layers: int):
|
| 75 |
+
super().__init__()
|
| 76 |
+
raise NotImplementedError("TODO: Person B 实现 TransformerDecoder.__init__")
|
| 77 |
+
|
| 78 |
+
def forward(
|
| 79 |
+
self,
|
| 80 |
+
tgt: torch.Tensor,
|
| 81 |
+
memory: torch.Tensor,
|
| 82 |
+
tgt_mask: Optional[torch.Tensor] = None,
|
| 83 |
+
memory_key_padding_mask: Optional[torch.BoolTensor] = None,
|
| 84 |
+
tgt_key_padding_mask: Optional[torch.BoolTensor] = None,
|
| 85 |
+
) -> torch.Tensor:
|
| 86 |
+
raise NotImplementedError("TODO: Person B 实现 TransformerDecoder.forward")
|
src/easytranslate/model/encoder.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Transformer Encoder 模块 — Person B 负责实现
|
| 3 |
+
|
| 4 |
+
包含:
|
| 5 |
+
- TransformerEncoderLayer: 单层编码器
|
| 6 |
+
- TransformerEncoder: 多层编码器堆叠
|
| 7 |
+
|
| 8 |
+
架构 (Pre-LayerNorm):
|
| 9 |
+
x → LayerNorm → MultiHeadAttention → Residual → LayerNorm → FFN → Residual
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
from __future__ import annotations
|
| 13 |
+
|
| 14 |
+
import torch
|
| 15 |
+
import torch.nn as nn
|
| 16 |
+
from typing import Optional
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class TransformerEncoderLayer(nn.Module):
|
| 20 |
+
"""
|
| 21 |
+
单层 Transformer Encoder。
|
| 22 |
+
|
| 23 |
+
TODO [Person B]: 实现以下组件:
|
| 24 |
+
1. Self-Attention: MultiHeadAttention (支持 Flash Attention)
|
| 25 |
+
2. Feed-Forward Network: Linear → Activation → Dropout → Linear
|
| 26 |
+
3. 两个 LayerNorm
|
| 27 |
+
4. Residual connections
|
| 28 |
+
5. Dropout
|
| 29 |
+
|
| 30 |
+
注意: 使用 Pre-LayerNorm 架构 (先 norm 再 attention/ffn)
|
| 31 |
+
"""
|
| 32 |
+
|
| 33 |
+
def __init__(
|
| 34 |
+
self,
|
| 35 |
+
d_model: int = 512,
|
| 36 |
+
nhead: int = 8,
|
| 37 |
+
dim_feedforward: int = 2048,
|
| 38 |
+
dropout: float = 0.1,
|
| 39 |
+
activation: str = "gelu",
|
| 40 |
+
use_flash_attention: bool = True,
|
| 41 |
+
use_rotary_embedding: bool = True,
|
| 42 |
+
pre_norm: bool = True,
|
| 43 |
+
):
|
| 44 |
+
super().__init__()
|
| 45 |
+
raise NotImplementedError("TODO: Person B 实现 TransformerEncoderLayer.__init__")
|
| 46 |
+
|
| 47 |
+
def forward(
|
| 48 |
+
self,
|
| 49 |
+
src: torch.Tensor, # [B, S, D]
|
| 50 |
+
src_key_padding_mask: Optional[torch.BoolTensor] = None, # [B, S]
|
| 51 |
+
) -> torch.Tensor:
|
| 52 |
+
"""
|
| 53 |
+
TODO [Person B]: Pre-LayerNorm 前向传播:
|
| 54 |
+
1. residual = src
|
| 55 |
+
2. src = layer_norm_1(src)
|
| 56 |
+
3. src = self_attention(src, src, src, key_padding_mask=src_key_padding_mask)
|
| 57 |
+
4. src = residual + dropout(src)
|
| 58 |
+
5. residual = src
|
| 59 |
+
6. src = layer_norm_2(src)
|
| 60 |
+
7. src = ffn(src)
|
| 61 |
+
8. src = residual + dropout(src)
|
| 62 |
+
"""
|
| 63 |
+
raise NotImplementedError("TODO: Person B 实现 TransformerEncoderLayer.forward")
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
class TransformerEncoder(nn.Module):
|
| 67 |
+
"""
|
| 68 |
+
多层 Transformer Encoder。
|
| 69 |
+
|
| 70 |
+
TODO [Person B]:
|
| 71 |
+
1. 堆叠 N 个 TransformerEncoderLayer
|
| 72 |
+
2. 最终加一个 LayerNorm (Pre-Norm 架构需要)
|
| 73 |
+
"""
|
| 74 |
+
|
| 75 |
+
def __init__(self, encoder_layer: TransformerEncoderLayer, num_layers: int):
|
| 76 |
+
super().__init__()
|
| 77 |
+
raise NotImplementedError("TODO: Person B 实现 TransformerEncoder.__init__")
|
| 78 |
+
|
| 79 |
+
def forward(
|
| 80 |
+
self,
|
| 81 |
+
src: torch.Tensor,
|
| 82 |
+
src_key_padding_mask: Optional[torch.BoolTensor] = None,
|
| 83 |
+
) -> torch.Tensor:
|
| 84 |
+
raise NotImplementedError("TODO: Person B 实现 TransformerEncoder.forward")
|
src/easytranslate/model/finetune.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
预训练模型微调模块 — Person B 负责实现
|
| 3 |
+
|
| 4 |
+
功能要求:
|
| 5 |
+
1. load_pretrained_model: 加载 NLLB / mBART 等预训练翻译模型
|
| 6 |
+
2. setup_lora: 配置 LoRA 参数高效微调
|
| 7 |
+
|
| 8 |
+
技术要点:
|
| 9 |
+
- 使用 HuggingFace transformers 加载预训练模型
|
| 10 |
+
- 使用 PEFT (Parameter-Efficient Fine-Tuning) 库配置 LoRA
|
| 11 |
+
- 支持 NLLB-200 (Meta, 200种语言) 和 mBART-50 (Meta, 50种语言)
|
| 12 |
+
- 冻结预训练参数,只训练 LoRA adapter
|
| 13 |
+
|
| 14 |
+
这是实验对比的关键部分:
|
| 15 |
+
- 从头训练 vs 预训练微调
|
| 16 |
+
- Full fine-tuning vs LoRA
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from __future__ import annotations
|
| 20 |
+
|
| 21 |
+
import logging
|
| 22 |
+
from typing import Optional
|
| 23 |
+
|
| 24 |
+
import torch
|
| 25 |
+
import torch.nn as nn
|
| 26 |
+
|
| 27 |
+
logger = logging.getLogger(__name__)
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def load_pretrained_model(
|
| 31 |
+
model_name: str = "facebook/nllb-200-distilled-600M",
|
| 32 |
+
src_lang: str = "eng_Latn",
|
| 33 |
+
tgt_lang: str = "zho_Hans",
|
| 34 |
+
device: str = "cuda",
|
| 35 |
+
) -> tuple:
|
| 36 |
+
"""
|
| 37 |
+
加载预训练翻译模型和分词器。
|
| 38 |
+
|
| 39 |
+
TODO [Person B]: 实现以下逻辑:
|
| 40 |
+
1. 使用 AutoModelForSeq2SeqLM.from_pretrained(model_name) 加载模型
|
| 41 |
+
2. 使用 AutoTokenizer.from_pretrained(model_name) 加载分词器
|
| 42 |
+
3. 设置源语言和目标语言
|
| 43 |
+
4. 将模型移到指定设备
|
| 44 |
+
|
| 45 |
+
支持的模型:
|
| 46 |
+
- facebook/nllb-200-distilled-600M (推荐,轻量级)
|
| 47 |
+
- facebook/nllb-200-1.3B (中等规模)
|
| 48 |
+
- facebook/mbart-large-50-many-to-many-mmt
|
| 49 |
+
|
| 50 |
+
Returns:
|
| 51 |
+
(model, tokenizer)
|
| 52 |
+
"""
|
| 53 |
+
raise NotImplementedError("TODO: Person B 实现 load_pretrained_model")
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def setup_lora(
|
| 57 |
+
model: nn.Module,
|
| 58 |
+
r: int = 16,
|
| 59 |
+
alpha: int = 32,
|
| 60 |
+
dropout: float = 0.05,
|
| 61 |
+
target_modules: Optional[list[str]] = None,
|
| 62 |
+
) -> nn.Module:
|
| 63 |
+
"""
|
| 64 |
+
为模型配置 LoRA 微调。
|
| 65 |
+
|
| 66 |
+
TODO [Person B]: 实现以下逻辑:
|
| 67 |
+
1. 定义 LoraConfig:
|
| 68 |
+
- r: LoRA 秩 (低秩分解维度)
|
| 69 |
+
- lora_alpha: 缩放因子
|
| 70 |
+
- lora_dropout: LoRA dropout
|
| 71 |
+
- target_modules: 需要加 LoRA 的模块 (如 q_proj, v_proj)
|
| 72 |
+
- task_type: SEQ_2_SEQ_LM
|
| 73 |
+
2. 使用 get_peft_model(model, config) 包装模型
|
| 74 |
+
3. 打印可训练参数数量和比例
|
| 75 |
+
4. 返回 LoRA 模型
|
| 76 |
+
|
| 77 |
+
参考: https://huggingface.co/docs/peft
|
| 78 |
+
|
| 79 |
+
Returns:
|
| 80 |
+
peft_model: LoRA 包装后的模型
|
| 81 |
+
"""
|
| 82 |
+
if target_modules is None:
|
| 83 |
+
target_modules = ["q_proj", "v_proj"]
|
| 84 |
+
|
| 85 |
+
raise NotImplementedError("TODO: Person B 实现 setup_lora")
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def freeze_model_except_lora(model: nn.Module):
|
| 89 |
+
"""
|
| 90 |
+
冻结模型所有参数,只保留 LoRA 参数可训练。
|
| 91 |
+
|
| 92 |
+
TODO [Person B]: 遍历 model.named_parameters(),
|
| 93 |
+
如果参数名中不包含 "lora",则设置 requires_grad = False。
|
| 94 |
+
"""
|
| 95 |
+
raise NotImplementedError("TODO: Person B 实现 freeze_model_except_lora")
|
src/easytranslate/model/positional.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
位置编码模块 — Person B 负责实现
|
| 3 |
+
|
| 4 |
+
包含:
|
| 5 |
+
1. SinusoidalPositionalEncoding: 经典正弦余弦位置编码
|
| 6 |
+
2. RotaryPositionalEmbedding (RoPE): 旋转位置编码
|
| 7 |
+
|
| 8 |
+
RoPE 是当前最前沿的位置编码方案,被 LLaMA, GPT-NeoX 等大模型广泛采用。
|
| 9 |
+
优势: 更好地捕获相对位置信息,支持长度外推。
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
from __future__ import annotations
|
| 13 |
+
|
| 14 |
+
import math
|
| 15 |
+
|
| 16 |
+
import torch
|
| 17 |
+
import torch.nn as nn
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class SinusoidalPositionalEncoding(nn.Module):
|
| 21 |
+
"""
|
| 22 |
+
经典正弦余弦位置编码 (Vaswani et al., 2017)。
|
| 23 |
+
|
| 24 |
+
PE(pos, 2i) = sin(pos / 10000^(2i/d_model))
|
| 25 |
+
PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))
|
| 26 |
+
|
| 27 |
+
TODO [Person B]: 实现以下内容:
|
| 28 |
+
1. 在 __init__ 中预计算位置编码矩阵 [max_seq_len, d_model]
|
| 29 |
+
2. 注册为 buffer (不参与梯度更新)
|
| 30 |
+
3. forward(x): 返回 x + pe[:, :x.size(1), :]
|
| 31 |
+
"""
|
| 32 |
+
|
| 33 |
+
def __init__(self, d_model: int, max_seq_len: int = 5000, dropout: float = 0.1):
|
| 34 |
+
super().__init__()
|
| 35 |
+
raise NotImplementedError("TODO: Person B 实现 SinusoidalPositionalEncoding.__init__")
|
| 36 |
+
|
| 37 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 38 |
+
"""
|
| 39 |
+
Args:
|
| 40 |
+
x: [B, L, D]
|
| 41 |
+
Returns:
|
| 42 |
+
x + positional_encoding: [B, L, D]
|
| 43 |
+
"""
|
| 44 |
+
raise NotImplementedError("TODO: Person B 实现 SinusoidalPositionalEncoding.forward")
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
class RotaryPositionalEmbedding(nn.Module):
|
| 48 |
+
"""
|
| 49 |
+
旋转位置编码 (RoPE) — Su et al., 2021
|
| 50 |
+
|
| 51 |
+
核心思想: 通过旋转变换将位置信息编码到 Q, K 向量中。
|
| 52 |
+
q' = q * cos(θ) + rotate_half(q) * sin(θ)
|
| 53 |
+
k' = k * cos(θ) + rotate_half(k) * sin(θ)
|
| 54 |
+
|
| 55 |
+
TODO [Person B]: 实现以下内容:
|
| 56 |
+
|
| 57 |
+
__init__:
|
| 58 |
+
1. 计算频率: inv_freq = 1.0 / (base ** (torch.arange(0, dim, 2) / dim))
|
| 59 |
+
2. 注册为 buffer
|
| 60 |
+
|
| 61 |
+
_compute_rope(seq_len):
|
| 62 |
+
1. 计算 position indices: t = [0, 1, ..., seq_len-1]
|
| 63 |
+
2. 计算 freqs = torch.outer(t, inv_freq)
|
| 64 |
+
3. 构建 cos_cached, sin_cached
|
| 65 |
+
|
| 66 |
+
apply_rotary_pos_emb(q, k):
|
| 67 |
+
1. 对 q 和 k 应用旋转变换
|
| 68 |
+
2. 返回旋转后的 q', k'
|
| 69 |
+
|
| 70 |
+
参考: https://arxiv.org/abs/2104.09864
|
| 71 |
+
"""
|
| 72 |
+
|
| 73 |
+
def __init__(self, dim: int, max_seq_len: int = 2048, base: float = 10000.0):
|
| 74 |
+
super().__init__()
|
| 75 |
+
raise NotImplementedError("TODO: Person B 实现 RotaryPositionalEmbedding.__init__")
|
| 76 |
+
|
| 77 |
+
def _compute_rope(self, seq_len: int, device: torch.device):
|
| 78 |
+
raise NotImplementedError("TODO: Person B 实现 _compute_rope")
|
| 79 |
+
|
| 80 |
+
@staticmethod
|
| 81 |
+
def _rotate_half(x: torch.Tensor) -> torch.Tensor:
|
| 82 |
+
"""将 x 的后半部分取反并与前半部分交换。"""
|
| 83 |
+
x1, x2 = x.chunk(2, dim=-1)
|
| 84 |
+
return torch.cat((-x2, x1), dim=-1)
|
| 85 |
+
|
| 86 |
+
def apply_rotary_pos_emb(
|
| 87 |
+
self,
|
| 88 |
+
q: torch.Tensor, # [B, nhead, L, d_k]
|
| 89 |
+
k: torch.Tensor, # [B, nhead, L, d_k]
|
| 90 |
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 91 |
+
"""
|
| 92 |
+
对 Q, K 应用 RoPE。
|
| 93 |
+
|
| 94 |
+
Returns:
|
| 95 |
+
(q_rotated, k_rotated)
|
| 96 |
+
"""
|
| 97 |
+
raise NotImplementedError("TODO: Person B 实现 apply_rotary_pos_emb")
|
src/easytranslate/model/transformer.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Transformer 翻译模型主体 — Person B 负责实现
|
| 3 |
+
|
| 4 |
+
这是整个模型的核心文件,定义 Encoder-Decoder 架构。
|
| 5 |
+
|
| 6 |
+
架构设计:
|
| 7 |
+
- Pre-LayerNorm Transformer (训练更稳定)
|
| 8 |
+
- 可选 Flash Attention 2 (加速注意力计算)
|
| 9 |
+
- 可选 RoPE 旋转位置编码 (替代传统正弦位置编码)
|
| 10 |
+
- 共享 Embedding 权重 (可选)
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
from __future__ import annotations
|
| 14 |
+
|
| 15 |
+
import logging
|
| 16 |
+
import math
|
| 17 |
+
from typing import Optional
|
| 18 |
+
|
| 19 |
+
import torch
|
| 20 |
+
import torch.nn as nn
|
| 21 |
+
import torch.nn.functional as F
|
| 22 |
+
|
| 23 |
+
from easytranslate.model.encoder import TransformerEncoder
|
| 24 |
+
from easytranslate.model.decoder import TransformerDecoder
|
| 25 |
+
from easytranslate.model.positional import SinusoidalPositionalEncoding, RotaryPositionalEmbedding
|
| 26 |
+
|
| 27 |
+
logger = logging.getLogger(__name__)
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
class TransformerTranslationModel(nn.Module):
|
| 31 |
+
"""
|
| 32 |
+
完整的 Transformer 英中翻译模型。
|
| 33 |
+
|
| 34 |
+
架构:
|
| 35 |
+
Source Embedding + Positional Encoding
|
| 36 |
+
→ Transformer Encoder (N layers)
|
| 37 |
+
→ Transformer Decoder (N layers)
|
| 38 |
+
→ Linear Projection → Softmax
|
| 39 |
+
|
| 40 |
+
TODO [Person B]: 实现以下内容:
|
| 41 |
+
|
| 42 |
+
__init__:
|
| 43 |
+
1. 源语言 Embedding: nn.Embedding(src_vocab_size, d_model)
|
| 44 |
+
2. 目标语言 Embedding: nn.Embedding(tgt_vocab_size, d_model)
|
| 45 |
+
3. 位置编码: SinusoidalPositionalEncoding 或 RotaryPositionalEmbedding
|
| 46 |
+
4. Transformer Encoder
|
| 47 |
+
5. Transformer Decoder
|
| 48 |
+
6. 输出投影层: nn.Linear(d_model, tgt_vocab_size)
|
| 49 |
+
7. (可选) 共享 target embedding 和 output projection 的权重
|
| 50 |
+
8. 调用 _init_weights() 初始化参数
|
| 51 |
+
|
| 52 |
+
forward:
|
| 53 |
+
1. 源语言 embedding + 位置编码 → encoder_input
|
| 54 |
+
2. 目标语言 embedding + 位置编码 → decoder_input
|
| 55 |
+
3. 生成 masks (src_key_padding_mask, tgt_key_padding_mask, tgt_mask)
|
| 56 |
+
4. encoder_output = encoder(encoder_input, src_key_padding_mask)
|
| 57 |
+
5. decoder_output = decoder(decoder_input, encoder_output, masks...)
|
| 58 |
+
6. logits = output_projection(decoder_output)
|
| 59 |
+
7. 返回 logits [B, T, tgt_vocab_size]
|
| 60 |
+
"""
|
| 61 |
+
|
| 62 |
+
def __init__(
|
| 63 |
+
self,
|
| 64 |
+
src_vocab_size: int,
|
| 65 |
+
tgt_vocab_size: int,
|
| 66 |
+
d_model: int = 512,
|
| 67 |
+
nhead: int = 8,
|
| 68 |
+
num_encoder_layers: int = 6,
|
| 69 |
+
num_decoder_layers: int = 6,
|
| 70 |
+
dim_feedforward: int = 2048,
|
| 71 |
+
dropout: float = 0.1,
|
| 72 |
+
activation: str = "gelu",
|
| 73 |
+
max_seq_len: int = 512,
|
| 74 |
+
use_flash_attention: bool = True,
|
| 75 |
+
use_rotary_embedding: bool = True,
|
| 76 |
+
pre_norm: bool = True,
|
| 77 |
+
pad_id: int = 0,
|
| 78 |
+
share_embedding: bool = False,
|
| 79 |
+
):
|
| 80 |
+
super().__init__()
|
| 81 |
+
# TODO [Person B]: 实现模型初始化
|
| 82 |
+
# 保存超参数
|
| 83 |
+
self.d_model = d_model
|
| 84 |
+
self.pad_id = pad_id
|
| 85 |
+
|
| 86 |
+
raise NotImplementedError("TODO: Person B 实现 TransformerTranslationModel.__init__")
|
| 87 |
+
|
| 88 |
+
def _init_weights(self):
|
| 89 |
+
"""
|
| 90 |
+
参数初始化。
|
| 91 |
+
|
| 92 |
+
TODO [Person B]: 实现 Xavier/Kaiming 初始化:
|
| 93 |
+
- Embedding: normal_(0, d_model^-0.5)
|
| 94 |
+
- Linear: xavier_uniform_
|
| 95 |
+
- LayerNorm: ones_ / zeros_
|
| 96 |
+
"""
|
| 97 |
+
raise NotImplementedError("TODO: Person B 实现 _init_weights")
|
| 98 |
+
|
| 99 |
+
def _generate_square_subsequent_mask(self, sz: int, device: torch.device) -> torch.Tensor:
|
| 100 |
+
"""
|
| 101 |
+
生成因果注意力掩码 (causal mask)。
|
| 102 |
+
|
| 103 |
+
TODO [Person B]:
|
| 104 |
+
返回上三角矩阵 mask,shape [sz, sz],
|
| 105 |
+
mask[i][j] = -inf if j > i else 0
|
| 106 |
+
"""
|
| 107 |
+
raise NotImplementedError("TODO: Person B 实现 _generate_square_subsequent_mask")
|
| 108 |
+
|
| 109 |
+
def forward(
|
| 110 |
+
self,
|
| 111 |
+
src_ids: torch.Tensor, # [B, S]
|
| 112 |
+
tgt_input_ids: torch.Tensor, # [B, T]
|
| 113 |
+
src_padding_mask: Optional[torch.BoolTensor] = None, # [B, S]
|
| 114 |
+
tgt_padding_mask: Optional[torch.BoolTensor] = None, # [B, T]
|
| 115 |
+
) -> torch.Tensor:
|
| 116 |
+
"""
|
| 117 |
+
前向传播。
|
| 118 |
+
|
| 119 |
+
Returns:
|
| 120 |
+
logits: [B, T, tgt_vocab_size]
|
| 121 |
+
"""
|
| 122 |
+
raise NotImplementedError("TODO: Person B 实现 forward")
|
| 123 |
+
|
| 124 |
+
@torch.no_grad()
|
| 125 |
+
def encode(self, src_ids: torch.Tensor, src_padding_mask: Optional[torch.BoolTensor] = None) -> torch.Tensor:
|
| 126 |
+
"""
|
| 127 |
+
仅编码(用于推理时复用 encoder 输出)。
|
| 128 |
+
|
| 129 |
+
TODO [Person B]:
|
| 130 |
+
1. src embedding + positional encoding
|
| 131 |
+
2. encoder forward
|
| 132 |
+
3. 返回 encoder_output
|
| 133 |
+
"""
|
| 134 |
+
raise NotImplementedError("TODO: Person B 实现 encode")
|
| 135 |
+
|
| 136 |
+
@torch.no_grad()
|
| 137 |
+
def decode_step(
|
| 138 |
+
self,
|
| 139 |
+
tgt_input_ids: torch.Tensor,
|
| 140 |
+
encoder_output: torch.Tensor,
|
| 141 |
+
src_padding_mask: Optional[torch.BoolTensor] = None,
|
| 142 |
+
) -> torch.Tensor:
|
| 143 |
+
"""
|
| 144 |
+
解码一步(用于自回归推理)。
|
| 145 |
+
|
| 146 |
+
TODO [Person B]:
|
| 147 |
+
1. tgt embedding + positional encoding
|
| 148 |
+
2. 生成 causal mask
|
| 149 |
+
3. decoder forward
|
| 150 |
+
4. 取最后一个 token 的 logits
|
| 151 |
+
5. 返回 logits [B, vocab_size]
|
| 152 |
+
"""
|
| 153 |
+
raise NotImplementedError("TODO: Person B 实现 decode_step")
|
| 154 |
+
|
| 155 |
+
def count_parameters(self) -> int:
|
| 156 |
+
"""返回可训练参数数量。"""
|
| 157 |
+
return sum(p.numel() for p in self.parameters() if p.requires_grad)
|
src/easytranslate/training/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""训练模块 (Person C 负责)"""
|
| 2 |
+
|
| 3 |
+
from easytranslate.training.trainer import Trainer
|
| 4 |
+
from easytranslate.training.optimizer import build_optimizer, build_scheduler
|
| 5 |
+
from easytranslate.training.loss import LabelSmoothedCrossEntropyLoss
|
| 6 |
+
|
| 7 |
+
__all__ = [
|
| 8 |
+
"Trainer",
|
| 9 |
+
"build_optimizer",
|
| 10 |
+
"build_scheduler",
|
| 11 |
+
"LabelSmoothedCrossEntropyLoss",
|
| 12 |
+
]
|
src/easytranslate/training/loss.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
损失函数模块 — Person C 负责实现
|
| 3 |
+
|
| 4 |
+
功能要求:
|
| 5 |
+
1. LabelSmoothedCrossEntropyLoss: 带标签平滑的交叉熵损失
|
| 6 |
+
|
| 7 |
+
技术要点:
|
| 8 |
+
- 标签平滑 (Label Smoothing) 是 Transformer 训练的标准技巧
|
| 9 |
+
- smoothing=0.1 意味着将 10% 的概率均匀分配给非目标类
|
| 10 |
+
- 需要忽略 padding token 的损失
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
from __future__ import annotations
|
| 14 |
+
|
| 15 |
+
import torch
|
| 16 |
+
import torch.nn as nn
|
| 17 |
+
import torch.nn.functional as F
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class LabelSmoothedCrossEntropyLoss(nn.Module):
|
| 21 |
+
"""
|
| 22 |
+
带标签平滑的交叉熵损失。
|
| 23 |
+
|
| 24 |
+
TODO [Person C]: 实现以下逻辑:
|
| 25 |
+
|
| 26 |
+
__init__:
|
| 27 |
+
1. 保存 smoothing 系数和 pad_id
|
| 28 |
+
|
| 29 |
+
forward(logits, targets):
|
| 30 |
+
1. logits: [B, T, V] — 模型输出
|
| 31 |
+
2. targets: [B, T] — 目标 token ids
|
| 32 |
+
3. 展平为 [B*T, V] 和 [B*T]
|
| 33 |
+
4. 创建 smooth label distribution:
|
| 34 |
+
- target token 概率 = 1 - smoothing
|
| 35 |
+
- 其他 token 概率 = smoothing / (V - 1)
|
| 36 |
+
5. 计算 KL 散度作为损失
|
| 37 |
+
6. 忽略 pad_id 位置的损失
|
| 38 |
+
7. 返回平均损失
|
| 39 |
+
|
| 40 |
+
参考: "Rethinking the Inception Architecture for Computer Vision" (Szegedy et al.)
|
| 41 |
+
"""
|
| 42 |
+
|
| 43 |
+
def __init__(self, smoothing: float = 0.1, pad_id: int = 0):
|
| 44 |
+
super().__init__()
|
| 45 |
+
self.smoothing = smoothing
|
| 46 |
+
self.pad_id = pad_id
|
| 47 |
+
raise NotImplementedError("TODO: Person C 实现 LabelSmoothedCrossEntropyLoss.__init__")
|
| 48 |
+
|
| 49 |
+
def forward(self, logits: torch.Tensor, targets: torch.Tensor) -> torch.Tensor:
|
| 50 |
+
"""
|
| 51 |
+
Args:
|
| 52 |
+
logits: [B, T, V]
|
| 53 |
+
targets: [B, T]
|
| 54 |
+
Returns:
|
| 55 |
+
loss: scalar
|
| 56 |
+
"""
|
| 57 |
+
raise NotImplementedError("TODO: Person C 实现 LabelSmoothedCrossEntropyLoss.forward")
|
src/easytranslate/training/optimizer.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
优化器与学习率调度器 — Person C 负责实现
|
| 3 |
+
|
| 4 |
+
功能要求:
|
| 5 |
+
1. build_optimizer: 根据配置创建优化器
|
| 6 |
+
2. build_scheduler: 根据配置创建学习率调度器
|
| 7 |
+
3. InverseSqrtScheduler: 自定义 inverse square root 调度器
|
| 8 |
+
|
| 9 |
+
技术要点:
|
| 10 |
+
- AdamW 是 Transformer 训练的标准优化器
|
| 11 |
+
- Cosine with warmup 是目前最流行的调度策略
|
| 12 |
+
- Inverse sqrt 是经典 Transformer 论文使用的调度策略
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
from __future__ import annotations
|
| 16 |
+
|
| 17 |
+
import math
|
| 18 |
+
from typing import Optional
|
| 19 |
+
|
| 20 |
+
import torch
|
| 21 |
+
from torch.optim import Adam, AdamW
|
| 22 |
+
from torch.optim.lr_scheduler import LambdaLR
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def build_optimizer(model, config: dict) -> torch.optim.Optimizer:
|
| 26 |
+
"""
|
| 27 |
+
根据配置创建优化器。
|
| 28 |
+
|
| 29 |
+
TODO [Person C]: 实现以下逻辑:
|
| 30 |
+
1. 从 config 中读取 optimizer type, lr, weight_decay, betas, eps
|
| 31 |
+
2. 根据 type 创建 Adam / AdamW / Adafactor
|
| 32 |
+
3. (可选) 对不同参数组设置不同学习率:
|
| 33 |
+
- embedding 层可以用较小的 lr
|
| 34 |
+
- LayerNorm 的 bias 不加 weight_decay
|
| 35 |
+
"""
|
| 36 |
+
raise NotImplementedError("TODO: Person C 实现 build_optimizer")
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def build_scheduler(
|
| 40 |
+
optimizer: torch.optim.Optimizer,
|
| 41 |
+
config: dict,
|
| 42 |
+
num_training_steps: Optional[int] = None,
|
| 43 |
+
) -> torch.optim.lr_scheduler._LRScheduler:
|
| 44 |
+
"""
|
| 45 |
+
根据配置创建学习率调度器。
|
| 46 |
+
|
| 47 |
+
TODO [Person C]: 实现以下逻辑:
|
| 48 |
+
1. 从 config 中读取 scheduler type, warmup_steps, min_lr
|
| 49 |
+
2. type == "cosine_with_warmup":
|
| 50 |
+
使用 get_cosine_schedule_with_warmup (transformers 库)
|
| 51 |
+
3. type == "inverse_sqrt":
|
| 52 |
+
实现经典的 lr = d_model^(-0.5) * min(step^(-0.5), step * warmup^(-1.5))
|
| 53 |
+
4. type == "linear":
|
| 54 |
+
使用 get_linear_schedule_with_warmup
|
| 55 |
+
|
| 56 |
+
参考: Attention Is All You Need, Section 5.3
|
| 57 |
+
"""
|
| 58 |
+
raise NotImplementedError("TODO: Person C 实现 build_scheduler")
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
class InverseSqrtScheduler(LambdaLR):
|
| 62 |
+
"""
|
| 63 |
+
Inverse Square Root 学习率调度器。
|
| 64 |
+
|
| 65 |
+
lr = base_lr * min(step^{-0.5}, step * warmup_steps^{-1.5})
|
| 66 |
+
|
| 67 |
+
这是原始 Transformer 论文使用的调度策略。
|
| 68 |
+
|
| 69 |
+
TODO [Person C]:
|
| 70 |
+
1. 实现 lr_lambda 函数
|
| 71 |
+
2. warmup 阶段线性增长
|
| 72 |
+
3. warmup 后按 step^{-0.5} 衰减
|
| 73 |
+
"""
|
| 74 |
+
|
| 75 |
+
def __init__(self, optimizer, warmup_steps: int = 4000):
|
| 76 |
+
raise NotImplementedError("TODO: Person C 实现 InverseSqrtScheduler.__init__")
|
src/easytranslate/training/trainer.py
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
训练器模块 — Person C 负责实现
|
| 3 |
+
|
| 4 |
+
这是训练的核心控制器,负责:
|
| 5 |
+
1. 训练循环 (training loop)
|
| 6 |
+
2. 验证循环 (validation loop)
|
| 7 |
+
3. 检查点保存/加载
|
| 8 |
+
4. 日志记录
|
| 9 |
+
5. 分布式训练支持
|
| 10 |
+
6. 混合精度训练
|
| 11 |
+
7. 梯度累积
|
| 12 |
+
8. 早停
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
from __future__ import annotations
|
| 16 |
+
|
| 17 |
+
import logging
|
| 18 |
+
import os
|
| 19 |
+
import time
|
| 20 |
+
from pathlib import Path
|
| 21 |
+
from typing import Optional
|
| 22 |
+
|
| 23 |
+
import torch
|
| 24 |
+
import torch.nn as nn
|
| 25 |
+
from torch.utils.data import DataLoader
|
| 26 |
+
from tqdm import tqdm
|
| 27 |
+
|
| 28 |
+
logger = logging.getLogger(__name__)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class Trainer:
|
| 32 |
+
"""
|
| 33 |
+
翻译模型训练器。
|
| 34 |
+
|
| 35 |
+
TODO [Person C]: 实现以下所有方法。
|
| 36 |
+
|
| 37 |
+
使用方法:
|
| 38 |
+
trainer = Trainer(model, train_loader, val_loader, config)
|
| 39 |
+
trainer.train()
|
| 40 |
+
"""
|
| 41 |
+
|
| 42 |
+
def __init__(
|
| 43 |
+
self,
|
| 44 |
+
model: nn.Module,
|
| 45 |
+
train_loader: DataLoader,
|
| 46 |
+
val_loader: DataLoader,
|
| 47 |
+
config: dict,
|
| 48 |
+
optimizer=None,
|
| 49 |
+
scheduler=None,
|
| 50 |
+
criterion=None,
|
| 51 |
+
evaluator=None,
|
| 52 |
+
):
|
| 53 |
+
"""
|
| 54 |
+
TODO [Person C]: 初始化训练器:
|
| 55 |
+
1. 保存模型、数据加载器、配置
|
| 56 |
+
2. 如果 optimizer 为 None,调用 build_optimizer 创建
|
| 57 |
+
3. 如果 scheduler 为 None,调用 build_scheduler 创建
|
| 58 |
+
4. 如果 criterion 为 None,创建 LabelSmoothedCrossEntropyLoss
|
| 59 |
+
5. 设置混合精度: torch.amp.GradScaler (如果配置了 fp16/bf16)
|
| 60 |
+
6. 设置分布式训练: 根据配置初始化 DDP/FSDP/DeepSpeed
|
| 61 |
+
7. 初始化日志记录器 (TensorBoard / WandB)
|
| 62 |
+
8. 初始化训练状态 (epoch, step, best_metric)
|
| 63 |
+
"""
|
| 64 |
+
self.model = model
|
| 65 |
+
self.train_loader = train_loader
|
| 66 |
+
self.val_loader = val_loader
|
| 67 |
+
self.config = config
|
| 68 |
+
raise NotImplementedError("TODO: Person C 实现 Trainer.__init__")
|
| 69 |
+
|
| 70 |
+
def train(self):
|
| 71 |
+
"""
|
| 72 |
+
主训练循环。
|
| 73 |
+
|
| 74 |
+
TODO [Person C]: 实现以下逻辑:
|
| 75 |
+
1. for epoch in range(start_epoch, num_epochs):
|
| 76 |
+
2. train_loss = self._train_one_epoch(epoch)
|
| 77 |
+
3. val_metrics = self._validate(epoch)
|
| 78 |
+
4. self._log_metrics(epoch, train_loss, val_metrics)
|
| 79 |
+
5. self._save_checkpoint(epoch, val_metrics)
|
| 80 |
+
6. if self._should_early_stop(val_metrics): break
|
| 81 |
+
7. self._log_final_results()
|
| 82 |
+
"""
|
| 83 |
+
raise NotImplementedError("TODO: Person C 实现 train")
|
| 84 |
+
|
| 85 |
+
def _train_one_epoch(self, epoch: int) -> float:
|
| 86 |
+
"""
|
| 87 |
+
训练一个 epoch。
|
| 88 |
+
|
| 89 |
+
TODO [Person C]: 实现以下逻辑:
|
| 90 |
+
1. model.train()
|
| 91 |
+
2. 遍历 train_loader:
|
| 92 |
+
a. 将 batch 移到设备
|
| 93 |
+
b. 混合精度上下文: with torch.amp.autocast('cuda'):
|
| 94 |
+
c. logits = model(src_ids, tgt_input_ids, masks...)
|
| 95 |
+
d. loss = criterion(logits, labels)
|
| 96 |
+
e. loss = loss / gradient_accumulation_steps
|
| 97 |
+
f. scaler.scale(loss).backward()
|
| 98 |
+
g. 每 gradient_accumulation_steps 步:
|
| 99 |
+
- scaler.unscale_(optimizer)
|
| 100 |
+
- torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
|
| 101 |
+
- scaler.step(optimizer)
|
| 102 |
+
- scaler.update()
|
| 103 |
+
- scheduler.step()
|
| 104 |
+
- optimizer.zero_grad()
|
| 105 |
+
h. 记录 loss, lr 等指标
|
| 106 |
+
3. 返回平均训练 loss
|
| 107 |
+
"""
|
| 108 |
+
raise NotImplementedError("TODO: Person C 实现 _train_one_epoch")
|
| 109 |
+
|
| 110 |
+
def _validate(self, epoch: int) -> dict:
|
| 111 |
+
"""
|
| 112 |
+
验证。
|
| 113 |
+
|
| 114 |
+
TODO [Person C]: 实现以下逻辑:
|
| 115 |
+
1. model.eval()
|
| 116 |
+
2. with torch.no_grad():
|
| 117 |
+
3. 遍历 val_loader,计算 loss
|
| 118 |
+
4. 每 N 步调用 evaluator 计算 BLEU 等指标
|
| 119 |
+
5. 返回 {"val_loss": ..., "bleu": ..., "comet": ...}
|
| 120 |
+
"""
|
| 121 |
+
raise NotImplementedError("TODO: Person C 实现 _validate")
|
| 122 |
+
|
| 123 |
+
def _save_checkpoint(self, epoch: int, metrics: dict):
|
| 124 |
+
"""
|
| 125 |
+
保存检查点。
|
| 126 |
+
|
| 127 |
+
TODO [Person C]: 实现以下逻辑:
|
| 128 |
+
1. 构建 checkpoint dict:
|
| 129 |
+
{
|
| 130 |
+
"epoch": epoch,
|
| 131 |
+
"step": self.global_step,
|
| 132 |
+
"model_state_dict": model.state_dict(),
|
| 133 |
+
"optimizer_state_dict": optimizer.state_dict(),
|
| 134 |
+
"scheduler_state_dict": scheduler.state_dict(),
|
| 135 |
+
"metrics": metrics,
|
| 136 |
+
"config": config,
|
| 137 |
+
}
|
| 138 |
+
2. 保存到 checkpoint_dir/checkpoint_epoch_{epoch}.pt
|
| 139 |
+
3. 如果是最佳模型,额外保存为 best_model.pt
|
| 140 |
+
4. 清理旧的检查点 (保留最近 max_checkpoints 个)
|
| 141 |
+
"""
|
| 142 |
+
raise NotImplementedError("TODO: Person C 实现 _save_checkpoint")
|
| 143 |
+
|
| 144 |
+
def _load_checkpoint(self, checkpoint_path: str):
|
| 145 |
+
"""
|
| 146 |
+
加载检查点继续训练。
|
| 147 |
+
|
| 148 |
+
TODO [Person C]:
|
| 149 |
+
1. torch.load(checkpoint_path)
|
| 150 |
+
2. 恢复 model, optimizer, scheduler 状态
|
| 151 |
+
3. 恢复 epoch, step 计数器
|
| 152 |
+
"""
|
| 153 |
+
raise NotImplementedError("TODO: Person C ��现 _load_checkpoint")
|
| 154 |
+
|
| 155 |
+
def _should_early_stop(self, metrics: dict) -> bool:
|
| 156 |
+
"""
|
| 157 |
+
判断是否应该早停。
|
| 158 |
+
|
| 159 |
+
TODO [Person C]:
|
| 160 |
+
1. 比较当前指标与最佳指标
|
| 161 |
+
2. 如果连续 patience 个 epoch 没有改善,返回 True
|
| 162 |
+
"""
|
| 163 |
+
raise NotImplementedError("TODO: Person C 实现 _should_early_stop")
|
| 164 |
+
|
| 165 |
+
def _log_metrics(self, epoch: int, train_loss: float, val_metrics: dict):
|
| 166 |
+
"""
|
| 167 |
+
记录训练指标到 TensorBoard / WandB。
|
| 168 |
+
|
| 169 |
+
TODO [Person C]:
|
| 170 |
+
1. 使用 self.logger 记录 train_loss, val_loss, bleu, lr 等
|
| 171 |
+
2. 打印到控制台 (使用 rich 库的 table 格式)
|
| 172 |
+
"""
|
| 173 |
+
raise NotImplementedError("TODO: Person C 实现 _log_metrics")
|
| 174 |
+
|
| 175 |
+
def _setup_distributed(self):
|
| 176 |
+
"""
|
| 177 |
+
设置分布式训练。
|
| 178 |
+
|
| 179 |
+
TODO [Person C]: 根据 config["training"]["distributed"]["strategy"]:
|
| 180 |
+
1. "ddp": 使用 torch.nn.parallel.DistributedDataParallel
|
| 181 |
+
2. "fsdp": 使用 torch.distributed.fsdp.FullyShardedDataParallel
|
| 182 |
+
3. "deepspeed": 使用 deepspeed.initialize()
|
| 183 |
+
|
| 184 |
+
也可以使用 HuggingFace Accelerate 统一处理。
|
| 185 |
+
"""
|
| 186 |
+
raise NotImplementedError("TODO: Person C 实现 _setup_distributed")
|
src/easytranslate/utils/__init__.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""工具模块"""
|
| 2 |
+
|
| 3 |
+
from easytranslate.utils.config import load_config, merge_configs
|
| 4 |
+
from easytranslate.utils.seed import set_seed
|
| 5 |
+
from easytranslate.utils.logging import setup_logging
|
| 6 |
+
|
| 7 |
+
__all__ = ["load_config", "merge_configs", "set_seed", "setup_logging"]
|
src/easytranslate/utils/config.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
配置管理模块 — 公共模块 (Person E 可以先完成)
|
| 3 |
+
|
| 4 |
+
功能: 加载 YAML 配置,支持命令行覆盖和多配置合并。
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from __future__ import annotations
|
| 8 |
+
|
| 9 |
+
import logging
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
from typing import Any, Optional
|
| 12 |
+
|
| 13 |
+
from omegaconf import OmegaConf, DictConfig
|
| 14 |
+
|
| 15 |
+
logger = logging.getLogger(__name__)
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def load_config(config_path: str | Path) -> DictConfig:
|
| 19 |
+
"""
|
| 20 |
+
加载 YAML 配置文件。
|
| 21 |
+
|
| 22 |
+
TODO [Person E]: 实现以下逻辑:
|
| 23 |
+
1. 使用 OmegaConf.load(config_path) 加载配置
|
| 24 |
+
2. 验证配置完整性
|
| 25 |
+
3. 返回 DictConfig 对象
|
| 26 |
+
"""
|
| 27 |
+
raise NotImplementedError("TODO: Person E 实现 load_config")
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def merge_configs(base_config: DictConfig, override_config: DictConfig) -> DictConfig:
|
| 31 |
+
"""
|
| 32 |
+
合并配置 (override 覆盖 base)。
|
| 33 |
+
|
| 34 |
+
TODO [Person E]:
|
| 35 |
+
使用 OmegaConf.merge(base_config, override_config)
|
| 36 |
+
"""
|
| 37 |
+
raise NotImplementedError("TODO: Person E 实现 merge_configs")
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def config_from_cli(config_path: str, cli_args: list[str]) -> DictConfig:
|
| 41 |
+
"""
|
| 42 |
+
从配置文件 + 命令行参数构建最终配置。
|
| 43 |
+
|
| 44 |
+
TODO [Person E]:
|
| 45 |
+
1. 加载 config_path
|
| 46 |
+
2. 使用 OmegaConf.from_cli(cli_args) 解析命令行参数
|
| 47 |
+
3. 合并并返回
|
| 48 |
+
|
| 49 |
+
使用方式: python train.py --config configs/default.yaml training.lr=1e-4 model.nhead=16
|
| 50 |
+
"""
|
| 51 |
+
raise NotImplementedError("TODO: Person E 实现 config_from_cli")
|
src/easytranslate/utils/logging.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
日志管理模块 — 公共模块
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import logging
|
| 6 |
+
import sys
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
from typing import Optional
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def setup_logging(
|
| 12 |
+
log_dir: Optional[str] = None,
|
| 13 |
+
level: int = logging.INFO,
|
| 14 |
+
log_file: Optional[str] = None,
|
| 15 |
+
):
|
| 16 |
+
"""
|
| 17 |
+
配置日志系统。
|
| 18 |
+
|
| 19 |
+
TODO [Person E]: 实现以下逻辑:
|
| 20 |
+
1. 创建 root logger
|
| 21 |
+
2. 设置 StreamHandler (控制台输出)
|
| 22 |
+
3. (可选) 设置 FileHandler (文件输出)
|
| 23 |
+
4. 使用 rich.logging.RichHandler 美化控制台输出
|
| 24 |
+
5. 设置日志格式: [时间] [级别] [模块名] 消息
|
| 25 |
+
"""
|
| 26 |
+
raise NotImplementedError("TODO: Person E 实现 setup_logging")
|
src/easytranslate/utils/seed.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
随机种子管理模块 — 公共模块
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import random
|
| 6 |
+
|
| 7 |
+
import numpy as np
|
| 8 |
+
import torch
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def set_seed(seed: int = 42):
|
| 12 |
+
"""
|
| 13 |
+
设置全局随机种子以确保实验可复现。
|
| 14 |
+
|
| 15 |
+
TODO [Person E]:
|
| 16 |
+
1. random.seed(seed)
|
| 17 |
+
2. np.random.seed(seed)
|
| 18 |
+
3. torch.manual_seed(seed)
|
| 19 |
+
4. torch.cuda.manual_seed_all(seed)
|
| 20 |
+
5. torch.backends.cudnn.deterministic = True
|
| 21 |
+
6. torch.backends.cudnn.benchmark = False
|
| 22 |
+
"""
|
| 23 |
+
raise NotImplementedError("TODO: Person E 实现 set_seed")
|
tests/test_data.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
单元测试 — 数据模块 (Person A 实现后需通过)
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import pytest
|
| 6 |
+
import torch
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class TestTranslationDataset:
|
| 10 |
+
"""测试 TranslationDataset 类。"""
|
| 11 |
+
|
| 12 |
+
def test_dataset_length(self):
|
| 13 |
+
"""测试数据集长度。"""
|
| 14 |
+
# TODO: 创建 mock tokenizer,构建小型数据集,验证 len() 正确
|
| 15 |
+
pass
|
| 16 |
+
|
| 17 |
+
def test_getitem_returns_correct_keys(self):
|
| 18 |
+
"""测试 __getitem__ 返回正确的字段。"""
|
| 19 |
+
# TODO: 验证返回 dict 包含 src_ids, tgt_input_ids, labels, src_len, tgt_len
|
| 20 |
+
pass
|
| 21 |
+
|
| 22 |
+
def test_getitem_tensor_types(self):
|
| 23 |
+
"""测试返回的 tensor 类型正确。"""
|
| 24 |
+
pass
|
| 25 |
+
|
| 26 |
+
def test_src_tgt_mismatch_raises(self):
|
| 27 |
+
"""测试源目标数量不匹配时抛出异常。"""
|
| 28 |
+
pass
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class TestTokenizer:
|
| 32 |
+
"""测试分词器。"""
|
| 33 |
+
|
| 34 |
+
def test_bpe_train_and_encode(self):
|
| 35 |
+
"""测试 BPE 训练和编码。"""
|
| 36 |
+
pass
|
| 37 |
+
|
| 38 |
+
def test_encode_decode_roundtrip(self):
|
| 39 |
+
"""测试编码-解码往返一致性。"""
|
| 40 |
+
pass
|
| 41 |
+
|
| 42 |
+
def test_special_tokens(self):
|
| 43 |
+
"""测试特殊 token 正确。"""
|
| 44 |
+
pass
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
class TestPreprocessing:
|
| 48 |
+
"""测试预处理。"""
|
| 49 |
+
|
| 50 |
+
def test_clean_text_unicode(self):
|
| 51 |
+
"""测试 Unicode 标准化。"""
|
| 52 |
+
pass
|
| 53 |
+
|
| 54 |
+
def test_filter_by_length(self):
|
| 55 |
+
"""测试按长度过滤。"""
|
| 56 |
+
pass
|
| 57 |
+
|
| 58 |
+
def test_deduplicate(self):
|
| 59 |
+
"""测试去重。"""
|
| 60 |
+
pass
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
class TestCollator:
|
| 64 |
+
"""测试数据整理器。"""
|
| 65 |
+
|
| 66 |
+
def test_padding(self):
|
| 67 |
+
"""测试 padding 正确。"""
|
| 68 |
+
pass
|
| 69 |
+
|
| 70 |
+
def test_attention_mask(self):
|
| 71 |
+
"""测试 attention mask 正确。"""
|
| 72 |
+
pass
|
tests/test_evaluation.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
单元测试 — 评估模块 (Person D 实现后需通过)
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import pytest
|
| 6 |
+
import torch
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class TestMetrics:
|
| 10 |
+
"""测试评估指标。"""
|
| 11 |
+
|
| 12 |
+
def test_bleu_perfect(self):
|
| 13 |
+
"""完美翻译的 BLEU 应接近 100。"""
|
| 14 |
+
pass
|
| 15 |
+
|
| 16 |
+
def test_bleu_zero(self):
|
| 17 |
+
"""完全不相关的翻译 BLEU 应接近 0。"""
|
| 18 |
+
pass
|
| 19 |
+
|
| 20 |
+
def test_chrf_score(self):
|
| 21 |
+
"""测试 chrF 分数范围合理。"""
|
| 22 |
+
pass
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class TestDecoding:
|
| 26 |
+
"""测试解码策略。"""
|
| 27 |
+
|
| 28 |
+
def test_greedy_decode_shape(self):
|
| 29 |
+
"""测试贪心解码输出维度。"""
|
| 30 |
+
pass
|
| 31 |
+
|
| 32 |
+
def test_greedy_produces_eos(self):
|
| 33 |
+
"""测试贪心解码能生成 EOS。"""
|
| 34 |
+
pass
|
| 35 |
+
|
| 36 |
+
def test_beam_search_better_than_greedy(self):
|
| 37 |
+
"""束搜索质量应不低于贪心。"""
|
| 38 |
+
pass
|
| 39 |
+
|
| 40 |
+
def test_no_repeat_ngram(self):
|
| 41 |
+
"""测试 n-gram 不重复约束。"""
|
| 42 |
+
pass
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
class TestEvaluator:
|
| 46 |
+
"""测试评估器。"""
|
| 47 |
+
|
| 48 |
+
def test_translate_single(self):
|
| 49 |
+
"""测试单句翻译。"""
|
| 50 |
+
pass
|
| 51 |
+
|
| 52 |
+
def test_translate_batch(self):
|
| 53 |
+
"""测试批量翻译。"""
|
| 54 |
+
pass
|
tests/test_model.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
单元测试 — 模型模块 (Person B 实现后需通过)
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import pytest
|
| 6 |
+
import torch
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class TestMultiHeadAttention:
|
| 10 |
+
"""测试多头注意力。"""
|
| 11 |
+
|
| 12 |
+
def test_output_shape(self):
|
| 13 |
+
"""测试输出维度正确。"""
|
| 14 |
+
# TODO: 创建 MHA,输入 [2, 10, 512],验证输出 [2, 10, 512]
|
| 15 |
+
pass
|
| 16 |
+
|
| 17 |
+
def test_padding_mask(self):
|
| 18 |
+
"""测试 padding mask 效果。"""
|
| 19 |
+
pass
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class TestPositionalEncoding:
|
| 23 |
+
"""测试位置编码。"""
|
| 24 |
+
|
| 25 |
+
def test_sinusoidal_shape(self):
|
| 26 |
+
"""测试正弦位置编码输出维度。"""
|
| 27 |
+
pass
|
| 28 |
+
|
| 29 |
+
def test_rope_shape(self):
|
| 30 |
+
"""测试 RoPE 输出维度。"""
|
| 31 |
+
pass
|
| 32 |
+
|
| 33 |
+
def test_rope_relative_distance(self):
|
| 34 |
+
"""测试 RoPE 捕获相对距离。"""
|
| 35 |
+
pass
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
class TestTransformerModel:
|
| 39 |
+
"""测试完整 Transformer 模型。"""
|
| 40 |
+
|
| 41 |
+
def test_forward_shape(self):
|
| 42 |
+
"""测试前向传播输出维度。"""
|
| 43 |
+
# TODO: 构建小模型,输入 mock 数据,验证 logits shape [B, T, V]
|
| 44 |
+
pass
|
| 45 |
+
|
| 46 |
+
def test_encode_shape(self):
|
| 47 |
+
"""测试 encode 输出维度。"""
|
| 48 |
+
pass
|
| 49 |
+
|
| 50 |
+
def test_decode_step_shape(self):
|
| 51 |
+
"""测试 decode_step 输出维度。"""
|
| 52 |
+
pass
|
| 53 |
+
|
| 54 |
+
def test_parameter_count(self):
|
| 55 |
+
"""测试参数数量计算。"""
|
| 56 |
+
pass
|
| 57 |
+
|
| 58 |
+
def test_causal_mask(self):
|
| 59 |
+
"""测试因果掩码生成。"""
|
| 60 |
+
pass
|
tests/test_training.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
单元测试 — 训练模块 (Person C 实现后需通过)
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import pytest
|
| 6 |
+
import torch
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class TestLabelSmoothedLoss:
|
| 10 |
+
"""测试标签平滑损失。"""
|
| 11 |
+
|
| 12 |
+
def test_no_smoothing_equals_ce(self):
|
| 13 |
+
"""smoothing=0 时应等于标准交叉熵。"""
|
| 14 |
+
pass
|
| 15 |
+
|
| 16 |
+
def test_smoothing_reduces_confidence(self):
|
| 17 |
+
"""标签平滑应降低损失对正确预测的自信度。"""
|
| 18 |
+
pass
|
| 19 |
+
|
| 20 |
+
def test_ignore_padding(self):
|
| 21 |
+
"""应忽略 padding 位置的损失。"""
|
| 22 |
+
pass
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class TestOptimizer:
|
| 26 |
+
"""测试优化器构建。"""
|
| 27 |
+
|
| 28 |
+
def test_build_adamw(self):
|
| 29 |
+
"""测试构建 AdamW。"""
|
| 30 |
+
pass
|
| 31 |
+
|
| 32 |
+
def test_weight_decay_groups(self):
|
| 33 |
+
"""测试权重衰减参数组正确。"""
|
| 34 |
+
pass
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
class TestScheduler:
|
| 38 |
+
"""测试学习率调度器。"""
|
| 39 |
+
|
| 40 |
+
def test_warmup_phase(self):
|
| 41 |
+
"""测试 warmup 阶段 lr 线性增长。"""
|
| 42 |
+
pass
|
| 43 |
+
|
| 44 |
+
def test_decay_phase(self):
|
| 45 |
+
"""测试衰减阶段 lr 下降。"""
|
| 46 |
+
pass
|