Spaces:
Running on Zero
Running on Zero
Mehdi commited on
Commit ·
dc3b7d4
1
Parent(s): 57ebbd6
docs: Field Notes blog post — what we built and what we learned
Browse filesCovers the full journey: MiniCPM4-8B on ZeroGPU, the Off-Brand
hidden-Gradio bridge (height:0, textbox triggers, polling), FLUX.2-klein
prefetching, client-side MCQ grading, and the double-handler bug.
BLOG.md
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 📄 PaperProf: How We Fought Gradio, Won, and Built an AI Study Buddy in 10 Days
|
| 2 |
+
|
| 3 |
+
*Field notes from the Build Small Hackathon — June 5–15, 2026*
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## The Pitch
|
| 8 |
+
|
| 9 |
+
Every student knows the ritual: it's 11 PM, the exam is tomorrow, and you're re-reading the same lecture PDF for the fourth time, *feeling* productive while learning absolutely nothing. Passive re-reading is one of the worst-performing study techniques in the learning-science literature. Active recall — forcing yourself to answer questions — is one of the best.
|
| 10 |
+
|
| 11 |
+
So we built **PaperProf**: drop in any course PDF, and it becomes your personal professor. It reads the material, generates exam-style questions from it, grades your answers like a patient tutor, and even paints you a parting gift when you finish your session.
|
| 12 |
+
|
| 13 |
+
**[Try it live on Hugging Face Spaces →](https://huggingface.co/spaces/build-small-hackathon/PaperProf)**
|
| 14 |
+
|
| 15 |
+
Everything runs on free infrastructure with zero external API calls. No OpenAI key, no rate limits, no data leaving the machine. Just open-weight models doing honest work on a ZeroGPU slice.
|
| 16 |
+
|
| 17 |
+
---
|
| 18 |
+
|
| 19 |
+
## What It Does
|
| 20 |
+
|
| 21 |
+
1. **Upload a PDF** — lecture notes, a textbook chapter, slides, whatever you're cramming.
|
| 22 |
+
2. **PaperProf chunks it** into thematic sections and picks one at random.
|
| 23 |
+
3. **Choose your mode:**
|
| 24 |
+
- **Open questions** — write a free-form answer, get structured tutor feedback: a verdict, what you got right, what you missed, and a model answer.
|
| 25 |
+
- **MCQ** — four plausible options, instant client-side grading, and a one-sentence explanation for *every* choice, not just the right one.
|
| 26 |
+
4. **A score ring** tracks your session in real time.
|
| 27 |
+
5. **End the session** and FLUX.2-klein generates a unique image inspired by the topics you just studied — a small visual reward for showing up.
|
| 28 |
+
|
| 29 |
+
The whole question-answer-feedback loop runs on **MiniCPM4-8B**, an open-weight 8B model, loaded once and shared between question generation and answer evaluation.
|
| 30 |
+
|
| 31 |
+
```
|
| 32 |
+
PDF upload
|
| 33 |
+
└─► parser.py — PyMuPDF text extraction
|
| 34 |
+
└─► chunker.py — thematic chunking (min/max word caps)
|
| 35 |
+
└─► questioner.py — MiniCPM4-8B writes ONE focused question
|
| 36 |
+
└─► you answer
|
| 37 |
+
└─► evaluator.py — the same model grades you like a tutor
|
| 38 |
+
└─► image_gen.py — FLUX.2-klein paints your session
|
| 39 |
+
```
|
| 40 |
+
|
| 41 |
+
---
|
| 42 |
+
|
| 43 |
+
## The Real Story: 68 Commits of Lessons
|
| 44 |
+
|
| 45 |
+
A hackathon README tells you what was built. The git log tells you what actually happened. Ours has 68 commits, and roughly two-thirds of them start with `fix:`. Here is the honest version.
|
| 46 |
+
|
| 47 |
+
### Lesson 1 — Model choice is a compatibility problem, not a benchmark problem
|
| 48 |
+
|
| 49 |
+
We started with MiniCPM3-4B, upgraded to MiniCPM4-8B for better reasoning, and immediately hit the classic open-model trap: the model card says one thing, the `transformers` version on your machine says another.
|
| 50 |
+
|
| 51 |
+
```
|
| 52 |
+
fix: pin transformers==4.57.1 for MiniCPM4-8B compatibility
|
| 53 |
+
```
|
| 54 |
+
|
| 55 |
+
One pinned version later, everything worked. The follow-up lesson came from quantization: bitsandbytes 4-bit is great on a 16 GB local GPU and *completely unnecessary* on ZeroGPU's hardware — so we made it conditional:
|
| 56 |
+
|
| 57 |
+
```python
|
| 58 |
+
# HF Spaces (ZeroGPU): skip quantization, use bfloat16 directly
|
| 59 |
+
if os.environ.get("SPACE_ID"):
|
| 60 |
+
return None
|
| 61 |
+
# Locally: 4-bit when VRAM < 17 GB
|
| 62 |
+
```
|
| 63 |
+
|
| 64 |
+
Same code, two deployment targets, zero config files. Detect the environment, adapt.
|
| 65 |
+
|
| 66 |
+
### Lesson 2 — The Off-Brand badge nearly broke us (and taught us the most)
|
| 67 |
+
|
| 68 |
+
The hackathon has an **Off-Brand** badge: ship a UI that doesn't look like the framework you built it with. We wanted PaperProf to look like a real product — glassmorphism, animated score ring, dark academia palette — not a Gradio demo.
|
| 69 |
+
|
| 70 |
+
Attempt #1: restyle Gradio with CSS. We fought the theme system through *eleven consecutive commits* (`fix: CSS labels illisibles`, `fix: override variables CSS Gradio`, `fix: retire primary_hue orange qui changeait toutes les teintes`...). Gradio's theming always had one more `!important` than we did.
|
| 71 |
+
|
| 72 |
+
Attempt #2: nuke it from orbit. Docker SDK, FastAPI serving raw HTML, Gradio relegated to a backend. It worked locally and died on Spaces — we lost ZeroGPU integration, which only flows through the Gradio SDK.
|
| 73 |
+
|
| 74 |
+
Attempt #3, the one that shipped: **the hidden-component bridge**. Keep Gradio as an invisible backend *inside the page*. Serve a fully custom HTML/CSS/JS interface through `gr.HTML`, hide every real Gradio component off-screen, and let a 300ms JavaScript polling loop ferry data between the two worlds.
|
| 75 |
+
|
| 76 |
+
This pattern produced the three hardest-won discoveries of the hackathon:
|
| 77 |
+
|
| 78 |
+
**`display: none` silently kills Gradio.** Components hidden that way never get their Svelte event handlers attached. The fix is the oldest trick in CSS:
|
| 79 |
+
|
| 80 |
+
```css
|
| 81 |
+
/* collapsed but NOT display:none, so Gradio attaches event handlers */
|
| 82 |
+
#hidden-row-question { height: 0 !important; overflow: visible !important; }
|
| 83 |
+
```
|
| 84 |
+
|
| 85 |
+
**You can't `.click()` a Gradio button from JS.** Server-side rendering means the synthetic click goes nowhere. What *does* work: programmatically setting a hidden textbox's value through the native property descriptor, then dispatching `input`/`change` events so Svelte notices:
|
| 86 |
+
|
| 87 |
+
```javascript
|
| 88 |
+
function setGradioTA(sel, val) {
|
| 89 |
+
const el = document.querySelector(sel);
|
| 90 |
+
Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value')
|
| 91 |
+
.set.call(el, val);
|
| 92 |
+
el.dispatchEvent(new Event('input', {bubbles: true}));
|
| 93 |
+
el.dispatchEvent(new Event('change', {bubbles: true}));
|
| 94 |
+
}
|
| 95 |
+
```
|
| 96 |
+
|
| 97 |
+
Every action in PaperProf — generate question, submit answer, new MCQ — is a timestamp written into a hidden textbox, picked up by a `.change()` listener on the Python side. Buttons that aren't buttons.
|
| 98 |
+
|
| 99 |
+
**MutationObserver loses to Svelte.** Gradio's reactive DOM updates don't always fire observers the way you'd expect. We surrendered and switched to a humble `setInterval` polling loop. Less elegant, infinitely more reliable. Sometimes the dumb solution is the senior solution.
|
| 100 |
+
|
| 101 |
+
### Lesson 3 — ZeroGPU makes you think in seconds
|
| 102 |
+
|
| 103 |
+
ZeroGPU gives you a serious GPU for free, but only in short decorated windows. That budget reshapes your architecture:
|
| 104 |
+
|
| 105 |
+
- **First-call cold starts are real.** Loading an 8B model takes ~60–90s the first time. We built the UI to be honest about it: a live elapsed-time counter, escalating messages ("Model loading…", "Still loading… first call can take ~90s"), and a 3-minute hard timeout that unlocks the UI instead of spinning forever.
|
| 106 |
+
- **Never download inside the GPU window.** FLUX.2-klein weighs ~16 GB. We prefetch it in a daemon thread at *startup*, so the `@spaces.GPU` window is spent generating, not downloading. We even skip a 7.75 GB duplicate ComfyUI checkpoint in the repo that diffusers never reads.
|
| 107 |
+
- **Don't burn GPU time on things JavaScript can do.** MCQ grading needs no model call — the LLM emits a structured format once (`QUESTION:` / `A)`–`D)` / `CORRECT:` / `EXPLAIN_A:`…), we parse it into JSON, and the browser grades clicks instantly. Zero latency, zero GPU seconds.
|
| 108 |
+
|
| 109 |
+
### Lesson 4 — The bug that fired twice
|
| 110 |
+
|
| 111 |
+
Late in the hackathon, our session-summary modal showed every MCQ answer **duplicated**: answer one question, see it counted twice, score 0/2.
|
| 112 |
+
|
| 113 |
+
The cause was textbook event-handling: MCQ buttons had `btn.onclick = handler` assigned in the display function *and* an `addEventListener` registered by the global wiring function. One click, two handlers, two score increments. Our first fix removed the wrong one — the `addEventListener` had a timing flaw with its idempotency guard, so clicks then did *nothing at all*. The final fix kept the `onclick` (reassigned fresh with each question, inherently idempotent) and added a `mcqAnswered` re-entrancy guard for belt-and-suspenders.
|
| 114 |
+
|
| 115 |
+
Moral: when two pieces of code both "helpfully" wire the same button, you don't have redundancy — you have a race.
|
| 116 |
+
|
| 117 |
+
### Lesson 5 — Prompts are product decisions
|
| 118 |
+
|
| 119 |
+
Small prompt details made the difference between "tech demo" and "usable study tool":
|
| 120 |
+
|
| 121 |
+
- Early questions were rambling multi-part monsters. The fix was brutal constraint: *"ONE question only, on ONE concept. Maximum 25 words. No sub-questions, no 'and'."*
|
| 122 |
+
- The evaluator follows a fixed 4-part structure (Verdict / What was good / What was missing / Model answer) so the frontend can parse and render it as styled sections — prompt format *is* API contract.
|
| 123 |
+
- With French source PDFs, the model kept drifting into French. Polite instructions lost to the gravitational pull of the context. What finally worked: `IMPORTANT: Always write in English, even if the source text is in another language` — stated twice, once at the top and once at the bottom of the prompt. With 8B models, subtlety is wasted; repetition is a feature.
|
| 124 |
+
|
| 125 |
+
---
|
| 126 |
+
|
| 127 |
+
## What We'd Tell Past Us
|
| 128 |
+
|
| 129 |
+
1. **Read the git log of your own project sometimes.** Two-thirds `fix:` commits isn't failure — it's the actual texture of shipping. Each one was a lesson nobody had written down for us.
|
| 130 |
+
2. **Frameworks fight back hardest at the edges.** Using Gradio normally is easy. Using it as an invisible backend required understanding how it *actually* renders. The weird workarounds (`height:0`, textbox triggers, polling) are now reusable knowledge.
|
| 131 |
+
3. **Free infrastructure imposes honest engineering.** No API credits to hide behind means caring about cold starts, GPU seconds, and weight prefetching. Constraints made the architecture better.
|
| 132 |
+
4. **Client-side everything you can.** The MCQ mode is the snappiest feature in the app precisely because it never touches the server after generation.
|
| 133 |
+
5. **Ship the small thing.** PaperProf does one loop — read, ask, grade, encourage — and does it end-to-end. A hackathon project that completes one circle beats one that sketches five.
|
| 134 |
+
|
| 135 |
+
---
|
| 136 |
+
|
| 137 |
+
## The Stack
|
| 138 |
+
|
| 139 |
+
| Layer | Choice |
|
| 140 |
+
|---|---|
|
| 141 |
+
| Q&A + evaluation | MiniCPM4-8B (openbmb), bfloat16, transformers 4.57.1 |
|
| 142 |
+
| Session images | FLUX.2-klein-4B (Black Forest Labs), diffusers |
|
| 143 |
+
| PDF parsing | PyMuPDF |
|
| 144 |
+
| Backend / hosting | Gradio 6 on Hugging Face Spaces, ZeroGPU |
|
| 145 |
+
| Frontend | Hand-written HTML/CSS/JS over a hidden-Gradio bridge |
|
| 146 |
+
| External APIs | **None.** 🔌 Fully off the grid. |
|
| 147 |
+
|
| 148 |
+
---
|
| 149 |
+
|
| 150 |
+
*Built for the Build Small Hackathon, June 2026. The Space is live — bring a PDF and let the professor grill you: [huggingface.co/spaces/build-small-hackathon/PaperProf](https://huggingface.co/spaces/build-small-hackathon/PaperProf)*
|
TASK.md
CHANGED
|
@@ -57,7 +57,7 @@ Build Small Hackathon — June 5–15, 2026
|
|
| 57 |
- [ ] Replace transformers pipeline with llama-cpp-python
|
| 58 |
- [ ] Test performance vs transformers
|
| 59 |
- [ ] 📡 Sharing is Caring: export and share agent trace on HuggingFace Hub
|
| 60 |
-
- [
|
| 61 |
- [ ] Document architecture decisions
|
| 62 |
- [ ] Include benchmark results (speed, quality)
|
| 63 |
- [ ] Publish on HuggingFace blog or personal blog
|
|
@@ -66,6 +66,6 @@ Build Small Hackathon — June 5–15, 2026
|
|
| 66 |
- [ ] App running and stable on HuggingFace Space
|
| 67 |
- [ ] Demo video (~2 minutes) showing full flow: upload → question → answer → feedback
|
| 68 |
- [ ] Social media post (LinkedIn + Twitter) with Space link and demo
|
| 69 |
-
- [
|
| 70 |
- [ ] Submission form filled on HuggingFace
|
| 71 |
- [ ] All badge requirements verified
|
|
|
|
| 57 |
- [ ] Replace transformers pipeline with llama-cpp-python
|
| 58 |
- [ ] Test performance vs transformers
|
| 59 |
- [ ] 📡 Sharing is Caring: export and share agent trace on HuggingFace Hub
|
| 60 |
+
- [x] 📓 Field Notes: write blog post about what we built and learned (BLOG.md)
|
| 61 |
- [ ] Document architecture decisions
|
| 62 |
- [ ] Include benchmark results (speed, quality)
|
| 63 |
- [ ] Publish on HuggingFace blog or personal blog
|
|
|
|
| 66 |
- [ ] App running and stable on HuggingFace Space
|
| 67 |
- [ ] Demo video (~2 minutes) showing full flow: upload → question → answer → feedback
|
| 68 |
- [ ] Social media post (LinkedIn + Twitter) with Space link and demo
|
| 69 |
+
- [x] Blog post / Field Notes published (BLOG.md)
|
| 70 |
- [ ] Submission form filled on HuggingFace
|
| 71 |
- [ ] All badge requirements verified
|