chore: vendor sglang v0.5.10 snapshot
This commit is contained in:
29
third_party/sglang/python/sglang/srt/batch_invariant_ops/__init__.py
vendored
Normal file
29
third_party/sglang/python/sglang/srt/batch_invariant_ops/__init__.py
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
# Adapted from https://github.com/thinking-machines-lab/batch_invariant_ops/blob/main/batch_invariant_ops/__init__.py
|
||||
|
||||
from .batch_invariant_ops import (
|
||||
AttentionBlockSize,
|
||||
disable_batch_invariant_mode,
|
||||
enable_batch_invariant_mode,
|
||||
get_batch_invariant_attention_block_size,
|
||||
is_batch_invariant_mode_enabled,
|
||||
log_softmax,
|
||||
matmul_persistent,
|
||||
mean_dim,
|
||||
rms_norm_batch_invariant,
|
||||
set_batch_invariant_mode,
|
||||
)
|
||||
|
||||
__version__ = "0.1.0"
|
||||
|
||||
__all__ = [
|
||||
"set_batch_invariant_mode",
|
||||
"is_batch_invariant_mode_enabled",
|
||||
"disable_batch_invariant_mode",
|
||||
"enable_batch_invariant_mode",
|
||||
"matmul_persistent",
|
||||
"log_softmax",
|
||||
"mean_dim",
|
||||
"get_batch_invariant_attention_block_size",
|
||||
"AttentionBlockSize",
|
||||
"rms_norm_batch_invariant",
|
||||
]
|
||||
994
third_party/sglang/python/sglang/srt/batch_invariant_ops/batch_invariant_ops.py
vendored
Normal file
994
third_party/sglang/python/sglang/srt/batch_invariant_ops/batch_invariant_ops.py
vendored
Normal file
@@ -0,0 +1,994 @@
|
||||
# Adapted from https://github.com/thinking-machines-lab/batch_invariant_ops/blob/main/batch_invariant_ops/batch_invariant_ops.py
|
||||
|
||||
import contextlib
|
||||
from collections import namedtuple
|
||||
from collections.abc import Callable
|
||||
from typing import Any, Dict
|
||||
|
||||
import torch
|
||||
import triton
|
||||
import triton.language as tl
|
||||
|
||||
from sglang.srt.layers.deep_gemm_wrapper.configurer import ENABLE_JIT_DEEPGEMM
|
||||
from sglang.srt.utils.common import calc_diff, get_bool_env_var
|
||||
|
||||
if ENABLE_JIT_DEEPGEMM:
|
||||
import deep_gemm
|
||||
|
||||
_ENABLE_MM_DEEPGEMM = get_bool_env_var(
|
||||
"SGLANG_BATCH_INVARIANT_OPS_ENABLE_MM_DEEPGEMM", "1"
|
||||
)
|
||||
# If true, allows to fallback to batch variant gemm when the shape cannot be run in DeepGEMM
|
||||
_ENABLE_MM_FALLBACK_VARIANT = get_bool_env_var(
|
||||
"SGLANG_BATCH_INVARIANT_OPS_ENABLE_MM_FALLBACK_VARIANT", "0"
|
||||
)
|
||||
_ENABLE_MM_COMPARISON_TEST = get_bool_env_var(
|
||||
"SGLANG_BATCH_INVARIANT_OPS_ENABLE_MM_COMPARISON_TEST"
|
||||
)
|
||||
|
||||
if not _ENABLE_MM_DEEPGEMM:
|
||||
print("Disable DeepGEMM in batch invariant ops. Performance may be suboptimal.")
|
||||
|
||||
__all__ = [
|
||||
"set_batch_invariant_mode",
|
||||
"is_batch_invariant_mode_enabled",
|
||||
"disable_batch_invariant_mode",
|
||||
"enable_batch_invariant_mode",
|
||||
]
|
||||
|
||||
|
||||
def _matmul_launch_metadata(
|
||||
grid: Callable[..., Any], kernel: Any, args: Dict[str, Any]
|
||||
) -> Dict[str, Any]:
|
||||
ret = {}
|
||||
m, n, k = args["M"], args["N"], args["K"]
|
||||
ret["name"] = f"{kernel.name} [M={m}, N={n}, K={k}]"
|
||||
if "tiles_per_update" in args:
|
||||
ret["name"] = (
|
||||
f"{kernel.name} [M={m}, N={n}, K={k}, tiles_per_update={args['tiles_per_update']:02}]"
|
||||
)
|
||||
if "c_ptr" in args:
|
||||
bytes_per_elem = args["c_ptr"].element_size()
|
||||
else:
|
||||
bytes_per_elem = 1 if args["FP8_OUTPUT"] else 2
|
||||
ret[f"flops{bytes_per_elem * 8}"] = 2.0 * m * n * k
|
||||
ret["bytes"] = bytes_per_elem * (m * k + n * k + m * n)
|
||||
return ret
|
||||
|
||||
|
||||
@triton.jit
|
||||
def _compute_pid(tile_id, num_pid_in_group, num_pid_m, GROUP_SIZE_M, NUM_SMS):
|
||||
group_id = tile_id // num_pid_in_group
|
||||
first_pid_m = group_id * GROUP_SIZE_M
|
||||
group_size_m = min(num_pid_m - first_pid_m, GROUP_SIZE_M)
|
||||
pid_m = first_pid_m + (tile_id % group_size_m)
|
||||
pid_n = (tile_id % num_pid_in_group) // group_size_m
|
||||
return pid_m, pid_n
|
||||
|
||||
|
||||
@triton.jit(launch_metadata=_matmul_launch_metadata)
|
||||
def matmul_kernel_persistent(
|
||||
a_ptr,
|
||||
b_ptr,
|
||||
c_ptr, #
|
||||
bias_ptr,
|
||||
M,
|
||||
N,
|
||||
K, #
|
||||
stride_am,
|
||||
stride_ak,
|
||||
stride_bk,
|
||||
stride_bn,
|
||||
stride_cm,
|
||||
stride_cn,
|
||||
BLOCK_SIZE_M: tl.constexpr, #
|
||||
BLOCK_SIZE_N: tl.constexpr, #
|
||||
BLOCK_SIZE_K: tl.constexpr, #
|
||||
GROUP_SIZE_M: tl.constexpr, #
|
||||
NUM_SMS: tl.constexpr, #
|
||||
A_LARGE: tl.constexpr,
|
||||
B_LARGE: tl.constexpr,
|
||||
C_LARGE: tl.constexpr,
|
||||
HAS_BIAS: tl.constexpr,
|
||||
):
|
||||
start_pid = tl.program_id(axis=0)
|
||||
num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
|
||||
num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
|
||||
k_tiles = tl.cdiv(K, BLOCK_SIZE_K)
|
||||
num_tiles = num_pid_m * num_pid_n
|
||||
|
||||
offs_k_for_mask = tl.arange(0, BLOCK_SIZE_K)
|
||||
num_pid_in_group = GROUP_SIZE_M * num_pid_n
|
||||
|
||||
for tile_id in tl.range(start_pid, num_tiles, NUM_SMS, flatten=True):
|
||||
pid_m, pid_n = _compute_pid(
|
||||
tile_id, num_pid_in_group, num_pid_m, GROUP_SIZE_M, NUM_SMS
|
||||
)
|
||||
start_m = pid_m * BLOCK_SIZE_M
|
||||
start_n = pid_n * BLOCK_SIZE_N
|
||||
offs_am = start_m + tl.arange(0, BLOCK_SIZE_M)
|
||||
offs_bn = start_n + tl.arange(0, BLOCK_SIZE_N)
|
||||
if A_LARGE:
|
||||
offs_am = offs_am.to(tl.int64)
|
||||
if B_LARGE:
|
||||
offs_bn = offs_bn.to(tl.int64)
|
||||
offs_am = tl.where(offs_am < M, offs_am, 0)
|
||||
offs_bn = tl.where(offs_bn < N, offs_bn, 0)
|
||||
offs_am = tl.max_contiguous(tl.multiple_of(offs_am, BLOCK_SIZE_M), BLOCK_SIZE_M)
|
||||
offs_bn = tl.max_contiguous(tl.multiple_of(offs_bn, BLOCK_SIZE_N), BLOCK_SIZE_N)
|
||||
|
||||
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
||||
for ki in range(k_tiles):
|
||||
if A_LARGE or B_LARGE:
|
||||
offs_k = ki * BLOCK_SIZE_K + tl.arange(0, BLOCK_SIZE_K).to(tl.int64)
|
||||
else:
|
||||
offs_k = ki * BLOCK_SIZE_K + tl.arange(0, BLOCK_SIZE_K)
|
||||
a_ptrs = a_ptr + (
|
||||
offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak
|
||||
)
|
||||
b_ptrs = b_ptr + (
|
||||
offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn
|
||||
)
|
||||
|
||||
a = tl.load(
|
||||
a_ptrs, mask=offs_k_for_mask[None, :] < K - ki * BLOCK_SIZE_K, other=0.0
|
||||
)
|
||||
b = tl.load(
|
||||
b_ptrs, mask=offs_k_for_mask[:, None] < K - ki * BLOCK_SIZE_K, other=0.0
|
||||
)
|
||||
accumulator = tl.dot(a, b, accumulator)
|
||||
|
||||
offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
|
||||
offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
||||
if C_LARGE:
|
||||
offs_cm = offs_cm.to(tl.int64)
|
||||
offs_cn = offs_cn.to(tl.int64)
|
||||
c_ptrs = c_ptr + stride_cm * offs_cm[:, None] + stride_cn * offs_cn[None, :]
|
||||
c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < N)
|
||||
if HAS_BIAS:
|
||||
bias_ptrs = bias_ptr + offs_cn
|
||||
bias = tl.load(bias_ptrs, mask=offs_cn < N, other=0.0).to(tl.float32)
|
||||
accumulator += bias
|
||||
if c_ptr.dtype.element_ty == tl.float8e4nv:
|
||||
c = accumulator.to(tl.float8e4nv)
|
||||
elif c_ptr.dtype.element_ty == tl.bfloat16:
|
||||
c = accumulator.to(tl.bfloat16)
|
||||
elif c_ptr.dtype.element_ty == tl.float32:
|
||||
c = accumulator.to(tl.float32)
|
||||
else:
|
||||
c = accumulator.to(tl.float16)
|
||||
tl.store(c_ptrs, c, mask=c_mask)
|
||||
|
||||
|
||||
def _matmul_persistent_triton(
|
||||
a: torch.Tensor, b: torch.Tensor, bias: torch.Tensor | None = None
|
||||
):
|
||||
# Check constraints.
|
||||
assert a.shape[1] == b.shape[0], "Incompatible dimensions"
|
||||
assert a.dtype == b.dtype, "Incompatible dtypes"
|
||||
assert (
|
||||
bias is None or bias.dim() == 1
|
||||
), "Currently assuming bias is 1D, let Horace know if you run into this"
|
||||
NUM_SMS = torch.cuda.get_device_properties("cuda").multi_processor_count
|
||||
M, K = a.shape
|
||||
K, N = b.shape
|
||||
dtype = a.dtype
|
||||
# Allocates output.
|
||||
c = torch.empty((M, N), device=a.device, dtype=dtype)
|
||||
|
||||
# 1D launch kernel where each block gets its own program.
|
||||
def grid(META):
|
||||
return (
|
||||
min(
|
||||
NUM_SMS,
|
||||
triton.cdiv(M, META["BLOCK_SIZE_M"])
|
||||
* triton.cdiv(N, META["BLOCK_SIZE_N"]),
|
||||
),
|
||||
)
|
||||
|
||||
configs = {
|
||||
torch.bfloat16: {
|
||||
"BLOCK_SIZE_M": 128,
|
||||
"BLOCK_SIZE_N": 128,
|
||||
"BLOCK_SIZE_K": 64,
|
||||
"GROUP_SIZE_M": 8,
|
||||
"num_stages": 3,
|
||||
"num_warps": 8,
|
||||
},
|
||||
torch.float16: {
|
||||
"BLOCK_SIZE_M": 128,
|
||||
"BLOCK_SIZE_N": 256,
|
||||
"BLOCK_SIZE_K": 64,
|
||||
"GROUP_SIZE_M": 8,
|
||||
"num_stages": 3,
|
||||
"num_warps": 8,
|
||||
},
|
||||
torch.float32: {
|
||||
"BLOCK_SIZE_M": 128,
|
||||
"BLOCK_SIZE_N": 128,
|
||||
"BLOCK_SIZE_K": 32,
|
||||
"GROUP_SIZE_M": 8,
|
||||
"num_stages": 3,
|
||||
"num_warps": 8,
|
||||
},
|
||||
}
|
||||
# print(a.device, b.device, c.device)
|
||||
matmul_kernel_persistent[grid](
|
||||
a,
|
||||
b,
|
||||
c, #
|
||||
bias,
|
||||
M,
|
||||
N,
|
||||
K, #
|
||||
a.stride(0),
|
||||
a.stride(1), #
|
||||
b.stride(0),
|
||||
b.stride(1), #
|
||||
c.stride(0),
|
||||
c.stride(1), #
|
||||
NUM_SMS=NUM_SMS, #
|
||||
A_LARGE=a.numel() > 2**31,
|
||||
B_LARGE=b.numel() > 2**31,
|
||||
C_LARGE=c.numel() > 2**31,
|
||||
HAS_BIAS=bias is not None,
|
||||
**configs[dtype],
|
||||
)
|
||||
return c
|
||||
|
||||
|
||||
def _matmul_persistent_deepgemm(
|
||||
a: torch.Tensor, b: torch.Tensor, bias: torch.Tensor | None = None
|
||||
):
|
||||
M, K = a.shape
|
||||
K, N = b.shape
|
||||
dtype = a.dtype
|
||||
out = torch.empty((M, N), device=a.device, dtype=dtype)
|
||||
|
||||
try:
|
||||
deep_gemm.bf16_gemm_nn(a, b, out)
|
||||
except RuntimeError as e:
|
||||
raise RuntimeError(
|
||||
f"DeepGEMM failed for matrix shapes M={M}, N={N}, K={K}. "
|
||||
f"This typically occurs when dimensions are too small for DeepGEMM's TMA descriptors. "
|
||||
f"Consider increasing MIN_DEEPGEMM_DIM in matmul_persistent() or disabling DeepGEMM "
|
||||
f"for small matrices. Original error: {e}"
|
||||
) from e
|
||||
|
||||
# TODO can this be put in DeepGEMM's `c`?
|
||||
if bias is not None:
|
||||
out += bias
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def matmul_persistent(
|
||||
a: torch.Tensor, b: torch.Tensor, bias: torch.Tensor | None = None
|
||||
):
|
||||
K, N = b.shape
|
||||
|
||||
# DeepGEMM has minimum dimension requirements for TMA descriptors
|
||||
MIN_DEEPGEMM_DIM = 16
|
||||
|
||||
if (
|
||||
_ENABLE_MM_DEEPGEMM
|
||||
and ENABLE_JIT_DEEPGEMM
|
||||
and (a.dtype == torch.bfloat16)
|
||||
and (b.dtype == torch.bfloat16)
|
||||
and a.is_contiguous()
|
||||
and b.transpose(0, 1).is_contiguous()
|
||||
and N >= MIN_DEEPGEMM_DIM
|
||||
):
|
||||
if _ENABLE_MM_COMPARISON_TEST:
|
||||
out_triton = _matmul_persistent_triton(a=a, b=b, bias=bias)
|
||||
out_deepgemm = _matmul_persistent_deepgemm(a=a, b=b, bias=bias)
|
||||
diff = calc_diff(out_triton, out_deepgemm)
|
||||
assert diff < 0.0001, f"{diff=} {out_triton=} {out_deepgemm=}"
|
||||
# can be enabled for debugging
|
||||
# print(
|
||||
# f"{diff=} "
|
||||
# f"{(out_triton - out_deepgemm).abs().mean()=} "
|
||||
# f"{(out_triton - out_deepgemm).abs().sum()=} "
|
||||
# f"{torch.sum(out_triton != out_deepgemm)=} "
|
||||
# )
|
||||
# print(f"{a=} {b=} {bias=} {out_triton=} {out_deepgemm=}")
|
||||
return out_deepgemm
|
||||
|
||||
return _matmul_persistent_deepgemm(a=a, b=b, bias=bias)
|
||||
|
||||
if _ENABLE_MM_FALLBACK_VARIANT:
|
||||
out = torch.einsum("ik,kj->ij", a, b)
|
||||
if bias is not None:
|
||||
out += bias
|
||||
return out
|
||||
|
||||
return _matmul_persistent_triton(a=a, b=b, bias=bias)
|
||||
|
||||
|
||||
@triton.jit
|
||||
def _log_softmax_kernel(
|
||||
input_ptr,
|
||||
output_ptr,
|
||||
input_row_stride: tl.constexpr,
|
||||
output_row_stride: tl.constexpr,
|
||||
n_cols: tl.constexpr,
|
||||
BLOCK_SIZE: tl.constexpr,
|
||||
):
|
||||
"""
|
||||
Compute log_softmax along the last dimension of a 2D tensor.
|
||||
Each block handles one row of the input tensor.
|
||||
"""
|
||||
# Get the row index for this block
|
||||
row_idx = tl.program_id(0).to(tl.int64)
|
||||
|
||||
# Compute base pointers for input and output rows
|
||||
row_start_ptr = input_ptr + row_idx * input_row_stride
|
||||
output_row_start_ptr = output_ptr + row_idx * output_row_stride
|
||||
|
||||
# Step 1: Find maximum value in the row for numerical stability
|
||||
# Load first block to infer dtype and initialize max_val with correct type
|
||||
col_idx_init = tl.arange(0, BLOCK_SIZE)
|
||||
mask_init = col_idx_init < n_cols
|
||||
vals_init = tl.load(
|
||||
row_start_ptr + col_idx_init, mask=mask_init, other=-float("inf")
|
||||
)
|
||||
max_val = tl.max(vals_init)
|
||||
|
||||
# Continue with remaining blocks
|
||||
for col_offset in range(BLOCK_SIZE, n_cols, BLOCK_SIZE):
|
||||
col_idx = col_offset + tl.arange(0, BLOCK_SIZE)
|
||||
mask = col_idx < n_cols
|
||||
|
||||
# Load values
|
||||
vals = tl.load(row_start_ptr + col_idx, mask=mask, other=-float("inf"))
|
||||
|
||||
# Update maximum
|
||||
max_val = tl.max(tl.maximum(vals, max_val))
|
||||
|
||||
# Step 2: Compute sum of exp(x - max_val)
|
||||
# Initialize sum_exp with correct dtype by using tl.sum on a zero vector
|
||||
sum_exp = tl.sum(tl.zeros([1], dtype=max_val.dtype))
|
||||
|
||||
for col_offset in range(0, n_cols, BLOCK_SIZE):
|
||||
col_idx = col_offset + tl.arange(0, BLOCK_SIZE)
|
||||
mask = col_idx < n_cols
|
||||
|
||||
# Load values
|
||||
vals = tl.load(row_start_ptr + col_idx, mask=mask, other=0.0)
|
||||
|
||||
# Compute exp(x - max_val) and accumulate
|
||||
exp_vals = tl.exp(vals - max_val)
|
||||
sum_exp += tl.sum(tl.where(mask, exp_vals, 0.0))
|
||||
|
||||
# Compute log(sum_exp)
|
||||
log_sum_exp = tl.log(sum_exp)
|
||||
|
||||
# Step 3: Compute final log_softmax values: x - max_val - log_sum_exp
|
||||
for col_offset in range(0, n_cols, BLOCK_SIZE):
|
||||
col_idx = col_offset + tl.arange(0, BLOCK_SIZE)
|
||||
mask = col_idx < n_cols
|
||||
|
||||
# Load values
|
||||
vals = tl.load(row_start_ptr + col_idx, mask=mask)
|
||||
|
||||
# Compute log_softmax
|
||||
output = vals - max_val - log_sum_exp
|
||||
|
||||
# Store results
|
||||
tl.store(output_row_start_ptr + col_idx, output, mask=mask)
|
||||
|
||||
|
||||
def log_softmax(input: torch.Tensor, dim: int = -1) -> torch.Tensor:
|
||||
"""
|
||||
Compute log_softmax using Triton kernel.
|
||||
|
||||
Args:
|
||||
input: Input tensor
|
||||
dim: Dimension along which to compute log_softmax (only -1 or last dim supported)
|
||||
>> Stashed changes
|
||||
Returns:
|
||||
Tensor with log_softmax applied along the specified dimension
|
||||
"""
|
||||
if dim != -1 and dim != input.ndim - 1:
|
||||
raise ValueError(
|
||||
"This implementation only supports log_softmax along the last dimension"
|
||||
)
|
||||
|
||||
# Flatten all dimensions except the last one
|
||||
original_shape = input.shape
|
||||
input_2d = input.reshape(-1, input.shape[-1])
|
||||
input_2d = input_2d.contiguous()
|
||||
|
||||
n_rows, n_cols = input_2d.shape
|
||||
|
||||
# Allocate output tensor
|
||||
output = torch.empty_like(input_2d)
|
||||
|
||||
# Choose block size based on the number of columns
|
||||
BLOCK_SIZE = 1024
|
||||
|
||||
# Launch kernel with one block per row
|
||||
grid = (n_rows,)
|
||||
_log_softmax_kernel[grid](
|
||||
input_2d,
|
||||
output,
|
||||
input_2d.stride(0),
|
||||
output.stride(0),
|
||||
n_cols,
|
||||
BLOCK_SIZE=BLOCK_SIZE,
|
||||
)
|
||||
# Reshape output back to original shape
|
||||
return output.reshape(original_shape)
|
||||
|
||||
|
||||
@triton.jit
|
||||
def mean_kernel(
|
||||
input_ptr,
|
||||
output_ptr,
|
||||
input_stride0,
|
||||
input_stride1,
|
||||
input_stride2,
|
||||
output_stride0,
|
||||
output_stride1,
|
||||
M, # size before reduction dim
|
||||
N, # size of reduction dim
|
||||
K, # size after reduction dim
|
||||
BLOCK_SIZE: tl.constexpr,
|
||||
):
|
||||
"""
|
||||
Kernel for computing mean along a single dimension.
|
||||
Input is viewed as (M, N, K) where N is the dimension being reduced.
|
||||
"""
|
||||
# Program ID gives us which output element we're computing
|
||||
pid = tl.program_id(0)
|
||||
|
||||
# Compute output indices
|
||||
m_idx = pid // K
|
||||
k_idx = pid % K
|
||||
|
||||
# Bounds check
|
||||
if m_idx >= M or k_idx >= K:
|
||||
return
|
||||
|
||||
# Accumulate sum across reduction dimension
|
||||
acc = 0.0
|
||||
for n_start in range(0, N, BLOCK_SIZE):
|
||||
n_offsets = n_start + tl.arange(0, BLOCK_SIZE)
|
||||
mask = n_offsets < N
|
||||
|
||||
# Calculate input indices
|
||||
input_idx = (
|
||||
m_idx * input_stride0 + n_offsets * input_stride1 + k_idx * input_stride2
|
||||
)
|
||||
|
||||
# Load and accumulate
|
||||
vals = tl.load(input_ptr + input_idx, mask=mask, other=0.0)
|
||||
acc += tl.sum(vals)
|
||||
|
||||
# Compute mean and store
|
||||
mean_val = acc / N
|
||||
output_idx = m_idx * output_stride0 + k_idx * output_stride1
|
||||
tl.store(output_ptr + output_idx, mean_val)
|
||||
|
||||
|
||||
def mean_dim(
|
||||
input: torch.Tensor,
|
||||
dim: int,
|
||||
keepdim: bool = False,
|
||||
dtype: torch.dtype | None = None,
|
||||
) -> torch.Tensor:
|
||||
"""
|
||||
Triton implementation of torch.mean with single dimension reduction.
|
||||
|
||||
Args:
|
||||
input: Input tensor
|
||||
dim: Single dimension along which to compute mean
|
||||
keepdim: Whether to keep the reduced dimension
|
||||
dtype: Output dtype. If None, uses input dtype (or float32 for integer inputs)
|
||||
|
||||
Returns:
|
||||
Tensor with mean values along specified dimension
|
||||
"""
|
||||
# Validate inputs
|
||||
assert input.is_cuda, "Input must be a CUDA tensor"
|
||||
assert (
|
||||
-input.ndim <= dim < input.ndim
|
||||
), f"Invalid dimension {dim} for tensor with {input.ndim} dimensions"
|
||||
|
||||
# Handle negative dim
|
||||
if dim < 0:
|
||||
dim = dim + input.ndim
|
||||
|
||||
# Handle dtype
|
||||
if dtype is None:
|
||||
if input.dtype in [torch.int8, torch.int16, torch.int32, torch.int64]:
|
||||
dtype = torch.float32
|
||||
else:
|
||||
dtype = input.dtype
|
||||
|
||||
# Convert input to appropriate dtype if needed
|
||||
if input.dtype != dtype:
|
||||
input = input.to(dtype)
|
||||
|
||||
# Get input shape and strides
|
||||
shape = list(input.shape)
|
||||
|
||||
# Calculate dimensions for kernel
|
||||
M = 1
|
||||
for i in range(dim):
|
||||
M *= shape[i]
|
||||
|
||||
N = shape[dim]
|
||||
|
||||
K = 1
|
||||
for i in range(dim + 1, len(shape)):
|
||||
K *= shape[i]
|
||||
|
||||
# Reshape input to 3D view (M, N, K)
|
||||
input_3d = input.reshape(M, N, K)
|
||||
|
||||
# Create output shape
|
||||
if keepdim:
|
||||
output_shape = shape.copy()
|
||||
output_shape[dim] = 1
|
||||
else:
|
||||
output_shape = shape[:dim] + shape[dim + 1 :]
|
||||
|
||||
# Create output tensor
|
||||
output = torch.empty(output_shape, dtype=dtype, device=input.device)
|
||||
|
||||
# Reshape output for kernel
|
||||
if keepdim:
|
||||
output_2d = output.reshape(M, 1, K).squeeze(1)
|
||||
else:
|
||||
output_2d = output.reshape(M, K)
|
||||
|
||||
# Launch kernel
|
||||
grid = (M * K,)
|
||||
BLOCK_SIZE = 1024
|
||||
|
||||
mean_kernel[grid](
|
||||
input_3d,
|
||||
output_2d,
|
||||
input_3d.stride(0),
|
||||
input_3d.stride(1),
|
||||
input_3d.stride(2),
|
||||
output_2d.stride(0),
|
||||
output_2d.stride(1) if output_2d.ndim > 1 else 0,
|
||||
M,
|
||||
N,
|
||||
K,
|
||||
BLOCK_SIZE,
|
||||
)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def mm_batch_invariant(a, b):
|
||||
return matmul_persistent(a, b)
|
||||
|
||||
|
||||
def addmm_batch_invariant(bias, a, b):
|
||||
return matmul_persistent(a, b, bias=bias)
|
||||
|
||||
|
||||
def _log_softmax_batch_invariant(input, dim, _half_to_float):
|
||||
assert not _half_to_float, "not implemented"
|
||||
return log_softmax(input, dim=dim)
|
||||
|
||||
|
||||
def mean_batch_invariant(input, dim, keepdim=False, dtype: torch.dtype | None = None):
|
||||
assert dtype is None or dtype == torch.float32, f"unsupported dtype: {dtype}"
|
||||
if len(dim) == 1:
|
||||
return mean_dim(input, dim[0], keepdim=keepdim)
|
||||
else:
|
||||
assert input.dtype in {
|
||||
torch.float16,
|
||||
torch.bfloat16,
|
||||
torch.float32,
|
||||
}, "only float types supported for now"
|
||||
n_elems = 1
|
||||
for d in dim:
|
||||
n_elems *= input.shape[d]
|
||||
return torch.sum(input, dim=dim, keepdim=keepdim, dtype=torch.float32) / n_elems
|
||||
|
||||
|
||||
@triton.jit
|
||||
def bmm_kernel_persistent(
|
||||
a_ptr,
|
||||
b_ptr,
|
||||
c_ptr, #
|
||||
B,
|
||||
M,
|
||||
N,
|
||||
K, #
|
||||
stride_ab,
|
||||
stride_am,
|
||||
stride_ak,
|
||||
stride_bb,
|
||||
stride_bk,
|
||||
stride_bn,
|
||||
stride_cb,
|
||||
stride_cm,
|
||||
stride_cn,
|
||||
BLOCK_SIZE_M: tl.constexpr, #
|
||||
BLOCK_SIZE_N: tl.constexpr, #
|
||||
BLOCK_SIZE_K: tl.constexpr, #
|
||||
GROUP_SIZE_M: tl.constexpr, #
|
||||
NUM_SMS: tl.constexpr, #
|
||||
A_LARGE: tl.constexpr,
|
||||
B_LARGE: tl.constexpr,
|
||||
C_LARGE: tl.constexpr,
|
||||
):
|
||||
"""
|
||||
Batched matrix multiplication kernel that processes batches in parallel.
|
||||
Each tile processes a (BLOCK_SIZE_M, BLOCK_SIZE_N) output block for a specific batch.
|
||||
"""
|
||||
start_pid = tl.program_id(axis=0)
|
||||
num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
|
||||
num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
|
||||
k_tiles = tl.cdiv(K, BLOCK_SIZE_K)
|
||||
num_tiles_per_batch = num_pid_m * num_pid_n
|
||||
num_tiles_total = B * num_tiles_per_batch
|
||||
|
||||
offs_k_for_mask = tl.arange(0, BLOCK_SIZE_K)
|
||||
num_pid_in_group = GROUP_SIZE_M * num_pid_n
|
||||
|
||||
# Process tiles in a deterministic order: batch-major ordering
|
||||
for tile_id in tl.range(start_pid, num_tiles_total, NUM_SMS, flatten=True):
|
||||
# Decompose tile_id into batch and within-batch tile
|
||||
batch_idx = tile_id // num_tiles_per_batch
|
||||
tile_in_batch = tile_id % num_tiles_per_batch
|
||||
|
||||
pid_m, pid_n = _compute_pid(
|
||||
tile_in_batch, num_pid_in_group, num_pid_m, GROUP_SIZE_M, NUM_SMS
|
||||
)
|
||||
start_m = pid_m * BLOCK_SIZE_M
|
||||
start_n = pid_n * BLOCK_SIZE_N
|
||||
offs_am = start_m + tl.arange(0, BLOCK_SIZE_M)
|
||||
offs_bn = start_n + tl.arange(0, BLOCK_SIZE_N)
|
||||
if A_LARGE:
|
||||
offs_am = offs_am.to(tl.int64)
|
||||
if B_LARGE:
|
||||
offs_bn = offs_bn.to(tl.int64)
|
||||
offs_am = tl.where(offs_am < M, offs_am, 0)
|
||||
offs_bn = tl.where(offs_bn < N, offs_bn, 0)
|
||||
offs_am = tl.max_contiguous(tl.multiple_of(offs_am, BLOCK_SIZE_M), BLOCK_SIZE_M)
|
||||
offs_bn = tl.max_contiguous(tl.multiple_of(offs_bn, BLOCK_SIZE_N), BLOCK_SIZE_N)
|
||||
|
||||
# Add batch offset
|
||||
if A_LARGE or B_LARGE:
|
||||
batch_idx_typed = batch_idx.to(tl.int64)
|
||||
else:
|
||||
batch_idx_typed = batch_idx
|
||||
|
||||
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
||||
for ki in range(k_tiles):
|
||||
if A_LARGE or B_LARGE:
|
||||
offs_k = ki * BLOCK_SIZE_K + tl.arange(0, BLOCK_SIZE_K).to(tl.int64)
|
||||
else:
|
||||
offs_k = ki * BLOCK_SIZE_K + tl.arange(0, BLOCK_SIZE_K)
|
||||
|
||||
a_ptrs = a_ptr + (
|
||||
batch_idx_typed * stride_ab
|
||||
+ offs_am[:, None] * stride_am
|
||||
+ offs_k[None, :] * stride_ak
|
||||
)
|
||||
b_ptrs = b_ptr + (
|
||||
batch_idx_typed * stride_bb
|
||||
+ offs_k[:, None] * stride_bk
|
||||
+ offs_bn[None, :] * stride_bn
|
||||
)
|
||||
|
||||
a = tl.load(
|
||||
a_ptrs, mask=offs_k_for_mask[None, :] < K - ki * BLOCK_SIZE_K, other=0.0
|
||||
)
|
||||
b = tl.load(
|
||||
b_ptrs, mask=offs_k_for_mask[:, None] < K - ki * BLOCK_SIZE_K, other=0.0
|
||||
)
|
||||
accumulator = tl.dot(a, b, accumulator)
|
||||
|
||||
offs_cm = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M)
|
||||
offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
|
||||
if C_LARGE:
|
||||
offs_cm = offs_cm.to(tl.int64)
|
||||
offs_cn = offs_cn.to(tl.int64)
|
||||
c_ptrs = (
|
||||
c_ptr
|
||||
+ batch_idx_typed * stride_cb
|
||||
+ stride_cm * offs_cm[:, None]
|
||||
+ stride_cn * offs_cn[None, :]
|
||||
)
|
||||
c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < N)
|
||||
|
||||
if c_ptr.dtype.element_ty == tl.float8e4nv:
|
||||
c = accumulator.to(tl.float8e4nv)
|
||||
elif c_ptr.dtype.element_ty == tl.bfloat16:
|
||||
c = accumulator.to(tl.bfloat16)
|
||||
elif c_ptr.dtype.element_ty == tl.float32:
|
||||
c = accumulator.to(tl.float32)
|
||||
else:
|
||||
c = accumulator.to(tl.float16)
|
||||
tl.store(c_ptrs, c, mask=c_mask)
|
||||
|
||||
|
||||
def bmm_batch_invariant(a, b, *, out=None):
|
||||
# Batched matrix multiply: (B, M, K) x (B, K, N) -> (B, M, N)
|
||||
# Process batches in parallel with our persistent kernel
|
||||
if a.ndim == 3 and b.ndim == 3:
|
||||
# Check constraints
|
||||
assert a.shape[0] == b.shape[0], "Batch sizes must match"
|
||||
assert a.shape[2] == b.shape[1], "Incompatible dimensions"
|
||||
assert a.dtype == b.dtype, "Incompatible dtypes"
|
||||
|
||||
B = a.shape[0]
|
||||
M = a.shape[1]
|
||||
K = a.shape[2]
|
||||
N = b.shape[2]
|
||||
dtype = a.dtype
|
||||
|
||||
# Allocate output
|
||||
if out is None:
|
||||
c = torch.empty((B, M, N), device=a.device, dtype=dtype)
|
||||
else:
|
||||
c = out
|
||||
|
||||
NUM_SMS = torch.cuda.get_device_properties("cuda").multi_processor_count
|
||||
|
||||
# Use fixed kernel configuration for determinism
|
||||
configs = {
|
||||
torch.bfloat16: {
|
||||
"BLOCK_SIZE_M": 128,
|
||||
"BLOCK_SIZE_N": 128,
|
||||
"BLOCK_SIZE_K": 64,
|
||||
"GROUP_SIZE_M": 8,
|
||||
"num_stages": 3,
|
||||
"num_warps": 8,
|
||||
},
|
||||
torch.float16: {
|
||||
"BLOCK_SIZE_M": 128,
|
||||
"BLOCK_SIZE_N": 256,
|
||||
"BLOCK_SIZE_K": 64,
|
||||
"GROUP_SIZE_M": 8,
|
||||
"num_stages": 3,
|
||||
"num_warps": 8,
|
||||
},
|
||||
torch.float32: {
|
||||
"BLOCK_SIZE_M": 128,
|
||||
"BLOCK_SIZE_N": 128,
|
||||
"BLOCK_SIZE_K": 32,
|
||||
"GROUP_SIZE_M": 8,
|
||||
"num_stages": 3,
|
||||
"num_warps": 8,
|
||||
},
|
||||
}
|
||||
|
||||
config = configs.get(dtype)
|
||||
if config is None:
|
||||
raise ValueError(
|
||||
f"Unsupported dtype {dtype} for bmm_batch_invariant. "
|
||||
f"Supported dtypes are: {list(configs.keys())}"
|
||||
)
|
||||
|
||||
# Grid: limit by NUM_SMS for persistent kernel approach
|
||||
num_tiles_per_batch = triton.cdiv(M, config["BLOCK_SIZE_M"]) * triton.cdiv(
|
||||
N, config["BLOCK_SIZE_N"]
|
||||
)
|
||||
num_tiles_total = B * num_tiles_per_batch
|
||||
grid = (min(NUM_SMS, num_tiles_total),)
|
||||
|
||||
bmm_kernel_persistent[grid](
|
||||
a,
|
||||
b,
|
||||
c, #
|
||||
B,
|
||||
M,
|
||||
N,
|
||||
K, #
|
||||
a.stride(0),
|
||||
a.stride(1),
|
||||
a.stride(2), #
|
||||
b.stride(0),
|
||||
b.stride(1),
|
||||
b.stride(2), #
|
||||
c.stride(0),
|
||||
c.stride(1),
|
||||
c.stride(2), #
|
||||
NUM_SMS=NUM_SMS, #
|
||||
A_LARGE=a.numel() > 2**31,
|
||||
B_LARGE=b.numel() > 2**31,
|
||||
C_LARGE=c.numel() > 2**31,
|
||||
**config,
|
||||
)
|
||||
|
||||
return c
|
||||
else:
|
||||
raise ValueError(
|
||||
f"bmm_batch_invariant expects 3D tensors, "
|
||||
f"got shapes {a.shape} and {b.shape}"
|
||||
)
|
||||
|
||||
|
||||
@triton.jit
|
||||
def _rms_norm_kernel(
|
||||
input_ptr,
|
||||
weight_ptr,
|
||||
output_ptr,
|
||||
input_row_stride: tl.constexpr,
|
||||
output_row_stride: tl.constexpr,
|
||||
n_cols: tl.constexpr,
|
||||
eps,
|
||||
BLOCK_SIZE: tl.constexpr,
|
||||
):
|
||||
"""
|
||||
Compute RMS normalization along the last dimension of a 2D tensor.
|
||||
RMS Norm: y = x / sqrt(mean(x^2) + eps) * weight
|
||||
Each block handles one row of the input tensor.
|
||||
"""
|
||||
row_idx = tl.program_id(0).to(tl.int64)
|
||||
row_start_ptr = input_ptr + row_idx * input_row_stride
|
||||
output_row_start_ptr = output_ptr + row_idx * output_row_stride
|
||||
|
||||
# Step 1: Compute sum of squares in float32 to avoid overflow
|
||||
sum_sq = tl.zeros([1], dtype=tl.float32)
|
||||
for col_offset in range(0, n_cols, BLOCK_SIZE):
|
||||
col_idx = col_offset + tl.arange(0, BLOCK_SIZE)
|
||||
mask = col_idx < n_cols
|
||||
|
||||
vals = tl.load(row_start_ptr + col_idx, mask=mask, other=0.0)
|
||||
# Convert to float32 for accumulation to prevent overflow
|
||||
vals_f32 = vals.to(tl.float32)
|
||||
sq_vals = vals_f32 * vals_f32
|
||||
sum_sq += tl.sum(tl.where(mask, sq_vals, 0.0))
|
||||
|
||||
# Step 2: Compute RMS (root mean square) in float32
|
||||
mean_sq = sum_sq / n_cols
|
||||
rms = tl.sqrt(mean_sq + eps)
|
||||
inv_rms = 1.0 / rms
|
||||
|
||||
# Step 3: Normalize and apply weight
|
||||
for col_offset in range(0, n_cols, BLOCK_SIZE):
|
||||
col_idx = col_offset + tl.arange(0, BLOCK_SIZE)
|
||||
mask = col_idx < n_cols
|
||||
vals = tl.load(row_start_ptr + col_idx, mask=mask, other=0.0)
|
||||
weight = tl.load(weight_ptr + col_idx, mask=mask, other=1.0)
|
||||
# Compute in float32 then convert back to input dtype
|
||||
vals_f32 = vals.to(tl.float32)
|
||||
weight_f32 = weight.to(tl.float32)
|
||||
output_f32 = vals_f32 * inv_rms * weight_f32
|
||||
output = output_f32.to(vals.dtype)
|
||||
tl.store(output_row_start_ptr + col_idx, output, mask=mask)
|
||||
|
||||
|
||||
def rms_norm(
|
||||
input: torch.Tensor, weight: torch.Tensor, eps: float = 1e-6
|
||||
) -> torch.Tensor:
|
||||
"""
|
||||
Compute RMS normalization using Triton kernel.
|
||||
|
||||
RMS Norm normalizes the input by the root mean square and scales by weight:
|
||||
output = input / sqrt(mean(input^2) + eps) * weight
|
||||
|
||||
Args:
|
||||
input: Input tensor of shape (..., hidden_size)
|
||||
weight: Weight tensor of shape (hidden_size,)
|
||||
eps: Small constant for numerical stability
|
||||
|
||||
Returns:
|
||||
Tensor with RMS normalization applied along the last dimension
|
||||
"""
|
||||
assert weight.dim() == 1, "Weight must be 1-dimensional"
|
||||
assert input.shape[-1] == weight.shape[0], (
|
||||
f"Input last dimension ({input.shape[-1]}) must match "
|
||||
f"weight dimension ({weight.shape[0]})"
|
||||
)
|
||||
|
||||
# Flatten all dimensions except the last one
|
||||
original_shape = input.shape
|
||||
input_2d = input.reshape(-1, input.shape[-1])
|
||||
input_2d = input_2d.contiguous()
|
||||
weight = weight.contiguous()
|
||||
|
||||
n_rows, n_cols = input_2d.shape
|
||||
|
||||
output = torch.empty_like(input_2d)
|
||||
BLOCK_SIZE = 1024
|
||||
grid = (n_rows,)
|
||||
_rms_norm_kernel[grid](
|
||||
input_2d,
|
||||
weight,
|
||||
output,
|
||||
input_2d.stride(0),
|
||||
output.stride(0),
|
||||
n_cols,
|
||||
eps,
|
||||
BLOCK_SIZE=BLOCK_SIZE,
|
||||
)
|
||||
return output.reshape(original_shape)
|
||||
|
||||
|
||||
def rms_norm_batch_invariant(
|
||||
input: torch.Tensor, weight: torch.Tensor, eps: float = 1e-6
|
||||
) -> torch.Tensor:
|
||||
"""
|
||||
Batch-invariant wrapper for RMS normalization.
|
||||
|
||||
This function provides a deterministic, batch-invariant implementation
|
||||
of RMS normalization for use with the batch_invariant mode.
|
||||
|
||||
Adapted from @https://github.com/vllm-project/vllm/blob/66a168a197ba214a5b70a74fa2e713c9eeb3251a/vllm/model_executor/layers/batch_invariant.py#L649
|
||||
|
||||
Args:
|
||||
input: Input tensor of shape (..., hidden_size)
|
||||
weight: Weight tensor of shape (hidden_size,)
|
||||
eps: Small constant for numerical stability
|
||||
|
||||
Returns:
|
||||
RMS normalized tensor
|
||||
"""
|
||||
return rms_norm(input, weight, eps=eps)
|
||||
|
||||
|
||||
_batch_invariant_MODE = False
|
||||
_batch_invariant_LIB = None
|
||||
_original_torch_bmm = None
|
||||
|
||||
|
||||
def is_batch_invariant_mode_enabled():
|
||||
return _batch_invariant_MODE
|
||||
|
||||
|
||||
def enable_batch_invariant_mode(
|
||||
enable_bmm: bool = True,
|
||||
):
|
||||
global _batch_invariant_MODE, _batch_invariant_LIB, _original_torch_bmm
|
||||
if _batch_invariant_MODE:
|
||||
return
|
||||
|
||||
_batch_invariant_MODE = True
|
||||
_batch_invariant_LIB = torch.library.Library("aten", "IMPL")
|
||||
_batch_invariant_LIB.impl("aten::mm", mm_batch_invariant, "CUDA")
|
||||
_batch_invariant_LIB.impl("aten::addmm", addmm_batch_invariant, "CUDA")
|
||||
_batch_invariant_LIB.impl(
|
||||
"aten::_log_softmax", _log_softmax_batch_invariant, "CUDA"
|
||||
)
|
||||
_batch_invariant_LIB.impl("aten::mean.dim", mean_batch_invariant, "CUDA")
|
||||
|
||||
if enable_bmm:
|
||||
_batch_invariant_LIB.impl("aten::bmm", bmm_batch_invariant, "CUDA")
|
||||
|
||||
# Also monkeypatch torch.bmm directly as a fallback
|
||||
_original_torch_bmm = torch.bmm
|
||||
torch.bmm = bmm_batch_invariant
|
||||
|
||||
|
||||
def disable_batch_invariant_mode():
|
||||
global _batch_invariant_MODE, _batch_invariant_LIB, _original_torch_bmm
|
||||
if _batch_invariant_LIB is not None:
|
||||
_batch_invariant_LIB._destroy()
|
||||
if _original_torch_bmm is not None:
|
||||
torch.bmm = _original_torch_bmm
|
||||
_original_torch_bmm = None
|
||||
_batch_invariant_MODE = False
|
||||
_batch_invariant_LIB = None
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def set_batch_invariant_mode(enabled: bool = True):
|
||||
global _batch_invariant_MODE, _batch_invariant_LIB
|
||||
old_data = (_batch_invariant_MODE, _batch_invariant_LIB)
|
||||
if enabled:
|
||||
enable_batch_invariant_mode()
|
||||
else:
|
||||
disable_batch_invariant_mode()
|
||||
yield
|
||||
if _batch_invariant_LIB is not None:
|
||||
_batch_invariant_LIB._destroy()
|
||||
_batch_invariant_MODE, _batch_invariant_LIB = old_data
|
||||
|
||||
|
||||
AttentionBlockSize = namedtuple("AttentionBlockSize", ["block_m", "block_n"])
|
||||
|
||||
|
||||
def get_batch_invariant_attention_block_size() -> AttentionBlockSize:
|
||||
return AttentionBlockSize(block_m=16, block_n=16)
|
||||
213
third_party/sglang/python/sglang/srt/batch_overlap/operations.py
vendored
Normal file
213
third_party/sglang/python/sglang/srt/batch_overlap/operations.py
vendored
Normal file
@@ -0,0 +1,213 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from contextlib import contextmanager
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Any, Callable, Dict, Generator, List, Sequence, Union
|
||||
|
||||
import torch
|
||||
|
||||
from sglang.srt.layers.dp_attention import set_dp_buffer_len
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sglang.srt.model_executor.forward_batch_info import ForwardBatch
|
||||
|
||||
_ENABLE_PROFILE = bool(int(os.environ.get("SGLANG_OPERATIONS_ENABLE_PROFILE", "0")))
|
||||
|
||||
if _ENABLE_PROFILE:
|
||||
import nvtx
|
||||
|
||||
|
||||
def execute_operations(inputs, operations):
|
||||
stages = _convert_operations_to_stages(operations)
|
||||
executor = _StageExecutor("primary", stages, inputs=inputs)
|
||||
for _ in range(executor.num_stages):
|
||||
executor.next()
|
||||
assert executor.done
|
||||
return executor.output
|
||||
|
||||
|
||||
def execute_overlapped_operations(
|
||||
inputs_arr: Sequence,
|
||||
operations_arr: Sequence,
|
||||
delta_stages: Sequence[int],
|
||||
) -> Sequence:
|
||||
# Make it explicit for clarity; if we need multi-batch overlap, this can be generalized
|
||||
inputs_a, inputs_b = inputs_arr
|
||||
operations_a, operations_b = operations_arr
|
||||
delta_stage_a, delta_stage_b = delta_stages
|
||||
assert delta_stage_a == 0
|
||||
delta_stage = delta_stage_b
|
||||
|
||||
stages_a = _convert_operations_to_stages(operations_a)
|
||||
stages_b = _convert_operations_to_stages(operations_b)
|
||||
executor_a = _StageExecutor("a", stages_a, inputs=inputs_a)
|
||||
executor_b = _StageExecutor("b", stages_b, inputs=inputs_b)
|
||||
|
||||
for _ in range(delta_stage):
|
||||
executor_a.next()
|
||||
|
||||
for _ in range(executor_a.num_stages - delta_stage):
|
||||
executor_a.next()
|
||||
executor_b.next()
|
||||
|
||||
for _ in range(delta_stage):
|
||||
executor_b.next()
|
||||
|
||||
assert executor_a.done and executor_b.done
|
||||
return [executor_a.output, executor_b.output]
|
||||
|
||||
|
||||
class YieldOperation:
|
||||
pass
|
||||
|
||||
|
||||
@dataclass
|
||||
class ExecutionOperation:
|
||||
debug_name: str
|
||||
fn: Callable
|
||||
|
||||
|
||||
Operation = Union[YieldOperation, ExecutionOperation, Callable]
|
||||
Stage = List[ExecutionOperation]
|
||||
|
||||
|
||||
class _StageExecutor:
|
||||
def __init__(self, debug_name: str, stages: List[Stage], inputs: dict):
|
||||
self._debug_name = debug_name
|
||||
self._stages = stages
|
||||
self._index = 0
|
||||
self._stage_state = _StateDict()
|
||||
self._stage_output = inputs
|
||||
|
||||
# handling DP attention
|
||||
forward_batch: ForwardBatch = inputs["forward_batch"]
|
||||
self._global_dp_buffer_len = forward_batch.global_dp_buffer_len
|
||||
self._local_dp_buffer_len = forward_batch.tbo_padded_len
|
||||
self._global_num_tokens = forward_batch.global_num_tokens_cpu
|
||||
self._is_dp_max_padding = forward_batch.dp_padding_mode.is_max_len()
|
||||
|
||||
def next(self):
|
||||
assert not self.done
|
||||
|
||||
stage = self._stages[self._index]
|
||||
|
||||
# TODO: We currently always call set_dp_buffer_len here because sub-batches
|
||||
# may have different padded lengths. It can likely be removed after TBO slice &
|
||||
# pad logic is refactored.
|
||||
set_dp_buffer_len(
|
||||
self._global_dp_buffer_len,
|
||||
self._local_dp_buffer_len,
|
||||
self._is_dp_max_padding,
|
||||
self._global_num_tokens,
|
||||
)
|
||||
|
||||
with _annotate_region(debug_name=f"{self._debug_name}{self._index}"):
|
||||
for op in stage:
|
||||
with _annotate_region(debug_name=op.debug_name):
|
||||
self._stage_output = op.fn(
|
||||
state=self._stage_state,
|
||||
**(
|
||||
self._stage_output if self._stage_output is not None else {}
|
||||
),
|
||||
)
|
||||
|
||||
self._index += 1
|
||||
|
||||
@property
|
||||
def output(self):
|
||||
assert self.done
|
||||
return self._stage_output
|
||||
|
||||
@property
|
||||
def done(self):
|
||||
return self._index >= self.num_stages
|
||||
|
||||
@property
|
||||
def num_stages(self):
|
||||
return len(self._stages)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def _annotate_region(debug_name):
|
||||
if _ENABLE_PROFILE:
|
||||
with torch.autograd.profiler.record_function(debug_name):
|
||||
with nvtx.annotate(debug_name):
|
||||
yield
|
||||
else:
|
||||
yield
|
||||
|
||||
|
||||
class _StateDict:
|
||||
def __init__(self):
|
||||
self._data = {}
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
if key == "_data":
|
||||
super().__setattr__(key, value)
|
||||
return
|
||||
assert (
|
||||
key not in self._data
|
||||
), f"`{key}` already exist, are you sure you want to override it?"
|
||||
self._data[key] = value
|
||||
|
||||
def __getattr__(self, item):
|
||||
return self._data[item]
|
||||
|
||||
def __delattr__(self, item):
|
||||
del self._data[item]
|
||||
|
||||
def pop(self, item):
|
||||
return self._data.pop(item)
|
||||
|
||||
def update(self, values: Dict[str, Any]):
|
||||
for k, v in values.items():
|
||||
setattr(self, k, v)
|
||||
|
||||
def get(self, item):
|
||||
return self._data.get(item)
|
||||
|
||||
def clear(self, expect_keys: Sequence[str]):
|
||||
if set(self._data.keys()) != set(expect_keys):
|
||||
raise Exception(
|
||||
f"Unexpected keys when clearing. This may indicate you do not release memory early enough but leave it until here. {list(self._data.keys())=} {expect_keys=}"
|
||||
)
|
||||
|
||||
self._data.clear()
|
||||
|
||||
|
||||
def _convert_operations_to_stages(operations: List[Operation]) -> List[Stage]:
|
||||
operations = _decorate_operations(operations)
|
||||
operation_chunks = list(
|
||||
_chunk_by_separator(operations, lambda op: isinstance(op, YieldOperation))
|
||||
)
|
||||
assert all(len(chunk) > 0 for chunk in operation_chunks)
|
||||
return operation_chunks
|
||||
|
||||
|
||||
def _chunk_by_separator(
|
||||
items: List[Any], is_separator: Callable[[Any], bool]
|
||||
) -> Generator[List[Any], None, None]:
|
||||
pending_items = []
|
||||
for item in items:
|
||||
if is_separator(item):
|
||||
yield pending_items
|
||||
pending_items = []
|
||||
else:
|
||||
pending_items.append(item)
|
||||
if len(pending_items) > 0:
|
||||
yield pending_items
|
||||
|
||||
|
||||
def _decorate_operations(operations: List[Operation], debug_name_prefix: str = ""):
|
||||
return [_decorate_operation(op, debug_name_prefix) for op in operations]
|
||||
|
||||
|
||||
def _decorate_operation(operation: Operation, debug_name_prefix: str):
|
||||
if isinstance(operation, YieldOperation):
|
||||
return operation
|
||||
return ExecutionOperation(
|
||||
debug_name=debug_name_prefix
|
||||
+ getattr(operation, "__name__", "unknown").replace("op_", ""),
|
||||
fn=operation,
|
||||
)
|
||||
302
third_party/sglang/python/sglang/srt/batch_overlap/operations_strategy.py
vendored
Normal file
302
third_party/sglang/python/sglang/srt/batch_overlap/operations_strategy.py
vendored
Normal file
@@ -0,0 +1,302 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Optional
|
||||
|
||||
import torch
|
||||
|
||||
from sglang.srt.batch_overlap import operations
|
||||
from sglang.srt.batch_overlap.operations import Operation
|
||||
from sglang.srt.layers.moe.token_dispatcher import DeepEPConfig
|
||||
from sglang.srt.model_executor.forward_batch_info import ForwardMode
|
||||
from sglang.srt.utils import is_hip
|
||||
|
||||
_is_hip = is_hip()
|
||||
|
||||
|
||||
@dataclass
|
||||
class OperationsStrategy:
|
||||
operations: List[Operation]
|
||||
deep_gemm_num_sms: Optional[int] = None
|
||||
tbo_delta_stages: Optional[int] = None
|
||||
|
||||
@classmethod
|
||||
def concat(cls, items: List["OperationsStrategy"]) -> "OperationsStrategy":
|
||||
return OperationsStrategy(
|
||||
operations=[x for item in items for x in item.operations],
|
||||
deep_gemm_num_sms=_assert_all_same(
|
||||
[item.deep_gemm_num_sms for item in items]
|
||||
),
|
||||
tbo_delta_stages=_assert_all_same(
|
||||
[item.tbo_delta_stages for item in items]
|
||||
),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def init_new_tbo(
|
||||
layers: torch.nn.ModuleList,
|
||||
forward_mode: ForwardMode,
|
||||
) -> "OperationsStrategy":
|
||||
layer_name = layers[0].__class__.__name__
|
||||
if layer_name == "DeepseekV2DecoderLayer":
|
||||
return OperationsStrategy.concat(
|
||||
[
|
||||
_compute_moe_deepseek_layer_operations_strategy_tbo(
|
||||
layer, forward_mode
|
||||
)
|
||||
for layer in layers
|
||||
]
|
||||
)
|
||||
elif layer_name == "Qwen3MoeDecoderLayer":
|
||||
return OperationsStrategy.concat(
|
||||
[
|
||||
_compute_moe_qwen3_layer_operations_strategy_tbo(
|
||||
layer, forward_mode
|
||||
)
|
||||
for layer in layers
|
||||
]
|
||||
)
|
||||
elif layer_name == "MiMoV2DecoderLayer":
|
||||
return OperationsStrategy.concat(
|
||||
[
|
||||
_compute_moe_mimov2_layer_operations_strategy_tbo(
|
||||
layer, forward_mode
|
||||
)
|
||||
for layer in layers
|
||||
]
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def _assert_all_same(items: List):
|
||||
assert all(item == items[0] for item in items)
|
||||
return items[0]
|
||||
|
||||
|
||||
# -------------------------------- Strategy for DeepSeek ---------------------------------------
|
||||
|
||||
|
||||
# TODO can refactor to make it more fancy if we have more complex strategies
|
||||
def _compute_moe_deepseek_layer_operations_strategy_tbo(
|
||||
layer: torch.nn.Module,
|
||||
forward_mode: ForwardMode,
|
||||
) -> OperationsStrategy:
|
||||
assert layer.is_layer_sparse, "dense layer TBO not yet implemented"
|
||||
if forward_mode == ForwardMode.EXTEND:
|
||||
return _compute_moe_deepseek_blog_prefill(layer)
|
||||
elif (
|
||||
forward_mode == ForwardMode.DECODE or forward_mode == ForwardMode.TARGET_VERIFY
|
||||
):
|
||||
return _compute_moe_deepseek_blog_decode(layer)
|
||||
else:
|
||||
raise NotImplementedError(f"Unsupported {forward_mode=}")
|
||||
|
||||
|
||||
def _compute_moe_deepseek_blog_prefill(layer):
|
||||
device_properties = torch.cuda.get_device_properties(device="cuda")
|
||||
total_num_sms = device_properties.multi_processor_count
|
||||
deep_gemm_num_sms = None
|
||||
if not _is_hip:
|
||||
deep_gemm_num_sms = total_num_sms - DeepEPConfig.get_instance().num_sms
|
||||
|
||||
return OperationsStrategy(
|
||||
deep_gemm_num_sms=deep_gemm_num_sms,
|
||||
tbo_delta_stages=0,
|
||||
operations=[
|
||||
layer.op_comm_prepare_attn,
|
||||
layer.self_attn.op_prepare,
|
||||
layer.self_attn.op_core,
|
||||
layer.op_comm_prepare_mlp,
|
||||
layer.mlp.op_gate,
|
||||
layer.mlp.op_select_experts,
|
||||
layer.mlp.op_dispatch_a,
|
||||
operations.YieldOperation(),
|
||||
layer.mlp.op_dispatch_b,
|
||||
layer.mlp.op_experts,
|
||||
layer.mlp.op_combine_a,
|
||||
operations.YieldOperation(),
|
||||
layer.mlp.op_shared_experts,
|
||||
layer.mlp.op_combine_b,
|
||||
layer.mlp.op_output,
|
||||
layer.op_comm_postprocess_layer,
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def _compute_moe_deepseek_blog_decode(layer):
|
||||
return OperationsStrategy(
|
||||
deep_gemm_num_sms=None,
|
||||
tbo_delta_stages=2,
|
||||
operations=[
|
||||
layer.op_comm_prepare_attn,
|
||||
layer.self_attn.op_prepare,
|
||||
operations.YieldOperation(),
|
||||
layer.self_attn.op_core,
|
||||
layer.op_comm_prepare_mlp,
|
||||
layer.mlp.op_gate,
|
||||
layer.mlp.op_select_experts,
|
||||
operations.YieldOperation(),
|
||||
layer.mlp.op_dispatch_a,
|
||||
layer.mlp.op_shared_experts,
|
||||
operations.YieldOperation(),
|
||||
layer.mlp.op_dispatch_b,
|
||||
layer.mlp.op_experts,
|
||||
layer.mlp.op_combine_a,
|
||||
operations.YieldOperation(),
|
||||
layer.mlp.op_combine_b,
|
||||
operations.YieldOperation(),
|
||||
layer.mlp.op_output,
|
||||
layer.op_comm_postprocess_layer,
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
# -------------------------------- Strategy for Qwen3 ---------------------------------------
|
||||
|
||||
|
||||
# TODO: unstable, current strategy is almost the same as DeepSeek, keep redundant code here for
|
||||
# convenience to adjust strategy
|
||||
def _compute_moe_qwen3_layer_operations_strategy_tbo(
|
||||
layer: torch.nn.Module,
|
||||
forward_mode: ForwardMode,
|
||||
) -> OperationsStrategy:
|
||||
assert layer.is_layer_sparse, "qwen3 moe only support sparse layers"
|
||||
if forward_mode == ForwardMode.EXTEND:
|
||||
return _compute_moe_qwen3_prefill(layer)
|
||||
elif (
|
||||
forward_mode == ForwardMode.DECODE or forward_mode == ForwardMode.TARGET_VERIFY
|
||||
):
|
||||
return _compute_moe_qwen3_decode(layer)
|
||||
else:
|
||||
raise NotImplementedError(f"Unsupported {forward_mode=}")
|
||||
|
||||
|
||||
def _compute_moe_qwen3_prefill(layer):
|
||||
device_properties = torch.cuda.get_device_properties(device="cuda")
|
||||
total_num_sms = device_properties.multi_processor_count
|
||||
deep_gemm_num_sms = None
|
||||
if not _is_hip:
|
||||
deep_gemm_num_sms = total_num_sms - DeepEPConfig.get_instance().num_sms
|
||||
|
||||
return OperationsStrategy(
|
||||
deep_gemm_num_sms=deep_gemm_num_sms,
|
||||
tbo_delta_stages=0,
|
||||
operations=[
|
||||
layer.op_comm_prepare_attn,
|
||||
layer.self_attn.op_prepare,
|
||||
layer.self_attn.op_core,
|
||||
layer.op_comm_prepare_mlp,
|
||||
layer.mlp.op_gate,
|
||||
layer.mlp.op_select_experts,
|
||||
layer.mlp.op_dispatch_a,
|
||||
operations.YieldOperation(),
|
||||
layer.mlp.op_dispatch_b,
|
||||
layer.mlp.op_experts,
|
||||
layer.mlp.op_combine_a,
|
||||
operations.YieldOperation(),
|
||||
layer.mlp.op_combine_b,
|
||||
layer.mlp.op_output,
|
||||
layer.op_comm_postprocess_layer,
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def _compute_moe_qwen3_decode(layer):
|
||||
return OperationsStrategy(
|
||||
deep_gemm_num_sms=None,
|
||||
tbo_delta_stages=2,
|
||||
operations=[
|
||||
layer.op_comm_prepare_attn,
|
||||
layer.self_attn.op_prepare,
|
||||
operations.YieldOperation(),
|
||||
layer.self_attn.op_core,
|
||||
layer.op_comm_prepare_mlp,
|
||||
layer.mlp.op_gate,
|
||||
layer.mlp.op_select_experts,
|
||||
operations.YieldOperation(),
|
||||
layer.mlp.op_dispatch_a,
|
||||
operations.YieldOperation(),
|
||||
layer.mlp.op_dispatch_b,
|
||||
layer.mlp.op_experts,
|
||||
layer.mlp.op_combine_a,
|
||||
operations.YieldOperation(),
|
||||
layer.mlp.op_combine_b,
|
||||
layer.mlp.op_output,
|
||||
layer.op_comm_postprocess_layer,
|
||||
operations.YieldOperation(),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
# -------------------------------- Strategy for MiMoV2DecoderLayer ---------------------------------------
|
||||
|
||||
|
||||
# TODO: unstable; current strategy matches DeepSeek for the common operations (MiMoV2 has no op_shared_experts),
|
||||
# so we keep this redundant code here for convenience when adjusting the strategy
|
||||
def _compute_moe_mimov2_layer_operations_strategy_tbo(
|
||||
layer: torch.nn.Module,
|
||||
forward_mode: ForwardMode,
|
||||
) -> OperationsStrategy:
|
||||
assert layer.is_layer_sparse, "MiMoV2DecoderLayer moe only support sparse layers"
|
||||
if forward_mode == ForwardMode.EXTEND:
|
||||
return _compute_moe_mimov2_prefill(layer)
|
||||
elif (
|
||||
forward_mode == ForwardMode.DECODE or forward_mode == ForwardMode.TARGET_VERIFY
|
||||
):
|
||||
return _compute_moe_mimov2_decode(layer)
|
||||
else:
|
||||
raise NotImplementedError(f"Unsupported {forward_mode=}")
|
||||
|
||||
|
||||
def _compute_moe_mimov2_prefill(layer):
|
||||
device_properties = torch.cuda.get_device_properties(device="cuda")
|
||||
total_num_sms = device_properties.multi_processor_count
|
||||
deep_gemm_num_sms = total_num_sms - DeepEPConfig.get_instance().num_sms
|
||||
|
||||
return OperationsStrategy(
|
||||
deep_gemm_num_sms=deep_gemm_num_sms,
|
||||
tbo_delta_stages=0,
|
||||
operations=[
|
||||
layer.op_comm_prepare_attn,
|
||||
layer.self_attn.op_prepare,
|
||||
layer.self_attn.op_core,
|
||||
layer.op_comm_prepare_mlp,
|
||||
layer.mlp.op_gate,
|
||||
layer.mlp.op_select_experts,
|
||||
layer.mlp.op_dispatch_a,
|
||||
operations.YieldOperation(),
|
||||
layer.mlp.op_dispatch_b,
|
||||
layer.mlp.op_experts,
|
||||
layer.mlp.op_combine_a,
|
||||
operations.YieldOperation(),
|
||||
layer.mlp.op_combine_b,
|
||||
layer.mlp.op_output,
|
||||
layer.op_comm_postprocess_layer,
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def _compute_moe_mimov2_decode(layer):
|
||||
return OperationsStrategy(
|
||||
deep_gemm_num_sms=None,
|
||||
tbo_delta_stages=2,
|
||||
operations=[
|
||||
layer.op_comm_prepare_attn,
|
||||
layer.self_attn.op_prepare,
|
||||
operations.YieldOperation(),
|
||||
layer.self_attn.op_core,
|
||||
layer.op_comm_prepare_mlp,
|
||||
layer.mlp.op_gate,
|
||||
layer.mlp.op_select_experts,
|
||||
operations.YieldOperation(),
|
||||
layer.mlp.op_dispatch_a,
|
||||
operations.YieldOperation(),
|
||||
layer.mlp.op_dispatch_b,
|
||||
layer.mlp.op_experts,
|
||||
layer.mlp.op_combine_a,
|
||||
operations.YieldOperation(),
|
||||
layer.mlp.op_combine_b,
|
||||
layer.mlp.op_output,
|
||||
layer.op_comm_postprocess_layer,
|
||||
operations.YieldOperation(),
|
||||
],
|
||||
)
|
||||
144
third_party/sglang/python/sglang/srt/batch_overlap/single_batch_overlap.py
vendored
Normal file
144
third_party/sglang/python/sglang/srt/batch_overlap/single_batch_overlap.py
vendored
Normal file
@@ -0,0 +1,144 @@
|
||||
# Copyright 2025 SGLang Team
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ==============================================================================
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
import torch
|
||||
|
||||
from sglang.srt.environ import envs
|
||||
from sglang.srt.layers.moe import get_moe_runner_backend
|
||||
from sglang.srt.layers.moe.utils import is_sbo_enabled
|
||||
from sglang.srt.utils import is_blackwell
|
||||
|
||||
|
||||
class SboFlags:
|
||||
# TODO may have: "enable_dispatch_gateup_gemm_two_stream_overlap", ...
|
||||
|
||||
@classmethod
|
||||
def enable_combine_down_gemm_two_stream_overlap(cls):
|
||||
return (
|
||||
is_sbo_enabled()
|
||||
# currently only cutedsl backend supports it
|
||||
and (
|
||||
get_moe_runner_backend().is_flashinfer_cutedsl()
|
||||
or (get_moe_runner_backend().is_deep_gemm() and not is_blackwell())
|
||||
)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def enable_combine_shared_two_stream_overlap(cls):
|
||||
return (
|
||||
is_sbo_enabled()
|
||||
and not cls.enable_dispatch_shared_one_stream_overlap()
|
||||
and not envs.SGLANG_BLACKWELL_OVERLAP_SHARED_EXPERTS_OUTSIDE_SBO.get()
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def enable_dispatch_shared_one_stream_overlap(cls):
|
||||
return is_sbo_enabled() and not is_blackwell()
|
||||
|
||||
@classmethod
|
||||
def fuse_shared_experts_inside_sbo(cls):
|
||||
return (
|
||||
cls.enable_combine_shared_two_stream_overlap()
|
||||
or cls.enable_dispatch_shared_one_stream_overlap()
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class CombineOverlapArgs:
|
||||
# this "overlap" flag means overlapping with down gemm, not the general two-stream overlap
|
||||
overlap: bool
|
||||
stream: torch.cuda.Stream
|
||||
wait_event: torch.cuda.Event
|
||||
num_sms: Optional[int] = None
|
||||
signal: Optional[torch.Tensor] = None
|
||||
block_m: Optional[int] = 64
|
||||
threshold: Optional[int] = 0
|
||||
|
||||
|
||||
@dataclass
|
||||
class DownGemmOverlapArgs:
|
||||
num_sms: int
|
||||
signal: torch.Tensor
|
||||
start_event: torch.cuda.Event
|
||||
|
||||
|
||||
def compute_overlap_args(dispatch_output, alt_stream):
|
||||
if not (
|
||||
SboFlags.enable_combine_down_gemm_two_stream_overlap()
|
||||
or SboFlags.enable_combine_shared_two_stream_overlap()
|
||||
):
|
||||
return None, None, {}
|
||||
|
||||
hidden_states = dispatch_output.hidden_states
|
||||
|
||||
num_local_experts, num_tokens_static, hidden_dim = hidden_states.shape
|
||||
|
||||
total_num_sms = torch.cuda.get_device_properties(
|
||||
device="cuda"
|
||||
).multi_processor_count
|
||||
|
||||
if envs.SGLANG_DEEPEP_LL_COMBINE_SEND_NUM_SMS.is_set():
|
||||
communicate_num_sms = envs.SGLANG_DEEPEP_LL_COMBINE_SEND_NUM_SMS.get()
|
||||
else:
|
||||
communicate_num_sms = 32 if is_blackwell() else 3
|
||||
compute_num_sms = total_num_sms - communicate_num_sms
|
||||
|
||||
assert alt_stream is not None
|
||||
combine_wait_event = torch.cuda.Event()
|
||||
combine_overlap_args = CombineOverlapArgs(
|
||||
overlap=False,
|
||||
num_sms=communicate_num_sms,
|
||||
stream=alt_stream,
|
||||
wait_event=combine_wait_event,
|
||||
)
|
||||
meta_overlap_args = dict(
|
||||
compute_num_sms=compute_num_sms,
|
||||
)
|
||||
down_gemm_overlap_args = None
|
||||
|
||||
if SboFlags.enable_combine_down_gemm_two_stream_overlap():
|
||||
# TODO use zero_allocator to remove this `torch.zeros` call
|
||||
# NOTE ours v2 use uint32 not int32 currently
|
||||
if is_blackwell():
|
||||
combine_signal = torch.zeros(
|
||||
num_local_experts, dtype=torch.uint32, device=hidden_states.device
|
||||
)
|
||||
else:
|
||||
MIN_BLOCK_M = 64
|
||||
combine_signal_size = num_local_experts * (
|
||||
(num_tokens_static + MIN_BLOCK_M - 1) // MIN_BLOCK_M
|
||||
)
|
||||
combine_signal = torch.zeros(
|
||||
combine_signal_size, dtype=torch.int32, device=hidden_states.device
|
||||
)
|
||||
|
||||
down_gemm_overlap_args = DownGemmOverlapArgs(
|
||||
signal=combine_signal,
|
||||
start_event=combine_wait_event,
|
||||
num_sms=compute_num_sms,
|
||||
)
|
||||
combine_overlap_args.overlap = True
|
||||
combine_overlap_args.signal = combine_signal
|
||||
combine_overlap_args.threshold = compute_num_sms
|
||||
else:
|
||||
meta_overlap_args |= dict(
|
||||
record_event_after_down=combine_wait_event,
|
||||
)
|
||||
|
||||
return combine_overlap_args, down_gemm_overlap_args, meta_overlap_args
|
||||
1088
third_party/sglang/python/sglang/srt/batch_overlap/two_batch_overlap.py
vendored
Normal file
1088
third_party/sglang/python/sglang/srt/batch_overlap/two_batch_overlap.py
vendored
Normal file
File diff suppressed because it is too large
Load Diff
9
third_party/sglang/python/sglang/srt/checkpoint_engine/__init__.py
vendored
Normal file
9
third_party/sglang/python/sglang/srt/checkpoint_engine/__init__.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
"""
|
||||
Checkpoint engine module for SGLang.
|
||||
|
||||
This module provides functionality for updating model weights via checkpoint engine.
|
||||
"""
|
||||
|
||||
from sglang.srt.checkpoint_engine.update import main
|
||||
|
||||
__all__ = ["main"]
|
||||
143
third_party/sglang/python/sglang/srt/checkpoint_engine/checkpoint_engine_worker.py
vendored
Normal file
143
third_party/sglang/python/sglang/srt/checkpoint_engine/checkpoint_engine_worker.py
vendored
Normal file
@@ -0,0 +1,143 @@
|
||||
# Copyright 2023-2024 SGLang Team
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ==============================================================================
|
||||
"""
|
||||
Checkpoint-engine integration for SGLang.
|
||||
This module provides weight update functionality via IPC for checkpoint-engine compatibility.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Callable, Dict, Optional
|
||||
|
||||
import torch
|
||||
import zmq
|
||||
|
||||
try:
|
||||
from checkpoint_engine.worker import update_weights_from_ipc
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"checkpoint-engine is not installed. "
|
||||
"Please install it with: pip install sglang[checkpoint-engine]"
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SGLangCheckpointEngineWorkerExtension:
|
||||
"""
|
||||
Worker extension for SGLang to support checkpoint-engine IPC weight updates.
|
||||
This class provides the interface needed for checkpoint-engine integration.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._zmq_ctx: Optional[zmq.Context] = None
|
||||
|
||||
def get_device_uuid(self) -> str:
|
||||
"""Get the UUID of current device."""
|
||||
# We need to implement this to get the device UUID
|
||||
# This will be overridden when integrated into SGLang's worker
|
||||
raise NotImplementedError(
|
||||
"This method should be overridden by SGLang integration"
|
||||
)
|
||||
|
||||
def get_device_id(self) -> int:
|
||||
"""Get the device ID."""
|
||||
raise NotImplementedError(
|
||||
"This method should be overridden by SGLang integration"
|
||||
)
|
||||
|
||||
def get_model_loader(self) -> Callable:
|
||||
"""Get the model weight loader function."""
|
||||
raise NotImplementedError(
|
||||
"This method should be overridden by SGLang integration"
|
||||
)
|
||||
|
||||
def get_post_hook(self) -> Optional[Callable]:
|
||||
"""Get the post-processing hook after weight loading."""
|
||||
return None
|
||||
|
||||
def update_weights_from_ipc(self, zmq_handles: Dict[str, str]):
|
||||
"""
|
||||
Update weights from IPC communication.
|
||||
Args:
|
||||
zmq_handles: Dict mapping device UUID to ZMQ socket path
|
||||
"""
|
||||
if self._zmq_ctx is None:
|
||||
self._zmq_ctx = zmq.Context()
|
||||
device_uuid = self.get_device_uuid()
|
||||
device_id = self.get_device_id()
|
||||
if device_uuid not in zmq_handles:
|
||||
raise ValueError(
|
||||
f"Device UUID {device_uuid} not found in zmq_handles: {list(zmq_handles.keys())}"
|
||||
)
|
||||
update_weights_from_ipc(
|
||||
self._zmq_ctx,
|
||||
zmq_handles[device_uuid],
|
||||
device_id=device_id,
|
||||
run=self.get_model_loader(),
|
||||
post_hook=self.get_post_hook(),
|
||||
)
|
||||
|
||||
|
||||
class SGLangCheckpointEngineWorkerExtensionImpl(SGLangCheckpointEngineWorkerExtension):
|
||||
"""
|
||||
Implementation of SGLangCheckpointEngineWorkerExtension that integrates with SGLang's model runner.
|
||||
This class provides the concrete implementation for checkpoint-engine IPC weight updates.
|
||||
"""
|
||||
|
||||
def __init__(self, model_runner):
|
||||
super().__init__()
|
||||
self.model_runner = model_runner
|
||||
|
||||
def get_device_uuid(self) -> str:
|
||||
"""Get the UUID of current device."""
|
||||
# Get device UUID for current device
|
||||
device_id = torch.cuda.current_device()
|
||||
try:
|
||||
return f"GPU-{torch.cuda.get_device_properties(device_id).uuid!s}"
|
||||
except AssertionError as e:
|
||||
raise ValueError(f"Failed to get GPU UUID for device {device_id}") from e
|
||||
|
||||
def get_device_id(self) -> int:
|
||||
"""Get the device ID."""
|
||||
return torch.cuda.current_device()
|
||||
|
||||
def get_model_loader(self) -> Callable:
|
||||
"""Get the model weight loader function."""
|
||||
return self.model_runner.model.load_weights
|
||||
|
||||
def get_post_hook(self) -> Optional[Callable]:
|
||||
"""Get the post-processing hook after weight loading."""
|
||||
|
||||
def post_hook():
|
||||
# Perform post-processing after weight loading similar to DefaultModelLoader
|
||||
try:
|
||||
from sglang.srt.model_loader.loader import device_loading_context
|
||||
|
||||
# Process quantization methods after loading weights
|
||||
for _, module in self.model_runner.model.named_modules():
|
||||
quant_method = getattr(module, "quant_method", None)
|
||||
if quant_method is not None:
|
||||
# Move parameters to device if needed for quantization processing
|
||||
target_device = torch.device(
|
||||
"cuda", torch.cuda.current_device()
|
||||
)
|
||||
with device_loading_context(module, target_device):
|
||||
quant_method.process_weights_after_loading(module)
|
||||
# Call model-specific post-loading hook if available
|
||||
if hasattr(self.model_runner.model, "post_load_weights"):
|
||||
self.model_runner.model.post_load_weights()
|
||||
except Exception as e:
|
||||
logger.warning(f"Post-hook processing failed: {e}")
|
||||
|
||||
return post_hook
|
||||
317
third_party/sglang/python/sglang/srt/checkpoint_engine/update.py
vendored
Normal file
317
third_party/sglang/python/sglang/srt/checkpoint_engine/update.py
vendored
Normal file
@@ -0,0 +1,317 @@
|
||||
"""
|
||||
Usage:
|
||||
1) Launch the server with wait-for-initial-weights option in one terminal:
|
||||
python -m sglang.launch_server --model-path /workspace/Qwen/Qwen3-4B/ --tensor-parallel-size 2 --port 19730 --load-format dummy --checkpoint-engine-wait-weights-before-ready --mem-fraction-static 0.7
|
||||
|
||||
2) Torchrun this script in another terminal:
|
||||
torchrun --nproc-per-node 2 update.py --update-method broadcast --checkpoint-path /workspace/Qwen/Qwen3-4B/ --inference-parallel-size 2
|
||||
|
||||
Or use the integrated entry point:
|
||||
python -m sglang.srt.checkpoint_engine.update --update-method broadcast --checkpoint-path /workspace/Qwen/Qwen3-4B/ --inference-parallel-size 2
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import pickle
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from collections import defaultdict
|
||||
from collections.abc import Callable
|
||||
from contextlib import contextmanager
|
||||
from typing import Literal
|
||||
|
||||
import httpx
|
||||
import torch
|
||||
import torch.distributed as dist
|
||||
from safetensors import safe_open
|
||||
|
||||
try:
|
||||
from checkpoint_engine.ps import ParameterServer
|
||||
from loguru import logger
|
||||
except ImportError:
|
||||
# Fallback for when checkpoint_engine is not available
|
||||
ParameterServer = None
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def timer(msg: str):
|
||||
start = time.perf_counter()
|
||||
yield
|
||||
end = time.perf_counter()
|
||||
logger.info(f"{msg} duration: {end - start:.2f} seconds")
|
||||
|
||||
|
||||
def check_sglang_ready(
|
||||
endpoint: str, inference_parallel_size: int, uds: str | None = None
|
||||
):
|
||||
rank = int(os.getenv("RANK", 0))
|
||||
if rank != rank // inference_parallel_size * inference_parallel_size:
|
||||
return
|
||||
retry_num = 0
|
||||
transport = None
|
||||
if uds is not None:
|
||||
transport = httpx.HTTPTransport(uds=uds)
|
||||
with httpx.Client(transport=transport) as client:
|
||||
while True:
|
||||
try:
|
||||
response = client.get(f"{endpoint}/ping", timeout=10)
|
||||
response.raise_for_status()
|
||||
break
|
||||
except (httpx.ConnectError, httpx.HTTPStatusError) as e:
|
||||
if retry_num % 10 == 0:
|
||||
logger.warning(
|
||||
f"fail to check sglang ready, retry {retry_num} times, error: {e}"
|
||||
)
|
||||
retry_num += 1
|
||||
time.sleep(0.1)
|
||||
|
||||
|
||||
def split_checkpoint_files(
|
||||
checkpoint_path: str, rank: int, world_size: int
|
||||
) -> list[str]:
|
||||
checkpoint_files = [
|
||||
os.path.join(checkpoint_path, f)
|
||||
for f in filter(
|
||||
lambda x: x.endswith(".safetensors"), os.listdir(checkpoint_path)
|
||||
)
|
||||
]
|
||||
files_per_rank = (len(checkpoint_files) + world_size - 1) // world_size
|
||||
return checkpoint_files[rank * files_per_rank : (rank + 1) * files_per_rank]
|
||||
|
||||
|
||||
def split_tensors(
|
||||
checkpoint_path: str, rank: int, world_size: int
|
||||
) -> dict[str, torch.Tensor]:
|
||||
index_fn = os.path.join(checkpoint_path, "model.safetensors.index.json")
|
||||
with open(index_fn) as f:
|
||||
weight_map: dict[str, str] = json.load(f)["weight_map"]
|
||||
weights_per_rank = (len(weight_map) + world_size - 1) // world_size
|
||||
fn_tensors: dict[str, list[str]] = defaultdict(list)
|
||||
weight_keys = list(weight_map.items())
|
||||
for name, file in weight_keys[
|
||||
rank * weights_per_rank : (rank + 1) * weights_per_rank
|
||||
]:
|
||||
fn_tensors[file].append(name)
|
||||
named_tensors = {}
|
||||
for file, names in fn_tensors.items():
|
||||
with safe_open(os.path.join(checkpoint_path, file), framework="pt") as f:
|
||||
for name in names:
|
||||
named_tensors[name] = f.get_tensor(name)
|
||||
return named_tensors
|
||||
|
||||
|
||||
def req_inference(
|
||||
endpoint: str,
|
||||
inference_parallel_size: int,
|
||||
timeout: float = 300.0,
|
||||
uds: str | None = None,
|
||||
weight_version: str | None = None,
|
||||
) -> Callable[[list[tuple[str, str]]], None]:
|
||||
rank = int(os.getenv("RANK", 0))
|
||||
src = rank // inference_parallel_size * inference_parallel_size
|
||||
|
||||
def req_func(socket_paths: list[tuple[str, str]]):
|
||||
if rank == src:
|
||||
with httpx.Client(transport=httpx.HTTPTransport(uds=uds)) as client:
|
||||
resp = client.post(
|
||||
f"{endpoint}/update_weights_from_ipc",
|
||||
json={
|
||||
"zmq_handles": dict(
|
||||
socket_paths[src : src + inference_parallel_size]
|
||||
),
|
||||
"flush_cache": True,
|
||||
"weight_version": weight_version,
|
||||
},
|
||||
timeout=timeout,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
|
||||
return req_func
|
||||
|
||||
|
||||
def update_weights(
|
||||
ps,
|
||||
checkpoint_name: str,
|
||||
checkpoint_files: list[str],
|
||||
named_tensors: dict[str, torch.Tensor],
|
||||
req_func: Callable[[list[tuple[str, str]]], None],
|
||||
inference_parallel_size: int,
|
||||
endpoint: str,
|
||||
save_metas_file: str | None = None,
|
||||
update_method: Literal["broadcast", "p2p", "all"] = "broadcast",
|
||||
uds: str | None = None,
|
||||
):
|
||||
ps.register_checkpoint(
|
||||
checkpoint_name, files=checkpoint_files, named_tensors=named_tensors
|
||||
)
|
||||
ps.init_process_group()
|
||||
check_sglang_ready(endpoint, inference_parallel_size, uds)
|
||||
dist.barrier()
|
||||
with timer("Gather metas"):
|
||||
ps.gather_metas(checkpoint_name)
|
||||
if save_metas_file and int(os.getenv("RANK")) == 0:
|
||||
with open(save_metas_file, "wb") as f:
|
||||
pickle.dump(ps.get_metas(), f)
|
||||
|
||||
if update_method == "broadcast" or update_method == "all":
|
||||
with timer("Update weights without setting ranks"):
|
||||
ps.update(checkpoint_name, req_func)
|
||||
|
||||
if update_method == "p2p" or update_method == "all":
|
||||
if update_method:
|
||||
# sleep 2s to wait destroy process group
|
||||
time.sleep(2)
|
||||
with timer("Update weights with setting ranks"):
|
||||
ps.update(
|
||||
checkpoint_name, req_func, ranks=list(range(inference_parallel_size))
|
||||
)
|
||||
|
||||
|
||||
def join(
|
||||
ps: ParameterServer,
|
||||
checkpoint_name: str,
|
||||
load_metas_file: str,
|
||||
req_func: Callable[[list[tuple[str, str]]], None],
|
||||
inference_parallel_size: int,
|
||||
endpoint: str,
|
||||
uds: str | None = None,
|
||||
):
|
||||
assert load_metas_file, "load_metas_file is required"
|
||||
with open(load_metas_file, "rb") as f:
|
||||
metas = pickle.load(f)
|
||||
ps.init_process_group()
|
||||
check_sglang_ready(endpoint, inference_parallel_size, uds)
|
||||
dist.barrier()
|
||||
with timer("Gather metas before join"):
|
||||
ps.gather_metas(checkpoint_name)
|
||||
ps.load_metas(metas)
|
||||
with timer(
|
||||
f"Update weights with setting ranks as range(0, {inference_parallel_size}) by using p2p"
|
||||
):
|
||||
ps.update(checkpoint_name, req_func, ranks=list(range(inference_parallel_size)))
|
||||
|
||||
|
||||
def run_with_torchrun():
|
||||
"""Run the update script with torchrun automatically."""
|
||||
# Parse inference_parallel_size from command line arguments to determine nproc-per-node
|
||||
inference_parallel_size = 8 # default
|
||||
args = sys.argv[1:] # Skip the script name
|
||||
|
||||
# Look for --inference-parallel-size in arguments
|
||||
for i, arg in enumerate(args):
|
||||
if arg == "--inference-parallel-size" and i + 1 < len(args):
|
||||
try:
|
||||
inference_parallel_size = int(args[i + 1])
|
||||
except ValueError:
|
||||
pass
|
||||
break
|
||||
elif arg.startswith("--inference-parallel-size="):
|
||||
try:
|
||||
inference_parallel_size = int(arg.split("=", 1)[1])
|
||||
except ValueError:
|
||||
pass
|
||||
break
|
||||
|
||||
# Build torchrun command
|
||||
cmd = ["torchrun", f"--nproc-per-node={inference_parallel_size}", __file__] + args
|
||||
|
||||
print(f"Running: {' '.join(cmd)}", file=sys.stderr)
|
||||
|
||||
# Execute torchrun with the original script
|
||||
try:
|
||||
result = subprocess.run(cmd, check=False)
|
||||
sys.exit(result.returncode)
|
||||
except FileNotFoundError:
|
||||
print(
|
||||
"Error: torchrun command not found. Please ensure PyTorch is installed.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
except KeyboardInterrupt:
|
||||
print("\nInterrupted by user", file=sys.stderr)
|
||||
sys.exit(130)
|
||||
|
||||
|
||||
def main():
|
||||
# Check if we're running under torchrun or need to invoke it
|
||||
if os.getenv("RANK") is None:
|
||||
# Not running under torchrun, so invoke it
|
||||
run_with_torchrun()
|
||||
return
|
||||
|
||||
# Running under torchrun, proceed with normal execution
|
||||
parser = argparse.ArgumentParser(description="Update weights example")
|
||||
parser.add_argument("--checkpoint-path", type=str, default=None)
|
||||
parser.add_argument("--save-metas-file", type=str, default=None)
|
||||
parser.add_argument("--load-metas-file", type=str, default=None)
|
||||
parser.add_argument("--sleep-time", type=int, default=0)
|
||||
parser.add_argument("--endpoint", type=str, default="http://localhost:19730")
|
||||
parser.add_argument("--inference-parallel-size", type=int, default=8)
|
||||
parser.add_argument("--checkpoint-name", type=str, default="my-checkpoint-iter-0")
|
||||
parser.add_argument("--update-method", type=str, default="broadcast")
|
||||
parser.add_argument("--uds", type=str, default=None)
|
||||
parser.add_argument("--weight-version", type=str, default=None)
|
||||
args = parser.parse_args()
|
||||
|
||||
# Get rank and world_size from environment (set by torchrun)
|
||||
rank = int(os.getenv("RANK", 0))
|
||||
world_size = int(os.getenv("WORLD_SIZE", 1))
|
||||
|
||||
req_func = req_inference(
|
||||
args.endpoint,
|
||||
args.inference_parallel_size,
|
||||
uds=args.uds,
|
||||
weight_version=args.weight_version,
|
||||
)
|
||||
|
||||
if ParameterServer is None:
|
||||
print("Error: checkpoint_engine package not available", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
ps = ParameterServer(auto_pg=True)
|
||||
ps._p2p_store = None
|
||||
if args.load_metas_file:
|
||||
join(
|
||||
ps,
|
||||
args.checkpoint_name,
|
||||
args.load_metas_file,
|
||||
req_func,
|
||||
args.inference_parallel_size,
|
||||
args.endpoint,
|
||||
args.uds,
|
||||
)
|
||||
else:
|
||||
if args.checkpoint_path and os.path.exists(
|
||||
os.path.join(args.checkpoint_path, "model.safetensors.index.json")
|
||||
):
|
||||
named_tensors = split_tensors(args.checkpoint_path, rank, world_size)
|
||||
checkpoint_files = []
|
||||
else:
|
||||
checkpoint_files = (
|
||||
split_checkpoint_files(args.checkpoint_path, rank, world_size)
|
||||
if args.checkpoint_path
|
||||
else []
|
||||
)
|
||||
named_tensors = {}
|
||||
update_weights(
|
||||
ps,
|
||||
args.checkpoint_name,
|
||||
checkpoint_files,
|
||||
named_tensors,
|
||||
req_func,
|
||||
args.inference_parallel_size,
|
||||
args.endpoint,
|
||||
args.save_metas_file,
|
||||
args.update_method,
|
||||
args.uds,
|
||||
)
|
||||
time.sleep(args.sleep_time)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
468
third_party/sglang/python/sglang/srt/compilation/backend.py
vendored
Normal file
468
third_party/sglang/python/sglang/srt/compilation/backend.py
vendored
Normal file
@@ -0,0 +1,468 @@
|
||||
# Adapted from https://github.com/vllm-project/vllm/blob/v0.10.0/vllm/compilation/backend.py
|
||||
|
||||
|
||||
import ast
|
||||
import dataclasses
|
||||
import logging
|
||||
import os
|
||||
import pprint
|
||||
import time
|
||||
from collections.abc import Sequence
|
||||
from contextlib import contextmanager
|
||||
from typing import Any, Callable, Optional
|
||||
|
||||
import torch
|
||||
import torch.fx as fx
|
||||
from torch._dispatch.python import enable_python_dispatcher
|
||||
|
||||
from sglang.srt.compilation.compilation_config import CompilationConfig
|
||||
from sglang.srt.compilation.compilation_counter import compilation_counter
|
||||
from sglang.srt.compilation.compiler_interface import EagerAdapter, InductorAdaptor
|
||||
from sglang.srt.compilation.cuda_piecewise_backend import CUDAPiecewiseBackend
|
||||
from sglang.srt.compilation.npu_piecewise_backend import NPUPiecewiseBackend
|
||||
from sglang.srt.compilation.pass_manager import PostGradPassManager
|
||||
from sglang.srt.utils.common import is_npu
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def make_compiler(config: CompilationConfig):
|
||||
if config.compiler == "eager":
|
||||
return EagerAdapter()
|
||||
elif config.compiler == "inductor":
|
||||
return InductorAdaptor()
|
||||
else:
|
||||
raise ValueError(f"Unknown compiler: {config.compiler}")
|
||||
|
||||
|
||||
def make_backend(
|
||||
graph: fx.GraphModule,
|
||||
compile_config: CompilationConfig,
|
||||
inductor_config: dict[str, Any],
|
||||
graph_pool: Any,
|
||||
piecewise_compile_index: int,
|
||||
total_piecewise_compiles: int,
|
||||
sym_shape_indices: list[int],
|
||||
compiled_graph_for_general_shape: Callable,
|
||||
sglang_backend,
|
||||
):
|
||||
|
||||
backend_cls = CUDAPiecewiseBackend if not is_npu() else NPUPiecewiseBackend
|
||||
return backend_cls(
|
||||
graph,
|
||||
compile_config,
|
||||
inductor_config,
|
||||
graph_pool,
|
||||
piecewise_compile_index,
|
||||
total_piecewise_compiles,
|
||||
sym_shape_indices,
|
||||
compiled_graph_for_general_shape,
|
||||
sglang_backend,
|
||||
)
|
||||
|
||||
|
||||
class CompilerManager:
|
||||
def __init__(
|
||||
self,
|
||||
config: CompilationConfig,
|
||||
):
|
||||
self.cache = dict()
|
||||
self.is_cache_updated = False
|
||||
self.compiler = make_compiler(config)
|
||||
|
||||
def compute_hash(self):
|
||||
return self.compiler.compute_hash()
|
||||
|
||||
def initialize_cache(
|
||||
self, cache_dir: str, disable_cache: bool = False, prefix: str = ""
|
||||
):
|
||||
self.disable_cache = disable_cache
|
||||
self.cache_dir = cache_dir
|
||||
self.cache_file_path = os.path.join(cache_dir, "sglang_compile_cache.py")
|
||||
|
||||
if not disable_cache and os.path.exists(self.cache_file_path):
|
||||
with open(self.cache_file_path) as f:
|
||||
self.cache = ast.literal_eval(f.read())
|
||||
|
||||
self.compiler.initialize_cache(
|
||||
cache_dir=cache_dir, disable_cache=disable_cache, prefix=prefix
|
||||
)
|
||||
|
||||
def save_to_file(self):
|
||||
if self.disable_cache or not self.is_cache_updated:
|
||||
return
|
||||
printer = pprint.PrettyPrinter(indent=4)
|
||||
data = printer.pformat(self.cache)
|
||||
with open(self.cache_file_path, "w") as f:
|
||||
f.write(data)
|
||||
|
||||
def load(
|
||||
self,
|
||||
graph: fx.GraphModule,
|
||||
example_inputs: list[Any],
|
||||
graph_index: int,
|
||||
runtime_shape: Optional[int] = None,
|
||||
) -> Optional[Callable]:
|
||||
handle = self.cache[(runtime_shape, graph_index, self.compiler.name)]
|
||||
compiled_graph = self.compiler.load(
|
||||
handle, graph, example_inputs, graph_index, runtime_shape
|
||||
)
|
||||
if runtime_shape is None:
|
||||
logger.debug(
|
||||
"Directly load the %s-th graph for dynamic shape from %s via "
|
||||
"handle %s",
|
||||
graph_index,
|
||||
self.compiler.name,
|
||||
handle,
|
||||
)
|
||||
else:
|
||||
logger.debug(
|
||||
"Directly load the %s-th graph for shape %s from %s via " "handle %s",
|
||||
graph_index,
|
||||
str(runtime_shape),
|
||||
self.compiler.name,
|
||||
handle,
|
||||
)
|
||||
return compiled_graph
|
||||
|
||||
def compile(
|
||||
self,
|
||||
graph: fx.GraphModule,
|
||||
example_inputs,
|
||||
inductor_config: dict[str, Any],
|
||||
graph_index: int = 0,
|
||||
num_graphs: int = 1,
|
||||
runtime_shape: Optional[int] = None,
|
||||
) -> Any:
|
||||
if graph_index == 0:
|
||||
# before compiling the first graph, record the start time
|
||||
global compilation_start_time
|
||||
compilation_start_time = time.time()
|
||||
|
||||
compilation_counter.num_backend_compilations += 1
|
||||
|
||||
compiled_graph = None
|
||||
|
||||
# TODO(Yuwei): support cache loading
|
||||
|
||||
# no compiler cached the graph, or the cache is disabled,
|
||||
# we need to compile it
|
||||
if isinstance(self.compiler, InductorAdaptor):
|
||||
maybe_key = None
|
||||
else:
|
||||
maybe_key = f"artifact_shape_{runtime_shape}_subgraph_{graph_index}"
|
||||
compiled_graph, handle = self.compiler.compile(
|
||||
graph, example_inputs, inductor_config, runtime_shape, maybe_key
|
||||
)
|
||||
|
||||
assert compiled_graph is not None, "Failed to compile the graph"
|
||||
|
||||
# store the artifact in the cache
|
||||
if handle is not None:
|
||||
self.cache[(runtime_shape, graph_index, self.compiler.name)] = handle
|
||||
compilation_counter.num_cache_entries_updated += 1
|
||||
self.is_cache_updated = True
|
||||
if graph_index == 0:
|
||||
# adds some info logging for the first graph
|
||||
if runtime_shape is None:
|
||||
logger.info("Cache the graph for dynamic shape for later use")
|
||||
else:
|
||||
logger.info(
|
||||
"Cache the graph of shape %s for later use", str(runtime_shape)
|
||||
)
|
||||
if runtime_shape is None:
|
||||
logger.debug(
|
||||
"Store the %s-th graph for dynamic shape from %s via " "handle %s",
|
||||
graph_index,
|
||||
self.compiler.name,
|
||||
handle,
|
||||
)
|
||||
else:
|
||||
logger.debug(
|
||||
"Store the %s-th graph for shape %s from %s via handle %s",
|
||||
graph_index,
|
||||
str(runtime_shape),
|
||||
self.compiler.name,
|
||||
handle,
|
||||
)
|
||||
|
||||
# after compiling the last graph, record the end time
|
||||
if graph_index == num_graphs - 1:
|
||||
now = time.time()
|
||||
elapsed = now - compilation_start_time
|
||||
if runtime_shape is None:
|
||||
logger.info("Compiling a graph for dynamic shape takes %.2f s", elapsed)
|
||||
else:
|
||||
logger.info(
|
||||
"Compiling a graph for shape %s takes %.2f s",
|
||||
runtime_shape,
|
||||
elapsed,
|
||||
)
|
||||
|
||||
return compiled_graph
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class SplitItem:
|
||||
submod_name: str
|
||||
graph_id: int
|
||||
is_splitting_graph: bool
|
||||
graph: fx.GraphModule
|
||||
|
||||
|
||||
def split_graph(
|
||||
graph: fx.GraphModule, ops: list[str]
|
||||
) -> tuple[fx.GraphModule, list[SplitItem]]:
|
||||
# split graph by ops
|
||||
subgraph_id = 0
|
||||
node_to_subgraph_id = {}
|
||||
split_op_graphs = []
|
||||
for node in graph.graph.nodes:
|
||||
if node.op in ("output", "placeholder"):
|
||||
continue
|
||||
if node.op == "call_function" and str(node.target) in ops:
|
||||
subgraph_id += 1
|
||||
node_to_subgraph_id[node] = subgraph_id
|
||||
split_op_graphs.append(subgraph_id)
|
||||
subgraph_id += 1
|
||||
else:
|
||||
node_to_subgraph_id[node] = subgraph_id
|
||||
|
||||
# `keep_original_order` is important!
|
||||
# otherwise pytorch might reorder the nodes and
|
||||
# the semantics of the graph will change when we
|
||||
# have mutations in the graph
|
||||
split_gm = torch.fx.passes.split_module.split_module(
|
||||
graph, None, lambda node: node_to_subgraph_id[node], keep_original_order=True
|
||||
)
|
||||
|
||||
outputs = []
|
||||
|
||||
names = [name for (name, module) in split_gm.named_modules()]
|
||||
|
||||
for name in names:
|
||||
if "." in name or name == "":
|
||||
# recursive child module or the root module
|
||||
continue
|
||||
|
||||
module = getattr(split_gm, name)
|
||||
|
||||
graph_id = int(name.replace("submod_", ""))
|
||||
outputs.append(SplitItem(name, graph_id, (graph_id in split_op_graphs), module))
|
||||
|
||||
# sort by intetger graph_id, rather than string name
|
||||
outputs.sort(key=lambda x: x.graph_id)
|
||||
|
||||
return split_gm, outputs
|
||||
|
||||
|
||||
# we share the global graph pool among all the backends
|
||||
global_graph_pool = None
|
||||
|
||||
compilation_start_time = 0.0
|
||||
|
||||
|
||||
class PiecewiseCompileInterpreter(torch.fx.Interpreter):
|
||||
def __init__(
|
||||
self,
|
||||
module: torch.fx.GraphModule,
|
||||
compile_submod_names: list[str],
|
||||
inductor_config: dict[str, Any],
|
||||
graph_pool,
|
||||
compile_config: CompilationConfig,
|
||||
sglang_backend: "SGLangBackend",
|
||||
):
|
||||
super().__init__(module)
|
||||
from torch._guards import detect_fake_mode
|
||||
|
||||
self.fake_mode = detect_fake_mode()
|
||||
self.compile_submod_names = compile_submod_names
|
||||
self.graph_pool = graph_pool
|
||||
self.sglang_backend = sglang_backend
|
||||
# When True, it annoyingly dumps the torch.fx.Graph on errors.
|
||||
self.extra_traceback = False
|
||||
self.inductor_config = inductor_config
|
||||
self.compile_config = compile_config
|
||||
|
||||
def run(self, *args):
|
||||
fake_args = [
|
||||
self.fake_mode.from_tensor(t) if isinstance(t, torch.Tensor) else t
|
||||
for t in args
|
||||
]
|
||||
with self.fake_mode, enable_python_dispatcher():
|
||||
return super().run(*fake_args)
|
||||
|
||||
def call_module(
|
||||
self,
|
||||
target: torch.fx.node.Target,
|
||||
args: tuple[torch.fx.node.Argument, ...],
|
||||
kwargs: dict[str, Any],
|
||||
) -> Any:
|
||||
assert isinstance(target, str)
|
||||
output = super().call_module(target, args, kwargs)
|
||||
|
||||
if target in self.compile_submod_names:
|
||||
index = self.compile_submod_names.index(target)
|
||||
submod = self.fetch_attr(target)
|
||||
sym_shape_indices = [
|
||||
i for i, x in enumerate(args) if isinstance(x, torch.SymInt)
|
||||
]
|
||||
global compilation_start_time
|
||||
compiled_graph_for_dynamic_shape = (
|
||||
self.sglang_backend.compiler_manager.compile(
|
||||
submod,
|
||||
args,
|
||||
self.inductor_config,
|
||||
graph_index=index,
|
||||
num_graphs=len(self.compile_submod_names),
|
||||
runtime_shape=None,
|
||||
)
|
||||
)
|
||||
|
||||
self.module.__dict__[target] = make_backend(
|
||||
submod,
|
||||
self.compile_config,
|
||||
self.inductor_config,
|
||||
self.graph_pool,
|
||||
index,
|
||||
len(self.compile_submod_names),
|
||||
sym_shape_indices,
|
||||
compiled_graph_for_dynamic_shape,
|
||||
self.sglang_backend,
|
||||
)
|
||||
|
||||
compilation_counter.num_piecewise_capturable_graphs_seen += 1
|
||||
|
||||
return output
|
||||
|
||||
|
||||
model_tag: str = "backbone"
|
||||
|
||||
|
||||
@contextmanager
|
||||
def set_model_tag(tag: str):
|
||||
"""Context manager to set the model tag."""
|
||||
global model_tag
|
||||
assert (
|
||||
tag != model_tag
|
||||
), f"Model tag {tag} is the same as the current tag {model_tag}."
|
||||
old_tag = model_tag
|
||||
model_tag = tag
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
model_tag = old_tag
|
||||
|
||||
|
||||
class SGLangBackend:
|
||||
|
||||
graph_pool: Any
|
||||
_called: bool = False
|
||||
# the graph we compiled
|
||||
graph: fx.GraphModule
|
||||
# the stiching graph module for all the piecewise graphs
|
||||
split_gm: fx.GraphModule
|
||||
piecewise_graphs: list[SplitItem]
|
||||
returned_callable: Callable
|
||||
# Inductor passes to run on the graph pre-defunctionalization
|
||||
post_grad_passes: Sequence[Callable]
|
||||
sym_tensor_indices: list[int]
|
||||
input_buffers: list[torch.Tensor]
|
||||
compiler_manager: CompilerManager
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: CompilationConfig,
|
||||
graph_pool: Any,
|
||||
):
|
||||
assert graph_pool is not None
|
||||
self.graph_pool = graph_pool
|
||||
|
||||
self.post_grad_pass_manager = PostGradPassManager()
|
||||
self.sym_tensor_indices = []
|
||||
self.input_buffers = []
|
||||
|
||||
self.compiler_manager = CompilerManager(config)
|
||||
self.inductor_config = {
|
||||
"enable_auto_functionalized_v2": False,
|
||||
}
|
||||
self.compile_config = config
|
||||
|
||||
def configure_post_pass(self):
|
||||
self.post_grad_pass_manager.configure()
|
||||
self.inductor_config["post_grad_custom_post_pass"] = self.post_grad_pass_manager
|
||||
|
||||
def __call__(self, graph: fx.GraphModule, example_inputs) -> Callable:
|
||||
base_cache_dir = os.path.expanduser(
|
||||
os.getenv("SGLANG_CACHE_DIR", "~/.cache/sglang/")
|
||||
)
|
||||
|
||||
cache_hash = self.compiler_manager.compute_hash()
|
||||
cache_dir = os.path.join(
|
||||
base_cache_dir,
|
||||
"torch_compile_cache",
|
||||
cache_hash,
|
||||
)
|
||||
|
||||
os.makedirs(cache_dir, exist_ok=True)
|
||||
rank = 0
|
||||
dp_rank = 0
|
||||
local_cache_dir = os.path.join(cache_dir, f"rank_{rank}_{dp_rank}", model_tag)
|
||||
os.makedirs(local_cache_dir, exist_ok=True)
|
||||
self.compiler_manager.initialize_cache(
|
||||
local_cache_dir, disable_cache=False, prefix=""
|
||||
)
|
||||
compilation_counter.num_graphs_seen += 1
|
||||
|
||||
assert not self._called, "SGLangBackend can only be called once"
|
||||
|
||||
self.graph = graph
|
||||
self.configure_post_pass()
|
||||
|
||||
self.split_gm, self.piecewise_graphs = split_graph(
|
||||
graph,
|
||||
self.compile_config.split_ops,
|
||||
)
|
||||
from torch._dynamo.utils import lazy_format_graph_code
|
||||
|
||||
# depyf will hook lazy_format_graph_code and dump the graph
|
||||
# for debugging, no need to print the graph here
|
||||
lazy_format_graph_code("before split", self.graph)
|
||||
lazy_format_graph_code("after split", self.split_gm)
|
||||
|
||||
compilation_counter.num_piecewise_graphs_seen += len(self.piecewise_graphs)
|
||||
|
||||
submod_names_to_compile = [
|
||||
item.submod_name
|
||||
for item in self.piecewise_graphs
|
||||
if not item.is_splitting_graph
|
||||
]
|
||||
|
||||
PiecewiseCompileInterpreter(
|
||||
self.split_gm,
|
||||
submod_names_to_compile,
|
||||
self.inductor_config,
|
||||
self.graph_pool,
|
||||
self.compile_config,
|
||||
self,
|
||||
).run(*example_inputs)
|
||||
|
||||
rank = torch.distributed.get_rank()
|
||||
|
||||
if rank == 0:
|
||||
graph_path = os.path.join(
|
||||
local_cache_dir, f"computation_graph_{time.time()}.py"
|
||||
)
|
||||
if not os.path.exists(graph_path):
|
||||
# code adapted from https://github.com/thuml/depyf/blob/dab831108a752d1facc00acdd6d4243891845c37/depyf/explain/patched_lazy_format_graph_code.py#L30 # noqa
|
||||
# use `print_readable` because it can include submodules
|
||||
src = (
|
||||
"from __future__ import annotations\nimport torch\n"
|
||||
+ self.split_gm.print_readable(print_output=False)
|
||||
)
|
||||
src = src.replace("<lambda>", "GraphModule")
|
||||
with open(graph_path, "w") as f:
|
||||
f.write(src)
|
||||
|
||||
self._called = True
|
||||
return self.split_gm
|
||||
45
third_party/sglang/python/sglang/srt/compilation/compilation_config.py
vendored
Normal file
45
third_party/sglang/python/sglang/srt/compilation/compilation_config.py
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
# Adapted from https://github.com/vllm-project/vllm/blob/v0.10.0/vllm/compilation/compilation_config.py
|
||||
|
||||
from typing import Callable, List, Optional
|
||||
|
||||
SPLIT_OPS = []
|
||||
|
||||
|
||||
def register_split_op(op_name: Optional[str] = None):
|
||||
def decorator(op_func: Callable):
|
||||
name = op_name or op_func.__name__
|
||||
SPLIT_OPS.append(f"sglang.{name}")
|
||||
return op_func
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
# TODO(Yuwei): support better compile config support
|
||||
class CompilationConfig:
|
||||
def __init__(
|
||||
self,
|
||||
capture_sizes: List[int],
|
||||
compiler: str = "eager",
|
||||
enable_debug_mode: bool = False,
|
||||
):
|
||||
self.traced_files = set()
|
||||
self.capture_sizes = capture_sizes
|
||||
self.compiler = compiler
|
||||
self.enable_debug_mode = enable_debug_mode
|
||||
self.split_ops = []
|
||||
self.split_ops.extend(SPLIT_OPS)
|
||||
|
||||
def add_split_op(self, op: str):
|
||||
self.split_ops.append(op)
|
||||
|
||||
def add_traced_file(self, file_path: str):
|
||||
self.traced_files.add(file_path)
|
||||
|
||||
def get_traced_files(self):
|
||||
return self.traced_files
|
||||
|
||||
def get_capture_sizes(self):
|
||||
return self.capture_sizes
|
||||
|
||||
def get_enable_debug_mode(self):
|
||||
return self.enable_debug_mode
|
||||
47
third_party/sglang/python/sglang/srt/compilation/compilation_counter.py
vendored
Normal file
47
third_party/sglang/python/sglang/srt/compilation/compilation_counter.py
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
# Adapted from https://github.com/vllm-project/vllm/blob/v0.10.0/vllm/compilation/compilation_counter.py
|
||||
|
||||
import copy
|
||||
import dataclasses
|
||||
from contextlib import contextmanager
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class CompilationCounter:
|
||||
num_models_seen: int = 0
|
||||
num_graphs_seen: int = 0
|
||||
# including the splitting ops
|
||||
num_piecewise_graphs_seen: int = 0
|
||||
# not including the splitting ops
|
||||
num_piecewise_capturable_graphs_seen: int = 0
|
||||
num_backend_compilations: int = 0
|
||||
# Number of gpu_model_runner attempts to trigger CUDAGraphs capture
|
||||
num_gpu_runner_capture_triggers: int = 0
|
||||
# Number of CUDAGraphs captured
|
||||
num_cudagraph_captured: int = 0
|
||||
# InductorAdapter.compile calls
|
||||
num_inductor_compiles: int = 0
|
||||
# EagerAdapter.compile calls
|
||||
num_eager_compiles: int = 0
|
||||
# The number of time vLLM's compiler cache entry was updated
|
||||
num_cache_entries_updated: int = 0
|
||||
# The number of standalone_compile compiled artifacts saved
|
||||
num_compiled_artifacts_saved: int = 0
|
||||
# Number of times a model was loaded with CompilationLevel.DYNAMO_AS_IS
|
||||
dynamo_as_is_count: int = 0
|
||||
|
||||
def clone(self) -> "CompilationCounter":
|
||||
return copy.deepcopy(self)
|
||||
|
||||
@contextmanager
|
||||
def expect(self, **kwargs):
|
||||
old = self.clone()
|
||||
yield
|
||||
for k, v in kwargs.items():
|
||||
assert getattr(self, k) - getattr(old, k) == v, (
|
||||
f"{k} not as expected, before it is {getattr(old, k)}"
|
||||
f", after it is {getattr(self, k)}, "
|
||||
f"expected diff is {v}"
|
||||
)
|
||||
|
||||
|
||||
compilation_counter = CompilationCounter()
|
||||
201
third_party/sglang/python/sglang/srt/compilation/compile.py
vendored
Normal file
201
third_party/sglang/python/sglang/srt/compilation/compile.py
vendored
Normal file
@@ -0,0 +1,201 @@
|
||||
import inspect
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import types
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Callable, Optional, Union
|
||||
|
||||
import torch
|
||||
|
||||
from sglang.srt.compilation.compilation_config import CompilationConfig
|
||||
from sglang.srt.compilation.piecewise_context_manager import is_in_piecewise_cuda_graph
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class IntermediateTensors:
|
||||
"""For all pipeline stages except the last, we need to return the hidden
|
||||
states and residuals to be sent to the next stage. This data structure
|
||||
contains the hidden states and residuals for a request.
|
||||
|
||||
Each stage also needs to handle its own finished_sending and
|
||||
finished_recving in case of kv transfer.
|
||||
"""
|
||||
|
||||
tensors: dict[str, torch.Tensor]
|
||||
# [req_ids]
|
||||
finished_sending: Optional[set[str]] = None
|
||||
finished_recving: Optional[set[str]] = None
|
||||
|
||||
def __init__(self, tensors):
|
||||
# manually define this function, so that
|
||||
# Dynamo knows `IntermediateTensors()` comes from this file.
|
||||
# Otherwise, dataclass will generate this function by evaluating
|
||||
# a string, and we will lose the information about the source file.
|
||||
self.tensors = tensors
|
||||
|
||||
def __getitem__(self, key: Union[str, slice]):
|
||||
if isinstance(key, str):
|
||||
return self.tensors[key]
|
||||
elif isinstance(key, slice):
|
||||
return self.__class__({k: v[key] for k, v in self.tensors.items()})
|
||||
|
||||
def __setitem__(self, key: str, value: torch.Tensor):
|
||||
self.tensors[key] = value
|
||||
|
||||
def items(self):
|
||||
return self.tensors.items()
|
||||
|
||||
def __len__(self):
|
||||
return len(self.tensors)
|
||||
|
||||
def __eq__(self, other: object):
|
||||
return isinstance(other, self.__class__) and self
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"IntermediateTensors(tensors={self.tensors})"
|
||||
|
||||
|
||||
def _normalize_dims(dims, ndim: int):
|
||||
dims = [dims] if isinstance(dims, int) else list(dims)
|
||||
return [d if d >= 0 else ndim + d for d in dims]
|
||||
|
||||
|
||||
class _MaybeIntermediateTensors:
|
||||
"""Duck-typed check to support your IntermediateTensors without importing."""
|
||||
|
||||
def __init__(self, obj):
|
||||
self.is_intermediate = hasattr(obj, "tensors") and isinstance(
|
||||
getattr(obj, "tensors"), dict
|
||||
)
|
||||
self.obj = obj
|
||||
|
||||
|
||||
def _mark_dynamic_on_value(val, dims):
|
||||
if isinstance(val, torch.Tensor):
|
||||
torch._dynamo.maybe_mark_dynamic(val, _normalize_dims(dims, val.ndim))
|
||||
else:
|
||||
mit = _MaybeIntermediateTensors(val)
|
||||
if mit.is_intermediate:
|
||||
for t in mit.obj.tensors.values():
|
||||
torch._dynamo.maybe_mark_dynamic(t, _normalize_dims(dims, t.ndim))
|
||||
# else: ignore (None or non-tensor)
|
||||
|
||||
|
||||
def _infer_dynamic_arg_dims_from_annotations(forward_fn):
|
||||
sig = inspect.signature(forward_fn)
|
||||
dyn = {}
|
||||
for name, p in sig.parameters.items():
|
||||
ann = p.annotation
|
||||
# Accept torch.Tensor / Optional[torch.Tensor] / your IntermediateTensors types by name
|
||||
if (
|
||||
ann is torch.Tensor
|
||||
or getattr(getattr(ann, "__args__", [None])[0], "__name__", "") == "Tensor"
|
||||
):
|
||||
dyn[name] = 0
|
||||
elif getattr(ann, "__name__", "") in ("IntermediateTensors",) or any(
|
||||
getattr(a, "__name__", "") == "IntermediateTensors"
|
||||
for a in getattr(ann, "__args__", [])
|
||||
):
|
||||
dyn[name] = 0
|
||||
elif ann == "torch.Tensor" or ann == "Optional[torch.Tensor]":
|
||||
# For future import annotations (e.g. from __future__ import annotations), the annotation is a string
|
||||
dyn[name] = 0
|
||||
if not dyn:
|
||||
raise ValueError("No dynamic dims inferred; pass dynamic_arg_dims explicitly.")
|
||||
return dyn
|
||||
|
||||
|
||||
def install_torch_compiled(
|
||||
module: torch.nn.Module,
|
||||
*,
|
||||
dynamic_arg_dims: dict[str, Union[int, list[int]]] | None = None,
|
||||
backend_factory: Optional[Callable[[torch.fx.GraphModule, list], Callable]] = None,
|
||||
compile_config: CompilationConfig = None,
|
||||
fullgraph: bool = True,
|
||||
graph_pool: Any = None,
|
||||
):
|
||||
unbound_fwd = module.__class__.forward
|
||||
if not callable(unbound_fwd):
|
||||
raise TypeError("module.__class__.forward must be callable")
|
||||
original_code = unbound_fwd.__code__
|
||||
|
||||
dyn_map = dynamic_arg_dims or _infer_dynamic_arg_dims_from_annotations(unbound_fwd)
|
||||
|
||||
if backend_factory is None:
|
||||
from sglang.srt.compilation.backend import SGLangBackend
|
||||
|
||||
backend_factory = lambda gm, ex: SGLangBackend(compile_config, graph_pool)(
|
||||
gm, ex
|
||||
)
|
||||
|
||||
compiled_codes: list[type(original_code)] = []
|
||||
state = {"compiled": False, "compiled_callable": None}
|
||||
|
||||
def bytecode_hook(old_code, new_code):
|
||||
if old_code is not original_code:
|
||||
return
|
||||
frame = sys._getframe()
|
||||
while frame and frame.f_back:
|
||||
frame = frame.f_back
|
||||
if (
|
||||
frame.f_code.co_name == "_compile"
|
||||
and os.path.basename(frame.f_code.co_filename) == "convert_frame.py"
|
||||
):
|
||||
break
|
||||
try:
|
||||
dynamo_frame = frame.f_locals["frame"]
|
||||
except Exception:
|
||||
return
|
||||
if dynamo_frame.f_code is not old_code:
|
||||
return
|
||||
if dynamo_frame.f_locals.get("self") is not module:
|
||||
return
|
||||
compiled_codes.append(new_code)
|
||||
|
||||
torch._dynamo.convert_frame.register_bytecode_hook(bytecode_hook)
|
||||
|
||||
def _ensure_compiled(self, *args, **kwargs):
|
||||
"""Compile on first use (with flag ON)."""
|
||||
if state["compiled"]:
|
||||
return
|
||||
# Mark dynamic dims only when we are about to compile
|
||||
sig = inspect.signature(unbound_fwd)
|
||||
ba = sig.bind(self, *args, **kwargs)
|
||||
ba.apply_defaults()
|
||||
for name, dims in (dyn_map or {}).items():
|
||||
if name in ba.arguments:
|
||||
val = ba.arguments[name]
|
||||
if val is not None:
|
||||
_mark_dynamic_on_value(val, dims)
|
||||
|
||||
# Avoid cross-instance cache reuse
|
||||
torch._dynamo.eval_frame.remove_from_cache(unbound_fwd.__code__)
|
||||
|
||||
bound = types.MethodType(unbound_fwd, self)
|
||||
compiled_callable = torch.compile(
|
||||
bound, fullgraph=fullgraph, backend=backend_factory
|
||||
)
|
||||
|
||||
# Trigger Dynamo so bytecode hook can capture
|
||||
compiled_callable(*args, **kwargs)
|
||||
|
||||
state["compiled"] = True
|
||||
state["compiled_callable"] = compiled_callable
|
||||
|
||||
def trampoline(self, *args, **kwargs):
|
||||
use_compiled = is_in_piecewise_cuda_graph()
|
||||
if use_compiled:
|
||||
if not state["compiled"]:
|
||||
_ensure_compiled(self, *args, **kwargs)
|
||||
|
||||
compiled_callable = state["compiled_callable"]
|
||||
return compiled_callable(*args, **kwargs)
|
||||
else:
|
||||
# Explicitly run the original uncompiled forward
|
||||
return unbound_fwd(self, *args, **kwargs)
|
||||
|
||||
module.forward = types.MethodType(trampoline, module)
|
||||
return module
|
||||
504
third_party/sglang/python/sglang/srt/compilation/compiler_interface.py
vendored
Normal file
504
third_party/sglang/python/sglang/srt/compilation/compiler_interface.py
vendored
Normal file
@@ -0,0 +1,504 @@
|
||||
# Adapted from https://github.com/vllm-project/vllm/blob/v0.10.0/vllm/compilation/compiler_interface.py
|
||||
|
||||
import contextlib
|
||||
import copy
|
||||
import hashlib
|
||||
import os
|
||||
from contextlib import ExitStack
|
||||
from typing import Any, Callable, Optional
|
||||
from unittest.mock import patch
|
||||
|
||||
import torch
|
||||
import torch._inductor.compile_fx
|
||||
import torch.fx as fx
|
||||
|
||||
from sglang.srt.compilation.compilation_counter import compilation_counter
|
||||
from sglang.srt.compilation.inductor_pass import pass_context
|
||||
from sglang.srt.utils.common import torch_release
|
||||
|
||||
|
||||
class CompilerInterface:
|
||||
"""
|
||||
The interface for a compiler that can be used by vLLM.
|
||||
"""
|
||||
|
||||
# The name of the compiler, e.g. inductor.
|
||||
# This is a class-level attribute.
|
||||
name: str
|
||||
|
||||
def initialize_cache(
|
||||
self, cache_dir: str, disable_cache: bool = False, prefix: str = ""
|
||||
):
|
||||
"""
|
||||
when the vLLM process uses `cache_dir` as the cache directory,
|
||||
the compiler should initialize itself with the cache directory,
|
||||
e.g. by re-directing its own cache directory to a sub-directory.
|
||||
|
||||
prefix can be used in combination with cache_dir to figure out the base
|
||||
cache directory, e.g. there're multiple parts of model being compiled,
|
||||
but we want to share the same cache directory for all of them.
|
||||
|
||||
e.g.
|
||||
cache_dir = "/path/to/dir/backbone", prefix = "backbone"
|
||||
cache_dir = "/path/to/dir/eagle_head", prefix = "eagle_head"
|
||||
"""
|
||||
pass
|
||||
|
||||
def compute_hash(self) -> str:
|
||||
"""
|
||||
Gather all the relevant information from the vLLM config,
|
||||
to compute a hash so that we can cache the compiled model.
|
||||
|
||||
See [`VllmConfig.compute_hash`][vllm.config.VllmConfig.compute_hash]
|
||||
to check what information
|
||||
is already considered by default. This function should only
|
||||
consider the information that is specific to the compiler.
|
||||
"""
|
||||
return ""
|
||||
|
||||
def compile(
|
||||
self,
|
||||
graph: fx.GraphModule,
|
||||
example_inputs: list[Any],
|
||||
compiler_config: dict[str, Any],
|
||||
runtime_shape: Optional[int] = None,
|
||||
key: Optional[str] = None,
|
||||
) -> tuple[Optional[Callable], Optional[Any]]:
|
||||
"""
|
||||
Compile the graph with the given example inputs and compiler config,
|
||||
with a runtime shape. If the `runtime_shape` is None, it means
|
||||
the `example_inputs` have a dynamic shape. Otherwise, the
|
||||
`runtime_shape` specifies the shape of the inputs. Right now we only
|
||||
support one variable shape for all inputs, which is the batchsize
|
||||
(number of tokens) during inference.
|
||||
|
||||
Dynamo will make sure `graph(*example_inputs)` is valid.
|
||||
|
||||
The function should return a compiled callable function, as well as
|
||||
a handle that can be used to directly load the compiled function.
|
||||
|
||||
The handle should be a plain Python object, preferably a string or a
|
||||
file path for readability.
|
||||
|
||||
If the compiler doesn't support caching, it should return None for the
|
||||
handle. If the compiler fails to compile the graph, it should return
|
||||
None for the compiled function as well.
|
||||
|
||||
`key` is required for StandaloneInductorAdapter, it specifies where to
|
||||
save the compiled artifact. The compiled artifact gets saved to
|
||||
`cache_dir/key`.
|
||||
"""
|
||||
return None, None
|
||||
|
||||
def load(
|
||||
self,
|
||||
handle: Any,
|
||||
graph: fx.GraphModule,
|
||||
example_inputs: list[Any],
|
||||
graph_index: int,
|
||||
runtime_shape: Optional[int] = None,
|
||||
) -> Callable:
|
||||
"""
|
||||
Load the compiled function from the handle.
|
||||
Raises an error if the handle is invalid.
|
||||
|
||||
The handle is the second return value of the `compile` function.
|
||||
"""
|
||||
raise NotImplementedError("caching is not supported")
|
||||
|
||||
|
||||
def get_inductor_factors() -> list[Any]:
|
||||
factors: list[Any] = []
|
||||
# summarize system state
|
||||
from torch._inductor.codecache import CacheBase
|
||||
|
||||
system_factors = CacheBase.get_system()
|
||||
factors.append(system_factors)
|
||||
|
||||
# summarize pytorch state
|
||||
from torch._inductor.codecache import torch_key
|
||||
|
||||
torch_factors = torch_key()
|
||||
factors.append(torch_factors)
|
||||
return factors
|
||||
|
||||
|
||||
class AlwaysHitShapeEnv:
|
||||
"""
|
||||
Why do we need this class:
|
||||
|
||||
For normal `torch.compile` usage, every compilation will have
|
||||
one Dynamo bytecode compilation and one Inductor compilation.
|
||||
The Inductor compilation happens under the context of the
|
||||
Dynamo bytecode compilation, and that context is used to
|
||||
determine the dynamic shape information, etc.
|
||||
|
||||
For our use case, we only run Dynamo bytecode compilation once,
|
||||
and run Inductor compilation multiple times with different shapes
|
||||
plus a general shape. The compilation for specific shapes happens
|
||||
outside of the context of the Dynamo bytecode compilation. At that
|
||||
time, we don't have shape environment to provide to Inductor, and
|
||||
it will fail the Inductor code cache lookup.
|
||||
|
||||
By providing a dummy shape environment that always hits, we can
|
||||
make the Inductor code cache lookup always hit, and we can
|
||||
compile the graph for different shapes as needed.
|
||||
|
||||
The following dummy methods are obtained by trial-and-error
|
||||
until it works.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.guards: list[Any] = []
|
||||
|
||||
def evaluate_guards_expression(self, *args, **kwargs):
|
||||
return True
|
||||
|
||||
def get_pruned_guards(self, *args, **kwargs):
|
||||
return []
|
||||
|
||||
def produce_guards_expression(self, *args, **kwargs):
|
||||
return ""
|
||||
|
||||
|
||||
class InductorAdaptor(CompilerInterface):
|
||||
"""
|
||||
The adaptor for the Inductor compiler, version 2.5, 2.6, 2.7.
|
||||
"""
|
||||
|
||||
name = "inductor"
|
||||
|
||||
def compute_hash(self) -> str:
|
||||
factors = get_inductor_factors()
|
||||
hash_str = hashlib.md5(
|
||||
str(factors).encode(), usedforsecurity=False
|
||||
).hexdigest()[:10]
|
||||
return hash_str
|
||||
|
||||
def initialize_cache(
|
||||
self, cache_dir: str, disable_cache: bool = False, prefix: str = ""
|
||||
):
|
||||
self.cache_dir = cache_dir
|
||||
self.prefix = prefix
|
||||
self.base_cache_dir = cache_dir[: -len(prefix)] if prefix else cache_dir
|
||||
if disable_cache:
|
||||
return
|
||||
# redirect the cache directory to a sub-directory
|
||||
# set flags so that Inductor and Triton store their cache
|
||||
# in the cache_dir, then users only need to copy the cache_dir
|
||||
# to another machine to reuse the cache.
|
||||
inductor_cache = os.path.join(self.base_cache_dir, "inductor_cache")
|
||||
os.makedirs(inductor_cache, exist_ok=True)
|
||||
os.environ["TORCHINDUCTOR_CACHE_DIR"] = inductor_cache
|
||||
triton_cache = os.path.join(self.base_cache_dir, "triton_cache")
|
||||
os.makedirs(triton_cache, exist_ok=True)
|
||||
os.environ["TRITON_CACHE_DIR"] = triton_cache
|
||||
|
||||
def compile(
|
||||
self,
|
||||
graph: fx.GraphModule,
|
||||
example_inputs: list[Any],
|
||||
compiler_config: dict[str, Any],
|
||||
runtime_shape: Optional[int] = None,
|
||||
key: Optional[str] = None,
|
||||
) -> tuple[Optional[Callable], Optional[Any]]:
|
||||
compilation_counter.num_inductor_compiles += 1
|
||||
from torch._inductor.compile_fx import compile_fx
|
||||
|
||||
current_config = {}
|
||||
if compiler_config is not None:
|
||||
current_config.update(compiler_config)
|
||||
|
||||
# disable remote cache
|
||||
current_config["fx_graph_cache"] = True
|
||||
current_config["fx_graph_remote_cache"] = False
|
||||
|
||||
set_inductor_config(current_config, runtime_shape)
|
||||
|
||||
# inductor can inplace modify the graph, so we need to copy it
|
||||
# see https://github.com/pytorch/pytorch/issues/138980
|
||||
graph = copy.deepcopy(graph)
|
||||
|
||||
# it's the first time we compile this graph
|
||||
# the assumption is that we don't have nested Inductor compilation.
|
||||
# compiled_fx_graph_hash will only be called once, and we can hook
|
||||
# it to get the hash of the compiled graph directly.
|
||||
|
||||
hash_str, file_path = None, None
|
||||
from torch._inductor.codecache import FxGraphCache, compiled_fx_graph_hash
|
||||
|
||||
if torch_release[:2] == (2, 5):
|
||||
original_load = FxGraphCache.load
|
||||
original_load_name = "torch._inductor.codecache.FxGraphCache.load"
|
||||
|
||||
def hijack_load(*args, **kwargs):
|
||||
inductor_compiled_graph = original_load(*args, **kwargs)
|
||||
nonlocal file_path
|
||||
compiled_fn = inductor_compiled_graph.current_callable
|
||||
file_path = compiled_fn.__code__.co_filename # noqa
|
||||
if not file_path.startswith(self.base_cache_dir):
|
||||
# hooked in the align_inputs_from_check_idxs function
|
||||
# in torch/_inductor/utils.py
|
||||
for cell in compiled_fn.__closure__:
|
||||
if not callable(cell.cell_contents):
|
||||
continue
|
||||
if cell.cell_contents.__code__.co_filename.startswith(
|
||||
self.base_cache_dir
|
||||
):
|
||||
# this is the real file path compiled from Inductor
|
||||
file_path = cell.cell_contents.__code__.co_filename
|
||||
break
|
||||
return inductor_compiled_graph
|
||||
|
||||
hijacked_compile_fx_inner = (
|
||||
torch._inductor.compile_fx.compile_fx_inner
|
||||
) # noqa
|
||||
elif torch_release >= (2, 6):
|
||||
# function renamed in 2.6
|
||||
original_load_name = None
|
||||
|
||||
def hijacked_compile_fx_inner(*args, **kwargs):
|
||||
output = torch._inductor.compile_fx.compile_fx_inner(*args, **kwargs)
|
||||
nonlocal hash_str
|
||||
inductor_compiled_graph = output
|
||||
if inductor_compiled_graph is not None:
|
||||
nonlocal file_path
|
||||
compiled_fn = inductor_compiled_graph.current_callable
|
||||
file_path = compiled_fn.__code__.co_filename # noqa
|
||||
if not file_path.startswith(self.base_cache_dir):
|
||||
# hooked in the align_inputs_from_check_idxs function
|
||||
# in torch/_inductor/utils.py
|
||||
for cell in compiled_fn.__closure__:
|
||||
if not callable(cell.cell_contents):
|
||||
continue
|
||||
code = cell.cell_contents.__code__
|
||||
if code.co_filename.startswith(self.base_cache_dir):
|
||||
# this is the real file path
|
||||
# compiled from Inductor
|
||||
file_path = code.co_filename
|
||||
break
|
||||
hash_str = inductor_compiled_graph._fx_graph_cache_key
|
||||
return output
|
||||
|
||||
def hijack_compiled_fx_graph_hash(*args, **kwargs):
|
||||
out = compiled_fx_graph_hash(*args, **kwargs)
|
||||
nonlocal hash_str
|
||||
hash_str = out[0]
|
||||
return out
|
||||
|
||||
def _check_can_cache(*args, **kwargs):
|
||||
# no error means it can be cached.
|
||||
# Inductor refuses to cache the graph outside of Dynamo
|
||||
# tracing context, and also disables caching for graphs
|
||||
# with high-order ops.
|
||||
# For vLLM, in either case, we want to cache the graph.
|
||||
# see https://github.com/pytorch/pytorch/blob/9f5ebf3fc609105a74eab4ccc24932d6353ff566/torch/_inductor/codecache.py#L1221 # noqa
|
||||
return
|
||||
|
||||
def _get_shape_env() -> AlwaysHitShapeEnv:
|
||||
return AlwaysHitShapeEnv()
|
||||
|
||||
with ExitStack() as stack:
|
||||
# hijack to get the compiled graph itself
|
||||
if original_load_name is not None:
|
||||
stack.enter_context(patch(original_load_name, hijack_load))
|
||||
|
||||
# for hijacking the hash of the compiled graph
|
||||
stack.enter_context(
|
||||
patch(
|
||||
"torch._inductor.codecache.compiled_fx_graph_hash",
|
||||
hijack_compiled_fx_graph_hash,
|
||||
)
|
||||
)
|
||||
|
||||
# for providing a dummy shape environment
|
||||
stack.enter_context(
|
||||
patch(
|
||||
"torch._inductor.codecache.FxGraphCache._get_shape_env",
|
||||
_get_shape_env,
|
||||
)
|
||||
)
|
||||
|
||||
from torch._functorch._aot_autograd.autograd_cache import AOTAutogradCache
|
||||
|
||||
# torch 2.8+ on main uses _get_shape_env in AOTAutogradCache
|
||||
if hasattr(AOTAutogradCache, "_get_shape_env"):
|
||||
stack.enter_context(
|
||||
patch(
|
||||
"torch._functorch._aot_autograd.autograd_cache.AOTAutogradCache._get_shape_env",
|
||||
_get_shape_env,
|
||||
)
|
||||
)
|
||||
|
||||
# for forcing the graph to be cached
|
||||
stack.enter_context(
|
||||
patch(
|
||||
"torch._inductor.codecache.FxGraphCache._check_can_cache",
|
||||
_check_can_cache,
|
||||
)
|
||||
)
|
||||
|
||||
# Dynamo metrics context, see method for more details.
|
||||
stack.enter_context(self.metrics_context())
|
||||
|
||||
# Disable remote caching. When these are on, on remote cache-hit,
|
||||
# the monkey-patched functions never actually get called.
|
||||
# vLLM today assumes and requires the monkey-patched functions to
|
||||
# get hit.
|
||||
# TODO(zou3519): we're going to replace this all with
|
||||
# standalone_compile sometime.
|
||||
|
||||
stack.enter_context(
|
||||
torch._inductor.config.patch(fx_graph_remote_cache=False)
|
||||
)
|
||||
# InductorAdaptor (unfortunately) requires AOTAutogradCache
|
||||
# to be turned off to run. It will fail to acquire the hash_str
|
||||
# and error if not.
|
||||
# StandaloneInductorAdaptor (PyTorch 2.8+) fixes this problem.
|
||||
stack.enter_context(
|
||||
torch._functorch.config.patch(enable_autograd_cache=False)
|
||||
)
|
||||
stack.enter_context(
|
||||
torch._functorch.config.patch(enable_remote_autograd_cache=False)
|
||||
)
|
||||
|
||||
with pass_context(runtime_shape):
|
||||
compiled_graph = compile_fx(
|
||||
graph,
|
||||
example_inputs,
|
||||
inner_compile=hijacked_compile_fx_inner,
|
||||
config_patches=current_config,
|
||||
)
|
||||
return compiled_graph, (hash_str, file_path)
|
||||
|
||||
def load(
|
||||
self,
|
||||
handle: Any,
|
||||
graph: fx.GraphModule,
|
||||
example_inputs: list[Any],
|
||||
graph_index: int,
|
||||
runtime_shape: Optional[int] = None,
|
||||
) -> Callable:
|
||||
assert isinstance(handle, tuple)
|
||||
assert isinstance(handle[0], str)
|
||||
assert isinstance(handle[1], str)
|
||||
hash_str = handle[0]
|
||||
|
||||
from torch._functorch._aot_autograd.autograd_cache import AOTAutogradCache
|
||||
from torch._inductor.codecache import FxGraphCache
|
||||
|
||||
with ExitStack() as exit_stack:
|
||||
exit_stack.enter_context(
|
||||
patch(
|
||||
"torch._inductor.codecache.FxGraphCache._get_shape_env",
|
||||
lambda *args, **kwargs: AlwaysHitShapeEnv(),
|
||||
)
|
||||
)
|
||||
# torch 2.8+ on main uses _get_shape_env in AOTAutogradCache
|
||||
if hasattr(AOTAutogradCache, "_get_shape_env"):
|
||||
exit_stack.enter_context(
|
||||
patch(
|
||||
"torch._functorch._aot_autograd.autograd_cache.AOTAutogradCache._get_shape_env",
|
||||
lambda *args, **kwargs: AlwaysHitShapeEnv(),
|
||||
)
|
||||
)
|
||||
|
||||
# Dynamo metrics context, see method for more details.
|
||||
exit_stack.enter_context(self.metrics_context())
|
||||
|
||||
if torch_release[:2] == (2, 5):
|
||||
inductor_compiled_graph = FxGraphCache._lookup_graph(
|
||||
hash_str, example_inputs, True, False
|
||||
)
|
||||
assert inductor_compiled_graph is not None, (
|
||||
"Inductor cache lookup failed. Please remove"
|
||||
f"the cache directory and try again." # noqa
|
||||
)
|
||||
elif torch_release >= (2, 6):
|
||||
from torch._inductor.output_code import CompiledFxGraphConstantsWithGm
|
||||
|
||||
constants = CompiledFxGraphConstantsWithGm(graph)
|
||||
inductor_compiled_graph, _ = FxGraphCache._lookup_graph(
|
||||
hash_str, example_inputs, True, None, constants
|
||||
)
|
||||
assert inductor_compiled_graph is not None, (
|
||||
"Inductor cache lookup failed. Please remove"
|
||||
f"the cache directory and try again." # noqa
|
||||
)
|
||||
|
||||
# Inductor calling convention (function signature):
|
||||
# f(list) -> tuple
|
||||
# Dynamo calling convention (function signature):
|
||||
# f(*args) -> Any
|
||||
|
||||
# need to know if the graph returns a tuple
|
||||
from torch._inductor.compile_fx import graph_returns_tuple
|
||||
|
||||
returns_tuple = graph_returns_tuple(graph)
|
||||
|
||||
# this is the callable we return to Dynamo to run
|
||||
def compiled_graph(*args):
|
||||
# convert args to list
|
||||
list_args = list(args)
|
||||
graph_output = inductor_compiled_graph(list_args)
|
||||
# unpack the tuple if needed
|
||||
if returns_tuple:
|
||||
return graph_output
|
||||
else:
|
||||
return graph_output[0]
|
||||
|
||||
return compiled_graph
|
||||
|
||||
def metrics_context(self) -> contextlib.AbstractContextManager:
|
||||
"""
|
||||
This method returns the Dynamo metrics context (if it exists,
|
||||
otherwise a null context). It is used by various compile components.
|
||||
Present in torch>=2.6, it's used inside FxGraphCache in
|
||||
torch==2.6 (but not after). It might also be used in various other
|
||||
torch.compile internal functions.
|
||||
|
||||
Because it is re-entrant, we always set it (even if entering via Dynamo
|
||||
and the context was already entered). We might want to revisit if it
|
||||
should be set at a different level of compilation.
|
||||
|
||||
This is likely a bug in PyTorch: public APIs should not rely on
|
||||
manually setting up internal contexts. But we also rely on non-public
|
||||
APIs which might not provide these guarantees.
|
||||
"""
|
||||
import torch._dynamo.utils
|
||||
|
||||
return torch._dynamo.utils.get_metrics_context()
|
||||
|
||||
|
||||
def set_inductor_config(config, runtime_shape):
|
||||
if isinstance(runtime_shape, int):
|
||||
# for a specific batchsize, tuning triton kernel parameters
|
||||
# can be beneficial
|
||||
config["max_autotune"] = True
|
||||
config["coordinate_descent_tuning"] = True
|
||||
|
||||
|
||||
class EagerAdapter(CompilerInterface):
|
||||
name = "eager"
|
||||
|
||||
def compile(
|
||||
self,
|
||||
graph: fx.GraphModule,
|
||||
example_inputs: list[Any],
|
||||
compiler_config: dict[str, Any],
|
||||
runtime_shape: Optional[int] = None,
|
||||
key: Optional[str] = None,
|
||||
num_graphs: int = 1,
|
||||
) -> tuple[Optional[Callable], Optional[Any]]:
|
||||
return graph, None
|
||||
|
||||
def load(
|
||||
self,
|
||||
handle: Any,
|
||||
graph: fx.GraphModule,
|
||||
example_inputs: list[Any],
|
||||
graph_index: int,
|
||||
runtime_shape: Optional[int] = None,
|
||||
num_graphs: int = 1,
|
||||
) -> Callable:
|
||||
raise NotImplementedError("eager compilation is not supported")
|
||||
206
third_party/sglang/python/sglang/srt/compilation/cuda_piecewise_backend.py
vendored
Normal file
206
third_party/sglang/python/sglang/srt/compilation/cuda_piecewise_backend.py
vendored
Normal file
@@ -0,0 +1,206 @@
|
||||
# Adapted from https://github.com/vllm-project/vllm/blob/v0.10.0/vllm/compilation/cuda_piecewise_backend.py
|
||||
|
||||
import dataclasses
|
||||
import logging
|
||||
from contextlib import ExitStack
|
||||
from typing import Any, Callable, Optional
|
||||
from unittest.mock import patch
|
||||
|
||||
import torch
|
||||
import torch.fx as fx
|
||||
|
||||
from sglang.srt.compilation.compilation_config import CompilationConfig
|
||||
from sglang.srt.compilation.compilation_counter import compilation_counter
|
||||
from sglang.srt.compilation.piecewise_context_manager import (
|
||||
get_pcg_capture_stream,
|
||||
is_in_pcg_torch_compile,
|
||||
)
|
||||
from sglang.srt.compilation.weak_ref_tensor import weak_ref_tensors
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class ConcreteSizeEntry:
|
||||
runtime_shape: int
|
||||
need_to_compile: bool # the size is in compile_sizes
|
||||
use_cudagraph: bool # the size is in cudagraph_capture_sizes
|
||||
|
||||
compiled: bool = False
|
||||
runnable: Callable = None # type: ignore
|
||||
num_finished_warmup: int = 0
|
||||
cudagraph: Optional[torch.cuda.CUDAGraph] = None
|
||||
output: Optional[Any] = None
|
||||
|
||||
# for cudagraph debugging, track the input addresses
|
||||
# during capture, and check if they are the same during replay
|
||||
input_addresses: Optional[list[int]] = None
|
||||
|
||||
|
||||
class CUDAPiecewiseBackend:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
graph: fx.GraphModule,
|
||||
compile_config: CompilationConfig,
|
||||
inductor_config: dict[str, Any],
|
||||
graph_pool: Any,
|
||||
piecewise_compile_index: int,
|
||||
total_piecewise_compiles: int,
|
||||
sym_shape_indices: list[int],
|
||||
compiled_graph_for_general_shape: Callable,
|
||||
sglang_backend,
|
||||
):
|
||||
"""
|
||||
The backend for piecewise compilation.
|
||||
It mainly handles the compilation and cudagraph capturing.
|
||||
|
||||
We will compile `self.graph` once for the general shape,
|
||||
and then compile for different shapes specified in
|
||||
`compilation_config.compile_sizes`.
|
||||
|
||||
Independently, we will capture cudagraph for different shapes.
|
||||
|
||||
If a shape needs both compilation and cudagraph, we will
|
||||
compile it first, and then capture cudagraph.
|
||||
"""
|
||||
self.graph = graph
|
||||
self.inductor_config = inductor_config
|
||||
self.graph_pool = graph_pool
|
||||
self.piecewise_compile_index = piecewise_compile_index
|
||||
self.total_piecewise_compiles = total_piecewise_compiles
|
||||
self.sglang_backend = sglang_backend
|
||||
|
||||
self.is_first_graph = piecewise_compile_index == 0
|
||||
self.is_last_graph = piecewise_compile_index == total_piecewise_compiles - 1
|
||||
|
||||
self.compile_sizes: set[int] = set([])
|
||||
self.compile_config = compile_config
|
||||
self.cudagraph_capture_sizes: set[int] = set(compile_config.get_capture_sizes())
|
||||
|
||||
self.first_run_finished = False
|
||||
|
||||
self.compiled_graph_for_general_shape = compiled_graph_for_general_shape # noqa
|
||||
|
||||
self.sym_shape_indices = sym_shape_indices
|
||||
|
||||
# the entries for different shapes that we need to either
|
||||
# compile or capture cudagraph
|
||||
self.concrete_size_entries: dict[int, ConcreteSizeEntry] = {}
|
||||
|
||||
# to_be_compiled_sizes tracks the remaining sizes to compile,
|
||||
# and updates during the compilation process, so we need to copy it
|
||||
self.to_be_compiled_sizes: set[int] = self.compile_sizes.copy()
|
||||
for shape in self.compile_sizes.union(self.cudagraph_capture_sizes):
|
||||
self.concrete_size_entries[shape] = ConcreteSizeEntry(
|
||||
runtime_shape=shape,
|
||||
need_to_compile=shape in self.compile_sizes,
|
||||
use_cudagraph=shape in self.cudagraph_capture_sizes,
|
||||
)
|
||||
|
||||
def check_for_ending_compilation(self):
|
||||
if self.is_last_graph and not self.to_be_compiled_sizes:
|
||||
# no specific sizes to compile
|
||||
# save the hash of the inductor graph for the next run
|
||||
self.sglang_backend.compiler_manager.save_to_file()
|
||||
|
||||
def __call__(self, *args) -> Any:
|
||||
if not self.first_run_finished:
|
||||
self.first_run_finished = True
|
||||
self.check_for_ending_compilation()
|
||||
return self.compiled_graph_for_general_shape(*args)
|
||||
|
||||
if len(self.sym_shape_indices) == 0:
|
||||
return self.compiled_graph_for_general_shape(*args)
|
||||
|
||||
runtime_shape = args[self.sym_shape_indices[0]]
|
||||
if runtime_shape not in self.concrete_size_entries:
|
||||
# we don't need to do anything for this shape
|
||||
return self.compiled_graph_for_general_shape(*args)
|
||||
|
||||
entry = self.concrete_size_entries[runtime_shape]
|
||||
|
||||
if entry.runnable is None:
|
||||
entry.runnable = self.compiled_graph_for_general_shape
|
||||
|
||||
if entry.need_to_compile and not entry.compiled:
|
||||
entry.compiled = True
|
||||
self.to_be_compiled_sizes.remove(runtime_shape)
|
||||
# args are real arguments
|
||||
entry.runnable = self.sglang_backend.compiler_manager.compile(
|
||||
self.graph,
|
||||
args,
|
||||
self.inductor_config,
|
||||
graph_index=self.piecewise_compile_index,
|
||||
num_graphs=self.total_piecewise_compiles,
|
||||
runtime_shape=runtime_shape,
|
||||
)
|
||||
|
||||
# finished compilations for all required shapes
|
||||
if self.is_last_graph and not self.to_be_compiled_sizes:
|
||||
self.check_for_ending_compilation()
|
||||
|
||||
if is_in_pcg_torch_compile():
|
||||
return entry.runnable(*args)
|
||||
|
||||
if entry.cudagraph is None:
|
||||
if entry.num_finished_warmup < 1: # noqa
|
||||
entry.num_finished_warmup += 1
|
||||
return entry.runnable(*args)
|
||||
|
||||
if self.compile_config.get_enable_debug_mode():
|
||||
input_addresses = [
|
||||
x.data_ptr() for x in args if isinstance(x, torch.Tensor)
|
||||
]
|
||||
entry.input_addresses = input_addresses
|
||||
cudagraph = torch.cuda.CUDAGraph()
|
||||
|
||||
with ExitStack() as stack:
|
||||
if not self.is_first_graph:
|
||||
# during every model forward, we will capture
|
||||
# many pieces of cudagraphs (roughly one per layer).
|
||||
# running gc again and again across layers will
|
||||
# make the cudagraph capture very slow.
|
||||
# therefore, we only run gc for the first graph,
|
||||
# and disable gc for the rest of the graphs.
|
||||
stack.enter_context(patch("gc.collect", lambda: None))
|
||||
stack.enter_context(patch("torch.cuda.empty_cache", lambda: None))
|
||||
# mind-exploding: carefully manage the reference and memory.
|
||||
stream = get_pcg_capture_stream()
|
||||
assert (
|
||||
stream is not None
|
||||
), "PCG capture stream is not set, please check if runtime recompilation happened"
|
||||
with torch.cuda.graph(cudagraph, pool=self.graph_pool, stream=stream):
|
||||
# `output` is managed by pytorch's cudagraph pool
|
||||
output = entry.runnable(*args)
|
||||
if self.is_last_graph:
|
||||
# by converting it to weak ref,
|
||||
# the original `output` will immediately be released
|
||||
# to save memory. It is only safe to do this for
|
||||
# the last graph, because the output of the last graph
|
||||
# will not be used by any other cuda graph.
|
||||
output = weak_ref_tensors(output)
|
||||
|
||||
# here we always use weak ref for the output
|
||||
# to save memory
|
||||
entry.output = weak_ref_tensors(output)
|
||||
entry.cudagraph = cudagraph
|
||||
|
||||
compilation_counter.num_cudagraph_captured += 1
|
||||
|
||||
# important: we need to return the output, rather than
|
||||
# the weak ref of the output, so that pytorch can correctly
|
||||
# manage the memory during cuda graph capture
|
||||
return output
|
||||
|
||||
if self.compile_config.get_enable_debug_mode():
|
||||
# check if the input addresses are the same
|
||||
new_input_addresses = [
|
||||
x.data_ptr() for x in args if isinstance(x, torch.Tensor)
|
||||
]
|
||||
assert new_input_addresses == entry.input_addresses, (
|
||||
"Input addresses for cudagraphs are different during replay."
|
||||
f" Expected {entry.input_addresses}, got {new_input_addresses}"
|
||||
)
|
||||
entry.cudagraph.replay()
|
||||
return entry.output
|
||||
134
third_party/sglang/python/sglang/srt/compilation/fix_functionalization.py
vendored
Normal file
134
third_party/sglang/python/sglang/srt/compilation/fix_functionalization.py
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
# Adapted from https://github.com/vllm-project/vllm/blob/v0.10.0/vllm/compilation/fix_functionalization.py
|
||||
|
||||
import logging
|
||||
import operator
|
||||
from collections.abc import Iterable
|
||||
from typing import Optional, Union
|
||||
|
||||
import torch
|
||||
from torch._higher_order_ops.auto_functionalize import auto_functionalized
|
||||
|
||||
from sglang.srt.compilation.fx_utils import is_func
|
||||
from sglang.srt.compilation.inductor_pass import SGLangInductorPass
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FixFunctionalizationPass(SGLangInductorPass):
|
||||
"""
|
||||
This pass defunctionalizes certain nodes to avoid redundant tensor copies.
|
||||
After this pass, DCE (dead-code elimination) should never be run,
|
||||
as de-functionalized nodes may appear as dead code.
|
||||
|
||||
To add new nodes to defunctionalize, add to the if-elif chain in __call__.
|
||||
"""
|
||||
|
||||
def __call__(self, graph: torch.fx.Graph):
|
||||
self.begin()
|
||||
self.dump_graph(graph, "before_fix_functionalization")
|
||||
|
||||
self.nodes_to_remove: list[torch.fx.Node] = []
|
||||
count = 0
|
||||
for node in graph.nodes:
|
||||
if not is_func(node, auto_functionalized):
|
||||
continue # Avoid deep if-elif nesting
|
||||
count += 1
|
||||
|
||||
self.dump_graph(graph, "before_fix_functionalization_cleanup")
|
||||
|
||||
# Remove the nodes all at once
|
||||
count_removed = len(self.nodes_to_remove)
|
||||
for node in self.nodes_to_remove:
|
||||
graph.erase_node(node)
|
||||
|
||||
logger.debug(
|
||||
"De-functionalized %s nodes, removed %s nodes", count, count_removed
|
||||
)
|
||||
self.dump_graph(graph, "after_fix_functionalization")
|
||||
self.end_and_log()
|
||||
|
||||
def _remove(self, node_or_nodes: Union[torch.fx.Node, Iterable[torch.fx.Node]]):
|
||||
"""
|
||||
Stage a node (or nodes) for removal at the end of the pass.
|
||||
"""
|
||||
if isinstance(node_or_nodes, torch.fx.Node):
|
||||
self.nodes_to_remove.append(node_or_nodes)
|
||||
else:
|
||||
self.nodes_to_remove.extend(node_or_nodes)
|
||||
|
||||
def defunctionalize(
|
||||
self,
|
||||
graph: torch.fx.Graph,
|
||||
node: torch.fx.Node,
|
||||
mutated_args: dict[int, Union[torch.fx.Node, str]],
|
||||
args: Optional[tuple[Union[torch.fx.Node, str], ...]] = None,
|
||||
):
|
||||
"""
|
||||
De-functionalize a node by replacing it with a call to the original.
|
||||
It also replaces the getitem users with the mutated arguments.
|
||||
See replace_users_with_mutated_args and insert_defunctionalized.
|
||||
"""
|
||||
self.replace_users_with_mutated_args(node, mutated_args)
|
||||
self.insert_defunctionalized(graph, node, args=args)
|
||||
self._remove(node)
|
||||
|
||||
def replace_users_with_mutated_args(
|
||||
self, node: torch.fx.Node, mutated_args: dict[int, Union[torch.fx.Node, str]]
|
||||
):
|
||||
"""
|
||||
Replace all getitem users of the auto-functionalized node with the
|
||||
mutated arguments.
|
||||
:param node: The auto-functionalized node
|
||||
:param mutated_args: The mutated arguments, indexed by getitem index.
|
||||
If the value of an arg is a string, `node.kwargs[arg]` is used.
|
||||
"""
|
||||
for idx, user in self.getitem_users(node).items():
|
||||
arg = mutated_args[idx]
|
||||
arg = node.kwargs[arg] if isinstance(arg, str) else arg
|
||||
user.replace_all_uses_with(arg)
|
||||
self._remove(user)
|
||||
|
||||
def getitem_users(self, node: torch.fx.Node) -> dict[int, torch.fx.Node]:
|
||||
"""
|
||||
Returns the operator.getitem users of the auto-functionalized node,
|
||||
indexed by the index they are getting.
|
||||
"""
|
||||
users = {}
|
||||
for user in node.users:
|
||||
if is_func(user, operator.getitem):
|
||||
idx = user.args[1]
|
||||
users[idx] = user
|
||||
return users
|
||||
|
||||
def insert_defunctionalized(
|
||||
self,
|
||||
graph: torch.fx.Graph,
|
||||
node: torch.fx.Node,
|
||||
args: Optional[tuple[Union[torch.fx.Node, str], ...]] = None,
|
||||
):
|
||||
"""
|
||||
Insert a new defunctionalized node into the graph before node.
|
||||
If one of the kwargs is 'out', provide args directly,
|
||||
as node.kwargs cannot be used.
|
||||
See https://github.com/pytorch/pytorch/blob/a00faf440888ffb724bad413f329a49e2b6388e7/torch/_inductor/lowering.py#L351
|
||||
|
||||
:param graph: Graph to insert the defunctionalized node into
|
||||
:param node: The auto-functionalized node to defunctionalize
|
||||
:param args: If we cannot use kwargs, specify args directly.
|
||||
If an arg is a string, `node.kwargs[arg]` is used.
|
||||
""" # noqa: E501
|
||||
assert is_func(
|
||||
node, auto_functionalized
|
||||
), f"node must be auto-functionalized, is {node} instead"
|
||||
|
||||
# Create a new call to the original function
|
||||
with graph.inserting_before(node):
|
||||
function = node.args[0]
|
||||
if args is None:
|
||||
graph.call_function(function, kwargs=node.kwargs)
|
||||
else:
|
||||
# Args passed as strings refer to items in node.kwargs
|
||||
args = tuple(
|
||||
node.kwargs[arg] if isinstance(arg, str) else arg for arg in args
|
||||
)
|
||||
graph.call_function(function, args=args)
|
||||
83
third_party/sglang/python/sglang/srt/compilation/fx_utils.py
vendored
Normal file
83
third_party/sglang/python/sglang/srt/compilation/fx_utils.py
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
# Adapted from https://github.com/vllm-project/vllm/blob/v0.10.0/vllm/compilation/fx_utils.py
|
||||
|
||||
import operator
|
||||
from collections.abc import Iterable, Iterator
|
||||
from typing import Optional
|
||||
|
||||
from torch import fx
|
||||
from torch._higher_order_ops.auto_functionalize import auto_functionalized
|
||||
from torch._ops import OpOverload
|
||||
|
||||
|
||||
def is_func(node: fx.Node, target) -> bool:
|
||||
return node.op == "call_function" and node.target == target
|
||||
|
||||
|
||||
def is_auto_func(node: fx.Node, op: OpOverload) -> bool:
|
||||
return is_func(node, auto_functionalized) and node.args[0] == op
|
||||
|
||||
|
||||
# Returns the first specified node with the given op (if it exists)
|
||||
def find_specified_fn_maybe(
|
||||
nodes: Iterable[fx.Node], op: OpOverload
|
||||
) -> Optional[fx.Node]:
|
||||
for node in nodes:
|
||||
if node.target == op:
|
||||
return node
|
||||
return None
|
||||
|
||||
|
||||
# Returns the first specified node with the given op
|
||||
def find_specified_fn(nodes: Iterable[fx.Node], op: OpOverload) -> fx.Node:
|
||||
node = find_specified_fn_maybe(nodes, op)
|
||||
assert node is not None, f"Could not find {op} in nodes {nodes}"
|
||||
return node
|
||||
|
||||
|
||||
# Returns the first auto_functionalized node with the given op (if it exists)
|
||||
def find_auto_fn_maybe(nodes: Iterable[fx.Node], op: OpOverload) -> Optional[fx.Node]:
|
||||
for node in nodes:
|
||||
if is_func(node, auto_functionalized) and node.args[0] == op: # noqa
|
||||
return node
|
||||
return None
|
||||
|
||||
|
||||
# Returns the first auto_functionalized node with the given op
|
||||
def find_auto_fn(nodes: Iterable[fx.Node], op: OpOverload) -> fx.Node:
|
||||
node = find_auto_fn_maybe(nodes, op)
|
||||
assert node is not None, f"Could not find {op} in nodes {nodes}"
|
||||
return node
|
||||
|
||||
|
||||
# Returns the getitem node that extracts the idx-th element from node
|
||||
# (if it exists)
|
||||
def find_getitem_maybe(node: fx.Node, idx: int) -> Optional[fx.Node]:
|
||||
for user in node.users:
|
||||
if is_func(user, operator.getitem) and user.args[1] == idx:
|
||||
return user
|
||||
return None
|
||||
|
||||
|
||||
# Returns the getitem node that extracts the idx-th element from node
|
||||
def find_getitem(node: fx.Node, idx: int) -> fx.Node:
|
||||
ret = find_getitem_maybe(node, idx)
|
||||
assert ret is not None, f"Could not find getitem {idx} in node {node}"
|
||||
return ret
|
||||
|
||||
|
||||
# An auto-functionalization-aware utility for finding nodes with a specific op
|
||||
def find_op_nodes(op: OpOverload, graph: fx.Graph) -> Iterator[fx.Node]:
|
||||
if not op._schema.is_mutable:
|
||||
yield from graph.find_nodes(op="call_function", target=op)
|
||||
|
||||
for n in graph.find_nodes(op="call_function", target=auto_functionalized):
|
||||
if n.args[0] == op:
|
||||
yield n
|
||||
|
||||
|
||||
# Asserts that the node only has one user and returns it
|
||||
# Even if a node has only 1 user, it might share storage with another node,
|
||||
# which might need to be taken into account.
|
||||
def get_only_user(node: fx.Node) -> fx.Node:
|
||||
assert len(node.users) == 1
|
||||
return next(iter(node.users))
|
||||
140
third_party/sglang/python/sglang/srt/compilation/inductor_pass.py
vendored
Normal file
140
third_party/sglang/python/sglang/srt/compilation/inductor_pass.py
vendored
Normal file
@@ -0,0 +1,140 @@
|
||||
# Adapted from https://github.com/vllm-project/vllm/blob/v0.10.0/vllm/compilation/inductor_pass.py
|
||||
|
||||
import hashlib
|
||||
import inspect
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
import types
|
||||
from contextlib import contextmanager
|
||||
from typing import Any, Callable, Optional, Union
|
||||
|
||||
import torch
|
||||
from torch import fx
|
||||
from torch._dynamo.utils import lazy_format_graph_code
|
||||
from torch._inductor.custom_graph_pass import CustomGraphPass
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_pass_context = None
|
||||
|
||||
|
||||
class PassContext:
|
||||
|
||||
def __init__(self, runtime_shape: Optional[int]):
|
||||
self.runtime_shape = runtime_shape
|
||||
|
||||
|
||||
def get_pass_context() -> PassContext:
|
||||
"""Get the current pass context."""
|
||||
assert _pass_context is not None
|
||||
return _pass_context
|
||||
|
||||
|
||||
@contextmanager
|
||||
def pass_context(runtime_shape: Optional[int]):
|
||||
"""A context manager that stores the current pass context,
|
||||
usually it is a list of sizes to specialize.
|
||||
"""
|
||||
global _pass_context
|
||||
prev_context = _pass_context
|
||||
_pass_context = PassContext(runtime_shape)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
_pass_context = prev_context
|
||||
|
||||
|
||||
class InductorPass(CustomGraphPass):
|
||||
"""
|
||||
A custom graph pass that uses a hash of its source as the UUID.
|
||||
This is defined as a convenience and should work in most cases.
|
||||
"""
|
||||
|
||||
def uuid(self) -> Any:
|
||||
"""
|
||||
Provide a unique identifier for the pass, used in Inductor code cache.
|
||||
This should depend on the pass implementation, so that changes to the
|
||||
pass result in recompilation.
|
||||
By default, the object source is hashed.
|
||||
"""
|
||||
return InductorPass.hash_source(self)
|
||||
|
||||
@staticmethod
|
||||
def hash_source(*srcs: Union[str, Any]):
|
||||
"""
|
||||
Utility method to hash the sources of functions or objects.
|
||||
:param srcs: strings or objects to add to the hash.
|
||||
Objects and functions have their source inspected.
|
||||
:return:
|
||||
"""
|
||||
hasher = hashlib.sha256()
|
||||
for src in srcs:
|
||||
if isinstance(src, str):
|
||||
src_str = src
|
||||
elif isinstance(src, types.FunctionType):
|
||||
src_str = inspect.getsource(src)
|
||||
else:
|
||||
src_str = inspect.getsource(src.__class__)
|
||||
hasher.update(src_str.encode("utf-8"))
|
||||
return hasher.hexdigest()
|
||||
|
||||
@staticmethod
|
||||
def hash_dict(dict_: dict[Any, Any]):
|
||||
"""
|
||||
Utility method to hash a dictionary, can alternatively be used for uuid.
|
||||
:return: A sha256 hash of the json rep of the dictionary.
|
||||
"""
|
||||
encoded = json.dumps(dict_, sort_keys=True).encode("utf-8")
|
||||
return hashlib.sha256(encoded).hexdigest()
|
||||
|
||||
def is_applicable_for_shape(self, shape: Optional[int]):
|
||||
return True
|
||||
|
||||
|
||||
class CallableInductorPass(InductorPass):
|
||||
"""
|
||||
This class is a wrapper for a callable that automatically provides an
|
||||
implementation of the UUID.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, callable: Callable[[fx.Graph], None], uuid: Optional[Any] = None
|
||||
):
|
||||
self.callable = callable
|
||||
self._uuid = self.hash_source(callable) if uuid is None else uuid
|
||||
|
||||
def __call__(self, graph: torch.fx.Graph):
|
||||
self.callable(graph)
|
||||
|
||||
def uuid(self) -> Any:
|
||||
return self._uuid
|
||||
|
||||
|
||||
class SGLangInductorPass(InductorPass):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
):
|
||||
self.pass_name = self.__class__.__name__
|
||||
|
||||
def dump_graph(self, graph: torch.fx.Graph, stage: str):
|
||||
lazy_format_graph_code(stage, graph.owning_module)
|
||||
|
||||
def begin(self):
|
||||
self._start_time = time.perf_counter_ns()
|
||||
|
||||
def end_and_log(self):
|
||||
self._end_time = time.perf_counter_ns()
|
||||
duration_ms = float(self._end_time - self._start_time) / 1.0e6
|
||||
logger.debug("%s completed in %.1f ms", self.pass_name, duration_ms)
|
||||
|
||||
|
||||
class PrinterInductorPass(SGLangInductorPass):
|
||||
|
||||
def __init__(self, name: str):
|
||||
super().__init__()
|
||||
self.name = name
|
||||
|
||||
def __call__(self, graph: torch.fx.Graph):
|
||||
self.dump_graph(graph, self.name)
|
||||
109
third_party/sglang/python/sglang/srt/compilation/npu_piecewise_backend.py
vendored
Normal file
109
third_party/sglang/python/sglang/srt/compilation/npu_piecewise_backend.py
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
from contextlib import ExitStack
|
||||
from typing import Any, Callable
|
||||
from unittest.mock import patch
|
||||
|
||||
import torch
|
||||
import torch.fx as fx
|
||||
|
||||
from sglang.srt.compilation.compilation_config import CompilationConfig
|
||||
from sglang.srt.compilation.compilation_counter import compilation_counter
|
||||
from sglang.srt.compilation.cuda_piecewise_backend import (
|
||||
CUDAPiecewiseBackend,
|
||||
weak_ref_tensors,
|
||||
)
|
||||
|
||||
|
||||
class NPUPiecewiseBackend(CUDAPiecewiseBackend):
|
||||
def __init__(
|
||||
self,
|
||||
graph: fx.GraphModule,
|
||||
compile_config: CompilationConfig,
|
||||
inductor_config: dict[str, Any],
|
||||
graph_pool: Any,
|
||||
piecewise_compile_index: int,
|
||||
total_piecewise_compiles: int,
|
||||
sym_shape_indices: list[int],
|
||||
compiled_graph_for_general_shape: Callable,
|
||||
sglang_backend,
|
||||
):
|
||||
super().__init__(
|
||||
graph,
|
||||
compile_config,
|
||||
inductor_config,
|
||||
graph_pool,
|
||||
piecewise_compile_index,
|
||||
total_piecewise_compiles,
|
||||
sym_shape_indices,
|
||||
compiled_graph_for_general_shape,
|
||||
sglang_backend,
|
||||
)
|
||||
|
||||
def __call__(self, *args):
|
||||
runtime_shape = args[self.sym_shape_indices[0]]
|
||||
if runtime_shape not in self.concrete_size_entries:
|
||||
# we don't need to do anything for this shape
|
||||
return self.compiled_graph_for_general_shape(*args)
|
||||
|
||||
entry = self.concrete_size_entries[runtime_shape]
|
||||
|
||||
if entry.runnable is None:
|
||||
entry.runnable = self.compiled_graph_for_general_shape
|
||||
|
||||
if entry.cudagraph is None:
|
||||
if entry.num_finished_warmup < 1: # noqa
|
||||
entry.num_finished_warmup += 1
|
||||
return entry.runnable(*args)
|
||||
|
||||
if self.compile_config.get_enable_debug_mode():
|
||||
input_addresses = [
|
||||
x.data_ptr() for x in args if isinstance(x, torch.Tensor)
|
||||
]
|
||||
entry.input_addresses = input_addresses
|
||||
npugraph = torch.npu.NPUGraph()
|
||||
|
||||
with ExitStack() as stack:
|
||||
if not self.is_first_graph:
|
||||
# during every model forward, we will capture
|
||||
# many pieces of cudagraphs (roughly one per layer).
|
||||
# running gc again and again across layers will
|
||||
# make the cudagraph capture very slow.
|
||||
# therefore, we only run gc for the first graph,
|
||||
# and disable gc for the rest of the graphs.
|
||||
stack.enter_context(patch("gc.collect", lambda: None))
|
||||
stack.enter_context(patch("torch.npu.empty_cache", lambda: None))
|
||||
|
||||
# mind-exploding: carefully manage the reference and memory.
|
||||
with torch.npu.graph(npugraph, pool=self.graph_pool):
|
||||
# `output` is managed by pytorch's cudagraph pool
|
||||
output = entry.runnable(*args)
|
||||
if self.is_last_graph:
|
||||
# by converting it to weak ref,
|
||||
# the original `output` will immediately be released
|
||||
# to save memory. It is only safe to do this for
|
||||
# the last graph, because the output of the last graph
|
||||
# will not be used by any other cuda graph.
|
||||
output = weak_ref_tensors(output)
|
||||
|
||||
# here we always use weak ref for the output
|
||||
# to save memory
|
||||
entry.output = weak_ref_tensors(output)
|
||||
entry.cudagraph = npugraph
|
||||
|
||||
compilation_counter.num_cudagraph_captured += 1
|
||||
|
||||
# important: we need to return the output, rather than
|
||||
# the weak ref of the output, so that pytorch can correctly
|
||||
# manage the memory during cuda graph capture
|
||||
return output
|
||||
|
||||
if self.compile_config.get_enable_debug_mode():
|
||||
# check if the input addresses are the same
|
||||
new_input_addresses = [
|
||||
x.data_ptr() for x in args if isinstance(x, torch.Tensor)
|
||||
]
|
||||
assert new_input_addresses == entry.input_addresses, (
|
||||
"Input addresses for cudagraphs are different during replay."
|
||||
f" Expected {entry.input_addresses}, got {new_input_addresses}"
|
||||
)
|
||||
entry.cudagraph.replay()
|
||||
return entry.output
|
||||
66
third_party/sglang/python/sglang/srt/compilation/pass_manager.py
vendored
Normal file
66
third_party/sglang/python/sglang/srt/compilation/pass_manager.py
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
# Adapted from https://github.com/vllm-project/vllm/blob/v0.10.0/vllm/compilation/pass_manager.py
|
||||
|
||||
import logging
|
||||
|
||||
from torch import fx as fx
|
||||
|
||||
from sglang.srt.compilation.fix_functionalization import FixFunctionalizationPass
|
||||
from sglang.srt.compilation.inductor_pass import (
|
||||
CustomGraphPass,
|
||||
InductorPass,
|
||||
SGLangInductorPass,
|
||||
get_pass_context,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PostGradPassManager(CustomGraphPass):
|
||||
"""
|
||||
The pass manager for post-grad passes.
|
||||
It handles configuration, adding custom passes, and running passes.
|
||||
It supports uuid for the Inductor code cache. That includes torch<2.6
|
||||
support using pickling (in .inductor_pass.CustomGraphPass).
|
||||
|
||||
The order of the post-grad post-passes is:
|
||||
1. passes (constructor parameter)
|
||||
2. default passes (NoopEliminationPass, FusionPass)
|
||||
3. config["post_grad_custom_post_pass"] (if it exists)
|
||||
4. fix_functionalization
|
||||
This way, all passes operate on a functionalized graph.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.passes: list[SGLangInductorPass] = []
|
||||
|
||||
def __call__(self, graph: fx.Graph):
|
||||
shape = get_pass_context().runtime_shape
|
||||
for pass_ in self.passes:
|
||||
if pass_.is_applicable_for_shape(shape):
|
||||
pass_(graph)
|
||||
|
||||
# always run fix_functionalization last
|
||||
self.fix_functionalization(graph)
|
||||
|
||||
def configure(
|
||||
self,
|
||||
):
|
||||
self.pass_config = dict()
|
||||
self.fix_functionalization = FixFunctionalizationPass()
|
||||
|
||||
def add(self, pass_: InductorPass):
|
||||
assert isinstance(pass_, InductorPass)
|
||||
self.passes.append(pass_)
|
||||
|
||||
def uuid(self):
|
||||
"""
|
||||
The PostGradPassManager is set as a custom pass in the Inductor and
|
||||
affects compilation caching. Its uuid depends on the UUIDs of all
|
||||
dependent passes and the pass config. See InductorPass for more info.
|
||||
"""
|
||||
pass_manager_uuid = "fshdakhsa"
|
||||
state = {"pass_config": pass_manager_uuid, "passes": []}
|
||||
for pass_ in self.passes:
|
||||
state["passes"].append(pass_.uuid())
|
||||
state["passes"].append(self.fix_functionalization.uuid())
|
||||
return InductorPass.hash_dict(state)
|
||||
128
third_party/sglang/python/sglang/srt/compilation/piecewise_context_manager.py
vendored
Normal file
128
third_party/sglang/python/sglang/srt/compilation/piecewise_context_manager.py
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from contextlib import contextmanager
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Any, List, Optional
|
||||
|
||||
import torch
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sglang.srt.model_executor.forward_batch_info import ForwardBatch
|
||||
|
||||
_in_piecewise_cuda_graph = False
|
||||
_in_pcg_torch_compile = False
|
||||
_pcg_capture_stream = None
|
||||
|
||||
|
||||
def is_in_piecewise_cuda_graph():
|
||||
return _in_piecewise_cuda_graph
|
||||
|
||||
|
||||
def is_in_pcg_torch_compile():
|
||||
return _in_pcg_torch_compile
|
||||
|
||||
|
||||
def get_pcg_capture_stream():
|
||||
return _pcg_capture_stream
|
||||
|
||||
|
||||
@contextmanager
|
||||
def enable_piecewise_cuda_graph_compile():
|
||||
global _in_pcg_torch_compile
|
||||
_in_pcg_torch_compile = True
|
||||
yield
|
||||
_in_pcg_torch_compile = False
|
||||
|
||||
|
||||
@contextmanager
|
||||
def enable_piecewise_cuda_graph():
|
||||
global _in_piecewise_cuda_graph
|
||||
_in_piecewise_cuda_graph = True
|
||||
try:
|
||||
yield
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
"Piecewise CUDA Graph failed with error: %s\n%s",
|
||||
e,
|
||||
PIECEWISE_CUDA_GRAPH_CAPTURE_FAILED_MSG,
|
||||
)
|
||||
raise
|
||||
finally:
|
||||
_in_piecewise_cuda_graph = False
|
||||
|
||||
|
||||
@contextmanager
|
||||
def set_pcg_capture_stream(stream: torch.cuda.Stream):
|
||||
global _pcg_capture_stream
|
||||
_pcg_capture_stream = stream
|
||||
yield
|
||||
_pcg_capture_stream = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class ForwardContext:
|
||||
def __init__(self):
|
||||
self.forward_batch = None
|
||||
self.attention_layers = None
|
||||
self.quant_config = None
|
||||
self.moe_layers = None
|
||||
self.moe_fusions = None
|
||||
self.num_tokens: Optional[int] = None
|
||||
|
||||
def set_forward_batch(self, forward_batch: ForwardBatch):
|
||||
self.forward_batch = forward_batch
|
||||
|
||||
def set_attention_layers(self, layers: List[Any]):
|
||||
self.attention_layers = layers
|
||||
|
||||
def set_quant_config(self, quant_config: Any):
|
||||
self.quant_config = quant_config
|
||||
|
||||
def set_moe_layers(self, layers: List[Any]):
|
||||
self.moe_layers = layers
|
||||
|
||||
def set_moe_fusions(self, fusions: List[Any]):
|
||||
self.moe_fusions = fusions
|
||||
|
||||
|
||||
_forward_context: Optional[ForwardContext] = None
|
||||
|
||||
|
||||
def get_forward_context() -> Optional[ForwardContext]:
|
||||
if _forward_context is None:
|
||||
return None
|
||||
return _forward_context
|
||||
|
||||
|
||||
@contextmanager
|
||||
def set_forward_context(
|
||||
forward_batch: ForwardBatch,
|
||||
attention_layers: List[Any],
|
||||
quant_config: Any,
|
||||
moe_layers: List[Any],
|
||||
moe_fusions: List[Any],
|
||||
num_tokens: Optional[int] = None,
|
||||
):
|
||||
global _forward_context
|
||||
_forward_context = ForwardContext()
|
||||
_forward_context.set_forward_batch(forward_batch)
|
||||
_forward_context.set_attention_layers(attention_layers)
|
||||
_forward_context.set_quant_config(quant_config)
|
||||
_forward_context.set_moe_layers(moe_layers)
|
||||
_forward_context.set_moe_fusions(moe_fusions)
|
||||
_forward_context.num_tokens = num_tokens
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
_forward_context = None
|
||||
|
||||
|
||||
PIECEWISE_CUDA_GRAPH_CAPTURE_FAILED_MSG = (
|
||||
"Piecewise CUDA Graph is enabled by default as an experimental feature.\n"
|
||||
"To work around this error, add --disable-piecewise-cuda-graph to your launch command.\n"
|
||||
"Please report this issue at https://github.com/sgl-project/sglang/issues/new/choose"
|
||||
)
|
||||
28
third_party/sglang/python/sglang/srt/compilation/weak_ref_tensor.py
vendored
Normal file
28
third_party/sglang/python/sglang/srt/compilation/weak_ref_tensor.py
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
from typing import Any, Union
|
||||
|
||||
import torch
|
||||
|
||||
from sglang.srt.utils.common import is_cuda, is_hip, is_musa, is_npu
|
||||
|
||||
if is_cuda() or is_hip() or is_musa():
|
||||
from sgl_kernel import weak_ref_tensor
|
||||
elif is_npu():
|
||||
from torch_npu._C import _weak_ref_tensor as weak_ref_tensor
|
||||
else:
|
||||
raise NotImplementedError("weak_ref_tensor is implemented only for CUDA and NPU.")
|
||||
|
||||
|
||||
def weak_ref_tensors(
|
||||
tensors: Union[torch.Tensor, list[torch.Tensor], tuple[torch.Tensor]],
|
||||
) -> Union[torch.Tensor, list[Any], tuple[Any], Any]:
|
||||
"""
|
||||
Convenience function to create weak references to tensors,
|
||||
for single tensor, list of tensors or tuple of tensors.
|
||||
"""
|
||||
if isinstance(tensors, torch.Tensor):
|
||||
return weak_ref_tensor(tensors)
|
||||
if isinstance(tensors, list):
|
||||
return [weak_ref_tensor(t) for t in tensors]
|
||||
if isinstance(tensors, tuple):
|
||||
return tuple(weak_ref_tensor(t) for t in tensors)
|
||||
raise ValueError("Invalid type for tensors")
|
||||
66
third_party/sglang/python/sglang/srt/configs/__init__.py
vendored
Normal file
66
third_party/sglang/python/sglang/srt/configs/__init__.py
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
from sglang.srt.configs.afmoe import AfmoeConfig
|
||||
from sglang.srt.configs.bailing_hybrid import BailingHybridConfig
|
||||
from sglang.srt.configs.chatglm import ChatGLMConfig
|
||||
from sglang.srt.configs.dbrx import DbrxConfig
|
||||
from sglang.srt.configs.deepseekvl2 import DeepseekVL2Config
|
||||
from sglang.srt.configs.dots_ocr import DotsOCRConfig
|
||||
from sglang.srt.configs.dots_vlm import DotsVLMConfig
|
||||
from sglang.srt.configs.exaone import ExaoneConfig
|
||||
from sglang.srt.configs.falcon_h1 import FalconH1Config
|
||||
from sglang.srt.configs.granitemoehybrid import GraniteMoeHybridConfig
|
||||
from sglang.srt.configs.janus_pro import MultiModalityConfig
|
||||
from sglang.srt.configs.jet_nemotron import JetNemotronConfig
|
||||
from sglang.srt.configs.jet_vlm import JetVLMConfig
|
||||
from sglang.srt.configs.kimi_k25 import KimiK25Config
|
||||
from sglang.srt.configs.kimi_linear import KimiLinearConfig
|
||||
from sglang.srt.configs.kimi_vl import KimiVLConfig
|
||||
from sglang.srt.configs.kimi_vl_moonvit import MoonViTConfig
|
||||
from sglang.srt.configs.lfm2 import Lfm2Config
|
||||
from sglang.srt.configs.lfm2_moe import Lfm2MoeConfig
|
||||
from sglang.srt.configs.lfm2_vl import Lfm2VlConfig
|
||||
from sglang.srt.configs.longcat_flash import LongcatFlashConfig
|
||||
from sglang.srt.configs.nano_nemotron_vl import NemotronH_Nano_VL_V2_Config
|
||||
from sglang.srt.configs.nemotron_h import NemotronHConfig
|
||||
from sglang.srt.configs.olmo3 import Olmo3Config
|
||||
from sglang.srt.configs.qwen3_5 import Qwen3_5Config, Qwen3_5MoeConfig
|
||||
from sglang.srt.configs.qwen3_next import Qwen3NextConfig
|
||||
from sglang.srt.configs.step3_vl import (
|
||||
Step3TextConfig,
|
||||
Step3VisionEncoderConfig,
|
||||
Step3VLConfig,
|
||||
)
|
||||
from sglang.srt.configs.step3p5 import Step3p5Config
|
||||
|
||||
__all__ = [
|
||||
"AfmoeConfig",
|
||||
"BailingHybridConfig",
|
||||
"ExaoneConfig",
|
||||
"ChatGLMConfig",
|
||||
"DbrxConfig",
|
||||
"DeepseekVL2Config",
|
||||
"LongcatFlashConfig",
|
||||
"MultiModalityConfig",
|
||||
"KimiVLConfig",
|
||||
"MoonViTConfig",
|
||||
"Step3VLConfig",
|
||||
"Step3TextConfig",
|
||||
"Step3VisionEncoderConfig",
|
||||
"Olmo3Config",
|
||||
"KimiLinearConfig",
|
||||
"KimiK25Config",
|
||||
"Qwen3NextConfig",
|
||||
"Qwen3_5Config",
|
||||
"Qwen3_5MoeConfig",
|
||||
"DotsVLMConfig",
|
||||
"DotsOCRConfig",
|
||||
"FalconH1Config",
|
||||
"GraniteMoeHybridConfig",
|
||||
"Lfm2Config",
|
||||
"Lfm2MoeConfig",
|
||||
"Lfm2VlConfig",
|
||||
"NemotronHConfig",
|
||||
"NemotronH_Nano_VL_V2_Config",
|
||||
"JetNemotronConfig",
|
||||
"JetVLMConfig",
|
||||
"Step3p5Config",
|
||||
]
|
||||
102
third_party/sglang/python/sglang/srt/configs/afmoe.py
vendored
Normal file
102
third_party/sglang/python/sglang/srt/configs/afmoe.py
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
from typing import List, Optional
|
||||
|
||||
from transformers import PretrainedConfig
|
||||
|
||||
|
||||
class AfmoeConfig(PretrainedConfig):
|
||||
model_type = "afmoe"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vocab_size: int = 32000,
|
||||
hidden_size: int = 4096,
|
||||
intermediate_size: int = 11008,
|
||||
moe_intermediate_size: int = 256,
|
||||
num_hidden_layers: int = 32,
|
||||
num_attention_heads: int = 32,
|
||||
num_key_value_heads: Optional[int] = None,
|
||||
head_dim: Optional[int] = None,
|
||||
hidden_act: str = "silu",
|
||||
max_position_embeddings: int = 131072,
|
||||
initializer_range: float = 0.02,
|
||||
rms_norm_eps: float = 1e-5,
|
||||
use_cache: bool = True,
|
||||
pad_token_id: Optional[int] = None,
|
||||
bos_token_id: int = 1,
|
||||
eos_token_id: int = 2,
|
||||
tie_word_embeddings: bool = False,
|
||||
rope_theta: float = 10000.0,
|
||||
rope_scaling: Optional[dict] = None,
|
||||
attention_bias: bool = False,
|
||||
attention_dropout: float = 0.0,
|
||||
# MoE parameters
|
||||
num_experts: Optional[int] = None,
|
||||
num_experts_per_tok: Optional[int] = None,
|
||||
num_shared_experts: int = 0,
|
||||
num_dense_layers: int = 0,
|
||||
# Routing parameters
|
||||
score_func: str = "sigmoid",
|
||||
route_norm: bool = True,
|
||||
route_scale: float = 1.0,
|
||||
n_group: int = 1,
|
||||
topk_group: int = 1,
|
||||
# Attention parameters
|
||||
sliding_window: Optional[int] = None,
|
||||
layer_types: Optional[List[str]] = None,
|
||||
global_attn_every_n_layers: int = 4,
|
||||
# muP scaling
|
||||
mup_enabled: bool = False,
|
||||
**kwargs,
|
||||
):
|
||||
self.vocab_size = vocab_size
|
||||
self.max_position_embeddings = max_position_embeddings
|
||||
self.hidden_size = hidden_size
|
||||
self.intermediate_size = intermediate_size
|
||||
self.moe_intermediate_size = moe_intermediate_size
|
||||
self.num_hidden_layers = num_hidden_layers
|
||||
self.num_attention_heads = num_attention_heads
|
||||
|
||||
if num_key_value_heads is None:
|
||||
num_key_value_heads = num_attention_heads
|
||||
|
||||
self.num_key_value_heads = num_key_value_heads
|
||||
self.head_dim = (
|
||||
head_dim if head_dim is not None else hidden_size // num_attention_heads
|
||||
)
|
||||
self.hidden_act = hidden_act
|
||||
self.initializer_range = initializer_range
|
||||
self.rms_norm_eps = rms_norm_eps
|
||||
self.use_cache = use_cache
|
||||
self.rope_theta = rope_theta
|
||||
self.rope_scaling = rope_scaling
|
||||
self.attention_bias = attention_bias
|
||||
self.attention_dropout = attention_dropout
|
||||
|
||||
# MoE parameters
|
||||
self.num_experts = num_experts
|
||||
self.num_experts_per_tok = num_experts_per_tok
|
||||
self.num_shared_experts = num_shared_experts
|
||||
self.num_dense_layers = num_dense_layers
|
||||
|
||||
# Routing parameters
|
||||
self.score_func = score_func
|
||||
self.route_norm = route_norm
|
||||
self.route_scale = route_scale
|
||||
self.n_group = n_group
|
||||
self.topk_group = topk_group
|
||||
|
||||
# Attention parameters
|
||||
self.sliding_window = sliding_window
|
||||
self.layer_types = layer_types
|
||||
self.global_attn_every_n_layers = global_attn_every_n_layers
|
||||
|
||||
# muP scaling
|
||||
self.mup_enabled = mup_enabled
|
||||
|
||||
super().__init__(
|
||||
pad_token_id=pad_token_id,
|
||||
bos_token_id=bos_token_id,
|
||||
eos_token_id=eos_token_id,
|
||||
tie_word_embeddings=tie_word_embeddings,
|
||||
**kwargs,
|
||||
)
|
||||
188
third_party/sglang/python/sglang/srt/configs/bailing_hybrid.py
vendored
Normal file
188
third_party/sglang/python/sglang/srt/configs/bailing_hybrid.py
vendored
Normal file
@@ -0,0 +1,188 @@
|
||||
# coding=utf-8
|
||||
# Copyright 2024 The Qwen team, Alibaba Group and the HuggingFace Inc. team. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
"""BailingHybrid model configuration"""
|
||||
|
||||
import enum
|
||||
|
||||
from transformers.configuration_utils import PretrainedConfig
|
||||
from transformers.utils import logging
|
||||
|
||||
from sglang.srt.configs.mamba_utils import Mamba2CacheParams, Mamba2StateShape
|
||||
|
||||
logger = logging.get_logger(__name__)
|
||||
|
||||
|
||||
class HybridLayerType(enum.Enum):
|
||||
full_attention = "attention"
|
||||
linear_attention = "linear_attention"
|
||||
|
||||
|
||||
class BailingHybridConfig(PretrainedConfig):
|
||||
|
||||
model_type = "bailing_hybrid"
|
||||
keys_to_ignore_at_inference = ["past_key_values"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vocab_size=157184,
|
||||
hidden_size=2048,
|
||||
intermediate_size=5120,
|
||||
num_hidden_layers=20,
|
||||
num_attention_heads=16,
|
||||
num_key_value_heads=4,
|
||||
hidden_act="silu",
|
||||
use_qkv_bias=False, # bailing only
|
||||
use_bias=False, # bailing only
|
||||
rms_norm_eps=1e-06,
|
||||
tie_word_embeddings=False, # PretrainedConfig key, here change default value.
|
||||
embedding_dropout=0.0,
|
||||
attention_dropout=0.0,
|
||||
output_dropout=0.0,
|
||||
initializer_range=0.02,
|
||||
max_position_embeddings=32768,
|
||||
rope_theta=600000.0,
|
||||
use_cache=True,
|
||||
max_window_layers=20,
|
||||
rope_scaling=None,
|
||||
pad_token_id=156892,
|
||||
eos_token_id=156892,
|
||||
num_experts=256,
|
||||
num_shared_experts=1,
|
||||
num_experts_per_tok=8,
|
||||
n_group=8,
|
||||
topk_group=4,
|
||||
moe_intermediate_size=512,
|
||||
first_k_dense_replace=1,
|
||||
head_dim=128,
|
||||
output_router_logits=False,
|
||||
use_qk_norm=True,
|
||||
num_nextn_predict_layers=0,
|
||||
mtp_loss_scaling_factor=0,
|
||||
moe_router_enable_expert_bias=True,
|
||||
routed_scaling_factor=1.0,
|
||||
layer_group_size=1,
|
||||
group_norm_size=1,
|
||||
linear_silu=False,
|
||||
kv_lora_rank=512,
|
||||
q_lora_rank=None,
|
||||
qk_rope_head_dim=64,
|
||||
v_head_dim=128,
|
||||
qk_nope_head_dim=128,
|
||||
rope_interleave=True,
|
||||
**kwargs,
|
||||
):
|
||||
self.num_hidden_layers = num_hidden_layers
|
||||
self.vocab_size = vocab_size
|
||||
self.hidden_size = hidden_size
|
||||
self.intermediate_size = intermediate_size
|
||||
self.num_attention_heads = num_attention_heads
|
||||
self.num_key_value_heads = num_key_value_heads
|
||||
self.hidden_act = hidden_act
|
||||
self.use_qkv_bias = use_qkv_bias
|
||||
self.use_bias = use_bias
|
||||
self.rms_norm_eps = rms_norm_eps
|
||||
self.embedding_dropout = embedding_dropout
|
||||
self.attention_dropout = attention_dropout
|
||||
self.output_dropout = output_dropout
|
||||
self.num_nextn_predict_layers = num_nextn_predict_layers
|
||||
self.mtp_loss_scaling_factor = mtp_loss_scaling_factor
|
||||
self.initializer_range = initializer_range
|
||||
self.max_position_embeddings = max_position_embeddings
|
||||
self.rope_theta = rope_theta
|
||||
self.use_cache = use_cache
|
||||
self.max_window_layers = max_window_layers
|
||||
self.head_dim = head_dim or self.hidden_size // self.num_attention_heads
|
||||
self.rope_scaling = rope_scaling
|
||||
self.use_qk_norm = use_qk_norm
|
||||
self.moe_router_enable_expert_bias = moe_router_enable_expert_bias
|
||||
self.routed_scaling_factor = routed_scaling_factor
|
||||
|
||||
# MoE configs
|
||||
self.num_experts = num_experts
|
||||
self.num_shared_experts = num_shared_experts
|
||||
self.num_experts_per_tok = num_experts_per_tok
|
||||
self.n_group = n_group
|
||||
self.topk_group = topk_group
|
||||
self.moe_intermediate_size = moe_intermediate_size
|
||||
self.first_k_dense_replace = first_k_dense_replace
|
||||
self.output_router_logits = output_router_logits
|
||||
|
||||
# Linear configs
|
||||
self.layer_group_size = layer_group_size
|
||||
self.group_norm_size = group_norm_size
|
||||
self.linear_silu = linear_silu
|
||||
self.num_linear_key_value_heads = num_attention_heads
|
||||
# mla
|
||||
self.kv_lora_rank = kv_lora_rank
|
||||
self.q_lora_rank = q_lora_rank
|
||||
self.qk_rope_head_dim = qk_rope_head_dim
|
||||
self.v_head_dim = v_head_dim
|
||||
self.qk_nope_head_dim = qk_nope_head_dim
|
||||
self.qk_head_dim = qk_nope_head_dim + qk_rope_head_dim
|
||||
self.rope_interleave = rope_interleave
|
||||
self.for_nextn_model = False
|
||||
super().__init__(
|
||||
pad_token_id=pad_token_id,
|
||||
eos_token_id=eos_token_id,
|
||||
tie_word_embeddings=tie_word_embeddings,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@property
|
||||
def layers_block_type(self):
|
||||
if self.for_nextn_model:
|
||||
return [HybridLayerType.full_attention.value]
|
||||
|
||||
layer_type_list = []
|
||||
|
||||
for l in range(self.num_hidden_layers):
|
||||
if (l + 1) % self.layer_group_size == 0:
|
||||
layer_type_list.append(HybridLayerType.full_attention.value)
|
||||
else:
|
||||
layer_type_list.append(HybridLayerType.linear_attention.value)
|
||||
|
||||
return layer_type_list
|
||||
|
||||
@property
|
||||
def linear_layer_ids(self):
|
||||
return [
|
||||
i
|
||||
for i, type_value in enumerate(self.layers_block_type)
|
||||
if type_value == HybridLayerType.linear_attention.value
|
||||
]
|
||||
|
||||
@property
|
||||
def full_attention_layer_ids(self):
|
||||
return [
|
||||
i
|
||||
for i, type_value in enumerate(self.layers_block_type)
|
||||
if type_value == HybridLayerType.full_attention.value
|
||||
]
|
||||
|
||||
@property
|
||||
def mamba2_cache_params(self) -> Mamba2CacheParams:
|
||||
from sglang.srt.layers.dp_attention import get_attention_tp_size
|
||||
|
||||
shape = Mamba2StateShape.create(
|
||||
tp_world_size=get_attention_tp_size(),
|
||||
intermediate_size=0,
|
||||
n_groups=0,
|
||||
num_heads=self.num_linear_key_value_heads,
|
||||
head_dim=self.head_dim,
|
||||
state_size=self.head_dim,
|
||||
conv_kernel=1,
|
||||
)
|
||||
|
||||
return Mamba2CacheParams(shape=shape, layers=self.linear_layer_ids)
|
||||
78
third_party/sglang/python/sglang/srt/configs/chatglm.py
vendored
Normal file
78
third_party/sglang/python/sglang/srt/configs/chatglm.py
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
# Adapted from
|
||||
# https://github.com/THUDM/ChatGLM2-6B
|
||||
# https://github.com/vllm-project/vllm/blob/main/vllm/transformers_utils/configs/chatglm.py
|
||||
|
||||
# ChatGLM2 and ChatGLM3 share the same config.
|
||||
# ChatGLM4 is officially supported by Huggingface
|
||||
# transformers >= 4.46.0 is required
|
||||
# https://huggingface.co/docs/transformers/en/model_doc/glm
|
||||
from transformers import PretrainedConfig
|
||||
|
||||
|
||||
class ChatGLMConfig(PretrainedConfig):
|
||||
model_type = "chatglm"
|
||||
attribute_map = {
|
||||
"num_hidden_layers": "num_layers",
|
||||
"n_head_kv": "multi_query_group_num",
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
num_layers=28,
|
||||
padded_vocab_size=65024,
|
||||
hidden_size=4096,
|
||||
ffn_hidden_size=13696,
|
||||
kv_channels=128,
|
||||
num_attention_heads=32,
|
||||
seq_length=2048,
|
||||
hidden_dropout=0.0,
|
||||
attention_dropout=0.0,
|
||||
layernorm_epsilon=1e-5,
|
||||
rmsnorm=True,
|
||||
apply_residual_connection_post_layernorm=False,
|
||||
post_layer_norm=True,
|
||||
add_bias_linear=False,
|
||||
add_qkv_bias=False,
|
||||
interleaved_qkv=False,
|
||||
bias_dropout_fusion=True,
|
||||
multi_query_attention=False,
|
||||
multi_query_group_num=1,
|
||||
apply_query_key_layer_scaling=True,
|
||||
attention_softmax_in_fp32=True,
|
||||
fp32_residual_connection=False,
|
||||
quantization_bit=0,
|
||||
pre_seq_len=None,
|
||||
prefix_projection=False,
|
||||
**kwargs
|
||||
):
|
||||
self.num_layers = num_layers
|
||||
self.vocab_size = padded_vocab_size
|
||||
self.padded_vocab_size = padded_vocab_size
|
||||
self.hidden_size = hidden_size
|
||||
self.ffn_hidden_size = ffn_hidden_size
|
||||
self.kv_channels = kv_channels
|
||||
self.num_attention_heads = num_attention_heads
|
||||
self.seq_length = seq_length
|
||||
# It is to be compatible with long lora.
|
||||
self.max_position_embeddings = seq_length
|
||||
self.hidden_dropout = hidden_dropout
|
||||
self.attention_dropout = attention_dropout
|
||||
self.layernorm_epsilon = layernorm_epsilon
|
||||
self.rmsnorm = rmsnorm
|
||||
self.apply_residual_connection_post_layernorm = (
|
||||
apply_residual_connection_post_layernorm
|
||||
)
|
||||
self.post_layer_norm = post_layer_norm
|
||||
self.add_bias_linear = add_bias_linear
|
||||
self.add_qkv_bias = add_qkv_bias
|
||||
self.bias_dropout_fusion = bias_dropout_fusion
|
||||
self.multi_query_attention = multi_query_attention
|
||||
self.multi_query_group_num = multi_query_group_num
|
||||
self.apply_query_key_layer_scaling = apply_query_key_layer_scaling
|
||||
self.attention_softmax_in_fp32 = attention_softmax_in_fp32
|
||||
self.fp32_residual_connection = fp32_residual_connection
|
||||
self.quantization_bit = quantization_bit
|
||||
self.pre_seq_len = pre_seq_len
|
||||
self.prefix_projection = prefix_projection
|
||||
self.interleaved_qkv = interleaved_qkv
|
||||
super().__init__(**kwargs)
|
||||
279
third_party/sglang/python/sglang/srt/configs/dbrx.py
vendored
Normal file
279
third_party/sglang/python/sglang/srt/configs/dbrx.py
vendored
Normal file
@@ -0,0 +1,279 @@
|
||||
# Adapted from
|
||||
# https://huggingface.co/databricks/dbrx-base/blob/main/configuration_dbrx.py
|
||||
# https://github.com/vllm-project/vllm/blob/main/vllm/transformers_utils/configs/dbrx.py
|
||||
"""Dbrx configuration."""
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
from transformers.configuration_utils import PretrainedConfig
|
||||
from transformers.utils import logging
|
||||
|
||||
logger = logging.get_logger(__name__)
|
||||
|
||||
DBRX_PRETRAINED_CONFIG_ARCHIVE_MAP = {} # type: ignore
|
||||
|
||||
|
||||
class DbrxAttentionConfig(PretrainedConfig):
|
||||
"""Configuration class for Dbrx Attention.
|
||||
|
||||
[`DbrxAttention`] class. It is used to instantiate attention layers
|
||||
according to the specified arguments, defining the layers architecture.
|
||||
|
||||
Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the
|
||||
documentation from [`PretrainedConfig`] for more information.
|
||||
|
||||
Args:
|
||||
attn_pdrop (`float`, *optional*, defaults to 0.0):
|
||||
The dropout probability for the attention layers.
|
||||
clip_qkv (`float`, *optional*, defaults to None):
|
||||
If not `None`, clip the queries, keys, and values in the attention layer to this value.
|
||||
kv_n_heads (Optional[int]): For grouped_query_attention only, allow user to specify number of kv heads.
|
||||
rope_theta (float): The base frequency for rope.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
attn_pdrop: float = 0,
|
||||
clip_qkv: Optional[float] = None,
|
||||
kv_n_heads: int = 1,
|
||||
rope_theta: float = 10000.0,
|
||||
**kwargs: Any,
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
self.attn_pdrop = attn_pdrop
|
||||
self.clip_qkv = clip_qkv
|
||||
self.kv_n_heads = kv_n_heads
|
||||
self.rope_theta = rope_theta
|
||||
|
||||
for k in ["model_type"]:
|
||||
if k in kwargs:
|
||||
kwargs.pop(k)
|
||||
if len(kwargs) != 0:
|
||||
raise ValueError(f"Found unknown {kwargs=}")
|
||||
|
||||
@classmethod
|
||||
def from_pretrained(
|
||||
cls, pretrained_model_name_or_path: str, **kwargs: Any
|
||||
) -> "PretrainedConfig":
|
||||
cls._set_token_in_kwargs(kwargs)
|
||||
|
||||
config_dict, kwargs = cls.get_config_dict(
|
||||
pretrained_model_name_or_path, **kwargs
|
||||
)
|
||||
|
||||
if config_dict.get("model_type") == "dbrx":
|
||||
config_dict = config_dict["attn_config"]
|
||||
|
||||
if (
|
||||
"model_type" in config_dict
|
||||
and hasattr(cls, "model_type")
|
||||
and config_dict["model_type"] != cls.model_type
|
||||
):
|
||||
logger.warning(
|
||||
"You are using a model of type %s to instantiate a model of "
|
||||
"type %s. This is not supported for all configurations of "
|
||||
"models and can yield errors.",
|
||||
config_dict["model_type"],
|
||||
cls.model_type,
|
||||
)
|
||||
|
||||
return cls.from_dict(config_dict, **kwargs)
|
||||
|
||||
|
||||
class DbrxFFNConfig(PretrainedConfig):
|
||||
"""Configuration class for Dbrx FFN.
|
||||
|
||||
[`DbrxFFN`] class. It is used to instantiate feedforward layers according to
|
||||
the specified arguments, defining the layers architecture.
|
||||
|
||||
Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the
|
||||
documentation from [`PretrainedConfig`] for more information.
|
||||
|
||||
Args:
|
||||
ffn_act_fn (dict, optional): A dict specifying activation function for the FFN.
|
||||
The dict should have a key 'name' with the value being the name of
|
||||
the activation function along with any additional keyword arguments.
|
||||
ffn_hidden_size (int, optional): The hidden size of the feedforward network.
|
||||
moe_num_experts (int, optional): The number of experts in the mixture of experts layer.
|
||||
moe_top_k (int, optional): The number of experts to use in the mixture of experts layer.
|
||||
moe_jitter_eps (float, optional): The jitter epsilon for the mixture of experts layer.
|
||||
moe_loss_weight (float, optional): The loss weight for the mixture of experts layer.
|
||||
moe_normalize_expert_weights (float, optional): The normalization factor for the expert weights.
|
||||
uniform_expert_assignment (bool, optional): Whether to use uniform expert assignment.
|
||||
This should only be used for benchmarking purposes.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
ffn_act_fn: Optional[dict] = None,
|
||||
ffn_hidden_size: int = 3584,
|
||||
moe_num_experts: int = 4,
|
||||
moe_top_k: int = 1,
|
||||
moe_jitter_eps: Optional[float] = None,
|
||||
moe_loss_weight: float = 0.01,
|
||||
moe_normalize_expert_weights: Optional[float] = 1,
|
||||
uniform_expert_assignment: bool = False,
|
||||
**kwargs: Any,
|
||||
):
|
||||
super().__init__()
|
||||
if ffn_act_fn is None:
|
||||
ffn_act_fn = {"name": "silu"}
|
||||
self.ffn_act_fn = ffn_act_fn
|
||||
self.ffn_hidden_size = ffn_hidden_size
|
||||
self.moe_num_experts = moe_num_experts
|
||||
self.moe_top_k = moe_top_k
|
||||
self.moe_jitter_eps = moe_jitter_eps
|
||||
self.moe_loss_weight = moe_loss_weight
|
||||
self.moe_normalize_expert_weights = moe_normalize_expert_weights
|
||||
self.uniform_expert_assignment = uniform_expert_assignment
|
||||
|
||||
for k in ["model_type"]:
|
||||
if k in kwargs:
|
||||
kwargs.pop(k)
|
||||
if len(kwargs) != 0:
|
||||
raise ValueError(f"Found unknown {kwargs=}")
|
||||
|
||||
@classmethod
|
||||
def from_pretrained(
|
||||
cls, pretrained_model_name_or_path: str, **kwargs: Any
|
||||
) -> "PretrainedConfig":
|
||||
cls._set_token_in_kwargs(kwargs)
|
||||
|
||||
config_dict, kwargs = cls.get_config_dict(
|
||||
pretrained_model_name_or_path, **kwargs
|
||||
)
|
||||
|
||||
if config_dict.get("model_type") == "dbrx":
|
||||
config_dict = config_dict["ffn_config"]
|
||||
|
||||
if (
|
||||
"model_type" in config_dict
|
||||
and hasattr(cls, "model_type")
|
||||
and config_dict["model_type"] != cls.model_type
|
||||
):
|
||||
logger.warning(
|
||||
"You are using a model of type %s to instantiate a model of "
|
||||
"type %s. This is not supported for all "
|
||||
"configurations of models and can yield errors.",
|
||||
config_dict["model_type"],
|
||||
cls.model_type,
|
||||
)
|
||||
|
||||
return cls.from_dict(config_dict, **kwargs)
|
||||
|
||||
|
||||
class DbrxConfig(PretrainedConfig):
|
||||
"""Configuration class for Dbrx.
|
||||
|
||||
[`DbrxModel`]. It is used to instantiate a Dbrx model according to the
|
||||
specified arguments, defining the model architecture.
|
||||
|
||||
Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the
|
||||
documentation from [`PretrainedConfig`] for more information.
|
||||
|
||||
|
||||
Args:
|
||||
d_model (`int`, *optional*, defaults to 6144):
|
||||
Dimensionality of the embeddings and hidden states.
|
||||
n_heads (`int`, *optional*, defaults to 48):
|
||||
Number of attention heads for each attention layer in the Transformer encoder.
|
||||
n_layers (`int`, *optional*, defaults to 40):
|
||||
Number of hidden layers in the Transformer encoder.
|
||||
max_seq_len (`int`, *optional*, defaults to 32768):
|
||||
The maximum sequence length of the model.
|
||||
vocab_size (`int`, *optional*, defaults to 100352):
|
||||
Vocabulary size of the Dbrx model. Defines the maximum number of different tokens that can be represented by
|
||||
the `inputs_ids` passed when calling [`DbrxModel`].
|
||||
resid_pdrop (`float`, *optional*, defaults to 0.0):
|
||||
The dropout probability applied to the attention output before combining with residual.
|
||||
emb_pdrop (`float`, *optional*, defaults to 0.0):
|
||||
The dropout probability for the embedding layer.
|
||||
attn_config (`dict`, *optional*):
|
||||
A dictionary used to configure the model's attention module.
|
||||
ffn_config (`dict`, *optional*):
|
||||
A dictionary used to configure the model's FFN module.
|
||||
use_cache (`bool`, *optional*, defaults to `False`):
|
||||
Whether or not the model should return the last key/values attentions (not used by all models).
|
||||
initializer_range (`float`, *optional*, defaults to 0.02):
|
||||
The standard deviation of the truncated_normal_initializer for initializing all weight matrices.
|
||||
output_router_logits (`bool`, *optional*, defaults to `False`):
|
||||
Whether or not the router logits should be returned by the model. Enabling this will also
|
||||
allow the model to output the auxiliary loss. See [here]() for more details
|
||||
router_aux_loss_coef (`float`, *optional*, defaults to 0.001):
|
||||
The aux loss factor for the total loss.
|
||||
|
||||
|
||||
Example:
|
||||
```python
|
||||
>>> from transformers import DbrxConfig, DbrxModel
|
||||
|
||||
>>> # Initializing a Dbrx configuration
|
||||
>>> configuration = DbrxConfig()
|
||||
|
||||
>>> # Initializing a model (with random weights) from the configuration
|
||||
>>> model = DbrxModel(configuration)
|
||||
|
||||
>>> # Accessing the model configuration
|
||||
>>> configuration = model.config
|
||||
```
|
||||
"""
|
||||
|
||||
model_type = "dbrx"
|
||||
attribute_map = {
|
||||
"num_attention_heads": "n_heads",
|
||||
"hidden_size": "d_model",
|
||||
"num_hidden_layers": "n_layers",
|
||||
"max_position_embeddings": "max_seq_len",
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
d_model: int = 2048,
|
||||
n_heads: int = 16,
|
||||
n_layers: int = 24,
|
||||
max_seq_len: int = 2048,
|
||||
vocab_size: int = 32000,
|
||||
resid_pdrop: float = 0.0,
|
||||
emb_pdrop: float = 0.0,
|
||||
attn_config: Optional[DbrxAttentionConfig] = None,
|
||||
ffn_config: Optional[DbrxFFNConfig] = None,
|
||||
use_cache: bool = True,
|
||||
initializer_range: float = 0.02,
|
||||
output_router_logits: bool = False,
|
||||
router_aux_loss_coef: float = 0.05,
|
||||
**kwargs: Any,
|
||||
):
|
||||
if attn_config is None:
|
||||
self.attn_config = DbrxAttentionConfig()
|
||||
elif isinstance(attn_config, dict):
|
||||
self.attn_config = DbrxAttentionConfig(**attn_config)
|
||||
else:
|
||||
self.attn_config = attn_config
|
||||
|
||||
if ffn_config is None:
|
||||
self.ffn_config = DbrxFFNConfig()
|
||||
elif isinstance(ffn_config, dict):
|
||||
self.ffn_config = DbrxFFNConfig(**ffn_config)
|
||||
else:
|
||||
self.ffn_config = ffn_config
|
||||
|
||||
self.d_model = d_model
|
||||
self.n_heads = n_heads
|
||||
self.n_layers = n_layers
|
||||
self.max_seq_len = max_seq_len
|
||||
self.vocab_size = vocab_size
|
||||
self.resid_pdrop = resid_pdrop
|
||||
self.emb_pdrop = emb_pdrop
|
||||
self.use_cache = use_cache
|
||||
self.initializer_range = initializer_range
|
||||
self.output_router_logits = output_router_logits
|
||||
self.router_aux_loss_coef = router_aux_loss_coef
|
||||
|
||||
tie_word_embeddings = kwargs.pop("tie_word_embeddings", False)
|
||||
if tie_word_embeddings:
|
||||
raise ValueError("tie_word_embeddings is not supported for Dbrx models.")
|
||||
|
||||
super().__init__(
|
||||
tie_word_embeddings=tie_word_embeddings,
|
||||
**kwargs,
|
||||
)
|
||||
817
third_party/sglang/python/sglang/srt/configs/deepseek_ocr.py
vendored
Normal file
817
third_party/sglang/python/sglang/srt/configs/deepseek_ocr.py
vendored
Normal file
@@ -0,0 +1,817 @@
|
||||
import math
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
import torch
|
||||
from PIL import Image, ImageOps
|
||||
from transformers import (
|
||||
AutoProcessor,
|
||||
LlamaTokenizerFast,
|
||||
PretrainedConfig,
|
||||
ProcessorMixin,
|
||||
)
|
||||
|
||||
from sglang.srt.multimodal.customized_mm_processor_utils import (
|
||||
register_customized_processor,
|
||||
)
|
||||
from sglang.srt.sampling.custom_logit_processor import (
|
||||
DeepseekOCRNoRepeatNGramLogitProcessor,
|
||||
)
|
||||
|
||||
BASE_SIZE = 1024
|
||||
IMAGE_SIZE = 640
|
||||
CROP_MODE = True
|
||||
MIN_CROPS = 2
|
||||
MAX_CROPS = 6 # max:9; If your GPU memory is small, it is recommended to set it to 6.
|
||||
MAX_CONCURRENCY = 100 # If you have limited GPU memory, lower the concurrency count.
|
||||
NUM_WORKERS = 64 # image pre-process (resize/padding) workers
|
||||
PRINT_NUM_VIS_TOKENS = False
|
||||
SKIP_REPEAT = True
|
||||
MODEL_PATH = "deepseek-ai/DeepSeek-OCR" # change to your model path
|
||||
|
||||
NGRAM_NO_REPEAT_SIZE = 30
|
||||
NGRAM_NO_REPEAT_WINDOW = 90
|
||||
# Whitelist `<td>` and `</td>` token ids to allow table structures.
|
||||
NGRAM_NO_REPEAT_WHITELIST = (128821, 128822)
|
||||
|
||||
DEFAULT_CUSTOM_LOGIT_PROCESSOR = DeepseekOCRNoRepeatNGramLogitProcessor.to_str()
|
||||
|
||||
|
||||
def get_default_ngram_custom_params() -> Dict[str, Any]:
|
||||
"""Return default custom params for the DeepSeek-OCR n-gram no repeat processor."""
|
||||
|
||||
return {
|
||||
"ngram_size": NGRAM_NO_REPEAT_SIZE,
|
||||
"window_size": NGRAM_NO_REPEAT_WINDOW,
|
||||
"whitelist_token_ids": list(NGRAM_NO_REPEAT_WHITELIST),
|
||||
}
|
||||
|
||||
|
||||
PROMPT = "<image>\n<|grounding|>Convert the document to markdown."
|
||||
|
||||
|
||||
class DictOutput(object):
|
||||
def items(self):
|
||||
return self.__dict__.items()
|
||||
|
||||
def keys(self):
|
||||
return self.__dict__.keys()
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self.__dict__[item]
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self.__dict__
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.__dict__[key] = value
|
||||
|
||||
|
||||
@dataclass
|
||||
class VLChatProcessorOutput(DictOutput):
|
||||
input_ids: torch.LongTensor
|
||||
target_ids: torch.LongTensor
|
||||
images_crop: torch.LongTensor
|
||||
pixel_values: (
|
||||
torch.Tensor
|
||||
) # rename from "images" to "pixel_values" for compatibility
|
||||
images_seq_mask: torch.BoolTensor
|
||||
images_spatial_crop: torch.LongTensor
|
||||
|
||||
def __len__(self):
|
||||
return len(self.input_ids)
|
||||
|
||||
|
||||
class ImageTransform(object):
|
||||
def __init__(
|
||||
self,
|
||||
mean: Optional[Tuple[float, float, float]] = (0.5, 0.5, 0.5),
|
||||
std: Optional[Tuple[float, float, float]] = (0.5, 0.5, 0.5),
|
||||
normalize: bool = True,
|
||||
):
|
||||
self.mean = mean
|
||||
self.std = std
|
||||
self.normalize = normalize
|
||||
|
||||
# only load torchvision.transforms when needed
|
||||
try:
|
||||
import torchvision.transforms as T
|
||||
|
||||
# FIXME: add version check for gguf
|
||||
except ImportError as err:
|
||||
raise ImportError(
|
||||
"Please install torchvision via `pip install torchvision` to use Deepseek-VL2."
|
||||
) from err
|
||||
|
||||
transform_pipelines = [T.ToTensor()]
|
||||
|
||||
if normalize:
|
||||
transform_pipelines.append(T.Normalize(mean, std))
|
||||
|
||||
self.transform = T.Compose(transform_pipelines)
|
||||
|
||||
def __call__(self, pil_img: Image.Image):
|
||||
x = self.transform(pil_img)
|
||||
return x
|
||||
|
||||
|
||||
def find_closest_aspect_ratio(aspect_ratio, target_ratios, width, height, image_size):
|
||||
best_ratio_diff = float("inf")
|
||||
best_ratio = (1, 1)
|
||||
area = width * height
|
||||
for ratio in target_ratios:
|
||||
target_aspect_ratio = ratio[0] / ratio[1]
|
||||
ratio_diff = abs(aspect_ratio - target_aspect_ratio)
|
||||
if ratio_diff < best_ratio_diff:
|
||||
best_ratio_diff = ratio_diff
|
||||
best_ratio = ratio
|
||||
elif ratio_diff == best_ratio_diff:
|
||||
if area > 0.5 * image_size * image_size * ratio[0] * ratio[1]:
|
||||
best_ratio = ratio
|
||||
return best_ratio
|
||||
|
||||
|
||||
def dynamic_preprocess(
|
||||
image, min_num=MIN_CROPS, max_num=MAX_CROPS, image_size=640, use_thumbnail=False
|
||||
):
|
||||
orig_width, orig_height = image.size
|
||||
aspect_ratio = orig_width / orig_height
|
||||
|
||||
# calculate the existing image aspect ratio
|
||||
target_ratios = set(
|
||||
(i, j)
|
||||
for n in range(min_num, max_num + 1)
|
||||
for i in range(1, n + 1)
|
||||
for j in range(1, n + 1)
|
||||
if i * j <= max_num and i * j >= min_num
|
||||
)
|
||||
target_ratios = sorted(target_ratios, key=lambda x: x[0] * x[1])
|
||||
|
||||
# find the closest aspect ratio to the target
|
||||
target_aspect_ratio = find_closest_aspect_ratio(
|
||||
aspect_ratio, target_ratios, orig_width, orig_height, image_size
|
||||
)
|
||||
|
||||
# calculate the target width and height
|
||||
target_width = image_size * target_aspect_ratio[0]
|
||||
target_height = image_size * target_aspect_ratio[1]
|
||||
blocks = target_aspect_ratio[0] * target_aspect_ratio[1]
|
||||
|
||||
# resize the image
|
||||
resized_img = image.resize((target_width, target_height))
|
||||
processed_images = []
|
||||
for i in range(blocks):
|
||||
box = (
|
||||
(i % (target_width // image_size)) * image_size,
|
||||
(i // (target_width // image_size)) * image_size,
|
||||
((i % (target_width // image_size)) + 1) * image_size,
|
||||
((i // (target_width // image_size)) + 1) * image_size,
|
||||
)
|
||||
# split the image
|
||||
split_img = resized_img.crop(box)
|
||||
processed_images.append(split_img)
|
||||
assert len(processed_images) == blocks
|
||||
if use_thumbnail and len(processed_images) != 1:
|
||||
thumbnail_img = image.resize((image_size, image_size))
|
||||
processed_images.append(thumbnail_img)
|
||||
return processed_images, target_aspect_ratio
|
||||
|
||||
|
||||
class DeepseekOCRProcessor(ProcessorMixin):
|
||||
tokenizer_class = ("LlamaTokenizer", "LlamaTokenizerFast")
|
||||
attributes = ["tokenizer"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
tokenizer: LlamaTokenizerFast,
|
||||
candidate_resolutions: Tuple[Tuple[int, int]],
|
||||
patch_size: int,
|
||||
downsample_ratio: int,
|
||||
image_mean: Tuple[float, float, float] = (0.5, 0.5, 0.5),
|
||||
image_std: Tuple[float, float, float] = (0.5, 0.5, 0.5),
|
||||
normalize: bool = True,
|
||||
image_token: str = "<image>",
|
||||
pad_token: str = "<|▁pad▁|>",
|
||||
add_special_token: bool = False,
|
||||
sft_format: str = "deepseek",
|
||||
mask_prompt: bool = True,
|
||||
ignore_id: int = -100,
|
||||
ocr2_mode: bool = False,
|
||||
**kwargs,
|
||||
):
|
||||
|
||||
self.candidate_resolutions = candidate_resolutions
|
||||
self.image_size = candidate_resolutions[0][0]
|
||||
self.patch_size = patch_size
|
||||
self.image_mean = image_mean
|
||||
self.image_std = image_std
|
||||
self.normalize = normalize
|
||||
self.downsample_ratio = downsample_ratio
|
||||
self.base_size = BASE_SIZE
|
||||
self.image_transform = ImageTransform(
|
||||
mean=image_mean, std=image_std, normalize=normalize
|
||||
)
|
||||
self.tokenizer = tokenizer
|
||||
# must set this,padding side with make a difference in batch inference
|
||||
self.tokenizer.padding_side = "left"
|
||||
|
||||
# add the pad_token as special token to use 'tokenizer.pad_token' and 'tokenizer.pad_token_id'
|
||||
if tokenizer.pad_token is None:
|
||||
self.tokenizer.add_special_tokens({"pad_token": pad_token})
|
||||
|
||||
# add image token
|
||||
image_token_id = self.tokenizer.vocab.get(image_token)
|
||||
if image_token_id is None:
|
||||
special_tokens = [image_token]
|
||||
special_tokens_dict = {"additional_special_tokens": special_tokens}
|
||||
self.tokenizer.add_special_tokens(special_tokens_dict)
|
||||
self.image_token_id = self.tokenizer.vocab.get(image_token)
|
||||
|
||||
# add five special tokens for grounding-related tasks
|
||||
# <|ref|>, <|/ref|>, <|det|>, <|/det|>, <|grounding|>
|
||||
special_tokens = ["<|ref|>", "<|/ref|>", "<|det|>", "<|/det|>", "<|grounding|>"]
|
||||
special_tokens_dict = {"additional_special_tokens": special_tokens}
|
||||
self.tokenizer.add_special_tokens(special_tokens_dict)
|
||||
|
||||
# add special tokens for SFT data
|
||||
special_tokens = ["<|User|>", "<|Assistant|>"]
|
||||
special_tokens_dict = {"additional_special_tokens": special_tokens}
|
||||
self.tokenizer.add_special_tokens(special_tokens_dict)
|
||||
|
||||
self.image_token = image_token
|
||||
self.pad_token = pad_token
|
||||
self.add_special_token = add_special_token
|
||||
self.sft_format = sft_format
|
||||
self.mask_prompt = mask_prompt
|
||||
self.ignore_id = ignore_id
|
||||
self.ocr2_mode = ocr2_mode
|
||||
|
||||
super().__init__(
|
||||
tokenizer,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def format_messages_v2(self, messages: str, pil_images, max_req_input_len=-1):
|
||||
"""play the role of format_messages_v2 and get_images_info in the last version"""
|
||||
tokenized_data = []
|
||||
masked_tokenized_data = [] # labels
|
||||
images_list = []
|
||||
images_seq_mask = []
|
||||
images_spatial_crop = []
|
||||
|
||||
image_index = 0
|
||||
image_token_cnt = messages.count(self.image_token)
|
||||
(
|
||||
input_ids,
|
||||
images,
|
||||
images_crop,
|
||||
seq_mask,
|
||||
spatial_crop,
|
||||
num_image_tokens,
|
||||
image_shapes,
|
||||
) = self.tokenize_with_images(
|
||||
messages,
|
||||
pil_images[image_index : image_index + image_token_cnt],
|
||||
bos=True,
|
||||
eos=True,
|
||||
cropping=len(pil_images) <= 2,
|
||||
)
|
||||
|
||||
image_index = image_token_cnt
|
||||
images_list += images
|
||||
images_seq_mask += seq_mask
|
||||
images_spatial_crop = spatial_crop
|
||||
|
||||
return (
|
||||
input_ids,
|
||||
masked_tokenized_data,
|
||||
images_list,
|
||||
images_seq_mask,
|
||||
images_spatial_crop,
|
||||
images_crop,
|
||||
)
|
||||
|
||||
@property
|
||||
def bos_id(self):
|
||||
return self.tokenizer.bos_token_id
|
||||
|
||||
@property
|
||||
def eos_id(self):
|
||||
return self.tokenizer.eos_token_id
|
||||
|
||||
@property
|
||||
def pad_id(self):
|
||||
return self.tokenizer.pad_token_id
|
||||
|
||||
def encode(self, text: str, bos: bool = True, eos: bool = False):
|
||||
t = self.tokenizer.encode(text, add_special_tokens=False)
|
||||
|
||||
if bos:
|
||||
t = [self.bos_id] + t
|
||||
if eos:
|
||||
t = t + [self.eos_id]
|
||||
|
||||
return t
|
||||
|
||||
def decode(self, t: List[int], **kwargs) -> str:
|
||||
return self.tokenizer.decode(t, **kwargs)
|
||||
|
||||
def process_one(
|
||||
self,
|
||||
prompt: str = None,
|
||||
conversations: List[Dict[str, str]] = None,
|
||||
images: List[Image.Image] = None,
|
||||
apply_sft_format: bool = False,
|
||||
inference_mode: bool = True,
|
||||
system_prompt: str = "",
|
||||
max_req_input_len: int = -1,
|
||||
cropping: bool = True,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
|
||||
Args:
|
||||
prompt (str): the formatted prompt;
|
||||
conversations (List[Dict]): conversations with a list of messages;
|
||||
images (List[ImageType]): the list of images;
|
||||
apply_sft_format (bool): if prompt is not None, then apply the SFT format to prompt;
|
||||
if conversations is not None, then it will always apply the SFT format to conversations;
|
||||
inference_mode (bool): if True, then remove the last eos token;
|
||||
system_prompt (str): the system prompt;
|
||||
**kwargs:
|
||||
|
||||
Returns:
|
||||
outputs (BaseProcessorOutput): the output of the processor,
|
||||
- input_ids (torch.LongTensor): [N + image tokens]
|
||||
- target_ids (torch.LongTensor): [N + image tokens]
|
||||
- images (torch.FloatTensor): [n_images, 3, H, W]
|
||||
- image_id (int): the id of the image token
|
||||
- num_image_tokens (List[int]): the number of image tokens
|
||||
"""
|
||||
|
||||
prompt = conversations or prompt
|
||||
(
|
||||
input_ids,
|
||||
masked_tokenized_str,
|
||||
images_list,
|
||||
images_seq_mask,
|
||||
images_spatial_crop,
|
||||
images_crop,
|
||||
) = self.format_messages_v2(prompt, images, max_req_input_len)
|
||||
|
||||
target_ids = torch.LongTensor(masked_tokenized_str)
|
||||
|
||||
has_images = len(images_list) > 0
|
||||
has_local_crops = False
|
||||
if len(images_spatial_crop) > 0:
|
||||
has_local_crops = any(
|
||||
crop[0] > 1 or crop[1] > 1 for crop in images_spatial_crop
|
||||
)
|
||||
|
||||
if len(images_list) == 0:
|
||||
images = torch.zeros((1, 3, self.image_size, self.image_size))
|
||||
else:
|
||||
images = torch.stack(images_list, dim=0)
|
||||
|
||||
images_spatial_crop = torch.stack(
|
||||
[images_spatial_crop], dim=0
|
||||
) # stack the tensor to make it a batch of 1
|
||||
|
||||
prepare = VLChatProcessorOutput(
|
||||
input_ids=input_ids,
|
||||
target_ids=target_ids,
|
||||
images_crop=images_crop,
|
||||
pixel_values=images,
|
||||
images_seq_mask=images_seq_mask,
|
||||
images_spatial_crop=images_spatial_crop,
|
||||
)
|
||||
prepare.has_images = has_images
|
||||
prepare.has_local_crops = has_local_crops
|
||||
|
||||
return prepare
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
*,
|
||||
prompt: str = None,
|
||||
conversations: List[Dict[str, str]] = None,
|
||||
images: List[Image.Image] = None,
|
||||
apply_sft_format: bool = False,
|
||||
inference_mode: bool = True,
|
||||
system_prompt: str = "",
|
||||
max_req_input_len: int = -1,
|
||||
text: list[str] = None,
|
||||
**kwargs,
|
||||
):
|
||||
assert text is None or isinstance(text, list)
|
||||
if text is not None:
|
||||
text = text[0]
|
||||
prepare = self.process_one(
|
||||
prompt=prompt or text,
|
||||
conversations=conversations,
|
||||
images=images,
|
||||
apply_sft_format=apply_sft_format,
|
||||
inference_mode=inference_mode,
|
||||
system_prompt=system_prompt,
|
||||
max_req_input_len=max_req_input_len,
|
||||
)
|
||||
|
||||
return prepare
|
||||
|
||||
def find_all_indices(self, messages, target_value):
|
||||
indices = []
|
||||
for index, item in enumerate(messages):
|
||||
if item == target_value:
|
||||
indices.append(index)
|
||||
return indices
|
||||
|
||||
def tokenize_with_images(
|
||||
self,
|
||||
conversation: str,
|
||||
images: List[Image.Image],
|
||||
bos: bool = True,
|
||||
eos: bool = True,
|
||||
cropping: bool = True,
|
||||
):
|
||||
"""Tokenize text with <image> tags."""
|
||||
|
||||
conversation = conversation
|
||||
assert conversation.count(self.image_token) == len(images)
|
||||
text_splits = conversation.split(self.image_token)
|
||||
images_list, images_crop_list, images_seq_mask, images_spatial_crop = (
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
)
|
||||
image_shapes = []
|
||||
num_image_tokens = []
|
||||
tokenized_str = []
|
||||
for text_sep, image in zip(text_splits, images):
|
||||
"""encode text_sep"""
|
||||
tokenized_sep = self.encode(text_sep, bos=False, eos=False)
|
||||
|
||||
tokenized_str += tokenized_sep
|
||||
images_seq_mask += [False] * len(tokenized_sep)
|
||||
|
||||
image_shapes.append(image.size)
|
||||
|
||||
if image.size[0] <= 640 and image.size[1] <= 640:
|
||||
crop_ratio = [1, 1]
|
||||
else:
|
||||
if cropping:
|
||||
images_crop_raw, crop_ratio = dynamic_preprocess(
|
||||
image, image_size=IMAGE_SIZE
|
||||
)
|
||||
else:
|
||||
crop_ratio = [1, 1]
|
||||
|
||||
"""process the global view"""
|
||||
if self.image_size <= 640 and not cropping:
|
||||
image = image.resize((self.image_size, self.image_size))
|
||||
|
||||
global_view = ImageOps.pad(
|
||||
image,
|
||||
(self.base_size, self.base_size),
|
||||
color=tuple(int(x * 255) for x in self.image_transform.mean),
|
||||
)
|
||||
images_list.append(self.image_transform(global_view))
|
||||
|
||||
num_width_tiles, num_height_tiles = crop_ratio
|
||||
images_spatial_crop.append([num_width_tiles, num_height_tiles])
|
||||
|
||||
if num_width_tiles > 1 or num_height_tiles > 1:
|
||||
for i in range(len(images_crop_raw)):
|
||||
images_crop_list.append(self.image_transform(images_crop_raw[i]))
|
||||
|
||||
"""add image tokens"""
|
||||
num_queries = math.ceil(
|
||||
(self.image_size // self.patch_size) / self.downsample_ratio
|
||||
)
|
||||
num_queries_base = math.ceil(
|
||||
(self.base_size // self.patch_size) / self.downsample_ratio
|
||||
)
|
||||
|
||||
if self.ocr2_mode:
|
||||
tokenized_image = []
|
||||
if num_width_tiles > 1 or num_height_tiles > 1:
|
||||
tokenized_image += [self.image_token_id] * (
|
||||
num_queries * num_width_tiles * num_queries * num_height_tiles
|
||||
)
|
||||
tokenized_image += [self.image_token_id] * (
|
||||
num_queries_base * num_queries_base
|
||||
)
|
||||
# One extra token for the view separator.
|
||||
tokenized_image += [self.image_token_id]
|
||||
else:
|
||||
tokenized_image = (
|
||||
[self.image_token_id] * num_queries_base + [self.image_token_id]
|
||||
) * num_queries_base
|
||||
tokenized_image += [self.image_token_id]
|
||||
if num_width_tiles > 1 or num_height_tiles > 1:
|
||||
tokenized_image += (
|
||||
[self.image_token_id] * (num_queries * num_width_tiles)
|
||||
+ [self.image_token_id]
|
||||
) * (num_queries * num_height_tiles)
|
||||
tokenized_str += tokenized_image
|
||||
|
||||
images_seq_mask += [True] * len(tokenized_image)
|
||||
num_image_tokens.append(len(tokenized_image))
|
||||
|
||||
"""process the last text split"""
|
||||
tokenized_sep = self.encode(text_splits[-1], bos=False, eos=False)
|
||||
|
||||
tokenized_str += tokenized_sep
|
||||
images_seq_mask += [False] * len(tokenized_sep)
|
||||
|
||||
"""add the bos and eos tokens"""
|
||||
if bos:
|
||||
tokenized_str = [self.bos_id] + tokenized_str
|
||||
images_seq_mask = [False] + images_seq_mask
|
||||
if eos:
|
||||
tokenized_str = tokenized_str + [self.eos_id]
|
||||
images_seq_mask = images_seq_mask + [False]
|
||||
|
||||
assert len(tokenized_str) == len(
|
||||
images_seq_mask
|
||||
), f"tokenize_with_images func: tokenized_str's length {len(tokenized_str)} is not equal to imags_seq_mask's length {len(images_seq_mask)}"
|
||||
|
||||
masked_tokenized_str = []
|
||||
for token_index in tokenized_str:
|
||||
if token_index != self.image_token_id:
|
||||
masked_tokenized_str.append(token_index)
|
||||
else:
|
||||
masked_tokenized_str.append(self.ignore_id)
|
||||
|
||||
assert (
|
||||
len(tokenized_str) == len(images_seq_mask) == len(masked_tokenized_str)
|
||||
), (
|
||||
f"tokenized_str's length {len(tokenized_str)}, input_ids' length {len(masked_tokenized_str)}, "
|
||||
f"imags_seq_mask's length {len(images_seq_mask)}, are not equal"
|
||||
)
|
||||
input_ids = torch.LongTensor(tokenized_str)
|
||||
target_ids = torch.LongTensor(masked_tokenized_str)
|
||||
images_seq_mask = torch.tensor(images_seq_mask, dtype=torch.bool)
|
||||
|
||||
# set input_ids < 0 | input_ids == self.image_token_id as ignore_id
|
||||
target_ids[(input_ids < 0) | (input_ids == self.image_token_id)] = (
|
||||
self.ignore_id
|
||||
)
|
||||
input_ids[input_ids < 0] = self.pad_id
|
||||
|
||||
inference_mode = True
|
||||
|
||||
if inference_mode:
|
||||
# Remove the ending eos token
|
||||
assert input_ids[-1] == self.eos_id
|
||||
input_ids = input_ids[:-1]
|
||||
target_ids = target_ids[:-1]
|
||||
images_seq_mask = images_seq_mask[:-1]
|
||||
|
||||
if len(images_list) == 0:
|
||||
pixel_values = torch.zeros((1, 3, self.base_size, self.base_size))
|
||||
images_spatial_crop = torch.zeros((1, 1), dtype=torch.long)
|
||||
images_crop = torch.zeros(
|
||||
(1, 3, self.image_size, self.image_size)
|
||||
).unsqueeze(0)
|
||||
else:
|
||||
pixel_values = torch.stack(images_list, dim=0)
|
||||
images_spatial_crop = torch.tensor(images_spatial_crop, dtype=torch.long)
|
||||
if images_crop_list:
|
||||
images_crop = torch.stack(images_crop_list, dim=0).unsqueeze(0)
|
||||
else:
|
||||
images_crop = torch.zeros(
|
||||
(1, 3, self.image_size, self.image_size)
|
||||
).unsqueeze(0)
|
||||
|
||||
input_ids = input_ids.unsqueeze(0)
|
||||
return (
|
||||
input_ids,
|
||||
pixel_values,
|
||||
images_crop,
|
||||
images_seq_mask,
|
||||
images_spatial_crop,
|
||||
num_image_tokens,
|
||||
image_shapes,
|
||||
)
|
||||
|
||||
|
||||
class VisionEncoderConfig(PretrainedConfig):
|
||||
model_type: str = "vision"
|
||||
|
||||
model_name: str = "vit_so400m_patch14_siglip_384.webli"
|
||||
image_size: int = 384
|
||||
patch_size: int = 16
|
||||
width: int = 1024
|
||||
layers: int = 24
|
||||
heads: int = 16
|
||||
mlp_ratio: int = 4
|
||||
global_pool: str = "map"
|
||||
ignore_head: bool = True
|
||||
class_token: bool = False
|
||||
num_classes: int = 0
|
||||
use_checkpoint: bool = False
|
||||
weight_init: str = "skip"
|
||||
deterministic: bool = False
|
||||
num_recomputing_layers: int = 0
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
model_name: str = "vit_so400m_patch14_siglip_384.webli",
|
||||
image_size: int = 384,
|
||||
patch_size: int = 16,
|
||||
width: int = 1024,
|
||||
layers: int = 24,
|
||||
heads: int = 16,
|
||||
mlp_ratio: int = 4,
|
||||
global_pool: str = "map",
|
||||
ignore_head: bool = True,
|
||||
class_token: bool = False,
|
||||
num_classes: int = 0,
|
||||
use_checkpoint: bool = False,
|
||||
**kwargs,
|
||||
):
|
||||
self.model_name = model_name
|
||||
self.image_size = image_size
|
||||
self.patch_size = patch_size
|
||||
self.width = width
|
||||
self.layers = layers
|
||||
self.heads = heads
|
||||
self.mlp_ratio = mlp_ratio
|
||||
self.global_pool = global_pool
|
||||
self.ignore_head = ignore_head
|
||||
self.class_token = class_token
|
||||
self.num_classes = num_classes
|
||||
self.use_checkpoint = use_checkpoint
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
class MlpProjectorConfig(PretrainedConfig):
|
||||
model_type = "mlp_projector"
|
||||
projector_type: str = "downsample_mlp_gelu"
|
||||
input_dim: int = 1152
|
||||
n_embed: int = 2048
|
||||
depth: int = 2
|
||||
mlp_ratio: int = 1
|
||||
downsample_ratio: int = 2
|
||||
token_pooling: bool = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
projector_type: str = "downsample_mlp_gelu",
|
||||
input_dim: int = 1152,
|
||||
n_embed: int = 2048,
|
||||
depth: int = 2,
|
||||
mlp_ratio: int = 1,
|
||||
downsample_ratio: int = 2,
|
||||
**kwargs,
|
||||
):
|
||||
self.projector_type = projector_type
|
||||
self.input_dim = input_dim
|
||||
self.n_embed = n_embed
|
||||
self.depth = depth
|
||||
self.mlp_ratio = mlp_ratio
|
||||
self.downsample_ratio = downsample_ratio
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
class DeepseekV2Config(PretrainedConfig):
|
||||
model_type = "deepseek_v2"
|
||||
keys_to_ignore_at_inference = ["past_key_values"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vocab_size=102400,
|
||||
hidden_size=4096,
|
||||
intermediate_size=11008,
|
||||
moe_intermediate_size=1407,
|
||||
num_hidden_layers=30,
|
||||
num_attention_heads=32,
|
||||
num_key_value_heads=32,
|
||||
n_shared_experts=None,
|
||||
n_routed_experts=None,
|
||||
ep_size=1,
|
||||
routed_scaling_factor=1.0,
|
||||
kv_lora_rank=512,
|
||||
q_lora_rank=1536,
|
||||
qk_rope_head_dim=64,
|
||||
v_head_dim=128,
|
||||
qk_nope_head_dim=128,
|
||||
topk_method="gready",
|
||||
n_group=None,
|
||||
topk_group=None,
|
||||
num_experts_per_tok=None,
|
||||
moe_layer_freq=1,
|
||||
first_k_dense_replace=0,
|
||||
norm_topk_prob=False,
|
||||
scoring_func="softmax",
|
||||
aux_loss_alpha=0.001,
|
||||
seq_aux=True,
|
||||
hidden_act="silu",
|
||||
max_position_embeddings=2048,
|
||||
initializer_range=0.02,
|
||||
rms_norm_eps=1e-6,
|
||||
use_cache=True,
|
||||
pad_token_id=None,
|
||||
bos_token_id=100000,
|
||||
eos_token_id=100001,
|
||||
pretraining_tp=1,
|
||||
tie_word_embeddings=False,
|
||||
rope_theta=10000.0,
|
||||
rope_scaling=None,
|
||||
attention_bias=False,
|
||||
attention_dropout=0.0,
|
||||
use_mla=True,
|
||||
**kwargs,
|
||||
):
|
||||
self.vocab_size = vocab_size
|
||||
self.max_position_embeddings = max_position_embeddings
|
||||
self.hidden_size = hidden_size
|
||||
self.intermediate_size = intermediate_size
|
||||
self.moe_intermediate_size = moe_intermediate_size
|
||||
self.num_hidden_layers = num_hidden_layers
|
||||
self.num_attention_heads = num_attention_heads
|
||||
self.n_shared_experts = n_shared_experts
|
||||
self.n_routed_experts = n_routed_experts
|
||||
self.ep_size = ep_size
|
||||
self.routed_scaling_factor = routed_scaling_factor
|
||||
self.kv_lora_rank = kv_lora_rank
|
||||
self.q_lora_rank = q_lora_rank
|
||||
self.qk_rope_head_dim = qk_rope_head_dim
|
||||
self.v_head_dim = v_head_dim
|
||||
self.qk_nope_head_dim = qk_nope_head_dim
|
||||
self.topk_method = topk_method
|
||||
self.n_group = n_group
|
||||
self.topk_group = topk_group
|
||||
self.num_experts_per_tok = num_experts_per_tok
|
||||
self.moe_layer_freq = moe_layer_freq
|
||||
self.first_k_dense_replace = first_k_dense_replace
|
||||
self.norm_topk_prob = norm_topk_prob
|
||||
self.scoring_func = scoring_func
|
||||
self.aux_loss_alpha = aux_loss_alpha
|
||||
self.seq_aux = seq_aux
|
||||
# for backward compatibility
|
||||
if num_key_value_heads is None:
|
||||
num_key_value_heads = num_attention_heads
|
||||
|
||||
self.num_key_value_heads = num_key_value_heads
|
||||
self.hidden_act = hidden_act
|
||||
self.initializer_range = initializer_range
|
||||
self.rms_norm_eps = float(rms_norm_eps)
|
||||
self.pretraining_tp = pretraining_tp
|
||||
self.use_cache = use_cache
|
||||
self.rope_theta = rope_theta
|
||||
self.rope_scaling = rope_scaling
|
||||
self.attention_bias = attention_bias
|
||||
self.attention_dropout = attention_dropout
|
||||
self.use_mla = use_mla
|
||||
|
||||
super().__init__(
|
||||
pad_token_id=pad_token_id,
|
||||
bos_token_id=bos_token_id,
|
||||
eos_token_id=eos_token_id,
|
||||
tie_word_embeddings=tie_word_embeddings,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
@register_customized_processor(processor_class=DeepseekOCRProcessor)
|
||||
class DeepseekVLV2Config(PretrainedConfig):
|
||||
# model_type = "deepseek_vl_v2"
|
||||
model_type = "deepseek-ocr"
|
||||
vision_config: VisionEncoderConfig = None
|
||||
projector_config: MlpProjectorConfig = None
|
||||
|
||||
tile_tag: str = "2D"
|
||||
global_view_pos: str = "head"
|
||||
candidate_resolutions: tuple[tuple[int, int]] = ((384, 384),)
|
||||
customized_processor_type: type[Any] = DeepseekOCRProcessor
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
tile_tag: str = "tile_tag",
|
||||
global_view_pos: str = "head",
|
||||
candidate_resolutions: tuple[tuple[int, int]] = ((384, 384),),
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
vision_config = kwargs.get("vision_config", {})
|
||||
self.vision_config = VisionEncoderConfig(**vision_config)
|
||||
|
||||
projector_config = kwargs.get("projector_config", {})
|
||||
self.projector_config = MlpProjectorConfig(**projector_config)
|
||||
|
||||
language_config = kwargs.get("language_config", {})
|
||||
self.text_config = DeepseekV2Config(**language_config)
|
||||
|
||||
self.tile_tag = tile_tag
|
||||
self.global_view_pos = global_view_pos
|
||||
self.candidate_resolutions = candidate_resolutions
|
||||
self.vocab_size = self.text_config.vocab_size
|
||||
self.hidden_size = self.text_config.hidden_size
|
||||
|
||||
|
||||
AutoProcessor.register(DeepseekVLV2Config, DeepseekOCRProcessor)
|
||||
687
third_party/sglang/python/sglang/srt/configs/deepseekvl2.py
vendored
Normal file
687
third_party/sglang/python/sglang/srt/configs/deepseekvl2.py
vendored
Normal file
@@ -0,0 +1,687 @@
|
||||
import math
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
import torch
|
||||
from PIL import Image, ImageOps
|
||||
from transformers import (
|
||||
AutoProcessor,
|
||||
LlamaTokenizerFast,
|
||||
PretrainedConfig,
|
||||
ProcessorMixin,
|
||||
)
|
||||
|
||||
|
||||
def select_best_resolution(image_size, candidate_resolutions):
|
||||
# used for cropping
|
||||
original_width, original_height = image_size
|
||||
best_fit = None
|
||||
max_effective_resolution = 0
|
||||
min_wasted_resolution = float("inf")
|
||||
|
||||
for width, height in candidate_resolutions:
|
||||
scale = min(width / original_width, height / original_height)
|
||||
downscaled_width, downscaled_height = int(original_width * scale), int(
|
||||
original_height * scale
|
||||
)
|
||||
effective_resolution = min(
|
||||
downscaled_width * downscaled_height, original_width * original_height
|
||||
)
|
||||
wasted_resolution = (width * height) - effective_resolution
|
||||
|
||||
if effective_resolution > max_effective_resolution or (
|
||||
effective_resolution == max_effective_resolution
|
||||
and wasted_resolution < min_wasted_resolution
|
||||
):
|
||||
max_effective_resolution = effective_resolution
|
||||
min_wasted_resolution = wasted_resolution
|
||||
best_fit = (width, height)
|
||||
|
||||
return best_fit
|
||||
|
||||
|
||||
class DictOutput(object):
|
||||
def items(self):
|
||||
return self.__dict__.items()
|
||||
|
||||
def keys(self):
|
||||
return self.__dict__.keys()
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self.__dict__[item]
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self.__dict__
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.__dict__[key] = value
|
||||
|
||||
|
||||
@dataclass
|
||||
class VLChatProcessorOutput(DictOutput):
|
||||
input_ids: torch.LongTensor
|
||||
target_ids: torch.LongTensor
|
||||
pixel_values: (
|
||||
torch.Tensor
|
||||
) # rename from "images" to "pixel_values" for compatibility
|
||||
images_seq_mask: torch.BoolTensor
|
||||
images_spatial_crop: torch.LongTensor
|
||||
|
||||
def __len__(self):
|
||||
return len(self.input_ids)
|
||||
|
||||
|
||||
class ImageTransform(object):
|
||||
def __init__(
|
||||
self,
|
||||
mean: Optional[Tuple[float, float, float]] = (0.5, 0.5, 0.5),
|
||||
std: Optional[Tuple[float, float, float]] = (0.5, 0.5, 0.5),
|
||||
normalize: bool = True,
|
||||
):
|
||||
self.mean = mean
|
||||
self.std = std
|
||||
self.normalize = normalize
|
||||
|
||||
# only load torchvision.transforms when needed
|
||||
try:
|
||||
import torchvision.transforms as T
|
||||
|
||||
# FIXME: add version check for gguf
|
||||
except ImportError as err:
|
||||
raise ImportError(
|
||||
"Please install torchvision via `pip install torchvision` to use Deepseek-VL2."
|
||||
) from err
|
||||
|
||||
transform_pipelines = [T.ToTensor()]
|
||||
|
||||
if normalize:
|
||||
transform_pipelines.append(T.Normalize(mean, std))
|
||||
|
||||
self.transform = T.Compose(transform_pipelines)
|
||||
|
||||
def __call__(self, pil_img: Image.Image):
|
||||
x = self.transform(pil_img)
|
||||
return x
|
||||
|
||||
|
||||
class DeepseekVLV2Processor(ProcessorMixin):
|
||||
tokenizer_class = ("LlamaTokenizer", "LlamaTokenizerFast")
|
||||
attributes = ["tokenizer"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
tokenizer: LlamaTokenizerFast,
|
||||
candidate_resolutions: Tuple[Tuple[int, int]],
|
||||
patch_size: int,
|
||||
downsample_ratio: int,
|
||||
image_mean: Tuple[float, float, float] = (0.5, 0.5, 0.5),
|
||||
image_std: Tuple[float, float, float] = (0.5, 0.5, 0.5),
|
||||
normalize: bool = True,
|
||||
image_token: str = "<image>",
|
||||
pad_token: str = "<|▁pad▁|>",
|
||||
add_special_token: bool = False,
|
||||
sft_format: str = "deepseek",
|
||||
mask_prompt: bool = True,
|
||||
ignore_id: int = -100,
|
||||
**kwargs,
|
||||
):
|
||||
|
||||
self.candidate_resolutions = candidate_resolutions
|
||||
self.image_size = candidate_resolutions[0][0]
|
||||
self.patch_size = patch_size
|
||||
self.image_mean = image_mean
|
||||
self.image_std = image_std
|
||||
self.normalize = normalize
|
||||
self.downsample_ratio = downsample_ratio
|
||||
|
||||
self.image_transform = ImageTransform(
|
||||
mean=image_mean, std=image_std, normalize=normalize
|
||||
)
|
||||
self.tokenizer = tokenizer
|
||||
# must set this,padding side with make a difference in batch inference
|
||||
self.tokenizer.padding_side = "left"
|
||||
|
||||
# add the pad_token as special token to use 'tokenizer.pad_token' and 'tokenizer.pad_token_id'
|
||||
if tokenizer.pad_token is None:
|
||||
self.tokenizer.add_special_tokens({"pad_token": pad_token})
|
||||
|
||||
# add image token
|
||||
image_token_id = self.tokenizer.vocab.get(image_token)
|
||||
if image_token_id is None:
|
||||
special_tokens = [image_token]
|
||||
special_tokens_dict = {"additional_special_tokens": special_tokens}
|
||||
self.tokenizer.add_special_tokens(special_tokens_dict)
|
||||
self.image_token_id = self.tokenizer.vocab.get(image_token)
|
||||
|
||||
# add five special tokens for grounding-related tasks
|
||||
# <|ref|>, <|/ref|>, <|det|>, <|/det|>, <|grounding|>
|
||||
special_tokens = ["<|ref|>", "<|/ref|>", "<|det|>", "<|/det|>", "<|grounding|>"]
|
||||
special_tokens_dict = {"additional_special_tokens": special_tokens}
|
||||
self.tokenizer.add_special_tokens(special_tokens_dict)
|
||||
|
||||
# add special tokens for SFT data
|
||||
special_tokens = ["<|User|>", "<|Assistant|>"]
|
||||
special_tokens_dict = {"additional_special_tokens": special_tokens}
|
||||
self.tokenizer.add_special_tokens(special_tokens_dict)
|
||||
|
||||
self.image_token = image_token
|
||||
self.pad_token = pad_token
|
||||
self.add_special_token = add_special_token
|
||||
self.sft_format = sft_format
|
||||
self.mask_prompt = mask_prompt
|
||||
self.ignore_id = ignore_id
|
||||
|
||||
super().__init__(
|
||||
tokenizer,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def format_messages_v2(self, messages, pil_images, max_req_input_len=-1):
|
||||
"""play the role of format_messages_v2 and get_images_info in the last version"""
|
||||
tokenized_data = []
|
||||
masked_tokenized_data = [] # labels
|
||||
images_list = []
|
||||
images_seq_mask = []
|
||||
images_spatial_crop = []
|
||||
|
||||
image_index = 0
|
||||
image_token_cnt = messages.count(self.image_token)
|
||||
tokenized_str, images, seq_mask, spatial_crop = self.tokenize_with_images(
|
||||
messages,
|
||||
pil_images[image_index : image_index + image_token_cnt],
|
||||
bos=True,
|
||||
eos=True,
|
||||
cropping=len(pil_images) <= 2,
|
||||
max_req_input_len=max_req_input_len,
|
||||
)
|
||||
|
||||
image_index = image_token_cnt
|
||||
tokenized_data += tokenized_str
|
||||
if self.mask_prompt:
|
||||
masked_tokenized_data += [self.ignore_id] * len(tokenized_str)
|
||||
else:
|
||||
masked_tokenized_data += tokenized_str
|
||||
images_list += images
|
||||
images_seq_mask += seq_mask
|
||||
images_spatial_crop += spatial_crop
|
||||
|
||||
assert len(tokenized_data) == len(
|
||||
images_seq_mask
|
||||
), f"format_messages_v2: tokenized_str's length {len(tokenized_str)} is not equal to imags_seq_mask's length {len(images_seq_mask)}"
|
||||
|
||||
return (
|
||||
tokenized_data,
|
||||
masked_tokenized_data,
|
||||
images_list,
|
||||
images_seq_mask,
|
||||
images_spatial_crop,
|
||||
)
|
||||
|
||||
@property
|
||||
def bos_id(self):
|
||||
return self.tokenizer.bos_token_id
|
||||
|
||||
@property
|
||||
def eos_id(self):
|
||||
return self.tokenizer.eos_token_id
|
||||
|
||||
@property
|
||||
def pad_id(self):
|
||||
return self.tokenizer.pad_token_id
|
||||
|
||||
def encode(self, text: str, bos: bool = True, eos: bool = False):
|
||||
t = self.tokenizer.encode(text, add_special_tokens=False)
|
||||
|
||||
if bos:
|
||||
t = [self.bos_id] + t
|
||||
if eos:
|
||||
t = t + [self.eos_id]
|
||||
|
||||
return t
|
||||
|
||||
def decode(self, t: List[int], **kwargs) -> str:
|
||||
return self.tokenizer.decode(t, **kwargs)
|
||||
|
||||
def process_one(
|
||||
self,
|
||||
prompt: str = None,
|
||||
conversations: List[Dict[str, str]] = None,
|
||||
images: List[Image.Image] = None,
|
||||
apply_sft_format: bool = False,
|
||||
inference_mode: bool = True,
|
||||
system_prompt: str = "",
|
||||
max_req_input_len: int = -1,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
|
||||
Args:
|
||||
prompt (str): the formatted prompt;
|
||||
conversations (List[Dict]): conversations with a list of messages;
|
||||
images (List[ImageType]): the list of images;
|
||||
apply_sft_format (bool): if prompt is not None, then apply the SFT format to prompt;
|
||||
if conversations is not None, then it will always apply the SFT format to conversations;
|
||||
inference_mode (bool): if True, then remove the last eos token;
|
||||
system_prompt (str): the system prompt;
|
||||
**kwargs:
|
||||
|
||||
Returns:
|
||||
outputs (BaseProcessorOutput): the output of the processor,
|
||||
- input_ids (torch.LongTensor): [N + image tokens]
|
||||
- target_ids (torch.LongTensor): [N + image tokens]
|
||||
- images (torch.FloatTensor): [n_images, 3, H, W]
|
||||
- image_id (int): the id of the image token
|
||||
- num_image_tokens (List[int]): the number of image tokens
|
||||
"""
|
||||
|
||||
assert (
|
||||
prompt is None or conversations is None
|
||||
), "prompt and conversations cannot be used at the same time."
|
||||
|
||||
(
|
||||
tokenized_str,
|
||||
masked_tokenized_str,
|
||||
images_list,
|
||||
images_seq_mask,
|
||||
images_spatial_crop,
|
||||
) = self.format_messages_v2(conversations, images, max_req_input_len)
|
||||
|
||||
assert (
|
||||
len(tokenized_str) == len(images_seq_mask) == len(masked_tokenized_str)
|
||||
), (
|
||||
f"tokenized_str's length {len(tokenized_str)}, input_ids' length {len(masked_tokenized_str)}, "
|
||||
f"imags_seq_mask's length {len(images_seq_mask)}, are not equal"
|
||||
)
|
||||
|
||||
input_ids = torch.LongTensor(tokenized_str)
|
||||
target_ids = torch.LongTensor(masked_tokenized_str)
|
||||
images_seq_mask = torch.tensor(images_seq_mask, dtype=torch.bool)
|
||||
|
||||
# set input_ids < 0 | input_ids == self.image_token_id as ignore_id
|
||||
target_ids[(input_ids < 0) | (input_ids == self.image_token_id)] = (
|
||||
self.ignore_id
|
||||
)
|
||||
input_ids[input_ids < 0] = self.pad_id
|
||||
|
||||
if inference_mode:
|
||||
assert input_ids[-1] == self.eos_id
|
||||
input_ids = input_ids[:-1]
|
||||
target_ids = target_ids[:-1]
|
||||
images_seq_mask = images_seq_mask[:-1]
|
||||
|
||||
if len(images_list) == 0:
|
||||
images = torch.zeros((1, 3, self.image_size, self.image_size))
|
||||
images_spatial_crop = torch.zeros((1, 2), dtype=torch.long)
|
||||
else:
|
||||
images = torch.stack(images_list, dim=0)
|
||||
images_spatial_crop = torch.tensor(images_spatial_crop, dtype=torch.long)
|
||||
|
||||
images_spatial_crop = torch.stack(
|
||||
[images_spatial_crop], dim=0
|
||||
) # stack the tensor to make it a batch of 1
|
||||
|
||||
prepare = VLChatProcessorOutput(
|
||||
input_ids=input_ids,
|
||||
target_ids=target_ids,
|
||||
pixel_values=images,
|
||||
images_seq_mask=images_seq_mask,
|
||||
images_spatial_crop=images_spatial_crop,
|
||||
)
|
||||
|
||||
return prepare
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
*,
|
||||
prompt: str = None,
|
||||
conversations: List[Dict[str, str]] = None,
|
||||
images: List[Image.Image] = None,
|
||||
apply_sft_format: bool = False,
|
||||
inference_mode: bool = True,
|
||||
system_prompt: str = "",
|
||||
max_req_input_len: int = -1,
|
||||
**kwargs,
|
||||
):
|
||||
prepare = self.process_one(
|
||||
prompt=prompt,
|
||||
conversations=conversations,
|
||||
images=images,
|
||||
apply_sft_format=apply_sft_format,
|
||||
inference_mode=inference_mode,
|
||||
system_prompt=system_prompt,
|
||||
max_req_input_len=max_req_input_len,
|
||||
)
|
||||
|
||||
return prepare
|
||||
|
||||
def find_all_indices(self, messages, target_value):
|
||||
indices = []
|
||||
for index, item in enumerate(messages):
|
||||
if item == target_value:
|
||||
indices.append(index)
|
||||
return indices
|
||||
|
||||
def tokenize_with_images(
|
||||
self,
|
||||
conversation: str,
|
||||
images: List[Image.Image],
|
||||
bos: bool = True,
|
||||
eos: bool = True,
|
||||
cropping: bool = True,
|
||||
max_req_input_len: int = -1,
|
||||
):
|
||||
"""Tokenize text with <image> tags."""
|
||||
images_list, images_seq_mask, images_spatial_crop = [], [], []
|
||||
text_splits = conversation.split(self.image_token)
|
||||
tokenized_str = []
|
||||
for text_sep, image in zip(text_splits, images):
|
||||
"""encode text_sep"""
|
||||
tokenized_sep = self.encode(text_sep, bos=False, eos=False)
|
||||
tokenized_str += tokenized_sep
|
||||
images_seq_mask += [False] * len(tokenized_sep)
|
||||
|
||||
"""select best resolution for anyres"""
|
||||
if cropping:
|
||||
best_width, best_height = select_best_resolution(
|
||||
image.size, self.candidate_resolutions
|
||||
)
|
||||
else:
|
||||
best_width, best_height = self.image_size, self.image_size
|
||||
# print(image.size, (best_width, best_height)) # check the select_best_resolutions func
|
||||
|
||||
"""process the global view"""
|
||||
global_view = ImageOps.pad(
|
||||
image,
|
||||
(self.image_size, self.image_size),
|
||||
color=tuple(int(x * 255) for x in self.image_transform.mean),
|
||||
)
|
||||
images_list.append(self.image_transform(global_view))
|
||||
|
||||
"""process the local views"""
|
||||
local_view = ImageOps.pad(
|
||||
image,
|
||||
(best_width, best_height),
|
||||
color=tuple(int(x * 255) for x in self.image_transform.mean),
|
||||
)
|
||||
for i in range(0, best_height, self.image_size):
|
||||
for j in range(0, best_width, self.image_size):
|
||||
images_list.append(
|
||||
self.image_transform(
|
||||
local_view.crop(
|
||||
(j, i, j + self.image_size, i + self.image_size)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
"""record height / width crop num"""
|
||||
num_width_tiles, num_height_tiles = (
|
||||
best_width // self.image_size,
|
||||
best_height // self.image_size,
|
||||
)
|
||||
images_spatial_crop.append([num_width_tiles, num_height_tiles])
|
||||
|
||||
"""add image tokens"""
|
||||
h = w = math.ceil(
|
||||
(self.image_size // self.patch_size) / self.downsample_ratio
|
||||
)
|
||||
# global views tokens h * (w + 1), 1 is for line separator
|
||||
tokenized_image = [self.image_token_id] * h * (w + 1)
|
||||
# add a separator between global and local views
|
||||
tokenized_image += [self.image_token_id]
|
||||
# local views tokens, (num_height_tiles * h) * (num_width_tiles * w + 1)
|
||||
tokenized_image += (
|
||||
[self.image_token_id]
|
||||
* (num_height_tiles * h)
|
||||
* (num_width_tiles * w + 1)
|
||||
)
|
||||
|
||||
tokenized_str += tokenized_image
|
||||
images_seq_mask += [True] * len(tokenized_image)
|
||||
# print(width_crop_num, height_crop_num, len(tokenized_image)) # test the correctness of the number of image-related tokens
|
||||
|
||||
"""process the last text split"""
|
||||
tokenized_sep = self.encode(text_splits[-1], bos=False, eos=False)
|
||||
# deal with video, limit with request len
|
||||
if max_req_input_len > -1:
|
||||
if max_req_input_len < len(tokenized_sep) + len(tokenized_str) - 1:
|
||||
rest = max_req_input_len - len(tokenized_sep) - 1 - 1024
|
||||
tokenized_str = tokenized_str[:rest]
|
||||
images_seq_mask = images_seq_mask[:rest]
|
||||
tokenized_str += tokenized_sep
|
||||
images_seq_mask += [False] * len(tokenized_sep)
|
||||
|
||||
"""add the bos and eos tokens"""
|
||||
if bos:
|
||||
tokenized_str = [self.bos_id] + tokenized_str
|
||||
images_seq_mask = [False] + images_seq_mask
|
||||
if eos:
|
||||
tokenized_str = tokenized_str + [self.eos_id]
|
||||
images_seq_mask = images_seq_mask + [False]
|
||||
|
||||
assert len(tokenized_str) == len(
|
||||
images_seq_mask
|
||||
), f"tokenize_with_images func: tokenized_str's length {len(tokenized_str)} is not equal to imags_seq_mask's length {len(images_seq_mask)}"
|
||||
|
||||
return tokenized_str, images_list, images_seq_mask, images_spatial_crop
|
||||
|
||||
|
||||
class DeepseekVL2VisionEncoderConfig(PretrainedConfig):
|
||||
model_type: str = "vision"
|
||||
|
||||
model_name: str = "siglip_large_patch16_384"
|
||||
image_size: int = 384
|
||||
patch_size: int = 16
|
||||
width: int = 1024
|
||||
layers: int = 24
|
||||
heads: int = 16
|
||||
mlp_ratio: int = 4
|
||||
global_pool: str = "map"
|
||||
ignore_head: bool = True
|
||||
class_token: bool = False
|
||||
num_classes: int = 0
|
||||
use_checkpoint: bool = False
|
||||
weight_init: str = "skip"
|
||||
deterministic: bool = False
|
||||
num_recomputing_layers: int = 0
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
model_name: str = "siglip_large_patch16_384",
|
||||
image_size: int = 384,
|
||||
patch_size: int = 16,
|
||||
width: int = 1024,
|
||||
layers: int = 24,
|
||||
heads: int = 16,
|
||||
mlp_ratio: int = 4,
|
||||
global_pool: str = "map",
|
||||
ignore_head: bool = True,
|
||||
class_token: bool = False,
|
||||
num_classes: int = 0,
|
||||
use_checkpoint: bool = False,
|
||||
**kwargs,
|
||||
):
|
||||
self.model_name = model_name
|
||||
self.image_size = image_size
|
||||
self.patch_size = patch_size
|
||||
self.width = width
|
||||
self.layers = layers
|
||||
self.heads = heads
|
||||
self.mlp_ratio = mlp_ratio
|
||||
self.global_pool = global_pool
|
||||
self.ignore_head = ignore_head
|
||||
self.class_token = class_token
|
||||
self.num_classes = num_classes
|
||||
self.use_checkpoint = use_checkpoint
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
class DeepseekVL2MlpProjectorConfig(PretrainedConfig):
|
||||
model_type = "mlp_projector"
|
||||
projector_type: str = "downsample_mlp_gelu"
|
||||
input_dim: int = 1152
|
||||
n_embed: int = 2048
|
||||
depth: int = 2
|
||||
mlp_ratio: int = 1
|
||||
downsample_ratio: int = 2
|
||||
token_pooling: bool = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
projector_type: str = "downsample_mlp_gelu",
|
||||
input_dim: int = 1152,
|
||||
n_embed: int = 2048,
|
||||
depth: int = 2,
|
||||
mlp_ratio: int = 1,
|
||||
downsample_ratio: int = 2,
|
||||
**kwargs,
|
||||
):
|
||||
self.projector_type = projector_type
|
||||
self.input_dim = input_dim
|
||||
self.n_embed = n_embed
|
||||
self.depth = depth
|
||||
self.mlp_ratio = mlp_ratio
|
||||
self.downsample_ratio = downsample_ratio
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
class DeepseekV2Config(PretrainedConfig):
|
||||
|
||||
model_type = "deepseek_v2"
|
||||
keys_to_ignore_at_inference = ["past_key_values"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vocab_size=102400,
|
||||
hidden_size=4096,
|
||||
intermediate_size=11008,
|
||||
moe_intermediate_size=1407,
|
||||
num_hidden_layers=30,
|
||||
num_attention_heads=32,
|
||||
num_key_value_heads=32,
|
||||
n_shared_experts=None,
|
||||
n_routed_experts=None,
|
||||
ep_size=1,
|
||||
routed_scaling_factor=1.0,
|
||||
kv_lora_rank=512,
|
||||
q_lora_rank=1536,
|
||||
qk_rope_head_dim=64,
|
||||
v_head_dim=128,
|
||||
qk_nope_head_dim=128,
|
||||
topk_method="gready",
|
||||
n_group=None,
|
||||
topk_group=None,
|
||||
num_experts_per_tok=None,
|
||||
moe_layer_freq=1,
|
||||
first_k_dense_replace=0,
|
||||
norm_topk_prob=False,
|
||||
scoring_func="softmax",
|
||||
aux_loss_alpha=0.001,
|
||||
seq_aux=True,
|
||||
hidden_act="silu",
|
||||
max_position_embeddings=2048,
|
||||
initializer_range=0.02,
|
||||
rms_norm_eps=1e-6,
|
||||
use_cache=True,
|
||||
pad_token_id=None,
|
||||
bos_token_id=100000,
|
||||
eos_token_id=100001,
|
||||
pretraining_tp=1,
|
||||
tie_word_embeddings=False,
|
||||
rope_theta=10000.0,
|
||||
rope_scaling=None,
|
||||
attention_bias=False,
|
||||
attention_dropout=0.0,
|
||||
use_mla=True,
|
||||
**kwargs,
|
||||
):
|
||||
self.vocab_size = vocab_size
|
||||
self.max_position_embeddings = max_position_embeddings
|
||||
self.hidden_size = hidden_size
|
||||
self.intermediate_size = intermediate_size
|
||||
self.moe_intermediate_size = moe_intermediate_size
|
||||
self.num_hidden_layers = num_hidden_layers
|
||||
self.num_attention_heads = num_attention_heads
|
||||
self.n_shared_experts = n_shared_experts
|
||||
self.n_routed_experts = n_routed_experts
|
||||
self.ep_size = ep_size
|
||||
self.routed_scaling_factor = routed_scaling_factor
|
||||
self.kv_lora_rank = kv_lora_rank
|
||||
self.q_lora_rank = q_lora_rank
|
||||
self.qk_rope_head_dim = qk_rope_head_dim
|
||||
self.v_head_dim = v_head_dim
|
||||
self.qk_nope_head_dim = qk_nope_head_dim
|
||||
self.topk_method = topk_method
|
||||
self.n_group = n_group
|
||||
self.topk_group = topk_group
|
||||
self.num_experts_per_tok = num_experts_per_tok
|
||||
self.moe_layer_freq = moe_layer_freq
|
||||
self.first_k_dense_replace = first_k_dense_replace
|
||||
self.norm_topk_prob = norm_topk_prob
|
||||
self.scoring_func = scoring_func
|
||||
self.aux_loss_alpha = aux_loss_alpha
|
||||
self.seq_aux = seq_aux
|
||||
# for backward compatibility
|
||||
if num_key_value_heads is None:
|
||||
num_key_value_heads = num_attention_heads
|
||||
|
||||
self.num_key_value_heads = num_key_value_heads
|
||||
self.hidden_act = hidden_act
|
||||
self.initializer_range = initializer_range
|
||||
self.rms_norm_eps = float(rms_norm_eps)
|
||||
self.pretraining_tp = pretraining_tp
|
||||
self.use_cache = use_cache
|
||||
self.rope_theta = rope_theta
|
||||
self.rope_scaling = rope_scaling
|
||||
self.attention_bias = attention_bias
|
||||
self.attention_dropout = attention_dropout
|
||||
self.use_mla = use_mla
|
||||
|
||||
super().__init__(
|
||||
pad_token_id=pad_token_id,
|
||||
bos_token_id=bos_token_id,
|
||||
eos_token_id=eos_token_id,
|
||||
tie_word_embeddings=tie_word_embeddings,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
class DeepseekVL2Config(PretrainedConfig):
|
||||
model_type = "deepseek_vl_v2"
|
||||
vision_config: DeepseekVL2VisionEncoderConfig = None
|
||||
projector_config: DeepseekVL2MlpProjectorConfig = None
|
||||
language_config: DeepseekV2Config = None
|
||||
|
||||
tile_tag: str = "2D"
|
||||
global_view_pos: str = "head"
|
||||
candidate_resolutions: Tuple[Tuple[int, int]] = ((384, 384),)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
tile_tag: str = "tile_tag",
|
||||
global_view_pos: str = "head",
|
||||
candidate_resolutions: Tuple[Tuple[int, int]] = ((384, 384),),
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
vision_config = kwargs.get("vision_config", {})
|
||||
self.vision_config = DeepseekVL2VisionEncoderConfig(**vision_config)
|
||||
|
||||
projector_config = kwargs.get("projector_config", {})
|
||||
self.projector_config = DeepseekVL2MlpProjectorConfig(**projector_config)
|
||||
|
||||
language_config = kwargs.get("language_config", {})
|
||||
if isinstance(language_config, DeepseekV2Config):
|
||||
self.language_config = language_config
|
||||
else:
|
||||
self.language_config = DeepseekV2Config(**language_config)
|
||||
|
||||
self.tile_tag = tile_tag
|
||||
self.global_view_pos = global_view_pos
|
||||
self.candidate_resolutions = candidate_resolutions
|
||||
self.architectures = ["DeepseekVL2ForCausalLM"]
|
||||
|
||||
|
||||
AutoProcessor.register(DeepseekVL2Config, DeepseekVLV2Processor)
|
||||
21
third_party/sglang/python/sglang/srt/configs/device_config.py
vendored
Normal file
21
third_party/sglang/python/sglang/srt/configs/device_config.py
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
import torch
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
SUPPORTED_DEVICES = ["cuda", "xpu", "hpu", "cpu", "npu", "musa", "mps"]
|
||||
|
||||
|
||||
class DeviceConfig:
|
||||
device: Optional[torch.device]
|
||||
gpu_id: Optional[int]
|
||||
|
||||
def __init__(self, device: str = "cuda", gpu_id: int = -1) -> None:
|
||||
if device in SUPPORTED_DEVICES:
|
||||
self.device_type = device
|
||||
else:
|
||||
raise RuntimeError(f"Not supported device type: {device}")
|
||||
self.device = torch.device(self.device_type)
|
||||
self.gpu_id = gpu_id
|
||||
64
third_party/sglang/python/sglang/srt/configs/dots_ocr.py
vendored
Normal file
64
third_party/sglang/python/sglang/srt/configs/dots_ocr.py
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
from typing import Optional
|
||||
|
||||
from transformers import AutoProcessor, Qwen2_5_VLProcessor
|
||||
from transformers.image_processing_utils import BaseImageProcessor
|
||||
from transformers.models.qwen2 import Qwen2Config
|
||||
|
||||
from sglang.srt.configs.dots_vlm import DotsVisionConfig
|
||||
|
||||
|
||||
class DotsOCRConfig(Qwen2Config):
|
||||
model_type = "dots_ocr"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
image_token_id=151665,
|
||||
video_token_id=151656,
|
||||
vision_config: Optional[dict] = None,
|
||||
*args,
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.image_token_id = image_token_id
|
||||
self.video_token_id = video_token_id
|
||||
self.vision_config = DotsVisionConfig(**(vision_config or {}))
|
||||
|
||||
def save_pretrained(self, save_directory, **kwargs):
|
||||
self._auto_class = None
|
||||
super().save_pretrained(save_directory, **kwargs)
|
||||
|
||||
|
||||
class DummyVideoProcessor(BaseImageProcessor):
|
||||
model_input_names = ["pixel_values"]
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
return None
|
||||
|
||||
|
||||
class DotsVLProcessor(Qwen2_5_VLProcessor):
|
||||
def __init__(
|
||||
self,
|
||||
image_processor=None,
|
||||
tokenizer=None,
|
||||
video_processor=None,
|
||||
chat_template=None,
|
||||
**kwargs
|
||||
):
|
||||
if video_processor is None:
|
||||
video_processor = DummyVideoProcessor()
|
||||
super().__init__(
|
||||
image_processor, tokenizer, video_processor, chat_template=chat_template
|
||||
)
|
||||
self.image_token = (
|
||||
"<|imgpad|>"
|
||||
if not hasattr(tokenizer, "image_token")
|
||||
else tokenizer.image_token
|
||||
)
|
||||
self.image_token_id = (
|
||||
tokenizer.image_token_id
|
||||
if getattr(tokenizer, "image_token_id", None) is not None
|
||||
else tokenizer.convert_tokens_to_ids(self.image_token)
|
||||
)
|
||||
|
||||
|
||||
AutoProcessor.register(DotsOCRConfig, DotsVLProcessor)
|
||||
134
third_party/sglang/python/sglang/srt/configs/dots_vlm.py
vendored
Normal file
134
third_party/sglang/python/sglang/srt/configs/dots_vlm.py
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
from transformers import AutoProcessor, PretrainedConfig
|
||||
from transformers.processing_utils import ProcessingKwargs
|
||||
|
||||
try:
|
||||
from transformers import Qwen2_5_VLProcessor
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"Qwen2_5_VLProcessor can not be found. Please upgrade your transformers version."
|
||||
)
|
||||
|
||||
from sglang.srt.configs.deepseekvl2 import DeepseekV2Config
|
||||
|
||||
|
||||
class DotsVisionConfig(PretrainedConfig):
|
||||
model_type: str = "dots_vit"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
embed_dim: int = 1536, # vision encoder embed size
|
||||
hidden_size: int = 1536, # after merger hidden size
|
||||
intermediate_size: int = 4224,
|
||||
num_hidden_layers: int = 42,
|
||||
num_attention_heads: int = 12,
|
||||
num_channels: int = 3,
|
||||
patch_size: int = 14,
|
||||
spatial_merge_size: int = 2,
|
||||
temporal_patch_size: int = 1,
|
||||
rms_norm_eps: float = 1e-5,
|
||||
use_bias: bool = False,
|
||||
attn_implementation="flash_attention_2", # "eager","sdpa","flash_attention_2"
|
||||
initializer_range=0.02,
|
||||
init_merger_std=0.02,
|
||||
is_causal=False, # ve causal forward
|
||||
post_norm=True,
|
||||
gradient_checkpointing=False,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
self.embed_dim = embed_dim
|
||||
self.hidden_size = hidden_size
|
||||
self.intermediate_size = intermediate_size
|
||||
self.num_hidden_layers = num_hidden_layers
|
||||
self.num_attention_heads = num_attention_heads
|
||||
self.num_channels = num_channels
|
||||
self.patch_size = patch_size
|
||||
self.spatial_merge_size = spatial_merge_size
|
||||
self.temporal_patch_size = temporal_patch_size
|
||||
self.rms_norm_eps = rms_norm_eps
|
||||
self.use_bias = use_bias
|
||||
self.attn_implementation = attn_implementation
|
||||
self.initializer_range = initializer_range
|
||||
self.init_merger_std = init_merger_std
|
||||
self.is_causal = is_causal
|
||||
self.post_norm = post_norm
|
||||
self.gradient_checkpointing = gradient_checkpointing
|
||||
|
||||
|
||||
class DotsVLMConfig(PretrainedConfig):
|
||||
model_type = "dots_vlm"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
vision_config = kwargs.get("vision_config", {})
|
||||
self.im_span_id = kwargs.get("image_token_id", 128815)
|
||||
self.video_span_id = kwargs.get("video_token_id", 128836)
|
||||
self.vision_config = DotsVisionConfig(**vision_config)
|
||||
self.language_config = DeepseekV2Config(**kwargs)
|
||||
self.architectures = ["DotsVLMForCausalLM"]
|
||||
|
||||
|
||||
class DotsVLMProcessorKwargs(ProcessingKwargs, total=False):
|
||||
_defaults = {
|
||||
"text_kwargs": {
|
||||
"padding": False,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class DotsVLMProcessor(Qwen2_5_VLProcessor):
|
||||
r"""
|
||||
Constructs a DotsVLM processor which derives from Qwen2_5_VLProcessor, but overrides the image and video token ids.
|
||||
Besides, its tokenizer is a LlamaTokenizerFast instead of Qwen2TokenizerFast.
|
||||
[`DotsVLMProcessor`] offers all the functionalities of [`DotsVisionConfig`] and [`LlamaTokenizerFast`]. See the
|
||||
[`~DotsVLMProcessor.__call__`] and [`~DotsVLMProcessor.decode`] for more information.
|
||||
Args:
|
||||
image_processor ([`Qwen2VLImageProcessor`], *optional*):
|
||||
The image processor is a required input.
|
||||
tokenizer ([`LlamaTokenizerFast`], *optional*):
|
||||
The tokenizer is a required input.
|
||||
chat_template (`str`, *optional*): A Jinja template which will be used to convert lists of messages
|
||||
in a chat into a tokenizable string.
|
||||
"""
|
||||
|
||||
attributes = ["image_processor", "tokenizer"]
|
||||
|
||||
valid_kwargs = ["chat_template"]
|
||||
|
||||
tokenizer_class = ("LlamaTokenizer", "LlamaTokenizerFast")
|
||||
|
||||
def __init__(
|
||||
self, image_processor=None, tokenizer=None, chat_template=None, **kwargs
|
||||
):
|
||||
super().__init__(image_processor, tokenizer, chat_template=chat_template)
|
||||
self.image_token = (
|
||||
"<|imgpad|>"
|
||||
if not hasattr(tokenizer, "image_token")
|
||||
else tokenizer.image_token
|
||||
)
|
||||
self.video_token = (
|
||||
"<|video_pad|>"
|
||||
if not hasattr(tokenizer, "video_token")
|
||||
else tokenizer.video_token
|
||||
)
|
||||
self.img_token = (
|
||||
"<|img|>" if not hasattr(tokenizer, "img_token") else tokenizer.img_token
|
||||
)
|
||||
self.endofimg_token = (
|
||||
"<|endofimg|>"
|
||||
if not hasattr(tokenizer, "endofimg_token")
|
||||
else tokenizer.endofimg_token
|
||||
)
|
||||
self.image_token_id = (
|
||||
tokenizer.image_token_id
|
||||
if getattr(tokenizer, "image_token_id", None)
|
||||
else tokenizer.encode(self.image_token)[0]
|
||||
)
|
||||
self.video_token_id = (
|
||||
tokenizer.video_token_id
|
||||
if getattr(tokenizer, "video_token_id", None)
|
||||
else tokenizer.encode(self.video_token)[0]
|
||||
)
|
||||
|
||||
|
||||
AutoProcessor.register(DotsVLMConfig, DotsVLMProcessor)
|
||||
196
third_party/sglang/python/sglang/srt/configs/exaone.py
vendored
Normal file
196
third_party/sglang/python/sglang/srt/configs/exaone.py
vendored
Normal file
@@ -0,0 +1,196 @@
|
||||
# coding=utf-8
|
||||
# Copyright 2024 The LG AI Research EXAONE Lab. All rights reserved.
|
||||
# Copyright 2024 The LG CNS AI Engineering Team.
|
||||
# Copyright 2023-2024 SGLang Team.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
"""EXAONE model configuration"""
|
||||
|
||||
from typing import Any, Dict
|
||||
|
||||
from transformers.configuration_utils import PretrainedConfig
|
||||
from transformers.utils import logging
|
||||
|
||||
logger = logging.get_logger(__name__)
|
||||
|
||||
EXAONE_PRETRAINED_CONFIG_ARCHIVE_MAP: Dict[str, Any] = {}
|
||||
|
||||
|
||||
# ruff: noqa: E501
|
||||
class ExaoneConfig(PretrainedConfig):
|
||||
r"""
|
||||
This is the configuration class to store the configuration of a :class:`~transformers.ExaoneModel`. It is used to
|
||||
instantiate a EXAONE model according to the specified arguments, defining the model architecture. Instantiating a
|
||||
configuration with the defaults will yield a similar configuration to that of the Exaone
|
||||
|
||||
Configuration objects inherit from :class:`~transformers.PretrainedConfig` and can be used to control the model
|
||||
outputs. Read the documentation from :class:`~transformers.PretrainedConfig` for more information.
|
||||
|
||||
|
||||
Args:
|
||||
vocab_size (:obj:`int`, `optional`, defaults to 102400):
|
||||
Vocabulary size of the EXAONE model. Defines the number of different tokens that can be represented by the
|
||||
:obj:`inputs_ids` passed when calling :class:`~transformers.ExaoneModel`. Vocabulary size of the model.
|
||||
Defines the different tokens that can be represented by the `inputs_ids` passed to the forward method of
|
||||
:class:`~transformers.EXAONEModel`.
|
||||
max_position_embeddings (:obj:`int`, `optional`, defaults to 2048):
|
||||
The maximum sequence length that this model might ever be used with. Typically set this to something large
|
||||
just in case (e.g., 512 or 1024 or 2048).
|
||||
hidden_size (:obj:`int`, `optional`, defaults to 2048):
|
||||
Dimensionality of the encoder layers and the pooler layer.
|
||||
num_layers (:obj:`int`, `optional`, defaults to 32):
|
||||
Number of hidden layers in the Transformer encoder.
|
||||
num_attention_heads (:obj:`int`, `optional`, defaults to 32):
|
||||
Number of attention heads for each attention layer in the Transformer decoder.
|
||||
num_key_value_heads (:obj:`int`, `optional`):
|
||||
This is the number of key_value heads that should be used to implement Grouped Query Attention. If
|
||||
`num_key_value_heads=num_attention_heads`, the model will use Multi Head Attention (MHA), if
|
||||
`num_key_value_heads=1 the model will use Multi Query Attention (MQA) otherwise GQA is used. When
|
||||
converting a multi-head checkpoint to a GQA checkpoint, each group key and value head should be constructed
|
||||
by meanpooling all the original heads within that group. For more details checkout [this
|
||||
paper](https://arxiv.org/pdf/2305.13245.pdf). If it is not specified, will default to
|
||||
`num_attention_heads`.
|
||||
intermediate_size (:obj:`int`, `optional`, defaults to `hidden_size * 4`):
|
||||
Dimensionality of the "intermediate" (i.e., feed-forward) layer in the Transformer encoder.
|
||||
activation_function (:obj:`str` or :obj:`function`, `optional`, defaults to :obj:`"silu"`):
|
||||
The non-linear activation function (function or string) in the decoder.
|
||||
rope_theta (:obj:`float`, `optional`, defaults to 10000.0):
|
||||
The base period of the RoPE embeddings.
|
||||
rope_scaling (:obj:`Dict`, `optional`):
|
||||
Dictionary containing the scaling configuration for the RoPE embeddings. NOTE: if you apply new rope type
|
||||
and you expect the model to work on longer `max_position_embeddings`, we recommend you to update this value
|
||||
accordingly.
|
||||
Expected contents:
|
||||
`rope_type` (:obj:`str`):
|
||||
The sub-variant of RoPE to use. Can be one of ['default', 'linear', 'dynamic', 'yarn', 'longrope',
|
||||
'llama3'], with 'default' being the original RoPE implementation.
|
||||
`factor` (:obj:`float`, `optional`):
|
||||
Used with all rope types except 'default'. The scaling factor to apply to the RoPE embeddings. In
|
||||
most scaling types, a `factor` of x will enable the model to handle sequences of length x *
|
||||
original maximum pre-trained length.
|
||||
`original_max_position_embeddings` (:obj:`int`, `optional`):
|
||||
Used with 'dynamic', 'longrope' and 'llama3'. The original max position embeddings used during
|
||||
pretraining.
|
||||
`attention_factor` (:obj:`float`, `optional`):
|
||||
Used with 'yarn' and 'longrope'. The scaling factor to be applied on the attention
|
||||
computation. If unspecified, it defaults to value recommended by the implementation, using the
|
||||
`factor` field to infer the suggested value.
|
||||
`beta_fast` (:obj:`float`, `optional`):
|
||||
Only used with 'yarn'. Parameter to set the boundary for extrapolation (only) in the linear
|
||||
ramp function. If unspecified, it defaults to 32.
|
||||
`beta_slow` (:obj:`float`, `optional`):
|
||||
Only used with 'yarn'. Parameter to set the boundary for interpolation (only) in the linear
|
||||
ramp function. If unspecified, it defaults to 1.
|
||||
`short_factor` (:obj:`List[float]`, `optional`):
|
||||
Only used with 'longrope'. The scaling factor to be applied to short contexts (<
|
||||
`original_max_position_embeddings`). Must be a list of numbers with the same length as the hidden
|
||||
size divided by the number of attention heads divided by 2
|
||||
`long_factor` (:obj:`List[float]`, `optional`):
|
||||
Only used with 'longrope'. The scaling factor to be applied to long contexts (<
|
||||
`original_max_position_embeddings`). Must be a list of numbers with the same length as the hidden
|
||||
size divided by the number of attention heads divided by 2
|
||||
`low_freq_factor` (:obj:`float`, `optional`):
|
||||
Only used with 'llama3'. Scaling factor applied to low frequency components of the RoPE
|
||||
`high_freq_factor` (:obj:`float`, `optional`):
|
||||
Only used with 'llama3'. Scaling factor applied to high frequency components of the RoPE
|
||||
embed_dropout (:obj:`float`, `optional`, defaults to 0.0):
|
||||
The dropout probabilitiy for all fully connected layers in the embeddings, encoder, and pooler.
|
||||
attention_dropout (:obj:`float`, `optional`, defaults to 0.0):
|
||||
The dropout ratio for the attention probabilities.
|
||||
layer_norm_epsilon (:obj:`float`, `optional`, defaults to 1e-5):
|
||||
The epsilon used by the layer normalization layers.
|
||||
initializer_range (:obj:`float`, `optional`, defaults to 0.02):
|
||||
The standard deviation of the truncated_normal_initializer for initializing all weight matrices.
|
||||
use_cache (:obj:`bool`, `optional`, defaults to :obj:`True`):
|
||||
Whether or not the model should return the last key/values attentions (not used by all models). Only
|
||||
relevant if ``configs.is_decoder=True``.
|
||||
bos_token_id (:obj:`int`, `optional`, defaults to 0):
|
||||
Beginning of stream token id.
|
||||
eos_token_id (:obj:`int`, `optional`, defaults to 2):
|
||||
End of stream token id.
|
||||
tie_word_embeddings (:obj:`bool`, `optional`, defaults to :obj:`True`):
|
||||
Whether to tie weight embeddings
|
||||
gradient_checkpointing (:obj:`bool`, `optional`, defaults to :obj:`False`):
|
||||
If True, use gradient checkpointing to save memory at the expense of slower backward pass.
|
||||
|
||||
Example::
|
||||
|
||||
>>> from transformers import EXAONEModel, ExaoneConfig
|
||||
|
||||
>>> # Initializing a EXAONE configuration
|
||||
>>> configuration = ExaoneConfig()
|
||||
|
||||
>>> # Initializing a model from configuration
|
||||
>>> model = EXAONEModel(configuration)
|
||||
|
||||
>>> # Accessing the model configuration
|
||||
>>> configuration = model.configs
|
||||
"""
|
||||
|
||||
model_type = "exaone"
|
||||
keys_to_ignore_at_inference = ["past_key_values"]
|
||||
attribute_map = {"num_hidden_layers": "num_layers"}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vocab_size=102400,
|
||||
max_position_embeddings=2048,
|
||||
hidden_size=2048,
|
||||
num_layers=32,
|
||||
num_attention_heads=32,
|
||||
num_key_value_heads=None,
|
||||
intermediate_size=None,
|
||||
activation_function="silu",
|
||||
rope_theta=10000.0,
|
||||
rope_scaling=None,
|
||||
embed_dropout=0.0,
|
||||
attention_dropout=0.0,
|
||||
layer_norm_epsilon=1e-5,
|
||||
initializer_range=0.02,
|
||||
use_cache=True,
|
||||
bos_token_id=0,
|
||||
eos_token_id=2,
|
||||
tie_word_embeddings=True,
|
||||
**kwargs
|
||||
):
|
||||
self.vocab_size = vocab_size
|
||||
self.max_position_embeddings = max_position_embeddings
|
||||
self.hidden_size = hidden_size
|
||||
self.num_layers = num_layers
|
||||
self.num_attention_heads = num_attention_heads
|
||||
self.num_hidden_layers = num_layers
|
||||
if num_key_value_heads is None:
|
||||
num_key_value_heads = num_attention_heads
|
||||
self.num_key_value_heads = num_key_value_heads
|
||||
if intermediate_size:
|
||||
self.intermediate_size = intermediate_size
|
||||
else:
|
||||
self.intermediate_size = hidden_size * 4
|
||||
self.activation_function = activation_function
|
||||
self.embed_dropout = embed_dropout
|
||||
self.attention_dropout = attention_dropout
|
||||
self.layer_norm_epsilon = layer_norm_epsilon
|
||||
self.initializer_range = initializer_range
|
||||
self.use_cache = use_cache
|
||||
self.rope_theta = rope_theta
|
||||
self.rope_scaling = rope_scaling
|
||||
|
||||
self.bos_token_id = bos_token_id
|
||||
self.eos_token_id = eos_token_id
|
||||
|
||||
super().__init__(
|
||||
bos_token_id=bos_token_id,
|
||||
eos_token_id=eos_token_id,
|
||||
tie_word_embeddings=tie_word_embeddings,
|
||||
**kwargs
|
||||
)
|
||||
315
third_party/sglang/python/sglang/srt/configs/falcon_h1.py
vendored
Normal file
315
third_party/sglang/python/sglang/srt/configs/falcon_h1.py
vendored
Normal file
@@ -0,0 +1,315 @@
|
||||
# coding=utf-8
|
||||
# Copyright 2024 TII and the HuggingFace Inc. team. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
"""Falcon-H1 model configuration"""
|
||||
|
||||
from transformers.configuration_utils import PretrainedConfig
|
||||
from transformers.utils import logging
|
||||
|
||||
from sglang.srt.configs.mamba_utils import (
|
||||
Mamba2CacheParams,
|
||||
Mamba2StateShape,
|
||||
mamba2_state_dtype,
|
||||
)
|
||||
|
||||
logger = logging.get_logger(__name__)
|
||||
|
||||
|
||||
class FalconH1Config(PretrainedConfig):
|
||||
r"""
|
||||
This is the configuration class to store the configuration of a [`FalconH1Model`]. It is used to instantiate a
|
||||
FalconH1Model model according to the specified arguments, defining the model architecture. Instantiating a configuration
|
||||
with defaults taken from [ibm-fms/FalconH1-9.8b-2.2T-hf](https://huggingface.co/ibm-fms/FalconH1-9.8b-2.2T-hf).
|
||||
The FalconH1Model is a hybrid [mamba2](https://github.com/state-spaces/mamba) architecture with SwiGLU.
|
||||
The checkpoints are jointly trained by IBM, Princeton, and UIUC.
|
||||
Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the
|
||||
documentation from [`PretrainedConfig`] for more information.
|
||||
Args:
|
||||
vocab_size (`int`, *optional*, defaults to 128000):
|
||||
Vocabulary size of the FalconH1 model. Defines the number of different tokens that can be represented by the
|
||||
`inputs_ids` passed when calling [`FalconH1Model`]
|
||||
tie_word_embeddings (`bool`, *optional*, defaults to `False`):
|
||||
Whether the model's input and output word embeddings should be tied. Note that this is only relevant if the
|
||||
model has a output word embedding layer.
|
||||
hidden_size (`int`, *optional*, defaults to 4096):
|
||||
Dimension of the hidden representations.
|
||||
intermediate_size (`int`, *optional*, defaults to 14336):
|
||||
Dimension of the MLP representations.
|
||||
num_hidden_layers (`int`, *optional*, defaults to 32):
|
||||
Number of hidden layers in the Transformer encoder.
|
||||
num_attention_heads (`int`, *optional*, defaults to 32):
|
||||
Number of attention heads for each attention layer in the Transformer encoder.
|
||||
num_key_value_heads (`int`, *optional*, defaults to 8):
|
||||
This is the number of key_value heads that should be used to implement Grouped Query Attention. If
|
||||
`num_key_value_heads=num_attention_heads`, the model will use Multi Head Attention (MHA), if
|
||||
`num_key_value_heads=1` the model will use Multi Query Attention (MQA) otherwise GQA is used. When
|
||||
converting a multi-head checkpoint to a GQA checkpoint, each group key and value head should be constructed
|
||||
by meanpooling all the original heads within that group. For more details, check out [this
|
||||
paper](https://huggingface.co/papers/2305.13245). If it is not specified, will default to `8`.
|
||||
hidden_act (`str` or `function`, *optional*, defaults to `"silu"`):
|
||||
The non-linear activation function (function or string) in the decoder.
|
||||
initializer_range (`float`, *optional*, defaults to 0.02):
|
||||
The standard deviation of the truncated_normal_initializer for initializing all weight matrices.
|
||||
rms_norm_eps (`float`, *optional*, defaults to 1e-05):
|
||||
The epsilon used by the rms normalization layers.
|
||||
use_cache (`bool`, *optional*, defaults to `True`):
|
||||
Whether or not the model should return the last key/values attentions (not used by all models). Only
|
||||
relevant if `config.is_decoder=True`.
|
||||
num_logits_to_keep (`int` or `None`, *optional*, defaults to 1):
|
||||
Number of prompt logits to calculate during generation. If `None`, all logits will be calculated. If an
|
||||
integer value, only last `num_logits_to_keep` logits will be calculated. Default is 1 because only the
|
||||
logits of the last prompt token are needed for generation. For long sequences, the logits for the entire
|
||||
sequence may use a lot of memory so, setting `num_logits_to_keep=1` will reduce memory footprint
|
||||
significantly.
|
||||
pad_token_id (`int`, *optional*, defaults to 0):
|
||||
The id of the padding token.
|
||||
bos_token_id (`int`, *optional*, defaults to 1):
|
||||
The id of the "beginning-of-sequence" token.
|
||||
eos_token_id (`int`, *optional*, defaults to 2):
|
||||
The id of the "end-of-sequence" token.
|
||||
max_position_embeddings (`int`, *optional*, defaults to 8192):
|
||||
Max cached sequence length for the model
|
||||
attention_dropout (`float`, *optional*, defaults to 0.0):
|
||||
The dropout ratio for the attention probabilities.
|
||||
mamba_d_ssm (`int`, *optional*, defaults to 1024):
|
||||
The dimension of the SSM state space latents.
|
||||
mamba_n_heads (`int`, *optional*, defaults to 128):
|
||||
The number of mamba heads used in the v2 implementation.
|
||||
mamba_d_head (`int`, *optional*, defaults to `"auto"`):
|
||||
Head embedding dimension size
|
||||
mamba_n_groups (`int`, *optional*, defaults to 1):
|
||||
The number of the mamba groups used in the v2 implementation.
|
||||
mamba_d_state (`int`, *optional*, defaults to 256):
|
||||
The dimension the mamba state space latents
|
||||
mamba_d_conv (`int`, *optional*, defaults to 4):
|
||||
The size of the mamba convolution kernel
|
||||
mamba_expand (`int`, *optional*, defaults to 2):
|
||||
Expanding factor (relative to hidden_size) used to determine the mamba intermediate size
|
||||
mamba_chunk_size (`int`, *optional*, defaults to 256):
|
||||
The chunks in which to break the sequence when doing prefill/training
|
||||
mamba_conv_bias (`bool`, *optional*, defaults to `True`):
|
||||
Flag indicating whether or not to use bias in the convolution layer of the mamba mixer block.
|
||||
mamba_proj_bias (`bool`, *optional*, defaults to `False`):
|
||||
Flag indicating whether or not to use bias in the input and output projections (["in_proj", "out_proj"]) of the mamba mixer block
|
||||
mamba_norm_before_gate (`bool`, *optional*, defaults to `True`):
|
||||
Whether to use RMSNorm before the gate in the Mamba block
|
||||
mamba_rms_norm (`bool`, *optional*, defaults to `False`):
|
||||
Whether to use RMSNorm instead of LayerNorm in the Mamba block
|
||||
projectors_bias (`bool`, *optional*, defaults to `False`):
|
||||
Flag indicating whether or not to use bias in the input and output projections (["in_proj", "out_proj"]) of the attention block
|
||||
rope_theta (`float`, *optional*, defaults to 100000.0):
|
||||
The theta value used for the RoPE embeddings.
|
||||
rope_scaling (`float`, *optional*):
|
||||
The scaling value used for the RoPE embeddings. If `None`, no scaling is applied.
|
||||
lm_head_multiplier (`float`, *optional*, defaults to 1.0):
|
||||
The multiplier for the LM head. This is used to scale the output of the LM head.
|
||||
embedding_multiplier (`float`, *optional*, defaults to 1.0):
|
||||
The multiplier for the embedding layer. This is used to scale the output of the embedding layer.
|
||||
mlp_multipliers (`list[float]`, *optional*):
|
||||
The multipliers for the MLP layers. This is used to scale the output of the MLP layers. The first value is
|
||||
the multiplier of gate layer, the second value is the multiplier of the down_proj layer.
|
||||
key_multiplier (`float`, *optional*):
|
||||
The multiplier for the key layer. This is used to scale the output of the key layer.
|
||||
attention_out_multiplier (`float`, *optional*):
|
||||
The multiplier for the attention output layer. This is used to scale the output of the attention output
|
||||
attention_in_multiplier (`float`, *optional*):
|
||||
The multiplier for the attention input layer. This is used to scale the output of the attention input layer.
|
||||
ssm_multipliers (`list[float]`, *optional*):
|
||||
The multipliers for the SSM layers. This is used to scale the output of the SSM layers.
|
||||
ssm_in_multiplier (`float`, *optional*):
|
||||
The multiplier for the SSM input layer. This is used to scale the output of the SSM input layer.
|
||||
ssm_out_multiplier (`float`, *optional*):
|
||||
The multiplier for the SSM output layer. This is used to scale the output of the SSM output layer.
|
||||
"""
|
||||
|
||||
model_type = "falcon_h1"
|
||||
keys_to_ignore_at_inference = ["past_key_values"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vocab_size=128000,
|
||||
tie_word_embeddings=False,
|
||||
hidden_size=4096,
|
||||
intermediate_size=14336,
|
||||
num_hidden_layers=32,
|
||||
num_attention_heads=32,
|
||||
num_key_value_heads=8,
|
||||
hidden_act="silu",
|
||||
initializer_range=0.02,
|
||||
rms_norm_eps=1e-5,
|
||||
use_cache=True,
|
||||
num_logits_to_keep=1,
|
||||
pad_token_id=0,
|
||||
bos_token_id=1,
|
||||
eos_token_id=2,
|
||||
max_position_embeddings=8192,
|
||||
attention_dropout=0.0,
|
||||
mamba_d_ssm=1024,
|
||||
mamba_n_heads=128,
|
||||
mamba_d_head="auto",
|
||||
mamba_n_groups=1,
|
||||
mamba_d_state=256,
|
||||
mamba_d_conv=4,
|
||||
mamba_expand=2,
|
||||
mamba_chunk_size=256,
|
||||
mamba_conv_bias=True,
|
||||
mamba_proj_bias=False,
|
||||
mamba_norm_before_gate=True,
|
||||
mamba_rms_norm=False,
|
||||
projectors_bias=False,
|
||||
rope_theta=100000.0,
|
||||
rope_scaling=None,
|
||||
lm_head_multiplier=1.0,
|
||||
embedding_multiplier=1.0,
|
||||
mlp_multipliers=None,
|
||||
key_multiplier=None,
|
||||
attention_out_multiplier=None,
|
||||
attention_in_multiplier=None,
|
||||
ssm_multipliers=None,
|
||||
ssm_in_multiplier=None,
|
||||
ssm_out_multiplier=None,
|
||||
**kwargs,
|
||||
):
|
||||
self.vocab_size = vocab_size
|
||||
self.hidden_size = hidden_size
|
||||
self.intermediate_size = intermediate_size
|
||||
self.num_hidden_layers = num_hidden_layers
|
||||
self.num_attention_heads = num_attention_heads
|
||||
self.max_position_embeddings = max_position_embeddings
|
||||
self.attention_dropout = attention_dropout
|
||||
self.attention_bias = False
|
||||
self.mlp_bias = False
|
||||
|
||||
# for backward compatibility
|
||||
if num_key_value_heads is None:
|
||||
num_key_value_heads = num_attention_heads
|
||||
|
||||
self.num_key_value_heads = num_key_value_heads
|
||||
self.hidden_act = hidden_act
|
||||
self.initializer_range = initializer_range
|
||||
self.rms_norm_eps = rms_norm_eps
|
||||
|
||||
self.use_cache = use_cache
|
||||
self.num_logits_to_keep = num_logits_to_keep
|
||||
|
||||
self.rope_theta = rope_theta
|
||||
self.rope_scaling = None
|
||||
self.rope_scaling = rope_scaling
|
||||
self.projectors_bias = projectors_bias
|
||||
self.mamba_intermediate = mamba_intermediate = (
|
||||
mamba_expand * hidden_size if mamba_d_ssm is None else mamba_d_ssm
|
||||
)
|
||||
|
||||
if mamba_intermediate % mamba_n_heads != 0:
|
||||
raise ValueError("mamba_n_heads must divide mamba_expand * hidden_size")
|
||||
|
||||
# for the mamba_v2, must satisfy the following
|
||||
if mamba_d_head == "auto":
|
||||
mamba_d_head = mamba_intermediate // mamba_n_heads
|
||||
|
||||
if mamba_d_head * mamba_n_heads != mamba_intermediate:
|
||||
raise ValueError(
|
||||
"The dimensions for the Mamba head state do not match the model intermediate_size"
|
||||
)
|
||||
|
||||
self.mamba_d_ssm = mamba_d_ssm
|
||||
self.mamba_n_heads = mamba_n_heads
|
||||
self.mamba_d_head = mamba_d_head
|
||||
self.mamba_n_groups = mamba_n_groups
|
||||
self.mamba_d_state = mamba_d_state
|
||||
self.mamba_d_conv = mamba_d_conv
|
||||
self.mamba_expand = mamba_expand
|
||||
self.mamba_chunk_size = mamba_chunk_size
|
||||
self.mamba_conv_bias = mamba_conv_bias
|
||||
self.mamba_proj_bias = mamba_proj_bias
|
||||
|
||||
self.mamba_norm_before_gate = mamba_norm_before_gate
|
||||
self.mamba_rms_norm = mamba_rms_norm
|
||||
|
||||
self.lm_head_multiplier = lm_head_multiplier
|
||||
self.embedding_multiplier = embedding_multiplier
|
||||
|
||||
if mlp_multipliers is not None:
|
||||
self.mlp_multipliers = mlp_multipliers
|
||||
else:
|
||||
self.mlp_multipliers = [1.0, 1.0]
|
||||
|
||||
if attention_out_multiplier is not None:
|
||||
self.attention_out_multiplier = attention_out_multiplier
|
||||
else:
|
||||
self.attention_out_multiplier = 1.0
|
||||
|
||||
if attention_in_multiplier is not None:
|
||||
self.attention_in_multiplier = attention_in_multiplier
|
||||
else:
|
||||
self.attention_in_multiplier = 1.0
|
||||
|
||||
if key_multiplier is not None:
|
||||
self.key_multiplier = key_multiplier
|
||||
else:
|
||||
self.key_multiplier = 1.0
|
||||
|
||||
if ssm_multipliers is not None:
|
||||
self.ssm_multipliers = ssm_multipliers
|
||||
else:
|
||||
self.ssm_multipliers = [1.0, 1.0, 1.0, 1.0, 1.0]
|
||||
|
||||
if ssm_in_multiplier is not None:
|
||||
self.ssm_in_multiplier = ssm_in_multiplier
|
||||
else:
|
||||
self.ssm_in_multiplier = 1.0
|
||||
|
||||
if ssm_out_multiplier is not None:
|
||||
self.ssm_out_multiplier = ssm_out_multiplier
|
||||
else:
|
||||
self.ssm_out_multiplier = 1.0
|
||||
|
||||
super().__init__(
|
||||
pad_token_id=pad_token_id,
|
||||
bos_token_id=bos_token_id,
|
||||
eos_token_id=eos_token_id,
|
||||
tie_word_embeddings=tie_word_embeddings,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@property
|
||||
def layers_block_type(self):
|
||||
return ["falcon_h1" for i in range(self.num_hidden_layers)]
|
||||
|
||||
@property
|
||||
def full_attention_layer_ids(self):
|
||||
# For Falcon-H1, we do have attention on all layers
|
||||
return range(self.num_hidden_layers)
|
||||
|
||||
@property
|
||||
def linear_layer_ids(self):
|
||||
# For Falcon-H1, we do have mamba on all layers
|
||||
return range(self.num_hidden_layers)
|
||||
|
||||
@property
|
||||
def mamba2_cache_params(self):
|
||||
from sglang.srt.layers.dp_attention import get_attention_tp_size
|
||||
|
||||
shape = Mamba2StateShape.create(
|
||||
tp_world_size=get_attention_tp_size(),
|
||||
intermediate_size=self.mamba_intermediate,
|
||||
n_groups=self.mamba_n_groups,
|
||||
num_heads=self.mamba_n_heads,
|
||||
head_dim=self.mamba_d_head,
|
||||
state_size=self.mamba_d_state,
|
||||
conv_kernel=self.mamba_d_conv,
|
||||
)
|
||||
return Mamba2CacheParams(
|
||||
shape=shape, layers=self.linear_layer_ids, dtype=mamba2_state_dtype(self)
|
||||
)
|
||||
301
third_party/sglang/python/sglang/srt/configs/granitemoehybrid.py
vendored
Normal file
301
third_party/sglang/python/sglang/srt/configs/granitemoehybrid.py
vendored
Normal file
@@ -0,0 +1,301 @@
|
||||
# coding=utf-8
|
||||
# Copyright 2025 IBM and the HuggingFace Inc. team. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
"""GraniteMoeHybrid model configuration"""
|
||||
|
||||
from transformers.configuration_utils import PretrainedConfig
|
||||
from transformers.utils import logging
|
||||
|
||||
from sglang.srt.configs.mamba_utils import Mamba2CacheParams, Mamba2StateShape
|
||||
|
||||
logger = logging.get_logger(__name__)
|
||||
|
||||
MAMBA = "mamba"
|
||||
ATTENTION = "attention"
|
||||
|
||||
|
||||
class GraniteMoeHybridConfig(PretrainedConfig):
|
||||
r"""
|
||||
This is the configuration class to store the configuration of a [`GraniteMoeHybridModel`]. It is used to instantiate a
|
||||
GraniteMoeHybrid model according to the specified arguments, defining the model architecture. The GraniteMoeHybrid is a
|
||||
hybrid architecture combining Mamba2 layers with attention layers, developed by IBM.
|
||||
|
||||
Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the
|
||||
documentation from [`PretrainedConfig`] for more information.
|
||||
|
||||
Args:
|
||||
vocab_size (`int`, *optional*, defaults to 100352):
|
||||
Vocabulary size of the GraniteMoeHybrid model. Defines the number of different tokens that can be represented by the
|
||||
`inputs_ids` passed when calling [`GraniteMoeHybridModel`]
|
||||
tie_word_embeddings (`bool`, *optional*, defaults to `True`):
|
||||
Whether the model's input and output word embeddings should be tied. Note that this is only relevant if the
|
||||
model has a output word embedding layer.
|
||||
hidden_size (`int`, *optional*, defaults to 2048):
|
||||
Dimension of the hidden representations.
|
||||
intermediate_size (`int`, *optional*, defaults to 8192):
|
||||
Dimension of the MLP representations.
|
||||
num_hidden_layers (`int`, *optional*, defaults to 40):
|
||||
Number of hidden layers in the model.
|
||||
layer_types (`list[str]`, *optional*):
|
||||
List of layer types for each layer. Each element should be either "mamba" or "attention".
|
||||
If not provided, defaults to alternating pattern based on num_hidden_layers.
|
||||
num_attention_heads (`int`, *optional*, defaults to 32):
|
||||
Number of attention heads for each attention layer.
|
||||
num_key_value_heads (`int`, *optional*, defaults to 8):
|
||||
This is the number of key_value heads that should be used to implement Grouped Query Attention. If
|
||||
`num_key_value_heads=num_attention_heads`, the model will use Multi Head Attention (MHA), if
|
||||
`num_key_value_heads=1` the model will use Multi Query Attention (MQA) otherwise GQA is used.
|
||||
hidden_act (`str` or `function`, *optional*, defaults to `"silu"`):
|
||||
The non-linear activation function (function or string) in the decoder.
|
||||
initializer_range (`float`, *optional*, defaults to 0.1):
|
||||
The standard deviation of the truncated_normal_initializer for initializing all weight matrices.
|
||||
rms_norm_eps (`float`, *optional*, defaults to 1e-05):
|
||||
The epsilon used by the rms normalization layers.
|
||||
normalization_function (`str`, *optional*, defaults to `"rmsnorm"`):
|
||||
The normalization function to use. Currently only "rmsnorm" is supported.
|
||||
use_cache (`bool`, *optional*, defaults to `True`):
|
||||
Whether or not the model should return the last key/values attentions (not used by all models). Only
|
||||
relevant if `config.is_decoder=True`.
|
||||
pad_token_id (`int`, *optional*, defaults to 100256):
|
||||
The id of the padding token.
|
||||
bos_token_id (`int`, *optional*, defaults to 100257):
|
||||
The id of the "beginning-of-sequence" token.
|
||||
eos_token_id (`int`, *optional*, defaults to 100257):
|
||||
The id of the "end-of-sequence" token.
|
||||
max_position_embeddings (`int`, *optional*, defaults to 131072):
|
||||
Max cached sequence length for the model
|
||||
attention_dropout (`float`, *optional*, defaults to 0.0):
|
||||
The dropout ratio for the attention probabilities.
|
||||
attention_bias (`bool`, *optional*, defaults to `False`):
|
||||
Whether to use bias in attention layers.
|
||||
position_embedding_type (`str`, *optional*, defaults to `"nope"`):
|
||||
Type of position embedding. Can be "nope" (no position embedding) or "rope".
|
||||
rope_theta (`float`, *optional*, defaults to 10000.0):
|
||||
The theta value used for the RoPE embeddings.
|
||||
rope_scaling (`dict`, *optional*):
|
||||
The scaling configuration for the RoPE embeddings. If `None`, no scaling is applied.
|
||||
mamba_d_state (`int`, *optional*, defaults to 128):
|
||||
The dimension of the mamba state space latents
|
||||
mamba_d_conv (`int`, *optional*, defaults to 4):
|
||||
The size of the mamba convolution kernel
|
||||
mamba_expand (`int`, *optional*, defaults to 2):
|
||||
Expanding factor (relative to hidden_size) used to determine the mamba intermediate size
|
||||
mamba_d_head (`int`, *optional*, defaults to 64):
|
||||
Head embedding dimension size for Mamba
|
||||
mamba_n_heads (`int`, *optional*, defaults to 64):
|
||||
The number of mamba heads
|
||||
mamba_n_groups (`int`, *optional*, defaults to 1):
|
||||
The number of the mamba groups
|
||||
mamba_chunk_size (`int`, *optional*, defaults to 256):
|
||||
The chunks in which to break the sequence when doing prefill/training
|
||||
mamba_conv_bias (`bool`, *optional*, defaults to `True`):
|
||||
Flag indicating whether or not to use bias in the convolution layer of the mamba mixer block.
|
||||
mamba_proj_bias (`bool`, *optional*, defaults to `False`):
|
||||
Flag indicating whether or not to use bias in the input and output projections of the mamba mixer block
|
||||
embedding_multiplier (`float`, *optional*, defaults to 12.0):
|
||||
The multiplier for the embedding layer. This is used to scale the output of the embedding layer.
|
||||
logits_scaling (`float`, *optional*, defaults to 8.0):
|
||||
The scaling factor for the logits.
|
||||
attention_multiplier (`float`, *optional*, defaults to 0.015625):
|
||||
The multiplier for the attention layers.
|
||||
residual_multiplier (`float`, *optional*, defaults to 0.22):
|
||||
The multiplier for residual connections.
|
||||
num_local_experts (`int`, *optional*, defaults to 0):
|
||||
Number of local experts in MoE layers.
|
||||
num_experts_per_tok (`int`, *optional*, defaults to 0):
|
||||
Number of experts to use per token in MoE layers.
|
||||
shared_intermediate_size (`int`, *optional*, defaults to 8192):
|
||||
Intermediate size for shared experts.
|
||||
output_router_logits (`bool`, *optional*, defaults to `False`):
|
||||
Whether to output router logits.
|
||||
router_aux_loss_coef (`float`, *optional*, defaults to 0.01):
|
||||
Auxiliary loss coefficient for the router.
|
||||
"""
|
||||
|
||||
model_type = "granitemoehybrid"
|
||||
keys_to_ignore_at_inference = ["past_key_values"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vocab_size=100352,
|
||||
tie_word_embeddings=True,
|
||||
hidden_size=2048,
|
||||
intermediate_size=8192,
|
||||
num_hidden_layers=40,
|
||||
layer_types=None,
|
||||
num_attention_heads=32,
|
||||
num_key_value_heads=8,
|
||||
hidden_act="silu",
|
||||
initializer_range=0.1,
|
||||
rms_norm_eps=1e-5,
|
||||
normalization_function="rmsnorm",
|
||||
use_cache=True,
|
||||
pad_token_id=100256,
|
||||
bos_token_id=100257,
|
||||
eos_token_id=100257,
|
||||
max_position_embeddings=131072,
|
||||
attention_dropout=0.0,
|
||||
attention_bias=False,
|
||||
position_embedding_type="nope",
|
||||
rope_theta=10000.0,
|
||||
rope_scaling=None,
|
||||
mamba_d_state=128,
|
||||
mamba_d_conv=4,
|
||||
mamba_expand=2,
|
||||
mamba_d_head=64,
|
||||
mamba_n_heads=64,
|
||||
mamba_n_groups=1,
|
||||
mamba_chunk_size=256,
|
||||
mamba_conv_bias=True,
|
||||
mamba_proj_bias=False,
|
||||
embedding_multiplier=12.0,
|
||||
logits_scaling=8.0,
|
||||
attention_multiplier=0.015625,
|
||||
residual_multiplier=0.22,
|
||||
num_local_experts=0,
|
||||
num_experts_per_tok=0,
|
||||
shared_intermediate_size=8192,
|
||||
output_router_logits=False,
|
||||
router_aux_loss_coef=0.01,
|
||||
**kwargs,
|
||||
):
|
||||
self.vocab_size = vocab_size
|
||||
self.hidden_size = hidden_size
|
||||
self.intermediate_size = intermediate_size
|
||||
self.num_hidden_layers = num_hidden_layers
|
||||
|
||||
# Set layer types - if not provided, create default pattern
|
||||
if layer_types is None:
|
||||
# Default pattern: mamba layers with attention every 6th layer (roughly)
|
||||
self.layer_types = []
|
||||
for i in range(num_hidden_layers):
|
||||
if (i + 1) % 6 == 0:
|
||||
self.layer_types.append(ATTENTION)
|
||||
else:
|
||||
self.layer_types.append(MAMBA)
|
||||
else:
|
||||
self.layer_types = layer_types
|
||||
|
||||
# Validate layer_types
|
||||
if len(self.layer_types) != self.num_hidden_layers:
|
||||
raise ValueError(
|
||||
f"layer_types must have length equal to num_hidden_layers ({num_hidden_layers}), "
|
||||
f"but got {len(self.layer_types)}"
|
||||
)
|
||||
|
||||
for layer_type in self.layer_types:
|
||||
if layer_type not in [MAMBA, ATTENTION]:
|
||||
raise ValueError(
|
||||
f"Each element in layer_types must be either '{MAMBA}' or '{ATTENTION}', "
|
||||
f"but got '{layer_type}'"
|
||||
)
|
||||
|
||||
self.num_attention_heads = num_attention_heads
|
||||
self.num_key_value_heads = num_key_value_heads
|
||||
self.hidden_act = hidden_act
|
||||
self.initializer_range = initializer_range
|
||||
self.rms_norm_eps = rms_norm_eps
|
||||
self.normalization_function = normalization_function
|
||||
|
||||
self.use_cache = use_cache
|
||||
self.max_position_embeddings = max_position_embeddings
|
||||
self.attention_dropout = attention_dropout
|
||||
self.attention_bias = attention_bias
|
||||
|
||||
self.position_embedding_type = position_embedding_type
|
||||
self.rope_theta = rope_theta
|
||||
self.rope_scaling = rope_scaling
|
||||
|
||||
# Mamba configuration
|
||||
self.mamba_d_state = mamba_d_state
|
||||
self.mamba_d_conv = mamba_d_conv
|
||||
self.mamba_expand = mamba_expand
|
||||
self.mamba_d_head = mamba_d_head
|
||||
self.mamba_n_heads = mamba_n_heads
|
||||
self.mamba_n_groups = mamba_n_groups
|
||||
self.mamba_chunk_size = mamba_chunk_size
|
||||
self.mamba_conv_bias = mamba_conv_bias
|
||||
self.mamba_proj_bias = mamba_proj_bias
|
||||
|
||||
# Calculate mamba intermediate size
|
||||
self.mamba_intermediate_size = mamba_expand * hidden_size
|
||||
|
||||
# Validate mamba configuration
|
||||
if self.mamba_intermediate_size % mamba_n_heads != 0:
|
||||
raise ValueError(
|
||||
f"mamba_intermediate_size ({self.mamba_intermediate_size}) must be divisible by "
|
||||
f"mamba_n_heads ({mamba_n_heads})"
|
||||
)
|
||||
|
||||
if mamba_d_head * mamba_n_heads != self.mamba_intermediate_size:
|
||||
raise ValueError(
|
||||
f"mamba_d_head ({mamba_d_head}) * mamba_n_heads ({mamba_n_heads}) must equal "
|
||||
f"mamba_intermediate_size ({self.mamba_intermediate_size})"
|
||||
)
|
||||
|
||||
# Scaling factors
|
||||
self.embedding_multiplier = embedding_multiplier
|
||||
self.logits_scaling = logits_scaling
|
||||
self.attention_multiplier = attention_multiplier
|
||||
self.residual_multiplier = residual_multiplier
|
||||
|
||||
# MoE configuration
|
||||
self.num_local_experts = num_local_experts
|
||||
self.num_experts_per_tok = num_experts_per_tok
|
||||
self.shared_intermediate_size = shared_intermediate_size
|
||||
self.output_router_logits = output_router_logits
|
||||
self.router_aux_loss_coef = router_aux_loss_coef
|
||||
|
||||
super().__init__(
|
||||
pad_token_id=pad_token_id,
|
||||
bos_token_id=bos_token_id,
|
||||
eos_token_id=eos_token_id,
|
||||
tie_word_embeddings=tie_word_embeddings,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@property
|
||||
def mamba_layer_ids(self):
|
||||
"""Returns the indices of layers that are Mamba layers."""
|
||||
return [
|
||||
i for i in range(self.num_hidden_layers) if self.layer_types[i] == MAMBA
|
||||
]
|
||||
|
||||
@property
|
||||
def attention_layer_ids(self):
|
||||
"""Returns the indices of layers that are attention layers."""
|
||||
return [
|
||||
i for i in range(self.num_hidden_layers) if self.layer_types[i] == ATTENTION
|
||||
]
|
||||
|
||||
@property
|
||||
def full_attention_layer_ids(self):
|
||||
"""Alias for attention_layer_ids for compatibility."""
|
||||
return self.attention_layer_ids
|
||||
|
||||
@property
|
||||
def mamba2_cache_params(self):
|
||||
"""Returns the Mamba2 cache parameters for this configuration."""
|
||||
from sglang.srt.layers.dp_attention import get_attention_tp_size
|
||||
|
||||
shape = Mamba2StateShape.create(
|
||||
tp_world_size=get_attention_tp_size(),
|
||||
intermediate_size=self.mamba_intermediate_size,
|
||||
n_groups=self.mamba_n_groups,
|
||||
num_heads=self.mamba_n_heads,
|
||||
head_dim=self.mamba_d_head,
|
||||
state_size=self.mamba_d_state,
|
||||
conv_kernel=self.mamba_d_conv,
|
||||
)
|
||||
return Mamba2CacheParams(shape=shape, layers=self.mamba_layer_ids)
|
||||
705
third_party/sglang/python/sglang/srt/configs/internvl.py
vendored
Normal file
705
third_party/sglang/python/sglang/srt/configs/internvl.py
vendored
Normal file
@@ -0,0 +1,705 @@
|
||||
import copy
|
||||
import os
|
||||
from shutil import copyfile
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||
|
||||
import sentencepiece as spm
|
||||
from transformers import (
|
||||
TOKENIZER_MAPPING,
|
||||
GptOssConfig,
|
||||
LlamaConfig,
|
||||
PretrainedConfig,
|
||||
PreTrainedTokenizer,
|
||||
Qwen2Config,
|
||||
Qwen3Config,
|
||||
Qwen3MoeConfig,
|
||||
)
|
||||
|
||||
from sglang.utils import logger
|
||||
|
||||
# Copied from: https://github.com/OpenGVLab/InternVL/blob/34a81000402bf8f716bab8c9b57aff1f6b436bd0/internvl_chat/internvl/model/internvl_chat/configuration_internvl_chat.py#L21
|
||||
|
||||
|
||||
VOCAB_FILES_NAMES = {"vocab_file": "./tokenizer.model"}
|
||||
|
||||
PRETRAINED_VOCAB_FILES_MAP = {}
|
||||
|
||||
|
||||
# Modified from transformers.model.llama.configuration_llama.LlamaConfig
|
||||
class InternLM2Config(PretrainedConfig):
|
||||
r"""
|
||||
This is the configuration class to store the configuration of a [`InternLM2Model`]. It is used to instantiate
|
||||
an InternLM2 model according to the specified arguments, defining the model architecture. Instantiating a
|
||||
configuration with the defaults will yield a similar configuration to that of the InternLM2-7B.
|
||||
|
||||
Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the
|
||||
documentation from [`PretrainedConfig`] for more information.
|
||||
|
||||
|
||||
Args:
|
||||
vocab_size (`int`, *optional*, defaults to 32000):
|
||||
Vocabulary size of the InternLM2 model. Defines the number of different tokens that can be represented by the
|
||||
`inputs_ids` passed when calling [`InternLM2Model`]
|
||||
hidden_size (`int`, *optional*, defaults to 4096):
|
||||
Dimension of the hidden representations.
|
||||
intermediate_size (`int`, *optional*, defaults to 11008):
|
||||
Dimension of the MLP representations.
|
||||
num_hidden_layers (`int`, *optional*, defaults to 32):
|
||||
Number of hidden layers in the Transformer encoder.
|
||||
num_attention_heads (`int`, *optional*, defaults to 32):
|
||||
Number of attention heads for each attention layer in the Transformer encoder.
|
||||
num_key_value_heads (`int`, *optional*):
|
||||
This is the number of key_value heads that should be used to implement Grouped Query Attention. If
|
||||
`num_key_value_heads=num_attention_heads`, the model will use Multi Head Attention (MHA), if
|
||||
`num_key_value_heads=1 the model will use Multi Query Attention (MQA) otherwise GQA is used. When
|
||||
converting a multi-head checkpoint to a GQA checkpoint, each group key and value head should be constructed
|
||||
by meanpooling all the original heads within that group. For more details checkout [this
|
||||
paper](https://arxiv.org/pdf/2305.13245.pdf). If it is not specified, will default to
|
||||
`num_attention_heads`.
|
||||
hidden_act (`str` or `function`, *optional*, defaults to `"silu"`):
|
||||
The non-linear activation function (function or string) in the decoder.
|
||||
max_position_embeddings (`int`, *optional*, defaults to 2048):
|
||||
The maximum sequence length that this model might ever be used with. Typically set this to something large
|
||||
just in case (e.g., 512 or 1024 or 2048).
|
||||
initializer_range (`float`, *optional*, defaults to 0.02):
|
||||
The standard deviation of the truncated_normal_initializer for initializing all weight matrices.
|
||||
rms_norm_eps (`float`, *optional*, defaults to 1e-12):
|
||||
The epsilon used by the rms normalization layers.
|
||||
use_cache (`bool`, *optional*, defaults to `True`):
|
||||
Whether or not the model should return the last key/values attentions (not used by all models). Only
|
||||
relevant if `config.is_decoder=True`.
|
||||
tie_word_embeddings(`bool`, *optional*, defaults to `False`):
|
||||
Whether to tie weight embeddings
|
||||
Example:
|
||||
|
||||
"""
|
||||
|
||||
model_type = "internlm2"
|
||||
_auto_class = "AutoConfig"
|
||||
|
||||
def __init__( # pylint: disable=W0102
|
||||
self,
|
||||
vocab_size=103168,
|
||||
hidden_size=4096,
|
||||
intermediate_size=11008,
|
||||
num_hidden_layers=32,
|
||||
num_attention_heads=32,
|
||||
num_key_value_heads=None,
|
||||
hidden_act="silu",
|
||||
max_position_embeddings=2048,
|
||||
initializer_range=0.02,
|
||||
rms_norm_eps=1e-6,
|
||||
use_cache=True,
|
||||
pad_token_id=0,
|
||||
bos_token_id=1,
|
||||
eos_token_id=2,
|
||||
tie_word_embeddings=False,
|
||||
bias=True,
|
||||
rope_theta=10000,
|
||||
rope_scaling=None,
|
||||
attn_implementation="eager",
|
||||
**kwargs,
|
||||
):
|
||||
self.vocab_size = vocab_size
|
||||
self.max_position_embeddings = max_position_embeddings
|
||||
self.hidden_size = hidden_size
|
||||
self.intermediate_size = intermediate_size
|
||||
self.num_hidden_layers = num_hidden_layers
|
||||
self.num_attention_heads = num_attention_heads
|
||||
self.bias = bias
|
||||
|
||||
if num_key_value_heads is None:
|
||||
num_key_value_heads = num_attention_heads
|
||||
self.num_key_value_heads = num_key_value_heads
|
||||
|
||||
self.hidden_act = hidden_act
|
||||
self.initializer_range = initializer_range
|
||||
self.rms_norm_eps = rms_norm_eps
|
||||
self.use_cache = use_cache
|
||||
self.rope_theta = rope_theta
|
||||
self.rope_scaling = rope_scaling
|
||||
self._rope_scaling_validation()
|
||||
|
||||
self.attn_implementation = attn_implementation
|
||||
if self.attn_implementation is None:
|
||||
self.attn_implementation = "eager"
|
||||
super().__init__(
|
||||
pad_token_id=pad_token_id,
|
||||
bos_token_id=bos_token_id,
|
||||
eos_token_id=eos_token_id,
|
||||
tie_word_embeddings=tie_word_embeddings,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def _rope_scaling_validation(self):
|
||||
"""
|
||||
Validate the `rope_scaling` configuration.
|
||||
"""
|
||||
if self.rope_scaling is None:
|
||||
return
|
||||
|
||||
if not isinstance(self.rope_scaling, dict) or len(self.rope_scaling) != 2:
|
||||
raise ValueError(
|
||||
"`rope_scaling` must be a dictionary with with two fields, `type` and `factor`, "
|
||||
f"got {self.rope_scaling}"
|
||||
)
|
||||
rope_scaling_type = self.rope_scaling.get("type", None)
|
||||
rope_scaling_factor = self.rope_scaling.get("factor", None)
|
||||
if rope_scaling_type is None or rope_scaling_type not in ["linear", "dynamic"]:
|
||||
raise ValueError(
|
||||
f"`rope_scaling`'s type field must be one of ['linear', 'dynamic'], got {rope_scaling_type}"
|
||||
)
|
||||
if (
|
||||
rope_scaling_factor is None
|
||||
or not isinstance(rope_scaling_factor, (float, int))
|
||||
or rope_scaling_factor < 1.0
|
||||
):
|
||||
raise ValueError(
|
||||
f"`rope_scaling`'s factor field must be a float|int >= 1, got {rope_scaling_factor=}, {type(rope_scaling_factor)=}"
|
||||
)
|
||||
if isinstance(rope_scaling_factor, int):
|
||||
rope_scaling_factor = float(rope_scaling_factor)
|
||||
|
||||
|
||||
class InternVisionConfig(PretrainedConfig):
|
||||
r"""
|
||||
This is the configuration class to store the configuration of a [`InternVisionModel`]. It is used to
|
||||
instantiate a vision encoder according to the specified arguments, defining the model architecture.
|
||||
|
||||
Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the
|
||||
documentation from [`PretrainedConfig`] for more information.
|
||||
|
||||
Args:
|
||||
num_channels (`int`, *optional*, defaults to 3):
|
||||
Number of color channels in the input images (e.g., 3 for RGB).
|
||||
patch_size (`int`, *optional*, defaults to 14):
|
||||
The size (resolution) of each patch.
|
||||
image_size (`int`, *optional*, defaults to 224):
|
||||
The size (resolution) of each image.
|
||||
qkv_bias (`bool`, *optional*, defaults to `False`):
|
||||
Whether to add a bias to the queries and values in the self-attention layers.
|
||||
hidden_size (`int`, *optional*, defaults to 3200):
|
||||
Dimensionality of the encoder layers and the pooler layer.
|
||||
num_attention_heads (`int`, *optional*, defaults to 25):
|
||||
Number of attention heads for each attention layer in the Transformer encoder.
|
||||
intermediate_size (`int`, *optional*, defaults to 12800):
|
||||
Dimensionality of the "intermediate" (i.e., feed-forward) layer in the Transformer encoder.
|
||||
qk_normalization (`bool`, *optional*, defaults to `True`):
|
||||
Whether to normalize the queries and keys in the self-attention layers.
|
||||
num_hidden_layers (`int`, *optional*, defaults to 48):
|
||||
Number of hidden layers in the Transformer encoder.
|
||||
use_flash_attn (`bool`, *optional*, defaults to `True`):
|
||||
Whether to use flash attention mechanism.
|
||||
hidden_act (`str` or `function`, *optional*, defaults to `"gelu"`):
|
||||
The non-linear activation function (function or string) in the encoder and pooler. If string, `"gelu"`,
|
||||
`"relu"`, `"selu"` and `"gelu_new"` ``"gelu"` are supported.
|
||||
layer_norm_eps (`float`, *optional*, defaults to 1e-6):
|
||||
The epsilon used by the layer normalization layers.
|
||||
dropout (`float`, *optional*, defaults to 0.0):
|
||||
The dropout probability for all fully connected layers in the embeddings, encoder, and pooler.
|
||||
drop_path_rate (`float`, *optional*, defaults to 0.0):
|
||||
Dropout rate for stochastic depth.
|
||||
attention_dropout (`float`, *optional*, defaults to 0.0):
|
||||
The dropout ratio for the attention probabilities.
|
||||
initializer_range (`float`, *optional*, defaults to 0.02):
|
||||
The standard deviation of the truncated_normal_initializer for initializing all weight matrices.
|
||||
initializer_factor (`float`, *optional*, defaults to 0.1):
|
||||
A factor for layer scale.
|
||||
"""
|
||||
|
||||
model_type = "intern_vit_6b"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
num_channels=3,
|
||||
patch_size=14,
|
||||
image_size=224,
|
||||
qkv_bias=False,
|
||||
hidden_size=3200,
|
||||
num_attention_heads=25,
|
||||
intermediate_size=12800,
|
||||
qk_normalization=True,
|
||||
num_hidden_layers=48,
|
||||
use_flash_attn=True,
|
||||
hidden_act="gelu",
|
||||
layer_norm_eps=1e-6,
|
||||
dropout=0.0,
|
||||
drop_path_rate=0.0,
|
||||
attention_dropout=0.0,
|
||||
initializer_range=0.02,
|
||||
initializer_factor=0.1,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.hidden_size = hidden_size
|
||||
self.intermediate_size = intermediate_size
|
||||
self.dropout = dropout
|
||||
self.drop_path_rate = drop_path_rate
|
||||
self.num_hidden_layers = num_hidden_layers
|
||||
self.num_attention_heads = num_attention_heads
|
||||
self.num_channels = num_channels
|
||||
self.patch_size = patch_size
|
||||
self.image_size = image_size
|
||||
self.initializer_range = initializer_range
|
||||
self.initializer_factor = initializer_factor
|
||||
self.attention_dropout = attention_dropout
|
||||
self.layer_norm_eps = layer_norm_eps
|
||||
self.hidden_act = hidden_act
|
||||
self.qkv_bias = qkv_bias
|
||||
self.qk_normalization = qk_normalization
|
||||
self.use_flash_attn = use_flash_attn
|
||||
|
||||
@classmethod
|
||||
def from_pretrained(
|
||||
cls, pretrained_model_name_or_path: Union[str, os.PathLike], **kwargs
|
||||
) -> "PretrainedConfig":
|
||||
config_dict, kwargs = cls.get_config_dict(
|
||||
pretrained_model_name_or_path, **kwargs
|
||||
)
|
||||
|
||||
if "vision_config" in config_dict:
|
||||
config_dict = config_dict["vision_config"]
|
||||
|
||||
if (
|
||||
"model_type" in config_dict
|
||||
and hasattr(cls, "model_type")
|
||||
and config_dict["model_type"] != cls.model_type
|
||||
):
|
||||
logger.warning(
|
||||
f"You are using a model of type {config_dict['model_type']} to instantiate a model of type "
|
||||
f"{cls.model_type}. This is not supported for all configurations of models and can yield errors."
|
||||
)
|
||||
|
||||
return cls.from_dict(config_dict, **kwargs)
|
||||
|
||||
|
||||
class InternVLChatConfig(PretrainedConfig):
|
||||
model_type = "internvl_chat"
|
||||
is_composition = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vision_config=None,
|
||||
llm_config=None,
|
||||
use_backbone_lora=0,
|
||||
use_llm_lora=0,
|
||||
pad2square=False,
|
||||
select_layer=-1,
|
||||
force_image_size=None,
|
||||
downsample_ratio=0.5,
|
||||
template=None,
|
||||
dynamic_image_size=False,
|
||||
use_thumbnail=False,
|
||||
ps_version="v1",
|
||||
min_dynamic_patch=1,
|
||||
max_dynamic_patch=6,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
if vision_config is None:
|
||||
vision_config = {"architectures": ["InternVisionModel"]}
|
||||
logger.info(
|
||||
"vision_config is None. Initializing the InternVisionConfig with default values."
|
||||
)
|
||||
|
||||
if llm_config is None:
|
||||
llm_config = {"architectures": ["InternLM2ForCausalLM"]}
|
||||
logger.info(
|
||||
"llm_config is None. Initializing the LlamaConfig config with default values (`LlamaConfig`)."
|
||||
)
|
||||
|
||||
self.vision_config = InternVisionConfig(**vision_config)
|
||||
if llm_config.get("architectures")[0] == "LlamaForCausalLM":
|
||||
self.llm_config = LlamaConfig(**llm_config)
|
||||
elif llm_config.get("architectures")[0] == "InternLM2ForCausalLM":
|
||||
self.llm_config = InternLM2Config(**llm_config)
|
||||
elif llm_config.get("architectures")[0] == "Qwen2ForCausalLM":
|
||||
self.llm_config = Qwen2Config(**llm_config)
|
||||
elif llm_config.get("architectures")[0] == "Qwen3MoeForCausalLM":
|
||||
self.llm_config = Qwen3MoeConfig(**llm_config)
|
||||
elif llm_config.get("architectures")[0] == "Qwen3ForCausalLM":
|
||||
self.llm_config = Qwen3Config(**llm_config)
|
||||
elif llm_config.get("architectures")[0] == "GptOssForCausalLM":
|
||||
self.llm_config = GptOssConfig(**llm_config)
|
||||
else:
|
||||
raise ValueError(
|
||||
"Unsupported architecture: {}".format(
|
||||
llm_config.get("architectures")[0]
|
||||
)
|
||||
)
|
||||
|
||||
self.use_backbone_lora = use_backbone_lora
|
||||
self.use_llm_lora = use_llm_lora
|
||||
self.pad2square = pad2square
|
||||
self.select_layer = select_layer
|
||||
self.force_image_size = force_image_size
|
||||
self.downsample_ratio = downsample_ratio
|
||||
self.template = template
|
||||
self.dynamic_image_size = dynamic_image_size
|
||||
self.use_thumbnail = use_thumbnail
|
||||
self.ps_version = ps_version # pixel shuffle version
|
||||
self.min_dynamic_patch = min_dynamic_patch
|
||||
self.max_dynamic_patch = max_dynamic_patch
|
||||
|
||||
self.hidden_size = self.llm_config.hidden_size
|
||||
# By default, we use tie_word_embeddings=False for models of all sizes.
|
||||
self.tie_word_embeddings = False
|
||||
self.llm_config.tie_word_embeddings = self.tie_word_embeddings
|
||||
|
||||
def to_dict(self):
|
||||
"""
|
||||
Serializes this instance to a Python dictionary. Override the default [`~PretrainedConfig.to_dict`].
|
||||
|
||||
Returns:
|
||||
`Dict[str, any]`: Dictionary of all the attributes that make up this configuration instance,
|
||||
"""
|
||||
output = copy.deepcopy(self.__dict__)
|
||||
output["vision_config"] = self.vision_config.to_dict()
|
||||
output["llm_config"] = self.llm_config.to_dict()
|
||||
output["model_type"] = self.__class__.model_type
|
||||
output["use_backbone_lora"] = self.use_backbone_lora
|
||||
output["use_llm_lora"] = self.use_llm_lora
|
||||
output["select_layer"] = self.select_layer
|
||||
output["force_image_size"] = self.force_image_size
|
||||
output["downsample_ratio"] = self.downsample_ratio
|
||||
output["template"] = self.template
|
||||
output["dynamic_image_size"] = self.dynamic_image_size
|
||||
output["use_thumbnail"] = self.use_thumbnail
|
||||
output["ps_version"] = self.ps_version
|
||||
output["min_dynamic_patch"] = self.min_dynamic_patch
|
||||
output["max_dynamic_patch"] = self.max_dynamic_patch
|
||||
|
||||
return output
|
||||
|
||||
|
||||
# # Modified from transformers.model.llama.tokenization_llama_fast.LlamaTokenizerFast -> InternLM2TokenizerFast
|
||||
# class InternLM2TokenizerFast(PreTrainedTokenizerFast):
|
||||
# vocab_files_names = VOCAB_FILES_NAMES
|
||||
# slow_tokenizer_class = InternLM2Tokenizer
|
||||
# padding_side = 'left'
|
||||
# model_input_names = ['input_ids', 'attention_mask']
|
||||
# _auto_class = 'AutoTokenizer'
|
||||
#
|
||||
# def __init__(
|
||||
# self,
|
||||
# vocab_file,
|
||||
# unk_token='<unk>',
|
||||
# bos_token='<s>',
|
||||
# eos_token='</s>',
|
||||
# pad_token='</s>',
|
||||
# sp_model_kwargs: Optional[Dict[str, Any]] = None,
|
||||
# add_bos_token=True,
|
||||
# add_eos_token=False,
|
||||
# decode_with_prefix_space=False,
|
||||
# clean_up_tokenization_spaces=False,
|
||||
# **kwargs,
|
||||
# ):
|
||||
# super().__init__(
|
||||
# vocab_file=vocab_file,
|
||||
# unk_token=unk_token,
|
||||
# bos_token=bos_token,
|
||||
# eos_token=eos_token,
|
||||
# pad_token=pad_token,
|
||||
# sp_model_kwargs=sp_model_kwargs,
|
||||
# add_bos_token=add_bos_token,
|
||||
# add_eos_token=add_eos_token,
|
||||
# decode_with_prefix_space=decode_with_prefix_space,
|
||||
# clean_up_tokenization_spaces=clean_up_tokenization_spaces,
|
||||
# **kwargs,
|
||||
# )
|
||||
# self._add_bos_token = add_bos_token
|
||||
# self._add_eos_token = add_eos_token
|
||||
# self.update_post_processor()
|
||||
# self.vocab_file = vocab_file
|
||||
#
|
||||
# @property
|
||||
# def can_save_slow_tokenizer(self) -> bool:
|
||||
# return os.path.isfile(self.vocab_file) if self.vocab_file else False
|
||||
#
|
||||
# def update_post_processor(self):
|
||||
# """
|
||||
# Updates the underlying post processor with the current `bos_token` and `eos_token`.
|
||||
# """
|
||||
# bos = self.bos_token
|
||||
# bos_token_id = self.bos_token_id
|
||||
# if bos is None and self.add_bos_token:
|
||||
# raise ValueError('add_bos_token = True but bos_token = None')
|
||||
#
|
||||
# eos = self.eos_token
|
||||
# eos_token_id = self.eos_token_id
|
||||
# if eos is None and self.add_eos_token:
|
||||
# raise ValueError('add_eos_token = True but eos_token = None')
|
||||
#
|
||||
# single = f"{(bos + ':0 ') if self.add_bos_token else ''}$A:0{(' ' + eos + ':0') if self.add_eos_token else ''}"
|
||||
# pair = f"{single}{(' ' + bos + ':1') if self.add_bos_token else ''} $B:1{(' ' + eos + ':1') if self.add_eos_token else ''}"
|
||||
#
|
||||
# special_tokens = []
|
||||
# if self.add_bos_token:
|
||||
# special_tokens.append((bos, bos_token_id))
|
||||
# if self.add_eos_token:
|
||||
# special_tokens.append((eos, eos_token_id))
|
||||
# self._tokenizer.post_processor = processors.TemplateProcessing(
|
||||
# single=single, pair=pair, special_tokens=special_tokens
|
||||
# )
|
||||
#
|
||||
# @property
|
||||
# def add_eos_token(self):
|
||||
# return self._add_eos_token
|
||||
#
|
||||
# @property
|
||||
# def add_bos_token(self):
|
||||
# return self._add_bos_token
|
||||
#
|
||||
# @add_eos_token.setter
|
||||
# def add_eos_token(self, value):
|
||||
# self._add_eos_token = value
|
||||
# self.update_post_processor()
|
||||
#
|
||||
# @add_bos_token.setter
|
||||
# def add_bos_token(self, value):
|
||||
# self._add_bos_token = value
|
||||
# self.update_post_processor()
|
||||
#
|
||||
# def save_vocabulary(self, save_directory: str, filename_prefix: Optional[str] = None) -> Tuple[str]:
|
||||
# if not self.can_save_slow_tokenizer:
|
||||
# raise ValueError(
|
||||
# 'Your fast tokenizer does not have the necessary information to save the vocabulary for a slow '
|
||||
# 'tokenizer.'
|
||||
# )
|
||||
#
|
||||
# if not os.path.isdir(save_directory):
|
||||
# logger.error(f'Vocabulary path ({save_directory}) should be a directory')
|
||||
# return
|
||||
# out_vocab_file = os.path.join(
|
||||
# save_directory, (filename_prefix + '-' if filename_prefix else '') + VOCAB_FILES_NAMES['vocab_file']
|
||||
# )
|
||||
#
|
||||
# if os.path.abspath(self.vocab_file) != os.path.abspath(out_vocab_file):
|
||||
# copyfile(self.vocab_file, out_vocab_file)
|
||||
#
|
||||
# return (out_vocab_file,)
|
||||
|
||||
|
||||
# Modified from transformers.model.llama.tokenization_llama.LlamaTokenizer
|
||||
class InternLM2Tokenizer(PreTrainedTokenizer):
|
||||
"""
|
||||
Construct a InternLM2 tokenizer. Based on byte-level Byte-Pair-Encoding.
|
||||
|
||||
Args:
|
||||
vocab_file (`str`):
|
||||
Path to the vocabulary file.
|
||||
"""
|
||||
|
||||
vocab_files_names = VOCAB_FILES_NAMES
|
||||
pretrained_vocab_files_map = PRETRAINED_VOCAB_FILES_MAP
|
||||
model_input_names = ["input_ids", "attention_mask"]
|
||||
_auto_class = "AutoTokenizer"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vocab_file,
|
||||
unk_token="<unk>",
|
||||
bos_token="<s>",
|
||||
eos_token="</s>",
|
||||
pad_token="</s>",
|
||||
sp_model_kwargs: Optional[Dict[str, Any]] = None,
|
||||
add_bos_token=True,
|
||||
add_eos_token=False,
|
||||
decode_with_prefix_space=False,
|
||||
clean_up_tokenization_spaces=False,
|
||||
**kwargs,
|
||||
):
|
||||
print("register succeed")
|
||||
self.sp_model_kwargs = {} if sp_model_kwargs is None else sp_model_kwargs
|
||||
self.vocab_file = vocab_file
|
||||
self.add_bos_token = add_bos_token
|
||||
self.add_eos_token = add_eos_token
|
||||
self.decode_with_prefix_space = decode_with_prefix_space
|
||||
self.sp_model = spm.SentencePieceProcessor(**self.sp_model_kwargs)
|
||||
self.sp_model.Load(vocab_file)
|
||||
self._no_prefix_space_tokens = None
|
||||
super().__init__(
|
||||
bos_token=bos_token,
|
||||
eos_token=eos_token,
|
||||
unk_token=unk_token,
|
||||
pad_token=pad_token,
|
||||
clean_up_tokenization_spaces=clean_up_tokenization_spaces,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@property
|
||||
def no_prefix_space_tokens(self):
|
||||
if self._no_prefix_space_tokens is None:
|
||||
vocab = self.convert_ids_to_tokens(list(range(self.vocab_size)))
|
||||
self._no_prefix_space_tokens = {
|
||||
i for i, tok in enumerate(vocab) if not tok.startswith("▁")
|
||||
}
|
||||
return self._no_prefix_space_tokens
|
||||
|
||||
@property
|
||||
def vocab_size(self):
|
||||
"""Returns vocab size"""
|
||||
return self.sp_model.get_piece_size()
|
||||
|
||||
@property
|
||||
def bos_token_id(self) -> Optional[int]:
|
||||
return self.sp_model.bos_id()
|
||||
|
||||
@property
|
||||
def eos_token_id(self) -> Optional[int]:
|
||||
return self.sp_model.eos_id()
|
||||
|
||||
def get_vocab(self):
|
||||
"""Returns vocab as a dict"""
|
||||
vocab = {self.convert_ids_to_tokens(i): i for i in range(self.vocab_size)}
|
||||
vocab.update(self.added_tokens_encoder)
|
||||
return vocab
|
||||
|
||||
def _tokenize(self, text):
|
||||
"""Returns a tokenized string."""
|
||||
return self.sp_model.encode(text, out_type=str)
|
||||
|
||||
def _convert_token_to_id(self, token):
|
||||
"""Converts a token (str) in an id using the vocab."""
|
||||
return self.sp_model.piece_to_id(token)
|
||||
|
||||
def _convert_id_to_token(self, index):
|
||||
"""Converts an index (integer) in a token (str) using the vocab."""
|
||||
token = self.sp_model.IdToPiece(index)
|
||||
return token
|
||||
|
||||
def _maybe_add_prefix_space(self, tokens, decoded):
|
||||
if tokens and tokens[0] not in self.no_prefix_space_tokens:
|
||||
return " " + decoded
|
||||
else:
|
||||
return decoded
|
||||
|
||||
def convert_tokens_to_string(self, tokens):
|
||||
"""Converts a sequence of tokens (string) in a single string."""
|
||||
current_sub_tokens = []
|
||||
out_string = ""
|
||||
prev_is_special = False
|
||||
for token in tokens:
|
||||
# make sure that special tokens are not decoded using sentencepiece model
|
||||
if token in self.all_special_tokens:
|
||||
if not prev_is_special:
|
||||
out_string += " "
|
||||
out_string += self.sp_model.decode(current_sub_tokens) + token
|
||||
prev_is_special = True
|
||||
current_sub_tokens = []
|
||||
else:
|
||||
current_sub_tokens.append(token)
|
||||
prev_is_special = False
|
||||
out_string += self.sp_model.decode(current_sub_tokens)
|
||||
out_string = self._maybe_add_prefix_space(tokens=tokens, decoded=out_string)
|
||||
return out_string[1:]
|
||||
|
||||
def save_vocabulary(
|
||||
self, save_directory, filename_prefix: Optional[str] = None
|
||||
) -> Tuple[str]:
|
||||
"""
|
||||
Save the vocabulary and special tokens file to a directory.
|
||||
|
||||
Args:
|
||||
save_directory (`str`):
|
||||
The directory in which to save the vocabulary.
|
||||
|
||||
Returns:
|
||||
`Tuple(str)`: Paths to the files saved.
|
||||
"""
|
||||
if not os.path.isdir(save_directory):
|
||||
logger.error(f"Vocabulary path ({save_directory}) should be a directory")
|
||||
return
|
||||
out_vocab_file = os.path.join(
|
||||
save_directory,
|
||||
(filename_prefix + "-" if filename_prefix else "")
|
||||
+ VOCAB_FILES_NAMES["vocab_file"],
|
||||
)
|
||||
|
||||
if os.path.abspath(self.vocab_file) != os.path.abspath(
|
||||
out_vocab_file
|
||||
) and os.path.isfile(self.vocab_file):
|
||||
copyfile(self.vocab_file, out_vocab_file)
|
||||
elif not os.path.isfile(self.vocab_file):
|
||||
with open(out_vocab_file, "wb") as fi:
|
||||
content_spiece_model = self.sp_model.serialized_model_proto()
|
||||
fi.write(content_spiece_model)
|
||||
|
||||
return (out_vocab_file,)
|
||||
|
||||
def build_inputs_with_special_tokens(self, token_ids_0, token_ids_1=None):
|
||||
if self.add_bos_token:
|
||||
bos_token_ids = [self.bos_token_id]
|
||||
else:
|
||||
bos_token_ids = []
|
||||
|
||||
output = bos_token_ids + token_ids_0
|
||||
|
||||
if token_ids_1 is not None:
|
||||
output = output + token_ids_1
|
||||
|
||||
if self.add_eos_token:
|
||||
output = output + [self.eos_token_id]
|
||||
|
||||
return output
|
||||
|
||||
def get_special_tokens_mask(
|
||||
self,
|
||||
token_ids_0: List[int],
|
||||
token_ids_1: Optional[List[int]] = None,
|
||||
already_has_special_tokens: bool = False,
|
||||
) -> List[int]:
|
||||
"""
|
||||
Retrieve sequence ids from a token list that has no special tokens added. This method is called when adding
|
||||
special tokens using the tokenizer `prepare_for_model` method.
|
||||
|
||||
Args:
|
||||
token_ids_0 (`List[int]`):
|
||||
List of IDs.
|
||||
token_ids_1 (`List[int]`, *optional*):
|
||||
Optional second list of IDs for sequence pairs.
|
||||
already_has_special_tokens (`bool`, *optional*, defaults to `False`):
|
||||
Whether or not the token list is already formatted with special tokens for the model.
|
||||
|
||||
Returns:
|
||||
`List[int]`: A list of integers in the range [0, 1]: 1 for a special token, 0 for a sequence token.
|
||||
"""
|
||||
if already_has_special_tokens:
|
||||
return super().get_special_tokens_mask(
|
||||
token_ids_0=token_ids_0,
|
||||
token_ids_1=token_ids_1,
|
||||
already_has_special_tokens=True,
|
||||
)
|
||||
|
||||
if token_ids_1 is None:
|
||||
return [1] + ([0] * len(token_ids_0)) + [1]
|
||||
return [1] + ([0] * len(token_ids_0)) + [1, 1] + ([0] * len(token_ids_1)) + [1]
|
||||
|
||||
def create_token_type_ids_from_sequences(
|
||||
self, token_ids_0: List[int], token_ids_1: Optional[List[int]] = None
|
||||
) -> List[int]:
|
||||
"""
|
||||
Create a mask from the two sequences passed to be used in a sequence-pair classification task. T5 does not make
|
||||
use of token type ids, therefore a list of zeros is returned.
|
||||
|
||||
Args:
|
||||
token_ids_0 (`List[int]`):
|
||||
List of IDs.
|
||||
token_ids_1 (`List[int]`, *optional*):
|
||||
Optional second list of IDs for sequence pairs.
|
||||
|
||||
Returns:
|
||||
`List[int]`: List of zeros.
|
||||
"""
|
||||
eos = [self.eos_token_id]
|
||||
|
||||
if token_ids_1 is None:
|
||||
return len(token_ids_0 + eos) * [0]
|
||||
return len(token_ids_0 + eos + token_ids_1 + eos) * [0]
|
||||
|
||||
|
||||
TOKENIZER_MAPPING.register(
|
||||
InternVLChatConfig, (InternLM2Tokenizer, None), exist_ok=True
|
||||
)
|
||||
634
third_party/sglang/python/sglang/srt/configs/janus_pro.py
vendored
Normal file
634
third_party/sglang/python/sglang/srt/configs/janus_pro.py
vendored
Normal file
@@ -0,0 +1,634 @@
|
||||
# Adapted from:
|
||||
# https://github.com/deepseek-ai/Janus/tree/main/janus/models
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, List, Tuple, Union
|
||||
|
||||
import numpy as np
|
||||
import PIL
|
||||
import torch
|
||||
from PIL.Image import Image
|
||||
from transformers import (
|
||||
BaseImageProcessor,
|
||||
BatchFeature,
|
||||
LlamaConfig,
|
||||
LlamaTokenizerFast,
|
||||
PretrainedConfig,
|
||||
ProcessorMixin,
|
||||
)
|
||||
from transformers.image_utils import to_numpy_array
|
||||
|
||||
from sglang.srt.configs.utils import register_image_processor, register_processor
|
||||
from sglang.srt.multimodal.mm_utils import expand2square
|
||||
|
||||
|
||||
class DictToObject(dict):
|
||||
def __init__(self, dictionary):
|
||||
super(self).__init__(dictionary)
|
||||
|
||||
for key, value in dictionary.items():
|
||||
if isinstance(value, dict):
|
||||
value = DictToObject(value)
|
||||
setattr(self, key, value)
|
||||
|
||||
|
||||
class VisionConfig(PretrainedConfig):
|
||||
model_type = "vision"
|
||||
cls: str = ""
|
||||
params = {}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.cls = kwargs.get("cls", "")
|
||||
if not isinstance(self.cls, str):
|
||||
self.cls = self.cls.__name__
|
||||
|
||||
self.params = kwargs.get("params", {})
|
||||
|
||||
|
||||
class GenAlignerConfig(PretrainedConfig):
|
||||
model_type = "gen_aligner"
|
||||
cls: str = ""
|
||||
params = {}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.cls = kwargs.get("cls", "")
|
||||
if not isinstance(self.cls, str):
|
||||
self.cls = self.cls.__name__
|
||||
|
||||
self.params = kwargs.get("params", {})
|
||||
|
||||
|
||||
class GenHeadConfig(PretrainedConfig):
|
||||
model_type = "gen_head"
|
||||
cls: str = ""
|
||||
params = {}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.cls = kwargs.get("cls", "")
|
||||
if not isinstance(self.cls, str):
|
||||
self.cls = self.cls.__name__
|
||||
|
||||
self.params = kwargs.get("params", {})
|
||||
|
||||
|
||||
class AlignerConfig(PretrainedConfig):
|
||||
model_type = "aligner"
|
||||
cls: str = ""
|
||||
params = {}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.cls = kwargs.get("cls", "")
|
||||
if not isinstance(self.cls, str):
|
||||
self.cls = self.cls.__name__
|
||||
|
||||
self.params = kwargs.get("params", {})
|
||||
|
||||
|
||||
class GenVisionConfig(PretrainedConfig):
|
||||
model_type = "gen_vision"
|
||||
cls: str = ""
|
||||
params = {}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.cls = kwargs.get("cls", "")
|
||||
if not isinstance(self.cls, str):
|
||||
self.cls = self.cls.__name__
|
||||
|
||||
self.params = kwargs.get("params", {})
|
||||
|
||||
|
||||
@dataclass
|
||||
class SigLIPVisionCfg:
|
||||
width: int = 1152
|
||||
layers: Union[Tuple[int, int, int, int], int] = 27
|
||||
heads: int = 16
|
||||
patch_size: int = 14
|
||||
image_size: Union[Tuple[int, int], int] = 336
|
||||
global_pool: str = "map"
|
||||
mlp_ratio: float = 3.7362
|
||||
class_token: bool = False
|
||||
num_classes: int = 0
|
||||
use_checkpoint: bool = False
|
||||
|
||||
|
||||
class MultiModalityConfig(PretrainedConfig):
|
||||
model_type = "multi_modality"
|
||||
vision_config: VisionConfig = None
|
||||
aligner_config: AlignerConfig = None
|
||||
|
||||
gen_vision_config: GenVisionConfig = None
|
||||
gen_aligner_config: GenAlignerConfig = None
|
||||
gen_head_config: GenHeadConfig = None
|
||||
|
||||
language_config: LlamaConfig = None
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
vision_config = kwargs.get("vision_config", {})
|
||||
self.vision_config = VisionConfig(**vision_config)
|
||||
|
||||
aligner_config = kwargs.get("aligner_config", {})
|
||||
self.aligner_config = AlignerConfig(**aligner_config)
|
||||
|
||||
gen_vision_config = kwargs.get("gen_vision_config", {})
|
||||
self.gen_vision_config = GenVisionConfig(**gen_vision_config)
|
||||
|
||||
gen_aligner_config = kwargs.get("gen_aligner_config", {})
|
||||
self.gen_aligner_config = GenAlignerConfig(**gen_aligner_config)
|
||||
|
||||
gen_head_config = kwargs.get("gen_head_config", {})
|
||||
self.gen_head_config = GenHeadConfig(**gen_head_config)
|
||||
|
||||
language_config = kwargs.get("language_config", {})
|
||||
if isinstance(language_config, LlamaConfig):
|
||||
self.language_config = language_config
|
||||
else:
|
||||
self.language_config = LlamaConfig(**language_config)
|
||||
|
||||
|
||||
class VLMImageProcessor(BaseImageProcessor):
|
||||
model_input_names = ["pixel_values"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
image_size: int,
|
||||
min_size: int = 14,
|
||||
image_mean: Union[Tuple[float, float, float], List[float]] = (
|
||||
0.48145466,
|
||||
0.4578275,
|
||||
0.40821073,
|
||||
),
|
||||
image_std: Union[Tuple[float, float, float], List[float]] = (
|
||||
0.26862954,
|
||||
0.26130258,
|
||||
0.27577711,
|
||||
),
|
||||
rescale_factor: float = 1.0 / 255.0,
|
||||
do_normalize: bool = True,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.image_size = image_size
|
||||
self.rescale_factor = rescale_factor
|
||||
self.image_mean = image_mean
|
||||
self.image_std = image_std
|
||||
self.min_size = min_size
|
||||
self.do_normalize = do_normalize
|
||||
|
||||
if image_mean is None:
|
||||
self.background_color = (127, 127, 127)
|
||||
else:
|
||||
self.background_color = tuple([int(x * 255) for x in image_mean])
|
||||
|
||||
def resize(self, pil_img: Image) -> np.ndarray:
|
||||
"""
|
||||
|
||||
Args:
|
||||
pil_img (PIL.Image): [H, W, 3] in PIL.Image in RGB
|
||||
|
||||
Returns:
|
||||
x (np.ndarray): [3, self.image_size, self.image_size]
|
||||
"""
|
||||
|
||||
width, height = pil_img.size
|
||||
max_size = max(width, height)
|
||||
|
||||
size = [
|
||||
max(int(height / max_size * self.image_size), self.min_size),
|
||||
max(int(width / max_size * self.image_size), self.min_size),
|
||||
]
|
||||
|
||||
if width <= 0 or height <= 0 or size[0] <= 0 or size[1] <= 0:
|
||||
# print(f"orig size = {pil_img.size}, new size = {size}")
|
||||
raise ValueError("Invalid size!")
|
||||
|
||||
def resize(
|
||||
pil_img, size, interpolation=PIL.Image.Resampling.BICUBIC, antialias=True
|
||||
):
|
||||
if isinstance(size, int):
|
||||
w, h = pil_img.size
|
||||
if (w <= h and w == size) or (h <= w and h == size):
|
||||
return pil_img
|
||||
if w < h:
|
||||
ow = size
|
||||
oh = int(size * h / w)
|
||||
else:
|
||||
oh = size
|
||||
ow = int(size * w / h)
|
||||
size = (ow, oh)
|
||||
else:
|
||||
size = (size[1], size[0])
|
||||
|
||||
return pil_img.resize(
|
||||
size, resample=interpolation, reducing_gap=None if antialias else 3.0
|
||||
)
|
||||
|
||||
pil_img = resize(
|
||||
pil_img, size, interpolation=PIL.Image.Resampling.BICUBIC, antialias=True
|
||||
)
|
||||
|
||||
pil_img = expand2square(pil_img, self.background_color)
|
||||
x = to_numpy_array(pil_img)
|
||||
|
||||
# [H, W, 3] -> [3, H, W]
|
||||
x = np.transpose(x, (2, 0, 1))
|
||||
|
||||
return x
|
||||
|
||||
def preprocess(self, images, return_tensors: str = "pt", **kwargs) -> BatchFeature:
|
||||
# resize and pad to [self.image_size, self.image_size]
|
||||
# then convert from [H, W, 3] to [3, H, W]
|
||||
if not isinstance(images, list):
|
||||
images = [images]
|
||||
images: List[np.ndarray] = [self.resize(image) for image in images]
|
||||
images = [image[:3, ...] for image in images]
|
||||
|
||||
# rescale from [0, 255] -> [0, 1]
|
||||
images = [
|
||||
self.rescale(
|
||||
image=image,
|
||||
scale=self.rescale_factor,
|
||||
input_data_format="channels_first",
|
||||
)
|
||||
for image in images
|
||||
]
|
||||
|
||||
# normalize
|
||||
if self.do_normalize:
|
||||
images = [
|
||||
self.normalize(
|
||||
image=image,
|
||||
mean=self.image_mean,
|
||||
std=self.image_std,
|
||||
input_data_format="channels_first",
|
||||
)
|
||||
for image in images
|
||||
]
|
||||
data = {"pixel_values": images}
|
||||
return BatchFeature(data=data, tensor_type=return_tensors)
|
||||
|
||||
@property
|
||||
def default_shape(self):
|
||||
return [3, self.image_size, self.image_size]
|
||||
|
||||
|
||||
class DictOutput(object):
|
||||
def items(self):
|
||||
return self.__dict__.items()
|
||||
|
||||
def keys(self):
|
||||
return self.__dict__.keys()
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self.__dict__[item]
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self.__dict__
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.__dict__[key] = value
|
||||
|
||||
|
||||
@dataclass
|
||||
class VLChatProcessorOutput(DictOutput):
|
||||
sft_format: str
|
||||
input_ids: torch.Tensor
|
||||
pixel_values: torch.Tensor
|
||||
num_image_tokens: torch.IntTensor
|
||||
|
||||
def __len__(self):
|
||||
return len(self.input_ids)
|
||||
|
||||
|
||||
@dataclass
|
||||
class BatchedVLChatProcessorOutput(DictOutput):
|
||||
sft_format: List[str]
|
||||
input_ids: torch.Tensor
|
||||
pixel_values: torch.Tensor
|
||||
attention_mask: torch.Tensor
|
||||
images_seq_mask: torch.BoolTensor
|
||||
images_emb_mask: torch.BoolTensor
|
||||
|
||||
|
||||
# FIXME: had to place Official Processor here, since image_processor module would not be imported in all threads,
|
||||
# hence AutoProcessor registration would not be affective in some cases
|
||||
class VLChatProcessor(ProcessorMixin):
|
||||
image_processor_class = "AutoImageProcessor"
|
||||
tokenizer_class = ("LlamaTokenizer", "LlamaTokenizerFast")
|
||||
|
||||
attributes = ["image_processor", "tokenizer"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
image_processor: VLMImageProcessor,
|
||||
tokenizer: LlamaTokenizerFast,
|
||||
image_tag: str = "<image_placeholder>",
|
||||
image_start_tag: str = "<begin_of_image>",
|
||||
image_end_tag: str = "<end_of_image>",
|
||||
pad_tag: str = "<|▁pad▁|>",
|
||||
num_image_tokens: int = 576,
|
||||
add_special_token: bool = False,
|
||||
sft_format: str = "deepseek",
|
||||
mask_prompt: bool = True,
|
||||
ignore_id: int = -100,
|
||||
**kwargs,
|
||||
):
|
||||
self.image_processor = image_processor
|
||||
self.tokenizer = tokenizer
|
||||
|
||||
image_id = self.tokenizer.vocab.get(image_tag)
|
||||
if image_id is None:
|
||||
special_tokens = [image_tag]
|
||||
special_tokens_dict = {"additional_special_tokens": special_tokens}
|
||||
self.tokenizer.add_special_tokens(special_tokens_dict)
|
||||
# print(f"Add image tag = {image_tag} to the tokenizer")
|
||||
|
||||
self.image_tag = image_tag
|
||||
self.image_start_tag = image_start_tag
|
||||
self.image_end_tag = image_end_tag
|
||||
self.pad_tag = pad_tag
|
||||
|
||||
self.num_image_tokens = num_image_tokens
|
||||
self.add_special_token = add_special_token
|
||||
self.sft_format = sft_format
|
||||
self.ignore_id = ignore_id
|
||||
|
||||
super().__init__(
|
||||
image_processor,
|
||||
tokenizer,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@property
|
||||
def image_token(self):
|
||||
return self.image_tag
|
||||
|
||||
@property
|
||||
def image_id(self) -> int:
|
||||
image_id = self.tokenizer.vocab.get(self.image_tag)
|
||||
return image_id
|
||||
|
||||
@property
|
||||
def image_start_id(self):
|
||||
image_start_id = self.tokenizer.vocab.get(self.image_start_tag)
|
||||
return image_start_id
|
||||
|
||||
@property
|
||||
def image_end_id(self):
|
||||
image_end_id = self.tokenizer.vocab.get(self.image_end_tag)
|
||||
return image_end_id
|
||||
|
||||
@property
|
||||
def image_start_token(self):
|
||||
return self.image_start_tag
|
||||
|
||||
@property
|
||||
def image_end_token(self):
|
||||
return self.image_end_tag
|
||||
|
||||
@property
|
||||
def pad_id(self):
|
||||
pad_id = self.tokenizer.vocab.get(self.pad_tag)
|
||||
return pad_id
|
||||
|
||||
def add_image_token(
|
||||
self,
|
||||
image_indices: List[int],
|
||||
input_ids: torch.LongTensor,
|
||||
):
|
||||
"""
|
||||
|
||||
Args:
|
||||
image_indices (List[int]): [index_0, index_1, ..., index_j]
|
||||
input_ids (torch.LongTensor): [N]
|
||||
|
||||
Returns:
|
||||
input_ids (torch.LongTensor): [N + image tokens]
|
||||
num_image_tokens (torch.IntTensor): [n_images]
|
||||
"""
|
||||
|
||||
input_slices = []
|
||||
|
||||
start = 0
|
||||
for index in image_indices:
|
||||
if self.add_special_token:
|
||||
end = index + 1
|
||||
else:
|
||||
end = index
|
||||
|
||||
# original text tokens
|
||||
input_slices.append(input_ids[start:end])
|
||||
|
||||
# add boi, image tokens, eoi and set the mask as False
|
||||
input_slices.append(self.image_start_id * torch.ones((1), dtype=torch.long))
|
||||
input_slices.append(
|
||||
self.image_id * torch.ones((self.num_image_tokens,), dtype=torch.long)
|
||||
)
|
||||
input_slices.append(self.image_end_id * torch.ones((1), dtype=torch.long))
|
||||
start = index + 1
|
||||
|
||||
# the left part
|
||||
input_slices.append(input_ids[start:])
|
||||
|
||||
# concat all slices
|
||||
input_ids = torch.cat(input_slices, dim=0)
|
||||
num_image_tokens = torch.IntTensor([self.num_image_tokens] * len(image_indices))
|
||||
|
||||
return input_ids, num_image_tokens
|
||||
|
||||
def process_one(
|
||||
self,
|
||||
prompt: str = None,
|
||||
images: List[Image] = None,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
|
||||
Args:
|
||||
prompt (str): the formatted prompt;
|
||||
images (List[ImageType]): the list of images;
|
||||
**kwargs:
|
||||
|
||||
Returns:
|
||||
outputs (BaseProcessorOutput): the output of the processor,
|
||||
- input_ids (torch.LongTensor): [N + image tokens]
|
||||
- target_ids (torch.LongTensor): [N + image tokens]
|
||||
- images (torch.FloatTensor): [n_images, 3, H, W]
|
||||
- image_id (int): the id of the image token
|
||||
- num_image_tokens (List[int]): the number of image tokens
|
||||
"""
|
||||
|
||||
sft_format = prompt
|
||||
# tokenize
|
||||
input_ids = self.tokenizer.encode(sft_format)
|
||||
input_ids = torch.LongTensor(input_ids)
|
||||
|
||||
# add image tokens to the input_ids
|
||||
image_token_mask: torch.Tensor = (input_ids == self.image_id).to(torch.bool)
|
||||
image_indices = image_token_mask.nonzero()
|
||||
input_ids, num_image_tokens = self.add_image_token(
|
||||
image_indices=image_indices,
|
||||
input_ids=input_ids,
|
||||
)
|
||||
|
||||
# load images
|
||||
images_outputs = self.image_processor(images, return_tensors="pt")
|
||||
|
||||
prepare = VLChatProcessorOutput(
|
||||
sft_format=sft_format,
|
||||
input_ids=input_ids,
|
||||
pixel_values=images_outputs.pixel_values,
|
||||
num_image_tokens=num_image_tokens,
|
||||
)
|
||||
|
||||
return prepare
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
*,
|
||||
prompt: str = None,
|
||||
conversations: List[Dict[str, str]] = None,
|
||||
images: List[Image] = None,
|
||||
force_batchify: bool = True,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
|
||||
Args:
|
||||
prompt (str): the formatted prompt;
|
||||
conversations (List[Dict]): conversations with a list of messages;
|
||||
images (List[ImageType]): the list of images;
|
||||
force_batchify (bool): force batchify the inputs;
|
||||
**kwargs:
|
||||
|
||||
Returns:
|
||||
outputs (BaseProcessorOutput): the output of the processor,
|
||||
- input_ids (torch.LongTensor): [N + image tokens]
|
||||
- images (torch.FloatTensor): [n_images, 3, H, W]
|
||||
- image_id (int): the id of the image token
|
||||
- num_image_tokens (List[int]): the number of image tokens
|
||||
"""
|
||||
|
||||
prepare = self.process_one(
|
||||
prompt=prompt, conversations=conversations, images=images
|
||||
)
|
||||
|
||||
if force_batchify:
|
||||
prepare = self.batchify([prepare])
|
||||
|
||||
return prepare
|
||||
|
||||
def batchify(
|
||||
self, prepare_list: List[VLChatProcessorOutput]
|
||||
) -> BatchedVLChatProcessorOutput:
|
||||
"""
|
||||
Preprocesses the inputs for multimodal inference.
|
||||
|
||||
Args:
|
||||
prepare_list (List[VLChatProcessorOutput]): A list of VLChatProcessorOutput.
|
||||
|
||||
Returns:
|
||||
BatchedVLChatProcessorOutput: A dictionary of the inputs to use for multimodal inference.
|
||||
"""
|
||||
|
||||
batch_size = len(prepare_list)
|
||||
sft_format = []
|
||||
n_images = []
|
||||
seq_lens = []
|
||||
for prepare in prepare_list:
|
||||
n_images.append(len(prepare.num_image_tokens))
|
||||
seq_lens.append(len(prepare))
|
||||
|
||||
input_token_max_len = max(seq_lens)
|
||||
max_n_images = max(1, max(n_images))
|
||||
|
||||
batched_input_ids = torch.full(
|
||||
(batch_size, input_token_max_len), self.pad_id
|
||||
).long() # FIXME
|
||||
batched_attention_mask = torch.zeros((batch_size, input_token_max_len)).long()
|
||||
batched_pixel_values = torch.zeros(
|
||||
(batch_size, max_n_images, *self.image_processor.default_shape)
|
||||
).float()
|
||||
batched_images_seq_mask = torch.zeros((batch_size, input_token_max_len)).bool()
|
||||
batched_images_emb_mask = torch.zeros(
|
||||
(batch_size, max_n_images, self.num_image_tokens)
|
||||
).bool()
|
||||
|
||||
for i, prepare in enumerate(prepare_list):
|
||||
input_ids = prepare.input_ids
|
||||
seq_len = len(prepare)
|
||||
n_image = len(prepare.num_image_tokens)
|
||||
# left-padding
|
||||
batched_attention_mask[i, -seq_len:] = 1
|
||||
batched_input_ids[i, -seq_len:] = torch.LongTensor(input_ids)
|
||||
batched_images_seq_mask[i, -seq_len:] = input_ids == self.image_id
|
||||
|
||||
if n_image > 0:
|
||||
batched_pixel_values[i, :n_image] = prepare.pixel_values
|
||||
for j, n_image_tokens in enumerate(prepare.num_image_tokens):
|
||||
batched_images_emb_mask[i, j, :n_image_tokens] = True
|
||||
|
||||
sft_format.append(prepare.sft_format)
|
||||
|
||||
batched_prepares = BatchedVLChatProcessorOutput(
|
||||
input_ids=batched_input_ids,
|
||||
attention_mask=batched_attention_mask,
|
||||
pixel_values=batched_pixel_values,
|
||||
images_seq_mask=batched_images_seq_mask,
|
||||
images_emb_mask=batched_images_emb_mask,
|
||||
sft_format=sft_format,
|
||||
)
|
||||
|
||||
return batched_prepares
|
||||
|
||||
|
||||
class VLMImageProcessorConfig(PretrainedConfig):
|
||||
model_type = "deepseek_vlm"
|
||||
image_size: int = None
|
||||
min_size: int = None
|
||||
image_mean: Union[Tuple[float, float, float], List[float]] = None
|
||||
image_std: Union[Tuple[float, float, float], List[float]] = None
|
||||
rescale_factor: float = None
|
||||
do_normalize: bool = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
image_size: int,
|
||||
min_size: int = 14,
|
||||
image_mean: Union[Tuple[float, float, float], List[float]] = (
|
||||
0.48145466,
|
||||
0.4578275,
|
||||
0.40821073,
|
||||
),
|
||||
image_std: Union[Tuple[float, float, float], List[float]] = (
|
||||
0.26862954,
|
||||
0.26130258,
|
||||
0.27577711,
|
||||
),
|
||||
rescale_factor: float = 1.0 / 255.0,
|
||||
do_normalize: bool = True,
|
||||
**kwargs,
|
||||
):
|
||||
self.image_size = image_size
|
||||
self.min_size = min_size
|
||||
self.image_mean = image_mean
|
||||
self.image_std = image_std
|
||||
self.rescale_factor = rescale_factor
|
||||
self.do_normalize = do_normalize
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
register_processor(MultiModalityConfig, VLChatProcessor)
|
||||
register_image_processor(MultiModalityConfig, VLMImageProcessor)
|
||||
80
third_party/sglang/python/sglang/srt/configs/jet_nemotron.py
vendored
Normal file
80
third_party/sglang/python/sglang/srt/configs/jet_nemotron.py
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from transformers.configuration_utils import PretrainedConfig
|
||||
|
||||
from sglang.srt.configs.mamba_utils import (
|
||||
Mamba2CacheParams,
|
||||
Mamba2StateShape,
|
||||
mamba2_state_dtype,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class JetBlockConfig:
|
||||
mode: str
|
||||
expand_v: float
|
||||
num_heads: int
|
||||
head_dim: int
|
||||
norm_eps: str
|
||||
conv_size: int
|
||||
dconv_generator_reduction: int
|
||||
dconv_implementation: str
|
||||
|
||||
|
||||
class JetNemotronConfig(PretrainedConfig):
|
||||
model_type: str = "jet_nemotron"
|
||||
|
||||
efficient_attention_config: dict[str, dict[str, Any]] = None
|
||||
hidden_act: str = None
|
||||
hidden_size: int = None
|
||||
initializer_range: float = None
|
||||
intermediate_size: int = None
|
||||
layer_types: list[str] = None
|
||||
max_position_embeddings: int = None
|
||||
num_attention_heads: int = None
|
||||
num_key_value_heads: int = None
|
||||
rms_norm_eps: float = None
|
||||
rope_scaling: None = None
|
||||
rope_theta: float = None
|
||||
|
||||
@property
|
||||
def full_attention_layer_ids(self) -> list[int]:
|
||||
return [
|
||||
idx
|
||||
for idx, layer_type in enumerate(self.layer_types)
|
||||
if layer_type in ("attn", "swa")
|
||||
]
|
||||
|
||||
@property
|
||||
def linear_layer_ids(self) -> list[int]:
|
||||
return [
|
||||
idx
|
||||
for idx, layer_type in enumerate(self.layer_types)
|
||||
if layer_type == "jet"
|
||||
]
|
||||
|
||||
@property
|
||||
def mamba2_cache_params(self) -> Mamba2CacheParams:
|
||||
from sglang.srt.layers.dp_attention import get_attention_tp_size
|
||||
|
||||
jet_block_config = JetBlockConfig(**self.efficient_attention_config["jet"])
|
||||
|
||||
num_heads = jet_block_config.num_heads
|
||||
head_k_dim = jet_block_config.head_dim
|
||||
head_v_dim = int(head_k_dim * jet_block_config.expand_v)
|
||||
total_v_dim = num_heads * head_v_dim
|
||||
|
||||
shape = Mamba2StateShape.create(
|
||||
tp_world_size=get_attention_tp_size(),
|
||||
intermediate_size=total_v_dim,
|
||||
n_groups=num_heads,
|
||||
num_heads=num_heads,
|
||||
head_dim=head_v_dim,
|
||||
state_size=head_k_dim,
|
||||
conv_kernel=jet_block_config.conv_size,
|
||||
)
|
||||
|
||||
return Mamba2CacheParams(
|
||||
shape=shape, layers=self.linear_layer_ids, dtype=mamba2_state_dtype(self)
|
||||
)
|
||||
53
third_party/sglang/python/sglang/srt/configs/jet_vlm.py
vendored
Normal file
53
third_party/sglang/python/sglang/srt/configs/jet_vlm.py
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
from typing import Any
|
||||
|
||||
from transformers.configuration_utils import PretrainedConfig
|
||||
from transformers.models.siglip import SiglipVisionConfig
|
||||
|
||||
from sglang.srt.configs.jet_nemotron import JetNemotronConfig
|
||||
from sglang.srt.configs.mamba_utils import Mamba2CacheParams
|
||||
|
||||
|
||||
class JetVLMConfig(PretrainedConfig):
|
||||
model_type = "jet_vlm"
|
||||
sub_configs = {
|
||||
"text_config": JetNemotronConfig,
|
||||
"vision_config": SiglipVisionConfig,
|
||||
}
|
||||
_auto_class = "AutoConfig"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
text_config: dict[str, Any] | None = None,
|
||||
vision_config: dict[str, Any] | None = None,
|
||||
image_token_id: int | None = None,
|
||||
video_token_id: int | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
self.text_config = (
|
||||
JetNemotronConfig(**text_config)
|
||||
if text_config is not None
|
||||
else JetNemotronConfig()
|
||||
)
|
||||
self.vision_config = (
|
||||
SiglipVisionConfig(**vision_config)
|
||||
if vision_config is not None
|
||||
else SiglipVisionConfig()
|
||||
)
|
||||
|
||||
self.image_token_id = image_token_id if image_token_id is not None else -1
|
||||
self.video_token_id = video_token_id if video_token_id is not None else -1
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
@property
|
||||
def full_attention_layer_ids(self) -> list[int]:
|
||||
return self.text_config.full_attention_layer_ids
|
||||
|
||||
@property
|
||||
def linear_layer_ids(self) -> list[int]:
|
||||
return self.text_config.linear_layer_ids
|
||||
|
||||
@property
|
||||
def mamba2_cache_params(self) -> Mamba2CacheParams:
|
||||
return self.text_config.mamba2_cache_params
|
||||
171
third_party/sglang/python/sglang/srt/configs/kimi_k25.py
vendored
Normal file
171
third_party/sglang/python/sglang/srt/configs/kimi_k25.py
vendored
Normal file
@@ -0,0 +1,171 @@
|
||||
"""
|
||||
Kimi K25 Model Configuration.
|
||||
"""
|
||||
|
||||
from transformers import DeepseekV3Config
|
||||
from transformers.configuration_utils import PretrainedConfig
|
||||
|
||||
|
||||
class KimiK25VisionConfig(PretrainedConfig):
|
||||
"""Vision configuration for K2-VL (vision tower + mm projector).
|
||||
|
||||
Args:
|
||||
Vision Tower Parameters:
|
||||
patch_size: Patch size for vision tower.
|
||||
init_pos_emb_height: Initial position embedding height.
|
||||
init_pos_emb_width: Initial position embedding width.
|
||||
init_pos_emb_time: Initial position embedding time dimension.
|
||||
pos_emb_type: Type of position embedding.
|
||||
num_attention_heads: Number of attention heads in vision tower.
|
||||
num_hidden_layers: Number of hidden layers in vision tower.
|
||||
hidden_size: Hidden size of vision tower.
|
||||
intermediate_size: Intermediate size in vision tower FFN.
|
||||
merge_kernel_size: Kernel size for spatial patch merging.
|
||||
video_attn_type: Type of video attention.
|
||||
merge_type: Type of merge operation.
|
||||
|
||||
MM Projector Parameters:
|
||||
mm_projector_type: Type of multimodal projector.
|
||||
mm_hidden_size: Hidden size for projector (defaults to hidden_size).
|
||||
projector_hidden_act: Activation function for projector.
|
||||
projector_ln_eps: Layer norm epsilon for projector.
|
||||
"""
|
||||
|
||||
model_type = "kimi_k25"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
# Vision Tower
|
||||
patch_size: int = 14,
|
||||
init_pos_emb_height: int = 64,
|
||||
init_pos_emb_width: int = 64,
|
||||
init_pos_emb_time: int = 4,
|
||||
pos_emb_type: str = "divided_fixed",
|
||||
num_attention_heads: int = 16,
|
||||
num_hidden_layers: int = 27,
|
||||
hidden_size: int = 1152,
|
||||
intermediate_size: int = 4304,
|
||||
merge_kernel_size: tuple[int, int] = (2, 2),
|
||||
video_attn_type: str = "spatial_temporal",
|
||||
merge_type: str = "sd2_tpool",
|
||||
# MM Projector
|
||||
mm_projector_type: str = "patchmerger",
|
||||
mm_hidden_size: int | None = None,
|
||||
projector_hidden_act: str = "gelu",
|
||||
projector_ln_eps: float = 1e-5,
|
||||
text_hidden_size: int = 7168,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
# Vision Tower
|
||||
self.patch_size = patch_size
|
||||
self.init_pos_emb_height = init_pos_emb_height
|
||||
self.init_pos_emb_width = init_pos_emb_width
|
||||
self.init_pos_emb_time = init_pos_emb_time
|
||||
self.pos_emb_type = pos_emb_type
|
||||
self.num_attention_heads = num_attention_heads
|
||||
self.num_hidden_layers = num_hidden_layers
|
||||
self.hidden_size = hidden_size
|
||||
self.intermediate_size = intermediate_size
|
||||
self.merge_kernel_size = merge_kernel_size
|
||||
self.video_attn_type = video_attn_type
|
||||
self.merge_type = merge_type
|
||||
# MM Projector
|
||||
self.mm_projector_type = mm_projector_type
|
||||
if mm_hidden_size is not None:
|
||||
self.mm_hidden_size = mm_hidden_size
|
||||
else:
|
||||
self.mm_hidden_size = hidden_size
|
||||
self.projector_hidden_act = projector_hidden_act
|
||||
self.projector_ln_eps = projector_ln_eps
|
||||
self.text_hidden_size = text_hidden_size
|
||||
|
||||
|
||||
class KimiK25Config(PretrainedConfig):
|
||||
"""K2-VL model configuration.
|
||||
|
||||
K2-VL extends Kimi-VL with video support using video-chunks.
|
||||
A video-chunk consists of multiple consecutive frames (default: 4)
|
||||
that are processed together with temporal pooling.
|
||||
|
||||
Args:
|
||||
text_config: Configuration for the text model (DeepseekV3).
|
||||
|
||||
Vision Tower Parameters:
|
||||
patch_size: Patch size for vision tower.
|
||||
init_pos_emb_height: Initial position embedding height.
|
||||
init_pos_emb_width: Initial position embedding width.
|
||||
init_pos_emb_time: Initial position embedding time dimension.
|
||||
pos_emb_type: Type of position embedding.
|
||||
vt_num_attention_heads: Number of attention heads in vision tower.
|
||||
vt_num_hidden_layers: Number of hidden layers in vision tower.
|
||||
vt_hidden_size: Hidden size of vision tower.
|
||||
vt_intermediate_size: Intermediate size in vision tower FFN.
|
||||
merge_kernel_size: Kernel size for spatial patch merging.
|
||||
video_attn_type: Type of video attention.
|
||||
merge_type: Type of merge operation.
|
||||
|
||||
Video-Chunk Parameters:
|
||||
temporal_merge_kernel_size: Number of frames per video chunk.
|
||||
Default is 4, meaning 4 frames are merged into 1 chunk.
|
||||
sample_fps: Video sampling frame rate.
|
||||
timestamp_mode: Format for chunk timestamps.
|
||||
|
||||
MM Projector Parameters:
|
||||
mm_projector_type: Type of multimodal projector.
|
||||
mm_hidden_size: Hidden size from vision tower.
|
||||
projector_hidden_act: Activation function for projector.
|
||||
projector_ln_eps: Layer norm epsilon for projector.
|
||||
|
||||
Other Parameters:
|
||||
ignore_index: The ignore index for the loss function.
|
||||
media_placeholder_token_id: The token ID for media placeholders.
|
||||
pad_token_id: The token ID for padding.
|
||||
"""
|
||||
|
||||
model_type = "kimi_k25"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
text_config: dict | DeepseekV3Config | None = None,
|
||||
vision_config: dict | KimiK25VisionConfig | None = None,
|
||||
# Other parameters
|
||||
ignore_index: int = -100,
|
||||
media_placeholder_token_id: int = 163605,
|
||||
pad_token_id: int = 0,
|
||||
use_unified_vision_chunk: bool = False,
|
||||
video_placeholder: str = "<|kimi_k25_video_placeholder|>",
|
||||
**kwargs,
|
||||
):
|
||||
if text_config is None:
|
||||
text_config = DeepseekV3Config()
|
||||
elif isinstance(text_config, dict):
|
||||
text_config = DeepseekV3Config(**text_config)
|
||||
|
||||
if vision_config is None:
|
||||
vision_config = KimiK25VisionConfig()
|
||||
elif isinstance(vision_config, dict):
|
||||
vision_config = KimiK25VisionConfig(**vision_config)
|
||||
self.vision_config = vision_config
|
||||
self.text_config = text_config
|
||||
# Other config
|
||||
self.ignore_index = ignore_index
|
||||
self.media_placeholder_token_id = media_placeholder_token_id
|
||||
self.use_unified_vision_chunk = use_unified_vision_chunk
|
||||
self.video_placeholder = video_placeholder
|
||||
|
||||
# Propagate quantization config from text model
|
||||
if getattr(self.text_config, "quantization_config", None) is not None:
|
||||
self.quantization_config = self.text_config.quantization_config
|
||||
|
||||
super().__init__(pad_token_id=pad_token_id, **kwargs)
|
||||
|
||||
@property
|
||||
def hidden_size(self) -> int:
|
||||
"""Get hidden size from text config for compatibility."""
|
||||
return self.text_config.hidden_size
|
||||
|
||||
@property
|
||||
def vocab_size(self) -> int:
|
||||
"""Get vocab size from text config for compatibility."""
|
||||
return self.text_config.vocab_size
|
||||
161
third_party/sglang/python/sglang/srt/configs/kimi_linear.py
vendored
Normal file
161
third_party/sglang/python/sglang/srt/configs/kimi_linear.py
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
# Adapted from: https://github.com/vllm-project/vllm/blob/0384aa7150c4c9778efca041ffd1beb3ad2bd694/vllm/transformers_utils/configs/kimi_linear.py
|
||||
from transformers.configuration_utils import PretrainedConfig
|
||||
|
||||
from sglang.srt.configs.mamba_utils import KimiLinearCacheParams, KimiLinearStateShape
|
||||
|
||||
|
||||
class KimiLinearConfig(PretrainedConfig):
|
||||
model_type = "kimi_linear"
|
||||
keys_to_ignore_at_inference = ["past_key_values"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
model_type="kimi_linear",
|
||||
vocab_size=163840,
|
||||
hidden_size=4096,
|
||||
head_dim=None,
|
||||
intermediate_size=11008,
|
||||
num_hidden_layers=32,
|
||||
num_attention_heads=32,
|
||||
num_key_value_heads=None,
|
||||
hidden_act="silu",
|
||||
initializer_range=0.02,
|
||||
rms_norm_eps=1e-6,
|
||||
use_cache=True,
|
||||
pad_token_id=0,
|
||||
bos_token_id=1,
|
||||
eos_token_id=2,
|
||||
rope_theta=10000.0,
|
||||
rope_scaling=None,
|
||||
tie_word_embeddings=False,
|
||||
moe_intermediate_size: int | None = None,
|
||||
moe_renormalize: bool = True,
|
||||
moe_router_activation_func: str = "sigmoid",
|
||||
num_experts: int | None = None,
|
||||
num_experts_per_token: int | None = None,
|
||||
num_shared_experts: int = 0,
|
||||
routed_scaling_factor: float = 1.0,
|
||||
first_k_dense_replace: int = 0,
|
||||
moe_layer_freq: int = 1,
|
||||
use_grouped_topk: bool = True,
|
||||
num_expert_group: int = 1,
|
||||
topk_group: int = 1,
|
||||
q_lora_rank: int | None = None,
|
||||
kv_lora_rank: int | None = None,
|
||||
qk_nope_head_dim: int | None = None,
|
||||
qk_rope_head_dim: int | None = None,
|
||||
v_head_dim: int | None = None,
|
||||
mla_use_nope: bool | None = False,
|
||||
num_nextn_predict_layers: int = 0,
|
||||
linear_attn_config: dict | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
self.model_type = model_type
|
||||
self.vocab_size = vocab_size
|
||||
self.hidden_size = hidden_size
|
||||
self.head_dim = (
|
||||
head_dim if head_dim is not None else hidden_size // num_attention_heads
|
||||
)
|
||||
self.intermediate_size = intermediate_size
|
||||
self.num_hidden_layers = num_hidden_layers
|
||||
self.num_attention_heads = num_attention_heads
|
||||
|
||||
# for backward compatibility
|
||||
if num_key_value_heads is None:
|
||||
num_key_value_heads = num_attention_heads
|
||||
|
||||
self.num_key_value_heads = num_key_value_heads
|
||||
self.hidden_act = hidden_act
|
||||
self.initializer_range = initializer_range
|
||||
self.rms_norm_eps = rms_norm_eps
|
||||
self.use_cache = use_cache
|
||||
self.rope_theta = rope_theta
|
||||
self.rope_scaling = rope_scaling
|
||||
|
||||
self.q_lora_rank = q_lora_rank
|
||||
self.kv_lora_rank = kv_lora_rank
|
||||
self.qk_nope_head_dim = qk_nope_head_dim
|
||||
self.qk_rope_head_dim = qk_rope_head_dim
|
||||
self.v_head_dim = v_head_dim
|
||||
self.mla_use_nope = mla_use_nope
|
||||
# moe config
|
||||
self.n_routed_experts = self.num_experts = num_experts
|
||||
self.num_experts_per_token = num_experts_per_token
|
||||
self.moe_renormalize = moe_renormalize
|
||||
self.num_shared_experts = num_shared_experts
|
||||
self.routed_scaling_factor = routed_scaling_factor
|
||||
self.moe_router_activation_func = moe_router_activation_func
|
||||
assert self.moe_router_activation_func in ("softmax", "sigmoid")
|
||||
self.moe_intermediate_size = moe_intermediate_size
|
||||
self.first_k_dense_replace = first_k_dense_replace
|
||||
self.moe_layer_freq = moe_layer_freq
|
||||
self.use_grouped_topk = use_grouped_topk
|
||||
self.num_expert_group = num_expert_group
|
||||
self.topk_group = topk_group
|
||||
self.num_nextn_predict_layers = num_nextn_predict_layers
|
||||
|
||||
if linear_attn_config is not None:
|
||||
assert linear_attn_config["kda_layers"] is not None
|
||||
assert linear_attn_config["full_attn_layers"] is not None
|
||||
self.linear_attn_config = linear_attn_config
|
||||
|
||||
super().__init__(
|
||||
pad_token_id=pad_token_id,
|
||||
bos_token_id=bos_token_id,
|
||||
eos_token_id=eos_token_id,
|
||||
tie_word_embeddings=tie_word_embeddings,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@property
|
||||
def is_mla(self):
|
||||
return (
|
||||
self.q_lora_rank is not None
|
||||
or self.kv_lora_rank is not None
|
||||
or self.qk_nope_head_dim is not None
|
||||
or self.qk_rope_head_dim is not None
|
||||
or self.v_head_dim is not None
|
||||
or self.mla_use_nope is True
|
||||
)
|
||||
|
||||
@property
|
||||
def is_moe(self):
|
||||
return self.num_experts is not None
|
||||
|
||||
@property
|
||||
def is_linear_attn(self) -> bool:
|
||||
return not (
|
||||
self.linear_attn_config is None
|
||||
or (
|
||||
isinstance(self.linear_attn_config, dict)
|
||||
and self.linear_attn_config["kda_layers"] is not None
|
||||
and len(self.linear_attn_config["kda_layers"]) == 0
|
||||
)
|
||||
)
|
||||
|
||||
def is_kda_layer(self, layer_idx: int):
|
||||
return (
|
||||
self.linear_attn_config is not None
|
||||
and (layer_idx + 1) in self.linear_attn_config["kda_layers"]
|
||||
)
|
||||
|
||||
@property
|
||||
def linear_layer_ids(self):
|
||||
return [i for i in range(self.num_hidden_layers) if self.is_kda_layer(i)]
|
||||
|
||||
@property
|
||||
def full_attention_layer_ids(self):
|
||||
return [i for i in range(self.num_hidden_layers) if not self.is_kda_layer(i)]
|
||||
|
||||
@property
|
||||
def mamba2_cache_params(self) -> KimiLinearCacheParams:
|
||||
from sglang.srt.layers.dp_attention import get_attention_tp_size
|
||||
|
||||
shape = KimiLinearStateShape.create(
|
||||
tp_world_size=get_attention_tp_size(),
|
||||
num_heads=self.linear_attn_config["num_heads"],
|
||||
head_dim=self.linear_attn_config["head_dim"],
|
||||
conv_kernel_size=self.linear_attn_config["short_conv_kernel_size"],
|
||||
)
|
||||
|
||||
return KimiLinearCacheParams(shape=shape, layers=self.linear_layer_ids)
|
||||
38
third_party/sglang/python/sglang/srt/configs/kimi_vl.py
vendored
Normal file
38
third_party/sglang/python/sglang/srt/configs/kimi_vl.py
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Adapted from https://huggingface.co/moonshotai/Kimi-VL-A3B-Instruct/blob/main/configuration_kimi_vl.py
|
||||
from typing import Optional, Union
|
||||
|
||||
from transformers.configuration_utils import PretrainedConfig
|
||||
|
||||
from sglang.srt.configs.deepseekvl2 import DeepseekV2Config
|
||||
from sglang.srt.configs.kimi_vl_moonvit import MoonViTConfig
|
||||
|
||||
|
||||
class KimiVLConfig(PretrainedConfig):
|
||||
model_type = "kimi_vl"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vision_config: Optional[Union[dict, MoonViTConfig]] = None,
|
||||
text_config: Optional[Union[dict, DeepseekV2Config]] = None,
|
||||
ignore_index: int = -100,
|
||||
media_placeholder_token_id: int = 163605,
|
||||
pad_token_id: int = 0,
|
||||
**kwargs
|
||||
):
|
||||
if vision_config is None:
|
||||
vision_config = MoonViTConfig()
|
||||
elif isinstance(vision_config, dict):
|
||||
vision_config = MoonViTConfig(**vision_config)
|
||||
self.vision_config = vision_config
|
||||
|
||||
if text_config is None:
|
||||
text_config = DeepseekV2Config()
|
||||
elif isinstance(text_config, dict):
|
||||
text_config = DeepseekV2Config(**text_config)
|
||||
self.text_config = text_config
|
||||
|
||||
self.ignore_index = ignore_index
|
||||
self.media_placeholder_token_id = media_placeholder_token_id
|
||||
|
||||
super().__init__(pad_token_id=pad_token_id, **kwargs)
|
||||
32
third_party/sglang/python/sglang/srt/configs/kimi_vl_moonvit.py
vendored
Normal file
32
third_party/sglang/python/sglang/srt/configs/kimi_vl_moonvit.py
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Adapted from https://huggingface.co/moonshotai/Kimi-VL-A3B-Instruct/blob/main/configuration_kimi_vl.py
|
||||
from transformers.configuration_utils import PretrainedConfig
|
||||
|
||||
|
||||
class MoonViTConfig(PretrainedConfig):
|
||||
model_type = "moonvit"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
patch_size: int = 14,
|
||||
init_pos_emb_height: int = 64,
|
||||
init_pos_emb_width: int = 64,
|
||||
num_attention_heads: int = 16,
|
||||
num_hidden_layers: int = 27,
|
||||
hidden_size: int = 1152,
|
||||
intermediate_size: int = 4304,
|
||||
merge_kernel_size: tuple[int, int] = (2, 2),
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
self.patch_size = patch_size
|
||||
# Positional embedding config
|
||||
self.init_pos_emb_height = init_pos_emb_height
|
||||
self.init_pos_emb_width = init_pos_emb_width
|
||||
# Transformer config
|
||||
self.num_hidden_layers = num_hidden_layers
|
||||
self.num_attention_heads = num_attention_heads
|
||||
self.hidden_size = hidden_size
|
||||
self.intermediate_size = intermediate_size
|
||||
# Patch merger config
|
||||
self.merge_kernel_size = merge_kernel_size
|
||||
104
third_party/sglang/python/sglang/srt/configs/lfm2.py
vendored
Normal file
104
third_party/sglang/python/sglang/srt/configs/lfm2.py
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
# coding=utf-8
|
||||
# Copyright 2024 Liquid AI and the HuggingFace Inc. team. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
"""LFM2 (Liquid Foundation Model 2) configuration"""
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from transformers import CONFIG_MAPPING
|
||||
from transformers import Lfm2Config as HFLfm2Config
|
||||
from transformers.utils import logging
|
||||
|
||||
from sglang.srt.configs.mamba_utils import (
|
||||
Mamba2CacheParams,
|
||||
Mamba2StateShape,
|
||||
mamba2_state_dtype,
|
||||
)
|
||||
|
||||
logger = logging.get_logger(__name__)
|
||||
|
||||
|
||||
class Lfm2Config(HFLfm2Config):
|
||||
"""
|
||||
SGLang configuration for LFM2 models.
|
||||
|
||||
Extends HuggingFace's Lfm2Config with hybrid model properties needed by SGLang.
|
||||
LFM2 uses a hybrid architecture mixing full attention and ShortConv layers.
|
||||
"""
|
||||
|
||||
@property
|
||||
def full_attention_layer_ids(self) -> List[int]:
|
||||
"""Return indices of attention layers for KV cache."""
|
||||
return [i for i, lt in enumerate(self.layer_types) if lt == "full_attention"]
|
||||
|
||||
@property
|
||||
def linear_layer_ids(self) -> List[int]:
|
||||
"""Return indices of conv layers for conv state cache."""
|
||||
return [
|
||||
i for i, lt in enumerate(self.layer_types) if lt in ("conv", "short_conv")
|
||||
]
|
||||
|
||||
@property
|
||||
def mamba_chunk_size(self) -> int:
|
||||
"""Return chunk size for Mamba2 backend. LFM2 doesn't use chunking, return 1."""
|
||||
return 1
|
||||
|
||||
@property
|
||||
def mamba2_cache_params(self) -> Optional[Mamba2CacheParams]:
|
||||
"""
|
||||
Get cache params for HybridReqToTokenPool initialization.
|
||||
|
||||
LFM2 uses ShortConv layers with a small fixed-size cache (kernel_size - 1).
|
||||
Unlike full Mamba2 models, LFM2 only uses the conv state, not SSM temporal state.
|
||||
"""
|
||||
from sglang.srt.layers.dp_attention import get_attention_tp_size
|
||||
|
||||
conv_layer_ids = self.linear_layer_ids
|
||||
if not conv_layer_ids:
|
||||
return None
|
||||
|
||||
hidden_size = self.hidden_size
|
||||
conv_kernel = int(self.conv_L_cache)
|
||||
|
||||
# get_attention_tp_size() requires initialization, default to 1 if not available
|
||||
try:
|
||||
tp_size = get_attention_tp_size()
|
||||
except (AssertionError, RuntimeError):
|
||||
tp_size = 1
|
||||
|
||||
# For ShortConv layers, we use a simplified Mamba2StateShape
|
||||
# LFM2 doesn't use SSM state (state_size=0), only conv state
|
||||
# We pass num_heads=tp_size so divide(tp_size, tp_size)=1 always works.
|
||||
# Since state_size=0, the temporal state shape has zero elements anyway.
|
||||
shape = Mamba2StateShape.create(
|
||||
tp_world_size=tp_size,
|
||||
intermediate_size=hidden_size,
|
||||
n_groups=1, # ShortConv doesn't use grouping
|
||||
num_heads=tp_size, # Ensures divide works; temporal state is empty anyway
|
||||
head_dim=hidden_size, # Conv operates on full hidden dim
|
||||
state_size=0, # No SSM temporal state for ShortConv
|
||||
conv_kernel=conv_kernel,
|
||||
)
|
||||
|
||||
return Mamba2CacheParams(
|
||||
shape=shape,
|
||||
layers=conv_layer_ids,
|
||||
dtype=mamba2_state_dtype(self),
|
||||
)
|
||||
|
||||
|
||||
# Override HuggingFace's Lfm2Config with our extended version
|
||||
# Cannot use .register() because lfm2 is already registered by transformers
|
||||
# Directly modify the internal _extra_content dict instead
|
||||
CONFIG_MAPPING._extra_content["lfm2"] = Lfm2Config
|
||||
192
third_party/sglang/python/sglang/srt/configs/lfm2_moe.py
vendored
Normal file
192
third_party/sglang/python/sglang/srt/configs/lfm2_moe.py
vendored
Normal file
@@ -0,0 +1,192 @@
|
||||
# Copyright 2025 SGLang Team
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
"""LFM2-MoE (Liquid Foundation Model 2 - Mixture of Experts) configuration
|
||||
|
||||
Note: HF transformers has Lfm2MoeConfig in v5.0.0rc2 (unreleased).
|
||||
Once released, we could inherit from it like Lfm2Config does with HFLfm2Config.
|
||||
For now, we define a standalone config to support the model immediately.
|
||||
"""
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from transformers import CONFIG_MAPPING
|
||||
from transformers.configuration_utils import PretrainedConfig
|
||||
|
||||
from sglang.srt.configs.mamba_utils import Mamba2CacheParams, Mamba2StateShape
|
||||
|
||||
|
||||
class Lfm2MoeConfig(PretrainedConfig):
|
||||
"""
|
||||
Configuration for LFM2-MoE models (e.g., LiquidAI/LFM2-8B-A1B).
|
||||
|
||||
LFM2-MoE is a hybrid architecture with:
|
||||
- Attention layers and ShortConv layers (like dense LFM2)
|
||||
- MoE (Mixture of Experts) FFN layers with sigmoid routing
|
||||
|
||||
Key MoE specifics:
|
||||
- First `num_dense_layers` use dense MLP, rest use MoE
|
||||
- Sigmoid routing (not softmax) with expert_bias for load balancing
|
||||
- expert_bias is fp32 for numerical stability
|
||||
"""
|
||||
|
||||
model_type = "lfm2_moe"
|
||||
keys_to_ignore_at_inference = ["past_key_values"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vocab_size: int = 65536,
|
||||
hidden_size: int = 2048,
|
||||
intermediate_size: int = 7168,
|
||||
moe_intermediate_size: int = 1792,
|
||||
num_hidden_layers: int = 32,
|
||||
num_attention_heads: int = 32,
|
||||
num_key_value_heads: int = 8,
|
||||
max_position_embeddings: int = 128000,
|
||||
initializer_range: float = 0.02,
|
||||
norm_eps: float = 1e-5,
|
||||
use_cache: bool = True,
|
||||
pad_token_id: int = 0,
|
||||
bos_token_id: int = 1,
|
||||
eos_token_id: int = 2,
|
||||
tie_word_embeddings: bool = True,
|
||||
rope_parameters: Optional[dict] = None,
|
||||
conv_bias: bool = False,
|
||||
conv_L_cache: int = 3,
|
||||
# MoE-specific parameters
|
||||
num_dense_layers: int = 2,
|
||||
num_experts: int = 32,
|
||||
num_experts_per_tok: int = 4,
|
||||
use_expert_bias: bool = True,
|
||||
routed_scaling_factor: float = 1.0,
|
||||
norm_topk_prob: bool = True,
|
||||
# Layer types
|
||||
layer_types: Optional[List[str]] = None,
|
||||
**kwargs,
|
||||
):
|
||||
self.vocab_size = vocab_size
|
||||
self.hidden_size = hidden_size
|
||||
self.intermediate_size = intermediate_size
|
||||
self.moe_intermediate_size = moe_intermediate_size
|
||||
self.num_hidden_layers = num_hidden_layers
|
||||
self.num_attention_heads = num_attention_heads
|
||||
self.num_key_value_heads = num_key_value_heads
|
||||
self.max_position_embeddings = max_position_embeddings
|
||||
self.initializer_range = initializer_range
|
||||
self.norm_eps = norm_eps
|
||||
self.use_cache = use_cache
|
||||
|
||||
# Conv parameters
|
||||
self.conv_bias = conv_bias
|
||||
self.conv_L_cache = conv_L_cache
|
||||
|
||||
# MoE parameters
|
||||
self.num_dense_layers = num_dense_layers
|
||||
self.num_experts = num_experts
|
||||
self.num_experts_per_tok = num_experts_per_tok
|
||||
self.use_expert_bias = use_expert_bias
|
||||
self.routed_scaling_factor = routed_scaling_factor
|
||||
self.norm_topk_prob = norm_topk_prob
|
||||
|
||||
# Layer types (attention vs conv)
|
||||
self.layer_types = layer_types
|
||||
|
||||
# RoPE parameters
|
||||
self.rope_parameters = rope_parameters
|
||||
|
||||
# Validate layer_types length matches num_hidden_layers
|
||||
if layer_types is not None and len(layer_types) != num_hidden_layers:
|
||||
raise ValueError(
|
||||
f"layer_types length ({len(layer_types)}) must match "
|
||||
f"num_hidden_layers ({num_hidden_layers})"
|
||||
)
|
||||
|
||||
# Handle tie_embedding alias from original config
|
||||
tie_word_embeddings = kwargs.pop("tie_embedding", tie_word_embeddings)
|
||||
|
||||
super().__init__(
|
||||
pad_token_id=pad_token_id,
|
||||
bos_token_id=bos_token_id,
|
||||
eos_token_id=eos_token_id,
|
||||
tie_word_embeddings=tie_word_embeddings,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@property
|
||||
def full_attention_layer_ids(self) -> List[int]:
|
||||
"""Return indices of attention layers for KV cache."""
|
||||
if self.layer_types is None:
|
||||
return []
|
||||
return [i for i, lt in enumerate(self.layer_types) if lt == "full_attention"]
|
||||
|
||||
@property
|
||||
def linear_layer_ids(self) -> List[int]:
|
||||
"""Return indices of conv layers for conv state cache."""
|
||||
if self.layer_types is None:
|
||||
return []
|
||||
return [
|
||||
i for i, lt in enumerate(self.layer_types) if lt in ("conv", "short_conv")
|
||||
]
|
||||
|
||||
@property
|
||||
def mamba_chunk_size(self) -> int:
|
||||
"""Return chunk size for Mamba2 backend. LFM2 doesn't use chunking."""
|
||||
return 1
|
||||
|
||||
@property
|
||||
def mamba2_cache_params(self) -> Optional[Mamba2CacheParams]:
|
||||
"""
|
||||
Get cache params for HybridReqToTokenPool initialization.
|
||||
|
||||
LFM2-MoE uses ShortConv layers with a small fixed-size cache.
|
||||
"""
|
||||
from sglang.srt.layers.dp_attention import get_attention_tp_size
|
||||
|
||||
conv_layer_ids = self.linear_layer_ids
|
||||
if not conv_layer_ids:
|
||||
return None
|
||||
|
||||
hidden_size = self.hidden_size
|
||||
# conv_L_cache in config is kernel_size (e.g., 3)
|
||||
conv_kernel = int(self.conv_L_cache)
|
||||
# actual cache size is kernel_size - 1 (e.g., 2 for kernel=3)
|
||||
|
||||
try:
|
||||
tp_size = get_attention_tp_size()
|
||||
except (AssertionError, RuntimeError):
|
||||
tp_size = 1
|
||||
|
||||
shape = Mamba2StateShape.create(
|
||||
tp_world_size=tp_size,
|
||||
intermediate_size=hidden_size,
|
||||
n_groups=1,
|
||||
num_heads=tp_size, # Ensures divide works; temporal state is empty anyway
|
||||
head_dim=hidden_size,
|
||||
state_size=0,
|
||||
conv_kernel=conv_kernel,
|
||||
)
|
||||
|
||||
# Uses default mamba2_state_dtype() which reads SGLANG_MAMBA_CONV_DTYPE env var
|
||||
# (defaults to bfloat16). Set SGLANG_MAMBA_CONV_DTYPE=float16 for fp16 inference.
|
||||
return Mamba2CacheParams(
|
||||
shape=shape,
|
||||
layers=conv_layer_ids,
|
||||
)
|
||||
|
||||
|
||||
# Register with transformers CONFIG_MAPPING so AutoConfig.from_pretrained()
|
||||
# can instantiate our config class when loading models with model_type="lfm2_moe"
|
||||
try:
|
||||
CONFIG_MAPPING.register("lfm2_moe", Lfm2MoeConfig)
|
||||
except Exception:
|
||||
# Already registered or registration failed - use direct assignment
|
||||
CONFIG_MAPPING._extra_content["lfm2_moe"] = Lfm2MoeConfig
|
||||
109
third_party/sglang/python/sglang/srt/configs/lfm2_vl.py
vendored
Normal file
109
third_party/sglang/python/sglang/srt/configs/lfm2_vl.py
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
# Copyright 2026 Liquid AI. All rights reserved.
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
"""LFM2-VL (Liquid Foundation Model 2 Vision-Language) configuration"""
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from transformers import CONFIG_MAPPING
|
||||
from transformers import Lfm2VlConfig as HFLfm2VlConfig
|
||||
from transformers.utils import logging
|
||||
|
||||
from sglang.srt.configs.mamba_utils import Mamba2CacheParams, Mamba2StateShape
|
||||
|
||||
logger = logging.get_logger(__name__)
|
||||
|
||||
|
||||
class Lfm2VlConfig(HFLfm2VlConfig):
|
||||
"""
|
||||
SGLang configuration for LFM2-VL models.
|
||||
|
||||
Extends HuggingFace's Lfm2VlConfig with hybrid model properties needed by SGLang.
|
||||
LFM2-VL combines:
|
||||
- SigLip2 vision encoder with NaFlex variable-resolution support
|
||||
- LFM2 language model with hybrid attention + short convolution
|
||||
- Multimodal projector with pixel unshuffle downsampling
|
||||
"""
|
||||
|
||||
@property
|
||||
def full_attention_layer_ids(self) -> List[int]:
|
||||
"""Return indices of attention layers for KV cache (from text_config)."""
|
||||
return [
|
||||
i
|
||||
for i, lt in enumerate(self.text_config.layer_types)
|
||||
if lt == "full_attention"
|
||||
]
|
||||
|
||||
@property
|
||||
def linear_layer_ids(self) -> List[int]:
|
||||
"""Return indices of conv layers for conv state cache (from text_config)."""
|
||||
return [
|
||||
i
|
||||
for i, lt in enumerate(self.text_config.layer_types)
|
||||
if lt in ("conv", "short_conv")
|
||||
]
|
||||
|
||||
@property
|
||||
def mamba_chunk_size(self) -> int:
|
||||
"""Return chunk size for Mamba2 backend. LFM2 doesn't use chunking, return 1."""
|
||||
return 1
|
||||
|
||||
@property
|
||||
def mamba2_cache_params(self) -> Optional[Mamba2CacheParams]:
|
||||
"""
|
||||
Get cache params for HybridReqToTokenPool initialization.
|
||||
|
||||
LFM2 uses ShortConv layers with a small fixed-size cache (kernel_size - 1).
|
||||
Unlike full Mamba2 models, LFM2 only uses the conv state, not SSM temporal state.
|
||||
"""
|
||||
from sglang.srt.layers.dp_attention import get_attention_tp_size
|
||||
|
||||
conv_layer_ids = self.linear_layer_ids
|
||||
if not conv_layer_ids:
|
||||
return None
|
||||
|
||||
hidden_size = self.text_config.hidden_size
|
||||
# conv_L_cache in config is kernel_size (e.g., 3)
|
||||
conv_kernel = int(self.text_config.conv_L_cache)
|
||||
|
||||
# get_attention_tp_size() requires initialization, default to 1 if not available
|
||||
try:
|
||||
tp_size = get_attention_tp_size()
|
||||
except (AssertionError, RuntimeError):
|
||||
tp_size = 1
|
||||
|
||||
# For ShortConv layers, we use a simplified Mamba2StateShape
|
||||
# LFM2 doesn't use SSM state (state_size=0), only conv state
|
||||
# We pass num_heads=tp_size so divide(tp_size, tp_size)=1 always works.
|
||||
# Since state_size=0, the temporal state shape has zero elements anyway.
|
||||
shape = Mamba2StateShape.create(
|
||||
tp_world_size=tp_size,
|
||||
intermediate_size=hidden_size,
|
||||
n_groups=1, # ShortConv doesn't use grouping
|
||||
num_heads=tp_size, # Ensures divide works; temporal state is empty anyway
|
||||
head_dim=hidden_size, # Conv operates on full hidden dim
|
||||
state_size=0, # No SSM temporal state for ShortConv
|
||||
conv_kernel=conv_kernel,
|
||||
)
|
||||
|
||||
# Uses default mamba2_state_dtype() which reads SGLANG_MAMBA_CONV_DTYPE env var
|
||||
# (defaults to bfloat16). Set SGLANG_MAMBA_CONV_DTYPE=float16 for fp16 inference.
|
||||
return Mamba2CacheParams(
|
||||
shape=shape,
|
||||
layers=conv_layer_ids,
|
||||
)
|
||||
|
||||
|
||||
# Override HuggingFace's Lfm2VlConfig with our extended version
|
||||
# Cannot use .register() because lfm2_vl may already be registered by transformers
|
||||
# Directly modify the internal _extra_content dict instead
|
||||
CONFIG_MAPPING._extra_content["lfm2_vl"] = Lfm2VlConfig
|
||||
139
third_party/sglang/python/sglang/srt/configs/load_config.py
vendored
Normal file
139
third_party/sglang/python/sglang/srt/configs/load_config.py
vendored
Normal file
@@ -0,0 +1,139 @@
|
||||
# Adapted from https://github.com/vllm-project/vllm/blob/v0.6.4.post1/vllm/config.py
|
||||
import enum
|
||||
import logging
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, List, Optional, Union
|
||||
|
||||
import orjson
|
||||
|
||||
from sglang.srt.configs.modelopt_config import ModelOptConfig
|
||||
from sglang.srt.utils import is_hip
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LoadFormat(str, enum.Enum):
|
||||
AUTO = "auto"
|
||||
PT = "pt"
|
||||
SAFETENSORS = "safetensors"
|
||||
NPCACHE = "npcache"
|
||||
DUMMY = "dummy"
|
||||
SHARDED_STATE = "sharded_state"
|
||||
GGUF = "gguf"
|
||||
BITSANDBYTES = "bitsandbytes"
|
||||
MISTRAL = "mistral"
|
||||
LAYERED = "layered"
|
||||
FLASH_RL = "flash_rl" # For RL training with quantized models
|
||||
JAX = "jax"
|
||||
REMOTE = "remote"
|
||||
REMOTE_INSTANCE = "remote_instance"
|
||||
RDMA = "rdma"
|
||||
LOCAL_CACHED = "local_cached"
|
||||
FASTSAFETENSORS = "fastsafetensors"
|
||||
PRIVATE = "private"
|
||||
RUNAI_STREAMER = "runai_streamer"
|
||||
|
||||
|
||||
@dataclass
|
||||
class LoadConfig:
|
||||
"""
|
||||
download_dir: Directory to download and load the weights, default to the
|
||||
default cache directory of huggingface.
|
||||
load_format: The format of the model weights to load:
|
||||
"auto" will try to load the weights in the safetensors format and
|
||||
fall back to the pytorch bin format if safetensors format is
|
||||
not available.
|
||||
"pt" will load the weights in the pytorch bin format.
|
||||
"safetensors" will load the weights in the safetensors format.
|
||||
"npcache" will load the weights in pytorch format and store
|
||||
a numpy cache to speed up the loading.
|
||||
"dummy" will initialize the weights with random values, which is
|
||||
mainly for profiling.
|
||||
"bitsandbytes" will load nf4 type weights.
|
||||
"flash_rl" will load weights with support for RL training
|
||||
with quantized models, enabling efficient weight reloading.
|
||||
ignore_patterns: The list of patterns to ignore when loading the model.
|
||||
Default to "original/**/*" to avoid repeated loading of llama's
|
||||
checkpoints.
|
||||
decryption_key_file: If set, decrypts the output files with a password read
|
||||
from this file (after PBKDF2).
|
||||
decrypt_max_concurrency: The maximum number of concurrent processes to decrypt the safetensor files. -1 means no limit.
|
||||
|
||||
# ModelOpt-specific loading options
|
||||
modelopt_checkpoint_restore_path: Optional[str] = None
|
||||
modelopt_checkpoint_save_path: Optional[str] = None
|
||||
modelopt_export_path: Optional[str] = None
|
||||
"""
|
||||
|
||||
load_format: Union[str, LoadFormat] = LoadFormat.AUTO
|
||||
download_dir: Optional[str] = None
|
||||
model_loader_extra_config: Optional[Union[str, dict]] = field(default_factory=dict)
|
||||
ignore_patterns: Optional[Union[List[str], str]] = None
|
||||
decryption_key_file: Optional[str] = None
|
||||
decrypt_max_concurrency: int = -1
|
||||
tp_rank: Optional[int] = None
|
||||
remote_instance_weight_loader_seed_instance_ip: Optional[str] = None
|
||||
remote_instance_weight_loader_seed_instance_service_port: Optional[int] = None
|
||||
remote_instance_weight_loader_send_weights_group_ports: Optional[List[int]] = None
|
||||
remote_instance_weight_loader_backend: Optional[str] = None
|
||||
remote_instance_weight_loader_transfer_engine: Optional[Any] = None
|
||||
modelexpress_url: Optional[str] = None
|
||||
modelexpress_model_name: Optional[str] = None
|
||||
|
||||
# ModelOpt-specific loading options
|
||||
modelopt_checkpoint_restore_path: Optional[str] = None
|
||||
modelopt_checkpoint_save_path: Optional[str] = None
|
||||
modelopt_export_path: Optional[str] = None
|
||||
|
||||
# ModelOpt configuration object
|
||||
modelopt_config: Optional[ModelOptConfig] = None
|
||||
|
||||
# QuantizedRL-specific options (for FlashRL-style quantization)
|
||||
rl_quant_profile: Optional[str] = (
|
||||
None # Path to rollout quantization profile (e.g., /root/profile.7b.pt)
|
||||
)
|
||||
|
||||
# For multi-layer MTP
|
||||
draft_model_idx: Optional[int] = None
|
||||
|
||||
def __post_init__(self):
|
||||
model_loader_extra_config = self.model_loader_extra_config or {}
|
||||
if isinstance(model_loader_extra_config, str):
|
||||
self.model_loader_extra_config = orjson.loads(model_loader_extra_config)
|
||||
self._verify_load_format()
|
||||
|
||||
if self.ignore_patterns is not None and len(self.ignore_patterns) > 0:
|
||||
logger.info(
|
||||
"Ignoring the following patterns when downloading weights: %s",
|
||||
self.ignore_patterns,
|
||||
)
|
||||
else:
|
||||
self.ignore_patterns = ["original/**/*"]
|
||||
|
||||
# Create ModelOptConfig if not provided
|
||||
if self.modelopt_config is None:
|
||||
self.modelopt_config = ModelOptConfig(
|
||||
checkpoint_restore_path=self.modelopt_checkpoint_restore_path,
|
||||
checkpoint_save_path=self.modelopt_checkpoint_save_path,
|
||||
export_path=self.modelopt_export_path,
|
||||
)
|
||||
|
||||
def _verify_load_format(self) -> None:
|
||||
if not isinstance(self.load_format, str):
|
||||
return
|
||||
|
||||
load_format = self.load_format.lower()
|
||||
self.load_format = LoadFormat(load_format)
|
||||
|
||||
rocm_not_supported_load_format: List[str] = []
|
||||
if is_hip() and load_format in rocm_not_supported_load_format:
|
||||
rocm_supported_load_format = [
|
||||
f
|
||||
for f in LoadFormat.__members__
|
||||
if (f not in rocm_not_supported_load_format)
|
||||
]
|
||||
raise ValueError(
|
||||
f"load format '{load_format}' is not supported in ROCm. "
|
||||
f"Supported load formats are "
|
||||
f"{rocm_supported_load_format}"
|
||||
)
|
||||
112
third_party/sglang/python/sglang/srt/configs/longcat_flash.py
vendored
Normal file
112
third_party/sglang/python/sglang/srt/configs/longcat_flash.py
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
from transformers.configuration_utils import PretrainedConfig
|
||||
from transformers.utils import logging
|
||||
|
||||
logger = logging.get_logger(__name__)
|
||||
|
||||
FLASH_PRETRAINED_CONFIG_ARCHIVE_MAP = {}
|
||||
|
||||
|
||||
class LongcatFlashConfig(PretrainedConfig):
|
||||
model_type = "longcat_flash"
|
||||
keys_to_ignore_at_inference = ["past_key_values"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vocab_size=131072,
|
||||
hidden_size=6144,
|
||||
intermediate_size=None,
|
||||
ffn_hidden_size=12288,
|
||||
expert_ffn_hidden_size=2048,
|
||||
num_layers=28,
|
||||
num_hidden_layers=None,
|
||||
num_attention_heads=64,
|
||||
ep_size=1,
|
||||
kv_lora_rank=512,
|
||||
q_lora_rank=1536,
|
||||
qk_rope_head_dim=128,
|
||||
qk_nope_head_dim=128,
|
||||
v_head_dim=128,
|
||||
n_routed_experts=512,
|
||||
moe_topk=12,
|
||||
norm_topk_prob=False,
|
||||
max_position_embeddings=131072,
|
||||
rms_norm_eps=1e-05,
|
||||
use_cache=True,
|
||||
pad_token_id=None,
|
||||
bos_token_id=1,
|
||||
eos_token_id=2,
|
||||
pretraining_tp=1,
|
||||
tie_word_embeddings=False,
|
||||
rope_theta=10000000.0,
|
||||
rope_scaling=None,
|
||||
attention_bias=False,
|
||||
attention_dropout=0.0,
|
||||
mla_scale_q_lora=True,
|
||||
mla_scale_kv_lora=True,
|
||||
torch_dtype="bfloat16",
|
||||
params_dtype="bfloat16",
|
||||
rounter_params_dtype="float32",
|
||||
router_bias=False,
|
||||
topk_method=None,
|
||||
routed_scaling_factor=6.0,
|
||||
zero_expert_num=256,
|
||||
zero_expert_type="identity",
|
||||
nextn_use_scmoe=False,
|
||||
num_nextn_predict_layers=1,
|
||||
ngram_vocab_size_ratio=None,
|
||||
emb_neighbor_num=None,
|
||||
emb_split_num=None,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(
|
||||
pad_token_id=pad_token_id,
|
||||
bos_token_id=bos_token_id,
|
||||
eos_token_id=eos_token_id,
|
||||
tie_word_embeddings=tie_word_embeddings,
|
||||
torch_dtype=torch_dtype,
|
||||
params_dtype=params_dtype,
|
||||
rounter_params_dtype=rounter_params_dtype,
|
||||
topk_method=topk_method,
|
||||
router_bias=router_bias,
|
||||
nextn_use_scmoe=nextn_use_scmoe,
|
||||
num_nextn_predict_layers=num_nextn_predict_layers,
|
||||
**kwargs,
|
||||
)
|
||||
self.vocab_size = vocab_size
|
||||
self.max_position_embeddings = max_position_embeddings
|
||||
self.hidden_size = hidden_size
|
||||
self.num_hidden_layers = (
|
||||
num_hidden_layers if num_hidden_layers is not None else num_layers
|
||||
)
|
||||
self.intermediate_size = (
|
||||
intermediate_size if intermediate_size is not None else ffn_hidden_size
|
||||
)
|
||||
self.moe_intermediate_size = expert_ffn_hidden_size
|
||||
self.num_attention_heads = num_attention_heads
|
||||
self.ep_size = ep_size
|
||||
self.kv_lora_rank = kv_lora_rank
|
||||
self.q_lora_rank = q_lora_rank
|
||||
self.qk_rope_head_dim = qk_rope_head_dim
|
||||
self.v_head_dim = v_head_dim
|
||||
self.qk_nope_head_dim = qk_nope_head_dim
|
||||
self.n_routed_experts = n_routed_experts
|
||||
self.moe_topk = moe_topk
|
||||
self.norm_topk_prob = norm_topk_prob
|
||||
self.rms_norm_eps = rms_norm_eps
|
||||
self.pretraining_tp = pretraining_tp
|
||||
self.use_cache = use_cache
|
||||
self.rope_theta = rope_theta
|
||||
self.rope_scaling = rope_scaling
|
||||
self.attention_bias = attention_bias
|
||||
self.attention_dropout = attention_dropout
|
||||
self.mla_scale_q_lora = mla_scale_q_lora
|
||||
self.mla_scale_kv_lora = mla_scale_kv_lora
|
||||
self.zero_expert_num = zero_expert_num
|
||||
self.zero_expert_type = zero_expert_type
|
||||
self.routed_scaling_factor = routed_scaling_factor
|
||||
self.hidden_act = "silu"
|
||||
self.use_ngram_embedding = ngram_vocab_size_ratio is not None
|
||||
if self.use_ngram_embedding:
|
||||
self.ngram_embedding_m = int(ngram_vocab_size_ratio * vocab_size)
|
||||
self.ngram_embedding_n = emb_neighbor_num
|
||||
self.ngram_embedding_k = emb_split_num
|
||||
239
third_party/sglang/python/sglang/srt/configs/mamba_utils.py
vendored
Normal file
239
third_party/sglang/python/sglang/srt/configs/mamba_utils.py
vendored
Normal file
@@ -0,0 +1,239 @@
|
||||
# Copyright 2025 SGLang Team
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
"""Common config utils for mamba2 - NemotronH, FalconH1, Qwen3Next, LFM2, etc."""
|
||||
|
||||
import logging
|
||||
from abc import ABC
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Optional
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from sglang.srt.distributed.utils import divide
|
||||
from sglang.srt.environ import envs
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def extra_groups_for_head_shards(ngroups: int, tp_size: int):
|
||||
"""Compute the increase in group numbers to account for
|
||||
replication in order to accompany the head shards."""
|
||||
|
||||
# in the case ngoups % tp_size == 0, this will be zero
|
||||
if ngroups % tp_size == 0:
|
||||
return 0
|
||||
|
||||
# for n_groups == 1, this is exactly tp_size - n_groups
|
||||
return tp_size - ngroups
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class Mamba2StateDType:
|
||||
conv: torch.dtype
|
||||
temporal: torch.dtype
|
||||
|
||||
|
||||
def mamba2_state_dtype(config=None) -> Mamba2StateDType:
|
||||
"""
|
||||
Get mamba2 state dtype from config or environment variable.
|
||||
|
||||
Priority (from highest to lowest):
|
||||
1. Environment variable SGLANG_MAMBA_SSM_DTYPE
|
||||
2. Config file (config.mamba_ssm_dtype or config.text_config.mamba_ssm_dtype)
|
||||
3. Default "float32"
|
||||
|
||||
Args:
|
||||
config: Optional config object (PretrainedConfig). If provided, will read
|
||||
mamba_ssm_dtype from it. For VL models, reads from text_config.
|
||||
|
||||
Returns:
|
||||
Mamba2StateDType with conv and temporal dtypes
|
||||
"""
|
||||
dtype_map = {
|
||||
"float32": torch.float32,
|
||||
"bfloat16": torch.bfloat16,
|
||||
"float16": torch.float16,
|
||||
}
|
||||
conv_dtype = dtype_map.get(envs.SGLANG_MAMBA_CONV_DTYPE.get(), torch.bfloat16)
|
||||
|
||||
# Get SSM dtype: default -> config -> env var
|
||||
ssm_dtype = torch.float32 # Step 1: Default value
|
||||
|
||||
# Step 2: Try to read from config
|
||||
if config is not None:
|
||||
config_dtype = None
|
||||
if hasattr(config, "text_config") and hasattr(
|
||||
config.text_config, "mamba_ssm_dtype"
|
||||
):
|
||||
# VL model: read from text_config
|
||||
config_dtype = config.text_config.mamba_ssm_dtype
|
||||
elif hasattr(config, "mamba_ssm_dtype"):
|
||||
# Text model: read from root config
|
||||
config_dtype = config.mamba_ssm_dtype
|
||||
|
||||
if config_dtype is not None:
|
||||
if config_dtype not in dtype_map:
|
||||
logger.warning(
|
||||
f"Invalid mamba_ssm_dtype '{config_dtype}' in config. "
|
||||
f"Must be one of {list(dtype_map.keys())}. Using default 'float32'."
|
||||
)
|
||||
else:
|
||||
ssm_dtype = dtype_map[config_dtype]
|
||||
|
||||
# Step 3: Check environment variable, if not None, override
|
||||
env_ssm_dtype = envs.SGLANG_MAMBA_SSM_DTYPE.get()
|
||||
if env_ssm_dtype is not None:
|
||||
if env_ssm_dtype not in dtype_map:
|
||||
logger.warning(
|
||||
f"Invalid mamba_ssm_dtype '{env_ssm_dtype}' from environment variable. "
|
||||
f"Must be one of {list(dtype_map.keys())}. Using default 'float32'."
|
||||
)
|
||||
else:
|
||||
ssm_dtype = dtype_map[env_ssm_dtype]
|
||||
|
||||
logger.debug(f"Mamba2 state dtype: conv_dtype={conv_dtype}, ssm_dtype={ssm_dtype}")
|
||||
|
||||
return Mamba2StateDType(conv=conv_dtype, temporal=ssm_dtype)
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class BaseLinearStateParams(ABC):
|
||||
dtype: Mamba2StateDType = field(default_factory=lambda: mamba2_state_dtype(None))
|
||||
layers: list[int]
|
||||
|
||||
@property
|
||||
def mamba_cache_per_req(self) -> int:
|
||||
conv_numel = int(
|
||||
np.sum([np.prod(conv_shape) for conv_shape in self.shape.conv])
|
||||
)
|
||||
|
||||
ssm_numel = int(np.prod(self.shape.temporal))
|
||||
return (
|
||||
conv_numel * self.dtype.conv.itemsize
|
||||
+ ssm_numel * self.dtype.temporal.itemsize
|
||||
) * len(self.layers)
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class Mamba2StateShape:
|
||||
conv: list[tuple[int, int]]
|
||||
temporal: tuple[int, int, int]
|
||||
|
||||
intermediate_size: int
|
||||
conv_dim: int
|
||||
ssm_state_size: int
|
||||
num_heads: int
|
||||
head_dim: int
|
||||
state_size: int
|
||||
conv_kernel: int
|
||||
|
||||
@staticmethod
|
||||
def create(
|
||||
*,
|
||||
tp_world_size: int,
|
||||
intermediate_size: int,
|
||||
n_groups: int,
|
||||
num_heads: int,
|
||||
head_dim: int,
|
||||
state_size: int,
|
||||
conv_kernel: int,
|
||||
) -> "Mamba2StateShape":
|
||||
# if n_groups is not divisible by world_size, need to extend the shards
|
||||
# to ensure all groups needed by a head is sharded along with it
|
||||
if n_groups % tp_world_size != 0:
|
||||
extra_groups = extra_groups_for_head_shards(n_groups, tp_world_size)
|
||||
n_groups += extra_groups
|
||||
# heads and n_groups are TP-ed
|
||||
conv_dim = intermediate_size + 2 * n_groups * state_size
|
||||
|
||||
# contiguous along 'dim' axis
|
||||
conv_state_shape = divide(conv_dim, tp_world_size), conv_kernel - 1
|
||||
|
||||
# These are not TP-ed as they depend on A, dt_bias, D
|
||||
# - they are typically small
|
||||
# e.g., QWen3-Next: (32, 128, 128)
|
||||
temporal_state_shape = (divide(num_heads, tp_world_size), head_dim, state_size)
|
||||
return Mamba2StateShape(
|
||||
conv=[conv_state_shape],
|
||||
temporal=temporal_state_shape,
|
||||
intermediate_size=intermediate_size,
|
||||
conv_dim=conv_dim,
|
||||
ssm_state_size=state_size,
|
||||
num_heads=num_heads,
|
||||
head_dim=head_dim,
|
||||
state_size=state_size,
|
||||
conv_kernel=conv_kernel,
|
||||
)
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class Mamba2CacheParams(BaseLinearStateParams):
|
||||
shape: Mamba2StateShape
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class KimiLinearStateShape:
|
||||
conv: List[tuple[int, int]]
|
||||
temporal: tuple[int, int, int]
|
||||
|
||||
num_heads: int
|
||||
head_dim: int
|
||||
num_k_heads: int
|
||||
head_k_dim: int
|
||||
conv_kernel: int
|
||||
num_spec: int
|
||||
|
||||
@staticmethod
|
||||
def create(
|
||||
*,
|
||||
tp_world_size: int,
|
||||
num_heads: int,
|
||||
head_dim: int,
|
||||
num_k_heads: Optional[int] = None,
|
||||
head_k_dim: Optional[int] = None,
|
||||
conv_kernel_size: int = 4,
|
||||
num_spec: int = 0,
|
||||
) -> "KimiLinearStateShape":
|
||||
if num_k_heads is None:
|
||||
num_k_heads = num_heads
|
||||
if head_k_dim is None:
|
||||
head_k_dim = head_dim
|
||||
|
||||
proj_size = num_heads * head_dim
|
||||
proj_k_size = num_k_heads * head_k_dim
|
||||
|
||||
conv_state_shape = (divide(proj_size, tp_world_size), conv_kernel_size - 1)
|
||||
conv_state_k_shape = (divide(proj_k_size, tp_world_size), conv_kernel_size - 1)
|
||||
temporal_state_shape = (divide(num_heads, tp_world_size), head_dim, head_dim)
|
||||
|
||||
conv_state_shape = (
|
||||
conv_state_shape[1],
|
||||
conv_state_shape[0] + conv_state_k_shape[0] * 2,
|
||||
)
|
||||
|
||||
return KimiLinearStateShape(
|
||||
conv=[conv_state_shape],
|
||||
temporal=temporal_state_shape,
|
||||
num_heads=num_heads,
|
||||
head_dim=head_dim,
|
||||
num_k_heads=num_k_heads,
|
||||
head_k_dim=head_k_dim,
|
||||
conv_kernel=conv_kernel_size,
|
||||
num_spec=num_spec,
|
||||
)
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class KimiLinearCacheParams(BaseLinearStateParams):
|
||||
shape: KimiLinearStateShape
|
||||
1500
third_party/sglang/python/sglang/srt/configs/model_config.py
vendored
Normal file
1500
third_party/sglang/python/sglang/srt/configs/model_config.py
vendored
Normal file
File diff suppressed because it is too large
Load Diff
30
third_party/sglang/python/sglang/srt/configs/modelopt_config.py
vendored
Normal file
30
third_party/sglang/python/sglang/srt/configs/modelopt_config.py
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
# Configuration for NVIDIA ModelOpt quantization integration
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
class ModelOptConfig:
|
||||
"""Configuration for NVIDIA ModelOpt quantization operations.
|
||||
|
||||
This configuration class holds parameters for ModelOpt quantization,
|
||||
checkpoint management, and model export operations.
|
||||
|
||||
Args:
|
||||
quant: Quantization method/type (e.g., "fp8", "fp4")
|
||||
checkpoint_restore_path: Path to restore ModelOpt checkpoint from
|
||||
checkpoint_save_path: Path to save ModelOpt checkpoint to
|
||||
export_path: Path to export quantized model in HuggingFace format
|
||||
quantize_and_serve: Whether to quantize and serve in one step
|
||||
"""
|
||||
|
||||
quant: Optional[str] = None
|
||||
checkpoint_restore_path: Optional[str] = None
|
||||
checkpoint_save_path: Optional[str] = None
|
||||
export_path: Optional[str] = None
|
||||
quantize_and_serve: bool = False
|
||||
|
||||
def __post_init__(self):
|
||||
"""Validate configuration after initialization."""
|
||||
# Add any validation logic if needed
|
||||
pass
|
||||
114
third_party/sglang/python/sglang/srt/configs/nano_nemotron_vl.py
vendored
Normal file
114
third_party/sglang/python/sglang/srt/configs/nano_nemotron_vl.py
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
# Copyright 2025 SGLang Team
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ==============================================================================
|
||||
# Adapted from https://huggingface.co/nvidia/NVIDIA-Nemotron-Nano-12B-v2-VL-BF16/blob/cb5a65ff10232128389d882d805fa609427544f1/configuration.py
|
||||
|
||||
from typing import Any
|
||||
|
||||
from transformers.configuration_utils import PretrainedConfig
|
||||
|
||||
from sglang.srt.configs.nemotron_h import NemotronHConfig
|
||||
from sglang.srt.configs.radio import RadioConfig
|
||||
from sglang.srt.multimodal.internvl_utils import IMAGENET_MEAN, IMAGENET_STD
|
||||
|
||||
|
||||
def float_triplet(seq: Any):
|
||||
a, b, c = tuple(seq)
|
||||
assert (
|
||||
isinstance(a, float) and isinstance(b, float) and isinstance(c, float)
|
||||
), "expected three floats"
|
||||
return a, b, c
|
||||
|
||||
|
||||
class NemotronH_Nano_VL_V2_Config(PretrainedConfig):
|
||||
model_type = "NemotronH_Nano_VL_V2"
|
||||
is_composition = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vision_config=None,
|
||||
llm_config=None,
|
||||
force_image_size: int = 512,
|
||||
patch_size: int = 16,
|
||||
downsample_ratio=0.5,
|
||||
template=None,
|
||||
ps_version="v2",
|
||||
image_tag_type="internvl",
|
||||
projector_hidden_size=4096,
|
||||
vit_hidden_size=1280,
|
||||
video_pruning_rate: float = 0.0,
|
||||
video_context_token: str = "<video>",
|
||||
img_context_token: str = "<image>",
|
||||
img_start_token: str = "<img>",
|
||||
img_end_token: str = "</img>",
|
||||
norm_mean: tuple[float, float, float] | list[float] = IMAGENET_MEAN,
|
||||
norm_std: tuple[float, float, float] | list[float] = IMAGENET_STD,
|
||||
use_thumbnail: bool = True,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
# Handle both cases: when loading from JSON (llm_config is dict) and when called internally by transformers (llm_config; vision_config are None)
|
||||
if llm_config is not None:
|
||||
self.llm_config = NemotronHConfig(**llm_config)
|
||||
assert isinstance(vision_config, dict), "vision_config must be a dictionary"
|
||||
self.raw_vision_config = vision_config
|
||||
else:
|
||||
assert vision_config is None
|
||||
self.llm_config = NemotronHConfig()
|
||||
self.raw_vision_config = {}
|
||||
|
||||
# Assign configuration values
|
||||
vision_image_size = self.raw_vision_config.get("image_size", force_image_size)
|
||||
vision_patch_size = self.raw_vision_config.get("patch_size", patch_size)
|
||||
self.image_size = int(
|
||||
vision_image_size[0]
|
||||
if isinstance(vision_image_size, list)
|
||||
else vision_image_size
|
||||
)
|
||||
self.patch_size = int(
|
||||
vision_patch_size[0]
|
||||
if isinstance(vision_patch_size, list)
|
||||
else vision_patch_size
|
||||
)
|
||||
|
||||
self.downsample_ratio = downsample_ratio
|
||||
self.video_context_token = video_context_token
|
||||
self.img_context_token = img_context_token
|
||||
self.template = template # TODO move out of here and into the tokenizer
|
||||
self.ps_version = ps_version # Pixel shuffle version
|
||||
self.image_tag_type = image_tag_type # TODO: into the tokenizer too?
|
||||
self.projector_hidden_size = projector_hidden_size
|
||||
self.vit_hidden_size = vit_hidden_size
|
||||
self.video_pruning_rate = video_pruning_rate
|
||||
|
||||
self.norm_mean = float_triplet(norm_mean)
|
||||
self.norm_std = float_triplet(norm_std)
|
||||
self.use_thumbnail = use_thumbnail
|
||||
self.img_start_token = img_start_token
|
||||
self.img_end_token = img_end_token
|
||||
|
||||
def create_radio_config(self):
|
||||
config = self.raw_vision_config
|
||||
model_name = config["args"]["model"]
|
||||
reg_tokens = config["args"].get("register_multiple")
|
||||
image_size = config.get("preferred_resolution", [224])[0]
|
||||
radio_config = RadioConfig(
|
||||
patch_size=self.patch_size,
|
||||
norm_mean=self.norm_mean,
|
||||
norm_std=self.norm_std,
|
||||
model_name=model_name,
|
||||
reg_tokens=reg_tokens,
|
||||
image_size=image_size,
|
||||
)
|
||||
return radio_config
|
||||
504
third_party/sglang/python/sglang/srt/configs/nemotron_h.py
vendored
Normal file
504
third_party/sglang/python/sglang/srt/configs/nemotron_h.py
vendored
Normal file
@@ -0,0 +1,504 @@
|
||||
# Copyright 2025 SGLang Team
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ==============================================================================
|
||||
# Adapted from https://github.com/vllm-project/vllm/blob/main/vllm/transformers_utils/configs/nemotron_h.py
|
||||
|
||||
"""NemotronH model configuration"""
|
||||
|
||||
from transformers.configuration_utils import PretrainedConfig
|
||||
from transformers.utils import logging
|
||||
|
||||
from sglang.srt.configs.mamba_utils import (
|
||||
Mamba2CacheParams,
|
||||
Mamba2StateShape,
|
||||
mamba2_state_dtype,
|
||||
)
|
||||
|
||||
logger = logging.get_logger(__name__)
|
||||
|
||||
MAMBA = "M"
|
||||
ATTENTION = "*"
|
||||
MLP = "-"
|
||||
MOE = "E"
|
||||
DEFAULT_LAYERS_BLOCK_TYPE = ["mamba", "moe", "attention", "moe"]
|
||||
DEFAULT_MTP_LAYERS_BLOCK_TYPE = ["attention", "moe"]
|
||||
DEFAULT_MAMBA_CHUNK_SIZE = 256
|
||||
|
||||
|
||||
class NemotronHConfig(PretrainedConfig):
|
||||
r"""
|
||||
This is the configuration class to store the configuration of a
|
||||
[`NemotronHModel`]. It is used to instantiate a NemotronH model according
|
||||
to the specified arguments, defining the model architecture. Instantiating
|
||||
a configuration with the defaults will yield a similar configuration to
|
||||
that of the NemotronH-v0.1 model.
|
||||
Args:
|
||||
vocab_size (`int`, *optional*, defaults to 131072):
|
||||
Vocabulary size of the NemotronH model. Defines the number of
|
||||
different tokens that can be represented by the `inputs_ids`
|
||||
passed when calling [`NemotronHModel`]
|
||||
tie_word_embeddings (`bool`, *optional*, defaults to `False`):
|
||||
Whether the model's input and output word embeddings should be
|
||||
tied. Note that this is only relevant if the model has an output
|
||||
word embedding layer.
|
||||
hidden_size (`int`, *optional*, defaults to 4096):
|
||||
Dimension of the hidden representations.
|
||||
intermediate_size (`int`, *optional*, defaults to 21504):
|
||||
Dimension of the MLP representations.
|
||||
num_hidden_layers (`int`, *optional*):
|
||||
Deprecated. Kept only for backward compatibility. The effective
|
||||
layer count is derived from `layers_block_type`.
|
||||
hybrid_override_pattern (`str`, *optional*, defaults to
|
||||
`"M-M-M-M*-M-M-M-M-M*-M-M-M-M-M*-M-M-M-M-M*-M-M-M-M-M-"`):
|
||||
Deprecated compatibility field. Pattern string where each
|
||||
character represents Mamba2 (`M`), Attention (`*`), MLP (`-`),
|
||||
or MoE (`E`).
|
||||
layers_block_type (`list[str]`, *optional*):
|
||||
Canonical layer layout. Each entry is one of:
|
||||
`"mamba"`, `"attention"`, `"mlp"`, `"moe"`.
|
||||
num_attention_heads (`int`, *optional*, defaults to 32):
|
||||
Number of attention heads for each attention layer in the
|
||||
Transformer encoder.
|
||||
attention_head_dim (`int`, *optional*, defaults to 128):
|
||||
Dimension of each attention head.
|
||||
num_key_value_heads (`int`, *optional*, defaults to 8):
|
||||
This is the number of key_value heads that should be used to
|
||||
implement Grouped Query Attention. If
|
||||
`num_key_value_heads=num_attention_heads`, the model will use
|
||||
Multi Head Attention (MHA), if `num_key_value_heads=1` the model
|
||||
will use Multi Query Attention (MQA) otherwise GQA is used.
|
||||
mlp_hidden_act (`str`, *optional*, defaults to "relu2"):
|
||||
The non-linear activation function in the MLP layers.
|
||||
attention_bias (`bool`, *optional*, defaults to `False`):
|
||||
Whether to use bias in attention layers.
|
||||
mlp_bias (`bool`, *optional*, defaults to `False`):
|
||||
Whether to use bias in MLP layers.
|
||||
use_bias (`bool`, *optional*, defaults to `False`):
|
||||
Whether to use bias in the model.
|
||||
initializer_range (`float`, *optional*, defaults to 0.02):
|
||||
The standard deviation of the truncated_normal_initializer for
|
||||
initializing all weight matrices.
|
||||
layer_norm_epsilon (`float`, *optional*, defaults to 1e-5):
|
||||
The epsilon used by the layer normalization layers.
|
||||
residual_in_fp32 (`bool`, *optional*, defaults to `False`):
|
||||
Whether or not residuals should be in `float32`. If set to `False`
|
||||
residuals will keep the same `dtype` as the rest of the model.
|
||||
use_cache (`bool`, *optional*, defaults to `True`):
|
||||
Whether or not the model should return the last key/values
|
||||
attentions (not used by all models). Only relevant if
|
||||
`config.is_decoder=True`.
|
||||
num_logits_to_keep (`int` or `None`, *optional*, defaults to 1):
|
||||
Number of prompt logits to calculate during generation. If `None`,
|
||||
all logits will be calculated. If an integer value, only last
|
||||
`num_logits_to_keep` logits will be calculated.
|
||||
pad_token_id (`int`, *optional*, defaults to 0):
|
||||
The id of the padding token.
|
||||
bos_token_id (`int`, *optional*, defaults to 1):
|
||||
The id of the "beginning-of-sequence" token.
|
||||
eos_token_id (`int`, *optional*, defaults to 2):
|
||||
The id of the "end-of-sequence" token.
|
||||
sliding_window (`int`, *optional*, defaults to None):
|
||||
Sliding window attention window size.
|
||||
max_position_embeddings (`int`, *optional*, defaults to 4096):
|
||||
The maximum sequence length that this model might ever be used
|
||||
with.
|
||||
attention_dropout (`float`, *optional*, defaults to 0.0):
|
||||
The dropout ratio for the attention probabilities.
|
||||
hidden_dropout (`float`, *optional*, defaults to 0.0):
|
||||
The dropout ratio for the hidden states.
|
||||
use_mamba_kernels (`bool`, *optional*, defaults to `True`):
|
||||
Flag indicating whether or not to use the fast mamba kernels.
|
||||
These are available only if `mamba-ssm` and `causal-conv1d`
|
||||
are installed, and the mamba modules are running on a CUDA device.
|
||||
ssm_state_size (`int`, *optional*, defaults to 128):
|
||||
The dimension of the mamba state space latents.
|
||||
mamba_num_heads (`int`, *optional*, defaults to 128):
|
||||
Number of heads in Mamba layers.
|
||||
mamba_n_groups (`int`, *optional*, defaults to 8):
|
||||
Number of groups in Mamba layers.
|
||||
mamba_head_dim (`int`, *optional*, defaults to 64):
|
||||
Dimension of each Mamba head.
|
||||
mamba_d_conv (`int`, *optional*, defaults to 4):
|
||||
The size of the mamba convolution kernel.
|
||||
mamba_expand (`int`, *optional*, defaults to 2):
|
||||
Expanding factor used to determine the mamba intermediate size.
|
||||
mamba_hidden_act (`str`, *optional*, defaults to "silu"):
|
||||
The non-linear activation function in the Mamba layers.
|
||||
mamba_dt_min (`float`, *optional*, defaults to 0.001):
|
||||
Minimum value for the time step in Mamba.
|
||||
mamba_dt_max (`float`, *optional*, defaults to 0.1):
|
||||
Maximum value for the time step in Mamba.
|
||||
mamba_dt_limit (`tuple`, *optional*, defaults to (0.0, float("inf"))):
|
||||
Limits for the time step in Mamba.
|
||||
mamba_dt_init_floor (`float`, *optional*, defaults to 1e-4):
|
||||
Floor value for time step initialization in Mamba.
|
||||
mamba_conv_bias (`bool`, *optional*, defaults to `True`):
|
||||
Whether to use bias in the convolution layer of the mamba mixer
|
||||
block.
|
||||
mamba_proj_bias (`bool`, *optional*, defaults to `False`):
|
||||
Whether to use bias in the input and output projections of the
|
||||
mamba mixer block.
|
||||
mamba_chunk_size (`int`, *optional*, defaults to 256):
|
||||
Size of chunks for Mamba processing.
|
||||
rescale_prenorm_residual (`bool`, *optional*, defaults to `True`):
|
||||
Whether to rescale the pre-normalization residual connections.
|
||||
"""
|
||||
|
||||
model_type = "nemotron_h"
|
||||
keys_to_ignore_at_inference = ["past_key_values"]
|
||||
|
||||
@staticmethod
|
||||
def _validate_layers_block_type(
|
||||
layers_block_type, expected_length=None, param_name="layers_block_type"
|
||||
):
|
||||
"""
|
||||
Validate layers_block_type list.
|
||||
Args:
|
||||
layers_block_type: List of layer types to validate.
|
||||
expected_length: If provided, validate the list has this length.
|
||||
param_name: Parameter name for error messages.
|
||||
Raises:
|
||||
ValueError: If validation fails.
|
||||
"""
|
||||
if not isinstance(layers_block_type, list):
|
||||
raise ValueError(
|
||||
f"{param_name} must be a list of strings. Got type: {type(layers_block_type)}"
|
||||
)
|
||||
if expected_length is not None and len(layers_block_type) != expected_length:
|
||||
raise ValueError(
|
||||
f"{param_name} must have length {expected_length}. Got length {len(layers_block_type)}."
|
||||
)
|
||||
valid_types = {"mamba", "attention", "mlp", "moe"}
|
||||
if not all(block_type in valid_types for block_type in layers_block_type):
|
||||
invalid = set(layers_block_type) - valid_types
|
||||
raise ValueError(
|
||||
f"{param_name} contains invalid types: {invalid}. Must be one of: {valid_types}"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _resolve_layers_block_type(
|
||||
layers_block_type, hybrid_override_pattern, kwargs
|
||||
) -> list[str]:
|
||||
"""Resolve canonical layers_block_type from new and legacy config fields."""
|
||||
# Prefer explicit kwargs override first (legacy HF path), otherwise use
|
||||
# the function argument value from config fields.
|
||||
pattern = kwargs.pop("hybrid_override_pattern", hybrid_override_pattern)
|
||||
if layers_block_type is None:
|
||||
if pattern is not None:
|
||||
layers_block_type = NemotronHConfig._pattern_to_list(pattern)
|
||||
else:
|
||||
# Last-resort fallback to preserve compatibility when neither
|
||||
# canonical nor legacy pattern fields are provided.
|
||||
layers_block_type = DEFAULT_LAYERS_BLOCK_TYPE
|
||||
return layers_block_type
|
||||
|
||||
@staticmethod
|
||||
def _resolve_mtp_layers_block_type(mtp_layers_block_type, kwargs) -> list[str]:
|
||||
"""Resolve canonical mtp_layers_block_type from new and legacy config fields."""
|
||||
if "mtp_hybrid_override_pattern" in kwargs:
|
||||
pattern = kwargs.pop("mtp_hybrid_override_pattern")
|
||||
if mtp_layers_block_type is None or mtp_layers_block_type == [
|
||||
"attention",
|
||||
"moe",
|
||||
]:
|
||||
mtp_layers_block_type = NemotronHConfig._pattern_to_list(pattern)
|
||||
return mtp_layers_block_type
|
||||
|
||||
@staticmethod
|
||||
def _resolve_mamba_chunk_size(mamba_chunk_size, kwargs) -> int:
|
||||
"""Resolve canonical mamba_chunk_size from new and legacy config fields."""
|
||||
chunk_size = kwargs.pop("chunk_size", None)
|
||||
if (
|
||||
mamba_chunk_size is not None
|
||||
and chunk_size is not None
|
||||
and mamba_chunk_size != chunk_size
|
||||
):
|
||||
logger.warning(
|
||||
"Both chunk_size=%s and mamba_chunk_size=%s were provided. "
|
||||
"Using mamba_chunk_size.",
|
||||
chunk_size,
|
||||
mamba_chunk_size,
|
||||
)
|
||||
|
||||
if mamba_chunk_size is None:
|
||||
mamba_chunk_size = chunk_size
|
||||
if mamba_chunk_size is None:
|
||||
mamba_chunk_size = DEFAULT_MAMBA_CHUNK_SIZE
|
||||
return mamba_chunk_size
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vocab_size=131072,
|
||||
tie_word_embeddings=False,
|
||||
hidden_size=4096,
|
||||
intermediate_size=21504,
|
||||
num_hidden_layers=None, # Deprecated, only for backward compatibility
|
||||
hybrid_override_pattern="M-M-M-M*-M-M-M-M-M*-M-M-M-M-M*-M-M-M-M-M*-M-M-M-M-M-",
|
||||
layers_block_type=None,
|
||||
num_attention_heads=32,
|
||||
head_dim=128,
|
||||
num_key_value_heads=8, # nemo: num_query_groups
|
||||
mlp_hidden_act="relu2",
|
||||
attention_bias=False,
|
||||
mlp_bias=False,
|
||||
use_bias=False,
|
||||
initializer_range=0.02, # nemo: init_method_std
|
||||
layer_norm_epsilon=1e-5, # nemo: layernorm_epsilon
|
||||
residual_in_fp32=False, # Megatron Core default value
|
||||
use_cache=True,
|
||||
num_logits_to_keep=1,
|
||||
pad_token_id=0,
|
||||
bos_token_id=1,
|
||||
eos_token_id=2,
|
||||
sliding_window=None,
|
||||
max_position_embeddings=4096,
|
||||
attention_dropout=0.0,
|
||||
hidden_dropout=0.0, # * ADDED
|
||||
use_mamba_kernels=True,
|
||||
ssm_state_size=128, # mamba_state_size
|
||||
mamba_num_heads=128,
|
||||
mamba_n_groups=8, # nemo: mamba_ssm_ngroups = num_heads
|
||||
mamba_head_dim=64,
|
||||
mamba_d_conv=4,
|
||||
mamba_expand=2,
|
||||
mamba_hidden_act="silu",
|
||||
mamba_dt_min=0.001,
|
||||
mamba_dt_max=0.1,
|
||||
mamba_dt_limit=(0.0, float("inf")),
|
||||
mamba_dt_init_floor=1e-4,
|
||||
mamba_conv_bias=True,
|
||||
mamba_proj_bias=False,
|
||||
mamba_chunk_size=None,
|
||||
rescale_prenorm_residual=True,
|
||||
n_routed_experts=8,
|
||||
n_shared_experts=1,
|
||||
moe_intermediate_size=7688,
|
||||
moe_shared_expert_intermediate_size=7688,
|
||||
moe_latent_size=None,
|
||||
num_experts_per_tok=2,
|
||||
routed_scaling_factor=1.0,
|
||||
n_group=1,
|
||||
topk_group=1,
|
||||
norm_topk_prob=True,
|
||||
num_nextn_predict_layers=0,
|
||||
mtp_layers_block_type=DEFAULT_MTP_LAYERS_BLOCK_TYPE,
|
||||
**kwargs,
|
||||
):
|
||||
mamba_chunk_size = self._resolve_mamba_chunk_size(mamba_chunk_size, kwargs)
|
||||
|
||||
# Compatibility parsing: normalize legacy pattern fields into canonical list fields.
|
||||
layers_block_type = self._resolve_layers_block_type(
|
||||
layers_block_type, hybrid_override_pattern, kwargs
|
||||
)
|
||||
mtp_layers_block_type = self._resolve_mtp_layers_block_type(
|
||||
mtp_layers_block_type, kwargs
|
||||
)
|
||||
|
||||
# num_hidden_layers is deprecated and ignored as a source of truth.
|
||||
if (
|
||||
num_hidden_layers is not None
|
||||
and len(layers_block_type) != num_hidden_layers
|
||||
):
|
||||
logger.warning(
|
||||
f"num_hidden_layers ({num_hidden_layers}) is deprecated and doesn't match "
|
||||
f"layers_block_type length ({len(layers_block_type)}). Using layers_block_type length."
|
||||
)
|
||||
|
||||
# Core model attributes.
|
||||
self.vocab_size = vocab_size
|
||||
self.tie_word_embeddings = tie_word_embeddings
|
||||
self.hidden_size = hidden_size
|
||||
self.intermediate_size = intermediate_size
|
||||
self.num_attention_heads = num_attention_heads
|
||||
self.head_dim = head_dim
|
||||
self.sliding_window = sliding_window
|
||||
self.max_position_embeddings = max_position_embeddings
|
||||
self.attention_dropout = attention_dropout
|
||||
self.hidden_dropout = hidden_dropout
|
||||
|
||||
self._validate_layers_block_type(
|
||||
layers_block_type, expected_length=None, param_name="layers_block_type"
|
||||
)
|
||||
self.layers_block_type = layers_block_type
|
||||
|
||||
# for backward compatibility
|
||||
if num_key_value_heads is None:
|
||||
num_key_value_heads = num_attention_heads
|
||||
|
||||
self.num_key_value_heads = num_key_value_heads
|
||||
self.mlp_hidden_act = mlp_hidden_act
|
||||
self.attention_bias = attention_bias
|
||||
self.mlp_bias = mlp_bias
|
||||
self.use_bias = use_bias
|
||||
self.initializer_range = initializer_range
|
||||
self.layer_norm_epsilon = layer_norm_epsilon
|
||||
self.residual_in_fp32 = residual_in_fp32
|
||||
|
||||
self.use_cache = use_cache
|
||||
self.num_logits_to_keep = num_logits_to_keep
|
||||
|
||||
# Mamba attributes.
|
||||
self.use_mamba_kernels = use_mamba_kernels
|
||||
self.mamba_n_groups = mamba_n_groups
|
||||
self.mamba_head_dim = mamba_head_dim
|
||||
self.ssm_state_size = ssm_state_size
|
||||
self.mamba_num_heads = mamba_num_heads
|
||||
self.conv_kernel = mamba_d_conv
|
||||
self.expand = mamba_expand
|
||||
self.mamba_hidden_act = mamba_hidden_act
|
||||
self.time_step_min = mamba_dt_min
|
||||
self.time_step_max = mamba_dt_max
|
||||
self.time_step_limit = mamba_dt_limit
|
||||
self.time_step_floor = mamba_dt_init_floor
|
||||
self.use_conv_bias = mamba_conv_bias
|
||||
self.mamba_proj_bias = mamba_proj_bias
|
||||
self.mamba_chunk_size = mamba_chunk_size
|
||||
self.rescale_prenorm_residual = rescale_prenorm_residual
|
||||
# MoE attributes.
|
||||
self.n_routed_experts = n_routed_experts
|
||||
self.n_shared_experts = n_shared_experts
|
||||
self.moe_intermediate_size = moe_intermediate_size
|
||||
self.moe_shared_expert_intermediate_size = moe_shared_expert_intermediate_size
|
||||
self.moe_latent_size = moe_latent_size
|
||||
self.num_experts_per_tok = num_experts_per_tok
|
||||
self.routed_scaling_factor = routed_scaling_factor
|
||||
self.n_group = n_group
|
||||
self.topk_group = topk_group
|
||||
self.norm_topk_prob = norm_topk_prob
|
||||
# MTP attributes.
|
||||
self.num_nextn_predict_layers = num_nextn_predict_layers
|
||||
|
||||
if self.num_nextn_predict_layers > 0:
|
||||
if mtp_layers_block_type is None:
|
||||
raise ValueError(
|
||||
"mtp_layers_block_type is required when num_nextn_predict_layers > 0. "
|
||||
"Please provide an explicit list of layer types for MTP layers. "
|
||||
"Example: mtp_layers_block_type=['attention', 'moe']"
|
||||
)
|
||||
self._validate_layers_block_type(
|
||||
mtp_layers_block_type, None, "mtp_layers_block_type"
|
||||
)
|
||||
self.mtp_layers_block_type = mtp_layers_block_type
|
||||
|
||||
super().__init__(
|
||||
pad_token_id=pad_token_id,
|
||||
bos_token_id=bos_token_id,
|
||||
eos_token_id=eos_token_id,
|
||||
tie_word_embeddings=tie_word_embeddings,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@property
|
||||
def mamba_layer_ids(self):
|
||||
return [
|
||||
i
|
||||
for i in range(self.num_hidden_layers)
|
||||
if self.hybrid_override_pattern[i] == MAMBA
|
||||
]
|
||||
|
||||
@property
|
||||
def full_attention_layer_ids(self):
|
||||
return [
|
||||
i
|
||||
for i in range(self.num_hidden_layers)
|
||||
if self.hybrid_override_pattern[i] == ATTENTION
|
||||
]
|
||||
|
||||
@property
|
||||
def mamba2_cache_params(self) -> Mamba2CacheParams:
|
||||
from sglang.srt.layers.dp_attention import get_attention_tp_size
|
||||
|
||||
shape = Mamba2StateShape.create(
|
||||
tp_world_size=get_attention_tp_size(),
|
||||
intermediate_size=self.mamba_num_heads * self.mamba_head_dim,
|
||||
n_groups=self.n_groups,
|
||||
num_heads=self.mamba_num_heads,
|
||||
head_dim=self.mamba_head_dim,
|
||||
state_size=self.ssm_state_size,
|
||||
conv_kernel=self.conv_kernel,
|
||||
)
|
||||
|
||||
return Mamba2CacheParams(
|
||||
shape=shape, layers=self.mamba_layer_ids, dtype=mamba2_state_dtype(self)
|
||||
)
|
||||
|
||||
@property
|
||||
def num_hidden_layers(self) -> int:
|
||||
"""
|
||||
Number of hidden layers derived from the length of layers_block_type.
|
||||
This property replaces the deprecated num_hidden_layers parameter.
|
||||
"""
|
||||
return len(self.layers_block_type)
|
||||
|
||||
@num_hidden_layers.setter
|
||||
def num_hidden_layers(self, value):
|
||||
"""
|
||||
Setter for backward compatibility when loading configs.
|
||||
The value is ignored since num_hidden_layers is computed from layers_block_type.
|
||||
"""
|
||||
pass
|
||||
|
||||
@property
|
||||
def hybrid_override_pattern(self) -> str:
|
||||
"""
|
||||
Backward compatibility property.
|
||||
Returns the pattern string representation of layers_block_type.
|
||||
"""
|
||||
return self._list_to_pattern(self.layers_block_type)
|
||||
|
||||
@hybrid_override_pattern.setter
|
||||
def hybrid_override_pattern(self, value):
|
||||
"""
|
||||
Setter for backward compatibility when loading configs.
|
||||
"""
|
||||
self.layers_block_type = self._pattern_to_list(value)
|
||||
|
||||
@property
|
||||
def mtp_hybrid_override_pattern(self) -> str:
|
||||
"""
|
||||
Backward compatibility property.
|
||||
Returns the pattern string representation of mtp_layers_block_type.
|
||||
"""
|
||||
return self._list_to_pattern(self.mtp_layers_block_type)
|
||||
|
||||
@mtp_hybrid_override_pattern.setter
|
||||
def mtp_hybrid_override_pattern(self, value):
|
||||
"""Setter for backward compatibility when loading configs."""
|
||||
self.mtp_layers_block_type = self._pattern_to_list(value)
|
||||
|
||||
@staticmethod
|
||||
def _list_to_pattern(layers_list: list[str]) -> str:
|
||||
"""Convert list of layer types back to pattern string (for backward compatibility)."""
|
||||
reverse_mapping = {
|
||||
"mamba": MAMBA,
|
||||
"moe": MOE,
|
||||
"attention": ATTENTION,
|
||||
"mlp": MLP,
|
||||
}
|
||||
return "".join(reverse_mapping[layer_type] for layer_type in layers_list)
|
||||
|
||||
@staticmethod
|
||||
def _pattern_to_list(pattern: str) -> list[str]:
|
||||
"""Convert pattern string to list of layer types (for backward compatibility)."""
|
||||
if any(char not in {MAMBA, MOE, ATTENTION, MLP} for char in pattern):
|
||||
raise ValueError(
|
||||
"Pattern must only contain characters 'M', '*', '-' or 'E'. "
|
||||
f"Got: {pattern}"
|
||||
)
|
||||
pattern_mapping = {
|
||||
MAMBA: "mamba",
|
||||
MOE: "moe",
|
||||
ATTENTION: "attention",
|
||||
MLP: "mlp",
|
||||
}
|
||||
return [pattern_mapping[char] for char in pattern]
|
||||
103
third_party/sglang/python/sglang/srt/configs/olmo3.py
vendored
Normal file
103
third_party/sglang/python/sglang/srt/configs/olmo3.py
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
# coding=utf-8
|
||||
# Copyright 2024 The Qwen team, Alibaba Group and the HuggingFace Inc. team. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
"""Olmo3 model configuration"""
|
||||
|
||||
import enum
|
||||
|
||||
from transformers.configuration_utils import PretrainedConfig
|
||||
from transformers.utils import logging
|
||||
|
||||
logger = logging.get_logger(__name__)
|
||||
|
||||
|
||||
class Olmo3LayerType(enum.Enum):
|
||||
full_attention = "full_attention"
|
||||
sliding_attention = "sliding_attention"
|
||||
|
||||
|
||||
class Olmo3Config(PretrainedConfig):
|
||||
|
||||
model_type = "olmo3"
|
||||
keys_to_ignore_at_inference = ["past_key_values"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vocab_size=50304,
|
||||
hidden_size=4096,
|
||||
intermediate_size=11008,
|
||||
num_hidden_layers=32,
|
||||
num_attention_heads=32,
|
||||
num_key_value_heads=None,
|
||||
hidden_act="silu",
|
||||
max_position_embeddings=2048,
|
||||
initializer_range=0.02,
|
||||
use_cache=True,
|
||||
pad_token_id=1,
|
||||
bos_token_id=None,
|
||||
eos_token_id=50279,
|
||||
tie_word_embeddings=False,
|
||||
rope_theta=10000.0,
|
||||
rope_scaling=None,
|
||||
attention_bias=False,
|
||||
attention_dropout=0.0,
|
||||
rms_norm_eps=1e-5,
|
||||
sliding_window=4096,
|
||||
layer_types=None,
|
||||
**kwargs,
|
||||
):
|
||||
# This model uses Olmo3ForCausalLM in transformers but Olmo2ForCausalLM
|
||||
# in sglang.
|
||||
if "architectures" not in kwargs:
|
||||
kwargs["architectures"] = ["Olmo2ForCausalLM"]
|
||||
elif "Olmo3ForCausalLM" in kwargs["architectures"]:
|
||||
kwargs["architectures"].remove("Olmo3ForCausalLM")
|
||||
kwargs["architectures"].append("Olmo2ForCausalLM")
|
||||
|
||||
super().__init__(
|
||||
pad_token_id=pad_token_id,
|
||||
bos_token_id=bos_token_id,
|
||||
eos_token_id=eos_token_id,
|
||||
tie_word_embeddings=tie_word_embeddings,
|
||||
**kwargs,
|
||||
)
|
||||
self.vocab_size = vocab_size
|
||||
self.max_position_embeddings = max_position_embeddings
|
||||
self.hidden_size = hidden_size
|
||||
self.intermediate_size = intermediate_size
|
||||
self.num_hidden_layers = num_hidden_layers
|
||||
self.num_attention_heads = num_attention_heads
|
||||
|
||||
# for backward compatibility
|
||||
if num_key_value_heads is None:
|
||||
num_key_value_heads = num_attention_heads
|
||||
|
||||
self.num_key_value_heads = num_key_value_heads
|
||||
self.hidden_act = hidden_act
|
||||
self.initializer_range = initializer_range
|
||||
self.use_cache = use_cache
|
||||
self.rope_theta = rope_theta
|
||||
self.rope_scaling = rope_scaling
|
||||
self.attention_bias = attention_bias
|
||||
self.attention_dropout = attention_dropout
|
||||
|
||||
self.rms_norm_eps = rms_norm_eps
|
||||
|
||||
self.sliding_window = sliding_window
|
||||
self.layer_types = layer_types
|
||||
if self.layer_types is None:
|
||||
self.layer_types = [
|
||||
"sliding_attention" if (i + 1) % 4 != 0 else "full_attention"
|
||||
for i in range(self.num_hidden_layers)
|
||||
]
|
||||
29
third_party/sglang/python/sglang/srt/configs/points_v15_chat.py
vendored
Normal file
29
third_party/sglang/python/sglang/srt/configs/points_v15_chat.py
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
from typing import Optional, Union
|
||||
|
||||
from transformers import PretrainedConfig, Qwen2Config
|
||||
from transformers.models.qwen2_vl.configuration_qwen2_vl import Qwen2VLVisionConfig
|
||||
|
||||
|
||||
class POINTSV15ChatConfig(PretrainedConfig):
|
||||
model_type = "pointsv1.5_chat"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vision_config: Optional[Union[dict, Qwen2VLVisionConfig]] = None,
|
||||
llm_config: Optional[Union[dict, Qwen2Config]] = None,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
if vision_config is None:
|
||||
vision_config = Qwen2VLVisionConfig()
|
||||
elif isinstance(vision_config, dict):
|
||||
vision_config = Qwen2VLVisionConfig(**vision_config)
|
||||
self.vision_config = vision_config
|
||||
|
||||
if llm_config is None:
|
||||
llm_config = Qwen2Config()
|
||||
elif isinstance(llm_config, dict):
|
||||
llm_config = Qwen2Config(**llm_config)
|
||||
|
||||
self.llm_config = llm_config
|
||||
self.hidden_size = self.llm_config.hidden_size
|
||||
122
third_party/sglang/python/sglang/srt/configs/qwen3_5.py
vendored
Normal file
122
third_party/sglang/python/sglang/srt/configs/qwen3_5.py
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
from transformers import PretrainedConfig
|
||||
|
||||
from sglang.srt.configs.qwen3_next import Qwen3NextConfig
|
||||
from sglang.srt.configs.qwen3_vl import Qwen3VLVisionConfig
|
||||
|
||||
|
||||
class Qwen3_5VisionConfig(Qwen3VLVisionConfig):
|
||||
model_type = "qwen3_5"
|
||||
base_config_key = "vision_config"
|
||||
|
||||
|
||||
class Qwen3_5TextConfig(Qwen3NextConfig):
|
||||
model_type = "qwen3_5_text"
|
||||
base_config_key = "text_config"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
**kwargs,
|
||||
):
|
||||
# HF Qwen3.5 checkpoints may provide RoPE settings under rope_parameters.
|
||||
# Normalize it before parent init so downstream code sees the expected values.
|
||||
rope_parameters = kwargs.pop("rope_parameters", None)
|
||||
if kwargs.get("rope_scaling") is None and rope_parameters is not None:
|
||||
kwargs["rope_scaling"] = rope_parameters
|
||||
|
||||
super().__init__(**kwargs)
|
||||
if self.rope_scaling is None:
|
||||
self.rope_scaling = rope_parameters or {}
|
||||
|
||||
# Keep both names for compatibility with model code paths that read either.
|
||||
self.rope_parameters = rope_parameters or self.rope_scaling
|
||||
|
||||
|
||||
class Qwen3_5Config(PretrainedConfig):
|
||||
r"""
|
||||
This is the configuration class to store the configuration of a [`Qwen3_5Model`]. It is used to instantiate a
|
||||
Qwen3.5 model according to the specified arguments, defining the model architecture. Instantiating a configuration
|
||||
with the defaults will yield a similar configuration to that of
|
||||
Qwen3.5.
|
||||
|
||||
Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the
|
||||
documentation from [`PretrainedConfig`] for more information.
|
||||
|
||||
|
||||
Args:
|
||||
text_config (`Union[PreTrainedConfig, dict]`, *optional*, defaults to `Qwen3_5TextConfig`):
|
||||
The config object or dictionary of the text backbone.
|
||||
vision_config (`Union[PreTrainedConfig, dict]`, *optional*, defaults to `Qwen3_5VisionConfig`):
|
||||
The config object or dictionary of the vision backbone.
|
||||
image_token_id (`int`, *optional*, defaults to 151655):
|
||||
The image token index to encode the image prompt.
|
||||
video_token_id (`int`, *optional*, defaults to 151656):
|
||||
The video token index to encode the image prompt.
|
||||
vision_start_token_id (`int`, *optional*, defaults to 151652):
|
||||
The start token index to encode the image prompt.
|
||||
vision_end_token_id (`int`, *optional*, defaults to 151653):
|
||||
The end token index to encode the image prompt.
|
||||
tie_word_embeddings (`bool`, *optional*, defaults to `False`):
|
||||
Whether to tie the word embeddings.
|
||||
|
||||
```python
|
||||
>>> from transformers import Qwen3_5ForConditionalGeneration, Qwen3_5Config
|
||||
|
||||
>>> # Initializing a Qwen3.5 style configuration
|
||||
>>> configuration = Qwen3_5Config()
|
||||
|
||||
>>> # Initializing a model from the Qwen3.5 style configuration
|
||||
>>> model = Qwen3_5ForConditionalGeneration(configuration)
|
||||
|
||||
>>> # Accessing the model configuration
|
||||
>>> configuration = model.config
|
||||
```"""
|
||||
|
||||
model_type = "qwen3_5"
|
||||
sub_configs = {
|
||||
"vision_config": Qwen3_5VisionConfig,
|
||||
"text_config": Qwen3_5TextConfig,
|
||||
}
|
||||
keys_to_ignore_at_inference = ["past_key_values"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
text_config=None,
|
||||
vision_config=None,
|
||||
image_token_id=151655,
|
||||
video_token_id=151656,
|
||||
vision_start_token_id=151652,
|
||||
vision_end_token_id=151653,
|
||||
tie_word_embeddings=False,
|
||||
**kwargs,
|
||||
):
|
||||
if isinstance(vision_config, dict):
|
||||
self.vision_config = self.sub_configs["vision_config"](**vision_config)
|
||||
elif vision_config is None:
|
||||
self.vision_config = self.sub_configs["vision_config"]()
|
||||
|
||||
if isinstance(text_config, dict):
|
||||
self.text_config = self.sub_configs["text_config"](**text_config)
|
||||
elif text_config is None:
|
||||
self.text_config = self.sub_configs["text_config"]()
|
||||
|
||||
self.image_token_id = image_token_id
|
||||
self.video_token_id = video_token_id
|
||||
self.vision_start_token_id = vision_start_token_id
|
||||
self.vision_end_token_id = vision_end_token_id
|
||||
super().__init__(**kwargs, tie_word_embeddings=tie_word_embeddings)
|
||||
|
||||
|
||||
class Qwen3_5MoeVisionConfig(Qwen3_5VisionConfig):
|
||||
model_type = "qwen3_5_moe"
|
||||
|
||||
|
||||
class Qwen3_5MoeTextConfig(Qwen3_5TextConfig):
|
||||
model_type = "qwen3_5_moe_text"
|
||||
|
||||
|
||||
class Qwen3_5MoeConfig(Qwen3_5Config):
|
||||
model_type = "qwen3_5_moe"
|
||||
sub_configs = {
|
||||
"vision_config": Qwen3_5MoeVisionConfig,
|
||||
"text_config": Qwen3_5MoeTextConfig,
|
||||
}
|
||||
302
third_party/sglang/python/sglang/srt/configs/qwen3_next.py
vendored
Normal file
302
third_party/sglang/python/sglang/srt/configs/qwen3_next.py
vendored
Normal file
@@ -0,0 +1,302 @@
|
||||
# coding=utf-8
|
||||
# Copyright 2024 The Qwen team, Alibaba Group and the HuggingFace Inc. team. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
"""Qwen3Hybrid model configuration"""
|
||||
|
||||
import enum
|
||||
|
||||
from transformers.configuration_utils import PretrainedConfig
|
||||
from transformers.utils import logging
|
||||
|
||||
from sglang.srt.configs.mamba_utils import (
|
||||
Mamba2CacheParams,
|
||||
Mamba2StateShape,
|
||||
mamba2_state_dtype,
|
||||
)
|
||||
from sglang.srt.configs.update_config import adjust_tp_num_heads_if_necessary
|
||||
from sglang.srt.utils import is_cpu
|
||||
|
||||
logger = logging.get_logger(__name__)
|
||||
_is_cpu = is_cpu()
|
||||
|
||||
|
||||
class HybridLayerType(enum.Enum):
|
||||
full_attention = "attention"
|
||||
linear_attention = "linear_attention"
|
||||
|
||||
|
||||
class Qwen3NextConfig(PretrainedConfig):
|
||||
r"""
|
||||
This is the configuration class to store the configuration of a [`Qwen3NextModel`]. It is used to instantiate a
|
||||
Qwen3-Next model according to the specified arguments, defining the model architecture.
|
||||
Instantiating a configuration with the defaults will yield a similar configuration to that of
|
||||
Qwen3-Next-80B-A3B-Instruct [Qwen/Qwen3-Next-80B-A3B-Instruct](https://huggingface.co/Qwen/Qwen3-Next-80B-A3B-Instruct).
|
||||
|
||||
Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the
|
||||
documentation from [`PretrainedConfig`] for more information.
|
||||
|
||||
|
||||
Args:
|
||||
vocab_size (`int`, *optional*, defaults to 151936):
|
||||
Vocabulary size of the model. Defines the number of different tokens that can be represented by the
|
||||
`inputs_ids`.
|
||||
hidden_size (`int`, *optional*, defaults to 2048):
|
||||
Dimension of the hidden representations.
|
||||
intermediate_size (`int`, *optional*, defaults to 5632):
|
||||
Dimension of the MLP representations.
|
||||
num_hidden_layers (`int`, *optional*, defaults to 48):
|
||||
Number of hidden layers in the Transformer encoder.
|
||||
num_attention_heads (`int`, *optional*, defaults to 16):
|
||||
Number of attention heads for each attention layer in the Transformer encoder.
|
||||
num_key_value_heads (`int`, *optional*, defaults to 2):
|
||||
This is the number of key_value heads that should be used to implement Grouped Query Attention. If
|
||||
`num_key_value_heads=num_attention_heads`, the model will use Multi Head Attention (MHA), if
|
||||
`num_key_value_heads=1` the model will use Multi Query Attention (MQA) otherwise GQA is used. When
|
||||
converting a multi-head checkpoint to a GQA checkpoint, each group key and value head should be constructed
|
||||
by meanpooling all the original heads within that group. For more details checkout [this
|
||||
paper](https://arxiv.org/pdf/2305.13245.pdf). If it is not specified, will default to `32`.
|
||||
hidden_act (`str`, *optional*, defaults to `"silu"`):
|
||||
The non-linear activation function in the decoder.
|
||||
max_position_embeddings (`int`, *optional*, defaults to 32768):
|
||||
The maximum sequence length that this model might ever be used with.
|
||||
initializer_range (`float`, *optional*, defaults to 0.02):
|
||||
The standard deviation of the truncated_normal_initializer for initializing all weight matrices.
|
||||
rms_norm_eps (`float`, *optional*, defaults to 1e-06):
|
||||
The epsilon used by the rms normalization layers.
|
||||
use_cache (`bool`, *optional*, defaults to `True`):
|
||||
Whether or not the model should return the last key/values attentions (not used by all models). Only
|
||||
relevant if `config.is_decoder=True`.
|
||||
tie_word_embeddings (`bool`, *optional*, defaults to `False`):
|
||||
Whether the model's input and output word embeddings should be tied.
|
||||
rope_theta (`float`, *optional*, defaults to 10000.0):
|
||||
The base period of the RoPE embeddings.
|
||||
rope_scaling (`Dict`, *optional*):
|
||||
Dictionary containing the scaling configuration for the RoPE embeddings. NOTE: if you apply new rope type
|
||||
and you expect the model to work on longer `max_position_embeddings`, we recommend you to update this value
|
||||
accordingly.
|
||||
Expected contents:
|
||||
`rope_type` (`str`):
|
||||
The sub-variant of RoPE to use. Can be one of ['default', 'linear', 'dynamic', 'yarn', 'longrope',
|
||||
'llama3'], with 'default' being the original RoPE implementation.
|
||||
`factor` (`float`, *optional*):
|
||||
Used with all rope types except 'default'. The scaling factor to apply to the RoPE embeddings. In
|
||||
most scaling types, a `factor` of x will enable the model to handle sequences of length x *
|
||||
original maximum pre-trained length.
|
||||
`original_max_position_embeddings` (`int`, *optional*):
|
||||
Used with 'dynamic', 'longrope' and 'llama3'. The original max position embeddings used during
|
||||
pretraining.
|
||||
`attention_factor` (`float`, *optional*):
|
||||
Used with 'yarn' and 'longrope'. The scaling factor to be applied on the attention
|
||||
computation. If unspecified, it defaults to value recommended by the implementation, using the
|
||||
`factor` field to infer the suggested value.
|
||||
`beta_fast` (`float`, *optional*):
|
||||
Only used with 'yarn'. Parameter to set the boundary for extrapolation (only) in the linear
|
||||
ramp function. If unspecified, it defaults to 32.
|
||||
`beta_slow` (`float`, *optional*):
|
||||
Only used with 'yarn'. Parameter to set the boundary for interpolation (only) in the linear
|
||||
ramp function. If unspecified, it defaults to 1.
|
||||
`short_factor` (`List[float]`, *optional*):
|
||||
Only used with 'longrope'. The scaling factor to be applied to short contexts (<
|
||||
`original_max_position_embeddings`). Must be a list of numbers with the same length as the hidden
|
||||
size divided by the number of attention heads divided by 2
|
||||
`long_factor` (`List[float]`, *optional*):
|
||||
Only used with 'longrope'. The scaling factor to be applied to long contexts (<
|
||||
`original_max_position_embeddings`). Must be a list of numbers with the same length as the hidden
|
||||
size divided by the number of attention heads divided by 2
|
||||
`low_freq_factor` (`float`, *optional*):
|
||||
Only used with 'llama3'. Scaling factor applied to low frequency components of the RoPE
|
||||
`high_freq_factor` (`float`, *optional*):
|
||||
Only used with 'llama3'. Scaling factor applied to high frequency components of the RoPE
|
||||
partial_rotary_factor (`float`, *optional*, defaults to 0.25):
|
||||
Percentage of the query and keys which will have rotary embedding.
|
||||
attention_bias (`bool`, *optional*, defaults to `False`):
|
||||
Whether to use a bias in the query, key, value and output projection layers during self-attention.
|
||||
attention_dropout (`float`, *optional*, defaults to 0.0):
|
||||
The dropout ratio for the attention probabilities.
|
||||
head_dim (`int`, *optional*, defaults to 256):
|
||||
Projection weights dimension in multi-head attention.
|
||||
linear_conv_kernel_dim (`int`, *optional*, defaults to 4):
|
||||
Kernel size of the convolution used in linear attention layers.
|
||||
linear_key_head_dim (`int`, *optional*, defaults to 128):
|
||||
Dimension of each key head in linear attention.
|
||||
linear_value_head_dim (`int`, *optional*, defaults to 128):
|
||||
Dimension of each value head in linear attention.
|
||||
linear_num_key_heads (`int`, *optional*, defaults to 16):
|
||||
Number of key heads used in linear attention layers.
|
||||
linear_num_value_heads (`int`, *optional*, defaults to 32):
|
||||
Number of value heads used in linear attention layers.
|
||||
decoder_sparse_step (`int`, *optional*, defaults to 1):
|
||||
The frequency of the MoE layer.
|
||||
moe_intermediate_size (`int`, *optional*, defaults to 512):
|
||||
Intermediate size of the routed expert.
|
||||
shared_expert_intermediate_size (`int`, *optional*, defaults to 512):
|
||||
Intermediate size of the shared expert.
|
||||
num_experts_per_tok (`int`, *optional*, defaults to 10):
|
||||
Number of selected experts.
|
||||
num_experts (`int`, *optional*, defaults to 512):
|
||||
Number of routed experts.
|
||||
norm_topk_prob (`bool`, *optional*, defaults to `True`):
|
||||
Whether to normalize the topk probabilities.
|
||||
output_router_logits (`bool`, *optional*, defaults to `False`):
|
||||
Whether or not the router logits should be returned by the model. Enabling this will also
|
||||
allow the model to output the auxiliary loss, including load balancing loss and router z-loss.
|
||||
router_aux_loss_coef (`float`, *optional*, defaults to 0.001):
|
||||
The aux loss factor for the total loss.
|
||||
mlp_only_layers (`list[int]`, *optional*, defaults to `[]`):
|
||||
Indicate which layers use Qwen3NextMLP rather than Qwen3NextSparseMoeBlock
|
||||
The list contains layer index, from 0 to num_layers-1 if we have num_layers layers
|
||||
If `mlp_only_layers` is empty, `decoder_sparse_step` is used to determine the sparsity.
|
||||
layer_types (`list[str]`, *optional*, defaults to None):
|
||||
Types of each layer (attention or linear).
|
||||
|
||||
```python
|
||||
>>> from transformers import Qwen3NextModel, Qwen3NextConfig
|
||||
|
||||
>>> # Initializing a Qwen3Next style configuration
|
||||
>>> configuration = Qwen3NextConfig()
|
||||
|
||||
>>> # Initializing a model from the Qwen3-Next-80B-A3B style configuration
|
||||
>>> model = Qwen3NextModel(configuration)
|
||||
|
||||
>>> # Accessing the model configuration
|
||||
>>> configuration = model.config
|
||||
```
|
||||
"""
|
||||
|
||||
model_type = "qwen3_next"
|
||||
keys_to_ignore_at_inference = ["past_key_values"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vocab_size=151936,
|
||||
hidden_size=2048,
|
||||
intermediate_size=5632,
|
||||
num_hidden_layers=48,
|
||||
num_attention_heads=16,
|
||||
num_key_value_heads=2,
|
||||
hidden_act="silu",
|
||||
max_position_embeddings=32768,
|
||||
initializer_range=0.02,
|
||||
rms_norm_eps=1e-6,
|
||||
use_cache=True,
|
||||
tie_word_embeddings=False,
|
||||
rope_theta=10000.0,
|
||||
rope_scaling=None,
|
||||
partial_rotary_factor=0.25,
|
||||
attention_bias=False,
|
||||
attention_dropout=0.0,
|
||||
head_dim=256,
|
||||
linear_conv_kernel_dim=4,
|
||||
linear_key_head_dim=128,
|
||||
linear_value_head_dim=128,
|
||||
linear_num_key_heads=16,
|
||||
linear_num_value_heads=32,
|
||||
decoder_sparse_step=1,
|
||||
moe_intermediate_size=512,
|
||||
shared_expert_intermediate_size=512,
|
||||
num_experts_per_tok=10,
|
||||
num_experts=512,
|
||||
norm_topk_prob=True,
|
||||
output_router_logits=False,
|
||||
router_aux_loss_coef=0.001,
|
||||
mlp_only_layers=[],
|
||||
layer_types=None,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(tie_word_embeddings=tie_word_embeddings, **kwargs)
|
||||
self.vocab_size = vocab_size
|
||||
self.max_position_embeddings = max_position_embeddings
|
||||
self.hidden_size = hidden_size
|
||||
self.intermediate_size = intermediate_size
|
||||
self.num_hidden_layers = num_hidden_layers
|
||||
self.num_attention_heads = num_attention_heads
|
||||
self.num_key_value_heads = num_key_value_heads
|
||||
self.hidden_act = hidden_act
|
||||
self.initializer_range = initializer_range
|
||||
self.rms_norm_eps = rms_norm_eps
|
||||
self.use_cache = use_cache
|
||||
self.rope_theta = rope_theta
|
||||
self.rope_scaling = rope_scaling
|
||||
self.partial_rotary_factor = partial_rotary_factor
|
||||
self.attention_bias = attention_bias
|
||||
self.attention_dropout = attention_dropout
|
||||
self.head_dim = head_dim
|
||||
|
||||
# linear attention (gdn now part)
|
||||
self.linear_conv_kernel_dim = linear_conv_kernel_dim
|
||||
self.linear_key_head_dim = linear_key_head_dim
|
||||
self.linear_value_head_dim = linear_value_head_dim
|
||||
self.linear_num_key_heads = linear_num_key_heads
|
||||
self.linear_num_value_heads = linear_num_value_heads
|
||||
|
||||
# MoE arguments
|
||||
self.decoder_sparse_step = decoder_sparse_step
|
||||
self.moe_intermediate_size = moe_intermediate_size
|
||||
self.shared_expert_intermediate_size = shared_expert_intermediate_size
|
||||
self.num_experts_per_tok = num_experts_per_tok
|
||||
self.num_experts = num_experts
|
||||
self.norm_topk_prob = norm_topk_prob
|
||||
self.output_router_logits = output_router_logits
|
||||
self.router_aux_loss_coef = router_aux_loss_coef
|
||||
self.mlp_only_layers = mlp_only_layers
|
||||
|
||||
@property
|
||||
def layers_block_type(self):
|
||||
layer_type_list = []
|
||||
|
||||
for l in range(self.num_hidden_layers):
|
||||
if (l + 1) % self.full_attention_interval == 0:
|
||||
layer_type_list.append(HybridLayerType.full_attention.value)
|
||||
else:
|
||||
layer_type_list.append(HybridLayerType.linear_attention.value)
|
||||
|
||||
return layer_type_list
|
||||
|
||||
@property
|
||||
def linear_layer_ids(self):
|
||||
return [
|
||||
i
|
||||
for i, type_value in enumerate(self.layers_block_type)
|
||||
if type_value == HybridLayerType.linear_attention.value
|
||||
]
|
||||
|
||||
@property
|
||||
def full_attention_layer_ids(self):
|
||||
return [
|
||||
i
|
||||
for i, type_value in enumerate(self.layers_block_type)
|
||||
if type_value == HybridLayerType.full_attention.value
|
||||
]
|
||||
|
||||
@property
|
||||
def mamba2_cache_params(self) -> Mamba2CacheParams:
|
||||
from sglang.srt.layers.dp_attention import get_attention_tp_size
|
||||
|
||||
if _is_cpu:
|
||||
world_size = get_attention_tp_size()
|
||||
adjust_tp_num_heads_if_necessary(self, world_size, False)
|
||||
|
||||
shape = Mamba2StateShape.create(
|
||||
tp_world_size=get_attention_tp_size(),
|
||||
intermediate_size=self.linear_value_head_dim * self.linear_num_value_heads,
|
||||
n_groups=self.linear_num_key_heads,
|
||||
num_heads=self.linear_num_value_heads,
|
||||
head_dim=self.linear_value_head_dim,
|
||||
state_size=self.linear_key_head_dim,
|
||||
conv_kernel=self.linear_conv_kernel_dim,
|
||||
)
|
||||
|
||||
return Mamba2CacheParams(
|
||||
shape=shape, layers=self.linear_layer_ids, dtype=mamba2_state_dtype(self)
|
||||
)
|
||||
609
third_party/sglang/python/sglang/srt/configs/qwen3_omni.py
vendored
Normal file
609
third_party/sglang/python/sglang/srt/configs/qwen3_omni.py
vendored
Normal file
@@ -0,0 +1,609 @@
|
||||
from transformers import PretrainedConfig
|
||||
from transformers.configuration_utils import layer_type_validation
|
||||
|
||||
from sglang.utils import logger
|
||||
|
||||
|
||||
class Qwen3OmniMoeAudioEncoderConfig(PretrainedConfig):
|
||||
model_type = "qwen3_omni_moe_audio_encoder"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
num_mel_bins=128,
|
||||
encoder_layers=32,
|
||||
encoder_attention_heads=20,
|
||||
encoder_ffn_dim=5120,
|
||||
d_model=1280,
|
||||
dropout=0,
|
||||
attention_dropout=0,
|
||||
activation_function="gelu",
|
||||
activation_dropout=0,
|
||||
scale_embedding=False,
|
||||
initializer_range=0.02,
|
||||
max_source_positions=1500,
|
||||
n_window=100,
|
||||
output_dim=3584,
|
||||
n_window_infer=400,
|
||||
conv_chunksize=500,
|
||||
downsample_hidden_size=480,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.num_mel_bins = num_mel_bins
|
||||
self.d_model = d_model
|
||||
self.encoder_layers = encoder_layers
|
||||
self.encoder_attention_heads = encoder_attention_heads
|
||||
self.encoder_ffn_dim = encoder_ffn_dim
|
||||
self.dropout = dropout
|
||||
self.attention_dropout = attention_dropout
|
||||
self.activation_function = activation_function
|
||||
self.activation_dropout = activation_dropout
|
||||
self.num_hidden_layers = encoder_layers
|
||||
self.initializer_range = initializer_range
|
||||
self.scale_embedding = (
|
||||
scale_embedding # scale factor will be sqrt(d_model) if True
|
||||
)
|
||||
self.max_source_positions = max_source_positions
|
||||
self.n_window = n_window
|
||||
self.output_dim = output_dim
|
||||
self.n_window_infer = n_window_infer
|
||||
self.conv_chunksize = conv_chunksize
|
||||
self.downsample_hidden_size = downsample_hidden_size
|
||||
|
||||
|
||||
class Qwen3OmniMoeVisionEncoderConfig(PretrainedConfig):
|
||||
model_type = "qwen3_omni_moe_vision_encoder"
|
||||
base_config_key = "vision_config"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
depth=27,
|
||||
hidden_size=1152,
|
||||
hidden_act="gelu_pytorch_tanh",
|
||||
intermediate_size=4304,
|
||||
num_heads=16,
|
||||
in_channels=3,
|
||||
patch_size=16,
|
||||
spatial_merge_size=2,
|
||||
temporal_patch_size=2,
|
||||
out_hidden_size=3584,
|
||||
num_position_embeddings=2304,
|
||||
deepstack_visual_indexes=[8, 16, 24],
|
||||
initializer_range=0.02,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.depth = depth
|
||||
self.hidden_size = hidden_size
|
||||
self.hidden_act = hidden_act
|
||||
self.intermediate_size = intermediate_size
|
||||
self.num_heads = num_heads
|
||||
self.in_channels = in_channels
|
||||
self.patch_size = patch_size
|
||||
self.spatial_merge_size = spatial_merge_size
|
||||
self.temporal_patch_size = temporal_patch_size
|
||||
self.out_hidden_size = out_hidden_size
|
||||
self.num_position_embeddings = num_position_embeddings
|
||||
self.initializer_range = initializer_range
|
||||
self.deepstack_visual_indexes = deepstack_visual_indexes
|
||||
|
||||
|
||||
class Qwen3OmniMoeTextConfig(PretrainedConfig):
|
||||
model_type = "qwen3_omni_moe_text"
|
||||
keys_to_ignore_at_inference = ["past_key_values"]
|
||||
|
||||
# Default tensor parallel plan for base model `Qwen3OmniMoeText`
|
||||
base_model_tp_plan = {
|
||||
"layers.*.self_attn.q_proj": "colwise",
|
||||
"layers.*.self_attn.k_proj": "colwise",
|
||||
"layers.*.self_attn.v_proj": "colwise",
|
||||
"layers.*.self_attn.o_proj": "rowwise",
|
||||
"layers.*.mlp.experts.*.gate_proj": "colwise",
|
||||
"layers.*.mlp.experts.*.up_proj": "colwise",
|
||||
"layers.*.mlp.experts.*.down_proj": "rowwise",
|
||||
"layers.*.mlp.gate_proj": "colwise",
|
||||
"layers.*.mlp.up_proj": "colwise",
|
||||
"layers.*.mlp.down_proj": "rowwise",
|
||||
}
|
||||
base_model_pp_plan = {
|
||||
"embed_tokens": (["input_ids"], ["inputs_embeds"]),
|
||||
"layers": (["hidden_states", "attention_mask"], ["hidden_states"]),
|
||||
"norm": (["hidden_states"], ["hidden_states"]),
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vocab_size=3584,
|
||||
hidden_size=2048,
|
||||
intermediate_size=18944,
|
||||
num_hidden_layers=28,
|
||||
num_attention_heads=28,
|
||||
num_key_value_heads=4,
|
||||
hidden_act="silu",
|
||||
max_position_embeddings=32768,
|
||||
initializer_range=0.02,
|
||||
rms_norm_eps=1e-6,
|
||||
use_cache=True,
|
||||
tie_word_embeddings=False,
|
||||
rope_theta=1000000.0,
|
||||
rope_scaling=None,
|
||||
attention_bias=False,
|
||||
sliding_window=None,
|
||||
attention_dropout=0,
|
||||
decoder_sparse_step=1,
|
||||
moe_intermediate_size=768,
|
||||
num_experts_per_tok=8,
|
||||
num_experts=128,
|
||||
norm_topk_prob=True,
|
||||
output_router_logits=False,
|
||||
router_aux_loss_coef=0.001,
|
||||
mlp_only_layers=None,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(
|
||||
tie_word_embeddings=tie_word_embeddings,
|
||||
**kwargs,
|
||||
)
|
||||
self.vocab_size = vocab_size
|
||||
self.max_position_embeddings = max_position_embeddings
|
||||
self.hidden_size = hidden_size
|
||||
self.intermediate_size = intermediate_size
|
||||
self.num_hidden_layers = num_hidden_layers
|
||||
self.num_attention_heads = num_attention_heads
|
||||
self.sliding_window = sliding_window
|
||||
|
||||
self.num_key_value_heads = num_key_value_heads
|
||||
self.hidden_act = hidden_act
|
||||
self.initializer_range = initializer_range
|
||||
self.rms_norm_eps = rms_norm_eps
|
||||
self.use_cache = use_cache
|
||||
self.rope_theta = rope_theta
|
||||
self.rope_scaling = rope_scaling
|
||||
self.attention_bias = attention_bias
|
||||
self.attention_dropout = attention_dropout
|
||||
# Validate the correctness of rotary position embeddings parameters
|
||||
# BC: if there is a 'type' field, move it to 'rope_type'.
|
||||
if self.rope_scaling is not None and "type" in self.rope_scaling:
|
||||
self.rope_scaling["rope_type"] = self.rope_scaling["type"]
|
||||
|
||||
# MoE arguments
|
||||
self.decoder_sparse_step = decoder_sparse_step
|
||||
self.moe_intermediate_size = moe_intermediate_size
|
||||
self.num_experts_per_tok = num_experts_per_tok
|
||||
self.num_experts = num_experts
|
||||
self.norm_topk_prob = norm_topk_prob
|
||||
self.output_router_logits = output_router_logits
|
||||
self.router_aux_loss_coef = router_aux_loss_coef
|
||||
self.mlp_only_layers = [] if mlp_only_layers is None else mlp_only_layers
|
||||
|
||||
|
||||
class Qwen3OmniMoeThinkerConfig(PretrainedConfig):
|
||||
model_type = "qwen3_omni_moe_thinker"
|
||||
attribute_map = {
|
||||
"image_token_id": "image_token_index",
|
||||
"video_token_id": "video_token_index",
|
||||
"audio_token_id": "audio_token_index",
|
||||
}
|
||||
sub_configs = {
|
||||
"audio_config": Qwen3OmniMoeAudioEncoderConfig,
|
||||
"vision_config": Qwen3OmniMoeVisionEncoderConfig,
|
||||
"text_config": Qwen3OmniMoeTextConfig,
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
audio_config=None,
|
||||
vision_config=None,
|
||||
text_config=None,
|
||||
audio_token_id=151646,
|
||||
image_token_id=151655,
|
||||
video_token_id=151656,
|
||||
position_id_per_seconds=25,
|
||||
audio_start_token_id=151647,
|
||||
user_token_id=872,
|
||||
initializer_range=0.02,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
self.user_token_id = user_token_id
|
||||
self.position_id_per_seconds = position_id_per_seconds
|
||||
self.audio_start_token_id = audio_start_token_id
|
||||
self.initializer_range = initializer_range
|
||||
|
||||
if isinstance(vision_config, dict):
|
||||
vision_config = Qwen3OmniMoeVisionEncoderConfig(**vision_config)
|
||||
elif vision_config is None:
|
||||
vision_config = Qwen3OmniMoeVisionEncoderConfig()
|
||||
self.vision_config = vision_config
|
||||
|
||||
if isinstance(audio_config, dict):
|
||||
audio_config = Qwen3OmniMoeAudioEncoderConfig(**audio_config)
|
||||
elif audio_config is None:
|
||||
audio_config = Qwen3OmniMoeAudioEncoderConfig()
|
||||
self.audio_config = audio_config
|
||||
|
||||
if isinstance(text_config, dict):
|
||||
text_config = Qwen3OmniMoeTextConfig(**text_config)
|
||||
elif text_config is None:
|
||||
text_config = Qwen3OmniMoeTextConfig()
|
||||
self.text_config = text_config
|
||||
self.audio_token_id = audio_token_id
|
||||
self.image_token_id = image_token_id
|
||||
self.video_token_id = video_token_id
|
||||
|
||||
|
||||
class Qwen3OmniMoeTalkerCodePredictorConfig(PretrainedConfig):
|
||||
|
||||
model_type = "qwen3_omni_moe_talker_code_predictor"
|
||||
keys_to_ignore_at_inference = ["past_key_values"]
|
||||
|
||||
# Default tensor parallel plan for base model `Qwen3OmniMoeTalkerCodePredictor`
|
||||
base_model_tp_plan = {
|
||||
"layers.*.self_attn.q_proj": "colwise",
|
||||
"layers.*.self_attn.k_proj": "colwise",
|
||||
"layers.*.self_attn.v_proj": "colwise",
|
||||
"layers.*.self_attn.o_proj": "rowwise",
|
||||
"layers.*.mlp.gate_proj": "colwise",
|
||||
"layers.*.mlp.up_proj": "colwise",
|
||||
"layers.*.mlp.down_proj": "rowwise",
|
||||
}
|
||||
base_model_pp_plan = {
|
||||
"embed_tokens": (["input_ids"], ["inputs_embeds"]),
|
||||
"layers": (["hidden_states", "attention_mask"], ["hidden_states"]),
|
||||
"norm": (["hidden_states"], ["hidden_states"]),
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vocab_size=2048,
|
||||
hidden_size=1024,
|
||||
intermediate_size=3072,
|
||||
num_hidden_layers=5,
|
||||
num_attention_heads=16,
|
||||
num_key_value_heads=8,
|
||||
head_dim=128,
|
||||
hidden_act="silu",
|
||||
max_position_embeddings=32768,
|
||||
initializer_range=0.02,
|
||||
rms_norm_eps=0.000001,
|
||||
use_cache=True,
|
||||
tie_word_embeddings=False,
|
||||
rope_theta=10000,
|
||||
rope_scaling=None,
|
||||
attention_bias=False,
|
||||
sliding_window=None,
|
||||
layer_types=None,
|
||||
attention_dropout=0,
|
||||
num_code_groups=32,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(
|
||||
tie_word_embeddings=tie_word_embeddings,
|
||||
**kwargs,
|
||||
)
|
||||
self.vocab_size = vocab_size
|
||||
self.max_position_embeddings = max_position_embeddings
|
||||
self.hidden_size = hidden_size
|
||||
self.intermediate_size = intermediate_size
|
||||
self.num_hidden_layers = num_hidden_layers
|
||||
self.num_attention_heads = num_attention_heads
|
||||
self.sliding_window = sliding_window
|
||||
|
||||
# for backward compatibility
|
||||
if num_key_value_heads is None:
|
||||
num_key_value_heads = num_attention_heads
|
||||
|
||||
self.num_key_value_heads = num_key_value_heads
|
||||
self.head_dim = head_dim
|
||||
self.hidden_act = hidden_act
|
||||
self.initializer_range = initializer_range
|
||||
self.rms_norm_eps = rms_norm_eps
|
||||
self.use_cache = use_cache
|
||||
self.rope_theta = rope_theta
|
||||
self.rope_scaling = rope_scaling
|
||||
self.attention_bias = attention_bias
|
||||
self.attention_dropout = attention_dropout
|
||||
# Validate the correctness of rotary position embeddings parameters
|
||||
# BC: if there is a 'type' field, move it to 'rope_type'.
|
||||
if self.rope_scaling is not None and "type" in self.rope_scaling:
|
||||
self.rope_scaling["rope_type"] = self.rope_scaling["type"]
|
||||
|
||||
self.layer_types = layer_types
|
||||
if self.layer_types is None:
|
||||
self.layer_types = [
|
||||
(
|
||||
"sliding_attention"
|
||||
if self.sliding_window is not None and i >= self.max_window_layers
|
||||
else "full_attention"
|
||||
)
|
||||
for i in range(self.num_hidden_layers)
|
||||
]
|
||||
layer_type_validation(self.layer_types, self.num_hidden_layers)
|
||||
self.num_code_groups = num_code_groups
|
||||
|
||||
|
||||
class Qwen3OmniMoeTalkerTextConfig(PretrainedConfig):
|
||||
|
||||
model_type = "qwen3_omni_moe_talker_text"
|
||||
keys_to_ignore_at_inference = ["past_key_values"]
|
||||
|
||||
# Default tensor parallel plan for base model `Qwen3OmniMoeTalkerText`
|
||||
base_model_tp_plan = {
|
||||
"layers.*.self_attn.q_proj": "colwise",
|
||||
"layers.*.self_attn.k_proj": "colwise",
|
||||
"layers.*.self_attn.v_proj": "colwise",
|
||||
"layers.*.self_attn.o_proj": "rowwise",
|
||||
"layers.*.mlp.experts.*.gate_proj": "colwise",
|
||||
"layers.*.mlp.experts.*.up_proj": "colwise",
|
||||
"layers.*.mlp.experts.*.down_proj": "rowwise",
|
||||
"layers.*.mlp.gate_proj": "colwise",
|
||||
"layers.*.mlp.up_proj": "colwise",
|
||||
"layers.*.mlp.down_proj": "rowwise",
|
||||
}
|
||||
base_model_pp_plan = {
|
||||
"embed_tokens": (["input_ids"], ["inputs_embeds"]),
|
||||
"layers": (["hidden_states", "attention_mask"], ["hidden_states"]),
|
||||
"norm": (["hidden_states"], ["hidden_states"]),
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vocab_size=3072,
|
||||
hidden_size=1024,
|
||||
intermediate_size=2048,
|
||||
num_hidden_layers=20,
|
||||
num_attention_heads=16,
|
||||
num_key_value_heads=2,
|
||||
hidden_act="silu",
|
||||
max_position_embeddings=32768,
|
||||
initializer_range=0.02,
|
||||
rms_norm_eps=0.000001,
|
||||
use_cache=True,
|
||||
tie_word_embeddings=False,
|
||||
rope_theta=10000,
|
||||
rope_scaling=None,
|
||||
attention_bias=False,
|
||||
sliding_window=None,
|
||||
attention_dropout=0,
|
||||
decoder_sparse_step=1,
|
||||
moe_intermediate_size=384,
|
||||
num_experts_per_tok=8,
|
||||
num_experts=128,
|
||||
norm_topk_prob=False,
|
||||
output_router_logits=False,
|
||||
router_aux_loss_coef=0.001,
|
||||
mlp_only_layers=None,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(
|
||||
tie_word_embeddings=tie_word_embeddings,
|
||||
**kwargs,
|
||||
)
|
||||
self.vocab_size = vocab_size
|
||||
self.max_position_embeddings = max_position_embeddings
|
||||
self.hidden_size = hidden_size
|
||||
self.intermediate_size = intermediate_size
|
||||
self.num_hidden_layers = num_hidden_layers
|
||||
self.num_attention_heads = num_attention_heads
|
||||
self.sliding_window = sliding_window
|
||||
|
||||
self.num_key_value_heads = num_key_value_heads
|
||||
self.hidden_act = hidden_act
|
||||
self.initializer_range = initializer_range
|
||||
self.rms_norm_eps = rms_norm_eps
|
||||
self.use_cache = use_cache
|
||||
self.rope_theta = rope_theta
|
||||
self.rope_scaling = rope_scaling
|
||||
self.attention_bias = attention_bias
|
||||
self.attention_dropout = attention_dropout
|
||||
# Validate the correctness of rotary position embeddings parameters
|
||||
# BC: if there is a 'type' field, move it to 'rope_type'.
|
||||
if self.rope_scaling is not None and "type" in self.rope_scaling:
|
||||
self.rope_scaling["rope_type"] = self.rope_scaling["type"]
|
||||
|
||||
# MoE arguments
|
||||
self.decoder_sparse_step = decoder_sparse_step
|
||||
self.moe_intermediate_size = moe_intermediate_size
|
||||
self.num_experts_per_tok = num_experts_per_tok
|
||||
self.num_experts = num_experts
|
||||
self.norm_topk_prob = norm_topk_prob
|
||||
self.output_router_logits = output_router_logits
|
||||
self.router_aux_loss_coef = router_aux_loss_coef
|
||||
self.mlp_only_layers = [] if mlp_only_layers is None else mlp_only_layers
|
||||
|
||||
|
||||
class Qwen3OmniMoeTalkerConfig(PretrainedConfig):
|
||||
|
||||
sub_configs = {
|
||||
"code_predictor_config": Qwen3OmniMoeTalkerCodePredictorConfig,
|
||||
"text_config": Qwen3OmniMoeTalkerTextConfig,
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
code_predictor_config=None,
|
||||
text_config=None,
|
||||
num_code_groups=32,
|
||||
thinker_hidden_size=2048,
|
||||
codec_eos_token_id=4198,
|
||||
accept_hidden_layer=18,
|
||||
codec_nothink_id=4203,
|
||||
codec_think_bos_id=4204,
|
||||
codec_think_eos_id=4205,
|
||||
codec_pad_id=4196,
|
||||
codec_bos_id=4197,
|
||||
audio_token_id=151646,
|
||||
image_token_id=151655,
|
||||
video_token_id=151656,
|
||||
vision_start_token_id=151652,
|
||||
position_id_per_seconds=25,
|
||||
audio_start_token_id=151669,
|
||||
speaker_id=None,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
if code_predictor_config is None:
|
||||
code_predictor_config = {}
|
||||
self.code_predictor_config = Qwen3OmniMoeTalkerCodePredictorConfig()
|
||||
logger.info(
|
||||
"code_predictor_config is None. Initializing code_predictor_config model with default values"
|
||||
)
|
||||
elif isinstance(code_predictor_config, Qwen3OmniMoeTalkerCodePredictorConfig):
|
||||
self.code_predictor_config = code_predictor_config
|
||||
else:
|
||||
self.code_predictor_config = Qwen3OmniMoeTalkerCodePredictorConfig(
|
||||
**code_predictor_config
|
||||
)
|
||||
|
||||
if text_config is None:
|
||||
text_config = {}
|
||||
self.text_config = Qwen3OmniMoeTalkerTextConfig()
|
||||
logger.info(
|
||||
"talker text_config is None. Initializing talker text model with default values"
|
||||
)
|
||||
elif isinstance(text_config, Qwen3OmniMoeTalkerTextConfig):
|
||||
self.text_config = text_config
|
||||
else:
|
||||
self.text_config = Qwen3OmniMoeTalkerTextConfig(**text_config)
|
||||
self.num_code_groups = num_code_groups
|
||||
self.thinker_hidden_size = thinker_hidden_size
|
||||
self.codec_eos_token_id = codec_eos_token_id
|
||||
self.accept_hidden_layer = accept_hidden_layer
|
||||
self.codec_nothink_id = codec_nothink_id
|
||||
self.codec_think_bos_id = codec_think_bos_id
|
||||
self.codec_think_eos_id = codec_think_eos_id
|
||||
self.codec_pad_id = codec_pad_id
|
||||
self.codec_bos_id = codec_bos_id
|
||||
self.audio_token_id = audio_token_id
|
||||
self.image_token_id = image_token_id
|
||||
self.video_token_id = video_token_id
|
||||
self.position_id_per_seconds = position_id_per_seconds
|
||||
self.audio_start_token_id = audio_start_token_id
|
||||
self.vision_start_token_id = vision_start_token_id
|
||||
self.speaker_id = speaker_id
|
||||
|
||||
|
||||
class Qwen3OmniMoeCode2WavConfig(PretrainedConfig):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
codebook_size=2048,
|
||||
hidden_size=1024,
|
||||
max_position_embeddings=8000,
|
||||
rope_theta=10000,
|
||||
num_attention_heads=16,
|
||||
num_key_value_heads=16,
|
||||
attention_bias=False,
|
||||
sliding_window=72,
|
||||
intermediate_size=3072,
|
||||
hidden_act="silu",
|
||||
layer_scale_initial_scale=0.01,
|
||||
rms_norm_eps=1e-5,
|
||||
num_hidden_layers=8,
|
||||
num_quantizers=16,
|
||||
upsample_rates=(8, 5, 4, 3),
|
||||
upsampling_ratios=(2, 2),
|
||||
decoder_dim=1536,
|
||||
attention_dropout=0.0,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
self.codebook_size = codebook_size
|
||||
self.hidden_size = hidden_size
|
||||
self.max_position_embeddings = max_position_embeddings
|
||||
self.rope_theta = rope_theta
|
||||
self.num_attention_heads = num_attention_heads
|
||||
self.num_key_value_heads = num_key_value_heads
|
||||
self.attention_bias = attention_bias
|
||||
self.sliding_window = sliding_window
|
||||
self.intermediate_size = intermediate_size
|
||||
self.hidden_act = hidden_act
|
||||
self.layer_scale_initial_scale = layer_scale_initial_scale
|
||||
self.rms_norm_eps = rms_norm_eps
|
||||
self.num_hidden_layers = num_hidden_layers
|
||||
self.num_quantizers = num_quantizers
|
||||
self.upsample_rates = upsample_rates
|
||||
self.upsampling_ratios = upsampling_ratios
|
||||
self.decoder_dim = decoder_dim
|
||||
self.attention_dropout = attention_dropout
|
||||
|
||||
@property
|
||||
def layer_types(self):
|
||||
"""
|
||||
All layer in code2wav should be sliding attention
|
||||
"""
|
||||
return ["sliding_attention"] * self.num_hidden_layers
|
||||
|
||||
|
||||
class Qwen3OmniMoeConfig(PretrainedConfig):
|
||||
|
||||
model_type = "qwen3_omni_moe"
|
||||
sub_configs = {
|
||||
"thinker_config": Qwen3OmniMoeThinkerConfig,
|
||||
"talker_config": Qwen3OmniMoeTalkerConfig,
|
||||
"code2wav_config": Qwen3OmniMoeCode2WavConfig,
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
thinker_config=None,
|
||||
talker_config=None,
|
||||
code2wav_config=None,
|
||||
enable_audio_output=True,
|
||||
im_start_token_id=151644,
|
||||
im_end_token_id=151645,
|
||||
tts_pad_token_id=151671,
|
||||
tts_bos_token_id=151672,
|
||||
tts_eos_token_id=151673,
|
||||
system_token_id=8948,
|
||||
user_token_id=872,
|
||||
assistant_token_id=77091,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
if thinker_config is None:
|
||||
thinker_config = {}
|
||||
logger.info(
|
||||
"thinker_config is None. Initializing thinker model with default values"
|
||||
)
|
||||
|
||||
if talker_config is None:
|
||||
talker_config = {}
|
||||
logger.info(
|
||||
"talker_config is None. Initializing talker model with default values"
|
||||
)
|
||||
|
||||
if code2wav_config is None:
|
||||
code2wav_config = {}
|
||||
logger.info(
|
||||
"code2wav_config is None. Initializing code2wav model with default values"
|
||||
)
|
||||
|
||||
self.thinker_config = Qwen3OmniMoeThinkerConfig(**thinker_config)
|
||||
self.talker_config = Qwen3OmniMoeTalkerConfig(**talker_config)
|
||||
self.code2wav_config = Qwen3OmniMoeCode2WavConfig(**code2wav_config)
|
||||
self.enable_audio_output = enable_audio_output
|
||||
self.im_start_token_id = im_start_token_id
|
||||
self.im_end_token_id = im_end_token_id
|
||||
self.tts_pad_token_id = tts_pad_token_id
|
||||
self.tts_bos_token_id = tts_bos_token_id
|
||||
self.tts_eos_token_id = tts_eos_token_id
|
||||
self.system_token_id = system_token_id
|
||||
self.user_token_id = user_token_id
|
||||
self.assistant_token_id = assistant_token_id
|
||||
|
||||
def get_text_config(self, decoder=False) -> "PretrainedConfig":
|
||||
"""
|
||||
Returns the config that is meant to be used with text IO. On most models, it is the original config instance
|
||||
itself. On specific composite models, it is under a set of valid names.
|
||||
|
||||
Args:
|
||||
decoder (`Optional[bool]`, *optional*, defaults to `False`):
|
||||
If set to `True`, then only search for decoder config names.
|
||||
"""
|
||||
# Overridden for deeply nested config like Qwen2-Omni. We don't have any omni model
|
||||
# except for Qwen yet. This has to be generalized if more deeply nested configs are
|
||||
# added. NOTE: currently method used only by vLLM
|
||||
return self.thinker_config.get_text_config()
|
||||
571
third_party/sglang/python/sglang/srt/configs/qwen3_vl.py
vendored
Normal file
571
third_party/sglang/python/sglang/srt/configs/qwen3_vl.py
vendored
Normal file
@@ -0,0 +1,571 @@
|
||||
from transformers import PretrainedConfig
|
||||
|
||||
|
||||
class Qwen3VLVisionConfig(PretrainedConfig):
|
||||
model_type = "qwen3_vl"
|
||||
base_config_key = "vision_config"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
depth=27,
|
||||
hidden_size=1152,
|
||||
hidden_act="gelu_pytorch_tanh",
|
||||
intermediate_size=4304,
|
||||
num_heads=16,
|
||||
in_channels=3,
|
||||
patch_size=16,
|
||||
spatial_merge_size=2,
|
||||
temporal_patch_size=2,
|
||||
out_hidden_size=3584,
|
||||
num_position_embeddings=2304,
|
||||
deepstack_visual_indexes=[8, 16, 24],
|
||||
initializer_range=0.02,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.depth = depth
|
||||
self.hidden_size = hidden_size
|
||||
self.hidden_act = hidden_act
|
||||
self.intermediate_size = intermediate_size
|
||||
self.num_heads = num_heads
|
||||
self.in_channels = in_channels
|
||||
self.patch_size = patch_size
|
||||
self.spatial_merge_size = spatial_merge_size
|
||||
self.temporal_patch_size = temporal_patch_size
|
||||
self.out_hidden_size = out_hidden_size
|
||||
self.num_position_embeddings = num_position_embeddings
|
||||
self.initializer_range = initializer_range
|
||||
self.deepstack_visual_indexes = deepstack_visual_indexes
|
||||
|
||||
|
||||
class Qwen3VLTextConfig(PretrainedConfig):
|
||||
r"""
|
||||
This is the configuration class to store the configuration of a [`Qwen3VLTextModel`]. It is used to instantiate a
|
||||
Qwen3-VL model according to the specified arguments, defining the model architecture. Instantiating a configuration
|
||||
with the defaults will yield a similar configuration to that of
|
||||
Qwen3-VL-4B-Instruct [Qwen/Qwen3-VL-4B-Instruct](https://huggingface.co/Qwen/Qwen3-VL-4B-Instruct).
|
||||
|
||||
Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the
|
||||
documentation from [`PretrainedConfig`] for more information.
|
||||
|
||||
Args:
|
||||
vocab_size (`int`, *optional*, defaults to 151936):
|
||||
Vocabulary size of the Qwen3VL model. Defines the number of different tokens that can be represented by the
|
||||
`inputs_ids` passed when calling [`Qwen3VLModel`]
|
||||
hidden_size (`int`, *optional*, defaults to 4096):
|
||||
Dimension of the hidden representations.
|
||||
intermediate_size (`int`, *optional*, defaults to 22016):
|
||||
Dimension of the MLP representations.
|
||||
num_hidden_layers (`int`, *optional*, defaults to 32):
|
||||
Number of hidden layers in the Transformer encoder.
|
||||
num_attention_heads (`int`, *optional*, defaults to 32):
|
||||
Number of attention heads for each attention layer in the Transformer encoder.
|
||||
num_key_value_heads (`int`, *optional*, defaults to 32):
|
||||
This is the number of key_value heads that should be used to implement Grouped Query Attention. If
|
||||
`num_key_value_heads=num_attention_heads`, the model will use Multi Head Attention (MHA), if
|
||||
`num_key_value_heads=1` the model will use Multi Query Attention (MQA) otherwise GQA is used. When
|
||||
converting a multi-head checkpoint to a GQA checkpoint, each group key and value head should be constructed
|
||||
by meanpooling all the original heads within that group. For more details, check out [this
|
||||
paper](https://huggingface.co/papers/2305.13245). If it is not specified, will default to `32`.
|
||||
head_dim (`int`, *optional*, defaults to 128):
|
||||
The dimension of the head. If not specified, will default to `hidden_size // num_attention_heads`.
|
||||
hidden_act (`str` or `function`, *optional*, defaults to `"silu"`):
|
||||
The non-linear activation function (function or string) in the decoder.
|
||||
max_position_embeddings (`int`, *optional*, defaults to 128000):
|
||||
The maximum sequence length that this model might ever be used with.
|
||||
initializer_range (`float`, *optional*, defaults to 0.02):
|
||||
The standard deviation of the truncated_normal_initializer for initializing all weight matrices.
|
||||
rms_norm_eps (`float`, *optional*, defaults to 1e-06):
|
||||
The epsilon used by the rms normalization layers.
|
||||
use_cache (`bool`, *optional*, defaults to `True`):
|
||||
Whether or not the model should return the last key/values attentions (not used by all models). Only
|
||||
relevant if `config.is_decoder=True`.
|
||||
tie_word_embeddings (`bool`, *optional*, defaults to `False`):
|
||||
Whether the model's input and output word embeddings should be tied.
|
||||
rope_theta (`float`, *optional*, defaults to 5000000.0):
|
||||
The base period of the RoPE embeddings.
|
||||
rope_scaling (`Dict`, *optional*):
|
||||
Dictionary containing the scaling configuration for the RoPE embeddings. NOTE: if you apply new rope type
|
||||
and you expect the model to work on longer `max_position_embeddings`, we recommend you to update this value
|
||||
accordingly.
|
||||
Expected contents:
|
||||
`rope_type` (`str`):
|
||||
The sub-variant of RoPE to use. Can be one of ['default', 'linear', 'dynamic', 'yarn', 'longrope',
|
||||
'llama3'], with 'default' being the original RoPE implementation.
|
||||
`factor` (`float`, *optional*):
|
||||
Used with all rope types except 'default'. The scaling factor to apply to the RoPE embeddings. In
|
||||
most scaling types, a `factor` of x will enable the model to handle sequences of length x *
|
||||
original maximum pre-trained length.
|
||||
`original_max_position_embeddings` (`int`, *optional*):
|
||||
Used with 'dynamic', 'longrope' and 'llama3'. The original max position embeddings used during
|
||||
pretraining.
|
||||
`attention_factor` (`float`, *optional*):
|
||||
Used with 'yarn' and 'longrope'. The scaling factor to be applied on the attention
|
||||
computation. If unspecified, it defaults to value recommended by the implementation, using the
|
||||
`factor` field to infer the suggested value.
|
||||
`beta_fast` (`float`, *optional*):
|
||||
Only used with 'yarn'. Parameter to set the boundary for extrapolation (only) in the linear
|
||||
ramp function. If unspecified, it defaults to 32.
|
||||
`beta_slow` (`float`, *optional*):
|
||||
Only used with 'yarn'. Parameter to set the boundary for interpolation (only) in the linear
|
||||
ramp function. If unspecified, it defaults to 1.
|
||||
`short_factor` (`list[float]`, *optional*):
|
||||
Only used with 'longrope'. The scaling factor to be applied to short contexts (<
|
||||
`original_max_position_embeddings`). Must be a list of numbers with the same length as the hidden
|
||||
size divided by the number of attention heads divided by 2
|
||||
`long_factor` (`list[float]`, *optional*):
|
||||
Only used with 'longrope'. The scaling factor to be applied to long contexts (<
|
||||
`original_max_position_embeddings`). Must be a list of numbers with the same length as the hidden
|
||||
size divided by the number of attention heads divided by 2
|
||||
`low_freq_factor` (`float`, *optional*):
|
||||
Only used with 'llama3'. Scaling factor applied to low frequency components of the RoPE
|
||||
`high_freq_factor` (`float`, *optional*):
|
||||
Only used with 'llama3'. Scaling factor applied to high frequency components of the RoPE
|
||||
attention_bias (`bool`, defaults to `False`, *optional*, defaults to `False`):
|
||||
Whether to use a bias in the query, key, value and output projection layers during self-attention.
|
||||
attention_dropout (`float`, *optional*, defaults to 0.0):
|
||||
The dropout ratio for the attention probabilities.
|
||||
|
||||
```python
|
||||
>>> from transformers import Qwen3VLTextModel, Qwen3VLTextConfig
|
||||
|
||||
>>> # Initializing a Qwen3VL style configuration
|
||||
>>> configuration = Qwen3VLTextConfig()
|
||||
|
||||
>>> # Initializing a model from the Qwen3-VL-7B style configuration
|
||||
>>> model = Qwen3VLTextModel(configuration)
|
||||
|
||||
>>> # Accessing the model configuration
|
||||
>>> configuration = model.config
|
||||
```"""
|
||||
|
||||
model_type = "qwen3_vl_text"
|
||||
base_config_key = "text_config"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vocab_size=151936,
|
||||
hidden_size=4096,
|
||||
intermediate_size=22016,
|
||||
num_hidden_layers=32,
|
||||
num_attention_heads=32,
|
||||
num_key_value_heads=32,
|
||||
head_dim=128,
|
||||
hidden_act="silu",
|
||||
max_position_embeddings=128000,
|
||||
initializer_range=0.02,
|
||||
rms_norm_eps=1e-6,
|
||||
use_cache=True,
|
||||
tie_word_embeddings=False,
|
||||
rope_theta=5000000.0,
|
||||
rope_scaling=None,
|
||||
attention_bias=False,
|
||||
attention_dropout=0.0,
|
||||
**kwargs,
|
||||
):
|
||||
self.vocab_size = vocab_size
|
||||
self.max_position_embeddings = max_position_embeddings
|
||||
self.hidden_size = hidden_size
|
||||
self.intermediate_size = intermediate_size
|
||||
self.num_hidden_layers = num_hidden_layers
|
||||
self.num_attention_heads = num_attention_heads
|
||||
|
||||
# for backward compatibility
|
||||
if num_key_value_heads is None:
|
||||
num_key_value_heads = num_attention_heads
|
||||
|
||||
self.num_key_value_heads = num_key_value_heads
|
||||
self.head_dim = head_dim
|
||||
self.hidden_act = hidden_act
|
||||
self.initializer_range = initializer_range
|
||||
self.rms_norm_eps = rms_norm_eps
|
||||
self.use_cache = use_cache
|
||||
self.rope_theta = rope_theta
|
||||
self.rope_scaling = rope_scaling
|
||||
self.attention_bias = attention_bias
|
||||
self.attention_dropout = attention_dropout
|
||||
|
||||
super().__init__(tie_word_embeddings=tie_word_embeddings, **kwargs)
|
||||
|
||||
|
||||
class Qwen3VLConfig(PretrainedConfig):
|
||||
r"""
|
||||
This is the configuration class to store the configuration of a [`Qwen3VLModel`]. It is used to instantiate a
|
||||
Qwen3-VL model according to the specified arguments, defining the model architecture. Instantiating a configuration
|
||||
with the defaults will yield a similar configuration to that of
|
||||
Qwen3-VL-4B-Instruct [Qwen/Qwen3-VL-4B-Instruct](https://huggingface.co/Qwen/Qwen3-VL-4B-Instruct).
|
||||
|
||||
Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the
|
||||
documentation from [`PretrainedConfig`] for more information.
|
||||
|
||||
|
||||
Args:
|
||||
text_config (`Union[PreTrainedConfig, dict]`, *optional*, defaults to `Qwen3VLTextConfig`):
|
||||
The config object or dictionary of the text backbone.
|
||||
vision_config (`Union[PreTrainedConfig, dict]`, *optional*, defaults to `Qwen3VLVisionConfig`):
|
||||
The config object or dictionary of the vision backbone.
|
||||
image_token_id (`int`, *optional*, defaults to 151655):
|
||||
The image token index to encode the image prompt.
|
||||
video_token_id (`int`, *optional*, defaults to 151656):
|
||||
The video token index to encode the image prompt.
|
||||
vision_start_token_id (`int`, *optional*, defaults to 151652):
|
||||
The start token index to encode the image prompt.
|
||||
vision_end_token_id (`int`, *optional*, defaults to 151653):
|
||||
The end token index to encode the image prompt.
|
||||
tie_word_embeddings (`bool`, *optional*, defaults to `False`):
|
||||
Whether to tie the word embeddings.
|
||||
|
||||
```python
|
||||
>>> from transformers import Qwen3VLForConditionalGeneration, Qwen3VLConfig
|
||||
|
||||
>>> # Initializing a Qwen3-VL style configuration
|
||||
>>> configuration = Qwen3VLConfig()
|
||||
|
||||
>>> # Initializing a model from the Qwen3-VL-4B style configuration
|
||||
>>> model = Qwen3VLForConditionalGeneration(configuration)
|
||||
|
||||
>>> # Accessing the model configuration
|
||||
>>> configuration = model.config
|
||||
```"""
|
||||
|
||||
model_type = "qwen3_vl"
|
||||
sub_configs = {
|
||||
"vision_config": Qwen3VLVisionConfig,
|
||||
"text_config": Qwen3VLTextConfig,
|
||||
}
|
||||
keys_to_ignore_at_inference = ["past_key_values"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
text_config=None,
|
||||
vision_config=None,
|
||||
image_token_id=151655,
|
||||
video_token_id=151656,
|
||||
vision_start_token_id=151652,
|
||||
vision_end_token_id=151653,
|
||||
tie_word_embeddings=False,
|
||||
**kwargs,
|
||||
):
|
||||
if isinstance(vision_config, dict):
|
||||
self.vision_config = self.sub_configs["vision_config"](**vision_config)
|
||||
elif vision_config is None:
|
||||
self.vision_config = self.sub_configs["vision_config"]()
|
||||
|
||||
if isinstance(text_config, dict):
|
||||
self.text_config = self.sub_configs["text_config"](**text_config)
|
||||
elif text_config is None:
|
||||
self.text_config = self.sub_configs["text_config"]()
|
||||
|
||||
self.image_token_id = image_token_id
|
||||
self.video_token_id = video_token_id
|
||||
self.vision_start_token_id = vision_start_token_id
|
||||
self.vision_end_token_id = vision_end_token_id
|
||||
super().__init__(**kwargs, tie_word_embeddings=tie_word_embeddings)
|
||||
|
||||
|
||||
class Qwen3VLMoeTextConfig(PretrainedConfig):
|
||||
r"""
|
||||
This is the configuration class to store the configuration of a [`Qwen3VLMoeTextModel`]. It is used to instantiate a
|
||||
Qwen3-VL-MOE model according to the specified arguments, defining the model architecture. Instantiating a configuration
|
||||
with the defaults will yield a similar configuration to that of
|
||||
Qwen3-VL-30B-A3B-Instruct [Qwen/Qwen3-VL-30B-A3B-Instruct](https://huggingface.co/Qwen/Qwen3-VL-30B-A3B-Instruct).
|
||||
|
||||
Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the
|
||||
documentation from [`PretrainedConfig`] for more information.
|
||||
|
||||
Args:
|
||||
vocab_size (`int`, *optional*, defaults to 151936):
|
||||
Vocabulary size of the Qwen2MoE model. Defines the number of different tokens that can be represented by the
|
||||
`inputs_ids` passed when calling [`Qwen2MoeModel`]
|
||||
hidden_size (`int`, *optional*, defaults to 2048):
|
||||
Dimension of the hidden representations.
|
||||
intermediate_size (`int`, *optional*, defaults to 5632):
|
||||
Dimension of the MLP representations.
|
||||
num_hidden_layers (`int`, *optional*, defaults to 24):
|
||||
Number of hidden layers in the Transformer encoder.
|
||||
num_attention_heads (`int`, *optional*, defaults to 16):
|
||||
Number of attention heads for each attention layer in the Transformer encoder.
|
||||
num_key_value_heads (`int`, *optional*, defaults to 16):
|
||||
This is the number of key_value heads that should be used to implement Grouped Query Attention. If
|
||||
`num_key_value_heads=num_attention_heads`, the model will use Multi Head Attention (MHA), if
|
||||
`num_key_value_heads=1` the model will use Multi Query Attention (MQA) otherwise GQA is used. When
|
||||
converting a multi-head checkpoint to a GQA checkpoint, each group key and value head should be constructed
|
||||
by meanpooling all the original heads within that group. For more details checkout [this
|
||||
paper](https://arxiv.org/pdf/2305.13245.pdf). If it is not specified, will default to `32`.
|
||||
hidden_act (`str` or `function`, *optional*, defaults to `"silu"`):
|
||||
The non-linear activation function (function or string) in the decoder.
|
||||
max_position_embeddings (`int`, *optional*, defaults to 128000):
|
||||
The maximum sequence length that this model might ever be used with.
|
||||
initializer_range (`float`, *optional*, defaults to 0.02):
|
||||
The standard deviation of the truncated_normal_initializer for initializing all weight matrices.
|
||||
rms_norm_eps (`float`, *optional*, defaults to 1e-06):
|
||||
The epsilon used by the rms normalization layers.
|
||||
use_cache (`bool`, *optional*, defaults to `True`):
|
||||
Whether or not the model should return the last key/values attentions (not used by all models). Only
|
||||
relevant if `config.is_decoder=True`.
|
||||
tie_word_embeddings (`bool`, *optional*, defaults to `False`):
|
||||
Whether the model's input and output word embeddings should be tied.
|
||||
rope_theta (`float`, *optional*, defaults to 5000000.0):
|
||||
The base period of the RoPE embeddings.
|
||||
attention_bias (`bool`, defaults to `False`, *optional*, defaults to `False`):
|
||||
Whether to use a bias in the query, key, value and output projection layers during self-attention.
|
||||
attention_dropout (`float`, *optional*, defaults to 0.0):
|
||||
The dropout ratio for the attention probabilities.
|
||||
decoder_sparse_step (`int`, *optional*, defaults to 1):
|
||||
The frequency of the MoE layer.
|
||||
moe_intermediate_size (`int`, *optional*, defaults to 1408):
|
||||
Intermediate size of the routed expert.
|
||||
num_experts_per_tok (`int`, *optional*, defaults to 4):
|
||||
Number of selected experts.
|
||||
num_experts (`int`, *optional*, defaults to 60):
|
||||
Number of routed experts.
|
||||
norm_topk_prob (`bool`, *optional*, defaults to `True`):
|
||||
Whether to normalize the topk probabilities.
|
||||
mlp_only_layers (`List[int]`, *optional*, defaults to `[]`):
|
||||
Indicate which layers use Qwen3VLMoeMLP rather than Qwen3VLMoeSparseMoeBlock
|
||||
The list contains layer index, from 0 to num_layers-1 if we have num_layers layers
|
||||
If `mlp_only_layers` is empty, `decoder_sparse_step` is used to determine the sparsity.
|
||||
rope_scaling (`Dict`, *optional*):
|
||||
Dictionary containing the scaling configuration for the RoPE embeddings. NOTE: if you apply new rope type
|
||||
and you expect the model to work on longer `max_position_embeddings`, we recommend you to update this value
|
||||
accordingly.
|
||||
Expected contents:
|
||||
`rope_type` (`str`):
|
||||
The sub-variant of RoPE to use. Can be one of ['default', 'linear', 'dynamic', 'yarn', 'longrope',
|
||||
'llama3'], with 'default' being the original RoPE implementation.
|
||||
`factor` (`float`, *optional*):
|
||||
Used with all rope types except 'default'. The scaling factor to apply to the RoPE embeddings. In
|
||||
most scaling types, a `factor` of x will enable the model to handle sequences of length x *
|
||||
original maximum pre-trained length.
|
||||
`original_max_position_embeddings` (`int`, *optional*):
|
||||
Used with 'dynamic', 'longrope' and 'llama3'. The original max position embeddings used during
|
||||
pretraining.
|
||||
`attention_factor` (`float`, *optional*):
|
||||
Used with 'yarn' and 'longrope'. The scaling factor to be applied on the attention
|
||||
computation. If unspecified, it defaults to value recommended by the implementation, using the
|
||||
`factor` field to infer the suggested value.
|
||||
`beta_fast` (`float`, *optional*):
|
||||
Only used with 'yarn'. Parameter to set the boundary for extrapolation (only) in the linear
|
||||
ramp function. If unspecified, it defaults to 32.
|
||||
`beta_slow` (`float`, *optional*):
|
||||
Only used with 'yarn'. Parameter to set the boundary for interpolation (only) in the linear
|
||||
ramp function. If unspecified, it defaults to 1.
|
||||
`short_factor` (`List[float]`, *optional*):
|
||||
Only used with 'longrope'. The scaling factor to be applied to short contexts (<
|
||||
`original_max_position_embeddings`). Must be a list of numbers with the same length as the hidden
|
||||
size divided by the number of attention heads divided by 2
|
||||
`long_factor` (`List[float]`, *optional*):
|
||||
Only used with 'longrope'. The scaling factor to be applied to long contexts (<
|
||||
`original_max_position_embeddings`). Must be a list of numbers with the same length as the hidden
|
||||
size divided by the number of attention heads divided by 2
|
||||
`low_freq_factor` (`float`, *optional*):
|
||||
Only used with 'llama3'. Scaling factor applied to low frequency components of the RoPE
|
||||
`high_freq_factor` (`float`, *optional*):
|
||||
Only used with 'llama3'. Scaling factor applied to high frequency components of the RoPE
|
||||
head_dim (`int`, *optional*):
|
||||
The dimension of the head. If not specified, will default to `hidden_size // num_attention_heads`.
|
||||
|
||||
```python
|
||||
>>> from transformers import Qwen3VLMoeForConditionalGeneration, Qwen3VLMoeConfig
|
||||
|
||||
>>> # Initializing a Qwen3VLMoe style configuration
|
||||
>>> configuration = Qwen3VLMoeConfig()
|
||||
|
||||
>>> # Initializing a model from the Qwen3-VL-30B-A3B style configuration
|
||||
>>> model = Qwen3VLMoeForConditionalGeneration(configuration)
|
||||
|
||||
>>> # Accessing the model configuration
|
||||
>>> configuration = model.config
|
||||
```"""
|
||||
|
||||
model_type = "qwen3_vl_moe_text"
|
||||
base_config_key = "text_config"
|
||||
keys_to_ignore_at_inference = ["past_key_values"]
|
||||
# Default tensor parallel plan for base model `Qwen3VLMoe`
|
||||
base_model_tp_plan = {
|
||||
"layers.*.self_attn.q_proj": "colwise",
|
||||
"layers.*.self_attn.k_proj": "colwise",
|
||||
"layers.*.self_attn.v_proj": "colwise",
|
||||
"layers.*.self_attn.o_proj": "rowwise",
|
||||
"layers.*.mlp.gate_proj": "colwise",
|
||||
"layers.*.mlp.up_proj": "colwise",
|
||||
"layers.*.mlp.down_proj": "rowwise",
|
||||
}
|
||||
base_model_pp_plan = {
|
||||
"embed_tokens": (["input_ids"], ["inputs_embeds"]),
|
||||
"layers": (["hidden_states", "attention_mask"], ["hidden_states"]),
|
||||
"norm": (["hidden_states"], ["hidden_states"]),
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vocab_size=151936,
|
||||
hidden_size=2048,
|
||||
intermediate_size=5632,
|
||||
num_hidden_layers=24,
|
||||
num_attention_heads=16,
|
||||
num_key_value_heads=16,
|
||||
hidden_act="silu",
|
||||
max_position_embeddings=128000,
|
||||
initializer_range=0.02,
|
||||
rms_norm_eps=1e-6,
|
||||
use_cache=True,
|
||||
tie_word_embeddings=False,
|
||||
rope_theta=5000000.0,
|
||||
attention_bias=False,
|
||||
attention_dropout=0.0,
|
||||
decoder_sparse_step=1,
|
||||
moe_intermediate_size=1408,
|
||||
num_experts_per_tok=4,
|
||||
num_experts=60,
|
||||
norm_topk_prob=True,
|
||||
mlp_only_layers=None,
|
||||
rope_scaling=None,
|
||||
head_dim=None,
|
||||
**kwargs,
|
||||
):
|
||||
self.vocab_size = vocab_size
|
||||
self.max_position_embeddings = max_position_embeddings
|
||||
self.hidden_size = hidden_size
|
||||
self.intermediate_size = intermediate_size
|
||||
self.num_hidden_layers = num_hidden_layers
|
||||
self.num_attention_heads = num_attention_heads
|
||||
|
||||
# for backward compatibility
|
||||
if num_key_value_heads is None:
|
||||
num_key_value_heads = num_attention_heads
|
||||
|
||||
self.num_key_value_heads = num_key_value_heads
|
||||
self.hidden_act = hidden_act
|
||||
self.initializer_range = initializer_range
|
||||
self.rms_norm_eps = rms_norm_eps
|
||||
self.use_cache = use_cache
|
||||
self.rope_theta = rope_theta
|
||||
self.attention_bias = attention_bias
|
||||
self.attention_dropout = attention_dropout
|
||||
self.rope_scaling = rope_scaling
|
||||
self.head_dim = head_dim or hidden_size // num_attention_heads
|
||||
|
||||
# MoE arguments
|
||||
self.decoder_sparse_step = decoder_sparse_step
|
||||
self.moe_intermediate_size = moe_intermediate_size
|
||||
self.num_experts_per_tok = num_experts_per_tok
|
||||
self.num_experts = num_experts
|
||||
self.norm_topk_prob = norm_topk_prob
|
||||
self.mlp_only_layers = [] if mlp_only_layers is None else mlp_only_layers
|
||||
|
||||
super().__init__(tie_word_embeddings=tie_word_embeddings, **kwargs)
|
||||
|
||||
|
||||
class Qwen3VLMoeVisionConfig(PretrainedConfig):
|
||||
model_type = "qwen3_vl_moe"
|
||||
base_config_key = "vision_config"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
depth=27,
|
||||
hidden_size=1152,
|
||||
hidden_act="gelu_pytorch_tanh",
|
||||
intermediate_size=4304,
|
||||
num_heads=16,
|
||||
in_channels=3,
|
||||
patch_size=16,
|
||||
spatial_merge_size=2,
|
||||
temporal_patch_size=2,
|
||||
out_hidden_size=3584,
|
||||
num_position_embeddings=2304,
|
||||
deepstack_visual_indexes=[8, 16, 24],
|
||||
initializer_range=0.02,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.depth = depth
|
||||
self.hidden_size = hidden_size
|
||||
self.hidden_act = hidden_act
|
||||
self.intermediate_size = intermediate_size
|
||||
self.num_heads = num_heads
|
||||
self.in_channels = in_channels
|
||||
self.patch_size = patch_size
|
||||
self.spatial_merge_size = spatial_merge_size
|
||||
self.temporal_patch_size = temporal_patch_size
|
||||
self.out_hidden_size = out_hidden_size
|
||||
self.num_position_embeddings = num_position_embeddings
|
||||
self.initializer_range = initializer_range
|
||||
self.deepstack_visual_indexes = deepstack_visual_indexes
|
||||
|
||||
|
||||
class Qwen3VLMoeConfig(PretrainedConfig):
|
||||
r"""
|
||||
This is the configuration class to store the configuration of a [`Qwen3VLMoeModel`]. It is used to instantiate a
|
||||
Qwen3-VL-MOE model according to the specified arguments, defining the model architecture. Instantiating a configuration
|
||||
with the defaults will yield a similar configuration to that of
|
||||
Qwen3-VL-30B-A3B-Instruct [Qwen/Qwen3-VL-30B-A3B-Instruct](https://huggingface.co/Qwen/Qwen3-VL-30B-A3B-Instruct).
|
||||
|
||||
Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the
|
||||
documentation from [`PretrainedConfig`] for more information.
|
||||
|
||||
|
||||
Args:
|
||||
text_config (`Union[PreTrainedConfig, dict]`, *optional*, defaults to `Qwen3VLMoeTextConfig`):
|
||||
The config object or dictionary of the text backbone.
|
||||
vision_config (`Union[PreTrainedConfig, dict]`, *optional*, defaults to `Qwen3VLMoeVisionConfig`):
|
||||
The config object or dictionary of the vision backbone.
|
||||
image_token_id (`int`, *optional*, defaults to 151655):
|
||||
The image token index to encode the image prompt.
|
||||
video_token_id (`int`, *optional*, defaults to 151656):
|
||||
The video token index to encode the image prompt.
|
||||
vision_start_token_id (`int`, *optional*, defaults to 151652):
|
||||
The start token index to encode the image prompt.
|
||||
vision_end_token_id (`int`, *optional*, defaults to 151653):
|
||||
The end token index to encode the image prompt.
|
||||
tie_word_embeddings (`bool`, *optional*, defaults to `False`):
|
||||
Whether to tie the word embeddings.
|
||||
|
||||
```python
|
||||
>>> from transformers import Qwen3VLMoeForConditionalGeneration, Qwen3VLMoeConfig
|
||||
|
||||
>>> # Initializing a Qwen3-VL-MOE style configuration
|
||||
>>> configuration = Qwen3VLMoeConfig()
|
||||
|
||||
>>> # Initializing a model from the Qwen3-VL-30B-A3B style configuration
|
||||
>>> model = Qwen3VLMoeForConditionalGeneration(configuration)
|
||||
|
||||
>>> # Accessing the model configuration
|
||||
>>> configuration = model.config
|
||||
```"""
|
||||
|
||||
model_type = "qwen3_vl_moe"
|
||||
sub_configs = {
|
||||
"vision_config": Qwen3VLMoeVisionConfig,
|
||||
"text_config": Qwen3VLMoeTextConfig,
|
||||
}
|
||||
keys_to_ignore_at_inference = ["past_key_values"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
text_config=None,
|
||||
vision_config=None,
|
||||
image_token_id=151655,
|
||||
video_token_id=151656,
|
||||
vision_start_token_id=151652,
|
||||
vision_end_token_id=151653,
|
||||
tie_word_embeddings=False,
|
||||
**kwargs,
|
||||
):
|
||||
if isinstance(vision_config, dict):
|
||||
self.vision_config = self.sub_configs["vision_config"](**vision_config)
|
||||
elif vision_config is None:
|
||||
self.vision_config = self.sub_configs["vision_config"]()
|
||||
|
||||
if isinstance(text_config, dict):
|
||||
self.text_config = self.sub_configs["text_config"](**text_config)
|
||||
elif text_config is None:
|
||||
self.text_config = self.sub_configs["text_config"]()
|
||||
|
||||
self.image_token_id = image_token_id
|
||||
self.video_token_id = video_token_id
|
||||
self.vision_start_token_id = vision_start_token_id
|
||||
self.vision_end_token_id = vision_end_token_id
|
||||
super().__init__(**kwargs, tie_word_embeddings=tie_word_embeddings)
|
||||
106
third_party/sglang/python/sglang/srt/configs/radio.py
vendored
Normal file
106
third_party/sglang/python/sglang/srt/configs/radio.py
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
# Copyright 2025 SGLang Team
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ==============================================================================
|
||||
# Adapted from https://github.com/vllm-project/vllm/blob/main/vllm/transformers_utils/configs/radio.py
|
||||
|
||||
"""Radio vision model configuration"""
|
||||
|
||||
from transformers.configuration_utils import PretrainedConfig
|
||||
from transformers.utils import logging
|
||||
|
||||
logger = logging.get_logger(__name__)
|
||||
|
||||
VIT_TIMM_DIM_BY_NAME: dict[str, tuple[int, int, int, int]] = {
|
||||
"vit_small_patch16_224": (384, 12, 6, 1536),
|
||||
"vit_base_patch16_224": (768, 12, 12, 3072),
|
||||
"vit_large_patch16_224": (1024, 24, 16, 4096),
|
||||
"vit_huge_patch16_224": (1280, 32, 16, 5120),
|
||||
}
|
||||
|
||||
OPENAI_CLIP_MEAN = (0.48145466, 0.4578275, 0.40821073)
|
||||
OPENAI_CLIP_STD = (0.26862954, 0.26130258, 0.27577711)
|
||||
|
||||
|
||||
class RadioConfig(PretrainedConfig):
|
||||
r"""
|
||||
This is the configuration class to store the configuration of a Radio
|
||||
vision model. It is used to instantiate a Radio model according to the
|
||||
specified arguments, defining the model architecture.
|
||||
|
||||
Args:
|
||||
model_name: Name of the vision transformer model
|
||||
(e.g., "vit_base_patch16_224"). Used to determine architecture
|
||||
dimensions from `VIT_TIMM_DIM_BY_NAME`.
|
||||
image_size: The size (resolution) of each image.
|
||||
patch_size: The size (resolution) of each patch.
|
||||
qkv_bias: Whether to add a bias to the queries, keys and values.
|
||||
qk_normalization: Whether to apply normalization to queries and keys.
|
||||
norm_type: The normalization type to use.
|
||||
layer_norm_eps: The epsilon used by the layer normalization layers.
|
||||
initializer_factor: A factor for initializing all weight matrices.
|
||||
hidden_act: The non-linear activation function in the encoder.
|
||||
max_img_size: Maximum image size for position embeddings.
|
||||
norm_mean: Mean values for image normalization (RGB channels).
|
||||
Defaults to (0.48145466, 0.4578275, 0.40821073)).
|
||||
norm_std: Standard deviation values for image normalization
|
||||
(RGB channels). Defaults to (0.26862954, 0.26130258, 0.27577711)).
|
||||
reg_tokens: Number of register tokens to use.
|
||||
"""
|
||||
|
||||
model_type = "radio"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
model_name: str,
|
||||
image_size: int = 224,
|
||||
patch_size: int = 16,
|
||||
qkv_bias: bool = True,
|
||||
qk_normalization: bool = False,
|
||||
norm_type: str = "layer_norm",
|
||||
layer_norm_eps: float = 1e-6,
|
||||
initializer_factor: float = 1.0,
|
||||
hidden_act: str = "gelu",
|
||||
max_img_size: int = 2048,
|
||||
norm_mean: tuple[float, float, float] | list = OPENAI_CLIP_MEAN,
|
||||
norm_std: tuple[float, float, float] | list = OPENAI_CLIP_STD,
|
||||
reg_tokens: int | None = None,
|
||||
drop_path_rate: float = 0.0,
|
||||
dropout: float = 0.0,
|
||||
**kwargs,
|
||||
):
|
||||
self.model_name = model_name
|
||||
(
|
||||
self.hidden_size,
|
||||
self.num_hidden_layers,
|
||||
self.num_attention_heads,
|
||||
self.intermediate_size,
|
||||
) = VIT_TIMM_DIM_BY_NAME[model_name]
|
||||
self.image_size = image_size
|
||||
self.patch_size = patch_size
|
||||
self.qkv_bias = qkv_bias
|
||||
self.qk_normalization = qk_normalization
|
||||
self.norm_type = norm_type
|
||||
self.layer_norm_eps = layer_norm_eps
|
||||
self.initializer_factor = initializer_factor
|
||||
self.hidden_act = hidden_act
|
||||
self.max_img_size = max_img_size
|
||||
self.norm_mean = (
|
||||
list(norm_mean) if isinstance(norm_mean, (tuple, list)) else norm_mean
|
||||
)
|
||||
self.norm_std = (
|
||||
list(norm_std) if isinstance(norm_std, (tuple, list)) else norm_std
|
||||
)
|
||||
self.reg_tokens = reg_tokens
|
||||
self.drop_path_rate = drop_path_rate
|
||||
self.dropout = dropout
|
||||
super().__init__(**kwargs)
|
||||
172
third_party/sglang/python/sglang/srt/configs/step3_vl.py
vendored
Normal file
172
third_party/sglang/python/sglang/srt/configs/step3_vl.py
vendored
Normal file
@@ -0,0 +1,172 @@
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
from transformers.configuration_utils import PretrainedConfig
|
||||
|
||||
|
||||
class Step3VisionEncoderConfig(PretrainedConfig):
|
||||
model_type = "step3_vision_encoder"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hidden_size=1792,
|
||||
intermediate_size=3072,
|
||||
output_hidden_size=4096,
|
||||
num_hidden_layers=63,
|
||||
num_attention_heads=16,
|
||||
num_channels=3,
|
||||
image_size=728,
|
||||
patch_size=14,
|
||||
hidden_act="quick_gelu",
|
||||
layer_norm_eps=1e-5,
|
||||
**kwargs,
|
||||
):
|
||||
self.hidden_size = hidden_size
|
||||
self.intermediate_size = intermediate_size
|
||||
self.output_hidden_size = output_hidden_size
|
||||
self.num_hidden_layers = num_hidden_layers
|
||||
self.num_attention_heads = num_attention_heads
|
||||
self.num_channels = num_channels
|
||||
self.patch_size = patch_size
|
||||
self.image_size = image_size
|
||||
self.layer_norm_eps = layer_norm_eps
|
||||
self.hidden_act = hidden_act
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
class Step3TextConfig(PretrainedConfig):
|
||||
model_type = "step3_text"
|
||||
architectures = ["Step3TextForCausalLM"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hidden_size: int = 7168,
|
||||
intermediate_size: int = 18432,
|
||||
num_attention_heads: int = 64,
|
||||
num_attention_groups: int = 1,
|
||||
num_hidden_layers: int = 61,
|
||||
max_seq_len: int = 65536,
|
||||
vocab_size: int = 128815,
|
||||
rms_norm_eps: float = 1e-5,
|
||||
moe_intermediate_size: int = 5120,
|
||||
moe_num_experts: int = 48,
|
||||
moe_top_k: int = 3,
|
||||
rope_theta: float = 500000,
|
||||
rope_scaling: Optional[dict[str, Any]] = None,
|
||||
max_position_embedding: int = 65536,
|
||||
share_expert_dim: int = 5120,
|
||||
share_q_dim: int = 2048,
|
||||
head_dim: int = 256,
|
||||
norm_expert_weight: bool = False,
|
||||
moe_layers_enum: tuple[int] = (
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26,
|
||||
27,
|
||||
28,
|
||||
29,
|
||||
30,
|
||||
31,
|
||||
32,
|
||||
33,
|
||||
34,
|
||||
35,
|
||||
36,
|
||||
37,
|
||||
38,
|
||||
39,
|
||||
40,
|
||||
41,
|
||||
42,
|
||||
43,
|
||||
44,
|
||||
45,
|
||||
46,
|
||||
47,
|
||||
48,
|
||||
49,
|
||||
50,
|
||||
51,
|
||||
52,
|
||||
53,
|
||||
54,
|
||||
55,
|
||||
56,
|
||||
57,
|
||||
58,
|
||||
59,
|
||||
),
|
||||
**kwargs,
|
||||
) -> None:
|
||||
self.hidden_size = hidden_size
|
||||
self.intermediate_size = intermediate_size
|
||||
self.num_attention_heads = num_attention_heads
|
||||
self.num_attention_groups = num_attention_groups
|
||||
self.num_hidden_layers = num_hidden_layers
|
||||
self.max_seq_len = max_seq_len
|
||||
self.vocab_size = vocab_size
|
||||
self.rms_norm_eps = rms_norm_eps
|
||||
self.moe_intermediate_size = moe_intermediate_size
|
||||
self.moe_num_experts = moe_num_experts
|
||||
self.moe_top_k = moe_top_k
|
||||
self.rope_theta = rope_theta
|
||||
self.rope_scaling = rope_scaling
|
||||
self.max_position_embedding = max_position_embedding
|
||||
self.share_expert_dim = share_expert_dim
|
||||
self.share_q_dim = share_q_dim
|
||||
self.head_dim = head_dim
|
||||
self.norm_expert_weight = norm_expert_weight
|
||||
self.moe_layers_enum = moe_layers_enum
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
class Step3VLConfig(PretrainedConfig):
|
||||
model_type = "step3_vl"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vision_config: Optional[Union[dict, Step3VisionEncoderConfig]] = None,
|
||||
text_config: Optional[Union[dict, Step3TextConfig]] = None,
|
||||
understand_projector_stride: int = 1,
|
||||
projector_bias: bool = True,
|
||||
image_token_id: int = 128001,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
if vision_config is None:
|
||||
vision_config = Step3VisionEncoderConfig()
|
||||
elif isinstance(vision_config, dict):
|
||||
vision_config = Step3VisionEncoderConfig(**vision_config)
|
||||
self.vision_config = vision_config
|
||||
|
||||
if text_config is None:
|
||||
text_config = Step3TextConfig()
|
||||
elif isinstance(text_config, dict):
|
||||
text_config = Step3TextConfig(**text_config)
|
||||
self.text_config = text_config
|
||||
|
||||
self.understand_projector_stride = understand_projector_stride
|
||||
self.projector_bias = projector_bias
|
||||
self.hidden_size = text_config.hidden_size
|
||||
self.image_token_id = image_token_id
|
||||
|
||||
super().__init__(**kwargs)
|
||||
97
third_party/sglang/python/sglang/srt/configs/step3p5.py
vendored
Normal file
97
third_party/sglang/python/sglang/srt/configs/step3p5.py
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
from typing import Any, Optional
|
||||
|
||||
from transformers.configuration_utils import PretrainedConfig
|
||||
|
||||
|
||||
class Step3p5Config(PretrainedConfig):
|
||||
model_type = "step3p5"
|
||||
architectures = ["Step3p5ForCausalLM"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hidden_size: int = 4096,
|
||||
intermediate_size: int = 11264,
|
||||
num_attention_heads: int = 64,
|
||||
num_attention_groups: int = 8,
|
||||
num_hidden_layers: int = 45,
|
||||
max_seq_len: int = 128000,
|
||||
vocab_size: int = 128815,
|
||||
rms_norm_eps: float = 1e-5,
|
||||
moe_intermediate_size: int = 1280,
|
||||
moe_num_experts: int = 288,
|
||||
moe_top_k: int = 8,
|
||||
rope_theta: float = 10000,
|
||||
rope_scaling: Optional[dict[str, Any]] = None,
|
||||
max_position_embeddings: int = 128000,
|
||||
share_expert_dims: int = 1280,
|
||||
head_dim: int = 128,
|
||||
norm_expert_weight: bool = True,
|
||||
layer_types: list[str] = None,
|
||||
sliding_window: Optional[int] = None,
|
||||
moe_layers_enum: tuple[int] = (
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26,
|
||||
27,
|
||||
28,
|
||||
29,
|
||||
30,
|
||||
31,
|
||||
32,
|
||||
33,
|
||||
34,
|
||||
35,
|
||||
36,
|
||||
37,
|
||||
38,
|
||||
39,
|
||||
40,
|
||||
41,
|
||||
42,
|
||||
43,
|
||||
44,
|
||||
),
|
||||
**kwargs,
|
||||
) -> None:
|
||||
self.hidden_size = hidden_size
|
||||
self.intermediate_size = intermediate_size
|
||||
self.num_attention_heads = num_attention_heads
|
||||
self.num_attention_groups = num_attention_groups
|
||||
self.num_hidden_layers = num_hidden_layers
|
||||
self.max_seq_len = max_seq_len
|
||||
self.vocab_size = vocab_size
|
||||
self.rms_norm_eps = rms_norm_eps
|
||||
self.moe_intermediate_size = moe_intermediate_size
|
||||
self.moe_num_experts = moe_num_experts
|
||||
self.moe_top_k = moe_top_k
|
||||
self.rope_theta = rope_theta
|
||||
self.rope_scaling = rope_scaling
|
||||
self.max_position_embeddings = max_position_embeddings
|
||||
self.share_expert_dim = share_expert_dims
|
||||
self.head_dim = head_dim
|
||||
self.norm_expert_weight = norm_expert_weight
|
||||
self.moe_layers_enum = moe_layers_enum
|
||||
self.layer_types = layer_types
|
||||
self.sliding_window = sliding_window
|
||||
super().__init__(**kwargs)
|
||||
211
third_party/sglang/python/sglang/srt/configs/update_config.py
vendored
Normal file
211
third_party/sglang/python/sglang/srt/configs/update_config.py
vendored
Normal file
@@ -0,0 +1,211 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
DEFAULT_MOE_PADDING_SIZE = 32
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sglang.srt.configs.load_config import LoadConfig
|
||||
from sglang.srt.configs.model_config import ModelConfig
|
||||
|
||||
|
||||
def may_get_weight_block_size(model_config, load_config):
|
||||
from sglang.srt.model_loader.loader import _get_quantization_config
|
||||
|
||||
quant_config = _get_quantization_config(model_config, load_config)
|
||||
|
||||
if quant_config is not None and hasattr(quant_config, "weight_block_size"):
|
||||
return getattr(quant_config, "weight_block_size")
|
||||
return None
|
||||
|
||||
|
||||
def get_moe_padding_size(weight_block_size):
|
||||
if weight_block_size is not None:
|
||||
# See NOTE(HandH1998): To ensure proper alignment of the block-wise quantization scales, the output_size of the weights for both the gate and up layers must be divisible by block_n.
|
||||
assert (
|
||||
len(weight_block_size) == 2
|
||||
), "Only len(weight_block_size) == 2 is supported"
|
||||
assert (
|
||||
weight_block_size[0] == weight_block_size[1]
|
||||
), "Only weight_block_size[0] == weight_block_size[1] is supported"
|
||||
|
||||
return weight_block_size[0]
|
||||
|
||||
return DEFAULT_MOE_PADDING_SIZE
|
||||
|
||||
|
||||
def get_num_heads_padding_size(tp_size, weight_block_size, head_dim):
|
||||
pad_size = tp_size
|
||||
|
||||
if weight_block_size is not None and head_dim % weight_block_size[0] != 0:
|
||||
import math
|
||||
|
||||
pad_size = tp_size * (
|
||||
math.lcm(head_dim, weight_block_size[0]) // weight_block_size[0]
|
||||
)
|
||||
|
||||
return pad_size
|
||||
|
||||
|
||||
def adjust_tp_num_heads_if_necessary(model_config, tp_size, is_post_update):
|
||||
# is_post_update: whether to update an existing config
|
||||
from sglang.srt.layers.vocab_parallel_embedding import pad_vocab_size
|
||||
|
||||
# Linear attn check logic
|
||||
if hasattr(model_config, "linear_num_key_heads") and hasattr(
|
||||
model_config, "linear_num_value_heads"
|
||||
):
|
||||
if (
|
||||
model_config.linear_num_key_heads % tp_size != 0
|
||||
or model_config.linear_num_value_heads % tp_size != 0
|
||||
):
|
||||
pad_size = tp_size
|
||||
linear_num_key_heads_cpu = pad_vocab_size(
|
||||
model_config.linear_num_key_heads, pad_size
|
||||
)
|
||||
linear_num_value_heads_cpu = (
|
||||
linear_num_key_heads_cpu
|
||||
* model_config.linear_num_value_heads
|
||||
// model_config.linear_num_key_heads
|
||||
)
|
||||
if is_post_update:
|
||||
model_config.linear_num_key_heads_cpu = linear_num_key_heads_cpu
|
||||
model_config.linear_num_value_heads_cpu = linear_num_value_heads_cpu
|
||||
else:
|
||||
model_config.linear_num_key_heads = linear_num_key_heads_cpu
|
||||
model_config.linear_num_value_heads = linear_num_value_heads_cpu
|
||||
|
||||
else:
|
||||
if is_post_update:
|
||||
model_config.linear_num_key_heads_cpu = (
|
||||
model_config.linear_num_key_heads
|
||||
)
|
||||
model_config.linear_num_value_heads_cpu = (
|
||||
model_config.linear_num_value_heads
|
||||
)
|
||||
|
||||
|
||||
def update_intermediate_size(model_config, attr_name, intermediate_padding_size):
|
||||
attr_value = intermediate_padding_size
|
||||
if hasattr(model_config, "hf_config") and hasattr(
|
||||
model_config.hf_config, attr_name
|
||||
):
|
||||
attr_value = getattr(model_config.hf_config, attr_name)
|
||||
elif hasattr(model_config, attr_name):
|
||||
attr_value = getattr(model_config, attr_name)
|
||||
|
||||
if attr_value % intermediate_padding_size != 0:
|
||||
from sglang.srt.layers.vocab_parallel_embedding import pad_vocab_size
|
||||
|
||||
attr_value = pad_vocab_size(attr_value, intermediate_padding_size)
|
||||
if hasattr(model_config, "hf_config"):
|
||||
setattr(model_config.hf_config, attr_name, attr_value)
|
||||
if hasattr(model_config, "hf_text_config"):
|
||||
setattr(model_config.hf_text_config, attr_name, attr_value)
|
||||
else:
|
||||
setattr(model_config, attr_name, attr_value)
|
||||
|
||||
return model_config
|
||||
|
||||
|
||||
def adjust_config_with_unaligned_cpu_tp(
|
||||
model_config: ModelConfig, load_config: LoadConfig, tp_size: int
|
||||
) -> ModelConfig:
|
||||
# Support the case where the num_attention_heads is not divisible by the TP size.
|
||||
weight_block_size = may_get_weight_block_size(model_config, load_config)
|
||||
|
||||
model_config.hf_config.original_num_attention_heads = (
|
||||
model_config.num_attention_heads
|
||||
)
|
||||
model_config.hf_text_config.original_num_attention_heads = (
|
||||
model_config.num_attention_heads
|
||||
)
|
||||
|
||||
model_config.hf_config.original_total_num_kv_heads = (
|
||||
model_config.get_total_num_kv_heads()
|
||||
)
|
||||
model_config.hf_text_config.original_total_num_kv_heads = (
|
||||
model_config.get_total_num_kv_heads()
|
||||
)
|
||||
|
||||
if (
|
||||
model_config.num_attention_heads % tp_size != 0
|
||||
or model_config.get_total_num_kv_heads() % tp_size != 0
|
||||
):
|
||||
# Compute the head_dim using the model_config.num_attention_heads before padding
|
||||
if not hasattr(model_config.hf_config, "head_dim"):
|
||||
model_config.hf_config.head_dim = (
|
||||
model_config.hidden_size // model_config.num_attention_heads
|
||||
)
|
||||
if hasattr(model_config.hf_config, "qk_nope_head_dim") and hasattr(
|
||||
model_config.hf_config, "qk_rope_head_dim"
|
||||
):
|
||||
model_config.hf_config.qk_head_dim = (
|
||||
model_config.hf_config.qk_nope_head_dim
|
||||
+ model_config.hf_config.qk_rope_head_dim
|
||||
)
|
||||
|
||||
query_heads_per_kv = (
|
||||
model_config.num_attention_heads // model_config.get_total_num_kv_heads()
|
||||
)
|
||||
total_kv_heads = model_config.get_total_num_kv_heads()
|
||||
from sglang.srt.layers.vocab_parallel_embedding import pad_vocab_size
|
||||
|
||||
head_dim = (
|
||||
model_config.hf_config.qk_head_dim
|
||||
if hasattr(model_config.hf_config, "qk_head_dim")
|
||||
else model_config.hf_config.head_dim
|
||||
)
|
||||
pad_size = get_num_heads_padding_size(tp_size, weight_block_size, head_dim)
|
||||
num_key_value_heads = pad_vocab_size(total_kv_heads, pad_size)
|
||||
|
||||
model_config.num_key_value_heads = num_key_value_heads
|
||||
model_config.hf_config.num_key_value_heads = num_key_value_heads
|
||||
model_config.hf_text_config.num_key_value_heads = num_key_value_heads
|
||||
|
||||
num_attention_heads = num_key_value_heads * query_heads_per_kv
|
||||
model_config.num_attention_heads = num_attention_heads
|
||||
model_config.hf_config.num_attention_heads = num_attention_heads
|
||||
model_config.hf_text_config.num_attention_heads = num_attention_heads
|
||||
|
||||
adjust_tp_num_heads_if_necessary(model_config.hf_config, tp_size, True)
|
||||
|
||||
intermediate_padding_size = tp_size * get_moe_padding_size(weight_block_size)
|
||||
model_config = update_intermediate_size(
|
||||
model_config, "moe_intermediate_size", intermediate_padding_size
|
||||
)
|
||||
model_config = update_intermediate_size(
|
||||
model_config, "intermediate_size", intermediate_padding_size
|
||||
)
|
||||
model_config = update_intermediate_size(
|
||||
model_config, "intermediate_size_mlp", intermediate_padding_size
|
||||
)
|
||||
model_config = update_intermediate_size(
|
||||
model_config, "shared_expert_intermediate_size", intermediate_padding_size
|
||||
)
|
||||
if (
|
||||
hasattr(model_config.hf_config, "vision_config")
|
||||
and model_config.hf_config.vision_config.model_type == "siglip_vision_model"
|
||||
):
|
||||
model_config.hf_config.vision_config.original_num_attention_heads = (
|
||||
model_config.num_attention_heads
|
||||
)
|
||||
if model_config.hf_config.vision_config.num_attention_heads % tp_size != 0:
|
||||
model_config.hf_config.vision_config.head_dim = (
|
||||
model_config.hf_config.vision_config.hidden_size
|
||||
// model_config.hf_config.vision_config.num_attention_heads
|
||||
)
|
||||
from sglang.srt.layers.vocab_parallel_embedding import pad_vocab_size
|
||||
|
||||
pad_size = get_num_heads_padding_size(tp_size, weight_block_size)
|
||||
model_config.hf_config.vision_config.num_attention_heads = pad_vocab_size(
|
||||
model_config.hf_config.vision_config.num_attention_heads, pad_size
|
||||
)
|
||||
model_config.hf_config.vision_config = update_intermediate_size(
|
||||
model_config.hf_config.vision_config,
|
||||
"intermediate_size",
|
||||
intermediate_padding_size,
|
||||
)
|
||||
|
||||
return model_config
|
||||
27
third_party/sglang/python/sglang/srt/configs/utils.py
vendored
Normal file
27
third_party/sglang/python/sglang/srt/configs/utils.py
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
from typing import Type
|
||||
|
||||
from transformers import (
|
||||
AutoImageProcessor,
|
||||
AutoProcessor,
|
||||
BaseImageProcessor,
|
||||
PretrainedConfig,
|
||||
ProcessorMixin,
|
||||
)
|
||||
|
||||
|
||||
def register_image_processor(
|
||||
config: Type[PretrainedConfig], image_processor: Type[BaseImageProcessor]
|
||||
):
|
||||
"""
|
||||
register customized hf image processor while removing hf impl
|
||||
"""
|
||||
AutoImageProcessor.register(
|
||||
config, slow_image_processor_class=image_processor, exist_ok=True
|
||||
)
|
||||
|
||||
|
||||
def register_processor(config: Type[PretrainedConfig], processor: Type[ProcessorMixin]):
|
||||
"""
|
||||
register customized hf processor while removing hf impl
|
||||
"""
|
||||
AutoProcessor.register(config, processor, exist_ok=True)
|
||||
58
third_party/sglang/python/sglang/srt/connector/__init__.py
vendored
Normal file
58
third_party/sglang/python/sglang/srt/connector/__init__.py
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import enum
|
||||
import logging
|
||||
|
||||
from sglang.srt.connector.base_connector import (
|
||||
BaseConnector,
|
||||
BaseFileConnector,
|
||||
BaseKVConnector,
|
||||
)
|
||||
from sglang.srt.connector.redis import RedisConnector
|
||||
from sglang.srt.connector.remote_instance import RemoteInstanceConnector
|
||||
from sglang.srt.connector.s3 import S3Connector
|
||||
from sglang.srt.utils import parse_connector_type
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ConnectorType(str, enum.Enum):
|
||||
FS = "filesystem"
|
||||
KV = "KV"
|
||||
INSTANCE = "instance"
|
||||
|
||||
|
||||
def create_remote_connector(url, device=None, **kwargs) -> BaseConnector:
|
||||
connector_type = parse_connector_type(url)
|
||||
if connector_type == "redis":
|
||||
return RedisConnector(url)
|
||||
elif connector_type == "s3":
|
||||
return S3Connector(url)
|
||||
elif connector_type == "instance":
|
||||
return RemoteInstanceConnector(url, device)
|
||||
else:
|
||||
raise ValueError(f"Invalid connector type: {url}")
|
||||
|
||||
|
||||
def get_connector_type(client: BaseConnector) -> ConnectorType:
|
||||
if isinstance(client, BaseKVConnector):
|
||||
return ConnectorType.KV
|
||||
if isinstance(client, BaseFileConnector):
|
||||
return ConnectorType.FS
|
||||
if isinstance(client, RemoteInstanceConnector):
|
||||
return ConnectorType.INSTANCE
|
||||
|
||||
raise ValueError(f"Invalid connector type: {client}")
|
||||
|
||||
|
||||
__all__ = [
|
||||
"BaseConnector",
|
||||
"BaseFileConnector",
|
||||
"BaseKVConnector",
|
||||
"RedisConnector",
|
||||
"RemoteInstanceConnector",
|
||||
"S3Connector",
|
||||
"ConnectorType",
|
||||
"create_remote_connector",
|
||||
"get_connector_type",
|
||||
]
|
||||
111
third_party/sglang/python/sglang/srt/connector/base_connector.py
vendored
Normal file
111
third_party/sglang/python/sglang/srt/connector/base_connector.py
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import signal
|
||||
import tempfile
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Generator, List, Optional, Tuple
|
||||
|
||||
import torch
|
||||
|
||||
|
||||
class BaseConnector(ABC):
|
||||
"""
|
||||
For fs connector such as s3:
|
||||
<connector_type>://<path>/<filename>
|
||||
|
||||
For kv connector such as redis:
|
||||
<connector_type>://<host>:<port>/<model_name>/keys/<key>
|
||||
<connector_type://<host>:<port>/<model_name>/files/<filename>
|
||||
"""
|
||||
|
||||
def __init__(self, url: str):
|
||||
self.url = url
|
||||
self.closed = False
|
||||
self.local_dir = tempfile.mkdtemp()
|
||||
for sig in (signal.SIGINT, signal.SIGTERM):
|
||||
existing_handler = signal.getsignal(sig)
|
||||
signal.signal(sig, self._close_by_signal(existing_handler))
|
||||
|
||||
def get_local_dir(self):
|
||||
return self.local_dir
|
||||
|
||||
@abstractmethod
|
||||
def weight_iterator(
|
||||
self, rank: int = 0
|
||||
) -> Generator[Tuple[str, torch.Tensor], None, None]:
|
||||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
def pull_files(
|
||||
self,
|
||||
allow_pattern: Optional[List[str]] = None,
|
||||
ignore_pattern: Optional[List[str]] = None,
|
||||
) -> None:
|
||||
raise NotImplementedError()
|
||||
|
||||
def close(self):
|
||||
if self.closed:
|
||||
return
|
||||
|
||||
self.closed = True
|
||||
if os.path.exists(self.local_dir):
|
||||
shutil.rmtree(self.local_dir)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.close()
|
||||
|
||||
def __del__(self):
|
||||
self.close()
|
||||
|
||||
def _close_by_signal(self, existing_handler=None):
|
||||
|
||||
def new_handler(signum, frame):
|
||||
self.close()
|
||||
if existing_handler:
|
||||
existing_handler(signum, frame)
|
||||
|
||||
return new_handler
|
||||
|
||||
|
||||
class BaseKVConnector(BaseConnector):
|
||||
|
||||
@abstractmethod
|
||||
def get(self, key: str) -> Optional[torch.Tensor]:
|
||||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
def getstr(self, key: str) -> Optional[str]:
|
||||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
def set(self, key: str, obj: torch.Tensor) -> None:
|
||||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
def setstr(self, key: str, obj: str) -> None:
|
||||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
def list(self, prefix: str) -> List[str]:
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class BaseFileConnector(BaseConnector):
|
||||
"""
|
||||
List full file names from remote fs path and filter by allow pattern.
|
||||
|
||||
Args:
|
||||
allow_pattern: A list of patterns of which files to pull.
|
||||
|
||||
Returns:
|
||||
list[str]: List of full paths allowed by the pattern
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def glob(self, allow_pattern: str) -> List[str]:
|
||||
raise NotImplementedError()
|
||||
85
third_party/sglang/python/sglang/srt/connector/redis.py
vendored
Normal file
85
third_party/sglang/python/sglang/srt/connector/redis.py
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import logging
|
||||
from typing import Generator, List, Optional, Tuple
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import torch
|
||||
|
||||
from sglang.srt.connector import BaseKVConnector
|
||||
from sglang.srt.connector.serde import create_serde
|
||||
from sglang.srt.connector.utils import pull_files_from_db
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RedisConnector(BaseKVConnector):
|
||||
|
||||
def __init__(self, url: str):
|
||||
import redis
|
||||
|
||||
super().__init__(url)
|
||||
parsed_url = urlparse(url)
|
||||
self.connection = redis.Redis(host=parsed_url.hostname, port=parsed_url.port)
|
||||
self.model_name = parsed_url.path.lstrip("/")
|
||||
# TODO: more serde options
|
||||
self.s, self.d = create_serde("safe")
|
||||
|
||||
def get(self, key: str) -> Optional[torch.Tensor]:
|
||||
val = self.connection.get(key)
|
||||
|
||||
if val is None:
|
||||
logger.error("Key %s not found", key)
|
||||
return None
|
||||
|
||||
return self.d.from_bytes(val)
|
||||
|
||||
def getstr(self, key: str) -> Optional[str]:
|
||||
val = self.connection.get(key)
|
||||
if val is None:
|
||||
logger.error("Key %s not found", key)
|
||||
return None
|
||||
|
||||
return val.decode("utf-8")
|
||||
|
||||
def set(self, key: str, tensor: torch.Tensor) -> None:
|
||||
assert tensor is not None
|
||||
self.connection.set(key, self.s.to_bytes(tensor))
|
||||
|
||||
def setstr(self, key: str, obj: str) -> None:
|
||||
self.connection.set(key, obj)
|
||||
|
||||
def list(self, prefix: str) -> List[str]:
|
||||
cursor = 0
|
||||
all_keys: List[bytes] = []
|
||||
|
||||
while True:
|
||||
ret: Tuple[int, List[bytes]] = self.connection.scan(
|
||||
cursor=cursor, match=f"{prefix}*"
|
||||
) # type: ignore
|
||||
cursor, keys = ret
|
||||
all_keys.extend(keys)
|
||||
if cursor == 0:
|
||||
break
|
||||
|
||||
return [key.decode("utf-8") for key in all_keys]
|
||||
|
||||
def weight_iterator(
|
||||
self, rank: int = 0
|
||||
) -> Generator[Tuple[str, bytes], None, None]:
|
||||
keys = self.list(f"{self.model_name}/keys/rank_{rank}/")
|
||||
for key in keys:
|
||||
val = self.get(key)
|
||||
key = key.removeprefix(f"{self.model_name}/keys/rank_{rank}/")
|
||||
yield key, val
|
||||
|
||||
def pull_files(
|
||||
self,
|
||||
allow_pattern: Optional[List[str]] = None,
|
||||
ignore_pattern: Optional[List[str]] = None,
|
||||
) -> None:
|
||||
pull_files_from_db(self, self.model_name, allow_pattern, ignore_pattern)
|
||||
|
||||
def close(self):
|
||||
self.connection.close()
|
||||
super().close()
|
||||
82
third_party/sglang/python/sglang/srt/connector/remote_instance.py
vendored
Normal file
82
third_party/sglang/python/sglang/srt/connector/remote_instance.py
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import logging
|
||||
from typing import Generator, Optional, Tuple
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import torch
|
||||
import torch.distributed as dist
|
||||
|
||||
from sglang.srt.connector import BaseConnector
|
||||
from sglang.srt.utils import init_custom_process_group
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RemoteInstanceConnector(BaseConnector):
|
||||
|
||||
def __init__(self, url: str, device: torch.device = "cpu"):
|
||||
assert (
|
||||
device.type == "cuda" or device.type == "npu"
|
||||
), "RemoteInstanceConnector only supports cuda device."
|
||||
super().__init__(url)
|
||||
self.url = url
|
||||
self.device = device
|
||||
|
||||
def build_group(
|
||||
self,
|
||||
gpu_id: int = -1,
|
||||
tp_rank: int = -1,
|
||||
instance_ip: str = None,
|
||||
group_rank: int = 1,
|
||||
world_size: int = 2,
|
||||
):
|
||||
assert (
|
||||
self.device.type == "cuda" or self.device.type == "npu"
|
||||
), "RemoteInstanceConnector only supports cuda device."
|
||||
assert (
|
||||
gpu_id != -1 and tp_rank != -1
|
||||
), "gpu_id and tp_rank must be specified for RemoteInstanceConnector. "
|
||||
|
||||
self.device_id = torch.device(self.device.type, gpu_id)
|
||||
|
||||
parsed_url = urlparse(self.url)
|
||||
master_address = parsed_url.hostname
|
||||
master_port = parsed_url.port
|
||||
group_name = f"send_weights_{instance_ip}_{master_port}_{tp_rank}"
|
||||
backend = "nccl"
|
||||
|
||||
logger.info(
|
||||
f"init custom process group: master_address={master_address}, master_port={master_port}, "
|
||||
f"rank_offset={group_rank}, world_size={world_size}, group_name={group_name}, backend={backend}"
|
||||
)
|
||||
|
||||
try:
|
||||
self._model_update_group = init_custom_process_group(
|
||||
backend=backend,
|
||||
init_method=f"tcp://{master_address}:{master_port}",
|
||||
world_size=world_size,
|
||||
rank=group_rank,
|
||||
group_name=group_name,
|
||||
device_id=self.device_id,
|
||||
)
|
||||
dist.barrier(group=self._model_update_group)
|
||||
return True, "Succeeded to initialize custom process group."
|
||||
except Exception as e:
|
||||
message = f"Failed to initialize custom process group: {e}."
|
||||
logger.error(message)
|
||||
return False, message
|
||||
|
||||
# Implemented as a no-op to make BaseConnector interface consistent.
|
||||
def pull_files(
|
||||
self,
|
||||
allow_pattern: Optional[list[str]] = None,
|
||||
ignore_pattern: Optional[list[str]] = None,
|
||||
) -> None:
|
||||
return
|
||||
|
||||
# Implemented as a no-op to make BaseConnector interface consistent.
|
||||
def weight_iterator(
|
||||
self, rank: int = 0
|
||||
) -> Generator[Tuple[str, torch.Tensor], None, None]:
|
||||
return
|
||||
122
third_party/sglang/python/sglang/srt/connector/s3.py
vendored
Normal file
122
third_party/sglang/python/sglang/srt/connector/s3.py
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import fnmatch
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Generator, Optional, Tuple
|
||||
|
||||
import torch
|
||||
|
||||
from sglang.srt.connector import BaseFileConnector
|
||||
|
||||
|
||||
def _filter_allow(paths: list[str], patterns: list[str]) -> list[str]:
|
||||
return [
|
||||
path
|
||||
for path in paths
|
||||
if any(fnmatch.fnmatch(path, pattern) for pattern in patterns)
|
||||
]
|
||||
|
||||
|
||||
def _filter_ignore(paths: list[str], patterns: list[str]) -> list[str]:
|
||||
return [
|
||||
path
|
||||
for path in paths
|
||||
if not any(fnmatch.fnmatch(path, pattern) for pattern in patterns)
|
||||
]
|
||||
|
||||
|
||||
def list_files(
|
||||
s3,
|
||||
path: str,
|
||||
allow_pattern: Optional[list[str]] = None,
|
||||
ignore_pattern: Optional[list[str]] = None,
|
||||
) -> tuple[str, str, list[str]]:
|
||||
"""
|
||||
List files from S3 path and filter by pattern.
|
||||
|
||||
Args:
|
||||
s3: S3 client to use.
|
||||
path: The S3 path to list from.
|
||||
allow_pattern: A list of patterns of which files to pull.
|
||||
ignore_pattern: A list of patterns of which files not to pull.
|
||||
|
||||
Returns:
|
||||
tuple[str, str, list[str]]: A tuple where:
|
||||
- The first element is the bucket name
|
||||
- The second element is string represent the bucket
|
||||
and the prefix as a dir like string
|
||||
- The third element is a list of files allowed or
|
||||
disallowed by pattern
|
||||
"""
|
||||
parts = path.removeprefix("s3://").split("/")
|
||||
prefix = "/".join(parts[1:])
|
||||
bucket_name = parts[0]
|
||||
|
||||
objects = s3.list_objects_v2(Bucket=bucket_name, Prefix=prefix)
|
||||
paths = [obj["Key"] for obj in objects.get("Contents", [])]
|
||||
|
||||
paths = _filter_ignore(paths, ["*/"])
|
||||
if allow_pattern is not None:
|
||||
paths = _filter_allow(paths, allow_pattern)
|
||||
|
||||
if ignore_pattern is not None:
|
||||
paths = _filter_ignore(paths, ignore_pattern)
|
||||
|
||||
return bucket_name, prefix, paths
|
||||
|
||||
|
||||
class S3Connector(BaseFileConnector):
|
||||
|
||||
def __init__(self, url: str) -> None:
|
||||
import boto3
|
||||
|
||||
super().__init__(url)
|
||||
self.client = boto3.client("s3")
|
||||
|
||||
def glob(self, allow_pattern: Optional[list[str]] = None) -> list[str]:
|
||||
bucket_name, _, paths = list_files(
|
||||
self.client, path=self.url, allow_pattern=allow_pattern
|
||||
)
|
||||
return [f"s3://{bucket_name}/{path}" for path in paths]
|
||||
|
||||
def pull_files(
|
||||
self,
|
||||
allow_pattern: Optional[list[str]] = None,
|
||||
ignore_pattern: Optional[list[str]] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Pull files from S3 storage into the temporary directory.
|
||||
|
||||
Args:
|
||||
s3_model_path: The S3 path of the model.
|
||||
allow_pattern: A list of patterns of which files to pull.
|
||||
ignore_pattern: A list of patterns of which files not to pull.
|
||||
|
||||
"""
|
||||
bucket_name, base_dir, files = list_files(
|
||||
self.client, self.url, allow_pattern, ignore_pattern
|
||||
)
|
||||
if len(files) == 0:
|
||||
return
|
||||
|
||||
for file in files:
|
||||
destination_file = os.path.join(self.local_dir, file.removeprefix(base_dir))
|
||||
local_dir = Path(destination_file).parent
|
||||
os.makedirs(local_dir, exist_ok=True)
|
||||
self.client.download_file(bucket_name, file, destination_file)
|
||||
|
||||
def weight_iterator(
|
||||
self, rank: int = 0
|
||||
) -> Generator[Tuple[str, torch.Tensor], None, None]:
|
||||
from sglang.srt.model_loader.weight_utils import (
|
||||
runai_safetensors_weights_iterator,
|
||||
)
|
||||
|
||||
# only support safetensor files now
|
||||
hf_weights_files = self.glob(allow_pattern=["*.safetensors"])
|
||||
return runai_safetensors_weights_iterator(hf_weights_files)
|
||||
|
||||
def close(self):
|
||||
self.client.close()
|
||||
super().close()
|
||||
31
third_party/sglang/python/sglang/srt/connector/serde/__init__.py
vendored
Normal file
31
third_party/sglang/python/sglang/srt/connector/serde/__init__.py
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# inspired by LMCache
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import torch
|
||||
|
||||
from sglang.srt.connector.serde.safe_serde import SafeDeserializer, SafeSerializer
|
||||
from sglang.srt.connector.serde.serde import Deserializer, Serializer
|
||||
|
||||
|
||||
def create_serde(serde_type: str) -> Tuple[Serializer, Deserializer]:
|
||||
s: Optional[Serializer] = None
|
||||
d: Optional[Deserializer] = None
|
||||
|
||||
if serde_type == "safe":
|
||||
s = SafeSerializer()
|
||||
d = SafeDeserializer()
|
||||
else:
|
||||
raise ValueError(f"Unknown serde type: {serde_type}")
|
||||
|
||||
return s, d
|
||||
|
||||
|
||||
__all__ = [
|
||||
"Serializer",
|
||||
"Deserializer",
|
||||
"SafeSerializer",
|
||||
"SafeDeserializer",
|
||||
"create_serde",
|
||||
]
|
||||
30
third_party/sglang/python/sglang/srt/connector/serde/safe_serde.py
vendored
Normal file
30
third_party/sglang/python/sglang/srt/connector/serde/safe_serde.py
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
from typing import Union
|
||||
|
||||
import torch
|
||||
from safetensors.torch import load, save
|
||||
|
||||
from sglang.srt.connector.serde.serde import Deserializer, Serializer
|
||||
|
||||
|
||||
class SafeSerializer(Serializer):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def to_bytes(self, t: torch.Tensor) -> bytes:
|
||||
return save({"tensor_bytes": t.cpu().contiguous()})
|
||||
|
||||
|
||||
class SafeDeserializer(Deserializer):
|
||||
|
||||
def __init__(self):
|
||||
# TODO: dtype options
|
||||
super().__init__(torch.float32)
|
||||
|
||||
def from_bytes_normal(self, b: Union[bytearray, bytes]) -> torch.Tensor:
|
||||
return load(bytes(b))["tensor_bytes"]
|
||||
|
||||
def from_bytes(self, b: Union[bytearray, bytes]) -> torch.Tensor:
|
||||
return self.from_bytes_normal(b)
|
||||
43
third_party/sglang/python/sglang/srt/connector/serde/serde.py
vendored
Normal file
43
third_party/sglang/python/sglang/srt/connector/serde/serde.py
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import abc
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
import torch
|
||||
|
||||
|
||||
class Serializer(ABC):
|
||||
|
||||
@abstractmethod
|
||||
def to_bytes(self, t: torch.Tensor) -> bytes:
|
||||
"""
|
||||
Serialize a pytorch tensor to bytes. The serialized bytes should contain
|
||||
both the data and the metadata (shape, dtype, etc.) of the tensor.
|
||||
|
||||
Input:
|
||||
t: the input pytorch tensor, can be on any device, in any shape,
|
||||
with any dtype
|
||||
|
||||
Returns:
|
||||
bytes: the serialized bytes
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class Deserializer(metaclass=abc.ABCMeta):
|
||||
|
||||
def __init__(self, dtype):
|
||||
self.dtype = dtype
|
||||
|
||||
@abstractmethod
|
||||
def from_bytes(self, bs: bytes) -> torch.Tensor:
|
||||
"""
|
||||
Deserialize a pytorch tensor from bytes.
|
||||
|
||||
Input:
|
||||
bytes: a stream of bytes
|
||||
|
||||
Output:
|
||||
torch.Tensor: the deserialized pytorch tensor
|
||||
"""
|
||||
raise NotImplementedError
|
||||
35
third_party/sglang/python/sglang/srt/connector/utils.py
vendored
Normal file
35
third_party/sglang/python/sglang/srt/connector/utils.py
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from sglang.srt.connector import BaseConnector
|
||||
|
||||
|
||||
def parse_model_name(url: str) -> str:
|
||||
"""
|
||||
Parse the model name from the url.
|
||||
Only used for db connector
|
||||
"""
|
||||
parsed_url = urlparse(url)
|
||||
return parsed_url.path.lstrip("/")
|
||||
|
||||
|
||||
def pull_files_from_db(
|
||||
connector: BaseConnector,
|
||||
model_name: str,
|
||||
allow_pattern: Optional[list[str]] = None,
|
||||
ignore_pattern: Optional[list[str]] = None,
|
||||
) -> None:
|
||||
prefix = f"{model_name}/files/"
|
||||
local_dir = connector.get_local_dir()
|
||||
files = connector.list(prefix)
|
||||
|
||||
for file in files:
|
||||
destination_file = os.path.join(local_dir, file.removeprefix(prefix))
|
||||
local_dir = Path(destination_file).parent
|
||||
os.makedirs(local_dir, exist_ok=True)
|
||||
with open(destination_file, "wb") as f:
|
||||
f.write(connector.getstr(file).encode("utf-8"))
|
||||
12
third_party/sglang/python/sglang/srt/constants.py
vendored
Normal file
12
third_party/sglang/python/sglang/srt/constants.py
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
# GPU Memory Types
|
||||
GPU_MEMORY_TYPE_KV_CACHE = "kv_cache"
|
||||
GPU_MEMORY_TYPE_WEIGHTS = "weights"
|
||||
GPU_MEMORY_TYPE_CUDA_GRAPH = "cuda_graph"
|
||||
|
||||
GPU_MEMORY_ALL_TYPES = [
|
||||
GPU_MEMORY_TYPE_KV_CACHE,
|
||||
GPU_MEMORY_TYPE_WEIGHTS,
|
||||
GPU_MEMORY_TYPE_CUDA_GRAPH,
|
||||
]
|
||||
|
||||
HEALTH_CHECK_RID_PREFIX = "HEALTH_CHECK"
|
||||
270
third_party/sglang/python/sglang/srt/constrained/base_grammar_backend.py
vendored
Normal file
270
third_party/sglang/python/sglang/srt/constrained/base_grammar_backend.py
vendored
Normal file
@@ -0,0 +1,270 @@
|
||||
# Copyright 2023-2024 SGLang Team
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ==============================================================================
|
||||
"""The baseclass of a backend for grammar-guided constrained decoding."""
|
||||
|
||||
import logging
|
||||
import time
|
||||
from concurrent.futures import Future, ThreadPoolExecutor
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
import torch
|
||||
|
||||
from sglang.srt.server_args import ServerArgs
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class GrammarStats:
|
||||
compilation_time: Optional[float] = None
|
||||
schema_count: Optional[int] = None
|
||||
ebnf_size: Optional[int] = None
|
||||
is_cache_hit: bool = False
|
||||
is_grammar_aborted: bool = False
|
||||
tree_traversal_time: List[float] = field(default_factory=list)
|
||||
dispatch_type: Optional[str] = None
|
||||
num_timeout: int = 0
|
||||
|
||||
|
||||
class BaseGrammarObject:
|
||||
|
||||
def __init__(self):
|
||||
self._finished = False
|
||||
self.grammar_stats = None
|
||||
self.current_token = None
|
||||
|
||||
def maybe_init_reasoning(self, reasoning: bool):
|
||||
pass
|
||||
|
||||
def accept_token(self, token: int) -> None:
|
||||
"""
|
||||
Accept a token in the grammar.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def rollback(self, k: int):
|
||||
raise NotImplementedError()
|
||||
|
||||
def is_terminated(self):
|
||||
return False
|
||||
|
||||
def allocate_vocab_mask(
|
||||
self, vocab_size: int, batch_size: int, device
|
||||
) -> torch.Tensor:
|
||||
raise NotImplementedError()
|
||||
|
||||
def fill_vocab_mask(self, vocab_mask: torch.Tensor, idx: int) -> None:
|
||||
raise NotImplementedError()
|
||||
|
||||
@staticmethod
|
||||
def move_vocab_mask(vocab_mask: torch.Tensor, device) -> torch.Tensor:
|
||||
raise NotImplementedError()
|
||||
|
||||
@staticmethod
|
||||
def apply_vocab_mask(logits: torch.Tensor, vocab_mask: torch.Tensor) -> None:
|
||||
raise NotImplementedError()
|
||||
|
||||
def copy(self) -> "BaseGrammarObject":
|
||||
return self
|
||||
|
||||
@property
|
||||
def finished(self):
|
||||
return self._finished
|
||||
|
||||
@finished.setter
|
||||
def finished(self, finished):
|
||||
self._finished = finished
|
||||
|
||||
def try_jump_forward(self, tokenizer) -> Optional[Tuple[List[int], str]]:
|
||||
"""
|
||||
Try to jump forward in the grammar.
|
||||
|
||||
Returns:
|
||||
A jump forward helper which may be used in `jump_forward_str_state`.
|
||||
None if the jump forward is not possible.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def jump_forward_str_state(self, helper: Tuple[List[int], str]) -> Tuple[str, int]:
|
||||
"""
|
||||
Jump forward for the grammar.
|
||||
|
||||
Returns:
|
||||
A tuple of the jump forward string and the next state of the grammar
|
||||
(which can be used in `jump_and_retokenize` if needed).
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def jump_and_retokenize(
|
||||
self, old_output_ids: List[int], new_output_ids: List[int], next_state: int
|
||||
) -> None:
|
||||
"""
|
||||
Jump forward occurs, and update the grammar state if needed.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class InvalidGrammarObject(BaseGrammarObject):
|
||||
"""Represents a grammar that failed to compile, carrying the original error message."""
|
||||
|
||||
def __init__(self, error_message: str = "Unknown grammar error"):
|
||||
super().__init__()
|
||||
self.error_message = error_message
|
||||
|
||||
def __repr__(self):
|
||||
return f"InvalidGrammarObject(error_message={self.error_message!r})"
|
||||
|
||||
|
||||
class BaseGrammarBackend:
|
||||
def __init__(self):
|
||||
self.executor = ThreadPoolExecutor()
|
||||
self.cache: Dict[Tuple[str, str], BaseGrammarObject] = {}
|
||||
|
||||
def _not_supported(self, key_type: str, key_string: str) -> BaseGrammarObject:
|
||||
logger.warning(f"Skip unsupported {key_type=}, {key_string=}")
|
||||
return InvalidGrammarObject()
|
||||
|
||||
def dispatch_fallback(self, key_type: str, key_string: str) -> BaseGrammarObject:
|
||||
"""
|
||||
This function should not be reached in any case.
|
||||
"""
|
||||
raise ValueError(f"Invalid key_type: {key_type}={key_string}")
|
||||
|
||||
def dispatch_json(self, key_string: str) -> BaseGrammarObject:
|
||||
return self._not_supported("json", key_string)
|
||||
|
||||
def dispatch_regex(self, key_string: str) -> BaseGrammarObject:
|
||||
return self._not_supported("regex", key_string)
|
||||
|
||||
def dispatch_ebnf(self, key_string: str) -> BaseGrammarObject:
|
||||
return self._not_supported("ebnf", key_string)
|
||||
|
||||
def dispatch_structural_tag(self, key_string: str) -> BaseGrammarObject:
|
||||
return self._not_supported("structural_tag", key_string)
|
||||
|
||||
def _init_value_dispatch(
|
||||
self, key: Tuple[str, str], require_reasoning: bool
|
||||
) -> BaseGrammarObject:
|
||||
s = time.perf_counter()
|
||||
key_type, key_string = key
|
||||
if key_type == "json":
|
||||
grammar = self.dispatch_json(key_string)
|
||||
elif key_type == "regex":
|
||||
grammar = self.dispatch_regex(key_string)
|
||||
elif key_type == "ebnf":
|
||||
grammar = self.dispatch_ebnf(key_string)
|
||||
elif key_type == "structural_tag":
|
||||
grammar = self.dispatch_structural_tag(key_string)
|
||||
else:
|
||||
grammar = self.dispatch_fallback(key_type, key_string)
|
||||
|
||||
if grammar is not None and grammar.grammar_stats is not None:
|
||||
grammar.grammar_stats.compilation_time = time.perf_counter() - s
|
||||
return grammar
|
||||
|
||||
def get_cached_or_future_value(
|
||||
self, key: Tuple[str, str], require_reasoning: bool
|
||||
) -> Tuple[BaseGrammarObject | Future[BaseGrammarObject], bool]:
|
||||
value = self.cache.get(key)
|
||||
if value:
|
||||
copied_value = value.copy()
|
||||
copied_value.maybe_init_reasoning(require_reasoning)
|
||||
return copied_value, True
|
||||
value = self.executor.submit(self._init_value_dispatch, key, require_reasoning)
|
||||
return value, False
|
||||
|
||||
def set_cache(self, key: Tuple[str, str], value: BaseGrammarObject):
|
||||
self.cache[key] = value
|
||||
|
||||
def reset(self):
|
||||
self.cache.clear()
|
||||
|
||||
|
||||
GRAMMAR_BACKEND_REGISTRY = {}
|
||||
|
||||
|
||||
def register_grammar_backend(name, init_func):
|
||||
GRAMMAR_BACKEND_REGISTRY[name] = init_func
|
||||
|
||||
|
||||
def create_grammar_backend(
|
||||
server_args: ServerArgs,
|
||||
tokenizer,
|
||||
vocab_size: int,
|
||||
eos_token_ids: Optional[set] = None,
|
||||
) -> Optional[BaseGrammarBackend]:
|
||||
name = server_args.grammar_backend
|
||||
|
||||
# Custom grammar backend has the highest priority
|
||||
if name in GRAMMAR_BACKEND_REGISTRY:
|
||||
return GRAMMAR_BACKEND_REGISTRY[name](
|
||||
server_args, tokenizer, vocab_size, eos_token_ids
|
||||
)
|
||||
|
||||
# Default grammar backends
|
||||
if name == "outlines":
|
||||
from sglang.srt.constrained.outlines_backend import OutlinesGrammarBackend
|
||||
|
||||
grammar_backend = OutlinesGrammarBackend(
|
||||
tokenizer,
|
||||
whitespace_pattern=server_args.constrained_json_whitespace_pattern,
|
||||
)
|
||||
elif name == "xgrammar":
|
||||
from sglang.srt.constrained.xgrammar_backend import (
|
||||
TokenizerNotSupportedError,
|
||||
XGrammarGrammarBackend,
|
||||
)
|
||||
|
||||
# Convert Set[int] to List[int] if needed
|
||||
eos_list = list(eos_token_ids) if eos_token_ids else None
|
||||
|
||||
try:
|
||||
grammar_backend = XGrammarGrammarBackend(
|
||||
tokenizer,
|
||||
vocab_size=vocab_size,
|
||||
model_eos_token_ids=eos_list,
|
||||
any_whitespace=not server_args.constrained_json_disable_any_whitespace,
|
||||
)
|
||||
except TokenizerNotSupportedError as e:
|
||||
logger.warning(
|
||||
f"Grammar backend disabled because tokenizer is not supported by XGrammar: {e}. "
|
||||
"Falling back to grammar_backend='none'. "
|
||||
"Structured outputs (JSON schema, regex, EBNF) will not be available."
|
||||
)
|
||||
server_args.grammar_backend = "none"
|
||||
return None
|
||||
elif name == "llguidance":
|
||||
from sglang.srt.constrained.llguidance_backend import GuidanceBackend
|
||||
|
||||
grammar_backend = GuidanceBackend(
|
||||
tokenizer=tokenizer,
|
||||
any_whitespace=not server_args.constrained_json_disable_any_whitespace,
|
||||
whitespace_pattern=server_args.constrained_json_whitespace_pattern,
|
||||
)
|
||||
elif name == "none":
|
||||
return None
|
||||
else:
|
||||
raise ValueError(f"Invalid grammar backend: {name}")
|
||||
|
||||
if server_args.reasoning_parser and hasattr(tokenizer, "think_end_id"):
|
||||
from sglang.srt.constrained.reasoner_grammar_backend import (
|
||||
ReasonerGrammarBackend,
|
||||
)
|
||||
|
||||
grammar_backend = ReasonerGrammarBackend(
|
||||
grammar_backend, tokenizer.think_end_id
|
||||
)
|
||||
|
||||
return grammar_backend
|
||||
204
third_party/sglang/python/sglang/srt/constrained/grammar_manager.py
vendored
Normal file
204
third_party/sglang/python/sglang/srt/constrained/grammar_manager.py
vendored
Normal file
@@ -0,0 +1,204 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import time
|
||||
from concurrent import futures
|
||||
from typing import TYPE_CHECKING, List
|
||||
|
||||
import torch
|
||||
|
||||
from sglang.srt.constrained.base_grammar_backend import (
|
||||
InvalidGrammarObject,
|
||||
create_grammar_backend,
|
||||
)
|
||||
from sglang.srt.environ import envs
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sglang.srt.managers.io_struct import AbortReq
|
||||
from sglang.srt.managers.schedule_batch import Req
|
||||
from sglang.srt.managers.scheduler import Scheduler
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GrammarManager:
|
||||
def __init__(self, scheduler: Scheduler):
|
||||
self.scheduler = scheduler
|
||||
self.server_args = scheduler.server_args
|
||||
self.grammar_queue: List[Req] = []
|
||||
if not self.server_args.skip_tokenizer_init:
|
||||
self.grammar_backend = create_grammar_backend(
|
||||
self.server_args,
|
||||
scheduler.tokenizer,
|
||||
scheduler.model_config.vocab_size,
|
||||
scheduler.model_config.hf_eos_token_id,
|
||||
)
|
||||
else:
|
||||
self.grammar_backend = None
|
||||
|
||||
self.grammar_sync_group = scheduler.dp_tp_cpu_group
|
||||
self.grammar_sync_size = scheduler.dp_tp_group.world_size
|
||||
self.grammar_sync_entry = scheduler.dp_tp_group.first_rank
|
||||
self.is_grammar_sync_entry = scheduler.dp_tp_group.is_first_rank
|
||||
|
||||
self.SGLANG_GRAMMAR_POLL_INTERVAL = envs.SGLANG_GRAMMAR_POLL_INTERVAL.get()
|
||||
self.SGLANG_GRAMMAR_MAX_POLL_ITERATIONS = (
|
||||
envs.SGLANG_GRAMMAR_MAX_POLL_ITERATIONS.get()
|
||||
)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.grammar_queue)
|
||||
|
||||
def clear(self):
|
||||
if self.grammar_backend:
|
||||
self.grammar_backend.reset()
|
||||
|
||||
def has_waiting_grammars(self) -> bool:
|
||||
return len(self.grammar_queue) > 0
|
||||
|
||||
def abort_requests(self, recv_req: AbortReq):
|
||||
for req in self.grammar_queue:
|
||||
if recv_req.abort_all or req.rid.startswith(recv_req.rid):
|
||||
logger.debug(f"Abort grammar queue request. {req.rid=}")
|
||||
if isinstance(req.grammar, futures.Future) and req.grammar:
|
||||
req.grammar.cancel()
|
||||
req.set_finish_with_abort("Aborted by AbortReq.")
|
||||
|
||||
def process_req_with_grammar(self, req: Req) -> bool:
|
||||
# Init grammar cache for this request
|
||||
add_to_grammar_queue = False
|
||||
if (
|
||||
req.sampling_params.json_schema is not None
|
||||
or req.sampling_params.regex is not None
|
||||
or req.sampling_params.ebnf is not None
|
||||
or req.sampling_params.structural_tag is not None
|
||||
):
|
||||
if self.grammar_backend is None:
|
||||
error_msg = "Grammar-based generation (json_schema, regex, ebnf, structural_tag) is not supported when the server is launched with --grammar-backend none"
|
||||
req.set_finish_with_abort(error_msg)
|
||||
else:
|
||||
if req.sampling_params.json_schema is not None:
|
||||
key = ("json", req.sampling_params.json_schema)
|
||||
elif req.sampling_params.regex is not None:
|
||||
key = ("regex", req.sampling_params.regex)
|
||||
elif req.sampling_params.ebnf is not None:
|
||||
key = ("ebnf", req.sampling_params.ebnf)
|
||||
elif req.sampling_params.structural_tag:
|
||||
key = ("structural_tag", req.sampling_params.structural_tag)
|
||||
|
||||
value, cache_hit = self.grammar_backend.get_cached_or_future_value(
|
||||
key, req.require_reasoning
|
||||
)
|
||||
req.grammar = value
|
||||
|
||||
if not cache_hit:
|
||||
req.grammar_key = key
|
||||
add_to_grammar_queue = True
|
||||
else:
|
||||
if isinstance(
|
||||
value, InvalidGrammarObject
|
||||
): # We hit a cached invalid grammar.
|
||||
error_msg = (
|
||||
f"Failed to compile {key[0]} grammar: {value.error_message}"
|
||||
)
|
||||
req.set_finish_with_abort(error_msg)
|
||||
|
||||
if add_to_grammar_queue:
|
||||
self.grammar_queue.append(req)
|
||||
|
||||
return add_to_grammar_queue
|
||||
|
||||
def get_ready_grammar_requests(self) -> List[Req]:
|
||||
"""
|
||||
Move requests whose grammar objects are ready from grammar_queue to waiting_queue.
|
||||
|
||||
Rank i returns two sets ready_reqs_i, failed_reqs_i
|
||||
ready_reqs_all = all_gather(ready_reqs_i)
|
||||
failed_reqs_all = all_gather(failed_reqs_i)
|
||||
|
||||
ready_reqs = intersect(ready_reqs_all)
|
||||
failed_reqs = union(failed_reqs_all)
|
||||
"""
|
||||
assert self.grammar_backend
|
||||
ready_req_idxs: set[int] = set()
|
||||
failed_req_idxs: set[int] = set()
|
||||
|
||||
# Poll for ready requests
|
||||
start_time = time.perf_counter()
|
||||
while time.perf_counter() - start_time < self.SGLANG_GRAMMAR_POLL_INTERVAL:
|
||||
for i, req in enumerate(self.grammar_queue):
|
||||
if i in ready_req_idxs:
|
||||
continue
|
||||
|
||||
if req.finished() or req.grammar is None: # It is aborted by AbortReq
|
||||
ready_req_idxs.add(i)
|
||||
continue
|
||||
|
||||
assert isinstance(req.grammar, futures.Future), f"{req=}"
|
||||
if req.grammar.done():
|
||||
ready_req_idxs.add(i)
|
||||
|
||||
# Sleep a bit to avoid busy waiting
|
||||
time.sleep(self.SGLANG_GRAMMAR_POLL_INTERVAL / 10)
|
||||
|
||||
# Check failed requests
|
||||
for i, req in enumerate(self.grammar_queue):
|
||||
if i not in ready_req_idxs:
|
||||
self.grammar_queue[i].grammar_wait_ct += 1
|
||||
if (
|
||||
self.grammar_queue[i].grammar_wait_ct
|
||||
>= self.SGLANG_GRAMMAR_MAX_POLL_ITERATIONS
|
||||
):
|
||||
# Timeout after max poll iterations
|
||||
# The actual waiting time is SGLANG_GRAMMAR_MAX_POLL_ITERATIONS * max(SGLANG_GRAMMAR_POLL_INTERVAL, GPU_forward_batch_latency)
|
||||
failed_req_idxs.add(i)
|
||||
|
||||
# Sync ready and failed requests across all ranks
|
||||
if self.grammar_sync_size == 1:
|
||||
synced_ready_req_idxs = ready_req_idxs
|
||||
synced_failed_req_idxs = failed_req_idxs
|
||||
else:
|
||||
all_gather_output = [None] * self.grammar_sync_size
|
||||
torch.distributed.all_gather_object(
|
||||
all_gather_output,
|
||||
(ready_req_idxs, failed_req_idxs),
|
||||
group=self.grammar_sync_group,
|
||||
)
|
||||
synced_ready_req_idxs = set.intersection(*[x[0] for x in all_gather_output])
|
||||
synced_failed_req_idxs = set.union(*[x[1] for x in all_gather_output])
|
||||
|
||||
# Return ready requests
|
||||
return_reqs: List[Req] = []
|
||||
for i in synced_ready_req_idxs:
|
||||
req = self.grammar_queue[i]
|
||||
return_reqs.append(req)
|
||||
if req.finished() or req.grammar is None: # It is aborted by AbortReq
|
||||
continue
|
||||
|
||||
assert isinstance(req.grammar, futures.Future) and req.grammar_key
|
||||
req.grammar = req.grammar.result()
|
||||
self.grammar_backend.set_cache(req.grammar_key, req.grammar.copy())
|
||||
if isinstance(req.grammar, InvalidGrammarObject):
|
||||
error_msg = f"Failed to compile {req.grammar_key[0]} grammar: {req.grammar.error_message}"
|
||||
req.set_finish_with_abort(error_msg)
|
||||
|
||||
# Return failed requests
|
||||
for i in synced_failed_req_idxs:
|
||||
req = self.grammar_queue[i]
|
||||
return_reqs.append(req)
|
||||
|
||||
assert isinstance(req.grammar, futures.Future) and req.grammar_key
|
||||
req.grammar.cancel()
|
||||
self.grammar_backend.set_cache(
|
||||
req.grammar_key, InvalidGrammarObject("Grammar preprocessing timed out")
|
||||
)
|
||||
error_msg = f"Grammar preprocessing timed out: {req.grammar_key=}"
|
||||
req.set_finish_with_abort(error_msg)
|
||||
|
||||
# Remove finished requests from grammar_queue
|
||||
self.grammar_queue = [
|
||||
req
|
||||
for i, req in enumerate(self.grammar_queue)
|
||||
if i not in synced_ready_req_idxs and i not in synced_failed_req_idxs
|
||||
]
|
||||
return return_reqs
|
||||
200
third_party/sglang/python/sglang/srt/constrained/llguidance_backend.py
vendored
Normal file
200
third_party/sglang/python/sglang/srt/constrained/llguidance_backend.py
vendored
Normal file
@@ -0,0 +1,200 @@
|
||||
# Copyright 2023-2024 SGLang Team
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ==============================================================================
|
||||
"""Constrained decoding with llguidance backend."""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
import torch
|
||||
from llguidance import LLMatcher, LLTokenizer, StructTag, grammar_from
|
||||
from llguidance.hf import from_tokenizer
|
||||
from llguidance.torch import (
|
||||
allocate_token_bitmask,
|
||||
apply_token_bitmask_inplace,
|
||||
fill_next_token_bitmask,
|
||||
)
|
||||
|
||||
from sglang.srt.constrained.base_grammar_backend import (
|
||||
BaseGrammarBackend,
|
||||
BaseGrammarObject,
|
||||
InvalidGrammarObject,
|
||||
)
|
||||
from sglang.srt.constrained.utils import is_legacy_structural_tag
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuidanceGrammar(BaseGrammarObject):
|
||||
|
||||
def __init__(self, llguidance_tokenizer: LLTokenizer, serialized_grammar: str):
|
||||
super().__init__()
|
||||
self.llguidance_tokenizer = llguidance_tokenizer
|
||||
self.serialized_grammar = serialized_grammar
|
||||
|
||||
self.ll_matcher = LLMatcher(
|
||||
self.llguidance_tokenizer,
|
||||
self.serialized_grammar,
|
||||
log_level=int(os.environ.get("LLGUIDANCE_LOG_LEVEL", "1")),
|
||||
)
|
||||
self._check_err()
|
||||
|
||||
self.bitmask = None
|
||||
self.eos_token = self.llguidance_tokenizer.eos_token
|
||||
|
||||
def accept_token(self, token: int):
|
||||
if self.finished:
|
||||
return
|
||||
if self.ll_matcher.is_stopped() and token == self.eos_token:
|
||||
self.finished = True
|
||||
return
|
||||
self.ll_matcher.consume_token(token)
|
||||
self._check_err()
|
||||
|
||||
def rollback(self, num_tokens: int) -> None:
|
||||
if num_tokens <= 0:
|
||||
return
|
||||
if self.finished:
|
||||
self.finished = False
|
||||
# EOS token after stop isn't tracked in ll_matcher
|
||||
num_tokens -= 1
|
||||
self.ll_matcher.rollback(num_tokens)
|
||||
self._check_err()
|
||||
|
||||
def is_terminated(self):
|
||||
return self.finished
|
||||
|
||||
def fill_vocab_mask(self, vocab_mask: torch.Tensor, idx: int) -> None:
|
||||
fill_next_token_bitmask(self.ll_matcher, vocab_mask, idx)
|
||||
self._check_err()
|
||||
|
||||
def allocate_vocab_mask(
|
||||
self, vocab_size: int, batch_size: int, device
|
||||
) -> torch.Tensor:
|
||||
if self.bitmask is None or self.bitmask.shape[0] < batch_size:
|
||||
# only create bitmask when batch gets larger
|
||||
self.bitmask = allocate_token_bitmask(
|
||||
batch_size, self.llguidance_tokenizer.vocab_size
|
||||
)
|
||||
bitmask = self.bitmask
|
||||
else:
|
||||
bitmask = self.bitmask[:batch_size]
|
||||
|
||||
return bitmask
|
||||
|
||||
@staticmethod
|
||||
def move_vocab_mask(vocab_mask: torch.Tensor, device) -> torch.Tensor:
|
||||
return vocab_mask.to(device, non_blocking=True)
|
||||
|
||||
@staticmethod
|
||||
def apply_vocab_mask(logits: torch.Tensor, vocab_mask: torch.Tensor) -> None:
|
||||
apply_token_bitmask_inplace(logits, vocab_mask)
|
||||
|
||||
def copy(self):
|
||||
return GuidanceGrammar(
|
||||
llguidance_tokenizer=self.llguidance_tokenizer,
|
||||
serialized_grammar=self.serialized_grammar,
|
||||
)
|
||||
|
||||
def try_jump_forward(self, tokenizer) -> Optional[Tuple[List[int], str]]:
|
||||
ff_tokens = self.ll_matcher.compute_ff_tokens()
|
||||
if ff_tokens:
|
||||
return ff_tokens, ""
|
||||
else:
|
||||
return None
|
||||
|
||||
def jump_forward_str_state(self, helper: Tuple[List[int], str]) -> Tuple[str, int]:
|
||||
return "", -1
|
||||
|
||||
def jump_and_retokenize(
|
||||
self, old_output_ids: List[int], new_output_ids: List[int], next_state: int
|
||||
):
|
||||
pass
|
||||
|
||||
def _check_err(self) -> None:
|
||||
if self.ll_matcher.is_error():
|
||||
raise ValueError(self.ll_matcher.get_error())
|
||||
|
||||
|
||||
class GuidanceBackend(BaseGrammarBackend):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
tokenizer,
|
||||
any_whitespace: bool = True,
|
||||
whitespace_pattern: Optional[str] = None,
|
||||
n_vocab: Optional[int] = None,
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
self.tokenizer = tokenizer
|
||||
self.any_whitespace = any_whitespace
|
||||
self.whitespace_pattern = whitespace_pattern
|
||||
self.llguidance_tokenizer = from_tokenizer(self.tokenizer, n_vocab)
|
||||
|
||||
def _from_serialized(self, serialized_grammar) -> BaseGrammarObject:
|
||||
try:
|
||||
return GuidanceGrammar(
|
||||
llguidance_tokenizer=self.llguidance_tokenizer,
|
||||
serialized_grammar=serialized_grammar,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Hit invalid grammar: {serialized_grammar=}, {e=}")
|
||||
return InvalidGrammarObject(str(e))
|
||||
|
||||
def dispatch_json(self, key_string: str) -> BaseGrammarObject:
|
||||
try:
|
||||
serialized_grammar = LLMatcher.grammar_from_json_schema(
|
||||
key_string,
|
||||
defaults={
|
||||
"whitespace_flexible": self.any_whitespace,
|
||||
"whitespace_pattern": self.whitespace_pattern,
|
||||
},
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Hit invalid json_schema: {key_string=}, {e=}")
|
||||
return InvalidGrammarObject(str(e))
|
||||
return self._from_serialized(serialized_grammar)
|
||||
|
||||
def dispatch_regex(self, key_string: str) -> BaseGrammarObject:
|
||||
serialized_grammar = grammar_from("regex", key_string)
|
||||
return self._from_serialized(serialized_grammar)
|
||||
|
||||
def dispatch_ebnf(self, key_string: str) -> BaseGrammarObject:
|
||||
try:
|
||||
serialized_grammar = grammar_from("ebnf", key_string)
|
||||
return self._from_serialized(serialized_grammar)
|
||||
except ValueError as e:
|
||||
logger.error(f"Hit invalid ebnf: {key_string=}, {e=}")
|
||||
return InvalidGrammarObject(str(e))
|
||||
|
||||
def dispatch_structural_tag(self, key_string: str) -> BaseGrammarObject:
|
||||
try:
|
||||
structural_tag = json.loads(key_string)
|
||||
assert is_legacy_structural_tag(structural_tag)
|
||||
tags = [
|
||||
StructTag(
|
||||
begin=structure["begin"],
|
||||
grammar=structure["schema"],
|
||||
end=structure["end"],
|
||||
trigger=structural_tag["triggers"][0], # TODO?
|
||||
)
|
||||
for structure in structural_tag["structures"]
|
||||
]
|
||||
g = StructTag.to_grammar(tags)
|
||||
return self._from_serialized(g)
|
||||
except Exception as e:
|
||||
logger.error(f"Hit invalid structural_tag: {key_string=}, {e=}")
|
||||
return InvalidGrammarObject(str(e))
|
||||
190
third_party/sglang/python/sglang/srt/constrained/outlines_backend.py
vendored
Normal file
190
third_party/sglang/python/sglang/srt/constrained/outlines_backend.py
vendored
Normal file
@@ -0,0 +1,190 @@
|
||||
# Copyright 2023-2024 SGLang Team
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ==============================================================================
|
||||
"""Constrained decoding with outlines backend."""
|
||||
|
||||
import json
|
||||
import logging
|
||||
from typing import Dict, List, Optional, Tuple, Union
|
||||
|
||||
import interegular
|
||||
import torch
|
||||
from outlines.fsm.guide import RegexGuide
|
||||
from outlines.models.transformers import TransformerTokenizer
|
||||
from pydantic import BaseModel
|
||||
|
||||
from sglang.srt.constrained.base_grammar_backend import (
|
||||
BaseGrammarBackend,
|
||||
BaseGrammarObject,
|
||||
InvalidGrammarObject,
|
||||
)
|
||||
from sglang.srt.constrained.outlines_jump_forward import OutlinesJumpForwardMap
|
||||
|
||||
try:
|
||||
from outlines.fsm.json_schema import build_regex_from_schema
|
||||
except ImportError:
|
||||
from outlines_core.fsm.json_schema import build_regex_from_schema
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OutlinesGrammar(BaseGrammarObject):
|
||||
def __init__(
|
||||
self,
|
||||
guide: RegexGuide,
|
||||
jump_forward_map: Union[OutlinesJumpForwardMap, None],
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.guide = guide
|
||||
self.jump_forward_map = jump_forward_map
|
||||
self.state = 0
|
||||
|
||||
def accept_token(self, token: int):
|
||||
self.state = self.guide.get_next_state(self.state, token)
|
||||
|
||||
def allocate_vocab_mask(
|
||||
self, vocab_size: int, batch_size: int, device
|
||||
) -> torch.Tensor:
|
||||
return torch.zeros(batch_size, vocab_size, dtype=torch.bool, device=device)
|
||||
|
||||
@staticmethod
|
||||
def move_vocab_mask(vocab_mask: torch.Tensor, device) -> torch.Tensor:
|
||||
return vocab_mask
|
||||
|
||||
def fill_vocab_mask(self, vocab_mask: torch.Tensor, idx: int) -> None:
|
||||
tokens = torch.tensor(
|
||||
self.guide.get_next_instruction(self.state).tokens, dtype=torch.int64
|
||||
).to(vocab_mask.device, non_blocking=True)
|
||||
vocab_mask = vocab_mask[idx]
|
||||
vocab_mask.fill_(1)
|
||||
vocab_mask.scatter_(0, tokens, torch.zeros_like(tokens, dtype=torch.bool))
|
||||
|
||||
@staticmethod
|
||||
def apply_vocab_mask(logits: torch.Tensor, vocab_mask: torch.Tensor):
|
||||
logits.masked_fill_(vocab_mask, float("-inf"))
|
||||
|
||||
def copy(self):
|
||||
return OutlinesGrammar(self.guide, self.jump_forward_map)
|
||||
|
||||
def try_jump_forward(self, tokenizer) -> Optional[Tuple]:
|
||||
if not self.jump_forward_map:
|
||||
return None
|
||||
|
||||
jump_forward_bytes = self.jump_forward_map.jump_forward_byte(self.state)
|
||||
if jump_forward_bytes is None or len(jump_forward_bytes) <= 1:
|
||||
return None
|
||||
|
||||
# preprocess the jump forward string
|
||||
suffix_bytes = []
|
||||
continuation_range = range(0x80, 0xC0)
|
||||
cur_state = self.state
|
||||
while (
|
||||
len(jump_forward_bytes) and jump_forward_bytes[0][0] in continuation_range
|
||||
):
|
||||
# continuation bytes
|
||||
byte_edge = jump_forward_bytes.pop(0)
|
||||
suffix_bytes.append(byte_edge[0])
|
||||
cur_state = byte_edge[1]
|
||||
|
||||
suffix_tokens = [f"<0x{hex(b)[2:].upper()}>" for b in suffix_bytes]
|
||||
suffix_ids = tokenizer.convert_tokens_to_ids(suffix_tokens)
|
||||
return suffix_ids, cur_state
|
||||
|
||||
def jump_forward_str_state(self, helper: Tuple[List[int], str]) -> Tuple[str, int]:
|
||||
_, cur_state = helper
|
||||
return self.jump_forward_map.jump_forward_symbol(cur_state)
|
||||
|
||||
def jump_and_retokenize(
|
||||
self, old_output_ids: List[int], new_output_ids: List[int], next_state: int
|
||||
):
|
||||
self.state = next_state
|
||||
|
||||
|
||||
class OutlinesGrammarBackend(BaseGrammarBackend):
|
||||
def __init__(
|
||||
self,
|
||||
tokenizer,
|
||||
whitespace_pattern: str | None,
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
try:
|
||||
self.outlines_tokenizer = TransformerTokenizer(tokenizer)
|
||||
except AttributeError:
|
||||
# FIXME: tmp fix for chatglm2 & chatglm3 (pad_token_id=0)
|
||||
origin_pad_token_id = tokenizer.pad_token_id
|
||||
|
||||
def fset(self, value):
|
||||
self._value = value
|
||||
|
||||
type(tokenizer).pad_token_id = property(
|
||||
fget=type(tokenizer).pad_token_id.fget, fset=fset
|
||||
)
|
||||
self.outlines_tokenizer = TransformerTokenizer(tokenizer)
|
||||
self.outlines_tokenizer.tokenizer.pad_token_id = origin_pad_token_id
|
||||
self.outlines_tokenizer.pad_token_id = origin_pad_token_id
|
||||
self.outlines_tokenizer.pad_token = (
|
||||
self.outlines_tokenizer.tokenizer.pad_token
|
||||
)
|
||||
self.outlines_tokenizer.vocabulary = (
|
||||
self.outlines_tokenizer.tokenizer.get_vocab()
|
||||
)
|
||||
self.whitespace_pattern = whitespace_pattern
|
||||
|
||||
def _compile_regex(self, regex: str) -> BaseGrammarObject:
|
||||
try:
|
||||
if hasattr(RegexGuide, "from_regex"):
|
||||
# outlines >= 0.1.1
|
||||
guide = RegexGuide.from_regex(regex, self.outlines_tokenizer)
|
||||
else:
|
||||
# outlines <= 0.0.46
|
||||
guide = RegexGuide(regex, self.outlines_tokenizer)
|
||||
except interegular.patterns.InvalidSyntax as e:
|
||||
logger.error(f"Hit invalid regex schema: {regex=}, {e=}")
|
||||
return InvalidGrammarObject(str(e))
|
||||
|
||||
jump_forward_map = None
|
||||
return OutlinesGrammar(guide, jump_forward_map)
|
||||
|
||||
def dispatch_ebnf(self, key_string: str):
|
||||
return super().dispatch_ebnf(key_string)
|
||||
|
||||
def dispatch_structural_tag(self, key_string: str):
|
||||
return super().dispatch_structural_tag(key_string)
|
||||
|
||||
def dispatch_json(self, key_string: str):
|
||||
try:
|
||||
regex = build_regex_from_object(
|
||||
key_string,
|
||||
whitespace_pattern=self.whitespace_pattern,
|
||||
)
|
||||
except (NotImplementedError, json.decoder.JSONDecodeError, ValueError) as e:
|
||||
logger.error(f"Hit invalid json_schema: {key_string=}, {e=}")
|
||||
return InvalidGrammarObject(str(e))
|
||||
return self._compile_regex(regex)
|
||||
|
||||
def dispatch_regex(self, key_string: str):
|
||||
return self._compile_regex(key_string)
|
||||
|
||||
|
||||
def build_regex_from_object(
|
||||
object: Union[str, BaseModel, Dict], whitespace_pattern: Optional[str] = None
|
||||
):
|
||||
if isinstance(object, type(BaseModel)):
|
||||
schema = json.dumps(object.model_json_schema())
|
||||
elif isinstance(object, Dict):
|
||||
schema = json.dumps(object)
|
||||
else:
|
||||
schema = object
|
||||
return build_regex_from_schema(schema, whitespace_pattern)
|
||||
200
third_party/sglang/python/sglang/srt/constrained/outlines_jump_forward.py
vendored
Normal file
200
third_party/sglang/python/sglang/srt/constrained/outlines_jump_forward.py
vendored
Normal file
@@ -0,0 +1,200 @@
|
||||
# Copyright 2023-2024 SGLang Team
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ==============================================================================
|
||||
"""
|
||||
Faster constrained decoding with jump forward decoding / compressed finite state machine.
|
||||
Reference: https://lmsys.org/blog/2024-02-05-compressed-fsm/
|
||||
"""
|
||||
|
||||
import dataclasses
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
from typing import Optional
|
||||
|
||||
import interegular
|
||||
from interegular import InvalidSyntax
|
||||
from outlines.caching import cache
|
||||
|
||||
from sglang.srt.utils import get_bool_env_var
|
||||
|
||||
try:
|
||||
# outlines >= 0.1.0
|
||||
from outlines_core.fsm.outlines_core_rs import FSMInfo
|
||||
from outlines_core.fsm.regex import make_byte_level_fsm, make_deterministic_fsm
|
||||
except ImportError:
|
||||
# outlines <= 0.0.46
|
||||
from outlines.fsm.regex import FSMInfo, make_byte_level_fsm, make_deterministic_fsm
|
||||
|
||||
IP_REGEX = r"((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)"
|
||||
|
||||
# Env var was set in sglang.srt.server_args.ServerArgs.__post_init__
|
||||
DISABLE_DISK_CACHE = get_bool_env_var("SGLANG_DISABLE_OUTLINES_DISK_CACHE", "true")
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class JumpEdge:
|
||||
symbol: str = None
|
||||
symbol_next_state: int = None
|
||||
byte: int = None
|
||||
byte_next_state: int = None
|
||||
|
||||
|
||||
def disk_cache(expire: Optional[float] = None, typed=False, ignore=()):
|
||||
if not DISABLE_DISK_CACHE:
|
||||
return cache(expire, typed, ignore)
|
||||
else:
|
||||
return lambda fn: None
|
||||
|
||||
|
||||
@disk_cache()
|
||||
def init_state_to_jump_forward(regex_string):
|
||||
try:
|
||||
regex_pattern = interegular.parse_pattern(regex_string)
|
||||
except InvalidSyntax as e:
|
||||
logger.warning(f"skip invalid regex: {regex_string}, {e=}")
|
||||
return
|
||||
|
||||
byte_fsm = make_byte_level_fsm(regex_pattern.to_fsm().reduce(), keep_utf8=True)
|
||||
regex_fsm, _ = make_deterministic_fsm(byte_fsm)
|
||||
|
||||
fsm_info: FSMInfo = regex_fsm.fsm_info
|
||||
|
||||
symbol_to_id = fsm_info.alphabet_symbol_mapping
|
||||
id_to_symbol = {}
|
||||
for symbol, id_ in symbol_to_id.items():
|
||||
id_to_symbol.setdefault(id_, []).append(symbol)
|
||||
|
||||
transitions = fsm_info.transitions
|
||||
|
||||
outgoings_ct = defaultdict(int)
|
||||
# NOTE(lsyin): Final states can lead to terminate, so they have one outgoing edge naturally
|
||||
for s in fsm_info.finals:
|
||||
outgoings_ct[s] = 1
|
||||
|
||||
state_to_jump_forward = {}
|
||||
for (state, id_), next_state in transitions.items():
|
||||
if id_ == fsm_info.alphabet_anything_value:
|
||||
# Arbitrarily symbol cannot be recognized as jump forward
|
||||
continue
|
||||
|
||||
symbols = id_to_symbol[id_]
|
||||
for c in symbols:
|
||||
if len(c) > 1:
|
||||
# Skip byte level transitions like c = "5E"
|
||||
continue
|
||||
|
||||
outgoings_ct[state] += 1
|
||||
if outgoings_ct[state] > 1:
|
||||
if state in state_to_jump_forward:
|
||||
del state_to_jump_forward[state]
|
||||
break
|
||||
|
||||
state_to_jump_forward[state] = JumpEdge(
|
||||
symbol=c,
|
||||
symbol_next_state=next_state,
|
||||
)
|
||||
|
||||
# Process the byte level jump forward
|
||||
outgoings_ct = defaultdict(int)
|
||||
for s in fsm_info.finals:
|
||||
outgoings_ct[s] = 1
|
||||
|
||||
for (state, id_), next_state in transitions.items():
|
||||
if id_ == fsm_info.alphabet_anything_value:
|
||||
continue
|
||||
symbols = id_to_symbol[id_]
|
||||
for c in symbols:
|
||||
byte_ = None
|
||||
if len(c) == 1 and ord(c) < 0x80:
|
||||
# ASCII character
|
||||
byte_ = ord(c)
|
||||
elif len(c) > 1:
|
||||
# FIXME: This logic is due to the leading \x00
|
||||
# https://github.com/outlines-dev/outlines/pull/930
|
||||
byte_ = int(symbols[0][1:], 16)
|
||||
|
||||
if byte_ is not None:
|
||||
outgoings_ct[state] += 1
|
||||
if outgoings_ct[state] > 1:
|
||||
if state in state_to_jump_forward:
|
||||
del state_to_jump_forward[state]
|
||||
break
|
||||
e = state_to_jump_forward.get(state, JumpEdge())
|
||||
e.byte = byte_
|
||||
e.byte_next_state = next_state
|
||||
state_to_jump_forward[state] = e
|
||||
|
||||
return state_to_jump_forward
|
||||
|
||||
|
||||
class OutlinesJumpForwardMap:
|
||||
def __init__(self, regex_string):
|
||||
self.state_to_jump_forward = init_state_to_jump_forward(regex_string)
|
||||
|
||||
def jump_forward_symbol(self, state):
|
||||
jump_forward_str = ""
|
||||
next_state = state
|
||||
while state in self.state_to_jump_forward:
|
||||
e = self.state_to_jump_forward[state]
|
||||
if e.symbol is None:
|
||||
break
|
||||
jump_forward_str += e.symbol
|
||||
next_state = e.symbol_next_state
|
||||
state = next_state
|
||||
|
||||
return jump_forward_str, next_state
|
||||
|
||||
def jump_forward_byte(self, state):
|
||||
if state not in self.state_to_jump_forward:
|
||||
return None
|
||||
|
||||
jump_forward_bytes = []
|
||||
next_state = None
|
||||
while state in self.state_to_jump_forward:
|
||||
e = self.state_to_jump_forward[state]
|
||||
assert e.byte is not None and e.byte_next_state is not None
|
||||
jump_forward_bytes.append((e.byte, e.byte_next_state))
|
||||
next_state = e.byte_next_state
|
||||
state = next_state
|
||||
|
||||
return jump_forward_bytes
|
||||
|
||||
def is_jump_forward_symbol_state(self, state):
|
||||
return (
|
||||
state in self.state_to_jump_forward
|
||||
and self.state_to_jump_forward[state].symbol is not None
|
||||
)
|
||||
|
||||
|
||||
def test_main(regex_string):
|
||||
jump_forward_map = OutlinesJumpForwardMap(regex_string)
|
||||
for state, e in jump_forward_map.state_to_jump_forward.items():
|
||||
if e.symbol is not None:
|
||||
jump_forward_str, next_state = jump_forward_map.jump_forward_symbol(state)
|
||||
print(f"{state} -> {next_state}", jump_forward_str)
|
||||
bytes_ = jump_forward_map.jump_forward_byte(state)
|
||||
print(f"{state} -> {bytes_[-1][1]}", [hex(b) for b, _ in bytes_])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import outlines
|
||||
|
||||
outlines.caching.clear_cache()
|
||||
test_main(r"The google's DNS sever address is " + IP_REGEX)
|
||||
test_main(r"霍格沃茨特快列车|霍比特人比尔博")
|
||||
# 霍格: \xe9\x9c\x8d \xe6\xa0\xbc ...
|
||||
# 霍比: \xe9\x9c\x8d \xe6\xaf\x94 ...
|
||||
|
||||
test_main(r"[-+]?[0-9]+[ ]*")
|
||||
124
third_party/sglang/python/sglang/srt/constrained/reasoner_grammar_backend.py
vendored
Normal file
124
third_party/sglang/python/sglang/srt/constrained/reasoner_grammar_backend.py
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
# Copyright 2023-2024 SGLang Team
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ==============================================================================
|
||||
"""The baseclass of a backend for reasoner grammar-guided constrained decoding."""
|
||||
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
import torch
|
||||
|
||||
from .base_grammar_backend import (
|
||||
BaseGrammarBackend,
|
||||
BaseGrammarObject,
|
||||
InvalidGrammarObject,
|
||||
)
|
||||
|
||||
|
||||
class ReasonerGrammarObject(BaseGrammarObject):
|
||||
def __init__(self, grammar: BaseGrammarObject, think_end_id: int):
|
||||
super().__init__()
|
||||
self.grammar = grammar
|
||||
self.think_end_id = think_end_id
|
||||
# -1 means thinking has not ended yet
|
||||
# 0 means just ended thinking in the last token
|
||||
# + means number of tokens after thinking ended
|
||||
self.tokens_after_think_end = -1
|
||||
|
||||
def maybe_init_reasoning(self, reasoning: bool):
|
||||
self.tokens_after_think_end = -1 if reasoning else 0
|
||||
|
||||
def transfer_state(self, token: int) -> int:
|
||||
if self.tokens_after_think_end == -1 and token == self.think_end_id:
|
||||
self.tokens_after_think_end = 0
|
||||
elif self.tokens_after_think_end >= 0:
|
||||
self.tokens_after_think_end += 1
|
||||
|
||||
def rollback_state(self):
|
||||
if self.tokens_after_think_end == 0:
|
||||
self.tokens_after_think_end = -1
|
||||
elif self.tokens_after_think_end > 0:
|
||||
self.tokens_after_think_end -= 1
|
||||
|
||||
def accept_token(self, token: int):
|
||||
if self.tokens_after_think_end >= 0:
|
||||
self.grammar.accept_token(token)
|
||||
self.transfer_state(token)
|
||||
|
||||
def is_terminated(self):
|
||||
return self.grammar.is_terminated()
|
||||
|
||||
def rollback(self, k):
|
||||
steps_after_think = min(k, self.tokens_after_think_end)
|
||||
if steps_after_think > 0:
|
||||
self.grammar.rollback(steps_after_think)
|
||||
|
||||
for _ in range(k):
|
||||
self.rollback_state()
|
||||
|
||||
def allocate_vocab_mask(
|
||||
self, vocab_size: int, batch_size: int, device
|
||||
) -> torch.Tensor:
|
||||
return self.grammar.allocate_vocab_mask(vocab_size, batch_size, device)
|
||||
|
||||
def fill_vocab_mask(self, vocab_mask: torch.Tensor, idx: int) -> None:
|
||||
if self.tokens_after_think_end >= 0:
|
||||
self.grammar.fill_vocab_mask(vocab_mask, idx)
|
||||
|
||||
def move_vocab_mask(self, vocab_mask: torch.Tensor, device) -> torch.Tensor:
|
||||
return self.grammar.move_vocab_mask(vocab_mask, device)
|
||||
|
||||
@property
|
||||
def apply_vocab_mask(self):
|
||||
return self.grammar.apply_vocab_mask
|
||||
|
||||
def copy(self) -> BaseGrammarObject:
|
||||
return ReasonerGrammarObject(self.grammar.copy(), self.think_end_id)
|
||||
|
||||
@property
|
||||
def finished(self):
|
||||
return self.grammar.finished
|
||||
|
||||
@finished.setter
|
||||
def finished(self, finished):
|
||||
self.grammar.finished = finished
|
||||
|
||||
def try_jump_forward(self, tokenizer):
|
||||
return self.grammar.try_jump_forward(tokenizer)
|
||||
|
||||
def jump_forward_str_state(self, helper):
|
||||
return self.grammar.jump_forward_str_state(helper)
|
||||
|
||||
def jump_and_retokenize(
|
||||
self, old_output_ids: List[int], new_output_ids: List[int], next_state: int
|
||||
):
|
||||
return self.grammar.jump_and_retokenize(
|
||||
old_output_ids, new_output_ids, next_state
|
||||
)
|
||||
|
||||
|
||||
class ReasonerGrammarBackend(BaseGrammarBackend):
|
||||
def __init__(self, grammar_backend: BaseGrammarBackend, think_end_id):
|
||||
super().__init__()
|
||||
self.grammar_backend = grammar_backend
|
||||
self.think_end_id = think_end_id
|
||||
|
||||
def _init_value_dispatch(
|
||||
self, key: Tuple[str, str], reasoning: bool
|
||||
) -> Optional[BaseGrammarObject]:
|
||||
ret = self.grammar_backend._init_value_dispatch(key, reasoning)
|
||||
# avoid wrapping invalid grammar, so that the scheduler can detect it
|
||||
if ret is None or isinstance(ret, InvalidGrammarObject):
|
||||
return ret
|
||||
obj = ReasonerGrammarObject(ret, self.think_end_id)
|
||||
obj.maybe_init_reasoning(reasoning)
|
||||
return obj
|
||||
141
third_party/sglang/python/sglang/srt/constrained/triton_ops/bitmask_ops.py
vendored
Normal file
141
third_party/sglang/python/sglang/srt/constrained/triton_ops/bitmask_ops.py
vendored
Normal file
@@ -0,0 +1,141 @@
|
||||
# Adapt from
|
||||
# https://github.com/mlc-ai/xgrammar/blob/v0.1.17/python/xgrammar/kernels/apply_token_bitmask_inplace_triton.py
|
||||
|
||||
from typing import List, Optional, Union
|
||||
|
||||
import torch
|
||||
import triton
|
||||
import triton.language as tl
|
||||
|
||||
from sglang.srt.utils import get_device_core_count
|
||||
|
||||
|
||||
@triton.jit
|
||||
def apply_token_bitmask_inplace_kernel(
|
||||
logits_ptr,
|
||||
bitmask_ptr,
|
||||
indices_ptr,
|
||||
num_rows,
|
||||
vocab_size,
|
||||
logits_strides,
|
||||
bitmask_strides,
|
||||
NUM_SMS: tl.constexpr,
|
||||
BLOCK_SIZE: tl.constexpr,
|
||||
):
|
||||
"""Apply a bitmask to logits in-place using Triton. The bitmask is a 01 bitwise compressed tensor,
|
||||
where 0 means the token is masked and 1 means the token is not masked. After applying the bitmask,
|
||||
the masked logits will be set to -inf.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
logits_ptr : tl.tensor
|
||||
Pointer to the logits tensor to apply the bitmask to.
|
||||
|
||||
bitmask_ptr : tl.tensor
|
||||
Pointer to the bitmask tensor to apply.
|
||||
|
||||
indices_ptr : Optional[tl.tensor]
|
||||
Optional pointer to indices tensor specifying which rows to apply the mask to.
|
||||
|
||||
num_rows : int
|
||||
Number of rows to process. If indices_ptr is provided, this is the number of unique indices.
|
||||
|
||||
vocab_size : int
|
||||
Size of the vocabulary dimension. If the logits does not have a vocab padding, this is the
|
||||
same as the logits's second dimension. Otherwise, this is the actual size of the vocabulary.
|
||||
|
||||
logits_strides : int
|
||||
Stride between rows in the logits tensor.
|
||||
|
||||
bitmask_strides : int
|
||||
Stride between rows in the bitmask tensor.
|
||||
|
||||
NUM_SMS : int
|
||||
Number of streaming multiprocessors to use.
|
||||
|
||||
BLOCK_SIZE : int
|
||||
Size of processing blocks.
|
||||
"""
|
||||
|
||||
pid = tl.program_id(0)
|
||||
num_blocks = tl.cdiv(vocab_size, BLOCK_SIZE)
|
||||
for work_id in tl.range(pid, num_rows * num_blocks, NUM_SMS):
|
||||
row_id = work_id // num_blocks
|
||||
block_offset = (work_id % num_blocks) * BLOCK_SIZE
|
||||
batch_id = row_id if indices_ptr is None else tl.load(indices_ptr + row_id)
|
||||
offsets = block_offset + tl.arange(0, BLOCK_SIZE)
|
||||
bitmask_offsets = block_offset // 32 + tl.arange(0, BLOCK_SIZE // 32)
|
||||
vocab_mask = offsets < vocab_size
|
||||
packed_bitmask_mask = bitmask_offsets < bitmask_strides
|
||||
packed_bitmask = tl.load(
|
||||
bitmask_ptr + batch_id * bitmask_strides + bitmask_offsets,
|
||||
packed_bitmask_mask,
|
||||
)
|
||||
bitmask = ((packed_bitmask[:, None] >> (tl.arange(0, 32)[None, :])) & 1) == 0
|
||||
bitmask = bitmask.reshape(BLOCK_SIZE)
|
||||
|
||||
tl.store(
|
||||
logits_ptr + batch_id * logits_strides + offsets,
|
||||
-float("inf"),
|
||||
vocab_mask & bitmask,
|
||||
)
|
||||
|
||||
|
||||
def apply_token_bitmask_inplace_triton(
|
||||
logits: torch.Tensor,
|
||||
bitmask: torch.Tensor,
|
||||
indices: Optional[Union[List[int], torch.Tensor]] = None,
|
||||
):
|
||||
NUM_SMS = get_device_core_count()
|
||||
BLOCK_SIZE = 4096
|
||||
BITS_PER_BLOCK = 32
|
||||
|
||||
# Check input dtype
|
||||
assert bitmask.dtype == torch.int32, "bitmask must be of type int32"
|
||||
|
||||
# Check input tensor shapes.
|
||||
logits_shape = logits.shape
|
||||
bitmask_shape = bitmask.shape
|
||||
if logits.ndim == 1:
|
||||
logits_shape = (1, logits_shape[0])
|
||||
if bitmask.ndim == 1:
|
||||
bitmask_shape = (1, bitmask_shape[0])
|
||||
|
||||
required_bitmask_width = (logits_shape[1] + BITS_PER_BLOCK - 1) // BITS_PER_BLOCK
|
||||
assert required_bitmask_width >= bitmask_shape[1], (
|
||||
f"Bitmask width too large: allow at most {required_bitmask_width} int32s for "
|
||||
f"logits' width {logits_shape[1]}, but got {bitmask_shape[1]}"
|
||||
)
|
||||
|
||||
vocab_size = min(logits_shape[1], bitmask_shape[1] * BITS_PER_BLOCK)
|
||||
|
||||
num_rows = None
|
||||
if isinstance(indices, list) or isinstance(indices, torch.Tensor):
|
||||
indices = torch.tensor(indices, dtype=torch.int32, device=logits.device)
|
||||
num_rows = indices.shape[0]
|
||||
else:
|
||||
assert (
|
||||
logits_shape[0] == bitmask_shape[0]
|
||||
), f"batch size mismatch: logits {logits_shape[0]} vs bitmask {bitmask_shape[0]}"
|
||||
num_rows = logits_shape[0]
|
||||
|
||||
if NUM_SMS > 0:
|
||||
grid = (NUM_SMS,)
|
||||
else:
|
||||
num_blocks = triton.cdiv(vocab_size, BLOCK_SIZE)
|
||||
grid = (num_rows * num_blocks,)
|
||||
NUM_SMS = triton.next_power_of_2(grid[0])
|
||||
|
||||
apply_token_bitmask_inplace_kernel[grid](
|
||||
logits,
|
||||
bitmask,
|
||||
indices,
|
||||
num_rows,
|
||||
vocab_size,
|
||||
logits_shape[1],
|
||||
bitmask_shape[1],
|
||||
NUM_SMS,
|
||||
BLOCK_SIZE,
|
||||
num_warps=BLOCK_SIZE // 32 // (16 // logits.element_size()),
|
||||
num_stages=3,
|
||||
)
|
||||
12
third_party/sglang/python/sglang/srt/constrained/utils.py
vendored
Normal file
12
third_party/sglang/python/sglang/srt/constrained/utils.py
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
from typing import Dict
|
||||
|
||||
|
||||
def is_legacy_structural_tag(obj: Dict) -> bool:
|
||||
# test whether an object is a legacy structural tag
|
||||
# see `StructuralTagResponseFormat` at `sglang.srt.entrypoints.openai.protocol`
|
||||
if obj.get("structures", None) is not None:
|
||||
assert obj.get("triggers", None) is not None
|
||||
return True
|
||||
else:
|
||||
assert obj.get("format", None) is not None
|
||||
return False
|
||||
353
third_party/sglang/python/sglang/srt/constrained/xgrammar_backend.py
vendored
Normal file
353
third_party/sglang/python/sglang/srt/constrained/xgrammar_backend.py
vendored
Normal file
@@ -0,0 +1,353 @@
|
||||
# Copyright 2023-2024 SGLang Team
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ==============================================================================
|
||||
"""Constrained decoding with xgrammar backend."""
|
||||
|
||||
import dataclasses
|
||||
import json
|
||||
import logging
|
||||
from typing import Dict, List, Optional, Tuple, Union
|
||||
|
||||
import torch
|
||||
from xgrammar import (
|
||||
CompiledGrammar,
|
||||
GrammarCompiler,
|
||||
GrammarMatcher,
|
||||
StructuralTagItem,
|
||||
TokenizerInfo,
|
||||
allocate_token_bitmask,
|
||||
)
|
||||
|
||||
from sglang.srt.constrained.base_grammar_backend import (
|
||||
BaseGrammarBackend,
|
||||
BaseGrammarObject,
|
||||
GrammarStats,
|
||||
InvalidGrammarObject,
|
||||
)
|
||||
from sglang.srt.constrained.utils import is_legacy_structural_tag
|
||||
from sglang.srt.utils import is_hip
|
||||
|
||||
_is_hip = is_hip()
|
||||
if _is_hip:
|
||||
from sgl_kernel import apply_token_bitmask_inplace_cuda
|
||||
else:
|
||||
from sglang.srt.constrained.triton_ops.bitmask_ops import (
|
||||
apply_token_bitmask_inplace_triton,
|
||||
)
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
MAX_ROLLBACK_TOKENS = 200
|
||||
|
||||
|
||||
class XGrammarGrammar(BaseGrammarObject):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
matcher: GrammarMatcher,
|
||||
vocab_size: int,
|
||||
ctx: CompiledGrammar,
|
||||
override_stop_tokens: Optional[Union[List[int], int]],
|
||||
key_string: Optional[str] = None, # TODO (sk): for debugging, remove later
|
||||
grammar_stats: Optional[GrammarStats] = GrammarStats(),
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.matcher = matcher
|
||||
self.vocab_size = vocab_size
|
||||
self.ctx = ctx
|
||||
self.override_stop_tokens = override_stop_tokens
|
||||
self.accepted_tokens = []
|
||||
self.key_string = key_string
|
||||
self.grammar_stats = grammar_stats
|
||||
|
||||
def accept_token(self, token: int):
|
||||
if not self.is_terminated():
|
||||
self.current_token = token
|
||||
accepted = self.matcher.accept_token(token)
|
||||
if not accepted:
|
||||
# log for debugging
|
||||
raise ValueError(
|
||||
f"Tokens not accepted: {token}\n"
|
||||
f"Accepted tokens: {self.accepted_tokens}\n"
|
||||
f"Key string: {self.key_string}"
|
||||
)
|
||||
else:
|
||||
self.accepted_tokens.append(token)
|
||||
|
||||
def rollback(self, k: int):
|
||||
self.matcher.rollback(k)
|
||||
self.accepted_tokens = self.accepted_tokens[:-k]
|
||||
|
||||
def is_terminated(self):
|
||||
return self.matcher.is_terminated()
|
||||
|
||||
def allocate_vocab_mask(
|
||||
self, vocab_size: int, batch_size: int, device
|
||||
) -> torch.Tensor:
|
||||
return allocate_token_bitmask(batch_size, vocab_size)
|
||||
|
||||
def fill_vocab_mask(self, vocab_mask: torch.Tensor, idx: int) -> None:
|
||||
self.matcher.fill_next_token_bitmask(vocab_mask, idx)
|
||||
|
||||
@staticmethod
|
||||
def move_vocab_mask(vocab_mask: torch.Tensor, device) -> torch.Tensor:
|
||||
return vocab_mask.to(device, non_blocking=True)
|
||||
|
||||
def apply_vocab_mask(self, logits: torch.Tensor, vocab_mask: torch.Tensor) -> None:
|
||||
if logits.device.type in {"cuda", "npu", "xpu", "musa"}:
|
||||
if _is_hip:
|
||||
apply_token_bitmask_inplace_cuda(logits, vocab_mask)
|
||||
else:
|
||||
apply_token_bitmask_inplace_triton(logits, vocab_mask)
|
||||
else:
|
||||
raise RuntimeError(f"Unsupported device: {logits.device.type}")
|
||||
|
||||
def copy(self):
|
||||
matcher = GrammarMatcher(
|
||||
self.ctx,
|
||||
max_rollback_tokens=MAX_ROLLBACK_TOKENS,
|
||||
override_stop_tokens=self.override_stop_tokens,
|
||||
)
|
||||
if grammar_stats := self.grammar_stats:
|
||||
grammar_stats = dataclasses.replace(
|
||||
grammar_stats, is_cache_hit=True, tree_traversal_time=[]
|
||||
)
|
||||
return XGrammarGrammar(
|
||||
matcher,
|
||||
self.vocab_size,
|
||||
self.ctx,
|
||||
self.override_stop_tokens,
|
||||
self.key_string,
|
||||
grammar_stats,
|
||||
)
|
||||
|
||||
def try_jump_forward(self, tokenizer) -> Optional[Tuple[List[int], str]]:
|
||||
s = self.matcher.find_jump_forward_string()
|
||||
if s:
|
||||
return [], s
|
||||
return None
|
||||
|
||||
def jump_forward_str_state(self, helper: Tuple[List[int], str]) -> Tuple[str, int]:
|
||||
_, data = helper
|
||||
return data, -1
|
||||
|
||||
def jump_and_retokenize(
|
||||
self, old_output_ids: List[int], new_output_ids: List[int], next_state: int
|
||||
):
|
||||
k = 0
|
||||
for i, old_id in enumerate(old_output_ids):
|
||||
if old_id == new_output_ids[i]:
|
||||
k = i + 1
|
||||
else:
|
||||
break
|
||||
|
||||
# rollback to the last token that is the same
|
||||
if k < len(old_output_ids):
|
||||
self.matcher.rollback(len(old_output_ids) - k)
|
||||
|
||||
for i in range(k, len(new_output_ids)):
|
||||
assert self.matcher.accept_token(new_output_ids[i])
|
||||
|
||||
def __repr__(self):
|
||||
return f"XGrammarGrammar({self.key_string=}, {self.accepted_tokens=}, {self.current_token=})"
|
||||
|
||||
|
||||
class TokenizerNotSupportedError(Exception):
|
||||
"""Raised when tokenizer is not supported by XGrammar backend."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class XGrammarGrammarBackend(BaseGrammarBackend):
|
||||
def __init__(
|
||||
self,
|
||||
tokenizer,
|
||||
vocab_size: int,
|
||||
model_eos_token_ids: Optional[List[int]] = None,
|
||||
any_whitespace: bool = True,
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
if hasattr(tokenizer, "init_xgrammar"):
|
||||
# For special tokenizer
|
||||
tokenizer_info, override_stop_tokens = tokenizer.init_xgrammar()
|
||||
|
||||
if tokenizer_info is None:
|
||||
# Not supported tokenizer
|
||||
raise TokenizerNotSupportedError(
|
||||
f"Tokenizer type {type(tokenizer).__name__} is not supported by XGrammar"
|
||||
)
|
||||
else:
|
||||
# Create TokenizerInfo with model's EOS tokens as the authoritative stop tokens
|
||||
# This ensures consistency between what the model considers EOS and what XGrammar uses
|
||||
try:
|
||||
tokenizer_info = TokenizerInfo.from_huggingface(
|
||||
tokenizer, vocab_size=vocab_size, stop_token_ids=model_eos_token_ids
|
||||
)
|
||||
override_stop_tokens = None
|
||||
except Exception as e:
|
||||
raise TokenizerNotSupportedError(
|
||||
f"Failed to create XGrammar TokenizerInfo from tokenizer: {e}"
|
||||
)
|
||||
|
||||
self.grammar_compiler = GrammarCompiler(tokenizer_info=tokenizer_info)
|
||||
self.vocab_size = vocab_size
|
||||
self.override_stop_tokens = override_stop_tokens
|
||||
self.any_whitespace = any_whitespace
|
||||
|
||||
@staticmethod
|
||||
def _sanitize_structural_format(structural_format):
|
||||
"""Recursively replace missing json_schema fields with an empty schema."""
|
||||
if not isinstance(structural_format, dict):
|
||||
return
|
||||
|
||||
fmt_type = structural_format.get("type")
|
||||
if fmt_type in {"json_schema", "qwen_xml_parameter"}:
|
||||
if structural_format.get("json_schema") is None:
|
||||
structural_format["json_schema"] = {}
|
||||
|
||||
if fmt_type == "tag":
|
||||
XGrammarGrammarBackend._sanitize_structural_format(
|
||||
structural_format.get("content")
|
||||
)
|
||||
elif fmt_type in {"sequence", "or"}:
|
||||
for element in structural_format.get("elements", []):
|
||||
XGrammarGrammarBackend._sanitize_structural_format(element)
|
||||
elif fmt_type in {"triggered_tags", "tags_with_separator"}:
|
||||
for tag in structural_format.get("tags", []):
|
||||
XGrammarGrammarBackend._sanitize_structural_format(tag)
|
||||
|
||||
@staticmethod
|
||||
def _sanitize_structural_tag_structures(structural_tag: Dict) -> None:
|
||||
for structure in structural_tag.get("structures", []):
|
||||
if structure.get("schema") is None:
|
||||
structure["schema"] = {}
|
||||
|
||||
def _from_context(
|
||||
self, ctx: CompiledGrammar, key_string: str, grammar_stats: GrammarStats
|
||||
) -> XGrammarGrammar:
|
||||
matcher = GrammarMatcher(
|
||||
ctx,
|
||||
max_rollback_tokens=MAX_ROLLBACK_TOKENS,
|
||||
override_stop_tokens=self.override_stop_tokens,
|
||||
)
|
||||
return XGrammarGrammar(
|
||||
matcher,
|
||||
self.vocab_size,
|
||||
ctx,
|
||||
self.override_stop_tokens,
|
||||
key_string,
|
||||
grammar_stats,
|
||||
)
|
||||
|
||||
def dispatch_json(self, key_string: str) -> BaseGrammarObject:
|
||||
try:
|
||||
if key_string == "$$ANY$$":
|
||||
# Note: This builtin JSON grammar includes *all* valid JSON (including, for example, arrays at the root)
|
||||
ctx = self.grammar_compiler.compile_builtin_json_grammar()
|
||||
else:
|
||||
ctx = self.grammar_compiler.compile_json_schema(
|
||||
schema=key_string, any_whitespace=self.any_whitespace
|
||||
)
|
||||
|
||||
except (RuntimeError, json.decoder.JSONDecodeError, UnicodeDecodeError) as e:
|
||||
logger.error(f"Hit invalid json_schema: {key_string=}, {e=}")
|
||||
return InvalidGrammarObject(str(e))
|
||||
return self._from_context(ctx, key_string, GrammarStats(dispatch_type="json"))
|
||||
|
||||
def dispatch_ebnf(self, key_string: str) -> BaseGrammarObject:
|
||||
try:
|
||||
ctx = self.grammar_compiler.compile_grammar(key_string)
|
||||
except RuntimeError as e:
|
||||
logger.error(f"Hit invalid ebnf: {key_string=}, {e=}")
|
||||
return InvalidGrammarObject(str(e))
|
||||
return self._from_context(ctx, key_string, GrammarStats(dispatch_type="ebnf"))
|
||||
|
||||
def dispatch_regex(self, key_string: str) -> BaseGrammarObject:
|
||||
try:
|
||||
ctx = self.grammar_compiler.compile_regex(key_string)
|
||||
except RuntimeError as e:
|
||||
logger.error(f"Hit invalid regex: {key_string=}, {e=}")
|
||||
return InvalidGrammarObject(str(e))
|
||||
return self._from_context(ctx, key_string, GrammarStats(dispatch_type="regex"))
|
||||
|
||||
def dispatch_structural_tag(self, key_string: str) -> BaseGrammarObject:
|
||||
try:
|
||||
# TODO(dark): it's REALLY stupid to construct object from string and decode it again
|
||||
structural_tag = json.loads(key_string)
|
||||
if is_legacy_structural_tag(structural_tag):
|
||||
self._sanitize_structural_tag_structures(structural_tag)
|
||||
tags = [
|
||||
StructuralTagItem(
|
||||
begin=structure["begin"],
|
||||
schema=json.dumps(structure["schema"]),
|
||||
end=structure["end"],
|
||||
)
|
||||
for structure in structural_tag["structures"]
|
||||
]
|
||||
ctx = self.grammar_compiler.compile_structural_tag(
|
||||
tags, structural_tag["triggers"]
|
||||
)
|
||||
else:
|
||||
format_dict = structural_tag.get("format")
|
||||
if isinstance(format_dict, dict):
|
||||
self._sanitize_structural_format(format_dict)
|
||||
structural_tag["format"] = format_dict
|
||||
key_string = json.dumps(structural_tag)
|
||||
ctx = self.grammar_compiler.compile_structural_tag(key_string)
|
||||
except (RuntimeError, json.decoder.JSONDecodeError) as e:
|
||||
logger.error(f"Hit invalid structural_tag: {key_string=}, {e=}")
|
||||
return InvalidGrammarObject(str(e))
|
||||
return self._from_context(
|
||||
ctx, key_string, GrammarStats(dispatch_type="structural_tag")
|
||||
)
|
||||
|
||||
def reset(self):
|
||||
super().reset()
|
||||
self.grammar_compiler.clear_cache()
|
||||
|
||||
|
||||
def demo_test():
|
||||
from transformers import AutoConfig, AutoTokenizer
|
||||
|
||||
from sglang.test.test_utils import DEFAULT_MODEL_NAME_FOR_TEST
|
||||
|
||||
tokenizer = AutoTokenizer.from_pretrained(DEFAULT_MODEL_NAME_FOR_TEST)
|
||||
hf_config = AutoConfig.from_pretrained(DEFAULT_MODEL_NAME_FOR_TEST)
|
||||
|
||||
# Should use vocab size from model config
|
||||
vocab_size = hf_config.vocab_size
|
||||
eos_token_id = tokenizer.eos_token_id
|
||||
|
||||
backend = XGrammarGrammarBackend(
|
||||
tokenizer, vocab_size=vocab_size, model_eos_token_ids=[eos_token_id]
|
||||
)
|
||||
regex = r"hello (world|there)"
|
||||
grammar = backend.dispatch_regex(regex)
|
||||
tokens = [
|
||||
tokenizer.encode(t, add_special_tokens=False)[0] for t in ["hello", " world"]
|
||||
]
|
||||
|
||||
# Test termination
|
||||
grammar.accept_token(tokens[0]) # accept "hello"
|
||||
grammar.accept_token(tokens[1]) # accept " world"
|
||||
grammar.accept_token(eos_token_id) # accept EOS
|
||||
assert grammar.is_terminated()
|
||||
|
||||
# Test rollback the terminated state
|
||||
grammar.rollback(1)
|
||||
assert not grammar.is_terminated()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
demo_test()
|
||||
0
third_party/sglang/python/sglang/srt/debug_utils/__init__.py
vendored
Normal file
0
third_party/sglang/python/sglang/srt/debug_utils/__init__.py
vendored
Normal file
9
third_party/sglang/python/sglang/srt/debug_utils/comparator/__init__.py
vendored
Normal file
9
third_party/sglang/python/sglang/srt/debug_utils/comparator/__init__.py
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
from sglang.srt.debug_utils.comparator.aligner.entrypoint.traced_types import ( # noqa: F401
|
||||
TracedAlignerPlan,
|
||||
)
|
||||
from sglang.srt.debug_utils.comparator.aligner.entrypoint.types import ( # noqa: F401
|
||||
AlignerPlan,
|
||||
)
|
||||
from sglang.srt.debug_utils.comparator.output_types import ComparisonTensorRecord
|
||||
|
||||
ComparisonTensorRecord.model_rebuild()
|
||||
4
third_party/sglang/python/sglang/srt/debug_utils/comparator/__main__.py
vendored
Normal file
4
third_party/sglang/python/sglang/srt/debug_utils/comparator/__main__.py
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
from sglang.srt.debug_utils.comparator.entrypoint import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
0
third_party/sglang/python/sglang/srt/debug_utils/comparator/aligner/__init__.py
vendored
Normal file
0
third_party/sglang/python/sglang/srt/debug_utils/comparator/aligner/__init__.py
vendored
Normal file
218
third_party/sglang/python/sglang/srt/debug_utils/comparator/aligner/axis_aligner.py
vendored
Normal file
218
third_party/sglang/python/sglang/srt/debug_utils/comparator/aligner/axis_aligner.py
vendored
Normal file
@@ -0,0 +1,218 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
import torch
|
||||
from einops import rearrange
|
||||
|
||||
from sglang.srt.debug_utils.comparator.dims_spec import (
|
||||
_FUSED_NAME_SEP,
|
||||
SEQ_DIM_NAME,
|
||||
TOKEN_DIM_NAME,
|
||||
DimSpec,
|
||||
_SingletonDimUtil,
|
||||
parse_dims,
|
||||
)
|
||||
from sglang.srt.debug_utils.comparator.log_sink import log_sink
|
||||
from sglang.srt.debug_utils.comparator.utils import Pair, _FrozenBase
|
||||
|
||||
# --- types ---
|
||||
|
||||
|
||||
class AxisAlignerPlan(_FrozenBase):
|
||||
pattern: Pair[Optional[str]] # einops pattern per side, None = no-op
|
||||
|
||||
|
||||
# --- planner ---
|
||||
|
||||
|
||||
def compute_axis_aligner_plan(
|
||||
dims_str_pair: Pair[Optional[str]],
|
||||
) -> Optional[AxisAlignerPlan]:
|
||||
if dims_str_pair.x is None or dims_str_pair.y is None:
|
||||
return None
|
||||
|
||||
dims_pair: Pair[str] = Pair(x=dims_str_pair.x, y=dims_str_pair.y)
|
||||
specs_pair: Pair[list[DimSpec]] = dims_pair.map(lambda s: parse_dims(s).dims)
|
||||
|
||||
if not _semantic_names_match(specs_pair):
|
||||
return None
|
||||
|
||||
# Canonical dim order follows y; fused groups stay fused (flatten, not unflatten).
|
||||
canonical_order: Optional[list[str]] = _build_canonical_order(specs_pair)
|
||||
if canonical_order is None:
|
||||
return None
|
||||
|
||||
pattern: Pair[Optional[str]] = specs_pair.map(
|
||||
lambda specs: _build_side_pattern(specs=specs, canonical_order=canonical_order)
|
||||
)
|
||||
|
||||
if pattern.x is None and pattern.y is None:
|
||||
return None
|
||||
|
||||
return AxisAlignerPlan(pattern=pattern)
|
||||
|
||||
|
||||
_SEQ_DIM_EQUIVALENCES: frozenset[frozenset[str]] = frozenset(
|
||||
{
|
||||
frozenset({SEQ_DIM_NAME, TOKEN_DIM_NAME}), # s ≡ t
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _normalize_dim_name(name: str) -> str:
|
||||
for equiv_set in _SEQ_DIM_EQUIVALENCES:
|
||||
if name in equiv_set:
|
||||
return min(equiv_set)
|
||||
return name
|
||||
|
||||
|
||||
def _semantic_names_match(specs_pair: Pair[list[DimSpec]]) -> bool:
|
||||
"""Check that both sides share the same semantic name set (ignoring squeeze dims)."""
|
||||
names_pair: Pair[list[str]] = specs_pair.map(_expand_and_skip_squeeze)
|
||||
|
||||
if set(map(_normalize_dim_name, names_pair.x)) == set(
|
||||
map(_normalize_dim_name, names_pair.y)
|
||||
):
|
||||
return True
|
||||
|
||||
# Local import to avoid circular dependency:
|
||||
# output_types -> aligner/entrypoint/types -> axis_aligner -> output_types
|
||||
from sglang.srt.debug_utils.comparator.output_types import ErrorLog
|
||||
|
||||
log_sink.add(
|
||||
ErrorLog(
|
||||
category="axis_aligner_dim_mismatch",
|
||||
message=(
|
||||
f"AxisAligner: dim name sets differ (x={names_pair.x}, y={names_pair.y}), "
|
||||
f"skipping axis swap"
|
||||
),
|
||||
)
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
def _expand_and_skip_squeeze(specs: list[DimSpec]) -> list[str]:
|
||||
"""Expand DimSpecs to flat semantic names, skipping squeeze dims."""
|
||||
return [
|
||||
name
|
||||
for spec in specs
|
||||
if not _SingletonDimUtil.is_squeeze(spec)
|
||||
for name in spec.sub_dims
|
||||
]
|
||||
|
||||
|
||||
def _build_canonical_order(specs_pair: Pair[list[DimSpec]]) -> Optional[list[str]]:
|
||||
"""Build canonical dim order following y, preferring fused representation.
|
||||
|
||||
Each element is either a plain name (``"c"``) or a fused placeholder (``"a___b"``).
|
||||
Fused groups from *either* side are merged — the separate side must flatten.
|
||||
Squeeze dims are excluded.
|
||||
|
||||
Returns ``None`` if the two sides have overlapping but incompatible fused groups
|
||||
(e.g. x fuses ``(a*b)`` while y fuses ``(b*c)``).
|
||||
"""
|
||||
# Map each sub-dim name → (placeholder, siblings) from both sides
|
||||
fused_lookup: dict[str, tuple[str, frozenset[str]]] = {}
|
||||
for spec in (*specs_pair.x, *specs_pair.y):
|
||||
if spec.is_fused:
|
||||
placeholder: str = spec.sanitized_name
|
||||
siblings: frozenset[str] = frozenset(spec.sub_dims)
|
||||
for sub_name in spec.sub_dims:
|
||||
existing: Optional[tuple[str, frozenset[str]]] = fused_lookup.get(
|
||||
sub_name
|
||||
)
|
||||
if existing is not None and existing[1] != siblings:
|
||||
from sglang.srt.debug_utils.comparator.output_types import ErrorLog
|
||||
|
||||
log_sink.add(
|
||||
ErrorLog(
|
||||
category="axis_aligner_fused_conflict",
|
||||
message=(
|
||||
f"AxisAligner: overlapping fused groups for sub-dim {sub_name!r} "
|
||||
f"({existing[0]} vs {placeholder}), skipping axis alignment"
|
||||
),
|
||||
)
|
||||
)
|
||||
return None
|
||||
fused_lookup.setdefault(sub_name, (placeholder, siblings))
|
||||
|
||||
result: list[str] = []
|
||||
consumed: set[str] = set()
|
||||
|
||||
for spec in specs_pair.y:
|
||||
if _SingletonDimUtil.is_squeeze(spec):
|
||||
continue
|
||||
|
||||
names: list[str] = spec.sub_dims
|
||||
if any(n in consumed for n in names):
|
||||
continue
|
||||
|
||||
entry: Optional[tuple[str, frozenset[str]]] = fused_lookup.get(names[0])
|
||||
if entry is not None:
|
||||
fused_placeholder, sibs = entry
|
||||
result.append(fused_placeholder)
|
||||
consumed.update(sibs)
|
||||
else:
|
||||
result.append(_normalize_dim_name(spec.name))
|
||||
consumed.update(names)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _build_side_pattern(
|
||||
*, specs: list[DimSpec], canonical_order: list[str]
|
||||
) -> Optional[str]:
|
||||
"""Build an einops pattern for one side to reach ``canonical_order``.
|
||||
|
||||
Fused specs become their placeholder; separate specs that belong to a fused group
|
||||
stay as individual names on the LHS and become ``(a b)`` on the RHS (einops flatten).
|
||||
Squeeze dims (``1``) appear on the LHS but are dropped from the RHS.
|
||||
"""
|
||||
source_tokens: list[str] = [spec.sanitized_name for spec in specs]
|
||||
|
||||
# Map normalized dim names back to this side's original names so that
|
||||
# einops patterns use consistent identifiers on LHS and RHS.
|
||||
norm_to_original: dict[str, str] = {
|
||||
_normalize_dim_name(spec.name): spec.name for spec in specs
|
||||
}
|
||||
|
||||
def _to_side_name(token: str) -> str:
|
||||
return norm_to_original.get(token, token)
|
||||
|
||||
# Build per-side target: replace fused placeholders with ``(a b)`` only if this side
|
||||
# has the sub-dims as separate (non-fused) names in the source
|
||||
fused_placeholders: set[str] = {
|
||||
spec.sanitized_name for spec in specs if spec.is_fused
|
||||
}
|
||||
translated_order: list[str] = [_to_side_name(t) for t in canonical_order]
|
||||
target_tokens: list[str] = [
|
||||
(
|
||||
f"({t.replace(_FUSED_NAME_SEP, ' ')})"
|
||||
if _FUSED_NAME_SEP in t and t not in fused_placeholders
|
||||
else t
|
||||
)
|
||||
for t in translated_order
|
||||
]
|
||||
|
||||
if source_tokens == target_tokens:
|
||||
return None
|
||||
|
||||
return f"{' '.join(source_tokens)} -> {' '.join(target_tokens)}"
|
||||
|
||||
|
||||
# --- executor ---
|
||||
|
||||
|
||||
def execute_axis_aligner_plan(
|
||||
tensor: torch.Tensor, plan: AxisAlignerPlan, *, side: str
|
||||
) -> torch.Tensor:
|
||||
if side not in ("x", "y"):
|
||||
raise ValueError(f"side must be 'x' or 'y', got {side!r}")
|
||||
|
||||
pattern: Optional[str] = plan.pattern.x if side == "x" else plan.pattern.y
|
||||
|
||||
if pattern is not None:
|
||||
tensor = rearrange(tensor.rename(None), pattern)
|
||||
|
||||
return tensor
|
||||
0
third_party/sglang/python/sglang/srt/debug_utils/comparator/aligner/entrypoint/__init__.py
vendored
Normal file
0
third_party/sglang/python/sglang/srt/debug_utils/comparator/aligner/entrypoint/__init__.py
vendored
Normal file
212
third_party/sglang/python/sglang/srt/debug_utils/comparator/aligner/entrypoint/executor.py
vendored
Normal file
212
third_party/sglang/python/sglang/srt/debug_utils/comparator/aligner/entrypoint/executor.py
vendored
Normal file
@@ -0,0 +1,212 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import NamedTuple, Optional
|
||||
|
||||
import torch
|
||||
|
||||
from sglang.srt.debug_utils.comparator.aligner.axis_aligner import (
|
||||
execute_axis_aligner_plan,
|
||||
)
|
||||
from sglang.srt.debug_utils.comparator.aligner.entrypoint.traced_types import (
|
||||
TracedAlignerPlan,
|
||||
TracedSidePlan,
|
||||
TracedStepPlan,
|
||||
TracedSubPlan,
|
||||
)
|
||||
from sglang.srt.debug_utils.comparator.aligner.entrypoint.types import (
|
||||
AlignerPerStepPlan,
|
||||
AlignerPerStepSubPlan,
|
||||
AlignerPlan,
|
||||
)
|
||||
from sglang.srt.debug_utils.comparator.aligner.reorderer.executor import (
|
||||
execute_reorderer_plan,
|
||||
)
|
||||
from sglang.srt.debug_utils.comparator.aligner.reorderer.types import ReordererPlan
|
||||
from sglang.srt.debug_utils.comparator.aligner.token_aligner.concat_steps import (
|
||||
execute_token_aligner_concat_steps,
|
||||
)
|
||||
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.executor import (
|
||||
execute_token_aligner,
|
||||
)
|
||||
from sglang.srt.debug_utils.comparator.aligner.unsharder.executor import (
|
||||
UnsharderResult,
|
||||
execute_unsharder_plan,
|
||||
)
|
||||
from sglang.srt.debug_utils.comparator.aligner.unsharder.types import UnsharderPlan
|
||||
from sglang.srt.debug_utils.comparator.output_types import (
|
||||
ReplicatedCheckResult,
|
||||
ShapeSnapshot,
|
||||
)
|
||||
from sglang.srt.debug_utils.comparator.utils import Pair
|
||||
|
||||
|
||||
class StepPlansResult(NamedTuple):
|
||||
tensors: dict[int, torch.Tensor]
|
||||
checks: list[ReplicatedCheckResult]
|
||||
traced_side: TracedSidePlan
|
||||
|
||||
|
||||
class SubPlansResult(NamedTuple):
|
||||
tensor: Optional[torch.Tensor]
|
||||
checks: list[ReplicatedCheckResult]
|
||||
snapshots: list[ShapeSnapshot]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AlignerResult:
|
||||
tensors: Optional[Pair[torch.Tensor]]
|
||||
failed_side_xy: Optional[str] # "x" or "y"; None if success
|
||||
replicated_checks: list[ReplicatedCheckResult] = field(default_factory=list)
|
||||
traced_plan: Optional[TracedAlignerPlan] = None
|
||||
|
||||
|
||||
def execute_aligner_plan(
|
||||
*,
|
||||
tensors_pair: Pair[list[torch.Tensor]],
|
||||
plan: AlignerPlan,
|
||||
) -> AlignerResult:
|
||||
"""Execute unified unshard/reorder + token-align."""
|
||||
all_checks: list[ReplicatedCheckResult] = []
|
||||
|
||||
# Per-side: unshard + reorder -> dict[step, tensor]
|
||||
result_x: StepPlansResult = _execute_step_plans(
|
||||
tensors=tensors_pair.x, step_plans=plan.per_step_plans.x
|
||||
)
|
||||
all_checks.extend(result_x.checks)
|
||||
|
||||
result_y: StepPlansResult = _execute_step_plans(
|
||||
tensors=tensors_pair.y, step_plans=plan.per_step_plans.y
|
||||
)
|
||||
all_checks.extend(result_y.checks)
|
||||
|
||||
traced_plan: TracedAlignerPlan = TracedAlignerPlan(
|
||||
plan=plan,
|
||||
per_side=Pair(x=result_x.traced_side, y=result_y.traced_side),
|
||||
)
|
||||
|
||||
if not result_x.tensors or not result_y.tensors:
|
||||
failed_side_xy: str = "x" if not result_x.tensors else "y"
|
||||
return AlignerResult(
|
||||
tensors=None,
|
||||
failed_side_xy=failed_side_xy,
|
||||
replicated_checks=all_checks,
|
||||
traced_plan=traced_plan,
|
||||
)
|
||||
|
||||
# Cross-side: token alignment (or direct extraction for single-step)
|
||||
step_pair: Pair[dict[int, torch.Tensor]] = Pair(
|
||||
x=result_x.tensors, y=result_y.tensors
|
||||
)
|
||||
combined: Pair[torch.Tensor]
|
||||
if plan.token_aligner_mode == "concat_steps":
|
||||
combined = execute_token_aligner_concat_steps(tensor_of_step_pair=step_pair)
|
||||
elif plan.token_aligner_mode == "smart":
|
||||
assert plan.token_aligner_plan is not None
|
||||
combined = execute_token_aligner(
|
||||
plan=plan.token_aligner_plan,
|
||||
tensor_of_step_pair=step_pair,
|
||||
)
|
||||
else:
|
||||
assert len(result_x.tensors) == 1 and len(result_y.tensors) == 1
|
||||
combined = Pair(
|
||||
x=list(result_x.tensors.values())[0],
|
||||
y=list(result_y.tensors.values())[0],
|
||||
)
|
||||
|
||||
# Cross-side: axis alignment (squeeze singletons + rearrange dim order)
|
||||
if (aligner_plan := plan.axis_aligner_plan) is not None:
|
||||
combined = Pair(
|
||||
x=execute_axis_aligner_plan(tensor=combined.x, plan=aligner_plan, side="x"),
|
||||
y=execute_axis_aligner_plan(tensor=combined.y, plan=aligner_plan, side="y"),
|
||||
)
|
||||
|
||||
return AlignerResult(
|
||||
tensors=combined,
|
||||
failed_side_xy=None,
|
||||
replicated_checks=all_checks,
|
||||
traced_plan=traced_plan,
|
||||
)
|
||||
|
||||
|
||||
def _execute_step_plans(
|
||||
tensors: list[torch.Tensor],
|
||||
step_plans: list[AlignerPerStepPlan],
|
||||
) -> StepPlansResult:
|
||||
result: dict[int, torch.Tensor] = {}
|
||||
all_checks: list[ReplicatedCheckResult] = []
|
||||
traced_steps: list[TracedStepPlan] = []
|
||||
|
||||
for step_plan in step_plans:
|
||||
step_tensors: list[torch.Tensor] = [
|
||||
tensors[i] for i in step_plan.input_object_indices
|
||||
]
|
||||
sub_result: SubPlansResult = execute_sub_plans(
|
||||
tensors=step_tensors, plans=step_plan.sub_plans
|
||||
)
|
||||
all_checks.extend(sub_result.checks)
|
||||
|
||||
traced_subs: list[TracedSubPlan] = [
|
||||
TracedSubPlan(plan=sub_plan, snapshot=snapshot)
|
||||
for sub_plan, snapshot in zip(step_plan.sub_plans, sub_result.snapshots)
|
||||
]
|
||||
traced_steps.append(
|
||||
TracedStepPlan(
|
||||
step=step_plan.step,
|
||||
input_object_indices=step_plan.input_object_indices,
|
||||
sub_plans=traced_subs,
|
||||
)
|
||||
)
|
||||
|
||||
if sub_result.tensor is not None:
|
||||
result[step_plan.step] = sub_result.tensor
|
||||
|
||||
return StepPlansResult(
|
||||
tensors=result,
|
||||
checks=all_checks,
|
||||
traced_side=TracedSidePlan(step_plans=traced_steps),
|
||||
)
|
||||
|
||||
|
||||
def execute_sub_plans(
|
||||
tensors: list[torch.Tensor],
|
||||
plans: list[AlignerPerStepSubPlan],
|
||||
) -> SubPlansResult:
|
||||
if not tensors:
|
||||
return SubPlansResult(tensor=None, checks=[], snapshots=[])
|
||||
|
||||
if not plans:
|
||||
if len(tensors) != 1:
|
||||
return SubPlansResult(tensor=None, checks=[], snapshots=[])
|
||||
return SubPlansResult(tensor=tensors[0], checks=[], snapshots=[])
|
||||
|
||||
current: list[torch.Tensor] = tensors
|
||||
all_checks: list[ReplicatedCheckResult] = []
|
||||
all_snapshots: list[ShapeSnapshot] = []
|
||||
for plan in plans:
|
||||
input_shapes: list[list[int]] = [list(t.shape) for t in current]
|
||||
current, checks = execute_sub_plan(tensors=current, plan=plan)
|
||||
output_shapes: list[list[int]] = [list(t.shape) for t in current]
|
||||
all_checks.extend(checks)
|
||||
all_snapshots.append(
|
||||
ShapeSnapshot(
|
||||
input_shapes=input_shapes,
|
||||
output_shapes=output_shapes,
|
||||
)
|
||||
)
|
||||
|
||||
assert len(current) == 1
|
||||
return SubPlansResult(tensor=current[0], checks=all_checks, snapshots=all_snapshots)
|
||||
|
||||
|
||||
def execute_sub_plan(
|
||||
tensors: list[torch.Tensor],
|
||||
plan: AlignerPerStepSubPlan,
|
||||
) -> tuple[list[torch.Tensor], list[ReplicatedCheckResult]]:
|
||||
if isinstance(plan, UnsharderPlan):
|
||||
unsharder_result: UnsharderResult = execute_unsharder_plan(plan, tensors)
|
||||
return unsharder_result.tensors, unsharder_result.replicated_checks
|
||||
elif isinstance(plan, ReordererPlan):
|
||||
return execute_reorderer_plan(plan, tensors), []
|
||||
else:
|
||||
raise NotImplementedError(f"Unknown {plan=}")
|
||||
134
third_party/sglang/python/sglang/srt/debug_utils/comparator/aligner/entrypoint/planner.py
vendored
Normal file
134
third_party/sglang/python/sglang/srt/debug_utils/comparator/aligner/entrypoint/planner.py
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
from sglang.srt.debug_utils.comparator.aligner.axis_aligner import (
|
||||
AxisAlignerPlan,
|
||||
compute_axis_aligner_plan,
|
||||
)
|
||||
from sglang.srt.debug_utils.comparator.aligner.entrypoint.types import (
|
||||
AlignerPerStepPlan,
|
||||
AlignerPerStepSubPlan,
|
||||
AlignerPlan,
|
||||
)
|
||||
from sglang.srt.debug_utils.comparator.aligner.reorderer.planner import (
|
||||
compute_reorderer_plans,
|
||||
)
|
||||
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.types import (
|
||||
TokenAlignerPlan,
|
||||
)
|
||||
from sglang.srt.debug_utils.comparator.aligner.unsharder.parallel_info import (
|
||||
normalize_parallel_info,
|
||||
)
|
||||
from sglang.srt.debug_utils.comparator.aligner.unsharder.planner import (
|
||||
compute_unsharder_plan,
|
||||
)
|
||||
from sglang.srt.debug_utils.comparator.dims_spec import (
|
||||
DimSpec,
|
||||
DimsSpec,
|
||||
ParallelAxis,
|
||||
_SingletonDimUtil,
|
||||
parse_dims,
|
||||
)
|
||||
from sglang.srt.debug_utils.comparator.utils import Pair
|
||||
|
||||
|
||||
def compute_aligner_plan(
|
||||
*,
|
||||
metas_pair: Pair[list[dict[str, Any]]],
|
||||
token_aligner_mode: Optional[str],
|
||||
token_aligner_plan: Optional[TokenAlignerPlan],
|
||||
thd_seq_lens_by_step_pair: Pair[Optional[dict[int, list[int]]]] = Pair(
|
||||
x=None, y=None
|
||||
),
|
||||
) -> AlignerPlan:
|
||||
dims_str_pair: Pair[Optional[str]] = metas_pair.map(
|
||||
lambda metas: metas[0].get("dims") if metas else None
|
||||
)
|
||||
axis_aligner_plan: Optional[AxisAlignerPlan] = compute_axis_aligner_plan(
|
||||
dims_str_pair=dims_str_pair
|
||||
)
|
||||
|
||||
return AlignerPlan(
|
||||
per_step_plans=Pair(
|
||||
x=_compute_per_step_plans(
|
||||
metas=metas_pair.x,
|
||||
thd_seq_lens_by_step=thd_seq_lens_by_step_pair.x,
|
||||
),
|
||||
y=_compute_per_step_plans(
|
||||
metas=metas_pair.y,
|
||||
thd_seq_lens_by_step=thd_seq_lens_by_step_pair.y,
|
||||
),
|
||||
),
|
||||
token_aligner_mode=token_aligner_mode,
|
||||
token_aligner_plan=token_aligner_plan,
|
||||
axis_aligner_plan=axis_aligner_plan,
|
||||
)
|
||||
|
||||
|
||||
def _compute_per_step_plans(
|
||||
metas: list[dict[str, Any]],
|
||||
*,
|
||||
thd_seq_lens_by_step: Optional[dict[int, list[int]]] = None,
|
||||
) -> list[AlignerPerStepPlan]:
|
||||
step_to_input_indices: dict[int, list[int]] = {}
|
||||
for i, meta in enumerate(metas):
|
||||
step: int = int(meta["step"])
|
||||
step_to_input_indices.setdefault(step, []).append(i)
|
||||
|
||||
result: list[AlignerPerStepPlan] = []
|
||||
for step in sorted(step_to_input_indices):
|
||||
input_indices: list[int] = step_to_input_indices[step]
|
||||
step_metas: list[dict[str, Any]] = [metas[idx] for idx in input_indices]
|
||||
step_seq_lens: Optional[list[int]] = (
|
||||
thd_seq_lens_by_step.get(step) if thd_seq_lens_by_step is not None else None
|
||||
)
|
||||
plans: list[AlignerPerStepSubPlan] = compute_per_step_sub_plans(
|
||||
metas=step_metas,
|
||||
thd_global_seq_lens=step_seq_lens,
|
||||
)
|
||||
result.append(
|
||||
AlignerPerStepPlan(
|
||||
step=step, input_object_indices=input_indices, sub_plans=plans
|
||||
)
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def compute_per_step_sub_plans(
|
||||
metas: list[dict[str, Any]],
|
||||
*,
|
||||
thd_global_seq_lens: Optional[list[int]] = None,
|
||||
) -> list[AlignerPerStepSubPlan]:
|
||||
if not metas or len(metas) == 1:
|
||||
return []
|
||||
|
||||
dims_str = metas[0].get("dims")
|
||||
if dims_str is None:
|
||||
return []
|
||||
|
||||
dims_spec: DimsSpec = parse_dims(dims_str)
|
||||
dim_specs: list[DimSpec] = _SingletonDimUtil.filter_out(dims_spec.dims)
|
||||
replicated_axes: frozenset[ParallelAxis] = dims_spec.replicated_axes
|
||||
parallel_infos = [normalize_parallel_info(meta) for meta in metas]
|
||||
|
||||
dp_axis: ParallelAxis = (
|
||||
ParallelAxis(dims_spec.dp_group_alias)
|
||||
if dims_spec.dp_group_alias
|
||||
else ParallelAxis.DP
|
||||
)
|
||||
|
||||
unsharder_plans = compute_unsharder_plan(
|
||||
dim_specs=dim_specs,
|
||||
parallel_infos=parallel_infos,
|
||||
explicit_replicated_axes=replicated_axes,
|
||||
thd_global_seq_lens=thd_global_seq_lens,
|
||||
dp_filtered_axis=dims_spec.dp_axis,
|
||||
)
|
||||
reorderer_plans = compute_reorderer_plans(
|
||||
dim_specs=dim_specs,
|
||||
parallel_infos=parallel_infos,
|
||||
thd_global_seq_lens=thd_global_seq_lens,
|
||||
)
|
||||
return [*unsharder_plans, *reorderer_plans]
|
||||
37
third_party/sglang/python/sglang/srt/debug_utils/comparator/aligner/entrypoint/traced_types.py
vendored
Normal file
37
third_party/sglang/python/sglang/srt/debug_utils/comparator/aligner/entrypoint/traced_types.py
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
"""Traced wrapper types that embed execution traces (ShapeSnapshots) into plan nodes.
|
||||
|
||||
These types are created *after* execution, pairing each sub-plan with its
|
||||
observed shape snapshot so that downstream formatters never need to manually
|
||||
zip plan + trace by index.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from sglang.srt.debug_utils.comparator.aligner.entrypoint.types import (
|
||||
AlignerPerStepSubPlan,
|
||||
AlignerPlan,
|
||||
)
|
||||
from sglang.srt.debug_utils.comparator.output_types import ShapeSnapshot
|
||||
from sglang.srt.debug_utils.comparator.utils import Pair, _StrictBase
|
||||
|
||||
|
||||
class TracedSubPlan(_StrictBase):
|
||||
plan: AlignerPerStepSubPlan
|
||||
snapshot: Optional[ShapeSnapshot] = None
|
||||
|
||||
|
||||
class TracedStepPlan(_StrictBase):
|
||||
step: int
|
||||
input_object_indices: list[int]
|
||||
sub_plans: list[TracedSubPlan]
|
||||
|
||||
|
||||
class TracedSidePlan(_StrictBase):
|
||||
step_plans: list[TracedStepPlan]
|
||||
|
||||
|
||||
class TracedAlignerPlan(_StrictBase):
|
||||
plan: AlignerPlan
|
||||
per_side: Pair[TracedSidePlan]
|
||||
31
third_party/sglang/python/sglang/srt/debug_utils/comparator/aligner/entrypoint/types.py
vendored
Normal file
31
third_party/sglang/python/sglang/srt/debug_utils/comparator/aligner/entrypoint/types.py
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Annotated, Optional, Union
|
||||
|
||||
from pydantic import Discriminator
|
||||
|
||||
from sglang.srt.debug_utils.comparator.aligner.axis_aligner import AxisAlignerPlan
|
||||
from sglang.srt.debug_utils.comparator.aligner.reorderer.types import ReordererPlan
|
||||
from sglang.srt.debug_utils.comparator.aligner.token_aligner.smart.types import (
|
||||
TokenAlignerPlan,
|
||||
)
|
||||
from sglang.srt.debug_utils.comparator.aligner.unsharder.types import UnsharderPlan
|
||||
from sglang.srt.debug_utils.comparator.utils import Pair, _FrozenBase
|
||||
|
||||
AlignerPerStepSubPlan = Annotated[
|
||||
Union[UnsharderPlan, ReordererPlan],
|
||||
Discriminator("type"),
|
||||
]
|
||||
|
||||
|
||||
class AlignerPerStepPlan(_FrozenBase):
|
||||
step: int
|
||||
input_object_indices: list[int]
|
||||
sub_plans: list[AlignerPerStepSubPlan]
|
||||
|
||||
|
||||
class AlignerPlan(_FrozenBase):
|
||||
per_step_plans: Pair[list[AlignerPerStepPlan]]
|
||||
token_aligner_mode: Optional[str] = None # "concat_steps" | "smart" | None
|
||||
token_aligner_plan: Optional[TokenAlignerPlan] = None
|
||||
axis_aligner_plan: Optional[AxisAlignerPlan] = None
|
||||
0
third_party/sglang/python/sglang/srt/debug_utils/comparator/aligner/reorderer/__init__.py
vendored
Normal file
0
third_party/sglang/python/sglang/srt/debug_utils/comparator/aligner/reorderer/__init__.py
vendored
Normal file
101
third_party/sglang/python/sglang/srt/debug_utils/comparator/aligner/reorderer/executor.py
vendored
Normal file
101
third_party/sglang/python/sglang/srt/debug_utils/comparator/aligner/reorderer/executor.py
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
from typing import Optional
|
||||
|
||||
import torch
|
||||
|
||||
from sglang.srt.debug_utils.comparator.aligner.reorderer.types import (
|
||||
ReordererPlan,
|
||||
ZigzagToNaturalParams,
|
||||
ZigzagToNaturalThdParams,
|
||||
)
|
||||
from sglang.srt.debug_utils.comparator.dims_spec import (
|
||||
resolve_dim_by_name,
|
||||
strip_dim_names,
|
||||
)
|
||||
|
||||
|
||||
def execute_reorderer_plan(
|
||||
plan: ReordererPlan,
|
||||
tensors: list[torch.Tensor],
|
||||
) -> list[torch.Tensor]:
|
||||
if isinstance(plan.params, ZigzagToNaturalThdParams):
|
||||
thd_dim: int = resolve_dim_by_name(tensors[0], plan.params.dim_name)
|
||||
return [
|
||||
_reorder_zigzag_to_natural_thd(
|
||||
tensor,
|
||||
dim=thd_dim,
|
||||
cp_size=plan.params.cp_size,
|
||||
seq_lens=plan.params.seq_lens,
|
||||
)
|
||||
for tensor in tensors
|
||||
]
|
||||
|
||||
if isinstance(plan.params, ZigzagToNaturalParams):
|
||||
dim: int = resolve_dim_by_name(tensors[0], plan.params.dim_name)
|
||||
return [
|
||||
_reorder_zigzag_to_natural(tensor, dim=dim, cp_size=plan.params.cp_size)
|
||||
for tensor in tensors
|
||||
]
|
||||
|
||||
raise ValueError(f"Unsupported reorderer params type: {type(plan.params).__name__}")
|
||||
|
||||
|
||||
def _reorder_zigzag_to_natural_thd(
|
||||
tensor: torch.Tensor, *, dim: int, cp_size: int, seq_lens: list[int]
|
||||
) -> torch.Tensor:
|
||||
"""Undo CP zigzag interleaving for THD (packed-seq) format.
|
||||
|
||||
Each seq in seq_lens is independently reordered from zigzag to natural order
|
||||
along the given dim.
|
||||
"""
|
||||
stripped: torch.Tensor = strip_dim_names(tensor)
|
||||
names: tuple[Optional[str], ...] = tensor.names
|
||||
|
||||
split_sizes: list[int] = list(seq_lens)
|
||||
remainder: int = stripped.shape[dim] - sum(split_sizes)
|
||||
if remainder < 0:
|
||||
raise ValueError(
|
||||
f"sum(seq_lens)={sum(split_sizes)} exceeds tensor dim size "
|
||||
f"{stripped.shape[dim]} along dim={dim}"
|
||||
)
|
||||
if remainder > 0:
|
||||
split_sizes.append(remainder)
|
||||
|
||||
segments: list[torch.Tensor] = list(stripped.split(split_sizes, dim=dim))
|
||||
|
||||
reordered_segments: list[torch.Tensor] = [
|
||||
_reorder_zigzag_to_natural(seg, dim=dim, cp_size=cp_size)
|
||||
for seg in segments[: len(seq_lens)]
|
||||
]
|
||||
|
||||
# Tail padding — pass through unchanged
|
||||
if remainder > 0:
|
||||
reordered_segments.append(segments[-1])
|
||||
|
||||
result: torch.Tensor = torch.cat(reordered_segments, dim=dim)
|
||||
|
||||
if names[0] is not None:
|
||||
result = result.refine_names(*names)
|
||||
return result
|
||||
|
||||
|
||||
def _reorder_zigzag_to_natural(
|
||||
tensor: torch.Tensor, *, dim: int, cp_size: int
|
||||
) -> torch.Tensor:
|
||||
"""Undo CP zigzag interleaving, restoring natural chunk order.
|
||||
|
||||
Generalized from Megatron-LM _undo_attention_load_balancing
|
||||
(megatron/core/ssm/mamba_context_parallel.py:360-373).
|
||||
"""
|
||||
stripped: torch.Tensor = strip_dim_names(tensor)
|
||||
names: tuple[Optional[str], ...] = tensor.names
|
||||
|
||||
num_chunks: int = cp_size * 2
|
||||
chunks: tuple[torch.Tensor, ...] = stripped.chunk(num_chunks, dim=dim)
|
||||
order: list[int] = [2 * i for i in range(cp_size)] + [
|
||||
num_chunks - 2 * i - 1 for i in range(cp_size)
|
||||
]
|
||||
result: torch.Tensor = torch.cat([chunks[i] for i in order], dim=dim)
|
||||
|
||||
if names[0] is not None:
|
||||
result = result.refine_names(*names)
|
||||
return result
|
||||
67
third_party/sglang/python/sglang/srt/debug_utils/comparator/aligner/reorderer/planner.py
vendored
Normal file
67
third_party/sglang/python/sglang/srt/debug_utils/comparator/aligner/reorderer/planner.py
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
from typing import Optional
|
||||
|
||||
from sglang.srt.debug_utils.comparator.aligner.reorderer.types import (
|
||||
ReordererPlan,
|
||||
ZigzagToNaturalParams,
|
||||
ZigzagToNaturalThdParams,
|
||||
)
|
||||
from sglang.srt.debug_utils.comparator.aligner.unsharder.types import AxisInfo
|
||||
from sglang.srt.debug_utils.comparator.dims_spec import (
|
||||
SEQ_DIM_NAME,
|
||||
TOKEN_DIM_NAME,
|
||||
DimSpec,
|
||||
Ordering,
|
||||
ParallelAxis,
|
||||
)
|
||||
|
||||
_ALLOWED_ZIGZAG_DIM_NAMES: set[str] = {SEQ_DIM_NAME, TOKEN_DIM_NAME}
|
||||
|
||||
|
||||
def compute_reorderer_plans(
|
||||
dim_specs: list[DimSpec],
|
||||
parallel_infos: list[dict[ParallelAxis, AxisInfo]],
|
||||
*,
|
||||
thd_global_seq_lens: Optional[list[int]] = None,
|
||||
) -> list[ReordererPlan]:
|
||||
plans: list[ReordererPlan] = []
|
||||
|
||||
for spec in dim_specs:
|
||||
for modifier in spec.parallel_modifiers:
|
||||
if modifier.ordering is None or modifier.ordering == Ordering.NATURAL:
|
||||
continue
|
||||
|
||||
if spec.name not in _ALLOWED_ZIGZAG_DIM_NAMES:
|
||||
raise ValueError(
|
||||
f"Zigzag ordering is only supported on sequence dims "
|
||||
f"(dim name must be one of "
|
||||
f"{sorted(_ALLOWED_ZIGZAG_DIM_NAMES)}), "
|
||||
f"but got dim name {spec.name!r} in {spec}"
|
||||
)
|
||||
|
||||
if modifier.ordering != Ordering.ZIGZAG:
|
||||
raise ValueError(
|
||||
f"Unsupported ordering {modifier.ordering!r} for dim {spec.name!r}"
|
||||
)
|
||||
axis_size: int = parallel_infos[0][modifier.axis].axis_size
|
||||
|
||||
if spec.name == TOKEN_DIM_NAME:
|
||||
if thd_global_seq_lens is None:
|
||||
raise ValueError(
|
||||
"thd_global_seq_lens is required for zigzag reorder on 't' dimension"
|
||||
)
|
||||
params = ZigzagToNaturalThdParams(
|
||||
dim_name=spec.name,
|
||||
cp_size=axis_size,
|
||||
seq_lens=thd_global_seq_lens,
|
||||
)
|
||||
elif spec.name == SEQ_DIM_NAME:
|
||||
params = ZigzagToNaturalParams(dim_name=spec.name, cp_size=axis_size)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Unsupported zigzag dim name {spec.name!r}, "
|
||||
f"expected one of {sorted(_ALLOWED_ZIGZAG_DIM_NAMES)}"
|
||||
)
|
||||
|
||||
plans.append(ReordererPlan(params=params))
|
||||
|
||||
return plans
|
||||
29
third_party/sglang/python/sglang/srt/debug_utils/comparator/aligner/reorderer/types.py
vendored
Normal file
29
third_party/sglang/python/sglang/srt/debug_utils/comparator/aligner/reorderer/types.py
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
from typing import Annotated, Literal, Union
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from sglang.srt.debug_utils.comparator.utils import _FrozenBase
|
||||
|
||||
|
||||
class ZigzagToNaturalParams(_FrozenBase):
|
||||
op: Literal["zigzag_to_natural"] = "zigzag_to_natural"
|
||||
dim_name: str
|
||||
cp_size: int
|
||||
|
||||
|
||||
class ZigzagToNaturalThdParams(_FrozenBase):
|
||||
op: Literal["zigzag_to_natural_thd"] = "zigzag_to_natural_thd"
|
||||
dim_name: str
|
||||
cp_size: int
|
||||
seq_lens: list[int] # unshard-ed per-seq token counts, e.g. [100, 64, 92]
|
||||
|
||||
|
||||
ReordererParams = Annotated[
|
||||
Union[ZigzagToNaturalParams, ZigzagToNaturalThdParams],
|
||||
Field(discriminator="op"),
|
||||
]
|
||||
|
||||
|
||||
class ReordererPlan(_FrozenBase):
|
||||
type: Literal["reorderer"] = "reorderer"
|
||||
params: ReordererParams
|
||||
@@ -0,0 +1,7 @@
|
||||
from sglang.srt.debug_utils.comparator.aligner.token_aligner.concat_steps.executor import (
|
||||
execute_token_aligner_concat_steps,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"execute_token_aligner_concat_steps",
|
||||
]
|
||||
@@ -0,0 +1,45 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
import torch
|
||||
|
||||
from sglang.srt.debug_utils.comparator.dims_spec import (
|
||||
SEQ_DIM_NAME,
|
||||
TOKEN_DIM_NAME,
|
||||
)
|
||||
from sglang.srt.debug_utils.comparator.utils import Pair
|
||||
|
||||
_UNNAMED_TOKEN_DIM_FALLBACK: int = 0
|
||||
|
||||
|
||||
def execute_token_aligner_concat_steps(
|
||||
tensor_of_step_pair: Pair[dict[int, torch.Tensor]],
|
||||
) -> Pair[torch.Tensor]:
|
||||
"""Concat all steps in order, then truncate to min(total_x, total_y) tokens."""
|
||||
some_tensor: torch.Tensor = next(iter(tensor_of_step_pair.x.values()))
|
||||
token_dim: int = _resolve_token_dim(some_tensor)
|
||||
|
||||
concatenated: Pair[torch.Tensor] = tensor_of_step_pair.map(
|
||||
lambda d: _concat_steps(d, dim=token_dim)
|
||||
)
|
||||
common: int = min(concatenated.x.shape[token_dim], concatenated.y.shape[token_dim])
|
||||
return concatenated.map(lambda t: t.narrow(dim=token_dim, start=0, length=common))
|
||||
|
||||
|
||||
def _resolve_token_dim(tensor: torch.Tensor) -> int:
|
||||
"""Find the token/seq dim index. Falls back to dim 0 for unnamed tensors or
|
||||
tensors without a recognised token/seq dim."""
|
||||
if tensor.names[0] is None:
|
||||
return _UNNAMED_TOKEN_DIM_FALLBACK
|
||||
|
||||
names: tuple[Optional[str], ...] = tensor.names
|
||||
for candidate in (TOKEN_DIM_NAME, SEQ_DIM_NAME):
|
||||
if candidate in names:
|
||||
return list(names).index(candidate)
|
||||
|
||||
return _UNNAMED_TOKEN_DIM_FALLBACK
|
||||
|
||||
|
||||
def _concat_steps(tensor_of_step: dict[int, torch.Tensor], *, dim: int) -> torch.Tensor:
|
||||
return torch.cat([tensor_of_step[s] for s in sorted(tensor_of_step)], dim=dim)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user