MiniMax H3 本地部署指南(ComfyUI + RTX 5070 12G)

2 阅读12分钟

MiniMax H3 本地部署指南(ComfyUI + RTX 5070 12G)

本文档记录了在 Windows 11 + RTX 5070 12GB 环境下,从零部署 MiniMax H3 视频生成模型并产出烹饪视频的完整过程。所有步骤均可复刻。


目录

  1. 环境概述
  2. 前置条件
  3. 一键部署
  4. PyTorch cu130 升级(关键)
  5. 模型下载说明
  6. 启动 ComfyUI
  7. 工作流说明
  8. API 调用方式
  9. 批量生成:干锅虾 14 镜头
  10. 示例效果
  11. 常见问题
  12. 附录 A:关键文件路径速查
  13. 附录 B:完整脚本代码

1. 环境概述

项目配置
操作系统Windows 11 Home China
GPUNVIDIA GeForce RTX 5070 12GB(Blackwell 架构,CC 12.0)
Python3.12.10
PyTorch2.13.0+cu130
ComfyUI0.33.0
模型格式NVFP4(Blackwell 原生支持,12.5 GB)

为什么选 NVFP4? RTX 5070 是 Blackwell 架构(sm_120),只有 cu130 版本的 PyTorch 编译了对应的 CUDA 内核。NVFP4 量化格式在保证画质的前提下将扩散模型从 66 GB(bf16)压缩到 12.5 GB,是 12GB 显存唯一可行的选择。


2. 前置条件

2.1 安装 Python 3.12

  • 下载地址:www.python.org/downloads/
  • 安装时勾选 Add Python to PATH
  • 推荐安装路径:C:\Users\<用户名>\AppData\Local\Programs\Python\Python312\

验证:

python --version
# Python 3.12.10

2.2 安装 Git

验证:

git --version
# git version 2.53.0

2.3 确认 GPU 驱动

  • 安装 NVIDIA 最新驱动(>= 580),支持 CUDA 13.0
  • 在 NVIDIA 控制面板确认 GPU 已被系统识别

3. 一键部署

路径约定:本文档假设你的项目根目录为 d:\AI\minimax-h3\。如果你使用其他路径(如 D:\projects\minimax-h3\),请将文中所有 d:\AI\minimax-h3\ 替换为你的实际路径。deploy_h3.ps1 中的 $PSScriptRoot 会自动识别脚本所在目录,无需手动修改。

项目根目录 d:\AI\minimax-h3\ 下包含完整的部署脚本。

3.1 目录结构

d:\AI\minimax-h3\
├── deploy_h3.ps1          # 部署脚本(环境检查 + 依赖安装 + 模型下载)
├── start_h3.bat           # 启动脚本
├── t2v_workflow.json       # 单镜头 T2V 工作流模板
├── batch_generate.py       # 批量生成脚本(14 镜头)
├── ComfyUI/                # ComfyUI 主程序
│   ├── venv/               # Python 虚拟环境
│   ├── models/
│   │   ├── diffusion_models/
│   │   ├── text_encoders/
│   │   └── vae/
│   └── output/video/       # 视频输出目录
└── README.md

3.2 执行部署

在 PowerShell 中运行:

cd d:\AI\minimax-h3
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -Force
.\deploy_h3.ps1

部署脚本执行 9 个步骤:

步骤说明耗时
1/9检查 Python 和 Git 环境< 5s
2/9克隆/更新 ComfyUI~30s
3/9创建 Python 虚拟环境~10s
4/9安装 PyTorch cu130~15 min(取决于网速)
5/9安装 ComfyUI 依赖~2 min
6/9安装 huggingface_hub + sageattention~1 min
7/9创建模型目录< 1s
8/9下载模型文件(ModelScope 国内镜像)~35 min
9/9完成-

重要提示deploy_h3.ps1 已配置使用 cu130 源安装 PyTorch。如果部署脚本安装的是 cu126 版本,请参考第 4 节手动升级。


4. PyTorch cu130 升级(关键)

4.1 为什么需要升级

RTX 5070 是 Blackwell 架构(Compute Capability 12.0,即 sm_120)。PyTorch cu126 版本只编译到 sm_90,运行时会报错:

CUDA error: no kernel image is available for execution on the device

必须使用 cu130 版本的 PyTorch。

4.2 升级方法

方法 A:pip 直接安装(推荐,但国内下载慢)

cd d:\AI\minimax-h3\ComfyUI
.\venv\Scripts\python.exe -m pip install torch torchvision torchaudio `
    --index-url https://download.pytorch.org/whl/cu130 `
    --force-reinstall --no-deps --retries 10 --timeout 120

方法 B:多线程下载加速(国内推荐)

如果 pip 下载速度太慢(< 1 MB/s),可用 Python 多线程脚本下载 wheel 文件后本地安装。以下脚本使用 8 线程并行下载,速度从 0.2 MB/s 提升到 3.0 MB/s:

# download_torch_mt.py 核心逻辑
# 1. HEAD 请求获取文件总大小
# 2. 将文件分成 8 块,每块用独立线程下载
# 3. 支持 Range header 断点续传
# 4. 下载完成后合并所有分块

import urllib.request, threading, os

url = "https://download.pytorch.org/whl/cu130/torch-2.13.0%2Bcu130-cp312-cp312-win_amd64.whl"
num_threads = 8
# ... 分块下载 + 合并逻辑

下载完成后本地安装:

