Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 80e59b67d3 | |||
| daaf85f4e5 | |||
| 31994d940f | |||
| d0e260c322 | |||
| 9f523e208a |
@@ -1,3 +1,3 @@
|
||||
FRAMEWORK=vllm
|
||||
FRAMEWORK=vllm_llama_4
|
||||
GPU_TYPE=Iluvatar_bi-150
|
||||
TASK_TYPE=text-generation
|
||||
|
||||
21
Dockerfile
21
Dockerfile
@@ -4,13 +4,13 @@
|
||||
# - Llama-4-Scout-17B-16E-Instruct(BF16,TP8,8 卡)
|
||||
# - Llama-4-Maverick-17B-128E-Instruct-FP8(FP8,TP8+PP2,16 卡)
|
||||
#
|
||||
# 模型权重不打进镜像,运行时挂载模型目录;容器启动后读取 config.json
|
||||
# 自动判断 Scout / Maverick,并选择对应的 TP / PP / 端口。
|
||||
# 镜像只包含运行时依赖和 Llama 4 补丁,不设置启动命令、ENV、WORKDIR 或 EXPOSE;
|
||||
# 模型路径、端口、TP/PP、GPU、挂载等全部由 run.sh / docker-compose.yml 在启动时传入。
|
||||
#
|
||||
# 构建:
|
||||
# docker build -t llama4-bi150:2026-10-26 .
|
||||
#
|
||||
# 运行:
|
||||
# 运行(由 run.sh 传入 entrypoint 和全部参数):
|
||||
# ./run.sh /mnt/disk0/models/LLM-Research/Llama-4-Scout-17B-16E-Instruct
|
||||
# ./run.sh /mnt/disk0/models/LLM-Research/Llama-4-Maverick-17B-128E-Instruct-FP8
|
||||
#
|
||||
@@ -35,16 +35,5 @@ RUN chmod +x /usr/local/bin/llama4-entrypoint.sh \
|
||||
&& chmod +x /opt/llama4/detect_model.py \
|
||||
&& python3 /opt/llama4/apply_patch.py
|
||||
|
||||
ENV MODEL_PATH=/models/llama4 \
|
||||
AUTO_DETECT=1 \
|
||||
HOST=0.0.0.0 \
|
||||
MAX_MODEL_LEN=4096 \
|
||||
MAX_NUM_SEQS=1 \
|
||||
GPU_MEM_UTIL=0.90 \
|
||||
ENFORCE_EAGER=1 \
|
||||
PYTHONUNBUFFERED=1
|
||||
|
||||
WORKDIR /opt/llama4
|
||||
EXPOSE 8091 8092
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/llama4-entrypoint.sh"]
|
||||
# 故意不设置 ENV / WORKDIR / EXPOSE / ENTRYPOINT / CMD:
|
||||
# 镜像保持通用,启动方式由 run.sh 或 docker-compose.yml 决定。
|
||||
|
||||
189
README.md
189
README.md
@@ -10,10 +10,12 @@
|
||||
- 两个模型共用 Llama 4 chunked-attention 补丁;Maverick 额外使用 FP8 量化 / FP8 MoE 补丁
|
||||
- CI 使用 ModelHub 标准 Gitea Actions:推送 `v*` 标签后构建、推送镜像并提交审核
|
||||
|
||||
| 模型 | 精度 | 自动选择 | GPU 数 | 默认端口 |
|
||||
| 模型 | 精度 | 自动选择 | GPU 数 | 容器内端口 |
|
||||
|---|---|---|---|---|
|
||||
| `Llama-4-Scout-17B-16E-Instruct` | BF16 | TP8 / PP1 | 8 | 8091 |
|
||||
| `Llama-4-Maverick-17B-128E-Instruct-FP8` | FP8 | TP8 / PP2 | 16 | 8092 |
|
||||
| `Llama-4-Scout-17B-16E-Instruct` | BF16 | TP8 / PP1 | 8 | 80 |
|
||||
| `Llama-4-Maverick-17B-128E-Instruct-FP8` | FP8 | TP8 / PP2 | 16 | 80 |
|
||||
|
||||
容器内固定监听 80,模型名固定 `llm`,宿主机端口由 `-p` 决定(默认 24396)。
|
||||
|
||||
---
|
||||
|
||||
@@ -74,26 +76,36 @@ MODEL_HOST_DIR=/mnt/disk0/models/LLM-Research/Llama-4-Maverick-17B-128E-Instruct
|
||||
```text
|
||||
==> detected maverick: Llama 4 Maverick 17B-128E Instruct FP8 (TP8+PP2, 16 GPUs)
|
||||
model : /mnt/disk0/models/LLM-Research/Llama-4-Maverick-17B-128E-Instruct-FP8
|
||||
TP/PP : 8/2 (16 GPUs), port=8092, name=llama4-maverick
|
||||
TP/PP : 8/2 (16 GPUs)
|
||||
port : 127.0.0.1:24396 -> container:80
|
||||
name : llm
|
||||
GPUs : 0,1,2,...,15
|
||||
```
|
||||
|
||||
> 镜像本身不设置 `ENTRYPOINT` / `CMD` / `ENV` / `WORKDIR` / `EXPOSE`,
|
||||
> 启动方式、模型路径、端口、TP/PP、GPU 和挂载全部由 `run.sh` 或 `docker-compose.yml` 传入。
|
||||
> 如果直接 `docker run`,需要显式指定:
|
||||
> `--entrypoint /usr/local/bin/llama4-entrypoint.sh --workdir /opt/llama4`。
|
||||
|
||||
### 2.1 自动检测规则
|
||||
|
||||
`detect_model.py` 读取 `config.json`:
|
||||
|
||||
- `model_type` 必须是 `llama4`;
|
||||
- `text_config.num_local_experts == 16` → **Scout**,TP8 / PP1 / 8091;
|
||||
- `text_config.num_local_experts == 128` → **Maverick**,TP8 / PP2 / 8092;
|
||||
- `text_config.num_local_experts == 16` → **Scout**,TP8 / PP1;
|
||||
- `text_config.num_local_experts == 128` → **Maverick**,TP8 / PP2;
|
||||
- 字段缺失时用目录名里的 `scout` / `maverick` 兜底;
|
||||
- 其他情况直接报错,不会用错误的 TP/PP 启动。
|
||||
|
||||
容器启动时 `entrypoint.sh` 也会再检测一次。也就是说:
|
||||
|
||||
- 用 `run.sh` 启动:宿主侧先检测,决定 GPU 数、容器名、健康检查端口;
|
||||
- 直接 `docker run` 挂载模型:容器内也会自动检测,按对应 TP/PP/端口启动;
|
||||
- 显式设置的 `TP` / `PP` / `PORT` / `SERVED_NAME` 会覆盖自动检测;
|
||||
- 设置 `AUTO_DETECT=0` 可关闭容器内检测;未显式设置的项仍使用 `run.sh` 在宿主侧检测出的默认值。
|
||||
- 直接 `docker run` 挂载模型:容器内也会自动检测,按对应 TP/PP 启动;
|
||||
- 显式设置的 `TP` / `PP` / `SERVED_NAME` 会覆盖自动检测;
|
||||
- 设置 `AUTO_DETECT=0` 可关闭容器内检测。
|
||||
|
||||
**端口和模型名不再由检测决定**:容器内端口恒为 80,模型名恒为 `llm`。
|
||||
检测出的 `port` / `served_name` 只作为日志提示(`hint` 行)。
|
||||
|
||||
### 2.2 指定 GPU
|
||||
|
||||
@@ -111,14 +123,29 @@ GPUS=0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 \
|
||||
|
||||
`run.sh` 会检查 GPU 数是否少于 `TP*PP`;少于时直接报错。
|
||||
|
||||
### 2.2.1 指定宿主机端口
|
||||
|
||||
容器内固定 80,宿主机端口默认 **24396**。串行部署时多个模型可以复用同一个端口:
|
||||
|
||||
```bash
|
||||
# 默认用 24396
|
||||
./run.sh /mnt/disk0/models/LLM-Research/Llama-4-Scout-17B-16E-Instruct
|
||||
|
||||
# 改端口
|
||||
HOST_PORT=24397 ./run.sh /mnt/disk0/models/LLM-Research/Llama-4-Scout-17B-16E-Instruct
|
||||
```
|
||||
|
||||
**并行跑多个模型时必须给不同的 `HOST_PORT`**,否则第二个容器 `bind: address already in use`。
|
||||
不过 Maverick 占满 16 卡,实际和 Scout 无法并存。
|
||||
|
||||
### 2.3 额外挂载 / 额外 vLLM 参数
|
||||
|
||||
```bash
|
||||
EXTRA_DOCKER_ARGS="-v /mnt/disk1:/data:ro" ./run.sh /path/to/model
|
||||
EXTRA_ARGS="--max-model-len 8192" ./run.sh /path/to/model
|
||||
|
||||
# 已有镜像,不重新构建
|
||||
BUILD=0 IMAGE=registry.example.com/llama4-bi150:v1.0.0 ./run.sh /path/to/model
|
||||
# 已有镜像,不重新构建(把 <ci-image> 换成第 5.1 节的实际地址)
|
||||
BUILD=0 IMAGE=<ci-image> ./run.sh /path/to/model
|
||||
|
||||
# 只打印 docker run 命令
|
||||
DRY_RUN=1 ./run.sh /path/to/model
|
||||
@@ -139,7 +166,7 @@ MODEL_HOST_DIR=/mnt/disk0/models/LLM-Research/Llama-4-Maverick-17B-128E-Instruct
|
||||
|
||||
# 指定 GPU / 端口 / 额外参数
|
||||
MODEL_HOST_DIR=/mnt/disk0/models/... \
|
||||
GPUS=8,9,10,11,12,13,14,15 PORT=8095 \
|
||||
GPUS=8,9,10,11,12,13,14,15 HOST_PORT=24397 \
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
@@ -153,8 +180,9 @@ GPUS=8,9,10,11,12,13,14,15 PORT=8095 \
|
||||
| `MODEL_MOUNT` | `/models/llama4` | 容器内挂载点 |
|
||||
| `IMAGE` | `llama4-bi150:2026-10-26` | 镜像名 |
|
||||
| `NAME` | `llama4-<kind>` | 容器名 |
|
||||
| `PORT` | Scout 8091 / Maverick 8092 | 宿主机端口 |
|
||||
| `SERVED_NAME` | Scout `llama4-scout` / Maverick `llama4-maverick` | OpenAI API 模型名 |
|
||||
| `HOST_PORT` | `24396` | 宿主机端口(容器内固定 80) |
|
||||
| `PORT` | `24396` | `HOST_PORT` 的兼容别名,推荐用 `HOST_PORT` |
|
||||
| `SERVED_NAME` | `llm` | OpenAI API 模型名,固定 |
|
||||
| `TP` | Scout 8 / Maverick 8 | 张量并行 |
|
||||
| `PP` | Scout 1 / Maverick 2 | 流水线并行 |
|
||||
| `GPUS` | 前 `TP*PP` 张卡 | `CUDA_VISIBLE_DEVICES` |
|
||||
@@ -168,7 +196,7 @@ GPUS=8,9,10,11,12,13,14,15 PORT=8095 \
|
||||
| `BUILD` | `1` | `0` 表示跳过构建 |
|
||||
| `DRY_RUN` | `0` | `1` 只打印命令 |
|
||||
|
||||
`TP` / `PP` / `PORT` / `SERVED_NAME` 显式设置时优先于自动检测。
|
||||
`TP` / `PP` / `SERVED_NAME` 显式设置时优先于自动检测。
|
||||
|
||||
---
|
||||
|
||||
@@ -202,7 +230,7 @@ GPUS=8,9,10,11,12,13,14,15 PORT=8095 \
|
||||
./verify.sh --container llama4-scout --with-reasoning
|
||||
|
||||
# 直接指定服务地址
|
||||
./verify.sh --base-url http://127.0.0.1:8092 --served-name llama4-maverick
|
||||
./verify.sh --base-url http://127.0.0.1:24396 --served-name llm
|
||||
```
|
||||
|
||||
主要参数:
|
||||
@@ -211,7 +239,7 @@ GPUS=8,9,10,11,12,13,14,15 PORT=8095 \
|
||||
|---|---|
|
||||
| `--model-dir PATH` | 模型目录,用于自动判断 Scout/Maverick |
|
||||
| `--container NAME` | 容器名;默认自动查找 |
|
||||
| `--port PORT` | 服务端口;默认从模型/容器推断 |
|
||||
| `--port PORT` | 宿主机端口;默认从容器端口映射推断,兜底 24396 |
|
||||
| `--served-name NAME` | API 模型名;默认从模型/容器推断 |
|
||||
| `--base-url URL` | 直接指定服务地址 |
|
||||
| `--timeout SEC` | 等待/请求超时,默认 180 |
|
||||
@@ -221,7 +249,7 @@ GPUS=8,9,10,11,12,13,14,15 PORT=8095 \
|
||||
|
||||
脚本只依赖 `bash`、`curl`、`python3`;不需要 `jq`。返回码为 0 表示全部通过。
|
||||
|
||||
在 `ugly_hopper` 内运行、没有 `docker` CLI 时,`verify.sh` 会自动探测本机 `8091` / `8092` 的 `/health`,并据此选择 Scout 或 Maverick;也可以显式传 `--model-dir` 或 `--port`。
|
||||
在 `ugly_hopper` 内运行、没有 `docker` CLI 时,`verify.sh` 会先探测 `24396`,再依次探测旧的 `8091` / `8092`;也可以用 `--model-dir` 或 `--port` 显式指定。
|
||||
|
||||
也可以直接查看模型检测结果:
|
||||
|
||||
@@ -243,22 +271,128 @@ python3 detect_model.py /mnt/disk0/models/LLM-Research/Llama-4-Maverick-17B-128E
|
||||
触发方式:
|
||||
|
||||
```bash
|
||||
git tag v1.0.0
|
||||
git push origin v1.0.0
|
||||
git tag v0.1.3
|
||||
git push origin v0.1.3
|
||||
```
|
||||
|
||||
已发布:`v0.1.0`(取消)、`v0.1.1`(取消)、`v0.1.2`(run #3 成功)。
|
||||
|
||||
CI 会:
|
||||
|
||||
1. 在 `amd64-ubuntu-24.04` runner 上 clone 仓库;
|
||||
2. 校验 `task_info.env`;
|
||||
3. `docker build`;
|
||||
4. 推送 `${DOCKER_REGISTRY}/${DOCKER_USERNAME}/llama-4-bi150:v1.0.0`;
|
||||
4. 推送 `${DOCKER_REGISTRY}/${DOCKER_USERNAME}/llama-4-bi150:<tag>`;
|
||||
5. 调 ModelHub `image-verify` 接口提交审核。
|
||||
|
||||
### 5.1 已发布的 CI 镜像
|
||||
|
||||
| 标签 | Digest | 压缩大小 | 推送时间 |
|
||||
|---|---|---:|---|
|
||||
| `v0.1.2` | `sha256:42dc1d3fe60ca9d311b4ceb3dc9d0d4eef3ff5cdad560e63635fe75c6121602f` | 10.39 GiB | 2026-10-26 12:04 UTC |
|
||||
|
||||
```text
|
||||
harbor.4pd.io/modelhubxc/xiejiahao/llama-4-bi150:v0.1.2
|
||||
```
|
||||
|
||||
拉取(需要 Harbor 凭据 `DOCKER_USERNAME` / `DOCKER_PASSWORD`):
|
||||
|
||||
```bash
|
||||
docker login harbor.4pd.io -u modelhubxc
|
||||
docker pull harbor.4pd.io/modelhubxc/xiejiahao/llama-4-bi150:v0.1.2
|
||||
```
|
||||
|
||||
### 5.2 用 CI 镜像启动服务
|
||||
|
||||
镜像不声明 `ENTRYPOINT`,`--entrypoint` 必须显式给。以下命令可直接粘贴。
|
||||
|
||||
**Llama-4-Scout-17B-16E(TP8/PP1,8 卡,宿主 24396 → 容器 80)**
|
||||
|
||||
```bash
|
||||
docker run -d --name llama4-scout \
|
||||
--entrypoint /usr/local/bin/llama4-entrypoint.sh \
|
||||
--workdir /opt/llama4 \
|
||||
--privileged --pid host --ipc host \
|
||||
-v /dev:/dev \
|
||||
-v /mnt/disk0/models/LLM-Research/Llama-4-Scout-17B-16E-Instruct:/models/llama4:ro \
|
||||
--ulimit memlock=-1 --ulimit stack=67108864 \
|
||||
-p 24396:80 \
|
||||
-e CUDA_VISIBLE_DEVICES=8,9,10,11,12,13,14,15 \
|
||||
-e MODEL_PATH=/models/llama4 \
|
||||
-e PORT=80 \
|
||||
-e SERVED_NAME=llm \
|
||||
-e AUTO_DETECT=1 \
|
||||
-e PYTHONUNBUFFERED=1 \
|
||||
--restart unless-stopped \
|
||||
harbor.4pd.io/modelhubxc/xiejiahao/llama-4-bi150:v0.1.2
|
||||
```
|
||||
|
||||
**Llama-4-Maverick-17B-128E-Instruct-FP8(TP8/PP2,16 卡,宿主 24396 → 容器 80)**
|
||||
|
||||
```bash
|
||||
docker run -d --name llama4-maverick \
|
||||
--entrypoint /usr/local/bin/llama4-entrypoint.sh \
|
||||
--workdir /opt/llama4 \
|
||||
--privileged --pid host --ipc host \
|
||||
-v /dev:/dev \
|
||||
-v /mnt/disk0/models/LLM-Research/Llama-4-Maverick-17B-128E-Instruct-FP8:/models/llama4:ro \
|
||||
--ulimit memlock=-1 --ulimit stack=67108864 \
|
||||
-p 24396:80 \
|
||||
-e CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 \
|
||||
-e MODEL_PATH=/models/llama4 \
|
||||
-e PORT=80 \
|
||||
-e SERVED_NAME=llm \
|
||||
-e AUTO_DETECT=1 \
|
||||
-e PYTHONUNBUFFERED=1 \
|
||||
--restart unless-stopped \
|
||||
harbor.4pd.io/modelhubxc/xiejiahao/llama-4-bi150:v0.1.2
|
||||
```
|
||||
|
||||
两个变体共用挂载点 `/models/llama4`,靠 `config.json` 的 `num_local_experts` 区分;TP/PP、端口、served name 不用传,入口脚本会自动判定(见 2.1 节)。
|
||||
|
||||
Maverick 占用全部 16 张卡,与其它模型互斥;Scout 只占 8 张,可以和其它小模型共存,此时需改 `-e CUDA_VISIBLE_DEVICES` 避免重叠。
|
||||
|
||||
**注意**:两个变体都映射到宿主 `24396`,串行部署没问题;并行时必须改 `-p` 左侧端口。
|
||||
`-e SERVED_NAME=llm` 让 `/v1/models` 返回 `llm`,客户端不用为换模型改 `model` 字段。
|
||||
|
||||
### 5.2.1 覆盖参数:三种写法,优先级从高到低
|
||||
|
||||
| 优先级 | 写法 | 例子 |
|
||||
|---|---|---|
|
||||
| 1 | 在镜像后面直接跟 vllm 参数 | `... llama-4-bi150:v0.1.2 --served-model-name mymodel` |
|
||||
| 2 | `-e EXTRA_ARGS="..."` | `-e EXTRA_ARGS="--max-model-len 8192"` |
|
||||
| 3 | 同名 `-e` | `-e MAX_MODEL_LEN=8192 -e SERVED_NAME=mymodel` |
|
||||
| 4 | 默认值 / config.json 检测 | 什么都不传 |
|
||||
|
||||
三种写法都能生效,不会重复传参。覆盖 `--port` / `--served-model-name` 时会打印 WARN,
|
||||
因为宿主机 `-p` 右侧端口和客户端 `model` 字段也要同步改。
|
||||
|
||||
```bash
|
||||
# 镜像后直接跟参数
|
||||
docker run -d --name llama4-scout \
|
||||
--entrypoint /usr/local/bin/llama4-entrypoint.sh --workdir /opt/llama4 \
|
||||
--privileged --pid host --ipc host -v /dev:/dev \
|
||||
-v /mnt/disk0/models/LLM-Research/Llama-4-Scout-17B-16E-Instruct:/models/llama4:ro \
|
||||
--ulimit memlock=-1 --ulimit stack=67108864 \
|
||||
-p 24396:80 \
|
||||
-e CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 -e MODEL_PATH=/models/llama4 -e AUTO_DETECT=1 \
|
||||
harbor.4pd.io/modelhubxc/xiejiahao/llama-4-bi150:v0.1.2 \
|
||||
--served-model-name mymodel --max-model-len 8192
|
||||
```
|
||||
|
||||
### 5.3 用 run.sh 启动 CI 镜像
|
||||
|
||||
```bash
|
||||
BUILD=0 IMAGE=harbor.4pd.io/modelhubxc/xiejiahao/llama-4-bi150:v0.1.2 \
|
||||
./run.sh /mnt/disk0/models/LLM-Research/Llama-4-Scout-17B-16E-Instruct
|
||||
|
||||
./verify.sh --model-dir /mnt/disk0/models/LLM-Research/Llama-4-Scout-17B-16E-Instruct
|
||||
```
|
||||
|
||||
`task_info.env`:
|
||||
|
||||
```bash
|
||||
FRAMEWORK=vllm
|
||||
FRAMEWORK=vllm_llama_4
|
||||
GPU_TYPE=Iluvatar_bi-150
|
||||
TASK_TYPE=text-generation
|
||||
```
|
||||
@@ -276,7 +410,7 @@ FIXED_TOKEN
|
||||
|
||||
注意事项:
|
||||
|
||||
- 当前仓库默认没有打 tag,所以推送代码不会自动构建;需要手动推送 `v*` 标签才会触发。
|
||||
- 推送代码不会自动构建;需要手动推送 `v*` 标签才会触发。
|
||||
- Runner 必须能拉取 `registry.iluvatar.com.cn:10443` 的基础镜像;如果该仓库需要登录,请在 runner 上预登录,或自行给 workflow 增加基础镜像 registry 的 login step。
|
||||
- CI 只负责构建和推送,不负责 GPU / 模型挂载;GPU 和挂载通过 `run.sh` 或 `docker compose` 设置。
|
||||
- CI 构建机不需要 GPU,但需要足够的磁盘空间。
|
||||
@@ -292,3 +426,12 @@ FIXED_TOKEN
|
||||
- `--privileged --pid host -v /dev:/dev` 是 Iluvatar 运行时要求;只给 `/dev/iluvatar*` 可能报 `cudaGetDeviceCount() ... Error 1001`。
|
||||
- 模型目录只读挂载,容器不会修改宿主机模型文件。
|
||||
- 统一镜像首次使用时,建议 Scout 和 Maverick 都各跑一次 `verify.sh`;尤其是 Scout,因为镜像里也包含 FP8 MoE 补丁,需要确认共享的 `fused_moe.py` 修改没有影响 BF16 路径。
|
||||
|
||||
---
|
||||
|
||||
## 7. 变更记录
|
||||
|
||||
| 日期 | 变更 |
|
||||
|---|---|
|
||||
| 2026-10-26 | CI `v0.1.2` 发布成功(digest `42dc1d3f…`,10.39 GiB) |
|
||||
| 2026-10-28 | 补录 CI 镜像 digest 与 Scout / Maverick 的完整 `docker run` 命令(§5.1–5.3);修正 README 中的示例镜像地址和标签 |
|
||||
|
||||
@@ -6,8 +6,17 @@ services:
|
||||
image: llama4-bi150:${TAG:-2026-10-26}
|
||||
container_name: llama4
|
||||
|
||||
# vLLM 多卡 NCCL / 端口直通
|
||||
network_mode: host
|
||||
# 镜像本身不设置启动命令,启动方式全部由这里决定。
|
||||
entrypoint: ["/usr/local/bin/llama4-entrypoint.sh"]
|
||||
working_dir: /opt/llama4
|
||||
|
||||
# 容器内固定监听 80,宿主机端口由 HOST_PORT 决定(默认 24396)。
|
||||
expose:
|
||||
- "80"
|
||||
ports:
|
||||
- "${HOST_PORT:-24396}:80"
|
||||
|
||||
# bridge 网络 + 端口映射;ipc host 供 vLLM/PyTorch 多卡共享内存。
|
||||
ipc: host
|
||||
|
||||
# Iluvatar 运行时要求
|
||||
@@ -21,22 +30,25 @@ services:
|
||||
environment:
|
||||
MODEL_PATH: /models/llama4
|
||||
AUTO_DETECT: "${AUTO_DETECT:-1}"
|
||||
HOST: 0.0.0.0
|
||||
HOST: "${HOST:-0.0.0.0}"
|
||||
|
||||
# 默认暴露全部 16 张卡;entrypoint 会根据模型选择实际 TP*PP。
|
||||
CUDA_VISIBLE_DEVICES: "${GPUS:-0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}"
|
||||
|
||||
# 容器内端口固定 80,模型名固定 llm(AGENTS.md 5.2 / 5.5)。
|
||||
PORT: "80"
|
||||
SERVED_NAME: "${SERVED_NAME:-llm}"
|
||||
|
||||
# 留空时由 entrypoint 根据 config.json 自动判断。
|
||||
# 需要手动覆盖时直接设置同名环境变量即可。
|
||||
TP: "${TP:-}"
|
||||
PP: "${PP:-}"
|
||||
PORT: "${PORT:-}"
|
||||
SERVED_NAME: "${SERVED_NAME:-}"
|
||||
|
||||
MAX_MODEL_LEN: "${MAX_MODEL_LEN:-4096}"
|
||||
MAX_NUM_SEQS: "${MAX_NUM_SEQS:-1}"
|
||||
GPU_MEM_UTIL: "${GPU_MEM_UTIL:-0.90}"
|
||||
ENFORCE_EAGER: "${ENFORCE_EAGER:-1}"
|
||||
PYTHONUNBUFFERED: "1"
|
||||
EXTRA_ARGS: "${EXTRA_ARGS:-}"
|
||||
|
||||
ulimits:
|
||||
|
||||
116
entrypoint.sh
116
entrypoint.sh
@@ -34,34 +34,28 @@ if [[ "${AUTO_DETECT}" == "1" ]]; then
|
||||
MODEL_KIND="${MODEL_KIND:-${DETECTED_MODEL_KIND}}"
|
||||
TP="${TP:-${DETECTED_TP}}"
|
||||
PP="${PP:-${DETECTED_PP}}"
|
||||
PORT="${PORT:-${DETECTED_PORT}}"
|
||||
SERVED_NAME="${SERVED_NAME:-${DETECTED_SERVED_NAME}}"
|
||||
MODEL_DESCRIPTION="${DETECTED_DESCRIPTION:-}"
|
||||
# 检测出的 port / served_name 仅用于日志提示,不再决定容器内监听端口和模型名:
|
||||
# 容器内端口固定 80,模型名固定 llm(见 AGENTS.md 5.2 / 5.5)。
|
||||
DETECTED_HOST_PORT_HINT="${DETECTED_PORT:-}"
|
||||
DETECTED_NAME_HINT="${DETECTED_SERVED_NAME:-}"
|
||||
else
|
||||
MODEL_KIND="${MODEL_KIND:-unknown}"
|
||||
DETECTED_HOST_PORT_HINT=""
|
||||
DETECTED_NAME_HINT=""
|
||||
fi
|
||||
|
||||
HOST="${HOST:-0.0.0.0}"
|
||||
TP="${TP:-8}"
|
||||
PP="${PP:-1}"
|
||||
PORT="${PORT:-8091}"
|
||||
SERVED_NAME="${SERVED_NAME:-llama4}"
|
||||
PORT="${PORT:-80}"
|
||||
SERVED_NAME="${SERVED_NAME:-llm}"
|
||||
MAX_MODEL_LEN="${MAX_MODEL_LEN:-4096}"
|
||||
MAX_NUM_SEQS="${MAX_NUM_SEQS:-1}"
|
||||
GPU_MEM_UTIL="${GPU_MEM_UTIL:-0.90}"
|
||||
ENFORCE_EAGER="${ENFORCE_EAGER:-1}"
|
||||
EXTRA_ARGS="${EXTRA_ARGS:-}"
|
||||
|
||||
PP_ARGS=()
|
||||
if (( PP > 1 )); then
|
||||
PP_ARGS=(--pipeline-parallel-size "${PP}")
|
||||
fi
|
||||
|
||||
EAGER_ARGS=()
|
||||
if [[ "${ENFORCE_EAGER}" == "1" ]]; then
|
||||
EAGER_ARGS=(--enforce-eager)
|
||||
fi
|
||||
|
||||
echo "============================================================"
|
||||
echo "Llama 4 (BI-V150 unified image)"
|
||||
echo " model kind : ${MODEL_KIND}"
|
||||
@@ -78,17 +72,91 @@ echo " extra : ${EXTRA_ARGS}"
|
||||
if [[ -n "${MODEL_DESCRIPTION}" ]]; then
|
||||
echo " detected : ${MODEL_DESCRIPTION}"
|
||||
fi
|
||||
if [[ -n "${DETECTED_HOST_PORT_HINT}" ]]; then
|
||||
echo " hint : 变体默认宿主端口 ${DETECTED_HOST_PORT_HINT},模型名曾用 ${DETECTED_NAME_HINT}"
|
||||
echo " (容器内固定 80 / served name 固定 llm,实际端口由 docker run -p 决定)"
|
||||
fi
|
||||
echo "============================================================"
|
||||
|
||||
# ---------------------------------------------------------------- 用户参数
|
||||
# 优先级(高 -> 低):
|
||||
# 1. docker run <image> 之后追加的 vllm 参数("$@")
|
||||
# 2. -e EXTRA_ARGS="..."
|
||||
# 3. -e 显式设置(SERVED_NAME / PORT / TP / PP / MAX_MODEL_LEN ...)
|
||||
# 4. config.json 自动检测
|
||||
# 用户已经给了的 flag,本脚本不再重复传,避免 vllm 报 duplicate key。
|
||||
# 末尾参数的 flag 优先于 EXTRA_ARGS 里的同名 flag:把后者摘掉,避免 vllm 报 duplicate key。
|
||||
declare -A _CLI_FLAGS=()
|
||||
if (( $# > 0 )); then
|
||||
for _t in "$@"; do
|
||||
[[ "${_t}" == --* ]] && _CLI_FLAGS["${_t%%=*}"]=1
|
||||
done
|
||||
fi
|
||||
|
||||
EXTRA_FILTERED=()
|
||||
if [[ -n "${EXTRA_ARGS}" ]]; then
|
||||
read -r -a _extra_tokens <<< "${EXTRA_ARGS}"
|
||||
_skip=0
|
||||
for _t in "${_extra_tokens[@]}"; do
|
||||
if (( _skip > 0 )); then
|
||||
_skip=0
|
||||
continue
|
||||
fi
|
||||
if [[ "${_t}" == --* ]]; then
|
||||
_name="${_t%%=*}"
|
||||
if [[ -n "${_CLI_FLAGS[${_name}]:-}" ]]; then
|
||||
# 该 flag 在命令行里也给了,跳过它(以及紧跟的取值)
|
||||
[[ "${_t}" != *=* ]] && _skip=1
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
EXTRA_FILTERED+=("${_t}")
|
||||
done
|
||||
fi
|
||||
|
||||
USER_TOKENS=("${EXTRA_FILTERED[@]}")
|
||||
if (( $# > 0 )); then
|
||||
USER_TOKENS+=("$@")
|
||||
fi
|
||||
|
||||
user_has() {
|
||||
local want="$1" tok
|
||||
(( ${#USER_TOKENS[@]} == 0 )) && return 1
|
||||
for tok in "${USER_TOKENS[@]}"; do
|
||||
if [[ "${tok}" == "${want}" || "${tok}" == "${want}="* ]]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
if (( $# > 0 )); then
|
||||
echo "用户追加参数 : $*"
|
||||
echo "(优先级最高,覆盖 -e 与自动检测)"
|
||||
user_has --port && echo " WARN: --port 会改变容器内监听端口,宿主机 -p 的右侧端口也要同步改" >&2
|
||||
user_has --served-model-name && echo " WARN: --served-model-name 已覆盖默认的 llm,客户端 model 字段需同步修改" >&2
|
||||
echo "------------------------------------------------------------"
|
||||
fi
|
||||
|
||||
SERVE_ARGS=()
|
||||
user_has --host || SERVE_ARGS+=(--host "${HOST}")
|
||||
user_has --port || SERVE_ARGS+=(--port "${PORT}")
|
||||
user_has --served-model-name || SERVE_ARGS+=(--served-model-name "${SERVED_NAME}")
|
||||
user_has --tensor-parallel-size || SERVE_ARGS+=(--tensor-parallel-size "${TP}")
|
||||
if (( PP > 1 )) && ! user_has --pipeline-parallel-size; then
|
||||
SERVE_ARGS+=(--pipeline-parallel-size "${PP}")
|
||||
fi
|
||||
user_has --max-model-len || SERVE_ARGS+=(--max-model-len "${MAX_MODEL_LEN}")
|
||||
user_has --max-num-seqs || SERVE_ARGS+=(--max-num-seqs "${MAX_NUM_SEQS}")
|
||||
user_has --gpu-memory-utilization || SERVE_ARGS+=(--gpu-memory-utilization "${GPU_MEM_UTIL}")
|
||||
if ! user_has --enforce-eager && ! user_has --no-enforce-eager; then
|
||||
if [[ "${ENFORCE_EAGER}" == "1" ]]; then
|
||||
SERVE_ARGS+=(--enforce-eager)
|
||||
fi
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
exec "${VLLM_BIN}" serve "${MODEL_PATH}" \
|
||||
--host "${HOST}" \
|
||||
--port "${PORT}" \
|
||||
--served-model-name "${SERVED_NAME}" \
|
||||
--tensor-parallel-size "${TP}" \
|
||||
"${PP_ARGS[@]}" \
|
||||
--max-model-len "${MAX_MODEL_LEN}" \
|
||||
--max-num-seqs "${MAX_NUM_SEQS}" \
|
||||
--gpu-memory-utilization "${GPU_MEM_UTIL}" \
|
||||
"${EAGER_ARGS[@]}" \
|
||||
${EXTRA_ARGS}
|
||||
"${SERVE_ARGS[@]}" \
|
||||
"${EXTRA_FILTERED[@]}" \
|
||||
"$@"
|
||||
|
||||
35
run.sh
35
run.sh
@@ -16,7 +16,8 @@
|
||||
# GPUS=8,9,10,11,12,13,14,15 ./run.sh /path/to/scout
|
||||
# EXTRA_DOCKER_ARGS="-v /mnt/disk1:/data:ro" ./run.sh /path/to/model
|
||||
# EXTRA_ARGS="--max-model-len 8192" ./run.sh /path/to/model
|
||||
# TP=8 PP=1 PORT=8095 SERVED_NAME=my-llama4 ./run.sh /path/to/model
|
||||
# HOST_PORT=24397 ./run.sh /path/to/model # 换宿主机端口(默认 24396)
|
||||
# TP=8 PP=1 SERVED_NAME=llm ./run.sh /path/to/model
|
||||
# BUILD=0 ./run.sh /path/to/model # 使用已有镜像
|
||||
# DRY_RUN=1 ./run.sh /path/to/model # 只打印 docker run 命令
|
||||
#
|
||||
@@ -35,8 +36,9 @@ usage() {
|
||||
模型类型由 MODEL_DIR/config.json 自动判断,不需要 scout/maverick 参数。
|
||||
|
||||
环境变量覆盖:
|
||||
IMAGE NAME MODEL_HOST_DIR MODEL_MOUNT PORT SERVED_NAME
|
||||
TP PP GPUS MAX_MODEL_LEN MAX_NUM_SEQS GPU_MEM_UTIL
|
||||
IMAGE NAME MODEL_HOST_DIR MODEL_MOUNT SERVED_NAME
|
||||
TP PP GPUS HOST_PORT(宿主机端口,默认 24396;容器内固定 80)
|
||||
MAX_MODEL_LEN MAX_NUM_SEQS GPU_MEM_UTIL
|
||||
ENFORCE_EAGER AUTO_DETECT EXTRA_ARGS EXTRA_DOCKER_ARGS
|
||||
BUILD=0/1 DRY_RUN=0/1
|
||||
EOF
|
||||
@@ -77,9 +79,13 @@ eval "${DETECT_OUTPUT}"
|
||||
MODEL_KIND="${DETECTED_MODEL_KIND}"
|
||||
IMAGE="${IMAGE:-llama4-bi150:2026-10-26}"
|
||||
NAME="${NAME:-llama4-${MODEL_KIND}}"
|
||||
PORT="${PORT:-${DETECTED_PORT}}"
|
||||
HOST="${HOST:-0.0.0.0}"
|
||||
# 容器内端口固定 80;宿主机端口是 HOST_PORT,默认 24396(串行部署复用同一端口)。
|
||||
# PORT 保留为兼容别名,若显式给 PORT 则视作 HOST_PORT。
|
||||
CONTAINER_PORT=80
|
||||
HOST_PORT="${HOST_PORT:-${PORT:-24396}}"
|
||||
MODEL_MOUNT="${MODEL_MOUNT:-/models/llama4}"
|
||||
SERVED_NAME="${SERVED_NAME:-${DETECTED_SERVED_NAME}}"
|
||||
SERVED_NAME="${SERVED_NAME:-llm}"
|
||||
TP="${TP:-${DETECTED_TP}}"
|
||||
PP="${PP:-${DETECTED_PP}}"
|
||||
MAX_MODEL_LEN="${MAX_MODEL_LEN:-4096}"
|
||||
@@ -122,10 +128,13 @@ fi
|
||||
|
||||
DOCKER_RUN=(
|
||||
docker run -d --name "${NAME}"
|
||||
--entrypoint /usr/local/bin/llama4-entrypoint.sh
|
||||
--workdir /opt/llama4
|
||||
--expose "${CONTAINER_PORT}"
|
||||
--privileged
|
||||
--pid host
|
||||
--network host
|
||||
--ipc host
|
||||
-p "${HOST_PORT}:${CONTAINER_PORT}"
|
||||
-v /dev:/dev
|
||||
-v "${MODEL_HOST_DIR}:${MODEL_MOUNT}:ro"
|
||||
--ulimit memlock=-1
|
||||
@@ -134,14 +143,16 @@ DOCKER_RUN=(
|
||||
-e MODEL_PATH="${MODEL_MOUNT}"
|
||||
-e AUTO_DETECT="${AUTO_DETECT}"
|
||||
-e MODEL_KIND="${MODEL_KIND}"
|
||||
-e HOST="${HOST}"
|
||||
-e SERVED_NAME="${SERVED_NAME}"
|
||||
-e PORT="${PORT}"
|
||||
-e PORT="${CONTAINER_PORT}"
|
||||
-e TP="${TP}"
|
||||
-e PP="${PP}"
|
||||
-e MAX_MODEL_LEN="${MAX_MODEL_LEN}"
|
||||
-e MAX_NUM_SEQS="${MAX_NUM_SEQS}"
|
||||
-e GPU_MEM_UTIL="${GPU_MEM_UTIL}"
|
||||
-e ENFORCE_EAGER="${ENFORCE_EAGER}"
|
||||
-e PYTHONUNBUFFERED=1
|
||||
-e EXTRA_ARGS="${EXTRA_ARGS}"
|
||||
"${DOCKER_EXTRA[@]}"
|
||||
--restart unless-stopped
|
||||
@@ -150,7 +161,9 @@ DOCKER_RUN=(
|
||||
|
||||
echo "==> detected ${MODEL_KIND}: ${DETECTED_DESCRIPTION}"
|
||||
echo " model : ${MODEL_HOST_DIR}"
|
||||
echo " TP/PP : ${TP}/${PP} (${GPU_COUNT} GPUs), port=${PORT}, name=${SERVED_NAME}"
|
||||
echo " TP/PP : ${TP}/${PP} (${GPU_COUNT} GPUs)"
|
||||
echo " port : 127.0.0.1:${HOST_PORT} -> container:${CONTAINER_PORT}"
|
||||
echo " name : ${SERVED_NAME}"
|
||||
echo " GPUs : ${GPUS}"
|
||||
|
||||
if [[ "${DRY_RUN}" == "1" ]]; then
|
||||
@@ -172,10 +185,10 @@ docker rm -f "${NAME}" 2>/dev/null || true
|
||||
echo "==> run ${MODEL_KIND}"
|
||||
"${DOCKER_RUN[@]}"
|
||||
|
||||
echo "==> wait for http://127.0.0.1:${PORT}/health"
|
||||
echo "==> wait for http://127.0.0.1:${HOST_PORT}/health"
|
||||
for _ in $(seq 1 120); do
|
||||
if curl -sf "http://127.0.0.1:${PORT}/health" >/dev/null 2>&1; then
|
||||
echo "ready: http://127.0.0.1:${PORT}/v1/models"
|
||||
if curl -sf "http://127.0.0.1:${HOST_PORT}/health" >/dev/null 2>&1; then
|
||||
echo "ready: http://127.0.0.1:${HOST_PORT}/v1/models"
|
||||
exit 0
|
||||
fi
|
||||
sleep 5
|
||||
|
||||
40
verify.sh
40
verify.sh
@@ -21,7 +21,7 @@ cd "$(dirname "$0")"
|
||||
|
||||
MODEL_DIR="${MODEL_HOST_DIR:-}"
|
||||
CONTAINER="${CONTAINER:-}"
|
||||
PORT="${PORT:-}"
|
||||
PORT="${PORT:-${HOST_PORT:-}}"
|
||||
SERVED_NAME="${SERVED_NAME:-}"
|
||||
BASE_URL="${BASE_URL:-}"
|
||||
TIMEOUT="${TIMEOUT:-180}"
|
||||
@@ -101,7 +101,21 @@ for m in mounts:
|
||||
break
|
||||
' || true)"
|
||||
fi
|
||||
[[ -z "${PORT}" ]] && PORT="$(container_env "${CONTAINER}" PORT)"
|
||||
# 宿主端口来自端口映射(-p),不是容器内的 PORT(现在恒为 80)
|
||||
if [[ -z "${PORT}" ]]; then
|
||||
PORT="$(docker inspect "${CONTAINER}" --format '{{json .HostConfig.PortBindings}}' 2>/dev/null \
|
||||
| python3 -c '
|
||||
import json, sys
|
||||
try:
|
||||
binds = json.load(sys.stdin) or {}
|
||||
except Exception:
|
||||
sys.exit(0)
|
||||
for container_port, mapping in binds.items():
|
||||
if mapping:
|
||||
print(mapping[0].get("HostPort", ""))
|
||||
break
|
||||
' || true)"
|
||||
fi
|
||||
[[ -z "${SERVED_NAME}" ]] && SERVED_NAME="$(container_env "${CONTAINER}" SERVED_NAME)"
|
||||
fi
|
||||
|
||||
@@ -111,7 +125,7 @@ MAVERICK_DEFAULT="/mnt/disk0/models/LLM-Research/Llama-4-Maverick-17B-128E-Instr
|
||||
|
||||
# 没有 docker 时(例如在 ugly_hopper 内运行),按端口探测正在运行的服务。
|
||||
if [[ -z "${CONTAINER}" && -z "${PORT}" && -z "${MODEL_DIR}" && -z "${BASE_URL}" ]]; then
|
||||
for candidate_port in 8091 8092; do
|
||||
for candidate_port in 24396 8091 8092; do
|
||||
if curl -fsS --max-time 2 "http://127.0.0.1:${candidate_port}/health" >/dev/null 2>&1; then
|
||||
PORT="${candidate_port}"
|
||||
break
|
||||
@@ -119,8 +133,9 @@ if [[ -z "${CONTAINER}" && -z "${PORT}" && -z "${MODEL_DIR}" && -z "${BASE_URL}"
|
||||
done
|
||||
fi
|
||||
|
||||
# 从 served name 反推模型目录(方便 --base-url + --served-name 的用法)。
|
||||
if [[ -z "${MODEL_DIR}" && -n "${SERVED_NAME}" ]]; then
|
||||
# 模型名现在恒为 llm(AGENTS.md 5.5),不能再靠它反推变体。
|
||||
# 兼容旧用法:如果显式传了带变体的 served name,仍然尝试反推目录。
|
||||
if [[ -z "${MODEL_DIR}" && -n "${SERVED_NAME}" && "${SERVED_NAME}" != "llm" ]]; then
|
||||
case "${SERVED_NAME}" in
|
||||
*maverick*)
|
||||
if [[ -f "${MAVERICK_DEFAULT}/config.json" ]]; then
|
||||
@@ -135,10 +150,9 @@ if [[ -z "${MODEL_DIR}" && -n "${SERVED_NAME}" ]]; then
|
||||
esac
|
||||
fi
|
||||
|
||||
# 端口也不再区分变体(宿主端口默认统一 24396),只按目录存在性取默认值。
|
||||
if [[ -z "${MODEL_DIR}" && -z "${BASE_URL}" ]]; then
|
||||
if [[ "${PORT}" == "8092" && -f "${MAVERICK_DEFAULT}/config.json" ]]; then
|
||||
MODEL_DIR="${MAVERICK_DEFAULT}"
|
||||
elif [[ -f "${SCOUT_DEFAULT}/config.json" ]]; then
|
||||
if [[ -f "${SCOUT_DEFAULT}/config.json" ]]; then
|
||||
MODEL_DIR="${SCOUT_DEFAULT}"
|
||||
elif [[ -f "${MAVERICK_DEFAULT}/config.json" ]]; then
|
||||
MODEL_DIR="${MAVERICK_DEFAULT}"
|
||||
@@ -163,13 +177,13 @@ fi
|
||||
if [[ -n "${DETECT_JSON}" ]]; then
|
||||
KIND="$(python3 -c 'import json,sys; print(json.load(sys.stdin)["kind"])' <<< "${DETECT_JSON}")"
|
||||
DETECTED_PORT="$(python3 -c 'import json,sys; print(json.load(sys.stdin)["port"])' <<< "${DETECT_JSON}")"
|
||||
DETECTED_NAME="$(python3 -c 'import json,sys; print(json.load(sys.stdin)["served_name"])' <<< "${DETECT_JSON}")"
|
||||
[[ -z "${PORT}" ]] && PORT="${DETECTED_PORT}"
|
||||
[[ -z "${SERVED_NAME}" ]] && SERVED_NAME="${DETECTED_NAME}"
|
||||
# DETECTED_PORT / served_name 只是变体信息,不作为实际端口和模型名:
|
||||
# 容器内恒 80,宿主端口默认 24396,模型名恒 llm(AGENTS.md 5.2 / 5.4 / 5.5)。
|
||||
DETECTED_HOST_PORT_HINT="${DETECTED_PORT:-}"
|
||||
fi
|
||||
|
||||
PORT="${PORT:-8091}"
|
||||
SERVED_NAME="${SERVED_NAME:-llama4}"
|
||||
PORT="${PORT:-24396}"
|
||||
SERVED_NAME="${SERVED_NAME:-llm}"
|
||||
BASE_URL="${BASE_URL:-http://127.0.0.1:${PORT}}"
|
||||
BASE_URL="${BASE_URL%/}"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user