Skip to content

[GPU] MiniMax H3 영상생성 모델과 ComfyUI 워크플로 ​

개요 ​

이전 글에서 DeepSeek과 GLM 두 텍스트 LLM을 CVD GPU 인스턴스에 자체 배포하고 비교했습니다. 이번 글은 같은 GPU 인프라 위에서 텍스트가 아니라 영상을 생성하는 모델, MiniMax H3를 직접 구성해 돌려본 기록입니다.

MiniMax H3는 텍스트-투-비디오, 첫 프레임/마지막 프레임 조건부 생성, 이미지/영상/오디오 참조 기반 생성을 지원하는 영상생성 모델입니다. 이 모델을 실제로 쓰려면 두 가지를 먼저 정해야 합니다. 화면으로 조작하는 ComfyUI 워크플로를 쓸지, 프로그램에서 HTTP로 호출하는 API를 쓸지, 그리고 GPU 4장을 하나로 묶어 쓸지 4개로 쪼개 쓸지입니다. 이 글은 이 두 선택지를 각각 직접 구성해보고 실제로 어떻게 달라지는지 확인합니다.

실행 경로 세 가지 ​

H3 환경은 같은 4 GPU 구성 위에서 세 가지 실행 경로를 제공합니다.

실행 경로포트사용 목적기본 형태
ComfyUI + Raylight8189웹 UI에서 노드 편집과 영상 제작INT8, 4 GPU 단일 인스턴스
SGLang30010HTTP API로 영상 생성h3-use 기본 BF16 경로
초해상도 Agent7002생성 후 1080p / 2K / 4K 처리SGLang + MPS 후처리 조정

셋 다 같은 GPU 4장을 두고 경쟁하기 때문에, 실제로 중요한 제약은 "여러 시작 명령을 연속 실행하면 실행 경로를 계속 바꾸게 된다"는 점입니다. h3-use 명령 하나가 다른 경로의 컨테이너를 중지한 뒤 선택한 서비스를 시작하는 구조라서, 원하는 명령 하나만 실행해야 합니다. H3는 부팅 후 자동 시작하지 않으므로 매번 명시적으로 켜야 합니다.

bash
h3-use status            # 현재 상태 확인. 첫 시작 전에는 모두 중지 상태

전환에는 비용이 있습니다. ComfyUI는 비교적 빠르게 열리지만 SGLang은 가중치를 다시 읽어야 해서 준비 시간이 더 걸립니다. 기본 전환이 컨테이너를 삭제하는 건 아니지만, 진행 중이던 생성 작업이 보존된다는 보장도 없습니다. 그래서 대기열과 진행 작업을 먼저 정리한 뒤에 경로를 바꾸는 게 안전합니다.

FL2VA와 Ref2VA: 두 가지 모델 로딩 모드 ​

H3는 로딩하는 가중치에 따라 받을 수 있는 입력이 다릅니다.

로딩 모드받을 수 있는 task입력
fl2vat2va / fl2va텍스트만 또는 첫 프레임/마지막 프레임 이미지
ref2varef2va인물/동작/소리를 위한 이미지/영상/오디오 참조

여기서 실제로 겪은 함정은, 모드 전환이 HTTP 요청의 옵션 하나를 바꾸는 수준이 아니라는 점입니다. fl2va와 ref2va는 서로 다른 가중치 세트를 로딩하므로 전환할 때마다 로딩 시간이 그대로 다시 듭니다. 같은 모드의 요청을 묶어서 처리하는 편이 전환 비용을 아낄 수 있다는 걸 확인했습니다.

ComfyUI 워크플로 직접 구성하기 ​

먼저 화면으로 조작하는 경로부터 구성했습니다.

bash
h3-use raylight                       # INT8 기본 실행, 4 GPU USP4, 기본 20단계
h3-use raylight --lora=larryvrh      # Turbo LoRA 파일 확인. 4 GPU 병합 경로의 품질 제약에 유의
h3-use raylight --replicas=4          # 4 GPU를 단일 GPU 인스턴스 4개로 분리, 포트 8189–8192

