YiYiXu HF Staff commited on
Commit
f78cf71
·
verified ·
1 Parent(s): 5207650

Run on ZeroGPU: spaces.GPU session generator + command queue for controls

Browse files
Files changed (2) hide show
  1. README.md +2 -1
  2. app.py +46 -24
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
- Needs a persistent GPU (the rollout keeps a K/V cache alive across blocks); ~20 GB VRAM in bf16.
 
 
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
- # one live world at a time: buttons write the held control here, the rollout polls it per block
35
- session = {"action": [0] * 8, "stop": False}
 
 
36
  run_lock = threading.Lock()
37
 
38
 
39
  def hold(control):
40
- session["action"] = CONTROLS[control]
41
  return f"holding: {control}"
42
 
43
 
44
  def stop():
45
- session["stop"] = True
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
- session["stop"] = False
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=120, step=1, value=60)
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")