Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions docs/CN/source/cookbook/sensenova_loop_deployment.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
.. _sensenova_loop_deployment:

SenseNova Loop 部署指南
===========================

启动服务
--------

下面以 4 卡部署为例,在仓库根目录执行:

.. code-block:: bash

CUDA_VISIBLE_DEVICES=0,1,2,3 LOADWORKER=4 LIGHTLLM_TRITON_AUTOTUNE_LEVEL=1 \
python -m lightllm.server.api_server \
--model_dir /path/to/SenseNova-Loop \
--model_name sensenova_loop \
--tp 4 \
--port 8000

启用 MTP
--------

checkpoint 已包含 MTP 权重,增加以下参数即可启用:

.. code-block:: bash

CUDA_VISIBLE_DEVICES=0,1,2,3 LOADWORKER=4 LIGHTLLM_TRITON_AUTOTUNE_LEVEL=1 \
python -m lightllm.server.api_server \
--model_dir /path/to/SenseNova-Loop \
--model_name sensenova_loop \
--mtp_mode eagle_with_att \
--mtp_step 3 \
--tp 4 \
--port 8000

2P1D 部署
---------

下面的脚本默认使用 8 张 GPU,启动两个 TP2 Prefill 服务和一个 TP4 Decode 服务,并启用 MTP:

.. code-block:: bash

MODEL_DIR=/path/to/SenseNova-Loop PORT=8000 \
bash test/start_scripts/sensenova_loop/sensenova_loop_pd_2p1d.sh

可通过 ``GPU_IDS`` 指定显卡列表,添加 ``DRY_RUN=1`` 可先查看启动命令。
按 ``Ctrl+C`` 会关闭所有服务。

请求示例
--------

.. code-block:: bash

curl http://localhost:8000/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{
"model": "sensenova_loop",
"messages": [{"role": "user", "content": "你好,请用一句话介绍自己。"}],
"max_tokens": 128
}'
1 change: 1 addition & 0 deletions docs/CN/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ Lightllm 整合了众多的开源方案的优点,包括但不限于 FasterTran

GLM-4.7-Flash 部署 <cookbook/glm4_deployment>
Qwen3.5 部署 <cookbook/qwen35_deployment>
SenseNova Loop 部署 <cookbook/sensenova_loop_deployment>

.. toctree::
:maxdepth: 1
Expand Down
60 changes: 60 additions & 0 deletions docs/EN/source/cookbook/sensenova_loop_deployment.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
.. _sensenova_loop_deployment:

SenseNova Loop Deployment Guide
==============================

Start the Server
----------------

This example starts a BF16 service on four GPUs. Run it from the repository root:

.. code-block:: bash

CUDA_VISIBLE_DEVICES=0,1,2,3 LOADWORKER=4 LIGHTLLM_TRITON_AUTOTUNE_LEVEL=1 \
python -m lightllm.server.api_server \
--model_dir /path/to/SenseNova-Loop \
--model_name sensenova_loop \
--tp 4 \
--port 8000

Enable MTP
----------

The checkpoint includes MTP weights. Add the following options to enable MTP:

.. code-block:: bash

CUDA_VISIBLE_DEVICES=0,1,2,3 LOADWORKER=4 LIGHTLLM_TRITON_AUTOTUNE_LEVEL=1 \
python -m lightllm.server.api_server \
--model_dir /path/to/SenseNova-Loop \
--model_name sensenova_loop \
--mtp_mode eagle_with_att \
--mtp_step 3 \
--tp 4 \
--port 8000

2P1D Deployment
---------------

This script uses eight GPUs to start two TP2 prefill services and one TP4 decode service, with MTP enabled:

.. code-block:: bash

MODEL_DIR=/path/to/SenseNova-Loop PORT=8000 \
bash test/start_scripts/sensenova_loop/sensenova_loop_pd_2p1d.sh

Set ``GPU_IDS`` to select the GPUs, or add ``DRY_RUN=1`` to preview the commands.
Press ``Ctrl+C`` to stop all services.

Example Request
---------------

.. code-block:: bash

curl http://localhost:8000/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{
"model": "sensenova_loop",
"messages": [{"role": "user", "content": "Hello, introduce yourself in one sentence."}],
"max_tokens": 128
}'
1 change: 1 addition & 0 deletions docs/EN/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ Documentation List

GLM-4.7-Flash Deployment <cookbook/glm4_deployment>
Qwen3.5 Deployment <cookbook/qwen35_deployment>
SenseNova Loop Deployment <cookbook/sensenova_loop_deployment>

