A newer version of the Gradio SDK is available: 6.20.0
title: The Shape Of Words
emoji: π₯
colorFrom: indigo
colorTo: purple
sdk: gradio
sdk_version: 6.18.0
python_version: '3.12'
app_file: app.py
pinned: false
short_description: Tell a story. Watch its feelings take shape.
license: mit
tags:
- track:wood
- sponsor:openbmb
- sponsor:modal
- achievement:offbrand
- achievement:llama
- achievement:fieldnotes
The Shape of Words
A collaborative storytelling toy: tell a story one beat at a time, and the model turns the feeling of each beat into an abstract shape. At the end, hidden colors bloom through the shapes one by one, the model adds its own companion shapes, and everything composes into a single arrangement you can rearrange, sonify, save, or turn into a painting.
Live SpaceDemo VideoSocial Media Post- Field notes (deep dive):
Hugging Face Articleorfield_notes.md
Built on gradio.Server (FastAPI + Gradio's API engine, gradio 6.x) so the custom HTML/JS frontend gets queuing, streaming, and Hugging Face Spaces hosting.
Architecture
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Browser β static/index.html (custom HTML/JS, no build step) β
β β’ renderer Β· mappings Β· layout Β· sound Β· share card Β· UI β
β β’ all rendering is client-side (JS port of engine/) β
β β’ calls the backend for MODEL JUDGMENT only β
βββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββββ
β /gradio_api/call/<name> (queued, SSE)
βββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββ
β app.py β gradio.Server β
β @app.api judge_beat Β· judge_beat_segmented Β· β
β continue_story Β· reveal Β· title_story Β· paint β
β @app.get "/" serves the frontend β
βββββββββ¬βββββββββββββββββββββββββββββββββββββββ¬βββββββββββββββ
β model/backend.py β model/painter.py
β STORY_SHAPES_BACKEND β STORY_SHAPES_PAINT_BACKEND
βΌ βΌ
llamacpp GGUF in-process (llama.cpp) modal Modal endpoint (FLUX.2 Klein)
modal_llm Modal endpoint (transformers) flux_local in-process diffusers
The model only judges affect (valence / arousal / dominance + a few flags +
a voice line). Everything visual β geometry, color (OKLCH), layout, scoring β is
deterministic code (in engine/, mirrored client-side for rendering), grounded
in perception research (see the spec's references).
Run locally (llama.cpp + MiniCPM4.1-8B)
The default backend runs the LLM in-process via llama-cpp-python β no daemon,
no separate model server.
pip install -r requirements.txt
python app.py # serves http://localhost:7860
On first run it pulls a quantized GGUF (default openbmb/MiniCPM4.1-8B-GGUF,
Q4_K_M, ~5 GB). With a GPU it offloads all layers automatically; CPU-only?
set STORY_SHAPES_LLAMACPP_GPU_LAYERS=0. Then open http://localhost:7860.
Config via env vars (all optional):
| var | default | meaning |
|---|---|---|
STORY_SHAPES_BACKEND |
llamacpp |
llamacpp (in-process) or modal_llm (Modal endpoint) |
STORY_SHAPES_LLAMACPP_REPO |
openbmb/MiniCPM4.1-8B-GGUF |
GGUF Hub repo |
STORY_SHAPES_LLAMACPP_FILE |
*Q4_K_M.gguf |
GGUF filename glob |
STORY_SHAPES_LLAMACPP_GPU_LAYERS |
-1 |
layers on GPU (-1 all, 0 CPU-only) |
PORT |
7860 |
server port |
The frontend falls back to a built-in keyword stub if the backend is unreachable, so
static/index.htmlalso opens standalone for quick UI testing.
Deploy to a Hugging Face Space
The README YAML frontmatter above is the HF Space config (sdk: gradio,
sdk_version: 6.18.0, app_file: app.py). The gradio version is pinned there
because gr.Server requires gradio 6.x. Push this repo to a Space and set the
env vars below.
Recommended setup β both models on one 24 GB GPU (e.g. a dedicated L4):
| var | value | notes |
|---|---|---|
STORY_SHAPES_BACKEND |
llamacpp |
MiniCPM4.1-8B as a GGUF via llama.cpp (~5 GB at Q4_K_M) |
STORY_SHAPES_PAINT_BACKEND |
flux_local |
FLUX.2 Klein 4B in-process with CPU offload |
A quantized LLM (5 GB) + cpu-offloaded FLUX (8β12 GB peak) fit comfortably
together on 24 GB. A dedicated GPU Space is also not ZeroGPU, which avoids
ZeroGPU's per-request quota attribution.
Alternative β offload the heavy work to Modal (Space then needs no GPU):
| var | value | notes |
|---|---|---|
STORY_SHAPES_BACKEND |
modal_llm |
LLM on a Modal GPU (modal deploy modal_llm.py) |
STORY_SHAPES_LLM_MODAL_URL |
(from modal deploy) |
the printed modal_llm URL |
STORY_SHAPES_PAINT_BACKEND |
modal |
painter on a Modal GPU (modal deploy modal_painter.py) |
STORY_SHAPES_PAINT_MODAL_URL |
(from modal deploy) |
the printed modal_painter URL |
DISCLAIMER: I was attempting the Off the Grid challenge (local models only) but I could not test the model deployment on ZeroGPU due to consistently encountering 429 Too Many Requests error (more details in the "deployment story" in the Architecture section in field_notes.md). For stable/expected results on the Space I'm routing both models to Modal (shape judgment & painter; this will increase initial inference time due to cold start); I apologize for the inconvenience. I have attached a local_run_logs.txt for proof that the shape judgment model runs locally.
Why not run MiniCPM4.1 in-process under plain
transformers? gradio 6 forcestransformers β₯ 5.0, and MiniCPM4.1'strust_remote_codemodeling code is incompatible with transformers 5.x (it loads with a shim but crashes during generation). That's exactly why the LLM runs via llama.cpp (no transformers at all) or on Modal with transformers pinned to 4.x.
See requirements.txt for the llama.cpp install (a prebuilt CUDA wheel, with a
one-comment CPU-fallback toggle) and the diffusers-from-source line FLUX needs.
Layout
app.py gradio.Server: API endpoints + serves frontend
requirements.txt
engine/ deterministic core (also mirrored in the frontend JS)
mappings.py affect -> geometry, affect -> color (OKLCH, research-grounded)
renderer.py geometry -> shape points
scorer.py zone-based puzzle scoring (+ band + hint)
model/
backend.py LLM abstraction: llamacpp / modal_llm
painter.py painting abstraction: modal / flux_local
modal_llm.py Modal GPU endpoint for the LLM (deploy once)
modal_painter.py Modal GPU endpoint for FLUX.2 Klein (deploy once)
prompts/ system prompts + JSON schemas / GBNF grammars per call
static/
index.html the entire frontend (custom HTML/JS)
docs/
make_spec_figures.py renders the spec/field-notes illustrations from the engine
Features
- β Exploration mode: full loop, hidden-color reveal, AI companion shapes, narrative composition, drag / rotate / layers / hide / save.
- β Per-segment essence: shape morphs through each phrase's felt character.
- β OKLCH color: perceptually-uniform lightness (Python + JS parity).
- β Reveal sound (Web Audio): pentatonic chimes, shape-born tick, atmospheric pad, replay β togglable in Settings.
- β Share card (canvas + painting): model-named title, popup, PNG export.
- β Origin tooltip: hover/tap a shape or layer row to see its source beat.
- β
Painting step (FLUX.2 Klein img2img): "Paint this"; free style field +
preset styles + π² Surprise me; randomized seed;
modalorflux_local. - β
LLM backends:
llamacpp(GGUF, Llama Champion) andmodal_llm. Both produce schema-constrained JSON. - β
Felt-quality eval (
eval_felt_quality.py): A/B models on a fixed set. - β
HF Space deployment (gradio 6.18,
gr.Server).