Spaces:
Running
Running
| # 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. | |