Skip to content

[Core] structured generation mode for DiffusionGemma model (Jev-like) - #57250

Merged
LucasWilkinson merged 26 commits into
vllm-project:mainfrom
mmastrac:structured-reads-main
Sep 22, 2026
Merged

LucasWilkinson merged 26 commits into
vllm-project:mainfrom
mmastrac:structured-reads-main

Conversation

@mmastrac

@mmastrac mmastrac commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

Prerequisite PRs

A few PRs that can go in on their own merits were split out:

Purpose

DiffusionGemma (and likely other models in vLLM) contains all of the necessary components to build a Jev-like model that provides structured answers with bounded choices.

By fixing canvas positions in the given structured output format, you can ask the model to provide a token and logprobs (from which a client derives entropy) that can be used to determine the models most likely answer and its confidence in that answer. If entropy does not pass a given threshold, you can perform additional sampling to determine agreement between runs and therefore the actual confidence of the model's answer.

This PR contains a prototype example server for serving the /v1/systemone endpoint. It does not implement a standard endpoint in vLLM.

The model is sufficiently powerful to work with any structured output that follows one limitation: the token choices must be a single token, so that the canvas itself does not shift. The requesting client can always translate multi-token options into single token options, however (ie: "moderation_spam" -> "A"). The choices are always laid out in a system prompt and the model correctly diffuses single-token-slot answers (or makes a guess and flags it with uncertainty).

Why do we require the token IDs in the request? Currently we provide the token slot positions that are unfixed for the model to choose and this allows the two to be matched more accurately. A followup PR will likely add a template syntax.

The model, using this framework, can answer yes/no ("noul" in Jev parlance), scale, or multiple choice questions, and provides its certainty and error bars with the answer.

The only missing pieces for this to work in vLLM today are in this PR:

  • A way to pre-seed the canvas with the fixed tokens
  • A way to send read-only requests that emit the argmax canvas on the converging step and skip the commit forward (logprobs come from that step at temperature 1)
  • A way to limit the number of steps run (we only want the first step)
  • A bugfix for two parallel DiffusionGemma generations that stomp on each other in lockstep: two concurrent requests with logprobs on where one converges in the same step another commits loses its stash and the request fails with HTTP 500 "list index out of range".

This PR currently contains the machinery to make this model work and a sample interposer server is included to demonstrate end-to-end functionality that is sufficient to serve traffic at reasonable scale.

field type validation effect
diffusion_seed_canvas list[int], exactly the request's canvas width ids in [0, vocab), length checked against diffusion_canvas_length, else the served --diffusion-config replaces the random canvas after prefill
diffusion_max_steps int >= 1 clamped to max_denoising_steps denoise steps before the canvas is emitted
diffusion_pinned list[int] of canvas positions needs a diffusion_seed_canvas; positions must lie inside the request's canvas held at their seed value on every denoise step, and their self-conditioning soft embed is zeroed, so a read past one step denoises the canvas the request seeded instead of one the model rewrote
diffusion_read_only bool (the OpenAI server delivers JSON booleans in vllm_xargs as 0/1, which is accepted) caps max_tokens at the canvas, sets ignore_eos emit on the converging step, skip the commit forward, temperature-1 logprobs
diffusion_canvas_length int, 1 to the served canvas_length validated in verify; the seed must be this long the request runs on a canvas this wide: the scheduler schedules this many draft tokens and the sampler tiles the read at this width, so a short schema on a wide server costs the same as on a server of its own width
logprob_token_ids (a request field, not in vllm_xargs) list[int], up to 128 existing vLLM validation exact logprobs for those ids at every canvas position. The sampler now honors it on the converging step, so a read asks for its label ids instead of hoping they rank in the top-k (with 26 options only 1-8 did)

Example request:

{
  "model": "dgemma",
  "messages": [
    {"role": "system", "content": "Answer a fixed set of questions about the state ... Question urgent: ...\n  yes\n  no\n..."},
    {"role": "user",   "content": "{\"ticket\": \"Since this morning the dashboard shows a blank page after login...\"}"}
  ],
  "chat_template_kwargs": {"enable_thinking": false},
  "max_tokens": 17,
  "logprobs": true,
  "top_logprobs": 20,
  "return_tokens_as_token_ids": true,
  "vllm_xargs": {
    "diffusion_seed_canvas": [100, 45518, 107, 101, 148436, 236787, 71203, 107, 36164, 236787, 9950, 107, 66387, 236787, 236743, 189012, 106, 0, 0, "... 64 ids total"],
    "diffusion_max_steps": 1,
    "diffusion_read_only": true
  }
}

