Skip to content

feat(api): add the TypeSafe System One API (/v1/systemone) on DiffusionGemma - #3897

Open
MasakiMu319 wants to merge 1 commit into
jundot:mainfrom
MasakiMu319:feat/systemone-diffusiongemma
Open

MasakiMu319 wants to merge 1 commit into
jundot:mainfrom
MasakiMu319:feat/systemone-diffusiongemma

Conversation

@MasakiMu319

@MasakiMu319 MasakiMu319 commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds POST /v1/systemone: TypeSafe's System One API (the Jev API, OpenAPI 0.2.0), served by DiffusionGemma. Instead of generating text, oMLX reads each typed noul / choice / score answer directly from the diffusion decoder. It prefills the prompt once and runs a read-only decoder pass over a canvas seeded with the answer template, noise tokens in the answer slots only. Each answer is a calibrated probability distribution over the question's labels, so it cannot go off-schema. TypeSafe's SDKs work unchanged when TYPESAFE_BASE_URL points at oMLX.

Related: #1805 (DiffusionGemma support).

Design decisions to review

  • Contract: every request and response field follows TypeSafe's OpenAPI 0.2.0 (tests compare the models against it field for field). Where the contract is silent, the read follows djev, the structured-read server of [Core] structured generation mode for DiffusionGemma model (Jev-like) vllm-project/vllm#57250: its prompts, answer templates, 64-token canvas, seeded noise, single-token labels, "auto" re-read policy and usage counts.
  • Where the two differ, the contract wins:
    • Question names never reach the model.
    • A choice may have up to 255 options.
    • A question with only one possible answer is answered without a read.
    • confidence is (K·max(p) − 1)/(K − 1), which reproduces every worked example in TypeSafe's docs; djev reports max(p).
  • Supported models: only diffusion_gemma, because the read relies on DiffusionGemma's chat format and special tokens. Any other model gets a 400.
  • Out of scope: the openjev/djev extension fields (images, steps, samples, think, sequential, span).
  • GET /v1/models keeps its OpenAI shape, so the SDKs' model listing isn't supported. Aliasing the model as jev-latest makes the SDKs' default model work.
  • New per-model setting system_one_rereads_enabled (default on, which is djev's policy). When a read is uncertain it takes three more noise draws and averages them; turning it off reads once, for about half the latency. It is a universal (request-time) profile field, so a profile or exposed profile can switch it without reloading the engine. The web admin and the macOS app show it for diffusion models only.

Questions for you: is the endpoint name/path OK, and should the re-read default stay on?

Implementation

  • omlx/api/systemone_models.py: the request and response models.
  • omlx/api/systemone.py: request planning and the reads.
    • Planning covers validation, label discovery, answer templates, slot resolution and chunking.
    • Planning finishes before the request waits for the model, so malformed requests get a real 400.
  • VLMBatchedEngine.diffusion_read_session(): prefills once on the MLX executor while holding the diffusion lane's lock.
    • session.read() runs read-only decoder passes against that prefill.
    • Each pass returns top-k plus label log-probabilities, the same data djev gets from vLLM, so extra noise draws cost decoder passes, not prompt tokens.
  • omlx/server.py: the /v1/systemone route (engine lease, JSON keepalive, metrics).
  • Setting plumbing: model settings, profiles, admin API, dashboard modal, macOS app, i18n (10 web locales plus the app's string catalog), READMEs (5 languages), docs/TESTING.md.

Benchmark

Setup: M5, 32 GB; mlx-community/diffusiongemma-26B-A4B-it-4bit; mlx-vlm ea79808.

Accuracy: JevBench public items. 231 items (easy 48, standard 72, hard 111), typesafe adapter, one request at a time. The other rows are JevBench's published per-task outcomes for the same items.

System Total Easy Standard Hard
This PR (MLX 4-bit) 197 (85.3%) 48 71 78
Jev 1.13.0 200 (86.6%) 48 71 81
djev (Maisa, api.djev.dev) 194 (84.0%) 48 71 75
OpenJev (DiffusionGemma NVFP4) 189 (81.8%) 48 70 71
  • 0 failed requests; schema validity 100%.
  • ECE 0.079, Brier 0.226, ordinal MAE 0.137.
  • Per-item agreement with djev: 218/231.

Re-read ablation. Same setup with system_one_rereads_enabled off:

  • Accuracy is identical (197/231; 226 of 231 labels are the same).
  • ECE is 0.094 against 0.079 with re-reads. The paired bootstrap difference is −0.016, 95% CI [−0.030, +0.010], so not significant.
  • Brier is 0.226 either way.
  • djev's auto policy re-read 95% of the reads.

Latency. HTTP p50 with the model warm:

Case Re-reads on Re-reads off
1 question 703 ms 343 ms
3 questions 1018 ms 473 ms
12 questions (one read) 1574 ms 732 ms
40 questions (three reads) 3875 ms 2047 ms
3 questions, 3.5k-token state 4165 ms 3757 ms
  • Throughput: 1.0 req/s with re-reads on and 2.2 req/s with them off. The diffusion lane is serial, so concurrent requests queue.
  • Measured in process:
    • Prefill takes 250 ms for a 182-token prompt and 3170 ms for a 3535-token prompt.
    • One decoder pass takes 185–193 ms.
  • The model uses 16.2 GB of memory.

Reproduction: start omlx serve, then from a JevBench checkout run
TYPESAFE_API_KEY=local python -m jevbench.cli run --tasks datasets/public/easy.jsonl,datasets/public/original.jsonl,datasets/public/hard.jsonl --adapter typesafe --endpoint http://127.0.0.1:8000 --model <model id> --price-in-per-m 0 --price-out-per-m 0 --results results.jsonl
followed by jevbench.cli summarize. Compare the results with results/v1.2/jevbench-v1.2-per-task.json.

Validation

  • python -m pytest on this branch: 14774 passed, 1 failed. The failure, test_cluster_rdma_mailbox.py::test_a_daemon_ending_the_registration_is_noticed, is timing-dependent: it passes 3 out of 3 times on its own and is unrelated to this change.
  • New tests:
    • The models' fields and required fields against the OpenAPI 0.2.0 contract, plus the documented confidence examples.
    • Label and slot resolution, including the error when labels share a token and the error when labels don't share a slot.
    • Chunking and prompt contents, the auto re-read policy and its setting, usage, and error mapping.
    • The engine's one prefill per read and its log-probabilities, against a fake diffusion model.
    • The admin API, profile application, and the per-request settings of an exposed profile.
  • node tests/system_one_rereads_ui.test.cjs: checks the toggle's state, visibility, PUT payload and profile values.
  • With a real checkpoint, OMLX_DIFFUSIONGEMMA_MODEL=… pytest tests/test_systemone.py -m slow passes: all 255 labels are single tokens, long mixed question lists resolve, and one real read runs.
  • TypeSafe SDK 0.7.1 end to end: responses parse, and a 400 maps to TypeSafeBadRequestError.
  • Live server with the setting persisted off: every read takes one draw. The web admin toggle saves, persists and reopens correctly (screenshots below).
  • macOS app: the Debug build succeeds. oMLXTests runs 326 tests with 1 failure, ThemeTests.testLightWindowBackgroundUsesStandardWindowColor, which also fails on a clean main here (macOS 27 beta system colors).
  • The existing node tests moe_expert_offload_ui, network_auth_ui and qwen_oq_a8_ui also fail on a clean main here (Node 26); this PR doesn't change them.

Not tested

  • No macOS app screenshot, because running the app needs the staged bundle with the embedded server.
  • States longer than 4k tokens; 8bit, bf16 and mxfp4 checkpoints; the jev-latest alias path (the SDK was tested with TYPESAFE_DEFAULT_MODEL).

Screenshots

Web admin, DiffusionGemma model settings:

POST /v1/systemone answers typed noul/choice/score questions about a state
with calibrated probabilities read from DiffusionGemma's diffusion decoder,
following TypeSafe's OpenAPI 0.2.0. Where the contract is silent the read
follows djev (the structured-read server of vllm-project/vllm#57250).

A per-model setting, system_one_rereads_enabled (default on), keeps djev's
auto noise re-reads or reads once for lower latency.
@MasakiMu319
MasakiMu319 marked this pull request as ready for review September 24, 2026 16:02
@maxpfingsthorn

Copy link
Copy Markdown
Contributor

Thanks for this. I tried the branch locally and ran the same JevBench public set (231 items, typesafe adapter, one request at a time) on an M5 Pro with 64 GB, with mlx-community/diffusiongemma-26B-A4B-it-4bit, re-reads on (the default), and the Python packages of a Homebrew oMLX build (MLX 0.32.2, mlx-lm 0.32.0, mlx-vlm 0.7.1). It got 195/231 (84.4%; easy 48, standard 71, hard 76), 0 failed requests, ECE 0.077, Brier 0.237, and p50/p95 latency of 398 ms / 1496 ms. That is close to your 197/231 (you used mlx-vlm ea79808 on an M5 with 32 GB).

I have been trying a different read path for the same endpoint: scoring typed questions with the chat models oMLX already serves, from the next-token logits of the answer labels, as featherless-ai/simple-jev does. The prototype is standalone, not wired into oMLX's engine: it runs as its own process outside oMLX's scheduler, one request at a time, and caches the system prompt and question list across requests. It uses Simple Jev's baseline prompt and scoring (thinking off; noul read as a 1-9 rating rather than a yes/no label) with an mlx-lm forward pass that computes the shared prefix once and batches the question suffixes. On the same 231 items and machine:

system total easy standard hard p50 p95 ECE Brier
this PR, DiffusionGemma 26B-A4B 4bit 195 (84.4%) 48 71 76 398 ms 1496 ms 0.077 0.237
logits prototype, Qwen3.6-35B-A3B-OptiQ-4bit 193 (83.5%) 48 70 75 357 ms 1282 ms 0.073 0.250
logits prototype, gemma-4-e4b-it-6bit 179 (77.5%) 48 71 60 148 ms 776 ms 0.144 0.355

The latencies are not a like-for-like comparison: the PR row has re-reads on (your own ablation shows roughly half the latency with them off, at the same accuracy), and the prototype's single-process, single-request setup and prompt caching described above also favor it. The models differ in size too (weight files 16.5 GB, 22.1 GB and 7.0 GB). So on this set the logits path with Qwen3.6 lands close to this PR, while the much smaller e4b trades hard-tier accuracy and calibration for speed. The logits path works with ordinary chat models, no dedicated checkpoint needed. The PR and the Qwen3.6 prototype give the same correct/incorrect outcome on 211 of 231 items. Happy to share the prototype code if that's useful.

Would a follow-up be welcome that answers /v1/systemone for non-diffusion models instead of returning 400? It would keep this PR's request/response models, validation, confidence formula and route, and only add a second read path: where the route checks for a DiffusionGemma engine, other LLM/VLM engines would go to a label-logits read (shared prefix, batched question suffixes). GET /v1/models and the jev-latest alias would work as they do now, and system_one_rereads_enabled would stay diffusion-only. I would wait until this PR is merged and build on it. @jundot, @MasakiMu319, does that fit how you see the endpoint?

@rndmcnlly

Copy link
Copy Markdown
Contributor

I would really like to see this land, with a path for Cloudflare’s two Apache-2.0 open-weight decision models, Clef (27B) and Clef-Flash (9B):

These look like compelling candidates for everyday local SystemOne inference. They implement the same state + typed questions interface, but use a dedicated joint decision head to score the options across questions rather than diffusion reads or ordinary next-token label scoring. MLX conversions with a custom runner are already available, for example https://huggingface.co/mlx-community/clef-flash-4bit .

An agent-assisted local smoke test on my M3 Max (64 GB), using current llama.cpp with Metal and Q4_K_M weights, passed all 30 checks for both models across choice, noul, and score. Median latency for three questions over short inputs was about 549 ms for Flash and 1.82 s for Clef. That is only a functional check, not a quality comparison with Jev or the DiffusionGemma results here.

What I especially want is oMLX’s existing model management around this capability: download and configure a decision model in the UI, call /v1/systemone, and have it auto-load and unload under the same memory budget as my chat models. A separate server gets me the API, but misses that integration.

Would it make sense to keep the SystemOne request/response contract backend-independent, with DiffusionGemma reads as the first implementation and Clef’s joint head as a follow-up? That seems compatible with the non-diffusion direction discussed above, while giving dedicated decision models their own execution path. Happy to help test the Clef side.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants