Huggingface Lora Space Builder
Hugging FaceOfficialPublished by Hugging Face
- Skill
- Workflow
- Agent tooling
- Apache-2.0
- Low risk
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill guides the user through the end-to-end process of building and publishing a Gradio demo for a specific LoRA on Hugging Face Spaces. It handles the identification of base models, selection of appropriate inference pipelines, UI design tailored to the LoRA's specific task, and the generation of necessary configuration files. The workflow ensures that the resulting Space is configured for ZeroGPU hardware and follows best practices for model loading and dependency management.
What it does
- Analyzes LoRA repositories to determine base models, tasks, and inference requirements.
- Selects the correct base pipeline and inference recipe based on model card data.
- Designs a custom Gradio UI tailored to the specific inputs and functionality of the LoRA.
- Generates app.py, requirements.txt, and README.md files for the Space.
- Configures Space metadata for ZeroGPU hardware and model discovery.
- Publishes the final demo as a private Hugging Face Space.
When to use it
- When you need to create a Gradio demo for a LoRA hosted on the Hugging Face Hub.
- When you want to share a trained LoRA with a functional web interface.
- When you need to deploy a model demo to ZeroGPU hardware.
- When you require assistance in mapping a LoRA's specific task to an appropriate UI layout.
Advantages
- Ensures the demo is customized to the LoRA's specific input and output requirements.
- Automates the complex configuration of ZeroGPU-compatible Space files.
- Reduces manual effort by batching information gathering and file generation.
- Promotes consistent and reliable deployment patterns for diffusion models.
Usage examples
- “Create a Gradio demo for my LoRA at username/my-flux-lora.”
- The agent will analyze the repository, identify the base model, design a UI for the LoRA's task, and generate the files required to publish a private Space.
- “I have a new LTX-Video LoRA. Can you build a playground for it?”
- The agent will determine the necessary LTX pipeline, configure the Space for video processing, and prepare the code to launch a functional demo.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/huggingface/huggingface-lora-space-builder/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip huggingface-lora-space-builder.zip -d .agents/skills
Documentation by Hugging Face
Gradio LoRA Space Builder
Build and publish a Gradio demo on Hugging Face Spaces that runs inference with a user-provided LoRA. Use whenever someone asks to create, generate, ship, or publish "a Space", "a demo", "a Gradio app", or "a playground" for a LoRA — whether the base model is Qwen-Image, Qwen-Image-Edit, LTX, or another diffusion model. Also use when someone describes a LoRA they trained or hosts on the Hub and wants to share it. The default target is ZeroGPU hardware and the default inference library is diffusers when the base model supports it.
The output is a real, published Space (private by default) that the user can try in the browser, not a local script.
What "good" looks like for these demos
The demo should feel handcrafted for this specific LoRA, not a generic template with the LoRA bolted on. Two LoRAs that share a task can still need different demos: a pose-control video LoRA and an outpainting video LoRA both take video in and produce video out, but the inputs the user provides, the preprocessing, and the controls are completely different. Recognizing that is the central job here.
Concretely, a good demo:
Loads fast and runs fast — minimal model loading, sensible step count, no wasted computation per call.
Has a UI with exactly the controls this LoRA needs and nothing else. Excess sliders are a cost, not a feature.
Shows the user what's happening — progress, intermediate outputs where useful, the seed used, a clear error when input is missing.
Honors the LoRA's own recommendations from its model card: trigger words, recommended step count, recommended guidance scale, recommended LoRA scale, example inputs.
Is creative where creativity helps — interactive canvases, before/after sliders, side-by-side previews of intermediate processing — and plain where plainness is right.
Workflow
Work through these phases in order. Information gathered in one phase decides the next.
Gather the LoRA info needed to pick a pipeline and design a UI.
Pick the base pipeline and inference recipe.
Design the UI for this specific LoRA's task and inputs.
Write
app.py,requirements.txt, andREADME.mdtogether; show all three to the user for one batched approval.Publish the Space (private).
Don't drip-feed questions across multiple turns. Batch them.
Phase 1 — Gather LoRA info
Required: a LoRA repo on the Hub (e.g. username/my-lora).
First, try to read the repo without a token. If it succeeds, the repo is public — proceed. If it fails with 401/403, the repo is private/gated and you need an authenticated session to read it. Don't immediately ask for a token. Check first whether the user is already authenticated.
from huggingface_hub import HfApi, get_token
cached_token = get_token() # picks up HF_TOKEN env var or cached CLI login
if cached_token:
try:
info = HfApi().whoami(token=cached_token)
username = info["name"]
# info also has fine-grained token scope info if applicable
except Exception:
cached_token = None # token exists but is invalid/expiredThen:
If a valid cached token exists and it can read the repo, use it. No prompt needed.
If no cached token, or the cached token can't read this private repo, ask the user for a token — once, with the explanation below.
When asking for a token (and only when you actually need to ask):
I need a Hugging Face access token with write scope (to read the LoRA if it's private/gated, and to publish the Space). Create one at https://huggingface.co/settings/tokens. Paste it here.
The same token will be reused for publishing in the final phase, so this is a one-time ask.
Then read what's in the repo:
List the repo files (
huggingface_hub.HfApi().list_repo_files(repo_id)). Look for.safetensors,README.md, example images/videos, multiple checkpoints.Fetch the model card (
huggingface_hub.ModelCard.load(repo_id)). Thedatadict has structured fields; thetexthas the README body.If multiple
.safetensorsfiles exist, pick the right one — see "Picking the LoRA weights file" inreferences/zerogpu-and-publishing.md. Briefly: README-recommended file wins, thenpytorch_lora_weights.safetensors, then latest training checkpoint, otherwise ask.
From the model card, try to determine:
Base model — the
base_modelfield, or text mentions in the README. Usually present. Use it to pick the pipeline reference file (see Phase 2).Task —
pipeline_tagif set, otherwise inferred from the base model and README text. The five tasks this skill handles:text-to-image,image-to-image,text-to-video,image-to-video,video-to-video.Trigger words — often called "trigger word", "instance prompt", "activation word"; sometimes embedded in example prompts.
Recommended inference recipe — step count, guidance scale, true CFG scale, LoRA scale, resolution. Many LoRA cards include a Python snippet; trust its parameters (steps, guidance, CFG, LoRA scale, dtype). For loading mechanics, see
adapting-to-the-lora.md— preferpipe.load_lora_weights(...)over whatever loading approach the snippet uses.Example prompts and example media — use these as Gradio examples in the UI.
Sub-task / specific use case — for image edits and video LoRAs, "what does this LoRA actually do" matters as much as the task category. A relighting LoRA, a face-swap LoRA, and a style LoRA all might be image-to-image, but the UI for each is different.
When something can't be inferred, ask the user — once, in a single batched message. Format the question to make answering trivial. For task category, list the five options as a numbered choice. For sub-task, give a one-line description ("what does this LoRA do? e.g. 'relight portraits', 'apply manga style', 'extend videos to wider aspect ratios'"). Don't ask if you can already infer it confidently from the base model or README.
If the model card has nothing helpful at all — no base model, no task, no example — surface that clearly: "The model card has no usable info. I'll need you to tell me: (1) base model, (2) what this LoRA does, (3) recommended step count and guidance scale if you know them."
Phase 2 — Pick the base pipeline
Two things to decide here: which reference file to load, and which pipeline class to use. They're not the same question — a base-model family file (e.g. qwen-image.md) covers multiple variants, and variants in the same family don't always share a pipeline class. Get this wrong and the Space loads but produces wrong output, or fails at startup.
Step 1 — Load the reference file for this base model family.
references/base-models/qwen-image.md— covers Qwen-Image and Qwen-Image-Edit family (text-to-image and image-to-image).references/base-models/ltx.md— covers LTX family (text-to-video, image-to-video, video-to-video, including IC-LoRAs).references/base-models/krea-2.md— covers Krea 2 (K2), text-to-image (train on RAW, run inference/LoRAs on the Turbo distilled checkpoint).
If the base model isn't in one of these files, this skill doesn't have first-class support yet. Tell the user, and ask whether they want to proceed by analogy (use the closest model's recipe and adjust) or stop. Don't guess silently.
Step 2 — Verify the pipeline class against the base model's own card. This step is mandatory, not optional.
A new base model variant might use the same pipeline class with a different repo path, or a new pipeline class entirely. Don't trust the reference file's table alone — it's best-effort and can lag a recent release. Verify before committing:
from huggingface_hub import ModelCard
base_card = ModelCard.load(base_model_id)
# Read base_card.text — find the diffusers inference snippet, note the pipeline class it imports.The class imported in the base model card's diffusers snippet is the source of truth. Real examples where this matters:
Qwen-Image-EditusesQwenImageEditPipeline.Qwen-Image-Edit-2509andQwen-Image-Edit-2511useQwenImageEditPlusPipeline— different class, different default parameters, takes a list of images instead of one. A LoRA targeting 2511 loaded ontoQwenImageEditPipelineproduces broken output.LTX-Video uses
LTXPipeline/LTXImageToVideoPipeline/LTXConditionPipeline. LTX-2 usesLTX2Pipelinefrom a different module path. LTX-2.3 sometimes needs a native pipeline outside diffusers.
If the base model card has no diffusers snippet at all, fall back to the reference file's table — and tell the user you're falling back, in case they know something the table doesn't.
The cost of this verification is one Hub fetch and a few seconds of reading. The cost of skipping it is the failure mode the previous bullet describes — a "working" Space that's quietly using the wrong class.
Step 3 — Diffusers vs native pipeline. Default to diffusers when the base model has a diffusers pipeline class. That's the case for Qwen-Image and Qwen-Image-Edit and most of LTX. Some LTX variants (notably LTX-2.3 with certain IC-LoRAs) need a native pipeline; the LTX reference says when. Diffusers gives standard load_lora_weights / set_adapters semantics; the native path needs LoRA-specific glue.
Phase 3 — Design the UI for this LoRA
Don't reach for a template. Reason from the LoRA's task and inputs to a UI.
Read references/tasks.md for the per-task baseline UI patterns (what the standard inputs/outputs look like for T2I, I2I, T2V, I2V, V2V).
Then read references/adapting-to-the-lora.md, which is about thinking through what this specific LoRA needs — beyond the task category. That file is the most important one in this skill. The same task can need very different UIs: a pose-control LTX LoRA needs a video input and a pose-extraction preview; an outpaint LTX LoRA needs an aspect-ratio picker and a black-margin preview; a relighting Flux LoRA needs an image and a brush canvas for indicating where to add light. None of those reduce to "the V2V template" or "the I2I template".
Self-check before writing the UI. Write one sentence describing what a user does with this Space in 10 seconds. If that sentence doesn't distinguish this LoRA from any other LoRA of the same task, the UI isn't shaped enough yet.
Examples that pass the self-check:
"Upload a video, pick a target aspect ratio, click Generate; the model fills the empty margins."
"Draw colored brush strokes where you want light, pick an illumination style, click Generate; the model relights the photo."
"Upload a video of someone moving and an image of a different character; the model produces a video of the character doing the motion."
Examples that fail:
"Type a prompt and click generate." (Generic T2I — say more.)
"Upload an image and an instruction." (Generic edit — what kind of edit?)
Gradio component freshness. Gradio's component set evolves. Before defaulting to plain components, consider whether something newer fits better — for example gr.ImageSlider for before/after on edit LoRAs, gr.BrowserState for persistent prefs, @gr.render for UIs that change based on input. If you're unsure whether a component exists or what its signature is, web-fetch the current Gradio docs at https://www.gradio.app/docs rather than guessing.
When stock and Hub custom components aren't enough — creative mode. If the LoRA's natural input is a shape no Gradio component (built-in or on the Hub) expresses well — point sets, strokes, trajectories, multi-region annotations with metadata, 3D rotation gizmos, timeline scrubbers, anything where the user manipulates a thing on top of media — drop down to custom HTML/JS via gr.HTML. See references/creative-mode.md for the Gradio primitives (gr.HTML, head= injection, elem_id addressing, the two JS↔Python state-sync approaches), the discipline around defining a JSON wire format, and the pitfalls. Don't reach for creative mode just because it would be cool — reach for it when the LoRA's input shape demands it. And don't skip the Hub custom components rung above (e.g. gradio_image_annotation) before going fully bespoke.
gr.Examples for media-input Spaces. When no fitting example media is available from the model's own repo, pull from the shared input pools — split by modality so the HF dataset viewer can render proper thumbnails: images at linoyts/repo-to-space-example-inputs, videos at linoyts/repo-to-space-example-videos. Both are CC0 with categories + natural-language caption metadata and the same filter/rank recipe in each dataset README. Pick 2–3 that fit the task, preprocess to the shapes the model expects, and bake the copies into the Space. Set cache_examples=True, cache_mode="lazy" so the first click caches without running examples at build time (see references/zerogpu-and-publishing.md).
Phase 4 — Write the Space files
Before writing, tell the user concretely what's about to happen — name the actual files. Not "I'll write the three files" but something like:
"Now I'll write the three files needed to publish a Space:
app.py(the Gradio demo and inference code),requirements.txt(Python dependencies), andREADME.md(Space configuration including ZeroGPU hardware setting). Then I'll show all three for your review before publishing."
This anchors the user in what's being produced. Don't say "three files" without naming them — it's vague and signals lack of commitment to the deliverable.
The three files are tightly coupled: requirements.txt is determined by what app.py imports, and the README.md YAML frontmatter sets the SDK version, hardware, and Space title that have to match. Write them together, then show all three to the user for approval in one batched message before publishing.
Read references/zerogpu-and-publishing.md for the ZeroGPU rules. The non-obvious ones:
Models go on
cudaat module level (not lazy-loaded inside the GPU function). ZeroGPU has a CUDA emulation that makes this work pre-allocation, and module-level placement is significantly faster than deferred placement.The function that runs inference is decorated with
@spaces.GPU(duration=...). Pick a duration appropriate for the task — short for image generation, longer for video.Don't use
torch.compile— it's incompatible with ZeroGPU's process model.
app.py
Compose from the pieces decided in Phases 1–3. Don't paste from a template. Each section should be there because it's needed:
Imports —
gradio as gr,torch,spaces, the pipeline class, anything the preprocessing needs.Constants —
LORA_REPO,BASE_MODEL, recommended step count, guidance, LoRA scale, trigger word.Module-level model load — pipeline
from_pretrained,.to("cuda"),load_lora_weights. If the LoRA repo is private, passtoken=os.environ["HF_TOKEN"].Preprocessing functions (if any) — pose extraction, padding, mask building, etc. CPU code can run at module level; GPU code needs to be inside a
@spaces.GPUfunction.The inference function — decorated with
@spaces.GPU(duration=...). Validates inputs, applies trigger word, builds the pipeline kwargs, returns outputs.The Gradio Blocks — the UI from Phase 3, wired to the inference function.
Common things to get right:
Return the actually-used seed alongside the result so the user can reproduce.
gr.Progress(track_tqdm=True)on the inference function surfaces diffusers' internal progress bar.Validate inputs — raise
gr.Error("Please upload an image first.")when a required input is missing, rather than letting the pipeline fail with a cryptic error.On
gr.Examples, usecache_examples=True, cache_mode="lazy"— plaincache_examples=Trueruns examples at build time and fails on ZeroGPU; lazy mode defers caching to the first user click.When
gr.Exampleshasfn=, clicking a row callsfnwith only theinputs=values, positionally — so everyfnparameter not ininputsneeds a default, or the click raisesTypeError: missing N required positional arguments. The run event wires all components, so this passes manual and smoke tests and only breaks on the example click. Fix: default the extra params (keepinputs=[prompt]), or list every input with full example rows, or dropfn/outputsso a click just fills the fields.
requirements.txt
Don't ship a fixed minimal list and hope for the best. The "minimal" list works for plain T2I LoRAs and breaks the moment the base model has a vision-language text encoder, video output, or any non-trivial preprocessing. Derive requirements.txt from what the Space actually needs, in this order:
Every top-level non-stdlib import in
app.py. Ifapp.pydoesimport cv2,requirements.txthasopencv-python. If it doesfrom controlnet_aux import OpenposeDetector,requirements.txthascontrolnet-aux. Walk the imports mechanically. (Note the exclusions in the next paragraph — some imports are runtime built-ins and don't need to be listed.)What the base-model reference's "Required dependencies" subsection says. Each base-model file lists the non-obvious extras the pipeline pulls in —
torchvisionfor Qwen-Image (Qwen 2.5-VL text encoder),imageio[ffmpeg]for LTX (video export), etc. Include all of them. These are the deps that aren't picked up from imports because the pipeline's components import them transitively at load time.What the LoRA's own model card explicitly mentions installing. If the LoRA README has its own
pip installblock, lift the deps from there.The diffusers/ML stack:
diffusers,transformers,accelerate,peft,safetensors. Default to plain (unpinned). Switchdiffuserstogit+https://github.com/huggingface/diffusersif the base-model reference says the model needs it (recent releases often do — Qwen-Image-Edit-2511 is a current example).
What not to list in requirements.txt:
gradio— controlled by thesdk_version:field inREADME.md's YAML frontmatter, not byrequirements.txt. Listing it in requirements is at best ignored, at worst causes a version conflict with the SDK. Set the version in the README only.torch— provided by the Space runtime. Only add if you need a specific version pinned (rare, and usually a sign something else is wrong).spaces— provided by the Space runtime. Only add if you need a specific version pinned.huggingface_hub— provided by the Space runtime. Only add if you need a specific version pinned.
These four come pre-installed in the ZeroGPU container. Listing them anyway is the kind of "include rather than skip" instinct that's right for non-baseline deps but wrong for baseline ones, because pinning conflicts with the runtime's managed versions.
Bias for everything else: include rather than skip when uncertain. A package the Space doesn't actually use causes a slightly slower build. A missing required package causes a startup-time crash that's much harder for the user to diagnose. These costs aren't symmetric — the test failure that prompted this rule was exactly the second kind.
But two specific deps are not safe to add reflexively because they routinely cause more problems than they solve on ZeroGPU:
xformers— pinned to specific torch versions, frequent source of conflicts. The ZeroGPU runtime ships torch 2.8+, so any pinnedxformersversion must support that. Additional gotcha on Blackwell: xformers' FA3 dispatch mis-gates the hardware (FA3 kernels are Hopper-only atsm_90a, but the dispatcher gates ondevice_capability >= (9, 0), which also matches Blackwell) and crashes at kernel launch withCUDA invalid argument. If a Space using xformers attention hits this, disable FA3 dispatch at module load:try: from xformers.ops.fmha import _set_use_fa3 _set_use_fa3(False) except Exception: passOnly include
xformersifapp.pyactually uses it.flash-attn— needs a build step, often fails to install. Same torch 2.8+ alignment caveat asxformers. Only include ifapp.pyactually uses it.
Pin other versions only when you have a reason (e.g. a known incompatibility, or matching a recipe from the model card).
README.md
Spaces are configured by the YAML frontmatter at the top of README.md. This frontmatter is what selects ZeroGPU.
---
title: <human-readable title>
emoji: 🎨
colorFrom: pink
colorTo: purple
sdk: gradio
sdk_version: <current Gradio version>
app_file: app.py
pinned: false
hardware: zero-a10g
short_description: <one short line for the Space tile, ~60 chars max>
models:
- <base model repo>
- <lora repo>
---
# <title>
A short description with links to the LoRA and base model.Key fields:
sdk: gradio— required for ZeroGPU.sdk_version— match the Gradio version you wrote against. Look up the current version (pip index versions gradio, or check https://www.gradio.app) rather than guessing.hardware: zero-a10g— the legacy string for ZeroGPU. The actual hardware is NVIDIA RTX Pro 6000 Blackwell, but the identifier iszero-a10g. ZeroGPU is available to PRO, Team, and Enterprise accounts; if the user isn't subscribed, the Space will fall back to CPU. Mention this if you suspect they aren't on PRO.models:— list base and LoRA repos. This enables Hub caching and discovery.short_description— appears on the Space tile. Keep it short (~60 characters or less). The Hub's YAML validator rejects long values with a 400 fromhttps://huggingface.co/api/validate-yaml, which surfaces as anHfHubHTTPErrorduringcreate_repoorupload_file. The exact server-side limit isn't documented and may change, so target the visible-tile-length range rather than pushing right up to a cap. If you do hit the 400, the fix is almost always to shorten this field. One sentence describing what the Space does is plenty — the README body below the YAML is where you put longer prose.
Single batched approval — order of operations matters
The discipline here is write all three files first, then show them all together in one message. Not "write app.py → talk about it → write requirements → talk about it → write README → talk about it." That rhythm produces three approval moments even if you don't explicitly ask for approval, because the user is being asked to react after each file.
Concretely:
Write
app.py,requirements.txt, andREADME.mdin succession with no intervening prose. No commentary between files. No "Now I'll write the next one." No description of what each file does as you produce it. Just the three files, back to back.Then, in a single message, ask for approval covering all three at once. Something like: "Here's the Space —
app.py(N lines),requirements.txt, andREADME.md. Review and confirm to publish, or tell me what to change."The user responds once, covering whatever they want changed across any of the three files.
What to avoid:
Walking through
app.py's structure or design choices after writing it but before writing the others. Save commentary for either the pre-writing announcement (Phase 4 opening) or the single approval message after all three exist.Asking "ready for the next one?" or "want me to continue with requirements?" — those are implicit per-file approvals.
Showing one file inline and offering to "show the next when you're ready" — same trap.
Treating any of the three files as optional or as a follow-up. They are produced together as one deliverable.
If the user interrupts after seeing the first file with feedback or a question, that's fine — engage with it — but the rule still applies: the next time you produce code, produce all remaining files together, not one at a time.
Phase 5 — Publish the Space
Use the authenticated session from Phase 1. Default to private, so the user can vet the Space before flipping it public. Confirm the target username with the user before creating: "I'll publish to {username}/{space_name} — confirm?"
from huggingface_hub import HfApi, SpaceHardware
api = HfApi(token=hf_token)
username = api.whoami()["name"]
repo_id = f"{username}/{space_name}"
api.create_repo(
repo_id=repo_id,
repo_type="space",
space_sdk="gradio",
space_hardware=SpaceHardware.ZERO_A10G,
private=True,
exist_ok=True,
)
# Upload files
for path in ["app.py", "requirements.txt", "README.md"]:
api.upload_file(path_or_fileobj=path, path_in_repo=path,
repo_id=repo_id, repo_type="space")If the LoRA repo itself is private/gated, the Space needs the token at runtime to download the LoRA. Set it as a Space secret:
api.add_space_secret(repo_id=repo_id, key="HF_TOKEN", value=HF_TOKEN)…and in app.py, load the LoRA with token=os.environ["HF_TOKEN"].
After upload, run the smoke-test below before sharing — the build runs asynchronously and silent failures (wrong weight_name, missing dep, wrong pipeline class) only surface at first inference. Once the smoke-test passes, share the Space URL (https://huggingface.co/spaces/{repo_id}) and tell the user the Space is private — they'll need to be logged in to view it. Note that the build takes a few minutes; the logs are at https://huggingface.co/spaces/{repo_id}/logs/container if anything fails.
Publish-time failures (before the build starts):
HfHubHTTPError: 400 Bad Requestfromhttps://huggingface.co/api/validate-yamlduringcreate_repoorupload_file. The README YAML failed server-side validation. By far the most common cause is ashort_descriptionthat's too long; sometimes a stray field or malformed value. Fix: shortenshort_descriptionto ~60 characters and retry. If shortening doesn't fix it, look for typos in field names or invalid values (e.g. unsupported colors incolorFrom/colorTo, an invalidhardwarestring).403 on
create_repowithspace_hardware="zero-a10g": user isn't on PRO/Team/Enterprise, so they can't request ZeroGPU at creation time. Fix: retrycreate_repowithoutspace_hardware, leavehardware: zero-a10gin the README YAML — the Space gets created on CPU. The user can then either upgrade to PRO (auto-promotes to ZeroGPU) or apply for a community GPU grant (request via the Space's hardware settings).401/403 on
upload_file: token doesn't have write scope. Fix: ask the user for a write-scoped token.
Common build failures (after the build starts):
LoRA
weight_namemismatch inload_lora_weights→ check the actual filename vialist_repo_files.Base model is gated and the token wasn't set as a Space secret.
ZeroGPU not allocated (user not on PRO) → Space falls back to CPU and is unusably slow.
Diffusers version doesn't recognize the pipeline class → pin to git diffusers in
requirements.txt.Missing dependency at module load → see
requirements.txtderivation rules above; the most common case is a transitive dep liketorchvisionfor Qwen-Image's text encoder.
If a build fails, offer to read the logs and propose a fix.
Phase 6 — Smoke-test the Space
Before declaring the Space done and handing the URL to the user, exercise it once end-to-end. Several failure modes (wrong weight_name, wrong pipeline class, missing transitive dep, gated-base-model token issue) build cleanly and only surface at first inference. The gradio Python package ships a CLI that does exactly this — gradio info returns the endpoint signature, gradio predict runs an actual inference. Both ship with the gradio pip dependency the Space already needs, so they're available in any environment where this skill ran.
Step 1 — Wait for the build. create_repo returns immediately, but the container image is still building. Poll HfApi().get_space_runtime(repo_id).stage until it reaches RUNNING:
import time
from huggingface_hub import HfApi
api = HfApi(token=hf_token)
while True:
stage = api.get_space_runtime(repo_id).stage
if stage == "RUNNING": break
if stage in {"BUILD_ERROR", "RUNTIME_ERROR", "CONFIG_ERROR"}:
raise RuntimeError(f"Build failed: {stage}. Logs: https://huggingface.co/spaces/{repo_id}/logs/container")
time.sleep(15)If the build fails, fetch the container logs (https://huggingface.co/spaces/{repo_id}/logs/container), read the traceback, and propose a fix. Don't run gradio info against a Space that isn't running — it'll hang or 503.
Step 2 — Verify the endpoint signature. gradio info {repo_id} --token {hf_token} returns the exposed endpoints and their parameter types. Read the output and confirm: (a) the endpoint exists (default is /predict, but Blocks Spaces often have a custom name from the Python function name), (b) the parameters in order match what app.py declares, (c) file-typed params show "type": "filepath" as expected. If any of this is off, the user-facing UI may still appear correct but API calls will fail — fix and re-upload.
Step 3 — Run one real inference. Pick the lightest viable input — the simplest example from the LoRA card, or one of the gr.Examples entries. Pass --token for private Spaces. For file inputs, the payload uses {"path": "...", "meta": {"_type": "gradio.FileData"}}.
# Text-to-image:
gradio predict {repo_id} /predict '{"prompt": "...", "aspect_ratio": "1:1", ...}' --token $HF_TOKEN
# Image-to-image (file input):
gradio predict {repo_id} /predict '{"input_image": {"path": "/tmp/sample.jpg", "meta": {"_type": "gradio.FileData"}}, "prompt": "..."}' --token $HF_TOKENIf you don't have a local sample image for I2I, lift one from the LoRA repo (hf_hub_download(repo_id, filename="example.png")) or the base model card.
Caveat for creative-mode Spaces. gradio info and gradio predict only exercise the Python endpoint — they tell you nothing about whether custom JS in a gr.HTML widget works. If the Space uses creative mode (see references/creative-mode.md), after the API smoke-test passes, open the Space URL in a browser and verify the interaction once before sharing. Server-side green plus broken JS is the most common failure mode for these.
Step 4 — Interpret the result.
Returns successfully and the output looks plausible → done. Share the URL.
HTTPError 503 / "Space is sleeping" → the Space spun down between steps 1 and 3. Wake it (
api.restart_space(repo_id)) and retry.Inference error mentioning
weight_name/safetensors→ the LoRA filename inapp.pydoesn't match the actual file in the LoRA repo. Re-checklist_repo_files, fixweight_name=, re-uploadapp.py.Inference error mentioning a missing pipeline class or attribute → diffusers version too old. Switch
requirements.txttogit+https://github.com/huggingface/diffusersand re-upload.ImportErrorat module load → missing dep. Add it torequirements.txtand re-upload. The runtime logs (/logs/run) name the missing package.OOM → reduce default resolution or step count, or pick a smaller base variant.
Timeout / hangs → bump
@spaces.GPU(duration=...)and re-upload.
The smoke-test exists to convert these from "user discovers it and reports back" to "you discover it and fix it before sharing." Don't skip it because the build went green — green-build-broken-inference is the most common failure mode for Spaces with a non-trivial pipeline.
What to avoid
A generic "one demo for all LoRAs" template. The whole point of this skill is to tailor.
Lazy-loading the model inside the GPU function. Slow on ZeroGPU, and hides startup errors until first request.
torch.compile. Not supported on ZeroGPU.cache_examples=Truewithoutcache_mode="lazy"on ZeroGPU.gr.Examples(fn=…)whoseinputsdon't cover the inference function's required args — builds green, then crashes on the first example click with a missing-positional-argument error.Uploading the LoRA weights into the Space repo. Pull from the LoRA's own Hub repo at runtime.
Asking for the HF token only at the end, then discovering the LoRA was private all along and you couldn't read the model card.
Exposing every diffusers knob. Pick the 1–3 controls that matter for this LoRA.
Long preambles in the chat reply once the Space is published. The Space URL is the deliverable; keep the wrap-up brief.
Files
- ATTRIBUTION.mdAdded by AutoGineer904 B
- LICENSELicence file added by AutoGineer11.1 KiB
- SKILL.md33.0 KiB
- references/adapting-to-the-lora.md13.5 KiB
- references/base-models/krea-2.md4.0 KiB
- references/base-models/ltx.md15.0 KiB
- references/base-models/qwen-image.md7.3 KiB
- references/creative-mode.md18.7 KiB
- references/tasks.md10.1 KiB
- references/zerogpu-and-publishing.md14.2 KiB
references/adapting-to-the-lora.md
Adapting the demo to the specific LoRA
The task category (T2I, I2I, T2V, I2V, V2V) gets you a starting shape. It does not get you a UI. Two LoRAs in the same task category can need very different demos. This file is about the reasoning that gets you from "I know the task" to "I know what this demo should look like."
The core question
For each LoRA, ask: what does this LoRA actually need from the user, and what's the most natural way for the user to provide it?
These are different questions. The model might need a pose video as conditioning. That doesn't mean the user has to provide a pose video — they can provide a regular video and the demo extracts pose from it. The model might need a black-bordered video for outpainting. That doesn't mean the user uploads a pre-bordered video — they upload a normal video and pick an aspect ratio, and the demo adds the borders.
The job of the demo is to translate between what the user has (a video, a photo, an idea) and what the model wants (pose conditioning, padded frames, masked latents, a prompt prefixed with a trigger word).
Reading what the LoRA needs
Sources, in order of usefulness:
The model card's example code snippet — for parameters. If the README has a Python block showing how to call the LoRA, trust it for inference parameters: pipeline class, step count, guidance scale, true CFG, LoRA scale, dtype, resolution, negative prompt. Trust it for inputs: if it passes
image=..., the demo takes an image; if it passesimage=...andmask_image=..., the demo needs both.For loading mechanics, treat the snippet as a signal, not a directive. Prefer the standard diffusers path:
pipe.load_lora_weights(repo_id, weight_name=...). This is the maintained, well-tested path that handles DoRA, rsLoRA, custom target modules, and most format variants when diffusers + PEFT are recent enough. If the model card uses something else —PeftModel.from_pretrained(pipe.transformer, ...),diffsynth_engine, custom imports, manual state-dict surgery — that's a flag to investigate, not to copy.Reasons model card authors reach for non-standard loading paths often don't transfer: training-time conventions, environment quirks (older diffusers/PEFT versions, CPU offload patterns), or even malformed configs that pass silently in their setup but crash elsewhere. (Real example: an
adapter_config.jsonwithtask_type: "DIFFUSION"works locally on some PEFT versions but crashes on current PEFT, because PEFT'sTaskTypeenum only contains NLP tasks; diffusers' loader bypasses this validation by reading the safetensors directly.)Custom inference paths absolutely can work on ZeroGPU when needed (LTX-2.3 native pipeline is a real example). But default to diffusers because it's the standard, current, maintained path. Only adopt the model card's loading approach if
load_lora_weightsdemonstrably can't handle this LoRA — and when you do, port it to the ZeroGPU constraints (module-level.to('cuda'), noenable_model_cpu_offload).Trigger words and prompt patterns. "Use the trigger word X at the start of the prompt" → automatically prepend
Xto the user's prompt rather than asking them to type it. "Prompts should describe the scene as Y" → add prompt formatting examples or a placeholder. If the LoRA expects a structured input embedded in the prompt (like bounding-box coordinates or named regions), the UI should produce that structure for the user, not require them to type it.Example media in the repo. Example outputs tell you what the LoRA does. Example inputs tell you what the user has to provide. Example paired inputs+outputs (input video → output video, input image → output image) tell you the transformation. Lift these into
gr.Examplesso the user can click through them.The model's task family. A pose-conditioned model wants pose maps. A depth-conditioned model wants depth. An outpaint model wants padded frames with masked regions. Each implies preprocessing.
Recommended hyperparameters. Step count, guidance scale, true CFG, LoRA scale. Bake the recommended values in as defaults. Expose a slider only if the LoRA's behavior is sensitive to the value across a range (e.g. LoRA scale 0.7–1.3 produces meaningfully different results); otherwise it's just clutter.
When the model card has none of these, you have three options:
Infer from precedent. If similar LoRAs exist for the same base model, study their demos.
Ask the user. Once, batched, with concrete questions: "Does this LoRA have a trigger word? What's a recommended step count? Got an example prompt?"
Use sensible defaults from the base model. Worse than the previous two; only fall back to this when nothing else is available.
Verifying the pipeline class
The pipeline class is decided in Phase 2, not here. The procedure (read the base model's own card, trust its diffusers snippet over reference tables) is in SKILL.md under "Phase 2 — Pick the base pipeline." Mentioning it here as a pointer because skipping that verification is one of the most common ways to ship a broken Space — when in doubt, re-read Phase 2.
From "what the LoRA needs" to UI shape
For each thing the LoRA expects as input, decide:
Where does the user get this from? A pose video — extract from a regular video, or accept a pre-extracted pose video, or both? An aspect ratio — picker, sliders for width/height, or auto from input? A reference image — separate slot, or embedded in a "drag and drop here" area?
Can a more natural input be transformed into this? Almost always yes. A user has a video, not a pose video. A user wants "wider", not "padded with black bars at these specific coordinates". The demo bridges the gap.
Should the user see the intermediate? Often yes — a pose extraction preview, a letterbox preview, a generated mask preview. Showing the intermediate builds trust ("yes, the model is conditioning on what I expected") and helps the user iterate. But a preview that takes 10 seconds to generate every time the user changes a setting is a worse UX than no preview.
What's the smallest set of controls that lets the user actually drive this LoRA? Anything beyond that is clutter. A LoRA that's only good at one specific transformation might need just an input slot and a Generate button.
Examples of the reasoning, applied
Pose-control video LoRA (V2V). The model conditions on pose video. The user has a regular video. The demo takes a video, extracts pose, optionally takes an appearance reference image (for "the character looks like this, doing that motion"), runs inference, returns a video. The pose extraction is shown as a preview so the user knows what's being used. Aspect-ratio picker is irrelevant — the output matches the source.
Outpaint video LoRA (V2V). The model fills black-margined frames. The user has a video and wants it wider/taller. The demo takes a video, takes a target aspect ratio (dropdown: 16:9, 9:16, 1:1, etc.), pads frames with black bars to that aspect ratio, shows a preview of the padded first frame, runs inference. No appearance reference — the LoRA's job is to extend, not transform. If the LoRA's model card mentions that gamma correction helps for dark scenes, expose that as an Advanced toggle.
Relight image LoRA (I2I). The model relights based on prompt. The user has a photo and an idea of what lighting they want. The demo takes an image, takes a brush canvas where the user paints colored strokes indicating where light should come from, takes an illumination style dropdown ("golden hour", "neon", "studio"), takes an optional background change, builds the prompt, runs inference. Brush color and position become structured prompt content.
Style image LoRA (T2I). The model produces images in a specific style. The user has a prompt. The demo takes a prompt (with the trigger word auto-prepended), takes an aspect ratio, runs inference. That's the whole UI. No reference image, no brush canvas, no preprocessing — the LoRA does the work.
Bounding-box drag-drop LoRA (I2I). The model moves and resizes objects between two bounding boxes drawn on the image. The user has an image and an intent ("move that vase from here to there"). The demo takes an image, lets the user draw two boxes (red for source, green for target) directly on the image with a custom canvas component, runs inference. The boxes become the structured input the model expects.
Identity-preserving I2V. The model animates a character while preserving identity. The user has a still photo and a motion intent. The demo takes an image (the character), takes a prompt (the motion), runs inference. If the model also accepts a driving video for motion, expose that as an alternative input mode rather than a second mandatory input.
The pattern in all of these: start from what the user has and what they want, and build the UI that bridges to what the model needs.
Things that change UI shape
Signals from the model card or the LoRA's behavior that should change the UI:
Few-step inference (≤ 8 steps). Hide the steps slider — at this regime the model is recipe-locked. CFG is often 1.0 too. Lock these defaults rather than exposing them.
Recommended LoRA scale ≠ 1.0 or scale-sensitive behavior. Expose a LoRA-scale slider centered on the recommended value.
Multiple reference inputs. Two image slots, clearly labeled (e.g. "appearance" and "pose source") with help text explaining the role of each.
Optional inputs. Make optional clearly optional — placeholder text, "(optional)" in the label, the demo runs without it.
Multi-stage pipelines (e.g. extract + generate, generate + refine). Show stage progress (
progress(0.3, desc="Extracting pose...")thenprogress(0.6, desc="Generating...")). Otherwise the user stares at a blank progress bar for a long generation.Output is video > 5s. Bump
@spaces.GPU(duration=...)higher and warn the user in the UI that generation takes longer.LoRA expects structured prompt content (coordinates, region tags, named entities). Build a small UI that produces that structure rather than asking the user to type it raw.
Things that don't change UI shape
The base model identity, beyond determining the pipeline class. A Qwen-Image style LoRA and a Flux style LoRA can have identical UIs.
Pure performance details (dtype, device map, attention impl). These belong in the model load code, not the UI.
The LoRA's training data composition. Interesting, not load-bearing.
When LoRA loading fails
The right move when pipe.load_lora_weights(...) fails is to read the error and recognize the category of failure. Each category has a different fix path. Don't guess — different errors imply different things about whether the LoRA is salvageable on the diffusers path at all.
Don't preemptively call conversion utilities. load_lora_weights already calls the appropriate converters internally for the formats it knows about. Calling convert_state_dict_to_diffusers or similar before there's an error is redundant in the common case and risky if you guess wrong — you can mangle a state dict that would have loaded fine.
Failure categories:
Config-validation failure (errors mentioning
task_type,peft_type, "Invalid task type",PeftConfig, or anything frompeft/config.py): the safetensors weights themselves may be fine;adapter_config.jsonis the problem. Fix paths: passweight_name=explicitly soload_lora_weightsreads the safetensors directly without going through PEFT's strict config parser; or download the safetensors and load via state dict. Falling back toPeftModel.from_pretrainedwill not help — that path crashes on the same config.Missing keys / unexpected keys ("Loading adapter weights from state_dict led to missing keys" or "led to unexpected keys"): the state dict's key naming doesn't match what diffusers' loader expects. This often means the LoRA was trained with a non-diffusers convention (kohya, ComfyUI, OneTrainer, custom training scripts), or with custom target modules. Fix paths: try diffusers' built-in conversion utilities (
convert_state_dict_to_diffusers, base-model-specific converters indiffusers.loaders); if those don't help, the format may not be supported yet — surface this clearly to the user rather than silently using a partial load.Shape mismatch errors: the LoRA's tensor shapes don't match the base model's. Usually means the LoRA was trained against a different base model variant than the one being used (e.g. trained on Qwen-Image-Edit but loaded against Qwen-Image, or trained on FLUX.1-dev but loaded against FLUX.1-schnell). Fix: check the model card's
base_modelfield carefully and switch to the correct base.OOM during load or first inference: not really a loading failure — the LoRA loaded but the combined base + LoRA + activations don't fit. Fix paths involve
pipe.enable_vae_tiling(), smaller resolutions, FP8/quantized base variants. Not in scope for this section.Missing keys for some weights only (e.g. text encoder LoRA missing but transformer LoRA present): often a partial-coverage LoRA that only targets one component. May actually be intentional and may still work — generate a test image and see if the LoRA effect is present.
When none of these fit and load_lora_weights simply doesn't work, falling back to a non-diffusers path becomes a real option. At that point the model card's snippet becomes more useful — but port it to ZeroGPU constraints (no enable_model_cpu_offload, module-level .to('cuda'), models on cuda outside @spaces.GPU).
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs network access, Needs Python
Security
What our scanner noticed· Low risk
- Instructions that use the network
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Published by
- Hugging Face
- Publisher
- Hugging Face (Official)
- Source
- huggingface/skills · skills/huggingface-lora-space-builder · commit 25910b3
- Licence
- Apache License 2.0 (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Hugging Face or Hugging Face.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download huggingface-lora-space-builder.zip