Anonymous code release: MSA inference and evaluation
Browse files- QUICK_START.md +115 -0
- README.md +291 -0
- requirements.txt +29 -0
- scripts/calculate_llm_score.sh +5 -0
- scripts/resave_model.sh +38 -0
- scripts/run_benchmarks.sh +119 -0
- src/__init__.py +0 -0
- src/app/__init__.py +0 -0
- src/app/benchmark.py +335 -0
- src/benchmarks.py +120 -0
- src/config/memory_config.py +56 -0
- src/evaluation/llm_judge.py +218 -0
- src/msa/__init__.py +3 -0
- src/msa/configuration_msa.py +53 -0
- src/msa/generate.py +354 -0
- src/msa/memory_sparse_attention.py +852 -0
- src/msa/model.py +733 -0
- src/msa_service.py +1911 -0
- src/prefill.py +306 -0
- src/types.py +21 -0
- src/utils/__init__.py +15 -0
- src/utils/cache.py +512 -0
- src/utils/callbacks.py +172 -0
- src/utils/common.py +187 -0
- src/utils/data_utils.py +318 -0
- src/utils/gpu_monitor.py +384 -0
- src/utils/gpu_worker.py +19 -0
- src/utils/misc.py +175 -0
- src/utils/resave_model.py +70 -0
- src/utils/scale.py +10 -0
- src/utils/template.py +9 -0
- src/utils/tools.py +120 -0
QUICK_START.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
## Project Structure
|
| 2 |
+
|
| 3 |
+
```
|
| 4 |
+
MSA/
|
| 5 |
+
├── scripts/
|
| 6 |
+
│ ├── run_benchmarks.sh # Run inference on benchmarks
|
| 7 |
+
│ ├── calculate_llm_score.sh # LLM-based answer evaluation
|
| 8 |
+
│ └── resave_model.sh # Convert base model to MSA format
|
| 9 |
+
└── src/
|
| 10 |
+
├── msa/ # Core MSA implementation
|
| 11 |
+
│ ├── configuration_msa.py # MSA model configuration
|
| 12 |
+
│ ├── memory_sparse_attention.py # MemorySparseAttention layer
|
| 13 |
+
│ ├── model.py # MSAForCausalLM / MSAModel
|
| 14 |
+
│ └── generate.py # Generation logic
|
| 15 |
+
├── config/
|
| 16 |
+
│ └── memory_config.py # GenerateConfig, ModelConfig, MemoryConfig
|
| 17 |
+
├── evaluation/
|
| 18 |
+
│ └── llm_judge.py # LLM-based evaluation metrics
|
| 19 |
+
├── app/
|
| 20 |
+
│ └── benchmark.py # Benchmark runner
|
| 21 |
+
├── utils/ # GPU workers, caching, templates, etc.
|
| 22 |
+
├── msa_service.py # Multi-GPU inference engine (MSAEngine)
|
| 23 |
+
├── prefill.py # Stage 1 prefill worker
|
| 24 |
+
├── benchmarks.py # Benchmark registry & specs
|
| 25 |
+
└── types.py # Core type definitions
|
| 26 |
+
```
|
| 27 |
+
|
| 28 |
+
## Installation
|
| 29 |
+
|
| 30 |
+
**1. Create conda environment**
|
| 31 |
+
|
| 32 |
+
```bash
|
| 33 |
+
conda create -n msa python=3.12 -y
|
| 34 |
+
conda activate msa
|
| 35 |
+
```
|
| 36 |
+
|
| 37 |
+
**2. Install dependencies**
|
| 38 |
+
|
| 39 |
+
```bash
|
| 40 |
+
pip install -r requirements.txt
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
<details>
|
| 44 |
+
<summary>requirements.txt</summary>
|
| 45 |
+
|
| 46 |
+
```
|
| 47 |
+
torch==2.6.0
|
| 48 |
+
torchvision==0.21.0
|
| 49 |
+
transformers==4.51.3 # exact version required
|
| 50 |
+
accelerate==1.0.1
|
| 51 |
+
liger_kernel==0.5.10
|
| 52 |
+
huggingface_hub==0.31.4 # must stay <1.0 for transformers 4.51.3
|
| 53 |
+
datasets==3.1.0
|
| 54 |
+
lmdb==1.6.2
|
| 55 |
+
tqdm==4.67.1
|
| 56 |
+
numpy==1.26.4
|
| 57 |
+
pillow==11.2.1
|
| 58 |
+
packaging==25.0
|
| 59 |
+
nvidia-ml-py==12.575.51 # provides the `pynvml` module
|
| 60 |
+
openai==1.79.0
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
</details>
|
| 64 |
+
|
| 65 |
+
**3. Install Flash Attention**
|
| 66 |
+
|
| 67 |
+
Option A — build from source:
|
| 68 |
+
|
| 69 |
+
```bash
|
| 70 |
+
pip install flash-attn==2.7.4.post1 --no-build-isolation
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
Option B — install prebuilt wheel (CUDA 12, Python 3.12):
|
| 74 |
+
|
| 75 |
+
```bash
|
| 76 |
+
wget -P /tmp https://github.com/Dao-AILab/flash-attention/releases/download/v2.7.4.post1/flash_attn-2.7.4.post1+cu12torch2.6cxx11abiFALSE-cp312-cp312-linux_x86_64.whl
|
| 77 |
+
pip install /tmp/flash_attn-2.7.4.post1+cu12torch2.6cxx11abiFALSE-cp312-cp312-linux_x86_64.whl
|
| 78 |
+
rm /tmp/flash_attn-2.7.4.post1+cu12torch2.6cxx11abiFALSE-cp312-cp312-linux_x86_64.whl
|
| 79 |
+
```
|
| 80 |
+
|
| 81 |
+
## Download
|
| 82 |
+
|
| 83 |
+
**1. Download model**
|
| 84 |
+
|
| 85 |
+
```bash
|
| 86 |
+
mkdir ckpt
|
| 87 |
+
pip install -U huggingface_hub==0.31.4
|
| 88 |
+
export HF_ENDPOINT=https://hf-mirror.com
|
| 89 |
+
huggingface-cli download --resume-download Anoy123423123/MSA-4B --local-dir ckpt/MSA-4B
|
| 90 |
+
```
|
| 91 |
+
|
| 92 |
+
**2. Download benchmarks**
|
| 93 |
+
|
| 94 |
+
Benchmark data is hosted on [Anoy123423123/MSA-RAG-BENCHMARKS](https://huggingface.co/datasets/Anoy123423123/MSA-RAG-BENCHMARKS) and will be automatically downloaded to `data/` on first run, based on the benchmarks specified in `scripts/run_benchmarks.sh`. No manual download is needed.
|
| 95 |
+
|
| 96 |
+
## Quick Start
|
| 97 |
+
|
| 98 |
+
**1. Run inference on benchmarks**
|
| 99 |
+
|
| 100 |
+
```bash
|
| 101 |
+
bash scripts/run_benchmarks.sh eval_benchmark
|
| 102 |
+
```
|
| 103 |
+
|
| 104 |
+
**2. Compute LLM-based scores**
|
| 105 |
+
|
| 106 |
+
```bash
|
| 107 |
+
bash scripts/calculate_llm_score.sh eval_benchmark
|
| 108 |
+
```
|
| 109 |
+
|
| 110 |
+
## Supported Benchmarks
|
| 111 |
+
|
| 112 |
+
| Category | Benchmark |
|
| 113 |
+
|---|---|
|
| 114 |
+
| Multi-hop QA | `2wikimultihopqa`, `hotpotqa`, `musique` |
|
| 115 |
+
| Single-hop QA | `nature_questions`, `triviaqa_06M`, `triviaqa_10M`, `msmarco_v1`, `dureader`, `ms_100M`, `hipporag_narrative`, `hipporag_popqa` |
|
README.md
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: mit
|
| 3 |
+
language:
|
| 4 |
+
- en
|
| 5 |
+
tags:
|
| 6 |
+
- msa
|
| 7 |
+
---
|
| 8 |
+
# MSA: Memory Sparse Attention
|
| 9 |
+
|
| 10 |
+
*A scalable, end-to-end trainable latent-memory framework for 100M-token contexts*
|
| 11 |
+
|
| 12 |
+
**Anonymous code release for peer review.**
|
| 13 |
+
|
| 14 |
+
| | |
|
| 15 |
+
|---|---|
|
| 16 |
+
| Code (this repo) | `Anoy123423123/MSA-Code` |
|
| 17 |
+
| Model weights | [`Anoy123423123/MSA-4B`](https://huggingface.co/Anoy123423123/MSA-4B) |
|
| 18 |
+
| Benchmark data | [`Anoy123423123/MSA-RAG-BENCHMARKS`](https://huggingface.co/datasets/Anoy123423123/MSA-RAG-BENCHMARKS) |
|
| 19 |
+
|
| 20 |
+
Jump to [**Quick Start**](#-quick-start) to reproduce the reported numbers.
|
| 21 |
+
|
| 22 |
+
## 📝 Abstract
|
| 23 |
+
|
| 24 |
+
Long-term memory is essential for general intelligence, yet the **full attention** bottleneck constrains most LLMs' **effective context length** to **128K–1M**. Existing attempts — hybrid linear attention, fixed-size state memory (e.g., RNNs), and external storage like **RAG/agents** — either suffer rapid precision decay and latency growth at extreme scales, lack end-to-end differentiability or dynamic memory maintenance, or require complex pipelines. We present **Memory Sparse Attention (MSA)**: an **end-to-end trainable, scalable sparse latent-state memory** framework. Core ideas include:
|
| 25 |
+
|
| 26 |
+
- **Scalable sparse attention** + **document-wise RoPE** (parallel/global) achieving **near-linear complexity** in both training and inference;
|
| 27 |
+
- **KV cache compression** with a **Memory Parallel** inference engine to deliver **100M token** throughput on **2×A800** GPUs;
|
| 28 |
+
- **Memory Interleave** for multi-round, multi-hop reasoning across scattered memory segments.
|
| 29 |
+
|
| 30 |
+
On long-context QA and NIAH (Needle-in-a-Haystack) benchmarks, **MSA** surpasses same-backbone RAG, best-of-breed RAG stacks, and leading long-context models. Across an unprecedented **16K→100M token** range, MSA shows **< 9%** degradation, suggesting a practical path to **decouple memory capacity from reasoning**.
|
| 31 |
+
|
| 32 |
+
> **Scaling from 16K→100M tokens**: MSA fuses top-k selection with sparse attention to remain end-to-end differentiable while allowing document decoupling at inference. On MS MARCO, MSA sustains **<9%** degradation and exhibits strong extrapolation.
|
| 33 |
+
|
| 34 |
+
## ✨ Key Contributions
|
| 35 |
+
|
| 36 |
+
- **Memory-Sparse Attention (MSA)**: an **end-to-end trainable**, **scalable sparse attention** layer with **document-wise RoPE**, realizing **O(L)** complexity and **<9%** degradation from **16K→100M tokens**.
|
| 37 |
+
- **KV cache compression + Memory Parallel**: tiered storage (GPU-resident routing keys, CPU content K/V), distributed scoring, and on-demand transfers to enable **100M-token** inference on **2×A800**.
|
| 38 |
+
- **Memory Interleave**: adaptive alternating "generative retrieval → context expansion → generation," significantly boosting **multi-hop** reasoning across documents.
|
| 39 |
+
- **Comprehensive evaluation**: MSA outperforms same-backbone RAG, best-of-breed RAG pipelines, and top long-context models on long-context QA and NIAH, showing superior **stability** and **accuracy** at scale.
|
| 40 |
+
|
| 41 |
+
---
|
| 42 |
+
|
| 43 |
+
## 🧩 Overall Design
|
| 44 |
+
|
| 45 |
+
### Architecture
|
| 46 |
+
|
| 47 |
+
MSA integrates **retrieval and generation** into a single differentiable loop. Document latent states (**K/V/Kᵣ**) are **chunk-mean pooled** for compression. A **router projector** computes relevance via cosine similarity (mean-pooled over heads, then token-wise max), selects **Top‑k documents**, then concatenates their **compressed K/V** with the query's local **K/V** for autoregressive decoding. Routing applies only to **upper layers**; lower layers keep **independent document processing** for hierarchical alignment.
|
| 48 |
+
|
| 49 |
+
- **Parallel (document-wise) RoPE**: Each document resets positions from 0, preventing position drift between **train-short** and **infer-long**, enabling 64k training to extrapolate to 100M.
|
| 50 |
+
- **Global RoPE (active context)**: The query's starting index is **offset by k** (Top‑k retrieved blocks), preserving causal ordering: *background → query → generation*.
|
| 51 |
+
|
| 52 |
+
Implemented in [`src/msa/memory_sparse_attention.py`](./src/msa/memory_sparse_attention.py) and [`src/msa/model.py`](./src/msa/model.py).
|
| 53 |
+
|
| 54 |
+
---
|
| 55 |
+
|
| 56 |
+
### Inference Pipeline
|
| 57 |
+
|
| 58 |
+
MSA uses a **three-stage** pipeline:
|
| 59 |
+
|
| 60 |
+
1. **Global Memory Encoding (offline)**: forward over the corpus to cache chunk-pooled **(K̄, V̄, K̄ᵣ)**.
|
| 61 |
+
2. **Online Routing & Context Assembly**: project query to **Qᵣ**, match with **K̄ᵣ** to pick **Top‑k**, then load only the selected **K̄/V̄** and concatenate with local context.
|
| 62 |
+
3. **Sparse Generation**: autoregress over the **sparse context**.
|
| 63 |
+
|
| 64 |
+
**Memory Parallel** shards **K̄ᵣ** across GPUs (query broadcast → local scoring → global reduce). Content **K̄/V̄** stays in host DRAM and is **asynchronously fetched** when selected—balancing **VRAM** and **throughput** for **100M-token** deployment.
|
| 65 |
+
|
| 66 |
+
Implemented in [`src/msa_service.py`](./src/msa_service.py) (`MSAEngine`, memory-parallel bucketing) and [`src/prefill.py`](./src/prefill.py) (stage-1 encoding).
|
| 67 |
+
|
| 68 |
+
---
|
| 69 |
+
|
| 70 |
+
## 🚀 Results
|
| 71 |
+
|
| 72 |
+
> **Setup**
|
| 73 |
+
> **QA**: 9 datasets (MS MARCO v1, NQ, DuReader, TriviaQA(10M), NarrativeQA, PopQA, 2WikiMultiHopQA, HotpotQA, MuSiQue), memory banks **277K→10M tokens**, metric: **LLM judge (0–5)**.
|
| 74 |
+
> **NIAH (RULER)**: 8 subtasks, **32K→1M tokens**, report average accuracy.
|
| 75 |
+
> **Backbone**: Qwen3‑4B‑Instruct‑2507. Compare to same-backbone RAG and best-of-breed RAG stacks (KaLMv2 + large generators, optional reranker).
|
| 76 |
+
|
| 77 |
+
### Table 1: MSA vs same-backbone RAG (Qwen3‑4B)
|
| 78 |
+
|
| 79 |
+
**Summary**: Average **3.760**, improving over standard RAG (**+16.0%**), RAG+rerank (**+11.5%**), and HippoRAG2 (**+14.8%**) using their best@k; MSA leads on all but NarrativeQA within the same-backbone group.
|
| 80 |
+
|
| 81 |
+
| Dataset | Tokens | Qwen3-4B R@1 | R@5 | R@10 | Qwen3-4B (RR) R@1 | R@5 | R@10 | HippoRAG2 R@1 | R@5 | R@10 | MSA (adaptive) |
|
| 82 |
+
|---------|--------|--------------|------|-------|----------------------|------|-------|------------------|------|--------|----------------|
|
| 83 |
+
| MS MARCO v1 | 7.34M | 2.893 | 3.011 | 3.005 | 2.934 | <u>3.032</u> | 3.017 | 2.676 | 3.005 | 3.019 | **4.141** |
|
| 84 |
+
| Natural Questions | 1.47M | 3.452 | 3.374 | 3.297 | <u>3.494</u> | 3.408 | 3.385 | 3.338 | 3.389 | 3.374 | **3.545** |
|
| 85 |
+
| DuReader | 277K | 3.726 | 3.579 | 3.594 | <u>3.848</u> | 3.618 | 3.607 | 2.941 | 3.485 | 3.415 | **4.155** |
|
| 86 |
+
| TriviaQA (10M) | 10M | 4.133 | 4.414 | 4.273 | 4.313 | 4.375 | 4.391 | 4.188 | <u>4.430</u> | 4.367 | **4.621** |
|
| 87 |
+
| NarrativeQA | 538K | 1.611 | 2.567 | 2.860 | **3.638** | 3.492 | <u>3.536</u> | 1.959 | 2.628 | 2.655 | 3.395 |
|
| 88 |
+
| PopQA | 1.18M | 2.959 | 3.273 | 3.299 | <u>3.315</u> | 3.264 | 3.266 | 3.111 | 3.249 | 3.249 | **3.433** |
|
| 89 |
+
| 2WikiMultiHopQA | 722K | 1.065 | 3.055 | 3.136 | 1.187 | 3.057 | 3.159 | 1.045 | 3.180 | <u>3.330</u> | **4.280** |
|
| 90 |
+
| HotpotQA | 1.35M | 2.252 | 3.582 | 3.787 | 2.642 | 3.990 | <u>4.022</u> | 3.230 | 3.770 | 3.970 | **4.061** |
|
| 91 |
+
| MuSiQue | 1.41M | 0.936 | 1.752 | 1.928 | 1.144 | 1.960 | 1.965 | 1.020 | 1.907 | <u>2.095</u> | **2.211** |
|
| 92 |
+
| **Average** | — | 2.559 | 3.179 | 3.242 | 2.946 | 3.355 | <u>3.372</u> | 2.612 | 3.227 | 3.275 | **3.760** |
|
| 93 |
+
|
| 94 |
+
*Table 1: Same-backbone RAG vs MSA (@1/@5/@10 vs MSA @adaptive)*
|
| 95 |
+
|
| 96 |
+
---
|
| 97 |
+
|
| 98 |
+
### Table 2: MSA vs best-of-breed RAG (large backbones)
|
| 99 |
+
|
| 100 |
+
**Summary**: Against **KaLMv2+Qwen3‑235B** and **KaLMv2+Llama‑3.3‑70B** (w/ and w/o reranking), MSA achieves the best score on **4/9** datasets and an average **3.760**, with relative gains of **+7.2%**, **+5.0%**, **+10.7%**, and **+5.4%** over the strongest configurations respectively. Gaps on a few datasets (e.g., MuSiQue) are largely attributable to parameter-count and intrinsic reasoning capacity.
|
| 101 |
+
|
| 102 |
+
| Dataset | KaLMv2 + Qwen3‑235B R@1 | R@5 | R@10 | Qwen3‑235B (RR) R@1 | R@5 | R@10 | KaLMv2 + Llama‑3.3 R@1 | R@5 | R@10 | Llama‑3.3 (RR) R@1 | R@5 | R@10 | MSA (adaptive) |
|
| 103 |
+
|---------|---------------------------|------|-------|--------------------------|------|-------|------------------------------|------|--------|-------------------------|------|--------|----------------|
|
| 104 |
+
| MS MARCO v1 | 2.846 | <u>3.028</u> | 3.027 | 2.886 | 3.020 | 2.995 | 2.649 | 2.904 | 2.919 | 2.881 | 2.955 | 2.952 | **4.141** |
|
| 105 |
+
| Natural Questions | <u>3.711</u> | 3.670 | 3.694 | 3.621 | 3.610 | 3.645 | 3.675 | 3.674 | 3.662 | **3.756** | 3.665 | 3.647 | 3.545 |
|
| 106 |
+
| DuReader | 4.044 | 3.991 | 3.978 | 3.973 | 3.932 | 3.891 | <u>4.051</u> | 3.846 | 3.742 | 3.967 | 3.776 | 3.780 | **4.155** |
|
| 107 |
+
| TriviaQA (10M) | 4.367 | 4.656 | 4.578 | 4.492 | 4.320 | 4.555 | 4.273 | **4.740** | <u>4.719</u> | 4.547 | 4.703 | 4.695 | 4.621 |
|
| 108 |
+
| NarrativeQA | 1.413 | 2.130 | 2.427 | 3.212 | **3.427** | 3.375 | 1.290 | 2.123 | 2.382 | 3.150 | 3.263 | 3.317 | <u>3.395</u> |
|
| 109 |
+
| PopQA | 2.810 | 3.347 | <u>3.396</u> | 3.268 | 3.380 | 3.376 | 2.787 | 3.298 | 3.305 | 3.337 | 3.384 | 3.362 | **3.433** |
|
| 110 |
+
| 2WikiMultiHopQA | 2.646 | 3.579 | 3.582 | 1.855 | 3.381 | <u>3.583</u> | 1.339 | 3.263 | 3.445 | 1.651 | 3.332 | 3.541 | **4.280** |
|
| 111 |
+
| HotpotQA | 3.497 | 4.090 | **4.225** | 3.341 | 4.141 | 4.194 | 3.070 | 3.896 | 4.127 | 3.428 | 4.145 | <u>4.203</u> | 4.061 |
|
| 112 |
+
| MuSiQue | 1.988 | 2.462 | **2.647** | 1.801 | 2.522 | 2.605 | 1.704 | 2.317 | 2.258 | 1.895 | 2.462 | <u>2.614</u> | 2.211 |
|
| 113 |
+
| **Average** | 3.036 | 3.439 | 3.506 | 3.161 | 3.526 | <u>3.580</u> | 2.760 | 3.340 | 3.396 | 3.179 | 3.521 | 3.568 | **3.760** |
|
| 114 |
+
|
| 115 |
+
*Table 2: SOTA RAG stacks (strong retriever + large generator + optional reranker) vs MSA*
|
| 116 |
+
|
| 117 |
+
---
|
| 118 |
+
|
| 119 |
+
### RULER NIAH stability (32K→1M)
|
| 120 |
+
|
| 121 |
+
**Summary**: MSA maintains **94.84%** at **1M tokens**. The unmodified backbone collapses beyond **128K** (down to **24.69% @1M**). Hybrid linear-attention long-context models degrade noticeably at **≥128K/256K**. External-memory agents (e.g., RL‑MemoryAgent‑14B) remain stable but are weaker in **absolute accuracy** and show steeper decay than MSA.
|
| 122 |
+
|
| 123 |
+
---
|
| 124 |
+
|
| 125 |
+
## Implementation Notes
|
| 126 |
+
|
| 127 |
+
- **Training**: 158.95B-token continuous pretraining with **auxiliary routing loss**, followed by two-stage SFT (**8k→64k** curriculum).
|
| 128 |
+
- **Ablations** (paper Table 4): curriculum extension, Memory Interleave, continuous pretraining, and injecting original text all contribute substantially; removing them causes **5%–37%** drops depending on task.
|
| 129 |
+
- **Training cost**: MSA's near-linear complexity shows up directly in wall-clock time — full attention grows quadratically with context length while MSA stays close to linear.
|
| 130 |
+
|
| 131 |
+
---
|
| 132 |
+
|
| 133 |
+
## 🚀 Quick Start
|
| 134 |
+
|
| 135 |
+
> This section is self-contained. [**QUICK_START.md**](./QUICK_START.md) additionally documents a
|
| 136 |
+
> prebuilt `flash-attn` wheel, which avoids a slow source build.
|
| 137 |
+
|
| 138 |
+
### Requirements
|
| 139 |
+
|
| 140 |
+
| | |
|
| 141 |
+
|---|---|
|
| 142 |
+
| OS | Linux, x86-64 |
|
| 143 |
+
| Python | 3.12 (tested on 3.12.9) |
|
| 144 |
+
| CUDA | 12.x |
|
| 145 |
+
| GPUs | **8× 80GB** for the default benchmark suite; **2× 80GB (A800)** is enough for the `ms_100M` 100M-token setting via Memory Parallel |
|
| 146 |
+
| Disk | ~9 GB for weights + ~0.6 GB for benchmark data |
|
| 147 |
+
|
| 148 |
+
`transformers` **must** be exactly `4.51.3` — the attention implementation targets that API and will not
|
| 149 |
+
load on 4.52+ or on 5.x. The other pins in `requirements.txt` are the versions everything was tested with.
|
| 150 |
+
|
| 151 |
+
### 1. Get the code
|
| 152 |
+
|
| 153 |
+
```bash
|
| 154 |
+
pip install -U "huggingface_hub==0.31.4"
|
| 155 |
+
# If you are behind a mirror, uncomment the next line:
|
| 156 |
+
# export HF_ENDPOINT=https://hf-mirror.com
|
| 157 |
+
|
| 158 |
+
huggingface-cli download Anoy123423123/MSA-Code --repo-type=model --local-dir MSA
|
| 159 |
+
cd MSA
|
| 160 |
+
```
|
| 161 |
+
|
| 162 |
+
> `git clone https://huggingface.co/Anoy123423123/MSA-Code` works too.
|
| 163 |
+
|
| 164 |
+
### 2. Install dependencies
|
| 165 |
+
|
| 166 |
+
```bash
|
| 167 |
+
conda create -n msa python=3.12 -y
|
| 168 |
+
conda activate msa
|
| 169 |
+
pip install -r requirements.txt
|
| 170 |
+
```
|
| 171 |
+
|
| 172 |
+
`flash-attn` is installed separately because it needs `--no-build-isolation`:
|
| 173 |
+
|
| 174 |
+
```bash
|
| 175 |
+
pip install flash-attn==2.7.4.post1 --no-build-isolation
|
| 176 |
+
```
|
| 177 |
+
|
| 178 |
+
Verify:
|
| 179 |
+
|
| 180 |
+
```bash
|
| 181 |
+
python -c "import torch, transformers, flash_attn, pynvml; print(torch.__version__, transformers.__version__, flash_attn.__version__)"
|
| 182 |
+
# expected: 2.6.0 4.51.3 2.7.4.post1
|
| 183 |
+
```
|
| 184 |
+
|
| 185 |
+
### 3. Download the model weights
|
| 186 |
+
|
| 187 |
+
```bash
|
| 188 |
+
huggingface-cli download --resume-download Anoy123423123/MSA-4B --local-dir ckpt/MSA-4B
|
| 189 |
+
```
|
| 190 |
+
|
| 191 |
+
This is the path `scripts/run_benchmarks.sh` expects (`model_path=ckpt/MSA-4B`). ~9 GB.
|
| 192 |
+
|
| 193 |
+
### 4. Benchmark data
|
| 194 |
+
|
| 195 |
+
Nothing to do — benchmark files are fetched on first use from
|
| 196 |
+
[`Anoy123423123/MSA-RAG-BENCHMARKS`](https://huggingface.co/datasets/Anoy123423123/MSA-RAG-BENCHMARKS)
|
| 197 |
+
into `./data/<benchmark>/`. Each benchmark is two pickle files: `mdata_*.pkl` (memory corpus) and
|
| 198 |
+
`qdata_*.pkl` (queries). ~0.6 GB for everything; `ms_100M` alone is 440 MB.
|
| 199 |
+
|
| 200 |
+
### 5. Run inference
|
| 201 |
+
|
| 202 |
+
```bash
|
| 203 |
+
bash scripts/run_benchmarks.sh eval_benchmark
|
| 204 |
+
```
|
| 205 |
+
|
| 206 |
+
Results land in `src/evaluation/outputs/eval_benchmark/` — one `.log` and one `.json` per benchmark —
|
| 207 |
+
and retrieval precision metrics are printed as each benchmark finishes.
|
| 208 |
+
|
| 209 |
+
Check the top of `scripts/run_benchmarks.sh` first:
|
| 210 |
+
|
| 211 |
+
```bash
|
| 212 |
+
export CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 # defaults to 8 GPUs
|
| 213 |
+
model_path=ckpt/MSA-4B
|
| 214 |
+
```
|
| 215 |
+
|
| 216 |
+
The `benchmarks` array holds `name:batch_size` entries. **Batch size must be scaled together with the
|
| 217 |
+
GPU count**: the memory corpus is partitioned into one bucket per visible GPU, so the defaults below
|
| 218 |
+
assume 8 GPUs.
|
| 219 |
+
|
| 220 |
+
| Category | Benchmark | Default batch size |
|
| 221 |
+
|---|---|---|
|
| 222 |
+
| Multi-hop QA | `2wikimultihopqa`, `hotpotqa`, `musique` | 16 |
|
| 223 |
+
| Single-hop QA | `nature_questions`, `triviaqa_06M` | 16 |
|
| 224 |
+
| Single-hop QA (large corpus) | `triviaqa_10M` | 2 |
|
| 225 |
+
| HippoRAG | `hipporag_narrative`, `hipporag_popqa` | 16 |
|
| 226 |
+
| Passage retrieval / multilingual | `msmarco_v1`, `dureader` | 16 |
|
| 227 |
+
| Length scaling (100M tokens) | `ms_100M` | 16 |
|
| 228 |
+
|
| 229 |
+
`ms_100M` and `triviaqa_06M` are commented out by default — uncomment them to reproduce the
|
| 230 |
+
length-scaling results. `ms_100M` is the 100M-token setting and is by far the heaviest run.
|
| 231 |
+
|
| 232 |
+
### 6. Score the answers
|
| 233 |
+
|
| 234 |
+
Answer quality is graded by an LLM judge served through OpenRouter:
|
| 235 |
+
|
| 236 |
+
```bash
|
| 237 |
+
export OPENROUTER_API_KEY=<your key> # or edit scripts/calculate_llm_score.sh
|
| 238 |
+
bash scripts/calculate_llm_score.sh eval_benchmark
|
| 239 |
+
```
|
| 240 |
+
|
| 241 |
+
The argument must match the log directory name from step 5.
|
| 242 |
+
|
| 243 |
+
---
|
| 244 |
+
|
| 245 |
+
## Project Structure
|
| 246 |
+
|
| 247 |
+
```
|
| 248 |
+
├── scripts/
|
| 249 |
+
│ ├── run_benchmarks.sh # Run inference on benchmarks
|
| 250 |
+
│ ├── calculate_llm_score.sh # LLM-based answer evaluation
|
| 251 |
+
│ └── resave_model.sh # Convert base model to MSA format
|
| 252 |
+
└── src/
|
| 253 |
+
├── msa/ # Core MSA implementation
|
| 254 |
+
│ ├── configuration_msa.py # MSA model configuration
|
| 255 |
+
│ ├── memory_sparse_attention.py # MemorySparseAttention layer
|
| 256 |
+
│ ├── model.py # MSAForCausalLM / MSAModel
|
| 257 |
+
│ └── generate.py # Generation logic
|
| 258 |
+
├── config/
|
| 259 |
+
│ └── memory_config.py # GenerateConfig, ModelConfig, MemoryConfig
|
| 260 |
+
├── evaluation/
|
| 261 |
+
│ └── llm_judge.py # LLM-based evaluation metrics
|
| 262 |
+
├── app/
|
| 263 |
+
│ └── benchmark.py # Benchmark runner
|
| 264 |
+
├── utils/ # GPU workers, caching, templates, etc.
|
| 265 |
+
├── msa_service.py # Multi-GPU inference engine (MSAEngine)
|
| 266 |
+
���── prefill.py # Stage 1 prefill worker
|
| 267 |
+
├── benchmarks.py # Benchmark registry & specs
|
| 268 |
+
└── types.py # Core type definitions
|
| 269 |
+
```
|
| 270 |
+
|
| 271 |
+
---
|
| 272 |
+
|
| 273 |
+
## Troubleshooting
|
| 274 |
+
|
| 275 |
+
| Symptom | Cause / fix |
|
| 276 |
+
|---|---|
|
| 277 |
+
| `KeyError` / `AttributeError` while loading the model | Wrong `transformers` version. It must be exactly `4.51.3`. |
|
| 278 |
+
| `import pynvml` fails or NVML calls are missing | The `pynvml` module here comes from `nvidia-ml-py`. If the separate, deprecated `pynvml` distribution is also installed it shadows the module: `pip uninstall -y pynvml && pip install nvidia-ml-py==12.575.51`. |
|
| 279 |
+
| CUDA OOM | Lower the per-benchmark `batch_size` in `scripts/run_benchmarks.sh`, or reduce the GPU count together with the batch size. |
|
| 280 |
+
| `flash_attn` import error | The wheel must match your torch / Python / CUDA combination. Build from source with `--no-build-isolation`. |
|
| 281 |
+
| Benchmark data download stalls | Set `export HF_ENDPOINT=https://hf-mirror.com` and retry; partial files resume. |
|
| 282 |
+
|
| 283 |
+
---
|
| 284 |
+
|
| 285 |
+
## Citation
|
| 286 |
+
|
| 287 |
+
Citation details are withheld during anonymous peer review.
|
| 288 |
+
|
| 289 |
+
## License
|
| 290 |
+
|
| 291 |
+
MIT.
|
requirements.txt
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Tested with Python 3.12.9 on CUDA 12.x.
|
| 2 |
+
# NOTE: flash-attn is intentionally NOT listed here, it needs --no-build-isolation.
|
| 3 |
+
# Install it separately, see step 3 of QUICK_START.md.
|
| 4 |
+
|
| 5 |
+
# --- Core model stack (versions are strict: MSA targets the transformers 4.51 API) ---
|
| 6 |
+
torch==2.6.0
|
| 7 |
+
torchvision==0.21.0
|
| 8 |
+
transformers==4.51.3
|
| 9 |
+
accelerate==1.0.1
|
| 10 |
+
liger_kernel==0.5.10
|
| 11 |
+
|
| 12 |
+
# --- Hub / data IO ---
|
| 13 |
+
# huggingface_hub must stay <1.0 to remain compatible with transformers 4.51.3.
|
| 14 |
+
huggingface_hub==0.31.4
|
| 15 |
+
datasets==3.1.0
|
| 16 |
+
lmdb==1.6.2
|
| 17 |
+
tqdm==4.67.1
|
| 18 |
+
|
| 19 |
+
# --- Numerics ---
|
| 20 |
+
numpy==1.26.4
|
| 21 |
+
pillow==11.2.1
|
| 22 |
+
packaging==25.0
|
| 23 |
+
|
| 24 |
+
# --- GPU monitoring: provides the `pynvml` module.
|
| 25 |
+
# Do NOT replace with the deprecated `pynvml` package, its import path differs.
|
| 26 |
+
nvidia-ml-py==12.575.51
|
| 27 |
+
|
| 28 |
+
# --- LLM-based answer scoring (scripts/calculate_llm_score.sh) ---
|
| 29 |
+
openai==1.79.0
|
scripts/calculate_llm_score.sh
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
export OPENROUTER_API_KEY="your_openrouter_key"
|
| 4 |
+
exp_name=$1
|
| 5 |
+
python src/evaluation/llm_judge.py src/evaluation/outputs/${exp_name}
|
scripts/resave_model.sh
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 模型超参数
|
| 2 |
+
export POOLING_KERNEL_SIZE=64
|
| 3 |
+
export TOP_K_DOCS=16
|
| 4 |
+
|
| 5 |
+
# trick
|
| 6 |
+
export AUX_LOSS="false"
|
| 7 |
+
|
| 8 |
+
export REWRITE_POSITION="true"
|
| 9 |
+
export ROUTER_LAYER_IDX="18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35"
|
| 10 |
+
# export ROUTER_LAYER_IDX="20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39"
|
| 11 |
+
|
| 12 |
+
# special tokens
|
| 13 |
+
export ADD_MEM_TOKENS="false"
|
| 14 |
+
export NUM_PREFIX_TOKENS=64
|
| 15 |
+
export NUM_TAIL_TOKENS=64
|
| 16 |
+
export FIRST_ADD_MEM_TOKENS="false"
|
| 17 |
+
export WARM_UP_MEM_TOKENS="false"
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
# loss weight
|
| 21 |
+
export LMLOSS_WEIGHT=1.0
|
| 22 |
+
export REC_LOSS_WEIGHT=0.0
|
| 23 |
+
export AUX_LOSS_WEIGHT=0.1
|
| 24 |
+
export ANS_LOSS_WEIGHT=1.0
|
| 25 |
+
export AUX_LOSS_METHOD="INFONCE_DECOUPLE" # INFONCE, INFONCE_FOCAL, BCE, INFONCE_DECOUPLE, INFONCE_DECOUPLE_FOCAL
|
| 26 |
+
export INFONCE_LOSS_TEMP=0.1 # 如果使用infonce的话
|
| 27 |
+
|
| 28 |
+
# 结构搜索
|
| 29 |
+
export DECOUPLE_ROUTER="true"
|
| 30 |
+
export HEAD_REDUCE_METHOD="mean"
|
| 31 |
+
export QUERY_REDUCE_METHOD="max"
|
| 32 |
+
export CHUNK_REDUCE_METHOD="max"
|
| 33 |
+
export DECOUPLE_POOLING_MODE="mean"
|
| 34 |
+
|
| 35 |
+
ORIGIN_MODEL_PATH=$1
|
| 36 |
+
SAVE_MODEL_PATH=$2
|
| 37 |
+
|
| 38 |
+
python src/utils/resave_model.py $ORIGIN_MODEL_PATH $SAVE_MODEL_PATH
|
scripts/run_benchmarks.sh
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
export MASTER_PORT=29509
|
| 3 |
+
export CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7
|
| 4 |
+
# export CUDA_VISIBLE_DEVICES=0,1
|
| 5 |
+
|
| 6 |
+
# model
|
| 7 |
+
model_path=ckpt/MSA-4B
|
| 8 |
+
|
| 9 |
+
# data
|
| 10 |
+
# bench_name:batch_size
|
| 11 |
+
# Adjust the batch size appropriately based on the number of GPUs.
|
| 12 |
+
benchmarks=(
|
| 13 |
+
# "ms_100M:16"
|
| 14 |
+
"hipporag_narrative:16"
|
| 15 |
+
"nature_questions:16"
|
| 16 |
+
"2wikimultihopqa:16"
|
| 17 |
+
"hotpotqa:16"
|
| 18 |
+
"musique:16"
|
| 19 |
+
"hipporag_popqa:16"
|
| 20 |
+
# "triviaqa_06M:16"
|
| 21 |
+
"triviaqa_10M:2"
|
| 22 |
+
"dureader:16"
|
| 23 |
+
"msmarco_v1:16"
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
top_p=0.9
|
| 27 |
+
temperature=0.0
|
| 28 |
+
max_length=2048
|
| 29 |
+
template=QWEN3_INSTRUCT_TEMPLATE
|
| 30 |
+
|
| 31 |
+
# Create log directory (use first argument as custom name, otherwise use timestamp)
|
| 32 |
+
log_name="${1:-eval_logs_$(date +%Y%m%d_%H%M%S)}"
|
| 33 |
+
log_dir="./src/evaluation/outputs/${log_name}"
|
| 34 |
+
if [ -d "$log_dir" ]; then
|
| 35 |
+
echo "Error: log directory already exists: $log_dir"
|
| 36 |
+
exit 1
|
| 37 |
+
fi
|
| 38 |
+
mkdir -p "$log_dir"
|
| 39 |
+
|
| 40 |
+
# Statistics
|
| 41 |
+
total_benchmarks=${#benchmarks[@]}
|
| 42 |
+
current=0
|
| 43 |
+
success_count=0
|
| 44 |
+
fail_count=0
|
| 45 |
+
failed_benchmarks=()
|
| 46 |
+
|
| 47 |
+
echo "=========================================="
|
| 48 |
+
echo "Start running all benchmark evaluations"
|
| 49 |
+
echo "Total: ${total_benchmarks} benchmarks"
|
| 50 |
+
echo "Log directory: $log_dir"
|
| 51 |
+
echo "=========================================="
|
| 52 |
+
echo ""
|
| 53 |
+
|
| 54 |
+
# Run each benchmark
|
| 55 |
+
for entry in "${benchmarks[@]}"; do
|
| 56 |
+
benchmark="${entry%%:*}"
|
| 57 |
+
batch_size="${entry##*:}"
|
| 58 |
+
current=$((current + 1))
|
| 59 |
+
echo "[$current/$total_benchmarks] Running: $benchmark (batch_size=$batch_size)"
|
| 60 |
+
echo "Start time: $(date '+%Y-%m-%d %H:%M:%S')"
|
| 61 |
+
|
| 62 |
+
# Create separate log file for each benchmark
|
| 63 |
+
log_file="$log_dir/${benchmark}.log"
|
| 64 |
+
json_file="$log_dir/${benchmark}.json"
|
| 65 |
+
|
| 66 |
+
# Run evaluation and record logs
|
| 67 |
+
python -u src/app/benchmark.py \
|
| 68 |
+
--benchmark "$benchmark" \
|
| 69 |
+
--model_path "$model_path" \
|
| 70 |
+
--top_p "$top_p" \
|
| 71 |
+
--temperature "$temperature" \
|
| 72 |
+
--max_length "$max_length" \
|
| 73 |
+
--template "$template" \
|
| 74 |
+
--output_file "$json_file" \
|
| 75 |
+
--max_batch_size "$batch_size" \
|
| 76 |
+
--max_chunk_per_block 16384 \
|
| 77 |
+
--block_size 2048 \
|
| 78 |
+
2>&1 | tee $log_file
|
| 79 |
+
|
| 80 |
+
# Check exit status
|
| 81 |
+
exit_code=${PIPESTATUS[0]}
|
| 82 |
+
|
| 83 |
+
if [ $exit_code -eq 0 ]; then
|
| 84 |
+
echo "[$current/$total_benchmarks] $benchmark finished (success)"
|
| 85 |
+
# Print benchmark name and metrics
|
| 86 |
+
echo "========== $benchmark Results =========="
|
| 87 |
+
python -c "import json; d=json.load(open('$json_file')); [print(f' {k}: {v}') for k,v in d.get(list(d.keys())[0],{}).get('precision',{}).get('metrics',{}).items()]" 2>/dev/null || echo " (failed to parse metrics)"
|
| 88 |
+
echo "========================================"
|
| 89 |
+
success_count=$((success_count + 1))
|
| 90 |
+
else
|
| 91 |
+
echo "[$current/$total_benchmarks] $benchmark failed (exit code: $exit_code)"
|
| 92 |
+
fail_count=$((fail_count + 1))
|
| 93 |
+
failed_benchmarks+=("$benchmark")
|
| 94 |
+
fi
|
| 95 |
+
|
| 96 |
+
echo "End time: $(date '+%Y-%m-%d %H:%M:%S')"
|
| 97 |
+
echo "----------------------------------------"
|
| 98 |
+
echo ""
|
| 99 |
+
done
|
| 100 |
+
|
| 101 |
+
# Summary
|
| 102 |
+
echo "=========================================="
|
| 103 |
+
echo "All benchmark evaluations completed"
|
| 104 |
+
echo "=========================================="
|
| 105 |
+
echo "Total: $total_benchmarks"
|
| 106 |
+
echo "Success: $success_count"
|
| 107 |
+
echo "Failed: $fail_count"
|
| 108 |
+
echo ""
|
| 109 |
+
|
| 110 |
+
if [ $fail_count -gt 0 ]; then
|
| 111 |
+
echo "Failed benchmarks:"
|
| 112 |
+
for failed in "${failed_benchmarks[@]}"; do
|
| 113 |
+
echo " - $failed"
|
| 114 |
+
done
|
| 115 |
+
echo ""
|
| 116 |
+
fi
|
| 117 |
+
|
| 118 |
+
echo "All logs saved in: $log_dir"
|
| 119 |
+
echo "=========================================="
|
src/__init__.py
ADDED
|
File without changes
|
src/app/__init__.py
ADDED
|
File without changes
|
src/app/benchmark.py
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
from collections import OrderedDict
|
| 3 |
+
import pickle
|
| 4 |
+
import re
|
| 5 |
+
from typing import Dict, List
|
| 6 |
+
from tqdm import tqdm
|
| 7 |
+
import json
|
| 8 |
+
import torch
|
| 9 |
+
import numpy as np
|
| 10 |
+
import pathlib
|
| 11 |
+
import sys
|
| 12 |
+
import os
|
| 13 |
+
import random
|
| 14 |
+
import numpy as np
|
| 15 |
+
import multiprocessing as mp
|
| 16 |
+
|
| 17 |
+
project_path = pathlib.Path(__file__).parent.parent.parent
|
| 18 |
+
sys.path.append(str(project_path))
|
| 19 |
+
|
| 20 |
+
from src.benchmarks import BenchMarks
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def set_seed(seed):
|
| 24 |
+
"""
|
| 25 |
+
固定所有随机种子以确保实验的可复现性。
|
| 26 |
+
"""
|
| 27 |
+
random.seed(seed)
|
| 28 |
+
np.random.seed(seed)
|
| 29 |
+
torch.manual_seed(seed)
|
| 30 |
+
if torch.cuda.is_available():
|
| 31 |
+
torch.cuda.manual_seed(seed)
|
| 32 |
+
torch.cuda.manual_seed_all(seed) # 适用于多GPU环境
|
| 33 |
+
os.environ['PYTHONHASHSEED'] = str(seed)
|
| 34 |
+
|
| 35 |
+
# 在程序的开始部分调用此函数
|
| 36 |
+
seed_value = 42
|
| 37 |
+
set_seed(seed_value)
|
| 38 |
+
|
| 39 |
+
def parse_benchmark_file(args):
|
| 40 |
+
benchmark = BenchMarks(bench_name=args.benchmark)
|
| 41 |
+
path, mem_path = benchmark.get_bench_files()
|
| 42 |
+
args.query_file = path
|
| 43 |
+
args.memory_file = mem_path
|
| 44 |
+
print(" ==========> load query file from:", path)
|
| 45 |
+
if path.endswith(".json"):
|
| 46 |
+
with open(path, "r") as f:
|
| 47 |
+
raw_data = json.load(f)
|
| 48 |
+
## 重组数据集
|
| 49 |
+
data = [{
|
| 50 |
+
'question': d['question'],
|
| 51 |
+
'labels': d['labels'],
|
| 52 |
+
'answer': d['answer']
|
| 53 |
+
} for d in raw_data]
|
| 54 |
+
# 判断是否是一个路径
|
| 55 |
+
elif path.endswith("pkl"):
|
| 56 |
+
with open(path, "rb") as f:
|
| 57 |
+
query_metas = pickle.load(f)
|
| 58 |
+
|
| 59 |
+
data = [{
|
| 60 |
+
'question': q_meta['query'],
|
| 61 |
+
'labels': q_meta['reference_list'],
|
| 62 |
+
'answer': q_meta['answer']
|
| 63 |
+
} for q_meta in query_metas]
|
| 64 |
+
else:
|
| 65 |
+
raise ValueError(f"Unsupported file format: {path}")
|
| 66 |
+
print(f"num sample: {len(data)}")
|
| 67 |
+
return data
|
| 68 |
+
|
| 69 |
+
def sort_requests(data_items: List[Dict]) -> List[dict]:
|
| 70 |
+
"""
|
| 71 |
+
创建动态批次:按照input_id长度排序,然后根据max_input_length分批
|
| 72 |
+
"""
|
| 73 |
+
print("Tokenizing and sorting data for dynamic batching...")
|
| 74 |
+
|
| 75 |
+
# 计算每个item的input_id长度
|
| 76 |
+
items_with_length = []
|
| 77 |
+
for item in tqdm(data_items):
|
| 78 |
+
prompt = item["question"]
|
| 79 |
+
length = len(prompt)
|
| 80 |
+
|
| 81 |
+
items_with_length.append((item, length))
|
| 82 |
+
|
| 83 |
+
# 按长度排序
|
| 84 |
+
items_with_length.sort(key=lambda x: x[1])
|
| 85 |
+
return [item[0] for item in items_with_length]
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def base_it(predict, label, at, score_func):
|
| 89 |
+
assert len(predict) == len(label)
|
| 90 |
+
scores = []
|
| 91 |
+
for pred, lbs in zip(predict, label):
|
| 92 |
+
pred = pred.tolist() if not isinstance(pred, list) else pred
|
| 93 |
+
best_score = 0.
|
| 94 |
+
if not isinstance(lbs, list):
|
| 95 |
+
lbs = [lbs]
|
| 96 |
+
for lb in lbs:
|
| 97 |
+
if isinstance(lb, list):
|
| 98 |
+
lb = lb[0]
|
| 99 |
+
rank = pred[:at].index(lb) + 1 if lb in pred[:at] else 0
|
| 100 |
+
cur_score = score_func(rank)
|
| 101 |
+
best_score = max(best_score, cur_score)
|
| 102 |
+
scores.append(best_score)
|
| 103 |
+
return scores
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
def eval_recall(predict, label, at=10):
|
| 107 |
+
scores = base_it(predict, label, at, lambda rank: int(rank != 0))
|
| 108 |
+
return {f'R@{at}': sum(scores) / len(scores)}
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def eval_mrr(predict, label, at=10):
|
| 112 |
+
scores = base_it(predict, label, at, lambda rank: 1 / rank if rank != 0 else 0)
|
| 113 |
+
return {f'MRR@{at}': sum(scores) / len(scores)}
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
def eval_all(predict, label):
|
| 117 |
+
log_dict = {}
|
| 118 |
+
log_dict.update(eval_recall(predict, label, at=1))
|
| 119 |
+
log_dict.update(eval_recall(predict, label, at=5))
|
| 120 |
+
log_dict.update(eval_recall(predict, label, at=10))
|
| 121 |
+
log_dict.update(eval_mrr(predict, label, at=1))
|
| 122 |
+
return log_dict
|
| 123 |
+
|
| 124 |
+
def calculate_ir_metrics(true_labels: List[int], pred_labels: List[int]):
|
| 125 |
+
"""计算信息检索中的 Precision, Recall, F1, 和 IoU。"""
|
| 126 |
+
true_set = set(true_labels)
|
| 127 |
+
pred_set = set(pred_labels)
|
| 128 |
+
if not true_set: return {'precision': 0.0, 'recall': 0.0, 'f1': 0.0, 'iou': 0.0}
|
| 129 |
+
tp = len(true_set & pred_set)
|
| 130 |
+
precision = tp / len(pred_set) if pred_set else 0.0
|
| 131 |
+
recall = tp / len(true_set)
|
| 132 |
+
f1 = 2 * (precision * recall) / (precision + recall) if (precision + recall) > 0 else 0.0
|
| 133 |
+
iou = len(true_set & pred_set) / len(true_set | pred_set) if len(true_set | pred_set) > 0 else 0.0
|
| 134 |
+
|
| 135 |
+
return {'precision': precision, 'recall': recall, 'f1': f1, 'iou': iou}
|
| 136 |
+
|
| 137 |
+
def process_results(requests: List[Dict], index_to_doc, doc_to_index):
|
| 138 |
+
num_error = 0
|
| 139 |
+
record_list = []
|
| 140 |
+
all_metrics = {"precision": [], "recall": [], "f1": [], "iou": []}
|
| 141 |
+
|
| 142 |
+
for request in requests:
|
| 143 |
+
question = request["question"]
|
| 144 |
+
answer = request["answer"]
|
| 145 |
+
generated_text = request["response"].replace('<|endoftext|>', '')
|
| 146 |
+
generated_text = "\nPlease answer the question based" + generated_text.split("\nPlease answer the question based")[1]
|
| 147 |
+
|
| 148 |
+
labels = [doc_to_index[txt] for txt in request["labels"]]
|
| 149 |
+
predictions = list(set(map(int, re.findall(r'\[(\d+)\]', generated_text))))
|
| 150 |
+
|
| 151 |
+
try:
|
| 152 |
+
pred_answer = generated_text.split('The answer to the question is:')[-1].split("<|im_end|>")[0].strip()
|
| 153 |
+
except Exception as e:
|
| 154 |
+
pred_answer = ""
|
| 155 |
+
raise ValueError(f"输出格式异常: {e}, generated_text: {generated_text}")
|
| 156 |
+
|
| 157 |
+
try:
|
| 158 |
+
record_list.append({
|
| 159 |
+
"labels_id": labels,
|
| 160 |
+
"pred_id": predictions,
|
| 161 |
+
"question": question,
|
| 162 |
+
"true_answer": answer,
|
| 163 |
+
"pred_answer": pred_answer,
|
| 164 |
+
"generated_text": generated_text,
|
| 165 |
+
"predict_context": [{i: index_to_doc[pid]} for i, pid in enumerate(predictions)],
|
| 166 |
+
"gt_context": [{i: index_to_doc[pid]} for i, pid in enumerate(labels)],
|
| 167 |
+
})
|
| 168 |
+
except Exception as e:
|
| 169 |
+
num_error += 1
|
| 170 |
+
|
| 171 |
+
metrics = calculate_ir_metrics(labels, predictions)
|
| 172 |
+
for k, v in metrics.items():
|
| 173 |
+
all_metrics[k].append(v)
|
| 174 |
+
|
| 175 |
+
metrics_dict = {k: round(float(np.mean(v)), 4) for k, v in all_metrics.items()}
|
| 176 |
+
print(" ==================== Retrieve metrics ======================= ")
|
| 177 |
+
print("AR Metrics: ", metrics_dict)
|
| 178 |
+
print(" ==================== Retrieve metrics ======================= ")
|
| 179 |
+
return {"metrics": metrics_dict, "record_list": record_list}
|
| 180 |
+
|
| 181 |
+
def parse_args():
|
| 182 |
+
parser = argparse.ArgumentParser()
|
| 183 |
+
parser.add_argument('--benchmark', type=str)
|
| 184 |
+
parser.add_argument('--max_batch_size', type=int)
|
| 185 |
+
parser.add_argument('--model_path', type=str)
|
| 186 |
+
parser.add_argument('--top_p', type=float, default=0.9)
|
| 187 |
+
parser.add_argument('--temperature', type=float, default=0.0)
|
| 188 |
+
parser.add_argument('--block_size', type=int, default=2048) # tokens for one memory inference
|
| 189 |
+
parser.add_argument('--max_chunk_per_block', type=int, default=16*1024) # chunks per block slice
|
| 190 |
+
parser.add_argument('--max_length', type=int, default=64) # max output length
|
| 191 |
+
parser.add_argument('--max_seq_len', type=int, default=0) # max input+output length
|
| 192 |
+
parser.add_argument('--max_query_seq_len', type=int, default=0) # max input seq len
|
| 193 |
+
parser.add_argument('--template', type=str, default="QWEN3_TEMPLATE")
|
| 194 |
+
parser.add_argument('--output_file', type=str, default="")
|
| 195 |
+
parser.add_argument('--case_name', type=str, default="anonymous")
|
| 196 |
+
|
| 197 |
+
args = parser.parse_args()
|
| 198 |
+
|
| 199 |
+
return args
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
def should_regenerate(request:dict, response: str):
|
| 203 |
+
"""return a text if the response should be regenerated, or return None"""
|
| 204 |
+
|
| 205 |
+
if response[-len("<|object_ref_end|>"):] == "<|object_ref_end|>" and "The answer to the question is:" not in response:
|
| 206 |
+
if not request.get('regenerated', False):
|
| 207 |
+
request["regenerated"] = True
|
| 208 |
+
response = "\nPlease answer the question based"+response.split("\nPlease answer the question based")[1]
|
| 209 |
+
response = response.replace('<|endoftext|>', '')
|
| 210 |
+
return "<regenerate>"+ response
|
| 211 |
+
|
| 212 |
+
return None
|
| 213 |
+
|
| 214 |
+
def read_config_to_args(config_path):
|
| 215 |
+
with open(os.path.join(config_path, "config.json"), 'r') as f:
|
| 216 |
+
msa_config = json.load(f).get("msa_config")
|
| 217 |
+
args.doc_top_k = msa_config.get("doc_top_k", 16)
|
| 218 |
+
args.pooling_kernel_size = msa_config.get("pooling_kernel_size", 64)
|
| 219 |
+
args.router_layer_idx = msa_config.get("router_layer_idx", "all")
|
| 220 |
+
return args, msa_config
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
def msa_benchmark(args, data):
|
| 224 |
+
|
| 225 |
+
from src.msa_service import GenerateConfig, ModelConfig, MemoryConfig, MSAEngine
|
| 226 |
+
args, msa_config = read_config_to_args(args.model_path)
|
| 227 |
+
|
| 228 |
+
model_config = ModelConfig(model_path=args.model_path,
|
| 229 |
+
doc_top_k=args.doc_top_k,
|
| 230 |
+
pooling_kernel_size=args.pooling_kernel_size,
|
| 231 |
+
router_layer_idx=args.router_layer_idx,
|
| 232 |
+
)
|
| 233 |
+
generate_config = GenerateConfig(devices=list(range(torch.cuda.device_count())),
|
| 234 |
+
template=args.template,
|
| 235 |
+
max_generate_tokens=args.max_length,
|
| 236 |
+
max_seq_len=args.max_seq_len,
|
| 237 |
+
max_query_seq_len=args.max_query_seq_len,
|
| 238 |
+
max_batch_size=args.max_batch_size,
|
| 239 |
+
top_p=args.top_p,
|
| 240 |
+
temperature=args.temperature,
|
| 241 |
+
qa_mode=True)
|
| 242 |
+
memory_config = MemoryConfig(block_size=args.block_size,
|
| 243 |
+
pooling_kernel_size=args.pooling_kernel_size,
|
| 244 |
+
slice_chunk_size=args.max_chunk_per_block,
|
| 245 |
+
memory_file_path=args.memory_file,
|
| 246 |
+
)
|
| 247 |
+
|
| 248 |
+
final_result = {}
|
| 249 |
+
if args.output_file:
|
| 250 |
+
try:
|
| 251 |
+
with open(args.output_file, 'r') as f:
|
| 252 |
+
exist_result = json.load(f)
|
| 253 |
+
final_result = exist_result[args.case_name]
|
| 254 |
+
except:
|
| 255 |
+
pass
|
| 256 |
+
|
| 257 |
+
with MSAEngine(generate_config, model_config, memory_config) as engine:
|
| 258 |
+
print("start precision test")
|
| 259 |
+
idx_to_doc = engine.get_idx_to_doc()
|
| 260 |
+
doc_to_idx = {v: k for k, v in idx_to_doc.items()}
|
| 261 |
+
|
| 262 |
+
bsz = args.max_batch_size * generate_config.world
|
| 263 |
+
sorted_requests = sort_requests(data)
|
| 264 |
+
requests = OrderedDict({idx: item for idx, item in enumerate(sorted_requests)})
|
| 265 |
+
|
| 266 |
+
for req_idx in requests:
|
| 267 |
+
requests[req_idx]['idx'] = req_idx
|
| 268 |
+
|
| 269 |
+
total = len(requests)
|
| 270 |
+
results = [] # processed requests
|
| 271 |
+
pbar = tqdm(total=total, desc=f"Precision Test")
|
| 272 |
+
|
| 273 |
+
to_send = {}
|
| 274 |
+
while len(results) < total:
|
| 275 |
+
num = min(bsz-len(to_send), len(requests))
|
| 276 |
+
for _ in range(num):
|
| 277 |
+
idx, request = requests.popitem(last=False)
|
| 278 |
+
to_send[idx] = request
|
| 279 |
+
|
| 280 |
+
prompts, indices = [], []
|
| 281 |
+
for idx, request in to_send.items():
|
| 282 |
+
prompts.append(request.get("new_question", request["question"]))
|
| 283 |
+
indices.append(idx)
|
| 284 |
+
|
| 285 |
+
texts, recall_topks, _ = engine.generate(prompts, require_recall_topk=True)
|
| 286 |
+
|
| 287 |
+
for idx, response in enumerate(texts):
|
| 288 |
+
req_idx = indices[idx]
|
| 289 |
+
request = to_send[req_idx]
|
| 290 |
+
response = "\nPlease answer the question based"+response.split("\nPlease answer the question based")[1]
|
| 291 |
+
response = response.replace('<|endoftext|>', '')
|
| 292 |
+
new_prompt = should_regenerate(request, response)
|
| 293 |
+
|
| 294 |
+
# 判断是否需要重新生成
|
| 295 |
+
if new_prompt is not None:
|
| 296 |
+
request["new_question"] = new_prompt
|
| 297 |
+
else:
|
| 298 |
+
recall_topk = {layer: v[idx] for layer, v in recall_topks.items()}
|
| 299 |
+
request = to_send.pop(req_idx)
|
| 300 |
+
request['recall_topk'] = recall_topk
|
| 301 |
+
request["response"] = response
|
| 302 |
+
results.append(request)
|
| 303 |
+
pbar.update(bsz - len(to_send))
|
| 304 |
+
pbar.close()
|
| 305 |
+
|
| 306 |
+
assert len(results) == total, \
|
| 307 |
+
f"Results count mismatch: got {len(results)}, expected {total} (from query_file)"
|
| 308 |
+
|
| 309 |
+
final_result['precision'] = process_results(results, idx_to_doc, doc_to_idx)
|
| 310 |
+
|
| 311 |
+
if args.output_file:
|
| 312 |
+
exist_result = {}
|
| 313 |
+
try:
|
| 314 |
+
with open(args.output_file, 'r') as f:
|
| 315 |
+
exist_result = json.load(f)
|
| 316 |
+
except:
|
| 317 |
+
pass
|
| 318 |
+
exist_result[args.case_name] = final_result
|
| 319 |
+
with open(args.output_file, 'w') as f:
|
| 320 |
+
json.dump(exist_result, f, indent=4, ensure_ascii=False)
|
| 321 |
+
else:
|
| 322 |
+
s = json.dumps(final_result, indent=4, ensure_ascii=False)
|
| 323 |
+
print(s)
|
| 324 |
+
|
| 325 |
+
if __name__ == "__main__":
|
| 326 |
+
mp.set_start_method('spawn')
|
| 327 |
+
args = parse_args()
|
| 328 |
+
assert args.template in ["QWEN3_TEMPLATE", "QWEN3_INSTRUCT_TEMPLATE"]
|
| 329 |
+
if args.output_file:
|
| 330 |
+
assert args.case_name != "", "when output result to a file, please give this test case a name"
|
| 331 |
+
|
| 332 |
+
print(json.dumps(vars(args), indent=4, sort_keys=True))
|
| 333 |
+
|
| 334 |
+
data = parse_benchmark_file(args)
|
| 335 |
+
msa_benchmark(args, data)
|
src/benchmarks.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from dataclasses import dataclass
|
| 3 |
+
from enum import Enum, auto
|
| 4 |
+
from typing import ClassVar
|
| 5 |
+
|
| 6 |
+
from huggingface_hub import hf_hub_download
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class Category(Enum):
|
| 10 |
+
"""Benchmark categories with associated root directories and path patterns."""
|
| 11 |
+
RAG = auto()
|
| 12 |
+
RAG_0108 = auto()
|
| 13 |
+
LENGTH_SCALE = auto()
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
# ============================================================================
|
| 17 |
+
# HuggingFace config & local data root
|
| 18 |
+
# ============================================================================
|
| 19 |
+
|
| 20 |
+
HF_REPO_ID = "Anoy123423123/MSA-RAG-BENCHMARKS"
|
| 21 |
+
_DATA_ROOT = os.path.join(os.getcwd(), "data")
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
@dataclass(frozen=True)
|
| 25 |
+
class BenchmarkSpec:
|
| 26 |
+
"""Immutable specification for a single benchmark's file layout."""
|
| 27 |
+
bench_name: str # benchmark name, also the HF subdirectory
|
| 28 |
+
query_file: str
|
| 29 |
+
memory_file: str
|
| 30 |
+
|
| 31 |
+
def _resolve(self, filename: str) -> str:
|
| 32 |
+
"""Return local path if cached, otherwise download from HF into data/."""
|
| 33 |
+
local_path = os.path.join(_DATA_ROOT, self.bench_name, filename)
|
| 34 |
+
if os.path.exists(local_path):
|
| 35 |
+
return local_path
|
| 36 |
+
os.makedirs(os.path.dirname(local_path), exist_ok=True)
|
| 37 |
+
return hf_hub_download(
|
| 38 |
+
repo_id=HF_REPO_ID,
|
| 39 |
+
filename=f"{self.bench_name}/{filename}",
|
| 40 |
+
repo_type="dataset",
|
| 41 |
+
local_dir=_DATA_ROOT,
|
| 42 |
+
)
|
| 43 |
+
|
| 44 |
+
@property
|
| 45 |
+
def query_path(self) -> str:
|
| 46 |
+
return self._resolve(self.query_file)
|
| 47 |
+
|
| 48 |
+
@property
|
| 49 |
+
def memory_path(self) -> str:
|
| 50 |
+
return self._resolve(self.memory_file)
|
| 51 |
+
|
| 52 |
+
def get_bench_files(self) -> tuple[str, str]:
|
| 53 |
+
return self.query_path, self.memory_path
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
# ============================================================================
|
| 57 |
+
# Registry: benchmark name -> spec
|
| 58 |
+
# ============================================================================
|
| 59 |
+
|
| 60 |
+
def _rag(name: str) -> BenchmarkSpec:
|
| 61 |
+
return BenchmarkSpec(name, f"qdata_{name}.pkl", f"mdata_{name}.pkl")
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def _rag_0108(name: str) -> BenchmarkSpec:
|
| 65 |
+
return BenchmarkSpec(name, f"qdata_{name}.pkl", f"mdata_{name}.pkl")
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
_REGISTRY: dict[str, BenchmarkSpec] = {
|
| 69 |
+
# --- Length-scale benchmarks ---
|
| 70 |
+
"ms_100M": BenchmarkSpec("ms_100M", "qdata_msmarco_16K.pkl", "mdata_msmarco_100M.pkl"),
|
| 71 |
+
# --- Multi-hop QA ---
|
| 72 |
+
"2wikimultihopqa": _rag("2wikimultihopqa"),
|
| 73 |
+
"hotpotqa": _rag("hotpotqa"),
|
| 74 |
+
"musique": _rag("musique"),
|
| 75 |
+
# --- HippoRAG ---
|
| 76 |
+
"hipporag_narrative": _rag_0108("hipporag_narrative"),
|
| 77 |
+
"hipporag_popqa": _rag_0108("hipporag_popqa"),
|
| 78 |
+
# --- Single-hop QA ---
|
| 79 |
+
"nature_questions": _rag("nature_questions"),
|
| 80 |
+
"triviaqa_06M": _rag("triviaqa_06M"),
|
| 81 |
+
"triviaqa_10M": _rag("triviaqa_10M"),
|
| 82 |
+
# --- Multilingual / Passage retrieval ---
|
| 83 |
+
"dureader": _rag("dureader"),
|
| 84 |
+
"msmarco_v1": _rag("msmarco_v1"),
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
ALL_BENCH_NAMES: list[str] = list(_REGISTRY)
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
# ============================================================================
|
| 91 |
+
# Public API
|
| 92 |
+
# ============================================================================
|
| 93 |
+
|
| 94 |
+
class BenchMarks:
|
| 95 |
+
"""Resolve benchmark name to query / memory file paths.
|
| 96 |
+
|
| 97 |
+
Usage:
|
| 98 |
+
bench = BenchMarks("hotpotqa")
|
| 99 |
+
query_file, memory_file = bench.get_bench_files()
|
| 100 |
+
"""
|
| 101 |
+
|
| 102 |
+
AVAILABLE: ClassVar[list[str]] = ALL_BENCH_NAMES
|
| 103 |
+
|
| 104 |
+
def __init__(self, bench_name: str) -> None:
|
| 105 |
+
if bench_name not in _REGISTRY:
|
| 106 |
+
raise ValueError(
|
| 107 |
+
f"Unknown benchmark: {bench_name!r}. "
|
| 108 |
+
f"Available: {', '.join(ALL_BENCH_NAMES)}"
|
| 109 |
+
)
|
| 110 |
+
self._spec = _REGISTRY[bench_name]
|
| 111 |
+
self.name = bench_name
|
| 112 |
+
self.bench_name = self._spec.bench_name
|
| 113 |
+
self.query_file_name = self._spec.query_file
|
| 114 |
+
self.memory_file_name = self._spec.memory_file
|
| 115 |
+
|
| 116 |
+
def get_bench_files(self) -> tuple[str, str]:
|
| 117 |
+
return self._spec.get_bench_files()
|
| 118 |
+
|
| 119 |
+
def __repr__(self) -> str:
|
| 120 |
+
return f"BenchMarks({self.name!r})"
|
src/config/memory_config.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Union, Dict, List
|
| 2 |
+
from dataclasses import dataclass
|
| 3 |
+
|
| 4 |
+
from src.utils.template import QWEN3_TEMPLATE, QWEN3_INSTRUCT_TEMPLATE
|
| 5 |
+
|
| 6 |
+
@dataclass
|
| 7 |
+
class GenerateConfig:
|
| 8 |
+
devices: List[int] = None
|
| 9 |
+
template: Union[str, Dict] = None
|
| 10 |
+
max_generate_tokens: int = 256
|
| 11 |
+
max_seq_len: int = 0 # total sequence length in a batch
|
| 12 |
+
max_query_seq_len: int = 0 # max sequence for a single query
|
| 13 |
+
max_batch_size: int = 0 # 0 if batch size is not limited
|
| 14 |
+
top_p: float = 0.9
|
| 15 |
+
temperature: float = 0.0
|
| 16 |
+
qa_mode: bool = False
|
| 17 |
+
|
| 18 |
+
def __post_init__(self):
|
| 19 |
+
if isinstance(self.template, str):
|
| 20 |
+
assert self.template in ["QWEN3_TEMPLATE", "QWEN3_INSTRUCT_TEMPLATE"]
|
| 21 |
+
self.template = eval(self.template)
|
| 22 |
+
assert isinstance(self.template, dict)
|
| 23 |
+
|
| 24 |
+
@property
|
| 25 |
+
def world(self):
|
| 26 |
+
return len(self.devices) if self.devices else 0
|
| 27 |
+
|
| 28 |
+
@dataclass
|
| 29 |
+
class ModelConfig:
|
| 30 |
+
model_path: str = "Anoy123423123/MSA-4B"
|
| 31 |
+
|
| 32 |
+
doc_top_k: int = 16
|
| 33 |
+
pooling_kernel_size: int = 64
|
| 34 |
+
router_layer_idx: str = "all"
|
| 35 |
+
|
| 36 |
+
# template
|
| 37 |
+
template_token_id = -2
|
| 38 |
+
template_id_num = 3
|
| 39 |
+
|
| 40 |
+
def get_model_envs(self):
|
| 41 |
+
envs = {}
|
| 42 |
+
# envs["TOP_K_DOCS"] = str(self.doc_top_k)
|
| 43 |
+
# envs["POOLING_KERNEL_SIZE"] = str(self.pooling_kernel_size)
|
| 44 |
+
# envs["ROUTER_LAYER_IDX"] = self.router_layer_idx
|
| 45 |
+
return envs
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
@dataclass
|
| 49 |
+
class MemoryConfig:
|
| 50 |
+
block_size: int = 16000 # 当对 memory 进行推理时使用的分块大小(tokens)
|
| 51 |
+
slice_chunk_size: int = 16 * 1024
|
| 52 |
+
pooling_kernel_size: int = 64
|
| 53 |
+
memory_file_path: str = ""
|
| 54 |
+
|
| 55 |
+
socket_ip: str = ""
|
| 56 |
+
socket_port: int = 0
|
src/evaluation/llm_judge.py
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from openai import OpenAI
|
| 2 |
+
import os
|
| 3 |
+
from tqdm import tqdm
|
| 4 |
+
import sys
|
| 5 |
+
import json
|
| 6 |
+
import numpy as np
|
| 7 |
+
from glob import glob
|
| 8 |
+
from concurrent.futures import ThreadPoolExecutor
|
| 9 |
+
|
| 10 |
+
def parse_match_result(text):
|
| 11 |
+
text_lower = text.strip().lower()
|
| 12 |
+
if 'match' in text_lower and 'mismatch' not in text_lower:
|
| 13 |
+
return 1
|
| 14 |
+
return 0
|
| 15 |
+
|
| 16 |
+
def build_match_prompt(gold_answer, model_answer):
|
| 17 |
+
return f"""You are a strict but fair evaluator.
|
| 18 |
+
|
| 19 |
+
Judge whether the Generated Answer correctly includes
|
| 20 |
+
the core meaning of the Reference Answer.
|
| 21 |
+
|
| 22 |
+
- The Generated Answer may add extra details.
|
| 23 |
+
- Do NOT penalize additional information.
|
| 24 |
+
- The core concept in the Reference Answer must be present
|
| 25 |
+
or clearly implied.
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
Reference Answer:
|
| 29 |
+
{gold_answer}
|
| 30 |
+
|
| 31 |
+
Generated Answer:
|
| 32 |
+
{model_answer}
|
| 33 |
+
|
| 34 |
+
Output:
|
| 35 |
+
match or mismatch"""
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def build_score_prompt(gold_answer, model_answer, query):
|
| 39 |
+
return f""""Based on the accuracy, completeness, and relevance of the predicted answer to the real answer in the context of the **query**, assign an objective score from 0 to 5 (5 being the highest, 0 the lowest).
|
| 40 |
+
|
| 41 |
+
The scoring must strictly adhere to the following criteria. The final output can only be a single number.
|
| 42 |
+
|
| 43 |
+
Scoring Criteria:
|
| 44 |
+
|
| 45 |
+
5: The predicted answer is exactly the same as the real answer and correctly answers the query. Differences in wording do not affect factual accuracy.
|
| 46 |
+
|
| 47 |
+
4: The predicted answer contains all the core information of the real answer, with no errors, but includes a small amount of non-critical redundant content.
|
| 48 |
+
|
| 49 |
+
3: The predicted answer captures the core information but differs from the real answer in some aspects. The predicted answer is slightly incomplete or imprecise, but contains no errors.
|
| 50 |
+
|
| 51 |
+
2: The predicted answer is partially relevant to the real answer but omits a significant amount of information or deviates from the core topic of the query.
|
| 52 |
+
|
| 53 |
+
1: The predicted answer attempts to address the query (maintains basic relevance to the topic) but provides factually incorrect information. It does not contradict the core claim of the real answer, but shows incomplete or inaccurate understanding of the topic.
|
| 54 |
+
|
| 55 |
+
0. The predicted answer is completely unrelated to the query, consists of gibberish, or is a pure hallucination that shares no logical connection with the real answer.
|
| 56 |
+
|
| 57 |
+
Query:
|
| 58 |
+
|
| 59 |
+
{query}
|
| 60 |
+
|
| 61 |
+
True Answer:
|
| 62 |
+
|
| 63 |
+
{gold_answer}
|
| 64 |
+
|
| 65 |
+
Predicted Answer:
|
| 66 |
+
|
| 67 |
+
{model_answer}
|
| 68 |
+
|
| 69 |
+
Output only a single number (0, 1, 2, 3, 4, or 5): """
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def parse_score_result(text):
|
| 74 |
+
"""解析 LLM 返回的评分结果,返回 0-5 的分数"""
|
| 75 |
+
text = text.strip()
|
| 76 |
+
# 尝试直接解析数字
|
| 77 |
+
for char in text:
|
| 78 |
+
if char.isdigit() and int(char) <= 5:
|
| 79 |
+
return int(char)
|
| 80 |
+
# 如果没有找到有效数字,返回 0
|
| 81 |
+
return 0
|
| 82 |
+
|
| 83 |
+
def get_eval_response(prompt):
|
| 84 |
+
try:
|
| 85 |
+
completion = client.chat.completions.create(
|
| 86 |
+
extra_headers={
|
| 87 |
+
},
|
| 88 |
+
extra_body={},
|
| 89 |
+
model='google/gemini-2.5-flash',
|
| 90 |
+
messages=[
|
| 91 |
+
{
|
| 92 |
+
"role": "user",
|
| 93 |
+
"content": [
|
| 94 |
+
{
|
| 95 |
+
"type": "text",
|
| 96 |
+
"text": prompt
|
| 97 |
+
},
|
| 98 |
+
]
|
| 99 |
+
}
|
| 100 |
+
],
|
| 101 |
+
temperature=0.0
|
| 102 |
+
)
|
| 103 |
+
return completion.choices[0].message.content
|
| 104 |
+
except Exception as e:
|
| 105 |
+
print(e)
|
| 106 |
+
return ''
|
| 107 |
+
|
| 108 |
+
if __name__ == "__main__":
|
| 109 |
+
dirname = sys.argv[1]
|
| 110 |
+
openrouter_api_key = os.environ.get("OPENROUTER_API_KEY", "")
|
| 111 |
+
if not openrouter_api_key:
|
| 112 |
+
raise ValueError("Please set the OPENROUTER_API_KEY environment variable.")
|
| 113 |
+
client = OpenAI(
|
| 114 |
+
base_url="https://openrouter.ai/api/v1",
|
| 115 |
+
api_key=openrouter_api_key,
|
| 116 |
+
)
|
| 117 |
+
JUDGE_MODEL = 'google/gemini-2.5-flash'
|
| 118 |
+
if dirname.endswith(".json") and os.path.isfile(dirname):
|
| 119 |
+
json_dir = os.path.split(dirname)[0]
|
| 120 |
+
json_paths = [dirname]
|
| 121 |
+
elif os.path.exists(dirname) and os.path.isdir(dirname):
|
| 122 |
+
json_dir = dirname
|
| 123 |
+
json_paths = glob(f'{json_dir}/*.json')
|
| 124 |
+
else:
|
| 125 |
+
json_dir = f"src/evaluation/outputs/{dirname}"
|
| 126 |
+
json_paths = glob(f'{json_dir}/*.json')
|
| 127 |
+
|
| 128 |
+
for json_path in tqdm(json_paths, total=len(json_paths)):
|
| 129 |
+
if 'score' in json_path:
|
| 130 |
+
continue
|
| 131 |
+
json_name = os.path.split(json_path)[-1].split('.')[0]
|
| 132 |
+
with open(json_path, 'r') as f:
|
| 133 |
+
datas = json.load(f)["anonymous"]["precision"]
|
| 134 |
+
print(datas["metrics"])
|
| 135 |
+
|
| 136 |
+
final_dict = dict()
|
| 137 |
+
final_dict['metrics'] = datas["metrics"]
|
| 138 |
+
final_dict['record_list'] = []
|
| 139 |
+
pred_answers = []
|
| 140 |
+
true_answers = []
|
| 141 |
+
questions = []
|
| 142 |
+
pred_contexts = []
|
| 143 |
+
true_contexts = []
|
| 144 |
+
pred_ids = []
|
| 145 |
+
labels_ids = []
|
| 146 |
+
|
| 147 |
+
for dic in datas['record_list']:
|
| 148 |
+
question = dic['question']
|
| 149 |
+
questions.append(question)
|
| 150 |
+
true_answer = dic['true_answer']
|
| 151 |
+
key_word = "response" if "response" in dic else "pred_answer"
|
| 152 |
+
pred_answer = dic[key_word].replace("<|im_end|>",'').replace("<|endoftext|>",'')
|
| 153 |
+
|
| 154 |
+
pred_ids.append(dic['pred_id'])
|
| 155 |
+
labels_ids.append(dic['labels_id'])
|
| 156 |
+
|
| 157 |
+
pred_con_li = [v for di in dic['predict_context'] for k,v in di.items()]
|
| 158 |
+
true_con_li = [v for di in dic['gt_context'] for k,v in di.items()]
|
| 159 |
+
pred_contexts.append(pred_con_li)
|
| 160 |
+
true_contexts.append(true_con_li)
|
| 161 |
+
|
| 162 |
+
true_answers.append(true_answer)
|
| 163 |
+
qa = pred_answer.split('<answer>')[-1]
|
| 164 |
+
try:
|
| 165 |
+
q = qa.split("The answer to the question is: ")[-2].split("The user's question is: ")[-1].replace('\n<|object_ref_end|>','')
|
| 166 |
+
except:
|
| 167 |
+
q = ''
|
| 168 |
+
a = qa.split("The answer to the question is: ")[-1].replace('</answer>','').replace("The user's question is:",'')
|
| 169 |
+
a = a.split('</think>')[-1]
|
| 170 |
+
if "Answer:" in a:
|
| 171 |
+
a = a.split("Answer:")[1].strip()
|
| 172 |
+
pred_answers.append(a)
|
| 173 |
+
|
| 174 |
+
match_prompts = []
|
| 175 |
+
score_prompts = []
|
| 176 |
+
for idx in tqdm(range(len(pred_answers))):
|
| 177 |
+
true_answer = true_answers[idx]
|
| 178 |
+
pred_answer = pred_answers[idx]
|
| 179 |
+
question = questions[idx]
|
| 180 |
+
match_prompt = build_match_prompt(true_answer, pred_answer)
|
| 181 |
+
score_prompt = build_score_prompt(true_answer, pred_answer, question)
|
| 182 |
+
match_prompts.append(match_prompt)
|
| 183 |
+
score_prompts.append(score_prompt)
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
with ThreadPoolExecutor(max_workers=32) as executor:
|
| 187 |
+
score_results = list(tqdm(executor.map(get_eval_response, score_prompts), total=len(score_prompts), desc="Tokenizing texts"))
|
| 188 |
+
|
| 189 |
+
match_final_scores = []
|
| 190 |
+
score_final_scores = []
|
| 191 |
+
for idx in range(len(score_results)):
|
| 192 |
+
score_result = score_results[idx]
|
| 193 |
+
score_score = parse_score_result(score_result)
|
| 194 |
+
score_final_scores.append(score_score)
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
match_final_scores_mean = np.mean(match_final_scores)
|
| 198 |
+
score_final_scores_mean = np.mean(score_final_scores)
|
| 199 |
+
print(" ======================================= ")
|
| 200 |
+
print(f"{json_name} LLM Score: {score_final_scores_mean:.4f}")
|
| 201 |
+
print(" ======================================= ")
|
| 202 |
+
|
| 203 |
+
old_file_path = json_path
|
| 204 |
+
new_file_path = json_path.replace(".json", f"_score{str(score_final_scores_mean).replace('.','point')[:9]}.json")
|
| 205 |
+
os.rename(old_file_path, new_file_path)
|
| 206 |
+
|
| 207 |
+
# final_dict
|
| 208 |
+
for idx in range(len(questions)):
|
| 209 |
+
question = questions[idx]
|
| 210 |
+
answer = true_answers[idx]
|
| 211 |
+
pred_answer = pred_answers[idx]
|
| 212 |
+
score = score_final_scores[idx]
|
| 213 |
+
final_dict['record_list'].append({'question':question, 'true_answer':answer, 'pred_answer':pred_answer, 'pred_score':score})
|
| 214 |
+
final_dict["score_final_scores_mean"] = score_final_scores_mean
|
| 215 |
+
new_file_path = f'{json_dir}/{json_name}_llmscore.json'
|
| 216 |
+
|
| 217 |
+
with open(new_file_path, 'w',encoding='utf-8') as f:
|
| 218 |
+
json.dump(final_dict, f, indent=4,ensure_ascii=False)
|
src/msa/__init__.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .generate import MSAGenerationMixin
|
| 2 |
+
from .memory_sparse_attention import MemorySparseAttention
|
| 3 |
+
from .configuration_msa import MSAConfig
|
src/msa/configuration_msa.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""MSA (Memory Sparse Attention) Configuration"""
|
| 2 |
+
|
| 3 |
+
from transformers.models.qwen3.configuration_qwen3 import Qwen3Config
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class DotDict(dict):
|
| 7 |
+
"""支持点号访问的字典类"""
|
| 8 |
+
__getattr__ = dict.get
|
| 9 |
+
__setattr__ = dict.__setitem__
|
| 10 |
+
__delattr__ = dict.__delitem__
|
| 11 |
+
|
| 12 |
+
def __getstate__(self):
|
| 13 |
+
return dict(self)
|
| 14 |
+
|
| 15 |
+
def __setstate__(self, state):
|
| 16 |
+
self.update(state)
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class MSAConfig(Qwen3Config):
|
| 20 |
+
"""
|
| 21 |
+
MSA 模型的配置类,继承自 Qwen3Config。
|
| 22 |
+
|
| 23 |
+
主要功能:确保 msa_config 在加载时自动转换为 DotDict,
|
| 24 |
+
支持使用点号访问属性(如 config.msa_config.pad_free)
|
| 25 |
+
"""
|
| 26 |
+
|
| 27 |
+
model_type = "msa"
|
| 28 |
+
|
| 29 |
+
def __init__(self, msa_config=None, **kwargs):
|
| 30 |
+
super().__init__(**kwargs)
|
| 31 |
+
if msa_config is not None:
|
| 32 |
+
self.msa_config = DotDict(msa_config) if not isinstance(msa_config, DotDict) else msa_config
|
| 33 |
+
|
| 34 |
+
def __setattr__(self, name, value):
|
| 35 |
+
"""重写 __setattr__,确保设置 msa_config 时自动转换为 DotDict"""
|
| 36 |
+
if name == "msa_config" and isinstance(value, dict) and not isinstance(value, DotDict):
|
| 37 |
+
value = DotDict(value)
|
| 38 |
+
super().__setattr__(name, value)
|
| 39 |
+
|
| 40 |
+
@classmethod
|
| 41 |
+
def from_dict(cls, config_dict, **kwargs):
|
| 42 |
+
"""
|
| 43 |
+
从字典创建配置对象时,确保 msa_config 被转换为 DotDict。
|
| 44 |
+
这是关键方法,AutoConfig.from_pretrained() 最终会调用这个方法。
|
| 45 |
+
"""
|
| 46 |
+
# 先调用父类的 from_dict
|
| 47 |
+
config = super().from_dict(config_dict, **kwargs)
|
| 48 |
+
|
| 49 |
+
# 确保 msa_config 是 DotDict
|
| 50 |
+
if hasattr(config, 'msa_config') and isinstance(config.msa_config, dict) and not isinstance(config.msa_config, DotDict):
|
| 51 |
+
config.msa_config = DotDict(config.msa_config)
|
| 52 |
+
|
| 53 |
+
return config
|
src/msa/generate.py
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
import os
|
| 3 |
+
import re
|
| 4 |
+
from typing import Optional, Union
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
from torch import nn
|
| 8 |
+
from transformers.cache_utils import Cache
|
| 9 |
+
from transformers.generation.configuration_utils import GenerationConfig
|
| 10 |
+
from transformers.generation.logits_process import LogitsProcessorList
|
| 11 |
+
from transformers.generation.stopping_criteria import StoppingCriteriaList
|
| 12 |
+
from transformers.generation.streamers import BaseStreamer
|
| 13 |
+
from transformers.generation.utils import (
|
| 14 |
+
GenerateDecoderOnlyOutput,
|
| 15 |
+
GenerateEncoderDecoderOutput,
|
| 16 |
+
GenerateNonBeamOutput,
|
| 17 |
+
GenerationMixin,
|
| 18 |
+
)
|
| 19 |
+
|
| 20 |
+
logger = logging.getLogger(__name__)
|
| 21 |
+
|
| 22 |
+
class MSAGenerationMixin(GenerationMixin):
|
| 23 |
+
def _sample(
|
| 24 |
+
self,
|
| 25 |
+
input_ids: torch.LongTensor,
|
| 26 |
+
logits_processor: LogitsProcessorList,
|
| 27 |
+
stopping_criteria: StoppingCriteriaList,
|
| 28 |
+
generation_config: GenerationConfig,
|
| 29 |
+
synced_gpus: bool,
|
| 30 |
+
streamer: Optional["BaseStreamer"],
|
| 31 |
+
**model_kwargs,
|
| 32 |
+
) -> Union[GenerateNonBeamOutput, torch.LongTensor]:
|
| 33 |
+
# init values
|
| 34 |
+
pad_token_id = generation_config._pad_token_tensor
|
| 35 |
+
output_attentions = generation_config.output_attentions
|
| 36 |
+
output_hidden_states = generation_config.output_hidden_states
|
| 37 |
+
output_scores = generation_config.output_scores
|
| 38 |
+
output_logits = generation_config.output_logits
|
| 39 |
+
return_dict_in_generate = generation_config.return_dict_in_generate
|
| 40 |
+
has_eos_stopping_criteria = any(hasattr(criteria, "eos_token_id") for criteria in stopping_criteria)
|
| 41 |
+
do_sample = generation_config.do_sample
|
| 42 |
+
|
| 43 |
+
# init attention / hidden states / scores tuples
|
| 44 |
+
scores = () if (return_dict_in_generate and output_scores) else None
|
| 45 |
+
raw_logits = () if (return_dict_in_generate and output_logits) else None
|
| 46 |
+
decoder_attentions = () if (return_dict_in_generate and output_attentions) else None
|
| 47 |
+
cross_attentions = () if (return_dict_in_generate and output_attentions) else None
|
| 48 |
+
decoder_hidden_states = () if (return_dict_in_generate and output_hidden_states) else None
|
| 49 |
+
|
| 50 |
+
# if model is an encoder-decoder, retrieve encoder attention weights and hidden states
|
| 51 |
+
if return_dict_in_generate and self.config.is_encoder_decoder:
|
| 52 |
+
encoder_attentions = model_kwargs["encoder_outputs"].get("attentions") if output_attentions else None
|
| 53 |
+
encoder_hidden_states = (
|
| 54 |
+
model_kwargs["encoder_outputs"].get("hidden_states") if output_hidden_states else None
|
| 55 |
+
)
|
| 56 |
+
|
| 57 |
+
# keep track of which sequences are already finished
|
| 58 |
+
batch_size, cur_len = input_ids.shape
|
| 59 |
+
this_peer_finished = False
|
| 60 |
+
unfinished_sequences = torch.ones(batch_size, dtype=torch.long, device=input_ids.device)
|
| 61 |
+
model_kwargs = self._get_initial_cache_position(input_ids, model_kwargs)
|
| 62 |
+
|
| 63 |
+
model_forward = self.__call__
|
| 64 |
+
if isinstance(model_kwargs.get("past_key_values"), Cache):
|
| 65 |
+
is_compileable = model_kwargs["past_key_values"].is_compileable and self._supports_static_cache
|
| 66 |
+
if getattr(self, "hf_quantizer", None) is not None:
|
| 67 |
+
is_compileable &= self.hf_quantizer.is_compileable
|
| 68 |
+
is_compileable = is_compileable and not generation_config.disable_compile
|
| 69 |
+
if is_compileable and (
|
| 70 |
+
self.device.type == "cuda" or generation_config.compile_config._compile_all_devices
|
| 71 |
+
):
|
| 72 |
+
os.environ["TOKENIZERS_PARALLELISM"] = "0"
|
| 73 |
+
model_forward = self.get_compiled_call(generation_config.compile_config)
|
| 74 |
+
|
| 75 |
+
if generation_config.prefill_chunk_size is not None:
|
| 76 |
+
model_kwargs = self._prefill_chunking(input_ids, generation_config, **model_kwargs)
|
| 77 |
+
is_prefill = False
|
| 78 |
+
else:
|
| 79 |
+
is_prefill = True
|
| 80 |
+
|
| 81 |
+
meta = model_kwargs["past_key_values"].meta
|
| 82 |
+
max_generate_tokens = model_kwargs["past_key_values"].meta["max_generate_tokens"]
|
| 83 |
+
tokenizer = meta['tokenizer']
|
| 84 |
+
response_string = meta["response_string"]
|
| 85 |
+
idx_to_doc = meta["idx_to_doc"]
|
| 86 |
+
pattern = meta["pattern"]
|
| 87 |
+
retrieval_end_flags = torch.zeros(batch_size, dtype=torch.bool, device=input_ids.device)
|
| 88 |
+
round_end_flags = torch.zeros(batch_size, dtype=torch.bool, device=input_ids.device)
|
| 89 |
+
inner_string = ["" for _ in range(batch_size)]
|
| 90 |
+
source_context_copied = False
|
| 91 |
+
all_input_str = [""] * batch_size
|
| 92 |
+
last_valid_inputs = input_ids[:, -1:].clone().to(input_ids.device)
|
| 93 |
+
is_first = 1
|
| 94 |
+
generate_stage = 1
|
| 95 |
+
cnt = 0
|
| 96 |
+
all_model_inputs = {}
|
| 97 |
+
has_generate_stage3 = False
|
| 98 |
+
first_stage2 = True
|
| 99 |
+
round_end = False
|
| 100 |
+
|
| 101 |
+
while self._has_unfinished_sequences(this_peer_finished, synced_gpus, device=input_ids.device):
|
| 102 |
+
if "position_ids" in model_kwargs:
|
| 103 |
+
position_ids = model_kwargs.pop("position_ids")
|
| 104 |
+
else:
|
| 105 |
+
position_ids = (position_ids[:, -1:] + model_kwargs["attention_mask"]).to(input_ids.device)
|
| 106 |
+
|
| 107 |
+
model_inputs = model_kwargs.copy()
|
| 108 |
+
|
| 109 |
+
if is_first == 1:
|
| 110 |
+
model_inputs.update({"input_ids": input_ids})
|
| 111 |
+
else:
|
| 112 |
+
model_inputs.update({"input_ids": last_valid_inputs})
|
| 113 |
+
|
| 114 |
+
model_inputs.update({"position_ids": position_ids})
|
| 115 |
+
model_inputs.update({"output_attentions": output_attentions} if output_attentions else {})
|
| 116 |
+
model_inputs.update({"output_hidden_states": output_hidden_states} if output_hidden_states else {})
|
| 117 |
+
|
| 118 |
+
input_str = tokenizer.batch_decode(model_inputs['input_ids'])
|
| 119 |
+
all_input_str = [s + input_str[i] for i, s in enumerate(all_input_str)]
|
| 120 |
+
|
| 121 |
+
if not is_prefill:
|
| 122 |
+
for layer_idx in range(self.config.num_hidden_layers):
|
| 123 |
+
model_inputs["past_key_values"].record_kwargs(layer_idx, {"stage": "generate"})
|
| 124 |
+
|
| 125 |
+
if 'doc_ids' not in all_model_inputs:
|
| 126 |
+
all_model_inputs['doc_ids'] = model_inputs['doc_ids'].clone().to(input_ids.device)
|
| 127 |
+
else:
|
| 128 |
+
all_model_inputs['doc_ids'] = torch.cat((all_model_inputs['doc_ids'], model_inputs['doc_ids'][:, -model_inputs['attention_mask'].shape[1]:]), dim=1)
|
| 129 |
+
|
| 130 |
+
all_model_inputs['attention_mask'] = torch.cat((all_model_inputs['attention_mask'], model_inputs['attention_mask']), dim=1) if 'attention_mask' in all_model_inputs else model_inputs['attention_mask'].clone().to(input_ids.device)
|
| 131 |
+
all_model_inputs['input_ids'] = torch.cat((all_model_inputs['input_ids'], model_inputs['input_ids']), dim=1) if 'input_ids' in all_model_inputs else model_inputs['input_ids'].clone().to(input_ids.device)
|
| 132 |
+
all_model_inputs['position_ids'] = torch.cat((all_model_inputs['position_ids'], model_inputs['position_ids']), dim=1) if 'position_ids' in all_model_inputs else model_inputs['position_ids'].clone().to(input_ids.device)
|
| 133 |
+
all_model_inputs['past_key_values'] = model_inputs.get('past_key_values')
|
| 134 |
+
all_model_inputs['cache_position'] = model_inputs['cache_position'].clone().to(input_ids.device)
|
| 135 |
+
|
| 136 |
+
if round_end:
|
| 137 |
+
break
|
| 138 |
+
|
| 139 |
+
if is_prefill:
|
| 140 |
+
outputs = self(**all_model_inputs, return_dict=True)
|
| 141 |
+
is_prefill = False
|
| 142 |
+
first_stage2 = False
|
| 143 |
+
else:
|
| 144 |
+
outputs = model_forward(**model_inputs, return_dict=True)
|
| 145 |
+
is_first = 0
|
| 146 |
+
|
| 147 |
+
# update model kwargs for next generation step
|
| 148 |
+
model_kwargs = self._update_model_kwargs_for_generation(
|
| 149 |
+
outputs,
|
| 150 |
+
model_kwargs,
|
| 151 |
+
is_encoder_decoder=self.config.is_encoder_decoder,
|
| 152 |
+
)
|
| 153 |
+
if synced_gpus and this_peer_finished:
|
| 154 |
+
continue
|
| 155 |
+
|
| 156 |
+
next_token_logits = outputs.logits[:, -1, :].to(copy=True, dtype=torch.float32, device=input_ids.device)
|
| 157 |
+
|
| 158 |
+
next_token_scores = logits_processor(last_valid_inputs, next_token_logits)
|
| 159 |
+
|
| 160 |
+
# Store scores, attentions and hidden_states when required
|
| 161 |
+
if return_dict_in_generate:
|
| 162 |
+
if output_scores:
|
| 163 |
+
scores += (next_token_scores,)
|
| 164 |
+
if output_logits:
|
| 165 |
+
raw_logits += (next_token_logits,)
|
| 166 |
+
if output_attentions:
|
| 167 |
+
decoder_attentions += (
|
| 168 |
+
(outputs.decoder_attentions,) if self.config.is_encoder_decoder else (outputs.attentions,)
|
| 169 |
+
)
|
| 170 |
+
if self.config.is_encoder_decoder:
|
| 171 |
+
cross_attentions += (outputs.cross_attentions,)
|
| 172 |
+
if output_hidden_states:
|
| 173 |
+
decoder_hidden_states += (
|
| 174 |
+
(outputs.decoder_hidden_states,)
|
| 175 |
+
if self.config.is_encoder_decoder
|
| 176 |
+
else (outputs.hidden_states,)
|
| 177 |
+
)
|
| 178 |
+
|
| 179 |
+
# token selection
|
| 180 |
+
if do_sample:
|
| 181 |
+
probs = nn.functional.softmax(next_token_scores, dim=-1)
|
| 182 |
+
next_tokens = torch.multinomial(probs, num_samples=1).squeeze(1)
|
| 183 |
+
else:
|
| 184 |
+
next_tokens = torch.argmax(next_token_scores, dim=-1)
|
| 185 |
+
if has_eos_stopping_criteria:
|
| 186 |
+
next_tokens = next_tokens * unfinished_sequences + pad_token_id * (1 - unfinished_sequences)
|
| 187 |
+
|
| 188 |
+
cur_generate_context = tokenizer.batch_decode(next_tokens)
|
| 189 |
+
|
| 190 |
+
if generate_stage == 2 or generate_stage == 3:
|
| 191 |
+
source_context_list = []
|
| 192 |
+
imstart_str = "<|im_start|>"
|
| 193 |
+
|
| 194 |
+
if generate_stage == 2:
|
| 195 |
+
for i, response_sample in enumerate(inner_string):
|
| 196 |
+
if '<End-of-Retrieve>' in response_sample:
|
| 197 |
+
question = all_input_str[i].split('historical document information\n\n')[-1]
|
| 198 |
+
question = question.split('\nPlease return all documents related to the question')[0]
|
| 199 |
+
source_context_list.append(imstart_str + 'The user\'s question is: %s\n<|object_ref_end|>' % (question))
|
| 200 |
+
else:
|
| 201 |
+
result = re.findall(pattern, response_sample)
|
| 202 |
+
indices = sorted(list(set(map(int, result))))
|
| 203 |
+
indices = [idx for idx in indices if idx in idx_to_doc]
|
| 204 |
+
try:
|
| 205 |
+
response_doc_str = ''.join(f"[{idx}]. {idx_to_doc[idx]}\n" for idx in indices)
|
| 206 |
+
response_doc_str = response_doc_str + '<|object_ref_end|>'
|
| 207 |
+
except KeyError as e:
|
| 208 |
+
logger.warning("Document not found for index %s, available indices: %s", e, indices)
|
| 209 |
+
response_doc_str = "" + '<|object_ref_end|>'
|
| 210 |
+
source_context_list.append(response_doc_str)
|
| 211 |
+
|
| 212 |
+
if generate_stage == 3:
|
| 213 |
+
for i, response_sample in enumerate(response_string):
|
| 214 |
+
if '<End-of-Retrieve>' in response_sample:
|
| 215 |
+
question = all_input_str[i].split('historical document information\n\n')[-1]
|
| 216 |
+
question = question.split('\nPlease return all documents related to the question')[0]
|
| 217 |
+
source_context_list.append(imstart_str + 'The user\'s question is: %s\n<|object_ref_end|>' % (question))
|
| 218 |
+
else:
|
| 219 |
+
source_context_list.append("")
|
| 220 |
+
|
| 221 |
+
source_batch = tokenizer(
|
| 222 |
+
source_context_list,
|
| 223 |
+
padding="longest",
|
| 224 |
+
truncation=True,
|
| 225 |
+
return_tensors="pt",
|
| 226 |
+
add_special_tokens=True,
|
| 227 |
+
padding_side="left",
|
| 228 |
+
)
|
| 229 |
+
|
| 230 |
+
sh = source_batch['input_ids'].shape
|
| 231 |
+
|
| 232 |
+
batch_source_input_ids = source_batch['input_ids'].clone().detach().long().to(input_ids.device)
|
| 233 |
+
batch_source_attn_mask = source_batch['attention_mask'].clone().detach().long().to(input_ids.device)
|
| 234 |
+
batch_source_doc_ids = torch.zeros_like(source_batch['input_ids'], dtype=torch.long, device=input_ids.device)
|
| 235 |
+
batch_source_position_ids = torch.arange(sh[1], dtype=torch.long, device=input_ids.device).unsqueeze(0).expand(sh[0], -1)
|
| 236 |
+
batch_source_position_ids = batch_source_position_ids + model_inputs['position_ids'] + torch.sum(batch_source_attn_mask, dim=1, keepdim=True) - sh[1] + 1
|
| 237 |
+
|
| 238 |
+
input_ids = batch_source_input_ids
|
| 239 |
+
model_kwargs['attention_mask'] = batch_source_attn_mask
|
| 240 |
+
model_kwargs['doc_ids'] = batch_source_doc_ids
|
| 241 |
+
model_kwargs['position_ids'] = batch_source_position_ids
|
| 242 |
+
source_context_copied = True
|
| 243 |
+
|
| 244 |
+
cur_len += sh[1]
|
| 245 |
+
this_peer_finished = False
|
| 246 |
+
del outputs
|
| 247 |
+
is_first = 1
|
| 248 |
+
inner_string = ["" for _ in range(batch_size)]
|
| 249 |
+
|
| 250 |
+
if generate_stage == 2:
|
| 251 |
+
round_end = True
|
| 252 |
+
|
| 253 |
+
if generate_stage == 2:
|
| 254 |
+
generate_stage = 1
|
| 255 |
+
|
| 256 |
+
if generate_stage == 3:
|
| 257 |
+
generate_stage = 4
|
| 258 |
+
|
| 259 |
+
round_end_flags = torch.zeros(batch_size, dtype=torch.bool, device=input_ids.device)
|
| 260 |
+
|
| 261 |
+
else:
|
| 262 |
+
input_ids = torch.cat([input_ids, next_tokens[:, None]], dim=-1)
|
| 263 |
+
if True:
|
| 264 |
+
temp = stopping_criteria(input_ids[:, -1:], scores)
|
| 265 |
+
unfinished_sequences = unfinished_sequences & ~temp
|
| 266 |
+
# max_res_len = 3000
|
| 267 |
+
# if max([len(n) for n in response_string]) > max_res_len:
|
| 268 |
+
# unfinished_sequences = torch.zeros_like(unfinished_sequences)
|
| 269 |
+
|
| 270 |
+
this_peer_finished = unfinished_sequences.max() == 0
|
| 271 |
+
if this_peer_finished and not source_context_copied:
|
| 272 |
+
logger.warning("Generation finished before source context was copied")
|
| 273 |
+
|
| 274 |
+
if streamer is not None:
|
| 275 |
+
streamer.put(next_tokens.cpu())
|
| 276 |
+
cur_len += 1
|
| 277 |
+
del outputs
|
| 278 |
+
input_ids = input_ids[:, -1:]
|
| 279 |
+
model_kwargs['doc_ids'] = torch.nn.functional.pad(model_kwargs['doc_ids'], (0, 1), value=-1)
|
| 280 |
+
|
| 281 |
+
model_kwargs["attention_mask"] = torch.ones(batch_size, 1, dtype=torch.long, device=input_ids.device)
|
| 282 |
+
if generate_stage == 1:
|
| 283 |
+
for i, retrieval_end_flag in enumerate(retrieval_end_flags):
|
| 284 |
+
if retrieval_end_flag or round_end_flags[i]:
|
| 285 |
+
model_kwargs["attention_mask"][i, 0] = 0
|
| 286 |
+
else:
|
| 287 |
+
last_valid_inputs[i, -1] = input_ids[i, -1]
|
| 288 |
+
else:
|
| 289 |
+
for i, retrieval_end_flag in enumerate(retrieval_end_flags):
|
| 290 |
+
last_valid_inputs[i, -1] = input_ids[i, -1]
|
| 291 |
+
|
| 292 |
+
assert len(cur_generate_context) == len(response_string)
|
| 293 |
+
for i in range(len(cur_generate_context)):
|
| 294 |
+
response_string[i] += cur_generate_context[i]
|
| 295 |
+
inner_string[i] += cur_generate_context[i]
|
| 296 |
+
round_end_flags[i] |= "<|object_ref_end|>" in inner_string[i]
|
| 297 |
+
retrieval_end_flags[i] |= '<End-of-Retrieve>' in response_string[i]
|
| 298 |
+
if source_context_copied and generate_stage == 1:
|
| 299 |
+
mypattern = r"^\[\d*\]?$"
|
| 300 |
+
is_id = bool(re.fullmatch(mypattern, inner_string[i]))
|
| 301 |
+
is_EOR = inner_string[i] == '<End-of-Retrieve>'[:len(inner_string[i])]
|
| 302 |
+
round_end_flags[i] |= (not is_id and not is_EOR)
|
| 303 |
+
|
| 304 |
+
if not source_context_copied:
|
| 305 |
+
max_ret_len = 1000
|
| 306 |
+
retrieval_end_flags[i] |= len(response_string[i]) > max_ret_len
|
| 307 |
+
|
| 308 |
+
if sum(round_end_flags * unfinished_sequences * (~retrieval_end_flags)) == sum(unfinished_sequences * (~retrieval_end_flags)) and not has_generate_stage3:
|
| 309 |
+
generate_stage = 2
|
| 310 |
+
for i in range(len(cur_generate_context)):
|
| 311 |
+
if source_context_copied and round_end_flags[i]:
|
| 312 |
+
if "<|object_ref_end|>" not in inner_string[i] or not bool(re.fullmatch(mypattern, inner_string[i].split("<|object_ref_end|>")[0])):
|
| 313 |
+
retrieval_end_flags[i] = True
|
| 314 |
+
if sum(retrieval_end_flags * unfinished_sequences) == sum(unfinished_sequences) and not has_generate_stage3:
|
| 315 |
+
generate_stage = 3
|
| 316 |
+
has_generate_stage3 = True
|
| 317 |
+
cnt += 1
|
| 318 |
+
if cnt > max_generate_tokens:
|
| 319 |
+
break
|
| 320 |
+
|
| 321 |
+
all_model_inputs['attention_mask'], indices = all_model_inputs['attention_mask'].sort(dim=1, descending=False, stable=True)
|
| 322 |
+
all_model_inputs['input_ids'] = all_model_inputs['input_ids'].gather(dim=1, index=indices)
|
| 323 |
+
all_model_inputs['position_ids'] = all_model_inputs['position_ids'].gather(dim=1, index=indices)
|
| 324 |
+
all_model_inputs['doc_ids'] = all_model_inputs['doc_ids'].gather(dim=1, index=indices)
|
| 325 |
+
|
| 326 |
+
input_ids = all_model_inputs['input_ids']
|
| 327 |
+
|
| 328 |
+
if streamer is not None:
|
| 329 |
+
streamer.end()
|
| 330 |
+
|
| 331 |
+
if return_dict_in_generate:
|
| 332 |
+
if self.config.is_encoder_decoder:
|
| 333 |
+
return GenerateEncoderDecoderOutput(
|
| 334 |
+
sequences=input_ids,
|
| 335 |
+
scores=scores,
|
| 336 |
+
logits=raw_logits,
|
| 337 |
+
encoder_attentions=encoder_attentions,
|
| 338 |
+
encoder_hidden_states=encoder_hidden_states,
|
| 339 |
+
decoder_attentions=decoder_attentions,
|
| 340 |
+
cross_attentions=cross_attentions,
|
| 341 |
+
decoder_hidden_states=decoder_hidden_states,
|
| 342 |
+
past_key_values=model_kwargs.get("past_key_values"),
|
| 343 |
+
)
|
| 344 |
+
else:
|
| 345 |
+
return GenerateDecoderOnlyOutput(
|
| 346 |
+
sequences=input_ids,
|
| 347 |
+
scores=scores,
|
| 348 |
+
logits=raw_logits,
|
| 349 |
+
attentions=decoder_attentions,
|
| 350 |
+
hidden_states=decoder_hidden_states,
|
| 351 |
+
past_key_values=model_kwargs.get("past_key_values"),
|
| 352 |
+
)
|
| 353 |
+
else:
|
| 354 |
+
return input_ids
|
src/msa/memory_sparse_attention.py
ADDED
|
@@ -0,0 +1,852 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import torch
|
| 3 |
+
import torch.nn as nn
|
| 4 |
+
import torch.nn.functional as F
|
| 5 |
+
from typing import Optional, Tuple
|
| 6 |
+
|
| 7 |
+
from transformers.models.qwen3.modeling_qwen3 import (
|
| 8 |
+
Qwen3Attention,
|
| 9 |
+
Qwen3Config,
|
| 10 |
+
apply_rotary_pos_emb,
|
| 11 |
+
repeat_kv,
|
| 12 |
+
)
|
| 13 |
+
try:
|
| 14 |
+
from flash_attn import flash_attn_varlen_func
|
| 15 |
+
except ImportError:
|
| 16 |
+
print("请安装flash-attn库: pip install flash-attn --no-build-isolation")
|
| 17 |
+
flash_attn_varlen_func = None
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class MemorySparseAttention(Qwen3Attention):
|
| 21 |
+
def __init__(self, config: Qwen3Config, layer_idx: int):
|
| 22 |
+
super().__init__(config=config, layer_idx=layer_idx)
|
| 23 |
+
if flash_attn_varlen_func is None:
|
| 24 |
+
raise ImportError("flash_attn is required. Please install it via 'pip install flash-attn --no-build-isolation'")
|
| 25 |
+
|
| 26 |
+
self.layer_idx = layer_idx
|
| 27 |
+
self.top_k_docs = config.msa_config.top_k_docs
|
| 28 |
+
self.pooling_kernel_size = config.msa_config.pooling_kernel_size
|
| 29 |
+
self.router_layer_idx = config.msa_config.router_layer_idx
|
| 30 |
+
|
| 31 |
+
if self.router_layer_idx == "all":
|
| 32 |
+
self.router_layer_idx = list(range(config.num_hidden_layers))
|
| 33 |
+
else:
|
| 34 |
+
self.router_layer_idx = [int(i) for i in self.router_layer_idx.split(",")]
|
| 35 |
+
self.is_router_layer = self.layer_idx in self.router_layer_idx
|
| 36 |
+
|
| 37 |
+
self.head_reduce_method = config.msa_config.head_reduce_method
|
| 38 |
+
self.query_reduce_method = config.msa_config.query_reduce_method
|
| 39 |
+
self.chunk_reduce_method = config.msa_config.chunk_reduce_method
|
| 40 |
+
self.decouple_pooling_mode = config.msa_config.decouple_pooling_mode
|
| 41 |
+
self.aux_loss_method = config.msa_config.aux_loss_method
|
| 42 |
+
|
| 43 |
+
self.decouple_router = config.msa_config.decouple_router
|
| 44 |
+
if self.is_router_layer and self.decouple_router:
|
| 45 |
+
self.router_k_proj = nn.Sequential(
|
| 46 |
+
nn.Linear(config.hidden_size, config.num_key_value_heads * self.head_dim, bias=False),
|
| 47 |
+
# nn.GELU(),
|
| 48 |
+
# nn.Linear(config.num_key_value_heads * self.head_dim, config.num_key_value_heads * self.head_dim, bias=False)
|
| 49 |
+
)
|
| 50 |
+
self.router_q_proj = nn.Sequential(
|
| 51 |
+
nn.Linear(config.hidden_size, config.num_attention_heads * self.head_dim, bias=False),
|
| 52 |
+
# nn.GELU(),
|
| 53 |
+
# nn.Linear(config.num_attention_heads * self.head_dim, config.num_attention_heads * self.head_dim, bias=False)
|
| 54 |
+
)
|
| 55 |
+
self.num_kv_heads = config.num_key_value_heads
|
| 56 |
+
|
| 57 |
+
self.sliding_window = None
|
| 58 |
+
self.selected_docs_indices = None
|
| 59 |
+
self.max_doc_id = None
|
| 60 |
+
self.num_split_for_kv = 8
|
| 61 |
+
self.template_prefix_kcache = None
|
| 62 |
+
self.template_prefix_vcache = None
|
| 63 |
+
self.memory_client = None
|
| 64 |
+
|
| 65 |
+
def set_memory_client(self, memory_client):
|
| 66 |
+
self.memory_client = memory_client
|
| 67 |
+
|
| 68 |
+
def forward(
|
| 69 |
+
self,
|
| 70 |
+
hidden_states: torch.Tensor,
|
| 71 |
+
doc_ids: torch.LongTensor,
|
| 72 |
+
attention_mask: Optional[torch.Tensor] = None,
|
| 73 |
+
position_embeddings: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
|
| 74 |
+
past_key_value: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
|
| 75 |
+
**kwargs,
|
| 76 |
+
) -> Tuple[torch.Tensor, Optional[torch.Tensor]]:
|
| 77 |
+
|
| 78 |
+
if self.training:
|
| 79 |
+
return self._forward(
|
| 80 |
+
hidden_states,
|
| 81 |
+
doc_ids,
|
| 82 |
+
attention_mask,
|
| 83 |
+
position_embeddings,
|
| 84 |
+
past_key_value,
|
| 85 |
+
**kwargs,
|
| 86 |
+
)
|
| 87 |
+
elif past_key_value is not None:
|
| 88 |
+
return self.forward_with_kvcache_for_batch_parrallel(
|
| 89 |
+
hidden_states,
|
| 90 |
+
doc_ids,
|
| 91 |
+
attention_mask,
|
| 92 |
+
position_embeddings,
|
| 93 |
+
past_key_value,
|
| 94 |
+
**kwargs,
|
| 95 |
+
)
|
| 96 |
+
else:
|
| 97 |
+
raise Exception("error!")
|
| 98 |
+
|
| 99 |
+
@staticmethod
|
| 100 |
+
def map_tensor_to_group_ids(a: torch.Tensor) -> torch.Tensor:
|
| 101 |
+
if a.ndim != 1:
|
| 102 |
+
raise ValueError("输入 Tensor a 必须是一维的。")
|
| 103 |
+
|
| 104 |
+
diff_mask = torch.diff(a) != 0 # [L-1]
|
| 105 |
+
id_increments = diff_mask.int() # [L-1]
|
| 106 |
+
group_indices_offset = torch.cumsum(id_increments, dim=0) # [L-1]
|
| 107 |
+
|
| 108 |
+
b = torch.cat((
|
| 109 |
+
torch.tensor([0], device=a.device, dtype=a.dtype),
|
| 110 |
+
group_indices_offset
|
| 111 |
+
)) + 1
|
| 112 |
+
|
| 113 |
+
return b
|
| 114 |
+
|
| 115 |
+
def forward_with_kvcache_for_batch_parrallel(
|
| 116 |
+
self,
|
| 117 |
+
hidden_states: torch.Tensor,
|
| 118 |
+
doc_ids: torch.LongTensor,
|
| 119 |
+
attention_mask: Optional[torch.Tensor] = None,
|
| 120 |
+
position_embeddings: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
|
| 121 |
+
past_key_value: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
|
| 122 |
+
**kwargs,
|
| 123 |
+
) -> Tuple[torch.Tensor, Optional[torch.Tensor]]:
|
| 124 |
+
|
| 125 |
+
bsz, q_len, _ = hidden_states.shape
|
| 126 |
+
device, dtype = hidden_states.device, hidden_states.dtype
|
| 127 |
+
hidden_shape = (bsz, q_len, -1, self.head_dim)
|
| 128 |
+
|
| 129 |
+
query_states = self.q_norm(self.q_proj(hidden_states).view(hidden_shape)).transpose(1, 2)
|
| 130 |
+
key_states = self.k_norm(self.k_proj(hidden_states).view(hidden_shape)).transpose(1, 2)
|
| 131 |
+
value_states = self.v_proj(hidden_states).view(hidden_shape).transpose(1, 2)
|
| 132 |
+
cos, sin = position_embeddings
|
| 133 |
+
query_states, key_states = apply_rotary_pos_emb(query_states, key_states, cos, sin)
|
| 134 |
+
|
| 135 |
+
stage = past_key_value.cache_kwargs[self.layer_idx]["stage"]
|
| 136 |
+
|
| 137 |
+
if stage == "prefill_stage1":
|
| 138 |
+
max_doc_id = int(doc_ids.max().item())
|
| 139 |
+
doc_token_mask = (doc_ids > 0) & (attention_mask == 1)
|
| 140 |
+
doc_indices = torch.nonzero(doc_token_mask, as_tuple=False)
|
| 141 |
+
original_doc_ids = doc_ids[doc_token_mask]
|
| 142 |
+
original_doc_batch_indices = doc_indices[:, 0]
|
| 143 |
+
global_doc_ids = original_doc_batch_indices * (max_doc_id + 1) + original_doc_ids
|
| 144 |
+
|
| 145 |
+
if self.is_router_layer:
|
| 146 |
+
_, counts = torch.unique_consecutive(global_doc_ids, return_counts=True)
|
| 147 |
+
total_doc_tokens = global_doc_ids.shape[0]
|
| 148 |
+
|
| 149 |
+
cu_seqlens = counts.cumsum(0)
|
| 150 |
+
offsets = torch.zeros(counts.shape[0] + 1, dtype=counts.dtype, device=device)
|
| 151 |
+
offsets[1:] = cu_seqlens
|
| 152 |
+
offsets = offsets[:-1]
|
| 153 |
+
|
| 154 |
+
expanded_offsets = torch.repeat_interleave(offsets, counts)
|
| 155 |
+
original_order_ranks = torch.arange(total_doc_tokens, device=device) - expanded_offsets
|
| 156 |
+
|
| 157 |
+
chunk_indices = original_order_ranks // self.pooling_kernel_size
|
| 158 |
+
max_chunks_per_doc = (q_len // self.pooling_kernel_size) + 1
|
| 159 |
+
|
| 160 |
+
global_chunk_ids = global_doc_ids * max_chunks_per_doc + chunk_indices
|
| 161 |
+
|
| 162 |
+
unique_global_chunk_ids, chunk_token_counts = torch.unique_consecutive(global_chunk_ids, return_counts=True)
|
| 163 |
+
pooled_doc_ids = unique_global_chunk_ids // max_chunks_per_doc % (max_doc_id + 1)
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
pooled_k_chunks, pooled_v_chunks = self.sequence_pooling_kv(
|
| 167 |
+
key_states,
|
| 168 |
+
value_states,
|
| 169 |
+
doc_indices,
|
| 170 |
+
global_chunk_ids,
|
| 171 |
+
)
|
| 172 |
+
|
| 173 |
+
pooled_k_chunks = pooled_k_chunks.transpose(0, 1).unsqueeze(0)
|
| 174 |
+
pooled_v_chunks = pooled_v_chunks.transpose(0, 1).unsqueeze(0)
|
| 175 |
+
|
| 176 |
+
pooled_router_k = None
|
| 177 |
+
if self.decouple_router:
|
| 178 |
+
r_k_raw = self.router_k_proj(hidden_states).view(hidden_shape).transpose(1, 2)
|
| 179 |
+
r_k_docs = r_k_raw[doc_indices[:, 0], :, doc_indices[:, 1]]
|
| 180 |
+
|
| 181 |
+
_, chunk_lengths = torch.unique_consecutive(global_chunk_ids, return_counts=True)
|
| 182 |
+
chunk_counts_view = chunk_lengths.view(-1, 1, 1).to(dtype=torch.float32)
|
| 183 |
+
b_k, h_k, d_k = r_k_docs.shape
|
| 184 |
+
k_flat = r_k_docs.reshape(b_k, -1).to(dtype=torch.float32)
|
| 185 |
+
k_cumsum = F.pad(torch.cumsum(k_flat, dim=0), (0, 0, 1, 0))
|
| 186 |
+
chunk_cu_seqlens = F.pad(torch.cumsum(chunk_lengths, 0), (1, 0))
|
| 187 |
+
k_sums_flat = k_cumsum[chunk_cu_seqlens[1:]] - k_cumsum[chunk_cu_seqlens[:-1]]
|
| 188 |
+
pooled_router_k = (k_sums_flat.view(unique_global_chunk_ids.shape[0], h_k, d_k) / chunk_counts_view).to(dtype=r_k_docs.dtype)
|
| 189 |
+
|
| 190 |
+
pooled_router_k = pooled_router_k.transpose(0, 1).unsqueeze(0)
|
| 191 |
+
|
| 192 |
+
if self.aux_loss_method == "INFONCE":
|
| 193 |
+
router_k = pooled_router_k if pooled_router_k is not None else pooled_k_chunks
|
| 194 |
+
pooled_router_k = F.normalize(router_k, p=2, dim=-1)
|
| 195 |
+
|
| 196 |
+
if past_key_value is not None:
|
| 197 |
+
num_template_mask_prefix = (doc_ids == -2).sum()
|
| 198 |
+
template_prefix_kcache = key_states[:, :, :num_template_mask_prefix]
|
| 199 |
+
template_prefix_vcache = value_states[:, :, :num_template_mask_prefix]
|
| 200 |
+
kwargs = {
|
| 201 |
+
"template_prefix_kcache": template_prefix_kcache,
|
| 202 |
+
"template_prefix_vcache": template_prefix_vcache,
|
| 203 |
+
}
|
| 204 |
+
if self.is_router_layer:
|
| 205 |
+
pooled_k_chunks, pooled_v_chunks = past_key_value.update(pooled_k_chunks, pooled_v_chunks, self.layer_idx)
|
| 206 |
+
kwargs2 = {
|
| 207 |
+
"doc_id_bias": doc_ids.shape[1],
|
| 208 |
+
"pooled_doc_ids": pooled_doc_ids,
|
| 209 |
+
"prefill_stage1_kvcache_size": pooled_k_chunks.shape[2],
|
| 210 |
+
}
|
| 211 |
+
if pooled_router_k is not None:
|
| 212 |
+
past_key_value.update_router_kcache(pooled_router_k, self.layer_idx)
|
| 213 |
+
kwargs.update(kwargs2)
|
| 214 |
+
past_key_value.record_kwargs(self.layer_idx, kwargs)
|
| 215 |
+
|
| 216 |
+
key_states = repeat_kv(key_states, self.num_key_value_groups)
|
| 217 |
+
value_states = repeat_kv(value_states, self.num_key_value_groups)
|
| 218 |
+
|
| 219 |
+
attn_output = torch.zeros((bsz, q_len, self.config.num_attention_heads * self.head_dim), device=device, dtype=dtype)
|
| 220 |
+
indices_b = torch.nonzero(doc_token_mask, as_tuple=False)
|
| 221 |
+
|
| 222 |
+
if indices_b.shape[0] > 0:
|
| 223 |
+
q_b, k_b, v_b = query_states[indices_b[:, 0], :, indices_b[:, 1]], key_states[indices_b[:, 0], :, indices_b[:, 1]], value_states[indices_b[:, 0], :, indices_b[:, 1]]
|
| 224 |
+
doc_ids_b = doc_ids[indices_b[:, 0], indices_b[:, 1]]
|
| 225 |
+
batch_indices_b = indices_b[:, 0]
|
| 226 |
+
global_doc_ids_b = batch_indices_b * (max_doc_id + 1) + doc_ids_b
|
| 227 |
+
_, counts_b = torch.unique_consecutive(global_doc_ids_b, return_counts=True)
|
| 228 |
+
cu_seqlens_b = F.pad(torch.cumsum(counts_b, dim=0, dtype=torch.int32), (1, 0))
|
| 229 |
+
output_b_flat = flash_attn_varlen_func(q_b, k_b, v_b, cu_seqlens_q=cu_seqlens_b, cu_seqlens_k=cu_seqlens_b, max_seqlen_q=int(counts_b.max()), max_seqlen_k=int(counts_b.max()), dropout_p=self.attention_dropout if self.training else 0.0, causal=True).view(-1, self.config.num_attention_heads * self.head_dim)
|
| 230 |
+
attn_output[indices_b[:, 0], indices_b[:, 1]] += output_b_flat
|
| 231 |
+
|
| 232 |
+
template_mask = (doc_ids == -2) & (attention_mask == 1)
|
| 233 |
+
template_indices = torch.nonzero(template_mask, as_tuple=False)
|
| 234 |
+
if template_indices.shape[0] > 0:
|
| 235 |
+
q_template = query_states.transpose(1, 2)[template_mask]
|
| 236 |
+
k_template = key_states.transpose(1, 2)[template_mask]
|
| 237 |
+
v_template = value_states.transpose(1, 2)[template_mask]
|
| 238 |
+
template_counts_per_sample = torch.bincount(template_indices[:, 0], minlength=bsz)
|
| 239 |
+
cu_seqlens_template = F.pad(torch.cumsum(template_counts_per_sample, dim=0, dtype=torch.int32), (1, 0))
|
| 240 |
+
output_template_flat = flash_attn_varlen_func(q_template, k_template, v_template, cu_seqlens_q=cu_seqlens_template, cu_seqlens_k=cu_seqlens_template, max_seqlen_q=int(template_counts_per_sample.max()), max_seqlen_k=int(template_counts_per_sample.max()), dropout_p=0.0, causal=True).view(-1, self.config.num_attention_heads * self.head_dim)
|
| 241 |
+
attn_output[template_mask] = output_template_flat
|
| 242 |
+
|
| 243 |
+
return self.o_proj(attn_output), None
|
| 244 |
+
|
| 245 |
+
elif stage == "prefill_stage2":
|
| 246 |
+
cache_kwargs = past_key_value.cache_kwargs[self.layer_idx]
|
| 247 |
+
if self.memory_client is not None:
|
| 248 |
+
if self.template_prefix_kcache is None:
|
| 249 |
+
self.template_prefix_kcache , self.template_prefix_vcache = self.memory_client.get_template_prefix_kvcaches(self.layer_idx)
|
| 250 |
+
if not self.template_prefix_kcache.is_cuda:
|
| 251 |
+
self.template_prefix_kcache = self.template_prefix_kcache.to(device)
|
| 252 |
+
if not self.template_prefix_vcache.is_cuda:
|
| 253 |
+
self.template_prefix_vcache = self.template_prefix_vcache.to(device)
|
| 254 |
+
template_prefix_kcache = self.template_prefix_kcache
|
| 255 |
+
template_prefix_vcache = self.template_prefix_vcache
|
| 256 |
+
else:
|
| 257 |
+
template_prefix_kcache = cache_kwargs["template_prefix_kcache"].to(device)
|
| 258 |
+
template_prefix_vcache = cache_kwargs["template_prefix_vcache"].to(device)
|
| 259 |
+
|
| 260 |
+
final_k_to_scatter, final_v_to_scatter = None, None
|
| 261 |
+
|
| 262 |
+
if self.is_router_layer:
|
| 263 |
+
routing_q_for_scoring = self.router_q_proj(hidden_states).view(hidden_shape).transpose(1, 2) if self.decouple_router else query_states
|
| 264 |
+
if self.aux_loss_method == "INFONCE":
|
| 265 |
+
routing_q_for_scoring = F.normalize(routing_q_for_scoring, p=2, dim=-1)
|
| 266 |
+
|
| 267 |
+
query_mask = ((doc_ids == 0) & (attention_mask == 1))
|
| 268 |
+
res = self.memory_client.doc_query(routing_q_for_scoring, query_mask, self.layer_idx)
|
| 269 |
+
final_k_to_scatter, final_v_to_scatter, final_scores, num_selected_chunks_per_sample, final_selected_doc_ids = res
|
| 270 |
+
|
| 271 |
+
if past_key_value.meta.get("require_recall_topk", False):
|
| 272 |
+
recall_topk_list = []
|
| 273 |
+
for i in range(bsz):
|
| 274 |
+
recall_topk_list.append({
|
| 275 |
+
"topk_doc_ids": final_selected_doc_ids[i].cpu().detach().tolist(),
|
| 276 |
+
"score": final_scores[i].cpu().detach().tolist(),
|
| 277 |
+
})
|
| 278 |
+
cache_kwargs["recall_topk"] = recall_topk_list
|
| 279 |
+
else:
|
| 280 |
+
num_selected_chunks_per_sample = torch.zeros(bsz, dtype=torch.long, device=device)
|
| 281 |
+
|
| 282 |
+
num_q_per_sample = attention_mask.sum(dim=1)
|
| 283 |
+
template_len = template_prefix_kcache.shape[2]
|
| 284 |
+
kv_lengths = template_len + num_selected_chunks_per_sample + num_q_per_sample
|
| 285 |
+
|
| 286 |
+
cu_seqlens_q = F.pad(num_q_per_sample.cumsum(0, dtype=torch.int32), (1, 0))
|
| 287 |
+
cu_seqlens_kv = F.pad(kv_lengths.cumsum(0, dtype=torch.int32), (1, 0))
|
| 288 |
+
|
| 289 |
+
total_q_tokens = cu_seqlens_q[-1].item()
|
| 290 |
+
total_kv_tokens = cu_seqlens_kv[-1].item()
|
| 291 |
+
|
| 292 |
+
q_final = torch.empty((total_q_tokens, self.config.num_attention_heads, self.head_dim), device=device, dtype=dtype)
|
| 293 |
+
k_final_unrepeated = torch.empty((self.config.num_key_value_heads, total_kv_tokens, self.head_dim), device=device, dtype=dtype)
|
| 294 |
+
v_final_unrepeated = torch.empty((self.config.num_key_value_heads, total_kv_tokens, self.head_dim), device=device, dtype=dtype)
|
| 295 |
+
|
| 296 |
+
valid_q_mask = (attention_mask == 1)
|
| 297 |
+
q_final = query_states.permute(0, 2, 1, 3)[valid_q_mask]
|
| 298 |
+
|
| 299 |
+
offset_start_sample = cu_seqlens_kv[:-1]
|
| 300 |
+
offset_start_template = offset_start_sample
|
| 301 |
+
offset_start_chunks = offset_start_sample + template_len
|
| 302 |
+
offset_start_question = offset_start_chunks + num_selected_chunks_per_sample
|
| 303 |
+
|
| 304 |
+
template_indices = torch.arange(template_len, device=device).unsqueeze(0) + offset_start_template.unsqueeze(1)
|
| 305 |
+
source_k_template = template_prefix_kcache.expand(bsz, -1, -1, -1).permute(1, 0, 2, 3).reshape(self.config.num_key_value_heads, -1, self.head_dim)
|
| 306 |
+
k_final_unrepeated[:, template_indices.flatten(), :] = source_k_template
|
| 307 |
+
source_v_template = template_prefix_vcache.expand(bsz, -1, -1, -1).permute(1, 0, 2, 3).reshape(self.config.num_key_value_heads, -1, self.head_dim)
|
| 308 |
+
v_final_unrepeated[:, template_indices.flatten(), :] = source_v_template
|
| 309 |
+
|
| 310 |
+
if self.is_router_layer and final_k_to_scatter is not None and final_k_to_scatter.shape[1] > 0:
|
| 311 |
+
batch_indices_for_chunks = torch.arange(bsz, device=device).repeat_interleave(num_selected_chunks_per_sample)
|
| 312 |
+
is_start_of_sample = torch.cat([torch.tensor([True], device=device), batch_indices_for_chunks[1:] != batch_indices_for_chunks[:-1]])
|
| 313 |
+
cumsum_ranks = torch.ones_like(batch_indices_for_chunks).cumsum(0)
|
| 314 |
+
start_offsets = cumsum_ranks[is_start_of_sample].repeat_interleave(num_selected_chunks_per_sample)
|
| 315 |
+
chunk_rank_in_sample = cumsum_ranks - start_offsets
|
| 316 |
+
|
| 317 |
+
chunk_dest_indices = offset_start_chunks[batch_indices_for_chunks] + chunk_rank_in_sample
|
| 318 |
+
|
| 319 |
+
k_final_unrepeated[:, chunk_dest_indices, :] = final_k_to_scatter
|
| 320 |
+
if final_v_to_scatter.device == torch.device("cpu"):
|
| 321 |
+
final_v_to_scatter = final_v_to_scatter.to(device)
|
| 322 |
+
v_final_unrepeated[:, chunk_dest_indices, :] = final_v_to_scatter
|
| 323 |
+
|
| 324 |
+
batch_indices_for_q = torch.arange(bsz, device=device).repeat_interleave(num_q_per_sample)
|
| 325 |
+
q_rank_in_sample = (torch.cumsum(valid_q_mask.int(), dim=1) - 1)[valid_q_mask]
|
| 326 |
+
q_dest_indices = offset_start_question[batch_indices_for_q] + q_rank_in_sample
|
| 327 |
+
|
| 328 |
+
k_final_unrepeated[:, q_dest_indices, :] = key_states.permute(1, 0, 2, 3).reshape(self.config.num_key_value_heads, -1, self.head_dim)[:, valid_q_mask.flatten(), :]
|
| 329 |
+
v_final_unrepeated[:, q_dest_indices, :] = value_states.permute(1, 0, 2, 3).reshape(self.config.num_key_value_heads, -1, self.head_dim)[:, valid_q_mask.flatten(), :]
|
| 330 |
+
|
| 331 |
+
k_final = k_final_unrepeated
|
| 332 |
+
v_final = v_final_unrepeated
|
| 333 |
+
|
| 334 |
+
output_flat = flash_attn_varlen_func(
|
| 335 |
+
q=q_final, k=k_final.transpose(0,1), v=v_final.transpose(0,1),
|
| 336 |
+
cu_seqlens_q=cu_seqlens_q, cu_seqlens_k=cu_seqlens_kv,
|
| 337 |
+
max_seqlen_q=num_q_per_sample.max().item(), max_seqlen_k=kv_lengths.max().item(),
|
| 338 |
+
dropout_p=0.0, causal=True
|
| 339 |
+
).view(-1, self.config.num_attention_heads * self.head_dim)
|
| 340 |
+
|
| 341 |
+
attn_output = torch.zeros((bsz, q_len, self.config.num_attention_heads * self.head_dim), device=device, dtype=dtype)
|
| 342 |
+
attn_output[valid_q_mask] = output_flat
|
| 343 |
+
|
| 344 |
+
max_kv_len = kv_lengths.max().item()
|
| 345 |
+
compacked_key_cache = torch.zeros((bsz, self.config.num_key_value_heads, max_kv_len, self.head_dim), dtype=dtype, device=device)
|
| 346 |
+
compacked_value_cache = torch.zeros((bsz, self.config.num_key_value_heads, max_kv_len, self.head_dim), dtype=dtype, device=device)
|
| 347 |
+
|
| 348 |
+
left_pad_mask = torch.arange(max_kv_len, device=device).unsqueeze(0) >= (max_kv_len - kv_lengths.unsqueeze(1))
|
| 349 |
+
|
| 350 |
+
compacked_key_cache.permute(0, 2, 1, 3)[left_pad_mask] = k_final_unrepeated.permute(1, 0, 2)
|
| 351 |
+
compacked_value_cache.permute(0, 2, 1, 3)[left_pad_mask] = v_final_unrepeated.permute(1, 0, 2)
|
| 352 |
+
|
| 353 |
+
cache_kwargs["compacked_key_cache"] = compacked_key_cache
|
| 354 |
+
cache_kwargs["compacked_value_cache"] = compacked_value_cache
|
| 355 |
+
cache_kwargs["kv_lengths"] = kv_lengths
|
| 356 |
+
cache_kwargs["attention_mask"] = left_pad_mask
|
| 357 |
+
past_key_value.record_kwargs(self.layer_idx, cache_kwargs)
|
| 358 |
+
|
| 359 |
+
return self.o_proj(attn_output), None
|
| 360 |
+
|
| 361 |
+
else:
|
| 362 |
+
cache_kwargs = past_key_value.cache_kwargs[self.layer_idx]
|
| 363 |
+
if "compacked_key_cache" not in cache_kwargs:
|
| 364 |
+
raise ValueError("批次化紧凑KV缓存未找到。Prefill stage 2 是否正确运行?")
|
| 365 |
+
|
| 366 |
+
compacked_key_cache = cache_kwargs["compacked_key_cache"]
|
| 367 |
+
compacked_value_cache = cache_kwargs["compacked_value_cache"]
|
| 368 |
+
kv_lengths = cache_kwargs["kv_lengths"]
|
| 369 |
+
layer_attention_mask = cache_kwargs["attention_mask"]
|
| 370 |
+
|
| 371 |
+
max_kv_len = compacked_key_cache.shape[2]
|
| 372 |
+
full_k_unrepeated = torch.cat([compacked_key_cache, key_states], dim=2)
|
| 373 |
+
full_v_unrepeated = torch.cat([compacked_value_cache, value_states], dim=2)
|
| 374 |
+
|
| 375 |
+
if past_key_value.meta.get("qa_mode", False):
|
| 376 |
+
cur_layer_attention_mask = torch.LongTensor([[1] * q_len for _ in range(bsz)]).to(device)
|
| 377 |
+
cur_layer_attention_mask = (cur_layer_attention_mask * attention_mask).type(layer_attention_mask.dtype)
|
| 378 |
+
layer_attention_mask = torch.cat([layer_attention_mask, cur_layer_attention_mask], dim=1)
|
| 379 |
+
attn_mask_4d = layer_attention_mask[:, None, None, :].expand(-1, self.config.num_attention_heads, 1, -1)
|
| 380 |
+
cache_kwargs["attention_mask"] = layer_attention_mask
|
| 381 |
+
else:
|
| 382 |
+
new_kv_lengths = kv_lengths + 1
|
| 383 |
+
max_new_kv_len = max_kv_len + 1
|
| 384 |
+
attn_mask_2d = torch.arange(max_new_kv_len, device=device).unsqueeze(0) >= (max_new_kv_len - new_kv_lengths.unsqueeze(1))
|
| 385 |
+
|
| 386 |
+
attn_mask_4d = attn_mask_2d[:, None, None, :].expand(-1, self.config.num_attention_heads, 1, -1)
|
| 387 |
+
cache_kwargs["kv_lengths"] = new_kv_lengths
|
| 388 |
+
|
| 389 |
+
key_states_gqa = repeat_kv(full_k_unrepeated, self.num_key_value_groups)
|
| 390 |
+
value_states_gqa = repeat_kv(full_v_unrepeated, self.num_key_value_groups)
|
| 391 |
+
|
| 392 |
+
attn_output = F.scaled_dot_product_attention(
|
| 393 |
+
query_states,
|
| 394 |
+
key_states_gqa,
|
| 395 |
+
value_states_gqa,
|
| 396 |
+
attn_mask=attn_mask_4d,
|
| 397 |
+
dropout_p=0.0,
|
| 398 |
+
is_causal=False
|
| 399 |
+
).transpose(1, 2).reshape(bsz, q_len, -1)
|
| 400 |
+
|
| 401 |
+
cache_kwargs["compacked_key_cache"] = full_k_unrepeated
|
| 402 |
+
cache_kwargs["compacked_value_cache"] = full_v_unrepeated
|
| 403 |
+
past_key_value.record_kwargs(self.layer_idx, cache_kwargs)
|
| 404 |
+
|
| 405 |
+
return self.o_proj(attn_output), None
|
| 406 |
+
|
| 407 |
+
def _calculate_routing_scores_adaptive(
|
| 408 |
+
self,
|
| 409 |
+
query_states: torch.Tensor, # [B, H, Q_len, D]
|
| 410 |
+
pooled_k_bched: torch.Tensor, # [B, C, H, D]
|
| 411 |
+
routing_query_mask: torch.Tensor, # [B, Q_len] - 1 for valid, 0 for pad
|
| 412 |
+
chunk_mask: torch.Tensor, # [B, C] - 1 for valid, 0 for pad
|
| 413 |
+
) -> torch.Tensor:
|
| 414 |
+
bsz, num_heads, q_len, head_dim = query_states.shape
|
| 415 |
+
_, max_chunks, _, _ = pooled_k_bched.shape
|
| 416 |
+
dtype, device = query_states.dtype, query_states.device
|
| 417 |
+
min_val = torch.finfo(dtype).min
|
| 418 |
+
|
| 419 |
+
k_states_T = pooled_k_bched.permute(0, 2, 3, 1)
|
| 420 |
+
|
| 421 |
+
current_scaling = 1.0 if self.decouple_router and "INFONCE" in self.aux_loss_method else self.scaling
|
| 422 |
+
scores = torch.matmul(query_states, k_states_T) * current_scaling
|
| 423 |
+
|
| 424 |
+
q_mask_expanded = routing_query_mask.view(bsz, 1, q_len, 1)
|
| 425 |
+
k_mask_expanded = chunk_mask.view(bsz, 1, 1, max_chunks)
|
| 426 |
+
|
| 427 |
+
final_mask = q_mask_expanded & k_mask_expanded
|
| 428 |
+
scores.masked_fill_(~final_mask, min_val)
|
| 429 |
+
|
| 430 |
+
if self.head_reduce_method == "max":
|
| 431 |
+
scores = scores.max(dim=1).values
|
| 432 |
+
elif self.head_reduce_method == "mean":
|
| 433 |
+
scores = scores.mean(dim=1)
|
| 434 |
+
else:
|
| 435 |
+
raise NotImplementedError(f"Unsupported head reduce method: {self.head_reduce_method}")
|
| 436 |
+
|
| 437 |
+
if self.query_reduce_method == "max":
|
| 438 |
+
scores_final = scores.max(dim=1).values
|
| 439 |
+
|
| 440 |
+
elif self.query_reduce_method == "mean":
|
| 441 |
+
valid_mask = final_mask.squeeze(1) # [B, Q_len, C]
|
| 442 |
+
|
| 443 |
+
scores_clean = torch.where(valid_mask, scores, torch.zeros_like(scores))
|
| 444 |
+
sum_scores = scores_clean.sum(dim=1) # [B, C]
|
| 445 |
+
counts = valid_mask.sum(dim=1).to(dtype).clamp(min=1.0)
|
| 446 |
+
mean_scores = sum_scores / counts
|
| 447 |
+
|
| 448 |
+
scores_final = torch.where(
|
| 449 |
+
chunk_mask,
|
| 450 |
+
mean_scores,
|
| 451 |
+
torch.tensor(min_val, device=device, dtype=dtype)
|
| 452 |
+
)
|
| 453 |
+
|
| 454 |
+
elif self.query_reduce_method == "last":
|
| 455 |
+
q_lens = routing_query_mask.sum(dim=1).long()
|
| 456 |
+
last_indices = (q_lens - 1).clamp(min=0)
|
| 457 |
+
|
| 458 |
+
gather_idx = last_indices.view(bsz, 1, 1).expand(-1, 1, max_chunks)
|
| 459 |
+
scores_final = scores.gather(1, gather_idx).squeeze(1)
|
| 460 |
+
scores_final.masked_fill_(~chunk_mask, min_val)
|
| 461 |
+
|
| 462 |
+
else:
|
| 463 |
+
raise NotImplementedError(f"Unsupported query reduce method: {self.query_reduce_method}")
|
| 464 |
+
|
| 465 |
+
return scores_final
|
| 466 |
+
|
| 467 |
+
def sequence_pooling_kv(self, key_states, value_states, doc_indices, global_chunk_ids):
|
| 468 |
+
k_docs = key_states[doc_indices[:, 0], :, doc_indices[:, 1]]
|
| 469 |
+
v_docs = value_states[doc_indices[:, 0], :, doc_indices[:, 1]]
|
| 470 |
+
unique_global_chunk_ids, chunk_lengths = torch.unique_consecutive(global_chunk_ids, return_counts=True)
|
| 471 |
+
|
| 472 |
+
num_unique_chunks = unique_global_chunk_ids.shape[0]
|
| 473 |
+
chunk_counts_view = chunk_lengths.view(-1, 1, 1).to(dtype=torch.float32)
|
| 474 |
+
|
| 475 |
+
def compute_pooled_states_via_cumsum(states, counts_view, lengths):
|
| 476 |
+
b, h, d = states.shape
|
| 477 |
+
states_flat = states.reshape(b, -1).to(dtype=torch.float32)
|
| 478 |
+
states_cumsum = F.pad(torch.cumsum(states_flat, dim=0), (0, 0, 1, 0))
|
| 479 |
+
chunk_cu_seqlens = F.pad(torch.cumsum(lengths, 0), (1, 0))
|
| 480 |
+
state_sums_flat = states_cumsum[chunk_cu_seqlens[1:]] - states_cumsum[chunk_cu_seqlens[:-1]]
|
| 481 |
+
state_sums = state_sums_flat.view(num_unique_chunks, h, d)
|
| 482 |
+
return (state_sums / counts_view).to(dtype=states.dtype)
|
| 483 |
+
|
| 484 |
+
pooled_k_chunks = compute_pooled_states_via_cumsum(k_docs, chunk_counts_view, chunk_lengths)
|
| 485 |
+
pooled_v_chunks = compute_pooled_states_via_cumsum(v_docs, chunk_counts_view, chunk_lengths)
|
| 486 |
+
return pooled_k_chunks, pooled_v_chunks
|
| 487 |
+
|
| 488 |
+
def sequence_pooling_qkv(self, query_states, key_states, value_states, doc_indices, global_chunk_ids):
|
| 489 |
+
q_docs = query_states[doc_indices[:, 0], :, doc_indices[:, 1]]
|
| 490 |
+
k_docs = key_states[doc_indices[:, 0], :, doc_indices[:, 1]]
|
| 491 |
+
v_docs = value_states[doc_indices[:, 0], :, doc_indices[:, 1]]
|
| 492 |
+
unique_global_chunk_ids, chunk_lengths = torch.unique_consecutive(global_chunk_ids, return_counts=True)
|
| 493 |
+
|
| 494 |
+
num_unique_chunks = unique_global_chunk_ids.shape[0]
|
| 495 |
+
chunk_counts_view = chunk_lengths.view(-1, 1, 1).to(dtype=torch.float32)
|
| 496 |
+
def compute_pooled_states_via_cumsum(states, counts_view, lengths):
|
| 497 |
+
b, h, d = states.shape
|
| 498 |
+
states_flat = states.reshape(b, -1).to(dtype=torch.float32)
|
| 499 |
+
states_cumsum = F.pad(torch.cumsum(states_flat, dim=0), (0, 0, 1, 0))
|
| 500 |
+
chunk_cu_seqlens = F.pad(torch.cumsum(lengths, 0), (1, 0))
|
| 501 |
+
|
| 502 |
+
state_sums_flat = states_cumsum[chunk_cu_seqlens[1:]] - states_cumsum[chunk_cu_seqlens[:-1]]
|
| 503 |
+
state_sums = state_sums_flat.view(num_unique_chunks, h, d)
|
| 504 |
+
return (state_sums / counts_view).to(dtype=states.dtype)
|
| 505 |
+
|
| 506 |
+
pooled_q_chunks = compute_pooled_states_via_cumsum(q_docs, chunk_counts_view, chunk_lengths)
|
| 507 |
+
pooled_k_chunks = compute_pooled_states_via_cumsum(k_docs, chunk_counts_view, chunk_lengths)
|
| 508 |
+
pooled_v_chunks = compute_pooled_states_via_cumsum(v_docs, chunk_counts_view, chunk_lengths)
|
| 509 |
+
return pooled_q_chunks, pooled_k_chunks, pooled_v_chunks
|
| 510 |
+
|
| 511 |
+
def count_chunks_per_batch(self, doc_ids, attention_mask, kernel_size):
|
| 512 |
+
batch_size = doc_ids.size(0)
|
| 513 |
+
chunk_counts = []
|
| 514 |
+
|
| 515 |
+
for i in range(batch_size):
|
| 516 |
+
mask = attention_mask[i]
|
| 517 |
+
ids = doc_ids[i]
|
| 518 |
+
valid_ids = ids[mask == 1]
|
| 519 |
+
|
| 520 |
+
if len(valid_ids) == 0:
|
| 521 |
+
chunk_counts.append(0)
|
| 522 |
+
continue
|
| 523 |
+
_, counts = torch.unique_consecutive(valid_ids, return_counts=True)
|
| 524 |
+
|
| 525 |
+
num_chunks = (counts + kernel_size - 1) // kernel_size
|
| 526 |
+
total_chunks = num_chunks.sum().item()
|
| 527 |
+
chunk_counts.append(total_chunks)
|
| 528 |
+
|
| 529 |
+
return torch.LongTensor(chunk_counts).to(doc_ids.device)
|
| 530 |
+
|
| 531 |
+
def _forward(
|
| 532 |
+
self,
|
| 533 |
+
hidden_states: torch.Tensor,
|
| 534 |
+
doc_ids: torch.LongTensor,
|
| 535 |
+
attention_mask: Optional[torch.Tensor] = None,
|
| 536 |
+
position_embeddings: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
|
| 537 |
+
past_key_value: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
|
| 538 |
+
**kwargs,
|
| 539 |
+
) -> Tuple[torch.Tensor, Optional[torch.Tensor]]:
|
| 540 |
+
bsz, q_len, _ = hidden_states.shape
|
| 541 |
+
device, dtype = hidden_states.device, hidden_states.dtype
|
| 542 |
+
hidden_shape = (bsz, q_len, -1, self.head_dim)
|
| 543 |
+
|
| 544 |
+
query_states = self.q_norm(self.q_proj(hidden_states).view(hidden_shape)).transpose(1, 2)
|
| 545 |
+
key_states = self.k_norm(self.k_proj(hidden_states).view(hidden_shape)).transpose(1, 2)
|
| 546 |
+
value_states = self.v_proj(hidden_states).view(hidden_shape).transpose(1, 2)
|
| 547 |
+
cos, sin = position_embeddings
|
| 548 |
+
query_states, key_states = apply_rotary_pos_emb(query_states, key_states, cos, sin)
|
| 549 |
+
|
| 550 |
+
key_states = repeat_kv(key_states, self.num_key_value_groups)
|
| 551 |
+
value_states = repeat_kv(value_states, self.num_key_value_groups)
|
| 552 |
+
|
| 553 |
+
routing_query_mask = (doc_ids == 0) & (attention_mask == 1)
|
| 554 |
+
doc_token_mask = (doc_ids > 0) & (attention_mask == 1)
|
| 555 |
+
|
| 556 |
+
query_indices = torch.nonzero(routing_query_mask, as_tuple=False)
|
| 557 |
+
doc_indices = torch.nonzero(doc_token_mask, as_tuple=False)
|
| 558 |
+
|
| 559 |
+
if doc_indices.shape[0] == 0 or query_indices.shape[0] == 0:
|
| 560 |
+
raise ValueError("No query or doc tokens found")
|
| 561 |
+
|
| 562 |
+
max_doc_id = int(doc_ids.max().item())
|
| 563 |
+
attn_output = torch.zeros((bsz, q_len, self.config.num_attention_heads * self.head_dim), device=device, dtype=dtype)
|
| 564 |
+
if self.is_router_layer:
|
| 565 |
+
original_doc_ids = doc_ids[doc_token_mask]
|
| 566 |
+
original_doc_batch_indices = doc_indices[:, 0]
|
| 567 |
+
|
| 568 |
+
global_doc_ids = original_doc_batch_indices * (max_doc_id + 1) + original_doc_ids
|
| 569 |
+
_, counts = torch.unique_consecutive(global_doc_ids, return_counts=True)
|
| 570 |
+
total_doc_tokens = global_doc_ids.shape[0]
|
| 571 |
+
|
| 572 |
+
offsets = torch.zeros(counts.shape[0] + 1, dtype=counts.dtype, device=device)
|
| 573 |
+
offsets[1:] = counts.cumsum(0)
|
| 574 |
+
offsets = offsets[:-1]
|
| 575 |
+
|
| 576 |
+
expanded_offsets = torch.repeat_interleave(offsets, counts)
|
| 577 |
+
original_order_ranks = torch.arange(total_doc_tokens, device=device) - expanded_offsets
|
| 578 |
+
|
| 579 |
+
chunk_indices = original_order_ranks // self.pooling_kernel_size
|
| 580 |
+
max_chunks_per_doc = (q_len // self.pooling_kernel_size) + 1
|
| 581 |
+
|
| 582 |
+
global_chunk_ids = global_doc_ids * max_chunks_per_doc + chunk_indices
|
| 583 |
+
|
| 584 |
+
routing_q_states = None
|
| 585 |
+
routing_pooled_k_chunks = None
|
| 586 |
+
|
| 587 |
+
if self.decouple_router:
|
| 588 |
+
routing_q_states = self.router_q_proj(hidden_states).view(hidden_shape).transpose(1, 2)
|
| 589 |
+
if "INFONCE" in self.aux_loss_method:
|
| 590 |
+
routing_q_states = F.normalize(routing_q_states, p=2, dim=-1)
|
| 591 |
+
|
| 592 |
+
|
| 593 |
+
r_k_raw = self.router_k_proj(hidden_states).view(hidden_shape).transpose(1, 2)
|
| 594 |
+
r_k_raw = repeat_kv(r_k_raw, self.num_key_value_groups)
|
| 595 |
+
r_k_docs = r_k_raw[doc_indices[:, 0], :, doc_indices[:, 1]]
|
| 596 |
+
|
| 597 |
+
unique_global_chunk_ids = torch.unique_consecutive(global_chunk_ids)
|
| 598 |
+
_, chunk_lengths = torch.unique_consecutive(global_chunk_ids, return_counts=True)
|
| 599 |
+
|
| 600 |
+
chunk_counts_view = chunk_lengths.view(-1, 1, 1).to(dtype=torch.float32)
|
| 601 |
+
b_k, h_k, d_k = r_k_docs.shape
|
| 602 |
+
k_flat = r_k_docs.reshape(b_k, -1).to(dtype=torch.float32)
|
| 603 |
+
k_cumsum = F.pad(torch.cumsum(k_flat, dim=0), (0, 0, 1, 0))
|
| 604 |
+
chunk_cu_seqlens = F.pad(torch.cumsum(chunk_lengths, 0), (1, 0))
|
| 605 |
+
k_sums_flat = k_cumsum[chunk_cu_seqlens[1:]] - k_cumsum[chunk_cu_seqlens[:-1]]
|
| 606 |
+
routing_pooled_k_chunks = (k_sums_flat.view(unique_global_chunk_ids.shape[0], h_k, d_k) / chunk_counts_view).to(dtype=r_k_docs.dtype)
|
| 607 |
+
if "INFONCE" in self.aux_loss_method:
|
| 608 |
+
routing_pooled_k_chunks = F.normalize(routing_pooled_k_chunks, p=2, dim=-1)
|
| 609 |
+
|
| 610 |
+
pooled_q_chunks = query_states[doc_indices[:, 0], :, doc_indices[:, 1]]
|
| 611 |
+
pooled_k_chunks = key_states[doc_indices[:, 0], :, doc_indices[:, 1]]
|
| 612 |
+
pooled_v_chunks = value_states[doc_indices[:, 0], :, doc_indices[:, 1]]
|
| 613 |
+
num_doc_tokens = pooled_q_chunks.shape[0]
|
| 614 |
+
num_chunks = num_doc_tokens // self.pooling_kernel_size
|
| 615 |
+
|
| 616 |
+
pooled_q_chunks = pooled_q_chunks.view(num_chunks, self.pooling_kernel_size, self.num_heads, self.head_dim).mean(dim=1)
|
| 617 |
+
pooled_k_chunks = pooled_k_chunks.view(num_chunks, self.pooling_kernel_size, self.num_heads, self.head_dim).mean(dim=1)
|
| 618 |
+
pooled_v_chunks = pooled_v_chunks.view(num_chunks, self.pooling_kernel_size, self.num_heads, self.head_dim).mean(dim=1)
|
| 619 |
+
num_heads = self.config.num_attention_heads
|
| 620 |
+
head_dim = self.head_dim
|
| 621 |
+
else:
|
| 622 |
+
pooled_q_chunks, pooled_k_chunks, pooled_v_chunks = self.sequence_pooling_qkv(
|
| 623 |
+
query_states,
|
| 624 |
+
key_states,
|
| 625 |
+
value_states,
|
| 626 |
+
doc_indices,
|
| 627 |
+
global_chunk_ids,
|
| 628 |
+
)
|
| 629 |
+
num_heads = self.config.num_attention_heads
|
| 630 |
+
head_dim = self.head_dim
|
| 631 |
+
|
| 632 |
+
routing_q_states = query_states
|
| 633 |
+
routing_pooled_k_chunks = pooled_k_chunks
|
| 634 |
+
if "INFONCE" in self.aux_loss_method:
|
| 635 |
+
routing_q_states = F.normalize(routing_q_states, p=2, dim=-1)
|
| 636 |
+
routing_pooled_k_chunks = F.normalize(routing_pooled_k_chunks, p=2, dim=-1)
|
| 637 |
+
|
| 638 |
+
unique_global_chunk_ids = torch.unique_consecutive(global_chunk_ids)
|
| 639 |
+
num_unique_chunks = unique_global_chunk_ids.shape[0]
|
| 640 |
+
chunks_per_sample = self.count_chunks_per_batch(doc_ids, doc_token_mask, kernel_size=self.pooling_kernel_size)
|
| 641 |
+
|
| 642 |
+
max_chunks = chunks_per_sample.max().item()
|
| 643 |
+
pooled_router_k_bched = torch.zeros((bsz, max_chunks, num_heads, self.head_dim), device=device, dtype=dtype)
|
| 644 |
+
chunk_mask = torch.arange(max_chunks, device=device).unsqueeze(0) < chunks_per_sample.unsqueeze(1)
|
| 645 |
+
pooled_router_k_bched[chunk_mask] = routing_pooled_k_chunks
|
| 646 |
+
q_lens = routing_query_mask.sum(dim=1) # (B,)
|
| 647 |
+
max_q_len = int(q_lens.max().item())
|
| 648 |
+
|
| 649 |
+
if max_q_len == 0:
|
| 650 |
+
max_q_len = 1
|
| 651 |
+
valid_q_flat = routing_q_states.transpose(1, 2)[routing_query_mask] # [Total_Valid_Q, H, D]
|
| 652 |
+
|
| 653 |
+
compact_q_states_t = torch.zeros(
|
| 654 |
+
bsz, max_q_len, self.config.num_attention_heads, self.head_dim,
|
| 655 |
+
device=device, dtype=dtype
|
| 656 |
+
)
|
| 657 |
+
|
| 658 |
+
idx_range = torch.arange(max_q_len, device=device).unsqueeze(0)
|
| 659 |
+
mask_compact_q = idx_range < q_lens.unsqueeze(1)
|
| 660 |
+
|
| 661 |
+
compact_q_states_t[mask_compact_q] = valid_q_flat
|
| 662 |
+
compact_q_states = compact_q_states_t.transpose(1, 2)
|
| 663 |
+
|
| 664 |
+
max_scores_per_chunk = self._calculate_routing_scores_adaptive(
|
| 665 |
+
compact_q_states, # (B, H, S, D)
|
| 666 |
+
pooled_router_k_bched, # (B, C, H, D)
|
| 667 |
+
mask_compact_q, # (B, S)
|
| 668 |
+
chunk_mask # (B, C)
|
| 669 |
+
)
|
| 670 |
+
|
| 671 |
+
pooled_global_doc_ids = unique_global_chunk_ids // max_chunks_per_doc
|
| 672 |
+
pooled_doc_ids_in_sample = pooled_global_doc_ids % (max_doc_id + 1)
|
| 673 |
+
chunk_to_doc_id_flat = pooled_doc_ids_in_sample # 形状: (total_chunks, )
|
| 674 |
+
|
| 675 |
+
chunk_to_doc_id_bched = torch.full((bsz, max_chunks), 0, dtype=torch.long, device=device)
|
| 676 |
+
chunk_to_doc_id_bched[chunk_mask] = chunk_to_doc_id_flat
|
| 677 |
+
|
| 678 |
+
offsets = torch.arange(bsz, device=device) * (max_doc_id + 1)
|
| 679 |
+
global_chunk_to_doc_id = chunk_to_doc_id_bched + offsets.unsqueeze(1)
|
| 680 |
+
flat_doc_scores = torch.full((bsz * (max_doc_id + 1),), -float('inf'), device=device, dtype=dtype)
|
| 681 |
+
|
| 682 |
+
valid_scores_flat = max_scores_per_chunk[chunk_mask]
|
| 683 |
+
valid_global_doc_ids_flat = global_chunk_to_doc_id[chunk_mask]
|
| 684 |
+
|
| 685 |
+
if self.chunk_reduce_method == "max":
|
| 686 |
+
doc_scores = flat_doc_scores.scatter_reduce(
|
| 687 |
+
dim=0,
|
| 688 |
+
index=valid_global_doc_ids_flat,
|
| 689 |
+
src=valid_scores_flat,
|
| 690 |
+
reduce="amax",
|
| 691 |
+
include_self=True
|
| 692 |
+
)
|
| 693 |
+
|
| 694 |
+
elif self.chunk_reduce_method == "mean":
|
| 695 |
+
flat_doc_sums = torch.zeros_like(flat_doc_scores)
|
| 696 |
+
|
| 697 |
+
flat_doc_sums = flat_doc_sums.scatter_reduce(
|
| 698 |
+
dim=0,
|
| 699 |
+
index=valid_global_doc_ids_flat,
|
| 700 |
+
src=valid_scores_flat,
|
| 701 |
+
reduce="sum",
|
| 702 |
+
include_self=False
|
| 703 |
+
)
|
| 704 |
+
|
| 705 |
+
flat_doc_counts = torch.zeros_like(flat_doc_scores)
|
| 706 |
+
ones = torch.ones_like(valid_scores_flat)
|
| 707 |
+
|
| 708 |
+
flat_doc_counts = flat_doc_counts.scatter_reduce(
|
| 709 |
+
dim=0,
|
| 710 |
+
index=valid_global_doc_ids_flat,
|
| 711 |
+
src=ones,
|
| 712 |
+
reduce="sum",
|
| 713 |
+
include_self=False
|
| 714 |
+
)
|
| 715 |
+
|
| 716 |
+
flat_doc_counts_safe = flat_doc_counts.clamp(min=1.0)
|
| 717 |
+
mean_scores = flat_doc_sums / flat_doc_counts_safe
|
| 718 |
+
|
| 719 |
+
doc_scores = torch.where(
|
| 720 |
+
flat_doc_counts > 0,
|
| 721 |
+
mean_scores,
|
| 722 |
+
flat_doc_scores # 这里是 -inf
|
| 723 |
+
)
|
| 724 |
+
|
| 725 |
+
else:
|
| 726 |
+
raise ValueError(f"Invalid chunk reduction method: {self.chunk_reduce_method}")
|
| 727 |
+
|
| 728 |
+
scores_by_batch = doc_scores.view(bsz, -1)
|
| 729 |
+
return_scores_by_batch = scores_by_batch.clone()
|
| 730 |
+
|
| 731 |
+
num_docs_per_sample = (scores_by_batch > -1e9).sum(dim=1)
|
| 732 |
+
# 为每个样本计算k值:取配置的top_k和实际文档数的较小者
|
| 733 |
+
k_per_sample = torch.min(num_docs_per_sample, torch.full_like(num_docs_per_sample, self.top_k_docs))
|
| 734 |
+
|
| 735 |
+
_, sorted_indices = torch.sort(scores_by_batch, dim=1, descending=True)
|
| 736 |
+
|
| 737 |
+
range_tensor = torch.arange(scores_by_batch.shape[1], device=device).expand(bsz, -1)
|
| 738 |
+
selection_mask = range_tensor < k_per_sample.unsqueeze(1)
|
| 739 |
+
|
| 740 |
+
selected_docs_indices = sorted_indices.masked_fill(~selection_mask, -50)
|
| 741 |
+
|
| 742 |
+
prompt_and_response_mask = (doc_ids < 1) & (attention_mask == 1)
|
| 743 |
+
# 此处的 selected_docs_indices 已经是修复后的张量,所以这行代码无需修改
|
| 744 |
+
selected_docs_mask = torch.any(doc_ids.unsqueeze(-1) == selected_docs_indices.unsqueeze(1), dim=-1) & doc_token_mask
|
| 745 |
+
|
| 746 |
+
pa_indices = torch.nonzero(prompt_and_response_mask, as_tuple=False)
|
| 747 |
+
q_pa_flat = query_states[pa_indices[:, 0], :, pa_indices[:, 1]]
|
| 748 |
+
k_pa_flat = key_states[pa_indices[:, 0], :, pa_indices[:, 1]]
|
| 749 |
+
v_pa_flat = value_states[pa_indices[:, 0], :, pa_indices[:, 1]]
|
| 750 |
+
sort_key_pa = pa_indices[:, 0] * q_len + pa_indices[:, 1]
|
| 751 |
+
|
| 752 |
+
selected_doc_token_indices = torch.nonzero(selected_docs_mask, as_tuple=False)
|
| 753 |
+
is_doc_token_mask_flat = doc_token_mask.flatten()
|
| 754 |
+
global_chunk_ids_padded = torch.full((bsz * q_len,), -1, dtype=torch.long, device=device)
|
| 755 |
+
global_chunk_ids_padded[is_doc_token_mask_flat] = global_chunk_ids
|
| 756 |
+
selected_chunk_ids_flat = global_chunk_ids_padded.view(bsz, q_len)[selected_docs_mask]
|
| 757 |
+
|
| 758 |
+
unique_selected_chunk_ids, inverse_indices_fix = torch.unique(selected_chunk_ids_flat, sorted=True, return_inverse=True)
|
| 759 |
+
if unique_selected_chunk_ids.numel() > 0:
|
| 760 |
+
first_occurrence_indices = torch.empty_like(unique_selected_chunk_ids, dtype=torch.long)
|
| 761 |
+
first_occurrence_indices.scatter_reduce_(src=torch.arange(selected_chunk_ids_flat.numel(), device=device),index=inverse_indices_fix, dim=0, reduce='amin', include_self=False)
|
| 762 |
+
|
| 763 |
+
representative_indices = selected_doc_token_indices[first_occurrence_indices]
|
| 764 |
+
sort_key_chunks = representative_indices[:, 0] * q_len + representative_indices[:, 1]
|
| 765 |
+
|
| 766 |
+
map_gcid_to_poolidx = torch.full((int(global_chunk_ids.max().item()) + 1,), -1, dtype=torch.long, device=device)
|
| 767 |
+
map_gcid_to_poolidx[unique_global_chunk_ids] = torch.arange(num_unique_chunks, device=device)
|
| 768 |
+
|
| 769 |
+
pool_indices_to_gather = map_gcid_to_poolidx[unique_selected_chunk_ids]
|
| 770 |
+
|
| 771 |
+
assert (pool_indices_to_gather.sort().values != pool_indices_to_gather).sum() == 0
|
| 772 |
+
q_pooled_sel_flat = pooled_q_chunks[pool_indices_to_gather]
|
| 773 |
+
k_pooled_sel_flat = pooled_k_chunks[pool_indices_to_gather]
|
| 774 |
+
v_pooled_sel_flat = pooled_v_chunks[pool_indices_to_gather]
|
| 775 |
+
|
| 776 |
+
batch_indices_chunks = representative_indices[:, 0]
|
| 777 |
+
else:
|
| 778 |
+
sort_key_chunks = torch.tensor([], dtype=torch.long, device=device)
|
| 779 |
+
q_pooled_sel_flat = torch.tensor([], dtype=dtype, device=device).view(0, num_heads, head_dim)
|
| 780 |
+
k_pooled_sel_flat = torch.tensor([], dtype=dtype, device=device).view(0, num_heads, head_dim)
|
| 781 |
+
v_pooled_sel_flat = torch.tensor([], dtype=dtype, device=device).view(0, num_heads, head_dim)
|
| 782 |
+
batch_indices_chunks = torch.tensor([], dtype=torch.long, device=device)
|
| 783 |
+
|
| 784 |
+
q_combined = torch.cat([q_pa_flat, q_pooled_sel_flat], dim=0)
|
| 785 |
+
k_combined = torch.cat([k_pa_flat, k_pooled_sel_flat], dim=0)
|
| 786 |
+
v_combined = torch.cat([v_pa_flat, v_pooled_sel_flat], dim=0)
|
| 787 |
+
|
| 788 |
+
combined_sort_keys = torch.cat([sort_key_pa, sort_key_chunks], dim=0)
|
| 789 |
+
_, final_sort_indices = torch.sort(combined_sort_keys)
|
| 790 |
+
|
| 791 |
+
q_a_final = q_combined[final_sort_indices]
|
| 792 |
+
k_a_final = k_combined[final_sort_indices]
|
| 793 |
+
v_a_final = v_combined[final_sort_indices]
|
| 794 |
+
|
| 795 |
+
# 4.4 计算cu_seqlens (逻辑不变)
|
| 796 |
+
batch_indices_pa = pa_indices[:, 0]
|
| 797 |
+
batch_indices_combined = torch.cat([batch_indices_pa, batch_indices_chunks], dim=0)
|
| 798 |
+
sorted_batch_indices = batch_indices_combined[final_sort_indices]
|
| 799 |
+
|
| 800 |
+
batch_counts_a = torch.bincount(sorted_batch_indices, minlength=bsz)
|
| 801 |
+
cu_seqlens_a = F.pad(torch.cumsum(batch_counts_a, dim=0, dtype=torch.int32), (1, 0))
|
| 802 |
+
else:
|
| 803 |
+
prompt_and_response_mask = (doc_ids < 1) & (attention_mask == 1)
|
| 804 |
+
pa_indices = torch.nonzero(prompt_and_response_mask, as_tuple=False)
|
| 805 |
+
q_a_final = query_states[pa_indices[:, 0], :, pa_indices[:, 1]]
|
| 806 |
+
k_a_final = key_states[pa_indices[:, 0], :, pa_indices[:, 1]]
|
| 807 |
+
v_a_final = value_states[pa_indices[:, 0], :, pa_indices[:, 1]]
|
| 808 |
+
batch_counts_a = prompt_and_response_mask.sum(dim=1)
|
| 809 |
+
cu_seqlens_a = F.pad(torch.cumsum(batch_counts_a, dim=0, dtype=torch.int32), (1, 0))
|
| 810 |
+
return_scores_by_batch = None
|
| 811 |
+
|
| 812 |
+
if q_a_final.shape[0] > 0:
|
| 813 |
+
output_a_final = flash_attn_varlen_func(
|
| 814 |
+
q_a_final, k_a_final, v_a_final,
|
| 815 |
+
cu_seqlens_q=cu_seqlens_a, cu_seqlens_k=cu_seqlens_a,
|
| 816 |
+
max_seqlen_q=int(batch_counts_a.max()), max_seqlen_k=int(batch_counts_a.max()),
|
| 817 |
+
dropout_p=self.attention_dropout if self.training else 0.0,
|
| 818 |
+
causal=True
|
| 819 |
+
).view(-1, self.config.num_attention_heads * self.head_dim)
|
| 820 |
+
|
| 821 |
+
if self.is_router_layer:
|
| 822 |
+
is_pa_mask_combined = torch.cat([
|
| 823 |
+
torch.ones(pa_indices.shape[0], dtype=torch.bool, device=device),
|
| 824 |
+
torch.zeros(q_pooled_sel_flat.shape[0], dtype=torch.bool, device=device) # 修正为使用池化块的数量
|
| 825 |
+
], dim=0)
|
| 826 |
+
is_pa_mask_sorted = is_pa_mask_combined[final_sort_indices]
|
| 827 |
+
|
| 828 |
+
output_pa_part = output_a_final[is_pa_mask_sorted]
|
| 829 |
+
attn_output[pa_indices[:, 0], pa_indices[:, 1]] = output_pa_part
|
| 830 |
+
else:
|
| 831 |
+
attn_output[pa_indices[:, 0], pa_indices[:, 1]] = output_a_final
|
| 832 |
+
|
| 833 |
+
indices_b = torch.nonzero(doc_token_mask, as_tuple=False)
|
| 834 |
+
if indices_b.shape[0] > 0:
|
| 835 |
+
q_b, k_b, v_b = query_states[indices_b[:, 0], :, indices_b[:, 1]], key_states[indices_b[:, 0], :, indices_b[:, 1]], value_states[indices_b[:, 0], :, indices_b[:, 1]]
|
| 836 |
+
doc_ids_b = doc_ids[indices_b[:, 0], indices_b[:, 1]]
|
| 837 |
+
batch_indices_b = indices_b[:, 0]
|
| 838 |
+
|
| 839 |
+
global_doc_ids_b = batch_indices_b * (max_doc_id + 1) + doc_ids_b
|
| 840 |
+
|
| 841 |
+
_, counts_b = torch.unique_consecutive(global_doc_ids_b, return_counts=True)
|
| 842 |
+
cu_seqlens_b = F.pad(torch.cumsum(counts_b, dim=0, dtype=torch.int32), (1, 0))
|
| 843 |
+
|
| 844 |
+
output_b_flat = flash_attn_varlen_func(
|
| 845 |
+
q_b, k_b, v_b, cu_seqlens_q=cu_seqlens_b, cu_seqlens_k=cu_seqlens_b,
|
| 846 |
+
max_seqlen_q=int(counts_b.max()), max_seqlen_k=int(counts_b.max()),
|
| 847 |
+
dropout_p=self.attention_dropout if self.training else 0.0, causal=True
|
| 848 |
+
).view(-1, self.config.num_attention_heads * self.head_dim)
|
| 849 |
+
|
| 850 |
+
attn_output[indices_b[:, 0], indices_b[:, 1]] += output_b_flat
|
| 851 |
+
|
| 852 |
+
return (self.o_proj(attn_output), return_scores_by_batch), None
|
src/msa/model.py
ADDED
|
@@ -0,0 +1,733 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
import os
|
| 3 |
+
sys.path.append(os.getcwd())
|
| 4 |
+
import torch
|
| 5 |
+
import torch.nn as nn
|
| 6 |
+
import torch.nn.functional as F
|
| 7 |
+
from typing import Union, Optional, Tuple, List, Dict
|
| 8 |
+
from functools import partial
|
| 9 |
+
from dataclasses import dataclass
|
| 10 |
+
from transformers.modeling_outputs import ModelOutput
|
| 11 |
+
from transformers.models.qwen3.modeling_qwen3 import (
|
| 12 |
+
Qwen3Model,
|
| 13 |
+
FlashAttentionKwargs,
|
| 14 |
+
Qwen3Config,
|
| 15 |
+
Qwen3RMSNorm,
|
| 16 |
+
Qwen3RotaryEmbedding,
|
| 17 |
+
DynamicCache,
|
| 18 |
+
Qwen3DecoderLayer,
|
| 19 |
+
Qwen3MLP,
|
| 20 |
+
Qwen3Attention,
|
| 21 |
+
Qwen3PreTrainedModel,
|
| 22 |
+
)
|
| 23 |
+
from transformers.modeling_outputs import CausalLMOutputWithPast, BaseModelOutputWithPast
|
| 24 |
+
from transformers.processing_utils import Unpack
|
| 25 |
+
from transformers.cache_utils import Cache
|
| 26 |
+
from liger_kernel.transformers.model.loss_utils import LigerForCausalLMLoss
|
| 27 |
+
from src.msa import MemorySparseAttention, MSAGenerationMixin, MSAConfig
|
| 28 |
+
|
| 29 |
+
@dataclass
|
| 30 |
+
class MSALayerModelOutputWithPast(ModelOutput):
|
| 31 |
+
last_hidden_state: Optional[torch.FloatTensor] = None
|
| 32 |
+
past_key_values: Optional[Tuple[Tuple[torch.FloatTensor]]] = None
|
| 33 |
+
hidden_states: Optional[Tuple[torch.FloatTensor, ...]] = None
|
| 34 |
+
attentions: Optional[Tuple[torch.FloatTensor, ...]] = None
|
| 35 |
+
all_docs_scores: Optional[Dict] = None
|
| 36 |
+
|
| 37 |
+
@dataclass
|
| 38 |
+
class MSACausalLMOutputWithPast(ModelOutput):
|
| 39 |
+
loss: Optional[torch.FloatTensor] = None
|
| 40 |
+
lm_loss: Optional[torch.FloatTensor] = None
|
| 41 |
+
aux_loss: Optional[torch.FloatTensor] = None
|
| 42 |
+
answer_loss: Optional[torch.FloatTensor] = None
|
| 43 |
+
reconstruction_loss: Optional[torch.FloatTensor] = None
|
| 44 |
+
logits: Optional[torch.FloatTensor] = None
|
| 45 |
+
past_key_values: Optional[Tuple[Tuple[torch.FloatTensor]]] = None
|
| 46 |
+
hidden_states: Optional[Tuple[torch.FloatTensor, ...]] = None
|
| 47 |
+
attentions: Optional[Tuple[torch.FloatTensor, ...]] = None
|
| 48 |
+
temperature: Optional[torch.FloatTensor] = None
|
| 49 |
+
train_router_metrics: Optional[Dict] = None
|
| 50 |
+
|
| 51 |
+
class MSADeocoderLayer(Qwen3DecoderLayer):
|
| 52 |
+
def __init__(self, config: Qwen3Config, layer_idx: int, attn_type: str = "sparse_attention"):
|
| 53 |
+
super().__init__(config=config, layer_idx=layer_idx)
|
| 54 |
+
self.layer_idx = layer_idx
|
| 55 |
+
self.attn_type = attn_type
|
| 56 |
+
self.hidden_size = config.hidden_size
|
| 57 |
+
if attn_type == "full_attention":
|
| 58 |
+
self.self_attn = Qwen3Attention(config=config, layer_idx=layer_idx)
|
| 59 |
+
elif attn_type == "sparse_attention":
|
| 60 |
+
self.self_attn = MemorySparseAttention(config=config, layer_idx=layer_idx)
|
| 61 |
+
|
| 62 |
+
self.mlp = Qwen3MLP(config)
|
| 63 |
+
self.input_layernorm = Qwen3RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
|
| 64 |
+
self.post_attention_layernorm = Qwen3RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
|
| 65 |
+
config.sliding_window = False
|
| 66 |
+
|
| 67 |
+
def forward(
|
| 68 |
+
self,
|
| 69 |
+
hidden_states: torch.Tensor,
|
| 70 |
+
attention_mask: Optional[torch.Tensor] = None,
|
| 71 |
+
position_ids: Optional[torch.LongTensor] = None,
|
| 72 |
+
past_key_value: Optional[Cache] = None,
|
| 73 |
+
output_attentions: Optional[bool] = False,
|
| 74 |
+
output_docs_score: Optional[bool] = False,
|
| 75 |
+
use_cache: Optional[bool] = False,
|
| 76 |
+
cache_position: Optional[torch.LongTensor] = None,
|
| 77 |
+
position_embeddings: Optional[Tuple[torch.Tensor, torch.Tensor]] = None, # necessary, but kept here for BC
|
| 78 |
+
doc_ids: Optional[torch.Tensor] = None,
|
| 79 |
+
input_ids: Optional[torch.LongTensor] = None,
|
| 80 |
+
**kwargs: Unpack[FlashAttentionKwargs],
|
| 81 |
+
) -> Tuple[torch.FloatTensor, Optional[Tuple[torch.FloatTensor, torch.FloatTensor]]]:
|
| 82 |
+
residual = hidden_states
|
| 83 |
+
|
| 84 |
+
hidden_states = self.input_layernorm(hidden_states)
|
| 85 |
+
|
| 86 |
+
# Self Attention
|
| 87 |
+
if self.attn_type == "full_attention":
|
| 88 |
+
hidden_states, self_attn_weights = self.self_attn(
|
| 89 |
+
hidden_states=hidden_states,
|
| 90 |
+
attention_mask=attention_mask,
|
| 91 |
+
position_ids=position_ids,
|
| 92 |
+
past_key_value=past_key_value,
|
| 93 |
+
output_attentions=output_attentions,
|
| 94 |
+
use_cache=use_cache,
|
| 95 |
+
cache_position=cache_position,
|
| 96 |
+
position_embeddings=position_embeddings,
|
| 97 |
+
doc_ids=doc_ids,
|
| 98 |
+
input_ids=input_ids,
|
| 99 |
+
**kwargs,
|
| 100 |
+
)
|
| 101 |
+
else:
|
| 102 |
+
hidden_states, self_attn_weights = self.self_attn(
|
| 103 |
+
hidden_states=hidden_states,
|
| 104 |
+
attention_mask=attention_mask,
|
| 105 |
+
position_ids=position_ids,
|
| 106 |
+
past_key_value=past_key_value,
|
| 107 |
+
output_attentions=output_attentions,
|
| 108 |
+
use_cache=use_cache,
|
| 109 |
+
cache_position=cache_position,
|
| 110 |
+
position_embeddings=position_embeddings,
|
| 111 |
+
doc_ids=doc_ids,
|
| 112 |
+
input_ids=input_ids,
|
| 113 |
+
**kwargs,
|
| 114 |
+
)
|
| 115 |
+
|
| 116 |
+
if isinstance(hidden_states, tuple):
|
| 117 |
+
hidden_states, docs_score = hidden_states
|
| 118 |
+
else:
|
| 119 |
+
docs_score = None
|
| 120 |
+
hidden_states = residual + hidden_states
|
| 121 |
+
|
| 122 |
+
# Fully Connected
|
| 123 |
+
residual = hidden_states
|
| 124 |
+
hidden_states = self.post_attention_layernorm(hidden_states)
|
| 125 |
+
hidden_states = self.mlp(hidden_states)
|
| 126 |
+
hidden_states = residual + hidden_states
|
| 127 |
+
|
| 128 |
+
outputs = (hidden_states,)
|
| 129 |
+
if output_attentions:
|
| 130 |
+
outputs += (self_attn_weights,)
|
| 131 |
+
|
| 132 |
+
if output_docs_score:
|
| 133 |
+
outputs += (docs_score,)
|
| 134 |
+
|
| 135 |
+
return outputs
|
| 136 |
+
|
| 137 |
+
class MSAModel(Qwen3Model):
|
| 138 |
+
def __init__(self, config: Qwen3Config):
|
| 139 |
+
super().__init__(config)
|
| 140 |
+
self.padding_idx = config.pad_token_id
|
| 141 |
+
self.vocab_size = config.vocab_size
|
| 142 |
+
|
| 143 |
+
self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size, self.padding_idx)
|
| 144 |
+
self.rewrite_position = config.msa_config.rewrite_position
|
| 145 |
+
self.layers = nn.ModuleList([
|
| 146 |
+
MSADeocoderLayer(config, layer_idx, attn_type="sparse_attention")
|
| 147 |
+
for layer_idx in range(config.num_hidden_layers)
|
| 148 |
+
])
|
| 149 |
+
self.norm = Qwen3RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
|
| 150 |
+
self.rotary_emb = Qwen3RotaryEmbedding(config=config)
|
| 151 |
+
self.gradient_checkpointing = False
|
| 152 |
+
|
| 153 |
+
# Initialize weights and apply final processing
|
| 154 |
+
self.post_init()
|
| 155 |
+
|
| 156 |
+
def forward(
|
| 157 |
+
self,
|
| 158 |
+
input_ids: Optional[torch.LongTensor] = None,
|
| 159 |
+
attention_mask: Optional[torch.Tensor] = None,
|
| 160 |
+
position_ids: Optional[torch.LongTensor] = None,
|
| 161 |
+
past_key_values: Optional[Cache] = None,
|
| 162 |
+
inputs_embeds: Optional[torch.FloatTensor] = None,
|
| 163 |
+
use_cache: Optional[bool] = None,
|
| 164 |
+
output_attentions: Optional[bool] = None,
|
| 165 |
+
output_hidden_states: Optional[bool] = None,
|
| 166 |
+
output_docs_score: Optional[bool] = None,
|
| 167 |
+
cache_position: Optional[torch.LongTensor] = None,
|
| 168 |
+
doc_ids: Optional[torch.LongTensor] = None,
|
| 169 |
+
**flash_attn_kwargs: Unpack[FlashAttentionKwargs],
|
| 170 |
+
) -> BaseModelOutputWithPast:
|
| 171 |
+
output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions
|
| 172 |
+
output_hidden_states = (
|
| 173 |
+
output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states
|
| 174 |
+
)
|
| 175 |
+
|
| 176 |
+
use_cache = use_cache if use_cache is not None else self.config.use_cache
|
| 177 |
+
|
| 178 |
+
if (input_ids is None) ^ (inputs_embeds is not None):
|
| 179 |
+
raise ValueError("You must specify exactly one of input_ids or inputs_embeds")
|
| 180 |
+
|
| 181 |
+
if self.gradient_checkpointing and self.training and use_cache:
|
| 182 |
+
use_cache = False
|
| 183 |
+
|
| 184 |
+
# TODO (joao): remove this exception in v4.56 -- it exists for users that try to pass a legacy cache
|
| 185 |
+
if not isinstance(past_key_values, (type(None), Cache)):
|
| 186 |
+
raise ValueError("The `past_key_values` should be either a `Cache` object or `None`.")
|
| 187 |
+
|
| 188 |
+
if inputs_embeds is None:
|
| 189 |
+
inputs_embeds = self.embed_tokens(input_ids)
|
| 190 |
+
|
| 191 |
+
if use_cache and past_key_values is None:
|
| 192 |
+
past_key_values = DynamicCache()
|
| 193 |
+
|
| 194 |
+
if cache_position is None:
|
| 195 |
+
past_seen_tokens = past_key_values.get_seq_length() if past_key_values is not None else 0
|
| 196 |
+
cache_position = torch.arange(
|
| 197 |
+
past_seen_tokens, past_seen_tokens + inputs_embeds.shape[1], device=inputs_embeds.device
|
| 198 |
+
)
|
| 199 |
+
|
| 200 |
+
if not self.rewrite_position and self.training:
|
| 201 |
+
position_ids = None
|
| 202 |
+
if position_ids is None:
|
| 203 |
+
position_ids = cache_position.unsqueeze(0)
|
| 204 |
+
|
| 205 |
+
# causal_mask = self._update_causal_mask(
|
| 206 |
+
# attention_mask, inputs_embeds, cache_position, past_key_values, output_attentions
|
| 207 |
+
# )
|
| 208 |
+
causal_mask = attention_mask
|
| 209 |
+
|
| 210 |
+
hidden_states = inputs_embeds
|
| 211 |
+
# import pdb;pdb.set_trace()
|
| 212 |
+
# create position embeddings to be shared across the decoder layers
|
| 213 |
+
position_embeddings = self.rotary_emb(hidden_states, position_ids)
|
| 214 |
+
|
| 215 |
+
# decoder layers
|
| 216 |
+
all_hidden_states = () if output_hidden_states else None
|
| 217 |
+
all_self_attns = () if output_attentions else None
|
| 218 |
+
all_docs_scores = () if output_docs_score else None
|
| 219 |
+
|
| 220 |
+
for decoder_layer in self.layers[: self.config.num_hidden_layers]:
|
| 221 |
+
if output_hidden_states:
|
| 222 |
+
all_hidden_states += (hidden_states,)
|
| 223 |
+
|
| 224 |
+
if self.gradient_checkpointing and self.training:
|
| 225 |
+
layer_outputs = self._gradient_checkpointing_func(
|
| 226 |
+
partial(decoder_layer.__call__, **flash_attn_kwargs),
|
| 227 |
+
hidden_states,
|
| 228 |
+
causal_mask,
|
| 229 |
+
position_ids,
|
| 230 |
+
past_key_values,
|
| 231 |
+
output_attentions,
|
| 232 |
+
output_docs_score,
|
| 233 |
+
use_cache,
|
| 234 |
+
cache_position,
|
| 235 |
+
position_embeddings,
|
| 236 |
+
doc_ids,
|
| 237 |
+
input_ids,
|
| 238 |
+
)
|
| 239 |
+
else:
|
| 240 |
+
layer_outputs = decoder_layer(
|
| 241 |
+
hidden_states,
|
| 242 |
+
attention_mask=causal_mask,
|
| 243 |
+
position_ids=position_ids,
|
| 244 |
+
past_key_value=past_key_values,
|
| 245 |
+
output_attentions=output_attentions,
|
| 246 |
+
output_docs_score=output_docs_score,
|
| 247 |
+
use_cache=use_cache,
|
| 248 |
+
cache_position=cache_position,
|
| 249 |
+
position_embeddings=position_embeddings,
|
| 250 |
+
doc_ids=doc_ids,
|
| 251 |
+
input_ids=input_ids,
|
| 252 |
+
**flash_attn_kwargs,
|
| 253 |
+
)
|
| 254 |
+
|
| 255 |
+
hidden_states = layer_outputs[0]
|
| 256 |
+
|
| 257 |
+
if output_attentions:
|
| 258 |
+
all_self_attns += (layer_outputs[1],)
|
| 259 |
+
|
| 260 |
+
if output_docs_score:
|
| 261 |
+
all_docs_scores += (layer_outputs[-1],)
|
| 262 |
+
|
| 263 |
+
hidden_states = self.norm(hidden_states)
|
| 264 |
+
|
| 265 |
+
# add hidden states from the last decoder layer
|
| 266 |
+
if output_hidden_states:
|
| 267 |
+
all_hidden_states += (hidden_states,)
|
| 268 |
+
|
| 269 |
+
return MSALayerModelOutputWithPast(
|
| 270 |
+
last_hidden_state=hidden_states,
|
| 271 |
+
past_key_values=past_key_values if use_cache else None,
|
| 272 |
+
hidden_states=all_hidden_states,
|
| 273 |
+
attentions=all_self_attns,
|
| 274 |
+
all_docs_scores=all_docs_scores
|
| 275 |
+
)
|
| 276 |
+
|
| 277 |
+
class MSAForCausalLM(Qwen3PreTrainedModel, MSAGenerationMixin):
|
| 278 |
+
config_class = MSAConfig
|
| 279 |
+
_tied_weights_keys = ["lm_head.weight"]
|
| 280 |
+
_tp_plan = {"lm_head": "colwise_rep"}
|
| 281 |
+
_pp_plan = {"lm_head": (["hidden_states"], ["logits"])}
|
| 282 |
+
|
| 283 |
+
def __init__(self, config):
|
| 284 |
+
super().__init__(config)
|
| 285 |
+
self.num_layers = config.num_hidden_layers
|
| 286 |
+
self.router_layer_idx = config.msa_config.router_layer_idx
|
| 287 |
+
|
| 288 |
+
if self.router_layer_idx == "all":
|
| 289 |
+
self.router_layer_idx = list(range(config.num_hidden_layers))
|
| 290 |
+
else:
|
| 291 |
+
self.router_layer_idx = [int(i) for i in self.router_layer_idx.split(",")]
|
| 292 |
+
|
| 293 |
+
self.mid_layers = config.num_hidden_layers // 2
|
| 294 |
+
self.model = MSAModel(config)
|
| 295 |
+
self.vocab_size = config.vocab_size
|
| 296 |
+
self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
|
| 297 |
+
|
| 298 |
+
self.aux_loss = config.msa_config.aux_loss
|
| 299 |
+
self.lmloss_weigth = config.msa_config.lmloss_weigth
|
| 300 |
+
self.auxloss_weight = config.msa_config.auxloss_weight
|
| 301 |
+
self.recloss_weight = config.msa_config.recloss_weight
|
| 302 |
+
self.ansloss_weight = config.msa_config.ansloss_weight
|
| 303 |
+
self.aux_loss_method = config.msa_config.aux_loss_method # INFONCE, BCE, INFONCE_DECOUPLE, INFONCE_DECOUPLE_FOCAL
|
| 304 |
+
self.decouple_router = config.msa_config.decouple_router
|
| 305 |
+
|
| 306 |
+
if "INFONCE" in self.aux_loss_method:
|
| 307 |
+
temperature = config.msa_config.infonce_loss_temp
|
| 308 |
+
self.temperature = nn.Parameter(torch.ones([]) * temperature, requires_grad=False)
|
| 309 |
+
elif self.aux_loss_method == "BCE":
|
| 310 |
+
self.b = nn.Parameter(-20 * torch.ones([]), requires_grad=True)
|
| 311 |
+
# Initialize weights and apply final processing
|
| 312 |
+
self.post_init()
|
| 313 |
+
|
| 314 |
+
def get_input_embeddings(self):
|
| 315 |
+
return self.model.embed_tokens
|
| 316 |
+
|
| 317 |
+
def set_input_embeddings(self, value):
|
| 318 |
+
self.model.embed_tokens = value
|
| 319 |
+
|
| 320 |
+
def get_output_embeddings(self):
|
| 321 |
+
return self.lm_head
|
| 322 |
+
|
| 323 |
+
def set_output_embeddings(self, new_embeddings):
|
| 324 |
+
self.lm_head = new_embeddings
|
| 325 |
+
|
| 326 |
+
def set_decoder(self, decoder):
|
| 327 |
+
self.model = decoder
|
| 328 |
+
|
| 329 |
+
def get_decoder(self):
|
| 330 |
+
return self.model
|
| 331 |
+
|
| 332 |
+
def forward(
|
| 333 |
+
self,
|
| 334 |
+
input_ids: Optional[torch.LongTensor] = None,
|
| 335 |
+
attention_mask: Optional[torch.Tensor] = None,
|
| 336 |
+
position_ids: Optional[torch.LongTensor] = None,
|
| 337 |
+
past_key_values: Optional[List[torch.FloatTensor]] = None,
|
| 338 |
+
inputs_embeds: Optional[torch.FloatTensor] = None,
|
| 339 |
+
labels: Optional[torch.LongTensor] = None,
|
| 340 |
+
use_cache: Optional[bool] = None,
|
| 341 |
+
output_attentions: Optional[bool] = None,
|
| 342 |
+
output_hidden_states: Optional[bool] = None,
|
| 343 |
+
output_docs_score: Optional[bool] = None,
|
| 344 |
+
cache_position: Optional[torch.LongTensor] = None,
|
| 345 |
+
logits_to_keep: Union[int, torch.Tensor] = 0,
|
| 346 |
+
# msa
|
| 347 |
+
doc_ids: Optional[torch.LongTensor] = None,
|
| 348 |
+
batch_aux_labels: List[List[int]] = None,
|
| 349 |
+
batch_reconstruction_labels: Optional[torch.LongTensor] = None,
|
| 350 |
+
batch_answer_labels: Optional[torch.LongTensor] = None,
|
| 351 |
+
train_qa_samples: Optional[torch.BoolTensor] = None,
|
| 352 |
+
**kwargs,
|
| 353 |
+
) -> CausalLMOutputWithPast:
|
| 354 |
+
r"""
|
| 355 |
+
labels (`torch.LongTensor` of shape `(batch_size, sequence_length)`, *optional*):
|
| 356 |
+
Labels for computing the masked language modeling loss. Indices should either be in `[0, ...,
|
| 357 |
+
config.vocab_size]` or -100 (see `input_ids` docstring). Tokens with indices set to `-100` are ignored
|
| 358 |
+
(masked), the loss is only computed for the tokens with labels in `[0, ..., config.vocab_size]`.
|
| 359 |
+
|
| 360 |
+
logits_to_keep (`int` or `torch.Tensor`, *optional*):
|
| 361 |
+
If an `int`, compute logits for the last `logits_to_keep` tokens. If `0`, calculate logits for all
|
| 362 |
+
`input_ids` (special case). Only last token logits are needed for generation, and calculating them only for that
|
| 363 |
+
token can save memory, which becomes pretty significant for long sequences or large vocabulary size.
|
| 364 |
+
If a `torch.Tensor`, must be 1D corresponding to the indices to keep in the sequence length dimension.
|
| 365 |
+
This is useful when using packed tensor format (single dimension for batch and sequence length).
|
| 366 |
+
|
| 367 |
+
Returns:
|
| 368 |
+
|
| 369 |
+
Example:
|
| 370 |
+
|
| 371 |
+
```python
|
| 372 |
+
>>> from transformers import AutoTokenizer, Qwen3ForCausalLM
|
| 373 |
+
|
| 374 |
+
>>> model = Qwen3ForCausalLM.from_pretrained("Qwen/Qwen3-8B")
|
| 375 |
+
>>> tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen3-8B")
|
| 376 |
+
|
| 377 |
+
>>> prompt = "Hey, are you conscious? Can you talk to me?"
|
| 378 |
+
>>> inputs = tokenizer(prompt, return_tensors="pt")
|
| 379 |
+
|
| 380 |
+
>>> # Generate
|
| 381 |
+
>>> generate_ids = model.generate(inputs.input_ids, max_length=30)
|
| 382 |
+
>>> tokenizer.batch_decode(generate_ids, skip_special_tokens=True, clean_up_tokenization_spaces=False)[0]
|
| 383 |
+
"Hey, are you conscious? Can you talk to me?\nI'm not conscious, but I can talk to you."
|
| 384 |
+
```"""
|
| 385 |
+
output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions
|
| 386 |
+
output_hidden_states = (
|
| 387 |
+
output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states
|
| 388 |
+
)
|
| 389 |
+
|
| 390 |
+
output_docs_score = self.aux_loss
|
| 391 |
+
|
| 392 |
+
# decoder outputs consists of (dec_features, layer_state, dec_hidden, dec_attn)
|
| 393 |
+
outputs = self.model(
|
| 394 |
+
input_ids=input_ids,
|
| 395 |
+
attention_mask=attention_mask,
|
| 396 |
+
position_ids=position_ids,
|
| 397 |
+
past_key_values=past_key_values,
|
| 398 |
+
inputs_embeds=inputs_embeds,
|
| 399 |
+
use_cache=use_cache,
|
| 400 |
+
output_attentions=output_attentions,
|
| 401 |
+
output_hidden_states=output_hidden_states,
|
| 402 |
+
output_docs_score=output_docs_score,
|
| 403 |
+
cache_position=cache_position,
|
| 404 |
+
doc_ids=doc_ids,
|
| 405 |
+
**kwargs,
|
| 406 |
+
)
|
| 407 |
+
|
| 408 |
+
hidden_states = outputs[0]
|
| 409 |
+
|
| 410 |
+
# Only compute necessary logits, and do not upcast them to float if we are not computing the loss
|
| 411 |
+
slice_indices = slice(-logits_to_keep, None) if isinstance(logits_to_keep, int) else logits_to_keep
|
| 412 |
+
kept_hidden_states = hidden_states[:, slice_indices, :]
|
| 413 |
+
|
| 414 |
+
shift_labels = kwargs.pop("shift_labels", None)
|
| 415 |
+
logits = None
|
| 416 |
+
loss = None
|
| 417 |
+
reconstruction_loss = None
|
| 418 |
+
aux_loss = None
|
| 419 |
+
answer_loss = None
|
| 420 |
+
# if in training mode, don't materialize logits
|
| 421 |
+
if self.training and (labels is not None or shift_labels is not None):
|
| 422 |
+
loss = LigerForCausalLMLoss(
|
| 423 |
+
hidden_states=kept_hidden_states,
|
| 424 |
+
lm_head_weight=self.lm_head.weight,
|
| 425 |
+
labels=labels,
|
| 426 |
+
shift_labels=shift_labels,
|
| 427 |
+
hidden_size=self.config.hidden_size,
|
| 428 |
+
**kwargs,
|
| 429 |
+
)
|
| 430 |
+
if batch_reconstruction_labels is not None:
|
| 431 |
+
reconstruction_loss = LigerForCausalLMLoss(
|
| 432 |
+
hidden_states=kept_hidden_states,
|
| 433 |
+
lm_head_weight=self.lm_head.weight,
|
| 434 |
+
labels=batch_reconstruction_labels,
|
| 435 |
+
shift_labels=None,
|
| 436 |
+
hidden_size=self.config.hidden_size,
|
| 437 |
+
**kwargs,
|
| 438 |
+
)
|
| 439 |
+
else:
|
| 440 |
+
reconstruction_loss = torch.tensor(0.0).to(hidden_states.device)
|
| 441 |
+
|
| 442 |
+
if torch.sum(train_qa_samples) == 0:
|
| 443 |
+
batch_answer_labels = None
|
| 444 |
+
else:
|
| 445 |
+
train_qa_samples_mask = train_qa_samples == 1
|
| 446 |
+
temp_kept_hidden_states = kept_hidden_states[train_qa_samples_mask]
|
| 447 |
+
temp_batch_answer_labels = batch_answer_labels[train_qa_samples_mask]
|
| 448 |
+
|
| 449 |
+
if batch_answer_labels is not None:
|
| 450 |
+
answer_loss = LigerForCausalLMLoss(
|
| 451 |
+
hidden_states=temp_kept_hidden_states,
|
| 452 |
+
lm_head_weight=self.lm_head.weight,
|
| 453 |
+
labels=temp_batch_answer_labels,
|
| 454 |
+
shift_labels=None,
|
| 455 |
+
hidden_size=self.config.hidden_size,
|
| 456 |
+
**kwargs,
|
| 457 |
+
)
|
| 458 |
+
else:
|
| 459 |
+
answer_loss = torch.tensor(0.0).to(hidden_states.device)
|
| 460 |
+
|
| 461 |
+
|
| 462 |
+
else: # if in inference mode materialize logits
|
| 463 |
+
logits = self.lm_head(kept_hidden_states)
|
| 464 |
+
if labels is not None:
|
| 465 |
+
loss = self.loss_function(
|
| 466 |
+
logits=logits,
|
| 467 |
+
labels=labels,
|
| 468 |
+
vocab_size=self.config.vocab_size,
|
| 469 |
+
**kwargs,
|
| 470 |
+
)
|
| 471 |
+
if batch_reconstruction_labels is not None:
|
| 472 |
+
reconstruction_loss = self.loss_function(
|
| 473 |
+
logits=logits,
|
| 474 |
+
labels=batch_reconstruction_labels,
|
| 475 |
+
vocab_size=self.config.vocab_size,
|
| 476 |
+
**kwargs,
|
| 477 |
+
)
|
| 478 |
+
else:
|
| 479 |
+
reconstruction_loss = torch.tensor(0.0).to(hidden_states.device)
|
| 480 |
+
|
| 481 |
+
if batch_answer_labels is not None:
|
| 482 |
+
answer_loss = self.loss_function(
|
| 483 |
+
logits=logits,
|
| 484 |
+
labels=batch_answer_labels,
|
| 485 |
+
vocab_size=self.config.vocab_size,
|
| 486 |
+
**kwargs,
|
| 487 |
+
)
|
| 488 |
+
else:
|
| 489 |
+
answer_loss = torch.tensor(0.0).to(hidden_states.device)
|
| 490 |
+
|
| 491 |
+
lm_loss = torch.tensor(0.0).to(hidden_states.device)
|
| 492 |
+
if loss is not None:
|
| 493 |
+
lm_loss = loss.clone()
|
| 494 |
+
aux_loss = torch.tensor(0.0).to(hidden_states.device)
|
| 495 |
+
train_router_metrics = None
|
| 496 |
+
if batch_aux_labels is not None and self.aux_loss:
|
| 497 |
+
aux_loss, train_router_metrics = self.caculate_aux_loss(aux_loss, outputs, batch_aux_labels, hidden_states.device, hidden_states.dtype)
|
| 498 |
+
|
| 499 |
+
# Ensure all loss components have default values
|
| 500 |
+
reconstruction_loss = reconstruction_loss if reconstruction_loss is not None else torch.tensor(0.0).to(hidden_states.device)
|
| 501 |
+
answer_loss = answer_loss if answer_loss is not None else torch.tensor(0.0).to(hidden_states.device)
|
| 502 |
+
aux_loss = aux_loss if aux_loss is not None else torch.tensor(0.0).to(hidden_states.device)
|
| 503 |
+
|
| 504 |
+
if loss is not None:
|
| 505 |
+
loss = self.lmloss_weigth * loss + \
|
| 506 |
+
self.recloss_weight * reconstruction_loss + \
|
| 507 |
+
self.auxloss_weight * aux_loss + \
|
| 508 |
+
self.ansloss_weight * answer_loss
|
| 509 |
+
|
| 510 |
+
return MSACausalLMOutputWithPast(
|
| 511 |
+
loss=loss,
|
| 512 |
+
lm_loss=lm_loss,
|
| 513 |
+
aux_loss=aux_loss,
|
| 514 |
+
answer_loss=answer_loss,
|
| 515 |
+
reconstruction_loss=reconstruction_loss,
|
| 516 |
+
train_router_metrics=train_router_metrics,
|
| 517 |
+
logits=logits,
|
| 518 |
+
past_key_values=outputs.past_key_values,
|
| 519 |
+
hidden_states=outputs.hidden_states,
|
| 520 |
+
attentions=outputs.attentions,
|
| 521 |
+
temperature=self.temperature if "INFONCE" in self.aux_loss_method else torch.tensor(0.0).to(hidden_states.device),
|
| 522 |
+
)
|
| 523 |
+
|
| 524 |
+
def calculate_decoupled_infonce_loss(self, logits, label, num_pos):
|
| 525 |
+
"""
|
| 526 |
+
改进版:Decoupled InfoNCE
|
| 527 |
+
解决了多正样本之间的互斥问题,检索任务推荐使用。
|
| 528 |
+
"""
|
| 529 |
+
with torch.no_grad():
|
| 530 |
+
temperature = self.temperature.clamp(0.001, 0.5)
|
| 531 |
+
|
| 532 |
+
# 1. 缩放
|
| 533 |
+
scaled_logits = logits / temperature
|
| 534 |
+
|
| 535 |
+
# 2. 数值稳定:转为 exp 域
|
| 536 |
+
# 减去最大值防止溢出 (标准 Softmax trick)
|
| 537 |
+
max_logits = torch.max(scaled_logits, dim=0, keepdim=True)[0].detach()
|
| 538 |
+
exp_logits = torch.exp(scaled_logits - max_logits)
|
| 539 |
+
|
| 540 |
+
# 获取所有负样本的 exp 之和
|
| 541 |
+
neg_exp_sum = torch.sum(exp_logits * (1 - label), dim=0, keepdim=True)
|
| 542 |
+
|
| 543 |
+
# 4. 计算 Log Prob
|
| 544 |
+
# 对于每一个正样本 i:
|
| 545 |
+
# Prob_i = exp_i / (exp_i + neg_exp_sum)
|
| 546 |
+
# 这样分母里就没有“其他正样本”在竞争了
|
| 547 |
+
denominators = exp_logits + neg_exp_sum
|
| 548 |
+
|
| 549 |
+
log_probs = scaled_logits - max_logits - torch.log(denominators + 1e-10)
|
| 550 |
+
|
| 551 |
+
# 5. 计算 Loss
|
| 552 |
+
# 只取正样本位置的 log_prob
|
| 553 |
+
loss_map = - log_probs * (label / num_pos.clamp(min=1.0))
|
| 554 |
+
|
| 555 |
+
return loss_map.sum(dim=0)
|
| 556 |
+
|
| 557 |
+
def caculate_infonce_loss(self, logits, label, num_pos):
|
| 558 |
+
# 限制温度范围避免数值不稳定
|
| 559 |
+
with torch.no_grad():
|
| 560 |
+
temperature = self.temperature.clamp(0.001,0.5)
|
| 561 |
+
# 对logits进行温度缩放
|
| 562 |
+
scaled_logits = logits / temperature
|
| 563 |
+
# 确保正样本数至少为1,避免除零错误
|
| 564 |
+
safe_num_pos = num_pos.clamp(min=1.0)
|
| 565 |
+
# 将标签转换为与缩放后logits相同的浮点类型
|
| 566 |
+
aux_label_float = label.to(dtype=scaled_logits.dtype)
|
| 567 |
+
# 计算InfoNCE损失:负的对数softmax概率与标签的加权和
|
| 568 |
+
one_aux_loss = -torch.sum(F.log_softmax(scaled_logits, dim=0) * (aux_label_float / safe_num_pos), dim=0)
|
| 569 |
+
return one_aux_loss
|
| 570 |
+
|
| 571 |
+
def caculate_bce_loss(self, logits, label):
|
| 572 |
+
label[label==0] = -1 # 将 0 (负样本) 映射为 -1
|
| 573 |
+
one_aux_loss = -torch.mean(F.logsigmoid((logits + self.b) * label))
|
| 574 |
+
return one_aux_loss
|
| 575 |
+
|
| 576 |
+
def calculate_multi_pos_focal_infonce(self, logits, label, gamma=2.0):
|
| 577 |
+
"""
|
| 578 |
+
Args:
|
| 579 |
+
logits: (Batch_Size, Num_Candidates) 或者是 (N, 1) 的形式
|
| 580 |
+
label: (Batch_Size, Num_Candidates) Multi-hot 标签,1为正,0为负
|
| 581 |
+
gamma: Focal 参数
|
| 582 |
+
"""
|
| 583 |
+
with torch.no_grad():
|
| 584 |
+
temperature = self.temperature.clamp(0.001, 0.5)
|
| 585 |
+
|
| 586 |
+
# 1. 缩放 logits
|
| 587 |
+
scaled_logits = logits / temperature
|
| 588 |
+
|
| 589 |
+
# 2. 核心 Trick:数值稳定地将 logits 转为 exp 域
|
| 590 |
+
# 为了防止 exp 溢出,先减去最大值
|
| 591 |
+
max_logits = torch.max(scaled_logits, dim=0, keepdim=True)[0].detach()
|
| 592 |
+
exp_logits = torch.exp(scaled_logits - max_logits)
|
| 593 |
+
|
| 594 |
+
# 3. 构建分母 (Denominator)
|
| 595 |
+
# 传统的 InfoNCE 分母是 sum(exp_logits)
|
| 596 |
+
# 我们现在的分母应该是:当前正样本的 exp + 所有负样本的 exp
|
| 597 |
+
# 等价于:总和 - 其他正样本的 exp
|
| 598 |
+
|
| 599 |
+
sum_exp = torch.sum(exp_logits, dim=0, keepdim=True) # 所有样本的 exp 之和
|
| 600 |
+
|
| 601 |
+
# 利用 label (multi-hot) 找出所有正样本的 exp
|
| 602 |
+
# label 为 1 的位置是正样本
|
| 603 |
+
pos_exp = exp_logits * label
|
| 604 |
+
neg_exp_sum = torch.sum(exp_logits * (1 - label), dim=0, keepdim=True) # 所有负样本的 exp 之和
|
| 605 |
+
|
| 606 |
+
# 4. 计算每个正样本对应的 Softmax 概率
|
| 607 |
+
# 对于每一个位置 i (如果是正样本):
|
| 608 |
+
# Prob_i = exp_i / (exp_i + sum(exp_negatives))
|
| 609 |
+
# 注意:这里分母不包含 label 中其他的正样本!
|
| 610 |
+
|
| 611 |
+
# 这里利用广播机制:
|
| 612 |
+
# 分母 = 当前位置的 exp (如果是正样本) + 所有负样本的 sum_exp
|
| 613 |
+
denominators = exp_logits + neg_exp_sum
|
| 614 |
+
|
| 615 |
+
# 计算概率 P
|
| 616 |
+
probs = exp_logits / denominators
|
| 617 |
+
|
| 618 |
+
# 5. 计算 Log Softmax (数值更稳定)
|
| 619 |
+
# log(p) = logits - log(denominators)
|
| 620 |
+
# 同样使用数值稳定的 logaddexp 或者直接操作
|
| 621 |
+
# 这里为了代码清晰,直接对上面算出的 probs 取 log,实际工程中建议用 log_softmax 形式优化
|
| 622 |
+
log_probs = torch.log(probs + 1e-10)
|
| 623 |
+
|
| 624 |
+
# 6. Focal Weight
|
| 625 |
+
# w = (1 - p)^gamma
|
| 626 |
+
focal_weights = (1 - probs).pow(gamma)
|
| 627 |
+
|
| 628 |
+
# 7. 计算 Loss
|
| 629 |
+
# 只保留正样本位置的 Loss
|
| 630 |
+
loss_map = - focal_weights * log_probs * label
|
| 631 |
+
|
| 632 |
+
# 8. 归一化
|
| 633 |
+
# 除以正样本的总数
|
| 634 |
+
num_pos = label.sum()
|
| 635 |
+
loss = loss_map.sum() / (num_pos + 1e-6)
|
| 636 |
+
return loss
|
| 637 |
+
|
| 638 |
+
def calculate_focal_infonce_loss(self, logits, label, num_pos, gamma=2.0):
|
| 639 |
+
"""
|
| 640 |
+
Args:
|
| 641 |
+
logits: 模型输出的 logits
|
| 642 |
+
label: 正样本的 mask (通常是 multi-hot)
|
| 643 |
+
num_pos: 正样本的数量
|
| 644 |
+
gamma: Focal Loss 的超参数,控制挖掘难样本的程度,通常设为 2.0
|
| 645 |
+
"""
|
| 646 |
+
with torch.no_grad():
|
| 647 |
+
# 限制温度范围,防止数值不稳定
|
| 648 |
+
temperature = self.temperature.clamp(0.001, 0.5)
|
| 649 |
+
|
| 650 |
+
# 1. 缩放 Logits
|
| 651 |
+
scaled_logits = logits / temperature
|
| 652 |
+
|
| 653 |
+
# 2. 提前计算 Softmax 分数 (即公式中的 P = exp(s)/Z)
|
| 654 |
+
# dim=0 对应你的输入是单个向量的情况
|
| 655 |
+
probs = F.softmax(scaled_logits, dim=0)
|
| 656 |
+
|
| 657 |
+
# 3. 计算 Log Softmax (用于 Loss 计算,比 log(probs) 数值更稳定)
|
| 658 |
+
log_probs = F.log_softmax(scaled_logits, dim=0)
|
| 659 |
+
|
| 660 |
+
# 4. 计算 Focal Weight: w = (1 - p)^gamma
|
| 661 |
+
# 公式对应:w_i = (1 - p_i)^gamma
|
| 662 |
+
# 这里不对权重进行 detach,允许梯度回传以调整对难易样本的关注度 (参考原始 Focal Loss 论文)
|
| 663 |
+
focal_weights = (1 - probs).pow(gamma)
|
| 664 |
+
|
| 665 |
+
# 5. 准备 Label 和归一化项
|
| 666 |
+
safe_num_pos = num_pos.clamp(min=1.0)
|
| 667 |
+
aux_label_float = label.to(dtype=scaled_logits.dtype)
|
| 668 |
+
|
| 669 |
+
# 6. 计算加权的 InfoNCE Loss
|
| 670 |
+
# 公式对应:Sum [ -w_i * log(p_i) ]
|
| 671 |
+
# 这里利用 label (0/1) 来只保留正样本的 Loss,并除以 num_pos 做平均
|
| 672 |
+
weighted_loss = -torch.sum(
|
| 673 |
+
focal_weights * log_probs * (aux_label_float / safe_num_pos),
|
| 674 |
+
dim=0
|
| 675 |
+
)
|
| 676 |
+
|
| 677 |
+
return weighted_loss
|
| 678 |
+
|
| 679 |
+
def caculate_aux_loss(self, aux_loss, outputs, batch_aux_labels, device, dtype):
|
| 680 |
+
count = 0
|
| 681 |
+
all_layer_doc_score = outputs.all_docs_scores
|
| 682 |
+
train_router_metrics = {}
|
| 683 |
+
for layer_idx, layer_doc_score in enumerate(all_layer_doc_score):
|
| 684 |
+
# if self.mid_layers <= layer_idx < self.num_layers:
|
| 685 |
+
if self.mid_layers <= layer_idx and layer_idx in self.router_layer_idx:
|
| 686 |
+
for b in range(len(batch_aux_labels)):
|
| 687 |
+
aux_logits_full = layer_doc_score[b]
|
| 688 |
+
aux_label = torch.LongTensor(batch_aux_labels[b]).type(dtype).to(device)
|
| 689 |
+
|
| 690 |
+
# 1. 创建一个 mask 来找到所有有效的 (非 -inf) doc
|
| 691 |
+
valid_doc_mask = (aux_logits_full > -1e9) # 或 != -float('inf')
|
| 692 |
+
|
| 693 |
+
# 2. 使用 *同一个 mask* 来过滤 logits 和 labels
|
| 694 |
+
aux_logits = aux_logits_full[valid_doc_mask]
|
| 695 |
+
|
| 696 |
+
if aux_label.shape[0] == 0:
|
| 697 |
+
continue
|
| 698 |
+
|
| 699 |
+
num_pos = aux_label.sum()
|
| 700 |
+
if self.aux_loss_method == "BCE":
|
| 701 |
+
one_aux_loss = self.caculate_bce_loss(aux_logits, aux_label)
|
| 702 |
+
elif self.aux_loss_method == "INFONCE":
|
| 703 |
+
one_aux_loss = self.caculate_infonce_loss(aux_logits, aux_label, num_pos)
|
| 704 |
+
elif self.aux_loss_method == "INFONCE_FOCAL":
|
| 705 |
+
one_aux_loss = self.calculate_focal_infonce_loss(aux_logits, aux_label, num_pos)
|
| 706 |
+
elif self.aux_loss_method == "INFONCE_DECOUPLE":
|
| 707 |
+
one_aux_loss = self.calculate_decoupled_infonce_loss(aux_logits, aux_label, num_pos)
|
| 708 |
+
elif self.aux_loss_method == "INFONCE_DECOUPLE_FOCAL":
|
| 709 |
+
one_aux_loss = self.calculate_multi_pos_focal_infonce(aux_logits, aux_label, num_pos)
|
| 710 |
+
|
| 711 |
+
aux_loss += one_aux_loss
|
| 712 |
+
count += 1
|
| 713 |
+
|
| 714 |
+
# 计算recall
|
| 715 |
+
recall_at_n = [1, 5, 10]
|
| 716 |
+
for at_n in recall_at_n:
|
| 717 |
+
at_n = min(at_n, aux_logits.shape[0])
|
| 718 |
+
top_k_indices = torch.topk(aux_logits, at_n)[1].cpu().tolist()
|
| 719 |
+
hit_count = 0
|
| 720 |
+
for idx in top_k_indices:
|
| 721 |
+
if aux_label[idx] == 1:
|
| 722 |
+
hit_count += 1
|
| 723 |
+
recall_n = hit_count / min(num_pos.item(), 10)
|
| 724 |
+
if f'recall@{at_n}' not in train_router_metrics:
|
| 725 |
+
train_router_metrics[f'recall@{at_n}'] = [recall_n]
|
| 726 |
+
else:
|
| 727 |
+
train_router_metrics[f'recall@{at_n}'].append(recall_n)
|
| 728 |
+
|
| 729 |
+
if count != 0:
|
| 730 |
+
aux_loss = aux_loss / count
|
| 731 |
+
for k, v in train_router_metrics.items():
|
| 732 |
+
train_router_metrics[k] = sum(v) / len(v)
|
| 733 |
+
return aux_loss, train_router_metrics
|
src/msa_service.py
ADDED
|
@@ -0,0 +1,1911 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import queue
|
| 2 |
+
import pickle
|
| 3 |
+
import json
|
| 4 |
+
from tqdm import tqdm
|
| 5 |
+
import torch
|
| 6 |
+
import torch.distributed as dist
|
| 7 |
+
import multiprocessing as mp
|
| 8 |
+
import threading
|
| 9 |
+
import os
|
| 10 |
+
from typing import Tuple, List, Optional, Dict, Union, Any, Callable
|
| 11 |
+
from dataclasses import dataclass
|
| 12 |
+
from transformers import AutoTokenizer, BitsAndBytesConfig, AutoConfig
|
| 13 |
+
|
| 14 |
+
from src.config.memory_config import GenerateConfig, MemoryConfig, ModelConfig
|
| 15 |
+
from src.prefill import PrefillStage1Worker
|
| 16 |
+
from src.types import ProtocolConstants
|
| 17 |
+
from src.utils.cache import create_cache, CustomDynamicCache
|
| 18 |
+
from src.utils.gpu_worker import GpuWorker
|
| 19 |
+
from src.utils.tools import RequestLimiter, format_bytes, cumulative_concat, compose_input
|
| 20 |
+
from src.msa.model import MSAForCausalLM, MSAConfig
|
| 21 |
+
|
| 22 |
+
# ==========================================
|
| 23 |
+
# 数据结构定义
|
| 24 |
+
# ==========================================
|
| 25 |
+
|
| 26 |
+
MEMORY_WORKER_READY = "MEMORY_WORKER_READY"
|
| 27 |
+
MEMORY_WORKER_CLOSE = "MEMORY_WORKER_CLOSE"
|
| 28 |
+
MEMORY_WORKER_BLOCKS = "MEMORY_WORKER_BLOCKS"
|
| 29 |
+
MEMORY_WORKER_IDX_TO_DOC = "MEMORY_WORKER_IDX_TO_DOC"
|
| 30 |
+
MEMORY_WORKER_REPORT_KV_STATES = "MEMORY_WORKER_REPORT_KV_STATES"
|
| 31 |
+
|
| 32 |
+
@dataclass
|
| 33 |
+
class CmdBase:
|
| 34 |
+
name: str = ""
|
| 35 |
+
|
| 36 |
+
@dataclass
|
| 37 |
+
class ResultBase:
|
| 38 |
+
name: str = ""
|
| 39 |
+
gpu_id: int = 0
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
@dataclass
|
| 43 |
+
class QueryTemplateKVPrefixCmd(CmdBase):
|
| 44 |
+
layer_idx: int = -1 # -1 indicate get all layers
|
| 45 |
+
|
| 46 |
+
def __post_init__(self):
|
| 47 |
+
self.name = "query_template_kv_prefix"
|
| 48 |
+
|
| 49 |
+
@dataclass
|
| 50 |
+
class QueryTemplateKVPrefixResult(ResultBase):
|
| 51 |
+
template_kvcache: Dict[int, Dict[str, torch.Tensor]] = None
|
| 52 |
+
|
| 53 |
+
def __post_init__(self):
|
| 54 |
+
self.name = "query_template_kv_prefix"
|
| 55 |
+
|
| 56 |
+
@dataclass
|
| 57 |
+
class PrefillStage2Cmd(CmdBase):
|
| 58 |
+
query_states: torch.Tensor = None
|
| 59 |
+
attention_mask: torch.Tensor = None
|
| 60 |
+
doc_ids: torch.Tensor = None
|
| 61 |
+
layer_index: int = 0
|
| 62 |
+
|
| 63 |
+
def __post_init__(self):
|
| 64 |
+
self.name = "prefill_stage2"
|
| 65 |
+
|
| 66 |
+
@dataclass
|
| 67 |
+
class PrefillStage2Result(ResultBase):
|
| 68 |
+
key: Union[torch.Tensor, List[torch.Tensor]] = None
|
| 69 |
+
value: Union[torch.Tensor, List[torch.Tensor]] = None
|
| 70 |
+
pooled_doc_ids: Union[torch.Tensor, List[torch.Tensor]] = None
|
| 71 |
+
|
| 72 |
+
def __post_init__(self):
|
| 73 |
+
self.name = "prefill_stage2"
|
| 74 |
+
|
| 75 |
+
# TODO: 这个可以取消,因为这是为了让prefill产生的 pooled doc id 全局唯一,但是这个完全可以
|
| 76 |
+
# 在归集stage2结果的时候才去做,只要用 block 或者 gpu id 做一个偏移就好
|
| 77 |
+
@dataclass
|
| 78 |
+
class SetDocIDCmd(CmdBase):
|
| 79 |
+
pooled_doc_id_bias: int = 0
|
| 80 |
+
|
| 81 |
+
def __post_init__(self):
|
| 82 |
+
self.name = "set_doc_id"
|
| 83 |
+
|
| 84 |
+
@dataclass
|
| 85 |
+
class ReportDocID(ResultBase):
|
| 86 |
+
pooled_doc_id_bias: int = 0
|
| 87 |
+
|
| 88 |
+
def __post_init__(self):
|
| 89 |
+
self.name = "report_doc_id"
|
| 90 |
+
|
| 91 |
+
class CustomDynamicCacheOnCPU(CustomDynamicCache):
|
| 92 |
+
def __init__(self, _distributed_cache_data=None):
|
| 93 |
+
super().__init__(_distributed_cache_data)
|
| 94 |
+
|
| 95 |
+
# def record_kwargs(self, layer_idx, kwargs):
|
| 96 |
+
# d = {}
|
| 97 |
+
# for k, v in kwargs.items():
|
| 98 |
+
# if v is not None and torch.is_tensor(v):
|
| 99 |
+
# d[k] = v.cpu() if v.is_cuda else v.clone()
|
| 100 |
+
# else:
|
| 101 |
+
# d[k] = v
|
| 102 |
+
# super().record_kwargs(layer_idx, kwargs)
|
| 103 |
+
|
| 104 |
+
def update(
|
| 105 |
+
self,
|
| 106 |
+
key_states: torch.Tensor,
|
| 107 |
+
value_states: torch.Tensor,
|
| 108 |
+
layer_idx: int,
|
| 109 |
+
cache_kwargs=None,
|
| 110 |
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 111 |
+
# if key_states is not None and torch.is_tensor(key_states) and key_states.is_cuda:
|
| 112 |
+
# key_states = key_states.cpu()
|
| 113 |
+
if value_states is not None and torch.is_tensor(value_states) and value_states.is_cuda:
|
| 114 |
+
value_states = value_states.cpu()
|
| 115 |
+
return super().update(key_states, value_states, layer_idx, cache_kwargs)
|
| 116 |
+
|
| 117 |
+
@dataclass
|
| 118 |
+
class GenerateRequest(CmdBase):
|
| 119 |
+
# 每次用户发送一个 generate 请求,我们都分配一个 msgid
|
| 120 |
+
# 但是我们会将其分成world个GenerateRequest,seq_id表示其自请求顺序
|
| 121 |
+
msg_id: int = 0
|
| 122 |
+
seq_id: int = 0
|
| 123 |
+
input_ids: List[int] = None
|
| 124 |
+
attention_mask: List[int] = None
|
| 125 |
+
doc_ids: List[int] = None
|
| 126 |
+
positions: List[int] = None
|
| 127 |
+
require_recall_topk: bool = False
|
| 128 |
+
prompts: List[str] = None
|
| 129 |
+
|
| 130 |
+
def __post_init__(self):
|
| 131 |
+
self.name = "generate_request"
|
| 132 |
+
|
| 133 |
+
@dataclass
|
| 134 |
+
class GenerateResponse():
|
| 135 |
+
msg_id: int = -1
|
| 136 |
+
seq_id: int = -1
|
| 137 |
+
gpu_id: int = 0
|
| 138 |
+
generated_texts: List[str] = None
|
| 139 |
+
recall_topk: Dict[int, List] = None
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
# response receiver: callback(generated_texts, required_recall_topk, userdata)
|
| 143 |
+
GenerateReceiver = Callable[[List[str], Any, Any], None]
|
| 144 |
+
|
| 145 |
+
@dataclass
|
| 146 |
+
class GenerateStub:
|
| 147 |
+
msg_id: int = 0
|
| 148 |
+
userdata: Any = None
|
| 149 |
+
responses: List[GenerateResponse] = None
|
| 150 |
+
nr_dummy: int = 0 # 标记这个 batch 尾部有几个query 是填充的空请求
|
| 151 |
+
callback: Optional[GenerateReceiver] = None
|
| 152 |
+
|
| 153 |
+
def respond(self):
|
| 154 |
+
if self.callback is None:
|
| 155 |
+
print("no callback")
|
| 156 |
+
return
|
| 157 |
+
|
| 158 |
+
self.responses.sort(key=lambda r: r.seq_id)
|
| 159 |
+
txts = []
|
| 160 |
+
recall_topk: Dict[int, List] = {}
|
| 161 |
+
for rsp in self.responses:
|
| 162 |
+
txts.extend(rsp.generated_texts)
|
| 163 |
+
if rsp.recall_topk:
|
| 164 |
+
for layer, topks in rsp.recall_topk.items():
|
| 165 |
+
if layer not in recall_topk:
|
| 166 |
+
recall_topk[layer] = []
|
| 167 |
+
recall_topk[layer].extend(topks)
|
| 168 |
+
|
| 169 |
+
self.callback(txts[:len(txts)-self.nr_dummy], recall_topk, self.userdata)
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
@dataclass
|
| 176 |
+
class Document:
|
| 177 |
+
doc: str = ""
|
| 178 |
+
doc_id: int = 0
|
| 179 |
+
num_chunks: int = 0
|
| 180 |
+
|
| 181 |
+
@dataclass
|
| 182 |
+
class BlockModelInput:
|
| 183 |
+
doc_input_ids: torch.Tensor
|
| 184 |
+
doc_attention_mask: torch.Tensor
|
| 185 |
+
doc_ids: torch.Tensor
|
| 186 |
+
position_ids: torch.Tensor
|
| 187 |
+
num_chunks: int
|
| 188 |
+
chunk_sizes: List[int]
|
| 189 |
+
|
| 190 |
+
@dataclass
|
| 191 |
+
class BlockDesc:
|
| 192 |
+
"""一个 block 的描述数据"""
|
| 193 |
+
docs: List[Document] = None # 所有分配到此 GPU 上的文档
|
| 194 |
+
tmp_doc_ids: List[torch.Tensor] = None # 临时存储
|
| 195 |
+
|
| 196 |
+
# kvcache的每一个 chunk 分组编号,从 1 开始,形如[1,1,1,2,2,3,3,3,...]
|
| 197 |
+
# pool_ids[-1] 就是文档总数,相同的编号对应的 chunks 表示同属于同一个文档
|
| 198 |
+
pool_ids: torch.Tensor = None
|
| 199 |
+
# 用于通过 pool_id查找 global doc id,即 global_doc_ids = doc_ids[pool_ids]
|
| 200 |
+
doc_ids: torch.Tensor = None
|
| 201 |
+
doc_ids_cpu: torch.Tensor = None # doc_ids 的 CPU 版本,方便后续索引使用
|
| 202 |
+
|
| 203 |
+
# 用 local id,也就是pool_ids中的 id 来进行检索
|
| 204 |
+
doc_offsets_cpu: torch.Tensor = None # 每个文档 chunk 的起始索引 [nr_docs + 1]
|
| 205 |
+
doc_lens_cpu: torch.Tensor = None # 每个文档包含的 chunk 数量 [nr_docs + 1]
|
| 206 |
+
|
| 207 |
+
def init_docs(self, docs: List[Document], device):
|
| 208 |
+
self.docs = docs
|
| 209 |
+
self.doc_ids_cpu = torch.LongTensor([0] + [item.doc_id for item in self.docs])
|
| 210 |
+
self.doc_ids = self.doc_ids_cpu.to(device)
|
| 211 |
+
self.tmp_doc_ids = []
|
| 212 |
+
|
| 213 |
+
# 计算偏移量:假设 doc 0 长度为 0
|
| 214 |
+
lens = [0] + [doc.num_chunks for doc in docs]
|
| 215 |
+
self.doc_lens_cpu = torch.LongTensor(lens)
|
| 216 |
+
self.doc_offsets_cpu = torch.cumsum(torch.LongTensor([0] + lens[:-1]), dim=0)
|
| 217 |
+
|
| 218 |
+
def create_global_doc_ids(self, local_doc_ids):
|
| 219 |
+
return self.doc_ids[local_doc_ids]
|
| 220 |
+
|
| 221 |
+
def merge_poolig_doc_id(self, device):
|
| 222 |
+
self.pool_ids_cpu = cumulative_concat(self.tmp_doc_ids)
|
| 223 |
+
self.pool_ids = self.pool_ids_cpu.to(device)
|
| 224 |
+
self.tmp_doc_ids.clear()
|
| 225 |
+
|
| 226 |
+
def chunks(self):
|
| 227 |
+
return sum(doc.num_chunks for doc in self.docs)
|
| 228 |
+
|
| 229 |
+
@property
|
| 230 |
+
def nr_docs(self):
|
| 231 |
+
return len(self.docs)
|
| 232 |
+
|
| 233 |
+
@dataclass
|
| 234 |
+
class BlockData:
|
| 235 |
+
"""本 GPU 上的文档 kvcache:[1, num_head, num_chunks, head_dim]"""
|
| 236 |
+
k: torch.Tensor = None # k 用于 attn 计算,如果 rk 不存在则保存于 GPU,同时用于检索,否则保存在 CPU
|
| 237 |
+
v: torch.Tensor = None # 存储于 CPU
|
| 238 |
+
rk: torch.Tensor = None # 用于检索,此时我们有独立的检索 k,需要保存于 GPU
|
| 239 |
+
|
| 240 |
+
def get_router_k(self):
|
| 241 |
+
"""获取用于检索的k"""
|
| 242 |
+
return self.rk if self.rk is not None else self.k
|
| 243 |
+
|
| 244 |
+
|
| 245 |
+
@dataclass
|
| 246 |
+
class SliceDesc:
|
| 247 |
+
"""block分片信息,每一个分片包含了一部分文档信息
|
| 248 |
+
"""
|
| 249 |
+
nr_docs: int = 0 # 文档数量
|
| 250 |
+
nr_chunks: int = 0 # chunk数量
|
| 251 |
+
global_doc_ids: torch.Tensor = None # 文档 ID,shape 为[nr_chunks]
|
| 252 |
+
|
| 253 |
+
# 下面这几个 tensor 是我们预先计算好,用于 prefill stage2 的数据
|
| 254 |
+
local_doc_ids: torch.Tensor = None
|
| 255 |
+
local_doc_ids_0: torch.Tensor = None
|
| 256 |
+
original_doc_ids: torch.Tensor = None
|
| 257 |
+
|
| 258 |
+
|
| 259 |
+
class MemoryClientBase():
|
| 260 |
+
"""被模型调用的接口"""
|
| 261 |
+
def get_template_prefix_kvcaches(self, layer_idx: int):
|
| 262 |
+
pass
|
| 263 |
+
def doc_query(self, query_states: torch.Tensor, attention_mask: torch.Tensor, layer_idx: int):
|
| 264 |
+
pass
|
| 265 |
+
|
| 266 |
+
# ==========================================
|
| 267 |
+
# Service 实现
|
| 268 |
+
# ==========================================
|
| 269 |
+
class Memory(GpuWorker):
|
| 270 |
+
"""Memory工作进程"""
|
| 271 |
+
|
| 272 |
+
def __init__(self,
|
| 273 |
+
gpu_id: int,
|
| 274 |
+
generate_config: GenerateConfig,
|
| 275 |
+
model_config: ModelConfig,
|
| 276 |
+
memory_config: MemoryConfig):
|
| 277 |
+
"""
|
| 278 |
+
该 worker 被MemoryWorker创建并仅执行prefill stage 1获取block 的kv cache
|
| 279 |
+
"""
|
| 280 |
+
super().__init__(gpu_id, model_config.get_model_envs())
|
| 281 |
+
|
| 282 |
+
self.model_config = model_config
|
| 283 |
+
self.generate_config = generate_config
|
| 284 |
+
self.memory_config = memory_config
|
| 285 |
+
|
| 286 |
+
self.msa_model_config = MSAConfig.from_pretrained(model_config.model_path)
|
| 287 |
+
|
| 288 |
+
self.router_layer_ids = list(range(self.num_model_layers()) if model_config.router_layer_idx == "all"
|
| 289 |
+
else map(int, model_config.router_layer_idx.split(",")))
|
| 290 |
+
self.router_layer_ids.sort()
|
| 291 |
+
|
| 292 |
+
self.num_key_value_groups = self.msa_model_config.num_attention_heads // self.msa_model_config.num_key_value_heads
|
| 293 |
+
|
| 294 |
+
|
| 295 |
+
self.vcache_in_cpu = True
|
| 296 |
+
self.v_device = 'cpu' if self.vcache_in_cpu else self.device
|
| 297 |
+
|
| 298 |
+
######## 下面几个定义是可以动态加载进来的
|
| 299 |
+
# 每一个router层上的分片数据列表: layer_idx => [block 1, block 2, ...]
|
| 300 |
+
self.blocks: Dict[int, BlockData] = {layer: BlockData() for layer in self.router_layer_ids}
|
| 301 |
+
# 每一个 block 的描述数据, [block 1 desc, block 2 desc, ...]
|
| 302 |
+
self.block_desc: BlockDesc = BlockDesc()
|
| 303 |
+
# 每一层上的template prefix kv cache: layer idx -> (k, v)
|
| 304 |
+
self.template_prefix_kvcache: Dict[int, Tuple[torch.Tensor, torch.Tensor]] = {}
|
| 305 |
+
|
| 306 |
+
# 对 block 进行chunk 分割时,每个 block 大小
|
| 307 |
+
self.slice_chunk_size = memory_config.slice_chunk_size
|
| 308 |
+
# 为了后续计算方便,我们预先处理好了 k slice,shape 为[1, num_kv_heads, 1, hdim, chunk]
|
| 309 |
+
self.k_slices: Dict[int, List[torch.Tensor]] = []
|
| 310 |
+
self.slice_desc: List[SliceDesc] = []
|
| 311 |
+
|
| 312 |
+
self.head_reduce_method = self.msa_model_config.msa_config.head_reduce_method
|
| 313 |
+
self.query_reduce_method = self.msa_model_config.msa_config.query_reduce_method
|
| 314 |
+
self.chunk_reduce_method = self.msa_model_config.msa_config.chunk_reduce_method
|
| 315 |
+
self.aux_loss_method = self.msa_model_config.msa_config.aux_loss_method
|
| 316 |
+
self.decouple_router = self.msa_model_config.msa_config.decouple_router
|
| 317 |
+
|
| 318 |
+
if self.aux_loss_method == "INFONCE" and self.decouple_router:
|
| 319 |
+
self.scaling = -1.0
|
| 320 |
+
else:
|
| 321 |
+
self.scaling = self.msa_model_config.head_dim**-0.5
|
| 322 |
+
|
| 323 |
+
def _start_worker(self):
|
| 324 |
+
self._worker_req_q, self._worker_rsp_q = mp.Queue(), mp.Queue()
|
| 325 |
+
|
| 326 |
+
self._worker_process = mp.Process(
|
| 327 |
+
target=PrefillStage1Worker.prefill_worker_main,
|
| 328 |
+
args=(self.gpu_id, self._worker_req_q, self._worker_rsp_q,
|
| 329 |
+
self.model_config.model_path, self.generate_config.template,
|
| 330 |
+
self.memory_config.pooling_kernel_size, self.memory_config.block_size,
|
| 331 |
+
self.model_config.get_model_envs()),
|
| 332 |
+
name=f"PrefillStage1Worker-{self.gpu_id}"
|
| 333 |
+
)
|
| 334 |
+
self._worker_process.start()
|
| 335 |
+
|
| 336 |
+
PrefillStage1Worker.wait_for_ready(self._worker_rsp_q)
|
| 337 |
+
print(f"Prefill worker {self.gpu_id} is ready")
|
| 338 |
+
|
| 339 |
+
def _stop_worker(self):
|
| 340 |
+
print(f"Stop prefill worker on GPU {self.gpu_id}")
|
| 341 |
+
PrefillStage1Worker.close_worker(self._worker_req_q)
|
| 342 |
+
self._worker_process.join()
|
| 343 |
+
self._worker_process = None
|
| 344 |
+
self._worker_req_q, self._worker_rsp_q = None, None
|
| 345 |
+
|
| 346 |
+
|
| 347 |
+
def num_model_layers(self):
|
| 348 |
+
return self.msa_model_config.num_hidden_layers
|
| 349 |
+
|
| 350 |
+
|
| 351 |
+
def serialize(self, path: str):
|
| 352 |
+
"""
|
| 353 |
+
安全分片序列化:
|
| 354 |
+
1. 创建目录
|
| 355 |
+
2. 保存元数据 (meta.pt)
|
| 356 |
+
3. 逐层保存 Tensor 到独立文件,避免内存峰值
|
| 357 |
+
"""
|
| 358 |
+
# 1. 准备目录 (如果存在则警告或清空,这里选择清空以确保一致性)
|
| 359 |
+
if os.path.exists(path):
|
| 360 |
+
print(f"Warning: Directory {path} exists. Overwriting data inside.")
|
| 361 |
+
else:
|
| 362 |
+
os.makedirs(path, exist_ok=True)
|
| 363 |
+
|
| 364 |
+
stub = {
|
| 365 |
+
"memory_file_path": self.memory_config.memory_file_path,
|
| 366 |
+
"world": self.generate_config.world,
|
| 367 |
+
}
|
| 368 |
+
with open(os.path.join(path, "stub.json"), "w") as fp:
|
| 369 |
+
json.dump(stub, fp)
|
| 370 |
+
|
| 371 |
+
# 2. 保存元数据 (BlockDesc + 结构信息)
|
| 372 |
+
# 注意:BlockData 里的 heavy tensor 不在这里存,只存结构
|
| 373 |
+
meta_dict = {
|
| 374 |
+
"router_layer_ids": self.router_layer_ids,
|
| 375 |
+
"block_desc": {
|
| 376 |
+
"docs": self.block_desc.docs,
|
| 377 |
+
"doc_ids": self.block_desc.doc_ids.cpu(),
|
| 378 |
+
# pool_ids 转 CPU 保存
|
| 379 |
+
"pool_ids": self.block_desc.pool_ids.cpu() if self.block_desc.pool_ids is not None else None
|
| 380 |
+
},
|
| 381 |
+
}
|
| 382 |
+
torch.save(meta_dict, os.path.join(path, "meta.pt"))
|
| 383 |
+
|
| 384 |
+
# 3. 逐层保存 Blocks (重头戏)
|
| 385 |
+
print("Serializing Blocks (Sharded)...")
|
| 386 |
+
for layer_id, block in tqdm(self.blocks.items(), desc="Saving Layers"):
|
| 387 |
+
# 分离存储 K 和 V
|
| 388 |
+
if block.k is not None:
|
| 389 |
+
# K 是 GPU -> CPU
|
| 390 |
+
torch.save(block.k.cpu(), os.path.join(path, f"layer_{layer_id}_k.pt"))
|
| 391 |
+
|
| 392 |
+
if block.v is not None:
|
| 393 |
+
# V 已经在 CPU,直接存
|
| 394 |
+
# 提示:对于 CPU 上的极大 Tensor,torch.save 效率很高
|
| 395 |
+
torch.save(block.v.cpu(), os.path.join(path, f"layer_{layer_id}_v.pt"))
|
| 396 |
+
|
| 397 |
+
if block.rk is not None:
|
| 398 |
+
torch.save(block.rk.cpu(), os.path.join(path, f"layer_{layer_id}_rk.pt"))
|
| 399 |
+
|
| 400 |
+
# 4. 保存 Prefix Cache
|
| 401 |
+
if self.template_prefix_kvcache:
|
| 402 |
+
print("Serializing Prefix Cache...")
|
| 403 |
+
prefix_data = {}
|
| 404 |
+
for layer_id, (k, v) in self.template_prefix_kvcache.items():
|
| 405 |
+
prefix_data[layer_id] = (k.cpu(), v.cpu())
|
| 406 |
+
torch.save(prefix_data, os.path.join(path, "prefix_cache.pt"))
|
| 407 |
+
|
| 408 |
+
print(f"Serialization complete. Data saved to {path}")
|
| 409 |
+
|
| 410 |
+
def deserialize(self, path: str):
|
| 411 |
+
"""
|
| 412 |
+
流式反序列化:
|
| 413 |
+
每次只加载一个文件进内存,处理完后立即释放或转移到 GPU,
|
| 414 |
+
将系统内存(RAM)占用控制在最低。
|
| 415 |
+
"""
|
| 416 |
+
if not os.path.exists(path):
|
| 417 |
+
raise FileNotFoundError(f"Path {path} does not exist.")
|
| 418 |
+
|
| 419 |
+
print("Deserialize from ", path)
|
| 420 |
+
|
| 421 |
+
# 1. 加载元数据
|
| 422 |
+
meta_path = os.path.join(path, "meta.pt")
|
| 423 |
+
meta = torch.load(meta_path, map_location="cpu", weights_only=False)
|
| 424 |
+
|
| 425 |
+
# 恢复 BlockDesc
|
| 426 |
+
bd_info = meta["block_desc"]
|
| 427 |
+
self.block_desc = BlockDesc(
|
| 428 |
+
docs=bd_info["docs"],
|
| 429 |
+
doc_ids=bd_info["doc_ids"].to(self.device),
|
| 430 |
+
pool_ids=bd_info["pool_ids"].to(self.device) if bd_info["pool_ids"] is not None else None
|
| 431 |
+
)
|
| 432 |
+
|
| 433 |
+
# 2. 逐层恢复 Blocks
|
| 434 |
+
# 关键:我们不一次性加载所有文件,而是循环加载
|
| 435 |
+
self.blocks = {}
|
| 436 |
+
print(f"Deserializing Blocks to GPU {self.gpu_id}...")
|
| 437 |
+
|
| 438 |
+
for layer_id in tqdm(self.router_layer_ids, desc=f"GPU-{self.gpu_id} Loading Layers", position=self.gpu_id):
|
| 439 |
+
k_path = os.path.join(path, f"layer_{layer_id}_k.pt")
|
| 440 |
+
v_path = os.path.join(path, f"layer_{layer_id}_v.pt")
|
| 441 |
+
rk_path = os.path.join(path, f"layer_{layer_id}_rk.pt")
|
| 442 |
+
|
| 443 |
+
current_k = None
|
| 444 |
+
current_v = None
|
| 445 |
+
current_rk = None
|
| 446 |
+
|
| 447 |
+
# 加载 K -> 立即转 GPU -> 删除 CPU 副本
|
| 448 |
+
if os.path.exists(k_path):
|
| 449 |
+
temp_k = torch.load(k_path, map_location="cpu", weights_only=False)
|
| 450 |
+
current_k = temp_k.to(self.device) # 移入显存
|
| 451 |
+
del temp_k # 立即释放内存
|
| 452 |
+
|
| 453 |
+
# 加载 V -> 驻留 CPU
|
| 454 |
+
if os.path.exists(v_path):
|
| 455 |
+
# 既然 V 是 CPU 存储且极大,直接 load 即可
|
| 456 |
+
# 如果 V 极其巨大(比如超过 RAM),可以考虑 mmap=True,
|
| 457 |
+
# 但这要求保存时格式兼容,且 V 通常作为 cache 需要频繁读取,RAM 还是最快的
|
| 458 |
+
current_v = torch.load(v_path, map_location="cpu", weights_only=False)
|
| 459 |
+
|
| 460 |
+
if os.path.exists(rk_path):
|
| 461 |
+
current_rk = torch.load(rk_path, map_location="cpu", weights_only=False)
|
| 462 |
+
|
| 463 |
+
self.blocks[layer_id] = BlockData(k=current_k, v=current_v, rk=current_rk)
|
| 464 |
+
|
| 465 |
+
# 显式触发垃圾回收(可选,但在内存吃紧时有用)
|
| 466 |
+
# import gc; gc.collect()
|
| 467 |
+
|
| 468 |
+
# 3. 恢复 Prefix Cache
|
| 469 |
+
prefix_path = os.path.join(path, "prefix_cache.pt")
|
| 470 |
+
if os.path.exists(prefix_path):
|
| 471 |
+
prefix_raw = torch.load(prefix_path, map_location="cpu", weights_only=False)
|
| 472 |
+
self.template_prefix_kvcache = {}
|
| 473 |
+
for layer_id, (k_cpu, v_cpu) in prefix_raw.items():
|
| 474 |
+
self.template_prefix_kvcache[layer_id] = (
|
| 475 |
+
k_cpu.to(self.device),
|
| 476 |
+
v_cpu.to(self.device)
|
| 477 |
+
)
|
| 478 |
+
|
| 479 |
+
print("Deserialization complete.")
|
| 480 |
+
|
| 481 |
+
def _init_block_data(self, shape: torch.Size, dtype: torch.dtype, has_rk=False):
|
| 482 |
+
total_chunks = self.block_desc.chunks()
|
| 483 |
+
for layer_idx in self.router_layer_ids:
|
| 484 |
+
block_data = self.blocks[layer_idx]
|
| 485 |
+
|
| 486 |
+
if block_data.k is None:
|
| 487 |
+
kv_shape = list(shape)
|
| 488 |
+
kv_shape[2] = total_chunks
|
| 489 |
+
# TODO: deserialize 时需要考虑 pin_memory
|
| 490 |
+
block_data.k = torch.empty(kv_shape, device=self.v_device if has_rk else self.device, dtype=dtype, pin_memory=has_rk)
|
| 491 |
+
block_data.v = torch.empty(kv_shape, device=self.v_device, dtype=dtype, pin_memory=True)
|
| 492 |
+
block_data.rk = torch.empty(kv_shape, device=self.device, dtype=dtype) if has_rk else None
|
| 493 |
+
|
| 494 |
+
def save_idx_to_doc(self, idx_to_doc: Dict[int, str]):
|
| 495 |
+
self.idx_to_doc = idx_to_doc
|
| 496 |
+
|
| 497 |
+
def generate_blocks(self, docs: List[Document]):
|
| 498 |
+
memory_data_path = os.environ.get("MEMORY_DATA_PATH", "")
|
| 499 |
+
if memory_data_path:
|
| 500 |
+
memory_data_path = os.path.join(memory_data_path, f"gpu{self.generate_config.world}", str(self.gpu_id))
|
| 501 |
+
if os.path.exists(memory_data_path):
|
| 502 |
+
self.deserialize(memory_data_path)
|
| 503 |
+
assert len(docs) == self.block_desc.nr_docs
|
| 504 |
+
self._post_process()
|
| 505 |
+
return
|
| 506 |
+
|
| 507 |
+
self._start_worker()
|
| 508 |
+
|
| 509 |
+
self.block_desc.init_docs(docs, self.device)
|
| 510 |
+
|
| 511 |
+
PrefillStage1Worker.send_documents(self._worker_req_q, docs)
|
| 512 |
+
|
| 513 |
+
kv_offset = 0
|
| 514 |
+
recv = 0
|
| 515 |
+
|
| 516 |
+
total_docs = len(docs)
|
| 517 |
+
total_chunks = self.block_desc.chunks()
|
| 518 |
+
pbar = tqdm(total=total_docs, desc=f"Worker-{self.gpu_id} memory inference", position=self.gpu_id)
|
| 519 |
+
while recv < total_docs:
|
| 520 |
+
meta = PrefillStage1Worker.recv_meta(self._worker_rsp_q)
|
| 521 |
+
|
| 522 |
+
recv += meta['nr_docs'] # 此 meta 包含了几个文档
|
| 523 |
+
pbar.update(meta['nr_docs'])
|
| 524 |
+
|
| 525 |
+
past_key_values: CustomDynamicCacheOnCPU = meta["past_key_values"]
|
| 526 |
+
|
| 527 |
+
# 仅保存一次
|
| 528 |
+
if not self.template_prefix_kvcache:
|
| 529 |
+
kwargs = past_key_values.cache_kwargs
|
| 530 |
+
self.template_prefix_kvcache = {
|
| 531 |
+
layer_idx: (
|
| 532 |
+
kwargs[layer_idx]['template_prefix_kcache'].to(self.device),
|
| 533 |
+
kwargs[layer_idx]['template_prefix_vcache'].to(self.device)
|
| 534 |
+
)
|
| 535 |
+
for layer_idx in range(self.num_model_layers())
|
| 536 |
+
}
|
| 537 |
+
|
| 538 |
+
|
| 539 |
+
meta_chunks = 0 # 这次的 meta 有多少 chunks
|
| 540 |
+
|
| 541 |
+
for layer_idx in self.router_layer_ids:
|
| 542 |
+
block_data = self.blocks[layer_idx]
|
| 543 |
+
|
| 544 |
+
k, v = past_key_values.get_kvcache(layer_idx) # for attn calc
|
| 545 |
+
rk = past_key_values.get_router_kcache(layer_idx) # for router, if any
|
| 546 |
+
|
| 547 |
+
desc: BlockDesc = self.block_desc
|
| 548 |
+
if block_data.k is None:
|
| 549 |
+
self._init_block_data(k.shape, k.dtype, has_rk=rk is not None)
|
| 550 |
+
|
| 551 |
+
if layer_idx == self.router_layer_ids[0]:
|
| 552 |
+
# 聚合 pooling doc id,因为每一层上的 pooling doc id 都是一样的,所以只聚合第一层
|
| 553 |
+
desc.tmp_doc_ids.append(past_key_values.cache_kwargs[layer_idx]['pooled_doc_ids'])
|
| 554 |
+
|
| 555 |
+
k_cache, v_cache, rk_cache = block_data.k, block_data.v, block_data.rk
|
| 556 |
+
if meta_chunks == 0:
|
| 557 |
+
meta_chunks = k.shape[2]
|
| 558 |
+
else:
|
| 559 |
+
assert meta_chunks == k.shape[2], f"expect meta_chunks {meta_chunks} but got {k.shape[2]}"
|
| 560 |
+
assert kv_offset + meta_chunks <= total_chunks, f"overflow: offset {kv_offset}, meta_chunks {meta_chunks}, total_chunks {total_chunks}"
|
| 561 |
+
|
| 562 |
+
# print(f"shape {k.shape} offset {kv_offset}")
|
| 563 |
+
k_cache[:, :, kv_offset:kv_offset+meta_chunks] = k.to(k_cache.device)
|
| 564 |
+
v_cache[:, :, kv_offset:kv_offset+meta_chunks] = v.to(v_cache.device)
|
| 565 |
+
if rk is not None:
|
| 566 |
+
rk_cache[:, :, kv_offset:kv_offset+meta_chunks] = rk.to(rk_cache.device)
|
| 567 |
+
|
| 568 |
+
# 每次一个 meta 处理完才需要进偏移
|
| 569 |
+
kv_offset += meta_chunks
|
| 570 |
+
|
| 571 |
+
past_key_values.clear_kvcache()
|
| 572 |
+
|
| 573 |
+
pbar.close()
|
| 574 |
+
self._stop_worker()
|
| 575 |
+
|
| 576 |
+
# 合并临时pooled doc id
|
| 577 |
+
self.block_desc.merge_poolig_doc_id(self.device)
|
| 578 |
+
|
| 579 |
+
if memory_data_path:
|
| 580 |
+
self.serialize(memory_data_path)
|
| 581 |
+
|
| 582 |
+
self._post_process()
|
| 583 |
+
|
| 584 |
+
def _post_process(self):
|
| 585 |
+
# 产生切片
|
| 586 |
+
self._generate_slice(self.slice_chunk_size)
|
| 587 |
+
|
| 588 |
+
kv_bytes = self.blocks[self.router_layer_ids[0]].k.nbytes * len(self.router_layer_ids)
|
| 589 |
+
if not self.vcache_in_cpu:
|
| 590 |
+
kv_bytes *= 2
|
| 591 |
+
|
| 592 |
+
kv_shape = self.blocks[self.router_layer_ids[0]].k.shape
|
| 593 |
+
|
| 594 |
+
print(f"GPU {self.gpu_id} memory usage: {format_bytes(kv_bytes)} kv shape {kv_shape}")
|
| 595 |
+
return kv_bytes, kv_shape
|
| 596 |
+
|
| 597 |
+
@staticmethod
|
| 598 |
+
def map_tensor_to_group_ids(a: torch.Tensor) -> torch.Tensor:
|
| 599 |
+
"""
|
| 600 |
+
根据相邻元素是否相同,将输入 Tensor a 的值映射到递增的组 ID Tensor b。
|
| 601 |
+
|
| 602 |
+
a[i] == a[i-1] => b[i] = b[i-1]
|
| 603 |
+
a[i] != a[i-1] => b[i] = b[i-1] + 1
|
| 604 |
+
b[0] 始终为 1。
|
| 605 |
+
|
| 606 |
+
Args:
|
| 607 |
+
a (torch.Tensor): 待处理的一维 int Tensor,必须在 GPU 上。
|
| 608 |
+
|
| 609 |
+
Returns:
|
| 610 |
+
torch.Tensor: 生成的组 ID Tensor b,在 GPU 上。
|
| 611 |
+
"""
|
| 612 |
+
if a.ndim != 1:
|
| 613 |
+
raise ValueError("输入 Tensor a 必须是一维的。")
|
| 614 |
+
|
| 615 |
+
# 1. 检查相邻元素是否不同 (a[i] != a[i-1])
|
| 616 |
+
# torch.diff(a) 计算相邻元素之间的差值 a[i] - a[i-1]
|
| 617 |
+
# 差值不为 0 (!= 0) 即表示 a[i] != a[i-1]
|
| 618 |
+
# 结果是一个布尔 Tensor,长度比 a 短 1,代表从 a[1] 开始的相邻比较
|
| 619 |
+
diff_mask = torch.diff(a) != 0 # [L-1]
|
| 620 |
+
|
| 621 |
+
# 2. 将布尔值转换为整数 (0 或 1)
|
| 622 |
+
# True -> 1 (需要增加 ID),False -> 0 (保持 ID)
|
| 623 |
+
# torch.diff 的结果长度是 L-1,我们只关心从 a[1] 开始的递增
|
| 624 |
+
id_increments = diff_mask.int() # [L-1]
|
| 625 |
+
|
| 626 |
+
# 3. 累计增量
|
| 627 |
+
# torch.cumsum 对增量进行累加:
|
| 628 |
+
# 结果: [0, 0+1, 0+1+0, ...]
|
| 629 |
+
# 长度仍然是 L-1
|
| 630 |
+
group_indices_offset = torch.cumsum(id_increments, dim=0) # [L-1]
|
| 631 |
+
|
| 632 |
+
# 4. 构造最终的 Tensor b
|
| 633 |
+
# b[0] 始终是 1
|
| 634 |
+
# 对于 i > 0,b[i] = group_indices_offset[i-1] + 1
|
| 635 |
+
|
| 636 |
+
# 在 group_indices_offset 前面添加一个 0 作为 a[0] 的基准偏移
|
| 637 |
+
# [0, group_indices_offset[0], group_indices_offset[1], ...]
|
| 638 |
+
# 结果长度为 L
|
| 639 |
+
b = torch.cat((
|
| 640 |
+
torch.tensor([0], device=a.device, dtype=a.dtype),
|
| 641 |
+
group_indices_offset
|
| 642 |
+
)) + 1
|
| 643 |
+
return b
|
| 644 |
+
def _generate_slice(self, chunks):
|
| 645 |
+
nr_doc_slices = [] # 每个 block slice 所含的文档数量
|
| 646 |
+
nr_chunk_slices = [] # 每个 block slice 所含的 chunk 数量
|
| 647 |
+
chunk_slices_offset = [] # 每个 block slice 所含的 chunk 位置
|
| 648 |
+
chunk_slice_position: Tuple[int, int] = [] # 每个 chunk slice 的起始和结束位置
|
| 649 |
+
|
| 650 |
+
current_chunks = 0
|
| 651 |
+
current_docs = 0
|
| 652 |
+
chunk_offset = 0
|
| 653 |
+
for doc in self.block_desc.docs:
|
| 654 |
+
current_chunks += doc.num_chunks
|
| 655 |
+
current_docs += 1
|
| 656 |
+
chunk_offset += doc.num_chunks
|
| 657 |
+
|
| 658 |
+
if current_chunks >= chunks:
|
| 659 |
+
nr_doc_slices.append(current_docs)
|
| 660 |
+
nr_chunk_slices.append(current_chunks)
|
| 661 |
+
chunk_slices_offset.append(chunk_offset)
|
| 662 |
+
current_chunks = 0
|
| 663 |
+
current_docs = 0
|
| 664 |
+
|
| 665 |
+
if current_chunks > 0:
|
| 666 |
+
if current_chunks < 1024 and len(nr_doc_slices) > 0:
|
| 667 |
+
# too small, append to last
|
| 668 |
+
nr_doc_slices[-1] += current_docs
|
| 669 |
+
nr_chunk_slices[-1] += current_chunks
|
| 670 |
+
chunk_slices_offset[-1] = chunk_offset
|
| 671 |
+
else:
|
| 672 |
+
nr_doc_slices.append(current_docs)
|
| 673 |
+
nr_chunk_slices.append(current_chunks)
|
| 674 |
+
chunk_slices_offset.append(chunk_offset)
|
| 675 |
+
|
| 676 |
+
starts = [0] + chunk_slices_offset[:-1]
|
| 677 |
+
chunk_slice_position = [(start, end) for start, end in zip(starts, chunk_slices_offset)]
|
| 678 |
+
|
| 679 |
+
self.k_slices = {}
|
| 680 |
+
self.slice_desc = []
|
| 681 |
+
for slice_idx, (start, end) in enumerate(chunk_slice_position):
|
| 682 |
+
desc = SliceDesc(nr_docs=nr_doc_slices[slice_idx],
|
| 683 |
+
nr_chunks=nr_chunk_slices[slice_idx],
|
| 684 |
+
global_doc_ids=self.block_desc.pool_ids[start:end],
|
| 685 |
+
local_doc_ids=Memory.map_tensor_to_group_ids(self.block_desc.pool_ids[start:end]))
|
| 686 |
+
desc.local_doc_ids_0 = (desc.local_doc_ids - 1).view(1, -1)
|
| 687 |
+
unique_indices = torch.cat([torch.tensor([0], device=self.device), torch.where(desc.local_doc_ids[1:] != desc.local_doc_ids[:-1])[0] + 1])
|
| 688 |
+
desc.original_doc_ids = desc.global_doc_ids[unique_indices].view(1, -1)
|
| 689 |
+
|
| 690 |
+
self.slice_desc.append(desc)
|
| 691 |
+
|
| 692 |
+
for layer_idx in self.router_layer_ids:
|
| 693 |
+
rk = self.blocks[layer_idx].get_router_k()
|
| 694 |
+
slice = [rk[:,:,start:end].transpose(-1, -2).unsqueeze(2) for start, end in chunk_slice_position]
|
| 695 |
+
self.k_slices[layer_idx] = slice
|
| 696 |
+
|
| 697 |
+
def get_max_local_pool_doc_id(self) -> List[int]:
|
| 698 |
+
return self.block_desc.pool_ids[-1].item()
|
| 699 |
+
|
| 700 |
+
def get_template_prefix_kvcaches(self, layer_idx: int):
|
| 701 |
+
return self.template_prefix_kvcache[layer_idx]
|
| 702 |
+
|
| 703 |
+
def prefill_stage2(self, layer_idx: int, query_states: torch.Tensor, query_mask: torch.Tensor):
|
| 704 |
+
# query_mask: [bsz, 1, 1, seqlen, 1]
|
| 705 |
+
# query_states: [bsz, n_kv_head, n_kv_groups, seqlen, hdim]
|
| 706 |
+
|
| 707 |
+
bsz, num_kv_heads, num_kv_groups, seq_len, head_dim = query_states.shape
|
| 708 |
+
min_val = torch.finfo(query_states.dtype).min
|
| 709 |
+
routing_mask = ~query_mask
|
| 710 |
+
query_mask = query_mask.squeeze(2) # [bsz, 1, seqlen, 1]
|
| 711 |
+
q_lens = None
|
| 712 |
+
|
| 713 |
+
# 初始化分数表
|
| 714 |
+
# 严重注意:请确认 nr_docs 是否真的等于 max_doc_id + 1。
|
| 715 |
+
# 如果 pool_ids 是全局唯一的(例如加了 bias 100000),这里的 total_docs 必须能容纳最大的 ID。
|
| 716 |
+
total_docs = self.block_desc.nr_docs + 1
|
| 717 |
+
global_doc_scores = torch.full((bsz, total_docs), min_val, dtype=query_states.dtype, device=self.device)
|
| 718 |
+
|
| 719 |
+
# --- Phase 1: 打分 (移除 Query Batch Loop) ---
|
| 720 |
+
|
| 721 |
+
# 针对 A100,如果 bsz < 256,直接全量计算收益最高
|
| 722 |
+
# 我们直接遍历 slice,不再对 query 进行切片
|
| 723 |
+
|
| 724 |
+
for slice_desc, k_slice_t in zip(self.slice_desc, self.k_slices[layer_idx]):
|
| 725 |
+
# k_slice_t: [1, num_kv_heads, 1, hdim, chunk] (GPU)
|
| 726 |
+
|
| 727 |
+
# 1. Matmul (Compute Bound)
|
| 728 |
+
# [bsz, num_kv_heads, groups, seqlen, hdim] @ [1, num_kv_heads, 1, hdim, chunk]
|
| 729 |
+
# -> [bsz, num_kv_heads, groups, seqlen, chunk]
|
| 730 |
+
attn_scores = torch.matmul(query_states, k_slice_t)
|
| 731 |
+
|
| 732 |
+
# 2. Mask & Reduce (Memory Bound)
|
| 733 |
+
attn_scores.masked_fill_(routing_mask, min_val)
|
| 734 |
+
# Flatten & Reduce heads/seqlen
|
| 735 |
+
# flatten(1,2): [bsz, num_kv_heads*groups, seqlen, chunk]
|
| 736 |
+
# max(dim=2)[0]: [bsz, num_kv_heads*groups, chunk]
|
| 737 |
+
# max(dim=1)[0]: [bsz, chunk]
|
| 738 |
+
# max_scores_per_chunk = attn_scores.flatten(1, 2).max(dim=2)[0].max(dim=1)[0] # [bsz, chunk]
|
| 739 |
+
max_scores_per_chunk = attn_scores.flatten(1, 2)
|
| 740 |
+
|
| 741 |
+
# [bsz, num_kv_heads*groups, chunk]
|
| 742 |
+
if self.head_reduce_method == "max":
|
| 743 |
+
max_scores_per_chunk = max_scores_per_chunk.max(dim=1).values
|
| 744 |
+
elif self.head_reduce_method == "mean":
|
| 745 |
+
max_scores_per_chunk = max_scores_per_chunk.mean(dim=1)
|
| 746 |
+
else:
|
| 747 |
+
raise NotImplementedError(f"Unsupported head reduce method: {self.head_reduce_method}")
|
| 748 |
+
|
| 749 |
+
if self.query_reduce_method == "max":
|
| 750 |
+
max_scores_per_chunk = max_scores_per_chunk.max(dim=1).values
|
| 751 |
+
elif self.query_reduce_method == "mean":
|
| 752 |
+
# 1. 调整 mask 形状以匹配 max_scores_per_chunk
|
| 753 |
+
# [bsz, 1, seqlen, 1] -> [bsz, seqlen, 1]
|
| 754 |
+
mask = query_mask.squeeze(1)
|
| 755 |
+
|
| 756 |
+
# 2. 在求和前,将无效位置(min_val)归零
|
| 757 |
+
# 使用 torch.where 比乘法更安全,且能有效处理极小的 min_val
|
| 758 |
+
masked_sum = torch.where(mask, max_scores_per_chunk, torch.zeros_like(max_scores_per_chunk)).sum(dim=1)
|
| 759 |
+
|
| 760 |
+
# 3. 计算每个 sequence 中的有效 token 数量
|
| 761 |
+
# 防止除以 0,使用 clamp 或加一个极小值 eps
|
| 762 |
+
valid_counts = mask.sum(dim=1).clamp(min=1.0)
|
| 763 |
+
|
| 764 |
+
# 4. 得到最终均值 [bsz, chunk]
|
| 765 |
+
max_scores_per_chunk = masked_sum / valid_counts
|
| 766 |
+
elif self.query_reduce_method == "last":
|
| 767 |
+
if q_lens is None:
|
| 768 |
+
q_lens = query_mask.squeeze(3).squeeze(1).sum(dim=1).long()
|
| 769 |
+
|
| 770 |
+
# 边界保护: 如果 length 为 0 (异常情况), clamp 到 0
|
| 771 |
+
last_indices = (q_lens - 1).clamp(min=0)
|
| 772 |
+
|
| 773 |
+
# 3. 调整形状用于 gather
|
| 774 |
+
# [B, 1, C]
|
| 775 |
+
gather_idx = last_indices.view(bsz, 1, 1).expand(-1, 1, slice_desc.nr_chunks)
|
| 776 |
+
|
| 777 |
+
# 4. 提取
|
| 778 |
+
max_scores_per_chunk = max_scores_per_chunk.gather(1, gather_idx).squeeze(1)
|
| 779 |
+
else:
|
| 780 |
+
raise NotImplementedError(f"Unsupported query reduce method: {self.query_reduce_method}")
|
| 781 |
+
|
| 782 |
+
# 3. Local Scatter (Chunk -> Doc)
|
| 783 |
+
# slice_desc.local_doc_ids_0 已经在 GPU 上预计算好了
|
| 784 |
+
# [1, chunk] -> [bsz, chunk]
|
| 785 |
+
scatter_indices = slice_desc.local_doc_ids_0.expand(bsz, -1)
|
| 786 |
+
|
| 787 |
+
local_doc_scores = torch.full((bsz, slice_desc.nr_docs), -float('inf'), device=self.device, dtype=query_states.dtype)
|
| 788 |
+
if self.chunk_reduce_method == "max":
|
| 789 |
+
local_doc_scores.scatter_reduce_(
|
| 790 |
+
dim=1, index=scatter_indices, src=max_scores_per_chunk,
|
| 791 |
+
reduce="amax", include_self=True
|
| 792 |
+
)
|
| 793 |
+
|
| 794 |
+
elif self.chunk_reduce_method == "mean":
|
| 795 |
+
doc_sums = torch.zeros_like(local_doc_scores)
|
| 796 |
+
doc_sums = doc_sums.scatter_reduce(
|
| 797 |
+
dim=1, index=scatter_indices, src=max_scores_per_chunk,
|
| 798 |
+
reduce="sum", include_self=False
|
| 799 |
+
)
|
| 800 |
+
doc_counts = torch.zeros_like(local_doc_scores)
|
| 801 |
+
ones = torch.ones_like(max_scores_per_chunk)
|
| 802 |
+
doc_counts = doc_counts.scatter_reduce(
|
| 803 |
+
dim=1, index=scatter_indices, src=ones,
|
| 804 |
+
reduce="sum", include_self=False
|
| 805 |
+
)
|
| 806 |
+
doc_counts = doc_counts.clamp(min=1.0)
|
| 807 |
+
mean_scores = doc_sums / doc_counts
|
| 808 |
+
# TODO: 能不能不要重新产生一个local_doc_scores?
|
| 809 |
+
local_doc_scores = torch.where(doc_counts > 0, mean_scores, local_doc_scores)
|
| 810 |
+
|
| 811 |
+
del doc_sums, doc_counts, mean_scores
|
| 812 |
+
else:
|
| 813 |
+
raise ValueError(f"Invalid chunk reduction method: {self.chunk_reduce_method}")
|
| 814 |
+
|
| 815 |
+
# 4. Global Scatter (Local Doc -> Global Doc)
|
| 816 |
+
global_indices = slice_desc.original_doc_ids.expand(bsz, -1)
|
| 817 |
+
global_doc_scores.scatter_reduce_(
|
| 818 |
+
dim=1, index=global_indices, src=local_doc_scores,
|
| 819 |
+
reduce="amax", include_self=True
|
| 820 |
+
)
|
| 821 |
+
del attn_scores, max_scores_per_chunk, local_doc_scores
|
| 822 |
+
|
| 823 |
+
# --- Phase 2: Top-K 选择 (使用 topk 替代 sort) ---
|
| 824 |
+
|
| 825 |
+
# 优化点:topk 比 sort 快得多
|
| 826 |
+
final_scores, batch_selected_doc_ids = torch.topk(global_doc_scores, k=min(self.model_config.doc_top_k, global_doc_scores.shape[1]), dim=1) # [bsz, k]
|
| 827 |
+
batch_selected_global_doc_ids = self.block_desc.create_global_doc_ids(batch_selected_doc_ids)
|
| 828 |
+
return final_scores, batch_selected_doc_ids, batch_selected_global_doc_ids
|
| 829 |
+
|
| 830 |
+
def prefill_stage2_1(self, layer_idx: int, query_states: torch.Tensor, query_mask: torch.Tensor):
|
| 831 |
+
# ... [Timer Start] ...
|
| 832 |
+
|
| 833 |
+
# 优化点:Mask可以稍后处���,先让 Query 进计算
|
| 834 |
+
|
| 835 |
+
bsz, num_heads, seq_len, head_dim = query_states.shape
|
| 836 |
+
num_kv_groups = self.num_key_value_groups
|
| 837 |
+
num_kv_heads = num_heads // num_kv_groups
|
| 838 |
+
min_val = torch.finfo(query_states.dtype).min
|
| 839 |
+
routing_mask = ~query_mask
|
| 840 |
+
q_lens = None
|
| 841 |
+
|
| 842 |
+
# 1. View 重塑 (Zero-copy)
|
| 843 |
+
# TODO: scaling 计算
|
| 844 |
+
scaling = self.scaling
|
| 845 |
+
query_states = query_states.view(bsz, num_kv_heads, num_kv_groups, seq_len, head_dim) * scaling
|
| 846 |
+
|
| 847 |
+
# 初始化分数表
|
| 848 |
+
# 严重注意:请确认 nr_docs 是否真的等于 max_doc_id + 1。
|
| 849 |
+
# 如果 pool_ids 是全局唯一的(例如加了 bias 100000),这里的 total_docs 必须能容纳最大的 ID。
|
| 850 |
+
total_docs = self.block_desc.nr_docs + 1
|
| 851 |
+
global_doc_scores = torch.full((bsz, total_docs), min_val, dtype=query_states.dtype, device=self.device)
|
| 852 |
+
|
| 853 |
+
# --- Phase 1: 打分 (移除 Query Batch Loop) ---
|
| 854 |
+
|
| 855 |
+
# 针对 A100,如果 bsz < 256,直接全量计算收益最高
|
| 856 |
+
# 我们直接遍历 slice,不再对 query 进行切片
|
| 857 |
+
|
| 858 |
+
for slice_desc, k_slice_t in zip(self.slice_desc, self.k_slices[layer_idx]):
|
| 859 |
+
# k_slice_t: [1, nhead, 1, hdim, chunk] (GPU)
|
| 860 |
+
|
| 861 |
+
# 1. Matmul (Compute Bound)
|
| 862 |
+
# [bsz, nhead, groups, seqlen, hdim] @ [1, nhead, 1, hdim, chunk]
|
| 863 |
+
# -> [bsz, nhead, groups, seqlen, chunk]
|
| 864 |
+
attn_scores = torch.matmul(query_states, k_slice_t)
|
| 865 |
+
|
| 866 |
+
# 2. Mask & Reduce (Memory Bound)
|
| 867 |
+
attn_scores.masked_fill_(routing_mask, min_val)
|
| 868 |
+
# Flatten & Reduce heads/seqlen
|
| 869 |
+
# flatten(1,2): [bsz, nhead*groups, seqlen, chunk]
|
| 870 |
+
# max(dim=2)[0]: [bsz, nhead*groups, chunk]
|
| 871 |
+
# max(dim=1)[0]: [bsz, chunk]
|
| 872 |
+
# max_scores_per_chunk = attn_scores.flatten(1, 2).max(dim=2)[0].max(dim=1)[0] # [bsz, chunk]
|
| 873 |
+
max_scores_per_chunk = attn_scores.flatten(1, 2)
|
| 874 |
+
|
| 875 |
+
# [bsz, nhead*groups, chunk]
|
| 876 |
+
if self.head_reduce_method == "max":
|
| 877 |
+
max_scores_per_chunk = max_scores_per_chunk.max(dim=1).values
|
| 878 |
+
elif self.head_reduce_method == "mean":
|
| 879 |
+
max_scores_per_chunk = max_scores_per_chunk.mean(dim=1)
|
| 880 |
+
else:
|
| 881 |
+
raise NotImplementedError(f"Unsupported head reduce method: {self.head_reduce_method}")
|
| 882 |
+
|
| 883 |
+
if self.query_reduce_method == "max":
|
| 884 |
+
max_scores_per_chunk = max_scores_per_chunk.max(dim=1).values
|
| 885 |
+
elif self.query_reduce_method == "mean":
|
| 886 |
+
# 1. 调整 mask 形状以匹配 max_scores_per_chunk
|
| 887 |
+
# [bsz, 1, seqlen, 1] -> [bsz, seqlen, 1]
|
| 888 |
+
mask = query_mask.squeeze(1)
|
| 889 |
+
|
| 890 |
+
# 2. 在求和前,将无效位置(min_val)归零
|
| 891 |
+
# 使用 torch.where 比乘法更安全,且能有效处理极小的 min_val
|
| 892 |
+
masked_sum = torch.where(mask, max_scores_per_chunk, torch.zeros_like(max_scores_per_chunk)).sum(dim=1)
|
| 893 |
+
|
| 894 |
+
# 3. 计算每个 sequence 中的有效 token 数量
|
| 895 |
+
# 防止除以 0,使用 clamp 或加一个极小值 eps
|
| 896 |
+
valid_counts = mask.sum(dim=1).clamp(min=1.0)
|
| 897 |
+
|
| 898 |
+
# 4. 得到最终均值 [bsz, chunk]
|
| 899 |
+
max_scores_per_chunk = masked_sum / valid_counts
|
| 900 |
+
elif self.query_reduce_method == "last":
|
| 901 |
+
if q_lens is None:
|
| 902 |
+
q_lens = query_mask.squeeze(3).squeeze(1).sum(dim=1).long()
|
| 903 |
+
|
| 904 |
+
# 边界保护: 如果 length 为 0 (异常情况), clamp 到 0
|
| 905 |
+
last_indices = (q_lens - 1).clamp(min=0)
|
| 906 |
+
|
| 907 |
+
# 3. 调整形状用于 gather
|
| 908 |
+
# [B, 1, C]
|
| 909 |
+
gather_idx = last_indices.view(bsz, 1, 1).expand(-1, 1, slice_desc.nr_chunks)
|
| 910 |
+
|
| 911 |
+
# 4. 提取
|
| 912 |
+
max_scores_per_chunk = max_scores_per_chunk.gather(1, gather_idx).squeeze(1)
|
| 913 |
+
else:
|
| 914 |
+
raise NotImplementedError(f"Unsupported query reduce method: {self.query_reduce_method}")
|
| 915 |
+
|
| 916 |
+
# 3. Local Scatter (Chunk -> Doc)
|
| 917 |
+
# slice_desc.local_doc_ids_0 已经在 GPU 上预计算好了
|
| 918 |
+
# [1, chunk] -> [bsz, chunk]
|
| 919 |
+
scatter_indices = slice_desc.local_doc_ids_0.expand(bsz, -1)
|
| 920 |
+
|
| 921 |
+
local_doc_scores = torch.full((bsz, slice_desc.nr_docs), -float('inf'), device=self.device, dtype=query_states.dtype)
|
| 922 |
+
if self.chunk_reduce_method == "max":
|
| 923 |
+
local_doc_scores.scatter_reduce_(
|
| 924 |
+
dim=1, index=scatter_indices, src=max_scores_per_chunk,
|
| 925 |
+
reduce="amax", include_self=True
|
| 926 |
+
)
|
| 927 |
+
|
| 928 |
+
elif self.chunk_reduce_method == "mean":
|
| 929 |
+
doc_sums = torch.zeros_like(local_doc_scores)
|
| 930 |
+
doc_sums = doc_sums.scatter_reduce(
|
| 931 |
+
dim=1, index=scatter_indices, src=max_scores_per_chunk,
|
| 932 |
+
reduce="sum", include_self=False
|
| 933 |
+
)
|
| 934 |
+
doc_counts = torch.zeros_like(local_doc_scores)
|
| 935 |
+
ones = torch.ones_like(max_scores_per_chunk)
|
| 936 |
+
doc_counts = doc_counts.scatter_reduce(
|
| 937 |
+
dim=1, index=scatter_indices, src=ones,
|
| 938 |
+
reduce="sum", include_self=False
|
| 939 |
+
)
|
| 940 |
+
doc_counts = doc_counts.clamp(min=1.0)
|
| 941 |
+
mean_scores = doc_sums / doc_counts
|
| 942 |
+
# TODO: 能不能不要重新产生一个local_doc_scores?
|
| 943 |
+
local_doc_scores = torch.where(doc_counts > 0, mean_scores, local_doc_scores)
|
| 944 |
+
|
| 945 |
+
del doc_sums, doc_counts, mean_scores
|
| 946 |
+
else:
|
| 947 |
+
raise ValueError(f"Invalid chunk reduction method: {self.chunk_reduce_method}")
|
| 948 |
+
|
| 949 |
+
# 4. Global Scatter (Local Doc -> Global Doc)
|
| 950 |
+
global_indices = slice_desc.original_doc_ids.expand(bsz, -1)
|
| 951 |
+
global_doc_scores.scatter_reduce_(
|
| 952 |
+
dim=1, index=global_indices, src=local_doc_scores,
|
| 953 |
+
reduce="amax", include_self=True
|
| 954 |
+
)
|
| 955 |
+
del attn_scores, max_scores_per_chunk, local_doc_scores
|
| 956 |
+
|
| 957 |
+
# --- Phase 2: Top-K 选择 (使用 topk 替代 sort) ---
|
| 958 |
+
|
| 959 |
+
# 优化点:topk 比 sort 快得多
|
| 960 |
+
final_scores, batch_selected_doc_ids = torch.topk(global_doc_scores, k=self.model_config.doc_top_k, dim=1) # [bsz, k]
|
| 961 |
+
|
| 962 |
+
required_doc_mask = torch.zeros(total_docs, dtype=torch.bool, device=self.device)
|
| 963 |
+
required_doc_mask.scatter_(0, batch_selected_doc_ids.flatten(), True)
|
| 964 |
+
|
| 965 |
+
if not required_doc_mask.any():
|
| 966 |
+
return None, None, None, None
|
| 967 |
+
|
| 968 |
+
# --- Phase 3: 异步并行提取 (Async Retrieval) ---
|
| 969 |
+
|
| 970 |
+
block = self.blocks[layer_idx]
|
| 971 |
+
pooled_doc_ids = self.block_desc.pool_ids # [num_chunks]
|
| 972 |
+
|
| 973 |
+
# 计算 Chunk Mask (GPU)
|
| 974 |
+
required_chunk_mask = required_doc_mask[pooled_doc_ids] # [num_chunks] Bool
|
| 975 |
+
|
| 976 |
+
# 确保先处理 K
|
| 977 |
+
k_selected = block.k[:, :, required_chunk_mask, :]
|
| 978 |
+
pooled_doc_ids_selected = self.block_desc.create_global_doc_ids(pooled_doc_ids[required_chunk_mask])
|
| 979 |
+
|
| 980 |
+
cpu_indices = torch.where(required_chunk_mask)[0].to("cpu")
|
| 981 |
+
if rk_selected.is_cpu:
|
| 982 |
+
rk_selected = block.rk[:, :, cpu_indices, :]
|
| 983 |
+
rk_selected = rk_selected.to(self.device, non_blocking=True)
|
| 984 |
+
else:
|
| 985 |
+
rk_selected = block.rk[:, :, required_chunk_mask, :]
|
| 986 |
+
|
| 987 |
+
v_selected = block.v[:, :, cpu_indices, :].to(self.device, non_blocking=True)
|
| 988 |
+
|
| 989 |
+
# 由于 K 和 IDs 用了 non_blocking,我们需要在使用它们之前确保传输完成
|
| 990 |
+
# 这里的 synchronize 确保 k_retrieved 和 pooled_doc_ids_retrieved 数据已就绪
|
| 991 |
+
torch.cuda.current_stream().synchronize()
|
| 992 |
+
|
| 993 |
+
return final_scores, k_selected, rk_selected, v_selected, pooled_doc_ids_selected
|
| 994 |
+
|
| 995 |
+
def gpu_select(self, scores, k, rk, v, pooled_doc_ids, total_docs):
|
| 996 |
+
final_scores, batch_selected_doc_ids = torch.topk(scores, k=self.model_config.doc_top_k, dim=1) # [bsz, k]
|
| 997 |
+
|
| 998 |
+
required_doc_mask = torch.zeros(total_docs, dtype=torch.bool, device=self.device)
|
| 999 |
+
required_doc_mask.scatter_(0, batch_selected_doc_ids.flatten(), True)
|
| 1000 |
+
|
| 1001 |
+
# 计算 Chunk Mask (GPU)
|
| 1002 |
+
required_chunk_mask = required_doc_mask[pooled_doc_ids] # [num_chunks] Bool
|
| 1003 |
+
|
| 1004 |
+
# 确保先处理 K
|
| 1005 |
+
k_selected = k[:, :, required_chunk_mask, :]
|
| 1006 |
+
pooled_doc_ids_selected = pooled_doc_ids[required_chunk_mask]
|
| 1007 |
+
rk_selected = rk[:, :, required_chunk_mask, :]
|
| 1008 |
+
v_selected = v[:, :, required_chunk_mask, :]
|
| 1009 |
+
|
| 1010 |
+
return final_scores, k_selected, rk_selected, v_selected, pooled_doc_ids_selected
|
| 1011 |
+
|
| 1012 |
+
|
| 1013 |
+
class MSAService(Memory, MemoryClientBase):
|
| 1014 |
+
def __init__(self,
|
| 1015 |
+
gpu_id: int,
|
| 1016 |
+
generate_config: GenerateConfig,
|
| 1017 |
+
model_config: ModelConfig,
|
| 1018 |
+
memory_config: MemoryConfig
|
| 1019 |
+
):
|
| 1020 |
+
self.world_size = generate_config.world
|
| 1021 |
+
|
| 1022 |
+
# 初始化 NCCL
|
| 1023 |
+
# 注意:这里假设是在单机多卡环境下运行,使用 localhost
|
| 1024 |
+
if "MASTER_ADDR" not in os.environ:
|
| 1025 |
+
os.environ["MASTER_ADDR"] = "localhost"
|
| 1026 |
+
if "MASTER_PORT" not in os.environ:
|
| 1027 |
+
os.environ["MASTER_PORT"] = "29500"
|
| 1028 |
+
|
| 1029 |
+
# 初始化进程组
|
| 1030 |
+
# timeout 设置稍长一点,防止初始化时某些进程慢导致超时
|
| 1031 |
+
dist.init_process_group(
|
| 1032 |
+
"nccl",
|
| 1033 |
+
rank=gpu_id,
|
| 1034 |
+
world_size=self.world_size
|
| 1035 |
+
)
|
| 1036 |
+
print(f"[GPU {gpu_id}] NCCL Initialized successfully.")
|
| 1037 |
+
|
| 1038 |
+
super().__init__(gpu_id, generate_config, model_config, memory_config)
|
| 1039 |
+
|
| 1040 |
+
self.generate_kwarg = self._create_generate_args()
|
| 1041 |
+
|
| 1042 |
+
def _create_generate_args(self):
|
| 1043 |
+
generate_kwarg = {
|
| 1044 |
+
"do_sample": True,
|
| 1045 |
+
"top_p": self.generate_config.top_p,
|
| 1046 |
+
"temperature": self.generate_config.temperature,
|
| 1047 |
+
"max_new_tokens": self.generate_config.max_generate_tokens,
|
| 1048 |
+
}
|
| 1049 |
+
if self.generate_config.temperature == 0.0:
|
| 1050 |
+
generate_kwarg["do_sample"] = False
|
| 1051 |
+
generate_kwarg["temperature"] = None
|
| 1052 |
+
generate_kwarg["top_p"] = None
|
| 1053 |
+
generate_kwarg["top_k"] = None
|
| 1054 |
+
return generate_kwarg
|
| 1055 |
+
|
| 1056 |
+
|
| 1057 |
+
def setup_memory_client(self, model):
|
| 1058 |
+
def _setup_module(module):
|
| 1059 |
+
if hasattr(module, 'set_memory_client') and callable(module.set_memory_client):
|
| 1060 |
+
try:
|
| 1061 |
+
module.set_memory_client(self)
|
| 1062 |
+
except Exception as e:
|
| 1063 |
+
print(f"✗ 调用 {module.__class__.__name__} 的 set_memory_client() 失败: {e}")
|
| 1064 |
+
|
| 1065 |
+
# 递归遍历所有模块
|
| 1066 |
+
for _, module in model.named_modules():
|
| 1067 |
+
_setup_module(module)
|
| 1068 |
+
|
| 1069 |
+
|
| 1070 |
+
def load_model(self):
|
| 1071 |
+
"""加载模型和tokenizer"""
|
| 1072 |
+
# 加载tokenizer
|
| 1073 |
+
self.tokenizer = AutoTokenizer.from_pretrained(self.model_config.model_path)
|
| 1074 |
+
|
| 1075 |
+
# 加载模型
|
| 1076 |
+
self.model = MSAForCausalLM.from_pretrained(
|
| 1077 |
+
self.model_config.model_path,
|
| 1078 |
+
use_cache=True,
|
| 1079 |
+
attn_implementation="flash_attention_2",
|
| 1080 |
+
torch_dtype="auto",
|
| 1081 |
+
device_map=self.device,
|
| 1082 |
+
)
|
| 1083 |
+
|
| 1084 |
+
self.model.eval()
|
| 1085 |
+
self.setup_memory_client(self.model)
|
| 1086 |
+
|
| 1087 |
+
|
| 1088 |
+
def _gather_querys(self, query_states: torch.Tensor, query_mask: torch.Tensor):
|
| 1089 |
+
"""处理变长 seqlen:填充到统一长度并 all_gather"""
|
| 1090 |
+
bsz, nhead, seqlen, hdim = query_states.shape
|
| 1091 |
+
num_kv_groups = self.num_key_value_groups
|
| 1092 |
+
num_kv_heads = nhead // num_kv_groups
|
| 1093 |
+
world_size = self.world_size
|
| 1094 |
+
device = self.device
|
| 1095 |
+
if self.scaling > 0:
|
| 1096 |
+
query_states = query_states * self.scaling
|
| 1097 |
+
|
| 1098 |
+
# --- Step 1: 准备全量 Query (分布式 All-Gather) ---
|
| 1099 |
+
m_for_search = query_mask.view(bsz, 1, 1, seqlen, 1)
|
| 1100 |
+
q_for_search = query_states.view(bsz, num_kv_heads, num_kv_groups, seqlen, hdim)
|
| 1101 |
+
|
| 1102 |
+
# --- Step 2: 处理变长 Seqlen (隐式同步处理) ---
|
| 1103 |
+
local_seqlen = torch.tensor([seqlen], device=device, dtype=torch.long)
|
| 1104 |
+
all_seqlens = [torch.empty(1, device=device, dtype=torch.long) for _ in range(world_size)]
|
| 1105 |
+
dist.all_gather(all_seqlens, local_seqlen)
|
| 1106 |
+
|
| 1107 |
+
# 此处 s.item() 确实会触发同步,但这是为了在 CPU 端分配 all_queries 的 List
|
| 1108 |
+
seqlens = [s.item() for s in all_seqlens]
|
| 1109 |
+
max_seqlen = max(seqlens)
|
| 1110 |
+
|
| 1111 |
+
# --- Step 3: 预分配连续内存进行 All-Gather (解决 Contiguous 问题) ---
|
| 1112 |
+
# 我们预先分配一个大的连续 Buffer,然后把 local 数据 copy 进去
|
| 1113 |
+
# 这样 gather 出来的数据天生就是连续的,不需要调 .contiguous()
|
| 1114 |
+
def get_padded_gather_list(tensor, target_seqlen):
|
| 1115 |
+
# 构造 padded 形状
|
| 1116 |
+
padded_shape = list(tensor.shape)
|
| 1117 |
+
padded_shape[3] = target_seqlen # 修改 seqlen 维度
|
| 1118 |
+
|
| 1119 |
+
# 分配全量列表
|
| 1120 |
+
gather_list = [torch.zeros(padded_shape, dtype=tensor.dtype, device=device) for _ in range(world_size)]
|
| 1121 |
+
|
| 1122 |
+
# 将本地数据 copy 到 gather_list 中对应的位置
|
| 1123 |
+
# 这里 slice 操作 + copy_ 比先 pad 再 gather 更节省临时显存
|
| 1124 |
+
gather_list[self.gpu_id][:, :, :, :seqlen, :].copy_(tensor)
|
| 1125 |
+
dist.all_gather(gather_list, gather_list[self.gpu_id])
|
| 1126 |
+
return gather_list
|
| 1127 |
+
|
| 1128 |
+
all_queries = get_padded_gather_list(q_for_search, max_seqlen)
|
| 1129 |
+
all_masks = get_padded_gather_list(m_for_search, max_seqlen)
|
| 1130 |
+
|
| 1131 |
+
return all_queries, all_masks, seqlens
|
| 1132 |
+
|
| 1133 |
+
def doc_query(self, query_states: torch.Tensor, query_mask: torch.Tensor, layer_idx: int):
|
| 1134 |
+
"""
|
| 1135 |
+
集成版:检索 + 跨卡提取 + 查表加速 + 结果归集
|
| 1136 |
+
返回:
|
| 1137 |
+
final_k, final_v: [H, Total_Selected_C, D] 格式,直接用于 Flash Attn 散射
|
| 1138 |
+
final_scores: [B, TopK] 文档检索分数
|
| 1139 |
+
num_selected_chunks_per_sample: [B] 每个 Batch 选中的 chunk 总数
|
| 1140 |
+
"""
|
| 1141 |
+
world_size = self.world_size
|
| 1142 |
+
bsz, nhead, seqlen, hdim = query_states.shape
|
| 1143 |
+
num_kv_groups = self.num_key_value_groups
|
| 1144 |
+
num_kv_heads = nhead // num_kv_groups
|
| 1145 |
+
device = self.device
|
| 1146 |
+
dtype = query_states.dtype
|
| 1147 |
+
top_k = self.model_config.doc_top_k
|
| 1148 |
+
|
| 1149 |
+
all_queries, all_masks, seqlens = self._gather_querys(query_states, query_mask)
|
| 1150 |
+
|
| 1151 |
+
# --- Step 2: 循环处理各个 Rank 的请求并本地打分 ---
|
| 1152 |
+
scores_list, combined_ids_list = [], []
|
| 1153 |
+
for target_rank in range(world_size):
|
| 1154 |
+
seqlen = seqlens[target_rank]
|
| 1155 |
+
q_batch = all_queries[target_rank][:, :, :seqlen, :]
|
| 1156 |
+
m_batch = all_masks[target_rank][:, :, :seqlen, :]
|
| 1157 |
+
# q_batch 来自 target_rank,我们需要在当前 GPU 上寻找最佳匹配
|
| 1158 |
+
l_scores, l_ids, g_ids = self.prefill_stage2(layer_idx, q_batch, m_batch)
|
| 1159 |
+
scores_list.append(l_scores) # [bsz, top_k]
|
| 1160 |
+
# 将 local_ids 和 global_ids 打包在最后一个维度,减少一次 all-to-all
|
| 1161 |
+
combined_ids_list.append(torch.stack([l_ids, g_ids], dim=-1))
|
| 1162 |
+
|
| 1163 |
+
# --- Step 3: 元数据交换 (All-to-All) ---
|
| 1164 |
+
# 发送:我为各 Rank 算的 Top-K;接收:各 Rank 为“我”算的 Top-K
|
| 1165 |
+
send_scores = torch.stack(scores_list, dim=0) # [world_size, bsz, top_k]
|
| 1166 |
+
send_combined_ids = torch.stack(combined_ids_list, dim=0) # [world_size, bsz, top_k, 2]
|
| 1167 |
+
|
| 1168 |
+
recv_scores = torch.empty_like(send_scores)
|
| 1169 |
+
recv_combined_ids = torch.empty_like(send_combined_ids)
|
| 1170 |
+
|
| 1171 |
+
dist.all_to_all_single(recv_scores, send_scores)
|
| 1172 |
+
dist.all_to_all_single(recv_combined_ids, send_combined_ids)
|
| 1173 |
+
|
| 1174 |
+
# --- Step 4: 全局 Top-K 决策 ---
|
| 1175 |
+
# shapes: [world_size, bsz, top_k] -> [bsz, world_size * top_k]
|
| 1176 |
+
candidate_scores = recv_scores.transpose(0, 1).reshape(bsz, -1)
|
| 1177 |
+
candidate_local_ids = recv_combined_ids[..., 0].transpose(0, 1).reshape(bsz, -1)
|
| 1178 |
+
candidate_global_ids = recv_combined_ids[..., 1].transpose(0, 1).reshape(bsz, -1)
|
| 1179 |
+
|
| 1180 |
+
origin_ranks = torch.arange(world_size, device=device).view(world_size, 1, 1).expand(-1, bsz, top_k) # [world_size, bsz, top_k]
|
| 1181 |
+
origin_ranks = origin_ranks.transpose(0, 1).reshape(bsz, -1) # [bsz, world_size * top_k]
|
| 1182 |
+
|
| 1183 |
+
# [bsz, top_k]
|
| 1184 |
+
final_scores, final_indices = torch.topk(candidate_scores, k=top_k, dim=1)
|
| 1185 |
+
winner_local_ids = candidate_local_ids.gather(1, final_indices)
|
| 1186 |
+
winner_global_ids = candidate_global_ids.gather(1, final_indices)
|
| 1187 |
+
winner_ranks = origin_ranks.gather(1, final_indices)
|
| 1188 |
+
|
| 1189 |
+
# 屏蔽掉无效分数
|
| 1190 |
+
valid_mask = final_scores > -1e9
|
| 1191 |
+
winner_local_ids = winner_local_ids.masked_fill(~valid_mask, -1)
|
| 1192 |
+
|
| 1193 |
+
# --- Step 5: 任务下发 (Request) ---
|
| 1194 |
+
# 告诉其他 Rank:我最终选了你哪几个 local_id
|
| 1195 |
+
task_mask = torch.arange(world_size, device=device).view(world_size, 1, 1)
|
| 1196 |
+
tasks_to_send = torch.where(winner_ranks == task_mask, winner_local_ids, -1)
|
| 1197 |
+
remote_tasks = torch.empty_like(tasks_to_send)
|
| 1198 |
+
dist.all_to_all_single(remote_tasks, tasks_to_send)
|
| 1199 |
+
remote_tasks_cpu = remote_tasks.to("cpu")
|
| 1200 |
+
|
| 1201 |
+
# --- Step 6: 提取 KV (核心:针对百万级数据的查表加速) ---
|
| 1202 |
+
|
| 1203 |
+
block = self.blocks[layer_idx]
|
| 1204 |
+
desc = self.block_desc
|
| 1205 |
+
|
| 1206 |
+
# 1. 预处理:收集每个 Rank 的请求元数据 (在 CPU 上完成,极快)
|
| 1207 |
+
all_rank_local_ids = []
|
| 1208 |
+
chunks_per_rank = [] # 记录每个 Rank 分得的 chunk 总数,用于后续拆分
|
| 1209 |
+
all_rank_u_lens = [] # 记录每个 Rank 选中的每个 doc 的长度
|
| 1210 |
+
|
| 1211 |
+
for r in range(world_size):
|
| 1212 |
+
requested_ids = remote_tasks_cpu[r]
|
| 1213 |
+
# 获取该 Rank 选中的唯一 local_doc_ids
|
| 1214 |
+
unique_req_ids = requested_ids[requested_ids != -1].unique()
|
| 1215 |
+
all_rank_local_ids.append(unique_req_ids)
|
| 1216 |
+
|
| 1217 |
+
if unique_req_ids.numel() > 0:
|
| 1218 |
+
u_lens = desc.doc_lens_cpu[unique_req_ids]
|
| 1219 |
+
all_rank_u_lens.append(u_lens)
|
| 1220 |
+
chunks_per_rank.append(u_lens.sum().item())
|
| 1221 |
+
else:
|
| 1222 |
+
all_rank_u_lens.append(torch.empty(0, dtype=torch.long))
|
| 1223 |
+
chunks_per_rank.append(0)
|
| 1224 |
+
|
| 1225 |
+
# 2. 构造全局批次索引 (One-shot Index Generation)
|
| 1226 |
+
flat_unique_ids = torch.cat(all_rank_local_ids)
|
| 1227 |
+
total_chunks_all_ranks = sum(chunks_per_rank)
|
| 1228 |
+
|
| 1229 |
+
# 直接在 fetch 过程中完成 K 和 V 的合并
|
| 1230 |
+
# 预分配用于 all-to-all 的连续显存
|
| 1231 |
+
# total_chunks 是当前 Rank 需要发给所有其他 Rank 的 chunk 总和
|
| 1232 |
+
send_kv_combined = torch.empty((total_chunks_all_ranks, 2, num_kv_heads, hdim), device=device, dtype=dtype)
|
| 1233 |
+
|
| 1234 |
+
if total_chunks_all_ranks > 0:
|
| 1235 |
+
# 批量获取所有选定文档的偏移和长度
|
| 1236 |
+
batch_u_offsets = desc.doc_offsets_cpu[flat_unique_ids]
|
| 1237 |
+
batch_u_lens = torch.cat(all_rank_u_lens)
|
| 1238 |
+
|
| 1239 |
+
# 向量化生成全局索引列表
|
| 1240 |
+
base_indices = torch.repeat_interleave(batch_u_offsets, batch_u_lens)
|
| 1241 |
+
inner_seq = torch.arange(total_chunks_all_ranks) - torch.repeat_interleave(
|
| 1242 |
+
torch.cumsum(batch_u_lens, dim=0) - batch_u_lens, batch_u_lens
|
| 1243 |
+
)
|
| 1244 |
+
all_cpu_indices = base_indices + inner_seq
|
| 1245 |
+
all_gpu_indices = all_cpu_indices.to(device, non_blocking=True)
|
| 1246 |
+
|
| 1247 |
+
# 3. 一次性提取并搬运 (核心性能提升点)
|
| 1248 |
+
# 由于 block.k/v 是 pinned,这一步是满速 DMA 异步传输
|
| 1249 |
+
# [1, H, C_all, D]
|
| 1250 |
+
def fetch(tensor, slot_idx):
|
| 1251 |
+
# tensor: [1, H, C, D] -> index_select -> [1, H, Sel_C, D]
|
| 1252 |
+
if tensor.is_cpu:
|
| 1253 |
+
selected = tensor.index_select(2, all_cpu_indices).to(device, non_blocking=True)
|
| 1254 |
+
else:
|
| 1255 |
+
selected = tensor.index_select(2, all_gpu_indices)
|
| 1256 |
+
send_kv_combined[:, slot_idx, :, :].copy_(selected.squeeze(0).transpose(0, 1), non_blocking=True)
|
| 1257 |
+
|
| 1258 |
+
fetch(block.k, 0)
|
| 1259 |
+
fetch(block.v, 1)
|
| 1260 |
+
|
| 1261 |
+
# 批量处理全局 Doc ID 映射
|
| 1262 |
+
all_g_ids_rep = desc.doc_ids_cpu[flat_unique_ids].repeat_interleave(batch_u_lens).to(device, non_blocking=True)
|
| 1263 |
+
else:
|
| 1264 |
+
# 全量空处理
|
| 1265 |
+
all_g_ids_rep = torch.empty(0, device=device, dtype=torch.long)
|
| 1266 |
+
|
| 1267 |
+
send_num_chunks = torch.tensor(chunks_per_rank, device=device)
|
| 1268 |
+
|
| 1269 |
+
# --- Step 7: 变长数据交换 (All-to-All) ---
|
| 1270 |
+
recv_num_chunks = torch.zeros_like(send_num_chunks)
|
| 1271 |
+
dist.all_to_all_single(recv_num_chunks, send_num_chunks)
|
| 1272 |
+
torch.cuda.current_stream().synchronize() # 确保 fetch(CPU) 已完成同步
|
| 1273 |
+
|
| 1274 |
+
total_recv = recv_num_chunks.sum().item()
|
| 1275 |
+
recv_kv_flat = torch.empty(total_recv, 2, num_kv_heads, hdim, dtype=dtype, device=device)
|
| 1276 |
+
recv_g_ids_flat = torch.empty(total_recv, dtype=torch.long, device=device)
|
| 1277 |
+
|
| 1278 |
+
in_splits = send_num_chunks.tolist()
|
| 1279 |
+
out_splits = recv_num_chunks.tolist()
|
| 1280 |
+
|
| 1281 |
+
dist.all_to_all_single(recv_kv_flat, send_kv_combined, out_splits, in_splits)
|
| 1282 |
+
dist.all_to_all_single(recv_g_ids_flat, all_g_ids_rep, out_splits, in_splits)
|
| 1283 |
+
torch.cuda.current_stream().synchronize()
|
| 1284 |
+
|
| 1285 |
+
# --- Step 8: 后期处理集成 (按 Batch 重排归集) ---
|
| 1286 |
+
# 构造匹配矩阵 [B, TopK, Total_Recv_Chunks]
|
| 1287 |
+
# 利用广播找到每个 Batch Item 选中的 chunk
|
| 1288 |
+
# TODO: 这里可能会爆显存,后续考虑分块处理
|
| 1289 |
+
match_matrix = (winner_global_ids.unsqueeze(-1) == recv_g_ids_flat.unsqueeze(0).unsqueeze(0))
|
| 1290 |
+
mask_per_batch = match_matrix.any(dim=1) # [B, Total_Recv_Chunks]
|
| 1291 |
+
|
| 1292 |
+
# 假设 recv_k_flat 形状为 [C, 1, H, D]
|
| 1293 |
+
# 1. 调整维度,使 B 和 C 排在最前面,方便 mask 索引
|
| 1294 |
+
# unsqueeze(0) -> [1, C, 1, H, D]
|
| 1295 |
+
# expand(bsz, ...) -> [bsz, C, 1, H, D]
|
| 1296 |
+
candidate_k_exp = recv_kv_flat[:,0:1, :, :].unsqueeze(0).expand(bsz, -1, -1, -1, -1)
|
| 1297 |
+
candidate_v_exp = recv_kv_flat[:,1:2, :, :].unsqueeze(0).expand(bsz, -1, -1, -1, -1)
|
| 1298 |
+
|
| 1299 |
+
# 2. 利用布尔索引提取。
|
| 1300 |
+
# candidate_k_exp[mask_per_batch] 会根据 [B, C] 的 True 位置提取对应的 [1, H, D]
|
| 1301 |
+
# 结果形状: [Total_Selected_In_Batch, 1, H, D]
|
| 1302 |
+
final_k_raw = candidate_k_exp[mask_per_batch]
|
| 1303 |
+
final_v_raw = candidate_v_exp[mask_per_batch]
|
| 1304 |
+
|
| 1305 |
+
# 3. 统计数量(这部分没问题)
|
| 1306 |
+
num_selected_chunks_per_sample = mask_per_batch.sum(dim=1)
|
| 1307 |
+
|
| 1308 |
+
# 4. 适配输出格式 [H, Total_Selected_C, D]
|
| 1309 |
+
# 先 squeeze(1) 去掉那个大小为 1 的维度 -> [Total_Selected, H, D]
|
| 1310 |
+
# 再 permute(1, 0, 2) 交换 Total_Selected 和 H -> [H, Total_Selected, D]
|
| 1311 |
+
final_k_to_scatter = final_k_raw.squeeze(1).permute(1, 0, 2).contiguous()
|
| 1312 |
+
final_v_to_scatter = final_v_raw.squeeze(1).permute(1, 0, 2).contiguous()
|
| 1313 |
+
|
| 1314 |
+
return final_k_to_scatter, final_v_to_scatter, final_scores, num_selected_chunks_per_sample, winner_global_ids
|
| 1315 |
+
|
| 1316 |
+
def generate(self, req: GenerateRequest) -> GenerateResponse:
|
| 1317 |
+
# 转换为tensor
|
| 1318 |
+
input_ids_tensor = torch.LongTensor(req.input_ids).to(self.device)
|
| 1319 |
+
attention_mask_tensor = torch.LongTensor(req.attention_mask).to(self.device)
|
| 1320 |
+
doc_ids_tensor = torch.LongTensor(req.doc_ids).to(self.device)
|
| 1321 |
+
position_ids_tensor = torch.LongTensor(req.positions).to(self.device)
|
| 1322 |
+
|
| 1323 |
+
past_key_values: CustomDynamicCache = create_cache()
|
| 1324 |
+
past_key_values.meta["require_recall_topk"] = req.require_recall_topk
|
| 1325 |
+
past_key_values.meta["qa_mode"] = self.generate_config.qa_mode
|
| 1326 |
+
past_key_values.meta["max_generate_tokens"] = self.generate_config.max_generate_tokens
|
| 1327 |
+
if self.generate_config.qa_mode:
|
| 1328 |
+
past_key_values.meta["tokenizer"] = self.tokenizer
|
| 1329 |
+
past_key_values.meta["idx_to_doc"] = self.idx_to_doc
|
| 1330 |
+
past_key_values.meta["pattern"] = r"\[(\d+)\]"
|
| 1331 |
+
past_key_values.meta["response_string"] = ['' for _ in range(len(req.input_ids))]
|
| 1332 |
+
|
| 1333 |
+
|
| 1334 |
+
# 准备generate kwargs
|
| 1335 |
+
for layer_idx in range(self.msa_model_config.num_hidden_layers):
|
| 1336 |
+
past_key_values.record_kwargs(layer_idx, {"stage": "prefill_stage2"})
|
| 1337 |
+
|
| 1338 |
+
generate_kwargs = self.generate_kwarg.copy()
|
| 1339 |
+
generate_kwargs["past_key_values"] = past_key_values
|
| 1340 |
+
|
| 1341 |
+
inputs = {
|
| 1342 |
+
"input_ids": input_ids_tensor,
|
| 1343 |
+
"attention_mask": attention_mask_tensor,
|
| 1344 |
+
"doc_ids": doc_ids_tensor,
|
| 1345 |
+
"use_cache": True,
|
| 1346 |
+
"position_ids": position_ids_tensor,
|
| 1347 |
+
}
|
| 1348 |
+
|
| 1349 |
+
with torch.no_grad():
|
| 1350 |
+
generated_ids = self.model.generate(
|
| 1351 |
+
**inputs,
|
| 1352 |
+
**generate_kwargs,
|
| 1353 |
+
)
|
| 1354 |
+
generated_seqs = self.tokenizer.batch_decode(generated_ids, skip_special_token=True)
|
| 1355 |
+
if req.require_recall_topk:
|
| 1356 |
+
recall_topks = {layer: args['recall_topk'] for layer, args in past_key_values.cache_kwargs.items() if 'recall_topk' in args}
|
| 1357 |
+
else:
|
| 1358 |
+
recall_topks = None
|
| 1359 |
+
|
| 1360 |
+
return GenerateResponse(msg_id=req.msg_id,
|
| 1361 |
+
seq_id=req.seq_id,
|
| 1362 |
+
gpu_id=self.gpu_id,
|
| 1363 |
+
generated_texts=generated_seqs,
|
| 1364 |
+
recall_topk=recall_topks)
|
| 1365 |
+
|
| 1366 |
+
|
| 1367 |
+
|
| 1368 |
+
class MSAEngine:
|
| 1369 |
+
def __init__(self,
|
| 1370 |
+
generate_config: GenerateConfig,
|
| 1371 |
+
model_config: ModelConfig,
|
| 1372 |
+
memory_config: MemoryConfig
|
| 1373 |
+
):
|
| 1374 |
+
|
| 1375 |
+
# NCCL require global cuda devices, so wen change the device setting here
|
| 1376 |
+
# user should set CUDA_VISIBLE_DEVICES envionment vars if they wish to select specific devices
|
| 1377 |
+
generate_config.devices = list(range(torch.cuda.device_count()))
|
| 1378 |
+
|
| 1379 |
+
self.device_ids = generate_config.devices
|
| 1380 |
+
self.world_size = generate_config.world
|
| 1381 |
+
self.generate_config = generate_config
|
| 1382 |
+
self.model_config = model_config
|
| 1383 |
+
self.memory_config = memory_config
|
| 1384 |
+
|
| 1385 |
+
self.worker_processes = []
|
| 1386 |
+
self.worker_qs: Dict[int, mp.Queue] = {}
|
| 1387 |
+
self.response_queue = mp.Queue()
|
| 1388 |
+
self.request_queue = mp.Queue()
|
| 1389 |
+
self.start_workers()
|
| 1390 |
+
|
| 1391 |
+
self.msg_id = 0
|
| 1392 |
+
self.nr_active_req = 0
|
| 1393 |
+
self.limiter = RequestLimiter(4)
|
| 1394 |
+
self.requests: Dict[int, GenerateStub] = {} # msgid -> stub
|
| 1395 |
+
self.lock = threading.Lock()
|
| 1396 |
+
self.sync_event = threading.Event()
|
| 1397 |
+
self.sync_rsp = None
|
| 1398 |
+
|
| 1399 |
+
self.initialize()
|
| 1400 |
+
|
| 1401 |
+
def initialize(self):
|
| 1402 |
+
"""初始化Memory服务"""
|
| 1403 |
+
print("Initializing Memory service...")
|
| 1404 |
+
|
| 1405 |
+
# 加载tokenizer
|
| 1406 |
+
self.tokenizer = AutoTokenizer.from_pretrained(self.model_config.model_path)
|
| 1407 |
+
self._prepare_template()
|
| 1408 |
+
|
| 1409 |
+
# 加载memory文件
|
| 1410 |
+
self._load_memory_file()
|
| 1411 |
+
|
| 1412 |
+
# 先等待所有的worker都就绪了再发送 blocks
|
| 1413 |
+
self._wait_ready_signal()
|
| 1414 |
+
self._process_buckets()
|
| 1415 |
+
|
| 1416 |
+
# 必须要在bucket处理完成后才启动线程
|
| 1417 |
+
self.running = True
|
| 1418 |
+
self.recv_rsp_thread = threading.Thread(target=self.receive_response, args=(), daemon=True)
|
| 1419 |
+
self.recv_rsp_thread.start()
|
| 1420 |
+
|
| 1421 |
+
self.recv_req_thread = threading.Thread(target=self.receive_request, args=(), daemon=True)
|
| 1422 |
+
self.recv_req_thread.start()
|
| 1423 |
+
|
| 1424 |
+
print("MSAEngine initialized")
|
| 1425 |
+
|
| 1426 |
+
|
| 1427 |
+
def _worker_all_gather(self, cmdname: str, timeout=600):
|
| 1428 |
+
collected_count = 0
|
| 1429 |
+
responses = []
|
| 1430 |
+
|
| 1431 |
+
while collected_count < len(self.worker_qs):
|
| 1432 |
+
try:
|
| 1433 |
+
response = self.response_queue.get(timeout=timeout)
|
| 1434 |
+
assert response.name == cmdname, f"worker all gather got response {response.name} expect {cmdname}"
|
| 1435 |
+
responses.append(response)
|
| 1436 |
+
collected_count += 1
|
| 1437 |
+
|
| 1438 |
+
except queue.Empty:
|
| 1439 |
+
continue
|
| 1440 |
+
|
| 1441 |
+
responses.sort(key=lambda x: x.gpu_id)
|
| 1442 |
+
return responses
|
| 1443 |
+
|
| 1444 |
+
def _wait_ready_signal(self):
|
| 1445 |
+
collected_count = 0
|
| 1446 |
+
|
| 1447 |
+
while collected_count < len(self.worker_qs):
|
| 1448 |
+
ProtocolConstants.expect(self.response_queue, MEMORY_WORKER_READY)
|
| 1449 |
+
collected_count += 1
|
| 1450 |
+
|
| 1451 |
+
@staticmethod
|
| 1452 |
+
def balanced_bucket_partition(docs: List[Document], n_buckets: int) -> List[List[Document]]:
|
| 1453 |
+
"""
|
| 1454 |
+
全局均衡划分算法:先全局分blocks,再分配到buckets,
|
| 1455 |
+
这里并不需要每个 block 必须是最多max_chunk_per_block个 chunk,
|
| 1456 |
+
而是根据max_chunk_per_block这个数大致决定分成多少 block(GPU 个数的倍数),然后将这些
|
| 1457 |
+
block 做得尽可能 chunk 均衡,最后分配给各个 GPU
|
| 1458 |
+
|
| 1459 |
+
Returns:
|
| 1460 |
+
List[List[Document]]: 每个 bucket 分配到的文档列表
|
| 1461 |
+
"""
|
| 1462 |
+
|
| 1463 |
+
# 全局 blocks 数量
|
| 1464 |
+
bucket_docs: List[List[Document]] = [[] for _ in range(n_buckets)] # 每个 bucket 所含的 doc 列表
|
| 1465 |
+
bucket_chunk_count = [0] * n_buckets
|
| 1466 |
+
|
| 1467 |
+
def next_bucket():
|
| 1468 |
+
return bucket_chunk_count.index(min(bucket_chunk_count))
|
| 1469 |
+
|
| 1470 |
+
for doc in docs:
|
| 1471 |
+
bucket_idx = next_bucket()
|
| 1472 |
+
bucket_docs[bucket_idx].append(doc)
|
| 1473 |
+
bucket_chunk_count[bucket_idx] += doc.num_chunks
|
| 1474 |
+
|
| 1475 |
+
|
| 1476 |
+
return bucket_docs
|
| 1477 |
+
|
| 1478 |
+
def _sort_reference(self, docs: List[str]) -> Tuple[List[str], List[int], List[int], List[List[int]]]:
|
| 1479 |
+
"""
|
| 1480 |
+
对文档进行重排序并且分 bucket和block,分配到一个 GPU 上的文档被��为 bucket,
|
| 1481 |
+
bucket 和 bucket 之间的 chunk 数量尽可能均衡
|
| 1482 |
+
Args:
|
| 1483 |
+
docs: 文档数据列表
|
| 1484 |
+
|
| 1485 |
+
Returns:
|
| 1486 |
+
List[List[Document]]: 每个 bucket 分配到的文档列表
|
| 1487 |
+
"""
|
| 1488 |
+
|
| 1489 |
+
|
| 1490 |
+
documents: List[Document] = []
|
| 1491 |
+
kernel_sz = self.model_config.pooling_kernel_size
|
| 1492 |
+
for idx, doc in enumerate(docs): # idx 是 block 内部的 doc 索引
|
| 1493 |
+
new_doc, doc_inputs = compose_input(doc, idx, self.tokenizer)
|
| 1494 |
+
length = len(doc_inputs["input_ids"])
|
| 1495 |
+
num_chunks = (length + kernel_sz - 1) // kernel_sz
|
| 1496 |
+
# print(f"doc {idx} str {len(doc)} id length: {length}, num_chunks: {num_chunks}")
|
| 1497 |
+
|
| 1498 |
+
documents.append(Document(doc=doc, doc_id=idx, num_chunks=num_chunks))
|
| 1499 |
+
|
| 1500 |
+
return MSAEngine.balanced_bucket_partition(documents, self.generate_config.world)
|
| 1501 |
+
|
| 1502 |
+
def _load_memory_file(self):
|
| 1503 |
+
"""加载memory文件"""
|
| 1504 |
+
print(f"Loading memory file: {self.memory_config.memory_file_path}")
|
| 1505 |
+
|
| 1506 |
+
if self.memory_config.memory_file_path.endswith('.json'):
|
| 1507 |
+
with open(self.memory_config.memory_file_path, 'r') as f:
|
| 1508 |
+
data = json.load(f)
|
| 1509 |
+
# 处理JSON格式数据
|
| 1510 |
+
# 这里需要根据实际的JSON格式来实现
|
| 1511 |
+
context_list = data
|
| 1512 |
+
|
| 1513 |
+
elif self.memory_config.memory_file_path.endswith('.pkl'):
|
| 1514 |
+
with open(self.memory_config.memory_file_path, 'rb') as f:
|
| 1515 |
+
reference_metas = pickle.load(f)
|
| 1516 |
+
|
| 1517 |
+
context_list = list(reference_metas)
|
| 1518 |
+
|
| 1519 |
+
else:
|
| 1520 |
+
raise ValueError(f"Unsupported file format: {self.memory_config.memory_file_path}")
|
| 1521 |
+
|
| 1522 |
+
# debug function:
|
| 1523 |
+
# when env DEBUG_SCALE_MEMORY is set by MemoryFilePath:ScaleFactor
|
| 1524 |
+
# we will read from MemoryFilePath and scale it to (ScaleFactor+1) times of memory documents
|
| 1525 |
+
# if it is set by ScaleFactor, without MemoryFilePath, we will use self.memory_config.memory_file_path instead
|
| 1526 |
+
scale_path = os.environ.get("DEBUG_SCALE_MEMORY", "")
|
| 1527 |
+
if scale_path:
|
| 1528 |
+
g = scale_path.split(":")
|
| 1529 |
+
scale = 0
|
| 1530 |
+
if len(g) == 2:
|
| 1531 |
+
scale_path = g[0]
|
| 1532 |
+
scale=int(g[1])
|
| 1533 |
+
elif len(g) == 1:
|
| 1534 |
+
scale_path = self.memory_config.memory_file_path
|
| 1535 |
+
scale=int(g[0])
|
| 1536 |
+
else:
|
| 1537 |
+
print("invalid scaling, ignore: ", scale_path)
|
| 1538 |
+
|
| 1539 |
+
if scale > 0:
|
| 1540 |
+
print(f">>>>>>>>>>>>>>>>>>>> DEBUG: scale from query file {scale_path} with scale {scale}", )
|
| 1541 |
+
from src.utils.scale import scale_memory
|
| 1542 |
+
scale_memory(context_list, scale_path, scale=scale)
|
| 1543 |
+
|
| 1544 |
+
self.buckets: List[List[Document]] = self._sort_reference(context_list)
|
| 1545 |
+
self.docs: List[Document] = []
|
| 1546 |
+
for bucket in self.buckets:
|
| 1547 |
+
self.docs.extend(bucket)
|
| 1548 |
+
chunks = sum(doc.num_chunks for doc in self.docs)
|
| 1549 |
+
|
| 1550 |
+
print(f"Loaded {len(self.docs)} memory document, total chunks: {chunks}")
|
| 1551 |
+
for idx, bucket in enumerate(self.buckets):
|
| 1552 |
+
print(f"bucket {idx} contains {len(bucket)} documents, {sum(doc.num_chunks for doc in bucket)} chunks")
|
| 1553 |
+
|
| 1554 |
+
def _process_buckets(self):
|
| 1555 |
+
"""
|
| 1556 |
+
发送 blocks 到各 worker
|
| 1557 |
+
"""
|
| 1558 |
+
|
| 1559 |
+
idx_to_doc = self.get_idx_to_doc()
|
| 1560 |
+
for idx, gpu_id in enumerate(self.generate_config.devices):
|
| 1561 |
+
ProtocolConstants.send(self.worker_qs[gpu_id],
|
| 1562 |
+
MEMORY_WORKER_BLOCKS,
|
| 1563 |
+
data=self.buckets[idx],
|
| 1564 |
+
block=False)
|
| 1565 |
+
ProtocolConstants.send(self.worker_qs[gpu_id],
|
| 1566 |
+
MEMORY_WORKER_IDX_TO_DOC,
|
| 1567 |
+
data=idx_to_doc,
|
| 1568 |
+
block=False)
|
| 1569 |
+
|
| 1570 |
+
# 拿到所有的 doc id bias然后累积更新各 worker
|
| 1571 |
+
rsps: List[ReportDocID] = self._worker_all_gather( "report_doc_id", timeout=None)
|
| 1572 |
+
|
| 1573 |
+
# pooled_doc_id_bias = 0
|
| 1574 |
+
# for rsp in rsps:
|
| 1575 |
+
# self.worker_qs[rsp.gpu_id].put(SetDocIDCmd(pooled_doc_id_bias=pooled_doc_id_bias))
|
| 1576 |
+
# pooled_doc_id_bias += rsp.pooled_doc_id_bias
|
| 1577 |
+
# _ = self._worker_all_gather("set_doc_id")
|
| 1578 |
+
|
| 1579 |
+
def receive_request(self):
|
| 1580 |
+
while self.running:
|
| 1581 |
+
try:
|
| 1582 |
+
gpu_id, res = self.request_queue.get(timeout=3)
|
| 1583 |
+
print(f"Received response from GPU {gpu_id}")
|
| 1584 |
+
except queue.Empty:
|
| 1585 |
+
continue
|
| 1586 |
+
|
| 1587 |
+
def get_idx_to_doc(self):
|
| 1588 |
+
return {doc.doc_id: doc.doc for doc in self.docs}
|
| 1589 |
+
|
| 1590 |
+
@staticmethod
|
| 1591 |
+
def service_main(gpu_id: int,
|
| 1592 |
+
request_queue: mp.Queue,
|
| 1593 |
+
response_queue: mp.Queue,
|
| 1594 |
+
generate_config: GenerateConfig,
|
| 1595 |
+
model_config: ModelConfig,
|
| 1596 |
+
memory_config: MemoryConfig
|
| 1597 |
+
):
|
| 1598 |
+
# 1. 初始化 Service (包含 NCCL Init)
|
| 1599 |
+
service = MSAService(gpu_id, generate_config, model_config, memory_config)
|
| 1600 |
+
|
| 1601 |
+
print(f"MSAService-{gpu_id} is ready")
|
| 1602 |
+
|
| 1603 |
+
# notify parent I'm ready
|
| 1604 |
+
ProtocolConstants.send(response_queue, MEMORY_WORKER_READY, block=False)
|
| 1605 |
+
|
| 1606 |
+
# wait parent to give me blocks to process
|
| 1607 |
+
docs: List[Document] = ProtocolConstants.expect(request_queue, MEMORY_WORKER_BLOCKS)
|
| 1608 |
+
idx_to_doc: Dict[int, str] = ProtocolConstants.expect(request_queue, MEMORY_WORKER_IDX_TO_DOC)
|
| 1609 |
+
service.save_idx_to_doc(idx_to_doc)
|
| 1610 |
+
|
| 1611 |
+
service.generate_blocks(docs)
|
| 1612 |
+
del docs
|
| 1613 |
+
torch.cuda.empty_cache()
|
| 1614 |
+
|
| 1615 |
+
service.load_model()
|
| 1616 |
+
|
| 1617 |
+
# tell Memory my doc id bias
|
| 1618 |
+
response_queue.put(ReportDocID(gpu_id=gpu_id, pooled_doc_id_bias=service.get_max_local_pool_doc_id()), block=False)
|
| 1619 |
+
|
| 1620 |
+
|
| 1621 |
+
while True:
|
| 1622 |
+
try:
|
| 1623 |
+
|
| 1624 |
+
cmd: CmdBase = request_queue.get()
|
| 1625 |
+
# 检查是否是终止信号
|
| 1626 |
+
if cmd is None:
|
| 1627 |
+
if dist.is_initialized():
|
| 1628 |
+
dist.destroy_process_group()
|
| 1629 |
+
return
|
| 1630 |
+
|
| 1631 |
+
elif cmd.name == "generate_request":
|
| 1632 |
+
rsp = service.generate(cmd)
|
| 1633 |
+
response_queue.put(rsp)
|
| 1634 |
+
# torch.cuda.empty_cache() # make perf worse
|
| 1635 |
+
|
| 1636 |
+
else:
|
| 1637 |
+
print(f"GPU_ID {gpu_id} recv unknown cmd {cmd.name}")
|
| 1638 |
+
|
| 1639 |
+
except Exception as e:
|
| 1640 |
+
print(f"[subprocess {gpu_id}] error: {e}")
|
| 1641 |
+
import traceback
|
| 1642 |
+
traceback.print_exc()
|
| 1643 |
+
|
| 1644 |
+
|
| 1645 |
+
def __enter__(self):
|
| 1646 |
+
return self
|
| 1647 |
+
|
| 1648 |
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
| 1649 |
+
self.stop_workers()
|
| 1650 |
+
return False # 不抑制异常
|
| 1651 |
+
|
| 1652 |
+
|
| 1653 |
+
def start_workers(self):
|
| 1654 |
+
# 设置启动方式,CUDA 必须用 spawn
|
| 1655 |
+
mp.set_start_method('spawn', force=True)
|
| 1656 |
+
|
| 1657 |
+
for gpu_id in self.generate_config.devices:
|
| 1658 |
+
request_queue = mp.Queue()
|
| 1659 |
+
process = mp.Process(
|
| 1660 |
+
target=MSAEngine.service_main,
|
| 1661 |
+
args=(gpu_id, request_queue, self.response_queue, self.generate_config, self.model_config, self.memory_config),
|
| 1662 |
+
name=f"MSAService-{gpu_id}"
|
| 1663 |
+
)
|
| 1664 |
+
process.start()
|
| 1665 |
+
self.worker_processes.append(process)
|
| 1666 |
+
self.worker_qs[gpu_id] = request_queue
|
| 1667 |
+
|
| 1668 |
+
def stop_workers(self):
|
| 1669 |
+
print("MSAEngine stop all services")
|
| 1670 |
+
self.running = False
|
| 1671 |
+
|
| 1672 |
+
for q in self.worker_qs.values():
|
| 1673 |
+
q.put(None)
|
| 1674 |
+
|
| 1675 |
+
self.worker_qs = {}
|
| 1676 |
+
|
| 1677 |
+
# 等待所有worker退出
|
| 1678 |
+
for process in self.worker_processes:
|
| 1679 |
+
process.join()
|
| 1680 |
+
self.worker_processes = []
|
| 1681 |
+
|
| 1682 |
+
self.recv_rsp_thread.join()
|
| 1683 |
+
self.recv_rsp_thread = None
|
| 1684 |
+
|
| 1685 |
+
self.recv_req_thread.join()
|
| 1686 |
+
self.recv_req_thread = None
|
| 1687 |
+
|
| 1688 |
+
def _validate_inputs(self, input_ids: List[List[int]]):
|
| 1689 |
+
if any(len(sub) == 0 for sub in input_ids):
|
| 1690 |
+
raise ValueError(f"empty query is not acceptable")
|
| 1691 |
+
|
| 1692 |
+
if self.generate_config.max_seq_len > 0:
|
| 1693 |
+
seqlen = sum(sum(sub) for sub in input_ids)
|
| 1694 |
+
if seqlen > self.generate_config.max_seq_len:
|
| 1695 |
+
raise ValueError(f"total sequence length {seqlen} exceeds limitation {self.generate_config.max_seq_len}")
|
| 1696 |
+
|
| 1697 |
+
if self.generate_config.max_query_seq_len > 0:
|
| 1698 |
+
max_seq_len = max(sum(sub) for sub in input_ids)
|
| 1699 |
+
if max_seq_len > self.generate_config.max_query_seq_len:
|
| 1700 |
+
raise ValueError(f"max query sequence length {max_seq_len} exceeds limitation {self.generate_config.max_query_seq_len}")
|
| 1701 |
+
|
| 1702 |
+
def _prepare_template(self):
|
| 1703 |
+
response_head = "<|im_start|>"
|
| 1704 |
+
response_head_inputs = self.tokenizer(response_head, add_special_tokens=False)
|
| 1705 |
+
response_head_input_ids = response_head_inputs["input_ids"]
|
| 1706 |
+
response_head_attention_mask = response_head_inputs["attention_mask"]
|
| 1707 |
+
|
| 1708 |
+
pad_token = self.tokenizer.pad_token
|
| 1709 |
+
pad_token_id = self.tokenizer.pad_token_id
|
| 1710 |
+
prompt_template = self.generate_config.template["prompt"].replace("{prompt}", pad_token)
|
| 1711 |
+
prompt_template_inputs = self.tokenizer(prompt_template, add_special_tokens=False)
|
| 1712 |
+
prompt_template_input_ids = prompt_template_inputs["input_ids"]
|
| 1713 |
+
prompt_template_attention_mask = prompt_template_inputs["attention_mask"]
|
| 1714 |
+
pad_index = prompt_template_input_ids.index(pad_token_id)
|
| 1715 |
+
template_tail_input_ids = prompt_template_input_ids[pad_index+1:]
|
| 1716 |
+
template_tail_attention_mask = prompt_template_attention_mask[pad_index+1:]
|
| 1717 |
+
|
| 1718 |
+
self.prompt_tail_input_ids = template_tail_input_ids + response_head_input_ids
|
| 1719 |
+
self.prompt_tail_attention_mask = template_tail_attention_mask + response_head_attention_mask
|
| 1720 |
+
self.tail_doc_ids = [-2] * (len(template_tail_input_ids)) + [-1] * len(response_head_input_ids)
|
| 1721 |
+
|
| 1722 |
+
def _apply_template(self, prompt):
|
| 1723 |
+
# question = "\n请根据以上历史文档信息,回答问题\n\n" + prompt + "\n" + f"请返回与问题有关的所有文档\n"
|
| 1724 |
+
# question = "\n请根据以上历史文档信息,回答问题\n\n" + prompt + "\n" + f"请返回与问题有关的1个文档\n"
|
| 1725 |
+
question = "\nPlease answer the question based on the above historical document information\n\n" + prompt + "\n" + f"Please return all documents related to the question\n"
|
| 1726 |
+
prompt_inputs = self.tokenizer(question, add_special_tokens=False)
|
| 1727 |
+
prompt_inputs["input_ids"], prompt_inputs["attention_mask"]
|
| 1728 |
+
|
| 1729 |
+
input_ids = prompt_inputs["input_ids"] + self.prompt_tail_input_ids
|
| 1730 |
+
attention_mask = prompt_inputs["attention_mask"] + self.prompt_tail_attention_mask
|
| 1731 |
+
doc_ids = [0] * (len(prompt_inputs["input_ids"])) + self.tail_doc_ids
|
| 1732 |
+
return input_ids, attention_mask, doc_ids
|
| 1733 |
+
|
| 1734 |
+
def _apply_template_regenerate(self, prompt):
|
| 1735 |
+
question = prompt.split("<regenerate>")[1]
|
| 1736 |
+
|
| 1737 |
+
prompt_inputs = self.tokenizer(question, add_special_tokens=False)
|
| 1738 |
+
prompt_inputs["input_ids"], prompt_inputs["attention_mask"]
|
| 1739 |
+
|
| 1740 |
+
input_ids = prompt_inputs["input_ids"]
|
| 1741 |
+
attention_mask = prompt_inputs["attention_mask"]
|
| 1742 |
+
|
| 1743 |
+
# Build doc_ids: 0 for specific regions, -1 for the rest
|
| 1744 |
+
doc_ids = [-1] * len(prompt_inputs["input_ids"])
|
| 1745 |
+
|
| 1746 |
+
# Region 1: tokens between the last "]<|object_ref_end|>[" and the first "<|object_ref_end|>" after it
|
| 1747 |
+
last_marker_start = question.rfind("]<|object_ref_end|>[")
|
| 1748 |
+
if last_marker_start != -1:
|
| 1749 |
+
search_start = last_marker_start + len("]<|object_ref_end|>[") - 1
|
| 1750 |
+
next_marker = question.find("<|object_ref_end|>", search_start)
|
| 1751 |
+
if next_marker != -1:
|
| 1752 |
+
region1_text = question[search_start:next_marker]
|
| 1753 |
+
region1_tokens = self.tokenizer(region1_text, add_special_tokens=False)["input_ids"]
|
| 1754 |
+
prefix_before_region1 = question[:search_start]
|
| 1755 |
+
prefix_tokens_len = len(self.tokenizer(prefix_before_region1, add_special_tokens=False)["input_ids"])
|
| 1756 |
+
for i in range(prefix_tokens_len, prefix_tokens_len + len(region1_tokens)):
|
| 1757 |
+
if i < len(doc_ids):
|
| 1758 |
+
doc_ids[i] = 0
|
| 1759 |
+
|
| 1760 |
+
# Region 2: "Please return all documents related to the question" and everything before it
|
| 1761 |
+
anchor = "Please return all documents related to the question"
|
| 1762 |
+
anchor_pos = question.find(anchor)
|
| 1763 |
+
if anchor_pos != -1:
|
| 1764 |
+
prefix_with_anchor = question[:anchor_pos + len(anchor)]
|
| 1765 |
+
prefix_token_len = len(self.tokenizer(prefix_with_anchor, add_special_tokens=False)["input_ids"])
|
| 1766 |
+
for i in range(prefix_token_len):
|
| 1767 |
+
if i < len(doc_ids):
|
| 1768 |
+
doc_ids[i] = 0
|
| 1769 |
+
|
| 1770 |
+
return input_ids, attention_mask, doc_ids
|
| 1771 |
+
|
| 1772 |
+
def receive_response(self):
|
| 1773 |
+
while self.running:
|
| 1774 |
+
try:
|
| 1775 |
+
rsp: GenerateResponse = self.response_queue.get(timeout=3)
|
| 1776 |
+
|
| 1777 |
+
final_stub = None
|
| 1778 |
+
with self.limiter.lock:
|
| 1779 |
+
assert rsp.msg_id in self.requests
|
| 1780 |
+
stub = self.requests[rsp.msg_id]
|
| 1781 |
+
stub.responses.append(rsp)
|
| 1782 |
+
# print(f"Request {rsp.msg_id} got {len(stub.responses)} responses")
|
| 1783 |
+
if len(stub.responses) == self.world_size:
|
| 1784 |
+
final_stub = self.requests.pop(rsp.msg_id)
|
| 1785 |
+
|
| 1786 |
+
if final_stub:
|
| 1787 |
+
# print("Release")
|
| 1788 |
+
self.limiter.release()
|
| 1789 |
+
final_stub.respond()
|
| 1790 |
+
|
| 1791 |
+
except queue.Empty:
|
| 1792 |
+
continue
|
| 1793 |
+
|
| 1794 |
+
def default_callback(self, texts: List[str], recall_topk, userdata):
|
| 1795 |
+
self.sync_rsp = (texts, recall_topk, userdata)
|
| 1796 |
+
self.sync_event.set()
|
| 1797 |
+
|
| 1798 |
+
def generate(self,
|
| 1799 |
+
prompts: Union[str, List[str]],
|
| 1800 |
+
userdata=None,
|
| 1801 |
+
require_recall_topk=False,
|
| 1802 |
+
callback: GenerateReceiver=None):
|
| 1803 |
+
|
| 1804 |
+
if isinstance(prompts, str):
|
| 1805 |
+
prompts = [prompts]
|
| 1806 |
+
|
| 1807 |
+
world = self.generate_config.world
|
| 1808 |
+
bsz = (len(prompts) + world - 1) // world
|
| 1809 |
+
if self.generate_config.max_batch_size > 0 and bsz > self.generate_config.max_batch_size:
|
| 1810 |
+
raise ValueError(f"Batch size exceeds maximum allowed batch size {self.generate_config.max_batch_size}")
|
| 1811 |
+
# Important:
|
| 1812 |
+
# 如果输入的 prompts 不够 world size,则需要 pad,我们的方法是用最后一个prompt 填充
|
| 1813 |
+
pads = bsz * world - len(prompts)
|
| 1814 |
+
if pads > 0:
|
| 1815 |
+
prompts += [prompts[-1]] * pads
|
| 1816 |
+
|
| 1817 |
+
batch_input_ids = []
|
| 1818 |
+
batch_attention_mask = []
|
| 1819 |
+
batch_doc_ids = []
|
| 1820 |
+
batch_positions = []
|
| 1821 |
+
|
| 1822 |
+
# template + pooled docs
|
| 1823 |
+
current_position = 3 + self.model_config.doc_top_k
|
| 1824 |
+
|
| 1825 |
+
for prompt in prompts:
|
| 1826 |
+
if "<regenerate>" in prompt:
|
| 1827 |
+
input_ids, attention_mask, doc_ids = self._apply_template_regenerate(prompt)
|
| 1828 |
+
else:
|
| 1829 |
+
input_ids, attention_mask, doc_ids = self._apply_template(prompt)
|
| 1830 |
+
batch_input_ids.append(input_ids)
|
| 1831 |
+
batch_attention_mask.append(attention_mask)
|
| 1832 |
+
batch_doc_ids.append(doc_ids)
|
| 1833 |
+
|
| 1834 |
+
# 计算position ids
|
| 1835 |
+
position_ids = [current_position + i for i in range(len(input_ids))]
|
| 1836 |
+
batch_positions.append(position_ids)
|
| 1837 |
+
# current_position += len(input_ids) # 为什么要累计长度?
|
| 1838 |
+
|
| 1839 |
+
# Pad sequences to the same length
|
| 1840 |
+
padded_input_ids = []
|
| 1841 |
+
padded_attention_mask = []
|
| 1842 |
+
padded_doc_ids = []
|
| 1843 |
+
padded_position_ids = []
|
| 1844 |
+
|
| 1845 |
+
pad_token_id = self.tokenizer.pad_token_id if self.tokenizer.pad_token_id else self.tokenizer.eos_token_id
|
| 1846 |
+
# 使用 doc_end_id 作为 doc_ids 的 padding 值
|
| 1847 |
+
# doc_end_id = self.tokenizer("<|im_end|>", add_special_tokens=False)["input_ids"][0]
|
| 1848 |
+
|
| 1849 |
+
for i in range(len(batch_input_ids)):
|
| 1850 |
+
if i % bsz == 0:
|
| 1851 |
+
max_length = max(len(ids) for ids in batch_input_ids[i:i+bsz])
|
| 1852 |
+
input_ids = batch_input_ids[i]
|
| 1853 |
+
attention_mask = batch_attention_mask[i]
|
| 1854 |
+
doc_ids_seq = batch_doc_ids[i]
|
| 1855 |
+
position_ids = batch_positions[i]
|
| 1856 |
+
|
| 1857 |
+
# Pad to max_length
|
| 1858 |
+
pad_length = max_length - len(input_ids)
|
| 1859 |
+
# 改成left pad,方便position_ids迭代,可直接与自定义的generate兼容
|
| 1860 |
+
if pad_length > 0:
|
| 1861 |
+
input_ids = [pad_token_id] * pad_length + input_ids
|
| 1862 |
+
attention_mask = [0] * pad_length + attention_mask
|
| 1863 |
+
doc_ids_seq = [0] * pad_length + doc_ids_seq
|
| 1864 |
+
position_ids = [position_ids[0]] * pad_length + position_ids
|
| 1865 |
+
|
| 1866 |
+
padded_input_ids.append(input_ids)
|
| 1867 |
+
padded_attention_mask.append(attention_mask)
|
| 1868 |
+
padded_doc_ids.append(doc_ids_seq)
|
| 1869 |
+
padded_position_ids.append(position_ids)
|
| 1870 |
+
|
| 1871 |
+
# 只检查每个 batch 的第一个 query 就好
|
| 1872 |
+
self._validate_inputs(padded_input_ids[0:-1:bsz])
|
| 1873 |
+
|
| 1874 |
+
|
| 1875 |
+
# TODO: when callback is None
|
| 1876 |
+
self.limiter.acquire()
|
| 1877 |
+
|
| 1878 |
+
sync = callback is None
|
| 1879 |
+
if callback is None:
|
| 1880 |
+
callback = self.default_callback
|
| 1881 |
+
|
| 1882 |
+
self.msg_id += 1
|
| 1883 |
+
with self.limiter.lock:
|
| 1884 |
+
self.requests[self.msg_id] = GenerateStub(msg_id=self.msg_id,
|
| 1885 |
+
userdata=userdata,
|
| 1886 |
+
nr_dummy=pads,
|
| 1887 |
+
responses=[],
|
| 1888 |
+
callback=callback)
|
| 1889 |
+
|
| 1890 |
+
for idx, gpu_id in enumerate(self.generate_config.devices):
|
| 1891 |
+
start, end = idx * bsz, (idx+1) * bsz
|
| 1892 |
+
req = GenerateRequest(msg_id=self.msg_id,
|
| 1893 |
+
seq_id=idx,
|
| 1894 |
+
prompts=prompts[start:end],
|
| 1895 |
+
input_ids=padded_input_ids[start:end],
|
| 1896 |
+
attention_mask=padded_attention_mask[start:end],
|
| 1897 |
+
doc_ids=padded_doc_ids[start:end],
|
| 1898 |
+
positions=padded_position_ids[start:end],
|
| 1899 |
+
require_recall_topk=require_recall_topk)
|
| 1900 |
+
# print(f"send GPU{gpu_id} batch {len(req.input_ids)}")
|
| 1901 |
+
self.worker_qs[gpu_id].put(req, block=False)
|
| 1902 |
+
|
| 1903 |
+
if sync:
|
| 1904 |
+
self.sync_event.wait()
|
| 1905 |
+
self.sync_event.clear()
|
| 1906 |
+
return self.sync_rsp
|
| 1907 |
+
|
| 1908 |
+
|
| 1909 |
+
|
| 1910 |
+
|
| 1911 |
+
|
src/prefill.py
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
import os
|
| 3 |
+
from typing import List, Dict
|
| 4 |
+
from dataclasses import dataclass
|
| 5 |
+
import multiprocessing as mp
|
| 6 |
+
import pathlib
|
| 7 |
+
import torch
|
| 8 |
+
|
| 9 |
+
from transformers import AutoTokenizer, BitsAndBytesConfig
|
| 10 |
+
|
| 11 |
+
project_path = pathlib.Path(__file__).parent.parent
|
| 12 |
+
sys.path.append(str(project_path))
|
| 13 |
+
|
| 14 |
+
from src.utils.gpu_worker import GpuWorker
|
| 15 |
+
from src.utils.template import QWEN3_TEMPLATE, QWEN3_INSTRUCT_TEMPLATE
|
| 16 |
+
from src.utils.cache import copy_kv_cache_to_device, CustomDynamicCacheOnCPU
|
| 17 |
+
from src.msa.model import MSAForCausalLM
|
| 18 |
+
from src.utils.gpu_worker import GpuWorker
|
| 19 |
+
from src.utils.tools import compose_input
|
| 20 |
+
from src.types import Document, ProtocolConstants
|
| 21 |
+
|
| 22 |
+
@dataclass
|
| 23 |
+
class BlockModelInput:
|
| 24 |
+
doc_input_ids: torch.Tensor
|
| 25 |
+
doc_attention_mask: torch.Tensor
|
| 26 |
+
doc_ids: torch.Tensor
|
| 27 |
+
position_ids: torch.Tensor
|
| 28 |
+
num_chunks: int
|
| 29 |
+
chunk_sizes: List[int]
|
| 30 |
+
|
| 31 |
+
PREFILL_WORKER_READY = "PREFILL_WORKER_READY"
|
| 32 |
+
PREFILL_WORKER_CLOSE = "PREFILL_WORKER_CLOSE"
|
| 33 |
+
PREFILL_WORKER_MEMORY_DOCS = "PREFILL_WORKER_MEMORY_DOCS"
|
| 34 |
+
PREFILL_WORKER_NUM_CHUNKS_REPORT = "PREFILL_WORKER_NUM_CHUNKS_REPORT"
|
| 35 |
+
PREFILL_WORKER_META = "PREFILL_WORKER_META"
|
| 36 |
+
|
| 37 |
+
class PrefillStage1Worker(GpuWorker):
|
| 38 |
+
"""Memory工作进程"""
|
| 39 |
+
|
| 40 |
+
def __init__(self, gpu_id: int, model_path: str, template: dict,
|
| 41 |
+
pooling_kernel_size: int, envs: dict):
|
| 42 |
+
"""
|
| 43 |
+
该 worker 被MemoryWorker创建并仅执行prefill stage 1获取block 的kv cache
|
| 44 |
+
"""
|
| 45 |
+
super().__init__(gpu_id, envs)
|
| 46 |
+
self.model_path = model_path
|
| 47 |
+
self.pooling_kernel_size = pooling_kernel_size
|
| 48 |
+
|
| 49 |
+
self._load_model()
|
| 50 |
+
self.model_config = self.model.config
|
| 51 |
+
|
| 52 |
+
self.template_id = -2
|
| 53 |
+
self._prepare_template(template)
|
| 54 |
+
|
| 55 |
+
def num_model_layers(self):
|
| 56 |
+
return self.model_config.num_hidden_layers
|
| 57 |
+
|
| 58 |
+
def _load_model(self):
|
| 59 |
+
"""加载模型和tokenizer"""
|
| 60 |
+
# 加载tokenizer
|
| 61 |
+
self.tokenizer = AutoTokenizer.from_pretrained(self.model_path)
|
| 62 |
+
|
| 63 |
+
# 加载模型
|
| 64 |
+
self.model = MSAForCausalLM.from_pretrained(
|
| 65 |
+
self.model_path,
|
| 66 |
+
use_cache=True,
|
| 67 |
+
attn_implementation="flash_attention_2",
|
| 68 |
+
torch_dtype="bfloat16",
|
| 69 |
+
device_map=self.device,
|
| 70 |
+
)
|
| 71 |
+
self.model.eval()
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
@staticmethod
|
| 75 |
+
def split_docs(docs: List[Document], block_size: int):
|
| 76 |
+
sub_blocks = []
|
| 77 |
+
curr_block = []
|
| 78 |
+
sz = 0
|
| 79 |
+
for doc in docs:
|
| 80 |
+
chunks = doc.num_chunks
|
| 81 |
+
if sz + chunks > block_size and curr_block:
|
| 82 |
+
sub_blocks.append(curr_block)
|
| 83 |
+
sz = 0
|
| 84 |
+
curr_block = []
|
| 85 |
+
curr_block.append(doc)
|
| 86 |
+
sz += chunks
|
| 87 |
+
|
| 88 |
+
if curr_block:
|
| 89 |
+
sub_blocks.append(curr_block)
|
| 90 |
+
|
| 91 |
+
return sub_blocks
|
| 92 |
+
|
| 93 |
+
@staticmethod
|
| 94 |
+
def wait_for_ready(q: mp.Queue):
|
| 95 |
+
ProtocolConstants.expect(q, PREFILL_WORKER_READY)
|
| 96 |
+
|
| 97 |
+
@staticmethod
|
| 98 |
+
def close_worker(q: mp.Queue):
|
| 99 |
+
ProtocolConstants.send(q, PREFILL_WORKER_CLOSE, block=True)
|
| 100 |
+
|
| 101 |
+
@staticmethod
|
| 102 |
+
def send_documents(q: mp.Queue, docs):
|
| 103 |
+
ProtocolConstants.send(q,
|
| 104 |
+
PREFILL_WORKER_MEMORY_DOCS,
|
| 105 |
+
data=docs,
|
| 106 |
+
block=False)
|
| 107 |
+
@staticmethod
|
| 108 |
+
def recv_meta(q: mp.Queue):
|
| 109 |
+
return ProtocolConstants.expect(q, PREFILL_WORKER_META)
|
| 110 |
+
|
| 111 |
+
@staticmethod
|
| 112 |
+
def prefill_worker_main(gpu_id: int, request_queue: mp.Queue, response_queue: mp.Queue,
|
| 113 |
+
model_path: str, template: Dict,
|
| 114 |
+
pooling_kernel_size: int, block_size: int, envs):
|
| 115 |
+
|
| 116 |
+
# print(f"prefill worker {gpu_id} started")
|
| 117 |
+
worker = PrefillStage1Worker(gpu_id, model_path, template, pooling_kernel_size, envs)
|
| 118 |
+
|
| 119 |
+
# notify parent I'm ready
|
| 120 |
+
ProtocolConstants.send(response_queue, PREFILL_WORKER_READY, block=False)
|
| 121 |
+
|
| 122 |
+
docs: List[Document] = ProtocolConstants.expect(request_queue, PREFILL_WORKER_MEMORY_DOCS)
|
| 123 |
+
try:
|
| 124 |
+
for block in PrefillStage1Worker.split_docs(docs, block_size):
|
| 125 |
+
meta = worker.inference(block)
|
| 126 |
+
|
| 127 |
+
# send to master worker process and continue, DO NOT block
|
| 128 |
+
ProtocolConstants.send(response_queue,
|
| 129 |
+
PREFILL_WORKER_META,
|
| 130 |
+
data=meta,
|
| 131 |
+
block=False)
|
| 132 |
+
|
| 133 |
+
except Exception as e:
|
| 134 |
+
print(f"[子进程 {gpu_id}] 发生错误: {e}")
|
| 135 |
+
import traceback
|
| 136 |
+
traceback.print_exc()
|
| 137 |
+
|
| 138 |
+
# wait for exit signal
|
| 139 |
+
ProtocolConstants.expect(request_queue, PREFILL_WORKER_CLOSE)
|
| 140 |
+
print(f"prefill worker {gpu_id} ended")
|
| 141 |
+
|
| 142 |
+
def inference(self, block: List[Document]):
|
| 143 |
+
"""
|
| 144 |
+
处理memory block
|
| 145 |
+
|
| 146 |
+
Args:
|
| 147 |
+
memory_block: 分配给此GPU的memory block
|
| 148 |
+
|
| 149 |
+
"""
|
| 150 |
+
model_input = self._prepare_block_inputs(block)
|
| 151 |
+
|
| 152 |
+
kv_meta = self._inference(model_input)
|
| 153 |
+
kv_meta['nr_docs'] = len(block)
|
| 154 |
+
kv_meta['doc_ids'] = [item.doc_id for item in block]
|
| 155 |
+
kv_meta['nr_chunks'] = [item.num_chunks for item in block]
|
| 156 |
+
|
| 157 |
+
return kv_meta
|
| 158 |
+
|
| 159 |
+
def _prepare_template(self, template: Dict) -> Dict:
|
| 160 |
+
"""
|
| 161 |
+
重新加载单个memory block
|
| 162 |
+
完整复制eval_anything_v2_batch.py中reload_memory的逻辑
|
| 163 |
+
|
| 164 |
+
Args:
|
| 165 |
+
block: memory block数据 [(doc_id, doc_str), ...]
|
| 166 |
+
template: 模板字典
|
| 167 |
+
|
| 168 |
+
Returns:
|
| 169 |
+
KV cache元数据
|
| 170 |
+
"""
|
| 171 |
+
# 获取模板信息
|
| 172 |
+
self.pad_token = self.tokenizer.pad_token
|
| 173 |
+
self.pad_token_id = self.tokenizer.pad_token_id
|
| 174 |
+
self.doc_end_id = self.tokenizer("<|im_end|>", add_special_tokens=False)["input_ids"]
|
| 175 |
+
|
| 176 |
+
prompt_template = template["prompt"].replace("{prompt}", self.pad_token)
|
| 177 |
+
prompt_template_inputs = self.tokenizer(prompt_template, add_special_tokens=False)
|
| 178 |
+
self.prompt_template_input_ids = prompt_template_inputs["input_ids"]
|
| 179 |
+
self.prompt_template_attention_mask = prompt_template_inputs["attention_mask"]
|
| 180 |
+
|
| 181 |
+
self.pad_index = self.prompt_template_input_ids.index(self.pad_token_id)
|
| 182 |
+
self.template_head_input_ids = self.prompt_template_input_ids[:self.pad_index]
|
| 183 |
+
self.template_head_attention_mask = self.prompt_template_attention_mask[:self.pad_index]
|
| 184 |
+
self.template_tail_input_ids = self.prompt_template_input_ids[self.pad_index+1:]
|
| 185 |
+
self.template_tail_attention_mask = self.prompt_template_attention_mask[self.pad_index+1:]
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
def _prepare_block_inputs(self, block: List[Document]) -> Dict:
|
| 189 |
+
"""
|
| 190 |
+
重新加载单个memory block
|
| 191 |
+
完整复制eval_anything_v2_batch.py中reload_memory的逻辑
|
| 192 |
+
|
| 193 |
+
Args:
|
| 194 |
+
block: memory block数据 [(doc_id, doc_str), ...]
|
| 195 |
+
template: 模板字典
|
| 196 |
+
|
| 197 |
+
Returns:
|
| 198 |
+
KV cache元数据
|
| 199 |
+
"""
|
| 200 |
+
|
| 201 |
+
# 准备文档数据
|
| 202 |
+
|
| 203 |
+
# block starts with template head
|
| 204 |
+
doc_ids = [self.template_id] * len(self.template_head_input_ids)
|
| 205 |
+
doc_input_ids = [i for i in self.template_head_input_ids ]
|
| 206 |
+
doc_attention_mask = [i for i in self.template_head_attention_mask]
|
| 207 |
+
position_ids = [i for i in range(self.pad_index)]
|
| 208 |
+
|
| 209 |
+
chunk_sizes = [] # 记录每一份文档占用的 chunk 数量
|
| 210 |
+
|
| 211 |
+
for doc_idx, item in enumerate(block):
|
| 212 |
+
doc_id, doc, pre_calculated_num_chunk = item.doc_id, item.doc, item.num_chunks
|
| 213 |
+
new_doc, doc_inputs = compose_input(doc, doc_id, self.tokenizer)
|
| 214 |
+
# print(f"inference {doc_id+1}: {new_doc}")
|
| 215 |
+
# 此处必须使用 1 起始的doc_idx,因为此 id 是用于生成 pool doc ID 的
|
| 216 |
+
# 注意不可以使用doc_id,doc_id只能用于嵌入在语料中,使得 generate 时能生成出来,
|
| 217 |
+
# 而pool doc ID的作用却是用于标注产生的 kv cache chunks 和文档的对应关系
|
| 218 |
+
# 所以每次 stage1 的推理doc id 都是从 1 开始的
|
| 219 |
+
temp_doc_ids = [doc_idx+1] * len(doc_inputs["input_ids"])
|
| 220 |
+
temp_doc_input_ids = doc_inputs["input_ids"]
|
| 221 |
+
temp_doc_attention_mask = doc_inputs["attention_mask"]
|
| 222 |
+
length = len(temp_doc_input_ids)
|
| 223 |
+
temp_position_ids = [i for i in range(length)]
|
| 224 |
+
|
| 225 |
+
chunk_size = (len(temp_doc_ids) + self.pooling_kernel_size - 1) // self.pooling_kernel_size
|
| 226 |
+
chunk_sizes.append(chunk_size)
|
| 227 |
+
assert chunk_size == pre_calculated_num_chunk, f"pre calculated chunk {pre_calculated_num_chunk} got {chunk_size}, doc str {len(doc)} id len {length}/{len(temp_doc_ids)}: [{doc_id}] <{doc}>"
|
| 228 |
+
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
doc_ids.extend(temp_doc_ids)
|
| 232 |
+
doc_input_ids.extend(temp_doc_input_ids)
|
| 233 |
+
doc_attention_mask.extend(temp_doc_attention_mask)
|
| 234 |
+
position_ids.extend(temp_position_ids)
|
| 235 |
+
|
| 236 |
+
input_ids_tensor = torch.LongTensor([doc_input_ids])
|
| 237 |
+
attention_mask_tensor = torch.LongTensor([doc_attention_mask])
|
| 238 |
+
doc_ids_tensor = torch.LongTensor([doc_ids])
|
| 239 |
+
position_ids_tensor = torch.LongTensor([position_ids])
|
| 240 |
+
|
| 241 |
+
return BlockModelInput(doc_input_ids=input_ids_tensor,
|
| 242 |
+
doc_attention_mask=attention_mask_tensor,
|
| 243 |
+
doc_ids=doc_ids_tensor,
|
| 244 |
+
position_ids=position_ids_tensor,
|
| 245 |
+
num_chunks=sum(chunk_sizes),
|
| 246 |
+
chunk_sizes=chunk_sizes)
|
| 247 |
+
|
| 248 |
+
def _inference(self, model_input: BlockModelInput) -> Dict:
|
| 249 |
+
"""
|
| 250 |
+
重新加载单个memory block
|
| 251 |
+
完整复制eval_anything_v2_batch.py中reload_memory的逻辑
|
| 252 |
+
|
| 253 |
+
Args:
|
| 254 |
+
block: memory block数据 [(doc_id, doc_str), ...]
|
| 255 |
+
template: 模板字典
|
| 256 |
+
|
| 257 |
+
Returns:
|
| 258 |
+
KV cache元数据
|
| 259 |
+
"""
|
| 260 |
+
|
| 261 |
+
# 转换为tensor
|
| 262 |
+
input_ids_tensor = model_input.doc_input_ids.to(self.device)
|
| 263 |
+
attention_mask_tensor = model_input.doc_attention_mask.to(self.device)
|
| 264 |
+
doc_ids_tensor = model_input.doc_ids.to(self.device)
|
| 265 |
+
position_ids_tensor = model_input.position_ids.to(self.device)
|
| 266 |
+
|
| 267 |
+
# 创建past_key_values,这里我们会直接将 kvcache 和其他的 tensor 全部保存在 cpu 上
|
| 268 |
+
past_key_values = CustomDynamicCacheOnCPU()
|
| 269 |
+
for layer_idx in range(self.num_model_layers()):
|
| 270 |
+
past_key_values.record_kwargs(layer_idx, {"stage": "prefill_stage1"})
|
| 271 |
+
|
| 272 |
+
# 执行prefill
|
| 273 |
+
# TODO: 多 batch 会更快
|
| 274 |
+
with torch.no_grad():
|
| 275 |
+
if True:
|
| 276 |
+
"""我们的数据太大了,会导致model lm_head产生大量的显存堆积,所以直接用model.model避过去"""
|
| 277 |
+
outputs = self.model.model(
|
| 278 |
+
input_ids=input_ids_tensor,
|
| 279 |
+
attention_mask=attention_mask_tensor,
|
| 280 |
+
position_ids=position_ids_tensor,
|
| 281 |
+
past_key_values=past_key_values,
|
| 282 |
+
use_cache=True,
|
| 283 |
+
output_attentions=False,
|
| 284 |
+
output_hidden_states=False,
|
| 285 |
+
output_docs_score=False,
|
| 286 |
+
doc_ids=doc_ids_tensor,
|
| 287 |
+
)
|
| 288 |
+
else:
|
| 289 |
+
outputs = self.model(
|
| 290 |
+
input_ids=input_ids_tensor,
|
| 291 |
+
attention_mask=attention_mask_tensor,
|
| 292 |
+
doc_ids=doc_ids_tensor,
|
| 293 |
+
use_cache=True,
|
| 294 |
+
position_ids=position_ids_tensor,
|
| 295 |
+
past_key_values=past_key_values,
|
| 296 |
+
)
|
| 297 |
+
|
| 298 |
+
torch.cuda.empty_cache()
|
| 299 |
+
# 构建返回的元数据
|
| 300 |
+
kvcache_meta = {
|
| 301 |
+
"chunk_sizes": model_input.chunk_sizes,
|
| 302 |
+
"past_key_values": outputs.past_key_values,
|
| 303 |
+
}
|
| 304 |
+
|
| 305 |
+
return kvcache_meta
|
| 306 |
+
|
src/types.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from dataclasses import dataclass
|
| 2 |
+
import multiprocessing as mp
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
@dataclass
|
| 6 |
+
class Document:
|
| 7 |
+
doc: str = ""
|
| 8 |
+
doc_id: int = 0
|
| 9 |
+
num_chunks: int = 0
|
| 10 |
+
|
| 11 |
+
class ProtocolConstants:
|
| 12 |
+
|
| 13 |
+
@staticmethod
|
| 14 |
+
def expect(q: mp.Queue, constant):
|
| 15 |
+
k, v = q.get()
|
| 16 |
+
assert k == constant, f"expect {constant} but got {k}"
|
| 17 |
+
return v
|
| 18 |
+
|
| 19 |
+
@staticmethod
|
| 20 |
+
def send(q: mp.Queue, constant, data=None, block=True):
|
| 21 |
+
q.put((constant, data), block=block)
|
src/utils/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .common import (
|
| 2 |
+
patch_transformer_logging,
|
| 3 |
+
print_trainable_params,
|
| 4 |
+
draw_bounding_boxes,
|
| 5 |
+
post_process_generate_ids,
|
| 6 |
+
decode_generate_ids,
|
| 7 |
+
smart_tokenizer_and_embedding_resize,
|
| 8 |
+
)
|
| 9 |
+
from .callbacks import (
|
| 10 |
+
ModeltimeCallback,
|
| 11 |
+
ProfCallback,
|
| 12 |
+
SacredCallback,
|
| 13 |
+
ModelEvalCallback,
|
| 14 |
+
DSEmptyCacheCallback
|
| 15 |
+
)
|
src/utils/cache.py
ADDED
|
@@ -0,0 +1,512 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
缓存模块
|
| 4 |
+
包含CustomDynamicCache和CustomQuantizeDynamicCache两个类
|
| 5 |
+
从eval_anything_v2_batch.py中提取出来,实现模块化管理
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import copy
|
| 9 |
+
from typing import Optional, Tuple
|
| 10 |
+
import torch
|
| 11 |
+
from transformers.cache_utils import DynamicCache, QuantoQuantizedCache, QuantizedCacheConfig
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class CustomDynamicCache(DynamicCache):
|
| 15 |
+
"""
|
| 16 |
+
自定义动态缓存类
|
| 17 |
+
扩展标准的DynamicCache,添加额外的元数据存储和查询功能
|
| 18 |
+
"""
|
| 19 |
+
def __init__(self, _distributed_cache_data=None):
|
| 20 |
+
super().__init__(_distributed_cache_data)
|
| 21 |
+
self.cache_kwargs = {}
|
| 22 |
+
self.group_cache = {}
|
| 23 |
+
self.meta = {}
|
| 24 |
+
self.router_key_cache = []
|
| 25 |
+
|
| 26 |
+
def clear_kvcache(self):
|
| 27 |
+
self.key_cache = []
|
| 28 |
+
self.value_cache = []
|
| 29 |
+
|
| 30 |
+
def record_kwargs(self, layer_idx, kwargs):
|
| 31 |
+
"""
|
| 32 |
+
记录层的元数据信息
|
| 33 |
+
|
| 34 |
+
Args:
|
| 35 |
+
layer_idx: 层索引
|
| 36 |
+
kwargs: 包含路由层信息的字典
|
| 37 |
+
"""
|
| 38 |
+
if layer_idx in self.cache_kwargs:
|
| 39 |
+
self.cache_kwargs[layer_idx].update(kwargs)
|
| 40 |
+
else:
|
| 41 |
+
self.cache_kwargs[layer_idx] = kwargs
|
| 42 |
+
|
| 43 |
+
def get_layer_length(self):
|
| 44 |
+
return len(self.cache_kwargs)
|
| 45 |
+
|
| 46 |
+
def get_kvcache(self, layer_idx):
|
| 47 |
+
"""
|
| 48 |
+
获取指定层的KV缓存
|
| 49 |
+
|
| 50 |
+
Args:
|
| 51 |
+
layer_idx: 层索引
|
| 52 |
+
|
| 53 |
+
Returns:
|
| 54 |
+
Tuple[torch.Tensor, torch.Tensor]: (key_cache, value_cache)
|
| 55 |
+
"""
|
| 56 |
+
key_cache = self.key_cache[layer_idx]
|
| 57 |
+
value_cache = self.value_cache[layer_idx]
|
| 58 |
+
return key_cache, value_cache
|
| 59 |
+
|
| 60 |
+
def get_router_kcache(self, layer_idx):
|
| 61 |
+
if layer_idx < len(self.router_key_cache):
|
| 62 |
+
return self.router_key_cache[layer_idx]
|
| 63 |
+
else:
|
| 64 |
+
return None
|
| 65 |
+
|
| 66 |
+
def clear_query(self):
|
| 67 |
+
"""
|
| 68 |
+
清理查询相关的临时数据
|
| 69 |
+
移除查询过程中产生的临时数据,保持缓存清洁
|
| 70 |
+
"""
|
| 71 |
+
for k, v in self.cache_kwargs.items():
|
| 72 |
+
if "compacked_key_cache" in v:
|
| 73 |
+
v.pop("compacked_key_cache")
|
| 74 |
+
v.pop("compacked_value_cache")
|
| 75 |
+
if "prefill_stage2_kvcache_size" in v:
|
| 76 |
+
v.pop("prefill_stage2_kvcache_size")
|
| 77 |
+
if "prefill_stage1_kvcache_size" in v:
|
| 78 |
+
v.pop("prefill_stage1_kvcache_size")
|
| 79 |
+
if "recall_topk" in v:
|
| 80 |
+
v.pop("recall_topk")
|
| 81 |
+
return self
|
| 82 |
+
|
| 83 |
+
def get_seq_length(self, layer_idx=0) -> int:
|
| 84 |
+
"""
|
| 85 |
+
返回缓存状态的序列长度
|
| 86 |
+
|
| 87 |
+
Args:
|
| 88 |
+
layer_idx: 可选的层索引
|
| 89 |
+
|
| 90 |
+
Returns:
|
| 91 |
+
int: 序列长度
|
| 92 |
+
"""
|
| 93 |
+
is_empty_layer = (
|
| 94 |
+
len(self.key_cache) == 0 # no cache in any layer
|
| 95 |
+
or len(self.key_cache) <= layer_idx # skipped `layer_idx` and hasn't run a layer with cache after it
|
| 96 |
+
or not self.key_cache[layer_idx].numel() # the layer has no cache
|
| 97 |
+
)
|
| 98 |
+
layer_seq_length = self.key_cache[layer_idx].shape[-2] if not is_empty_layer else 0
|
| 99 |
+
return layer_seq_length
|
| 100 |
+
|
| 101 |
+
def copy(self):
|
| 102 |
+
"""
|
| 103 |
+
创建缓存的深拷贝
|
| 104 |
+
|
| 105 |
+
Returns:
|
| 106 |
+
CustomDynamicCache: 缓存的新副本
|
| 107 |
+
"""
|
| 108 |
+
new_cache = CustomDynamicCache()
|
| 109 |
+
new_cache.key_cache = [k.clone() for k in self.key_cache]
|
| 110 |
+
new_cache.value_cache = [v.clone() for v in self.value_cache]
|
| 111 |
+
new_cache.cache_kwargs = copy.deepcopy(self.cache_kwargs)
|
| 112 |
+
new_cache.group_cache = copy.deepcopy(self.group_cache)
|
| 113 |
+
new_cache.meta = copy.deepcopy(self.meta)
|
| 114 |
+
new_cache._seen_tokens = self._seen_tokens
|
| 115 |
+
return new_cache
|
| 116 |
+
|
| 117 |
+
def update_router_kcache(
|
| 118 |
+
self,
|
| 119 |
+
key_states: torch.Tensor,
|
| 120 |
+
layer_idx: int,
|
| 121 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 122 |
+
|
| 123 |
+
# Update the cache
|
| 124 |
+
if key_states is not None:
|
| 125 |
+
if len(self.router_key_cache) <= layer_idx:
|
| 126 |
+
# There may be skipped layers, fill them with empty lists
|
| 127 |
+
for _ in range(len(self.router_key_cache), layer_idx):
|
| 128 |
+
self.router_key_cache.append(torch.tensor([]))
|
| 129 |
+
self.router_key_cache.append(key_states)
|
| 130 |
+
elif (
|
| 131 |
+
not self.router_key_cache[layer_idx].numel() # prefers not t.numel() to len(t) == 0 to export the model
|
| 132 |
+
): # fills previously skipped layers; checking for tensor causes errors
|
| 133 |
+
self.router_key_cache[layer_idx] = key_states
|
| 134 |
+
else:
|
| 135 |
+
self.router_key_cache[layer_idx] = torch.cat([self.router_key_cache[layer_idx], key_states], dim=-2)
|
| 136 |
+
|
| 137 |
+
return self.router_key_cache[layer_idx]
|
| 138 |
+
|
| 139 |
+
class CustomDynamicCacheOnCPU(CustomDynamicCache):
|
| 140 |
+
def __init__(self, _distributed_cache_data=None):
|
| 141 |
+
super().__init__(_distributed_cache_data)
|
| 142 |
+
|
| 143 |
+
def record_kwargs(self, layer_idx, kwargs):
|
| 144 |
+
d = {}
|
| 145 |
+
for k, v in kwargs.items():
|
| 146 |
+
if v is not None and torch.is_tensor(v):
|
| 147 |
+
d[k] = v.cpu() if v.is_cuda else v.clone()
|
| 148 |
+
else:
|
| 149 |
+
d[k] = v
|
| 150 |
+
super().record_kwargs(layer_idx, d)
|
| 151 |
+
|
| 152 |
+
def update(
|
| 153 |
+
self,
|
| 154 |
+
key_states: torch.Tensor,
|
| 155 |
+
value_states: torch.Tensor,
|
| 156 |
+
layer_idx: int,
|
| 157 |
+
cache_kwargs=None,
|
| 158 |
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 159 |
+
if key_states is not None and torch.is_tensor(key_states) and key_states.is_cuda:
|
| 160 |
+
key_states = key_states.cpu()
|
| 161 |
+
if value_states is not None and torch.is_tensor(value_states) and value_states.is_cuda:
|
| 162 |
+
value_states = value_states.cpu()
|
| 163 |
+
return super().update(key_states, value_states, layer_idx, cache_kwargs)
|
| 164 |
+
|
| 165 |
+
def update_router_kcache(
|
| 166 |
+
self,
|
| 167 |
+
key_states: torch.Tensor,
|
| 168 |
+
layer_idx: int,
|
| 169 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 170 |
+
if key_states is not None and torch.is_tensor(key_states) and key_states.is_cuda:
|
| 171 |
+
key_states = key_states.cpu()
|
| 172 |
+
return super().update_router_kcache(key_states, layer_idx)
|
| 173 |
+
|
| 174 |
+
class CustomQuantizeDynamicCache(QuantoQuantizedCache):
|
| 175 |
+
"""
|
| 176 |
+
自定义量化动态缓存类
|
| 177 |
+
扩展标准的QuantoQuantizedCache,添加额外的元数据存储和查询功能
|
| 178 |
+
"""
|
| 179 |
+
def __init__(self, cache_config):
|
| 180 |
+
super().__init__(cache_config)
|
| 181 |
+
self.cache_config = cache_config
|
| 182 |
+
self.cache_kwargs = {}
|
| 183 |
+
self.group_cache = {}
|
| 184 |
+
self.meta = {}
|
| 185 |
+
|
| 186 |
+
def record_kwargs(self, layer_idx, kwargs):
|
| 187 |
+
"""
|
| 188 |
+
记录层的元数据信息
|
| 189 |
+
|
| 190 |
+
Args:
|
| 191 |
+
layer_idx: 层索引
|
| 192 |
+
kwargs: 包含路由层信息的字典
|
| 193 |
+
"""
|
| 194 |
+
if layer_idx in self.cache_kwargs:
|
| 195 |
+
self.cache_kwargs[layer_idx].update(kwargs)
|
| 196 |
+
else:
|
| 197 |
+
self.cache_kwargs[layer_idx] = kwargs
|
| 198 |
+
|
| 199 |
+
def get_layer_length(self):
|
| 200 |
+
return len(self.cache_kwargs)
|
| 201 |
+
|
| 202 |
+
def clear_kvcache(self):
|
| 203 |
+
self._quantized_key_cache = []
|
| 204 |
+
self._quantized_value_cache = []
|
| 205 |
+
self.key_cache = []
|
| 206 |
+
self.value_cache = []
|
| 207 |
+
|
| 208 |
+
def get_kvcache(self, layer_idx):
|
| 209 |
+
"""
|
| 210 |
+
获取指定层的KV缓存(反量化后)
|
| 211 |
+
|
| 212 |
+
Args:
|
| 213 |
+
layer_idx: 层索引
|
| 214 |
+
|
| 215 |
+
Returns:
|
| 216 |
+
Tuple[torch.Tensor, torch.Tensor]: (key_cache, value_cache)
|
| 217 |
+
"""
|
| 218 |
+
dequant_key = self._dequantize(self._quantized_key_cache[layer_idx])
|
| 219 |
+
dequant_value = self._dequantize(self._quantized_value_cache[layer_idx])
|
| 220 |
+
return dequant_key, dequant_value
|
| 221 |
+
|
| 222 |
+
def update(
|
| 223 |
+
self,
|
| 224 |
+
key_states: torch.Tensor,
|
| 225 |
+
value_states: torch.Tensor,
|
| 226 |
+
layer_idx: int,
|
| 227 |
+
cache_kwargs=None,
|
| 228 |
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 229 |
+
"""
|
| 230 |
+
更新缓存
|
| 231 |
+
|
| 232 |
+
Args:
|
| 233 |
+
key_states: 新的key状态
|
| 234 |
+
value_states: 新的value状态
|
| 235 |
+
layer_idx: 层索引
|
| 236 |
+
cache_kwargs: 缓存关键字参数
|
| 237 |
+
|
| 238 |
+
Returns:
|
| 239 |
+
Tuple[torch.Tensor, torch.Tensor]: 更新后的key和value状态
|
| 240 |
+
"""
|
| 241 |
+
# Update the number of seen tokens
|
| 242 |
+
if layer_idx == 0:
|
| 243 |
+
self._seen_tokens += key_states.shape[-2]
|
| 244 |
+
|
| 245 |
+
if len(self.key_cache) < layer_idx:
|
| 246 |
+
for i in range(len(self.key_cache), layer_idx):
|
| 247 |
+
self.key_cache.append(torch.zeros(0, dtype=key_states.dtype, device=key_states.device))
|
| 248 |
+
self.value_cache.append(torch.zeros(0, dtype=key_states.dtype, device=key_states.device))
|
| 249 |
+
self._quantized_key_cache.append(torch.zeros(0, dtype=key_states.dtype, device=key_states.device))
|
| 250 |
+
self._quantized_value_cache.append(torch.zeros(0, dtype=key_states.dtype, device=key_states.device))
|
| 251 |
+
|
| 252 |
+
if len(self.key_cache) == layer_idx:
|
| 253 |
+
self._quantized_key_cache.append(self._quantize(key_states.contiguous(), axis=self.axis_key))
|
| 254 |
+
self._quantized_value_cache.append(self._quantize(value_states.contiguous(), axis=self.axis_value))
|
| 255 |
+
self.key_cache.append(torch.zeros(0, dtype=key_states.dtype, device=key_states.device))
|
| 256 |
+
self.value_cache.append(torch.zeros(0, dtype=key_states.dtype, device=key_states.device))
|
| 257 |
+
keys_to_return, values_to_return = key_states, value_states
|
| 258 |
+
else:
|
| 259 |
+
dequant_key = self._dequantize(self._quantized_key_cache[layer_idx])
|
| 260 |
+
dequant_value = self._dequantize(self._quantized_value_cache[layer_idx])
|
| 261 |
+
keys_to_return = [dequant_key, self.key_cache[layer_idx], key_states]
|
| 262 |
+
values_to_return = [dequant_value, self.value_cache[layer_idx], value_states]
|
| 263 |
+
|
| 264 |
+
keys_to_return = torch.cat(keys_to_return, dim=-2)
|
| 265 |
+
values_to_return = torch.cat(values_to_return, dim=-2)
|
| 266 |
+
if (
|
| 267 |
+
self.key_cache[layer_idx].dim() == 4
|
| 268 |
+
and self.key_cache[layer_idx].shape[-2] + 1 >= self.residual_length
|
| 269 |
+
):
|
| 270 |
+
self._quantized_key_cache[layer_idx] = self._quantize(keys_to_return.contiguous(), axis=self.axis_key)
|
| 271 |
+
self._quantized_value_cache[layer_idx] = self._quantize(
|
| 272 |
+
values_to_return.contiguous(), axis=self.axis_value
|
| 273 |
+
)
|
| 274 |
+
self.key_cache[layer_idx] = torch.zeros(0, dtype=key_states.dtype, device=key_states.device)
|
| 275 |
+
self.value_cache[layer_idx] = torch.zeros(0, dtype=key_states.dtype, device=key_states.device)
|
| 276 |
+
else:
|
| 277 |
+
self.key_cache[layer_idx] = torch.cat([self.key_cache[layer_idx], key_states], dim=-2)
|
| 278 |
+
self.value_cache[layer_idx] = torch.cat([self.value_cache[layer_idx], value_states], dim=-2)
|
| 279 |
+
|
| 280 |
+
return keys_to_return, values_to_return
|
| 281 |
+
|
| 282 |
+
def get_seq_length(self, layer_idx=0) -> int:
|
| 283 |
+
"""
|
| 284 |
+
返回缓存状态的序列长度
|
| 285 |
+
|
| 286 |
+
Args:
|
| 287 |
+
layer_idx: 可选的层索引
|
| 288 |
+
|
| 289 |
+
Returns:
|
| 290 |
+
int: 序列长度
|
| 291 |
+
"""
|
| 292 |
+
is_empty_layer = (
|
| 293 |
+
len(self._quantized_key_cache) == 0 # no cache in any layer
|
| 294 |
+
or len(self._quantized_key_cache) <= layer_idx # skipped `layer_idx` and hasn't run a layer with cache after it
|
| 295 |
+
or not self._quantized_key_cache[layer_idx].numel() # the layer has no cache
|
| 296 |
+
)
|
| 297 |
+
layer_seq_length = self._quantized_key_cache[layer_idx].shape[-2] if not is_empty_layer else 0
|
| 298 |
+
return layer_seq_length
|
| 299 |
+
|
| 300 |
+
def clear_query(self):
|
| 301 |
+
"""
|
| 302 |
+
清理查询相关的临时数据
|
| 303 |
+
移除查询过程中产生的临时数据,保持缓存清洁
|
| 304 |
+
"""
|
| 305 |
+
for k, v in self.cache_kwargs.items():
|
| 306 |
+
if "compacked_key_cache" in v:
|
| 307 |
+
v.pop("compacked_key_cache")
|
| 308 |
+
v.pop("compacked_value_cache")
|
| 309 |
+
if "prefill_stage2_kvcache_size" in v:
|
| 310 |
+
v.pop("prefill_stage2_kvcache_size")
|
| 311 |
+
if "prefill_stage1_kvcache_size" in v:
|
| 312 |
+
v.pop("prefill_stage1_kvcache_size")
|
| 313 |
+
if "recall_topk" in v:
|
| 314 |
+
v.pop("recall_topk")
|
| 315 |
+
return self
|
| 316 |
+
|
| 317 |
+
def copy(self):
|
| 318 |
+
"""
|
| 319 |
+
创建缓存的深拷贝
|
| 320 |
+
|
| 321 |
+
Returns:
|
| 322 |
+
CustomQuantizeDynamicCache: 缓存的新副本
|
| 323 |
+
"""
|
| 324 |
+
new_cache = CustomQuantizeDynamicCache(self.cache_config)
|
| 325 |
+
if hasattr(self, '_quantized_key_cache'):
|
| 326 |
+
new_cache._quantized_key_cache = [k.clone() for k in self._quantized_key_cache]
|
| 327 |
+
new_cache._quantized_value_cache = [v.clone() for v in self._quantized_value_cache]
|
| 328 |
+
new_cache.key_cache = [k.clone() for k in self.key_cache]
|
| 329 |
+
new_cache.value_cache = [v.clone() for v in self.value_cache]
|
| 330 |
+
new_cache.cache_kwargs = copy.deepcopy(self.cache_kwargs)
|
| 331 |
+
new_cache.group_cache = copy.deepcopy(self.group_cache)
|
| 332 |
+
new_cache.meta = copy.deepcopy(self.meta)
|
| 333 |
+
new_cache._seen_tokens = self._seen_tokens
|
| 334 |
+
return new_cache
|
| 335 |
+
|
| 336 |
+
|
| 337 |
+
def create_cache(quantize_nbits: Optional[int] = 0):
|
| 338 |
+
"""
|
| 339 |
+
根据参数创建合适的缓存实例
|
| 340 |
+
|
| 341 |
+
Args:
|
| 342 |
+
args: 包含量化相关参数的命名空间对象
|
| 343 |
+
|
| 344 |
+
Returns:
|
| 345 |
+
CustomDynamicCache or CustomQuantizeDynamicCache: 缓存实例
|
| 346 |
+
"""
|
| 347 |
+
if quantize_nbits > 0:
|
| 348 |
+
quan_cache_config = QuantizedCacheConfig(nbits=quantize_nbits)
|
| 349 |
+
return CustomQuantizeDynamicCache(quan_cache_config)
|
| 350 |
+
else:
|
| 351 |
+
return CustomDynamicCache()
|
| 352 |
+
|
| 353 |
+
def manual_deepcopy_kv_cache(cache_obj):
|
| 354 |
+
"""
|
| 355 |
+
Manually performs a deep copy of a custom KV cache object,
|
| 356 |
+
avoiding the issues with quanto's __deepcopy__.
|
| 357 |
+
"""
|
| 358 |
+
# 1. 创建一个新的、空的 cache 对象实例
|
| 359 |
+
if isinstance(cache_obj, CustomQuantizeDynamicCache):
|
| 360 |
+
# 如果是量化缓存,需要传入配置
|
| 361 |
+
new_cache = CustomQuantizeDynamicCache(cache_obj.cache_config)
|
| 362 |
+
elif isinstance(cache_obj, CustomDynamicCache):
|
| 363 |
+
new_cache = CustomDynamicCache()
|
| 364 |
+
else:
|
| 365 |
+
# 如果有其他类型的缓存,可以在这里扩展
|
| 366 |
+
raise TypeError(f"Unsupported cache type for manual deepcopy: {type(cache_obj)}")
|
| 367 |
+
|
| 368 |
+
# 2. 复制非张量元数据
|
| 369 |
+
# 使用标准 deepcopy 是安全的,因为这些是字典和列表
|
| 370 |
+
new_cache.cache_kwargs = copy.deepcopy(cache_obj.cache_kwargs)
|
| 371 |
+
new_cache.meta = copy.deepcopy(cache_obj.meta)
|
| 372 |
+
|
| 373 |
+
# 3. 逐层复制核心的 key-value 张量缓存
|
| 374 |
+
if hasattr(cache_obj, '_quantized_key_cache'): # 处理 QuantoQuantizedCache
|
| 375 |
+
for layer_cache in cache_obj._quantized_key_cache:
|
| 376 |
+
# .clone().detach() 是安全复制张量的标准方法
|
| 377 |
+
new_cache._quantized_key_cache.append(layer_cache.clone().detach())
|
| 378 |
+
for layer_cache in cache_obj._quantized_value_cache:
|
| 379 |
+
new_cache._quantized_value_cache.append(layer_cache.clone().detach())
|
| 380 |
+
new_cache._seen_tokens = cache_obj._seen_tokens
|
| 381 |
+
|
| 382 |
+
if hasattr(cache_obj, 'key_cache'): # 处理 DynamicCache
|
| 383 |
+
for layer_cache in cache_obj.key_cache:
|
| 384 |
+
new_cache.key_cache.append(layer_cache.clone().detach())
|
| 385 |
+
for layer_cache in cache_obj.value_cache:
|
| 386 |
+
new_cache.value_cache.append(layer_cache.clone().detach())
|
| 387 |
+
new_cache._seen_tokens = cache_obj._seen_tokens
|
| 388 |
+
|
| 389 |
+
return new_cache
|
| 390 |
+
|
| 391 |
+
def convert_tensor(data, cuda_device):
|
| 392 |
+
"""转换结构体的 tensor device,如果cuda_device非 None,则将cpu 转换到 cuda,否则将 cuda 转换到 cpu
|
| 393 |
+
支持dict, list, tuple, set
|
| 394 |
+
"""
|
| 395 |
+
|
| 396 |
+
converted_count = [0] # 使用列表以便在嵌套函数中修改
|
| 397 |
+
|
| 398 |
+
def _convert_recursive(obj):
|
| 399 |
+
# 如果是torch tensor且在CUDA上
|
| 400 |
+
if torch.is_tensor(obj):
|
| 401 |
+
if cuda_device:
|
| 402 |
+
return obj.to(cuda_device) if obj.is_cpu else obj
|
| 403 |
+
if obj.is_cuda:
|
| 404 |
+
return obj.cpu()
|
| 405 |
+
return obj
|
| 406 |
+
|
| 407 |
+
# 处理各种容器类型
|
| 408 |
+
elif isinstance(obj, dict):
|
| 409 |
+
return {k: _convert_recursive(v) for k, v in obj.items()}
|
| 410 |
+
|
| 411 |
+
elif isinstance(obj, list):
|
| 412 |
+
return [_convert_recursive(item) for item in obj]
|
| 413 |
+
|
| 414 |
+
elif isinstance(obj, tuple):
|
| 415 |
+
# 元组不可变,总是创建新的
|
| 416 |
+
return tuple(_convert_recursive(item) for item in obj)
|
| 417 |
+
|
| 418 |
+
elif isinstance(obj, set):
|
| 419 |
+
return {_convert_recursive(item) for item in obj}
|
| 420 |
+
|
| 421 |
+
# 其他数据类型直接返回
|
| 422 |
+
else:
|
| 423 |
+
return obj
|
| 424 |
+
|
| 425 |
+
return _convert_recursive(data)
|
| 426 |
+
|
| 427 |
+
def copy_dict_to_cpu(d: dict):
|
| 428 |
+
ret = {}
|
| 429 |
+
for k, v in d.items():
|
| 430 |
+
ret[k] = v.cpu() if torch.is_tensor(v) and v.is_cuda else v
|
| 431 |
+
return ret
|
| 432 |
+
|
| 433 |
+
def copy_dict_to_gpu(d: dict, device):
|
| 434 |
+
if not d:
|
| 435 |
+
return d
|
| 436 |
+
ret = {}
|
| 437 |
+
for k, v in d.items():
|
| 438 |
+
ret[k] = v.to(device) if torch.is_tensor(v) and not v.is_cuda else v
|
| 439 |
+
return ret
|
| 440 |
+
|
| 441 |
+
def copy_kv_cache_to_device(cache_obj, cuda_device, copy_v: bool=True):
|
| 442 |
+
if isinstance(cache_obj, CustomQuantizeDynamicCache):
|
| 443 |
+
new_cache = CustomQuantizeDynamicCache(cache_obj.cache_config)
|
| 444 |
+
elif isinstance(cache_obj, CustomDynamicCache):
|
| 445 |
+
new_cache = CustomDynamicCache()
|
| 446 |
+
else:
|
| 447 |
+
raise TypeError(f"Unsupported cache type for manual deepcopy: {type(cache_obj)}")
|
| 448 |
+
|
| 449 |
+
# 复制非张量元数据
|
| 450 |
+
new_cache.cache_kwargs = convert_tensor(cache_obj.cache_kwargs, cuda_device)
|
| 451 |
+
new_cache.meta = convert_tensor(cache_obj.meta, cuda_device)
|
| 452 |
+
|
| 453 |
+
# 复制核心张量缓存
|
| 454 |
+
if hasattr(cache_obj, '_quantized_key_cache'):
|
| 455 |
+
new_cache._quantized_key_cache = convert_tensor(cache_obj._quantized_key_cache, cuda_device)
|
| 456 |
+
if copy_v:
|
| 457 |
+
new_cache._quantized_value_cache = convert_tensor(cache_obj._quantized_value_cache, cuda_device)
|
| 458 |
+
else:
|
| 459 |
+
new_cache._quantized_value_cache = cache_obj._quantized_value_cache
|
| 460 |
+
new_cache._seen_tokens = cache_obj._seen_tokens
|
| 461 |
+
|
| 462 |
+
if hasattr(cache_obj, 'key_cache'):
|
| 463 |
+
new_cache.key_cache = convert_tensor(cache_obj.key_cache, cuda_device)
|
| 464 |
+
if copy_v:
|
| 465 |
+
new_cache.value_cache = convert_tensor(cache_obj.value_cache, cuda_device)
|
| 466 |
+
else:
|
| 467 |
+
new_cache.value_cache = cache_obj.value_cache
|
| 468 |
+
new_cache._seen_tokens = cache_obj._seen_tokens
|
| 469 |
+
|
| 470 |
+
return new_cache
|
| 471 |
+
|
| 472 |
+
# def copy_kv_cache_to_gpu(cache_obj, device, copy_v: bool=True):
|
| 473 |
+
# """
|
| 474 |
+
# 手动执行缓存对象的深拷贝
|
| 475 |
+
# 避免quanto库__deepcopy__的问题
|
| 476 |
+
|
| 477 |
+
# Args:
|
| 478 |
+
# cache_obj: 要拷贝的缓存对象
|
| 479 |
+
|
| 480 |
+
# Returns:
|
| 481 |
+
# 缓存对象的深拷贝副本
|
| 482 |
+
# """
|
| 483 |
+
# if isinstance(cache_obj, CustomQuantizeDynamicCache):
|
| 484 |
+
# new_cache = CustomQuantizeDynamicCache(cache_obj.cache_config)
|
| 485 |
+
# elif isinstance(cache_obj, CustomDynamicCache):
|
| 486 |
+
# new_cache = CustomDynamicCache()
|
| 487 |
+
# else:
|
| 488 |
+
# raise TypeError(f"Unsupported cache type for manual deepcopy: {type(cache_obj)}")
|
| 489 |
+
|
| 490 |
+
# # 复制非张量元数据
|
| 491 |
+
# new_cache.cache_kwargs = copy_dict_to_gpu(cache_obj.cache_kwargs, device)
|
| 492 |
+
# new_cache.meta = copy_dict_to_gpu(cache_obj.meta, device)
|
| 493 |
+
|
| 494 |
+
# # 复制核心张量缓存
|
| 495 |
+
# if hasattr(cache_obj, '_quantized_key_cache'):
|
| 496 |
+
# new_cache._quantized_key_cache = [t.to(device) for t in cache_obj._quantized_key_cache]
|
| 497 |
+
# if copy_v:
|
| 498 |
+
# new_cache._quantized_value_cache = [t.to(device) for t in cache_obj._quantized_value_cache]
|
| 499 |
+
# else:
|
| 500 |
+
# new_cache._quantized_value_cache = cache_obj._quantized_value_cache
|
| 501 |
+
# new_cache._seen_tokens = cache_obj._seen_tokens
|
| 502 |
+
|
| 503 |
+
# if hasattr(cache_obj, 'key_cache'):
|
| 504 |
+
# new_cache.key_cache = [t.to(device) for t in cache_obj.key_cache]
|
| 505 |
+
# if copy_v:
|
| 506 |
+
# new_cache.value_cache = [t.to(device) for t in cache_obj.value_cache]
|
| 507 |
+
# else:
|
| 508 |
+
# new_cache.value_cache = cache_obj.value_cache
|
| 509 |
+
|
| 510 |
+
# new_cache._seen_tokens = cache_obj._seen_tokens
|
| 511 |
+
|
| 512 |
+
# return new_cache
|
src/utils/callbacks.py
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import time
|
| 3 |
+
import typing
|
| 4 |
+
# from deepspeed.accelerator import get_accelerator
|
| 5 |
+
|
| 6 |
+
# NOTE: 最新版开始迁移到 integration_utils
|
| 7 |
+
try:
|
| 8 |
+
from transformers.integrations import TrainerCallback
|
| 9 |
+
except ImportError:
|
| 10 |
+
from transformers.integrations.integration_utils import TrainerCallback
|
| 11 |
+
|
| 12 |
+
# USE_FLASH_ATTN, USE_XFORMERS_ATTN = False, False
|
| 13 |
+
# if os.getenv('FLASH_ATTN', 'false').lower() == 'true':
|
| 14 |
+
# USE_FLASH_ATTN = True
|
| 15 |
+
# from mllm.utils.llama_flash_attn_monkey_patch import replace_llama_attn_with_flash_attn, restore_llama_attn
|
| 16 |
+
# if os.getenv("XFORMERS_ATTN", 'false').lower() == 'true':
|
| 17 |
+
# USE_XFORMERS_ATTN = True
|
| 18 |
+
# from mllm.utils.llama_xformers_monkey_patch import replace_llama_attn_with_xformers_attn, restore_llama_attn
|
| 19 |
+
|
| 20 |
+
class ModeltimeCallback(TrainerCallback):
|
| 21 |
+
def __init__(self):
|
| 22 |
+
self.model_time = 0.
|
| 23 |
+
self.data_time = 0.
|
| 24 |
+
self._start = 0.
|
| 25 |
+
self._end = 0.
|
| 26 |
+
|
| 27 |
+
def on_train_begin(self, args, state, control, **kwargs):
|
| 28 |
+
self._end = time.time()
|
| 29 |
+
|
| 30 |
+
def on_step_begin(self, args, state, control, **kwargs):
|
| 31 |
+
self._start = time.time()
|
| 32 |
+
self.data_time += (self._start - self._end)
|
| 33 |
+
|
| 34 |
+
def on_step_end(self, args, state, control, **kwargs):
|
| 35 |
+
self._end = time.time()
|
| 36 |
+
self.model_time += (self._end - self._start)
|
| 37 |
+
|
| 38 |
+
def on_log(self, args, state, control, logs=None, **kwargs):
|
| 39 |
+
if not state.is_world_process_zero:
|
| 40 |
+
return
|
| 41 |
+
|
| 42 |
+
data_time = self.data_time / state.logging_steps
|
| 43 |
+
model_time = self.model_time / state.logging_steps
|
| 44 |
+
self.data_time = 0.
|
| 45 |
+
self.model_time = 0.
|
| 46 |
+
|
| 47 |
+
info = f'\nSTEP: {state.global_step}, data_time: {data_time:.3f}, model_time: {model_time:.3f}'
|
| 48 |
+
print(info)
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
class SacredCallback(TrainerCallback):
|
| 52 |
+
def __init__(self, _run=None):
|
| 53 |
+
self._run = _run
|
| 54 |
+
self.model_time = 0.
|
| 55 |
+
self.data_time = 0.
|
| 56 |
+
self._start = 0.
|
| 57 |
+
self._end = 0.
|
| 58 |
+
self._zero_loss_cnt = 0
|
| 59 |
+
|
| 60 |
+
def on_train_begin(self, args, state, control, **kwargs):
|
| 61 |
+
if self._run:
|
| 62 |
+
self._end = time.time()
|
| 63 |
+
|
| 64 |
+
def on_step_begin(self, args, state, control, **kwargs):
|
| 65 |
+
if self._run:
|
| 66 |
+
self._start = time.time()
|
| 67 |
+
self.data_time += (self._start - self._end)
|
| 68 |
+
|
| 69 |
+
def on_step_end(self, args, state, control, **kwargs):
|
| 70 |
+
if self._run:
|
| 71 |
+
self._end = time.time()
|
| 72 |
+
self.model_time += (self._end - self._start)
|
| 73 |
+
|
| 74 |
+
def on_log(self, args, state, control, logs=None, **kwargs):
|
| 75 |
+
if not state.is_world_process_zero:
|
| 76 |
+
return
|
| 77 |
+
|
| 78 |
+
if self._run is None:
|
| 79 |
+
return
|
| 80 |
+
|
| 81 |
+
data_time = self.data_time / state.logging_steps
|
| 82 |
+
model_time = self.model_time / state.logging_steps
|
| 83 |
+
self.data_time = 0.
|
| 84 |
+
self.model_time = 0.
|
| 85 |
+
|
| 86 |
+
self._run.log_scalar("loss", logs.get("loss", 0.), state.global_step)
|
| 87 |
+
self._run.log_scalar("learning_rate", logs.get("learning_rate", 0.), state.global_step)
|
| 88 |
+
self._run.log_scalar("grad_norm", logs.get("grad_norm", 0.), state.global_step)
|
| 89 |
+
self._run.log_scalar("epoch", state.epoch, state.global_step)
|
| 90 |
+
self._run.log_scalar("data_time", data_time, state.global_step)
|
| 91 |
+
self._run.log_scalar("model_time", model_time, state.global_step)
|
| 92 |
+
|
| 93 |
+
if logs.get("loss", 0.) == 0.:
|
| 94 |
+
self._zero_loss_cnt += 1
|
| 95 |
+
if self._zero_loss_cnt > 1:
|
| 96 |
+
raise RuntimeError("Loss is zero, something is wrong!")
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
class ModelEvalCallback(TrainerCallback):
|
| 100 |
+
def __init__(self, _run=None, multitest=None, trainer=None, gen_kwargs=None):
|
| 101 |
+
self._run = _run
|
| 102 |
+
# datasets dict
|
| 103 |
+
# key1: dataset_name
|
| 104 |
+
# val1: {'dataset': dataset inst, 'compute_metric': metric inst}
|
| 105 |
+
self.multitest = typing.cast(dict, multitest)
|
| 106 |
+
self.trainer = trainer
|
| 107 |
+
self.gen_kwargs = gen_kwargs
|
| 108 |
+
|
| 109 |
+
def on_step_end(self, args, state, control, **kwargs):
|
| 110 |
+
if args.eval_steps is None:
|
| 111 |
+
eval_steps = args.save_steps
|
| 112 |
+
elif isinstance(args.eval_steps, int) and args.eval_steps > 0:
|
| 113 |
+
eval_steps = args.eval_steps
|
| 114 |
+
else:
|
| 115 |
+
return
|
| 116 |
+
if state.global_step > 0 and state.global_step % eval_steps == 0:
|
| 117 |
+
if not args.do_multi_predict:
|
| 118 |
+
return
|
| 119 |
+
|
| 120 |
+
# flash-attn currently not supports eval mode!
|
| 121 |
+
# if USE_FLASH_ATTN or USE_XFORMERS_ATTN:
|
| 122 |
+
# restore_llama_attn()
|
| 123 |
+
|
| 124 |
+
old_compute_metrics = self.trainer.compute_metrics
|
| 125 |
+
|
| 126 |
+
for dataset_idx, (dataset_name, item) in enumerate(self.multitest.items()):
|
| 127 |
+
print(f'processing multitest set {dataset_idx}/{len(self.multitest)}: {dataset_name}')
|
| 128 |
+
_ds = item['dataset']
|
| 129 |
+
_compute_metrics = item['compute_metric']
|
| 130 |
+
_prefix = dataset_name
|
| 131 |
+
|
| 132 |
+
self.trainer.compute_metrics = _compute_metrics
|
| 133 |
+
# transformers.trainer_utils.PredictionOutput
|
| 134 |
+
_pred_results = self.trainer.predict(_ds, metric_key_prefix=_prefix, **self.gen_kwargs)
|
| 135 |
+
if state.is_world_process_zero:
|
| 136 |
+
self.trainer.log_metrics(_prefix, _pred_results.metrics) # noqa
|
| 137 |
+
self.trainer.save_metrics(_prefix, _pred_results.metrics) # noqa
|
| 138 |
+
self.trainer.save_prediction(_pred_results, file_key_prefix=_prefix)
|
| 139 |
+
|
| 140 |
+
if self._run is not None:
|
| 141 |
+
keywords_to_remove = ['runtime', 'second']
|
| 142 |
+
for k, v in _pred_results.metrics.items():
|
| 143 |
+
# remove time releated metrics
|
| 144 |
+
if any(kw in k for kw in keywords_to_remove):
|
| 145 |
+
continue
|
| 146 |
+
self._run.log_scalar(f'{k}', v, state.global_step)
|
| 147 |
+
|
| 148 |
+
self.trainer.compute_metrics = old_compute_metrics
|
| 149 |
+
|
| 150 |
+
# if USE_FLASH_ATTN:
|
| 151 |
+
# replace_llama_attn_with_flash_attn()
|
| 152 |
+
# if USE_XFORMERS_ATTN:
|
| 153 |
+
# replace_llama_attn_with_xformers_attn()
|
| 154 |
+
|
| 155 |
+
class DSEmptyCacheCallback(TrainerCallback):
|
| 156 |
+
def on_step_end(self, args, state, control, **kwargs):
|
| 157 |
+
empty_cache_steps = int(os.getenv("EMPTY_CACHE_STEP", '0').strip())
|
| 158 |
+
can_flush = state.global_step > 0 and empty_cache_steps > 0 and state.global_step % empty_cache_steps == 0
|
| 159 |
+
if can_flush:
|
| 160 |
+
# print('Flush Cache here.')
|
| 161 |
+
get_accelerator().empty_cache()
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
# usage: https://github.com/yqhu/profiler-workshop/blob/c8d4a7c30a61cc7b909d89f88f5fd36b70c55769/hf_training_trainer_prof.py#L49C6-L49C28
|
| 165 |
+
# additionally, with_modules can be set True, with_flops must be set False
|
| 166 |
+
# deps: pip install -U tensorboard-plugin-profilepip torch_tb_profiler
|
| 167 |
+
class ProfCallback(TrainerCallback):
|
| 168 |
+
def __init__(self, prof):
|
| 169 |
+
self.prof = prof
|
| 170 |
+
|
| 171 |
+
def on_step_end(self, args, state, control, **kwargs):
|
| 172 |
+
self.prof.step()
|
src/utils/common.py
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import copy
|
| 2 |
+
from typing import List, Union, Dict
|
| 3 |
+
|
| 4 |
+
import PIL.Image
|
| 5 |
+
import torch
|
| 6 |
+
import numpy as np
|
| 7 |
+
import torchvision.transforms.functional as F
|
| 8 |
+
import transformers
|
| 9 |
+
|
| 10 |
+
from transformers import PreTrainedTokenizer
|
| 11 |
+
|
| 12 |
+
IGNORE_INDEX = -100
|
| 13 |
+
|
| 14 |
+
def print_trainable_params(model: torch.nn.Module) -> None:
|
| 15 |
+
trainable_params, all_param = 0, 0
|
| 16 |
+
for param in model.parameters():
|
| 17 |
+
num_params = param.numel()
|
| 18 |
+
# if using DS Zero 3 and the weights are initialized empty
|
| 19 |
+
if num_params == 0 and hasattr(param, "ds_numel"):
|
| 20 |
+
num_params = param.ds_numel
|
| 21 |
+
all_param += num_params
|
| 22 |
+
if param.requires_grad:
|
| 23 |
+
trainable_params += num_params
|
| 24 |
+
print("trainable params: {:d} || all params: {:d} || trainable%: {:.4f}".format(
|
| 25 |
+
trainable_params, all_param, 100 * trainable_params / all_param))
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def post_process_generate_ids(tokenizer: PreTrainedTokenizer, ids: torch.Tensor):
|
| 29 |
+
ids = copy.deepcopy(ids) # do not modify origin preds and targets
|
| 30 |
+
ids[ids < 0] = tokenizer.pad_token_id
|
| 31 |
+
# pad_to_multiof 开启后, 多余的部分没法解码, 这里暂时替换为 ','
|
| 32 |
+
ids[ids >= len(tokenizer)] = tokenizer.convert_tokens_to_ids(',')
|
| 33 |
+
return ids
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def decode_generate_ids(tokenizer: PreTrainedTokenizer, ids: torch.Tensor) -> Union[List[str], str]:
|
| 37 |
+
assert ids.ndim in [1, 2]
|
| 38 |
+
only_one_sentence = ids.ndim == 1
|
| 39 |
+
if only_one_sentence:
|
| 40 |
+
ids = ids.unsqueeze(0)
|
| 41 |
+
ids = post_process_generate_ids(tokenizer, ids)
|
| 42 |
+
res = tokenizer.batch_decode(ids, skip_special_tokens=True, clean_up_tokenization_spaces=True)
|
| 43 |
+
if only_one_sentence:
|
| 44 |
+
return res[0]
|
| 45 |
+
return res
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def draw_bounding_boxes(
|
| 50 |
+
image: Union[torch.Tensor, PIL.Image.Image],
|
| 51 |
+
boxes: Union[torch.Tensor, List, np.ndarray],
|
| 52 |
+
**kwargs,
|
| 53 |
+
):
|
| 54 |
+
if isinstance(image, PIL.Image.Image):
|
| 55 |
+
from torchvision.transforms import PILToTensor
|
| 56 |
+
image = PILToTensor()(image)
|
| 57 |
+
assert isinstance(image, torch.Tensor), ""
|
| 58 |
+
|
| 59 |
+
if not isinstance(boxes, torch.Tensor):
|
| 60 |
+
boxes = torch.as_tensor(boxes)
|
| 61 |
+
assert isinstance(boxes, torch.Tensor)
|
| 62 |
+
|
| 63 |
+
from torchvision.utils import draw_bounding_boxes as _draw_bounding_boxes
|
| 64 |
+
return _draw_bounding_boxes(image, boxes, **kwargs)
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
# https://github.com/huggingface/tokenizers/issues/247#issuecomment-675458087
|
| 68 |
+
def smart_tokenizer_and_embedding_resize(
|
| 69 |
+
special_tokens_dict: Dict,
|
| 70 |
+
tokenizer: transformers.PreTrainedTokenizer,
|
| 71 |
+
model: transformers.PreTrainedModel,
|
| 72 |
+
):
|
| 73 |
+
"""Resize tokenizer and embedding.
|
| 74 |
+
|
| 75 |
+
Note: This is the unoptimized version that may make your embedding size not be divisible by 64.
|
| 76 |
+
"""
|
| 77 |
+
num_new_tokens = tokenizer.add_special_tokens(special_tokens_dict)
|
| 78 |
+
model.resize_token_embeddings(len(tokenizer))
|
| 79 |
+
|
| 80 |
+
if num_new_tokens > 0:
|
| 81 |
+
input_embeddings = model.get_input_embeddings().weight.data
|
| 82 |
+
output_embeddings = model.get_output_embeddings().weight.data
|
| 83 |
+
|
| 84 |
+
input_embeddings_avg = input_embeddings[:-num_new_tokens].mean(dim=0, keepdim=True)
|
| 85 |
+
output_embeddings_avg = output_embeddings[:-num_new_tokens].mean(dim=0, keepdim=True)
|
| 86 |
+
|
| 87 |
+
input_embeddings[-num_new_tokens:] = input_embeddings_avg
|
| 88 |
+
output_embeddings[-num_new_tokens:] = output_embeddings_avg
|
| 89 |
+
|
| 90 |
+
def patch_transformer_logging():
|
| 91 |
+
import logging
|
| 92 |
+
import transformers
|
| 93 |
+
def enable_explicit_format():
|
| 94 |
+
handlers = transformers.utils.logging._get_library_root_logger().handlers
|
| 95 |
+
|
| 96 |
+
for handler in handlers:
|
| 97 |
+
formatter = logging.Formatter("[(%(levelname)s) %(pathname)s:%(lineno)s ] %(asctime)s >> %(message)s")
|
| 98 |
+
handler.setFormatter(formatter)
|
| 99 |
+
transformers.utils.logging.enable_explicit_format = enable_explicit_format
|
| 100 |
+
|
| 101 |
+
def print_model_stats(model):
|
| 102 |
+
"""
|
| 103 |
+
通用模型参数统计工具。
|
| 104 |
+
自动识别是 Dense 还是 MoE 模型,并计算 Total vs Active 参数量。
|
| 105 |
+
"""
|
| 106 |
+
|
| 107 |
+
# 1. 计算物理总参数量 (Total Parameters)
|
| 108 |
+
# 使用 set 避免计算共享参数 (Shared Weights),例如 Embedding 和 lm_head 共享权重的情况
|
| 109 |
+
unique_params = {p.data_ptr(): p for p in model.parameters()}.values()
|
| 110 |
+
total_params = sum(p.numel() for p in unique_params)
|
| 111 |
+
|
| 112 |
+
# 2. 初始化激活参数量 (Active Parameters)
|
| 113 |
+
# 默认假设是 Dense 模型,所有参数都是激活的
|
| 114 |
+
active_params = total_params
|
| 115 |
+
|
| 116 |
+
moe_infos = [] # 用于存储发现的 MoE 层信息
|
| 117 |
+
|
| 118 |
+
# 3. 遍历所有子模块,寻找 MoE 层特征
|
| 119 |
+
# 我们不匹配类名,而是匹配"特征" (Duck Typing)
|
| 120 |
+
for name, module in model.named_modules():
|
| 121 |
+
# 特征判定:有 num_experts 属性,且有一个叫 experts 的 ModuleList
|
| 122 |
+
if hasattr(module, 'num_experts') and hasattr(module, 'experts') and isinstance(module.experts, nn.ModuleList):
|
| 123 |
+
|
| 124 |
+
# 获取关键超参
|
| 125 |
+
num_experts = getattr(module, 'num_experts', 0)
|
| 126 |
+
# 兼容不同的 top_k 命名 (top_k 或 num_experts_per_tok)
|
| 127 |
+
top_k = getattr(module, 'top_k', getattr(module, 'num_experts_per_tok', 0))
|
| 128 |
+
|
| 129 |
+
# 如果找不到 top_k,可能不是标准的 Sparse MoE,跳过
|
| 130 |
+
if top_k == 0:
|
| 131 |
+
continue
|
| 132 |
+
|
| 133 |
+
# --- 核心计算逻辑 ---
|
| 134 |
+
# 1. 计算单个专家的参数量 (假设所有专家结构相同,取第一个)
|
| 135 |
+
# 这里必须用 recursion=True 确保统计专家内部所有层
|
| 136 |
+
single_expert_params = sum(p.numel() for p in module.experts[0].parameters())
|
| 137 |
+
|
| 138 |
+
# 2. 计算"休眠"专家数量
|
| 139 |
+
dormant_experts = num_experts - top_k
|
| 140 |
+
|
| 141 |
+
# 3. 从激活总数中扣除休眠专家的参数
|
| 142 |
+
# 注意:total_params 里已经包含了 N 个专家,我们只需要减去 (N-K) 个
|
| 143 |
+
if dormant_experts > 0:
|
| 144 |
+
deduction = dormant_experts * single_expert_params
|
| 145 |
+
active_params -= deduction
|
| 146 |
+
|
| 147 |
+
moe_infos.append({
|
| 148 |
+
"layer": name,
|
| 149 |
+
"experts": num_experts,
|
| 150 |
+
"active": top_k,
|
| 151 |
+
"expert_size": single_expert_params
|
| 152 |
+
})
|
| 153 |
+
|
| 154 |
+
# --- 4. 格式化输出 ---
|
| 155 |
+
def format_num(num):
|
| 156 |
+
if num >= 1e9: return f"{num/1e9:.2f}B"
|
| 157 |
+
if num >= 1e6: return f"{num/1e6:.2f}M"
|
| 158 |
+
if num >= 1e3: return f"{num/1e3:.2f}K"
|
| 159 |
+
return str(num)
|
| 160 |
+
|
| 161 |
+
print("=" * 50)
|
| 162 |
+
print(f"Model Architecture Analysis")
|
| 163 |
+
print("=" * 50)
|
| 164 |
+
|
| 165 |
+
if len(moe_infos) > 0:
|
| 166 |
+
print(f"👉 Detection: MoE Model (Sparse Mixture-of-Experts)")
|
| 167 |
+
print(f" - Found {len(moe_infos)} MoE layers")
|
| 168 |
+
print(f" - Config: {moe_infos[0]['experts']} Experts, Top-{moe_infos[0]['active']} Active")
|
| 169 |
+
else:
|
| 170 |
+
print(f"👉 Detection: Dense Model (Standard Transformer)")
|
| 171 |
+
|
| 172 |
+
print("-" * 50)
|
| 173 |
+
print(f"Total Parameters (VRAM): {format_num(total_params)}")
|
| 174 |
+
print(f"Active Parameters (FLOPs): {format_num(active_params)}")
|
| 175 |
+
|
| 176 |
+
if len(moe_infos) > 0:
|
| 177 |
+
sparsity = 1 - (active_params / total_params)
|
| 178 |
+
print(f"Sparsity Ratio: {sparsity:.2%}")
|
| 179 |
+
# 计算相比 Dense 版本的倍数
|
| 180 |
+
# 假设 Dense 版本就是 active_params 大小(不太严谨但直观)
|
| 181 |
+
print(f"Upcycling Scale: {total_params/active_params:.2f}x Larger than Dense Base")
|
| 182 |
+
else:
|
| 183 |
+
print(f"Sparsity Ratio: 0.00% (Dense)")
|
| 184 |
+
|
| 185 |
+
print("=" * 50)
|
| 186 |
+
|
| 187 |
+
return total_params, active_params
|
src/utils/data_utils.py
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import lmdb
|
| 3 |
+
from multiprocessing import Pool
|
| 4 |
+
from tqdm import tqdm
|
| 5 |
+
import importlib.metadata
|
| 6 |
+
import importlib.util
|
| 7 |
+
from packaging import version
|
| 8 |
+
from typing import TYPE_CHECKING
|
| 9 |
+
from functools import lru_cache
|
| 10 |
+
from datasets import Dataset, IterableDataset
|
| 11 |
+
from src.utils.common import IGNORE_INDEX
|
| 12 |
+
from collections import defaultdict
|
| 13 |
+
from functools import partial
|
| 14 |
+
import bisect
|
| 15 |
+
from typing import List, Sequence, Tuple, Optional, Union
|
| 16 |
+
from src.utils.common import pdb_debug
|
| 17 |
+
|
| 18 |
+
if TYPE_CHECKING:
|
| 19 |
+
from packaging.version import Version
|
| 20 |
+
|
| 21 |
+
def write_lmdb(output_dir, name):
|
| 22 |
+
|
| 23 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 24 |
+
output_name = os.path.join(output_dir, f'{name}.lmdb')
|
| 25 |
+
|
| 26 |
+
try:
|
| 27 |
+
os.remove(output_name)
|
| 28 |
+
except:
|
| 29 |
+
pass
|
| 30 |
+
env_new = lmdb.open(
|
| 31 |
+
output_name,
|
| 32 |
+
subdir=False,
|
| 33 |
+
readonly=False,
|
| 34 |
+
lock=False,
|
| 35 |
+
readahead=False,
|
| 36 |
+
meminit=False,
|
| 37 |
+
max_readers=1,
|
| 38 |
+
map_size=int(100e9),
|
| 39 |
+
)
|
| 40 |
+
txn_write = env_new.begin(write=True)
|
| 41 |
+
|
| 42 |
+
return txn_write, env_new
|
| 43 |
+
|
| 44 |
+
def read_lmdb(lmdb_path):
|
| 45 |
+
env = lmdb.open(
|
| 46 |
+
lmdb_path,
|
| 47 |
+
subdir=False,
|
| 48 |
+
readonly=True,
|
| 49 |
+
lock=False,
|
| 50 |
+
readahead=False,
|
| 51 |
+
meminit=False,
|
| 52 |
+
max_readers=256,
|
| 53 |
+
)
|
| 54 |
+
txn = env.begin()
|
| 55 |
+
return env, txn
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def get_length(index):
|
| 59 |
+
return index, len(global_dataset[index]["input_ids"])
|
| 60 |
+
|
| 61 |
+
def get_sequence_length(dataset, num_worker=16):
|
| 62 |
+
global global_dataset
|
| 63 |
+
global_dataset = dataset
|
| 64 |
+
num_data = len(dataset)
|
| 65 |
+
lengths = [0] * num_data
|
| 66 |
+
with Pool(processes=num_worker) as pool:
|
| 67 |
+
iters = pool.imap(get_length, range(num_data))
|
| 68 |
+
for i, length in tqdm(iters, total=num_data):
|
| 69 |
+
lengths[i] = length
|
| 70 |
+
return lengths
|
| 71 |
+
|
| 72 |
+
def get_data(index):
|
| 73 |
+
item = global_torch_dataset[index]
|
| 74 |
+
length = len(global_torch_dataset[index]["input_ids"])
|
| 75 |
+
return item, index, length
|
| 76 |
+
|
| 77 |
+
def torch_dataset_to_hf_dataset(torch_dataset, num_worker=16):
|
| 78 |
+
global global_torch_dataset
|
| 79 |
+
global_torch_dataset = torch_dataset
|
| 80 |
+
num_data = len(global_torch_dataset)
|
| 81 |
+
lengths = [0] * num_data
|
| 82 |
+
hf_dict = {key: [] for key in torch_dataset[0].keys()}
|
| 83 |
+
with Pool(processes=num_worker) as pool:
|
| 84 |
+
iters = pool.imap(get_data, range(num_data))
|
| 85 |
+
for data, i, length in tqdm(iters, total=num_data):
|
| 86 |
+
for key, value in data.items():
|
| 87 |
+
hf_dict[key].append(value)
|
| 88 |
+
lengths[i] = length
|
| 89 |
+
hf_dataset = Dataset.from_dict(hf_dict)
|
| 90 |
+
return hf_dataset, lengths
|
| 91 |
+
|
| 92 |
+
def _get_package_version(name: str) -> "Version":
|
| 93 |
+
try:
|
| 94 |
+
return version.parse(importlib.metadata.version(name))
|
| 95 |
+
except Exception:
|
| 96 |
+
return version.parse("0.0.0")
|
| 97 |
+
|
| 98 |
+
@lru_cache
|
| 99 |
+
def is_transformers_version_greater_than(content: str):
|
| 100 |
+
return _get_package_version("transformers") >= version.parse(content)
|
| 101 |
+
|
| 102 |
+
@lru_cache
|
| 103 |
+
def is_transformers_version_equal_to_4_46():
|
| 104 |
+
return version.parse("4.46.0") <= _get_package_version("transformers") <= version.parse("4.46.1")
|
| 105 |
+
|
| 106 |
+
def search_for_fit(numbers: Sequence[int], capacity: int) -> int:
|
| 107 |
+
r"""
|
| 108 |
+
Finds the index of largest number that fits into the knapsack with the given capacity.
|
| 109 |
+
"""
|
| 110 |
+
index = bisect.bisect(numbers, capacity)
|
| 111 |
+
return -1 if index == 0 else (index - 1)
|
| 112 |
+
|
| 113 |
+
def greedy_knapsack(numbers: List[int], capacity: int) -> List[List[int]]:
|
| 114 |
+
r"""
|
| 115 |
+
An efficient greedy algorithm with binary search for the knapsack problem.
|
| 116 |
+
"""
|
| 117 |
+
numbers.sort() # sort numbers in ascending order for binary search
|
| 118 |
+
knapsacks = []
|
| 119 |
+
|
| 120 |
+
while numbers:
|
| 121 |
+
current_knapsack = []
|
| 122 |
+
remaining_capacity = capacity
|
| 123 |
+
|
| 124 |
+
while True:
|
| 125 |
+
index = search_for_fit(numbers, remaining_capacity)
|
| 126 |
+
if index == -1:
|
| 127 |
+
break # no more numbers fit in this knapsack
|
| 128 |
+
|
| 129 |
+
remaining_capacity -= numbers[index] # update the remaining capacity
|
| 130 |
+
current_knapsack.append(numbers.pop(index)) # add the number to knapsack
|
| 131 |
+
|
| 132 |
+
knapsacks.append(current_knapsack)
|
| 133 |
+
|
| 134 |
+
return knapsacks
|
| 135 |
+
|
| 136 |
+
def preprocess_packed_supervised_dataset(examples, tokenizer, cutoff_len):
|
| 137 |
+
valid_num = 0
|
| 138 |
+
batch_input_ids, batch_labels = [], []
|
| 139 |
+
lengths = []
|
| 140 |
+
length2indexes = defaultdict(list)
|
| 141 |
+
for i in range(len(examples["input_ids"])):
|
| 142 |
+
input_ids, labels = examples["input_ids"][i], examples["labels"][i]
|
| 143 |
+
length = len(input_ids)
|
| 144 |
+
if length >= cutoff_len - 1:
|
| 145 |
+
continue
|
| 146 |
+
else:
|
| 147 |
+
lengths.append(length)
|
| 148 |
+
length2indexes[length].append(valid_num)
|
| 149 |
+
batch_input_ids.append(input_ids)
|
| 150 |
+
batch_labels.append(labels)
|
| 151 |
+
valid_num += 1
|
| 152 |
+
model_inputs = defaultdict(list)
|
| 153 |
+
knapsacks = greedy_knapsack(lengths, cutoff_len - 1)
|
| 154 |
+
for knapsack in knapsacks:
|
| 155 |
+
packed_input_ids, packed_attention_masks, packed_labels = [], [], []
|
| 156 |
+
for i, length in enumerate(knapsack):
|
| 157 |
+
index = length2indexes[length].pop()
|
| 158 |
+
packed_input_ids += batch_input_ids[index]
|
| 159 |
+
packed_labels += batch_labels[index]
|
| 160 |
+
packed_attention_masks += [1] * len(batch_input_ids[index])
|
| 161 |
+
|
| 162 |
+
if len(packed_input_ids) < cutoff_len:
|
| 163 |
+
pad_length = cutoff_len - len(packed_input_ids)
|
| 164 |
+
packed_input_ids += [tokenizer.pad_token_id] * pad_length
|
| 165 |
+
packed_labels += [IGNORE_INDEX] * pad_length
|
| 166 |
+
packed_attention_masks += [1] * pad_length # more efficient flash_attn
|
| 167 |
+
|
| 168 |
+
if len(packed_input_ids) != cutoff_len:
|
| 169 |
+
raise ValueError("The length of packed example should be identical to the cutoff length.")
|
| 170 |
+
|
| 171 |
+
model_inputs["input_ids"].append(packed_input_ids)
|
| 172 |
+
model_inputs["attention_mask"].append(packed_attention_masks)
|
| 173 |
+
model_inputs["position_ids"].append(list(range(len(packed_input_ids))))
|
| 174 |
+
model_inputs["labels"].append(packed_labels)
|
| 175 |
+
return model_inputs
|
| 176 |
+
|
| 177 |
+
def pad_sequence(examples, cutoff_len, tokenizer):
|
| 178 |
+
max_length = cutoff_len
|
| 179 |
+
input_pad_token_id = tokenizer.pad_token_id
|
| 180 |
+
label_pad_token_id = IGNORE_INDEX
|
| 181 |
+
|
| 182 |
+
for k, v in examples.items():
|
| 183 |
+
if k.endswith("input_ids"):
|
| 184 |
+
pad_token_id = input_pad_token_id
|
| 185 |
+
elif k.endswith("labels"):
|
| 186 |
+
pad_token_id = label_pad_token_id
|
| 187 |
+
# shift labels here
|
| 188 |
+
for i in range(len(v)):
|
| 189 |
+
v[i] = v[i][1:]
|
| 190 |
+
elif k.endswith("attention_mask"):
|
| 191 |
+
pad_token_id = 0
|
| 192 |
+
elif k.endswith("position_ids"):
|
| 193 |
+
pad_token_id = max_length - 1 # pad the max position id
|
| 194 |
+
elif k == "images" or k == "videos":
|
| 195 |
+
pad_token_id = -1
|
| 196 |
+
continue # TODO: haven't tested multi-modal yet
|
| 197 |
+
else:
|
| 198 |
+
continue
|
| 199 |
+
for i in range(len(v)):
|
| 200 |
+
v[i].extend([pad_token_id] * (max_length - len(v[i])))
|
| 201 |
+
examples[k] = v
|
| 202 |
+
|
| 203 |
+
return examples
|
| 204 |
+
|
| 205 |
+
def preprocess_sp_dataset(seq_ids, world_size, sequence_parallel_mode):
|
| 206 |
+
if sequence_parallel_mode == "zigzag-ring":
|
| 207 |
+
step = len(seq_ids) // (2 * world_size)
|
| 208 |
+
value_chunks = [seq_ids[s : s + step] for s in range(0, len(seq_ids), step)]
|
| 209 |
+
local_values = list()
|
| 210 |
+
for rank in range(world_size):
|
| 211 |
+
local_values.append(value_chunks[rank] + value_chunks[2 * world_size - rank - 1])
|
| 212 |
+
return local_values
|
| 213 |
+
elif sequence_parallel_mode == "ulysses":
|
| 214 |
+
step = len(seq_ids) // world_size
|
| 215 |
+
local_values = [seq_ids[s : s + step] for s in range(0, len(seq_ids), step)]
|
| 216 |
+
return local_values
|
| 217 |
+
else:
|
| 218 |
+
raise NotImplementedError("Other sequence parallel modes are to be implemented.")
|
| 219 |
+
|
| 220 |
+
|
| 221 |
+
# sp for Sequence Parallel
|
| 222 |
+
def sp_split(examples, sequence_parallel_size, sequence_parallel_mode="ulysses"):
|
| 223 |
+
for k, v in examples.items():
|
| 224 |
+
chunks = list()
|
| 225 |
+
for row in v:
|
| 226 |
+
if k.endswith("attention_mask"):
|
| 227 |
+
chunks.extend([row] * sequence_parallel_size)
|
| 228 |
+
elif row is None:
|
| 229 |
+
chunks.extend([None] * sequence_parallel_size)
|
| 230 |
+
else:
|
| 231 |
+
chunks.extend(
|
| 232 |
+
preprocess_sp_dataset(row, sequence_parallel_size, sequence_parallel_mode)
|
| 233 |
+
)
|
| 234 |
+
examples[k] = chunks
|
| 235 |
+
return examples
|
| 236 |
+
|
| 237 |
+
def get_sequence_parallel_preprocess(stage, tokenizer, cutoff_len=None, sequence_parallel_size=1, sequence_parallel_mode="ulysses"):
|
| 238 |
+
if stage == "pad":
|
| 239 |
+
assert cutoff_len is not None
|
| 240 |
+
preprocess_func = partial(pad_sequence, cutoff_len=cutoff_len, tokenizer=tokenizer)
|
| 241 |
+
elif stage == "split":
|
| 242 |
+
preprocess_func = partial(sp_split, sequence_parallel_size=sequence_parallel_size, sequence_parallel_mode=sequence_parallel_mode)
|
| 243 |
+
else:
|
| 244 |
+
raise NotImplementedError(f"Unexpected stage in sequence_parallel_preprocess: {stage}")
|
| 245 |
+
|
| 246 |
+
return preprocess_func
|
| 247 |
+
|
| 248 |
+
def _get_sequence_parallel_dataset(dataset, num_works, tokenizer=None, cutoff_len=10000,
|
| 249 |
+
sequence_parallel_size=1, sequence_parallel_mode="ulysses",
|
| 250 |
+
cache_dataset_overwrite=False) -> Optional[Union["Dataset", "IterableDataset"]]:
|
| 251 |
+
kwargs = dict(
|
| 252 |
+
num_proc=num_works,
|
| 253 |
+
load_from_cache_file=not cache_dataset_overwrite,
|
| 254 |
+
desc="Running padding split on dataset",
|
| 255 |
+
)
|
| 256 |
+
pad_sequence_func = get_sequence_parallel_preprocess(
|
| 257 |
+
stage="pad",
|
| 258 |
+
tokenizer=tokenizer,
|
| 259 |
+
cutoff_len=cutoff_len
|
| 260 |
+
)
|
| 261 |
+
padded_dataset = dataset.map(
|
| 262 |
+
pad_sequence_func, batched=True, batch_size=num_works, **kwargs
|
| 263 |
+
)
|
| 264 |
+
kwargs = dict(
|
| 265 |
+
num_proc=num_works,
|
| 266 |
+
load_from_cache_file=not cache_dataset_overwrite,
|
| 267 |
+
desc="Running sequence parallel split on dataset",
|
| 268 |
+
)
|
| 269 |
+
sp_dataset_func = get_sequence_parallel_preprocess(
|
| 270 |
+
stage="split",
|
| 271 |
+
tokenizer=tokenizer,
|
| 272 |
+
sequence_parallel_size=sequence_parallel_size,
|
| 273 |
+
sequence_parallel_mode=sequence_parallel_mode,
|
| 274 |
+
)
|
| 275 |
+
sp_dataset = padded_dataset.map(
|
| 276 |
+
sp_dataset_func, batched=True, batch_size=num_works, **kwargs
|
| 277 |
+
)
|
| 278 |
+
return sp_dataset
|
| 279 |
+
|
| 280 |
+
def packing_dataset(dataset, tokenizer, cutoff_len, num_worker, cache_dataset_overwrite):
|
| 281 |
+
preprocess_func = partial(preprocess_packed_supervised_dataset, tokenizer=tokenizer, cutoff_len=cutoff_len)
|
| 282 |
+
kwargs = dict(
|
| 283 |
+
num_proc=num_worker,
|
| 284 |
+
load_from_cache_file=not cache_dataset_overwrite,
|
| 285 |
+
desc="Running postprocess on dataset",
|
| 286 |
+
)
|
| 287 |
+
import pdb; pdb.set_trace()
|
| 288 |
+
dataset = dataset.map(
|
| 289 |
+
preprocess_func,
|
| 290 |
+
batched=True,
|
| 291 |
+
batch_size=num_worker,
|
| 292 |
+
**kwargs,
|
| 293 |
+
)
|
| 294 |
+
return dataset
|
| 295 |
+
|
| 296 |
+
def data_post_process_sequence_parallel(
|
| 297 |
+
dataset,
|
| 298 |
+
training_args,
|
| 299 |
+
sequence_parallel_size,
|
| 300 |
+
sequence_parallel_mode,
|
| 301 |
+
cutoff_len,
|
| 302 |
+
num_worker=16,
|
| 303 |
+
packing=False,
|
| 304 |
+
tokenizer=None,
|
| 305 |
+
cache_dataset_overwrite=False,
|
| 306 |
+
):
|
| 307 |
+
dataset = dataset.shuffle(seed=training_args.seed)
|
| 308 |
+
if packing:
|
| 309 |
+
dataset = packing_dataset(dataset, tokenizer, cutoff_len, num_worker, cache_dataset_overwrite)
|
| 310 |
+
|
| 311 |
+
dataset = _get_sequence_parallel_dataset(dataset,
|
| 312 |
+
num_works=num_worker,
|
| 313 |
+
tokenizer=tokenizer,
|
| 314 |
+
cutoff_len=cutoff_len,
|
| 315 |
+
sequence_parallel_size=sequence_parallel_size,
|
| 316 |
+
sequence_parallel_mode=sequence_parallel_mode,
|
| 317 |
+
cache_dataset_overwrite=cache_dataset_overwrite)
|
| 318 |
+
return dataset
|
src/utils/gpu_monitor.py
ADDED
|
@@ -0,0 +1,384 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pynvml
|
| 2 |
+
import time
|
| 3 |
+
import threading
|
| 4 |
+
from datetime import datetime
|
| 5 |
+
import sys
|
| 6 |
+
import os
|
| 7 |
+
|
| 8 |
+
class GPUMemoryMonitor:
|
| 9 |
+
def __init__(self, gpu_index=0, interval=1.0, unit='GB'):
|
| 10 |
+
"""
|
| 11 |
+
初始化GPU显存监控器
|
| 12 |
+
|
| 13 |
+
Args:
|
| 14 |
+
gpu_index: 要监控的GPU索引,默认0
|
| 15 |
+
interval: 监控间隔时间(秒),默认1秒
|
| 16 |
+
unit: 返回的单位,支持 'MB' 或 'GB',默认'GB'
|
| 17 |
+
"""
|
| 18 |
+
self.gpu_index = gpu_index
|
| 19 |
+
self.interval = interval
|
| 20 |
+
self.unit = unit.upper()
|
| 21 |
+
|
| 22 |
+
# 验证单位参数
|
| 23 |
+
if self.unit not in ['MB', 'GB']:
|
| 24 |
+
raise ValueError("单位必须是 'MB' 或 'GB'")
|
| 25 |
+
|
| 26 |
+
# 监控相关状态
|
| 27 |
+
self.monitor_thread = None
|
| 28 |
+
self._running = False
|
| 29 |
+
self._lock = threading.Lock()
|
| 30 |
+
self.peak_memory_usage = 0 # 峰值显存使用量
|
| 31 |
+
self.start_time = None
|
| 32 |
+
self.stop_time = None
|
| 33 |
+
|
| 34 |
+
# 初始化NVML
|
| 35 |
+
try:
|
| 36 |
+
pynvml.nvmlInit()
|
| 37 |
+
self.device_count = pynvml.nvmlDeviceGetCount()
|
| 38 |
+
if self.gpu_index >= self.device_count:
|
| 39 |
+
raise ValueError(f"GPU索引 {self.gpu_index} 超出范围,系统只有 {self.device_count} 个GPU")
|
| 40 |
+
self.handle = pynvml.nvmlDeviceGetHandleByIndex(self.gpu_index)
|
| 41 |
+
|
| 42 |
+
# 获取GPU名称
|
| 43 |
+
self.gpu_name = pynvml.nvmlDeviceGetName(self.handle)
|
| 44 |
+
|
| 45 |
+
except Exception as e:
|
| 46 |
+
print(f"初始化NVML失败: {e}")
|
| 47 |
+
if 'pynvml' in sys.modules:
|
| 48 |
+
pynvml.nvmlShutdown()
|
| 49 |
+
raise
|
| 50 |
+
|
| 51 |
+
def _get_memory_info(self):
|
| 52 |
+
"""获取当前GPU显存信息"""
|
| 53 |
+
try:
|
| 54 |
+
mem_info = pynvml.nvmlDeviceGetMemoryInfo(self.handle)
|
| 55 |
+
return mem_info
|
| 56 |
+
except Exception as e:
|
| 57 |
+
print(f"获取GPU {self.gpu_index} 显存信息失败: {e}")
|
| 58 |
+
return None
|
| 59 |
+
|
| 60 |
+
def _convert_units(self, bytes_value):
|
| 61 |
+
"""转换字节为指定单位"""
|
| 62 |
+
if self.unit == 'MB':
|
| 63 |
+
return bytes_value / (1024 * 1024)
|
| 64 |
+
else: # GB
|
| 65 |
+
return bytes_value / (1024 * 1024 * 1024)
|
| 66 |
+
|
| 67 |
+
def _get_current_usage(self):
|
| 68 |
+
"""获取当前显存使用量(按指定单位)"""
|
| 69 |
+
mem_info = self._get_memory_info()
|
| 70 |
+
if mem_info:
|
| 71 |
+
return self._convert_units(mem_info.used)
|
| 72 |
+
return 0
|
| 73 |
+
|
| 74 |
+
def _monitor_loop(self):
|
| 75 |
+
"""监控循环,运行在单独线程中"""
|
| 76 |
+
# print(f"开始监控 GPU {self.gpu_index} ({self.gpu_name})")
|
| 77 |
+
# print(f"监控间隔: {self.interval}秒")
|
| 78 |
+
# print(f"单位: {self.unit}")
|
| 79 |
+
# print("按回车键停止监控...\n")
|
| 80 |
+
|
| 81 |
+
try:
|
| 82 |
+
while self._running:
|
| 83 |
+
# 获取当前显存使用量
|
| 84 |
+
current_usage = self._get_current_usage()
|
| 85 |
+
|
| 86 |
+
# 更新峰值
|
| 87 |
+
with self._lock:
|
| 88 |
+
if current_usage > self.peak_memory_usage:
|
| 89 |
+
self.peak_memory_usage = current_usage
|
| 90 |
+
|
| 91 |
+
# 等待下一个监控周期
|
| 92 |
+
time.sleep(self.interval)
|
| 93 |
+
|
| 94 |
+
except Exception as e:
|
| 95 |
+
print(f"监控线程出错: {e}")
|
| 96 |
+
finally:
|
| 97 |
+
# print(f"GPU {self.gpu_index} 监控线程结束")
|
| 98 |
+
pass
|
| 99 |
+
|
| 100 |
+
def start(self):
|
| 101 |
+
"""
|
| 102 |
+
启动显存监控
|
| 103 |
+
|
| 104 |
+
Returns:
|
| 105 |
+
bool: 是否成功启动
|
| 106 |
+
"""
|
| 107 |
+
if self._running:
|
| 108 |
+
# print(f"GPU {self.gpu_index} 监控已经在运行中")
|
| 109 |
+
return False
|
| 110 |
+
|
| 111 |
+
try:
|
| 112 |
+
# 重置峰值数据
|
| 113 |
+
with self._lock:
|
| 114 |
+
self.peak_memory_usage = 0
|
| 115 |
+
|
| 116 |
+
# 记录开始时间
|
| 117 |
+
self.start_time = datetime.now()
|
| 118 |
+
|
| 119 |
+
# 启动监控线程
|
| 120 |
+
self._running = True
|
| 121 |
+
self.monitor_thread = threading.Thread(target=self._monitor_loop)
|
| 122 |
+
self.monitor_thread.daemon = True
|
| 123 |
+
self.monitor_thread.start()
|
| 124 |
+
|
| 125 |
+
# 验证线程已启动
|
| 126 |
+
time.sleep(0.1)
|
| 127 |
+
if self.monitor_thread.is_alive():
|
| 128 |
+
# print(f"GPU {self.gpu_index} 监控已启动")
|
| 129 |
+
return True
|
| 130 |
+
else:
|
| 131 |
+
self._running = False
|
| 132 |
+
# print(f"GPU {self.gpu_index} 监控线程启动失败")
|
| 133 |
+
return False
|
| 134 |
+
|
| 135 |
+
except Exception as e:
|
| 136 |
+
print(f"启动GPU {self.gpu_index} 监控失败: {e}")
|
| 137 |
+
self._running = False
|
| 138 |
+
return False
|
| 139 |
+
|
| 140 |
+
def stop(self, verbose=False):
|
| 141 |
+
"""
|
| 142 |
+
停止显存监控并返回峰值显存使用量
|
| 143 |
+
|
| 144 |
+
Args:
|
| 145 |
+
verbose: 是否打印详细信息
|
| 146 |
+
|
| 147 |
+
Returns:
|
| 148 |
+
float: 峰值显存使用量(单位:GB或MB)
|
| 149 |
+
"""
|
| 150 |
+
if not self._running:
|
| 151 |
+
if verbose:
|
| 152 |
+
print(f"GPU {self.gpu_index} 监控未在运行")
|
| 153 |
+
return 0.0
|
| 154 |
+
|
| 155 |
+
try:
|
| 156 |
+
# 停止监控线程
|
| 157 |
+
self._running = False
|
| 158 |
+
self.stop_time = datetime.now()
|
| 159 |
+
|
| 160 |
+
# 等待线程结束(最多等待2秒)
|
| 161 |
+
if self.monitor_thread and self.monitor_thread.is_alive():
|
| 162 |
+
self.monitor_thread.join(timeout=2.0)
|
| 163 |
+
|
| 164 |
+
# 获取最终峰值
|
| 165 |
+
peak_usage = 0.0
|
| 166 |
+
with self._lock:
|
| 167 |
+
peak_usage = self.peak_memory_usage
|
| 168 |
+
|
| 169 |
+
if verbose:
|
| 170 |
+
duration = (self.stop_time - self.start_time).total_seconds()
|
| 171 |
+
print(f"\n{'='*50}")
|
| 172 |
+
print(f"GPU {self.gpu_index} ({self.gpu_name}) 监控结果")
|
| 173 |
+
print(f"{'='*50}")
|
| 174 |
+
print(f"开始时间: {self.start_time.strftime('%Y-%m-%d %H:%M:%S')}")
|
| 175 |
+
print(f"结束时间: {self.stop_time.strftime('%Y-%m-%d %H:%M:%S')}")
|
| 176 |
+
print(f"监控时长: {duration:.1f} 秒")
|
| 177 |
+
print(f"峰值显存使用量: {peak_usage:.3f} {self.unit}")
|
| 178 |
+
|
| 179 |
+
# 获取当前显存信息作为对比
|
| 180 |
+
mem_info = self._get_memory_info()
|
| 181 |
+
if mem_info:
|
| 182 |
+
total = self._convert_units(mem_info.total)
|
| 183 |
+
current = self._convert_units(mem_info.used)
|
| 184 |
+
print(f"当前显存使用: {current:.3f} / {total:.3f} {self.unit}")
|
| 185 |
+
print(f"峰值占比: {(peak_usage/total*100):.1f}%")
|
| 186 |
+
print(f"{'='*50}")
|
| 187 |
+
|
| 188 |
+
return peak_usage
|
| 189 |
+
|
| 190 |
+
except Exception as e:
|
| 191 |
+
print(f"停止GPU {self.gpu_index} 监控时出错: {e}")
|
| 192 |
+
return 0.0
|
| 193 |
+
|
| 194 |
+
def get_current_usage(self):
|
| 195 |
+
"""获取当前显存使用量(不停止监控)"""
|
| 196 |
+
return self._get_current_usage()
|
| 197 |
+
|
| 198 |
+
def get_peak_usage(self):
|
| 199 |
+
"""获取当前记录的峰值显存使用量(不停止监控)"""
|
| 200 |
+
with self._lock:
|
| 201 |
+
return self.peak_memory_usage
|
| 202 |
+
|
| 203 |
+
def is_running(self):
|
| 204 |
+
"""检查监控是否在运行"""
|
| 205 |
+
return self._running
|
| 206 |
+
|
| 207 |
+
def __del__(self):
|
| 208 |
+
"""析构函数,确保清理资源"""
|
| 209 |
+
if self._running:
|
| 210 |
+
self.stop(verbose=False)
|
| 211 |
+
try:
|
| 212 |
+
pynvml.nvmlShutdown()
|
| 213 |
+
except:
|
| 214 |
+
pass
|
| 215 |
+
|
| 216 |
+
|
| 217 |
+
# 使用示例函数
|
| 218 |
+
def monitor_gpu_memory_example():
|
| 219 |
+
"""使用示例"""
|
| 220 |
+
print("GPU显存监控示例")
|
| 221 |
+
print("=" * 50)
|
| 222 |
+
|
| 223 |
+
try:
|
| 224 |
+
# 创建监控器
|
| 225 |
+
monitor = GPUMemoryMonitor(
|
| 226 |
+
gpu_index=0, # 监控第一个GPU
|
| 227 |
+
interval=0.5, # 每0.5秒检查一次
|
| 228 |
+
unit='GB' # 使用GB作为单位
|
| 229 |
+
)
|
| 230 |
+
|
| 231 |
+
# 启动监控
|
| 232 |
+
if monitor.start():
|
| 233 |
+
# 这里可以运行你的GPU任务
|
| 234 |
+
print("\n现在开始运行你的GPU任务...")
|
| 235 |
+
print("监控正在后台进行")
|
| 236 |
+
|
| 237 |
+
# 模拟一些工作(在实际使用中,这里应该是你的GPU任务)
|
| 238 |
+
print("按回车键停止监控并获取峰值显存用量...")
|
| 239 |
+
input() # 等待用户按回车
|
| 240 |
+
|
| 241 |
+
# 停止监控并获取结果
|
| 242 |
+
peak_memory_gb = monitor.stop()
|
| 243 |
+
|
| 244 |
+
print(f"\n监控完成!峰值显存用量: {peak_memory_gb:.3f} GB")
|
| 245 |
+
|
| 246 |
+
except Exception as e:
|
| 247 |
+
print(f"错误: {e}")
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
# 多GPU监控示例
|
| 251 |
+
class MultiGPUMonitor:
|
| 252 |
+
"""多GPU监控器"""
|
| 253 |
+
def __init__(self, gpu_indices=None, interval=1.0, unit='GB'):
|
| 254 |
+
"""
|
| 255 |
+
初始化多GPU监控器
|
| 256 |
+
|
| 257 |
+
Args:
|
| 258 |
+
gpu_indices: 要监控的GPU索引列表,None表示监控所有GPU
|
| 259 |
+
interval: 监控间隔
|
| 260 |
+
unit: 返回的单位
|
| 261 |
+
"""
|
| 262 |
+
pynvml.nvmlInit()
|
| 263 |
+
device_count = pynvml.nvmlDeviceGetCount()
|
| 264 |
+
|
| 265 |
+
if gpu_indices is None:
|
| 266 |
+
gpu_indices = list(range(device_count))
|
| 267 |
+
|
| 268 |
+
self.monitors = []
|
| 269 |
+
for idx in gpu_indices:
|
| 270 |
+
if idx < device_count:
|
| 271 |
+
monitor = GPUMemoryMonitor(gpu_index=idx, interval=interval, unit=unit)
|
| 272 |
+
self.monitors.append(monitor)
|
| 273 |
+
else:
|
| 274 |
+
print(f"警告: GPU索引 {idx} 不存在,跳过")
|
| 275 |
+
|
| 276 |
+
def start_all(self):
|
| 277 |
+
"""启动所有GPU监控"""
|
| 278 |
+
results = []
|
| 279 |
+
for monitor in self.monitors:
|
| 280 |
+
success = monitor.start()
|
| 281 |
+
results.append((monitor.gpu_index, success))
|
| 282 |
+
return results
|
| 283 |
+
|
| 284 |
+
def stop_all(self):
|
| 285 |
+
"""停止所有GPU监控并返回结果"""
|
| 286 |
+
results = {}
|
| 287 |
+
max_peak = 0.0
|
| 288 |
+
for monitor in self.monitors:
|
| 289 |
+
peak = monitor.stop()
|
| 290 |
+
results[monitor.gpu_index] = {
|
| 291 |
+
'name': monitor.gpu_name,
|
| 292 |
+
'peak_memory': peak,
|
| 293 |
+
'unit': monitor.unit
|
| 294 |
+
}
|
| 295 |
+
if peak > max_peak:
|
| 296 |
+
max_peak = peak
|
| 297 |
+
return results, max_peak
|
| 298 |
+
|
| 299 |
+
def stop_all_and_get_max(self):
|
| 300 |
+
"""停止所有监控并返回最大峰值"""
|
| 301 |
+
results = self.stop_all()
|
| 302 |
+
if not results:
|
| 303 |
+
return 0.0
|
| 304 |
+
|
| 305 |
+
max_peak = max(item['peak_memory'] for item in results.values())
|
| 306 |
+
return max_peak
|
| 307 |
+
|
| 308 |
+
|
| 309 |
+
# 快速使用函数
|
| 310 |
+
def quick_monitor(gpu_index=0, interval=0.5, unit='GB', wait_for_input=True):
|
| 311 |
+
"""
|
| 312 |
+
快速启动监控的便捷函数
|
| 313 |
+
|
| 314 |
+
Args:
|
| 315 |
+
gpu_index: GPU索引
|
| 316 |
+
interval: 监控间隔
|
| 317 |
+
unit: 单位
|
| 318 |
+
wait_for_input: 是否等待用户输入
|
| 319 |
+
|
| 320 |
+
Returns:
|
| 321 |
+
float: 峰值显存使用量
|
| 322 |
+
"""
|
| 323 |
+
monitor = GPUMemoryMonitor(gpu_index=gpu_index, interval=interval, unit=unit)
|
| 324 |
+
|
| 325 |
+
try:
|
| 326 |
+
if monitor.start():
|
| 327 |
+
if wait_for_input:
|
| 328 |
+
print("按回车键停止监控...")
|
| 329 |
+
input()
|
| 330 |
+
else:
|
| 331 |
+
# 如果不等待用户输入,这里可以设置其他停止条件
|
| 332 |
+
# 例如:监控特定时间或直到某个条件满足
|
| 333 |
+
print("监控已启动,将在后台运行")
|
| 334 |
+
print("调用 monitor.stop() 来停止并获取结果")
|
| 335 |
+
return monitor
|
| 336 |
+
return None
|
| 337 |
+
except Exception as e:
|
| 338 |
+
print(f"监控失败: {e}")
|
| 339 |
+
return None
|
| 340 |
+
|
| 341 |
+
|
| 342 |
+
# 测试代码
|
| 343 |
+
if __name__ == "__main__":
|
| 344 |
+
# # 示例1: 基本使用
|
| 345 |
+
# print("示例1: 基本使用")
|
| 346 |
+
# monitor = GPUMemoryMonitor(gpu_index=0, interval=0.5, unit='GB')
|
| 347 |
+
# monitor.start()
|
| 348 |
+
|
| 349 |
+
# # 模拟一些GPU工作
|
| 350 |
+
# print("模拟GPU工作...")
|
| 351 |
+
# time.sleep(3)
|
| 352 |
+
|
| 353 |
+
# # 停止并获取结果
|
| 354 |
+
# peak = monitor.stop()
|
| 355 |
+
# print(f"峰值显存: {peak:.3f} GB\n")
|
| 356 |
+
|
| 357 |
+
# # 示例2: 使用便捷函数
|
| 358 |
+
# print("示例2: 使用便捷函数")
|
| 359 |
+
# result = quick_monitor(gpu_index=0, interval=0.2, unit='MB')
|
| 360 |
+
# if result:
|
| 361 |
+
# # 这里 result 是 monitor 对象
|
| 362 |
+
# time.sleep(2)
|
| 363 |
+
# peak = result.stop()
|
| 364 |
+
# print(f"峰值显存: {peak:.3f} MB")
|
| 365 |
+
|
| 366 |
+
# 示例3: 多GPU监控
|
| 367 |
+
print("\n示例3: 多GPU监控")
|
| 368 |
+
try:
|
| 369 |
+
pynvml.nvmlInit()
|
| 370 |
+
device_count = pynvml.nvmlDeviceGetCount()
|
| 371 |
+
print(f"系统中有 {device_count} 个GPU")
|
| 372 |
+
|
| 373 |
+
if device_count > 1:
|
| 374 |
+
# multi_monitor = MultiGPUMonitor(gpu_indices=[0, 1], interval=0.5, unit='GB')
|
| 375 |
+
multi_monitor = MultiGPUMonitor(interval=5, unit='GB')
|
| 376 |
+
multi_monitor.start_all()
|
| 377 |
+
time.sleep(2)
|
| 378 |
+
results, max_peak = multi_monitor.stop_all()
|
| 379 |
+
|
| 380 |
+
for gpu_idx, data in results.items():
|
| 381 |
+
print(f"GPU {gpu_idx} ({data['name']}): {data['peak_memory']:.3f} {data['unit']}")
|
| 382 |
+
print(f"最大峰值显存使用量: {max_peak:.3f} GB")
|
| 383 |
+
except:
|
| 384 |
+
pass
|
src/utils/gpu_worker.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import torch
|
| 3 |
+
|
| 4 |
+
class GpuWorker:
|
| 5 |
+
|
| 6 |
+
def __init__(self, gpu_id: int, envs: dict):
|
| 7 |
+
self.gpu_id = gpu_id
|
| 8 |
+
self._setup_environment(envs)
|
| 9 |
+
self.device = self._setup_device()
|
| 10 |
+
|
| 11 |
+
def _setup_environment(self, envs):
|
| 12 |
+
"""设置环境变量"""
|
| 13 |
+
for k, v in envs.items():
|
| 14 |
+
os.environ[k] = v
|
| 15 |
+
|
| 16 |
+
def _setup_device(self):
|
| 17 |
+
"""设置CUDA设备"""
|
| 18 |
+
torch.cuda.set_device(self.gpu_id)
|
| 19 |
+
return f"cuda:{self.gpu_id}"
|
src/utils/misc.py
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import subprocess
|
| 3 |
+
import math
|
| 4 |
+
import torch
|
| 5 |
+
import torch.distributed as dist
|
| 6 |
+
import transformers
|
| 7 |
+
from typing import Union, Iterable, List, Dict, Tuple, Optional
|
| 8 |
+
|
| 9 |
+
class DictWithDotAccess(dict):
|
| 10 |
+
def __init__(self, *args, **kwargs):
|
| 11 |
+
super().__init__(*args, **kwargs)
|
| 12 |
+
for arg in args:
|
| 13 |
+
if isinstance(arg, dict):
|
| 14 |
+
for key, value in arg.items():
|
| 15 |
+
if isinstance(value, dict):
|
| 16 |
+
value = DictWithDotAccess(value)
|
| 17 |
+
self[key] = value
|
| 18 |
+
|
| 19 |
+
if kwargs:
|
| 20 |
+
for key, value in kwargs.items():
|
| 21 |
+
if isinstance(value, dict):
|
| 22 |
+
value = DictWithDotAccess(value)
|
| 23 |
+
self[key] = value
|
| 24 |
+
|
| 25 |
+
def __getattr__(self, attr):
|
| 26 |
+
return self.get(attr)
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def is_dist_avail_and_initialized():
|
| 30 |
+
if not dist.is_available():
|
| 31 |
+
return False
|
| 32 |
+
if not dist.is_initialized():
|
| 33 |
+
return False
|
| 34 |
+
return True
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def get_rank():
|
| 38 |
+
if not is_dist_avail_and_initialized():
|
| 39 |
+
return 0
|
| 40 |
+
return dist.get_rank()
|
| 41 |
+
|
| 42 |
+
def get_local_rank():
|
| 43 |
+
if not is_dist_avail_and_initialized():
|
| 44 |
+
return 0
|
| 45 |
+
return int(os.environ["LOCAL_RANK"])
|
| 46 |
+
|
| 47 |
+
def Print(*args):
|
| 48 |
+
if get_rank() == 0:
|
| 49 |
+
print(*args)
|
| 50 |
+
|
| 51 |
+
def get_sha():
|
| 52 |
+
cwd = os.path.dirname(os.path.abspath(__file__))
|
| 53 |
+
|
| 54 |
+
def _run(command):
|
| 55 |
+
return subprocess.check_output(command, cwd=cwd).decode("ascii").strip()
|
| 56 |
+
|
| 57 |
+
sha = "N/A"
|
| 58 |
+
diff = "clean"
|
| 59 |
+
branch = "N/A"
|
| 60 |
+
try:
|
| 61 |
+
sha = _run(["git", "rev-parse", "HEAD"])
|
| 62 |
+
subprocess.check_output(["git", "diff"], cwd=cwd)
|
| 63 |
+
diff = _run(["git", "diff-index", "HEAD"])
|
| 64 |
+
diff = "has uncommited changes" if diff else "clean"
|
| 65 |
+
branch = _run(["git", "rev-parse", "--abbrev-ref", "HEAD"])
|
| 66 |
+
except Exception:
|
| 67 |
+
pass
|
| 68 |
+
message = f"sha: {sha}, status: {diff}, branch: {branch}"
|
| 69 |
+
return message
|
| 70 |
+
|
| 71 |
+
def patch_cosine_with_warmup_schedule(minimal_lr=0.0):
|
| 72 |
+
def _get_cosine_schedule_with_warmup_lr_lambda(
|
| 73 |
+
current_step: int, *, num_warmup_steps: int, num_training_steps: int, num_cycles: float
|
| 74 |
+
):
|
| 75 |
+
if current_step < num_warmup_steps:
|
| 76 |
+
return float(current_step) / float(max(1, num_warmup_steps))
|
| 77 |
+
progress = float(current_step - num_warmup_steps) / float(max(1, num_training_steps - num_warmup_steps))
|
| 78 |
+
return max(minimal_lr, 0.5 * (1.0 + math.cos(math.pi * float(num_cycles) * 2.0 * progress)))
|
| 79 |
+
|
| 80 |
+
transformers.optimization._get_cosine_schedule_with_warmup_lr_lambda = _get_cosine_schedule_with_warmup_lr_lambda
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
# patch torch clip grad norm so that we can skip nan grad norm
|
| 84 |
+
from torch import Tensor, inf
|
| 85 |
+
from torch.utils._foreach_utils import _group_tensors_by_device_and_dtype, _has_foreach_support
|
| 86 |
+
_tensor_or_tensors = Union[torch.Tensor, Iterable[torch.Tensor]]
|
| 87 |
+
def clip_grad_norm_(
|
| 88 |
+
parameters: _tensor_or_tensors, max_norm: float, norm_type: float = 2.0,
|
| 89 |
+
error_if_nonfinite: bool = False, foreach: Optional[bool] = None) -> torch.Tensor:
|
| 90 |
+
r"""Clips gradient norm of an iterable of parameters.
|
| 91 |
+
|
| 92 |
+
The norm is computed over all gradients together, as if they were
|
| 93 |
+
concatenated into a single vector. Gradients are modified in-place.
|
| 94 |
+
|
| 95 |
+
Args:
|
| 96 |
+
parameters (Iterable[Tensor] or Tensor): an iterable of Tensors or a
|
| 97 |
+
single Tensor that will have gradients normalized
|
| 98 |
+
max_norm (float): max norm of the gradients
|
| 99 |
+
norm_type (float): type of the used p-norm. Can be ``'inf'`` for
|
| 100 |
+
infinity norm.
|
| 101 |
+
error_if_nonfinite (bool): if True, an error is thrown if the total
|
| 102 |
+
norm of the gradients from :attr:`parameters` is ``nan``,
|
| 103 |
+
``inf``, or ``-inf``. Default: False (will switch to True in the future)
|
| 104 |
+
foreach (bool): use the faster foreach-based implementation.
|
| 105 |
+
If ``None``, use the foreach implementation for CUDA and CPU native tensors and silently
|
| 106 |
+
fall back to the slow implementation for other device types.
|
| 107 |
+
Default: ``None``
|
| 108 |
+
|
| 109 |
+
Returns:
|
| 110 |
+
Total norm of the parameter gradients (viewed as a single vector).
|
| 111 |
+
"""
|
| 112 |
+
if isinstance(parameters, torch.Tensor):
|
| 113 |
+
parameters = [parameters]
|
| 114 |
+
grads = [p.grad for p in parameters if p.grad is not None]
|
| 115 |
+
max_norm = float(max_norm)
|
| 116 |
+
norm_type = float(norm_type)
|
| 117 |
+
if len(grads) == 0:
|
| 118 |
+
return torch.tensor(0.)
|
| 119 |
+
|
| 120 |
+
if torch.isnan(max_norm) or torch.isinf(max_norm):
|
| 121 |
+
for grad in grads:
|
| 122 |
+
grad.zero_()
|
| 123 |
+
print('>>>Found nan or inf max_norm, set grads to zero')
|
| 124 |
+
return torch.tensor(0.)
|
| 125 |
+
|
| 126 |
+
first_device = grads[0].device
|
| 127 |
+
grouped_grads: Dict[Tuple[torch.device, torch.dtype], List[List[Tensor]]] \
|
| 128 |
+
= _group_tensors_by_device_and_dtype([[g.detach() for g in grads]]) # type: ignore[assignment]
|
| 129 |
+
|
| 130 |
+
if norm_type == inf:
|
| 131 |
+
norms = [g.detach().abs().max().to(first_device) for g in grads]
|
| 132 |
+
total_norm = norms[0] if len(norms) == 1 else torch.max(torch.stack(norms))
|
| 133 |
+
else:
|
| 134 |
+
norms = []
|
| 135 |
+
for ((device, _), [grads]) in grouped_grads.items():
|
| 136 |
+
if (foreach is None or foreach) and _has_foreach_support(grads, device=device):
|
| 137 |
+
norms.extend(torch._foreach_norm(grads, norm_type))
|
| 138 |
+
elif foreach:
|
| 139 |
+
raise RuntimeError(f'foreach=True was passed, but can\'t use the foreach API on {device.type} tensors')
|
| 140 |
+
else:
|
| 141 |
+
norms.extend([torch.norm(g, norm_type) for g in grads])
|
| 142 |
+
|
| 143 |
+
total_norm = torch.norm(torch.stack([norm.to(first_device) for norm in norms]), norm_type)
|
| 144 |
+
|
| 145 |
+
if torch.isnan(total_norm) or torch.isinf(total_norm):
|
| 146 |
+
for grad in grads:
|
| 147 |
+
grad.zero_()
|
| 148 |
+
print('>>>Found nan or inf total_norm, set grads to zero')
|
| 149 |
+
return torch.tensor(0.)
|
| 150 |
+
|
| 151 |
+
if error_if_nonfinite and torch.logical_or(total_norm.isnan(), total_norm.isinf()):
|
| 152 |
+
raise RuntimeError(
|
| 153 |
+
f'The total norm of order {norm_type} for gradients from '
|
| 154 |
+
'`parameters` is non-finite, so it cannot be clipped. To disable '
|
| 155 |
+
'this error and scale the gradients by the non-finite norm anyway, '
|
| 156 |
+
'set `error_if_nonfinite=False`')
|
| 157 |
+
clip_coef = max_norm / (total_norm + 1e-6)
|
| 158 |
+
# Note: multiplying by the clamped coef is redundant when the coef is clamped to 1, but doing so
|
| 159 |
+
# avoids a `if clip_coef < 1:` conditional which can require a CPU <=> device synchronization
|
| 160 |
+
# when the gradients do not reside in CPU memory.
|
| 161 |
+
clip_coef_clamped = torch.clamp(clip_coef, max=1.0)
|
| 162 |
+
for ((device, _), [grads]) in grouped_grads.items():
|
| 163 |
+
if (foreach is None or foreach) and _has_foreach_support(grads, device=device):
|
| 164 |
+
torch._foreach_mul_(grads, clip_coef_clamped.to(device)) # type: ignore[call-overload]
|
| 165 |
+
elif foreach:
|
| 166 |
+
raise RuntimeError(f'foreach=True was passed, but can\'t use the foreach API on {device.type} tensors')
|
| 167 |
+
else:
|
| 168 |
+
clip_coef_clamped_device = clip_coef_clamped.to(device)
|
| 169 |
+
for g in grads:
|
| 170 |
+
g.detach().mul_(clip_coef_clamped_device)
|
| 171 |
+
|
| 172 |
+
return total_norm
|
| 173 |
+
|
| 174 |
+
def patch_torch_clip_grad_norm():
|
| 175 |
+
torch.nn.utils.clip_grad_norm_ = clip_grad_norm_
|
src/utils/resave_model.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
sys.path.append(os.getcwd())
|
| 4 |
+
from src.msa.model import MSAForCausalLM
|
| 5 |
+
from src.msa.configuration_msa import MSAConfig
|
| 6 |
+
from src.utils.common import print_model_stats
|
| 7 |
+
from transformers import AutoTokenizer
|
| 8 |
+
|
| 9 |
+
def save_checkpoint(model, tokenizer, save_model_path):
|
| 10 |
+
model.save_pretrained(save_model_path)
|
| 11 |
+
tokenizer.save_pretrained(save_model_path)
|
| 12 |
+
|
| 13 |
+
def main(origin_model_path, save_model_path):
|
| 14 |
+
router_layer_idx = os.environ.get("ROUTER_LAYER_IDX", "all")
|
| 15 |
+
aux_loss = os.environ.get("AUX_LOSS", "false") == "true"
|
| 16 |
+
lmloss_weigth = float(os.environ.get("LMLOSS_WEIGHT", 1.0))
|
| 17 |
+
auxloss_weight = float(os.environ.get("AUX_LOSS_WEIGHT", 0.1))
|
| 18 |
+
recloss_weight = float(os.environ.get("REC_LOSS_WEIGHT", 0.0))
|
| 19 |
+
ansloss_weight = float(os.environ.get("ANS_LOSS_WEIGHT", 1.0))
|
| 20 |
+
aux_loss_method = os.environ.get("AUX_LOSS_METHOD", "INFONCE") # INFONCE, BCE, INFONCE_DECOUPLE, INFONCE_DECOUPLE_FOCAL
|
| 21 |
+
decouple_router = os.environ.get("DECOUPLE_ROUTER", "false").lower() == "true"
|
| 22 |
+
rewrite_position = os.environ.get("REWRITE_POSITION", "false") == "true"
|
| 23 |
+
|
| 24 |
+
top_k_docs = int(os.environ.get("TOP_K_DOCS", 2))
|
| 25 |
+
pooling_kernel_size = int(os.environ.get("POOLING_KERNEL_SIZE", 2))
|
| 26 |
+
|
| 27 |
+
head_reduce_method = os.environ.get("HEAD_REDUCE_METHOD", "max")
|
| 28 |
+
query_reduce_method = os.environ.get("QUERY_REDUCE_METHOD", "max")
|
| 29 |
+
chunk_reduce_method = os.environ.get("CHUNK_REDUCE_METHOD", "max")
|
| 30 |
+
decouple_pooling_mode = os.environ.get("DECOUPLE_POOLING_MODE", "mean")
|
| 31 |
+
infonce_loss_temp = float(os.environ.get("INFONCE_LOSS_TEMP", 0.1))
|
| 32 |
+
|
| 33 |
+
msa_config = {
|
| 34 |
+
"router_layer_idx": router_layer_idx,
|
| 35 |
+
"aux_loss": aux_loss,
|
| 36 |
+
"lmloss_weigth": lmloss_weigth,
|
| 37 |
+
"auxloss_weight": auxloss_weight,
|
| 38 |
+
"recloss_weight": recloss_weight,
|
| 39 |
+
"ansloss_weight": ansloss_weight,
|
| 40 |
+
"aux_loss_method": aux_loss_method,
|
| 41 |
+
"decouple_router": decouple_router,
|
| 42 |
+
"rewrite_position": rewrite_position,
|
| 43 |
+
"top_k_docs": top_k_docs,
|
| 44 |
+
"pooling_kernel_size": pooling_kernel_size,
|
| 45 |
+
"infonce_loss_temp": infonce_loss_temp,
|
| 46 |
+
"head_reduce_method": head_reduce_method,
|
| 47 |
+
"query_reduce_method": query_reduce_method,
|
| 48 |
+
"chunk_reduce_method": chunk_reduce_method,
|
| 49 |
+
"decouple_pooling_mode": decouple_pooling_mode,
|
| 50 |
+
}
|
| 51 |
+
# 使用 MSAConfig,它会自动将 msa_config 转换为 DotDict
|
| 52 |
+
config = MSAConfig.from_pretrained(origin_model_path)
|
| 53 |
+
config.msa_config = msa_config # MSAConfig 会自动转换为 DotDict
|
| 54 |
+
tokenizer = AutoTokenizer.from_pretrained(origin_model_path)
|
| 55 |
+
model = MSAForCausalLM.from_pretrained(
|
| 56 |
+
origin_model_path,
|
| 57 |
+
config=config,
|
| 58 |
+
torch_dtype="bfloat16",
|
| 59 |
+
)
|
| 60 |
+
print_model_stats(model)
|
| 61 |
+
|
| 62 |
+
# save
|
| 63 |
+
save_checkpoint(model, tokenizer, save_model_path)
|
| 64 |
+
|
| 65 |
+
if __name__ == "__main__":
|
| 66 |
+
origin_model_path = sys.argv[1]
|
| 67 |
+
save_model_path = sys.argv[2]
|
| 68 |
+
main(origin_model_path, save_model_path)
|
| 69 |
+
print(f"Model has been saved to : {save_model_path}")
|
| 70 |
+
print("Done")
|
src/utils/scale.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import pickle
|
| 3 |
+
|
| 4 |
+
def scale_memory(context, query, scale=3):
|
| 5 |
+
with open(query, "rb") as f:
|
| 6 |
+
query_metas = pickle.load(f)
|
| 7 |
+
|
| 8 |
+
labels_contents = [ref for q_meta in query_metas for ref in q_meta['reference_list']]
|
| 9 |
+
copy_reference = [f"{ref}_copy{bias}" for bias, ref in enumerate(context * scale) if ref not in labels_contents]
|
| 10 |
+
context.extend(copy_reference)
|
src/utils/template.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
QWEN3_TEMPLATE={
|
| 2 |
+
"prompt": "<|im_start|>user\n{prompt}<|im_end|>\n",
|
| 3 |
+
"response": "<|im_start|>assistant\n<think>\n{think_content}\n</think>\n\n{output}<|im_end|>"
|
| 4 |
+
}
|
| 5 |
+
|
| 6 |
+
QWEN3_INSTRUCT_TEMPLATE={
|
| 7 |
+
"prompt": "<|im_start|>user\n{prompt}<|im_end|>\n",
|
| 8 |
+
"response": "<|im_start|>assistant\n{output}<|im_end|>"
|
| 9 |
+
}
|
src/utils/tools.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import time
|
| 2 |
+
import torch
|
| 3 |
+
import threading
|
| 4 |
+
|
| 5 |
+
def format_bytes(size_in_bytes):
|
| 6 |
+
"""
|
| 7 |
+
将字节数转换为人类可读的格式
|
| 8 |
+
单位为 M 及以下不要小数点,单位为 G 以上保留一位小数,数值不能小于 1
|
| 9 |
+
"""
|
| 10 |
+
# 定义单位
|
| 11 |
+
units = ['B', 'K', 'M', 'G', 'T', 'P']
|
| 12 |
+
|
| 13 |
+
# 处理边界情况
|
| 14 |
+
if size_in_bytes < 1:
|
| 15 |
+
return "0B"
|
| 16 |
+
|
| 17 |
+
# 计算单位索引
|
| 18 |
+
unit_index = 0
|
| 19 |
+
size = float(size_in_bytes)
|
| 20 |
+
|
| 21 |
+
while size >= 1024 and unit_index < len(units) - 1:
|
| 22 |
+
size /= 1024
|
| 23 |
+
unit_index += 1
|
| 24 |
+
|
| 25 |
+
# 根据单位决定格式化方式
|
| 26 |
+
if unit_index <= 2: # B, K, M 不要小数点
|
| 27 |
+
if size == int(size):
|
| 28 |
+
return f"{int(size)}{units[unit_index]}"
|
| 29 |
+
else:
|
| 30 |
+
return f"{int(round(size))}{units[unit_index]}"
|
| 31 |
+
else: # G 及以上保留一位小数
|
| 32 |
+
return f"{size:.1f}{units[unit_index]}"
|
| 33 |
+
|
| 34 |
+
def cumulative_concat(tensors):
|
| 35 |
+
# 一次性获取所有信息
|
| 36 |
+
lengths = [len(t) for t in tensors]
|
| 37 |
+
last_values = [t[-1] for t in tensors]
|
| 38 |
+
|
| 39 |
+
# 计算累积偏移(不包括最后一个tensor)
|
| 40 |
+
cum_offsets = torch.cumsum(torch.tensor([0] + last_values[:-1]), dim=0)
|
| 41 |
+
|
| 42 |
+
# 构建偏移数组
|
| 43 |
+
total_length = sum(lengths)
|
| 44 |
+
offsets = torch.zeros(total_length, dtype=tensors[0].dtype, device=tensors[0].device)
|
| 45 |
+
|
| 46 |
+
# 为每个tensor设置对应的偏移
|
| 47 |
+
start_idx = 0
|
| 48 |
+
for i, length in enumerate(lengths):
|
| 49 |
+
if i > 0: # 第一个tensor不需要偏移
|
| 50 |
+
offsets[start_idx:start_idx + length] = cum_offsets[i]
|
| 51 |
+
start_idx += length
|
| 52 |
+
|
| 53 |
+
# 一次性拼接和添加偏移
|
| 54 |
+
concatenated = torch.cat(tensors)
|
| 55 |
+
return concatenated + offsets
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
class RequestLimiter:
|
| 59 |
+
def __init__(self, max_concurrent=10):
|
| 60 |
+
"""
|
| 61 |
+
初始化请求限流器
|
| 62 |
+
|
| 63 |
+
Args:
|
| 64 |
+
max_concurrent: 最大并发请求数,默认10
|
| 65 |
+
"""
|
| 66 |
+
self.max_concurrent = max_concurrent
|
| 67 |
+
self.current_count = 0
|
| 68 |
+
self.lock = threading.Lock()
|
| 69 |
+
self.condition = threading.Condition(self.lock)
|
| 70 |
+
|
| 71 |
+
def acquire(self):
|
| 72 |
+
"""
|
| 73 |
+
获取执行权限,如果超过最大并发数则阻塞
|
| 74 |
+
|
| 75 |
+
Returns:
|
| 76 |
+
bool: 是否成功获取权限
|
| 77 |
+
"""
|
| 78 |
+
with self.lock:
|
| 79 |
+
while self.current_count >= self.max_concurrent:
|
| 80 |
+
# 等待有请求完成
|
| 81 |
+
self.condition.wait()
|
| 82 |
+
self.current_count += 1
|
| 83 |
+
return True
|
| 84 |
+
|
| 85 |
+
def release(self):
|
| 86 |
+
"""
|
| 87 |
+
释放一个执行权限,唤醒等待的请求
|
| 88 |
+
"""
|
| 89 |
+
with self.lock:
|
| 90 |
+
if self.current_count > 0:
|
| 91 |
+
self.current_count -= 1
|
| 92 |
+
# 通知一个等待的线程
|
| 93 |
+
self.condition.notify()
|
| 94 |
+
|
| 95 |
+
def compose_input(doc, doc_idx, tokenizer):
|
| 96 |
+
"""组建reference的 input"""
|
| 97 |
+
new_doc = "<|im_start|>" + f"[{doc_idx}]. {doc}[{doc_idx}]<|im_end|>"
|
| 98 |
+
return new_doc,tokenizer(new_doc, add_special_tokens=False)
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
class TimePoint:
|
| 102 |
+
def __init__(self, disabled=False):
|
| 103 |
+
self.pts = []
|
| 104 |
+
self.disabled = disabled
|
| 105 |
+
|
| 106 |
+
def add(self, name):
|
| 107 |
+
if not self.disabled:
|
| 108 |
+
self.pts.append((name, time.time()))
|
| 109 |
+
|
| 110 |
+
def print(self):
|
| 111 |
+
if len(self.pts) < 2:
|
| 112 |
+
return
|
| 113 |
+
|
| 114 |
+
total = self.pts[-1][1] - self.pts[0][1]
|
| 115 |
+
s = ""
|
| 116 |
+
if len(self.pts) > 2:
|
| 117 |
+
lst1 = self.pts[:-1]
|
| 118 |
+
lst2 = self.pts[1:]
|
| 119 |
+
s = " | ".join(f"{item1[0]}->{item2[0]}: {item2[1] - item1[1]:.3f}" for item1, item2 in zip(lst1, lst2))
|
| 120 |
+
print(f"total: {total:.2f} {s}")
|