Skip to content

serving: flip the blessed release to Qwen3.8-27B (multi-release plumbing, model-directory pin, thinking flag) - #1758

Merged
anderdc merged 7 commits into
testfrom
serving/multi-release
Sep 8, 2026
Merged

serving: flip the blessed release to Qwen3.8-27B (multi-release plumbing, model-directory pin, thinking flag)#1758
anderdc merged 7 commits into
testfrom
serving/multi-release

Conversation

@anderdc

@anderdc anderdc commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

What

Flips the serving release from Qwen3.6-35B-A3B to Qwen3.8-27B (NVFP4, sparkinfer 19ef39ec2) as the single loadout entry, with everything the validator, miner image and compose files need for it, and re-prices the fallbacks so a card still earns $0.70 per hour. Decision 2026-09-07: flip outright; two models later.

Commits, in order:

  1. Multi-release plumbing — validator learns which release each miner serves from the miner's own refusal / served completions; audits, pay, probation, reports and routing per release; SERVING_REFERENCE_URL__<RELEASE_ID>; gateway timeout per release. Not needed for the flip, but it makes a second release later a loadout entry plus a reference.
  2. Model-directory artifactdocker/sparkinfer-entrypoint.sh serves either one GGUF (unchanged) or an HF model directory pinned by commit + per-shard sha256 (MODEL_DIR_*), refusing to start on any mismatch.
  3. Release schemaenable_thinking (sent by the miner backend, the live reference on chat and /v1/score, and the checker), model_dir, runtime_env; both compose files pass either artifact's pins through; env examples and README updated.
  4. Checker fixes found while qualifying — R8 failures print status+body, thinking rides on every R8 request, verify_served forces the runtime's own token ids like production.
  5. The loadout entryqwen3.8-27b-nvfp4-sparkinfer-19ef39ec2: gittensor-model-hub/Qwen3.8-27B-NVFP4-RTX5090@8e2c0cd2 (both shard digests), entrius/sparkinfer:19ef39ec2@sha256:d35719b0…, thinking off, context_tokens 65536 on a 131k KV pool, end-of-turn 248046.
  6. Pay re-priced — pay is derived from the release's measured speeds, so the flip re-prices itself: $0.70 per card-hour is now ~$0.58 per million output tokens and ~$0.026 per million prompt tokens (one card flat out ≈ 1.2M output tokens or ≈ 27M prompt tokens an hour; was ~1M / ~86M). The constants only back a release without measurements; they and their comments now describe the 27B (aggregate 338, prefill 7,500, curve 99.4/49.7/23.4 at 1/6/16, 26.4 GB resident, 64k context). The prompt-size DoS backstop doubles to 0.5 MB so a full 64k prompt fits. serving_rounds gains model_dir_sha256 + runtime_env for the release card.

Measured (rented RTX 5090s, 2026-09-08, scripts/check_serving_runtime.py, deterministic mode, logprobs on every request)

  • Every MUST passes: D1 prefix agreement 1.000 / drift 0.0000 (30×3), /v1/score exact (max |Δ| 0), 429-not-queue at 16, attest 832 ms.
  • Decode: single-stream 99.4 tok/s; 16-concurrent aggregate ~338; per-request 99.4 / 92.1 / 74.0 / 56.8 / 56.8 / 49.7 / 39.6 / 24.8 / 23.4 at 1/2/3/4/5/6/8/12/16 (mean of two runs, monotone).
  • Prefill, cold unique prompts, engine ttft_ms: 7.6k tok/s at 10k tokens, 9.7k at 41k, 7.8k at 102k (carried as 7,500).
  • Resident VRAM 25.1 GiB idle at CTX 131072 → vram_model_reserved_bytes 26.4e9.
  • Caveat: a dense 27B barely slows at 2 streams (92 vs 99), so two hotkeys on one card are no longer exposed by the decode curve the way the MoE's 144-vs-440 was; the same-instant attest fill carries that check.

Full readout in the team run-log (2026-09-08 entry).

Companion PRs (merge order)

  1. entrius/gittensor-db#61 — the two columns
  2. this PR — validator writes them, new release
  3. entrius/das-gittensor#103 — release object passes them through
  4. compute: Current release card for a model-directory release (Qwen3.8-27B) gittensor-ui#1377 — Current release card for the directory shape
  5. entrius/gittensor-docs-ui (compute pages) and e35VenturaLabs/gittensor-app#10 (default model, 64k context) — independent

Tests

805 passed; pre-commit + pre-push (ruff, format, pyright, vulture) clean.

Before mainnet

Testnet soak with real cards on the new image; miner announcement (flag day: new image, new artifact); validator reference switched to the 27B (SPARKINFER_MODEL_DIR_*, SPARKINFER_CTX in .env).

https://claude.ai/code/session_01VjhStJbNW6WzxucTcwuw4y

Two releases in the loadout used to deadlock the second: baseline prompts
only ever named the primary, a miner on the other release refused them,
the refusals were misses in the primary's window, and after three rounds
the miner was dormant, never READY, never routed.

The validator now learns which release each miner serves from the miner
itself. A refusal of a baseline prompt names the release the miner does
run ("serving release X, not Y"); if X is in the loadout the round records
it and prompts X from then on, never a miss. A completion served for a
release declares it the same way. The mapping is persisted (miner_release
table) so a restart seeds probation on the right release.