Example Queries

These are raw example queries passed to the model:

Answer a fixed set of questions about the state the user provides. Each question lists its allowed answers; reply with exactly one label per question.

You are reviewing a user post on a community forum.

Question violates: Does the post break the community rules?
  yes
  no

Question category: Which rule does it come closest to breaking?
  A: none
  B: spam (unsolicited promotion)
  C: harassment (targets a person or group)
  D: self_harm

Question severity: How severe is it?
  1: none
  2: mild
  3: serious
  4: critical

Reply with one line per question, in this order, formatted as "id: label".

Answer template (@ standing in for a noised slot):

<|channel>thought\n<channel|>violates: @\ncategory: @\nseverity: @

Seeded canvas:

[100, 45518, 107, 101, 51584, 1090, 236787, 58369, 107, 10618, 236787, 13112, 107, 127243, 236787, 236743, 144194, 106, 0, 0, ... 0]
  pos 7:  violates <- 58369  (' Mell')
  pos 11: category <- 13112  (' laugh')
  pos 16: severity <- 144194 ('اهرة')

Test Plan

Run locally on a DGX spark, with a concurrency sweep to ensure it scales and does not corrupt parallel generations.

pytest tests/test_sampling_params.py tests/v1/sample/test_diffusion_gemma_reads.py

Run with:

vllm serve nvidia/diffusiongemma-26B-A4B-it-NVFP4 --diffusion-config '{"canvas_length": 32}' --max-logprobs 32 --enable-prefix-caching --async-scheduling --attention-backend TRITON_ATTN --max-num-seqs 32
python examples/features/structured_diffusion/structured_server.py --upstream http://127.0.0.1:8000 --tokenizer nvidia/diffusiongemma-26B-A4B-it-NVFP4 --canvas 32

Test Result

Hardware: 1 DGX Spark, sole active GPU process.

Setup: read-only single reads, 32-row canvas, three decisions per request, cache-busted state with the schema prefix cached

Results: 1-way 8.7 req/s at 0.12 s, 32-way 54.0 req/s at 0.58 s, about 162 decisions/s.

Using a sample request/response corpus to determine, from a mock support request, the urgency, tone and routing of an outage report, we get the correct variable number of samples per request (the auto policy re-reads only when the first read is uncertain):

H1: is the first read's entropy per question over the top-20 logprobs (note that reads=4 when H1>0.1)

== samples=auto
outage      527ms reads=4 urgent=0.88±0.04 bucket=outage(1.00) tone=furious(3.00) H1=urgent:0.87 bucket:0.36 tone:0.08
billing     301ms reads=4 urgent=0.00±0.00 bucket=billing(1.00) tone=calm(1.00) H1=urgent:0.65 bucket:0.36 tone:0.28
feature     117ms reads=1 urgent=0.00 bucket=feature(1.00) tone=calm(1.00) H1=urgent:0.06 bucket:0.07 tone:0.02
mixed       296ms reads=4 urgent=0.00±0.00 bucket=billing(1.00) tone=annoyed(2.02) H1=urgent:0.26 bucket:0.13 tone:0.25
image-1789615968197

Additional Results

== programming language
  ok  want=python       got=python       p(want)=1.00±0.00  reads=4  528ms
  ok  want=rust         got=rust         p(want)=0.99±0.00  reads=4  485ms
  ok  want=javascript   got=javascript   p(want)=1.00±0.00  reads=4  493ms
  ok  want=go           got=go           p(want)=0.97±0.01  reads=4  478ms
  ok  want=c            got=c            p(want)=1.00±0.00  reads=4  497ms
  ok  want=sql          got=sql          p(want)=1.00±0.00  reads=4  428ms
  ok  want=bash         got=bash         p(want)=1.00±0.00  reads=4  420ms
  ok  want=ruby         got=ruby         p(want)=0.99±0.00  reads=4  483ms
  ok  want=java         got=java         p(want)=1.00±0.00  reads=4  423ms
  ok  want=haskell      got=haskell      p(want)=0.90±0.02  reads=4  420ms
  accuracy 10/10
