We fine-tuned a modelthat speaks generative UI.
MDMA is designed to work on small models. To find out how small, we trained one. It reads a one-line DSL intent and returns a valid MDMA document — and it passes every case in the gate we run against it.
What it is
A domain-specific fine-tune of Google's Gemma 4, trained on one job and nothing else: read an MDMA-IL DSL intent, write the MDMA document it describes. The LoRA is merged into the base weights, so it is one model to download and serve — not a base plus an adapter.
- Base model
- Gemma 4 26B-A4B (Instruct)
- Parameters
- 27B
- Method
- LoRA, merged into the base weights
- Published as
- BF16 safetensors
- Served as
- INT8, OpenAI-compatible endpoint
- License
- Apache 2.0
Built on unsloth/gemma-4-26B-A4B-it. A modified derivative of Google's Gemma 4, distributed under the Apache License 2.0.
One line in, a working component out
The model does not read prose. It reads MDMA-IL: a compact intent language, one line per component. That is what makes it cheap — the prompt is small, the input is small, and the output is the only part that has to be right.
form#contact[full-name*:t, email*^:e](action=contact-submitted)
A contact form with a required name and a required email flagged as personal data, submitting the contact-submitted action.
type: form
id: contact
fields:
- name: full-name
type: text
label: "Full Name"
required: true
- name: email
type: email
label: "Email"
required: true
sensitive: true
onSubmit: contact-submittedRenderable as-is by any of the three renderers. The ^ became sensitive: true, which is what drives PII redaction in the runtime.
<type>#<id>[<field>, <field>, ...](<prop>, <prop>, ...)
field = <name>[*][^]:<typecode>[{opt1|opt2|...}]
* = required ^ = sensitive (PII)
typecode: t=text n=number e=email d=date
s=select c=checkbox ta=textarea f=file
props = text="..." | action=<id> | variant=<name>Gated, not vibe-checked
The model ships behind the same PromptFoo suites the third-party models run, plus a holdout gate of 95 scenarios it never saw in training. Every run below is committed to the repository as JSON.
Committed eval runs
181/181 cases · 0 failures · 0 errors
| Suite | Cases | Passed | What it asserts |
|---|---|---|---|
| DSL holdout gate | 95 | 95 | Every output parses and validates as an MDMA document |
| Author | 28 | 28 | Correct component, exact field count, PII flags |
| Flows | 15 | 15 | Multi-step example flows |
| Fixer | 15 | 15 | Repairs a broken document to valid output |
| Guidance | 15 | 15 | Calls the generate_mdma tool correctly |
| Custom prompt | 13 | 13 | Author prompt layered with a custom one |
An earlier and much smaller fine-tune, on Gemma 4 E4B with a 2048-token context, topped out around 90.5% valid on the same holdout. The jump to the 26B base is what closed the gap.
The serving contract
This is a narrow model, and it behaves like one. Four settings separate clean output from a Gemma 4 reasoning loop — none of them optional.
The model was tuned against this exact prompt. A different one is out of distribution and quality drops.
Sent as a chat template argument. Left on, the reasoning channel leaks into the output.
Greedy decoding for the deterministic DSL mode. The conversational mode runs at 1.
The repetition guard for Gemma 4's reasoning-loop failure mode.
- Deterministic. The DSL intent in, greedy decoding, one document out. Reproducible.
- Conversational. Sampling at temperature 1 with tool calling, so an agent can hold a dialogue and reach for a
generate_mdmatool when it needs UI.
Each mode has its own system prompt. Both ship in @mobile-reality/mdma-prompt-pack.
Why bother training one
Every generative-UI format claims it works on small models. The honest test is whether you would bet your own weights on it.
A strict JSON UI contract falls apart at this size — one unbalanced brace and the whole payload is unusable, so teams quietly fall back to the largest tier and pay for it on every request. MDMA is forgiving Markdown with a schema behind it, which is exactly the shape a small model can hold together. A 27B open-weights model, served quantized, emits it reliably enough to gate on.
Which means the interesting number is not the model. It is what the model implies: you can run generative UI on hardware you control, at a cost that does not scale with your traffic, on weights nobody can deprecate under you.