Skip to content
Open
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
45 changes: 45 additions & 0 deletions lightllm/common/basemodel/attention/fa3/fp.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@
from lightllm.common.basemodel.triton_kernel.gen_prefill_params import gen_cumsum_pad0_tensor
from lightllm.common.basemodel.triton_kernel.mtp_utils import build_mtp_shared_group_markers

try:
from flash_attn_interface import flash_attn_with_kvcache as flash_attn_with_kvcache_neo
import inspect

# Verify this is the neo-patched FA3 build (with image_token_tag support),
_sig = inspect.signature(flash_attn_with_kvcache_neo)
if "image_token_tag" not in _sig.parameters:
raise ImportError("flash_attn_interface found but missing image_token_tag support (need neo build)")

HAS_FLASH_ATTN_INTERFACE = True
except ImportError:
flash_attn_with_kvcache_neo = None
HAS_FLASH_ATTN_INTERFACE = False


class Fa3AttBackend(BaseAttBackend):
"""Common fixed page-table storage for FA3 attention backends."""
Expand Down Expand Up @@ -129,6 +143,37 @@ def _nomarl_prefill_att(
k_descale, v_descale = None, None # disable quantization
Lq = q.shape[-1]
sm_scale = 1.0 / (Lq ** 0.5)

# neo_chat*: image-token bidirectional attention requires flash_attn_interface
# (sgl_kernel's flash_attn_with_kvcache does not support image_token_tag).
if att_control.image_token_tag is not None:
if not HAS_FLASH_ATTN_INTERFACE:
raise ImportError(
"flash_attn_interface (fa3-neo) is required for image_token_tag bidirectional "
"attention. Install it or set LIGHTLLM_NEO_PREFILL_TRITON_BACKEND=1 to use the "
"triton fallback."
)
extra_kwargs = {"image_token_tag": att_control.image_token_tag}
o = flash_attn_with_kvcache_neo(
q=q,
k_cache=k.view(k.shape[0], 1, k.shape[1], k.shape[2]),
v_cache=v.view(v.shape[0], 1, v.shape[1], v.shape[2]),
page_table=self.page_table,
cache_seqlens=self.infer_state.b_seq_len,
cu_seqlens_q=self.cu_seqlens_q,
cu_seqlens_k_new=self.cu_seqlens_k,
max_seqlen_q=self.infer_state.max_q_seq_len,
softmax_scale=sm_scale,
causal=True,
window_size=window_size,
softcap=0.0,
k_descale=k_descale,
v_descale=v_descale,
return_softmax_lse=False,
**extra_kwargs,
)
return o

o = flash_attn_with_kvcache(
q=q,
k_cache=k.view(k.shape[0], 1, k.shape[1], k.shape[2]),
Expand Down
2 changes: 2 additions & 0 deletions lightllm/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
)
from lightllm.models.gpt_oss.model import GptOssTpPartModel
from lightllm.models.qwen3_omni_moe_thinker.model import Qwen3OmniMOETpPartModel
from lightllm.models.neo_chat_moe.model import NeoTpMOEPartModel
from lightllm.models.neo_chat.model import NeoTpPartModel
from lightllm.models.qwen3_5.model import Qwen3_5TpPartModel
from lightllm.models.qwen3_5_moe.model import Qwen3_5MOETpPartModel
from lightllm.models.deepseek_mtp.model import Deepseek3MTPModel
Expand Down
55 changes: 51 additions & 4 deletions lightllm/models/llama/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,20 @@ def _init_custom(self):
rope_scaling = self.config.get("rope_scaling", None)
if rope_scaling is None:
self._init_to_get_rotary()
return

if "rope_type" in rope_scaling:
elif "rope_type" in rope_scaling:
scaling_type = rope_scaling["rope_type"]
self._init_rotary_by_scaling_type(scaling_type, rope_scaling)
elif "type" in rope_scaling:
scaling_type = rope_scaling["type"]
self._init_rotary_by_scaling_type(scaling_type, rope_scaling)
else:
raise ValueError(f"Unknown RoPE scaling format {rope_scaling}")

if "rope_theta_hw" in self.config:
self._init_to_get_hw_rotary()
super()._init_custom()

def _init_rotary_by_scaling_type(self, scaling_type, rope_scaling):
if scaling_type == "default" or "mrope_section" in rope_scaling:
self._init_to_get_rotary()
elif scaling_type == "yarn":
Expand Down Expand Up @@ -126,9 +132,10 @@ def _init_to_get_rotary(self, default_base=10000):
except:
pass

inv_freq = 1.0 / (
full_inv_freq = 1.0 / (
base ** (torch.arange(0, partial_head_dim, 2, device="cpu", dtype=torch.float32) / partial_head_dim)
)
inv_freq = full_inv_freq[::2] # for neo
t = (
torch.arange(max(max_seq_len + 1024 * 128, self.max_seq_length), device="cpu", dtype=torch.float32)
/ rope_scaling_factor
Expand All @@ -139,6 +146,46 @@ def _init_to_get_rotary(self, default_base=10000):
self._sin_cached = torch.sin(freqs).to(self.data_type).cuda()
return

def _init_to_get_hw_rotary(self, default_base=10000):
partial_head_dim = int(self.config.get("partial_rotary_factor", 1) * self.head_dim_ // 2)
if self.config.get("rope_scaling", {}) is None:
rope_scaling_factor = 1.0
else:
rope_scaling_factor = self.config.get("rope_scaling", {}).get("factor", 1.0)

base = self.config.get("rope_theta_hw", float(default_base))
if "max_sequence_length" in self.config:
max_seq_len = self.config["max_sequence_length"]
else:
max_position_embeddings = self.config.get(
"max_position_embeddings_hw", 2048 if base <= 10000.0 + 1e-5 else 16384
)
max_seq_len = max_position_embeddings * rope_scaling_factor

# NTK
try:
ntk_alpha = float(os.environ.get("LIGHTLLM_NTK_ALPHA", 1))
assert ntk_alpha >= 1
if ntk_alpha > 1:
logger.info(f"Note: NTK enabled, alpha set to {ntk_alpha}")
max_seq_len *= ntk_alpha
base = base * (ntk_alpha ** (partial_head_dim / (partial_head_dim - 2))) # Base change formula
except:
pass
full_inv_freq = 1.0 / (
base ** (torch.arange(0, partial_head_dim, 2, device="cpu", dtype=torch.float32) / partial_head_dim)
)
inv_freq = full_inv_freq[::2]
t = (
torch.arange(max(max_seq_len + 1024 * 128, self.max_seq_length), device="cpu", dtype=torch.float32)
/ rope_scaling_factor
)
freqs = torch.outer(t, inv_freq)

self._hw_cos_cached = torch.cos(freqs).to(self.data_type).cuda()
self._hw_sin_cached = torch.sin(freqs).to(self.data_type).cuda()
return

def _init_to_get_dynamic_ntk_rotary(self):
partial_head_dim = int(self.config.get("partial_rotary_factor", 1) * self.head_dim_)
max_position_embeddings = self.config.get("max_position_embeddings", 2048)
Expand Down
Empty file.
Empty file.
148 changes: 148 additions & 0 deletions lightllm/models/neo_chat/layer_infer/transformer_layer_infer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import os
import torch
from functools import partial
from typing import Tuple
from lightllm.models.llama.triton_kernel.rotary_emb import rotary_emb_fwd
from lightllm.models.neo_chat_moe.infer_struct import NeoChatInferStateInfo
from lightllm.models.neo_chat_moe.triton_kernel.context_attention_fwd_neo import context_attention_fwd_neo
from lightllm.models.llama.triton_kernel.token_attention_nopad_att1 import token_att_fwd
from lightllm.models.qwen3.layer_infer.transformer_layer_infer import Qwen3TransformerLayerInfer
from lightllm.models.neo_chat.layer_weights.transformer_layer_weight import NeoChatTransformerLayerWeight
from lightllm.distributed import all_reduce
import torch.distributed as dist
from lightllm.models.llama.layer_infer.transformer_layer_infer import LlamaTransformerLayerInfer
from lightllm.common.basemodel.attention.base_att import AttControl
from lightllm.common.basemodel.attention.fa3.fp import HAS_FLASH_ATTN_INTERFACE
from lightllm.utils.log_utils import init_logger

logger = init_logger(__name__)

_USE_TRITON_PREFILL = os.environ.get("LIGHTLLM_NEO_PREFILL_TRITON_BACKEND", "0").strip().lower() in ("1", "true")
if not _USE_TRITON_PREFILL and not HAS_FLASH_ATTN_INTERFACE:
logger.warning(
"flash_attn_interface (fa3-neo) is not installed; falling back to triton prefill backend "
"for neo_chat. Install fa3-neo or set LIGHTLLM_NEO_PREFILL_TRITON_BACKEND=1 to silence "
"this warning."
)
_USE_TRITON_PREFILL = True


class NeoChatTransformerLayerInfer(Qwen3TransformerLayerInfer):
def __init__(self, data_type, network_config):
super().__init__(data_type, network_config)
return

def _bind_attention(self):
self._context_attention_kernel = self._context_attention_kernel
self._token_attention_kernel = self._token_attention_kernel
return

def _get_qkv(self, input, infer_state: NeoChatInferStateInfo, layer_weight: NeoChatTransformerLayerWeight):
input = input.view(-1, self.embed_dim_)

qkv = layer_weight.qkv_proj.mm(input)
q, cache_kv = qkv.split(
[self.tp_q_head_num_ * self.head_dim_, (self.tp_k_head_num_ + self.tp_v_head_num_) * self.head_dim_], dim=-1
)
q = q.view(q.shape[0], self.tp_q_head_num_, self.head_dim_)
q_t, q_hw = q.chunk(2, dim=-1)

cache_kv = cache_kv.view(-1, (self.tp_k_head_num_ + self.tp_v_head_num_), self.head_dim_)
k = cache_kv[:, : self.tp_k_head_num_, :]
v = cache_kv[:, self.tp_k_head_num_ :, :]
k_t, k_hw = k.chunk(2, dim=-1)

q_t_2d = q_t.reshape(q.shape[0], -1)
q_hw_2d = q_hw.reshape(q.shape[0], -1)
k_t_2d = k_t.reshape(k.shape[0], -1)
k_hw_2d = k_hw.reshape(k.shape[0], -1)

layer_weight.qk_norm_weight_(q_t_2d, k_t_2d, eps=self.eps_)
layer_weight.qk_hw_norm_weight_(q_hw_2d, k_hw_2d, eps=self.eps_)

q_t = q_t_2d.view(q.shape[0], self.tp_q_head_num_, self.head_dim_ // 2)
q_hw = q_hw_2d.view(q.shape[0], self.tp_q_head_num_, self.head_dim_ // 2)
q_h, q_w = q_hw.chunk(2, dim=-1)

k_t = k_t_2d.view(k.shape[0], self.tp_k_head_num_, self.head_dim_ // 2)
k_hw = k_hw_2d.view(k.shape[0], self.tp_k_head_num_, self.head_dim_ // 2)
k_h, k_w = k_hw.chunk(2, dim=-1)

rotary_emb_fwd(
q_t,
k_t,
infer_state.position_cos,
infer_state.position_sin,
)
rotary_emb_fwd(
q_h,
k_h,
infer_state.position_cos_h,
infer_state.position_sin_h,
)
rotary_emb_fwd(
q_w,
k_w,
infer_state.position_cos_w,
infer_state.position_sin_w,
)

q = torch.cat([q_t, q_h, q_w], dim=-1)
q = q.reshape(q.shape[0], -1)

k = torch.cat([k_t, k_h, k_w], dim=-1)
cache_kv = torch.cat([k, v], dim=1)
return q, cache_kv

def _context_attention_kernel(
self, q, kv, infer_state: NeoChatInferStateInfo, layer_weight, out=None
) -> torch.Tensor:

if _USE_TRITON_PREFILL:
o_tensor = self.alloc_tensor(q.shape, q.dtype) if out is None else out
kv = infer_state.mem_manager.kv_buffer[self.layer_num_]
context_attention_fwd_neo(
q.view(-1, self.tp_q_head_num_, self.head_dim_),
kv[:, 0 : self.tp_k_head_num_, :],
kv[:, self.tp_k_head_num_ : self.tp_k_head_num_ + self.tp_v_head_num_, :],
o_tensor.view(-1, self.tp_q_head_num_, self.head_dim_),
infer_state.b_req_idx,
infer_state.b_q_start_loc,
infer_state.b_seq_len,
infer_state.b_ready_cache_len,
infer_state.max_q_seq_len,
infer_state.req_manager.req_to_token_indexs,
infer_state.b_image_token_end,
)
return o_tensor

_q = q.view(-1, self.tp_q_head_num_, self.head_dim_)
_k, _v = infer_state.mem_manager.get_att_input_params(layer_index=self.layer_num_)

att_control = AttControl()
att_control.image_token_tag = getattr(infer_state, "b_image_token_tag", None)

o_tensor = infer_state.prefill_att_state.prefill_att(
q=_q,
k=_k,
v=_v,
att_control=att_control,
alloc_func=self.alloc_tensor,
)
return o_tensor.view(q.shape)

def _token_attention_kernel(
self,
q: torch.Tensor,
infer_state: NeoChatInferStateInfo,
layer_weight: NeoChatTransformerLayerWeight,
) -> torch.Tensor:
_k, _v = infer_state.mem_manager.get_att_input_params(layer_index=self.layer_num_)
_q = q.view(-1, self.tp_q_head_num_, self.head_dim_)
att_control = AttControl()
# att_control.mla_decode_dict["softmax_scale"] = 1.0 / (self.head_dim_ ** 0.5)
o_tensor = infer_state.decode_att_state.decode_att(
q=_q, k=_k, v=_v, att_control=att_control, alloc_func=self.alloc_tensor
)
o_tensor = o_tensor.view(-1, self.tp_q_head_num_, self.head_dim_)[:, :, : self.head_dim_].contiguous()
return o_tensor
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import torch
import numpy as np
from lightllm.models.qwen2.layer_weights.pre_and_post_layer_weight import Qwen2PreAndPostLayerWeight

# add key: language_model.xxx -> xxx
# only change keys at PreAndPostLayerWeight load, TransformLayerWeight is correct now
def rename_weight_keys(weights):
prefix = "language_model."
keys = list(weights.keys())
for k in keys:
if prefix in k:
weights[k.replace(prefix, "")] = weights.pop(k)


class NeoChatPreAndPostLayerWeight(Qwen2PreAndPostLayerWeight):
def __init__(self, data_type, network_config):
super().__init__(data_type, network_config)
return

def load_hf_weights(self, weights):
rename_weight_keys(weights)
super().load_hf_weights(weights)
return
57 changes: 57 additions & 0 deletions lightllm/models/neo_chat/layer_weights/transformer_layer_weight.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from lightllm.models.qwen3.layer_weights.transformer_layer_weight import Qwen3TransformerLayerWeight
from lightllm.common.basemodel.layer_weights.meta_weights import (
QKRMSNORMWeight,
RMSNormWeight,
QKVROWNMMWeight,
)


class NeoChatTransformerLayerWeight(Qwen3TransformerLayerWeight):
def __init__(self, layer_num, data_type, network_config, quant_cfg=None):
super().__init__(layer_num, data_type, network_config, quant_cfg)
return

def _init_weight_names(self):
super()._init_weight_names()
self._q_norm_hw_name = f"model.layers.{self.layer_num_}.self_attn.q_norm_hw.weight"
self._k_norm_hw_name = f"model.layers.{self.layer_num_}.self_attn.k_norm_hw.weight"

def _init_qkv(self):
in_dim = self.n_embed
self.qkv_proj = QKVROWNMMWeight(
in_dim=in_dim,
q_head_num=self.q_head_num_,
kv_head_num=self.k_head_num_,
head_dim=self.head_dim,
weight_names=[self._q_weight_name, self._k_weight_name, self._v_weight_name],
data_type=self.data_type_,
bias_names=[self._q_bias_name, self._k_bias_name, self._v_bias_name],
quant_method=self.get_quant_method("qkv_proj"),
)

def _init_norm(self):
hidden_size = self.network_config_["hidden_size"]
self.att_norm_weight_ = RMSNormWeight(
dim=hidden_size,
weight_name=self._att_norm_weight_name,
data_type=self.data_type_,
)
self.ffn_norm_weight_ = RMSNormWeight(
dim=hidden_size,
weight_name=self._ffn_norm_weight_name,
data_type=self.data_type_,
)

self.qk_norm_weight_ = QKRMSNORMWeight(
dim=self.head_dim // 2,
q_weight_name=self._q_norm_name,
k_weight_name=self._k_norm_name,
data_type=self.data_type_,
)

self.qk_hw_norm_weight_ = QKRMSNORMWeight(
dim=self.head_dim // 2,
q_weight_name=self._q_norm_hw_name,
k_weight_name=self._k_norm_hw_name,
data_type=self.data_type_,
)
Loading
Loading