Describe the model you want in plain language. Get back a calibrated, exported, deployable vision model, or a documented refusal. Prompt2Model is a typed pipeline: a prompt becomes a dataset config, then a trained model, then a conformally calibrated model that can abstain, then a verified ONNX export, then an optional compressed variant that only ships if it clears an accuracy floor. The factory's contract is not "a model, always". It is "a model you can trust, or an explicit, logged refusal".
34103b3): the full test suite (118 tests), the end-to-end smoke test for
both tasks, per-image inference on all 36 committed classification examples, per-image top-3
detections on all 12 committed detection examples, the synthetic out-of-distribution abstain
check, and a quantization run through the accuracy-floor gate. Retained from earlier
same-day sessions and labeled as such inline: the historical accuracy-floor refusal
(commit 4e478b2) and both training-loss curves (the current report format records
final metrics only, so no fresh per-epoch curve exists to replace them).Picture a small operations team running a packing line. They want a camera to sort items into three bins by shape and color, or to flag when a specific object shows up in a fixed camera view. They can describe the task in one sentence. What they do not have is a labeling team, an ML engineer, or six weeks. This is exactly the kind of narrow, well-bounded vision task that a small model handles well, and exactly the kind of project that never gets built because the path from "one sentence" to "deployed model" traditionally runs through hand-labeling thousands of images and hiring someone to babysit a training pipeline.
Automated model factories exist, but most share one dangerous property: they always hand you a model. Ask for the impossible and you still get a model file back, one that is confidently wrong on inputs it has never seen, or one that was quietly quantized into uselessness to hit a size target. For an operator, a factory that silently ships an overconfident or under-accurate model is worse than no factory at all, because the failure is invisible until it costs something. The model does not know what it does not know, and neither does the person deploying it.
Prompt2Model is built around the opposite contract. The factory compiles a plain-language task description into a full training and export pipeline, and at the two points where silent failure usually happens it installs an explicit refusal: a per-input abstention gate calibrated on held-out data (the model says "I don't know" instead of guessing), and a build-time accuracy floor on compression (the factory keeps the uncompressed model rather than ship a degraded one). Both refusals are enforced in code and logged in the run report, not just documented.
The whole factory is one typed pipeline. Every stage consumes and produces a validated, inspectable object, so a half-parsed prompt or a missing field fails loudly at the boundary instead of surfacing three stages later as a mystery. The two red boxes below are the refusal gates.
1. Prompt to config. A deterministic, dependency-free parser turns the prompt into a typed pipeline configuration: task, labels, augmentations (for example "under low light"), and constraints such as a latency target, a power budget, a parameter cap, or an accuracy floor ("keep at least 90% accuracy" becomes a hard number the compression gate must respect). An optional LLM planner can sit in front of it for indirect phrasing; it only overlays fields it actually extracted, never replaces the deterministic parse, and any planner failure falls back to the parser, so a config is always produced.
2. Data and training. Image-folder classification and COCO-style detection loaders, a small model registry (a stated constraint always pushes model choice down to a smaller tier, never up), and standard training loops with the prompt's augmentations injected. For this page's evidence the data is the factory's own generated toy shape set; on a real task you point it at your own folder.
3. Refusal gate 1: conformal abstention calibration. On the held-out validation split the factory fits a temperature (logit scaling that minimizes validation NLL, with expected calibration error recorded before and after so the effect is auditable) and a split-conformal abstention threshold: the (1 - alpha) quantile of validation nonconformity, where nonconformity is 1 minus the calibrated top-class probability. Both values are embedded in the ONNX file's metadata, so they travel with the artifact. At inference, nonconformity above the threshold means the model returns ABSTAIN instead of a guessed label.
4. Export. The model is exported to ONNX with its labels, preprocessing parameters, and calibration block injected as metadata, then re-loaded and executed in ONNX Runtime to verify the file actually runs, not just that it exists on disk.
5. Refusal gate 2: the accuracy floor on compression. Optionally the factory distills or INT8-quantizes the exported model. The compressed candidate is then re-evaluated in ONNX Runtime, the same runtime it would ship in, and compared against the stricter of a relative floor (98% of baseline accuracy by default) and any absolute floor stated in the prompt. If it cannot hold that floor, the factory refuses to ship the compressed artifact and keeps the uncompressed one, logging the refusal verbatim in the run report.
6. Deployment target. The artifact compiles to a target, not a box: ONNX
Runtime by default, TensorRT when requested (built locally when trtexec is present,
otherwise the factory emits a reproducible on-device build script instead of failing
silently).
7. Flywheel. At inference, abstained and low-confidence frames can be captured into a bounded hard-case store, so the inputs the model could not handle become the candidate training data for the next iteration instead of being guessed at and forgotten.
Both browsers below show real, committed example images and the real per-image output of the
factory's own exported ONNX models, produced by
python -m prompt2model.cli smoke-test --output-dir output/smoke_verify at commit
34103b3 this session (2026-07-09) and then run image by image. No number below is
interpolated or invented; this Space is static, so what you see is a faithful record of those
runs, not live compute.
The prompt behind this run: "Classify red square, blue circle, and green triangle images
under low light and prioritize speed." (the factory's own committed classification
smoke-test prompt). The factory trained a 1.520931M-parameter mobilenet_v3_small
from ImageNet-pretrained weights, fit a conformal threshold of 0.004888 from 7
held-out validation samples at alpha=0.1, with logits temperature-scaled by
temperature=0.05 (ece_before=0.3864 to ece_after=0.0014, a
real calibration improvement). Pick an image to see the model's real prediction, its real
nonconformity score, and the gate's decision:
The real, honest finding: the model predicts confidently and correctly on all
36 of 36 real committed images checked this session: score 1.000000 (nonconformity 0.0) on all 24
red-square and blue-circle images, and score 0.995113 (nonconformity 0.004887) on all 12
green-triangle images, every one below the 0.004888 threshold by a razor-thin margin
(the green-triangle nonconformity sits just 0.000001 under the threshold), so the
gate answers on all 36. Zero real abstentions came out of this run: on this near-perfectly
separable synthetic toy set, a healthy pretrained backbone simply does not find anything to
hesitate on. That is disclosed as the real result, not softened into a fake abstain example.
Because the real run produced no ABSTAIN case to show, here is the gate exercised on an
illustrative, synthetic, out-of-distribution input instead of one of the 36 real
committed images, so the refusal mechanism is still demonstrated end to end with the same real
model and the same real threshold. Feeding the exact same ONNX model a 96×96 image of pure
random RGB noise (not a photo of anything, generated with
numpy.random.default_rng(seed=7)) produces top-class score 0.969760,
nonconformity 0.030240, above the 0.004888 threshold, and the model's
own EdgeModel.run_inference returned abstained: true, rerun fresh this
session:
The abstention decision is one comparison, computed here in your browser exactly as the
deployed EdgeModel computes it: abstain when 1 - p > threshold.
Decision rule exactly as implemented. Calibration failures never kill a
run; calibrate_classification returns calibrated: false on any error,
and artifacts exported before this stage existed simply load with abstention off, so old
artifacts keep working.
The detection smoke test uses the prompt "Detect squares and circles in low light images
and prioritize speed." and trains an ssdlite320_mobilenet_v3_large for one toy
epoch. Below, real committed COCO-style ground-truth boxes (green) versus the freshly exported
detector's real top-3 scored boxes (red), run image by image this session:
Green boxes: real committed ground truth. Red boxes: the real exported model's top-3 scored boxes this session.
The real, honest finding: across all 12 committed example images, the fresh
detector's top-3 boxes now vary with the image (the earlier, pre-fix export produced identical
boxes on every image; that specific pathology is gone after the pretrained-backbone fix), but
they still do not track the real object locations, scores sit in a low 0.3683 to 0.4504 band, and
34 of the 36 top boxes carry the raw label index 2, which falls outside the 2-class
label list (logged here as the raw index rather than invented as a real class name; the remaining
2 boxes, on image_008, are labeled circle and land loosely near the real circle).
This matches the freshly measured mAP@0.5 of 0.0338: a toy detector trained for one epoch on a
handful of synthetic images has not learned to localize yet. Reported as measured, not smoothed
over.
Rules for this section: every table states where each number comes from (exact command and commit, or exact file). Rows rerun this session are labeled fresh; rows kept from earlier same-day runs are labeled historical and are never silently mixed with fresh ones.
| check | result | when | provenance |
|---|---|---|---|
| full test suite, fresh | 118 tests, 0 errors, 0 failures, 0 skipped, exit code 0 | 2026-07-09, commit 34103b3 | .venv/bin/python -m pytest tests/ -q --junitxml=...; JUnit summary verbatim:
tests="118" errors="0" failures="0" skipped="0" time="106.196" |
| test count at the v0.1.0 tag | 117 tests passing | 2026-07-09, tag v0.1.0 (commit 4e478b2) | the tag's own release notes; the fresh count of 118 reconciles exactly: PR #19 added one
test (test_edge_model_reads_real_exporter_contract) |
| host for all fresh runs | macOS arm64, CPU only | 2026-07-09 | output/smoke_verify/*/telemetry.json: Darwin 25.5.0, arm64, Python 3.12.13,
no GPU; all latency numbers on this page are CPU latencies on this host |
The plain pytest terminal summary line was swallowed by a local reporting quirk (same quirk as the earlier session), so the machine-written JUnit XML counts are quoted instead; the run's exit code was 0.
| task | run | model | params | CPU latency | throughput | headline metric |
|---|---|---|---|---|---|---|
| classification | fresh, 34103b3 | mobilenet_v3_small (pretrained) | 1.520931 M | 70.59 ms | 14.17 fps | accuracy 1.0, macro F1 1.0 |
| detection | fresh, 34103b3 | ssdlite320_mobilenet_v3_large | 3.72542 M | 37.29 ms | 26.81 fps | mAP@0.5 0.0338, mAP@[0.5:0.95] 0.0131 |
| classification | historical, ba62c2f | mobilenet_v3_small (pretrained) | 1.520931 M | 81.00 ms | 12.35 fps | accuracy 1.0, macro F1 1.0 |
| detection | historical, 4e478b2 (pre-fix, from scratch) | ssdlite320_mobilenet_v3_large | 2.220380 M | 66.26 ms | 15.09 fps | mAP@0.5 0.0208, mAP@[0.5:0.95] 0.0045 |
Fresh rows: python -m prompt2model.cli smoke-test --output-dir
output/smoke_verify at commit 34103b3, 2026-07-09; numbers verbatim from
output/smoke_verify/classification_run/telemetry.json (latency_ms 70.59494140557945,
fps 14.165320915203214) and output/smoke_verify/detection_run/telemetry.json
(map@0.5 0.03375959079283888, map@[0.5:0.95] 0.013069053708439898, latency_ms 37.29286682792008,
fps 26.814779475503588, gflops 0.510593344). Both fresh ONNX exports were verified runnable
(edge_inference_verified: true). Historical rows are retained from the earlier
same-day sessions at the commits shown; the historical detection row predates the
pretrained-backbone fix and used a smaller from-scratch configuration, which is why its parameter
count differs. The point of this table is that the typed pipeline runs end to end and both
refusal gates fire correctly when they should, not that these toy models are competitive.
| quantity | value | provenance |
|---|---|---|
| temperature (logit scaling) | 0.05 | fresh, commit 34103b3:
calibration block in output/smoke_verify/classification_run/telemetry.json,
embedded in the exported ONNX metadata; identical values were produced by the earlier
ba62c2f run |
| ECE before calibration | 0.3864 | |
| ECE after calibration | 0.0014 | |
| alpha (target 90% coverage) | 0.1 | |
| held-out validation samples | 7 | |
| conformal threshold | 0.004888 |
| input set | n | top-class score | nonconformity | gate decision | correct |
|---|---|---|---|---|---|
| red_square examples | 12 | 1.000000 | 0.000000 | PREDICT (12/12) | 12/12 |
| blue_circle examples | 12 | 1.000000 | 0.000000 | PREDICT (12/12) | 12/12 |
| green_triangle examples | 12 | 0.995113 | 0.004887 | PREDICT (12/12) | 12/12 |
| synthetic OOD: 96x96 RGB noise, seed=7 (illustrative, not a committed example) | 1 | 0.969760 | 0.030240 | ABSTAIN | n/a (no true label) |
Fresh, this session (2026-07-09, commit 34103b3): per-image
EdgeModel.run_inference over all 36 committed example images and the seed-7 noise
image, against output/smoke_verify/classification_run/model.onnx. The scores
reproduce the earlier ba62c2f session's per-image results exactly. Note the caveat the numbers
themselves carry: a threshold fit from only 7 validation samples is statistically noisy, and the
green-triangle margin under the threshold is 0.000001, both disclosed rather than smoothed.
The gate's promise is "a smaller model OR a refusal". Both outcomes have now actually happened, and both are shown with full numbers rather than picking the flattering one:
| quantity | historical refusal (4e478b2) | fresh pass (34103b3) |
|---|---|---|
| compression attempted | dynamic INT8 quantization | dynamic INT8 quantization |
| baseline val accuracy | 0.0 | 1.0 |
| compressed val accuracy | 0.0 | 1.0 |
| accuracy floor applied | 0.6 | 0.98 |
| gate result | passed=false | passed=true |
| baseline ONNX size | 6,094,110 bytes (5.81 MB) | 16,801,037 bytes (16.02 MB) |
| compressed ONNX size | 1,693,773 bytes (1.62 MB), would have been 72% smaller | 4,361,895 bytes (4.16 MB), 74.0% smaller |
| reason, logged verbatim | "REFUSED: compressed artifact fell below the accuracy floor, shipping uncompressed" | "compressed artifact holds the accuracy floor" |
| what shipped | the uncompressed model.onnx | model_int8.onnx |
Historical column: retained unchanged from the earlier same-day session at
commit 4e478b2, a separate custom "low-power edge camera" prompt run whose own "keep at least 60%
accuracy" wording set the 0.6 absolute floor; that run's model was undertrained (from scratch,
pre-fix), which is why both accuracies are 0.0, and the gate correctly refused to ship a smaller
copy of a broken model. Fresh column: rerun this session, 2026-07-09, commit 34103b3, command
python -m prompt2model.cli run --prompt "Classify red square, blue circle, and green
triangle images, keep at least 90% accuracy" --task classification --dataset-root
output/smoke_verify/classification_data --dataset-format imagefolder --quantize --pretrained
--output-dir output/quant_verify. The prompt's stated 90% floor and the default 98%
relative retention floor are both computed and the stricter one (0.98) applied. That fresh run
picked a larger 4.205875M-parameter backbone (its prompt has no "prioritize speed" constraint)
and its own calibration block reads temperature 0.05, conformal_threshold 0.389276, ece_before
0.3336, ece_after 0.1112, quoted here for completeness.
Deployment step, historical 4e478b2 run: target tensorrt,
built=false, logged reason: "trtexec not found on this host, run build_tensorrt.sh
on the target device". The pipeline emits a ready-to-run build script rather than failing
silently when the target toolchain is absent. Not rerun this session.
The current report format records final metrics only, so no fresh per-epoch curve exists for the healthy 34103b3 models; the curves below are the complete recorded histories from the earlier runs, kept as labeled historical artifacts. Nothing is truncated: the toy classification run has exactly 2 epochs of history and the toy detection run exactly 1.
| task | epoch | train loss | train accuracy | val loss | val accuracy |
|---|---|---|---|---|---|
| classification (historical, pre-fix) | 1 | 0.892219 | 0.653846 | 1.120550 | 0.0 |
| classification (historical, pre-fix) | 2 | 0.347198 | 0.846154 | 1.118115 | 0.0 |
| detection (historical, pre-fix) | 1 | 10.901141 | n/a (not recorded for detection) | 8.816789 | n/a |
Verbatim from the Training History blocks of
output/smoke/classification_run/evaluation_report.md and
output/smoke/detection_run/evaluation_report.md (the earlier runs' committed
reports). The classification curve's 0.0 validation accuracy is the pre-fix bug this project
already disclosed: an unseeded, from-scratch backbone whose BatchNorm statistics collapsed on the
tiny split. The fix (seeded RNGs, stratified splits, pretrained backbone) landed in PR #18, and
the fresh 34103b3 rerun reaches accuracy 1.0 / macro F1 1.0; the broken curve is kept here
because deleting an already published negative result would defeat the point of this page.
| quantity | fresh value (34103b3) |
|---|---|
| committed example images checked | 12 (each with 1 real square and 1 real circle ground-truth box) |
| top-3 predicted box score range | 0.3683 to 0.4504 |
| top boxes with out-of-range label index "2" | 34 of 36 |
| top boxes labeled with a real class | 2 of 36 ("circle", both on image_008) |
| boxes tracking real object locations | no (consistent with mAP@0.5 = 0.0338) |
Fresh, this session: per-image ONNX inference against
output/smoke_verify/detection_run/model.onnx over all 12 committed detection
examples; ground truth from the committed assets/p2m_examples/detection/annotations.json.
Every per-image box drawn in the viewer above comes from this run.
trtexec); the pipeline emitted a build script instead. No on-device (Jetson-class)
latency for the emitted recipe has been measured yet.What data were these numbers measured on?
Entirely synthetic toy sets the factory generated for itself: procedurally drawn shape images (12 red squares, 12 blue circles, 12 green triangles committed as the classification examples; 12 images each containing one square and one circle, with COCO-style annotations, as the detection examples). They are illustrative fixtures whose job is to prove the pipeline runs end to end. They are published in full in the prompt2model-examples dataset so every per-image number here can be checked against the actual pixels.
Is any of this real-world performance?
No, and this page says so everywhere a number appears. There is no real-world benchmark result for Prompt2Model yet. The measured artifacts are a pipeline smoke test: what is demonstrated is that the factory's machinery (typed config, training, calibration, verified export, both refusal gates, deployment targets) actually executes and that its refusals actually fire. Real-task results would be published separately, labeled with their own data and metrics.
Can I run it on my own task and data?
That is the design: you point the CLI at an image-folder (classification) or COCO-style (detection) dataset with a plain-language prompt, and opt into compression, deployment targets, or the LLM planner with flags. Constraints written in the prompt ("keep at least 90% accuracy", "runs at 2 watts", a latency target) become enforced configuration, not comments. The code itself is open source under MIT, public at GitHub.
What does the refusal gate actually do when the model is not good enough? What does an operator see?
Two different things at two different moments. At inference, per input, the
deployed model returns an explicit abstained: true with its score instead of a
guessed label; the operator sees ABSTAIN and can route the frame to a human or to the flywheel
hard-case store rather than acting on a guess. At build time, per artifact, the compression gate
re-evaluates the compressed candidate in the runtime it ships in; if it falls below the floor,
the factory ships the uncompressed model instead, sets compression.passed=false in
the machine-readable result, and writes the refusal reason verbatim into the run report. Nothing
is silently degraded and nothing is silently blocked: you always get an artifact plus the written
record of the decision.
Why two separate refusal gates instead of one?
Because they catch different failure modes at different times. Gate 1 is a per-input, run-time decision: this particular image is unlike what the model was calibrated on, so do not answer. Gate 2 is a per-artifact, build-time decision: this compressed file broke the accuracy contract the prompt stated, so do not ship it. Neither subsumes the other: a perfectly calibrated model can still be ruined by quantization, and a quantized model that held its floor can still meet inputs it should refuse. The historical refusal above is exactly gate 2 catching a model that gate 1's calibration could not save.
How were these numbers produced, and can I check them?
Every table carries a provenance line with the exact command, commit, and file
the numbers came from, and the page separates fresh reruns (2026-07-09, commit
34103b3) from retained historical results instead of mixing them. This Space is
static, so nothing is recomputed at page load; what you read is a fixed record of those runs. The
example images and annotations are public in the dataset linked below, and the
v0.1.0 release post
walks through the release-level numbers and the full list of open issues.