Files
Sun Ruoxi a3c456f29a
All checks were successful
Docker Build and Push / docker (push) Successful in 1m4s
do patch ops in dockerfile
Signed-off-by: Sun Ruoxi <sunruoxi@4paradigm.com>
2026-07-28 14:10:43 +08:00

471 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# vLLM 自动修复方案
Tokenizer修复 + Head Size补丁 + Ixformer Ops补丁
## 1. 背景
在使用 vLLM 部署部分模型时,可能会遇到如下报错:
```
ValueError: Tokenizer class TokenizersBackend does not exist or is not currently imported.
```
该问题通常由 transformers 的 tokenizer 加载机制导致:
- tokenizer_config.json 中指定了不存在或不兼容的 tokenizer_class
- 开启 trust_remote_code=True 时transformers 会强制加载该 class
- vLLM 无法通过参数 override tokenizer class
另外,某些模型的 head_size 可能不在 vLLM 默认支持的列表中64, 80, 96, 112, 120, 128, 192, 256导致运行时错误。
此外,某些模型需要特定的 ixformer 操作函数(如 `gelu_tanh_and_mul`),但这些函数可能不在默认的 ixformer 库中。
---
## 2. 方案目标
本方案实现:
```
无需修改模型文件
无需修改启动命令
自动修复 tokenizer 并启动 vLLM
自动检测并 patch 不支持的 head_size
```
---
## 3. 核心思路
在容器启动时:
```
entrypoint.sh
检测 tokenizer 是否异常
复制 tokenizer 文件 → /tmp/fixed_tokenizer
修复 tokenizer_config.json
检测模型 head_size
如果 head_size 不在支持列表中patch vLLM 代码
vllm serve --tokenizer /tmp/fixed_tokenizer
````
**注意**Ixformer ops 的 patch 在**镜像构建时**完成,不需要在容器启动时执行。
---
## 4. 支持的自动修复场景
| 原 tokenizer_class | 修复为 |
|-------------------|--------|
| TokenizersBackend | PreTrainedTokenizerFast |
| TiktokenTokenizer | GPT2TokenizerFast |
| 缺失 tokenizer_config | 自动生成 |
| SentencePiece | LlamaTokenizer |
### 修复 extra_special_tokens 格式
当 `extra_special_tokens` 为 list 格式时,自动转换为 dict 格式:
```json
// 修复前
"extra_special_tokens": ["<|im_start|>", "<|im_end|>", "<|box_start|>", "<|box_end|>", ...]
// 修复后
"extra_special_tokens": {
"<|im_start|>": "<|im_start|>",
"<|im_end|>": "<|im_end|>",
"<|box_start|>": "<|box_start|>",
"<|box_end|>": "<|box_end|>",
...
}
```
---
## 5. 生成的 tokenizer 目录
```
/tmp/fixed_tokenizer/
├── tokenizer.json
├── tokenizer_config.json (已修复)
├── special_tokens_map.json (可选)
├── vocab.json / merges.txt (如需要)
```
---
## 6. 日志说明
### 正常情况
```
[entrypoint] tokenizer OK, skip fix
```
### 自动修复
```
[entrypoint] fixing tokenizer...
[fix] override bad tokenizer_class: TokenizersBackend → PreTrainedTokenizerFast
[fix] converted extra_special_tokens from list (13 items) to dict format
```
触发条件AUTO_FIX=auto 时):
- tokenizer_config.json 包含 `TokenizersBackend``TiktokenTokenizer`
- tokenizer_config.json 中 `extra_special_tokens` 为 list 格式(`"extra_special_tokens": [`
---
## 7. 验证方法
进入容器执行:
```python
from transformers import AutoTokenizer
tok = AutoTokenizer.from_pretrained("/tmp/fixed_tokenizer")
print(tok.encode("hello world"))
print(tok.decode(tok.encode("hello world")))
```
确保:
```
encode → decode 可逆
```
---
## 8. 注意事项
### ⚠️ 1. tokenizer 文件必须存在
至少需要:
| 类型 | 必需文件 |
| -------------- | ----------------------- |
| Fast tokenizer | tokenizer.json |
| BPE | vocab.json + merges.txt |
| SentencePiece | tokenizer.model |
---
### ⚠️ 2. 不影响模型推理
本方案:
```
仅影响 tokenizer文本 ↔ token
不影响模型计算attention / KV cache
```
---
### ⚠️ 3. 特殊 token 风险
需确认:
```
bos_token / eos_token / pad_token 一致
```
否则可能影响生成结果
---
## 9. Head Size 自动补丁
### 问题
vLLM 默认只支持以下 head_size 值:
```
[64, 80, 96, 112, 120, 128, 192, 256]
```
如果模型的 head_size 不在此列表中,会导致运行时错误。
### 检测逻辑
系统会自动从模型的 `config.json` 中检测 head_size支持以下多种配置格式
1. **直接读取 `head_dim` 字段**
```json
{
"head_dim": 128
}
```
2. **从 `hidden_size / num_attention_heads` 计算**
```json
{
"hidden_size": 2048,
"num_attention_heads": 16
}
// head_size = 2048 / 16 = 128
```
3. **从 `n_embd / n_head` 计算GPTJ等模型**
```json
{
"n_embd": 2048,
"n_head": 16
}
```
4. **直接读取 `d_kv` 字段T5等模型**
```json
{
"d_kv": 128
}
```
### 补丁逻辑
如果检测到的 head_size 不在支持列表中,系统会:
1. **备份原文件**
```
/usr/local/corex/lib64/python3/dist-packages/vllam/attention/ops/paged_attn.py.backup
```
2. **修改 get_supported_head_sizes 方法**
```python
@staticmethod
def get_supported_head_sizes() -> List[int]:
return [64, 80, 96, 112, 120, 128, 192, 256, YOUR_NEW_SIZE]
```
3. **保持列表排序**
新的 head_size 会被插入到正确的位置,保持列表升序排列。
### 日志示例
**无需补丁的情况**
```
[entrypoint] checking model head_size...
[detect_head_size] Found head_dim in config: 128
[detect_head_size] Model head_size: 128
[detect_head_size] head_size 128 is already supported by vLLM, skipping patch
```
**需要补丁的情况**
```
[entrypoint] checking model head_size...
[detect_head_size] Calculated from hidden_size(4096) / num_attention_heads(32) = 128
[detect_head_size] Model head_size: 128
[detect_head_size] head_size 160 is NOT in default supported list: [64, 80, 96, 112, 120, 128, 192, 256]
[detect_head_size] Attempting to patch vLLM...
[patch] Backed up original file to /usr/local/.../paged_attn.py.backup
[patch] Successfully added head_size 160 to supported list: [64, 80, 96, 112, 120, 128, 160, 192, 256]
[detect_head_size] Successfully patched vLLM to support head_size 160
```
### 补丁恢复
如需恢复原始文件:
```bash
cp /usr/local/corex/lib64/python3/dist-packages/vllm/attention/ops/paged_attn.py.backup \
/usr/local/corex/lib64/python3/dist-packages/vllm/attention/ops/paged_attn.py
```
### 容错机制
**重要**head_size 检测和 patch 功能具有完整的容错机制:
- ✅ **检测失败不影响启动**:如果无法检测 head_sizevLLM 仍会正常启动
- ✅ **Patch 失败不影响启动**:如果 patch 过程出错vLLM 仍会正常启动
- ✅ **代码异常不影响启动**如果检测脚本本身出现异常vLLM 仍会正常启动
这确保了:
```
本来能跑的模型 → 即使 head_size 检测失败 → 仍然能跑
```
### 日志示例
**检测失败的情况仍会启动vLLM**
```
[entrypoint] checking model head_size...
[detect_head_size] Error during head_size detection/patch: config.json not found
[detect_head_size] Continuing with vLLM startup anyway...
[entrypoint] head_size check failed, but continuing with vLLM startup
[entrypoint] starting vLLM...
```
---
## 10. Ixformer Ops 自动补丁
### 问题
某些模型需要特定的 ixformer 操作函数,如 `gelu_tanh_and_mul`,但这些函数可能不在默认的 ixformer 库中。
### 补丁时机
**重要**Ixformer ops 的 patch 在**镜像构建时**执行,而不是在容器启动时执行。这意味着:
- ✅ **性能优化**:容器启动速度更快,不需要每次都执行 patch 操作
- ✅ **一次构建,多次运行**patch 操作只在构建镜像时执行一次
- ✅ **符合最佳实践**:将构建时操作放在 Dockerfile 中,运行时操作放在 entrypoint 中
### 通用解决方案
**重要特性**:系统采用**通用扫描机制**,无需每次修改代码:
1. **自动扫描**:自动扫描 `patched_ops` 目录中的所有 `.py` 文件
2. **批量处理**:批量复制所有文件到目标目录
3. **自动生成import**:为每个文件自动生成对应的 `from .xxx import *` 语句
4. **智能去重**自动检测已存在的import避免重复添加
### 使用方法
只需将需要补丁的 ops 文件放入 `patched_ops` 目录即可:
```bash
patched_ops/
├── gelu_tanh_and_mul.py # 第一个ops文件
├── another_op.py # 第二个ops文件
├── third_operation.py # 第三个ops文件
└── ...
```
系统会在镜像构建时自动:
- 扫描所有 `.py` 文件(排除 `__init__.py`
- 复制到 `/usr/local/corex/lib64/python3/dist-packages/ixformer/functions/`
- 在 `__init__.py` 中添加对应的 import 语句
### 补丁逻辑
系统会在镜像构建时自动执行以下操作:
1. **扫描源目录**
```bash
源目录: /opt/patched_ops/
自动查找所有 .py 文件(排除 __init__.py
```
2. **批量复制文件**
```
源文件: /opt/patched_ops/*.py
目标: /usr/local/corex/lib64/python3/dist-packages/ixformer/functions/
```
3. **自动生成并添加 import 语句**
```
目标文件: /usr/local/corex/lib64/python3/dist-packages/ixformer/functions/__init__.py
自动生成: from .gelu_tanh_and_mul import *
from .another_op import *
from .third_operation import *
```
4. **自动备份**
在修改前会自动备份原始的 `__init__.py` 文件为 `__init__.py.backup`
### 构建时日志示例
**成功的批量补丁操作**
```
Step 4/8 : COPY patched_ops /opt/patched_ops/
---> Using cache
---> 1234567890ab
Step 5/8 : RUN python3 /opt/patch_ops.py && chmod +x /opt/entrypoint.sh
---> Running in 9876543210fe
[patch_ops] Starting ixformer ops patch...
[patch_ops] Found 3 ops file(s): gelu_tanh_and_mul.py, another_op.py, third_operation.py
[patch_ops] Backed up /usr/local/.../__init__.py to /usr/local/.../__init__.py.backup
[patch_ops] Copied gelu_tanh_and_mul.py to /usr/local/.../ixformer/functions/
[patch_ops] Copied another_op.py to /usr/local/.../ixformer/functions/
[patch_ops] Copied third_operation.py to /usr/local/.../ixformer/functions/
[patch_ops] Added 3 import statement(s) to /usr/local/.../__init__.py
[patch_ops] Successfully patched ixformer ops
[patch_ops] Patch completed successfully
---> Removed intermediate container
---> abcdef123456
```
**重复构建已存在import**
```
Step 5/8 : RUN python3 /opt/patch_ops.py && chmod +x /opt/entrypoint.sh
---> Running in 1234567890ab
[patch_ops] Starting ixformer ops patch...
[patch_ops] Found 3 ops file(s): gelu_tanh_and_mul.py, another_op.py, third_operation.py
[patch_ops] Backup already exists: /usr/local/.../__init__.py.backup
[patch_ops] Copied gelu_tanh_and_mul.py to /usr/local/.../ixformer/functions/
[patch_ops] Copied another_op.py to /usr/local/.../ixformer/functions/
[patch_ops] Copied third_operation.py to /usr/local/.../ixformer/functions/
[patch_ops] All imports already exist in /usr/local/.../__init__.py, skipping modification
[patch_ops] Successfully patched ixformer ops
```
### 补丁恢复
如需恢复原始的 `__init__.py` 文件:
```bash
cp /usr/local/corex/lib64/python3/dist-packages/ixformer/functions/__init__.py.backup \
/usr/local/corex/lib64/python3/dist-packages/ixformer/functions/__init__.py
```
### 容错机制
由于 patch 在镜像构建时执行,如果构建失败:
- ✅ **构建失败会立即发现**:不会发布有问题的镜像
- ✅ **部分失败不影响整体**:即使某个文件处理失败,其他文件仍会继续处理
- ✅ **清晰的错误日志**:构建日志会明确显示失败原因
---
## 11. 总结
本方案通过在容器启动阶段引入多种自动修复和补丁逻辑,实现:
```
"模型不动,运行时自适应兼容"
```
主要功能:
- ✅ 自动修复不兼容的 tokenizer 配置
- ✅ 自动检测并补丁不支持的 head_size
- ✅ 自动补丁 ixformer ops如 gelu_tanh_and_mul- **在镜像构建时执行**
- ✅ 无需修改模型文件,无需修改启动命令
- ✅ 完全透明,不影响正常模型部署
- ✅ **完整的容错机制,确保本来能跑的模型不受影响**
### 启动流程总结
**镜像构建时**
```
复制 patched_ops 文件
执行 patch_ops.py一次
补丁 ixformer ops 到目标目录
```
**容器启动时**
```
容器启动
修复 tokenizer如需要
检查并 patch head_size如需要
启动 vLLM
```
每个步骤都有完整的容错机制,确保即使某个步骤失败,也不会影响后续步骤的执行。