Custom LoRAs
Upload your own LoRA once, then use it in any generation request.
Upload your own LoRA, then pass its ID as adapter in a normal generation
request. Custom LoRAs are available through the REST API.
Generating with a custom LoRA costs extra. See Pricing for the rates.
| Supported today | |
|---|---|
| Model | MiniMax-H3 only, for now |
T2V, I2V | LoRAs trained on the FL2VA base |
R2V | LoRAs trained on the Ref2VA base |
| Resolution | 480p, 768p, 1080p |
| Duration | 1-15 seconds |
| Request mode | Asynchronous only (no sync mode, no long video) |
Prepare your LoRA
An upload is one .safetensors weights file plus two small JSON configs: one
describing the file, and one describing how to sample with it. Each upload has
one of each, sent as the adapter_config and inference_config objects in the
reserve request.
adapter_config
Says which base the LoRA was trained on and how its tensors are laid out.
{
"base_model": "minimax-h3",
"base_partition": "FL2VA",
"export_format": "h3-native-v1",
"scaling": { "mode": "unit" }
}| Field | Values |
|---|---|
base_model | minimax-h3 |
base_partition | FL2VA for T2V and I2V, Ref2VA for R2V. To use one LoRA for both, upload it once per base. |
scaling | {"mode": "unit"} for no scaling, {"mode": "alpha_over_rank", "alpha": <number>}, or {"mode": "alpha_tensor_over_rank"} when the file stores an .alpha tensor per layer. |
export_format | How your file names its tensors, see below. |
Each layer needs a .lora_A.weight and .lora_B.weight pair in BF16.
export_format | Use when your tensor names look like |
|---|---|
machgen-h3-direct-v1 | diffusion_model. prefix, separate Q/K/V (common for ComfyUI exports) |
h3-native-v1 | Native H3 module names, no prefix |
h3-comfy-packed-v1 | One packed QKV projection per block (ComfyUI packed exports) |
h3-diffusers-hyperflow-v1 | Diffusers-style split Q/K/V with HyperFlow time embedders |
inference_config
Sets the default strength, an optional trigger phrase, and the sampling recipe your LoRA was trained for.
{
"default_strength": 1.0,
"trigger_prompt": "",
"sampling": {
"num_inference_steps": 6,
"guidance_scale": 1.0,
"video_shift": 12.0,
"audio_shift": 3.0,
"sampler": "euler",
"sigma_grid": "linspace"
}
}trigger_promptis added to the start of every prompt for you. Leave it empty if your LoRA has no trigger word.guidance_scalemust be1.0.sampleriseulerorres_multistep.sigma_gridislinspace,simpleorfixed.res_multistepneedssigma_grid: "simple".simpleandfixedneedaudio_shift: 3.0.- A
fixedgrid also needsfixed_sigmas: steps + 1 values decreasing from 1 to 0.
Upload
Uploading takes three calls: reserve, send the file, commit.
1. Reserve
POST /api/v0/adapters?model=MiniMax-H3 with the file's size and MD5 (md5sum
on Linux, md5 -q on macOS) and both configs.
curl -X POST "https://api.machgen.ai/api/v0/adapters?model=MiniMax-H3" \
-H "Authorization: Bearer $MACHGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"size_bytes": 779849816,
"md5": "<md5 of the file>",
"adapter_config": {...},
"inference_config": {...}
}'The response carries your LoRA's ID, which starts with up-, and a one-time
upload_url:
{
"adapter": "up-3f9c0e5b7a2d41e8c6f0b9a1d2e3c4b5a6f7e8d9",
"model": "MiniMax-H3",
"status": "pending",
"size_bytes": 779849816,
"created_at": 1791820800,
"expires_at": 1792425600,
"chunk_size": 8388608,
"upload_url": "https://storage.googleapis.com/upload/..."
}The ID is derived from the file and its configs, so reserving the same upload
again returns the same ID. Each reserve hands out a fresh upload_url and
retires the previous one, so always send the file to the latest URL.
upload_url is null when the file already arrived; go straight to commit.
If status is already ready, there is nothing left to do.
2. Send the file
PUT the weights to upload_url. Treat the URL like a password: anyone
holding it can write your file. It stays valid for 7 days.
curl -X PUT -H "Content-Type: application/octet-stream" \
--upload-file my_lora.safetensors "$UPLOAD_URL"For large files, upload_url also accepts resumable uploads in pieces that
are multiples of chunk_size, so an interrupted transfer can pick up where it
stopped.
3. Commit
Checks the file and makes the LoRA ready to use.
curl -X POST "https://api.machgen.ai/api/v0/adapters/$LORA_ID/commit" \
-H "Authorization: Bearer $MACHGEN_API_KEY"It returns the same shape as reserve, with status: "ready". Commit is safe to
repeat.
Generate
Add your LoRA's ID as adapter in a normal MiniMax-H3 request. Use an
FL2VA LoRA for T2V or I2V, and a Ref2VA LoRA for R2V.
curl https://api.machgen.ai/api/v0/generate \
-H "Authorization: Bearer $MACHGEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "MiniMax-H3",
"task_type": "T2V",
"adapter": "up-3f9c0e5b7a2d41e8c6f0b9a1d2e3c4b5a6f7e8d9",
"prompt": "A paper boat drifting down a rainy street at dusk",
"video_config": {"duration_secs": 5, "height": 768, "aspect_ratio": "16:9"}
}'- The number of steps comes from your LoRA's
inference_config. Leaveinfer_stepsout of the request. - Your
trigger_prompt, if set, is added to the prompt automatically.
The first generation with a newly uploaded LoRA takes longer while the LoRA is prepared.
Manage
Each LoRA is either pending (reserved, not committed yet) or ready (usable
in requests). A pending upload expires 7 days after it was reserved. A ready
LoRA stays until you delete it.
# All your LoRAs, plus how much of your storage you've used
curl -H "Authorization: Bearer $MACHGEN_API_KEY" \
https://api.machgen.ai/api/v0/adapters
# One LoRA
curl -H "Authorization: Bearer $MACHGEN_API_KEY" \
https://api.machgen.ai/api/v0/adapters/$LORA_ID
# Delete a LoRA (safe to repeat)
curl -X DELETE -H "Authorization: Bearer $MACHGEN_API_KEY" \
https://api.machgen.ai/api/v0/adapters/$LORA_IDThe list response:
{
"adapters": [
{
"adapter": "up-3f9c0e5b7a2d41e8c6f0b9a1d2e3c4b5a6f7e8d9",
"model": "MiniMax-H3",
"status": "ready",
"size_bytes": 779849816,
"created_at": 1791820800
}
],
"used_bytes": 779851204,
"max_bytes": 15000000000,
"used_count": 1,
"max_count": 100
}New requests with a deleted LoRA are refused right away. LoRAs are private to your account: nobody else can see or use them.
| Limit | Value |
|---|---|
| Weights file | 5 GB |
| Each config | 1 MB |
| Storage per account | 15 GB |
| LoRAs per account | 100 |
Troubleshooting
| You see | What it means | What to do |
|---|---|---|
409 on commit: weights have not been uploaded yet | The file didn't finish uploading, or a newer reserve replaced its URL | Reserve again, send the file to the new upload_url, then commit |
400 on commit: size or MD5 does not match | The file differs from what you reserved | Reserve again (same ID, new upload_url) and send the correct file |
400 on commit: invalid safetensors header | The file isn't a valid LoRA safetensors file | Check the file and export it again |
409 changed concurrently, retry | Two identical requests ran at the same time | Retry the call |
409 storage quota exceeded | Your account is at 15 GB or 100 LoRAs | Delete LoRAs you no longer need |
404 adapter upload not found | Wrong ID, deleted, or a pending upload older than 7 days | Upload again |
400 No adapter named ... in your account | The LoRA isn't ready, was deleted, or the ID has a typo | Check the ID with GET /api/v0/adapters |
400 This adapter requires the ... task partition | An FL2VA LoRA on R2V, or a Ref2VA LoRA on T2V / I2V | Use a LoRA trained for that task type |
400 infer_steps must match the uploaded inference_config | The request set a different step count | Leave infer_steps out |
| Task fails with "Invalid customer H3 adapter" | The file's tensors don't match MiniMax H3 or the chosen export_format | Check export_format, scaling and base_partition, then upload again |