tabras / FIELD_NOTES.md
Codex
Add field notes (lessons learned) to README and FIELD_NOTES.md
e9342cc
|
Raw
History Blame Contribute Delete
4.44 kB
# Field Notes β€” Building Tabras
What I learned building a small-model card duel for the Build Small hackathon.
## 1. ZeroGPU is a GPU-sharing mechanism, not a hosted GPU provider
This was my biggest wall, and I hit it late. I built Tabras assuming a Hugging Face
ZeroGPU Space would behave like a hosted GPU I could just run my models on. It does
not. ZeroGPU *time-slices* a shared GPU and only grants it inside `@spaces.GPU`
functions β€” and it ships args/returns between processes by pickling them. That
breaks the moment you try to hand it a `trust_remote_code` model (MiniCPM) or a
diffusers pipeline: they aren't picklable, and low-level CUDA init isn't allowed in
the main process at all. I burned real time learning this through a wall of
`PicklingError` and "CUDA init reached" tracebacks.
The last-minute fix was to **re-architect**: make the Space a thin HTTP client and
move every model onto **Modal** GPU endpoints (MiniCPM for cards, Nemotron for the
boss, SDXL-Turbo for art), each autoscaled on its own dedicated GPU. The Space just
POSTs prompts and renders the results. Lesson: understand the *execution model* of
your compute before you design around it, not after.
Crucially, Tabras still runs **fully local / off-grid** β€” a `MODE` switch flips
between LOCAL (in-process Transformers/Diffusers, or a local `llama.cpp` server for
MiniCPM) and MODAL (the hosted endpoints). The README has the local instructions.
Small and local was always the point, and keeping that path alive mattered to me.
## 2. Small models: surprisingly powerful, with sharp edges
- **Image models punch way above their size.** SDXL-Turbo, in ~4 denoising steps,
produced genuinely striking card art and theme backgrounds. The visual identity of
the whole game is carried by a tiny, fast diffusion model.
- **Nemotron was the standout for agentic / tool-calling work.** Giving it the public
board state and a constrained JSON action schema, it reliably reads the situation
and returns valid, sensible plays. A 4B model running a competent opponent was the
part I expected to be flaky and wasn't.
- **The text model owns meaning, not structure.** MiniCPM writes evocative names and
flavor, but it leans on lazy patterns ("Fire Card"), leaks prompt vocabulary, and
truncates its own JSON. The single highest-leverage fix was reordering the requested
JSON so `effects` and `name` come *first* β€” they survive a token cutoff that used to
silently collapse a whole card to a fallback. Design around what the model is bad at.
## 3. Perceived latency beats raw latency
Small/local inference isn't instant, and the variance (some packs fast, some slow)
looked worse than a consistent wait. The fixes that made the demo feel *good* were
mostly UX, not compute:
- A **minimum loading window** on each draft transition, so every pick shows the same
deliberate "forging" beat β€” that uniform pause hides the slow ones behind the same
animation the fast ones use.
- **Prefetching every branch** of the draft during idle time (the reveal/rules screens,
and while you read the current pack), so whichever card you pick, its next pack is
already generating.
- **Pre-baking static art** for the fixed backbone cards once and bundling it, so they
never spend a live generation or shimmer.
A consistent 2s always beats an unpredictable 0–8s.
## 4. Card-game design is hard β€” and a ton of fun
Balancing a generative card game is genuinely difficult. The core principle that made
it tractable: **the LLM owns meaning, the engine owns math.** The model picks *which*
effects a card has and writes its identity; deterministic Python prices every number
against a point budget, so cards are balanced-by-construction no matter what the model
invents. The draft is deck-aware β€” it reads the build you're assembling and shapes
packs toward it, sometimes dangling a tempting off-archetype card. Getting that loop to
feel fair *and* surprising was the most fun part of the whole project.
## 5. Ambition under a deadline
This was an ambitious build for the time window β€” three small models, a custom UI, a
last-minute compute re-architecture, and a real game loop. The thing that saved me was
treating the **demo video as the deliverable** and optimizing the local recording
surface hard, rather than betting everything on a flawless live Space. I'm proud of how
much of it came together, and I had a lot of fun doing it.