Commit ea24b952c for llama.cpp
commit ea24b952cd21a3c00ecffa23b328a6037a8692eb
Author: Yuri Khrustalev <ykhrustalev@users.noreply.github.com>
Date: Mon Nov 2 07:08:21 2026 -0500
ggml : use `vm_allocate` instead of `posix_memalign` on all Apple platforms (#31437)
* ggml : use `vm_allocate` instead of `posix_memalign` on iOS
* ggml : use vm_allocate on all Apple platforms, run test-host-buffer-release by default
diff --git a/ggml/src/ggml-metal/ggml-metal-device.m b/ggml/src/ggml-metal/ggml-metal-device.m
index 3d4ebf86f..19ed68f9a 100644
--- a/ggml/src/ggml-metal/ggml-metal-device.m
+++ b/ggml/src/ggml-metal/ggml-metal-device.m
@@ -2102,19 +2102,11 @@ static void ggml_metal_buffer_rset_free(ggml_metal_buffer_t buf) {
static void * ggml_metal_host_malloc(size_t n) {
void * data = NULL;
-#if TARGET_OS_OSX
kern_return_t err = vm_allocate((vm_map_t) mach_task_self(), (void *) &data, n, VM_FLAGS_ANYWHERE);
if (err != KERN_SUCCESS) {
GGML_LOG_ERROR("%s: error: vm_allocate failed\n", __func__);
return NULL;
}
-#else
- const int result = posix_memalign((void **) &data, sysconf(_SC_PAGESIZE), n);
- if (result != 0) {
- GGML_LOG_ERROR("%s: error: posix_memalign failed\n", __func__);
- return NULL;
- }
-#endif
return data;
}
@@ -2296,11 +2288,7 @@ void ggml_metal_buffer_free(ggml_metal_buffer_t buf) {
}
if (buf->is_shared && buf->owned) {
-#if TARGET_OS_OSX
vm_deallocate((vm_map_t)mach_task_self(), (vm_address_t)buf->all_data, buf->all_size);
-#else
- free(buf->all_data);
-#endif
}
free(buf);
diff --git a/ggml/src/ggml.c b/ggml/src/ggml.c
index 822c8ab64..a324ec951 100644
--- a/ggml/src/ggml.c
+++ b/ggml/src/ggml.c
@@ -357,7 +357,8 @@ void * ggml_aligned_malloc(size_t size) {
void * aligned_memory = NULL;
#ifdef GGML_USE_CPU_HBM
int result = hbw_posix_memalign(&aligned_memory, alignment, size);
- #elif TARGET_OS_OSX && !defined(GGML_TSAN_ENABLED)
+ #elif defined(__APPLE__) && !defined(GGML_TSAN_ENABLED)
+ // libmalloc can keep freed pages in the process footprint, vm_deallocate returns them at once
GGML_UNUSED(alignment);
kern_return_t alloc_status = vm_allocate((vm_map_t) mach_task_self(), (vm_address_t *) &aligned_memory, size, VM_FLAGS_ANYWHERE);
int result = EFAULT;
@@ -404,7 +405,7 @@ void ggml_aligned_free(void * ptr, size_t size) {
if (ptr != NULL) {
hbw_free(ptr);
}
-#elif TARGET_OS_OSX && !defined(GGML_TSAN_ENABLED)
+#elif defined(__APPLE__) && !defined(GGML_TSAN_ENABLED)
if (ptr != NULL) {
vm_deallocate((vm_map_t)mach_task_self(), (vm_address_t)ptr, size);
}
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 841339356..4224a15ae 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -364,6 +364,7 @@ llama_build_and_test(test-backend-sampler.cpp LABEL "model behavior backend")
if (APPLE)
llama_build(test-rset-release.cpp)
+ llama_build_and_test(test-host-buffer-release.cpp LINK ggml)
endif()
if (NOT GGML_BACKEND_DL AND GGML_CPU)
diff --git a/tests/test-host-buffer-release.cpp b/tests/test-host-buffer-release.cpp
new file mode 100644
index 000000000..594157306
--- /dev/null
+++ b/tests/test-host-buffer-release.cpp
@@ -0,0 +1,47 @@
+// ref: https://github.com/ggml-org/llama.cpp/issues/30556
+
+#include "ggml-backend.h"
+
+#include <cstdint>
+#include <cstdio>
+#include <cstring>
+#include <mach/mach.h>
+
+static uint64_t phys_footprint() {
+ task_vm_info_data_t info;
+ mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
+ const kern_return_t ret = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t) &info, &count);
+ GGML_ASSERT(ret == KERN_SUCCESS);
+ return info.phys_footprint;
+}
+
+static bool test_release(ggml_backend_buffer_type_t buft) {
+ const size_t size = 256ull*1024*1024;
+
+ ggml_backend_buffer_t buf = ggml_backend_buft_alloc_buffer(buft, size);
+ GGML_ASSERT(buf != nullptr);
+ ggml_backend_buffer_clear(buf, 1);
+
+ const uint64_t footprint_alloc = phys_footprint();
+ ggml_backend_buffer_free(buf);
+ const uint64_t footprint_free = phys_footprint();
+
+ printf("%s: phys_footprint %.1f MiB -> %.1f MiB after freeing %.1f MiB\n", ggml_backend_buft_name(buft), footprint_alloc/1048576.0, footprint_free/1048576.0, size/1048576.0);
+
+ // do not poll, the footprint must drop at free time
+ return footprint_free < footprint_alloc - 0.75*size;
+}
+
+int main() {
+ ggml_backend_load_all();
+
+ bool ok = test_release(ggml_backend_cpu_buffer_type());
+
+ // private Metal buffers are allocated by Metal, not by ggml_metal_host_malloc
+ ggml_backend_dev_t dev = ggml_backend_dev_by_name("MTL0");
+ if (dev && strcmp(ggml_backend_buft_name(ggml_backend_dev_buffer_type(dev)), "MTL0") == 0) {
+ ok = test_release(ggml_backend_dev_buffer_type(dev)) && ok;
+ }
+
+ return ok ? 0 : 1;
+}