Skip to content

fix(metrics): align WandB axes and weighted minibatch stats#410

Closed
Hecate0821 wants to merge 1 commit into
mainfrom
chengxi/wandb-metrics-fix
Closed

fix(metrics): align WandB axes and weighted minibatch stats#410
Hecate0821 wants to merge 1 commit into
mainfrom
chengxi/wandb-metrics-fix

Conversation

@Hecate0821

@Hecate0821 Hecate0821 commented Apr 30, 2026

Copy link
Copy Markdown
Collaborator

Closed because this split is no longer desired: the WandB metric-axis fix belongs in PR #382, and the weighted fwd_bwd aggregation change is not needed.

@Hecate0821 Hecate0821 closed this Apr 30, 2026
VigSri pushed a commit to VigSri/cookbook that referenced this pull request May 5, 2026
…w-ai#405)

* feat(verifier): empirical renderer probe (Phase 0)

Adds training/verifier/, the first slice of the renderer verifier:
an empirical probe that pairs each rendered token's renderer claim
(chunk source, training weight) against deployment-observed
provenance (was this token actually emitted by the model, or was it
template-injected). The probe is observational only — verdicts come
in follow-up PRs (L1 spec-driven CPU checks, L2 corpus-scale checks)
and consume the same JSON envelope.

What's in this PR
- training/verifier/probe.py: render → call deployed model → render
  full transcript → align tokens → emit JSON artifact with audit
  table (chunk_source, renderer_claim_weight, provenance per token).
- training/verifier/cli.py + __main__.py: ``python -m
  training.verifier render --renderer glm5 --tokenizer-model ... --model
  ... --input msgs.json --output probe.json``.
- training/verifier/README.md: artifact schema and authoring workflow
  (what spec authors look for in the audit table).
- training/verifier/examples/: two GLM5 input fixtures.
- training/tests/unit/test_verifier_probe.py: network-free smoke test
  with a stub Fireworks-shaped client and a toy renderer; asserts
  artifact shape, sanity flags, and the provenance partition
  (prompt_hard_append / native_generated / trailing_hard_append /
  tokenization_diverged).

Why
The bug class behind GLM5 fw-ai#400 (a trailing role-tag with
weight=1 the model never emits) is exactly the disagreement the
audit table surfaces row-by-row. The probe is the authoring tool
for the YAML chunk_rules spec the L1 verifier will assert against;
without it, spec authoring relies on imagining the token stream
from raw Jinja, which is how the original bug shipped.

Out of scope
- No spec format, no L1/L2 verdict layers (follow-up PRs).
- No deployment lifecycle: ``--model`` takes an existing Fireworks
  model identifier; pair with the existing rl_loop-style spinup
  helper.
- No multimodal / VL renderer support; v1 is text-only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* verifier: add minimal probe deployment spinup helper

Adds training/verifier/spinup_deployment.py — a small CLI wrapper
around DeploymentManager + DeploymentConfig (the same path
training/recipes/rl_loop.py uses) so the probe has a documented way
to provision a serving deployment.

Shape resolution mirrors the rest of the cookbook:

* deploymentShapes/<id>/versions/<v>     → used as-is
* deploymentShapes/<id>                  → latest validated version
* trainingShapes/<id>/versions/<v>       → that version's pinned
                                            deployment_shape_version
* trainingShapes/<id>                    → latest validated training-
                                            shape version's pinned
                                            deployment_shape_version

Default --shape is the GLM5-on-B300 deployment shape
(accounts/fireworks/deploymentShapes/glm-5p1-b300/versions/jqami1br).
README updated with the end-to-end flow:

    MODEL=$(python -m training.verifier.spinup_deployment up ... | tail -1)
    python -m training.verifier render --model "$MODEL" ...
    python -m training.verifier.spinup_deployment down --deployment-id ...

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* verifier: fix probe round-trip — strip echoed prompt, parse via renderer

Two corrections found while running the probe end-to-end against
serverless GLM5 (accounts/fireworks/models/glm-5p1):