== human language
  ok  want=english      got=english      p(want)=1.00±0.00  reads=4  508ms
  ok  want=french       got=french       p(want)=1.00±0.00  reads=4  494ms
  ok  want=german       got=german       p(want)=1.00±0.00  reads=4  474ms
  ok  want=spanish      got=spanish      p(want)=1.00±0.00  reads=4  481ms
  ok  want=italian      got=italian      p(want)=0.95±0.01  reads=4  482ms
  MISS want=portuguese   got=french       p(want)=0.47±0.11  reads=4  496ms
  ok  want=dutch        got=dutch        p(want)=0.98±0.01  reads=4  512ms
  ok  want=japanese     got=japanese     p(want)=0.98±0.01  reads=4  514ms
  ok  want=swedish      got=swedish      p(want)=0.93±0.04  reads=4  508ms
  ok  want=polish       got=polish       p(want)=0.98±0.01  reads=4  504ms
  accuracy 9/10
== unit comparison
  ok  want=first        got=first        p(want)=0.98±0.00  reads=4  503ms
  ok  want=second       got=second       p(want)=0.69±0.03  reads=4  507ms
  MISS want=first        got=second       p(want)=0.20±0.03  reads=4  504ms
  ok  want=equal        got=equal        p(want)=1.00±0.00  reads=4  503ms
  ok  want=equal        got=equal        p(want)=1.00±0.00  reads=4  491ms
  ok  want=first        got=first        p(want)=0.86±0.01  reads=4  498ms
  ok  want=equal        got=equal        p(want)=1.00±0.00  reads=4  418ms
  ok  want=second       got=second       p(want)=0.99±0.00  reads=4  435ms
  MISS want=first        got=second       p(want)=0.19±0.02  reads=4  493ms
  ok  want=second       got=second       p(want)=0.98±0.00  reads=4  493ms
  ok  want=second       got=second       p(want)=0.80±0.01  reads=4  502ms
  ok  want=first        got=first        p(want)=0.77±0.01  reads=4  441ms
  accuracy 10/12

Solve an ASCII maze giving it only N/E/W/S options:

maze

It even works with the vision tower!

== color, by square size (8 colors)
  size 8px        8/8   p(correct) 1.00    426 ms
  size 16px       8/8   p(correct) 1.00    459 ms
  size 32px       8/8   p(correct) 1.00    427 ms
  size 64px       8/8   p(correct) 1.00    452 ms
  size 128px      8/8   p(correct) 1.00    449 ms
== count, red 32px squares on a 4x4 grid
  1 squares       4/4   p(correct) 0.99    495 ms
  2 squares       4/4   p(correct) 0.98    487 ms
  3 squares       4/4   p(correct) 0.89    478 ms
  4 squares       4/4   p(correct) 0.94    492 ms
== quadrant, 32px square
  top_left        4/4   p(correct) 0.97    505 ms
  top_right       4/4   p(correct) 1.00    497 ms
  bottom_left     4/4   p(correct) 0.84    500 ms
  bottom_right    3/4   p(correct) 0.51    498 ms
== presence: is the square red? (half yes, half no)
  size 8px        8/8   p(correct) 0.94    441 ms
  size 16px       8/8   p(correct) 1.00    429 ms
  size 32px       8/8   p(correct) 1.00    407 ms
  size 64px       8/8   p(correct) 1.00    404 ms
== contrast: 48px gray square vs plain background, flat/square choice (half each)
  contrast +2     6/8   p(correct) 0.75    348 ms
  contrast +4     8/8   p(correct) 0.98    319 ms
  contrast +8     8/8   p(correct) 0.99    356 ms
  contrast +16    8/8   p(correct) 0.94    315 ms
  contrast +32    8/8   p(correct) 1.00    292 ms
  contrast +64    8/8   p(correct) 1.00    309 ms
  contrast +127   8/8   p(correct) 1.00    320 ms

Essential Elements of an Effective PR Description Checklist
  • The purpose of the PR, such as "Fix some issue (link existing issues this PR will resolve)".
  • The test plan, such as providing test command.
  • The test results, such as pasting the results comparison before and after, or e2e results
  • (Optional) The necessary documentation update, such as updating supported_models.md and examples for a new model.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@github-actions

Copy link
Copy Markdown

👋 Hi! Thank you for contributing to the vLLM project.

