Pull requests / #3527

#3527 [Engine] Metal: release old prompt scratch buffers when the workspace grows

open · @Elvis-Luo · 0 コメント · GitHub で見る

AMD / HIPNVIDIA / CUDAModels & quants

本文

## Problem and result

On the resident Metal prompt-chain path, `Window::pc_setup` replaces `PromptBufs` when a larger chunk is needed. `mtl::Buffer` stores a retained raw Metal handle and has no releasing destructor, so resetting the struct loses the old allocations without releasing them.

I reproduced the lifetime problem without loading a model on an Apple M4 Max: growing from 64 to 96 rows leaves all **82 of 82** old buffers alive on upstream `9de04cd44e927a9a3df6bbc0e94fc1be8b844ba4`. With this change, the same test observes **0 of 82** retained at each growth: 64→96, 96→128, 128→192. Same-size and smaller requests retain their buffer identities and contents.

This is a growth-triggered leak, not a claim of unbounded growth on every request, a measured throughput regression, or an observed system crash.

## Related discussion

Found while investigating the Metal prompt path related to #2903. That PR fixes the per-layer dispatch grid, not buffer ownership. This PR is independent and intentionally does **not** include that grid fix, vision settings, model files, or desktop customizations. I searched open and closed discussions for `PromptBufs`, `pc_setup`, Metal leaks and workspace growth; no matching ownership fix was found. This is a focused bug fix, not a feature/default change.

## Approach

Record each scratch allocation once and release the old set before replacing it. Keep the current high-water reuse behavior, library IDs and kernel-layout selection. No release on every request; the current workspace remains reusable. KV/recurrent state and model weights are not in this ownership list.

The production prompt submissions wait for completion before returning (`Context::run/run_graph`), so growth is between completed prompt chunks.

The regression check extracts the actual buffer declaration and complete `pc_setup` method into an allocation-only Window shim, links the real Metal runtime, and uses weak Objective-C references to all buffer members. It does not reimplement the allocation/free code or require a model. Observer autorelease pools are drained before checking lifetime; otherwise the observer itself can temporarily retain objects. The layout probe is stubbed to the non-M5 layout. A missing Metal device returns a CTest skip, not a pass.

## Validation

Revision: **093ed3077e8f03ae54ffc302ac0b1af37592b38e**, based on **9de04cd44e927a9a3df6bbc0e94fc1be8b844ba4**.
Environment: Apple M4 Max, 128 GB unified memory, macOS 27.0.1 (26A434), Apple clang 21.0.0; metal-cpp 26 (official archive SHA-256 verified); ggml from llama.cpp `3cf03257f219afbe7334045ff7c6a06ac68c627d`.

**Passed:**

```sh
cmake -S metal -B build-metal -G Ninja -DCMAKE_BUILD_TYPE=Release \
  -DMETAL_CPP_DIR=<metal-cpp-26> -DSTRATA_GGML_DIR=<llama.cpp-3cf03257> \
  -DGGML_CCACHE=OFF
cmake --build build-metal --target strata runtime_hello -j 6
ctest --test-dir build-metal --output-on-failure -V
git diff --check
```

Full Metal engine build succeeded. CTest: **2/2 passed**, neither skipped: `prompt_workspace_growth` and `runtime_hello`. Lifetime check excerpt:

```text
device: Apple M4 Max; 82 prompt buffers; no model loaded
capacity 64 -> 96: old buffers retained = 0 of 82
capacity 96 -> 128: old buffers retained = 0 of 82
capacity 128 -> 192: old buffers retained = 0 of 82
prompt workspace lifetime: PASS
```

**Failed:**

The intentional unpatched negative control, using the same harness/runtime, exits 1:

```sh
python3 metal/tests/test_prompt_workspace.py --cxx /usr/bin/clang++ \
  --runtime build-metal/libmtl_rt.a --build-dir build-metal/prompt-workspace-test \
  --source-ref 9de04cd44e927a9a3df6bbc0e94fc1be8b844ba4
```

```text
capacity 64 -> 96: old buffers retained = 82 of 82
FAIL: growth releases all old Metal buffers
```

No unexpected failure on the submitted revision with device access. An initial sandboxed attempt had no Metal device; initial lifetime-harness attempts were corrected to drain observer autorelease references before the final positive/negative comparison.

**Not run:**

- Full-model inference or throughput on this isolated PR head. Earlier combined local-deployment tests are not counted as validation of this exact standalone patch.
- Other Apple chips / M5 layout, CUDA, HIP or SYCL builds. The change is confined to the independent `metal/` project; other backends are unchanged.

## Limits

This fixes replacement during growth only. It does not redesign general Metal resource ownership, change the cumulative meaning of `Context::allocated()`, shrink the workspace after smaller requests, or address #2903's incorrect grid. The model-free test validates actual Metal object lifetime and reuse, not model outputs or in-flight inference.

Prepared with Codex assistance; source changes and recorded outputs were checked before submission.

関連リンク

インストール・モデル・リリースへのサイト内リンク。