Nightly 2026-07-22/23: template byte gate (prompts byte-identical to transformers), dtype-pollution fix, config-driven EOS/BOS salvage, dep refresh - #12
Merged
Conversation
…bumps Routine dependency-freshness increment. `cargo update` advances five transitive patch versions (libc 0.2.186→0.2.188, tokio 1.53.0→1.53.1, xxhash-rust 0.8.17→0.8.18, zerocopy/zerocopy-derive 0.8.54→0.8.55); all off the GPU compute path. The only `cargo upgrade --incompatible` candidate is wgpu 29→30, held per the intentional pin (burn 0.21 resolves wgpu 29 transitively — wgpu 30 unblocks with a burn bump, not a `cargo upgrade`). burn/tokenizers/criterion are already at latest. Verified green: cargo fmt, cargo build --workspace, cargo clippy --all-targets (no warnings), cargo test --workspace --lib (176 passed). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…iven EOS/BOS (salvaged from the crashed 2026-07-21 run) All three safetensors loaders now capture the TokenizerConfig that validate_checkpoint_dir already parsed and surface it as a public `tokenizer_config: Option<TokenizerConfig>` field on LoadedQwen2/LoadedQwen3/LoadedLfm2, so a consumer reads config-driven EOS/BOS/PAD ids straight off the loaded model. Additive by design: is_eos still rides config.json's cross-checked eos_token_id, so decode/parity are byte-unchanged; a debug-assert per loader upholds EOS agreement. GGUF loads surface None (self-contained). Verified this run: fmt/clippy clean, 176/176 unit tests, build green. REAL-GPU: safetensors leg asserts the surfaced eos_id() == 151645 and add_bos false; GGUF leg asserts tokenizer_config.is_none(); strict Qwen3 parity vs llama.cpp re-passed (top-5 ids exact in order, max |dlogprob| 4.0156e-1, unchanged). Also folds 2026-07-22 research notes into ROADMAP (pin watch: burn 0.21 / tokenizers 0.23.1 current; wgpu 30 lifts SHADER_F16 to WGSL; Qwen3.5/3.6 FC field notes; hf-chat-template 0.2.1). Known pre-existing flake surfaced while verifying (A/B-confirmed at f1e547a, fixed in the next commit): running the GpuF16 leg before the Gpu GGUF leg in one process flips the per-device default float dtype, so the f32 readback dies TypeMismatch(expected F16). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…late GpuF16 test legs per process
Burn 0.21 resolves unspecified-dtype tensor creation against a per-DEVICE
settings policy, not the backend type alias: a GpuF16 client and a Gpu
client sharing the device inside one process flip each other's ambient
float dtype. Found live: with the three real_qwen3 GPU tests in one binary,
running the f16 leg before the f32 GGUF leg made the GGUF load's
zeros-probed target_float come back F16 — the whole "f32" model loaded and
ran in f16 and the strict f32 logits readback died
TypeMismatch("expected F16, got F32"). Deterministic with --test-threads=1
(f16 first), racy in parallel, passes solo; A/B-confirmed PRE-EXISTING at
f1e547a (not the tokenizer_config work).
Two mitigations:
- every loader (qwen2/qwen3/lfm2/minilm, safetensors + GGUF paths) now
derives target_float as <B::FloatElem as Element>::dtype() — a type-level
fact immune to ambient device state;
- real_qwen3_decodes_coherently_in_f16 moves to real_f16.rs, so every
GpuF16 leg lives in its own test binary (process isolation), documented
in that file's module docs.
The proper in-process mixed-precision policy (set_default_dtypes at
planner/model-load level + a two-alias regression test) is folded into the
roadmap as a P6 item.
Verified: fmt/clippy clean, 176/176 unit tests; real_qwen3 suite now 2/2
green single-threaded AND parallel; the moved f16 leg green in its own
process; strict Qwen3 parity gate vs llama.cpp unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e-identical to transformers, FC included
Lands the P3 hf-chat-template evaluation as tests/template_gate.rs: render
the checkpoint''s own imported chat_template through hf-chat-template 0.2.1
(byte-identical to transformers.apply_chat_template) and byte-compare with
our hardcoded ChatMl renderer. All four legs byte-identical on Qwen3-0.6B:
plain (142 B), multi-turn (201 B), full Hermes tools block (748 B), and FC
history (tool_call turn + tool response, 324 B).
The gate caught two real divergences, both fixed here:
- serde_json now runs with preserve_order as a first-class workspace
feature: insertion-order keys are what Python/transformers renders (the
training distribution). Without it, the dev-dep (which hard-requires
preserve_order) would have silently flipped TEST builds to insertion
order while production consumer builds stayed alphabetical - tests
verifying different prompt bytes than consumers ship.
- prompt JSON now serializes with Python json.dumps separators (", " /
": ") via a small custom Formatter (chat::python_json), used by the
Hermes <tools> block, the LFM "List of tools:" line, and <tool_call>
history blocks - the exact spacing the reference stack renders and the
models emit back.
Live re-proof on the 4070 Ti SUPER with the changed prompt bytes, all
three families: Qwen3-0.6B, Qwen2.5-1.5B (checkpoint fetched this run)
emitted clean parseable Hermes calls; LFM2.5-1.2B (fetched this run)
emitted exactly <|tool_call_start|>[get_weather(city="Paris")]
<|tool_call_end|>. Parity unmoved: Qwen2 vs Candle max |dlogit| 2.670e-5 +
Ollama greedy byte-identical; Qwen3 vs llama.cpp top-5 exact, max
|dlogprob| 4.0156e-1. fmt/clippy/176 unit tests green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…inor bumps; wgpu 30 pin held cargo update: clap 4.6.3->4.6.4, glob 0.3.3->0.3.4, libc 0.2.188->0.2.189, syn 3.0.2->3.0.3, tokio-util 0.7.18->0.7.19. cargo upgrade --incompatible offered exactly one major - our direct wgpu probe dep 29->30 - and it is REVERTED per the documented pin: burn 0.21 resolves wgpu 29 transitively, so accepting 30 would put two wgpu majors in the binary (the adapter probe inspecting different state than the runtime); it unblocks with a burn bump. burn 0.21 and tokenizers 0.23.1 confirmed current upstream this run. Verified: clippy clean, full test suite green, release build green; perf budgets HOLD on a quiet GPU - budget.rs TTFT 126.9 ms (<= 150) / decode 13.2 tok/s (>= 10), budget_cpu.rs 10.44 tok/s (>= 6, 0.5B checkpoint fetched this run). First budget attempt read 246 ms / 5.0 tok/s because an Ollama qwen3.5:9b (6.7 GB) was resident on the card - re-ran after it unloaded; corroborates the ROADMAP note that budget gates need a quiet machine. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
physics515
pushed a commit
that referenced
this pull request
Jul 24, 2026
…rallel nightlies Both runs independently found and fixed the same three things; main's (already-merged) implementations win wherever they overlap: - Template byte gate: keep main's in-process hf-chat-template dev-dep + workspace-level serde_json preserve_order (insertion-order prompt bytes in PRODUCTION — the stronger resolution of the same feature-unification hazard this branch dodged with an out-of-workspace probe). tools/template-probe is DROPPED as superseded; this branch's extra coverage is PORTED into main's harness: Qwen2.5 plain/tools/FC-history, Qwen2.5 + Qwen3 divergence pinning (default preambles, think-stripping), LFM2.5 plain/tools/think/pythonic legs (also exercising the chat_template.jinja fallback). Gate now 10 cases, all passing. - python_json spacing: keep main's (same formatter, private fn); drop this branch's public py_json duplicate. - GpuF16 isolation: keep main's (same test move + the deeper loader fix — target_float from the TYPE); keep main's richer dtype-policy roadmap item, drop this branch's duplicate. - Keep from this branch: burn.toml kernel-OOB validation + the bench-crate cubecl.toml opt-out, the P6 heterogeneous-precision design note, the P9 KV-cache FP8 research item, ChatMl::qwen3() think-strip item. - Keep from main: tokenizer_config surfaced on Loaded* structs (config-driven EOS/BOS), all model loader changes. Verified on the merged tree: fmt + clippy clean; 176 unit tests; template gate 10/10 byte-identical; real_qwen3 2/2 (validate armed, tokenizer_config assertions live), real_toolcall clean spaced call, real_f16 2/2; budgets hold — CPU 13.56 tok/s, GPU 12.3/13.4 tok/s + TTFT ~104 ms (first run after fresh binaries reads low while autotune re-tunes, per the roadmap note). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Nightly run report for 2026-07-22/23 (the run crossed midnight). Four increments on top of the salvaged 2026-07-21 dep bump, all gates green.
What shipped
1. Salvaged the crashed 2026-07-21 run's work — config-driven EOS/BOS (
feefac8).The previous nightly crashed after finishing (but not committing) its increment. Re-verified from scratch, then committed: all three safetensors loaders surface the parsed
tokenizer_config.jsonas a publictokenizer_config: Option<TokenizerConfig>field onLoadedQwen2/LoadedQwen3/LoadedLfm2, so consumers read config-driven EOS/BOS/PAD ids off the loaded model. Additive —is_eosstill ridesconfig.json's cross-checkedeos_token_id, decode/parity byte-unchanged, debug-asserted EOS agreement, GGUF loads surfaceNone. REAL-GPU: safetensors leg asserts surfacedeos_id() == 151645; GGUF leg assertsNone.2. Root-caused and fixed a live dtype-pollution flake (
cbe4846).While verifying #1, the f32 GGUF leg died
TypeMismatch("expected F16, got F32")whenever the f16 test ran first in the same process — A/B-confirmed pre-existing atf1e547a, not the salvage. Cause: Burn 0.21 resolves unspecified-dtype tensor creation against a per-DEVICE settings policy, soGpuF16andGpuclients sharing the device flip each other's ambient float dtype; the loaders' zeros-probedtarget_floatinherited it, silently loading the "f32" model in f16. Fix: every loader derivestarget_floatfrom the TYPE (<B::FloatElem as Element>::dtype()), and allGpuF16legs moved toreal_f16.rs(own process). The remaining planner-level policy work is a new P6 roadmap item.real_qwen3is now 2/2 green sequential AND parallel.3. Template byte gate — prompts now byte-identical to
transformers(ed310f0).Landed the P3
hf-chat-templateevaluation astests/template_gate.rs: renders the checkpoint's own importedchat_templatethroughhf-chat-template0.2.1 and byte-compares withChatMl. All four legs byte-identical on Qwen3-0.6B: plain 142 B, multi-turn 201 B, full Hermes tools block 748 B, FC history 324 B. The gate caught two real divergences, both fixed:serde_json/preserve_orderis now a first-class workspace feature — insertion-order keys are what Python/transformers renders; without it the dev-dep would have silently flipped TEST builds to insertion order while production builds stayed alphabetical (tests verifying different prompt bytes than consumers ship).json.dumpsseparators via a small customFormatter(chat::python_json) — the exact spacing the reference stack renders and models emit back.Live FC re-proof with the changed prompt bytes on ALL THREE families (4070 Ti SUPER): Qwen3-0.6B and Qwen2.5-1.5B emitted clean parseable Hermes calls; LFM2.5-1.2B emitted exactly
<|tool_call_start|>[get_weather(city="Paris")]<|tool_call_end|>.4. Dependency freshness (
9f64ed7).cargo update: clap 4.6.4, glob 0.3.4, libc 0.2.189, syn 3.0.3, tokio-util 0.7.19.cargo upgrade --incompatibleoffered exactly one major — the direct wgpu probe dep 29→30 — reverted per the documented pin (burn 0.21 resolves wgpu 29 transitively; two wgpu majors in one binary = the probe inspecting different state than the runtime; unblocks with a burn bump). Upstream check: burn 0.21 and tokenizers 0.23.1 are still current.Gates (all green)
--all-targets/ buildtransformers.apply_chat_templatebudget.rs)budget_cpu.rs)Operational note: the first GPU budget attempt read 246 ms / 5.0 tok/s because another agent's
qwen3.5:9b(6.7 GB) was resident in Ollama on the card — re-ran clean after it unloaded. Corroborates the ROADMAP "quiet machine" warning; checkollama psbefore trusting a budget number.Fixtures fetched this run
Qwen2.5-1.5B-Instruct (2.9 GB), Qwen2.5-0.5B-Instruct (1.0 GB), LFM2.5-1.2B-Instruct (2.2 GB) — the local cache now covers both parity_qwen2 legs, all three FC suites, and both budget gates offline.
Research folded into ROADMAP
SHADER_F16to ALL shader kinds (WGSL included) + the Vulkan f16 IO polyfill — when burn bumps to wgpu 30, f16 stops being SPIR-V-only.qwen3_codertool-call parser — check template markers before assuming Hermes covers a 3.6-class port.hf-chat-template0.2.1 current; adopted (dev-dep) with a new[ ]split for a general fallback renderer.What's next
tokenizer.modelimport (route documented; needs a fixture fetch).burn.tomlcheck_mode="validate"bench A/B (weights for it are now cached).🤖 Generated with Claude Code