0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

MiniMax H3をRTX A6000で動かし、画像から動画を生成するWebアプリにした

0
Last updated at Posted at 2026-08-04

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) です。

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人の画像を参照フレームにしました。実在人物の写真や声は使っていません。

入力画像

Geminiで生成した架空の芸人の参照フレーム

promptには、役割、台詞、画角、維持したい要素、AI生成表示をまとめて指定しました。

明確なAI生成パロディ映像。舞台上の二人が日本語でテンポよく漫才をする。
左の男性が落ち着いたツッコミ、右の男性が陽気なボケ。
本人の声は模倣せず、二人とも架空の自然な日本語男性合成音声。
右「どうもー、生成AI漫才です!」
左「コンビ名みたいに言うな」
右「今日はGPUを三分だけ借りました」
左「動画より短いやないか!」
観客の自然な笑い声。固定カメラ、自然な口の動き、
人物・衣装・舞台・マイクを維持。
画面左上に小さく AI生成パロディ と常時表示。

出力(10.1秒、GIF版は無音)

MiniMax H3で生成した漫才動画

:speaker: 音声付きMP4を開く

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差分でした。

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?