Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- lib/python3.12/site-packages/sglang/srt/constrained/__pycache__/base_grammar_backend.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/constrained/__pycache__/llguidance_backend.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/constrained/__pycache__/outlines_backend.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/constrained/__pycache__/outlines_jump_forward.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/constrained/__pycache__/reasoner_grammar_backend.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/constrained/__pycache__/xgrammar_backend.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/constrained/base_grammar_backend.py +219 -0
- lib/python3.12/site-packages/sglang/srt/constrained/llguidance_backend.py +169 -0
- lib/python3.12/site-packages/sglang/srt/constrained/outlines_backend.py +189 -0
- lib/python3.12/site-packages/sglang/srt/constrained/outlines_jump_forward.py +200 -0
- lib/python3.12/site-packages/sglang/srt/constrained/reasoner_grammar_backend.py +101 -0
- lib/python3.12/site-packages/sglang/srt/constrained/triton_ops/__pycache__/bitmask_ops.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/constrained/triton_ops/bitmask_ops.py +141 -0
- lib/python3.12/site-packages/sglang/srt/constrained/xgrammar_backend.py +207 -0
- lib/python3.12/site-packages/sglang/srt/disaggregation/__pycache__/decode.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/disaggregation/__pycache__/mini_lb.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/disaggregation/__pycache__/prefill.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/disaggregation/__pycache__/utils.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/disaggregation/base/__init__.py +8 -0
- lib/python3.12/site-packages/sglang/srt/disaggregation/base/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/disaggregation/base/__pycache__/conn.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/disaggregation/base/conn.py +113 -0
- lib/python3.12/site-packages/sglang/srt/disaggregation/mooncake/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/disaggregation/mooncake/__pycache__/conn.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/disaggregation/nixl/__init__.py +1 -0
- lib/python3.12/site-packages/sglang/srt/disaggregation/nixl/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/disaggregation/nixl/__pycache__/conn.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/disaggregation/nixl/conn.py +622 -0
- lib/python3.12/site-packages/sglang/srt/lora/__pycache__/layers.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/lora/__pycache__/lora.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/lora/__pycache__/lora_config.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/lora/__pycache__/lora_manager.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/lora/__pycache__/mem_pool.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/lora/__pycache__/utils.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/lora/backend/__pycache__/base_backend.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/lora/backend/__pycache__/flashinfer_backend.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/lora/backend/__pycache__/triton_backend.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/lora/backend/base_backend.py +133 -0
- lib/python3.12/site-packages/sglang/srt/lora/backend/flashinfer_backend.py +131 -0
- lib/python3.12/site-packages/sglang/srt/lora/backend/triton_backend.py +88 -0
- lib/python3.12/site-packages/sglang/srt/lora/layers.py +347 -0
- lib/python3.12/site-packages/sglang/srt/lora/lora.py +181 -0
- lib/python3.12/site-packages/sglang/srt/lora/lora_config.py +47 -0
- lib/python3.12/site-packages/sglang/srt/lora/lora_manager.py +228 -0
- lib/python3.12/site-packages/sglang/srt/lora/mem_pool.py +239 -0
- lib/python3.12/site-packages/sglang/srt/lora/triton_ops/__init__.py +11 -0
- lib/python3.12/site-packages/sglang/srt/lora/triton_ops/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/lora/triton_ops/__pycache__/gate_up_lora_b.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/lora/triton_ops/__pycache__/qkv_lora_b.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sglang/srt/lora/triton_ops/__pycache__/sgemm_lora_a.cpython-312.pyc +0 -0
lib/python3.12/site-packages/sglang/srt/constrained/__pycache__/base_grammar_backend.cpython-312.pyc
ADDED
|
Binary file (11 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/constrained/__pycache__/llguidance_backend.cpython-312.pyc
ADDED
|
Binary file (8.31 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/constrained/__pycache__/outlines_backend.cpython-312.pyc
ADDED
|
Binary file (10.4 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/constrained/__pycache__/outlines_jump_forward.cpython-312.pyc
ADDED
|
Binary file (7.56 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/constrained/__pycache__/reasoner_grammar_backend.cpython-312.pyc
ADDED
|
Binary file (6.27 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/constrained/__pycache__/xgrammar_backend.cpython-312.pyc
ADDED
|
Binary file (10 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/constrained/base_grammar_backend.py
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2023-2024 SGLang Team
|
| 2 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 3 |
+
# you may not use this file except in compliance with the License.
|
| 4 |
+
# You may obtain a copy of the License at
|
| 5 |
+
#
|
| 6 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 7 |
+
#
|
| 8 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 9 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 10 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 11 |
+
# See the License for the specific language governing permissions and
|
| 12 |
+
# limitations under the License.
|
| 13 |
+
# ==============================================================================
|
| 14 |
+
"""The baseclass of a backend for grammar-guided constrained decoding."""
|
| 15 |
+
|
| 16 |
+
import logging
|
| 17 |
+
from abc import ABC, abstractmethod
|
| 18 |
+
from concurrent.futures import Future, ThreadPoolExecutor
|
| 19 |
+
from dataclasses import dataclass
|
| 20 |
+
from threading import Event, Lock
|
| 21 |
+
from typing import Dict, List, Optional, Tuple
|
| 22 |
+
|
| 23 |
+
import torch
|
| 24 |
+
|
| 25 |
+
from sglang.srt.server_args import ServerArgs
|
| 26 |
+
|
| 27 |
+
logger = logging.getLogger(__name__)
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
class BaseGrammarObject(ABC):
|
| 31 |
+
|
| 32 |
+
def __init__(self):
|
| 33 |
+
self._finished = False
|
| 34 |
+
|
| 35 |
+
@property
|
| 36 |
+
def finished(self):
|
| 37 |
+
return self._finished
|
| 38 |
+
|
| 39 |
+
@finished.setter
|
| 40 |
+
def finished(self, finished):
|
| 41 |
+
self._finished = finished
|
| 42 |
+
|
| 43 |
+
@abstractmethod
|
| 44 |
+
def try_jump_forward(self, tokenizer) -> Optional[Tuple[List[int], str]]:
|
| 45 |
+
"""
|
| 46 |
+
Try to jump forward in the grammar.
|
| 47 |
+
|
| 48 |
+
Returns:
|
| 49 |
+
A jump forward helper which may be used in `jump_forward_str_state`.
|
| 50 |
+
None if the jump forward is not possible.
|
| 51 |
+
"""
|
| 52 |
+
raise NotImplementedError
|
| 53 |
+
|
| 54 |
+
@abstractmethod
|
| 55 |
+
def jump_forward_str_state(self, helper: Tuple[List[int], str]) -> Tuple[str, int]:
|
| 56 |
+
"""
|
| 57 |
+
Jump forward for the grammar.
|
| 58 |
+
|
| 59 |
+
Returns:
|
| 60 |
+
A tuple of the jump forward string and the next state of the grammar
|
| 61 |
+
(which can be used in `jump_and_retokenize` if needed).
|
| 62 |
+
"""
|
| 63 |
+
raise NotImplementedError
|
| 64 |
+
|
| 65 |
+
@abstractmethod
|
| 66 |
+
def jump_and_retokenize(
|
| 67 |
+
self, old_output_ids: List[int], new_output_ids: List[int], next_state: int
|
| 68 |
+
) -> None:
|
| 69 |
+
"""
|
| 70 |
+
Jump forward occurs, and update the grammar state if needed.
|
| 71 |
+
"""
|
| 72 |
+
raise NotImplementedError
|
| 73 |
+
|
| 74 |
+
@abstractmethod
|
| 75 |
+
def accept_token(self, token: int) -> None:
|
| 76 |
+
"""
|
| 77 |
+
Accept a token in the grammar.
|
| 78 |
+
"""
|
| 79 |
+
raise NotImplementedError
|
| 80 |
+
|
| 81 |
+
@abstractmethod
|
| 82 |
+
def allocate_vocab_mask(
|
| 83 |
+
self, vocab_size: int, batch_size: int, device
|
| 84 |
+
) -> torch.Tensor:
|
| 85 |
+
raise NotImplementedError
|
| 86 |
+
|
| 87 |
+
@abstractmethod
|
| 88 |
+
def fill_vocab_mask(self, vocab_mask: torch.Tensor, idx: int) -> None:
|
| 89 |
+
raise NotImplementedError
|
| 90 |
+
|
| 91 |
+
@staticmethod
|
| 92 |
+
@abstractmethod
|
| 93 |
+
def move_vocab_mask(vocab_mask: torch.Tensor, device) -> torch.Tensor:
|
| 94 |
+
raise NotImplementedError
|
| 95 |
+
|
| 96 |
+
@staticmethod
|
| 97 |
+
@abstractmethod
|
| 98 |
+
def apply_vocab_mask(logits: torch.Tensor, vocab_mask: torch.Tensor) -> None:
|
| 99 |
+
raise NotImplementedError
|
| 100 |
+
|
| 101 |
+
@abstractmethod
|
| 102 |
+
def copy(self) -> "BaseGrammarObject":
|
| 103 |
+
raise NotImplementedError
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
@dataclass
|
| 107 |
+
class CacheEntry:
|
| 108 |
+
value: Optional[BaseGrammarObject]
|
| 109 |
+
event: Event
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
class BaseGrammarBackend:
|
| 113 |
+
def __init__(self):
|
| 114 |
+
self.executor = ThreadPoolExecutor()
|
| 115 |
+
self.cache: Dict[Tuple[str, str], CacheEntry] = {}
|
| 116 |
+
self.cache_lock = Lock()
|
| 117 |
+
|
| 118 |
+
def _not_supported(self, key_type: str, key_string: str) -> None:
|
| 119 |
+
logger.warning(f"Skip unsupported {key_type}: {key_type}={key_string}")
|
| 120 |
+
|
| 121 |
+
def dispatch_fallback(
|
| 122 |
+
self, key_type: str, key_string: str
|
| 123 |
+
) -> Optional[BaseGrammarObject]:
|
| 124 |
+
"""
|
| 125 |
+
This function should not be reached in any case.
|
| 126 |
+
"""
|
| 127 |
+
raise ValueError(f"Invalid key_type: {key_type}={key_string}")
|
| 128 |
+
|
| 129 |
+
def dispatch_json(self, key_string: str) -> Optional[BaseGrammarObject]:
|
| 130 |
+
return self._not_supported("json", key_string)
|
| 131 |
+
|
| 132 |
+
def dispatch_regex(self, key_string: str) -> Optional[BaseGrammarObject]:
|
| 133 |
+
return self._not_supported("regex", key_string)
|
| 134 |
+
|
| 135 |
+
def dispatch_ebnf(self, key_string: str) -> Optional[BaseGrammarObject]:
|
| 136 |
+
return self._not_supported("ebnf", key_string)
|
| 137 |
+
|
| 138 |
+
def dispatch_structural_tag(self, key_string: str) -> Optional[BaseGrammarObject]:
|
| 139 |
+
return self._not_supported("structural_tag", key_string)
|
| 140 |
+
|
| 141 |
+
def _init_value_dispatch(self, key: Tuple[str, str]) -> Optional[BaseGrammarObject]:
|
| 142 |
+
key_type, key_string = key
|
| 143 |
+
if key_type == "json":
|
| 144 |
+
return self.dispatch_json(key_string)
|
| 145 |
+
elif key_type == "regex":
|
| 146 |
+
return self.dispatch_regex(key_string)
|
| 147 |
+
elif key_type == "ebnf":
|
| 148 |
+
return self.dispatch_ebnf(key_string)
|
| 149 |
+
elif key_type == "structural_tag":
|
| 150 |
+
return self.dispatch_structural_tag(key_string)
|
| 151 |
+
else:
|
| 152 |
+
return self.dispatch_fallback(key_type, key_string)
|
| 153 |
+
|
| 154 |
+
def _init_value(self, key: Tuple[str, str]) -> Optional[BaseGrammarObject]:
|
| 155 |
+
with self.cache_lock:
|
| 156 |
+
if key in self.cache:
|
| 157 |
+
cache_hit = True
|
| 158 |
+
entry = self.cache[key]
|
| 159 |
+
else:
|
| 160 |
+
cache_hit = False
|
| 161 |
+
entry = CacheEntry(None, Event())
|
| 162 |
+
self.cache[key] = entry
|
| 163 |
+
|
| 164 |
+
if cache_hit:
|
| 165 |
+
entry.event.wait()
|
| 166 |
+
else:
|
| 167 |
+
entry.value = self._init_value_dispatch(key)
|
| 168 |
+
entry.event.set()
|
| 169 |
+
return entry.value.copy() if entry.value else None
|
| 170 |
+
|
| 171 |
+
def get_cached_value(self, key: Tuple[str, str]) -> Optional[BaseGrammarObject]:
|
| 172 |
+
with self.cache_lock:
|
| 173 |
+
entry = self.cache.get(key)
|
| 174 |
+
if not entry or not entry.event.is_set():
|
| 175 |
+
return None
|
| 176 |
+
val = self.cache[key].value
|
| 177 |
+
return val.copy() if val else None
|
| 178 |
+
|
| 179 |
+
def get_future_value(self, key: Tuple[str, str]) -> Future:
|
| 180 |
+
return self.executor.submit(self._init_value, key)
|
| 181 |
+
|
| 182 |
+
def reset(self):
|
| 183 |
+
with self.cache_lock:
|
| 184 |
+
self.cache.clear()
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
def create_grammar_backend(
|
| 188 |
+
server_args: ServerArgs, tokenizer, vocab_size: int
|
| 189 |
+
) -> Optional[BaseGrammarBackend]:
|
| 190 |
+
if server_args.grammar_backend == "outlines":
|
| 191 |
+
from sglang.srt.constrained.outlines_backend import OutlinesGrammarBackend
|
| 192 |
+
|
| 193 |
+
grammar_backend = OutlinesGrammarBackend(
|
| 194 |
+
tokenizer,
|
| 195 |
+
whitespace_pattern=server_args.constrained_json_whitespace_pattern,
|
| 196 |
+
)
|
| 197 |
+
elif server_args.grammar_backend == "xgrammar":
|
| 198 |
+
from sglang.srt.constrained.xgrammar_backend import XGrammarGrammarBackend
|
| 199 |
+
|
| 200 |
+
grammar_backend = XGrammarGrammarBackend(tokenizer, vocab_size=vocab_size)
|
| 201 |
+
elif server_args.grammar_backend == "llguidance":
|
| 202 |
+
from sglang.srt.constrained.llguidance_backend import GuidanceBackend
|
| 203 |
+
|
| 204 |
+
grammar_backend = GuidanceBackend(
|
| 205 |
+
tokenizer=tokenizer,
|
| 206 |
+
whitespace_pattern=server_args.constrained_json_whitespace_pattern,
|
| 207 |
+
)
|
| 208 |
+
elif server_args.grammar_backend == "none":
|
| 209 |
+
return None
|
| 210 |
+
else:
|
| 211 |
+
raise ValueError(f"Invalid grammar backend: {server_args.grammar_backend}")
|
| 212 |
+
|
| 213 |
+
if server_args.reasoning_parser and hasattr(tokenizer, "think_end_id"):
|
| 214 |
+
from .reasoner_grammar_backend import ReasonerGrammarBackend
|
| 215 |
+
|
| 216 |
+
grammar_backend = ReasonerGrammarBackend(
|
| 217 |
+
grammar_backend, tokenizer.think_end_id
|
| 218 |
+
)
|
| 219 |
+
return grammar_backend
|
lib/python3.12/site-packages/sglang/srt/constrained/llguidance_backend.py
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2023-2024 SGLang Team
|
| 2 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 3 |
+
# you may not use this file except in compliance with the License.
|
| 4 |
+
# You may obtain a copy of the License at
|
| 5 |
+
#
|
| 6 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 7 |
+
#
|
| 8 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 9 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 10 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 11 |
+
# See the License for the specific language governing permissions and
|
| 12 |
+
# limitations under the License.
|
| 13 |
+
# ==============================================================================
|
| 14 |
+
"""Constrained decoding with llguidance backend."""
|
| 15 |
+
|
| 16 |
+
import json
|
| 17 |
+
import logging
|
| 18 |
+
import os
|
| 19 |
+
from typing import List, Optional, Tuple
|
| 20 |
+
|
| 21 |
+
import torch
|
| 22 |
+
from llguidance import LLMatcher, LLTokenizer, StructTag, grammar_from
|
| 23 |
+
from llguidance.hf import from_tokenizer
|
| 24 |
+
from llguidance.torch import (
|
| 25 |
+
allocate_token_bitmask,
|
| 26 |
+
apply_token_bitmask_inplace,
|
| 27 |
+
fill_next_token_bitmask,
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
from sglang.srt.constrained.base_grammar_backend import (
|
| 31 |
+
BaseGrammarBackend,
|
| 32 |
+
BaseGrammarObject,
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
logger = logging.getLogger(__name__)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
class GuidanceGrammar(BaseGrammarObject):
|
| 39 |
+
|
| 40 |
+
def __init__(self, llguidance_tokenizer: LLTokenizer, serialized_grammar: str):
|
| 41 |
+
super().__init__()
|
| 42 |
+
self.llguidance_tokenizer = llguidance_tokenizer
|
| 43 |
+
self.serialized_grammar = serialized_grammar
|
| 44 |
+
|
| 45 |
+
self.ll_matcher = LLMatcher(
|
| 46 |
+
self.llguidance_tokenizer,
|
| 47 |
+
self.serialized_grammar,
|
| 48 |
+
log_level=int(os.environ.get("LLGUIDANCE_LOG_LEVEL", "1")),
|
| 49 |
+
)
|
| 50 |
+
self.finished = False
|
| 51 |
+
self.bitmask = None
|
| 52 |
+
|
| 53 |
+
def try_jump_forward(self, tokenizer) -> Optional[Tuple[List[int], str]]:
|
| 54 |
+
ff_tokens = self.ll_matcher.compute_ff_tokens()
|
| 55 |
+
if ff_tokens:
|
| 56 |
+
return ff_tokens, ""
|
| 57 |
+
else:
|
| 58 |
+
return None
|
| 59 |
+
|
| 60 |
+
def jump_forward_str_state(self, helper: Tuple[List[int], str]) -> Tuple[str, int]:
|
| 61 |
+
return "", -1
|
| 62 |
+
|
| 63 |
+
def jump_and_retokenize(
|
| 64 |
+
self, old_output_ids: List[int], new_output_ids: List[int], next_state: int
|
| 65 |
+
):
|
| 66 |
+
pass
|
| 67 |
+
|
| 68 |
+
def accept_token(self, token: int):
|
| 69 |
+
if not self.ll_matcher.consume_token(token):
|
| 70 |
+
logger.warning(f"matcher error: {self.ll_matcher.get_error()}")
|
| 71 |
+
self.finished = True
|
| 72 |
+
|
| 73 |
+
def fill_vocab_mask(self, vocab_mask: torch.Tensor, idx: int) -> None:
|
| 74 |
+
if self.ll_matcher.is_stopped():
|
| 75 |
+
self.finished = True
|
| 76 |
+
|
| 77 |
+
fill_next_token_bitmask(self.ll_matcher, vocab_mask, idx)
|
| 78 |
+
|
| 79 |
+
def allocate_vocab_mask(
|
| 80 |
+
self, vocab_size: int, batch_size: int, device
|
| 81 |
+
) -> torch.Tensor:
|
| 82 |
+
if self.bitmask is None or self.bitmask.shape[0] < batch_size:
|
| 83 |
+
# only create bitmask when batch gets larger
|
| 84 |
+
self.bitmask = allocate_token_bitmask(
|
| 85 |
+
batch_size, self.llguidance_tokenizer.vocab_size
|
| 86 |
+
)
|
| 87 |
+
bitmask = self.bitmask
|
| 88 |
+
else:
|
| 89 |
+
bitmask = self.bitmask[:batch_size]
|
| 90 |
+
|
| 91 |
+
return bitmask
|
| 92 |
+
|
| 93 |
+
@staticmethod
|
| 94 |
+
def move_vocab_mask(vocab_mask: torch.Tensor, device) -> torch.Tensor:
|
| 95 |
+
return vocab_mask.to(device, non_blocking=True)
|
| 96 |
+
|
| 97 |
+
@staticmethod
|
| 98 |
+
def apply_vocab_mask(logits: torch.Tensor, vocab_mask: torch.Tensor) -> None:
|
| 99 |
+
apply_token_bitmask_inplace(logits, vocab_mask)
|
| 100 |
+
|
| 101 |
+
def copy(self):
|
| 102 |
+
return GuidanceGrammar(
|
| 103 |
+
llguidance_tokenizer=self.llguidance_tokenizer,
|
| 104 |
+
serialized_grammar=self.serialized_grammar,
|
| 105 |
+
)
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
class GuidanceBackend(BaseGrammarBackend):
|
| 109 |
+
|
| 110 |
+
def __init__(
|
| 111 |
+
self,
|
| 112 |
+
tokenizer,
|
| 113 |
+
whitespace_pattern: Optional[str] = None,
|
| 114 |
+
n_vocab: Optional[int] = None,
|
| 115 |
+
):
|
| 116 |
+
super().__init__()
|
| 117 |
+
|
| 118 |
+
self.tokenizer = tokenizer
|
| 119 |
+
self.whitespace_pattern = whitespace_pattern
|
| 120 |
+
self.llguidance_tokenizer = from_tokenizer(self.tokenizer, n_vocab)
|
| 121 |
+
|
| 122 |
+
def _from_serialized(self, serialized_grammar) -> Optional[GuidanceGrammar]:
|
| 123 |
+
try:
|
| 124 |
+
return GuidanceGrammar(
|
| 125 |
+
llguidance_tokenizer=self.llguidance_tokenizer,
|
| 126 |
+
serialized_grammar=serialized_grammar,
|
| 127 |
+
)
|
| 128 |
+
except Exception as e:
|
| 129 |
+
logger.warning(f"Skip invalid grammar: {serialized_grammar}, {e=}")
|
| 130 |
+
return None
|
| 131 |
+
|
| 132 |
+
def dispatch_json(self, key_string: str) -> Optional[GuidanceGrammar]:
|
| 133 |
+
serialized_grammar = LLMatcher.grammar_from_json_schema(
|
| 134 |
+
key_string,
|
| 135 |
+
defaults={
|
| 136 |
+
"whitespace_pattern": self.whitespace_pattern,
|
| 137 |
+
},
|
| 138 |
+
)
|
| 139 |
+
return self._from_serialized(serialized_grammar)
|
| 140 |
+
|
| 141 |
+
def dispatch_regex(self, key_string: str) -> Optional[GuidanceGrammar]:
|
| 142 |
+
serialized_grammar = grammar_from("regex", key_string)
|
| 143 |
+
return self._from_serialized(serialized_grammar)
|
| 144 |
+
|
| 145 |
+
def dispatch_ebnf(self, key_string: str) -> Optional[GuidanceGrammar]:
|
| 146 |
+
try:
|
| 147 |
+
serialized_grammar = grammar_from("ebnf", key_string)
|
| 148 |
+
return self._from_serialized(serialized_grammar)
|
| 149 |
+
except ValueError as e:
|
| 150 |
+
logger.warning(f"Skip invalid ebnf: regex={key_string}, {e=}")
|
| 151 |
+
return None
|
| 152 |
+
|
| 153 |
+
def dispatch_structural_tag(self, key_string: str) -> Optional[GuidanceGrammar]:
|
| 154 |
+
try:
|
| 155 |
+
structural_tag = json.loads(key_string)
|
| 156 |
+
tags = [
|
| 157 |
+
StructTag(
|
| 158 |
+
begin=structure["begin"],
|
| 159 |
+
grammar=structure["schema"],
|
| 160 |
+
end=structure["end"],
|
| 161 |
+
trigger=structural_tag["triggers"][0], # TODO?
|
| 162 |
+
)
|
| 163 |
+
for structure in structural_tag["structures"]
|
| 164 |
+
]
|
| 165 |
+
g = StructTag.to_grammar(tags)
|
| 166 |
+
return self._from_serialized(g)
|
| 167 |
+
except Exception as e:
|
| 168 |
+
logging.warning(f"Skip invalid structural_tag: {key_string}, {e=}")
|
| 169 |
+
return None
|
lib/python3.12/site-packages/sglang/srt/constrained/outlines_backend.py
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2023-2024 SGLang Team
|
| 2 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 3 |
+
# you may not use this file except in compliance with the License.
|
| 4 |
+
# You may obtain a copy of the License at
|
| 5 |
+
#
|
| 6 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 7 |
+
#
|
| 8 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 9 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 10 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 11 |
+
# See the License for the specific language governing permissions and
|
| 12 |
+
# limitations under the License.
|
| 13 |
+
# ==============================================================================
|
| 14 |
+
"""Constrained decoding with outlines backend."""
|
| 15 |
+
|
| 16 |
+
import json
|
| 17 |
+
import logging
|
| 18 |
+
from typing import Dict, List, Optional, Tuple, Union
|
| 19 |
+
|
| 20 |
+
import interegular
|
| 21 |
+
import torch
|
| 22 |
+
from outlines.fsm.guide import RegexGuide
|
| 23 |
+
from outlines.models.transformers import TransformerTokenizer
|
| 24 |
+
from pydantic import BaseModel
|
| 25 |
+
|
| 26 |
+
from sglang.srt.constrained.base_grammar_backend import (
|
| 27 |
+
BaseGrammarBackend,
|
| 28 |
+
BaseGrammarObject,
|
| 29 |
+
)
|
| 30 |
+
from sglang.srt.constrained.outlines_jump_forward import OutlinesJumpForwardMap
|
| 31 |
+
|
| 32 |
+
try:
|
| 33 |
+
from outlines.fsm.json_schema import build_regex_from_schema
|
| 34 |
+
except ImportError:
|
| 35 |
+
from outlines_core.fsm.json_schema import build_regex_from_schema
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
logger = logging.getLogger(__name__)
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
class OutlinesGrammar(BaseGrammarObject):
|
| 42 |
+
def __init__(
|
| 43 |
+
self,
|
| 44 |
+
guide: RegexGuide,
|
| 45 |
+
jump_forward_map: Union[OutlinesJumpForwardMap, None],
|
| 46 |
+
) -> None:
|
| 47 |
+
super().__init__()
|
| 48 |
+
self.guide = guide
|
| 49 |
+
self.jump_forward_map = jump_forward_map
|
| 50 |
+
self.state = 0
|
| 51 |
+
self.finished = False
|
| 52 |
+
|
| 53 |
+
def accept_token(self, token: int):
|
| 54 |
+
self.state = self.guide.get_next_state(self.state, token)
|
| 55 |
+
|
| 56 |
+
def try_jump_forward(self, tokenizer) -> Optional[Tuple]:
|
| 57 |
+
if not self.jump_forward_map:
|
| 58 |
+
return None
|
| 59 |
+
|
| 60 |
+
jump_forward_bytes = self.jump_forward_map.jump_forward_byte(self.state)
|
| 61 |
+
if jump_forward_bytes is None or len(jump_forward_bytes) <= 1:
|
| 62 |
+
return None
|
| 63 |
+
|
| 64 |
+
# preprocess the jump forward string
|
| 65 |
+
suffix_bytes = []
|
| 66 |
+
continuation_range = range(0x80, 0xC0)
|
| 67 |
+
cur_state = self.state
|
| 68 |
+
while (
|
| 69 |
+
len(jump_forward_bytes) and jump_forward_bytes[0][0] in continuation_range
|
| 70 |
+
):
|
| 71 |
+
# continuation bytes
|
| 72 |
+
byte_edge = jump_forward_bytes.pop(0)
|
| 73 |
+
suffix_bytes.append(byte_edge[0])
|
| 74 |
+
cur_state = byte_edge[1]
|
| 75 |
+
|
| 76 |
+
suffix_tokens = [f"<0x{hex(b)[2:].upper()}>" for b in suffix_bytes]
|
| 77 |
+
suffix_ids = tokenizer.convert_tokens_to_ids(suffix_tokens)
|
| 78 |
+
return suffix_ids, cur_state
|
| 79 |
+
|
| 80 |
+
def jump_forward_str_state(self, helper: Tuple[List[int], str]) -> Tuple[str, int]:
|
| 81 |
+
_, cur_state = helper
|
| 82 |
+
return self.jump_forward_map.jump_forward_symbol(cur_state)
|
| 83 |
+
|
| 84 |
+
def jump_and_retokenize(
|
| 85 |
+
self, old_output_ids: List[int], new_output_ids: List[int], next_state: int
|
| 86 |
+
):
|
| 87 |
+
self.state = next_state
|
| 88 |
+
|
| 89 |
+
def allocate_vocab_mask(
|
| 90 |
+
self, vocab_size: int, batch_size: int, device
|
| 91 |
+
) -> torch.Tensor:
|
| 92 |
+
return torch.zeros(batch_size, vocab_size, dtype=torch.bool, device=device)
|
| 93 |
+
|
| 94 |
+
@staticmethod
|
| 95 |
+
def move_vocab_mask(vocab_mask: torch.Tensor, device) -> torch.Tensor:
|
| 96 |
+
return vocab_mask
|
| 97 |
+
|
| 98 |
+
def fill_vocab_mask(self, vocab_mask: torch.Tensor, idx: int) -> None:
|
| 99 |
+
tokens = torch.tensor(
|
| 100 |
+
self.guide.get_next_instruction(self.state).tokens, dtype=torch.int64
|
| 101 |
+
).to(vocab_mask.device, non_blocking=True)
|
| 102 |
+
vocab_mask = vocab_mask[idx]
|
| 103 |
+
vocab_mask.fill_(1)
|
| 104 |
+
vocab_mask.scatter_(0, tokens, torch.zeros_like(tokens, dtype=torch.bool))
|
| 105 |
+
|
| 106 |
+
@staticmethod
|
| 107 |
+
def apply_vocab_mask(logits: torch.Tensor, vocab_mask: torch.Tensor):
|
| 108 |
+
logits.masked_fill_(vocab_mask, float("-inf"))
|
| 109 |
+
|
| 110 |
+
def copy(self):
|
| 111 |
+
return OutlinesGrammar(self.guide, self.jump_forward_map)
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
class OutlinesGrammarBackend(BaseGrammarBackend):
|
| 115 |
+
def __init__(
|
| 116 |
+
self,
|
| 117 |
+
tokenizer,
|
| 118 |
+
whitespace_pattern: bool,
|
| 119 |
+
):
|
| 120 |
+
super().__init__()
|
| 121 |
+
|
| 122 |
+
try:
|
| 123 |
+
self.outlines_tokenizer = TransformerTokenizer(tokenizer)
|
| 124 |
+
except AttributeError:
|
| 125 |
+
# FIXME: tmp fix for chatglm2 & chatglm3 (pad_token_id=0)
|
| 126 |
+
origin_pad_token_id = tokenizer.pad_token_id
|
| 127 |
+
|
| 128 |
+
def fset(self, value):
|
| 129 |
+
self._value = value
|
| 130 |
+
|
| 131 |
+
type(tokenizer).pad_token_id = property(
|
| 132 |
+
fget=type(tokenizer).pad_token_id.fget, fset=fset
|
| 133 |
+
)
|
| 134 |
+
self.outlines_tokenizer = TransformerTokenizer(tokenizer)
|
| 135 |
+
self.outlines_tokenizer.tokenizer.pad_token_id = origin_pad_token_id
|
| 136 |
+
self.outlines_tokenizer.pad_token_id = origin_pad_token_id
|
| 137 |
+
self.outlines_tokenizer.pad_token = (
|
| 138 |
+
self.outlines_tokenizer.tokenizer.pad_token
|
| 139 |
+
)
|
| 140 |
+
self.outlines_tokenizer.vocabulary = (
|
| 141 |
+
self.outlines_tokenizer.tokenizer.get_vocab()
|
| 142 |
+
)
|
| 143 |
+
self.whitespace_pattern = whitespace_pattern
|
| 144 |
+
|
| 145 |
+
def _compile_regex(self, regex: str) -> Optional[OutlinesGrammar]:
|
| 146 |
+
try:
|
| 147 |
+
if hasattr(RegexGuide, "from_regex"):
|
| 148 |
+
# outlines >= 0.1.1
|
| 149 |
+
guide = RegexGuide.from_regex(regex, self.outlines_tokenizer)
|
| 150 |
+
else:
|
| 151 |
+
# outlines <= 0.0.46
|
| 152 |
+
guide = RegexGuide(regex, self.outlines_tokenizer)
|
| 153 |
+
except interegular.patterns.InvalidSyntax as e:
|
| 154 |
+
logger.warning(f"skip invalid regex schema: {regex=}, {e=}")
|
| 155 |
+
return None
|
| 156 |
+
|
| 157 |
+
jump_forward_map = None
|
| 158 |
+
return OutlinesGrammar(guide, jump_forward_map)
|
| 159 |
+
|
| 160 |
+
def dispatch_ebnf(self, key_string: str):
|
| 161 |
+
return super().dispatch_ebnf(key_string)
|
| 162 |
+
|
| 163 |
+
def dispatch_structural_tag(self, key_string: str):
|
| 164 |
+
return super().dispatch_structural_tag(key_string)
|
| 165 |
+
|
| 166 |
+
def dispatch_json(self, key_string: str):
|
| 167 |
+
try:
|
| 168 |
+
regex = build_regex_from_object(
|
| 169 |
+
key_string,
|
| 170 |
+
whitespace_pattern=self.whitespace_pattern,
|
| 171 |
+
)
|
| 172 |
+
except (NotImplementedError, json.decoder.JSONDecodeError) as e:
|
| 173 |
+
logger.warning(f"Skip invalid json_schema: json_schema={key_string}, {e=}")
|
| 174 |
+
return self._compile_regex(regex)
|
| 175 |
+
|
| 176 |
+
def dispatch_regex(self, key_string: str):
|
| 177 |
+
return self._compile_regex(key_string)
|
| 178 |
+
|
| 179 |
+
|
| 180 |
+
def build_regex_from_object(
|
| 181 |
+
object: Union[str, BaseModel, Dict], whitespace_pattern: Optional[str] = None
|
| 182 |
+
):
|
| 183 |
+
if isinstance(object, type(BaseModel)):
|
| 184 |
+
schema = json.dumps(object.model_json_schema())
|
| 185 |
+
elif isinstance(object, Dict):
|
| 186 |
+
schema = json.dumps(object)
|
| 187 |
+
else:
|
| 188 |
+
schema = object
|
| 189 |
+
return build_regex_from_schema(schema, whitespace_pattern)
|
lib/python3.12/site-packages/sglang/srt/constrained/outlines_jump_forward.py
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2023-2024 SGLang Team
|
| 2 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 3 |
+
# you may not use this file except in compliance with the License.
|
| 4 |
+
# You may obtain a copy of the License at
|
| 5 |
+
#
|
| 6 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 7 |
+
#
|
| 8 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 9 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 10 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 11 |
+
# See the License for the specific language governing permissions and
|
| 12 |
+
# limitations under the License.
|
| 13 |
+
# ==============================================================================
|
| 14 |
+
"""
|
| 15 |
+
Faster constrained decoding with jump forward decoding / compressed finite state machine.
|
| 16 |
+
Reference: https://lmsys.org/blog/2024-02-05-compressed-fsm/
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
import dataclasses
|
| 20 |
+
import logging
|
| 21 |
+
from collections import defaultdict
|
| 22 |
+
from typing import Optional
|
| 23 |
+
|
| 24 |
+
import interegular
|
| 25 |
+
from interegular import InvalidSyntax
|
| 26 |
+
from outlines.caching import cache
|
| 27 |
+
|
| 28 |
+
from sglang.srt.utils import get_bool_env_var
|
| 29 |
+
|
| 30 |
+
try:
|
| 31 |
+
# outlines >= 0.1.0
|
| 32 |
+
from outlines_core.fsm.outlines_core_rs import FSMInfo
|
| 33 |
+
from outlines_core.fsm.regex import make_byte_level_fsm, make_deterministic_fsm
|
| 34 |
+
except ImportError:
|
| 35 |
+
# outlines <= 0.0.46
|
| 36 |
+
from outlines.fsm.regex import FSMInfo, make_byte_level_fsm, make_deterministic_fsm
|
| 37 |
+
|
| 38 |
+
IP_REGEX = r"((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)"
|
| 39 |
+
|
| 40 |
+
# Env var was set in sglang.srt.server_args.ServerArgs.__post__init__
|
| 41 |
+
DISABLE_DISK_CACHE = get_bool_env_var("SGLANG_DISABLE_OUTLINES_DISK_CACHE", "true")
|
| 42 |
+
|
| 43 |
+
logger = logging.getLogger(__name__)
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
@dataclasses.dataclass
|
| 47 |
+
class JumpEdge:
|
| 48 |
+
symbol: str = None
|
| 49 |
+
symbol_next_state: int = None
|
| 50 |
+
byte: int = None
|
| 51 |
+
byte_next_state: int = None
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def disk_cache(expire: Optional[float] = None, typed=False, ignore=()):
|
| 55 |
+
if not DISABLE_DISK_CACHE:
|
| 56 |
+
return cache(expire, typed, ignore)
|
| 57 |
+
else:
|
| 58 |
+
return lambda fn: None
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
@disk_cache()
|
| 62 |
+
def init_state_to_jump_forward(regex_string):
|
| 63 |
+
try:
|
| 64 |
+
regex_pattern = interegular.parse_pattern(regex_string)
|
| 65 |
+
except InvalidSyntax as e:
|
| 66 |
+
logger.warning(f"skip invalid regex: {regex_string}, {e=}")
|
| 67 |
+
return
|
| 68 |
+
|
| 69 |
+
byte_fsm = make_byte_level_fsm(regex_pattern.to_fsm().reduce(), keep_utf8=True)
|
| 70 |
+
regex_fsm, _ = make_deterministic_fsm(byte_fsm)
|
| 71 |
+
|
| 72 |
+
fsm_info: FSMInfo = regex_fsm.fsm_info
|
| 73 |
+
|
| 74 |
+
symbol_to_id = fsm_info.alphabet_symbol_mapping
|
| 75 |
+
id_to_symbol = {}
|
| 76 |
+
for symbol, id_ in symbol_to_id.items():
|
| 77 |
+
id_to_symbol.setdefault(id_, []).append(symbol)
|
| 78 |
+
|
| 79 |
+
transitions = fsm_info.transitions
|
| 80 |
+
|
| 81 |
+
outgoings_ct = defaultdict(int)
|
| 82 |
+
# NOTE(lsyin): Final states can lead to terminate, so they have one outgoing edge naturally
|
| 83 |
+
for s in fsm_info.finals:
|
| 84 |
+
outgoings_ct[s] = 1
|
| 85 |
+
|
| 86 |
+
state_to_jump_forward = {}
|
| 87 |
+
for (state, id_), next_state in transitions.items():
|
| 88 |
+
if id_ == fsm_info.alphabet_anything_value:
|
| 89 |
+
# Arbitrarily symbol cannot be recognized as jump forward
|
| 90 |
+
continue
|
| 91 |
+
|
| 92 |
+
symbols = id_to_symbol[id_]
|
| 93 |
+
for c in symbols:
|
| 94 |
+
if len(c) > 1:
|
| 95 |
+
# Skip byte level transitions like c = "5E"
|
| 96 |
+
continue
|
| 97 |
+
|
| 98 |
+
outgoings_ct[state] += 1
|
| 99 |
+
if outgoings_ct[state] > 1:
|
| 100 |
+
if state in state_to_jump_forward:
|
| 101 |
+
del state_to_jump_forward[state]
|
| 102 |
+
break
|
| 103 |
+
|
| 104 |
+
state_to_jump_forward[state] = JumpEdge(
|
| 105 |
+
symbol=c,
|
| 106 |
+
symbol_next_state=next_state,
|
| 107 |
+
)
|
| 108 |
+
|
| 109 |
+
# Process the byte level jump forward
|
| 110 |
+
outgoings_ct = defaultdict(int)
|
| 111 |
+
for s in fsm_info.finals:
|
| 112 |
+
outgoings_ct[s] = 1
|
| 113 |
+
|
| 114 |
+
for (state, id_), next_state in transitions.items():
|
| 115 |
+
if id_ == fsm_info.alphabet_anything_value:
|
| 116 |
+
continue
|
| 117 |
+
symbols = id_to_symbol[id_]
|
| 118 |
+
for c in symbols:
|
| 119 |
+
byte_ = None
|
| 120 |
+
if len(c) == 1 and ord(c) < 0x80:
|
| 121 |
+
# ASCII character
|
| 122 |
+
byte_ = ord(c)
|
| 123 |
+
elif len(c) > 1:
|
| 124 |
+
# FIXME: This logic is due to the leading \x00
|
| 125 |
+
# https://github.com/outlines-dev/outlines/pull/930
|
| 126 |
+
byte_ = int(symbols[0][1:], 16)
|
| 127 |
+
|
| 128 |
+
if byte_ is not None:
|
| 129 |
+
outgoings_ct[state] += 1
|
| 130 |
+
if outgoings_ct[state] > 1:
|
| 131 |
+
if state in state_to_jump_forward:
|
| 132 |
+
del state_to_jump_forward[state]
|
| 133 |
+
break
|
| 134 |
+
e = state_to_jump_forward.get(state, JumpEdge())
|
| 135 |
+
e.byte = byte_
|
| 136 |
+
e.byte_next_state = next_state
|
| 137 |
+
state_to_jump_forward[state] = e
|
| 138 |
+
|
| 139 |
+
return state_to_jump_forward
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
class OutlinesJumpForwardMap:
|
| 143 |
+
def __init__(self, regex_string):
|
| 144 |
+
self.state_to_jump_forward = init_state_to_jump_forward(regex_string)
|
| 145 |
+
|
| 146 |
+
def jump_forward_symbol(self, state):
|
| 147 |
+
jump_forward_str = ""
|
| 148 |
+
next_state = state
|
| 149 |
+
while state in self.state_to_jump_forward:
|
| 150 |
+
e = self.state_to_jump_forward[state]
|
| 151 |
+
if e.symbol is None:
|
| 152 |
+
break
|
| 153 |
+
jump_forward_str += e.symbol
|
| 154 |
+
next_state = e.symbol_next_state
|
| 155 |
+
state = next_state
|
| 156 |
+
|
| 157 |
+
return jump_forward_str, next_state
|
| 158 |
+
|
| 159 |
+
def jump_forward_byte(self, state):
|
| 160 |
+
if state not in self.state_to_jump_forward:
|
| 161 |
+
return None
|
| 162 |
+
|
| 163 |
+
jump_forward_bytes = []
|
| 164 |
+
next_state = None
|
| 165 |
+
while state in self.state_to_jump_forward:
|
| 166 |
+
e = self.state_to_jump_forward[state]
|
| 167 |
+
assert e.byte is not None and e.byte_next_state is not None
|
| 168 |
+
jump_forward_bytes.append((e.byte, e.byte_next_state))
|
| 169 |
+
next_state = e.byte_next_state
|
| 170 |
+
state = next_state
|
| 171 |
+
|
| 172 |
+
return jump_forward_bytes
|
| 173 |
+
|
| 174 |
+
def is_jump_forward_symbol_state(self, state):
|
| 175 |
+
return (
|
| 176 |
+
state in self.state_to_jump_forward
|
| 177 |
+
and self.state_to_jump_forward[state].symbol is not None
|
| 178 |
+
)
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
def test_main(regex_string):
|
| 182 |
+
jump_forward_map = OutlinesJumpForwardMap(regex_string)
|
| 183 |
+
for state, e in jump_forward_map.state_to_jump_forward.items():
|
| 184 |
+
if e.symbol is not None:
|
| 185 |
+
jump_forward_str, next_state = jump_forward_map.jump_forward_symbol(state)
|
| 186 |
+
print(f"{state} -> {next_state}", jump_forward_str)
|
| 187 |
+
bytes_ = jump_forward_map.jump_forward_byte(state)
|
| 188 |
+
print(f"{state} -> {bytes_[-1][1]}", [hex(b) for b, _ in bytes_])
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
if __name__ == "__main__":
|
| 192 |
+
import outlines
|
| 193 |
+
|
| 194 |
+
outlines.caching.clear_cache()
|
| 195 |
+
test_main(r"The google's DNS sever address is " + IP_REGEX)
|
| 196 |
+
test_main(r"霍格沃茨特快列车|霍比特人比尔博")
|
| 197 |
+
# 霍格: \xe9\x9c\x8d \xe6\xa0\xbc ...
|
| 198 |
+
# 霍比: \xe9\x9c\x8d \xe6\xaf\x94 ...
|
| 199 |
+
|
| 200 |
+
test_main(r"[-+]?[0-9]+[ ]*")
|
lib/python3.12/site-packages/sglang/srt/constrained/reasoner_grammar_backend.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2023-2024 SGLang Team
|
| 2 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 3 |
+
# you may not use this file except in compliance with the License.
|
| 4 |
+
# You may obtain a copy of the License at
|
| 5 |
+
#
|
| 6 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 7 |
+
#
|
| 8 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 9 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 10 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 11 |
+
# See the License for the specific language governing permissions and
|
| 12 |
+
# limitations under the License.
|
| 13 |
+
# ==============================================================================
|
| 14 |
+
"""The baseclass of a backend for reasoner grammar-guided constrained decoding."""
|
| 15 |
+
|
| 16 |
+
from concurrent.futures import Future
|
| 17 |
+
from typing import List, Optional, Tuple
|
| 18 |
+
|
| 19 |
+
import torch
|
| 20 |
+
|
| 21 |
+
from .base_grammar_backend import BaseGrammarBackend, BaseGrammarObject
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class ReasonerGrammarObject(BaseGrammarObject):
|
| 25 |
+
def __init__(self, grammar: BaseGrammarObject, think_end_id):
|
| 26 |
+
super().__init__()
|
| 27 |
+
self.grammar = grammar
|
| 28 |
+
self.think_end_id = think_end_id
|
| 29 |
+
self.is_in_reasoning = True
|
| 30 |
+
|
| 31 |
+
@property
|
| 32 |
+
def finished(self):
|
| 33 |
+
return self.grammar.finished
|
| 34 |
+
|
| 35 |
+
@finished.setter
|
| 36 |
+
def finished(self, finished):
|
| 37 |
+
self.grammar.finished = finished
|
| 38 |
+
|
| 39 |
+
def allocate_vocab_mask(
|
| 40 |
+
self, vocab_size: int, batch_size: int, device
|
| 41 |
+
) -> torch.Tensor:
|
| 42 |
+
return self.grammar.allocate_vocab_mask(vocab_size, batch_size, device)
|
| 43 |
+
|
| 44 |
+
def fill_vocab_mask(self, vocab_mask: torch.Tensor, idx: int) -> None:
|
| 45 |
+
if not self.is_in_reasoning:
|
| 46 |
+
self.grammar.fill_vocab_mask(vocab_mask, idx)
|
| 47 |
+
|
| 48 |
+
def move_vocab_mask(self, vocab_mask: torch.Tensor, device) -> torch.Tensor:
|
| 49 |
+
return self.grammar.move_vocab_mask(vocab_mask, device)
|
| 50 |
+
|
| 51 |
+
@property
|
| 52 |
+
def apply_vocab_mask(self):
|
| 53 |
+
return self.grammar.apply_vocab_mask
|
| 54 |
+
|
| 55 |
+
def accept_token(self, token: int):
|
| 56 |
+
if token == self.think_end_id:
|
| 57 |
+
self.is_in_reasoning = False
|
| 58 |
+
|
| 59 |
+
if not self.is_in_reasoning and token != self.think_end_id:
|
| 60 |
+
self.grammar.accept_token(token)
|
| 61 |
+
|
| 62 |
+
def try_jump_forward(self, tokenizer):
|
| 63 |
+
return self.grammar.try_jump_forward(tokenizer)
|
| 64 |
+
|
| 65 |
+
def jump_forward_str_state(self, helper):
|
| 66 |
+
return self.grammar.jump_forward_str_state(helper)
|
| 67 |
+
|
| 68 |
+
def jump_and_retokenize(
|
| 69 |
+
self, old_output_ids: List[int], new_output_ids: List[int], next_state: int
|
| 70 |
+
):
|
| 71 |
+
return self.grammar.jump_and_retokenize(
|
| 72 |
+
old_output_ids, new_output_ids, next_state
|
| 73 |
+
)
|
| 74 |
+
|
| 75 |
+
def copy(self) -> BaseGrammarObject:
|
| 76 |
+
return ReasonerGrammarObject(self.grammar.copy(), self.think_end_id)
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
class ReasonerGrammarBackend(BaseGrammarBackend):
|
| 80 |
+
def __init__(self, grammar_backend: BaseGrammarBackend, think_end_id):
|
| 81 |
+
self.grammar_backend = grammar_backend
|
| 82 |
+
self.think_end_id = think_end_id
|
| 83 |
+
|
| 84 |
+
def get_cached_value(self, key: Tuple[str, str]) -> Optional[ReasonerGrammarObject]:
|
| 85 |
+
grammar = self.grammar_backend.get_cached_value(key)
|
| 86 |
+
return ReasonerGrammarObject(grammar, self.think_end_id) if grammar else None
|
| 87 |
+
|
| 88 |
+
def get_future_value(self, key: Tuple[str, str]) -> Future:
|
| 89 |
+
grammar = Future()
|
| 90 |
+
|
| 91 |
+
def callback(f: Future):
|
| 92 |
+
if result := f.result():
|
| 93 |
+
grammar.set_result(ReasonerGrammarObject(result, self.think_end_id))
|
| 94 |
+
else:
|
| 95 |
+
grammar.set_result(None)
|
| 96 |
+
|
| 97 |
+
self.grammar_backend.get_future_value(key).add_done_callback(callback)
|
| 98 |
+
return grammar
|
| 99 |
+
|
| 100 |
+
def reset(self):
|
| 101 |
+
self.grammar_backend.reset()
|
lib/python3.12/site-packages/sglang/srt/constrained/triton_ops/__pycache__/bitmask_ops.cpython-312.pyc
ADDED
|
Binary file (5.46 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/constrained/triton_ops/bitmask_ops.py
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Adapt from
|
| 2 |
+
# https://github.com/mlc-ai/xgrammar/blob/v0.1.17/python/xgrammar/kernels/apply_token_bitmask_inplace_triton.py
|
| 3 |
+
|
| 4 |
+
from typing import List, Optional, Union
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
import triton
|
| 8 |
+
import triton.language as tl
|
| 9 |
+
|
| 10 |
+
from sglang.srt.utils import get_device_core_count
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
@triton.jit
|
| 14 |
+
def apply_token_bitmask_inplace_kernel(
|
| 15 |
+
logits_ptr,
|
| 16 |
+
bitmask_ptr,
|
| 17 |
+
indices_ptr,
|
| 18 |
+
num_rows,
|
| 19 |
+
vocab_size,
|
| 20 |
+
logits_strides,
|
| 21 |
+
bitmask_strides,
|
| 22 |
+
NUM_SMS: tl.constexpr,
|
| 23 |
+
BLOCK_SIZE: tl.constexpr,
|
| 24 |
+
):
|
| 25 |
+
"""Apply a bitmask to logits in-place using Triton. The bitmask is a 01 bitwise compressed tensor,
|
| 26 |
+
where 0 means the token is masked and 1 means the token is not masked. After applying the bitmask,
|
| 27 |
+
the masked logits will be set to -inf.
|
| 28 |
+
|
| 29 |
+
Parameters
|
| 30 |
+
----------
|
| 31 |
+
logits_ptr : tl.tensor
|
| 32 |
+
Pointer to the logits tensor to apply the bitmask to.
|
| 33 |
+
|
| 34 |
+
bitmask_ptr : tl.tensor
|
| 35 |
+
Pointer to the bitmask tensor to apply.
|
| 36 |
+
|
| 37 |
+
indices_ptr : Optional[tl.tensor]
|
| 38 |
+
Optional pointer to indices tensor specifying which rows to apply the mask to.
|
| 39 |
+
|
| 40 |
+
num_rows : int
|
| 41 |
+
Number of rows to process. If indices_ptr is provided, this is the number of unique indices.
|
| 42 |
+
|
| 43 |
+
vocab_size : int
|
| 44 |
+
Size of the vocabulary dimension. If the logits does not have a vocab padding, this is the
|
| 45 |
+
same as the logits's second dimension. Otherwise, this is the actual size of the vocabulary.
|
| 46 |
+
|
| 47 |
+
logits_strides : int
|
| 48 |
+
Stride between rows in the logits tensor.
|
| 49 |
+
|
| 50 |
+
bitmask_strides : int
|
| 51 |
+
Stride between rows in the bitmask tensor.
|
| 52 |
+
|
| 53 |
+
NUM_SMS : int
|
| 54 |
+
Number of streaming multiprocessors to use.
|
| 55 |
+
|
| 56 |
+
BLOCK_SIZE : int
|
| 57 |
+
Size of processing blocks.
|
| 58 |
+
"""
|
| 59 |
+
|
| 60 |
+
pid = tl.program_id(0)
|
| 61 |
+
num_blocks = tl.cdiv(vocab_size, BLOCK_SIZE)
|
| 62 |
+
for work_id in tl.range(pid, num_rows * num_blocks, NUM_SMS):
|
| 63 |
+
row_id = work_id // num_blocks
|
| 64 |
+
block_offset = (work_id % num_blocks) * BLOCK_SIZE
|
| 65 |
+
batch_id = row_id if indices_ptr is None else tl.load(indices_ptr + row_id)
|
| 66 |
+
offsets = block_offset + tl.arange(0, BLOCK_SIZE)
|
| 67 |
+
bitmask_offsets = block_offset // 32 + tl.arange(0, BLOCK_SIZE // 32)
|
| 68 |
+
vocab_mask = offsets < vocab_size
|
| 69 |
+
packed_bitmask_mask = bitmask_offsets < bitmask_strides
|
| 70 |
+
packed_bitmask = tl.load(
|
| 71 |
+
bitmask_ptr + batch_id * bitmask_strides + bitmask_offsets,
|
| 72 |
+
packed_bitmask_mask,
|
| 73 |
+
)
|
| 74 |
+
bitmask = ((packed_bitmask[:, None] >> (tl.arange(0, 32)[None, :])) & 1) == 0
|
| 75 |
+
bitmask = bitmask.reshape(BLOCK_SIZE)
|
| 76 |
+
|
| 77 |
+
tl.store(
|
| 78 |
+
logits_ptr + batch_id * logits_strides + offsets,
|
| 79 |
+
-float("inf"),
|
| 80 |
+
vocab_mask & bitmask,
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def apply_token_bitmask_inplace_triton(
|
| 85 |
+
logits: torch.Tensor,
|
| 86 |
+
bitmask: torch.Tensor,
|
| 87 |
+
indices: Optional[Union[List[int], torch.Tensor]] = None,
|
| 88 |
+
):
|
| 89 |
+
NUM_SMS = get_device_core_count()
|
| 90 |
+
BLOCK_SIZE = 4096
|
| 91 |
+
BITS_PER_BLOCK = 32
|
| 92 |
+
|
| 93 |
+
# Check input dtype
|
| 94 |
+
assert bitmask.dtype == torch.int32, "bitmask must be of type int32"
|
| 95 |
+
|
| 96 |
+
# Check input tensor shapes.
|
| 97 |
+
logits_shape = logits.shape
|
| 98 |
+
bitmask_shape = bitmask.shape
|
| 99 |
+
if logits.ndim == 1:
|
| 100 |
+
logits_shape = (1, logits_shape[0])
|
| 101 |
+
if bitmask.ndim == 1:
|
| 102 |
+
bitmask_shape = (1, bitmask_shape[0])
|
| 103 |
+
|
| 104 |
+
required_bitmask_width = (logits_shape[1] + BITS_PER_BLOCK - 1) // BITS_PER_BLOCK
|
| 105 |
+
assert required_bitmask_width >= bitmask_shape[1], (
|
| 106 |
+
f"Bitmask width too large: allow at most {required_bitmask_width} int32s for "
|
| 107 |
+
f"logits' width {logits_shape[1]}, but got {bitmask_shape[1]}"
|
| 108 |
+
)
|
| 109 |
+
|
| 110 |
+
vocab_size = min(logits_shape[1], bitmask_shape[1] * BITS_PER_BLOCK)
|
| 111 |
+
|
| 112 |
+
num_rows = None
|
| 113 |
+
if isinstance(indices, list) or isinstance(indices, torch.Tensor):
|
| 114 |
+
indices = torch.tensor(indices, dtype=torch.int32, device=logits.device)
|
| 115 |
+
num_rows = indices.shape[0]
|
| 116 |
+
else:
|
| 117 |
+
assert (
|
| 118 |
+
logits_shape[0] == bitmask_shape[0]
|
| 119 |
+
), f"batch size mismatch: logits {logits_shape[0]} vs bitmask {bitmask_shape[0]}"
|
| 120 |
+
num_rows = logits_shape[0]
|
| 121 |
+
|
| 122 |
+
if NUM_SMS > 0:
|
| 123 |
+
grid = (NUM_SMS,)
|
| 124 |
+
else:
|
| 125 |
+
num_blocks = triton.cdiv(vocab_size, BLOCK_SIZE)
|
| 126 |
+
grid = (num_rows * num_blocks,)
|
| 127 |
+
NUM_SMS = triton.next_power_of_2(grid[0])
|
| 128 |
+
|
| 129 |
+
apply_token_bitmask_inplace_kernel[grid](
|
| 130 |
+
logits,
|
| 131 |
+
bitmask,
|
| 132 |
+
indices,
|
| 133 |
+
num_rows,
|
| 134 |
+
vocab_size,
|
| 135 |
+
logits_shape[1],
|
| 136 |
+
bitmask_shape[1],
|
| 137 |
+
NUM_SMS,
|
| 138 |
+
BLOCK_SIZE,
|
| 139 |
+
num_warps=BLOCK_SIZE // 32 // (16 // logits.element_size()),
|
| 140 |
+
num_stages=3,
|
| 141 |
+
)
|
lib/python3.12/site-packages/sglang/srt/constrained/xgrammar_backend.py
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2023-2024 SGLang Team
|
| 2 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 3 |
+
# you may not use this file except in compliance with the License.
|
| 4 |
+
# You may obtain a copy of the License at
|
| 5 |
+
#
|
| 6 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 7 |
+
#
|
| 8 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 9 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 10 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 11 |
+
# See the License for the specific language governing permissions and
|
| 12 |
+
# limitations under the License.
|
| 13 |
+
# ==============================================================================
|
| 14 |
+
"""Constrained decoding with xgrammar backend."""
|
| 15 |
+
|
| 16 |
+
import json
|
| 17 |
+
import logging
|
| 18 |
+
from typing import List, Optional, Tuple, Union
|
| 19 |
+
|
| 20 |
+
import torch
|
| 21 |
+
from xgrammar import (
|
| 22 |
+
CompiledGrammar,
|
| 23 |
+
GrammarCompiler,
|
| 24 |
+
GrammarMatcher,
|
| 25 |
+
StructuralTagItem,
|
| 26 |
+
TokenizerInfo,
|
| 27 |
+
allocate_token_bitmask,
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
from sglang.srt.constrained.base_grammar_backend import (
|
| 31 |
+
BaseGrammarBackend,
|
| 32 |
+
BaseGrammarObject,
|
| 33 |
+
)
|
| 34 |
+
from sglang.srt.constrained.triton_ops.bitmask_ops import (
|
| 35 |
+
apply_token_bitmask_inplace_triton,
|
| 36 |
+
)
|
| 37 |
+
from sglang.srt.utils import get_bool_env_var
|
| 38 |
+
|
| 39 |
+
logger = logging.getLogger(__name__)
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
MAX_ROLLBACK_TOKENS = 200
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
class XGrammarGrammar(BaseGrammarObject):
|
| 46 |
+
|
| 47 |
+
def __init__(
|
| 48 |
+
self,
|
| 49 |
+
matcher: GrammarMatcher,
|
| 50 |
+
vocab_size: int,
|
| 51 |
+
ctx: CompiledGrammar,
|
| 52 |
+
override_stop_tokens: Optional[Union[List[int], int]],
|
| 53 |
+
) -> None:
|
| 54 |
+
super().__init__()
|
| 55 |
+
self.matcher = matcher
|
| 56 |
+
self.vocab_size = vocab_size
|
| 57 |
+
self.ctx = ctx
|
| 58 |
+
self.override_stop_tokens = override_stop_tokens
|
| 59 |
+
self.finished = False
|
| 60 |
+
|
| 61 |
+
# Fix (from vLLM team): postpone the import of apply_token_bitmask_inplace_kernels to the
|
| 62 |
+
# class init site to avoid re-initializing CUDA in forked subprocess.
|
| 63 |
+
from xgrammar.kernels import apply_token_bitmask_inplace_kernels
|
| 64 |
+
|
| 65 |
+
self.use_token_bitmask_triton = get_bool_env_var(
|
| 66 |
+
"SGLANG_TOKEN_BITMASK_TRITON", "false"
|
| 67 |
+
)
|
| 68 |
+
self.apply_vocab_mask_cuda = apply_token_bitmask_inplace_kernels.get(
|
| 69 |
+
"cuda", None
|
| 70 |
+
)
|
| 71 |
+
self.apply_vocab_mask_cpu = apply_token_bitmask_inplace_kernels.get("cpu", None)
|
| 72 |
+
|
| 73 |
+
def accept_token(self, token: int):
|
| 74 |
+
assert self.matcher.accept_token(token)
|
| 75 |
+
|
| 76 |
+
def try_jump_forward(self, tokenizer) -> Optional[Tuple[List[int], str]]:
|
| 77 |
+
s = self.matcher.find_jump_forward_string()
|
| 78 |
+
if s:
|
| 79 |
+
return [], s
|
| 80 |
+
return None
|
| 81 |
+
|
| 82 |
+
def jump_forward_str_state(self, helper: Tuple[List[int], str]) -> Tuple[str, int]:
|
| 83 |
+
_, data = helper
|
| 84 |
+
return data, -1
|
| 85 |
+
|
| 86 |
+
def jump_and_retokenize(
|
| 87 |
+
self, old_output_ids: List[int], new_output_ids: List[int], next_state: int
|
| 88 |
+
):
|
| 89 |
+
k = 0
|
| 90 |
+
for i, old_id in enumerate(old_output_ids):
|
| 91 |
+
if old_id == new_output_ids[i]:
|
| 92 |
+
k = i + 1
|
| 93 |
+
else:
|
| 94 |
+
break
|
| 95 |
+
|
| 96 |
+
# rollback to the last token that is the same
|
| 97 |
+
if k < len(old_output_ids):
|
| 98 |
+
self.matcher.rollback(len(old_output_ids) - k)
|
| 99 |
+
|
| 100 |
+
for i in range(k, len(new_output_ids)):
|
| 101 |
+
assert self.matcher.accept_token(new_output_ids[i])
|
| 102 |
+
|
| 103 |
+
def allocate_vocab_mask(
|
| 104 |
+
self, vocab_size: int, batch_size: int, device
|
| 105 |
+
) -> torch.Tensor:
|
| 106 |
+
return allocate_token_bitmask(batch_size, vocab_size)
|
| 107 |
+
|
| 108 |
+
def fill_vocab_mask(self, vocab_mask: torch.Tensor, idx: int) -> None:
|
| 109 |
+
self.matcher.fill_next_token_bitmask(vocab_mask, idx)
|
| 110 |
+
|
| 111 |
+
@staticmethod
|
| 112 |
+
def move_vocab_mask(vocab_mask: torch.Tensor, device) -> torch.Tensor:
|
| 113 |
+
return vocab_mask.to(device, non_blocking=True)
|
| 114 |
+
|
| 115 |
+
def apply_vocab_mask(self, logits: torch.Tensor, vocab_mask: torch.Tensor) -> None:
|
| 116 |
+
if (
|
| 117 |
+
not self.use_token_bitmask_triton
|
| 118 |
+
and logits.device.type == "cuda"
|
| 119 |
+
and self.apply_vocab_mask_cuda
|
| 120 |
+
):
|
| 121 |
+
return self.apply_vocab_mask_cuda(logits, vocab_mask)
|
| 122 |
+
if logits.device.type == "cpu" and self.apply_vocab_mask_cpu:
|
| 123 |
+
return self.apply_vocab_mask_cpu(logits, vocab_mask)
|
| 124 |
+
apply_token_bitmask_inplace_triton(logits, vocab_mask)
|
| 125 |
+
|
| 126 |
+
def copy(self):
|
| 127 |
+
matcher = GrammarMatcher(
|
| 128 |
+
self.ctx,
|
| 129 |
+
max_rollback_tokens=MAX_ROLLBACK_TOKENS,
|
| 130 |
+
override_stop_tokens=self.override_stop_tokens,
|
| 131 |
+
)
|
| 132 |
+
return XGrammarGrammar(
|
| 133 |
+
matcher, self.vocab_size, self.ctx, self.override_stop_tokens
|
| 134 |
+
)
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
class XGrammarGrammarBackend(BaseGrammarBackend):
|
| 138 |
+
def __init__(
|
| 139 |
+
self,
|
| 140 |
+
tokenizer,
|
| 141 |
+
vocab_size: int,
|
| 142 |
+
):
|
| 143 |
+
super().__init__()
|
| 144 |
+
|
| 145 |
+
tokenizer_info = TokenizerInfo.from_huggingface(
|
| 146 |
+
tokenizer, vocab_size=vocab_size
|
| 147 |
+
)
|
| 148 |
+
override_stop_tokens = None
|
| 149 |
+
|
| 150 |
+
self.grammar_compiler = GrammarCompiler(tokenizer_info=tokenizer_info)
|
| 151 |
+
self.vocab_size = vocab_size
|
| 152 |
+
self.override_stop_tokens = override_stop_tokens
|
| 153 |
+
|
| 154 |
+
def _from_context(self, ctx: CompiledGrammar) -> XGrammarGrammar:
|
| 155 |
+
matcher = GrammarMatcher(ctx, max_rollback_tokens=MAX_ROLLBACK_TOKENS)
|
| 156 |
+
return XGrammarGrammar(matcher, self.vocab_size, ctx, self.override_stop_tokens)
|
| 157 |
+
|
| 158 |
+
def dispatch_json(self, key_string: str) -> Optional[XGrammarGrammar]:
|
| 159 |
+
try:
|
| 160 |
+
if key_string == "$$ANY$$":
|
| 161 |
+
# Note: This builtin JSON grammar includes *all* valid JSON (including, for example, arrays at the root)
|
| 162 |
+
ctx = self.grammar_compiler.compile_builtin_json_grammar()
|
| 163 |
+
else:
|
| 164 |
+
ctx = self.grammar_compiler.compile_json_schema(schema=key_string)
|
| 165 |
+
except RuntimeError as e:
|
| 166 |
+
logging.warning(f"Skip invalid json_schema: json_schema={key_string}, {e=}")
|
| 167 |
+
return None
|
| 168 |
+
return self._from_context(ctx)
|
| 169 |
+
|
| 170 |
+
def dispatch_ebnf(self, key_string: str) -> Optional[XGrammarGrammar]:
|
| 171 |
+
try:
|
| 172 |
+
ctx = self.grammar_compiler.compile_grammar(key_string)
|
| 173 |
+
except RuntimeError as e:
|
| 174 |
+
logging.warning(f"Skip invalid ebnf: ebnf={key_string}, {e=}")
|
| 175 |
+
return None
|
| 176 |
+
return self._from_context(ctx)
|
| 177 |
+
|
| 178 |
+
def dispatch_regex(self, key_string: str) -> Optional[XGrammarGrammar]:
|
| 179 |
+
try:
|
| 180 |
+
ctx = self.grammar_compiler.compile_regex(key_string)
|
| 181 |
+
except RuntimeError as e:
|
| 182 |
+
logging.warning(f"Skip invalid regex: regex={key_string}, {e=}")
|
| 183 |
+
return None
|
| 184 |
+
return self._from_context(ctx)
|
| 185 |
+
|
| 186 |
+
def dispatch_structural_tag(self, key_string: str) -> Optional[XGrammarGrammar]:
|
| 187 |
+
try:
|
| 188 |
+
structural_tag = json.loads(key_string)
|
| 189 |
+
tags = [
|
| 190 |
+
StructuralTagItem(
|
| 191 |
+
begin=structure["begin"],
|
| 192 |
+
schema=json.dumps(structure["schema"]),
|
| 193 |
+
end=structure["end"],
|
| 194 |
+
)
|
| 195 |
+
for structure in structural_tag["structures"]
|
| 196 |
+
]
|
| 197 |
+
ctx = self.grammar_compiler.compile_structural_tag(
|
| 198 |
+
tags, structural_tag["triggers"]
|
| 199 |
+
)
|
| 200 |
+
except RuntimeError as e:
|
| 201 |
+
logging.warning(f"Skip invalid regex: regex={key_string}, {e=}")
|
| 202 |
+
return None
|
| 203 |
+
return self._from_context(ctx)
|
| 204 |
+
|
| 205 |
+
def reset(self):
|
| 206 |
+
if self.grammar_compiler:
|
| 207 |
+
self.grammar_compiler.clear_cache()
|
lib/python3.12/site-packages/sglang/srt/disaggregation/__pycache__/decode.cpython-312.pyc
ADDED
|
Binary file (31.5 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/disaggregation/__pycache__/mini_lb.cpython-312.pyc
ADDED
|
Binary file (15.8 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/disaggregation/__pycache__/prefill.cpython-312.pyc
ADDED
|
Binary file (18.4 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/disaggregation/__pycache__/utils.cpython-312.pyc
ADDED
|
Binary file (5.29 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/disaggregation/base/__init__.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .conn import (
|
| 2 |
+
BaseKVBootstrapServer,
|
| 3 |
+
BaseKVManager,
|
| 4 |
+
BaseKVReceiver,
|
| 5 |
+
BaseKVSender,
|
| 6 |
+
KVArgs,
|
| 7 |
+
KVPoll,
|
| 8 |
+
)
|
lib/python3.12/site-packages/sglang/srt/disaggregation/base/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (380 Bytes). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/disaggregation/base/__pycache__/conn.cpython-312.pyc
ADDED
|
Binary file (4.72 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/disaggregation/base/conn.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from abc import ABC, abstractmethod
|
| 2 |
+
from typing import Optional
|
| 3 |
+
|
| 4 |
+
import numpy as np
|
| 5 |
+
import numpy.typing as npt
|
| 6 |
+
|
| 7 |
+
from sglang.srt.disaggregation.utils import DisaggregationMode
|
| 8 |
+
from sglang.srt.server_args import ServerArgs
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class KVArgs:
|
| 12 |
+
engine_rank: int
|
| 13 |
+
kv_data_ptrs: list[int]
|
| 14 |
+
kv_data_lens: list[int]
|
| 15 |
+
kv_item_lens: list[int]
|
| 16 |
+
aux_data_ptrs: list[int]
|
| 17 |
+
aux_data_lens: list[int]
|
| 18 |
+
aux_item_lens: list[int]
|
| 19 |
+
ib_device: str
|
| 20 |
+
gpu_id: int
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
class KVPoll:
|
| 24 |
+
Failed = 0
|
| 25 |
+
Bootstrapping = 1
|
| 26 |
+
WaitingForInput = 2
|
| 27 |
+
Transferring = 3
|
| 28 |
+
Success = 4
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class BaseKVManager(ABC):
|
| 32 |
+
"""Base class for managing transfers states"""
|
| 33 |
+
|
| 34 |
+
@abstractmethod
|
| 35 |
+
def __init__(
|
| 36 |
+
self,
|
| 37 |
+
args: KVArgs,
|
| 38 |
+
disaggregation_mode: DisaggregationMode,
|
| 39 |
+
server_args: ServerArgs,
|
| 40 |
+
): ...
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
class BaseKVSender(ABC):
|
| 44 |
+
|
| 45 |
+
@abstractmethod
|
| 46 |
+
def __init__(
|
| 47 |
+
self, mgr: BaseKVManager, bootstrap_addr: str, bootstrap_room: int
|
| 48 |
+
): ...
|
| 49 |
+
|
| 50 |
+
@abstractmethod
|
| 51 |
+
def init(self, num_kv_indices: int, aux_index: Optional[int] = None):
|
| 52 |
+
"""
|
| 53 |
+
Notify the decoder server about the kv indices length and aux index
|
| 54 |
+
"""
|
| 55 |
+
...
|
| 56 |
+
|
| 57 |
+
@abstractmethod
|
| 58 |
+
def send(self, kv_indices: npt.NDArray[np.int64]):
|
| 59 |
+
"""
|
| 60 |
+
Send the kv cache at the given kv indices to the decoder server
|
| 61 |
+
"""
|
| 62 |
+
...
|
| 63 |
+
|
| 64 |
+
@abstractmethod
|
| 65 |
+
def poll(self) -> KVPoll:
|
| 66 |
+
"""
|
| 67 |
+
Check the status of the kv cache transfer
|
| 68 |
+
"""
|
| 69 |
+
...
|
| 70 |
+
|
| 71 |
+
@abstractmethod
|
| 72 |
+
def failure_exception(self):
|
| 73 |
+
"""
|
| 74 |
+
Raise an exception if the kv cache transfer fails
|
| 75 |
+
"""
|
| 76 |
+
...
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
class BaseKVReceiver(ABC):
|
| 80 |
+
|
| 81 |
+
@abstractmethod
|
| 82 |
+
def __init__(
|
| 83 |
+
self,
|
| 84 |
+
mgr: BaseKVManager,
|
| 85 |
+
bootstrap_addr: str,
|
| 86 |
+
bootstrap_room: Optional[int] = None,
|
| 87 |
+
): ...
|
| 88 |
+
|
| 89 |
+
@abstractmethod
|
| 90 |
+
def init(self, kv_indices: npt.NDArray[np.int64], aux_index: Optional[int] = None):
|
| 91 |
+
"""
|
| 92 |
+
Notify the prefill server about the kv indices and aux index
|
| 93 |
+
"""
|
| 94 |
+
...
|
| 95 |
+
|
| 96 |
+
@abstractmethod
|
| 97 |
+
def poll(self) -> KVPoll:
|
| 98 |
+
"""
|
| 99 |
+
Check the status of the kv cache transfer
|
| 100 |
+
"""
|
| 101 |
+
...
|
| 102 |
+
|
| 103 |
+
@abstractmethod
|
| 104 |
+
def failure_exception(self):
|
| 105 |
+
"""
|
| 106 |
+
Raise an exception if the kv cache transfer fails
|
| 107 |
+
"""
|
| 108 |
+
...
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
class BaseKVBootstrapServer(ABC):
|
| 112 |
+
@abstractmethod
|
| 113 |
+
def __init__(self, port: int): ...
|
lib/python3.12/site-packages/sglang/srt/disaggregation/mooncake/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (361 Bytes). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/disaggregation/mooncake/__pycache__/conn.cpython-312.pyc
ADDED
|
Binary file (38.8 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/disaggregation/nixl/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
from .conn import NixlKVBootstrapServer, NixlKVManager, NixlKVReceiver, NixlKVSender
|
lib/python3.12/site-packages/sglang/srt/disaggregation/nixl/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (337 Bytes). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/disaggregation/nixl/__pycache__/conn.cpython-312.pyc
ADDED
|
Binary file (34.6 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/disaggregation/nixl/conn.py
ADDED
|
@@ -0,0 +1,622 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import asyncio
|
| 4 |
+
import dataclasses
|
| 5 |
+
import logging
|
| 6 |
+
import queue
|
| 7 |
+
import socket
|
| 8 |
+
import struct
|
| 9 |
+
import threading
|
| 10 |
+
import uuid
|
| 11 |
+
from collections import defaultdict
|
| 12 |
+
from functools import cache
|
| 13 |
+
from typing import Dict, List, Optional, Tuple, Union
|
| 14 |
+
|
| 15 |
+
import numpy as np
|
| 16 |
+
import numpy.typing as npt
|
| 17 |
+
import requests
|
| 18 |
+
import zmq
|
| 19 |
+
from aiohttp import web
|
| 20 |
+
|
| 21 |
+
from sglang.srt.disaggregation.base.conn import (
|
| 22 |
+
BaseKVBootstrapServer,
|
| 23 |
+
BaseKVManager,
|
| 24 |
+
BaseKVReceiver,
|
| 25 |
+
BaseKVSender,
|
| 26 |
+
KVArgs,
|
| 27 |
+
KVPoll,
|
| 28 |
+
)
|
| 29 |
+
from sglang.srt.disaggregation.utils import DisaggregationMode
|
| 30 |
+
from sglang.srt.server_args import ServerArgs
|
| 31 |
+
from sglang.srt.utils import get_free_port, get_ip, get_local_ip_by_remote
|
| 32 |
+
|
| 33 |
+
logger = logging.getLogger(__name__)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
@dataclasses.dataclass
|
| 37 |
+
class TransferInfo:
|
| 38 |
+
room: int
|
| 39 |
+
endpoint: str
|
| 40 |
+
dst_port: int
|
| 41 |
+
agent_metadata: bytes
|
| 42 |
+
dst_kv_ptrs: list[int]
|
| 43 |
+
dst_kv_indices: npt.NDArray[np.int64]
|
| 44 |
+
dst_aux_ptrs: list[int]
|
| 45 |
+
dst_aux_index: int
|
| 46 |
+
dst_gpu_id: int
|
| 47 |
+
|
| 48 |
+
@classmethod
|
| 49 |
+
def from_zmq(cls, msg: List[bytes]):
|
| 50 |
+
return cls(
|
| 51 |
+
room=int(msg[0].decode("ascii")),
|
| 52 |
+
endpoint=msg[1].decode("ascii"),
|
| 53 |
+
dst_port=int(msg[2].decode("ascii")),
|
| 54 |
+
agent_metadata=msg[3],
|
| 55 |
+
dst_kv_ptrs=list(struct.unpack(f"{len(msg[4])//8}Q", msg[4])),
|
| 56 |
+
dst_kv_indices=np.frombuffer(msg[5], dtype=np.int64),
|
| 57 |
+
dst_aux_ptrs=list(struct.unpack(f"{len(msg[6])//8}Q", msg[6])),
|
| 58 |
+
dst_aux_index=int(msg[7].decode("ascii")),
|
| 59 |
+
dst_gpu_id=int(msg[8].decode("ascii")),
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
@dataclasses.dataclass
|
| 64 |
+
class TransferStatus:
|
| 65 |
+
"""Used by KV Receiver to know when a transfer is done."""
|
| 66 |
+
|
| 67 |
+
# KV chunk IDs that have been received.
|
| 68 |
+
received_kvs: Set[int] = dataclasses.field(default_factory=set)
|
| 69 |
+
# Number of kv chunks to expect, will know this after last chunk is received.
|
| 70 |
+
num_kvs_expected: Optional[int] = None
|
| 71 |
+
# Whether aux data has been received.
|
| 72 |
+
received_aux: bool = False
|
| 73 |
+
|
| 74 |
+
def is_done(self):
|
| 75 |
+
if self.num_kvs_expected is None:
|
| 76 |
+
return False
|
| 77 |
+
return self.num_kvs_expected == len(self.received_kvs) and self.received_aux
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
class NixlKVManager(BaseKVManager):
|
| 81 |
+
def __init__(
|
| 82 |
+
self,
|
| 83 |
+
args: KVArgs,
|
| 84 |
+
disaggregation_mode: DisaggregationMode,
|
| 85 |
+
server_args: ServerArgs,
|
| 86 |
+
):
|
| 87 |
+
try:
|
| 88 |
+
from nixl._api import nixl_agent
|
| 89 |
+
except ImportError as e:
|
| 90 |
+
raise ImportError(
|
| 91 |
+
"Please install NIXL by following the instructions at "
|
| 92 |
+
"https://github.com/ai-dynamo/nixl/blob/main/README.md "
|
| 93 |
+
"to run SGLang with NixlTransferEngine."
|
| 94 |
+
) from e
|
| 95 |
+
self.agent = nixl_agent(str(uuid.uuid4()))
|
| 96 |
+
self.kv_args = args
|
| 97 |
+
self.disaggregation_mode = disaggregation_mode
|
| 98 |
+
# for p/d multi node infer
|
| 99 |
+
self.bootstrap_port = server_args.disaggregation_bootstrap_port
|
| 100 |
+
self.dist_init_addr = server_args.dist_init_addr
|
| 101 |
+
self.rank_port = None
|
| 102 |
+
self.server_socket = zmq.Context().socket(zmq.PULL)
|
| 103 |
+
self.register_buffer_to_engine()
|
| 104 |
+
|
| 105 |
+
self.rank_port = get_free_port()
|
| 106 |
+
if self.disaggregation_mode == DisaggregationMode.PREFILL:
|
| 107 |
+
self.transfer_infos: Dict[int, TransferInfo] = {}
|
| 108 |
+
self.condition = threading.Condition()
|
| 109 |
+
self.peer_names: Dict[int, str] = {}
|
| 110 |
+
self._start_bootstrap_thread()
|
| 111 |
+
self._register_to_bootstrap()
|
| 112 |
+
elif self.disaggregation_mode == DisaggregationMode.DECODE:
|
| 113 |
+
self.connection_pool: Dict[str, Dict[str, Union[str, int]]] = {}
|
| 114 |
+
self.transfer_statuses: Dict[int, TransferStatus] = defaultdict(
|
| 115 |
+
TransferStatus
|
| 116 |
+
)
|
| 117 |
+
else:
|
| 118 |
+
raise ValueError(
|
| 119 |
+
f"Unsupported DisaggregationMode: {self.disaggregation_mode}"
|
| 120 |
+
)
|
| 121 |
+
|
| 122 |
+
def register_buffer_to_engine(self):
|
| 123 |
+
kv_addrs = []
|
| 124 |
+
for kv_data_ptr, kv_data_len in zip(
|
| 125 |
+
self.kv_args.kv_data_ptrs, self.kv_args.kv_data_lens
|
| 126 |
+
):
|
| 127 |
+
kv_addrs.append((kv_data_ptr, kv_data_len, self.kv_args.gpu_id, ""))
|
| 128 |
+
self.kv_descs = self.agent.register_memory(kv_addrs, "VRAM", is_sorted=True)
|
| 129 |
+
if not self.kv_descs:
|
| 130 |
+
raise Exception("NIXL memory registration failed for kv tensors")
|
| 131 |
+
aux_addrs = []
|
| 132 |
+
for aux_data_ptr, aux_data_len in zip(
|
| 133 |
+
self.kv_args.aux_data_ptrs, self.kv_args.aux_data_lens
|
| 134 |
+
):
|
| 135 |
+
aux_addrs.append((aux_data_ptr, aux_data_len, 0, ""))
|
| 136 |
+
self.aux_descs = self.agent.register_memory(aux_addrs, "DRAM", is_sorted=True)
|
| 137 |
+
if not self.aux_descs:
|
| 138 |
+
raise Exception("NIXL memory registration failed for aux tensors")
|
| 139 |
+
|
| 140 |
+
@cache
|
| 141 |
+
def _connect(self, endpoint: str):
|
| 142 |
+
socket = zmq.Context().socket(zmq.PUSH)
|
| 143 |
+
socket.connect(endpoint)
|
| 144 |
+
return socket
|
| 145 |
+
|
| 146 |
+
def _add_remote(self, room: int, agent_metadata: bytes):
|
| 147 |
+
if room not in self.peer_names:
|
| 148 |
+
self.peer_names[room] = self.agent.add_remote_agent(agent_metadata)
|
| 149 |
+
return self.peer_names[room]
|
| 150 |
+
|
| 151 |
+
def send_kvcache(
|
| 152 |
+
self,
|
| 153 |
+
peer_name: str,
|
| 154 |
+
prefill_kv_indices: npt.NDArray[np.int64],
|
| 155 |
+
dst_kv_ptrs: list[int],
|
| 156 |
+
dst_kv_indices: npt.NDArray[np.int64],
|
| 157 |
+
dst_gpu_id: int,
|
| 158 |
+
notif: str,
|
| 159 |
+
):
|
| 160 |
+
# Make descs
|
| 161 |
+
num_layers = len(self.kv_args.kv_data_ptrs)
|
| 162 |
+
src_addrs = []
|
| 163 |
+
dst_addrs = []
|
| 164 |
+
for layer_id in range(num_layers):
|
| 165 |
+
src_ptr = self.kv_args.kv_data_ptrs[layer_id]
|
| 166 |
+
dst_ptr = dst_kv_ptrs[layer_id]
|
| 167 |
+
item_len = self.kv_args.kv_item_lens[layer_id]
|
| 168 |
+
|
| 169 |
+
for prefill_index, decode_index in zip(prefill_kv_indices, dst_kv_indices):
|
| 170 |
+
src_addr = src_ptr + int(prefill_index) * item_len
|
| 171 |
+
dst_addr = dst_ptr + int(decode_index) * item_len
|
| 172 |
+
length = item_len
|
| 173 |
+
src_addrs.append((src_addr, length, self.kv_args.gpu_id))
|
| 174 |
+
dst_addrs.append((dst_addr, length, dst_gpu_id))
|
| 175 |
+
src_descs = self.agent.get_xfer_descs(src_addrs, "VRAM", is_sorted=True)
|
| 176 |
+
dst_descs = self.agent.get_xfer_descs(dst_addrs, "VRAM", is_sorted=True)
|
| 177 |
+
# Transfer data
|
| 178 |
+
xfer_handle = self.agent.initialize_xfer(
|
| 179 |
+
"WRITE",
|
| 180 |
+
src_descs,
|
| 181 |
+
dst_descs,
|
| 182 |
+
peer_name,
|
| 183 |
+
notif.encode("ascii"),
|
| 184 |
+
)
|
| 185 |
+
if not xfer_handle:
|
| 186 |
+
raise Exception("KVSender failed to create transfer")
|
| 187 |
+
state = self.agent.transfer(xfer_handle)
|
| 188 |
+
if state == "ERR":
|
| 189 |
+
raise Exception("KVSender failed to post transfer")
|
| 190 |
+
return xfer_handle
|
| 191 |
+
|
| 192 |
+
def send_aux(
|
| 193 |
+
self,
|
| 194 |
+
peer_name: str,
|
| 195 |
+
prefill_aux_index: int,
|
| 196 |
+
dst_aux_ptrs: list[int],
|
| 197 |
+
dst_aux_index: int,
|
| 198 |
+
notif: str,
|
| 199 |
+
):
|
| 200 |
+
# Make descs
|
| 201 |
+
aux_item_len = self.kv_args.aux_item_lens[0]
|
| 202 |
+
prefill_aux_addr = (
|
| 203 |
+
self.kv_args.aux_data_ptrs[0] + prefill_aux_index * aux_item_len
|
| 204 |
+
)
|
| 205 |
+
decode_aux_addr = dst_aux_ptrs[0] + dst_aux_index * aux_item_len
|
| 206 |
+
src_addrs = [(prefill_aux_addr, aux_item_len, 0)]
|
| 207 |
+
dst_addrs = [(decode_aux_addr, aux_item_len, 0)]
|
| 208 |
+
src_descs = self.agent.get_xfer_descs(src_addrs, "DRAM", is_sorted=True)
|
| 209 |
+
dst_descs = self.agent.get_xfer_descs(dst_addrs, "DRAM", is_sorted=True)
|
| 210 |
+
# Transfer data
|
| 211 |
+
xfer_handle = self.agent.initialize_xfer(
|
| 212 |
+
"WRITE",
|
| 213 |
+
src_descs,
|
| 214 |
+
dst_descs,
|
| 215 |
+
peer_name,
|
| 216 |
+
notif.encode("ascii"),
|
| 217 |
+
)
|
| 218 |
+
if not xfer_handle:
|
| 219 |
+
raise Exception("KVSender failed to create transfer")
|
| 220 |
+
state = self.agent.transfer(xfer_handle)
|
| 221 |
+
if state == "ERR":
|
| 222 |
+
raise Exception("KVSender failed to post transfer")
|
| 223 |
+
return xfer_handle
|
| 224 |
+
|
| 225 |
+
def add_transfer_request(
|
| 226 |
+
self,
|
| 227 |
+
bootstrap_room: int,
|
| 228 |
+
kv_indices: npt.NDArray[np.int64],
|
| 229 |
+
index_slice: slice,
|
| 230 |
+
is_last: bool,
|
| 231 |
+
chunk_id: int,
|
| 232 |
+
aux_index: Optional[int] = None,
|
| 233 |
+
):
|
| 234 |
+
assert self.disaggregation_mode == DisaggregationMode.PREFILL
|
| 235 |
+
assert not is_last or (is_last and aux_index is not None)
|
| 236 |
+
|
| 237 |
+
# Wait for transfer info to be populated by bootstrap thread.
|
| 238 |
+
with self.condition:
|
| 239 |
+
self.condition.wait_for(lambda: bootstrap_room in self.transfer_infos)
|
| 240 |
+
req = self.transfer_infos[bootstrap_room]
|
| 241 |
+
assert bootstrap_room == req.room
|
| 242 |
+
|
| 243 |
+
peer_name = self._add_remote(bootstrap_room, req.agent_metadata)
|
| 244 |
+
chunked_dst_kv_indice = req.dst_kv_indices[index_slice]
|
| 245 |
+
assert len(chunked_dst_kv_indice) == len(kv_indices)
|
| 246 |
+
|
| 247 |
+
notif = "_".join([str(req.room), "kv", str(chunk_id), str(int(is_last))])
|
| 248 |
+
kv_xfer_handle = self.send_kvcache(
|
| 249 |
+
peer_name,
|
| 250 |
+
kv_indices,
|
| 251 |
+
req.dst_kv_ptrs,
|
| 252 |
+
chunked_dst_kv_indice,
|
| 253 |
+
req.dst_gpu_id,
|
| 254 |
+
notif,
|
| 255 |
+
)
|
| 256 |
+
handles = [kv_xfer_handle]
|
| 257 |
+
# Only the last chunk we need to send the aux data.
|
| 258 |
+
if is_last:
|
| 259 |
+
aux_xfer_handle = self.send_aux(
|
| 260 |
+
peer_name,
|
| 261 |
+
aux_index,
|
| 262 |
+
req.dst_aux_ptrs,
|
| 263 |
+
req.dst_aux_index,
|
| 264 |
+
str(req.room) + "_aux",
|
| 265 |
+
)
|
| 266 |
+
handles.append(aux_xfer_handle)
|
| 267 |
+
return handles
|
| 268 |
+
|
| 269 |
+
def update_transfer_status(self):
|
| 270 |
+
# Process notifications from received transfers.
|
| 271 |
+
notif_map = self.agent.get_new_notifs()
|
| 272 |
+
for peer_name, messages in notif_map.items():
|
| 273 |
+
# We could also check that self.bootstrap_info['agent_name'] matches
|
| 274 |
+
# the message sender. But the bootstrap room alone should be
|
| 275 |
+
# sufficient to map the status.
|
| 276 |
+
for msg in messages:
|
| 277 |
+
components = msg.decode("ascii").split("_")
|
| 278 |
+
room = int(components[0])
|
| 279 |
+
if components[1] == "kv":
|
| 280 |
+
chunk_id = int(components[2])
|
| 281 |
+
is_last = bool(components[3])
|
| 282 |
+
self.transfer_statuses[room].received_kvs.add(chunk_id)
|
| 283 |
+
if is_last:
|
| 284 |
+
self.transfer_statuses[room].num_kvs_expected = chunk_id + 1
|
| 285 |
+
elif components[1] == "aux":
|
| 286 |
+
self.transfer_statuses[room].received_aux = True
|
| 287 |
+
|
| 288 |
+
def check_transfer_done(self, room: int):
|
| 289 |
+
if room not in self.transfer_statuses:
|
| 290 |
+
return False
|
| 291 |
+
return self.transfer_statuses[room].is_done()
|
| 292 |
+
|
| 293 |
+
def _register_to_bootstrap(self):
|
| 294 |
+
"""Register KVSender to bootstrap server via HTTP POST."""
|
| 295 |
+
if self.dist_init_addr:
|
| 296 |
+
ip_address = socket.gethostbyname(self.dist_init_addr.split(":")[0])
|
| 297 |
+
else:
|
| 298 |
+
ip_address = get_ip()
|
| 299 |
+
|
| 300 |
+
bootstrap_server_url = f"{ip_address}:{self.bootstrap_port}"
|
| 301 |
+
url = f"http://{bootstrap_server_url}/route"
|
| 302 |
+
payload = {
|
| 303 |
+
"role": "Prefill",
|
| 304 |
+
"rank_ip": get_local_ip_by_remote(),
|
| 305 |
+
"rank_port": self.rank_port,
|
| 306 |
+
"engine_rank": self.kv_args.engine_rank,
|
| 307 |
+
"agent_name": self.agent.name,
|
| 308 |
+
}
|
| 309 |
+
|
| 310 |
+
try:
|
| 311 |
+
response = requests.put(url, json=payload)
|
| 312 |
+
if response.status_code == 200:
|
| 313 |
+
logger.debug("Prefill successfully registered to bootstrap server.")
|
| 314 |
+
else:
|
| 315 |
+
logger.error(
|
| 316 |
+
f"Prefill Failed to connect to bootstrap server: {response.status_code}, {response.text}"
|
| 317 |
+
)
|
| 318 |
+
except Exception as e:
|
| 319 |
+
logger.error(f"Prefill Failed to register to bootstrap server: {e}")
|
| 320 |
+
|
| 321 |
+
def _start_bootstrap_thread(self):
|
| 322 |
+
self.server_socket.bind(f"tcp://{get_local_ip_by_remote()}:{self.rank_port}")
|
| 323 |
+
|
| 324 |
+
def bootstrap_thread():
|
| 325 |
+
"""This thread recvs transfer info from the decode engine"""
|
| 326 |
+
while True:
|
| 327 |
+
waiting_req_bytes = self.server_socket.recv_multipart()
|
| 328 |
+
room = waiting_req_bytes[0].decode("ascii")
|
| 329 |
+
if room == "None":
|
| 330 |
+
continue
|
| 331 |
+
room = int(room)
|
| 332 |
+
with self.condition:
|
| 333 |
+
self.transfer_infos[room] = TransferInfo.from_zmq(waiting_req_bytes)
|
| 334 |
+
self.condition.notify_all()
|
| 335 |
+
|
| 336 |
+
threading.Thread(target=bootstrap_thread).start()
|
| 337 |
+
|
| 338 |
+
|
| 339 |
+
class NixlKVSender(BaseKVSender):
|
| 340 |
+
|
| 341 |
+
def __init__(self, mgr: NixlKVManager, bootstrap_addr: str, bootstrap_room: int):
|
| 342 |
+
self.kv_mgr = mgr
|
| 343 |
+
self.bootstrap_room = bootstrap_room
|
| 344 |
+
self.aux_index = None
|
| 345 |
+
self.bootstrap_server_url = bootstrap_addr
|
| 346 |
+
self.xfer_handles = []
|
| 347 |
+
self.has_sent = False
|
| 348 |
+
self.chunk_id = 0
|
| 349 |
+
|
| 350 |
+
def init(self, num_kv_indices: int, aux_index: Optional[int] = None):
|
| 351 |
+
self.num_kv_indices = num_kv_indices
|
| 352 |
+
self.aux_index = aux_index
|
| 353 |
+
|
| 354 |
+
def send(
|
| 355 |
+
self,
|
| 356 |
+
kv_indices: npt.NDArray[np.int64],
|
| 357 |
+
index_slice: slice,
|
| 358 |
+
is_last: bool,
|
| 359 |
+
):
|
| 360 |
+
new_xfer_handles = self.kv_mgr.add_transfer_request(
|
| 361 |
+
self.bootstrap_room,
|
| 362 |
+
kv_indices,
|
| 363 |
+
index_slice,
|
| 364 |
+
is_last,
|
| 365 |
+
self.chunk_id,
|
| 366 |
+
self.aux_index,
|
| 367 |
+
)
|
| 368 |
+
self.xfer_handles.extend(new_xfer_handles)
|
| 369 |
+
self.chunk_id += 1
|
| 370 |
+
if is_last:
|
| 371 |
+
self.has_sent = True
|
| 372 |
+
|
| 373 |
+
def poll(self) -> KVPoll:
|
| 374 |
+
if not self.has_sent:
|
| 375 |
+
return KVPoll.WaitingForInput
|
| 376 |
+
|
| 377 |
+
states = [self.kv_mgr.agent.check_xfer_state(x) for x in self.xfer_handles]
|
| 378 |
+
if all([x == "DONE" for x in states]):
|
| 379 |
+
return KVPoll.Success
|
| 380 |
+
if any([x == "ERR" for x in states]):
|
| 381 |
+
raise Exception("KVSender transfer encountered an error.")
|
| 382 |
+
return KVPoll.WaitingForInput
|
| 383 |
+
|
| 384 |
+
def failure_exception(self):
|
| 385 |
+
raise Exception("Fake KVSender Exception")
|
| 386 |
+
|
| 387 |
+
|
| 388 |
+
class NixlKVReceiver(BaseKVReceiver):
|
| 389 |
+
|
| 390 |
+
def __init__(
|
| 391 |
+
self,
|
| 392 |
+
mgr: NixlKVManager,
|
| 393 |
+
bootstrap_addr: str,
|
| 394 |
+
bootstrap_room: Optional[int] = None,
|
| 395 |
+
):
|
| 396 |
+
self.bootstrap_room = bootstrap_room
|
| 397 |
+
self.bootstrap_addr = bootstrap_addr
|
| 398 |
+
self.kv_mgr = mgr
|
| 399 |
+
self.started_transfer = False
|
| 400 |
+
|
| 401 |
+
# NOTE: key distinguished by bootstrap_addr and engine_rank
|
| 402 |
+
bootstrap_key = f"{self.bootstrap_addr}_{self.kv_mgr.kv_args.engine_rank}"
|
| 403 |
+
|
| 404 |
+
if bootstrap_key not in self.kv_mgr.connection_pool:
|
| 405 |
+
self.bootstrap_info = self._get_bootstrap_info_from_server(
|
| 406 |
+
self.kv_mgr.kv_args.engine_rank
|
| 407 |
+
)
|
| 408 |
+
if self.bootstrap_info is None:
|
| 409 |
+
logger.error(
|
| 410 |
+
f"Could not fetch bootstrap info for engine rank: {self.kv_mgr.kv_args.engine_rank}"
|
| 411 |
+
)
|
| 412 |
+
else:
|
| 413 |
+
self.kv_mgr.connection_pool[bootstrap_key] = self.bootstrap_info
|
| 414 |
+
else:
|
| 415 |
+
self.bootstrap_info = self.kv_mgr.connection_pool[bootstrap_key]
|
| 416 |
+
|
| 417 |
+
assert self.bootstrap_info is not None
|
| 418 |
+
|
| 419 |
+
def _get_bootstrap_info_from_server(self, engine_rank):
|
| 420 |
+
"""Fetch the bootstrap info from the bootstrap server."""
|
| 421 |
+
try:
|
| 422 |
+
url = f"http://{self.bootstrap_addr}/route?engine_rank={engine_rank}"
|
| 423 |
+
response = requests.get(url)
|
| 424 |
+
if response.status_code == 200:
|
| 425 |
+
bootstrap_info = response.json()
|
| 426 |
+
return bootstrap_info
|
| 427 |
+
else:
|
| 428 |
+
logger.error(
|
| 429 |
+
f"Failed to get prefill server info: {response.status_code}, {response.text}"
|
| 430 |
+
)
|
| 431 |
+
return None
|
| 432 |
+
except Exception as e:
|
| 433 |
+
logger.error(f"Error fetching prefill info from bootstrap: {e}")
|
| 434 |
+
return None
|
| 435 |
+
|
| 436 |
+
@cache
|
| 437 |
+
def _connect(self, endpoint: str):
|
| 438 |
+
socket = zmq.Context().socket(zmq.PUSH)
|
| 439 |
+
socket.connect(endpoint)
|
| 440 |
+
return socket
|
| 441 |
+
|
| 442 |
+
def init(self, kv_indices: npt.NDArray[np.int64], aux_index: Optional[int] = None):
|
| 443 |
+
self.prefill_server_url = (
|
| 444 |
+
f"{self.bootstrap_info['rank_ip']}:{self.bootstrap_info['rank_port']}"
|
| 445 |
+
)
|
| 446 |
+
logger.debug(
|
| 447 |
+
f"Fetched bootstrap info: {self.bootstrap_info} for engine rank: {self.kv_mgr.kv_args.engine_rank}"
|
| 448 |
+
)
|
| 449 |
+
|
| 450 |
+
packed_kv_data_ptrs = b"".join(
|
| 451 |
+
struct.pack("Q", ptr) for ptr in self.kv_mgr.kv_args.kv_data_ptrs
|
| 452 |
+
)
|
| 453 |
+
packed_aux_data_ptrs = b"".join(
|
| 454 |
+
struct.pack("Q", ptr) for ptr in self.kv_mgr.kv_args.aux_data_ptrs
|
| 455 |
+
)
|
| 456 |
+
self._connect("tcp://" + self.prefill_server_url).send_multipart(
|
| 457 |
+
[
|
| 458 |
+
str(self.bootstrap_room).encode("ascii"),
|
| 459 |
+
get_local_ip_by_remote().encode("ascii"),
|
| 460 |
+
str(self.kv_mgr.rank_port).encode("ascii"),
|
| 461 |
+
self.kv_mgr.agent.get_agent_metadata(),
|
| 462 |
+
packed_kv_data_ptrs,
|
| 463 |
+
kv_indices.tobytes(),
|
| 464 |
+
packed_aux_data_ptrs,
|
| 465 |
+
str(aux_index).encode("ascii"),
|
| 466 |
+
str(self.kv_mgr.kv_args.gpu_id).encode("ascii"),
|
| 467 |
+
]
|
| 468 |
+
)
|
| 469 |
+
self.started_transfer = True
|
| 470 |
+
|
| 471 |
+
def poll(self) -> KVPoll:
|
| 472 |
+
if not self.started_transfer:
|
| 473 |
+
return KVPoll.WaitingForInput
|
| 474 |
+
|
| 475 |
+
self.kv_mgr.update_transfer_status()
|
| 476 |
+
|
| 477 |
+
if self.kv_mgr.check_transfer_done(self.bootstrap_room):
|
| 478 |
+
return KVPoll.Success
|
| 479 |
+
return KVPoll.WaitingForInput
|
| 480 |
+
|
| 481 |
+
def failure_exception(self):
|
| 482 |
+
raise Exception("Fake KVReceiver Exception")
|
| 483 |
+
|
| 484 |
+
|
| 485 |
+
class NixlKVBootstrapServer(BaseKVBootstrapServer):
|
| 486 |
+
def __init__(self, port: int):
|
| 487 |
+
self.port = port
|
| 488 |
+
self.app = web.Application()
|
| 489 |
+
self.store = dict()
|
| 490 |
+
self.lock = asyncio.Lock()
|
| 491 |
+
self._setup_routes()
|
| 492 |
+
self.prefill_port_table: Dict[int, Dict[str, Union[str, int]]] = {}
|
| 493 |
+
|
| 494 |
+
# Start bootstrap server
|
| 495 |
+
self.thread = threading.Thread(target=self._run_server, daemon=True)
|
| 496 |
+
self.run()
|
| 497 |
+
|
| 498 |
+
def run(self):
|
| 499 |
+
self.thread.start()
|
| 500 |
+
|
| 501 |
+
def _setup_routes(self):
|
| 502 |
+
self.app.router.add_route("*", "/metadata", self._handle_metadata)
|
| 503 |
+
self.app.router.add_route("*", "/route", self._handle_route)
|
| 504 |
+
|
| 505 |
+
async def _handle_metadata(self, request: web.Request):
|
| 506 |
+
key = request.query.get("key", "")
|
| 507 |
+
|
| 508 |
+
if request.method == "GET":
|
| 509 |
+
return await self._handle_metadata_get(key)
|
| 510 |
+
elif request.method == "PUT":
|
| 511 |
+
return await self._handle_metadata_put(key, request)
|
| 512 |
+
elif request.method == "DELETE":
|
| 513 |
+
return await self._handle_metadata_delete(key)
|
| 514 |
+
return web.Response(
|
| 515 |
+
text="Method not allowed", status=405, content_type="application/json"
|
| 516 |
+
)
|
| 517 |
+
|
| 518 |
+
async def _handle_metadata_get(self, key):
|
| 519 |
+
async with self.lock:
|
| 520 |
+
value = self.store.get(key)
|
| 521 |
+
if value is None:
|
| 522 |
+
return web.Response(
|
| 523 |
+
text="metadata not found", status=404, content_type="application/json"
|
| 524 |
+
)
|
| 525 |
+
return web.Response(body=value, status=200, content_type="application/json")
|
| 526 |
+
|
| 527 |
+
async def _handle_metadata_put(self, key, request):
|
| 528 |
+
data = await request.read()
|
| 529 |
+
async with self.lock:
|
| 530 |
+
self.store[key] = data
|
| 531 |
+
return web.Response(
|
| 532 |
+
text="metadata updated", status=200, content_type="application/json"
|
| 533 |
+
)
|
| 534 |
+
|
| 535 |
+
async def _handle_metadata_delete(self, key):
|
| 536 |
+
async with self.lock:
|
| 537 |
+
if key not in self.store:
|
| 538 |
+
return web.Response(
|
| 539 |
+
text="metadata not found",
|
| 540 |
+
status=404,
|
| 541 |
+
content_type="application/json",
|
| 542 |
+
)
|
| 543 |
+
del self.store[key]
|
| 544 |
+
return web.Response(
|
| 545 |
+
text="metadata deleted", status=200, content_type="application/json"
|
| 546 |
+
)
|
| 547 |
+
|
| 548 |
+
async def _handle_route(self, request: web.Request):
|
| 549 |
+
method = request.method
|
| 550 |
+
if method == "PUT":
|
| 551 |
+
return await self._handle_route_put(request)
|
| 552 |
+
elif method == "GET":
|
| 553 |
+
return await self._handle_route_get(request)
|
| 554 |
+
else:
|
| 555 |
+
return web.Response(
|
| 556 |
+
text="Method not allowed", status=405, content_type="application/json"
|
| 557 |
+
)
|
| 558 |
+
|
| 559 |
+
async def _handle_route_put(self, request: web.Request):
|
| 560 |
+
data = await request.json()
|
| 561 |
+
role = data["role"]
|
| 562 |
+
rank_ip = data["rank_ip"]
|
| 563 |
+
rank_port = int(data["rank_port"])
|
| 564 |
+
engine_rank = int(data["engine_rank"])
|
| 565 |
+
agent_name = data["agent_name"]
|
| 566 |
+
|
| 567 |
+
# Add lock to make sure thread-safe
|
| 568 |
+
if role == "Prefill":
|
| 569 |
+
self.prefill_port_table[engine_rank] = {
|
| 570 |
+
"rank_ip": rank_ip,
|
| 571 |
+
"rank_port": rank_port,
|
| 572 |
+
"agent_name": agent_name,
|
| 573 |
+
}
|
| 574 |
+
logger.info(
|
| 575 |
+
f"Registered Prefill boostrap: {engine_rank} with rank_ip: {rank_ip} and rank_port: {rank_port} and name: {agent_name}"
|
| 576 |
+
)
|
| 577 |
+
|
| 578 |
+
return web.Response(text="OK", status=200)
|
| 579 |
+
|
| 580 |
+
async def _handle_route_get(self, request: web.Request):
|
| 581 |
+
engine_rank = request.query.get("engine_rank")
|
| 582 |
+
if not engine_rank:
|
| 583 |
+
return web.Response(text="Missing rank", status=400)
|
| 584 |
+
|
| 585 |
+
# Find corresponding prefill info
|
| 586 |
+
async with self.lock:
|
| 587 |
+
bootstrap_info = self.prefill_port_table.get(int(engine_rank))
|
| 588 |
+
if bootstrap_info is not None:
|
| 589 |
+
return web.json_response(bootstrap_info, status=200)
|
| 590 |
+
else:
|
| 591 |
+
return web.Response(text="Not Found", status=404)
|
| 592 |
+
|
| 593 |
+
def _run_server(self):
|
| 594 |
+
try:
|
| 595 |
+
# Event Loop
|
| 596 |
+
self._loop = asyncio.new_event_loop()
|
| 597 |
+
asyncio.set_event_loop(self._loop)
|
| 598 |
+
|
| 599 |
+
self._runner = web.AppRunner(self.app)
|
| 600 |
+
self._loop.run_until_complete(self._runner.setup())
|
| 601 |
+
|
| 602 |
+
site = web.TCPSite(self._runner, port=self.port)
|
| 603 |
+
self._loop.run_until_complete(site.start())
|
| 604 |
+
self._loop.run_forever()
|
| 605 |
+
except Exception as e:
|
| 606 |
+
logger.error(f"Server error: {str(e)}")
|
| 607 |
+
finally:
|
| 608 |
+
# Cleanup
|
| 609 |
+
self._loop.run_until_complete(self._runner.cleanup())
|
| 610 |
+
self._loop.close()
|
| 611 |
+
|
| 612 |
+
def close(self):
|
| 613 |
+
"""Shutdown"""
|
| 614 |
+
if self._loop is not None and self._loop.is_running():
|
| 615 |
+
self._loop.call_soon_threadsafe(self._loop.stop)
|
| 616 |
+
logger.info("Stopping server loop...")
|
| 617 |
+
|
| 618 |
+
if self.thread.is_alive():
|
| 619 |
+
self.thread.join(timeout=2)
|
| 620 |
+
logger.info("Server thread stopped")
|
| 621 |
+
|
| 622 |
+
def poll(self) -> KVPoll: ...
|
lib/python3.12/site-packages/sglang/srt/lora/__pycache__/layers.cpython-312.pyc
ADDED
|
Binary file (16.1 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/lora/__pycache__/lora.cpython-312.pyc
ADDED
|
Binary file (7.4 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/lora/__pycache__/lora_config.cpython-312.pyc
ADDED
|
Binary file (2.1 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/lora/__pycache__/lora_manager.cpython-312.pyc
ADDED
|
Binary file (10.6 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/lora/__pycache__/mem_pool.cpython-312.pyc
ADDED
|
Binary file (9.85 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/lora/__pycache__/utils.cpython-312.pyc
ADDED
|
Binary file (5.51 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/lora/backend/__pycache__/base_backend.cpython-312.pyc
ADDED
|
Binary file (6.46 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/lora/backend/__pycache__/flashinfer_backend.cpython-312.pyc
ADDED
|
Binary file (5.75 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/lora/backend/__pycache__/triton_backend.cpython-312.pyc
ADDED
|
Binary file (3.43 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/lora/backend/base_backend.py
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Tuple, Union
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
|
| 5 |
+
from sglang.srt.lora.utils import LoRABatchInfo
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def get_fuse_output_add_from_name(name: str) -> bool:
|
| 9 |
+
mapping = {
|
| 10 |
+
"triton": True,
|
| 11 |
+
"flashinfer": False,
|
| 12 |
+
}
|
| 13 |
+
return mapping.get(name, False)
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def get_fuse_stacked_lora_b_from_name(name: str) -> bool:
|
| 17 |
+
mapping = {
|
| 18 |
+
"triton": True,
|
| 19 |
+
"flashinfer": False,
|
| 20 |
+
}
|
| 21 |
+
return mapping.get(name, False)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class BaseLoRABackend:
|
| 25 |
+
"""Base class for different Lora backends.
|
| 26 |
+
Each backend has its own implementation of Lora kernels.
|
| 27 |
+
|
| 28 |
+
Args:
|
| 29 |
+
name: name of backend
|
| 30 |
+
batch_info: information of current batch for use
|
| 31 |
+
fuse_output_add: if set to True, the output buffer for storing result will be passed in when doing lora_b forward,
|
| 32 |
+
and the operation of adding will be fused into kernel
|
| 33 |
+
"""
|
| 34 |
+
|
| 35 |
+
def __init__(self, name: str, batch_info: LoRABatchInfo = None):
|
| 36 |
+
self.name = name
|
| 37 |
+
self.batch_info = batch_info
|
| 38 |
+
self.fuse_output_add = get_fuse_output_add_from_name(name)
|
| 39 |
+
self.fuse_stacked_lora_b = get_fuse_stacked_lora_b_from_name(name)
|
| 40 |
+
|
| 41 |
+
def run_lora_a_sgemm(
|
| 42 |
+
self, x: torch.Tensor, weights: torch.Tensor, *args, **kwargs
|
| 43 |
+
) -> torch.Tensor:
|
| 44 |
+
"""Run segment Gemm of lora a modules with current backend.
|
| 45 |
+
The definition of segment Gemm can be referred to https://docs.flashinfer.ai/api/gemm.html.
|
| 46 |
+
|
| 47 |
+
Args:
|
| 48 |
+
x: input matrix with shape (s, input_dim), here s is the sum of all sequence lengths
|
| 49 |
+
weights: a set of lora weights with shape (num_lora, c * r, input_dim),
|
| 50 |
+
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)
|
| 51 |
+
usually input_dim is much larger than r
|
| 52 |
+
Returns:
|
| 53 |
+
result with shape (s, c * r)
|
| 54 |
+
"""
|
| 55 |
+
pass
|
| 56 |
+
|
| 57 |
+
def run_lora_b_sgemm(
|
| 58 |
+
self, x: torch.Tensor, weights: torch.Tensor, *args, **kwargs
|
| 59 |
+
) -> torch.Tensor:
|
| 60 |
+
"""Run segment Gemm of lora b modules with current backend.
|
| 61 |
+
The definition of segment Gemm can be referred to https://docs.flashinfer.ai/api/gemm.html.
|
| 62 |
+
|
| 63 |
+
Args:
|
| 64 |
+
x: input matrix with shape (s, r), here s is the sum of all sequence lengths, r is lora rank
|
| 65 |
+
weights: a set of lora weights with shape (num_lora, output_dim, r)
|
| 66 |
+
usually output_dim is much larger than r
|
| 67 |
+
Returns:
|
| 68 |
+
result with shape (s, output_dim)
|
| 69 |
+
"""
|
| 70 |
+
pass
|
| 71 |
+
|
| 72 |
+
def run_qkv_lora(
|
| 73 |
+
self,
|
| 74 |
+
x: torch.Tensor,
|
| 75 |
+
qkv_lora_a: torch.Tensor,
|
| 76 |
+
qkv_lora_b: Union[torch.Tensor, Tuple[torch.Tensor]],
|
| 77 |
+
*args,
|
| 78 |
+
**kwargs,
|
| 79 |
+
) -> torch.Tensor:
|
| 80 |
+
"""Run the lora pass for QKV Layer.
|
| 81 |
+
|
| 82 |
+
Args:
|
| 83 |
+
x: input matrix with shape (s, input_dim), here s is the sum of all sequence lengths
|
| 84 |
+
qkv_lora_a: lora_a module for qkv, with shape (num_lora, 3 * r, input_dim)
|
| 85 |
+
qkv_lora_b: lora_b module for qkv.
|
| 86 |
+
If passed in as a tensor, its shape should be (num_lora,output_dim_q + 2 * output_dim_kv, r)
|
| 87 |
+
If passed in as a tuple of two tensors, it should contain:
|
| 88 |
+
a lora_b module for q, with shape (1, num_lora, output_dim_q, r)
|
| 89 |
+
and a combined lora_b module for kv, with shape (2, num_lora, output_dim_kv, r)
|
| 90 |
+
Returns:
|
| 91 |
+
result with shape (s, output_dim_q + 2 * output_dim_kv)
|
| 92 |
+
"""
|
| 93 |
+
pass
|
| 94 |
+
|
| 95 |
+
def run_gate_up_lora(
|
| 96 |
+
self,
|
| 97 |
+
x: torch.Tensor,
|
| 98 |
+
gate_up_lora_a: torch.Tensor,
|
| 99 |
+
gate_up_lora_b: Union[torch.Tensor, Tuple[torch.Tensor]],
|
| 100 |
+
*args,
|
| 101 |
+
**kwargs,
|
| 102 |
+
) -> torch.Tensor:
|
| 103 |
+
"""Run the lora pass for gate_up_proj, usually attached to MergedColumnParallelLayer.
|
| 104 |
+
|
| 105 |
+
Args:
|
| 106 |
+
x: input matrix with shape (s, input_dim), here s is the sum of all sequence lengths
|
| 107 |
+
gate_up_lora_a: lora_a module for gate_up_proj, with shape (num_lora, 2 * r, input_dim)
|
| 108 |
+
gate_up_lora_b: lora_b module for qkv.
|
| 109 |
+
If passed in as a tensor, its shape should be (num_lora, 2 * output_dim, r)
|
| 110 |
+
If passed in as a tuple, it should contain two tensors with shape (num_lora, output_dim, r)
|
| 111 |
+
Returns:
|
| 112 |
+
result with shape (s, 2 * output_dim)
|
| 113 |
+
"""
|
| 114 |
+
pass
|
| 115 |
+
|
| 116 |
+
def set_batch_info(self, batch_info: LoRABatchInfo):
|
| 117 |
+
self.batch_info = batch_info
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
def get_backend_from_name(name: str) -> BaseLoRABackend:
|
| 121 |
+
"""
|
| 122 |
+
Get corresponding backend class from backend's name
|
| 123 |
+
"""
|
| 124 |
+
if name == "triton":
|
| 125 |
+
from sglang.srt.lora.backend.triton_backend import TritonLoRABackend
|
| 126 |
+
|
| 127 |
+
return TritonLoRABackend
|
| 128 |
+
elif name == "flashinfer":
|
| 129 |
+
from sglang.srt.lora.backend.flashinfer_backend import FlashInferLoRABackend
|
| 130 |
+
|
| 131 |
+
return FlashInferLoRABackend
|
| 132 |
+
else:
|
| 133 |
+
raise ValueError(f"Invalid backend: {name}")
|
lib/python3.12/site-packages/sglang/srt/lora/backend/flashinfer_backend.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Tuple
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
|
| 5 |
+
from sglang.srt.lora.backend.base_backend import BaseLoRABackend
|
| 6 |
+
from sglang.srt.lora.utils import LoRABatchInfo
|
| 7 |
+
from sglang.srt.utils import is_flashinfer_available
|
| 8 |
+
|
| 9 |
+
if is_flashinfer_available():
|
| 10 |
+
from flashinfer import SegmentGEMMWrapper
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class FlashInferLoRABackend(BaseLoRABackend):
|
| 14 |
+
|
| 15 |
+
def __init__(self, name: str, batch_info: LoRABatchInfo = None):
|
| 16 |
+
super().__init__(name, batch_info)
|
| 17 |
+
|
| 18 |
+
# Set up SGemm Wrapper from flashinfer
|
| 19 |
+
# FIXME wait for flashinfer segment gemm update
|
| 20 |
+
workspace_buffer = torch.empty(1 * 1024 * 1024, dtype=torch.int8, device="cuda")
|
| 21 |
+
self.segment_gemm = SegmentGEMMWrapper(workspace_buffer)
|
| 22 |
+
|
| 23 |
+
def run_lora_a_sgemm(
|
| 24 |
+
self, x: torch.Tensor, weights: torch.Tensor, *args, **kwargs
|
| 25 |
+
) -> torch.Tensor:
|
| 26 |
+
|
| 27 |
+
return self.segment_gemm.run(
|
| 28 |
+
x=x,
|
| 29 |
+
weights=weights,
|
| 30 |
+
batch_size=self.batch_info.bs,
|
| 31 |
+
weight_column_major=True,
|
| 32 |
+
seg_indptr=self.batch_info.seg_indptr,
|
| 33 |
+
weight_indices=self.batch_info.weight_indices,
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
def run_lora_b_sgemm(
|
| 37 |
+
self, x: torch.Tensor, weights: torch.Tensor, *args, **kwargs
|
| 38 |
+
) -> torch.Tensor:
|
| 39 |
+
|
| 40 |
+
return (
|
| 41 |
+
self.segment_gemm.run(
|
| 42 |
+
x=x,
|
| 43 |
+
weights=weights,
|
| 44 |
+
batch_size=self.batch_info.bs,
|
| 45 |
+
weight_column_major=True,
|
| 46 |
+
seg_indptr=self.batch_info.seg_indptr,
|
| 47 |
+
weight_indices=self.batch_info.weight_indices,
|
| 48 |
+
)
|
| 49 |
+
* self.batch_info.scalings[0]
|
| 50 |
+
)
|
| 51 |
+
|
| 52 |
+
def run_qkv_lora(
|
| 53 |
+
self,
|
| 54 |
+
x: torch.Tensor,
|
| 55 |
+
qkv_lora_a: torch.Tensor,
|
| 56 |
+
qkv_lora_b: Tuple[torch.Tensor],
|
| 57 |
+
*args,
|
| 58 |
+
**kwargs,
|
| 59 |
+
) -> torch.Tensor:
|
| 60 |
+
|
| 61 |
+
assert isinstance(qkv_lora_b, tuple) and len(qkv_lora_b) == 2
|
| 62 |
+
|
| 63 |
+
# Shape of lora_a_output: (s, 3 * r)
|
| 64 |
+
lora_a_output = self.run_lora_a_sgemm(x=x, weights=qkv_lora_a)
|
| 65 |
+
|
| 66 |
+
q_lora_b, kv_lora_b = qkv_lora_b
|
| 67 |
+
lora_rank = kv_lora_b.shape[-1]
|
| 68 |
+
output_dim_q = q_lora_b.shape[-2]
|
| 69 |
+
output_dim_kv = kv_lora_b.shape[-2]
|
| 70 |
+
lora_output = torch.empty(
|
| 71 |
+
(x.shape[0], output_dim_q + 2 * output_dim_kv),
|
| 72 |
+
device=x.device,
|
| 73 |
+
dtype=x.dtype,
|
| 74 |
+
)
|
| 75 |
+
|
| 76 |
+
# q
|
| 77 |
+
lora_output[:, :output_dim_q] = self.run_lora_b_sgemm(
|
| 78 |
+
x=lora_a_output[:, :lora_rank].contiguous(), weights=q_lora_b[0]
|
| 79 |
+
)
|
| 80 |
+
|
| 81 |
+
# kv
|
| 82 |
+
lora_output[:, output_dim_q : output_dim_q + output_dim_kv] = (
|
| 83 |
+
self.run_lora_b_sgemm(
|
| 84 |
+
x=lora_a_output[:, lora_rank : 2 * lora_rank].contiguous(),
|
| 85 |
+
weights=kv_lora_b[0],
|
| 86 |
+
)
|
| 87 |
+
)
|
| 88 |
+
|
| 89 |
+
lora_output[
|
| 90 |
+
:, output_dim_q + output_dim_kv : output_dim_q + 2 * output_dim_kv
|
| 91 |
+
] = self.run_lora_b_sgemm(
|
| 92 |
+
x=lora_a_output[:, 2 * lora_rank : 3 * lora_rank].contiguous(),
|
| 93 |
+
weights=kv_lora_b[1],
|
| 94 |
+
)
|
| 95 |
+
|
| 96 |
+
return lora_output * self.batch_info.scalings[0]
|
| 97 |
+
|
| 98 |
+
def run_gate_up_lora(
|
| 99 |
+
self,
|
| 100 |
+
x: torch.Tensor,
|
| 101 |
+
gate_up_lora_a: torch.Tensor,
|
| 102 |
+
gate_up_lora_b: Tuple[torch.Tensor],
|
| 103 |
+
*args,
|
| 104 |
+
**kwargs,
|
| 105 |
+
) -> torch.Tensor:
|
| 106 |
+
|
| 107 |
+
assert isinstance(gate_up_lora_b, tuple) and len(gate_up_lora_b) == 2
|
| 108 |
+
lora_rank = gate_up_lora_b[0].shape[-1]
|
| 109 |
+
output_dim = gate_up_lora_b[0].shape[-2]
|
| 110 |
+
|
| 111 |
+
# Shape of lora_a_output: (s, 2 * r)
|
| 112 |
+
lora_a_output = self.run_lora_a_sgemm(x=x, weights=gate_up_lora_a)
|
| 113 |
+
|
| 114 |
+
lora_output = torch.empty(
|
| 115 |
+
(x.shape[0], 2 * output_dim),
|
| 116 |
+
device=x.device,
|
| 117 |
+
dtype=x.dtype,
|
| 118 |
+
)
|
| 119 |
+
|
| 120 |
+
# Compute lora for gate and up proj respectively
|
| 121 |
+
lora_output[:, :output_dim] = self.run_lora_b_sgemm(
|
| 122 |
+
x=lora_a_output[:, :lora_rank].contiguous(),
|
| 123 |
+
weights=gate_up_lora_b[0],
|
| 124 |
+
)
|
| 125 |
+
|
| 126 |
+
lora_output[:, output_dim:] = self.run_lora_b_sgemm(
|
| 127 |
+
x=lora_a_output[:, lora_rank:].contiguous(),
|
| 128 |
+
weights=gate_up_lora_b[1],
|
| 129 |
+
)
|
| 130 |
+
|
| 131 |
+
return lora_output * self.batch_info.scalings[0]
|
lib/python3.12/site-packages/sglang/srt/lora/backend/triton_backend.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
|
| 3 |
+
from sglang.srt.lora.backend.base_backend import BaseLoRABackend
|
| 4 |
+
from sglang.srt.lora.triton_ops import (
|
| 5 |
+
gate_up_lora_b_fwd,
|
| 6 |
+
qkv_lora_b_fwd,
|
| 7 |
+
sgemm_lora_a_fwd,
|
| 8 |
+
sgemm_lora_b_fwd,
|
| 9 |
+
)
|
| 10 |
+
from sglang.srt.lora.utils import LoRABatchInfo
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class TritonLoRABackend(BaseLoRABackend):
|
| 14 |
+
|
| 15 |
+
def __init__(self, name: str, batch_info: LoRABatchInfo = None):
|
| 16 |
+
super().__init__(name, batch_info)
|
| 17 |
+
|
| 18 |
+
def run_lora_a_sgemm(
|
| 19 |
+
self, x: torch.Tensor, weights: torch.Tensor, *args, **kwargs
|
| 20 |
+
) -> torch.Tensor:
|
| 21 |
+
return sgemm_lora_a_fwd(x, weights, self.batch_info)
|
| 22 |
+
|
| 23 |
+
def run_lora_b_sgemm(
|
| 24 |
+
self,
|
| 25 |
+
x: torch.Tensor,
|
| 26 |
+
weights: torch.Tensor,
|
| 27 |
+
base_output: torch.Tensor = None,
|
| 28 |
+
*args,
|
| 29 |
+
**kwargs
|
| 30 |
+
) -> torch.Tensor:
|
| 31 |
+
return sgemm_lora_b_fwd(x, weights, self.batch_info, base_output)
|
| 32 |
+
|
| 33 |
+
def run_qkv_lora(
|
| 34 |
+
self,
|
| 35 |
+
x: torch.Tensor,
|
| 36 |
+
qkv_lora_a: torch.Tensor,
|
| 37 |
+
qkv_lora_b: torch.Tensor,
|
| 38 |
+
output_offset: torch.Tensor,
|
| 39 |
+
max_qkv_out_dim: int,
|
| 40 |
+
base_output: torch.Tensor = None,
|
| 41 |
+
*args,
|
| 42 |
+
**kwargs
|
| 43 |
+
) -> torch.Tensor:
|
| 44 |
+
|
| 45 |
+
# x: (s, input_dim)
|
| 46 |
+
# qkv_lora_a: (num_lora, 3 * r, input_dim)
|
| 47 |
+
# qkv_lora_b: (num_lora, output_dim_q + 2 * output_dim_kv, r)
|
| 48 |
+
assert isinstance(qkv_lora_b, torch.Tensor)
|
| 49 |
+
|
| 50 |
+
lora_a_output = sgemm_lora_a_fwd(x, qkv_lora_a, self.batch_info, stack_num=3)
|
| 51 |
+
lora_output = qkv_lora_b_fwd(
|
| 52 |
+
lora_a_output,
|
| 53 |
+
qkv_lora_b,
|
| 54 |
+
self.batch_info,
|
| 55 |
+
output_offset,
|
| 56 |
+
max_qkv_out_dim,
|
| 57 |
+
base_output,
|
| 58 |
+
)
|
| 59 |
+
return lora_output
|
| 60 |
+
|
| 61 |
+
def run_gate_up_lora(
|
| 62 |
+
self,
|
| 63 |
+
x: torch.Tensor,
|
| 64 |
+
gate_up_lora_a: torch.Tensor,
|
| 65 |
+
gate_up_lora_b: torch.Tensor,
|
| 66 |
+
base_output: torch.Tensor = None,
|
| 67 |
+
*args,
|
| 68 |
+
**kwargs
|
| 69 |
+
) -> torch.Tensor:
|
| 70 |
+
|
| 71 |
+
# x: (s, input_dim)
|
| 72 |
+
# gate_up_lora_a: (num_lora, 2 * r, input_dim)
|
| 73 |
+
# gate_up_lora_b: (num_lora, 2 * output_dim, r)
|
| 74 |
+
assert isinstance(gate_up_lora_b, torch.Tensor)
|
| 75 |
+
output_dim = gate_up_lora_b.shape[-2] // 2
|
| 76 |
+
|
| 77 |
+
# lora_a_output: (s, 2 * r)
|
| 78 |
+
lora_a_output = sgemm_lora_a_fwd(
|
| 79 |
+
x, gate_up_lora_a, self.batch_info, stack_num=2
|
| 80 |
+
)
|
| 81 |
+
lora_output = gate_up_lora_b_fwd(
|
| 82 |
+
lora_a_output,
|
| 83 |
+
gate_up_lora_b,
|
| 84 |
+
self.batch_info,
|
| 85 |
+
output_dim,
|
| 86 |
+
base_output,
|
| 87 |
+
)
|
| 88 |
+
return lora_output
|
lib/python3.12/site-packages/sglang/srt/lora/layers.py
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import List, Tuple
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
from torch import nn
|
| 5 |
+
|
| 6 |
+
from sglang.srt.distributed import (
|
| 7 |
+
get_tensor_model_parallel_rank,
|
| 8 |
+
split_tensor_along_last_dim,
|
| 9 |
+
tensor_model_parallel_all_gather,
|
| 10 |
+
tensor_model_parallel_all_reduce,
|
| 11 |
+
)
|
| 12 |
+
from sglang.srt.layers.linear import (
|
| 13 |
+
ColumnParallelLinear,
|
| 14 |
+
MergedColumnParallelLinear,
|
| 15 |
+
QKVParallelLinear,
|
| 16 |
+
RowParallelLinear,
|
| 17 |
+
)
|
| 18 |
+
from sglang.srt.layers.vocab_parallel_embedding import VocabParallelEmbedding
|
| 19 |
+
from sglang.srt.lora.backend.base_backend import BaseLoRABackend
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class BaseLayerWithLoRA(nn.Module):
|
| 23 |
+
def __init__(
|
| 24 |
+
self,
|
| 25 |
+
base_layer: nn.Module,
|
| 26 |
+
lora_backend: BaseLoRABackend,
|
| 27 |
+
):
|
| 28 |
+
super().__init__()
|
| 29 |
+
self.base_layer: nn.Module = base_layer
|
| 30 |
+
self.set_lora: bool = False
|
| 31 |
+
self.lora_backend: BaseLoRABackend = lora_backend
|
| 32 |
+
|
| 33 |
+
def forward(self, x: torch.Tensor):
|
| 34 |
+
return self.base_layer.forward(x)
|
| 35 |
+
|
| 36 |
+
def set_lora_info(self, *args):
|
| 37 |
+
pass
|
| 38 |
+
|
| 39 |
+
def slice_lora_a_weights(self, A: torch.Tensor, tp_rank: int):
|
| 40 |
+
pass
|
| 41 |
+
|
| 42 |
+
def slice_lora_b_weights(self, B: torch.Tensor, tp_rank: int):
|
| 43 |
+
pass
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
class VocabParallelEmbeddingWithLoRA(BaseLayerWithLoRA):
|
| 47 |
+
"""
|
| 48 |
+
Vocab parallel embedding layer with support for LoRA (Low-Rank Adaptation).
|
| 49 |
+
|
| 50 |
+
Note: The current version does not yet implement the LoRA functionality.
|
| 51 |
+
This class behaves exactly the same as the base VocabParallelEmbedding.
|
| 52 |
+
Future versions will integrate LoRA functionality to support efficient parameter fine-tuning.
|
| 53 |
+
"""
|
| 54 |
+
|
| 55 |
+
def __init__(
|
| 56 |
+
self,
|
| 57 |
+
base_layer: VocabParallelEmbedding,
|
| 58 |
+
lora_backend: BaseLoRABackend,
|
| 59 |
+
) -> None:
|
| 60 |
+
super().__init__(base_layer, lora_backend)
|
| 61 |
+
self.weight = base_layer.weight
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
class ColumnParallelLinearWithLoRA(BaseLayerWithLoRA):
|
| 65 |
+
def __init__(
|
| 66 |
+
self,
|
| 67 |
+
base_layer: ColumnParallelLinear,
|
| 68 |
+
lora_backend: BaseLoRABackend,
|
| 69 |
+
) -> None:
|
| 70 |
+
super().__init__(base_layer, lora_backend)
|
| 71 |
+
|
| 72 |
+
def set_lora_info(
|
| 73 |
+
self,
|
| 74 |
+
A_buffer: torch.Tensor,
|
| 75 |
+
B_buffer: torch.Tensor,
|
| 76 |
+
):
|
| 77 |
+
self.set_lora = True
|
| 78 |
+
self.A_buffer = A_buffer
|
| 79 |
+
self.B_buffer = B_buffer
|
| 80 |
+
|
| 81 |
+
def apply_lora(self, base_output: torch.Tensor, x: torch.Tensor) -> torch.Tensor:
|
| 82 |
+
backend_kwargs = {"base_output": base_output}
|
| 83 |
+
lora_a_output = self.lora_backend.run_lora_a_sgemm(x, self.A_buffer)
|
| 84 |
+
lora_output = self.lora_backend.run_lora_b_sgemm(
|
| 85 |
+
lora_a_output,
|
| 86 |
+
self.B_buffer[0],
|
| 87 |
+
**backend_kwargs,
|
| 88 |
+
)
|
| 89 |
+
return (
|
| 90 |
+
lora_output
|
| 91 |
+
if self.lora_backend.fuse_output_add
|
| 92 |
+
else base_output + lora_output
|
| 93 |
+
)
|
| 94 |
+
|
| 95 |
+
def forward(self, input_: torch.Tensor):
|
| 96 |
+
# duplicate the logic in ColumnParallelLinear
|
| 97 |
+
bias = self.base_layer.bias if not self.base_layer.skip_bias_add else None
|
| 98 |
+
output_parallel = self.base_layer.quant_method.apply(
|
| 99 |
+
self.base_layer, input_, bias
|
| 100 |
+
)
|
| 101 |
+
|
| 102 |
+
if self.set_lora:
|
| 103 |
+
output_parallel = self.apply_lora(output_parallel, input_)
|
| 104 |
+
|
| 105 |
+
if self.base_layer.gather_output:
|
| 106 |
+
output = tensor_model_parallel_all_gather(output_parallel)
|
| 107 |
+
else:
|
| 108 |
+
output = output_parallel
|
| 109 |
+
output_bias = self.base_layer.bias if self.base_layer.skip_bias_add else None
|
| 110 |
+
return output, output_bias
|
| 111 |
+
|
| 112 |
+
def slice_lora_a_weights(self, A: torch.Tensor, tp_rank: int):
|
| 113 |
+
return A
|
| 114 |
+
|
| 115 |
+
def slice_lora_b_weights(self, B: torch.Tensor, tp_rank: int):
|
| 116 |
+
shard_size = self.base_layer.output_partition_sizes[0]
|
| 117 |
+
start_idx = tp_rank * shard_size
|
| 118 |
+
end_idx = (tp_rank + 1) * shard_size
|
| 119 |
+
B = B[start_idx:end_idx, :]
|
| 120 |
+
return B
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
class MergedColumnParallelLinearWithLoRA(ColumnParallelLinearWithLoRA):
|
| 124 |
+
def __init__(
|
| 125 |
+
self,
|
| 126 |
+
base_layer: MergedColumnParallelLinear,
|
| 127 |
+
lora_backend: BaseLoRABackend,
|
| 128 |
+
) -> None:
|
| 129 |
+
super().__init__(base_layer, lora_backend)
|
| 130 |
+
|
| 131 |
+
def set_lora_info(
|
| 132 |
+
self,
|
| 133 |
+
A_buffer: torch.Tensor,
|
| 134 |
+
B_buffer: torch.Tensor,
|
| 135 |
+
):
|
| 136 |
+
self.set_lora = True
|
| 137 |
+
self.A_buffer_gate_up = A_buffer
|
| 138 |
+
if self.lora_backend.fuse_stacked_lora_b:
|
| 139 |
+
# TODO: avoid using contiguous() in GPU.
|
| 140 |
+
# B_buffer_gate_up: (num_lora, 2 * output_dim, r)
|
| 141 |
+
self.B_buffer_gate_up = torch.cat(
|
| 142 |
+
(B_buffer[0], B_buffer[1]), dim=-2
|
| 143 |
+
).contiguous()
|
| 144 |
+
else:
|
| 145 |
+
self.B_buffer_gate_up = (B_buffer[0], B_buffer[1])
|
| 146 |
+
|
| 147 |
+
def apply_lora(self, base_output: torch.Tensor, x: torch.Tensor) -> torch.Tensor:
|
| 148 |
+
backend_kwargs = {"base_output": base_output}
|
| 149 |
+
|
| 150 |
+
lora_output = self.lora_backend.run_gate_up_lora(
|
| 151 |
+
x,
|
| 152 |
+
self.A_buffer_gate_up,
|
| 153 |
+
self.B_buffer_gate_up,
|
| 154 |
+
**backend_kwargs,
|
| 155 |
+
)
|
| 156 |
+
return (
|
| 157 |
+
lora_output
|
| 158 |
+
if self.lora_backend.fuse_output_add
|
| 159 |
+
else base_output + lora_output
|
| 160 |
+
)
|
| 161 |
+
|
| 162 |
+
def slice_lora_a_weights(self, A: torch.Tensor, tp_rank: int):
|
| 163 |
+
return A
|
| 164 |
+
|
| 165 |
+
def slice_lora_b_weights(self, B: torch.Tensor, tp_rank: int):
|
| 166 |
+
# Since the outputs for both gate and up are identical, we use a random one.
|
| 167 |
+
shard_size = self.base_layer.output_partition_sizes[0]
|
| 168 |
+
start_idx = tp_rank * shard_size
|
| 169 |
+
end_idx = (tp_rank + 1) * shard_size
|
| 170 |
+
return B[:, start_idx:end_idx, :]
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
class QKVParallelLinearWithLoRA(ColumnParallelLinearWithLoRA):
|
| 174 |
+
def init__(
|
| 175 |
+
self,
|
| 176 |
+
base_layer: QKVParallelLinear,
|
| 177 |
+
lora_backend: BaseLoRABackend,
|
| 178 |
+
) -> None:
|
| 179 |
+
super().__init__(base_layer, lora_backend)
|
| 180 |
+
|
| 181 |
+
def set_lora_info(
|
| 182 |
+
self,
|
| 183 |
+
A_buffer_qkv: torch.Tensor,
|
| 184 |
+
B_buffer_q: torch.Tensor,
|
| 185 |
+
B_buffer_kv: torch.Tensor,
|
| 186 |
+
):
|
| 187 |
+
self.set_lora = True
|
| 188 |
+
self.A_buffer_qkv = A_buffer_qkv
|
| 189 |
+
|
| 190 |
+
if self.lora_backend.fuse_stacked_lora_b:
|
| 191 |
+
assert (
|
| 192 |
+
B_buffer_q.shape[-1] == B_buffer_kv.shape[-1]
|
| 193 |
+
), "The lora rank of q and kv should be the same when enabling fusion of qkv lora_b"
|
| 194 |
+
output_dim_q, output_dim_kv = B_buffer_q.shape[-2], B_buffer_kv.shape[-2]
|
| 195 |
+
|
| 196 |
+
# B_buffer_qkv: (num_lora, output_dim_q + 2 * output_dim_kv, r)
|
| 197 |
+
self.B_buffer_qkv = torch.cat(
|
| 198 |
+
(B_buffer_q[0], B_buffer_kv[0], B_buffer_kv[1]), dim=-2
|
| 199 |
+
).contiguous()
|
| 200 |
+
|
| 201 |
+
# Offsets of q/k/v in output dimension
|
| 202 |
+
self.output_offset = torch.tensor(
|
| 203 |
+
[
|
| 204 |
+
0,
|
| 205 |
+
output_dim_q,
|
| 206 |
+
output_dim_q + output_dim_kv,
|
| 207 |
+
output_dim_q + 2 * output_dim_kv,
|
| 208 |
+
],
|
| 209 |
+
dtype=torch.int32,
|
| 210 |
+
device=B_buffer_q.device,
|
| 211 |
+
)
|
| 212 |
+
# For computing number of launched blocks
|
| 213 |
+
self.max_qkv_out_dim = max(output_dim_q, output_dim_kv)
|
| 214 |
+
else:
|
| 215 |
+
self.B_buffer_qkv = (
|
| 216 |
+
B_buffer_q,
|
| 217 |
+
B_buffer_kv,
|
| 218 |
+
)
|
| 219 |
+
|
| 220 |
+
def apply_lora(self, base_output: torch.Tensor, x: torch.Tensor) -> torch.Tensor:
|
| 221 |
+
backend_kwargs = {"base_output": base_output}
|
| 222 |
+
if self.lora_backend.fuse_stacked_lora_b:
|
| 223 |
+
backend_kwargs["output_offset"] = self.output_offset
|
| 224 |
+
backend_kwargs["max_qkv_out_dim"] = self.max_qkv_out_dim
|
| 225 |
+
|
| 226 |
+
lora_output = self.lora_backend.run_qkv_lora(
|
| 227 |
+
x,
|
| 228 |
+
self.A_buffer_qkv,
|
| 229 |
+
self.B_buffer_qkv,
|
| 230 |
+
**backend_kwargs,
|
| 231 |
+
)
|
| 232 |
+
return (
|
| 233 |
+
lora_output
|
| 234 |
+
if self.lora_backend.fuse_output_add
|
| 235 |
+
else base_output + lora_output
|
| 236 |
+
)
|
| 237 |
+
|
| 238 |
+
def slice_lora_a_weights(self, A: torch.Tensor, tp_rank: int):
|
| 239 |
+
return A
|
| 240 |
+
|
| 241 |
+
def slice_lora_b_weights(
|
| 242 |
+
self, B: List[torch.Tensor], tp_rank: int
|
| 243 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 244 |
+
B_q, B_kv = B
|
| 245 |
+
base_layer = self.base_layer
|
| 246 |
+
q_proj_shard_size = base_layer.q_proj_shard_size
|
| 247 |
+
kv_proj_shard_size = base_layer.kv_proj_shard_size
|
| 248 |
+
num_kv_head_replicas = base_layer.num_kv_head_replicas
|
| 249 |
+
|
| 250 |
+
q_start_idx = q_proj_shard_size * tp_rank
|
| 251 |
+
q_end_idx = q_start_idx + q_proj_shard_size
|
| 252 |
+
|
| 253 |
+
kv_shard_id = tp_rank // num_kv_head_replicas
|
| 254 |
+
kv_start_idx = kv_proj_shard_size * kv_shard_id
|
| 255 |
+
kv_end_idx = kv_start_idx + kv_proj_shard_size
|
| 256 |
+
|
| 257 |
+
return B_q[q_start_idx:q_end_idx, :], B_kv[:, kv_start_idx:kv_end_idx, :]
|
| 258 |
+
|
| 259 |
+
|
| 260 |
+
class RowParallelLinearWithLoRA(BaseLayerWithLoRA):
|
| 261 |
+
def __init__(
|
| 262 |
+
self,
|
| 263 |
+
base_layer: RowParallelLinear,
|
| 264 |
+
lora_backend: BaseLoRABackend,
|
| 265 |
+
) -> None:
|
| 266 |
+
super().__init__(base_layer, lora_backend)
|
| 267 |
+
|
| 268 |
+
def set_lora_info(self, A_buffer: torch.Tensor, B_buffer: torch.Tensor):
|
| 269 |
+
self.set_lora = True
|
| 270 |
+
self.A_buffer = A_buffer
|
| 271 |
+
self.B_buffer = B_buffer
|
| 272 |
+
|
| 273 |
+
def apply_lora(self, base_output: torch.Tensor, x: torch.Tensor) -> torch.Tensor:
|
| 274 |
+
backend_kwargs = {"base_output": base_output}
|
| 275 |
+
lora_a_output = self.lora_backend.run_lora_a_sgemm(x, self.A_buffer)
|
| 276 |
+
lora_output = self.lora_backend.run_lora_b_sgemm(
|
| 277 |
+
lora_a_output,
|
| 278 |
+
self.B_buffer[0],
|
| 279 |
+
**backend_kwargs,
|
| 280 |
+
)
|
| 281 |
+
return (
|
| 282 |
+
lora_output
|
| 283 |
+
if self.lora_backend.fuse_output_add
|
| 284 |
+
else base_output + lora_output
|
| 285 |
+
)
|
| 286 |
+
|
| 287 |
+
def forward(self, input_: torch.Tensor):
|
| 288 |
+
# duplicate the logic in RowParallelLinear
|
| 289 |
+
if self.base_layer.input_is_parallel:
|
| 290 |
+
input_parallel = input_
|
| 291 |
+
else:
|
| 292 |
+
tp_rank = get_tensor_model_parallel_rank()
|
| 293 |
+
splitted_input = split_tensor_along_last_dim(
|
| 294 |
+
input_, num_partitions=self.base_layer.tp_size
|
| 295 |
+
)
|
| 296 |
+
input_parallel = splitted_input[tp_rank].contiguous()
|
| 297 |
+
output_parallel = self.base_layer.quant_method.apply(
|
| 298 |
+
self.base_layer, input_parallel
|
| 299 |
+
)
|
| 300 |
+
|
| 301 |
+
if self.set_lora:
|
| 302 |
+
output_parallel = self.apply_lora(output_parallel, input_parallel)
|
| 303 |
+
|
| 304 |
+
if self.base_layer.reduce_results and self.base_layer.tp_size > 1:
|
| 305 |
+
output_ = tensor_model_parallel_all_reduce(output_parallel)
|
| 306 |
+
else:
|
| 307 |
+
output_ = output_parallel
|
| 308 |
+
|
| 309 |
+
if not self.base_layer.skip_bias_add:
|
| 310 |
+
output = (
|
| 311 |
+
output_ + self.base_layer.bias
|
| 312 |
+
if self.base_layer.bias is not None
|
| 313 |
+
else output_
|
| 314 |
+
)
|
| 315 |
+
output_bias = None
|
| 316 |
+
else:
|
| 317 |
+
output = output_
|
| 318 |
+
output_bias = self.base_layer.bias
|
| 319 |
+
return output, output_bias
|
| 320 |
+
|
| 321 |
+
def slice_lora_a_weights(self, A: torch.Tensor, tp_rank: int):
|
| 322 |
+
shard_size = self.base_layer.input_size_per_partition
|
| 323 |
+
start_idx = tp_rank * shard_size
|
| 324 |
+
end_idx = (tp_rank + 1) * shard_size
|
| 325 |
+
A = A[:, start_idx:end_idx].contiguous()
|
| 326 |
+
return A
|
| 327 |
+
|
| 328 |
+
def slice_lora_b_weights(self, B: torch.Tensor, tp_rank: int):
|
| 329 |
+
return B
|
| 330 |
+
|
| 331 |
+
|
| 332 |
+
def get_lora_layer(
|
| 333 |
+
layer: nn.Module, lora_backend: BaseLoRABackend
|
| 334 |
+
) -> BaseLayerWithLoRA:
|
| 335 |
+
supported_layer_types = {
|
| 336 |
+
# the order matters
|
| 337 |
+
VocabParallelEmbedding: VocabParallelEmbeddingWithLoRA,
|
| 338 |
+
QKVParallelLinear: QKVParallelLinearWithLoRA,
|
| 339 |
+
MergedColumnParallelLinear: MergedColumnParallelLinearWithLoRA,
|
| 340 |
+
ColumnParallelLinear: ColumnParallelLinearWithLoRA,
|
| 341 |
+
RowParallelLinear: RowParallelLinearWithLoRA,
|
| 342 |
+
}
|
| 343 |
+
for src_layer_type, lora_layer_type in supported_layer_types.items():
|
| 344 |
+
if isinstance(layer, src_layer_type): # pylint: disable=unidiomatic-typecheck
|
| 345 |
+
ret = lora_layer_type(layer, lora_backend)
|
| 346 |
+
return ret
|
| 347 |
+
raise Exception(f"No corresponding LoRA layer supported for {type(layer)}.")
|
lib/python3.12/site-packages/sglang/srt/lora/lora.py
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2023-2024 SGLang Team
|
| 2 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 3 |
+
# you may not use this file except in compliance with the License.
|
| 4 |
+
# You may obtain a copy of the License at
|
| 5 |
+
#
|
| 6 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 7 |
+
#
|
| 8 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 9 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 10 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 11 |
+
# See the License for the specific language governing permissions and
|
| 12 |
+
# limitations under the License.
|
| 13 |
+
# ==============================================================================
|
| 14 |
+
|
| 15 |
+
# Integrates "S-LoRA: Serving Thousands of Concurrent LoRA Adapters"
|
| 16 |
+
# and "Punica: Multi-Tenant LoRA Serving"
|
| 17 |
+
|
| 18 |
+
# LoRA layers class inheritance adapted from:
|
| 19 |
+
# https://github.com/vllm-project/vllm/blob/4abf6336ec65c270343eb895e7b18786e9274176/vllm/lora/layers.py
|
| 20 |
+
|
| 21 |
+
import logging
|
| 22 |
+
import re
|
| 23 |
+
from typing import Dict, List
|
| 24 |
+
|
| 25 |
+
import torch
|
| 26 |
+
from torch import nn
|
| 27 |
+
|
| 28 |
+
from sglang.srt.configs.load_config import LoadConfig
|
| 29 |
+
from sglang.srt.hf_transformers_utils import AutoConfig
|
| 30 |
+
from sglang.srt.lora.backend.base_backend import BaseLoRABackend
|
| 31 |
+
from sglang.srt.lora.lora_config import LoRAConfig
|
| 32 |
+
from sglang.srt.model_loader.loader import DefaultModelLoader
|
| 33 |
+
|
| 34 |
+
logger = logging.getLogger(__name__)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
class LoRALayer(nn.Module):
|
| 38 |
+
def __init__(self, config: LoRAConfig, base_hf_config: AutoConfig):
|
| 39 |
+
super().__init__()
|
| 40 |
+
self.config: LoRAConfig = config
|
| 41 |
+
self.base_hf_config: AutoConfig = base_hf_config
|
| 42 |
+
|
| 43 |
+
# lora weights in cpu. The weights are loaded from checkpoint.
|
| 44 |
+
self.weights: Dict[str, torch.Tensor] = {}
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
class LoRAAdapter(nn.Module):
|
| 48 |
+
def __init__(
|
| 49 |
+
self,
|
| 50 |
+
uid: str,
|
| 51 |
+
config: LoRAConfig,
|
| 52 |
+
base_hf_config: AutoConfig,
|
| 53 |
+
load_config: LoadConfig,
|
| 54 |
+
lora_backend: BaseLoRABackend,
|
| 55 |
+
):
|
| 56 |
+
super().__init__()
|
| 57 |
+
self.uid: str = uid
|
| 58 |
+
self.config: LoRAConfig = config
|
| 59 |
+
assert self.config.hf_config["peft_type"].lower() == "lora"
|
| 60 |
+
self.base_hf_config: AutoConfig = base_hf_config
|
| 61 |
+
self.load_config: LoadConfig = load_config
|
| 62 |
+
self.lora_backend: BaseLoRABackend = lora_backend
|
| 63 |
+
self.scaling: float = self.config.lora_alpha / self.config.r
|
| 64 |
+
|
| 65 |
+
self.layers: List[LoRALayer] = nn.ModuleList(
|
| 66 |
+
[
|
| 67 |
+
LoRALayer(config, base_hf_config)
|
| 68 |
+
for i in range(base_hf_config.num_hidden_layers)
|
| 69 |
+
]
|
| 70 |
+
)
|
| 71 |
+
|
| 72 |
+
self.weights: Dict[str, torch.Tensor] = {}
|
| 73 |
+
|
| 74 |
+
# initialize the LoRA weights to cpu
|
| 75 |
+
def initialize_weights(self):
|
| 76 |
+
model_path = self.config.path
|
| 77 |
+
loader = DefaultModelLoader(self.load_config)
|
| 78 |
+
revision = getattr(self.config.hf_config, "revision", None)
|
| 79 |
+
for name, loaded_weight in loader._get_weights_iterator(
|
| 80 |
+
DefaultModelLoader.Source(
|
| 81 |
+
model_path, revision=revision, fall_back_to_pt=True
|
| 82 |
+
)
|
| 83 |
+
):
|
| 84 |
+
match = re.search(r"layers\.(\d+)\.", name)
|
| 85 |
+
if match is not None:
|
| 86 |
+
layer_id = int(match.group(1))
|
| 87 |
+
self.layers[layer_id].weights[name] = loaded_weight.cpu()
|
| 88 |
+
else:
|
| 89 |
+
self.weights[name] = loaded_weight.cpu()
|
| 90 |
+
|
| 91 |
+
# stack kv_proj and gate_up_proj
|
| 92 |
+
for i in range(self.base_hf_config.num_hidden_layers):
|
| 93 |
+
layer = self.layers[i]
|
| 94 |
+
weight_names = [name for name, _ in layer.weights.items()]
|
| 95 |
+
self.stack_qkv_proj(weight_names, layer.weights)
|
| 96 |
+
self.stack_gate_up_proj(weight_names, layer.weights)
|
| 97 |
+
|
| 98 |
+
def stack_qkv_proj(self, weight_names: List[str], weights: Dict[str, torch.Tensor]):
|
| 99 |
+
|
| 100 |
+
# Collect target q/k/v modules. This process is necessary since there might be no lora attached to k_proj
|
| 101 |
+
target_module = set()
|
| 102 |
+
for weight_name in weight_names:
|
| 103 |
+
if "k_proj" in weight_name:
|
| 104 |
+
target_module.add("k_proj")
|
| 105 |
+
if "q_proj" in weight_name:
|
| 106 |
+
target_module.add("q_proj")
|
| 107 |
+
if "v_proj" in weight_name:
|
| 108 |
+
target_module.add("v_proj")
|
| 109 |
+
if len(target_module) == 0:
|
| 110 |
+
return
|
| 111 |
+
|
| 112 |
+
for weight_name in weight_names:
|
| 113 |
+
# We assume every lora adaptor should contain lora modules for q_proj
|
| 114 |
+
if "q_proj" in weight_name:
|
| 115 |
+
q_name = weight_name
|
| 116 |
+
k_name = weight_name.replace("q_proj", "k_proj")
|
| 117 |
+
v_name = weight_name.replace("q_proj", "v_proj")
|
| 118 |
+
kv_name = weight_name.replace("q_proj", "kv_proj")
|
| 119 |
+
qkv_name = weight_name.replace("q_proj", "qkv_proj")
|
| 120 |
+
|
| 121 |
+
# If k_proj doesn't have lora, initialize it to zero
|
| 122 |
+
k_proj_weight = (
|
| 123 |
+
weights[k_name]
|
| 124 |
+
if "k_proj" in target_module
|
| 125 |
+
else torch.zeros_like(weights[v_name])
|
| 126 |
+
)
|
| 127 |
+
if "lora_A" in weight_name:
|
| 128 |
+
weights[qkv_name] = torch.cat(
|
| 129 |
+
(
|
| 130 |
+
weights[q_name],
|
| 131 |
+
k_proj_weight,
|
| 132 |
+
weights[v_name],
|
| 133 |
+
),
|
| 134 |
+
0,
|
| 135 |
+
)
|
| 136 |
+
weights.pop(q_name)
|
| 137 |
+
if "k_proj" in target_module:
|
| 138 |
+
weights.pop(k_name)
|
| 139 |
+
weights.pop(v_name)
|
| 140 |
+
else:
|
| 141 |
+
weights[kv_name] = torch.stack(
|
| 142 |
+
[
|
| 143 |
+
k_proj_weight,
|
| 144 |
+
weights[v_name],
|
| 145 |
+
],
|
| 146 |
+
dim=0,
|
| 147 |
+
)
|
| 148 |
+
if "k_proj" in target_module:
|
| 149 |
+
weights.pop(k_name)
|
| 150 |
+
weights.pop(v_name)
|
| 151 |
+
|
| 152 |
+
def stack_gate_up_proj(
|
| 153 |
+
self, weight_names: List[str], weights: Dict[str, torch.Tensor]
|
| 154 |
+
):
|
| 155 |
+
for weight_name in weight_names:
|
| 156 |
+
if "gate_proj" in weight_name:
|
| 157 |
+
up_name = weight_name.replace("gate_proj", "up_proj")
|
| 158 |
+
gate_up_name = weight_name.replace("gate_proj", "gate_up_proj")
|
| 159 |
+
if up_name not in weights:
|
| 160 |
+
logger.warning(
|
| 161 |
+
f"Gate projection {weight_name} does not have a corresponding up projection {up_name}. "
|
| 162 |
+
f"Initializing up projection to zero."
|
| 163 |
+
)
|
| 164 |
+
weights[up_name] = torch.zeros_like(weights[weight_name])
|
| 165 |
+
# FIXME: Add gate-only support for flashinfer in future implementations
|
| 166 |
+
assert self.lora_backend.name == "triton", (
|
| 167 |
+
f"LoRA weight initialization currently only supported for 'triton' backend. "
|
| 168 |
+
f"Received backend: {self.lora_backend.name}. Please verify your backend configuration "
|
| 169 |
+
f"or consider implementing custom initialization logic for other backends."
|
| 170 |
+
)
|
| 171 |
+
if "lora_A" in weight_name:
|
| 172 |
+
weights[gate_up_name] = torch.cat(
|
| 173 |
+
(weights[weight_name], weights[up_name]), 0
|
| 174 |
+
)
|
| 175 |
+
else:
|
| 176 |
+
weights[gate_up_name] = torch.stack(
|
| 177 |
+
[weights[weight_name], weights[up_name]], dim=0
|
| 178 |
+
)
|
| 179 |
+
weights.pop(weight_name)
|
| 180 |
+
if up_name in weights:
|
| 181 |
+
weights.pop(up_name)
|
lib/python3.12/site-packages/sglang/srt/lora/lora_config.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2023-2024 SGLang Team
|
| 2 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 3 |
+
# you may not use this file except in compliance with the License.
|
| 4 |
+
# You may obtain a copy of the License at
|
| 5 |
+
#
|
| 6 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 7 |
+
#
|
| 8 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 9 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 10 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 11 |
+
# See the License for the specific language governing permissions and
|
| 12 |
+
# limitations under the License.
|
| 13 |
+
# ==============================================================================
|
| 14 |
+
|
| 15 |
+
import json
|
| 16 |
+
import os
|
| 17 |
+
|
| 18 |
+
from huggingface_hub import snapshot_download
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class LoRAConfig:
|
| 22 |
+
def __init__(
|
| 23 |
+
self,
|
| 24 |
+
path: str,
|
| 25 |
+
) -> None:
|
| 26 |
+
self.path = path
|
| 27 |
+
self.hf_config = self.get_lora_config()
|
| 28 |
+
self.target_modules = self.hf_config["target_modules"]
|
| 29 |
+
|
| 30 |
+
# TODO: Support more modules
|
| 31 |
+
if any(module in self.target_modules for module in ["embed_tokens", "lm_head"]):
|
| 32 |
+
raise ValueError("Not supported yet")
|
| 33 |
+
|
| 34 |
+
self.r = self.hf_config["r"]
|
| 35 |
+
self.lora_alpha = self.hf_config["lora_alpha"]
|
| 36 |
+
|
| 37 |
+
def get_lora_config(self, dummy=False):
|
| 38 |
+
if dummy:
|
| 39 |
+
raise NotImplementedError()
|
| 40 |
+
else:
|
| 41 |
+
if not os.path.isdir(self.path):
|
| 42 |
+
weights_dir = snapshot_download(self.path, allow_patterns=["*.json"])
|
| 43 |
+
else:
|
| 44 |
+
weights_dir = self.path
|
| 45 |
+
config_name = "adapter_config.json"
|
| 46 |
+
with open(os.path.join(weights_dir, config_name), "r") as f:
|
| 47 |
+
return json.load(f)
|
lib/python3.12/site-packages/sglang/srt/lora/lora_manager.py
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2023-2024 SGLang Team
|
| 2 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 3 |
+
# you may not use this file except in compliance with the License.
|
| 4 |
+
# You may obtain a copy of the License at
|
| 5 |
+
#
|
| 6 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 7 |
+
#
|
| 8 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 9 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 10 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 11 |
+
# See the License for the specific language governing permissions and
|
| 12 |
+
# limitations under the License.
|
| 13 |
+
# ==============================================================================
|
| 14 |
+
|
| 15 |
+
# Integrates "S-LoRA: Serving Thousands of Concurrent LoRA Adapters"
|
| 16 |
+
# and "Punica: Multi-Tenant LoRA Serving"
|
| 17 |
+
|
| 18 |
+
import logging
|
| 19 |
+
from typing import Dict, List, Set, Tuple
|
| 20 |
+
|
| 21 |
+
import torch
|
| 22 |
+
|
| 23 |
+
from sglang.srt.configs.load_config import LoadConfig
|
| 24 |
+
from sglang.srt.hf_transformers_utils import AutoConfig
|
| 25 |
+
from sglang.srt.lora.backend.base_backend import BaseLoRABackend, get_backend_from_name
|
| 26 |
+
from sglang.srt.lora.layers import BaseLayerWithLoRA, get_lora_layer
|
| 27 |
+
from sglang.srt.lora.lora import LoRAAdapter
|
| 28 |
+
from sglang.srt.lora.lora_config import LoRAConfig
|
| 29 |
+
from sglang.srt.lora.mem_pool import LoRAMemoryPool
|
| 30 |
+
from sglang.srt.lora.utils import (
|
| 31 |
+
LoRABatchInfo,
|
| 32 |
+
LoRAType,
|
| 33 |
+
get_customized_names_from_hf_names,
|
| 34 |
+
get_layer_id,
|
| 35 |
+
get_stacked_name,
|
| 36 |
+
get_weight_name,
|
| 37 |
+
)
|
| 38 |
+
from sglang.srt.model_executor.forward_batch_info import ForwardBatch
|
| 39 |
+
from sglang.srt.utils import replace_submodule
|
| 40 |
+
|
| 41 |
+
logger = logging.getLogger(__name__)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class LoRAManager:
|
| 45 |
+
def __init__(
|
| 46 |
+
self,
|
| 47 |
+
base_model: torch.nn.Module,
|
| 48 |
+
lora_paths: Dict[str, str],
|
| 49 |
+
base_hf_config: AutoConfig,
|
| 50 |
+
max_loras_per_batch: int,
|
| 51 |
+
load_config: LoadConfig,
|
| 52 |
+
dtype: torch.dtype,
|
| 53 |
+
lora_backend: str = "triton",
|
| 54 |
+
tp_size: int = 1,
|
| 55 |
+
tp_rank: int = 0,
|
| 56 |
+
):
|
| 57 |
+
self.base_model: torch.nn.Module = base_model
|
| 58 |
+
self.lora_paths: Dict[str, str] = lora_paths
|
| 59 |
+
self.base_hf_config: AutoConfig = base_hf_config
|
| 60 |
+
self.max_loras_per_batch: int = max_loras_per_batch
|
| 61 |
+
self.load_config: LoadConfig = load_config
|
| 62 |
+
self.dtype: torch.dtype = dtype
|
| 63 |
+
self.device: torch.device = next(self.base_model.parameters()).device
|
| 64 |
+
self.tp_size: int = tp_size
|
| 65 |
+
self.tp_rank: int = tp_rank
|
| 66 |
+
|
| 67 |
+
# LoRA backend for running sgemm kernels
|
| 68 |
+
logger.info(f"Using {lora_backend} as backend of LoRA kernels.")
|
| 69 |
+
backend_type = get_backend_from_name(lora_backend)
|
| 70 |
+
self.lora_backend: BaseLoRABackend = backend_type(lora_backend)
|
| 71 |
+
|
| 72 |
+
self.init_loras()
|
| 73 |
+
self.init_lora_memory_pool()
|
| 74 |
+
|
| 75 |
+
def init_loras(self):
|
| 76 |
+
# Config of each LoRA adapter
|
| 77 |
+
self.configs: Dict[str, LoRAConfig] = {}
|
| 78 |
+
|
| 79 |
+
# Target module names in huggingface lora configs.
|
| 80 |
+
# e.g., {"k_proj", "q_proj", "v_proj", "o_proj"}
|
| 81 |
+
self.hf_target_names: Set[str] = set()
|
| 82 |
+
for name, path in self.lora_paths.items():
|
| 83 |
+
self.configs[name] = LoRAConfig(path)
|
| 84 |
+
self.hf_target_names.update(self.configs[name].target_modules)
|
| 85 |
+
|
| 86 |
+
# Target lora weight names for lora_a and lora_b modules repectively.
|
| 87 |
+
# e.g., {("qkv_proj", "q_proj"), ("qkv_proj", "kv_proj")}
|
| 88 |
+
self.lora_weight_names: Set[Tuple[str]] = set(
|
| 89 |
+
[get_stacked_name(module) for module in self.hf_target_names]
|
| 90 |
+
)
|
| 91 |
+
|
| 92 |
+
# load all weights to cpu
|
| 93 |
+
self.loras: Dict[str, LoRAAdapter] = {}
|
| 94 |
+
for name in self.lora_paths.keys():
|
| 95 |
+
lora_adapter = LoRAAdapter(
|
| 96 |
+
name,
|
| 97 |
+
self.configs[name],
|
| 98 |
+
self.base_hf_config,
|
| 99 |
+
self.load_config,
|
| 100 |
+
self.lora_backend,
|
| 101 |
+
)
|
| 102 |
+
lora_adapter.initialize_weights()
|
| 103 |
+
self.loras[name] = lora_adapter
|
| 104 |
+
|
| 105 |
+
# misc lora configs
|
| 106 |
+
self.max_lora_dim: int = max([x.hf_config["r"] for x in self.configs.values()])
|
| 107 |
+
|
| 108 |
+
if self.lora_backend == "flashinfer":
|
| 109 |
+
# FIXME remove the restrictions after supporting multi-rank for flashinfer backend
|
| 110 |
+
max_lora_dim = max([x.hf_config["r"] for x in self.configs.values()])
|
| 111 |
+
scaling = list(self.loras.values())[0].scaling
|
| 112 |
+
assert all(x.hf_config["r"] == max_lora_dim for x in self.configs.values())
|
| 113 |
+
assert all(x.scaling == scaling for x in self.loras.values())
|
| 114 |
+
|
| 115 |
+
# Convert original model layers to layers with LoRA
|
| 116 |
+
self.convert_to_lora_layers()
|
| 117 |
+
|
| 118 |
+
def init_lora_memory_pool(self):
|
| 119 |
+
# Initialize memory pool
|
| 120 |
+
self.memory_pool = LoRAMemoryPool(
|
| 121 |
+
self.base_hf_config,
|
| 122 |
+
self.max_loras_per_batch,
|
| 123 |
+
self.max_lora_dim,
|
| 124 |
+
self.dtype,
|
| 125 |
+
self.tp_size,
|
| 126 |
+
self.tp_rank,
|
| 127 |
+
self.lora_modules,
|
| 128 |
+
)
|
| 129 |
+
|
| 130 |
+
# Initialize target lora modules in memory pool
|
| 131 |
+
self.memory_pool.init_buffers(self.lora_weight_names, self.base_model)
|
| 132 |
+
|
| 133 |
+
def prepare_lora_batch(self, forward_batch: ForwardBatch):
|
| 134 |
+
# load active loras into lora memory pool
|
| 135 |
+
cur_uids = set(forward_batch.lora_paths)
|
| 136 |
+
assert len(cur_uids) <= self.max_loras_per_batch
|
| 137 |
+
self.memory_pool.prepare_lora_batch(cur_uids, self.loras)
|
| 138 |
+
|
| 139 |
+
# FIXME: Handle lora uid with None more safely
|
| 140 |
+
if cur_uids == set([None]):
|
| 141 |
+
return
|
| 142 |
+
|
| 143 |
+
# set up batch info shared by all lora moruldes
|
| 144 |
+
bs = forward_batch.batch_size
|
| 145 |
+
seg_lens = (
|
| 146 |
+
forward_batch.extend_seq_lens
|
| 147 |
+
if forward_batch.forward_mode.is_extend()
|
| 148 |
+
else torch.ones(bs, device=self.device)
|
| 149 |
+
)
|
| 150 |
+
seg_indptr = torch.zeros((bs + 1,), dtype=torch.int32, device=self.device)
|
| 151 |
+
seg_indptr[1:] = torch.cumsum(seg_lens, dim=0)
|
| 152 |
+
max_len = int(torch.max(seg_lens))
|
| 153 |
+
weight_indices = torch.empty((bs,), dtype=torch.int64, device=self.device)
|
| 154 |
+
|
| 155 |
+
lora_ranks = torch.empty(
|
| 156 |
+
(self.max_loras_per_batch,), dtype=torch.int64, device="cuda"
|
| 157 |
+
)
|
| 158 |
+
scalings = torch.empty(
|
| 159 |
+
(self.max_loras_per_batch,), dtype=torch.float, device="cuda"
|
| 160 |
+
)
|
| 161 |
+
for i, lora_path in enumerate(forward_batch.lora_paths):
|
| 162 |
+
weight_indices[i] = self.memory_pool.get_buffer_id(lora_path)
|
| 163 |
+
lora = self.loras[lora_path]
|
| 164 |
+
lora_ranks[weight_indices[i]] = lora.config.hf_config["r"]
|
| 165 |
+
scalings[weight_indices[i]] = lora.scaling
|
| 166 |
+
|
| 167 |
+
batch_info = LoRABatchInfo(
|
| 168 |
+
bs=bs,
|
| 169 |
+
seg_lens=seg_lens,
|
| 170 |
+
seg_indptr=seg_indptr,
|
| 171 |
+
max_len=max_len,
|
| 172 |
+
weight_indices=weight_indices,
|
| 173 |
+
lora_ranks=lora_ranks,
|
| 174 |
+
scalings=scalings,
|
| 175 |
+
)
|
| 176 |
+
self.lora_backend.set_batch_info(batch_info)
|
| 177 |
+
|
| 178 |
+
# call set_lora_info for each lora modules
|
| 179 |
+
for layer_id, modules in self.lora_modules.items():
|
| 180 |
+
for module_name, module in modules:
|
| 181 |
+
if "qkv_proj" in module_name:
|
| 182 |
+
module.set_lora_info(
|
| 183 |
+
self.memory_pool.get_tensor(
|
| 184 |
+
"qkv_proj", layer_id, LoRAType.LORA_A
|
| 185 |
+
),
|
| 186 |
+
self.memory_pool.get_tensor(
|
| 187 |
+
"q_proj", layer_id, LoRAType.LORA_B
|
| 188 |
+
),
|
| 189 |
+
self.memory_pool.get_tensor(
|
| 190 |
+
"kv_proj", layer_id, LoRAType.LORA_B
|
| 191 |
+
),
|
| 192 |
+
)
|
| 193 |
+
else:
|
| 194 |
+
weight_name = get_weight_name(
|
| 195 |
+
module_name, self.lora_weight_names, LoRAType.LORA_A
|
| 196 |
+
)
|
| 197 |
+
module.set_lora_info(
|
| 198 |
+
self.memory_pool.get_tensor(
|
| 199 |
+
weight_name, layer_id, LoRAType.LORA_A
|
| 200 |
+
),
|
| 201 |
+
self.memory_pool.get_tensor(
|
| 202 |
+
weight_name, layer_id, LoRAType.LORA_B
|
| 203 |
+
),
|
| 204 |
+
)
|
| 205 |
+
|
| 206 |
+
def set_lora_module(self, module_name, module):
|
| 207 |
+
lora_module = get_lora_layer(module, self.lora_backend)
|
| 208 |
+
replace_submodule(self.base_model, module_name, lora_module)
|
| 209 |
+
return lora_module
|
| 210 |
+
|
| 211 |
+
def convert_to_lora_layers(self):
|
| 212 |
+
# Target module names of customized layers defined in python/sglang/srt/layers
|
| 213 |
+
# e.g., {"qkv_proj", "o_proj"}
|
| 214 |
+
customized_target_names = get_customized_names_from_hf_names(
|
| 215 |
+
self.hf_target_names, self.base_model
|
| 216 |
+
)
|
| 217 |
+
|
| 218 |
+
# Monkey patch to use the LoRA version layers
|
| 219 |
+
self.lora_modules: Dict[int, List[Tuple[str, BaseLayerWithLoRA]]] = {
|
| 220 |
+
i: [] for i in range(self.base_hf_config.num_hidden_layers)
|
| 221 |
+
}
|
| 222 |
+
for module_name, module in self.base_model.named_modules():
|
| 223 |
+
# The module should be converted if it is included in target_names
|
| 224 |
+
if module_name.split(".")[-1] in customized_target_names:
|
| 225 |
+
layer_id = get_layer_id(module_name)
|
| 226 |
+
self.lora_modules[layer_id].append(
|
| 227 |
+
(module_name, self.set_lora_module(module_name, module))
|
| 228 |
+
)
|
lib/python3.12/site-packages/sglang/srt/lora/mem_pool.py
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Dict, List, Optional, Set, Tuple
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
|
| 5 |
+
from sglang.srt.distributed import divide
|
| 6 |
+
from sglang.srt.hf_transformers_utils import AutoConfig
|
| 7 |
+
from sglang.srt.lora.layers import BaseLayerWithLoRA
|
| 8 |
+
from sglang.srt.lora.lora import LoRAAdapter
|
| 9 |
+
from sglang.srt.lora.utils import (
|
| 10 |
+
ROW_PARALLELISM_LINEAR_LORA_NAMES,
|
| 11 |
+
LoRAType,
|
| 12 |
+
get_hidden_dim,
|
| 13 |
+
get_stacked_multiply,
|
| 14 |
+
get_weight_name,
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class LoRAMemoryPool:
|
| 19 |
+
"""Class for memory pool management of lora modules"""
|
| 20 |
+
|
| 21 |
+
def __init__(
|
| 22 |
+
self,
|
| 23 |
+
base_hf_config: AutoConfig,
|
| 24 |
+
max_loras_per_batch: int,
|
| 25 |
+
max_lora_dim: int,
|
| 26 |
+
dtype: torch.dtype,
|
| 27 |
+
tp_size: int,
|
| 28 |
+
tp_rank: int,
|
| 29 |
+
lora_modules: Dict[int, List[Tuple[str, BaseLayerWithLoRA]]],
|
| 30 |
+
):
|
| 31 |
+
|
| 32 |
+
self.base_hf_config: AutoConfig = base_hf_config
|
| 33 |
+
self.num_layer: int = base_hf_config.num_hidden_layers
|
| 34 |
+
self.max_loras_per_batch: int = max_loras_per_batch
|
| 35 |
+
self.max_lora_dim: int = max_lora_dim
|
| 36 |
+
self.dtype: torch.dtype = dtype
|
| 37 |
+
self.tp_size: int = tp_size
|
| 38 |
+
self.tp_rank: int = tp_rank
|
| 39 |
+
self.lora_modules: Dict[int, List[Tuple[str, BaseLayerWithLoRA]]] = lora_modules
|
| 40 |
+
|
| 41 |
+
# Both A_buffer and B_buffer maps lora weight names to its buffer space.
|
| 42 |
+
# A_buffer contains num_layer number of row-major tensors with shape
|
| 43 |
+
# (max_loras_per_batch, stacked_num * max_lora_dim, input_dim)
|
| 44 |
+
# B_buffer contains num_layer number of column-major tensors with shape
|
| 45 |
+
# (stacked_num, max_loras_per_batch, output_dim, max_lora_dim)
|
| 46 |
+
self.A_buffer: Dict[str, List[torch.Tensor]] = {}
|
| 47 |
+
self.B_buffer: Dict[str, List[torch.Tensor]] = {}
|
| 48 |
+
|
| 49 |
+
# Lora uid -> buffer idx in memory pool
|
| 50 |
+
self.uid_to_buffer_id: Dict[Optional[str], int] = {}
|
| 51 |
+
|
| 52 |
+
# Buffer idx -> lora uid in memory pool
|
| 53 |
+
# All uids are initalized as empty strings for empty buffer slots
|
| 54 |
+
# Here we don't initalize to None since None is a valid uid
|
| 55 |
+
self.buffer_id_to_uid: List[Optional[str]] = [""] * self.max_loras_per_batch
|
| 56 |
+
|
| 57 |
+
def get_lora_A_shape(
|
| 58 |
+
self, module_name: str, base_model: torch.nn.Module
|
| 59 |
+
) -> Tuple[int]:
|
| 60 |
+
"""
|
| 61 |
+
Given a module_name (might be a stacked name), return the hidden dims of modules's input and output.
|
| 62 |
+
"""
|
| 63 |
+
input_dim, _ = get_hidden_dim(module_name, self.base_hf_config, base_model)
|
| 64 |
+
c = get_stacked_multiply(module_name)
|
| 65 |
+
if self.tp_size > 1:
|
| 66 |
+
if module_name in ROW_PARALLELISM_LINEAR_LORA_NAMES:
|
| 67 |
+
input_dim = divide(input_dim, self.tp_size)
|
| 68 |
+
return (
|
| 69 |
+
self.max_loras_per_batch,
|
| 70 |
+
self.max_lora_dim * c,
|
| 71 |
+
input_dim,
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
def get_lora_B_shape(
|
| 75 |
+
self, module_name: str, base_model: torch.nn.Module
|
| 76 |
+
) -> Tuple[int]:
|
| 77 |
+
"""
|
| 78 |
+
Given a module_name (might be a stacked name), return the hidden dims of modules's input and output.
|
| 79 |
+
"""
|
| 80 |
+
_, output_dim = get_hidden_dim(module_name, self.base_hf_config, base_model)
|
| 81 |
+
c = get_stacked_multiply(module_name)
|
| 82 |
+
if self.tp_size > 1:
|
| 83 |
+
if module_name not in ROW_PARALLELISM_LINEAR_LORA_NAMES:
|
| 84 |
+
output_dim = divide(output_dim, self.tp_size)
|
| 85 |
+
return (
|
| 86 |
+
c,
|
| 87 |
+
self.max_loras_per_batch,
|
| 88 |
+
output_dim,
|
| 89 |
+
self.max_lora_dim,
|
| 90 |
+
)
|
| 91 |
+
|
| 92 |
+
def init_buffers(
|
| 93 |
+
self,
|
| 94 |
+
lora_weight_names: Set[Tuple[str]],
|
| 95 |
+
base_model: torch.nn.Module,
|
| 96 |
+
):
|
| 97 |
+
|
| 98 |
+
# lora_weight_names is a set of name pairs indicating each pair of lora modules to load
|
| 99 |
+
# e.g., {("qkv_proj", "q_proj"), ("qkv_proj", "kv_proj"), ("o_proj", "o_proj")}
|
| 100 |
+
self.lora_weight_names: Set[Tuple[str]] = lora_weight_names
|
| 101 |
+
device = next(base_model.parameters()).device
|
| 102 |
+
lora_module_A_names = set([name[0] for name in lora_weight_names])
|
| 103 |
+
lora_module_B_names = set([name[1] for name in lora_weight_names])
|
| 104 |
+
# Init A tensor, column_major=False
|
| 105 |
+
for module_A in lora_module_A_names:
|
| 106 |
+
lora_A_shape = self.get_lora_A_shape(module_A, base_model)
|
| 107 |
+
self.A_buffer[module_A] = [
|
| 108 |
+
torch.empty(
|
| 109 |
+
lora_A_shape,
|
| 110 |
+
dtype=self.dtype,
|
| 111 |
+
device=device,
|
| 112 |
+
)
|
| 113 |
+
for i in range(self.num_layer)
|
| 114 |
+
]
|
| 115 |
+
# Init B tensor, column_major=True
|
| 116 |
+
for module_B in lora_module_B_names:
|
| 117 |
+
lora_B_shape = self.get_lora_B_shape(module_B, base_model)
|
| 118 |
+
self.B_buffer[module_B] = [
|
| 119 |
+
torch.empty(
|
| 120 |
+
lora_B_shape,
|
| 121 |
+
dtype=self.dtype,
|
| 122 |
+
device=device,
|
| 123 |
+
)
|
| 124 |
+
for _ in range(self.num_layer)
|
| 125 |
+
]
|
| 126 |
+
|
| 127 |
+
def prepare_lora_batch(
|
| 128 |
+
self,
|
| 129 |
+
cur_uids: Set[Optional[str]],
|
| 130 |
+
lora_adapters: Dict[str, LoRAAdapter],
|
| 131 |
+
):
|
| 132 |
+
|
| 133 |
+
def get_available_buffer_slot():
|
| 134 |
+
for buffer_id in range(self.max_loras_per_batch):
|
| 135 |
+
# Prioritize empty slots
|
| 136 |
+
if self.buffer_id_to_uid[buffer_id] == "":
|
| 137 |
+
return buffer_id, ""
|
| 138 |
+
|
| 139 |
+
for buffer_id in range(self.max_loras_per_batch):
|
| 140 |
+
# Evict unneeded lora
|
| 141 |
+
if self.buffer_id_to_uid[buffer_id] not in cur_uids:
|
| 142 |
+
return buffer_id, self.buffer_id_to_uid[buffer_id]
|
| 143 |
+
|
| 144 |
+
raise ValueError(
|
| 145 |
+
"No available buffer slots found. Please ensure the number of active loras is less than max_loras_per_batch."
|
| 146 |
+
)
|
| 147 |
+
|
| 148 |
+
for uid in cur_uids:
|
| 149 |
+
if uid not in self.uid_to_buffer_id:
|
| 150 |
+
buffer_id, evicted_lora_uid = get_available_buffer_slot()
|
| 151 |
+
if evicted_lora_uid != "":
|
| 152 |
+
self.uid_to_buffer_id.pop(evicted_lora_uid)
|
| 153 |
+
self.load_lora_weight_to_buffer(
|
| 154 |
+
uid, buffer_id, lora_adapters.get(uid, None)
|
| 155 |
+
)
|
| 156 |
+
self.uid_to_buffer_id[uid] = buffer_id
|
| 157 |
+
self.buffer_id_to_uid[buffer_id] = uid
|
| 158 |
+
|
| 159 |
+
def load_lora_weight_to_buffer(
|
| 160 |
+
self, uid: str, buffer_id: int, lora_adapter: LoRAAdapter = None
|
| 161 |
+
):
|
| 162 |
+
|
| 163 |
+
if uid is None:
|
| 164 |
+
for i in range(self.num_layer):
|
| 165 |
+
for k in self.A_buffer.keys():
|
| 166 |
+
self.A_buffer[k][i][buffer_id] = 0
|
| 167 |
+
return
|
| 168 |
+
|
| 169 |
+
assert lora_adapter is not None
|
| 170 |
+
lora_rank = lora_adapter.config.hf_config["r"]
|
| 171 |
+
for layer_id in range(self.num_layer):
|
| 172 |
+
layer_weights = lora_adapter.layers[layer_id].weights
|
| 173 |
+
temp_A_buffer: Dict[str, torch.Tensor] = {}
|
| 174 |
+
temp_B_buffer: Dict[str, torch.Tensor] = {}
|
| 175 |
+
for name, weights in layer_weights.items():
|
| 176 |
+
if "lora_A" in name:
|
| 177 |
+
lora_weight_name = get_weight_name(
|
| 178 |
+
name, self.lora_weight_names, LoRAType.LORA_A
|
| 179 |
+
)
|
| 180 |
+
temp_A_buffer[lora_weight_name] = weights
|
| 181 |
+
else:
|
| 182 |
+
lora_weight_name = get_weight_name(
|
| 183 |
+
name, self.lora_weight_names, LoRAType.LORA_B
|
| 184 |
+
)
|
| 185 |
+
temp_B_buffer[lora_weight_name] = weights
|
| 186 |
+
|
| 187 |
+
if self.tp_size > 1:
|
| 188 |
+
cur_layer_modules = self.lora_modules[layer_id]
|
| 189 |
+
for module_name, module in cur_layer_modules:
|
| 190 |
+
if "qkv_proj" in module_name:
|
| 191 |
+
temp_A_buffer["qkv_proj"] = module.slice_lora_a_weights(
|
| 192 |
+
temp_A_buffer["qkv_proj"], self.tp_rank
|
| 193 |
+
)
|
| 194 |
+
temp_B_buffer["q_proj"], temp_B_buffer["kv_proj"] = (
|
| 195 |
+
module.slice_lora_b_weights(
|
| 196 |
+
[temp_B_buffer["q_proj"], temp_B_buffer["kv_proj"]],
|
| 197 |
+
self.tp_rank,
|
| 198 |
+
)
|
| 199 |
+
)
|
| 200 |
+
else:
|
| 201 |
+
weight_name = get_weight_name(
|
| 202 |
+
module_name, self.lora_weight_names, LoRAType.LORA_A
|
| 203 |
+
)
|
| 204 |
+
temp_A_buffer[weight_name] = module.slice_lora_a_weights(
|
| 205 |
+
temp_A_buffer[weight_name], self.tp_rank
|
| 206 |
+
)
|
| 207 |
+
temp_B_buffer[weight_name] = module.slice_lora_b_weights(
|
| 208 |
+
temp_B_buffer[weight_name], self.tp_rank
|
| 209 |
+
)
|
| 210 |
+
|
| 211 |
+
for name, weights in temp_A_buffer.items():
|
| 212 |
+
c = get_stacked_multiply(name)
|
| 213 |
+
self.A_buffer[name][layer_id][buffer_id][: lora_rank * c, :].copy_(
|
| 214 |
+
weights
|
| 215 |
+
)
|
| 216 |
+
|
| 217 |
+
for name, weights in temp_B_buffer.items():
|
| 218 |
+
c = get_stacked_multiply(name)
|
| 219 |
+
if c > 1:
|
| 220 |
+
for stacked_id in range(c):
|
| 221 |
+
self.B_buffer[name][layer_id][stacked_id][buffer_id][
|
| 222 |
+
:, :lora_rank
|
| 223 |
+
].copy_(weights[stacked_id])
|
| 224 |
+
else:
|
| 225 |
+
self.B_buffer[name][layer_id][0][buffer_id][:, :lora_rank].copy_(
|
| 226 |
+
weights
|
| 227 |
+
)
|
| 228 |
+
|
| 229 |
+
def get_tensor(
|
| 230 |
+
self, weight_name: str, layer_id: int, lora_type: LoRAType
|
| 231 |
+
) -> torch.Tensor:
|
| 232 |
+
|
| 233 |
+
if lora_type == LoRAType.LORA_A:
|
| 234 |
+
return self.A_buffer[weight_name][layer_id]
|
| 235 |
+
|
| 236 |
+
return self.B_buffer[weight_name][layer_id]
|
| 237 |
+
|
| 238 |
+
def get_buffer_id(self, lora_uid: str):
|
| 239 |
+
return self.uid_to_buffer_id[lora_uid]
|
lib/python3.12/site-packages/sglang/srt/lora/triton_ops/__init__.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .gate_up_lora_b import gate_up_lora_b_fwd
|
| 2 |
+
from .qkv_lora_b import qkv_lora_b_fwd
|
| 3 |
+
from .sgemm_lora_a import sgemm_lora_a_fwd
|
| 4 |
+
from .sgemm_lora_b import sgemm_lora_b_fwd
|
| 5 |
+
|
| 6 |
+
__all__ = [
|
| 7 |
+
"gate_up_lora_b_fwd",
|
| 8 |
+
"qkv_lora_b_fwd",
|
| 9 |
+
"sgemm_lora_a_fwd",
|
| 10 |
+
"sgemm_lora_b_fwd",
|
| 11 |
+
]
|
lib/python3.12/site-packages/sglang/srt/lora/triton_ops/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (469 Bytes). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/lora/triton_ops/__pycache__/gate_up_lora_b.cpython-312.pyc
ADDED
|
Binary file (5.47 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/lora/triton_ops/__pycache__/qkv_lora_b.cpython-312.pyc
ADDED
|
Binary file (5.8 kB). View file
|
|
|
lib/python3.12/site-packages/sglang/srt/lora/triton_ops/__pycache__/sgemm_lora_a.cpython-312.pyc
ADDED
|
Binary file (5.2 kB). View file
|
|
|