cd d:\AI\minimax-h3\ComfyUI
.\venv\Scripts\python.exe -m pip install --force-reinstall --no-deps `
    "d:\AI\minimax-h3\torch-2.13.0+cu130-cp312-cp312-win_amd64.whl"
.\venv\Scripts\python.exe -m pip install --force-reinstall --no-deps `
    "d:\AI\minimax-h3\torchvision-0.28.0+cu130-cp312-cp312-win_amd64.whl" `
    "d:\AI\minimax-h3\torchaudio-2.11.0+cu130-cp312-cp312-win_amd64.whl"

4.3 验证

cd d:\AI\minimax-h3\ComfyUI
.\venv\Scripts\python.exe -c "import torch; print('PyTorch:', torch.__version__); print('CUDA:', torch.cuda.is_available()); print('Device:', torch.cuda.get_device_name(0)); print('CC:', torch.cuda.get_device_capability(0)); x = torch.randn(3,3, device='cuda'); print('Tensor test:', x.sum().item())"

预期输出:

PyTorch: 2.13.0+cu130
CUDA available: True
Device: NVIDIA GeForce RTX 5070
CC: (12, 0)
CUDA tensor test: -0.869...

5. 模型下载说明

5.1 模型清单

模型文件大小用途来源
MiniMax_H3_FL2VA_pruned_nvfp4.safetensors11.67 GB扩散模型(NVFP4 量化)ModelScope Abiray 仓库
qwen3vl_32b_minimax_h3_nvfp4_awq.safetensors14.61 GB文本编码器(Qwen3-VL 32B)ModelScope Comfy-Org 仓库
minimax_h3_video_vae_fp16.safetensors4.85 GB视频 VAEModelScope Comfy-Org 仓库
minimax_h3_audio_vae_fp32.safetensors0.56 GB音频 VAEModelScope Comfy-Org 仓库

总计约 31.7 GB

5.2 ModelScope 国内镜像

模型从 ModelScope(魔搭社区)下载,国内速度约 15 MB/s(相比 HuggingFace 的 0.18 MB/s 快 80 倍)。

下载 URL 格式:

https://www.modelscope.cn/models/{model_id}/resolve/master/{file_path}

具体 URL:

# 扩散模型
https://www.modelscope.cn/models/Abiray/Minimax-H3-nvfp4-INT4-INT8-Convrot/resolve/master/MiniMax_H3_FL2VA_pruned_nvfp4.safetensors

# 文本编码器
https://www.modelscope.cn/models/Comfy-Org/MiniMax-H3/resolve/master/text_encoders/qwen3vl_32b_minimax_h3_nvfp4_awq.safetensors

# 视频 VAE
https://www.modelscope.cn/models/Comfy-Org/MiniMax-H3/resolve/master/vae/minimax_h3_video_vae_fp16.safetensors

# 音频 VAE
https://www.modelscope.cn/models/Comfy-Org/MiniMax-H3/resolve/master/vae/minimax_h3_audio_vae_fp32.safetensors

5.3 模型放置路径

ComfyUI/
└── models/
    ├── diffusion_models/
    │   └── MiniMax_H3_FL2VA_pruned_nvfp4.safetensors
    ├── text_encoders/
    │   └── qwen3vl_32b_minimax_h3_nvfp4_awq.safetensors
    └── vae/
        ├── minimax_h3_video_vae_fp16.safetensors
        └── minimax_h3_audio_vae_fp32.safetensors

5.4 断点续传

部署脚本使用 curl -C - 实现断点续传,支持网络中断后自动重试(最多 30 次)。已完整下载的文件会通过 Content-Length 对比自动跳过。


6. 启动 ComfyUI

6.1 双击启动

双击 start_h3.bat,等待控制台显示:

To see the GUI go to: http://127.0.0.1:8188

6.2 启动参数说明

python main.py --disable-pinned-memory --lowvram
参数作用
--disable-pinned-memory规避 Windows 下 pinned memory 加载缓慢问题
--lowvram低显存模式,模型动态加载/卸载(12GB 必须开启)

注意:不要使用 --use-sage-attention,因为 Windows 上缺少 triton 依赖会导致启动失败。

6.3 命令行启动

cd d:\AI\minimax-h3\ComfyUI
.\venv\Scripts\python.exe main.py --disable-pinned-memory --lowvram

浏览器打开 http://127.0.0.1:8188 即可使用 ComfyUI 界面。


7. 工作流说明

7.1 节点结构

MiniMax H3 T2V(文本到视频)工作流由以下节点组成:

UNETLoader ──┐
             ├──► BasicGuider ──┐
CLIPLoader ──┤                   ├──► SamplerCustomAdvanced ──► VAEDecode ──┐
             │                   │                               VAEDecodeAudio ──┤
MiniMaxH3ImageToVideo ──────────┘                                                ├──► CreateVideo ──► SaveVideo
  (prompt + width + height + length)

7.2 关键参数

参数说明推荐值
unet_name扩散模型文件名MiniMax_H3_FL2VA_pruned_nvfp4.safetensors
clip_name文本编码器文件名qwen3vl_32b_minimax_h3_nvfp4_awq.safetensors
type (CLIPLoader)CLIP 类型minimax
vae_name (视频)视频 VAEminimax_h3_video_vae_fp16.safetensors
vae_name (音频)音频 VAEminimax_h3_audio_vae_fp32.safetensors
width / height视频分辨率864 x 480(0.4 MP)
length帧数(24fps,需符合 17k+5 网格)124(~5s)/ 209(~8.7s)
steps采样步数20
sampler_name采样器res_multistep
scheduler调度器simple
fps (CreateVideo)输出帧率24

7.3 帧数网格说明

MiniMax H3 要求帧数符合 17k + 5 网格:

帧数时长说明
56~2.3s最短(低于训练范围,效果不稳定)
124~5.2s推荐最短时长
209~8.7s加长版本(显存允许时使用)
362~15.1s最长训练范围

计算公式:length = 17 * k + 5(k 为正整数)

7.4 界面中使用

  1. 打开 http://127.0.0.1:8188
  2. 点击右侧面板 WorkflowBrowse TemplatesVideoMiniMax H3 T2V
  3. 修改 MiniMaxH3ImageToVideo 节点中的 prompt 文本框
  4. 选择正确的模型文件(各 Loader 节点)
  5. 点击 Queue Prompt 开始生成

8. API 调用方式

8.1 提交工作流

# 读取工作流 JSON 并提交
$workflow = Get-Content "d:\AI\minimax-h3\t2v_workflow.json" -Raw | ConvertFrom-Json
$body = @{ prompt = $workflow } | ConvertTo-Json -Depth 20 -Compress
$resp = Invoke-RestMethod -Uri "http://127.0.0.1:8188/prompt" -Method Post -ContentType "application/json" -Body $body
$resp | ConvertTo-Json

返回:

{
    "prompt_id": "a3b6f143-d23c-46bc-a68c-0e6366d5ff18",
    "number": 0,
    "node_errors": {}
}

8.2 查询队列状态

Invoke-RestMethod -Uri "http://127.0.0.1:8188/queue"
# {"queue_running": [...], "queue_pending": [...]}

8.3 查询生成历史

Invoke-RestMethod -Uri "http://127.0.0.1:8188/history/{prompt_id}"

8.4 工作流 JSON 模板

完整的 API 格式工作流保存在 t2v_workflow.json,可直接修改 promptwidthheightlengthnoise_seed 字段后提交。


9. 批量生成:干锅虾 14 镜头

9.1 设计思路

针对 12GB 显存限制,采用以下策略:

  • 每个镜头独立生成,避免显存溢出
  • 帧数设为 209(~8.7 秒),比默认 124 帧更长
  • 分辨率 864x480(0.4 MP),保证显存充足
  • 食材和锅具描述全程统一(黑色铁锅、大虾、干红辣椒等)
  • 14 个任务一次性提交,ComfyUI 自动排队执行

9.2 镜头清单

序号镜头名称Prompt 要点
01淘洗大虾不锈钢碗中清水冲洗半透明灰虾,气泡上升
02剪虾开背剪刀修剪虾须虾枪,刀开背去虾线
03沥干装盘处理干净的虾排列在白瓷盘上,水珠闪烁
04腌制抓匀撒盐、料酒、白胡椒粉,手抓匀腌制
05下锅煎制黑铁锅油温升温,虾逐个平铺入锅
06煎至酥脆虾从灰色变为橙红色,外壳酥脆金黄
07捞出控油漏勺捞出煎好的虾,油滴落回锅中
08备菜切配切洋葱、青椒、蒜末、干辣椒
09爆香配菜黑铁锅少油下配菜,炒出香味和蒸汽
10大火翻炒虾回锅,淋调料,颠勺翻拌,食材飞起
11锅气融合虾和配菜翻滚,酱汁包裹均匀,锅气十足
12撒芝麻葱花白芝麻和葱花从上方撒落,落在光泽虾面上
13出锅装盘从铁锅转移到铸铁盘,摆盘点缀
14成品特写慢镜头特写,热气升腾,酱汁反光

9.3 运行批量生成

cd d:\AI\minimax-h3
.\ComfyUI\venv\Scripts\python.exe batch_generate.py

脚本会:

  1. 依次构建 14 个工作流(不同 prompt + seed)
  2. 提交到 ComfyUI API(间隔 1 秒)
  3. 所有任务自动排队执行
  4. 视频输出到 ComfyUI/output/video/gan_guo_xia/

9.4 生成耗时

参数
单镜头采样20 步 x ~22s/步 = ~7 min
单镜头 VAE 解码~1.5 min
单镜头总计~8.5 min
14 镜头总计~2 小时

9.5 自定义修改

编辑 batch_generate.py 中的 SHOTS 列表,修改 prompt 字段即可生成不同内容。调整 build_workflow() 的参数可修改分辨率和帧数:

def build_workflow(prompt_text, seed, filename_prefix, width=864, height=480, length=209):
    # 修改 width/height 调整分辨率
    # 修改 length 调整时长(需符合 17k+5 网格)

10. 示例效果

10.1 测试视频

以下视频均在本环境(RTX 5070 12GB + PyTorch cu130 + NVFP4 模型)下生成:

文件名Prompt 摘要分辨率帧数时长大小
MiniMax_H3_t2v_00001_.mp4山湖晨景:金色阳光穿透云层,薄雾从水面升起,鸟群飞过864x480124~5s0.6 MB
MiniMax_H3_t2v_00002_.mp4干锅虾翻炒:铁锅大火翻炒,辣椒蒜瓣飞溅,虾变金黄864x480124~5s1.4 MB

视频文件路径:

  • d:\AI\minimax-h3\ComfyUI\output\video\MiniMax_H3_t2v_00001_.mp4
  • d:\AI\minimax-h3\ComfyUI\output\video\MiniMax_H3_t2v_00002_.mp4

10.2 干锅虾系列(14 镜头批量生成)

输出目录:d:\AI\minimax-h3\ComfyUI\output\video\gan_guo_xia\

文件名镜头分辨率帧数时长大小
01_shrimp_wash_00001_.mp4淘洗大虾864x480209~8.7s2.1 MB
02_shrimp_trim_00001_.mp4剪虾开背864x480209~8.7s1.3 MB
03_shrimp_plate_00001_.mp4沥干装盘864x480209~8.7s0.9 MB
04-14(后续镜头依次生成)864x480209~8.7s~1-2 MB

14 个镜头可导入剪映/Premiere,配以温柔 BGM,按顺序拼接即可得到完整的干锅虾烹饪教学视频(约 2 分钟)。

10.3 生成性能数据

指标124 帧(~5s)209 帧(~8.7s)
采样速度~8s/步~22s/步
采样总耗时~2.6 min~7 min
VAE 解码~0.5 min~1.5 min
单镜头总耗时~3.4 min~8.5 min
显存峰值~10 GB~11.5 GB

10.4 Prompt 设计技巧

干锅虾烹饪视频的 Prompt 设计原则:

  1. 食材一致性:所有镜头中虾的描述统一为 "translucent gray shrimp"(生虾)或 "golden-orange crispy shrimp"(熟虾),不可混用
  2. 锅具一致性:全程使用 "black iron wok"(黑色铁锅),不可在某个镜头变成不粘锅
  3. 配菜一致性:统一描述 "purple onions, green bell peppers, garlic, dried red chilies"
  4. 镜头语言:每个镜头开头标明景别(Extreme close-up / Close-up / Wide shot / Dynamic shot)
  5. 氛围词:统一使用 "cinematic lighting, food photography, warm tones" 保持整体调性
  6. 动态描述:用具体动作词(sizzling, tossing, drizzling, sprinkling)让模型理解运动方向

11. 常见问题

Q1: CUDA error: no kernel image is available

原因:PyTorch 版本不匹配 GPU 架构。RTX 5070 是 Blackwell(sm_120),需要 cu130。

解决:参考第 4 节升级 PyTorch 到 cu130。

Q2: sageattention 启动报错(缺少 triton)

原因:sageattention 依赖 triton,Windows 上不兼容。

解决:启动时不要加 --use-sage-attention 参数。start_h3.bat 已移除该参数。

Q3: 模型下载速度极慢

原因:HuggingFace 服务器在国外,国内访问速度约 0.18 MB/s。

解决:使用 ModelScope 国内镜像(部署脚本已默认配置),速度约 15 MB/s。

Q4: 生成视频时 OOM(显存不足)

原因:12GB 显存运行 12.5 GB 模型比较紧张。

解决

  • 确保启动时加了 --lowvram 参数
  • 降低分辨率(如 768x432)
  • 减少帧数(如 124 而非 209)
  • 关闭其他占用显存的程序

Q5: Python 不在 PATH 中

解决:部署脚本中的 Find-Python 函数会自动从注册表和常见路径搜索 Python。如果仍找不到,请手动将 Python 安装路径添加到系统 PATH 环境变量。

Q6: 帧数不符合 17k+5 网格

原因:MiniMax H3 模型要求帧数为 17k+5(5, 22, 39, 56, 73, 90, ... 124, 141, ... 209, ...)。

解决:使用公式 length = 17 * k + 5 计算合适的帧数。推荐值:124(~5s)、209(~8.7s)、362(~15s)。

Q7: 如何生成不同内容的视频

方法 A(界面):在 ComfyUI 界面中修改 MiniMaxH3ImageToVideo 节点的 prompt 文本框。

方法 B(API):修改 t2v_workflow.json 中的 prompt 字段,然后通过 API 提交。

方法 C(批量):编辑 batch_generate.py 中的 SHOTS 列表,添加自定义镜头。

Q8: PyTorch 安装后 pip 显示 "Requirement already satisfied"

原因:cu126 和 cu130 的版本号相同(2.13.0),pip 认为已安装而跳过。

解决:加 --force-reinstall --no-deps 参数强制重装:

.\venv\Scripts\python.exe -m pip install torch==2.13.0 --index-url https://download.pytorch.org/whl/cu130 --force-reinstall --no-deps

附录 A:关键文件路径速查

文件路径
部署脚本d:\AI\minimax-h3\deploy_h3.ps1
启动脚本d:\AI\minimax-h3\start_h3.bat
工作流模板d:\AI\minimax-h3\t2v_workflow.json
批量生成脚本d:\AI\minimax-h3\batch_generate.py
ComfyUI 主程序d:\AI\minimax-h3\ComfyUI\main.py
虚拟环境 Pythond:\AI\minimax-h3\ComfyUI\venv\Scripts\python.exe
扩散模型d:\AI\minimax-h3\ComfyUI\models\diffusion_models\MiniMax_H3_FL2VA_pruned_nvfp4.safetensors
文本编码器d:\AI\minimax-h3\ComfyUI\models\text_encoders\qwen3vl_32b_minimax_h3_nvfp4_awq.safetensors
视频 VAEd:\AI\minimax-h3\ComfyUI\models\vae\minimax_h3_video_vae_fp16.safetensors
音频 VAEd:\AI\minimax-h3\ComfyUI\models\vae\minimax_h3_audio_vae_fp32.safetensors
视频输出目录d:\AI\minimax-h3\ComfyUI\output\video\

附录 B:完整脚本代码

以下为项目中所有脚本的完整源码,可直接复制使用。

B.1 deploy_h3.ps1(部署脚本)

# ============================================================
# MiniMax H3 部署脚本(RTX 5070 12G)
# 改进:自动查找 Python/Git、全路径调用、错误处理、支持重复运行
# ============================================================

$ErrorActionPreference = "Stop"

$ROOT     = $PSScriptRoot
$comfyDir = Join-Path $ROOT "ComfyUI"

function Test-PythonWorks($exe) {
    if (-not $exe -or -not (Test-Path $exe)) { return $false }
    $prev = $ErrorActionPreference
    $ErrorActionPreference = "SilentlyContinue"
    & $exe -c "import encodings" 2>$null
    $code = $LASTEXITCODE
    $ErrorActionPreference = $prev
    return ($code -eq 0)
}

function Find-Python {
    $candidates = @()
    # 从注册表查找(优先高版本)
    foreach ($hive in @("HKLM:\SOFTWARE\Python\PythonCore", "HKCU:\SOFTWARE\Python\PythonCore")) {
        $keys = Get-ChildItem $hive -ErrorAction SilentlyContinue | Sort-Object PSChildName -Descending
        foreach ($k in $keys) {
            $p = (Get-ItemProperty "$($k.PSPath)\InstallPath" -ErrorAction SilentlyContinue)."(default)"
            if ($p) { $candidates += (Join-Path $p "python.exe") }
        }
    }
    # 常见安装路径
    $candidates += @(
        "$env:LOCALAPPDATA\Programs\Python\Python313\python.exe",
        "$env:LOCALAPPDATA\Programs\Python\Python312\python.exe",
        "$env:LOCALAPPDATA\Programs\Python\Python311\python.exe",
        "C:\Python314\python.exe", "C:\Python313\python.exe", "C:\Python312\python.exe", "C:\Python311\python.exe"
    )
    # 返回第一个能正常工作的 Python(跳过残缺安装)
    foreach ($c in $candidates) {
        if (Test-PythonWorks $c) { return $c }
    }
    return $null
}

function Find-Git {
    if (Get-Command git -ErrorAction SilentlyContinue) { return "git" }
    foreach ($p in @("C:\Program Files\Git\cmd\git.exe", "C:\Program Files (x86)\Git\cmd\git.exe")) {
        if (Test-Path $p) { return $p }
    }
    return $null
}

# === 1. 检查环境 ===
Write-Host "`n[1/9] 检查环境依赖..." -ForegroundColor Cyan