표준 실행은 h3-use raylight이고, CVD 브라우저에서 직접 열거나 SSH 터널로 로컬 브라우저를 연결해서 기본 UI 주소(http://127.0.0.1:8189/)에 접속합니다.

워크플로 파일은 UI용과 API용이 서로 다른 JSON입니다. 4 GPU 기본 워크플로 파일 경로는 다음과 같습니다.

/srv/minimax-h3/workflows/MiniMax-H3_Raylight-4GPU_전능력视觉工作流.json

(파일명은 원본에서 Unicode 이스케이프로 표기되어 있어서, 실제 파일을 찾을 때는 이름을 다시 타이핑하기보다 아래처럼 직접 조회하는 편이 안전합니다.)

bash
find /srv/minimax-h3/workflows -maxdepth 1 -type f -name '*.json'

Workflow → Open으로 이 파일을 불러오거나 브라우저 캔버스에 끌어 놓습니다. 여기서 실제로 걸린 부분이 하나 있었는데, FL2VA 흐름과 Ref2VA 흐름 중 쓸 쪽의 SaveVideo 노드를 Always로, 다른 쪽은 Never로 설정해야 한다는 점입니다. 둘 다 켜두면 두 모델 작업이 동시에 큐에 들어갈 수 있습니다.

입력 파일은 library 폴더 바로 아래에 넣어야 합니다. 이 환경의 입력 선택기는 하위 폴더를 재귀 탐색하지 않기 때문에, 폴더를 만들어 정리해도 UI에서는 보이지 않습니다.

입력: /srv/minimax-h3/raylight/library/
출력: /srv/minimax-h3/raylight/output/video/

프롬프트와 입력 파일, 단계 수를 확인하고 Run을 누르면, 완료 후 출력 폴더에서 영상을 가져올 수 있습니다.

Turbo LoRA: 실행 경로에 따라 결과가 달라진다 ​

Turbo LoRA를 적용하면 요청 단계 수가 기본 20단계에서 9단계로 줄어듭니다. 그런데 이 LoRA를 실제로 붙이는 방식은 4 GPU 경로와 단일 GPU 경로가 다르고, 그 차이가 결과물 품질에 그대로 영향을 줍니다.

4 GPU LoRA 워크플로 파일은 별도로 있습니다.

/srv/minimax-h3/workflows/MiniMax-H3_Raylight-4GPU_fl2va_lora_视觉工作流.json

이 워크플로는 RayUNETLoader에 Load Lora Model (Ray)를 연결하고, minimax_h3_turbo_v4_step600_ema.safetensors 가중치를 강도 1.0, 9단계, simple 스케줄러로 사용합니다. 여기서 확인한 중요한 사실은, h3-use raylight --lora=larryvrh 명령이 LoRA 파일이 준비돼 있는지만 확인해줄 뿐, 실제 노드 연결과 단계 설정까지 대신해주지는 않는다는 점입니다. 워크플로 파일을 직접 불러오고 노드를 확인해야 합니다.

더 중요한 함정은 따로 있었습니다. 4 GPU Raylight 병합 경로에서는 Turbo의 adaln 시간 조건 재주입이 반영되지 않아서, 영상이 흐려질 수 있다는 점입니다. 그래서 Turbo 사용이 목적이라면 단일 GPU의 MiniMaxH3TurboLoRA + MiniMaxH3TurboSampler 경로를 먼저 쓰는 게 맞습니다. 속도만 보고 4 GPU 경로에 같은 LoRA를 얹으면, 이름은 같은 LoRA인데 결과는 달라집니다.

단일 GPU 인스턴스 네 개로 실행하기 ​

4 GPU를 하나로 묶는 대신, GPU 하나씩 총 네 개의 독립 인스턴스로 쪼개는 경로도 구성해봤습니다.

bash
h3-use raylight --replicas=4

이 명령을 실행하면 GPU별로 별도 UI가 뜹니다.

http://127.0.0.1:8189/   # GPU0
http://127.0.0.1:8190/   # GPU1
http://127.0.0.1:8191/   # GPU2
http://127.0.0.1:8192/   # GPU3

각 인스턴스에는 4 GPU용 워크플로가 아니라 단일 GPU 전용 Turbo 워크플로(/srv/minimax-h3/minimax_h3_t2v_turbo.json)를 불러와야 합니다. 출력은 raylight/output/0부터 3까지 인스턴스별로 분리됩니다. ComfyUI-MiniMax-H3-Turbo와 ComfyUI-MarkdownNote 커스텀 노드는 이 이미지에 이미 포함돼 있어서 별도 설치가 필요 없었습니다.

이 구성도 결국 GPU 4장을 전부 점유하는 건 같아서, SGLang 경로와는 동시에 쓸 수 없습니다. 4개로 쪼개서 서로 다른 4개의 짧은 요청을 병렬로 돌릴지, 4장을 합쳐서 하나의 무거운 요청을 빠르게 돌릴지는 워크로드 특성에 따라 고를 문제입니다.

수동 복제 구성: GPU 분배와 병렬 차수 ​

h3-use raylight --replicas=4가 아니라 직접 Docker 컨테이너를 나눠 실행하는 방법도 확인했습니다. GPU 1장씩 4개로 나누는 경우입니다.

bash
for i in 0 1 2 3; do
sudo docker run -d --name comfy-1g-$i \
  --gpus "\"device=$i\"" --shm-size=8g \
  -p 0.0.0.0:$((8200+i)):8188 \
  -v /srv/minimax-h3/models:/workspace/ComfyUI/models:ro \
  -v /srv/minimax-h3/raylight/output:/workspace/ComfyUI/output \
  -v /srv/minimax-h3/raylight/library:/workspace/ComfyUI/input \
  minimax-h3-raylight:usp4 \
  python main.py --listen 0.0.0.0 --port 8188
done

GPU 2장씩 묶는 경우도 별도 컨테이너 두 개로 구성합니다.

bash
sudo docker run -d --name comfy-2g-a --gpus '"device=0,1"' --shm-size=8g \
  -p 0.0.0.0:8210:8188 \
  -v /srv/minimax-h3/models:/workspace/ComfyUI/models:ro \
  -v /srv/minimax-h3/raylight/output:/workspace/ComfyUI/output \
  -v /srv/minimax-h3/raylight/library:/workspace/ComfyUI/input \
  minimax-h3-raylight:usp4 python main.py --listen 0.0.0.0 --port 8188

sudo docker run -d --name comfy-2g-b --gpus '"device=2,3"' --shm-size=8g \
  -p 0.0.0.0:8211:8188 \
  -v /srv/minimax-h3/models:/workspace/ComfyUI/models:ro \
  -v /srv/minimax-h3/raylight/output:/workspace/ComfyUI/output \
  -v /srv/minimax-h3/raylight/library:/workspace/ComfyUI/input \
  minimax-h3-raylight:usp4 python main.py --listen 0.0.0.0 --port 8188

여기서 실제로 확인한 규칙은, GPU를 몇 장 보여주느냐와 워크플로의 병렬 차수를 맞추는 건 별개 작업이라는 점입니다.

구성GPU 분배ulysses_degreeray_cluster_namespace
1 GPU × 4GPU 0 / 1 / 2 / 31h3_1gpu_0 … h3_1gpu_3처럼 각각 다르게
2 GPU × 2GPU 0+1 / 2+32h3_2gpu_a / h3_2gpu_b
4 GPU 단일GPU 0+1+2+34해당 인스턴스 전용 이름

GPU 수 = ulysses × ring × cfg × dp이고, 나머지 차수는 1로 유지해야 합니다. 여기서 실제로 조심해야 하는 부분은 ray_cluster_namespace입니다. namespace가 같으면 서로 다른 인스턴스가 같은 Ray 클러스터에 붙어서 GPU를 서로 빼앗을 수 있습니다. 단순히 Docker에 GPU 두 장을 보여주는 것만으로 워크플로의 병렬 차수가 자동으로 맞춰지지는 않는다는 걸 이 과정에서 확인했습니다.

수동 예제는 출력 디렉터리를 공유하므로, 충돌 없는 파일 이름 규칙이나 별도 출력 마운트를 써야 합니다. 확인이 끝난 뒤에는 아래처럼 정리했습니다.

bash
sudo docker rm -f comfy-1g-0 comfy-1g-1 comfy-1g-2 comfy-1g-3 comfy-2g-a comfy-2g-b

API로 영상 생성 요청 보내기: 1080p 최소 예제 ​

UI 없이 프로그램에서 바로 영상을 생성하는 경로도 확인했습니다. 초해상도 Agent(7002)가 이미 구동 중이고 MPS 연동이 구성돼 있어야 합니다.

bash
# 1) Agent가 사용할 추론 서비스 시작
h3-use fl2va

# 2) short_edge=1080으로 생성 후 초해상도 처리 요청
video_id=$(curl -fsS -X POST http://127.0.0.1:7002/v1/videos \
  -H 'Content-Type: application/json' \
  -d '{"prompt":"A vast canyon at sunrise, slow aerial push forward.","task":"t2va","seconds":5,"target":{"short_edge":1080,"aspect_ratio":"16:9"}}' \
  | jq -r '.id')

# 3) 완료까지 조회. 생성 및 초해상도 처리 중에는 running
while [ "$(curl -fsS http://127.0.0.1:7002/v1/videos/$video_id | jq -r .status)" != completed ]; do sleep 10; done

# 4) 완성 영상 다운로드. CDN 링크 유효기간 7일
curl -fsS http://127.0.0.1:7002/v1/videos/$video_id | jq -r .content_url | xargs curl -o output_1080p.mp4

이 최소 예제의 반복문에는 실패/취소/최대 대기시간 처리가 없다는 점은 짚어둘 필요가 있습니다. 연동 확인용으로만 쓰고, 운영에서는 제한 시간이 있는 클라이언트로 바꿔야 합니다.

FP8과 LightX2V LoRA를 함께 쓰면 요청 단계가 20에서 5로 줄어드는데, 1344×768 해상도 5초 테스트에서 처리 시간이 43.0초에서 23.6초로 줄었습니다. 다만 아래 명령 자체는 BF16 경로이고, 23.6초는 FP8 테스트값이라는 점을 혼동하면 안 됩니다.

bash
h3-use fl2va --lora=lightx2v     # 기본 제공 실행 경로는 BF16

FP8 LightX2V 전체 실행 명령은 별도로 있고, 실제 성능 튜닝은 다음 글에서 다룹니다.

컨테이너 상태 읽는 법 ​

h3-use status로 상태를 확인하면 Docker 컨테이너, 서비스 포트, GPU 점유량이 함께 나옵니다. 단일 인스턴스일 때는 이렇게 나옵니다.

--- 기본 Docker (Raylight/ComfyUI) ---
NAMES                 STATUS
minimax-h3-raylight   Up 5 minutes
--- SGLang Docker (별도 daemon) ---
NAMES                          STATUS
minimax-h3-sglang-fl2va-4gpu   Exited (0) 20 minutes ago
--- 서비스 상태 ---
ComfyUI  8189 : OK
SGLang  30010 : 중지됨
--- GPU ---
0, 26650 MiB, 73415 MiB, 0 %
1, 21224 MiB, 73415 MiB, 0 %
2, 21224 MiB, 73415 MiB, 0 %
3, 21224 MiB, 73415 MiB, 0 %

--replicas=4로 4분할 인스턴스를 띄우면 이렇게 바뀝니다.

--- 기본 Docker (Raylight/ComfyUI) ---
NAMES                   STATUS
minimax-h3-raylight-0   Up 3 minutes
minimax-h3-raylight-1   Up 3 minutes
minimax-h3-raylight-2   Up 3 minutes
minimax-h3-raylight-3   Up 3 minutes
--- 서비스 상태 ---
ComfyUI  8189 : OK
ComfyUI  8190 : OK
ComfyUI  8191 : OK
ComfyUI  8192 : OK
SGLang  30010 : 중지됨

여기서 헷갈리기 쉬운 부분은 Exited 상태입니다. Exited는 그저 중지된 컨테이너가 남아 있다는 뜻이라서, 지금 쓰는 경로가 아닌 다른 컨테이너가 Exited인 건 정상일 수 있습니다. 다만 지금 실제로 쓰려는 서비스가 Exited라면 종료 코드와 로그부터 확인해야 합니다.

H3 파일 구조와 기준 환경 ​

이번 실측에 쓴 기준 환경 스펙입니다.

항목기준 환경
OS / GPUUbuntu 22.04 LTS / PRO 5000 72GB × 4
드라이버 / CUDA580.126.20 / 13.0
Docker29.7.2
ComfyUI / Raylightv0.30.0 / commit 349d7a4d
PyTorch / NCCL2.13.0+cu130 / 2.29.7
SGLang실행 이미지 minimax-h3-sglang:0.5.18-cu130 / 버전 목록 commit 7c90840b
가중치ComfyUI 양자화 5개 파일 약 60GB. BF16은 모드별 29개 분할 파일

가중치 용량도 실제로 확인했는데, BF16 파일 크기는 모드별로 144,016,376,436바이트였습니다. FL2VA와 Ref2VA 두 모드를 합치면 약 288GB, 이진 단위로는 약 268.3GiB입니다. 폴더 표시가 GB인지 GiB인지 구분하지 않으면 디스크 용량 산정에서 오차가 날 수 있고, 여기에 Docker 계층/캐시/출력 파일 여유분까지 별도로 확보해야 합니다. 파일 패키지 전체 크기와 실행 시 GPU에 실제로 올라가는 텐서 크기가 같지 않다는 점도 유념할 부분입니다.

전체 디렉터리 구조 중 실제로 자주 참조하게 되는 경로는 다음과 같습니다.

/srv/minimax-h3/
├── VERSIONS.txt                      # 이미지 digest, commit, 가중치 체크섬
├── bin/h3-use                        # 전역으로 호출 가능한 실행 경로 전환 스크립트
├── workflows/                        # ComfyUI 워크플로 (UI용, API용 JSON)
├── models/                           # ComfyUI 양자화 가중치 약 60GB, 읽기 전용 마운트
├── raylight/                         # ComfyUI 입력(library/)과 출력(output/video/)
├── sglang/                           # SGLang 가중치(BF16 약 269GiB)와 API 입출력
└── minimax_h3_t2v_turbo.json         # 단일 GPU Turbo 워크플로

sglang/docker-data는 별도 Docker daemon이 쓰는 데이터라서 임의로 지우면 안 되고, VERSIONS.txt와 models/SHA256SUMS.txt는 설치된 버전과 가중치를 확인하는 기준 파일로 남겨뒀습니다.

정리 ​

  1. H3는 ComfyUI(웹 UI), SGLang(API), 초해상도 Agent 세 경로가 같은 GPU 4장을 공유하며, h3-use 명령으로 경로를 전환할 때마다 다른 컨테이너가 중지됩니다. 전환 비용(특히 SGLang의 가중치 재로딩)을 감안해서 원하는 경로 하나만 실행해야 합니다.
  2. fl2va(텍스트/첫/마지막 프레임)와 ref2va(이미지/영상/오디오 참조)는 서로 다른 가중치를 로딩하는 별개 모드이고, 전환마다 로딩 시간이 새로 듭니다.
  3. Turbo LoRA는 4 GPU Raylight 병합 경로에서는 시간 조건 재주입이 반영되지 않아 결과가 흐려질 수 있습니다. Turbo가 목적이면 단일 GPU 전용 노드 경로(MiniMaxH3TurboLoRA + MiniMaxH3TurboSampler)를 먼저 써야 합니다.
  4. GPU를 1장/2장/4장 단위로 나눌 때는 ulysses_degree를 그 구성에 맞게 지정하고, ray_cluster_namespace를 인스턴스마다 다르게 줘야 서로 다른 인스턴스가 같은 Ray 클러스터에 묶여 GPU를 빼앗기는 문제를 피할 수 있습니다.
  5. FP8 + LightX2V LoRA 조합은 요청 단계를 20에서 5로 줄이고, 실측 기준 처리 시간을 43.0초에서 23.6초로 단축했습니다. 다만 이 수치는 FP8 경로 기준이고, 기본 제공 명령(h3-use fl2va --lora=lightx2v)은 BF16 경로라는 점을 구분해야 합니다.
  6. 가중치 용량은 모드별 144,016,376,436바이트(BF16)이고, GB/GiB 단위 차이와 Docker 계층/캐시 여유분까지 포함해서 디스크를 산정해야 실제 운영에서 용량 부족을 피할 수 있습니다.

이어지는 실측은 다음 글에서 다룹니다. 이번 글에서 확인한 FP8/LightX2V/다중 GPU 구성을 SGLang API 경로로 좁혀서, 구성별 처리 시간을 실제로 벤치마크합니다.

基于 VitePress 构建 · 部署于腾讯云 EdgeOne Pages