Spaces:
Running on Zero
Running on Zero
Upload README.md
Browse files
README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
---
|
| 2 |
-
title: MiniMax-H3 · reference
|
| 3 |
emoji: 🎭
|
| 4 |
colorFrom: pink
|
| 5 |
colorTo: purple
|
|
@@ -7,7 +7,7 @@ sdk: gradio
|
|
| 7 |
sdk_version: 6.20.0
|
| 8 |
app_file: app.py
|
| 9 |
pinned: true
|
| 10 |
-
short_description: Video + soundtrack, your lora + CivitAI,
|
| 11 |
suggested_hardware: zero-a10g
|
| 12 |
tags:
|
| 13 |
- video
|
|
@@ -26,27 +26,49 @@ audio references, at **bfloat16 with no quantization anywhere**.
|
|
| 26 |
|
| 27 |
This Space is the denoising half of the `ref2va` task: the 61.73 GiB `transformer_ref` partition and the two
|
| 28 |
autoencoders. The 62.14 GiB Qwen3-VL conditioner runs in
|
| 29 |
-
[`qwen3vl-conditioner`](https://huggingface.co/spaces/multimodalart/qwen3vl-conditioner), which this
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
uses.
|
| 33 |
|
| 34 |
## What this fork adds
|
| 35 |
|
| 36 |
-
**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
file. Adapters have to be trained against the `transformer_ref/` partition — a `transformer/` adapter is a different
|
| 38 |
partition and will not match.
|
| 39 |
|
| 40 |
-
**
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
every new generation joins on its own. It runs on the CPU, so it costs nothing from the GPU allowance. Everything is
|
| 51 |
scaled to the first clip's frame, and a clip without audio gets silence rather than breaking the join.
|
| 52 |
|
|
@@ -58,10 +80,6 @@ so a request that will not fit is visible as a number rather than as a failed ru
|
|
| 58 |
**Randomize seed**, drawn fresh per press and written back into the box, so the number shown is always the one the
|
| 59 |
clip was made with.
|
| 60 |
|
| 61 |
-
Set `CIVITAI_TOKEN` under *Settings → Variables and secrets* for gated CivitAI models: the Space downloads on its own
|
| 62 |
-
machine, so being signed in to CivitAI in a browser does not authenticate it — a gated model answers a server with an
|
| 63 |
-
HTML login page, which is caught and reported rather than saved as weights.
|
| 64 |
-
|
| 65 |
## Why split
|
| 66 |
|
| 67 |
MiniMax-H3 is 195.9 GiB in bfloat16 and a ZeroGPU Space is evicted at **150 GB of storage**. An unquantized single
|
|
@@ -80,9 +98,7 @@ reads them**. The order is semantic: it numbers the labels of MiniMax-H3's promp
|
|
| 80 |
`<Video 1>`, `<Audio 1>`) and it advances the shared audio/video rotary clock, so the same references in a different
|
| 81 |
order are a different request. This demo lays the slots out as one tab per modality in reading order — images, then
|
| 82 |
audio, then video — and assembles the request that way. The Images tab opens with two slots and **+ Add another
|
| 83 |
-
image** reveals the rest, up to the model's own nine
|
| 84 |
-
tab that is not the open one is still part of the request; the tabs lay the slots out, they do not choose between
|
| 85 |
-
them.
|
| 86 |
|
| 87 |
Rules the model imposes, enforced here before anything is uploaded:
|
| 88 |
|
|
@@ -92,108 +108,23 @@ Rules the model imposes, enforced here before anything is uploaded:
|
|
| 92 |
which is why the duration slider disappears when a single reference can set it, and comes back when two can or
|
| 93 |
when the one that could is out of range.
|
| 94 |
|
| 95 |
-
## Example assets
|
| 96 |
-
|
| 97 |
-
`examples/subject.png` is [a studio portrait by Oliver
|
| 98 |
-
Dohrn](https://www.pexels.com/photo/casual-portrait-of-smiling-woman-in-studio-31747504/) cropped to head and
|
| 99 |
-
shoulders — Pexels licence, free to use. `examples/motion.mp4` is a synthetic clip from the parity fixtures, and
|
| 100 |
-
`examples/voice.wav` is utterance `1462-170145-0022` of
|
| 101 |
-
[LibriSpeech](https://www.openslr.org/12) `dev-clean` — CC BY 4.0, read from a public-domain LibriVox recording. It
|
| 102 |
-
is 16 kHz mono on purpose: the audio VAE wants 32 kHz, so the example exercises the `torchaudio` resample the
|
| 103 |
-
`ref2va` path needs.
|
| 104 |
-
|
| 105 |
-
## How the split is expressed
|
| 106 |
-
|
| 107 |
-
`MiniMaxH3Blocks` is one `SequentialPipelineBlocks` whose branches are picked per request — and per `workflow=` —
|
| 108 |
-
from the inputs, `ref2va` being the branch `references` selects:
|
| 109 |
-
|
| 110 |
-
```
|
| 111 |
-
setup -> text_encoder -> reference_encoder -> denoise -> after_denoise -> decode
|
| 112 |
-
```
|
| 113 |
-
|
| 114 |
-
where `denoise` is itself `prepare_layout -> prepare_latents -> set_timesteps -> denoise`, against the
|
| 115 |
-
`transformer_ref` partition.
|
| 116 |
-
|
| 117 |
-
`h3_split_blocks.py` subclasses it with the `text_encoder` step removed. Dropping the step drops the three
|
| 118 |
-
components it declares, so `load_components` resolves `transformer_ref` / `vae` / `audio_vae` / the two schedulers
|
| 119 |
-
out of the shared `modular_model_index.json` and never fetches the conditioner — and `prompt_embeds` and
|
| 120 |
-
`text_token_tags` become ordinary required inputs of the pipeline call:
|
| 121 |
-
|
| 122 |
-
```py
|
| 123 |
-
pipe = MiniMaxH3Ref2VAGeneratorBlocks().init_pipeline("MiniMaxAI/MiniMax-H3")
|
| 124 |
-
pipe.load_components(dtype=torch.bfloat16)
|
| 125 |
-
state = pipe(prompt_embeds=..., text_token_tags=..., references=[...], height=544, width=960, num_frames=124,
|
| 126 |
-
num_inference_steps=28)
|
| 127 |
-
```
|
| 128 |
-
|
| 129 |
-
Only **text** encoding is remote. `reference_encoder` is the `ref2va` branch's own encoder step — it runs the video
|
| 130 |
-
VAE over the image and video references and the audio VAE over the soundtracks, and it is where the references'
|
| 131 |
-
latent geometry is resolved — so it stays on this side, next to the autoencoders the conditioner Space does not hold.
|
| 132 |
-
|
| 133 |
-
The wire format is the same two tensors as the keyframe half: `(1, num_text_tokens, 5120)` bfloat16 and
|
| 134 |
-
`(num_text_tokens,)` int64, carried as one safetensors file with the resolved `height` / `width` / `num_frames` in
|
| 135 |
-
its metadata header. What differs is only what the conditioner is shown, so the references travel to it as files:
|
| 136 |
-
`ref2va`'s presentation puts a vision block in front of the prompt for every image and every merged video frame
|
| 137 |
-
pair. An audio reference contributes its `"<Audio j>: "` label and nothing else — a waveform never reaches the
|
| 138 |
-
conditioner — but it still goes over, because a single audio-bearing reference is what resolves `num_frames` when
|
| 139 |
-
the request leaves it open.
|
| 140 |
-
|
| 141 |
-
The `setup` step runs on **both** halves. It owns no component (PIL, PyAV-decoded media and arithmetic) and it
|
| 142 |
-
resolves the canvas, the `17 * n + 5` frame count and the references prepared at their own resolutions. It is
|
| 143 |
-
deterministic over the same files, and the conditioner returns the plan it resolved so this Space pins the same
|
| 144 |
-
canvas and frame count rather than re-deriving them.
|
| 145 |
-
|
| 146 |
-
## AoTI-compiled blocks
|
| 147 |
-
|
| 148 |
-
With `H3_AOTI=1` the 50 repeated transformer blocks run from a compiled package,
|
| 149 |
-
[`multimodalart/minimax-h3-aoti`](https://huggingface.co/multimodalart/minimax-h3-aoti)`:bf16/torch2.11/sm120/dynamic` — a single dynamic-sequence artifact that serves
|
| 150 |
-
every canvas, duration, reference set and prompt length.
|
| 151 |
-
|
| 152 |
-
It is the **same package the `transformer/` partition runs**, and nothing about it is partition-specific. The two
|
| 153 |
-
`config.json` files are identical field for field, and the package carries no weights at all: `LazyAOTIModel` binds
|
| 154 |
-
each block's own live `state_dict()` by name on its first forward. Patching it in is startup CPU work and costs no
|
| 155 |
-
GPU time.
|
| 156 |
-
|
| 157 |
-
It removes a near-constant ~0.5 s/step — 50 blocks' worth of kernel-launch overhead plus the norm / rotary / AdaLN
|
| 158 |
-
epilogues around the matmuls — and cannot touch the matmuls themselves, so it pays best where the block is not
|
| 159 |
-
compute bound. `ref2va` packs the reference rows in front of the generated ones, which makes the sequence longer
|
| 160 |
-
than a keyframe request at the same canvas and moves it further toward compute bound.
|
| 161 |
-
|
| 162 |
-
## Nothing is paid for with GPU time
|
| 163 |
-
|
| 164 |
-
The 77.3 GB download and the load happen at **startup**: `import spaces` at module top patches `torch.cuda` before
|
| 165 |
-
any GPU is attached, so nothing about the load needs a card. The conditioner round trip is a network call on this
|
| 166 |
-
Space's CPU. A `@spaces.GPU` call is therefore only the placement (once), the two reference encoders, the denoise
|
| 167 |
-
loop and the two decoders.
|
| 168 |
-
|
| 169 |
-
One thing does *not* happen at startup: the move onto the card. `spaces`' startup `torch.pack()` writes every
|
| 170 |
-
startup-resident CUDA tensor to a second copy on disk before deleting the downloaded originals, and 77.3 GB of
|
| 171 |
-
weights plus a 77.3 GB pack is 154.6 GB against a 150 GB quota — the Space is evicted mid-pack with `OSError:
|
| 172 |
-
[Errno 28] No space left on device`. Placement therefore happens on the **first GPU call**, `PIPE.to("cuda")` at the
|
| 173 |
-
top of the `@spaces.GPU` function: about 10 s of PCIe once, then a no-op walk, and the denoise loop runs with
|
| 174 |
-
everything resident and no offloading at all.
|
| 175 |
-
|
| 176 |
-
The references are decoded inside that call too, from their paths rather than as decoded media. A `@spaces.GPU`
|
| 177 |
-
argument crosses a process boundary by pickling, and a 5 s 1344x768 reference video is 370 MB of frames once PyAV
|
| 178 |
-
has expanded it.
|
| 179 |
-
|
| 180 |
## Generation constraints
|
| 181 |
|
| 182 |
Fixed by the checkpoint: 24 fps, a 768 pixel short edge, 5 to 15 s, `num_frames` snapped up to the next `17 * n + 5`,
|
| 183 |
no CFG and no negative prompt (it is guidance-distilled, so every step is one forward pass). The duration slider
|
| 184 |
-
stops at 14 s because it is the *snapped* count that has to hold for the ceiling: 15 s is 360 frames, which rounds
|
| 185 |
-
|
| 186 |
|
| 187 |
## GPU time is reserved per request, not per Space
|
| 188 |
|
| 189 |
-
MiniMax-H3 attends over one packed sequence, so what a step costs is a function of that sequence's length alone —
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
|
| 194 |
-
`get_duration` prices that before the call instead of reserving a flat ceiling for everything. It takes the
|
| 195 |
-
|
| 196 |
-
|
| 197 |
|
| 198 |
```
|
| 199 |
S = text rows + reference rows + target rows
|
|
@@ -204,8 +135,12 @@ fitted on the `t2va` half and checked against live `ref2va` requests to about 10
|
|
| 204 |
pool reserves whatever number it is given, and a flat 900 s is what makes a busy account fail admission with *"You
|
| 205 |
have too many ZeroGPU credits allocated to running tasks."* A typical single-image request now reserves ~460 s.
|
| 206 |
|
| 207 |
-
|
| 208 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 209 |
|
| 210 |
## Space variables
|
| 211 |
|
|
@@ -215,47 +150,22 @@ on is cold and a cold one pays the lazy 72.16 GiB `PIPE.to("cuda")` inside its f
|
|
| 215 |
| `H3_MODEL_REPO` | `MiniMaxAI/MiniMax-H3` | The diffusers-layout checkpoint. Public. |
|
| 216 |
| `H3_AOTI` | `0` | `1` loads the compiled block package. |
|
| 217 |
| `H3_PLACEMENT` | `lazy` | `lazy` moves all 72.16 GiB onto the card on the first GPU call and leaves it there; `offload` hands placement to `ComponentsManager.enable_auto_cpu_offload` instead. |
|
| 218 |
-
| `H3_ATTENTION` | `_native_cudnn` | cuDNN's fused kernel, 10–20% faster than the SDPA default
|
| 219 |
| `H3_GPU_DURATION_MIN` / `_MAX` | `120` / `1500` | Bounds on what `get_duration` may reserve. |
|
| 220 |
| `H3_PLACEMENT_ALLOWANCE` | `90` | Seconds of the reservation set aside for a cold worker's placement. |
|
| 221 |
| `H3_GPU_SIZE` | `xlarge` | ZeroGPU allocation size. `large` does not fit. |
|
| 222 |
-
| `
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
Two cards are booked per request — this Space's denoise loop and the conditioner's forward — and both are billed to the
|
| 227 |
-
requesting user, with nothing here arranging it: `gradio_client` attaches the caller's own `x-ip-token` to every
|
| 228 |
-
outgoing call, reading it off gradio's `LocalContext` inside the event listener (`Client.send_data` ->
|
| 229 |
-
`add_zero_gpu_headers`), and ZeroGPU charges the booking to whatever that token identifies.
|
| 230 |
-
|
| 231 |
-
A caller with no token to forward — a `gradio_client` script rather than a browser — leaves the conditioner's booking
|
| 232 |
-
attributed to this Space's pod IP and its small shared quota. An unattributed caller may book at most 120 credits at a
|
| 233 |
-
time and an `xlarge` booking costs twice its seconds, so the conditioner books the encode (45 s) and a prompt upsample
|
| 234 |
-
(60 s) as two separate calls, each within that ceiling.
|
| 235 |
-
|
| 236 |
-
## Secrets
|
| 237 |
-
|
| 238 |
-
None are required for generation. Everything this Space downloads is public — the
|
| 239 |
-
[`MiniMaxAI/MiniMax-H3`](https://huggingface.co/MiniMaxAI/MiniMax-H3) checkpoint and the
|
| 240 |
-
[`multimodalart/minimax-h3-aoti`](https://huggingface.co/multimodalart/minimax-h3-aoti) packages — and the conditioner
|
| 241 |
-
is a public Space called on the requesting user's own ZeroGPU token, never on an org token. `CIVITAI_TOKEN` is the one
|
| 242 |
-
optional addition, and only for a gated adapter.
|
| 243 |
|
| 244 |
## Where diffusers comes from
|
| 245 |
|
| 246 |
MiniMax-H3 is modular-only and not in a released `diffusers`, so `requirements.txt` installs it from the canonical
|
| 247 |
pull request, [huggingface/diffusers#14371](https://github.com/huggingface/diffusers/pull/14371), pinned to the commit
|
| 248 |
-
`665f5782`
|
| 249 |
-
|
| 250 |
-
That PR is a WIP, so it needs re-pinning whenever it updates, and `h3_split_blocks.py` — which subclasses its block
|
| 251 |
-
classes to cut the pipeline in two — has to be re-checked against the new head at the same time.
|
| 252 |
-
|
| 253 |
-
Two of those are `ref2va`-only and easy to miss. PyAV decodes a reference video or audio file as the reference is
|
| 254 |
-
built, and **`torchaudio`** resamples a soundtrack that is not already at the audio VAE's 32 kHz — a 32 kHz
|
| 255 |
-
reference skips the resample entirely, so the dependency only shows up once someone brings audio at another rate:
|
| 256 |
|
| 257 |
-
|
| 258 |
-
ImportError: Resampling a MiniMax-H3 reference soundtrack from 24000 Hz to 32000 Hz needs `torchaudio`.
|
| 259 |
-
```
|
| 260 |
|
| 261 |
-
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: MiniMax-H3 · reference → video + audio · Custom lora
|
| 3 |
emoji: 🎭
|
| 4 |
colorFrom: pink
|
| 5 |
colorTo: purple
|
|
|
|
| 7 |
sdk_version: 6.20.0
|
| 8 |
app_file: app.py
|
| 9 |
pinned: true
|
| 10 |
+
short_description: Video + soundtrack, your lora + CivitAI, structured prompts
|
| 11 |
suggested_hardware: zero-a10g
|
| 12 |
tags:
|
| 13 |
- video
|
|
|
|
| 26 |
|
| 27 |
This Space is the denoising half of the `ref2va` task: the 61.73 GiB `transformer_ref` partition and the two
|
| 28 |
autoencoders. The 62.14 GiB Qwen3-VL conditioner runs in
|
| 29 |
+
[`qwen3vl-conditioner`](https://huggingface.co/spaces/multimodalart/qwen3vl-conditioner), which this Space calls over
|
| 30 |
+
the gradio API for every request — the same conditioner Space, and the same resident weights, that the keyframe half
|
| 31 |
+
[`minimax-h3`](https://huggingface.co/spaces/multimodalart/minimax-h3) uses.
|
|
|
|
| 32 |
|
| 33 |
## What this fork adds
|
| 34 |
|
| 35 |
+
**A structured prompt builder.** H3 was trained on the output of H3-Context-IR, a preprocessor that rewrites a plain
|
| 36 |
+
request into labelled sections, and MiniMax's own model card calls that structure *critical to the quality of the
|
| 37 |
+
final output*. Nothing in this pipeline adds it — the string reaches the transformer as typed. The builder writes
|
| 38 |
+
those sections for you: the reference line naming `<Picture 1>`, `<Picture 2>` in connection order, then
|
| 39 |
+
`integrated_multimodal_description` with the shot type, the camera move written as type + amplitude + speed, and the
|
| 40 |
+
dialogue verbatim inside `<d>` tags, then `overall_soundscape` and `non_diegetic_music`. Speech is generated together
|
| 41 |
+
with the picture, so naming that someone speaks without giving the words produces correct mouth shapes with nothing
|
| 42 |
+
in them; the builder makes that hard to get wrong.
|
| 43 |
+
|
| 44 |
+
**Custom lora, five slots.** A Hugging Face repo, a file inside one, a **CivitAI download link**, or an uploaded
|
| 45 |
file. Adapters have to be trained against the `transformer_ref/` partition — a `transformer/` adapter is a different
|
| 46 |
partition and will not match.
|
| 47 |
|
| 48 |
+
**CivitAI, searched from inside the Space.** Type a word, get the model's title, version, file, size, downloads and
|
| 49 |
+
trigger words, and drop the one you want straight into a slot. A link that is already in a slot can be named with
|
| 50 |
+
one press, so a row reads *Hairy dad butt · v1.0 · hairy.safetensors* rather than a number. Gated entries need
|
| 51 |
+
`CIVITAI_TOKEN` under *Settings → Variables and secrets*: the Space downloads on its own machine, so being signed in
|
| 52 |
+
to CivitAI in a browser does not authenticate it — a gated model answers a server with an HTML login page, which is
|
| 53 |
+
caught and reported rather than saved as weights.
|
| 54 |
+
|
| 55 |
+
**kohya, CivitAI and LoKr files are converted on the fly.** Most of what CivitAI carries is kohya-named, and it
|
| 56 |
+
differs from diffusers in ways that quietly ruin a result rather than raise: flat underscored names, a fused QKV
|
| 57 |
+
interleaved *per attention head* (a plain three-way split hands q's rows to k), a gated MLP whose two halves sit in
|
| 58 |
+
the opposite order, and an `alpha` that sets the scale. LyCORIS **LoKr** goes further and stores each layer as the
|
| 59 |
+
Kronecker product of two small factors, which PEFT cannot load at all; it is rebuilt into an ordinary low-rank pair
|
| 60 |
+
at load time — exactly, since the SVD of a Kronecker product is the outer product of the factors' SVDs, so nothing
|
| 61 |
+
the size of the full 7168×7168 layer is ever built. `.pt` files are read as well as `.safetensors`.
|
| 62 |
+
|
| 63 |
+
**Turbo presets.** Few-step distillations that render joint video + soundtrack in 4–8 steps instead of the usual ~28.
|
| 64 |
+
Picking one fills a free slot at that build's own strength and moves the steps slider to the count it was distilled
|
| 65 |
+
for.
|
| 66 |
+
|
| 67 |
+
**Continue the scene.** One press takes the last frame of the clip you have, makes it the first reference, generates
|
| 68 |
+
again with the settings untouched, and joins the two into a single file — soundtrack included. Press again for a
|
| 69 |
+
third clip. Each press costs one normal generation; the joining is free.
|
| 70 |
+
|
| 71 |
+
**Clip stitching, soundtrack included.** Queue several clips and they are joined into one file; tick the box and
|
| 72 |
every new generation joins on its own. It runs on the CPU, so it costs nothing from the GPU allowance. Everything is
|
| 73 |
scaled to the first clip's frame, and a clip without audio gets silence rather than breaking the join.
|
| 74 |
|
|
|
|
| 80 |
**Randomize seed**, drawn fresh per press and written back into the box, so the number shown is always the one the
|
| 81 |
clip was made with.
|
| 82 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
## Why split
|
| 84 |
|
| 85 |
MiniMax-H3 is 195.9 GiB in bfloat16 and a ZeroGPU Space is evicted at **150 GB of storage**. An unquantized single
|
|
|
|
| 98 |
`<Video 1>`, `<Audio 1>`) and it advances the shared audio/video rotary clock, so the same references in a different
|
| 99 |
order are a different request. This demo lays the slots out as one tab per modality in reading order — images, then
|
| 100 |
audio, then video — and assembles the request that way. The Images tab opens with two slots and **+ Add another
|
| 101 |
+
image** reveals the rest, up to the model's own nine.
|
|
|
|
|
|
|
| 102 |
|
| 103 |
Rules the model imposes, enforced here before anything is uploaded:
|
| 104 |
|
|
|
|
| 108 |
which is why the duration slider disappears when a single reference can set it, and comes back when two can or
|
| 109 |
when the one that could is out of range.
|
| 110 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
## Generation constraints
|
| 112 |
|
| 113 |
Fixed by the checkpoint: 24 fps, a 768 pixel short edge, 5 to 15 s, `num_frames` snapped up to the next `17 * n + 5`,
|
| 114 |
no CFG and no negative prompt (it is guidance-distilled, so every step is one forward pass). The duration slider
|
| 115 |
+
stops at 14 s because it is the *snapped* count that has to hold for the ceiling: 15 s is 360 frames, which rounds up
|
| 116 |
+
to 362, i.e. 15.083 s, and is refused.
|
| 117 |
|
| 118 |
## GPU time is reserved per request, not per Space
|
| 119 |
|
| 120 |
+
MiniMax-H3 attends over one packed sequence, so what a step costs is a function of that sequence's length alone — and
|
| 121 |
+
on this half the references dominate it. A single 1344x768 image reference is ~7168 conditioning rows plus the vision
|
| 122 |
+
block it puts in front of the prompt; a 2.5 s video reference is another ~17000. The same 960x544, 124-frame request
|
| 123 |
+
runs 2.4 s/step with no references and 16 s/step with an image and a video.
|
| 124 |
|
| 125 |
+
`get_duration` prices that before the call instead of reserving a flat ceiling for everything. It takes the arguments
|
| 126 |
+
of the `@spaces.GPU` function, so it has the conditioner's own `text_token_tags` (exact) and the reference files
|
| 127 |
+
(measured from metadata, no decode), and evaluates
|
| 128 |
|
| 129 |
```
|
| 130 |
S = text rows + reference rows + target rows
|
|
|
|
| 135 |
pool reserves whatever number it is given, and a flat 900 s is what makes a busy account fail admission with *"You
|
| 136 |
have too many ZeroGPU credits allocated to running tasks."* A typical single-image request now reserves ~460 s.
|
| 137 |
|
| 138 |
+
## Nothing is paid for with GPU time that does not have to be
|
| 139 |
+
|
| 140 |
+
The 77.3 GB download and the load happen at **startup**: `import spaces` at module top patches `torch.cuda` before
|
| 141 |
+
any GPU is attached, so nothing about the load needs a card. The conditioner round trip is a network call on this
|
| 142 |
+
Space's CPU. So is every lora download, every CivitAI lookup, and all clip stitching. A `@spaces.GPU` call is only
|
| 143 |
+
the placement (once), the two reference encoders, the denoise loop and the two decoders.
|
| 144 |
|
| 145 |
## Space variables
|
| 146 |
|
|
|
|
| 150 |
| `H3_MODEL_REPO` | `MiniMaxAI/MiniMax-H3` | The diffusers-layout checkpoint. Public. |
|
| 151 |
| `H3_AOTI` | `0` | `1` loads the compiled block package. |
|
| 152 |
| `H3_PLACEMENT` | `lazy` | `lazy` moves all 72.16 GiB onto the card on the first GPU call and leaves it there; `offload` hands placement to `ComponentsManager.enable_auto_cpu_offload` instead. |
|
| 153 |
+
| `H3_ATTENTION` | `_native_cudnn` | cuDNN's fused kernel, 10–20% faster than the SDPA default. flash-attention 3 is sm90-only and this pool is sm120. |
|
| 154 |
| `H3_GPU_DURATION_MIN` / `_MAX` | `120` / `1500` | Bounds on what `get_duration` may reserve. |
|
| 155 |
| `H3_PLACEMENT_ALLOWANCE` | `90` | Seconds of the reservation set aside for a cold worker's placement. |
|
| 156 |
| `H3_GPU_SIZE` | `xlarge` | ZeroGPU allocation size. `large` does not fit. |
|
| 157 |
+
| `H3_LOKR_RANK` | `32` | Rank the LoKr converter targets. Raise it if the log reports a weak layer. |
|
| 158 |
+
| `CIVITAI_TOKEN` | unset | Used for CivitAI downloads and search, and required for gated or adult entries. |
|
| 159 |
+
| `CIVITAI_API_HOST` | unset | Pins the search host; otherwise `civitai.red` is asked first and `civitai.com` is the fallback. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
|
| 161 |
## Where diffusers comes from
|
| 162 |
|
| 163 |
MiniMax-H3 is modular-only and not in a released `diffusers`, so `requirements.txt` installs it from the canonical
|
| 164 |
pull request, [huggingface/diffusers#14371](https://github.com/huggingface/diffusers/pull/14371), pinned to the commit
|
| 165 |
+
`665f5782` rather than to the moving `minimax-h3-refactor` branch. That PR is a WIP, so it needs re-pinning whenever
|
| 166 |
+
it updates, and `h3_split_blocks.py` has to be re-checked against the new head at the same time.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
|
| 168 |
+
## Attribution
|
|
|
|
|
|
|
| 169 |
|
| 170 |
+
An optimized derivative of [`multimodalart/minimax-h3`](https://huggingface.co/spaces/multimodalart/minimax-h3).
|
| 171 |
+
MiniMax-H3 weights remain governed by the MiniMax-H3 Community License Agreement.
|