1. Echo handling. With ``echo=True`` the Fireworks gateway returns
   ``completion_token_ids`` and ``message.content`` as
   prompt+completion concatenated. The first iteration treated them
   as completion-only, so every assistant-turn token was classified
   ``tokenization_diverged``. The probe now detects when
   ``completion_token_ids`` starts with ``prompt_token_ids`` and
   slices the prompt prefix off (recording ``echo_prompt_stripped``
   in ``sanity``). The completion text is re-decoded from the
   sliced ids so it doesn't carry the echoed prompt.

2. Round-trip via parse_response. Camp A renderers (GLM5) emit the
   trailing role tag as ``stop_overlap``. When the model itself
   emits the same tag (which it does — the model is trained to use
   it as a stop signal), feeding the raw completion text back as
   ``content`` doubles up: once embedded in the content string,
   once as the renderer's stop_overlap. The probe now hands the
   completion tokens to ``renderer.parse_response`` first, then
   re-renders the structured ``Message`` it returns. This is the
   renderer's own definition of "what the assistant turn was", so
   the round-trip stays non-circular while the alignment becomes
   1:1 with the API's emission. Falls back to raw-content feed when
   parse_response signals failure (``parse_response_ok`` recorded
   in sanity).

Other small changes:

* Default ``--max-tokens`` 256 → 1024. Thinking-enabled models like
  GLM5 routinely emit 100+ tokens before a natural stop; under the
  old default the probe hit ``stop_reason: length`` and the
  trailing-token bucket couldn't be empirically classified.
* New ``glm5_short_answer.json`` example fixture — a math question
  with a one-integer constraint, designed so the model finishes
  naturally inside 1024 tokens for a clean stop_reason='stop' read.
* Sanity additions: ``echo_prompt_stripped``,
  ``parse_response_ok``, ``completion_token_count``,
  ``completion_stop_reason``.
* Test rework: the toy tokenizer now uses greedy-longest-match so
  decode→encode round-trips cleanly, and the second test was
  rewritten to cover the new echo-stripping path (the prior
  "tokenization_diverged" assertion no longer applies once
  parse_response is in the loop).

E2E validation against serverless GLM5: artifact lands clean
(provenance partition: 27 prompt_hard_append + 127 native_generated,
zero divergence, zero spurious trailing rows). Confirms that
post-fw-ai#400 GLM5 stop_overlap weight=1.0 is empirically validated —
the model itself emits the trailing <|user|> as its Camp A stop
signal.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* verifier: trust renderer.build_supervised_example for weights; add inspect

Two fixes / additions, both surfaced by reading the GLM5 audit table
from the previous run:

1. Renderer's claim_weight is now read from
   ``renderer.build_supervised_example(messages, train_on_what)``'s
   weights tensor, not re-derived from a base-class loop in the
   probe. The old approach was wrong for any renderer that
   customizes weighting — for GLM5 specifically it reported the
   leading ``<think>`` token as weight=1.0, when GLM5's actual
   override splits that token off and assigns it weight=0 (per
   the renderer's docstring: "kept in rendered sequence for
   template parity but masked out of loss because it is already
   injected by the generation suffix").

   This was also a circularity hazard: re-implementing weight
   logic in the verifier would have made the verifier validate
   the renderer against the verifier's idea of what the renderer
   should do, which catches nothing.

   New design: ``_structural_spans`` walks render_message for
   chunk_source / role / msg_idx attribution only — no weight
   computation. ``build_supervised_example`` is the single source
   of truth for weights. The two walks must agree on token
   identity; if they don't (renderer customization that changes
   tokens, not just chunk boundaries), ``sanity.structural_walk_token_match
   = false`` flags it.

2. ``python -m training.verifier inspect <probe.json>`` —
   pretty-prints sanity, provenance counts, and a digest of the
   audit table (chunk boundaries, every special-source row,
   every empirically suspect row). Drops the need for ad-hoc
   ``python -c`` snippets when reviewing a probe artifact.
   ``--all`` and ``--filter <provenance>`` flags for narrower
   views.

