Spaces:
Running on Zero
Running on Zero
Run on ZeroGPU: spaces.GPU session generator + command queue for controls
Browse files
README.md
CHANGED
|
@@ -15,4 +15,5 @@ Interactive [ABot-World](https://huggingface.co/YiYiXu/ABot-World-0-5B-LF-Diffus
|
|
| 15 |
action-conditioned world model, Wan2.2-TI2V-5B) running natively on Modular Diffusers: the whole app is a
|
| 16 |
`pipe.stream(action_source=...)` consumer — the pipeline polls the held control once per generated block.
|
| 17 |
|
| 18 |
-
|
|
|
|
|
|
| 15 |
action-conditioned world model, Wan2.2-TI2V-5B) running natively on Modular Diffusers: the whole app is a
|
| 16 |
`pipe.stream(action_source=...)` consumer — the pipeline polls the held control once per generated block.
|
| 17 |
|
| 18 |
+
Runs on ZeroGPU: one `@spaces.GPU` generator owns the whole session (the rollout keeps a K/V cache alive
|
| 19 |
+
across blocks) and the held control reaches it through a queue. ~20 GB VRAM in bf16.
|
app.py
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import threading
|
| 2 |
|
| 3 |
import gradio as gr
|
|
@@ -12,7 +16,7 @@ REPO = "YiYiXu/ABot-World-0-5B-LF-Diffusers"
|
|
| 12 |
|
| 13 |
pipe = ABotWorldStreamingBlocks().init_pipeline(REPO)
|
| 14 |
pipe.load_components(dtype=torch.bfloat16)
|
| 15 |
-
pipe.to("cuda")
|
| 16 |
|
| 17 |
DEFAULT_PROMPT = (
|
| 18 |
"A realistic outdoor world scene with a navigable path, natural lighting, "
|
|
@@ -31,45 +35,63 @@ CONTROLS = {
|
|
| 31 |
"· idle": [0] * 8,
|
| 32 |
}
|
| 33 |
|
| 34 |
-
#
|
| 35 |
-
|
|
|
|
|
|
|
| 36 |
run_lock = threading.Lock()
|
| 37 |
|
| 38 |
|
| 39 |
def hold(control):
|
| 40 |
-
|
| 41 |
return f"holding: {control}"
|
| 42 |
|
| 43 |
|
| 44 |
def stop():
|
| 45 |
-
|
| 46 |
return "stopping after this block…"
|
| 47 |
|
| 48 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
def play(image, prompt, max_blocks):
|
| 50 |
if not run_lock.acquire(blocking=False):
|
| 51 |
raise gr.Error("A session is already running — stop it first.")
|
| 52 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
if image is None:
|
| 54 |
image = load_image("examples/desert_valley.png")
|
| 55 |
-
|
| 56 |
-
session["action"] = CONTROLS["▲ forward (W)"]
|
| 57 |
-
|
| 58 |
-
def action_source(block_index):
|
| 59 |
-
if session["stop"] or block_index >= max_blocks:
|
| 60 |
-
return None
|
| 61 |
-
return session["action"]
|
| 62 |
-
|
| 63 |
-
for event in pipe.stream(
|
| 64 |
-
prompt=(prompt or DEFAULT_PROMPT).strip() or DEFAULT_PROMPT,
|
| 65 |
-
image=image,
|
| 66 |
-
action_source=action_source,
|
| 67 |
-
generator=torch.Generator("cpu").manual_seed(42),
|
| 68 |
-
):
|
| 69 |
-
if event.path != "denoise.rollout":
|
| 70 |
-
continue
|
| 71 |
-
frames = event.state.get("frames")
|
| 72 |
-
yield (frames[-1] * 255).clip(0, 255).astype(np.uint8)
|
| 73 |
finally:
|
| 74 |
run_lock.release()
|
| 75 |
|
|
@@ -86,7 +108,7 @@ per block ([huggingface/diffusers#14159](https://github.com/huggingface/diffuser
|
|
| 86 |
with gr.Column(scale=1):
|
| 87 |
image = gr.Image(label="starting scene", type="pil", height=220)
|
| 88 |
prompt = gr.Textbox(label="scene prompt", value=DEFAULT_PROMPT, lines=2)
|
| 89 |
-
max_blocks = gr.Slider(label="max blocks (~seconds)", minimum=4, maximum=
|
| 90 |
with gr.Row():
|
| 91 |
start_button = gr.Button("Start", variant="primary")
|
| 92 |
stop_button = gr.Button("Stop")
|
|
|
|
| 1 |
+
import spaces # must come before torch: on ZeroGPU it patches CUDA so the model can be staged at import
|
| 2 |
+
|
| 3 |
+
import multiprocessing
|
| 4 |
+
import queue
|
| 5 |
import threading
|
| 6 |
|
| 7 |
import gradio as gr
|
|
|
|
| 16 |
|
| 17 |
pipe = ABotWorldStreamingBlocks().init_pipeline(REPO)
|
| 18 |
pipe.load_components(dtype=torch.bfloat16)
|
| 19 |
+
pipe.to("cuda") # on ZeroGPU this only packs the weights; they land on the GPU inside the @spaces.GPU call
|
| 20 |
|
| 21 |
DEFAULT_PROMPT = (
|
| 22 |
"A realistic outdoor world scene with a navigable path, natural lighting, "
|
|
|
|
| 35 |
"· idle": [0] * 8,
|
| 36 |
}
|
| 37 |
|
| 38 |
+
# One live world at a time. On ZeroGPU the rollout runs in a forked worker process, so the held control
|
| 39 |
+
# crosses over a multiprocessing queue created before the fork: buttons put an action, Stop puts None,
|
| 40 |
+
# and the rollout drains the queue once per block.
|
| 41 |
+
commands = multiprocessing.Queue()
|
| 42 |
run_lock = threading.Lock()
|
| 43 |
|
| 44 |
|
| 45 |
def hold(control):
|
| 46 |
+
commands.put(CONTROLS[control])
|
| 47 |
return f"holding: {control}"
|
| 48 |
|
| 49 |
|
| 50 |
def stop():
|
| 51 |
+
commands.put(None)
|
| 52 |
return "stopping after this block…"
|
| 53 |
|
| 54 |
|
| 55 |
+
@spaces.GPU(duration=lambda image, prompt, max_blocks: 30 + 4 * int(max_blocks))
|
| 56 |
+
def rollout(image, prompt, max_blocks):
|
| 57 |
+
held = CONTROLS["▲ forward (W)"]
|
| 58 |
+
|
| 59 |
+
def action_source(block_index):
|
| 60 |
+
nonlocal held
|
| 61 |
+
while True:
|
| 62 |
+
try:
|
| 63 |
+
command = commands.get_nowait()
|
| 64 |
+
except queue.Empty:
|
| 65 |
+
break
|
| 66 |
+
if command is None:
|
| 67 |
+
return None
|
| 68 |
+
held = command
|
| 69 |
+
return held if block_index < max_blocks else None
|
| 70 |
+
|
| 71 |
+
for event in pipe.stream(
|
| 72 |
+
prompt=(prompt or DEFAULT_PROMPT).strip() or DEFAULT_PROMPT,
|
| 73 |
+
image=image,
|
| 74 |
+
action_source=action_source,
|
| 75 |
+
generator=torch.Generator("cpu").manual_seed(42),
|
| 76 |
+
):
|
| 77 |
+
if event.path != "denoise.rollout":
|
| 78 |
+
continue
|
| 79 |
+
frames = event.state.get("frames")
|
| 80 |
+
yield (frames[-1] * 255).clip(0, 255).astype(np.uint8)
|
| 81 |
+
|
| 82 |
+
|
| 83 |
def play(image, prompt, max_blocks):
|
| 84 |
if not run_lock.acquire(blocking=False):
|
| 85 |
raise gr.Error("A session is already running — stop it first.")
|
| 86 |
try:
|
| 87 |
+
while True: # drop controls left over from the previous session
|
| 88 |
+
try:
|
| 89 |
+
commands.get_nowait()
|
| 90 |
+
except queue.Empty:
|
| 91 |
+
break
|
| 92 |
if image is None:
|
| 93 |
image = load_image("examples/desert_valley.png")
|
| 94 |
+
yield from rollout(image, prompt, max_blocks)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
finally:
|
| 96 |
run_lock.release()
|
| 97 |
|
|
|
|
| 108 |
with gr.Column(scale=1):
|
| 109 |
image = gr.Image(label="starting scene", type="pil", height=220)
|
| 110 |
prompt = gr.Textbox(label="scene prompt", value=DEFAULT_PROMPT, lines=2)
|
| 111 |
+
max_blocks = gr.Slider(label="max blocks (~seconds)", minimum=4, maximum=40, step=1, value=20)
|
| 112 |
with gr.Row():
|
| 113 |
start_button = gr.Button("Start", variant="primary")
|
| 114 |
stop_button = gr.Button("Stop")
|