Repository navigation
#867 stage 2: step-cache skip counts (timing.cache, cache_stats) #886
Copy link
Copy link
Closed
Labels
featureWork bigger than a fix: designed with Don, built in stages (harnest R11)Work bigger than a fix: designed with Don, built in stages (harnest R11)owner:testerTester's turn to actTester's turn to actstageA child issue: one stage of a featureA child issue: one stage of a featurestatus:verifiedTester confirmed the fix via a real MCP callTester confirmed the fix via a real MCP calltarget:mini-aiFound against a non-lem server (DW_TARGET=mini-ai); Don triagesFound against a non-lem server (DW_TARGET=mini-ai); Don triagesverified-on:mps
Description
Stage 2 of #867. Plan: plan v2, "Stage 2". The plan comment is authoritative; this is a summary. It builds on stage 1's manifest-entry timing.
Builds
Step-cache counts:
- A forward hook on the cache transformer (
get_cache_transformer,components.py:812). It is registered insidestateful_cache_contextand counts computed or skipped per forward from the leader hook's shared state (fbc_leader_block_hook,mag_cache_leader_block_hook). - The hook is removed in the
finallyafter emittingcache_statsand before_reset_stateful_cache(). - Mind the nested
cache_context("cond"/"uncond")that pipelines open inside dw's context (components.py:834). timing.cache = {type, threshold, forwards, computed, skipped, hit_rate}forfirst_blockandmag.taylorseer/faster/text_kvget the type and config keys with null counts.- A
cache_statsevent, once per pipeline call, carrying the same object plusstep. - Missing private diffusers attribute: null counts plus one
warningwithkind: "cache_stats_unavailable". It never fails the run. get_job_eventsdescription: namecache_statsamong the kinds. Raise the budget if needed, with a dated comment.
Owes
- Unit tests:
- First, a tiny real pipeline with CFG and
first_block:computed + skipped == forwardsandforwards == 2 × passes. - Fake-leader-state counting, with a reset between calls and emission before the reset.
- Missing attribute → null + warning, and the run continues.
- A test pinning the installed diffusers' hook names and state attributes.
- First, a tiny real pipeline with CFG and
- Docs:
docs/ACCELERATION.mdcache section: readinghit_rate, and how a cache with 0 skips can still change output.docs/SERVER.mdevent table:+cache_stats.
- Coordination: H3 cinema-path speed: follow-up experiments after first_block -22% (threshold, 20 steps, close-up, seeds, MagCache) #875's carved fix also edits
stateful_cache_context. Whichever lands second rebases.
Deploy
Server restart.
Passes when
The plan's stage 2 acceptance intent holds:
- high threshold skips;
- near-zero threshold doesn't skip;
- one
cache_statsevent per call; - no cache → no key and no event;
magcounts;text_kvis type-only with no warning;- H3 on lem, if available.
It also has to pass every pending: #<this issue> case the tester writes.
Filed by the lead, claude-opus-5-5 / anthropic.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
featureWork bigger than a fix: designed with Don, built in stages (harnest R11)Work bigger than a fix: designed with Don, built in stages (harnest R11)owner:testerTester's turn to actTester's turn to actstageA child issue: one stage of a featureA child issue: one stage of a featurestatus:verifiedTester confirmed the fix via a real MCP callTester confirmed the fix via a real MCP calltarget:mini-aiFound against a non-lem server (DW_TARGET=mini-ai); Don triagesFound against a non-lem server (DW_TARGET=mini-ai); Don triagesverified-on:mps
Activity
feat(cache): #886 - fold the skip counter into timing.py; type timing…
dkackman commentedon Oct 14, 2026
Built - stage #886 (#867 stage 2: step-cache skip counts). Merged to
developat87d5f035(branchfeat/867-B-cache-stats). The driver deploysdevelopto mini-ai after this session; I did not deploy.What shipped, by plan item
should_computeonce per forward. The hook is registered instateful_cache_contextfor the pipeline call and removed after it. It reads inside the cond/uncondcache_contextthe pipeline opened, so classifier-free guidance counts both forwards. Code is in the step-cache section at the end ofdw/pipeline_processors/timing.py, wired incomponents.stateful_cache_context.FluxPipelinewithtrue_cfg_scale=2and first_block, 6 steps, givesforwards == 12andcomputed + skipped == forwards. Threshold 0.5 skips; threshold 1e-6 skips nothing.timing.cacheon the manifest entry:{type, threshold, forwards, computed, skipped, hit_rate}, wherehit_rate = skipped/forwardsto 3 places. mag also reportsnum_inference_steps, max_skip_steps, retention_ratio, calibrate. A chain sums its segments' counts. A step with no cache has nocachekey.StepTiming.cacheis in the API contract and the OpenAPI/UI types are regenerated.cache_statsevent: one per pipeline call, emitted before dw resets the cache state (pinned by a test). It carriesstepand the same fields.get_job_events(kinds=["cache_stats"])finds it, and the tool description now names the kind.typeplus config keys, with null threshold and counts, and no warning.warningwithkind: "cache_stats_unavailable"(pluscache_type), and the run finishes (tested). Pin tests on the installed diffusers cover the two leader-hook names,should_computeon both state classes,StateManager.get_stateandHookRegistry.get_hook.cache_statsrow and warning kind indocs/SERVER.md;timing.cacheindocs/WORKSPACES.mdStep timing.docs/ACCELERATION.mdgains Reading the skip counts: hit_rate is per forward, and a 0-skip run is not bit-identical to an uncached one (the P2 finding), so judge quality against an uncached run.Deviations from the plan
segmenton the event. On a chain segment,cache_statsalso carriessegment(e.g.segment 1/3), because a chain emits one event per call. Plain steps have nosegmentkey.Pipeline.name, which is the model name.cache_stats.pymodule. The counter lives intiming.pyand the API field is typed as an opendict, not a model of its own. dw's architecture ratchet refused a new module andapi_models.pycrossing its 1,100-line ceiling. Behaviour and shape are unchanged.breaking-change.Gate: ruff is clean. Full pytest: 14,328 passed and 17 failed. The 17 (
test_plugin_skillsKandinsky ×5,test_examples×9,test_component_type_errors×3) fail the same way onorigin/developwithout this change.Hardware: the mini-ai cases can run here with any first_block/mag workflow. The H3-on-lem acceptance case needs CUDA/lem: please hand that one on.
— lead agent, model
claude-opus-5-5, provideranthropicdkackman commentedon Oct 14, 2026
Architecture review queued by the loop driver: this hand-off changed
dw/ordw_mcp/ondevelop(8b9dad7f2d..87d5f0358e: dw/pipeline_processors/components.py dw/pipeline_processors/pipeline.py dw/pipeline_processors/timing.py dw/server/api_models.py dw_mcp/tools_jobs.py). The reviewer checks it againstdocs/ARCHITECTURE.mdbefore the tester verifies.9 remaining items
dkackman commentedon Oct 14, 2026
Bounce fixed: the
cache_statsevent now reaches the job. Lead: claude-opus-5-5 / anthropic.Merged to
developate37f2fbd(commit0ead3b7f, branchfeat/867-B-cache-stats-wire). The driver deploysdevelopto mini-ai after this session; I did not deploy. It needs the server restart, because the fix is in the worker protocol.Cause. The worker did emit the event;
timing.cachewas filled from the same call.Progress.to_wireputs a run event's keys beside the wire message's own"type": "progress".cache_statscarries the cache'stype("first_block","mag"), and that key overwrote the message type. The server'sparse_replythen saw an unknown message type and dropped it, so it never reached the job's events. The earlier unit tests fed the event to a stand-in context, so they never crossed the wire.Fix (
dw/worker_protocol.py,Progress):typekey crosses the wire asevent_type, andfrom_wirerestores it astype.cache_statsevent carriestype, as the plan says.Tests:
tests/test_cache_stats.py::test_the_event_crosses_the_worker_wire_with_its_cache_typecovers the real path. A tiny real FluxPipeline with CFG and first_block runs under a realRunContext, whose sink sends each event throughProgress(...).to_wire()andparse_reply. Exactly onecache_statsarrives as aProgress, withtype: "first_block",stepandforwards == 12.tests/test_worker_messages.pyhas a newProgressexample carrying atypekey, checked for wire form, round trip and parse.ad2c918cand pass with the fix.Map: I extended the Worker protocol row of
docs/ARCHITECTURE.mdwith theevent_typerule and the new test. This change touchesdw/, so the arch review re-queues.Checks:
ruffis clean. The full suite has 14336 passed and 17 failed. The 17 are the same Kandinsky-6 catalog and skill failures as in the last hand-off; they also fail ondevelopwithout this change.For the verify:
get_job_events(kinds=["cache_stats"]).Deviations from the plan: none beyond those in the first hand-off.
dkackman commentedon Oct 14, 2026
Architecture review queued by the loop driver: this hand-off changed
dw/ordw_mcp/ondevelop(6950277743..e37f2fbdc8: dw/worker_protocol.py). The reviewer checks it againstdocs/ARCHITECTURE.mdbefore the tester verifies.dkackman commentedon Oct 14, 2026
Architecture review: passed. Reviewer: claude-opus-5-5 / anthropic.
I read
git diff 69502777..e37f2fbdunderdw/anddw_mcp/, limited to the #886 commit0ead3b7f. It changes onlydw/worker_protocol.py(Progress.to_wire/from_wire), plusdocs/ARCHITECTURE.mdand tests. I checked it againstdocs/ARCHITECTURE.mdand item 3 ofdocs/stabilization/harness/stage-c-guardrails.md.dw/worker_protocol.py. Every progress reply indw/anddw_mcp/is encoded throughProgress(...).to_wire()(dw/worker.py:475,:843). Nothing else builds the wire dict, so no second encoder still spreadstype.event_typedoesn't collide with any event key. Indw/events.py:79it is onlyemit's positional parameter name, so it can't appear in**data.pyproject.tomlcovers it.type→event_type→typerule and liststests/test_cache_stats.py::test_the_event_crosses_the_worker_wire_with_its_cache_type. That test exists attests/test_cache_stats.py:196. It adds no new owner.Progress.EVENT_TYPE_KEY, in the owning module. NoCLAUDE.mdchanged.Nothing was waived (the issue has no
arch-approvedlabel). Labels: removedarch-review. Next is the tester verify.dkackman commentedon Oct 14, 2026
Verified on mps (mini-ai.lan). Tester: claude-opus-5-5 / anthropic. Server: develop @ e37f2fb, workspace
regression-complete.Base: the same inline
FluxPipelineI used last time (black-forest-labs/FLUX.1-devbf16, 512×512, 20 steps,guidance_scale3.5, seed 42, stepmain). C-F439's SD 1.5 base warns "not applied" forfirst_block.The bounce is fixed:
get_job_events(kinds=["cache_stats"])now returns the event, withtypepreserved.first_block0.5timing.cache={first_block, 0.5, forwards 20, computed 5, skipped 15, hit_rate 0.75},passes20, no warnings. Exactly onecache_statsevent (seq 37),step: "main", same fields. Passfirst_block0.001forwards 20, computed 20, skipped 0, hit_rate 0.0. One matching event. Passmainwith 0.5 and 5/15/0.75, andsecondwith 0.3 and 6/14/0.7. Each matches its own entry'stiming.cache. Passtiminghas nocachekey. Nocache_statsevent, and nocache_stats_unavailable(the only warning is aphase_stall). Passmag(mag_ratios: "flux")threshold 0.06, forwards 20, computed 10, skipped 10, hit_rate 0.5, plusnum_inference_steps,max_skip_steps,retention_ratio,calibrate. One matching event. Passtext_kvtaylorseer(cache_interval5)cache_interval: 5,max_order: 1. One matching event. Nocache_stats_unavailable; the only warning is validate'staylorseer_state_unestimated. PassfasterIndexError: tuple index out of rangeatdiffusers/hooks/faster_cache.py:470(out.chunk(2, dim=0)[1]).Why I'm not counting the
fasterrun against this stage:fastertype-only, which installs no counter.validate_workflowacceptsfasteron this pipeline, a gap that predates stage 2. I filed it as validate_workflow accepts cache type 'faster' on FluxPipeline (FLUX.1-dev); run fails in diffusers faster_cache with IndexError #895.fasteris "not applied" with a warning and gets nocachekey (job 33e2f17e6e5c). That is consistent with C-F442.Acceptance intent from the issue: a high threshold skips, a near-zero threshold doesn't, one event per call, no cache gives no key and no event,
magcounts, andtext_kvis type-only with no warning. All hold on this server. H3 is left to lem.I removed the
pending: #886lines from C-F439 through C-F445 inregression-suite-complete.md, so they are now ordinary cases. Every output above has been deleted.Labels: added
status:verifiedandverified-on:mps, removedstatus:fixed-pending-verify, and closed the issue as completed. CUDA hasn't run this yet.