Skip to content

DL4: a custom function's preparation runs before the server reports it usable - #1235

Draft
trissim wants to merge 12 commits into
mainfrom
refactor/DL4-custom-function-preparation
Draft

trissim wants to merge 12 commits into
mainfrom
refactor/DL4-custom-function-preparation

Conversation

@trissim

@trissim trissim commented Oct 12, 2026

Copy link
Copy Markdown
Collaborator

DL4 · draft · guards 3/3 new (5/5 in the file) · production −21 +107 · tests −0 +423

Gap 5 of the Cellpose/N2V2 receipt (#1230): a custom function's declared preparation ran in the first compile after readiness, and workers loaded the resource again on later runs. Receipt: docs/refactor/consolidation-20261009/DL4-custom-function-preparation.md.

What changed

  • A process that prepared its catalogue for serving prepares a custom source when it loads it, through the same CallablePreparation.prepare_for_serving catalogued functions use (CallablePreparation.prepare_published, called from project_declaration in the custom-function manager). Authoring processes load without preparing.
  • At server start the persisted sources are therefore prepared before readiness and before the workers fork. A source stored while the server is ready is prepared when the server loads it: by the registration request, or by the catalogue-preparation request, during which the server reports pending.
  • A preparation that raises is a CustomFunctionLoadFailure of that source alone (file, line, error); a registration that fails publishes nothing.
  • After the server prepares something late it forks its idle workers again and replaces leased ones on return (PreparedForkWorkerLaneRunner.replace_with_current_forks), so fork workers inherit the loaded resource.

Measured (Cellpose nuclei, CPU, 4 BBBC039 fields, 2 workers, cold weights)

main, fork branch, fork main, spawn branch, spawn
Weight download + model load first compile, 4.86 s preparation, 6.35 s first compile, 3.23 s preparation, 5.63 s
First compile / second compile 5.46 s / 0.09 s 0.21 s / 0.09 s 3.58 s / 0.07 s 0.14 s / 0.11 s
Model loads per worker, run 1 / run 2 1 / 1 0 / 0 1 / 1 1 / 1

Not done (details in the receipt)

  1. Spawn workers (the default start method, and every GPU step) still start per execution and load per run.
  2. Preparation that depends on a parameter (which model): proposal and owner decision in the receipt, not implemented.
  3. The GUI editor does not tell a running server about a new source, so that path still prepares in the first compile that imports it.
  4. No GPU run.

Gates

  • guardrails_local.sh: base a51f4eab135b8b56f55f048391497fa62015b41d head b182fefef392a6006c9352d8ae1306e78401183f guardrails rc=0
  • smoke_local.sh: smoke pytest exit 0 (335 s): 7 passed, 2 warnings in 322.33s (0:05:22), smoke gui exit 0 (51 s), smoke rc=0
  • Guard file tests/integration/test_no_jit_after_readiness.py: 5 passed in 205.22s (0:03:25)
  • Touched unit tests: 180 passed, 3 warnings in 22.07s
  • verify_branch.sh (full suite) and official30 parity: queued, not yet reported; results will be added to the receipt.

🤖 Generated with Claude Code

trissim and others added 6 commits October 12, 2026 14:54
… workers forked again after a late registration

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…class; a prepared runner builds itself

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…alue, loaded per pipeline in its workers

A hook may name parameters of its function. The acquire hook is called once
per value of each parameter's declared type during catalogue preparation; a
compile selecting a value that was not acquired is refused, naming the
acquired values. The load hook runs in the workers of a pipeline, with the
values the compiled step selects, in a 'prepare this pipeline' job that is
separate from compile and execute. An execution that was not prepared runs
the same load phase before any lane starts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ted server

The manager's three operations tell connected_servers, which sends each
running server the change (one family: created, edited, deleted; the member is
the wire payload and carries its effect in the server) and asks for its state
until it stops reporting pending. Until then the manager lists the function as
preparing; a source the server could not acquire is listed as failed to load
with the server's error. A compile naming a deleted function is refused by
name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@trissim
trissim force-pushed the refactor/DL4-custom-function-preparation branch from 6119384 to ee25394 Compare October 12, 2026 20:37
trissim and others added 6 commits October 12, 2026 16:47
…hmark prepares before its clock

The implicit load phase is a field of the execution record
(BundleExecutionExtras.load_phase: started_at, ended_at, loads), written to the
results summary; benchmark/timing.py reads it and subtracts it from the
execution's seconds. run_compiled_pipeline is compile + execute; the prepare
job is its own call (prepare_pipeline), which well_throughput_scaling sends
before total_seconds starts and reports as prepare_seconds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A catalogue projection is keyed by the FunctionMetadata it was derived from,
not by the identity of the registry's dict. A custom-function change no longer
drops the registries' loaded metadata (custom functions are never in a
library's disk catalogue); only the unified lookup is derived again. Measured
in-process, 269 functions: the projection after a registration 6.15 s / 270
entries before, 0.05 s / 1 entry after.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… request or the next pipeline preparation

A serving process tries every declared value: a function with at least one
acquired value is published and lists the values it lacks with their causes.
The retry is the fourth member of the change family (CustomFunctionRetried),
exposed in the manager dialog and through MCP, and the server retries the
values a pipeline selects when it prepares that pipeline, before any worker
loads. Listing, loading and compiling never retry.

Also: the custom-catalogue state travels in the typed control response; a
connected server is identified by its address; load= is typed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… accelerator_workers limit

Unfinished: the agent building this stopped at a usage limit. KeptWorkers
(openhcs/runtime/kept_workers.py) owns the forked and the self-started workers
and states the lifetime rule; GlobalPipelineConfig.accelerator_workers (default
1) is declared. Six unit files pass (131 tests); the real-server spawn driver
and its tests were not observed passing. See the receipt's 'what remains'.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…device contract for hooks, what remains

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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