TL;DR
- オープンウェイトで公開された MiniMax H3 のImage-to-Videoを、RTX A6000 48GB で動かした
- ComfyUIを推論バックエンド、FastAPIをジョブ管理API、HTML/CSS/JavaScriptをUIにして、画像とプロンプトを投げられるWebアプリにした
- 量子化済みモデル一式は約40GB。384x672、約5.2秒、24fpsの動画生成に 約288秒、GPUメモリ約29GB だった
- CUDAの互換性よりも、ComfyUI APIの動画出力が
videosではなくimagesに入る問題で最後に詰まった - サーバーは外部公開せず、
127.0.0.1で待ち受けてSSHポートフォワーディング経由で利用している
MiniMax H3のweights公開と同日にComfyUI側にも対応が入ったので、さっそく動かしました。
ComfyUIの画面をそのまま使うだけなら比較的簡単ですが、今回は「画像を選ぶ・動きを文章で指定する・待つ・動画を見る」だけのWebアプリとして使えるところまで持っていきます。
この記事は2026年8月4日時点の検証記録です。公開直後のモデル・ComfyUIを扱っているため、ファイル名やノード仕様は今後変わる可能性があります。
MiniMax H3とは
MiniMax H3は、動画と音声を一体で生成できるオープンウェイトの動画生成モデルです。ComfyUIの公開時アナウンスでは、Text-to-Video、Image-to-Video、First/Last Frame、Reference-to-Videoなどのworkflowが案内されています。
今回使うのは、1枚の参照画像とテキストから動画を作る Image-to-Video (I2V) です。
- 公式weights: https://huggingface.co/MiniMaxAI/MiniMax-H3
- ComfyUI向けweights: https://huggingface.co/Comfy-Org/MiniMax-H3
- ComfyUI workflow: https://docs.comfy.org/tutorials/video/minimax/minimax-h3
ComfyUI向けの小型構成では、modulation weightのpruning、int8 convrot量子化、custom kernelなどにより、full precisionよりメモリ使用量を抑えています。とはいえ、テキストエンコーダー、diffusion model、video/audio VAEを合わせると、保存だけで約40GB必要でした。
構成
今回の構成は次の通りです。
MacBook / Browser
|
| SSH local port forwarding
v
FastAPI :7860 (127.0.0.1)
|
| POST /prompt, GET /history/{prompt_id}
v
ComfyUI :8190 (127.0.0.1)
|
v
MiniMax H3 / RTX A6000 48GB
| 項目 | 構成 |
|---|---|
| GPU | NVIDIA RTX A6000 48GB |
| OS | Linux |
| ComfyUI | 0.30.0 |
| Python | 3.12 |
| PyTorch | 2.5.1+cu121 |
| Web API | FastAPI + Uvicorn |
| Frontend | Vanilla HTML/CSS/JavaScript |
| 接続 | SSH local port forwarding |
Webアプリを推論サーバーと分けた理由は、ComfyUIを「workflow実行エンジン」として扱いたかったからです。ノードをGUIで検証した後、そのworkflowをAPI形式のJSONに落とせば、UIは用途に合わせて自由に作れます。
1. ComfyUIとモデルを用意する
ComfyUIをcloneし、仮想環境へ依存関係を入れます。
git clone https://github.com/Comfy-Org/ComfyUI.git
cd ComfyUI
python3 -m venv .venv
.venv/bin/python -m pip install -r requirements.txt
今回はGPUドライバがCUDA 13系PyTorchを扱えなかったため、CUDA 12.1版へ合わせました。
.venv/bin/python -m pip install \
torch==2.5.1 torchvision torchaudio \
--index-url https://download.pytorch.org/whl/cu121
使ったモデルファイルは次の4つです。
models/
├── diffusion_models/
│ └── minimax_h3_fl2va_pruned_int8_convrot.safetensors
├── text_encoders/
│ └── qwen3vl_32b_minimax_h3_nvfp4_awq.safetensors
└── vae/
├── minimax_h3_video_vae_fp16.safetensors
└── minimax_h3_audio_vae_fp32.safetensors
ファイル名や配置先は更新される可能性があるので、実際にはComfyUI公式workflowページのリンクを確認するのが確実です。
起動は次の通りです。
.venv/bin/python main.py \
--disable-auto-launch \
--listen 127.0.0.1 \
--port 8190 \
--lowvram
0.0.0.0ではなく127.0.0.1で待ち受けます。認証なしのComfyUIを、そのまま共有サーバーのネットワークへ露出させないためです。
2. ComfyUI workflowをAPI用JSONにする
I2V workflowの主要部分は次の流れです。
LoadImage
-> MiniMaxH3ImageToVideo
-> SamplerCustomAdvanced
-> Video VAE Decode
-> Audio VAE Decode
-> CreateVideo
-> SaveVideo
FastAPI側では、入力画像名、prompt、解像度、フレーム数、seed、出力先だけをリクエストごとに差し替えます。
def workflow(image_name, prompt, width, height, length, seed, job_id):
return {
"1": {
"class_type": "LoadImage",
"inputs": {"image": image_name},
},
"6": {
"class_type": "MiniMaxH3ImageToVideo",
"inputs": {
"clip": ["3", 0],
"vae": ["4", 0],
"first_frame": ["1", 0],
"prompt": prompt,
"width": width,
"height": height,
"length": length,
},
},
# sampler / decode nodesは省略
"15": {
"class_type": "SaveVideo",
"inputs": {
"video": ["14", 0],
"filename_prefix": f"h3-web/{job_id}",
"format": "mp4",
"codec": "h264",
},
},
}
フレーム数を17k+5へ合わせる
MiniMax H3のノードでは、長さを任意の整数にするのではなく、workflowが受け付ける系列長へ合わせる必要がありました。24fpsで指定秒数に近いフレーム数を求め、17k+5の形へ切り上げます。
def frame_count(seconds: int) -> int:
frames = max(5, round(seconds * 24))
return frames + (5 - frames % 17) % 17
5秒指定の場合は124 framesとなり、出力は約5.17秒でした。
3. FastAPIで非同期ジョブにする
動画生成には数分かかります。HTTPリクエストを開いたまま待つ設計にはせず、生成要求でjob IDを即座に返し、ブラウザがstatusをpollingする形にしました。
@app.post("/api/generate")
async def generate(
background_tasks: BackgroundTasks,
image: UploadFile = File(...),
prompt: str = Form(...),
preset: str = Form("portrait"),
seconds: int = Form(5),
seed: int = Form(26080401),
):
job_id = uuid.uuid4().hex
# MIME type、15MB上限、Pillowで画像実体を検証
# ComfyUI/inputへ保存
jobs[job_id] = {"status": "queued", "progress": 0}
background_tasks.add_task(run_generation, job_id, ...)
return jobs[job_id]
バックグラウンドではComfyUIの/promptへworkflowを送信し、返ってきたprompt_idのhistoryをpollingします。
queued = await client.post(
"http://127.0.0.1:8190/prompt",
json={"prompt": workflow_json, "client_id": f"h3-web-{job_id}"},
)
prompt_id = queued.json()["prompt_id"]
while True:
await asyncio.sleep(5)
history = (
await client.get(f"http://127.0.0.1:8190/history/{prompt_id}")
).json()
if history:
# outputを取り出してcompleteへ更新
break
GPU 1枚へ同時に複数jobを投げないよう、asyncio.Lockで生成部分を直列化しています。本格運用ならRedisとworker queueへ分けるところですが、自分用の単一GPU環境ではこれで十分です。
4. 最後に詰まった: 動画なのにimagesへ入る
推論logには次のように出て、MP4も保存されていました。
Prompt executed in 287.50 seconds
ところがWeb側は永遠にgeneratingのまま。historyを直接見ると、原因はこれでした。
{
"outputs": {
"15": {
"images": [
{
"filename": "..._00001_.mp4",
"subfolder": "h3-web",
"type": "output"
}
],
"animated": [true]
}
}
}
SaveVideoの出力なのでvideosを想定していましたが、手元のComfyUI 0.30.0ではMP4もimages配列に格納されていました。両方を許容して解決しました。
save_output = record.get("outputs", {}).get("15", {})
outputs = save_output.get("videos") or save_output.get("images", [])
5. Web UI
UIには次の項目だけを出しました。
- 参照画像のdrag & drop
- 動き・カメラ・維持したい要素を書くprompt
- portrait / square / landscape
- 5秒 / 8秒 / 10秒
- seed
- 生成statusと動画preview/download
また、人物画像の利用を想定して、本人または権利者の許可を得た画像であることの確認と、生成結果がAI生成である旨の表示を入れています。
6. 外部公開せずSSH tunnelで使う
FastAPIもlocalhostだけで起動します。
uvicorn app:app --host 127.0.0.1 --port 7860
Mac側からSSH local port forwardingを張ります。
ssh -N -L 7860:127.0.0.1:7860 <gpu-server>
これで手元のブラウザから次へアクセスできます。
http://127.0.0.1:7860
GPUサーバー側でpublic portを開ける必要はありません。個人利用や検証段階なら、認証・TLS・reverse proxyを急いで作るより、SSH tunnelに閉じ込める方が管理しやすいです。
実際の生成例: 架空の芸人に漫才をさせる
静止画からどの程度動かせるのかを見るため、以前Geminiで生成した架空の芸人2人の画像を参照フレームにしました。実在人物の写真や声は使っていません。
入力画像
promptには、役割、台詞、画角、維持したい要素、AI生成表示をまとめて指定しました。
明確なAI生成パロディ映像。舞台上の二人が日本語でテンポよく漫才をする。
左の男性が落ち着いたツッコミ、右の男性が陽気なボケ。
本人の声は模倣せず、二人とも架空の自然な日本語男性合成音声。
右「どうもー、生成AI漫才です!」
左「コンビ名みたいに言うな」
右「今日はGPUを三分だけ借りました」
左「動画より短いやないか!」
観客の自然な笑い声。固定カメラ、自然な口の動き、
人物・衣装・舞台・マイクを維持。
画面左上に小さく AI生成パロディ と常時表示。
出力(10.1秒、GIF版は無音)
672x384、24fps、20 stepsで生成し、約8分でした。5秒・384x672の試行より時間が延びているので、当然ながら「尺を2倍にすると同じ時間で済む」わけではありません。
中間フレームでも人物、衣装、舞台、中央のマイクは維持されました。一方、長い台本を一度に正確に読ませる用途ではなく、短い発話単位に分割した方が制御しやすいです。2〜3分の漫才を作るなら、10〜15秒のshotを複数生成し、音量と間を調整して結合する構成になります。
実測結果
同一条件で、Web UIからupload、workflow投入、生成完了検知、MP4 downloadまで確認しました。
| 項目 | 結果 |
|---|---|
| GPU | RTX A6000 48GB |
| 入力 | 人物を含む縦長JPEG 1枚 |
| 出力解像度 | 384x672 |
| 出力尺 | 5.167秒 |
| fps | 24 |
| 映像codec | H.264 |
| 音声codec | AAC stereo |
| sampling steps | 20 |
| 推論時間 | 287.50秒 |
| 確認したGPUメモリ使用量 | 約29GB |
| 出力サイズ | 約477KB |
これは高解像度性能のbenchmarkではなく、「48GB GPU 1枚でWebアプリとして一連の経路が動くか」のsmoke testです。解像度・尺・offload・量子化方式を変えれば、時間とメモリ使用量は大きく変わります。
おわりに
MiniMax H3を動かすだけならComfyUIの公式workflowで十分です。一方、他の人が繰り返し使う道具にするには、upload validation、job queue、完了検知、output配信、network境界まで含めて設計する必要があります。
今回の実験では、A6000 48GB 1枚で、量子化済みI2Vを約29GBのGPUメモリ、約5分で実行できました。そして一番時間を使ったのはモデル本体ではなく、ComfyUI APIのimages/videos差分でした。

