This commit is contained in:
56
.gitea/workflows/build.yml
Normal file
56
.gitea/workflows/build.yml
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
name: Docker Build
|
||||||
|
|
||||||
|
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: Load task info
|
||||||
|
run: |
|
||||||
|
set -a
|
||||||
|
. .gitea/workflows/task_info.env
|
||||||
|
set +a
|
||||||
|
echo "标签: ${{ gitea.ref_name }}"
|
||||||
|
echo "FRAMEWORK=${FRAMEWORK:-}"
|
||||||
|
echo "GPU_TYPE=${GPU_TYPE:-}"
|
||||||
|
echo "TASK_TYPE=${TASK_TYPE:-}"
|
||||||
|
if [ -z "${FRAMEWORK:-}" ] || [ "${FRAMEWORK}" = "vllm_fix_tokenizer" ]; then
|
||||||
|
echo "FRAMEWORK 无效。本仓使用 vllm_0_17_0_corex_4_4_0。"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ "${GPU_TYPE:-}" != "Iluvatar_bi-150" ]; then
|
||||||
|
echo "GPU_TYPE 与本仓锁定值不一致: ${GPU_TYPE:-空}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
IMAGE_NAME="$(echo "${{ gitea.repository }}" | tr '[:upper:]' '[:lower:]' | tr '_' '-')"
|
||||||
|
echo "IMAGE_NAME=${IMAGE_NAME}" >> "$GITEA_ENV"
|
||||||
|
echo "IMAGE_TAG=${{ gitea.ref_name }}" >> "$GITEA_ENV"
|
||||||
|
|
||||||
|
- name: Build image
|
||||||
|
run: |
|
||||||
|
echo "开始 docker build,标签 ${IMAGE_TAG}"
|
||||||
|
docker build -t "${IMAGE_NAME}:${IMAGE_TAG}" .
|
||||||
|
echo "docker build 完成: ${IMAGE_NAME}:${IMAGE_TAG}"
|
||||||
|
|
||||||
|
- name: Push when registry credentials exist
|
||||||
|
run: |
|
||||||
|
if [ -z "${DOCKER_REGISTRY:-}" ] || [ -z "${DOCKER_USERNAME:-}" ] || [ -z "${DOCKER_PASSWORD:-}" ]; then
|
||||||
|
echo "未配置 DOCKER_REGISTRY、DOCKER_USERNAME 或 DOCKER_PASSWORD。"
|
||||||
|
echo "本次为 build-only,不登录仓库,不推送镜像。"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
remote="${DOCKER_REGISTRY}/${DOCKER_USERNAME}/${IMAGE_NAME}:${IMAGE_TAG}"
|
||||||
|
echo "推送目标: ${remote}"
|
||||||
|
echo "$DOCKER_PASSWORD" | docker login "$DOCKER_REGISTRY" -u "$DOCKER_USERNAME" --password-stdin
|
||||||
|
docker tag "${IMAGE_NAME}:${IMAGE_TAG}" "$remote"
|
||||||
|
docker push "$remote"
|
||||||
|
echo "推送完成: ${remote}"
|
||||||
3
.gitea/workflows/task_info.env
Normal file
3
.gitea/workflows/task_info.env
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
GPU_TYPE=Iluvatar_bi-150
|
||||||
|
FRAMEWORK=vllm_0_17_0_corex_4_4_0
|
||||||
|
TASK_TYPE=text-generation
|
||||||
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
*.pem
|
||||||
|
*.key
|
||||||
|
secrets/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
.pytest_cache/
|
||||||
20
Dockerfile
Normal file
20
Dockerfile
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# 基线 tag 来自公开仓反推,不是官方另行发布的锁定说明。
|
||||||
|
# 官方入口若给出不同 tag,只改 BASE_IMAGE。
|
||||||
|
ARG BASE_IMAGE=registry.iluvatar.com.cn:10443/customer/sz/vllm0.17.0-4.4.0-x86:v4.1
|
||||||
|
FROM ${BASE_IMAGE}
|
||||||
|
|
||||||
|
ENV FRAMEWORK=vllm_0_17_0_corex_4_4_0 \
|
||||||
|
GPU_TYPE=Iluvatar_bi-150 \
|
||||||
|
TASK_TYPE=text-generation \
|
||||||
|
MODEL_DIR=/model \
|
||||||
|
ARCH_UNKNOWN_POLICY=fail \
|
||||||
|
APPLY_TOKENIZER_PATCH=0
|
||||||
|
|
||||||
|
WORKDIR /opt/engine
|
||||||
|
COPY scripts/ /opt/engine/scripts/
|
||||||
|
COPY patches/ /opt/engine/patches/
|
||||||
|
COPY task_info.env.example /opt/engine/task_info.env
|
||||||
|
|
||||||
|
RUN chmod +x /opt/engine/scripts/entrypoint.sh
|
||||||
|
|
||||||
|
ENTRYPOINT ["/opt/engine/scripts/entrypoint.sh"]
|
||||||
9
Makefile
Normal file
9
Makefile
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
.PHONY: test dry-run
|
||||||
|
|
||||||
|
export PYTHONPATH := scripts
|
||||||
|
|
||||||
|
test:
|
||||||
|
python3 -m unittest discover -s tests -v
|
||||||
|
|
||||||
|
dry-run:
|
||||||
|
bash scripts/entrypoint.sh --dry-run
|
||||||
67
README.md
Normal file
67
README.md
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
# 天垓 150 文本生成兼容引擎
|
||||||
|
|
||||||
|
本仓是 ModelHub XC 上 Iluvatar bi-150(天垓 150)的文本生成引擎:**Gitea 仓库 + CI 构建镜像**。日常引擎贡献不走 ZIP。
|
||||||
|
|
||||||
|
**不代提交。** 本仓不向 modelhub.org.cn 提交适配任务,也不调用平台审核接口。推送、打 Tag、在模盒选用引擎都由人完成,步骤见 [docs/upload-runbook.md](docs/upload-runbook.md)。
|
||||||
|
|
||||||
|
积分:待平台核实。成功看审核通过,以及适配任务能否选用该引擎。
|
||||||
|
|
||||||
|
## 非目标
|
||||||
|
|
||||||
|
- Track B(竞赛)为 **NO-GO**。没有 `contest/`,没有竞赛构建。
|
||||||
|
- 没有 ZIP 打包,没有上传驱动的 Makefile 目标。
|
||||||
|
- `FRAMEWORK` 使用 `vllm_0_17_0_corex_4_4_0`,不使用 `vllm_fix_tokenizer`。
|
||||||
|
- tokenizer 修补(I5)未开启。现有 bi150 引擎已覆盖 tokenizer;本仓补的是 chat_template(R2)和 architecture 白名单(R4)。
|
||||||
|
|
||||||
|
## 基线镜像
|
||||||
|
|
||||||
|
默认:
|
||||||
|
|
||||||
|
`registry.iluvatar.com.cn:10443/customer/sz/vllm0.17.0-4.4.0-x86:v4.1`
|
||||||
|
|
||||||
|
这个 tag 来自公开仓反推,置信度为中高,不是官方另行发布的锁定说明。官方入口若给出不同 tag,只改 [Dockerfile](Dockerfile) 里的 `BASE_IMAGE`。
|
||||||
|
|
||||||
|
构建覆盖:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build --build-arg BASE_IMAGE=<官方 tag> -t modelhub-engine-bi150-compat:dev .
|
||||||
|
```
|
||||||
|
|
||||||
|
无卡环境只能验证修补逻辑,不能证明吞吐。
|
||||||
|
|
||||||
|
## 本地检查
|
||||||
|
|
||||||
|
需要本机 `python3`、`bash`、`make`。不需要 GPU,也不需要平台账号。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make test
|
||||||
|
make dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
`make dry-run` 只打印启动计划与模板预览,不启动 vLLM,不访问平台。
|
||||||
|
|
||||||
|
## 改 FRAMEWORK
|
||||||
|
|
||||||
|
默认值写在 [task_info.env.example](task_info.env.example)。镜像内和 CI 使用同一组值:
|
||||||
|
|
||||||
|
- `GPU_TYPE=Iluvatar_bi-150`
|
||||||
|
- `FRAMEWORK=vllm_0_17_0_corex_4_4_0`
|
||||||
|
- `TASK_TYPE=text-generation`
|
||||||
|
|
||||||
|
若平台给出新的 FRAMEWORK 名,同时改这三处,并让 `make test` 保持通过:
|
||||||
|
|
||||||
|
- `task_info.env.example`
|
||||||
|
- `.gitea/workflows/task_info.env`
|
||||||
|
- `Dockerfile` 的 `ENV`
|
||||||
|
|
||||||
|
不要改回 `vllm_fix_tokenizer`。
|
||||||
|
|
||||||
|
## 建仓与选用
|
||||||
|
|
||||||
|
见 [docs/upload-runbook.md](docs/upload-runbook.md)。事项状态见 [docs/issues.md](docs/issues.md)。证据模板见 [docs/evidence.md](docs/evidence.md)。
|
||||||
|
|
||||||
|
参考公开仓的启动包装和架构改写思路,没有整仓复制:
|
||||||
|
|
||||||
|
- zhengzhongwei/vLLM-Iluvatar_bi-100-opt
|
||||||
|
- wanglin/enginex-bi100-compat
|
||||||
|
- 公开 bi150 tokenizer 引擎只覆盖 tokenizer,本仓补 R2/R4
|
||||||
14
docs/evidence.md
Normal file
14
docs/evidence.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# bi150-compat 证据
|
||||||
|
|
||||||
|
时区:**Asia/Shanghai**。本模板由人工填写。仓库不自动采集审核状态。
|
||||||
|
|
||||||
|
- 镜像 tag:待填写
|
||||||
|
- CI 地址:待填写
|
||||||
|
- 审核:未开始
|
||||||
|
- 审核通过时间(Asia/Shanghai):待填写
|
||||||
|
- 首次被适配任务选用的任务 ID:待填写
|
||||||
|
- 选用时间(Asia/Shanghai):待填写
|
||||||
|
- 平台状态:未推送
|
||||||
|
- 积分:待平台核实
|
||||||
|
|
||||||
|
无卡本地测试只说明 R2/R4 修补逻辑能跑,不说明天垓 150 上的吞吐。
|
||||||
14
docs/issues.md
Normal file
14
docs/issues.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# bi150-compat 事项
|
||||||
|
|
||||||
|
Track B 不在本仓实现,状态保持 NO-GO。
|
||||||
|
|
||||||
|
- [x] I0 仓骨架 + README + 非目标声明
|
||||||
|
- [x] I1 Dockerfile + task_info.env.example
|
||||||
|
- [x] I2 wrapper + dry-run
|
||||||
|
- [x] I3 R2 chat_template 修补
|
||||||
|
- [x] I4 R4 architecture 白名单
|
||||||
|
- [ ] I5 tokenizer 修补(未开启;等 R2/R4 确认盖不住目标模型族再做)
|
||||||
|
- [x] I6 CI build(Tag 触发;无凭据时 build-only;不提交平台任务)
|
||||||
|
- [x] I7 人工 runbook + evidence 模板
|
||||||
|
|
||||||
|
本地 `make test` 已覆盖 I3/I4 与 dry-run。镜像是否能在 Runner 上拉到基线 tag,要等第一次 `v*` 流水线。
|
||||||
39
docs/upload-runbook.md
Normal file
39
docs/upload-runbook.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# 建仓、打 Tag、选用引擎(人工)
|
||||||
|
|
||||||
|
本仓没有自动提交命令。CI 只构建镜像;仓库凭据齐全时才推送。CI 不调用 modelhub.org.cn,不代领适配任务。
|
||||||
|
|
||||||
|
积分:待平台核实。
|
||||||
|
|
||||||
|
## 1. 在 Gitea 建仓
|
||||||
|
|
||||||
|
1. 在模盒的 Gitea 上新建仓库,名称建议 `modelhub-engine-bi150-compat`。
|
||||||
|
2. 把本目录推上去。不要提交 `.env`、密钥或 Token。
|
||||||
|
3. 确认默认分支上有 `Dockerfile`、`.gitea/workflows/build.yml` 和 `.gitea/workflows/task_info.env`。
|
||||||
|
|
||||||
|
## 2. 打 Tag
|
||||||
|
|
||||||
|
CI 在 `v*` 标签上触发,例如 `v0.1.0`。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git tag v0.1.0
|
||||||
|
git push origin v0.1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
标签名会成为镜像标签。
|
||||||
|
|
||||||
|
## 3. 看 CI
|
||||||
|
|
||||||
|
1. 打开该 Tag 的 Actions / 流水线。
|
||||||
|
2. `Load task info` 应打印 `FRAMEWORK=vllm_0_17_0_corex_4_4_0` 和 `GPU_TYPE=Iluvatar_bi-150`。
|
||||||
|
3. `Build image` 执行 `docker build`。基线镜像拉取失败时,日志会停在构建步骤;无卡不能代替真机验证。
|
||||||
|
4. 若 Runner 没有 `DOCKER_REGISTRY`、`DOCKER_USERNAME`、`DOCKER_PASSWORD`,流水线打印 build-only 并成功结束,不推送。
|
||||||
|
5. 三份凭据都由 Runner 环境提供,不写进仓库。有凭据时才会 `docker login` 并推送。
|
||||||
|
|
||||||
|
## 4. 在模盒选用引擎
|
||||||
|
|
||||||
|
1. 用浏览器登录模盒。
|
||||||
|
2. 等镜像审核通过。审核状态由人在页面上确认。
|
||||||
|
3. 在适配任务里选用该引擎。本仓不代为创建或提交任务。
|
||||||
|
4. 把审核通过时间、镜像 tag、以及首次被选用的适配任务 ID 写入 `docs/evidence.md`。积分栏保持「待平台核实」。
|
||||||
|
|
||||||
|
字段以登录后的控制台为准。
|
||||||
7
docs/weekly-report.md
Normal file
7
docs/weekly-report.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
【引擎 bi150-compat】2026-10-13
|
||||||
|
- 本周:I0–I4、I6、I7 完成;I5 未开启。阻塞:尚未推送 Gitea,也还没有真机流水线。
|
||||||
|
- 镜像 tag:未构建。默认基线 registry.iluvatar.com.cn:10443/customer/sz/vllm0.17.0-4.4.0-x86:v4.1(公开仓反推)
|
||||||
|
- 本地 test:见 make test
|
||||||
|
- 平台:未推送
|
||||||
|
- 积分:待核实
|
||||||
|
- 下周:人工建仓、打 v* Tag、看 CI;审核通过或被选用后再填 docs/evidence.md
|
||||||
1
patches/architectures/aliases.json
Normal file
1
patches/architectures/aliases.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
13
patches/architectures/whitelist.txt
Normal file
13
patches/architectures/whitelist.txt
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# 本仓本地兼容白名单,不是平台公布的完整列表。
|
||||||
|
# 只收录 vLLM 0.17 文本生成里常见、且本引擎准备接受的架构名。
|
||||||
|
LlamaForCausalLM
|
||||||
|
Qwen2ForCausalLM
|
||||||
|
Qwen3ForCausalLM
|
||||||
|
Qwen3MoeForCausalLM
|
||||||
|
MistralForCausalLM
|
||||||
|
MixtralForCausalLM
|
||||||
|
Gemma2ForCausalLM
|
||||||
|
Phi3ForCausalLM
|
||||||
|
InternLM2ForCausalLM
|
||||||
|
BaichuanForCausalLM
|
||||||
|
ChatGLMForConditionalGeneration
|
||||||
1
patches/chat_template/default_chat_template.jinja
Normal file
1
patches/chat_template/default_chat_template.jinja
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{% for message in messages %}{{ '<|' + message['role'] + '|>\n' + message['content'] + '<|end|>\n' }}{% endfor %}{% if add_generation_prompt %}{{ '<|assistant|>\n' }}{% endif %}
|
||||||
5
patches/tokenizer/README.md
Normal file
5
patches/tokenizer/README.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# tokenizer 修补(I5,未开启)
|
||||||
|
|
||||||
|
现有 bi150 引擎已经覆盖 tokenizer。本仓先补 chat_template(R2)和 architecture 白名单(R4)。
|
||||||
|
|
||||||
|
`APPLY_TOKENIZER_PATCH` 默认为 `0`。在确认某类模型只靠 R2/R4 仍无法加载之前,不要打开这项。
|
||||||
187
scripts/apply_patches.py
Executable file
187
scripts/apply_patches.py
Executable file
@@ -0,0 +1,187 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""把 R2 chat_template 与 R4 architecture 白名单应用到模型目录。
|
||||||
|
|
||||||
|
不访问平台,不启动 vLLM。
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
FORBIDDEN_FRAMEWORK = "vllm_fix_tokenizer"
|
||||||
|
TOKENIZER_PATCH_KEY = "modelhub_tokenizer_patch"
|
||||||
|
|
||||||
|
|
||||||
|
class PatchError(Exception):
|
||||||
|
"""模型目录不满足本引擎的修补前提。"""
|
||||||
|
|
||||||
|
|
||||||
|
def engine_root() -> Path:
|
||||||
|
return Path(__file__).resolve().parents[1]
|
||||||
|
|
||||||
|
|
||||||
|
def assert_framework(framework: str) -> None:
|
||||||
|
if not framework or framework == FORBIDDEN_FRAMEWORK:
|
||||||
|
raise PatchError(
|
||||||
|
"FRAMEWORK 必须是 vllm_0_17_0_corex_4_4_0,"
|
||||||
|
"不能使用 vllm_fix_tokenizer"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def load_whitelist(path: Path) -> set[str]:
|
||||||
|
names: set[str] = set()
|
||||||
|
for line in path.read_text(encoding="utf-8").splitlines():
|
||||||
|
item = line.split("#", 1)[0].strip()
|
||||||
|
if item:
|
||||||
|
names.add(item)
|
||||||
|
if not names:
|
||||||
|
raise PatchError(f"架构白名单为空: {path}")
|
||||||
|
return names
|
||||||
|
|
||||||
|
|
||||||
|
def load_aliases(path: Path) -> dict[str, str]:
|
||||||
|
data = json.loads(path.read_text(encoding="utf-8"))
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
raise PatchError(f"别名文件必须是对象: {path}")
|
||||||
|
return {str(key): str(value) for key, value in data.items()}
|
||||||
|
|
||||||
|
|
||||||
|
def preview_default_template(messages: list[dict[str, str]], add_generation_prompt: bool = True) -> str:
|
||||||
|
"""与 default_chat_template.jinja 同一套角色标记,供无 Jinja 依赖的本地预览。"""
|
||||||
|
parts: list[str] = []
|
||||||
|
for message in messages:
|
||||||
|
role = message["role"]
|
||||||
|
content = message["content"]
|
||||||
|
parts.append(f"<|{role}|>\n{content}<|end|>\n")
|
||||||
|
if add_generation_prompt:
|
||||||
|
parts.append("<|assistant|>\n")
|
||||||
|
return "".join(parts)
|
||||||
|
|
||||||
|
|
||||||
|
def apply_chat_template(model_dir: Path, template_text: str, force: bool = False) -> dict:
|
||||||
|
path = model_dir / "tokenizer_config.json"
|
||||||
|
if not path.is_file():
|
||||||
|
raise PatchError(f"缺少 tokenizer_config.json: {path}")
|
||||||
|
data = json.loads(path.read_text(encoding="utf-8"))
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
raise PatchError("tokenizer_config.json 必须是对象")
|
||||||
|
current = data.get("chat_template")
|
||||||
|
if isinstance(current, str) and current.strip() and not force:
|
||||||
|
return {"injected": False, "reason": "已有 chat_template"}
|
||||||
|
data["chat_template"] = template_text
|
||||||
|
path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||||
|
return {"injected": True, "reason": "已写入 chat_template"}
|
||||||
|
|
||||||
|
|
||||||
|
def apply_architectures(
|
||||||
|
model_dir: Path,
|
||||||
|
whitelist: set[str],
|
||||||
|
aliases: dict[str, str],
|
||||||
|
unknown_policy: str = "fail",
|
||||||
|
) -> dict:
|
||||||
|
if unknown_policy not in {"fail", "skip"}:
|
||||||
|
raise PatchError("ARCH_UNKNOWN_POLICY 只能是 fail 或 skip")
|
||||||
|
path = model_dir / "config.json"
|
||||||
|
if not path.is_file():
|
||||||
|
raise PatchError(f"缺少 config.json: {path}")
|
||||||
|
data = json.loads(path.read_text(encoding="utf-8"))
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
raise PatchError("config.json 必须是对象")
|
||||||
|
architectures = data.get("architectures")
|
||||||
|
if not isinstance(architectures, list) or not architectures:
|
||||||
|
raise PatchError("config.json 缺少 architectures")
|
||||||
|
|
||||||
|
rewritten: list[str] = []
|
||||||
|
skipped: list[str] = []
|
||||||
|
updated: list[str] = []
|
||||||
|
for name in architectures:
|
||||||
|
if not isinstance(name, str) or not name:
|
||||||
|
raise PatchError("architectures 含有空名称")
|
||||||
|
if name in whitelist:
|
||||||
|
updated.append(name)
|
||||||
|
continue
|
||||||
|
target = aliases.get(name)
|
||||||
|
if target in whitelist:
|
||||||
|
updated.append(target)
|
||||||
|
rewritten.append(f"{name}->{target}")
|
||||||
|
continue
|
||||||
|
if unknown_policy == "skip":
|
||||||
|
skipped.append(name)
|
||||||
|
updated.append(name)
|
||||||
|
continue
|
||||||
|
raise PatchError(f"未知架构: {name}")
|
||||||
|
|
||||||
|
changed = updated != architectures
|
||||||
|
if changed:
|
||||||
|
data["architectures"] = updated
|
||||||
|
path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||||
|
return {
|
||||||
|
"architectures": updated,
|
||||||
|
"rewritten": rewritten,
|
||||||
|
"skipped": skipped,
|
||||||
|
"changed": changed,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def apply_tokenizer_patch(model_dir: Path, enabled: bool) -> dict:
|
||||||
|
if not enabled:
|
||||||
|
return {"applied": False}
|
||||||
|
path = model_dir / "tokenizer_config.json"
|
||||||
|
if not path.is_file():
|
||||||
|
raise PatchError(f"缺少 tokenizer_config.json: {path}")
|
||||||
|
data = json.loads(path.read_text(encoding="utf-8"))
|
||||||
|
data[TOKENIZER_PATCH_KEY] = "optional-v1"
|
||||||
|
path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||||
|
return {"applied": True}
|
||||||
|
|
||||||
|
|
||||||
|
def apply_all(
|
||||||
|
model_dir: Path,
|
||||||
|
template_text: str,
|
||||||
|
whitelist: set[str],
|
||||||
|
aliases: dict[str, str],
|
||||||
|
unknown_policy: str = "fail",
|
||||||
|
force_chat_template: bool = False,
|
||||||
|
tokenizer_patch: bool = False,
|
||||||
|
) -> dict:
|
||||||
|
chat = apply_chat_template(model_dir, template_text, force=force_chat_template)
|
||||||
|
architectures = apply_architectures(model_dir, whitelist, aliases, unknown_policy)
|
||||||
|
tokenizer = apply_tokenizer_patch(model_dir, tokenizer_patch)
|
||||||
|
return {"chat_template": chat, "architectures": architectures, "tokenizer": tokenizer}
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: list[str] | None = None) -> int:
|
||||||
|
root = engine_root()
|
||||||
|
parser = argparse.ArgumentParser(description="应用 R2/R4 模型目录修补")
|
||||||
|
parser.add_argument("--model-dir", required=True, type=Path)
|
||||||
|
parser.add_argument("--framework", default="vllm_0_17_0_corex_4_4_0")
|
||||||
|
parser.add_argument("--unknown-policy", default="fail", choices=("fail", "skip"))
|
||||||
|
parser.add_argument("--force-chat-template", action="store_true")
|
||||||
|
parser.add_argument("--tokenizer-patch", action="store_true")
|
||||||
|
parser.add_argument("--template", type=Path, default=root / "patches/chat_template/default_chat_template.jinja")
|
||||||
|
parser.add_argument("--whitelist", type=Path, default=root / "patches/architectures/whitelist.txt")
|
||||||
|
parser.add_argument("--aliases", type=Path, default=root / "patches/architectures/aliases.json")
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
try:
|
||||||
|
assert_framework(args.framework)
|
||||||
|
result = apply_all(
|
||||||
|
args.model_dir,
|
||||||
|
args.template.read_text(encoding="utf-8"),
|
||||||
|
load_whitelist(args.whitelist),
|
||||||
|
load_aliases(args.aliases),
|
||||||
|
unknown_policy=args.unknown_policy,
|
||||||
|
force_chat_template=args.force_chat_template,
|
||||||
|
tokenizer_patch=args.tokenizer_patch,
|
||||||
|
)
|
||||||
|
except (PatchError, OSError, json.JSONDecodeError) as exc:
|
||||||
|
print(f"修补失败: {exc}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
print(json.dumps(result, ensure_ascii=False))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
125
scripts/entrypoint.sh
Executable file
125
scripts/entrypoint.sh
Executable file
@@ -0,0 +1,125 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# 读取 task_info.env,修补模型目录,并准备 vLLM 启动命令。
|
||||||
|
# --dry-run 只打印计划,不启动进程,不访问平台。
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo "用法: entrypoint.sh [--dry-run]" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
DRY_RUN=0
|
||||||
|
if [[ "${1:-}" == "--dry-run" ]]; then
|
||||||
|
DRY_RUN=1
|
||||||
|
elif [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
elif [[ -n "${1:-}" ]]; then
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
info_file="${TASK_INFO_FILE:-}"
|
||||||
|
if [[ -z "$info_file" ]]; then
|
||||||
|
if [[ -f "${ROOT}/task_info.env" ]]; then
|
||||||
|
info_file="${ROOT}/task_info.env"
|
||||||
|
else
|
||||||
|
info_file="${ROOT}/task_info.env.example"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "$info_file" ]]; then
|
||||||
|
echo "找不到任务信息文件: ${info_file}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 已在环境中的变量优先于文件,方便本地覆盖。
|
||||||
|
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||||
|
case "$line" in
|
||||||
|
""|\#*) continue ;;
|
||||||
|
esac
|
||||||
|
key="${line%%=*}"
|
||||||
|
value="${line#*=}"
|
||||||
|
if [[ -z "$key" || "$key" == "$line" ]]; then
|
||||||
|
echo "任务信息行无法解析: ${line}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ -z "${!key+x}" ]]; then
|
||||||
|
export "${key}=${value}"
|
||||||
|
fi
|
||||||
|
done <"$info_file"
|
||||||
|
|
||||||
|
FRAMEWORK="${FRAMEWORK:-}"
|
||||||
|
GPU_TYPE="${GPU_TYPE:-}"
|
||||||
|
TASK_TYPE="${TASK_TYPE:-text-generation}"
|
||||||
|
MODEL_DIR="${MODEL_DIR:-/model}"
|
||||||
|
SERVED_MODEL_NAME="${SERVED_MODEL_NAME:-llm}"
|
||||||
|
PORT="${PORT:-8000}"
|
||||||
|
ARCH_UNKNOWN_POLICY="${ARCH_UNKNOWN_POLICY:-fail}"
|
||||||
|
APPLY_TOKENIZER_PATCH="${APPLY_TOKENIZER_PATCH:-0}"
|
||||||
|
FORCE_CHAT_TEMPLATE="${FORCE_CHAT_TEMPLATE:-0}"
|
||||||
|
|
||||||
|
if [[ -z "$FRAMEWORK" || "$FRAMEWORK" == "vllm_fix_tokenizer" ]]; then
|
||||||
|
echo "拒绝 FRAMEWORK=${FRAMEWORK:-空}。本仓使用 vllm_0_17_0_corex_4_4_0。" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ -z "$GPU_TYPE" ]]; then
|
||||||
|
echo "GPU_TYPE 为空" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
plan=(
|
||||||
|
python3 -m vllm.entrypoints.openai.api_server
|
||||||
|
--model "$MODEL_DIR"
|
||||||
|
--port "$PORT"
|
||||||
|
--served-model-name "$SERVED_MODEL_NAME"
|
||||||
|
--trust-remote-code
|
||||||
|
)
|
||||||
|
|
||||||
|
echo "FRAMEWORK=${FRAMEWORK}"
|
||||||
|
echo "GPU_TYPE=${GPU_TYPE}"
|
||||||
|
echo "TASK_TYPE=${TASK_TYPE}"
|
||||||
|
echo "MODEL_DIR=${MODEL_DIR}"
|
||||||
|
echo "ARCH_UNKNOWN_POLICY=${ARCH_UNKNOWN_POLICY}"
|
||||||
|
echo "plan: ${plan[*]}"
|
||||||
|
|
||||||
|
if [[ "$DRY_RUN" -eq 1 ]]; then
|
||||||
|
preview="$(
|
||||||
|
python3 - "$ROOT" <<'PY'
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
root = Path(sys.argv[1])
|
||||||
|
sys.path.insert(0, str(root / "scripts"))
|
||||||
|
import apply_patches
|
||||||
|
messages = [
|
||||||
|
{"role": "system", "content": "你是助手"},
|
||||||
|
{"role": "user", "content": "你好"},
|
||||||
|
]
|
||||||
|
print(apply_patches.preview_default_template(messages).replace("\n", "\\n"))
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
echo "template_preview: ${preview}"
|
||||||
|
echo "dry-run: 不启动进程,不访问平台"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -d "$MODEL_DIR" ]]; then
|
||||||
|
echo "模型目录不存在: ${MODEL_DIR}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
patch_args=(
|
||||||
|
--model-dir "$MODEL_DIR"
|
||||||
|
--framework "$FRAMEWORK"
|
||||||
|
--unknown-policy "$ARCH_UNKNOWN_POLICY"
|
||||||
|
)
|
||||||
|
if [[ "$FORCE_CHAT_TEMPLATE" == "1" ]]; then
|
||||||
|
patch_args+=(--force-chat-template)
|
||||||
|
fi
|
||||||
|
if [[ "$APPLY_TOKENIZER_PATCH" == "1" ]]; then
|
||||||
|
patch_args+=(--tokenizer-patch)
|
||||||
|
fi
|
||||||
|
|
||||||
|
python3 "${ROOT}/scripts/apply_patches.py" "${patch_args[@]}"
|
||||||
|
exec "${plan[@]}"
|
||||||
12
task_info.env.example
Normal file
12
task_info.env.example
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# 本地与镜像内的默认任务信息。平台若给出不同 FRAMEWORK,只改这一处,并保持 Dockerfile 的 ENV 与 CI 文件一致。
|
||||||
|
GPU_TYPE=Iluvatar_bi-150
|
||||||
|
FRAMEWORK=vllm_0_17_0_corex_4_4_0
|
||||||
|
TASK_TYPE=text-generation
|
||||||
|
MODEL_DIR=/model
|
||||||
|
SERVED_MODEL_NAME=llm
|
||||||
|
PORT=8000
|
||||||
|
# 未知架构:fail 拒绝启动;skip 只记录并继续。默认拒绝。
|
||||||
|
ARCH_UNKNOWN_POLICY=fail
|
||||||
|
# I5 tokenizer 修补默认关闭。
|
||||||
|
APPLY_TOKENIZER_PATCH=0
|
||||||
|
FORCE_CHAT_TEMPLATE=0
|
||||||
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
4
tests/fixtures/fake-model/config.json
vendored
Normal file
4
tests/fixtures/fake-model/config.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"model_type": "llama",
|
||||||
|
"architectures": ["LlamaForCausalLM"]
|
||||||
|
}
|
||||||
3
tests/fixtures/fake-model/tokenizer.json
vendored
Normal file
3
tests/fixtures/fake-model/tokenizer.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"version": "1.0"
|
||||||
|
}
|
||||||
3
tests/fixtures/fake-model/tokenizer_config.json
vendored
Normal file
3
tests/fixtures/fake-model/tokenizer_config.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"tokenizer_class": "LlamaTokenizer"
|
||||||
|
}
|
||||||
96
tests/test_contract.py
Normal file
96
tests/test_contract.py
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
|
||||||
|
|
||||||
|
class EngineContractTest(unittest.TestCase):
|
||||||
|
def test_locked_values_match(self) -> None:
|
||||||
|
example = (ROOT / "task_info.env.example").read_text(encoding="utf-8")
|
||||||
|
workflow_env = (ROOT / ".gitea/workflows/task_info.env").read_text(encoding="utf-8")
|
||||||
|
dockerfile = (ROOT / "Dockerfile").read_text(encoding="utf-8")
|
||||||
|
self.assertIn("FRAMEWORK=vllm_0_17_0_corex_4_4_0", example)
|
||||||
|
self.assertIn("GPU_TYPE=Iluvatar_bi-150", example)
|
||||||
|
self.assertIn("FRAMEWORK=vllm_0_17_0_corex_4_4_0", workflow_env)
|
||||||
|
self.assertIn("GPU_TYPE=Iluvatar_bi-150", workflow_env)
|
||||||
|
self.assertIn("FRAMEWORK=vllm_0_17_0_corex_4_4_0", dockerfile)
|
||||||
|
self.assertIn("GPU_TYPE=Iluvatar_bi-150", dockerfile)
|
||||||
|
self.assertIn(
|
||||||
|
"registry.iluvatar.com.cn:10443/customer/sz/vllm0.17.0-4.4.0-x86:v4.1",
|
||||||
|
dockerfile,
|
||||||
|
)
|
||||||
|
self.assertNotIn("FRAMEWORK=vllm_fix_tokenizer", example)
|
||||||
|
self.assertNotIn("FRAMEWORK=vllm_fix_tokenizer", workflow_env)
|
||||||
|
self.assertNotIn("FRAMEWORK=vllm_fix_tokenizer", dockerfile)
|
||||||
|
|
||||||
|
def test_ci_does_not_submit_platform_task(self) -> None:
|
||||||
|
workflow = (ROOT / ".gitea/workflows/build.yml").read_text(encoding="utf-8")
|
||||||
|
self.assertNotIn("adminApi", workflow)
|
||||||
|
self.assertNotIn("modelhub.org.cn", workflow)
|
||||||
|
self.assertIn("build-only", workflow)
|
||||||
|
self.assertIn('tags:', workflow)
|
||||||
|
|
||||||
|
def test_no_zip_or_contest_targets(self) -> None:
|
||||||
|
makefile = (ROOT / "Makefile").read_text(encoding="utf-8")
|
||||||
|
self.assertNotIn("package", makefile)
|
||||||
|
self.assertNotIn("submit", makefile)
|
||||||
|
self.assertFalse((ROOT / "contest").exists())
|
||||||
|
self.assertFalse((ROOT / "computility-run.yaml").exists())
|
||||||
|
|
||||||
|
def test_dry_run_prints_plan(self) -> None:
|
||||||
|
env = os.environ.copy()
|
||||||
|
env.pop("FRAMEWORK", None)
|
||||||
|
env.pop("GPU_TYPE", None)
|
||||||
|
env.pop("MODEL_DIR", None)
|
||||||
|
completed = subprocess.run(
|
||||||
|
["bash", "scripts/entrypoint.sh", "--dry-run"],
|
||||||
|
cwd=ROOT,
|
||||||
|
env=env,
|
||||||
|
text=True,
|
||||||
|
capture_output=True,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
self.assertEqual(completed.returncode, 0, completed.stderr)
|
||||||
|
self.assertIn("FRAMEWORK=vllm_0_17_0_corex_4_4_0", completed.stdout)
|
||||||
|
self.assertIn("GPU_TYPE=Iluvatar_bi-150", completed.stdout)
|
||||||
|
self.assertIn("python3 -m vllm.entrypoints.openai.api_server", completed.stdout)
|
||||||
|
self.assertIn("dry-run: 不启动进程,不访问平台", completed.stdout)
|
||||||
|
self.assertNotIn("modelhub.org.cn", completed.stdout)
|
||||||
|
self.assertIn("<|user|>", completed.stdout)
|
||||||
|
|
||||||
|
def test_dry_run_rejects_forbidden_framework(self) -> None:
|
||||||
|
env = os.environ.copy()
|
||||||
|
env["FRAMEWORK"] = "vllm_fix_tokenizer"
|
||||||
|
completed = subprocess.run(
|
||||||
|
["bash", "scripts/entrypoint.sh", "--dry-run"],
|
||||||
|
cwd=ROOT,
|
||||||
|
env=env,
|
||||||
|
text=True,
|
||||||
|
capture_output=True,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
self.assertNotEqual(completed.returncode, 0)
|
||||||
|
self.assertIn("vllm_fix_tokenizer", completed.stderr)
|
||||||
|
|
||||||
|
def test_tokenizer_patch_stays_off(self) -> None:
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
import apply_patches
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
model = Path(tmp) / "model"
|
||||||
|
shutil.copytree(ROOT / "tests" / "fixtures" / "fake-model", model)
|
||||||
|
result = apply_patches.apply_all(
|
||||||
|
model,
|
||||||
|
(ROOT / "patches/chat_template/default_chat_template.jinja").read_text(encoding="utf-8"),
|
||||||
|
apply_patches.load_whitelist(ROOT / "patches/architectures/whitelist.txt"),
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
self.assertFalse(result["tokenizer"]["applied"])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
67
tests/test_r2_chat_template.py
Normal file
67
tests/test_r2_chat_template.py
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import json
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import apply_patches
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
FIXTURE = ROOT / "tests" / "fixtures" / "fake-model"
|
||||||
|
TEMPLATE = (ROOT / "patches" / "chat_template" / "default_chat_template.jinja").read_text(encoding="utf-8")
|
||||||
|
WHITELIST = apply_patches.load_whitelist(ROOT / "patches" / "architectures" / "whitelist.txt")
|
||||||
|
ALIASES = apply_patches.load_aliases(ROOT / "patches" / "architectures" / "aliases.json")
|
||||||
|
|
||||||
|
|
||||||
|
class ChatTemplateTest(unittest.TestCase):
|
||||||
|
def setUp(self) -> None:
|
||||||
|
self.tmp = tempfile.TemporaryDirectory()
|
||||||
|
self.model = Path(self.tmp.name) / "model"
|
||||||
|
shutil.copytree(FIXTURE, self.model)
|
||||||
|
|
||||||
|
def tearDown(self) -> None:
|
||||||
|
self.tmp.cleanup()
|
||||||
|
|
||||||
|
def test_injects_missing_template(self) -> None:
|
||||||
|
result = apply_patches.apply_chat_template(self.model, TEMPLATE)
|
||||||
|
self.assertTrue(result["injected"])
|
||||||
|
data = json.loads((self.model / "tokenizer_config.json").read_text(encoding="utf-8"))
|
||||||
|
self.assertEqual(data["chat_template"], TEMPLATE)
|
||||||
|
|
||||||
|
def test_keeps_existing_template(self) -> None:
|
||||||
|
path = self.model / "tokenizer_config.json"
|
||||||
|
data = json.loads(path.read_text(encoding="utf-8"))
|
||||||
|
data["chat_template"] = "already-set"
|
||||||
|
path.write_text(json.dumps(data), encoding="utf-8")
|
||||||
|
result = apply_patches.apply_chat_template(self.model, TEMPLATE)
|
||||||
|
self.assertFalse(result["injected"])
|
||||||
|
kept = json.loads(path.read_text(encoding="utf-8"))
|
||||||
|
self.assertEqual(kept["chat_template"], "already-set")
|
||||||
|
|
||||||
|
def test_force_overwrites_template(self) -> None:
|
||||||
|
path = self.model / "tokenizer_config.json"
|
||||||
|
data = json.loads(path.read_text(encoding="utf-8"))
|
||||||
|
data["chat_template"] = "already-set"
|
||||||
|
path.write_text(json.dumps(data), encoding="utf-8")
|
||||||
|
result = apply_patches.apply_chat_template(self.model, TEMPLATE, force=True)
|
||||||
|
self.assertTrue(result["injected"])
|
||||||
|
|
||||||
|
def test_missing_tokenizer_config_fails(self) -> None:
|
||||||
|
(self.model / "tokenizer_config.json").unlink()
|
||||||
|
with self.assertRaises(apply_patches.PatchError):
|
||||||
|
apply_patches.apply_chat_template(self.model, TEMPLATE)
|
||||||
|
|
||||||
|
def test_preview_renders_roles(self) -> None:
|
||||||
|
text = apply_patches.preview_default_template(
|
||||||
|
[
|
||||||
|
{"role": "system", "content": "你是助手"},
|
||||||
|
{"role": "user", "content": "你好"},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
self.assertIn("<|user|>\n你好<|end|>\n", text)
|
||||||
|
self.assertTrue(text.endswith("<|assistant|>\n"))
|
||||||
|
self.assertIn("message['role']", TEMPLATE)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
65
tests/test_r4_architectures.py
Normal file
65
tests/test_r4_architectures.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import json
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import apply_patches
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
FIXTURE = ROOT / "tests" / "fixtures" / "fake-model"
|
||||||
|
WHITELIST = apply_patches.load_whitelist(ROOT / "patches" / "architectures" / "whitelist.txt")
|
||||||
|
|
||||||
|
|
||||||
|
class ArchitectureTest(unittest.TestCase):
|
||||||
|
def setUp(self) -> None:
|
||||||
|
self.tmp = tempfile.TemporaryDirectory()
|
||||||
|
self.model = Path(self.tmp.name) / "model"
|
||||||
|
shutil.copytree(FIXTURE, self.model)
|
||||||
|
|
||||||
|
def tearDown(self) -> None:
|
||||||
|
self.tmp.cleanup()
|
||||||
|
|
||||||
|
def write_architectures(self, names: list[str]) -> None:
|
||||||
|
path = self.model / "config.json"
|
||||||
|
data = json.loads(path.read_text(encoding="utf-8"))
|
||||||
|
data["architectures"] = names
|
||||||
|
path.write_text(json.dumps(data), encoding="utf-8")
|
||||||
|
|
||||||
|
def test_whitelist_passes(self) -> None:
|
||||||
|
result = apply_patches.apply_architectures(self.model, WHITELIST, {}, "fail")
|
||||||
|
self.assertEqual(result["architectures"], ["LlamaForCausalLM"])
|
||||||
|
self.assertFalse(result["changed"])
|
||||||
|
|
||||||
|
def test_unknown_architecture_fails(self) -> None:
|
||||||
|
self.write_architectures(["ExampleForCausalLM"])
|
||||||
|
with self.assertRaises(apply_patches.PatchError) as ctx:
|
||||||
|
apply_patches.apply_architectures(self.model, WHITELIST, {}, "fail")
|
||||||
|
self.assertIn("ExampleForCausalLM", str(ctx.exception))
|
||||||
|
|
||||||
|
def test_unknown_architecture_skip_keeps_name(self) -> None:
|
||||||
|
self.write_architectures(["ExampleForCausalLM"])
|
||||||
|
result = apply_patches.apply_architectures(self.model, WHITELIST, {}, "skip")
|
||||||
|
self.assertEqual(result["skipped"], ["ExampleForCausalLM"])
|
||||||
|
data = json.loads((self.model / "config.json").read_text(encoding="utf-8"))
|
||||||
|
self.assertEqual(data["architectures"], ["ExampleForCausalLM"])
|
||||||
|
|
||||||
|
def test_alias_rewrites_to_whitelist(self) -> None:
|
||||||
|
self.write_architectures(["LlamaForCausalLMAlias"])
|
||||||
|
result = apply_patches.apply_architectures(
|
||||||
|
self.model,
|
||||||
|
WHITELIST,
|
||||||
|
{"LlamaForCausalLMAlias": "LlamaForCausalLM"},
|
||||||
|
"fail",
|
||||||
|
)
|
||||||
|
self.assertEqual(result["rewritten"], ["LlamaForCausalLMAlias->LlamaForCausalLM"])
|
||||||
|
data = json.loads((self.model / "config.json").read_text(encoding="utf-8"))
|
||||||
|
self.assertEqual(data["architectures"], ["LlamaForCausalLM"])
|
||||||
|
|
||||||
|
def test_shipped_aliases_are_empty(self) -> None:
|
||||||
|
aliases = apply_patches.load_aliases(ROOT / "patches" / "architectures" / "aliases.json")
|
||||||
|
self.assertEqual(aliases, {})
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user