Skip to content

Commit ed502c5

Browse files
committed
perf (TFFT): Optimize longest_token_prefix with Numpy SIMD and fast-fail probe
- Vectorization: Replaced standard Python zip loop with Numpy SIMD comparison for high-performance context matching. - Fast Exit: Added an O(1) probe check (a[0] != b[0]) to eliminate Numpy conversion overhead on mismatches. Making the "Time To First Token" virtually instantaneous for cached sessions. - Memory Optimization: Only the intersection of the two sequences (`[:min_len]`) is converted to Numpy arrays, minimizing memory allocation. - Result: Achieved ~5x speedup (129ms -> 25ms) in KV cache reuse scenarios and ~2.5x speedup (554.23ms -> 201.62ms) in load time while maintaining stability on long contexts. This change significantly reduces latency in RAG and chat applications on long contexts. Signed-off-by: JamePeng <jame_peng@sina.com>
1 parent be6277e commit ed502c5

1 file changed

Lines changed: 52 additions & 14 deletions

File tree

llama_cpp/llama.py

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,12 +1006,7 @@ def generate(
10061006

10071007
# Check for kv cache prefix match
10081008
if reset and self.n_tokens > 0:
1009-
longest_prefix = 0
1010-
for a, b in zip(self._input_ids, tokens[:-1]):
1011-
if a == b:
1012-
longest_prefix += 1
1013-
else:
1014-
break
1009+
longest_prefix = self.longest_token_prefix(self._input_ids.tolist(), tokens[:-1])
10151010
if longest_prefix > 0:
10161011
reset = False
10171012
tokens = tokens[longest_prefix:]
@@ -2551,14 +2546,57 @@ def logits_to_logprobs(
25512546
return subtract_maxs - out
25522547

25532548
@staticmethod
2554-
def longest_token_prefix(a: Sequence[int], b: Sequence[int]):
2555-
longest_prefix = 0
2556-
for _a, _b in zip(a, b):
2557-
if _a == _b:
2558-
longest_prefix += 1
2559-
else:
2560-
break
2561-
return longest_prefix
2549+
def longest_token_prefix(current_ids: Sequence[int], new_tokens: Sequence[int]) -> int:
2550+
"""
2551+
Calculates the length of the longest common prefix between two token sequences.
2552+
2553+
This implementation uses NumPy for vectorized comparison (SIMD), which offers
2554+
significant performance improvements (up to 2x~100x+ speedup) over standard Python
2555+
loops for long contexts (e.g., RAG or chat history).
2556+
2557+
Args:
2558+
current_ids: The existing token sequence (e.g., KV cache).
2559+
new_tokens: The new input token sequence.
2560+
2561+
Returns:
2562+
int: The number of matching tokens from the start.
2563+
"""
2564+
# Fast exit for empty sequences to avoid unnecessary processing
2565+
if not current_ids or not new_tokens:
2566+
return 0
2567+
2568+
# Probe inspection: Use Python to quickly compare the first token
2569+
# If the tokens are different from the beginning, return immediately to avoid any NumPy overhead.
2570+
if current_ids[0] != new_tokens[0]:
2571+
return 0
2572+
2573+
# Determine the comparison range (limited by the shorter sequence)
2574+
min_len = min(len(current_ids), len(new_tokens))
2575+
if min_len == 0:
2576+
return 0
2577+
2578+
# Accelerating SIMD for Large Data Volumes
2579+
# Only transform necessary slices, avoid processing irrelevant data
2580+
# Use asarray to ensure zero-copy (if the input is already an array)
2581+
current_ids_array = np.asarray(current_ids[:min_len], dtype=np.int32)
2582+
new_tokens_array = np.asarray(new_tokens[:min_len], dtype=np.int32)
2583+
2584+
# Perform vectorized element-wise comparison (SIMD instruction set usage)
2585+
# Creates a boolean array where True indicates a match (e.g., [True, True, False, ...])
2586+
matches = (current_ids_array == new_tokens_array)
2587+
2588+
# Find the index of the first mismatch efficiently
2589+
# np.argmin returns the index of the minimum value. Since False (0) < True (1),
2590+
# this locates the first False value (mismatch).
2591+
idx = np.argmin(matches)
2592+
2593+
# Handle the "Full Match" edge case
2594+
# This means that the match between the two arrays will still result in True in the end.
2595+
if matches[idx]:
2596+
return int(min_len)
2597+
2598+
# Otherwise, idx is the position of the first mismatch, which equals the prefix length.
2599+
return int(idx)
25622600

25632601
@classmethod
25642602
def from_pretrained(

0 commit comments

Comments
 (0)