Repository navigation
Conversation
…tion dump The fp8 half of the klein build could not run on a clean checkout. export_klein_fp8_calib.py collects amax over real activations, and it read them from $KLEIN_FP8_GOLD -- a directory of .npy tensors written by a validation harness that lives in another tree entirely (the dump in use was daydream-streamdiffusion's validation/goldens/matrix/klein-4b__instruct__flow__s2__320x576__cfg-none, produced by its gen_klein_golden.py). train-lora.py never set that variable, so `--type klein` with the default --klein-quality both died at that step unless the caller happened to have the harness output on disk. That is why the working FLUX presets point at hand-assembled bundle directories while sd-turbo points at a builder-keyed engine hash. Every tensor it needs is just what the reference diffusers pipeline computes, so make_klein_calib.py runs that pipeline once and writes them. One hook on the transformer's forward captures the encoder hidden states, the RoPE ids, the sigma schedule and the per-step input -- the same five tensors export_klein.py traces as the engine's inputs -- so the capture cannot drift from the pipeline the way a hand-written dump can. It also reads bn_mean/bn_std off pipe.vae.bn instead of shipping them as blobs, copies the model's own tokenizer.json (byte-identical to the vendored one: sha256 aeb13307a71acd8fe81861d94ad54ab689df773318809eed3cbe794b4492dae4), and saves the pipeline's own frame as a reference to compare the finished bundle against. It runs first in the sequence because it is the cheapest step that touches the weights: a bad model or venv now fails in minutes instead of after the ONNX export. The geometry was hardcoded to 320x576 in four places that each had to agree -- export_klein.py's W/H, build_klein_engines.py's and build_one_fp8.py's shape profiles, and the magic 1232 in fix_klein_dynamic_seq.py, which is Lp+Lt. They now derive from KLEIN_WIDTH/KLEIN_HEIGHT/ KLEIN_TEXT_LEN, which train-lora.py sets, so the resolution flags work for klein as they do for sd15/sdxl. They only take effect when the caller actually passes one: the sd defaults would silently build a 1024x1024 klein at four times the token count. Staging now only expects the transformer variant --klein-quality asked for, checks the bundle is complete before writing bundle.json rather than leaving a plausible-looking directory with a missing engine, and makes a missing RIFE ONNX fatal instead of a warning. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019QYyvhRXvSMSRQPaq12Teq
A klein build that finishes is not a klein build that works. The engines come out of a traced graph, two ONNX rewrites and an FP8 calibration, and each of those can yield an engine that loads, runs and returns noise -- the static-Lp transformer that shipped in klein-bundle-0620 is exactly that failure, and it was found by eye long after the build. make_klein_calib.py already runs the reference pipeline, so it now also records what the engines are supposed to produce: the per-step velocity, the text encoder's input_ids/attention_mask, the per-step latents, and the VAE decoder's input as the pipeline hands it over. The decoder input is captured rather than reconstructed so the check exercises the engine instead of a second implementation of the pack/bn math. tools/check_klein_bundle.py replays those through the bundle: cosine on the qwen encoder and on both transformer variants, and a PNG out of the TRT VAE decoder compared against the pipeline's own frame, so "is this a real generation or noise" is answerable by looking. RIFE gets two real frames (one a shifted copy of the other) and must return a finite, non-flat blend of them rather than a copy of either input. Non-zero exit on any miss, so it works as a build gate. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019QYyvhRXvSMSRQPaq12Teq
…cipe
uv's cache defaults to %LOCALAPPDATA%\uv\cache on C:, and this project's venv is ~10 GB of
torch/TensorRT wheels that nobody builds on C: because the engines and the HF snapshot need tens of
GB elsewhere. Across a volume boundary uv cannot hardlink out of its cache and copies instead,
costing the wheels twice on disk plus the copy time. Measured on desktop-u6umokq with the venv on D:
and the cache left at its default:
warning: Failed to hardlink files; falling back to full copy. This may lead to degraded performance.
If the cache and target directories are on different filesystems, hardlinking may not be
supported.
The same install with --cache-dir on D: emits no warning (54ms -> 19ms for one package). Koaia's
packaged builder already passes --cache-dir c:\uv for both the sync and the build, so the README now
says what the shipped app does.
The klein recipe was one line sitting under the img2img-turbo heading, with no mention that the
weights have to be fetched first, that excluding the unused single-file .safetensors saves 7.3 GB of
the 23 GB, or what the build costs. It gets its own section with the download, what the one command
actually does, the ~18 GB footprint, and the quality/resolution/hw-compat flags.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QYyvhRXvSMSRQPaq12Teq
The spelling has moved between TensorRT majors; an attribute access on one that is absent fails at import time and takes the whole checker with it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019QYyvhRXvSMSRQPaq12Teq
klein_env.sh pointed PYTHONPATH at a hand-placed ./klein-pydeps checkout of git diffusers 0.39 next to a separate ./.flux-venv, and two docstrings told the reader to source it. None of that has been true since the Flux2-capable diffusers fork became a pinned dependency in pyproject.toml -- following those instructions now side-loads a second diffusers over the installed one. The scripts run in the project venv like everything else. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019QYyvhRXvSMSRQPaq12Teq
The txt2img reference run never encodes, so vae_encoder_bf16.plan was the one engine the bundle ships that nothing verified -- and it is the engine the img2img and reference-edit presets depend on. The calibration run now round-trips the frame it just generated through the real encoder and records the pair, and the checker compares the engine against it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019QYyvhRXvSMSRQPaq12Teq
…t after TensorRT will not build an FP8 Q/DQ engine on anything older than Ada (SM 8.9), and it only says so when it reaches that engine -- the last step of the pipeline. Measured on an RTX 3090 (SM 8.6), the default --klein-quality both ran the reference pass, the whole ONNX export and all four bf16 engines, then died 13.6 minutes in with [TRT] [E] IBuilder::buildSerializedNetwork: Error Code 9: API Usage Error (Networks with FP8 Q/DQ layers require hardware with FP8 support.) surfaced to the caller as `AssertionError: build returned None`, leaving no bundle at all despite every other engine being on disk. The capability is now checked up front. When the caller did not ask for a precision, the default falls back to the bf16 transformer and says why; when they passed --klein-quality speed/both explicitly, it is an error before any work rather than a silent substitution. build_one_fp8.py also explains itself instead of asserting, for standalone use. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019QYyvhRXvSMSRQPaq12Teq
Run against a bundle whose build had failed, the checker skipped all six checks and printed "ALL CHECKS PASSED" with exit 0. A gate that passes when it verified nothing is worse than no gate: it would have signed off the empty bundle the FP8 failure left behind. A skip is now only benign when the thing is genuinely optional -- the fp8 transformer, which depends on --klein-quality and on SM 8.9+. A missing bf16 transformer, qwen encoder, VAE engine, RIFE engine or reference tensor is a failure, and a run where no check executed at all fails regardless. The report records what was skipped and why alongside what ran. Also fix the VAE-encoder capture added in the previous commit: it read next(pipe.vae.parameters()) .device, which under enable_model_cpu_offload is the CPU between calls, so the input went to the CPU while accelerate's pre-hook moved the weights to the GPU -- "Expected all tensors to be on the same device". The execution device is the right one to ask for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019QYyvhRXvSMSRQPaq12Teq
The encoder capture sat ahead of image.save(), so when it raised, the run lost calib_reference.png and calib.json too -- the frame is the primary artifact and the one thing worth having from a run that fails later. Observed exactly that: a device mismatch in the encoder block left a calibration directory with every tensor but no reference image, and the bundle check then skipped its PSNR comparison without saying why. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019QYyvhRXvSMSRQPaq12Teq
End-to-end validation — Windows
|
| file | size | reference bundle (RTX 4090, hand-built) |
|---|---|---|
transformer_bf16.plan |
7758.1 MB | 7756 MB |
qwen3_encoder_bf16.plan |
6231.7 MB | 6232 MB (exact) |
vae_decoder_bf16.plan |
99.9 MB | 100 MB (exact) |
vae_encoder_bf16.plan |
71.2 MB | 71 MB (exact) |
rife_ifnet_fp16.plan |
8.8 MB | built here from the vendored ONNX |
tokenizer.json, bn_mean.bin, bn_std.bin, bundle.json |
model-derived | — |
The transformer built with a live dynamic profile (minShape is not the same as maxShape on
hidden_states) — not the static-720 engine that shipped in klein-bundle-0620.
tools/check_klein_bundle.py vs an independent reference run
A second pipeline pass the engines were not built from, so the comparison is not circular.
ALL CHECKS PASSED (7 ran, 1 skipped), exit 0.
| check | metric | threshold |
|---|---|---|
| qwen3_encoder | cos 0.999926 | 0.999 |
| transformer_bf16 | min cos over both steps 0.998913 | 0.995 |
| transformer_fp8 | SKIP (not built — SM 8.6) | optional |
| vae_decoder vs reference frame | PSNR 50.79 dB | 25.0 |
| vae_decoder signal | std 48.76 | 10.0 |
| vae_encoder | cos 0.999959 | 0.999 |
| rife | finite, std 47.87, PSNR vs A 15.4 / vs B 15.2 (blended, not a copy) | — |
Negative control: the same checker against an empty bundle exits 1 —
FAILED: no check actually ran -- nothing was verified.
All three images eyeballed and coherent (not noise): the pipeline reference, the TRT VAE decode
(visually identical to it, consistent with 50.79 dB), and the RIFE mid-frame (a visible half-shift
between its two inputs).
Not validated
- The bundle has not been loaded by score /
librediffusion.dll; engines were exercised through
the TensorRT Python API directly. - FP8 TRT build is unexercised (no Ada+ GPU). Its calibration and ONNX export — the part that was
previously impossible — did run to completion. - Only 320x576 built end-to-end; the geometry arithmetic agrees for 512x512 and 1024x1024 but was not
built. --hw-compat ampere_plus, explicit--klein-quality, Linux and macOS: not exercised.
…g valid speed bundles Two defects of the same shape as the one the previous commit fixed globally, surviving inside individual checks. The transformer check seeded a running minimum at 1.0 and `continue`d past any step whose reference was missing, so with all four step tensors absent it reported `PASS transformer_bf16 min_cos_over_steps=1` having executed the engine zero times -- and the whole-run guard was satisfied because other checks had run. A reference with no timesteps at all did the same. Both are now failures, and the step count is recorded next to the result so a run that verified one step cannot be mistaken for one that verified two. A missing `transformer_bf16.plan` was fatal unconditionally, but `--klein-quality speed` stages only the FP8 plan by design, so the gate rejected a bundle its own builder had just produced correctly. Which variants exist is the builder's choice; what the bundle must have is at least one transformer that verifies, which is what is checked now. The decoder comparison was the only thing separating a correct decode from structured noise, and it vanished without a trace when the reference frame or PIL was absent, leaving `std >= 10` to pass the check on its own. RIFE was gated on finite-and-not-flat, which an engine echoing one of its inputs satisfies, while the line printed under it asserted the output was blended whatever the numbers said. Returning an input is the realistic way RIFE fails, so the output must now be unlike both inputs and roughly equidistant from them: measured 15.4/15.2 dB (gap 0.2) for a correct engine against 54.2/7.6 (gap 46.6) for one echoing frame A. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019QYyvhRXvSMSRQPaq12Teq
Correction + review fixes (
|
| control | expected | got |
|---|---|---|
| NC0 real bundle | 0 | 0 — ALL CHECKS PASSED (9 ran, 1 skipped) |
| NC1 transformer step refs deleted | 1 | 1 — FAIL transformer_bf16 (reference missing for step(s) [0, 1]) |
| NC1b zero-length timesteps | 1 | 1 — FAIL transformer_bf16 (reference has no timesteps) |
| NC2 speed-style bundle (fp8, no bf16) | 0 | 0 — fp8 checked, bf16 benign skip |
| NC3 reference frame deleted | 1 | 1 — FAIL vae_decoder_vs_reference |
| NC4 empty bundle | 1 | 1 — no check actually ran |
NC0: qwen cos 0.999926 · transformer min-cos 0.998913 (steps=2) · VAE decode PSNR 50.7914 dB · VAE
encoder cos 0.999959 · RIFE not_a_copy 15.40 dB (<40) and balanced 0.16 dB (<6). The
echo-frame-A case (54.2 / 7.6) fails both new RIFE checks.
Still not validated
NC2 proves the gate logic; the plan it checked was a renamed copy of the bf16 engine, not a
real FP8 engine — none has been built anywhere, no Ada+ GPU was available. The bundle has still
never been loaded by score / librediffusion.dll. Only 320x576 end-to-end.
Note: ed1d8ef's message describes only the checker changes; the commit also carries the
train-lora.py and README fixes listed above (an amend collapsed them, and I left history alone
rather than force-push a shared branch).
Moves the runtime pin from 2a230dc (release/sat-2026-09) to e4456c6b (main), picking up jcelerier/librediffusion#25: klein builds from the model alone, without the out-of-tree activation dump that made `--type klein` impossible on any machine lacking it. 2a230dc is not an ancestor of main -- the sat-2026-09 work reached main as a squash-merge -- so this crosses histories rather than fast-forwards. The tree diff is Python and README only, 0 .cpp/.hpp/.cu/CMakeLists, so librediffusion.{so,dll} is unchanged and no runtime rebuild is implied. koaia pins the same sha; scripts/check-librediffusion-pin.sh compares the two and refuses to build on drift, so they move together. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019QYyvhRXvSMSRQPaq12Teq
…work Moves our pin from 2a230dc to e4456c6b, matching ossia/score-addon-librediffusion#27. The engine builder we ship and the runtime the app dlopens must come from the same commit, which scripts/check-librediffusion-pin.sh enforces; it now reports "OK -- both at e4456c6b3". This picks up jcelerier/librediffusion#25: klein builds from the model alone, so `--type klein` no longer requires an out-of-tree activation dump that existed on exactly one machine. Python and README only between the two shas, 0 native files, so librediffusion.{so,dll} is unchanged and the TensorRT/CUDA matrix is unaffected. prepare-package.sh already copies src/streamdiffusion and tools/*.py wholesale, so make_klein_calib.py and check_klein_bundle.py ship without further change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019QYyvhRXvSMSRQPaq12Teq
Why FLUX was fragile
--type kleincould not complete on a clean checkout.export_klein_fp8_calib.pycollects FP8 amaxover real activations and read them from
$KLEIN_FP8_GOLD, a directory of.npytensors produced bya validation harness in a different repository. The dump in use lives on
ai-workstation-01at/media/data1/daydream-streamdiffusion/validation/goldens/matrix/klein-4b__instruct__flow__s2__320x576__cfg-none/,written by that tree's
gen_klein_golden.py.train-lora.py::export_kleinnever set the variable, sothe default
--klein-quality bothdied at that step on any machine without the harness output.That is why the working FLUX presets point at hand-assembled directories
(
/media/data2/flux-trt/klein-bundle-0620) while SD-Turbo points at a builder-keyed engine hash(
/media/data2/lrd-engines/abb77149636ece27).The geometry was hardcoded to 320x576 in four places that each had to agree. The cost of that is
recorded on the workstation:
/media/data2/flux-trt/klein-landscape/README.txtsays "landscapeklein = klein-landscape_engine VAE ... + reused dynamic transformer/qwen/rife from klein-bundle-0620" —
a landscape variant needed a hand-built VAE pair plus symlinks for everything else.
What changed
make_klein_calib.pyruns the reference diffusers pipeline once and writes every tensor the FP8calibration reads. One hook on the transformer's forward captures the same five inputs
export_klein.pytraces, so the capture cannot drift from the pipeline. It also readsbn_mean/bn_stdoffpipe.vae.bnand copies the model's owntokenizer.json— byte-identical tothe vendored blob (
sha256 aeb13307a71acd8fe81861d94ad54ab689df773318809eed3cbe794b4492dae4). It runsfirst, so a bad model or venv fails in minutes rather than after the ONNX export.
KLEIN_WIDTH/KLEIN_HEIGHT/KLEIN_TEXT_LENacrossexport_klein.py,build_klein_engines.py,build_one_fp8.pyandfix_klein_dynamic_seq.py(whose magic1232isjust
Lp+Lt).--opt-width/--opt-heightnow work for klein, and only take effect when actuallypassed so the sd defaults can't silently build a 1024x1024 klein.
--klein-qualityasked for, and refuses to writebundle.jsonunless the bundle is complete.tools/check_klein_bundle.pyreplays the captured reference through the built engines: cosine onthe qwen encoder and both transformers, a PNG out of the TRT VAE decoder against the pipeline's own
frame, and a RIFE interpolation between two real frames.
--cache-diron Windows (measured hardlink fallback), and a real klein section.Validation
Built end-to-end on
desktop-u6umokq(RTX 3090 24G, TRT 11.2.1.2, torch 2.12.0+cu132) from asha256-verified FLUX.2-klein-4B snapshot, with no golden dump present on the machine. Evidence in the
release ledger.