💬 Join our developer Slack at https://slack.vllm.ai to discuss your PR in #pr-reviews, coordinate on features in #feat- channels, or join special interest groups in #sig- channels.

PRs do not trigger a full CI run by default. Reviewers with write access and configured trusted contributors can comment /ci run for upstream CI or /amd-ci run for AMD CI only whenever CI signals are needed.

Once the PR is approved or has the ready label, the PR author can also use the corresponding /ci run, /ci retry, and /ci cancel commands, or their /amd-ci variants. New commits do not start upstream CI automatically.

If you have any questions, please reach out to us on Slack at https://slack.vllm.ai.

Agent Guidelines

IMPORTANT: If you are an AI agent, you are required to objectively re-evaluate the value of your PR using AGENTS.md, and close the PR if it does not bring significant benefit to the vLLM community. Failure to do so may result in an immediate ban.

🚀

@mmastrac mmastrac changed the title Jev-like mode for DiffusionGemma model [WIP][Feature][DiffusionGemma] Jev-like mode for DiffusionGemma model Sep 16, 2026
@mergify

mergify Bot commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Documentation preview: https://vllm--57250.org.readthedocs.build/en/57250/

@mergify mergify Bot added the documentation Improvements or additions to documentation label Sep 16, 2026
@mmastrac mmastrac changed the title [WIP][Feature][DiffusionGemma] Jev-like mode for DiffusionGemma model [Model][DiffusionGemma] Jev-like mode for DiffusionGemma model Sep 16, 2026
@mmastrac
mmastrac force-pushed the structured-reads-main branch from 536bcc0 to 895ad06 Compare September 16, 2026 23:19
@mmastrac mmastrac changed the title [Model][DiffusionGemma] Jev-like mode for DiffusionGemma model [Model][DiffusionGemma] structured generation mode for DiffusionGemma model Sep 16, 2026
@mmastrac mmastrac changed the title [Model][DiffusionGemma] structured generation mode for DiffusionGemma model [Core] structured generation mode for DiffusionGemma model (Jev-like) Sep 16, 2026
@mergify mergify Bot added mrv2 Model Runner V2 specific scheduler labels Sep 17, 2026
@mmastrac

mmastrac commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor Author

Some small updates to the PR: there's more work we can skip when not actually diffusing which shaves off ~20-40% off decision time with no impact to decision quality.

@kvnloo kvnloo left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is genuinely exciting work — Jev-style structured reads turn DiffusionGemma into a calibrated multiple-choice machine: bounded answers with real uncertainty bars, which is exactly what evals, moderation, and agentic tool-calling need instead of prose you have to parse and pray over. The machinery here is close. A few things I'd want resolved before it lands.