$pythonExe = Find-Python
if (-not $pythonExe) {
    Write-Host "  [X] 未找到 Python!请安装 Python 3.11-3.14 并加入 PATH" -ForegroundColor Red
    Write-Host "      下载: https://www.python.org/downloads/" -ForegroundColor Yellow
    exit 1
}
$pyVer = & $pythonExe --version 2>&1
Write-Host "  [OK] $pyVer  ($pythonExe)" -ForegroundColor Green

$gitExe = Find-Git
if (-not $gitExe) {
    Write-Host "  [X] 未找到 Git!请安装 Git 并加入 PATH" -ForegroundColor Red
    Write-Host "      下载: https://git-scm.com/download/win" -ForegroundColor Yellow
    exit 1
}
Write-Host "  [OK] $(& $gitExe --version)  ($gitExe)" -ForegroundColor Green

# === 2. 克隆/更新 ComfyUI ===
Write-Host "`n[2/9] 克隆 ComfyUI..." -ForegroundColor Cyan
if (Test-Path (Join-Path $comfyDir ".git")) {
    Write-Host "  ComfyUI 已存在,拉取最新代码..." -ForegroundColor Yellow
    & $gitExe -C $comfyDir pull --quiet
} else {
    & $gitExe clone https://github.com/comfyanonymous/ComfyUI.git $comfyDir
}
if ($LASTEXITCODE -ne 0) { Write-Host "  [!] ComfyUI 更新失败(网络问题),使用本地已有版本继续" -ForegroundColor Yellow }
Write-Host "  [OK] ComfyUI 就绪" -ForegroundColor Green

