2222import asyncio
2323import concurrent .futures
2424import logging
25+ import struct
2526import threading
2627import time
2728import traceback
3233from typing import Any , Awaitable , Callable , Dict , Optional , Union
3334
3435import msgspec
36+ import xxhash
3537import zmq
3638from prometheus_client import CollectorRegistry
3739
3840from dynamo .common .utils .prometheus import LLMBackendMetrics
39- from dynamo .llm import FpmDirectPublisher , KvEventPublisher , WorkerMetricsPublisher
41+ from dynamo .llm import (
42+ FpmDirectPublisher ,
43+ KvEventPublisher ,
44+ WorkerMetricsPublisher ,
45+ compute_block_hash_for_seq ,
46+ )
4047
4148logger = logging .getLogger (__name__ )
4249
@@ -107,6 +114,66 @@ def _to_signed_i64(value: int | None) -> int | None:
107114 return value
108115
109116
117+ _XXH3_SEED = 1337
118+ _UINT64_MASK = (1 << 64 ) - 1
119+ # TRT-LLM's PyExecutor/VANILLA path can emit request tokens with an
120+ # engine-added leading special token even when the router request token_ids did
121+ # not contain it. Strip only common BOS-style ids before computing router
122+ # hashes for oversized TRT event blocks.
123+ _TRT_LEADING_SPECIAL_TOKEN_IDS = {0 , 1 , 2 , 128000 , 128001 , 128002 }
124+
125+
126+ def _xxh3_u64 (data : bytes , seed : int = _XXH3_SEED ) -> int :
127+ return xxhash .xxh3_64_intdigest (data , seed = seed & _UINT64_MASK ) & _UINT64_MASK
128+
129+
130+ def _compute_router_sequence_hashes (local_hashes : list [int ]) -> list [int ]:
131+ if not local_hashes :
132+ return []
133+
134+ sequence_hashes = [int (local_hashes [0 ]) & _UINT64_MASK ]
135+ for local_hash in local_hashes [1 :]:
136+ payload = struct .pack (
137+ "<QQ" , sequence_hashes [- 1 ], int (local_hash ) & _UINT64_MASK
138+ )
139+ sequence_hashes .append (_xxh3_u64 (payload ))
140+ return sequence_hashes
141+
142+
143+ def _strip_trt_leading_special_token (
144+ token_ids : list [int ], kv_block_size : int
145+ ) -> tuple [list [int ], bool ]:
146+ if len (token_ids ) <= kv_block_size or not token_ids :
147+ return token_ids , False
148+ if token_ids [0 ] in _TRT_LEADING_SPECIAL_TOKEN_IDS :
149+ stripped = token_ids [1 :]
150+ if len (stripped ) >= kv_block_size :
151+ return stripped , True
152+ return token_ids , False
153+
154+
155+ def _split_oversized_trt_block_for_router (
156+ token_ids : list [int ], kv_block_size : int , lora_name : Optional [str ] = None
157+ ) -> tuple [list [int ], list [int ], list [int ], bool ]:
158+ if kv_block_size <= 0 :
159+ return [], [], [], False
160+
161+ normalized_tokens , stripped_special = _strip_trt_leading_special_token (
162+ token_ids , kv_block_size
163+ )
164+ usable_len = len (normalized_tokens ) - (len (normalized_tokens ) % kv_block_size )
165+ if usable_len <= 0 :
166+ return [], [], [], stripped_special
167+
168+ full_tokens = normalized_tokens [:usable_len ]
169+ local_hashes = compute_block_hash_for_seq (
170+ [int (token ) for token in full_tokens ], kv_block_size , lora_name = lora_name
171+ )
172+ sequence_hashes = _compute_router_sequence_hashes (local_hashes )
173+ num_block_tokens = [kv_block_size ] * len (sequence_hashes )
174+ return full_tokens , num_block_tokens , sequence_hashes , stripped_special
175+
176+
110177class ZmqKvEventPublisher :
111178 """
112179 Pure Python ZMQ PUBLISHER for TensorRT-LLM KV events.
@@ -426,11 +493,17 @@ def __init__(
426493 # A set to store the block hash of partial block (i.e. block containing less than kv_block_size tokens) hashes.
427494 # It is used to prevent sending remove event to kv router since partial blocks are not stored.
428495 self .partial_block_hashes : set [int ] = set ()
496+ # Maps oversized TRT engine block hashes to the synthetic router-visible
497+ # sequence hashes we publish for those blocks, so later remove events
498+ # evict the same hashes from the router indexer.
499+ self .synthetic_block_hashes : dict [int , list [int ]] = {}
429500 self .error_queue : Queue = Queue ()
430501 self ._stop_event = threading .Event ()
431502 # Track the last engine event_id per attention-DP rank. TRT-LLM emits
432503 # independent rank-local sequences before gathering them on rank 0.
433504 self ._last_engine_event_id_by_rank : dict [int , int ] = {}
505+ self ._cleanup_lock = asyncio .Lock ()
506+ self ._cleanup_started = False
434507
435508 # Initialize ZMQ publisher if endpoint is provided (consolidator enabled)
436509 if zmq_endpoint :
@@ -826,26 +899,59 @@ def _handle_kv_event(self, event):
826899 block_mm_infos : list [dict | None ] = []
827900 kv_block_size = self .kv_block_size
828901 partial_block_hashes = self .partial_block_hashes
902+ synthetic_block_hashes = self .synthetic_block_hashes
903+ lora_name = data .get ("lora_name" )
904+ synthetic_parent_hash = parent_hash
829905 for block in data ["blocks" ]:
830906 block_tokens = block ["tokens" ]
831907 token_num_in_block = len (block_tokens )
832- if token_num_in_block > kv_block_size :
833- logging .error (
834- f"Block contains { token_num_in_block } tokens, which is greater than kv_block_size { kv_block_size } "
835- )
836- return
908+ raw_token_ids = [int (t ["token_id" ]) for t in block_tokens ]
837909 block_hash = _to_signed_i64 (block ["block_hash" ])
838910 if block_hash is None :
839911 logging .warning (
840912 f"Skipping block with None hash containing { token_num_in_block } tokens"
841913 )
842914 continue
915+
916+ if token_num_in_block > kv_block_size :
917+ (
918+ full_tokens ,
919+ synthetic_num_block_tokens ,
920+ synthetic_hashes ,
921+ stripped_special ,
922+ ) = _split_oversized_trt_block_for_router (
923+ raw_token_ids , kv_block_size , lora_name
924+ )
925+ if not synthetic_hashes :
926+ logging .debug (
927+ "Skipping oversized TRT KV block with no full router blocks: "
928+ f"token_count={ token_num_in_block } , kv_block_size={ kv_block_size } "
929+ )
930+ partial_block_hashes .add (block_hash )
931+ continue
932+
933+ signed_hashes = [_to_signed_i64 (h ) for h in synthetic_hashes ]
934+ token_ids .extend (full_tokens )
935+ num_block_tokens .extend (synthetic_num_block_tokens )
936+ block_hashes .extend (h for h in signed_hashes if h is not None )
937+ block_mm_infos .extend ([None ] * len (signed_hashes ))
938+ synthetic_block_hashes [block_hash ] = [
939+ h for h in signed_hashes if h is not None
940+ ]
941+ synthetic_parent_hash = None
942+ logging .debug (
943+ "Split oversized TRT KV block for router indexing: "
944+ f"engine_hash={ block_hash } , token_count={ token_num_in_block } , "
945+ f"published_blocks={ len (signed_hashes )} , stripped_special={ stripped_special } "
946+ )
947+ continue
948+
843949 if token_num_in_block < kv_block_size :
844950 partial_block_hashes .add (block_hash )
845951 break
846952 num_block_tokens .append (token_num_in_block )
847953 block_hashes .append (block_hash )
848- token_ids .extend (int ( t [ "token_id" ]) for t in block_tokens )
954+ token_ids .extend (raw_token_ids )
849955
850956 mm_keys = block .get ("mm_keys" )
851957 if mm_keys :
@@ -867,7 +973,11 @@ def _handle_kv_event(self, event):
867973 else :
868974 block_mm_infos .append (None )
869975
870- lora_name = data .get ("lora_name" )
976+ if not block_hashes :
977+ logging .debug (
978+ f"Skipping stored KV event { event_id } with no full router blocks"
979+ )
980+ return
871981
872982 logger .debug (
873983 "Publishing stored KV event: engine_event_id=%s "
@@ -888,7 +998,7 @@ def _handle_kv_event(self, event):
888998 token_ids ,
889999 num_block_tokens ,
8901000 block_hashes ,
891- parent_hash ,
1001+ synthetic_parent_hash ,
8921002 block_mm_infos ,
8931003 attention_dp_rank ,
8941004 lora_name ,
@@ -902,7 +1012,7 @@ def _handle_kv_event(self, event):
9021012 token_ids ,
9031013 num_block_tokens ,
9041014 block_hashes ,
905- parent_hash ,
1015+ synthetic_parent_hash ,
9061016 block_mm_infos ,
9071017 lora_name = lora_name ,
9081018 )
@@ -919,6 +1029,10 @@ def _handle_kv_event(self, event):
9191029 block_hash = _to_signed_i64 (block_hash )
9201030 if block_hash is None :
9211031 continue
1032+ synthetic_hashes = self .synthetic_block_hashes .pop (block_hash , None )
1033+ if synthetic_hashes :
1034+ removed_block_hashes .extend (synthetic_hashes )
1035+ continue
9221036 if block_hash in self .partial_block_hashes :
9231037 self .partial_block_hashes .remove (block_hash )
9241038 skipped_partial_blocks += 1
@@ -981,38 +1095,47 @@ def check_error_queue(self) -> Optional[Exception]:
9811095
9821096 async def cleanup (self ) -> None :
9831097 """Cleanup threads and resources"""
984- self ._stop_event .set ()
985- # Add timeout to prevent hanging
986- cleanup_timeout = 5.0 # seconds
987-
988- if self .publish_stats_thread and self .publish_stats_thread .is_alive ():
989- self .publish_stats_thread .stop ()
990- self .publish_stats_thread .join (timeout = cleanup_timeout )
991- if self .publish_stats_thread .is_alive ():
992- logging .warning ("Stats thread did not stop within timeout" )
993-
994- if (
995- self .publish_kv_cache_events_thread
996- and self .publish_kv_cache_events_thread .is_alive ()
997- ):
998- self .publish_kv_cache_events_thread .stop ()
999- self .publish_kv_cache_events_thread .join (timeout = cleanup_timeout )
1000- if self .publish_kv_cache_events_thread .is_alive ():
1001- logging .warning ("KV cache events thread did not stop within timeout" )
1002-
1003- # Shutdown ZMQ publisher if it exists
1004- if self .zmq_kv_event_publisher :
1005- self .zmq_kv_event_publisher .shutdown ()
1098+ async with self ._cleanup_lock :
1099+ if self ._cleanup_started :
1100+ return
1101+ self ._cleanup_started = True
1102+
1103+ self ._stop_event .set ()
1104+ # Add timeout to prevent hanging
1105+ cleanup_timeout = 5.0 # seconds
1106+
1107+ thread_warnings = (
1108+ (self .publish_stats_thread , "Stats thread did not stop within timeout" ),
1109+ (
1110+ self .publish_kv_cache_events_thread ,
1111+ "KV cache events thread did not stop within timeout" ,
1112+ ),
1113+ )
1114+ live_threads = [
1115+ (thread , warning )
1116+ for thread , warning in thread_warnings
1117+ if thread and thread .is_alive ()
1118+ ]
1119+ for thread , _warning in live_threads :
1120+ thread .stop ()
1121+ for thread , warning in live_threads :
1122+ thread .join (timeout = cleanup_timeout )
1123+ if thread .is_alive ():
1124+ logging .warning (warning )
1125+
1126+ # Shutdown ZMQ publisher if it exists
1127+ if self .zmq_kv_event_publisher :
1128+ self .zmq_kv_event_publisher .shutdown ()
10061129
1007- # Shutdown FpmDirectPublisher (stops the per-rank serialization tasks
1008- # and the event-plane publisher task on the Rust side). PyO3 surfaces
1009- # shutdown failures as PyRuntimeError; narrower catch keeps real
1010- # programming errors visible.
1011- if self .fpm_publisher is not None :
1012- try :
1013- self .fpm_publisher .shutdown ()
1014- except RuntimeError as e :
1015- logging .warning (f"FpmDirectPublisher shutdown failed: { e } " )
1130+ # Shutdown FpmDirectPublisher (stops the per-rank serialization tasks
1131+ # and the event-plane publisher task on the Rust side). PyO3 surfaces
1132+ # shutdown failures as PyRuntimeError; narrower catch keeps real
1133+ # programming errors visible.
1134+ if self .fpm_publisher is not None :
1135+ try :
1136+ self .fpm_publisher .shutdown ()
1137+ except RuntimeError as e :
1138+ logging .warning (f"FpmDirectPublisher shutdown failed: { e } " )
10161139
10171140 def update_max_window_size (self , event : dict ) -> None :
10181141 if "window_size" in event :
0 commit comments