Atlas 800I A2 8卡部署 MiniMax-H3:vLLM-Omni + MindIE-SD OOM排障与异步任务实践
前言
本文记录一次在 Atlas 800I A2(8×Ascend 910B3,单卡 64 GB HBM) 上部署 MiniMax-H3 的完整过程。
目标是使用 vLLM-Omni 提供 T2VA(文生视频+音频)和 FL2VA(首帧/首尾帧生视频+音频)服务,输出 1344×768、24 FPS 的视频。过程中主要遇到了三类问题:
- 8 张 64 GB NPU 仍然在 15 秒、1344×768 请求上 OOM;
- 同步接口长时间等待,服务端已经生成完成,客户端却收不到 MP4;
- 同时提交两三个任务时看似“假死”,但 NPU Core 使用率一直很高。
最终结果:15 秒、1344×768、24 FPS、50 steps 的 T2VA 请求成功完成,单卡峰值 HBM 约 46 GB,未再触发 OOM;同时将客户端切换为异步任务接口,解决了长连接断开后结果无法回传的问题。
1. 参考资料
- MiniMax-H3 官方模型:https://huggingface.co/MiniMaxAI/MiniMax-H3
- vLLM-Omni:GitHub - vllm-project/vllm-omni: A framework for efficient model inference with omni-modality models · GitHub
- vLLM-Omni MiniMax-H3 NPU 配置:vllm-omni/recipes/MiniMaxAI/MiniMax-H3-NPU.md at main · vllm-project/vllm-omni · GitHub
- MindIE-SD MiniMax-H3 A2/A3 推理文档:MindIE-SD/examples/minimax-h3/infer.md-代码预览-MindIE-SD:基于昇腾硬件的稳定扩散模型推理解决方案项目 - AtomGit
- packed-varlen OOM 修复 PR:[Perf][Diffusion] MiniMax-H3: opt into packed varlen attention on NPU to eliminate quadratic mask materialization by brandneway · Pull Request #5891 · vllm-project/vllm-omni · GitHub
2. 环境与模型目录
本次验证环境:
| 项目 | 配置 |
|---|---|
| 服务器 | Atlas 800I A2 |
| NPU | 8×Ascend 910B3,64 GB HBM/卡 |
| 驱动 | 26.0.rc1 |
| PyTorch | 2.10.0+cpu |
| vLLM-Omni | 0.26 分支 + 已合入上游的 MiniMax-H3 修复 |
| 推理模式 | USP8、Text Encoder TP8、VAE Patch Parallel 8 |
| 显存优化 | Distributed Layerwise Offload(DLO) |
| 注意力 | RainFusion,后续合入 packed-varlen 修复 |
T2VA 与 FL2VA 共享同一份模型权重,Ref2VA 使用另一份权重。示例目录如下:
/data/model/MiniMax-H3/
├── FL2VA
└── Ref2VA
本文启动的是 T2VA/FL2VA 服务:
export MODEL=/data/model/MiniMax-H3/FL2VA
如果需要 Ref2VA,使用同一套容器参数,将 MODEL 改成 /data/model/MiniMax-H3/Ref2VA 即可。
3. 安装 MindIE-SD 算子依赖
Ascend 上建议安装 MindIE-SD,以获得 MiniMax-H3 所需的融合算子和 RainFusion 注意力实现。以下流程来自公开参考文档:
git clone https://gitcode.com/Ascend/MindIE-SD.git
cd MindIE-SD
# 当前场景不需要 tik_ops 时,可按官方 NPU recipe 跳过该构建步骤
sed -i 's|^\(\s*\)source ${current_script_dir}/build_tik_ops.sh|\1# source ${current_script_dir}/build_tik_ops.sh|' build/build_ops.sh
python setup.py bdist_wheel
pip install dist/mindiesd-*.whl
如果环境必须通过代理访问外网,可通过环境变量或企业统一配置注入代理地址:
export http_proxy=http://proxy.example.com:PORT
export https_proxy=http://proxy.example.com:PORT
4. 8 卡后台启动脚本
下面是经过验证的启动结构。启动前设置实际镜像名,并根据主机内存调整 --shm-size。
#!/usr/bin/env bash
set -euo pipefail
NAME=minimax-h3-fl2va
IMAGE=your-registry/vllm-omni-minimax-h3:latest
MODEL=/data/model/MiniMax-H3/FL2VA
VIDEO_STORAGE=/data/vllm-omni-videos
mkdir -p "${VIDEO_STORAGE}"
docker rm -f "${NAME}" >/dev/null 2>&1 || true
docker run -d \
--name "${NAME}" \
--restart unless-stopped \
--shm-size=128g \
--net=host \
--privileged=true \
--device /dev/davinci0 \
--device /dev/davinci1 \
--device /dev/davinci2 \
--device /dev/davinci3 \
--device /dev/davinci4 \
--device /dev/davinci5 \
--device /dev/davinci6 \
--device /dev/davinci7 \
--device /dev/davinci_manager \
--device /dev/devmm_svm \
--device /dev/hisi_hdc \
-v /data/model:/data/model \
-v "${VIDEO_STORAGE}:${VIDEO_STORAGE}" \
-v /usr/local/dcmi:/usr/local/dcmi \
-v /usr/local/Ascend/driver/tools/hccn_tool:/usr/local/Ascend/driver/tools/hccn_tool \
-v /usr/local/bin/npu-smi:/usr/local/bin/npu-smi \
-v /usr/local/Ascend/driver/lib64/:/usr/local/Ascend/driver/lib64/ \
-v /usr/local/Ascend/driver/version.info:/usr/local/Ascend/driver/version.info \
-v /etc/ascend_install.info:/etc/ascend_install.info \
-v /etc/hccn.conf:/etc/hccn.conf \
-v /root/.cache:/root/.cache \
-e ASCEND_RT_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 \
-e VLLM_WORKER_MULTIPROC_METHOD=spawn \
-e VLLM_OMNI_VIDEO_SYNC_TIMEOUT=1800 \
-e VLLM_OMNI_SERVER_STORAGE__PATH="${VIDEO_STORAGE}" \
-e PYTHONDONTWRITEBYTECODE=1 \
-e HF_HUB_OFFLINE=1 \
-e TRANSFORMERS_OFFLINE=1 \
-e PYTORCH_NPU_ALLOC_CONF=expandable_segments:True \
-e HCCL_NPU_SOCKET_PORT_RANGE=auto \
"${IMAGE}" \
bash -lc "source /usr/local/Ascend/ascend-toolkit/set_env.sh && exec vllm serve '${MODEL}' \
--omni \
--host 0.0.0.0 \
--port 9098 \
--trust-remote-code \
--num-gpus 8 \
--usp 8 \
--ring 1 \
--text-encoder-tp-size 8 \
--enable-distributed-layerwise-offload \
--vae-parallel-mode tile \
--vae-use-tiling \
--vae-patch-parallel-size 8 \
--enable-diffusion-pipeline-profiler \
--diffusion-attention-config '{\"default\":{\"backend\":\"RAINFUSION_ATTN\",\"block_sparse\":{\"sparsity\":0.8,\"start_step\":12}}}'"
docker run -d 表示后台运行,关闭 SSH 窗口不会停止服务。--restart unless-stopped 会在容器异常退出或服务器重启后自动拉起;但如果手动执行 docker stop,容器不会立即自动启动。
常用检查命令:
docker ps --filter name=minimax-h3-fl2va
docker logs -f minimax-h3-fl2va
curl -f http://127.0.0.1:9098/health
npu-smi info
5. 问题一:8 张 64 GB NPU 为什么仍然 OOM
最初的 15 秒、1344×768 请求在注意力阶段失败。报错显示某个 rank 还要一次性申请约 44.78 GiB,但该卡当时只剩约 24 GiB。
关键点是:8 卡总显存足够,不等于任意一次 rank-local 临时分配都能成功。
旧实现会执行类似操作:
full_mask = attn_mask.expand(batch, heads, q_len, kv_len).contiguous()
这会在每个 rank 上物化一个近似 Sq × Skv 的完整注意力 mask。序列并行虽然分摊了模型和部分计算,但这个巨型连续临时张量仍然是单卡分配,因此总显存相加没有意义。
修复方式:packed-varlen mask-free attention
vLLM-Omni 上游 PR #5891 为 MiniMax-H3 的 NPU 路径增加了 packed-varlen attention:
- 使用
cu_seqlens和max_seqlen描述有效 token; - 支持 K/V 有效前缀切片;
- 不再物化完整的二次方 padding mask;
- 保留不支持该能力的 attention backend 的安全回退路径。
如果使用较新的 vLLM-Omni 主线,该修复已经包含在内。旧版本需要升级,或在严格核对版本差异后 backport 上游提交:
git cherry-pick 740f45abcd2e6754d1cdded01ee9901d595ccac5
不建议只删除 mask:如果 backend 仍会读取 padding token,可能得到静默错误。正确做法是由 backend 显式声明 mask-free packed 能力,并通过契约测试验证。
修复后的结果
单任务验证参数:
任务:T2VA
时长:15 秒
分辨率:1344×768
帧率:24 FPS
采样步数:50
并行:USP8 + Text Encoder TP8 + VAE Patch Parallel 8
注意力:RainFusion,sparsity=0.8,start_step=12
实测结果:
- 推理成功,无 OOM;
- 单卡峰值 HBM 约 46,973 MB;
- 客户端总耗时约 781 秒(约 13 分钟);
- 输出为 H.264 1344×768、24 FPS,带 AAC 32 kHz 立体声音频。
这些数字只代表当前软硬件组合,不应当视作所有 A2/A3 服务器的统一性能基线。
6. 问题二:同步请求已生成完成,客户端为什么还一直等待
POST /v1/videos/sync 会一直保持 HTTP 连接,直到生成、编码和整个 MP4 返回完成。长达十几分钟的连接容易受到 VPN、代理、客户端超时和网络切换影响。
一次排查中,服务端日志已经出现 200 OK,但 TCP 连接仍有较大的 Send-Q,持续重传且客户端不再 ACK。此时 NPU 推理已经结束,真正卡住的是 MP4 回传链路。
推荐:使用异步视频任务 API
vLLM-Omni 已经提供完整的异步接口:
| 接口 | 作用 |
|---|---|
POST /v1/videos |
创建任务,立即返回任务 ID |
GET /v1/videos/{video_id} |
查询状态和元数据 |
GET /v1/videos/{video_id}/content |
下载已完成的 MP4 |
GET /v1/videos |
列出当前服务进程保存的任务 |
DELETE /v1/videos/{video_id} |
删除任务及其输出 |
提交任务:
BASE_URL=http://your-server.example.com:9098
create_response=$(curl -sS -X POST "${BASE_URL}/v1/videos" \
-F 'prompt=A camper van drives along a wet alpine road at dawn.' \
-F 'width=1344' \
-F 'height=768' \
-F 'aspect_ratio=16:9' \
-F 'fps=24' \
-F 'num_inference_steps=50' \
-F 'flow_shift=12' \
-F 'seed=1101' \
-F 'extra_params={"task":"t2va","duration":8.7,"audio_flow_shift":3.0}')
video_id=$(echo "${create_response}" | jq -r '.id')
echo "video_id=${video_id}"
轮询并下载:
while true; do
status_response=$(curl -sS "${BASE_URL}/v1/videos/${video_id}")
status=$(echo "${status_response}" | jq -r '.status')
echo "status=${status}"
case "${status}" in
queued|in_progress) sleep 5 ;;
completed) break ;;
failed) echo "${status_response}" | jq .; exit 1 ;;
*) echo "unexpected status"; exit 1 ;;
esac
done
curl -fL "${BASE_URL}/v1/videos/${video_id}/content" -o output.mp4
客户端退出后,只要保存了 video_id,重新连接即可继续查询,不会重复占用 NPU 提交同一任务。
需要注意:当前普通 H3 request-execution 模式下,progress 字段实际上通常只会从 0 变成 100,应以 queued、in_progress、completed、failed 状态为准。另外,任务元数据目前保存在服务进程内存中;服务重启后 API 元数据可能丢失,因此还需要把视频文件目录挂载到宿主机持久化路径。
export VLLM_OMNI_SERVER_STORAGE__PATH=/data/vllm-omni-videos
7. 问题三:两三个任务一起提交,为什么感觉全部假死
MiniMax-H3 当前服务配置中,一个生成请求就会占满 8 张卡进行 USP8 推理。观察到单任务运行时 NPU Core 使用率经常达到 74%~100%,因此客户端并发不会把同一组 8 卡自动变成多路并行服务。
实际表现通常是:
- 1 个请求正在运行;
- 其余请求在调度队列等待;
- 每个客户端都显示“等待”,于是看起来像所有请求都卡住;
- 后续任务的端到端耗时包含前面任务的排队时间。
通过 metrics 判断运行与排队数量:
curl -s http://127.0.0.1:9098/metrics | \
grep -E 'vllm_omni:(num_requests_running|num_requests_waiting)'
如果 running=1 且 8 卡 AICore 持续较高,通常不是假死。真正需要关注的是:日志长时间没有 step 进展、NPU 利用率降为 0、容器重启、出现 OOM,或者网络发送队列持续堆积。
若需要真正同时生成多个视频,最直接的办法是增加另一组独立的 8 卡副本;同一组 8 卡已经被单任务饱和。降低分辨率、时长或 steps 可以缩短单任务耗时,但会带来质量取舍。
8. 首尾帧 FL2VA 请求示例
MiniMax-H3 的首尾帧模式通过两个同名 input_references 字段按顺序上传,并设置 frame_indices=[0,-1]:
curl -sS -X POST "${BASE_URL}/v1/videos" \
-F 'prompt=The subject moves naturally from the first image to the last image.' \
-F 'fps=24' \
-F 'num_inference_steps=50' \
-F 'flow_shift=12' \
-F 'seed=2102' \
-F 'extra_params={"task":"fl2va","duration":8.7,"frame_indices":[0,-1],"audio_flow_shift":3.0}' \
-F 'input_references=@first_frame.png;type=image/png' \
-F 'input_references=@last_frame.png;type=image/png'
提交后同样使用返回的 video_id 轮询和下载。
9. 验证清单
服务健康
curl -f http://127.0.0.1:9098/health
docker inspect minimax-h3-fl2va \
--format 'status={{.State.Status}} restart={{.RestartCount}} oom={{.State.OOMKilled}}'
NPU 状态
npu-smi info
输出文件
ffprobe -v error \
-show_entries stream=codec_name,width,height,r_frame_rate \
-show_entries format=duration,size \
-of json output.mp4
至少确认:
- 视频编码为 H.264;
- 分辨率和帧率正确;
- AAC 音轨存在;
- 时长与请求接近;
- 容器没有重启或 OOM;
- 输出文件已写入宿主机持久化目录。
总结
这次部署中最容易混淆的两件事是:
- 8 卡总显存充足,不代表单 rank 的 44.78 GiB 连续临时分配能够成功,packed-varlen 的价值是从根源上消除二次方 mask;
- 服务端推理完成,不代表同步长连接一定能把 MP4 成功送达客户端,长任务应优先使用异步任务 ID、轮询和独立下载。
完成 packed-varlen 修复、DLO、RainFusion 与异步任务改造后,MiniMax-H3 可以在 8×910B3 的 Atlas 800I A2 上稳定完成 15 秒 768P 视频+音频生成。后续优化重点可以放在真实 step 进度上报、任务元数据持久化,以及多副本并发调度。
鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。
更多推荐
所有评论(0)