Hi antirez,
Thanks for DwarfStar. We have been running the two-machine pipeline-parallel setup as a
nightly batch for long-document summarization since early July, and it has been reliable.
This is a field report from a machine class that is below the documented target (the README
says Metal's primary target is 96 GB or more). We upgraded from 80ebbc3 to the current
main line and hit two issues that only appear in this configuration. Both have workarounds,
so nothing here is blocking us — we are posting mainly because 64 GB machines are probably
invisible in your test matrix, and the second issue may affect future memory-budget
decisions.
Environment
- 2x MacBook Pro M1 Max, 64 GB each, macOS Tahoe 26, connected by a Thunderbolt 4 cable
- coordinator
--layers 0:23 on one machine, worker --layers 24:output on the other
- Model:
q2-q4-imatrix (last 6 layers Q4K), fully resident on both sides, no --ssd-streaming
--ctx 65536, --dist-activation-bits 32, disk KV cache enabled
- Metal working-set ceiling on this machine is about 51.3 GiB; the resident model plus KV and
buffers is 47.05 GiB, so roughly 4.3 GiB is left for prefill working memory
Results after the upgrade (both workarounds applied)
Measured against 80ebbc3 with identical scripts, same prompts, same TB link. decode is
computed as completion_tokens / (wall - prefill); for the coding tasks completion_tokens
was identical between the two builds, so those rows are a like-for-like comparison.
| Workload |
80ebbc3 |
427e281 + workarounds |
| Summarization prefill (17.5k / 27.4k / 15.6k tok) |
148.7 / 163.0 / 147.1 t/s |
155.3 / 162.7 / 153.7 t/s |
| Summarization decode |
9.60 / 9.67 t/s |
10.03 / 10.07 t/s (+4.5% / +4.1%) |
| Code generation decode (401-2,977 tok prompts) |
10.83-12.63 t/s |
11.00-12.95 t/s (+1.0-4.1%) |
Output quality is unchanged; if anything the new build cites figure and page numbers slightly
more often. Thank you for the kernel work — it is a real improvement on pre-M5 hardware too.
Issue 1: distributed coordinator segfaults at startup
A coordinator started with --layers 0:23 crashes before listening:
EXC_BAD_ACCESS / SIGSEGV / KERN_INVALID_ADDRESS at 0x0000000000000020
metal_graph_alloc_raw_cap ds4.c:16828
ds4_session_create ds4.c:56585
main ds4_server.c:12986
ds4.c:16828 is:
const uint64_t vocab_dim = weights->output->dim[1];
In 80ebbc3 the same line in the same function had a guard:
const uint64_t vocab_dim = weights->output ? weights->output->dim[1] : DS4_N_VOCAB;
The startup log shows why this now matters — the coordinator stopped mapping the output tensor:
80ebbc3: restricting metal model map to layers 0:23+output (52 spans, 45.72 GiB tensor span)
current: restricting metal model map to layers 0:23 (49 spans, 45.20 GiB tensor span)
So weights->output is NULL on a coordinator that does not own the output head. Single-process
runs always own it, which is presumably why this is not caught in CI. Restoring the old guard
is enough; vocab_dim only sizes output_logits_elems, which a coordinator that does not
compute logits never uses.
Issue 2: long prefill hits Metal OOM, bisected to 427e281
With the coordinator running, any prompt above roughly 1.4k-3k tokens fails:
0725 19:41:44 ds4-server: chat ctx=0..17562:17562 prompt start
ds4: Metal command batch failed: Insufficient Memory (00000008:kIOGPUCommandBufferCallbackErrorOutOfMemory)
The server survives and returns HTTP 500. It appears on both the coordinator and the worker.
At around 3k tokens the failure mode is quieter and more dangerous: HTTP 200 with
gen=0 finish=stop, i.e. an empty completion rather than an error.
What we checked:
80ebbc3 runs the same 27,434-token prompts with zero Insufficient Memory in its logs
- Static allocation is identical between builds (
planned 46.56 GiB, prefill_cap=4096,
raw_kv_rows=4352), so this is prefill working memory, not startup residency
DS4_METAL_TRACE_ALLOCS records nothing between prompt start and the OOM — the trace
count is identical before and after the request. Consistent with the error being a command
buffer callback rather than an allocation failure
--prefill-chunk 1024 does not help (it lowers planned to 45.93 GiB and raw_kv_rows to
1280, but the OOM is unchanged)
DS4_METAL_EXACT_VIEW_CACHE_GIB=2 with _PROFILE=1 produces no evict lines at all, so the
exact view cache is not involved
Bisect over 80ebbc3..0a7ad77, testing a 17,562-token prompt at each step:
| Commit |
Long prompt |
80ebbc3 |
ok |
e0824dd (427e281^) |
ok, prefill 152.8 t/s |
427e281 |
OOM |
efdadd4, 0a7ad77 |
OOM |
Within 427e281, disabling the contiguous copy kernels fixes it, and it is specifically the
F16 to F16 one:
| Configuration |
Long prompt |
| none disabled |
OOM |
DS4_METAL_DISABLE_CONTIG_F32_F16_COPY=1 |
OOM |
DS4_METAL_DISABLE_CONTIG_F16_F16_COPY=1 |
ok |
| both disabled |
ok |
We now run with DS4_METAL_DISABLE_CONTIG_F16_F16_COPY=1 only. Keeping the F32 to F16 path
enabled is worth about 1.6 points of decode throughput, so the cost of the workaround is small.
Note on 64 GB machines
We understand 64 GB is below the documented target, and we are not asking for support for it.
The reason we are writing this up is that the failure is silent in a specific way: startup
succeeds, short prompts work, and only prompts past a threshold fail — with an empty
completion rather than an error in part of the range. On a machine with 30-70 GiB of headroom
the same change is invisible.
If future kernel work has a rough sense of how much prefill working memory it adds, that
number alone would let people on smaller machines predict whether an upgrade is safe.
Reproduction
# coordinator
./ds4-server --ctx 65536 -m ds4flash.gguf --role coordinator --layers 0:23 \
--listen 10.99.0.2 9000 --host 0.0.0.0 --port 8000 --dist-activation-bits 32
# worker
./ds4 --role worker --layers 24:output --coordinator 10.99.0.2 9000 --ctx 65536 -m ds4flash.gguf
# then POST a ~17k token prompt to /v1/chat/completions
Issue 1 reproduces at coordinator startup with no request at all.
Full disclosure: the investigation above (bisect, log analysis, workaround search) was carried
out with Claude Code, and all numbers are measured on our own hardware.
Happy to answer questions or share more log detail. Longer experiment series are harder for us,
since these two machines run a scheduled workload every night, but short checks are fine.
Hi antirez,
Thanks for DwarfStar. We have been running the two-machine pipeline-parallel setup as a
nightly batch for long-document summarization since early July, and it has been reliable.
This is a field report from a machine class that is below the documented target (the README
says Metal's primary target is 96 GB or more). We upgraded from
80ebbc3to the currentmain line and hit two issues that only appear in this configuration. Both have workarounds,
so nothing here is blocking us — we are posting mainly because 64 GB machines are probably
invisible in your test matrix, and the second issue may affect future memory-budget
decisions.
Environment
--layers 0:23on one machine, worker--layers 24:outputon the otherq2-q4-imatrix(last 6 layers Q4K), fully resident on both sides, no--ssd-streaming--ctx 65536,--dist-activation-bits 32, disk KV cache enabledbuffers is 47.05 GiB, so roughly 4.3 GiB is left for prefill working memory
Results after the upgrade (both workarounds applied)
Measured against
80ebbc3with identical scripts, same prompts, same TB link. decode iscomputed as
completion_tokens / (wall - prefill); for the coding taskscompletion_tokenswas identical between the two builds, so those rows are a like-for-like comparison.
Output quality is unchanged; if anything the new build cites figure and page numbers slightly
more often. Thank you for the kernel work — it is a real improvement on pre-M5 hardware too.
Issue 1: distributed coordinator segfaults at startup
A coordinator started with
--layers 0:23crashes beforelistening:ds4.c:16828is:In
80ebbc3the same line in the same function had a guard:The startup log shows why this now matters — the coordinator stopped mapping the output tensor:
So
weights->outputis NULL on a coordinator that does not own the output head. Single-processruns always own it, which is presumably why this is not caught in CI. Restoring the old guard
is enough;
vocab_dimonly sizesoutput_logits_elems, which a coordinator that does notcompute logits never uses.
Issue 2: long prefill hits Metal OOM, bisected to 427e281
With the coordinator running, any prompt above roughly 1.4k-3k tokens fails:
The server survives and returns HTTP 500. It appears on both the coordinator and the worker.
At around 3k tokens the failure mode is quieter and more dangerous: HTTP 200 with
gen=0 finish=stop, i.e. an empty completion rather than an error.What we checked:
80ebbc3runs the same 27,434-token prompts with zeroInsufficient Memoryin its logsplanned 46.56 GiB,prefill_cap=4096,raw_kv_rows=4352), so this is prefill working memory, not startup residencyDS4_METAL_TRACE_ALLOCSrecords nothing betweenprompt startand the OOM — the tracecount is identical before and after the request. Consistent with the error being a command
buffer callback rather than an allocation failure
--prefill-chunk 1024does not help (it lowersplannedto 45.93 GiB andraw_kv_rowsto1280, but the OOM is unchanged)
DS4_METAL_EXACT_VIEW_CACHE_GIB=2with_PROFILE=1produces no evict lines at all, so theexact view cache is not involved
Bisect over
80ebbc3..0a7ad77, testing a 17,562-token prompt at each step:80ebbc3e0824dd(427e281^)427e281efdadd4,0a7ad77Within
427e281, disabling the contiguous copy kernels fixes it, and it is specifically theF16 to F16 one:
DS4_METAL_DISABLE_CONTIG_F32_F16_COPY=1DS4_METAL_DISABLE_CONTIG_F16_F16_COPY=1We now run with
DS4_METAL_DISABLE_CONTIG_F16_F16_COPY=1only. Keeping the F32 to F16 pathenabled is worth about 1.6 points of decode throughput, so the cost of the workaround is small.
Note on 64 GB machines
We understand 64 GB is below the documented target, and we are not asking for support for it.
The reason we are writing this up is that the failure is silent in a specific way: startup
succeeds, short prompts work, and only prompts past a threshold fail — with an empty
completion rather than an error in part of the range. On a machine with 30-70 GiB of headroom
the same change is invisible.
If future kernel work has a rough sense of how much prefill working memory it adds, that
number alone would let people on smaller machines predict whether an upgrade is safe.
Reproduction
Issue 1 reproduces at coordinator startup with no request at all.
Full disclosure: the investigation above (bisect, log analysis, workaround search) was carried
out with Claude Code, and all numbers are measured on our own hardware.
Happy to answer questions or share more log detail. Longer experiment series are harder for us,
since these two machines run a scheduled workload every night, but short checks are fine.