Skip to content

Add asynchronous workers#338

Open
nefrathenrici wants to merge 1 commit into
mainfrom
ne/async
Open

Add asynchronous workers#338
nefrathenrici wants to merge 1 commit into
mainfrom
ne/async

Conversation

@nefrathenrici

@nefrathenrici nefrathenrici commented Jul 14, 2026

Copy link
Copy Markdown
Member

This PR makes workers individually submitted and self-registering. Each worker is its own scheduler allocation, adds itself to a process-wide pool once it has started and loaded model code, and the scheduler loop dispatches forward-model runs to workers as they become available. Closes #296.

Changes

  • Updated add_workers to be asynchronous. This is the new intended way to use the WorkerBackend, though addprocs still works.
  • Add global, self-filling worker pool GLOBAL_WORKER_POOL. This pool is guarded by POOL_LOCK
  • Distributed.launch now submits n separate single-process allocations instead of one job array. poll_files_for_worker_startup now polls all worker signal files, since they spin up separately now.
  • Added @worker_setup to replace @everywhere, which required all workers to be already registered. The new macro records setup expressions on the main process and replays them on every worker as it joins.
  • run_iteration now starts with a possibly empty pool and waits for workers. Added EMPTY_POOL_TIMEOUT = 7200 so that jobs don't just hang indefinitely, maybe we can make this configurable.
  • Added cancel_worker_jobs to cancel all workers. This now runs in an atexit hook.

@nefrathenrici
nefrathenrici force-pushed the ne/async branch 2 times, most recently from dc4a20f to f96a37e Compare July 15, 2026 18:33
@nefrathenrici
nefrathenrici marked this pull request as ready for review July 15, 2026 19:27
@nefrathenrici
nefrathenrici force-pushed the ne/async branch 3 times, most recently from dedd0c1 to 5dde6d5 Compare July 17, 2026 02:34
@nefrathenrici
nefrathenrici requested a review from ph-kev July 17, 2026 18:33
Comment thread docs/src/backends.md
Comment on lines +32 to +38
iteration. [`add_workers`](@ref) submits each worker as an individual
scheduler allocation, so on a busy cluster the scheduler can start them
independently as resources free up rather than needing to place every worker
at once. Each worker adds itself to a global pool once it has started and
loaded its code, and forward model runs are dispatched to workers as they
become available. Use [`@worker_setup`](@ref) instead of `@everywhere` to load
model code so that workers joining later are initialized correctly. Because

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this note, I would prefer it talks about the differences between the backends. A lot of content in this note is talking about the HPC Cluster Backends and the relevant functions. Could this be moved into the section for "HPC Cluster Backends"? Maybe it is worth another note about talking about the difference between @everywhere and @worker_setup.

while !all(id -> id in pool.workers, ids)
(time() - tstart) > timeout &&
error("Workers did not initialize within $(timeout)s")
sleep(0.5)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a better solution than busy waiting? I don't think it need to be addressed in this PR, but there is probably a better solution.

Comment thread src/backends/workers.jl
Comment on lines +138 to +140
on any worker that joins later. Use this instead of `@everywhere` to load model
code for an asynchronous [`WorkerBackend`](@ref) calibration, where workers may
join after the calibration has started.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add more emphasis or use an admonition? I don't think this emphasize the importance of using this macro (e.g. what can happen if you don't?).

Comment thread src/backends/workers.jl
Comment on lines +142 to +144
`using`/`import` statements run on the master first (to precompile once), and
the current source path is propagated so relative `include` works on workers.
As with `@everywhere`, local variables must be interpolated with `\$`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I am not too familiar with the teminology, but I don't understand what master is here. Is it the same as the main process or is this specific to the Distributed.jl?

Comment thread src/backends/workers.jl
Comment on lines +227 to +228
# Run `cmd`, discarding its output.
_run_quiet(cmd) = run(pipeline(cmd; stdout = devnull, stderr = devnull))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When this run, is the output of each of the forward model still saved in the logs?

Comment thread src/backends/workers.jl

# Workers that have connected but are still loading code: present in `workers()`
# but not yet schedulable. Keeps `default_worker_pool` from pooling them early.
const INITIALIZING = Set{Int}()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a nit pick, but I don't like this variable name being a verb or action.

Comment thread src/backends/workers.jl
# Generous bound (seconds) on how long a calibration will wait on an empty
# worker pool before erroring, so an asynchronous calibration cannot hang
# forever when no workers ever start.
const EMPTY_POOL_TIMEOUT = 7200

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be configurable by the user?

Comment thread src/backends/workers.jl
function default_worker_pool()
lock(POOL_LOCK) do
for id in workers()
id == 1 && continue

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment about the id == 1 case? I think this is the ID of the main worker, but I am not confident.

Comment thread src/backends/workers.jl
Comment on lines +203 to +204
deregisters (e.g. walltime expiry or crash). Any stale entry left in the pool's
channel is filtered out at `take!` time.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you clarify the last sentence? I am not sure what context this is referring to.

Comment thread src/calibration.jl
Comment on lines +431 to +433
throw(
ErrorException(
"No workers available for $(round(Int, t_empty))s \

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this better than using error(msg)?

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.

Asynchronously request for workers for WorkerBackend

2 participants