VincentHancoder commited on
Commit
38c5f38
·
verified ·
1 Parent(s): c58c39d

Publish-ready PDF to poster demo with validated BF16 example and safe continuation

Browse files
.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(new_ids and new_ids[-1] in eos) or bool(re.search(r'</html\s*>',text,re.I))
66
- result={'ids':all_ids,'raw':text,'done':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,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('This poster is complete. Analyze again to start a new one.')
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
- status=(f'**{"Complete" if p["done"] else "Partial — click Generate / Continue to resume"}** · '
 
 
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
- def diagnostics():
92
- setup=ROOT/'.runtime/layout_setup.log'
93
- return {'parser_ready':(ROOT/'.runtime/layout_env/rap_parser_ready').exists(),
94
- 'parser_log_tail':setup.read_text(errors='replace')[-7000:] if setup.exists() else 'not started'}
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. ZeroGPU uses your HF usage quota; sign in to HF for a larger allowance. '
104
- 'Long posters may need more than one Generate / Continue request.')
 
 
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
- if os.environ.get('RAP_DIAGNOSTICS')=='1':
138
- gr.Button('Private diagnostics').click(diagnostics,None,gr.JSON(),api_name='diagnostics')
 
 
 
 
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

  • SHA256: c23f2139630e2b7a2345c013a5c81762131a1cddc5c56ad5fd13e6ad806dcd79
  • Pointer size: 131 Bytes
  • Size of remote file: 349 kB
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'] for s in l['spans'] if s['text'].strip()]
 
 
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]