# === 3. 创建虚拟环境 ===
Write-Host "`n[3/9] 创建虚拟环境..." -ForegroundColor Cyan
$venvPython = Join-Path $comfyDir "venv\Scripts\python.exe"
if (Test-Path $venvPython) {
    Write-Host "  虚拟环境已存在,跳过创建" -ForegroundColor Yellow
} else {
    & $pythonExe -m venv (Join-Path $comfyDir "venv")
    if ($LASTEXITCODE -ne 0) { Write-Host "  [X] 虚拟环境创建失败" -ForegroundColor Red; exit 1 }
}
Write-Host "  [OK] 虚拟环境就绪" -ForegroundColor Green

# === 4. 安装 PyTorch ===
Write-Host "`n[4/9] 安装 PyTorch (CUDA 13.0 - RTX 5070 Blackwell)..." -ForegroundColor Cyan
& $venvPython -m pip install --upgrade pip
& $venvPython -m pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu130 --retries 10 --timeout 120
if ($LASTEXITCODE -ne 0) { Write-Host "  [X] PyTorch 安装失败" -ForegroundColor Red; exit 1 }
Write-Host "  [OK] PyTorch 安装完成" -ForegroundColor Green

# === 5. 安装 ComfyUI 依赖 ===
Write-Host "`n[5/9] 安装 ComfyUI 依赖..." -ForegroundColor Cyan
& $venvPython -m pip install -r (Join-Path $comfyDir "requirements.txt")
if ($LASTEXITCODE -ne 0) { Write-Host "  [X] ComfyUI 依赖安装失败" -ForegroundColor Red; exit 1 }
Write-Host "  [OK] 依赖安装完成" -ForegroundColor Green

