Spaces:
Runtime error
Runtime error
File size: 6,188 Bytes
4ff39a4 0e6f305 80cb2a6 4ff39a4 2d71939 0e6f305 4ff39a4 0e6f305 4ff39a4 0e6f305 4ff39a4 80cb2a6 4ff39a4 80cb2a6 4ff39a4 2d71939 4ff39a4 2d71939 4ff39a4 2d71939 4ff39a4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | """Inference backend for the DiffusionDB SD 1.5 LoRA.
A Gradio app whose only job is to expose generation as an API. The UI Space
(spaces/whosouravsharma/diffusiondb-sd15-lora) calls it with gradio_client;
the minimal interface here is also usable directly for debugging.
This is the Space that needs the GPU. Weights are pulled from the model repo
at first request, not baked into the image.
"""
import os
import gradio as gr
from src.pipeline import LORA_CHECKPOINT, LORA_REPO, ModelRunner, pick_device
from src.tracing import (
TRACING_ACTIVE,
configure_logging,
continue_trace,
logger,
)
configure_logging()
# Loaded on first request rather than at import, so the Space becomes
# reachable immediately and a slow weight download cannot kill startup.
_runner = None
def get_runner() -> ModelRunner:
global _runner
if _runner is None:
_runner = ModelRunner()
return _runner
def model_is_warm() -> bool:
return _runner is not None
# Status strings for the visible UI. The cold path is genuinely slow -- the
# container has to wake, then torch/diffusers import, then ~9 s of weight
# loading before a single step runs -- so say so rather than showing a
# spinner that looks indistinguishable from a hang.
STATUS_IDLE = "Ready."
STATUS_COLD = (
"⏳ **Starting server…** waking the GPU and loading the model. "
"The first image takes around 40 seconds; later ones take a few."
)
STATUS_WARM = "⏳ **Generating…**"
def status_on_click() -> str:
"""Fires the instant the button is pressed, before the GPU job queues."""
return STATUS_WARM if model_is_warm() else STATUS_COLD
def status_on_load() -> str:
"""Fires when someone opens the page."""
if model_is_warm():
return STATUS_IDLE
return (
"Model not loaded yet — the first generation also starts the server "
"and takes around 40 seconds."
)
def generate(prompt, negative_prompt, steps, guidance, lora_scale, seed,
request: gr.Request = None):
"""One image per call. Returns (image, seed_used).
Deliberately single-image: gradio_client handles a plain Image return
far more predictably than a Gallery, and the UI can call again for more.
`request` is supplied by Gradio from the type annotation and is not a UI
input, so the published /generate signature stays at six arguments. It
exists only to pick up the caller's trace headers.
"""
try:
# When the UI Space sent trace headers this run becomes a child of
# its run, so one LangSmith trace covers UI and backend -- and the
# visitor's location, carried in baggage, lands on this run too.
with continue_trace(request):
result = get_runner().generate(
prompt=prompt,
negative_prompt=negative_prompt,
steps=int(steps),
guidance=float(guidance),
lora_scale=float(lora_scale),
seed=None if int(seed) < 0 else int(seed),
count=1,
)
except ValueError as error:
raise gr.Error(str(error))
except Exception as error:
# The gr.Error string is all the caller sees; without this the
# traceback is lost and the Space logs show nothing useful.
logger.exception("request failed")
raise gr.Error(f"Generation failed: {error}")
return result.images[0], result.seed
with gr.Blocks(title="DiffusionDB SD 1.5 LoRA — inference API") as demo:
gr.Markdown(
f"## Inference API\n"
f"Backend for [`diffusiondb-sd15-lora`]"
f"(https://huggingface.co/spaces/whosouravsharma/diffusiondb-sd15-lora).\n\n"
f"Model `{LORA_REPO}` @ `{LORA_CHECKPOINT}` · device `{pick_device()}` · "
f"tracing `{'on' if TRACING_ACTIVE else 'off'}`\n\n"
f"Call it with `gradio_client`:\n"
f"```python\n"
f"from gradio_client import Client\n"
f"c = Client('whosouravsharma/diffusiondb-sd15-lora-inference')\n"
f"image, seed = c.predict('a steampunk owl', '', 25, 7.5, 1.0, 42,\n"
f" api_name='/generate')\n"
f"```"
)
with gr.Row():
with gr.Column():
prompt = gr.Textbox(label="prompt", lines=2)
negative = gr.Textbox(label="negative_prompt", lines=1)
steps = gr.Number(value=25, label="steps", precision=0)
guidance = gr.Number(value=7.5, label="guidance")
lora_scale = gr.Number(value=1.0, label="lora_scale")
seed = gr.Number(value=-1, label="seed", precision=0)
run = gr.Button("Generate", variant="primary")
with gr.Column():
# Initial value assumes cold, because at import time it always
# is; demo.load corrects it to "Ready." on a warm server. The
# reverse default would flash a false "Ready." at every cold
# visitor before the load event lands.
status = gr.Markdown(status_on_load())
image = gr.Image(label="image", type="pil")
used_seed = gr.Number(label="seed_used", interactive=False)
# Two independent listeners on the same button. The cosmetic one uses
# queue=False so it paints immediately instead of waiting behind the GPU
# job it is announcing. The API contract of /generate is untouched: same
# six inputs, same two outputs, so gradio_client callers are unaffected.
# api_name=False keeps these cosmetic events out of the published API,
# so gradio_client sees /generate and nothing else.
run.click(status_on_click, outputs=status, queue=False, api_name=False)
generate_event = run.click(
generate,
inputs=[prompt, negative, steps, guidance, lora_scale, seed],
outputs=[image, used_seed],
api_name="generate",
)
generate_event.then(
lambda: STATUS_IDLE, outputs=status, queue=False, api_name=False
)
demo.load(status_on_load, outputs=status, api_name=False)
if __name__ == "__main__":
demo.queue(max_size=12).launch(
server_name="0.0.0.0",
server_port=int(os.environ.get("PORT", 7860)),
)
|