E2E re-validation against serverless GLM5 with the short_answer
fixture: idx=26 (the leading ``<think>``) now reads
``w=0.0  prov=prompt_hard_append`` — both halves of the
verification principle agree, GLM5 post-fw-ai#400 stays clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* verifier: unit-test inspect renderer

Adds test_inspect_renders_structured_summary covering the sanity /
provenance / audit-table sections and the --filter narrowing path.
Keeps the new CLI surface in CI without needing network or HF model
downloads.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* verifier: cross-renderer e2e validation (Qwen3 Camp B)

Probed accounts/fireworks/models/qwen3-8b with the qwen3 renderer.
Result: 240 audit rows, 0 tokenization_diverged, the trailing
<|im_end|> is correctly classified output / w=1.0 / native_generated
(Camp B layout — no stop_overlap row, by design). Confirms the
probe is renderer-agnostic across the two structural shapes
(Camp A's next-role-tag-as-stop and Camp B's inline end marker)
without code changes.

Adds qwen3_short_answer.json fixture and a "Verified against"
table in the README so the next renderer addition (Gemma4, MiniMax,
Nemotron, Kimi K2.5) has a concrete shape comparison to slot into.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* verifier: add sweep harness over serverless-reachable renderers

Adds python -m training.verifier.sweep that runs the probe against a
fixed catalogue of (renderer, model, input) rows wired to PR-history
bug refs, and emits both per-row JSON artifacts and a summary.json
plus a compact stdout table. The sweep is a regression harness
(records and reports), not a test runner — failures stay observable
without forcing a CI red.

Initial run against accessible Fireworks serverless models:

  glm5-single-turn                          PASS    cookbook#384,fw-ai#389,fw-ai#400
  glm5-multi-turn                           PASS    cookbook#397,fw-ai#400
  qwen3-thinking-single-turn                PASS    tinker#178,fw-ai#247,fw-ai#341
  qwen3-disable-thinking-single-turn        FAIL: HF prompt parity
  deepseekv3-single-turn                    FAIL: HF prompt parity
  deepseekv3-thinking-single-turn           FAIL: HF prompt parity

The three FAILs are real prompt-parity disagreements between the
cookbook renderer and the Fireworks serverless serving template:
* qwen3_disable_thinking renders a 4-token suffix
  ``<think>\\n\\n</think>\\n\\n`` to force the model to skip thinking;
  serverless qwen3-8b just emits ``<think>\\n``.