.. toctree::
:maxdepth: 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ def mtp_diverse_stage1_single_token(

Lq, Lk = int(q.shape[2]), int(k.shape[2])
assert Lq == Lk
assert Lk in {16, 32, 64, 128}
assert Lk in {16, 32, 64, 128, 256}
batch = int(B_req_idx.shape[0])
kv_head_num = int(k.shape[1])
q_head_num = int(q.shape[1])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ def mtp_diverse_stage2_single_token(
num_stages = run_config["num_stages"]

Lk = mid_out.shape[-1]
assert Lk in {16, 32, 64, 128}
assert Lk in {16, 32, 64, 128, 256}
batch, head_num = mid_out.shape[0], mid_out.shape[1]
grid = (batch, head_num)

Expand Down
5 changes: 1 addition & 4 deletions lightllm/common/req_manager/linear_att.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,7 @@ def save_state(self, req: "InferReq", buffer_idx: int, state_cache_manager: Line
dst_ssm_state.copy_(gpu_ssm_state, non_blocking=True)

def get_mamba_cache(self, layer_idx_in_all: int):
assert (
0 <= layer_idx_in_all < self.linear_config.all_layer_num
), f"invalid transformer layer index {layer_idx_in_all}"
layer_idx_in_linear = layer_idx_in_all - (layer_idx_in_all // self.linear_config.full_attention_interval)
layer_idx_in_linear = self.linear_config.get_linear_state_layer_index(layer_idx_in_all)
conv_states = self.req_to_conv_state.buffer[layer_idx_in_linear]
ssm_states = self.req_to_ssm_state.buffer[layer_idx_in_linear]
return conv_states, ssm_states
Expand Down
2 changes: 1 addition & 1 deletion lightllm/common/state_cache_manager/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ def get_hybrid_cache_config():
return DeepseekV4CacheConfig.from_model_config(model_cfg, args)
if model_type in ("glm5_next", "glm5_next_text"):
return Glm5NextCacheConfig.from_model_config(model_cfg, args)
if model_type in ("qwen3_5", "qwen3_5_moe", "qwen3_5_text", "qwen3_5_moe_text"):
if model_type in ("qwen3_5", "sensenova_loop", "qwen3_5_moe", "qwen3_5_text", "qwen3_5_moe_text"):
return LinearAttCacheConfig.load_from_args()
raise ValueError("No hybrid state-cache layout registered for this model")
8 changes: 1 addition & 7 deletions lightllm/common/state_cache_manager/glm5_next.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,6 @@ def from_model_config(cls, config, args):
linear = config["linear_attn_config"]
tp = args.tp // args.dp
assert linear["num_heads"] % tp == 0
layers = config["num_hidden_layers"]
assert config["layer_types"] == [
"deepseek_sparse_attention" if i % 4 == 3 else "linear_attention" for i in range(layers)
]
dtype = get_torch_dtype(args.data_type)
# Raw index keys and compression scores only live in request tails.
packed_dim = config["kv_lora_rank"] + cls.INDEX_PADDING_BYTES // dtype.itemsize
Expand All @@ -41,11 +37,9 @@ def from_model_config(cls, config, args):
head_linear_k_dim=linear["head_dim"],
head_linear_v_dim=linear["head_dim"],
conv_kernel_size=linear["short_conv_kernel_size"],
linear_layer_num=len(linear["kda_layers"]),
conv_state_dtype=dtype,
ssm_state_dtype=get_torch_dtype(args.linear_att_ssm_data_type),
full_attention_interval=4,
all_layer_num=layers,
layer_types=config["layer_types"],
draft_full_att_kv_layer_num=get_added_mtp_kv_layer_num() if args.mtp_mode is not None else 0,
index_kpool=config["index_kpool"],
index_head_dim=config["index_head_dim"],
Expand Down
58 changes: 31 additions & 27 deletions lightllm/common/state_cache_manager/linear_att.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,32 @@ class LinearAttCacheConfig:
head_linear_k_dim: int
head_linear_v_dim: int
conv_kernel_size: int
linear_layer_num: int
conv_state_dtype: torch.dtype
ssm_state_dtype: torch.dtype
full_attention_interval: int
all_layer_num: int # 包括 linear att 和 full att 的层加起来的层数
layer_types: tuple[str, ...]
draft_full_att_kv_layer_num: int = 0
linear_layer_num: int = dataclasses.field(init=False)
all_layer_num: int = dataclasses.field(init=False)

def __post_init__(self):
self.layer_types = tuple(self.layer_types)
indices = []
full_count = linear_count = 0
for kind in self.layer_types:
if kind == "linear_attention":
indices.append(linear_count)
linear_count += 1
else:
indices.append(full_count)
full_count += 1
# MTP draft layers only use full-attention KV slots after the main model.
indices.extend(range(full_count, full_count + self.draft_full_att_kv_layer_num))
self._layer_indices = tuple(indices)
self.linear_layer_num = linear_count
self.all_layer_num = len(self.layer_types)

def get_linear_state_layer_index(self, layer_index: int) -> int:
return self._layer_indices[layer_index]

def get_conv_dim(self):
# 第一项对应q的参数,第二项对应k的参数,第三项对应v的参数
Expand All @@ -49,7 +69,6 @@ def get_conv_dim(self):

def get_main_model_full_att_layer_num(self):
full_att_layer_num = self.all_layer_num - self.linear_layer_num
assert full_att_layer_num == self.all_layer_num // self.full_attention_interval
return full_att_layer_num

def get_full_att_kv_layer_num_with_draft_model(self):
Expand All @@ -58,16 +77,7 @@ def get_full_att_kv_layer_num_with_draft_model(self):
def get_full_att_kv_layer_index(self, layer_index: int) -> int:
"""Map a global target/draft layer index to the packed full-attention cache."""

layer_index = int(layer_index)
if layer_index >= self.all_layer_num:
kv_layer_index = layer_index - self.linear_layer_num
else:
kv_layer_index = layer_index // self.full_attention_interval
assert 0 <= kv_layer_index < self.get_full_att_kv_layer_num_with_draft_model(), (
f"layer {layer_index} maps outside the packed full-attention KV cache: "
f"slot={kv_layer_index}, slots={self.get_full_att_kv_layer_num_with_draft_model()}"
)
return kv_layer_index
return self._layer_indices[layer_index]

def get_conv_state_shape(self):
# Base committed sliding-window state, without speculative MTP tail.
Expand Down Expand Up @@ -117,17 +127,13 @@ def load_from_args() -> "LinearAttCacheConfig":
from transformers.configuration_utils import PretrainedConfig

model_cfg, _ = PretrainedConfig.get_config_dict(model_path)
model_type = model_cfg["model_type"]
assert model_type in ["qwen3_5", "qwen3_5_moe", "qwen3_5_text", "qwen3_5_moe_text"]
llm_config = model_cfg
try:
llm_config = llm_config["text_config"]
except:
pass

n_layer = llm_config["num_hidden_layers"]
llm_config = model_cfg.get("text_config", model_cfg)
layer_types = llm_config["layer_types"]
if model_cfg["model_type"] == "sensenova_loop":
from lightllm.models.sensenova_loop.config import get_loop_layer_types

tp_world_size = get_env_start_args().tp // get_env_start_args().dp
layer_types = get_loop_layer_types(model_cfg["loop_settings"], llm_config)
tp_world_size = args.tp // args.dp
full_att_dtype = (
torch.uint8 if args.llm_kv_type in {"fp8kv_sph", "fp8kv_spt"} else get_torch_dtype(args.data_type)
)
Expand All @@ -144,11 +150,9 @@ def load_from_args() -> "LinearAttCacheConfig":
head_linear_k_dim=llm_config["linear_key_head_dim"],
head_linear_v_dim=llm_config["linear_value_head_dim"],
conv_kernel_size=llm_config["linear_conv_kernel_dim"],
linear_layer_num=n_layer - (n_layer // llm_config["full_attention_interval"]),
conv_state_dtype=get_torch_dtype(args.data_type),
ssm_state_dtype=get_torch_dtype(args.linear_att_ssm_data_type),
full_attention_interval=llm_config["full_attention_interval"],
all_layer_num=n_layer,
layer_types=layer_types,
draft_full_att_kv_layer_num=get_added_mtp_kv_layer_num(),
)

Expand Down
5 changes: 4 additions & 1 deletion lightllm/models/builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ def _tarsier_text_model_is(model_type):
)
ModelRegistry.register("qwen3", "lightllm.models.qwen3.model:Qwen3TpPartModel")
ModelRegistry.register(["qwen3_5"], "lightllm.models.qwen3_5.model:Qwen3_5TpPartModel", is_multimodal=True)
ModelRegistry.register(
"sensenova_loop", "lightllm.models.sensenova_loop.model:SenseNovaLoopTpPartModel", is_multimodal=True
)
ModelRegistry.register("qwen3_5_moe", "lightllm.models.qwen3_5_moe.model:Qwen3_5MOETpPartModel", is_multimodal=True)
ModelRegistry.register("qwen3_moe", "lightllm.models.qwen3_moe.model:Qwen3MOEModel")
ModelRegistry.register(
Expand Down Expand Up @@ -186,7 +189,7 @@ def _tarsier_text_model_is(model_type):
"lightllm.models.qwen3_5_moe_mtp.model:Qwen3_5MoeMTPModel",
)
DraftModelRegistry.register(
("qwen3_5", "qwen3_5_text"),
("qwen3_5", "qwen3_5_text", "sensenova_loop"),
("vanilla_with_att", "eagle_with_att"),
"lightllm.models.qwen3_5_mtp.model:Qwen3_5MTPModel",
)
Expand Down
31 changes: 3 additions & 28 deletions lightllm/models/qwen3next/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
)
from lightllm.models.qwen3next.infer_struct import Qwen3NextInferStateInfo
from lightllm.utils.log_utils import init_logger
from lightllm.utils.envs_utils import get_added_mtp_kv_layer_num, get_env_start_args
from lightllm.utils.envs_utils import get_env_start_args
from lightllm.common.kv_cache_mem_manager.qwen3next_mem_manager import (
FP8StaticPerHeadQuantQwen3NextMemManager,
FP8StaticPerTensorQuantQwen3NextMemManager,
Expand All @@ -25,7 +25,6 @@


class Qwen3NextTpPartModel(Qwen3MOEModel):

# weight class
pre_and_post_weight_class = Qwen3NextPreAndPostLayerWeight
transformer_weight_class = Qwen3NextTransformerLayerWeight
Expand Down Expand Up @@ -60,31 +59,6 @@ def _init_config(self):
def _init_mem_manager(self):
assert self.config["num_attention_heads"] % self.tp_world_size_ == 0
start_args: StartArgs = get_env_start_args()
ssm_dtype_dict = {"bfloat16": torch.bfloat16, "float32": torch.float32}
draft_full_att_kv_layer_num = get_added_mtp_kv_layer_num()
full_att_dtype = torch.uint8 if start_args.llm_kv_type in {"fp8kv_sph", "fp8kv_spt"} else self.data_type
self.linear_config = LinearAttCacheConfig(
tp_world_size=self.tp_world_size_,
full_att_all_num_kv_heads=self.config["num_key_value_heads"],
full_att_dtype=full_att_dtype,
full_att_num_kv_heads=self.num_kv_heads,
full_att_head_dim=self.config["head_dim"],
global_linear_k_heads=self.config["linear_num_key_heads"],
global_linear_v_heads=self.config["linear_num_value_heads"],
num_linear_k_heads=max(1, self.config["linear_num_key_heads"] // self.tp_world_size_),
num_linear_v_heads=max(1, self.config["linear_num_value_heads"] // self.tp_world_size_),
head_linear_k_dim=self.config["linear_key_head_dim"],
head_linear_v_dim=self.config["linear_value_head_dim"],
conv_kernel_size=self.config["linear_conv_kernel_dim"],
linear_layer_num=self.config["n_layer"]
- (self.config["n_layer"] // self.config["full_attention_interval"]),
conv_state_dtype=self.data_type,
ssm_state_dtype=ssm_dtype_dict[start_args.linear_att_ssm_data_type],
full_attention_interval=self.config["full_attention_interval"],
all_layer_num=self.config["n_layer"],
draft_full_att_kv_layer_num=draft_full_att_kv_layer_num,
)

if start_args.llm_kv_type == "None":
mem_manager_class = Qwen3NextMemManager
elif start_args.llm_kv_type == "fp8kv_sph":
Expand All @@ -104,6 +78,7 @@ def _init_mem_manager(self):
)

def _init_req_manager(self):
self.linear_config = LinearAttCacheConfig.load_from_args()
create_max_seq_len = 0

if self.batch_max_tokens is not None:
Expand All @@ -112,7 +87,7 @@ def _init_req_manager(self):
create_max_seq_len = max(create_max_seq_len, self.max_seq_length)

self.req_manager = ReqManagerForMamba(
self.max_req_num, create_max_seq_len, None, linear_config=LinearAttCacheConfig.load_from_args()
self.max_req_num, create_max_seq_len, None, linear_config=self.linear_config
)
return

Expand Down
Empty file.
14 changes: 14 additions & 0 deletions lightllm/models/sensenova_loop/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""SenseNova Loop execution layout."""


def get_loop_layer_indices(settings: dict, num_layers: int) -> tuple[int, ...]:
"""Map execution depth to checkpoint layers; caches use execution depth."""
start, end, count = settings["start"], settings["end"], settings["eval_count"]
return tuple(range(start)) + tuple(range(start, end)) * count + tuple(range(end, num_layers))


def get_loop_layer_types(settings: dict, text_config: dict) -> tuple[str, ...]:
num_layers = text_config["num_hidden_layers"]
indices = get_loop_layer_indices(settings, num_layers)
layer_types = text_config["layer_types"]
return tuple(layer_types[i] for i in indices)
Empty file.
Loading
Loading