audit_round settles each release over its own miners only: windows,
probation and the round report carry the miner's release instead of
whichever release ran last, and the round log prices tokens per release.
The gateway dispatches with the routed release's request_timeout.

Env: SERVING_REFERENCE_URL (and api key / attest sidecar) keep naming the
primary's reference; every other release's is SERVING_REFERENCE_URL__<ID>
with the release id upper-cased, so a validator hosts one reference per
release without committing endpoints. Miner-side SERVING_BASE_URL applies
to the release SERVING_RELEASE names.

Claude-Session: https://claude.ai/code/session_01VjhStJbNW6WzxucTcwuw4y
…ry too

Qwen3.8-27B's blessed artifact is a compressed-tensors NVFP4 directory,
not one GGUF. The image's entrypoint now takes MODEL_DIR_REPO +
MODEL_DIR_REVISION (the HF commit: the repo is mutable, the revision is
the pin) + MODEL_DIR_SHA256 (file=sha256 per weight shard), fetches the
directory at that revision, refuses to start on any digest mismatch, and
serves it (sparkinfer_server dispatches on -m <dir>). Without MODEL_DIR_*
it is run.sh --download as before, so the 35B release is unchanged.

scripts/serving_conformance_on_lium.sh measures a non-primary artifact
with CONFORMANCE_MODEL_ID + CONFORMANCE_MODEL_ENV, for qualifying a
candidate second release before it has a loadout entry.

Claude-Session: https://claude.ai/code/session_01VjhStJbNW6WzxucTcwuw4y
…es both

Qwen3.8-27B thinks by default and ships as a Hugging Face directory, so
a release now carries `enable_thinking` (sent as-is by the miner's
backend, the validator's live reference on both /v1/chat/completions and
/v1/score, and the checker's --thinking flag, so served text and its
teacher-forced score template identically) and a `model_dir` block
(repo, pinned revision, per-shard sha256s) beside the GGUF fields.
/v1/models surfaces model_dir for the release card.

docker-compose.miner.yml and the validator's reference profile pass
either artifact's pins plus TOK_REPO/MODEL_NAME/CTX through to
docker/sparkinfer-entrypoint.sh; the env examples and README say which
to copy. The Lium conformance script takes CONFORMANCE_CHECKER_ARGS and
CONFORMANCE_SKIP_NODES.

Claude-Session: https://claude.ai/code/session_01VjhStJbNW6WzxucTcwuw4y
@anderdc anderdc changed the title serving: a second blessed release goes READY for itself, not the primary serving: flip the blessed release to Qwen3.8-27B (multi-release plumbing, model-directory pin, thinking flag) Sep 7, 2026
…ing on every R8 request

requests' Response is falsy for any 4xx/5xx, so the R8 failure line printed
'HTTP ?' instead of the status. Show status + body, and send the same
enable_thinking on the R8 greedy generations and the verify_served
reference as on the score call.

Claude-Session: https://claude.ai/code/session_01VjhStJbNW6WzxucTcwuw4y
…e release

Replaces Qwen3.6-35B-A3B in serving_loadout.json with
qwen3.8-27b-nvfp4-sparkinfer-19ef39ec2: the gittensor-model-hub NVFP4
directory pinned at HF commit 8e2c0cd2 with both shard digests, image
entrius/sparkinfer:19ef39ec2 by digest, thinking off, 64k prompts on a
131k KV pool, end-of-turn 248046.

Measured on rented RTX 5090s (2026-09-08, scripts/check_serving_runtime.py,
deterministic mode, logprobs on every request): every MUST passes, D1
1.000 / 0.0000, /v1/score exact; single-stream 99.4 tok/s, 16-concurrent
aggregate ~338, per-request curve 1-16 in the entry; prefill 7.5-9.7k
tok/s cold (carried as 7,500); 25 GiB resident -> attest reserves 26.4 GB.
Full readout in the team run-log.

Checker: verify_served now forces the runtime's own token ids (as the
validator does) instead of re-tokenizing text; greedy() returns
reference_token_ids. A runtime_env block on the release carries the
container env (CTX, MODEL_NAME, TOK_REPO) for the release card.

Tests repointed at the 27B curve; note that a dense 27B at 2 streams
(92 vs 99 tok/s) no longer exposes a shared card by decode speed the way
the MoE did - the same-instant attest fill carries that.

Claude-Session: https://claude.ai/code/session_01VjhStJbNW6WzxucTcwuw4y
Pay is derived from the release's own measured speeds, so the flip already
re-priced itself: $0.70 per card-hour is now ~$0.58 per million output
tokens and ~$0.026 per million prompt tokens (one card flat out is ~1.2M
output tokens or ~27M prompt tokens an hour). The constants only back a
release without measurements, and they and their comments now describe the
27B: aggregate 338 tok/s, prefill 7,500, per-request curve 99.4/49.7/23.4
at 1/6/16, 26.4 GB resident, 64k context. The prompt-size DoS backstop
doubles to 0.5 MB so a full 64k prompt fits.

serving_rounds gains model_dir_sha256 and runtime_env (JSON) so the
Current release card can show what a miner copies for a model-directory
release (gittensor-db adds the columns).

Claude-Session: https://claude.ai/code/session_01VjhStJbNW6WzxucTcwuw4y
@anderdc
anderdc merged commit 52e7cba into test Sep 8, 2026
3 checks passed
@anderdc
anderdc deleted the serving/multi-release branch September 8, 2026 02:03
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.

1 participant