Spaces:
Running on Zero
Running on Zero
Publish-ready PDF to poster demo with validated BF16 example and safe continuation
Browse files- .gitattributes +1 -0
- README.md +5 -0
- app.py +2 -1
- demo_ui.py +34 -14
- examples/gaussian_privacy/poster.html +0 -0
- examples/gaussian_privacy/poster.png +3 -0
- examples/gaussian_privacy/poster_bundle.zip +3 -0
- layout_runtime.py +3 -0
- pdf_pipeline.py +3 -1
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
examples/gaussian_privacy/poster.png filter=lfs diff=lfs merge=lfs -text
|
README.md
CHANGED
|
@@ -33,11 +33,16 @@ it is never included in the app source or browser output.
|
|
| 33 |
ZeroGPU requests; partial previews are explicitly labeled incomplete.
|
| 34 |
5. Download the completed poster and check its scientific content and layout.
|
| 35 |
|
|
|
|
|
|
|
|
|
|
| 36 |
## Runtime and limitations
|
| 37 |
|
| 38 |
- ZeroGPU xlarge (96 GB) is requested per generation; no dedicated paid GPU.
|
| 39 |
- Visitors use their own Hugging Face quota. A complete long poster can require
|
| 40 |
more time than a free visitor's daily quota. This is not an unlimited service.
|
|
|
|
|
|
|
| 41 |
- Each request reserves 120 seconds; generation stops after approximately 100
|
| 42 |
seconds and can resume without adding chat turns or changing the prompt.
|
| 43 |
- The first PDF extraction after a cold start installs an isolated CPU parser
|
|
|
|
| 33 |
ZeroGPU requests; partial previews are explicitly labeled incomplete.
|
| 34 |
5. Download the completed poster and check its scientific content and layout.
|
| 35 |
|
| 36 |
+
A saved, actually generated example is available in the page without using GPU
|
| 37 |
+
quota. It is clearly labeled as a saved output, not an interactive generation.
|
| 38 |
+
|
| 39 |
## Runtime and limitations
|
| 40 |
|
| 41 |
- ZeroGPU xlarge (96 GB) is requested per generation; no dedicated paid GPU.
|
| 42 |
- Visitors use their own Hugging Face quota. A complete long poster can require
|
| 43 |
more time than a free visitor's daily quota. This is not an unlimited service.
|
| 44 |
+
- Sign in before live generation. Anonymous quota is too small for the current
|
| 45 |
+
120-second xlarge reservation. Viewing the saved example needs no GPU quota.
|
| 46 |
- Each request reserves 120 seconds; generation stops after approximately 100
|
| 47 |
seconds and can resume without adding chat turns or changing the prompt.
|
| 48 |
- The first PDF extraction after a cold start installs an isolated CPU parser
|
app.py
CHANGED
|
@@ -6,6 +6,7 @@ os.environ.setdefault('TOKENIZERS_PARALLELISM','false')
|
|
| 6 |
import json
|
| 7 |
import shutil
|
| 8 |
import time
|
|
|
|
| 9 |
import torch
|
| 10 |
import gradio as gr
|
| 11 |
import psutil
|
|
@@ -30,7 +31,7 @@ print("RAP_MODEL_READY", flush=True)
|
|
| 30 |
demo, artifacts = build(model, processor)
|
| 31 |
demo.queue(default_concurrency_limit=1, max_size=8).launch(
|
| 32 |
show_error=False, max_file_size='40mb', ssr_mode=False,
|
| 33 |
-
allowed_paths=[str(artifacts)],
|
| 34 |
blocked_paths=[str(__import__('pathlib').Path(__file__).parent/'.runtime'),
|
| 35 |
'/root/.cache/huggingface','/home/user/.cache/huggingface',
|
| 36 |
'/etc','/proc','/sys'],
|
|
|
|
| 6 |
import json
|
| 7 |
import shutil
|
| 8 |
import time
|
| 9 |
+
from pathlib import Path
|
| 10 |
import torch
|
| 11 |
import gradio as gr
|
| 12 |
import psutil
|
|
|
|
| 31 |
demo, artifacts = build(model, processor)
|
| 32 |
demo.queue(default_concurrency_limit=1, max_size=8).launch(
|
| 33 |
show_error=False, max_file_size='40mb', ssr_mode=False,
|
| 34 |
+
allowed_paths=[str(artifacts),str(Path(__file__).parent/'examples')],
|
| 35 |
blocked_paths=[str(__import__('pathlib').Path(__file__).parent/'.runtime'),
|
| 36 |
'/root/.cache/huggingface','/home/user/.cache/huggingface',
|
| 37 |
'/etc','/proc','/sys'],
|
demo_ui.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
| 1 |
"""Public PDF -> assets -> model HTML -> rendered poster workflow."""
|
| 2 |
import json
|
| 3 |
-
import os
|
| 4 |
import re
|
| 5 |
import tempfile
|
| 6 |
import time
|
|
@@ -62,8 +61,9 @@ def build(model,processor):
|
|
| 62 |
text=processor.decode(all_ids,skip_special_tokens=True)
|
| 63 |
eos=model.generation_config.eos_token_id
|
| 64 |
eos=eos if isinstance(eos,list) else [eos]
|
| 65 |
-
complete=bool(
|
| 66 |
-
|
|
|
|
| 67 |
'new_tokens':len(new_ids),'gpu':torch.cuda.get_device_name(),
|
| 68 |
'total_tokens':p['input_tokens']+len(all_ids)}
|
| 69 |
del inputs,output
|
|
@@ -71,12 +71,14 @@ def build(model,processor):
|
|
| 71 |
|
| 72 |
def generate(p,progress=gr.Progress()):
|
| 73 |
if not p:raise gr.Error('Click Analyze PDF first.')
|
| 74 |
-
if p.get('done'):raise gr.Error('
|
| 75 |
progress(.1,desc='Waiting for ZeroGPU xlarge, then generating HTML…')
|
| 76 |
result=gpu_chunk(p)
|
| 77 |
p=dict(p,continuation_ids=result['ids'],raw=result['raw'],done=result['done'])
|
| 78 |
rate=result['new_tokens']/max(result['seconds'],.01)
|
| 79 |
-
|
|
|
|
|
|
|
| 80 |
f'{len(result["ids"]):,} generated tokens · {result["total_tokens"]:,} / 16,384 total · '
|
| 81 |
f'{rate:.1f} tokens/s\n\nGPU: {result["gpu"]} · BF16 · checkpoint-240')
|
| 82 |
progress(.9,desc='Resolving assets and rendering the poster…')
|
|
@@ -87,11 +89,11 @@ def build(model,processor):
|
|
| 87 |
return p,status,png,html,archive,p['raw']
|
| 88 |
except ValueError as e:
|
| 89 |
return p,status+'\n\n'+str(e),None,None,None,p['raw']
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
|
| 96 |
css='''#hero {padding:20px 24px;border-radius:18px;background:linear-gradient(115deg,#241541,#534180);color:white}
|
| 97 |
#hero h1,#hero p {color:white} .gradio-container {max-width:1400px!important}'''
|
|
@@ -100,8 +102,10 @@ def build(model,processor):
|
|
| 100 |
gr.Markdown('# RAP · Paper to Poster\nTurn a research paper into an editable academic poster.',elem_id='hero')
|
| 101 |
gr.Markdown('**RL checkpoint-240 · BF16, no quantization · no thinking · 16K context**\n\n'
|
| 102 |
'Upload a text-based PDF (≤40 MB). We analyze its first 12 pages, extract figures/tables/equations, '
|
| 103 |
-
'then generate poster HTML.
|
| 104 |
-
'
|
|
|
|
|
|
|
| 105 |
state=gr.State()
|
| 106 |
with gr.Row():
|
| 107 |
with gr.Column(scale=4):
|
|
@@ -126,6 +130,18 @@ def build(model,processor):
|
|
| 126 |
spec_view=gr.Textbox(label='Layout specification used for generation',lines=6,interactive=False)
|
| 127 |
with gr.Accordion('Model-generated HTML',open=False):
|
| 128 |
code=gr.Code(language='html',label='Model output (not executed in this page)',interactive=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
gr.Markdown('Research demo: verify scientific statements, captions and layout before publication. '
|
| 130 |
'Uploaded papers are processed on the Space; do not upload confidential material. '
|
| 131 |
'Outputs are temporary and may disappear after a restart. '
|
|
@@ -134,6 +150,10 @@ def build(model,processor):
|
|
| 134 |
[state,status,gallery,spec_view,poster,html_download,bundle,code],api_name='analyze',concurrency_limit=1)
|
| 135 |
generate_btn.click(generate,[state],[state,status,poster,html_download,bundle,code],
|
| 136 |
api_name='generate',concurrency_limit=1)
|
| 137 |
-
|
| 138 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
return demo,ARTIFACTS
|
|
|
|
| 1 |
"""Public PDF -> assets -> model HTML -> rendered poster workflow."""
|
| 2 |
import json
|
|
|
|
| 3 |
import re
|
| 4 |
import tempfile
|
| 5 |
import time
|
|
|
|
| 61 |
text=processor.decode(all_ids,skip_special_tokens=True)
|
| 62 |
eos=model.generation_config.eos_token_id
|
| 63 |
eos=eos if isinstance(eos,list) else [eos]
|
| 64 |
+
complete=bool(re.search(r'</html\s*>',text,re.I))
|
| 65 |
+
stopped=complete or bool(new_ids and new_ids[-1] in eos) or length+len(new_ids)>=WINDOW
|
| 66 |
+
result={'ids':all_ids,'raw':text,'done':stopped,'complete':complete,'seconds':time.monotonic()-start,
|
| 67 |
'new_tokens':len(new_ids),'gpu':torch.cuda.get_device_name(),
|
| 68 |
'total_tokens':p['input_tokens']+len(all_ids)}
|
| 69 |
del inputs,output
|
|
|
|
| 71 |
|
| 72 |
def generate(p,progress=gr.Progress()):
|
| 73 |
if not p:raise gr.Error('Click Analyze PDF first.')
|
| 74 |
+
if p.get('done'):raise gr.Error('Generation has ended. Analyze again to start a new poster.')
|
| 75 |
progress(.1,desc='Waiting for ZeroGPU xlarge, then generating HTML…')
|
| 76 |
result=gpu_chunk(p)
|
| 77 |
p=dict(p,continuation_ids=result['ids'],raw=result['raw'],done=result['done'])
|
| 78 |
rate=result['new_tokens']/max(result['seconds'],.01)
|
| 79 |
+
label=('Complete' if result['complete'] else 'Stopped — incomplete HTML; analyze again to retry'
|
| 80 |
+
if p['done'] else 'Partial — click Generate / Continue to resume')
|
| 81 |
+
status=(f'**{label}** · '
|
| 82 |
f'{len(result["ids"]):,} generated tokens · {result["total_tokens"]:,} / 16,384 total · '
|
| 83 |
f'{rate:.1f} tokens/s\n\nGPU: {result["gpu"]} · BF16 · checkpoint-240')
|
| 84 |
progress(.9,desc='Resolving assets and rendering the poster…')
|
|
|
|
| 89 |
return p,status,png,html,archive,p['raw']
|
| 90 |
except ValueError as e:
|
| 91 |
return p,status+'\n\n'+str(e),None,None,None,p['raw']
|
| 92 |
+
except Exception as e:
|
| 93 |
+
print('POSTER_RENDER_ERROR',type(e).__name__,flush=True)
|
| 94 |
+
saved=Path(p['job'])/'output/poster.html'
|
| 95 |
+
return p,status+'\n\nPreview rendering failed; the generated HTML is preserved.',None,\
|
| 96 |
+
str(saved) if saved.exists() else None,None,p['raw']
|
| 97 |
|
| 98 |
css='''#hero {padding:20px 24px;border-radius:18px;background:linear-gradient(115deg,#241541,#534180);color:white}
|
| 99 |
#hero h1,#hero p {color:white} .gradio-container {max-width:1400px!important}'''
|
|
|
|
| 102 |
gr.Markdown('# RAP · Paper to Poster\nTurn a research paper into an editable academic poster.',elem_id='hero')
|
| 103 |
gr.Markdown('**RL checkpoint-240 · BF16, no quantization · no thinking · 16K context**\n\n'
|
| 104 |
'Upload a text-based PDF (≤40 MB). We analyze its first 12 pages, extract figures/tables/equations, '
|
| 105 |
+
'then generate poster HTML. Sign in to Hugging Face before generating. '
|
| 106 |
+
'**ZeroGPU xlarge uses 2× your personal GPU quota. A full poster usually needs several requests '
|
| 107 |
+
'and can exceed a free account’s daily allowance.** Partial previews are not finished posters. '
|
| 108 |
+
'The saved example below can be viewed without GPU quota.')
|
| 109 |
state=gr.State()
|
| 110 |
with gr.Row():
|
| 111 |
with gr.Column(scale=4):
|
|
|
|
| 130 |
spec_view=gr.Textbox(label='Layout specification used for generation',lines=6,interactive=False)
|
| 131 |
with gr.Accordion('Model-generated HTML',open=False):
|
| 132 |
code=gr.Code(language='html',label='Model output (not executed in this page)',interactive=False)
|
| 133 |
+
example=ROOT/'examples/gaussian_privacy'
|
| 134 |
+
if (example/'poster.png').exists():
|
| 135 |
+
with gr.Accordion('View a completed example — no GPU quota required',open=False):
|
| 136 |
+
gr.Markdown('**Saved output, not a live generation.** This poster was generated on this '
|
| 137 |
+
'Space using RL checkpoint-240 in BF16 from '
|
| 138 |
+
'[Gaussian Membership Inference Privacy](https://arxiv.org/abs/2306.07273). '
|
| 139 |
+
'Its figures are extracted from that paper. Please check scientific details before reuse.')
|
| 140 |
+
gr.Image(value=str(example/'poster.png'),label='Completed model-generated example',
|
| 141 |
+
interactive=False,height=600)
|
| 142 |
+
with gr.Row():
|
| 143 |
+
gr.File(value=str(example/'poster.html'),label='Example HTML',interactive=False)
|
| 144 |
+
gr.File(value=str(example/'poster_bundle.zip'),label='Example poster and assets',interactive=False)
|
| 145 |
gr.Markdown('Research demo: verify scientific statements, captions and layout before publication. '
|
| 146 |
'Uploaded papers are processed on the Space; do not upload confidential material. '
|
| 147 |
'Outputs are temporary and may disappear after a restart. '
|
|
|
|
| 150 |
[state,status,gallery,spec_view,poster,html_download,bundle,code],api_name='analyze',concurrency_limit=1)
|
| 151 |
generate_btn.click(generate,[state],[state,status,poster,html_download,bundle,code],
|
| 152 |
api_name='generate',concurrency_limit=1)
|
| 153 |
+
def reset_inputs():
|
| 154 |
+
return None,'Inputs changed — click Analyze PDF again.',None,None,None,'',[],''
|
| 155 |
+
for component in (pdf,instruction,canvas,accent,columns,custom_spec):
|
| 156 |
+
component.change(reset_inputs,None,
|
| 157 |
+
[state,status,poster,html_download,bundle,code,gallery,spec_view],
|
| 158 |
+
queue=False,api_name=False)
|
| 159 |
return demo,ARTIFACTS
|
examples/gaussian_privacy/poster.html
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
examples/gaussian_privacy/poster.png
ADDED
|
Git LFS Details
|
examples/gaussian_privacy/poster_bundle.zip
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d40955b68a38d689d8a6ddc715a67ccf24149d0a07d13f075ec6f2c0896d8dc9
|
| 3 |
+
size 649785
|
layout_runtime.py
CHANGED
|
@@ -36,6 +36,8 @@ def setup():
|
|
| 36 |
result=subprocess.run([str(python),'-m','pip','install','--disable-pip-version-check',*packages],
|
| 37 |
env=env,stdout=log,stderr=log,timeout=900)
|
| 38 |
if result.returncode:
|
|
|
|
|
|
|
| 39 |
raise RuntimeError('LayoutLMv3 dependency setup failed. Please contact the demo maintainer.')
|
| 40 |
marker.write_text('layoutlmv3-cpu-v1\n')
|
| 41 |
return str(python)
|
|
@@ -47,4 +49,5 @@ def extract(pdf, out):
|
|
| 47 |
result=subprocess.run([python,str(ROOT/'layout_worker.py'),str(pdf),str(out)],
|
| 48 |
env=worker_environment(),stdout=log,stderr=log,timeout=900)
|
| 49 |
if result.returncode:
|
|
|
|
| 50 |
raise RuntimeError('LayoutLMv3 could not parse this PDF. Please try a text-based research paper.')
|
|
|
|
| 36 |
result=subprocess.run([str(python),'-m','pip','install','--disable-pip-version-check',*packages],
|
| 37 |
env=env,stdout=log,stderr=log,timeout=900)
|
| 38 |
if result.returncode:
|
| 39 |
+
log.flush()
|
| 40 |
+
print('PARSER_SETUP_ERROR', (RUNTIME/'layout_setup.log').read_text(errors='replace')[-6000:],flush=True)
|
| 41 |
raise RuntimeError('LayoutLMv3 dependency setup failed. Please contact the demo maintainer.')
|
| 42 |
marker.write_text('layoutlmv3-cpu-v1\n')
|
| 43 |
return str(python)
|
|
|
|
| 49 |
result=subprocess.run([python,str(ROOT/'layout_worker.py'),str(pdf),str(out)],
|
| 50 |
env=worker_environment(),stdout=log,stderr=log,timeout=900)
|
| 51 |
if result.returncode:
|
| 52 |
+
print('PARSER_EXTRACTION_ERROR', (out/'extraction.log').read_text(errors='replace')[-6000:],flush=True)
|
| 53 |
raise RuntimeError('LayoutLMv3 could not parse this PDF. Please try a text-based research paper.')
|
pdf_pipeline.py
CHANGED
|
@@ -45,7 +45,9 @@ def prepare(pdf, job, instruction, canvas, accent, columns, custom_spec=''):
|
|
| 45 |
chosen=select_for_prompt(registry['assets'],[],16,{'figure':10,'table':5,'formula':3})
|
| 46 |
with fitz.open(pdf) as doc:
|
| 47 |
first=doc[0].get_text('dict')
|
| 48 |
-
spans=[s for b in first['blocks'] if 'lines' in b for l in b['lines']
|
|
|
|
|
|
|
| 49 |
largest=max((s['size'] for s in spans),default=0)
|
| 50 |
title=clean_text(' '.join(s['text'] for s in spans if s['size']>=largest*.85 and s['bbox'][1]<doc[0].rect.height*.45))[:400]
|
| 51 |
authors=clean_text(doc.metadata.get('author',''))[:500]
|
|
|
|
| 45 |
chosen=select_for_prompt(registry['assets'],[],16,{'figure':10,'table':5,'formula':3})
|
| 46 |
with fitz.open(pdf) as doc:
|
| 47 |
first=doc[0].get_text('dict')
|
| 48 |
+
spans=[s for b in first['blocks'] if 'lines' in b for l in b['lines']
|
| 49 |
+
if abs(l.get('dir',(1,0))[1])<.1 for s in l['spans']
|
| 50 |
+
if s['text'].strip() and not re.search(r'arxiv:|preprint|under review',s['text'],re.I)]
|
| 51 |
largest=max((s['size'] for s in spans),default=0)
|
| 52 |
title=clean_text(' '.join(s['text'] for s in spans if s['size']>=largest*.85 and s['bbox'][1]<doc[0].rect.height*.45))[:400]
|
| 53 |
authors=clean_text(doc.metadata.get('author',''))[:500]
|