[Model] Support LongCat 2.0 FP8 (#30275)

Co-authored-by: sunjiaqi11 <sunjiaqi11@meituan.com>
Co-authored-by: Ke Bao <ispobaoke@gmail.com>
This commit is contained in:
Xiaoyu Zhang
2026-07-07 19:51:12 +08:00
committed by GitHub
co-authored by sunjiaqi11 Ke Bao
parent cfd3fdc54f
commit e339c83f82
23 changed files with 481 additions and 91 deletions
@@ -30,7 +30,8 @@ __global__ void ComputeNGramIdsKernel(
int max_context_len, // max_context_len
const int64_t* __restrict__ row_indices, // [batch_size]
int* column_starts, // [batch_size]
int* n_gram_ids // [ne_n-1,ne_k,token_num]
int* n_gram_ids, // [ne_n-1,ne_k,token_num]
int eos_token_id // tokens before an eos are excluded from the n-gram context
) {
// Determine which n, k, and request this block handles.
/**
@@ -73,12 +74,17 @@ __global__ void ComputeNGramIdsKernel(
// Out of this request's range, stop computing n_gram_id
break;
}
if (ne_token_table[current_token_table_index - j] < 0) {
const int table_token = ne_token_table[current_token_table_index - j];
if (table_token < 0) {
// Token was marked as ignored during write
break;
}
const uint64_t term =
(uint64_t)ne_token_table[current_token_table_index - j] * (uint64_t)ne_weights[ne_weight_base_idx + j];
if (table_token == eos_token_id && j > 0) {
// Don't let the n-gram context cross an eos boundary. j==0 (the
// current token) is allowed; only break when looking back.
break;
}
const uint64_t term = (uint64_t)table_token * (uint64_t)ne_weights[ne_weight_base_idx + j];
n_gram_id += term % ne_mod;
}
n_gram_id %= ne_mod;
@@ -99,7 +105,8 @@ __global__ void ComputeNGramIdsDecodeKernel(
int max_context_len, // max_context_len
const int64_t* __restrict__ row_indices, // [batch_size]
const int* __restrict__ column_starts, // [batch_size]
int* __restrict__ n_gram_ids // [batch_size, (ne_n-1)*ne_k]
int* __restrict__ n_gram_ids, // [batch_size, (ne_n-1)*ne_k]
int eos_token_id // tokens before an eos are excluded from the n-gram context
) {
const int num_configs = (ne_n - 1) * ne_k;
const int total_outputs = batch_size * num_configs;
@@ -124,6 +131,11 @@ __global__ void ComputeNGramIdsDecodeKernel(
if (token < 0) {
break;
}
if (token == eos_token_id && j > 0) {
// Don't let the n-gram context cross an eos boundary. j==0 (the
// current token) is allowed; only break when looking back.
break;
}
const uint64_t term = static_cast<uint64_t>(token) * static_cast<uint64_t>(ne_weights[weight_offset + j]);
n_gram_id += term % ne_mod;
}
@@ -201,7 +213,8 @@ struct NgramEmbeddingKernel {
const tvm::ffi::TensorView ne_token_table,
const tvm::ffi::TensorView row_indices,
const tvm::ffi::TensorView column_starts,
const tvm::ffi::TensorView n_gram_ids) {
const tvm::ffi::TensorView n_gram_ids,
const int64_t eos_token_id) {
using namespace host;
auto device_ = SymbolicDevice{};
@@ -274,7 +287,8 @@ struct NgramEmbeddingKernel {
max_context_len,
static_cast<const int64_t*>(row_indices.data_ptr()),
static_cast<int*>(column_starts.data_ptr()),
static_cast<int*>(n_gram_ids.data_ptr()));
static_cast<int*>(n_gram_ids.data_ptr()),
static_cast<int>(eos_token_id));
}
static void compute_n_gram_ids_decode(
@@ -286,7 +300,8 @@ struct NgramEmbeddingKernel {
const tvm::ffi::TensorView ne_token_table,
const tvm::ffi::TensorView row_indices,
const tvm::ffi::TensorView column_starts,
const tvm::ffi::TensorView n_gram_ids) {
const tvm::ffi::TensorView n_gram_ids,
const int64_t eos_token_id) {
using namespace host;
auto device_ = SymbolicDevice{};
@@ -354,7 +369,8 @@ struct NgramEmbeddingKernel {
max_context_len,
static_cast<const int64_t*>(row_indices.data_ptr()),
static_cast<const int*>(column_starts.data_ptr()),
static_cast<int*>(n_gram_ids.data_ptr()));
static_cast<int*>(n_gram_ids.data_ptr()),
static_cast<int>(eos_token_id));
}
static void update_token_table(
@@ -43,6 +43,7 @@ def compute_n_gram_ids(
row_indices: torch.Tensor,
column_starts: torch.Tensor,
n_gram_ids: torch.Tensor,
eos_token_id: int,
) -> None:
"""
Compute n-gram IDs for embedding.
@@ -59,6 +60,7 @@ def compute_n_gram_ids(
row_indices: row indices for each request
column_starts: column start positions for each request
n_gram_ids: output tensor for n-gram ids
eos_token_id: tokens before an eos are excluded from the n-gram context
"""
module = _jit_ngram_embedding_module()
module.compute_n_gram_ids(
@@ -73,6 +75,7 @@ def compute_n_gram_ids(
row_indices,
column_starts,
n_gram_ids,
eos_token_id,
)
@@ -87,6 +90,7 @@ def compute_n_gram_ids_decode(
row_indices: torch.Tensor,
column_starts: torch.Tensor,
n_gram_ids: torch.Tensor,
eos_token_id: int,
) -> None:
"""
Compute n-gram IDs for decode, where each request contributes one token.
@@ -102,6 +106,7 @@ def compute_n_gram_ids_decode(
row_indices,
column_starts,
n_gram_ids,
eos_token_id,
)
+15
View File
@@ -313,6 +313,8 @@ def _register_for(*architectures: str):
"MistralLarge3ForCausalLM",
"PixtralForConditionalGeneration",
"GlmMoeDsaForCausalLM",
"LongcatFlashForCausalLM",
"LongcatFlashForCausalLMNextN",
)
def _deepseek_family_overrides(server_args: Any, hf_config: Any) -> dict:
"""Order-safe declarations of the DeepSeek/DSA branch. The CP parallel
@@ -1170,6 +1172,8 @@ _DEEPSEEK_FAMILY_ARCHS = frozenset(
"MistralLarge3ForCausalLM",
"PixtralForConditionalGeneration",
"GlmMoeDsaForCausalLM",
"LongcatFlashForCausalLM",
"LongcatFlashForCausalLMNextN",
}
)
@@ -1243,6 +1247,17 @@ def _deepseek_moe_quant_resolution(view: Any) -> dict:
logger.info(
"Use flashinfer_trtllm as MoE runner backend on sm100 for DeepseekV3ForCausalLM"
)
if (
model_arch in ["LongcatFlashForCausalLM", "LongcatFlashForCausalLMNextN"]
and view.fp8_gemm_runner_backend == "auto"
and quantization in ["fp8", "modelopt_fp8"]
and quant_cfg.get("scale_fmt", None) != "ue8m0"
):
overrides["fp8_gemm_runner_backend"] = "flashinfer_trtllm"
logger.info(
"Use flashinfer_trtllm as FP8 GEMM backend on Blackwell for LongCat FP8 "
"checkpoint with non-ue8m0 scales"
)
return overrides
@@ -56,6 +56,9 @@ class LongcatFlashConfig(PretrainedConfig):
ngram_vocab_size_ratio=None,
emb_neighbor_num=None,
emb_split_num=None,
oe_vocab_size_ratio=None,
oe_neighbor_num=None,
oe_split_num=None,
**kwargs,
):
super().__init__(
@@ -105,6 +108,15 @@ class LongcatFlashConfig(PretrainedConfig):
self.zero_expert_type = zero_expert_type
self.routed_scaling_factor = routed_scaling_factor
self.hidden_act = "silu"
if ngram_vocab_size_ratio is None:
ngram_vocab_size_ratio = oe_vocab_size_ratio
if emb_neighbor_num is None:
emb_neighbor_num = oe_neighbor_num
if emb_split_num is None:
emb_split_num = oe_split_num
self.oe_vocab_size_ratio = oe_vocab_size_ratio
self.oe_neighbor_num = oe_neighbor_num
self.oe_split_num = oe_split_num
self.use_ngram_embedding = ngram_vocab_size_ratio is not None
if self.use_ngram_embedding:
self.ngram_embedding_m = int(ngram_vocab_size_ratio * vocab_size)
@@ -110,6 +110,8 @@ def is_deepseek_dsa(config) -> bool:
"MistralLarge3ForCausalLM",
"PixtralForConditionalGeneration",
"GlmMoeDsaForCausalLM",
"LongcatFlashForCausalLM",
"LongcatFlashForCausalLMNextN",
)
and _hf_attr(config, "index_topk") is not None
)
@@ -31,7 +31,7 @@ from sglang.srt.layers.attention.dsa.utils import (
is_graph_dsa_split_op_surface,
)
from sglang.srt.layers.dp_attention import attn_tp_all_gather_into_tensor
from sglang.srt.layers.layernorm import LayerNorm
from sglang.srt.layers.layernorm import LayerNorm, RMSNorm
from sglang.srt.layers.quantization.fp8_kernel import fp8_dtype, is_fp8_fnuz
from sglang.srt.layers.utils import MultiPlatformOp
from sglang.srt.model_executor.runner_backend_utils.breakable_cuda_graph import (
@@ -364,6 +364,7 @@ class Indexer(MultiPlatformOp):
prefix: str = "",
quant_config: Optional[QuantizationConfig] = None,
alt_stream: Optional[torch.cuda.Stream] = None,
config=None,
):
super().__init__()
self.hidden_size = hidden_size
@@ -425,9 +426,15 @@ class Indexer(MultiPlatformOp):
params_dtype=torch.bfloat16,
prefix=add_prefix("weights_proj", prefix),
)
self.k_norm = LayerNorm(
self.head_dim, dtype=torch.bfloat16 if _use_aiter else torch.float32
)
if (
config is not None
and getattr(config, "index_k_norm_type", "layer") == "rms"
):
self.k_norm = RMSNorm(self.head_dim)
else:
self.k_norm = LayerNorm(
self.head_dim, dtype=torch.bfloat16 if _use_aiter else torch.float32
)
self.rotary_emb = get_rope_wrapper(
rope_head_dim,
rotary_dim=rope_head_dim,
@@ -440,6 +447,10 @@ class Indexer(MultiPlatformOp):
self.block_size = block_size
self.scale_fmt = scale_fmt
self.softmax_scale = self.head_dim**-0.5
self.num_init_tokens = self.num_local_tokens = 0
if config is not None:
self.num_init_tokens = getattr(config, "index_init_tokens", 0)
self.num_local_tokens = getattr(config, "index_local_tokens", 0)
self.paged_mqa_logits_backend = DSAPagedMQALogitsBackend.resolve(
get_server_args().dsa_paged_mqa_logits_backend
@@ -786,6 +797,52 @@ class Indexer(MultiPlatformOp):
return
dst.copy_(src)
@staticmethod
def _pad_heads_for_deep_gemm(q_fp8, weights):
"""Pad q and weights to 32 heads when num_heads < 32,
so that block_q = 128/num_heads doesn't exceed seq_len_alignment(4)."""
num_heads = q_fp8.shape[1]
if num_heads >= 32:
return q_fp8, weights, num_heads
target_heads = 32
q_fp8 = torch.nn.functional.pad(q_fp8, (0, 0, 0, target_heads - num_heads))
weights = torch.nn.functional.pad(weights, (0, target_heads - num_heads))
return q_fp8, weights, num_heads
def _mask_init_and_local_tokens(
self,
logits: torch.Tensor,
lengths: torch.Tensor,
row_starts: Optional[torch.Tensor] = None,
):
if self.num_init_tokens == 0 and self.num_local_tokens == 0:
return logits
if row_starts is None:
row_starts = lengths.new_zeros(lengths.shape[0])
num_init_tokens = self.num_init_tokens
num_local_tokens = self.num_local_tokens
if num_init_tokens > 0:
init_idxs = (
torch.arange(
num_init_tokens, dtype=lengths.dtype, device=lengths.device
)[None, :]
+ row_starts[:, None]
)
init_idxs.clamp_max_(logits.shape[-1] - 1)
logits.scatter_(dim=1, index=init_idxs, value=float("inf"))
if num_local_tokens > 0:
local_idxs = (
lengths[:, None]
- 1
+ row_starts[:, None]
- torch.arange(
num_local_tokens, dtype=lengths.dtype, device=lengths.device
)[None, :]
)
local_idxs.clamp_min_(0)
logits.scatter_(dim=1, index=local_idxs, value=float("inf"))
return logits
def _get_topk_paged(
self,
forward_batch: ForwardBatch,
@@ -947,6 +1004,7 @@ class Indexer(MultiPlatformOp):
)
# NOTE(dark): logits should be cleaned in topk_transform
self._mask_init_and_local_tokens(logits, seqlens_32)
topk_result = metadata.topk_transform(logits, self.index_topk)
# Restore possible padding exist in the hidden states.
if not _is_hip and q_offset < q_fp8.shape[0]:
@@ -1121,10 +1179,13 @@ class Indexer(MultiPlatformOp):
clean_logits=False,
)
else:
q_padded, w_padded, _ = self._pad_heads_for_deep_gemm(
q_fp8[:q_offset], weights[:q_offset]
)
logits = deep_gemm.fp8_mqa_logits(
q_fp8[:q_offset],
q_padded,
kv_fp8,
weights[:q_offset],
w_padded,
ks,
ke,
clean_logits=False,
@@ -1132,6 +1193,7 @@ class Indexer(MultiPlatformOp):
assert logits.shape[0] == len(seq_lens_expanded)
assert logits.shape[1] == k_offset
self._mask_init_and_local_tokens(logits, seq_lens_expanded, ks)
raw_topk_result = metadata.topk_transform(logits, self.index_topk, ks=ks)
topk_result[:q_offset] = raw_topk_result
return topk_result
@@ -1173,16 +1235,20 @@ class Indexer(MultiPlatformOp):
clean_logits=False,
)
else:
q_padded, w_padded, _ = self._pad_heads_for_deep_gemm(
q_fp8[start:end], weights[start:end]
)
logits_chunk = deep_gemm.fp8_mqa_logits(
q_fp8[start:end],
q_padded,
kv_fp8,
weights[start:end],
w_padded,
ks[start:end],
ke[start:end],
clean_logits=False,
)
lengths_chunk = seq_lens_expanded[start:end]
self._mask_init_and_local_tokens(logits_chunk, lengths_chunk, ks[start:end])
# RAGGED: use global offset; PAGED: construct local cu_seqlens_q per chunk
if global_topk_offset is not None:
@@ -1379,10 +1445,11 @@ class Indexer(MultiPlatformOp):
ke = ks + ke_offset
actual_seq_q = torch.cat(actual_seq_q_list, dim=0)
with self._with_real_sm_count():
q_padded, w_padded, _ = self._pad_heads_for_deep_gemm(q_fp8, weights)
logits = deep_gemm.fp8_mqa_logits(
q_fp8,
q_padded,
kv_fp8,
weights,
w_padded,
ks,
ke,
clean_logits=False,
@@ -1425,10 +1492,11 @@ class Indexer(MultiPlatformOp):
ke = ks + ke_offset
with self._with_real_sm_count():
q_padded, w_padded, _ = self._pad_heads_for_deep_gemm(q_fp8, weights)
logits = deep_gemm.fp8_mqa_logits(
q_fp8,
q_padded,
kv_fp8,
weights,
w_padded,
ks,
ke,
clean_logits=False,
+28 -40
View File
@@ -2,10 +2,7 @@ import torch
from torch import nn
from torch.nn import Parameter
from sglang.jit_kernel.ngram_embedding import (
compute_n_gram_ids,
compute_n_gram_ids_decode,
)
from sglang.jit_kernel.ngram_embedding import compute_n_gram_ids
from sglang.srt.layers.dp_attention import is_dp_attention_enabled
from sglang.srt.layers.vocab_parallel_embedding import VocabParallelEmbedding
from sglang.srt.model_executor.forward_batch_info import ForwardBatch
@@ -20,6 +17,7 @@ class NgramEmbedding(torch.nn.Module):
over_embedding_m: int,
over_embedding_k: int,
over_embedding_n: int,
eos_token_id: int,
):
super().__init__()
assert (
@@ -30,11 +28,13 @@ class NgramEmbedding(torch.nn.Module):
self.over_embedding_m = over_embedding_m
self.over_embedding_k = over_embedding_k
self.over_embedding_n = over_embedding_n
self.eos_token_id = eos_token_id
use_attn_tp_group = is_dp_attention_enabled()
self.word_embeder = VocabParallelEmbedding(
num_embeddings,
embedding_dim,
enable_tp=is_dp_attention_enabled(),
use_attn_tp_group=use_attn_tp_group,
)
self.n_grams = (over_embedding_n - 1) * over_embedding_k
oe_hidden_dim = embedding_dim // (over_embedding_k * (over_embedding_n - 1))
@@ -51,7 +51,7 @@ class NgramEmbedding(torch.nn.Module):
self.oe_embeder = VocabParallelEmbedding(
num_embeddings=self.exclusive_oe_embedder_size_sums[-1],
embedding_dim=oe_hidden_dim,
enable_tp=is_dp_attention_enabled(),
use_attn_tp_group=use_attn_tp_group,
)
self.oe_projection = nn.Parameter(
@@ -138,40 +138,28 @@ class NgramEmbedding(torch.nn.Module):
or forward_batch.forward_mode.is_decode()
):
ngram_embedding_info = forward_batch.ngram_embedding_info
if forward_batch.forward_mode.is_decode():
compute_n_gram_ids_decode(
ne_n=self.over_embedding_n,
ne_k=self.over_embedding_k,
ne_weights=self.oe_weights,
ne_mods=self.oe_mods,
exclusive_ne_embedder_size_sums=self.exclusive_oe_embedder_size_sums,
ne_token_table=ngram_embedding_info.token_table,
row_indices=forward_batch.req_pool_indices,
column_starts=ngram_embedding_info.column_starts,
n_gram_ids=self.oe_n_gram_ids[: len(input_ids)],
)
else:
torch.cumsum(
ngram_embedding_info.req_lens,
dim=0,
dtype=torch.int32,
out=self.exclusive_req_len_sums[1 : 1 + forward_batch.batch_size],
)
compute_n_gram_ids(
ne_n=self.over_embedding_n,
ne_k=self.over_embedding_k,
ne_weights=self.oe_weights,
ne_mods=self.oe_mods,
tokens=input_ids.to(torch.int32),
exclusive_ne_embedder_size_sums=self.exclusive_oe_embedder_size_sums,
exclusive_req_len_sums=self.exclusive_req_len_sums[
: forward_batch.batch_size + 1
],
ne_token_table=ngram_embedding_info.token_table,
row_indices=forward_batch.req_pool_indices,
column_starts=ngram_embedding_info.column_starts,
n_gram_ids=self.oe_n_gram_ids[: len(input_ids)],
)
torch.cumsum(
ngram_embedding_info.req_lens,
dim=0,
dtype=torch.int32,
out=self.exclusive_req_len_sums[1 : 1 + forward_batch.batch_size],
)
compute_n_gram_ids(
ne_n=self.over_embedding_n,
ne_k=self.over_embedding_k,
ne_weights=self.oe_weights,
ne_mods=self.oe_mods,
tokens=input_ids.to(torch.int32),
exclusive_ne_embedder_size_sums=self.exclusive_oe_embedder_size_sums,
exclusive_req_len_sums=self.exclusive_req_len_sums[
: forward_batch.batch_size + 1
],
ne_token_table=ngram_embedding_info.token_table,
row_indices=forward_batch.req_pool_indices,
column_starts=ngram_embedding_info.column_starts,
n_gram_ids=self.oe_n_gram_ids[: len(input_ids)],
eos_token_id=self.eos_token_id,
)
# [13, seq_len, hidden_dim]
all_hidden_states = torch.empty(
@@ -1744,6 +1744,9 @@ class ScheduleBatch(ScheduleBatchDisaggregationDecodeMixin):
# Read by ForwardBatch ngram embedding init
ne_token_table: torch.Tensor = None
# Mask marking chunked (not-yet-finished) prefill requests whose sampled
# pseudo next-token must NOT be written into the ngram token table.
ne_skip_token_table_update: torch.Tensor = None
req_pool_indices: torch.Tensor = None # shape: [b], int64
seq_lens: torch.Tensor = None # shape: [b], int64
+15
View File
@@ -1342,6 +1342,21 @@ class Scheduler(
),
ignore_tokens=None,
)
# Mark the chunked (not-yet-finished) prefill request so sample()
# skips writing its pseudo next-token into the ngram token table.
# Use self.chunked_req identity (not req.is_chunked) to avoid
# overlap-scheduling timing issues.
if self.chunked_req is not None:
skip_token_table_update = [
req is self.chunked_req for req in batch.reqs
]
batch.ne_skip_token_table_update = (
torch.tensor(
skip_token_table_update, dtype=torch.bool, device=device
)
if any(skip_token_table_update)
else None
)
return batch
def init_deterministic_inference_config(self):
@@ -286,6 +286,9 @@ class NgramEmbeddingInfo:
req_lens: torch.Tensor
out_column_starts: torch.Tensor
out_req_lens: torch.Tensor
# Mask marking chunked (not-yet-finished) prefill requests whose sampled
# pseudo next-token must NOT be written into the token table.
skip_token_table_update: Optional[torch.Tensor] = None
@classmethod
def create(
@@ -295,6 +298,7 @@ class NgramEmbeddingInfo:
device: torch.device,
column_starts=None,
req_lens=None,
skip_token_table_update=None,
) -> NgramEmbeddingInfo:
info = cls(
token_table=token_table,
@@ -302,6 +306,7 @@ class NgramEmbeddingInfo:
req_lens=torch.empty(batch_size, dtype=torch.int32, device=device),
out_column_starts=torch.empty(batch_size, dtype=torch.int32, device=device),
out_req_lens=torch.empty(batch_size, dtype=torch.int32, device=device),
skip_token_table_update=skip_token_table_update,
)
if column_starts is not None:
info.column_starts[:] = column_starts
@@ -316,6 +321,11 @@ class NgramEmbeddingInfo:
req_lens=self.req_lens[:bs],
out_column_starts=self.out_column_starts[:bs],
out_req_lens=self.out_req_lens[:bs],
skip_token_table_update=(
self.skip_token_table_update[:bs]
if self.skip_token_table_update is not None
else None
),
)
@@ -1003,6 +1013,7 @@ class ForwardBatch(ForwardBatchDeepSeekMHAMixin):
device,
column_starts=column_starts,
req_lens=req_lens,
skip_token_table_update=batch.ne_skip_token_table_update,
)
def compute_spec_mrope_positions(
@@ -32,7 +32,6 @@ import torch
import torch.distributed as dist
from torch import nn
from sglang.jit_kernel.ngram_embedding import update_token_table_decode
from sglang.srt.configs import (
BailingHybridConfig,
FalconH1Config,
@@ -165,6 +164,9 @@ from sglang.srt.model_executor.hook_manager import register_forward_hooks
from sglang.srt.model_executor.model_runner_kv_cache_mixin import (
ModelRunnerKVCacheMixin,
)
from sglang.srt.model_executor.ngram_token_table import (
update_ngram_token_table_after_sampling,
)
from sglang.srt.model_executor.pool_configurator import MemoryPoolConfig
from sglang.srt.model_executor.runner import (
EagerRunner,
@@ -2606,15 +2608,12 @@ class ModelRunner(ModelRunnerKVCacheMixin):
ngram_embedding_info = forward_batch.ngram_embedding_info
if ngram_embedding_info is None:
return
ngram_embedding_info.out_column_starts[: forward_batch.batch_size] = (
forward_batch.seq_lens
)
ngram_embedding_info.out_req_lens[: forward_batch.batch_size] = 1
update_token_table_decode(
ne_token_table=ngram_embedding_info.token_table,
tokens=next_token_ids.to(torch.int32),
row_indices=forward_batch.req_pool_indices,
column_starts=ngram_embedding_info.out_column_starts,
update_ngram_token_table_after_sampling(
ngram_embedding_info=ngram_embedding_info,
next_token_ids=next_token_ids,
req_pool_indices=forward_batch.req_pool_indices,
seq_lens=forward_batch.seq_lens,
batch_size=forward_batch.batch_size,
)
def init_decode_cuda_graph(self):
@@ -0,0 +1,51 @@
"""Utilities for updating LongCat ngram embedding token tables."""
from __future__ import annotations
import torch
from sglang.jit_kernel.ngram_embedding import update_token_table
def update_ngram_token_table_after_sampling(
*,
ngram_embedding_info,
next_token_ids: torch.Tensor,
req_pool_indices: torch.Tensor,
seq_lens: torch.Tensor,
batch_size: int,
) -> bool:
"""Update the ngram token table with sampled tokens.
Returns whether the token table was updated.
"""
skip_token_table_update = ngram_embedding_info.skip_token_table_update
if skip_token_table_update is not None:
# Skip chunked (not-yet-finished) prefill requests: their sampled token
# is a pseudo prediction and must not pollute the token table.
indices = (~skip_token_table_update).nonzero(as_tuple=True)[0]
if indices.numel() == 0:
return False
update_token_table(
ne_token_table=ngram_embedding_info.token_table,
tokens=next_token_ids[indices].to(torch.int32),
row_indices=req_pool_indices[indices],
column_starts=seq_lens[indices].to(torch.int32),
req_lens=torch.ones(
indices.numel(), dtype=torch.int32, device=next_token_ids.device
),
ignore_tokens=None,
)
return True
ngram_embedding_info.out_column_starts[:batch_size] = seq_lens
ngram_embedding_info.out_req_lens[:batch_size] = 1
update_token_table(
ne_token_table=ngram_embedding_info.token_table,
tokens=next_token_ids.to(torch.int32),
row_indices=req_pool_indices,
column_starts=ngram_embedding_info.out_column_starts,
req_lens=ngram_embedding_info.out_req_lens,
ignore_tokens=None,
)
return True
@@ -147,6 +147,7 @@ def _allocate_decode_buffers(
req_lens=torch.ones([max_bs], dtype=torch.int32),
out_column_starts=torch.zeros([max_bs], dtype=torch.int32),
out_req_lens=torch.ones([max_bs], dtype=torch.int32),
skip_token_table_update=torch.zeros([max_bs], dtype=torch.bool),
)
if ne_token_table is not None
else None
@@ -297,6 +298,7 @@ class BaseRunner(ABC):
num_tokens_per_bs=num_tokens_per_bs,
cache_loc_dtype=torch.int64,
enable_mamba_track=False,
ne_token_table=mr.token_table if mr.use_ngram_embedding else None,
hc_hidden_size=getattr(mr.model_config, "hc_hidden_size", None),
pp_proxy_topk_size=mr.get_pp_proxy_topk_size(),
)
@@ -524,6 +526,10 @@ class BaseRunner(ABC):
global_forward_mode=capture_forward_mode,
lora_ids=lora_ids,
)
if buffers.ngram_embedding_info is not None:
forward_batch.ngram_embedding_info = buffers.ngram_embedding_info.slice(
batch_size
)
if lora_ids is not None:
mr.lora_manager.prepare_lora_batch(forward_batch)
@@ -169,6 +169,7 @@ class DecodeInputBuffers(ForwardInputBuffers):
req_lens=torch.ones([max_bs], dtype=torch.int32),
out_column_starts=torch.zeros([max_bs], dtype=torch.int32),
out_req_lens=torch.ones([max_bs], dtype=torch.int32),
skip_token_table_update=torch.zeros([max_bs], dtype=torch.bool),
)
if ne_token_table is not None
else None
+8 -2
View File
@@ -1654,6 +1654,7 @@ class DeepseekV2AttentionMLA(
quant_config=quant_config,
layer_id=layer_id,
alt_stream=alt_stream,
config=config,
)
# Refer: https://arxiv.org/abs/2603.12201 for more details.
# skip_topk: when True, this layer will skip computation and reuse previous layer's topk indices.
@@ -1662,8 +1663,13 @@ class DeepseekV2AttentionMLA(
self.skip_topk = True
self.next_skip_topk = True
else:
self.skip_topk = dsa_layer_skips_topk(config, layer_id)
self.next_skip_topk = dsa_layer_skips_topk(config, layer_id + 1)
index_cli_factor = getattr(config, "cli_factor", 1)
if index_cli_factor > 1:
self.skip_topk = layer_id % index_cli_factor != 0
self.next_skip_topk = (layer_id + 1) % index_cli_factor != 0
else:
self.skip_topk = dsa_layer_skips_topk(config, layer_id)
self.next_skip_topk = dsa_layer_skips_topk(config, layer_id + 1)
self.kv_b_proj = ColumnParallelLinear(
self.kv_lora_rank,
+40 -11
View File
@@ -326,8 +326,8 @@ class LongcatFlashDecoderLayer(nn.Module):
v_head_dim=config.v_head_dim,
q_lora_rank=config.q_lora_rank,
kv_lora_rank=config.kv_lora_rank,
rope_theta=config.rope_parameters["rope_theta"],
rope_scaling=None,
rope_theta=config.rope_theta,
rope_scaling=config.rope_scaling,
max_position_embeddings=config.max_position_embeddings,
quant_config=(
None
@@ -420,18 +420,24 @@ class LongcatFlashDecoderLayer(nn.Module):
forward_batch: ForwardBatch,
residual: Optional[torch.Tensor],
zero_allocator: BumpAllocator,
prev_topk_indices: Optional[torch.Tensor],
) -> torch.Tensor:
# first_attn
hidden_states, residual = self.moe_layer_communicator.prepare_attn(
hidden_states, residual, forward_batch
)
if hidden_states.shape[0] != 0:
hidden_states = self.self_attn[0](
attn_out = self.self_attn[0](
positions=positions,
hidden_states=hidden_states,
forward_batch=forward_batch,
zero_allocator=zero_allocator,
prev_topk_indices=prev_topk_indices,
)
if isinstance(attn_out, tuple):
hidden_states, prev_topk_indices = attn_out
else:
hidden_states = attn_out
# moe
hidden_states, residual = self.moe_layer_communicator.prepare_mlp(
@@ -444,15 +450,26 @@ class LongcatFlashDecoderLayer(nn.Module):
moe_hidden_states, moe_residual, forward_batch
)
hidden_states, residual = self.forward_mlp(
hidden_states, positions, residual, forward_batch, zero_allocator
hidden_states, residual, prev_topk_indices = self.forward_mlp(
hidden_states,
positions,
residual,
forward_batch,
zero_allocator,
prev_topk_indices,
)
hidden_states = moe_hidden_states + hidden_states
return hidden_states, residual
return hidden_states, residual, prev_topk_indices
def forward_mlp(
self, hidden_states, positions, residual, forward_batch, zero_allocator
self,
hidden_states,
positions,
residual,
forward_batch,
zero_allocator,
prev_topk_indices,
):
# first_mlp
hidden_states = self.mlps[0](hidden_states)
@@ -464,12 +481,17 @@ class LongcatFlashDecoderLayer(nn.Module):
hidden_states, residual, forward_batch
)
if hidden_states.shape[0] != 0:
hidden_states = self.self_attn[1](
attn_out = self.self_attn[1](
positions=positions,
hidden_states=hidden_states,
forward_batch=forward_batch,
zero_allocator=zero_allocator,
prev_topk_indices=prev_topk_indices,
)
if isinstance(attn_out, tuple):
hidden_states, prev_topk_indices = attn_out
else:
hidden_states = attn_out
# second_mlp
hidden_states, residual = self.mlp_layer_communicator[1].prepare_mlp(
@@ -483,7 +505,7 @@ class LongcatFlashDecoderLayer(nn.Module):
hidden_states, residual, forward_batch
)
return hidden_states, residual
return hidden_states, residual, prev_topk_indices
class LongcatFlashModel(nn.Module):
@@ -506,6 +528,7 @@ class LongcatFlashModel(nn.Module):
over_embedding_m=config.ngram_embedding_m,
over_embedding_k=config.ngram_embedding_k,
over_embedding_n=config.ngram_embedding_n,
eos_token_id=config.eos_token_id,
)
else:
self.use_ngram_embedding = False
@@ -559,13 +582,19 @@ class LongcatFlashModel(nn.Module):
residual = None
aux_hidden_states = []
topk_indices = None
for i in range(total_num_layers):
if i in self.layers_to_capture:
aux_hidden_states.append(hidden_states + residual)
with get_global_expert_distribution_recorder().with_current_layer(i):
layer = self.layers[i]
hidden_states, residual = layer(
positions, hidden_states, forward_batch, residual, zero_allocator
hidden_states, residual, topk_indices = layer(
positions,
hidden_states,
forward_batch,
residual,
zero_allocator,
topk_indices,
)
if hidden_states.shape[0] != 0:
+1
View File
@@ -331,6 +331,7 @@ class Flags(_StaticFlags):
sampling_backend: str | None = None
page_size: int | None = None
quantization: str | None = None
fp8_gemm_runner_backend: str = "auto"
disable_overlap_schedule: bool = False
uses_mamba_radix_cache: bool = False
mamba_radix_cache_strategy: str = "auto"
+2
View File
@@ -1340,6 +1340,7 @@ class ServerArgs:
help="Choose the runner backend for Blockwise FP8 GEMM operations. Options: 'auto' (default, auto-selects based on hardware), 'deep_gemm' (JIT-compiled; enabled by default on NVIDIA Hopper (SM90) and Blackwell (SM100) when DeepGEMM is installed), 'flashinfer_trtllm' (optimal for Blackwell and low-latency), 'flashinfer_cutlass' (FlashInfer CUTLASS groupwise FP8 GEMM), 'flashinfer_deepgemm' (Hopper SM90 only; uses swapAB optimization for small M dimensions in decoding), 'cutlass' (optimal for Hopper/Blackwell GPUs and high-throughput), 'triton' (fallback, widely compatible), 'aiter' (ROCm only). ",
cli_name="--fp8-gemm-backend",
choices=FP8_GEMM_RUNNER_BACKEND_CHOICES,
resolvable=True,
),
] = "auto"
fp4_gemm_runner_backend: A[
@@ -3879,6 +3880,7 @@ class ServerArgs:
"MistralLarge3ForCausalLM",
"PixtralForConditionalGeneration",
"GlmMoeDsaForCausalLM",
"LongcatFlashForCausalLM",
]:
# Set attention backend for DeepSeek
if is_deepseek_dsa(hf_config): # DeepSeek 3.2/GLM 5
@@ -16,6 +16,7 @@
from pathlib import Path
from typing import Optional
from transformers import PretrainedConfig
from transformers.models.auto.modeling_auto import MODEL_FOR_CAUSAL_LM_MAPPING_NAMES
from sglang.srt.configs.model_config_parser_registry import (
@@ -51,6 +52,26 @@ def _apply_deepseek_ocr_overrides(config, model):
config._name_or_path = model
_LONGCAT_ARCHS = {
"LongcatCausalLM",
"LongcatFlashForCausalLM",
"LongcatFlashNgramForCausalLM",
}
def _try_load_longcat_config(model, revision: Optional[str], **kwargs):
config_dict, _ = PretrainedConfig.get_config_dict(
model, revision=revision, **kwargs
)
architectures = config_dict.get("architectures") or []
if not any(arch in _LONGCAT_ARCHS for arch in architectures):
return None
return _CONFIG_REGISTRY["longcat_flash"].from_pretrained(
model, revision=revision, **kwargs
)
@register_model_config_parser("hf")
class HfModelConfigParser(ModelConfigParserBase):
def parse(
@@ -60,12 +81,14 @@ class HfModelConfigParser(ModelConfigParserBase):
revision: Optional[str] = None,
**kwargs,
):
config = AutoConfig.from_pretrained(
model,
trust_remote_code=trust_remote_code,
revision=revision,
**kwargs,
)
config = _try_load_longcat_config(model, revision, **kwargs)
if config is None:
config = AutoConfig.from_pretrained(
model,
trust_remote_code=trust_remote_code,
revision=revision,
**kwargs,
)
if (
config.architectures is not None