Model load holds two full copies of the weights on Metal (2.93 GB peak for a 1.42 GB GGUF)
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 初心者へのやさしさ
- 48/100
- issue の種類
- バグ
- 明瞭さ
- おおむね明確
- 活発さ
- 静か
- 技術スタック
- cpp, macos
- 領域
- backend, performance
調査の方向性
まず ModelLoader::load() と ModelLoader::realize_weights() を追跡し、特に gguf_init_from_file()、デバイスへのアップロードループ、CPU バッファの経路を確認します。次に、llama.cpp の src/llama-model-loader.cpp にあるロードおよび mmap のアプローチと比較し、その後 Metal 上でロード中のピークメモリとロード後のメモリを測定します。完了条件は、デバイスへのロードで匿名のホスト側ステージングコピー全体が保持されなくなり、アップロードが引き続き正しく行われることです。
索引モデルが issue の本文から書いたものです。
説明
Summary
On Metal, loading a model transiently holds two full copies of the weights, making peak resident memory approximately twice the GGUF size. The generic non-CPU loading path appears to perform the same staging copy for CUDA and Vulkan, although I have only measured Metal.
Measured on an M2 with ctc-1.1b-q8_0.gguf:
| GGUF on disk | 1.42 GB |
| peak resident during load | 2.93 GB |
| resident after load | 1.51 GB |
The steady state is fine — it's the transient that doubles.
Where it comes from
ModelLoader::load() opens the file with no_alloc=false, so ggml allocates and reads every tensor into ordinary host memory:
struct gguf_init_params p{ /*no_alloc*/false, /*ctx*/&ctx_ };
gguf_ = gguf_init_from_file(path.c_str(), p);
ModelLoader::realize_weights() then, on the device path, allocates a second model-sized backend buffer and uploads each tensor from that host staging copy, releasing it only after every upload completes:
weights_buf_ = ggml_backend_alloc_ctx_tensors(device_ctx_, backend);
for (auto& pr : ups)
ggml_backend_tensor_set(pr.first, pr.second, 0, ggml_nbytes(pr.first));
...
ggml_free(ctx_);
Both copies are live for the duration of the upload loop, which accounts for the measured 1.42 GB → 2.93 GB → 1.51 GB progression: two weight copies plus metadata and alignment overhead.
The CPU path in the same function already avoids this, borrowing the loaded memory directly:
weights_buf_ = ggml_backend_cpu_buffer_from_ptr(base, size);
Why it matters
For an offline captioning app shipped to end users, the launch spike is what decides whether the app is usable on a small machine. On an 8 GB Mac, a 2.93 GB transient against a ~3-4 GB OS baseline creates substantial additional memory pressure and may cause compression or swapping depending on what else is running and on the model's compute buffers; 1.4 GB would leave considerably more headroom.
There's a second, smaller benefit: weights loaded this way are anonymous memory, so under pressure the OS can only compress or swap them. Weights backed by a file mapping can simply be dropped and re-read.
Possible direction
A broadly applicable first improvement may be to parse with no_alloc=true, mmap the GGUF tensor data, and upload tensors individually from that mapping. That would retain only the destination backend allocation plus file-backed source pages, avoiding the complete anonymous host staging allocation — a benefit on CUDA and Vulkan too, which still need backend-owned storage and a transfer.
On Apple Silicon specifically, ggml_backend_metal_buffer_from_ptr or the equivalent current Metal buffer API may permit the mapping itself to back the Metal weight buffers, potentially eliminating the copy entirely. llama.cpp takes a comparable approach — it parses with no_alloc=true and manages model storage separately, with mmap as a supported loading mode (use_mmap in src/llama-model-loader.cpp).
I haven't attempted a patch — I don't know whether the loader's tensor bookkeeping makes either step straightforward here, and you'd know immediately whether it's worth pursuing. Happy to test any change on the setup below.
Environment
- macOS 26.3.1, Apple M2, 24 GB
- parakeet.cpp v0.5.0 release build, Metal
ctc-1.1b-q8_0.gguffrommudler/parakeet-cpp-gguf
- 主要言語
- C++
- スター
- 831
- フォーク
- 101
- 平均マージ
- 8時間 27分
- マージ済み PR(30日)
- 26
環境構築
- Dockerfile または Docker Compose ファイルあり
- プルリクエストのテンプレートなし
- コントリビューションガイドなし
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
mudler/parakeet.cpp のほかの issue
-
Mention parakeet-ffm in README対応中かも @localai-org-maint-bot が今日担当しました。 オープン
難易度 1/5 1時間未満 初心者へのやさしさ 72/100
mudler/parakeet.cpp#99 ·
メンテナーはふだん 1 日以内に返信
-
難易度 4/5 3〜5日 初心者へのやさしさ 55/100
mudler/parakeet.cpp#68 ·
メンテナーはふだん 1 日以内に返信
-
難易度 3/5 1〜2日 初心者へのやさしさ 58/100
mudler/parakeet.cpp#62 ·
メンテナーはふだん 1 日以内に返信
-
難易度 4/5 3〜5日 初心者へのやさしさ 58/100
mudler/parakeet.cpp#61 ·
メンテナーはふだん 1 日以内に返信
-
Real streaming from a mic対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープン
難易度 3/5 1〜2日 初心者へのやさしさ 48/100
mudler/parakeet.cpp#60 ·
メンテナーはふだん 1 日以内に返信
mudler/parakeet.cpp の issue をすべて見る
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 74/100
メンテナーはふだん 1 日以内に返信
-
`IO.FS.realPath` reports every failure as `noFileOrDirectory` instead of classifying by `errno`オープンbug
難易度 2/5 1〜3時間 初心者へのやさしさ 66/100
leanprover/lean4#15683 ·
メンテナーはふだん 1 日以内に返信
-
[Bug] [geo] Haversine distance calculation can return NaN for antipodal coordinates対応中かも @516665061 が今日担当しました。 オープンstatus:needs-triage
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
PX4/PX4-Autopilot#29211 ·
メンテナーはふだん 1 日以内に返信
-
llvm.memcpy with destination align but no source align asserts "Missed Source alignment!"対応中かも @pvelesko が今日担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
KhronosGroup/SPIRV-LLVM-Translator#4093 ·
メンテナーはふだん 2 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 66/100
メンテナーはふだん 1 日以内に返信