# === 6. 安装额外工具 ===
Write-Host "`n[6/9] 安装 huggingface_hub + sageattention..." -ForegroundColor Cyan
& $venvPython -m pip install -U "huggingface_hub[cli]"
if ($LASTEXITCODE -ne 0) { Write-Host "  [X] huggingface_hub 安装失败" -ForegroundColor Red; exit 1 }
& $venvPython -m pip install sageattention
if ($LASTEXITCODE -ne 0) { Write-Host "  [X] sageattention 安装失败" -ForegroundColor Red; exit 1 }
Write-Host "  [OK] 额外工具安装完成" -ForegroundColor Green

# === 7. 创建模型目录 ===
Write-Host "`n[7/9] 创建模型目录..." -ForegroundColor Cyan
$diffusionDir = Join-Path $comfyDir "models\diffusion_models"
$textEncDir   = Join-Path $comfyDir "models\text_encoders"
$vaeDir       = Join-Path $comfyDir "models\vae"
@($diffusionDir, $textEncDir, $vaeDir) | ForEach-Object { New-Item -ItemType Directory -Path $_ -Force | Out-Null }
Write-Host "  [OK] 目录创建完成" -ForegroundColor Green

# === 8. 下载模型(ModelScope 国内镜像 + curl 断点续传)===
$curlExe = "C:\Windows\System32\curl.exe"
$msComfy   = "https://www.modelscope.cn/models/Comfy-Org/MiniMax-H3/resolve/master"
$msAbiray  = "https://www.modelscope.cn/models/Abiray/Minimax-H3-nvfp4-INT4-INT8-Convrot/resolve/master"
$prevEAP = $ErrorActionPreference
$ErrorActionPreference = "Continue"

function Download-File {
    param($url, $outPath, $maxRetries = 30)
    # 已下载完整则跳过(通过 Content-Length 对比)
    $needDownload = $true
    if (Test-Path $outPath) {
        $localSize = (Get-Item $outPath).Length
        $remoteSize = $null
        try {
            $resp = & $curlExe -sI -L $url 2>$null
            if ($resp -match 'Content-Length:\s*(\d+)') { $remoteSize = [int64]$Matches[1] }
        } catch {}
        if ($remoteSize -and $localSize -ge $remoteSize) {
            $sz = [math]::Round($localSize / 1GB, 2)
            Write-Host " [OK] 已存在 $sz GB" -ForegroundColor Green
            return $true
        }
    }
    for ($i = 1; $i -le $maxRetries; $i++) {
        & $curlExe -L -s -C - -o $outPath $url 2>&1 | Out-Null
        if ($LASTEXITCODE -eq 0 -and (Test-Path $outPath) -and (Get-Item $outPath).Length -gt 0) {
            $sz = [math]::Round((Get-Item $outPath).Length / 1GB, 2)
            Write-Host " [OK] $sz GB" -ForegroundColor Green
            return $true
        }
        $got = if (Test-Path $outPath) { [math]::Round((Get-Item $outPath).Length / 1MB, 0) } else { 0 }
        Write-Host " [!] 中断 ($got MB), 重试 $i/$maxRetries..." -ForegroundColor Yellow
        Start-Sleep -Seconds 2
    }
    return $false
}

Write-Host "`n[8/9] 下载模型文件(ModelScope 国内镜像,断点续传)..." -ForegroundColor Cyan

# [1/4] 扩散模型 NVFP4(Blackwell 架构专用,RTX 5070 适用)
Write-Host "  [1/4] 扩散模型 FL2VA NVFP4 pruned (~12.5 GB)" -ForegroundColor Yellow
$diffFile = "MiniMax_H3_FL2VA_pruned_nvfp4.safetensors"
Write-Host "    -> $diffFile" -NoNewline
$url = "$msAbiray/$diffFile"
if (-not (Download-File $url (Join-Path $diffusionDir $diffFile))) {
    Write-Host "  [X] 扩散模型下载失败" -ForegroundColor Red; $ErrorActionPreference = $prevEAP; exit 1
}
Write-Host "  [OK] 扩散模型完成`n" -ForegroundColor Green

# [2/4] 文本编码器 NVFP4 AWQ
Write-Host "  [2/4] 文本编码器 NVFP4 AWQ (~15.7 GB)" -ForegroundColor Yellow
$teFile = "qwen3vl_32b_minimax_h3_nvfp4_awq.safetensors"
Write-Host "    -> $teFile" -NoNewline
$url = "$msComfy/text_encoders/$teFile"
if (-not (Download-File $url (Join-Path $textEncDir $teFile))) {
    Write-Host "  [X] 文本编码器下载失败" -ForegroundColor Red; $ErrorActionPreference = $prevEAP; exit 1
}
Write-Host "  [OK] 完成`n" -ForegroundColor Green

# [3/4] 视频 VAE
Write-Host "  [3/4] 视频 VAE FP16 (~5.2 GB)" -ForegroundColor Yellow
$vaeFile1 = "minimax_h3_video_vae_fp16.safetensors"
Write-Host "    -> $vaeFile1" -NoNewline
$url = "$msComfy/vae/$vaeFile1"
if (-not (Download-File $url (Join-Path $vaeDir $vaeFile1))) {
    Write-Host "  [X] 视频 VAE 下载失败" -ForegroundColor Red; $ErrorActionPreference = $prevEAP; exit 1
}
Write-Host "  [OK] 完成`n" -ForegroundColor Green

