2222import asyncio
2323import concurrent .futures
2424import logging
25+ import struct
2526import threading
2627import time
2728import traceback
3132from queue import Queue
3233from typing import Any , Awaitable , Callable , Dict , Optional , Union
3334
35+ import msgpack
3436import msgspec
37+ import xxhash
3538import zmq
3639from prometheus_client import CollectorRegistry
3740
3841from dynamo .common .utils .prometheus import LLMBackendMetrics
39- from dynamo .llm import FpmDirectPublisher , KvEventPublisher , WorkerMetricsPublisher
42+ from dynamo .llm import (
43+ FpmDirectPublisher ,
44+ KvEventPublisher ,
45+ WorkerMetricsPublisher ,
46+ compute_block_hash_for_seq ,
47+ )
4048
4149logging .basicConfig (level = logging .DEBUG )
4250
@@ -107,6 +115,66 @@ def _to_signed_i64(value: int | None) -> int | None:
107115 return value
108116
109117
118+ _XXH3_SEED = 1337
119+ _UINT64_MASK = (1 << 64 ) - 1
120+ # TRT-LLM's PyExecutor/VANILLA path can emit request tokens with an
121+ # engine-added leading special token even when the router request token_ids did
122+ # not contain it. Strip only common BOS-style ids before computing router
123+ # hashes for oversized TRT event blocks.
124+ _TRT_LEADING_SPECIAL_TOKEN_IDS = {0 , 1 , 2 , 128000 , 128001 , 128002 }
125+
126+
127+ def _xxh3_u64 (data : bytes , seed : int = _XXH3_SEED ) -> int :
128+ return xxhash .xxh3_64_intdigest (data , seed = seed & _UINT64_MASK ) & _UINT64_MASK
129+
130+
131+ def _compute_router_sequence_hashes (local_hashes : list [int ]) -> list [int ]:
132+ if not local_hashes :
133+ return []
134+
135+ sequence_hashes = [int (local_hashes [0 ]) & _UINT64_MASK ]
136+ for local_hash in local_hashes [1 :]:
137+ payload = struct .pack (
138+ "<QQ" , sequence_hashes [- 1 ], int (local_hash ) & _UINT64_MASK
139+ )
140+ sequence_hashes .append (_xxh3_u64 (payload ))
141+ return sequence_hashes
142+
143+
144+ def _strip_trt_leading_special_token (
145+ token_ids : list [int ], kv_block_size : int
146+ ) -> tuple [list [int ], bool ]:
147+ if len (token_ids ) <= kv_block_size or not token_ids :
148+ return token_ids , False
149+ if token_ids [0 ] in _TRT_LEADING_SPECIAL_TOKEN_IDS :
150+ stripped = token_ids [1 :]
151+ if len (stripped ) >= kv_block_size :
152+ return stripped , True
153+ return token_ids , False
154+
155+
156+ def _split_oversized_trt_block_for_router (
157+ token_ids : list [int ], kv_block_size : int , lora_name : Optional [str ] = None
158+ ) -> tuple [list [int ], list [int ], list [int ], bool ]:
159+ if kv_block_size <= 0 :
160+ return [], [], [], False
161+
162+ normalized_tokens , stripped_special = _strip_trt_leading_special_token (
163+ token_ids , kv_block_size
164+ )
165+ usable_len = len (normalized_tokens ) - (len (normalized_tokens ) % kv_block_size )
166+ if usable_len <= 0 :
167+ return [], [], [], stripped_special
168+
169+ full_tokens = normalized_tokens [:usable_len ]
170+ local_hashes = compute_block_hash_for_seq (
171+ [int (token ) for token in full_tokens ], kv_block_size , lora_name = lora_name
172+ )
173+ sequence_hashes = _compute_router_sequence_hashes (local_hashes )
174+ num_block_tokens = [kv_block_size ] * len (sequence_hashes )
175+ return full_tokens , num_block_tokens , sequence_hashes , stripped_special
176+
177+
110178class ZmqKvEventPublisher :
111179 """
112180 Pure Python ZMQ PUBLISHER for TensorRT-LLM KV events.
@@ -431,8 +499,14 @@ def __init__(
431499 # A set to store the block hash of partial block (i.e. block containing less than kv_block_size tokens) hashes.
432500 # It is used to prevent sending remove event to kv router since partial blocks are not stored.
433501 self .partial_block_hashes : set [int ] = set ()
502+ # Maps oversized TRT engine block hashes to the synthetic router-visible
503+ # sequence hashes we publish for those blocks, so later remove events
504+ # evict the same hashes from the router indexer.
505+ self .synthetic_block_hashes : dict [int , list [int ]] = {}
434506 self .error_queue : Queue = Queue ()
435507 self ._stop_event = threading .Event ()
508+ self ._cleanup_lock = asyncio .Lock ()
509+ self ._cleanup_started = False
436510 # Track the last engine event_id to assert consecutive event IDs from the engine
437511 self ._last_engine_event_id : Optional [int ] = None
438512
@@ -815,26 +889,59 @@ def _handle_kv_event(self, event):
815889 block_mm_infos : list [dict | None ] = []
816890 kv_block_size = self .kv_block_size
817891 partial_block_hashes = self .partial_block_hashes
892+ synthetic_block_hashes = self .synthetic_block_hashes
893+ lora_name = data .get ("lora_name" )
894+ synthetic_parent_hash = parent_hash
818895 for block in data ["blocks" ]:
819896 block_tokens = block ["tokens" ]
820897 token_num_in_block = len (block_tokens )
821- if token_num_in_block > kv_block_size :
822- logging .error (
823- f"Block contains { token_num_in_block } tokens, which is greater than kv_block_size { kv_block_size } "
824- )
825- return
898+ raw_token_ids = [int (t ["token_id" ]) for t in block_tokens ]
826899 block_hash = _to_signed_i64 (block ["block_hash" ])
827900 if block_hash is None :
828901 logging .warning (
829902 f"Skipping block with None hash containing { token_num_in_block } tokens"
830903 )
831904 continue
905+
906+ if token_num_in_block > kv_block_size :
907+ (
908+ full_tokens ,
909+ synthetic_num_block_tokens ,
910+ synthetic_hashes ,
911+ stripped_special ,
912+ ) = _split_oversized_trt_block_for_router (
913+ raw_token_ids , kv_block_size , lora_name
914+ )
915+ if not synthetic_hashes :
916+ logging .debug (
917+ "Skipping oversized TRT KV block with no full router blocks: "
918+ f"token_count={ token_num_in_block } , kv_block_size={ kv_block_size } "
919+ )
920+ partial_block_hashes .add (block_hash )
921+ continue
922+
923+ signed_hashes = [_to_signed_i64 (h ) for h in synthetic_hashes ]
924+ token_ids .extend (full_tokens )
925+ num_block_tokens .extend (synthetic_num_block_tokens )
926+ block_hashes .extend (h for h in signed_hashes if h is not None )
927+ block_mm_infos .extend ([None ] * len (signed_hashes ))
928+ synthetic_block_hashes [block_hash ] = [
929+ h for h in signed_hashes if h is not None
930+ ]
931+ synthetic_parent_hash = None
932+ logging .debug (
933+ "Split oversized TRT KV block for router indexing: "
934+ f"engine_hash={ block_hash } , token_count={ token_num_in_block } , "
935+ f"published_blocks={ len (signed_hashes )} , stripped_special={ stripped_special } "
936+ )
937+ continue
938+
832939 if token_num_in_block < kv_block_size :
833940 partial_block_hashes .add (block_hash )
834941 break
835942 num_block_tokens .append (token_num_in_block )
836943 block_hashes .append (block_hash )
837- token_ids .extend (int ( t [ "token_id" ]) for t in block_tokens )
944+ token_ids .extend (raw_token_ids )
838945
839946 mm_keys = block .get ("mm_keys" )
840947 if mm_keys :
@@ -856,14 +963,18 @@ def _handle_kv_event(self, event):
856963 else :
857964 block_mm_infos .append (None )
858965
859- lora_name = data .get ("lora_name" )
966+ if not block_hashes :
967+ logging .debug (
968+ f"Skipping stored KV event { event_id } with no full router blocks"
969+ )
970+ return
860971
861972 # Get attention_dp_rank from event (TRT-LLM includes this in KVCacheEvent)
862973 # Default to 0 for backwards compatibility with older TRT-LLM versions
863974 attention_dp_rank = event .get ("attention_dp_rank" , 0 )
864975
865976 logging .debug (
866- f"publish stored event: engine_event_id: { event_id } , attention_dp_rank: { attention_dp_rank } , token_ids: { token_ids } , num_block_tokens: { num_block_tokens } , block_hashes: { block_hashes } , lora_name: { lora_name } , parent_hash: { parent_hash } "
977+ f"publish stored event: engine_event_id: { event_id } , attention_dp_rank: { attention_dp_rank } , token_ids: { token_ids } , num_block_tokens: { num_block_tokens } , block_hashes: { block_hashes } , lora_name: { lora_name } , parent_hash: { synthetic_parent_hash } "
867978 )
868979 # Publish to ZMQ if consolidator is enabled, otherwise publish to NATS
869980 # Note: event_id is managed internally by the publisher (monotonic counter per dp_rank)
@@ -873,7 +984,7 @@ def _handle_kv_event(self, event):
873984 token_ids ,
874985 num_block_tokens ,
875986 block_hashes ,
876- parent_hash ,
987+ synthetic_parent_hash ,
877988 block_mm_infos ,
878989 attention_dp_rank ,
879990 lora_name ,
@@ -887,7 +998,7 @@ def _handle_kv_event(self, event):
887998 token_ids ,
888999 num_block_tokens ,
8891000 block_hashes ,
890- parent_hash ,
1001+ synthetic_parent_hash ,
8911002 block_mm_infos ,
8921003 lora_name = lora_name ,
8931004 )
@@ -903,6 +1014,10 @@ def _handle_kv_event(self, event):
9031014 block_hash = _to_signed_i64 (block_hash )
9041015 if block_hash is None :
9051016 continue
1017+ synthetic_hashes = self .synthetic_block_hashes .pop (block_hash , None )
1018+ if synthetic_hashes :
1019+ removed_block_hashes .extend (synthetic_hashes )
1020+ continue
9061021 if block_hash in self .partial_block_hashes :
9071022 logging .debug (
9081023 f"Skipping removing block hash { block_hash } since it is a partial block"
@@ -962,38 +1077,47 @@ def check_error_queue(self) -> Optional[Exception]:
9621077
9631078 async def cleanup (self ) -> None :
9641079 """Cleanup threads and resources"""
965- self ._stop_event .set ()
966- # Add timeout to prevent hanging
967- cleanup_timeout = 5.0 # seconds
968-
969- if self .publish_stats_thread and self .publish_stats_thread .is_alive ():
970- self .publish_stats_thread .stop ()
971- self .publish_stats_thread .join (timeout = cleanup_timeout )
972- if self .publish_stats_thread .is_alive ():
973- logging .warning ("Stats thread did not stop within timeout" )
974-
975- if (
976- self .publish_kv_cache_events_thread
977- and self .publish_kv_cache_events_thread .is_alive ()
978- ):
979- self .publish_kv_cache_events_thread .stop ()
980- self .publish_kv_cache_events_thread .join (timeout = cleanup_timeout )
981- if self .publish_kv_cache_events_thread .is_alive ():
982- logging .warning ("KV cache events thread did not stop within timeout" )
983-
984- # Shutdown ZMQ publisher if it exists
985- if self .zmq_kv_event_publisher :
986- self .zmq_kv_event_publisher .shutdown ()
1080+ async with self ._cleanup_lock :
1081+ if self ._cleanup_started :
1082+ return
1083+ self ._cleanup_started = True
1084+
1085+ self ._stop_event .set ()
1086+ # Add timeout to prevent hanging
1087+ cleanup_timeout = 5.0 # seconds
1088+
1089+ thread_warnings = (
1090+ (self .publish_stats_thread , "Stats thread did not stop within timeout" ),
1091+ (
1092+ self .publish_kv_cache_events_thread ,
1093+ "KV cache events thread did not stop within timeout" ,
1094+ ),
1095+ )
1096+ live_threads = [
1097+ (thread , warning )
1098+ for thread , warning in thread_warnings
1099+ if thread and thread .is_alive ()
1100+ ]
1101+ for thread , _warning in live_threads :
1102+ thread .stop ()
1103+ for thread , warning in live_threads :
1104+ thread .join (timeout = cleanup_timeout )
1105+ if thread .is_alive ():
1106+ logging .warning (warning )
1107+
1108+ # Shutdown ZMQ publisher if it exists
1109+ if self .zmq_kv_event_publisher :
1110+ self .zmq_kv_event_publisher .shutdown ()
9871111
988- # Shutdown FpmDirectPublisher (stops the per-rank serialization tasks
989- # and the event-plane publisher task on the Rust side). PyO3 surfaces
990- # shutdown failures as PyRuntimeError; narrower catch keeps real
991- # programming errors visible.
992- if self .fpm_publisher is not None :
993- try :
994- self .fpm_publisher .shutdown ()
995- except RuntimeError as e :
996- logging .warning (f"FpmDirectPublisher shutdown failed: { e } " )
1112+ # Shutdown FpmDirectPublisher (stops the per-rank serialization tasks
1113+ # and the event-plane publisher task on the Rust side). PyO3 surfaces
1114+ # shutdown failures as PyRuntimeError; narrower catch keeps real
1115+ # programming errors visible.
1116+ if self .fpm_publisher is not None :
1117+ try :
1118+ self .fpm_publisher .shutdown ()
1119+ except RuntimeError as e :
1120+ logging .warning (f"FpmDirectPublisher shutdown failed: { e } " )
9971121
9981122 def update_max_window_size (self , event : dict ) -> None :
9991123 if "window_size" in event :
0 commit comments