Repository: ggml-org/llama.cpp
Pull request: llava: improve clip_ctx destructor to not memleak load_image_size

Detected clip memory leak with `leaks` on mac after building in debug mode from https://github.com/ggml-org/llama.cpp/commit/2dabf759e7c8c827d38cdd18d0792070e6a4f4e1
```
sudo leaks --atExit -- /Users/matt/Workspace/forks/llama.cpp/build/bin/llama-qwen2vl-cli -m /Users/matt/.cache/lm-studio/models/lmstudio-community/Qwen2-VL-2B-Instruct-GGUF/Qwen2-VL-2B-Instruct-Q4_K_M.gguf --mmproj /Users/matt/.cache/lm-studio/models/lmstudio-community/Qwen2-VL-2B-Instruct-GGUF/mmproj-model-f32.gguf --image /Users/matt/Documents/dice.jpg -p "What is this?" &> leaks_output.txt
```
Results in:
```
STACK OF 1 INSTANCE OF 'ROOT LEAK: <malloc in clip_image_size_init>':
9   dyld                                  0x1828990e0 start + 2360
8   llama-qwen2vl-cli                     0x102022978 main + 836  qwen2vl-cli.cpp:565
7   llama-qwen2vl-cli                     0x102023198 load_image(llava_context*, common_params*, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&) + 488  qwen2vl-cli.cpp:228
6   llama-qwen2vl-cli                     0x102037eb4 llava_image_embed_make_with_filename + 152  llava.cpp:565
5   llama-qwen2vl-cli                     0x102037d7c llava_image_embed_make_with_bytes + 156  llava.cpp:503
4   llama-qwen2vl-cli                     0x102036a50 llava_image_embed_make_with_clip_img + 280  llava.cpp:430
3   llama-qwen2vl-cli                     0x102036c74 encode_image_with_clip(clip_ctx*, int, clip_image_u8 const*, float*, int*) + 400  llava.cpp:265
2   llama-qwen2vl-cli                     0x1020479c4 clip_image_size_init + 20  clip.cpp:1607
1   libc++abi.dylib                       0x182bd2ec0 operator new(unsigned long) + 32
0   libsystem_malloc.dylib                0x182a57a44 _malloc_zone_malloc_instrumented_or_legacy + 136 
====
    1 (16 bytes) ROOT LEAK: <malloc in clip_image_size_init 0x136f8c170> [16]
```

This change fixes that, and makes `clip_free` safter to call by checking if the `ctx` is nullptr

Changed files:
filename: examples/llava/clip.cpp
patch: @@ -380,6 +380,7 @@ struct clip_ctx {
         if (backend_cpu != backend) {
             ggml_backend_free(backend_cpu);
         }
+        clip_image_size_free(load_image_size);
     }
 };
 
@@ -1618,6 +1619,12 @@ struct clip_image_f32 * clip_image_f32_init() {
     return new clip_image_f32();
 }
 
+void clip_image_size_free(struct clip_image_size * load_image_size) {
+    if (load_image_size == nullptr) {
+        return;
+    }
+    delete load_image_size;
+}
 void clip_image_u8_free(struct clip_image_u8  * img) { delete img; }
 void clip_image_f32_free(struct clip_image_f32 * img) { delete img; }
 void clip_image_u8_batch_free(struct clip_image_u8_batch  * batch) {
@@ -2270,6 +2277,9 @@ ggml_tensor * clip_get_newline_tensor(const struct clip_ctx * ctx) {
 }
 
 void clip_free(clip_ctx * ctx) {
+    if (ctx == nullptr) {
+        return;
+    }
     delete ctx;
 }
 

filename: examples/llava/clip.h
patch: @@ -77,6 +77,7 @@ CLIP_API struct clip_image_size * clip_image_size_init();
 CLIP_API struct clip_image_u8  * clip_image_u8_init ();
 CLIP_API struct clip_image_f32 * clip_image_f32_init();
 
+CLIP_API void clip_image_size_free (struct clip_image_size * img_size);
 CLIP_API void clip_image_u8_free (struct clip_image_u8  * img);
 CLIP_API void clip_image_f32_free(struct clip_image_f32 * img);
 CLIP_API void clip_image_u8_batch_free (struct clip_image_u8_batch  * batch);