Cadabra

Frontier models write CAD from a spec. Give them a drawing and they fall apart.

Kimi K3 gets 95% of our held-out parts right from a text description. Hand it the same part as a four-view drawing sheet and it drops to 61.8%. That gap is narrow, it matters, and it can be graded exactly.

80.7%correct on 497 held-out parts, sampling 8 programs and picking one without an answer key
GLM-5.3 Flash66.2%
Kimi K361.8%
the same 4B, untrained14.7%
our cost per 1K parts$0.22
01 · Pipeline

From a public dataset to a model that checks its own work

Public CAD data is noisy, so every stage either checks the data or removes a way for the result to be wrong.

  1. 1

    Audit every reference program

    Execute all 82,659 programs in CAD-Coder and check each resulting solid against the dimensions its own spec states. This is where we found the data was wrong.

  2. 2

    Build clean splits

    Hold 500 parts out by source part, then drop every training part whose geometry signature matches a benchmark part. 1,886 were removed this way.

  3. 3

    Render each part as a drawing

    Front, top and right at one shared scale, plus an isometric view, rendered from the part's own reference program so the drawing and the answer always agree. 30,260 sheets.

  4. 4

    LoRA fine-tune Qwen3-VL-4B

    Rank 64 on Baseten Training, adapters on the language model with the vision tower frozen, loss on the generated code only. Two epochs: 2.9 hours on one H100, then 55 minutes on four.

  5. 5

    Serve from the training checkpoint

    Merged weights are pulled straight out of the training job and served with vLLM. No copy step between training and serving.

  6. 6

    Sample, then check its own work

    Draw eight programs, render each candidate the way the input sheet was drawn, and keep whichever matches the drawing best. The verifier never sees the reference solid.

  7. 7

    Grade by executing the geometry

    Run the program in a sandbox and compare its solid to the reference by volumetric intersection over union. No model judges another model anywhere in this project.

02 · Data

Look at your data

We ran all 82,659 reference programs before training anything, and checked each solid against the size its own specification claimed.

14%

of the official test split contradicts its own specification, on single-part rows where the stated size is checkable. The hand-checked train_high split came in at 2.3%. Anyone reporting accuracy against that test split is reporting partly on noise.

Inconsistent transforms

References apply the specs' stated rotations and translations inconsistently, so the metric aligns both solids first, taking the best overlap across the 24 axis rotations.

Still strict

Alignment forgives placement, not shape or size. A part built 10% too large scores 0.75 and fails, well short of the 0.9 bar.

Code that writes to disk

Some reference programs export files while being evaluated. The sandbox strips export calls, allows only three imports, and runs in a throwaway directory.

03 · Grading

How a part is scored

A prediction is correct when its program runs and the solid it produces overlaps the reference by 0.9 or more. Everything else is a miss, including code that builds something plausible but wrong.

The metric

IoU = volume(A ∩ B) / volume(A ∪ B)

Exact boolean volumes from OpenCascade, not a mesh approximation and not a sampled estimate. We take the best value over all 24 axis-aligned rotations after centering both solids.

The verifier, which never sees the answer

To pick between candidate programs at inference time, each one is built and re-rendered from the same three orthographic views as the input sheet. The score is silhouette overlap across those views, multiplied by how closely the candidate's bounding box matches the one stated in the prompt.

0.96 AUC

separating correct programs from incorrect ones across 689 real frontier-model answers spanning 237 parts. Given several answers for one part it picks a correct one 98% of the time, against 76% for a random pick. It is not specific to our model: the same verifier lifts Kimi K3 from 28% to 48% on hard parts.

04 · Results

Every lane on the same 497 parts

Bootstrap 95% confidence intervals on every row. A request the API never answered is excluded and listed, never counted as a wrong answer.

