hanhaonan02 Claude Fable 5.1 commited on
Commit
4b73edf
·
1 Parent(s): 033094c

Adaptive ZeroGPU step length with a quota explanation

Browse files

- Ask for a 600 s xlarge step first (one weight transfer, most posters finish in one step) and fall
back to 300/120/60 s when ZeroGPU answers 'larger than the maximum allowed'.
- Generation budget derives from the step length; status shows the step length.
- Quota hint on failures: 27B BF16 needs the full GPU (2x quota), ~15-25 min per poster; PRO 40 min,
free 5 min, anonymous 2 min per day. README says the same.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Files changed (2) hide show
  1. README.md +4 -2
  2. demo_ui.py +24 -9
README.md CHANGED
@@ -19,8 +19,10 @@ Upload a research paper PDF and get an editable academic poster. A 27B vision-la
19
  and writes the poster as standalone HTML; the Space renders it to PNG.
20
 
21
  - Model: RL checkpoint-240, BF16, no quantization, 16K context.
22
- - Generation runs in ZeroGPU steps of up to 120 s; a full poster usually takes 3–6 steps and the
23
- demo chains them automatically. Sign in to Hugging Face to use your GPU quota.
 
 
24
  - API: call `/analyze` once (PDF + settings), then `/step` repeatedly until the status starts with
25
  `Done`, `Paused` or `Stopped`; each step is its own request so the ZeroGPU token stays valid.
26
  - The examples on the page were generated by the same checkpoint with the default instruction and settings.
 
19
  and writes the poster as standalone HTML; the Space renders it to PNG.
20
 
21
  - Model: RL checkpoint-240, BF16, no quantization, 16K context.
22
+ - The model runs in BF16 on a full 96 GB ZeroGPU slot (`xlarge`, counted 2x against quota). A poster
23
+ takes about 15–25 minutes of ZeroGPU quota; PRO accounts have 40 min/day, free accounts 5 min and
24
+ anonymous visitors 2 min, so sign in with a PRO account (on huggingface.co, not the bare hf.space URL)
25
+ to finish a poster. The demo asks for a 10-minute GPU step first and falls back to shorter steps.
26
  - API: call `/analyze` once (PDF + settings), then `/step` repeatedly until the status starts with
27
  `Done`, `Paused` or `Stopped`; each step is its own request so the ZeroGPU token stays valid.
28
  - The examples on the page were generated by the same checkpoint with the default instruction and settings.
demo_ui.py CHANGED
@@ -14,7 +14,10 @@ from poster_render import render
14
  ROOT = Path(__file__).resolve().parent
15
  ARTIFACTS = Path(tempfile.mkdtemp(prefix='rap_demo_'))
16
  WINDOW = 16384
17
- MAX_STEPS = 10 # ZeroGPU steps of <=120 s per run
 
 
 
18
 
19
  CSS = '''
20
  .gradio-container {max-width: 1360px !important; margin: 0 auto;}
@@ -64,8 +67,12 @@ def build(model, processor):
64
  p['input_tokens'] = n; p['continuation_ids'] = []; p['raw'] = ''; p['done'] = False
65
  return p
66
 
67
- @spaces.GPU(size='xlarge', duration=120)
 
 
 
68
  def gpu_chunk(p):
 
69
  inputs = processor.apply_chat_template(chat(p), tokenize=True, add_generation_prompt=True,
70
  enable_thinking=False, return_dict=True, return_tensors='pt')
71
  prefix = p.get('continuation_ids', [])
@@ -79,7 +86,7 @@ def build(model, processor):
79
  inputs = inputs.to('cuda')
80
  start = time.monotonic()
81
  with torch.inference_mode():
82
- output = model.generate(**inputs, max_new_tokens=min(8192, remaining), max_time=100, do_sample=False,
83
  use_cache=True, temperature=None, top_p=None, top_k=None, repetition_penalty=1.1)
84
  new_ids = output[0, length:].detach().cpu().tolist()
85
  all_ids = prefix + new_ids
@@ -122,13 +129,21 @@ def build(model, processor):
122
  return (dict(p, paused=True), f"Stopped after {MAX_STEPS} steps without a complete poster.", None, HIDE, HIDE,
123
  gallery, p['spec'], p['raw'], gr.update(visible=True))
124
  progress(min(.1 + .12 * k, .9), desc=f'Generating poster HTML (step {k})…')
125
- try:
126
- r = gpu_chunk(p)
127
- except Exception as e: # noqa: BLE001 (quota exhausted, timeout, …)
128
- return (dict(p, paused=True), f"Paused after step {k - 1}: {str(e)[:200]}", None, HIDE, HIDE,
129
- gallery, p['spec'], p['raw'], gr.update(visible=bool(p.get('continuation_ids'))))
 
 
 
 
 
 
 
 
130
  p = dict(p, continuation_ids=r['ids'], raw=r['raw'], done=r['done'], step=k)
131
- tokens = f"{len(r['ids']):,} tokens · {r['new_tokens'] / max(r['seconds'], .01):.0f} tok/s"
132
  if not (r['complete'] or p['done']):
133
  return (p, f"Step {k} done · {tokens} · continuing (step {k + 1})…", None, HIDE, HIDE,
134
  gallery, p['spec'], p['raw'], gr.update(visible=False))
 
14
  ROOT = Path(__file__).resolve().parent
15
  ARTIFACTS = Path(tempfile.mkdtemp(prefix='rap_demo_'))
16
  WINDOW = 16384
17
+ MAX_STEPS = 10 # chained ZeroGPU steps per run
18
+ GPU_LADDER = (600, 300, 120, 60) # requested step length; ZeroGPU refuses what the visitor's quota cannot cover
19
+ QUOTA_HINT = (' — this 27B BF16 model needs the full 96 GB ZeroGPU (2x quota) and a poster takes roughly 15–25 min of quota. '
20
+ 'Daily ZeroGPU quota: PRO 40 min, free account 5 min, anonymous 2 min, so sign in with a PRO account to finish a poster.')
21
 
22
  CSS = '''