# [4/4] 音频 VAE
Write-Host "  [4/4] 音频 VAE FP32 (~0.6 GB)" -ForegroundColor Yellow
$vaeFile2 = "minimax_h3_audio_vae_fp32.safetensors"
Write-Host "    -> $vaeFile2" -NoNewline
$url = "$msComfy/vae/$vaeFile2"
if (-not (Download-File $url (Join-Path $vaeDir $vaeFile2))) {
    Write-Host "  [X] 音频 VAE 下载失败" -ForegroundColor Red; $ErrorActionPreference = $prevEAP; exit 1
}
Write-Host "  [OK] 完成" -ForegroundColor Green
$ErrorActionPreference = $prevEAP

# === 9. 完成 ===
Write-Host "`n[9/9] 部署完成!" -ForegroundColor Green
Write-Host "`n启动方式:双击 start_h3.bat,浏览器打开 http://127.0.0.1:8188" -ForegroundColor Cyan

B.2 start_h3.bat(启动脚本)

@echo off
chcp 65001 >nul 2>&1
title MiniMax H3 - ComfyUI (12G Optimized)
color 0A

echo ============================================
echo   MiniMax H3 启动器
echo   RTX 5070 12G 优化配置
echo ============================================
echo.

cd /d "%~dp0ComfyUI"

:: 检查 ComfyUI 是否存在
if not exist "main.py" (
    echo [X] 未找到 ComfyUI,请先运行 deploy_h3.ps1 完成安装
    pause
    exit /b 1
)

:: 检查虚拟环境
if not exist "venv\Scripts\activate.bat" (
    echo [X] 未找到虚拟环境,请先运行 deploy_h3.ps1
    pause
    exit /b 1
)

echo [1/3] 激活虚拟环境...
call venv\Scripts\activate.bat

echo [2/3] 检查模型文件...
if not exist "models\text_encoders\qwen3vl_32b_minimax_h3_nvfp4_awq.safetensors" (
    echo     [!] 文本编码器缺失,运行 deploy_h3.ps1 下载
)
if not exist "models\vae\minimax_h3_video_vae_fp16.safetensors" (
    echo     [!] 视频 VAE 缺失
)
if not exist "models\vae\minimax_h3_audio_vae_fp32.safetensors" (
    echo     [!] 音频 VAE 缺失
)

echo [3/3] 启动 ComfyUI...
echo.
echo   ------------------------------------------
echo   启动参数:
echo     --disable-pinned-memory   规避加载缓慢
echo     --lowvram                 低显存动态卸载
echo   ------------------------------------------
echo.
echo   浏览器访问: http://127.0.0.1:8188
echo   加载工作流: 工作流 ^> 浏览模板 ^> 视频 ^> MiniMax H3
echo   分辨率建议: 0.4-0.6 MP (864x480)
echo.
echo   按 Ctrl+C 停止服务
echo   ==========================================
echo.

python main.py --disable-pinned-memory --lowvram

echo.
echo ComfyUI 已停止。
pause

B.3 t2v_workflow.json(T2V 工作流模板)

{
  "6": {
    "class_type": "UNETLoader",
    "inputs": {
      "unet_name": "MiniMax_H3_FL2VA_pruned_nvfp4.safetensors",
      "weight_dtype": "default"
    }
  },
  "13": {
    "class_type": "CLIPLoader",
    "inputs": {
      "clip_name": "qwen3vl_32b_minimax_h3_nvfp4_awq.safetensors",
      "type": "minimax",
      "device": "default"
    }
  },
  "11": {
    "class_type": "VAELoader",
    "inputs": {
      "vae_name": "minimax_h3_video_vae_fp16.safetensors"
    }
  },
  "24": {
    "class_type": "VAELoader",
    "inputs": {
      "vae_name": "minimax_h3_audio_vae_fp32.safetensors"
    }
  },
  "104": {
    "class_type": "MiniMaxH3ImageToVideo",
    "inputs": {
      "clip": ["13", 0],
      "vae": ["11", 0],
      "prompt": "在此输入你的视频描述 prompt",
      "width": 864,
      "height": 480,
      "length": 124
    }
  },
  "16": {
    "class_type": "BasicGuider",
    "inputs": {
      "model": ["6", 0],
      "conditioning": ["104", 0]
    }
  },
  "17": {
    "class_type": "KSamplerSelect",
    "inputs": {
      "sampler_name": "res_multistep"
    }
  },
  "9": {
    "class_type": "BasicScheduler",
    "inputs": {
      "model": ["6", 0],
      "scheduler": "simple",
      "steps": 20,
      "denoise": 1
    }
  },
  "15": {
    "class_type": "RandomNoise",
    "inputs": {
      "noise_seed": 42
    }
  },
  "14": {
    "class_type": "SamplerCustomAdvanced",
    "inputs": {
      "noise": ["15", 0],
      "guider": ["16", 0],
      "sampler": ["17", 0],
      "sigmas": ["9", 0],
      "latent_image": ["104", 1]
    }
  },
  "10": {
    "class_type": "VAEDecode",
    "inputs": {
      "samples": ["14", 0],
      "vae": ["11", 0]
    }
  },
  "23": {
    "class_type": "VAEDecodeAudio",
    "inputs": {
      "samples": ["14", 0],
      "vae": ["24", 0]
    }
  },
  "91": {
    "class_type": "CreateVideo",
    "inputs": {
      "images": ["10", 0],
      "fps": 24,
      "audio": ["23", 0],
      "bit_depth": 8
    }
  },
  "92": {
    "class_type": "SaveVideo",
    "inputs": {
      "video": ["91", 0],
      "filename_prefix": "video/MiniMax_H3_t2v",
      "format": "auto",
      "codec": "auto"
    }
  }
}

使用方法:修改 104 节点中的 prompt(视频描述)、width/height(分辨率)、length(帧数,需符合 17k+5 网格)和 15 节点中的 noise_seed(随机种子)即可。


B.4 batch_generate.py(批量生成脚本)

import json
import urllib.request
import time
import sys

SERVER = "http://127.0.0.1:8188"

