Skip to content

Commit ea4976d

Browse files
authored
Merge branch 'JamePeng:main' into main
2 parents 8c4dbcc + 01ced97 commit ea4976d

12 files changed

Lines changed: 680 additions & 187 deletions

File tree

.github/workflows/build-wheels-metal.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ jobs:
5454
-DGGML_METAL=on
5555
-DGGML_METAL_USE_BF16=on
5656
-DGGML_METAL_EMBED_LIBRARY=off
57-
-DGGML_METAL_SHADER_DEBUG=on
57+
-DGGML_METAL_SHADER_DEBUG=on"
5858
with:
5959
package-dir: .
6060
output-dir: wheelhouse2

CHANGELOG.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,79 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.3.27]
11+
12+
- feat: add `PaddleOCR-VL-1.5` multimodal chat handler `PaddleOCRChatHandler`
13+
14+
- fix: resolve VRAM leak in multimodal models by explicitly closing mtmd context
15+
- Remove the `ExitStack` closure in `Llava15ChatHandler` to break circular references preventing garbage collection of the vision context.
16+
- Implement explicit `close()` and `__del__()` methods in the chat handler to safely free `mtmd_ctx`.
17+
- Integrate `chat_handler.close()` into the main `Llama.close()` lifecycle and nullify related attributes for immediate memory reclamation.
18+
19+
- fix: resolve reload memory leaks by breaking circular references in cleanup
20+
- Remove closure-based `ExitStack` callbacks in `LlamaModel`, `LlamaContext`, and `LlamaBatch` to eliminate circular references.
21+
- Move explicit C-level memory freeing (`llama_*_free`) directly into `close()` methods.
22+
- Nullify additional attributes (`tokenizer_`, `model_params`, etc.) in `Llama.close()` to guarantee immediate garbage collection during unload.
23+
- Add todo comment to LoRA process logic, the current LoRa loading logic is outdated and needs to be refactored.
24+
25+
- feat: add memory breakdown and sampler performance timings APIs
26+
27+
- feat: Search system paths for shared libraries(`by @benniekiss`)
28+
29+
- Optimize `longest_token_prefix` to use zero-copy NumPy arrays and drop .tolist() overhead
30+
31+
- Free _candidates and large numpy arrays during explicit close()
32+
33+
- fix: resolve memory leaks in sampling context lifecycle
34+
- Safely close temporary `LlamaSamplingContext` in `sample()` using a try-finally block.
35+
- Explicitly release the previous `_sampling_ctx` in `generate()` before re-assignment to prevent orphaned pointers.
36+
- Ensure `_sampling_ctx` is properly freed in `Llama.close()`.
37+
38+
- Fix custom sampler memory cleanup and improve lifecycle management
39+
- Add explicit `close()` and `__del__()` to CustomSampler to safely free C resources and break Python reference cycles
40+
- Ensure custom samplers are properly detached and freed in `LlamaSampler.close()`
41+
- Add minor documentation comments for clarity
42+
43+
- feat: Update llama.cpp to [ggml-org/llama.cpp/commit/cacc371f99fb3b5b431d3fa89ac0c752bbd62a3b](https://github.com/ggml-org/llama.cpp/commit/cacc371f99fb3b5b431d3fa89ac0c752bbd62a3b)
44+
45+
- feat: Sync llama.cpp llama/mtmd API Binding 20260223
46+
47+
More information see: https://github.com/JamePeng/llama-cpp-python/compare/3d0fd1b75ee564361a4babf21f88855225ba1fe0...1f8341ee74a2fea15a1008487cbecaccf918c755
48+
49+
## [0.3.26]
50+
- perf(llama-cpp): optimize LlamaTokenDataArray memory operations
51+
- Cache NumPy field views for 'id', 'logit', and 'p' to bypass expensive property lookups.
52+
- Refactor copy_logits to use pre-generated ID sequences and cached views.
53+
- Ensure logical consistency by resetting token IDs every sampling step to counter C++ reordering.
54+
- Minimize redundant memory allocations during the inference loop.
55+
56+
- feat: Add explicit memory cleanup for sampling contexts
57+
- Implements `close()` and `__del__` for LlamaTokenDataArray and expands LlamaSamplingContext cleanup.
58+
- Ensures NumPy views and internal C-references are properly released to allow Python GC to reclaim memory.
59+
60+
- optimize(memory): reduce scores buffer size and optimize state saving
61+
- Update save_state and load_state API use.
62+
- Refactored self.scores to allocate only a single row (1, n_vocab) when logits_all=False, significantly reducing memory usage for large vocabulary models.
63+
- Optimized save_state to eliminate redundant memory allocations and copies by using ctypes.string_at.
64+
- Updated load_state, eval, and sampler adapters to correctly handle the dynamic shape of self.scores.
65+
66+
- Fix CMake install layout to avoid top-level bin directory in site-packages
67+
68+
- ggml: Load ggml library from candidate path list
69+
- Auto-select lib/ or bin/ directories
70+
- Add backend loading functions
71+
72+
- feat(loader): extend default library search paths on Linux and macOS
73+
- `load_shared_library` to include a path list feature (allowing you to add custom paths in addition to the default ones). You can later add your own paths to the `libggml_base_paths` candidate list in `_ggml.py`, such as those not commonly used Python paths.
74+
- fix: Enhance the handling logic for non-existent file paths.
75+
- Improves reliability of shared library discovery for system-wide installations.
76+
77+
- feat: Update llama.cpp to [ggml-org/llama.cpp/commit/abb9f3c42b5e6acee9e8e37836ef691d1a41bdb8](https://github.com/ggml-org/llama.cpp/commit/abb9f3c42b5e6acee9e8e37836ef691d1a41bdb8)
78+
79+
- feat: Sync llama.cpp llama/mtmd API Binding 20260219
80+
81+
More information see: https://github.com/JamePeng/llama-cpp-python/compare/b03224b2dde3c8cbdd8bf529794e3a41ac7f5751...6b38182a17effd0cedbf8736bee2464dd6c7b4da
82+
1083
## [0.3.25]
1184
- feat: [Refactor Llama class to use new LlamaSampler chain API from _internals](https://github.com/JamePeng/llama-cpp-python/commit/1e6094a327f0fb9dc35d52f84d8ebabc1faa1e95)
1285
This commit refactors the high-level Llama class to fully utilize the new C++ `llama_sampler` chain architecture via `LlamaSamplingContext`.

CMakeLists.txt

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ project(llama_cpp)
55
option(LLAMA_BUILD "Build llama.cpp shared library and install alongside python package" ON)
66
option(MTMD_BUILD "Build mtmd shared library and install alongside python package" ON)
77

8+
9+
set(CMAKE_INSTALL_BINDIR llama_cpp/bin CACHE PATH "" FORCE)
10+
set(CMAKE_INSTALL_LIBDIR llama_cpp/lib CACHE PATH "" FORCE)
11+
set(CMAKE_INSTALL_INCLUDEDIR llama_cpp/include CACHE PATH "" FORCE)
12+
13+
814
# Helper function to install targets to Python package directories
915
function(llama_cpp_python_install_target target)
1016
if(NOT TARGET ${target})
@@ -37,7 +43,7 @@ function(llama_cpp_python_install_target target)
3743
endif()
3844
endforeach()
3945

40-
# Configure RPATH
46+
# Proper RPATH handling
4147
if(UNIX)
4248
set(INSTALL_RPATH_VAL "$ORIGIN")
4349
if(APPLE)
@@ -68,6 +74,9 @@ if (LLAMA_BUILD)
6874
# Enable building of the common library
6975
set(LLAMA_BUILD_COMMON ON CACHE BOOL "llama.cpp: build common utils library" FORCE)
7076

77+
# Enable build and link OpenSSL
78+
set(LLAMA_OPENSSL ON CACHE BOOL "llama.cpp: build and link OpenSSL" FORCE)
79+
7180
# Disable building of examples
7281
set(LLAMA_BUILD_EXAMPLES OFF CACHE BOOL "llama.cpp: build examples" FORCE)
7382

@@ -77,9 +86,6 @@ if (LLAMA_BUILD)
7786
# Disable building curl support
7887
set(LLAMA_CURL OFF CACHE BOOL "llama.cpp: use libcurl to download model from an URL" FORCE)
7988

80-
# Enable build and link OpenSSL
81-
set(LLAMA_OPENSSL ON CACHE BOOL "llama.cpp: build and link OpenSSL" FORCE)
82-
8389
# Architecture detection and settings for Apple platforms
8490
if (APPLE)
8591
# If CMAKE_OSX_ARCHITECTURES is not set, use the host architecture
@@ -107,10 +113,8 @@ if (LLAMA_BUILD)
107113

108114
add_subdirectory(vendor/llama.cpp)
109115

110-
if (WIN32)
111-
if (TARGET llama)
112-
set_target_properties(llama PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON)
113-
endif()
116+
if (WIN32 AND TARGET llama)
117+
set_target_properties(llama PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON)
114118
endif()
115119

116120
# Define list of GGML targets to install

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,7 @@ Below are the supported multi-modal models and their respective chat handlers (P
519519
| [glm4.6v](https://huggingface.co/unsloth/GLM-4.6V-Flash-GGUF) | `GLM46VChatHandler` | `glm4.6v` |
520520
| [granite-docling](https://huggingface.co/ibm-granite/granite-docling-258M-GGUF) | `GraniteDoclingChatHandler` | `granite-docling` |
521521
| [lfm2-vl](https://huggingface.co/LiquidAI/LFM2-VL-3B-GGUF) | `LFM2VLChatHandler` | `lfm2-vl` |
522+
| [paddleocr-vl-1.5](https://huggingface.co/JamePeng2023/PaddleOCR-VL-1.5-GGUF) | `PaddleOCRChatHandler` | `paddleocr` |
522523
| [qwen2.5-vl](https://huggingface.co/unsloth/Qwen2.5-VL-3B-Instruct-GGUF) | `Qwen25VLChatHandler` | `qwen2.5-vl` |
523524
| [qwen3-vl](https://huggingface.co/unsloth/Qwen3-VL-8B-Thinking-GGUF) | `Qwen3VLChatHandler` | `qwen3-vl` |
524525

llama_cpp/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
from .llama_cpp import *
22
from .llama import *
33

4-
__version__ = "0.3.25"
4+
__version__ = "0.3.27"

llama_cpp/_ctypes_extensions.py

Lines changed: 63 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import ctypes
66
import functools
77
import pathlib
8-
8+
from ctypes.util import find_library
99
from typing import (
1010
Any,
1111
Callable,
@@ -20,25 +20,37 @@
2020

2121

2222
# Load the library
23-
def load_shared_library(lib_base_name: str, base_path: pathlib.Path):
24-
"""Platform independent shared library loader"""
25-
# Searching for the library in the current directory under the name "libllama" (default name
26-
# for llamacpp) and "llama" (default name for this repo)
27-
lib_paths: List[pathlib.Path] = []
28-
# Determine the file extension based on the platform
23+
def load_shared_library(lib_base_name: str, base_paths: Union[pathlib.Path, list[pathlib.Path]]):
24+
if isinstance(base_paths, pathlib.Path):
25+
base_paths = [base_paths]
26+
27+
lib_names = []
28+
2929
if sys.platform.startswith("linux") or sys.platform.startswith("freebsd"):
30-
lib_paths += [
31-
base_path / f"lib{lib_base_name}.so",
32-
]
30+
lib_names = [f"lib{lib_base_name}.so"]
31+
32+
base_paths.extend([
33+
"/usr/local/lib",
34+
"/usr/lib",
35+
"/usr/lib64",
36+
])
37+
3338
elif sys.platform == "darwin":
34-
lib_paths += [
35-
base_path / f"lib{lib_base_name}.so",
36-
base_path / f"lib{lib_base_name}.dylib",
39+
lib_names = [
40+
f"lib{lib_base_name}.dylib",
41+
f"lib{lib_base_name}.so",
3742
]
43+
44+
base_paths.extend([
45+
"/usr/local/lib",
46+
"/opt/homebrew/lib",
47+
"/usr/lib",
48+
])
49+
3850
elif sys.platform == "win32":
39-
lib_paths += [
40-
base_path / f"{lib_base_name}.dll",
41-
base_path / f"lib{lib_base_name}.dll",
51+
lib_names = [
52+
f"{lib_base_name}.dll",
53+
f"lib{lib_base_name}.dll",
4254
]
4355
else:
4456
raise RuntimeError("Unsupported platform")
@@ -47,11 +59,17 @@ def load_shared_library(lib_base_name: str, base_path: pathlib.Path):
4759

4860
# Add the library directory to the DLL search path on Windows (if needed)
4961
if sys.platform == "win32":
50-
os.add_dll_directory(str(base_path))
51-
os.environ["PATH"] = str(base_path) + os.pathsep + os.environ["PATH"]
52-
53-
if sys.platform == "win32" and sys.version_info >= (3, 8):
54-
os.add_dll_directory(str(base_path))
62+
for base_path in base_paths:
63+
p = pathlib.Path(base_path)
64+
if p.exists() and p.is_dir():
65+
os.add_dll_directory(str(p))
66+
os.environ["PATH"] = str(p) + os.pathsep + os.environ["PATH"]
67+
68+
if sys.platform == "win32" and sys.version_info >= (3, 9):
69+
for base_path in base_paths:
70+
p = pathlib.Path(base_path)
71+
if p.exists() and p.is_dir():
72+
os.add_dll_directory(str(p))
5573
if "CUDA_PATH" in os.environ:
5674
cuda_path = os.environ["CUDA_PATH"]
5775
sub_dirs_to_add = [
@@ -75,16 +93,30 @@ def load_shared_library(lib_base_name: str, base_path: pathlib.Path):
7593

7694
cdll_args["winmode"] = ctypes.RTLD_GLOBAL
7795

78-
# Try to load the shared library, handling potential errors
79-
for lib_path in lib_paths:
80-
if lib_path.exists():
81-
try:
82-
return ctypes.CDLL(str(lib_path), **cdll_args) # type: ignore
83-
except Exception as e:
84-
raise RuntimeError(f"Failed to load shared library '{lib_path}': {e}")
85-
86-
raise FileNotFoundError(
87-
f"Shared library with base name '{lib_base_name}' not found"
96+
errors = []
97+
98+
# First, try to find an available library through the system
99+
lib_path = find_library(lib_base_name)
100+
if lib_path:
101+
try:
102+
return ctypes.CDLL(lib_path, **cdll_args)
103+
except Exception as e:
104+
errors.append(f"{lib_path}: {e}")
105+
106+
# Then fallback to manually checking the list of paths.
107+
for base_path in base_paths:
108+
for lib_name in lib_names:
109+
lib_path = pathlib.Path(base_path) / lib_name
110+
111+
if lib_path.exists():
112+
try:
113+
return ctypes.CDLL(str(lib_path), **cdll_args)
114+
except Exception as e:
115+
errors.append(f"{lib_path}: {e}")
116+
117+
raise RuntimeError(
118+
f"Failed to load '{lib_base_name}' from {base_paths}\n"
119+
+ "\n".join(errors)
88120
)
89121

90122

llama_cpp/_ggml.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@
22
33
This module provides a minimal interface for working with ggml tensors from llama-cpp-python
44
"""
5+
import ctypes
56
import enum
67
import os
78
import pathlib
8-
import ctypes
99

10-
import llama_cpp._ctypes_extensions as ctypes_ext
10+
from llama_cpp._ctypes_extensions import (
11+
load_shared_library,
12+
byref,
13+
ctypes_function_for_shared_library,
14+
)
1115

1216
from typing import (
1317
Callable,
@@ -17,8 +21,15 @@
1721
TYPE_CHECKING,
1822
)
1923

20-
libggml_base_path = pathlib.Path(os.path.abspath(os.path.dirname(__file__))) / "lib"
21-
libggml = ctypes_ext.load_shared_library("ggml", libggml_base_path)
24+
libggml_base_path = pathlib.Path(os.path.abspath(os.path.dirname(__file__)))
25+
libggml_base_paths = [
26+
libggml_base_path / "lib",
27+
libggml_base_path / "bin",
28+
]
29+
30+
libggml = load_shared_library("ggml", libggml_base_paths)
31+
32+
ggml_function = ctypes_function_for_shared_library(libggml)
2233

2334
# // ====== ggml.h ======
2435

@@ -361,3 +372,20 @@ class ggml_opt_optimizer_params(ctypes.Structure):
361372
ggml_backend_sched_eval_callback = ctypes.CFUNCTYPE(
362373
ctypes.c_bool, ctypes.c_void_p, ctypes.c_bool, ctypes.c_void_p
363374
)
375+
376+
# //
377+
# // Backend registry
378+
# //
379+
380+
# // Load all known backends from dynamic libraries
381+
# GGML_API void ggml_backend_load_all(void);
382+
@ggml_function("ggml_backend_load_all", [], None)
383+
def ggml_backend_load_all():
384+
"""Load all known backends from dynamic libraries"""
385+
...
386+
387+
# GGML_API void ggml_backend_load_all_from_path(const char * dir_path);
388+
@ggml_function("ggml_backend_load_all_from_path", [ctypes.c_char_p], None)
389+
def ggml_backend_load_all_from_path(dir_path: ctypes.c_char_p):
390+
"""Load all known backends from path"""
391+
...

0 commit comments

Comments
 (0)