Repository navigation
Export cancel-byte callbacks for llama_decode and clip encode - #141
Merged
Merged
Conversation
Add three exports so a caller-owned cancel byte can stop native work in flight: - llama_dart_cancel_flag_abort_callback: a ggml_abort_callback for llama_set_abort_callback that reads the byte. A decode or encode on the CPU backend stops after the current node and returns 2. - llama_dart_mtmd_eval_callback: a cb_eval for mtmd contexts. Inside a TTS step it answers as llama_dart_tts_eval_callback; inside a cancellable encode it chunks the clip graph after attention nodes and ends the compute once the byte is nonzero. - llama_dart_mtmd_encode_chunk_cancellable: mtmd_encode_chunk with a cancel byte, returning LLAMA_DART_MTMD_ENCODE_CANCELLED (2) when the byte stopped or prevented the encode. The existing exports are unchanged. Fixes #88.
The CPU backend checks the abort callback only after a node that computes something, so a view node after the store still completes. On post-v0.4.0 the node after the store is one, and the exact node count failed there.
# Conflicts: # .github/workflows/validate_wrapper.yml # CMakeLists.txt # src/llama_dart_wrapper.h
Upstream cannot roll recurrent memory back, so an aborted decode leaves a hybrid or recurrent sequence undefined; a TTS context stopped in its prompt step fails instead of cancelling.
Owner
Author
|
Independent audit at
P3s are tracked on #88:
Evidence: |
leehack
marked this pull request as ready for review
October 30, 2026 02:37
This was referenced Oct 30, 2026
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three new exports let llamadart's caller-owned cancel byte stop native work in flight, instead of waiting for the current
llama_decodeor clip encode to finish. Fixes #88 (which absorbed #89).llama_dart_cancel_flag_abort_callback(void *data)llama_set_abort_callback(ctx, it, byte)llama_decode/llama_encodeon the CPU backend, after the current node; returns 2llama_dart_mtmd_eval_callback(tensor, ask, user_data)mtmd_context_params.cb_eval, replacingllama_dart_tts_eval_callbackllama_dart_mtmd_encode_chunk_cancellable(ctx, chunk, flag)LLAMA_DART_MTMD_ENCODE_CANCELLED(2) when the byte stopped the encode or kept it from startingThis change only adds exports. The existing required exports and their behaviour are unchanged.
validate_exports.pylists the new ones after #139's, so the required count goes from 93 to 96. llamadart starts using them in 0.18.Clip encode: no upstream abort hook
At v0.7.0 (pinned) and v0.8.0 (
8642541b), mtmd and clip expose no abort callback. Clip makes its own backends, sollama_set_abort_callbacknever reaches them. The only hook iscb_eval, so no upstream patch is needed. A thread-local scope that lives only for the call ties the call's byte to the callback, so a long-lived mtmd context never holds a pointer to a per-request byte.Where the graph is chunked. Splitting the graph at a node splits any backend fusion that spans that node. Metal, Vulkan and CUDA fuse a
MUL_MATwith the biasADDafter it, so the TTS callback's after-MUL_MATboundaries would change GPU output. Boundaries here fall only afterFLASH_ATTN_EXT/SOFT_MAX, once per 2.5e9 work units (the TTS budget).Metal output. Metal fuses only within one command buffer, and its node ranges depend on the size of the graph it computes. So on Metal any chunking still changes which ops fuse. Qwen3-ASR's chunked encode differs from the unchunked one by 1.3% of the largest output magnitude. The difference goes to 0 with
GGML_METAL_FUSION_DISABLE=1. CPU output is bit-identical. The difference is documented in the README.Measured (M4 Max, pinned v0.7.0,
tests/cancel_flag_test.cpp mtmd-encode)Medians of 5. Latency runs from the byte store to the return. Host load average was 13–90 from parallel lanes, so the CPU latency spread is wide.
After a cancel, the next encode on the same mtmd context returns 0 with the same output as before the cancel.
Teardown and barrier
llama_dart_mtmd_encode_chunk_cancellablehas the exception barrier and is a call in flight (llama_dart_exit_call), likellama_dart_mtmd_encode_chunk.llama_set_abort_callback(ctx, NULL, NULL)returns or the context is freed, and until the cancellable encode returns.Tests
tests/cancel_flag_test.cpp(newllamadart_cancel_flag_test):flag(every lane, MSVC included): the abort callback reads the byte; the eval callback asks for nothing outside a call; a byte set on entry returns 2 without touching the context; unit tests of the chunk/stop answers.decode(macOS and Linux, using the model the exit-teardown tests write): rc 2 with the byte set,seq_pos_max−1, and rc 0 again once it is cleared. A deterministic mid-decode stop: the store happens after node 3, the decode returns 2 and no computing node completes after the store. View nodes can still complete, because the CPU backend checks the abort callback only after a node that computes something. Clearing the callback restores full decodes.mtmd-encode(runs whenLLAMADART_EXIT_TEARDOWN_TEST_MODELand_MMPROJare set): produces the measurements above.Failing first: against stubs (abort returns false, the eval callback forwards to the TTS one, the cancellable call is the plain encode),
flag,decodeandmtmd-encodeall failed their assertions.Mutation check: 11 mutants, 11 killed.
MUL_MATask=falseignores cancelllama_dart_mtmd_eval_callbackllamadart_tts_smoke(Qwen3-TTS 0.6B, Metal) through the new callback: unmutated PASS with RMS and chunks identical to the TTS callback; mutant fails "chunk exceeds a quarter"Validation
decodescenario passes (the first push asserted an exact node count, which failed there).tools/build.py apple --target ios-device-arm64): builds and passesvalidate_exports.py(before the merge).origin/main(Harden the Linux arm64 ISA validator; guard the 24.04 pin and glibc floor #136, [0.18] Add opt-in LFM3-Audio speech output to the TTS wrapper #139, [0.17.1] Retire carried patch 0003 on llama.cpp v0.8.0 #140) at8e4d5c3, local macOS arm64 Debug:ctest60/60 on pinned v0.7.0 and 60/60 on v0.8.0 (8642541b);validate_exports.pypasses on both (96 exports, no__cxa_atexitimport). Themtmd-encodescenarios also pass on v0.8.0. The Debug CPU timings there are not comparable with the table above.ctest59/59;python3 -m unittest discover -s tests -p 'test_*.py'279 OK (1 skipped).Not covered
llama_set_abort_callbackreaches only the CPU backend upstream, so a GPU decode still runs to its end.MUL_MAT. At v0.7.0 Metal hasMUL_MAT_ADDfusion. TTS behaviour is unchanged by this PR; the comment is stale.CI results
7a4d6aawrapper-contract (post-v0.4.0)failed on the exact node-count assertion, fixed in1a782e0; run cancelled by the next push1a782e08e4d5c3(merge ofmain)HIP has no PR lane. The wrapper change there is host C++ only.