SHOTS = [
    {
        "id": "01_shrimp_wash",
        "name": "01_淘洗大虾",
        "prompt": "Extreme close-up: fresh live shrimp being rinsed in clear cold water in a stainless steel bowl. Water splashes gently over translucent gray shrimp with visible legs and long antennae. Tiny bubbles form and rise to the surface. Clean bright kitchen lighting, shallow depth of field, premium food photography style.",
    },
    {
        "id": "02_shrimp_trim",
        "name": "02_剪虾开背",
        "prompt": "Close-up: hands using small kitchen scissors to trim shrimp antennae and sharp rostrum. Then a sharp knife makes a precise shallow cut along the shrimp back, revealing and removing the dark vein. Detailed food preparation on a wooden cutting board, warm kitchen lighting, macro lens, professional technique.",
    },
    {
        "id": "03_shrimp_plate",
        "name": "03_沥干装盘",
        "prompt": "Wide overhead shot: cleaned shrimp arranged neatly in rows on a clean white ceramic plate, water droplets glistening on their translucent gray shells. A bamboo cutting board and fresh herbs nearby. Clean minimalist kitchen counter, soft natural window light, elegant food styling, muted tones.",
    },
    {
        "id": "04_marinate",
        "name": "04_腌制抓匀",
        "prompt": "Close-up of hands: sprinkling coarse sea salt, pouring golden cooking wine, and dusting white pepper powder over cleaned gray shrimp in a clear glass bowl. Hands toss and massage the shrimp to coat evenly. Marinating process, warm tones, shallow depth of field, gentle motion, appetizing.",
    },
    {
        "id": "05_oil_heat",
        "name": "05_下锅煎制",
        "prompt": "Close-up: hot oil shimmering in a black iron wok, surface rippling with heat haze. Marinated shrimp are carefully laid flat into the oil one by one using chopsticks. Oil sizzles violently upon contact, rapid bubbles forming around the shrimp. Dramatic steam, warm fire-lit ambiance, cinematic.",
    },
    {
        "id": "06_shrimp_fry",
        "name": "06_煎至酥脆",
        "prompt": "Dynamic shot: shrimp frying in hot oil in a black iron wok, gradually turning from translucent gray to vibrant orange-red. Shells become crispy and golden at the edges. Oil bubbling vigorously, wok flames visible at edges. Slow motion, dramatic warm lighting, glossy textures, premium food cinematography.",
    },
    {
        "id": "07_shrimp_lift",
        "name": "07_捞出控油",
        "prompt": "Close-up: golden-orange crispy fried shrimp being lifted out of the black iron wok with a stainless steel slotted spatula, excess oil dripping back into the wok. Shrimp shells glistening golden-orange. Light steam rising. Dark moody kitchen background, warm rim lighting, food photography.",
    },
    {
        "id": "08_prep_veg",
        "name": "08_备菜切配",
        "prompt": "Close-up montage on wooden cutting board: sharp knife slicing purple onion into strips, dicing green bell pepper, mincing fresh garlic cloves, and snipping bright red dried chilies. Quick precise knife cuts, vegetable pieces scattering on the board. Bright natural lighting, top-down view, fresh ingredients.",
    },
    {
        "id": "09_aromatics",
        "name": "09_爆香配菜",
        "prompt": "Close-up: small amount of oil heating in a black iron wok, sliced purple onions, green bell peppers, minced garlic, and bright red dried chilies tossed in. Vegetables sizzle and tumble vigorously, aromatics releasing visible steam and smoke. Wok hei flames, dramatic fire-lit scene, slow motion, rich colors.",
    },
    {
        "id": "10_toss_wok",
        "name": "10_大火翻炒",
        "prompt": "Action shot: crispy golden-orange fried shrimp tossed back into the black iron wok with sizzling vegetables. A stream of dark soy sauce is drizzled over the shrimp. Chef tosses the wok contents with a dramatic upward flip, shrimp and vegetables flying up in slow motion. Intense orange flames, wok hei, cinematic.",
    },
    {
        "id": "11_wok_hei",
        "name": "11_锅气融合",
        "prompt": "Dynamic extreme close-up: shrimp and vegetables tumbling together in the black iron wok, dark glossy sauce coating every piece evenly. Intense steam and smoke rising from the wok, orange flames licking the wok edges. Slow motion, dramatic lighting, glistening textures, premium food cinematography.",
    },
    {
        "id": "12_garnish",
        "name": "12_撒芝麻葱花",
        "prompt": "Close-up: white sesame seeds and finely chopped green scallions sprinkled from above over the finished dry pot shrimp in the black iron wok. Seeds bounce and settle on the glossy red-orange shrimp surface. Gentle steam wisps rising, warm golden lighting, macro food photography, appetizing detail.",
    },
    {
        "id": "13_plate_serve",
        "name": "13_出锅装盘",
        "prompt": "Wide shot: finished dry pot shrimp being transferred from the black iron wok to a rustic dark cast-iron serving plate. Careful plating with height and texture, shrimp piled artfully, garnished with extra scallion pieces and white sesame. Steam rising from the dish. Restaurant presentation, warm ambient lighting.",
    },
    {
        "id": "14_final_hero",
        "name": "14_成品特写",
        "prompt": "Extreme close-up slow motion: finished dry pot shrimp on a dark rustic plate, glossy red-orange sauce reflecting warm golden light. Gentle steam curling upward from crispy shrimp shells. White sesame seeds and green scallion pieces visible. Shallow depth of field, cinematic, luxurious food cinematography, ultra appetizing.",
    },
]


def build_workflow(prompt_text, seed, filename_prefix, width=864, height=480, length=209):
    return {
        "6": {
            "class_type": "UNETLoader",
            "inputs": {
                "unet_name": "MiniMax_H3_FL2VA_pruned_nvfp4.safetensors",
                "weight_dtype": "default"
            }
        },
        "13": {
            "class_type": "CLIPLoader",
            "inputs": {
                "clip_name": "qwen3vl_32b_minimax_h3_nvfp4_awq.safetensors",
                "type": "minimax",
                "device": "default"
            }
        },
        "11": {
            "class_type": "VAELoader",
            "inputs": {
                "vae_name": "minimax_h3_video_vae_fp16.safetensors"
            }
        },
        "24": {
            "class_type": "VAELoader",
            "inputs": {
                "vae_name": "minimax_h3_audio_vae_fp32.safetensors"
            }
        },
        "104": {
            "class_type": "MiniMaxH3ImageToVideo",
            "inputs": {
                "clip": ["13", 0],
                "vae": ["11", 0],
                "prompt": prompt_text,
                "width": width,
                "height": height,
                "length": length
            }
        },
        "16": {
            "class_type": "BasicGuider",
            "inputs": {
                "model": ["6", 0],
                "conditioning": ["104", 0]
            }
        },
        "17": {
            "class_type": "KSamplerSelect",
            "inputs": {
                "sampler_name": "res_multistep"
            }
        },
        "9": {
            "class_type": "BasicScheduler",
            "inputs": {
                "model": ["6", 0],
                "scheduler": "simple",
                "steps": 20,
                "denoise": 1
            }
        },
        "15": {
            "class_type": "RandomNoise",
            "inputs": {
                "noise_seed": seed
            }
        },
        "14": {
            "class_type": "SamplerCustomAdvanced",
            "inputs": {
                "noise": ["15", 0],
                "guider": ["16", 0],
                "sampler": ["17", 0],
                "sigmas": ["9", 0],
                "latent_image": ["104", 1]
            }
        },
        "10": {
            "class_type": "VAEDecode",
            "inputs": {
                "samples": ["14", 0],
                "vae": ["11", 0]
            }
        },
        "23": {
            "class_type": "VAEDecodeAudio",
            "inputs": {
                "samples": ["14", 0],
                "vae": ["24", 0]
            }
        },
        "91": {
            "class_type": "CreateVideo",
            "inputs": {
                "images": ["10", 0],
                "fps": 24,
                "audio": ["23", 0],
                "bit_depth": 8
            }
        },
        "92": {
            "class_type": "SaveVideo",
            "inputs": {
                "video": ["91", 0],
                "filename_prefix": filename_prefix,
                "format": "auto",
                "codec": "auto"
            }
        }
    }


