Skip to content

Commit 79f31a9

Browse files
committed
perf: optimize tokenization and detokenization logic
Refactor `tokenize`, `token_to_piece`, and `detokenize` methods in `_internals.py` to significantly reduce Python overhead and improve stability. Key changes: - Replace O(N) Python loops in `detokenize` with `llama.cpp` native batch C-API (`llama_detokenize`). - Implement dynamic buffer allocation to safely handle arbitrary token lengths (removing the hardcoded 32-byte limit). - Add automatic buffer resizing for `tokenize` to prevent overflow errors. Performance observations (based on user benchmarks): - Small Batch Processing (127 tokens): Latency reduced from ~117ms to ~37ms (approx. 3.1x speedup in processing loop). - Large Batch Processing (2420 tokens): Throughput improved from ~6905 t/s to ~8258 t/s. - General Latency: Total execution time for standard chat scenarios reduced by ~1.1s (from 8.4s to 7.3s). Signed-off-by: JamePeng <jame_peng@sina.com>
1 parent 457d711 commit 79f31a9

1 file changed

Lines changed: 63 additions & 24 deletions

File tree

llama_cpp/_internals.py

Lines changed: 63 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -200,46 +200,85 @@ def get_add_sep(self) -> bool:
200200
# Tokenization
201201

202202
def tokenize(self, text: bytes, add_bos: bool, special: bool):
203-
n_ctx = self.n_ctx_train()
204-
tokens = (llama_cpp.llama_token * n_ctx)()
203+
"""
204+
Tokenize a string.
205+
Optimized to use dynamic buffer allocation.
206+
"""
207+
n_tokens_alloc = len(text) + 2
208+
tokens = (llama_cpp.llama_token * n_tokens_alloc)()
209+
205210
n_tokens = llama_cpp.llama_tokenize(
206-
self.vocab, text, len(text), tokens, n_ctx, add_bos, special
211+
self.vocab, text, len(text), tokens, n_tokens_alloc, add_bos, special
207212
)
213+
214+
# If the buffer is insufficient (returns a negative number), reallocate the buffer.
208215
if n_tokens < 0:
209-
n_tokens = abs(n_tokens)
210-
tokens = (llama_cpp.llama_token * n_tokens)()
216+
n_tokens_alloc = -n_tokens
217+
tokens = (llama_cpp.llama_token * n_tokens_alloc)()
211218
n_tokens = llama_cpp.llama_tokenize(
212-
self.vocab, text, len(text), tokens, n_tokens, add_bos, special
219+
self.vocab, text, len(text), tokens, n_tokens_alloc, add_bos, special
213220
)
214221
if n_tokens < 0:
215222
raise RuntimeError(
216223
f'Failed to tokenize: text="{text}" n_tokens={n_tokens}'
217224
)
225+
226+
# return a buffer of n_tokens size.
218227
return list(tokens[:n_tokens])
219228

220229
def token_to_piece(self, token: int, special: bool = False) -> bytes:
221-
buf = ctypes.create_string_buffer(32)
222-
llama_cpp.llama_token_to_piece(self.vocab, token, buf, 32, 0, special)
223-
return bytes(buf)
230+
"""
231+
Convert a single token to bytes.
232+
Optimized to handle dynamic resizing for ultra-long tokens.
233+
"""
234+
size = 32
235+
buf = (ctypes.c_char * size)()
236+
n = llama_cpp.llama_token_to_piece(self.vocab, token, buf, size, 0, special)
237+
238+
# If the token is very long (returns a negative number), redistribute it according to the returned size.
239+
if n < 0:
240+
size = -n
241+
buf = (ctypes.c_char * size)()
242+
n = llama_cpp.llama_token_to_piece(self.vocab, token, buf, size, 0, special)
243+
if n < 0:
244+
raise RuntimeError(f"Failed to get piece for token {token}")
245+
246+
# return a buffer of n size.
247+
return bytes(buf[:n])
224248

225249
def detokenize(self, tokens: List[int], special: bool = False) -> bytes:
226-
output = b""
227-
size = 32
228-
buffer = (ctypes.c_char * size)()
229-
for token in tokens:
230-
n = llama_cpp.llama_token_to_piece(
231-
self.vocab, llama_cpp.llama_token(token), buffer, size, 0, special
232-
)
233-
assert n <= size
234-
output += bytes(buffer[:n])
235-
# NOTE: Llama1 models automatically added a space at the start of the prompt
236-
# this line removes a leading space if the first token is a beginning of sentence token
237-
return (
238-
output[1:]
239-
if len(tokens) > 0 and tokens[0] == self.token_bos() and output[0:1] == b" "
240-
else output
250+
"""
251+
Convert a list of tokens to bytes.
252+
Optimized to handle dynamic resizing for ultra-long tokens.
253+
"""
254+
if not tokens:
255+
return b""
256+
257+
n_tokens = len(tokens)
258+
# Convert a Python list to a C int array
259+
tokens_array = (llama_cpp.llama_token * n_tokens)(*tokens)
260+
261+
# Initial buffer size estimation
262+
buffer_size = max(n_tokens, 64)
263+
buffer = (ctypes.c_char * buffer_size)()
264+
265+
n_chars = llama_cpp.llama_detokenize(
266+
self.vocab, tokens_array, n_tokens, buffer, buffer_size, False, special
241267
)
242268

269+
# If the buffer is insufficient, expand it and retry.
270+
if n_chars < 0:
271+
buffer_size = -n_chars
272+
buffer = (ctypes.c_char * buffer_size)()
273+
n_chars = llama_cpp.llama_detokenize(
274+
self.vocab, tokens_array, n_tokens, buffer, buffer_size, False, special
275+
)
276+
if n_chars < 0:
277+
raise RuntimeError("Failed to detokenize")
278+
279+
return bytes(buffer[:n_chars])
280+
281+
243282
# Extra
244283
def metadata(self) -> Dict[str, str]:
245284
metadata: Dict[str, str] = {}

0 commit comments

Comments
 (0)