* deepseekv3 / deepseekv3_thinking prefill ``</think>`` /
  ``<think>`` after the assistant role tag (per upstream tinker
  fw-ai#267); the serverless template stops at ``<|Assistant|>`` without
  the prefill.

These are exactly the kind of cross-stack disagreement the probe is
designed to surface. The probe doesn't decide which side is correct —
the human does, by reading the audit table and the model card —
but it records the disagreement faithfully so it can no longer go
unnoticed. UNREACHABLE_BUGS in the sweep module documents bug
classes not exercised here (Nemotron / MiniMax / Gemma4 — not on
serverless; multimodal — out of scope; per-message weight=0 —
pre-renderer normalize_messages bug).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* verifier: expand sweep to all serverless-callable Fireworks models

Catalogue grew from 6 to 14 (renderer, model, input) rows covering
every chat model we can hit serverless from a stock dev API key:

PASS rows (renderer behaviour confirmed against serving template):
  glm5            x glm-5p1 single + multi-turn       cookbook#384,fw-ai#389,fw-ai#397,fw-ai#400
  glm5            x glm-5 base                         cookbook#384
  qwen3 (think)   x qwen3-8b single + multi-turn       tinker#142,fw-ai#178,fw-ai#247,fw-ai#341
  kimi_k25        x kimi-k2p5                          tinker#248,fw-ai#384,fw-ai#393,fw-ai#410
  kimi_k25        x kimi-k2p6 (cookbook#357 routing)   cookbook#357,tinker#410

FAIL: HF prompt parity (probe surfaces real disagreement at the
renderer-vs-serving-template seam):
  qwen3_disable_thinking x qwen3-8b
  kimi_k25_disable_thinking x kimi-k2p5
  deepseekv3 x deepseek-v3p1
  deepseekv3_thinking x deepseek-v3p1
  minimax_m2 x minimax-m2p7
  llama3 x llama-v3p3-70b single + multi-turn

The disable-thinking variants emit a suffix the serving template
doesn't (renderer forces think-skip, gateway defaults to thinking
enabled). DeepSeek renderers prefill <think> per upstream
tinker#267; the serving template hasn't followed. minimax_m2 has
an off-by-one extra newline after <think> in the generation suffix.
Llama 3.3 needs a closer look — the API may be returning
prompt_token_ids that include the completion when echo=True, so
that row's "FAIL" is partly a probe-extraction artefact rather than
a pure renderer bug.

Other catalogue notes (in code comments):
* qwen3 4b/32b/30b-a3b/instruct-2507 variants and qwen3p5-27b are in
  the inference catalogue listing but return 404 on chat.completions;
  not actually serverless from this dev API key.
* deepseek-v4-pro is callable but doesn't surface
  prompt_token_ids/token_ids, so the probe's echo-based parity check
  has no signal there.
* pyroworks-qwen3-1p7b is "no healthy upstream" (scaled to zero).

Verdict heuristic now reports stop_reason=length BEFORE
parse_response_ok=False — a length-truncated completion legitimately
fails the parser, so flagging that as a parse bug was a false
positive.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* verifier: add CPU HF chat-template parity layer (CI gate)

Adds the second of the two verifier probes — CPU-only renderer↔HF
parity, separate from the live empirical probe so PR CI doesn't
depend on a deployment and the live-only checks don't have to
pretend to validate upstream intent.

Layer split (now in README):

  CPU  — does the renderer match upstream HF's canonical chat
         template byte-for-byte?
         hf_parity.py + tests/unit/test_renderer_hf_parity.py
         pytest assertions, no JSON artifact, runs on every PR.

  Live — does the renderer match what the deployed model actually
         emits?
         probe.py (verifier render CLI) → JSON audit-table artifact.
         Manual / nightly, needs a Fireworks API key.

A renderer can pass one and still fail the other; both are needed.
Combining the two disambiguates the renderer-vs-gateway question:
* CPU PASS + live FAIL  → gateway is stale
* CPU FAIL + live FAIL  → renderer is wrong
* CPU PASS + live PASS  → fully validated

CPU layer initial cases (all PASS except the one xfail):

  glm5 single + multi-turn                  PASS
  qwen3 (default thinking) single + multi   PASS
  qwen3_disable_thinking                    PASS  ← resolves the live
                                                    sweep FAIL: gateway
                                                    is stale on the
                                                    enable_thinking=False
                                                    flag, NOT a renderer
                                                    bug.
  kimi_k25 single                           PASS
  minimax_m2 single                         XFAIL  ← confirms the live
                                                    sweep FAIL is a real
                                                    renderer bug (extra
                                                    \\n after <think> in
                                                    generation suffix).

The xfail flips to PASS automatically when the renderer is fixed; a
new regression in the opposite direction goes red. Tests skip
cleanly when the upstream tokenizer can't be downloaded (gated repo,
no network) so CI without HF Hub access still completes.

CI integration: cookbook's pyproject already has
testpaths=["training/tests"], so the new test file is picked up
without additional configuration.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* verifier: add React-based viewer for live probe artifacts

Single-file React app at training/verifier/viewer/index.html that
renders a probe JSON artifact as a colour-coded token stream.
Loaded via React 18 + Babel from CDN, no build step. Open the
file directly in a browser, pick (or drop) a probe JSON, see:

* Background colour     = provenance bucket
                          (prompt_hard_append / native_generated /
                           trailing_hard_append / tokenization_diverged).
* Left border colour    = chunk_source (bos / header / output /
                          stop_overlap / generation_suffix).
* Underline             = renderer_claim_weight > 0 (trainable token).
* Magenta + bold        = token id is in tokenizer.special_tokens
                          (the map from tokenizer.json's special-tokens
                          declaration; the artifact already carries this).
* Hover                 = full audit row in tooltip.

