Unified Llama 4 Scout/Maverick BI-V150 deployment bundle
This commit is contained in:
7
.dockerignore
Normal file
7
.dockerignore
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
.git
|
||||||
|
.gitea
|
||||||
|
README.md
|
||||||
|
run.sh
|
||||||
|
docker-compose.yml
|
||||||
|
presets
|
||||||
|
*.log
|
||||||
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
|
||||||
|
GPU_TYPE=Iluvatar_bi-150
|
||||||
|
TASK_TYPE=text-generation
|
||||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
*.log
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
50
Dockerfile
Normal file
50
Dockerfile
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# Llama 4 Scout / Maverick on Iluvatar BI-V150
|
||||||
|
#
|
||||||
|
# 一个镜像同时支持:
|
||||||
|
# - Llama-4-Scout-17B-16E-Instruct(BF16,TP8,8 卡)
|
||||||
|
# - Llama-4-Maverick-17B-128E-Instruct-FP8(FP8,TP8+PP2,16 卡)
|
||||||
|
#
|
||||||
|
# 模型权重不打进镜像,运行时按 run.sh 的 scout / maverick 预设挂载。
|
||||||
|
#
|
||||||
|
# 构建:
|
||||||
|
# docker build -t llama4-bi150:2026-10-26 .
|
||||||
|
#
|
||||||
|
# 运行:
|
||||||
|
# ./run.sh scout
|
||||||
|
# ./run.sh maverick
|
||||||
|
#
|
||||||
|
# 补丁是针对下面这个精确基础镜像生成的;换基础镜像需要重新生成 diff。
|
||||||
|
# registry.iluvatar.com.cn:10443/customer/sz/vllm0.17.0-4.4.0-x86:v7
|
||||||
|
# (vLLM 0.17.0+corex.20260612070235)
|
||||||
|
|
||||||
|
FROM registry.iluvatar.com.cn:10443/customer/sz/vllm0.17.0-4.4.0-x86:v7
|
||||||
|
|
||||||
|
ARG IMAGE_VERSION=2026-10-26
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.title="llama4-bi150" \
|
||||||
|
org.opencontainers.image.description="Llama 4 Scout / Maverick on Iluvatar BI-V150 with vLLM 0.17.0+corex" \
|
||||||
|
org.opencontainers.image.version="${IMAGE_VERSION}"
|
||||||
|
|
||||||
|
COPY patches /opt/llama4/patches
|
||||||
|
COPY apply_patch.py /opt/llama4/apply_patch.py
|
||||||
|
COPY entrypoint.sh /usr/local/bin/llama4-entrypoint.sh
|
||||||
|
|
||||||
|
RUN chmod +x /usr/local/bin/llama4-entrypoint.sh \
|
||||||
|
&& python3 /opt/llama4/apply_patch.py
|
||||||
|
|
||||||
|
ENV MODEL_PATH=/models/llama4 \
|
||||||
|
SERVED_NAME=llama4 \
|
||||||
|
HOST=0.0.0.0 \
|
||||||
|
PORT=8091 \
|
||||||
|
TP=8 \
|
||||||
|
PP=1 \
|
||||||
|
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"]
|
||||||
222
README.md
Normal file
222
README.md
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
# Llama 4 Scout / Maverick on Iluvatar BI-V150
|
||||||
|
|
||||||
|
在 16×Iluvatar BI-V150 32GB 上运行 Llama 4 Scout 与 Maverick 的统一 Docker 部署仓库。
|
||||||
|
|
||||||
|
- 基础镜像:`registry.iluvatar.com.cn:10443/customer/sz/vllm0.17.0-4.4.0-x86:v7`(vLLM `0.17.0+corex`)
|
||||||
|
- 一个镜像同时支持 Scout 和 Maverick;模型权重不打进镜像,运行时只读挂载
|
||||||
|
- 两个模型共用 Llama 4 chunked-attention 补丁;Maverick 额外使用 FP8 量化 / FP8 MoE 补丁
|
||||||
|
- CI 使用 ModelHub 标准 Gitea Actions:推送 `v*` 标签后构建、推送镜像并提交审核
|
||||||
|
|
||||||
|
| 模型 | 精度 | 并行 | GPU 数 | 默认端口 |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `LLM-Research/Llama-4-Scout-17B-16E-Instruct` | BF16 | TP8 | 8 | 8091 |
|
||||||
|
| `LLM-Research/Llama-4-Maverick-17B-128E-Instruct-FP8` | FP8 | TP8 + PP2 | 16 | 8092 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 目录内容
|
||||||
|
|
||||||
|
```text
|
||||||
|
llama4-bi150-deploy/
|
||||||
|
├── Dockerfile
|
||||||
|
├── entrypoint.sh
|
||||||
|
├── apply_patch.py # 幂等应用全部 Llama 4 补丁
|
||||||
|
├── patches/
|
||||||
|
│ ├── 01_fp8_quant.patch # Maverick FP8 量化
|
||||||
|
│ ├── 02_fp8_moe.patch # Maverick FP8 MoE
|
||||||
|
│ ├── 03_llama4_chunked_attn.patch
|
||||||
|
│ └── corexx_fp8_moe.py # 逐 expert 反量化 BF16 的 FP8 MoE 回退
|
||||||
|
├── presets/
|
||||||
|
│ ├── scout.env
|
||||||
|
│ └── maverick.env
|
||||||
|
├── run.sh # 构建 + 启动 + 等 /health
|
||||||
|
├── docker-compose.yml # 可选,按 profile 启动
|
||||||
|
├── .gitea/workflows/
|
||||||
|
│ ├── docker-build-push.yml
|
||||||
|
│ └── task_info.env
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 构建与启动
|
||||||
|
|
||||||
|
### 2.1 脚本(推荐)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Scout:8 卡,8091
|
||||||
|
./run.sh scout
|
||||||
|
|
||||||
|
# Maverick:16 卡,8092
|
||||||
|
./run.sh maverick
|
||||||
|
```
|
||||||
|
|
||||||
|
`run.sh` 会先 `docker build`,再删除同名容器、启动新容器,并等待 `/health` 最多 10 分钟。
|
||||||
|
|
||||||
|
### 2.2 指定 GPU 和模型目录
|
||||||
|
|
||||||
|
所有预设值都可以用环境变量覆盖:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Scout 使用 8-15 号卡
|
||||||
|
GPUS=8,9,10,11,12,13,14,15 ./run.sh scout
|
||||||
|
|
||||||
|
# Maverick 使用 0-15 号卡
|
||||||
|
GPUS=0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 ./run.sh maverick
|
||||||
|
|
||||||
|
# 模型放在其他目录
|
||||||
|
MODEL_HOST_DIR=/mnt/disk1/models/Llama-4-Scout-17B-16E-Instruct ./run.sh scout
|
||||||
|
|
||||||
|
# 额外挂载一个目录
|
||||||
|
EXTRA_DOCKER_ARGS="-v /mnt/disk1:/data:ro" ./run.sh scout
|
||||||
|
|
||||||
|
# 传递额外 vLLM 参数
|
||||||
|
EXTRA_ARGS="--max-model-len 8192" ./run.sh scout
|
||||||
|
|
||||||
|
# 已有镜像,不重新构建
|
||||||
|
BUILD=0 IMAGE=registry.example.com/llama4-bi150:v1.0.0 ./run.sh scout
|
||||||
|
|
||||||
|
# 只打印 docker run 命令,便于检查
|
||||||
|
DRY_RUN=1 ./run.sh scout
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 Docker Compose(可选)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose --profile scout up -d --build
|
||||||
|
docker compose --profile maverick up -d --build
|
||||||
|
|
||||||
|
# 覆盖模型目录 / GPU
|
||||||
|
SCOUT_MODEL_HOST_DIR=/mnt/disk1/models/scout SCOUT_GPUS=8,9,10,11,12,13,14,15 \
|
||||||
|
docker compose --profile scout up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 可配置项
|
||||||
|
|
||||||
|
| 变量 | Scout 默认 | Maverick 默认 | 说明 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `IMAGE` | `llama4-bi150:2026-10-26` | 同左 | 镜像名 |
|
||||||
|
| `NAME` | `llama4-scout` | `llama4-maverick` | 容器名 |
|
||||||
|
| `PORT` | `8091` | `8092` | 宿主机端口 |
|
||||||
|
| `MODEL_HOST_DIR` | `.../Llama-4-Scout-17B-16E-Instruct` | `.../Llama-4-Maverick-17B-128E-Instruct-FP8` | 宿主机模型目录 |
|
||||||
|
| `MODEL_MOUNT` | `/models/llama4` | 同左 | 容器内挂载点 |
|
||||||
|
| `SERVED_NAME` | `llama4-scout` | `llama4-maverick` | OpenAI API 模型名 |
|
||||||
|
| `TP` | `8` | `8` | 张量并行 |
|
||||||
|
| `PP` | `1` | `2` | 流水线并行 |
|
||||||
|
| `GPUS` | `0,...,7` | `0,...,15` | 传给 `CUDA_VISIBLE_DEVICES` |
|
||||||
|
| `MAX_MODEL_LEN` | `4096` | `4096` | 最大上下文 |
|
||||||
|
| `MAX_NUM_SEQS` | `1` | `1` | 最大并发 |
|
||||||
|
| `GPU_MEM_UTIL` | `0.90` | `0.90` | 显存利用率 |
|
||||||
|
| `ENFORCE_EAGER` | `1` | `1` | 是否 `--enforce-eager` |
|
||||||
|
| `EXTRA_ARGS` | 空 | 空 | 额外 vLLM 参数 |
|
||||||
|
| `EXTRA_DOCKER_ARGS` | 空 | 空 | 额外 docker 参数,如 `-v` |
|
||||||
|
| `BUILD` | `1` | `1` | `0` 表示跳过构建 |
|
||||||
|
| `DRY_RUN` | `0` | `0` | `1` 只打印命令 |
|
||||||
|
|
||||||
|
`GPUS` 也可以直接用 `CUDA_VISIBLE_DEVICES` 设置。`run.sh` 会检查 GPU 数是否少于 `TP*PP`,少于时直接报错。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 验证
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 健康检查
|
||||||
|
curl -s http://127.0.0.1:8091/health
|
||||||
|
curl -s http://127.0.0.1:8092/health
|
||||||
|
|
||||||
|
# 模型列表
|
||||||
|
curl -s http://127.0.0.1:8091/v1/models | python3 -m json.tool
|
||||||
|
curl -s http://127.0.0.1:8092/v1/models | python3 -m json.tool
|
||||||
|
|
||||||
|
# 文本
|
||||||
|
curl -s http://127.0.0.1:8091/v1/chat/completions \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-d '{"model":"llama4-scout","messages":[{"role":"user","content":"你好"}],"max_tokens":64}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Maverick 图片输入测试:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from PIL import Image
|
||||||
|
import base64, io, requests
|
||||||
|
|
||||||
|
img = Image.new("RGB", (336, 336), (220, 20, 20))
|
||||||
|
buf = io.BytesIO(); img.save(buf, format="PNG")
|
||||||
|
b64 = base64.b64encode(buf.getvalue()).decode()
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"model": "llama4-maverick",
|
||||||
|
"messages": [{"role": "user", "content": [
|
||||||
|
{"type": "text", "text": "这张图片主要是什么颜色?只回答颜色。"},
|
||||||
|
{"type": "image_url", "image_url": {"url": "data:image/png;base64," + b64}},
|
||||||
|
]}],
|
||||||
|
"max_tokens": 32,
|
||||||
|
"temperature": 0,
|
||||||
|
}
|
||||||
|
print(requests.post("http://127.0.0.1:8092/v1/chat/completions",
|
||||||
|
json=payload, timeout=300).json()["choices"][0]["message"]["content"])
|
||||||
|
```
|
||||||
|
|
||||||
|
首次使用统一镜像时,建议把 Scout 和 Maverick 都跑一遍,确认 FP8 补丁没有影响 Scout 的 BF16 路径。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. CI / ModelHub 发布
|
||||||
|
|
||||||
|
仓库已包含 ModelHub 标准 CI:
|
||||||
|
|
||||||
|
```text
|
||||||
|
.gitea/workflows/docker-build-push.yml
|
||||||
|
.gitea/workflows/task_info.env
|
||||||
|
```
|
||||||
|
|
||||||
|
触发方式:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git tag v1.0.0
|
||||||
|
git push origin v1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
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`;
|
||||||
|
5. 调 ModelHub `image-verify` 接口提交审核。
|
||||||
|
|
||||||
|
`task_info.env`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
FRAMEWORK=vllm
|
||||||
|
GPU_TYPE=Iluvatar_bi-150
|
||||||
|
TASK_TYPE=text-generation
|
||||||
|
```
|
||||||
|
|
||||||
|
Runner 需要配置以下环境变量(不是仓库 secret):
|
||||||
|
|
||||||
|
```text
|
||||||
|
DOCKER_REGISTRY
|
||||||
|
DOCKER_USERNAME
|
||||||
|
DOCKER_PASSWORD
|
||||||
|
FIXED_TOKEN
|
||||||
|
```
|
||||||
|
|
||||||
|
注意事项:
|
||||||
|
|
||||||
|
- Runner 必须能拉取 `registry.iluvatar.com.cn:10443` 的基础镜像;如果该仓库需要登录,请在 runner 上预登录,或自行给 workflow 增加基础镜像 registry 的 login step。
|
||||||
|
- CI 只负责构建和推送,不负责 GPU / 模型挂载;GPU 和挂载通过 `run.sh` 或 `docker compose` 设置。
|
||||||
|
- CI 构建机不需要 GPU,但需要足够的磁盘空间。
|
||||||
|
- 如果仓库改名,CI 生成的镜像名会跟着变。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 限制与注意事项
|
||||||
|
|
||||||
|
- Scout 需要 8 张卡(TP8);Maverick 需要 16 张卡(TP8+PP2)。16 卡机器上两者不能同时运行。
|
||||||
|
- 当前默认 `MAX_MODEL_LEN=4096`、`MAX_NUM_SEQS=1`,适合功能验证;生产并发需要先调整显存/上下文。
|
||||||
|
- 补丁是针对当前精确基础镜像生成的。更换基础镜像后不要直接复用,需要重新生成 diff 并在目标环境验证。
|
||||||
|
- `--privileged --pid host -v /dev:/dev` 是 Iluvatar 运行时要求;只给 `/dev/iluvatar*` 可能报 `cudaGetDeviceCount() ... Error 1001`。
|
||||||
|
- 模型目录只读挂载,容器不会修改宿主机模型文件。
|
||||||
117
apply_patch.py
Executable file
117
apply_patch.py
Executable file
@@ -0,0 +1,117 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Apply the CoreX vLLM patches needed by Llama 4 Scout / Maverick.
|
||||||
|
|
||||||
|
The patches in ./patches were generated against the exact base image
|
||||||
|
`registry.iluvatar.com.cn:10443/customer/sz/vllm0.17.0-4.4.0-x86:v7`
|
||||||
|
(vLLM 0.17.0+corex.20260612070235).
|
||||||
|
|
||||||
|
Scout needs only the chunked-attention patch. Maverick additionally needs the
|
||||||
|
FP8 quantization / FP8 MoE patches and the `corexx_fp8_moe` fallback module.
|
||||||
|
One image contains the union of both patch sets; the FP8 paths are only used
|
||||||
|
when the mounted model is FP8.
|
||||||
|
|
||||||
|
The script is idempotent: it checks for a marker in each target file and skips
|
||||||
|
patches that are already applied.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import ast
|
||||||
|
import os
|
||||||
|
import pathlib
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
PATCH_DIR = pathlib.Path(
|
||||||
|
os.environ.get("LLAMA4_PATCH_DIR", "/opt/llama4/patches")
|
||||||
|
)
|
||||||
|
|
||||||
|
# LLAMA4_PKG_ROOT is only needed for local testing against a copy of the
|
||||||
|
# vendor files. In the image it is derived from the installed vllm package.
|
||||||
|
_pkg_env = os.environ.get("LLAMA4_PKG_ROOT")
|
||||||
|
if _pkg_env:
|
||||||
|
PKG = pathlib.Path(_pkg_env)
|
||||||
|
else:
|
||||||
|
import vllm
|
||||||
|
|
||||||
|
PKG = pathlib.Path(vllm.__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
# patch file -> (target file relative to the package root, applied marker)
|
||||||
|
TARGETS = {
|
||||||
|
"01_fp8_quant.patch": (
|
||||||
|
"vllm/_custom_ops.py",
|
||||||
|
"COREXX_FP8_QUANT_PATCH",
|
||||||
|
),
|
||||||
|
"02_fp8_moe.patch": (
|
||||||
|
"vllm/model_executor/layers/fused_moe/fused_moe.py",
|
||||||
|
"COREXX_FP8_MOE_PATCH",
|
||||||
|
),
|
||||||
|
"03_llama4_chunked_attn.patch": (
|
||||||
|
"vllm/v1/attention/backends/utils.py",
|
||||||
|
"seq_lens_np=seqlens_k_local",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def apply_one(patch_name: str, target_rel: str, marker: str) -> None:
|
||||||
|
target = PKG / target_rel
|
||||||
|
if not target.exists():
|
||||||
|
sys.exit(f"target file not found: {target}")
|
||||||
|
if marker in target.read_text():
|
||||||
|
print(f"[skip] {target} (already patched)")
|
||||||
|
return
|
||||||
|
|
||||||
|
patch_file = PATCH_DIR / patch_name
|
||||||
|
if not patch_file.exists():
|
||||||
|
sys.exit(f"missing patch file: {patch_file}")
|
||||||
|
|
||||||
|
result = subprocess.run(
|
||||||
|
[
|
||||||
|
"patch",
|
||||||
|
"-p1",
|
||||||
|
"--forward",
|
||||||
|
"--batch",
|
||||||
|
"--no-backup-if-mismatch",
|
||||||
|
"-d",
|
||||||
|
str(PKG),
|
||||||
|
"-i",
|
||||||
|
str(patch_file),
|
||||||
|
],
|
||||||
|
text=True,
|
||||||
|
capture_output=True,
|
||||||
|
)
|
||||||
|
if result.stdout:
|
||||||
|
print(result.stdout, end="")
|
||||||
|
if result.returncode != 0:
|
||||||
|
if result.stderr:
|
||||||
|
print(result.stderr, file=sys.stderr)
|
||||||
|
sys.exit(f"patch failed: {patch_name}")
|
||||||
|
if marker not in target.read_text():
|
||||||
|
sys.exit(f"patch {patch_name} applied but marker not found in {target}")
|
||||||
|
print(f"[ok] {target}")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
for patch_name, (target_rel, marker) in TARGETS.items():
|
||||||
|
apply_one(patch_name, target_rel, marker)
|
||||||
|
|
||||||
|
src = PATCH_DIR / "corexx_fp8_moe.py"
|
||||||
|
dst = PKG / "vllm/model_executor/layers/fused_moe/corexx_fp8_moe.py"
|
||||||
|
if not src.exists():
|
||||||
|
sys.exit(f"missing module: {src}")
|
||||||
|
shutil.copy2(src, dst)
|
||||||
|
print(f"[copy] {src} -> {dst}")
|
||||||
|
|
||||||
|
for path in (
|
||||||
|
PKG / "vllm/_custom_ops.py",
|
||||||
|
PKG / "vllm/model_executor/layers/fused_moe/fused_moe.py",
|
||||||
|
dst,
|
||||||
|
PKG / "vllm/v1/attention/backends/utils.py",
|
||||||
|
):
|
||||||
|
ast.parse(path.read_text())
|
||||||
|
print("all patches applied; syntax OK")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
54
docker-compose.yml
Normal file
54
docker-compose.yml
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
x-llama4-common: &llama4-common
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
image: llama4-bi150:${TAG:-2026-10-26}
|
||||||
|
network_mode: host
|
||||||
|
ipc: host
|
||||||
|
privileged: true
|
||||||
|
pid: host
|
||||||
|
ulimits:
|
||||||
|
memlock: -1
|
||||||
|
stack: 67108864
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
services:
|
||||||
|
scout:
|
||||||
|
<<: *llama4-common
|
||||||
|
profiles: ["scout"]
|
||||||
|
container_name: llama4-scout
|
||||||
|
volumes:
|
||||||
|
- /dev:/dev
|
||||||
|
- ${SCOUT_MODEL_HOST_DIR:-/mnt/disk0/models/LLM-Research/Llama-4-Scout-17B-16E-Instruct}:/models/llama4:ro
|
||||||
|
environment:
|
||||||
|
MODEL_PATH: /models/llama4
|
||||||
|
SERVED_NAME: llama4-scout
|
||||||
|
PORT: "8091"
|
||||||
|
TP: "8"
|
||||||
|
PP: "1"
|
||||||
|
MAX_MODEL_LEN: "4096"
|
||||||
|
MAX_NUM_SEQS: "1"
|
||||||
|
GPU_MEM_UTIL: "0.90"
|
||||||
|
ENFORCE_EAGER: "1"
|
||||||
|
CUDA_VISIBLE_DEVICES: "${SCOUT_GPUS:-0,1,2,3,4,5,6,7}"
|
||||||
|
EXTRA_ARGS: "${SCOUT_EXTRA_ARGS:-}"
|
||||||
|
|
||||||
|
maverick:
|
||||||
|
<<: *llama4-common
|
||||||
|
profiles: ["maverick"]
|
||||||
|
container_name: llama4-maverick
|
||||||
|
volumes:
|
||||||
|
- /dev:/dev
|
||||||
|
- ${MAVERICK_MODEL_HOST_DIR:-/mnt/disk0/models/LLM-Research/Llama-4-Maverick-17B-128E-Instruct-FP8}:/models/llama4:ro
|
||||||
|
environment:
|
||||||
|
MODEL_PATH: /models/llama4
|
||||||
|
SERVED_NAME: llama4-maverick
|
||||||
|
PORT: "8092"
|
||||||
|
TP: "8"
|
||||||
|
PP: "2"
|
||||||
|
MAX_MODEL_LEN: "4096"
|
||||||
|
MAX_NUM_SEQS: "1"
|
||||||
|
GPU_MEM_UTIL: "0.90"
|
||||||
|
ENFORCE_EAGER: "1"
|
||||||
|
CUDA_VISIBLE_DEVICES: "${MAVERICK_GPUS:-0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}"
|
||||||
|
EXTRA_ARGS: "${MAVERICK_EXTRA_ARGS:-}"
|
||||||
71
entrypoint.sh
Executable file
71
entrypoint.sh
Executable file
@@ -0,0 +1,71 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Llama 4 Scout / Maverick 统一容器入口:
|
||||||
|
# 1. 幂等应用 CoreX vLLM 的 Llama 4 补丁(Scout 只用 chunked attention,
|
||||||
|
# Maverick 额外使用 FP8 补丁;补丁脚本会按 marker 自动跳过已应用项)
|
||||||
|
# 2. 检查模型目录
|
||||||
|
# 3. exec vLLM(信号可直接传给 vLLM)
|
||||||
|
#
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
PATCH_SCRIPT="${LLAMA4_PATCH_SCRIPT:-/opt/llama4/apply_patch.py}"
|
||||||
|
VLLM_BIN="${VLLM_BIN:-vllm}"
|
||||||
|
|
||||||
|
python3 "${PATCH_SCRIPT}"
|
||||||
|
|
||||||
|
MODEL_PATH="${MODEL_PATH:-/models/llama4}"
|
||||||
|
SERVED_NAME="${SERVED_NAME:-llama4}"
|
||||||
|
HOST="${HOST:-0.0.0.0}"
|
||||||
|
PORT="${PORT:-8091}"
|
||||||
|
TP="${TP:-8}"
|
||||||
|
PP="${PP:-1}"
|
||||||
|
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:-}"
|
||||||
|
|
||||||
|
if [[ ! -f "${MODEL_PATH}/config.json" ]]; then
|
||||||
|
echo "ERROR: 模型目录不完整:${MODEL_PATH}/config.json 不存在" >&2
|
||||||
|
echo "请确认已挂载模型,例如:" >&2
|
||||||
|
echo " -v /mnt/disk0/models/LLM-Research/Llama-4-Scout-17B-16E-Instruct:/models/llama4:ro" >&2
|
||||||
|
echo " -v /mnt/disk0/models/LLM-Research/Llama-4-Maverick-17B-128E-Instruct-FP8:/models/llama4:ro" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
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 : ${MODEL_PATH}"
|
||||||
|
echo " served name: ${SERVED_NAME}"
|
||||||
|
echo " listen : ${HOST}:${PORT}"
|
||||||
|
echo " TP / PP : ${TP} / ${PP} (total $((TP * PP)) GPUs)"
|
||||||
|
echo " max len : ${MAX_MODEL_LEN}"
|
||||||
|
echo " max seqs : ${MAX_NUM_SEQS}"
|
||||||
|
echo " gpu util : ${GPU_MEM_UTIL}"
|
||||||
|
echo " eager : ${ENFORCE_EAGER}"
|
||||||
|
echo " visible : ${CUDA_VISIBLE_DEVICES:-all}"
|
||||||
|
echo " extra : ${EXTRA_ARGS}"
|
||||||
|
echo "============================================================"
|
||||||
|
|
||||||
|
# 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}
|
||||||
46
patches/01_fp8_quant.patch
Normal file
46
patches/01_fp8_quant.patch
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
--- a/vllm/_custom_ops.py
|
||||||
|
+++ b/vllm/_custom_ops.py
|
||||||
|
@@ -1933,14 +1933,28 @@
|
||||||
|
if scale is None:
|
||||||
|
if use_per_token_if_dynamic:
|
||||||
|
scale = torch.empty((shape[0], 1), device=input.device, dtype=torch.float32)
|
||||||
|
- torch.ops._C.dynamic_per_token_scaled_fp8_quant(
|
||||||
|
- output, input, scale, scale_ub
|
||||||
|
- )
|
||||||
|
+ # COREXX_FP8_QUANT_PATCH: native fallback (vLLM _C op missing on CoreX)
|
||||||
|
+ m = input.shape[0]
|
||||||
|
+ s = input.abs().amax(dim=1, keepdim=True).float().clamp_min(1e-12) / 448.0
|
||||||
|
+ if scale_ub is not None:
|
||||||
|
+ ub = torch.as_tensor(scale_ub, device=s.device, dtype=s.dtype)
|
||||||
|
+ s = torch.minimum(s, ub)
|
||||||
|
+ scale[:m].copy_(s)
|
||||||
|
+ if shape[0] > m:
|
||||||
|
+ scale[m:].fill_(1.0)
|
||||||
|
+ output[m:].zero_()
|
||||||
|
+ output[:m].copy_((input.float() / s).to(out_dtype))
|
||||||
|
else:
|
||||||
|
scale = torch.empty(1, device=input.device, dtype=torch.float32)
|
||||||
|
- torch.ops._C.dynamic_scaled_fp8_quant(output, input, scale)
|
||||||
|
+ # COREXX_FP8_QUANT_PATCH: native fallback
|
||||||
|
+ amax = input.abs().amax().float().clamp_min(1e-12)
|
||||||
|
+ scale.fill_(amax / 448.0)
|
||||||
|
+ output.copy_((input.float() / scale).to(out_dtype))
|
||||||
|
else:
|
||||||
|
- torch.ops._C.static_scaled_fp8_quant(output, input, scale, group_shape)
|
||||||
|
+ # COREXX_FP8_QUANT_PATCH: native fallback
|
||||||
|
+ if group_shape is not None and tuple(group_shape) not in ((-1, -1), (-1, 1), (1, -1)):
|
||||||
|
+ raise NotImplementedError(f"native fp8 static quant: unsupported group_shape {group_shape}")
|
||||||
|
+ output.copy_((input.float() / scale).to(out_dtype))
|
||||||
|
|
||||||
|
return output, scale
|
||||||
|
|
||||||
|
@@ -3824,6 +3838,9 @@
|
||||||
|
mul_routed_weight,
|
||||||
|
top_k,
|
||||||
|
config['BLOCK_SIZE_M'],
|
||||||
|
+ # COREXX_FP8_MOE_PATCH: pass quant scales to ixformer kernel
|
||||||
|
+ w_scale=B_scale,
|
||||||
|
+ a_scale=A_scale,
|
||||||
|
bias=bias
|
||||||
|
)
|
||||||
|
|
||||||
183
patches/02_fp8_moe.patch
Normal file
183
patches/02_fp8_moe.patch
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
--- a/vllm/model_executor/layers/fused_moe/fused_moe.py
|
||||||
|
+++ b/vllm/model_executor/layers/fused_moe/fused_moe.py
|
||||||
|
@@ -1645,7 +1645,38 @@
|
||||||
|
# check constraints
|
||||||
|
if use_fp8_w8a8 or use_int8_w8a8 or use_int8_w8a16 or use_int4_w4a16 or w1_scale or \
|
||||||
|
w2_scale or w1_zp or w2_zp or a1_scale or a2_scale:
|
||||||
|
- raise ValueError("Quantized MoE is not supported")
|
||||||
|
+ # COREXX_FP8_MOE_PATCH: generic fused_experts_impl supports quantized MoE
|
||||||
|
+ res = fused_experts_impl(
|
||||||
|
+ hidden_states=hidden_states,
|
||||||
|
+ w1=w1,
|
||||||
|
+ w2=w2,
|
||||||
|
+ topk_weights=topk_weights,
|
||||||
|
+ topk_ids=topk_ids,
|
||||||
|
+ inplace=False,
|
||||||
|
+ activation=activation,
|
||||||
|
+ apply_router_weight_on_input=apply_router_weight_on_input,
|
||||||
|
+ use_fp8_w8a8=use_fp8_w8a8,
|
||||||
|
+ use_int8_w8a8=use_int8_w8a8,
|
||||||
|
+ use_int8_w8a16=use_int8_w8a16,
|
||||||
|
+ use_int4_w4a16=use_int4_w4a16,
|
||||||
|
+ ocp_mx_scheme=ocp_mx_scheme,
|
||||||
|
+ per_channel_quant=per_channel_quant,
|
||||||
|
+ global_num_experts=global_num_experts,
|
||||||
|
+ expert_map=expert_map,
|
||||||
|
+ w1_scale=w1_scale,
|
||||||
|
+ w2_scale=w2_scale,
|
||||||
|
+ w1_zp=w1_zp,
|
||||||
|
+ w2_zp=w2_zp,
|
||||||
|
+ a1_scale=a1_scale,
|
||||||
|
+ a2_scale=a2_scale,
|
||||||
|
+ block_shape=block_shape,
|
||||||
|
+ w1_bias=w1_bias,
|
||||||
|
+ w2_bias=w2_bias,
|
||||||
|
+ )
|
||||||
|
+ if output is not None:
|
||||||
|
+ output.copy_(res)
|
||||||
|
+ return output
|
||||||
|
+ return res
|
||||||
|
|
||||||
|
attn_metadata = get_forward_context().attn_metadata
|
||||||
|
use_ep = expert_map is not None
|
||||||
|
@@ -1912,8 +1943,32 @@
|
||||||
|
w1_bias: torch.Tensor | None = None,
|
||||||
|
w2_bias: torch.Tensor | None = None,
|
||||||
|
) -> torch.Tensor:
|
||||||
|
+ # COREXX_FP8_MOE_PATCH: dedicated per-expert BF16 fallback for FP8 MoE
|
||||||
|
+ if hidden_states.dtype in (
|
||||||
|
+ torch.float8_e4m3fn, torch.float8_e5m2,
|
||||||
|
+ torch.float8_e4m3fnuz, torch.float8_e5m2fnuz,
|
||||||
|
+ ):
|
||||||
|
+ from vllm.model_executor.layers.fused_moe.corexx_fp8_moe import corexx_fp8_moe
|
||||||
|
+ return corexx_fp8_moe(
|
||||||
|
+ hidden_states=hidden_states,
|
||||||
|
+ w1=w1,
|
||||||
|
+ w2=w2,
|
||||||
|
+ topk_weights=topk_weights,
|
||||||
|
+ topk_ids=topk_ids,
|
||||||
|
+ activation=activation,
|
||||||
|
+ apply_router_weight_on_input=apply_router_weight_on_input,
|
||||||
|
+ global_num_experts=global_num_experts,
|
||||||
|
+ expert_map=expert_map,
|
||||||
|
+ w1_scale=w1_scale,
|
||||||
|
+ w2_scale=w2_scale,
|
||||||
|
+ a1_scale=a1_scale,
|
||||||
|
+ a2_scale=a2_scale,
|
||||||
|
+ block_shape=block_shape,
|
||||||
|
+ w1_bias=w1_bias,
|
||||||
|
+ w2_bias=w2_bias,
|
||||||
|
+ )
|
||||||
|
+
|
||||||
|
# Convert string activation to enum for internal use
|
||||||
|
- activation_enum = MoEActivation.from_str(activation)
|
||||||
|
|
||||||
|
# Check constraints.
|
||||||
|
if use_int4_w4a16:
|
||||||
|
@@ -1937,7 +1992,12 @@
|
||||||
|
assert hidden_states.is_contiguous(), "Hidden_states must be contiguous"
|
||||||
|
assert w1.stride(-1) == 1, "Stride of last dimension must be 1"
|
||||||
|
assert w2.stride(-1) == 1, "Stride of last dimension must be 1"
|
||||||
|
- assert hidden_states.dtype in [torch.float32, torch.float16, torch.bfloat16]
|
||||||
|
+ # COREXX_FP8_MOE_PATCH: modular kernel may pass pre-quantized FP8 input
|
||||||
|
+ assert hidden_states.dtype in [
|
||||||
|
+ torch.float32, torch.float16, torch.bfloat16,
|
||||||
|
+ torch.float8_e4m3fn, torch.float8_e5m2,
|
||||||
|
+ torch.float8_e4m3fnuz, torch.float8_e5m2fnuz,
|
||||||
|
+ ]
|
||||||
|
|
||||||
|
num_tokens = hidden_states.size(0)
|
||||||
|
E, N, _ = w1.size()
|
||||||
|
@@ -1949,6 +2009,9 @@
|
||||||
|
# https://github.com/vllm-project/vllm/issues/5938
|
||||||
|
CHUNK_SIZE = envs.VLLM_FUSED_MOE_CHUNK_SIZE
|
||||||
|
M = min(num_tokens, CHUNK_SIZE)
|
||||||
|
+ # COREXX_FP8_MOE_PATCH: FP8 input still needs BF16 compute buffers/output
|
||||||
|
+ io_dtype = hidden_states.dtype if hidden_states.dtype in (
|
||||||
|
+ torch.float32, torch.float16, torch.bfloat16) else torch.bfloat16
|
||||||
|
|
||||||
|
config_dtype = _get_config_dtype_str(
|
||||||
|
use_fp8_w8a8=use_fp8_w8a8,
|
||||||
|
@@ -1982,7 +2045,7 @@
|
||||||
|
cache13 = torch.empty(
|
||||||
|
M * top_k_num * max(N, K),
|
||||||
|
device=hidden_states.device,
|
||||||
|
- dtype=hidden_states.dtype,
|
||||||
|
+ dtype=io_dtype,
|
||||||
|
)
|
||||||
|
intermediate_cache1 = cache13[: M * top_k_num * N].view(M, top_k_num, N)
|
||||||
|
intermediate_cache3 = cache13[: M * top_k_num * K].view(M, top_k_num, K)
|
||||||
|
@@ -1994,19 +2057,22 @@
|
||||||
|
intermediate_cache2 = torch.empty(
|
||||||
|
(M * top_k_num, activation_out_dim),
|
||||||
|
device=hidden_states.device,
|
||||||
|
- dtype=hidden_states.dtype,
|
||||||
|
+ dtype=io_dtype,
|
||||||
|
)
|
||||||
|
|
||||||
|
- if hidden_states.dtype == torch.bfloat16:
|
||||||
|
+ if io_dtype == torch.bfloat16:
|
||||||
|
compute_type = tl.bfloat16
|
||||||
|
- elif hidden_states.dtype == torch.float16:
|
||||||
|
+ elif io_dtype == torch.float16:
|
||||||
|
compute_type = tl.float16
|
||||||
|
- elif hidden_states.dtype == torch.float32:
|
||||||
|
+ elif io_dtype == torch.float32:
|
||||||
|
compute_type = tl.float32
|
||||||
|
else:
|
||||||
|
- raise ValueError(f"Unsupported compute_type: {hidden_states.dtype}")
|
||||||
|
+ raise ValueError(f"Unsupported compute_type: {io_dtype}")
|
||||||
|
|
||||||
|
- out_hidden_states = hidden_states if inplace else torch.empty_like(hidden_states)
|
||||||
|
+ if inplace and io_dtype == hidden_states.dtype:
|
||||||
|
+ out_hidden_states = hidden_states
|
||||||
|
+ else:
|
||||||
|
+ out_hidden_states = torch.empty_like(hidden_states, dtype=io_dtype)
|
||||||
|
|
||||||
|
if ocp_mx_scheme is not None:
|
||||||
|
# TODO: On platforms for which `current_platform.supports_mx()` is True
|
||||||
|
@@ -2064,14 +2130,24 @@
|
||||||
|
|
||||||
|
curr_topk_ids = topk_ids[begin_chunk_idx:end_chunk_idx]
|
||||||
|
curr_topk_weights = topk_weights[begin_chunk_idx:end_chunk_idx]
|
||||||
|
- qcurr_hidden_states, a1q_scale = moe_kernel_quantize_input(
|
||||||
|
- A=curr_hidden_states,
|
||||||
|
- A_scale=a1_scale,
|
||||||
|
- quant_dtype=quant_dtype,
|
||||||
|
- per_act_token_quant=per_channel_quant,
|
||||||
|
- block_shape=block_shape,
|
||||||
|
- ocp_mx_scheme=ocp_mx_scheme,
|
||||||
|
- )
|
||||||
|
+ # COREXX_FP8_MOE_PATCH: use pre-quantized FP8 input as-is
|
||||||
|
+ if curr_hidden_states.dtype in (
|
||||||
|
+ torch.float8_e4m3fn, torch.float8_e5m2,
|
||||||
|
+ torch.float8_e4m3fnuz, torch.float8_e5m2fnuz,
|
||||||
|
+ ):
|
||||||
|
+ qcurr_hidden_states = curr_hidden_states
|
||||||
|
+ a1q_scale = a1_scale
|
||||||
|
+ if a1q_scale is None:
|
||||||
|
+ raise ValueError("pre-quantized MoE input requires a1_scale")
|
||||||
|
+ else:
|
||||||
|
+ qcurr_hidden_states, a1q_scale = moe_kernel_quantize_input(
|
||||||
|
+ A=curr_hidden_states,
|
||||||
|
+ A_scale=a1_scale,
|
||||||
|
+ quant_dtype=quant_dtype,
|
||||||
|
+ per_act_token_quant=per_channel_quant,
|
||||||
|
+ block_shape=block_shape,
|
||||||
|
+ ocp_mx_scheme=ocp_mx_scheme,
|
||||||
|
+ )
|
||||||
|
|
||||||
|
# # SPARSITY_FACTOR is a heuristic margin ensuring tokens_in_chunk * top_k
|
||||||
|
# # activates only a small fraction of total experts
|
||||||
|
@@ -2305,8 +2381,9 @@
|
||||||
|
self.quant_config.w2_scale,
|
||||||
|
self.quant_config.w1_zp,
|
||||||
|
self.quant_config.w2_zp,
|
||||||
|
- self.quant_config.a1_scale,
|
||||||
|
- self.quant_config.a2_scale,
|
||||||
|
+ # COREXX_FP8_MOE_PATCH: prefer per-call dynamic scales
|
||||||
|
+ a1q_scale if a1q_scale is not None else self.quant_config.a1_scale,
|
||||||
|
+ a2_scale if a2_scale is not None else self.quant_config.a2_scale,
|
||||||
|
self.quant_config.block_shape,
|
||||||
|
self.quant_config.w1_bias,
|
||||||
|
self.quant_config.w2_bias,
|
||||||
16
patches/03_llama4_chunked_attn.patch
Normal file
16
patches/03_llama4_chunked_attn.patch
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
--- a/vllm/v1/attention/backends/utils.py
|
||||||
|
+++ b/vllm/v1/attention/backends/utils.py
|
||||||
|
@@ -346,7 +346,13 @@
|
||||||
|
return CommonAttentionMetadata(
|
||||||
|
query_start_loc_cpu=query_start_loc_cpu,
|
||||||
|
query_start_loc=query_start_loc_cpu.to(device=device, non_blocking=True),
|
||||||
|
+ key_start_loc=torch.from_numpy(
|
||||||
|
+ np.concatenate(
|
||||||
|
+ (np.zeros(1, dtype=np.int32), np.cumsum(seqlens_k_local, dtype=np.int32))
|
||||||
|
+ )
|
||||||
|
+ ).to(device=device, non_blocking=True),
|
||||||
|
seq_lens=seq_lens_cpu.to(device=device, non_blocking=True),
|
||||||
|
+ seq_lens_np=seqlens_k_local,
|
||||||
|
num_reqs=len(seq_lens_cpu),
|
||||||
|
num_actual_tokens=common_attn_metadata.num_actual_tokens,
|
||||||
|
max_query_len=seqlens_q_local.max(),
|
||||||
125
patches/corexx_fp8_moe.py
Normal file
125
patches/corexx_fp8_moe.py
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
"""CoreX fallback for FP8 W8A8 MoE on Iluvatar BI-V150.
|
||||||
|
|
||||||
|
The CoreX ixformer MoE kernels are W16A16 only and torch._scaled_mm supports
|
||||||
|
only tensor-wise scales, so per-token/per-channel FP8 MoE cannot use them.
|
||||||
|
This module dequantizes one expert at a time (each expert shard is only a few
|
||||||
|
hundred MB on a TP rank), runs BF16 matmuls, and scatters the result.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import torch
|
||||||
|
import torch.nn.functional as F
|
||||||
|
|
||||||
|
from vllm.logger import init_logger
|
||||||
|
|
||||||
|
logger = init_logger(__name__)
|
||||||
|
_FP8_DTYPES = (
|
||||||
|
torch.float8_e4m3fn,
|
||||||
|
torch.float8_e5m2,
|
||||||
|
torch.float8_e4m3fnuz,
|
||||||
|
torch.float8_e5m2fnuz,
|
||||||
|
)
|
||||||
|
_LOGGED = False
|
||||||
|
|
||||||
|
|
||||||
|
def _dequant_weight(w: torch.Tensor, scale: torch.Tensor | None) -> torch.Tensor:
|
||||||
|
"""w: [rows, cols] FP8, scale: per-output-row (len rows) or scalar."""
|
||||||
|
if scale is None:
|
||||||
|
return w.to(torch.bfloat16)
|
||||||
|
s = scale.reshape(-1)
|
||||||
|
if s.numel() == 1:
|
||||||
|
return (w.float() * s[0]).to(torch.bfloat16)
|
||||||
|
if s.numel() != w.shape[0]:
|
||||||
|
# try to broadcast a [1, rows] style scale
|
||||||
|
s = s.reshape(w.shape[0], -1)[:, 0]
|
||||||
|
return (w.float() * s.reshape(-1, 1)).to(torch.bfloat16)
|
||||||
|
|
||||||
|
|
||||||
|
def corexx_fp8_moe(
|
||||||
|
hidden_states: torch.Tensor,
|
||||||
|
w1: torch.Tensor,
|
||||||
|
w2: torch.Tensor,
|
||||||
|
topk_weights: torch.Tensor,
|
||||||
|
topk_ids: torch.Tensor,
|
||||||
|
activation,
|
||||||
|
apply_router_weight_on_input: bool,
|
||||||
|
global_num_experts: int,
|
||||||
|
expert_map: torch.Tensor | None,
|
||||||
|
w1_scale: torch.Tensor | None,
|
||||||
|
w2_scale: torch.Tensor | None,
|
||||||
|
a1_scale: torch.Tensor | None,
|
||||||
|
a2_scale: torch.Tensor | None,
|
||||||
|
block_shape,
|
||||||
|
w1_bias: torch.Tensor | None,
|
||||||
|
w2_bias: torch.Tensor | None,
|
||||||
|
) -> torch.Tensor:
|
||||||
|
global _LOGGED
|
||||||
|
if expert_map is not None:
|
||||||
|
raise NotImplementedError("CoreX FP8 MoE fallback does not support expert_map/EP")
|
||||||
|
if block_shape is not None:
|
||||||
|
raise NotImplementedError("CoreX FP8 MoE fallback does not support block FP8")
|
||||||
|
if not _LOGGED:
|
||||||
|
logger.info(
|
||||||
|
"CoreX FP8 MoE fallback: hidden=%s %s a1_scale=%s w1=%s w1_scale=%s w2=%s w2_scale=%s topk=%s",
|
||||||
|
tuple(hidden_states.shape), hidden_states.dtype,
|
||||||
|
None if a1_scale is None else tuple(a1_scale.shape),
|
||||||
|
tuple(w1.shape), None if w1_scale is None else tuple(w1_scale.shape),
|
||||||
|
tuple(w2.shape), None if w2_scale is None else tuple(w2_scale.shape),
|
||||||
|
tuple(topk_ids.shape),
|
||||||
|
)
|
||||||
|
_LOGGED = True
|
||||||
|
|
||||||
|
if hidden_states.dtype in _FP8_DTYPES:
|
||||||
|
if a1_scale is None:
|
||||||
|
raise ValueError("FP8 MoE input requires a1_scale")
|
||||||
|
A = (hidden_states.float() * a1_scale.reshape(-1, 1)).to(torch.bfloat16)
|
||||||
|
else:
|
||||||
|
A = hidden_states
|
||||||
|
|
||||||
|
E = w1.shape[0]
|
||||||
|
N = w1.shape[1]
|
||||||
|
K = w1.shape[2]
|
||||||
|
I = w2.shape[2]
|
||||||
|
assert N == 2 * I, f"expected w1 N=2*I, got {N} vs {I}"
|
||||||
|
|
||||||
|
act_name = getattr(activation, "value", activation)
|
||||||
|
|
||||||
|
M = A.shape[0]
|
||||||
|
out = torch.zeros((M, K), dtype=torch.bfloat16, device=A.device)
|
||||||
|
topk = topk_ids.shape[1]
|
||||||
|
|
||||||
|
# COREXX_FP8_MOE_PATCH: only touch experts that actually received tokens
|
||||||
|
active_experts = torch.unique(topk_ids).tolist()
|
||||||
|
for e in active_experts:
|
||||||
|
if e < 0 or e >= E:
|
||||||
|
continue
|
||||||
|
# rows of A routed to expert e (global id == local id when expert_map is None)
|
||||||
|
for k in range(topk):
|
||||||
|
idx = (topk_ids[:, k] == e).nonzero(as_tuple=False).flatten()
|
||||||
|
if idx.numel() == 0:
|
||||||
|
continue
|
||||||
|
x = A.index_select(0, idx)
|
||||||
|
if apply_router_weight_on_input:
|
||||||
|
x = x * topk_weights[idx, k].to(x.dtype).unsqueeze(1)
|
||||||
|
w1_e = _dequant_weight(w1[e], None if w1_scale is None else w1_scale[e])
|
||||||
|
h = x @ w1_e.t()
|
||||||
|
if w1_bias is not None:
|
||||||
|
h = h + w1_bias[e]
|
||||||
|
gate = h[:, :I]
|
||||||
|
up = h[:, I:N]
|
||||||
|
if act_name == "silu":
|
||||||
|
act = F.silu(gate) * up
|
||||||
|
elif act_name == "gelu":
|
||||||
|
act = F.gelu(gate) * up
|
||||||
|
else:
|
||||||
|
raise NotImplementedError(f"CoreX FP8 MoE activation {act_name}")
|
||||||
|
w2_e = _dequant_weight(w2[e], None if w2_scale is None else w2_scale[e])
|
||||||
|
y = act @ w2_e.t()
|
||||||
|
if w2_bias is not None:
|
||||||
|
y = y + w2_bias[e]
|
||||||
|
if not apply_router_weight_on_input:
|
||||||
|
y = y * topk_weights[idx, k].to(y.dtype).unsqueeze(1)
|
||||||
|
out.index_add_(0, idx, y)
|
||||||
|
del w1_e, w2_e, h, act, y
|
||||||
|
|
||||||
|
return out
|
||||||
12
presets/maverick.env
Normal file
12
presets/maverick.env
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Llama 4 Maverick FP8 默认配置
|
||||||
|
DEFAULT_IMAGE="llama4-bi150:2026-10-26"
|
||||||
|
DEFAULT_NAME="llama4-maverick"
|
||||||
|
DEFAULT_PORT="8092"
|
||||||
|
DEFAULT_MODEL_HOST_DIR="/mnt/disk0/models/LLM-Research/Llama-4-Maverick-17B-128E-Instruct-FP8"
|
||||||
|
DEFAULT_SERVED_NAME="llama4-maverick"
|
||||||
|
DEFAULT_TP="8"
|
||||||
|
DEFAULT_PP="2"
|
||||||
|
DEFAULT_GPUS="0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15"
|
||||||
|
DEFAULT_MAX_MODEL_LEN="4096"
|
||||||
|
DEFAULT_MAX_NUM_SEQS="1"
|
||||||
|
DEFAULT_GPU_MEM_UTIL="0.90"
|
||||||
12
presets/scout.env
Normal file
12
presets/scout.env
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Llama 4 Scout 默认配置
|
||||||
|
DEFAULT_IMAGE="llama4-bi150:2026-10-26"
|
||||||
|
DEFAULT_NAME="llama4-scout"
|
||||||
|
DEFAULT_PORT="8091"
|
||||||
|
DEFAULT_MODEL_HOST_DIR="/mnt/disk0/models/LLM-Research/Llama-4-Scout-17B-16E-Instruct"
|
||||||
|
DEFAULT_SERVED_NAME="llama4-scout"
|
||||||
|
DEFAULT_TP="8"
|
||||||
|
DEFAULT_PP="1"
|
||||||
|
DEFAULT_GPUS="0,1,2,3,4,5,6,7"
|
||||||
|
DEFAULT_MAX_MODEL_LEN="4096"
|
||||||
|
DEFAULT_MAX_NUM_SEQS="1"
|
||||||
|
DEFAULT_GPU_MEM_UTIL="0.90"
|
||||||
148
run.sh
Executable file
148
run.sh
Executable file
@@ -0,0 +1,148 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Llama 4 Scout / Maverick 一键构建 + 启动(Iluvatar BI-V150)
|
||||||
|
#
|
||||||
|
# ./run.sh scout
|
||||||
|
# ./run.sh maverick
|
||||||
|
#
|
||||||
|
# 常用覆盖项:
|
||||||
|
# GPUS=8,9,10,11,12,13,14,15 ./run.sh scout
|
||||||
|
# MODEL_HOST_DIR=/path/to/model ./run.sh maverick
|
||||||
|
# EXTRA_ARGS="--max-model-len 8192" ./run.sh scout
|
||||||
|
# EXTRA_DOCKER_ARGS="-v /mnt/disk1:/data:ro" ./run.sh scout
|
||||||
|
# BUILD=0 ./run.sh scout # 使用已有镜像,不重新构建
|
||||||
|
# DRY_RUN=1 ./run.sh scout # 只打印 docker run 命令
|
||||||
|
#
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat >&2 <<'EOF'
|
||||||
|
用法: ./run.sh <scout|maverick>
|
||||||
|
|
||||||
|
预设默认值在 presets/<name>.env 中,所有值都可以用环境变量覆盖:
|
||||||
|
IMAGE NAME PORT MODEL_HOST_DIR MODEL_MOUNT SERVED_NAME
|
||||||
|
TP PP GPUS MAX_MODEL_LEN MAX_NUM_SEQS GPU_MEM_UTIL
|
||||||
|
ENFORCE_EAGER EXTRA_ARGS EXTRA_DOCKER_ARGS
|
||||||
|
BUILD=0/1 DRY_RUN=0/1
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
VARIANT="${1:-}"
|
||||||
|
if [[ -z "${VARIANT}" || "${VARIANT}" == "-h" || "${VARIANT}" == "--help" ]]; then
|
||||||
|
usage
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
PRESET="presets/${VARIANT}.env"
|
||||||
|
if [[ ! -f "${PRESET}" ]]; then
|
||||||
|
echo "ERROR: 未知预设: ${VARIANT}" >&2
|
||||||
|
usage
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "${PRESET}"
|
||||||
|
|
||||||
|
IMAGE="${IMAGE:-${DEFAULT_IMAGE}}"
|
||||||
|
NAME="${NAME:-${DEFAULT_NAME}}"
|
||||||
|
PORT="${PORT:-${DEFAULT_PORT}}"
|
||||||
|
MODEL_HOST_DIR="${MODEL_HOST_DIR:-${DEFAULT_MODEL_HOST_DIR}}"
|
||||||
|
MODEL_MOUNT="${MODEL_MOUNT:-/models/llama4}"
|
||||||
|
SERVED_NAME="${SERVED_NAME:-${DEFAULT_SERVED_NAME}}"
|
||||||
|
TP="${TP:-${DEFAULT_TP}}"
|
||||||
|
PP="${PP:-${DEFAULT_PP}}"
|
||||||
|
GPUS="${GPUS:-${CUDA_VISIBLE_DEVICES:-${DEFAULT_GPUS}}}"
|
||||||
|
MAX_MODEL_LEN="${MAX_MODEL_LEN:-${DEFAULT_MAX_MODEL_LEN}}"
|
||||||
|
MAX_NUM_SEQS="${MAX_NUM_SEQS:-${DEFAULT_MAX_NUM_SEQS}}"
|
||||||
|
GPU_MEM_UTIL="${GPU_MEM_UTIL:-${DEFAULT_GPU_MEM_UTIL}}"
|
||||||
|
ENFORCE_EAGER="${ENFORCE_EAGER:-1}"
|
||||||
|
EXTRA_ARGS="${EXTRA_ARGS:-}"
|
||||||
|
EXTRA_DOCKER_ARGS="${EXTRA_DOCKER_ARGS:-}"
|
||||||
|
BUILD="${BUILD:-1}"
|
||||||
|
DRY_RUN="${DRY_RUN:-0}"
|
||||||
|
|
||||||
|
GPUS="${GPUS// /}"
|
||||||
|
|
||||||
|
if [[ ! -f "${MODEL_HOST_DIR}/config.json" ]]; then
|
||||||
|
echo "ERROR: 模型目录不完整: ${MODEL_HOST_DIR}/config.json" >&2
|
||||||
|
echo "请先下载模型,或设置 MODEL_HOST_DIR 指向正确目录。" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
EXPECTED_GPUS=$((TP * PP))
|
||||||
|
if [[ -n "${GPUS}" ]]; then
|
||||||
|
IFS=',' read -r -a GPU_LIST <<< "${GPUS}"
|
||||||
|
if (( ${#GPU_LIST[@]} < EXPECTED_GPUS )); then
|
||||||
|
echo "ERROR: TP*PP=${EXPECTED_GPUS},但 GPUS 只给了 ${#GPU_LIST[@]} 张: ${GPUS}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if (( ${#GPU_LIST[@]} > EXPECTED_GPUS )); then
|
||||||
|
echo "WARN: TP*PP=${EXPECTED_GPUS},但 GPUS 给了 ${#GPU_LIST[@]} 张;vLLM 只会使用其中 ${EXPECTED_GPUS} 张。" >&2
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
DOCKER_EXTRA=()
|
||||||
|
if [[ -n "${EXTRA_DOCKER_ARGS}" ]]; then
|
||||||
|
# 按空白拆分;路径中不要带空格。
|
||||||
|
read -r -a DOCKER_EXTRA <<< "${EXTRA_DOCKER_ARGS}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
DOCKER_RUN=(
|
||||||
|
docker run -d --name "${NAME}"
|
||||||
|
--privileged
|
||||||
|
--pid host
|
||||||
|
--network host
|
||||||
|
--ipc host
|
||||||
|
-v /dev:/dev
|
||||||
|
-v "${MODEL_HOST_DIR}:${MODEL_MOUNT}:ro"
|
||||||
|
--ulimit memlock=-1
|
||||||
|
--ulimit stack=67108864
|
||||||
|
-e CUDA_VISIBLE_DEVICES="${GPUS}"
|
||||||
|
-e MODEL_PATH="${MODEL_MOUNT}"
|
||||||
|
-e SERVED_NAME="${SERVED_NAME}"
|
||||||
|
-e PORT="${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 EXTRA_ARGS="${EXTRA_ARGS}"
|
||||||
|
"${DOCKER_EXTRA[@]}"
|
||||||
|
--restart unless-stopped
|
||||||
|
"${IMAGE}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if [[ "${DRY_RUN}" == "1" ]]; then
|
||||||
|
printf '%q ' "${DOCKER_RUN[@]}"
|
||||||
|
printf '\n'
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${BUILD}" == "1" ]]; then
|
||||||
|
echo "==> build ${IMAGE}"
|
||||||
|
docker build -t "${IMAGE}" .
|
||||||
|
else
|
||||||
|
echo "==> skip build (BUILD=0), image=${IMAGE}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "==> recreate ${NAME}"
|
||||||
|
docker rm -f "${NAME}" 2>/dev/null || true
|
||||||
|
|
||||||
|
echo "==> run ${VARIANT}: GPUs=${GPUS}, TP=${TP}, PP=${PP}, model=${MODEL_HOST_DIR}"
|
||||||
|
"${DOCKER_RUN[@]}"
|
||||||
|
|
||||||
|
echo "==> wait for http://127.0.0.1:${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"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "ERROR: 服务 10 分钟未就绪,查看日志:" >&2
|
||||||
|
echo " docker logs -f ${NAME}" >&2
|
||||||
|
exit 1
|
||||||
Reference in New Issue
Block a user