# 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_size,vLLM 仍会正常启动 - ✅ **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 ``` 每个步骤都有完整的容错机制,确保即使某个步骤失败,也不会影响后续步骤的执行。