Skip to content

Don't reuse a Metal buffer while a command buffer is still using it#3868

Open
shubhxho wants to merge 1 commit into
ml-explore:mainfrom
shubhxho:candidate-metal-defer-buffer-recycle
Open

Don't reuse a Metal buffer while a command buffer is still using it#3868
shubhxho wants to merge 1 commit into
ml-explore:mainfrom
shubhxho:candidate-metal-defer-buffer-recycle

Conversation

@shubhxho

@shubhxho shubhxho commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Under continuous batching with async_eval, the small int32 KV-cache bookkeeping arrays (offset / left_padding) sometimes come back as garbage that looks like bf16 bits in int32 slots -- e.g. 16256 (0x3F80, bf16 1.0). A buffer is being reused while a kernel is still writing it. Full write-up and repro in the linked issue; likely related to #3346.

MetalAllocator::free returns a buffer to the reuse pool without knowing which command buffer last used it. Fine on one stream (in order), but async_eval adds a second stream, so a buffer freed on one can be reused on the other before the first kernel finishes.

Fix: count how many in-flight command buffers reference each buffer -- marked at end_encoding, retired from the completion handler. free() holds a still-in-use buffer aside and recycles it once the work is done.

@shubhxho
shubhxho force-pushed the candidate-metal-defer-buffer-recycle branch from 0b0fe8d to 9650550 Compare July 17, 2026 22:59
@shubhxho shubhxho changed the title Defer Metal buffer recycle while referenced by an in-flight command buffer Don't reuse a Metal buffer while a command buffer is still using it Jul 17, 2026
@shubhxho
shubhxho force-pushed the candidate-metal-defer-buffer-recycle branch from 9650550 to 8903a93 Compare July 17, 2026 23:01
Under continuous batching with async_eval, small int32 KV-cache bookkeeping
arrays (offset/left_padding) sometimes come back as garbage that looks like
bf16 bits, e.g. 16256 (0x3F80) in an int32 slot -- a buffer reused while a
kernel is still writing it. free() recycles buffers without knowing which
command buffer last used them, which is fine in order on one stream but not
once async_eval adds a second.

Fix: count in-flight command buffers per buffer (marked at end_encoding,
retired from the completion handler). free() holds a still-in-use buffer aside
and recycles it once the work is done.
@shubhxho
shubhxho force-pushed the candidate-metal-defer-buffer-recycle branch from 8903a93 to e5a86d1 Compare July 17, 2026 23:05
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