Filters at the top let the reader hide specific provenance buckets,
restrict to trainable tokens only, or restrict to special tokens
only. Also surfaces metadata (renderer, tokenizer, sampling),
sanity flags as ok/fail pills, and provenance / chunk_source counts.

Visualization is *only* for the live empirical probe — the CPU HF
parity layer is assertion-style (test framework is the GUI). README
updated to document the layer split.

The viewer is CDN-only and dependency-free so it can ship inside
the repo and run from any developer's filesystem without a Node
toolchain. If we later want a richer GUI (multi-artifact diff,
sweep summary view, or a hosted webapp), the JSON envelope is the
long-lived contract — the viewer is just a lens on top of it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* verifier: clean up dispatch — serverless default + --deployment-id auto-resolve

The design now has exactly two parts:

1. ``training/verifier/spinup_deployment.py`` — owns deployment
   lifecycle (up / wait-ready / down). Unchanged in this commit;
   only documentation changes around how it pairs with fw-ai#2.

2. ``training/verifier/render`` — owns probing. Picks where to send
   the request based on the flags it gets:

       --deployment-id <id>   → accounts/<account>/deployments/<id>,
                                with <account> resolved automatically
                                from the API key via DeploymentManager.
                                Mutually exclusive with --model.
       --model <full-id>      → exactly that string (mode=explicit).
       neither                → renderer's registered Fireworks
                                serverless default (mode=serverless).
       neither + no default   → error pointed at spinup_deployment.

   The artifact's ``deployment`` block now records ``mode`` plus
   ``api_flags`` (echo / raw_output / return_token_ids) and
   ``extra_completion_kwargs`` so reviewers can see at a glance
   exactly what the gateway was asked to do.

   Per-renderer serverless defaults (RENDERER_SERVERLESS_DEFAULTS in
   probe.py) cover the renderers we verified callable in the sweep:
   glm5, qwen3 / qwen3_disable_thinking, kimi_k25 /
   kimi_k25_disable_thinking, deepseekv3 + thinking variants,
   minimax_m2, llama3. Renderers without a registered default
   (gemma4, nemotron3) trigger an explicit error message that
   points at spinup_deployment.

React viewer:

* Metadata splits into a "Renderer args" panel (name,
  train_on_what, every config.* key) and a "Deployment / API args"
  panel (mode pill, model, deployment_id, sampling.{temperature,
  max_tokens}, every api_flags.{echo, raw_output, return_token_ids}
  with ok/fail pills, extra_completion_kwargs, tool count).
* The user-visible knobs (disable_thinking via renderer.config,
  echo / raw_output via api_flags, etc.) now have their own
  labelled rows instead of being buried in a JSON dump.

README:

* New table showing the four dispatch cases.
* Default flow shrunk to "no --model, no --deployment-id" (just the
  serverless default, the simplest possible invocation).
* Personal-deployment flow shows --deployment-id auto-resolve so no
  shell pipe through ``tail -1`` is needed.

E2E spot-check against serverless GLM5 (no --model passed):
  dispatch=serverless model=accounts/fireworks/models/glm-5p1
  api_flags={'echo':True,'raw_output':True,'return_token_ids':True}
  sampling={'temperature':0.0,'max_tokens':1024}

Tests: 9 pass + 1 xfail (unchanged).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* verifier: live GUI — dev server + interactive form in the React viewer

Adds a stdlib HTTP server (training/verifier/serve.py) that hosts the
React viewer and exposes a single POST /probe endpoint. The viewer
gains a "Live probe" form with every user-visible knob:

  * renderer (datalist of registered names; tokenizer auto-fills)
  * tokenizer_model (HF id; editable)
  * model | deployment_id (mutually exclusive; blank both → serverless
    default; live "dispatch preview" line shows what will be used)
  * max_tokens, temperature, train_on_what
  * tools (optional JSON array)
  * messages (system/user/assistant/tool rows, add/remove inline)

Submitting POSTs to /probe → server runs run_probe with the form data
→ returns the same artifact JSON the CLI writes → the existing audit
table view renders it in place. The probe-fixed API flags
(echo / raw_output / return_token_ids) are spelled out next to the
form so the user can see what's actually sent to the gateway, and
they're already surfaced in the Deployment / API args panel below.