ModelCorrect95% CISimpleMediumComplexMulti$ / 1Kp50
Cadabra 4B, best of 880.7%77–8495.6%61.3%39.0%58.1%$1.192.3 s
Cadabra 4B, 1 sample74.8%71–7992.5%52.1%25.4%53.5%$0.222.0 s
GLM-5.3 Flash (high)66.2%62–7085.9%37.0%18.6%39.5%$1.003.7 s
Kimi K3 (high)61.8%58–6681.5%30.3%18.6%39.5%$36.8210.4 s
Base model (untuned)14.7%11–1819.4%8.4%1.7%16.3%$1.3820.3 s

Tiers are the reference solid's face count: simple ≤ 6, medium 7 to 12, complex ≥ 13. Frontier lanes get two worked examples and high reasoning effort. Ours gets neither.

Correct answers by part complexity, one bar per model, with 95% confidence intervals
The gap widens with complexity. On parts with 13 or more faces we are at 39.0% against 18.6% for both frontier models.
Accuracy against training step, with the frontier models drawn as reference lines
13% untuned, 61% by step 200, 68% by step 1,000, 73.5% after one epoch and 77.0% after two, on the frontier's 200 parts.
05 · Leakage

Is it memorising the benchmark?

The obvious objection to any fine-tune, so here is the evidence rather than an assurance.

No benchmark part is in training

The 500 benchmark parts are held out by source part, with zero shared, and every training part carrying a benchmark part's exact geometry signature was dropped. That removed 1,886 of them.

Validation loss never turned up

0.212 to 0.158 across the first epoch, 0.160 to 0.149 across the second, with token accuracy reaching 94.7%. Neither pass shows the upturn you would expect from overfitting.

ModelNear-duplicate (57)No near-duplicate (443)
Cadabra, 1 sample94.7%72.2%
Cadabra, best of 894.7%78.8%
Kimi K3 (high)73.7%60.3%
GLM-5.3 Flash (high)84.2%63.9%

57 of 500 benchmark parts have a training part within 1% on every bounding-box axis with the same face and part count. None within 0.1%. Those parts are easier for every model, including ones that never saw our data.

+14.9 points

is our margin over the best frontier model on the 443 parts with no near-duplicate in training, slightly wider than the +14.5 we hold overall. Removing the parts we could plausibly have memorised makes the result better, not worse.

06 · Negative result

What didn't work

The grader is an exact reward, so reinforcement learning on it looked obvious. It made the model worse.

We ran GRPO on top of the supervised model, 8 samples per sheet and 64 rollouts per step, for 60 steps on four H100s. Training reward rose from 0.89 to 1.2 and the share of correct rollouts went from 39% to 70%, so by its own training signal it was working.

Held-out accuracy fell anyway. We graded every saved checkpoint with the same harness, including the supervised checkpoint as a control, which reproduced its own score to within two parts. The loss lands almost entirely on medium-complexity parts: 56.3% down to 48.7% after ten steps, then flat near 42%.

We shipped the supervised model and published the checkpoint sweep, because a negative result with the evidence attached is worth more than quietly dropping the branch.

Held-out accuracy of each GRPO checkpoint against the supervised control
Every GRPO checkpoint graded against the supervised control. Training reward improved while held-out accuracy fell.
07 · Provenance

The data, and what it was built on

Everything derives from CAD-Coder, which is Apache-2.0 and itself derived from Text2CAD and DeepCAD.

SetPartsWhat it is
Audited82,659Every reference program run and checked against its own spec
Benchmark, held out500457 single-part, 43 multi-part, held out by source part and by geometry
Training, train_high5,698What remains after removing benchmark sources and geometries
Training, train_middle24,868Two batches, weighted toward medium, complex and multi-part
Sheets trained on30,260Plus 306 validation. 72% medium or complex, 35% multi-part
Cost per thousand parts against accuracy, one point per model
Cost per 1,000 parts against accuracy. Ours includes the GPU hour amortised over measured throughput, not just tokens.
Built on Baseten
  • Model APIs for the Kimi K3 and GLM-5.3 Flash baselines, at high reasoning effort.
  • Training Jobs for the LoRA fine-tune. One H100 for the first epoch, four with DDP for the second.
  • Deployments pulling merged weights from the training job's checkpoints, served with vLLM.
  • Switch, which ran Claude Code on open models while we built this.