From 5a2a0c545e5b7edab0ba4ec24279db1281e917b8 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Wed, 24 Jun 2026 22:00:26 +0800 Subject: [PATCH 01/86] Update Submodule vendor/llama.cpp bddfd2b..1191758 Signed-off-by: JamePeng --- vendor/llama.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/llama.cpp b/vendor/llama.cpp index bddfd2b11..1191758c5 160000 --- a/vendor/llama.cpp +++ b/vendor/llama.cpp @@ -1 +1 @@ -Subproject commit bddfd2b1137cd6e51fbb939081caf50e9f496a66 +Subproject commit 1191758c5d77e575b4531e057708a562f6f6f0b2 From a2e15200a5f05da91cc6911f6cd810daf90d04b5 Mon Sep 17 00:00:00 2001 From: patrikpatrik Date: Wed, 24 Jun 2026 10:27:39 -0700 Subject: [PATCH 02/86] Added command prompt scenario README.md *Updated command prompt scenario under Configuration -> Environment Variables *Sanity checking after installation of wheel --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 433e031ae..4361c857a 100644 --- a/README.md +++ b/README.md @@ -110,12 +110,21 @@ CMAKE_ARGS="-DGGML_BLAS=ON -DGGML_BLAS_VENDOR=OpenBLAS" \ ``` ```powershell -# Windows +# Windows powershell $env:CMAKE_ARGS = "-DGGML_BLAS=ON -DGGML_BLAS_VENDOR=OpenBLAS" pip install "llama-cpp-python @ git+https://github.com/JamePeng/llama-cpp-python.git" ``` + +```command prompt +# Windows command prompt +set CMAKE_ARGS = "-DGGML_BLAS=ON -DGGML_BLAS_VENDOR=OpenBLAS" +pip install "llama-cpp-python @ git+https://github.com/JamePeng/llama-cpp-python.git" +``` +### Sanity Checking +`python.exe -c "from llama_cpp import Llama; print('llama-cpp import OK')"` +
CLI / requirements.txt From 0116e76da361026898d743b33a4bf18e71f9ce07 Mon Sep 17 00:00:00 2001 From: patrikpatrik Date: Wed, 24 Jun 2026 10:35:34 -0700 Subject: [PATCH 03/86] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4361c857a..8b3daa9d8 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ pip install "llama-cpp-python @ git+https://github.com/JamePeng/llama-cpp-python ```
-### Sanity Checking +**Sanity Checking** `python.exe -c "from llama_cpp import Llama; print('llama-cpp import OK')"`
From cf44079c997f6f262d150982528ecd81ab0f19ac Mon Sep 17 00:00:00 2001 From: patrikpatrik Date: Wed, 24 Jun 2026 10:36:55 -0700 Subject: [PATCH 04/86] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8b3daa9d8..58f13d4fc 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ pip install "llama-cpp-python @ git+https://github.com/JamePeng/llama-cpp-python ```
-**Sanity Checking** +**Sanity Checking** `python.exe -c "from llama_cpp import Llama; print('llama-cpp import OK')"`
From c71baefff09a511d9df54b544555ca83f54148bc Mon Sep 17 00:00:00 2001 From: patrikpatrik Date: Wed, 24 Jun 2026 10:37:59 -0700 Subject: [PATCH 05/86] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 58f13d4fc..acf57289c 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ pip install "llama-cpp-python @ git+https://github.com/JamePeng/llama-cpp-python ```
-**Sanity Checking** +**Sanity Checking** `python.exe -c "from llama_cpp import Llama; print('llama-cpp import OK')"`
From e5782b1c423b3aa80362165dc67abc2b9f9bf912 Mon Sep 17 00:00:00 2001 From: patrikpatrik Date: Wed, 24 Jun 2026 10:40:44 -0700 Subject: [PATCH 06/86] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index acf57289c..8968d4ecb 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,8 @@ pip install "llama-cpp-python @ git+https://github.com/JamePeng/llama-cpp-python
**Sanity Checking** -`python.exe -c "from llama_cpp import Llama; print('llama-cpp import OK')"` +Use this line to check if installation was successful before moving further. +```python.exe -c "from llama_cpp import Llama; print('llama-cpp import OK')"```
CLI / requirements.txt From b74cabb133cce7a996129eb019c490d8f5a07922 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Sat, 27 Jun 2026 01:53:19 +0800 Subject: [PATCH 07/86] Update Submodule vendor/llama.cpp 1191758..3fc4e10 - Append `n_layer_nextn` call method Signed-off-by: JamePeng --- llama_cpp/_internals.py | 3 +++ llama_cpp/llama.py | 4 ++++ llama_cpp/llama_cpp.py | 5 +++++ vendor/llama.cpp | 2 +- 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/llama_cpp/_internals.py b/llama_cpp/_internals.py index 91befb224..ef9238ad3 100644 --- a/llama_cpp/_internals.py +++ b/llama_cpp/_internals.py @@ -123,6 +123,9 @@ def n_embd_out(self) -> int: def n_layer(self) -> int: return llama_cpp.llama_model_n_layer(self.model) + def n_layer_nextn(self) -> int: + return llama_cpp.llama_model_n_layer_nextn(self.model) + def n_head(self) -> int: return llama_cpp.llama_model_n_head(self.model) diff --git a/llama_cpp/llama.py b/llama_cpp/llama.py index b6a2c8d5a..1d8bb1688 100644 --- a/llama_cpp/llama.py +++ b/llama_cpp/llama.py @@ -3470,6 +3470,10 @@ def n_layer(self) -> int: """Return the n_layer value.""" return self._model.n_layer() + def n_layer_nextn(self) -> int: + """Return the n_layer_nextn value.""" + return self._model.n_layer_nextn() + def n_head(self) -> int: """Return the head size.""" return self._model.n_head() diff --git a/llama_cpp/llama_cpp.py b/llama_cpp/llama_cpp.py index 1e81d80f6..ee1b5244f 100644 --- a/llama_cpp/llama_cpp.py +++ b/llama_cpp/llama_cpp.py @@ -1746,6 +1746,11 @@ def llama_model_n_layer(model: llama_model_p, /) -> int: ... +# LLAMA_API int32_t llama_model_n_layer_nextn(const struct llama_model * model); +@ctypes_function("llama_model_n_layer_nextn", [llama_model_p_ctypes], ctypes.c_int32) +def llama_model_n_layer_nextn(model: llama_model_p, /) -> int: + ... + # LLAMA_API int32_t llama_model_n_head (const struct llama_model * model); @ctypes_function("llama_model_n_head", [llama_model_p_ctypes], ctypes.c_int32) def llama_model_n_head(model: llama_model_p, /) -> int: diff --git a/vendor/llama.cpp b/vendor/llama.cpp index 1191758c5..3fc4e1052 160000 --- a/vendor/llama.cpp +++ b/vendor/llama.cpp @@ -1 +1 @@ -Subproject commit 1191758c5d77e575b4531e057708a562f6f6f0b2 +Subproject commit 3fc4e105279105106b08a133a4e3e483116e621f From e6c16afd6baeb9ad67624ba13d224a26e1d441f9 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Mon, 29 Jun 2026 00:07:31 +0800 Subject: [PATCH 08/86] Update Submodule vendor/llama.cpp 3fc4e10..c818263 Signed-off-by: JamePeng --- vendor/llama.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/llama.cpp b/vendor/llama.cpp index 3fc4e1052..c818263f2 160000 --- a/vendor/llama.cpp +++ b/vendor/llama.cpp @@ -1 +1 @@ -Subproject commit 3fc4e105279105106b08a133a4e3e483116e621f +Subproject commit c818263f2a5ddab028dea5f169ea2b2266421125 From bb03d2ada63a9b69f2844f5459ada29af17d189b Mon Sep 17 00:00:00 2001 From: JamePeng Date: Wed, 1 Jul 2026 01:55:51 +0800 Subject: [PATCH 09/86] Update Submodule vendor/llama.cpp c818263..4f31eed Signed-off-by: JamePeng --- vendor/llama.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/llama.cpp b/vendor/llama.cpp index c818263f2..4f31eedb0 160000 --- a/vendor/llama.cpp +++ b/vendor/llama.cpp @@ -1 +1 @@ -Subproject commit c818263f2a5ddab028dea5f169ea2b2266421125 +Subproject commit 4f31eedb0ccf546b7e8d6bb243b170f12522f54d From 46708d11a26528dcd64a96df1c59fce7ec41e080 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Wed, 1 Jul 2026 01:57:59 +0800 Subject: [PATCH 10/86] refactor(mtmd): extract `mtmd_tokenize` into `_mtmd_tokenize` standalone helper - Introduce `_mtmd_tokenize()` to encapsulate llama.cpp mtmd_tokenize binding - Decouple hybrid tokenization logic from `_process_mtmd_prompt` - Improve separation of concerns between prompt construction and C++ binding - Preserve strict media marker validation to ensure token/bitmap alignment Signed-off-by: JamePeng --- llama_cpp/llama_multimodal.py | 109 ++++++++++++++++++++++++++-------- 1 file changed, 83 insertions(+), 26 deletions(-) diff --git a/llama_cpp/llama_multimodal.py b/llama_cpp/llama_multimodal.py index f1b320b77..60e29d7dd 100644 --- a/llama_cpp/llama_multimodal.py +++ b/llama_cpp/llama_multimodal.py @@ -515,6 +515,75 @@ def _is_audio_chunk(self, chunk_type: int) -> bool: == self._mtmd_cpp.mtmd_input_chunk_type.MTMD_INPUT_CHUNK_TYPE_AUDIO ) + def _mtmd_tokenize( + self, + llama: "llama_core.Llama", + text: str, + bitmaps: Optional[list] = None, + chunks: Optional[Any] = None, + ) -> Any: + """ + Perform MTMD hybrid tokenization. + + This function isolates the llama.cpp mtmd_tokenize call + so that prompt construction logic is decoupled from runtime execution. + + It guarantees: + - stable interface for future async/batch decoding + - isolated error handling for tokenizer failures + - clean separation between prompt building and C++ binding + """ + + if chunks is None: + chunks = self._mtmd_cpp.mtmd_input_chunks_init() + if chunks is None: + raise ValueError( + f"{self.log_prefix}(_mtmd_tokenize): failed to init mtmd_input_chunks" + ) + + # Validate strict alignment between rendered media markers and provided bitmaps + # to ensure MTMD tokenization consistency and prevent decoding mismatch errors. + if bitmaps is not None: + marker_count = text.count(self.media_marker) + if marker_count != len(bitmaps): + raise ValueError( + f"{self.log_prefix}(_mtmd_tokenize): marker mismatch " + f"(marker_count={marker_count}, bitmap_count={len(bitmaps)})" + ) + + input_text = self._mtmd_cpp.mtmd_input_text() + input_text.text = ctypes.c_char_p(text.encode("utf-8")) + input_text.add_special = (llama.n_tokens == 0) + input_text.parse_special = True + + bitmap_array = None + n_bitmaps = 0 + + if bitmaps: + n_bitmaps = len(bitmaps) + bitmap_array = (self._mtmd_cpp.mtmd_bitmap_p_ctypes * n_bitmaps)(*bitmaps) + else: + bitmap_array = None + n_bitmaps = 0 + + result = self._mtmd_cpp.mtmd_tokenize( + self.mtmd_ctx, + chunks, + ctypes.byref(input_text), + bitmap_array, + n_bitmaps, + ) + + if result != 0: + raise ValueError( + f"{self.log_prefix}(_mtmd_tokenize): tokenize failed\n" + f"- result={result}\n" + f"- text_len={len(text)}\n" + f"- n_bitmaps={n_bitmaps}\n" + ) + + return chunks + def _process_mtmd_prompt( self, llama: llama_core.Llama, @@ -572,8 +641,12 @@ def _process_mtmd_prompt( text = text.replace(item["url"], media_marker) if self.verbose: - print(f"{self.log_prefix}(_process_mtmd_prompt): Rendered prompt length: {len(text)} chars, Media count: {len(media_items)}.", file=sys.stderr) - print(f"{self.log_prefix}(_process_mtmd_prompt): Rendered prompt: {text}", file=sys.stderr) + print( + f"{self.log_prefix}(_process_mtmd_prompt): " + f"Rendered prompt length: {len(text)} chars, Media count: {len(media_items)}.\n" + f"Rendered prompt: {text}", + file=sys.stderr, + ) # 3. Pre-allocate bitmap array to guarantee chronological order during concurrent decoding bitmaps = [None] * len(media_items) @@ -614,29 +687,13 @@ def _create_bitmap_func(idx: int, item: dict): # If there are no images, set the bitmaps to empty. bitmaps = [] - # 4. Initialize mtmd_input_chunks - input_text = self._mtmd_cpp.mtmd_input_text() - input_text.text = text.encode('utf-8') - input_text.add_special = (llama.n_tokens == 0) - input_text.parse_special = True - - chunks = self._mtmd_cpp.mtmd_input_chunks_init() - if chunks is None: - raise ValueError(f"{self.log_prefix}(mtmd_input_chunks_init): Failed to initialize mtmd_input_chunks.") - - # 5. Hybrid Tokenization (Text + Media binding) - if len(bitmaps) > 0: - bitmap_array = (self._mtmd_cpp.mtmd_bitmap_p_ctypes * len(bitmaps))(*bitmaps) - result = self._mtmd_cpp.mtmd_tokenize( - self.mtmd_ctx, chunks, ctypes.byref(input_text), bitmap_array, len(bitmaps) - ) - else: - result = self._mtmd_cpp.mtmd_tokenize( - self.mtmd_ctx, chunks, ctypes.byref(input_text), None, 0 - ) - - if result != 0: - raise ValueError(f"{self.log_prefix}(mtmd_tokenize): Unable to tokenize prompt, res = {result}.") + # 4. Hybrid Tokenization (Text + Media) + chunks = self._mtmd_tokenize( + llama=llama, + text=text, + bitmaps=bitmaps, + chunks=None, + ) # Video helper contexts only need to stay alive until mtmd_tokenize() completes. if video_cleanup: @@ -644,7 +701,7 @@ def _create_bitmap_func(idx: int, item: dict): self._mtmd_cpp.mtmd_helper_video_free(video_ctx) video_cleanup.clear() - # 6. Virtual Token Ledger Construction + # 5. Virtual Token Ledger Construction full_prompt_ids = [] chunk_token_spans = [] current_idx = 0 From 6f21d4df8b711f8db2c87e10c5387c21fc9b29ed Mon Sep 17 00:00:00 2001 From: JamePeng Date: Wed, 1 Jul 2026 02:08:25 +0800 Subject: [PATCH 11/86] docs: update mtmd chat handler import paths in README - Update import statements for multi-modal chat handlers from llama_cpp.llama_chat_format to llama_cpp.llama_multimodal in the documentation examples. Signed-off-by: JamePeng --- README.md | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8968d4ecb..20d14e42e 100644 --- a/README.md +++ b/README.md @@ -1053,11 +1053,18 @@ Below are the supported multi-modal models and their respective chat handlers (P | [qwen3.6](https://huggingface.co/unsloth/Qwen3.6-35B-A3B-GGUF) | `Qwen35ChatHandler` | `qwen3.6` | | [step3-vl](https://huggingface.co/JamePeng2023/Step3-VL-10B-GGUF) | `Step3VLChatHandler` | `step3-vl` | -Then you'll need to use a custom chat handler to load the clip model and process the chat messages and images. +Then you'll need to use a custom chat handler to load the mmproj model and process the chat messages and images. + +Note: Starting from 0.3.41-preview, in order to extend MTMD capabilities, multimodal-related logic has been separated from `llama_chat_format` into `llama_multimodal`. + New implementations are recommended to use the updated multimodal interfaces in `llama_multimodal` + For backward compatibility, the legacy `llama_chat_format` path is still retained and continues to support existing integrations, but may be deprecated in future versions. + Additionally, the parameter `clip_model_path` has been renamed to `mmproj_path` to better reflect its purpose and align with the underlying multimodal projection model naming convention. + The old parameter name `clip_model_path` is kept as a compatibility alias in some interfaces, but new code should use `mmproj_path` exclusively. ```python from llama_cpp import Llama -from llama_cpp.llama_chat_format import Llava15ChatHandler +# from llama_cpp.llama_chat_format import Llava15ChatHandler +from llama_cpp.llama_multimodal import Llava15ChatHandler model_path="path/to/llava/ggml-model-f16.gguf" mmproj_path="path/to/llava/mmproj-model-f16.gguf" @@ -1086,7 +1093,8 @@ You can also pull the model from the Hugging Face Hub using the `from_pretrained ```python from llama_cpp import Llama -from llama_cpp.llama_chat_format import MoondreamChatHandler +# from llama_cpp.llama_chat_format import MoondreamChatHandler +from llama_cpp.llama_multimodal import MoondreamChatHandler chat_handler = MoondreamChatHandler.from_pretrained( repo_id="vikhyatk/moondream2", @@ -1128,7 +1136,8 @@ print(response["choices"][0]["text"]) ```python # Import necessary libraries from llama_cpp import Llama -from llama_cpp.llama_chat_format import Qwen3VLChatHandler +# from llama_cpp.llama_chat_format import Qwen3VLChatHandler +from llama_cpp.llama_multimodal import Qwen3VLChatHandler import base64 import os @@ -1285,7 +1294,8 @@ The `Qwen3ASRChatHandler` is specifically designed for the Qwen3 Automatic Speec ```python from llama_cpp import Llama -from llama_cpp.llama_chat_format import Qwen3ASRChatHandler +# from llama_cpp.llama_chat_format import Qwen3ASRChatHandler +from llama_cpp.llama_multimodal import Qwen3ASRChatHandler import base64 import os @@ -1390,7 +1400,8 @@ Below is a complete, production-ready example demonstrating how to dynamically r ```python from llama_cpp import Llama -from llama_cpp.llama_chat_format import Gemma4ChatHandler +# from llama_cpp.llama_chat_format import Gemma4ChatHandler +from llama_cpp.llama_multimodal import Gemma4ChatHandler import base64 import os From 8b38e72c655c15e064862a48e91c3fe0e32b28f1 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Fri, 3 Jul 2026 01:05:56 +0800 Subject: [PATCH 12/86] Update Submodule vendor/llama.cpp 4f31eed..fdb1db8 Signed-off-by: JamePeng --- llama_cpp/_internals.py | 6 ++++++ llama_cpp/llama_cpp.py | 29 +++++++++++++++++++++++++++++ vendor/llama.cpp | 2 +- 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/llama_cpp/_internals.py b/llama_cpp/_internals.py index ef9238ad3..b05a4207d 100644 --- a/llama_cpp/_internals.py +++ b/llama_cpp/_internals.py @@ -149,6 +149,12 @@ def model_desc(self) -> str: llama_cpp.llama_model_desc(self.model, buf, 256) return buf.value.decode("utf-8") + def model_ftype(self) -> int: + """ + Get the model file type (quantization), e.g. LLAMA_FTYPE_MOSTLY_Q8_0 + """ + return llama_cpp.llama_model_ftype(self.model) + def model_size(self) -> int: """ Returns the total size of all the tensors in the model in bytes diff --git a/llama_cpp/llama_cpp.py b/llama_cpp/llama_cpp.py index ee1b5244f..a4086fd70 100644 --- a/llama_cpp/llama_cpp.py +++ b/llama_cpp/llama_cpp.py @@ -408,6 +408,20 @@ class llama_ftype(enum.IntEnum): LLAMA_FTYPE_MOSTLY_Q1_0 = 40 LLAMA_FTYPE_GUESSED = 1024 +# // Get the model file type (quantization) as a string, e.g. "Q8_0" or "Q4_K - Medium" +# LLAMA_API const char * llama_ftype_name(enum llama_ftype ftype); +@ctypes_function( + "llama_ftype_name", + [ctypes.c_int], + ctypes.c_char_p, +) +def llama_ftype_name( + ftype: llama_ftype, / +) -> bytes: + """ + Get the model file type (quantization) as a string, e.g. "Q8_0" or "Q4_K - Medium" + """ + # enum llama_rope_scaling_type { # LLAMA_ROPE_SCALING_TYPE_UNSPECIFIED = -1, # LLAMA_ROPE_SCALING_TYPE_NONE = 0, @@ -1922,6 +1936,21 @@ def llama_model_desc( ... +# // Get the model file type (quantization), e.g. LLAMA_FTYPE_MOSTLY_Q8_0 +# LLAMA_API enum llama_ftype llama_model_ftype(const struct llama_model * model); +@ctypes_function( + "llama_model_ftype", + [llama_model_p_ctypes], + ctypes.c_int, +) +def llama_model_ftype( + model: llama_model_p, + /, +) -> int: + """Get the model file type (quantization), e.g. LLAMA_FTYPE_MOSTLY_Q8_0""" + ... + + # // Returns the total size of all the tensors in the model in bytes # LLAMA_API uint64_t llama_model_size(const struct llama_model * model); @ctypes_function("llama_model_size", [llama_model_p_ctypes], ctypes.c_uint64) diff --git a/vendor/llama.cpp b/vendor/llama.cpp index 4f31eedb0..fdb1db877 160000 --- a/vendor/llama.cpp +++ b/vendor/llama.cpp @@ -1 +1 @@ -Subproject commit 4f31eedb0ccf546b7e8d6bb243b170f12522f54d +Subproject commit fdb1db877c526ec90f668eca1b858da5dba85560 From 79a86357e9893399a2c5622e725f0579cb010fd3 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Sun, 5 Jul 2026 00:39:23 +0800 Subject: [PATCH 13/86] fix(vocab): update vocab_type to use self.vocab and add None checks Signed-off-by: JamePeng --- llama_cpp/_internals.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/llama_cpp/_internals.py b/llama_cpp/_internals.py index b05a4207d..0ae347c00 100644 --- a/llama_cpp/_internals.py +++ b/llama_cpp/_internals.py @@ -100,9 +100,13 @@ def __del__(self): self.close() def vocab_type(self) -> int: - return llama_cpp.llama_vocab_type(self.model) + if self.vocab is None: + raise RuntimeError("LlamaModel.vocab_type: vocab is None") + return llama_cpp.llama_vocab_type(self.vocab) def n_vocab(self) -> int: + if self.vocab is None: + raise RuntimeError("LlamaModel.n_vocab: vocab is None") return llama_cpp.llama_vocab_n_tokens(self.vocab) def n_ctx_train(self) -> int: From 9f87f6186a69583f2ad9a901db9fc2abfe3b183b Mon Sep 17 00:00:00 2001 From: JamePeng Date: Sun, 5 Jul 2026 00:45:14 +0800 Subject: [PATCH 14/86] Update Submodule vendor/llama.cpp fdb1db8..6658925 Signed-off-by: JamePeng --- vendor/llama.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/llama.cpp b/vendor/llama.cpp index fdb1db877..665892536 160000 --- a/vendor/llama.cpp +++ b/vendor/llama.cpp @@ -1 +1 @@ -Subproject commit fdb1db877c526ec90f668eca1b858da5dba85560 +Subproject commit 665892536dfb1b7532161e3182304bd35c33e768 From 8a0d085407f1eeddd9740e0d907c3e1191bcd79c Mon Sep 17 00:00:00 2001 From: JamePeng Date: Tue, 7 Jul 2026 02:56:29 +0800 Subject: [PATCH 15/86] Update Submodule vendor/llama.cpp 6658925..3899b39 Signed-off-by: JamePeng --- vendor/llama.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/llama.cpp b/vendor/llama.cpp index 665892536..3899b39ce 160000 --- a/vendor/llama.cpp +++ b/vendor/llama.cpp @@ -1 +1 @@ -Subproject commit 665892536dfb1b7532161e3182304bd35c33e768 +Subproject commit 3899b39ce2acc2e019f149b7107f24b6ca297390 From d3f0ac931ced646aade26198a6fd3a8be2dd5368 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Wed, 8 Jul 2026 02:01:57 +0800 Subject: [PATCH 16/86] refactor(mtmd): extract prompt rendering and media marker normalization Add extra_template_arguments to MTMD chat handlers and pass them through to the Jinja chat template render call. This allows generic model templates to receive render-time options such as enable_thinking, add_vision_id, or model-specific template jinja variables. Extract MTMD prompt rendering into dedicated helpers: * _render_mtmd_prompt() for pure chat template rendering * _replace_media_placeholders() for normalizing rendered media tags and URLs into the MTMD runtime marker * _render_and_replace_media() for the combined render-and-normalize stage This removes inline render/replace logic from _process_mtmd_prompt(), keeps media marker validation after normalization, and improves separation between prompt construction and MTMD tokenization. Signed-off-by: JamePeng --- llama_cpp/llama.py | 1 - llama_cpp/llama_multimodal.py | 128 +++++++++++++++++++++++++++++----- 2 files changed, 110 insertions(+), 19 deletions(-) diff --git a/llama_cpp/llama.py b/llama_cpp/llama.py index 1d8bb1688..75af691eb 100644 --- a/llama_cpp/llama.py +++ b/llama_cpp/llama.py @@ -721,7 +721,6 @@ def __init__( self.chat_handler = llama_multimodal.GenericMTMDChatHandler( chat_format = self.metadata.get("tokenizer.chat_template", None), mmproj_path = mmproj_path, - verbose = self.verbose, chat_template_name=chat_template_name, **chat_handler_kwargs ) diff --git a/llama_cpp/llama_multimodal.py b/llama_cpp/llama_multimodal.py index 60e29d7dd..5ef1a2feb 100644 --- a/llama_cpp/llama_multimodal.py +++ b/llama_cpp/llama_multimodal.py @@ -102,6 +102,7 @@ def __init__( image_max_tokens: int = -1, chat_template_override: Optional[str] = None, batch_max_tokens: int = 1024, + extra_template_arguments: Optional[Dict[str, Any]] = None, **kwargs ): @@ -148,7 +149,13 @@ def __init__( import llama_cpp.mtmd_cpp as mtmd_cpp self._mtmd_cpp = mtmd_cpp self.mtmd_ctx: Optional[mtmd_cpp.mtmd_context_p] = None - self.extra_template_arguments: dict[str, Any] = {} + + if extra_template_arguments is not None and not isinstance(extra_template_arguments, dict): + raise TypeError( + f"{self.log_prefix}(__init__): `extra_template_arguments` must be a dict." + ) + + self.extra_template_arguments: dict[str, Any] = dict(extra_template_arguments or {}) self.is_support_vision = False self.is_support_audio = False @@ -515,6 +522,104 @@ def _is_audio_chunk(self, chunk_type: int) -> bool: == self._mtmd_cpp.mtmd_input_chunk_type.MTMD_INPUT_CHUNK_TYPE_AUDIO ) + def _render_mtmd_prompt( + self, + messages: List[llama_types.ChatCompletionRequestMessage], + functions: Optional[List[llama_types.ChatCompletionFunction]] = None, + function_call: Optional[llama_types.ChatCompletionRequestFunctionCall] = None, + tools: Optional[List[llama_types.ChatCompletionTool]] = None, + tool_choice: Optional[llama_types.ChatCompletionToolChoiceOption] = None, + add_generation_prompt: bool = True, + ) -> str: + """ + Render the chat template into plain prompt text. + + This stage only renders the Jinja template. It does not normalize media + placeholders or replace media URLs with the MTMD runtime marker. + """ + return self.chat_template.render( + messages=messages, + add_generation_prompt=add_generation_prompt, + eos_token=self.mtmd_eos_token, + bos_token=self.mtmd_bos_token, + functions=functions, + function_call=function_call, + tools=tools, + tool_choice=tool_choice, + **getattr(self, "extra_template_arguments", {}), + ) + + def _replace_media_placeholders( + self, + text: str, + media_items: List[Dict[str, str]], + ) -> str: + """ + Normalize rendered media placeholders and media URLs into the MTMD runtime marker. + + llama.cpp MTMD tokenization recognizes the canonical media marker, usually + `<__media__>`. Model chat templates may render media as model-specific tags + such as ``, `<|image|>`, `[IMG]`, `<|image_pad|>`, or as the original + URL/data URI. This stage converts those rendered forms into the canonical + MTMD marker and validates that the final marker count matches the number of + media payloads. + """ + media_marker = self.media_marker + + # 1. Replace known template-specific media tags first. + # + # This handles templates that render placeholders such as: + # , <|image|>, [IMG], <|image_pad|>, <|media_pad|>, etc. + for tag in self._chat_format_parser_tags: + if tag in text: + text = text.replace(tag, media_marker) + + # 2. Replace rendered media URLs/data URIs. + # + # This handles templates that directly render the original image/audio/video + # URL or data URI instead of a symbolic placeholder. + for item in media_items: + url = item.get("url", "") + if url and url in text: + text = text.replace(url, media_marker, 1) + + # 3. Validate only after all normalization is complete. + marker_count = text.count(media_marker) + if marker_count != len(media_items): + raise ValueError( + f"{self.log_prefix}(_replace_media_placeholders): media marker mismatch " + f"(marker_count={marker_count}, media_count={len(media_items)})" + ) + + return text + + def _render_and_replace_media( + self, + messages: List[llama_types.ChatCompletionRequestMessage], + media_items: List[Dict[str, str]], + functions: Optional[List[llama_types.ChatCompletionFunction]] = None, + function_call: Optional[llama_types.ChatCompletionRequestFunctionCall] = None, + tools: Optional[List[llama_types.ChatCompletionTool]] = None, + tool_choice: Optional[llama_types.ChatCompletionToolChoiceOption] = None, + add_generation_prompt: bool = True, + ) -> str: + """ + Render chat messages and normalize rendered media placeholders into MTMD markers. + """ + text = self._render_mtmd_prompt( + messages=messages, + functions=functions, + function_call=function_call, + tools=tools, + tool_choice=tool_choice, + add_generation_prompt=add_generation_prompt, + ) + + return self._replace_media_placeholders( + text=text, + media_items=media_items, + ) + def _mtmd_tokenize( self, llama: "llama_core.Llama", @@ -615,31 +720,18 @@ def _process_mtmd_prompt( messages = [{"role": "system", "content": self.DEFAULT_SYSTEM_MESSAGE}] + messages media_items = self._get_media_items(messages) - media_marker = self.media_marker - # 2. Render the chat template and replace actual URLs with C++ media markers - text = self.chat_template.render( + # 2. Render chat template and normalize media placeholders to MTMD markers. + text = self._render_and_replace_media( messages=messages, - add_generation_prompt=add_generation_prompt, - eos_token=self.mtmd_eos_token, - bos_token=self.mtmd_bos_token, + media_items=media_items, functions=functions, function_call=function_call, tools=tools, tool_choice=tool_choice, - **getattr(self, 'extra_template_arguments', {}) + add_generation_prompt=add_generation_prompt, ) - for tag in self._chat_format_parser_tags: - if tag not in text: - continue - - text = text.replace(tag, media_marker) - - # Replace image_url by media_marker in text - for item in media_items: - text = text.replace(item["url"], media_marker) - if self.verbose: print( f"{self.log_prefix}(_process_mtmd_prompt): " From 8cf0be28c1b8f82d57542287570cedae4ffb77cd Mon Sep 17 00:00:00 2001 From: JamePeng Date: Wed, 8 Jul 2026 02:41:49 +0800 Subject: [PATCH 17/86] docs(README): add GenericMTMDChatHandler usage guide Replace the legacy Llava multimodal loading example with a GenericMTMDChatHandler usage guide for template-driven multimodal GGUF models. Document loading mmproj through Llama, chat template resolution order, extra_template_arguments for model-specific Jinja variables, and when to prefer a dedicated multimodal chat handler. Also clarify the mmproj_path naming, llama_multimodal migration, and note that the generic handler is intended as a flexible fallback for models without dedicated handlers and may require additional testing for model-specific prompting behavior. Signed-off-by: JamePeng --- README.md | 150 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 108 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 20d14e42e..f11f26a58 100644 --- a/README.md +++ b/README.md @@ -1053,76 +1053,142 @@ Below are the supported multi-modal models and their respective chat handlers (P | [qwen3.6](https://huggingface.co/unsloth/Qwen3.6-35B-A3B-GGUF) | `Qwen35ChatHandler` | `qwen3.6` | | [step3-vl](https://huggingface.co/JamePeng2023/Step3-VL-10B-GGUF) | `Step3VLChatHandler` | `step3-vl` | -Then you'll need to use a custom chat handler to load the mmproj model and process the chat messages and images. +Then you'll need to load the multimodal projection model (`mmproj`) together with the main language model. -Note: Starting from 0.3.41-preview, in order to extend MTMD capabilities, multimodal-related logic has been separated from `llama_chat_format` into `llama_multimodal`. - New implementations are recommended to use the updated multimodal interfaces in `llama_multimodal` - For backward compatibility, the legacy `llama_chat_format` path is still retained and continues to support existing integrations, but may be deprecated in future versions. - Additionally, the parameter `clip_model_path` has been renamed to `mmproj_path` to better reflect its purpose and align with the underlying multimodal projection model naming convention. - The old parameter name `clip_model_path` is kept as a compatibility alias in some interfaces, but new code should use `mmproj_path` exclusively. +Starting from `0.3.41-preview`, new multimodal implementations are recommended to use the updated interfaces in `llama_multimodal`. For backward compatibility, the legacy `llama_chat_format` path is still retained, but may be deprecated in future versions. + +The parameter `clip_model_path` has been renamed to `mmproj_path` to better reflect its purpose and align with llama.cpp's multimodal projection model naming convention. New code should use `mmproj_path` exclusively. + +### Generic MTMD Chat Handler + +For multimodal GGUF models that already include a valid `tokenizer.chat_template`, you can use the generic MTMD handler through `mmproj_path`. + +This is especially useful for newer multimodal models that have not yet received a dedicated Python chat handler. The generic handler renders the model-provided Jinja chat template, then normalizes rendered media placeholders or media URLs into the canonical llama.cpp MTMD media marker, usually `<__media__>`, before calling `mtmd_tokenize`. + +> **Note:** `GenericMTMDChatHandler` is intended as a flexible fallback for template-driven multimodal models. Because different model families may use different media ordering rules, reasoning switches, stop tokens, or special template variables, some models may still require a dedicated chat handler. Please test carefully and report issues if you encounter incorrect prompts, missing media markers, or mismatched media counts. ```python from llama_cpp import Llama -# from llama_cpp.llama_chat_format import Llava15ChatHandler -from llama_cpp.llama_multimodal import Llava15ChatHandler -model_path="path/to/llava/ggml-model-f16.gguf" -mmproj_path="path/to/llava/mmproj-model-f16.gguf" +# Model and multimodal projection paths +MODEL_PATH = r"path/to/model.gguf" +MMPROJ_PATH = r"path/to/mmproj.gguf" llm = Llama( - model_path=model_path, - chat_handler=Llava15ChatHandler(clip_model_path=mmproj_path), - n_ctx=2048, + model_path=MODEL_PATH, + mmproj_path=MMPROJ_PATH, + n_gpu_layers=-1, + n_ctx=10240, + verbose=True, + verbosity=2, + chat_handler_kwargs={ + "verbose": True, + }, ) -llm.create_chat_completion( - messages = [ - {"role": "system", "content": "You are an assistant who perfectly describes images."}, +response = llm.create_chat_completion( + messages=[ { "role": "user", "content": [ - {"type" : "text", "text": "What's in this image?"}, - {"type": "image_url", "image_url": {"url": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg" } } - ] + { + "type": "image_url", + "image_url": { + "url": "path/to/image.jpg", + }, + }, + { + "type": "text", + "text": "Describe this image in detail.", + }, + ], } ] ) + +print(response["choices"][0]["message"]["content"]) +```` + +#### Chat Template Resolution Order + +`GenericMTMDChatHandler` resolves the chat template in the following order: + +1. Use the explicit `chat_format` passed through `chat_handler_kwargs`, if provided. +2. Use the named model chat template if `chat_template_name` is provided. +3. Fall back to the default `tokenizer.chat_template` stored in the GGUF model metadata. +4. Fall back to the built-in MTMD chat template if no model template is available. + +Example using a named chat template: + +```python +llm = Llama( + model_path=r"path/to/model.gguf", + mmproj_path=r"path/to/mmproj.gguf", + # chat_template_name="default", + n_gpu_layers=-1, + n_ctx=4096, + chat_handler_kwargs={ + "verbose": False, + }, +) ``` -You can also pull the model from the Hugging Face Hub using the `from_pretrained` method. +#### Passing Extra Template Arguments + +Some model chat templates expose optional Jinja variables such as `enable_thinking`, `add_vision_id`, or model-specific media token switches. Further details can be obtained by analyzing the chat templates provided in `chat_template.jinja` or `tokenizer_config.json` for each model. + +You can pass those values through `chat_handler_kwargs["extra_template_arguments"]`: ```python from llama_cpp import Llama -# from llama_cpp.llama_chat_format import MoondreamChatHandler -from llama_cpp.llama_multimodal import MoondreamChatHandler -chat_handler = MoondreamChatHandler.from_pretrained( - repo_id="vikhyatk/moondream2", - filename="*mmproj*", -) +# Model and multimodal projection paths +MODEL_PATH = r"path/to/model.gguf" +MMPROJ_PATH = r"path/to/mmproj.gguf" -llm = Llama.from_pretrained( - repo_id="vikhyatk/moondream2", - filename="*text-model*", - chat_handler=chat_handler, - n_ctx=2048, # n_ctx should be increased to accommodate the image embedding +llm = Llama( + model_path=MODEL_PATH, + mmproj_path=MMPROJ_PATH, + n_gpu_layers=-1, + n_ctx=10240, + verbose=False, + verbosity=1, + chat_handler_kwargs={ + "extra_template_arguments": { + "enable_thinking": True, + }, + "verbose": False, + }, ) +... +``` -response = llm.create_chat_completion( - messages = [ - { - "role": "user", - "content": [ - {"type" : "text", "text": "What's in this image?"}, - {"type": "image_url", "image_url": {"url": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg" } } +The values inside `extra_template_arguments` are passed directly into the Jinja template render call. - ] - } - ] +For models that already have a dedicated handler, you can still instantiate that handler directly: + +```python +from llama_cpp import Llama +from llama_cpp.llama_multimodal import PaddleOCRChatHandler + +MODEL_PATH = r"path/to/model.gguf" +MMPROJ_PATH = r"path/to/mmproj.gguf" + +llm = Llama( + model_path=MODEL_PATH, + chat_handler=PaddleOCRChatHandler( + mmproj_path=MMPROJ_PATH, + ), + n_gpu_layers=-1, # Use all available GPU layers + n_ctx = 0, # Context window size + n_batch=2048, ) -print(response["choices"][0]["text"]) +... ``` +Use `GenericMTMDChatHandler` when the model-provided `tokenizer.chat_template` already works correctly. Prefer a dedicated handler when the model requires custom prompt construction, special reasoning behavior, custom stop tokens, OCR/ASR-specific handling, or non-standard media ordering. + + **Note**: Multi-modal models also support tool calling and JSON mode. From b9b58594023ab673c2dda6723f8909d85d65a2e5 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Wed, 8 Jul 2026 02:49:18 +0800 Subject: [PATCH 18/86] docs(README): Update Generic MTMD Chat Handler directory index. Signed-off-by: JamePeng --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f11f26a58..ab8c39d77 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ This package provides: - [How to use the ReasoningBudgetSampler](https://github.com/JamePeng/llama-cpp-python#reasoning-budget-first-reasoning-block) - [Multi-modal Models Support](https://github.com/JamePeng/llama-cpp-python#multi-modal-models) - Support Models Lists + - [Introducing Generic MTMD Chat Handler](https://github.com/JamePeng/llama-cpp-python#generic-mtmd-chat-handler) - [Loading a Local Image With Qwen3VL(Thinking/Instruct)](https://github.com/JamePeng/llama-cpp-python#loading-a-local-image-with-qwen3vlthinkinginstruct) - [Speech Recognition With Qwen3-ASR (Speech-to-Text)](https://github.com/JamePeng/llama-cpp-python#speech-recognition-with-qwen3-asr-speech-to-text) - [Comprehensive Omni MultiModal Example: Gemma-4 (Vision + Audio + Text)](https://github.com/JamePeng/llama-cpp-python#comprehensive-omni-multimodal-example-gemma-4-vision--audio--text) From 169d5e1a43fb6ff4e5b6f5d0f26f1ec8acbd97b8 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Wed, 8 Jul 2026 03:27:38 +0800 Subject: [PATCH 19/86] Bump version to 0.3.41 - This release is mainly focused on improving the multimodal architecture, making MTMD chat handling cleaner, more flexible, and easier to extend for future image, audio, and video workflows. I also continued syncing with the latest llama.cpp APIs and improved several developer-facing diagnostics around model templates, shared library loading, and Windows OpenMP runtime discovery. Signed-off-by: JamePeng --- CHANGELOG.md | 133 ++++++++++++++++++++++++++++++++++++++++++ llama_cpp/__init__.py | 2 +- 2 files changed, 134 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1865195db..83c3beb12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,139 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.3.41] Template-Driven MTMD, Broader Multimodal Inputs, and Smarter N-Gram Drafting + +- refactor(mtmd): extract prompt rendering and media marker normalization + - Add extra_template_arguments to MTMD chat handlers and pass them through to the Jinja chat template render call. This allows generic model templates to receive render-time options such as enable_thinking, add_vision_id, or model-specific template jinja variables. + - Extract MTMD prompt rendering into dedicated helpers: + * _render_mtmd_prompt() for pure chat template rendering + * _replace_media_placeholders() for normalizing rendered media tags and URLs into the MTMD runtime marker + * _render_and_replace_media() for the combined render-and-normalize stage + - This removes inline render/replace logic from _process_mtmd_prompt(), keeps media marker validation after normalization, and improves separation between prompt construction and MTMD tokenization. + +- docs(README): add GenericMTMDChatHandler usage guide + - Replace the legacy Llava multimodal loading example with a GenericMTMDChatHandler + usage guide for template-driven multimodal GGUF models. + - Document loading mmproj through Llama, chat template resolution order, + extra_template_arguments for model-specific Jinja variables, and when to prefer + a dedicated multimodal chat handler. + - Also clarify the mmproj_path naming, llama_multimodal migration, and note that + the generic handler is intended as a flexible fallback for models without + dedicated handlers and may require additional testing for model-specific + prompting behavior. + - Update Generic MTMD Chat Handler directory index. + +- refactor(mtmd): extract mtmd_tokenize into _mtmd_tokenize standalone helper + - Introduce `_mtmd_tokenize()` to encapsulate llama.cpp mtmd_tokenize binding + - Decouple hybrid tokenization logic from `_process_mtmd_prompt` + - Improve separation of concerns between prompt construction and C++ binding + - Preserve strict media marker validation to ensure token/bitmap alignment + +- feat(speculative): Improve ngram-map draft selection and accept feedback + - Store accepted draft lengths per key/value and truncate future drafts accordingly + - Make key-only mode draft on any key match without applying min_hits + - Select k4v continuations by frequency instead of latest occurrence + - Skip ambiguous k4v drafts when the top continuation is not dominant + - Track fixed-size k4v continuations to keep frequency statistics comparable + +- feat(mtmd): broaden multimodal media extraction + - Broaden MTMD media extraction to support common multimodal content shapes used + by model chat templates. + - In addition to OpenAI-style image_url/audio_url/video_url chunks, accept + image/audio/video typed chunks and direct media keys such as {"image": "..."}, + {"audio": "..."}, or {"video": "..."}. This keeps the extracted media list + aligned with templates that emit placeholders for image, audio, or video content + without requiring URL-specific chunk names. + - Add a shared helper for extracting URLs, local paths, existing data URIs, or + inline base64 payloads from media content items. Preserve capability checks, + strict input_audio format validation, and explicit errors for missing or + ambiguous media payloads. + +- feat(mtmd): enhance generic chat template support + - Enhance GenericMTMDChatHandler to better support model-provided chat templates. + - Allow the generic handler to accept an optional named chat template, load it + from the model at call time via llama_model_chat_template(), fall back to the + model's default chat template, and finally use the built-in MTMD CHAT_FORMAT + when no model template is available. + - Also expand the generic media placeholder list for common multimodal templates + and document the handler as a template-driven MTMD implementation. This prepares + the generic path for a later render-driven placeholder replacement pass. + +- fix(model): handle missing chat templates + - Update `LlamaModel.model_chat_template()` to return Optional[str] and accept + name=None for the default model chat template. + - `llama_model_chat_template()` may return nullptr when no chat template is + available. Handle that case explicitly instead of decoding a null pointer, and + return None so callers can apply their own fallback logic. + +- fix(vocab): update `LlamaModel.vocab_type` to use self.vocab and add None checks + +- refactor(mtmd): move multimodal handlers to separate module `llama_multimodal` + - Move `MTMDChatHandler`, `GenericMTMDChatHandler``, and model-specific multimodal + chat handlers out of `llama_chat_format.py` into `llama_multimodal.py`. + - `llama_chat_format.py` has grown too large and difficult to maintain, especially + as MTMD support expands beyond image-only use cases. Splitting multimodal + handling into its own module makes the chat formatting layer smaller and keeps + media loading, MTMD tokenization, multimodal KV-cache bookkeeping, and handler + implementations in a dedicated place. + - This also prepares the codebase for broader multimodal support and future video + frame / image batch evaluation, where the media-processing path will need to + evolve independently from text-only chat formatting. + - Keep backward-compatible re-exports from `llama_chat_format.py` so existing + imports continue to work. + - Also keep `clip_model_path` as a deprecated initialization alias for + `mmproj_path` in the base MTMD handler. + - docs: update mtmd chat handler import paths in README + - Update import statements for multi-modal chat handlers from llama_cpp.llama_chat_format to llama_cpp.llama_multimodal in the documentation examples. + +- feat: Implemented generic multimodal chat handler prototype (by **@alcoftTAO**) + +- docs(README): Added command prompt scenario for README.md (by **@patrikpatrik**) + - Updated command prompt scenario under Configuration -> Environment Variables + - Sanity checking after successful installation of wheel + +- feat(MTMDChatHandler): add chunk type helpers + - Add small helper methods `_is_text_chunk`/`_is_image_chunk`/`_is_audio_chunk` for checking + MTMD text, image, and audio chunk type enum values. + - This keeps MTMD prompt processing easier to read and avoids repeating direct + enum comparisons when building token spans for text and media chunks. + +- feat(mtmd): add video input support to `MTMDChatHandler` + - Add video_url handling to the MTMD chat template and media extraction + pipeline. Detect whether the loaded libmtmd build supports video helpers + and reject video inputs early when MTMD_VIDEO is unavailable. + - Update media loading and bitmap creation for the new helper wrapper API. + mtmd_helper_bitmap_init_from_buf now returns a bitmap wrapper containing + both the decoded bitmap and an optional video helper context, so keep the + video context alive until mtmd_tokenize completes and release it afterward. + - Also consolidate duplicated audio/video byte loading into a shared + _load_bytes helper, reuse it for image loading, and add richer default HTTP + headers for remote media requests. + +- build(CMakelists): Improve Windows LLVM OpenMP runtime `libomp140.x86_64.dll` discovery + - Also improve diagnostics by reporting the selected runtime source and path, + warning when an explicit override points to a missing file, and keeping a clear + runtime warning when no OpenMP DLL can be found. + - prefer VS 2022 VC143 OpenMP redist and keep System32 as final fallback。 + +- feat(_ctypes_extensions): improve error diagnostics for shared library loading + When `load_shared_library` fails, the resulting `RuntimeError` now + includes a listing of the contents of the searched directories. This + provides immediate context to help developers diagnose missing, misplaced, + or incorrectly named library files. + + - Added `_format_library_dir_contents` to safely format directory listings. + - Appended the directory listing to the failure message. + - Confined this diagnostic work strictly to the failure path to avoid any + performance overhead during successful imports. + +- feat: Update llama.cpp to [ggml-org/llama.cpp/commit/3899b39ce2acc2e019f149b7107f24b6ca297390](https://github.com/ggml-org/llama.cpp/commit/3899b39ce2acc2e019f149b7107f24b6ca297390) + +- feat: Sync llama.cpp llama/mtmd/ggml API Binding 20260707 + +More information see: https://github.com/JamePeng/llama-cpp-python/compare/12861b918f67b62f78f28c5cabb7223f766e1097...b9b58594023ab673c2dda6723f8909d85d65a2e5 + + ## [0.3.40-Milestone] Reasoning Budget Control, Gemma 4 12B Support, Enhanced Jinja2ChatFormatter, NGram k/k4v Speculative Decoding, Faster Native Sampling and Multimodal Improvements - feat(internals): Add `ReasoningBudgetSampler` support diff --git a/llama_cpp/__init__.py b/llama_cpp/__init__.py index 1650e6af6..3c3aa6690 100644 --- a/llama_cpp/__init__.py +++ b/llama_cpp/__init__.py @@ -1,4 +1,4 @@ from .llama_cpp import * from .llama import * -__version__ = "0.3.40" +__version__ = "0.3.41" From b4c74bf9b3c95a2d63dc676e78f83183f4c6f0b2 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Wed, 8 Jul 2026 03:39:31 +0800 Subject: [PATCH 20/86] fix(types): make assistant message name optional - Mark the assistant message `name` field as `NotRequired[Optional[str]]` to match the optional nature of assistant message metadata and avoid requiring callers to provide `name` in typed chat completion requests. Signed-off-by: JamePeng --- llama_cpp/llama_types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llama_cpp/llama_types.py b/llama_cpp/llama_types.py index 37b041ee8..56451ea25 100644 --- a/llama_cpp/llama_types.py +++ b/llama_cpp/llama_types.py @@ -427,7 +427,7 @@ class ChatCompletionRequestAssistantMessageFunctionCall(TypedDict): class ChatCompletionRequestAssistantMessage(TypedDict): """Messages sent by the model in response to user messages.""" role: Literal["assistant"] - name: Optional[str] + name: NotRequired[Optional[str]] content: NotRequired[Optional[str]] refusal: NotRequired[Optional[str]] tool_calls: NotRequired[ChatCompletionMessageToolCalls] From dbb4e21efcaadb412264a314606bb9dbed60bafe Mon Sep 17 00:00:00 2001 From: JamePeng Date: Thu, 9 Jul 2026 01:48:30 +0800 Subject: [PATCH 21/86] Update Submodule vendor/llama.cpp 3899b39..a646006 Signed-off-by: JamePeng --- vendor/llama.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/llama.cpp b/vendor/llama.cpp index 3899b39ce..a646006f0 160000 --- a/vendor/llama.cpp +++ b/vendor/llama.cpp @@ -1 +1 @@ -Subproject commit 3899b39ce2acc2e019f149b7107f24b6ca297390 +Subproject commit a646006f09d2f76f2d62d6c0d5e8e8490d570720 From 6c7b16fcccd6884d9163188bba25ecb40ab2d617 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Thu, 9 Jul 2026 03:45:34 +0800 Subject: [PATCH 22/86] fix: validate eval tokens before native decode Add token-id validation at the Llama.eval() boundary before context shifting, batch construction, or llama_decode execution. This prevents invalid token types, negative token ids, and out-of-vocabulary ids from reaching the native decode path, where they may otherwise cause hard crashes instead of Python exceptions. Wrap llama_decode with defensive exception handling in LlamaContext.decode() so native exceptions are surfaced with clearer diagnostic context. Also include a small token preview in Llama.eval() fatal decode errors to make backend failures easier to debug without changing the existing recoverable KV slot handling behavior. Signed-off-by: JamePeng --- llama_cpp/_internals.py | 9 +++++++- llama_cpp/llama.py | 40 ++++++++++++++++++++++++++++++++++- llama_cpp/llama_multimodal.py | 7 +++++- 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/llama_cpp/_internals.py b/llama_cpp/_internals.py index 0ae347c00..431077822 100644 --- a/llama_cpp/_internals.py +++ b/llama_cpp/_internals.py @@ -746,7 +746,14 @@ def decode(self, batch: 'LlamaBatch') -> int: (e.g., negative error codes or invalid batch structures). """ self._assert_ctx() - return_code = llama_cpp.llama_decode(self.ctx, batch.batch) + try: + return_code = llama_cpp.llama_decode(self.ctx, batch.batch) + except Exception as e: + raise RuntimeError( + "llama_decode raised a native exception before returning a status code. " + "This may indicate an invalid batch, invalid token id, corrupted context, " + "backend memory issue, or native access violation." + ) from e if return_code == 0: return 0 diff --git a/llama_cpp/llama.py b/llama_cpp/llama.py index 75af691eb..2ee8abaf5 100644 --- a/llama_cpp/llama.py +++ b/llama_cpp/llama.py @@ -1082,6 +1082,37 @@ def abort(self) -> None: print(f"Llama.abort: Abort signal received. Terminating generation...", file=sys.stderr) self._abort_event.set() + def _validate_eval_tokens( + self, + tokens: Sequence[int], + ) -> None: + """Validate token ids before passing them to llama_decode. + + This mirrors llama.cpp server-side token validation and prevents invalid + token ids from reaching the native decode path, where they may cause hard + crashes instead of Python exceptions. + """ + if not tokens: + return + + for i, tok in enumerate(tokens): + if not isinstance(tok, int): + raise ValueError( + f"Llama.eval: invalid token type at index {i}: " + f"{type(tok).__name__}" + ) + + if tok < 0: + raise ValueError( + f"Llama.eval: invalid negative token id at index {i}: {tok}" + ) + + if tok >= self._n_vocab: + raise ValueError( + f"Llama.eval: token out of vocab at index {i}: " + f"{tok} >= n_vocab({self._n_vocab})" + ) + def eval( self, tokens: Sequence[int], @@ -1106,6 +1137,11 @@ def eval( if n_eval == 0: return + # Validate token ids before any context shifting, batch construction, or + # native llama_decode call. Invalid ids may otherwise reach the C/C++ backend + # and cause hard crashes instead of Python exceptions. + self._validate_eval_tokens(tokens) + # Context Shift: Prevent OOM by discarding older tokens when context limit is reached. if self.n_tokens + n_eval > self._n_ctx: # 0. Check if the memory supports shifting @@ -1265,9 +1301,11 @@ def eval( current_batch_size //= 2 except Exception as e: + min_pos = min(current_batch_size, 16) + preview = chunk[:min_pos] # Catch fatal backend failures (e.g., Code -2, -3) raise RuntimeError(f"Llama.eval(decode): Fatal Decode Error at Pos {self.n_tokens}, " - f"Batch size {current_batch_size}: {str(e)}") from e + f"Batch size {current_batch_size}, chunk[:{min_pos}]={preview}: {str(e)}") from e if not success: raise RuntimeError("Llama.eval(decode): Failed completely even with batch size 1.") diff --git a/llama_cpp/llama_multimodal.py b/llama_cpp/llama_multimodal.py index 5ef1a2feb..5a41e5836 100644 --- a/llama_cpp/llama_multimodal.py +++ b/llama_cpp/llama_multimodal.py @@ -1010,7 +1010,12 @@ def __call__( if tokens_to_eval: if self.verbose: - print(f"{self.log_prefix}(__call__): Evaluating TEXT chunk ({len(tokens_to_eval)} tokens) at pos {llama.n_tokens}...", file=sys.stderr) + print( + f"{self.log_prefix}(__call__): Evaluating TEXT chunk " + f"({len(tokens_to_eval)} tokens) at pos {llama.n_tokens}...", + file=sys.stderr, + ) + # Text evaluation delegates shift and chunking to native llama.eval llama.eval(tokens_to_eval) n_past = llama.n_tokens From ae50508bcf256ad149592477035fa34145b3ea76 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Fri, 10 Jul 2026 02:42:16 +0800 Subject: [PATCH 23/86] Update Submodule vendor/llama.cpp a646006..049326a Signed-off-by: JamePeng --- vendor/llama.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/llama.cpp b/vendor/llama.cpp index a646006f0..049326a00 160000 --- a/vendor/llama.cpp +++ b/vendor/llama.cpp @@ -1 +1 @@ -Subproject commit a646006f09d2f76f2d62d6c0d5e8e8490d570720 +Subproject commit 049326a00025d00b08cc188ed716b681e984a3f8 From 0fdbc3d364c300e0ce985ebc96ed5905fe4a3cc1 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Fri, 10 Jul 2026 04:09:53 +0800 Subject: [PATCH 24/86] fix(_internals): harden LlamaBatch token writes Clarify llama_batch token vs embedding allocation semantics and keep future embedding/mixed-batch support open. Add token-buffer checks before add_token/add_sequence, validate add_sequence input lengths and seq_ids, and improve error messages for invalid batch configuration. Signed-off-by: JamePeng --- llama_cpp/_internals.py | 104 +++++++++++++++++++++++++++++++++------- 1 file changed, 86 insertions(+), 18 deletions(-) diff --git a/llama_cpp/_internals.py b/llama_cpp/_internals.py index 431077822..5058b7e32 100644 --- a/llama_cpp/_internals.py +++ b/llama_cpp/_internals.py @@ -1008,9 +1008,11 @@ def __init__( ): # logical validity of parameters if n_tokens <= 0: - raise ValueError(f"n_tokens must be positive, got {n_tokens}") + raise ValueError(f"LlamaBatch[__init__]: n_tokens must be positive, got {n_tokens}") + if embd < 0: + raise ValueError(f"LlamaBatch[__init__]: embd must be non-negative, got {embd}") if n_seq_max <= 0: - raise ValueError(f"n_seq_max must be positive, got {n_seq_max}") + raise ValueError(f"LlamaBatch[__init__]: n_seq_max must be positive, got {n_seq_max}") self.n_tokens_capacity = n_tokens self.embd = embd @@ -1018,11 +1020,24 @@ def __init__( self.verbose = verbose self._exit_stack = ExitStack() - batch = llama_cpp.llama_batch_init(self.n_tokens_capacity, self.embd, self.n_seq_max) + # llama_batch_init allocates either batch.token or batch.embd: + # + # embd == 0 -> token batch + # embd > 0 -> embedding batch + # + # Some llama.cpp paths, such as EAGLE3/MTP, manually create mixed + # token+embd batches after initialization. This wrapper keeps that + # possibility open, but add_token/add_sequence only support token input. + batch = llama_cpp.llama_batch_init( + self.n_tokens_capacity, + self.embd, + self.n_seq_max, + ) if batch is None: raise MemoryError( - f"Failed to allocate memory for llama_batch via llama_batch_init({n_tokens},{embd},{n_seq_max})" + f"Failed to allocate memory for llama_batch via " + f"llama_batch_init({n_tokens},{embd},{n_seq_max})" ) self.batch = batch @@ -1066,17 +1081,43 @@ def space_left(self) -> int: return self.n_tokens_capacity - self.batch.n_tokens else: raise RuntimeError( - f"LlamaBatch Critical Error: n_tokens ({self.batch.n_tokens}) exceeds capacity ({self.n_tokens_capacity}). " - "This implies a buffer overflow or corrupted internal state." + f"LlamaBatch Critical Error: n_tokens ({self.batch.n_tokens}) exceeds capacity " + f"({self.n_tokens_capacity}). This implies a buffer overflow or " + "corrupted internal state." ) def reset(self): """ - Resets the batch counter to 0. Does not free memory, just resets the index. - Call this before starting a new decoding step. + Reset the logical batch counter. + + This does not free or clear the underlying C buffers. llama_decode only + reads entries in [0, batch.n_tokens), so resetting n_tokens is enough and + matches llama.cpp's reusable batch pattern. """ - if self.batch is not None: - self.batch.n_tokens = 0 + if self.batch is None: + return + self.batch.n_tokens = 0 + + def _require_open(self, where: str) -> None: + if self.batch is None: + raise RuntimeError(f"LlamaBatch.{where}: batch has been closed.") + + def _require_token_buffer(self, where: str) -> None: + """ + Require that batch.token is available. + + llama_batch_init allocates batch.token only when embd == 0. Some advanced + llama.cpp paths manually create mixed token+embd batches, but this Python + token API should only write token ids when batch.token is non-null. + """ + self._require_open(where) + + if not bool(self.batch.token): + raise RuntimeError( + f"LlamaBatch.{where} requires a token buffer, but batch.token is NULL. " + "This batch was likely initialized as an embedding batch. Use a " + "separate embedding or mixed-batch path instead." + ) def add_token(self, token: int, pos: int, seq_ids: Sequence[int], logits: bool): """ @@ -1091,6 +1132,8 @@ def add_token(self, token: int, pos: int, seq_ids: Sequence[int], logits: bool): A single token can be part of multiple sequences simultaneously. logits: A boolean flag indicating whether the backend should compute logits for this token. """ + self._require_token_buffer("add_token") + idx = self.batch.n_tokens if idx >= self.n_tokens_capacity: raise IndexError(f"LlamaBatch overflow[add_token]: Cannot add token. Capacity {self.n_tokens_capacity} reached.") @@ -1114,23 +1157,37 @@ def add_sequence( self, token_array: Sequence[int], pos_array: Sequence[int], - seq_ids: Sequence[Sequence[int]], + seq_ids: Sequence[int], logits_array: Sequence[bool] ): """ - Adds a sequence of tokens to the batch in a vectorized manner. - Strictly maps the provided arrays to the underlying C++ batch structure without subjective overriding. + Adds a sequence of tokens to the batch. Args: - token_array: A sequence of token IDs to be evaluated. - pos_array: A sequence of logical positions corresponding to each token. - seq_id_array: A sequence of lists, where each list contains the sequence IDs for the respective token. - (e.g., [[0], [0], [0]] for 3 tokens belonging to sequence 0). - logits_array: A sequence of boolean flags indicating whether to compute logits for each token. + token_array: Token ids to evaluate. + pos_array: Logical positions for each token. + seq_ids: Sequence ids shared by every token in this call, usually [0]. + A token can belong to multiple sequences, for example [0, 1], + matching llama.cpp's per-token seq_id list. + logits_array: Whether to request logits/output for each token. """ + self._require_token_buffer("add_sequence") + n_tokens = len(token_array) current_count = self.batch.n_tokens + if len(pos_array) != n_tokens: + raise ValueError( + f"LlamaBatch.add_sequence: pos_array length mismatch: " + f"{len(pos_array)} != {n_tokens}." + ) + + if len(logits_array) != n_tokens: + raise ValueError( + f"LlamaBatch.add_sequence: logits_array length mismatch: " + f"{len(logits_array)} != {n_tokens}." + ) + if current_count + n_tokens > self.n_tokens_capacity: raise IndexError( f"LlamaBatch overflow[add_sequence]: Cannot add {n_tokens} tokens. " @@ -1138,12 +1195,23 @@ def add_sequence( ) n_seq_id = len(seq_ids) + if n_seq_id <= 0: + raise ValueError("LlamaBatch Error[add_sequence]: seq_ids must not be empty.") + if n_seq_id > self.n_seq_max: raise ValueError(f"LlamaBatch Error[add_sequence]: Token belongs to {n_seq_id} sequences, " f"but n_seq_max was initialized to {self.n_seq_max}.") + for seq_id in seq_ids: + if seq_id < 0 or seq_id >= self.n_seq_max: + raise ValueError( + f"LlamaBatch Error[add_sequence]: invalid seq_id {seq_id}; " + f"expected 0 <= seq_id < {self.n_seq_max}" + ) + for i in range(n_tokens): j = current_count + i + self.batch.token[j] = token_array[i] self.batch.pos[j] = pos_array[i] From a5ceecb723a58450ab5d9ee9fbfcb492a7f31c4b Mon Sep 17 00:00:00 2001 From: JamePeng Date: Fri, 10 Jul 2026 05:15:23 +0800 Subject: [PATCH 25/86] feat: add embedding rows to LlamaBatch - Add shared seq_id validation for token and embedding batch writes. - Introduce embedding-buffer checks plus add_embedding and add_embeddings helpers for embd-only llama_batch inputs, enabling decoder paths that consume external embedding rows while keeping token writes restricted to token buffers. - This prepares LlamaBatch for embedding-only decode paths, such as speculative decoding feature injection or external encoder/projector outputs. - It does not implement mixed token+embedding batches yet; those still need a separate ownership-safe design for the token buffer. Signed-off-by: JamePeng --- llama_cpp/_internals.py | 183 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 165 insertions(+), 18 deletions(-) diff --git a/llama_cpp/_internals.py b/llama_cpp/_internals.py index 5058b7e32..c8f049673 100644 --- a/llama_cpp/_internals.py +++ b/llama_cpp/_internals.py @@ -1119,6 +1119,33 @@ def _require_token_buffer(self, where: str) -> None: "separate embedding or mixed-batch path instead." ) + def _validate_seq_ids(self, seq_ids: Sequence[int], where: str) -> int: + n_seq_id = len(seq_ids) + + if n_seq_id <= 0: + raise ValueError(f"LlamaBatch.{where}: seq_ids must not be empty.") + + if n_seq_id > self.n_seq_max: + raise ValueError( + f"LlamaBatch.{where}: token belongs to {n_seq_id} sequences, " + f"but n_seq_max was initialized to {self.n_seq_max}." + ) + + for seq_id in seq_ids: + if not isinstance(seq_id, int): + raise ValueError( + f"LlamaBatch.{where}: seq_id must be int, got " + f"{type(seq_id).__name__}." + ) + + if seq_id < 0 or seq_id >= self.n_seq_max: + raise ValueError( + f"LlamaBatch.{where}: invalid seq_id {seq_id}; " + f"expected 0 <= seq_id < {self.n_seq_max}." + ) + + return n_seq_id + def add_token(self, token: int, pos: int, seq_ids: Sequence[int], logits: bool): """ Adds a single token to the batch. @@ -1141,10 +1168,8 @@ def add_token(self, token: int, pos: int, seq_ids: Sequence[int], logits: bool): self.batch.token[idx] = token self.batch.pos[idx] = pos - n_seq_id = len(seq_ids) - if n_seq_id > self.n_seq_max: - raise ValueError(f"LlamaBatch Error[add_token]: Token belongs to {n_seq_id} sequences, " - f"but n_seq_max was initialized to {self.n_seq_max}.") + n_seq_id = self._validate_seq_ids(seq_ids, "add_token") + self.batch.n_seq_id[idx] = n_seq_id for i, seq_id in enumerate(seq_ids): @@ -1194,20 +1219,7 @@ def add_sequence( f"Space left: {self.n_tokens_capacity - current_count}" ) - n_seq_id = len(seq_ids) - if n_seq_id <= 0: - raise ValueError("LlamaBatch Error[add_sequence]: seq_ids must not be empty.") - - if n_seq_id > self.n_seq_max: - raise ValueError(f"LlamaBatch Error[add_sequence]: Token belongs to {n_seq_id} sequences, " - f"but n_seq_max was initialized to {self.n_seq_max}.") - - for seq_id in seq_ids: - if seq_id < 0 or seq_id >= self.n_seq_max: - raise ValueError( - f"LlamaBatch Error[add_sequence]: invalid seq_id {seq_id}; " - f"expected 0 <= seq_id < {self.n_seq_max}" - ) + n_seq_id = self._validate_seq_ids(seq_ids, "add_sequence") for i in range(n_tokens): j = current_count + i @@ -1223,6 +1235,141 @@ def add_sequence( self.batch.n_tokens += n_tokens + def _require_embedding_buffer(self, where: str) -> None: + self._require_open(where) + + if self.embd <= 0: + raise RuntimeError( + f"LlamaBatch.{where} requires an embedding batch, but embd={self.embd}." + ) + + if not bool(self.batch.embd): + raise RuntimeError( + f"LlamaBatch.{where} requires batch.embd, but batch.embd is NULL." + ) + + def add_embedding( + self, + embedding: Sequence[float], + pos: int, + seq_ids: Sequence[int], + logits: bool = False, + ) -> None: + """ + Add one embedding row to an embedding batch. + + This is for embd-only llama_batch input: + batch.token == NULL + batch.embd != NULL + + Args: + embedding: One embedding vector of length self.embd. + pos: Logical sequence position. + seq_ids: Sequence ids this embedding belongs to, usually [0]. + logits: Whether to request output for this row. + """ + self._require_embedding_buffer("add_embedding") + + if len(embedding) != self.embd: + raise ValueError( + f"LlamaBatch.add_embedding: embedding length mismatch: " + f"{len(embedding)} != embd({self.embd})." + ) + + idx = self.batch.n_tokens + if idx >= self.n_tokens_capacity: + raise IndexError( + f"LlamaBatch overflow[add_embedding]: capacity " + f"{self.n_tokens_capacity} reached." + ) + + n_seq_id = self._validate_seq_ids(seq_ids, "add_embedding") + + base = idx * self.embd + for d, value in enumerate(embedding): + self.batch.embd[base + d] = float(value) + + self.batch.pos[idx] = pos + self.batch.n_seq_id[idx] = n_seq_id + + for i, seq_id in enumerate(seq_ids): + self.batch.seq_id[idx][i] = seq_id + + self.batch.logits[idx] = logits + self.batch.n_tokens += 1 + + def add_embeddings( + self, + embeddings: Sequence[float], + *, + pos_array: Sequence[int], + seq_ids: Sequence[int], + logits_array: Optional[Sequence[bool]] = None, + ) -> None: + """ + Add multiple embedding rows to an embedding batch. + + embeddings layout: + row-major [n_tokens, self.embd] + + The number of rows is inferred from pos_array. This method supports + embedding-only llama_batch inputs: + + batch.token == NULL + batch.embd != NULL + + It only supports one logical position per embedding row. M-RoPE media + embedding batches should continue to use MTMD helper APIs. + """ + self._require_embedding_buffer("add_embeddings") + + n_tokens = len(pos_array) + if n_tokens <= 0: + raise ValueError("LlamaBatch.add_embeddings: pos_array must not be empty.") + + if logits_array is None: + logits_array = [False] * n_tokens + elif len(logits_array) != n_tokens: + raise ValueError( + f"LlamaBatch.add_embeddings: logits_array length mismatch: " + f"{len(logits_array)} != {n_tokens}." + ) + + expected = n_tokens * self.embd + if len(embeddings) != expected: + raise ValueError( + f"LlamaBatch.add_embeddings: embeddings length mismatch: " + f"{len(embeddings)} != n_tokens({n_tokens}) * embd({self.embd}) = {expected}." + ) + + current_count = self.batch.n_tokens + if current_count + n_tokens > self.n_tokens_capacity: + raise IndexError( + f"LlamaBatch overflow[add_embeddings]: cannot add {n_tokens} rows. " + f"Space left: {self.n_tokens_capacity - current_count}." + ) + + n_seq_id = self._validate_seq_ids(seq_ids, "add_embeddings") + + for i in range(n_tokens): + j = current_count + i + + src_base = i * self.embd + dst_base = j * self.embd + + for d in range(self.embd): + self.batch.embd[dst_base + d] = float(embeddings[src_base + d]) + + self.batch.pos[j] = int(pos_array[i]) + self.batch.n_seq_id[j] = n_seq_id + + for k, seq_id in enumerate(seq_ids): + self.batch.seq_id[j][k] = int(seq_id) + + self.batch.logits[j] = int(logits_array[i]) + + self.batch.n_tokens += n_tokens + # Embedding functions def normalize_embedding(embedding): From e3bd06bfde0018116e429bf6fbe6917ed0aa410e Mon Sep 17 00:00:00 2001 From: JamePeng Date: Fri, 10 Jul 2026 21:23:28 +0800 Subject: [PATCH 26/86] Update Submodule vendor/llama.cpp 049326a..a935fbf Signed-off-by: JamePeng --- vendor/llama.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/llama.cpp b/vendor/llama.cpp index 049326a00..a935fbffe 160000 --- a/vendor/llama.cpp +++ b/vendor/llama.cpp @@ -1 +1 @@ -Subproject commit 049326a00025d00b08cc188ed716b681e984a3f8 +Subproject commit a935fbffe1a3d31509c325c116454ab5d56b2eb8 From 11364d1cb0ae10e3a951375c4ca29e99d6213206 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Fri, 10 Jul 2026 22:14:38 +0800 Subject: [PATCH 27/86] feat(LlamaBatch): add mixed token embedding batch support - Add optional mixed=True initialization for LlamaBatch so token+embedding rows can be represented in a single llama_batch. Mixed batches keep the native embd buffer from llama_batch_init and attach a Python-owned token buffer, which is cleared before llama_batch_free() to avoid invalid ownership. - Route token-only and embedding-only write APIs away from mixed batches, add mixed-batch validation, and introduce add_token_embedding for EAGLE3/MTP-style decoder inputs containing both token ids and embedding vectors. - This prepares LlamaBatch for speculative decoding paths that require mixed token+hidden-state inputs, especially EAGLE3 and MTP. It keeps ordinary token-only and embedding-only APIs separated while providing a dedicated add_token_embedding path for mixed decoder rows. Signed-off-by: JamePeng --- llama_cpp/_internals.py | 117 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/llama_cpp/_internals.py b/llama_cpp/_internals.py index c8f049673..4eb2df7db 100644 --- a/llama_cpp/_internals.py +++ b/llama_cpp/_internals.py @@ -1004,6 +1004,7 @@ def __init__( n_tokens: int, embd: int, n_seq_max: int, + mixed: bool = False, verbose: bool = True ): # logical validity of parameters @@ -1013,11 +1014,16 @@ def __init__( raise ValueError(f"LlamaBatch[__init__]: embd must be non-negative, got {embd}") if n_seq_max <= 0: raise ValueError(f"LlamaBatch[__init__]: n_seq_max must be positive, got {n_seq_max}") + if mixed and embd <= 0: + raise ValueError("LlamaBatch[__init__]: mixed batch requires embd > 0.") self.n_tokens_capacity = n_tokens self.embd = embd self.n_seq_max = n_seq_max + self.mixed = mixed self.verbose = verbose + self._token_buf = None + self._owns_token = False self._exit_stack = ExitStack() # llama_batch_init allocates either batch.token or batch.embd: @@ -1040,17 +1046,42 @@ def __init__( f"llama_batch_init({n_tokens},{embd},{n_seq_max})" ) + if mixed: + if bool(batch.token): + raise RuntimeError( + "LlamaBatch[__init__]: expected batch.token to be NULL for " + "mixed embedding batch initialized with embd > 0." + ) + if not bool(batch.embd): + raise RuntimeError( + "LlamaBatch[__init__]: expected batch.embd to be non-NULL " + "for mixed batch." + ) + + self._token_buf = ( + llama_cpp.llama_token * self.n_tokens_capacity + )() + batch.token = self._token_buf + self._owns_token = True + self.batch = batch def close(self): """Manually free LlamaBatch resources.""" if getattr(self, "batch", None) is not None: try: + if getattr(self, "_owns_token", False): + # batch.token points to a Python-owned ctypes buffer in mixed mode. + # llama_batch_free() would call free(batch.token), so clear it first. + self.batch.token = None llama_cpp.llama_batch_free(self.batch) except Exception: pass self.batch = None + self._token_buf = None + self._owns_token = False + if getattr(self, "_exit_stack", None) is not None and hasattr(self._exit_stack, "close"): self._exit_stack.close() self._exit_stack = None @@ -1112,6 +1143,12 @@ def _require_token_buffer(self, where: str) -> None: """ self._require_open(where) + if self.mixed: + raise RuntimeError( + f"LlamaBatch.{where} is for token-only batches. " + "Use add_token_embedding for mixed batches." + ) + if not bool(self.batch.token): raise RuntimeError( f"LlamaBatch.{where} requires a token buffer, but batch.token is NULL. " @@ -1238,6 +1275,12 @@ def add_sequence( def _require_embedding_buffer(self, where: str) -> None: self._require_open(where) + if self.mixed: + raise RuntimeError( + f"LlamaBatch.{where} is for embedding-only batches. " + "Use add_token_embedding for mixed batches." + ) + if self.embd <= 0: raise RuntimeError( f"LlamaBatch.{where} requires an embedding batch, but embd={self.embd}." @@ -1370,6 +1413,80 @@ def add_embeddings( self.batch.n_tokens += n_tokens + def _require_mixed_buffer(self, where: str) -> None: + self._require_open(where) + + if not self.mixed: + raise RuntimeError( + f"LlamaBatch.{where} requires mixed=True batch." + ) + + if self.embd <= 0: + raise RuntimeError( + f"LlamaBatch.{where} requires mixed token+embedding batch, " + f"but embd={self.embd}." + ) + + if not bool(self.batch.token): + raise RuntimeError( + f"LlamaBatch.{where} requires batch.token, but batch.token is NULL." + ) + + if not bool(self.batch.embd): + raise RuntimeError( + f"LlamaBatch.{where} requires batch.embd, but batch.embd is NULL." + ) + + def add_token_embedding( + self, + token: int, + embedding: Sequence[float], + pos: int, + seq_ids: Sequence[int], + logits: bool, + ) -> None: + """ + Add one mixed token+embedding row. + + This is for EAGLE3/MTP-style decoder inputs where each batch row contains: + token id + embedding vector + position + seq ids + logits flag + """ + self._require_mixed_buffer("add_token_embedding") + + if len(embedding) != self.embd: + raise ValueError( + f"LlamaBatch.add_token_embedding: embedding length mismatch: " + f"{len(embedding)} != embd({self.embd})." + ) + + idx = self.batch.n_tokens + if idx >= self.n_tokens_capacity: + raise IndexError( + f"LlamaBatch overflow[add_token_embedding]: capacity " + f"{self.n_tokens_capacity} reached." + ) + + n_seq_id = self._validate_seq_ids(seq_ids, "add_token_embedding") + + self.batch.token[idx] = token + + base = idx * self.embd + for d, value in enumerate(embedding): + self.batch.embd[base + d] = float(value) + + self.batch.pos[idx] = pos + self.batch.n_seq_id[idx] = n_seq_id + + for i, seq_id in enumerate(seq_ids): + self.batch.seq_id[idx][i] = seq_id + + self.batch.logits[idx] = logits + self.batch.n_tokens += 1 + # Embedding functions def normalize_embedding(embedding): From 7f59a8611d76b4e202750355df7d01f3270a15da Mon Sep 17 00:00:00 2001 From: JamePeng Date: Fri, 10 Jul 2026 23:42:27 +0800 Subject: [PATCH 28/86] fix(mtmd): validate MTMD inputs before tokenization - Add Python-side MTMD input validation before calling the native mtmd_tokenize path. Normalize missing bitmap lists to empty lists for pure text prompts, check that rendered media markers match decoded bitmap inputs, reject missing bitmap entries, and validate that the media marker is available. - Improve media placeholder mismatch errors with marker counts and marker details, and surface mtmd_tokenize failures with richer diagnostic context including media counts and backend support flags. Signed-off-by: JamePeng --- llama_cpp/llama_multimodal.py | 130 ++++++++++++++++++++++++++++------ 1 file changed, 108 insertions(+), 22 deletions(-) diff --git a/llama_cpp/llama_multimodal.py b/llama_cpp/llama_multimodal.py index 5a41e5836..a8802baf6 100644 --- a/llama_cpp/llama_multimodal.py +++ b/llama_cpp/llama_multimodal.py @@ -565,6 +565,10 @@ def _replace_media_placeholders( media payloads. """ media_marker = self.media_marker + if not media_marker: + raise ValueError( + f"{self.log_prefix}(_replace_media_placeholders): media marker must not be empty." + ) # 1. Replace known template-specific media tags first. # @@ -583,12 +587,19 @@ def _replace_media_placeholders( if url and url in text: text = text.replace(url, media_marker, 1) - # 3. Validate only after all normalization is complete. + # 3. Validate after all normalization is complete. marker_count = text.count(media_marker) - if marker_count != len(media_items): + media_count = len(media_items) + + if marker_count != media_count: raise ValueError( - f"{self.log_prefix}(_replace_media_placeholders): media marker mismatch " - f"(marker_count={marker_count}, media_count={len(media_items)})" + f"{self.log_prefix}(_replace_media_placeholders): media marker mismatch\n" + f"- marker_count={marker_count}\n" + f"- media_count={media_count}\n" + f"- media_marker={media_marker!r}\n" + "Each media item must render to exactly one MTMD media marker. " + "Check whether the chat template rendered both a media tag and the " + "original URL/data URI, or failed to render a media placeholder." ) return text @@ -620,11 +631,68 @@ def _render_and_replace_media( media_items=media_items, ) + def _validate_mtmd_inputs( + self, + *, + text: str, + bitmaps: Optional[List[Any]] = None, + ) -> None: + """ + Validate Python-side MTMD tokenizer inputs before calling mtmd_tokenize. + + This mirrors the most important checks in llama.cpp mtmd_tokenizer: + - mtmd context must be initialized + - rendered text must be a string + - media marker must not be empty + - media marker count must match bitmap count + - bitmap entries must not be None + + Pure text input is valid: + bitmaps is None or [] + marker_count == 0 + """ + if self.mtmd_ctx is None: + raise ValueError( + f"{self.log_prefix}(_validate_mtmd_inputs): mtmd context not initialized." + ) + + if not isinstance(text, str): + raise TypeError( + f"{self.log_prefix}(_validate_mtmd_inputs): text must be str, " + f"got {type(text).__name__}." + ) + + if not self.media_marker: + raise ValueError( + f"{self.log_prefix}(_validate_mtmd_inputs): media marker must not be empty." + ) + + if bitmaps is None: + bitmaps = [] + + marker_count = text.count(self.media_marker) + bitmap_count = len(bitmaps) + + if marker_count != bitmap_count: + raise ValueError( + f"{self.log_prefix}(_validate_mtmd_inputs): media marker mismatch\n" + f"- marker_count={marker_count}\n" + f"- bitmap_count={bitmap_count}\n" + f"- media_marker={self.media_marker!r}\n" + "The rendered prompt must contain exactly one media marker per decoded media input." + ) + + for i, bitmap in enumerate(bitmaps): + if bitmap is None: + raise ValueError( + f"{self.log_prefix}(_validate_mtmd_inputs): bitmap[{i}] is None." + ) + def _mtmd_tokenize( self, llama: "llama_core.Llama", text: str, - bitmaps: Optional[list] = None, + bitmaps: Optional[List[Any]] = None, chunks: Optional[Any] = None, ) -> Any: """ @@ -637,7 +705,19 @@ def _mtmd_tokenize( - stable interface for future async/batch decoding - isolated error handling for tokenizer failures - clean separation between prompt building and C++ binding + - strict Python-side marker/bitmap validation before native tokenization + + Pure text input is valid: + bitmaps is None or [] + marker_count == 0 """ + if bitmaps is None: + bitmaps = [] + + self._validate_mtmd_inputs( + text=text, + bitmaps=bitmaps, + ) if chunks is None: chunks = self._mtmd_cpp.mtmd_input_chunks_init() @@ -646,30 +726,19 @@ def _mtmd_tokenize( f"{self.log_prefix}(_mtmd_tokenize): failed to init mtmd_input_chunks" ) - # Validate strict alignment between rendered media markers and provided bitmaps - # to ensure MTMD tokenization consistency and prevent decoding mismatch errors. - if bitmaps is not None: - marker_count = text.count(self.media_marker) - if marker_count != len(bitmaps): - raise ValueError( - f"{self.log_prefix}(_mtmd_tokenize): marker mismatch " - f"(marker_count={marker_count}, bitmap_count={len(bitmaps)})" - ) - input_text = self._mtmd_cpp.mtmd_input_text() input_text.text = ctypes.c_char_p(text.encode("utf-8")) input_text.add_special = (llama.n_tokens == 0) input_text.parse_special = True - bitmap_array = None - n_bitmaps = 0 + n_bitmaps = len(bitmaps) - if bitmaps: - n_bitmaps = len(bitmaps) - bitmap_array = (self._mtmd_cpp.mtmd_bitmap_p_ctypes * n_bitmaps)(*bitmaps) + if n_bitmaps > 0: + bitmap_array = ( + self._mtmd_cpp.mtmd_bitmap_p_ctypes * n_bitmaps + )(*bitmaps) else: bitmap_array = None - n_bitmaps = 0 result = self._mtmd_cpp.mtmd_tokenize( self.mtmd_ctx, @@ -680,11 +749,19 @@ def _mtmd_tokenize( ) if result != 0: + marker_count = text.count(self.media_marker) raise ValueError( - f"{self.log_prefix}(_mtmd_tokenize): tokenize failed\n" + f"{self.log_prefix}(_mtmd_tokenize): mtmd_tokenize failed\n" f"- result={result}\n" f"- text_len={len(text)}\n" + f"- marker_count={marker_count}\n" f"- n_bitmaps={n_bitmaps}\n" + f"- supports_vision={self.is_support_vision}\n" + f"- supports_audio={self.is_support_audio}\n" + f"- supports_video={self.is_support_video}\n" + "Possible causes: marker/bitmap mismatch, invalid image/audio data, " + "unsupported vision/audio projector, failed media preprocessing, " + "or text tokenization failure." ) return chunks @@ -859,6 +936,15 @@ def _create_bitmap_func(idx: int, item: dict): else: raise TypeError(f"{self.log_prefix}(mtmd_input_chunk_get_type): Invalid chunk type, chunk_type = {chunk_type}.") + if media_items_cur != media_items_count: + raise RuntimeError( + f"{self.log_prefix}(_process_mtmd_prompt): not all media inputs were consumed by MTMD chunks\n" + f"- consumed={media_items_cur}\n" + f"- media_items={media_items_count}\n" + "This usually means the rendered prompt did not produce enough media chunks, " + "or the chat template/media marker normalization is incorrect." + ) + return full_prompt_ids, chunk_token_spans, chunks, bitmap_cleanup except Exception as e: From 36bdecf341852ee7975ceefd1ea7350cba61070f Mon Sep 17 00:00:00 2001 From: JamePeng Date: Sat, 11 Jul 2026 21:11:18 +0800 Subject: [PATCH 29/86] fix(ggml): load ggml-base before ggml library - Load ggml-base shared library before ggml to ensure the base runtime dependency is initialized prior to loading the main ggml library. - This improves dynamic library loading reliability on platforms where ggml depends on ggml-base during initialization. Signed-off-by: JamePeng --- llama_cpp/_ggml.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/llama_cpp/_ggml.py b/llama_cpp/_ggml.py index c4ae7c94b..6ab16a971 100644 --- a/llama_cpp/_ggml.py +++ b/llama_cpp/_ggml.py @@ -27,14 +27,14 @@ libggml_base_path / "bin", ] -libggml = load_shared_library("ggml", libggml_base_paths) - -ggml_function = ctypes_function_for_shared_library(libggml) - libggml_base = load_shared_library("ggml-base", libggml_base_paths) ggml_base_function = ctypes_function_for_shared_library(libggml_base) +libggml = load_shared_library("ggml", libggml_base_paths) + +ggml_function = ctypes_function_for_shared_library(libggml) + # // ====== ggml.h ====== GGML_FILE_MAGIC = 0x67676d6c # b"ggml" From 1992f6a43e297ac8602ccd51ec2c6c9662576062 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Sat, 11 Jul 2026 21:14:03 +0800 Subject: [PATCH 30/86] build: Update CMakeLists.txt - append ggml-et backend Signed-off-by: JamePeng --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b2cfeeb8..2286fe5ee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -336,6 +336,7 @@ if (LLAMA_BUILD) set(GGML_BACKEND_TARGETS ggml-cann ggml-cuda + ggml-et ggml-hexagon ggml-hip ggml-metal From 6c954f42d1622e4f211abd3265f1d4fe2172f528 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Sat, 11 Jul 2026 21:21:24 +0800 Subject: [PATCH 31/86] Update Submodule vendor/llama.cpp a935fbf..c92e806 Signed-off-by: JamePeng --- llama_cpp/_ggml.py | 102 +++++++++++++++++++++++---------------------- vendor/llama.cpp | 2 +- 2 files changed, 54 insertions(+), 50 deletions(-) diff --git a/llama_cpp/_ggml.py b/llama_cpp/_ggml.py index 6ab16a971..0cb20d40e 100644 --- a/llama_cpp/_ggml.py +++ b/llama_cpp/_ggml.py @@ -292,6 +292,7 @@ class GGMLFType(enum.IntEnum): # GGML_OP_IM2COL, # GGML_OP_IM2COL_BACK, # GGML_OP_IM2COL_3D, +# GGML_OP_COL2IM_1D, # GGML_OP_CONV_2D, # GGML_OP_CONV_3D, # GGML_OP_CONV_2D_DW, @@ -324,6 +325,7 @@ class GGMLFType(enum.IntEnum): # GGML_OP_RWKV_WKV7, # GGML_OP_SOLVE_TRI, # GGML_OP_GATED_DELTA_NET, +# GGML_OP_LIGHTNING_INDEXER, # GGML_OP_UNARY, @@ -401,55 +403,57 @@ class GGML_OP(enum.IntEnum): GGML_OP_IM2COL = 52 GGML_OP_IM2COL_BACK = 53 GGML_OP_IM2COL_3D = 54 - GGML_OP_CONV_2D = 55 - GGML_OP_CONV_3D = 56 - GGML_OP_CONV_2D_DW = 57 - GGML_OP_CONV_TRANSPOSE_2D = 58 - GGML_OP_POOL_1D = 59 - GGML_OP_POOL_2D = 60 - GGML_OP_POOL_2D_BACK = 61 - GGML_OP_UPSCALE = 62 - GGML_OP_PAD = 63 - GGML_OP_PAD_REFLECT_1D = 64 - GGML_OP_ROLL = 65 - GGML_OP_ARANGE = 66 - GGML_OP_TIMESTEP_EMBEDDING = 67 - GGML_OP_ARGSORT = 68 - GGML_OP_TOP_K = 69 - GGML_OP_LEAKY_RELU = 70 - GGML_OP_TRI = 71 - GGML_OP_FILL = 72 - - GGML_OP_FLASH_ATTN_EXT = 73 - GGML_OP_FLASH_ATTN_BACK = 74 - GGML_OP_SSM_CONV = 75 - GGML_OP_SSM_SCAN = 76 - GGML_OP_WIN_PART = 77 - GGML_OP_WIN_UNPART = 78 - GGML_OP_GET_REL_POS = 79 - GGML_OP_ADD_REL_POS = 80 - GGML_OP_RWKV_WKV6 = 81 - GGML_OP_GATED_LINEAR_ATTN = 82 - GGML_OP_RWKV_WKV7 = 83 - GGML_OP_SOLVE_TRI = 84 - GGML_OP_GATED_DELTA_NET = 85 - - GGML_OP_UNARY = 86 - - GGML_OP_MAP_CUSTOM1 = 87 - GGML_OP_MAP_CUSTOM2 = 88 - GGML_OP_MAP_CUSTOM3 = 89 - - GGML_OP_CUSTOM = 90 - - GGML_OP_CROSS_ENTROPY_LOSS = 91 - GGML_OP_CROSS_ENTROPY_LOSS_BACK = 92 - GGML_OP_OPT_STEP_ADAMW = 93 - GGML_OP_OPT_STEP_SGD = 94 - - GGML_OP_GLU = 95 - - GGML_OP_COUNT = 96 + GGML_OP_COL2IM_1D = 55 + GGML_OP_CONV_2D = 56 + GGML_OP_CONV_3D = 57 + GGML_OP_CONV_2D_DW = 58 + GGML_OP_CONV_TRANSPOSE_2D = 59 + GGML_OP_POOL_1D = 60 + GGML_OP_POOL_2D = 61 + GGML_OP_POOL_2D_BACK = 62 + GGML_OP_UPSCALE = 63 + GGML_OP_PAD = 64 + GGML_OP_PAD_REFLECT_1D = 65 + GGML_OP_ROLL = 66 + GGML_OP_ARANGE = 67 + GGML_OP_TIMESTEP_EMBEDDING = 68 + GGML_OP_ARGSORT = 69 + GGML_OP_TOP_K = 70 + GGML_OP_LEAKY_RELU = 71 + GGML_OP_TRI = 72 + GGML_OP_FILL = 73 + + GGML_OP_FLASH_ATTN_EXT = 74 + GGML_OP_FLASH_ATTN_BACK = 75 + GGML_OP_SSM_CONV = 76 + GGML_OP_SSM_SCAN = 77 + GGML_OP_WIN_PART = 78 + GGML_OP_WIN_UNPART = 79 + GGML_OP_GET_REL_POS = 80 + GGML_OP_ADD_REL_POS = 81 + GGML_OP_RWKV_WKV6 = 82 + GGML_OP_GATED_LINEAR_ATTN = 83 + GGML_OP_RWKV_WKV7 = 84 + GGML_OP_SOLVE_TRI = 85 + GGML_OP_GATED_DELTA_NET = 86 + GGML_OP_LIGHTNING_INDEXER = 87 + + GGML_OP_UNARY = 88 + + GGML_OP_MAP_CUSTOM1 = 89 + GGML_OP_MAP_CUSTOM2 = 90 + GGML_OP_MAP_CUSTOM3 = 91 + + GGML_OP_CUSTOM = 92 + + GGML_OP_CROSS_ENTROPY_LOSS = 93 + GGML_OP_CROSS_ENTROPY_LOSS_BACK = 94 + GGML_OP_OPT_STEP_ADAMW = 95 + GGML_OP_OPT_STEP_SGD = 96 + + GGML_OP_GLU = 97 + + GGML_OP_COUNT = 98 # enum ggml_unary_op { # GGML_UNARY_OP_ABS, diff --git a/vendor/llama.cpp b/vendor/llama.cpp index a935fbffe..c92e806d1 160000 --- a/vendor/llama.cpp +++ b/vendor/llama.cpp @@ -1 +1 @@ -Subproject commit a935fbffe1a3d31509c325c116454ab5d56b2eb8 +Subproject commit c92e806d1c81091c9035edce99c35374da1b465e From 516ec3fbb91d02f710d36a8cab7e45a63884260d Mon Sep 17 00:00:00 2001 From: JamePeng Date: Sun, 12 Jul 2026 00:10:37 +0800 Subject: [PATCH 32/86] fix(loader): improve Windows DLL search path handling and diagnostics - Remove duplicated Windows DLL directory registration logic - Add optional CUDA, HIP, and Vulkan runtime DLL search paths - Keep bundled library paths with correct priority order for loading - Add comments explaining DLL search path ordering behavior - Add load source diagnostics for system and bundled libraries - Improve visibility when debugging shared library loading issues Signed-off-by: JamePeng --- llama_cpp/_ctypes_extensions.py | 41 +++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/llama_cpp/_ctypes_extensions.py b/llama_cpp/_ctypes_extensions.py index 1a9f8eb8c..1b4c05ff2 100644 --- a/llama_cpp/_ctypes_extensions.py +++ b/llama_cpp/_ctypes_extensions.py @@ -90,17 +90,8 @@ def load_shared_library(lib_base_name: str, base_paths: Union[pathlib.Path, list # Add the library directory to the DLL search path on Windows (if needed) if sys.platform == "win32": - for base_path in base_paths: - p = pathlib.Path(base_path) - if p.exists() and p.is_dir(): - os.add_dll_directory(str(p)) - os.environ["PATH"] = str(p) + os.pathsep + os.environ["PATH"] - if sys.platform == "win32" and sys.version_info >= (3, 9): - for base_path in base_paths: - p = pathlib.Path(base_path) - if p.exists() and p.is_dir(): - os.add_dll_directory(str(p)) + # Add CUDA runtime DLL directories if CUDA is available. if "CUDA_PATH" in os.environ: cuda_path = os.environ["CUDA_PATH"] sub_dirs_to_add = [ @@ -114,14 +105,36 @@ def load_shared_library(lib_base_name: str, base_paths: Union[pathlib.Path, list if os.path.exists(full_path): os.add_dll_directory(full_path) + # Add HIP runtime DLL directories when HIP backend is available. if "HIP_PATH" in os.environ: os.add_dll_directory(os.path.join(os.environ["HIP_PATH"], "bin")) os.add_dll_directory(os.path.join(os.environ["HIP_PATH"], "lib")) + # Add Vulkan SDK DLL directories when Vulkan backend is enabled. if "VULKAN_SDK" in os.environ: os.add_dll_directory(os.path.join(os.environ["VULKAN_SDK"], "Bin")) os.add_dll_directory(os.path.join(os.environ["VULKAN_SDK"], "Lib")) + # Add package-provided library directories. + # + # The paths are added in reverse order intentionally. + # This ensures that the first entry in base_paths gets prepended + # to PATH last, making it the highest priority search location. + # + # Example: + # base_paths = [ + # package/lib, + # package/bin, + # ] + # + # After reversed iteration: + # PATH = package/lib;package/bin;... + for base_path in reversed(base_paths): + p = pathlib.Path(base_path) + if p.exists() and p.is_dir(): + os.add_dll_directory(str(p)) + os.environ["PATH"] = str(p) + os.pathsep + os.environ["PATH"] + cdll_args["winmode"] = ctypes.RTLD_GLOBAL errors = [] @@ -130,7 +143,9 @@ def load_shared_library(lib_base_name: str, base_paths: Union[pathlib.Path, list lib_path = find_library(lib_base_name) if lib_path: try: - return ctypes.CDLL(lib_path, **cdll_args) + lib = ctypes.CDLL(lib_path, **cdll_args) + print(f"[llama-cpp-python].find_library: loaded library from {lib_path}") + return lib except Exception as e: errors.append(f"{lib_path}: {e}") @@ -141,7 +156,9 @@ def load_shared_library(lib_base_name: str, base_paths: Union[pathlib.Path, list if lib_path.exists(): try: - return ctypes.CDLL(str(lib_path), **cdll_args) + lib = ctypes.CDLL(str(lib_path), **cdll_args) + print(f"[llama-cpp-python].provided_path: loaded library from {lib_path}") + return lib except Exception as e: errors.append(f"{lib_path}: {e}") From 3da4c603612c3344031b32ffbeb1da1c84bb205a Mon Sep 17 00:00:00 2001 From: JamePeng Date: Sun, 12 Jul 2026 08:34:39 +0800 Subject: [PATCH 33/86] Update Submodule vendor/llama.cpp c92e806..e3546c7 Signed-off-by: JamePeng --- vendor/llama.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/llama.cpp b/vendor/llama.cpp index c92e806d1..e3546c794 160000 --- a/vendor/llama.cpp +++ b/vendor/llama.cpp @@ -1 +1 @@ -Subproject commit c92e806d1c81091c9035edce99c35374da1b465e +Subproject commit e3546c7948e3af463d0b401e6421d5a4c2faf565 From e522cecb93907c67ffe2e339b7009c93d3fb0f59 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Sun, 12 Jul 2026 09:03:57 +0800 Subject: [PATCH 34/86] Bump version to 0.3.42 - This release mainly addresses several issues discovered during real-world backend deployment, especially on Windows environments with dynamic backend loading, as well as improving the robustness of MTMD, batching, and native API interactions. Signed-off-by: JamePeng --- CHANGELOG.md | 95 +++++++++++++++++++++++++++++++++++++++++++ llama_cpp/__init__.py | 2 +- 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83c3beb12..9654d354e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,101 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.3.42] More Reliable Dynamic Backend Loading, Safer MTMD Processing, and Advanced Batch Support + +- fix(loader): improve Windows DLL search path handling and diagnostics + - Remove duplicated Windows DLL directory registration logic + - Add optional CUDA, HIP, and Vulkan runtime DLL search paths + - Keep bundled library paths with correct priority order for loading, need `/lib` > `/bin` + - Add comments explaining DLL search path ordering behavior + - Add load source diagnostics for system and bundled libraries + - Improve visibility when debugging shared library loading issues + + **Note**: + * For most single-DLL backends, the bin directory can still work as a fallback search path. However, some cases may fail due to missing dependencies such as `libomp140.x86_64.dll`. + * For `multi-DLL backends`, such as the `SYCL backend`, which depends on multiple DLLs (`dnnl.dll`, `tbb12.dll`, `mk_*.dll`, etc.), loading ggml-sycl.dll may fail when its dependent DLLs cannot be found, potentially resulting in an `access violation` crash. + * This update ensures that the DLL search path prioritizes /lib instead of /bin during the initial lookup stage, improving backend loading reliability. + * Special thanks to **@allanmeng** for reporting and testing the SYCL backend issue. + +- fix(ggml): load ggml-base before ggml library + - Load ggml-base shared library before ggml to ensure the base + runtime dependency is initialized prior to loading the main ggml + library. + + - This improves dynamic library loading reliability on platforms + where ggml depends on ggml-base during initialization. + +- fix(mtmd): validate MTMD inputs before tokenization + - Add Python-side MTMD input validation before calling the native mtmd_tokenize + path. Normalize missing bitmap lists to empty lists for pure text prompts, check + that rendered media markers match decoded bitmap inputs, reject missing bitmap + entries, and validate that the media marker is available. + + - Improve media placeholder mismatch errors with marker counts and marker details, + and surface mtmd_tokenize failures with richer diagnostic context including media + counts and backend support flags. + +- feat(LlamaBatch): add mixed token embedding batch support + - Add optional mixed=True initialization for LlamaBatch so token+embedding rows can + be represented in a single llama_batch. Mixed batches keep the native embd buffer + from llama_batch_init and attach a Python-owned token buffer, which is cleared + before llama_batch_free() to avoid invalid ownership. + + - Route token-only and embedding-only write APIs away from mixed batches, add + mixed-batch validation, and introduce add_token_embedding for EAGLE3/MTP-style + decoder inputs containing both token ids and embedding vectors. + + - This prepares LlamaBatch for speculative decoding paths that require mixed + token+hidden-state inputs, especially EAGLE3 and MTP. It keeps ordinary + token-only and embedding-only APIs separated while providing a dedicated + add_token_embedding path for mixed decoder rows. + +- feat(LlamaBatch): add embedding rows to LlamaBatch + - Add shared seq_id validation for token and embedding batch writes. + + - Introduce embedding-buffer checks plus add_embedding and add_embeddings helpers + for embd-only llama_batch inputs, enabling decoder paths that consume external + embedding rows while keeping token writes restricted to token buffers. + + - This prepares LlamaBatch for embedding-only decode paths, such as speculative + decoding feature injection or external encoder/projector outputs. + + - It does not implement mixed token+embedding batches yet; those still need a + separate ownership-safe design for the token buffer. + +- fix(LlamaBatch): harden LlamaBatch token writes + - Clarify llama_batch token vs embedding allocation semantics and keep future + embedding/mixed-batch support open. + + - Add token-buffer checks before add_token/add_sequence, validate add_sequence + input lengths and seq_ids, and improve error messages for invalid batch + configuration. + +- fix(eval): validate eval tokens before native decode + - Add token-id validation at the Llama.eval() boundary before context shifting, + batch construction, or llama_decode execution. This prevents invalid token + types, negative token ids, and out-of-vocabulary ids from reaching the native + decode path, where they may otherwise cause hard crashes instead of Python + exceptions. + + - Wrap llama_decode with defensive exception handling in LlamaContext.decode() so + native exceptions are surfaced with clearer diagnostic context. + + - Also include a small token preview in Llama.eval() fatal decode errors to make + backend failures easier to debug without changing the existing recoverable KV + slot handling behavior. + +- fix(types): make assistant message name optional + - Mark the assistant message `name` field as `NotRequired[Optional[str]]` + to match the optional nature of assistant message metadata and avoid + requiring callers to provide `name` in typed chat completion requests. + +- feat: Update llama.cpp to [ggml-org/llama.cpp/commit/e3546c7948e3af463d0b401e6421d5a4c2faf565](https://github.com/ggml-org/llama.cpp/commit/e3546c7948e3af463d0b401e6421d5a4c2faf565) + +- feat: Sync llama.cpp llama/mtmd/ggml API Binding 20260711 + +More information see: https://github.com/JamePeng/llama-cpp-python/compare/169d5e1a43fb6ff4e5b6f5d0f26f1ec8acbd97b8...3da4c603612c3344031b32ffbeb1da1c84bb205a + ## [0.3.41] Template-Driven MTMD, Broader Multimodal Inputs, and Smarter N-Gram Drafting - refactor(mtmd): extract prompt rendering and media marker normalization diff --git a/llama_cpp/__init__.py b/llama_cpp/__init__.py index 3c3aa6690..c6676c294 100644 --- a/llama_cpp/__init__.py +++ b/llama_cpp/__init__.py @@ -1,4 +1,4 @@ from .llama_cpp import * from .llama import * -__version__ = "0.3.41" +__version__ = "0.3.42" From f077c566ec8b4211a00c7687d95eac742e5885b0 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Sun, 12 Jul 2026 09:40:44 +0800 Subject: [PATCH 35/86] ci(metal): get package version from importlib metadata Avoid importing llama_cpp when detecting the package version. This prevents initialization side effects and keeps CI version extraction reliable. Signed-off-by: JamePeng --- .github/workflows/build-wheels-metal.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-wheels-metal.yaml b/.github/workflows/build-wheels-metal.yaml index 2b00d1aba..caca8907f 100644 --- a/.github/workflows/build-wheels-metal.yaml +++ b/.github/workflows/build-wheels-metal.yaml @@ -37,7 +37,7 @@ jobs: id: get_version shell: bash run: | - VERSION=$(python -c "import llama_cpp; print(llama_cpp.__version__)") + VERSION=$(python -c "import importlib.metadata; print(importlib.metadata.version('llama-cpp-python'))") echo "Detected version: $VERSION" echo "version=$VERSION" >> $GITHUB_OUTPUT From e417924131d0a26ade0668f64f7293a7d1c7280c Mon Sep 17 00:00:00 2001 From: JamePeng Date: Mon, 13 Jul 2026 02:07:13 +0800 Subject: [PATCH 36/86] patch(Llama): Increase chunk preview limit to 128 in Llama.eval exception - Raises the maximum tokens captured for the error message preview from 16 to 128, improving visibility into the offending chunk during fatal backend crashes. Signed-off-by: JamePeng --- llama_cpp/llama.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llama_cpp/llama.py b/llama_cpp/llama.py index 2ee8abaf5..f1f71be8e 100644 --- a/llama_cpp/llama.py +++ b/llama_cpp/llama.py @@ -1301,7 +1301,7 @@ def eval( current_batch_size //= 2 except Exception as e: - min_pos = min(current_batch_size, 16) + min_pos = min(current_batch_size, 128) preview = chunk[:min_pos] # Catch fatal backend failures (e.g., Code -2, -3) raise RuntimeError(f"Llama.eval(decode): Fatal Decode Error at Pos {self.n_tokens}, " From 1762647fe473a8cfc25447b53b32316f2ca3c391 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Tue, 14 Jul 2026 22:50:33 +0800 Subject: [PATCH 37/86] Update Submodule vendor/llama.cpp e3546c7..7f575c3 Signed-off-by: JamePeng --- llama_cpp/llama_multimodal.py | 4 +++- llama_cpp/mtmd_cpp.py | 2 ++ vendor/llama.cpp | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/llama_cpp/llama_multimodal.py b/llama_cpp/llama_multimodal.py index a8802baf6..166b69143 100644 --- a/llama_cpp/llama_multimodal.py +++ b/llama_cpp/llama_multimodal.py @@ -727,7 +727,9 @@ def _mtmd_tokenize( ) input_text = self._mtmd_cpp.mtmd_input_text() - input_text.text = ctypes.c_char_p(text.encode("utf-8")) + encoded_text = text.encode("utf-8") + input_text.text = ctypes.c_char_p(encoded_text) + input_text.text_len = len(encoded_text) input_text.add_special = (llama.n_tokens == 0) input_text.parse_special = True diff --git a/llama_cpp/mtmd_cpp.py b/llama_cpp/mtmd_cpp.py index 27a1a56d8..fcfaa86ee 100644 --- a/llama_cpp/mtmd_cpp.py +++ b/llama_cpp/mtmd_cpp.py @@ -178,12 +178,14 @@ class mtmd_pos_type(enum.IntEnum): # struct mtmd_input_text { # const char * text; +# size_t text_len; # bool add_special; # bool parse_special; # }; class mtmd_input_text(Structure): _fields_ = [ ("text", c_char_p), + ("text_len", c_size_t), ("add_special", c_bool), ("parse_special", c_bool), ] diff --git a/vendor/llama.cpp b/vendor/llama.cpp index e3546c794..7f575c39d 160000 --- a/vendor/llama.cpp +++ b/vendor/llama.cpp @@ -1 +1 @@ -Subproject commit e3546c7948e3af463d0b401e6421d5a4c2faf565 +Subproject commit 7f575c39d6a29a40c0ef22278eca6bd4a573c8a6 From 9d6e598623cad6a33684d04b23ced2ef0b86febe Mon Sep 17 00:00:00 2001 From: JamePeng Date: Wed, 15 Jul 2026 06:06:21 +0800 Subject: [PATCH 38/86] refactor(mtmd): cache Generic MTMD chat template resolution - Refactor MTMD chat template handling to resolve and analyze the chat template only once per handler instance instead of on every request. - Add template initialization state, cache parsed media placeholder tags, and support explicit chat template overrides through a dedicated field. Improve lifecycle cleanup by resetting cached template state and MTMD resources during handler close. - This keeps runtime processing focused on message rendering and media tokenization while avoiding repeated chat template resolution overhead. Signed-off-by: JamePeng --- llama_cpp/llama_multimodal.py | 49 ++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/llama_cpp/llama_multimodal.py b/llama_cpp/llama_multimodal.py index 166b69143..9e5b8af47 100644 --- a/llama_cpp/llama_multimodal.py +++ b/llama_cpp/llama_multimodal.py @@ -155,19 +155,23 @@ def __init__( f"{self.log_prefix}(__init__): `extra_template_arguments` must be a dict." ) + self.chat_format_override = chat_template_override self.extra_template_arguments: dict[str, Any] = dict(extra_template_arguments or {}) self.is_support_vision = False self.is_support_audio = False self.is_support_video = False + self.chat_template = None + self._chat_format_parser_tags = [] + self._template_initialized = False + # Pre-compile Jinja template - if (not hasattr(self, "chat_format") or self.chat_format is None) and chat_template_override is None: + if (not hasattr(self, "chat_format") or self.chat_format is None) and self.chat_format_override is not None: + self.chat_format = self.chat_format_override + elif self.chat_format is None and self.chat_format_override is None: self.chat_format = self.CHAT_FORMAT - elif chat_template_override is not None: - self.chat_format = chat_template_override - self._chat_format_parser_tags = [] self._change_chat_template(self.chat_format) self._exit_stack = ExitStack() @@ -250,11 +254,15 @@ def close(self) -> None: if getattr(self, "mtmd_ctx", None) is not None: try: self._mtmd_cpp.mtmd_free(self.mtmd_ctx) + self.mtmd_ctx = None except Exception: pass - self.mtmd_ctx = None - self.mctx_params = None - self.chat_template = None + self.mctx_params = None + self.chat_format = None + self.chat_template = None + self.chat_template_override = None + self._template_initialized = False + self._chat_format_parser_tags = [] if getattr(self, "_exit_stack", None) is not None and hasattr(self._exit_stack, "close"): self._exit_stack.close() @@ -1661,8 +1669,18 @@ def _resolve_chat_format(self, llama: llama_core.Llama) -> str: self.chat_format = chat_format return chat_format - def __call__(self, **kwargs): - llama = kwargs["llama"] + def _ensure_chat_template( + self, + llama: llama_core.Llama, + ) -> None: + """ + Resolve and analyze chat template once. + + Chat template metadata is static for a model instance, + so it should not be recomputed for every request. + """ + if self._template_initialized: + return self._resolve_chat_format(llama) @@ -1675,7 +1693,18 @@ def __call__(self, **kwargs): "a model that provides tokenizer.chat_template metadata." ) - self._chat_format_parser_tags = [tag for tag in self.KNOWN_MEDIA_TAGS if tag in self.chat_format] + self._chat_format_parser_tags = [ + tag + for tag in self.KNOWN_MEDIA_TAGS + if tag in self.chat_format + ] + + self._template_initialized = True + + def __call__(self, **kwargs): + llama = kwargs["llama"] + + self._ensure_chat_template(llama) if self.verbose: print(f"{self.log_prefix} - Start processing", file=sys.stderr) From 5334cb9025219811f10b8f0ac6b319e9c95fc0e3 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Thu, 16 Jul 2026 01:42:58 +0800 Subject: [PATCH 39/86] Update Submodule vendor/llama.cpp 7f575c3..aff6eb6 Signed-off-by: JamePeng --- llama_cpp/_ggml.py | 6 +++++- llama_cpp/llama_cpp.py | 9 +++++++-- vendor/llama.cpp | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/llama_cpp/_ggml.py b/llama_cpp/_ggml.py index 0cb20d40e..42ff66494 100644 --- a/llama_cpp/_ggml.py +++ b/llama_cpp/_ggml.py @@ -122,6 +122,7 @@ class GGMLStatus(enum.IntEnum): # GGML_TYPE_MXFP4 = 39, // MXFP4 (1 block) # GGML_TYPE_NVFP4 = 40, // NVFP4 (4 blocks, E4M3 scale) # GGML_TYPE_Q1_0 = 41, +# GGML_TYPE_Q2_0 = 42, # GGML_TYPE_COUNT = 42, # }; class GGMLType(enum.IntEnum): @@ -159,7 +160,8 @@ class GGMLType(enum.IntEnum): GGML_TYPE_MXFP4 = 39 GGML_TYPE_NVFP4 = 40 GGML_TYPE_Q1_0 = 41 - GGML_TYPE_COUNT = 42 + GGML_TYPE_Q2_0 = 42 + GGML_TYPE_COUNT = 43 # // precision @@ -201,6 +203,7 @@ class GGMLPrec(enum.IntEnum): # GGML_FTYPE_MOSTLY_MXFP4 = 25, // except 1d tensors # GGML_FTYPE_MOSTLY_NVFP4 = 26, // except 1d tensors # GGML_FTYPE_MOSTLY_Q1_0 = 27, // except 1d tensors +# GGML_FTYPE_MOSTLY_Q2_0 = 28, // except 1d tensors # }; class GGMLFType(enum.IntEnum): GGML_FTYPE_UNKNOWN = -1 @@ -230,6 +233,7 @@ class GGMLFType(enum.IntEnum): GGML_FTYPE_MOSTLY_MXFP4 = 25 GGML_FTYPE_MOSTLY_NVFP4 = 26 GGML_FTYPE_MOSTLY_Q1_0 = 27 + GGML_FTYPE_MOSTLY_Q2_0 = 28 # // available tensor operations: diff --git a/llama_cpp/llama_cpp.py b/llama_cpp/llama_cpp.py index a4086fd70..a6bbb26d3 100644 --- a/llama_cpp/llama_cpp.py +++ b/llama_cpp/llama_cpp.py @@ -364,6 +364,7 @@ class llama_token_type(enum.IntEnum): # LLAMA_FTYPE_MOSTLY_MXFP4_MOE = 38, // except 1d tensors # LLAMA_FTYPE_MOSTLY_NVFP4 = 39, // except 1d tensors # LLAMA_FTYPE_MOSTLY_Q1_0 = 40, // except 1d tensors +# LLAMA_FTYPE_MOSTLY_Q2_0 = 41, // except 1d tensors # # LLAMA_FTYPE_GUESSED = 1024, // not specified in the model file # }; @@ -372,6 +373,9 @@ class llama_ftype(enum.IntEnum): LLAMA_FTYPE_MOSTLY_F16 = 1 LLAMA_FTYPE_MOSTLY_Q4_0 = 2 LLAMA_FTYPE_MOSTLY_Q4_1 = 3 + # LLAMA_FTYPE_MOSTLY_Q4_1_SOME_F16 = 4 + # LLAMA_FTYPE_MOSTLY_Q4_2 = 5 + # LLAMA_FTYPE_MOSTLY_Q4_3 = 6 LLAMA_FTYPE_MOSTLY_Q8_0 = 7 LLAMA_FTYPE_MOSTLY_Q5_0 = 8 LLAMA_FTYPE_MOSTLY_Q5_1 = 9 @@ -406,6 +410,7 @@ class llama_ftype(enum.IntEnum): LLAMA_FTYPE_MOSTLY_MXFP4_MOE = 38 LLAMA_FTYPE_MOSTLY_NVFP4 = 39 LLAMA_FTYPE_MOSTLY_Q1_0 = 40 + LLAMA_FTYPE_MOSTLY_Q2_0 = 41 LLAMA_FTYPE_GUESSED = 1024 # // Get the model file type (quantization) as a string, e.g. "Q8_0" or "Q4_K - Medium" @@ -428,7 +433,7 @@ def llama_ftype_name( # LLAMA_ROPE_SCALING_TYPE_LINEAR = 1, # LLAMA_ROPE_SCALING_TYPE_YARN = 2, # LLAMA_ROPE_SCALING_TYPE_LONGROPE = 3, -# LLAMA_ROPE_SCALING_TYPE_MAX_VALUE = LLAMA_ROPE_SCALING_TYPE_YARN, +# LLAMA_ROPE_SCALING_TYPE_MAX_VALUE = LLAMA_ROPE_SCALING_TYPE_LONGROPE, # }; class llama_rope_scaling_type(enum.IntEnum): LLAMA_ROPE_SCALING_TYPE_UNSPECIFIED = -1 @@ -436,7 +441,7 @@ class llama_rope_scaling_type(enum.IntEnum): LLAMA_ROPE_SCALING_TYPE_LINEAR = 1 LLAMA_ROPE_SCALING_TYPE_YARN = 2 LLAMA_ROPE_SCALING_TYPE_LONGROPE = 3 - LLAMA_ROPE_SCALING_TYPE_MAX_VALUE = LLAMA_ROPE_SCALING_TYPE_YARN + LLAMA_ROPE_SCALING_TYPE_MAX_VALUE = LLAMA_ROPE_SCALING_TYPE_LONGROPE # enum llama_pooling_type { # LLAMA_POOLING_TYPE_UNSPECIFIED = -1, diff --git a/vendor/llama.cpp b/vendor/llama.cpp index 7f575c39d..aff6eb6e7 160000 --- a/vendor/llama.cpp +++ b/vendor/llama.cpp @@ -1 +1 @@ -Subproject commit 7f575c39d6a29a40c0ef22278eca6bd4a573c8a6 +Subproject commit aff6eb6e7503538fec1532dec2f584bc7a4a4e4d From 859a99d74f70b9ab0ad2795225dda0e0f528dea6 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Thu, 16 Jul 2026 02:08:14 +0800 Subject: [PATCH 40/86] refactor(embedding): rename llama_cpp import alias to llama_cpp_lib Rename the 'llama_cpp.llama_cpp' import alias to 'llama_cpp_lib' to avoid potential namespace conflicts with the local '.llama_cpp' imports. Update all affected call sites in llama_embedding.py. Signed-off-by: JamePeng --- llama_cpp/llama_embedding.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/llama_cpp/llama_embedding.py b/llama_cpp/llama_embedding.py index 0c1df339c..d8f6e6bf2 100644 --- a/llama_cpp/llama_embedding.py +++ b/llama_cpp/llama_embedding.py @@ -1,6 +1,6 @@ import numpy as np from typing import Union, List, Optional, Dict, Any, Tuple -import llama_cpp.llama_cpp as llama_cpp +import llama_cpp.llama_cpp as llama_cpp_lib from .llama_types import Embedding from .llama import Llama # Pooling types from .llama_cpp @@ -141,7 +141,7 @@ def embed( # Determine the output dimension if is_rank: - out_dim = llama_cpp.llama_model_n_cls_out(self._model.model) + out_dim = llama_cpp_lib.llama_model_n_cls_out(self._model.model) else: out_dim = self.n_embd() @@ -166,9 +166,9 @@ def embed( # Reset Context and Batch if self.verbose: - llama_cpp.llama_perf_context_reset(ctx) + llama_cpp_lib.llama_perf_context_reset(ctx) self._batch.reset() - llama_cpp.llama_memory_clear(llama_cpp.llama_get_memory(ctx), True) + llama_cpp_lib.llama_memory_clear(llama_cpp_lib.llama_get_memory(ctx), True) # Initialize State Variables results: List[Any] = [] @@ -190,7 +190,7 @@ def _decode_batch(): doc_tokens_embd = [] for _ in range(seq_len): # Get the vector of the i-th token - ptr = llama_cpp.llama_get_embeddings_ith(ctx, curr_token_idx) + ptr = llama_cpp_lib.llama_get_embeddings_ith(ctx, curr_token_idx) if ptr is None: # Fallback: append zero vector or skip (here we zero-pad to keep shape) doc_tokens_embd.append([0.0] * out_dim) @@ -207,7 +207,7 @@ def _decode_batch(): else: for i in range(len(batch_seq_lens)): # Obtain the vector of the i-th sequence. - ptr = llama_cpp.llama_get_embeddings_seq(ctx, i) + ptr = llama_cpp_lib.llama_get_embeddings_seq(ctx, i) data = ptr[:out_dim] if not is_rank: @@ -219,7 +219,7 @@ def _decode_batch(): results.append(data) self._batch.reset() - llama_cpp.llama_memory_clear(llama_cpp.llama_get_memory(ctx), True) + llama_cpp_lib.llama_memory_clear(llama_cpp_lib.llama_get_memory(ctx), True) batch_seq_lens = [] # Main Streaming Loop @@ -272,7 +272,7 @@ def _decode_batch(): _decode_batch() if self.verbose: - llama_cpp.llama_perf_context_print(ctx) + llama_cpp_lib.llama_perf_context_print(ctx) final_result = results[0] if is_single else results From 849f02a445a944c2defffd2ff65e2678af32ee63 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Thu, 16 Jul 2026 03:43:05 +0800 Subject: [PATCH 41/86] feat(ctypes): support ABI-compatible symbol aliases - Allow ctypes_function_for_shared_library to accept either a single symbol name or an ordered iterable of ABI-compatible aliases. - Resolve aliases in order and bind the first exported symbol found while preserving the selected symbol name for runtime diagnostics. Also improve error reporting for empty alias lists and missing symbols. Signed-off-by: JamePeng --- llama_cpp/_ctypes_extensions.py | 55 ++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/llama_cpp/_ctypes_extensions.py b/llama_cpp/_ctypes_extensions.py index 1b4c05ff2..03d7ee5cb 100644 --- a/llama_cpp/_ctypes_extensions.py +++ b/llama_cpp/_ctypes_extensions.py @@ -9,6 +9,7 @@ from typing import ( Any, Callable, + Iterable, List, Union, Optional, @@ -201,20 +202,60 @@ class CtypesRef(Generic[CtypesCData]): def ctypes_function_for_shared_library(lib: ctypes.CDLL): - """Decorator for defining ctypes functions with type hints""" + """Create a decorator used to bind typed Python declarations to C symbols. + + The returned decorator accepts either a single exported symbol name or an + iterable of ABI-compatible aliases. When aliases are provided, they are + checked in order and the first available symbol is selected. + """ def ctypes_function( - name: str, argtypes: List[Any], restype: Any, enabled: bool = True + name: Union[str, Iterable[str]], + argtypes: List[Any], + restype: Any, + enabled: bool = True, ): + """Bind a Python declaration to one of the requested C symbols. + + Args: + name: A symbol name or an ordered iterable of compatible aliases. + argtypes: The ctypes argument types assigned to the C function. + restype: The ctypes return type assigned to the C function. + enabled: Return the original Python declaration when disabled. + + Raises: + ValueError: If no symbol names are provided. + AttributeError: If none of the requested symbols exist in the + shared library. + """ + symbol_names = (name,) if isinstance(name, str) else tuple(name) + + if not symbol_names: + raise ValueError("At least one shared library symbol name is required") + def decorator(f: F) -> F: - if enabled: - func = getattr(lib, name) + if not enabled: + return f + + for symbol_name in symbol_names: + try: + func = getattr(lib, symbol_name) + except AttributeError: + continue + func.argtypes = argtypes func.restype = restype - functools.wraps(f)(func) + functools.update_wrapper(func, f) + + # Preserve the actual exported symbol selected at runtime for + # diagnostics, especially when ABI aliases are being used. + func.__ctypes_symbol_name__ = symbol_name return func - else: - return f + + raise AttributeError( + "None of the shared library symbols were found: " + + ", ".join(symbol_names) + ) return decorator From 142c58d790e6961c97b68c967111b2afd3bb21f3 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Thu, 16 Jul 2026 18:57:18 +0800 Subject: [PATCH 42/86] Update Submodule vendor/llama.cpp aff6eb6..79bba02 Signed-off-by: JamePeng --- vendor/llama.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/llama.cpp b/vendor/llama.cpp index aff6eb6e7..79bba02a6 160000 --- a/vendor/llama.cpp +++ b/vendor/llama.cpp @@ -1 +1 @@ -Subproject commit aff6eb6e7503538fec1532dec2f584bc7a4a4e4d +Subproject commit 79bba02a6741de194912d370015866414faa83ad From a804dc2c1483e5859693cf77f8b245bbf6a9c2c3 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Thu, 16 Jul 2026 19:11:40 +0800 Subject: [PATCH 43/86] fix(mtmd): preserve subclass chat format during MTMD initialization - Ensure MTMDChatHandler initialization remains compatible with specialized chat handlers that define their own chat_format before calling super().__init__(). - Initialize chat_format only when it is not already provided by the subclass, then apply chat_format_override or fallback to the built-in MTMD template. This prevents AttributeError during inherited handler initialization while keeping template override behavior unchanged. Signed-off-by: JamePeng --- llama_cpp/llama_multimodal.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/llama_cpp/llama_multimodal.py b/llama_cpp/llama_multimodal.py index 9e5b8af47..f3b7382e8 100644 --- a/llama_cpp/llama_multimodal.py +++ b/llama_cpp/llama_multimodal.py @@ -155,6 +155,10 @@ def __init__( f"{self.log_prefix}(__init__): `extra_template_arguments` must be a dict." ) + # Preserve subclass attributes + if not hasattr(self, "chat_format"): + self.chat_format = None + self.chat_format_override = chat_template_override self.extra_template_arguments: dict[str, Any] = dict(extra_template_arguments or {}) @@ -167,10 +171,11 @@ def __init__( self._template_initialized = False # Pre-compile Jinja template - if (not hasattr(self, "chat_format") or self.chat_format is None) and self.chat_format_override is not None: - self.chat_format = self.chat_format_override - elif self.chat_format is None and self.chat_format_override is None: - self.chat_format = self.CHAT_FORMAT + if self.chat_format is None: + if self.chat_format_override is not None: + self.chat_format = self.chat_format_override + else: + self.chat_format = self.CHAT_FORMAT self._change_chat_template(self.chat_format) From afafdd59251e7fbef0e80ea2c705c1f0798dffd3 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Fri, 17 Jul 2026 03:35:30 +0800 Subject: [PATCH 44/86] feat(ctypes): handle missing optional symbols gracefully - Allow ctypes bindings to mark symbols as optional through the `required` flag. - Missing symbols caused by ABI naming differences, API changes, or experimental extensions will no longer break library loading. Optional APIs emit diagnostic warnings and provide runtime unavailable stubs instead. Signed-off-by: JamePeng --- llama_cpp/_ctypes_extensions.py | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/llama_cpp/_ctypes_extensions.py b/llama_cpp/_ctypes_extensions.py index 03d7ee5cb..3c1709e75 100644 --- a/llama_cpp/_ctypes_extensions.py +++ b/llama_cpp/_ctypes_extensions.py @@ -214,6 +214,7 @@ def ctypes_function( argtypes: List[Any], restype: Any, enabled: bool = True, + required: bool = True, ): """Bind a Python declaration to one of the requested C symbols. @@ -222,6 +223,7 @@ def ctypes_function( argtypes: The ctypes argument types assigned to the C function. restype: The ctypes return type assigned to the C function. enabled: Return the original Python declaration when disabled. + required: Raise if symbol is missing. If False, create a runtime unavailable stub. Raises: ValueError: If no symbol names are provided. @@ -252,11 +254,37 @@ def decorator(f: F) -> F: func.__ctypes_symbol_name__ = symbol_name return func - raise AttributeError( + message = ( "None of the shared library symbols were found: " + ", ".join(symbol_names) ) + if required: + raise AttributeError(message) + + # Optional extension API. + # Keep import working when the symbol is unavailable. + print( + "[llama-cpp-python].ctypes_function: WARNING! optional API unavailable\n" + f" symbols: {', '.join(symbol_names)}\n" + f" library: {getattr(lib, '_name', '')}" + ) + + def unavailable(*args, **kwargs): + raise RuntimeError( + "This llama.cpp extension API is unavailable.\n" + f"Required symbol(s): {', '.join(symbol_names)}\n" + f"Library: {getattr(lib, '_name', '')}" + ) + + functools.update_wrapper(unavailable, f) + + # Mark unavailable extension API. + unavailable.__ctypes_symbol_name__ = None + unavailable.__ctypes_optional__ = True + + return unavailable + return decorator return ctypes_function From 083c4b59e812c7a3fc63f32af4b7c2c331da8c9c Mon Sep 17 00:00:00 2001 From: JamePeng Date: Fri, 17 Jul 2026 04:38:36 +0800 Subject: [PATCH 45/86] Update Submodule vendor/llama.cpp 79bba02..e8f19cc Signed-off-by: JamePeng --- vendor/llama.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/llama.cpp b/vendor/llama.cpp index 79bba02a6..e8f19cc0a 160000 --- a/vendor/llama.cpp +++ b/vendor/llama.cpp @@ -1 +1 @@ -Subproject commit 79bba02a6741de194912d370015866414faa83ad +Subproject commit e8f19cc0ad70a243c8012bf17b4be601abfc8ea2 From 6c163c261c5d33492787b0de5c477de5d589e856 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Fri, 17 Jul 2026 04:46:10 +0800 Subject: [PATCH 46/86] fix(ctypes): validate argument types before binding shared library functions - Add explicit validation for ctypes function argument declarations before assigning them to the loaded shared library function. - This provides clearer error messages when invalid Python types are passed to `argtypes`, instead of exposing the internal ctypes error about missing `from_param()` methods. Signed-off-by: JamePeng --- llama_cpp/_ctypes_extensions.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/llama_cpp/_ctypes_extensions.py b/llama_cpp/_ctypes_extensions.py index 3c1709e75..e88595f36 100644 --- a/llama_cpp/_ctypes_extensions.py +++ b/llama_cpp/_ctypes_extensions.py @@ -244,6 +244,18 @@ def decorator(f: F) -> F: func = getattr(lib, symbol_name) except AttributeError: continue + # Validate ctypes argument declarations before assigning them. + # ctypes requires every argtype to provide from_param(). + for index, argtype in enumerate(argtypes): + if not hasattr(argtype, "from_param"): + raise TypeError( + "Invalid ctypes argument type:\n" + f" function: {f.__name__}\n" + f" symbol: {symbol_name}\n" + f" arg index: {index}\n" + f" arg type: {argtype!r}\n" + f" expected: a ctypes type with from_param()" + ) func.argtypes = argtypes func.restype = restype From 978ff3252c6c56c2871470e67146b79aa3609e2e Mon Sep 17 00:00:00 2001 From: JamePeng Date: Fri, 17 Jul 2026 04:51:52 +0800 Subject: [PATCH 47/86] feat(llama_ext): support optional llama-ext.h API bindings - Add Python ctypes bindings for the experimental APIs exposed by llama-ext.h, including NextN/MTP embeddings, and model metadata extraction. - Extension symbols are loaded optionally to handle ABI changes, renamed symbols, and builds that do not export experimental APIs without breaking the main Python bindings. Signed-off-by: JamePeng --- llama_cpp/llama_cpp.py | 293 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 293 insertions(+) diff --git a/llama_cpp/llama_cpp.py b/llama_cpp/llama_cpp.py index a6bbb26d3..2f402cd74 100644 --- a/llama_cpp/llama_cpp.py +++ b/llama_cpp/llama_cpp.py @@ -10,6 +10,7 @@ ggml_backend_sched_eval_callback, ggml_log_callback, ggml_opt_get_optimizer_params, + ggml_cgraph ) from typing import ( @@ -5069,3 +5070,295 @@ def llama_opt_epoch( callback_eval: ctypes.c_void_p, / ): ... + +############################## +# // llama.cpp/src/llama-ext.h +############################## + +# // this is a staging header for new llama.cpp API +# // breaking changes and C++ are allowed. everything here should be considered WIP +# // try as much as possible to not include this header in the rest of the codebase + +ctypes_function_llama_ext = ctypes_function_for_shared_library(_lib) + +# // Reserve a new compute graph. It is valid until the next call to llama_graph_reserve. +# LLAMA_API struct ggml_cgraph * llama_graph_reserve( +# struct llama_context * ctx, +# uint32_t n_tokens, +# uint32_t n_seqs, +# uint32_t n_outputs); +@ctypes_function_llama_ext( + [ + "llama_graph_reserve", + "?llama_graph_reserve@@YAPEAUggml_cgraph@@PEAUllama_context@@III@Z", + "__Z19llama_graph_reserveP13llama_contextjjj", + ], + [llama_context_p_ctypes, ctypes.c_uint32, ctypes.c_uint32, ctypes.c_uint32], + ctypes.POINTER(ggml_cgraph), + required=False, +) +def llama_graph_reserve( + ctx: llama_context_p, + n_tokens: ctypes.c_uint32, + n_seqs: ctypes.c_uint32, + n_outputs: ctypes.c_uint32, +) -> ctypes.POINTER(ggml_cgraph): # type: ignore + """ + Reserve a new compute graph. It is valid until the next call to llama_graph_reserve. + """ + ... + +# // Get the default ggml_type for a given ftype. +# LLAMA_API ggml_type llama_ftype_get_default_type(llama_ftype ftype); +@ctypes_function_llama_ext( + [ + "llama_ftype_get_default_type", + "?llama_ftype_get_default_type@@YA?AW4ggml_type@@W4llama_ftype@@@Z", + "__Z28llama_ftype_get_default_type11llama_ftype", + ], + [ctypes.c_int], + int, + required=False, +) +def llama_ftype_get_default_type( + ftype: llama_ftype +) -> int: + """ + Get the default ggml_type for a given ftype. + """ + ... + +# LLAMA_API int32_t llama_model_n_expert (const struct llama_model * model); +@ctypes_function_llama_ext( + [ + "llama_model_n_expert", + "?llama_model_n_expert@@YAHPEBUllama_model@@@Z", + "__Z20llama_model_n_expertPK11llama_model", + ], + [llama_model_p_ctypes], + ctypes.c_int32, + required=False, +) +def llama_model_n_expert( + model: llama_model_p +) -> ctypes.c_int32: + ... + +# LLAMA_API int32_t llama_model_n_devices(const struct llama_model * model); +@ctypes_function_llama_ext( + [ + "llama_model_n_devices", + "?llama_model_n_devices@@YAHPEBUllama_model@@@Z", + "__Z21llama_model_n_devicesPK11llama_model", + ], + [llama_model_p_ctypes], + ctypes.c_int32, + required=False, +) +def llama_model_n_devices( + model: llama_model_p +) -> ctypes.c_int32: + ... + +# LLAMA_API ggml_backend_dev_t llama_model_get_device(const struct llama_model * model, int i); +@ctypes_function_llama_ext( + [ + "llama_model_get_device", + "?llama_model_get_device@@YAPEAUggml_backend_device@@PEBUllama_model@@H@Z", + "__Z22llama_model_get_devicePK11llama_modeli", + ], + [llama_model_p_ctypes, ctypes.c_int], + ctypes.c_void_p, + required=False, +) +def llama_model_get_device( + model: llama_model_p, + i: int, +) -> ctypes.c_void_p: + ... + +# // Set whether the context outputs nextn embeddings or not +# // If masked == true, output the embeddings only for the tokens with batch.logits != 0 +# // If masked == false, output the embeddings for all tokens in the batch regardless of batch.logits +# LLAMA_API void llama_set_embeddings_nextn(struct llama_context * ctx, bool value, bool masked); +@ctypes_function_llama_ext( + [ + "llama_set_embeddings_nextn", + "?llama_set_embeddings_nextn@@YAXPEAUllama_context@@_N1@Z", + "__Z26llama_set_embeddings_nextnP13llama_contextbb", + ], + [llama_context_p_ctypes, ctypes.c_bool, ctypes.c_bool], + None, + required=False, +) +def llama_set_embeddings_nextn( + ctx: llama_context_p, + value: bool, + masked: bool, +): + """ + Set whether the context outputs nextn embeddings or not + If masked == true, output the embeddings only for the tokens with batch.logits != 0 + If masked == false, output the embeddings for all tokens in the batch regardless of batch.logits + """ + ... + +# // Select which appended NextN block the DECODER_MTP graph runs (offset past +# // the trunk: il = n_layer() + offset). Used by the speculative NextN driver to +# // chain multiple trained NextN heads. Default 0 (first head). +# LLAMA_API void llama_set_nextn_layer_offset(struct llama_context * ctx, int32_t offset); +@ctypes_function_llama_ext( + [ + "llama_set_nextn_layer_offset", + "?llama_set_nextn_layer_offset@@YAXPEAUllama_context@@H@Z", + "__Z28llama_set_nextn_layer_offsetP13llama_contexti", + ], + [llama_context_p_ctypes, ctypes.c_int32], + None, + required=False, +) +def llama_set_nextn_layer_offset( + ctx: llama_context_p, + offset: ctypes.c_int32, +): + """ + Select which appended NextN block the DECODER_MTP graph runs (offset past + the trunk: il = n_layer() + offset). Used by the speculative NextN driver to + chain multiple trained NextN heads. Default 0 (first head). + """ + ... + +# // mirrors: +# // LLAMA_API float * llama_get_embeddings(struct llama_context * ctx); +# LLAMA_API float * llama_get_embeddings_nextn(struct llama_context * ctx); +@ctypes_function_llama_ext( + [ + "llama_get_embeddings_nextn", + "?llama_get_embeddings_nextn@@YAPEAMPEAUllama_context@@@Z", + "__Z26llama_get_embeddings_nextnP13llama_context", + ], + [llama_context_p_ctypes], + ctypes.POINTER(ctypes.c_float), + required=False, +) +def llama_get_embeddings_nextn( + ctx: llama_context_p, +) -> ctypes.POINTER(ctypes.c_float): # type: ignore + ... + +# // LLAMA_API float * llama_get_embeddings_ith(struct llama_context * ctx, int32_t i); +# LLAMA_API float * llama_get_embeddings_nextn_ith(struct llama_context * ctx, int32_t i); +@ctypes_function_llama_ext( + [ + "llama_get_embeddings_nextn_ith", + "?llama_get_embeddings_nextn_ith@@YAPEAMPEAUllama_context@@H@Z", + "__Z30llama_get_embeddings_nextn_ithP13llama_contexti", + ], + [llama_context_p_ctypes, ctypes.c_int32], + ctypes.POINTER(ctypes.c_float), + required=False, +) +def llama_get_embeddings_nextn_ith( + ctx: llama_context_p, + i: ctypes.c_int32, +) -> ctypes.POINTER(ctypes.c_float): # type: ignore + ... + +# // Set whether the context outputs the input embeddings of a specific layer +# LLAMA_API void llama_set_embeddings_layer_inp(struct llama_context * ctx, uint32_t lid, bool value); +@ctypes_function_llama_ext( + [ + "llama_set_embeddings_layer_inp", + "?llama_set_embeddings_layer_inp@@YAXPEAUllama_context@@I_N@Z", + "__Z30llama_set_embeddings_layer_inpP13llama_contextjb", + ], + [llama_context_p_ctypes, ctypes.c_int32, ctypes.c_bool], + ctypes.POINTER(ctypes.c_float), + required=False, +) +def llama_set_embeddings_layer_inp( + ctx: llama_context_p, + lid: ctypes.c_int32, + value: bool, +) -> ctypes.POINTER(ctypes.c_float): # type: ignore + """ + Set whether the context outputs the input embeddings of a specific layer + """ + ... + +# // mirrors: +# // LLAMA_API float * llama_get_embeddings(struct llama_context * ctx); +# LLAMA_API float * llama_get_embeddings_layer_inp(struct llama_context * ctx, uint32_t lid); +@ctypes_function_llama_ext( + [ + "llama_get_embeddings_layer_inp", + "?llama_get_embeddings_layer_inp@@YAPEAMPEAUllama_context@@I@Z", + "__Z30llama_get_embeddings_layer_inpP13llama_contextj", + ], + [llama_context_p_ctypes, ctypes.c_int32], + ctypes.POINTER(ctypes.c_float), + required=False, +) +def llama_get_embeddings_layer_inp( + ctx: llama_context_p, + lid: ctypes.c_int32, +) -> ctypes.POINTER(ctypes.c_float): # type: ignore + ... + +# LLAMA_API llama_context * llama_get_ctx_other(struct llama_context * ctx); +@ctypes_function_llama_ext( + [ + "llama_get_ctx_other", + "?llama_get_ctx_other@@YAPEAUllama_context@@PEAU1@@Z", + "__Z19llama_get_ctx_otherP13llama_context", + ], + [llama_context_p_ctypes], + llama_context_p_ctypes, + required=False, +) +def llama_get_ctx_other( + ctx: llama_context_p, +) -> llama_context_p: + ... + +# // model/context data extraction + +# // returns pointer to the target-model layer indices +# LLAMA_API const int32_t * llama_model_target_layer_ids (const struct llama_model * model); +@ctypes_function_llama_ext( + [ + "llama_model_target_layer_ids", + "?llama_model_target_layer_ids@@YAPEBHPEBUllama_model@@@Z", + "__Z28llama_model_target_layer_idsPK11llama_model", + ], + [llama_model_p_ctypes], + ctypes.POINTER(ctypes.c_int32), + required=False, +) +def llama_model_target_layer_ids( + model: llama_model_p +) -> ctypes.POINTER(ctypes.c_int32): # type: ignore + """ + returns pointer to the target-model layer indices + """ + ... + +# // returns the number of extracted layers from target model +# LLAMA_API uint32_t llama_model_target_layer_ids_n(const struct llama_model * model); +@ctypes_function_llama_ext( + [ + "llama_model_target_layer_ids_n", + "?llama_model_target_layer_ids_n@@YAIPEBUllama_model@@@Z", + "__Z30llama_model_target_layer_ids_nPK11llama_model" + ], + [llama_model_p_ctypes], + ctypes.POINTER(ctypes.c_uint32), + required=False, +) +def llama_model_target_layer_ids_n( + model: llama_model_p +) -> ctypes.POINTER(ctypes.c_uint32): # type: ignore + """ + returns the number of extracted layers from target model + """ + ... From 3df6144d2fd04e3e657b4fe354bb8bdd382dc1e7 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Sat, 18 Jul 2026 00:09:43 +0800 Subject: [PATCH 48/86] Update Submodule vendor/llama.cpp e8f19cc..86d86ed Signed-off-by: JamePeng --- vendor/llama.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/llama.cpp b/vendor/llama.cpp index e8f19cc0a..86d86ed43 160000 --- a/vendor/llama.cpp +++ b/vendor/llama.cpp @@ -1 +1 @@ -Subproject commit e8f19cc0ad70a243c8012bf17b4be601abfc8ea2 +Subproject commit 86d86ed4396b4130922f7b9af26e3d9fc11a591b From a64128351a1d04c6dd644e3908070f7ea2002f20 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Sat, 18 Jul 2026 01:35:17 +0800 Subject: [PATCH 49/86] =?UTF-8?q?patch(Gemma4ChatHandler):=20Synchronize?= =?UTF-8?q?=20huggingface=20gemma4=20latest=20chat=20template=20-=20fix:?= =?UTF-8?q?=20chat=20template=20=E2=80=94=20null=20handling,=20reasoning?= =?UTF-8?q?=20preservation,=20turn-tag=20balance,=20input=20validation=20-?= =?UTF-8?q?=20https://huggingface.co/google/gemma-4-31B-it/commit/68abe480?= =?UTF-8?q?10cbe15293462fa11e901a60639a44e5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: JamePeng --- llama_cpp/llama_multimodal.py | 142 ++++++++++++++++++++++------------ 1 file changed, 91 insertions(+), 51 deletions(-) diff --git a/llama_cpp/llama_multimodal.py b/llama_cpp/llama_multimodal.py index f3b7382e8..cc159924f 100644 --- a/llama_cpp/llama_multimodal.py +++ b/llama_cpp/llama_multimodal.py @@ -2582,7 +2582,9 @@ class Gemma4ChatHandler(MTMDChatHandler): " }\n" "{%- endmacro -%}\n" "{%- macro format_argument(argument, escape_keys=True) -%}\n" - " {%- if argument is string -%}\n" + " {%- if argument is none -%}\n" + " {{- 'null' -}}\n" + " {%- elif argument is string -%}\n" " {{- '<|\"|>' + argument + '<|\"|>' -}}\n" " {%- elif argument is boolean -%}\n" " {{- 'true' if argument else 'false' -}}\n" @@ -2638,18 +2640,21 @@ class Gemma4ChatHandler(MTMDChatHandler): " {{- '' -}}\n" "{%- endmacro -%}\n" "\n" + "{#- ===== SETUP ===== -#}" "{%- set ns = namespace(prev_message_type=None) -%}\n" "{%- set loop_messages = messages -%}\n" + "{%- set enable_thinking = enable_thinking | default(false) -%}\n" + "{%- set preserve_thinking = preserve_thinking | default(false) -%}\n" "{{- bos_token -}}\n" "{#- Handle System/Tool Definitions Block -#}\n" - "{%- if (enable_thinking is defined and enable_thinking) or tools or messages[0]['role'] in ['system', 'developer'] -%}\n" + "{%- if enable_thinking or tools or (messages and messages[0]['role'] in ['system', 'developer']) -%}\n" " {{- '<|turn>system\\n' -}}\n" " {#- Inject Thinking token at the very top of the FIRST system turn -#}\n" - " {%- if enable_thinking is defined and enable_thinking -%}\n" + " {%- if enable_thinking -%}\n" " {{- '<|think|>\\n' -}}\n" " {%- set ns.prev_message_type = 'think' -%}\n" " {%- endif -%}\n" - " {%- if messages[0]['role'] in ['system', 'developer'] -%}\n" + " {%- if messages and messages[0]['role'] in ['system', 'developer'] -%}\n" " {%- if messages[0]['content'] is string -%}\n" " {{- messages[0]['content'] | trim -}}\n" " {%- elif messages[0]['content'] is sequence -%}\n" @@ -2683,31 +2688,21 @@ class Gemma4ChatHandler(MTMDChatHandler): " {%- if message['role'] != 'tool' -%}\n" " {%- set ns.prev_message_type = None -%}\n" " {%- set role = 'model' if message['role'] == 'assistant' else message['role'] -%}\n" - " {#- Detect continuation: suppress duplicate <|turn>model when previous non-tool message was also assistant -#}\n" - " {%- set prev_nt = namespace(role=None, found=false) -%}\n" - " {%- if loop.index0 > 0 -%}\n" - " {%- for j in range(loop.index0 - 1, -1, -1) -%}\n" - " {%- if not prev_nt.found -%}\n" - " {%- if loop_messages[j]['role'] != 'tool' -%}\n" - " {%- set prev_nt.role = loop_messages[j]['role'] -%}\n" - " {%- set prev_nt.found = true -%}\n" - " {%- endif -%}\n" - " {%- endif -%}\n" - " {%- endfor -%}\n" - " {%- endif -%}\n" - " {%- set continue_same_model_turn = (role == 'model' and prev_nt.role == 'assistant') -%}\n" + "{#- Detect continuation using tracked state — O(1) instead of O(n) backward scan -#}\n" + "{%- set continue_same_model_turn = (role == 'model' and ns.prev_non_tool_role == 'assistant') -%}\n" " {%- if not continue_same_model_turn -%}\n" " {{- '<|turn>' + role + '\\n' }}\n" " {%- endif -%}\n" "\n" " {#- Render reasoning/reasoning_content as thinking channel -#}\n" " {%- set thinking_text = message.get('reasoning') or message.get('reasoning_content') -%}\n" - " {%- if thinking_text and loop.index0 > ns_turn.last_user_idx and message.get('tool_calls') -%}\n" - " {{- '<|channel>thought\\n' + thinking_text + '\\n' -}}\n" + " {%- set thinking_gate = (loop.index0 > ns_turn.last_user_idx) or (preserve_thinking and message.get('tool_calls')) -%}\n" + " {%- if thinking_text and thinking_gate -%}\n" + " {{- '<|channel>thought\n' + thinking_text + '\n' -}}\n" " {%- endif -%}\n" "\n" " {%- if message.get('tool_calls') -%}\n" - " {%- for tool_call in message['tool_calls'] -%}\n" + " {%- for tool_call in message.get('tool_calls') -%}\n" " {%- set function = tool_call['function'] -%}\n" " {{- '<|tool_call>call:' + function['name'] + '{' -}}\n" " {%- if function['arguments'] is mapping -%}\n" @@ -2717,8 +2712,13 @@ class Gemma4ChatHandler(MTMDChatHandler): " {%- set ns_args.found_first = true -%}\n" " {{- key -}}:{{- format_argument(value, escape_keys=False) -}}\n" " {%- endfor -%}\n" - " {%- elif function['arguments'] is string -%}\n" - " {{- function['arguments'] -}}\n" + " {%- elif function['arguments'] is none -%}\n" + " {%- else -%}\n" + " {{- raise_exception(\n" + " \"chat_template: tool_calls[].function.arguments must be a \"\n" + " \"JSON object (mapping), not a string. Deserialize arguments \"\n" + " \"before passing to the template.\"\n" + " ) -}}\n" " {%- endif -%}\n" " {{- '}' -}}\n" " {%- endfor -%}\n" @@ -2728,8 +2728,8 @@ class Gemma4ChatHandler(MTMDChatHandler): " {%- set ns_tr_out = namespace(flag=false) -%}\n" " {%- if message.get('tool_responses') -%}\n" " {#- Legacy: tool_responses embedded on the assistant message (Google/Gemma native) -#}\n" - " {%- for tool_response in message['tool_responses'] -%}\n" - " {{- format_tool_response_block(tool_response['name'] | default('unknown'), tool_response['response']) -}}\n" + " {%- for tool_response in message.get('tool_responses') -%}\n" + " {{- format_tool_response_block(tool_response['name'] | default('unknown', true), tool_response['response']) -}}\n" " {%- set ns_tr_out.flag = true -%}\n" " {%- set ns.prev_message_type = 'tool_response' -%}\n" " {%- endfor -%}\n" @@ -2743,8 +2743,8 @@ class Gemma4ChatHandler(MTMDChatHandler): " {%- else -%}\n" " {%- set follow = loop_messages[k] -%}\n" " {#- Resolve tool_call_id to function name -#}\n" - " {%- set ns_tname = namespace(name=follow.get('name') | default('unknown')) -%}\n" - " {%- for tc in message['tool_calls'] -%}\n" + " {%- set ns_tname = namespace(name=follow.get('name') or 'unknown') -%}\n" + " {%- for tc in message.get('tool_calls') -%}\n" " {%- if tc.get('id') == follow.get('tool_call_id') -%}\n" " {%- set ns_tname.name = tc['function']['name'] -%}\n" " {%- endif -%}\n" @@ -2762,9 +2762,14 @@ class Gemma4ChatHandler(MTMDChatHandler): " {%- endfor -%}\n" " {{- format_tool_response_block(ns_tname.name, ns_txt.s) -}}\n" " {%- for part in tool_body -%}\n" - " {%- if part.get('type') == 'image_url' -%}\n" - " {%- set url_val = part['image_url'] if part['image_url'] is string else part['image_url']['url'] -%}\n" - " {{- '<|image|>' + url_val -}}\n" + " {%- if part.get('type') in ['image', 'image_url'] -%}\n" + " {%- if part.get('type') == 'image_url' -%}\n" + " {%- set url_val = part['image_url'] if part['image_url'] is string else part['image_url']['url'] -%}\n" + " {{- '<|image|>' + url_val -}}\n" + " {%- elif part.get('type') == 'image' -%}\n" + " {%- set url_val = part['image'] if part['image'] is string else part['image']['url'] -%}\n" + " {{- '<|image|>' + url_val -}}\n" + " {%- endif -%}\n" " {%- elif part.get('type') in ['audio_url', 'input_audio'] -%}\n" " {%- if part.get('type') == 'audio_url' -%}\n" " {%- set audio_val = part['audio_url'] if part['audio_url'] is string else part['audio_url']['url'] -%}\n" @@ -2773,9 +2778,14 @@ class Gemma4ChatHandler(MTMDChatHandler): " {%- set audio_val = part['input_audio'] if part['input_audio'] is string else ('data:audio/' + part['input_audio']['format'] + ';base64,' + part['input_audio']['data']) -%}\n" " {{- '<|audio|>' + audio_val -}}\n" " {%- endif -%}\n" - # " {%- elif part.get('type') == 'video_url' -%}\n" - # " {%- set video_val = part['video_url'] if part['video_url'] is string else part['video_url']['url'] -%}\n" - # " {{- '<|video|>' + video_val -}}\n" + " {%- elif part.get('type') in ['video', 'video_url'] -%}\n" + " {%- if part.get('type') == 'video_url' -%}\n" + " {%- set video_val = part['video_url'] if part['video_url'] is string else part['video_url']['url'] -%}\n" + " {{- '<|video|>' + video_val -}}\n" + " {%- elif part.get('type') == 'video' -%}\n" + " {%- set video_val = part['video'] if part['video'] is string else part['video']['url'] -%}\n" + " {{- '<|video|>' + video_val -}}\n" + " {%- endif -%}\n" " {%- endif -%}\n" " {%- endfor -%}\n" " {%- else -%}\n" @@ -2788,38 +2798,45 @@ class Gemma4ChatHandler(MTMDChatHandler): " {%- endif -%}\n" "\n" " {%- set captured_content -%}\n" - " {%- if message['content'] is string -%}\n" + " {%- if message.get('content') is string -%}\n" " {%- if role == 'model' -%}\n" " {{- strip_thinking(message['content']) -}}\n" " {%- else -%}\n" " {{- message['content'] | trim -}}\n" " {%- endif -%}\n" - " {%- elif message['content'] is sequence -%}\n" + " {%- elif message.get('content') is sequence -%}\n" " {%- for item in message['content'] -%}\n" - " {%- if item['type'] == 'text' -%}\n" + " {%- if item.get('type') == 'text' -%}\n" " {%- if role == 'model' -%}\n" " {{- strip_thinking(item['text']) -}}\n" " {%- else -%}\n" " {{- item['text'] | trim -}}\n" " {%- endif -%}\n" - " {%- elif item['type'] == 'image_url' -%}\n" - " {%- set url_val = item['image_url'] if item['image_url'] is string else item['image_url']['url'] -%}\n" - " {{- '<|image|>' + url_val -}}\n" - " {%- set ns.prev_message_type = 'image' -%}\n" - " {%- elif item['type'] in ['audio_url', 'input_audio'] -%}\n" - " {%- if item['type'] == 'audio_url' -%}\n" + " {%- elif item.get('type') in ['image', 'image_url'] -%}\n" + " {%- if item.get('type')== 'image_url' -%}\n" + " {%- set url_val = item['image_url'] if item['image_url'] is string else item['image_url']['url'] -%}\n" + " {{- '<|image|>' + url_val -}}\n" + " {%- elif item.get('type') == 'image' -%}\n" + " {%- set url_val = item['image'] if item['image'] is string else item['image']['url'] -%}\n" + " {{- '<|image|>' + url_val -}}\n" + " {%- endif -%}\n" + " {%- elif item.get('type') in ['audio_url', 'input_audio'] -%}\n" + " {%- if item.get('type') == 'audio_url' -%}\n" " {%- set audio_val = item['audio_url'] if item['audio_url'] is string else item['audio_url']['url'] -%}\n" " {{- '<|audio|>' + audio_val -}}\n" - " {%- elif item['type'] == 'input_audio' -%}\n" + " {%- elif item.get('type') == 'input_audio' -%}\n" " {%- set audio_val = item['input_audio'] if item['input_audio'] is string else ('data:audio/' + item['input_audio']['format'] + ';base64,' + item['input_audio']['data']) -%}\n" " {{- '<|audio|>' + audio_val -}}\n" " {%- endif -%}\n" - " {%- set ns.prev_message_type = 'audio' -%}\n" + " {%- elif item.get('type') in ['video', 'video_url'] -%}\n" + " {%- if item.get('type') == 'video_url' -%}\n" + " {%- set video_val = part['video_url'] if part['video_url'] is string else part['video_url']['url'] -%}\n" + " {{- '<|video|>' + video_val -}}\n" + " {%- elif item.get('type') == 'video' -%}\n" + " {%- set video_val = part['video'] if part['video'] is string else part['video']['url'] -%}\n" + " {{- '<|video|>' + video_val -}}\n" + " {%- endif -%}\n" " {%- endif -%}\n" - # " {%- elif item['type'] == 'video_url' -%}\n" - # " {%- set video_val = item['video_url'] if item['video_url'] is string else item['video_url']['url'] -%}\n" - # " {{- '<|video|>' + video_val -}}\n" - # " {%- set ns.prev_message_type = 'video' -%}\n" " {%- endfor -%}\n" " {%- endif -%}\n" " {%- endset -%}\n" @@ -2827,20 +2844,43 @@ class Gemma4ChatHandler(MTMDChatHandler): " {{- captured_content -}}\n" " {%- set has_content = captured_content | trim | length > 0 -%}\n" "\n" + " {#- Forward-scan: find next non-tool message role for continuation detection -#}\n" + " {%- set next_nt = namespace(role=None, found=false) -%}\n" + " {%- for j in range(loop.index0 + 1, loop_messages | length) -%}\n" + " {%- if not next_nt.found -%}\n" + " {%- if loop_messages[j]['role'] != 'tool' -%}\n" + " {%- set next_nt.role = loop_messages[j]['role'] -%}\n" + " {%- set next_nt.found = true -%}\n" + " {%- endif -%}\n" + " {%- endif -%}\n" + " {%- endfor -%}\n" + + " {%- set continues_into_next = (\n" + " role == 'model'\n" + " and next_nt.role == 'assistant'\n" + " and (not message.get('tool_calls') or ns_tr_out.flag)\n" + " ) -%}\n" + "\n" " {%- if ns.prev_message_type == 'tool_call' and not ns_tr_out.flag -%}\n" " {{- '<|tool_response>' -}}\n" - " {%- elif not (ns_tr_out.flag and not has_content) -%}\n" + " {%- elif continues_into_next -%}\n" + " {%- elif not (ns_tr_out.flag and not has_content and not next_nt.found) -%}\n" " {{- '\\n' -}}\n" " {%- endif -%}\n" + "\n" + " {#- Track previous non-tool role for next iteration (avoids O(n) backward scan) -#}\n" + " {%- set ns.prev_non_tool_role = message['role'] -%}\n" " {%- endif -%}\n" "{%- endfor -%}\n" "\n" "{%- if add_generation_prompt -%}\n" " {%- if ns.prev_message_type != 'tool_response' and ns.prev_message_type != 'tool_call' -%}\n" - " {{- '<|turn>model\\n' -}}\n" - " {%- if not enable_thinking | default(false) -%}\n" - " {{- '<|channel>thought\\n' -}}\n" + " {{- '<|turn>model\n' -}}\n" + " {%- if not enable_thinking -%}\n" + " {{- '<|channel>thought\n' -}}\n" " {%- endif -%}\n" + " {%- elif ns.prev_message_type == 'tool_response' and enable_thinking -%}\n" + " {{- '<|channel>thought\n' -}}\n" " {%- endif -%}\n" "{%- endif -%}\n" ) From caa72e2ac2c971a4255799fc8f498395cf799f8a Mon Sep 17 00:00:00 2001 From: JamePeng Date: Sat, 18 Jul 2026 04:09:01 +0800 Subject: [PATCH 50/86] Bump version to 0.3.43 - This release focuses on improving compatibility with the rapidly evolving llama.cpp ecosystem, especially around experimental APIs, ABI changes, MTMD processing, and latest model integrations. Signed-off-by: JamePeng --- CHANGELOG.md | 76 ++++++++++++++++++++++++++++++++++++++++++- llama_cpp/__init__.py | 2 +- 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9654d354e..b25d56cfe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,85 @@ All notable changes to this project will be documented in this file. -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +## [0.3.43] Better llama.cpp ABI Compatibility, MTMD Performance and Extension API Support + +- patch(Gemma4ChatHandler): Synchronize huggingface gemma4 latest chat template + - fix: chat template — null handling, reasoning preservation, turn-tag balance, input validation + - https://huggingface.co/google/gemma-4-31B-it/commit/68abe48010cbe15293462fa11e901a60639a44e5 + +- feat(llama_ext): support optional llama-ext.h API bindings + - Add Python ctypes bindings for the experimental APIs exposed by + llama-ext.h, including NextN/MTP embeddings, and model metadata extraction. + - Extension symbols are loaded optionally to handle ABI changes, renamed + symbols, and builds that do not export experimental APIs without breaking + the main Python bindings. + +- feat(ctypes): handle missing optional symbols gracefully + - Allow ctypes bindings to mark symbols as optional through the `required` + flag. + - Missing symbols caused by ABI naming differences, API changes, or experimental + extensions will no longer break library loading. Optional APIs emit diagnostic + warnings and provide runtime unavailable stubs instead. + +- fix(ctypes): validate argument types before binding shared library functions + - Add explicit validation for ctypes function argument declarations before + assigning them to the loaded shared library function. + - This provides clearer error messages when invalid Python types are passed + to `argtypes`, instead of exposing the internal ctypes error about missing + `from_param()` methods. + +- fix(ctypes): validate argument types before binding shared library functions + - Add explicit validation for ctypes function argument declarations before + assigning them to the loaded shared library function. + - This provides clearer error messages when invalid Python types are passed + to `argtypes`, instead of exposing the internal ctypes error about missing + `from_param()` methods. + +- feat(ctypes): support ABI-compatible symbol aliases + - Allow ctypes_function_for_shared_library to accept either a single + symbol name or an ordered iterable of ABI-compatible aliases. + - Resolve aliases in order and bind the first exported symbol found while + preserving the selected symbol name for runtime diagnostics. Also improve + error reporting for empty alias lists and missing symbols. + +- refactor(mtmd): cache Generic MTMD chat template resolution for accelerate the processing speed of `__call__`. + - Refactor MTMD chat template handling to resolve and analyze the chat template only + once per handler instance instead of on every request. + - Add template initialization state, cache parsed media placeholder tags, and support + explicit chat template overrides through a dedicated field. Improve lifecycle cleanup + by resetting cached template state and MTMD resources during handler close. + - This keeps `GenericMTMDChatHandler` runtime processing focused on message rendering and media tokenization + while avoiding repeated chat template resolution overhead. + +- fix(mtmd): preserve subclass chat format during MTMD initialization + - Ensure MTMDChatHandler initialization remains compatible with specialized chat + handlers that define their own chat_format before calling super().__init__(). + - Initialize chat_format only when it is not already provided by the subclass, + then apply chat_format_override or fallback to the built-in MTMD template. + This prevents AttributeError during inherited handler initialization while + keeping template override behavior unchanged. + +- refactor(embedding): rename `llama_cpp` import alias to `llama_cpp_lib` + - Rename the `llama_cpp.llama_cpp` import alias to `llama_cpp_lib` to avoid potential namespace conflicts with the local `.llama_cpp` imports. Update all affected call sites in `llama_embedding.py`. + +- patch(Llama): Increase chunk preview limit to 128 in Llama.eval exception + - Raises the maximum tokens captured for the error message preview from 16 to 128, improving visibility into the offending chunk during fatal backend crashes. + +- ci(metal): get package version from importlib metadata + * Avoid importing llama_cpp when detecting the package version. + * This prevents initialization side effects and keeps CI version extraction reliable. + +- feat: Update llama.cpp to [ggml-org/llama.cpp/commit/86d86ed4396b4130922f7b9af26e3d9fc11a591b](https://github.com/ggml-org/llama.cpp/commit/86d86ed4396b4130922f7b9af26e3d9fc11a591b) + +- feat: Sync llama.cpp llama/mtmd/ggml API Binding 20260716 + +More information see: https://github.com/JamePeng/llama-cpp-python/compare/e522cecb93907c67ffe2e339b7009c93d3fb0f59...a64128351a1d04c6dd644e3908070f7ea2002f20 + ## [0.3.42] More Reliable Dynamic Backend Loading, Safer MTMD Processing, and Advanced Batch Support - fix(loader): improve Windows DLL search path handling and diagnostics diff --git a/llama_cpp/__init__.py b/llama_cpp/__init__.py index c6676c294..1695e3727 100644 --- a/llama_cpp/__init__.py +++ b/llama_cpp/__init__.py @@ -1,4 +1,4 @@ from .llama_cpp import * from .llama import * -__version__ = "0.3.42" +__version__ = "0.3.43" From 79b8b5d0824f44a63e5c8c8d8943b2768bf65fce Mon Sep 17 00:00:00 2001 From: JamePeng Date: Tue, 21 Jul 2026 03:46:25 +0800 Subject: [PATCH 51/86] Update Submodule vendor/llama.cpp 86d86ed..91d2fc3 Signed-off-by: JamePeng --- vendor/llama.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/llama.cpp b/vendor/llama.cpp index 86d86ed43..91d2fc387 160000 --- a/vendor/llama.cpp +++ b/vendor/llama.cpp @@ -1 +1 @@ -Subproject commit 86d86ed4396b4130922f7b9af26e3d9fc11a591b +Subproject commit 91d2fc387529940230555abd297a8b5e99737d3f From 0eb26153ff757498c22c4574c3edabe7e19b2ad5 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Tue, 21 Jul 2026 04:06:22 +0800 Subject: [PATCH 52/86] fix(ggml): preload bundled OpenMP runtime before loading ggml-base - Preload the packaged libomp140.x86_64.dll on Windows before initializing ggml-base to ensure CPU backend DLLs can resolve their OpenMP runtime dependency. - This only applies to Windows builds with llama-cpp-python >= 0.3.39 and uses the bundled runtime from the package lib directory, avoiding the need for users to configure system PATH or install additional OpenMP runtimes. Signed-off-by: JamePeng --- llama_cpp/_ctypes_extensions.py | 10 +++++++++ llama_cpp/_ggml.py | 37 +++++++++++++++++++++++++++++++-- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/llama_cpp/_ctypes_extensions.py b/llama_cpp/_ctypes_extensions.py index e88595f36..a9a2c02e5 100644 --- a/llama_cpp/_ctypes_extensions.py +++ b/llama_cpp/_ctypes_extensions.py @@ -5,6 +5,7 @@ import ctypes import functools import pathlib +import importlib.metadata from ctypes.util import find_library from typing import ( Any, @@ -19,6 +20,15 @@ ) from typing_extensions import TypeAlias +def _version_at_least(version: str) -> bool: + """Check whether installed llama-cpp-python version meets requirement.""" + try: + current = importlib.metadata.version("llama-cpp-python") + from packaging.version import Version + return Version(current) >= Version(version) + except Exception: + return False + def _format_library_dir_contents(base_paths: list[pathlib.Path]) -> str: """Format directory contents for diagnostics after library loading fails.""" sections = [] diff --git a/llama_cpp/_ggml.py b/llama_cpp/_ggml.py index 42ff66494..9a7dac517 100644 --- a/llama_cpp/_ggml.py +++ b/llama_cpp/_ggml.py @@ -6,10 +6,9 @@ import enum import os import pathlib - from llama_cpp._ctypes_extensions import ( + _version_at_least, load_shared_library, - byref, ctypes_function_for_shared_library, ) @@ -21,12 +20,46 @@ TYPE_CHECKING, ) +def _preload_openmp_runtime(): + """Preload bundled OpenMP runtime before loading ggml-base. + + This is required on Windows when CPU backends depend on the packaged + OpenMP runtime DLL. + """ + + # Only Windows DLL loading requires this workaround. + if os.name != "nt": + return + + # Keep compatibility with older package versions. + if not _version_at_least("0.3.39"): + return + + libomp_path = (pathlib.Path(__file__).parent / "lib" / "libomp140.x86_64.dll") + + if not libomp_path.exists(): + print(f"[llama-cpp-python] WARNING: bundled OpenMP runtime not found: {libomp_path}") + return + + try: + ctypes.CDLL(str(libomp_path), winmode=ctypes.RTLD_GLOBAL) + print(f"[llama-cpp-python] loaded bundled OpenMP runtime: {libomp_path}") + except Exception as e: + print( + "[llama-cpp-python] WARNING: failed to load bundled OpenMP runtime:\n" + f" path: {libomp_path}\n" + f" error: {e}" + ) + libggml_base_path = pathlib.Path(os.path.abspath(os.path.dirname(__file__))) libggml_base_paths = [ libggml_base_path / "lib", libggml_base_path / "bin", ] +# Load bundled OpenMP runtime before ggml-base on Windows. +_preload_openmp_runtime() + libggml_base = load_shared_library("ggml-base", libggml_base_paths) ggml_base_function = ctypes_function_for_shared_library(libggml_base) From db460e0c02aeb3400ee7776b19d60dca4aadb275 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Wed, 22 Jul 2026 02:02:42 +0800 Subject: [PATCH 53/86] Update Submodule vendor/llama.cpp 91d2fc3..846e991 Signed-off-by: JamePeng --- vendor/llama.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/llama.cpp b/vendor/llama.cpp index 91d2fc387..846e991ec 160000 --- a/vendor/llama.cpp +++ b/vendor/llama.cpp @@ -1 +1 @@ -Subproject commit 91d2fc387529940230555abd297a8b5e99737d3f +Subproject commit 846e991ec3c7ccec49112ff2c5b00b710e5f551d From ebf6099b81cf67cfb5eec569466367c9fa04e9d4 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Wed, 22 Jul 2026 02:16:45 +0800 Subject: [PATCH 54/86] Bump version to 0.3.44 - This release is a small but important maintenance update focused on improving Windows dynamic library loading reliability. Signed-off-by: JamePeng --- CHANGELOG.md | 12 ++++++++++++ llama_cpp/__init__.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b25d56cfe..69fc02b25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.3.44] Improved Windows DLL(OpenMP) Loading Reliability for GGML Backends + +- fix(ggml): preload bundled OpenMP runtime before loading ggml-base + - Preload the packaged `libomp140.x86_64.dll` on Windows before initializing + ggml-base to ensure CPU backend DLLs can resolve their OpenMP runtime + dependency. + - This only applies to Windows builds with llama-cpp-python >= 0.3.39 and + uses the bundled runtime from the package lib directory, avoiding the need + for users to configure system PATH or install additional OpenMP runtimes. + +- feat: Update llama.cpp to [ggml-org/llama.cpp/commit/846e991ec3c7ccec49112ff2c5b00b710e5f551d](https://github.com/ggml-org/llama.cpp/commit/846e991ec3c7ccec49112ff2c5b00b710e5f551d) + ## [0.3.43] Better llama.cpp ABI Compatibility, MTMD Performance and Extension API Support - patch(Gemma4ChatHandler): Synchronize huggingface gemma4 latest chat template diff --git a/llama_cpp/__init__.py b/llama_cpp/__init__.py index 1695e3727..10e452d5f 100644 --- a/llama_cpp/__init__.py +++ b/llama_cpp/__init__.py @@ -1,4 +1,4 @@ from .llama_cpp import * from .llama import * -__version__ = "0.3.43" +__version__ = "0.3.44" From ffbd951819fde28a43c7354391b979035dd3b3ed Mon Sep 17 00:00:00 2001 From: JamePeng Date: Thu, 23 Jul 2026 07:51:08 +0800 Subject: [PATCH 55/86] Update Submodule vendor/llama.cpp 846e991..4310aa4 Signed-off-by: JamePeng --- vendor/llama.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/llama.cpp b/vendor/llama.cpp index 846e991ec..4310aa4f8 160000 --- a/vendor/llama.cpp +++ b/vendor/llama.cpp @@ -1 +1 @@ -Subproject commit 846e991ec3c7ccec49112ff2c5b00b710e5f551d +Subproject commit 4310aa4f871c104698f6a6614a362bdec87c247a From 03124efa535f9de0974c36d54272be8303e649b5 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Sat, 25 Jul 2026 03:16:58 +0800 Subject: [PATCH 56/86] Update Submodule vendor/llama.cpp 4310aa4..88bfee1 Signed-off-by: JamePeng --- vendor/llama.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/llama.cpp b/vendor/llama.cpp index 4310aa4f8..88bfee142 160000 --- a/vendor/llama.cpp +++ b/vendor/llama.cpp @@ -1 +1 @@ -Subproject commit 4310aa4f871c104698f6a6614a362bdec87c247a +Subproject commit 88bfee1429a2dfacec65d1b0c0852eb327991865 From c4cdd46ab373393fab41d9a0e4f6faac55c5619f Mon Sep 17 00:00:00 2001 From: JamePeng Date: Sun, 26 Jul 2026 08:13:30 +0800 Subject: [PATCH 57/86] Update Submodule vendor/llama.cpp 88bfee1..8bb9093 Signed-off-by: JamePeng --- vendor/llama.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/llama.cpp b/vendor/llama.cpp index 88bfee142..8bb909374 160000 --- a/vendor/llama.cpp +++ b/vendor/llama.cpp @@ -1 +1 @@ -Subproject commit 88bfee1429a2dfacec65d1b0c0852eb327991865 +Subproject commit 8bb909374d04d40621340aee5ba2245860027fdc From 93ebd83b540e1e0ccb0489b7a42b6956fde3143b Mon Sep 17 00:00:00 2001 From: JamePeng Date: Sun, 26 Jul 2026 08:57:32 +0800 Subject: [PATCH 58/86] feat(embedding): modernize the built-in Llama embedding API - Replace the legacy embedding path with sequence-aware streaming batch processing based on the current LlamaBatch interface. - Support string, batched string, and pre-tokenized inputs, token-level and rank pooling outputs, separator-based splitting, token accounting, and llama.cpp-compatible normalization modes. - Restore embed() and create_embedding() as maintained Llama APIs while preserving the existing boolean normalization behavior. Signed-off-by: JamePeng --- llama_cpp/llama.py | 262 ++++++++++++++++++++++++++++++--------------- 1 file changed, 175 insertions(+), 87 deletions(-) diff --git a/llama_cpp/llama.py b/llama_cpp/llama.py index f1f71be8e..f733d7afb 100644 --- a/llama_cpp/llama.py +++ b/llama_cpp/llama.py @@ -1950,21 +1950,25 @@ def adapter(token_data_array: llama_cpp_lib.llama_token_data_array): ) def create_embedding( - self, input: Union[str, List[str]], model: Optional[str] = None + self, + input: Union[str, List[str]], + model: Optional[str] = None, + normalize: Union[bool, int] = False, + truncate: bool = True, ) -> CreateEmbeddingResponse: - """Embed a string. + """Create an OpenAI-compatible embedding response. Args: - input: The utf-8 encoded string to embed. + input: A string or list of strings to embed. + model: Model name reported in the response. + normalize: ``False`` disables normalization, ``True`` uses L2 + normalization, and integer values select a llama.cpp + normalization mode. + truncate: Truncate inputs to the available context/batch capacity. Returns: - An embedding object. + An OpenAI-compatible embedding response. """ - warnings.warn( - "The `create_embedding` method in `Llama` class is deprecated. " - "Please migrate to `LlamaEmbedding.create_embedding` for better efficiency.", - DeprecationWarning, - ) model_name: str = model if model is not None else self.model_path input = input if isinstance(input, list) else [input] @@ -1972,7 +1976,12 @@ def create_embedding( # get numeric embeddings embeds: Union[List[List[float]], List[List[List[float]]]] total_tokens: int - embeds, total_tokens = self.embed(input, return_count=True) # type: ignore + embeds, total_tokens = self.embed( # type: ignore + input, + normalize=normalize, + truncate=truncate, + return_count=True, + ) # convert to CreateEmbeddingResponse data: List[Embedding] = [ @@ -1996,130 +2005,209 @@ def create_embedding( def embed( self, - input: Union[str, List[str]], - normalize: bool = False, + input: Union[str, List[str], List[List[int]]], + normalize: Union[bool, int] = False, truncate: bool = True, + separator: Optional[str] = None, return_count: bool = False, ): - """Embed a string. + """Embed strings or pre-tokenized inputs. Args: - input: The utf-8 encoded string to embed. + input: A string, a list of strings, or a list of token-id lists. + normalize: ``False``/``-1`` disables normalization, ``True`` uses + L2 normalization. Integer modes follow llama.cpp's embedding + example: 0=max-absolute (scaled to 32760), 1=L1, 2=L2, and + values greater than 2 use the corresponding p-norm. + truncate: Truncate inputs that exceed the context/batch capacity. + separator: Split a single string into multiple inputs. + return_count: Return ``(embeddings, token_count)``. Returns: - A list of embeddings + Sequence embeddings, token-level embeddings for pooling type NONE, + or scalar/vector scores for pooling type RANK. """ - warnings.warn( - "The `embed` method in `Llama` class is deprecated and will be removed in future versions. " - "Please use the `LlamaEmbedding` class from `llama_embedding` module for optimized performance and reranking support.", - DeprecationWarning, - ) + if self.context_params.embeddings is False: + raise RuntimeError( + "Llama model must be created with embeddings=True to call this method" + ) - n_embd = self.n_embd() + ctx = self._ctx.ctx n_batch = self.n_batch + n_ctx = self._n_ctx + n_seq_max = self.context_params.n_seq_max - # get pooling information pooling_type = self.pooling_type() - logits_all = pooling_type == llama_cpp_lib.LLAMA_POOLING_TYPE_NONE + is_rank = pooling_type == llama_cpp_lib.LLAMA_POOLING_TYPE_RANK + is_none = pooling_type == llama_cpp_lib.LLAMA_POOLING_TYPE_NONE - if self.context_params.embeddings is False: - raise RuntimeError( - "Llama model must be created with embeddings=True to call this method" - ) + out_dim = ( + llama_cpp_lib.llama_model_n_cls_out(self._model.model) + if is_rank + else self.n_embd() + ) + + # Preserve the historical bool API while accepting llama.cpp's integer + # normalization modes used by LlamaEmbedding. + if isinstance(normalize, bool): + normalize_mode = 2 if normalize else -1 + elif isinstance(normalize, int): + normalize_mode = normalize + else: + raise TypeError("normalize must be a bool or int") + + def normalize_vector(vector: Sequence[float]) -> List[float]: + values = list(vector) + if normalize_mode == -1 or is_rank: + return values + + array = np.asarray(values, dtype=np.float32) + if normalize_mode == 0: + norm = float(np.max(np.abs(array))) if array.size else 0.0 + scale = 32760.0 + elif normalize_mode == 1: + norm = float(np.sum(np.abs(array))) + scale = 1.0 + elif normalize_mode == 2: + norm = float(np.linalg.norm(array)) + scale = 1.0 + elif normalize_mode > 2: + norm = float( + np.sum(np.abs(array) ** normalize_mode) + ** (1.0 / normalize_mode) + ) + scale = 1.0 + else: + return values + + if norm == 0.0: + return values + return ((array / norm) * scale).tolist() if self.verbose: - llama_cpp_lib.llama_perf_context_reset(self._ctx.ctx) + llama_cpp_lib.llama_perf_context_reset(ctx) if isinstance(input, str): - inputs = [input] + inputs: List[Union[str, List[int]]] = ( + input.split(separator) if separator is not None else [input] + ) + is_single = separator is None else: inputs = input + is_single = False - # reset batch self._batch.reset() + llama_cpp_lib.llama_memory_clear( + llama_cpp_lib.llama_get_memory(ctx), True + ) - # decode and fetch embeddings - data: Union[List[List[float]], List[List[List[float]]]] = [] + data: List[Any] = [] + seq_sizes: List[int] = [] + total_tokens = 0 + + def decode_batch() -> None: + nonlocal seq_sizes + if not seq_sizes: + return - def decode_batch(seq_sizes: List[int]): - llama_cpp_lib.llama_memory_clear(llama_cpp_lib.llama_get_memory(self._ctx.ctx), True) self._ctx.decode(self._batch) + + if is_none: + token_index = 0 + for size in seq_sizes: + token_embeddings: List[List[float]] = [] + for _ in range(size): + ptr = llama_cpp_lib.llama_get_embeddings_ith( + ctx, token_index + ) + token_embeddings.append( + [0.0] * out_dim + if ptr is None + else normalize_vector(ptr[:out_dim]) + ) + token_index += 1 + data.append(token_embeddings) + else: + for seq_id in range(len(seq_sizes)): + ptr = llama_cpp_lib.llama_get_embeddings_seq(ctx, seq_id) + if ptr is None: + embedding = [0.0] * out_dim + else: + embedding = list(ptr[:out_dim]) + + if is_rank: + data.append( + embedding[0] if len(embedding) == 1 else embedding + ) + else: + data.append(normalize_vector(embedding)) + self._batch.reset() + llama_cpp_lib.llama_memory_clear( + llama_cpp_lib.llama_get_memory(ctx), True + ) + seq_sizes = [] - # store embeddings - if pooling_type == llama_cpp_lib.LLAMA_POOLING_TYPE_NONE: - pos: int = 0 - for i, size in enumerate(seq_sizes): - ptr = llama_cpp_lib.llama_get_embeddings(self._ctx.ctx) - embedding: List[List[float]] = [ - ptr[pos + j * n_embd : pos + (j + 1) * n_embd] - for j in range(size) - ] - if normalize: - embedding = [ - internals.normalize_embedding(e) for e in embedding - ] - data.append(embedding) - pos += size + for item in inputs: + if isinstance(item, str): + tokens = self.tokenize(item.encode("utf-8")) + elif isinstance(item, list) and ( + not item or isinstance(item[0], int) + ): + tokens = item else: - for i in range(len(seq_sizes)): - ptr = llama_cpp_lib.llama_get_embeddings_seq(self._ctx.ctx, i) - embedding: List[float] = ptr[:n_embd] - if normalize: - embedding = internals.normalize_embedding(embedding) - data.append(embedding) - - # init state - total_tokens = 0 - s_batch = [] - t_batch = 0 - p_batch = 0 + raise ValueError("Input item must be str or List[int]") - # accumulate batches and encode - for text in inputs: - tokens = self.tokenize(text.encode("utf-8")) - if truncate: - tokens = tokens[:n_batch] + max_tokens = min(n_ctx, n_batch) + if truncate and len(tokens) > max_tokens: + tokens = tokens[:max_tokens] n_tokens = len(tokens) total_tokens += n_tokens - # check for overrun if n_tokens > n_batch: raise ValueError( f"Requested tokens ({n_tokens}) exceed batch size of {n_batch}" ) - # time to eval batch - if t_batch + n_tokens > n_batch: - decode_batch(s_batch) - s_batch = [] - t_batch = 0 - p_batch = 0 + if n_tokens == 0: + # Keep result ordering stable when an empty pre-tokenized input + # follows sequences that are still waiting to be decoded. + decode_batch() + data.append(0.0 if is_rank else []) + continue - # add to batch - self._batch.add_sequence(tokens, p_batch, logits_all) + if ( + self._batch.n_tokens() + n_tokens > n_batch + or len(seq_sizes) >= n_seq_max + ): + decode_batch() - # update batch stats - s_batch.append(n_tokens) - t_batch += n_tokens - p_batch += 1 + seq_id = len(seq_sizes) + logits_array = ( + [True] * n_tokens + if is_none + else [False] * (n_tokens - 1) + [True] + ) + self._batch.add_sequence( + token_array=tokens, + pos_array=list(range(n_tokens)), + seq_ids=[seq_id], + logits_array=logits_array, + ) + seq_sizes.append(n_tokens) - # hanlde last batch - decode_batch(s_batch) + decode_batch() if self.verbose: - llama_cpp_lib.llama_perf_context_print(self._ctx.ctx) + llama_cpp_lib.llama_perf_context_print(ctx) - output = data[0] if isinstance(input, str) else data - - llama_cpp_lib.llama_memory_clear(llama_cpp_lib.llama_get_memory(self._ctx.ctx), True) + output = data[0] if is_single else data self.reset() if return_count: return output, total_tokens - else: - return output + return output def _create_completion( self, From 0d3ae934e759afc6612554980cdcacd84d2ee21b Mon Sep 17 00:00:00 2001 From: JamePeng Date: Sun, 26 Jul 2026 08:58:30 +0800 Subject: [PATCH 59/86] refactor(batch): improve sequence capacity validation guidance - Make LlamaBatch sequence validation errors explain the configured n_seq_max value, valid sequence ID range, and minimum capacity required for parallel batching. - Handle negative sequence IDs separately and provide actionable setup guidance for Llama, LlamaEmbedding, and direct LlamaBatch users. - Remove the unused normalize_embedding helper now that normalization is handled by the embedding pipeline. Signed-off-by: JamePeng --- llama_cpp/_internals.py | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/llama_cpp/_internals.py b/llama_cpp/_internals.py index 4eb2df7db..a41764d7d 100644 --- a/llama_cpp/_internals.py +++ b/llama_cpp/_internals.py @@ -1165,7 +1165,9 @@ def _validate_seq_ids(self, seq_ids: Sequence[int], where: str) -> int: if n_seq_id > self.n_seq_max: raise ValueError( f"LlamaBatch.{where}: token belongs to {n_seq_id} sequences, " - f"but n_seq_max was initialized to {self.n_seq_max}." + f"but this batch was initialized with n_seq_max={self.n_seq_max}. " + f"Increase n_seq_max to at least {n_seq_id} when constructing " + "Llama, LlamaEmbedding, or LlamaBatch." ) for seq_id in seq_ids: @@ -1175,10 +1177,22 @@ def _validate_seq_ids(self, seq_ids: Sequence[int], where: str) -> int: f"{type(seq_id).__name__}." ) - if seq_id < 0 or seq_id >= self.n_seq_max: + if seq_id < 0: raise ValueError( f"LlamaBatch.{where}: invalid seq_id {seq_id}; " - f"expected 0 <= seq_id < {self.n_seq_max}." + "sequence IDs must be non-negative integers." + ) + + if seq_id >= self.n_seq_max: + required_n_seq_max = seq_id + 1 + raise ValueError( + f"LlamaBatch.{where}: seq_id={seq_id} exceeds the configured " + f"sequence capacity (n_seq_max={self.n_seq_max}; valid IDs " + f"are 0 through {self.n_seq_max - 1}). For parallel batching, " + f"initialize Llama or LlamaEmbedding with " + f"n_seq_max>={required_n_seq_max}, or create LlamaBatch " + "with that value. Use seq_id=0 when processing only one " + "sequence." ) return n_seq_id @@ -1487,15 +1501,6 @@ def add_token_embedding( self.batch.logits[idx] = logits self.batch.n_tokens += 1 - -# Embedding functions -def normalize_embedding(embedding): - norm = float(np.linalg.norm(embedding)) - if norm == 0.0: - return embedding - return [v / norm for v in embedding] - - class LlamaTokenDataArray: """ Performance-optimized wrapper for llama_token_data_array. From 29ad3505d2ff81838d21fa94eef6f8e39f1950cd Mon Sep 17 00:00:00 2001 From: JamePeng Date: Sun, 26 Jul 2026 09:01:53 +0800 Subject: [PATCH 60/86] fix(embedding): respect n_seq_max when streaming embedding batches - Use the configured sequence capacity instead of n_ubatch when deciding when to decode the current LlamaEmbedding batch. - This prevents invalid sequence IDs for multi-document inputs and allows the default n_seq_max=1 configuration to process documents sequentially without failing. Signed-off-by: JamePeng --- llama_cpp/llama_embedding.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/llama_cpp/llama_embedding.py b/llama_cpp/llama_embedding.py index d8f6e6bf2..baa4b9f06 100644 --- a/llama_cpp/llama_embedding.py +++ b/llama_cpp/llama_embedding.py @@ -128,7 +128,7 @@ def embed( ctx = self._ctx.ctx n_batch = self.n_batch n_ctx = self._n_ctx - n_ubatch = self.context_params.n_ubatch + n_seq_max = self.context_params.n_seq_max # Determine if it is in Rerank mode try: @@ -137,8 +137,6 @@ def embed( pooling_type = LLAMA_POOLING_TYPE_UNSPECIFIED is_rank = (pooling_type == LLAMA_POOLING_TYPE_RANK) is_none = (pooling_type == LLAMA_POOLING_TYPE_NONE) # Token-level embedding - logits_all = True if is_none else False - # Determine the output dimension if is_rank: out_dim = llama_cpp_lib.llama_model_n_cls_out(self._model.model) @@ -247,7 +245,10 @@ def _decode_batch(): continue # Check Batch Capacity - if (self._batch.n_tokens() + n_tokens > n_batch) or (idx_in_batch >= n_ubatch): + if ( + self._batch.n_tokens() + n_tokens > n_batch + or idx_in_batch >= n_seq_max + ): _decode_batch() idx_in_batch = 0 From 6896e3dadef9041cf3fb9f0ad43c92155c02fcea Mon Sep 17 00:00:00 2001 From: JamePeng Date: Sun, 26 Jul 2026 09:04:05 +0800 Subject: [PATCH 61/86] test(embedding): cover built-in and streaming embedding workflows - Add coverage for actionable LlamaBatch sequence-capacity errors and the maintained embedding APIs on the standard Llama class. - Verify pre-tokenized batches, normalization, separator-based inputs, token accounting, OpenAI-compatible responses, and LlamaEmbedding streaming behavior with n_seq_max=1. - Explicitly close embedding models after integration tests to release native context and model resources. Signed-off-by: JamePeng --- tests/test_llama.py | 107 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 96 insertions(+), 11 deletions(-) diff --git a/tests/test_llama.py b/tests/test_llama.py index bf075845e..df379df7f 100644 --- a/tests/test_llama.py +++ b/tests/test_llama.py @@ -64,6 +64,32 @@ def test_llama_cpp_tokenization(): assert text == llama.detokenize(tokens) +def test_llama_batch_seq_id_error_guidance(): + """Sequence-capacity errors should explain how to fix parallel batching.""" + batch = internals.LlamaBatch( + n_tokens=2, + embd=0, + n_seq_max=1, + verbose=False, + ) + try: + with pytest.raises(ValueError) as exc_info: + batch.add_sequence( + token_array=[1], + pos_array=[0], + seq_ids=[1], + logits_array=[True], + ) + + message = str(exc_info.value) + assert "n_seq_max=1" in message + assert "valid IDs are 0 through 0" in message + assert "n_seq_max>=2" in message + assert "LlamaEmbedding" in message + finally: + batch.close() + + @pytest.fixture def llama_cpp_model_path(): """Fixture to download a real GGUF model for integration tests.""" @@ -365,16 +391,75 @@ def no_e_processor(input_ids, scores): def test_real_llama_embeddings(llama_cpp_model_path): """ - Test Embedding Generation. - Verifies that the model can produce vector embeddings. + Test embedding generation through the specialized LlamaEmbedding class. """ model = LlamaEmbedding( - model_path=llama_cpp_model_path, - n_ctx=32, - n_batch=32, - n_ubatch=32, - pooling_type=LLAMA_POOLING_TYPE_NONE) - # Smoke test for now - embeddings = model.embed("Hello, world!") - assert isinstance(embeddings, list) - assert len(embeddings) > 0 + model_path=llama_cpp_model_path, + n_ctx=32, + n_batch=32, + n_ubatch=32, + pooling_type=LLAMA_POOLING_TYPE_NONE, + ) + try: + # The inherited n_seq_max=1 processes this list as three streaming + # decode batches instead of assigning an invalid seq_id. + embeddings = model.embed(["Hello", "world", "embedding"]) + assert isinstance(embeddings, list) + assert len(embeddings) == 3 + assert all(len(embedding) > 0 for embedding in embeddings) + finally: + model.close() + + +def test_real_llama_base_embedding_api(llama_cpp_model_path): + """ + Test the maintained embedding API on the standard Llama class. + + Covers pre-tokenized batching, normalization, separator-based string + batching, token counts, and the OpenAI-compatible response wrapper. + """ + model = llama_cpp.Llama( + model_path=llama_cpp_model_path, + embeddings=True, + n_ctx=32, + n_batch=32, + n_ubatch=32, + n_seq_max=2, + kv_unified=True, + pooling_type=LLAMA_POOLING_TYPE_NONE, + verbose=False, + ) + + try: + token_inputs = [ + model.tokenize(b"Hello"), + model.tokenize(b"world"), + ] + embeddings, token_count = model.embed( + token_inputs, + normalize=True, + return_count=True, + ) + + assert len(embeddings) == len(token_inputs) + assert token_count == sum(map(len, token_inputs)) + assert len(embeddings[0]) == len(token_inputs[0]) + assert np.linalg.norm(embeddings[0][0]) == pytest.approx(1.0) + + split_embeddings = model.embed( + "Hello\nworld", + separator="\n", + normalize=False, + ) + assert len(split_embeddings) == 2 + + response = model.create_embedding( + ["Hello", "world"], + normalize=2, + ) + assert response["object"] == "list" + assert len(response["data"]) == 2 + assert response["usage"]["prompt_tokens"] > 0 + assert response["usage"]["total_tokens"] == response["usage"]["prompt_tokens"] + finally: + model.close() From 77b7fa1d3e7022a4ebd971171c9c4b4106db402f Mon Sep 17 00:00:00 2001 From: JamePeng Date: Sun, 26 Jul 2026 09:04:40 +0800 Subject: [PATCH 62/86] docs(example): refresh the built-in embedding usage example - Fix the Llama constructor option from embedding=True to embeddings=True and demonstrate L2-normalized output through create_embedding(). Signed-off-by: JamePeng --- examples/high_level_api/high_level_api_embedding.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/high_level_api/high_level_api_embedding.py b/examples/high_level_api/high_level_api_embedding.py index feb0ed68d..bf9621321 100644 --- a/examples/high_level_api/high_level_api_embedding.py +++ b/examples/high_level_api/high_level_api_embedding.py @@ -6,6 +6,6 @@ parser.add_argument("-m", "--model", type=str, default="../models/7B/ggml-model.bin") args = parser.parse_args() -llm = Llama(model_path=args.model, embedding=True) +llm = Llama(model_path=args.model, embeddings=True) -print(llm.create_embedding("Hello world!")) +print(llm.create_embedding("Hello world!", normalize=True)) From 85e4cea81442c4e89255916b51d04988eb64eec2 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Sun, 26 Jul 2026 09:06:54 +0800 Subject: [PATCH 63/86] docs(embedding): document maintained APIs and sequence batch capacity - Replace the deprecated Llama embedding guidance with current embed() and create_embedding() usage. - Document the roles of n_batch, n_ubatch, and n_seq_max, including parallel batching examples, resource considerations, common sequence ID errors, and the required configuration changes. - Clarify that LlamaEmbedding remains a convenience interface for embedding-oriented defaults and reranking workflows. Signed-off-by: JamePeng --- README.md | 39 ++++++++++++++++---- docs/wiki/core/Llama.md | 38 ++++++++++++++++---- docs/wiki/modules/LlamaEmbedding.md | 55 ++++++++++++++++++++++++++--- 3 files changed, 114 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index ab8c39d77..1345c0690 100644 --- a/README.md +++ b/README.md @@ -1648,7 +1648,12 @@ To generate embeddings, use the `LlamaEmbedding` class. It automatically configu from llama_cpp.llama_embedding import LlamaEmbedding, LLAMA_POOLING_TYPE_NONE # Initialize the model (automatically sets embeddings=True) -llm = LlamaEmbedding(model_path="path/to/bge-m3.gguf", n_gpu_layers=-1, pooling_type=LLAMA_POOLING_TYPE_NONE) +llm = LlamaEmbedding( + model_path="path/to/bge-m3.gguf", + n_gpu_layers=-1, + pooling_type=LLAMA_POOLING_TYPE_NONE, + n_seq_max=128, # Maximum independent sequences in one decode batch +) # 1. Simple usage (OpenAI-compatible format) response = llm.create_embedding("Hello, world!") @@ -1662,6 +1667,14 @@ embeddings = llm.embed(documents) # Returns a list of lists (vectors) print(f"Generated {len(embeddings)} vectors.") ``` +> **Parallel batch capacity:** `n_seq_max` controls how many independent +> sequence IDs may coexist in one decode batch; it is not the total number of +> documents accepted by `embed()`. For batch embedding, set it high enough for +> the number of short documents that can fit within `n_batch`. If an error says +> `seq_id=1` exceeds `n_seq_max=1`, initialize the model with at least +> `n_seq_max=2`. For example, use `n_seq_max=8` for up to eight parallel +> sequences. Larger values can use more context resources. + **Advanced Output Formats:** You can request raw arrays or cosine similarity matrices directly: @@ -1755,14 +1768,28 @@ vec_int16 = llm.embed("text", normalize=NORM_MODE_MAX_INT16) embeddings_raw = llm.embed(["search query", "document text"], normalize=NORM_MODE_NONE) ``` -### Legacy Usage (Deprecated) +### Using the standard `Llama` class -The standard `Llama` class still supports basic embedding generation, but it lacks the memory optimizations and reranking capabilities of `LlamaEmbedding`. +The standard `Llama` class also supports the maintained streaming embedding +implementation. Initialize it with `embeddings=True`, then call `embed()` for +raw results or `create_embedding()` for an OpenAI-compatible response. +`LlamaEmbedding` remains a convenient specialized interface because it enables +embedding-oriented defaults and provides the `rank()` helper. ```python -# Old method - Not recommended for large batches or reranking -llm = llama_cpp.Llama(model_path="...", embeddings=True) -emb = llm.create_embedding("text") +llm = llama_cpp.Llama( + model_path="path/to/model.gguf", + embeddings=True, + n_batch=512, + n_seq_max=8, + kv_unified=True, +) + +# OpenAI-compatible response; normalize=True selects L2 normalization. +response = llm.create_embedding(["query", "document"], normalize=True) + +# Raw vectors. Integer normalization modes are also supported. +vectors = llm.embed(["query", "document"], normalize=2) ``` --- diff --git a/docs/wiki/core/Llama.md b/docs/wiki/core/Llama.md index 1f7cce206..d768e43ec 100644 --- a/docs/wiki/core/Llama.md +++ b/docs/wiki/core/Llama.md @@ -4,7 +4,7 @@ title: Llama Class module_name: llama_cpp.llama source_file: llama_cpp/llama.py class_name: Llama -last_updated: 2026-05-16 +last_updated: 2026-07-26 version_target: "latest" --- ``` @@ -430,15 +430,39 @@ The `Llama` class allows you to load multiple LoRAs into VRAM and apply them dyn --- -## Deprecated / Changed APIs +## Embeddings -> ⚠️ **Warning:** The internal embedding methods on the `Llama` class are deprecated and will be removed. +The `Llama` embedding methods are maintained and use streaming batches. Create +the model with `embeddings=True` before calling them. -* `embed()` ➔ **Deprecated.** -* `create_embedding()` ➔ **Deprecated.** +```python +llm = Llama( + model_path="path/to/model.gguf", + embeddings=True, + n_seq_max=8, + kv_unified=True, +) + +# Raw sequence or token-level embeddings. +vectors = llm.embed(["query", "document"], normalize=2) + +# OpenAI-compatible response. +response = llm.create_embedding(["query", "document"], normalize=True) +``` + +`embed()` accepts strings, lists of strings, or pre-tokenized inputs. It supports +token-level output (`LLAMA_POOLING_TYPE_NONE`), sequence pooling, rank-model +outputs, streaming batches, and llama.cpp integer normalization modes. + +For parallel batches, `n_seq_max` must cover every sequence ID active in a +single decode batch. The default `n_seq_max=1` supports only `seq_id=0`. +For example, `n_seq_max=8` permits IDs `0` through `7`. If this capacity is +exceeded, the exception reports the valid range and the minimum value required. +`n_batch` limits tokens, while `n_seq_max` limits independent sequences. -**Migration Note:** Do not use `Llama(..., embeddings=True)` combined with `model.create_embedding(...)`. Instead, use the dedicated `LlamaEmbedding` class, which offers optimized batching and reranking support. -*See: [[LlamaEmbedding]]* +`LlamaEmbedding` remains available as the specialized convenience class. It +automatically enables embedding-oriented context options and adds the `rank()` +helper for formatting query/document pairs. --- diff --git a/docs/wiki/modules/LlamaEmbedding.md b/docs/wiki/modules/LlamaEmbedding.md index 3aa242722..5aa3bd8e0 100644 --- a/docs/wiki/modules/LlamaEmbedding.md +++ b/docs/wiki/modules/LlamaEmbedding.md @@ -3,7 +3,7 @@ title: Llama Embedding module_name: llama_cpp.llama_embedding source_file: llama_cpp/llama_embedding.py class_name: LlamaEmbedding -last_updated: 2026-05-31 +last_updated: 2026-07-26 version_target: "latest" --- @@ -38,6 +38,7 @@ version_target: "latest" | `n_ctx` | int | 0 | Text context window size (0 = model default). | | `n_batch` | int | 512 | Maximum prompt processing batch size. | | `n_ubatch` | int | 512 | Physical batch size. | +| `n_seq_max` | int | 1 (inherited) | Maximum number of independent sequence IDs available in a decode batch. Increase this for parallel embedding batches. | | `pooling_type` | int | `LLAMA_POOLING_TYPE_UNSPECIFIED` (-1) | Pooling strategy used by the model: `LLAMA_POOLING_TYPE_RANK` (4) for rerankers, `LLAMA_POOLING_TYPE_UNSPECIFIED` (-1) for embeddings. | | `n_gpu_layers` | int | 0 | Number of layers offloaded to GPU (0 = CPU only, -1 = all layers). | | `verbose` | bool | True | Whether to print debug information. | @@ -46,9 +47,44 @@ version_target: "latest" ### Initialization Logic 1. Forces `embeddings=True` to enable embedding support. -2. Sets `kv_unified=True` to enable unified KV Cache, allowing arbitrary sequence IDs in a batch without "invalid seq_id" errors. +2. Sets `kv_unified=True` to enable unified KV Cache. Sequence IDs must still + fit within the configured `n_seq_max`. 3. Passes `pooling_type` to the parent class constructor. +### Parallel Batch Capacity + +`n_batch`, `n_ubatch`, and `n_seq_max` control different limits: + +- `n_batch`: maximum number of input tokens in a logical decode batch. +- `n_ubatch`: physical token batch size used by llama.cpp. +- `n_seq_max`: number of independent sequence IDs that may coexist in a decode + batch. + +For multiple documents, set `n_seq_max` to the desired parallel sequence +capacity: + +```python +model = LlamaEmbedding( + model_path="path/to/model.gguf", + n_batch=512, + n_ubatch=512, + n_seq_max=8, +) +``` + +If the configuration is too small, the error includes the current capacity, +valid ID range, and required minimum: + +```text +LlamaBatch.add_sequence: seq_id=1 exceeds the configured sequence capacity +(n_seq_max=1; valid IDs are 0 through 0). For parallel batching, initialize +Llama or LlamaEmbedding with n_seq_max>=2 ... +``` + +`n_seq_max` is not the total number of documents passed to `embed()`; it is the +number that can be active in one decode batch. Increase it carefully because +larger values may require more context resources. + ## Core Methods ### `embed(input, normalize=NORM_MODE_EUCLIDEAN, truncate=True, separator=None, return_count=False)` @@ -129,7 +165,10 @@ version_target: "latest" - Token-level embeddings: `LLAMA_POOLING_TYPE_NONE (0)`. 2. **Batch Optimization for Large Datasets**: - - Adjust `n_batch` and `n_ubatch` to balance performance and memory. + - Adjust `n_batch`, `n_ubatch`, and `n_seq_max` to balance parallelism, + performance, and memory. + - If `seq_id` exceeds the configured capacity, increase `n_seq_max` to at + least `seq_id + 1`. - Streaming processing avoids OOM for large datasets. 3. **Normalization Selection**: @@ -153,7 +192,12 @@ To generate embeddings, use the `LlamaEmbedding` class. It automatically configu from llama_cpp.llama_embedding import LlamaEmbedding, LLAMA_POOLING_TYPE_NONE # Initialize the model (automatically sets embeddings=True) -llm = LlamaEmbedding(model_path="path/to/bge-m3.gguf", n_gpu_layers=-1, pooling_type=LLAMA_POOLING_TYPE_NONE) +llm = LlamaEmbedding( + model_path="path/to/bge-m3.gguf", + n_gpu_layers=-1, + pooling_type=LLAMA_POOLING_TYPE_NONE, + n_seq_max=128, +) # 1. Simple usage (OpenAI-compatible format) response = llm.create_embedding("Hello, world!") @@ -263,7 +307,8 @@ embeddings_raw = llm.embed(["search query", "document text"], normalize=NORM_MOD ## Notes - This class is in development; some features may be unstable, especially reranking model support. -- Performance issues can be addressed by adjusting `n_batch`, `n_ubatch`, and `n_gpu_layers`. +- Performance issues can be addressed by adjusting `n_batch`, `n_ubatch`, + `n_seq_max`, and `n_gpu_layers`. - For custom models, manual `pooling_type` configuration may be required to match model behavior. ## Related Links From df3ca79d9cdd3f9374bd1b821fd73b5ae931a018 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Sun, 26 Jul 2026 09:27:05 +0800 Subject: [PATCH 64/86] docs(llama): expand embedding parameters and API guidance - Add a role overview and reorganize constructor options into focused, readable parameter groups. - Document embedding, pooling, attention, KV cache, sequence capacity, and recurrent-state settings with their defaults and runtime behavior. - Expand the embed() and create_embedding() sections with normalization modes, return shapes, batching semantics, pooling recommendations, OpenAI compatibility notes, and resource-safe examples. - Fix the YAML frontmatter and improve Markdown spacing for cleaner Wiki rendering. Signed-off-by: JamePeng --- docs/wiki/core/Llama.md | 176 ++++++++++++++++++++++++++++++++++------ 1 file changed, 150 insertions(+), 26 deletions(-) diff --git a/docs/wiki/core/Llama.md b/docs/wiki/core/Llama.md index d768e43ec..305624c8f 100644 --- a/docs/wiki/core/Llama.md +++ b/docs/wiki/core/Llama.md @@ -1,4 +1,3 @@ -```yaml --- title: Llama Class module_name: llama_cpp.llama @@ -7,16 +6,29 @@ class_name: Llama last_updated: 2026-07-26 version_target: "latest" --- -``` ## Overview + The `Llama` class is the core, high-level Python wrapper for a `llama.cpp` model. It handles model loading, memory management (KV cache), tokenization, and generation (both base text completion and chat formatting). It includes advanced features like dynamic LoRA routing, dual-mode hybrid/recurrent checkpointing, speculative decoding, and context shifting. +## Role in the Library + +`Llama` is the main user-facing entry point for loading a GGUF model and +creating a native `llama.cpp` context. It exposes completion, chat, tokenization, +embedding, state, sampling, and runtime configuration APIs through one managed +object. + +Use `Llama` when one application needs a general-purpose model interface. +For embedding-only applications, `LlamaEmbedding` provides embedding-oriented +defaults and additional reranking helpers while inheriting the same model and +context lifecycle. + ## Constructor (`__init__`) Initialize the model and context. Note that model loading will immediately allocate RAM/VRAM based on the selected offloading parameters. ### Core Model & Hardware Parameters + | Parameter | Type | Default | Description | | :--- | :--- | :--- | :--- | | `model_path` | `str` | **Required** | Model file path (GGUF format) | @@ -29,23 +41,39 @@ Initialize the model and context. Note that model loading will immediately alloc | `use_mmap` | `bool` | `True` | Whether to use memory mapping (mmap) if possible. | | `use_mlock` | `bool` | `False` | Force the system to keep the model in RAM, preventing swapping. | | `kv_overrides` | `Dict` | `None` | Key-value overrides for the model metadata (supports bool, int, float, str). | -| `numa` | `Union[bool, int]`| `False` | NUMA strategy (e.g., `GGML_NUMA_STRATEGY_DISTRIBUTE`). | +| `numa` | `Union[bool, int]` | `False` | NUMA strategy (e.g., `GGML_NUMA_STRATEGY_DISTRIBUTE`). | + +### Context & Batch Parameters -### Context & Performance Parameters | Parameter | Type | Default | Description | | :--- | :--- | :--- | :--- | | `n_ctx` | `int` | `512` | Text context size. Set to `0` to load from model metadata. | -| `n_batch` | `int` | `2048` | Maximum batch size for prompt processing. | -| `n_ubatch` | `int` | `512` | Physical batch size. | +| `n_keep` | `int` | `256` | Preferred number of leading tokens to preserve during automatic context shifting. | +| `n_batch` | `int` | `2048` | Maximum number of tokens in a logical prompt-processing batch. The effective value cannot exceed `n_ctx`. | +| `n_ubatch` | `int` | `512` | Maximum number of tokens in a physical micro-batch processed by llama.cpp. | +| `n_seq_max` | `int` | `1` | Maximum independent sequence states in one decode batch. Embedding calls split automatically at this limit; larger values enable more parallel sequences. | +| `n_rs_seq` | `int` | `0` | Experimental recurrent-state snapshots retained per sequence for rollback. `0` disables rollback snapshots. | +| `n_outputs_max` | `int` | `0` | Maximum outputs in a physical batch. `0` is converted to the effective `n_batch`. | | `n_threads` | `int` | `None` | Number of threads for generation (defaults to CPU count // 2). | -| `n_threads_batch`| `int` | `None` | Number of threads for batch processing (defaults to CPU count). | -| `flash_attn_type`| `int` | `AUTO` | Controls Flash Attention activation (`LLAMA_FLASH_ATTN_TYPE_AUTO`). | -| `swa_full` | `bool` | `None` | Whether to use full-size SWA cache | -| `kv_unified` | `bool` | `None` | Use single unified KV buffer for the KV cache of all sequences | -| `type_k` / `type_v`| `int` | `None` | KV cache data type for K and V (defaults to `f16`). | -| `offload_kqv` | `bool` | `True` | Whether to offload K, Q, V tensors to GPU. | +| `n_threads_batch` | `int` | `None` | Number of threads for batch processing (defaults to CPU count). | +| `ctx_type` | `int` | `LLAMA_CONTEXT_TYPE_DEFAULT` | Context implementation selected by llama.cpp. Keep the default unless a model or backend requires another context type. | + +### Embedding, Attention & KV Parameters + +| Parameter | Type | Default | Description | +| :--- | :--- | :--- | :--- | +| `embeddings` | `bool` | `False` | Enable embedding extraction alongside logits. Must be `True` before calling `embed()` or `create_embedding()`. | +| `pooling_type` | `int` | `LLAMA_POOLING_TYPE_UNSPECIFIED` | Pooling strategy for embedding output. `UNSPECIFIED` follows model metadata, `NONE` returns token-level vectors, and `RANK` returns classifier or reranking output. | +| `attention_type` | `int` | `LLAMA_ATTENTION_TYPE_UNSPECIFIED` | Attention mode used by the context. `UNSPECIFIED` lets llama.cpp select the model-compatible behavior. | +| `logits_all` | `bool` | `False` | Retain logits for every evaluated token instead of only requested outputs. Completion log probabilities require this mode. | +| `flash_attn_type` | `int` | `LLAMA_FLASH_ATTN_TYPE_AUTO` | Controls when Flash Attention is enabled. | +| `offload_kqv` | `bool` | `True` | Offload K, Q, and V tensor operations to the selected device when supported. | +| `swa_full` | `Optional[bool]` | `None` | Use a full-size sliding-window-attention cache. `None` keeps llama.cpp's default. | +| `kv_unified` | `Optional[bool]` | `None` | Use a unified KV buffer for all sequences. `LlamaEmbedding` enables this automatically. | +| `type_k` / `type_v` | `Optional[int]` | `None` | KV cache data types for keys and values. `None` uses llama.cpp defaults. | ### Advanced & Chat Parameters + | Parameter | Type | Default | Description | | :--- | :--- | :--- | :--- | | `chat_format` | `str` | `None` | String specifying the chat template (e.g., `"llama-2"`, `"chatml"`). Guessed from GGUF if None. | @@ -71,7 +99,9 @@ Initialize the model and context. Note that model loading will immediately alloc ## Core Methods ### `create_chat_completion` + Generates a chat response using the configured `chat_format` or `chat_handler`. + ```python import llama_cpp @@ -89,7 +119,9 @@ print(response["choices"][0]["message"]["content"]) ``` ### `create_completion` / `__call__` + Generates standard text completion from a raw string prompt. + ```python import llama_cpp @@ -99,7 +131,9 @@ print(output["choices"][0]["text"]) ``` ### `generate` + A low-level generator yielding token IDs one by one. Highly customizable with sampling parameters, dynamic LoRA mounting, and control vectors. + ```python import llama_cpp @@ -111,14 +145,18 @@ for token in model.generate(tokens, top_k=40, top_p=0.95, temp=0.2): ``` ### `eval` + Low-level method to ingest and evaluate a sequence of tokens. Used internally to update the KV cache and logits. Handles **Context Shifting** automatically to prevent OOM when the token count exceeds `n_ctx`. + ```python # Evaluates a chunk of tokens and updates internal state model.eval(tokens=[1, 453, 234, 987], active_loras=[{"name": "coding_adapter", "scale": 1.0}]) ``` ### `abort` + Immediately halts an active generation loop safely. + * **Usage**: Typically called from a separate monitoring thread (like a timer). When triggered, the running stream will exit and the final chunk will contain `"finish_reason": "abort"`. ### Runtime Logging Control @@ -158,7 +196,9 @@ llm.set_verbosity(1) ``` ### Dynamic LoRA Management + The `Llama` class allows you to load multiple LoRAs into VRAM and apply them dynamically per-generation or per-eval. + * `load_lora(name: str, path: str)`: Loads an adapter into VRAM (does not apply it yet). * `unload_lora(name: str)`: Releases the specific LoRA from VRAM. * `list_loras() -> List[str]`: Returns names of all registered LoRAs. @@ -436,33 +476,117 @@ The `Llama` embedding methods are maintained and use streaming batches. Create the model with `embeddings=True` before calling them. ```python +from llama_cpp import Llama, LLAMA_POOLING_TYPE_UNSPECIFIED + llm = Llama( - model_path="path/to/model.gguf", + model_path="path/to/embedding-model.gguf", embeddings=True, + pooling_type=LLAMA_POOLING_TYPE_UNSPECIFIED, + n_batch=512, + n_ubatch=512, n_seq_max=8, kv_unified=True, ) -# Raw sequence or token-level embeddings. -vectors = llm.embed(["query", "document"], normalize=2) +try: + # Raw sequence embeddings with explicit L2 normalization. + vectors = llm.embed(["query", "document"], normalize=2) -# OpenAI-compatible response. -response = llm.create_embedding(["query", "document"], normalize=True) + # OpenAI-compatible response. + response = llm.create_embedding( + ["query", "document"], + normalize=True, + ) +finally: + llm.close() ``` -`embed()` accepts strings, lists of strings, or pre-tokenized inputs. It supports -token-level output (`LLAMA_POOLING_TYPE_NONE`), sequence pooling, rank-model -outputs, streaming batches, and llama.cpp integer normalization modes. +### `embed(input, normalize=False, truncate=True, separator=None, return_count=False)` + +Generate raw embedding values for strings or pre-tokenized inputs. + +| Parameter | Type | Default | Description | +|---|---|---|---| +| `input` | `Union[str, List[str], List[List[int]]]` | Required | A single string, a list of strings, or a list containing pre-tokenized token-ID lists. | +| `normalize` | `Union[bool, int]` | `False` | `False` returns raw values, while `True` applies L2 normalization. Integer modes are listed below. Rank outputs are not normalized. | +| `truncate` | `bool` | `True` | Truncate each input to the smaller of the context capacity and logical batch capacity. If disabled, an input longer than `n_batch` raises `ValueError`. | +| `separator` | `Optional[str]` | `None` | Split a single string into multiple independent inputs. When set, the result uses the batch return shape. | +| `return_count` | `bool` | `False` | Return `(result, total_token_count)` instead of only the embedding result. | + +Normalization modes follow the llama.cpp embedding example: + +| Value | Behavior | +|---|---| +| `False` or `-1` | No normalization | +| `True` or `2` | Euclidean/L2 normalization | +| `0` | Scale by the maximum absolute value to a maximum magnitude of `32760` | +| `1` | Taxicab/L1 normalization | +| Integer greater than `2` | Corresponding p-norm normalization | + +Unlike `LlamaEmbedding.embed()`, the standard `Llama.embed()` method defaults to +raw, unnormalized output for backward compatibility. + +The return shape depends on the input and pooling type: + +| Input / pooling mode | Return shape | +|---|---| +| Single string with sequence pooling | `List[float]` | +| String list or separator-split string with sequence pooling | `List[List[float]]` | +| `LLAMA_POOLING_TYPE_NONE` | One token embedding matrix per input: `List[List[float]]` for a single string or `List[List[List[float]]]` for a batch | +| `LLAMA_POOLING_TYPE_RANK` with one classifier output | A scalar for a single string or a list of scalars for a batch | +| `LLAMA_POOLING_TYPE_RANK` with multiple classifier outputs | A classifier vector for each input | +| Any mode with `return_count=True` | `(result, total_token_count)` | + +Use `LLAMA_POOLING_TYPE_UNSPECIFIED` for ordinary sentence embeddings unless +the model documentation requires a specific sequence pooling strategy. +`LLAMA_POOLING_TYPE_NONE` is token-level output and should not be used when one +vector per input document is expected. + +### `create_embedding(input, model=None, normalize=False, truncate=True)` + +Wrap sequence or token-level embedding output in an OpenAI-compatible response: + +```python +{ + "object": "list", + "data": [ + { + "object": "embedding", + "embedding": [...], + "index": 0, + } + ], + "model": "path/to/embedding-model.gguf", + "usage": { + "prompt_tokens": 12, + "total_tokens": 12, + }, +} +``` + +| Parameter | Type | Default | Description | +|---|---|---|---| +| `input` | `Union[str, List[str]]` | Required | One string or a list of strings. | +| `model` | `Optional[str]` | `None` | Model name placed in the response. Defaults to `model_path`. | +| `normalize` | `Union[bool, int]` | `False` | Passed directly to `embed()`. | +| `truncate` | `bool` | `True` | Passed directly to `embed()`. | For parallel batches, `n_seq_max` must cover every sequence ID active in a -single decode batch. The default `n_seq_max=1` supports only `seq_id=0`. -For example, `n_seq_max=8` permits IDs `0` through `7`. If this capacity is -exceeded, the exception reports the valid range and the minimum value required. -`n_batch` limits tokens, while `n_seq_max` limits independent sequences. +single decode batch. The default `n_seq_max=1` is valid and processes multiple +inputs sequentially. Increasing it allows more inputs to be decoded in +parallel; for example, `n_seq_max=8` permits IDs `0` through `7` in one batch. +`n_batch` limits logical input tokens, `n_ubatch` controls the physical token +batch, and `n_seq_max` limits independent sequences. `LlamaEmbedding` remains available as the specialized convenience class. It -automatically enables embedding-oriented context options and adds the `rank()` -helper for formatting query/document pairs. +automatically enables embedding-oriented context options, defaults to L2 +normalization, provides additional output formats, and adds the `rank()` helper +for formatting query/document pairs. + +> **OpenAI compatibility:** use sequence pooling when calling +> `create_embedding()` through an OpenAI-compatible client. Token-level pooling +> (`LLAMA_POOLING_TYPE_NONE`) produces nested token vectors rather than the +> single flat vector normally expected for each input. --- From a27aa9b854180069c1d725315678030efdca58ba Mon Sep 17 00:00:00 2001 From: JamePeng Date: Sun, 26 Jul 2026 13:05:54 +0800 Subject: [PATCH 65/86] docs(embedding): add end-to-end embeddings and reranking guide - Create a schema-compliant feature guide covering sentence embeddings, token-level vectors, reranking workflows, pooling modes, normalization, streaming batch configuration, return shapes, and output formats. - Add complete examples for the standard Llama API, LlamaEmbedding, pre-tokenized inputs, cosine-similarity output, and cross-encoder reranking. - Document common configuration problems, implementation limitations, and the embedding and reranking model families currently listed as supported by the project. - Expose the new feature guide through the Wiki index. Signed-off-by: JamePeng --- docs/wiki/features/embeddings-rerank.md | 419 ++++++++++++++++++++++++ docs/wiki/index.md | 12 + 2 files changed, 431 insertions(+) diff --git a/docs/wiki/features/embeddings-rerank.md b/docs/wiki/features/embeddings-rerank.md index e69de29bb..b5cb54ed4 100644 --- a/docs/wiki/features/embeddings-rerank.md +++ b/docs/wiki/features/embeddings-rerank.md @@ -0,0 +1,419 @@ +--- +title: Embeddings and Reranking +feature_name: Embeddings and Reranking +source_files: + - llama_cpp/llama.py + - llama_cpp/llama_embedding.py + - llama_cpp/_internals.py +last_updated: 2026-07-26 +version_target: "latest" +--- + +# Embeddings and Reranking + +## Overview + +`llama-cpp-python` can use compatible GGUF models for three related inference +workflows: + +- **Sentence or document embeddings** produce one vector per input. +- **Token embeddings** produce one vector per token. +- **Reranking** scores each query/document pair with a cross-encoder model. + +The general-purpose `Llama` class and the specialized `LlamaEmbedding` class +share the same native model and context implementation. Both support streaming +batches, pre-tokenized inputs, multiple pooling modes, and configurable vector +normalization. + +`LlamaEmbedding` adds embedding-oriented defaults, extra output formats, and +the `rank()` helper. The standard `Llama` API is useful when an application +already manages models through the main class or needs both generation and +embedding capabilities. + +## When to Use + +| Goal | Recommended API | Pooling | +|---|---|---| +| Store one vector per sentence or document | `Llama.embed()` or `LlamaEmbedding.embed()` | `LLAMA_POOLING_TYPE_UNSPECIFIED`, or the model-required MEAN/CLS/LAST mode | +| Return an OpenAI-style embedding response | `create_embedding()` | Sequence pooling | +| Inspect a vector for every token | `embed()` | `LLAMA_POOLING_TYPE_NONE` | +| Score documents against a query | `LlamaEmbedding.rank()` | `LLAMA_POOLING_TYPE_RANK` | +| Return raw arrays or a cosine-similarity matrix | `LlamaEmbedding.create_embedding()` | Sequence pooling | + +Use the pooling configuration documented by the model author whenever one is +provided. `LLAMA_POOLING_TYPE_UNSPECIFIED` lets model metadata select the +sequence-pooling behavior and is the safest general default for ordinary +sentence embeddings. + +## Supported Models + +The project README currently lists the following GGUF model families as working +with the embedding and reranking APIs: + +| Model family | Task | GGUF model | +|---|---|---| +| `bge-m3` | Embedding | [bge-m3-GGUF](https://huggingface.co/gpustack/bge-m3-GGUF) | +| `jina-embeddings-v2-base-zh` | Embedding | [jina-embeddings-v2-base-zh-GGUF](https://huggingface.co/gpustack/jina-embeddings-v2-base-zh-GGUF) | +| `jina-embeddings-v3` | Embedding | [jina-embeddings-v3-GGUF](https://huggingface.co/second-state/jina-embeddings-v3-GGUF) | +| `bge-reranker-v2-m3` | Reranking | [bge-reranker-v2-m3-GGUF](https://huggingface.co/gpustack/bge-reranker-v2-m3-GGUF) | +| `qwen3-reranker` | Reranking | [Qwen3-Reranker-GGUF](https://huggingface.co/JamePeng2023/Qwen3-Reranker-GGUF) | + +This is a known-compatible list, not an exhaustive compatibility matrix. +Support for a specific file still depends on its GGUF metadata, pooling +configuration, classifier head, tokenizer, and reranking template. Validate +the output shape and quality before deploying a new model or quantization. + +## Related APIs + +| API | Role | +|---|---| +| `Llama(..., embeddings=True)` | General-purpose model interface with maintained `embed()` and `create_embedding()` methods | +| `LlamaEmbedding(...)` | Specialized subclass that forces `embeddings=True` and `kv_unified=True` | +| `Llama.embed()` | Raw sequence, token-level, or rank output with optional token counting | +| `Llama.create_embedding()` | OpenAI-compatible response wrapper; defaults to raw vectors | +| `LlamaEmbedding.embed()` | Specialized raw embedding API; defaults to L2 normalization | +| `LlamaEmbedding.create_embedding()` | Adds `json`, `json+`, and `array` output formats | +| `LlamaEmbedding.rank()` | Formats query/document pairs and returns reranking scores | +| `Llama.tokenize()` | Converts text into token IDs for pre-tokenized embedding input | + +See [[core/Llama|Llama]] for the general model lifecycle and +[[modules/LlamaEmbedding|Llama Embedding]] for the complete specialized class +reference. + +## Code Examples + +All examples assume that `MODEL_PATH` points to a compatible GGUF embedding or +reranking model. Pooling requirements and output dimensions are model-specific. + +### Sentence Embeddings with `Llama` + +```python +from llama_cpp import Llama, LLAMA_POOLING_TYPE_UNSPECIFIED + + +MODEL_PATH = "path/to/embedding-model.gguf" + +model = Llama( + model_path=MODEL_PATH, + embeddings=True, + pooling_type=LLAMA_POOLING_TYPE_UNSPECIFIED, + n_ctx=512, + n_batch=512, + n_ubatch=512, + n_seq_max=8, + kv_unified=True, + n_gpu_layers=-1, + verbose=False, +) + +try: + documents = [ + "The weather is pleasant today.", + "A storm is expected tomorrow.", + "Vector search compares semantic meaning.", + ] + + vectors, token_count = model.embed( + documents, + normalize=True, + return_count=True, + ) + + print("vectors:", len(vectors)) + print("dimension:", len(vectors[0])) + print("processed tokens:", token_count) + + response = model.create_embedding( + documents, + normalize=2, + ) + print(response["usage"]) +finally: + model.close() +``` + +For `Llama.embed()`, `normalize=False` is the backward-compatible default. +`True` and integer mode `2` both select L2 normalization. + +### Specialized Batch Embeddings and Similarity + +```python +from llama_cpp import LLAMA_POOLING_TYPE_UNSPECIFIED +from llama_cpp.llama_embedding import ( + LlamaEmbedding, + NORM_MODE_EUCLIDEAN, +) + + +MODEL_PATH = "path/to/embedding-model.gguf" + +model = LlamaEmbedding( + model_path=MODEL_PATH, + pooling_type=LLAMA_POOLING_TYPE_UNSPECIFIED, + n_ctx=512, + n_batch=512, + n_ubatch=512, + n_seq_max=8, + n_gpu_layers=-1, + verbose=False, +) + +try: + texts = ["apple", "fruit", "automobile"] + + # "array" always returns one vector entry per input. + vectors = model.create_embedding( + texts, + normalize=NORM_MODE_EUCLIDEAN, + output_format="array", + ) + print("first vector dimension:", len(vectors[0])) + + response = model.create_embedding( + texts, + normalize=NORM_MODE_EUCLIDEAN, + output_format="json+", + ) + print(response["cosineSimilarity"]) +finally: + model.close() +``` + +`json+` extends the OpenAI-style response with `cosineSimilarity` when at least +two compatible sequence vectors are available. + +### Token-Level Embeddings + +```python +from llama_cpp import Llama, LLAMA_POOLING_TYPE_NONE + + +model = Llama( + model_path="path/to/embedding-model.gguf", + embeddings=True, + pooling_type=LLAMA_POOLING_TYPE_NONE, + n_ctx=256, + n_batch=256, + verbose=False, +) + +try: + token_vectors = model.embed("Token-level example", normalize=True) + + print("tokens:", len(token_vectors)) + print("dimension per token:", len(token_vectors[0])) +finally: + model.close() +``` + +Token-level output is a matrix, not one flat vector per document. It is useful +for token analysis and custom pooling, but it is not the normal shape expected +by OpenAI-compatible vector-store clients. + +### Pre-tokenized and Separator-Split Inputs + +```python +from llama_cpp import Llama, LLAMA_POOLING_TYPE_UNSPECIFIED + + +model = Llama( + model_path="path/to/embedding-model.gguf", + embeddings=True, + pooling_type=LLAMA_POOLING_TYPE_UNSPECIFIED, + n_ctx=256, + n_batch=256, + verbose=False, +) + +try: + token_batches = [ + model.tokenize(b"first document"), + model.tokenize(b"second document"), + ] + vectors = model.embed(token_batches, normalize=2) + + split_vectors = model.embed( + "first document\nsecond document", + separator="\n", + normalize=2, + ) + + print(len(vectors), len(split_vectors)) +finally: + model.close() +``` + +When `separator` is set, a single string is treated as a batch and the return +value uses the batch shape. + +### Reranking Query/Document Pairs + +```python +from llama_cpp import LLAMA_POOLING_TYPE_RANK +from llama_cpp.llama_embedding import LlamaEmbedding + + +RERANK_MODEL_PATH = "path/to/reranker-model.gguf" + +ranker = LlamaEmbedding( + model_path=RERANK_MODEL_PATH, + pooling_type=LLAMA_POOLING_TYPE_RANK, + n_ctx=1024, + n_batch=1024, + n_ubatch=512, + n_seq_max=8, + n_gpu_layers=-1, + verbose=False, +) + +try: + query = "What causes rain?" + documents = [ + "Rain forms when atmospheric water vapor condenses and falls.", + "A cake is made from flour, eggs, and sugar.", + "Cloud droplets grow until gravity pulls them toward the ground.", + ] + + scores = ranker.rank(query, documents) + ranked = sorted( + zip(documents, scores), + key=lambda item: item[1], + reverse=True, + ) + + for document, score in ranked: + print(f"{score:.6f} {document}") +finally: + ranker.close() +``` + +`rank()` first checks for a model-provided `rerank` chat template. If no +template exists, it constructs a sequence from the model's BOS, separator, and +EOS tokens. + +## Configuration Notes + +### Pooling Modes + +| Constant | Output behavior | Typical use | +|---|---|---| +| `LLAMA_POOLING_TYPE_UNSPECIFIED` | Uses the model-configured pooling behavior | Default for sentence embedding models | +| `LLAMA_POOLING_TYPE_NONE` | One vector per token | Token analysis or custom pooling | +| `LLAMA_POOLING_TYPE_MEAN` | Mean-pooled sequence vector | Models trained for mean pooling | +| `LLAMA_POOLING_TYPE_CLS` | Vector from the classification token | Models trained with CLS pooling | +| `LLAMA_POOLING_TYPE_LAST` | Vector from the final token | Models trained with last-token pooling | +| `LLAMA_POOLING_TYPE_RANK` | Classifier or reranking output | Cross-encoder reranking models | + +Do not select `LLAMA_POOLING_TYPE_NONE` when one vector per input is required. +It changes both the amount of output and its nesting depth. + +### Normalization Modes + +| Mode | Value | Behavior | +|---|---:|---| +| `NORM_MODE_NONE` | `-1` | Return raw values | +| `NORM_MODE_MAX_INT16` | `0` | Scale the maximum absolute component to `32760` | +| `NORM_MODE_TAXICAB` | `1` | L1/taxicab normalization | +| `NORM_MODE_EUCLIDEAN` | `2` | L2/Euclidean normalization | +| p-norm | Any integer greater than `2` | Normalize using the corresponding p-norm | + +The constant `NORM_MODE_PNORM` currently has value `6`; callers may also pass a +different integer greater than `2`. + +Normalization defaults differ between the two classes: + +| API | Default | +|---|---| +| `Llama.embed()` / `Llama.create_embedding()` | Raw output (`False`) | +| `LlamaEmbedding.embed()` / `LlamaEmbedding.create_embedding()` | L2 (`NORM_MODE_EUCLIDEAN`) | +| Rank output | Never normalized | + +L2-normalized vectors are convenient for cosine similarity because their dot +product is their cosine similarity. + +### Batch and Context Capacity + +| Parameter | Controls | +|---|---| +| `n_ctx` | Maximum context length available to an input sequence | +| `n_batch` | Maximum tokens in one logical decode batch | +| `n_ubatch` | Physical token micro-batch size used by llama.cpp | +| `n_seq_max` | Maximum independent sequences decoded together | + +Embedding input lists are streamed through multiple decode batches. The +default `n_seq_max=1` is valid and processes inputs sequentially. Increasing it +allows more independent sequences to be decoded together, but may use more +context resources. + +Each individual tokenized sequence must fit the configured logical batch +capacity. Choose `n_batch` large enough for the longest intended input and use +`truncate=True` when truncation is acceptable. + +### Input and Return Shapes + +| Input and mode | Direct `embed()` result | +|---|---| +| Single string with sequence pooling | `List[float]` | +| String list with sequence pooling | `List[List[float]]` | +| Separator-split string with sequence pooling | `List[List[float]]` | +| Single string with token-level pooling | `List[List[float]]` | +| String list with token-level pooling | `List[List[List[float]]]` | +| Rank model with one classifier output | Scalar for one string; list of scalars for a batch | +| Rank model with multiple classifier outputs | Classifier vector per input | +| Any input with `return_count=True` | `(result, processed_token_count)` | + +Token counts are measured after tokenization and any applied truncation. + +### Output Wrappers + +`Llama.create_embedding()` returns an OpenAI-compatible dictionary containing +`object`, `data`, `model`, and token `usage`. + +`LlamaEmbedding.create_embedding()` supports: + +| `output_format` | Result | +|---|---| +| `"json"` | OpenAI-style response | +| `"json+"` | OpenAI-style response plus a cosine-similarity matrix when available | +| `"array"` | Raw list containing one output entry per input | + +For OpenAI-compatible vector-store clients, use sequence pooling so each +`data[i]["embedding"]` value is a flat vector. + +### Common Configuration Problems + +| Symptom | Cause | Action | +|---|---|---| +| `Llama model must be created with embeddings=True` | Standard `Llama` was initialized without embedding extraction | Recreate it with `embeddings=True` | +| Output is a matrix for each document | `LLAMA_POOLING_TYPE_NONE` selects token-level output | Use `UNSPECIFIED` or the pooling mode required by the model | +| `seq_id` exceeds `n_seq_max` in custom batch code | A manual sequence ID is outside the configured capacity | Increase `n_seq_max` or use IDs within `0..n_seq_max-1` | +| A long input exceeds `n_batch` | One tokenized sequence is larger than the logical batch | Increase `n_batch`, shorten the input, or enable truncation | +| Local source changes are not visible | Python imported an installed `site-packages` build | Print `llama_cpp.__file__`, then reinstall or adjust the development environment | + +## Limitations + +- Embedding dimensions, valid pooling modes, tokenization, and reranking heads + are determined by the GGUF model. A model that was not exported for the + requested task may not produce meaningful output. +- `rank()` returns raw model scores. They are not automatically calibrated as + probabilities and should primarily be compared within the same query. +- For a two-output reranking head, `rank()` uses the first output as the score. + It does not apply softmax. +- The fallback reranking prompt depends on the model's BOS, separator, and EOS + tokens. Prefer a GGUF model containing a suitable `rerank` chat template. +- `json+` similarity output is intended for at least two compatible, + fixed-length sequence vectors. It is not suitable for ragged token-level + matrices or scalar rank scores. +- Embedding calls clear the context memory used by the operation. Do not expect + a previous completion KV-cache state to remain reusable after embedding on + the same model instance. +- Model and reranking support still requires broader testing across GGUF + architectures. Validate output quality and shape before production use. + +## Related Features +- [[Index-Home](https://github.com/JamePeng/llama-cpp-python/blob/main/docs/wiki/index.md)] +- [[core/Llama|Llama](https://github.com/JamePeng/llama-cpp-python/blob/main/docs/wiki/core/Llama.md)] — General model lifecycle and built-in embedding APIs. +- [[modules/LlamaEmbedding|Llama Embedding](https://github.com/JamePeng/llama-cpp-python/blob/main/docs/wiki/modules/LlamaEmbedding.md)] — Specialized API reference, + normalization constants, and reranking methods. +- [[install|Installation](https://github.com/JamePeng/llama-cpp-python/blob/main/docs/wiki/install.md)] — Backend selection, GPU acceleration, and source + installation. diff --git a/docs/wiki/index.md b/docs/wiki/index.md index 8e5dbed14..bc029f739 100644 --- a/docs/wiki/index.md +++ b/docs/wiki/index.md @@ -44,6 +44,17 @@ These pages document major source modules and related classes. --- +### Features + +Workflow guides combine related classes and configuration into complete usage +patterns. + +| Page | Description | +|---|---| +| [features/embeddings-rerank\|Embeddings and Reranking] | Sentence embeddings, token-level vectors, normalization, streaming batches, similarity output, and cross-encoder reranking. | + +--- + ### Development This section contains maintainer-facing development notes, workflows, and LLM-assisted helper tools for working on `llama-cpp-python`. @@ -99,6 +110,7 @@ Currently available pages: - `modules/LlamaGrammar.md` - `modules/LlamaSpeculative.md` - `modules/Logger.md` +- `features/embeddings-rerank.md` - `development/git-commit-generation-agent.md` - `SCHEMA.md` - `contributing-to-wiki.md` From 1857a6065ea4ed47043ebbc7868b95dee6bf7e94 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Sun, 26 Jul 2026 14:51:44 +0800 Subject: [PATCH 66/86] docs(readme): replace the new logo with fork project branding - Add the new llama-cpp-python logo asset under docs and update the README header to reference the repository-local image. - the new logo which combined llama, C++, and Project branding remains readable. Signed-off-by: JamePeng --- README.md | 2 +- docs/icon.png | Bin 0 -> 331909 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 docs/icon.png diff --git a/README.md b/README.md index 1345c0690..c07fafa73 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- + llama-cpp-python logo

# Python Bindings for [`llama.cpp`](https://github.com/ggml-org/llama.cpp) diff --git a/docs/icon.png b/docs/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..d2d754d746d23a777296c98c996e0ca949cf80ee GIT binary patch literal 331909 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 literal 0 HcmV?d00001 From 5685fea7f410c1b4f4bbfbd7bf1d15f000a7b4da Mon Sep 17 00:00:00 2001 From: JamePeng Date: Sun, 26 Jul 2026 15:41:39 +0800 Subject: [PATCH 67/86] test(chat-format): modernize coverage with Qwen3.5-style templates Replace the legacy Mistral-focused chat format tests with self-contained Qwen3.5-style Jinja template coverage. - verify ChatML system, user, and assistant message rendering - cover enabled and disabled thinking generation prompts - test image and video placeholders with vision identifiers - validate tool definitions, tool calls, and tool response history - add clear error coverage for invalid message structures - verify model-specific stop token criteria - keep the tests independent of tokenizer files and model weights Signed-off-by: JamePeng --- tests/test_llama_chat_format.py | 368 +++++++++++++++++++++++++------- 1 file changed, 288 insertions(+), 80 deletions(-) diff --git a/tests/test_llama_chat_format.py b/tests/test_llama_chat_format.py index f031bf72b..4860a4c7d 100644 --- a/tests/test_llama_chat_format.py +++ b/tests/test_llama_chat_format.py @@ -1,89 +1,297 @@ -import json - import jinja2 +import numpy as np +import pytest + +from llama_cpp.llama_chat_format import Jinja2ChatFormatter + +QWEN35_EOS_TOKEN = "<|im_end|>" + +# A compact Qwen3.5-style template keeps these tests independent of model files. +QWEN35_CHAT_TEMPLATE = r""" +{%- set image_count = namespace(value=0) %} +{%- set video_count = namespace(value=0) %} +{%- macro render_content(content, is_system=false) %} + {%- if content is string %} + {{- content }} + {%- elif content is iterable and content is not mapping %} + {%- for item in content %} + {%- if "image" in item or "image_url" in item or item.type == "image" %} + {%- if is_system %} + {{- raise_exception("System message cannot contain images.") }} + {%- endif %} + {%- set image_count.value = image_count.value + 1 %} + {%- if add_vision_id %} + {{- "Picture " ~ image_count.value ~ ": " }} + {%- endif %} + {{- "<|vision_start|><|image_pad|><|vision_end|>" }} + {%- elif "video" in item or item.type == "video" %} + {%- if is_system %} + {{- raise_exception("System message cannot contain videos.") }} + {%- endif %} + {%- set video_count.value = video_count.value + 1 %} + {%- if add_vision_id %} + {{- "Video " ~ video_count.value ~ ": " }} + {%- endif %} + {{- "<|vision_start|><|video_pad|><|vision_end|>" }} + {%- elif "text" in item %} + {{- item.text }} + {%- else %} + {{- raise_exception("Unexpected item type in content.") }} + {%- endif %} + {%- endfor %} + {%- elif content is none or content is undefined %} + {{- "" }} + {%- else %} + {{- raise_exception("Unexpected content type.") }} + {%- endif %} +{%- endmacro %} +{%- if not messages %} + {{- raise_exception("No messages provided.") }} +{%- endif %} +{%- if tools %} + {{- "<|im_start|>system\n# Tools\n\n" }} + {%- for tool in tools %} + {{- "\n" ~ (tool | tojson) }} + {%- endfor %} + {{- "\n<|im_end|>\n" }} +{%- endif %} +{%- for message in messages %} + {%- set content = render_content( + message.content, message.role == "system" + ) | trim %} + {%- if message.role == "system" %} + {%- if not loop.first %} + {{- raise_exception("System message must be at the beginning.") }} + {%- endif %} + {{- "<|im_start|>system\n" ~ content ~ "<|im_end|>\n" }} + {%- elif message.role == "user" %} + {{- "<|im_start|>user\n" ~ content ~ "<|im_end|>\n" }} + {%- elif message.role == "assistant" %} + {{- "<|im_start|>assistant\n" }} + {%- if message.reasoning_content is string %} + {{- "\n" ~ (message.reasoning_content | trim) + ~ "\n\n\n" }} + {%- endif %} + {{- content }} + {%- if message.tool_calls %} + {%- for tool_call in message.tool_calls %} + {%- set call = tool_call.function %} + {{- "\n\n\n\n" }} + {%- for name, value in call.arguments | items %} + {{- "\n" ~ value + ~ "\n\n" }} + {%- endfor %} + {{- "\n" }} + {%- endfor %} + {%- endif %} + {{- "<|im_end|>\n" }} + {%- elif message.role == "tool" %} + {{- "<|im_start|>user\n\n" ~ content + ~ "\n<|im_end|>\n" }} + {%- else %} + {{- raise_exception("Unexpected message role.") }} + {%- endif %} +{%- endfor %} +{%- if add_generation_prompt %} + {{- "<|im_start|>assistant\n" }} + {%- if enable_thinking is defined and enable_thinking is false %} + {{- "\n\n\n\n" }} + {%- else %} + {{- "\n" }} + {%- endif %} +{%- endif %} +""" + + +@pytest.fixture() +def qwen35_formatter() -> Jinja2ChatFormatter: + return Jinja2ChatFormatter( + template=QWEN35_CHAT_TEMPLATE, + eos_token=QWEN35_EOS_TOKEN, + bos_token="", + add_generation_prompt=True, + ) + -from llama_cpp import ( - ChatCompletionRequestUserMessage, +def test_qwen35_basic_conversation(qwen35_formatter: Jinja2ChatFormatter): + response = qwen35_formatter( + messages=[ + {"role": "system", "content": "Be concise."}, + {"role": "user", "content": "Hello"}, + ], + enable_thinking=False, + ) + + assert response.prompt == ( + "<|im_start|>system\n" + "Be concise.<|im_end|>\n" + "<|im_start|>user\n" + "Hello<|im_end|>\n" + "<|im_start|>assistant\n" + "\n\n\n\n" + ) + assert response.stop == [QWEN35_EOS_TOKEN] + assert response.added_special is True + + +@pytest.mark.parametrize( + ("enable_thinking", "expected_suffix"), + [ + (True, "<|im_start|>assistant\n\n"), + (False, "<|im_start|>assistant\n\n\n\n\n"), + ], ) -import llama_cpp.llama_types as llama_types -import llama_cpp.llama_chat_format as llama_chat_format - -from llama_cpp.llama_chat_format import hf_tokenizer_config_to_chat_formatter - -def test_mistral_instruct(): - chat_template = "{{ bos_token }}{% for message in messages %}{% if (message['role'] == 'user') != (loop.index0 % 2 == 0) %}{{ raise_exception('Conversation roles must alternate user/assistant/user/assistant/...') }}{% endif %}{% if message['role'] == 'user' %}{{ '[INST] ' + message['content'] + ' [/INST]' }}{% elif message['role'] == 'assistant' %}{{ message['content'] + eos_token}}{% else %}{{ raise_exception('Only user and assistant roles are supported!') }}{% endif %}{% endfor %}" - chat_formatter = jinja2.Template(chat_template) - messages = [ - llama_types.ChatCompletionRequestUserMessage(role="user", content="Instruction"), - llama_types.ChatCompletionRequestAssistantMessage(role="assistant", content="Model answer"), - llama_types.ChatCompletionRequestUserMessage(role="user", content="Follow-up instruction"), - ] - response = llama_chat_format.format_mistral_instruct( - messages=messages, +def test_qwen35_generation_prompt_thinking_modes( + qwen35_formatter: Jinja2ChatFormatter, + enable_thinking: bool, + expected_suffix: str, +): + response = qwen35_formatter( + messages=[{"role": "user", "content": "Solve this problem."}], + enable_thinking=enable_thinking, ) - prompt = ("" if response.added_special else "") + response.prompt - reference = chat_formatter.render( - messages=messages, - bos_token="", - eos_token="", + + assert response.prompt.endswith(expected_suffix) + + +def test_qwen35_multimodal_content(qwen35_formatter: Jinja2ChatFormatter): + # Qwen3.5 assigns separate sequence numbers to images and videos. + response = qwen35_formatter( + messages=[ + { + "role": "user", + "content": [ + { + "type": "image_url", + "image_url": {"url": "image.png"}, + }, + {"type": "text", "text": "Compare this with "}, + {"type": "video", "video": "video.mp4"}, + ], + } + ], + add_vision_id=True, + enable_thinking=False, ) - assert prompt == reference - - -mistral_7b_tokenizer_config = """{ - "add_bos_token": true, - "add_eos_token": false, - "added_tokens_decoder": { - "0": { - "content": "", - "lstrip": false, - "normalized": false, - "rstrip": false, - "single_word": false, - "special": true - }, - "1": { - "content": "", - "lstrip": false, - "normalized": false, - "rstrip": false, - "single_word": false, - "special": true - }, - "2": { - "content": "", - "lstrip": false, - "normalized": false, - "rstrip": false, - "single_word": false, - "special": true - } - }, - "additional_special_tokens": [], - "bos_token": "", - "clean_up_tokenization_spaces": false, - "eos_token": "", - "legacy": true, - "model_max_length": 1000000000000000019884624838656, - "pad_token": null, - "sp_model_kwargs": {}, - "spaces_between_special_tokens": false, - "tokenizer_class": "LlamaTokenizer", - "unk_token": "", - "use_default_system_prompt": false, - "chat_template": "{{ bos_token }}{% for message in messages %}{% if (message['role'] == 'user') != (loop.index0 % 2 == 0) %}{{ raise_exception('Conversation roles must alternate user/assistant/user/assistant/...') }}{% endif %}{% if message['role'] == 'user' %}{{ '[INST] ' + message['content'] + ' [/INST]' }}{% elif message['role'] == 'assistant' %}{{ message['content'] + eos_token}}{% else %}{{ raise_exception('Only user and assistant roles are supported!') }}{% endif %}{% endfor %}" -}""" - - -def test_hf_tokenizer_config_str_to_chat_formatter(): - tokenizer_config = json.loads(mistral_7b_tokenizer_config) - chat_formatter = hf_tokenizer_config_to_chat_formatter( - tokenizer_config + + assert "Picture 1: <|vision_start|><|image_pad|><|vision_end|>" in response.prompt + assert ( + "Compare this with Video 1: " + "<|vision_start|><|video_pad|><|vision_end|>" in response.prompt ) - chat_formatter_respoonse = chat_formatter( + assert response.prompt.count("<|vision_start|>") == 2 + assert response.prompt.endswith("<|im_start|>assistant\n\n\n\n\n") + + +def test_qwen35_tools_and_tool_history(qwen35_formatter: Jinja2ChatFormatter): + tools = [ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get the current weather for a city", + "parameters": { + "type": "object", + "properties": {"city": {"type": "string"}}, + "required": ["city"], + }, + }, + } + ] + response = qwen35_formatter( messages=[ - ChatCompletionRequestUserMessage(role="user", content="Hello, world!"), - ] + {"role": "user", "content": "What is the weather?"}, + { + "role": "assistant", + "content": "I will check.", + "reasoning_content": "A weather lookup is required.", + "tool_calls": [ + { + "id": "call-1", + "type": "function", + "function": { + "name": "get_weather", + "arguments": {"city": "London"}, + }, + } + ], + }, + { + "role": "tool", + "tool_call_id": "call-1", + "content": "Sunny, 28 C", + }, + ], + tools=tools, + enable_thinking=False, + ) + + # Tool calls and their responses use Qwen3.5's XML-like markers. + assert '"description": "Get the current weather for a city"' in response.prompt + assert "\nA weather lookup is required.\n" in response.prompt + assert ( + "\n" + "\n" + "\n" + "London\n" + "\n" + "\n" + "" in response.prompt + ) + assert "\nSunny, 28 C\n" in response.prompt + + +@pytest.mark.parametrize( + ("messages", "error"), + [ + ([], "No messages provided."), + ( + [ + {"role": "user", "content": "Hello"}, + {"role": "system", "content": "Too late"}, + ], + "System message must be at the beginning.", + ), + ( + [ + { + "role": "system", + "content": [ + { + "type": "image_url", + "image_url": {"url": "image.png"}, + } + ], + }, + {"role": "user", "content": "Hello"}, + ], + "System message cannot contain images.", + ), + ], +) +def test_qwen35_rejects_invalid_messages( + qwen35_formatter: Jinja2ChatFormatter, + messages, + error: str, +): + with pytest.raises(jinja2.TemplateError, match=error): + qwen35_formatter(messages=messages) + + +def test_qwen35_stop_token_ids(): + # Verify that model-specific stop token IDs terminate generation. + formatter = Jinja2ChatFormatter( + template=QWEN35_CHAT_TEMPLATE, + eos_token=QWEN35_EOS_TOKEN, + bos_token="", + stop_token_ids=[248044], ) + response = formatter(messages=[{"role": "user", "content": "Hello"}]) + + assert response.stopping_criteria is not None + criterion = response.stopping_criteria[0] + logits = np.empty(0, dtype=np.single) - assert chat_formatter_respoonse.prompt == ("[INST] Hello, world! [/INST]" "") + assert criterion(np.array([], dtype=np.intc), logits) is False + assert criterion(np.array([1, 248044], dtype=np.intc), logits) is True + assert criterion(np.array([1, 2], dtype=np.intc), logits) is False From 998b7b5a5318ab6d53c66a7cf79f7760e5b9f83c Mon Sep 17 00:00:00 2001 From: JamePeng Date: Sun, 26 Jul 2026 17:34:37 +0800 Subject: [PATCH 68/86] fix(_internals): clean up native resources on initialization failures - Register native model and batch ownership immediately after allocation so later validation failures cannot leak llama.cpp resources. Free a loaded model when vocab lookup fails, and route mixed-batch setup failures through idempotent cleanup. - Initialize sampling-context resource fields before fallible setup and make partial teardown safe to repeat. This prevents missing attributes from interrupting cleanup when sampler-chain construction fails. - Clear model, vocabulary, and sampling parameter references after native context and sampler resources have been released. This prevents closed wrapper objects from unnecessarily keeping models and related Python objects alive. - Add failure-injection tests that verify model and batch handles are freed exactly once and partially initialized sampling contexts release their resources idempotently.Extend lifecycle tests to verify that parent references are cleared and that repeated close calls remain safe. Signed-off-by: JamePeng --- llama_cpp/_internals.py | 96 +++++++++++++++++-------- tests/test_llama.py | 152 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 218 insertions(+), 30 deletions(-) diff --git a/llama_cpp/_internals.py b/llama_cpp/_internals.py index a41764d7d..9b37ebcc7 100644 --- a/llama_cpp/_internals.py +++ b/llama_cpp/_internals.py @@ -54,11 +54,14 @@ def __init__( self.params = params self.verbose = verbose self._exit_stack = ExitStack() + self.model = None + self.vocab = None + self._lora_registry: Dict[str, LlamaLoraAdapter] = {} model = None if not os.path.exists(path_model): - raise ValueError(f"Model path does not exist: {path_model}") + raise ValueError(f"LlamaModel[__init__]: Model path does not exist: {path_model}") with suppress_stdout_stderr(disable=verbose): model = llama_cpp.llama_model_load_from_file( @@ -68,15 +71,20 @@ def __init__( if model is None: raise ValueError(f"Failed to load model from file: {path_model}") - vocab = llama_cpp.llama_model_get_vocab(model) - - if vocab is None: - raise ValueError(f"Failed to get vocab from model: {path_model}") - + # Record ownership immediately so every later failure can release the + # native model. In particular, a failed vocab lookup must not leak the + # successfully loaded model. self.model = model - self.vocab = vocab + try: + vocab = llama_cpp.llama_model_get_vocab(model) + if vocab is None: + raise ValueError(f"LlamaModel[__init__]: Failed to get vocab from model: {path_model}") + except BaseException: + llama_cpp.llama_model_free(model) + self.model = None + raise - self._lora_registry: Dict[str, LlamaLoraAdapter] = {} + self.vocab = vocab def close(self): """Manually free LlamaModel and Vocab/Lora resources.""" @@ -574,6 +582,10 @@ def close(self): self._exit_stack.close() self._exit_stack = None + # The context no longer needs to keep its parent model alive once the + # native context and its callbacks have been released. + self.model = None + def __del__(self): self.close() @@ -1025,6 +1037,7 @@ def __init__( self._token_buf = None self._owns_token = False self._exit_stack = ExitStack() + self.batch = None # llama_batch_init allocates either batch.token or batch.embd: # @@ -1046,25 +1059,30 @@ def __init__( f"llama_batch_init({n_tokens},{embd},{n_seq_max})" ) - if mixed: - if bool(batch.token): - raise RuntimeError( - "LlamaBatch[__init__]: expected batch.token to be NULL for " - "mixed embedding batch initialized with embd > 0." - ) - if not bool(batch.embd): - raise RuntimeError( - "LlamaBatch[__init__]: expected batch.embd to be non-NULL " - "for mixed batch." - ) - - self._token_buf = ( - llama_cpp.llama_token * self.n_tokens_capacity - )() - batch.token = self._token_buf - self._owns_token = True - + # Take ownership before validating or allocating supplementary Python + # buffers so close() can release the native allocation on every failure. self.batch = batch + try: + if mixed: + if bool(batch.token): + raise RuntimeError( + "LlamaBatch[__init__]: expected batch.token to be NULL for " + "mixed embedding batch initialized with embd > 0." + ) + if not bool(batch.embd): + raise RuntimeError( + "LlamaBatch[__init__]: expected batch.embd to be non-NULL " + "for mixed batch." + ) + + self._token_buf = ( + llama_cpp.llama_token * self.n_tokens_capacity + )() + batch.token = self._token_buf + self._owns_token = True + except BaseException: + self.close() + raise def close(self): """Manually free LlamaBatch resources.""" @@ -1894,6 +1912,19 @@ def __init__( self.model = model self.params = params + # Initialize every resource-bearing attribute before performing work + # that can fail. This keeps close() safe for partially initialized + # instances. + self.prev = None + self._cur_p = None + self.sampler_chain = None + self.grammar_sampler = None + self.reasoning_budget_sampler = None + self._logits_view = None + self._logits_ptr_addr = None + self._single_token = None + self._single_array = None + self.vocab = llama_cpp.llama_model_get_vocab(model.model) self.n_vocab = model.n_vocab() @@ -1940,7 +1971,6 @@ def __init__( self._build_sampler_chain() # Grammar sampler - self.grammar_sampler = None if params.grammar: self.grammar_sampler = GrammarSampler( model, @@ -2256,12 +2286,12 @@ def close(self): # Free grammar sampler if it was initialized. # This releases underlying llama.cpp sampler memory. - if self.grammar_sampler: + if getattr(self, "grammar_sampler", None): self.grammar_sampler.close() self.grammar_sampler = None # Free the sampler chain and all attached C samplers. - if self.sampler_chain: + if getattr(self, "sampler_chain", None): self.sampler_chain.close() self.sampler_chain = None @@ -2279,7 +2309,7 @@ def close(self): self._cur_p = None # Clear token history deque to drop references. - if hasattr(self, "prev"): + if getattr(self, "prev", None) is not None: self.prev.clear() self.prev = None @@ -2291,6 +2321,12 @@ def close(self): self._single_token = None self._single_array = None + # A closed sampling context must not keep the model or configuration + # graph alive merely because the wrapper itself is still referenced. + self.vocab = None + self.model = None + self.params = None + def __del__(self): try: self.close() diff --git a/tests/test_llama.py b/tests/test_llama.py index df379df7f..b233ea526 100644 --- a/tests/test_llama.py +++ b/tests/test_llama.py @@ -19,6 +19,158 @@ MODEL = "./vendor/llama.cpp/models/ggml-vocab-llama-spm.gguf" +def test_model_init_frees_native_model_when_vocab_lookup_fails(monkeypatch): + native_model_handle = object() + freed_model_handles = [] + + def model_path_exists(_path): + return True + + def load_native_model(_path, _params): + return native_model_handle + + def fail_to_get_model_vocab(_model_handle): + return None + + def record_model_free(model_handle): + freed_model_handles.append(model_handle) + + monkeypatch.setattr(internals.os.path, "exists", model_path_exists) + monkeypatch.setattr( + internals.llama_cpp, + "llama_model_load_from_file", + load_native_model, + ) + monkeypatch.setattr( + internals.llama_cpp, + "llama_model_get_vocab", + fail_to_get_model_vocab, + ) + monkeypatch.setattr( + internals.llama_cpp, + "llama_model_free", + record_model_free, + ) + + with pytest.raises(ValueError, match="Failed to get vocab"): + internals.LlamaModel( + path_model="model.gguf", + params=object(), + verbose=False, + ) + + assert freed_model_handles == [native_model_handle] + + +def test_batch_init_frees_native_batch_when_validation_fails(monkeypatch): + class InvalidMixedNativeBatch: + token = object() + embd = object() + + invalid_mixed_batch = InvalidMixedNativeBatch() + freed_batch_handles = [] + + def allocate_invalid_mixed_batch(_n_tokens, _embd, _n_seq_max): + return invalid_mixed_batch + + def record_batch_free(batch_handle): + freed_batch_handles.append(batch_handle) + + monkeypatch.setattr( + internals.llama_cpp, + "llama_batch_init", + allocate_invalid_mixed_batch, + ) + monkeypatch.setattr( + internals.llama_cpp, + "llama_batch_free", + record_batch_free, + ) + + with pytest.raises(RuntimeError, match="expected batch.token to be NULL"): + internals.LlamaBatch( + n_tokens=1, + embd=1, + n_seq_max=1, + mixed=True, + verbose=False, + ) + + assert freed_batch_handles == [invalid_mixed_batch] + + +def test_context_close_releases_parent_references(): + context = internals.LlamaContext.__new__(internals.LlamaContext) + context.ctx = None + context.model = object() + context.params = object() + context._exit_stack = None + + context.close() + context.close() # Closing an already closed context must be a no-op. + + assert context.model is None + assert context.params is None + + +def test_sampling_context_partial_init_can_close_idempotently(monkeypatch): + closed_resources = [] + + class MinimalModelForSampling: + model = object() + verbose = False + + def n_vocab(self): + return 8 + + class TrackedTokenDataArray: + def __init__(self, *, n_vocab): + assert n_vocab == 8 + + def close(self): + closed_resources.append("token-data") + + class TrackedSamplerChain: + def close(self): + closed_resources.append("sampler-chain") + + def get_sampling_vocab(_model_handle): + return object() + + def fail_sampler_chain_build(_sampling_context): + raise RuntimeError("sampler chain build failed") + + monkeypatch.setattr(internals, "LlamaTokenDataArray", TrackedTokenDataArray) + monkeypatch.setattr(internals, "LlamaSampler", TrackedSamplerChain) + monkeypatch.setattr( + internals.llama_cpp, + "llama_model_get_vocab", + get_sampling_vocab, + ) + monkeypatch.setattr( + internals.LlamaSamplingContext, + "_build_sampler_chain", + fail_sampler_chain_build, + ) + + sampling_context = internals.LlamaSamplingContext.__new__( + internals.LlamaSamplingContext + ) + with pytest.raises(RuntimeError, match="sampler chain build failed"): + sampling_context.__init__( + params=internals.LlamaSamplingParams(), + model=MinimalModelForSampling(), + ) + + sampling_context.close() + sampling_context.close() # Closing an already closed context must be a no-op. + + assert closed_resources == ["sampler-chain", "token-data"] + assert sampling_context.model is None + assert sampling_context.params is None + assert sampling_context.vocab is None + + def test_llama_cpp_version(): assert llama_cpp.__version__ From 866bed9565c1be3fd3c2985d71a62c05c9f1eea3 Mon Sep 17 00:00:00 2001 From: Emptyngton <40150265+emptyngton@users.noreply.github.com> Date: Sun, 26 Jul 2026 19:56:43 -0400 Subject: [PATCH 69/86] fix(loader): guard HIP_PATH and VULKAN_SDK dirs with os.path.exists os.add_dll_directory() raises FileNotFoundError [WinError 3] when the directory does not exist, so a stale HIP_PATH or VULKAN_SDK left behind by an uninstalled SDK makes "import llama_cpp" fail outright on Windows. The CUDA_PATH branch above already guards each candidate directory with os.path.exists(); this applies the same pattern to the HIP and Vulkan branches. Valid directories are still added individually, so a partially removed SDK contributes whichever of bin/lib remain instead of raising. --- llama_cpp/_ctypes_extensions.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/llama_cpp/_ctypes_extensions.py b/llama_cpp/_ctypes_extensions.py index a9a2c02e5..363472068 100644 --- a/llama_cpp/_ctypes_extensions.py +++ b/llama_cpp/_ctypes_extensions.py @@ -118,13 +118,19 @@ def load_shared_library(lib_base_name: str, base_paths: Union[pathlib.Path, list # Add HIP runtime DLL directories when HIP backend is available. if "HIP_PATH" in os.environ: - os.add_dll_directory(os.path.join(os.environ["HIP_PATH"], "bin")) - os.add_dll_directory(os.path.join(os.environ["HIP_PATH"], "lib")) + hip_path = os.environ["HIP_PATH"] + for sub_dir in ["bin", "lib"]: + full_path = os.path.join(hip_path, sub_dir) + if os.path.exists(full_path): + os.add_dll_directory(full_path) # Add Vulkan SDK DLL directories when Vulkan backend is enabled. if "VULKAN_SDK" in os.environ: - os.add_dll_directory(os.path.join(os.environ["VULKAN_SDK"], "Bin")) - os.add_dll_directory(os.path.join(os.environ["VULKAN_SDK"], "Lib")) + vulkan_sdk = os.environ["VULKAN_SDK"] + for sub_dir in ["Bin", "Lib"]: + full_path = os.path.join(vulkan_sdk, sub_dir) + if os.path.exists(full_path): + os.add_dll_directory(full_path) # Add package-provided library directories. # From 8e1ea5ef1b91a88bb26e9bc809b8aa645785c479 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Mon, 27 Jul 2026 22:43:32 +0800 Subject: [PATCH 70/86] Update Submodule vendor/llama.cpp 8bb9093..b77d646 Signed-off-by: JamePeng --- vendor/llama.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/llama.cpp b/vendor/llama.cpp index 8bb909374..b77d64675 160000 --- a/vendor/llama.cpp +++ b/vendor/llama.cpp @@ -1 +1 @@ -Subproject commit 8bb909374d04d40621340aee5ba2245860027fdc +Subproject commit b77d646751d01c0962bc203b6809e9d94f7d50b7 From 194dfb29e9dc504f942949328a52c7d4f372d445 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Tue, 28 Jul 2026 00:17:11 +0800 Subject: [PATCH 71/86] fix(ctypes): support GCC/Clang mangled symbols for optional llama_ext APIs - Add missing `_Z` Itanium C++ ABI symbol variants to ctypes function lookup lists. This improves compatibility with Linux and macOS builds where C++ symbols are exported using GCC/Clang name mangling. Signed-off-by: JamePeng --- llama_cpp/llama_cpp.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/llama_cpp/llama_cpp.py b/llama_cpp/llama_cpp.py index 2f402cd74..317a43548 100644 --- a/llama_cpp/llama_cpp.py +++ b/llama_cpp/llama_cpp.py @@ -5092,6 +5092,7 @@ def llama_opt_epoch( "llama_graph_reserve", "?llama_graph_reserve@@YAPEAUggml_cgraph@@PEAUllama_context@@III@Z", "__Z19llama_graph_reserveP13llama_contextjjj", + "_Z19llama_graph_reserveP13llama_contextjjj", ], [llama_context_p_ctypes, ctypes.c_uint32, ctypes.c_uint32, ctypes.c_uint32], ctypes.POINTER(ggml_cgraph), @@ -5115,6 +5116,7 @@ def llama_graph_reserve( "llama_ftype_get_default_type", "?llama_ftype_get_default_type@@YA?AW4ggml_type@@W4llama_ftype@@@Z", "__Z28llama_ftype_get_default_type11llama_ftype", + "_Z28llama_ftype_get_default_type11llama_ftype", ], [ctypes.c_int], int, @@ -5134,6 +5136,7 @@ def llama_ftype_get_default_type( "llama_model_n_expert", "?llama_model_n_expert@@YAHPEBUllama_model@@@Z", "__Z20llama_model_n_expertPK11llama_model", + "_Z20llama_model_n_expertPK11llama_model", ], [llama_model_p_ctypes], ctypes.c_int32, @@ -5150,6 +5153,7 @@ def llama_model_n_expert( "llama_model_n_devices", "?llama_model_n_devices@@YAHPEBUllama_model@@@Z", "__Z21llama_model_n_devicesPK11llama_model", + "_Z21llama_model_n_devicesPK11llama_model", ], [llama_model_p_ctypes], ctypes.c_int32, @@ -5166,6 +5170,7 @@ def llama_model_n_devices( "llama_model_get_device", "?llama_model_get_device@@YAPEAUggml_backend_device@@PEBUllama_model@@H@Z", "__Z22llama_model_get_devicePK11llama_modeli", + "_Z22llama_model_get_devicePK11llama_modeli", ], [llama_model_p_ctypes, ctypes.c_int], ctypes.c_void_p, @@ -5186,6 +5191,7 @@ def llama_model_get_device( "llama_set_embeddings_nextn", "?llama_set_embeddings_nextn@@YAXPEAUllama_context@@_N1@Z", "__Z26llama_set_embeddings_nextnP13llama_contextbb", + "_Z26llama_set_embeddings_nextnP13llama_contextbb", ], [llama_context_p_ctypes, ctypes.c_bool, ctypes.c_bool], None, @@ -5212,6 +5218,7 @@ def llama_set_embeddings_nextn( "llama_set_nextn_layer_offset", "?llama_set_nextn_layer_offset@@YAXPEAUllama_context@@H@Z", "__Z28llama_set_nextn_layer_offsetP13llama_contexti", + "_Z28llama_set_nextn_layer_offsetP13llama_contexti", ], [llama_context_p_ctypes, ctypes.c_int32], None, @@ -5236,6 +5243,7 @@ def llama_set_nextn_layer_offset( "llama_get_embeddings_nextn", "?llama_get_embeddings_nextn@@YAPEAMPEAUllama_context@@@Z", "__Z26llama_get_embeddings_nextnP13llama_context", + "_Z26llama_get_embeddings_nextnP13llama_context", ], [llama_context_p_ctypes], ctypes.POINTER(ctypes.c_float), @@ -5253,6 +5261,7 @@ def llama_get_embeddings_nextn( "llama_get_embeddings_nextn_ith", "?llama_get_embeddings_nextn_ith@@YAPEAMPEAUllama_context@@H@Z", "__Z30llama_get_embeddings_nextn_ithP13llama_contexti", + "_Z30llama_get_embeddings_nextn_ithP13llama_contexti", ], [llama_context_p_ctypes, ctypes.c_int32], ctypes.POINTER(ctypes.c_float), @@ -5271,6 +5280,7 @@ def llama_get_embeddings_nextn_ith( "llama_set_embeddings_layer_inp", "?llama_set_embeddings_layer_inp@@YAXPEAUllama_context@@I_N@Z", "__Z30llama_set_embeddings_layer_inpP13llama_contextjb", + "_Z30llama_set_embeddings_layer_inpP13llama_contextjb", ], [llama_context_p_ctypes, ctypes.c_int32, ctypes.c_bool], ctypes.POINTER(ctypes.c_float), @@ -5294,6 +5304,7 @@ def llama_set_embeddings_layer_inp( "llama_get_embeddings_layer_inp", "?llama_get_embeddings_layer_inp@@YAPEAMPEAUllama_context@@I@Z", "__Z30llama_get_embeddings_layer_inpP13llama_contextj", + "_Z30llama_get_embeddings_layer_inpP13llama_contextj", ], [llama_context_p_ctypes, ctypes.c_int32], ctypes.POINTER(ctypes.c_float), @@ -5311,6 +5322,7 @@ def llama_get_embeddings_layer_inp( "llama_get_ctx_other", "?llama_get_ctx_other@@YAPEAUllama_context@@PEAU1@@Z", "__Z19llama_get_ctx_otherP13llama_context", + "_Z19llama_get_ctx_otherP13llama_context", ], [llama_context_p_ctypes], llama_context_p_ctypes, @@ -5330,6 +5342,7 @@ def llama_get_ctx_other( "llama_model_target_layer_ids", "?llama_model_target_layer_ids@@YAPEBHPEBUllama_model@@@Z", "__Z28llama_model_target_layer_idsPK11llama_model", + "_Z28llama_model_target_layer_idsPK11llama_model", ], [llama_model_p_ctypes], ctypes.POINTER(ctypes.c_int32), @@ -5349,7 +5362,8 @@ def llama_model_target_layer_ids( [ "llama_model_target_layer_ids_n", "?llama_model_target_layer_ids_n@@YAIPEBUllama_model@@@Z", - "__Z30llama_model_target_layer_ids_nPK11llama_model" + "__Z30llama_model_target_layer_ids_nPK11llama_model", + "_Z30llama_model_target_layer_ids_nPK11llama_model", ], [llama_model_p_ctypes], ctypes.POINTER(ctypes.c_uint32), From 1e49f9da22b2125ed9d032ad88a9fd9afa8e4f2c Mon Sep 17 00:00:00 2001 From: JamePeng Date: Tue, 28 Jul 2026 04:04:19 +0800 Subject: [PATCH 72/86] feat(tools): add cross-platform ABI inspection utility Inspect PE, ELF, and Mach-O exports and normalize platform-specific symbol names. Validate optional llama_ext ctypes aliases across Windows, Linux, and macOS builds. Keep artifacts and timestamped privacy-safe reports local to the repository. Signed-off-by: JamePeng --- .gitignore | 11 +- tools/abi/README.md | 164 ++++ tools/abi/__init__.py | 3 + tools/abi/__main__.py | 5 + tools/abi/artifacts/.gitignore | 4 + tools/abi/artifacts/README.md | 20 + tools/abi/output/.gitignore | 4 + tools/abi/output/README.md | 7 + tools/abi/scan_dynamic.py | 863 +++++++++++++++++++++ tools/abi/tests/test_platform_artifacts.py | 85 ++ tools/abi/tests/test_scan_dynamic.py | 194 +++++ 11 files changed, 1359 insertions(+), 1 deletion(-) create mode 100644 tools/abi/README.md create mode 100644 tools/abi/__init__.py create mode 100644 tools/abi/__main__.py create mode 100644 tools/abi/artifacts/.gitignore create mode 100644 tools/abi/artifacts/README.md create mode 100644 tools/abi/output/.gitignore create mode 100644 tools/abi/output/README.md create mode 100644 tools/abi/scan_dynamic.py create mode 100644 tools/abi/tests/test_platform_artifacts.py create mode 100644 tools/abi/tests/test_scan_dynamic.py diff --git a/.gitignore b/.gitignore index fad7f4331..b5d60bf89 100644 --- a/.gitignore +++ b/.gitignore @@ -75,6 +75,15 @@ local_settings.py models/ docker/open_llama/*.bin +# Repository-only ABI tool inputs and generated reports. +# Keep only the directory instructions and local ignore rules tracked. +/tools/abi/artifacts/* +!/tools/abi/artifacts/.gitignore +!/tools/abi/artifacts/README.md +/tools/abi/output/* +!/tools/abi/output/.gitignore +!/tools/abi/output/README.md + # C extensions (llama_cpp bindings) llama_cpp/*.so llama_cpp/*.dylib @@ -208,4 +217,4 @@ docs/_build/ # Installer logs pip-log.txt -pip-delete-this-directory.txt \ No newline at end of file +pip-delete-this-directory.txt diff --git a/tools/abi/README.md b/tools/abi/README.md new file mode 100644 index 000000000..ea509f2f7 --- /dev/null +++ b/tools/abi/README.md @@ -0,0 +1,164 @@ +# Cross-platform ABI inspection + +Author: **JamePeng** + +This repository-only tool inspects PE (`.dll`), ELF (`.so` and `.so.*`), and +Mach-O (`.dylib`) exports. Its primary purpose is to collect ctypes symbol +candidates and verify optional `llama_ext` bindings across MSVC, GCC/Clang, +and macOS builds. + +## Boundary and safety + +The tool is intentionally excluded from wheels: + +```toml +wheel.packages = ["llama_cpp"] +``` + +It is not imported by `llama_cpp`, has no installed command, and keeps LIEF +out of project dependencies. Run it only from a trusted source checkout. +LIEF parses native binaries, so do not scan untrusted artifacts. + +Install the maintainer-only dependency: + +```bash +python -m pip install lief +``` + +The tool and its documentation use the same MIT License as this repository. + +## Artifact layout + +Run commands from the repository root. Put builds under +`tools/abi/artifacts`, or replace that argument with an external absolute +directory: + +```text +tools/abi/artifacts/ +├── windows-x86_64/ +│ └── +├── linux-x86_64/ +│ └── +└── macos-arm64/ + └── +``` + +Names are not significant. `--select-symbol llama_decode` identifies the +llama library by content when dependency and backend libraries share the same +directory. + +Artifacts may come from local builds, an installed or extracted wheel, +[project releases](https://github.com/JamePeng/llama-cpp-python/releases), or +[upstream releases](https://github.com/ggml-org/llama.cpp/releases). Record +the source revision, compiler, architecture, and build options. Upstream +artifacts may not contain fork-only `llama_ext` APIs. + +## Scan exports + +```bash +python -m tools.abi scan tools/abi/artifacts --recursive +``` + +The default output is one same-named JSONL file per library: + +```text +tools/abi/output/ +└── 20260728T153012.123456Z/ + ├── llama.dll.jsonl + ├── libllama.so.jsonl + └── libllama.dylib.jsonl +``` + +Useful options: + +```bash +# Select only binaries that export llama_decode. +python -m tools.abi scan tools/abi/artifacts --recursive \ + --select-symbol llama_decode + +# Print instead of writing per-library JSONL. +python -m tools.abi scan tools/abi/artifacts --recursive --format text + +# Write one aggregate file; its filename receives a UTC timestamp. +python -m tools.abi scan tools/abi/artifacts --recursive \ + --format jsonl --output all-symbols.jsonl +``` + +`--prefix` is optional. By default all exports are retained: + +```bash +python -m tools.abi scan tools/abi/artifacts --recursive \ + --prefix llama_ --prefix ggml_ +``` + +## Check optional llama_ext bindings + +This is the primary ABI validation command: + +```bash +python -m tools.abi check-bindings tools/abi/artifacts \ + --recursive \ + --source llama_cpp/llama_cpp.py +``` + +It statically reads ctypes decorators without importing `llama_cpp`. The +default `--scope optional` checks declarations marked `required=False` and +returns exit code 1 if any candidate is missing. Other scopes are available: + +```bash +python -m tools.abi check-bindings tools/abi/artifacts \ + --recursive --scope required +python -m tools.abi check-bindings tools/abi/artifacts \ + --recursive --scope all +``` + +## Compare and create a manifest + +```bash +python -m tools.abi compare tools/abi/artifacts \ + --recursive --select-symbol llama_decode + +python -m tools.abi manifest tools/abi/artifacts \ + --recursive --select-symbol llama_decode \ + --output llama-exports.json +``` + +Cross-platform comparison uses `canonical_name`: + +```text +?llama_graph_reserve@@... MSVC +_Z19llama_graph_reserve... Linux Itanium ABI +__Z19llama_graph_reserve... Mach-O symbol table + ↓ +llama_graph_reserve canonical name +``` + +Records retain `raw_name`, ctypes `lookup_name`, `canonical_name`, ABI, +address, ordinal, library filename, format, architecture, SHA-256, and UTC +generation time. They never contain the artifact's absolute source path. + +Every run receives a timestamp, preventing normal output from overwriting +previous results. Generated artifacts and reports are ignored by Git. + +## Verification + +Unit tests are independent from the project's default test suite: + +```bash +python -m pytest tools/abi/tests/test_scan_dynamic.py -q +``` + +The opt-in integration test requires Windows, Linux, and macOS artifacts: + +```powershell +$env:LLAMA_ABI_ARTIFACTS = "tools/abi/artifacts" +python -m pytest tools/abi/tests/test_platform_artifacts.py -q +``` + +```bash +LLAMA_ABI_ARTIFACTS=tools/abi/artifacts \ +python -m pytest tools/abi/tests/test_platform_artifacts.py -q +``` + +Without configured artifacts, integration tests skip. With +`LLAMA_ABI_ARTIFACTS` set, a missing platform or optional ABI alias fails. diff --git a/tools/abi/__init__.py b/tools/abi/__init__.py new file mode 100644 index 000000000..0a387e675 --- /dev/null +++ b/tools/abi/__init__.py @@ -0,0 +1,3 @@ +"""Cross-platform shared-library ABI inspection tools.""" + +__author__ = "JamePeng" diff --git a/tools/abi/__main__.py b/tools/abi/__main__.py new file mode 100644 index 000000000..acfe21acf --- /dev/null +++ b/tools/abi/__main__.py @@ -0,0 +1,5 @@ +from .scan_dynamic import main + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/abi/artifacts/.gitignore b/tools/abi/artifacts/.gitignore new file mode 100644 index 000000000..dbea0aa37 --- /dev/null +++ b/tools/abi/artifacts/.gitignore @@ -0,0 +1,4 @@ +# Keep downloaded or locally built native libraries out of Git. +* +!.gitignore +!README.md diff --git a/tools/abi/artifacts/README.md b/tools/abi/artifacts/README.md new file mode 100644 index 000000000..30863ea8c --- /dev/null +++ b/tools/abi/artifacts/README.md @@ -0,0 +1,20 @@ +# ABI artifacts + +Maintainer: **JamePeng** + +Place trusted Windows, Linux, and macOS build artifacts here for local ABI +inspection. Filenames do not need to follow a fixed convention. + +```text +tools/abi/artifacts/ +├── windows-x86_64/ +├── linux-x86_64/ +├── macos-arm64/ +└── macos-x86_64/ +``` + +Downloaded and copied content is ignored by both the local and repository +`.gitignore`; only this README and `.gitignore` are tracked. `git add -f` can +still deliberately override ignore rules. + +See `tools/abi/README.md` for commands and artifact provenance requirements. diff --git a/tools/abi/output/.gitignore b/tools/abi/output/.gitignore new file mode 100644 index 000000000..f12e9f061 --- /dev/null +++ b/tools/abi/output/.gitignore @@ -0,0 +1,4 @@ +# Keep generated ABI reports local. +* +!.gitignore +!README.md diff --git a/tools/abi/output/README.md b/tools/abi/output/README.md new file mode 100644 index 000000000..3601ac132 --- /dev/null +++ b/tools/abi/output/README.md @@ -0,0 +1,7 @@ +# Local ABI reports + +Each run is stored in a UTC timestamp directory. Reports omit artifact source +paths but may contain binary hashes and non-public symbols. + +Generated content is ignored by both the local and repository `.gitignore`; +only this README and `.gitignore` are tracked. Review reports before sharing. diff --git a/tools/abi/scan_dynamic.py b/tools/abi/scan_dynamic.py new file mode 100644 index 000000000..8ee11fd8c --- /dev/null +++ b/tools/abi/scan_dynamic.py @@ -0,0 +1,863 @@ +"""Inspect and compare exported symbols in PE, ELF, and Mach-O libraries. + +This repository-only maintainer utility supports collection and verification +of cross-platform ctypes symbol candidates, with particular focus on optional +llama_ext APIs. + +LIEF is imported lazily so that ``--help`` remains available when the optional +dependency is not installed. +""" + +from __future__ import annotations + +import argparse +import ast +import hashlib +import json +import re +import sys +from collections import defaultdict +from dataclasses import asdict, dataclass, replace +from datetime import datetime, timezone +from pathlib import Path +from typing import Any, Iterable, Sequence + +LIBRARY_SUFFIXES = {".dll", ".dylib", ".so"} +__author__ = "JamePeng" + + +class ScanError(RuntimeError): + """Raised when a shared library cannot be inspected.""" + + +@dataclass(frozen=True) +class SymbolRecord: + """One exported symbol and its cross-platform names.""" + + raw_name: str + lookup_name: str + canonical_name: str + abi: str + address: str + ordinal: int | None = None + + +@dataclass(frozen=True) +class BindingDeclaration: + """One ctypes decorator declaration extracted without importing llama_cpp.""" + + python_name: str + candidates: tuple[str, ...] + required: bool + line: int + + +@dataclass(frozen=True) +class LibraryScan: + """Metadata and exported symbols for one binary architecture.""" + + library: str + format: str + platform: str + architecture: str + sha256: str + symbols: tuple[SymbolRecord, ...] + + +def sha256_file(path: Path, chunk_size: int = 1024 * 1024) -> str: + digest = hashlib.sha256() + with path.open("rb") as stream: + for chunk in iter(lambda: stream.read(chunk_size), b""): + digest.update(chunk) + return digest.hexdigest() + + +def generation_timestamp() -> str: + """Return a sortable, collision-resistant UTC generation timestamp.""" + + return datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%S.%fZ") + + +def _enum_name(value: Any) -> str: + text = str(value) + return text.rsplit(".", 1)[-1] + + +def get_format(binary: Any) -> str: + value = str(binary.format).upper() + if "MACHO" in value: + return "Mach-O" + if "ELF" in value: + return "ELF" + if "PE" in value: + return "PE" + return str(binary.format) + + +def get_platform(binary_format: str) -> str: + return { + "PE": "windows", + "ELF": "linux", + "Mach-O": "darwin", + }.get(binary_format, "unknown") + + +def get_architecture(binary: Any, binary_format: str) -> str: + header = binary.header + if binary_format == "PE": + return _enum_name(header.machine) + if binary_format == "ELF": + return _enum_name(header.machine_type) + if binary_format == "Mach-O": + return _enum_name(header.cpu_type) + return "unknown" + + +def normalize_symbol_name(raw_name: str, binary_format: str) -> str: + """Return the name used by ctypes/dlsym and cross-platform comparison. + + Mach-O symbol tables prefix external C names with an underscore. dlsym and + ctypes callers use the source-level name without that platform prefix. + """ + + if binary_format == "Mach-O" and raw_name.startswith("_"): + return raw_name[1:] + return raw_name + + +def detect_abi(normalized_name: str) -> str: + if normalized_name.startswith("?"): + return "msvc-cxxabi" + if normalized_name.startswith("_Z"): + return "itanium-cxxabi" + return "unmangled" + + +def canonicalize_symbol_name(normalized_name: str, abi: str) -> str: + """Recover a source-level name from simple global C++ mangling. + + llama_ext functions are global functions, so their MSVC and Itanium + spellings can be mapped without a full ABI demangler. Namespaced, + overloaded, and templated symbols remain mangled to avoid false matches. + """ + + if abi == "msvc-cxxabi": + match = re.match(r"^\?([^@?$]+)@@", normalized_name) + if match: + return match.group(1) + + if abi == "itanium-cxxabi": + match = re.match(r"^_Z(\d+)", normalized_name) + if match: + length = int(match.group(1)) + start = match.end() + candidate = normalized_name[start : start + length] + if len(candidate) == length: + return candidate + + return normalized_name + + +def _symbol_address(symbol: Any) -> str: + value = getattr(symbol, "address", None) + if value is None: + value = getattr(symbol, "value", 0) + return hex(int(value)) + + +def _exported_symbols(binary: Any, binary_format: str) -> Iterable[Any]: + if binary_format == "PE": + if not binary.has_exports: + return () + return binary.get_export().entries + + # LIEF's exported_symbols filters undefined ELF imports and non-exported + # Mach-O symbols, unlike dynamic_symbols/symbols. + return binary.exported_symbols + + +def _iter_binaries(parsed: Any) -> list[Any]: + # A universal Mach-O may contain several architecture slices. + if type(parsed).__name__ == "FatBinary": + return list(parsed) + return [parsed] + + +def scan_library( + path: str | Path, +) -> list[LibraryScan]: + """Inspect one library, returning one result per architecture slice.""" + + try: + import lief + except ImportError as exc: + raise ScanError( + "LIEF is required for ABI inspection. Install it with: pip install lief" + ) from exc + + library_path = Path(path).expanduser().resolve() + if not library_path.is_file(): + raise ScanError(f"Not a file: {library_path.name}") + + try: + parsed = lief.parse(str(library_path)) + except Exception as exc: + detail = str(exc).replace(str(library_path), library_path.name) + raise ScanError(f"Failed to parse {library_path.name}: {detail}") from exc + + if parsed is None: + raise ScanError(f"LIEF did not recognize {library_path.name}") + + digest = sha256_file(library_path) + results: list[LibraryScan] = [] + + for binary in _iter_binaries(parsed): + binary_format = get_format(binary) + records: list[SymbolRecord] = [] + + for symbol in _exported_symbols(binary, binary_format): + raw_name = getattr(symbol, "name", None) + if not raw_name: + # PE supports ordinal-only exports. They cannot be matched to + # Python bindings by name, so keep a stable synthetic label. + ordinal = getattr(symbol, "ordinal", None) + if ordinal is None: + continue + raw_name = f"#{ordinal}" + + lookup_name = normalize_symbol_name(raw_name, binary_format) + abi = detect_abi(lookup_name) + canonical_name = canonicalize_symbol_name(lookup_name, abi) + + records.append( + SymbolRecord( + raw_name=raw_name, + lookup_name=lookup_name, + canonical_name=canonical_name, + abi=abi, + address=_symbol_address(symbol), + ordinal=getattr(symbol, "ordinal", None), + ) + ) + + records.sort(key=lambda item: (item.canonical_name, item.raw_name)) + results.append( + LibraryScan( + library=library_path.name, + format=binary_format, + platform=get_platform(binary_format), + architecture=get_architecture(binary, binary_format), + sha256=digest, + symbols=tuple(records), + ) + ) + + return results + + +def select_scans_by_symbols( + scans: Sequence[LibraryScan], + required_symbols: Sequence[str], +) -> list[LibraryScan]: + """Select binaries by exported canonical names, independent of filenames.""" + + if not required_symbols: + return list(scans) + selected = [] + for scan in scans: + exported = {symbol.canonical_name for symbol in scan.symbols} + if all(name in exported for name in required_symbols): + selected.append(scan) + return selected + + +def filter_scan_symbols( + scans: Sequence[LibraryScan], + prefixes: Sequence[str], +) -> list[LibraryScan]: + if not prefixes: + return list(scans) + return [ + replace( + scan, + symbols=tuple( + symbol + for symbol in scan.symbols + if any(symbol.canonical_name.startswith(prefix) for prefix in prefixes) + ), + ) + for scan in scans + ] + + +def extract_ctypes_bindings(source: str | Path) -> list[BindingDeclaration]: + """Extract literal ctypes decorator candidates without importing the module.""" + + source_path = Path(source) + try: + tree = ast.parse( + source_path.read_text(encoding="utf-8"), + filename=str(source_path), + ) + except (OSError, SyntaxError) as exc: + raise ScanError(f"Failed to parse binding source {source_path}: {exc}") from exc + + declarations: list[BindingDeclaration] = [] + for node in ast.walk(tree): + if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): + continue + for decorator in node.decorator_list: + if not isinstance(decorator, ast.Call) or not decorator.args: + continue + decorator_name = "" + if isinstance(decorator.func, ast.Name): + decorator_name = decorator.func.id + elif isinstance(decorator.func, ast.Attribute): + decorator_name = decorator.func.attr + if not decorator_name.startswith("ctypes_function"): + continue + + try: + names = ast.literal_eval(decorator.args[0]) + except (ValueError, TypeError): + continue + if isinstance(names, str): + candidates = (names,) + elif isinstance(names, (list, tuple)) and all( + isinstance(name, str) for name in names + ): + candidates = tuple(names) + else: + continue + + required = True + for keyword in decorator.keywords: + if keyword.arg == "required": + try: + required = bool(ast.literal_eval(keyword.value)) + except (ValueError, TypeError): + pass + + declarations.append( + BindingDeclaration( + python_name=node.name, + candidates=candidates, + required=required, + line=node.lineno, + ) + ) + + return sorted(declarations, key=lambda item: item.line) + + +def check_bindings( + scan: LibraryScan, + declarations: Sequence[BindingDeclaration], +) -> dict[str, Any]: + """Check which ctypes candidate would be selected for one library.""" + + exported = {symbol.lookup_name for symbol in scan.symbols} + available = [] + missing_required = [] + missing_optional = [] + + for declaration in declarations: + selected = next( + (name for name in declaration.candidates if name in exported), + None, + ) + item = { + "python_name": declaration.python_name, + "required": declaration.required, + "line": declaration.line, + "candidates": list(declaration.candidates), + "selected": selected, + } + if selected is not None: + available.append(item) + elif declaration.required: + missing_required.append(item) + else: + missing_optional.append(item) + + return { + "library": scan.library, + "platform": scan.platform, + "architecture": scan.architecture, + "declaration_count": len(declarations), + "available_count": len(available), + "available": available, + "missing_required": missing_required, + "missing_optional": missing_optional, + } + + +def compare_scans(scans: Sequence[LibraryScan]) -> dict[str, Any]: + if len(scans) < 2: + raise ValueError("At least two library scans are required for comparison") + + symbol_sets = [{symbol.canonical_name for symbol in scan.symbols} for scan in scans] + common = set.intersection(*symbol_sets) + libraries = [] + + for index, scan in enumerate(scans): + others = set.union(*(symbol_sets[i] for i in range(len(scans)) if i != index)) + libraries.append( + { + "library": scan.library, + "platform": scan.platform, + "architecture": scan.architecture, + "symbol_count": len(symbol_sets[index]), + "only_here": sorted(symbol_sets[index] - others), + "missing_here": sorted(others - symbol_sets[index]), + } + ) + + return { + "common_count": len(common), + "common": sorted(common), + "libraries": libraries, + } + + +def build_manifest( + scans: Sequence[LibraryScan], + *, + generated_at: str | None = None, +) -> dict[str, Any]: + generated_at = generated_at or generation_timestamp() + symbols: dict[str, list[dict[str, Any]]] = defaultdict(list) + libraries = [] + + for scan in scans: + libraries.append(_scan_metadata(scan)) + for symbol in scan.symbols: + symbols[symbol.canonical_name].append( + { + "library": scan.library, + "platform": scan.platform, + "architecture": scan.architecture, + "raw_name": symbol.raw_name, + "lookup_name": symbol.lookup_name, + "abi": symbol.abi, + "address": symbol.address, + "ordinal": symbol.ordinal, + } + ) + + return { + "schema_version": 1, + "generated_at": generated_at, + "libraries": libraries, + "symbols": dict(sorted(symbols.items())), + } + + +def collect_library_paths( + inputs: Sequence[str], + *, + recursive: bool = False, +) -> list[Path]: + def is_shared_library(path: Path) -> bool: + name = path.name.lower() + return path.suffix.lower() in LIBRARY_SUFFIXES or ".so." in name + + paths: list[Path] = [] + for value in inputs: + path = Path(value).expanduser() + if path.is_dir(): + candidates = path.rglob("*") if recursive else path.iterdir() + paths.extend( + candidate + for candidate in candidates + if candidate.is_file() and is_shared_library(candidate) + ) + else: + paths.append(path) + return sorted(set(paths), key=lambda item: str(item).lower()) + + +def _scan_paths( + paths: Sequence[Path], +) -> tuple[list[LibraryScan], list[str]]: + scans: list[LibraryScan] = [] + errors: list[str] = [] + for path in paths: + try: + scans.extend(scan_library(path)) + except ScanError as exc: + errors.append(str(exc)) + except Exception as exc: + detail = str(exc).replace(str(path.resolve()), path.name) + errors.append(f"{path.name}: {detail}") + return scans, errors + + +def _timestamped_output_path(output: str | Path, timestamp: str) -> Path: + path = Path(output) + return path.with_name(f"{path.stem}.{timestamp}{path.suffix}") + + +def _write_output( + text: str, + output: str | None, + *, + timestamp: str, +) -> None: + if output: + output_path = _timestamped_output_path(output, timestamp) + output_path.parent.mkdir(parents=True, exist_ok=True) + output_path.write_text(text + "\n", encoding="utf-8") + print(f"saved: {output_path}") + else: + print(text) + + +def _scan_metadata(scan: LibraryScan) -> dict[str, Any]: + return {key: value for key, value in asdict(scan).items() if key != "symbols"} + + +def _jsonl_rows(scan: LibraryScan, generated_at: str) -> list[str]: + metadata = _scan_metadata(scan) + return [ + json.dumps( + { + "generated_at": generated_at, + **metadata, + **asdict(symbol), + }, + ensure_ascii=False, + ) + for symbol in scan.symbols + ] + + +def write_library_jsonl( + scans: Sequence[LibraryScan], + output_dir: str | Path = "tools/abi/output", + *, + timestamp: str | None = None, +) -> list[Path]: + """Write one same-named JSONL per library under a timestamped run directory.""" + + timestamp = timestamp or generation_timestamp() + destination = Path(output_dir) / timestamp + destination.mkdir(parents=True, exist_ok=True) + grouped: dict[str, list[LibraryScan]] = defaultdict(list) + for scan in scans: + grouped[scan.library].append(scan) + + written = [] + for library, library_scans in sorted(grouped.items()): + output_path = destination / f"{library}.jsonl" + rows = [ + row + for library_scan in library_scans + for row in _jsonl_rows(library_scan, timestamp) + ] + output_path.write_text( + "\n".join(rows) + ("\n" if rows else ""), + encoding="utf-8", + ) + written.append(output_path) + return written + + +def _scan_text(scans: Sequence[LibraryScan], errors: Sequence[str]) -> str: + lines: list[str] = [] + for scan in scans: + lines.append( + f"{scan.library} [{scan.format}/{scan.architecture}]: " + f"{len(scan.symbols)} exported symbol(s)" + ) + for symbol in scan.symbols: + raw_suffix = ( + f" (raw: {symbol.raw_name})" + if symbol.raw_name != symbol.canonical_name + else "" + ) + lines.append( + f" {symbol.canonical_name} [{symbol.abi}]" + f" @ {symbol.address}{raw_suffix}" + ) + for error in errors: + lines.append(f"ERROR: {error}") + return "\n".join(lines) + + +def _compare_text(comparison: dict[str, Any]) -> str: + lines = [f"Common canonical symbols: {comparison['common_count']}"] + for library in comparison["libraries"]: + lines.extend( + [ + "", + ( + f"{library['library']} " + f"[{library['platform']}/{library['architecture']}]: " + f"{library['symbol_count']} symbol(s)" + ), + f" Only here: {len(library['only_here'])}", + ] + ) + lines.extend(f" {name}" for name in library["only_here"]) + lines.append(f" Missing here: {len(library['missing_here'])}") + lines.extend(f" {name}" for name in library["missing_here"]) + return "\n".join(lines) + + +def _bindings_text( + results: Sequence[dict[str, Any]], + scope: str, +) -> str: + lines: list[str] = [] + for result in results: + lines.append( + f"{result['library']} " + f"[{result['platform']}/{result['architecture']}]: " + f"{result['available_count']}/{result['declaration_count']} " + "binding(s) available in selected scope" + ) + if scope in {"required", "all"}: + lines.append(f" Missing required: {len(result['missing_required'])}") + lines.extend( + f" {item['python_name']} (line {item['line']})" + for item in result["missing_required"] + ) + if scope in {"optional", "all"}: + lines.append(f" Missing optional: {len(result['missing_optional'])}") + lines.extend( + f" {item['python_name']} (line {item['line']})" + for item in result["missing_optional"] + ) + return "\n".join(lines) + + +def create_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description="Inspect and compare PE, ELF, and Mach-O exported symbols." + ) + subparsers = parser.add_subparsers(dest="command", required=True) + + def add_common(subparser: argparse.ArgumentParser) -> None: + subparser.add_argument("paths", nargs="+", help="Library files or directories") + subparser.add_argument( + "--prefix", + action="append", + default=[], + help=( + "Optional canonical-name filter; may be repeated " + "(default: keep all exports)" + ), + ) + subparser.add_argument( + "--select-symbol", + action="append", + default=[], + help=( + "Select libraries exporting this canonical symbol; may be " + "repeated and does not depend on the library filename" + ), + ) + subparser.add_argument( + "--recursive", + action="store_true", + help="Recursively search directory inputs", + ) + subparser.add_argument("-o", "--output", help="Write output to this file") + + scan_parser = subparsers.add_parser("scan", help="List exported symbols") + add_common(scan_parser) + scan_parser.add_argument( + "--format", + choices=("text", "json", "jsonl"), + default="jsonl", + help="Output format", + ) + scan_parser.add_argument( + "--output-dir", + default="tools/abi/output", + help=( + "Directory for default per-library JSONL files " + "(default: tools/abi/output)" + ), + ) + + compare_parser = subparsers.add_parser( + "compare", help="Compare canonical symbol names across libraries" + ) + add_common(compare_parser) + compare_parser.add_argument( + "--format", + choices=("text", "json"), + default="text", + help="Output format", + ) + + manifest_parser = subparsers.add_parser( + "manifest", help="Create a cross-platform symbol manifest" + ) + add_common(manifest_parser) + + bindings_parser = subparsers.add_parser( + "check-bindings", + help="Check literal ctypes decorator candidates against libraries", + ) + add_common(bindings_parser) + bindings_parser.add_argument( + "--source", + default="llama_cpp/llama_cpp.py", + help="Python binding source to inspect without importing it", + ) + bindings_parser.add_argument( + "--format", + choices=("text", "json"), + default="text", + help="Output format", + ) + bindings_parser.add_argument( + "--scope", + choices=("optional", "required", "all"), + default="optional", + help=("Binding declarations to check " "(default: optional llama_ext APIs)"), + ) + + return parser + + +def main(argv: Sequence[str] | None = None) -> int: + args = create_parser().parse_args(argv) + paths = collect_library_paths(args.paths, recursive=args.recursive) + if not paths: + print("No shared libraries found.", file=sys.stderr) + return 2 + + # Scan all exports first. Selection must not depend on --prefix, because a + # caller may use an anchor outside the displayed prefix set. + scans, errors = _scan_paths(paths) + if not scans: + for error in errors: + print(f"ERROR: {error}", file=sys.stderr) + return 1 + + if args.select_symbol: + scans = select_scans_by_symbols(scans, args.select_symbol) + if not scans: + print( + "No library exports all requested selection symbols: " + + ", ".join(args.select_symbol), + file=sys.stderr, + ) + return 2 + + scans = filter_scan_symbols(scans, args.prefix) + if args.command in {"compare", "manifest"} and args.prefix: + # A package lib directory normally contains ggml and accelerator + # backends. Empty prefix matches are not comparison targets. + scans = [scan for scan in scans if scan.symbols] + timestamp = generation_timestamp() + + validation_failed = False + + if args.command == "scan": + if args.format == "text": + output = _scan_text(scans, errors) + elif args.format == "json": + output = json.dumps( + { + "generated_at": timestamp, + "libraries": [asdict(scan) for scan in scans], + "errors": errors, + }, + ensure_ascii=False, + indent=2, + ) + else: + output = "\n".join( + row for scan in scans for row in _jsonl_rows(scan, timestamp) + ) + + if args.format == "jsonl" and args.output is None: + try: + written = write_library_jsonl( + scans, + args.output_dir, + timestamp=timestamp, + ) + except OSError as exc: + print(f"ERROR: failed to write JSONL output: {exc}", file=sys.stderr) + return 1 + for path in written: + print(f"saved: {path}") + for error in errors: + print(f"ERROR: {error}", file=sys.stderr) + return 1 if errors else 0 + elif args.command == "compare": + if len(scans) < 2: + print("Comparison requires at least two libraries.", file=sys.stderr) + return 2 + comparison = compare_scans(scans) + output = ( + _compare_text(comparison) + if args.format == "text" + else json.dumps(comparison, ensure_ascii=False, indent=2) + ) + elif args.command == "manifest": + output = json.dumps( + build_manifest(scans, generated_at=timestamp), + ensure_ascii=False, + indent=2, + ) + else: + try: + declarations = extract_ctypes_bindings(args.source) + except ScanError as exc: + print(f"ERROR: {exc}", file=sys.stderr) + return 2 + if args.scope == "optional": + declarations = [ + declaration for declaration in declarations if not declaration.required + ] + elif args.scope == "required": + declarations = [ + declaration for declaration in declarations if declaration.required + ] + if not declarations: + print( + f"No {args.scope} ctypes binding declarations found in " + f"{args.source}.", + file=sys.stderr, + ) + return 2 + binding_results = [check_bindings(scan, declarations) for scan in scans] + if not args.select_symbol and binding_results: + # A package directory may contain arbitrarily named dependency and + # backend libraries. The library ctypes would want is the one with + # the greatest declaration coverage, regardless of filename. + best_count = max(result["available_count"] for result in binding_results) + binding_results = [ + result + for result in binding_results + if result["available_count"] == best_count + ] + output = ( + _bindings_text(binding_results, args.scope) + if args.format == "text" + else json.dumps(binding_results, ensure_ascii=False, indent=2) + ) + validation_failed = any( + result["missing_required"] or result["missing_optional"] + for result in binding_results + ) + + try: + _write_output(output, args.output, timestamp=timestamp) + except OSError as exc: + print(f"ERROR: failed to write output: {exc}", file=sys.stderr) + return 1 + for error in errors: + print(f"ERROR: {error}", file=sys.stderr) + return 1 if errors or validation_failed else 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/abi/tests/test_platform_artifacts.py b/tools/abi/tests/test_platform_artifacts.py new file mode 100644 index 000000000..b5f88dcc3 --- /dev/null +++ b/tools/abi/tests/test_platform_artifacts.py @@ -0,0 +1,85 @@ +"""Opt-in integration tests for real Windows, Linux, and macOS artifacts.""" + +from __future__ import annotations + +import os +from pathlib import Path + +import pytest + +from tools.abi.scan_dynamic import ( + check_bindings, + collect_library_paths, + extract_ctypes_bindings, + scan_library, + select_scans_by_symbols, +) + +ARTIFACTS_ENV = "LLAMA_ABI_ARTIFACTS" +DEFAULT_ARTIFACTS = Path("tools/abi/artifacts") +REQUIRED_PLATFORMS = {"windows", "linux", "darwin"} +STABLE_LLAMA_SYMBOLS = { + "llama_decode", + "llama_model_load_from_file", +} +BINDING_SOURCE = Path("llama_cpp/llama_cpp.py") + + +@pytest.fixture(scope="module") +def platform_scans(): + configured = os.environ.get(ARTIFACTS_ENV) + artifacts = Path(configured) if configured else DEFAULT_ARTIFACTS + paths = collect_library_paths([str(artifacts)], recursive=True) + + if not paths and not configured: + pytest.skip( + "No ABI artifacts installed. Set LLAMA_ABI_ARTIFACTS to run " + "the Windows/Linux/macOS integration test." + ) + + assert paths, f"No shared libraries found under {artifacts}" + scans = [scan for path in paths for scan in scan_library(path)] + scans = select_scans_by_symbols(scans, ["llama_decode"]) + by_platform = {scan.platform: scan for scan in scans} + assert REQUIRED_PLATFORMS <= set(by_platform), ( + "The ABI artifact set must contain llama libraries for Windows, " + f"Linux, and macOS. Found: {sorted(by_platform)}" + ) + return by_platform + + +def test_windows_linux_and_macos_llama_exports(platform_scans): + common = set.intersection( + *( + {symbol.canonical_name for symbol in platform_scans[platform].symbols} + for platform in sorted(REQUIRED_PLATFORMS) + ) + ) + assert STABLE_LLAMA_SYMBOLS <= common + + +def test_macos_macho_lookup_name_removes_symbol_table_prefix(platform_scans): + decode = next( + symbol + for symbol in platform_scans["darwin"].symbols + if symbol.canonical_name == "llama_decode" + ) + assert decode.raw_name == "_llama_decode" + assert decode.lookup_name == "llama_decode" + + +def test_optional_llama_ext_abi_aliases_on_all_platforms(platform_scans): + optional = [ + declaration + for declaration in extract_ctypes_bindings(BINDING_SOURCE) + if not declaration.required + ] + assert optional, "No optional llama_ext ctypes bindings were found" + + for platform in sorted(REQUIRED_PLATFORMS): + result = check_bindings(platform_scans[platform], optional) + assert ( + result["missing_optional"] == [] + ), f"{platform} is missing optional llama_ext ABI aliases: " + ", ".join( + item["python_name"] for item in result["missing_optional"] + ) diff --git a/tools/abi/tests/test_scan_dynamic.py b/tools/abi/tests/test_scan_dynamic.py new file mode 100644 index 000000000..ebcf75fd4 --- /dev/null +++ b/tools/abi/tests/test_scan_dynamic.py @@ -0,0 +1,194 @@ +import json + +import tools.abi.scan_dynamic as abi_tool + +from tools.abi.scan_dynamic import ( + BindingDeclaration, + SymbolRecord, + LibraryScan, + canonicalize_symbol_name, + check_bindings, + collect_library_paths, + compare_scans, + detect_abi, + extract_ctypes_bindings, + normalize_symbol_name, + select_scans_by_symbols, + write_library_jsonl, +) + + +def _scan(library: str, platform: str, names: list[str]) -> LibraryScan: + records = tuple( + SymbolRecord( + raw_name=name, + lookup_name=name, + canonical_name=name, + abi="unmangled", + address="0x0", + ) + for name in names + ) + return LibraryScan( + library=library, + format="test", + platform=platform, + architecture="test", + sha256="test", + symbols=records, + ) + + +def test_normalizes_macho_external_prefix(): + assert normalize_symbol_name("_llama_decode", "Mach-O") == "llama_decode" + assert normalize_symbol_name("__ZN5llama", "Mach-O") == "_ZN5llama" + assert normalize_symbol_name("llama_decode", "ELF") == "llama_decode" + assert normalize_symbol_name("llama_decode", "PE") == "llama_decode" + + +def test_detects_abi_after_platform_normalization(): + assert detect_abi("?function@@YAXXZ") == "msvc-cxxabi" + assert detect_abi("_ZN5llama") == "itanium-cxxabi" + assert detect_abi("llama_decode") == "unmangled" + + +def test_canonicalizes_simple_global_cpp_names(): + assert ( + canonicalize_symbol_name( + "?llama_graph_reserve@@YAXXZ", + "msvc-cxxabi", + ) + == "llama_graph_reserve" + ) + assert ( + canonicalize_symbol_name( + "_Z19llama_graph_reserveP13llama_contextjjj", + "itanium-cxxabi", + ) + == "llama_graph_reserve" + ) + nested = "_ZN5llama6detail3fooEv" + assert canonicalize_symbol_name(nested, "itanium-cxxabi") == nested + + +def test_compares_canonical_names(): + comparison = compare_scans( + [ + _scan("libllama.so", "linux", ["llama_decode"]), + _scan( + "llama.dll", + "windows", + ["llama_decode", "llama_windows_only"], + ), + ] + ) + + assert comparison["common"] == ["llama_decode"] + assert comparison["libraries"][0]["missing_here"] == ["llama_windows_only"] + assert comparison["libraries"][1]["only_here"] == ["llama_windows_only"] + + +def test_collects_versioned_elf_library(tmp_path): + library = tmp_path / "libllama.so.1" + library.touch() + + assert collect_library_paths([str(tmp_path)]) == [library] + + +def test_extracts_and_checks_literal_binding_aliases(tmp_path): + source = tmp_path / "bindings.py" + source.write_text( + """ +@ctypes_function( + ["llama_ext", "?llama_ext@@YAXXZ", "_Z9llama_extv"], + [], + None, + required=False, +) +def llama_ext(): + pass +""", + encoding="utf-8", + ) + declarations = extract_ctypes_bindings(source) + scan = _scan("libllama.so", "linux", ["_Z9llama_extv"]) + result = check_bindings(scan, declarations) + + assert declarations == [ + BindingDeclaration( + python_name="llama_ext", + candidates=( + "llama_ext", + "?llama_ext@@YAXXZ", + "_Z9llama_extv", + ), + required=False, + line=8, + ) + ] + assert result["available"][0]["selected"] == "_Z9llama_extv" + assert result["missing_optional"] == [] + + +def test_selects_library_by_symbol_not_filename(): + scans = [ + _scan("custom-backend-name.dll", "windows", ["ggml_backend_init"]), + _scan("renamed-native-output.bin", "windows", ["llama_decode"]), + ] + + selected = select_scans_by_symbols(scans, ["llama_decode"]) + + assert [scan.library for scan in selected] == ["renamed-native-output.bin"] + + +def test_writes_jsonl_named_after_dynamic_library(tmp_path): + output_dir = tmp_path / "output" + scans = [ + _scan("libllama.so", "linux", ["llama_decode"]), + _scan("llama.dll", "windows", ["llama_decode"]), + ] + + timestamp = "20260728T120000.123456Z" + written = write_library_jsonl( + scans, + output_dir, + timestamp=timestamp, + ) + + assert [path.name for path in written] == [ + "libllama.so.jsonl", + "llama.dll.jsonl", + ] + run_dir = output_dir / timestamp + row = json.loads((run_dir / "llama.dll.jsonl").read_text("utf-8")) + assert row["library"] == "llama.dll" + assert row["canonical_name"] == "llama_decode" + assert row["generated_at"] == timestamp + assert "path" not in row + + +def test_check_bindings_cli_fails_when_optional_api_is_missing( + tmp_path, + monkeypatch, +): + library = tmp_path / "renamed.dll" + library.touch() + source = tmp_path / "bindings.py" + source.write_text( + """ +@ctypes_function(["llama_ext", "_Z9llama_extv"], [], None, required=False) +def llama_ext(): + pass +""", + encoding="utf-8", + ) + scan = _scan("renamed.dll", "windows", ["llama_decode"]) + monkeypatch.setattr( + abi_tool, + "_scan_paths", + lambda paths: ([scan], []), + ) + + exit_code = abi_tool.main(["check-bindings", str(library), "--source", str(source)]) + + assert exit_code == 1 From 7708b3de2596fb3262df565ce46c14a4db9ab254 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Tue, 28 Jul 2026 23:55:47 +0800 Subject: [PATCH 73/86] Update Submodule vendor/llama.cpp b77d646..7e1e28c Signed-off-by: JamePeng --- llama_cpp/llama_cpp.py | 52 ++++++++++++++++++++++++++---------------- vendor/llama.cpp | 2 +- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/llama_cpp/llama_cpp.py b/llama_cpp/llama_cpp.py index 317a43548..2901c6e2e 100644 --- a/llama_cpp/llama_cpp.py +++ b/llama_cpp/llama_cpp.py @@ -504,6 +504,30 @@ class llama_split_mode(enum.IntEnum): LLAMA_SPLIT_MODE_ROW = 2 LLAMA_SPLIT_MODE_TENSOR = 3 +# enum llama_load_mode { +# LLAMA_LOAD_MODE_NONE = 0, // no special loading mode +# LLAMA_LOAD_MODE_MMAP = 1, // memory map the model +# LLAMA_LOAD_MODE_MLOCK = 2, // force system to keep model in RAM rather than swapping or compressing +# LLAMA_LOAD_MODE_MMAP_MLOCK = 3, // mmap + force system to keep model in RAM rather than swapping or compressing +# LLAMA_LOAD_MODE_DIRECT_IO = 4, // use direct I/O if available +# }; +class llama_load_mode(enum.IntEnum): + LLAMA_LOAD_MODE_NONE = 0 # no special loading mode + LLAMA_LOAD_MODE_MMAP = 1 # memory map the model + LLAMA_LOAD_MODE_MLOCK = 2 # force system to keep model in RAM rather than swapping or compressing + LLAMA_LOAD_MODE_MMAP_MLOCK = 3 # mmap + force system to keep model in RAM rather than swapping or compressing + LLAMA_LOAD_MODE_DIRECT_IO = 4 # use direct I/O if available + +# LLAMA_API const char * llama_load_mode_name(enum llama_load_mode load_mode); +@ctypes_function("llama_load_mode_name", [ctypes.c_int], ctypes.c_char_p) +def llama_load_mode_name(load_mode: int) -> bytes: + ... + +# LLAMA_API enum llama_load_mode llama_load_mode_from_str(const char * str); +@ctypes_function("llama_load_mode_from_str", [ctypes.c_char_p], ctypes.c_int) +def llama_load_mode_from_str(str: ctypes.c_char_p) -> int: + ... + # enum llama_context_type { # LLAMA_CONTEXT_TYPE_DEFAULT = 0, # LLAMA_CONTEXT_TYPE_MTP = 1, @@ -743,17 +767,15 @@ class llama_model_tensor_buft_override(ctypes.Structure): # struct llama_model_params { # // NULL-terminated list of devices to use for offloading (if NULL, all available devices are used) # ggml_backend_dev_t * devices; -# + # // NULL-terminated list of buffer types to use for tensors that match a pattern # const struct llama_model_tensor_buft_override * tensor_buft_overrides; -# + # int32_t n_gpu_layers; // number of layers to store in VRAM, a negative value means all layers # enum llama_split_mode split_mode; // how to split the model across multiple GPUs +# enum llama_load_mode load_mode; // how to load the model -# // main_gpu interpretation depends on split_mode: -# // LLAMA_SPLIT_MODE_NONE: the GPU that is used for the entire model -# // LLAMA_SPLIT_MODE_ROW: the GPU that is used for small tensors and intermediate results -# // LLAMA_SPLIT_MODE_LAYER: ignored +# // the GPU that is used for the entire model when split_mode is LLAMA_SPLIT_MODE_NONE # int32_t main_gpu; # // proportion of the model (layers or rows) to offload to each GPU, size: llama_max_devices() @@ -770,12 +792,8 @@ class llama_model_tensor_buft_override(ctypes.Structure): # // override key-value pairs of the model meta data # const struct llama_model_kv_override * kv_overrides; - # // Keep the booleans together to avoid misalignment during copy-by-value. # bool vocab_only; // only load the vocabulary, no weights -# bool use_mmap; // use mmap if possible -# bool use_direct_io; // use direct io, takes precedence over use_mmap when supported -# bool use_mlock; // force system to keep model in RAM # bool check_tensors; // validate model tensor data # bool use_extra_bufts; // use extra buffer types (used for weight repacking) # bool no_host; // bypass host buffer allowing extra buffers to be used @@ -789,15 +807,13 @@ class llama_model_params(ctypes.Structure): tensor_buft_overrides(llama_model_tensor_buft_override): NULL-terminated list of buffer types to use for tensors that match a pattern n_gpu_layers (int): number of layers to store in VRAM, a negative value means all layers split_mode (int): how to split the model across multiple GPUs + load_mode (int): how to load the model main_gpu (int): the GPU that is used for the entire model. main_gpu interpretation depends on split_mode: LLAMA_SPLIT_NONE: the GPU that is used for the entire model LLAMA_SPLIT_ROW: the GPU that is used for small tensors and intermediate results LLAMA_SPLIT_LAYER: ignored tensor_split (ctypes.Array[ctypes.ctypes.c_float]): proportion of the model (layers or rows) to offload to each GPU, size: llama_max_devices() progress_callback (llama_progress_callback): called with a progress value between 0.0 and 1.0. Pass NULL to disable. If the provided progress_callback returns true, model loading continues. If it returns false, model loading is immediately aborted. progress_callback_user_data (ctypes.ctypes.c_void_p): context pointer passed to the progress callback kv_overrides (ctypes.Array[llama_model_kv_override]): override key-value pairs of the model meta data vocab_only (bool): only load the vocabulary, no weights - use_mmap (bool): use mmap if possible - use_direct_io(bool): use direct io, takes precedence over use_mmap when supported - use_mlock (bool): force system to keep model in RAM check_tensors (bool): validate model tensor data use_extra_bufts (bool): use extra buffer types (used for weight repacking) no_host (bool): bypass host buffer allowing extra buffers to be used @@ -808,34 +824,30 @@ class llama_model_params(ctypes.Structure): tensor_buft_overrides: CtypesPointer[llama_model_tensor_buft_override] n_gpu_layers: int split_mode: int + load_mode: int main_gpu: int tensor_split: CtypesArray[ctypes.c_float] progress_callback: Callable[[float, ctypes.c_void_p], bool] progress_callback_user_data: ctypes.c_void_p kv_overrides: CtypesArray[llama_model_kv_override] vocab_only: bool - use_mmap: bool - use_direct_io: bool - use_mlock: bool check_tensors: bool use_extra_bufts: bool no_host: bool no_alloc: bool _fields_ = [ - ("devices", ctypes.c_void_p), # NOTE: unnused + ("devices", ctypes.POINTER(ctypes.c_void_p)), # NOTE: unnused ("tensor_buft_overrides", ctypes.POINTER(llama_model_tensor_buft_override)), ("n_gpu_layers", ctypes.c_int32), ("split_mode", ctypes.c_int), + ("load_mode", ctypes.c_int), ("main_gpu", ctypes.c_int32), ("tensor_split", ctypes.POINTER(ctypes.c_float)), ("progress_callback", llama_progress_callback), ("progress_callback_user_data", ctypes.c_void_p), ("kv_overrides", ctypes.POINTER(llama_model_kv_override)), ("vocab_only", ctypes.c_bool), - ("use_mmap", ctypes.c_bool), - ("use_direct_io", ctypes.c_bool), - ("use_mlock", ctypes.c_bool), ("check_tensors", ctypes.c_bool), ("use_extra_bufts", ctypes.c_bool), ("no_host", ctypes.c_bool), diff --git a/vendor/llama.cpp b/vendor/llama.cpp index b77d64675..7e1e28cae 160000 --- a/vendor/llama.cpp +++ b/vendor/llama.cpp @@ -1 +1 @@ -Subproject commit b77d646751d01c0962bc203b6809e9d94f7d50b7 +Subproject commit 7e1e28cae36d41fe7bbe9dae7c9625de6565c063 From 00591a5b6a635682914e410224e3736d21ecb088 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Wed, 29 Jul 2026 00:03:14 +0800 Subject: [PATCH 74/86] feat(llama): support llama_model_params load_mode - Update model loading configuration to use the new `load_mode` field from llama_model_params and align with the latest llama.cpp API changes. - Remove deprecated internal handling of legacy loading flags and keep backward compatibility by warning users when `use_mmap`, `use_direct_io`, or `use_mlock` are still used. - This prepares the Python bindings for the updated llama.cpp model loading interface while providing a smoother migration path for existing users. Signed-off-by: JamePeng --- examples/low_level_api/common.py | 3 --- .../low_level_api/low_level_api_chat_cpp.py | 3 --- llama_cpp/llama.py | 22 +++++++++++-------- llama_cpp/server/model.py | 4 +--- llama_cpp/server/settings.py | 18 +++++---------- tests/test_llama.py | 3 --- 6 files changed, 19 insertions(+), 34 deletions(-) diff --git a/examples/low_level_api/common.py b/examples/low_level_api/common.py index 8adb2923c..601f5cebd 100644 --- a/examples/low_level_api/common.py +++ b/examples/low_level_api/common.py @@ -60,9 +60,6 @@ class GptParams: instruct: bool = False perplexity: bool = False - use_mmap: bool = True - use_direct_io: bool = False - use_mlock: bool = False mem_test: bool = False verbose_prompt: bool = False diff --git a/examples/low_level_api/low_level_api_chat_cpp.py b/examples/low_level_api/low_level_api_chat_cpp.py index 1f4f5b3e7..96c4121f4 100644 --- a/examples/low_level_api/low_level_api_chat_cpp.py +++ b/examples/low_level_api/low_level_api_chat_cpp.py @@ -76,9 +76,6 @@ def __init__(self, params: GptParams) -> None: self.lparams.n_parts = self.params.n_parts self.lparams.seed = self.params.seed self.lparams.memory_f16 = self.params.memory_f16 - self.lparams.use_mlock = self.params.use_mlock - self.lparams.use_mmap = self.params.use_mmap - self.lparams.use_direct_io = self.params.use_direct_io self.model = llama_cpp.llama_load_model_from_file( self.params.model.encode("utf8"), self.lparams diff --git a/llama_cpp/llama.py b/llama_cpp/llama.py index f733d7afb..3ce635b54 100644 --- a/llama_cpp/llama.py +++ b/llama_cpp/llama.py @@ -104,10 +104,11 @@ def __init__( cpu_moe: bool = False, n_cpu_moe: int = 0, split_mode: int = llama_cpp_lib.llama_split_mode.LLAMA_SPLIT_MODE_LAYER, + load_mode: int = llama_cpp_lib.llama_load_mode.LLAMA_LOAD_MODE_MMAP, main_gpu: int = 0, tensor_split: Optional[List[float]] = None, vocab_only: bool = False, - use_mmap: bool = True, + use_mmap: bool = False, use_direct_io: bool = False, use_mlock: bool = False, check_tensors: bool = False, @@ -215,11 +216,10 @@ def __init__( n_cpu_moe: Keep the MoE expert weights of the first N layers on CPU. Useful when VRAM is insufficient for MoE models. split_mode: How to split the model across GPUs. See llama_cpp.LLAMA_SPLIT_* for options. + load_mode: How to load the model. See llama_cpp.LLAMA_LOAD_MODE_* for options. main_gpu: main_gpu interpretation depends on split_mode: LLAMA_SPLIT_MODE_NONE: the GPU that is used for the entire model. LLAMA_SPLIT_MODE_ROW: the GPU that is used for small tensors and intermediate results. LLAMA_SPLIT_MODE_LAYER: ignored tensor_split: How split tensors should be distributed across GPUs. If None, the model is not split. vocab_only: Only load the vocabulary no weights. - use_mmap: Use mmap if possible. - use_mlock: Force the system to keep the model in RAM. check_tensors: validate model tensor data use_extra_bufts: use extra buffer types (used for weight repacking) no_host: bypass host buffer allowing extra buffers to be used @@ -352,10 +352,19 @@ def __init__( self.model_path = model_path + if (use_mmap or use_direct_io or use_mlock) and verbose: + print( + "Llama.__init__: WARNING: " + "Legacy load options (`use_mmap`, `use_direct_io`, `use_mlock`) " + "are deprecated. Use `load_mode` instead.", + file=sys.stderr, + ) + # Model Params self.model_params = llama_cpp_lib.llama_model_default_params() self.model_params.n_gpu_layers = self._parse_n_gpu_layers(n_gpu_layers) self.model_params.split_mode = split_mode + self.model_params.load_mode = load_mode self.model_params.main_gpu = main_gpu self.tensor_split = tensor_split self._c_tensor_split = None @@ -371,9 +380,6 @@ def __init__( ) # keep a reference to the array so it is not gc'd self.model_params.tensor_split = self._c_tensor_split self.model_params.vocab_only = vocab_only - self.model_params.use_mmap = use_mmap - self.model_params.use_direct_io = use_direct_io - self.model_params.use_mlock = use_mlock self.model_params.check_tensors = check_tensors self.model_params.use_extra_bufts = use_extra_bufts self.model_params.no_host = no_host @@ -3445,12 +3451,10 @@ def __getstate__(self): cpu_moe=self.cpu_moe, n_cpu_moe=self.n_cpu_moe, split_mode=self.model_params.split_mode, + load_mode=self.model_params.load_mode, main_gpu=self.model_params.main_gpu, tensor_split=self.tensor_split, vocab_only=self.model_params.vocab_only, - use_mmap=self.model_params.use_mmap, - use_direct_io=self.model_params.use_direct_io, - use_mlock=self.model_params.use_mlock, check_tensors=self.model_params.check_tensors, use_extra_bufts=self.model_params.use_extra_bufts, no_host=self.model_params.no_host, diff --git a/llama_cpp/server/model.py b/llama_cpp/server/model.py index 6b3fd1dd1..0d509bbcf 100644 --- a/llama_cpp/server/model.py +++ b/llama_cpp/server/model.py @@ -294,12 +294,10 @@ def load_llama_from_model_settings(settings: ModelSettings) -> llama_cpp.Llama: # Model Params n_gpu_layers=settings.n_gpu_layers, split_mode=settings.split_mode, + load_mode=settings.load_mode, main_gpu=settings.main_gpu, tensor_split=settings.tensor_split, vocab_only=settings.vocab_only, - use_mmap=settings.use_mmap, - use_direct_io=settings.use_direct_io, - use_mlock=settings.use_mlock, check_tensors=settings.check_tensors, use_extra_bufts=settings.use_extra_bufts, no_host=settings.no_host, diff --git a/llama_cpp/server/settings.py b/llama_cpp/server/settings.py index 350ccc232..62ce3b504 100644 --- a/llama_cpp/server/settings.py +++ b/llama_cpp/server/settings.py @@ -32,8 +32,12 @@ class ModelSettings(BaseSettings): ) split_mode: int = Field( default=llama_cpp.llama_split_mode.LLAMA_SPLIT_MODE_LAYER, - description="The split mode to use.", + description="how to split the model across multiple GPUs", ) + load_mode: int = Field( + default=llama_cpp.llama_load_mode.LLAMA_LOAD_MODE_MMAP, + description="how to load the model", + ) main_gpu: int = Field( default=0, ge=0, @@ -46,18 +50,6 @@ class ModelSettings(BaseSettings): vocab_only: bool = Field( default=False, description="Whether to only return the vocabulary." ) - use_mmap: bool = Field( - default=True, - description="Enable mmap to use filesystem cache.", - ) - use_direct_io: bool = Field( - default=False, - description="Use direct io, takes precedence over use_mmap.", - ) - use_mlock: bool = Field( - default=False, - description="Use mlock for force system to keep model in RAM", - ) check_tensors: bool = Field( default=False, description="Validate model tensor data.", diff --git a/tests/test_llama.py b/tests/test_llama.py index b233ea526..d0053feab 100644 --- a/tests/test_llama.py +++ b/tests/test_llama.py @@ -260,9 +260,6 @@ def test_real_model(llama_cpp_model_path): # 1. Setup Model Parameters params = llama_cpp.llama_model_default_params() - params.use_mmap = llama_cpp.llama_supports_mmap() - params.use_direct_io = False - params.use_mlock = llama_cpp.llama_supports_mlock() params.check_tensors = False # 2. Load the Model From bab30611b4035bd69765d4856f907c763a6a69fb Mon Sep 17 00:00:00 2001 From: JamePeng Date: Wed, 29 Jul 2026 00:11:27 +0800 Subject: [PATCH 75/86] docs: document `load_mode` migration - Replace references to the legacy model loading flags with load_mode, document all supported loading modes for the Python API and server, and update the performance tuning example. Signed-off-by: JamePeng --- docs/server.md | 20 +++++++++++ docs/wiki/core/Llama.md | 40 ++++++++++++++++++++-- examples/notebooks/PerformanceTuning.ipynb | 12 +++++-- 3 files changed, 66 insertions(+), 6 deletions(-) diff --git a/docs/server.md b/docs/server.md index cd6f86c51..3bdc0c7e6 100644 --- a/docs/server.md +++ b/docs/server.md @@ -37,6 +37,26 @@ CLI arguments and environment variables are available for all of the fields defi Additionally the server supports configuration check out the [configuration section](#configuration-and-multi-model-support) for more information and examples. +#### Model loading mode + +Use `load_mode` to select how the server loads model data. The corresponding +CLI option is `--load_mode`, the environment variable is `LOAD_MODE`, and a +multi-model JSON configuration can set `"load_mode"` for each model. +`use_mmap`, `use_direct_io`, and `use_mlock` are no longer server settings. + +| Value | Mode | Description | +|---:|---|---| +| `0` | `LLAMA_LOAD_MODE_NONE` | Use no special model-loading mode. | +| `1` | `LLAMA_LOAD_MODE_MMAP` | Memory-map the model. This is the default. | +| `2` | `LLAMA_LOAD_MODE_MLOCK` | Keep the loaded model in RAM rather than allowing it to be swapped or compressed. | +| `3` | `LLAMA_LOAD_MODE_MMAP_MLOCK` | Memory-map the model and keep its mapped pages in RAM. | +| `4` | `LLAMA_LOAD_MODE_DIRECT_IO` | Use direct I/O when it is available. | + +For example, start the server with memory mapping plus memory locking: + +```bash +python3 -m llama_cpp.server --model --load_mode 3 +``` ## Guides diff --git a/docs/wiki/core/Llama.md b/docs/wiki/core/Llama.md index 305624c8f..00add6ea4 100644 --- a/docs/wiki/core/Llama.md +++ b/docs/wiki/core/Llama.md @@ -3,7 +3,7 @@ title: Llama Class module_name: llama_cpp.llama source_file: llama_cpp/llama.py class_name: Llama -last_updated: 2026-07-26 +last_updated: 2026-07-29 version_target: "latest" --- @@ -36,13 +36,47 @@ Initialize the model and context. Note that model loading will immediately alloc | `cpu_moe` | `bool` | `False` | Whether to keep all MoE weights on CPU | | `n_cpu_moe` | `int` | `0` | Number of first N MoE layers to keep on CPU (compatible with `cpu_moe`) | | `split_mode` | `int` | `LLAMA_SPLIT_MODE_LAYER` | Model GPU split mode:
• `LLAMA_SPLIT_MODE_NONE`: single GPU
• `LLAMA_SPLIT_MODE_ROW`: row-level split
• `LLAMA_SPLIT_MODE_LAYER`: layer-level split | +| `load_mode` | `int` (`llama_load_mode`) | `LLAMA_LOAD_MODE_MMAP` | How model data is loaded. Select one of the `LLAMA_LOAD_MODE_*` values described below. | | `main_gpu` | `int` | `0` | The primary GPU to use for intermediate results or the entire model. | | `tensor_split` | `List[float]` | `None` | Proportional split of tensors across GPUs (max `LLAMA_MAX_DEVICES`). | -| `use_mmap` | `bool` | `True` | Whether to use memory mapping (mmap) if possible. | -| `use_mlock` | `bool` | `False` | Force the system to keep the model in RAM, preventing swapping. | | `kv_overrides` | `Dict` | `None` | Key-value overrides for the model metadata (supports bool, int, float, str). | | `numa` | `Union[bool, int]` | `False` | NUMA strategy (e.g., `GGML_NUMA_STRATEGY_DISTRIBUTE`). | +#### Model Load Modes + +`load_mode` replaces the legacy `use_mmap`, `use_direct_io`, and `use_mlock` +arguments. It accepts a member of `llama_cpp.llama_load_mode`: + +| Value | Integer | Description | +| :--- | :---: | :--- | +| `LLAMA_LOAD_MODE_NONE` | `0` | Use no special model-loading mode. | +| `LLAMA_LOAD_MODE_MMAP` | `1` | Memory-map the model. This is the default. | +| `LLAMA_LOAD_MODE_MLOCK` | `2` | Keep the loaded model in RAM rather than allowing it to be swapped or compressed. | +| `LLAMA_LOAD_MODE_MMAP_MLOCK` | `3` | Memory-map the model and keep its mapped pages in RAM. | +| `LLAMA_LOAD_MODE_DIRECT_IO` | `4` | Use direct I/O when it is available. | + +```python +import llama_cpp + +llm = llama_cpp.Llama( + model_path="models/model.gguf", + load_mode=llama_cpp.llama_load_mode.LLAMA_LOAD_MODE_MMAP_MLOCK, +) +``` + +The legacy loading arguments are retained only for call compatibility. They no +longer configure the underlying model parameters and may emit a deprecation +warning; set `load_mode` explicitly instead. Use the following migration +mapping: + +| Legacy configuration | Replacement | +| :--- | :--- | +| `use_mmap=False, use_mlock=False` | `load_mode=LLAMA_LOAD_MODE_NONE` | +| `use_mmap=True, use_mlock=False` | `load_mode=LLAMA_LOAD_MODE_MMAP` | +| `use_mmap=False, use_mlock=True` | `load_mode=LLAMA_LOAD_MODE_MLOCK` | +| `use_mmap=True, use_mlock=True` | `load_mode=LLAMA_LOAD_MODE_MMAP_MLOCK` | +| `use_direct_io=True` | `load_mode=LLAMA_LOAD_MODE_DIRECT_IO` | + ### Context & Batch Parameters | Parameter | Type | Default | Description | diff --git a/examples/notebooks/PerformanceTuning.ipynb b/examples/notebooks/PerformanceTuning.ipynb index ba74e4a41..43772a5b7 100644 --- a/examples/notebooks/PerformanceTuning.ipynb +++ b/examples/notebooks/PerformanceTuning.ipynb @@ -24,7 +24,13 @@ "# Hyperparameters\n", "space = [\n", " Categorical([True, False], name=\"f16_kv\"),\n", - " Categorical([True, False], name=\"use_mlock\"),\n", + " Categorical(\n", + " [\n", + " llama_cpp.llama_load_mode.LLAMA_LOAD_MODE_MMAP,\n", + " llama_cpp.llama_load_mode.LLAMA_LOAD_MODE_MMAP_MLOCK,\n", + " ],\n", + " name=\"load_mode\",\n", + " ),\n", " Integer(1, multiprocessing.cpu_count(), name=\"n_threads\"),\n", " Integer(1, 2048, name=\"n_batch\"),\n", "]\n", @@ -46,13 +52,13 @@ "@use_named_args(space)\n", "def objective(**params):\n", " f16_kv = params[\"f16_kv\"]\n", - " use_mlock = params[\"use_mlock\"]\n", + " load_mode = params[\"load_mode\"]\n", " n_threads = params[\"n_threads\"]\n", " n_batch = params[\"n_batch\"]\n", " llm = llama_cpp.Llama(\n", " model_path=MODEL_PATH,\n", " f16_kv=f16_kv,\n", - " use_mlock=use_mlock,\n", + " load_mode=load_mode,\n", " n_threads=n_threads,\n", " n_batch=n_batch,\n", " )\n", From f8bc6b05f8c384e675c6378e17e3c548c4f465e5 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Thu, 30 Jul 2026 21:18:43 +0800 Subject: [PATCH 76/86] Update Submodule vendor/llama.cpp 7e1e28c..e1a1abb Signed-off-by: JamePeng --- llama_cpp/llama_cpp.py | 20 ++++++++++++++++++++ vendor/llama.cpp | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/llama_cpp/llama_cpp.py b/llama_cpp/llama_cpp.py index 2901c6e2e..a956dda47 100644 --- a/llama_cpp/llama_cpp.py +++ b/llama_cpp/llama_cpp.py @@ -3544,6 +3544,26 @@ def llama_vocab_get_add_sep(vocab: llama_vocab_p, /) -> bool: ... +# // model-specific suppress tokens (gguf key: tokenizer.ggml.suppress_tokens) +# LLAMA_API const llama_token * llama_vocab_get_suppress_tokens(const struct llama_vocab * vocab, int32_t * n_suppress_tokens); +@ctypes_function( + "llama_vocab_get_suppress_tokens", + [ + llama_vocab_p_ctypes, + ctypes.POINTER(ctypes.c_int32), + ], + llama_token_p, +) +def llama_vocab_get_suppress_tokens( + vocab: llama_vocab_p, + n_suppress_tokens: ctypes.POINTER(ctypes.c_int32), # type: ignore +) -> llama_token_p: # type: ignore + """ + model-specific suppress tokens (gguf key: tokenizer.ggml.suppress_tokens) + """ + ... + + # LLAMA_API llama_token llama_vocab_fim_pre(const struct llama_vocab * vocab); @ctypes_function( "llama_vocab_fim_pre", diff --git a/vendor/llama.cpp b/vendor/llama.cpp index 7e1e28cae..e1a1abb78 160000 --- a/vendor/llama.cpp +++ b/vendor/llama.cpp @@ -1 +1 @@ -Subproject commit 7e1e28cae36d41fe7bbe9dae7c9625de6565c063 +Subproject commit e1a1abb78746c025f5e9039f590e37ccdb758ae7 From 9ac3f545ac99048526a1dfcf6d3f30f1bc10df82 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Sat, 1 Aug 2026 00:14:19 +0800 Subject: [PATCH 77/86] Update Submodule vendor/llama.cpp e1a1abb..876a432 Signed-off-by: JamePeng --- llama_cpp/llama_cpp.py | 6 +++++- vendor/llama.cpp | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/llama_cpp/llama_cpp.py b/llama_cpp/llama_cpp.py index a956dda47..0c2663709 100644 --- a/llama_cpp/llama_cpp.py +++ b/llama_cpp/llama_cpp.py @@ -798,6 +798,7 @@ class llama_model_tensor_buft_override(ctypes.Structure): # bool use_extra_bufts; // use extra buffer types (used for weight repacking) # bool no_host; // bypass host buffer allowing extra buffers to be used # bool no_alloc; // only load metadata and simulate memory allocations +# bool load_mtp; // whether to load MTP layers # }; class llama_model_params(ctypes.Structure): """Parameters for llama_model @@ -817,7 +818,8 @@ class llama_model_params(ctypes.Structure): check_tensors (bool): validate model tensor data use_extra_bufts (bool): use extra buffer types (used for weight repacking) no_host (bool): bypass host buffer allowing extra buffers to be used - no_alloc (bool): only load metadata and simulate memory allocations""" + no_alloc (bool): only load metadata and simulate memory allocations + load_mtp (bool): whether to load MTP layers""" if TYPE_CHECKING: devices: CtypesArray[ctypes.c_void_p] # NOTE: unused @@ -835,6 +837,7 @@ class llama_model_params(ctypes.Structure): use_extra_bufts: bool no_host: bool no_alloc: bool + load_mtp: bool _fields_ = [ ("devices", ctypes.POINTER(ctypes.c_void_p)), # NOTE: unnused @@ -852,6 +855,7 @@ class llama_model_params(ctypes.Structure): ("use_extra_bufts", ctypes.c_bool), ("no_host", ctypes.c_bool), ("no_alloc", ctypes.c_bool), + ("load_mtp", ctypes.c_bool), ] llama_model_params_p = ctypes.POINTER(llama_model_params) diff --git a/vendor/llama.cpp b/vendor/llama.cpp index e1a1abb78..876a43211 160000 --- a/vendor/llama.cpp +++ b/vendor/llama.cpp @@ -1 +1 @@ -Subproject commit e1a1abb78746c025f5e9039f590e37ccdb758ae7 +Subproject commit 876a4321163249c43ca4e986818fab5ab081f282 From 6f60d0347bba9e2f987f36af51a9bf9865ae824b Mon Sep 17 00:00:00 2001 From: JamePeng Date: Sat, 1 Aug 2026 00:18:03 +0800 Subject: [PATCH 78/86] feat(llama): expose additional model loading options - add `no_alloc` and `load_mtp` parameters - enable `extra buffer types` by default Signed-off-by: JamePeng --- llama_cpp/llama.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/llama_cpp/llama.py b/llama_cpp/llama.py index 3ce635b54..8092f8695 100644 --- a/llama_cpp/llama.py +++ b/llama_cpp/llama.py @@ -107,14 +107,16 @@ def __init__( load_mode: int = llama_cpp_lib.llama_load_mode.LLAMA_LOAD_MODE_MMAP, main_gpu: int = 0, tensor_split: Optional[List[float]] = None, - vocab_only: bool = False, + kv_overrides: Optional[Dict[str, Union[bool, int, float, str]]] = None, use_mmap: bool = False, use_direct_io: bool = False, use_mlock: bool = False, + vocab_only: bool = False, check_tensors: bool = False, - use_extra_bufts: bool = False, + use_extra_bufts: bool = True, no_host: bool = False, - kv_overrides: Optional[Dict[str, Union[bool, int, float, str]]] = None, + no_alloc: bool = False, + load_mtp: bool = False, # Context Params seed: int = llama_cpp_lib.LLAMA_DEFAULT_SEED, n_ctx: int = 512, @@ -219,11 +221,13 @@ def __init__( load_mode: How to load the model. See llama_cpp.LLAMA_LOAD_MODE_* for options. main_gpu: main_gpu interpretation depends on split_mode: LLAMA_SPLIT_MODE_NONE: the GPU that is used for the entire model. LLAMA_SPLIT_MODE_ROW: the GPU that is used for small tensors and intermediate results. LLAMA_SPLIT_MODE_LAYER: ignored tensor_split: How split tensors should be distributed across GPUs. If None, the model is not split. + kv_overrides: Key-value overrides for the model. vocab_only: Only load the vocabulary no weights. check_tensors: validate model tensor data use_extra_bufts: use extra buffer types (used for weight repacking) no_host: bypass host buffer allowing extra buffers to be used - kv_overrides: Key-value overrides for the model. + no_alloc: only load metadata and simulate memory allocations + load_mtp: whether to load MTP layers seed: RNG seed, -1 for random n_ctx: Text context, 0 = from model n_keep: Number of tokens to keep from initial prompt @@ -383,6 +387,8 @@ def __init__( self.model_params.check_tensors = check_tensors self.model_params.use_extra_bufts = use_extra_bufts self.model_params.no_host = no_host + self.model_params.no_alloc = no_alloc + self.model_params.load_mtp = load_mtp # Logic of cpu_moe, n_cpu_moe # Reference from llama.cpp/tools/llama-bench/llama-bench.cpp @@ -3454,11 +3460,13 @@ def __getstate__(self): load_mode=self.model_params.load_mode, main_gpu=self.model_params.main_gpu, tensor_split=self.tensor_split, + kv_overrides=self.kv_overrides, vocab_only=self.model_params.vocab_only, check_tensors=self.model_params.check_tensors, use_extra_bufts=self.model_params.use_extra_bufts, no_host=self.model_params.no_host, - kv_overrides=self.kv_overrides, + no_alloc=self.model_params.no_alloc, + load_mtp=self.model_params.load_mtp, # Context Params seed=self._seed, n_ctx=self.context_params.n_ctx, From aafc6fb74ebfba6a044510f80b5e9ad277109c12 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Sat, 1 Aug 2026 00:59:46 +0800 Subject: [PATCH 79/86] fix(ctypes): correct llama-ext binding signatures - use uint32_t for layer IDs - fix void return type for embedding extraction control - return target layer count as uint32_t Signed-off-by: JamePeng --- llama_cpp/llama_cpp.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/llama_cpp/llama_cpp.py b/llama_cpp/llama_cpp.py index 0c2663709..cc6a1a67f 100644 --- a/llama_cpp/llama_cpp.py +++ b/llama_cpp/llama_cpp.py @@ -5318,15 +5318,15 @@ def llama_get_embeddings_nextn_ith( "__Z30llama_set_embeddings_layer_inpP13llama_contextjb", "_Z30llama_set_embeddings_layer_inpP13llama_contextjb", ], - [llama_context_p_ctypes, ctypes.c_int32, ctypes.c_bool], - ctypes.POINTER(ctypes.c_float), + [llama_context_p_ctypes, ctypes.c_uint32, ctypes.c_bool], + None, required=False, ) def llama_set_embeddings_layer_inp( ctx: llama_context_p, - lid: ctypes.c_int32, + lid: ctypes.c_uint32, value: bool, -) -> ctypes.POINTER(ctypes.c_float): # type: ignore +) -> None: # type: ignore """ Set whether the context outputs the input embeddings of a specific layer """ @@ -5342,13 +5342,13 @@ def llama_set_embeddings_layer_inp( "__Z30llama_get_embeddings_layer_inpP13llama_contextj", "_Z30llama_get_embeddings_layer_inpP13llama_contextj", ], - [llama_context_p_ctypes, ctypes.c_int32], + [llama_context_p_ctypes, ctypes.c_uint32], ctypes.POINTER(ctypes.c_float), required=False, ) def llama_get_embeddings_layer_inp( ctx: llama_context_p, - lid: ctypes.c_int32, + lid: ctypes.c_uint32, ) -> ctypes.POINTER(ctypes.c_float): # type: ignore ... @@ -5402,12 +5402,12 @@ def llama_model_target_layer_ids( "_Z30llama_model_target_layer_ids_nPK11llama_model", ], [llama_model_p_ctypes], - ctypes.POINTER(ctypes.c_uint32), + ctypes.c_uint32, required=False, ) def llama_model_target_layer_ids_n( model: llama_model_p -) -> ctypes.POINTER(ctypes.c_uint32): # type: ignore +) -> int: """ returns the number of extracted layers from target model """ From d9d27a7bdf1c27d50c1490ad6acd825f31804902 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Sat, 1 Aug 2026 04:17:19 +0800 Subject: [PATCH 80/86] Bump version to 0.3.45 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - This release focuses on reactivating and modernizing Llama’s built-in embedding capabilities, aligning the Python bindings with the latest llama.cpp APIs, and improving reliability across platforms. Signed-off-by: JamePeng --- CHANGELOG.md | 152 ++++++++++++++++++++++++++++++++++++++++++ llama_cpp/__init__.py | 2 +- 2 files changed, 153 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69fc02b25..075b97884 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,158 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.3.45] Reactivated Built-in Embeddings, Modern Model Loading, and Stronger Cross-Platform Reliability + +- fix(ctypes): correct llama-ext binding signatures + - use uint32_t for layer IDs + - fix void return type for embedding extraction control + - return target layer count as uint32_t + +- feat(llama): expose additional model loading options + - add `no_alloc` and `load_mtp` parameters + - enable `extra buffer types` by default + +- feat(llama): support llama_model_params `load_mode` + - Update model loading configuration to use the new `load_mode` field from + llama_model_params and align with the latest llama.cpp API changes. + - Remove deprecated internal handling of legacy loading flags and keep + backward compatibility by warning users when `use_mmap`, `use_direct_io`, + or `use_mlock` are still used. + - This prepares the Python bindings for the updated llama.cpp model loading + interface while providing a smoother migration path for existing users. + - docs: document `load_mode` migration + - Replace references to the legacy model loading flags with load_mode, document all supported loading modes for the Python API and server, and update the performance tuning example. + +- feat(tools): add cross-platform ABI inspection utility + - Inspect `PE`, `ELF`, and `Mach-O` exports and normalize platform-specific symbol names. + - Validate optional `llama_ext` ctypes aliases across Windows, Linux, and macOS builds. Keep artifacts and timestamped privacy-safe reports local to the repository. + - More information see here: [Cross-platform ABI inspection](https://github.com/JamePeng/llama-cpp-python/tree/main/tools/abi) + +- fix(ctypes): support GCC/Clang mangled symbols for optional llama_ext APIs + - Add missing `_Z` Itanium C++ ABI symbol variants to ctypes function + lookup lists. This improves compatibility with Linux and macOS builds + where C++ symbols are exported using GCC/Clang name mangling. + - Issue report from **@ckcfcc** (https://github.com/JamePeng/llama-cpp-python/issues/159) + +- fix(loader): guard `HIP_PATH` and `VULKAN_SDK` dirs with os.path.exists +os.add_dll_directory() raises FileNotFoundError [WinError 3] when the +directory does not exist, so a stale `HIP_PATH` or `VULKAN_SDK` left behind by +an uninstalled SDK makes "import llama_cpp" fail outright on Windows.(by **@emptyngton**) + + The CUDA_PATH branch above already guards each candidate directory with + os.path.exists(); this applies the same pattern to the HIP and Vulkan + branches. Valid directories are still added individually, so a partially + removed SDK contributes whichever of bin/lib remain instead of raising. + +- fix(_internals): clean up native resources on initialization failures + - Register native model and batch ownership immediately after allocation + so later validation failures cannot leak llama.cpp resources. + Free a loaded model when vocab lookup fails, and route mixed-batch setup + failures through idempotent cleanup. + - Initialize sampling-context resource fields before fallible setup and + make partial teardown safe to repeat. This prevents missing attributes + from interrupting cleanup when sampler-chain construction fails. + - Clear model, vocabulary, and sampling parameter references after native + context and sampler resources have been released. This prevents closed + wrapper objects from unnecessarily keeping models and related Python + objects alive. + - Add failure-injection tests that verify model and batch handles are freed + exactly once and partially initialized sampling contexts release their resources + idempotently.Extend lifecycle tests to verify that parent references are cleared + and that repeated close calls remain safe. + +- test(chat-format): modernize coverage with Qwen3.5-style templates + - Replace the legacy Mistral-focused chat format tests with self-contained + Qwen3.5-style Jinja template coverage: + - verify ChatML system, user, and assistant message rendering + - cover enabled and disabled thinking generation prompts + - test image and video placeholders with vision identifiers + - validate tool definitions, tool calls, and tool response history + - add clear error coverage for invalid message structures + - verify model-specific stop token criteria + - keep the tests independent of tokenizer files and model weights + +- docs(readme): replace the new logo with fork project branding + - Add the new llama-cpp-python logo asset under docs and update the README + header to reference the repository-local image. + - the new logo which combined llama, C++, and Project branding remains readable. + +- docs(embedding): add end-to-end embeddings and reranking guide + - Create a schema-compliant feature guide covering sentence embeddings, + token-level vectors, reranking workflows, pooling modes, normalization, + streaming batch configuration, return shapes, and output formats. + - Add complete examples for the standard Llama API, LlamaEmbedding, + pre-tokenized inputs, cosine-similarity output, and cross-encoder + reranking. + - Document common configuration problems, implementation limitations, and + the embedding and reranking model families currently listed as supported + by the project. + - Expose the new feature guide through the Wiki index. + +- docs(llama): expand embedding parameters and API guidance + - Add a role overview and reorganize constructor options into focused, + readable parameter groups. + - Document embedding, pooling, attention, KV cache, sequence capacity, and + recurrent-state settings with their defaults and runtime behavior. + - Expand the embed() and create_embedding() sections with normalization + modes, return shapes, batching semantics, pooling recommendations, + OpenAI compatibility notes, and resource-safe examples. + - Fix the YAML frontmatter and improve Markdown spacing for cleaner Wiki + rendering. + +- docs(embedding): document maintained APIs and sequence batch capacity + - Replace the deprecated Llama embedding guidance with current embed() and + create_embedding() usage. + - Document the roles of n_batch, n_ubatch, and n_seq_max, including + parallel batching examples, resource considerations, common sequence ID + errors, and the required configuration changes. + - Clarify that LlamaEmbedding remains a convenience interface for + embedding-oriented defaults and reranking workflows. + +- docs(example): refresh the built-in embedding usage example + - Fix the Llama constructor option from embedding=True to embeddings=True + and demonstrate L2-normalized output through create_embedding(). + +- test(embedding): cover built-in and streaming embedding workflows + - Add coverage for actionable LlamaBatch sequence-capacity errors and the + maintained embedding APIs on the standard Llama class. + - Verify pre-tokenized batches, normalization, separator-based inputs, + token accounting, OpenAI-compatible responses, and LlamaEmbedding + streaming behavior with n_seq_max=1. + - Explicitly close embedding models after integration tests to release + native context and model resources. + +- fix(embedding): respect n_seq_max when streaming embedding batches + - Use the configured sequence capacity instead of n_ubatch when deciding + when to decode the current LlamaEmbedding batch. + - This prevents invalid sequence IDs for multi-document inputs and allows + the default n_seq_max=1 configuration to process documents sequentially + without failing. + +- refactor(batch): improve sequence capacity validation guidance + - Make LlamaBatch sequence validation errors explain the configured + n_seq_max value, valid sequence ID range, and minimum capacity required + for parallel batching. + - Handle negative sequence IDs separately and provide actionable setup + guidance for Llama, LlamaEmbedding, and direct LlamaBatch users. + - Remove the unused normalize_embedding helper now that normalization is + handled by the embedding pipeline. + +- feat(embedding): modernize the built-in Llama embedding API + - Replace the legacy embedding path with sequence-aware streaming batch + processing based on the current LlamaBatch interface. + - Support string, batched string, and pre-tokenized inputs, token-level and + rank pooling outputs, separator-based splitting, token accounting, and + llama.cpp-compatible normalization modes. + - Restore embed() and create_embedding() as maintained Llama APIs while + preserving the existing boolean normalization behavior. + +- feat: Update llama.cpp to [ggml-org/llama.cpp/commit/876a4321163249c43ca4e986818fab5ab081f282](https://github.com/ggml-org/llama.cpp/commit/876a4321163249c43ca4e986818fab5ab081f282) + +- feat: Sync llama.cpp llama/mtmd/ggml API Binding 20260801 + +More information see: https://github.com/JamePeng/llama-cpp-python/compare/ebf6099b81cf67cfb5eec569466367c9fa04e9d4...aafc6fb74ebfba6a044510f80b5e9ad277109c12 + ## [0.3.44] Improved Windows DLL(OpenMP) Loading Reliability for GGML Backends - fix(ggml): preload bundled OpenMP runtime before loading ggml-base diff --git a/llama_cpp/__init__.py b/llama_cpp/__init__.py index 10e452d5f..b359355f9 100644 --- a/llama_cpp/__init__.py +++ b/llama_cpp/__init__.py @@ -1,4 +1,4 @@ from .llama_cpp import * from .llama import * -__version__ = "0.3.44" +__version__ = "0.3.45" From 07c04257e3116b3575199748abb4d66f16168ae3 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Sun, 2 Aug 2026 22:57:19 +0800 Subject: [PATCH 81/86] Update Submodule vendor/llama.cpp 876a432..221f0f6 Signed-off-by: JamePeng --- vendor/llama.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/llama.cpp b/vendor/llama.cpp index 876a43211..221f0f635 160000 --- a/vendor/llama.cpp +++ b/vendor/llama.cpp @@ -1 +1 @@ -Subproject commit 876a4321163249c43ca4e986818fab5ab081f282 +Subproject commit 221f0f6356efe2260023208365705ec5d5a7c8f5 From 88fce160be1e6f21834c1b2ddcee6c4b72ccfed8 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Mon, 3 Aug 2026 21:19:44 +0800 Subject: [PATCH 82/86] Update Submodule vendor/llama.cpp 221f0f6..563dec8 Signed-off-by: JamePeng --- llama_cpp/llama_cpp.py | 9 ++++++--- vendor/llama.cpp | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/llama_cpp/llama_cpp.py b/llama_cpp/llama_cpp.py index cc6a1a67f..c3b38005d 100644 --- a/llama_cpp/llama_cpp.py +++ b/llama_cpp/llama_cpp.py @@ -4130,6 +4130,7 @@ def llama_chat_builtin_templates( # struct ggml_tensor * probs; # struct ggml_tensor * sampled; # struct ggml_tensor * candidates; +# int64_t n_vocab; # }; class llama_sampler_data(ctypes.Structure): if TYPE_CHECKING: @@ -4137,12 +4138,14 @@ class llama_sampler_data(ctypes.Structure): probs: ctypes.c_void_p sampled: ctypes.c_void_p candidates: ctypes.c_void_p + n_vocab: ctypes.c_int64 _fields_ = [ ("logits", ctypes.c_void_p), ("probs", ctypes.c_void_p), ("sampled", ctypes.c_void_p), ("candidates", ctypes.c_void_p), + ("n_vocab", ctypes.c_int64), ] @@ -4654,9 +4657,9 @@ def llama_sampler_init_grammar_lazy_patterns( # /// NOTE: Avoid using on the full vocabulary as searching for repeated tokens can become slow. For example, apply top-k or top-p sampling first. # LLAMA_API struct llama_sampler * llama_sampler_init_penalties( # int32_t penalty_last_n, // last n tokens to penalize (0 = disable penalty, -1 = context size) -# float penalty_repeat, // 1.0 = disabled -# float penalty_freq, // 0.0 = disabled -# float penalty_present); // 0.0 = disabled +# float penalty_repeat, // must be > 0.0, 1.0 = disabled +# float penalty_freq, // must be finite, 0.0 = disabled +# float penalty_present); // must be finite, 0.0 = disabled @ctypes_function( "llama_sampler_init_penalties", [ctypes.c_int32, ctypes.c_float, ctypes.c_float, ctypes.c_float], diff --git a/vendor/llama.cpp b/vendor/llama.cpp index 221f0f635..563dec81c 160000 --- a/vendor/llama.cpp +++ b/vendor/llama.cpp @@ -1 +1 @@ -Subproject commit 221f0f6356efe2260023208365705ec5d5a7c8f5 +Subproject commit 563dec81c1c538aac0fad465ea933eb2a621a183 From 62d3ae5a20dea3a973bd06acd0abac9fdf694e70 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Mon, 3 Aug 2026 22:46:59 +0800 Subject: [PATCH 83/86] fix(windows): handle conflicting OpenMP and ggml libraries - Allow duplicate OpenMP runtimes in complex environments such as ComfyUI - Stop searching the deprecated /bin directory for ggml dynamic libraries Signed-off-by: JamePeng --- llama_cpp/_ggml.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/llama_cpp/_ggml.py b/llama_cpp/_ggml.py index 9a7dac517..ee1a10187 100644 --- a/llama_cpp/_ggml.py +++ b/llama_cpp/_ggml.py @@ -35,6 +35,12 @@ def _preload_openmp_runtime(): if not _version_at_least("0.3.39"): return + # Some ComfyUI environments include complex software packages and may also contain + # additional OpenMP libraries (such as `libiomp5md.dll`); + # the best approach is to delete the conflicting libraries + # (i.e., OpenMP dynamic libraries that are not the VC143 version). + os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE" + libomp_path = (pathlib.Path(__file__).parent / "lib" / "libomp140.x86_64.dll") if not libomp_path.exists(): @@ -54,7 +60,7 @@ def _preload_openmp_runtime(): libggml_base_path = pathlib.Path(os.path.abspath(os.path.dirname(__file__))) libggml_base_paths = [ libggml_base_path / "lib", - libggml_base_path / "bin", + # libggml_base_path / "bin", # The `bin` path is no longer used as a search path for dynamic ggml libraries. ] # Load bundled OpenMP runtime before ggml-base on Windows. From 9af4ec35e7d25036a187c5b9fbdcbc44290d86b8 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Mon, 3 Aug 2026 23:10:00 +0800 Subject: [PATCH 84/86] feat(internals): expose NextN embedding APIs on `LlamaContext` - Add accessors for NextN and layer input embeddings - Support selecting the NextN layer offset - Expose the auxiliary context handle - Validate layer IDs, offsets, and unavailable outputs Signed-off-by: JamePeng --- llama_cpp/_internals.py | 57 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/llama_cpp/_internals.py b/llama_cpp/_internals.py index 9b37ebcc7..ebb785b3f 100644 --- a/llama_cpp/_internals.py +++ b/llama_cpp/_internals.py @@ -868,6 +868,63 @@ def get_embeddings_seq(self, seq_id: int): self._assert_ctx() return llama_cpp.llama_get_embeddings_seq(self.ctx, seq_id) + def set_embeddings_nextn(self, enabled: bool, masked: bool) -> None: + """ + Set whether the context outputs nextn embeddings or not + If masked == true, output the embeddings only for the tokens with batch.logits != 0 + If masked == false, output the embeddings for all tokens in the batch regardless of batch.logits + """ + self._assert_ctx() + llama_cpp.llama_set_embeddings_nextn(self.ctx, enabled, masked) + + def get_embeddings_nextn(self): + self._assert_ctx() + embeddings = llama_cpp.llama_get_embeddings_nextn(self.ctx) + if not embeddings: + raise RuntimeError("LlamaContext.get_embeddings_nextn: output is unavailable") + return embeddings + + def get_embeddings_nextn_ith(self, i: int): + self._assert_ctx() + embeddings = llama_cpp.llama_get_embeddings_nextn_ith(self.ctx, i) + if not embeddings: + raise RuntimeError( + f"LlamaContext.get_embeddings_nextn_ith: invalid output index {i}" + ) + return embeddings + + def set_embeddings_layer_inp(self, layer_id: int, enabled: bool) -> None: + self._assert_ctx() + if layer_id < 0: + raise ValueError("layer_id must be non-negative") + llama_cpp.llama_set_embeddings_layer_inp(self.ctx, layer_id, enabled) + + def get_embeddings_layer_inp(self, layer_id: int): + self._assert_ctx() + if layer_id < 0: + raise ValueError("layer_id must be non-negative") + embeddings = llama_cpp.llama_get_embeddings_layer_inp(self.ctx, layer_id) + if not embeddings: + raise RuntimeError( + f"LlamaContext.get_embeddings_layer_inp: layer {layer_id} output is unavailable" + ) + return embeddings + + def set_nextn_layer_offset(self, offset: int) -> None: + """ + Select which appended NextN block the DECODER_MTP graph runs (offset past + the trunk: il = n_layer() + offset). Used by the speculative NextN driver to + chain multiple trained NextN heads. Default 0 (first head). + """ + self._assert_ctx() + if offset < 0: + raise ValueError("NextN layer offset must be non-negative") + llama_cpp.llama_set_nextn_layer_offset(self.ctx, offset) + + def get_ctx_other(self): + self._assert_ctx() + return llama_cpp.llama_get_ctx_other(self.ctx) + def reset_timings(self): llama_cpp.llama_perf_context_reset(self.ctx) From 00a84126e0bd168b030a74c1abe5dbb6ffa85827 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Tue, 4 Aug 2026 21:08:51 +0800 Subject: [PATCH 85/86] Update Submodule vendor/llama.cpp 563dec8..1c3c967 Signed-off-by: JamePeng --- llama_cpp/_internals.py | 5 +- llama_cpp/llama_cpp.py | 127 ++++++++++++++++++++++------------------ vendor/llama.cpp | 2 +- 3 files changed, 73 insertions(+), 61 deletions(-) diff --git a/llama_cpp/_internals.py b/llama_cpp/_internals.py index ebb785b3f..7cbd87e4b 100644 --- a/llama_cpp/_internals.py +++ b/llama_cpp/_internals.py @@ -2064,6 +2064,7 @@ def _build_sampler_chain(self): # Note: In some implementations, penalties come before other samplers if CommonSamplerType.PENALTIES in p.samplers: s.add_penalties( + self.n_vocab, p.penalty_last_n, p.penalty_repeat, p.penalty_freq, @@ -3176,8 +3177,8 @@ def add_grammar( c_trigger_tokens, len(trigger_tokens) )) - def add_penalties(self, penalty_last_n: int, penalty_repeat: float, penalty_freq: float, penalty_present: float): - self._add_sampler(llama_cpp.llama_sampler_init_penalties(penalty_last_n, penalty_repeat, penalty_freq, penalty_present)) + def add_penalties(self, n_vocab: int, penalty_last_n: int, penalty_repeat: float, penalty_freq: float, penalty_present: float): + self._add_sampler(llama_cpp.llama_sampler_init_penalties(n_vocab, penalty_last_n, penalty_repeat, penalty_freq, penalty_present)) def add_dry(self, model: LlamaModel, multiplier: float, base: float, allowed_len: int, last_n: int, breakers: List[str]): """DRY (Don't Repeat Yourself) sampler.""" diff --git a/llama_cpp/llama_cpp.py b/llama_cpp/llama_cpp.py index c3b38005d..609e0bb3b 100644 --- a/llama_cpp/llama_cpp.py +++ b/llama_cpp/llama_cpp.py @@ -146,60 +146,63 @@ class llama_vocab_type(enum.IntEnum): # https://github.com/ggml-org/llama.cpp/blob/master/src/llama-vocab.h#L10 # // pre-tokenization types # enum llama_vocab_pre_type { -# LLAMA_VOCAB_PRE_TYPE_DEFAULT = 0, -# LLAMA_VOCAB_PRE_TYPE_LLAMA3 = 1, -# LLAMA_VOCAB_PRE_TYPE_DEEPSEEK_LLM = 2, -# LLAMA_VOCAB_PRE_TYPE_DEEPSEEK_CODER = 3, -# LLAMA_VOCAB_PRE_TYPE_FALCON = 4, -# LLAMA_VOCAB_PRE_TYPE_MPT = 5, -# LLAMA_VOCAB_PRE_TYPE_STARCODER = 6, -# LLAMA_VOCAB_PRE_TYPE_GPT2 = 7, -# LLAMA_VOCAB_PRE_TYPE_REFACT = 8, -# LLAMA_VOCAB_PRE_TYPE_COMMAND_R = 9, -# LLAMA_VOCAB_PRE_TYPE_STABLELM2 = 10, -# LLAMA_VOCAB_PRE_TYPE_QWEN2 = 11, -# LLAMA_VOCAB_PRE_TYPE_OLMO = 12, -# LLAMA_VOCAB_PRE_TYPE_DBRX = 13, -# LLAMA_VOCAB_PRE_TYPE_SMAUG = 14, -# LLAMA_VOCAB_PRE_TYPE_PORO = 15, -# LLAMA_VOCAB_PRE_TYPE_CHATGLM3 = 16, -# LLAMA_VOCAB_PRE_TYPE_CHATGLM4 = 17, -# LLAMA_VOCAB_PRE_TYPE_VIKING = 18, -# LLAMA_VOCAB_PRE_TYPE_JAIS = 19, -# LLAMA_VOCAB_PRE_TYPE_TEKKEN = 20, -# LLAMA_VOCAB_PRE_TYPE_SMOLLM = 21, -# LLAMA_VOCAB_PRE_TYPE_CODESHELL = 22, -# LLAMA_VOCAB_PRE_TYPE_BLOOM = 23, -# LLAMA_VOCAB_PRE_TYPE_GPT3_FINNISH = 24, -# LLAMA_VOCAB_PRE_TYPE_EXAONE = 25, -# LLAMA_VOCAB_PRE_TYPE_CHAMELEON = 26, -# LLAMA_VOCAB_PRE_TYPE_MINERVA = 27, -# LLAMA_VOCAB_PRE_TYPE_DEEPSEEK3_LLM = 28, -# LLAMA_VOCAB_PRE_TYPE_GPT4O = 29, -# LLAMA_VOCAB_PRE_TYPE_SUPERBPE = 30, -# LLAMA_VOCAB_PRE_TYPE_TRILLION = 31, -# LLAMA_VOCAB_PRE_TYPE_BAILINGMOE = 32, -# LLAMA_VOCAB_PRE_TYPE_LLAMA4 = 33, -# LLAMA_VOCAB_PRE_TYPE_PIXTRAL = 34, -# LLAMA_VOCAB_PRE_TYPE_SEED_CODER = 35, -# LLAMA_VOCAB_PRE_TYPE_HUNYUAN = 36, -# LLAMA_VOCAB_PRE_TYPE_KIMI_K2 = 37, -# LLAMA_VOCAB_PRE_TYPE_HUNYUAN_DENSE = 38, -# LLAMA_VOCAB_PRE_TYPE_GROK_2 = 39, -# LLAMA_VOCAB_PRE_TYPE_GRANITE_DOCLING = 40, -# LLAMA_VOCAB_PRE_TYPE_MINIMAX_M2 = 41, -# LLAMA_VOCAB_PRE_TYPE_AFMOE = 42, -# LLAMA_VOCAB_PRE_TYPE_SOLAR_OPEN = 43, -# LLAMA_VOCAB_PRE_TYPE_YOUTU = 44, -# LLAMA_VOCAB_PRE_TYPE_EXAONE_MOE = 45, -# LLAMA_VOCAB_PRE_TYPE_QWEN35 = 46, -# LLAMA_VOCAB_PRE_TYPE_TINY_AYA = 47, -# LLAMA_VOCAB_PRE_TYPE_JOYAI_LLM = 48, -# LLAMA_VOCAB_PRE_TYPE_JAIS2 = 49, -# LLAMA_VOCAB_PRE_TYPE_GEMMA4 = 50, -# LLAMA_VOCAB_PRE_TYPE_SARVAM_MOE = 51, -# LLAMA_VOCAB_PRE_TYPE_MINICPM5 = 52, -# LLAMA_VOCAB_PRE_TYPE_WHITESPACE = 53, +# LLAMA_VOCAB_PRE_TYPE_DEFAULT = 0, +# LLAMA_VOCAB_PRE_TYPE_LLAMA3 = 1, +# LLAMA_VOCAB_PRE_TYPE_DEEPSEEK_LLM = 2, +# LLAMA_VOCAB_PRE_TYPE_DEEPSEEK_CODER = 3, +# LLAMA_VOCAB_PRE_TYPE_FALCON = 4, +# LLAMA_VOCAB_PRE_TYPE_MPT = 5, +# LLAMA_VOCAB_PRE_TYPE_STARCODER = 6, +# LLAMA_VOCAB_PRE_TYPE_GPT2 = 7, +# LLAMA_VOCAB_PRE_TYPE_REFACT = 8, +# LLAMA_VOCAB_PRE_TYPE_COMMAND_R = 9, +# LLAMA_VOCAB_PRE_TYPE_STABLELM2 = 10, +# LLAMA_VOCAB_PRE_TYPE_QWEN2 = 11, +# LLAMA_VOCAB_PRE_TYPE_OLMO = 12, +# LLAMA_VOCAB_PRE_TYPE_DBRX = 13, +# LLAMA_VOCAB_PRE_TYPE_SMAUG = 14, +# LLAMA_VOCAB_PRE_TYPE_PORO = 15, +# LLAMA_VOCAB_PRE_TYPE_CHATGLM3 = 16, +# LLAMA_VOCAB_PRE_TYPE_CHATGLM4 = 17, +# LLAMA_VOCAB_PRE_TYPE_VIKING = 18, +# LLAMA_VOCAB_PRE_TYPE_JAIS = 19, +# LLAMA_VOCAB_PRE_TYPE_TEKKEN = 20, +# LLAMA_VOCAB_PRE_TYPE_SMOLLM = 21, +# LLAMA_VOCAB_PRE_TYPE_CODESHELL = 22, +# LLAMA_VOCAB_PRE_TYPE_BLOOM = 23, +# LLAMA_VOCAB_PRE_TYPE_GPT3_FINNISH = 24, +# LLAMA_VOCAB_PRE_TYPE_EXAONE = 25, +# LLAMA_VOCAB_PRE_TYPE_CHAMELEON = 26, +# LLAMA_VOCAB_PRE_TYPE_MINERVA = 27, +# LLAMA_VOCAB_PRE_TYPE_DEEPSEEK3_LLM = 28, +# LLAMA_VOCAB_PRE_TYPE_GPT4O = 29, +# LLAMA_VOCAB_PRE_TYPE_SUPERBPE = 30, +# LLAMA_VOCAB_PRE_TYPE_TRILLION = 31, +# LLAMA_VOCAB_PRE_TYPE_BAILINGMOE = 32, +# LLAMA_VOCAB_PRE_TYPE_LLAMA4 = 33, +# LLAMA_VOCAB_PRE_TYPE_PIXTRAL = 34, +# LLAMA_VOCAB_PRE_TYPE_SEED_CODER = 35, +# LLAMA_VOCAB_PRE_TYPE_HUNYUAN = 36, +# LLAMA_VOCAB_PRE_TYPE_KIMI_K2 = 37, +# LLAMA_VOCAB_PRE_TYPE_HUNYUAN_DENSE = 38, +# LLAMA_VOCAB_PRE_TYPE_GROK_2 = 39, +# LLAMA_VOCAB_PRE_TYPE_GRANITE_DOCLING = 40, +# LLAMA_VOCAB_PRE_TYPE_MINIMAX_M2 = 41, +# LLAMA_VOCAB_PRE_TYPE_AFMOE = 42, +# LLAMA_VOCAB_PRE_TYPE_SOLAR_OPEN = 43, +# LLAMA_VOCAB_PRE_TYPE_YOUTU = 44, +# LLAMA_VOCAB_PRE_TYPE_EXAONE_MOE = 45, +# LLAMA_VOCAB_PRE_TYPE_QWEN35 = 46, +# LLAMA_VOCAB_PRE_TYPE_TINY_AYA = 47, +# LLAMA_VOCAB_PRE_TYPE_JOYAI_LLM = 48, +# LLAMA_VOCAB_PRE_TYPE_JAIS2 = 49, +# LLAMA_VOCAB_PRE_TYPE_GEMMA4 = 50, +# LLAMA_VOCAB_PRE_TYPE_SARVAM_MOE = 51, +# LLAMA_VOCAB_PRE_TYPE_MINICPM5 = 52, +# LLAMA_VOCAB_PRE_TYPE_WHITESPACE = 53, +# LLAMA_VOCAB_PRE_TYPE_GRANITE_EMB_MULTI = 54, +# LLAMA_VOCAB_PRE_TYPE_MELLUM2 = 55, +# LLAMA_VOCAB_PRE_TYPE_LAGUNA = 56, # }; class llama_vocab_pre_type(enum.IntEnum): LLAMA_VOCAB_PRE_TYPE_DEFAULT = 0 @@ -256,6 +259,9 @@ class llama_vocab_pre_type(enum.IntEnum): LLAMA_VOCAB_PRE_TYPE_SARVAM_MOE = 51 LLAMA_VOCAB_PRE_TYPE_MINICPM5 = 52 LLAMA_VOCAB_PRE_TYPE_WHITESPACE = 53 + LLAMA_VOCAB_PRE_TYPE_GRANITE_EMB_MULTI = 54 + LLAMA_VOCAB_PRE_TYPE_MELLUM2 = 55 + LLAMA_VOCAB_PRE_TYPE_LAGUNA = 56 # // note: these values should be synchronized with ggml_rope @@ -4130,7 +4136,6 @@ def llama_chat_builtin_templates( # struct ggml_tensor * probs; # struct ggml_tensor * sampled; # struct ggml_tensor * candidates; -# int64_t n_vocab; # }; class llama_sampler_data(ctypes.Structure): if TYPE_CHECKING: @@ -4138,14 +4143,12 @@ class llama_sampler_data(ctypes.Structure): probs: ctypes.c_void_p sampled: ctypes.c_void_p candidates: ctypes.c_void_p - n_vocab: ctypes.c_int64 _fields_ = [ ("logits", ctypes.c_void_p), ("probs", ctypes.c_void_p), ("sampled", ctypes.c_void_p), ("candidates", ctypes.c_void_p), - ("n_vocab", ctypes.c_int64), ] @@ -4656,16 +4659,24 @@ def llama_sampler_init_grammar_lazy_patterns( # /// NOTE: Avoid using on the full vocabulary as searching for repeated tokens can become slow. For example, apply top-k or top-p sampling first. # LLAMA_API struct llama_sampler * llama_sampler_init_penalties( +# int32_t n_vocab, # int32_t penalty_last_n, // last n tokens to penalize (0 = disable penalty, -1 = context size) # float penalty_repeat, // must be > 0.0, 1.0 = disabled # float penalty_freq, // must be finite, 0.0 = disabled # float penalty_present); // must be finite, 0.0 = disabled @ctypes_function( "llama_sampler_init_penalties", - [ctypes.c_int32, ctypes.c_float, ctypes.c_float, ctypes.c_float], + [ + ctypes.c_int32, + ctypes.c_int32, + ctypes.c_float, + ctypes.c_float, + ctypes.c_float, + ], llama_sampler_p_ctypes, ) def llama_sampler_init_penalties( + n_vocab: int, penalty_last_n: int, penalty_repeat: float, penalty_freq: float, diff --git a/vendor/llama.cpp b/vendor/llama.cpp index 563dec81c..1c3c9674d 160000 --- a/vendor/llama.cpp +++ b/vendor/llama.cpp @@ -1 +1 @@ -Subproject commit 563dec81c1c538aac0fad465ea933eb2a621a183 +Subproject commit 1c3c9674de4d455f1e571bed808252af54932767 From 4b454d712ed601834c58a95b63adbdb692544975 Mon Sep 17 00:00:00 2001 From: JamePeng Date: Thu, 6 Aug 2026 01:44:47 +0800 Subject: [PATCH 86/86] test vendor fix patch:> Signed-off-by: JamePeng --- vendor/llama.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/llama.cpp b/vendor/llama.cpp index 1c3c9674d..0ec1cfd05 160000 --- a/vendor/llama.cpp +++ b/vendor/llama.cpp @@ -1 +1 @@ -Subproject commit 1c3c9674de4d455f1e571bed808252af54932767 +Subproject commit 0ec1cfd056fb97dbe2a354cca74e5a5091e48674