def submit_prompt(workflow):
    body = json.dumps({"prompt": workflow}).encode("utf-8")
    req = urllib.request.Request(
        f"{SERVER}/prompt",
        data=body,
        headers={"Content-Type": "application/json"},
        method="POST"
    )
    resp = urllib.request.urlopen(req, timeout=30)
    result = json.loads(resp.read())
    return result


def check_queue():
    try:
        resp = urllib.request.urlopen(f"{SERVER}/queue", timeout=10)
        data = json.loads(resp.read())
        return len(data.get("queue_running", [])), len(data.get("queue_pending", []))
    except:
        return 0, 0


print("=" * 60)
print("干锅虾烹饪视频 - 14镜头批量生成")
print(f"分辨率: 864x480 | 帧数: 209 (~8.7秒) | 步数: 20")
print("=" * 60)

submitted = []
for i, shot in enumerate(SHOTS):
    seed = 1000 + i * 37
    prefix = f"video/gan_guo_xia/{shot['id']}"
    wf = build_workflow(shot["prompt"], seed, prefix)

    try:
        result = submit_prompt(wf)
        prompt_id = result.get("prompt_id", "unknown")
        errors = result.get("node_errors", {})
        if errors:
            print(f"  [{i+1:02d}/14] ERROR in {shot['name']}: {errors}")
        else:
            print(f"  [{i+1:02d}/14] OK  {shot['name']} -> {prompt_id[:8]}...", flush=True)
            submitted.append({"id": shot["id"], "name": shot["name"], "prompt_id": prompt_id})
    except Exception as e:
        print(f"  [{i+1:02d}/14] FAILED {shot['name']}: {e}", flush=True)

    time.sleep(1)

print(f"\n提交完成: {len(submitted)}/14 个镜头已加入队列")
running, pending = check_queue()
print(f"当前队列: {running} 个运行中, {pending} 个等待中")
print("预计总耗时: 约 50-70 分钟 (每个镜头约 3.5-5 分钟)")

自定义方法

  • 修改 SHOTS 列表中的 prompt 字段来生成不同内容的视频
  • 修改 build_workflow()widthheightlength 参数来调整分辨率和时长
  • 修改 seed 计算方式来控制随机性
  • 修改 filename_prefix 来改变输出文件路径

B.5 download_torch_mt.py(PyTorch 多线程下载脚本)

import urllib.request
import os
import sys
import time
import threading

url = "https://download.pytorch.org/whl/cu130/torch-2.13.0%2Bcu130-cp312-cp312-win_amd64.whl"
out = r"d:\AI\minimax-h3\torch-2.13.0+cu130-cp312-cp312-win_amd64.whl"
num_threads = 8
chunk_size = 1024 * 1024  # 1MB read chunk

# Get file size
req = urllib.request.Request(url, method="HEAD")
resp = urllib.request.urlopen(req, timeout=30)
total_size = int(resp.headers.get("Content-Length", 0))
resp.close()
print(f"Total size: {total_size / 1e6:.1f} MB, using {num_threads} threads", flush=True)

# Calculate ranges
ranges = []
part_size = total_size // num_threads
for i in range(num_threads):
    start = i * part_size
    end = (i + 1) * part_size - 1 if i < num_threads - 1 else total_size - 1
    ranges.append((i, start, end))

progress = [0] * num_threads
lock = threading.Lock()

def download_part(idx, start, end):
    part_file = f"{out}.part{idx}"
    existing = os.path.getsize(part_file) if os.path.exists(part_file) else 0
    if existing >= (end - start + 1):
        progress[idx] = end - start + 1
        return

    req = urllib.request.Request(url)
    req.add_header("Range", f"bytes={start + existing}-{end}")

    retries = 0
    while retries < 20:
        try:
            resp = urllib.request.urlopen(req, timeout=60)
            with open(part_file, "ab" if existing > 0 else "wb") as f:
                while True:
                    data = resp.read(chunk_size)
                    if not data:
                        break
                    f.write(data)
                    with lock:
                        progress[idx] += len(data)
                    existing += len(data)
            break
        except Exception as e:
            retries += 1
            time.sleep(3)
            req = urllib.request.Request(url)
            req.add_header("Range", f"bytes={start + existing}-{end}")

threads = []
for idx, start, end in ranges:
    t = threading.Thread(target=download_part, args=(idx, start, end))
    t.start()
    threads.append(t)

# Progress monitor
start_time = time.time()
while any(t.is_alive() for t in threads):
    time.sleep(10)
    downloaded = sum(progress)
    elapsed = time.time() - start_time
    speed = downloaded / max(elapsed, 1) / 1e6
    pct = downloaded / total_size * 100 if total_size > 0 else 0
    print(f"  {downloaded / 1e6:.1f} / {total_size / 1e6:.1f} MB ({pct:.1f}%) - {speed:.1f} MB/s", flush=True)

for t in threads:
    t.join()

# Merge parts
print("Merging parts...", flush=True)
with open(out, "wb") as f:
    for i in range(num_threads):
        part_file = f"{out}.part{i}"
        if os.path.exists(part_file):
            with open(part_file, "rb") as pf:
                f.write(pf.read())
            os.remove(part_file)

final_size = os.path.getsize(out)
print(f"Done! {final_size / 1e6:.1f} MB", flush=True)

使用场景:当 pip install 下载 PyTorch cu130 速度过慢(< 1 MB/s)时,使用此脚本进行 8 线程并行下载。下载完成后用 pip install --force-reinstall --no-deps <wheel文件路径> 本地安装。同样需要下载 torchvisiontorchaudio 的 cu130 wheel 文件(URL 类似,替换文件名即可)。


文档更新日期:2026-08-17 环境:Windows 11 + RTX 5070 12GB + PyTorch 2.13.0+cu130 + ComfyUI 0.33.0