Refactor:

* probe.py grows ``resolve_dispatch`` and ``DispatchError``;
  cli.py's _resolve_dispatch is now a thin argparse adapter so CLI
  and server share the same precedence rules (--deployment-id auto-
  resolve → --model → renderer's serverless default → error).
* Tokenizer + Fireworks client are cached at module level in serve.py
  so repeated browser submissions don't repay HF download / SDK auth
  on every keystroke.

Verified end-to-end:
  curl /health                                  → {"ok": true}
  curl -X POST /probe (glm5, 2+2 prompt)        → mode=serverless,
                                                  model=accounts/fireworks/models/glm-5p1,
                                                  89 native tokens, 105 audit rows,
                                                  stop_reason=stop.

Existing JSON-load path is preserved (collapsed under a "Or load an
existing probe JSON" disclosure). Tests: 9 pass + 1 xfail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* verifier: batch-native UI + YAML-driven inspection rules + triage workflow

UI / batch model
- Each "Run probe" or file-load appends a case; cases stack with per-case
  delete + Clear-all button. Counts panel removed.
- Per-case sanity flags / renderer args / deployment args (collapsed by
  default, after the token stream).
- Token stream + sticky right-side detail sidebar; amber-rippling
  background marks tokens that match an inspection rule.
- Unified attribute filter chips (provenance / chunk_source / trainable /
  special / inspect-flag), shared across cases.

Inspection rules — single source of truth
- New training/verifier/inspect_rules.yaml describes "worth inspecting"
  attribute combinations (id / when / reason). Generic equality matcher
  in inspect_rules.py; viewer fetches /inspect_rules and evaluates the
  same rules client-side.

Triage + reachability
- training/verifier/triage.py loops a JSON corpus through run_probe,
  pre-flights with a serverless ping, and writes a session file consumed
  by serve.py --session-file (auto-seeds the GUI on mount).
- training/verifier/check_renderers.py sweeps every registered
  renderer's serverless model and reports reachability.
- training/verifier/default_prompts.json ships an 8-case starter corpus.

Skill
- training/verifier/SKILL.md documents the workflow end-to-end:
  rules → interactive vs batch → renderer/serverless preflight → GUI.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* verifier: split into utils/ + rules/, drop sweep + check_renderers

Folder layout
- utils/ — engine modules (probe, inspect_rules, inspect, hf_parity)
- rules/ — data files (inspect_rules.yaml, default_prompts.json)
- top level — CLI entry points only (cli, serve, triage, spinup_deployment)
- SKILL.md drives the test flow; the package exposes utilities, not
  many overlapping pre-packaged scripts.

Removals
- sweep.py: parallel verdict heuristic, not the inspect_rules.yaml
  source of truth — drop.
- check_renderers.py: redundant with triage's pre-flight ping; SKILL.md
  describes how to verify reachability with a one-prompt triage run.

User-facing CLI paths unchanged
- python -m training.verifier render | inspect
- python -m training.verifier.serve
- python -m training.verifier.triage
- python -m training.verifier.spinup_deployment

Tests updated; 9 passed + 1 xfail (existing MiniMax xfail).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* verifier: SKILL.md is the only doc; drop default prompt catalogue

- Delete README.md — SKILL.md is the canonical entry point.
- Delete rules/default_prompts.json — a baked-in corpus is itself a
  pre-packaged workflow. The package now exposes utilities and a thin
  triage runner; users author their own prompt JSON for the cases that
  exercise the surface they care about.
- triage.py: --prompts is now required (no default fallback).
- triage.sh + SKILL.md: updated to require an explicit prompts.json arg.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: surface the renderer verifier in cookbook README + dev skill

- README.md: add `verifier/` to the repository tree and a short
  "Verifying a renderer" section pointing at training/verifier/SKILL.md.
- skills/dev/SKILL.md: two new task-routing rows so agents who hit a
  renderer-shaped problem land on the verifier doc directly.
- skills/dev/references/tools.md: header note that the verifier is a
  separate standalone tool with its own SKILL.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* viewer: renderer field is a real <select>, not a datalist autocomplete

The previous <input list="renderers"> only surfaces suggestions when
the user clicks into the field — on most browsers it looks like a
plain text box, so users couldn't tell other renderers were even
available and only ran glm5. A plain <select> exposes the full
RENDERER_TOKENIZER_DEFAULTS list at a glance.

Tokenizer_model stays as a free-text input so users can point at any
HuggingFace tokenizer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* verifier: drop static renderer→model defaults; query Fireworks live

The cookbook used to ship RENDERER_SERVERLESS_DEFAULTS — a hard-coded
dict of Fireworks model ids per renderer. It went stale every time
Fireworks bumped a serverless model. SKILL.md owns the human-readable
mapping; the runtime queries the live API for what's actually available.

probe.py
- Remove RENDERER_SERVERLESS_DEFAULTS and serverless_default_for().
- resolve_dispatch now requires --model or --deployment-id; "serverless"
  mode is gone. The error message points at the /models endpoint and
  Fireworks().models.list() so the caller can discover what to pass.

serve.py
- /registered → drop. Replaced with two live endpoints:
  * /renderers — calls tinker_cookbook.get_registered_renderer_names()
    so the GUI dropdown reflects what's actually registered.
  * /models — calls Fireworks().models.list(account_id="fireworks") so
    the model dropdown reflects the live Fireworks catalogue.

viewer/index.html
- Remove RENDERER_TOKENIZER_DEFAULTS — no static mappings on the client.
- Renderer field becomes a dropdown populated from /renderers.
- Model field becomes a dropdown populated from /models (with the
  /models error surfaced inline if it fails).
- Drop the renderer→tokenizer auto-fill; tokenizer is a free-text input
  so users can pick any HF tokenizer.
- Dispatch preview reads "ERROR: pick a model from the dropdown or
  enter a deployment_id" when neither is set, matching the new contract.

triage.py
- Pre-flight registration check uses is_renderer_registered() instead
  of dict membership. Aborts on NOT REGISTERED.

SKILL.md
- New section 1: "Pick a renderer / tokenizer / Fireworks model" with
  the discovery snippet and a starter reference table.
- Renumber the rest; drop "serverless" from the dispatch-mode wording.

Tests: 9 passed + 1 xfail (MiniMax xfail unchanged).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* verifier: /models filters to supports_serverless=true server-side

The control plane's gateway Model proto has an OUTPUT_ONLY
`supports_serverless` bool, and ListModels accepts an AIP-160
`filter` parameter (see fireworks/control_plane/protos/gateway/model.proto).
Pass `filter="supports_serverless=true"` so the dev server only returns
the models the GUI can actually probe via serverless — no client-side
filtering, no walking the entire fireworks-account catalogue.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* viewer: top-of-page advisory — prefer RL deployments over serverless

Serverless and RL deployments don't always render and tokenize the
same chat: different runtimes, different optimizations, occasionally
different chat templates. The page now surfaces this up front so
users default to deployment_id when they have one, and treat
serverless probes as approximations.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* viewer: replace native file-picker button with a styled label

The browser-default <input type="file"> button doesn't match anything
else in the form. Hide it offscreen and trigger it from a styled label
that uses the same border + radius + hover treatment as the other
buttons. The selected file name renders next to it in mono.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* viewer: hard mutex on model vs deployment_id + serverless billing note

- Frontend now enforces the mutual exclusion the backend already
  required: when `deployment_id` has a value the model dropdown is
  disabled, and vice versa. No more accidentally setting both and
  hitting the server-side reject.
- A small amber inline note appears under the model dropdown when a
  serverless model is selected, reminding the user that those probes
  hit the live API and are billed at the model's standard per-token
  rate. The advisory at the top of the page already nudges users
  toward `deployment_id`; this surfaces the cost reminder right where
  the choice is being made.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* skills: split renderer impl + verifier into cookbook/skills/

- Move training/verifier/SKILL.md → skills/verifier/SKILL.md so it
  lives where the cookbook expects skills (next to skills/dev/).
- New skills/renderer/SKILL.md — implementation guide for adding a
  new renderer: contract, training-mechanics invariants, common
  shape decisions with examples in the tree, dev loop, and gotchas.
  Pairs with the verifier skill.
- training/verifier/README.md is now a minimal placeholder pointing
  at both skills, with a layout map and a placeholder for the GUI
  screenshot to be added next.
- Cookbook README + dev skill cross-reference the new locations.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* verifier: forward HF_TOKEN; wrap tokenizer-load errors with guidance

The HF Hub error users hit when a tokenizer id is misspelled or the
repo is gated is opaque. New utils/tokenizer.py:load_tokenizer():
- forwards $HF_TOKEN / $HUGGING_FACE_HUB_TOKEN through to
  AutoTokenizer.from_pretrained so gated repos work for authenticated
  users without code changes
- catches OSError/ValueError from from_pretrained and re-raises with
  a RuntimeError that lists the three common causes (typo, gated
  repo, no network) and mentions the ~/.cache/huggingface/ cache so
  users know subsequent loads are offline-friendly

serve._tokenizer, triage._build_tokenizer, cli._build_tokenizer all
funnel through the new helper.

skills/verifier/SKILL.md mentions HF_TOKEN as an optional prereq and
explains the cache behaviour.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* verifier: full renderer catalog with tokenizer auto-fill

Why only 4 renderers showed before: get_registered_renderer_names()
returns custom-registered renderers only. tinker_cookbook's ~20 built-in
renderers live in get_renderer()'s elif ladder, never registered.
serve.py now ships a static RENDERER_TOKENIZER_DEFAULTS catalog covering:

- tinker_cookbook built-ins: role_colon, llama3, qwen3 (+ vl, instruct,
  disable-thinking variants), qwen3_5, deepseekv3 (+ thinking), kimi_k2,
  kimi_k25, nemotron3, gpt_oss (no_sys / low / medium / high reasoning)
- cookbook-local: gemma4, glm5, minimax_m2, nemotron
- anticipated next: kimi_k26, kimi_k26_disable_thinking, qwen3_6,
  qwen3_6_disable_thinking — surface them in the dropdown so they show
  up; selecting one before its renderer module exists yields a clean
  "renderer not registered" error.

Also force-import training.renderer at module load so cookbook
renderers are in the registry when /renderers fires.

GUI: tokenizer auto-fills from the catalog when you pick a renderer.
The Fireworks Model proto has huggingface_files (uploaded blobs) but
no canonical HF repo id, so static mapping is the cleanest answer —
documented as the source of truth in skills/verifier/SKILL.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* viewer: fix blanked page — render renderer dropdown from {name, ...}

The /renderers endpoint started returning objects when the catalog
landed, but the dropdown was still doing <option value={r}>{r}</option>
which throws "Objects are not valid as a React child" and blanks the
entire page. Read .name explicitly and surface "not registered" inline
on aspirational entries. Also auto-fill tokenizer_default on change
(was only happening on initial mount).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* verifier: move under training/renderer/, embed UI screenshots in README

The verifier validates renderers — colocating it under
training/renderer/verifier/ makes that ownership obvious and matches
how the cookbook organises related code (tests live next to what they
test, etc).

What moved:
  training/verifier/  →  training/renderer/verifier/

What changed:
- All Python imports retargeted to training.renderer.verifier.*.
- Workspace-root scripts (run.sh, triage.sh, run-bug-*.sh) updated to
  invoke `python -m training.renderer.verifier.*`.
- skills/verifier/SKILL.md, skills/renderer/SKILL.md, cookbook/README.md
  point at the new path.
- training/renderer/verifier/README.md now embeds two screenshots
  (config form + token stream) from the new images/ subfolder so
  visitors see the GUI before reading the long-form skill.
- Tests (test_verifier_probe.py, test_renderer_hf_parity.py) updated
  to the new module paths.

CLI invocations are now `python -m training.renderer.verifier render`
etc. — verbose, but it's the price of correct nesting. The shell
scripts hide this; only direct CLI users will notice.

9 passed + 1 xfail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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