Observability enhancement for HiCache (#32388)
This commit is contained in:
@@ -163,6 +163,11 @@ class HiCacheAck(NamedTuple):
|
|||||||
node_ids: List[int]
|
node_ids: List[int]
|
||||||
num_tokens: int = 0
|
num_tokens: int = 0
|
||||||
timing_enabled: bool = False
|
timing_enabled: bool = False
|
||||||
|
# Tokens transferred per host pool (PoolName value -> count).
|
||||||
|
num_tokens_by_pool: Optional[dict[str, int]] = None
|
||||||
|
# Total bytes moved by the op across all pools, including draft piggyback
|
||||||
|
# and sidecar transfers that the per-pool token counts exclude.
|
||||||
|
num_bytes: int = 0
|
||||||
|
|
||||||
|
|
||||||
class StorageOperation:
|
class StorageOperation:
|
||||||
@@ -714,11 +719,12 @@ class HiCacheController:
|
|||||||
self.write_queue.clear()
|
self.write_queue.clear()
|
||||||
|
|
||||||
start_event = device_module.Event()
|
start_event = device_module.Event()
|
||||||
finish_event = device_module.Event()
|
ack_start_event, ack_finish_event, timing_enabled = make_timing_event_pair()
|
||||||
|
|
||||||
start_event.record()
|
start_event.record()
|
||||||
with device_module.stream(self.write_stream):
|
with device_module.stream(self.write_stream):
|
||||||
start_event.wait(self.write_stream)
|
start_event.wait(self.write_stream)
|
||||||
|
ack_start_event.record()
|
||||||
self.mem_pool_host.backup_from_device_all_layer(
|
self.mem_pool_host.backup_from_device_all_layer(
|
||||||
self.mem_pool_device, host_indices, device_indices, self.io_backend
|
self.mem_pool_device, host_indices, device_indices, self.io_backend
|
||||||
)
|
)
|
||||||
@@ -729,7 +735,7 @@ class HiCacheController:
|
|||||||
device_indices,
|
device_indices,
|
||||||
self.io_backend,
|
self.io_backend,
|
||||||
)
|
)
|
||||||
finish_event.record()
|
ack_finish_event.record()
|
||||||
# NOTE: We must save the host indices and device indices here,
|
# NOTE: We must save the host indices and device indices here,
|
||||||
# this is because we need to guarantee that these tensors are
|
# this is because we need to guarantee that these tensors are
|
||||||
# still alive when the write stream is executing.
|
# still alive when the write stream is executing.
|
||||||
@@ -738,7 +744,25 @@ class HiCacheController:
|
|||||||
if device_indices.is_cuda:
|
if device_indices.is_cuda:
|
||||||
device_indices.record_stream(self.write_stream)
|
device_indices.record_stream(self.write_stream)
|
||||||
|
|
||||||
self.ack_write_queue.append(HiCacheAck(start_event, finish_event, op.node_ids))
|
self.ack_write_queue.append(
|
||||||
|
HiCacheAck(
|
||||||
|
start_event=ack_start_event,
|
||||||
|
finish_event=ack_finish_event,
|
||||||
|
node_ids=op.node_ids,
|
||||||
|
num_tokens=len(op.device_indices),
|
||||||
|
timing_enabled=timing_enabled,
|
||||||
|
num_tokens_by_pool={PoolName.KV.value: len(op.device_indices)},
|
||||||
|
num_bytes=self._transfer_num_bytes(op),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _transfer_num_bytes(self, op: CacheOperation) -> int:
|
||||||
|
"""Total bytes moved by a merged transfer op (draft piggyback included)."""
|
||||||
|
num_tokens = len(op.device_indices)
|
||||||
|
num_bytes = num_tokens * self.mem_pool_host.size_per_token
|
||||||
|
if self.has_draft:
|
||||||
|
num_bytes += num_tokens * self.mem_pool_host_draft.size_per_token
|
||||||
|
return num_bytes
|
||||||
|
|
||||||
def load(
|
def load(
|
||||||
self,
|
self,
|
||||||
@@ -830,6 +854,8 @@ class HiCacheController:
|
|||||||
node_ids=op.node_ids,
|
node_ids=op.node_ids,
|
||||||
num_tokens=len(op.device_indices),
|
num_tokens=len(op.device_indices),
|
||||||
timing_enabled=timing_enabled,
|
timing_enabled=timing_enabled,
|
||||||
|
num_tokens_by_pool={PoolName.KV.value: len(op.device_indices)},
|
||||||
|
num_bytes=self._transfer_num_bytes(op),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return producer_id
|
return producer_id
|
||||||
|
|||||||
@@ -191,6 +191,21 @@ def sanity_check_mm_pad_shift_value(vocab_size: int) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def split_cached_prefix_by_tier(
|
||||||
|
prefix_len: int, host_hit_len: int, storage_hit_len: int
|
||||||
|
) -> tuple[int, int, int]:
|
||||||
|
"""Split a request's cached prefix into (device, host, storage) tokens.
|
||||||
|
|
||||||
|
prefix_len is len(prefix_indices) AFTER host load-back, so it contains the
|
||||||
|
host-loaded portion; host_hit_len in turn contains the storage-prefetched
|
||||||
|
portion (storage is clamped to it to handle edge cases).
|
||||||
|
"""
|
||||||
|
storage = min(host_hit_len, storage_hit_len)
|
||||||
|
host = host_hit_len - storage
|
||||||
|
device = max(0, prefix_len - host_hit_len)
|
||||||
|
return device, host, storage
|
||||||
|
|
||||||
|
|
||||||
def _compute_pad_value(hash: int) -> int:
|
def _compute_pad_value(hash: int) -> int:
|
||||||
"""Compute pad value from hash."""
|
"""Compute pad value from hash."""
|
||||||
return MM_PAD_SHIFT_VALUE + (hash % (1 << 30))
|
return MM_PAD_SHIFT_VALUE + (hash % (1 << 30))
|
||||||
@@ -2394,24 +2409,17 @@ class ScheduleBatch(ScheduleBatchDisaggregationDecodeMixin):
|
|||||||
# Only compute once on FIRST chunk - subsequent chunks in chunked prefill
|
# Only compute once on FIRST chunk - subsequent chunks in chunked prefill
|
||||||
# would incorrectly count previously computed tokens as cache hits.
|
# would incorrectly count previously computed tokens as cache hits.
|
||||||
if not req._cache_breakdown_computed:
|
if not req._cache_breakdown_computed:
|
||||||
# At this point, prefix_indices has been extended with host data
|
|
||||||
# via init_load_back in schedule_policy, so:
|
|
||||||
# - len(prefix_indices) = device_original + host_loaded
|
|
||||||
# - host_hit_length = total tokens from host cache (including storage-prefetched)
|
|
||||||
# - storage_hit_length = tokens loaded from storage backend (L3 hits)
|
|
||||||
# - device_portion = len(prefix_indices) - host_hit_length
|
|
||||||
#
|
|
||||||
# Storage hits are now tracked via scheduler after prefetch completes.
|
|
||||||
# storage_hit_length is set by scheduler.pop_prefetch_loaded_tokens()
|
# storage_hit_length is set by scheduler.pop_prefetch_loaded_tokens()
|
||||||
host_total = req.host_hit_length
|
# after prefetch completes.
|
||||||
# Clamp storage to host_total to handle edge cases
|
(
|
||||||
storage_portion = min(host_total, req.storage_hit_length)
|
req.cached_tokens_device,
|
||||||
host_portion = host_total - storage_portion
|
req.cached_tokens_host,
|
||||||
device_portion = max(0, len(req.prefix_indices) - host_total)
|
req.cached_tokens_storage,
|
||||||
|
) = split_cached_prefix_by_tier(
|
||||||
req.cached_tokens_device = device_portion
|
prefix_len=len(req.prefix_indices),
|
||||||
req.cached_tokens_host = host_portion
|
host_hit_len=req.host_hit_length,
|
||||||
req.cached_tokens_storage = storage_portion
|
storage_hit_len=req.storage_hit_length,
|
||||||
|
)
|
||||||
req._cache_breakdown_computed = True
|
req._cache_breakdown_computed = True
|
||||||
|
|
||||||
req.already_computed = seq_len
|
req.already_computed = seq_len
|
||||||
|
|||||||
@@ -38,7 +38,11 @@ import torch
|
|||||||
from sglang.srt.dllm.config import DllmConfig
|
from sglang.srt.dllm.config import DllmConfig
|
||||||
from sglang.srt.layers.attention.dsa.utils import is_dsa_prefill_cp_in_seq_split
|
from sglang.srt.layers.attention.dsa.utils import is_dsa_prefill_cp_in_seq_split
|
||||||
from sglang.srt.layers.utils.cp_utils import is_prefill_context_parallel_enabled
|
from sglang.srt.layers.utils.cp_utils import is_prefill_context_parallel_enabled
|
||||||
from sglang.srt.managers.schedule_batch import Req, ScheduleBatch
|
from sglang.srt.managers.schedule_batch import (
|
||||||
|
Req,
|
||||||
|
ScheduleBatch,
|
||||||
|
split_cached_prefix_by_tier,
|
||||||
|
)
|
||||||
from sglang.srt.mem_cache.allocator.hisparse import (
|
from sglang.srt.mem_cache.allocator.hisparse import (
|
||||||
DeepSeekV4HiSparseTokenToKVPoolAllocator,
|
DeepSeekV4HiSparseTokenToKVPoolAllocator,
|
||||||
)
|
)
|
||||||
@@ -483,6 +487,9 @@ class PrefillAdder:
|
|||||||
self.new_chunked_req = None
|
self.new_chunked_req = None
|
||||||
self.log_hit_tokens = 0
|
self.log_hit_tokens = 0
|
||||||
self.reprocessed_log_hit_tokens = 0
|
self.reprocessed_log_hit_tokens = 0
|
||||||
|
self.log_device_hit_tokens = 0
|
||||||
|
self.log_host_hit_tokens = 0
|
||||||
|
self.log_storage_hit_tokens = 0
|
||||||
# TODO(lsyin): report the real input tokens excluding page alignment
|
# TODO(lsyin): report the real input tokens excluding page alignment
|
||||||
self.log_input_tokens = 0
|
self.log_input_tokens = 0
|
||||||
self.reprocessed_log_input_tokens = 0
|
self.reprocessed_log_input_tokens = 0
|
||||||
@@ -745,6 +752,8 @@ class PrefillAdder:
|
|||||||
max_new_tokens: int,
|
max_new_tokens: int,
|
||||||
retracted_stain: bool,
|
retracted_stain: bool,
|
||||||
mamba_gap_reserve: int = 0,
|
mamba_gap_reserve: int = 0,
|
||||||
|
host_hit_len: int = 0,
|
||||||
|
storage_hit_len: int = 0,
|
||||||
):
|
):
|
||||||
# TODO(lsyin): check this workaround logic, which only ensures the prefill will not out of memory, and may be too conservative
|
# TODO(lsyin): check this workaround logic, which only ensures the prefill will not out of memory, and may be too conservative
|
||||||
extend_input_len = self.ceil_paged_tokens(extend_input_len)
|
extend_input_len = self.ceil_paged_tokens(extend_input_len)
|
||||||
@@ -784,6 +793,15 @@ class PrefillAdder:
|
|||||||
if retracted_stain:
|
if retracted_stain:
|
||||||
self.reprocessed_log_hit_tokens += prefix_len
|
self.reprocessed_log_hit_tokens += prefix_len
|
||||||
self.reprocessed_log_input_tokens += extend_input_len
|
self.reprocessed_log_input_tokens += extend_input_len
|
||||||
|
elif prefix_len > 0:
|
||||||
|
device_hit, host_hit, storage_hit = split_cached_prefix_by_tier(
|
||||||
|
prefix_len=prefix_len,
|
||||||
|
host_hit_len=host_hit_len,
|
||||||
|
storage_hit_len=storage_hit_len,
|
||||||
|
)
|
||||||
|
self.log_device_hit_tokens += device_hit
|
||||||
|
self.log_host_hit_tokens += host_hit
|
||||||
|
self.log_storage_hit_tokens += storage_hit
|
||||||
|
|
||||||
def _get_dllm_remain_tokens(self) -> int:
|
def _get_dllm_remain_tokens(self) -> int:
|
||||||
_rem_tokens = min(
|
_rem_tokens = min(
|
||||||
@@ -816,6 +834,8 @@ class PrefillAdder:
|
|||||||
0,
|
0,
|
||||||
req.retracted_stain,
|
req.retracted_stain,
|
||||||
mamba_gap_reserve=self._mamba_gap_budget_for_req(req),
|
mamba_gap_reserve=self._mamba_gap_budget_for_req(req),
|
||||||
|
host_hit_len=req.host_hit_length,
|
||||||
|
storage_hit_len=req.storage_hit_length,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _req_inc_lock_ref(self, req: Req):
|
def _req_inc_lock_ref(self, req: Req):
|
||||||
@@ -1209,6 +1229,8 @@ class PrefillAdder:
|
|||||||
),
|
),
|
||||||
req.retracted_stain,
|
req.retracted_stain,
|
||||||
mamba_gap_reserve=self._mamba_gap_budget_for_req(req),
|
mamba_gap_reserve=self._mamba_gap_budget_for_req(req),
|
||||||
|
host_hit_len=req.host_hit_length,
|
||||||
|
storage_hit_len=req.storage_hit_length,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# Make sure at least one page is available
|
# Make sure at least one page is available
|
||||||
@@ -1250,6 +1272,8 @@ class PrefillAdder:
|
|||||||
0,
|
0,
|
||||||
req.retracted_stain,
|
req.retracted_stain,
|
||||||
mamba_gap_reserve=self._mamba_gap_budget_for_req(req),
|
mamba_gap_reserve=self._mamba_gap_budget_for_req(req),
|
||||||
|
host_hit_len=req.host_hit_length,
|
||||||
|
storage_hit_len=req.storage_hit_length,
|
||||||
)
|
)
|
||||||
|
|
||||||
return self.budget_state()
|
return self.budget_state()
|
||||||
|
|||||||
@@ -7,13 +7,7 @@ import tempfile
|
|||||||
import time
|
import time
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import (
|
from typing import TYPE_CHECKING, List, Optional, Tuple, Union
|
||||||
TYPE_CHECKING,
|
|
||||||
List,
|
|
||||||
Optional,
|
|
||||||
Tuple,
|
|
||||||
Union,
|
|
||||||
)
|
|
||||||
|
|
||||||
from sglang.srt.disaggregation.utils import DisaggregationMode
|
from sglang.srt.disaggregation.utils import DisaggregationMode
|
||||||
from sglang.srt.environ import envs
|
from sglang.srt.environ import envs
|
||||||
@@ -64,6 +58,9 @@ class PrefillStats:
|
|||||||
num_new_seqs: int # len(can_run_list)
|
num_new_seqs: int # len(can_run_list)
|
||||||
reprocessed_log_input_tokens: int = 0
|
reprocessed_log_input_tokens: int = 0
|
||||||
reprocessed_log_hit_tokens: int = 0
|
reprocessed_log_hit_tokens: int = 0
|
||||||
|
log_device_hit_tokens: int = 0
|
||||||
|
log_host_hit_tokens: int = 0
|
||||||
|
log_storage_hit_tokens: int = 0
|
||||||
num_pending_tokens: int = 0
|
num_pending_tokens: int = 0
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -79,6 +76,9 @@ class PrefillStats:
|
|||||||
log_hit_tokens=adder.log_hit_tokens,
|
log_hit_tokens=adder.log_hit_tokens,
|
||||||
reprocessed_log_input_tokens=adder.reprocessed_log_input_tokens,
|
reprocessed_log_input_tokens=adder.reprocessed_log_input_tokens,
|
||||||
reprocessed_log_hit_tokens=adder.reprocessed_log_hit_tokens,
|
reprocessed_log_hit_tokens=adder.reprocessed_log_hit_tokens,
|
||||||
|
log_device_hit_tokens=adder.log_device_hit_tokens,
|
||||||
|
log_host_hit_tokens=adder.log_host_hit_tokens,
|
||||||
|
log_storage_hit_tokens=adder.log_storage_hit_tokens,
|
||||||
new_token_ratio=adder.new_token_ratio,
|
new_token_ratio=adder.new_token_ratio,
|
||||||
num_running_reqs=QueueCount.from_reqs(
|
num_running_reqs=QueueCount.from_reqs(
|
||||||
running_reqs, enable_priority_scheduling
|
running_reqs, enable_priority_scheduling
|
||||||
@@ -637,6 +637,12 @@ class SchedulerMetricsReporter:
|
|||||||
cache_hit_rate = (
|
cache_hit_rate = (
|
||||||
effective_hit_tokens / total_tokens if total_tokens > 0 else 0.0
|
effective_hit_tokens / total_tokens if total_tokens > 0 else 0.0
|
||||||
)
|
)
|
||||||
|
self.metrics_collector.increment_effective_prefill_tokens(
|
||||||
|
input_tokens=effective_input_tokens,
|
||||||
|
device_hit_tokens=prefill_stats.log_device_hit_tokens,
|
||||||
|
host_hit_tokens=prefill_stats.log_host_hit_tokens,
|
||||||
|
storage_hit_tokens=prefill_stats.log_storage_hit_tokens,
|
||||||
|
)
|
||||||
|
|
||||||
# Basics
|
# Basics
|
||||||
if (
|
if (
|
||||||
@@ -970,9 +976,7 @@ class SchedulerMetricsReporter:
|
|||||||
if not self.scheduler.enable_fpm:
|
if not self.scheduler.enable_fpm:
|
||||||
return
|
return
|
||||||
|
|
||||||
from sglang.srt.observability.forward_pass_metrics import (
|
from sglang.srt.observability.forward_pass_metrics import ForwardPassMetrics
|
||||||
ForwardPassMetrics,
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.scheduler._fpm_uses_device_timer:
|
if self.scheduler._fpm_uses_device_timer:
|
||||||
self.forward_pass_device_timer._report()
|
self.forward_pass_device_timer._report()
|
||||||
|
|||||||
@@ -455,7 +455,13 @@ class HiMambaRadixCache(MambaRadixCache):
|
|||||||
self.dec_lock_ref(end_node)
|
self.dec_lock_ref(end_node)
|
||||||
|
|
||||||
if self.metrics_collector is not None:
|
if self.metrics_collector is not None:
|
||||||
self.metrics_collector.increment_load_back_num_tokens(ack.num_tokens)
|
for pool, num_tokens in (ack.num_tokens_by_pool or {}).items():
|
||||||
|
if num_tokens > 0:
|
||||||
|
self.metrics_collector.increment_load_back_num_tokens(
|
||||||
|
num_tokens=num_tokens, pool=pool
|
||||||
|
)
|
||||||
|
if ack.num_bytes > 0:
|
||||||
|
self.metrics_collector.increment_load_back_num_bytes(ack.num_bytes)
|
||||||
if ack.timing_enabled:
|
if ack.timing_enabled:
|
||||||
duration_ms = ack.start_event.elapsed_time(ack.finish_event)
|
duration_ms = ack.start_event.elapsed_time(ack.finish_event)
|
||||||
self.metrics_collector.observe_load_back_duration(
|
self.metrics_collector.observe_load_back_duration(
|
||||||
|
|||||||
@@ -1028,6 +1028,7 @@ class HiRadixCache(RadixCache):
|
|||||||
ack.finish_event.synchronize()
|
ack.finish_event.synchronize()
|
||||||
for ack_id in ack.node_ids:
|
for ack_id in ack.node_ids:
|
||||||
self._finish_write_through_ack(ack_id, release_lock=False)
|
self._finish_write_through_ack(ack_id, release_lock=False)
|
||||||
|
self._log_write_ack_metrics(ack)
|
||||||
self.cache_controller.ack_write_queue.clear()
|
self.cache_controller.ack_write_queue.clear()
|
||||||
assert len(self.ongoing_write_through) == 0
|
assert len(self.ongoing_write_through) == 0
|
||||||
return
|
return
|
||||||
@@ -1055,8 +1056,24 @@ class HiRadixCache(RadixCache):
|
|||||||
ack.finish_event.synchronize()
|
ack.finish_event.synchronize()
|
||||||
for ack_id in ack.node_ids:
|
for ack_id in ack.node_ids:
|
||||||
self._finish_write_through_ack(ack_id, release_lock=True)
|
self._finish_write_through_ack(ack_id, release_lock=True)
|
||||||
|
self._log_write_ack_metrics(ack)
|
||||||
finish_count -= 1
|
finish_count -= 1
|
||||||
|
|
||||||
|
def _log_write_ack_metrics(self, ack) -> None:
|
||||||
|
"""Record D->H backup volume and duration for a completed write ack."""
|
||||||
|
if self.metrics_collector is None:
|
||||||
|
return
|
||||||
|
for pool, num_tokens in (ack.num_tokens_by_pool or {}).items():
|
||||||
|
if num_tokens > 0:
|
||||||
|
self.metrics_collector.increment_backup_num_tokens(
|
||||||
|
num_tokens=num_tokens, pool=pool
|
||||||
|
)
|
||||||
|
if ack.num_bytes > 0:
|
||||||
|
self.metrics_collector.increment_backup_num_bytes(ack.num_bytes)
|
||||||
|
if ack.timing_enabled:
|
||||||
|
duration_ms = ack.start_event.elapsed_time(ack.finish_event)
|
||||||
|
self.metrics_collector.observe_backup_duration(duration_ms / 1000.0)
|
||||||
|
|
||||||
def loading_check(self, finish_count: Optional[int] = None):
|
def loading_check(self, finish_count: Optional[int] = None):
|
||||||
if finish_count is None:
|
if finish_count is None:
|
||||||
finish_count = 0
|
finish_count = 0
|
||||||
@@ -1080,7 +1097,13 @@ class HiRadixCache(RadixCache):
|
|||||||
self.dec_lock_ref(end_node)
|
self.dec_lock_ref(end_node)
|
||||||
|
|
||||||
if self.metrics_collector is not None:
|
if self.metrics_collector is not None:
|
||||||
self.metrics_collector.increment_load_back_num_tokens(ack.num_tokens)
|
for pool, num_tokens in (ack.num_tokens_by_pool or {}).items():
|
||||||
|
if num_tokens > 0:
|
||||||
|
self.metrics_collector.increment_load_back_num_tokens(
|
||||||
|
num_tokens=num_tokens, pool=pool
|
||||||
|
)
|
||||||
|
if ack.num_bytes > 0:
|
||||||
|
self.metrics_collector.increment_load_back_num_bytes(ack.num_bytes)
|
||||||
if ack.timing_enabled:
|
if ack.timing_enabled:
|
||||||
duration_ms = ack.start_event.elapsed_time(ack.finish_event)
|
duration_ms = ack.start_event.elapsed_time(ack.finish_event)
|
||||||
self.metrics_collector.observe_load_back_duration(
|
self.metrics_collector.observe_load_back_duration(
|
||||||
@@ -1300,6 +1323,12 @@ class HiRadixCache(RadixCache):
|
|||||||
root.parent.children.pop(key, None)
|
root.parent.children.pop(key, None)
|
||||||
self._update_leaf_status(root.parent)
|
self._update_leaf_status(root.parent)
|
||||||
self._update_host_leaf_status(root.parent)
|
self._update_host_leaf_status(root.parent)
|
||||||
|
if freed_device > 0 and self.metrics_collector is not None:
|
||||||
|
self.metrics_collector.increment_dropped_tokens(
|
||||||
|
num_tokens=freed_device,
|
||||||
|
reason="host_pressure",
|
||||||
|
pool=PoolName.KV.value,
|
||||||
|
)
|
||||||
return freed_device
|
return freed_device
|
||||||
|
|
||||||
def evict_host(self, num_tokens: int):
|
def evict_host(self, num_tokens: int):
|
||||||
|
|||||||
@@ -419,10 +419,11 @@ class HybridCacheController(BaseHiCacheController):
|
|||||||
)
|
)
|
||||||
self.write_queue.clear()
|
self.write_queue.clear()
|
||||||
start_event = device_module.Event()
|
start_event = device_module.Event()
|
||||||
finish_event = device_module.Event()
|
ack_start_event, ack_finish_event, timing_enabled = make_timing_event_pair()
|
||||||
start_event.record()
|
start_event.record()
|
||||||
with device_module.stream(self.write_stream):
|
with device_module.stream(self.write_stream):
|
||||||
start_event.wait(self.write_stream)
|
start_event.wait(self.write_stream)
|
||||||
|
ack_start_event.record()
|
||||||
self.mem_pool_host.backup_from_device_all_layer(
|
self.mem_pool_host.backup_from_device_all_layer(
|
||||||
self.mem_pool_device,
|
self.mem_pool_device,
|
||||||
host_indices,
|
host_indices,
|
||||||
@@ -437,14 +438,60 @@ class HybridCacheController(BaseHiCacheController):
|
|||||||
device_indices,
|
device_indices,
|
||||||
self.io_backend,
|
self.io_backend,
|
||||||
)
|
)
|
||||||
finish_event.record()
|
ack_finish_event.record()
|
||||||
self._record_transfer_indices_on_stream(
|
self._record_transfer_indices_on_stream(
|
||||||
self.write_stream,
|
self.write_stream,
|
||||||
host_indices,
|
host_indices,
|
||||||
device_indices,
|
device_indices,
|
||||||
resolved_pool_transfers,
|
resolved_pool_transfers,
|
||||||
)
|
)
|
||||||
self.ack_write_queue.append(HiCacheAck(start_event, finish_event, op.node_ids))
|
self.ack_write_queue.append(
|
||||||
|
HiCacheAck(
|
||||||
|
start_event=ack_start_event,
|
||||||
|
finish_event=ack_finish_event,
|
||||||
|
node_ids=op.node_ids,
|
||||||
|
num_tokens=len(op.device_indices),
|
||||||
|
timing_enabled=timing_enabled,
|
||||||
|
num_tokens_by_pool=self._num_tokens_by_pool(op),
|
||||||
|
num_bytes=self._transfer_num_bytes(op),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _num_tokens_by_pool(self, op: CacheOperation) -> dict[str, int]:
|
||||||
|
"""Per-pool token counts for a merged transfer op (anchor + extra
|
||||||
|
pools), shared by D->H write and H->D load acks; sidecar transfers
|
||||||
|
reusing another pool's indices are excluded."""
|
||||||
|
counts = {self.mem_pool_host.anchor_entry.name.value: len(op.device_indices)}
|
||||||
|
for transfer in op.pool_transfers or []:
|
||||||
|
if transfer.indices_from_pool is not None or transfer.host_indices is None:
|
||||||
|
continue
|
||||||
|
name = transfer.name.value
|
||||||
|
counts[name] = counts.get(name, 0) + len(transfer.host_indices)
|
||||||
|
return counts
|
||||||
|
|
||||||
|
def _transfer_num_bytes(self, op: CacheOperation) -> int:
|
||||||
|
"""Total bytes moved by a merged transfer op across all pools,
|
||||||
|
including draft piggyback and sidecar transfers riding another
|
||||||
|
pool's indices (both excluded from the per-pool token counts)."""
|
||||||
|
kv_tokens = len(op.device_indices)
|
||||||
|
num_bytes = kv_tokens * self.mem_pool_host.anchor_entry.host_pool.size_per_token
|
||||||
|
if self.has_draft:
|
||||||
|
num_bytes += kv_tokens * self.mem_pool_host_draft.size_per_token
|
||||||
|
# Slot counts of the pools sidecars can ride on.
|
||||||
|
source_len = {self.mem_pool_host.anchor_entry.name: kv_tokens}
|
||||||
|
for t in op.pool_transfers or []:
|
||||||
|
if t.indices_from_pool is None and t.host_indices is not None:
|
||||||
|
source_len[t.name] = len(t.host_indices)
|
||||||
|
for t in op.pool_transfers or []:
|
||||||
|
entry = self.mem_pool_host.entry_map.get(t.name)
|
||||||
|
if entry is None:
|
||||||
|
continue
|
||||||
|
if t.indices_from_pool is not None:
|
||||||
|
num_slots = source_len.get(t.indices_from_pool, 0)
|
||||||
|
else:
|
||||||
|
num_slots = len(t.host_indices) if t.host_indices is not None else 0
|
||||||
|
num_bytes += num_slots * entry.host_pool.size_per_token
|
||||||
|
return num_bytes
|
||||||
|
|
||||||
def load(
|
def load(
|
||||||
self,
|
self,
|
||||||
@@ -542,6 +589,8 @@ class HybridCacheController(BaseHiCacheController):
|
|||||||
op.node_ids,
|
op.node_ids,
|
||||||
num_tokens=len(op.device_indices),
|
num_tokens=len(op.device_indices),
|
||||||
timing_enabled=timing_enabled,
|
timing_enabled=timing_enabled,
|
||||||
|
num_tokens_by_pool=self._num_tokens_by_pool(op),
|
||||||
|
num_bytes=self._transfer_num_bytes(op),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return producer_id
|
return producer_id
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ from sglang.srt.observability.metrics_collector import (
|
|||||||
from sglang.srt.session.streaming_session import StreamingSession
|
from sglang.srt.session.streaming_session import StreamingSession
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from sglang.srt.managers.cache_controller import HiCacheAck
|
||||||
from sglang.srt.managers.schedule_batch import Req
|
from sglang.srt.managers.schedule_batch import Req
|
||||||
from sglang.srt.mem_cache.cache_init_params import CacheInitParams
|
from sglang.srt.mem_cache.cache_init_params import CacheInitParams
|
||||||
from sglang.srt.mem_cache.hybrid_cache.hybrid_cache_controller import (
|
from sglang.srt.mem_cache.hybrid_cache.hybrid_cache_controller import (
|
||||||
@@ -82,6 +83,14 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
# Metric label per component, matching the host pool names used by
|
||||||
|
# hicache_backup_tokens_total and the host occupancy gauges.
|
||||||
|
_COMPONENT_POOL_LABEL = {
|
||||||
|
ComponentType.FULL: PoolName.KV.value,
|
||||||
|
ComponentType.SWA: PoolName.SWA.value,
|
||||||
|
ComponentType.MAMBA: PoolName.MAMBA.value,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
COMPONENT_REGISTRY: dict[ComponentType, type[TreeComponent]] = {
|
COMPONENT_REGISTRY: dict[ComponentType, type[TreeComponent]] = {
|
||||||
ComponentType.FULL: FullComponent,
|
ComponentType.FULL: FullComponent,
|
||||||
@@ -360,6 +369,14 @@ class UnifiedRadixCache(BasePrefixCache):
|
|||||||
self.cache_controller is not None
|
self.cache_controller is not None
|
||||||
and self.cache_controller.write_policy == "write_back"
|
and self.cache_controller.write_policy == "write_back"
|
||||||
)
|
)
|
||||||
|
# Pre-seed the dropped-tokens series at 0 per pool
|
||||||
|
if self.metrics_collector is not None and self.cache_controller is not None:
|
||||||
|
for ct in self.tree_components:
|
||||||
|
self.metrics_collector.increment_dropped_tokens(
|
||||||
|
num_tokens=0,
|
||||||
|
reason="host_pressure",
|
||||||
|
pool=_COMPONENT_POOL_LABEL[ct],
|
||||||
|
)
|
||||||
self.load_back_threshold = 10
|
self.load_back_threshold = 10
|
||||||
self.prefetch_stop_policy = server_args.hicache_storage_prefetch_policy
|
self.prefetch_stop_policy = server_args.hicache_storage_prefetch_policy
|
||||||
|
|
||||||
@@ -436,7 +453,8 @@ class UnifiedRadixCache(BasePrefixCache):
|
|||||||
):
|
):
|
||||||
self.writing_check(write_back=True)
|
self.writing_check(write_back=True)
|
||||||
|
|
||||||
self.update_eviction_metrics(sum(tracker.values()), start_time)
|
# Report full-layer tokens only
|
||||||
|
self.update_eviction_metrics(tracker[BASE_COMPONENT_TYPE], start_time)
|
||||||
return EvictResult(
|
return EvictResult(
|
||||||
num_tokens_evicted=tracker[BASE_COMPONENT_TYPE],
|
num_tokens_evicted=tracker[BASE_COMPONENT_TYPE],
|
||||||
swa_num_tokens_evicted=tracker.get(ComponentType.SWA, 0),
|
swa_num_tokens_evicted=tracker.get(ComponentType.SWA, 0),
|
||||||
@@ -519,10 +537,12 @@ class UnifiedRadixCache(BasePrefixCache):
|
|||||||
written = self._execute_and_commit_kv_backup(
|
written = self._execute_and_commit_kv_backup(
|
||||||
backup_kv, write_back=True
|
backup_kv, write_back=True
|
||||||
)
|
)
|
||||||
|
freed_before_drop = dict(tracker)
|
||||||
if written > 0:
|
if written > 0:
|
||||||
self.writing_check(write_back=True)
|
self.writing_check(write_back=True)
|
||||||
self._demote(node_id, tracker)
|
self._demote(node_id, tracker)
|
||||||
elif self._drop_subtree_no_host(node_id, tracker):
|
elif self._drop_subtree_no_host(node_id, tracker):
|
||||||
|
self._record_dropped_tokens(tracker, freed_before_drop)
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"write_back: KV subtree dropped without backup "
|
"write_back: KV subtree dropped without backup "
|
||||||
"due to host memory pressure, root node %d",
|
"due to host memory pressure, root node %d",
|
||||||
@@ -539,6 +559,23 @@ class UnifiedRadixCache(BasePrefixCache):
|
|||||||
finally:
|
finally:
|
||||||
self.tree_core.evict_device_end(ct)
|
self.tree_core.evict_device_end(ct)
|
||||||
|
|
||||||
|
def _record_dropped_tokens(
|
||||||
|
self,
|
||||||
|
tracker: dict[ComponentType, int],
|
||||||
|
freed_before_drop: dict[ComponentType, int],
|
||||||
|
) -> None:
|
||||||
|
"""Record per-pool tokens dropped without backup under host pressure."""
|
||||||
|
if self.metrics_collector is None:
|
||||||
|
return
|
||||||
|
for ct, freed in tracker.items():
|
||||||
|
dropped = freed - freed_before_drop[ct]
|
||||||
|
if dropped > 0:
|
||||||
|
self.metrics_collector.increment_dropped_tokens(
|
||||||
|
num_tokens=dropped,
|
||||||
|
reason="host_pressure",
|
||||||
|
pool=_COMPONENT_POOL_LABEL[ct],
|
||||||
|
)
|
||||||
|
|
||||||
def inc_lock_ref(
|
def inc_lock_ref(
|
||||||
self, node_id: NodeId, skip_lock_components: Sequence[ComponentType] = ()
|
self, node_id: NodeId, skip_lock_components: Sequence[ComponentType] = ()
|
||||||
) -> IncLockRefResult:
|
) -> IncLockRefResult:
|
||||||
@@ -1832,6 +1869,7 @@ class UnifiedRadixCache(BasePrefixCache):
|
|||||||
for ack_id in ack.node_ids:
|
for ack_id in ack.node_ids:
|
||||||
if ack_id in self.ongoing_write_through:
|
if ack_id in self.ongoing_write_through:
|
||||||
self._finish_write_through_ack(ack_id)
|
self._finish_write_through_ack(ack_id)
|
||||||
|
self._log_write_ack_metrics(ack)
|
||||||
cc.ack_write_queue.clear()
|
cc.ack_write_queue.clear()
|
||||||
assert len(self.ongoing_write_through) == 0
|
assert len(self.ongoing_write_through) == 0
|
||||||
return
|
return
|
||||||
@@ -1854,8 +1892,24 @@ class UnifiedRadixCache(BasePrefixCache):
|
|||||||
ack.finish_event.synchronize()
|
ack.finish_event.synchronize()
|
||||||
for ack_id in ack.node_ids:
|
for ack_id in ack.node_ids:
|
||||||
self._finish_write_through_ack(ack_id)
|
self._finish_write_through_ack(ack_id)
|
||||||
|
self._log_write_ack_metrics(ack)
|
||||||
finish_count -= 1
|
finish_count -= 1
|
||||||
|
|
||||||
|
def _log_write_ack_metrics(self, ack: HiCacheAck) -> None:
|
||||||
|
"""Record D->H backup volume and duration for a completed write ack."""
|
||||||
|
if self.metrics_collector is None:
|
||||||
|
return
|
||||||
|
for pool, num_tokens in (ack.num_tokens_by_pool or {}).items():
|
||||||
|
if num_tokens > 0:
|
||||||
|
self.metrics_collector.increment_backup_num_tokens(
|
||||||
|
num_tokens=num_tokens, pool=pool
|
||||||
|
)
|
||||||
|
if ack.num_bytes > 0:
|
||||||
|
self.metrics_collector.increment_backup_num_bytes(ack.num_bytes)
|
||||||
|
if ack.timing_enabled:
|
||||||
|
duration_ms = ack.start_event.elapsed_time(ack.finish_event)
|
||||||
|
self.metrics_collector.observe_backup_duration(duration_ms / 1000.0)
|
||||||
|
|
||||||
def loading_check(self, finish_count: Optional[int] = None) -> None:
|
def loading_check(self, finish_count: Optional[int] = None) -> None:
|
||||||
"""Poll load-back completions."""
|
"""Poll load-back completions."""
|
||||||
cc = self.cache_controller
|
cc = self.cache_controller
|
||||||
@@ -1882,7 +1936,13 @@ class UnifiedRadixCache(BasePrefixCache):
|
|||||||
self.dec_host_lock_ref(node, host_lock_params)
|
self.dec_host_lock_ref(node, host_lock_params)
|
||||||
|
|
||||||
if self.metrics_collector is not None:
|
if self.metrics_collector is not None:
|
||||||
self.metrics_collector.increment_load_back_num_tokens(ack.num_tokens)
|
for pool, num_tokens in (ack.num_tokens_by_pool or {}).items():
|
||||||
|
if num_tokens > 0:
|
||||||
|
self.metrics_collector.increment_load_back_num_tokens(
|
||||||
|
num_tokens=num_tokens, pool=pool
|
||||||
|
)
|
||||||
|
if ack.num_bytes > 0:
|
||||||
|
self.metrics_collector.increment_load_back_num_bytes(ack.num_bytes)
|
||||||
if ack.timing_enabled:
|
if ack.timing_enabled:
|
||||||
duration_ms = ack.start_event.elapsed_time(ack.finish_event)
|
duration_ms = ack.start_event.elapsed_time(ack.finish_event)
|
||||||
self.metrics_collector.observe_load_back_duration(
|
self.metrics_collector.observe_load_back_duration(
|
||||||
|
|||||||
@@ -889,6 +889,20 @@ class SchedulerMetricsCollector(_StatLoggerDIMixin):
|
|||||||
),
|
),
|
||||||
labelnames=list(labels.keys()) + ["mode"],
|
labelnames=list(labels.keys()) + ["mode"],
|
||||||
)
|
)
|
||||||
|
self.prefill_effective_tokens_total = Counter(
|
||||||
|
name="sglang:prefill_effective_tokens_total",
|
||||||
|
documentation=(
|
||||||
|
"Effective prefill tokens with retracted-request re-counts "
|
||||||
|
"excluded, updated on each log interval. mode: device_hit, "
|
||||||
|
"host_hit, storage_hit, input. Windowed prefix cache hit "
|
||||||
|
"rate = rate(sum of *_hit) / rate(sum of all modes); "
|
||||||
|
"per-tier rate uses a single *_hit mode in the numerator."
|
||||||
|
),
|
||||||
|
labelnames=list(labels.keys()) + ["mode"],
|
||||||
|
)
|
||||||
|
# Pre-seed every mode at 0 so per-tier ratio charts get a complete operand set
|
||||||
|
for mode in ("input", "device_hit", "host_hit", "storage_hit"):
|
||||||
|
self.prefill_effective_tokens_total.labels(**labels, mode=mode)
|
||||||
self.forward_execution_seconds_total = Counter(
|
self.forward_execution_seconds_total = Counter(
|
||||||
name="sglang:forward_execution_seconds_total",
|
name="sglang:forward_execution_seconds_total",
|
||||||
documentation=(
|
documentation=(
|
||||||
@@ -1248,6 +1262,24 @@ class SchedulerMetricsCollector(_StatLoggerDIMixin):
|
|||||||
**dp_cooperation_info.to_labels(),
|
**dp_cooperation_info.to_labels(),
|
||||||
).inc(delta)
|
).inc(delta)
|
||||||
|
|
||||||
|
def increment_effective_prefill_tokens(
|
||||||
|
self,
|
||||||
|
input_tokens: int,
|
||||||
|
device_hit_tokens: int,
|
||||||
|
host_hit_tokens: int,
|
||||||
|
storage_hit_tokens: int,
|
||||||
|
) -> None:
|
||||||
|
for mode, delta in [
|
||||||
|
("input", input_tokens),
|
||||||
|
("device_hit", device_hit_tokens),
|
||||||
|
("host_hit", host_hit_tokens),
|
||||||
|
("storage_hit", storage_hit_tokens),
|
||||||
|
]:
|
||||||
|
if delta > 0:
|
||||||
|
self.prefill_effective_tokens_total.labels(
|
||||||
|
**self.labels, mode=mode
|
||||||
|
).inc(delta)
|
||||||
|
|
||||||
def increment_forward_execution_seconds(
|
def increment_forward_execution_seconds(
|
||||||
self,
|
self,
|
||||||
category: str,
|
category: str,
|
||||||
@@ -1964,6 +1996,11 @@ class RadixCacheMetricsCollector(_StatLoggerDIMixin):
|
|||||||
0.2,
|
0.2,
|
||||||
0.5,
|
0.5,
|
||||||
1.0,
|
1.0,
|
||||||
|
2.0,
|
||||||
|
5.0,
|
||||||
|
10.0,
|
||||||
|
30.0,
|
||||||
|
60.0,
|
||||||
]
|
]
|
||||||
bucket_load_back_duration = get_histogram_conf_from_env(
|
bucket_load_back_duration = get_histogram_conf_from_env(
|
||||||
"SGLANG_BUCKET_LOAD_BACK_DURATION"
|
"SGLANG_BUCKET_LOAD_BACK_DURATION"
|
||||||
@@ -1989,16 +2026,43 @@ class RadixCacheMetricsCollector(_StatLoggerDIMixin):
|
|||||||
0.5,
|
0.5,
|
||||||
1.0,
|
1.0,
|
||||||
]
|
]
|
||||||
|
# D->H backups include blocking merged ops issued during eviction under
|
||||||
|
# --hicache-write-policy write_back, which can run for seconds -- hence
|
||||||
|
# the wider default range than load-back.
|
||||||
|
bucket_backup_duration = [
|
||||||
|
0.001,
|
||||||
|
0.002,
|
||||||
|
0.005,
|
||||||
|
0.01,
|
||||||
|
0.02,
|
||||||
|
0.05,
|
||||||
|
0.1,
|
||||||
|
0.2,
|
||||||
|
0.5,
|
||||||
|
1.0,
|
||||||
|
2.0,
|
||||||
|
5.0,
|
||||||
|
10.0,
|
||||||
|
30.0,
|
||||||
|
60.0,
|
||||||
|
]
|
||||||
|
|
||||||
self.eviction_duration_seconds = Histogram(
|
self.eviction_duration_seconds = Histogram(
|
||||||
name="sglang:eviction_duration_seconds",
|
name="sglang:eviction_duration_seconds",
|
||||||
documentation="Time taken to evict memory from GPU to CPU in seconds.",
|
documentation="End-to-end time of a device eviction pass in "
|
||||||
|
"seconds; under --hicache-write-policy write_back this includes "
|
||||||
|
"the blocking D->H backup (see "
|
||||||
|
"sglang:hicache_backup_duration_seconds for the copy alone).",
|
||||||
labelnames=labels.keys(),
|
labelnames=labels.keys(),
|
||||||
buckets=bucket_eviction_duration,
|
buckets=bucket_eviction_duration,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.eviction_num_tokens = Counter(
|
self.eviction_num_tokens = Counter(
|
||||||
name="sglang:evicted_tokens_total",
|
name="sglang:evicted_tokens_total",
|
||||||
documentation="The number of tokens evicted from GPU to CPU.",
|
documentation="The number of device KV token slots freed by "
|
||||||
|
"eviction, regardless of whether the data was backed up to host "
|
||||||
|
"(see sglang:hicache_backup_tokens_total) or destroyed (see "
|
||||||
|
"sglang:hicache_dropped_tokens_total).",
|
||||||
labelnames=labels.keys(),
|
labelnames=labels.keys(),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -2011,15 +2075,63 @@ class RadixCacheMetricsCollector(_StatLoggerDIMixin):
|
|||||||
|
|
||||||
self.load_back_num_tokens = Counter(
|
self.load_back_num_tokens = Counter(
|
||||||
name="sglang:load_back_tokens_total",
|
name="sglang:load_back_tokens_total",
|
||||||
documentation="The number of tokens loaded from CPU to GPU.",
|
documentation="The number of tokens loaded back from local host "
|
||||||
|
"DRAM (L2) to GPU, by host pool (kv, swa, mamba, ...).",
|
||||||
|
labelnames=list(labels.keys()) + ["pool"],
|
||||||
|
)
|
||||||
|
|
||||||
|
self.backup_duration_seconds = Histogram(
|
||||||
|
name="sglang:hicache_backup_duration_seconds",
|
||||||
|
documentation="Time taken to back up KV cache from GPU to local "
|
||||||
|
"host DRAM (L2) in seconds, per merged write op. Covers all D->H "
|
||||||
|
"backups regardless of --hicache-write-policy. Distinct from the "
|
||||||
|
"host-to-storage (L3) sglang:backuped_tokens_total.",
|
||||||
labelnames=labels.keys(),
|
labelnames=labels.keys(),
|
||||||
|
buckets=bucket_backup_duration,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.backup_num_bytes = Counter(
|
||||||
|
name="sglang:hicache_backup_bytes_total",
|
||||||
|
documentation="Bytes backed up from GPU to local host DRAM (L2), "
|
||||||
|
"all pools combined, including draft/sidecar transfers that the "
|
||||||
|
"token counter excludes. Divided by the rate of "
|
||||||
|
"hicache_backup_duration_seconds_sum, gives the achieved D->H "
|
||||||
|
"bandwidth while transferring.",
|
||||||
|
labelnames=labels.keys(),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.load_back_num_bytes = Counter(
|
||||||
|
name="sglang:load_back_bytes_total",
|
||||||
|
documentation="Bytes loaded back from local host DRAM (L2) to "
|
||||||
|
"GPU, all pools combined, including draft/sidecar transfers that "
|
||||||
|
"the token counter excludes. Divided by the rate of "
|
||||||
|
"load_back_duration_seconds_sum, gives the achieved H->D "
|
||||||
|
"bandwidth while transferring.",
|
||||||
|
labelnames=labels.keys(),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.backup_num_tokens = Counter(
|
||||||
|
name="sglang:hicache_backup_tokens_total",
|
||||||
|
documentation="The number of tokens backed up from GPU to local "
|
||||||
|
"host DRAM (L2), by host pool (kv, swa, mamba, ...). Covers all "
|
||||||
|
"D->H backups regardless of --hicache-write-policy. Distinct from "
|
||||||
|
"the host-to-storage (L3) sglang:backuped_tokens_total.",
|
||||||
|
labelnames=list(labels.keys()) + ["pool"],
|
||||||
|
)
|
||||||
|
|
||||||
|
self.hicache_dropped_tokens = Counter(
|
||||||
|
name="sglang:hicache_dropped_tokens_total",
|
||||||
|
documentation="The number of device KV tokens destroyed without a "
|
||||||
|
"host backup, by pool (kv, swa, ...) and reason (e.g. write-back "
|
||||||
|
"backup failure under host memory pressure).",
|
||||||
|
labelnames=list(labels.keys()) + ["reason", "pool"],
|
||||||
)
|
)
|
||||||
|
|
||||||
def increment_eviction_num_tokens(self, num_tokens: int) -> None:
|
def increment_eviction_num_tokens(self, num_tokens: int) -> None:
|
||||||
self.eviction_num_tokens.labels(**self.labels).inc(num_tokens)
|
self.eviction_num_tokens.labels(**self.labels).inc(num_tokens)
|
||||||
|
|
||||||
def increment_load_back_num_tokens(self, num_tokens: int) -> None:
|
def increment_load_back_num_tokens(self, num_tokens: int, pool: str) -> None:
|
||||||
self.load_back_num_tokens.labels(**self.labels).inc(num_tokens)
|
self.load_back_num_tokens.labels(**self.labels, pool=pool).inc(num_tokens)
|
||||||
|
|
||||||
def observe_eviction_duration(self, duration_seconds: float) -> None:
|
def observe_eviction_duration(self, duration_seconds: float) -> None:
|
||||||
self.eviction_duration_seconds.labels(**self.labels).observe(duration_seconds)
|
self.eviction_duration_seconds.labels(**self.labels).observe(duration_seconds)
|
||||||
@@ -2027,6 +2139,23 @@ class RadixCacheMetricsCollector(_StatLoggerDIMixin):
|
|||||||
def observe_load_back_duration(self, duration_seconds: float) -> None:
|
def observe_load_back_duration(self, duration_seconds: float) -> None:
|
||||||
self.load_back_duration_seconds.labels(**self.labels).observe(duration_seconds)
|
self.load_back_duration_seconds.labels(**self.labels).observe(duration_seconds)
|
||||||
|
|
||||||
|
def increment_backup_num_tokens(self, num_tokens: int, pool: str) -> None:
|
||||||
|
self.backup_num_tokens.labels(**self.labels, pool=pool).inc(num_tokens)
|
||||||
|
|
||||||
|
def increment_backup_num_bytes(self, num_bytes: int) -> None:
|
||||||
|
self.backup_num_bytes.labels(**self.labels).inc(num_bytes)
|
||||||
|
|
||||||
|
def increment_load_back_num_bytes(self, num_bytes: int) -> None:
|
||||||
|
self.load_back_num_bytes.labels(**self.labels).inc(num_bytes)
|
||||||
|
|
||||||
|
def observe_backup_duration(self, duration_seconds: float) -> None:
|
||||||
|
self.backup_duration_seconds.labels(**self.labels).observe(duration_seconds)
|
||||||
|
|
||||||
|
def increment_dropped_tokens(self, num_tokens: int, reason: str, pool: str) -> None:
|
||||||
|
self.hicache_dropped_tokens.labels(**self.labels, reason=reason, pool=pool).inc(
|
||||||
|
num_tokens
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class EncoderMetricsCollector(_StatLoggerDIMixin):
|
class EncoderMetricsCollector(_StatLoggerDIMixin):
|
||||||
"""Metrics collector for the EPD encoder server (--encoder-only)."""
|
"""Metrics collector for the EPD encoder server (--encoder-only)."""
|
||||||
|
|||||||
@@ -91,6 +91,8 @@ class TestPrefillAdder(CustomTestCase):
|
|||||||
req.sampling_params = SimpleNamespace(max_new_tokens=max_new_tokens)
|
req.sampling_params = SimpleNamespace(max_new_tokens=max_new_tokens)
|
||||||
req.time_stats = SimpleNamespace(wait_queue_entry_time=wait_time)
|
req.time_stats = SimpleNamespace(wait_queue_entry_time=wait_time)
|
||||||
req.retracted_stain = False
|
req.retracted_stain = False
|
||||||
|
req.host_hit_length = 0
|
||||||
|
req.storage_hit_length = 0
|
||||||
req.finished.return_value = False
|
req.finished.return_value = False
|
||||||
req.needs_host_load_back.return_value = False
|
req.needs_host_load_back.return_value = False
|
||||||
return req
|
return req
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ class TestLoadBackDurationMetric(CustomTestCase):
|
|||||||
node_ids=[1, 2],
|
node_ids=[1, 2],
|
||||||
num_tokens=1024,
|
num_tokens=1024,
|
||||||
timing_enabled=True,
|
timing_enabled=True,
|
||||||
|
num_tokens_by_pool={"kv": 1024},
|
||||||
)
|
)
|
||||||
stub = object.__new__(HiRadixCache)
|
stub = object.__new__(HiRadixCache)
|
||||||
stub.cache_controller = SimpleNamespace(ack_load_queue=[ack])
|
stub.cache_controller = SimpleNamespace(ack_load_queue=[ack])
|
||||||
@@ -77,7 +78,7 @@ class TestLoadBackDurationMetric(CustomTestCase):
|
|||||||
stub.loading_check()
|
stub.loading_check()
|
||||||
|
|
||||||
stub.metrics_collector.increment_load_back_num_tokens.assert_called_once_with(
|
stub.metrics_collector.increment_load_back_num_tokens.assert_called_once_with(
|
||||||
1024
|
num_tokens=1024, pool="kv"
|
||||||
)
|
)
|
||||||
stub.metrics_collector.observe_load_back_duration.assert_called_once()
|
stub.metrics_collector.observe_load_back_duration.assert_called_once()
|
||||||
(observed,), _ = stub.metrics_collector.observe_load_back_duration.call_args
|
(observed,), _ = stub.metrics_collector.observe_load_back_duration.call_args
|
||||||
@@ -100,6 +101,7 @@ class TestLoadBackDurationMetric(CustomTestCase):
|
|||||||
node_ids=[7],
|
node_ids=[7],
|
||||||
num_tokens=512,
|
num_tokens=512,
|
||||||
timing_enabled=False,
|
timing_enabled=False,
|
||||||
|
num_tokens_by_pool={"kv": 512},
|
||||||
)
|
)
|
||||||
stub = object.__new__(HiRadixCache)
|
stub = object.__new__(HiRadixCache)
|
||||||
stub.cache_controller = SimpleNamespace(ack_load_queue=[ack])
|
stub.cache_controller = SimpleNamespace(ack_load_queue=[ack])
|
||||||
@@ -112,7 +114,7 @@ class TestLoadBackDurationMetric(CustomTestCase):
|
|||||||
stub.loading_check()
|
stub.loading_check()
|
||||||
|
|
||||||
stub.metrics_collector.increment_load_back_num_tokens.assert_called_once_with(
|
stub.metrics_collector.increment_load_back_num_tokens.assert_called_once_with(
|
||||||
512
|
num_tokens=512, pool="kv"
|
||||||
)
|
)
|
||||||
stub.metrics_collector.observe_load_back_duration.assert_not_called()
|
stub.metrics_collector.observe_load_back_duration.assert_not_called()
|
||||||
self.assertEqual(stub.cache_controller.ack_load_queue, [])
|
self.assertEqual(stub.cache_controller.ack_load_queue, [])
|
||||||
|
|||||||
@@ -139,6 +139,9 @@ def _cpu_per_layer_pf_lf_copy(
|
|||||||
|
|
||||||
|
|
||||||
class _FakeEvent:
|
class _FakeEvent:
|
||||||
|
def __init__(self, enable_timing=False):
|
||||||
|
self.enable_timing = enable_timing
|
||||||
|
|
||||||
def record(self):
|
def record(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -156,6 +159,15 @@ class _FakeDeviceModule:
|
|||||||
|
|
||||||
|
|
||||||
class TestHiCacheStagedWriteBackDispatch(unittest.TestCase):
|
class TestHiCacheStagedWriteBackDispatch(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
# start_writing probes timing support via a module-cached check;
|
||||||
|
# clear it on both sides so results from (or against) the fake
|
||||||
|
# device module never leak across tests.
|
||||||
|
manager_cache_controller._timing_events_supported.cache_clear()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
manager_cache_controller._timing_events_supported.cache_clear()
|
||||||
|
|
||||||
def _patched_transfers(self, src_registry=None, module=MEMORY_POOL_HOST_MODULE):
|
def _patched_transfers(self, src_registry=None, module=MEMORY_POOL_HOST_MODULE):
|
||||||
staged_side_effect = None
|
staged_side_effect = None
|
||||||
if src_registry is not None:
|
if src_registry is not None:
|
||||||
@@ -678,6 +690,10 @@ class TestHiCacheStagedWriteBackDispatch(unittest.TestCase):
|
|||||||
class FakeHostGroup:
|
class FakeHostGroup:
|
||||||
layout = "page_first"
|
layout = "page_first"
|
||||||
can_use_write_back_jit = True
|
can_use_write_back_jit = True
|
||||||
|
anchor_entry = SimpleNamespace(
|
||||||
|
name=PoolName.KV, host_pool=SimpleNamespace(size_per_token=2)
|
||||||
|
)
|
||||||
|
entry_map = {}
|
||||||
|
|
||||||
def backup_from_device_all_layer(
|
def backup_from_device_all_layer(
|
||||||
self,
|
self,
|
||||||
@@ -718,8 +734,13 @@ class TestHiCacheStagedWriteBackDispatch(unittest.TestCase):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
with mock.patch.object(
|
with (
|
||||||
hybrid_cache_controller, "device_module", _FakeDeviceModule
|
mock.patch.object(
|
||||||
|
hybrid_cache_controller, "device_module", _FakeDeviceModule
|
||||||
|
),
|
||||||
|
mock.patch.object(
|
||||||
|
manager_cache_controller, "device_module", _FakeDeviceModule
|
||||||
|
),
|
||||||
):
|
):
|
||||||
controller.start_writing()
|
controller.start_writing()
|
||||||
|
|
||||||
@@ -733,6 +754,10 @@ class TestHiCacheStagedWriteBackDispatch(unittest.TestCase):
|
|||||||
class FakeHostGroup:
|
class FakeHostGroup:
|
||||||
layout = "page_first"
|
layout = "page_first"
|
||||||
can_use_write_back_jit = False
|
can_use_write_back_jit = False
|
||||||
|
anchor_entry = SimpleNamespace(
|
||||||
|
name=PoolName.KV, host_pool=SimpleNamespace(size_per_token=2)
|
||||||
|
)
|
||||||
|
entry_map = {}
|
||||||
|
|
||||||
def backup_from_device_all_layer(
|
def backup_from_device_all_layer(
|
||||||
self,
|
self,
|
||||||
@@ -770,8 +795,13 @@ class TestHiCacheStagedWriteBackDispatch(unittest.TestCase):
|
|||||||
return_value=(op.host_indices, op.device_indices, op.pool_transfers)
|
return_value=(op.host_indices, op.device_indices, op.pool_transfers)
|
||||||
)
|
)
|
||||||
|
|
||||||
with mock.patch.object(
|
with (
|
||||||
hybrid_cache_controller, "device_module", _FakeDeviceModule
|
mock.patch.object(
|
||||||
|
hybrid_cache_controller, "device_module", _FakeDeviceModule
|
||||||
|
),
|
||||||
|
mock.patch.object(
|
||||||
|
manager_cache_controller, "device_module", _FakeDeviceModule
|
||||||
|
),
|
||||||
):
|
):
|
||||||
controller.start_writing()
|
controller.start_writing()
|
||||||
|
|
||||||
@@ -785,6 +815,7 @@ class TestHiCacheStagedWriteBackDispatch(unittest.TestCase):
|
|||||||
class FakeHostPool:
|
class FakeHostPool:
|
||||||
layout = "page_first"
|
layout = "page_first"
|
||||||
can_use_write_back_jit = True
|
can_use_write_back_jit = True
|
||||||
|
size_per_token = 2
|
||||||
|
|
||||||
def backup_from_device_all_layer(
|
def backup_from_device_all_layer(
|
||||||
self, device_pool, host_indices, device_indices, io_backend
|
self, device_pool, host_indices, device_indices, io_backend
|
||||||
@@ -825,6 +856,7 @@ class TestHiCacheStagedWriteBackDispatch(unittest.TestCase):
|
|||||||
class FakeHostPool:
|
class FakeHostPool:
|
||||||
layout = "page_first"
|
layout = "page_first"
|
||||||
can_use_write_back_jit = False
|
can_use_write_back_jit = False
|
||||||
|
size_per_token = 2
|
||||||
|
|
||||||
def backup_from_device_all_layer(
|
def backup_from_device_all_layer(
|
||||||
self, device_pool, host_indices, device_indices, io_backend
|
self, device_pool, host_indices, device_indices, io_backend
|
||||||
|
|||||||
Reference in New Issue
Block a user