- detect_model.py picks TP/port/served name/eager/util from config.json - validate TP against the model config (dense global KV heads, MoE k % 32) - one apply_patch.py covers both variants, idempotent - Dockerfile stays generic: no ENV/WORKDIR/EXPOSE/ENTRYPOINT - run.sh/verify.sh follow the llama4-bi150 layout
12 KiB
gemma4-bi150 — Gemma 4 家族统一部署(Iluvatar BI-V150)
一个镜像同时支持两个 Gemma 4 家族模型,启动时读模型 config.json 自动选择运行参数:
| 模型 | 类型 | 权重 | TP | 卡数 | 端口 | served name | eager | 显存 | seqs |
|---|---|---|---|---|---|---|---|---|---|
gemma-4-31B-it |
dense,60 层 | 58.25 GiB | 4 | 4 | 8095 | gemma-4-31B-it |
0 | 0.90 | 4 |
GEV-26B-Decide |
MoE,30 层 / 128 experts | 48.07 GiB | 2 | 2 | 8094 | GEV-26B-Decide |
1 | 0.95 | 2 |
本仓库由
memory/gemma4-31b-deploy/和memory/gev-26b-decide-deploy/合并而来, 原目录保留未删。合并依据见文末「为什么可以合并」。
1. 目录结构
gemma4-bi150-deploy/
├── Dockerfile # 只放基础镜像 + 补丁 + 脚本,不写 ENV/EXPOSE/ENTRYPOINT
├── entrypoint.sh # 容器入口:打补丁 → detect → exec vllm
├── detect_model.py # 从 config.json 判断 dense / MoE,并校验 TP 可行性
├── apply_patch.py # 幂等应用两个 CoreX vLLM 补丁
├── run.sh # 构建 + 启动 + 健康检查
├── verify.sh # health / models / chat / 可选推理 / 可选图片
├── docker-compose.yml
├── .dockerignore
├── .gitignore
├── .gitea/workflows/
│ ├── docker-build-push.yml
│ └── task_info.env
└── README.md
2. 快速开始
cd gemma4-bi150-deploy
# Gemma-4-31B(4 卡,默认用 15,14,13,12)
./run.sh /mnt/disk0/models/google/gemma-4-31B-it
# GEV-26B-Decide(2 卡,默认用 14,15)
./run.sh /mnt/disk0/models/autotrust/GEV-26B-Decide
# 验证
./verify.sh --model-dir /mnt/disk0/models/google/gemma-4-31B-it
./verify.sh --model-dir /mnt/disk0/models/autotrust/GEV-26B-Decide --with-image
模型类型不需要手写参数,run.sh 会调用 detect_model.py 读 config.json 判断。
2.1 不传模型目录
./run.sh
按顺序尝试:
/mnt/disk0/models/google/gemma-4-31B-it
/mnt/disk0/models/autotrust/GEV-26B-Decide
2.2 Compose
MODEL_HOST_DIR=/mnt/disk0/models/google/gemma-4-31B-it docker compose up -d --build
Compose 里 TP / PORT / SERVED_NAME / MAX_NUM_SEQS / GPU_MEM_UTIL / ENFORCE_EAGER
都默认留空,由容器内 entrypoint.sh 检测后填充。
3. 自动识别逻辑
3.1 判断变体
arch = config["architectures"][0] # 必须以 Gemma4 开头
text = config.get("text_config", config)
experts = num_experts / num_local_experts / n_routed_experts
| 条件 | 变体 | 运行参数 |
|---|---|---|
experts > 1 |
GEV-26B-Decide(MoE) | TP2 / 8094 / eager1 / 0.95 / 2 seqs |
experts 缺失或 == 1 |
Gemma-4-31B(dense) | TP4 / 8095 / eager0 / 0.90 / 4 seqs |
无 experts 且目录名含 gev |
GEV | 同上 |
无 experts 且目录名含 gemma |
Gemma-4-31B | 同上 |
| 其它 / 架构不是 Gemma4 | 直接报错 | 不启动 |
num_experts=1 按 dense 处理,因为部分 dense 配置用 1 表示"没有专家"。
3.2 顺便校验 TP 可行性(这是合并后的额外收益)
两个变体都有硬性 TP 上限,detect_model.py 会拿 config.json 反查:
dense: num_global_key_value_heads % TP == 0
Gemma-4-31B 的 4 个 global KV head,TP8 会切成 0.5 → 拒绝启动
MoE: (moe_intermediate_size / TP) % 32 == 0
CoreX ixfops.moe_w16a16_group_gemm 要求 k % 32 == 0
GEV 704/2=352 ✅,704/4=176 ❌
所以下面这些都会被提前拦住,而不是等 vLLM 跑到一半才崩:
TP=8 ./run.sh .../gemma-4-31B-it # num_global_key_value_heads=4 % 8 != 0
TP=4 ./run.sh .../GEV-26B-Decide # 704/4=176,k % 32 != 0
确实需要强跑时用 GEMMA4_SKIP_TP_CHECK=1 降级为警告。
3.3 检测输出
$ python3 detect_model.py /mnt/disk0/models/autotrust/GEV-26B-Decide
DETECTED_MODEL_KIND=gev-26b-decide
DETECTED_TP=2
DETECTED_PORT=8094
DETECTED_SERVED_NAME=GEV-26B-Decide
DETECTED_GPU_COUNT=2
DETECTED_MAX_NUM_SEQS=2
DETECTED_GPU_MEM_UTIL=0.95
DETECTED_ENFORCE_EAGER=1
DETECTED_DESCRIPTION='AutoTrust GEV-26B-Decide MoE (BF16, TP2, 2 GPUs)'
python3 detect_model.py <dir> --json # verify.sh 用
python3 detect_model.py <dir> --tp 4 # 按 TP4 校验可行性
4. 覆盖启动参数
显式环境变量优先于检测结果。
# 换卡
GPUS=12,13,14,15 ./run.sh /mnt/disk0/models/google/gemma-4-31B-it
# 换端口和模型名
PORT=9001 SERVED_NAME=my-gemma4 ./run.sh /mnt/disk0/models/google/gemma-4-31B-it
# 加 vLLM 参数
EXTRA_ARGS="--max-model-len 16384" ./run.sh /mnt/disk0/models/google/gemma-4-31B-it
# 加 docker 参数
EXTRA_DOCKER_ARGS="-v /mnt/disk1:/data:ro" ./run.sh /mnt/disk0/models/google/gemma-4-31B-it
# 用已有镜像(CI 产物)不重新 build
BUILD=0 IMAGE=harbor.4pd.io/modelhubxc/xiejiahao/gemma4-bi150:v0.1.0 \
./run.sh /mnt/disk0/models/google/gemma-4-31B-it
# 只打印命令
DRY_RUN=1 ./run.sh /mnt/disk0/models/google/gemma-4-31B-it
常用环境变量:
IMAGE NAME MODEL_HOST_DIR MODEL_MOUNT PORT SERVED_NAME TP GPUS
MAX_MODEL_LEN MAX_NUM_SEQS GPU_MEM_UTIL MAX_IMAGES ENFORCE_EAGER
AUTO_DETECT HEALTH_TIMEOUT EXTRA_ARGS EXTRA_DOCKER_ARGS BUILD DRY_RUN
GEMMA4_SKIP_TP_CHECK
AUTO_DETECT=0 可关闭容器内检测,全部手动给值。
5. 为什么 Dockerfile 里没有 ENV / EXPOSE / ENTRYPOINT
镜像必须能同时服务 dense 和 MoE 两个变体,而这些参数在两个变体之间不同:
| 参数 | Gemma-4-31B | GEV-26B-Decide |
|---|---|---|
| TP | 4 | 2 |
| 端口 | 8095 | 8094 |
| eager | 0 | 1 |
| 显存 | 0.90 | 0.95 |
| seqs | 4 | 2 |
写进镜像就只能二选一,或者做两个镜像,违背"一个家族一个镜像"的目标。
所以启动方式统一由 run.sh / docker-compose.yml 传入:
docker run ... \
--entrypoint /usr/local/bin/gemma4-entrypoint.sh \
--workdir /opt/gemma4 \
--expose 8095 \
-e MODEL_PATH=/models/gemma4 \
-e AUTO_DETECT=1 \
<image>
6. 补丁
apply_patch.py 应用两个补丁,构建镜像时和容器启动时都会幂等执行一次。
| 补丁 | 目标文件 | 不打的后果 |
|---|---|---|
MultiModalDataDict 导入路径 |
vllm/model_executor/models/gemma4_mm.py |
ImportError,模型检查阶段就失败(两个变体都需要) |
| decode fast path 的 attention metadata 判断 | vllm/model_executor/layers/fused_moe/fused_moe.py |
GEV 的 CUDA graph capture 报 AttributeError: no attribute 'num_decodes' |
第二个补丁对 dense 的 Gemma-4-31B 通常用不到,但保持幂等一起打,避免同一镜像换模型后失效。
补丁是针对这个精确基础镜像生成的,换镜像需要重新生成:
registry.iluvatar.com.cn:10443/customer/sz/vllm0.17.0-4.4.0-x86:v7
vLLM 0.17.0+corex.20260612070235
7. 验证
# 自动找运行中的容器
./verify.sh
# 指定模型目录(推荐,能顺带校验变体)
./verify.sh --model-dir /mnt/disk0/models/google/gemma-4-31B-it
# 指定容器
./verify.sh --container gemma4-31b
# 额外图片识别
./verify.sh --model-dir /mnt/disk0/models/autotrust/GEV-26B-Decide --with-image
# 额外推理题(期望 27)
./verify.sh --model-dir /mnt/disk0/models/google/gemma-4-31B-it --with-reasoning
# 直接指定服务地址
./verify.sh --base-url http://127.0.0.1:8095 --served-name gemma-4-31B-it
检查项:/health、/v1/models、/v1/chat/completions,可选推理题和图片识别。
只依赖 bash、curl、python3。全部通过返回 0。
/health只看curl退出码,不看 body——vLLM 会返回 200 但 body 为空。
8. 关键参数的理由
8.1 Gemma-4-31B 为什么是 TP4
Gemma4ForConditionalGeneration
num_attention_heads = 32
num_key_value_heads = 16 # sliding attention
num_global_key_value_heads = 4 # full attention
head_dim / global_head_dim = 256 / 512
num_hidden_layers = 60
num_global_key_value_heads=4 是最紧的约束:TP8 会切成 0.5,当前 vLLM 不支持。
TP4 下 4/4=1,权重 58.25 GiB / 4 ≈ 14.6 GiB/卡,实测加载后 15.89 GiB/卡。
8.2 GEV-26B-Decide 为什么是 TP2
Gemma4ForConditionalGeneration
hidden_size = 2816
num_hidden_layers = 30
num_attention_heads = 16
num_key_value_heads = 8
moe_intermediate_size = 704
num_experts = 128
top_k_experts = 8
CoreX ixfops.moe_w16a16_group_gemm 要求分组 GEMM 的 K 维能被 32 整除,
704 / TP 的结果:TP8→88 ❌、TP4→176 ❌、TP2→352 ✅。
TP1 显存不够,所以只有 TP2。
8.3 GEV 为什么开 --enforce-eager
MoE + CoreX 的 CUDA graph 路径不稳定,实测开 eager 才能稳定启动; dense 的 Gemma-4-31B 不需要,关掉可以拿到更好的吞吐。
8.4 GEV 为什么用 0.95 显存
权重 48.07 GiB / 2 ≈ 24.99 GiB/卡,0.95 下 KV 约 4.9 GiB,适合低并发。
max-num-seqs 同时降到 2,避免 KV 被并发打爆。
9. GPU 选择
run.sh 默认优先序号大的卡,避免和 16 卡大模型抢 0-11:
Gemma-4-31B → 15,14,13,12
GEV-26B-Decide → 14,15
卡数不足会直接报错,卡数多余只警告(vLLM 只用前 TP 张):
GPUS=12,13 ./run.sh .../gemma-4-31B-it # 报错:需要 4 张
GPUS=0,1,2,3,4,5 ./run.sh .../GEV-26B-Decide # 警告:只用其中 2 张
查看空闲卡:
/usr/local/corex/bin/ixsmi
10. 常见问题
| 现象 | 原因 | 处理 |
|---|---|---|
ImportError: cannot import name 'MultiModalDataDict' |
补丁没打上 | 确认 apply_patch.py 执行过 |
AttributeError: 'TritonAttentionMetadata' object has no attribute 'num_decodes' |
MoE 补丁没打上 | 同上 |
Expected k % 32 == 0 to be true |
GEV 用了 TP4/TP8 | 回到 TP2;detect_model.py 会提前拦住 |
| KV head 无法切分 / head 数不整除 | dense 用了 TP8 | 回到 TP4 |
cudaGetDeviceCount() ... Error 1001 |
少了 --privileged -v /dev:/dev |
用 run.sh,不要手写简化版 docker run |
| 启动 10 分钟还没 ready | 冷读 49–59 GB 权重 | 默认给 900s;docker logs -f 看 Loading weights |
/health 超时但容器在跑 |
还在加载权重 | 以 Loading weights took 和 /v1/models 为准 |
11. CI 与发布
# 首次
git tag v0.1.0 && git push origin v0.1.0
- 只有推送
v*标签才触发; - runner label:
amd64-ubuntu-24.04,需要DOCKER_*/FIXED_TOKEN; task_info.env:FRAMEWORK=vllm_gemma_4(必须独特,不要用泛化的vllm);- CI 被取消后不要复用标签,直接 bump patch;
- CI 构建 vLLM 基础镜像较久,本次预计 1.5 小时量级,别着急。
CI 产物通用,不会自动启动,用 run.sh 指定 entrypoint:
BUILD=0 IMAGE=<ci-image> ./run.sh /mnt/disk0/models/google/gemma-4-31B-it
12. 为什么可以合并
合并前是 gemma4-31b-deploy/ 和 gev-26b-decide-deploy/ 两套目录。逐项对比:
| 项 | Gemma-4-31B | GEV-26B-Decide | 能否 detect |
|---|---|---|---|
| 基础镜像 | 同一个 | 同一个 | — |
| 架构 | Gemma4ForConditionalGeneration |
同一个 | 不需要 |
| vLLM 补丁 | 两个 | 同样两个 | 不需要 |
| docker run 参数 | 相同 | 相同 | — |
| TP | 4 | 2 | ✅ config 可判 |
| 端口 | 8095 | 8094 | ✅ config 可判 |
| served name | gemma-4-31B-it |
GEV-26B-Decide |
✅ config 可判 |
| eager | 0 | 1 | ✅ config 可判 |
| 显存 / seqs | 0.90 / 4 | 0.95 / 2 | ✅ config 可判 |
差异项全部可由 config.json 推断,和 Llama 4 用 num_local_experts 分 Scout/Maverick 是同一形态,
因此按"一个模型家族一个仓库"的原则合并。
顺带解决的问题:
- 两份
apply_patch.py原本只差注释,现在只有一份; - 两个 Dockerfile 原本都写死了
ENV/EXPOSE/ENTRYPOINT,现在通用; - 一次 CI 覆盖两个模型,而不是两次构建(每次约 1.5 小时);
- 新增 TP 可行性校验,把"GEV 只能 TP2、Gemma4 只能 TP4"从文档结论变成启动前断言。
13. 变更记录
| 日期 | 变更 |
|---|---|
| 2026-10-27 | 由 gemma4-31b-deploy + gev-26b-decide-deploy 合并为本目录;新增 detect_model.py、verify.sh、.gitea;Dockerfile 去掉 ENV/EXPOSE/ENTRYPOINT;新增 TP 可行性校验和 GEMMA4_SKIP_TP_CHECK |