Add Inkling model support (#31681)

Co-authored-by: Chunan Zeng <zcnrex@gmail.com>
Co-authored-by: Ke Bao <ispobaoke@gmail.com>
Co-authored-by: Yanbin Jiang <jybsuper@gmail.com>
Co-authored-by: Yuhao Yang <47235274+yhyang201@users.noreply.github.com>
Co-authored-by: Qiaolin Yu <qiaolin.yu@radixark.ai>
Co-authored-by: Zhichen Zeng <zczeng@uw.edu>
Co-authored-by: Aurick Qiao <aurick@thinkingmachines.ai>
Co-authored-by: Joseph <jk@thinkingmachines.ai>
This commit is contained in:
Cheng Wan
2026-07-19 22:57:37 -07:00
committed by GitHub
co-authored by Chunan Zeng Ke Bao Yanbin Jiang Yuhao Yang Qiaolin Yu Zhichen Zeng Aurick Qiao Joseph
parent 829e9ce9d5
commit 02236fa38c
279 changed files with 74334 additions and 931 deletions
@@ -124,6 +124,7 @@ class DetokenizerManager(MultiHttpWorkerDetokenizerMixin):
def init_tokenizer(self, server_args: ServerArgs):
if server_args.skip_tokenizer_init:
self.tokenizer = None
self.vocab_size = None
else:
self.tokenizer = get_tokenizer(
server_args.tokenizer_path,
@@ -132,6 +133,10 @@ class DetokenizerManager(MultiHttpWorkerDetokenizerMixin):
revision=server_args.revision,
tokenizer_backend=server_args.tokenizer_backend,
)
try:
self.vocab_size = len(self.tokenizer)
except TypeError:
self.vocab_size = getattr(self.tokenizer, "vocab_size", None)
def init_running_status(self, server_args: ServerArgs):
self.decode_status = LimitedCapacityDict(capacity=DETOKENIZER_MAX_STATES)
@@ -204,6 +209,20 @@ class DetokenizerManager(MultiHttpWorkerDetokenizerMixin):
# If it is embedding model, no detokenization is needed.
return recv_obj
@staticmethod
def _clamp_decode_ids(ids: List[int], vocab_size: Optional[int]) -> List[int]:
"""Map out-of-range token ids to 0 so the tokenizer can decode them.
Multimodal placeholder ids (e.g. Inkling's negative -101/-102, or radix-cache
pad-value hashes) are not real vocab tokens; tiktoken-style backends raise
OverflowError on negative / out-of-range ids. These only appear in the
surrogate-context prefix (before read_offset) and carry no text, and the clamp
is applied identically to surr_ids and read_ids, so the incremental
(read-minus-surr) output text is unchanged.
"""
hi = vocab_size if vocab_size else None
return [t if (0 <= t and (hi is None or t < hi)) else 0 for t in ids]
def _grouped_batch_decode(
self,
ids_list: List[List[int]],
@@ -270,6 +289,7 @@ class DetokenizerManager(MultiHttpWorkerDetokenizerMixin):
def _decode_batch_token_id_output(self, recv_obj: BatchTokenIDOutput):
bs = len(recv_obj.rids)
vocab_size = self.vocab_size
# Initialize decode status
read_ids, surr_ids = [], []
@@ -278,14 +298,18 @@ class DetokenizerManager(MultiHttpWorkerDetokenizerMixin):
if rid not in self.decode_status:
s = DecodeStatus(
decoded_text=recv_obj.decoded_texts[i],
decode_ids=list(recv_obj.decode_ids[i]),
decode_ids=self._clamp_decode_ids(
recv_obj.decode_ids[i], vocab_size
),
surr_offset=0,
read_offset=recv_obj.read_offsets[i],
)
self.decode_status[rid] = s
else:
s = self.decode_status[rid]
s.decode_ids.extend(recv_obj.decode_ids[i])
s.decode_ids.extend(
self._clamp_decode_ids(recv_obj.decode_ids[i], vocab_size)
)
read_ids.append(
self.trim_matched_stop(
+1
View File
@@ -2041,6 +2041,7 @@ class LoadLoRAAdapterFromTensorsReqInput(BaseReq, kw_only=True):
added_tokens_config: Optional[Dict[str, int]] = None
lora_id: Optional[str] = None
load_format: Optional[str] = None
expected_checksums: Optional[Dict[str, str]] = None
def to_ref(self) -> LoRARef:
return LoRARef(
+43 -9
View File
@@ -636,10 +636,33 @@ class PrefillAdder:
alloc = min(extend_input_len, self.rem_chunk_tokens)
else:
alloc = extend_input_len
budget = max(alloc, self.tree_cache.sliding_window_size) + self.page_size
window = self.tree_cache.sliding_window_size
return max(alloc - window, 0) + self._swa_reserved_tokens(swa_host_hit_length)
def _swa_reserved_tokens(self, swa_host_hit_length: int = 0) -> int:
"""SWA tokens a request needs regardless of extend length: the sliding
window (decode headroom) + allocator page slack + the load-back window
charge. Shared floor of _swa_budget_for_req and _swa_chunk_cap."""
reserved = self.tree_cache.sliding_window_size + self.page_size
if swa_host_hit_length > 0:
budget += self.ceil_paged_tokens(swa_host_hit_length)
return budget
reserved += self.ceil_paged_tokens(swa_host_hit_length)
return reserved
def _swa_chunk_cap(self, swa_host_hit_length: int = 0) -> int:
"""Largest page-aligned extend chunk the SWA pool can admit right now,
keeping a sliding window of headroom below rem_swa_tokens; 0 if not
even one page fits. Only valid when is_hybrid_swa is True.
Escape hatch for a request whose budget can never pass the
_swa_budget_for_req gate (extend near/above the pool size, or a large
load-back charge): without shrinking its chunk it would be rejected
forever (head-of-line livelock). Shrinking is sound because past a
chunk boundary only the sliding window stays locked — the rest turns
evictable — so each pass's transient footprint fits the pool."""
cap = int(self.rem_swa_tokens) - self._swa_reserved_tokens(swa_host_hit_length)
if cap <= 0:
return 0
return cap // self.page_size * self.page_size
def _mamba_gap_budget_for_req(self, req: Req) -> int:
"""Shared-gap reservation (full-token-equivalents) for a request's new
@@ -1023,12 +1046,19 @@ class PrefillAdder:
if total_tokens >= self.rem_total_tokens:
return AddReqResult.NO_TOKEN
chunk_tokens_limit = self.rem_chunk_tokens
if self.is_hybrid_swa:
# host-hit prefix is loaded back, not re-prefilled, so the SWA peak is
# driven only by the freshly-prefilled tail (the loaded window is
# charged separately via swa_host_hit_length).
swa_needed = self._swa_budget_for_req(
cand_extend_input_len, swa_host_hit_length=req.swa_host_hit_length
real_input_tokens, swa_host_hit_length=req.swa_host_hit_length
)
if swa_needed >= self.rem_swa_tokens:
return AddReqResult.NO_TOKEN
swa_cap = self._swa_chunk_cap(req.swa_host_hit_length)
if self.rem_chunk_tokens is None or swa_cap <= 0:
return AddReqResult.NO_TOKEN
chunk_tokens_limit = min(self.rem_chunk_tokens, swa_cap)
if (
self.rem_chunk_tokens is None
@@ -1046,11 +1076,15 @@ class PrefillAdder:
return AddReqResult.NO_TOKEN
if self.is_hybrid_swa:
# self.rem_swa_tokens may decrease after the lock acquisition
swa_needed = self._swa_budget_for_req(
cand_extend_input_len, swa_host_hit_length=req.swa_host_hit_length
real_input_tokens, swa_host_hit_length=req.swa_host_hit_length
)
if swa_needed >= self.rem_swa_tokens:
return AddReqResult.NO_TOKEN
swa_cap = self._swa_chunk_cap(req.swa_host_hit_length)
if self.rem_chunk_tokens is None or swa_cap <= 0:
return AddReqResult.NO_TOKEN
chunk_tokens_limit = min(self.rem_chunk_tokens, swa_cap)
if req.needs_host_load_back():
new_indices, req.last_node = self.tree_cache.init_load_back(
@@ -1088,7 +1122,7 @@ class PrefillAdder:
self._add_dllm_req(req, prefix_len)
self._req_inc_lock_ref(req)
elif self.rem_chunk_tokens is None or input_tokens <= self.rem_chunk_tokens:
elif chunk_tokens_limit is None or input_tokens <= chunk_tokens_limit:
# Non-chunked prefill — the whole sequence is committed this iter.
req.set_extend_range(
len(req.prefix_indices), len(req.full_untruncated_fill_ids)
@@ -1108,7 +1142,7 @@ class PrefillAdder:
)
else:
# Make sure at least one page is available
trunc_len = self.rem_chunk_tokens // self.page_size * self.page_size
trunc_len = chunk_tokens_limit // self.page_size * self.page_size
if trunc_len <= 0:
return AddReqResult.OTHER
+12
View File
@@ -1481,6 +1481,18 @@ class Scheduler(
self.schedule_stream = self.device_module.Stream(priority=0)
if self.device == "cpu":
self.schedule_stream.synchronize = lambda: None # No-op for CPU
elif is_cuda() or _is_hip:
# CUDA/HIP streams come from a fixed round-robin pool. Redraw if this
# stream aliases forward_stream, which would eliminate scheduler
# overlap. Only CUDA/HIP streams expose a ``cuda_stream`` handle;
# other accelerators (e.g. NPU/XPU) skip the alias check.
_redraws = 0
while (
self.schedule_stream.cuda_stream == self.forward_stream.cuda_stream
and _redraws < 64
):
self.schedule_stream = self.device_module.Stream(priority=0)
_redraws += 1
# The global WAR barrier fences the scheduler's next shared-buffer write
# on the previous forward's read of the unified memory pool.
self._war_barrier_enabled = is_cuda() or envs.SGLANG_ENABLE_WAR_BARRIER.get()
@@ -160,11 +160,14 @@ class SchedulerInvariantChecker:
self.req_to_token_pool.mamba_pool.size,
)
if leak:
# Page-level leak diagnosis for mamba
free_full_pages = set(
self.token_to_kv_pool_allocator.free_pages.tolist()
+ self.token_to_kv_pool_allocator.release_pages.tolist()
)
# Page-level leak diagnosis for mamba. Allocator flavors without
# page free-lists (free_pages is None) skip the page census — the
# dump must never crash the watchdog thread that calls it.
free_pages = self.token_to_kv_pool_allocator.free_pages
release_pages = self.token_to_kv_pool_allocator.release_pages
if free_pages is None or release_pages is None:
return leak, msg
free_full_pages = set(free_pages.tolist() + release_pages.tolist())
cached_full_pages = set(self.tree_cache.all_values_flatten().tolist())
expected_full_pages = set(
range(1, self.token_to_kv_pool_allocator.size + 1)
+31
View File
@@ -221,6 +221,37 @@ class BaseTpWorker(ABC):
tensors = dict(bucket.reconstruct_tensors())
else:
tensors = MultiprocessingSerializer.deserialize(recv_req.serialized_tensors)
if recv_req.expected_checksums is not None:
import hashlib
exp = recv_req.expected_checksums
mismatch, missing = [], []
for name, want in exp.items():
if name not in tensors:
missing.append(name)
continue
got = hashlib.sha256(
tensors[name]
.detach()
.cpu()
.contiguous()
.flatten()
.view(torch.uint8)
.numpy()
.tobytes()
).hexdigest()
if got != want:
mismatch.append(name)
extra = [n for n in tensors if n not in exp]
if mismatch or missing or extra:
raise RuntimeError(
f"[LORA-CHECK] rank{self.tp_rank} adapter sync MISMATCH of {len(exp)} expected: "
f"{len(mismatch)} value-diff {mismatch[:5]}, {len(missing)} missing {missing[:5]}, "
f"{len(extra)} extra {extra[:5]}"
)
logger.info(
f"[LORA-CHECK] rank{self.tp_rank} adapter sync OK: {len(exp)}/{len(exp)} tensors match (sha256)"
)
result = self.model_runner.load_lora_adapter_from_tensors(
recv_req.to_ref(),
tensors,