Initial commit: bi100 tokenizer patch
All checks were successful
Docker Build and Push / docker (push) Successful in 1m14s
All checks were successful
Docker Build and Push / docker (push) Successful in 1m14s
Add fix_tokenizer.py, vllm_wrapper.sh, Dockerfile, and README for automatic tokenizer_config.json repair on Iluvatar BI-100 vLLM images.
This commit is contained in:
132
.gitea/workflows/docker-build-push.yml
Normal file
132
.gitea/workflows/docker-build-push.yml
Normal file
@@ -0,0 +1,132 @@
|
||||
name: Docker Build and Push
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: amd64-ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- name: Clone repository
|
||||
run: |
|
||||
git clone "${{ gitea.server_url }}/${{ gitea.repository }}.git" .
|
||||
git checkout "${{ gitea.ref_name }}"
|
||||
|
||||
- name: Set image metadata
|
||||
run: |
|
||||
IMAGE_NAME="$(echo "${{ gitea.repository }}" | tr '[:upper:]' '[:lower:]' | tr '_' '-')"
|
||||
IMAGE="${DOCKER_REGISTRY}/${DOCKER_USERNAME}/${IMAGE_NAME}:${{ gitea.ref_name }}"
|
||||
|
||||
echo "IMAGE_NAME=${IMAGE_NAME}" >> "$GITEA_ENV"
|
||||
echo "IMAGE=${IMAGE}" >> "$GITEA_ENV"
|
||||
|
||||
- name: Load and Validate Task Info
|
||||
run: |
|
||||
set -a
|
||||
. .gitea/workflows/task_info.env
|
||||
set +a
|
||||
|
||||
for name in FRAMEWORK GPU_TYPE TASK_TYPE; do
|
||||
eval "value=\${${name}:-}"
|
||||
if [ "$name" = "FRAMEWORK" ] && [ -z "$value" ]; then
|
||||
echo "${name} is empty in .gitea/workflows/task_info.env"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "${name}=${value}" >> "$GITEA_ENV"
|
||||
done
|
||||
|
||||
- name: Validate Image Verify Metadata
|
||||
run: |
|
||||
if [ -z "${FIXED_TOKEN:-}" ]; then
|
||||
echo "FIXED_TOKEN is not configured on runner"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! response="$(curl --silent --show-error --location --get 'https://modelhub.org.cn/adminApi/image-verify/validate' \
|
||||
--header "Xc-Token: ${FIXED_TOKEN}" \
|
||||
--data-urlencode "gpuType=${GPU_TYPE:-}" \
|
||||
--data-urlencode "taskType=${TASK_TYPE:-}")"; then
|
||||
echo "failed to call image verify validate API"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VALIDATE_RESPONSE="$response" python3 - <<'PY'
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
raw = os.environ.get("VALIDATE_RESPONSE", "")
|
||||
try:
|
||||
body = json.loads(raw)
|
||||
except json.JSONDecodeError:
|
||||
print("image verify validate API returned invalid JSON")
|
||||
print(raw)
|
||||
sys.exit(1)
|
||||
|
||||
if body.get("code") == 0 and body.get("data") is True:
|
||||
print("image verify metadata validation passed")
|
||||
sys.exit(0)
|
||||
|
||||
message = body.get("message") or "unknown error"
|
||||
print(f"image verify metadata validation failed: {message}")
|
||||
print(raw)
|
||||
sys.exit(1)
|
||||
PY
|
||||
|
||||
- name: Login to Docker Registry
|
||||
run: |
|
||||
echo "$DOCKER_PASSWORD" | docker login "$DOCKER_REGISTRY" \
|
||||
-u "$DOCKER_USERNAME" \
|
||||
--password-stdin
|
||||
|
||||
- name: Build Docker Image
|
||||
run: |
|
||||
docker build -t "$IMAGE" .
|
||||
|
||||
- name: Push Docker Image
|
||||
run: |
|
||||
for attempt in 1 2 3; do
|
||||
echo "Starting docker push attempt ${attempt}/3 for ${IMAGE}"
|
||||
docker push "$IMAGE" &
|
||||
PUSH_PID=$!
|
||||
|
||||
while kill -0 "$PUSH_PID" 2>/dev/null; do
|
||||
echo "docker push is still running at $(date -u '+%Y-%m-%dT%H:%M:%SZ')"
|
||||
sleep 60
|
||||
done
|
||||
|
||||
if wait "$PUSH_PID"; then
|
||||
echo "docker push completed successfully"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "docker push failed on attempt ${attempt}/3"
|
||||
sleep 30
|
||||
done
|
||||
|
||||
echo "docker push failed after 3 attempts"
|
||||
exit 1
|
||||
|
||||
- name: Notify Image Verify
|
||||
run: |
|
||||
if [ -z "${FIXED_TOKEN:-}" ]; then
|
||||
echo "FIXED_TOKEN is not configured on runner"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
curl --silent --show-error --fail-with-body --location --request POST 'https://modelhub.org.cn//adminApi/image-verify' \
|
||||
--header "Xc-Token: ${FIXED_TOKEN}" \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw "{
|
||||
\"framework\": \"${FRAMEWORK}\",
|
||||
\"gpuType\": \"${GPU_TYPE}\",
|
||||
\"imageUrl\": \"${IMAGE}\",
|
||||
\"taskType\": \"${TASK_TYPE}\",
|
||||
\"createBy\": \"${{ gitea.actor }}\",
|
||||
\"repoUrl\": \"${{ gitea.server_url }}/${{ gitea.repository }}\",
|
||||
\"tag\": \"${{ github.ref_name }}\"
|
||||
}"
|
||||
3
.gitea/workflows/task_info.env
Normal file
3
.gitea/workflows/task_info.env
Normal file
@@ -0,0 +1,3 @@
|
||||
FRAMEWORK=vllm-patch-tokenizer
|
||||
GPU_TYPE=Iluvatar_bi-100
|
||||
TASK_TYPE=text-generation
|
||||
10
Dockerfile
Normal file
10
Dockerfile
Normal file
@@ -0,0 +1,10 @@
|
||||
FROM harbor-contest.4pd.io/luopingyi/bi100/vllm:0.6.3
|
||||
|
||||
COPY fix_tokenizer.py /opt/fix_tokenizer.py
|
||||
COPY vllm_wrapper.sh /opt/vllm_wrapper.sh
|
||||
|
||||
RUN chmod +x /opt/vllm_wrapper.sh && \
|
||||
mv /usr/local/corex/lib64/python3/dist-packages/bin/vllm \
|
||||
/usr/local/corex/lib64/python3/dist-packages/bin/vllm_real && \
|
||||
ln -s /opt/vllm_wrapper.sh \
|
||||
/usr/local/corex/lib64/python3/dist-packages/bin/vllm
|
||||
55
README.md
Normal file
55
README.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# bi100-tokenizer-patch
|
||||
|
||||
基于 `harbor-contest.4pd.io/luopingyi/bi100/vllm:0.6.3` 的 tokenizer 自动修复镜像。
|
||||
|
||||
## 问题背景
|
||||
|
||||
部分模型的 `tokenizer_config.json` 存在以下问题,导致 vLLM 服务启动失败:
|
||||
|
||||
| 错误 | 原因 |
|
||||
|---|---|
|
||||
| `ValueError: Tokenizer class TokenizersBackend does not exist` | `tokenizer_class` 不是 transformers 合法类名 |
|
||||
| `AttributeError: 'list' object has no attribute 'keys'` | `extra_special_tokens` 为 list 格式,transformers 要求 dict |
|
||||
|
||||
## 修复方式
|
||||
|
||||
构建时将镜像内的 `vllm` 二进制替换为同名 wrapper 脚本,原二进制重命名为 `vllm_real`。
|
||||
|
||||
容器启动时 wrapper 自动检测 `tokenizer_config.json`:
|
||||
- 存在问题 → 将 tokenizer 文件复制到 `/tmp/fixed_tokenizer/` 并修复,追加 `--tokenizer /tmp/fixed_tokenizer` 参数后调用 `vllm_real`
|
||||
- 无问题 → 直接调用 `vllm_real`,行为与原镜像完全一致
|
||||
|
||||
原始模型目录不做任何修改。
|
||||
|
||||
## 使用方式
|
||||
|
||||
**原始 docker run 命令只需替换镜像名,其他参数不变:**
|
||||
|
||||
```bash
|
||||
# 原镜像
|
||||
harbor-contest.4pd.io/luopingyi/bi100/vllm:0.6.3
|
||||
|
||||
# 替换为
|
||||
<this-image>
|
||||
```
|
||||
|
||||
示例:
|
||||
```bash
|
||||
docker run -dit --name <container_name> \
|
||||
-p <port>:8000 \
|
||||
-v /lib/modules:/lib/modules -v /dev:/dev \
|
||||
--device=/dev/iluvatar0:/dev/iluvatar0 \
|
||||
-v /path/to/model:/model \
|
||||
--entrypoint vllm <this-image> \
|
||||
serve /model --port 8000 --served-model-name llm \
|
||||
--max-model-len 2048 --gpu-memory-utilization 0.9 \
|
||||
--enforce-eager --trust-remote-code -tp 1
|
||||
```
|
||||
|
||||
## 构建
|
||||
|
||||
```bash
|
||||
docker build -t bi100-tokenizer-patch:latest .
|
||||
```
|
||||
|
||||
CI 通过推送 `v*` tag 自动触发构建并推送镜像。
|
||||
95
fix_tokenizer.py
Normal file
95
fix_tokenizer.py
Normal file
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
检测并修复 tokenizer_config.json 中的两类问题:
|
||||
1. tokenizer_class 在 transformers 中不存在(如 TokenizersBackend)
|
||||
2. extra_special_tokens 为 list 格式(transformers 要求 dict)
|
||||
|
||||
若存在问题,将 tokenizer 文件复制到 /tmp/fixed_tokenizer/ 并修复,
|
||||
最后将修复目录路径输出到 stdout。若无需修复,输出为空。
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import shutil
|
||||
|
||||
MODEL_DIR = sys.argv[1] if len(sys.argv) > 1 else os.environ.get("MODEL_DIR", "/model")
|
||||
OUT_DIR = "/tmp/fixed_tokenizer"
|
||||
|
||||
|
||||
def main():
|
||||
cfg_path = os.path.join(MODEL_DIR, "tokenizer_config.json")
|
||||
if not os.path.exists(cfg_path):
|
||||
return
|
||||
|
||||
with open(cfg_path) as f:
|
||||
cfg = json.load(f)
|
||||
|
||||
fixes = []
|
||||
|
||||
# --- 检测 1:tokenizer_class 是否在 transformers 中存在 ---
|
||||
tokenizer_class = cfg.get("tokenizer_class", "")
|
||||
bad_tokenizer_class = False
|
||||
if tokenizer_class:
|
||||
import transformers
|
||||
if getattr(transformers, tokenizer_class, None) is None:
|
||||
bad_tokenizer_class = True
|
||||
fixes.append(f"tokenizer_class '{tokenizer_class}' not found in transformers")
|
||||
|
||||
# --- 检测 2:extra_special_tokens 是否为 list 格式 ---
|
||||
bad_extra_special_tokens = (
|
||||
"extra_special_tokens" in cfg
|
||||
and isinstance(cfg["extra_special_tokens"], list)
|
||||
)
|
||||
if bad_extra_special_tokens:
|
||||
fixes.append("extra_special_tokens is a list, expected dict")
|
||||
|
||||
if not fixes:
|
||||
return # 无需修复
|
||||
|
||||
# 复制 tokenizer 文件到临时目录
|
||||
os.makedirs(OUT_DIR, exist_ok=True)
|
||||
for fname in [
|
||||
"tokenizer.json",
|
||||
"tokenizer_config.json",
|
||||
"special_tokens_map.json",
|
||||
"vocab.json",
|
||||
"merges.txt",
|
||||
"tokenizer.model",
|
||||
]:
|
||||
src = os.path.join(MODEL_DIR, fname)
|
||||
if os.path.exists(src):
|
||||
shutil.copy(src, OUT_DIR)
|
||||
|
||||
# --- 修复 1:替换 tokenizer_class ---
|
||||
if bad_tokenizer_class:
|
||||
files = os.listdir(MODEL_DIR)
|
||||
if "tokenizer.json" in files:
|
||||
fixed_class = "PreTrainedTokenizerFast"
|
||||
elif "tokenizer.model" in files:
|
||||
fixed_class = "LlamaTokenizer"
|
||||
elif "vocab.json" in files and "merges.txt" in files:
|
||||
fixed_class = "GPT2TokenizerFast"
|
||||
else:
|
||||
fixed_class = "PreTrainedTokenizerFast"
|
||||
cfg["tokenizer_class"] = fixed_class
|
||||
print(
|
||||
f"[fix_tokenizer] tokenizer_class: '{tokenizer_class}' → '{fixed_class}'",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
# --- 修复 2:extra_special_tokens list → dict ---
|
||||
if bad_extra_special_tokens:
|
||||
orig_list = cfg["extra_special_tokens"]
|
||||
cfg["extra_special_tokens"] = {token: token for token in orig_list}
|
||||
print(
|
||||
f"[fix_tokenizer] extra_special_tokens: list({len(orig_list)}) → dict",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
with open(os.path.join(OUT_DIR, "tokenizer_config.json"), "w") as f:
|
||||
json.dump(cfg, f, indent=2)
|
||||
|
||||
print(OUT_DIR) # 输出修复目录,供 entrypoint.sh 捕获
|
||||
|
||||
|
||||
main()
|
||||
17
vllm_wrapper.sh
Normal file
17
vllm_wrapper.sh
Normal file
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# 只拦截 "serve <model_dir>" 子命令,其他子命令直接透传
|
||||
if [ "$1" = "serve" ] && [ -n "$2" ]; then
|
||||
MODEL_DIR="$2"
|
||||
shift 2
|
||||
|
||||
FIXED_DIR=$(python3 /opt/fix_tokenizer.py "$MODEL_DIR")
|
||||
if [ -n "$FIXED_DIR" ]; then
|
||||
exec /usr/local/corex/lib64/python3/dist-packages/bin/vllm_real serve "$MODEL_DIR" --tokenizer "$FIXED_DIR" "$@"
|
||||
else
|
||||
exec /usr/local/corex/lib64/python3/dist-packages/bin/vllm_real serve "$MODEL_DIR" "$@"
|
||||
fi
|
||||
fi
|
||||
|
||||
exec /usr/local/corex/lib64/python3/dist-packages/bin/vllm_real "$@"
|
||||
Reference in New Issue
Block a user