23
  .gradio-container {max-width: 1360px !important; margin: 0 auto;}
 
67
  p['input_tokens'] = n; p['continuation_ids'] = []; p['raw'] = ''; p['done'] = False
68
  return p
69
 
70
+ def gpu_seconds(p):
71
+ return int(p.get('gpu_seconds', 120))
72
+
73
+ @spaces.GPU(size='xlarge', duration=gpu_seconds) # 27B BF16 needs the full 96 GB GPU (2x quota)
74
  def gpu_chunk(p):
75
+ budget = max(30, gpu_seconds(p) - 90) # leave room for moving the weights to the GPU and the prefill
76
  inputs = processor.apply_chat_template(chat(p), tokenize=True, add_generation_prompt=True,
77
  enable_thinking=False, return_dict=True, return_tensors='pt')
78
  prefix = p.get('continuation_ids', [])
 
86
  inputs = inputs.to('cuda')
87
  start = time.monotonic()
88
  with torch.inference_mode():
89
+ output = model.generate(**inputs, max_new_tokens=min(8192, remaining), max_time=budget, do_sample=False,
90
  use_cache=True, temperature=None, top_p=None, top_k=None, repetition_penalty=1.1)
91
  new_ids = output[0, length:].detach().cpu().tolist()
92
  all_ids = prefix + new_ids
 
129
  return (dict(p, paused=True), f"Stopped after {MAX_STEPS} steps without a complete poster.", None, HIDE, HIDE,
130
  gallery, p['spec'], p['raw'], gr.update(visible=True))
131
  progress(min(.1 + .12 * k, .9), desc=f'Generating poster HTML (step {k})…')
132
+ r = None
133
+ for secs in [d for d in GPU_LADDER if d <= p.get('gpu_seconds', GPU_LADDER[0])]:
134
+ p = dict(p, gpu_seconds=secs)
135
+ try:
136
+ r = gpu_chunk(p)
137
+ break
138
+ except Exception as e: # noqa: BLE001 (quota, duration limit, timeout, …)
139
+ msg = str(e)
140
+ if 'larger than the maximum allowed' in msg and secs != GPU_LADDER[-1]:
141
+ continue # ask for a shorter GPU slot
142
+ hint = QUOTA_HINT if ('maximum allowed' in msg or 'quota' in msg.lower()) else ''
143
+ return (dict(p, paused=True), f"Paused after step {k - 1}: {msg[:200]}{hint}", None, HIDE, HIDE,
144
+ gallery, p['spec'], p['raw'], gr.update(visible=bool(p.get('continuation_ids'))))
145
  p = dict(p, continuation_ids=r['ids'], raw=r['raw'], done=r['done'], step=k)
146
+ tokens = f"{len(r['ids']):,} tokens · {r['new_tokens'] / max(r['seconds'], .01):.0f} tok/s · {p['gpu_seconds']} s GPU step"
147
  if not (r['complete'] or p['done']):
148
  return (p, f"Step {k} done · {tokens} · continuing (step {k + 1})…", None, HIDE, HIDE,
149
  gallery, p['spec'], p['raw'], gr.update(visible=False))