From ca291c3de2e99379d65d048eb962a0ee47780a80 Mon Sep 17 00:00:00 2001 From: yibo Date: Thu, 20 Aug 2026 19:05:55 +0800 Subject: [PATCH] Initial commit: GPU 服务器工作区(LLM 部署脚本 + MiniMax-H3 视频生成 + h3-console 管理台) --- .gitignore | 22 ++++++++++++++++++++++ CHANGELOG.md | 368 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ CLAUDE.md | 143 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ HANDOFF.md | 135 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ deploy-scripts/RESTORE-GPU.md | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ deploy-scripts/ctx256k.sh | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ deploy-scripts/cutover.sh | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ deploy-scripts/download.sh | 14 ++++++++++++++ deploy-scripts/ggufmeta.py | 32 ++++++++++++++++++++++++++++++++ deploy-scripts/pull2.sh | 9 +++++++++ deploy-scripts/restore.sh | 22 ++++++++++++++++++++++ h3-console/DESIGN.md | 188 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ h3-console/README.md | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ h3-console/run.sh | 14 ++++++++++++++ h3-console/server.py | 440 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ h3-console/static/index.html | 537 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ llm-setup.md | 444 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ minimax-h3/API.md | 133 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ minimax-h3/README.md | 457 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ minimax-h3/api-run.sh | 15 +++++++++++++++ minimax-h3/api.py | 267 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ minimax-h3/prompt.txt | 1 + minimax-h3/realrun.sh | 10 ++++++++++ minimax-h3/restart-8010.sh | 11 +++++++++++ minimax-h3/run.sh | 26 ++++++++++++++++++++++++++ minimax-h3/scripts/diag-nan.py | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ minimax-h3/scripts/diag3.py | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ minimax-h3/scripts/diag4.py | 91 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ minimax-h3/scripts/download.py | 38 ++++++++++++++++++++++++++++++++++++++ minimax-h3/scripts/probe-attention-backend.py | 11 +++++++++++ minimax-h3/scripts/probe-cp.py | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ minimax-h3/scripts/probe-h3-shapes.py | 32 ++++++++++++++++++++++++++++++++ minimax-h3/scripts/probe-nf4-fp16.py | 30 ++++++++++++++++++++++++++++++ minimax-h3/scripts/probe-turing-caps.py | 39 +++++++++++++++++++++++++++++++++++++++ minimax-h3/scripts/probe2.py | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ minimax-h3/scripts/run.sh | 24 ++++++++++++++++++++++++ minimax-h3/scripts/t2va.py | 227 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ minimax-h3/scripts/test-fp32.py | 39 +++++++++++++++++++++++++++++++++++++++ minimax-h3/scripts/test-patch.py | 39 +++++++++++++++++++++++++++++++++++++++ minimax-h3/scripts/verify.py | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 40 files changed, 4500 insertions(+), 0 deletions(-) create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 CLAUDE.md create mode 100644 HANDOFF.md create mode 100644 deploy-scripts/RESTORE-GPU.md create mode 100755 deploy-scripts/ctx256k.sh create mode 100755 deploy-scripts/cutover.sh create mode 100755 deploy-scripts/download.sh create mode 100644 deploy-scripts/ggufmeta.py create mode 100755 deploy-scripts/pull2.sh create mode 100755 deploy-scripts/restore.sh create mode 100644 h3-console/DESIGN.md create mode 100644 h3-console/README.md create mode 100755 h3-console/run.sh create mode 100644 h3-console/server.py create mode 100644 h3-console/static/index.html create mode 100644 llm-setup.md create mode 100644 minimax-h3/API.md create mode 100644 minimax-h3/README.md create mode 100644 minimax-h3/api-run.sh create mode 100644 minimax-h3/api.py create mode 100644 minimax-h3/prompt.txt create mode 100644 minimax-h3/realrun.sh create mode 100644 minimax-h3/restart-8010.sh create mode 100644 minimax-h3/run.sh create mode 100644 minimax-h3/scripts/diag-nan.py create mode 100644 minimax-h3/scripts/diag3.py create mode 100644 minimax-h3/scripts/diag4.py create mode 100644 minimax-h3/scripts/download.py create mode 100644 minimax-h3/scripts/probe-attention-backend.py create mode 100644 minimax-h3/scripts/probe-cp.py create mode 100644 minimax-h3/scripts/probe-h3-shapes.py create mode 100644 minimax-h3/scripts/probe-nf4-fp16.py create mode 100644 minimax-h3/scripts/probe-turing-caps.py create mode 100644 minimax-h3/scripts/probe2.py create mode 100755 minimax-h3/scripts/run.sh create mode 100644 minimax-h3/scripts/t2va.py create mode 100644 minimax-h3/scripts/test-fp32.py create mode 100644 minimax-h3/scripts/test-patch.py create mode 100644 minimax-h3/scripts/verify.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b5e41b4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# Python +__pycache__/ +*.pyc +.venv/ + +# 生成的图片/视频/音频(上传、模型出片),不是源码 +h3-console/assets/* +minimax-h3/outputs/ +*.mp4 +*.mov +*.mkv +*.avi +*.wav +*.mp3 + +# 运行时状态,不是源码 +*.sqlite3 +logs/ +*.log + +# macOS +.DS_Store diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a3e0f8e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,368 @@ +# 变更记录 + +## 2026-08-20(晚)— h3-console 任务详情页加「重跑」按钮 + +**结果**:`/task.html/` 详情页状态行旁加了「重跑」按钮。**第一版直接后台再 `POST /v1/videos` +一次,用户反馈不对**——应该是把参数带回新建任务表单让人看一眼再手动提交,不是静默重新提交。 +改成:点击后把这条任务当初的 `req`(prompt/分辨率/帧数/turbo/steps/seed/首尾帧或参考图 URL) +灌回对应的 tab(文生/首尾帧/参考图,按 req 里有没有 `first_frame_url`/`reference_image_urls` +自动判断)、跳到 `/newtask.html`,用户确认无误后自己点「提交」——跟手动新建任务走的是同一条 +路径、同一次人工确认。**没改后端**——`server.py` 的 `POST /v1/videos` 还是原来那个入口, +只是前端多了一步"回填表单"而不是直接调用。只改了 `static/index.html`(新增 +`prefillCreate()`/`setThumbs()` + detail 模板里的按钮),纯静态文件, +直接 `cat > .../console/static/index.html` 覆盖就生效,不需要重启 console 进程。 + +⚠️ 用这个按钮验证时顺手发现任务 `8755c5600378` 失败是**参数校验拒绝**,不是 bug: +`width=832,height=480` 配 `frames=362` 被 api.py 拒了(`frames` 上限 124,跟上一节 +「15 秒时长实测」结论一致——832×480 这个分辨率撑不住 362 帧)。重跑会用同样的参数、 +同样会失败,这是预期行为(按用户原话"一样的配置"重跑),**不是**这个按钮该吞掉或改的错误; +真要让这条任务跑通,需要换成 448×256 或降帧数,属于用户自己改参数重新提交的范畴。 + +--- + +## 2026-08-20(下午)— h3-console 部署上线,弹性 fl2va/ref2va 调度全链路验证通过 + +**结果**:`h3-console` 从本地实现推进到部署(服务器 `/data/models/minimax-h3/console/`,端口 8080), +用真实 GPU 完整验证了一遍"提交 fl2va 任务 → 排队 → 自动 flip 一张空闲卡 → 任务自动提交并跑完", +过程中揪出并修好两个真实 bug(细节见 `h3-console/README.md`「部署过程中揪出的两个真实 bug」): + +1. **PID 提取从来没匹配上过**:`ss -lptn` 输出里 pid 嵌在 `users:(("python",pid=NNNN,...))` 里, + 不是独立空格分隔 token,`tok.startswith("pid=")` 永远不命中,`kill` 从来没真的执行——旧进程 + 全须没被杀,新进程因端口冲突立刻退出。改用正则 `re.findall(r"pid=(\d+)", ...)`。 +2. **调度器不知道"已经在切换中"**:api.py 冷启动 13-20 秒跨了不止一个 10 秒 tick,中间几轮 + 误判需求没满足,一个 `pending=1` 的请求把三张卡都切了过去。加 `flipping` 字典记账修复。 +3. (操作事故,非代码 bug)**`pkill -f ""` 杀了自己的 SSH 会话**——命令行本身包含被 + 搜索的文本,`start-all.sh` 注释早警告过这个坑,这次真踩了一遍。以后一律用 `ss` 按端口找 PID。 + +四卡已恢复到干净的全 ref2va 基线状态(测试期间临时 flip 的那张卡已经切回去、验证过 `ready:4/4`)。 + +--- + +## 2026-08-20(上午)— 15 秒时长实测 + 新增 h3-console 管理台(设计+本地实现) + +**结果**:两件事都完成,console 本地跑通并冒烟测试通过(部署见上一节)。 + +### 15 秒时长边界实测 + +MiniMax-H3 官方规格 4–15 秒(362 帧/24fps)不是脚本瞎设的限制——`t2va.py`/`api.py` 里的 +124 帧上限是**我们自己测出的显存边界**,不是抄错官方示例(`API.md` 早就写明这点)。 +实测 362 帧在不同分辨率下: + +| 分辨率 | 结果 | 峰值显存 | 单步耗时 | +|---|---|---|---| +| 448×256 | ✅ 正常 | 12.0G | ~90s | +| 576×320 | ✅ 但吃紧(震荡换入换出) | 14.5G | 217s(慢 2.4×) | +| 640×384 | ❌ OOM | 撞到 20.6G 才崩 | — | +| 832×480(线上默认) | ❌ OOM(空卡也不行) | 撞到 20.65G 才崩 | — | + +**结论**:15 秒能做到,但线上默认的 832×480 做不到,实用上限在 448×256~576×320 之间。 +测试过程中临时停了 GPU3 的 ref2va 后端腾显存(这是测试机,`CLAUDE.md` 里明确允许), +测完已恢复,四卡 `ready: 4/4`。 + +### 被纠正的两个误解(用户转述 ChatGPT 的建议) + +1. "该先怀疑 Turbo 4-step 加速而不是 NF4"——**方向对**,我们自己的 A/B 数据支持:同一套 + NF4 权重,只关 Turbo、步数 4→50,帧间差异从 16.99 降到 7.366,音频从 −34dB 升到 −27dB, + 量化没变、画质肉眼可判断变好,说明 Turbo/步数是比 NF4 量化更大的画质杠杆。 +2. "该下载原版 BF16/FP16 权重,88GB 显存够跑更高精度"——**这条路已经被昨天的实验堵死, + 不是没试过**:bf16 在 sm_75 上无 attention kernel(`RuntimeError: No available kernel`), + fp16 会让模型输出溢出变黑屏(已用 A/B 实验证伪)。这台硬件上 compute dtype 只能是 fp32, + 跟权重存储精度是 NF4 还是 BF16 无关——bitsandbytes NF4 本来就是反量化到 fp32 再算。 + "88GB=4×22G 独立显存,H3 不支持模型并行"这条误解也再次被纠正(`README.md` 第四节早有结论)。 + 下载 BF16 权重(约 NF4 的 3-4 倍体积)在 5.9MB/s 出口带宽下要 4-5 小时,换来的只是去掉 + NF4 约 9% 的量化误差,性价比远不如已经验证过的"关 Turbo、加步数"。 + +### 新增 h3-console/(设计文档 + 本地实现,未部署) + +需求:给 minimax-h3 的四后端 API 加 Web 管理台(文生/首尾帧/参考图三个界面 + 上传 + 任务列表 + +按需 fl2va/ref2va 弹性分卡)。设计文档 `h3-console/DESIGN.md`、实现见 `h3-console/README.md`。 +架构上不重做 `gateway.py` 已经做好的调度/限流/健康聚合,只加它没有的 Web UI/上传/持久化/ +弹性分卡决策。本地端到端冒烟测试通过(上传→提交→轮询→下载全链路 + 浏览器 UI 手测), +弹性 partition 调度器(kill+restart GPU 后端切分区)只能在服务器上验证,还没测。 + +--- + +## 2026-08-18/19 — MiniMax-H3 文生视频从零到出片 + +**结果**:成功。两档都出片并通过 ffprobe + 逐像素 + 音频样本验收。 +**但环境未恢复**——见文末「遗留」。 + +### 最重要的一条:`minimax-h3/README.md` 原第一节是错的 + +原文写「`torch.bfloat16` → `torch.float16`(致命)」,把改用 fp16 当作**必须的修正**。 +**实测相反:改成 fp16 才是这条线一直出不了片的原因。** + +对照实验(448×256×28f,唯一变量是 `torch_dtype`): + +| | fp32 | fp16 | +|---|---|---| +| 视频像素 | mean 140.9 **std 50.6** | mean 0 **std 0** | +| 音频 | finite | NaN | +| 结果 | **正常出片 ✅** | **124 帧纯黑 ❌** | + +根因:模型按 bf16 训练(bf16 有 fp32 的动态范围),sm_75 无 bf16, +fp16 的 65504 上限装不下——`dit.blocks.0` 输入 absmax 89 就放大到 38176,50 层残差累积必爆。 + +### 被推翻的假设(六条,都实测过) + +| 原以为 | 实际 | +|---|---| +| fp16 是 Turing 上的正确选择 | **fp16 出全黑视频**,fp32 才行 | +| `--fp32-accum` 是降级第一档 | 开关生效但**输出逐字节相同**,对此故障完全不对症 | +| NF4 合成 probe 通过 ⇒ 真实模型可用 | 单层不溢出**推不出** 50 层残差不溢出 | +| `condition_proj` 的 inf 是根因 | 修好后问题依旧——它只是同一个 bug 的一个实例(库里有 100 处 `.to(x.dtype)`) | +| 钳位到 fp16 上限可规避溢出 | **反而更糟**:残差 `x + attn(...)` 中 x 恰为最大可表示值,再加任何正数立刻 inf | +| m3e / sd-forge 无 systemd ⇒ 手工拉起、恢复有风险 | 它们由 **PM2**(God Daemon PID 1467)托管,`kill` 会被 60 秒内拉回,必须 `pm2 stop` | + +### 实测数据(fp32,单任务) + +| 分辨率 | 步数 | 耗时 | 每步 | 峰值显存 | 像素 std | +|---|---|---|---|---|---| +| 448×256×28f | 4 turbo | 62 s | 15.5 s | 16.3 G | 50.6 | +| 640×384×61f | 4 turbo | 171 s | 42.8 s | 11.4 G | 51.7 | +| **832×480×124f** | **4 turbo** | **556 s** | **107 s** | **12.3 G** | **57.85** | +| **832×480×124f** | **50 全质量** | **5364 s** | **107.3 s** | **9.1 G** | **59.68** | + +**每步 107 s 在 turbo 与 50 步下完全一致**,是这台机器 fp32 全分辨率的稳定值。 +加载:首次从机械盘读 fp32 权重 238 s;page cache 命中后 12–20 s。 +fp16 是 36.8 s/步(快 2.9×)但**根本出不了片**——不是速度取舍。 + +### 产物验收 + +- Turbo:7,859,869 字节 · 帧间差异 **16.99** · 音频 mean −34.0 dB / rms 0.0199 / 非零 100% +- 50 步:6,726,194 字节 · 帧间差异 **7.366** · 音频 mean −27.2 dB / max −9.2 dB +- 两者均 832×480 · 24 fps · 124 帧 · 5.17 s · 含 aac 32 kHz 立体声 + +### 顺带修掉的 + +1. **`t2va.py` 退出码不可信**:原脚本只查 audio,**从不检查 video**,全黑视频照样打印 `[done]` 并返回 **0**。 + 已补 video 有限性 + 纯色检查(std < 1.0 判失败),坏产物**退出码 3**。 +2. **`--vram-reserve` 按 dtype 自适应**:fp32=14 / fp16=5。实测 `vram_limit` 7.3 G 时全分辨率跑通,19.3 G 时 OOM。 +3. **`PYTORCH_ALLOC_CONF=expandable_segments:True`** 写进 `run.sh`——22 G 卡上碎片曾达 2.49 G 并直接压垮运行。 +4. **4 个 probe-*.py 补齐到服务器**:README 第九节要求「换硬件后先跑 probe-* 四个」,但服务器上原本只有 3 个脚本。 + +### 新增 + +- `minimax-h3/api.py` + `api-run.sh`:异步任务式 HTTP API(提交拿 id → 轮询 → 下载)。 + worker 在写文件前自验像素 std 与音频有限性,**不会返回"成功但没用"的产物**。 +- `minimax-h3/API.md`:调用说明、参数范围与理由、错误码、Python 轮询示例。 +- `HANDOFF.md`:交接文档,`CLAUDE.md` 已加入口指引。 +- `deploy-scripts/RESTORE-GPU.md`:停机/恢复 runbook(含 PM2 那条更正)。 + +### 方法教训 + +- **诊断配置必须与故障配置逐项一致**:前几轮用中文 prompt 诊断、英文默认 prompt 复现, + 溢出点不同,定位出的路径在真实故障里不触发,白跑几轮。 +- **自检要守在会失败的位置**:查 `.to(fp16)` **之前**的 fp32 张量永远显示"正常",溢出恰在转换处。 +- **`cmd1 && cmd2 &` 的 `&` 作用于整条链**:`cat > file && ... &` 会让文件写入与执行竞争, + 曾因此跑了一个 0 字节脚本还以为它成功。上传与启动要分两步。 +- **`pgrep -f "gunicorn.*7590"` 会匹配到自己那条 SSH 命令行**,照着 kill 会误杀; + 用 `nvidia-smi --query-compute-apps=pid` 反查。 +- **`ffmpeg -af volumedetect` 输出易被 grep 漏掉**,误报"无音频流";用 PyAV 解码算 rms 更可靠。 +- **prompt 必须从文件读**:三层 shell 会吃掉引号,传进去是空串,报错是 + `cannot reshape tensor of 0 elements`——看着像 transformers 的 bug,实际是输入为空。 + +### ⚠️ 遗留(未完成) + +1. ~~四个服务仍是停的~~ → **已决策,不是遗留项**。2026-08-19 16:40 用户明确「停止 llm38」「只留着 h3 即可」, + 所以 `llm38` / `whisper` / `m3e` / `sd-forge` 停着即为目标状态;仅 H3 API(8010 + 8011)保留运行。 + 四者的恢复命令备在 `deploy-scripts/RESTORE-GPU.md`,需要时再起。 +2. **llm38 与 H3 共存的显存约束(将来恢复时才需处理)**:llm38 跑 256K 需四卡各约 14.3 G, + 而 GPU1 现被 8010 实例占着只剩 8.95 G。共存需 `--parallel 1 --ctx-size 262144` + (单请求仍 256K,并发降为 1,省 16 G),或先停一个 H3 实例。 +3. **`t2va.py:47` 的 `max(6.0, ...)` 地板值**:空闲显存 < 8 G 时会申请一个不存在的额度而非报错。串行跑绕开了,多负载共存时会咬人。 +4. **API 无鉴权**,与这台机器上其他服务一致;任务状态存在内存,进程重启即丢(本次已实际发生)。 + +## 2026-08-18 — 单请求上下文 32K → 256K(llm38) + +**结果**:成功。中断 **11 秒**(17:47:14 停 → 17:47:25 就绪,远快于预估的 2 分钟,因为 CUDA JIT 缓存已热)。 + +### 用户问的那个问题:不,上限不是 128K + +GGUF 元数据里 **`qwen35.context_length = 262144`**——模型原生 **256K**,之前所有文档写的 128K 都是低估。 + +```bash +python3 /home/admin/qwen38-deploy/ggufmeta.py /data/models/Qwen/Qwen3.8-27B-GGUF/Qwen3.8-27B-Q5_K_M.gguf +``` + +`llm-setup.md` 旧表里只列到 `--parallel 2 --ctx-size 262144`(128K/请求),那不是硬件极限,也不是模型极限,只是当时没查元数据。 + +### 改了什么 + +只动两个参数,其余(模型、镜像、采样参数、健康检查、挂载)逐字未变: + +| | 改前 | 改后 | +|---|---|---| +| `--ctx-size`(总池) | 131072 | **524288** | +| `--parallel`(槽位) | 4 | **2** | +| 单请求上限 | 32K | **256K** | +| 并发槽位 | 4 | 2 | + +**不需要 YaRN**:524288 是总池,被 2 槽均分后每槽 262144 = 正好等于 `n_ctx_train`,没有外插。`llm-setup.md` 第 65 行「524288 (YaRN 2x)」把总池和单请求上限混为一谈了,已订正。 + +### 实测数据 + +| 指标 | 改前(32K×4) | 改后(256K×2) | +|------|-------------|---------------| +| `n_ctx_slot` | 32768 | **262144** | +| 显存(llm38 自身) | 29.4 GB | **57.3 GB** | +| 四卡合计占用(含 whisper) | 32.7 GB | **61.4 GB / 88 GB** | +| 生成速度(短上下文) | 23.4 tok/s | 23.4 tok/s(无变化) | +| 生成速度(150K 上下文) | — | **16.7 tok/s** | +| 提示处理(11K 提示) | 1491 tok/s | 1491 tok/s | +| 提示处理(150K 提示) | — | **979 tok/s** | + +四卡分布:GPU0 15264 / GPU1 14310 / GPU2 16620 / GPU3 15254 MiB。**GPU2 只剩 5.4 GB 最紧**,起新负载时避开它。 + +### 验收:大海捞针(150K token) + +把一条真信息埋在 4000 条填充记录的第 1800 条,提示总长 **149,880 token**——这个长度连**改前的 131072 总池都放不下**,是硬证明而非软验证。 + +- `truncated = 0`(关键:没有静默截断) +- 精确取出「QX-7741-KM / 周敏」,thinking 里明确引用了「条目1800」 +- 中文连贯无乱码 +- 3 路并发全部正常返回,healthcheck `healthy` / FailingStreak=0 + +### 代价,明说 + +1. **并发从 4 降到 2。** 第 3 个请求不会被拒,会排队。公网 `qwen.wudaima.com` 若真有多人同时用,排队会变明显。 +2. **长提示很贵。** 256K 提示光 prefill 就约 **4.5 分钟**(按 979 tok/s 算),且期间独占一个槽位。150K 那次端到端 2m43s。 +3. **显存余量从 55 GB 掉到 26 GB。** minimax-h3 是按「空闲显存 − 2 GB」自适应的,这条线的可用显存被同步压缩了,两边共用一台机器时要注意。 + +### 回滚 + +旧容器**改名保留**没删: + +```bash +docker rm -f llm38 && docker rename llm38_prev llm38 && docker start llm38 # 约 15 秒 +``` + +切换脚本 `/home/admin/qwen38-deploy/ctx256k.sh` 自带失败自动回滚(容器起不来或 8 分钟不健康就自动退回)。**确认稳定后再 `docker rm llm38_prev`**——`/` 分区只剩 29 GB。 + +### 想要 256K × 4 并发?可以,但没做 + +`-ctk q8_0 -ctv q8_0` 把 KV 量化到 8 bit,KV 从 64 GiB 降到 32 GiB,256K×4 就放得下。没做的原因:用户要的是最大上下文不是最大并发,而量化 KV 会引入质量变化,不值得在一次变更里叠两个变量。要做的话单独做,单独测。 + +## 2026-08-18 — MiniMax-H3 文生视频部署(进行中) + +**结果**:环境就绪并通过全部兼容性实测;**模型下载中(33 GB / 6 MB/s),尚未完成首次出片**。 +详见 `minimax-h3/README.md`。 + +**服务器侧**:`/data/models/minimax-h3/`。**系统级零改动**——没装 CUDA、没动驱动、 +没碰 `/usr`、没改 `~/.local`。venv 用 `--system-site-packages` 复用已有的 +`torch 2.9.0+cu128`(其 `arch_list` 含 `sm_75`),只额外装了 bitsandbytes 0.50.1 和 diffsynth。 + +### 被推翻的假设 + +| 原以为 | 实际 | +|--------|------| +| **照抄官方 NF4 示例即可** | ❌ 三处必改:全篇 `torch.bfloat16` → `float16`;`vram_limit` 取的是 `mem_get_info()[1]`(**总**显存),四张卡都有常驻服务,必须改 `[0]`(空闲);low-vram 示例的 `offload_device="disk"` 在 7200rpm 机械盘上是 87 秒/步的 I/O 灾难,改 CPU 内存 offload。 | +| **bf16 只是慢一点** | ❌ sm_75 上 bf16 **没有任何高效 attention kernel**(EFFICIENT 和 FLASH 都报 `No available kernel`),PyTorch **不报错**、静默掉到 MATH 后端物化整个注意力矩阵。14k token 序列必 OOM,且症状伪装成显存不足而非 dtype 问题。**光测 `bf16 matmul` 会得出错误结论**——它是能跑的(cuBLAS 升位模拟)。 | +| **4×22 GB 可以合起来跑一个大任务** | ❌ DiffSynth 的序列并行(`utils/xfuser/`)只被 `wan_video.py` 和 `mova_audio_video.py` 引用,H3 pipeline 零引用。叠加四卡无 P2P + 跨 NUMA,只能做**独立 worker 并发**,且并发数由**内存**(每 worker 约 32 GB / 共 128 GB → 2–3 个)而非显存卡死。 | +| **Turing 上只能走 llama.cpp** | ❌ 那是 LLM 的结论。PyTorch + bitsandbytes NF4 在 sm_75 上实测可用(bnb 4-bit 的最低要求正好是 sm_75)。 | +| **`nohup` 足够让下载脱离 SSH** | ❌ 不够,SSH 通道关闭仍会带走进程,必须 `setsid`(手册第三节其实写了,没照做)。另外**开两个下载进程会互相破坏同一个 `._____temp` 分片**——出口带宽是瓶颈,并行毫无收益。 | + +### 实测数据(RTX 2080 Ti / sm_75 / torch 2.9.0+cu128) + +``` +SDPA bfloat16 EFFICIENT → No available kernel SDPA float16 EFFICIENT → OK +SDPA bfloat16 FLASH → No available kernel SDPA float16 FLASH → 仅 sm80–sm121 +SDPA bfloat16 MATH → OK(朴素实现,会 OOM) + +真实 H3 形状 S=14100 H=56 D=128 的 fp16 SDPA EFFICIENT → 0.21s +fp16 GEMM 有效算力 → 54.3 TFLOPS(张量核满血) +fp16 深链数值稳定性 → finite +NF4 fp16 前向 → finite,rel_err 0.0913;prequantized 重载 → match=True +``` + +DiT 是稠密 30.9B(50 层 / hidden 5376 / 56×128 heads / ffn 14336,无 MoE), +480×832×124 帧约 14,100 token、830 TFLOP/步。故 50 步估 20–35 分钟, +**Turbo 4-step LoRA 估 2–4 分钟**——LoRA 可叠在 NF4 上(`AutoWrappedQuantizedModule` +继承 `LoRAHotLoadMixin`,在量化输出之后做加法,不需反量化基座)。 + +### 环境里两个坑 + +- `~/.local` 的 **bitsandbytes 0.45.1 是坏的**(`'NoneType' object has no attribute + 'cquantize_blockwise_fp16_nf4'`),venv 内装 0.50.1 覆盖。 +- **不要设 `PYTHONNOUSERSITE=1`**:这台机器的 `torch` / `transformers` / `av` 都在 + `~/.local`,屏蔽掉会连 torch 一起丢。venv 的 site-packages 本就排在它前面。 + +### 容器变更(用户授权,可逆) + +停了 `llm`(旧 llama.cpp,unhealthy 5 天,已被 `llm38` 取代)——恢复 `docker start llm`。 +`whisper` 停后**已恢复**(只用单卡,停它零收益)。**`llm38` 全程未碰**。 +启动参数备份在 `/data/models/minimax-h3/backup/`。 + +## 2026-08-18 — Qwen3.6-27B → Qwen3.8-27B + +**结果**:成功。中断 1 分 51 秒(17:03:14 停 → 17:05:05 就绪)。 + +### 起因 + +用户想部署 `Qwen/Qwen3.8-27B-FP8`。**结论是 FP8 版本装不了**,改用 GGUF。 + +### 为什么 FP8 不行(三道坎,第一道是硬的) + +1. 2080 Ti 是 Turing,compute capability **7.5**。FP8 原生需 Ada(8.9)/Hopper(9.0);vLLM 的 `fp8_marlin` 回退内核要求 Ampere(8.0+)。7.5 连回退路径都够不着——不是配置能绕的,是芯片里没有那个电路。 +2. Turing 不支持 bf16,而 FP8 权重的 scale 用 bf16 存。(机器上现有 vLLM compose 全带 `--dtype=half` 就是这堵墙留下的痕迹。) +3. 即便前两条不存在,机器上最新的 vLLM 镜像是 v0.10.2(11 个月前),不认识 Qwen3.8 的 Gated DeltaNet + Gated Attention 混合架构。 + +### 最终方案 + +`bartowski/Qwen3.8-27B-GGUF` 的 **Q5_K_M**(19.32 GiB / 20,752,787,040 字节),llama.cpp **b10481** 镜像,容器名 `llm38`,端口 8000。 + +选 Q5_K_M 而非平移 IQ4_XS:四卡 88 GB 只用了 29 GB,余量充足,质量提升是白拿的。 + +### 过程中被推翻的三个假设 + +对抗性验证阶段推翻了三条原本打算照做的事,都值得记住: + +| 原计划 | 为什么错 | +|--------|---------| +| **新旧模型并存在 8000/8001 上 A/B 对比** | ❌ GPU3 只剩 10.7 GB(whisper 常驻 3.2 GB),而 llama.cpp 默认 tensor-split 按**总显存**而非**剩余显存**均分,不加 `-ts` 必 OOM。更要命的是现有服务的 context checkpoint 是**惰性分配**的(149.626 MiB × 4 slot × 8,上限约 4.7 GB),新容器吃光余量后**公网服务会在某次多轮长对话时突然 OOM 挂掉**。改为串行切换。 | +| **沿用 Qwen3.6 的采样参数** | ❌ Qwen3.8 thinking 模式官方推荐 `temp 1.0 / top-k 20 / min-p 0.0`;照抄 3.6 的 `0.7/40/0.01` 会让 thinking 质量下降。且 llama.cpp 默认是 `top_k=40 / min_p=0.05`,不显式写就是错的。 | +| **靠 MTP 推测解码提速** | ❌ `--spec-type draft-mtp` 在 sm_75 上对 hybrid 模型**根本不激活**(llama.cpp issue #24670)。网上「+33~39%」的说法在这台机器上吃不到。而且 MTP 头是独立 sidecar 文件,bartowski 仓库根本没有。 | + +### 差点踩中的静默陷阱 + +**llama.cpp build 必须 ≥ b10450**(DeltaNet CUDA 修复 commit `ece963f4`)。b10419~b10450 的症状是**加载正常、显存正常、tok/s 正常、输出全是乱码**——机器上原有的 b9570 正好在雷区里。 + +上线前验证:`docker run --rm --entrypoint /app/llama-server <镜像> --version`,实得 `build 10481, commit 25ae3a9b3`。 + +### 实测数据 + +| 指标 | Qwen3.6-27B IQ4_XS | Qwen3.8-27B Q5_K_M | +|------|-------------------|-------------------| +| 权重 | 15.77 GB | 19.32 GB | +| 生成速度 | 28 tok/s | **23.4 tok/s** | +| 显存(四卡合计) | 29.3 GB | **29.4 GB** | +| 上下文 | 131072 | 131072 | + +权重大了 3.5 GB 显存却几乎没变:Qwen3.8 的 64 层里只有 16 层是标准注意力(`full_attention_interval=4`),48 层是 Gated DeltaNet,KV cache 只有同规格纯注意力模型的约 27%。 + +四卡分布:GPU0 7448 / GPU1 7336 / GPU2 8110 / GPU3 6532 MiB。 + +### 验证结果 + +- SHA256 `e731e180460b906f373294a4e2de10541e80ee676af7f8c949a84dbb6ed3caa8` 与官方逐字节一致 +- 中文输出语义连贯,**无乱码**(本次最大风险,已排除) +- healthcheck `healthy` / FailingStreak=0 +- 5 轮连续请求显存恒定 29426 MiB,无 CUDA graphs 泄漏 +- 下游硬编码旧模型名 `Qwen_Qwen3.6-27B-IQ4_XS.gguf` 仍返回 200 + 正常内容(服务端不校验 model 字段) + +### 顺带修掉的 + +镜像自带的 healthcheck 写死 `curl localhost:8080`,而服务在 8000,导致旧容器从启动起连续失败 16321 次、常年显示 `unhealthy`。新容器用 `--health-cmd` 覆盖指向 8000。**这个假红灯之前会掩盖真故障。** + +### 遗留项 + +- **单请求上下文实际是 32K 不是 128K**:`--ctx-size 131072` 是总池,被 `--parallel 4` 均分。要单请求 128K 需改成 `--parallel 2 --ctx-size 262144`(约 42 GB,显存够)。**待用户决定。** +- 多模态 `mmproj-Qwen3.8-27B-f16.gguf`(885 MB)已下载在同目录,但**未挂载**,首轮保持纯文本。需要图像输入时加 `--mmproj` 重启。 +- CUDA graphs 被 `GGML_CUDA_DISABLE_GRAPHS=1` 关掉以规避 Turing 显存泄漏(issue #25835),上游修复后可 A/B 试着开。 + +### 同期发现的其他问题(未处理) + +- `/` 分区仅剩 29 GB(87%),docker 镜像和日志都写这里。 +- nginx 的 `4016`/`4018` 两个前端后端 `4017`/`4019`/`4020` **全部无监听**,聊天应用是坏的。 +- FRP 的 `gateway.wudaima.com → 8080` 和 `comfyui.wudaima.com → 8188` 两条隧道空挂,本地无服务。 +- 公网 `qwen.wudaima.com` **无鉴权**;SSH 22 经 FRP 映射到公网 6000 且允许密码登录。 diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..675b73b --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,143 @@ +# CLAUDE.md + +本目录是**公司 GPU 服务器 192.168.110.67(4 × RTX 2080 Ti 22G)的工作区**。真正的服务和模型都在那台远程机器上,这里只放手册、脚本和记录。 + +目前有两条并行的工作线: + +| 目录 | 工作线 | 状态 | +|------|--------|------| +| `llm-setup.md` / `deploy-scripts/` / `CHANGELOG.md` | **LLM 推理服务**(llama.cpp,对外 8000 端口) | Qwen3.8-27B 已上线 | +| `minimax-h3/` | **MiniMax-H3 FL2VA 文生视频+音频**(NF4 量化,sm_75) | 探查/调试中 | + +两条线共用同一批 GPU,**改动前先 `nvidia-smi` 看余量**——LLM 服务常驻约 29 GB / 88 GB,剩下的才是可用的。下面第「硬件的三条硬限制」一节对两条线都适用(尤其 bf16 不可用这条,`minimax-h3/scripts/probe-turing-caps.py` 就在探这个)。 + +## 接入 + +```bash +ssh admin@192.168.110.67 # 密码 P@ssw0rd +``` + +没装 `sshpass`。建议用 ControlMaster 复用连接(socket 路径必须短,否则超 Unix domain socket 长度限制): + +```bash +# 一次性建连(用 expect 喂密码),之后所有命令走复用 socket 免密 +ssh -o ControlMaster=yes -o ControlPath=~/.ssh/cm67 -o ControlPersist=30m -N admin@192.168.110.67 & +ssh -o ControlPath=~/.ssh/cm67 admin@192.168.110.67 '<命令>' +``` + +sudo 用 `echo "P@ssw0rd" | sudo -S -p "" <命令>`。注意 `/data` 是 root 属主,admin 直接 `mkdir` 会失败。 + +## 先读这个 + +**`HANDOFF.md` 是每次进来的入口**——现在是什么状态、哪些服务停着、下一步该干什么、 +已经踩过哪些坑。它比本文件更新得勤,因为它记的是"此刻"。 + +## 权威文档 + +**`llm-setup.md` 是这台机器的完整运维手册**——模型清单、启动命令、显存核算、回滚流程、FRP/nginx 映射、硬件限制全在里面。动手前先读它,不要凭记忆回答。 + +⚠️ **本地这份是服务器上 `/home/admin/llm-setup.md` 的副本,两边会漂移。** 以服务器上那份为准(SSH 进去的人读的是它)。同步: + +```bash +# 从服务器拉最新 +ssh -o ControlPath=~/.ssh/cm67 admin@192.168.110.67 'cat /home/admin/llm-setup.md' > llm-setup.md +# 推回服务器(改完本地后) +cat llm-setup.md | ssh -o ControlPath=~/.ssh/cm67 admin@192.168.110.67 'cat > /home/admin/llm-setup.md' +``` + +## 硬件的三条硬限制 + +细节见 `llm-setup.md` 第二、三节,这里只列最容易白费功夫的: + +1. **4 × RTX 2080 Ti = Turing / compute capability 7.5**。装不了 FP8 模型(原生需 Ada 8.9+,vLLM 的 `fp8_marlin` 回退也需 Ampere 8.0+),不支持 bf16。 + **LLM** 走 GGUF + llama.cpp 是唯一实际路径。**扩散/视频模型**不受此限——PyTorch + bitsandbytes NF4 在 sm_75 上实测可用,见 `minimax-h3/`。 + bf16 的坑比想象中深:sm_75 上 bf16 **没有任何高效 attention kernel**,PyTorch 不报错、静默掉到 MATH 后端物化整个注意力矩阵,长序列表现为莫名 OOM。全链路改 fp16 才行。 +2. **llama.cpp build 有下限**。qwen35 架构需 ≥ b10450,低于此**加载正常、显存正常、tok/s 正常、但输出全是乱码**。上线前必须 `--version` 查 build 号。 +3. **网络**:`huggingface.co` 与 `ghcr.io` 的 blob 层都不通,改用 `hf-mirror.com` 和 `ghcr.nju.edu.cn`。出口带宽约 5.9 MB/s 封顶,**并行下载不会更快,只会互相抢**——下载必须用 `curl -C -` 断点续传 + `setsid nohup` 脱离 SSH 会话。 + +## 目录约定(迁移工作内容时按此放) + +一个**工作负载**一个顶层目录,目录名 = 服务/模型的短名。目录内固定三样: + +``` +/ + README.md # 这个负载是什么、跑在哪张卡、怎么起、怎么停、怎么回滚 + scripts/ # 服务器上对应目录的副本(保持文件名一致,方便双向 diff) + notes.md # 可选:踩坑记录、被推翻的假设、实测数据 +``` + +顶层只放跨负载的东西:本文件、`CHANGELOG.md`(按时间倒序,一次变更一节)、`llm-setup.md`(整机手册)。 + +**迁移进来的工作必须补三件事**,否则等于没迁: + +1. 在下面「当前负载」表里加一行(占哪张卡、多少显存、对外端口)。 +2. 在 `CHANGELOG.md` 顶部加一节,写清**结果**、**被推翻的假设**、**实测数据**——CHANGELOG 的价值在踩坑记录,不在流水账。 +3. 说明脚本对应服务器上的哪个绝对路径。本地全是副本,**服务器上那份才是运行的那份**,会漂移。 + +## 当前负载 + +| 目录 | 跑什么 | 卡 | 显存 | 对外 | +|------|--------|----|------|------| +| `deploy-scripts/` | Qwen3.8-27B-Q5_K_M(llama.cpp,容器 `llm38`) | 0-3 分片 | **57.3 GB**(256K 上下文,2 槽位) | 8000 → `qwen.wudaima.com` | +| `minimax-h3/` | MiniMax-H3 FL2VA/Ref2VA NF4 文生视频,`api.py` 常驻四实例(GPU0-3,端口 8010-8013)+ `gateway.py` 调度层(端口 8000) | 四卡各一进程 | 单卡 fp32 峰值约 9-14 GB(视时长/分辨率),另占 CPU 内存约 32 GB/worker | 8000(内部,gateway.py) | +| `h3-console/` | H3 Web 管理台:上传/生成/预览三界面 + 任务持久化 + fl2va/ref2va 弹性调度器 | 不占显存(自己不跑模型,管理 minimax-h3 的四卡) | — | 8080 → `gateway.wudaima.com`(隧道已配好,公网访问未验证) | +| (常驻) | whisper | GPU3 | 3.2 GB | — | + +排卡前先 `nvidia-smi` 看实际余量:GPU3 被 whisper 占着,**llama.cpp 默认 tensor-split 按总显存而非剩余显存均分**,起新负载不加 `-ts` 容易把公网服务挤 OOM。 + +⚠️ **2026-08-18 上下文提到 256K 后余量大幅收紧**:四卡合计已用 61.4 / 88 GB,只剩约 26 GB,**GPU2 最紧只剩 5.4 GB**。minimax-h3 按「空闲显存 − 2 GB」自适应,等于它的可用显存被同步压缩了。两条线同时跑之前先看 `nvidia-smi`。 + +## 部署脚本 + +`deploy-scripts/` 下是服务器 `/home/admin/qwen38-deploy/` 的副本: + +| 脚本 | 作用 | +|------|------| +| `cutover.sh` | 切换到 Qwen3.8(校验完整性 → 停旧容器 → 起新容器 → 等健康) | +| `restore.sh` | **一键回滚**到 Qwen3.6,约 3-5 分钟 | +| `download.sh` | 断点续传下载模型 | +| `pull2.sh` | 从 NJU 代理拉镜像并 retag | +| `ctx256k.sh` | 把单请求上下文切到 256K(旧容器改名保留,失败自动回滚) | +| `ggufmeta.py` | 读 GGUF 元数据(查模型原生上下文上限、层数、KV 头维度)——**别凭记忆猜上下文上限,跑这个** | + +回滚依赖三样东西,**都不要删**:镜像 tag `llamacpp-rollback:b9570`、模型文件 `/data/models/Qwen/Qwen3.6-27B-GGUF/`、脚本 `restore.sh`。 + +## MiniMax-H3 脚本 + +`minimax-h3/scripts/` 对应服务器 `/data/models/minimax-h3/scripts/`(`run.sh` 在 +`/data/models/minimax-h3/run.sh`)。**权威文档是 `minimax-h3/README.md`**——为什么官方示例 +照抄跑不起来、Turing 的 dtype/attention/量化实测结论、多卡为什么只能做独立 worker。 + +其中 `probe-*.py` 四件套是当初得出兼容性结论的探测脚本。 +**换硬件或升级 torch / bitsandbytes 后,先跑这四个再动别的。** + +## 当前状态(2026-08-18) + +对外服务 `http://qwen.wudaima.com`(仅 HTTP,**无鉴权**),经 FRP 指向本机 8000,跑 Qwen3.8-27B-Q5_K_M。 + +**上下文:单请求 256K**(`--parallel 2 --ctx-size 524288`,每槽 262144)。256K 是**模型原生上限**——GGUF 元数据 `qwen35.context_length = 262144`,不是 128K;早期文档里的 128K 是低估。已用 150K token 大海捞针实测通过(`truncated=0`)。代价是并发从 4 降到 2,且 256K 提示光 prefill 约 4.5 分钟。 + +回滚:`docker rm -f llm38 && docker rename llm38_prev llm38 && docker start llm38`(约 15 秒)。旧容器 `llm38_prev` 停着保留,**稳定后再删**(`/` 只剩 29 GB)。 + +⚠️ 已知安全问题:公网可无鉴权直调 27B 推理;SSH 22 端口经 FRP 映射到公网 6000 且允许密码登录、admin 在 sudo 组。 + +## 这是一台测试机(2026-08-18 用户明确) + +**这台机器的用途就是测试各种模型,不是生产环境。** 显存不够时**直接停 llm38 给别的负载让路是允许的、也是预期做法**,不需要为此设计复杂的共存方案。 + +这条推翻了本文件早期版本里「8000 是对公网服务的,停服务是对外可见动作」的前提。**不要再把显存争抢当成需要精巧规避的约束**——串行跑就行: + +```bash +docker stop llm38 # 让出 ~57 GB,四卡各回到约 21 GB 空闲 +# ... 跑 minimax-h3 / 别的模型 ... +docker start llm38 # 跑完起回来,约 15 秒 +``` + +副作用:这样测出来的数字才是**干净的**(不被抢占扭曲),比在争抢状态下测更有参考价值。 + +## 操作纪律 + +- **改动线上服务前先确认回滚路径可用**。停服务本身不敏感(测试机),但起不回来会很麻烦。 +- 大文件下载和镜像拉取一律后台跑(`setsid nohup`),不要占着前台等。 +- **不要凭记忆回答这台机器的事。** 先读 `llm-setup.md` 和 `CHANGELOG.md`——里面记的坑(build 号下限、FP8 不可行、MTP 不激活)都是已经花过时间验证的,重复踩一遍是纯浪费。 +- 多个进程可能同时在这个目录里干活。改共享文件(本文件、`CHANGELOG.md`、`llm-setup.md`)用追加/局部编辑,不要整文件覆写。 diff --git a/HANDOFF.md b/HANDOFF.md new file mode 100644 index 0000000..c7434de --- /dev/null +++ b/HANDOFF.md @@ -0,0 +1,135 @@ +# 交接文档 · 2026-08-18 + +> 给下次进来的人(或进程)。**先读这份,再读 `CLAUDE.md`。** +> 权威细节在 `llm-setup.md`(整机)和 `minimax-h3/README.md`(视频线), +> 时间线在 `CHANGELOG.md`。这里只写「现在是什么状态、下一步该干什么」。 + +## 一句话 + +今天做了两件事:**llm38 单请求上下文 32K → 256K**(已完成、已验收、已恢复), +**MiniMax-H3 文生视频从零部署到出片 + 封了 HTTP API**(已出片、已验收,**服务当前在跑,环境未恢复**)。 + +## 2026-08-19 16:30 更新:断连 13 小时后重连 + +- **50 步全质量已完成并验收**(6.4 MB · 5364 s · 107.3 s/步 · 峰值 9.1 G · 帧间差异 7.366 · 音频 −27.2 dB)。 + 两档都出片,详见 `minimax-h3/README.md` 验收记录。 +- **断连原因是本地网络**:Mac 到 192.168.110.x 的路由被 VPN 隧道(`utun7` / `198.18.0.1`)劫持。 + 期间 `nc -z` 对**所有**端口都返回成功(连 9999 和只听 127.0.0.1 的 7860 也"通")—— + **那是假阳性,别据此判断服务状态**。用 `ping` + `route -n get` 确认路径。 +- **期间另有进程在这台机器上工作**:两个 H3 API 实例常驻(8010 → GPU1 13.0 G / 8011 → GPU2 7.7 G, + 两者 `/health` 均 ok),`api.py` 被改过(新增 `PARTITION` 字段),新增 `restart-8010.sh`, + `outputs/` 里有 6 个非本次生成的视频。 + +## ★ 当前是用户指定的目标状态:只留 H3 ★ + +**2026-08-19 16:40 用户明确:「停止 llm38」「只留着 h3 即可」。** +所以下面这四个**停着是对的,不要"顺手"起回来**: + +| 服务 | 状态 | 说明 | +|---|---|---| +| `llm38`(8000) | **停** | 配置已是 256K 单请求上下文并验收过,需要时 `docker start llm38` | +| `whisper`(8002) | **停** | `docker start whisper` | +| PM2 `m3e`(7590) | **停** | `/usr/local/node/bin/pm2 start m3e` | +| PM2 `sd-forge`(7860) | **停** | `/usr/local/node/bin/pm2 start sd-forge` | +| **H3 API 8010 / 8011** | **运行中** | 唯一保留的负载,占 GPU1+GPU2 共 20.8 G | + +GPU0 与 GPU3 全空(各 22002 MiB)。恢复命令详见 `deploy-scripts/RESTORE-GPU.md`。 + +> ⚠️ 要恢复 llm38 时注意:它跑 256K 需**四卡各约 14.3 G**,而 GPU1 现被 8010 占着只剩 8.95 G。 +> 要共存就用 `--parallel 1 --ctx-size 262144`(单请求仍 256K,并发降为 1,省 16 G), +> 或先停一个 H3 实例。 + +## ⚠️ 立刻要知道的三件事 + +1. **四个服务当前是停的,而这是用户要的状态**(见上一节),不是未完成的收尾。 + 需要时按 `deploy-scripts/RESTORE-GPU.md` 恢复,恢复后验 7590 / 7860 / 8000 / 8002 四个端口。 +2. **`m3e` 和 `sd-forge` 由 PM2 托管**(God Daemon PID 1467)。`kill` 它们**没用**, + 60 秒内会被拉回来;必须 `/usr/local/node/bin/pm2 stop m3e sd-forge`。恢复是 `pm2 start`。 + pm2 不在默认 PATH 里,非交互 shell 下写全路径。 +3. **一条 50 步全质量视频还在跑**(GPU2,22:22 启动,约 90 分钟,预计 23:50 左右完), + H3 的 **API 服务也在跑**(GPU1,端口 8010)。恢复环境前先确认这两个结束/停掉。 + +## 状态速查 + +| 东西 | 状态 | 位置 | +|---|---|---| +| llm38(Qwen3.8-27B) | **已停**。配置已改成 256K 单请求上下文,验收过 | `deploy-scripts/ctx256k.sh` | +| llm38 旧容器 `llm38_prev` | 保留着(32K 配置),确认稳定后可 `docker rm` | `/` 只剩 29 G,别忘 | +| whisper / m3e / sd-forge | **已停** | `deploy-scripts/RESTORE-GPU.md` | +| MiniMax-H3 权重 34 GB | 下载完 + 字节级校验 `ALL GOOD` | `/data/models/minimax-h3/models/` | +| H3 出片 | **成功**,已验收(见下) | `outputs/t2va_20260818-223136.mp4` | +| H3 API | **在跑**,GPU1:8010 | `minimax-h3/API.md` | +| 50 步全质量 | **进行中**,结果未验 | `logs/real-full50.log` | + +## 今天最重要的一条结论 + +**`minimax-h3/README.md` 原第一节写「`bfloat16` → `float16`(致命)」是错的。 +改成 fp16 才是这条线一直出不了片的原因。** + +对照实验(唯一变量是 dtype,448×256×28f): + +| | fp32 | fp16 | +|---|---|---| +| 视频 | mean 140.9 **std 50.6** | mean 0 **std 0** | +| 音频 | finite | NaN | +| 结果 | **出片 ✅** | **纯黑 ❌** | + +原因:模型按 bf16 训练(bf16 有 fp32 的动态范围),sm_75 没有 bf16, +fp16 的 65504 上限装不下——`dit.blocks.0` 输入 absmax 89 就放大到 38176,50 层残差累积必爆。 + +**逐点修 fp16 是行不通的**,我试了四条弯路全部失败(`--fp32-accum` 无效、替换 forward 绕过换入、 +转换处溢出、钳位到上限反而自造故障),细节和实测数据都在 `minimax-h3/README.md`。 +`t2va.py` 的 `--dtype` 现在**默认 fp32**,保留 `fp16` 只为复现故障。 + +## 验收数据(都是实测,没有推测) + +**H3 出片**:832×480 · 24fps · 124 帧 · 5.17 s · 7.5 MB · +像素 std 57.85 · **帧间差异 16.99(证明是动态视频)** · +音频 aac 32 kHz 立体声 mean −34.0 dB / rms 0.0199(**证明不是静音**)。 + +**性能**(fp32,单任务):全分辨率 Turbo 4 步约 **107 s/步**,峰值显存 **12.2 G**, +首次加载 238 s,page cache 命中后 12–20 s。fp16 是 36.8 s/步但出不了片。 + +**llm38 256K**:`n_ctx_slot = 262144`(模型原生上限,GGUF 元数据 `qwen35.context_length`), +150 K token 大海捞针 `truncated=0` 通过,显存 57.3 G,并发从 4 降到 2, +256 K 提示 prefill 约 4.5 分钟。 + +## 下一步(按优先级) + +1. **收尾**:等 50 步跑完 → 验收(`bash /tmp/verify-out.sh <文件>`,脚本在服务器 `/tmp`, + 要长期用得挪到 `scripts/`)→ 补 `README.md` 验收记录 → **恢复四个服务并验端口**。 +2. **决定 llm38 与 H3 的共存策略**。两者显存放不下(57.3 + 12.3 > 单卡余量)。 + 选项:H3 按需起停;或 llm38 降到 `--parallel 1 --ctx-size 262144`(保 256 K 单请求, + 并发降为 1,省 16 G);或换卡。**这是产品决策,需要用户拍板。** +3. **安全**:`qwen.wudaima.com` 公网无鉴权可直调 27B;H3 API 也无鉴权。 + SSH 22 经 FRP 映射到公网 6000 且允许密码登录、admin 在 sudo 组。**都没动,等授权。** +4. 可选:`t2va.py:47` 的 `max(6.0, ...)` 地板值在空闲显存 < 8 G 时会要一个不存在的额度, + 应改成显式报错。串行跑绕开了,多负载共存时会咬人。 + +## 踩过的坑(别重踩) + +- **退出码不可信**:原 `t2va.py` 只查 audio,全黑视频照样 `[done]` + 退出码 0。 + 已补 video 纯色检查,坏产物退出码 3。API 的 worker 也会验 std 和音频有限性。 +- **诊断配置必须和故障配置逐项一致**:我用中文 prompt 诊断、英文默认 prompt 复现, + 溢出点不同,白跑几轮。 +- **prompt 必须从文件读**:`ssh … bash -c "…"` 三层 shell 会吃掉引号,传进去是空串, + 报错是 `cannot reshape tensor of 0 elements`——看着像 transformers 的 bug,实际是输入为空。 +- **`cmd1 && cmd2 &` 的 `&` 作用于整条链**:`cat > file && ... &` 会让文件写入和执行竞争, + 我因此跑了一个 0 字节的脚本还以为它成功了。上传和启动要分两步。 +- **`pgrep -f "gunicorn.*7590"` 会匹配到你自己那条 SSH 命令行**,照着 kill 会误杀。 + 用 `nvidia-smi --query-compute-apps=pid` 反查。 +- **`ffmpeg -af volumedetect` 的输出容易被 grep 漏掉**,误报"无音频流"。 + 用 PyAV 直接解码算 rms 更可靠。 +- **合成小张量的 probe 测不出真问题**:`probe-nf4-fp16.py` 验证「单层 NF4 fp16 不溢出」通过, + 但推不出「50 层残差不溢出」。 + +## 本地与服务器的对应 + +| 本地 | 服务器 | +|---|---| +| `deploy-scripts/` | `/home/admin/qwen38-deploy/` | +| `llm-setup.md` | `/home/admin/llm-setup.md` | +| `minimax-h3/scripts/`、`API.md`、`README.md` | `/data/models/minimax-h3/`(`run.sh`/`api.py`/`realrun.sh` 在顶层) | + +**服务器那份才是运行的那份**,两边会漂移。今天就因为只在服务器上修了导入、 +本地那份没同步,重新生成脚本时把旧 bug 带了回去,白跑一轮。 diff --git a/deploy-scripts/RESTORE-GPU.md b/deploy-scripts/RESTORE-GPU.md new file mode 100644 index 0000000..3803ba0 --- /dev/null +++ b/deploy-scripts/RESTORE-GPU.md @@ -0,0 +1,80 @@ +# 腾显存 / 恢复清单 + +> 生成于 2026-08-18,为 minimax-h3 部署腾显存前的现场快照。 +> 用户已授权「部署 H3 时其他占显存的模型都可以停」,但**恢复方式必须先记全再动手**。 + +## 现场(停机前) + +| PID/容器 | 服务 | 端口 | 显存 | 恢复方式 | +|---|---|---|---|---| +| 容器 `llm38` | Qwen3.8-27B llama.cpp | 8000 | **57.3 GB** | `docker start` | +| 容器 `whisper` | 语音转写 | 8002 | **3.2 GB** | `docker start` | +| PM2 app `m3e` | m3e embedding gunicorn | 7590 | 668 MiB | `pm2 start m3e` | +| PM2 app `sd-forge` | SD WebUI Forge | 7860(仅本机) | 174 MiB | `pm2 start sd-forge` | + +**用户 2026-08-18 明确指示:需要显存时四个全停。** 这是测试机,不是生产环境。 +合计腾出 **61.4 GB**,四卡各回到约 21.5 GB 空闲。 + +### ★ 后两个是 PM2 托管的,不是手工拉起 ★ + +**2026-08-18 实测更正**:本文件早期版本写它们「无 systemd 单元、手工拉起、是恢复环节的主要风险点」——**错了**。 +它们由 **PM2 God Daemon(PID 1467, v5.3.1)** 托管,`pm2 list` 里叫 `m3e` 和 `sd-forge`。 + +后果很实际:**`kill ` 是没用的,PM2 会在几秒内把它们拉回来**(实测 kill 后 60 秒内自动重启,显存又被占回去)。必须用 `pm2 stop`。 +好消息是恢复也因此变简单——一条 `pm2 start` 即可,不需要手抄启动命令。 + +pm2 二进制在 **`/usr/local/node/bin/pm2`**(不在默认 PATH 里,SSH 非交互 shell 下要写全路径)。 + +PM2 还托管着 5 条 frpc 隧道(`frpc-ssh` / `frpc-qwen-client` / `frpc-m3e-client` / `frpc-sd-client` / `frpc-comfyui-client`)和几个 stopped 的条目(`qwen` / `deepseek` / `deepseek-14b`)。**隧道不占显存,不要停。** + +## 一键全停 + +```bash +docker stop llm38 whisper +/usr/local/node/bin/pm2 stop m3e sd-forge # ← 必须用 pm2,kill 会被自动拉回 +sleep 6 +nvidia-smi --query-compute-apps=pid,used_memory --format=csv,noheader # 应为空 +nvidia-smi --query-gpu=index,memory.free --format=csv,noheader # 实测四卡各 22002 MiB +``` +实测结果(2026-08-18 18:58):**四卡各 22002 MiB 空闲,合计 88 GB,计算进程为零。** + +> ⚠️ 别用 `pgrep -f "gunicorn.*7590"` 找 PID——**它会匹配到你自己那条 SSH 命令行**,照着 kill 会误杀。 +> 用 `nvidia-smi --query-compute-apps=pid` 报出来的 PID 反查才可靠。 + +## 恢复命令 + +### llm38(docker,首选) +```bash +docker start llm38 +# 验证(约 15 秒) +curl -sf http://127.0.0.1:8000/health +docker logs llm38 2>&1 | grep n_ctx_slot | tail -1 # 应为 n_ctx_slot = 262144 +docker inspect llm38 --format '{{.State.Health.Status}}' # healthy +``` +起不来时的兜底:`/home/admin/qwen38-deploy/ctx256k.sh`(自带失败回滚), +或旧配置容器 `llm38_prev`:`docker rm -f llm38 && docker rename llm38_prev llm38 && docker start llm38` + +### whisper(docker) +```bash +docker start whisper +ss -tln | grep 8002 +``` + +### m3e embedding + SD WebUI Forge(PM2) +```bash +/usr/local/node/bin/pm2 start m3e sd-forge +/usr/local/node/bin/pm2 list # 两者应为 online +ss -tln | grep -E '7590|7860' # 7590 全网 / 7860 仅 127.0.0.1 +``` +> 手工拉起的命令留档备查(**正常情况下用不到,PM2 里有配置**): +> - m3e:cwd `/home/admin/llm/embedding`, +> `/home/admin/.cache/pypoetry/virtualenvs/embedding-LbDC8KEi-py3.10/bin/python .../bin/gunicorn -w 1 -b 0.0.0.0:7590 --timeout 300 embedding.app:app --log-level info` +> - sd-forge:cwd `/data/models/stable-diffusion-webui-forge`,`bash webui.sh --api` + +## 收工检查 + +```bash +nvidia-smi --query-compute-apps=pid,used_memory --format=csv,noheader +ss -tln | grep -E '7590|7860|8000|8002' # 四个端口都要在 +curl -sf http://127.0.0.1:8000/health && echo OK +``` diff --git a/deploy-scripts/ctx256k.sh b/deploy-scripts/ctx256k.sh new file mode 100755 index 0000000..31111f6 --- /dev/null +++ b/deploy-scripts/ctx256k.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +# 把 llm38 的单请求上下文从 32K 提到 256K(模型原生上限)。 +# 手法:--parallel 4 --ctx-size 131072 → --parallel 2 --ctx-size 524288 +# 旧容器改名保留而非删除,起不来就秒退回去。 +set -uo pipefail +NEWCTX=524288; NEWPAR=2 + +echo "[1/4] 备份旧容器(改名保留,不删)" +docker rm -f llm38_prev 2>/dev/null +docker stop llm38 >/dev/null && docker rename llm38 llm38_prev +echo " 旧容器已停并改名 llm38_prev $(date '+%H:%M:%S')" + +rollback() { + echo "❌ 新配置未就绪,回滚到 32K" + docker rm -f llm38 2>/dev/null + docker rename llm38_prev llm38 && docker start llm38 + for i in $(seq 1 60); do + curl -sf http://127.0.0.1:8000/health >/dev/null 2>&1 && { echo " ✅ 已回滚就绪 $(date '+%H:%M:%S')"; exit 1; } + sleep 5 + done + echo " ⚠️ 回滚后仍未就绪,人工介入:docker logs llm38"; exit 2 +} + +echo "[2/4] 起新容器 parallel=$NEWPAR ctx-size=$NEWCTX(单请求 $((NEWCTX/NEWPAR/1024))K)" +docker run -d \ + --name llm38 \ + --restart unless-stopped \ + --gpus all \ + -p 8000:8000 \ + -v /data/models:/jsxc \ + -v /data/nvcache38:/root/.nv \ + -e GGML_CUDA_DISABLE_GRAPHS=1 \ + -e CUDA_CACHE_MAXSIZE=2147483648 \ + --health-cmd "curl -fsS http://localhost:8000/health || exit 1" \ + --health-interval 30s --health-timeout 5s \ + --health-retries 3 --health-start-period 600s \ + llamacpp:b10481 \ + -m /jsxc/Qwen/Qwen3.8-27B-GGUF/Qwen3.8-27B-Q5_K_M.gguf \ + --alias qwen3.8-27b \ + --host 0.0.0.0 --port 8000 \ + -ngl 99 -sm layer -fa on \ + --ctx-size $NEWCTX --no-context-shift \ + --batch-size 2048 --ubatch-size 512 \ + --parallel $NEWPAR --jinja --seed 3407 \ + --temp 1.0 --top-p 0.95 --top-k 20 --min-p 0.0 --presence-penalty 0.0 \ + || rollback + +echo "[3/4] 等待就绪(KV 变大,分配比之前慢;最多 8 分钟)" +for i in $(seq 1 96); do + if ! docker ps --format '{{.Names}}' | grep -qx llm38; then + echo " 容器退出了,尾部日志:"; docker logs --tail 25 llm38 2>&1; rollback + fi + if curl -sf http://127.0.0.1:8000/health >/dev/null 2>&1; then + echo " ✅ 就绪于第 $((i*5)) 秒 $(date '+%H:%M:%S')"; break + fi + [ "$i" = 96 ] && { docker logs --tail 25 llm38 2>&1; rollback; } + sleep 5 +done + +echo "[4/4] 实际生效值" +docker logs llm38 2>&1 | grep -E "n_ctx_slot|n_slots" | tail -2 +nvidia-smi --query-gpu=index,memory.used,memory.free --format=csv,noheader +echo "旧容器 llm38_prev 保留着,确认没问题后 docker rm llm38_prev" diff --git a/deploy-scripts/cutover.sh b/deploy-scripts/cutover.sh new file mode 100755 index 0000000..c7b7c0d --- /dev/null +++ b/deploy-scripts/cutover.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# Qwen3.6 -> Qwen3.8 串行切换。失败请跑 restore.sh +set -e +M=/data/models/Qwen/Qwen3.8-27B-GGUF/Qwen3.8-27B-Q5_K_M.gguf +E=20752787040 + +echo "[G3] 校验模型完整性..." +SZ=$(stat -c %s "$M") +[ "$SZ" = "$E" ] || { echo "❌ 字节数不符: $SZ != $E,中止"; exit 1; } +echo " ✅ $SZ 字节,完整" + +echo "[1/3] 停止 Qwen3.6 (llm)... 中断从此刻开始" +date '+%H:%M:%S' +docker stop llm + +echo "[2/3] 启动 Qwen3.8 (llm38) 于 8000..." +docker rm -f llm38 2>/dev/null || true +mkdir -p /data/nvcache38 2>/dev/null || true +docker run -d \ + --name llm38 \ + --restart unless-stopped \ + --gpus all \ + -p 8000:8000 \ + -v /data/models:/jsxc \ + -v /data/nvcache38:/root/.nv \ + -e GGML_CUDA_DISABLE_GRAPHS=1 \ + -e CUDA_CACHE_MAXSIZE=2147483648 \ + --health-cmd "curl -fsS http://localhost:8000/health || exit 1" \ + --health-interval 30s --health-timeout 5s \ + --health-retries 3 --health-start-period 600s \ + llamacpp:b10481 \ + -m /jsxc/Qwen/Qwen3.8-27B-GGUF/Qwen3.8-27B-Q5_K_M.gguf \ + --alias qwen3.8-27b \ + --host 0.0.0.0 --port 8000 \ + -ngl 99 -sm layer -fa on \ + --ctx-size 131072 --no-context-shift \ + --batch-size 2048 --ubatch-size 512 \ + --parallel 4 --jinja --seed 3407 \ + --temp 1.0 --top-p 0.95 --top-k 20 --min-p 0.0 --presence-penalty 0.0 + +echo "[3/3] 等待就绪(首次含 JIT 编译,最多 10 分钟)..." +for i in $(seq 1 60); do + if curl -sf http://127.0.0.1:8000/health >/dev/null 2>&1; then + echo " ✅ 就绪于第 $((i*10)) 秒"; date '+%H:%M:%S'; exit 0 + fi + sleep 10 +done +echo "❌ 超时未就绪。查 docker logs llm38;回滚跑 restore.sh"; exit 1 diff --git a/deploy-scripts/download.sh b/deploy-scripts/download.sh new file mode 100755 index 0000000..8ef9769 --- /dev/null +++ b/deploy-scripts/download.sh @@ -0,0 +1,14 @@ +#!/bin/bash +MODEL_DIR=/data/models/Qwen/Qwen3.8-27B-GGUF +FILE=Qwen3.8-27B-Q5_K_M.gguf +URL="https://hf-mirror.com/bartowski/Qwen3.8-27B-GGUF/resolve/main/$FILE" +EXPECT=20752787040 +for i in $(seq 1 60); do + echo "[$(date '+%F %T')] --- attempt $i ---" + curl -sL -C - --retry 5 --retry-delay 5 --connect-timeout 30 -o "$MODEL_DIR/$FILE" "$URL" + SZ=$(stat -c %s "$MODEL_DIR/$FILE" 2>/dev/null || echo 0) + if [ "$SZ" = "$EXPECT" ]; then echo "[$(date '+%F %T')] DOWNLOAD_COMPLETE size=$SZ"; exit 0; fi + echo "[$(date '+%F %T')] incomplete: $SZ / $EXPECT" + sleep 5 +done +echo "DOWNLOAD_FAILED"; exit 1 diff --git a/deploy-scripts/ggufmeta.py b/deploy-scripts/ggufmeta.py new file mode 100644 index 0000000..af37ef2 --- /dev/null +++ b/deploy-scripts/ggufmeta.py @@ -0,0 +1,32 @@ +import struct,sys +f=open(sys.argv[1],'rb') +def rd(n): return f.read(n) +def u32(): return struct.unpack('' + if t==10: return u64() + if t==11: return struct.unpack('/dev/null || true +echo "[restore] 清理残留 llm..." +docker rm -f llm 2>/dev/null || true +echo "[restore] 用回滚镜像重建 llm..." +docker run -d --name llm --restart unless-stopped --gpus all \ + -p 8000:8000 -v /data/models:/jsxc \ + llamacpp-rollback:b9570 \ + -m /jsxc/Qwen/Qwen3.6-27B-GGUF/Qwen_Qwen3.6-27B-IQ4_XS.gguf \ + --host 0.0.0.0 --port 8000 --seed 3407 \ + --temp 0.7 --top-p 0.95 --min-p 0.01 --top-k 40 \ + --jinja --batch-size 4096 --ubatch-size 1024 \ + --ctx-size 131072 --no-context-shift +echo "[restore] 等待健康..." +for i in $(seq 1 60); do + if curl -sf http://127.0.0.1:8000/health >/dev/null 2>&1; then echo "[restore] OK 已恢复"; exit 0; fi + sleep 10 +done +echo "[restore] 超时,请查 docker logs llm"; exit 1 diff --git a/h3-console/DESIGN.md b/h3-console/DESIGN.md new file mode 100644 index 0000000..c89aaee --- /dev/null +++ b/h3-console/DESIGN.md @@ -0,0 +1,188 @@ +# H3 Console 需求 / 设计文档(草案,待确认) + +给 MiniMax-H3 视频生成加一个 Web 管理台:上传 → 生成 → 预览,外加模型/资产/任务/调度的统一视图。 +本文档先定范围和方案,**代码还没写**,确认后再动手。 + +--- + +## 一、现状(已有什么,不用重建) + +服务器 `/data/models/minimax-h3/` 已经有一套两层服务,本目录 console 是**加在它前面的第三层**,不是替换: + +``` +浏览器 + │ + ▼ +[新增] H3 Console(Web UI + 上传/资产/任务持久化) ← 本文档要建的东西 + │ 内部调用 + ▼ +gateway.py(已有,端口 8000) ← 四后端调度:选最闲的、按 fl2va/ref2va 分流、429 顶回去 + │ + ├─ api.py @ 8010 (GPU1, ref2va) + ├─ api.py @ 8011 (GPU2, ref2va) + ├─ api.py @ 8012 (GPU0, ref2va) + └─ api.py @ 8013 (GPU3, ref2va) +``` + +`gateway.py` 已经做了:调度(选队列最短的健康后端)、分区路由(有首末帧→fl2va,否则→ref2va)、 +限流(`H3_MAX_QUEUE`,满了直接 429)、健康聚合、任务查询/下载代理。**这部分不重做。** + +`gateway.py` **没有**:Web 界面、文件上传(现在参考图/首末帧只接受 URL,`_fetch_image` 内部用 +`urllib.request.urlopen` 拉取)、任务持久化(`JOBS` 是内存 dict,进程重启就丢,`API.md` 里写明了)、 +模型/资产的统一视图。这些是 console 要补的。 + +**一个关键约束,直接决定架构**:四个 `api.py` 后端**现在全部锁死在 `ref2va` 分区** +(`start-all.sh` 写死 `H3_PARTITION=ref2va` ×4),**没有一个在跑 `fl2va`**。 +所以你要的三个界面里,"上传首尾帧生成视频"这个功能**现在提交上去会 503** +(gateway.py: `没有就绪的 fl2va 实例`)。这个必须先决定怎么办,见下面「需要你拍板」。 + +--- + +## 二、范围 + +### 三个生成界面 +1. **文生视频**:输入 prompt + 参数(分辨率/帧数/步数/turbo/seed)→ 提交 → 走 ref2va(不给参考图退回纯文生)。 +2. **首尾帧生视频**(FL2VA):上传首帧图(+可选尾帧)→ 提交 → 走 fl2va。 +3. **多参考图生视频**(Ref2VA):上传任意多张参考图 → 提交 → 走 ref2va。 + +### 统一管理视图 +- **任务列表**:状态(排队/运行/完成/失败)、进度、耗时、峰值显存、缩略图,点开内联播放 `