Merge order needs a decision first. The three prerequisite PRs (#57414, #57416, #57417) are stacked as commits inside this branch and open as standalone PRs. Both can't survive: if the standalone PRs land first, this branch conflicts; if this lands first, they become empty. Either rebase this after they land (dropping the three commits) or absorb them here and close the standalone PRs. Right now it's both, which guarantees a mess.

Should-fix:

  1. No regression test for the claimed race. All 7 new sampler tests are single-slot state checks; nothing runs two lockstep requests where A converges (stash freshly written) while B commits (stash popped) — the exact case that 500'd with list index out of range. Every new test stays green while this regresses. A two-request mixed-phase test would pin it.

  2. Example server has unbounded fan-out. auto_max is int-cast but never upper-bounded (fixed samples is capped at 32); read_many spawns one thread per sample and decide uses ThreadPoolExecutor(max_workers=len(groups)) with unbounded question count. {"samples": "auto", "auto_max": 100000} → ~10⁵ threads → can't start new thread or OOM. The server binds 0.0.0.0 with no auth. Mirror the min(..., 32) cap and bound question count.

  3. _diffusion_read_in_flight is void without diffusion_max_steps, which validation permits. Under async scheduling a read-only request without a cap gets one extra speculative denoise step after its converging output is already in flight; the output is dropped and its +width placeholders leak on the dead request — a wasted forward pass per read. Either require the cap alongside diffusion_read_only or make the guard cap-independent.

  4. diffusion_max_steps is silently clamped. Validation rejects < 1 but allows huge values, which the sampler clamps to max_denoising_steps with no signal (request 200 steps, get 48, read-only emits at 48). Validate the upper bound or log the clamp.

  5. Read-only with max_tokens < canvas width truncates the canvas. The cap only lowers (min(self.max_tokens or expected_len, expected_len)), contradicting the comment "one canvas is the whole output": max_tokens=5, width=16 → the structured reader gets 5 of 16 slots and downstream slot alignment breaks. Reject or canonicalize.

  6. verify() mutates the params it's validating. The read-only branch sets self.max_tokens and unconditionally stomps self.ignore_eos = True, overriding an explicit user ignore_eos=False. Idempotent on re-verify, but validation with side effects is a footgun — document the override instead of silently applying it.

Nits: README field table omits diffusion_canvas_length and says read-only emits "as soon as the cap is reached" — it emits at convergence, which can precede the cap. slot_distribution's entropy is computed over unnormalized top-set probabilities, systematically understating true entropy and biasing the auto policy toward single reads; the (properly normalized) label distribution's entropy would be the honest signal. set_seed_canvas only overwrites [:len(ids)], so a shorter seed on a reused slot leaves the previous request's tail in device memory past the new width — likely benign since those positions are never scheduled, but worth a zeroing for hygiene. When diffusion_config is None the seed-length check degrades to unenforced, re-opening the device-side assert the validation was built to prevent.

What's strong: the stash fix matches the race mechanism exactly — I traced placeholder add/drain across _update_after_schedule, the rejection path, and the async commit override, and the books balance for narrowed and full widths. Slot-reuse hygiene is careful (add_request resets seed/read-only/cap/width and evicts stale stashes — the exact leak vector for a preempt between converge and commit). And validating seed ids against vocab size before GPU indexing turns an unrecoverable device-side assert into a clean 400, including the bool-is-int exclusion most validators miss.

Design question: extra_args as the API surface. It works, but it's an untyped escape hatch — invisible to the OpenAPI schema, no typed client support, validation bolted on the side. You already anticipate a template syntax in a followup; I'd argue the three knobs (seed canvas, step cap, read-only) deserve first-class SamplingParams fields when that lands, with extra_args as the incubator, not the permanent home. Agree with keeping the JSON format itself outside vLLM — the interposer server is the right layer for it.

@LucasWilkinson
LucasWilkinson enabled auto-merge (squash) September 22, 2026 20:26
@LucasWilkinson
LucasWilkinson merged commit 1b3b88e into vllm-project:main Sep 22, 2026
194 checks passed
mmastrac added a commit to mmastrac/djev-spark that referenced this pull request Sep 24, 2026
Structured reads went upstream (vllm-project/vllm#57250), so the image now
starts from nightly-e9757321 and overlays only the perf branches, tracked in
vllm-stack.tsv: constrained-vocab (#58216), sc-last-step-skip (#58221),
fused-sampler (#58226) with a fix for masked leading blocks, diffusion-
entropy-masked (#58440), diffusion-samples (#58438), flashinfer-per-request-
causal and adaptive-canvas. The fork branch djev-spark-stack holds each
branch's own diff on the nightly's commit, so no other upstream change rides
along. The fork stage checks that every manifest head is in the stack, and
patches/verify_image.py that every branch's change is installed and imports.
scripts/vllm-stack.sh status says per branch when the fork moved or
upstream merged it into the base; build makes a new stack on a newer nightly.

The server is mmastrac/djev at a pinned commit, with --constrained and
--engine-samples on and the playground pages served from --pages. This
repo's server copy, its fake-upstream test and the recompile-limit patch
(upstream now, and a syntax error on this nightly) are gone.

Defaults follow the measurements: canvas 256 with the adaptive schedule,
the gemma4 reasoning and tool parsers, --language-model-only, max-logprobs
128, and a start-up budget of two fp32 copies (the container peaked at 33 GB).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mrmps added a commit to mrmps/classifier-dev that referenced this pull request Sep 24, 2026
* Make admin analytics dense and searchable on one page

* Fix payment reconciliation during overdue flags, refunds, and provider failures (#100)

* Add TypeSafe SDK-compatible API (#102)

* Reduce classification latency with west-region Laya and fewer account round trips (#103)

* Cut Laya fast latency below 150ms

* Retain Oregon placement and remove redundant account plan lookup

* Apply workspace keys to TypeSafe SDK requests (#104)

* Serve Laya on Beam and add Kev, retiring the Modal deployment (#105)

Beam serves the same System One protocol as TypeSafe, so Laya stops being a
bespoke HTTP client and becomes a third transport in src/jev.ts: one packer,
one retry policy, one validator, one meter. Kev joins it as `model: "kev"`.

Two Beam limits have no analogue at TypeSafe. Every model refuses more than
32 named questions per request, and the contexts are small — Laya answers
within 512 tokens of state plus one question, Kev packs 8,192. Both reject an
overflow rather than truncating, so a context refusal is translated to
max_tokens_exceeded and runJevBatches halves the batch and recovers. Laya's
16-item ceiling is empirical: Beam accepted 20 items of ordinary support text
and refused 25.

Behaviour preserved deliberately:
  - Lane quotas (fast 60/min, bulk 1,000/min) are a product decision about
    shared capacity, not a property of Modal, so callers keep their limits.
  - Beam requests are not retried. The old Modal client did not retry either,
    and a lane quota counts attempts, so retrying would spend a caller's
    budget on a model that will not clear inside a backoff. Retries stay a
    Jev-only behaviour, now expressed as Backend.attempts.
  - The quota-refusal abort from #103 still cancels in-flight inference.

Behaviour that changed, visibly:
  - A result is labelled jev/laya or jev/kev. Beam does not report which
    checkpoint answered, so laya-0.3.4-<checkpoint>-<lane> is gone, and with
    it the timing fields only Modal could populate (backendMs, headersMs).
  - Account analytics records a real provider cost for these models instead
    of marking spend unknown, because Beam reports token counts.
  - Cold-start 503s cannot happen: there is no pool of ours to start.

Cost: usage-priced at $0.021 per 1M input tokens with no idle charge, against
$1,168 a month for the warm Modal lane alone after the us-west multiplier.

BEAM_API_KEY is the only credential either model needs; LAYA_FAST_URL,
LAYA_BULK_URL, LAYA_MODAL_KEY and LAYA_MODAL_SECRET are removed.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Describe the Beam transport in the repository instructions (#106)

AGENTS.md told contributors that Jev's transports live in src/jev.ts and said
nothing about Laya or Kev. Both now go through the same module, and the two
rules that are easy to get wrong — Beam's 32-question ceiling and the fact
that a Beam request is never retried because a lane quota counts attempts —
were only discoverable by reading the code.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Bound free inference spending and isolate internal endpoints (#107)

* Bound public inference spending and protect internal endpoints

* Verify spending boundaries and finish paid admission and recovery alerts

* Demonstrate live provider billing through funded admission

* Keep anonymous live smart checks within the request allowance (#108)

* Simplify billing to input tokens plus Smart escalations (#109)

* Price base input at Jev's rate and Smart reviews per escalation

* Expose customer usage headers to browser API clients

* Fix agent-reported batch recovery and spending error reporting (#110)

* Let paid requests settle into a bounded negative balance (#111)

* Allow bounded paid overdrafts and preserve completed Smart results

* Describe the hold without implying prepaid coverage is required

* Add agent testimonials to feedback (#112)

* Route documents over 32,000 characters to chunklaya

An input over MAX_CHARS used to be refused with input_too_long. When
CHUNKLAYA_URL and CHUNKLAYA_TOKEN are set and CHUNKLAYA_ENABLED is "true",
it is now answered by chunklaya, our own long-document service (Laya behind
a chunk-and-index harness, github.com/myxamediyar/chunklaya, serve/), and
the result is labelled chunklaya/multilingual. Nothing at or under 32,000
characters changes; an explicit model "jev" keeps its ceiling; model
"chunklaya" selects it for shorter text. Unconfigured, the old 400 stands.

The service speaks System One, so it is a fourth Backend in src/jev.ts
through the bearer transport Beam already uses, with the URL and token read
from the environment, one document per request, a 60 s deadline, and no
per-token cost (the pod is billed by the hour). Its refusals are reported
as chunklaya_input, chunklaya_busy and chunklaya_unavailable in our own
words, and never fall back to Jev or the LLM chain. Ceilings: 4,000,000
characters per input, 20 inputs per request, no smart tier.

Dimensions pack against the backend's limits so every question about a
document travels in one request. Billing prices the model at zero in the
rate card, the spending table and the reservation bound. Docs, OpenAPI, MCP
and the CLI render the new model and codes from the same constants.

The deploy workflow passes CHUNKLAYA_URL and CHUNKLAYA_TOKEN to the Worker
when both exist as repository secrets, and leaves them out otherwise.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Price the chunklaya route as chunklaya under a spending permit

postBeam sent every request through providerFetch under Beam's name, so a
request routed to chunklaya was priced as beam:chunklaya/multilingual,
which the price table does not have, and was refused as unpriced_model.
The provider the function already receives is now the one it names.

test/chunklaya.test.ts runs the route under a Permit, as production does,
and asserts it is priced as chunklaya at zero.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Let the deploy's anonymous probe pass through the reputation gate

The post-deploy check curls one anonymous classification from the hosted
runner. The free-traffic reputation gate can refuse a runner's IP with 403
proxy_requires_payment, which is the Worker answering with its own gate,
not a broken deploy; the last two deploys were marked failed by it, and
`curl --fail` discarded the body that would have said so.

The probe now prints the status and body, passes with a notice on the
gate's own 403, still requires "spam" on a 200, and fails on anything
else. The funded end-to-end step that followed it runs again.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Give operator inference a separate bounded daily allowance

* Limit anonymous traffic by label set (#116)

* Limit anonymous traffic by label set

* Preserve public quota documentation contract

* Close cross-endpoint label allowance bypasses

* Add paginated admin view of all retained label sets (#117)

* Exclude obsolete raw keys from the label registry (#118)

* Index collected label names for the admin catalog (#119)

* Expose classification token usage and customer pricing (#121)

* Classify long documents with Jev screening and fixed context pricing (#122)

* Implement Jev long-context screening with fixed context pricing

* Budget selected evidence against serialized Jev requests

* Give long-context Jev calls bounded longer deadlines (#123)

* Open POST /v1/systemone to images through the dgemma service

A System One body that names model "dgemma" or carries an images array is
forwarded to the image-capable DiffusionGemma service (vLLM's structured-read
mode, vllm-project/vllm#57250), which speaks the same contract. Every other
body still goes to TypeSafe. Neither answers for the other: a refused body is
400 dgemma_input with the service's reason, a saturated service 429
dgemma_busy, a down or unconfigured one 503 dgemma_unavailable, and images
sent under another model 400 images_unsupported.

Images are data URLs (PNG, JPEG, WebP, GIF), at most 4 and 900,000 base64
characters together. The service is reached through the DGEMMA_URL and
DGEMMA_TOKEN Worker secrets when DGEMMA_ENABLED is "true"; the deploy
workflow passes the pair through when both repository secrets are set. The
route is priced as dgemma at zero for workspace permits, since the service is
billed by the hour. OpenAPI, the docs and AGENTS.md describe the field, the
model and the codes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Enable paid 10M-token long-context classification jobs (#125)

* Add paid ten-million-token long-context jobs

* Allow browser preflight for long-context uploads

* Tighten the dgemma door after review

Route on the presence of the images field, so an empty array is refused
rather than forwarded as an unknown key; require an https service address
and reject redirects; keep the 60-second deadline through the body read;
accept a 200 only when it reports this model, an entry for every question
asked and a usage count; and scope the zero-rate trial to System One, where
an images field selects the service as surely as its name.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Let the System One response schema carry a skipped dgemma answer

A question the service skips under ask_if is answered null; the published
response schema now allows that beside the answer objects.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Keep the dgemma request's redirects manual

The Workers runtime has no redirect "error" mode and throws on the option,
so every request to the image-capable service failed before it was sent and
the route answered dgemma_unavailable. Redirects are now "manual": a 3xx
comes back as a response and is answered as an outage, which is what the
runtime's own message recommends. A test pins the option and the mapping.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Support time-limited complimentary Pro grants

* Start complimentary Pro year on verified signup

* Accept whole 10M-token documents in one upload (#127)

* Add URL classification with metered Context.dev scraping (#128)

* Restore public chat with classification tools (#130)

* Restore public chat with classification tools

* Keep non-chat private routes protected

* Keep chat subtitle readable with conversation controls

* Measure chat usage in the unified analytics dashboard

* Align chart units and verify dashboard navigation and export

* Count every chat classification tool variant

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: myxamediyar <mukhamediyar@berkeley.edu>
Co-authored-by: Mukhamediyar <86684638+myxamediyar@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation mrv2 Model Runner V2 specific ready ONLY add when PR is ready to merge/full CI is needed scheduler speculative-decoding

Projects

None yet

Development

Successfully merging this pull request may close these issues.