Skip to content

Commit 7ac3409

Browse files
authored
Merge pull request #49 from ausimian/docs-async-worker
Refresh concurrency docs for async NIF dispatch
2 parents 13d9926 + f4ff5b8 commit 7ac3409

2 files changed

Lines changed: 17 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,12 @@ Initial release. See the git history for per-milestone detail.
5353
Qwen3-0.6B (dense and quantized), ViT-base, and Whisper-tiny,
5454
pinned against HuggingFace reference values.
5555
- **Worker-thread dispatch.** Each MLX stream is owned by a
56-
dedicated OS thread; NIFs hand work to the worker via a
57-
promise/future, keeping per-thread Metal `CommandEncoder` state
58-
consistent across BEAM scheduler migration.
56+
dedicated OS thread. NIFs enqueue work on the worker and return
57+
immediately; the worker posts the result back to the caller via
58+
`enif_send`, and the public wrapper awaits it with `receive`. No
59+
BEAM scheduler (regular or dirty) blocks on MLX work, and the
60+
per-thread Metal `CommandEncoder` state stays consistent regardless
61+
of how the BEAM migrates Elixir processes between schedulers.
5962
- **Vendored MLX build.** MLX is built from source via cmake from
6063
`vendor/mlx` (git submodule); no prebuilt download. Build cache
6164
keyed on the submodule SHA under `~/Library/Caches/emily/`.

README.md

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -206,11 +206,17 @@ and direct MLX round-trips, but most users should go through Nx.
206206

207207
MLX dispatches GPU work through Metal command queues. Emily owns one
208208
worker thread per command queue; each worker is a dedicated OS thread
209-
that runs the MLX ops on behalf of BEAM schedulers. NIFs hand their
210-
work to a worker via a `run_sync` promise (blocks the caller for
211-
~1–10 µs) rather than executing on the scheduler thread directly,
212-
which keeps MLX's per-thread `CommandEncoder` consistent and lets
213-
the BEAM migrate Elixir processes freely.
209+
that runs the MLX ops on behalf of BEAM processes. NIFs return
210+
immediately after enqueueing their work on a worker: the worker runs
211+
the op, then posts `{ref, {:ok, result}}` back to the caller via
212+
`enif_send`, and the caller's public wrapper awaits that message with
213+
a plain `receive`. No BEAM scheduler (regular or dirty) blocks on MLX
214+
work — callers see the same synchronous semantics as before, but the
215+
scheduler is free to run other processes while the GPU is busy.
216+
217+
Because the MLX stream is pinned to its worker thread, MLX's
218+
per-thread `CommandEncoder` state stays consistent regardless of how
219+
the BEAM migrates Elixir processes between schedulers.
214220

215221
By default, every op uses the **default worker** owned by the
216222
`Emily.MlxStream.Default` GenServer under the application supervisor.

0 commit comments

Comments
 (0)