Files
wehub-resource-sync 59a0a3844c
PR Test AMD / cancel-on-close (push) Has been skipped
PR Test NVIDIA ARM / scan (push) Has been skipped
PR Test NVIDIA / cancel-on-close (push) Has been skipped
PR Test AMD / scan (push) Has been skipped
PR Test NVIDIA ARM / cancel-on-close (push) Has been skipped
PR Test NVIDIA / scan (push) Has been skipped
Release Docker Images / build (cu129-torch-2.11.0) (push) Has been skipped
Release Docker Images / build (cu130-torch-2.11.0) (push) Has been skipped
Release PyPI / publish (push) Has been skipped
Scheduler Python Test / test (push) Successful in 27m19s
Docs / build (push) Successful in 28m8s
Scheduler C++ Test / test (push) Successful in 28m19s
Scheduler C++ Test / test-flat (push) Successful in 28m18s
Docs / deploy (push) Has been cancelled
PR Test AMD / finish (push) Has been cancelled
PR Test NVIDIA / finish (push) Has been cancelled
PR Test NVIDIA ARM / finish (push) Has been cancelled
PR Test NVIDIA ARM / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
PR Test AMD / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
PR Test NVIDIA / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:32:31 +08:00

160 lines
5.5 KiB
Python

# Copyright (c) 2026 LightSeek Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
from __future__ import annotations
from dataclasses import dataclass, field
import torch
from tokenspeed.runtime.execution.forward_batch_info import ForwardMode
from tokenspeed.runtime.layers.attention.kv_cache.deepseek_v4 import (
DeepseekV4CacheMetadata,
)
@dataclass
class DeepseekV4IndexerPrefillChunkPlan:
token_start: int
token_end: int
request_start: int
request_end: int
slot_start: int
slot_end: int
gather_row_start: int
gather_row_end: int
max_seq_len_k: int
cu_seq_lens_start: int
cu_seq_lens_end: int
skip_kv_gather: bool = False
@dataclass
class DeepseekV4IndexerPrefillMetadata:
chunks: tuple[DeepseekV4IndexerPrefillChunkPlan, ...]
chunk_specs: torch.Tensor
chunk_offsets: torch.Tensor
slots: torch.Tensor
cu_seq_lens: torch.Tensor
cu_seqlen_k_start: torch.Tensor
cu_seqlen_k_end: torch.Tensor
seq_lens_k: torch.Tensor
def max_gather_rows(self) -> int:
if not self.chunks:
return 0
return max(max(0, chunk.slot_end - chunk.slot_start) for chunk in self.chunks)
@classmethod
def empty(cls, device: torch.device) -> "DeepseekV4IndexerPrefillMetadata":
return cls(
chunks=(),
chunk_specs=torch.empty((0, 5), dtype=torch.int64, device="cpu"),
chunk_offsets=torch.empty((0, 7), dtype=torch.int64, device="cpu"),
slots=torch.empty(0, dtype=torch.int64, device=device),
cu_seq_lens=torch.empty(0, dtype=torch.int32, device=device),
cu_seqlen_k_start=torch.empty(0, dtype=torch.int32, device=device),
cu_seqlen_k_end=torch.empty(0, dtype=torch.int32, device=device),
seq_lens_k=torch.empty(0, dtype=torch.int32, device=device),
)
@dataclass
class DeepseekV4IndexerDecodePlan:
context_lens: torch.Tensor
block_table: torch.Tensor
max_context_len: int
@dataclass
class DeepseekV4IndexerBatchMetadata:
positions: torch.Tensor
token_to_req_indices: torch.Tensor
seq_lens_cpu: torch.Tensor
query_lens_cpu: torch.Tensor
num_prefill_tokens: int
num_decode_tokens: int
@dataclass
class DeepseekV4AttentionMetadata:
decode_swa_indices: torch.Tensor | None = None
decode_swa_lens: torch.Tensor | None = None
decode_swa_window_size: int = 0
decode_swa_block_size: int = 0
# Cache for dense compressed decode attention indices/lens. CSA decode uses
# dynamic top-k indices and does not populate this cache.
decode_dense_compressed_indices_cache: dict[
tuple[int, int, int, int], tuple[torch.Tensor, torch.Tensor]
] = field(default_factory=dict)
decode_dense_compressed_indices_capture_safe_keys: set[
tuple[int, int, int, int]
] = field(default_factory=set)
@dataclass
class DeepseekV4IndexerMetadata:
decode_schedule_metadata_cache: dict[tuple[int, int, int], torch.Tensor] = field(
default_factory=dict
)
decode_plan_cache: dict[tuple[int, int, int], DeepseekV4IndexerDecodePlan] = field(
default_factory=dict
)
decode_plan_refreshed_keys: set[tuple[int, int, int]] = field(default_factory=set)
prefill_plan_cache: dict[tuple[int, int, int], DeepseekV4IndexerPrefillMetadata] = (
field(default_factory=dict)
)
@dataclass
class DeepseekV4SparseIndexerMetadata:
batch_metadata: DeepseekV4IndexerBatchMetadata | None = None
prefill_metadata: DeepseekV4IndexerPrefillMetadata | None = None
decode_plan: DeepseekV4IndexerDecodePlan | None = None
decode_schedule_metadata: torch.Tensor | None = None
@dataclass
class DeepseekV4ForwardMetadata:
req_pool_indices: torch.Tensor
seq_lens: torch.Tensor
query_lens: torch.Tensor
query_start_loc: torch.Tensor
token_to_req_indices: torch.Tensor
cache: DeepseekV4CacheMetadata
attention: DeepseekV4AttentionMetadata = field(
default_factory=DeepseekV4AttentionMetadata
)
indexer: DeepseekV4IndexerMetadata = field(
default_factory=DeepseekV4IndexerMetadata
)
forward_mode: ForwardMode | None = None
# Padding mask for CUDA graph replay rows; this is not mixed-batch state.
is_valid_token: torch.Tensor | None = None
# CPU lens are retained for sparse prefill/indexer planning without
# forcing another device-to-host sync in the model path.
seq_lens_cpu: torch.Tensor | None = None
query_lens_cpu: torch.Tensor | None = None
# Cached split boundary derived from scheduler num_extends/query_lens.
num_prefill_reqs: int = 0
num_prefill_tokens: int = 0
def decode_req_count(self) -> int:
return max(0, int(self.req_pool_indices.shape[0]) - int(self.num_prefill_reqs))
def decode_token_count(self) -> int:
return max(
0,
int(self.token_to_req_indices.shape[0]) - int(self.num_prefill_tokens),
)