diff --git a/lib/python3.12/site-packages/sglang/srt/constrained/__pycache__/base_grammar_backend.cpython-312.pyc b/lib/python3.12/site-packages/sglang/srt/constrained/__pycache__/base_grammar_backend.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d7da9c9bbc005b8bb595292d91fe5dcbaf6ec07a Binary files /dev/null and b/lib/python3.12/site-packages/sglang/srt/constrained/__pycache__/base_grammar_backend.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sglang/srt/constrained/__pycache__/llguidance_backend.cpython-312.pyc b/lib/python3.12/site-packages/sglang/srt/constrained/__pycache__/llguidance_backend.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d307ca702c596043ca7f210c3c249a11f6e44021 Binary files /dev/null and b/lib/python3.12/site-packages/sglang/srt/constrained/__pycache__/llguidance_backend.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sglang/srt/constrained/__pycache__/outlines_backend.cpython-312.pyc b/lib/python3.12/site-packages/sglang/srt/constrained/__pycache__/outlines_backend.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ce219d82a8980af2ee83cdfba636eac0c459bcdf Binary files /dev/null and b/lib/python3.12/site-packages/sglang/srt/constrained/__pycache__/outlines_backend.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sglang/srt/constrained/__pycache__/outlines_jump_forward.cpython-312.pyc b/lib/python3.12/site-packages/sglang/srt/constrained/__pycache__/outlines_jump_forward.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..80f1f011f82d468f60d35de3ddc1bdcae97ae3f6 Binary files /dev/null and b/lib/python3.12/site-packages/sglang/srt/constrained/__pycache__/outlines_jump_forward.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sglang/srt/constrained/__pycache__/reasoner_grammar_backend.cpython-312.pyc b/lib/python3.12/site-packages/sglang/srt/constrained/__pycache__/reasoner_grammar_backend.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c1bc6262986e214792191c7fcd2ed75e13bc51ce Binary files /dev/null and b/lib/python3.12/site-packages/sglang/srt/constrained/__pycache__/reasoner_grammar_backend.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sglang/srt/constrained/__pycache__/xgrammar_backend.cpython-312.pyc b/lib/python3.12/site-packages/sglang/srt/constrained/__pycache__/xgrammar_backend.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cbf03c5c0876f551fe3400c0f62fa101e27c4265 Binary files /dev/null and b/lib/python3.12/site-packages/sglang/srt/constrained/__pycache__/xgrammar_backend.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sglang/srt/constrained/base_grammar_backend.py b/lib/python3.12/site-packages/sglang/srt/constrained/base_grammar_backend.py new file mode 100644 index 0000000000000000000000000000000000000000..58ee5ccb262a850e9eee2167408a08c7cfaeddb1 --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/constrained/base_grammar_backend.py @@ -0,0 +1,219 @@ +# Copyright 2023-2024 SGLang Team +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""The baseclass of a backend for grammar-guided constrained decoding.""" + +import logging +from abc import ABC, abstractmethod +from concurrent.futures import Future, ThreadPoolExecutor +from dataclasses import dataclass +from threading import Event, Lock +from typing import Dict, List, Optional, Tuple + +import torch + +from sglang.srt.server_args import ServerArgs + +logger = logging.getLogger(__name__) + + +class BaseGrammarObject(ABC): + + def __init__(self): + self._finished = False + + @property + def finished(self): + return self._finished + + @finished.setter + def finished(self, finished): + self._finished = finished + + @abstractmethod + def try_jump_forward(self, tokenizer) -> Optional[Tuple[List[int], str]]: + """ + Try to jump forward in the grammar. + + Returns: + A jump forward helper which may be used in `jump_forward_str_state`. + None if the jump forward is not possible. + """ + raise NotImplementedError + + @abstractmethod + def jump_forward_str_state(self, helper: Tuple[List[int], str]) -> Tuple[str, int]: + """ + Jump forward for the grammar. + + Returns: + A tuple of the jump forward string and the next state of the grammar + (which can be used in `jump_and_retokenize` if needed). + """ + raise NotImplementedError + + @abstractmethod + def jump_and_retokenize( + self, old_output_ids: List[int], new_output_ids: List[int], next_state: int + ) -> None: + """ + Jump forward occurs, and update the grammar state if needed. + """ + raise NotImplementedError + + @abstractmethod + def accept_token(self, token: int) -> None: + """ + Accept a token in the grammar. + """ + raise NotImplementedError + + @abstractmethod + def allocate_vocab_mask( + self, vocab_size: int, batch_size: int, device + ) -> torch.Tensor: + raise NotImplementedError + + @abstractmethod + def fill_vocab_mask(self, vocab_mask: torch.Tensor, idx: int) -> None: + raise NotImplementedError + + @staticmethod + @abstractmethod + def move_vocab_mask(vocab_mask: torch.Tensor, device) -> torch.Tensor: + raise NotImplementedError + + @staticmethod + @abstractmethod + def apply_vocab_mask(logits: torch.Tensor, vocab_mask: torch.Tensor) -> None: + raise NotImplementedError + + @abstractmethod + def copy(self) -> "BaseGrammarObject": + raise NotImplementedError + + +@dataclass +class CacheEntry: + value: Optional[BaseGrammarObject] + event: Event + + +class BaseGrammarBackend: + def __init__(self): + self.executor = ThreadPoolExecutor() + self.cache: Dict[Tuple[str, str], CacheEntry] = {} + self.cache_lock = Lock() + + def _not_supported(self, key_type: str, key_string: str) -> None: + logger.warning(f"Skip unsupported {key_type}: {key_type}={key_string}") + + def dispatch_fallback( + self, key_type: str, key_string: str + ) -> Optional[BaseGrammarObject]: + """ + This function should not be reached in any case. + """ + raise ValueError(f"Invalid key_type: {key_type}={key_string}") + + def dispatch_json(self, key_string: str) -> Optional[BaseGrammarObject]: + return self._not_supported("json", key_string) + + def dispatch_regex(self, key_string: str) -> Optional[BaseGrammarObject]: + return self._not_supported("regex", key_string) + + def dispatch_ebnf(self, key_string: str) -> Optional[BaseGrammarObject]: + return self._not_supported("ebnf", key_string) + + def dispatch_structural_tag(self, key_string: str) -> Optional[BaseGrammarObject]: + return self._not_supported("structural_tag", key_string) + + def _init_value_dispatch(self, key: Tuple[str, str]) -> Optional[BaseGrammarObject]: + key_type, key_string = key + if key_type == "json": + return self.dispatch_json(key_string) + elif key_type == "regex": + return self.dispatch_regex(key_string) + elif key_type == "ebnf": + return self.dispatch_ebnf(key_string) + elif key_type == "structural_tag": + return self.dispatch_structural_tag(key_string) + else: + return self.dispatch_fallback(key_type, key_string) + + def _init_value(self, key: Tuple[str, str]) -> Optional[BaseGrammarObject]: + with self.cache_lock: + if key in self.cache: + cache_hit = True + entry = self.cache[key] + else: + cache_hit = False + entry = CacheEntry(None, Event()) + self.cache[key] = entry + + if cache_hit: + entry.event.wait() + else: + entry.value = self._init_value_dispatch(key) + entry.event.set() + return entry.value.copy() if entry.value else None + + def get_cached_value(self, key: Tuple[str, str]) -> Optional[BaseGrammarObject]: + with self.cache_lock: + entry = self.cache.get(key) + if not entry or not entry.event.is_set(): + return None + val = self.cache[key].value + return val.copy() if val else None + + def get_future_value(self, key: Tuple[str, str]) -> Future: + return self.executor.submit(self._init_value, key) + + def reset(self): + with self.cache_lock: + self.cache.clear() + + +def create_grammar_backend( + server_args: ServerArgs, tokenizer, vocab_size: int +) -> Optional[BaseGrammarBackend]: + if server_args.grammar_backend == "outlines": + from sglang.srt.constrained.outlines_backend import OutlinesGrammarBackend + + grammar_backend = OutlinesGrammarBackend( + tokenizer, + whitespace_pattern=server_args.constrained_json_whitespace_pattern, + ) + elif server_args.grammar_backend == "xgrammar": + from sglang.srt.constrained.xgrammar_backend import XGrammarGrammarBackend + + grammar_backend = XGrammarGrammarBackend(tokenizer, vocab_size=vocab_size) + elif server_args.grammar_backend == "llguidance": + from sglang.srt.constrained.llguidance_backend import GuidanceBackend + + grammar_backend = GuidanceBackend( + tokenizer=tokenizer, + whitespace_pattern=server_args.constrained_json_whitespace_pattern, + ) + elif server_args.grammar_backend == "none": + return None + else: + raise ValueError(f"Invalid grammar backend: {server_args.grammar_backend}") + + if server_args.reasoning_parser and hasattr(tokenizer, "think_end_id"): + from .reasoner_grammar_backend import ReasonerGrammarBackend + + grammar_backend = ReasonerGrammarBackend( + grammar_backend, tokenizer.think_end_id + ) + return grammar_backend diff --git a/lib/python3.12/site-packages/sglang/srt/constrained/llguidance_backend.py b/lib/python3.12/site-packages/sglang/srt/constrained/llguidance_backend.py new file mode 100644 index 0000000000000000000000000000000000000000..49c3740fb63f295691fae66f92202075234ef8c0 --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/constrained/llguidance_backend.py @@ -0,0 +1,169 @@ +# Copyright 2023-2024 SGLang Team +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Constrained decoding with llguidance backend.""" + +import json +import logging +import os +from typing import List, Optional, Tuple + +import torch +from llguidance import LLMatcher, LLTokenizer, StructTag, grammar_from +from llguidance.hf import from_tokenizer +from llguidance.torch import ( + allocate_token_bitmask, + apply_token_bitmask_inplace, + fill_next_token_bitmask, +) + +from sglang.srt.constrained.base_grammar_backend import ( + BaseGrammarBackend, + BaseGrammarObject, +) + +logger = logging.getLogger(__name__) + + +class GuidanceGrammar(BaseGrammarObject): + + def __init__(self, llguidance_tokenizer: LLTokenizer, serialized_grammar: str): + super().__init__() + self.llguidance_tokenizer = llguidance_tokenizer + self.serialized_grammar = serialized_grammar + + self.ll_matcher = LLMatcher( + self.llguidance_tokenizer, + self.serialized_grammar, + log_level=int(os.environ.get("LLGUIDANCE_LOG_LEVEL", "1")), + ) + self.finished = False + self.bitmask = None + + def try_jump_forward(self, tokenizer) -> Optional[Tuple[List[int], str]]: + ff_tokens = self.ll_matcher.compute_ff_tokens() + if ff_tokens: + return ff_tokens, "" + else: + return None + + def jump_forward_str_state(self, helper: Tuple[List[int], str]) -> Tuple[str, int]: + return "", -1 + + def jump_and_retokenize( + self, old_output_ids: List[int], new_output_ids: List[int], next_state: int + ): + pass + + def accept_token(self, token: int): + if not self.ll_matcher.consume_token(token): + logger.warning(f"matcher error: {self.ll_matcher.get_error()}") + self.finished = True + + def fill_vocab_mask(self, vocab_mask: torch.Tensor, idx: int) -> None: + if self.ll_matcher.is_stopped(): + self.finished = True + + fill_next_token_bitmask(self.ll_matcher, vocab_mask, idx) + + def allocate_vocab_mask( + self, vocab_size: int, batch_size: int, device + ) -> torch.Tensor: + if self.bitmask is None or self.bitmask.shape[0] < batch_size: + # only create bitmask when batch gets larger + self.bitmask = allocate_token_bitmask( + batch_size, self.llguidance_tokenizer.vocab_size + ) + bitmask = self.bitmask + else: + bitmask = self.bitmask[:batch_size] + + return bitmask + + @staticmethod + def move_vocab_mask(vocab_mask: torch.Tensor, device) -> torch.Tensor: + return vocab_mask.to(device, non_blocking=True) + + @staticmethod + def apply_vocab_mask(logits: torch.Tensor, vocab_mask: torch.Tensor) -> None: + apply_token_bitmask_inplace(logits, vocab_mask) + + def copy(self): + return GuidanceGrammar( + llguidance_tokenizer=self.llguidance_tokenizer, + serialized_grammar=self.serialized_grammar, + ) + + +class GuidanceBackend(BaseGrammarBackend): + + def __init__( + self, + tokenizer, + whitespace_pattern: Optional[str] = None, + n_vocab: Optional[int] = None, + ): + super().__init__() + + self.tokenizer = tokenizer + self.whitespace_pattern = whitespace_pattern + self.llguidance_tokenizer = from_tokenizer(self.tokenizer, n_vocab) + + def _from_serialized(self, serialized_grammar) -> Optional[GuidanceGrammar]: + try: + return GuidanceGrammar( + llguidance_tokenizer=self.llguidance_tokenizer, + serialized_grammar=serialized_grammar, + ) + except Exception as e: + logger.warning(f"Skip invalid grammar: {serialized_grammar}, {e=}") + return None + + def dispatch_json(self, key_string: str) -> Optional[GuidanceGrammar]: + serialized_grammar = LLMatcher.grammar_from_json_schema( + key_string, + defaults={ + "whitespace_pattern": self.whitespace_pattern, + }, + ) + return self._from_serialized(serialized_grammar) + + def dispatch_regex(self, key_string: str) -> Optional[GuidanceGrammar]: + serialized_grammar = grammar_from("regex", key_string) + return self._from_serialized(serialized_grammar) + + def dispatch_ebnf(self, key_string: str) -> Optional[GuidanceGrammar]: + try: + serialized_grammar = grammar_from("ebnf", key_string) + return self._from_serialized(serialized_grammar) + except ValueError as e: + logger.warning(f"Skip invalid ebnf: regex={key_string}, {e=}") + return None + + def dispatch_structural_tag(self, key_string: str) -> Optional[GuidanceGrammar]: + try: + structural_tag = json.loads(key_string) + tags = [ + StructTag( + begin=structure["begin"], + grammar=structure["schema"], + end=structure["end"], + trigger=structural_tag["triggers"][0], # TODO? + ) + for structure in structural_tag["structures"] + ] + g = StructTag.to_grammar(tags) + return self._from_serialized(g) + except Exception as e: + logging.warning(f"Skip invalid structural_tag: {key_string}, {e=}") + return None diff --git a/lib/python3.12/site-packages/sglang/srt/constrained/outlines_backend.py b/lib/python3.12/site-packages/sglang/srt/constrained/outlines_backend.py new file mode 100644 index 0000000000000000000000000000000000000000..41128108a401c0ca17913d93f5538c59bd15c9fb --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/constrained/outlines_backend.py @@ -0,0 +1,189 @@ +# Copyright 2023-2024 SGLang Team +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Constrained decoding with outlines backend.""" + +import json +import logging +from typing import Dict, List, Optional, Tuple, Union + +import interegular +import torch +from outlines.fsm.guide import RegexGuide +from outlines.models.transformers import TransformerTokenizer +from pydantic import BaseModel + +from sglang.srt.constrained.base_grammar_backend import ( + BaseGrammarBackend, + BaseGrammarObject, +) +from sglang.srt.constrained.outlines_jump_forward import OutlinesJumpForwardMap + +try: + from outlines.fsm.json_schema import build_regex_from_schema +except ImportError: + from outlines_core.fsm.json_schema import build_regex_from_schema + + +logger = logging.getLogger(__name__) + + +class OutlinesGrammar(BaseGrammarObject): + def __init__( + self, + guide: RegexGuide, + jump_forward_map: Union[OutlinesJumpForwardMap, None], + ) -> None: + super().__init__() + self.guide = guide + self.jump_forward_map = jump_forward_map + self.state = 0 + self.finished = False + + def accept_token(self, token: int): + self.state = self.guide.get_next_state(self.state, token) + + def try_jump_forward(self, tokenizer) -> Optional[Tuple]: + if not self.jump_forward_map: + return None + + jump_forward_bytes = self.jump_forward_map.jump_forward_byte(self.state) + if jump_forward_bytes is None or len(jump_forward_bytes) <= 1: + return None + + # preprocess the jump forward string + suffix_bytes = [] + continuation_range = range(0x80, 0xC0) + cur_state = self.state + while ( + len(jump_forward_bytes) and jump_forward_bytes[0][0] in continuation_range + ): + # continuation bytes + byte_edge = jump_forward_bytes.pop(0) + suffix_bytes.append(byte_edge[0]) + cur_state = byte_edge[1] + + suffix_tokens = [f"<0x{hex(b)[2:].upper()}>" for b in suffix_bytes] + suffix_ids = tokenizer.convert_tokens_to_ids(suffix_tokens) + return suffix_ids, cur_state + + def jump_forward_str_state(self, helper: Tuple[List[int], str]) -> Tuple[str, int]: + _, cur_state = helper + return self.jump_forward_map.jump_forward_symbol(cur_state) + + def jump_and_retokenize( + self, old_output_ids: List[int], new_output_ids: List[int], next_state: int + ): + self.state = next_state + + def allocate_vocab_mask( + self, vocab_size: int, batch_size: int, device + ) -> torch.Tensor: + return torch.zeros(batch_size, vocab_size, dtype=torch.bool, device=device) + + @staticmethod + def move_vocab_mask(vocab_mask: torch.Tensor, device) -> torch.Tensor: + return vocab_mask + + def fill_vocab_mask(self, vocab_mask: torch.Tensor, idx: int) -> None: + tokens = torch.tensor( + self.guide.get_next_instruction(self.state).tokens, dtype=torch.int64 + ).to(vocab_mask.device, non_blocking=True) + vocab_mask = vocab_mask[idx] + vocab_mask.fill_(1) + vocab_mask.scatter_(0, tokens, torch.zeros_like(tokens, dtype=torch.bool)) + + @staticmethod + def apply_vocab_mask(logits: torch.Tensor, vocab_mask: torch.Tensor): + logits.masked_fill_(vocab_mask, float("-inf")) + + def copy(self): + return OutlinesGrammar(self.guide, self.jump_forward_map) + + +class OutlinesGrammarBackend(BaseGrammarBackend): + def __init__( + self, + tokenizer, + whitespace_pattern: bool, + ): + super().__init__() + + try: + self.outlines_tokenizer = TransformerTokenizer(tokenizer) + except AttributeError: + # FIXME: tmp fix for chatglm2 & chatglm3 (pad_token_id=0) + origin_pad_token_id = tokenizer.pad_token_id + + def fset(self, value): + self._value = value + + type(tokenizer).pad_token_id = property( + fget=type(tokenizer).pad_token_id.fget, fset=fset + ) + self.outlines_tokenizer = TransformerTokenizer(tokenizer) + self.outlines_tokenizer.tokenizer.pad_token_id = origin_pad_token_id + self.outlines_tokenizer.pad_token_id = origin_pad_token_id + self.outlines_tokenizer.pad_token = ( + self.outlines_tokenizer.tokenizer.pad_token + ) + self.outlines_tokenizer.vocabulary = ( + self.outlines_tokenizer.tokenizer.get_vocab() + ) + self.whitespace_pattern = whitespace_pattern + + def _compile_regex(self, regex: str) -> Optional[OutlinesGrammar]: + try: + if hasattr(RegexGuide, "from_regex"): + # outlines >= 0.1.1 + guide = RegexGuide.from_regex(regex, self.outlines_tokenizer) + else: + # outlines <= 0.0.46 + guide = RegexGuide(regex, self.outlines_tokenizer) + except interegular.patterns.InvalidSyntax as e: + logger.warning(f"skip invalid regex schema: {regex=}, {e=}") + return None + + jump_forward_map = None + return OutlinesGrammar(guide, jump_forward_map) + + def dispatch_ebnf(self, key_string: str): + return super().dispatch_ebnf(key_string) + + def dispatch_structural_tag(self, key_string: str): + return super().dispatch_structural_tag(key_string) + + def dispatch_json(self, key_string: str): + try: + regex = build_regex_from_object( + key_string, + whitespace_pattern=self.whitespace_pattern, + ) + except (NotImplementedError, json.decoder.JSONDecodeError) as e: + logger.warning(f"Skip invalid json_schema: json_schema={key_string}, {e=}") + return self._compile_regex(regex) + + def dispatch_regex(self, key_string: str): + return self._compile_regex(key_string) + + +def build_regex_from_object( + object: Union[str, BaseModel, Dict], whitespace_pattern: Optional[str] = None +): + if isinstance(object, type(BaseModel)): + schema = json.dumps(object.model_json_schema()) + elif isinstance(object, Dict): + schema = json.dumps(object) + else: + schema = object + return build_regex_from_schema(schema, whitespace_pattern) diff --git a/lib/python3.12/site-packages/sglang/srt/constrained/outlines_jump_forward.py b/lib/python3.12/site-packages/sglang/srt/constrained/outlines_jump_forward.py new file mode 100644 index 0000000000000000000000000000000000000000..cfc65f75fe7f9e5d8765ad6a92104b0d59da919c --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/constrained/outlines_jump_forward.py @@ -0,0 +1,200 @@ +# Copyright 2023-2024 SGLang Team +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +""" +Faster constrained decoding with jump forward decoding / compressed finite state machine. +Reference: https://lmsys.org/blog/2024-02-05-compressed-fsm/ +""" + +import dataclasses +import logging +from collections import defaultdict +from typing import Optional + +import interegular +from interegular import InvalidSyntax +from outlines.caching import cache + +from sglang.srt.utils import get_bool_env_var + +try: + # outlines >= 0.1.0 + from outlines_core.fsm.outlines_core_rs import FSMInfo + from outlines_core.fsm.regex import make_byte_level_fsm, make_deterministic_fsm +except ImportError: + # outlines <= 0.0.46 + from outlines.fsm.regex import FSMInfo, make_byte_level_fsm, make_deterministic_fsm + +IP_REGEX = r"((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)" + +# Env var was set in sglang.srt.server_args.ServerArgs.__post__init__ +DISABLE_DISK_CACHE = get_bool_env_var("SGLANG_DISABLE_OUTLINES_DISK_CACHE", "true") + +logger = logging.getLogger(__name__) + + +@dataclasses.dataclass +class JumpEdge: + symbol: str = None + symbol_next_state: int = None + byte: int = None + byte_next_state: int = None + + +def disk_cache(expire: Optional[float] = None, typed=False, ignore=()): + if not DISABLE_DISK_CACHE: + return cache(expire, typed, ignore) + else: + return lambda fn: None + + +@disk_cache() +def init_state_to_jump_forward(regex_string): + try: + regex_pattern = interegular.parse_pattern(regex_string) + except InvalidSyntax as e: + logger.warning(f"skip invalid regex: {regex_string}, {e=}") + return + + byte_fsm = make_byte_level_fsm(regex_pattern.to_fsm().reduce(), keep_utf8=True) + regex_fsm, _ = make_deterministic_fsm(byte_fsm) + + fsm_info: FSMInfo = regex_fsm.fsm_info + + symbol_to_id = fsm_info.alphabet_symbol_mapping + id_to_symbol = {} + for symbol, id_ in symbol_to_id.items(): + id_to_symbol.setdefault(id_, []).append(symbol) + + transitions = fsm_info.transitions + + outgoings_ct = defaultdict(int) + # NOTE(lsyin): Final states can lead to terminate, so they have one outgoing edge naturally + for s in fsm_info.finals: + outgoings_ct[s] = 1 + + state_to_jump_forward = {} + for (state, id_), next_state in transitions.items(): + if id_ == fsm_info.alphabet_anything_value: + # Arbitrarily symbol cannot be recognized as jump forward + continue + + symbols = id_to_symbol[id_] + for c in symbols: + if len(c) > 1: + # Skip byte level transitions like c = "5E" + continue + + outgoings_ct[state] += 1 + if outgoings_ct[state] > 1: + if state in state_to_jump_forward: + del state_to_jump_forward[state] + break + + state_to_jump_forward[state] = JumpEdge( + symbol=c, + symbol_next_state=next_state, + ) + + # Process the byte level jump forward + outgoings_ct = defaultdict(int) + for s in fsm_info.finals: + outgoings_ct[s] = 1 + + for (state, id_), next_state in transitions.items(): + if id_ == fsm_info.alphabet_anything_value: + continue + symbols = id_to_symbol[id_] + for c in symbols: + byte_ = None + if len(c) == 1 and ord(c) < 0x80: + # ASCII character + byte_ = ord(c) + elif len(c) > 1: + # FIXME: This logic is due to the leading \x00 + # https://github.com/outlines-dev/outlines/pull/930 + byte_ = int(symbols[0][1:], 16) + + if byte_ is not None: + outgoings_ct[state] += 1 + if outgoings_ct[state] > 1: + if state in state_to_jump_forward: + del state_to_jump_forward[state] + break + e = state_to_jump_forward.get(state, JumpEdge()) + e.byte = byte_ + e.byte_next_state = next_state + state_to_jump_forward[state] = e + + return state_to_jump_forward + + +class OutlinesJumpForwardMap: + def __init__(self, regex_string): + self.state_to_jump_forward = init_state_to_jump_forward(regex_string) + + def jump_forward_symbol(self, state): + jump_forward_str = "" + next_state = state + while state in self.state_to_jump_forward: + e = self.state_to_jump_forward[state] + if e.symbol is None: + break + jump_forward_str += e.symbol + next_state = e.symbol_next_state + state = next_state + + return jump_forward_str, next_state + + def jump_forward_byte(self, state): + if state not in self.state_to_jump_forward: + return None + + jump_forward_bytes = [] + next_state = None + while state in self.state_to_jump_forward: + e = self.state_to_jump_forward[state] + assert e.byte is not None and e.byte_next_state is not None + jump_forward_bytes.append((e.byte, e.byte_next_state)) + next_state = e.byte_next_state + state = next_state + + return jump_forward_bytes + + def is_jump_forward_symbol_state(self, state): + return ( + state in self.state_to_jump_forward + and self.state_to_jump_forward[state].symbol is not None + ) + + +def test_main(regex_string): + jump_forward_map = OutlinesJumpForwardMap(regex_string) + for state, e in jump_forward_map.state_to_jump_forward.items(): + if e.symbol is not None: + jump_forward_str, next_state = jump_forward_map.jump_forward_symbol(state) + print(f"{state} -> {next_state}", jump_forward_str) + bytes_ = jump_forward_map.jump_forward_byte(state) + print(f"{state} -> {bytes_[-1][1]}", [hex(b) for b, _ in bytes_]) + + +if __name__ == "__main__": + import outlines + + outlines.caching.clear_cache() + test_main(r"The google's DNS sever address is " + IP_REGEX) + test_main(r"霍格沃茨特快列车|霍比特人比尔博") + # 霍格: \xe9\x9c\x8d \xe6\xa0\xbc ... + # 霍比: \xe9\x9c\x8d \xe6\xaf\x94 ... + + test_main(r"[-+]?[0-9]+[ ]*") diff --git a/lib/python3.12/site-packages/sglang/srt/constrained/reasoner_grammar_backend.py b/lib/python3.12/site-packages/sglang/srt/constrained/reasoner_grammar_backend.py new file mode 100644 index 0000000000000000000000000000000000000000..3f6f59e5b6c85df8a9b6b62d7b9f63e50632d458 --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/constrained/reasoner_grammar_backend.py @@ -0,0 +1,101 @@ +# Copyright 2023-2024 SGLang Team +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""The baseclass of a backend for reasoner grammar-guided constrained decoding.""" + +from concurrent.futures import Future +from typing import List, Optional, Tuple + +import torch + +from .base_grammar_backend import BaseGrammarBackend, BaseGrammarObject + + +class ReasonerGrammarObject(BaseGrammarObject): + def __init__(self, grammar: BaseGrammarObject, think_end_id): + super().__init__() + self.grammar = grammar + self.think_end_id = think_end_id + self.is_in_reasoning = True + + @property + def finished(self): + return self.grammar.finished + + @finished.setter + def finished(self, finished): + self.grammar.finished = finished + + def allocate_vocab_mask( + self, vocab_size: int, batch_size: int, device + ) -> torch.Tensor: + return self.grammar.allocate_vocab_mask(vocab_size, batch_size, device) + + def fill_vocab_mask(self, vocab_mask: torch.Tensor, idx: int) -> None: + if not self.is_in_reasoning: + self.grammar.fill_vocab_mask(vocab_mask, idx) + + def move_vocab_mask(self, vocab_mask: torch.Tensor, device) -> torch.Tensor: + return self.grammar.move_vocab_mask(vocab_mask, device) + + @property + def apply_vocab_mask(self): + return self.grammar.apply_vocab_mask + + def accept_token(self, token: int): + if token == self.think_end_id: + self.is_in_reasoning = False + + if not self.is_in_reasoning and token != self.think_end_id: + self.grammar.accept_token(token) + + def try_jump_forward(self, tokenizer): + return self.grammar.try_jump_forward(tokenizer) + + def jump_forward_str_state(self, helper): + return self.grammar.jump_forward_str_state(helper) + + def jump_and_retokenize( + self, old_output_ids: List[int], new_output_ids: List[int], next_state: int + ): + return self.grammar.jump_and_retokenize( + old_output_ids, new_output_ids, next_state + ) + + def copy(self) -> BaseGrammarObject: + return ReasonerGrammarObject(self.grammar.copy(), self.think_end_id) + + +class ReasonerGrammarBackend(BaseGrammarBackend): + def __init__(self, grammar_backend: BaseGrammarBackend, think_end_id): + self.grammar_backend = grammar_backend + self.think_end_id = think_end_id + + def get_cached_value(self, key: Tuple[str, str]) -> Optional[ReasonerGrammarObject]: + grammar = self.grammar_backend.get_cached_value(key) + return ReasonerGrammarObject(grammar, self.think_end_id) if grammar else None + + def get_future_value(self, key: Tuple[str, str]) -> Future: + grammar = Future() + + def callback(f: Future): + if result := f.result(): + grammar.set_result(ReasonerGrammarObject(result, self.think_end_id)) + else: + grammar.set_result(None) + + self.grammar_backend.get_future_value(key).add_done_callback(callback) + return grammar + + def reset(self): + self.grammar_backend.reset() diff --git a/lib/python3.12/site-packages/sglang/srt/constrained/triton_ops/__pycache__/bitmask_ops.cpython-312.pyc b/lib/python3.12/site-packages/sglang/srt/constrained/triton_ops/__pycache__/bitmask_ops.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f0b2da5562526db820906b1aef470138f977f706 Binary files /dev/null and b/lib/python3.12/site-packages/sglang/srt/constrained/triton_ops/__pycache__/bitmask_ops.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sglang/srt/constrained/triton_ops/bitmask_ops.py b/lib/python3.12/site-packages/sglang/srt/constrained/triton_ops/bitmask_ops.py new file mode 100644 index 0000000000000000000000000000000000000000..9a195c006b69788f6003a4d58813ba888ca5f55e --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/constrained/triton_ops/bitmask_ops.py @@ -0,0 +1,141 @@ +# Adapt from +# https://github.com/mlc-ai/xgrammar/blob/v0.1.17/python/xgrammar/kernels/apply_token_bitmask_inplace_triton.py + +from typing import List, Optional, Union + +import torch +import triton +import triton.language as tl + +from sglang.srt.utils import get_device_core_count + + +@triton.jit +def apply_token_bitmask_inplace_kernel( + logits_ptr, + bitmask_ptr, + indices_ptr, + num_rows, + vocab_size, + logits_strides, + bitmask_strides, + NUM_SMS: tl.constexpr, + BLOCK_SIZE: tl.constexpr, +): + """Apply a bitmask to logits in-place using Triton. The bitmask is a 01 bitwise compressed tensor, + where 0 means the token is masked and 1 means the token is not masked. After applying the bitmask, + the masked logits will be set to -inf. + + Parameters + ---------- + logits_ptr : tl.tensor + Pointer to the logits tensor to apply the bitmask to. + + bitmask_ptr : tl.tensor + Pointer to the bitmask tensor to apply. + + indices_ptr : Optional[tl.tensor] + Optional pointer to indices tensor specifying which rows to apply the mask to. + + num_rows : int + Number of rows to process. If indices_ptr is provided, this is the number of unique indices. + + vocab_size : int + Size of the vocabulary dimension. If the logits does not have a vocab padding, this is the + same as the logits's second dimension. Otherwise, this is the actual size of the vocabulary. + + logits_strides : int + Stride between rows in the logits tensor. + + bitmask_strides : int + Stride between rows in the bitmask tensor. + + NUM_SMS : int + Number of streaming multiprocessors to use. + + BLOCK_SIZE : int + Size of processing blocks. + """ + + pid = tl.program_id(0) + num_blocks = tl.cdiv(vocab_size, BLOCK_SIZE) + for work_id in tl.range(pid, num_rows * num_blocks, NUM_SMS): + row_id = work_id // num_blocks + block_offset = (work_id % num_blocks) * BLOCK_SIZE + batch_id = row_id if indices_ptr is None else tl.load(indices_ptr + row_id) + offsets = block_offset + tl.arange(0, BLOCK_SIZE) + bitmask_offsets = block_offset // 32 + tl.arange(0, BLOCK_SIZE // 32) + vocab_mask = offsets < vocab_size + packed_bitmask_mask = bitmask_offsets < bitmask_strides + packed_bitmask = tl.load( + bitmask_ptr + batch_id * bitmask_strides + bitmask_offsets, + packed_bitmask_mask, + ) + bitmask = ((packed_bitmask[:, None] >> (tl.arange(0, 32)[None, :])) & 1) == 0 + bitmask = bitmask.reshape(BLOCK_SIZE) + + tl.store( + logits_ptr + batch_id * logits_strides + offsets, + -float("inf"), + vocab_mask & bitmask, + ) + + +def apply_token_bitmask_inplace_triton( + logits: torch.Tensor, + bitmask: torch.Tensor, + indices: Optional[Union[List[int], torch.Tensor]] = None, +): + NUM_SMS = get_device_core_count() + BLOCK_SIZE = 4096 + BITS_PER_BLOCK = 32 + + # Check input dtype + assert bitmask.dtype == torch.int32, "bitmask must be of type int32" + + # Check input tensor shapes. + logits_shape = logits.shape + bitmask_shape = bitmask.shape + if logits.ndim == 1: + logits_shape = (1, logits_shape[0]) + if bitmask.ndim == 1: + bitmask_shape = (1, bitmask_shape[0]) + + required_bitmask_width = (logits_shape[1] + BITS_PER_BLOCK - 1) // BITS_PER_BLOCK + assert required_bitmask_width >= bitmask_shape[1], ( + f"Bitmask width too large: allow at most {required_bitmask_width} int32s for " + f"logits' width {logits_shape[1]}, but got {bitmask_shape[1]}" + ) + + vocab_size = min(logits_shape[1], bitmask_shape[1] * BITS_PER_BLOCK) + + num_rows = None + if isinstance(indices, list) or isinstance(indices, torch.Tensor): + indices = torch.tensor(indices, dtype=torch.int32, device=logits.device) + num_rows = indices.shape[0] + else: + assert ( + logits_shape[0] == bitmask_shape[0] + ), f"batch size mismatch: logits {logits_shape[0]} vs bitmask {bitmask_shape[0]}" + num_rows = logits_shape[0] + + if NUM_SMS > 0: + grid = (NUM_SMS,) + else: + num_blocks = triton.cdiv(vocab_size, BLOCK_SIZE) + grid = (num_rows * num_blocks,) + NUM_SMS = triton.next_power_of_2(grid[0]) + + apply_token_bitmask_inplace_kernel[grid]( + logits, + bitmask, + indices, + num_rows, + vocab_size, + logits_shape[1], + bitmask_shape[1], + NUM_SMS, + BLOCK_SIZE, + num_warps=BLOCK_SIZE // 32 // (16 // logits.element_size()), + num_stages=3, + ) diff --git a/lib/python3.12/site-packages/sglang/srt/constrained/xgrammar_backend.py b/lib/python3.12/site-packages/sglang/srt/constrained/xgrammar_backend.py new file mode 100644 index 0000000000000000000000000000000000000000..400ab421f177e8a349d5b99f8901496ec9c2b6dd --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/constrained/xgrammar_backend.py @@ -0,0 +1,207 @@ +# Copyright 2023-2024 SGLang Team +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Constrained decoding with xgrammar backend.""" + +import json +import logging +from typing import List, Optional, Tuple, Union + +import torch +from xgrammar import ( + CompiledGrammar, + GrammarCompiler, + GrammarMatcher, + StructuralTagItem, + TokenizerInfo, + allocate_token_bitmask, +) + +from sglang.srt.constrained.base_grammar_backend import ( + BaseGrammarBackend, + BaseGrammarObject, +) +from sglang.srt.constrained.triton_ops.bitmask_ops import ( + apply_token_bitmask_inplace_triton, +) +from sglang.srt.utils import get_bool_env_var + +logger = logging.getLogger(__name__) + + +MAX_ROLLBACK_TOKENS = 200 + + +class XGrammarGrammar(BaseGrammarObject): + + def __init__( + self, + matcher: GrammarMatcher, + vocab_size: int, + ctx: CompiledGrammar, + override_stop_tokens: Optional[Union[List[int], int]], + ) -> None: + super().__init__() + self.matcher = matcher + self.vocab_size = vocab_size + self.ctx = ctx + self.override_stop_tokens = override_stop_tokens + self.finished = False + + # Fix (from vLLM team): postpone the import of apply_token_bitmask_inplace_kernels to the + # class init site to avoid re-initializing CUDA in forked subprocess. + from xgrammar.kernels import apply_token_bitmask_inplace_kernels + + self.use_token_bitmask_triton = get_bool_env_var( + "SGLANG_TOKEN_BITMASK_TRITON", "false" + ) + self.apply_vocab_mask_cuda = apply_token_bitmask_inplace_kernels.get( + "cuda", None + ) + self.apply_vocab_mask_cpu = apply_token_bitmask_inplace_kernels.get("cpu", None) + + def accept_token(self, token: int): + assert self.matcher.accept_token(token) + + def try_jump_forward(self, tokenizer) -> Optional[Tuple[List[int], str]]: + s = self.matcher.find_jump_forward_string() + if s: + return [], s + return None + + def jump_forward_str_state(self, helper: Tuple[List[int], str]) -> Tuple[str, int]: + _, data = helper + return data, -1 + + def jump_and_retokenize( + self, old_output_ids: List[int], new_output_ids: List[int], next_state: int + ): + k = 0 + for i, old_id in enumerate(old_output_ids): + if old_id == new_output_ids[i]: + k = i + 1 + else: + break + + # rollback to the last token that is the same + if k < len(old_output_ids): + self.matcher.rollback(len(old_output_ids) - k) + + for i in range(k, len(new_output_ids)): + assert self.matcher.accept_token(new_output_ids[i]) + + def allocate_vocab_mask( + self, vocab_size: int, batch_size: int, device + ) -> torch.Tensor: + return allocate_token_bitmask(batch_size, vocab_size) + + def fill_vocab_mask(self, vocab_mask: torch.Tensor, idx: int) -> None: + self.matcher.fill_next_token_bitmask(vocab_mask, idx) + + @staticmethod + def move_vocab_mask(vocab_mask: torch.Tensor, device) -> torch.Tensor: + return vocab_mask.to(device, non_blocking=True) + + def apply_vocab_mask(self, logits: torch.Tensor, vocab_mask: torch.Tensor) -> None: + if ( + not self.use_token_bitmask_triton + and logits.device.type == "cuda" + and self.apply_vocab_mask_cuda + ): + return self.apply_vocab_mask_cuda(logits, vocab_mask) + if logits.device.type == "cpu" and self.apply_vocab_mask_cpu: + return self.apply_vocab_mask_cpu(logits, vocab_mask) + apply_token_bitmask_inplace_triton(logits, vocab_mask) + + def copy(self): + matcher = GrammarMatcher( + self.ctx, + max_rollback_tokens=MAX_ROLLBACK_TOKENS, + override_stop_tokens=self.override_stop_tokens, + ) + return XGrammarGrammar( + matcher, self.vocab_size, self.ctx, self.override_stop_tokens + ) + + +class XGrammarGrammarBackend(BaseGrammarBackend): + def __init__( + self, + tokenizer, + vocab_size: int, + ): + super().__init__() + + tokenizer_info = TokenizerInfo.from_huggingface( + tokenizer, vocab_size=vocab_size + ) + override_stop_tokens = None + + self.grammar_compiler = GrammarCompiler(tokenizer_info=tokenizer_info) + self.vocab_size = vocab_size + self.override_stop_tokens = override_stop_tokens + + def _from_context(self, ctx: CompiledGrammar) -> XGrammarGrammar: + matcher = GrammarMatcher(ctx, max_rollback_tokens=MAX_ROLLBACK_TOKENS) + return XGrammarGrammar(matcher, self.vocab_size, ctx, self.override_stop_tokens) + + def dispatch_json(self, key_string: str) -> Optional[XGrammarGrammar]: + try: + if key_string == "$$ANY$$": + # Note: This builtin JSON grammar includes *all* valid JSON (including, for example, arrays at the root) + ctx = self.grammar_compiler.compile_builtin_json_grammar() + else: + ctx = self.grammar_compiler.compile_json_schema(schema=key_string) + except RuntimeError as e: + logging.warning(f"Skip invalid json_schema: json_schema={key_string}, {e=}") + return None + return self._from_context(ctx) + + def dispatch_ebnf(self, key_string: str) -> Optional[XGrammarGrammar]: + try: + ctx = self.grammar_compiler.compile_grammar(key_string) + except RuntimeError as e: + logging.warning(f"Skip invalid ebnf: ebnf={key_string}, {e=}") + return None + return self._from_context(ctx) + + def dispatch_regex(self, key_string: str) -> Optional[XGrammarGrammar]: + try: + ctx = self.grammar_compiler.compile_regex(key_string) + except RuntimeError as e: + logging.warning(f"Skip invalid regex: regex={key_string}, {e=}") + return None + return self._from_context(ctx) + + def dispatch_structural_tag(self, key_string: str) -> Optional[XGrammarGrammar]: + try: + structural_tag = json.loads(key_string) + tags = [ + StructuralTagItem( + begin=structure["begin"], + schema=json.dumps(structure["schema"]), + end=structure["end"], + ) + for structure in structural_tag["structures"] + ] + ctx = self.grammar_compiler.compile_structural_tag( + tags, structural_tag["triggers"] + ) + except RuntimeError as e: + logging.warning(f"Skip invalid regex: regex={key_string}, {e=}") + return None + return self._from_context(ctx) + + def reset(self): + if self.grammar_compiler: + self.grammar_compiler.clear_cache() diff --git a/lib/python3.12/site-packages/sglang/srt/disaggregation/__pycache__/decode.cpython-312.pyc b/lib/python3.12/site-packages/sglang/srt/disaggregation/__pycache__/decode.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4e8bb9dba7fc8b1f9e7e35acb403c396d7ace0b3 Binary files /dev/null and b/lib/python3.12/site-packages/sglang/srt/disaggregation/__pycache__/decode.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sglang/srt/disaggregation/__pycache__/mini_lb.cpython-312.pyc b/lib/python3.12/site-packages/sglang/srt/disaggregation/__pycache__/mini_lb.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6a846cbfb2efce61951ee08a4677970399d10ef6 Binary files /dev/null and b/lib/python3.12/site-packages/sglang/srt/disaggregation/__pycache__/mini_lb.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sglang/srt/disaggregation/__pycache__/prefill.cpython-312.pyc b/lib/python3.12/site-packages/sglang/srt/disaggregation/__pycache__/prefill.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..960de614e22de500164a31a341dc77d81c4f8382 Binary files /dev/null and b/lib/python3.12/site-packages/sglang/srt/disaggregation/__pycache__/prefill.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sglang/srt/disaggregation/__pycache__/utils.cpython-312.pyc b/lib/python3.12/site-packages/sglang/srt/disaggregation/__pycache__/utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2f4f5c5b215b911f9199c4e4b1bd3636bf4265ba Binary files /dev/null and b/lib/python3.12/site-packages/sglang/srt/disaggregation/__pycache__/utils.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sglang/srt/disaggregation/base/__init__.py b/lib/python3.12/site-packages/sglang/srt/disaggregation/base/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..bfeecfe1c8c8852f4bdb4c2b6c48b5219dcd8469 --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/disaggregation/base/__init__.py @@ -0,0 +1,8 @@ +from .conn import ( + BaseKVBootstrapServer, + BaseKVManager, + BaseKVReceiver, + BaseKVSender, + KVArgs, + KVPoll, +) diff --git a/lib/python3.12/site-packages/sglang/srt/disaggregation/base/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/sglang/srt/disaggregation/base/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6464244a363bc13a14c20f6b8d78bb643be772e7 Binary files /dev/null and b/lib/python3.12/site-packages/sglang/srt/disaggregation/base/__pycache__/__init__.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sglang/srt/disaggregation/base/__pycache__/conn.cpython-312.pyc b/lib/python3.12/site-packages/sglang/srt/disaggregation/base/__pycache__/conn.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3dcde85c40b284b894d930eecb8242f94bc5b639 Binary files /dev/null and b/lib/python3.12/site-packages/sglang/srt/disaggregation/base/__pycache__/conn.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sglang/srt/disaggregation/base/conn.py b/lib/python3.12/site-packages/sglang/srt/disaggregation/base/conn.py new file mode 100644 index 0000000000000000000000000000000000000000..42e0b2ae5476aee656691c18784b6facdd5d44a1 --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/disaggregation/base/conn.py @@ -0,0 +1,113 @@ +from abc import ABC, abstractmethod +from typing import Optional + +import numpy as np +import numpy.typing as npt + +from sglang.srt.disaggregation.utils import DisaggregationMode +from sglang.srt.server_args import ServerArgs + + +class KVArgs: + engine_rank: int + kv_data_ptrs: list[int] + kv_data_lens: list[int] + kv_item_lens: list[int] + aux_data_ptrs: list[int] + aux_data_lens: list[int] + aux_item_lens: list[int] + ib_device: str + gpu_id: int + + +class KVPoll: + Failed = 0 + Bootstrapping = 1 + WaitingForInput = 2 + Transferring = 3 + Success = 4 + + +class BaseKVManager(ABC): + """Base class for managing transfers states""" + + @abstractmethod + def __init__( + self, + args: KVArgs, + disaggregation_mode: DisaggregationMode, + server_args: ServerArgs, + ): ... + + +class BaseKVSender(ABC): + + @abstractmethod + def __init__( + self, mgr: BaseKVManager, bootstrap_addr: str, bootstrap_room: int + ): ... + + @abstractmethod + def init(self, num_kv_indices: int, aux_index: Optional[int] = None): + """ + Notify the decoder server about the kv indices length and aux index + """ + ... + + @abstractmethod + def send(self, kv_indices: npt.NDArray[np.int64]): + """ + Send the kv cache at the given kv indices to the decoder server + """ + ... + + @abstractmethod + def poll(self) -> KVPoll: + """ + Check the status of the kv cache transfer + """ + ... + + @abstractmethod + def failure_exception(self): + """ + Raise an exception if the kv cache transfer fails + """ + ... + + +class BaseKVReceiver(ABC): + + @abstractmethod + def __init__( + self, + mgr: BaseKVManager, + bootstrap_addr: str, + bootstrap_room: Optional[int] = None, + ): ... + + @abstractmethod + def init(self, kv_indices: npt.NDArray[np.int64], aux_index: Optional[int] = None): + """ + Notify the prefill server about the kv indices and aux index + """ + ... + + @abstractmethod + def poll(self) -> KVPoll: + """ + Check the status of the kv cache transfer + """ + ... + + @abstractmethod + def failure_exception(self): + """ + Raise an exception if the kv cache transfer fails + """ + ... + + +class BaseKVBootstrapServer(ABC): + @abstractmethod + def __init__(self, port: int): ... diff --git a/lib/python3.12/site-packages/sglang/srt/disaggregation/mooncake/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/sglang/srt/disaggregation/mooncake/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..67778f0514c55205789e619c9c5f7c4d305c97fd Binary files /dev/null and b/lib/python3.12/site-packages/sglang/srt/disaggregation/mooncake/__pycache__/__init__.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sglang/srt/disaggregation/mooncake/__pycache__/conn.cpython-312.pyc b/lib/python3.12/site-packages/sglang/srt/disaggregation/mooncake/__pycache__/conn.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bb1804f52666e27f71b3514a24e15c5024fe45a3 Binary files /dev/null and b/lib/python3.12/site-packages/sglang/srt/disaggregation/mooncake/__pycache__/conn.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sglang/srt/disaggregation/nixl/__init__.py b/lib/python3.12/site-packages/sglang/srt/disaggregation/nixl/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..d9456e0fd07d1d55ed0b859dc13948fec48da51f --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/disaggregation/nixl/__init__.py @@ -0,0 +1 @@ +from .conn import NixlKVBootstrapServer, NixlKVManager, NixlKVReceiver, NixlKVSender diff --git a/lib/python3.12/site-packages/sglang/srt/disaggregation/nixl/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/sglang/srt/disaggregation/nixl/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1f627672952bb9783ec1d4661b1212d8c11f4da8 Binary files /dev/null and b/lib/python3.12/site-packages/sglang/srt/disaggregation/nixl/__pycache__/__init__.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sglang/srt/disaggregation/nixl/__pycache__/conn.cpython-312.pyc b/lib/python3.12/site-packages/sglang/srt/disaggregation/nixl/__pycache__/conn.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..371cf80119cc7b913969c5f39ff8017dd0e80e35 Binary files /dev/null and b/lib/python3.12/site-packages/sglang/srt/disaggregation/nixl/__pycache__/conn.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sglang/srt/disaggregation/nixl/conn.py b/lib/python3.12/site-packages/sglang/srt/disaggregation/nixl/conn.py new file mode 100644 index 0000000000000000000000000000000000000000..080babb44bd926b0debb4ab9b22f536185968dbb --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/disaggregation/nixl/conn.py @@ -0,0 +1,622 @@ +from __future__ import annotations + +import asyncio +import dataclasses +import logging +import queue +import socket +import struct +import threading +import uuid +from collections import defaultdict +from functools import cache +from typing import Dict, List, Optional, Tuple, Union + +import numpy as np +import numpy.typing as npt +import requests +import zmq +from aiohttp import web + +from sglang.srt.disaggregation.base.conn import ( + BaseKVBootstrapServer, + BaseKVManager, + BaseKVReceiver, + BaseKVSender, + KVArgs, + KVPoll, +) +from sglang.srt.disaggregation.utils import DisaggregationMode +from sglang.srt.server_args import ServerArgs +from sglang.srt.utils import get_free_port, get_ip, get_local_ip_by_remote + +logger = logging.getLogger(__name__) + + +@dataclasses.dataclass +class TransferInfo: + room: int + endpoint: str + dst_port: int + agent_metadata: bytes + dst_kv_ptrs: list[int] + dst_kv_indices: npt.NDArray[np.int64] + dst_aux_ptrs: list[int] + dst_aux_index: int + dst_gpu_id: int + + @classmethod + def from_zmq(cls, msg: List[bytes]): + return cls( + room=int(msg[0].decode("ascii")), + endpoint=msg[1].decode("ascii"), + dst_port=int(msg[2].decode("ascii")), + agent_metadata=msg[3], + dst_kv_ptrs=list(struct.unpack(f"{len(msg[4])//8}Q", msg[4])), + dst_kv_indices=np.frombuffer(msg[5], dtype=np.int64), + dst_aux_ptrs=list(struct.unpack(f"{len(msg[6])//8}Q", msg[6])), + dst_aux_index=int(msg[7].decode("ascii")), + dst_gpu_id=int(msg[8].decode("ascii")), + ) + + +@dataclasses.dataclass +class TransferStatus: + """Used by KV Receiver to know when a transfer is done.""" + + # KV chunk IDs that have been received. + received_kvs: Set[int] = dataclasses.field(default_factory=set) + # Number of kv chunks to expect, will know this after last chunk is received. + num_kvs_expected: Optional[int] = None + # Whether aux data has been received. + received_aux: bool = False + + def is_done(self): + if self.num_kvs_expected is None: + return False + return self.num_kvs_expected == len(self.received_kvs) and self.received_aux + + +class NixlKVManager(BaseKVManager): + def __init__( + self, + args: KVArgs, + disaggregation_mode: DisaggregationMode, + server_args: ServerArgs, + ): + try: + from nixl._api import nixl_agent + except ImportError as e: + raise ImportError( + "Please install NIXL by following the instructions at " + "https://github.com/ai-dynamo/nixl/blob/main/README.md " + "to run SGLang with NixlTransferEngine." + ) from e + self.agent = nixl_agent(str(uuid.uuid4())) + self.kv_args = args + self.disaggregation_mode = disaggregation_mode + # for p/d multi node infer + self.bootstrap_port = server_args.disaggregation_bootstrap_port + self.dist_init_addr = server_args.dist_init_addr + self.rank_port = None + self.server_socket = zmq.Context().socket(zmq.PULL) + self.register_buffer_to_engine() + + self.rank_port = get_free_port() + if self.disaggregation_mode == DisaggregationMode.PREFILL: + self.transfer_infos: Dict[int, TransferInfo] = {} + self.condition = threading.Condition() + self.peer_names: Dict[int, str] = {} + self._start_bootstrap_thread() + self._register_to_bootstrap() + elif self.disaggregation_mode == DisaggregationMode.DECODE: + self.connection_pool: Dict[str, Dict[str, Union[str, int]]] = {} + self.transfer_statuses: Dict[int, TransferStatus] = defaultdict( + TransferStatus + ) + else: + raise ValueError( + f"Unsupported DisaggregationMode: {self.disaggregation_mode}" + ) + + def register_buffer_to_engine(self): + kv_addrs = [] + for kv_data_ptr, kv_data_len in zip( + self.kv_args.kv_data_ptrs, self.kv_args.kv_data_lens + ): + kv_addrs.append((kv_data_ptr, kv_data_len, self.kv_args.gpu_id, "")) + self.kv_descs = self.agent.register_memory(kv_addrs, "VRAM", is_sorted=True) + if not self.kv_descs: + raise Exception("NIXL memory registration failed for kv tensors") + aux_addrs = [] + for aux_data_ptr, aux_data_len in zip( + self.kv_args.aux_data_ptrs, self.kv_args.aux_data_lens + ): + aux_addrs.append((aux_data_ptr, aux_data_len, 0, "")) + self.aux_descs = self.agent.register_memory(aux_addrs, "DRAM", is_sorted=True) + if not self.aux_descs: + raise Exception("NIXL memory registration failed for aux tensors") + + @cache + def _connect(self, endpoint: str): + socket = zmq.Context().socket(zmq.PUSH) + socket.connect(endpoint) + return socket + + def _add_remote(self, room: int, agent_metadata: bytes): + if room not in self.peer_names: + self.peer_names[room] = self.agent.add_remote_agent(agent_metadata) + return self.peer_names[room] + + def send_kvcache( + self, + peer_name: str, + prefill_kv_indices: npt.NDArray[np.int64], + dst_kv_ptrs: list[int], + dst_kv_indices: npt.NDArray[np.int64], + dst_gpu_id: int, + notif: str, + ): + # Make descs + num_layers = len(self.kv_args.kv_data_ptrs) + src_addrs = [] + dst_addrs = [] + for layer_id in range(num_layers): + src_ptr = self.kv_args.kv_data_ptrs[layer_id] + dst_ptr = dst_kv_ptrs[layer_id] + item_len = self.kv_args.kv_item_lens[layer_id] + + for prefill_index, decode_index in zip(prefill_kv_indices, dst_kv_indices): + src_addr = src_ptr + int(prefill_index) * item_len + dst_addr = dst_ptr + int(decode_index) * item_len + length = item_len + src_addrs.append((src_addr, length, self.kv_args.gpu_id)) + dst_addrs.append((dst_addr, length, dst_gpu_id)) + src_descs = self.agent.get_xfer_descs(src_addrs, "VRAM", is_sorted=True) + dst_descs = self.agent.get_xfer_descs(dst_addrs, "VRAM", is_sorted=True) + # Transfer data + xfer_handle = self.agent.initialize_xfer( + "WRITE", + src_descs, + dst_descs, + peer_name, + notif.encode("ascii"), + ) + if not xfer_handle: + raise Exception("KVSender failed to create transfer") + state = self.agent.transfer(xfer_handle) + if state == "ERR": + raise Exception("KVSender failed to post transfer") + return xfer_handle + + def send_aux( + self, + peer_name: str, + prefill_aux_index: int, + dst_aux_ptrs: list[int], + dst_aux_index: int, + notif: str, + ): + # Make descs + aux_item_len = self.kv_args.aux_item_lens[0] + prefill_aux_addr = ( + self.kv_args.aux_data_ptrs[0] + prefill_aux_index * aux_item_len + ) + decode_aux_addr = dst_aux_ptrs[0] + dst_aux_index * aux_item_len + src_addrs = [(prefill_aux_addr, aux_item_len, 0)] + dst_addrs = [(decode_aux_addr, aux_item_len, 0)] + src_descs = self.agent.get_xfer_descs(src_addrs, "DRAM", is_sorted=True) + dst_descs = self.agent.get_xfer_descs(dst_addrs, "DRAM", is_sorted=True) + # Transfer data + xfer_handle = self.agent.initialize_xfer( + "WRITE", + src_descs, + dst_descs, + peer_name, + notif.encode("ascii"), + ) + if not xfer_handle: + raise Exception("KVSender failed to create transfer") + state = self.agent.transfer(xfer_handle) + if state == "ERR": + raise Exception("KVSender failed to post transfer") + return xfer_handle + + def add_transfer_request( + self, + bootstrap_room: int, + kv_indices: npt.NDArray[np.int64], + index_slice: slice, + is_last: bool, + chunk_id: int, + aux_index: Optional[int] = None, + ): + assert self.disaggregation_mode == DisaggregationMode.PREFILL + assert not is_last or (is_last and aux_index is not None) + + # Wait for transfer info to be populated by bootstrap thread. + with self.condition: + self.condition.wait_for(lambda: bootstrap_room in self.transfer_infos) + req = self.transfer_infos[bootstrap_room] + assert bootstrap_room == req.room + + peer_name = self._add_remote(bootstrap_room, req.agent_metadata) + chunked_dst_kv_indice = req.dst_kv_indices[index_slice] + assert len(chunked_dst_kv_indice) == len(kv_indices) + + notif = "_".join([str(req.room), "kv", str(chunk_id), str(int(is_last))]) + kv_xfer_handle = self.send_kvcache( + peer_name, + kv_indices, + req.dst_kv_ptrs, + chunked_dst_kv_indice, + req.dst_gpu_id, + notif, + ) + handles = [kv_xfer_handle] + # Only the last chunk we need to send the aux data. + if is_last: + aux_xfer_handle = self.send_aux( + peer_name, + aux_index, + req.dst_aux_ptrs, + req.dst_aux_index, + str(req.room) + "_aux", + ) + handles.append(aux_xfer_handle) + return handles + + def update_transfer_status(self): + # Process notifications from received transfers. + notif_map = self.agent.get_new_notifs() + for peer_name, messages in notif_map.items(): + # We could also check that self.bootstrap_info['agent_name'] matches + # the message sender. But the bootstrap room alone should be + # sufficient to map the status. + for msg in messages: + components = msg.decode("ascii").split("_") + room = int(components[0]) + if components[1] == "kv": + chunk_id = int(components[2]) + is_last = bool(components[3]) + self.transfer_statuses[room].received_kvs.add(chunk_id) + if is_last: + self.transfer_statuses[room].num_kvs_expected = chunk_id + 1 + elif components[1] == "aux": + self.transfer_statuses[room].received_aux = True + + def check_transfer_done(self, room: int): + if room not in self.transfer_statuses: + return False + return self.transfer_statuses[room].is_done() + + def _register_to_bootstrap(self): + """Register KVSender to bootstrap server via HTTP POST.""" + if self.dist_init_addr: + ip_address = socket.gethostbyname(self.dist_init_addr.split(":")[0]) + else: + ip_address = get_ip() + + bootstrap_server_url = f"{ip_address}:{self.bootstrap_port}" + url = f"http://{bootstrap_server_url}/route" + payload = { + "role": "Prefill", + "rank_ip": get_local_ip_by_remote(), + "rank_port": self.rank_port, + "engine_rank": self.kv_args.engine_rank, + "agent_name": self.agent.name, + } + + try: + response = requests.put(url, json=payload) + if response.status_code == 200: + logger.debug("Prefill successfully registered to bootstrap server.") + else: + logger.error( + f"Prefill Failed to connect to bootstrap server: {response.status_code}, {response.text}" + ) + except Exception as e: + logger.error(f"Prefill Failed to register to bootstrap server: {e}") + + def _start_bootstrap_thread(self): + self.server_socket.bind(f"tcp://{get_local_ip_by_remote()}:{self.rank_port}") + + def bootstrap_thread(): + """This thread recvs transfer info from the decode engine""" + while True: + waiting_req_bytes = self.server_socket.recv_multipart() + room = waiting_req_bytes[0].decode("ascii") + if room == "None": + continue + room = int(room) + with self.condition: + self.transfer_infos[room] = TransferInfo.from_zmq(waiting_req_bytes) + self.condition.notify_all() + + threading.Thread(target=bootstrap_thread).start() + + +class NixlKVSender(BaseKVSender): + + def __init__(self, mgr: NixlKVManager, bootstrap_addr: str, bootstrap_room: int): + self.kv_mgr = mgr + self.bootstrap_room = bootstrap_room + self.aux_index = None + self.bootstrap_server_url = bootstrap_addr + self.xfer_handles = [] + self.has_sent = False + self.chunk_id = 0 + + def init(self, num_kv_indices: int, aux_index: Optional[int] = None): + self.num_kv_indices = num_kv_indices + self.aux_index = aux_index + + def send( + self, + kv_indices: npt.NDArray[np.int64], + index_slice: slice, + is_last: bool, + ): + new_xfer_handles = self.kv_mgr.add_transfer_request( + self.bootstrap_room, + kv_indices, + index_slice, + is_last, + self.chunk_id, + self.aux_index, + ) + self.xfer_handles.extend(new_xfer_handles) + self.chunk_id += 1 + if is_last: + self.has_sent = True + + def poll(self) -> KVPoll: + if not self.has_sent: + return KVPoll.WaitingForInput + + states = [self.kv_mgr.agent.check_xfer_state(x) for x in self.xfer_handles] + if all([x == "DONE" for x in states]): + return KVPoll.Success + if any([x == "ERR" for x in states]): + raise Exception("KVSender transfer encountered an error.") + return KVPoll.WaitingForInput + + def failure_exception(self): + raise Exception("Fake KVSender Exception") + + +class NixlKVReceiver(BaseKVReceiver): + + def __init__( + self, + mgr: NixlKVManager, + bootstrap_addr: str, + bootstrap_room: Optional[int] = None, + ): + self.bootstrap_room = bootstrap_room + self.bootstrap_addr = bootstrap_addr + self.kv_mgr = mgr + self.started_transfer = False + + # NOTE: key distinguished by bootstrap_addr and engine_rank + bootstrap_key = f"{self.bootstrap_addr}_{self.kv_mgr.kv_args.engine_rank}" + + if bootstrap_key not in self.kv_mgr.connection_pool: + self.bootstrap_info = self._get_bootstrap_info_from_server( + self.kv_mgr.kv_args.engine_rank + ) + if self.bootstrap_info is None: + logger.error( + f"Could not fetch bootstrap info for engine rank: {self.kv_mgr.kv_args.engine_rank}" + ) + else: + self.kv_mgr.connection_pool[bootstrap_key] = self.bootstrap_info + else: + self.bootstrap_info = self.kv_mgr.connection_pool[bootstrap_key] + + assert self.bootstrap_info is not None + + def _get_bootstrap_info_from_server(self, engine_rank): + """Fetch the bootstrap info from the bootstrap server.""" + try: + url = f"http://{self.bootstrap_addr}/route?engine_rank={engine_rank}" + response = requests.get(url) + if response.status_code == 200: + bootstrap_info = response.json() + return bootstrap_info + else: + logger.error( + f"Failed to get prefill server info: {response.status_code}, {response.text}" + ) + return None + except Exception as e: + logger.error(f"Error fetching prefill info from bootstrap: {e}") + return None + + @cache + def _connect(self, endpoint: str): + socket = zmq.Context().socket(zmq.PUSH) + socket.connect(endpoint) + return socket + + def init(self, kv_indices: npt.NDArray[np.int64], aux_index: Optional[int] = None): + self.prefill_server_url = ( + f"{self.bootstrap_info['rank_ip']}:{self.bootstrap_info['rank_port']}" + ) + logger.debug( + f"Fetched bootstrap info: {self.bootstrap_info} for engine rank: {self.kv_mgr.kv_args.engine_rank}" + ) + + packed_kv_data_ptrs = b"".join( + struct.pack("Q", ptr) for ptr in self.kv_mgr.kv_args.kv_data_ptrs + ) + packed_aux_data_ptrs = b"".join( + struct.pack("Q", ptr) for ptr in self.kv_mgr.kv_args.aux_data_ptrs + ) + self._connect("tcp://" + self.prefill_server_url).send_multipart( + [ + str(self.bootstrap_room).encode("ascii"), + get_local_ip_by_remote().encode("ascii"), + str(self.kv_mgr.rank_port).encode("ascii"), + self.kv_mgr.agent.get_agent_metadata(), + packed_kv_data_ptrs, + kv_indices.tobytes(), + packed_aux_data_ptrs, + str(aux_index).encode("ascii"), + str(self.kv_mgr.kv_args.gpu_id).encode("ascii"), + ] + ) + self.started_transfer = True + + def poll(self) -> KVPoll: + if not self.started_transfer: + return KVPoll.WaitingForInput + + self.kv_mgr.update_transfer_status() + + if self.kv_mgr.check_transfer_done(self.bootstrap_room): + return KVPoll.Success + return KVPoll.WaitingForInput + + def failure_exception(self): + raise Exception("Fake KVReceiver Exception") + + +class NixlKVBootstrapServer(BaseKVBootstrapServer): + def __init__(self, port: int): + self.port = port + self.app = web.Application() + self.store = dict() + self.lock = asyncio.Lock() + self._setup_routes() + self.prefill_port_table: Dict[int, Dict[str, Union[str, int]]] = {} + + # Start bootstrap server + self.thread = threading.Thread(target=self._run_server, daemon=True) + self.run() + + def run(self): + self.thread.start() + + def _setup_routes(self): + self.app.router.add_route("*", "/metadata", self._handle_metadata) + self.app.router.add_route("*", "/route", self._handle_route) + + async def _handle_metadata(self, request: web.Request): + key = request.query.get("key", "") + + if request.method == "GET": + return await self._handle_metadata_get(key) + elif request.method == "PUT": + return await self._handle_metadata_put(key, request) + elif request.method == "DELETE": + return await self._handle_metadata_delete(key) + return web.Response( + text="Method not allowed", status=405, content_type="application/json" + ) + + async def _handle_metadata_get(self, key): + async with self.lock: + value = self.store.get(key) + if value is None: + return web.Response( + text="metadata not found", status=404, content_type="application/json" + ) + return web.Response(body=value, status=200, content_type="application/json") + + async def _handle_metadata_put(self, key, request): + data = await request.read() + async with self.lock: + self.store[key] = data + return web.Response( + text="metadata updated", status=200, content_type="application/json" + ) + + async def _handle_metadata_delete(self, key): + async with self.lock: + if key not in self.store: + return web.Response( + text="metadata not found", + status=404, + content_type="application/json", + ) + del self.store[key] + return web.Response( + text="metadata deleted", status=200, content_type="application/json" + ) + + async def _handle_route(self, request: web.Request): + method = request.method + if method == "PUT": + return await self._handle_route_put(request) + elif method == "GET": + return await self._handle_route_get(request) + else: + return web.Response( + text="Method not allowed", status=405, content_type="application/json" + ) + + async def _handle_route_put(self, request: web.Request): + data = await request.json() + role = data["role"] + rank_ip = data["rank_ip"] + rank_port = int(data["rank_port"]) + engine_rank = int(data["engine_rank"]) + agent_name = data["agent_name"] + + # Add lock to make sure thread-safe + if role == "Prefill": + self.prefill_port_table[engine_rank] = { + "rank_ip": rank_ip, + "rank_port": rank_port, + "agent_name": agent_name, + } + logger.info( + f"Registered Prefill boostrap: {engine_rank} with rank_ip: {rank_ip} and rank_port: {rank_port} and name: {agent_name}" + ) + + return web.Response(text="OK", status=200) + + async def _handle_route_get(self, request: web.Request): + engine_rank = request.query.get("engine_rank") + if not engine_rank: + return web.Response(text="Missing rank", status=400) + + # Find corresponding prefill info + async with self.lock: + bootstrap_info = self.prefill_port_table.get(int(engine_rank)) + if bootstrap_info is not None: + return web.json_response(bootstrap_info, status=200) + else: + return web.Response(text="Not Found", status=404) + + def _run_server(self): + try: + # Event Loop + self._loop = asyncio.new_event_loop() + asyncio.set_event_loop(self._loop) + + self._runner = web.AppRunner(self.app) + self._loop.run_until_complete(self._runner.setup()) + + site = web.TCPSite(self._runner, port=self.port) + self._loop.run_until_complete(site.start()) + self._loop.run_forever() + except Exception as e: + logger.error(f"Server error: {str(e)}") + finally: + # Cleanup + self._loop.run_until_complete(self._runner.cleanup()) + self._loop.close() + + def close(self): + """Shutdown""" + if self._loop is not None and self._loop.is_running(): + self._loop.call_soon_threadsafe(self._loop.stop) + logger.info("Stopping server loop...") + + if self.thread.is_alive(): + self.thread.join(timeout=2) + logger.info("Server thread stopped") + + def poll(self) -> KVPoll: ... diff --git a/lib/python3.12/site-packages/sglang/srt/lora/__pycache__/layers.cpython-312.pyc b/lib/python3.12/site-packages/sglang/srt/lora/__pycache__/layers.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a62149fe3eb24a6fe32baa52778dd7a5b2694e1b Binary files /dev/null and b/lib/python3.12/site-packages/sglang/srt/lora/__pycache__/layers.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sglang/srt/lora/__pycache__/lora.cpython-312.pyc b/lib/python3.12/site-packages/sglang/srt/lora/__pycache__/lora.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..30c35fe6debe03b3f40739e94bec1514bb6a63bd Binary files /dev/null and b/lib/python3.12/site-packages/sglang/srt/lora/__pycache__/lora.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sglang/srt/lora/__pycache__/lora_config.cpython-312.pyc b/lib/python3.12/site-packages/sglang/srt/lora/__pycache__/lora_config.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b29c35b5ddd8dc425ded74e6e7088e49fe23acd0 Binary files /dev/null and b/lib/python3.12/site-packages/sglang/srt/lora/__pycache__/lora_config.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sglang/srt/lora/__pycache__/lora_manager.cpython-312.pyc b/lib/python3.12/site-packages/sglang/srt/lora/__pycache__/lora_manager.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b0943ca01a226070e9944ecd14d42300b9fdb4e0 Binary files /dev/null and b/lib/python3.12/site-packages/sglang/srt/lora/__pycache__/lora_manager.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sglang/srt/lora/__pycache__/mem_pool.cpython-312.pyc b/lib/python3.12/site-packages/sglang/srt/lora/__pycache__/mem_pool.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2ecf3f040b6565ae93eb74d9de6470069bd71901 Binary files /dev/null and b/lib/python3.12/site-packages/sglang/srt/lora/__pycache__/mem_pool.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sglang/srt/lora/__pycache__/utils.cpython-312.pyc b/lib/python3.12/site-packages/sglang/srt/lora/__pycache__/utils.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d3f0ea1277a62a4af0e062a605e00c84b5ea42ba Binary files /dev/null and b/lib/python3.12/site-packages/sglang/srt/lora/__pycache__/utils.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sglang/srt/lora/backend/__pycache__/base_backend.cpython-312.pyc b/lib/python3.12/site-packages/sglang/srt/lora/backend/__pycache__/base_backend.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..44680dd90308867064da405748c7f5601f9031bc Binary files /dev/null and b/lib/python3.12/site-packages/sglang/srt/lora/backend/__pycache__/base_backend.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sglang/srt/lora/backend/__pycache__/flashinfer_backend.cpython-312.pyc b/lib/python3.12/site-packages/sglang/srt/lora/backend/__pycache__/flashinfer_backend.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bed24057ca48b3811b0d19e9327970019fb889cb Binary files /dev/null and b/lib/python3.12/site-packages/sglang/srt/lora/backend/__pycache__/flashinfer_backend.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sglang/srt/lora/backend/__pycache__/triton_backend.cpython-312.pyc b/lib/python3.12/site-packages/sglang/srt/lora/backend/__pycache__/triton_backend.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1196016f2c8e7e87d53cc1e8be854cbde61f29ce Binary files /dev/null and b/lib/python3.12/site-packages/sglang/srt/lora/backend/__pycache__/triton_backend.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sglang/srt/lora/backend/base_backend.py b/lib/python3.12/site-packages/sglang/srt/lora/backend/base_backend.py new file mode 100644 index 0000000000000000000000000000000000000000..e1bdc54081194c302e1f9f19db51e5bdb63c38ad --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/lora/backend/base_backend.py @@ -0,0 +1,133 @@ +from typing import Tuple, Union + +import torch + +from sglang.srt.lora.utils import LoRABatchInfo + + +def get_fuse_output_add_from_name(name: str) -> bool: + mapping = { + "triton": True, + "flashinfer": False, + } + return mapping.get(name, False) + + +def get_fuse_stacked_lora_b_from_name(name: str) -> bool: + mapping = { + "triton": True, + "flashinfer": False, + } + return mapping.get(name, False) + + +class BaseLoRABackend: + """Base class for different Lora backends. + Each backend has its own implementation of Lora kernels. + + Args: + name: name of backend + batch_info: information of current batch for use + fuse_output_add: if set to True, the output buffer for storing result will be passed in when doing lora_b forward, + and the operation of adding will be fused into kernel + """ + + def __init__(self, name: str, batch_info: LoRABatchInfo = None): + self.name = name + self.batch_info = batch_info + self.fuse_output_add = get_fuse_output_add_from_name(name) + self.fuse_stacked_lora_b = get_fuse_stacked_lora_b_from_name(name) + + def run_lora_a_sgemm( + self, x: torch.Tensor, weights: torch.Tensor, *args, **kwargs + ) -> torch.Tensor: + """Run segment Gemm of lora a modules with current backend. + The definition of segment Gemm can be referred to https://docs.flashinfer.ai/api/gemm.html. + + Args: + x: input matrix with shape (s, input_dim), here s is the sum of all sequence lengths + weights: a set of lora weights with shape (num_lora, c * r, input_dim), + here r is lora rank, c is a multiplier for stacked modules (e.g., c=3 for qkv_proj, c=2 for gate_up_proj) + usually input_dim is much larger than r + Returns: + result with shape (s, c * r) + """ + pass + + def run_lora_b_sgemm( + self, x: torch.Tensor, weights: torch.Tensor, *args, **kwargs + ) -> torch.Tensor: + """Run segment Gemm of lora b modules with current backend. + The definition of segment Gemm can be referred to https://docs.flashinfer.ai/api/gemm.html. + + Args: + x: input matrix with shape (s, r), here s is the sum of all sequence lengths, r is lora rank + weights: a set of lora weights with shape (num_lora, output_dim, r) + usually output_dim is much larger than r + Returns: + result with shape (s, output_dim) + """ + pass + + def run_qkv_lora( + self, + x: torch.Tensor, + qkv_lora_a: torch.Tensor, + qkv_lora_b: Union[torch.Tensor, Tuple[torch.Tensor]], + *args, + **kwargs, + ) -> torch.Tensor: + """Run the lora pass for QKV Layer. + + Args: + x: input matrix with shape (s, input_dim), here s is the sum of all sequence lengths + qkv_lora_a: lora_a module for qkv, with shape (num_lora, 3 * r, input_dim) + qkv_lora_b: lora_b module for qkv. + If passed in as a tensor, its shape should be (num_lora,output_dim_q + 2 * output_dim_kv, r) + If passed in as a tuple of two tensors, it should contain: + a lora_b module for q, with shape (1, num_lora, output_dim_q, r) + and a combined lora_b module for kv, with shape (2, num_lora, output_dim_kv, r) + Returns: + result with shape (s, output_dim_q + 2 * output_dim_kv) + """ + pass + + def run_gate_up_lora( + self, + x: torch.Tensor, + gate_up_lora_a: torch.Tensor, + gate_up_lora_b: Union[torch.Tensor, Tuple[torch.Tensor]], + *args, + **kwargs, + ) -> torch.Tensor: + """Run the lora pass for gate_up_proj, usually attached to MergedColumnParallelLayer. + + Args: + x: input matrix with shape (s, input_dim), here s is the sum of all sequence lengths + gate_up_lora_a: lora_a module for gate_up_proj, with shape (num_lora, 2 * r, input_dim) + gate_up_lora_b: lora_b module for qkv. + If passed in as a tensor, its shape should be (num_lora, 2 * output_dim, r) + If passed in as a tuple, it should contain two tensors with shape (num_lora, output_dim, r) + Returns: + result with shape (s, 2 * output_dim) + """ + pass + + def set_batch_info(self, batch_info: LoRABatchInfo): + self.batch_info = batch_info + + +def get_backend_from_name(name: str) -> BaseLoRABackend: + """ + Get corresponding backend class from backend's name + """ + if name == "triton": + from sglang.srt.lora.backend.triton_backend import TritonLoRABackend + + return TritonLoRABackend + elif name == "flashinfer": + from sglang.srt.lora.backend.flashinfer_backend import FlashInferLoRABackend + + return FlashInferLoRABackend + else: + raise ValueError(f"Invalid backend: {name}") diff --git a/lib/python3.12/site-packages/sglang/srt/lora/backend/flashinfer_backend.py b/lib/python3.12/site-packages/sglang/srt/lora/backend/flashinfer_backend.py new file mode 100644 index 0000000000000000000000000000000000000000..0370c6c815fe2f98e72b123ddf589ef102438c10 --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/lora/backend/flashinfer_backend.py @@ -0,0 +1,131 @@ +from typing import Tuple + +import torch + +from sglang.srt.lora.backend.base_backend import BaseLoRABackend +from sglang.srt.lora.utils import LoRABatchInfo +from sglang.srt.utils import is_flashinfer_available + +if is_flashinfer_available(): + from flashinfer import SegmentGEMMWrapper + + +class FlashInferLoRABackend(BaseLoRABackend): + + def __init__(self, name: str, batch_info: LoRABatchInfo = None): + super().__init__(name, batch_info) + + # Set up SGemm Wrapper from flashinfer + # FIXME wait for flashinfer segment gemm update + workspace_buffer = torch.empty(1 * 1024 * 1024, dtype=torch.int8, device="cuda") + self.segment_gemm = SegmentGEMMWrapper(workspace_buffer) + + def run_lora_a_sgemm( + self, x: torch.Tensor, weights: torch.Tensor, *args, **kwargs + ) -> torch.Tensor: + + return self.segment_gemm.run( + x=x, + weights=weights, + batch_size=self.batch_info.bs, + weight_column_major=True, + seg_indptr=self.batch_info.seg_indptr, + weight_indices=self.batch_info.weight_indices, + ) + + def run_lora_b_sgemm( + self, x: torch.Tensor, weights: torch.Tensor, *args, **kwargs + ) -> torch.Tensor: + + return ( + self.segment_gemm.run( + x=x, + weights=weights, + batch_size=self.batch_info.bs, + weight_column_major=True, + seg_indptr=self.batch_info.seg_indptr, + weight_indices=self.batch_info.weight_indices, + ) + * self.batch_info.scalings[0] + ) + + def run_qkv_lora( + self, + x: torch.Tensor, + qkv_lora_a: torch.Tensor, + qkv_lora_b: Tuple[torch.Tensor], + *args, + **kwargs, + ) -> torch.Tensor: + + assert isinstance(qkv_lora_b, tuple) and len(qkv_lora_b) == 2 + + # Shape of lora_a_output: (s, 3 * r) + lora_a_output = self.run_lora_a_sgemm(x=x, weights=qkv_lora_a) + + q_lora_b, kv_lora_b = qkv_lora_b + lora_rank = kv_lora_b.shape[-1] + output_dim_q = q_lora_b.shape[-2] + output_dim_kv = kv_lora_b.shape[-2] + lora_output = torch.empty( + (x.shape[0], output_dim_q + 2 * output_dim_kv), + device=x.device, + dtype=x.dtype, + ) + + # q + lora_output[:, :output_dim_q] = self.run_lora_b_sgemm( + x=lora_a_output[:, :lora_rank].contiguous(), weights=q_lora_b[0] + ) + + # kv + lora_output[:, output_dim_q : output_dim_q + output_dim_kv] = ( + self.run_lora_b_sgemm( + x=lora_a_output[:, lora_rank : 2 * lora_rank].contiguous(), + weights=kv_lora_b[0], + ) + ) + + lora_output[ + :, output_dim_q + output_dim_kv : output_dim_q + 2 * output_dim_kv + ] = self.run_lora_b_sgemm( + x=lora_a_output[:, 2 * lora_rank : 3 * lora_rank].contiguous(), + weights=kv_lora_b[1], + ) + + return lora_output * self.batch_info.scalings[0] + + def run_gate_up_lora( + self, + x: torch.Tensor, + gate_up_lora_a: torch.Tensor, + gate_up_lora_b: Tuple[torch.Tensor], + *args, + **kwargs, + ) -> torch.Tensor: + + assert isinstance(gate_up_lora_b, tuple) and len(gate_up_lora_b) == 2 + lora_rank = gate_up_lora_b[0].shape[-1] + output_dim = gate_up_lora_b[0].shape[-2] + + # Shape of lora_a_output: (s, 2 * r) + lora_a_output = self.run_lora_a_sgemm(x=x, weights=gate_up_lora_a) + + lora_output = torch.empty( + (x.shape[0], 2 * output_dim), + device=x.device, + dtype=x.dtype, + ) + + # Compute lora for gate and up proj respectively + lora_output[:, :output_dim] = self.run_lora_b_sgemm( + x=lora_a_output[:, :lora_rank].contiguous(), + weights=gate_up_lora_b[0], + ) + + lora_output[:, output_dim:] = self.run_lora_b_sgemm( + x=lora_a_output[:, lora_rank:].contiguous(), + weights=gate_up_lora_b[1], + ) + + return lora_output * self.batch_info.scalings[0] diff --git a/lib/python3.12/site-packages/sglang/srt/lora/backend/triton_backend.py b/lib/python3.12/site-packages/sglang/srt/lora/backend/triton_backend.py new file mode 100644 index 0000000000000000000000000000000000000000..d3a854b40fd42c8494a8864503e959cd940391b3 --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/lora/backend/triton_backend.py @@ -0,0 +1,88 @@ +import torch + +from sglang.srt.lora.backend.base_backend import BaseLoRABackend +from sglang.srt.lora.triton_ops import ( + gate_up_lora_b_fwd, + qkv_lora_b_fwd, + sgemm_lora_a_fwd, + sgemm_lora_b_fwd, +) +from sglang.srt.lora.utils import LoRABatchInfo + + +class TritonLoRABackend(BaseLoRABackend): + + def __init__(self, name: str, batch_info: LoRABatchInfo = None): + super().__init__(name, batch_info) + + def run_lora_a_sgemm( + self, x: torch.Tensor, weights: torch.Tensor, *args, **kwargs + ) -> torch.Tensor: + return sgemm_lora_a_fwd(x, weights, self.batch_info) + + def run_lora_b_sgemm( + self, + x: torch.Tensor, + weights: torch.Tensor, + base_output: torch.Tensor = None, + *args, + **kwargs + ) -> torch.Tensor: + return sgemm_lora_b_fwd(x, weights, self.batch_info, base_output) + + def run_qkv_lora( + self, + x: torch.Tensor, + qkv_lora_a: torch.Tensor, + qkv_lora_b: torch.Tensor, + output_offset: torch.Tensor, + max_qkv_out_dim: int, + base_output: torch.Tensor = None, + *args, + **kwargs + ) -> torch.Tensor: + + # x: (s, input_dim) + # qkv_lora_a: (num_lora, 3 * r, input_dim) + # qkv_lora_b: (num_lora, output_dim_q + 2 * output_dim_kv, r) + assert isinstance(qkv_lora_b, torch.Tensor) + + lora_a_output = sgemm_lora_a_fwd(x, qkv_lora_a, self.batch_info, stack_num=3) + lora_output = qkv_lora_b_fwd( + lora_a_output, + qkv_lora_b, + self.batch_info, + output_offset, + max_qkv_out_dim, + base_output, + ) + return lora_output + + def run_gate_up_lora( + self, + x: torch.Tensor, + gate_up_lora_a: torch.Tensor, + gate_up_lora_b: torch.Tensor, + base_output: torch.Tensor = None, + *args, + **kwargs + ) -> torch.Tensor: + + # x: (s, input_dim) + # gate_up_lora_a: (num_lora, 2 * r, input_dim) + # gate_up_lora_b: (num_lora, 2 * output_dim, r) + assert isinstance(gate_up_lora_b, torch.Tensor) + output_dim = gate_up_lora_b.shape[-2] // 2 + + # lora_a_output: (s, 2 * r) + lora_a_output = sgemm_lora_a_fwd( + x, gate_up_lora_a, self.batch_info, stack_num=2 + ) + lora_output = gate_up_lora_b_fwd( + lora_a_output, + gate_up_lora_b, + self.batch_info, + output_dim, + base_output, + ) + return lora_output diff --git a/lib/python3.12/site-packages/sglang/srt/lora/layers.py b/lib/python3.12/site-packages/sglang/srt/lora/layers.py new file mode 100644 index 0000000000000000000000000000000000000000..8da81826f0c48873d54a3759b3ac0375ef6d4978 --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/lora/layers.py @@ -0,0 +1,347 @@ +from typing import List, Tuple + +import torch +from torch import nn + +from sglang.srt.distributed import ( + get_tensor_model_parallel_rank, + split_tensor_along_last_dim, + tensor_model_parallel_all_gather, + tensor_model_parallel_all_reduce, +) +from sglang.srt.layers.linear import ( + ColumnParallelLinear, + MergedColumnParallelLinear, + QKVParallelLinear, + RowParallelLinear, +) +from sglang.srt.layers.vocab_parallel_embedding import VocabParallelEmbedding +from sglang.srt.lora.backend.base_backend import BaseLoRABackend + + +class BaseLayerWithLoRA(nn.Module): + def __init__( + self, + base_layer: nn.Module, + lora_backend: BaseLoRABackend, + ): + super().__init__() + self.base_layer: nn.Module = base_layer + self.set_lora: bool = False + self.lora_backend: BaseLoRABackend = lora_backend + + def forward(self, x: torch.Tensor): + return self.base_layer.forward(x) + + def set_lora_info(self, *args): + pass + + def slice_lora_a_weights(self, A: torch.Tensor, tp_rank: int): + pass + + def slice_lora_b_weights(self, B: torch.Tensor, tp_rank: int): + pass + + +class VocabParallelEmbeddingWithLoRA(BaseLayerWithLoRA): + """ + Vocab parallel embedding layer with support for LoRA (Low-Rank Adaptation). + + Note: The current version does not yet implement the LoRA functionality. + This class behaves exactly the same as the base VocabParallelEmbedding. + Future versions will integrate LoRA functionality to support efficient parameter fine-tuning. + """ + + def __init__( + self, + base_layer: VocabParallelEmbedding, + lora_backend: BaseLoRABackend, + ) -> None: + super().__init__(base_layer, lora_backend) + self.weight = base_layer.weight + + +class ColumnParallelLinearWithLoRA(BaseLayerWithLoRA): + def __init__( + self, + base_layer: ColumnParallelLinear, + lora_backend: BaseLoRABackend, + ) -> None: + super().__init__(base_layer, lora_backend) + + def set_lora_info( + self, + A_buffer: torch.Tensor, + B_buffer: torch.Tensor, + ): + self.set_lora = True + self.A_buffer = A_buffer + self.B_buffer = B_buffer + + def apply_lora(self, base_output: torch.Tensor, x: torch.Tensor) -> torch.Tensor: + backend_kwargs = {"base_output": base_output} + lora_a_output = self.lora_backend.run_lora_a_sgemm(x, self.A_buffer) + lora_output = self.lora_backend.run_lora_b_sgemm( + lora_a_output, + self.B_buffer[0], + **backend_kwargs, + ) + return ( + lora_output + if self.lora_backend.fuse_output_add + else base_output + lora_output + ) + + def forward(self, input_: torch.Tensor): + # duplicate the logic in ColumnParallelLinear + bias = self.base_layer.bias if not self.base_layer.skip_bias_add else None + output_parallel = self.base_layer.quant_method.apply( + self.base_layer, input_, bias + ) + + if self.set_lora: + output_parallel = self.apply_lora(output_parallel, input_) + + if self.base_layer.gather_output: + output = tensor_model_parallel_all_gather(output_parallel) + else: + output = output_parallel + output_bias = self.base_layer.bias if self.base_layer.skip_bias_add else None + return output, output_bias + + def slice_lora_a_weights(self, A: torch.Tensor, tp_rank: int): + return A + + def slice_lora_b_weights(self, B: torch.Tensor, tp_rank: int): + shard_size = self.base_layer.output_partition_sizes[0] + start_idx = tp_rank * shard_size + end_idx = (tp_rank + 1) * shard_size + B = B[start_idx:end_idx, :] + return B + + +class MergedColumnParallelLinearWithLoRA(ColumnParallelLinearWithLoRA): + def __init__( + self, + base_layer: MergedColumnParallelLinear, + lora_backend: BaseLoRABackend, + ) -> None: + super().__init__(base_layer, lora_backend) + + def set_lora_info( + self, + A_buffer: torch.Tensor, + B_buffer: torch.Tensor, + ): + self.set_lora = True + self.A_buffer_gate_up = A_buffer + if self.lora_backend.fuse_stacked_lora_b: + # TODO: avoid using contiguous() in GPU. + # B_buffer_gate_up: (num_lora, 2 * output_dim, r) + self.B_buffer_gate_up = torch.cat( + (B_buffer[0], B_buffer[1]), dim=-2 + ).contiguous() + else: + self.B_buffer_gate_up = (B_buffer[0], B_buffer[1]) + + def apply_lora(self, base_output: torch.Tensor, x: torch.Tensor) -> torch.Tensor: + backend_kwargs = {"base_output": base_output} + + lora_output = self.lora_backend.run_gate_up_lora( + x, + self.A_buffer_gate_up, + self.B_buffer_gate_up, + **backend_kwargs, + ) + return ( + lora_output + if self.lora_backend.fuse_output_add + else base_output + lora_output + ) + + def slice_lora_a_weights(self, A: torch.Tensor, tp_rank: int): + return A + + def slice_lora_b_weights(self, B: torch.Tensor, tp_rank: int): + # Since the outputs for both gate and up are identical, we use a random one. + shard_size = self.base_layer.output_partition_sizes[0] + start_idx = tp_rank * shard_size + end_idx = (tp_rank + 1) * shard_size + return B[:, start_idx:end_idx, :] + + +class QKVParallelLinearWithLoRA(ColumnParallelLinearWithLoRA): + def init__( + self, + base_layer: QKVParallelLinear, + lora_backend: BaseLoRABackend, + ) -> None: + super().__init__(base_layer, lora_backend) + + def set_lora_info( + self, + A_buffer_qkv: torch.Tensor, + B_buffer_q: torch.Tensor, + B_buffer_kv: torch.Tensor, + ): + self.set_lora = True + self.A_buffer_qkv = A_buffer_qkv + + if self.lora_backend.fuse_stacked_lora_b: + assert ( + B_buffer_q.shape[-1] == B_buffer_kv.shape[-1] + ), "The lora rank of q and kv should be the same when enabling fusion of qkv lora_b" + output_dim_q, output_dim_kv = B_buffer_q.shape[-2], B_buffer_kv.shape[-2] + + # B_buffer_qkv: (num_lora, output_dim_q + 2 * output_dim_kv, r) + self.B_buffer_qkv = torch.cat( + (B_buffer_q[0], B_buffer_kv[0], B_buffer_kv[1]), dim=-2 + ).contiguous() + + # Offsets of q/k/v in output dimension + self.output_offset = torch.tensor( + [ + 0, + output_dim_q, + output_dim_q + output_dim_kv, + output_dim_q + 2 * output_dim_kv, + ], + dtype=torch.int32, + device=B_buffer_q.device, + ) + # For computing number of launched blocks + self.max_qkv_out_dim = max(output_dim_q, output_dim_kv) + else: + self.B_buffer_qkv = ( + B_buffer_q, + B_buffer_kv, + ) + + def apply_lora(self, base_output: torch.Tensor, x: torch.Tensor) -> torch.Tensor: + backend_kwargs = {"base_output": base_output} + if self.lora_backend.fuse_stacked_lora_b: + backend_kwargs["output_offset"] = self.output_offset + backend_kwargs["max_qkv_out_dim"] = self.max_qkv_out_dim + + lora_output = self.lora_backend.run_qkv_lora( + x, + self.A_buffer_qkv, + self.B_buffer_qkv, + **backend_kwargs, + ) + return ( + lora_output + if self.lora_backend.fuse_output_add + else base_output + lora_output + ) + + def slice_lora_a_weights(self, A: torch.Tensor, tp_rank: int): + return A + + def slice_lora_b_weights( + self, B: List[torch.Tensor], tp_rank: int + ) -> Tuple[torch.Tensor, torch.Tensor]: + B_q, B_kv = B + base_layer = self.base_layer + q_proj_shard_size = base_layer.q_proj_shard_size + kv_proj_shard_size = base_layer.kv_proj_shard_size + num_kv_head_replicas = base_layer.num_kv_head_replicas + + q_start_idx = q_proj_shard_size * tp_rank + q_end_idx = q_start_idx + q_proj_shard_size + + kv_shard_id = tp_rank // num_kv_head_replicas + kv_start_idx = kv_proj_shard_size * kv_shard_id + kv_end_idx = kv_start_idx + kv_proj_shard_size + + return B_q[q_start_idx:q_end_idx, :], B_kv[:, kv_start_idx:kv_end_idx, :] + + +class RowParallelLinearWithLoRA(BaseLayerWithLoRA): + def __init__( + self, + base_layer: RowParallelLinear, + lora_backend: BaseLoRABackend, + ) -> None: + super().__init__(base_layer, lora_backend) + + def set_lora_info(self, A_buffer: torch.Tensor, B_buffer: torch.Tensor): + self.set_lora = True + self.A_buffer = A_buffer + self.B_buffer = B_buffer + + def apply_lora(self, base_output: torch.Tensor, x: torch.Tensor) -> torch.Tensor: + backend_kwargs = {"base_output": base_output} + lora_a_output = self.lora_backend.run_lora_a_sgemm(x, self.A_buffer) + lora_output = self.lora_backend.run_lora_b_sgemm( + lora_a_output, + self.B_buffer[0], + **backend_kwargs, + ) + return ( + lora_output + if self.lora_backend.fuse_output_add + else base_output + lora_output + ) + + def forward(self, input_: torch.Tensor): + # duplicate the logic in RowParallelLinear + if self.base_layer.input_is_parallel: + input_parallel = input_ + else: + tp_rank = get_tensor_model_parallel_rank() + splitted_input = split_tensor_along_last_dim( + input_, num_partitions=self.base_layer.tp_size + ) + input_parallel = splitted_input[tp_rank].contiguous() + output_parallel = self.base_layer.quant_method.apply( + self.base_layer, input_parallel + ) + + if self.set_lora: + output_parallel = self.apply_lora(output_parallel, input_parallel) + + if self.base_layer.reduce_results and self.base_layer.tp_size > 1: + output_ = tensor_model_parallel_all_reduce(output_parallel) + else: + output_ = output_parallel + + if not self.base_layer.skip_bias_add: + output = ( + output_ + self.base_layer.bias + if self.base_layer.bias is not None + else output_ + ) + output_bias = None + else: + output = output_ + output_bias = self.base_layer.bias + return output, output_bias + + def slice_lora_a_weights(self, A: torch.Tensor, tp_rank: int): + shard_size = self.base_layer.input_size_per_partition + start_idx = tp_rank * shard_size + end_idx = (tp_rank + 1) * shard_size + A = A[:, start_idx:end_idx].contiguous() + return A + + def slice_lora_b_weights(self, B: torch.Tensor, tp_rank: int): + return B + + +def get_lora_layer( + layer: nn.Module, lora_backend: BaseLoRABackend +) -> BaseLayerWithLoRA: + supported_layer_types = { + # the order matters + VocabParallelEmbedding: VocabParallelEmbeddingWithLoRA, + QKVParallelLinear: QKVParallelLinearWithLoRA, + MergedColumnParallelLinear: MergedColumnParallelLinearWithLoRA, + ColumnParallelLinear: ColumnParallelLinearWithLoRA, + RowParallelLinear: RowParallelLinearWithLoRA, + } + for src_layer_type, lora_layer_type in supported_layer_types.items(): + if isinstance(layer, src_layer_type): # pylint: disable=unidiomatic-typecheck + ret = lora_layer_type(layer, lora_backend) + return ret + raise Exception(f"No corresponding LoRA layer supported for {type(layer)}.") diff --git a/lib/python3.12/site-packages/sglang/srt/lora/lora.py b/lib/python3.12/site-packages/sglang/srt/lora/lora.py new file mode 100644 index 0000000000000000000000000000000000000000..b0db40d6ab6909684a71ac0e2221b16b7d487f32 --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/lora/lora.py @@ -0,0 +1,181 @@ +# Copyright 2023-2024 SGLang Team +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +# Integrates "S-LoRA: Serving Thousands of Concurrent LoRA Adapters" +# and "Punica: Multi-Tenant LoRA Serving" + +# LoRA layers class inheritance adapted from: +# https://github.com/vllm-project/vllm/blob/4abf6336ec65c270343eb895e7b18786e9274176/vllm/lora/layers.py + +import logging +import re +from typing import Dict, List + +import torch +from torch import nn + +from sglang.srt.configs.load_config import LoadConfig +from sglang.srt.hf_transformers_utils import AutoConfig +from sglang.srt.lora.backend.base_backend import BaseLoRABackend +from sglang.srt.lora.lora_config import LoRAConfig +from sglang.srt.model_loader.loader import DefaultModelLoader + +logger = logging.getLogger(__name__) + + +class LoRALayer(nn.Module): + def __init__(self, config: LoRAConfig, base_hf_config: AutoConfig): + super().__init__() + self.config: LoRAConfig = config + self.base_hf_config: AutoConfig = base_hf_config + + # lora weights in cpu. The weights are loaded from checkpoint. + self.weights: Dict[str, torch.Tensor] = {} + + +class LoRAAdapter(nn.Module): + def __init__( + self, + uid: str, + config: LoRAConfig, + base_hf_config: AutoConfig, + load_config: LoadConfig, + lora_backend: BaseLoRABackend, + ): + super().__init__() + self.uid: str = uid + self.config: LoRAConfig = config + assert self.config.hf_config["peft_type"].lower() == "lora" + self.base_hf_config: AutoConfig = base_hf_config + self.load_config: LoadConfig = load_config + self.lora_backend: BaseLoRABackend = lora_backend + self.scaling: float = self.config.lora_alpha / self.config.r + + self.layers: List[LoRALayer] = nn.ModuleList( + [ + LoRALayer(config, base_hf_config) + for i in range(base_hf_config.num_hidden_layers) + ] + ) + + self.weights: Dict[str, torch.Tensor] = {} + + # initialize the LoRA weights to cpu + def initialize_weights(self): + model_path = self.config.path + loader = DefaultModelLoader(self.load_config) + revision = getattr(self.config.hf_config, "revision", None) + for name, loaded_weight in loader._get_weights_iterator( + DefaultModelLoader.Source( + model_path, revision=revision, fall_back_to_pt=True + ) + ): + match = re.search(r"layers\.(\d+)\.", name) + if match is not None: + layer_id = int(match.group(1)) + self.layers[layer_id].weights[name] = loaded_weight.cpu() + else: + self.weights[name] = loaded_weight.cpu() + + # stack kv_proj and gate_up_proj + for i in range(self.base_hf_config.num_hidden_layers): + layer = self.layers[i] + weight_names = [name for name, _ in layer.weights.items()] + self.stack_qkv_proj(weight_names, layer.weights) + self.stack_gate_up_proj(weight_names, layer.weights) + + def stack_qkv_proj(self, weight_names: List[str], weights: Dict[str, torch.Tensor]): + + # Collect target q/k/v modules. This process is necessary since there might be no lora attached to k_proj + target_module = set() + for weight_name in weight_names: + if "k_proj" in weight_name: + target_module.add("k_proj") + if "q_proj" in weight_name: + target_module.add("q_proj") + if "v_proj" in weight_name: + target_module.add("v_proj") + if len(target_module) == 0: + return + + for weight_name in weight_names: + # We assume every lora adaptor should contain lora modules for q_proj + if "q_proj" in weight_name: + q_name = weight_name + k_name = weight_name.replace("q_proj", "k_proj") + v_name = weight_name.replace("q_proj", "v_proj") + kv_name = weight_name.replace("q_proj", "kv_proj") + qkv_name = weight_name.replace("q_proj", "qkv_proj") + + # If k_proj doesn't have lora, initialize it to zero + k_proj_weight = ( + weights[k_name] + if "k_proj" in target_module + else torch.zeros_like(weights[v_name]) + ) + if "lora_A" in weight_name: + weights[qkv_name] = torch.cat( + ( + weights[q_name], + k_proj_weight, + weights[v_name], + ), + 0, + ) + weights.pop(q_name) + if "k_proj" in target_module: + weights.pop(k_name) + weights.pop(v_name) + else: + weights[kv_name] = torch.stack( + [ + k_proj_weight, + weights[v_name], + ], + dim=0, + ) + if "k_proj" in target_module: + weights.pop(k_name) + weights.pop(v_name) + + def stack_gate_up_proj( + self, weight_names: List[str], weights: Dict[str, torch.Tensor] + ): + for weight_name in weight_names: + if "gate_proj" in weight_name: + up_name = weight_name.replace("gate_proj", "up_proj") + gate_up_name = weight_name.replace("gate_proj", "gate_up_proj") + if up_name not in weights: + logger.warning( + f"Gate projection {weight_name} does not have a corresponding up projection {up_name}. " + f"Initializing up projection to zero." + ) + weights[up_name] = torch.zeros_like(weights[weight_name]) + # FIXME: Add gate-only support for flashinfer in future implementations + assert self.lora_backend.name == "triton", ( + f"LoRA weight initialization currently only supported for 'triton' backend. " + f"Received backend: {self.lora_backend.name}. Please verify your backend configuration " + f"or consider implementing custom initialization logic for other backends." + ) + if "lora_A" in weight_name: + weights[gate_up_name] = torch.cat( + (weights[weight_name], weights[up_name]), 0 + ) + else: + weights[gate_up_name] = torch.stack( + [weights[weight_name], weights[up_name]], dim=0 + ) + weights.pop(weight_name) + if up_name in weights: + weights.pop(up_name) diff --git a/lib/python3.12/site-packages/sglang/srt/lora/lora_config.py b/lib/python3.12/site-packages/sglang/srt/lora/lora_config.py new file mode 100644 index 0000000000000000000000000000000000000000..185b7b8246ee5789233c42e20dd51dbb97bf2c3e --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/lora/lora_config.py @@ -0,0 +1,47 @@ +# Copyright 2023-2024 SGLang Team +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +import json +import os + +from huggingface_hub import snapshot_download + + +class LoRAConfig: + def __init__( + self, + path: str, + ) -> None: + self.path = path + self.hf_config = self.get_lora_config() + self.target_modules = self.hf_config["target_modules"] + + # TODO: Support more modules + if any(module in self.target_modules for module in ["embed_tokens", "lm_head"]): + raise ValueError("Not supported yet") + + self.r = self.hf_config["r"] + self.lora_alpha = self.hf_config["lora_alpha"] + + def get_lora_config(self, dummy=False): + if dummy: + raise NotImplementedError() + else: + if not os.path.isdir(self.path): + weights_dir = snapshot_download(self.path, allow_patterns=["*.json"]) + else: + weights_dir = self.path + config_name = "adapter_config.json" + with open(os.path.join(weights_dir, config_name), "r") as f: + return json.load(f) diff --git a/lib/python3.12/site-packages/sglang/srt/lora/lora_manager.py b/lib/python3.12/site-packages/sglang/srt/lora/lora_manager.py new file mode 100644 index 0000000000000000000000000000000000000000..7b8f1162964a7c213cbb4d9d491981814dc49e8d --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/lora/lora_manager.py @@ -0,0 +1,228 @@ +# Copyright 2023-2024 SGLang Team +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +# Integrates "S-LoRA: Serving Thousands of Concurrent LoRA Adapters" +# and "Punica: Multi-Tenant LoRA Serving" + +import logging +from typing import Dict, List, Set, Tuple + +import torch + +from sglang.srt.configs.load_config import LoadConfig +from sglang.srt.hf_transformers_utils import AutoConfig +from sglang.srt.lora.backend.base_backend import BaseLoRABackend, get_backend_from_name +from sglang.srt.lora.layers import BaseLayerWithLoRA, get_lora_layer +from sglang.srt.lora.lora import LoRAAdapter +from sglang.srt.lora.lora_config import LoRAConfig +from sglang.srt.lora.mem_pool import LoRAMemoryPool +from sglang.srt.lora.utils import ( + LoRABatchInfo, + LoRAType, + get_customized_names_from_hf_names, + get_layer_id, + get_stacked_name, + get_weight_name, +) +from sglang.srt.model_executor.forward_batch_info import ForwardBatch +from sglang.srt.utils import replace_submodule + +logger = logging.getLogger(__name__) + + +class LoRAManager: + def __init__( + self, + base_model: torch.nn.Module, + lora_paths: Dict[str, str], + base_hf_config: AutoConfig, + max_loras_per_batch: int, + load_config: LoadConfig, + dtype: torch.dtype, + lora_backend: str = "triton", + tp_size: int = 1, + tp_rank: int = 0, + ): + self.base_model: torch.nn.Module = base_model + self.lora_paths: Dict[str, str] = lora_paths + self.base_hf_config: AutoConfig = base_hf_config + self.max_loras_per_batch: int = max_loras_per_batch + self.load_config: LoadConfig = load_config + self.dtype: torch.dtype = dtype + self.device: torch.device = next(self.base_model.parameters()).device + self.tp_size: int = tp_size + self.tp_rank: int = tp_rank + + # LoRA backend for running sgemm kernels + logger.info(f"Using {lora_backend} as backend of LoRA kernels.") + backend_type = get_backend_from_name(lora_backend) + self.lora_backend: BaseLoRABackend = backend_type(lora_backend) + + self.init_loras() + self.init_lora_memory_pool() + + def init_loras(self): + # Config of each LoRA adapter + self.configs: Dict[str, LoRAConfig] = {} + + # Target module names in huggingface lora configs. + # e.g., {"k_proj", "q_proj", "v_proj", "o_proj"} + self.hf_target_names: Set[str] = set() + for name, path in self.lora_paths.items(): + self.configs[name] = LoRAConfig(path) + self.hf_target_names.update(self.configs[name].target_modules) + + # Target lora weight names for lora_a and lora_b modules repectively. + # e.g., {("qkv_proj", "q_proj"), ("qkv_proj", "kv_proj")} + self.lora_weight_names: Set[Tuple[str]] = set( + [get_stacked_name(module) for module in self.hf_target_names] + ) + + # load all weights to cpu + self.loras: Dict[str, LoRAAdapter] = {} + for name in self.lora_paths.keys(): + lora_adapter = LoRAAdapter( + name, + self.configs[name], + self.base_hf_config, + self.load_config, + self.lora_backend, + ) + lora_adapter.initialize_weights() + self.loras[name] = lora_adapter + + # misc lora configs + self.max_lora_dim: int = max([x.hf_config["r"] for x in self.configs.values()]) + + if self.lora_backend == "flashinfer": + # FIXME remove the restrictions after supporting multi-rank for flashinfer backend + max_lora_dim = max([x.hf_config["r"] for x in self.configs.values()]) + scaling = list(self.loras.values())[0].scaling + assert all(x.hf_config["r"] == max_lora_dim for x in self.configs.values()) + assert all(x.scaling == scaling for x in self.loras.values()) + + # Convert original model layers to layers with LoRA + self.convert_to_lora_layers() + + def init_lora_memory_pool(self): + # Initialize memory pool + self.memory_pool = LoRAMemoryPool( + self.base_hf_config, + self.max_loras_per_batch, + self.max_lora_dim, + self.dtype, + self.tp_size, + self.tp_rank, + self.lora_modules, + ) + + # Initialize target lora modules in memory pool + self.memory_pool.init_buffers(self.lora_weight_names, self.base_model) + + def prepare_lora_batch(self, forward_batch: ForwardBatch): + # load active loras into lora memory pool + cur_uids = set(forward_batch.lora_paths) + assert len(cur_uids) <= self.max_loras_per_batch + self.memory_pool.prepare_lora_batch(cur_uids, self.loras) + + # FIXME: Handle lora uid with None more safely + if cur_uids == set([None]): + return + + # set up batch info shared by all lora moruldes + bs = forward_batch.batch_size + seg_lens = ( + forward_batch.extend_seq_lens + if forward_batch.forward_mode.is_extend() + else torch.ones(bs, device=self.device) + ) + seg_indptr = torch.zeros((bs + 1,), dtype=torch.int32, device=self.device) + seg_indptr[1:] = torch.cumsum(seg_lens, dim=0) + max_len = int(torch.max(seg_lens)) + weight_indices = torch.empty((bs,), dtype=torch.int64, device=self.device) + + lora_ranks = torch.empty( + (self.max_loras_per_batch,), dtype=torch.int64, device="cuda" + ) + scalings = torch.empty( + (self.max_loras_per_batch,), dtype=torch.float, device="cuda" + ) + for i, lora_path in enumerate(forward_batch.lora_paths): + weight_indices[i] = self.memory_pool.get_buffer_id(lora_path) + lora = self.loras[lora_path] + lora_ranks[weight_indices[i]] = lora.config.hf_config["r"] + scalings[weight_indices[i]] = lora.scaling + + batch_info = LoRABatchInfo( + bs=bs, + seg_lens=seg_lens, + seg_indptr=seg_indptr, + max_len=max_len, + weight_indices=weight_indices, + lora_ranks=lora_ranks, + scalings=scalings, + ) + self.lora_backend.set_batch_info(batch_info) + + # call set_lora_info for each lora modules + for layer_id, modules in self.lora_modules.items(): + for module_name, module in modules: + if "qkv_proj" in module_name: + module.set_lora_info( + self.memory_pool.get_tensor( + "qkv_proj", layer_id, LoRAType.LORA_A + ), + self.memory_pool.get_tensor( + "q_proj", layer_id, LoRAType.LORA_B + ), + self.memory_pool.get_tensor( + "kv_proj", layer_id, LoRAType.LORA_B + ), + ) + else: + weight_name = get_weight_name( + module_name, self.lora_weight_names, LoRAType.LORA_A + ) + module.set_lora_info( + self.memory_pool.get_tensor( + weight_name, layer_id, LoRAType.LORA_A + ), + self.memory_pool.get_tensor( + weight_name, layer_id, LoRAType.LORA_B + ), + ) + + def set_lora_module(self, module_name, module): + lora_module = get_lora_layer(module, self.lora_backend) + replace_submodule(self.base_model, module_name, lora_module) + return lora_module + + def convert_to_lora_layers(self): + # Target module names of customized layers defined in python/sglang/srt/layers + # e.g., {"qkv_proj", "o_proj"} + customized_target_names = get_customized_names_from_hf_names( + self.hf_target_names, self.base_model + ) + + # Monkey patch to use the LoRA version layers + self.lora_modules: Dict[int, List[Tuple[str, BaseLayerWithLoRA]]] = { + i: [] for i in range(self.base_hf_config.num_hidden_layers) + } + for module_name, module in self.base_model.named_modules(): + # The module should be converted if it is included in target_names + if module_name.split(".")[-1] in customized_target_names: + layer_id = get_layer_id(module_name) + self.lora_modules[layer_id].append( + (module_name, self.set_lora_module(module_name, module)) + ) diff --git a/lib/python3.12/site-packages/sglang/srt/lora/mem_pool.py b/lib/python3.12/site-packages/sglang/srt/lora/mem_pool.py new file mode 100644 index 0000000000000000000000000000000000000000..3226d958722652fcccbeb41ea64efc15ce61f8ac --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/lora/mem_pool.py @@ -0,0 +1,239 @@ +from typing import Dict, List, Optional, Set, Tuple + +import torch + +from sglang.srt.distributed import divide +from sglang.srt.hf_transformers_utils import AutoConfig +from sglang.srt.lora.layers import BaseLayerWithLoRA +from sglang.srt.lora.lora import LoRAAdapter +from sglang.srt.lora.utils import ( + ROW_PARALLELISM_LINEAR_LORA_NAMES, + LoRAType, + get_hidden_dim, + get_stacked_multiply, + get_weight_name, +) + + +class LoRAMemoryPool: + """Class for memory pool management of lora modules""" + + def __init__( + self, + base_hf_config: AutoConfig, + max_loras_per_batch: int, + max_lora_dim: int, + dtype: torch.dtype, + tp_size: int, + tp_rank: int, + lora_modules: Dict[int, List[Tuple[str, BaseLayerWithLoRA]]], + ): + + self.base_hf_config: AutoConfig = base_hf_config + self.num_layer: int = base_hf_config.num_hidden_layers + self.max_loras_per_batch: int = max_loras_per_batch + self.max_lora_dim: int = max_lora_dim + self.dtype: torch.dtype = dtype + self.tp_size: int = tp_size + self.tp_rank: int = tp_rank + self.lora_modules: Dict[int, List[Tuple[str, BaseLayerWithLoRA]]] = lora_modules + + # Both A_buffer and B_buffer maps lora weight names to its buffer space. + # A_buffer contains num_layer number of row-major tensors with shape + # (max_loras_per_batch, stacked_num * max_lora_dim, input_dim) + # B_buffer contains num_layer number of column-major tensors with shape + # (stacked_num, max_loras_per_batch, output_dim, max_lora_dim) + self.A_buffer: Dict[str, List[torch.Tensor]] = {} + self.B_buffer: Dict[str, List[torch.Tensor]] = {} + + # Lora uid -> buffer idx in memory pool + self.uid_to_buffer_id: Dict[Optional[str], int] = {} + + # Buffer idx -> lora uid in memory pool + # All uids are initalized as empty strings for empty buffer slots + # Here we don't initalize to None since None is a valid uid + self.buffer_id_to_uid: List[Optional[str]] = [""] * self.max_loras_per_batch + + def get_lora_A_shape( + self, module_name: str, base_model: torch.nn.Module + ) -> Tuple[int]: + """ + Given a module_name (might be a stacked name), return the hidden dims of modules's input and output. + """ + input_dim, _ = get_hidden_dim(module_name, self.base_hf_config, base_model) + c = get_stacked_multiply(module_name) + if self.tp_size > 1: + if module_name in ROW_PARALLELISM_LINEAR_LORA_NAMES: + input_dim = divide(input_dim, self.tp_size) + return ( + self.max_loras_per_batch, + self.max_lora_dim * c, + input_dim, + ) + + def get_lora_B_shape( + self, module_name: str, base_model: torch.nn.Module + ) -> Tuple[int]: + """ + Given a module_name (might be a stacked name), return the hidden dims of modules's input and output. + """ + _, output_dim = get_hidden_dim(module_name, self.base_hf_config, base_model) + c = get_stacked_multiply(module_name) + if self.tp_size > 1: + if module_name not in ROW_PARALLELISM_LINEAR_LORA_NAMES: + output_dim = divide(output_dim, self.tp_size) + return ( + c, + self.max_loras_per_batch, + output_dim, + self.max_lora_dim, + ) + + def init_buffers( + self, + lora_weight_names: Set[Tuple[str]], + base_model: torch.nn.Module, + ): + + # lora_weight_names is a set of name pairs indicating each pair of lora modules to load + # e.g., {("qkv_proj", "q_proj"), ("qkv_proj", "kv_proj"), ("o_proj", "o_proj")} + self.lora_weight_names: Set[Tuple[str]] = lora_weight_names + device = next(base_model.parameters()).device + lora_module_A_names = set([name[0] for name in lora_weight_names]) + lora_module_B_names = set([name[1] for name in lora_weight_names]) + # Init A tensor, column_major=False + for module_A in lora_module_A_names: + lora_A_shape = self.get_lora_A_shape(module_A, base_model) + self.A_buffer[module_A] = [ + torch.empty( + lora_A_shape, + dtype=self.dtype, + device=device, + ) + for i in range(self.num_layer) + ] + # Init B tensor, column_major=True + for module_B in lora_module_B_names: + lora_B_shape = self.get_lora_B_shape(module_B, base_model) + self.B_buffer[module_B] = [ + torch.empty( + lora_B_shape, + dtype=self.dtype, + device=device, + ) + for _ in range(self.num_layer) + ] + + def prepare_lora_batch( + self, + cur_uids: Set[Optional[str]], + lora_adapters: Dict[str, LoRAAdapter], + ): + + def get_available_buffer_slot(): + for buffer_id in range(self.max_loras_per_batch): + # Prioritize empty slots + if self.buffer_id_to_uid[buffer_id] == "": + return buffer_id, "" + + for buffer_id in range(self.max_loras_per_batch): + # Evict unneeded lora + if self.buffer_id_to_uid[buffer_id] not in cur_uids: + return buffer_id, self.buffer_id_to_uid[buffer_id] + + raise ValueError( + "No available buffer slots found. Please ensure the number of active loras is less than max_loras_per_batch." + ) + + for uid in cur_uids: + if uid not in self.uid_to_buffer_id: + buffer_id, evicted_lora_uid = get_available_buffer_slot() + if evicted_lora_uid != "": + self.uid_to_buffer_id.pop(evicted_lora_uid) + self.load_lora_weight_to_buffer( + uid, buffer_id, lora_adapters.get(uid, None) + ) + self.uid_to_buffer_id[uid] = buffer_id + self.buffer_id_to_uid[buffer_id] = uid + + def load_lora_weight_to_buffer( + self, uid: str, buffer_id: int, lora_adapter: LoRAAdapter = None + ): + + if uid is None: + for i in range(self.num_layer): + for k in self.A_buffer.keys(): + self.A_buffer[k][i][buffer_id] = 0 + return + + assert lora_adapter is not None + lora_rank = lora_adapter.config.hf_config["r"] + for layer_id in range(self.num_layer): + layer_weights = lora_adapter.layers[layer_id].weights + temp_A_buffer: Dict[str, torch.Tensor] = {} + temp_B_buffer: Dict[str, torch.Tensor] = {} + for name, weights in layer_weights.items(): + if "lora_A" in name: + lora_weight_name = get_weight_name( + name, self.lora_weight_names, LoRAType.LORA_A + ) + temp_A_buffer[lora_weight_name] = weights + else: + lora_weight_name = get_weight_name( + name, self.lora_weight_names, LoRAType.LORA_B + ) + temp_B_buffer[lora_weight_name] = weights + + if self.tp_size > 1: + cur_layer_modules = self.lora_modules[layer_id] + for module_name, module in cur_layer_modules: + if "qkv_proj" in module_name: + temp_A_buffer["qkv_proj"] = module.slice_lora_a_weights( + temp_A_buffer["qkv_proj"], self.tp_rank + ) + temp_B_buffer["q_proj"], temp_B_buffer["kv_proj"] = ( + module.slice_lora_b_weights( + [temp_B_buffer["q_proj"], temp_B_buffer["kv_proj"]], + self.tp_rank, + ) + ) + else: + weight_name = get_weight_name( + module_name, self.lora_weight_names, LoRAType.LORA_A + ) + temp_A_buffer[weight_name] = module.slice_lora_a_weights( + temp_A_buffer[weight_name], self.tp_rank + ) + temp_B_buffer[weight_name] = module.slice_lora_b_weights( + temp_B_buffer[weight_name], self.tp_rank + ) + + for name, weights in temp_A_buffer.items(): + c = get_stacked_multiply(name) + self.A_buffer[name][layer_id][buffer_id][: lora_rank * c, :].copy_( + weights + ) + + for name, weights in temp_B_buffer.items(): + c = get_stacked_multiply(name) + if c > 1: + for stacked_id in range(c): + self.B_buffer[name][layer_id][stacked_id][buffer_id][ + :, :lora_rank + ].copy_(weights[stacked_id]) + else: + self.B_buffer[name][layer_id][0][buffer_id][:, :lora_rank].copy_( + weights + ) + + def get_tensor( + self, weight_name: str, layer_id: int, lora_type: LoRAType + ) -> torch.Tensor: + + if lora_type == LoRAType.LORA_A: + return self.A_buffer[weight_name][layer_id] + + return self.B_buffer[weight_name][layer_id] + + def get_buffer_id(self, lora_uid: str): + return self.uid_to_buffer_id[lora_uid] diff --git a/lib/python3.12/site-packages/sglang/srt/lora/triton_ops/__init__.py b/lib/python3.12/site-packages/sglang/srt/lora/triton_ops/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..da55e8fd584f5e5b490fd6ab5988741f798a053c --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/lora/triton_ops/__init__.py @@ -0,0 +1,11 @@ +from .gate_up_lora_b import gate_up_lora_b_fwd +from .qkv_lora_b import qkv_lora_b_fwd +from .sgemm_lora_a import sgemm_lora_a_fwd +from .sgemm_lora_b import sgemm_lora_b_fwd + +__all__ = [ + "gate_up_lora_b_fwd", + "qkv_lora_b_fwd", + "sgemm_lora_a_fwd", + "sgemm_lora_b_fwd", +] diff --git a/lib/python3.12/site-packages/sglang/srt/lora/triton_ops/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/sglang/srt/lora/triton_ops/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d456b65f2e4ab68c76bc0b72b64fdff41694b000 Binary files /dev/null and b/lib/python3.12/site-packages/sglang/srt/lora/triton_ops/__pycache__/__init__.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sglang/srt/lora/triton_ops/__pycache__/gate_up_lora_b.cpython-312.pyc b/lib/python3.12/site-packages/sglang/srt/lora/triton_ops/__pycache__/gate_up_lora_b.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..06461804cdde36f81e824b35ea03d1e4c22e9347 Binary files /dev/null and b/lib/python3.12/site-packages/sglang/srt/lora/triton_ops/__pycache__/gate_up_lora_b.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sglang/srt/lora/triton_ops/__pycache__/qkv_lora_b.cpython-312.pyc b/lib/python3.12/site-packages/sglang/srt/lora/triton_ops/__pycache__/qkv_lora_b.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c8883abe64351fe239d1543b21aa110109816859 Binary files /dev/null and b/lib/python3.12/site-packages/sglang/srt/lora/triton_ops/__pycache__/qkv_lora_b.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sglang/srt/lora/triton_ops/__pycache__/sgemm_lora_a.cpython-312.pyc b/lib/python3.12/site-packages/sglang/srt/lora/triton_ops/__pycache__/sgemm_lora_a.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e8ef7a7b998e09e19bb0e6db2227be6b016a6ef4 Binary files /dev/null and b/lib/python3.12/site-packages/sglang/srt/lora/triton_ops/__pycache__/sgemm_lora_a.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sglang/srt/lora/triton_ops/__pycache__/sgemm_lora_b.cpython-312.pyc b/lib/python3.12/site-packages/sglang/srt/lora/triton_ops/__pycache__/sgemm_lora_b.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..77352c128bf4b28ddacb457895b3eed4e4867bcf Binary files /dev/null and b/lib/python3.12/site-packages/sglang/srt/lora/triton_ops/__pycache__/sgemm_lora_b.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sglang/srt/lora/triton_ops/gate_up_lora_b.py b/lib/python3.12/site-packages/sglang/srt/lora/triton_ops/gate_up_lora_b.py new file mode 100644 index 0000000000000000000000000000000000000000..02140408c8fe3e7419eb22b2832082c9e7c580ea --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/lora/triton_ops/gate_up_lora_b.py @@ -0,0 +1,176 @@ +import torch +import triton +import triton.language as tl + +from sglang.srt.lora.utils import LoRABatchInfo + + +@triton.jit +def _gate_up_lora_b_kernel( + # Pointers to matrices + x, + weights, + output, + # Parameters of size + K, # K = R + output_dim, + # Strides + x_stride_0, + x_stride_1, + w_stride_0, + w_stride_1, + w_stride_2, + output_stride_0, + output_stride_1, + # Information on sequence lengths,ranks and weight id + seg_lens, + seg_indptr, + weight_indices, + lora_ranks, + # Meta parameters + BLOCK_S: tl.constexpr, + BLOCK_N: tl.constexpr, + BLOCK_K: tl.constexpr, + # For fused output scaling and adding + fuse_scaling_add, + scalings, +): + # This kernel packs 2 sgemms (gate/up) into a single kernel. + + # x: (s, 2 * K), s is the sum of sequence lengths, K equals to lora rank + # weights: (num_lora, 2 * output_dim, K) + # output: (s, 2 * output_dim) + # output_dim >> K + + # Current block computes sequence with batch_id, + # which starts from row seg_start of x with length seg_len. + # gate_up_id decides which of gate or up (0: gate, 1: up) + batch_id = tl.program_id(axis=2) + gate_up_id = tl.program_id(axis=1) + pid = tl.program_id(axis=0) + seg_len = tl.load(seg_lens + batch_id) + w_index = tl.load(weight_indices + batch_id) + seg_start = tl.load(seg_indptr + batch_id) + n_start = gate_up_id * output_dim # offset on output dim + rank = tl.load(lora_ranks + w_index) + scaling = tl.load(scalings + w_index) + + # Adjust K (rank) according to the specific LoRA adapter + K = tl.minimum(K, rank) + + # The tile in output matrix will have (pid_s, pid_n) as id + num_pid_n = tl.cdiv(output_dim, BLOCK_N) + pid_s = pid // num_pid_n + pid_n = pid % num_pid_n + + # Create pointers for the first block of x and weights + # The pointers will be advanced as we move in the K direction + # and accumulate + s_offset = tl.arange(0, BLOCK_S) + pid_s * BLOCK_S + n_offset = tl.arange(0, BLOCK_N) + pid_n * BLOCK_N + k_offset = tl.arange(0, BLOCK_K) + + x_ptrs = (x + seg_start * x_stride_0 + (gate_up_id * K) * x_stride_1) + ( + s_offset[:, None] * x_stride_0 + k_offset[None, :] * x_stride_1 + ) + w_ptrs = (weights + w_index * w_stride_0 + n_start * w_stride_1) + ( + k_offset[:, None] * w_stride_2 + n_offset[None, :] * w_stride_1 + ) + + # Iteate to compute the block in output matrix + partial_sum = tl.zeros((BLOCK_S, BLOCK_N), dtype=tl.float32) + for k in range(0, tl.cdiv(K, BLOCK_K)): + x_tile = tl.load( + x_ptrs, + mask=(s_offset[:, None] < seg_len) + and (k_offset[None, :] < K - k * BLOCK_K), + other=0.0, + ) + w_tile = tl.load( + w_ptrs, + mask=(k_offset[:, None] < K - k * BLOCK_K) + and (n_offset[None, :] < output_dim), + other=0.0, + ) + partial_sum += tl.dot(x_tile, w_tile) + + x_ptrs += BLOCK_K * x_stride_1 + w_ptrs += BLOCK_K * w_stride_2 + + # Store result to output matrix + partial_sum *= scaling + partial_sum = partial_sum.to(x.dtype.element_ty) + output_ptr = (output + seg_start * output_stride_0 + n_start * output_stride_1) + ( + s_offset[:, None] * output_stride_0 + n_offset[None, :] * output_stride_1 + ) + output_mask = (s_offset[:, None] < seg_len) and (n_offset[None, :] < output_dim) + if fuse_scaling_add: + partial_sum += tl.load(output_ptr, mask=output_mask) + tl.store(output_ptr, partial_sum, mask=output_mask) + + +def gate_up_lora_b_fwd( + x: torch.Tensor, + gate_up_lora_b: torch.Tensor, + batch_info: LoRABatchInfo, + output_dim: int, + base_output: torch.Tensor = None, +) -> torch.Tensor: + + # x: (s, 2 * r) + # gate_up_lora_b: (num_lora, 2 * output_dim, r) + # output: (s, 2 * output_dim) + + # Compute lora_output with shape (s, output_dim) as follows: + # lora_output[:, :output_dim] = sgemm(x[:, :r], gate_up_lora_b[:, :output_dim, :]) + # lora_output[:, output_dim:] + # = sgemm(x[:, r:], gate_up_lora_b[:, output_dim:, :]) + + # Get dims + s = x.shape[0] + input_dim = x.shape[1] + r = gate_up_lora_b.shape[-1] + assert input_dim == 2 * r + + BLOCK_S = 16 + BLOCK_R = 16 + BLOCK_OUT = 64 + + grid_b = ( + triton.cdiv(batch_info.max_len, BLOCK_S) * triton.cdiv(output_dim, BLOCK_OUT), + 2, # this dimension decides current block computes on gate or up proj + batch_info.bs, + ) + + if base_output is None: + output = torch.empty((s, 2 * output_dim), device=x.device, dtype=x.dtype) + fuse_scaling_add = False + else: + output = base_output + fuse_scaling_add = True + + _gate_up_lora_b_kernel[grid_b]( + x, + gate_up_lora_b, + output, + r, + output_dim, + x.stride(0), + x.stride(1), + gate_up_lora_b.stride(0), + gate_up_lora_b.stride(1), + gate_up_lora_b.stride(2), + output.stride(0), + output.stride(1), + batch_info.seg_lens, + batch_info.seg_indptr, + batch_info.weight_indices, + batch_info.lora_ranks, + BLOCK_S, + BLOCK_OUT, + BLOCK_R, + fuse_scaling_add, + batch_info.scalings, + ) + + return output diff --git a/lib/python3.12/site-packages/sglang/srt/lora/triton_ops/qkv_lora_b.py b/lib/python3.12/site-packages/sglang/srt/lora/triton_ops/qkv_lora_b.py new file mode 100644 index 0000000000000000000000000000000000000000..5c43ebdf43a3502e9e4ed40dff2ba3273ed27952 --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/lora/triton_ops/qkv_lora_b.py @@ -0,0 +1,187 @@ +import torch +import triton +import triton.language as tl + +from sglang.srt.lora.utils import LoRABatchInfo + + +@triton.jit +def _qkv_lora_b_kernel( + # Pointers to matrices + x, + weights, + output, + # Parameters of size + K, # K = R + max_qkv_out_dim, # max(output_q_dim, output_kv_dim) + # Strides + x_stride_0, + x_stride_1, + w_stride_0, + w_stride_1, + w_stride_2, + output_stride_0, + output_stride_1, + # Information on sequence lengths and weight id + seg_lens, + seg_indptr, + weight_indices, + lora_ranks, + # Offsets of q/k/v slice on output dimension + n_offs, + # Meta parameters + BLOCK_S: tl.constexpr, + BLOCK_N: tl.constexpr, + BLOCK_K: tl.constexpr, + # For fused output scaling and adding + fuse_scaling_add, + scalings, +): + # This kernel packs 3 sgemms (q/k/v) into a single kernel. + + # x: (s, 3 * K), s is the sum of sequence lengths, K equals to lora rank + # weights: (num_lora, N_Q + 2 * N_KV, K) + # output: (s, N_Q + 2 * N_KV) + # N_Q >> K, N_KV >> K + + # Current block computes sequence with batch_id, + # which starts from row seg_start of x with length seg_len. + # qkv_id decides which of q,k,v to compute (0: q, 1: k, 2: v) + batch_id = tl.program_id(axis=2) + qkv_id = tl.program_id(axis=1) + pid = tl.program_id(axis=0) + seg_len = tl.load(seg_lens + batch_id) + w_index = tl.load(weight_indices + batch_id) + seg_start = tl.load(seg_indptr + batch_id) + n_start = tl.load(n_offs + qkv_id) + n_size = tl.load(n_offs + qkv_id + 1) - n_start + rank = tl.load(lora_ranks + w_index) + scaling = tl.load(scalings + w_index) + # Adjust K (rank) according to the specific LoRA adapter + K = tl.minimum(K, rank) + + # The tile in output matrix will have (pid_s, pid_n) as id + num_pid_n = tl.cdiv(max_qkv_out_dim, BLOCK_N) + pid_s = pid // num_pid_n + pid_n = pid % num_pid_n + + # Create pointers for the first block of x and weights[batch_id][n_start: n_end][:] + # The pointers will be advanced as we move in the K direction + # and accumulate + s_offset = tl.arange(0, BLOCK_S) + pid_s * BLOCK_S + n_offset = tl.arange(0, BLOCK_N) + pid_n * BLOCK_N + k_offset = tl.arange(0, BLOCK_K) + + x_ptrs = (x + seg_start * x_stride_0 + (qkv_id * K) * x_stride_1) + ( + s_offset[:, None] * x_stride_0 + k_offset[None, :] * x_stride_1 + ) + w_ptrs = (weights + w_index * w_stride_0 + n_start * w_stride_1) + ( + k_offset[:, None] * w_stride_2 + n_offset[None, :] * w_stride_1 + ) + + # Iteate to compute the block in output matrix + partial_sum = tl.zeros((BLOCK_S, BLOCK_N), dtype=tl.float32) + for k in range(0, tl.cdiv(K, BLOCK_K)): + x_tile = tl.load( + x_ptrs, + mask=(s_offset[:, None] < seg_len) + and (k_offset[None, :] < K - k * BLOCK_K), + other=0.0, + ) + w_tile = tl.load( + w_ptrs, + mask=(k_offset[:, None] < K - k * BLOCK_K) and (n_offset[None, :] < n_size), + other=0.0, + ) + partial_sum += tl.dot(x_tile, w_tile) + + x_ptrs += BLOCK_K * x_stride_1 + w_ptrs += BLOCK_K * w_stride_2 + + # Store result to output matrix + partial_sum *= scaling + partial_sum = partial_sum.to(x.dtype.element_ty) + output_ptr = (output + seg_start * output_stride_0 + n_start * output_stride_1) + ( + s_offset[:, None] * output_stride_0 + n_offset[None, :] * output_stride_1 + ) + output_mask = (s_offset[:, None] < seg_len) and (n_offset[None, :] < n_size) + if fuse_scaling_add: + partial_sum += tl.load(output_ptr, mask=output_mask) + tl.store(output_ptr, partial_sum, mask=output_mask) + + +def qkv_lora_b_fwd( + x: torch.Tensor, + qkv_lora_b: torch.Tensor, + batch_info: LoRABatchInfo, + output_offset: torch.Tensor, + max_qkv_out_dim: int, + base_output: torch.Tensor = None, +) -> torch.Tensor: + + # x: (s, 3 * r) + # qkv_lora_b: (num_lora, output_dim_q + 2 * output_dim_kv, r) + # output_offset = [0, output_dim_q, output_dim_q + output_dim_kv, + # output_dim_q + 2 * output_dim_kv] + # max_qkv_out_dim = max(output_dim_q, output_dim_kv) + # output: (s, output_dim_q + 2 * output_dim_kv) + + # Compute lora_output with shape (s, output_dim) as follows: + # lora_output[:, :output_dim_q] = sgemm(x[:, :r], qkv_lora_b[:, :outptu_dim_q, :]) + # lora_output[:, output_dim_q: output_dim_q + output_dim_kv] + # = sgemm(x[:, r: 2 * r], qkv_lora_b[:, outptu_dim_q: output_dim_q + output_dim_kv, :]) + # lora_output[:, output_dim_q + output_dim_kv: ] + # = sgemm(x[:, 2 * r: , qkv_lora_b[:, output_dim_q + output_dim_kv: , :]) + + # Get dims + s = x.shape[0] + input_dim = x.shape[1] + r = qkv_lora_b.shape[-1] + output_dim = qkv_lora_b.shape[-2] + assert input_dim == 3 * r + assert output_offset.shape[0] == 4 + + BLOCK_S = 16 + BLOCK_R = 16 + BLOCK_OUT = 64 + + grid_b = ( + triton.cdiv(batch_info.max_len, BLOCK_S) + * triton.cdiv(max_qkv_out_dim, BLOCK_OUT), + 3, # this dimension decides current block computes on q, k or v + batch_info.bs, + ) + + if base_output is None: + output = torch.empty((s, output_dim), device=x.device, dtype=x.dtype) + fuse_scaling_add = False + else: + output = base_output + fuse_scaling_add = True + + _qkv_lora_b_kernel[grid_b]( + x, + qkv_lora_b, + output, + r, + max_qkv_out_dim, + x.stride(0), + x.stride(1), + qkv_lora_b.stride(0), + qkv_lora_b.stride(1), + qkv_lora_b.stride(2), + output.stride(0), + output.stride(1), + batch_info.seg_lens, + batch_info.seg_indptr, + batch_info.weight_indices, + batch_info.lora_ranks, + output_offset, + BLOCK_S, + BLOCK_OUT, + BLOCK_R, + fuse_scaling_add, + batch_info.scalings, + ) + + return output diff --git a/lib/python3.12/site-packages/sglang/srt/lora/triton_ops/sgemm_lora_a.py b/lib/python3.12/site-packages/sglang/srt/lora/triton_ops/sgemm_lora_a.py new file mode 100644 index 0000000000000000000000000000000000000000..3e0980c7ec87d8673747be0c5b9674547463eabd --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/lora/triton_ops/sgemm_lora_a.py @@ -0,0 +1,154 @@ +import torch +import triton +import triton.language as tl + +from sglang.srt.lora.utils import LoRABatchInfo + + +@triton.jit +def _sgemm_lora_a_kernel( + # Pointers to matrices + x, + weights, + output, + # Matrix dimensions + N, # stack_num * r + K, # input_dim + stack_num, + # Strides + x_stride_0, + x_stride_1, + w_stride_0, + w_stride_1, + w_stride_2, + output_stride_0, + output_stride_1, + # Information on sequence lengths,ranks and weight id + seg_lens, + seg_indptr, + weight_indices, + lora_ranks, + # Meta parameters + BLOCK_S: tl.constexpr, + BLOCK_N: tl.constexpr, + BLOCK_K: tl.constexpr, +): + + # x: (s, K), s is the sum of sequence lengths + # weights: (num_lora, N, K) + # output: (s, N) + + # Current block computes sequence with batch_id, + # which starts from row seg_start of x with length seg_len + batch_id = tl.program_id(axis=1) + pid = tl.program_id(axis=0) + seg_len = tl.load(seg_lens + batch_id) + w_index = tl.load(weight_indices + batch_id) + seg_start = tl.load(seg_indptr + batch_id) + rank = tl.load(lora_ranks + w_index) + # Adjust N (stack_num * max_rank) according to the specific LoRA adapter + N = tl.minimum(N, rank * stack_num) + + # The tile in output matrix will have (pid_s, pid_n) as id + num_pid_n = tl.cdiv(N, BLOCK_N) + pid_s = pid // num_pid_n + pid_n = pid % num_pid_n + + # Create pointers for the first block of x and weights[batch_id] + # The pointers will be advanced as we move in the K direction + # and accumulate + s_offset = tl.arange(0, BLOCK_S) + pid_s * BLOCK_S + n_offset = tl.arange(0, BLOCK_N) + pid_n * BLOCK_N + k_offset = tl.arange(0, BLOCK_K) + x_ptrs = (x + seg_start * x_stride_0) + ( + s_offset[:, None] * x_stride_0 + k_offset[None, :] * x_stride_1 + ) + w_ptrs = (weights + w_index * w_stride_0) + ( + k_offset[:, None] * w_stride_2 + n_offset[None, :] * w_stride_1 + ) + + # Iteate to compute the block in output matrix + partial_sum = tl.zeros((BLOCK_S, BLOCK_N), dtype=tl.float32) + for k in range(0, tl.cdiv(K, BLOCK_K)): + x_tile = tl.load( + x_ptrs, + mask=(s_offset[:, None] < seg_len) + and (k_offset[None, :] < K - k * BLOCK_K), + other=0.0, + ) + w_tile = tl.load( + w_ptrs, + mask=(k_offset[:, None] < K - k * BLOCK_K) and (n_offset[None, :] < N), + other=0.0, + ) + partial_sum += tl.dot(x_tile, w_tile) + + x_ptrs += BLOCK_K * x_stride_1 + w_ptrs += BLOCK_K * w_stride_2 + + # Store result to output matrix + partial_sum = partial_sum.to(x.dtype.element_ty) + output_ptr = (output + seg_start * output_stride_0) + ( + s_offset[:, None] * output_stride_0 + n_offset[None, :] * output_stride_1 + ) + output_mask = (s_offset[:, None] < seg_len) and (n_offset[None, :] < N) + tl.store(output_ptr, partial_sum, mask=output_mask) + + +def sgemm_lora_a_fwd( + x: torch.Tensor, + weights: torch.Tensor, + batch_info: LoRABatchInfo, + stack_num: int = 1, +) -> torch.Tensor: + # x: (s, input_dim) + # weights: (num_lora, stack_num * r, input_dim) + # output: (s, stack_num * r) + # stack_num: run_qkv_lora: 3, run_gate_up_lora: 2 + # when called by run_qkv_lora, the weights.shape[-2] will be 3 * r + # input_dim is much larger than r + + assert x.is_contiguous() + assert weights.is_contiguous() + assert len(x.shape) == 2 + assert len(weights.shape) == 3 + + S = x.shape[0] + R = weights.shape[-2] + K = weights.shape[-1] + assert x.shape[-1] == K + + # Block shapes + BLOCK_S = 16 + BLOCK_K = 256 + BLOCK_R = 16 + + grid = ( + triton.cdiv(batch_info.max_len, BLOCK_S) * triton.cdiv(R, BLOCK_R), + batch_info.bs, + ) + + output = torch.empty((S, R), device=x.device, dtype=x.dtype) + _sgemm_lora_a_kernel[grid]( + x, + weights, + output, + R, + K, + stack_num, + x.stride(0), + x.stride(1), + weights.stride(0), + weights.stride(1), + weights.stride(2), + output.stride(0), + output.stride(1), + batch_info.seg_lens, + batch_info.seg_indptr, + batch_info.weight_indices, + batch_info.lora_ranks, + BLOCK_S, + BLOCK_R, + BLOCK_K, + ) + return output diff --git a/lib/python3.12/site-packages/sglang/srt/lora/triton_ops/sgemm_lora_b.py b/lib/python3.12/site-packages/sglang/srt/lora/triton_ops/sgemm_lora_b.py new file mode 100644 index 0000000000000000000000000000000000000000..28b9f4fbd03c24b9f1627f29c21bacf8e1a9a161 --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/lora/triton_ops/sgemm_lora_b.py @@ -0,0 +1,164 @@ +import torch +import triton +import triton.language as tl + +from sglang.srt.lora.utils import LoRABatchInfo + + +@triton.jit +def _sgemm_lora_b_kernel( + # Pointers to matrices + x, + weights, + output, + # Matrix dimensions + N, # output_dim + K, # r + # Strides + x_stride_0, + x_stride_1, + w_stride_0, + w_stride_1, + w_stride_2, + output_stride_0, + output_stride_1, + # Information on sequence lengths and weight id + seg_lens, + seg_indptr, + weight_indices, + lora_ranks, + # Meta parameters + BLOCK_S: tl.constexpr, + BLOCK_N: tl.constexpr, + BLOCK_K: tl.constexpr, + # For fused output scaling and adding + fuse_scaling_add, + scalings, +): + # x: (s, K), s is the sum of sequence lengths + # weights: (num_lora, N, K) + # output: (s, N) + + # Current block computes sequence with batch_id, + # which starts from row seg_start of x with length seg_len + batch_id = tl.program_id(axis=1) + pid = tl.program_id(axis=0) + seg_len = tl.load(seg_lens + batch_id) + w_index = tl.load(weight_indices + batch_id) + seg_start = tl.load(seg_indptr + batch_id) + rank = tl.load(lora_ranks + w_index) + scaling = tl.load(scalings + w_index) + # Adjust K (rank) according to the specific LoRA adapter + K = tl.minimum(K, rank) + + # The tile in output matrix will have (pid_s, pid_n) as id + num_pid_n = tl.cdiv(N, BLOCK_N) + pid_s = pid // num_pid_n + pid_n = pid % num_pid_n + + # Create pointers for the first block of x and weights[batch_id] + # The pointers will be advanced as we move in the K direction + # and accumulate + s_offset = tl.arange(0, BLOCK_S) + pid_s * BLOCK_S + n_offset = tl.arange(0, BLOCK_N) + pid_n * BLOCK_N + k_offset = tl.arange(0, BLOCK_K) + x_ptrs = (x + seg_start * x_stride_0) + ( + s_offset[:, None] * x_stride_0 + k_offset[None, :] * x_stride_1 + ) + w_ptrs = (weights + w_index * w_stride_0) + ( + k_offset[:, None] * w_stride_2 + n_offset[None, :] * w_stride_1 + ) + + # Iteate to compute the block in output matrix + partial_sum = tl.zeros((BLOCK_S, BLOCK_N), dtype=tl.float32) + for k in range(0, tl.cdiv(K, BLOCK_K)): + x_tile = tl.load( + x_ptrs, + mask=(s_offset[:, None] < seg_len) + and (k_offset[None, :] < K - k * BLOCK_K), + other=0.0, + ) + w_tile = tl.load( + w_ptrs, + mask=(k_offset[:, None] < K - k * BLOCK_K), + other=0.0, + ) + partial_sum += tl.dot(x_tile, w_tile) + + x_ptrs += BLOCK_K * x_stride_1 + w_ptrs += BLOCK_K * w_stride_2 + + # Store result to output matrix + partial_sum *= scaling + partial_sum = partial_sum.to(x.dtype.element_ty) + output_ptr = (output + seg_start * output_stride_0) + ( + s_offset[:, None] * output_stride_0 + n_offset[None, :] * output_stride_1 + ) + output_mask = s_offset[:, None] < seg_len + if fuse_scaling_add: + partial_sum += tl.load(output_ptr, mask=output_mask) + tl.store(output_ptr, partial_sum, mask=output_mask) + + +def sgemm_lora_b_fwd( + x: torch.Tensor, + weights: torch.Tensor, + batch_info: LoRABatchInfo, + base_output: torch.Tensor = None, +) -> torch.Tensor: + # x: (s, max_r) + # weights: (num_lora, output_dim, max_r) + # output: (s, output_dim) + # output_dim is much larger than max_r + + assert x.is_contiguous() + assert weights.is_contiguous() + assert len(x.shape) == 2 + assert len(weights.shape) == 3 + + S = x.shape[0] + N = weights.shape[-2] + R = weights.shape[-1] + assert x.shape[-1] == R + + # Block shapes + BLOCK_S = 16 + BLOCK_R = 16 + BLOCK_N = 256 + + grid = ( + triton.cdiv(batch_info.max_len, BLOCK_S) * triton.cdiv(N, BLOCK_N), + batch_info.bs, + ) + + if base_output is None: + output = torch.empty((S, N), device=x.device, dtype=x.dtype) + fuse_scaling_add = False + else: + output = base_output + fuse_scaling_add = True + + _sgemm_lora_b_kernel[grid]( + x, + weights, + output, + N, + R, + x.stride(0), + x.stride(1), + weights.stride(0), + weights.stride(1), + weights.stride(2), + output.stride(0), + output.stride(1), + batch_info.seg_lens, + batch_info.seg_indptr, + batch_info.weight_indices, + batch_info.lora_ranks, + BLOCK_S, + BLOCK_N, + BLOCK_R, + fuse_scaling_add, + batch_info.scalings, + ) + return output diff --git a/lib/python3.12/site-packages/sglang/srt/lora/utils.py b/lib/python3.12/site-packages/sglang/srt/lora/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..2ae07b24e7071bbc32cd14210e9bcf2d9262c8e2 --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/lora/utils.py @@ -0,0 +1,158 @@ +import re +from dataclasses import dataclass +from enum import Enum +from typing import Optional, Set, Tuple + +import torch + +from sglang.srt.hf_transformers_utils import AutoConfig + + +@dataclass +class LoRABatchInfo: + # Batch size + bs: int + + # Lengths of each sequence in shape (bs,) + seg_lens: torch.Tensor + + # Indice pointers of each sequence in shape (bs + 1, ) + seg_indptr: torch.Tensor + + # Maximum sequence length of current batch + max_len: int + + # The index of lora adapter used by each sequence, in shape (bs,) + 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 + + +class LoRAType(Enum): + LORA_A = 0 + LORA_B = 1 + + +def get_layer_id(name: str) -> int: + """ + Extract integer id of layer from its name in string. + """ + match = re.search(r"layers\.(\d+)\.", name) + if match is None: + return None + return int(match.group(1)) + + +def get_customized_names_from_hf_names( + hf_module_names: Set[str], base_model: torch.nn.Module +) -> Set[str]: + """ + This function takes in a set of huggingface style module names: + e.g., {"k_proj", "q_proj", "v_proj", "o_proj"} + and outputs a set of module names of customized sglang layers: + e.g., {"qkv_proj", "o_proj"} + """ + if hasattr(base_model, "get_module_name"): + return {base_model.get_module_name(name) for name in hf_module_names} + else: + """ + Fallback solution of mapping from config module name to module name in model class. + Please check if it aligns with your base model. + Please implement the function in the model class if it is not. + You can reference this function in llama.py. + """ + params_mapping = { + "q_proj": "qkv_proj", + "k_proj": "qkv_proj", + "v_proj": "qkv_proj", + "gate_proj": "gate_up_proj", + "up_proj": "gate_up_proj", + } + return {params_mapping.get(name, name) for name in hf_module_names} + + +def get_hidden_dim( + module_name: str, config: AutoConfig, base_model: torch.nn.Module +) -> Tuple[int]: + """ + Given a module_name (might be a stacked name), return the hidden dims of modules's input and output. + """ + + if hasattr(base_model, "get_hidden_dim"): + return base_model.get_hidden_dim(module_name) + 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. + """ + if module_name in ["q_proj", "o_proj", "qkv_proj"]: + return config.hidden_size, config.hidden_size + elif module_name in ["kv_proj"]: + return config.hidden_size, config.hidden_size // ( + config.num_attention_heads // config.num_key_value_heads + ) + elif module_name == "gate_up_proj": + return config.hidden_size, config.intermediate_size + elif module_name == "down_proj": + return config.intermediate_size, config.hidden_size + else: + raise NotImplementedError() + + +def get_stacked_name(name: str) -> Tuple[str]: + """ + Mapping a target module name to (stacked name for Lora A, stacked name for Lora B) + """ + params_mapping = { + "q_proj": ("qkv_proj", "q_proj"), + "k_proj": ("qkv_proj", "kv_proj"), + "v_proj": ("qkv_proj", "kv_proj"), + "gate_proj": ("gate_up_proj", "gate_up_proj"), + "up_proj": ("gate_up_proj", "gate_up_proj"), + } + return params_mapping.get(name, (name, name)) + + +def get_stacked_multiply(module_name: str) -> int: + """ + Mapping a lora module name to its magnification at output dimension + """ + stacked_rank = { + "qkv_proj": 3, + "kv_proj": 2, + "gate_up_proj": 2, + } + return stacked_rank[module_name] if module_name in stacked_rank else 1 + + +def get_weight_name( + target_name: str, lora_weight_names: Set[Tuple[str]], lora_type: LoRAType +) -> Optional[str]: + """ + target_name is name of a given module, + lora_weight_names is a set of lora stacked name pairs (see get_stacked_name method above) + If there is a weight name in lora_weight_names that can match target_name, return this name + Else raise ValueError. + """ + idx = 0 if lora_type == LoRAType.LORA_A else 1 + for weight_name_pair in lora_weight_names: + if weight_name_pair[idx] in target_name: + return weight_name_pair[idx] + raise ValueError( + f"Cannot find weight name for {target_name} in {lora_weight_names}" + ) + + +# TODO: [PR #4274] For future use to simplify the mapping between HF module names and customized module names. +VOCAB_PARALLELISM_EMBEDDING_NAMES = ["embeddings"] +COLUMN_PARALLELISM_LINEAR_LORA_NAMES = ["gate_proj", "up_proj"] +MERGED_COLUMN_PARALLELISM_LINEAR_LORA_NAMES = ["gate_up_proj"] +QKV_PARALLELISM_LINEAR_LORA_NAMES = ["qkv_proj"] +ROW_PARALLELISM_LINEAR_LORA_NAMES = ["o_proj", "down_proj"] diff --git a/lib/python3.12/site-packages/sglang/srt/metrics/__pycache__/collector.cpython-312.pyc b/lib/python3.12/site-packages/sglang/srt/metrics/__pycache__/collector.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..adb0dfc3b0a3fcc0f5ed6065907c1c7a6eeaca69 Binary files /dev/null and b/lib/python3.12/site-packages/sglang/srt/metrics/__pycache__/collector.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sglang/srt/metrics/__pycache__/func_timer.cpython-312.pyc b/lib/python3.12/site-packages/sglang/srt/metrics/__pycache__/func_timer.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3be6fce9efd246e2e59e94bf3c2011b23badf1a2 Binary files /dev/null and b/lib/python3.12/site-packages/sglang/srt/metrics/__pycache__/func_timer.cpython-312.pyc differ diff --git a/lib/python3.12/site-packages/sglang/srt/metrics/collector.py b/lib/python3.12/site-packages/sglang/srt/metrics/collector.py new file mode 100644 index 0000000000000000000000000000000000000000..b881406e6debe7e45b7159d388891543a927841b --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/metrics/collector.py @@ -0,0 +1,260 @@ +# Copyright 2023-2024 SGLang Team +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Utilities for Prometheus Metrics Collection.""" + +import time +from dataclasses import dataclass +from typing import Dict, Union + + +@dataclass +class SchedulerStats: + num_running_reqs: int = 0 + num_used_tokens: int = 0 + token_usage: float = 0.0 + gen_throughput: float = 0.0 + num_queue_reqs: int = 0 + cache_hit_rate: float = 0.0 + spec_accept_length: float = 0.0 + avg_request_queue_latency: float = 0.0 + + +class SchedulerMetricsCollector: + + def __init__(self, labels: Dict[str, str]) -> None: + # We need to import prometheus_client after setting the env variable `PROMETHEUS_MULTIPROC_DIR` + from prometheus_client import Gauge, Histogram + + self.labels = labels + self.last_log_time = time.time() + + self.num_running_reqs = Gauge( + name="sglang:num_running_reqs", + documentation="The number of running requests.", + labelnames=labels.keys(), + multiprocess_mode="mostrecent", + ) + + self.num_used_tokens = Gauge( + name="sglang:num_used_tokens", + documentation="The number of used tokens.", + labelnames=labels.keys(), + multiprocess_mode="mostrecent", + ) + + self.token_usage = Gauge( + name="sglang:token_usage", + documentation="The token usage.", + labelnames=labels.keys(), + multiprocess_mode="mostrecent", + ) + + self.gen_throughput = Gauge( + name="sglang:gen_throughput", + documentation="The generation throughput (token/s).", + labelnames=labels.keys(), + multiprocess_mode="mostrecent", + ) + + self.num_queue_reqs = Gauge( + name="sglang:num_queue_reqs", + documentation="The number of requests in the waiting queue.", + labelnames=labels.keys(), + multiprocess_mode="mostrecent", + ) + + self.cache_hit_rate = Gauge( + name="sglang:cache_hit_rate", + documentation="The prefix cache hit rate.", + labelnames=labels.keys(), + multiprocess_mode="mostrecent", + ) + + self.spec_accept_length = Gauge( + name="sglang:spec_accept_length", + documentation="The average acceptance length of speculative decoding.", + labelnames=labels.keys(), + multiprocess_mode="mostrecent", + ) + + self.avg_request_queue_latency = Gauge( + name="sglang:avg_request_queue_latency", + documentation="The average request queue latency for the last batch of requests in seconds.", + labelnames=labels.keys(), + multiprocess_mode="mostrecent", + ) + + def _log_gauge(self, gauge, data: Union[int, float]) -> None: + # Convenience function for logging to gauge. + gauge.labels(**self.labels).set(data) + + def log_stats(self, stats: SchedulerStats) -> None: + self._log_gauge(self.num_running_reqs, stats.num_running_reqs) + self._log_gauge(self.num_used_tokens, stats.num_used_tokens) + self._log_gauge(self.token_usage, stats.token_usage) + self._log_gauge(self.gen_throughput, stats.gen_throughput) + self._log_gauge(self.num_queue_reqs, stats.num_queue_reqs) + self._log_gauge(self.cache_hit_rate, stats.cache_hit_rate) + self._log_gauge(self.spec_accept_length, stats.spec_accept_length) + self._log_gauge(self.avg_request_queue_latency, stats.avg_request_queue_latency) + self.last_log_time = time.time() + + +class TokenizerMetricsCollector: + def __init__(self, labels: Dict[str, str]) -> None: + # We need to import prometheus_client after setting the env variable `PROMETHEUS_MULTIPROC_DIR` + from prometheus_client import Counter, Histogram + + self.labels = labels + + self.prompt_tokens_total = Counter( + name="sglang:prompt_tokens_total", + documentation="Number of prefill tokens processed.", + labelnames=labels.keys(), + ) + + self.generation_tokens_total = Counter( + name="sglang:generation_tokens_total", + documentation="Number of generation tokens processed.", + labelnames=labels.keys(), + ) + + self.cached_tokens_total = Counter( + name="sglang:cached_tokens_total", + documentation="Number of cached prompt tokens.", + labelnames=labels.keys(), + ) + + self.num_requests_total = Counter( + name="sglang:num_requests_total", + documentation="Number of requests processed.", + labelnames=labels.keys(), + ) + + self.histogram_time_to_first_token = Histogram( + name="sglang:time_to_first_token_seconds", + documentation="Histogram of time to first token in seconds.", + labelnames=labels.keys(), + buckets=[ + 0.1, + 0.2, + 0.4, + 0.6, + 0.8, + 1, + 2, + 4, + 6, + 8, + 10, + 20, + 40, + 60, + 80, + 100, + 200, + 400, + ], + ) + + self.histogram_inter_token_latency_seconds = Histogram( + name="sglang:inter_token_latency_seconds", + documentation="Histogram of inter-token latency in seconds.", + labelnames=labels.keys(), + buckets=[ + 0.002, + 0.004, + 0.006, + 0.008, + 0.010, + 0.015, + 0.020, + 0.025, + 0.030, + 0.035, + 0.040, + 0.060, + 0.080, + 0.100, + 0.200, + 0.400, + 0.600, + 0.800, + 1.000, + 2.000, + 4.000, + 6.000, + 8.000, + ], + ) + + self.histogram_e2e_request_latency = Histogram( + name="sglang:e2e_request_latency_seconds", + documentation="Histogram of End-to-end request latency in seconds", + labelnames=labels.keys(), + buckets=[ + 0.1, + 0.2, + 0.4, + 0.6, + 0.8, + 1, + 2, + 4, + 6, + 8, + 10, + 20, + 40, + 60, + 80, + 100, + 200, + 400, + 800, + ], + ) + + def _log_histogram(self, histogram, data: Union[int, float]) -> None: + histogram.labels(**self.labels).observe(data) + + def observe_one_finished_request( + self, + prompt_tokens: int, + generation_tokens: int, + cached_tokens: int, + e2e_latency: float, + ): + self.prompt_tokens_total.labels(**self.labels).inc(prompt_tokens) + self.generation_tokens_total.labels(**self.labels).inc(generation_tokens) + if cached_tokens > 0: + self.cached_tokens_total.labels(**self.labels).inc(cached_tokens) + self.num_requests_total.labels(**self.labels).inc(1) + self._log_histogram(self.histogram_e2e_request_latency, e2e_latency) + + def observe_time_to_first_token(self, value: float): + self.histogram_time_to_first_token.labels(**self.labels).observe(value) + + def observe_inter_token_latency(self, internval: float, num_new_tokens: int): + adjusted_interval = internval / num_new_tokens + + # A faster version of the Histogram::observe which observes multiple values at the same time. + # reference: https://github.com/prometheus/client_python/blob/v0.21.1/prometheus_client/metrics.py#L639 + his = self.histogram_inter_token_latency_seconds.labels(**self.labels) + his._sum.inc(internval) + + for i, bound in enumerate(his._upper_bounds): + if adjusted_interval <= bound: + his._buckets[i].inc(num_new_tokens) + break diff --git a/lib/python3.12/site-packages/sglang/srt/metrics/func_timer.py b/lib/python3.12/site-packages/sglang/srt/metrics/func_timer.py new file mode 100644 index 0000000000000000000000000000000000000000..e965d25f8633d9558e55e01b8e3cbdafa8c52ca7 --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/metrics/func_timer.py @@ -0,0 +1,106 @@ +# Copyright 2023-2024 SGLang Team +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +""" +Records the latency of some functions +""" + +import asyncio +import time +from functools import wraps +from typing import Any, Callable, List, Optional + +enable_metrics = False + + +def enable_func_timer(): + # We need to import prometheus_client after setting the env variable `PROMETHEUS_MULTIPROC_DIR` + from prometheus_client import Histogram + + global enable_metrics, FUNC_LATENCY + enable_metrics = True + + FUNC_LATENCY = Histogram( + "sglang:func_latency_seconds", + "Function latency in seconds", + # captures latency in range [50ms - ~50s] + buckets=exponential_buckets(start=0.05, width=1.5, length=18), + labelnames=["name"], + ) + + +FUNC_LATENCY = None + + +def exponential_buckets(start: float, width: float, length: int) -> List[float]: + buckets = [] + for i in range(length): + buckets.append(start * (width**i)) + return buckets + + +def time_func_latency( + func: Callable = None, name: Optional[str] = None +) -> Callable[..., Any]: + """ + A decorator to observe the latency of a function's execution. Supports both sync and async functions. + + NOTE: We use our own implementation of a timer decorator since prometheus_client does not support async + context manager yet. + + Overhead: The overhead introduced here in case of an async function could likely be because of `await` introduced + which will return in another coroutine object creation and under heavy load could see longer wall time + (scheduling delays due to introduction of another awaitable). + """ + + def measure(func: Callable[..., Any]) -> Callable[..., Any]: + nonlocal name + + name = name or func.__name__ + + @wraps(func) + async def async_wrapper(*args, **kwargs): + if not enable_metrics: + return await func(*args, **kwargs) + + metric = FUNC_LATENCY + start = time.monotonic() + ret = func(*args, **kwargs) + if isinstance(ret, asyncio.Future) or asyncio.iscoroutine(ret): + try: + ret = await ret + finally: + metric.labels(name=name).observe(time.monotonic() - start) + return ret + + @wraps(func) + def sync_wrapper(*args, **kwargs): + if not enable_metrics: + return func(*args, **kwargs) + + metric = FUNC_LATENCY + start = time.monotonic() + try: + ret = func(*args, **kwargs) + finally: + metric.labels(name=name).observe(time.monotonic() - start) + return ret + + if asyncio.iscoroutinefunction(func): + return async_wrapper + return sync_wrapper + + if func: + return measure(func) + else: + return measure diff --git a/lib/python3.12/site-packages/sglang/srt/models/baichuan.py b/lib/python3.12/site-packages/sglang/srt/models/baichuan.py new file mode 100644 index 0000000000000000000000000000000000000000..6e8d3b4a345aacd5b4729be95293fe6783684dd5 --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/models/baichuan.py @@ -0,0 +1,441 @@ +# coding=utf-8 +# Copyright 2022 EleutherAI and the HuggingFace Inc. team. All rights reserved. +# +# This code is based on EleutherAI's GPT-NeoX library and the GPT-NeoX +# and OPT implementations in this library. It has been modified from its +# original forms to accommodate minor architectural differences compared +# to GPT-NeoX and OPT used by the Meta AI team that trained the model. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Inference-only BaiChuan model compatible with HuggingFace weights.""" +import math +from typing import Iterable, Optional, Tuple + +import torch +from torch import nn +from transformers import PretrainedConfig + +from sglang.srt.distributed import ( + get_tensor_model_parallel_rank, + get_tensor_model_parallel_world_size, +) +from sglang.srt.layers.activation import SiluAndMul +from sglang.srt.layers.layernorm import RMSNorm +from sglang.srt.layers.linear import ( + MergedColumnParallelLinear, + QKVParallelLinear, + RowParallelLinear, +) +from sglang.srt.layers.logits_processor import LogitsProcessor +from sglang.srt.layers.quantization.base_config import QuantizationConfig +from sglang.srt.layers.radix_attention import RadixAttention +from sglang.srt.layers.rotary_embedding import get_rope +from sglang.srt.layers.vocab_parallel_embedding import ( + ParallelLMHead, + VocabParallelEmbedding, +) +from sglang.srt.model_executor.forward_batch_info import ForwardBatch +from sglang.srt.model_loader.weight_utils import default_weight_loader +from sglang.srt.utils import add_prefix + + +def _get_alibi_slopes(total_num_heads: int) -> torch.Tensor: + closest_power_of_2 = 2 ** math.floor(math.log2(total_num_heads)) + base = torch.tensor( + 2 ** (-(2 ** -(math.log2(closest_power_of_2) - 3))), + dtype=torch.float32, + ) + powers = torch.arange(1, 1 + closest_power_of_2, dtype=torch.int32) + slopes = torch.pow(base, powers) + + if closest_power_of_2 != total_num_heads: + extra_base = torch.tensor( + 2 ** (-(2 ** -(math.log2(2 * closest_power_of_2) - 3))), + dtype=torch.float32, + ) + num_remaining_heads = min( + closest_power_of_2, total_num_heads - closest_power_of_2 + ) + extra_powers = torch.arange( + start=1, end=1 + 2 * num_remaining_heads, step=2, dtype=torch.int32 + ) + slopes = torch.cat([slopes, torch.pow(extra_base, extra_powers)], dim=0) + return slopes + + +class BaiChuanMLP(nn.Module): + + def __init__( + self, + hidden_size: int, + intermediate_size: int, + hidden_act: str, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ): + super().__init__() + self.gate_up_proj = MergedColumnParallelLinear( + hidden_size, + [intermediate_size] * 2, + bias=False, + quant_config=quant_config, + prefix=add_prefix("gate_up_proj", prefix), + ) + self.down_proj = RowParallelLinear( + intermediate_size, + hidden_size, + bias=False, + quant_config=quant_config, + prefix=add_prefix("down_proj", prefix), + ) + if hidden_act != "silu": + raise ValueError( + f"Unsupported activation: {hidden_act}. " + "Only silu is supported for now." + ) + self.act_fn = SiluAndMul() + + def forward(self, x): + gate_up, _ = self.gate_up_proj(x) + x = self.act_fn(gate_up) + x, _ = self.down_proj(x) + return x + + +class BaiChuanAttention(nn.Module): + """Multi-headed attention from 'Attention Is All You Need' paper""" + + def __init__( + self, + hidden_size: int, + num_heads: int, + position_embedding: str, + rope_theta: float = 10000, + max_position_embeddings: int = 8192, + quant_config: Optional[QuantizationConfig] = None, + layer_id: int = 0, + prefix: str = "", + ): + super().__init__() + self.hidden_size = hidden_size + tensor_model_parallel_world_size = get_tensor_model_parallel_world_size() + tp_size = get_tensor_model_parallel_world_size() + self.total_num_heads = num_heads + assert self.total_num_heads % tensor_model_parallel_world_size == 0 + self.num_heads = self.total_num_heads // tensor_model_parallel_world_size + self.head_dim = hidden_size // self.total_num_heads + self.postion_embedding = position_embedding + self.rope_theta = rope_theta + self.max_position_embeddings = max_position_embeddings + self.total_num_kv_heads = self.num_heads + if self.total_num_kv_heads >= tp_size: + # Number of KV heads is greater than TP size, so we partition + # the KV heads across multiple tensor parallel GPUs. + assert self.total_num_kv_heads % tp_size == 0 + else: + # Number of KV heads is less than TP size, so we replicate + # the KV heads across multiple tensor parallel GPUs. + assert tp_size % self.total_num_kv_heads == 0 + self.num_kv_heads = max(1, self.total_num_kv_heads // tp_size) + + # pylint: disable=invalid-name + self.W_pack = QKVParallelLinear( + hidden_size, + self.head_dim, + self.total_num_heads, + self.total_num_heads, + bias=False, + quant_config=quant_config, + ) + self.o_proj = RowParallelLinear( + self.total_num_heads * self.head_dim, + hidden_size, + bias=False, + quant_config=quant_config, + ) + # Create the alibi slopes and slice them. + if self.postion_embedding == "ALIBI": + tp_rank = get_tensor_model_parallel_rank() + head_start = tp_rank * self.num_heads + head_end = (tp_rank + 1) * self.num_heads + alibi_slopes = _get_alibi_slopes(self.total_num_heads) + alibi_slopes = alibi_slopes[head_start:head_end].tolist() + + scaling = self.head_dim**-0.5 + self.attn = RadixAttention( + self.num_heads, + self.head_dim, + scaling, + num_kv_heads=self.num_kv_heads, + layer_id=layer_id, + quant_config=quant_config, + prefix=add_prefix("attn", prefix), + ) + else: + self.rotary_emb = get_rope( + self.head_dim, + rotary_dim=self.head_dim, + max_position=self.max_position_embeddings, + base=self.rope_theta, + ) + self.scaling = self.head_dim**-0.5 + self.attn = RadixAttention( + self.num_heads, + self.head_dim, + self.scaling, + num_kv_heads=self.num_kv_heads, + layer_id=layer_id, + quant_config=quant_config, + prefix=add_prefix("attn", prefix), + ) + + def forward( + self, + positions: torch.Tensor, + hidden_states: torch.Tensor, + forward_batch: ForwardBatch, + ) -> torch.Tensor: + qkv, _ = self.W_pack(hidden_states) + q, k, v = qkv.chunk(chunks=3, dim=-1) + if self.postion_embedding != "ALIBI": + q, k = self.rotary_emb(positions, q, k) + attn_output = self.attn(q, k, v, forward_batch) + output, _ = self.o_proj(attn_output) + return output + + +class BaiChuanDecoderLayer(nn.Module): + + def __init__( + self, + config: PretrainedConfig, + position_embedding: str, + layer_id: int = 0, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ): + super().__init__() + self.hidden_size = config.hidden_size + rope_theta = getattr(config, "rope_theta", 10000) + max_position_embeddings = getattr(config, "max_position_embeddings", 8192) + self.self_attn = BaiChuanAttention( + hidden_size=self.hidden_size, + num_heads=config.num_attention_heads, + position_embedding=position_embedding, + rope_theta=rope_theta, + layer_id=layer_id, + max_position_embeddings=max_position_embeddings, + quant_config=quant_config, + prefix=add_prefix("self_attn", prefix), + ) + self.mlp = BaiChuanMLP( + hidden_size=self.hidden_size, + intermediate_size=config.intermediate_size, + hidden_act=config.hidden_act, + quant_config=quant_config, + prefix=add_prefix("mlp", prefix), + ) + self.input_layernorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps) + self.post_attention_layernorm = RMSNorm( + config.hidden_size, eps=config.rms_norm_eps + ) + + def forward( + self, + positions: torch.Tensor, + hidden_states: torch.Tensor, + forward_batch: ForwardBatch, + residual: Optional[torch.Tensor], + ) -> Tuple[torch.Tensor, torch.Tensor]: + # Self Attention + if residual is None: + residual = hidden_states + hidden_states = self.input_layernorm(hidden_states) + else: + hidden_states, residual = self.input_layernorm(hidden_states, residual) + hidden_states = self.self_attn( + positions=positions, + hidden_states=hidden_states, + forward_batch=forward_batch, + ) + + # Fully Connected + hidden_states, residual = self.post_attention_layernorm(hidden_states, residual) + hidden_states = self.mlp(hidden_states) + return hidden_states, residual + + +class BaiChuanModel(nn.Module): + + def __init__( + self, + config: PretrainedConfig, + position_embedding: str, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ): + super().__init__() + self.config = config + self.padding_idx = config.pad_token_id + self.vocab_size = config.vocab_size + + self.embed_tokens = VocabParallelEmbedding( + config.vocab_size, + config.hidden_size, + ) + self.layers = nn.ModuleList( + [ + BaiChuanDecoderLayer( + config, + layer_id=i, + position_embedding=position_embedding, + quant_config=quant_config, + prefix=add_prefix(f"layers.{i}", prefix), + ) + for i in range(config.num_hidden_layers) + ] + ) + self.norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps) + + def forward( + self, + input_ids: torch.Tensor, + positions: torch.Tensor, + forward_batch: ForwardBatch, + ) -> torch.Tensor: + hidden_states = self.embed_tokens(input_ids) + residual = None + for i in range(len(self.layers)): + layer = self.layers[i] + hidden_states, residual = layer( + positions, + hidden_states, + forward_batch, + residual, + ) + hidden_states, _ = self.norm(hidden_states, residual) + return hidden_states + + +class BaiChuanBaseForCausalLM(nn.Module): + packed_modules_mapping = { + "W_pack": ["W_pack"], + "gate_up_proj": [ + "gate_proj", + "up_proj", + ], + } + # LoRA specific attributes + supported_lora_modules = [ + "W_pack", + "o_proj", + "gate_up_proj", + "down_proj", + ] + embedding_modules = {} + embedding_padding_modules = [] + + def __init__( + self, + config: PretrainedConfig, + position_embedding: str, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ): + super().__init__() + + self.config = config + + self.quant_config = quant_config + self.model = BaiChuanModel( + config, position_embedding, quant_config, prefix=add_prefix("model", prefix) + ) + if self.config.tie_word_embeddings: + self.lm_head = self.model.embed_tokens + else: + self.lm_head = ParallelLMHead( + config.vocab_size, + config.hidden_size, + quant_config=quant_config, + prefix=add_prefix("lm_head", prefix), + ) + self.logits_processor = LogitsProcessor(config) + + def forward( + self, + input_ids: torch.Tensor, + positions: torch.Tensor, + forward_batch: ForwardBatch, + ) -> torch.Tensor: + hidden_states = self.model(input_ids, positions, forward_batch) + return self.logits_processor( + input_ids, hidden_states, self.lm_head, forward_batch + ) + + def load_weights(self, weights: Iterable[Tuple[str, torch.Tensor]]): + stacked_params_mapping = [ + # (param_name, shard_name, shard_id) + ("gate_up_proj", "gate_proj", 0), + ("gate_up_proj", "up_proj", 1), + ] + params_dict = dict(self.named_parameters()) + for name, loaded_weight in weights: + if "rotary_emb.inv_freq" in name: + continue + if name == "lm_head.weight": + # Unlike Baichuan, Baichuan2 normalizes the head weights. + # Refer to: + # https://huggingface.co/baichuan-inc/Baichuan2-7B-Chat/blob/84603cde5ebffb6084e476cfaeceaf0b8b91fe54/modeling_baichuan.py#L508 + # Distinguish between Baichuan and Baichuan2 by checking the + # vocab size. This is suggested by + # https://github.com/vllm-project/vllm/pull/1022#discussion_r1325652704 + is_baichuan2 = self.config.vocab_size == 125696 + if is_baichuan2: + loaded_weight = torch.nn.functional.normalize(loaded_weight) + + for param_name, weight_name, shard_id in stacked_params_mapping: + if weight_name not in name: + continue + name = name.replace(weight_name, param_name) + # Skip loading extra bias for GPTQ models. + if name.endswith(".bias") and name not in params_dict: + continue + param = params_dict[name] + weight_loader = param.weight_loader + weight_loader(param, loaded_weight, shard_id) + break + else: + # Skip loading extra bias for GPTQ models. + if name.endswith(".bias") and name not in params_dict: + continue + param = params_dict[name] + weight_loader = getattr(param, "weight_loader", default_weight_loader) + weight_loader(param, loaded_weight) + + +class BaichuanForCausalLM(BaiChuanBaseForCausalLM): + """Baichuan 13B and Baichuan2 7B/13B.""" + + def __init__( + self, + config, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ): + if config.hidden_size == 4096: # baichuan2 7b + super().__init__(config, "ROPE", quant_config, prefix=prefix) + else: # baichuan 13b, baichuan2 13b + super().__init__(config, "ALIBI", quant_config, prefix=prefix) + + +EntryClass = [BaichuanForCausalLM] diff --git a/lib/python3.12/site-packages/sglang/srt/models/deepseek_vl2.py b/lib/python3.12/site-packages/sglang/srt/models/deepseek_vl2.py new file mode 100644 index 0000000000000000000000000000000000000000..c0af78aca24b817a98148b831dab37eb63865cab --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/models/deepseek_vl2.py @@ -0,0 +1,364 @@ +from typing import Iterable, List, Optional, Tuple + +import torch +import torch.nn.functional as F +from einops import rearrange, repeat +from torch import nn + +from sglang.srt.configs.deepseekvl2 import ( + DeepseekVL2Config, + DeepseekVL2MlpProjectorConfig, +) +from sglang.srt.layers.linear import ReplicatedLinear +from sglang.srt.layers.quantization.base_config import QuantizationConfig +from sglang.srt.managers.mm_utils import ( + MultiModalityDataPaddingPatternMultimodalTokens, + general_mm_embed_routine, +) +from sglang.srt.managers.schedule_batch import MultimodalDataItem, MultimodalInputs +from sglang.srt.model_executor.forward_batch_info import ForwardBatch +from sglang.srt.model_loader.weight_utils import default_weight_loader +from sglang.srt.models.deepseek import DeepseekForCausalLM +from sglang.srt.models.deepseek_v2 import DeepseekV2ForCausalLM + + +class DeepseekVL2MlpProjector(nn.Module): + def __init__( + self, + config: DeepseekVL2MlpProjectorConfig, + quant_config: Optional[QuantizationConfig] = None, + ): + + super().__init__() + + self.config = config + + if config.projector_type == "identity": + modules = nn.Identity() + + elif config.projector_type == "linear": + self.layers = nn.ModuleList( + [ + ReplicatedLinear( + config.input_dim, + config.n_embed, + quant_config=quant_config, + ) + ] + ) + + elif config.projector_type == "mlp_gelu": + mlp_depth = config.depth + self.layers = nn.ModuleList( + [ + ReplicatedLinear( + config.input_dim, + config.n_embed, + quant_config=quant_config, + ) + ] + ) + for _ in range(1, mlp_depth): + self.layers.append(nn.GELU()) + self.layers.append( + ReplicatedLinear( + config.n_embed, + config.n_embed, + quant_config=quant_config, + ) + ) + + elif config.projector_type == "downsample_mlp_gelu": + mlp_depth = config.depth + mlp_ratio = config.mlp_ratio + self.layers = nn.ModuleList( + [ + ReplicatedLinear( + config.input_dim + * config.downsample_ratio + * config.downsample_ratio, + config.n_embed * mlp_ratio, + quant_config=quant_config, + ) + ] + ) + for _ in range(1, mlp_depth - 1): + self.layers.append(nn.GELU()) + self.layers.append( + ReplicatedLinear( + config.n_embed * mlp_ratio, + config.n_embed * mlp_ratio, + quant_config=quant_config, + ) + ) + self.layers.append(nn.GELU()) + self.layers.append( + ReplicatedLinear( + config.n_embed * mlp_ratio, + config.n_embed, + quant_config=quant_config, + ) + ) + + else: + raise ValueError(f"Unknown projector type: {config.projector_type}") + + if config.token_pooling: + self.token_pooling_layer = ReplicatedLinear( + config.input_dim * 4, config.input_dim, quant_config=quant_config + ) + + def forward(self, x): + if self.config.token_pooling: + batch_size, wxh, channels = x.shape + w = h = int(wxh**0.5) + x = x.view(batch_size, w, h, channels) + x = x.permute(0, 3, 1, 2) + + patches = x.unfold(2, 2, 2).unfold(3, 2, 2) + batch_size, channels, h_patches, w_patches, _, _ = patches.size() + patches = patches.contiguous().view( + batch_size, channels, h_patches * w_patches, -1 + ) + patches = patches.permute(0, 2, 1, 3).contiguous() + patches = patches.view(batch_size, h_patches * w_patches, channels * 4) + + x = self.token_pooling_layer(patches)[0] + + elif self.config.projector_type == "downsample_mlp_gelu": + bs, hw, input_dim = x.shape + h = w = int((hw) ** 0.5) + + """compute padding""" + if h % self.config.downsample_ratio: + pad = self.config.downsample_ratio - h % self.config.downsample_ratio + else: + pad = 0 + x = x.reshape(bs, h, w, input_dim) + if pad > 0: + x = F.pad(x, (0, 0, 0, pad, 0, pad), "constant", 0) + + """4 to 1 concat""" + x = x.permute(0, 3, 1, 2) # B, C, H, W + x = F.unfold( + x, + kernel_size=self.config.downsample_ratio, + stride=self.config.downsample_ratio, + padding=0, + ) # B, C*4, HW // 4 + x = x.permute(0, 2, 1) + + for layer in self.layers: + x = layer(x) + if isinstance(x, tuple): + x = x[0] + return x + + +class DeepseekVL2ForCausalLM(nn.Module): + + def __init__( + self, + config: DeepseekVL2Config, + quant_config: Optional[QuantizationConfig] = None, + ): + super().__init__() + + # ----------- vision encoder ------------ + vision_config = config.vision_config + self.vision = self._init_vision_module(vision_config, quant_config) + + # ----------- vl projector ------------ + projector_config = config.projector_config + self.projector = DeepseekVL2MlpProjector(projector_config, quant_config) + + self.tile_tag = config.tile_tag + self.global_view_pos = config.global_view_pos + + embed_std = 1 / torch.sqrt( + torch.tensor(projector_config.n_embed, dtype=torch.float32) + ) + if self.tile_tag == "2D": + self.image_newline = nn.Parameter( + torch.randn(projector_config.n_embed) * embed_std + ) + self.view_seperator = nn.Parameter( + torch.randn(projector_config.n_embed) * embed_std + ) + else: + raise ValueError(f"tile tag should be 2D, but got {self.tile_tag}") + + # ----------- language model ------------ + language_config = config.language_config + if language_config.use_mla: + self.language_model = DeepseekV2ForCausalLM(language_config) + else: + # deepseek-vl2-tiny forbids mla + self.language_model = DeepseekForCausalLM(language_config) + + def _init_vision_module( + self, vision_config, quant_config: Optional[QuantizationConfig] + ) -> nn.Module: + # TODO: refactor vision model through timm wrapper from transformers + try: + import timm + except ImportError: + raise ImportError("Please install timm") from ImportError + + model = timm.create_model( + "vit_so400m_patch14_siglip_384.webli", + pretrained=False, + num_classes=0, + dynamic_img_size=True, + dynamic_img_pad=True, + ) + + model = model.to(dtype=torch.get_default_dtype()) + return model + + def forward( + self, + input_ids: torch.Tensor, + positions: torch.Tensor, + forward_batch: ForwardBatch, + **kwargs: object, + ): + hs = general_mm_embed_routine( + input_ids=input_ids, + positions=positions, + forward_batch=forward_batch, + image_data_embedding_func=self.get_image_feature, + language_model=self.language_model, + ) + + return hs + + def load_weights(self, weights: Iterable[Tuple[str, torch.Tensor]]): + stacked_params_mapping = [ + # (param_name, shard_name, shard_id) + ("qkv_proj", "q_proj", "q"), + ("qkv_proj", "k_proj", "k"), + ("qkv_proj", "v_proj", "v"), + ("gate_up_proj", "up_proj", 1), + ("gate_up_proj", "gate_proj", 0), + ] + params_dict = dict(self.named_parameters()) + weights = list(weights) + for name, loaded_weight in weights: + if "language" in name: + name = name.replace("language.", "") + self.language_model.load_weights([(name, loaded_weight)]) + else: + param = params_dict[name] + weights_loader = getattr(param, "weight_loader", default_weight_loader) + weights_loader(param, loaded_weight) + + def pad_input_ids(self, input_ids: List[int], image_inputs: MultimodalInputs): + helper = MultiModalityDataPaddingPatternMultimodalTokens( + [image_inputs.im_token_id] + ) + return helper.pad_input_tokens(input_ids, image_inputs) + + def get_image_feature(self, items: List[MultimodalDataItem]): + + images_spatial_crop = torch.cat( + [item.image_spatial_crop for item in items], dim=0 + ) + + assert images_spatial_crop.dim() == 3 + + # TODO: can it be batched ? + images_in_this_batch = [] + for item in items: + assert item.pixel_values.dim() == 4 + image_feature = self.vision.forward_features( + item.pixel_values.type(next(self.vision.parameters()).dtype).to( + device=next(self.vision.parameters()).device + ) + ) + images_embeds = self.projector(image_feature) + _, hw, n_dim = images_embeds.shape + h = w = int(hw**0.5) + tile_index = 0 + for jdx in range(item.image_spatial_crop.shape[1]): + num_width_tiles, num_height_tiles = item.image_spatial_crop[0, jdx] + if num_width_tiles == 0 or num_height_tiles == 0: + break + num_tiles_in_image = num_width_tiles * num_height_tiles + + # [hw, D] + global_features = images_embeds[tile_index] + + # [num_height_tiles * num_width_tiles, hw, D] + local_features = images_embeds[ + tile_index + 1 : tile_index + 1 + num_tiles_in_image + ] + tile_index += num_tiles_in_image + 1 + + # format global and local features + # ----------------- global view add newline ----------------- + # [hw, D] -> [h, w, D] + global_features = global_features.view(h, w, n_dim) + + # [D] -> [h, 1, D] + new_lines_in_global = repeat(self.image_newline, "d -> h 1 d", h=h) + + # cat([h, w, D], [h, 1, D], dim=1) -> [h, w + 1, D] + global_features = torch.cat( + [global_features, new_lines_in_global], dim=1 + ) + + # [h, w + 1, D] -> [h * (w + 1), D] + global_features = global_features.view(-1, n_dim) + + # ----------------- local view add newline ----------------- + # [num_height_tiles * num_width_tiles, h * w, D] -> + # [num_height_tiles * h, num_width_tiles * w, D] + local_features = rearrange( + local_features, + "(th tw) (h w) d -> (th h) (tw w) d", + th=num_height_tiles, + tw=num_width_tiles, + h=h, + w=w, + ) + + # [D] -> [num_height_tiles * h, 1, D] + new_lines_in_local = repeat( + self.image_newline, + "d -> (th h) 1 d", + th=num_height_tiles, + h=h, + ) + + # [num_height_tiles * h, num_width_tiles * w + 1, D] + local_features = torch.cat([local_features, new_lines_in_local], dim=1) + + # [num_height_tiles * h, num_width_tiles * w + 1, D] + # --> [(num_height_tiles * h) * (num_width_tiles * w + 1), D] + local_features = local_features.view(-1, n_dim) + + # merge global and local tiles + if self.global_view_pos == "head": + global_local_features = torch.cat( + [ + global_features, + self.view_seperator[None, :], + local_features, + ] + ) + else: + global_local_features = torch.cat( + [ + local_features, + self.view_seperator[None, :], + global_features, + ] + ) + + images_in_this_batch.append(global_local_features) + + return torch.cat(images_in_this_batch, dim=0) + + +EntryClass = DeepseekVL2ForCausalLM diff --git a/lib/python3.12/site-packages/sglang/srt/models/grok.py b/lib/python3.12/site-packages/sglang/srt/models/grok.py new file mode 100644 index 0000000000000000000000000000000000000000..a8cde8e09c0257473cd9f35dc738411e5ad8911a --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/models/grok.py @@ -0,0 +1,799 @@ +# Copyright 2023-2024 SGLang Team +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +# Adapted from +# https://github.com/vllm-project/vllm/blob/c7f2cf2b7f67bce5842fedfdba508440fe257375/vllm/model_executor/models/mixtral.py#L1 +"""Inference-only Grok1 model.""" +import functools +import json +import logging +import math +import os +import warnings +from typing import Iterable, Optional, Tuple + +import numpy as np +import torch +from torch import nn +from transformers import PretrainedConfig + +from sglang.srt.distributed import ( + get_tensor_model_parallel_rank, + get_tensor_model_parallel_world_size, + tensor_model_parallel_all_gather, + tensor_model_parallel_all_reduce, +) +from sglang.srt.layers.elementwise import fused_dual_residual_rmsnorm, fused_rmsnorm +from sglang.srt.layers.layernorm import RMSNorm +from sglang.srt.layers.linear import ( + QKVParallelLinear, + ReplicatedLinear, + RowParallelLinear, +) +from sglang.srt.layers.logits_processor import LogitsProcessor +from sglang.srt.layers.moe.ep_moe.layer import EPMoE +from sglang.srt.layers.moe.fused_moe_triton import FusedMoE +from sglang.srt.layers.moe.router import fused_moe_router_shim +from sglang.srt.layers.quantization.base_config import QuantizationConfig +from sglang.srt.layers.radix_attention import RadixAttention +from sglang.srt.layers.rotary_embedding import get_rope +from sglang.srt.layers.vocab_parallel_embedding import ( + ParallelLMHead, + VocabParallelEmbedding, +) +from sglang.srt.managers.schedule_batch import global_server_args_dict +from sglang.srt.model_executor.forward_batch_info import ForwardBatch +from sglang.srt.model_loader.loader import DefaultModelLoader +from sglang.srt.model_loader.weight_utils import default_weight_loader +from sglang.srt.utils import dump_to_file + +logger = logging.getLogger(__name__) + + +debug_tensor_dump_output_folder = None +debug_tensor_dump_inject = False + + +class Grok1MoE(nn.Module): + """A tensor-parallel MoE implementation for Grok1 that shards each expert + across all ranks. + + Each expert's weights are sharded across all ranks and a fused MoE + kernel is used for the forward pass, and finally we reduce the outputs + across ranks. + """ + + def __init__( + self, + config: PretrainedConfig, + num_experts: int, + top_k: int, + hidden_size: int, + intermediate_size: int, + params_dtype: Optional[torch.dtype] = None, + quant_config: Optional[QuantizationConfig] = None, + tp_size: Optional[int] = None, + reduce_results=True, + use_presharded_weights: bool = False, + inplace: bool = True, + no_combine: bool = False, + ): + super().__init__() + self.hidden_size = hidden_size + + # Gate always runs at full precision for stability (see https://arxiv.org/pdf/2101.03961) + self.gate = ReplicatedLinear( + hidden_size, + num_experts, + bias=False, + params_dtype=torch.float32, + quant_config=None, + ) + + self.router_logit_softcapping = getattr( + config, "router_logit_softcapping", 30.0 + ) + custom_routing_function = functools.partial( + fused_moe_router_shim, self.router_logit_softcapping + ) + + kwargs = {} + if global_server_args_dict["enable_ep_moe"]: + MoEImpl = EPMoE + else: + MoEImpl = FusedMoE + kwargs["reduce_results"] = reduce_results + kwargs["use_presharded_weights"] = use_presharded_weights + kwargs["inplace"] = inplace + kwargs["no_combine"] = no_combine + + self.experts = MoEImpl( + num_experts=num_experts, + top_k=top_k, + hidden_size=hidden_size, + intermediate_size=intermediate_size, + params_dtype=params_dtype, + renormalize=False, + quant_config=quant_config, + tp_size=tp_size, + custom_routing_function=custom_routing_function, + activation="gelu", + **kwargs, + ) + + def forward(self, hidden_states: torch.Tensor) -> torch.Tensor: + # need to assert self.gate.quant_method is unquantized + return self.experts(hidden_states, self.gate.weight) + + +class Grok1Attention(nn.Module): + def __init__( + self, + config: PretrainedConfig, + hidden_size: int, + num_heads: int, + num_kv_heads: int, + layer_id: int = 0, + max_position: int = 4096 * 32, + rope_theta: float = 10000, + quant_config: Optional[QuantizationConfig] = None, + reduce_results: bool = True, + load_presharded_attn: bool = False, + ) -> None: + super().__init__() + self.config = config + self.layer_id = layer_id + self.hidden_size = hidden_size + attn_tp_rank = get_tensor_model_parallel_rank() + attn_tp_size = get_tensor_model_parallel_world_size() + self.total_num_heads = num_heads + assert self.total_num_heads % attn_tp_size == 0 + self.num_heads = self.total_num_heads // attn_tp_size + self.total_num_kv_heads = num_kv_heads + if self.total_num_kv_heads >= attn_tp_size: + # Number of KV heads is greater than TP size, so we partition + # the KV heads across multiple tensor parallel GPUs. + assert self.total_num_kv_heads % attn_tp_size == 0 + else: + # Number of KV heads is less than TP size, so we replicate + # the KV heads across multiple tensor parallel GPUs. + assert attn_tp_size % self.total_num_kv_heads == 0 + self.num_kv_heads = max(1, self.total_num_kv_heads // attn_tp_size) + self.head_dim = getattr(config, "head_dim", 128) + self.q_size = self.num_heads * self.head_dim + self.kv_size = self.num_kv_heads * self.head_dim + self.scaling = self.head_dim**-0.5 + self.rope_theta = rope_theta + self.load_presharded_attn = load_presharded_attn + + self.qkv_proj = QKVParallelLinear( + hidden_size, + self.head_dim, + self.total_num_heads, + self.total_num_kv_heads, + bias=False, + quant_config=quant_config, + tp_rank=attn_tp_rank, + tp_size=attn_tp_size, + load_presharded_attn=self.load_presharded_attn, + ) + self.o_proj = RowParallelLinear( + self.total_num_heads * self.head_dim, + hidden_size, + bias=False, + quant_config=quant_config, + reduce_results=reduce_results, + tp_rank=attn_tp_rank, + tp_size=attn_tp_size, + use_presharded_weights=self.load_presharded_attn, + ) + self.rotary_emb = get_rope( + self.head_dim, + rotary_dim=self.head_dim, + max_position=max_position, + base=int(self.rope_theta), + is_neox_style=True, + ) + + logit_cap = max(getattr(config, "attn_logit_softcapping", 30.0), 0.0) + + self.attn = RadixAttention( + self.num_heads, + self.head_dim, + self.scaling, + num_kv_heads=self.num_kv_heads, + layer_id=layer_id, + logit_cap=logit_cap, + quant_config=quant_config, + ) + + def forward( + self, + positions: torch.Tensor, + hidden_states: torch.Tensor, + forward_batch: ForwardBatch, + ) -> torch.Tensor: + if hidden_states.shape[0] == 0: + assert ( + not self.o_proj.reduce_results + ), "short-circuiting allreduce will lead to hangs" + return hidden_states + if debug_tensor_dump_output_folder: + dump_to_file( + debug_tensor_dump_output_folder, + f"attn_input_{self.layer_id}", + hidden_states, + ) + + if debug_tensor_dump_inject: + name = os.path.join( + debug_tensor_dump_output_folder, + f"jax_dump_attn_input_{self.layer_id}.npy", + ) + logger.info(f"Load {name} from jax.") + x = np.load(name) + hidden_states = torch.tensor(x[0, : hidden_states.shape[0]]).to( + hidden_states + ) + + qkv, _ = self.qkv_proj(hidden_states) + q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1) + q, k = self.rotary_emb(positions, q, k) + + if debug_tensor_dump_output_folder: + num_tokens = q.shape[0] + num_heads_q = self.num_heads + head_dim = self.head_dim + num_heads_kv = k.numel() // (num_tokens * head_dim) + + dump_to_file( + debug_tensor_dump_output_folder, + f"q_{self.layer_id}", + tensor_model_parallel_all_gather( + q.reshape(num_tokens, num_heads_q, head_dim).contiguous(), dim=1 + ).contiguous(), + ) + dump_to_file( + debug_tensor_dump_output_folder, + f"k_{self.layer_id}", + tensor_model_parallel_all_gather( + k.reshape(num_tokens, num_heads_kv, head_dim).contiguous(), dim=1 + ).contiguous(), + ) + dump_to_file( + debug_tensor_dump_output_folder, + f"v_{self.layer_id}", + tensor_model_parallel_all_gather( + v.reshape(num_tokens, num_heads_kv, head_dim).contiguous(), dim=1 + ).contiguous(), + ) + + attn_output = self.attn(q, k, v, forward_batch) + + if debug_tensor_dump_output_folder: + dump_to_file( + debug_tensor_dump_output_folder, + f"attn_output_{self.layer_id}", + tensor_model_parallel_all_gather( + attn_output.reshape(num_tokens, num_heads_q, head_dim).contiguous(), + dim=1, + ).contiguous(), + ) + + output, _ = self.o_proj(attn_output) + return output + + +class Grok1DecoderLayer(nn.Module): + def __init__( + self, + config: PretrainedConfig, + layer_id: int = 0, + quant_config: Optional[QuantizationConfig] = None, + load_presharded_moe: bool = False, + load_presharded_attn: bool = False, + load_presharded_mlp: bool = False, + ) -> None: + super().__init__() + self.num_experts = config.num_local_experts + self.hidden_size = config.hidden_size + self.layer_id = layer_id + + rope_theta = getattr(config, "rope_theta", 10000) + self.self_attn = Grok1Attention( + config=config, + hidden_size=self.hidden_size, + num_heads=config.num_attention_heads, + max_position=config.max_position_embeddings, + num_kv_heads=config.num_key_value_heads, + layer_id=layer_id, + rope_theta=rope_theta, + quant_config=quant_config, + reduce_results=False, + load_presharded_attn=load_presharded_attn, + ) + self.block_sparse_moe = Grok1MoE( + config=config, + num_experts=config.num_local_experts, + top_k=config.num_experts_per_tok, + hidden_size=config.hidden_size, + intermediate_size=getattr( + config, + "moe_intermediate_size", + getattr(config, "intermediate_size", None), + ), + quant_config=quant_config, + reduce_results=True, + use_presharded_weights=load_presharded_moe, + inplace=True, + no_combine=False, # just a suggestion to not combine topk + ) + + self.pre_attn_norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps) + self.post_attn_norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps) + self.pre_moe_norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps) + self.post_moe_norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps) + + self.ffn = self.block_sparse_moe + + def forward( + self, + positions: torch.Tensor, + hidden_states: torch.Tensor, + forward_batch: ForwardBatch, + residual: Optional[torch.Tensor] = None, + deferred_norm: Optional[RMSNorm] = None, + ) -> Tuple[torch.Tensor, torch.Tensor, RMSNorm]: + # Self Attention + if deferred_norm is not None: + assert residual is not None + # here hidden_states is output of ffn, residual is residual from after previous attn layer + hidden_states, residual = fused_dual_residual_rmsnorm( + hidden_states, + residual, + deferred_norm.weight, + self.pre_attn_norm.weight, + deferred_norm.variance_epsilon, + ) + else: + # here hidden_states is the residual + hidden_states, residual = ( + fused_rmsnorm( + hidden_states, + self.pre_attn_norm.weight, + self.pre_attn_norm.variance_epsilon, + ), + hidden_states, + ) + + hidden_states = self.self_attn( + positions=positions, + hidden_states=hidden_states, + forward_batch=forward_batch, + ) + + if get_tensor_model_parallel_world_size() > 1: + hidden_states = tensor_model_parallel_all_reduce(hidden_states) + + hidden_states, residual = fused_dual_residual_rmsnorm( + hidden_states, + residual, + self.post_attn_norm.weight, + self.pre_moe_norm.weight, + self.post_attn_norm.variance_epsilon, + ) + + # Fully Connected + hidden_states = self.ffn(hidden_states) + return hidden_states, residual, self.post_moe_norm # defer layernorm + + +class Grok1Model(nn.Module): + def __init__( + self, + config: PretrainedConfig, + quant_config: Optional[QuantizationConfig] = None, + load_presharded_moe: bool = False, + load_presharded_embedding: bool = False, + load_presharded_attn: bool = False, + load_presharded_mlp: bool = False, + ) -> None: + super().__init__() + self.config = config + self.padding_idx = config.pad_token_id + self.vocab_size = config.vocab_size + + self.embed_tokens = VocabParallelEmbedding( + config.vocab_size, + config.hidden_size, + use_presharded_weights=load_presharded_embedding, + ) + self.layers = nn.ModuleList( + [ + Grok1DecoderLayer( + config, + i, + quant_config=quant_config, + load_presharded_moe=load_presharded_moe, + load_presharded_attn=load_presharded_attn, + load_presharded_mlp=load_presharded_mlp, + ) + for i in range(config.num_hidden_layers) + ] + ) + self.norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps) + + def forward( + self, + input_ids: torch.Tensor, + positions: torch.Tensor, + forward_batch: ForwardBatch, + input_embeds: torch.Tensor = None, + ) -> torch.Tensor: + if input_embeds is None: + hidden_states = self.embed_tokens(input_ids) + hidden_states.mul_(self.config.embedding_multiplier_scale) + else: + hidden_states = input_embeds + + residual, deferred_norm = None, None + for i in range(len(self.layers)): + hidden_states, residual, deferred_norm = self.layers[i]( + positions, hidden_states, forward_batch, residual, deferred_norm + ) + + if debug_tensor_dump_output_folder: + hidden_states = ( + fused_rmsnorm( + hidden_states, + deferred_norm.weight, + deferred_norm.variance_epsilon, + ) + + residual + ) + + dump_to_file( + debug_tensor_dump_output_folder, + "last_hidden_before_norm", + hidden_states, + ) + + hidden_states = fused_rmsnorm( + hidden_states, + self.norm.weight, + self.norm.variance_epsilon, + ) + + dump_to_file( + debug_tensor_dump_output_folder, + "last_hidden_after_norm", + hidden_states, + ) + else: + hidden_states, _ = fused_dual_residual_rmsnorm( + hidden_states, + residual, + deferred_norm.weight, + self.norm.weight, + deferred_norm.variance_epsilon, + ) + + return hidden_states + + +class Grok1ForCausalLM(nn.Module): + def __init__( + self, + config: PretrainedConfig, + quant_config: Optional[QuantizationConfig] = None, + ) -> None: + super().__init__() + self.config = config + self.quant_config = quant_config + + # Get presharded weights. + self.load_presharded_mlp = getattr(config, "load_presharded_mlp", False) + self.load_presharded_moe = ( + self.config.num_local_experts > 0 + and get_tensor_model_parallel_world_size() > 1 + ) + self.load_presharded_attn = getattr(config, "load_presharded_attn", False) + self.load_presharded_embedding = getattr( + config, "load_presharded_embedding", False + ) + + self.is_weights_presharded = ( + self.load_presharded_mlp + or self.load_presharded_moe + or self.load_presharded_attn + or self.load_presharded_embedding + ) + + if self.is_weights_presharded: + setattr(DefaultModelLoader, "_prepare_weights", _prepare_presharded_weights) + + default_replicate_lm_head = False + self.replicate_lm_head = getattr( + config, "replicate_lm_head", default_replicate_lm_head + ) + + self.model = Grok1Model( + config, + quant_config=quant_config, + load_presharded_moe=self.load_presharded_moe, + load_presharded_embedding=self.load_presharded_embedding, + load_presharded_attn=self.load_presharded_attn, + load_presharded_mlp=self.load_presharded_mlp, + ) + + lm_head_params_dtype = None + if self.replicate_lm_head: + self.lm_head = ReplicatedLinear( + config.hidden_size, + config.vocab_size, + bias=False, + params_dtype=lm_head_params_dtype, + ) + self.logits_processor = LogitsProcessor(config, skip_all_gather=True) + else: + self.lm_head = ParallelLMHead( + config.vocab_size, + config.hidden_size, + use_presharded_weights=self.load_presharded_embedding, + params_dtype=lm_head_params_dtype, + ) + self.logits_processor = LogitsProcessor(config) + + # Dump tensors for debugging + global debug_tensor_dump_output_folder, debug_tensor_dump_inject + debug_tensor_dump_output_folder = global_server_args_dict[ + "debug_tensor_dump_output_folder" + ] + debug_tensor_dump_inject = global_server_args_dict["debug_tensor_dump_inject"] + warnings.filterwarnings("ignore", category=FutureWarning) + + if get_tensor_model_parallel_rank() == 0: + logger.info( + f"#parameters (analytical): {self.get_num_params_analytical() / 1e9:.2f} B, " + f"#parameters (actual): {self.get_num_params_torch() / 1e9:.2f} B" + ) + + def forward( + self, + input_ids: torch.Tensor, + positions: torch.Tensor, + forward_batch: ForwardBatch, + input_embeds: torch.Tensor = None, + ) -> torch.Tensor: + if debug_tensor_dump_output_folder: + dump_to_file(debug_tensor_dump_output_folder, "input_ids", input_ids) + + hidden_states = self.model(input_ids, positions, forward_batch, input_embeds) + return self.logits_processor( + input_ids, hidden_states, self.lm_head, forward_batch + ) + + def load_weights( + self, + weights: Iterable[Tuple[str, torch.Tensor]], + num_experts: Optional[int] = None, + ignore_parent_name: bool = False, + ) -> dict[str, torch.Tensor]: + if num_experts is None: + num_experts = self.config.num_local_experts + stacked_params_mapping = [] + stacked_params_mapping += [ + # (param_name, shard_name, shard_id) + ("qkv_proj", "q_proj", "q"), + ("qkv_proj", "k_proj", "k"), + ("qkv_proj", "v_proj", "v"), + ] + stacked_params_mapping += [ + # (param_name, shard_name, shard_id) + ("gate_up_proj", "gate_proj", 0), + ("gate_up_proj", "up_proj", 1), + ] + + # Params for weights, fp8 weight scales, fp8 activation scales + # (param_name, weight_name, expert_id, shard_id) + MoEImpl = EPMoE if global_server_args_dict["enable_ep_moe"] else FusedMoE + expert_params_mapping = MoEImpl.make_expert_params_mapping( + ckpt_gate_proj_name="w1", + ckpt_down_proj_name="w2", + ckpt_up_proj_name="w3", + num_experts=num_experts, + ) + + params_dict = dict(self.named_parameters()) + all_names = set(params_dict.keys()) + hit_names = set() + + def load_weight_wrapper( + name: str, loaded_weight: torch.Tensor, *args, **kwargs + ): + if ignore_parent_name: + name = name.split(".")[-1] + + if name not in params_dict: + return + + # Fuse constant multipliers into the weights + if "lm_head" in name: + loaded_weight = ( + loaded_weight.to(torch.float32) + * self.config.output_multiplier_scale + ) + + param = params_dict[name] + weight_loader = getattr(param, "weight_loader", default_weight_loader) + weight_loader(param, loaded_weight, *args, **kwargs) + hit_names.add(name) + + for name, loaded_weight in weights: + if "rotary_emb.inv_freq" in name: + continue + + for param_name, weight_name, shard_id in stacked_params_mapping: + if weight_name not in name: + continue + name = name.replace(weight_name, param_name) + # Skip loading extra bias for GPTQ models. + if name.endswith(".bias") and name not in params_dict: + continue + load_weight_wrapper(name, loaded_weight, shard_id) + break + else: + for mapping in expert_params_mapping: + param_name, weight_name, expert_id, shard_id = mapping + if weight_name not in name: + continue + name = name.replace(weight_name, param_name) + + load_weight_wrapper( + name, + loaded_weight, + name, + shard_id=shard_id, + expert_id=expert_id, + ) + break + else: + # Skip loading extra bias for GPTQ models. + if name.endswith(".bias") and name not in params_dict: + continue + if name is None: + continue + + load_weight_wrapper(name=name, loaded_weight=loaded_weight) + + if len(hit_names) > 5: + missing = all_names - hit_names + missing_exclude_scales = {x for x in missing if "scale" not in x} + logger.info( + f"#all_names: {len(all_names)}, #hit_names: {len(hit_names)}, #missing_exclude_scales: {len(missing_exclude_scales)}", + ) + if len(missing_exclude_scales) > 0: + raise ValueError( + f"load_weights failed because some weights are missing: {missing_exclude_scales=}." + ) + + elif len(hit_names) == 0: + raise ValueError("load_weights failed because it did not hit any names.") + + return hit_names + + def get_num_params_analytical(self): + cfg = self.config + moe_intermediate_size = getattr( + cfg, + "moe_intermediate_size", + getattr(cfg, "intermediate_size", None), + ) + num_experts = cfg.num_local_experts + + wq = ( + cfg.num_hidden_layers + * cfg.hidden_size + * cfg.num_attention_heads + * cfg.head_dim + ) + wkv = ( + cfg.num_hidden_layers + * cfg.hidden_size + * cfg.num_key_value_heads + * cfg.head_dim + * 2 + ) + out = ( + cfg.num_hidden_layers + * cfg.hidden_size + * cfg.num_attention_heads + * cfg.head_dim + ) + ffn1 = ( + cfg.num_hidden_layers + * num_experts + * cfg.hidden_size + * moe_intermediate_size + * 2 + ) + ffn2 = ( + cfg.num_hidden_layers + * num_experts + * cfg.hidden_size + * moe_intermediate_size + ) + embed = cfg.hidden_size * cfg.vocab_size * 2 + return wq + wkv + out + ffn1 + ffn2 + embed + + def get_num_params_torch(self): + return ( + sum(p.numel() for p in self.parameters()) + * get_tensor_model_parallel_world_size() + ) + + +old_prepare_weights = getattr(DefaultModelLoader, "_prepare_weights") + + +def _prepare_presharded_weights( + self, model_name_or_path: str, revision: Optional[str], fall_back_to_pt: bool +) -> Tuple[str, list[str], bool]: + import glob + import os + + if get_tensor_model_parallel_world_size() == 1: + return old_prepare_weights(self, model_name_or_path, revision, fall_back_to_pt) + + if not os.path.isdir(model_name_or_path): + from sglang.srt.model_loader.weight_utils import download_weights_from_hf + + allow_patterns = ["*.safetensors", "*.bin"] + hf_folder = download_weights_from_hf( + model_name_or_path, + self.load_config.download_dir, + allow_patterns, + revision, + ignore_patterns=self.load_config.ignore_patterns, + ) + else: + hf_folder = model_name_or_path + + tp_rank = get_tensor_model_parallel_rank() + + # The old format + allow_patterns = [f"*-{tp_rank:03d}.bin"] + + # The new format + allow_patterns += [f"*-TP-{tp_rank:03d}.safetensors", "*-TP-common.safetensors"] + + hf_weights_files = [] + for pattern in allow_patterns: + hf_weights_files += glob.glob(os.path.join(hf_folder, pattern)) + + if hf_weights_files[0].endswith("safetensors"): + use_safetensors = True + else: + use_safetensors = False + + return hf_folder, hf_weights_files, use_safetensors + + +class Grok1ModelForCausalLM(Grok1ForCausalLM): + """An alias for backward-compatbility.""" + + pass + + +EntryClass = [Grok1ForCausalLM, Grok1ModelForCausalLM] diff --git a/lib/python3.12/site-packages/sglang/srt/models/llama.py b/lib/python3.12/site-packages/sglang/srt/models/llama.py new file mode 100644 index 0000000000000000000000000000000000000000..008e542048a37eb9c0e3dee7e43b22d7042a31d9 --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/models/llama.py @@ -0,0 +1,653 @@ +# Copyright 2023-2024 SGLang Team +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +# Adapted from +# https://github.com/vllm-project/vllm/blob/c7f2cf2b7f67bce5842fedfdba508440fe257375/vllm/model_executor/models/llama.py#L1 +"""Inference-only LLaMA model compatible with HuggingFace weights.""" + +import logging +from typing import Any, Dict, Iterable, List, Optional, Tuple, Union + +import torch +from torch import nn +from transformers import LlamaConfig + +from sglang.srt.distributed import ( + get_tensor_model_parallel_rank, + get_tensor_model_parallel_world_size, +) +from sglang.srt.layers.activation import SiluAndMul +from sglang.srt.layers.layernorm import RMSNorm +from sglang.srt.layers.linear import ( + MergedColumnParallelLinear, + QKVParallelLinear, + RowParallelLinear, +) +from sglang.srt.layers.logits_processor import LogitsProcessor, LogitsProcessorOutput +from sglang.srt.layers.pooler import Pooler, PoolingType +from sglang.srt.layers.quantization.base_config import QuantizationConfig +from sglang.srt.layers.radix_attention import RadixAttention +from sglang.srt.layers.rotary_embedding import get_rope +from sglang.srt.layers.vocab_parallel_embedding import ( + ParallelLMHead, + VocabParallelEmbedding, +) +from sglang.srt.model_executor.forward_batch_info import ForwardBatch +from sglang.srt.model_loader.weight_utils import ( + default_weight_loader, + kv_cache_scales_loader, + maybe_remap_kv_scale_name, +) +from sglang.srt.utils import add_prefix, make_layers +from sglang.utils import get_exception_traceback + +logger = logging.getLogger(__name__) + + +class LlamaMLP(nn.Module): + def __init__( + self, + hidden_size: int, + intermediate_size: int, + hidden_act: str, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + reduce_results: bool = True, + ) -> None: + super().__init__() + self.gate_up_proj = MergedColumnParallelLinear( + hidden_size, + [intermediate_size] * 2, + bias=False, + quant_config=quant_config, + prefix=add_prefix("gate_up_proj", prefix), + ) + self.down_proj = RowParallelLinear( + intermediate_size, + hidden_size, + bias=False, + quant_config=quant_config, + prefix=add_prefix("down_proj", prefix), + reduce_results=reduce_results, + ) + if hidden_act != "silu": + raise ValueError( + f"Unsupported activation: {hidden_act}. " + "Only silu is supported for now." + ) + self.act_fn = SiluAndMul() + + def forward(self, x): + gate_up, _ = self.gate_up_proj(x) + x = self.act_fn(gate_up) + x, _ = self.down_proj(x) + return x + + +class LlamaAttention(nn.Module): + def __init__( + self, + config: LlamaConfig, + hidden_size: int, + num_heads: int, + num_kv_heads: int, + layer_id: int = 0, + rope_theta: float = 10000, + rope_scaling: Optional[Dict[str, Any]] = None, + rope_is_neox_style: bool = True, + max_position_embeddings: int = 8192, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + bias: bool = False, + ) -> None: + super().__init__() + self.hidden_size = hidden_size + tp_size = get_tensor_model_parallel_world_size() + self.total_num_heads = num_heads + assert self.total_num_heads % tp_size == 0 + self.num_heads = self.total_num_heads // tp_size + self.total_num_kv_heads = num_kv_heads + if self.total_num_kv_heads >= tp_size: + # Number of KV heads is greater than TP size, so we partition + # the KV heads across multiple tensor parallel GPUs. + assert self.total_num_kv_heads % tp_size == 0 + else: + # Number of KV heads is less than TP size, so we replicate + # the KV heads across multiple tensor parallel GPUs. + assert tp_size % self.total_num_kv_heads == 0 + self.num_kv_heads = max(1, self.total_num_kv_heads // tp_size) + # MistralConfig has an optional head_dim introduced by Mistral-Nemo + self.head_dim = getattr( + config, "head_dim", self.hidden_size // self.total_num_heads + ) + partial_rotary_factor = getattr(config, "partial_rotary_factor", 1) + self.rotary_dim = int(partial_rotary_factor * self.head_dim) + self.q_size = self.num_heads * self.head_dim + self.kv_size = self.num_kv_heads * self.head_dim + self.scaling = self.head_dim**-0.5 + self.rope_theta = rope_theta + self.max_position_embeddings = max_position_embeddings + + self.qkv_proj = QKVParallelLinear( + hidden_size, + self.head_dim, + self.total_num_heads, + self.total_num_kv_heads, + bias=bias, + quant_config=quant_config, + prefix=add_prefix("qkv_proj", prefix), + ) + self.o_proj = RowParallelLinear( + self.total_num_heads * self.head_dim, + hidden_size, + bias=bias, + quant_config=quant_config, + prefix=add_prefix("o_proj", prefix), + ) + + self.rotary_emb = get_rope( + self.head_dim, + rotary_dim=self.rotary_dim, + max_position=max_position_embeddings, + base=rope_theta, + rope_scaling=rope_scaling, + is_neox_style=rope_is_neox_style, + ) + self.attn = RadixAttention( + self.num_heads, + self.head_dim, + self.scaling, + num_kv_heads=self.num_kv_heads, + layer_id=layer_id, + quant_config=quant_config, + prefix=add_prefix("attn", prefix), + ) + + def forward( + self, + positions: torch.Tensor, + hidden_states: torch.Tensor, + forward_batch: ForwardBatch, + ) -> torch.Tensor: + qkv, _ = self.qkv_proj(hidden_states) + q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1) + q, k = self.rotary_emb(positions, q, k) + attn_output = self.attn(q, k, v, forward_batch) + output, _ = self.o_proj(attn_output) + return output + + +class LlamaDecoderLayer(nn.Module): + def __init__( + self, + config: LlamaConfig, + layer_id: int = 0, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ) -> None: + super().__init__() + self.hidden_size = config.hidden_size + rope_theta = getattr(config, "rope_theta", 10000) + rope_scaling = getattr(config, "rope_scaling", None) + if rope_scaling is not None and getattr( + config, "original_max_position_embeddings", None + ): + rope_scaling["original_max_position_embeddings"] = ( + config.original_max_position_embeddings + ) + rope_is_neox_style = getattr(config, "rope_is_neox_style", True) + max_position_embeddings = getattr(config, "max_position_embeddings", 8192) + # Support llamafy/Qwen-Qwen2.5-7B-Instruct-llamafied with attention_bias + # Support internlm/internlm-7b with bias + attention_bias = getattr(config, "attention_bias", False) or getattr( + config, "bias", False + ) + self.self_attn = LlamaAttention( + config=config, + hidden_size=self.hidden_size, + num_heads=config.num_attention_heads, + num_kv_heads=config.num_key_value_heads, + layer_id=layer_id, + rope_theta=rope_theta, + rope_scaling=rope_scaling, + rope_is_neox_style=rope_is_neox_style, + max_position_embeddings=max_position_embeddings, + quant_config=quant_config, + prefix=add_prefix("self_attn", prefix), + bias=attention_bias, + ) + self.mlp = LlamaMLP( + hidden_size=self.hidden_size, + intermediate_size=config.intermediate_size, + hidden_act=config.hidden_act, + quant_config=quant_config, + prefix=add_prefix("mlp", prefix), + ) + self.input_layernorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps) + self.post_attention_layernorm = RMSNorm( + config.hidden_size, eps=config.rms_norm_eps + ) + + def forward( + self, + positions: torch.Tensor, + hidden_states: torch.Tensor, + forward_batch: ForwardBatch, + residual: Optional[torch.Tensor], + ) -> Tuple[torch.Tensor, torch.Tensor]: + # Self Attention + if residual is None: + residual = hidden_states + hidden_states = self.input_layernorm(hidden_states) + else: + hidden_states, residual = self.input_layernorm(hidden_states, residual) + hidden_states = self.self_attn( + positions=positions, + hidden_states=hidden_states, + forward_batch=forward_batch, + ) + + # Fully Connected + hidden_states, residual = self.post_attention_layernorm(hidden_states, residual) + hidden_states = self.mlp(hidden_states) + return hidden_states, residual + + +class LlamaModel(nn.Module): + def __init__( + self, + config: LlamaConfig, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ) -> None: + super().__init__() + self.config = config + self.padding_idx = config.pad_token_id + self.vocab_size = config.vocab_size + self.embed_tokens = VocabParallelEmbedding( + config.vocab_size, + config.hidden_size, + quant_config=quant_config, + prefix=add_prefix("embed_tokens", prefix), + ) + self.layers = make_layers( + config.num_hidden_layers, + lambda idx, prefix: LlamaDecoderLayer( + config=config, layer_id=idx, quant_config=quant_config, prefix=prefix + ), + prefix="model.layers", + ) + + self.norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps) + self.layers_to_capture = [] + + def forward( + self, + input_ids: torch.Tensor, + positions: torch.Tensor, + forward_batch: ForwardBatch, + input_embeds: torch.Tensor = None, + ) -> Union[torch.Tensor, Tuple[torch.Tensor, List[torch.Tensor]]]: + if input_embeds is None: + hidden_states = self.embed_tokens(input_ids) + else: + hidden_states = input_embeds + residual = None + aux_hidden_states = [] + for i in range(len(self.layers)): + if i in self.layers_to_capture: + aux_hidden_states.append(hidden_states + residual) + layer = self.layers[i] + hidden_states, residual = layer( + positions, + hidden_states, + forward_batch, + residual, + ) + hidden_states, _ = self.norm(hidden_states, residual) + + if len(aux_hidden_states) == 0: + return hidden_states + + return hidden_states, aux_hidden_states + + # If this function is called, it should always initialize KV cache scale + # factors (or else raise an exception). Thus, handled exceptions should + # make sure to leave KV cache scale factors in a known good (dummy) state + def load_kv_cache_scales(self, quantization_param_path: str) -> None: + tp_size = get_tensor_model_parallel_world_size() + tp_rank = get_tensor_model_parallel_rank() + for layer_idx, scaling_factor in kv_cache_scales_loader( + quantization_param_path, + tp_rank, + tp_size, + self.config.num_hidden_layers, + self.config.__class__.model_type, + ): + if not isinstance(self.layers[layer_idx], nn.Identity): + layer_self_attn = self.layers[layer_idx].self_attn + + if hasattr(layer_self_attn.attn, "k_scale"): + layer_self_attn.attn.k_scale = scaling_factor + layer_self_attn.attn.v_scale = scaling_factor + else: + raise RuntimeError( + "Self attention has no KV cache scaling " "factor attribute!" + ) + + +class LlamaForCausalLM(nn.Module): + # BitandBytes specific attributes + default_bitsandbytes_target_modules = [ + ".gate_proj.", + ".down_proj.", + ".up_proj.", + ".q_proj.", + ".k_proj.", + ".v_proj.", + ".o_proj.", + ] + # in TP, these weights are partitioned along the column dimension (dim=-1) + column_parallel_weights_modules = [".down_proj.", ".o_proj."] + bitsandbytes_stacked_params_mapping = { + # shard_name, weight_name, index + ".q_proj": (".qkv_proj", 0), + ".k_proj": (".qkv_proj", 1), + ".v_proj": (".qkv_proj", 2), + ".gate_proj": (".gate_up_proj", 0), + ".up_proj": (".gate_up_proj", 1), + } + + def __init__( + self, + config: LlamaConfig, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ) -> None: + super().__init__() + self.config = config + self.quant_config = quant_config + self.model = self._init_model(config, quant_config, add_prefix("model", prefix)) + # Llama 3.2 1B Instruct set tie_word_embeddings to True + # Llama 3.1 8B Instruct set tie_word_embeddings to False + if self.config.tie_word_embeddings: + self.lm_head = self.model.embed_tokens + else: + self.lm_head = ParallelLMHead( + config.vocab_size, + config.hidden_size, + quant_config=quant_config, + prefix=add_prefix("lm_head", prefix), + ) + self.logits_processor = LogitsProcessor(config) + self.pooler = Pooler(pooling_type=PoolingType.LAST, normalize=True) + self.stacked_params_mapping = [ + # (param_name, shard_name, shard_id) + (".qkv_proj", ".q_proj", "q"), + (".qkv_proj", ".k_proj", "k"), + (".qkv_proj", ".v_proj", "v"), + (".gate_up_proj", ".gate_proj", 0), + (".gate_up_proj", ".up_proj", 1), + ] + + self.capture_aux_hidden_states = False + + def _init_model( + self, + config: LlamaConfig, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ): + return LlamaModel(config, quant_config=quant_config, prefix=prefix) + + @torch.no_grad() + def forward( + self, + input_ids: torch.Tensor, + positions: torch.Tensor, + forward_batch: ForwardBatch, + input_embeds: torch.Tensor = None, + get_embedding: bool = False, + ) -> LogitsProcessorOutput: + aux_hidden_states = None + if self.capture_aux_hidden_states: + hidden_states, aux_hidden_states = self.model( + input_ids, positions, forward_batch, input_embeds + ) + else: + hidden_states = self.model( + input_ids, positions, forward_batch, input_embeds + ) + + if not get_embedding: + return self.logits_processor( + input_ids, hidden_states, self.lm_head, forward_batch, aux_hidden_states + ) + else: + return self.pooler(hidden_states, forward_batch) + + def get_input_embeddings(self) -> nn.Embedding: + return self.model.embed_tokens + + def get_hidden_dim(self, module_name): + # return input_dim, output_dim + if module_name in ["q_proj", "o_proj", "qkv_proj"]: + return self.config.hidden_size, self.config.hidden_size + elif module_name in ["kv_proj"]: + return self.config.hidden_size, self.config.hidden_size // ( + self.config.num_attention_heads // self.config.num_key_value_heads + ) + elif module_name == "gate_up_proj": + return self.config.hidden_size, self.config.intermediate_size + elif module_name == "down_proj": + return self.config.intermediate_size, self.config.hidden_size + else: + raise NotImplementedError() + + def get_module_name(self, name): + params_mapping = { + "q_proj": "qkv_proj", + "k_proj": "qkv_proj", + "v_proj": "qkv_proj", + "gate_proj": "gate_up_proj", + "up_proj": "gate_up_proj", + } + return params_mapping.get(name, name) + + def get_module_name_from_weight_name(self, name): + for param_name, weight_name, shard_id, num_shard in self.stacked_params_mapping: + if weight_name in name: + return ( + name.replace(weight_name, param_name)[: -len(".weight")], + num_shard, + ) + return name[: -len(".weight")], 1 + + def get_num_params(self): + params_dict = dict(self.named_parameters()) + return len(params_dict) + + def load_weights(self, weights: Iterable[Tuple[str, torch.Tensor]]): + stacked_params_mapping = [ + # (param_name, shard_name, shard_id) + (".qkv_proj", ".q_proj", "q"), + (".qkv_proj", ".k_proj", "k"), + (".qkv_proj", ".v_proj", "v"), + (".gate_up_proj", ".gate_proj", 0), + (".gate_up_proj", ".up_proj", 1), + ] + + params_dict = dict(self.named_parameters()) + + for name, loaded_weight in weights: + if "rotary_emb.inv_freq" in name or "projector" in name: + continue + if "rotary_emb.cos_cached" in name or "rotary_emb.sin_cached" in name: + # Models trained using ColossalAI may include these tensors in + # the checkpoint. Skip them. + continue + if name.startswith("model.vision_tower") and name not in params_dict: + continue + if self.config.tie_word_embeddings and "lm_head.weight" in name: + continue + # Handle FP8 kv-scale remapping + if "scale" in name: + name = maybe_remap_kv_scale_name(name, params_dict) + if name is None: + continue + + for param_name, weight_name, shard_id in stacked_params_mapping: + if weight_name not in name: + continue + name = name.replace(weight_name, param_name) + # Skip loading extra bias for GPTQ models. + if name.endswith(".bias") and name not in params_dict: + continue + param = params_dict[name] + weight_loader = param.weight_loader + weight_loader(param, loaded_weight, shard_id) + break + else: + # Skip loading extra bias for GPTQ models. + if name.endswith(".bias") and name not in params_dict: + continue + # Skip loading kv_scale from ckpts towards new design. + if name.endswith(".kv_scale") and name not in params_dict: + continue + if name in params_dict.keys(): + param = params_dict[name] + weight_loader = getattr( + param, "weight_loader", default_weight_loader + ) + weight_loader(param, loaded_weight) + else: + logger.warning(f"Parameter {name} not found in params_dict") + + def get_weights_by_name( + self, name: str, truncate_size: int = 100, tp_size: int = 1 + ) -> Optional[torch.Tensor]: + """Get the weights of the parameter by its name. Similar to `get_parameter` in Hugging Face. + + Only used for unit test with an unoptimized performance. + For optimized performance, please use torch.save and torch.load. + """ + try: + if name == "lm_head.weight" and self.config.tie_word_embeddings: + logger.info( + "word embedding is tied for this model, return embed_tokens.weight as lm_head.weight." + ) + return ( + self.model.embed_tokens.weight.cpu() + .to(torch.float32) + .numpy() + .tolist()[:truncate_size] + ) + + mapped_name = name + mapped_shard_id = None + for param_name, weight_name, shard_id in self.stacked_params_mapping: + if weight_name in name: + mapped_name = name.replace(weight_name, param_name) + mapped_shard_id = shard_id + break + params_dict = dict(self.named_parameters()) + param = params_dict[mapped_name] + if mapped_shard_id is not None: + if mapped_shard_id in ["q", "k", "v"]: + num_heads = self.config.num_attention_heads // tp_size + num_kv_heads = self.config.num_key_value_heads // tp_size + head_dim = ( + self.config.hidden_size // self.config.num_attention_heads + ) + if mapped_shard_id == "q": + offset = 0 + size = num_heads * head_dim + elif mapped_shard_id == "k": + offset = num_heads * head_dim + size = num_kv_heads * head_dim + elif mapped_shard_id == "v": + offset = (num_heads + num_kv_heads) * head_dim + size = num_kv_heads * head_dim + weight = param.data.narrow(0, offset, size) + elif mapped_shard_id in [0, 1]: + intermediate_size = self.config.intermediate_size + slice_size = intermediate_size // tp_size + if mapped_shard_id == 0: # gate_proj + offset = 0 + size = slice_size + elif mapped_shard_id == 1: # up_proj + offset = slice_size + size = slice_size + + weight = param.data.narrow(0, offset, size) + else: + weight = param.data + else: + weight = param.data + if tp_size > 1 and ("o_proj" in name or "down_proj" in name): + gathered_weights = [torch.zeros_like(weight) for _ in range(tp_size)] + torch.distributed.all_gather(gathered_weights, weight) + weight = torch.cat(gathered_weights, dim=1) + return weight.cpu().to(torch.float32).numpy().tolist()[:truncate_size] + + except Exception: + logger.error( + f"Error getting weights by name {name} in LlamaForCausalLM: {get_exception_traceback()}" + ) + return None + + def get_embed_and_head(self): + return self.model.embed_tokens.weight, self.lm_head.weight + + def set_embed_and_head(self, embed, head): + del self.model.embed_tokens.weight + del self.lm_head.weight + self.model.embed_tokens.weight = embed + self.lm_head.weight = head + torch.cuda.empty_cache() + torch.cuda.synchronize() + + def get_embed(self): + return self.model.embed_tokens.weight + + def set_embed(self, embed): + # NOTE: If draft hidden size != target hidden size, the embed weight cannot be shared for EAGLE3 + if ( + hasattr(self.config, "target_hidden_size") + and self.config.target_hidden_size != self.config.hidden_size + ): + return + del self.model.embed_tokens.weight + self.model.embed_tokens.weight = embed + torch.cuda.empty_cache() + torch.cuda.synchronize() + + def load_kv_cache_scales(self, quantization_param_path: str) -> None: + self.model.load_kv_cache_scales(quantization_param_path) + + def set_eagle3_layers_to_capture(self): + self.capture_aux_hidden_states = True + num_layers = self.config.num_hidden_layers + self.model.layers_to_capture = [2, num_layers // 2, num_layers - 3] + + +class Phi3ForCausalLM(LlamaForCausalLM): + pass + + +class InternLM3ForCausalLM(LlamaForCausalLM): + pass + + +EntryClass = [LlamaForCausalLM, Phi3ForCausalLM, InternLM3ForCausalLM] diff --git a/lib/python3.12/site-packages/sglang/srt/models/llama_classification.py b/lib/python3.12/site-packages/sglang/srt/models/llama_classification.py new file mode 100644 index 0000000000000000000000000000000000000000..8387d20300b4264b97763857a3024799a2f10bcb --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/models/llama_classification.py @@ -0,0 +1,81 @@ +# Copyright 2023-2024 SGLang Team +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +from typing import Iterable, Optional, Tuple + +import torch +from torch import nn +from transformers import LlamaConfig + +from sglang.srt.layers.pooler import EmbeddingPoolerOutput, Pooler, PoolingType +from sglang.srt.layers.quantization.base_config import QuantizationConfig +from sglang.srt.model_executor.forward_batch_info import ForwardBatch +from sglang.srt.model_loader.weight_utils import default_weight_loader +from sglang.srt.models.llama import LlamaForCausalLM, LlamaModel +from sglang.srt.utils import add_prefix + + +class LlamaForClassification(nn.Module): + def __init__( + self, + config: LlamaConfig, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ) -> None: + super().__init__() + self.config = config + self.quant_config = quant_config + self.model = LlamaModel( + config, quant_config=quant_config, prefix=add_prefix("model", prefix) + ) + + self.classification_head = nn.Linear( + config.hidden_size, config.classification_out_size, bias=False + ) + self.pooler = Pooler(pooling_type=PoolingType.LAST, normalize=False) + + @torch.no_grad() + def forward( + self, + input_ids: torch.Tensor, + positions: torch.Tensor, + forward_batch: ForwardBatch, + input_embeds: torch.Tensor = None, + get_embedding: bool = True, + ) -> EmbeddingPoolerOutput: + assert ( + get_embedding + ), "LlamaForClassification is only used for embedding. Please add --is-embedding when you launch the server." + + hidden_states = self.model(input_ids, positions, forward_batch, input_embeds) + last_token_hidden = self.pooler(hidden_states, forward_batch).embeddings + scores = self.classification_head(last_token_hidden) + + return EmbeddingPoolerOutput(scores) + + def load_weights(self, weights: Iterable[Tuple[str, torch.Tensor]]): + params_dict = dict(self.named_parameters()) + + for name, loaded_weight in weights: + if "classification_head" in name: + param = params_dict[name] + weight_loader = getattr(param, "weight_loader", default_weight_loader) + weight_loader(param, loaded_weight) + elif "lm_head" in name: + continue + else: + LlamaForCausalLM.load_weights(self, [(name, loaded_weight)]) + + +EntryClass = LlamaForClassification diff --git a/lib/python3.12/site-packages/sglang/srt/models/llama_reward.py b/lib/python3.12/site-packages/sglang/srt/models/llama_reward.py new file mode 100644 index 0000000000000000000000000000000000000000..2f78dfa1bb3b48a9f0be4298e2fd15d25d281684 --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/models/llama_reward.py @@ -0,0 +1,126 @@ +# Copyright 2023-2024 SGLang Team +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +from typing import Iterable, Optional, Tuple + +import torch +from torch import nn +from transformers import LlamaConfig + +from sglang.srt.layers.pooler import EmbeddingPoolerOutput, Pooler, PoolingType +from sglang.srt.layers.quantization.base_config import QuantizationConfig +from sglang.srt.model_executor.forward_batch_info import ForwardBatch +from sglang.srt.models.llama import LlamaForCausalLM, LlamaModel +from sglang.srt.utils import add_prefix + + +class LlamaForSequenceClassification(nn.Module): + def __init__( + self, + config: LlamaConfig, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ) -> None: + super().__init__() + self.config = config + self.quant_config = quant_config + self.num_labels = config.num_labels + self.model = LlamaModel( + config, quant_config=quant_config, prefix=add_prefix("model", prefix) + ) + self.score = nn.Linear(config.hidden_size, self.num_labels, bias=False) + self.pooler = Pooler(pooling_type=PoolingType.LAST, normalize=False) + + self.eos_token_id = config.eos_token_id + + @torch.no_grad() + def forward( + self, + input_ids: torch.Tensor, + positions: torch.Tensor, + forward_batch: ForwardBatch, + input_embeds: torch.Tensor = None, + get_embedding: bool = True, + ) -> EmbeddingPoolerOutput: + assert ( + get_embedding + ), "LlamaForSequenceClassification is only used for embedding" + + hidden_states = self.model(input_ids, positions, forward_batch, input_embeds) + last_token_hidden = self.pooler(hidden_states, forward_batch).embeddings + scores = self.score(last_token_hidden) + + return EmbeddingPoolerOutput(scores) + + def load_weights(self, weights: Iterable[Tuple[str, torch.Tensor]]): + return LlamaForCausalLM.load_weights(self, weights) + + +class LlamaForSequenceClassificationWithNormal_Weights(LlamaForSequenceClassification): + class Weights(torch.nn.Module): + def __init__(self, hidden_size, num_label): + super().__init__() + self.fc = torch.nn.Sequential( + torch.nn.Linear(hidden_size, hidden_size, dtype=torch.float16), + torch.nn.SELU(), + torch.nn.Linear(hidden_size, hidden_size, dtype=torch.float16), + torch.nn.SELU(), + torch.nn.Linear(hidden_size, num_label // 2, dtype=torch.float16), + ) + + def forward(self, x): + return self.fc(x.to(torch.float16)) + + def __init__( + self, + config: LlamaConfig, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ) -> None: + super().__init__(config, quant_config, prefix=prefix) + self.weights = self.Weights(config.hidden_size, self.num_labels) + + @torch.no_grad() + def forward( + self, + input_ids: torch.Tensor, + positions: torch.Tensor, + forward_batch: ForwardBatch, + input_embeds: torch.Tensor = None, + get_embedding: bool = True, + ) -> EmbeddingPoolerOutput: + assert ( + get_embedding + ), "LlamaForSequenceClassification is only used for embedding" + hidden_states = self.model(input_ids, positions, forward_batch, input_embeds) + logits = self.score(hidden_states) + weights = self.weights(hidden_states) + + pooled_logits = self.pooler(logits, forward_batch).embeddings + pooled_weights = self.pooler(weights, forward_batch).embeddings + + rews = pooled_logits.view(-1, self.num_labels // 2, 2)[:, :, 0].view( + -1, self.num_labels // 2 + ) + scores = (rews * pooled_weights).sum(dim=-1).view(-1, 1) + return EmbeddingPoolerOutput(scores) + + def load_weights(self, weights: Iterable[Tuple[str, torch.Tensor]]): + return super().load_weights(weights) + + +EntryClass = [ + LlamaForSequenceClassification, + LlamaForSequenceClassificationWithNormal_Weights, +] diff --git a/lib/python3.12/site-packages/sglang/srt/models/minicpmv.py b/lib/python3.12/site-packages/sglang/srt/models/minicpmv.py new file mode 100644 index 0000000000000000000000000000000000000000..0323efb784e2f4c7105603dea647699e8847678d --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/models/minicpmv.py @@ -0,0 +1,1176 @@ +# Adapted from +# https://github.com/huggingface/transformers/blob/v4.28.0/src/transformers/models/llama/modeling_llama.py +# Copyright 2023 The SGLang team. +# Copyright 2022 EleutherAI and the HuggingFace Inc. team. All rights reserved. +# +# This code is based on EleutherAI's GPT-NeoX library and the GPT-NeoX +# and OPT implementations in this library. It has been modified from its +# original forms to accommodate minor architectural differences compared +# to GPT-NeoX and OPT used by the Meta AI team that trained the model. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Inference-only MiniCPM-V model compatible with HuggingFace weights.""" +from functools import partial +from typing import ( + Any, + Callable, + Iterable, + List, + Literal, + Optional, + Tuple, + TypedDict, + Union, +) + +import numpy as np +import torch +import torch.types +from PIL import Image +from torch import nn +from torch.nn.init import trunc_normal_ +from transformers import PretrainedConfig + +from sglang.srt.layers.activation import get_act_fn +from sglang.srt.layers.attention.vision import VisionAttention +from sglang.srt.layers.linear import ( + ColumnParallelLinear, + ReplicatedLinear, + RowParallelLinear, +) +from sglang.srt.layers.logits_processor import LogitsProcessor +from sglang.srt.layers.quantization.base_config import QuantizationConfig +from sglang.srt.managers.mm_utils import ( + MultiModalityDataPaddingPatternTokenPairs, + general_mm_embed_routine, +) +from sglang.srt.managers.schedule_batch import MultimodalDataItem, MultimodalInputs +from sglang.srt.model_executor.forward_batch_info import ForwardBatch +from sglang.srt.model_loader.utils import set_default_torch_dtype +from sglang.srt.model_loader.weight_utils import default_weight_loader +from sglang.srt.models.qwen2 import Qwen2Config, Qwen2ForCausalLM +from sglang.srt.utils import add_prefix, flatten_nested_list + +RawImageType = Union[Image.Image, torch.Tensor] + + +# sin/cos positional embedding helpers are adapted from: +# https://github.com/facebookresearch/mae/blob/efb2a8062c206524e35e47d04501ed4f544c0ae8/util/pos_embed.py#L20 +def get_1d_sincos_pos_embed_from_grid( + embed_dim: int, pos: np.ndarray, version: Tuple[int, int] = (2, 0) +) -> torch.Tensor: + """ + embed_dim: output dimension for each position + pos: a list of positions to be encoded: size (M,) / (H, W) + out: (M, D) / (H, W, D) + """ + assert embed_dim % 2 == 0 + omega = np.arange(embed_dim // 2, dtype=np.float32) + omega /= embed_dim / 2.0 + omega = 1.0 / 10000**omega # (D/2,) + + if version == (2, 0): + pos = pos.reshape(-1) # (M,) + out = np.einsum("m,d->md", pos, omega) # (M, D/2), outer product + emb_sin = np.sin(out) # (M, D/2) + emb_cos = np.cos(out) # (M, D/2) + emb = np.concatenate([emb_sin, emb_cos], axis=1) # (M, D) + else: + out = np.einsum("hw,d->hwd", pos, omega) # (H, W, D/2), outer product + emb_sin = np.sin(out) # (H, W, D/2) + emb_cos = np.cos(out) # (H, W, D/2) + emb = np.concatenate([emb_sin, emb_cos], axis=-1) # (H, W, D) + return emb + + +def get_2d_sincos_pos_embed_from_grid( + embed_dim: int, grid: np.ndarray, version: Tuple[int, int] = (2, 0) +) -> torch.Tensor: + assert embed_dim % 2 == 0 + + # use half of dimensions to encode grid_h + emb_h = get_1d_sincos_pos_embed_from_grid( + embed_dim // 2, grid[0], version + ) # (H*W, D/2) or (H, W, D/2) + emb_w = get_1d_sincos_pos_embed_from_grid( + embed_dim // 2, grid[1], version + ) # (H*W, D/2) or (H, W, D/2) + + if version == (2, 0): + emb = np.concatenate([emb_h, emb_w], axis=1) # (H*W, D) + else: + emb = np.concatenate([emb_h, emb_w], axis=-1) # (H, W, D) + return emb + + +def get_2d_sincos_pos_embed( + embed_dim: int, + grid_size: Union[int, Tuple[int, int]], + cls_token: bool = False, + version: Tuple[int, int] = (2, 0), +) -> torch.Tensor: + """ + grid_size: int of the grid height and width + return: + pos_embed: [grid_size*grid_size, embed_dim] or + [1+grid_size*grid_size, embed_dim] (w/ or w/o cls_token) + """ + if isinstance(grid_size, int): + grid_h_size, grid_w_size = grid_size, grid_size + else: + grid_h_size, grid_w_size = grid_size[0], grid_size[1] + + grid_h = np.arange(grid_h_size, dtype=np.float32) + grid_w = np.arange(grid_w_size, dtype=np.float32) + grid = np.meshgrid(grid_w, grid_h) # here w goes first + grid = np.stack(grid, axis=0) + assert isinstance(grid, np.ndarray) and grid.shape == (2, grid_h_size, grid_w_size) + + if version == (2, 0): + grid = grid.reshape([2, 1, grid_h_size, grid_w_size]) + pos_embed = get_2d_sincos_pos_embed_from_grid(embed_dim, grid, version) + if cls_token: + pos_embed = np.concatenate([np.zeros([1, embed_dim]), pos_embed], axis=0) + else: + pos_embed = get_2d_sincos_pos_embed_from_grid(embed_dim, grid, version) + return pos_embed + + +class Idefics2VisionMLP(nn.Module): + + def __init__( + self, + config: PretrainedConfig, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ) -> None: + super().__init__() + self.config = config + self.activation_fn = get_act_fn(config.hidden_act) + self.fc1 = ColumnParallelLinear( + config.hidden_size, + config.intermediate_size, + bias=True, + quant_config=quant_config, + prefix=add_prefix("fc1", prefix), + ) + self.fc2 = RowParallelLinear( + config.intermediate_size, + config.hidden_size, + bias=True, + quant_config=quant_config, + prefix=add_prefix("fc2", prefix), + ) + + def forward(self, hidden_states: torch.Tensor) -> torch.Tensor: + hidden_states, _ = self.fc1(hidden_states) + hidden_states = self.activation_fn(hidden_states) + hidden_states, _ = self.fc2(hidden_states) + return hidden_states + + +class Idefics2EncoderLayer(nn.Module): + + def __init__( + self, + config: PretrainedConfig, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ) -> None: + super().__init__() + self.embed_dim = config.hidden_size + self.num_heads = config.num_attention_heads + self.self_attn = VisionAttention( + embed_dim=config.hidden_size, + num_heads=self.num_heads, + projection_size=config.intermediate_size, + use_qkv_parallel=True, + quant_config=quant_config, + dropout=config.attention_dropout, + use_context_forward=False, + softmax_in_single_precision=True, + flatten_batch=False, + prefix=add_prefix("self_attn", prefix), + ) + self.layer_norm1 = nn.LayerNorm(self.embed_dim, eps=config.layer_norm_eps) + self.mlp = Idefics2VisionMLP( + config, + quant_config=quant_config, + prefix=add_prefix("mlp", prefix), + ) + self.layer_norm2 = nn.LayerNorm(self.embed_dim, eps=config.layer_norm_eps) + + def forward( + self, + hidden_states: torch.Tensor, + cu_seqlens: torch.Tensor, + ) -> torch.Tensor: + """ + Args: + hidden_states (`torch.FloatTensor`): + Input to the layer of shape `(batch, seq_len, embed_dim)`. + + """ + residual = hidden_states + hidden_states = self.layer_norm1(hidden_states) + hidden_states = self.self_attn(hidden_states, cu_seqlens=cu_seqlens) + + hidden_states = residual + hidden_states + residual = hidden_states + hidden_states = self.layer_norm2(hidden_states) + hidden_states = self.mlp(hidden_states) + hidden_states = residual + hidden_states + return hidden_states + + +class Idefics2Encoder(nn.Module): + """ + Transformer encoder consisting of `config.num_hidden_layers` self attention + layers. Each layer is a + [`Idefics2EncoderLayer`]. + + Args: + config: Idefics2Config + """ + + def __init__( + self, + config: PretrainedConfig, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ) -> None: + super().__init__() + + self.config = config + self.layers = nn.ModuleList( + [ + Idefics2EncoderLayer( + config, + quant_config=quant_config, + prefix=add_prefix(f"layers.{i}", prefix), + ) + for i in range(config.num_hidden_layers) + ] + ) + + def forward( + self, + inputs_embeds: torch.Tensor, + cu_seqlens: torch.Tensor, + ) -> torch.Tensor: + r""" + Args: + inputs_embeds (torch.Tensor): + Optionally, instead of passing `input_ids` you can choose to + directly pass an embedded representation. + This is useful if you want more control over how to convert + `input_ids` indices into associated vectorsthan the model's + internal embedding lookup matrix. + """ + hidden_states = inputs_embeds + for encoder_layer in self.layers: + layer_outputs = encoder_layer( + hidden_states, + cu_seqlens=cu_seqlens, + ) + hidden_states = layer_outputs + return hidden_states + + +class Idefics2VisionEmbeddings(nn.Module): + """ + This is a modified version of `siglip.modelign_siglip.SiglipVisionEmbeddings + ` to enable images of variable + resolution. + + The modifications are adapted from [Patch n' Pack: NaViT, a Vision + Transformer for any Aspect Ratio and Resolution](https://arxiv.org/abs/2307.06304) + which allows treating images in their native aspect ratio and without the + need to resize them to the same fixed size. In particular, we start from the + original pre-trained SigLIP model(which uses images of fixed-size square + images) and adapt it by training on images of variable resolutions. + """ + + def __init__(self, config: PretrainedConfig): + super().__init__() + self.embed_dim = config.hidden_size + self.image_size = config.image_size + self.patch_size = config.patch_size + self.patch_embedding = nn.Conv2d( + in_channels=config.num_channels, + out_channels=self.embed_dim, + kernel_size=self.patch_size, + stride=self.patch_size, + padding="valid", + ) + self.num_patches_per_side = self.image_size // self.patch_size + self.num_patches = self.num_patches_per_side**2 + self.num_positions = self.num_patches + self.position_embedding = nn.Embedding(self.num_positions, self.embed_dim) + + def get_position_ids( + self, + pixel_values: torch.FloatTensor, + patch_attention_mask: torch.BoolTensor, + tgt_sizes: Optional[torch.IntTensor] = None, + ): + batch_size, _, max_im_h, max_im_w = pixel_values.shape + + max_nb_patches_h, max_nb_patches_w = ( + max_im_h // self.patch_size, + max_im_w // self.patch_size, + ) + boundaries = torch.arange( + 1 / self.num_patches_per_side, 1.0, 1 / self.num_patches_per_side + ) + position_ids = torch.full( + size=(batch_size, max_nb_patches_h * max_nb_patches_w), fill_value=0 + ) + + for batch_idx, p_attn_mask in enumerate(patch_attention_mask): + + if tgt_sizes is not None: + nb_patches_h = tgt_sizes[batch_idx][0] + nb_patches_w = tgt_sizes[batch_idx][1] + else: + nb_patches_h = p_attn_mask[:, 0].sum() + nb_patches_w = p_attn_mask[0].sum() + fractional_coords_h = torch.arange(0, 1 - 1e-6, 1 / nb_patches_h) + fractional_coords_w = torch.arange(0, 1 - 1e-6, 1 / nb_patches_w) + bucket_coords_h = torch.bucketize( + fractional_coords_h, boundaries, right=True + ) + bucket_coords_w = torch.bucketize( + fractional_coords_w, boundaries, right=True + ) + pos_ids = ( + bucket_coords_h[:, None] * self.num_patches_per_side + bucket_coords_w + ).flatten() + position_ids[batch_idx][p_attn_mask.view(-1).cpu()] = pos_ids + position_ids = position_ids.to(self.position_embedding.weight.device) + return position_ids + + def forward( + self, + pixel_values: torch.FloatTensor, + patch_attention_mask: torch.BoolTensor, + tgt_sizes: Optional[torch.IntTensor] = None, + ) -> torch.Tensor: + target_dtype = self.patch_embedding.weight.dtype + pixel_values = pixel_values.to( + device=self.patch_embedding.weight.device, dtype=target_dtype + ) + patch_embeds = self.patch_embedding(pixel_values) + embeddings = patch_embeds.flatten(2).transpose(1, 2) + position_ids = self.get_position_ids( + pixel_values, patch_attention_mask, tgt_sizes + ) + + embeddings = embeddings + self.position_embedding(position_ids) + return embeddings + + +class Idefics2VisionTransformer(nn.Module): + + def __init__( + self, + config: PretrainedConfig, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ) -> None: + super().__init__() + + embed_dim = config.hidden_size + self.config = config + self.embeddings = Idefics2VisionEmbeddings(config) + self.encoder = Idefics2Encoder( + config=config, + quant_config=quant_config, + prefix=add_prefix("encoder", prefix), + ) + self.post_layernorm = nn.LayerNorm(embed_dim, eps=config.layer_norm_eps) + + def get_input_embeddings(self) -> nn.Embedding: + return self.embeddings + + def compute_cu_seqlens(self, tgt_sizes: torch.Tensor) -> torch.Tensor: + patch_len = tgt_sizes[:, 0] * tgt_sizes[:, 1] # shape: (batch_size,) + cu_seqlens = torch.cat( + [ + torch.tensor([0], device=patch_len.device, dtype=torch.int32), + torch.cumsum(patch_len, dim=0, dtype=torch.int32), + ], + dim=0, + ).to(tgt_sizes.device) + return cu_seqlens + + def forward( + self, + pixel_values, + patch_attention_mask: Optional[torch.BoolTensor] = None, + tgt_sizes: Optional[torch.IntTensor] = None, + ) -> torch.Tensor: + hidden_states = self.embeddings( + pixel_values=pixel_values, + patch_attention_mask=patch_attention_mask, + tgt_sizes=tgt_sizes, + ) + cu_seqlens = self.compute_cu_seqlens(tgt_sizes) + encoder_outputs = self.encoder( + hidden_states, + cu_seqlens=cu_seqlens, + ) + last_hidden_state = self.post_layernorm(encoder_outputs) + return last_hidden_state + + +class MiniCPMVImagePixelInputs(TypedDict): + type: Literal["pixel_values"] + data: List[torch.Tensor] + """ + Shape: `(batch_size * num_images, num_channels, height, width)` + + Note that the image size may vary, so we pass it as a list + instead of a batched tensor. + """ + + image_bounds: torch.Tensor + """ + Shape: `(batch_size * num_images, 2)` + + This should be in `(start, stop)` format. + """ + + tgt_sizes: torch.Tensor + """ + Shape: `(batch_size * num_images, 2)` + + This should be in `(height, width)` format. + """ + + +class MiniCPMVImageEmbeddingInputs(TypedDict): + type: Literal["image_embeds"] + data: torch.Tensor + """ + Shape: `(batch_size * num_images, image_feature_size, hidden_size)` + + `hidden_size` must match the hidden size of language model backbone. + instead of a batched tensor. + """ + + image_bounds: torch.Tensor + """ + Shape: `(batch_size * num_images, 2)` + + This should be in `(start, stop)` format. + """ + + +MiniCPMVImageInputs = Union[MiniCPMVImagePixelInputs, MiniCPMVImageEmbeddingInputs] + +DEFAULT_LN = partial(nn.LayerNorm, eps=1e-6) + + +class BaseResampler(nn.Module): + """ + A 2D perceiver-resampler network with one cross attention layers by + (grid_size**2) learnable queries and 2d sincos pos_emb. + Outputs: + A tensor with the shape of (grid_size**2, embed_dim) + """ + + def __init__( + self, + num_queries: int, + embed_dim: int, + num_heads: int, + kv_dim: Optional[int] = None, + norm_layer: Callable[[int], nn.LayerNorm] = DEFAULT_LN, + do_post_projection: bool = True, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ) -> None: + super().__init__() + + self.num_queries = num_queries + self.embed_dim = embed_dim + self.num_heads = num_heads + + self.query = nn.Parameter(torch.zeros(self.num_queries, embed_dim)) + trunc_normal_(self.query, std=0.02) + if kv_dim is not None and kv_dim != embed_dim: + self.kv_proj = ReplicatedLinear( + kv_dim, + embed_dim, + bias=False, + quant_config=quant_config, + prefix=add_prefix("kv_proj", prefix), + ) + else: + # Maintain the same return value with ReplicatedLinear.forward + self.kv_proj = lambda *args, **kwargs: ( # type: ignore # noqa + nn.Identity()(*args, **kwargs), + None, + ) + self.attn = nn.MultiheadAttention(embed_dim, num_heads) + self.ln_q = norm_layer(embed_dim) + self.ln_kv = norm_layer(embed_dim) + self.do_post_projection = do_post_projection + self.ln_post = norm_layer(embed_dim) if do_post_projection else None + self.proj = ( + nn.Parameter((embed_dim**-0.5) * torch.randn(embed_dim, embed_dim)) + if do_post_projection + else None + ) + + def _init_weights(self, m: nn.Module) -> None: + if isinstance(m, nn.Linear): + trunc_normal_(m.weight, std=0.02) + if isinstance(m, nn.Linear) and m.bias is not None: + nn.init.constant_(m.bias, 0) + elif isinstance(m, nn.LayerNorm): + nn.init.constant_(m.bias, 0) + nn.init.constant_(m.weight, 1.0) + + def _repeat(self, query, N: int): + return query.unsqueeze(1).repeat(1, N, 1) + + +class Resampler2_5(BaseResampler): + + def __init__( + self, + num_queries: int, + embed_dim: int, + num_heads: int, + kv_dim: Optional[int] = None, + norm_layer: Callable[[int], nn.LayerNorm] = DEFAULT_LN, + max_size: Tuple[int, int] = (70, 70), + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ) -> None: + super().__init__( + num_queries, + embed_dim, + num_heads, + kv_dim, + norm_layer, + quant_config=quant_config, + prefix=prefix, + ) + + self.max_size = max_size + self._set_2d_pos_cache(self.max_size) + + self.apply(self._init_weights) + + def _set_2d_pos_cache( + self, max_size: Tuple[int, int], device: torch.types.Device = "cpu" + ) -> None: + pos_embed_arr = get_2d_sincos_pos_embed( + self.embed_dim, max_size, version=(2, 5) + ) + pos_embed = torch.from_numpy(pos_embed_arr).float().to(device) + self.register_buffer("pos_embed", pos_embed, persistent=False) + + def _adjust_pos_cache( + self, tgt_sizes: torch.Tensor, device: torch.types.Device + ) -> None: + max_h = tgt_sizes[:, 0].max().item() + max_w = tgt_sizes[:, 1].max().item() + assert isinstance(max_h, int) and isinstance(max_w, int) + + if max_h > self.max_size[0] or max_w > self.max_size[1]: + self.max_size = ( + max(max_h, self.max_size[0]), + max(max_w, self.max_size[1]), + ) + self._set_2d_pos_cache(self.max_size, device) + + def forward(self, x: torch.Tensor, tgt_sizes: torch.Tensor) -> torch.Tensor: + assert x.shape[0] == tgt_sizes.shape[0] + bs = x.shape[0] + + device = x.device + dtype = x.dtype + + patch_len = tgt_sizes[:, 0] * tgt_sizes[:, 1] + + self._adjust_pos_cache(tgt_sizes, device=device) + + max_patch_len = patch_len.max().item() + assert isinstance(max_patch_len, int) + + key_padding_mask = torch.zeros( + (bs, max_patch_len), dtype=torch.bool, device=device + ) + + pos_embed = [] + for i in range(bs): + tgt_h, tgt_w = tgt_sizes[i].tolist() + pos_embed.append( + self.pos_embed[:tgt_h, :tgt_w, :].reshape((tgt_h * tgt_w, -1)).to(dtype) + ) # patches * D + key_padding_mask[i, patch_len[i] :] = True + pos_embed = torch.nn.utils.rnn.pad_sequence( + pos_embed, batch_first=True, padding_value=0.0 + ).permute( + 1, 0, 2 + ) # BLD => L * B * D + x, _ = self.kv_proj(x) # B * L * D + x = self.ln_kv(x).permute(1, 0, 2) # L * B * D + + q = self.ln_q(self.query) # Q * D + + out = self.attn( + self._repeat(q, bs), # Q * B * D + x + pos_embed, # L * B * D + L * B * D + x, + key_padding_mask=key_padding_mask, + )[0] + # out: Q * B * D + x = out.permute(1, 0, 2) # B * Q * D + + x = self.ln_post(x) + x = x @ self.proj + return x + + +def get_version_by_config(config: PretrainedConfig) -> Tuple[int, ...]: + version_float = getattr(config, "version", None) + + # The old configs do not include version number + # TODO: Remove this after the HF repos are updated + if version_float is None: + if config.hidden_size == 2304 and config.query_num == 64: + return 2, 0 + return 2, 5 + + version_str = str(version_float) + return tuple(int(x) for x in version_str.split(".")) + + +class MiniCPMBaseModel(nn.Module): + """ + The abstract class of MiniCPMV can only be inherited, but cannot be + instantiated. + """ + + def __init__( + self, + *, + config: PretrainedConfig, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ): + super().__init__() + # All MiniCPM-V models disable `tie_word_embeddings` but + # `PretrainedConfig.tie_word_embeddings` defaults to True; we cannot + # check `tie_word_embeddings` until SGLang integrate MiniCPM-V model + # and config class + self.config = config + + self.version = get_version_by_config(self.config) + self.llm = self.init_llm( + config=config, quant_config=quant_config, prefix=add_prefix("llm", prefix) + ) + self.vpm = self.init_vision_module( + config, quant_config, add_prefix("vpm", prefix) + ) + self.vision_dim = ( + self.vpm.embed_dim + if self.version == (2, 0) + else self.vpm.embeddings.embed_dim + ) + self.embed_dim = self.config.hidden_size + + self.resampler = self.init_resampler( + self.embed_dim, + self.vision_dim, + quant_config=quant_config, + prefix=add_prefix("resampler", prefix), + ) + + self.logits_processor = LogitsProcessor(config) + + def _get_image_bounds( + self, + input_ids: torch.Tensor, + pad_values: List[int], + im_start_id: int, + im_end_id: int, + slice_start_id: Optional[int] = None, + slice_end_id: Optional[int] = None, + ) -> torch.Tensor: + """ + Returns a tensor indicating the bounds (start and end token ids) of the images + """ + # All the images in the batch should share the same special image + # bound token ids. + start_cond = input_ids == im_start_id + end_cond = input_ids == im_end_id + if slice_start_id is not None: + start_cond |= input_ids == slice_start_id + end_cond |= input_ids == slice_end_id + + (image_start_tokens,) = torch.where(start_cond) + image_start_tokens += 1 + (image_end_tokens,) = torch.where(end_cond) + + # the im_start_id sometimes can be cached as prefix, but it is needed for the embedding of the images + if len(image_start_tokens) != len(image_end_tokens): + if ( + len(image_start_tokens) + 1 == len(image_end_tokens) + and input_ids[0] in pad_values + and len(image_start_tokens) != 0 + and len(image_end_tokens) != 0 + and image_end_tokens[0] < image_start_tokens[0] + ): + image_start_tokens = torch.cat( + [ + torch.tensor([0], device=image_start_tokens.device), + image_start_tokens, + ] + ) + valid_image_nums = min(len(image_start_tokens), len(image_end_tokens)) + + if valid_image_nums == 0: + return torch.zeros((0, 2), device=input_ids.device) + + # Filter out pairs where start_token >= end_token + valid_pairs = [] + for i in range(valid_image_nums): + start_token = image_start_tokens[i] + end_token = image_end_tokens[i] + if start_token < end_token: + valid_pairs.append((start_token, end_token)) + + if not valid_pairs: + return torch.zeros((0, 2), device=input_ids.device) + + # Convert valid pairs to tensor + valid_pairs_tensor = torch.tensor(valid_pairs, device=input_ids.device) + return valid_pairs_tensor + + def _parse_and_validate_inputs( + self, + input_ids: torch.Tensor, + **kwargs: object, + ) -> Optional[MiniCPMVImageInputs]: + pixel_values = kwargs.pop("pixel_values", []) + tgt_sizes = kwargs.pop("tgt_sizes", []) + im_start_id = kwargs.pop("im_start_id", None) + im_end_id = kwargs.pop("im_end_id", None) + slice_start_id = kwargs.pop("slice_start_id", None) + slice_end_id = kwargs.pop("slice_end_id", None) + image_embeds = kwargs.pop("image_embeds", None) + pad_values = kwargs.pop("pad_values", None) + + if image_embeds is not None: + image_bounds = self._get_image_bounds( + input_ids=input_ids, + pad_values=pad_values, + im_start_id=im_start_id, + im_end_id=im_end_id, + slice_start_id=slice_start_id, + slice_end_id=slice_end_id, + ) + if not isinstance(image_embeds, (torch.Tensor, list)): + raise ValueError( + f"Incorrect type of image embeds. " + f"Got type: {type(image_embeds)}" + ) + + if isinstance(image_embeds, list): + image_embeds = torch.cat(image_embeds) + + return MiniCPMVImageEmbeddingInputs( + image_bounds=image_bounds, + data=image_embeds, + type="image_embeds", + ) + + image_bounds = self._get_image_bounds( + input_ids=input_ids, + pad_values=pad_values, + im_start_id=im_start_id, + im_end_id=im_end_id, + slice_start_id=slice_start_id, + slice_end_id=slice_end_id, + ) + return MiniCPMVImagePixelInputs( + image_bounds=image_bounds.to(device=input_ids.device), + data=pixel_values, + tgt_sizes=tgt_sizes, + type="pixel_values", + ) + + def get_embedding( + self, + input_ids: torch.Tensor, + image_inputs: Optional[MiniCPMVImageInputs], + ) -> Tuple[torch.Tensor, torch.Tensor]: + vlm_embedding: torch.Tensor = self.llm.get_input_embeddings(input_ids) + + if image_inputs is None: # No image + vision_hidden_states = torch.tensor([], device=input_ids.device) + else: + if image_inputs["type"] == "image_embeds": + vision_hidden_states = ( + image_inputs["data"] + .type(vlm_embedding.dtype) + .to(vlm_embedding.device) + ) + else: + vision_hidden_states = self.get_vision_hidden_states(image_inputs) + # See NOTE in _parse_and_validate_inputs + image_bounds = image_inputs["image_bounds"] + if len(image_bounds) > 0: + image_indices = torch.stack( + [ + torch.arange(start, end, dtype=torch.long) + for start, end in image_bounds.tolist() + ] + ).to(vlm_embedding.device) + + vlm_embedding.scatter_( + 0, + image_indices.view(-1, 1).repeat(1, vlm_embedding.shape[-1]), + vision_hidden_states.view(-1, vision_hidden_states.shape[-1]), + ) + + return vlm_embedding, vision_hidden_states + + def get_input_embeddings(self) -> nn.Embedding: + return self.llm.get_input_embeddings() + + def forward( + self, + input_ids: torch.Tensor, + positions: torch.Tensor, + forward_batch: ForwardBatch, + **kwargs: Any, + ) -> torch.Tensor: + hidden_states = general_mm_embed_routine( + input_ids=input_ids, + forward_batch=forward_batch, + image_data_embedding_func=self.get_image_feature, + language_model=self.llm, + positions=positions, + ) + return hidden_states + + def init_llm( + self, + config: Qwen2Config, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ) -> nn.Module: + raise NotImplementedError + + def init_vision_module( + self, + config: PretrainedConfig, + quant_config: Optional[QuantizationConfig], + prefix: str = "", + ) -> nn.Module: + raise NotImplementedError + + def init_resampler( + self, + embed_dim: int, + vision_dim: int, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ) -> nn.Module: + raise NotImplementedError + + def get_vision_embedding( + self, + pixel_values: List[torch.Tensor], + patch_attn_mask: Optional[torch.Tensor] = None, + tgt_sizes: Optional[torch.Tensor] = None, + ) -> torch.Tensor: + raise NotImplementedError + + def get_image_feature(self, items: List[MultimodalDataItem]) -> torch.Tensor: + raise NotImplementedError + + +class MiniCPMV2_6(MiniCPMBaseModel): + packed_modules_mapping = { + "qkv_proj": [ + "q_proj", + "k_proj", + "v_proj", + ], + "gate_up_proj": [ + "gate_proj", + "up_proj", + ], + } + # LoRA specific attributes + supported_lora_modules = [ + # vision encoder + "fc1", + "fc2", + "out_proj", + # language model + "qkv_proj", # same name with vision encoder + "o_proj", + "gate_up_proj", + "down_proj", + # resampler + "kv_proj", + ] + + # BitandBytes specific attributes + bitsandbytes_stacked_params_mapping = { + # shard_name, weight_name, index + "q_proj": ("qkv_proj", 0), + "k_proj": ("qkv_proj", 1), + "v_proj": ("qkv_proj", 2), + "gate_proj": ("gate_up_proj", 0), + "up_proj": ("gate_up_proj", 1), + } + + embedding_modules = {} + embedding_padding_modules = [] + + def __init__( + self, + config: PretrainedConfig, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ): + super().__init__(config=config, quant_config=quant_config, prefix=prefix) + assert self.version == (2, 6) + + def init_llm( + self, + config: Qwen2Config, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ) -> nn.Module: + return Qwen2ForCausalLM(config=config, quant_config=quant_config, prefix=prefix) + + def init_vision_module( + self, + config: PretrainedConfig, + quant_config: Optional[QuantizationConfig], + prefix: str = "", + ) -> nn.Module: + model = Idefics2VisionTransformer( + config=config.vision_config, quant_config=quant_config, prefix=prefix + ) + if self.config.drop_vision_last_layer: + model.encoder.layers = model.encoder.layers[:-1] + + setattr(model, "embed_dim", model.embeddings.embed_dim) + setattr(model, "patch_size", model.embeddings.patch_size) + return model + + def init_resampler( + self, + embed_dim: int, + vision_dim: int, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ) -> nn.Module: + with set_default_torch_dtype(torch.float16): + # The resampler in 2.6 remains consistent with the one in 2.5. + resampler = Resampler2_5( + num_queries=self.config.query_num, + embed_dim=embed_dim, + num_heads=embed_dim // 128, + kv_dim=vision_dim, + quant_config=quant_config, + prefix=prefix, + ) + + return resampler.to(device="cuda", dtype=torch.get_default_dtype()) + + def get_vision_embedding( + self, + pixel_values: List[torch.Tensor], + patch_attn_mask: Optional[torch.Tensor] = None, + tgt_sizes: Optional[torch.Tensor] = None, + ) -> torch.Tensor: + vision_embedding = self.vpm( + pixel_values, + patch_attention_mask=patch_attn_mask, + tgt_sizes=tgt_sizes, + ) + return vision_embedding + + def get_image_feature(self, items: List[MultimodalDataItem]) -> torch.Tensor: + # list of tensors + pixel_values = flatten_nested_list([item.pixel_values for item in items]) + tgt_sizes = torch.stack( + flatten_nested_list([item.tgt_size for item in items]), dim=0 + ) + assert len(pixel_values) == tgt_sizes.shape[0] + + device = self.vpm.embeddings.position_embedding.weight.device + dtype = self.vpm.embeddings.position_embedding.weight.dtype + all_pixel_values_lst = [ + i.flatten(end_dim=1).permute(1, 0) for i in pixel_values + ] + + max_patches = (tgt_sizes[:, 0] * tgt_sizes[:, 1]).max().item() + assert isinstance(max_patches, int) + all_pixel_values = torch.nn.utils.rnn.pad_sequence( + all_pixel_values_lst, batch_first=True, padding_value=0.0 + ) + + B, L, _ = all_pixel_values.shape + all_pixel_values = all_pixel_values.permute(0, 2, 1).reshape(B, 3, -1, L) + patch_attn_mask = torch.zeros( + (B, 1, max_patches), dtype=torch.bool, device=device + ) + + tgt_sizes_tensor = tgt_sizes.clone().to(device=patch_attn_mask.device) + mask_shapes = tgt_sizes_tensor[:, 0] * tgt_sizes_tensor[:, 1] + patch_attn_mask[:, 0, :] = torch.arange( + patch_attn_mask.size(2), device=patch_attn_mask.device + ).unsqueeze(0) < mask_shapes.unsqueeze(1) + + vision_embedding = self.vpm( + all_pixel_values.type(dtype), + patch_attention_mask=patch_attn_mask, + tgt_sizes=tgt_sizes, + ) + return self.resampler(vision_embedding, tgt_sizes) + + def pad_input_ids(self, input_ids: List[int], image_inputs: MultimodalInputs): + # Get all special token IDs + im_start_id: int = image_inputs.im_start_id + im_end_id: int = image_inputs.im_end_id + slice_start_id: int = image_inputs.slice_start_id + slice_end_id: int = image_inputs.slice_end_id + + media_token_pairs = [(im_start_id, im_end_id), (slice_start_id, slice_end_id)] + pattern = MultiModalityDataPaddingPatternTokenPairs(media_token_pairs) + + return pattern.pad_input_tokens(input_ids, image_inputs) + + +_SUPPORT_VERSION = {(2, 6): MiniCPMV2_6} + + +class MiniCPMV: + """ + Different versions of MiniCPMV use different visual encoders and LLMs, + which is not conducive to the current integration logic of LoRA and + bitsandbytes in SGLang. Therefore, it is necessary to separate them. + """ + + # Ensure that the LoRA support check passes when the class is not + # initialized, but set all these attributes to empty. + packed_modules_mapping = {} + supported_lora_modules = [] + embedding_modules = {} + embedding_padding_modules = [] + + minicpmv: nn.Module + + def __init__( + self, + config: PretrainedConfig, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ) -> None: + super().__init__() + + if not hasattr(config, "version"): + version = (2, 6) + else: + version = str(config.version).split(".") + version = tuple([int(x) for x in version]) + # Dispatch class based on version + instance_class = _SUPPORT_VERSION.get(version) + if instance_class is None: + raise ValueError("Currently, MiniCPMV only supports versions 2.6") + + try: + minicpmv = instance_class( + config=config, quant_config=quant_config, prefix=prefix + ) + self.minicpmv = minicpmv + except Exception as e: + print(f"Failed to instantiate MiniCPMV: {e}") + raise e + self.config = config + + def __getattr__(self, name): + if name == "minicpmv": + return None + return getattr(self.minicpmv, name) + + def __call__(self, *args, **kwargs): + return self.minicpmv(*args, **kwargs) + + def load_weights(self, weights: Iterable[Tuple[str, torch.Tensor]]): + stacked_params_mapping = [ + # (param_name, shard_name, shard_id) + ("qkv_proj", "q_proj", "q"), + ("qkv_proj", "k_proj", "k"), + ("qkv_proj", "v_proj", "v"), + ("gate_up_proj", "gate_proj", 0), + ("gate_up_proj", "up_proj", 1), + ] + + params_dict = dict(self.minicpmv.named_parameters()) + for name, loaded_weight in weights: + if "rotary_emb.inv_freq~" in name or "projector" in name: + continue + if "rotary_emb.cos_cached" in name or "rotary_emb.sin_cached" in name: + # Models trained using ColossalAI may include these tensors in + # the checkpoint. Skip them. + continue + if name.startswith("model.vision_tower") and name not in params_dict: + continue + + # adapt to VisionAttention + name = name.replace(r"self_attn.out_proj", r"self_attn.proj") + + if "sampler" in name: + param = params_dict[name] + weight_loader = getattr(param, "weight_loader", default_weight_loader) + weight_loader(param, loaded_weight) + continue + + for param_name, weight_name, shard_id in stacked_params_mapping: + # replace the name and load with customized loader + if weight_name not in name: + continue + name = name.replace(weight_name, param_name) + # # Skip loading extra bias for GPTQ models. + if name.endswith(".bias") and name not in params_dict: + continue + param = params_dict[name] + weight_loader = param.weight_loader + weight_loader(param, loaded_weight, shard_id) + break + else: + # Skip loading extra bias for GPTQ models. + if name.endswith(".bias") and name not in params_dict: + continue + + param = params_dict[name] + weight_loader = getattr(param, "weight_loader", default_weight_loader) + weight_loader(param, loaded_weight) + + +EntryClass = MiniCPMV diff --git a/lib/python3.12/site-packages/sglang/srt/models/olmo2.py b/lib/python3.12/site-packages/sglang/srt/models/olmo2.py new file mode 100644 index 0000000000000000000000000000000000000000..75834e6fb9a7ad5e5bc3630e258201c24f9836be --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/models/olmo2.py @@ -0,0 +1,413 @@ +# Copyright 2023-2024 SGLang Team +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +# Adapted from +# https://github.com/vllm-project/vllm/blob/main/vllm/model_executor/models/olmo2.py +"""Inference-only OLMo2 model compatible with HuggingFace weights.""" +from functools import partial +from typing import Iterable, Optional, Tuple + +import torch +from torch import nn +from transformers import PretrainedConfig + +from sglang.srt.distributed import ( + get_tensor_model_parallel_rank, + get_tensor_model_parallel_world_size, + split_tensor_along_last_dim, + tensor_model_parallel_all_gather, +) +from sglang.srt.layers.activation import SiluAndMul +from sglang.srt.layers.layernorm import RMSNorm +from sglang.srt.layers.linear import ( + MergedColumnParallelLinear, + QKVParallelLinear, + RowParallelLinear, +) +from sglang.srt.layers.logits_processor import LogitsProcessor +from sglang.srt.layers.quantization.base_config import QuantizationConfig +from sglang.srt.layers.radix_attention import RadixAttention +from sglang.srt.layers.rotary_embedding import get_rope +from sglang.srt.layers.vocab_parallel_embedding import ( + ParallelLMHead, + VocabParallelEmbedding, +) +from sglang.srt.model_executor.forward_batch_info import ForwardBatch +from sglang.srt.model_loader.weight_utils import default_weight_loader +from sglang.srt.utils import add_prefix, make_layers + + +class Olmo2Attention(nn.Module): + """ + This is the attention block where the output is computed as + ``Attention(LN(x))`` in ``MLP(LN(x + Attention(LN(x))))`` + (plus another skip connection). + """ + + def __init__( + self, + config: PretrainedConfig, + layer_id: int = 0, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ): + super().__init__() + self.config = config + self.hidden_size = config.hidden_size + self.tp_size = get_tensor_model_parallel_world_size() + self.total_num_heads = config.num_attention_heads + + assert self.hidden_size % self.total_num_heads == 0 + assert self.total_num_heads % self.tp_size == 0 + + self.num_heads = self.total_num_heads // self.tp_size + self.total_num_kv_heads = self.config.num_key_value_heads + + if self.total_num_kv_heads >= self.tp_size: + # Number of KV heads is greater than TP size, so we partition + # the KV heads across multiple tensor parallel GPUs. + assert self.total_num_kv_heads % self.tp_size == 0 + else: + # Number of KV heads is less than TP size, so we replicate + # the KV heads across multiple tensor parallel GPUs. + assert self.tp_size % self.total_num_kv_heads == 0 + self.num_kv_heads = max(1, self.total_num_kv_heads // self.tp_size) + + self.head_dim = self.hidden_size // self.total_num_heads + self.max_position_embeddings = config.max_position_embeddings + self.rope_theta = config.rope_theta + + # Attention input projection. Projects x -> (q, k, v) + self.qkv_proj = QKVParallelLinear( + self.hidden_size, + self.head_dim, + self.total_num_heads, + bias=config.attention_bias, + quant_config=quant_config, + prefix=add_prefix("qkv_proj", prefix), + ) + self.tp_rank = get_tensor_model_parallel_rank() + + self.k_norm = RMSNorm( + self.total_num_kv_heads * self.head_dim, + eps=self.config.rms_norm_eps, + ) + self.q_norm = RMSNorm(self.config.hidden_size, eps=self.config.rms_norm_eps) + # Rotary embeddings. + self.rotary_emb = get_rope( + self.head_dim, + rotary_dim=self.head_dim, + max_position=self.max_position_embeddings, + base=self.rope_theta, + ) + self.scaling = self.head_dim**-0.5 + self.attn = RadixAttention( + self.num_heads, + self.head_dim, + self.scaling, + num_kv_heads=self.num_kv_heads, + layer_id=layer_id, + quant_config=quant_config, + prefix=add_prefix("attn", prefix), + ) + + # Attention output projection. + self.o_proj = RowParallelLinear( + self.head_dim * self.total_num_heads, + self.hidden_size, + bias=config.attention_bias, + quant_config=quant_config, + prefix=add_prefix("o_proj", prefix), + ) + + def _apply_qk_norm( + self, q: torch.Tensor, k: torch.Tensor + ) -> Tuple[torch.Tensor, torch.Tensor]: + if self.tp_size > 1: + q = tensor_model_parallel_all_gather(q.contiguous()) + k = tensor_model_parallel_all_gather(k.contiguous()) + q = self.q_norm.forward_native(q) + k = self.k_norm.forward_native(k) + if self.tp_size > 1: + splitter = partial(split_tensor_along_last_dim, num_partitions=self.tp_size) + q = splitter(q)[self.tp_rank] + k = splitter(k)[self.tp_rank] + return q, k + + def forward( + self, + positions: torch.Tensor, + hidden_states: torch.Tensor, + forward_batch: ForwardBatch, + ) -> torch.Tensor: + qkv, _ = self.qkv_proj(hidden_states) + q, k, v = qkv.chunk(chunks=3, dim=-1) + q, k = self._apply_qk_norm(q, k) + q, k = self.rotary_emb(positions, q, k) + attn_output = self.attn(q, k, v, forward_batch) + output, _ = self.o_proj(attn_output) + return output + + +class Olmo2MLP(nn.Module): + """ + This is the MLP block where the output is computed as + ``MLP(x)`` in ``LN(MLP(x + LN(Attention(x))))`` + (plus another skip connection). + """ + + def __init__( + self, + config: PretrainedConfig, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ): + super().__init__() + self.config = config + self.hidden_size = config.hidden_size + self.intermediate_size = config.intermediate_size + + # Feed-forward input projection. + self.gate_up_proj = MergedColumnParallelLinear( + self.hidden_size, + [self.intermediate_size] * 2, + bias=False, + quant_config=quant_config, + prefix=add_prefix("gate_up_proj", prefix), + ) + + # Activation function. + self.act_fn = SiluAndMul() + + # Feed-forward output projection. + self.down_proj = RowParallelLinear( + self.intermediate_size, + self.hidden_size, + bias=False, + quant_config=quant_config, + prefix=add_prefix("down_proj", prefix), + ) + + def forward( + self, + x: torch.Tensor, + ) -> torch.Tensor: + gate_up, _ = self.gate_up_proj(x) + x = self.act_fn(gate_up) + x, _ = self.down_proj(x) + return x + + +class Olmo2DecoderLayer(nn.Module): + """ + This is a typical transformer block where the output is + computed as ``MLP(LN(x + Attention(LN(x))))`` + (plus another skip connection). + """ + + def __init__( + self, + config: PretrainedConfig, + layer_id: int = 0, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ): + super().__init__() + # Attention block. + self.self_attn = Olmo2Attention( + config, layer_id, quant_config, prefix=add_prefix("self_attn", prefix) + ) + + # MLP block. + self.mlp = Olmo2MLP(config, quant_config, prefix=add_prefix("mlp", prefix)) + + # RMSNorm + self.post_attention_layernorm = RMSNorm( + config.hidden_size, eps=config.rms_norm_eps + ) + + self.post_feedforward_layernorm = RMSNorm( + config.hidden_size, eps=config.rms_norm_eps + ) + + def forward( + self, + positions: torch.Tensor, + hidden_states: torch.Tensor, + forward_batch: ForwardBatch, + ) -> torch.Tensor: + # Attention block. + residual = hidden_states + hidden_states = self.self_attn(positions, hidden_states, forward_batch) + hidden_states = self.post_attention_layernorm(hidden_states) + hidden_states = hidden_states + residual + + # MLP block. + residual = hidden_states + hidden_states = self.mlp(hidden_states) + hidden_states = self.post_feedforward_layernorm(hidden_states) + hidden_states = residual + hidden_states + return hidden_states + + +class Olmo2Model(nn.Module): + + def __init__( + self, + config: PretrainedConfig, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ): + super().__init__() + self.config = config + + self.embed_tokens = VocabParallelEmbedding( + config.vocab_size, + config.hidden_size, + prefix=add_prefix("embed_tokens", prefix), + ) + self.layers = make_layers( + config.num_hidden_layers, + lambda idx, prefix: Olmo2DecoderLayer( + layer_id=idx, + config=config, + quant_config=quant_config, + prefix=prefix, + ), + prefix=add_prefix("layers", prefix), + ) + self.norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps) + + def forward( + self, + input_ids: torch.Tensor, + positions: torch.Tensor, + forward_batch: ForwardBatch, + input_embeds: torch.Tensor = None, + ) -> torch.Tensor: + """ + :param input_ids: A tensor of shape `(batch_size, seq_len)`. + """ + # Get embeddings of input. + # shape: (batch_size, seq_len, d_model) + + if input_embeds is None: + hidden_states = self.embed_tokens(input_ids) + else: + hidden_states = input_embeds + + # Apply blocks one-by-one. + for layer_id, decoder_layer in enumerate(self.layers): + # shape: (batch_size, seq_len, d_model) + hidden_states = decoder_layer( + positions, + hidden_states, + forward_batch, + ) + + # Apply final layer norm. + # shape: (batch_size, seq_len or 1, d_model) + hidden_states = self.norm(hidden_states) + return hidden_states + + +class Olmo2ForCausalLM(nn.Module): + """ + Extremely barebones HF model wrapper. + """ + + def __init__( + self, + config: PretrainedConfig, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ): + super().__init__() + self.config = config + self.model = Olmo2Model( + config, quant_config, prefix=add_prefix("model", prefix) + ) + if config.tie_word_embeddings: + self.lm_head = self.model.embed_tokens + else: + self.unpadded_vocab_size = config.vocab_size + self.lm_head = ParallelLMHead( + self.unpadded_vocab_size, + config.hidden_size, + org_num_embeddings=config.vocab_size, + quant_config=quant_config, + prefix=add_prefix("lm_head", prefix), + ) + self.logits_processor = LogitsProcessor(config) + + def forward( + self, + input_ids: torch.Tensor, + positions: torch.Tensor, + forward_batch: ForwardBatch, + input_embeds: torch.Tensor = None, + ) -> torch.Tensor: + hidden_states = self.model( + input_ids=input_ids, + positions=positions, + forward_batch=forward_batch, + input_embeds=input_embeds, + ) + return self.logits_processor( + input_ids, hidden_states, self.lm_head, forward_batch + ) + + def load_weights(self, weights: Iterable[Tuple[str, torch.Tensor]]): + stacked_params_mapping = [ + # (param_name, shard_name, shard_id) + ("qkv_proj", "q_proj", "q"), + ("qkv_proj", "k_proj", "k"), + ("qkv_proj", "v_proj", "v"), + ("gate_up_proj", "gate_proj", 0), + ("gate_up_proj", "up_proj", 1), + ] + params_dict = dict(self.named_parameters(remove_duplicate=False)) + for name, loaded_weight in weights: + if "rotary_emb.inv_freq" in name: + continue + if "rotary_emb.cos_cached" in name or "rotary_emb.sin_cached" in name: + # Models trained using ColossalAI may include these tensors in + # the checkpoint. Skip them. + continue + # With tie_word_embeddings, we can skip lm_head.weight + # The weight might appear unnecessarily in the files if the model is + # processed with quantization, LoRA, fine-tuning, etc. + if self.config.tie_word_embeddings and "lm_head.weight" in name: + continue + for param_name, weight_name, shard_id in stacked_params_mapping: + if weight_name not in name: + continue + name = name.replace(weight_name, param_name) + # Skip loading extra bias for GPTQ models. + if name.endswith(".bias") and name not in params_dict: + continue + param = params_dict[name] + weight_loader = param.weight_loader + weight_loader(param, loaded_weight, shard_id) + break + else: + # Skip loading extra bias for GPTQ models. + if name.endswith(".bias") and name not in params_dict: + continue + param = params_dict[name] + weight_loader = getattr(param, "weight_loader", default_weight_loader) + weight_loader(param, loaded_weight) + + +EntryClass = Olmo2ForCausalLM diff --git a/lib/python3.12/site-packages/sglang/srt/models/phi3_small.py b/lib/python3.12/site-packages/sglang/srt/models/phi3_small.py new file mode 100644 index 0000000000000000000000000000000000000000..c59d296a65d882ee367c4f92858fff5baefbfd38 --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/models/phi3_small.py @@ -0,0 +1,462 @@ +import math +from typing import Iterable, Optional, Tuple, Union + +import torch +from torch import nn +from transformers import Phi3Config +from transformers.configuration_utils import PretrainedConfig + +from sglang.srt.distributed import get_tensor_model_parallel_world_size +from sglang.srt.layers.linear import ( + MergedColumnParallelLinear, + QKVParallelLinear, + RowParallelLinear, +) +from sglang.srt.layers.logits_processor import LogitsProcessor, LogitsProcessorOutput +from sglang.srt.layers.pooler import Pooler, PoolingType +from sglang.srt.layers.quantization.base_config import QuantizationConfig +from sglang.srt.layers.radix_attention import RadixAttention +from sglang.srt.layers.rotary_embedding import get_rope +from sglang.srt.layers.vocab_parallel_embedding import ( + DEFAULT_VOCAB_PADDING_SIZE, + ParallelLMHead, + VocabParallelEmbedding, +) +from sglang.srt.model_executor.forward_batch_info import ForwardBatch +from sglang.srt.model_loader.weight_utils import default_weight_loader +from sglang.srt.utils import add_prefix, make_layers + + +@torch.jit.script +def quick_gelu(x): + return x * torch.sigmoid(1.702 * x) + + +@torch.jit.script +def gegelu(input, limit: Optional[float] = None): + a_gelu, a_linear = input[..., ::2], input[..., 1::2] + if limit is not None: + a_gelu = torch.where( + torch.isinf(a_gelu), a_gelu, a_gelu.clamp(min=None, max=limit) + ) + a_linear = torch.where( + torch.isinf(a_linear), + a_linear, + a_linear.clamp(min=-limit, max=limit), + ) + out_gelu = quick_gelu(a_gelu) + return out_gelu * (a_linear + 1) + + +class Phi3SmallMLP(nn.Module): + + def __init__( + self, + config: PretrainedConfig, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ) -> None: + super().__init__() + self.config = config + assert ( + self.config.hidden_act == "gegelu" + ), "Only `gegelu` is supported for the 4.7 series of models .." + self.hidden_size = config.hidden_size + self.gegelu_limit = config.gegelu_limit + self.intermediate_size = config.intermediate_size + + self.up_proj = MergedColumnParallelLinear( + self.hidden_size, + 2 * [self.intermediate_size], + bias=True, + quant_config=quant_config, + prefix=add_prefix("up_proj", prefix), + ) + self.down_proj = RowParallelLinear( + self.intermediate_size, + self.hidden_size, + bias=True, + quant_config=quant_config, + prefix=add_prefix("down_proj", prefix), + ) + + def forward(self, x): + gate_up, _ = self.up_proj(x) + x = gegelu(gate_up) + x, _ = self.down_proj(x) + return x + + +class Phi3SmallSelfAttention(nn.Module): + + def __init__( + self, + config: PretrainedConfig, + layer_id: int = 0, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ) -> None: + super().__init__() + self.layer_id = layer_id + self.config = config + self.sparse_block_size = config.blocksparse_block_size + self.homo_heads = config.blocksparse_homo_head_pattern + self.local_blocks = config.blocksparse_num_local_blocks + self.vert_stride = config.blocksparse_vert_stride + + assert ( + config.blocksparse_block_size == config.blocksparse_triton_kernel_block_size + ) + + self.hidden_size = config.hidden_size + # Number of Query Heads + self.num_heads = config.num_attention_heads + + self.head_dim = self.hidden_size // self.num_heads + self.tp_size = get_tensor_model_parallel_world_size() + # Number of total Key Value Heads before tensor parallel + self.num_key_value_heads = config.num_key_value_heads + self.num_q_per_kv = self.num_heads // self.num_key_value_heads + if self.tp_size > 1: + assert self.num_key_value_heads % self.tp_size == 0 + self.num_kv_heads_per_partion = max(1, self.num_key_value_heads // self.tp_size) + self.num_heads_per_partition = self.num_heads // self.tp_size + + self.max_position_embeddings = config.max_position_embeddings + self.rope_embedding_base = config.rope_embedding_base + self.rope_position_scale = config.rope_position_scale + self.is_causal = True + + norm_factor = None + if config.mup_use_scaling: + norm_factor = self.head_dim / config.mup_attn_multiplier + else: + norm_factor = math.sqrt(self.head_dim) + self.scale = 1 / norm_factor + + self.query_key_value = QKVParallelLinear( + self.hidden_size, + self.head_dim, + self.num_heads, + self.num_key_value_heads, + bias=True, + quant_config=quant_config, + prefix=add_prefix("qkv_proj", prefix), + ) + + self.dense = RowParallelLinear( + self.hidden_size, + self.hidden_size, + bias=True, + quant_config=quant_config, + prefix=add_prefix("o_proj", prefix), + ) + + if getattr(self.config, "rope_scaling", None) is not None: + rope_scaling = self.config.rope_scaling + for key in rope_scaling: + if isinstance(rope_scaling[key], list): + rope_scaling[key] = tuple(rope_scaling[key]) + + if "factor" not in rope_scaling: + rope_scaling["factor"] = self.rope_position_scale + else: + rope_scaling = { + "rope_type": "linear", + "factor": self.rope_position_scale, + } + + self.rotary_emb = get_rope( + self.head_dim, + rotary_dim=self.head_dim, + max_position=self.max_position_embeddings, + base=self.rope_embedding_base, + rope_scaling=rope_scaling, + ) + + # blocksparse params + self.blocksparse_block_size = config.blocksparse_block_size + self.blocksparse_num_local_blocks = config.blocksparse_num_local_blocks + self.blocksparse_vert_stride = config.blocksparse_vert_stride + + use_dense_attn = ( + getattr(self.config, "dense_attention_every_n_layers", None) + and (self.layer_id + 1) % self.config.dense_attention_every_n_layers == 0 + ) + + bs_params = None + if not use_dense_attn: + bs_params = { + "max_seqlen": self.max_position_embeddings, + "num_heads": self.num_heads_per_partition, + "num_kv_heads": self.num_kv_heads_per_partion, + "block_size": self.sparse_block_size, + "local_blocks": self.local_blocks, + "vert_stride": self.vert_stride, + "homo_head": self.homo_heads, + } + + self.attn = RadixAttention( + self.num_heads_per_partition, + self.head_dim, + self.scale, + num_kv_heads=self.num_kv_heads_per_partion, + layer_id=layer_id, + quant_config=quant_config, + prefix=add_prefix("attn", prefix), + ) + + def forward( + self, + positions: torch.Tensor, + hidden_states: torch.Tensor, + forward_batch: ForwardBatch, + ) -> Tuple[torch.Tensor, Optional[torch.Tensor], Optional[Tuple[torch.Tensor]]]: + qkv, _ = self.query_key_value(hidden_states) + + qkv = qkv.view(qkv.shape[:-1] + (-1, (self.num_q_per_kv + 2), self.head_dim)) + q, k, v = qkv.split([self.num_q_per_kv, 1, 1], dim=-2) + + # NOTE: this is required by RotaryEmbed, which indeed does not have to + # TODO: allow 3D QK for rotary forward + q = q.reshape(-1, self.head_dim * self.num_heads_per_partition) + k = k.reshape(-1, self.head_dim * self.num_kv_heads_per_partion) + v = v.reshape(-1, self.head_dim * self.num_kv_heads_per_partion) + + q, k = self.rotary_emb(positions, q, k) + attn_output = self.attn(q, k, v, forward_batch=forward_batch) + output, _ = self.dense(attn_output) + + return output + + +class Phi3SmallDecoderLayer(nn.Module): + + def __init__( + self, + config: PretrainedConfig, + layer_id: int, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ): + super().__init__() + self.hidden_size = config.hidden_size + self.self_attn = Phi3SmallSelfAttention( + config, + layer_id, + quant_config=quant_config, + prefix=add_prefix("self_attn", prefix), + ) + self.mlp = Phi3SmallMLP( + config, + quant_config, + prefix=add_prefix("mlp", prefix), + ) + + self.input_layernorm = nn.LayerNorm( + config.hidden_size, eps=config.layer_norm_epsilon + ) + self.post_attention_layernorm = nn.LayerNorm( + config.hidden_size, eps=config.layer_norm_epsilon + ) + + def forward( + self, + positions: torch.Tensor, + hidden_states: torch.Tensor, + forward_batch: ForwardBatch, + ) -> torch.Tensor: + residual = hidden_states + hidden_states = self.input_layernorm(hidden_states) + + hidden_states = self.self_attn( + positions=positions, + hidden_states=hidden_states, + forward_batch=forward_batch, + ) + hidden_states = residual + hidden_states + + residual = hidden_states + hidden_states = self.post_attention_layernorm(hidden_states) + hidden_states = self.mlp(hidden_states) + hidden_states = residual + hidden_states + return hidden_states + + +class Phi3SmallModel(nn.Module): + + def __init__( + self, + config: Phi3Config, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ): + super().__init__() + + self.config = config + self.embed_tokens = VocabParallelEmbedding( + config.vocab_size, + config.hidden_size, + prefix=add_prefix("embed_tokens", prefix), + ) + self.mup_embedding_multiplier = config.mup_embedding_multiplier + self.start_layer, self.end_layer, self.layers = make_layers( + config.num_hidden_layers, + lambda idx, prefix: Phi3SmallDecoderLayer( + config, + int(prefix.split(".")[-1]), + quant_config, + prefix=prefix, + ), + prefix=add_prefix("layers", prefix), + ) + + self.final_layernorm = nn.LayerNorm( + config.hidden_size, eps=config.layer_norm_epsilon + ) + + def get_input_embeddings(self, input_ids: torch.Tensor) -> torch.Tensor: + return self.embed_tokens(input_ids) + + def forward( + self, + input_ids: torch.LongTensor, + positions: Optional[torch.LongTensor], + forward_batch: ForwardBatch, + inputs_embeds: Optional[torch.Tensor], + ) -> Union[torch.Tensor]: + + if inputs_embeds is not None: + hidden_states = inputs_embeds + else: + hidden_states = self.get_input_embeddings(input_ids) + if ( + self.mup_embedding_multiplier is not None + and self.mup_embedding_multiplier > 0.0 + ): + hidden_states = hidden_states * self.mup_embedding_multiplier + + for i in range(self.start_layer, self.end_layer): + layer = self.layers[i] + hidden_states = layer(positions, hidden_states, forward_batch=forward_batch) + + hidden_states = self.final_layernorm(hidden_states) + return hidden_states + + +class Phi3SmallForCausalLM(nn.Module): + _tied_weights_keys = ["lm_head.weight"] + + def __init__( + self, + config: Phi3Config, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ): + + super().__init__() + + self.config = config + self.quant_config = quant_config + self.model = Phi3SmallModel( + config=config, + quant_config=quant_config, + prefix=add_prefix("model", prefix), + ) + self.vocab_size = config.vocab_size + self.mup_width_multiplier = config.mup_width_multiplier + self.lm_head = ParallelLMHead( + self.vocab_size, + config.hidden_size, + org_num_embeddings=config.vocab_size, + padding_size=DEFAULT_VOCAB_PADDING_SIZE, + quant_config=quant_config, + prefix=add_prefix("lm_head", prefix), + ) + if self.config.tie_word_embeddings: + self.lm_head.weight = self.model.embed_tokens.weight + self.logits_processor = LogitsProcessor(config) + self.pooler = Pooler(pooling_type=PoolingType.LAST, normalize=True) + + # tokens in tiktoken but not used + if hasattr(config, "dummy_token_indices"): + device = self.lm_head.weight.device + self.register_buffer( + "dummy_token_indices", + torch.LongTensor(config.dummy_token_indices).to(device), + persistent=False, + ) + else: + self.dummy_token_indices = None + + def get_input_embeddings(self, input_ids: torch.Tensor) -> torch.Tensor: + return self.model.get_input_embeddings(input_ids) + + def set_input_embeddings(self, value): + self.model.embed_tokens = value + + def get_output_embeddings(self): + return self.lm_head + + def set_output_embeddings(self, value): + self.lm_head = value + + def set_decoder(self, decoder): + self.model = decoder + + def get_decoder(self): + return self.model + + def compute_logits( + self, + input_ids: torch.LongTensor, + hidden_states: torch.Tensor, + sampling_metadata, + ) -> Optional[torch.Tensor]: + logits = self.logits_processor( + input_ids, self.lm_head, hidden_states, sampling_metadata + ) + if self.dummy_token_indices is not None and logits is not None: + logits.index_fill_(-1, self.dummy_token_indices, -torch.inf) + return logits + + def forward( + self, + input_ids: torch.LongTensor, + positions: Optional[torch.LongTensor], + forward_batch: ForwardBatch, + inputs_embeds: Optional[torch.Tensor] = None, + get_embedding: bool = False, + ) -> LogitsProcessorOutput: + hidden_states = self.model( + input_ids=input_ids, + positions=positions, + forward_batch=forward_batch, + inputs_embeds=inputs_embeds, + ) + + if not get_embedding: + return self.logits_processor( + input_ids, hidden_states, self.lm_head, forward_batch + ) + + else: + return self.pooler(hidden_states, forward_batch) + + def load_weights(self, weights: Iterable[Tuple[str, torch.Tensor]]): + + params_dict = dict(self.named_parameters()) + for name, loaded_weight in weights: + if "rotary_emb.inv_freq" in name: + continue + if name.endswith(".bias") and name not in params_dict: + continue + if self.config.tie_word_embeddings and "lm_head.weight" in name: + continue + + param = params_dict[name] + weight_loader = getattr(param, "weight_loader", default_weight_loader) + weight_loader(param, loaded_weight) + + +EntryClass = Phi3SmallForCausalLM diff --git a/lib/python3.12/site-packages/sglang/srt/models/qwen.py b/lib/python3.12/site-packages/sglang/srt/models/qwen.py new file mode 100644 index 0000000000000000000000000000000000000000..f0660f62da6da8e9943965a9770b4859e24ba1a3 --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/models/qwen.py @@ -0,0 +1,319 @@ +# Copyright 2023-2024 SGLang Team +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +# Adapted from +# https://github.com/vllm-project/vllm/blob/c7f2cf2b7f67bce5842fedfdba508440fe257375/vllm/model_executor/models/qwen.py#L1 + +from typing import Any, Dict, Iterable, Optional, Tuple + +import torch +from torch import nn +from transformers import PretrainedConfig + +from sglang.srt.distributed import get_tensor_model_parallel_world_size +from sglang.srt.layers.activation import SiluAndMul +from sglang.srt.layers.layernorm import RMSNorm +from sglang.srt.layers.linear import ( + MergedColumnParallelLinear, + QKVParallelLinear, + RowParallelLinear, +) +from sglang.srt.layers.logits_processor import LogitsProcessor +from sglang.srt.layers.quantization.base_config import QuantizationConfig +from sglang.srt.layers.radix_attention import RadixAttention +from sglang.srt.layers.rotary_embedding import get_rope +from sglang.srt.layers.vocab_parallel_embedding import ( + ParallelLMHead, + VocabParallelEmbedding, +) +from sglang.srt.model_executor.forward_batch_info import ForwardBatch +from sglang.srt.model_loader.weight_utils import default_weight_loader +from sglang.srt.utils import add_prefix + + +class QWenMLP(nn.Module): + def __init__( + self, + hidden_size: int, + intermediate_size: int, + hidden_act: str = "silu", + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ): + super().__init__() + self.gate_up_proj = MergedColumnParallelLinear( + hidden_size, + 2 * [intermediate_size], + bias=False, + gather_output=False, + quant_config=quant_config, + prefix=add_prefix("gate_up_proj", prefix), + ) + self.c_proj = RowParallelLinear( + intermediate_size, + hidden_size, + bias=False, + input_is_parallel=True, + quant_config=quant_config, + prefix=add_prefix("c_proj", prefix), + ) + if hidden_act != "silu": + raise ValueError( + f"Unsupported activation: {hidden_act}. " + "Only silu is supported for now." + ) + self.act_fn = SiluAndMul() + + def forward(self, x): + gate_up, _ = self.gate_up_proj(x) + x = self.act_fn(gate_up) + x, _ = self.c_proj(x) + return x + + +class QWenAttention(nn.Module): + def __init__( + self, + hidden_size: int, + num_heads: int, + max_position_embeddings: int, + layer_id: int = 0, + rope_theta: float = 10000, + rope_scaling: Optional[Dict[str, Any]] = None, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ): + super().__init__() + self.hidden_size = hidden_size + tensor_model_parallel_world_size = get_tensor_model_parallel_world_size() + self.total_num_heads = num_heads + assert self.total_num_heads % tensor_model_parallel_world_size == 0 + self.num_heads = self.total_num_heads // tensor_model_parallel_world_size + self.head_dim = hidden_size // self.total_num_heads + + # pylint: disable=invalid-name + self.c_attn = QKVParallelLinear( + hidden_size, + self.head_dim, + self.total_num_heads, + bias=True, + quant_config=quant_config, + prefix=add_prefix("c_attn", prefix), + ) + self.c_proj = RowParallelLinear( + self.total_num_heads * self.head_dim, + hidden_size, + bias=False, + input_is_parallel=True, + quant_config=quant_config, + prefix=add_prefix("c_proj", prefix), + ) + self.rotary_emb = get_rope( + self.head_dim, + rotary_dim=self.head_dim, + max_position=max_position_embeddings, + base=rope_theta, + rope_scaling=rope_scaling, + ) + self.scaling = self.head_dim**-0.5 + self.attn = RadixAttention( + self.num_heads, + self.head_dim, + self.scaling, + num_kv_heads=self.num_heads, + layer_id=layer_id, + quant_config=quant_config, + prefix=add_prefix("attn", prefix), + ) + + def forward( + self, + positions: torch.Tensor, + hidden_states: torch.Tensor, + forward_batch: ForwardBatch, + ) -> torch.Tensor: + qkv, _ = self.c_attn(hidden_states) + q, k, v = qkv.chunk(chunks=3, dim=-1) + q, k = self.rotary_emb(positions, q, k) + attn_output = self.attn(q, k, v, forward_batch) + output, _ = self.c_proj(attn_output) + return output + + +class QWenBlock(nn.Module): + def __init__( + self, + config: PretrainedConfig, + layer_id, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ): + super().__init__() + self.ln_1 = RMSNorm(config.hidden_size, eps=config.layer_norm_epsilon) + + rope_theta = getattr(config, "rope_theta", 10000) + rope_scaling = getattr(config, "rope_scaling", None) + self.attn = QWenAttention( + config.hidden_size, + config.num_attention_heads, + config.max_position_embeddings, + rope_theta=rope_theta, + rope_scaling=rope_scaling, + layer_id=layer_id, + quant_config=quant_config, + prefix=add_prefix("attn", prefix), + ) + + self.ln_2 = RMSNorm(config.hidden_size, eps=config.layer_norm_epsilon) + + self.mlp = QWenMLP( + config.hidden_size, + config.intermediate_size // 2, + quant_config=quant_config, + prefix=add_prefix("mlp", prefix), + ) + + def forward( + self, + positions: torch.Tensor, + hidden_states: torch.Tensor, + forward_batch: ForwardBatch, + ) -> torch.Tensor: + # Self Attention + residual = hidden_states + hidden_states = self.ln_1(hidden_states) + hidden_states = self.attn( + positions=positions, + hidden_states=hidden_states, + forward_batch=forward_batch, + ) + hidden_states = residual + hidden_states + + # Fully Connected + residual = hidden_states + hidden_states = self.ln_2(hidden_states) + hidden_states = self.mlp(hidden_states) + hidden_states = residual + hidden_states + return hidden_states + + +class QWenModel(nn.Module): + def __init__( + self, + config: PretrainedConfig, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ): + super().__init__() + self.config = config + self.vocab_size = config.vocab_size + + vocab_size = ((config.vocab_size + 63) // 64) * 64 + self.wte = VocabParallelEmbedding( + vocab_size, + config.hidden_size, + prefix=add_prefix("wte", prefix), + ) + self.h = nn.ModuleList( + [ + QWenBlock( + config, + i, + quant_config=quant_config, + prefix=add_prefix(f"h.{i}", prefix), + ) + for i in range(config.num_hidden_layers) + ] + ) + self.ln_f = RMSNorm(config.hidden_size, eps=config.layer_norm_epsilon) + + def forward( + self, + input_ids: torch.Tensor, + positions: torch.Tensor, + forward_batch: ForwardBatch, + ) -> torch.Tensor: + hidden_states = self.wte(input_ids) + for i in range(len(self.h)): + layer = self.h[i] + hidden_states = layer( + positions, + hidden_states, + forward_batch, + ) + hidden_states = self.ln_f(hidden_states) + return hidden_states + + +class QWenLMHeadModel(nn.Module): + def __init__( + self, + config: PretrainedConfig, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ): + super().__init__() + self.config = config + self.transformer = QWenModel( + config, quant_config=quant_config, prefix=add_prefix("transformer", prefix) + ) + vocab_size = ((config.vocab_size + 63) // 64) * 64 + self.lm_head = ParallelLMHead( + vocab_size, config.hidden_size, prefix=add_prefix("lm_head", prefix) + ) + self.logits_processor = LogitsProcessor(config) + + @torch.no_grad() + def forward( + self, + input_ids: torch.Tensor, + positions: torch.Tensor, + forward_batch: ForwardBatch, + ): + hidden_states = self.transformer(input_ids, positions, forward_batch) + return self.logits_processor( + input_ids, hidden_states, self.lm_head, forward_batch + ) + + def load_weights(self, weights: Iterable[Tuple[str, torch.Tensor]]): + stacked_params_mapping = [ + # (param_name, shard_name, shard_id) + ("gate_up_proj", "w2", 0), + ("gate_up_proj", "w1", 1), + ] + params_dict = dict(self.named_parameters()) + for name, loaded_weight in weights: + if "rotary_emb.inv_freq" in name: + continue + for param_name, weight_name, shard_id in stacked_params_mapping: + if weight_name not in name: + continue + name = name.replace(weight_name, param_name) + # Skip loading extra bias for GPTQ models. + if name.endswith(".bias") and name not in params_dict: + continue + param = params_dict[name] + weight_loader = param.weight_loader + weight_loader(param, loaded_weight, shard_id) + break + else: + # Skip loading extra bias for GPTQ models. + if name.endswith(".bias") and name not in params_dict: + continue + param = params_dict[name] + weight_loader = getattr(param, "weight_loader", default_weight_loader) + weight_loader(param, loaded_weight) + + +EntryClass = QWenLMHeadModel diff --git a/lib/python3.12/site-packages/sglang/srt/models/qwen2.py b/lib/python3.12/site-packages/sglang/srt/models/qwen2.py new file mode 100644 index 0000000000000000000000000000000000000000..4b518eccc4b154be2f78548a0786ae68d9b80601 --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/models/qwen2.py @@ -0,0 +1,448 @@ +# Copyright 2023-2024 SGLang Team +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +# Adapted from llama2.py +# Modify details for the adaptation of Qwen2 model. +"""Inference-only Qwen2 model compatible with HuggingFace weights.""" +from typing import Any, Dict, Iterable, Optional, Tuple + +import torch +from torch import nn + +from sglang.srt.distributed import ( + get_tensor_model_parallel_rank, + get_tensor_model_parallel_world_size, +) +from sglang.srt.layers.activation import SiluAndMul +from sglang.srt.layers.layernorm import RMSNorm +from sglang.srt.layers.linear import ( + MergedColumnParallelLinear, + QKVParallelLinear, + RowParallelLinear, +) +from sglang.srt.layers.logits_processor import LogitsProcessor +from sglang.srt.layers.pooler import Pooler, PoolingType +from sglang.srt.layers.quantization.base_config import QuantizationConfig +from sglang.srt.layers.radix_attention import RadixAttention +from sglang.srt.layers.rotary_embedding import get_rope +from sglang.srt.layers.vocab_parallel_embedding import ( + ParallelLMHead, + VocabParallelEmbedding, +) +from sglang.srt.model_executor.forward_batch_info import ForwardBatch +from sglang.srt.model_loader.weight_utils import ( + default_weight_loader, + kv_cache_scales_loader, +) +from sglang.srt.utils import add_prefix, make_layers + +Qwen2Config = None + + +class Qwen2MLP(nn.Module): + def __init__( + self, + hidden_size: int, + intermediate_size: int, + hidden_act: str, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ) -> None: + super().__init__() + self.gate_up_proj = MergedColumnParallelLinear( + hidden_size, + [intermediate_size] * 2, + bias=False, + quant_config=quant_config, + prefix=add_prefix("gate_up_proj", prefix), + ) + self.down_proj = RowParallelLinear( + intermediate_size, + hidden_size, + bias=False, + quant_config=quant_config, + prefix=add_prefix("down_proj", prefix), + ) + if hidden_act != "silu": + raise ValueError( + f"Unsupported activation: {hidden_act}. " + "Only silu is supported for now." + ) + self.act_fn = SiluAndMul() + + def forward(self, x): + gate_up, _ = self.gate_up_proj(x) + x = self.act_fn(gate_up) + x, _ = self.down_proj(x) + return x + + +class Qwen2Attention(nn.Module): + def __init__( + self, + hidden_size: int, + num_heads: int, + num_kv_heads: int, + layer_id: int = 0, + rope_theta: float = 1000000, + rope_scaling: Optional[Dict[str, Any]] = None, + max_position_embeddings: int = 32768, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ) -> None: + super().__init__() + self.hidden_size = hidden_size + tp_size = get_tensor_model_parallel_world_size() + self.total_num_heads = num_heads + assert self.total_num_heads % tp_size == 0 + self.num_heads = self.total_num_heads // tp_size + self.total_num_kv_heads = num_kv_heads + if self.total_num_kv_heads >= tp_size: + # Number of KV heads is greater than TP size, so we partition + # the KV heads across multiple tensor parallel GPUs. + assert self.total_num_kv_heads % tp_size == 0 + else: + # Number of KV heads is less than TP size, so we replicate + # the KV heads across multiple tensor parallel GPUs. + assert tp_size % self.total_num_kv_heads == 0 + self.num_kv_heads = max(1, self.total_num_kv_heads // tp_size) + self.head_dim = hidden_size // self.total_num_heads + self.q_size = self.num_heads * self.head_dim + self.kv_size = self.num_kv_heads * self.head_dim + self.scaling = self.head_dim**-0.5 + self.rope_theta = rope_theta + self.max_position_embeddings = max_position_embeddings + + self.qkv_proj = QKVParallelLinear( + hidden_size, + self.head_dim, + self.total_num_heads, + self.total_num_kv_heads, + bias=True, + quant_config=quant_config, + prefix=add_prefix("qkv_proj", prefix), + ) + self.o_proj = RowParallelLinear( + self.total_num_heads * self.head_dim, + hidden_size, + bias=False, + quant_config=quant_config, + prefix=add_prefix("o_proj", prefix), + ) + + self.rotary_emb = get_rope( + self.head_dim, + rotary_dim=self.head_dim, + max_position=max_position_embeddings, + base=rope_theta, + rope_scaling=rope_scaling, + ) + self.attn = RadixAttention( + self.num_heads, + self.head_dim, + self.scaling, + num_kv_heads=self.num_kv_heads, + layer_id=layer_id, + quant_config=quant_config, + prefix=add_prefix("attn", prefix), + ) + + def forward( + self, + positions: torch.Tensor, + hidden_states: torch.Tensor, + forward_batch: ForwardBatch, + ) -> torch.Tensor: + qkv, _ = self.qkv_proj(hidden_states) + q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1) + q, k = self.rotary_emb(positions, q, k) + attn_output = self.attn(q, k, v, forward_batch) + output, _ = self.o_proj(attn_output) + return output + + +class Qwen2DecoderLayer(nn.Module): + def __init__( + self, + config: Qwen2Config, + layer_id: int = 0, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ) -> None: + super().__init__() + self.hidden_size = config.hidden_size + rope_theta = getattr(config, "rope_theta", 1000000) + rope_scaling = getattr(config, "rope_scaling", None) + max_position_embeddings = getattr(config, "max_position_embeddings", 32768) + self.self_attn = Qwen2Attention( + hidden_size=self.hidden_size, + num_heads=config.num_attention_heads, + num_kv_heads=config.num_key_value_heads, + layer_id=layer_id, + rope_theta=rope_theta, + rope_scaling=rope_scaling, + max_position_embeddings=max_position_embeddings, + quant_config=quant_config, + prefix=add_prefix("self_attn", prefix), + ) + self.mlp = Qwen2MLP( + hidden_size=self.hidden_size, + intermediate_size=config.intermediate_size, + hidden_act=config.hidden_act, + quant_config=quant_config, + prefix=add_prefix("mlp", prefix), + ) + self.input_layernorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps) + self.post_attention_layernorm = RMSNorm( + config.hidden_size, eps=config.rms_norm_eps + ) + + def forward( + self, + positions: torch.Tensor, + hidden_states: torch.Tensor, + forward_batch: ForwardBatch, + residual: Optional[torch.Tensor], + ) -> Tuple[torch.Tensor, torch.Tensor]: + # Self Attention + if residual is None: + residual = hidden_states + hidden_states = self.input_layernorm(hidden_states) + else: + hidden_states, residual = self.input_layernorm(hidden_states, residual) + hidden_states = self.self_attn( + positions=positions, + hidden_states=hidden_states, + forward_batch=forward_batch, + ) + + # Fully Connected + hidden_states, residual = self.post_attention_layernorm(hidden_states, residual) + hidden_states = self.mlp(hidden_states) + return hidden_states, residual + + +class Qwen2Model(nn.Module): + def __init__( + self, + config: Qwen2Config, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + decoder_layer_type: type[nn.Module] = Qwen2DecoderLayer, + ) -> None: + super().__init__() + self.config = config + self.padding_idx = config.pad_token_id + self.vocab_size = config.vocab_size + self.embed_tokens = VocabParallelEmbedding( + config.vocab_size, + config.hidden_size, + quant_config=quant_config, + prefix=add_prefix("embed_tokens", prefix), + ) + # Use the provided decoder layer type or default to Qwen2DecoderLayer + decoder_layer_type = decoder_layer_type or Qwen2DecoderLayer + self.layers = make_layers( + config.num_hidden_layers, + lambda idx, prefix: decoder_layer_type( + layer_id=idx, + config=config, + quant_config=quant_config, + prefix=prefix, + ), + prefix=add_prefix("layers", prefix), + ) + self.norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps) + + def get_input_embedding(self, input_ids: torch.Tensor) -> torch.Tensor: + if hasattr(self.config, "scale_emb"): + return self.get_input_embeddings()(input_ids) * self.config.scale_emb + else: + return self.get_input_embeddings()(input_ids) + + def get_input_embeddings(self) -> nn.Embedding: + return self.embed_tokens + + def forward( + self, + input_ids: torch.Tensor, + positions: torch.Tensor, + forward_batch: ForwardBatch, + input_embeds: torch.Tensor = None, + ) -> torch.Tensor: + if input_embeds is None: + hidden_states = self.embed_tokens(input_ids) + else: + hidden_states = input_embeds + residual = None + for i in range(len(self.layers)): + layer = self.layers[i] + hidden_states, residual = layer( + positions, + hidden_states, + forward_batch, + residual, + ) + hidden_states, _ = self.norm(hidden_states, residual) + return hidden_states + + # If this function is called, it should always initialize KV cache scale + # factors (or else raise an exception). Thus, handled exceptions should + # make sure to leave KV cache scale factors in a known good (dummy) state + def load_kv_cache_scales(self, quantization_param_path: str) -> None: + tp_size = get_tensor_model_parallel_world_size() + tp_rank = get_tensor_model_parallel_rank() + for layer_idx, scaling_factor in kv_cache_scales_loader( + quantization_param_path, + tp_rank, + tp_size, + self.config.num_hidden_layers, + self.config.__class__.model_type, + ): + if not isinstance(self.layers[layer_idx], nn.Identity): + layer_self_attn = self.layers[layer_idx].self_attn + if hasattr(layer_self_attn.attn, "k_scale"): + layer_self_attn.attn.k_scale = scaling_factor + layer_self_attn.attn.v_scale = scaling_factor + else: + raise RuntimeError( + "Self attention has no KV cache scaling " "factor attribute!" + ) + + +class Qwen2ForCausalLM(nn.Module): + # BitandBytes specific attributes + default_bitsandbytes_target_modules = [ + ".gate_proj.", + ".down_proj.", + ".up_proj.", + ".q_proj.", + ".k_proj.", + ".v_proj.", + ".o_proj.", + ] + bitsandbytes_stacked_params_mapping = { + # shard_name, weight_name, index + "q_proj": ("qkv_proj", 0), + "k_proj": ("qkv_proj", 1), + "v_proj": ("qkv_proj", 2), + "gate_proj": ("gate_up_proj", 0), + "up_proj": ("gate_up_proj", 1), + } + + def __init__( + self, + config: Qwen2Config, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ) -> None: + super().__init__() + self.config = config + self.quant_config = quant_config + self.model = Qwen2Model( + config, quant_config=quant_config, prefix=add_prefix("model", prefix) + ) + if config.tie_word_embeddings: + self.lm_head = self.model.embed_tokens + else: + self.lm_head = ParallelLMHead( + config.vocab_size, + config.hidden_size, + quant_config=quant_config, + prefix=add_prefix("lm_head", prefix), + ) + self.logits_processor = LogitsProcessor(config) + self.pooler = Pooler(pooling_type=PoolingType.LAST, normalize=True) + + def get_input_embedding(self, input_ids: torch.Tensor) -> torch.Tensor: + return self.model.get_input_embedding(input_ids) + + def get_input_embeddings(self) -> nn.Embedding: + return self.model.embed_tokens + + @torch.no_grad() + def forward( + self, + input_ids: torch.Tensor, + positions: torch.Tensor, + forward_batch: ForwardBatch, + input_embeds: torch.Tensor = None, + get_embedding: bool = False, + ) -> torch.Tensor: + hidden_states = self.model(input_ids, positions, forward_batch, input_embeds) + if not get_embedding: + return self.logits_processor( + input_ids, hidden_states, self.lm_head, forward_batch + ) + else: + return self.pooler(hidden_states, forward_batch) + + def load_weights(self, weights: Iterable[Tuple[str, torch.Tensor]]): + stacked_params_mapping = [ + # (param_name, shard_name, shard_id) + ("qkv_proj", "q_proj", "q"), + ("qkv_proj", "k_proj", "k"), + ("qkv_proj", "v_proj", "v"), + ("gate_up_proj", "gate_proj", 0), + ("gate_up_proj", "up_proj", 1), + ] + + params_dict = dict(self.named_parameters()) + for name, loaded_weight in weights: + if "rotary_emb.inv_freq" in name or "projector" in name: + continue + if "rotary_emb.cos_cached" in name or "rotary_emb.sin_cached" in name: + # Models trained using ColossalAI may include these tensors in + # the checkpoint. Skip them. + continue + if self.config.tie_word_embeddings and "lm_head.weight" in name: + continue + if name.startswith("model.vision_tower") and name not in params_dict: + continue + + for param_name, weight_name, shard_id in stacked_params_mapping: + if weight_name not in name: + continue + name = name.replace(weight_name, param_name) + # Skip loading extra bias for GPTQ models. + if name.endswith(".bias") and name not in params_dict: + continue + param = params_dict[name] + weight_loader = param.weight_loader + weight_loader(param, loaded_weight, shard_id) + break + else: + # Skip loading extra bias for GPTQ models. + if name.endswith(".bias") and name not in params_dict: + continue + param = params_dict[name] + weight_loader = getattr(param, "weight_loader", default_weight_loader) + weight_loader(param, loaded_weight) + + def get_embed_and_head(self): + return self.model.embed_tokens.weight, self.lm_head.weight + + def set_embed_and_head(self, embed, head): + del self.model.embed_tokens.weight + del self.lm_head.weight + self.model.embed_tokens.weight = embed + self.lm_head.weight = head + torch.cuda.empty_cache() + torch.cuda.synchronize() + + def load_kv_cache_scales(self, quantization_param_path: str) -> None: + self.model.load_kv_cache_scales(quantization_param_path) + + +EntryClass = Qwen2ForCausalLM diff --git a/lib/python3.12/site-packages/sglang/srt/models/qwen2_eagle.py b/lib/python3.12/site-packages/sglang/srt/models/qwen2_eagle.py new file mode 100644 index 0000000000000000000000000000000000000000..793d9156016470da31d669c29f786b50bacd6716 --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/models/qwen2_eagle.py @@ -0,0 +1,143 @@ +""" +Copyright 2023-2024 SGLang Team +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +from sglang.srt.utils import add_prefix + +# Adapted from +# https://github.com/SafeAILab/EAGLE/blob/main/eagle/model/cnets.py +"""Inference-only LLaMA-EAGLE model compatible with HuggingFace weights.""" + +from typing import Iterable, Optional, Tuple + +import torch +from torch import nn + +from sglang.srt.layers.logits_processor import LogitsProcessor +from sglang.srt.layers.quantization.base_config import QuantizationConfig +from sglang.srt.layers.vocab_parallel_embedding import ( + ParallelLMHead, + VocabParallelEmbedding, +) +from sglang.srt.model_executor.forward_batch_info import ForwardBatch +from sglang.srt.models.qwen2 import Qwen2DecoderLayer, Qwen2ForCausalLM + +Qwen2Config = None + + +class Qwen2DecoderLayer(Qwen2DecoderLayer): + def __init__( + self, + config: Qwen2Config, + layer_id: int = 0, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ) -> None: + super().__init__(config, layer_id, quant_config, prefix=prefix) + + # Skip the input_layernorm + # https://github.com/SafeAILab/EAGLE/blob/35c78f6cdc19a73e05cf5c330b4c358dad970c6a/eagle/model/cnets.py#L427 + if layer_id == 0: + del self.input_layernorm + setattr(self, "input_layernorm", lambda x: x) + + +class Qwen2Model(nn.Module): + def __init__( + self, + config: Qwen2Config, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ) -> None: + super().__init__() + self.config = config + self.vocab_size = config.vocab_size + self.embed_tokens = VocabParallelEmbedding( + config.vocab_size, + config.hidden_size, + prefix=add_prefix("embed_tokens", prefix), + ) + self.layers = nn.ModuleList( + [ + Qwen2DecoderLayer( + config, + i, + quant_config=quant_config, + prefix=add_prefix(f"layers.{i}", prefix), + ) + for i in range(config.num_hidden_layers) + ] + ) + self.fc = torch.nn.Linear(config.hidden_size * 2, config.hidden_size) + + def forward( + self, + input_ids: torch.Tensor, + positions: torch.Tensor, + forward_batch: ForwardBatch, + input_embeds: torch.Tensor = None, + ) -> torch.Tensor: + if input_embeds is None: + hidden_states = self.embed_tokens(input_ids) + else: + hidden_states = input_embeds + + hidden_states = self.fc( + torch.cat((hidden_states, forward_batch.spec_info.hidden_states), dim=-1) + ) + + residual = None + for i in range(len(self.layers)): + layer = self.layers[i] + hidden_states, residual = layer( + positions, + hidden_states, + forward_batch, + residual, + ) + return hidden_states + residual + + +class Qwen2ForCausalLMEagle(Qwen2ForCausalLM): + def __init__( + self, + config: Qwen2Config, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ) -> None: + nn.Module.__init__(self) + self.config = config + self.quant_config = quant_config + self.model = Qwen2Model( + config, quant_config=quant_config, prefix=add_prefix("model", prefix) + ) + if self.config.tie_word_embeddings: + self.lm_head = self.model.embed_tokens + else: + self.lm_head = ParallelLMHead( + config.vocab_size, + config.hidden_size, + quant_config=quant_config, + prefix=add_prefix("lm_head", prefix), + ) + self.logits_processor = LogitsProcessor(config) + + def load_weights(self, weights: Iterable[Tuple[str, torch.Tensor]]): + for name, loaded_weight in weights: + if "lm_head" not in name: + name = "model." + name + super().load_weights([(name, loaded_weight)]) + + +EntryClass = [Qwen2ForCausalLMEagle] diff --git a/lib/python3.12/site-packages/sglang/srt/models/qwen2_moe.py b/lib/python3.12/site-packages/sglang/srt/models/qwen2_moe.py new file mode 100644 index 0000000000000000000000000000000000000000..f1851f526df5bef58cbd4087f391d5d7b715a8ab --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/models/qwen2_moe.py @@ -0,0 +1,494 @@ +# Copyright 2023-2024 SGLang Team +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +# Adapted from +# https://github.com/vllm-project/vllm/blob/main/vllm/model_executor/models/qwen2_moe.py +"""Inference-only Qwen2MoE model compatible with HuggingFace weights.""" + +from typing import Any, Dict, Iterable, Optional, Tuple + +import torch +import torch.nn.functional as F +from torch import nn +from transformers import PretrainedConfig + +from sglang.srt.distributed import ( + get_tensor_model_parallel_world_size, + tensor_model_parallel_all_reduce, +) +from sglang.srt.layers.activation import SiluAndMul +from sglang.srt.layers.layernorm import RMSNorm +from sglang.srt.layers.linear import ( + MergedColumnParallelLinear, + QKVParallelLinear, + ReplicatedLinear, + RowParallelLinear, +) +from sglang.srt.layers.logits_processor import LogitsProcessor +from sglang.srt.layers.moe.fused_moe_triton import FusedMoE +from sglang.srt.layers.quantization.base_config import QuantizationConfig +from sglang.srt.layers.radix_attention import RadixAttention +from sglang.srt.layers.rotary_embedding import get_rope +from sglang.srt.layers.vocab_parallel_embedding import ( + ParallelLMHead, + VocabParallelEmbedding, +) +from sglang.srt.managers.expert_distribution import ExpertDistributionRecorder +from sglang.srt.model_executor.forward_batch_info import ForwardBatch +from sglang.srt.model_loader.weight_utils import default_weight_loader +from sglang.srt.utils import add_prefix, make_layers + +expert_distribution_recorder = ExpertDistributionRecorder() + + +class Qwen2MoeMLP(nn.Module): + def __init__( + self, + hidden_size: int, + intermediate_size: int, + hidden_act: str, + quant_config: Optional[QuantizationConfig] = None, + reduce_results: bool = True, + prefix: str = "", + ) -> None: + super().__init__() + self.gate_up_proj = MergedColumnParallelLinear( + hidden_size, + [intermediate_size] * 2, + bias=False, + quant_config=quant_config, + prefix=add_prefix("gate_up_proj", prefix), + ) + self.down_proj = RowParallelLinear( + intermediate_size, + hidden_size, + bias=False, + quant_config=quant_config, + reduce_results=reduce_results, + prefix=add_prefix("down_proj", prefix), + ) + if hidden_act != "silu": + raise ValueError( + f"Unsupported activation: {hidden_act}. " + "Only silu is supported for now." + ) + self.act_fn = SiluAndMul() + + def forward(self, x): + gate_up, _ = self.gate_up_proj(x) + x = self.act_fn(gate_up) + x, _ = self.down_proj(x) + return x + + +class Qwen2MoeSparseMoeBlock(nn.Module): + def __init__( + self, + config: PretrainedConfig, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ): + super().__init__() + self.tp_size = get_tensor_model_parallel_world_size() + + if self.tp_size > config.num_experts: + raise ValueError( + f"Tensor parallel size {self.tp_size} is greater than " + f"the number of experts {config.num_experts}." + ) + + self.experts = FusedMoE( + num_experts=config.num_experts, + top_k=config.num_experts_per_tok, + hidden_size=config.hidden_size, + intermediate_size=config.moe_intermediate_size, + reduce_results=False, + renormalize=config.norm_topk_prob, + quant_config=quant_config, + prefix=add_prefix("experts", prefix), + ) + + self.gate = ReplicatedLinear( + config.hidden_size, + config.num_experts, + bias=False, + quant_config=None, + prefix=add_prefix("gate", prefix), + ) + if config.shared_expert_intermediate_size > 0: + self.shared_expert = Qwen2MoeMLP( + hidden_size=config.hidden_size, + intermediate_size=config.shared_expert_intermediate_size, + hidden_act=config.hidden_act, + quant_config=quant_config, + reduce_results=False, + prefix=add_prefix("shared_expert", prefix), + ) + else: + self.shared_expert = None + self.shared_expert_gate = torch.nn.Linear(config.hidden_size, 1, bias=False) + + def forward(self, hidden_states: torch.Tensor) -> torch.Tensor: + num_tokens, hidden_dim = hidden_states.shape + hidden_states = hidden_states.view(-1, hidden_dim) + shared_output = None + if self.shared_expert is not None: + shared_output = self.shared_expert(hidden_states) + if self.shared_expert_gate is not None: + shared_output = ( + F.sigmoid(self.shared_expert_gate(hidden_states)) * shared_output + ) + + # router_logits: (num_tokens, n_experts) + router_logits, _ = self.gate(hidden_states) + final_hidden_states = self.experts( + hidden_states=hidden_states, router_logits=router_logits + ) + if shared_output is not None: + final_hidden_states = final_hidden_states + shared_output + if self.tp_size > 1: + final_hidden_states = tensor_model_parallel_all_reduce(final_hidden_states) + + return final_hidden_states.view(num_tokens, hidden_dim) + + +class Qwen2MoeAttention(nn.Module): + def __init__( + self, + hidden_size: int, + num_heads: int, + num_kv_heads: int, + layer_id: int = 0, + rope_theta: float = 10000, + rope_scaling: Optional[Dict[str, Any]] = None, + max_position_embeddings: int = 8192, + qkv_bias: int = True, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ) -> None: + super().__init__() + self.hidden_size = hidden_size + tp_size = get_tensor_model_parallel_world_size() + self.total_num_heads = num_heads + assert self.total_num_heads % tp_size == 0 + self.num_heads = self.total_num_heads // tp_size + self.total_num_kv_heads = num_kv_heads + if self.total_num_kv_heads >= tp_size: + # Number of KV heads is greater than TP size, so we partition + # the KV heads across multiple tensor parallel GPUs. + assert self.total_num_kv_heads % tp_size == 0 + else: + # Number of KV heads is less than TP size, so we replicate + # the KV heads across multiple tensor parallel GPUs. + assert tp_size % self.total_num_kv_heads == 0 + self.num_kv_heads = max(1, self.total_num_kv_heads // tp_size) + self.head_dim = hidden_size // self.total_num_heads + self.q_size = self.num_heads * self.head_dim + self.kv_size = self.num_kv_heads * self.head_dim + self.scaling = self.head_dim**-0.5 + self.rope_theta = rope_theta + self.max_position_embeddings = max_position_embeddings + + self.qkv_proj = QKVParallelLinear( + hidden_size, + self.head_dim, + self.total_num_heads, + self.total_num_kv_heads, + bias=qkv_bias, + quant_config=quant_config, + prefix=add_prefix("qkv_proj", prefix), + ) + + self.o_proj = RowParallelLinear( + self.total_num_heads * self.head_dim, + hidden_size, + bias=False, + quant_config=quant_config, + prefix=add_prefix("o_proj", prefix), + ) + + self.rotary_emb = get_rope( + self.head_dim, + rotary_dim=self.head_dim, + max_position=max_position_embeddings, + base=rope_theta, + rope_scaling=rope_scaling, + ) + self.attn = RadixAttention( + self.num_heads, + self.head_dim, + self.scaling, + num_kv_heads=self.num_kv_heads, + layer_id=layer_id, + quant_config=quant_config, + prefix=add_prefix("attn", prefix), + ) + + def forward( + self, + positions: torch.Tensor, + hidden_states: torch.Tensor, + forward_batch: ForwardBatch, + ) -> torch.Tensor: + qkv, _ = self.qkv_proj(hidden_states) + q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1) + q, k = self.rotary_emb(positions, q, k) + attn_output = self.attn(q, k, v, forward_batch) + output, _ = self.o_proj(attn_output) + return output + + +class Qwen2MoeDecoderLayer(nn.Module): + def __init__( + self, + config: PretrainedConfig, + layer_id: int, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ) -> None: + super().__init__() + self.hidden_size = config.hidden_size + rope_theta = getattr(config, "rope_theta", 10000) + rope_scaling = getattr(config, "rope_scaling", None) + max_position_embeddings = getattr(config, "max_position_embeddings", 8192) + qkv_bias = getattr(config, "qkv_bias", True) + self.self_attn = Qwen2MoeAttention( + hidden_size=self.hidden_size, + num_heads=config.num_attention_heads, + num_kv_heads=config.num_key_value_heads, + layer_id=layer_id, + rope_theta=rope_theta, + rope_scaling=rope_scaling, + max_position_embeddings=max_position_embeddings, + quant_config=quant_config, + qkv_bias=qkv_bias, + prefix=add_prefix("self_attn", prefix), + ) + + # Note: Qwen/Qwen2-57B-A14B-Instruct does not have + # `mlp_only_layers` in the config. + mlp_only_layers = ( + [] if not hasattr(config, "mlp_only_layers") else config.mlp_only_layers + ) + if (layer_id not in mlp_only_layers) and ( + config.num_experts > 0 and (layer_id + 1) % config.decoder_sparse_step == 0 + ): + self.mlp = Qwen2MoeSparseMoeBlock( + config=config, + quant_config=quant_config, + prefix=add_prefix("mlp", prefix), + ) + else: + self.mlp = Qwen2MoeMLP( + hidden_size=config.hidden_size, + intermediate_size=config.intermediate_size, + hidden_act=config.hidden_act, + quant_config=quant_config, + prefix=add_prefix("mlp", prefix), + ) + self.input_layernorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps) + self.post_attention_layernorm = RMSNorm( + config.hidden_size, eps=config.rms_norm_eps + ) + + def forward( + self, + positions: torch.Tensor, + hidden_states: torch.Tensor, + forward_batch: ForwardBatch, + residual: Optional[torch.Tensor], + ) -> torch.Tensor: + # Self Attention + if residual is None: + residual = hidden_states + hidden_states = self.input_layernorm(hidden_states) + else: + hidden_states, residual = self.input_layernorm(hidden_states, residual) + hidden_states = self.self_attn( + positions=positions, + hidden_states=hidden_states, + forward_batch=forward_batch, + ) + + # Fully Connected + hidden_states, residual = self.post_attention_layernorm(hidden_states, residual) + hidden_states = self.mlp(hidden_states) + return hidden_states, residual + + +class Qwen2MoeModel(nn.Module): + def __init__( + self, + config: PretrainedConfig, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + decoder_layer_type: type[nn.Module] = Qwen2MoeDecoderLayer, + ) -> None: + super().__init__() + self.padding_idx = config.pad_token_id + self.vocab_size = config.vocab_size + + self.embed_tokens = VocabParallelEmbedding( + config.vocab_size, + config.hidden_size, + prefix=add_prefix("embed_tokens", prefix), + ) + # Use the provided decoder layer type or default to Qwen2MoeDecoderLayer + decoder_layer_type = decoder_layer_type or Qwen2MoeDecoderLayer + self.layers = make_layers( + config.num_hidden_layers, + lambda idx, prefix: decoder_layer_type( + layer_id=idx, + config=config, + quant_config=quant_config, + prefix=prefix, + ), + prefix=add_prefix("layers", prefix), + ) + self.norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps) + + def forward( + self, + input_ids: torch.Tensor, + positions: torch.Tensor, + forward_batch: ForwardBatch, + input_embeds: torch.Tensor = None, + ) -> torch.Tensor: + if input_embeds is None: + hidden_states = self.embed_tokens(input_ids) + else: + hidden_states = input_embeds + residual = None + for i in range(len(self.layers)): + expert_distribution_recorder.set_current_layer(i) + layer = self.layers[i] + hidden_states, residual = layer( + positions, hidden_states, forward_batch, residual + ) + hidden_states, _ = self.norm(hidden_states, residual) + return hidden_states + + +class Qwen2MoeForCausalLM(nn.Module): + + fall_back_to_pt_during_load = False + + def __init__( + self, + config: PretrainedConfig, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ) -> None: + super().__init__() + self.config = config + self.quant_config = quant_config + self.model = Qwen2MoeModel( + config, quant_config, prefix=add_prefix("model", prefix) + ) + self.lm_head = ParallelLMHead( + config.vocab_size, + config.hidden_size, + quant_config=quant_config, + prefix=add_prefix("lm_head", prefix), + ) + self.logits_processor = LogitsProcessor(config) + + @torch.no_grad() + def forward( + self, + input_ids: torch.Tensor, + positions: torch.Tensor, + forward_batch: ForwardBatch, + input_embeds: torch.Tensor = None, + ) -> torch.Tensor: + hidden_states = self.model(input_ids, positions, forward_batch, input_embeds) + return self.logits_processor( + input_ids, hidden_states, self.lm_head, forward_batch + ) + + def load_weights(self, weights: Iterable[Tuple[str, torch.Tensor]]): + stacked_params_mapping = [ + # (param_name, shard_name, shard_id) + ("qkv_proj", "q_proj", "q"), + ("qkv_proj", "k_proj", "k"), + ("qkv_proj", "v_proj", "v"), + ("gate_up_proj", "gate_proj", 0), + ("gate_up_proj", "up_proj", 1), + ] + + expert_params_mapping = FusedMoE.make_expert_params_mapping( + ckpt_gate_proj_name="gate_proj", + ckpt_down_proj_name="down_proj", + ckpt_up_proj_name="up_proj", + num_experts=self.config.num_experts, + ) + + params_dict = dict(self.named_parameters()) + for name, loaded_weight in weights: + if "rotary_emb.inv_freq" in name: + continue + for param_name, weight_name, shard_id in stacked_params_mapping: + # Skip non-stacked layers and experts (experts handled below). + if weight_name not in name: + continue + # We have mlp.experts[0].gate_proj in the checkpoint. + # Since we handle the experts below in expert_params_mapping, + # we need to skip here BEFORE we update the name, otherwise + # name will be updated to mlp.experts[0].gate_up_proj, which + # will then be updated below in expert_params_mapping + # for mlp.experts[0].gate_gate_up_proj, which breaks load. + if "mlp.experts" in name: + continue + name = name.replace(weight_name, param_name) + # Skip loading extra bias for GPTQ models. + if name.endswith(".bias") and name not in params_dict: + continue + if name not in params_dict: + continue + + param = params_dict[name] + weight_loader = param.weight_loader + weight_loader(param, loaded_weight, shard_id) + break + else: + for mapping in expert_params_mapping: + param_name, weight_name, expert_id, shard_id = mapping + if weight_name not in name: + continue + name = name.replace(weight_name, param_name) + param = params_dict[name] + weight_loader = param.weight_loader + weight_loader( + param, + loaded_weight, + name, + shard_id=shard_id, + expert_id=expert_id, + ) + break + else: + # Skip loading extra bias for GPTQ models. + if name.endswith(".bias") and name not in params_dict: + continue + if name not in params_dict: + continue + + param = params_dict[name] + weight_loader = getattr( + param, "weight_loader", default_weight_loader + ) + weight_loader(param, loaded_weight) + + +EntryClass = Qwen2MoeForCausalLM diff --git a/lib/python3.12/site-packages/sglang/srt/models/qwen3.py b/lib/python3.12/site-packages/sglang/srt/models/qwen3.py new file mode 100644 index 0000000000000000000000000000000000000000..016f4b5de111c6f232246b79c96540ed10cd4ece --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/models/qwen3.py @@ -0,0 +1,335 @@ +# Adapted from qwen2.py + +from functools import partial +from typing import Any, Dict, Iterable, Optional, Tuple + +import torch +from torch import nn + +from sglang.srt.distributed import ( + get_tensor_model_parallel_rank, + get_tensor_model_parallel_world_size, + split_tensor_along_last_dim, + tensor_model_parallel_all_gather, +) +from sglang.srt.layers.layernorm import RMSNorm +from sglang.srt.layers.linear import QKVParallelLinear, RowParallelLinear +from sglang.srt.layers.logits_processor import LogitsProcessor +from sglang.srt.layers.pooler import Pooler, PoolingType +from sglang.srt.layers.quantization.base_config import QuantizationConfig +from sglang.srt.layers.radix_attention import RadixAttention +from sglang.srt.layers.rotary_embedding import get_rope +from sglang.srt.layers.vocab_parallel_embedding import ParallelLMHead +from sglang.srt.model_executor.forward_batch_info import ForwardBatch +from sglang.srt.model_loader.weight_utils import default_weight_loader +from sglang.srt.models.qwen2 import Qwen2MLP as Qwen3MLP +from sglang.srt.models.qwen2 import Qwen2Model +from sglang.srt.utils import add_prefix + +Qwen3Config = None + + +class Qwen3Attention(nn.Module): + def __init__( + self, + hidden_size: int, + num_heads: int, + num_kv_heads: int, + layer_id: int = 0, + rope_theta: float = 1000000, + rope_scaling: Optional[Dict[str, Any]] = None, + head_dim: Optional[int] = None, + max_position_embeddings: int = 32768, + quant_config: Optional[QuantizationConfig] = None, + rms_norm_eps: float = None, + attention_bias: bool = False, + prefix: str = "", + ) -> None: + super().__init__() + self.hidden_size = hidden_size + self.tp_size = get_tensor_model_parallel_world_size() + self.total_num_heads = num_heads + assert self.total_num_heads % self.tp_size == 0 + self.num_heads = self.total_num_heads // self.tp_size + self.total_num_kv_heads = num_kv_heads + if self.total_num_kv_heads >= self.tp_size: + # Number of KV heads is greater than TP size, so we partition + # the KV heads across multiple tensor parallel GPUs. + assert self.total_num_kv_heads % self.tp_size == 0 + else: + # Number of KV heads is less than TP size, so we replicate + # the KV heads across multiple tensor parallel GPUs. + assert self.tp_size % self.total_num_kv_heads == 0 + self.num_kv_heads = max(1, self.total_num_kv_heads // self.tp_size) + self.head_dim = head_dim or hidden_size // self.total_num_heads + self.q_size = self.num_heads * self.head_dim + self.kv_size = self.num_kv_heads * self.head_dim + self.scaling = self.head_dim**-0.5 + self.rope_theta = rope_theta + self.max_position_embeddings = max_position_embeddings + self.tp_rank = get_tensor_model_parallel_rank() + + self.q_norm = RMSNorm(self.head_dim, eps=rms_norm_eps) + self.k_norm = RMSNorm(self.head_dim, eps=rms_norm_eps) + + self.qkv_proj = QKVParallelLinear( + hidden_size, + self.head_dim, + self.total_num_heads, + self.total_num_kv_heads, + bias=attention_bias, + quant_config=quant_config, + prefix=add_prefix("qkv_proj", prefix), + ) + self.o_proj = RowParallelLinear( + self.total_num_heads * self.head_dim, + hidden_size, + bias=attention_bias, + quant_config=quant_config, + prefix=add_prefix("o_proj", prefix), + ) + + self.rotary_emb = get_rope( + self.head_dim, + rotary_dim=self.head_dim, + max_position=max_position_embeddings, + base=rope_theta, + rope_scaling=rope_scaling, + ) + self.attn = RadixAttention( + self.num_heads, + self.head_dim, + self.scaling, + num_kv_heads=self.num_kv_heads, + layer_id=layer_id, + prefix=add_prefix("attn", prefix), + ) + + def _apply_qk_norm( + self, q: torch.Tensor, k: torch.Tensor + ) -> Tuple[torch.Tensor, torch.Tensor]: + q_by_head = q.reshape(-1, self.head_dim) + q_by_head = self.q_norm(q_by_head) + q = q_by_head.view(q.shape) + k_by_head = k.reshape(-1, self.head_dim) + k_by_head = self.k_norm(k_by_head) + k = k_by_head.view(k.shape) + return q, k + + def forward( + self, + positions: torch.Tensor, + hidden_states: torch.Tensor, + forward_batch: ForwardBatch, + ) -> torch.Tensor: + qkv, _ = self.qkv_proj(hidden_states) + q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1) + q, k = self._apply_qk_norm(q, k) + q, k = self.rotary_emb(positions, q, k) + attn_output = self.attn(q, k, v, forward_batch) + output, _ = self.o_proj(attn_output) + return output + + +class Qwen3DecoderLayer(nn.Module): + def __init__( + self, + config: Qwen3Config, + layer_id: int = 0, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ) -> None: + super().__init__() + self.hidden_size = config.hidden_size + rope_theta = getattr(config, "rope_theta", 1000000) + rope_scaling = getattr(config, "rope_scaling", None) + max_position_embeddings = getattr(config, "max_position_embeddings", 32768) + head_dim = getattr(config, "head_dim", None) + self.self_attn = Qwen3Attention( + hidden_size=self.hidden_size, + num_heads=config.num_attention_heads, + num_kv_heads=config.num_key_value_heads, + layer_id=layer_id, + rope_theta=rope_theta, + rope_scaling=rope_scaling, + head_dim=head_dim, + max_position_embeddings=max_position_embeddings, + quant_config=quant_config, + rms_norm_eps=config.rms_norm_eps, + attention_bias=config.attention_bias, + prefix=add_prefix("self_attn", prefix), + ) + self.mlp = Qwen3MLP( + hidden_size=self.hidden_size, + intermediate_size=config.intermediate_size, + hidden_act=config.hidden_act, + quant_config=quant_config, + prefix=add_prefix("mlp", prefix), + ) + self.input_layernorm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps) + self.post_attention_layernorm = RMSNorm( + config.hidden_size, eps=config.rms_norm_eps + ) + + def forward( + self, + positions: torch.Tensor, + hidden_states: torch.Tensor, + forward_batch: ForwardBatch, + residual: Optional[torch.Tensor], + ) -> Tuple[torch.Tensor, torch.Tensor]: + # Self Attention + if residual is None: + residual = hidden_states + hidden_states = self.input_layernorm(hidden_states) + else: + hidden_states, residual = self.input_layernorm(hidden_states, residual) + hidden_states = self.self_attn( + positions=positions, + hidden_states=hidden_states, + forward_batch=forward_batch, + ) + + # Fully Connected + hidden_states, residual = self.post_attention_layernorm(hidden_states, residual) + hidden_states = self.mlp(hidden_states) + return hidden_states, residual + + +class Qwen3Model(Qwen2Model): + def __init__( + self, + config: Qwen3Config, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ) -> None: + super().__init__( + config=config, + quant_config=quant_config, + prefix=prefix, + decoder_layer_type=Qwen3DecoderLayer, + ) + + +class Qwen3ForCausalLM(nn.Module): + # BitandBytes specific attributes + default_bitsandbytes_target_modules = [ + ".gate_proj.", + ".down_proj.", + ".up_proj.", + ".q_proj.", + ".k_proj.", + ".v_proj.", + ".o_proj.", + ] + bitsandbytes_stacked_params_mapping = { + # shard_name, weight_name, index + "q_proj": ("qkv_proj", 0), + "k_proj": ("qkv_proj", 1), + "v_proj": ("qkv_proj", 2), + "gate_proj": ("gate_up_proj", 0), + "up_proj": ("gate_up_proj", 1), + } + + def __init__( + self, + config: Qwen3Config, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ) -> None: + super().__init__() + self.config = config + self.quant_config = quant_config + self.model = Qwen3Model( + config, quant_config=quant_config, prefix=add_prefix("model", prefix) + ) + if config.tie_word_embeddings: + self.lm_head = self.model.embed_tokens + else: + self.lm_head = ParallelLMHead( + config.vocab_size, + config.hidden_size, + quant_config=quant_config, + prefix=add_prefix("lm_head", prefix), + ) + self.logits_processor = LogitsProcessor(config) + self.pooler = Pooler(pooling_type=PoolingType.LAST, normalize=True) + + def get_input_embeddings(self, input_ids: torch.Tensor) -> torch.Tensor: + return self.model.get_input_embeddings(input_ids) + + @torch.no_grad() + def forward( + self, + input_ids: torch.Tensor, + positions: torch.Tensor, + forward_batch: ForwardBatch, + input_embeds: torch.Tensor = None, + get_embedding: bool = False, + ) -> torch.Tensor: + hidden_states = self.model(input_ids, positions, forward_batch, input_embeds) + if not get_embedding: + return self.logits_processor( + input_ids, hidden_states, self.lm_head, forward_batch + ) + else: + return self.pooler(hidden_states, forward_batch) + + def load_weights(self, weights: Iterable[Tuple[str, torch.Tensor]]): + stacked_params_mapping = [ + # (param_name, shard_name, shard_id) + ("qkv_proj", "q_proj", "q"), + ("qkv_proj", "k_proj", "k"), + ("qkv_proj", "v_proj", "v"), + ("gate_up_proj", "gate_proj", 0), + ("gate_up_proj", "up_proj", 1), + ] + + params_dict = dict(self.named_parameters()) + for name, loaded_weight in weights: + if "rotary_emb.inv_freq" in name or "projector" in name: + continue + if "rotary_emb.cos_cached" in name or "rotary_emb.sin_cached" in name: + # Models trained using ColossalAI may include these tensors in + # the checkpoint. Skip them. + continue + if self.config.tie_word_embeddings and "lm_head.weight" in name: + continue + if name.startswith("model.vision_tower") and name not in params_dict: + continue + + for param_name, weight_name, shard_id in stacked_params_mapping: + if weight_name not in name: + continue + name = name.replace(weight_name, param_name) + # Skip loading extra bias for GPTQ models. + if name.endswith(".bias") and name not in params_dict: + continue + param = params_dict[name] + weight_loader = param.weight_loader + weight_loader(param, loaded_weight, shard_id) + break + else: + # Skip loading extra bias for GPTQ models. + if name.endswith(".bias") and name not in params_dict: + continue + param = params_dict[name] + weight_loader = getattr(param, "weight_loader", default_weight_loader) + weight_loader(param, loaded_weight) + + def get_embed_and_head(self): + return self.model.embed_tokens.weight, self.lm_head.weight + + def set_embed_and_head(self, embed, head): + del self.model.embed_tokens.weight + del self.lm_head.weight + self.model.embed_tokens.weight = embed + self.lm_head.weight = head + torch.cuda.empty_cache() + torch.cuda.synchronize() + + def load_kv_cache_scales(self, quantization_param_path: str) -> None: + self.model.load_kv_cache_scales(quantization_param_path) + + +EntryClass = Qwen3ForCausalLM diff --git a/lib/python3.12/site-packages/sglang/srt/models/stablelm.py b/lib/python3.12/site-packages/sglang/srt/models/stablelm.py new file mode 100644 index 0000000000000000000000000000000000000000..1566893ee2fcf32fc0426a5ef4bf530217cf29e4 --- /dev/null +++ b/lib/python3.12/site-packages/sglang/srt/models/stablelm.py @@ -0,0 +1,331 @@ +# Copyright 2023-2024 SGLang Team +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +# Adapted from: +# https://github.com/vllm-project/vllm/blob/c7f2cf2b7f67bce5842fedfdba508440fe257375/vllm/model_executor/models/stablelm.py#L1 +""" +Inference-only StableLM-2 (https://huggingface.co/stabilityai/stablelm-2-1_6b) +model compatible with HuggingFace weights. +""" + +from typing import Iterable, Optional, Tuple + +import torch +from torch import nn +from transformers import PretrainedConfig + +from sglang.srt.distributed import get_tensor_model_parallel_world_size +from sglang.srt.layers.activation import SiluAndMul +from sglang.srt.layers.linear import ( + MergedColumnParallelLinear, + QKVParallelLinear, + RowParallelLinear, +) +from sglang.srt.layers.logits_processor import LogitsProcessor +from sglang.srt.layers.quantization.base_config import QuantizationConfig +from sglang.srt.layers.radix_attention import RadixAttention +from sglang.srt.layers.rotary_embedding import get_rope +from sglang.srt.layers.vocab_parallel_embedding import ( + ParallelLMHead, + VocabParallelEmbedding, +) +from sglang.srt.model_executor.forward_batch_info import ForwardBatch +from sglang.srt.model_loader.weight_utils import default_weight_loader +from sglang.srt.utils import add_prefix + + +class StablelmMLP(nn.Module): + def __init__( + self, + config: PretrainedConfig, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ) -> None: + super().__init__() + self.config = config + self.hidden_size = config.hidden_size + self.intermediate_size = config.intermediate_size + self.gate_up_proj = MergedColumnParallelLinear( + config.hidden_size, + [config.intermediate_size] * 2, + bias=False, + quant_config=quant_config, + prefix=add_prefix("gate_up_proj", prefix), + ) + self.down_proj = RowParallelLinear( + config.intermediate_size, + config.hidden_size, + bias=False, + quant_config=quant_config, + prefix=add_prefix("down_proj", prefix), + ) + self.act_fn = SiluAndMul() + + def forward(self, x: torch.Tensor) -> torch.Tensor: + gate_up, _ = self.gate_up_proj(x) + x = self.act_fn(gate_up) + x, _ = self.down_proj(x) + return x + + +class StablelmAttention(nn.Module): + def __init__( + self, + config: PretrainedConfig, + layer_id: int = 0, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ) -> None: + super().__init__() + self.config = config + self.hidden_size = config.hidden_size + tp_size = get_tensor_model_parallel_world_size() + self.total_num_heads = config.num_attention_heads + self.num_heads = self.total_num_heads // tp_size + + self.total_num_key_value_heads = config.num_key_value_heads + if self.total_num_key_value_heads >= tp_size: + # Number of KV heads is greater than TP size, so we partition + # the KV heads across multiple tensor parallel GPUs. + assert self.total_num_key_value_heads % tp_size == 0 + else: + # Number of KV heads is less than TP size, so we replicate + # the KV heads across multiple tensor parallel GPUs. + assert tp_size % self.total_num_key_value_heads == 0 + self.num_key_value_heads = max(1, self.total_num_key_value_heads // tp_size) + self.head_dim = self.hidden_size // self.total_num_heads + self.max_position_embeddings = config.max_position_embeddings + rope_pct = getattr( + config, "rope_pct", getattr(config, "partial_rotary_factor", 1) + ) + self.rotary_ndims = int(self.head_dim * rope_pct) + self.scaling = self.head_dim**-0.5 + self.q_size = self.num_heads * self.head_dim + self.kv_size = self.num_key_value_heads * self.head_dim + self.qkv_bias = getattr(config, "use_qkv_bias", False) + if (self.head_dim * self.num_heads * tp_size) != self.hidden_size: + raise ValueError( + f"hidden_size must be divisible by num_heads " + f"(got `hidden_size`: {self.hidden_size}" + f" and `num_heads`: {self.num_heads})." + ) + + self.qkv_proj = QKVParallelLinear( + self.hidden_size, + self.head_dim, + self.total_num_heads, + self.total_num_key_value_heads, + self.qkv_bias, + quant_config=quant_config, + prefix=add_prefix("qkv_proj", prefix), + ) + self.o_proj = RowParallelLinear( + self.total_num_heads * self.head_dim, + self.hidden_size, + bias=False, + quant_config=quant_config, + prefix=add_prefix("o_proj", prefix), + ) + self.rotary_emb = get_rope( + self.head_dim, + rotary_dim=self.rotary_ndims, + max_position=self.config.max_position_embeddings, + base=self.config.rope_theta, + ) + self.attn = RadixAttention( + self.num_heads, + self.head_dim, + self.scaling, + num_kv_heads=self.num_key_value_heads, + layer_id=layer_id, + quant_config=quant_config, + prefix=add_prefix("attn", prefix), + ) + + def forward( + self, + positions: torch.Tensor, + hidden_states: torch.Tensor, + forward_batch: ForwardBatch, + ) -> torch.Tensor: + qkv, _ = self.qkv_proj(hidden_states) + q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1) + q, k = self.rotary_emb(positions, q, k) + attn_output = self.attn(q, k, v, forward_batch) + output, _ = self.o_proj(attn_output) + return output + + +class StablelmDecoderLayer(nn.Module): + def __init__( + self, + config: PretrainedConfig, + layer_id: int = 0, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ) -> None: + super().__init__() + self.self_attn = StablelmAttention( + config, layer_id=layer_id, prefix=add_prefix("self_attn", prefix) + ) + self.mlp = StablelmMLP( + config, quant_config=quant_config, prefix=add_prefix("mlp", prefix) + ) + norm_eps = getattr(config, "norm_eps", getattr(config, "layer_norm_eps", 1e-05)) + self.input_layernorm = nn.LayerNorm(config.hidden_size, eps=norm_eps) + self.post_attention_layernorm = nn.LayerNorm(config.hidden_size, eps=norm_eps) + + def forward( + self, + positions: torch.Tensor, + hidden_states: torch.Tensor, + forward_batch: ForwardBatch, + ) -> Tuple[torch.Tensor, torch.Tensor]: + # Self Attention + residual = hidden_states + hidden_states = self.input_layernorm(hidden_states) + hidden_states = self.self_attn( + positions=positions, + hidden_states=hidden_states, + forward_batch=forward_batch, + ) + hidden_states = residual + hidden_states + + # Fully Connected + residual = hidden_states + hidden_states = self.post_attention_layernorm(hidden_states) + hidden_states = self.mlp(hidden_states) + hidden_states = residual + hidden_states + + return hidden_states, residual + + +class StableLMEpochModel(nn.Module): + def __init__( + self, + config: PretrainedConfig, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ) -> None: + super().__init__() + self.embed_tokens = VocabParallelEmbedding( + config.vocab_size, + config.hidden_size, + prefix=add_prefix("embed_tokens", prefix), + ) + self.layers = nn.ModuleList( + [ + StablelmDecoderLayer( + config, + i, + quant_config=quant_config, + prefix=add_prefix(f"layers.{i}", prefix), + ) + for i in range(config.num_hidden_layers) + ] + ) + norm_eps = getattr(config, "norm_eps", getattr(config, "layer_norm_eps", 1e-05)) + self.norm = nn.LayerNorm(config.hidden_size, eps=norm_eps) + + def forward( + self, + input_ids: torch.Tensor, + positions: torch.Tensor, + forward_batch: ForwardBatch, + input_embeds: torch.Tensor = None, + ) -> torch.Tensor: + if input_embeds is None: + hidden_states = self.embed_tokens(input_ids) + else: + hidden_states = input_embeds + for i in range(len(self.layers)): + layer = self.layers[i] + hidden_states, residual = layer( + positions, + hidden_states, + forward_batch, + ) + hidden_states = self.norm(hidden_states) + return hidden_states + + +class StableLmForCausalLM(nn.Module): + def __init__( + self, + config: PretrainedConfig, + quant_config: Optional[QuantizationConfig] = None, + prefix: str = "", + ) -> None: + super().__init__() + self.config = config + self.quant_config = quant_config + self.model = StableLMEpochModel( + config, quant_config=quant_config, prefix=add_prefix("model", prefix) + ) + self.lm_head = ParallelLMHead( + config.vocab_size, config.hidden_size, prefix=add_prefix("lm_head", prefix) + ) + self.logits_processor = LogitsProcessor(config) + + @torch.no_grad() + def forward( + self, + input_ids: torch.Tensor, + positions: torch.Tensor, + forward_batch: ForwardBatch, + input_embeds: torch.Tensor = None, + ) -> torch.Tensor: + hidden_states = self.model(input_ids, positions, forward_batch, input_embeds) + return self.logits_processor( + input_ids, hidden_states, self.lm_head, forward_batch + ) + + def load_weights(self, weights: Iterable[Tuple[str, torch.Tensor]]): + stacked_params_mapping = [ + # (param_name, shard_name, shard_id) + ("qkv_proj", "q_proj", "q"), + ("qkv_proj", "k_proj", "k"), + ("qkv_proj", "v_proj", "v"), + ("gate_up_proj", "gate_proj", 0), + ("gate_up_proj", "up_proj", 1), + ] + params_dict = dict(self.named_parameters()) + for name, loaded_weight in weights: + if "rotary_emb.inv_freq" in name: + continue + if "rotary_emb.cos_cached" in name or "rotary_emb.sin_cached" in name: + # Models trained using ColossalAI may include these tensors in + # the checkpoint. Skip them. + continue + for param_name, weight_name, shard_id in stacked_params_mapping: + if weight_name not in name: + continue + name = name.replace(weight_name, param_name) + # Skip loading extra bias for GPTQ models. + if name.endswith(".bias") and name not in params_dict: + continue + param = params_dict[name] + weight_loader = param.weight_loader + weight_loader(param, loaded_weight, shard_id) + break + else: + # Skip loading extra bias for GPTQ models. + if name.endswith(".bias") and name not in params_dict: + continue + param = params_dict[name] + weight_loader = getattr(param, "weight_loader", default_weight_loader) + weight_loader(param, loaded_weight) + + +EntryClass = StableLmForCausalLM