Skip to content

gguf: release weight mmap through buffer ownership - #47

Merged
CrispStrobe merged 1 commit into
mainfrom
fix/gguf-release-weight-buffer
Aug 13, 2026
Merged

gguf: release weight mmap through buffer ownership#47
CrispStrobe merged 1 commit into
mainfrom
fix/gguf-release-weight-buffer

Conversation

@CrispStrobe

Copy link
Copy Markdown
Owner

Companion to CrispASR #347. Adds the shared core_gguf::release_weight_buffer contract and mapping lifetime cleanup required by CrispASR shared sources (crisp_audio, crisp_punc, crisp_lid).

Validation on Apple Silicon:

  • Full CrispEmbed build: 364/364 targets
  • test-gguf-loader-mmap: pass; no-copy mmap path exercised, mapping count 1 while loaded / 0 after free
  • backend smoke: Metal device available and computation correct
  • clang-format and diff checks clean

This branch is the companion implementation already referenced by CrispASR #347.

…load

CrispASR is adding core_gguf::release_weight_buffer to its copy of this
loader and routing every weight-buffer teardown through it, including in the
three shared libraries CrispEmbed builds from ../CrispASR: crisp_audio,
crisp_punc and crisp_lid. Those sources compile against THIS header, so
without a matching entry point here all three fail to compile — verified, not
assumed: reverting this commit and building any target that pulls them in
gives "no member named 'release_weight_buffer' in namespace 'core_gguf'" at
crisp_audio/src/audio_tower.cpp:692 and crisp_lid/src/lid_cld3.cpp:807.

The point of the function upstream is that a no-copy buffer is a view onto
pages the backend does not own — ggml_backend_dev_buffer_from_host_ptr has no
deallocator parameter — so freeing the buffer alone leaves the weight file
mapped. Declaring the name without that behaviour would be worse than not
having it: the shared sources would compile here and silently mean something
different, which is the cross-repo drift the contract note in this header
already exists to prevent.

So the mapping is now keyed to the backend buffer, not only to the
WeightLoad. WeightLoad::mmap_addr/mmap_len stay as the caller-visible record
of a load; free_weights releases through release_weight_buffer and clears
them rather than unmapping a second time.

This repo has no instance of the leak today. The no-copy path is opt-in
(load_weights' try_mmap defaults to false) and its only two callers,
deepseek_ocr2 (DS_MMAP) and unlimited_ocr (UOCR_MMAP), keep the whole
WeightLoad and tear down through free_weights. But eleven models in src/ move
wl.buf into their own struct and free it directly, letting the WeightLoad and
its mmap_addr go: bidirlm_vision, fireredpunc, gliner_ner, glm_ocr, got_ocr,
internvl2_ocr, lfm2_embed (x2), pcs, qwen2vl_ocr (x2). Each is correct only
because its loader never maps, and each would leak the moment try_mmap were
added to it. Keying the region to the buffer means the release is correct for
both shapes; converting those eleven call sites is left out of this commit
deliberately, since none of them is wrong as written.

tests/test_gguf_loader_mmap.cpp now asks the kernel which regions still name
the weight file rather than inferring release from free_weights returning:
1 while loaded, 0 after. The positive control is not decoration — it caught
the first version of the probe comparing against /tmp while the kernel
reports /private/tmp, which would have made the absence check vacuous. Made
to fail on purpose by skipping the unmap inside release_weight_buffer:
"free_weights left 1 mapping(s)".

Build: crispembed and every test target build; the mmap test passes.
firered-punct-ab still fails to link on _fireredpunc_debug_token_ids, which
CrispEmbed's local src/fireredpunc.cpp defines and CrispASR's crisp_punc copy
does not — pre-existing drift in the punctuation pair, unrelated to this
change and invisible in CI, which has no sibling CrispASR checkout and so
builds the local copies. clang-format 18.1.8 clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@CrispStrobe
CrispStrobe merged commit 4ca16c0 into main Aug 13, 2026
11 checks passed
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.

2 participants