Files
wehub-resource-sync 94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:38:16 +08:00

585 lines
21 KiB
Python

from dataclasses import dataclass
from enum import Enum
from typing import Iterable, List, Optional, Set, Tuple, Union
import torch
from sglang.srt.model_executor.forward_batch_info import ForwardBatch
from sglang.srt.utils.hf_transformers_utils import AutoConfig
@dataclass
class MoELoRABatchInfo:
# Per-request segment indptrs used by MoE LoRA routing, shape (bs + 1,).
seg_indptr: torch.Tensor
# Per-request adapter index used by MoE LoRA routing, shape (bs,).
req_to_lora: torch.Tensor
# A mask indicating if lora adapter is enabled. Shape (num_loras,)
adapter_enabled: torch.Tensor
# A mapping of which lora adapter is used for each token. Shape (num_tokens,)
# If a token has no lora adapter, the value is -1.
token_lora_mapping: torch.Tensor
@dataclass
class LoRABatchInfo:
# The forward mode is using CUDA Graph.
use_cuda_graph: bool
# Batch size
bs: int
# Number of segments. For triton backend, it is equal to batch size.
num_segments: int
# Indice pointers of each segment in shape (num_segments + 1, )
seg_indptr: torch.Tensor
# The index of lora adapter used by each segment, in shape (num_segments,)
weight_indices: torch.Tensor
# ranks of each lora adapter, in shape (lora_num,)
lora_ranks: torch.Tensor
# scaling of each lora adapter, in shape (lora_num,)
scalings: torch.Tensor
# Maximum segment length of current batch
max_len: Optional[int]
# Lengths of each segments in shape (num_segments,)
seg_lens: Optional[torch.Tensor]
# The logical (re)ordering of input rows (tokens), in shape (num_tokens,)
permutation: Optional[torch.Tensor]
# Total number of tokens this batch info expects (host-side int).
# Used by lm_head LoRA to validate input shape without GPU sync.
expected_tokens: Optional[int] = None
# CPU-side flag: True when at least one request uses a LoRA adapter.
# Computed from Python lists in prepare_lora_batch to avoid GPU sync.
has_active_lora: bool = False
# Per-request segment indptrs, shape (bs + 1,). Required by MoE virtual
# experts which map tokens to requests regardless of the dense-LoRA
# backend's internal segmentation. For the triton backend these are
# identical to seg_indptr/weight_indices; for csgmv they differ because
# its segments are chunked across adapters.
req_seg_indptr: Optional[torch.Tensor] = None
# Per-request adapter index, shape (bs,).
req_weight_indices: Optional[torch.Tensor] = None
# MoE LoRA batch info
moe_lora_info: Optional[MoELoRABatchInfo] = None
class LoRAType(Enum):
LORA_A = 0
LORA_B = 1
def copy_weight_into_buffer(
buffer_view: torch.Tensor,
weight: torch.Tensor,
) -> None:
"""
Copy a LoRA weight tensor into a destination buffer.
When a pinned CPU source has a dtype mismatch with a device destination,
cast on the destination device instead of doing the conversion on CPU.
"""
if weight.dtype == buffer_view.dtype:
buffer_view.copy_(weight, non_blocking=True)
return
if weight.device.type == "cpu" and buffer_view.device.type != "cpu":
weight = weight.to(device=buffer_view.device, non_blocking=True)
buffer_view.copy_(weight.to(dtype=buffer_view.dtype), non_blocking=True)
def get_hidden_dim(
module_name: str,
config: AutoConfig,
base_model: torch.nn.Module,
layer_idx: int,
lora_added_vocab_size: int = 0,
) -> Tuple[int]:
"""
Given a module_name (might be a stacked name), return the hidden dims of modules' input and output.
"""
if hasattr(base_model, "get_hidden_dim"):
return base_model.get_hidden_dim(module_name, layer_idx)
else:
"""
WARNING: get_hidden_dim() is not defined,
which is used to get the hidden dim for different lora modules
Use the default one, but please check if it is correct for your model.
Please implement the function in the model class if it is not.
You can reference this function in llama.py.
"""
head_dim = getattr(
config, "head_dim", config.hidden_size // config.num_attention_heads
)
if module_name == "qkv_proj":
return config.hidden_size, head_dim * (
config.num_attention_heads + config.num_key_value_heads * 2
)
elif module_name == "o_proj":
o_head_dim = getattr(config, "v_head_dim", None) or head_dim
return (
o_head_dim * config.num_attention_heads,
config.hidden_size,
)
elif module_name == "gate_up_proj":
inter = config.intermediate_size
first_k = getattr(config, "first_k_dense_replace", None)
moe_freq = getattr(config, "moe_layer_freq", 1)
if (
first_k is not None
and layer_idx >= first_k
and layer_idx % moe_freq == 0
):
moe_inter = getattr(config, "moe_intermediate_size", None)
n_shared = getattr(config, "n_shared_experts", None)
if moe_inter is not None and n_shared is not None:
inter = moe_inter * n_shared
return config.hidden_size, inter * 2
elif module_name == "down_proj":
inter = config.intermediate_size
first_k = getattr(config, "first_k_dense_replace", None)
moe_freq = getattr(config, "moe_layer_freq", 1)
if (
first_k is not None
and layer_idx >= first_k
and layer_idx % moe_freq == 0
):
moe_inter = getattr(config, "moe_intermediate_size", None)
n_shared = getattr(config, "n_shared_experts", None)
if moe_inter is not None and n_shared is not None:
inter = moe_inter * n_shared
return inter, config.hidden_size
elif module_name == "fused_qkv_a_proj_with_mqa":
q_lora_rank = getattr(config, "q_lora_rank", None) or 0
kv_lora_rank = config.kv_lora_rank
qk_rope_head_dim = config.qk_rope_head_dim
return (
config.hidden_size,
q_lora_rank + kv_lora_rank + qk_rope_head_dim,
)
elif module_name == "q_b_proj":
return (
config.q_lora_rank,
config.num_attention_heads
* (config.qk_nope_head_dim + config.qk_rope_head_dim),
)
elif module_name == "kv_b_proj":
return (
config.kv_lora_rank,
config.num_attention_heads
* (config.qk_nope_head_dim + config.v_head_dim),
)
elif module_name in DSA_INDEXER_LORA_NAMES:
from sglang.srt.configs.model_config import (
get_dsa_index_head_dim,
get_dsa_index_n_heads,
)
if module_name == "indexer.wq_b":
return (
config.q_lora_rank,
get_dsa_index_n_heads(config) * get_dsa_index_head_dim(config),
)
elif module_name == "indexer.wk":
return config.hidden_size, get_dsa_index_head_dim(config)
else: # indexer.weights_proj
return config.hidden_size, get_dsa_index_n_heads(config)
elif module_name == "gate_up_proj_moe":
moe_inter = (
getattr(config, "moe_intermediate_size", None)
or config.intermediate_size
)
return config.hidden_size, moe_inter * 2
elif module_name == "down_proj_moe":
moe_inter = (
getattr(config, "moe_intermediate_size", None)
or config.intermediate_size
)
return moe_inter, config.hidden_size
elif module_name == "embed_tokens":
# For embedding: input is vocab_size (as embedding lookup), output is hidden_size
# if contain extra tokens will be added; otherwise is 0.
return config.vocab_size + lora_added_vocab_size, config.hidden_size
elif module_name == "lm_head":
# For lm_head: input is hidden_size, output is vocab_size
# if contain extra tokens will be added; otherwise is 0.
return config.hidden_size, config.vocab_size + lora_added_vocab_size
else:
raise NotImplementedError(
"get_hidden_dim not implemented for " + module_name
)
def get_normalized_target_modules(
target_modules: Union[str, Iterable[str]],
) -> set[str]:
"""
Mapping a list of target module name to names of the normalized LoRA weights.
Handles both base module names (e.g., "gate_proj") and prefixed module names (e.g., "feed_forward.gate_proj").
Also handles PEFT shorthand strings like "all-linear" or "all" by returning
{"all"} as a sentinel value. Callers that need a concrete module set
should use :func:`auto_detect_lora_target_modules` to resolve the shorthand
against the loaded base model.
"""
# Handle PEFT shorthand strings — return {"all"} as sentinel.
# Callers can resolve to concrete names via auto_detect_lora_target_modules().
if isinstance(target_modules, str):
if target_modules not in ["all", "all-linear"]:
raise ValueError(
"Only 'all' or 'all-linear' can be used as the string for target module"
)
return {"all"}
params_mapping = {
"q_proj": "qkv_proj",
"k_proj": "qkv_proj",
"v_proj": "qkv_proj",
"gate_proj": "gate_up_proj",
"up_proj": "gate_up_proj",
"out_proj": "out_proj",
"embed_tokens": "embed_tokens",
"vocab_emb": "embed_tokens",
"embeddings": "embed_tokens",
"word_embeddings": "embed_tokens",
"lm_head": "lm_head",
"output": "lm_head",
"unembed_tokens": "lm_head",
"q_a_proj": "fused_qkv_a_proj_with_mqa",
"kv_a_proj_with_mqa": "fused_qkv_a_proj_with_mqa",
# DSA indexer projections are qualified with their parent module name
# because the bare leaf names collide with unrelated modules in other
# models (e.g. DeepSeek-V4 attention `wq_b`, Pixtral vision `wk`).
"wq_b": "indexer.wq_b",
"wk": "indexer.wk",
"weights_proj": "indexer.weights_proj",
}
result = set()
for name in target_modules:
base_name = name.split(".")[-1]
normalized_name = params_mapping.get(base_name, base_name)
result.add(normalized_name)
return result
def get_stacked_multiply(
module_name: str, base_model: Optional[torch.nn.Module] = None
) -> int:
"""
Mapping a lora module name to its magnification at output dimension.
Models can override via a get_stacked_multiply(module_name) method.
"""
if base_model is not None and hasattr(base_model, "get_stacked_multiply"):
return base_model.get_stacked_multiply(module_name)
stacked_rank = {
"qkv_proj": 3,
"in_proj_qkvz": 4, # GDN packed input projection
"gate_up_proj": 2,
"gate_up_proj_moe": 2,
"in_proj": 2,
"fused_qkv_a_proj_with_mqa": 2,
}
return stacked_rank[module_name] if module_name in stacked_rank else 1
def get_target_module_name(full_module_name: str, target_modules: Set[str]) -> str:
"""
Get the target module name in target_modules that can match full_module_name.
If there is a target module name in target_modules that can match full_module_name, return this name
Else raise ValueError.
When multiple target modules match (e.g. both "up_proj" and "gate_up_proj"
are substrings), the longest match wins to avoid ambiguity.
"""
best = None
for target_module in target_modules:
if target_module in full_module_name:
if best is None or len(target_module) > len(best):
best = target_module
if best is not None:
return best
raise ValueError(
f"Cannot find target module name for {full_module_name} in {target_modules}"
)
EMBEDDING_NAMES = ["embed_tokens", "lm_head"]
ROW_PARALLELISM_LINEAR_LORA_NAMES = ["o_proj", "out_proj", "down_proj", "down_proj_moe"]
DSA_INDEXER_LORA_NAMES = frozenset(
{"indexer.wq_b", "indexer.wk", "indexer.weights_proj"}
)
REPLICATED_LINEAR_LORA_NAMES = [
"fused_qkv_a_proj_with_mqa",
"fc1_latent_proj",
"fc2_latent_proj",
*DSA_INDEXER_LORA_NAMES,
]
# Normalized module names that the LoRA system fully supports
# (i.e. get_hidden_dim, init_buffers, and init_lora_modules can handle them).
_KNOWN_LORA_TARGET_MODULES = frozenset(
{
"qkv_proj",
"o_proj",
"out_proj",
"in_proj",
"in_proj_qkvz",
"up_proj",
"gate_up_proj",
"down_proj",
"fc1_latent_proj",
"fc2_latent_proj",
"embed_tokens",
"lm_head",
"fused_qkv_a_proj_with_mqa",
"q_b_proj",
"kv_b_proj",
}
| DSA_INDEXER_LORA_NAMES
)
def auto_detect_lora_target_modules(model: "torch.nn.Module") -> set:
"""Discover LoRA-compatible modules by inspecting the base model.
Walks the model graph and returns the set of *normalized* target-module
names that (a) actually exist in the model and (b) the LoRA memory pool
can handle. This is used to resolve PEFT shorthands like ``"all-linear"``
without requiring the user to enumerate modules on the CLI.
"""
from sglang.srt.layers.linear import LinearBase
from sglang.srt.layers.moe.fused_moe_triton.layer import FusedMoE
from sglang.srt.layers.vocab_parallel_embedding import (
ParallelLMHead,
VocabParallelEmbedding,
)
raw_names: set = set()
dsa_indexer_leaf_names = {
target_name.split(".")[-1] for target_name in DSA_INDEXER_LORA_NAMES
}
for name, module in model.named_modules():
if isinstance(module, FusedMoE):
raw_names.add("gate_up_proj")
raw_names.add("down_proj")
elif isinstance(module, ParallelLMHead):
raw_names.add("lm_head")
elif isinstance(module, VocabParallelEmbedding):
raw_names.add("embed_tokens")
elif isinstance(module, LinearBase):
parts = name.split(".")
leaf_name = parts[-1]
parent_qualified_name = ".".join(parts[-2:])
if parent_qualified_name in DSA_INDEXER_LORA_NAMES:
raw_names.add(parent_qualified_name)
elif leaf_name in dsa_indexer_leaf_names:
# Bare DSA indexer leaf names are ambiguous across model
# families. Only auto-detect them when the actual module path
# proves they are under an `indexer` parent.
continue
else:
raw_names.add(leaf_name)
normalized = get_normalized_target_modules(raw_names)
result = normalized & _KNOWN_LORA_TARGET_MODULES
# Allow models to declare additional LoRA-compatible modules that
# cannot be auto-discovered or need to bypass normalization
# (e.g. Mamba in_proj, non-gated up_proj).
if hasattr(model, "supported_lora_modules"):
result.update(set(model.supported_lora_modules) & _KNOWN_LORA_TARGET_MODULES)
return result
def get_lm_head_lora_b_shard_size(output_dim: int, shard_indices=None) -> int:
"""Get the LoRA B output dimension for lm_head, accounting for TP.
lm_head is column-parallel, so its LoRA B must be sharded along the
vocab dimension to match the base output. When shard_indices is
provided, the returned size reflects the base model's actual per-rank
vocab partition.
Args:
output_dim: Full (unsharded) output dimension (vocab_size).
shard_indices: VocabParallelEmbeddingShardIndices from the base
ParallelLMHead layer. When provided, returns the per-rank
org vocab size from the base model's actual sharding.
"""
if shard_indices is not None:
return shard_indices.num_org_elements
return output_dim
def generate_sequence_lengths(
forward_batch: ForwardBatch, device: Optional[torch.device] = None
) -> torch.Tensor:
device = torch.get_default_device() if device is None else device
with torch.device(device):
if forward_batch.forward_mode.is_decode():
seg_lens = torch.ones(forward_batch.batch_size, dtype=torch.int32)
elif forward_batch.forward_mode.is_target_verify():
seg_lens = torch.full(
size=(forward_batch.batch_size,),
fill_value=forward_batch.spec_info.draft_token_num,
dtype=torch.int32,
)
elif forward_batch.forward_mode.is_extend():
seg_lens = (
forward_batch.extend_seq_lens
if forward_batch.extend_seq_lens.device == device
else torch.tensor(
forward_batch.extend_seq_lens_cpu,
dtype=torch.int32,
)
)
else:
raise ValueError(f"Unsupported forward mode: {forward_batch.forward_mode}")
return seg_lens
def get_lm_head_pruned_lens(
forward_batch: ForwardBatch,
) -> Optional[List[int]]:
"""
Compute per-sequence pruned lengths for lm_head LoRA.
Returns a list of pruned lengths (one per sequence) if pruning applies,
or None if lm_head pruning is not applicable for this batch.
Pruning rules:
- Extend without logprobs: 1 token per sequence
- Extend with logprobs: max(extend_len - logprob_start_len, 1) per sequence
- Decode / target_verify / draft_extend_v2: no pruning
IMPORTANT: This must stay in sync with LogitsProcessor._get_pruned_states()
in sglang/srt/layers/logits_processor.py, which determines how many tokens
per sequence are passed to lm_head. If the pruning conditions or lengths
there change, this function must be updated to match, otherwise the
lm_head LoRA will operate on incorrectly shaped inputs.
"""
lm_head_pruning = (
forward_batch.forward_mode.is_extend()
and not forward_batch.forward_mode.is_target_verify()
and not forward_batch.forward_mode.is_draft_extend_v2()
)
if not lm_head_pruning:
return None
if forward_batch.return_logprob:
pruned_lens = []
for ext_len, start_len in zip(
forward_batch.extend_seq_lens_cpu,
forward_batch.extend_logprob_start_lens_cpu,
):
pruned_lens.append(1 if ext_len == start_len else ext_len - start_len)
else:
pruned_lens = [1] * forward_batch.batch_size
return pruned_lens
def merge_and_chunk_segments(
weight_indices: list[int],
pruned_lens: List[int],
chunk_size: int,
) -> Tuple[List[int], List[int]]:
"""
Merge consecutive same-adapter sequences and chunk at chunk_size boundaries.
Merges consecutive sequences that use the same adapter into single
segments, splitting any segment that exceeds chunk_size.
Args:
weight_indices: Per-sequence adapter indices.
pruned_lens: Per-sequence pruned token counts.
chunk_size: Maximum segment length before splitting.
Returns:
(seg_weight_indices, seg_lens): Merged and chunked segments.
"""
seg_weight_indices: List[int] = []
seg_lens: List[int] = []
for wi, pl in zip(weight_indices, pruned_lens):
if seg_weight_indices and seg_weight_indices[-1] == wi:
seg_lens[-1] += pl
else:
seg_weight_indices.append(wi)
seg_lens.append(pl)
# Split the last segment if it exceeds chunk_size
while seg_lens[-1] > chunk_size:
remainder = seg_lens[-1] - chunk_size
seg_lens[-1] = chunk_size
seg_weight_indices.append(wi)
seg_lens.append(remainder)
return seg_weight_indices, seg_lens
def build_lm_head_pass_segments(
weight_indices: List[int],
pruned_lens: List[int],
logprobs_chunk_size: int,
) -> List[Tuple[List[int], List[int]]]:
"""
Precompute per-pass segment info for lm_head LoRA logprobs processing.
When LogitsProcessor uses chunked logprobs processing
(process_input_logprobs_by_chunk), pruned hidden states are split into
fixed-size passes. Each pass needs its own segmentation
(weight_indices, seg_lens) so that lm_head LoRA operates on the
correct adapter assignments per pass.
Args:
weight_indices: Per-sequence adapter indices.
pruned_lens: Per-sequence pruned token counts.
logprobs_chunk_size: Fixed pass size used by LogitsProcessor.
Returns:
List of (seg_weight_indices, seg_lens) tuples, one per pass.
"""
# Expand to per-token weight index
token_wi: List[int] = []
for wi, pl in zip(weight_indices, pruned_lens):
token_wi.extend([wi] * pl)
total = len(token_wi)
num_passes = (total + logprobs_chunk_size - 1) // logprobs_chunk_size
result: List[Tuple[List[int], List[int]]] = []
for i in range(num_passes):
start = i * logprobs_chunk_size
end = min((i + 1) * logprobs_chunk_size, total)
# Run-length encode the pass's adapter indices
seg_wi: List[int] = []
seg_lens: List[int] = []
for t in range(start, end):
if seg_wi and seg_wi[-1] == token_wi[t]:
seg_lens[-1] += 1
else:
seg_wi.append(token_wi[t])
seg_lens.append(1)
result.append((seg_wi, seg_lens))
return result