chore: import upstream snapshot with attribution
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,300 @@
|
||||
# Copyright 2023-2024 SGLang Team
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ==============================================================================
|
||||
"""Utilities for auto-deriving argparse CLI arguments from dataclass fields.
|
||||
|
||||
Usage::
|
||||
|
||||
from sglang.srt.arg_groups.arg_utils import A, Arg, add_cli_args_from_dataclass
|
||||
|
||||
@dataclasses.dataclass
|
||||
class ServerArgs:
|
||||
# Simple fields — bare string is the help text:
|
||||
host: A[str, "The host of the HTTP server."] = "127.0.0.1"
|
||||
port: A[int, "The port of the HTTP server."] = 30000
|
||||
trust_remote_code: A[bool, "Whether to allow custom models."] = False
|
||||
tokenizer_path: A[Optional[str], "The path of the tokenizer."] = None
|
||||
|
||||
# Fields with extra metadata — use Arg(...):
|
||||
model_path: A[str, Arg(help="Path to model weights.", aliases=["--model"])]
|
||||
load_format: A[str, Arg(help="Format.", choices=CHOICES)] = "auto"
|
||||
|
||||
@staticmethod
|
||||
def add_cli_args(parser):
|
||||
add_cli_args_from_dataclass(parser, ServerArgs)
|
||||
|
||||
``A`` is a short alias for ``typing.Annotated``. A bare ``str`` inside the
|
||||
annotation is equivalent to ``Arg(help=that_string)``.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import functools
|
||||
import types
|
||||
from typing import (
|
||||
Annotated,
|
||||
Any,
|
||||
Callable,
|
||||
List,
|
||||
Literal,
|
||||
Optional,
|
||||
Union,
|
||||
get_args,
|
||||
get_origin,
|
||||
get_type_hints,
|
||||
)
|
||||
|
||||
A = Annotated
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class Arg:
|
||||
"""CLI argument metadata attached to a dataclass field via ``Annotated``."""
|
||||
|
||||
help: str = ""
|
||||
choices: Optional[list] = None
|
||||
aliases: Optional[List[str]] = None
|
||||
cli_name: Optional[str] = None
|
||||
type_parser: Optional[Callable] = None
|
||||
nargs: Optional[str] = None
|
||||
required: Optional[bool] = None
|
||||
action: Optional[Any] = None
|
||||
action_kwargs: Optional[dict] = None
|
||||
const: Optional[Any] = None
|
||||
# When True, this field is skipped by add_cli_args_from_dataclass.
|
||||
# Use for fields that have no CLI surface (e.g. injected via Python only).
|
||||
no_cli: bool = False
|
||||
# When True, this field may be written by config resolution (model
|
||||
# overrides and post-process passes): it is part of the whitelist accepted
|
||||
# by the declaration stash, and its resolved value materializes onto the
|
||||
# field at the end of __post_init__.
|
||||
resolvable: bool = False
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def resolvable_fields(cls) -> frozenset:
|
||||
"""Names of ``cls`` dataclass fields whose ``Arg`` metadata declares
|
||||
``resolvable=True`` — the whitelist for config resolution.
|
||||
|
||||
Non-dataclass types (e.g. mock config objects in tests) have no Arg
|
||||
metadata and yield an empty whitelist."""
|
||||
if not dataclasses.is_dataclass(cls):
|
||||
return frozenset()
|
||||
hints = get_type_hints(cls, include_extras=True)
|
||||
names = set()
|
||||
for field in dataclasses.fields(cls):
|
||||
_, arg = _unwrap_annotated(hints.get(field.name, field.type))
|
||||
if arg is not None and arg.resolvable:
|
||||
names.add(field.name)
|
||||
return frozenset(names)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Internal helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_MISSING = dataclasses.MISSING
|
||||
|
||||
|
||||
def _unwrap_annotated(tp):
|
||||
"""Return (inner_type, Arg | None) from ``Annotated[T, Arg(...)]``.
|
||||
|
||||
Also accepts a bare string as shorthand: ``Annotated[T, "help text"]``
|
||||
is equivalent to ``Annotated[T, Arg(help="help text")]``.
|
||||
"""
|
||||
origin = get_origin(tp)
|
||||
if origin is Annotated:
|
||||
args = get_args(tp)
|
||||
inner = args[0]
|
||||
for a in args[1:]:
|
||||
if isinstance(a, Arg):
|
||||
return inner, a
|
||||
if isinstance(a, str):
|
||||
return inner, Arg(help=a)
|
||||
return inner, None
|
||||
return tp, None
|
||||
|
||||
|
||||
def _unwrap_optional(tp):
|
||||
"""If tp is Optional[X] (i.e. Union[X, None]), return (X, True). Else (tp, False)."""
|
||||
origin = get_origin(tp)
|
||||
is_union = origin is Union or (
|
||||
hasattr(types, "UnionType") and origin is types.UnionType
|
||||
)
|
||||
if is_union:
|
||||
args = get_args(tp)
|
||||
non_none = [a for a in args if a is not type(None)]
|
||||
if len(non_none) == 1:
|
||||
return non_none[0], True
|
||||
return tp, False
|
||||
|
||||
|
||||
def _unwrap_literal(tp):
|
||||
"""If tp is Literal[...], return list of values. Else None."""
|
||||
origin = get_origin(tp)
|
||||
if origin is Literal:
|
||||
return list(get_args(tp))
|
||||
return None
|
||||
|
||||
|
||||
def _infer_type_func(tp):
|
||||
"""Map a Python type annotation to an argparse ``type=`` callable."""
|
||||
if tp is str:
|
||||
return str
|
||||
if tp is int:
|
||||
return int
|
||||
if tp is float:
|
||||
return float
|
||||
return str
|
||||
|
||||
|
||||
def _field_default(field):
|
||||
"""Return the default value for a dataclass field, or _MISSING."""
|
||||
if field.default is not _MISSING:
|
||||
return field.default
|
||||
if field.default_factory is not _MISSING:
|
||||
return field.default_factory()
|
||||
return _MISSING
|
||||
|
||||
|
||||
def _field_to_cli_name(name: str) -> str:
|
||||
"""Convert a field name like ``model_path`` to ``--model-path``."""
|
||||
return "--" + name.replace("_", "-")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Public API
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def add_cli_args_from_dataclass(parser, cls, *, fields: Optional[List[str]] = None):
|
||||
"""Add argparse arguments for every ``A[T, "help"]`` or ``A[T, Arg(...)]`` field.
|
||||
|
||||
Fields without an ``Arg`` or bare-string annotation are silently skipped —
|
||||
they must still be registered manually (this allows incremental migration).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
parser : argparse.ArgumentParser
|
||||
cls : dataclass type
|
||||
fields : optional list of field names to include. If None, all fields with
|
||||
``Arg`` annotations are included.
|
||||
"""
|
||||
hints = get_type_hints(cls, include_extras=True)
|
||||
|
||||
for field in dataclasses.fields(cls):
|
||||
if fields is not None and field.name not in fields:
|
||||
continue
|
||||
|
||||
hint = hints.get(field.name)
|
||||
if hint is None:
|
||||
continue
|
||||
|
||||
raw_type, arg_meta = _unwrap_annotated(hint)
|
||||
if arg_meta is None:
|
||||
continue
|
||||
if arg_meta.no_cli:
|
||||
continue
|
||||
|
||||
cli_name = arg_meta.cli_name or _field_to_cli_name(field.name)
|
||||
names = [cli_name] + (arg_meta.aliases or [])
|
||||
default = _field_default(field)
|
||||
# Anchor dest to the field name so argparse stores the value
|
||||
# under the dataclass attribute directly, even when cli_name
|
||||
# differs (e.g. --tensor-parallel-size → tp_size).
|
||||
auto_dest = cli_name.lstrip("-").replace("-", "_")
|
||||
dest_kwarg = {"dest": field.name} if field.name != auto_dest else {}
|
||||
|
||||
# Handle custom action
|
||||
if arg_meta.action is not None:
|
||||
kwargs = {
|
||||
"action": arg_meta.action,
|
||||
"help": arg_meta.help,
|
||||
**dest_kwarg,
|
||||
}
|
||||
if default is not _MISSING:
|
||||
kwargs["default"] = default
|
||||
if arg_meta.action_kwargs:
|
||||
kwargs.update(arg_meta.action_kwargs)
|
||||
parser.add_argument(*names, **kwargs)
|
||||
continue
|
||||
|
||||
# Unwrap Optional
|
||||
inner_type, is_optional = _unwrap_optional(raw_type)
|
||||
|
||||
# Check for Literal — auto-derive choices
|
||||
literal_vals = _unwrap_literal(inner_type)
|
||||
if literal_vals is not None:
|
||||
choices = arg_meta.choices or literal_vals
|
||||
# Infer type from first literal value
|
||||
val_type = type(literal_vals[0]) if literal_vals else str
|
||||
type_func = arg_meta.type_parser or _infer_type_func(val_type)
|
||||
kwargs = dict(
|
||||
type=type_func, choices=choices, help=arg_meta.help, **dest_kwarg
|
||||
)
|
||||
if default is not _MISSING:
|
||||
kwargs["default"] = default
|
||||
if arg_meta.const is not None:
|
||||
kwargs["const"] = arg_meta.const
|
||||
parser.add_argument(*names, **kwargs)
|
||||
continue
|
||||
|
||||
# Check for List[X] — but skip if type_parser is set (the parser
|
||||
# handles the whole value as a single string, e.g. json_list_type).
|
||||
origin = get_origin(inner_type)
|
||||
if (origin is list or origin is List) and arg_meta.type_parser is None:
|
||||
elem_args = get_args(inner_type)
|
||||
elem_type = elem_args[0] if elem_args else str
|
||||
type_func = _infer_type_func(elem_type)
|
||||
nargs = arg_meta.nargs or "+"
|
||||
kwargs = dict(
|
||||
type=type_func,
|
||||
nargs=nargs,
|
||||
help=arg_meta.help,
|
||||
**dest_kwarg,
|
||||
)
|
||||
if arg_meta.choices:
|
||||
kwargs["choices"] = arg_meta.choices
|
||||
if default is not _MISSING:
|
||||
kwargs["default"] = default
|
||||
if arg_meta.const is not None:
|
||||
kwargs["const"] = arg_meta.const
|
||||
parser.add_argument(*names, **kwargs)
|
||||
continue
|
||||
|
||||
# Bool → store_true
|
||||
if inner_type is bool:
|
||||
kwargs = dict(action="store_true", help=arg_meta.help, **dest_kwarg)
|
||||
if default is not _MISSING:
|
||||
kwargs["default"] = default
|
||||
parser.add_argument(*names, **kwargs)
|
||||
continue
|
||||
|
||||
# Scalar types (str, int, float, etc.)
|
||||
type_func = arg_meta.type_parser or _infer_type_func(inner_type)
|
||||
kwargs = dict(type=type_func, help=arg_meta.help, **dest_kwarg)
|
||||
if arg_meta.choices:
|
||||
kwargs["choices"] = arg_meta.choices
|
||||
if arg_meta.nargs:
|
||||
kwargs["nargs"] = arg_meta.nargs
|
||||
if default is not _MISSING:
|
||||
kwargs["default"] = default
|
||||
if arg_meta.const is not None:
|
||||
kwargs["const"] = arg_meta.const
|
||||
if (
|
||||
arg_meta.required is True
|
||||
or (arg_meta.required is None and default is _MISSING)
|
||||
) and any(name.startswith("-") for name in names):
|
||||
kwargs["required"] = True
|
||||
parser.add_argument(*names, **kwargs)
|
||||
@@ -0,0 +1,110 @@
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LoRAPathAction(argparse.Action):
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
lora_paths = []
|
||||
if values:
|
||||
assert isinstance(values, list), "Expected a list of LoRA paths."
|
||||
for lora_path in values:
|
||||
lora_path = lora_path.strip()
|
||||
if lora_path.startswith("{") and lora_path.endswith("}"):
|
||||
obj = json.loads(lora_path)
|
||||
assert "lora_path" in obj and "lora_name" in obj, (
|
||||
f"{repr(lora_path)} looks like a JSON str, "
|
||||
"but it does not contain 'lora_name' and 'lora_path' keys."
|
||||
)
|
||||
lora_paths.append(obj)
|
||||
else:
|
||||
lora_paths.append(lora_path)
|
||||
|
||||
setattr(namespace, self.dest, lora_paths)
|
||||
|
||||
|
||||
def print_deprecated_warning(message: str):
|
||||
logger.warning(f"\033[1;33m{message}\033[0m")
|
||||
|
||||
|
||||
class DeprecatedAction(argparse.Action):
|
||||
def __init__(self, option_strings, dest, nargs=0, **kwargs):
|
||||
super(DeprecatedAction, self).__init__(
|
||||
option_strings, dest, nargs=nargs, **kwargs
|
||||
)
|
||||
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
print_deprecated_warning(
|
||||
f"The command line argument '{option_string}' is deprecated and will be removed in future versions."
|
||||
)
|
||||
|
||||
|
||||
class DeprecatedStoreTrueAction(argparse.Action):
|
||||
"""Deprecated flag that still stores True and prints a warning."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
option_strings,
|
||||
dest,
|
||||
new_flag=None,
|
||||
nargs=0,
|
||||
const=True,
|
||||
default=False,
|
||||
**kwargs,
|
||||
):
|
||||
self.new_flag = new_flag
|
||||
super().__init__(
|
||||
option_strings, dest, nargs=nargs, const=const, default=default, **kwargs
|
||||
)
|
||||
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
replacement = f" Use '{self.new_flag}' instead." if self.new_flag else ""
|
||||
print_deprecated_warning(
|
||||
f"'{option_string}' is deprecated and will be removed in a future release.{replacement}"
|
||||
)
|
||||
setattr(namespace, self.dest, True)
|
||||
|
||||
|
||||
class DeprecatedStoreConstAction(argparse.Action):
|
||||
"""Deprecated boolean flag that stores a fixed string/value into ``dest``
|
||||
and prints a warning. Used to translate a legacy boolean flag into a
|
||||
setting on the new per-phase config dict (e.g.
|
||||
``--disable-piecewise-cuda-graph`` -> ``cuda_graph_backend_prefill="disabled"``)."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
option_strings,
|
||||
dest,
|
||||
new_flag=None,
|
||||
const_value=None,
|
||||
nargs=0,
|
||||
default=None,
|
||||
**kwargs,
|
||||
):
|
||||
self.new_flag = new_flag
|
||||
self.const_value = const_value
|
||||
super().__init__(option_strings, dest, nargs=nargs, default=default, **kwargs)
|
||||
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
replacement = f" Use '{self.new_flag}' instead." if self.new_flag else ""
|
||||
print_deprecated_warning(
|
||||
f"'{option_string}' is deprecated and will be removed in a future release.{replacement}"
|
||||
)
|
||||
setattr(namespace, self.dest, self.const_value)
|
||||
|
||||
|
||||
class DeprecatedAliasStoreAction(argparse.Action):
|
||||
"""Deprecated alias that stores its value and prints a warning."""
|
||||
|
||||
def __init__(self, option_strings, dest, new_flag=None, **kwargs):
|
||||
self.new_flag = new_flag
|
||||
super().__init__(option_strings, dest, **kwargs)
|
||||
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
replacement = f" Use '{self.new_flag}' instead." if self.new_flag else ""
|
||||
print_deprecated_warning(
|
||||
f"'{option_string}' is deprecated and will be removed in a future release.{replacement}"
|
||||
)
|
||||
setattr(namespace, self.dest, values)
|
||||
@@ -0,0 +1,97 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sglang.srt.environ import envs
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sglang.srt.server_args import ServerArgs
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def apply_deepseek_v4_defaults(server_args: ServerArgs, model_arch: str) -> None:
|
||||
"""Residual imperative arm of the DeepSeek V4 defaults.
|
||||
|
||||
The attention/page/window/MoE-runner declarations moved to the override
|
||||
registry (arg_groups/overrides.py: _deepseek_v4_overrides) and the
|
||||
kv-cache dtype default to the resolution pipeline
|
||||
(_deepseek_v4_kv_cache_dtype, invoked below at its legacy slot). This
|
||||
keeps, at the legacy slot: the ROCm env fill (env-write policy), the
|
||||
max_running_requests fill (the speculative hook is a later writer of
|
||||
that field) and the validations.
|
||||
"""
|
||||
from sglang.srt.utils import is_hip
|
||||
|
||||
# FlashMLA sparse prefill (SGLANG_OPT_FLASHMLA_SPARSE_PREFILL, default on)
|
||||
# currently returns incorrect output for DeepSeek-V4-Flash on ROCm/HIP
|
||||
# (MI355X), which breaks the disaggregation nightly. Keep the previous
|
||||
# (dense prefill) behavior on ROCm until the sparse kernel is validated
|
||||
# there;
|
||||
if is_hip():
|
||||
logger.warning(
|
||||
"Disabling SGLANG_OPT_FLASHMLA_SPARSE_PREFILL by default on ROCm/HIP "
|
||||
f"for {model_arch}; set it explicitly to override."
|
||||
)
|
||||
envs.SGLANG_OPT_FLASHMLA_SPARSE_PREFILL.set(False)
|
||||
|
||||
# The kv-cache dtype default moved to the resolution pipeline
|
||||
# (arg_groups/overrides.py: _deepseek_v4_kv_cache_dtype), invoked here at
|
||||
# its legacy slot.
|
||||
from sglang.srt.arg_groups.overrides import (
|
||||
_deepseek_v4_kv_cache_dtype,
|
||||
run_post_process_pass,
|
||||
)
|
||||
|
||||
run_post_process_pass(server_args, _deepseek_v4_kv_cache_dtype)
|
||||
|
||||
if server_args.max_running_requests is None:
|
||||
server_args.max_running_requests = 256
|
||||
logger.warning(
|
||||
f"Setting max_running_requests to {server_args.max_running_requests} for {model_arch}."
|
||||
)
|
||||
|
||||
if server_args.speculative_algorithm is not None:
|
||||
assert server_args.speculative_algorithm in (
|
||||
"EAGLE",
|
||||
"DSPARK",
|
||||
), f"Only EAGLE and DSPARK speculative algorithms are supported for {model_arch}"
|
||||
if server_args.speculative_algorithm == "EAGLE":
|
||||
assert (
|
||||
server_args.speculative_eagle_topk == 1
|
||||
), f"Only EAGLE speculative algorithm with topk == 1 is supported for {model_arch}"
|
||||
|
||||
|
||||
def validate_deepseek_v4_cp(server_args: ServerArgs) -> None:
|
||||
"""Validate DeepSeek V4 context-parallel configuration."""
|
||||
if not server_args.enable_prefill_cp:
|
||||
return
|
||||
|
||||
if server_args.cp_strategy != "interleave":
|
||||
raise ValueError(
|
||||
"DeepSeekV4 only supports interleave CP strategy, "
|
||||
f"got {server_args.cp_strategy}"
|
||||
)
|
||||
|
||||
server_args.enable_dsa_prefill_context_parallel = True
|
||||
server_args.dsa_prefill_cp_mode = "round-robin-split"
|
||||
server_args.enable_dp_attention = True
|
||||
server_args.moe_dense_tp_size = 1
|
||||
server_args.attn_cp_size = server_args.tp_size // server_args.dp_size
|
||||
assert (
|
||||
server_args.dp_size == 1
|
||||
), "For round-robin split mode, dp attention is not supported."
|
||||
assert (
|
||||
server_args.tp_size <= 8
|
||||
), "Context parallel only supports single machine (tp_size <= 8). Cross-machine CP has precision issues."
|
||||
logger.warning(
|
||||
"Disabling SGLANG_OPT_FLASHMLA_SPARSE_PREFILL because DeepSeekV4 "
|
||||
"context parallelism is enabled."
|
||||
)
|
||||
envs.SGLANG_OPT_FLASHMLA_SPARSE_PREFILL.set(False)
|
||||
logger.warning(
|
||||
f"Enable Context Parallel for DeepSeekV4, "
|
||||
f"dp_size={server_args.dp_size}, moe_dense_tp_size={server_args.moe_dense_tp_size}, "
|
||||
f"attn_cp_size={server_args.attn_cp_size}, ep_size={server_args.ep_size}, tp_size={server_args.tp_size}"
|
||||
)
|
||||
@@ -0,0 +1,137 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sglang.srt.server_args import ServerArgs
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
HISPARSE_CUDA_DSA_BACKENDS_BY_DTYPE = {
|
||||
"bfloat16": {"flashmla_sparse"},
|
||||
"fp8_e4m3": {"flashmla_kv"},
|
||||
}
|
||||
HISPARSE_ROCM_DSA_BACKENDS = {"tilelang", "aiter"}
|
||||
HISPARSE_KV_CACHE_DTYPES = ("bfloat16", "fp8_e4m3")
|
||||
|
||||
|
||||
def _is_hip() -> bool:
|
||||
from sglang.srt.server_args import is_hip
|
||||
|
||||
return is_hip()
|
||||
|
||||
|
||||
def _hisparse_default_backend(kv_cache_dtype: str) -> str:
|
||||
if _is_hip():
|
||||
return "tilelang"
|
||||
return "flashmla_kv" if kv_cache_dtype == "fp8_e4m3" else "flashmla_sparse"
|
||||
|
||||
|
||||
def _hisparse_allowed_backends(kv_cache_dtype: str) -> set[str]:
|
||||
if _is_hip():
|
||||
return HISPARSE_ROCM_DSA_BACKENDS
|
||||
return HISPARSE_CUDA_DSA_BACKENDS_BY_DTYPE.get(
|
||||
kv_cache_dtype, {"flashmla_sparse", "flashmla_kv"}
|
||||
)
|
||||
|
||||
|
||||
# The hisparse DSA backend defaults moved to the resolution pipeline
|
||||
# (arg_groups/overrides.py: _dsa_split_backend_resolution, hisparse arm).
|
||||
|
||||
|
||||
def validate_hisparse_dsa_backend(
|
||||
server_args: ServerArgs, attr: str, label: str
|
||||
) -> None:
|
||||
from sglang.srt.arg_groups.overrides import resolved_view
|
||||
|
||||
# Invoked after the DSA kv-cache-dtype / split-backend declarations:
|
||||
# read the resolving state through the view.
|
||||
view = resolved_view(server_args)
|
||||
backend = getattr(view, attr)
|
||||
kv_cache_dtype = view.kv_cache_dtype
|
||||
allowed_backends = _hisparse_allowed_backends(kv_cache_dtype)
|
||||
if backend is not None and backend not in allowed_backends:
|
||||
raise ValueError(
|
||||
f"HiSparse supports DSA {label} backend(s) {sorted(allowed_backends)} "
|
||||
f"on this platform with --kv-cache-dtype={kv_cache_dtype}, "
|
||||
f"but got --dsa-{label}-backend={backend}. "
|
||||
f"Please use --dsa-{label}-backend="
|
||||
f"{_hisparse_default_backend(kv_cache_dtype)} "
|
||||
"or omit it."
|
||||
)
|
||||
|
||||
|
||||
def validate_hisparse_kv_cache_dtype(server_args: ServerArgs) -> None:
|
||||
from sglang.srt.arg_groups.overrides import resolved_view
|
||||
|
||||
kv_cache_dtype = resolved_view(server_args).kv_cache_dtype
|
||||
if kv_cache_dtype in HISPARSE_KV_CACHE_DTYPES:
|
||||
return
|
||||
|
||||
choices = " or ".join(
|
||||
f"--kv-cache-dtype={dtype}" for dtype in HISPARSE_KV_CACHE_DTYPES
|
||||
)
|
||||
raise ValueError(
|
||||
f"HiSparse requires one of {HISPARSE_KV_CACHE_DTYPES} KV cache dtypes, "
|
||||
f"but got --kv-cache-dtype={kv_cache_dtype}. Please use {choices}."
|
||||
)
|
||||
|
||||
|
||||
def validate_hisparse(server_args: ServerArgs) -> None:
|
||||
"""Validate --enable-hisparse constraints (model class, radix cache, DSA backend)."""
|
||||
if not server_args.enable_hisparse:
|
||||
return
|
||||
|
||||
from sglang.srt.configs.model_config import (
|
||||
is_deepseek_dsa,
|
||||
is_deepseek_v4,
|
||||
)
|
||||
|
||||
hf_config = server_args.get_model_config().hf_config
|
||||
is_v4_hisparse = is_deepseek_v4(hf_config)
|
||||
is_hip = _is_hip()
|
||||
assert is_deepseek_dsa(hf_config) or is_v4_hisparse, (
|
||||
"--enable-hisparse is only supported for DSA (DeepSeek Sparse Attention) "
|
||||
"models (e.g., DeepSeek V3.2, GLM-5) and DeepSeek V4 now. "
|
||||
)
|
||||
|
||||
assert (
|
||||
server_args.disable_radix_cache
|
||||
), "Hierarchical sparse attention currently requires --disable-radix-cache."
|
||||
|
||||
# DSv4 hisparse handles its own dtype/backend pairing elsewhere; the dtype-
|
||||
# aware checks below only apply to the DSA hisparse path.
|
||||
if is_hip and is_v4_hisparse:
|
||||
# TEMPORARY GUARD: DSv4 HiSparse is not supported on the unified-KV path.
|
||||
# In unified-KV mode c4_kv_pool is None, so DeepSeekV4HiSparseTokenToKVPoolAllocator
|
||||
# cannot attach and pool init dies with a cryptic AssertionError. Fail fast
|
||||
# at startup with a clear message instead. Remove once unified-KV HiSparse lands.
|
||||
from sglang.srt.layers.attention.dsv4.unified_kv_kernels.env_gate import (
|
||||
is_unified_kv_triton,
|
||||
)
|
||||
|
||||
if is_unified_kv_triton():
|
||||
raise ValueError(
|
||||
"--enable-hisparse is not supported with the unified-KV path on ROCm"
|
||||
"(SGLANG_HACK_FLASHMLA_BACKEND=unified_kv_triton) for DeepSeek-V4: "
|
||||
"HiSparse currently requires the separate packed KV layout. "
|
||||
"Either set SGLANG_HACK_FLASHMLA_BACKEND=triton, or run without "
|
||||
"--enable-hisparse."
|
||||
)
|
||||
return
|
||||
|
||||
from sglang.srt.arg_groups.overrides import resolved_view
|
||||
|
||||
if resolved_view(server_args).kv_cache_dtype not in (
|
||||
"bfloat16",
|
||||
"auto",
|
||||
"fp8_e4m3",
|
||||
):
|
||||
validate_hisparse_kv_cache_dtype(server_args)
|
||||
|
||||
for attr, label in [
|
||||
("dsa_prefill_backend", "prefill"),
|
||||
("dsa_decode_backend", "decode"),
|
||||
]:
|
||||
validate_hisparse_dsa_backend(server_args, attr, label)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,88 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sglang.srt.environ import envs
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sglang.srt.server_args import ServerArgs
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def handle_pd_disaggregation(server_args: ServerArgs) -> None:
|
||||
"""Validate and normalize PD-disaggregation server args."""
|
||||
# "mooncake_tcp" is mooncake with the TCP transport forced: set MC_FORCE_TCP
|
||||
# so mooncake installs TcpTransport instead of RDMA, rewrite the backend to
|
||||
# mooncake, and skip RDMA HCA selection. Must run before backend-name checks.
|
||||
if server_args.disaggregation_transfer_backend == "mooncake_tcp":
|
||||
os.environ.setdefault("MC_FORCE_TCP", "1")
|
||||
server_args.disaggregation_transfer_backend = "mooncake"
|
||||
server_args.disaggregation_ib_device = None
|
||||
logger.info(
|
||||
"disaggregation transfer backend 'mooncake_tcp' -> mooncake "
|
||||
"with MC_FORCE_TCP=1 (TCP transport, no RDMA)"
|
||||
)
|
||||
|
||||
if server_args.disaggregation_mode == "decode":
|
||||
if server_args.disaggregation_decode_enable_radix_cache:
|
||||
if server_args.enable_hisparse:
|
||||
raise ValueError(
|
||||
"--disaggregation-decode-enable-radix-cache is incompatible "
|
||||
"with --enable-hisparse"
|
||||
)
|
||||
if server_args.disaggregation_transfer_backend == "fake":
|
||||
raise ValueError(
|
||||
"--disaggregation-decode-enable-radix-cache is incompatible "
|
||||
"with --disaggregation-transfer-backend fake"
|
||||
)
|
||||
if server_args.speculative_algorithm is not None:
|
||||
raise ValueError(
|
||||
"--disaggregation-decode-enable-radix-cache is incompatible "
|
||||
"with speculative decoding "
|
||||
f"(--speculative-algorithm {server_args.speculative_algorithm})"
|
||||
)
|
||||
from sglang.srt.arg_groups.overrides import resolved_view
|
||||
|
||||
if resolved_view(server_args).enable_dp_attention:
|
||||
logger.warning(
|
||||
"EXPERIMENTAL: Decode radix cache with DP attention. "
|
||||
"Requires prefix-aware DP rank routing for optimal cache hits."
|
||||
)
|
||||
server_args.disable_radix_cache = False
|
||||
logger.warning("EXPERIMENTAL: Radix cache is enabled for decode server")
|
||||
else:
|
||||
server_args.disable_radix_cache = True
|
||||
logger.warning("KV cache is forced as chunk cache for decode server")
|
||||
|
||||
# Default the number of *extra* decode req_to_token slots reserved for
|
||||
# in-transfer (being-received-from-prefill) requests, on top of the
|
||||
# max_running_requests-derived pool. Large batches get none; small
|
||||
# per-worker batches reserve 2x the batch as cheap overlap headroom.
|
||||
if server_args.disaggregation_decode_extra_slots is None:
|
||||
extra_slots = 0
|
||||
if server_args.max_running_requests is not None:
|
||||
per_worker = server_args.max_running_requests // max(
|
||||
1, server_args.dp_size
|
||||
)
|
||||
if per_worker <= 32:
|
||||
extra_slots = per_worker * 2
|
||||
server_args.disaggregation_decode_extra_slots = extra_slots
|
||||
|
||||
elif server_args.disaggregation_mode == "prefill":
|
||||
assert (
|
||||
server_args.disaggregation_transfer_backend != "fake"
|
||||
), "Prefill server does not support 'fake' as the transfer backend"
|
||||
|
||||
if server_args.disaggregation_mode in ("prefill", "decode"):
|
||||
if (
|
||||
envs.SGLANG_DISAGG_STAGING_BUFFER.get()
|
||||
and server_args.disaggregation_transfer_backend not in ("mooncake", "nixl")
|
||||
):
|
||||
raise ValueError(
|
||||
f"SGLANG_DISAGG_STAGING_BUFFER requires "
|
||||
f"disaggregation_transfer_backend='mooncake' or 'nixl', "
|
||||
f"got '{server_args.disaggregation_transfer_backend}'."
|
||||
)
|
||||
@@ -0,0 +1,792 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sglang.srt.server_args import ServerArgs
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _disable_overlap_schedule_for_cpu(server_args: ServerArgs) -> None:
|
||||
if server_args.device != "cpu" or server_args.disable_overlap_schedule:
|
||||
return
|
||||
|
||||
server_args.disable_overlap_schedule = True
|
||||
logger.warning(
|
||||
"Overlap schedule is not implemented for speculative decoding on CPU."
|
||||
)
|
||||
|
||||
|
||||
def _resolve_speculative_algorithm_alias(
|
||||
speculative_algorithm: Optional[str],
|
||||
speculative_draft_model_path: Optional[str],
|
||||
trust_remote_code: bool = False,
|
||||
kwargs: Optional[dict] = {},
|
||||
) -> Optional[str]:
|
||||
"""Resolve CLI speculative algorithm; NEXTN/EAGLE may become FROZEN_KV_MTP for Gemma4 assistant drafts."""
|
||||
|
||||
is_gemma4_draft = False
|
||||
if speculative_draft_model_path:
|
||||
from sglang.srt.utils.hf_transformers_utils import get_config
|
||||
|
||||
cfg = get_config(
|
||||
speculative_draft_model_path, trust_remote_code=trust_remote_code, **kwargs
|
||||
)
|
||||
draft_archs = getattr(cfg, "architectures", None) or []
|
||||
is_gemma4_draft = any(
|
||||
arch in ("Gemma4AssistantForCausalLM", "Gemma4UnifiedAssistantForCausalLM")
|
||||
for arch in draft_archs
|
||||
)
|
||||
|
||||
if speculative_algorithm == "EAGLE3" and is_gemma4_draft:
|
||||
raise ValueError(
|
||||
"Gemma4AssistantForCausalLM draft requires "
|
||||
"--speculative-algorithm NEXTN or EAGLE; EAGLE3 is "
|
||||
"not supported for this draft architecture."
|
||||
)
|
||||
|
||||
if speculative_algorithm == "NEXTN" or speculative_algorithm == "EAGLE":
|
||||
if is_gemma4_draft:
|
||||
logger.info(
|
||||
"Detected Gemma4AssistantForCausalLM draft; "
|
||||
f"promoting --speculative-algorithm {speculative_algorithm} to FROZEN_KV_MTP."
|
||||
)
|
||||
return "FROZEN_KV_MTP"
|
||||
return "EAGLE"
|
||||
|
||||
return speculative_algorithm
|
||||
|
||||
|
||||
def handle_speculative_decoding(server_args: ServerArgs) -> None:
|
||||
if (
|
||||
server_args.speculative_draft_model_path is not None
|
||||
and server_args.speculative_draft_model_revision is None
|
||||
):
|
||||
server_args.speculative_draft_model_revision = "main"
|
||||
|
||||
# Moved to the resolution pipeline (arg_groups/overrides.py:
|
||||
# _speculative_moe_runner_default), invoked here at its legacy slot.
|
||||
from sglang.srt.arg_groups.overrides import (
|
||||
_speculative_moe_runner_default,
|
||||
run_post_process_pass,
|
||||
)
|
||||
|
||||
run_post_process_pass(server_args, _speculative_moe_runner_default)
|
||||
|
||||
if server_args.speculative_algorithm is not None:
|
||||
server_args.speculative_algorithm = server_args.speculative_algorithm.upper()
|
||||
|
||||
# Removal notice for the retired env var; raw os.getenv on purpose -- the
|
||||
# Envs descriptor is gone. Drop this check after one release.
|
||||
if os.getenv("SGLANG_ENABLE_SPEC_V2") is not None:
|
||||
logger.warning(
|
||||
"SGLANG_ENABLE_SPEC_V2 has been removed: speculative decoding "
|
||||
"always runs the V2 worker. Use --disable-overlap-schedule to "
|
||||
"select the non-overlap (synchronous) path."
|
||||
)
|
||||
|
||||
kwargs = {}
|
||||
|
||||
override_config_file = server_args.decrypted_draft_config_file
|
||||
if override_config_file and override_config_file.strip():
|
||||
kwargs["_configuration_file"] = override_config_file.strip()
|
||||
|
||||
server_args.speculative_algorithm = _resolve_speculative_algorithm_alias(
|
||||
server_args.speculative_algorithm,
|
||||
server_args.speculative_draft_model_path,
|
||||
trust_remote_code=server_args.trust_remote_code,
|
||||
kwargs=kwargs,
|
||||
)
|
||||
|
||||
# Validate --speculative-draft-window-size once, regardless of algorithm.
|
||||
# Consumed by DFLASH (compact draft KV cache) and Llama EAGLE-3 (drafter attention SWA).
|
||||
if server_args.speculative_draft_window_size is not None:
|
||||
window_size = int(server_args.speculative_draft_window_size)
|
||||
if window_size <= 0:
|
||||
raise ValueError(
|
||||
f"--speculative-draft-window-size must be positive, got {window_size}."
|
||||
)
|
||||
server_args.speculative_draft_window_size = window_size
|
||||
if server_args.speculative_algorithm not in ("EAGLE3", "DFLASH"):
|
||||
logger.warning(
|
||||
"--speculative-draft-window-size has no effect with "
|
||||
"speculative_algorithm=%s (honored by Llama EAGLE-3 and DFLASH only).",
|
||||
server_args.speculative_algorithm,
|
||||
)
|
||||
|
||||
algo = None
|
||||
if server_args.speculative_algorithm is not None:
|
||||
from sglang.srt.speculative.spec_info import SpeculativeAlgorithm
|
||||
from sglang.srt.speculative.spec_registry import CustomSpecAlgo
|
||||
|
||||
algo = SpeculativeAlgorithm.from_string(server_args.speculative_algorithm)
|
||||
|
||||
# TODO: move the per-algorithm validation below into spec module hooks.
|
||||
if isinstance(algo, CustomSpecAlgo) and algo.validate_server_args is not None:
|
||||
algo.validate_server_args(server_args)
|
||||
|
||||
if server_args.speculative_skip_dp_mlp_sync:
|
||||
assert server_args.speculative_algorithm == "EAGLE", (
|
||||
"--speculative-skip-dp-mlp-sync is only supported with "
|
||||
f"speculative_algorithm == EAGLE, got {server_args.speculative_algorithm}."
|
||||
)
|
||||
|
||||
if server_args.speculative_adaptive:
|
||||
_maybe_disable_adaptive(server_args)
|
||||
if server_args.speculative_adaptive:
|
||||
_init_adaptive_speculative_params(server_args)
|
||||
|
||||
if algo is not None:
|
||||
algo.handle_server_args(server_args)
|
||||
|
||||
|
||||
def _handle_dflash(server_args: ServerArgs) -> None:
|
||||
from sglang.srt.arg_groups.overrides import resolved_view
|
||||
|
||||
if not server_args.device.startswith("cuda"):
|
||||
raise ValueError("DFLASH speculative decoding only supports CUDA device.")
|
||||
|
||||
if resolved_view(server_args).enable_dp_attention:
|
||||
raise ValueError(
|
||||
"Currently DFLASH speculative decoding does not support dp attention."
|
||||
)
|
||||
|
||||
if server_args.pp_size != 1:
|
||||
raise ValueError(
|
||||
"Currently DFLASH speculative decoding only supports pp_size == 1."
|
||||
)
|
||||
|
||||
if server_args.speculative_draft_model_path is None:
|
||||
raise ValueError(
|
||||
"DFLASH speculative decoding requires setting --speculative-draft-model-path."
|
||||
)
|
||||
|
||||
# DFLASH does not use EAGLE-style `num_steps`/`topk`, but those fields still
|
||||
# affect generic scheduler/KV-cache accounting (buffer sizing, KV freeing,
|
||||
# RoPE reservation). Force them to 1 to avoid surprising memory behavior.
|
||||
#
|
||||
# For DFlash, the natural unit is `block_size` (verify window length).
|
||||
if server_args.speculative_num_steps is None:
|
||||
server_args.speculative_num_steps = 1
|
||||
elif int(server_args.speculative_num_steps) != 1:
|
||||
logger.warning(
|
||||
"DFLASH only supports speculative_num_steps == 1; overriding speculative_num_steps=%s to 1.",
|
||||
server_args.speculative_num_steps,
|
||||
)
|
||||
server_args.speculative_num_steps = 1
|
||||
|
||||
if server_args.speculative_eagle_topk is None:
|
||||
server_args.speculative_eagle_topk = 1
|
||||
elif int(server_args.speculative_eagle_topk) != 1:
|
||||
logger.warning(
|
||||
"DFLASH only supports speculative_eagle_topk == 1; overriding speculative_eagle_topk=%s to 1.",
|
||||
server_args.speculative_eagle_topk,
|
||||
)
|
||||
server_args.speculative_eagle_topk = 1
|
||||
|
||||
if server_args.speculative_dflash_block_size is not None:
|
||||
if int(server_args.speculative_dflash_block_size) <= 0:
|
||||
raise ValueError(
|
||||
"DFLASH requires --speculative-dflash-block-size to be positive, "
|
||||
f"got {server_args.speculative_dflash_block_size}."
|
||||
)
|
||||
if server_args.speculative_num_draft_tokens is not None and int(
|
||||
server_args.speculative_num_draft_tokens
|
||||
) != int(server_args.speculative_dflash_block_size):
|
||||
raise ValueError(
|
||||
"Both --speculative-num-draft-tokens and --speculative-dflash-block-size are set "
|
||||
"but they differ. For DFLASH they must match. "
|
||||
f"speculative_num_draft_tokens={server_args.speculative_num_draft_tokens}, "
|
||||
f"speculative_dflash_block_size={server_args.speculative_dflash_block_size}."
|
||||
)
|
||||
server_args.speculative_num_draft_tokens = int(
|
||||
server_args.speculative_dflash_block_size
|
||||
)
|
||||
|
||||
if server_args.speculative_num_draft_tokens is None:
|
||||
from sglang.srt.speculative.dflash_utils import (
|
||||
parse_dflash_draft_config,
|
||||
)
|
||||
|
||||
model_override_args = json.loads(server_args.json_model_override_args)
|
||||
inferred_block_size = None
|
||||
try:
|
||||
from sglang.srt.utils.hf_transformers_utils import get_config
|
||||
|
||||
draft_hf_config = get_config(
|
||||
server_args.speculative_draft_model_path,
|
||||
trust_remote_code=server_args.trust_remote_code,
|
||||
revision=server_args.speculative_draft_model_revision,
|
||||
model_override_args=model_override_args,
|
||||
)
|
||||
inferred_block_size = parse_dflash_draft_config(
|
||||
draft_hf_config=draft_hf_config
|
||||
).resolve_block_size(default=None)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
"Failed to infer DFLASH block_size from draft model config; "
|
||||
"defaulting speculative_num_draft_tokens to 16. Error: %s",
|
||||
e,
|
||||
)
|
||||
|
||||
if inferred_block_size is None:
|
||||
inferred_block_size = 16
|
||||
logger.warning(
|
||||
"speculative_num_draft_tokens is not set; defaulting to %d for DFLASH.",
|
||||
inferred_block_size,
|
||||
)
|
||||
server_args.speculative_num_draft_tokens = inferred_block_size
|
||||
|
||||
if server_args.speculative_draft_window_size is not None:
|
||||
draft_tokens = int(server_args.speculative_num_draft_tokens)
|
||||
if server_args.speculative_draft_window_size < draft_tokens:
|
||||
raise ValueError(
|
||||
"--speculative-draft-window-size must be >= "
|
||||
"--speculative-num-draft-tokens (block_size). "
|
||||
f"window_size={server_args.speculative_draft_window_size}, block_size={draft_tokens}."
|
||||
)
|
||||
|
||||
_resolve_dflash_draft_attention_backend(server_args)
|
||||
|
||||
if server_args.max_running_requests is None:
|
||||
server_args.max_running_requests = 48
|
||||
logger.warning(
|
||||
"Max running requests is reset to 48 for speculative decoding. You can override this by explicitly setting --max-running-requests."
|
||||
)
|
||||
|
||||
if server_args.enable_mixed_chunk:
|
||||
server_args.enable_mixed_chunk = False
|
||||
logger.warning(
|
||||
"Mixed chunked prefill is disabled because of using dflash speculative decoding."
|
||||
)
|
||||
|
||||
|
||||
def _target_checkpoint_bundles_dspark_draft(server_args: ServerArgs) -> bool:
|
||||
from sglang.srt.speculative.dspark_components.dspark_config import (
|
||||
checkpoint_bundles_dspark_draft,
|
||||
)
|
||||
|
||||
return checkpoint_bundles_dspark_draft(server_args.get_model_config().hf_config)
|
||||
|
||||
|
||||
def _handle_dspark(server_args: ServerArgs) -> None:
|
||||
if not server_args.device.startswith("cuda"):
|
||||
raise ValueError("DSpark speculative decoding only supports CUDA device.")
|
||||
|
||||
if server_args.enable_dp_attention:
|
||||
if not server_args.enable_dp_lm_head:
|
||||
raise ValueError("DSpark with dp attention requires --enable-dp-lm-head.")
|
||||
if server_args.moe_a2a_backend != "none":
|
||||
raise ValueError(
|
||||
"DSpark with dp attention only supports the built-in TP MoE "
|
||||
f"(moe_a2a_backend='none'), got {server_args.moe_a2a_backend!r}."
|
||||
)
|
||||
if server_args.attn_cp_size > 1:
|
||||
raise ValueError(
|
||||
"DSpark with dp attention does not support context parallel "
|
||||
f"(attn_cp_size={server_args.attn_cp_size})."
|
||||
)
|
||||
if (
|
||||
server_args.speculative_moe_a2a_backend is not None
|
||||
and server_args.speculative_moe_a2a_backend != server_args.moe_a2a_backend
|
||||
):
|
||||
raise ValueError(
|
||||
"DSpark ignores --speculative-moe-a2a-backend; with dp attention it "
|
||||
f"must match the target moe_a2a_backend={server_args.moe_a2a_backend!r} "
|
||||
f"(got {server_args.speculative_moe_a2a_backend!r})."
|
||||
)
|
||||
|
||||
if server_args.pp_size != 1:
|
||||
raise ValueError(
|
||||
"Currently DSpark speculative decoding only supports pp_size == 1."
|
||||
)
|
||||
|
||||
if server_args.speculative_draft_model_path is None:
|
||||
if _target_checkpoint_bundles_dspark_draft(server_args):
|
||||
server_args.speculative_draft_model_path = server_args.model_path
|
||||
server_args.speculative_draft_model_revision = server_args.revision
|
||||
logger.info(
|
||||
"DSpark draft weights are bundled in the target checkpoint; "
|
||||
"defaulting --speculative-draft-model-path to --model-path (%s).",
|
||||
server_args.model_path,
|
||||
)
|
||||
else:
|
||||
raise ValueError(
|
||||
"DSpark dense speculative decoding requires setting "
|
||||
"--speculative-draft-model-path."
|
||||
)
|
||||
|
||||
if server_args.speculative_num_steps is None:
|
||||
server_args.speculative_num_steps = 1
|
||||
elif int(server_args.speculative_num_steps) != 1:
|
||||
logger.warning(
|
||||
"DSpark only supports speculative_num_steps == 1; overriding speculative_num_steps=%s to 1.",
|
||||
server_args.speculative_num_steps,
|
||||
)
|
||||
server_args.speculative_num_steps = 1
|
||||
|
||||
if server_args.speculative_eagle_topk is None:
|
||||
server_args.speculative_eagle_topk = 1
|
||||
elif int(server_args.speculative_eagle_topk) != 1:
|
||||
logger.warning(
|
||||
"DSpark only supports speculative_eagle_topk == 1; overriding speculative_eagle_topk=%s to 1.",
|
||||
server_args.speculative_eagle_topk,
|
||||
)
|
||||
server_args.speculative_eagle_topk = 1
|
||||
|
||||
gamma: Optional[int] = None
|
||||
if server_args.speculative_dspark_block_size is not None:
|
||||
if int(server_args.speculative_dspark_block_size) <= 0:
|
||||
raise ValueError(
|
||||
"DSpark requires --speculative-dspark-block-size to be positive, "
|
||||
f"got {server_args.speculative_dspark_block_size}."
|
||||
)
|
||||
gamma = int(server_args.speculative_dspark_block_size)
|
||||
else:
|
||||
from sglang.srt.speculative.dspark_components.dspark_config import (
|
||||
DEFAULT_DSPARK_GAMMA,
|
||||
read_draft_checkpoint_gamma,
|
||||
)
|
||||
|
||||
try:
|
||||
gamma = read_draft_checkpoint_gamma(server_args=server_args)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
"Failed to read DSpark gamma from draft model config; "
|
||||
"cannot cross-check --speculative-num-draft-tokens. Error: %s",
|
||||
e,
|
||||
)
|
||||
if gamma is None and server_args.speculative_num_draft_tokens is None:
|
||||
gamma = DEFAULT_DSPARK_GAMMA
|
||||
logger.warning(
|
||||
"DSpark gamma is not set; defaulting to %d.",
|
||||
gamma,
|
||||
)
|
||||
|
||||
if gamma is not None:
|
||||
verify_window = int(gamma) + 1
|
||||
if (
|
||||
server_args.speculative_num_draft_tokens is not None
|
||||
and int(server_args.speculative_num_draft_tokens) != verify_window
|
||||
):
|
||||
raise ValueError(
|
||||
"DSpark speculative_num_draft_tokens must equal gamma + 1 "
|
||||
f"(= {verify_window} for gamma={gamma}), but got "
|
||||
f"speculative_num_draft_tokens={server_args.speculative_num_draft_tokens}."
|
||||
)
|
||||
server_args.speculative_num_draft_tokens = verify_window
|
||||
|
||||
if server_args.speculative_num_draft_tokens is None:
|
||||
raise ValueError(
|
||||
"DSpark could not resolve speculative_num_draft_tokens; set "
|
||||
"--speculative-dspark-block-size (= gamma)."
|
||||
)
|
||||
if int(server_args.speculative_num_draft_tokens) < 2:
|
||||
raise ValueError(
|
||||
"DSpark speculative_num_draft_tokens must be >= 2 (= gamma + 1), "
|
||||
f"got {server_args.speculative_num_draft_tokens}."
|
||||
)
|
||||
|
||||
if server_args.max_running_requests is None:
|
||||
server_args.max_running_requests = 48
|
||||
logger.warning(
|
||||
"Max running requests is reset to 48 for speculative decoding. You can override this by explicitly setting --max-running-requests."
|
||||
)
|
||||
|
||||
if server_args.enable_mixed_chunk:
|
||||
server_args.enable_mixed_chunk = False
|
||||
logger.warning(
|
||||
"Mixed chunked prefill is disabled because of using dspark speculative decoding."
|
||||
)
|
||||
|
||||
from sglang.srt.speculative.ragged_verify import (
|
||||
RaggedVerifyMode,
|
||||
read_ragged_verify_mode,
|
||||
)
|
||||
|
||||
ragged_mode = read_ragged_verify_mode()
|
||||
if (
|
||||
server_args.speculative_dspark_align_verify_tokens_to_graph_tier
|
||||
and ragged_mode is not RaggedVerifyMode.COMPACT
|
||||
):
|
||||
logger.warning(
|
||||
"--speculative-dspark-align-verify-tokens-to-graph-tier only takes "
|
||||
"effect with SGLANG_RAGGED_VERIFY_MODE=compact (got %r); it will be "
|
||||
"a no-op.",
|
||||
ragged_mode.value,
|
||||
)
|
||||
if (
|
||||
server_args.speculative_dspark_sps_table_path
|
||||
and ragged_mode is RaggedVerifyMode.STATIC
|
||||
):
|
||||
logger.warning(
|
||||
"--speculative-dspark-sps-table-path feeds the ragged-verify budget "
|
||||
"scheduler, which is off under SGLANG_RAGGED_VERIFY_MODE=static; it "
|
||||
"will be a no-op."
|
||||
)
|
||||
|
||||
|
||||
def _resolve_dflash_draft_attention_backend(server_args: ServerArgs) -> None:
|
||||
"""Resolve `speculative_draft_attention_backend` to a final, supported value.
|
||||
|
||||
Consumed by ModelRunner's `is_draft_worker` override (one backend for all
|
||||
draft modes).
|
||||
"""
|
||||
from sglang.srt.utils import is_hip
|
||||
|
||||
supported_draft_backends = ("flashinfer", "fa3", "fa4", "triton", "ascend")
|
||||
# Use triton on ROCm (no FlashInfer), flashinfer on CUDA.
|
||||
fallback_backend = "triton" if is_hip() else "flashinfer"
|
||||
|
||||
draft_backend = server_args.speculative_draft_attention_backend
|
||||
if draft_backend is None:
|
||||
from sglang.srt.arg_groups.overrides import (
|
||||
attention_backends_of,
|
||||
resolved_view,
|
||||
)
|
||||
|
||||
draft_backend, _ = attention_backends_of(resolved_view(server_args))
|
||||
if draft_backend is None:
|
||||
draft_backend = fallback_backend
|
||||
elif draft_backend == "trtllm_mha":
|
||||
logger.warning(
|
||||
"DFLASH draft worker does not support 'trtllm_mha' because the "
|
||||
"draft path requires per-layer DFlash attention. Falling back to "
|
||||
"'%s'.",
|
||||
fallback_backend,
|
||||
)
|
||||
draft_backend = fallback_backend
|
||||
elif draft_backend not in supported_draft_backends:
|
||||
logger.warning(
|
||||
"DFLASH draft worker only supports attention_backend in %s for now, "
|
||||
"but got %r. Falling back to '%s'.",
|
||||
supported_draft_backends,
|
||||
draft_backend,
|
||||
fallback_backend,
|
||||
)
|
||||
draft_backend = fallback_backend
|
||||
# FIXME: avoid overriding server args directly; pass the resolved draft
|
||||
# backend to the draft worker explicitly instead.
|
||||
server_args.speculative_draft_attention_backend = draft_backend
|
||||
|
||||
|
||||
def _handle_frozen_kv_mtp(server_args: ServerArgs) -> None:
|
||||
if server_args.max_running_requests is None:
|
||||
server_args.max_running_requests = 48
|
||||
logger.warning(
|
||||
"Max running requests is reset to 48 for speculative decoding. You can override this by explicitly setting --max-running-requests."
|
||||
)
|
||||
|
||||
if server_args.enable_mixed_chunk:
|
||||
server_args.enable_mixed_chunk = False
|
||||
logger.warning(
|
||||
"Mixed chunked prefill is disabled because of using "
|
||||
"Frozen-KV MTP speculative decoding."
|
||||
)
|
||||
|
||||
|
||||
def _handle_eagle_family(server_args: ServerArgs) -> None:
|
||||
from sglang.srt.arg_groups.overrides import (
|
||||
attention_backends_of,
|
||||
resolved_view,
|
||||
)
|
||||
|
||||
if (
|
||||
server_args.speculative_algorithm == "STANDALONE"
|
||||
and resolved_view(server_args).enable_dp_attention
|
||||
):
|
||||
# TODO: support dp attention for standalone speculative decoding
|
||||
raise ValueError(
|
||||
"Currently standalone speculative decoding does not support dp attention."
|
||||
)
|
||||
|
||||
if server_args.max_running_requests is None:
|
||||
server_args.max_running_requests = 48
|
||||
logger.warning(
|
||||
"Max running requests is reset to 48 for speculative decoding. You can override this by explicitly setting --max-running-requests."
|
||||
)
|
||||
|
||||
_disable_overlap_schedule_for_cpu(server_args)
|
||||
|
||||
if resolved_view(server_args).disable_overlap_schedule:
|
||||
logger.warning(
|
||||
"Non-overlap (synchronous) spec v2 is used for eagle/eagle3/standalone "
|
||||
"speculative decoding."
|
||||
)
|
||||
|
||||
if server_args.enable_mixed_chunk:
|
||||
server_args.enable_mixed_chunk = False
|
||||
logger.warning(
|
||||
"Mixed chunked prefill is disabled because of using "
|
||||
"eagle speculative decoding."
|
||||
)
|
||||
|
||||
model_arch = server_args.get_model_config().hf_config.architectures[0]
|
||||
if model_arch in [
|
||||
"DeepseekV32ForCausalLM",
|
||||
"DeepseekV3ForCausalLM",
|
||||
"DeepseekV4ForCausalLM",
|
||||
"Glm4MoeForCausalLM",
|
||||
"Glm4MoeLiteForCausalLM",
|
||||
"GlmMoeDsaForCausalLM",
|
||||
"BailingMoeForCausalLM",
|
||||
"BailingMoeV2ForCausalLM",
|
||||
"BailingMoeV2_5ForCausalLM",
|
||||
"MistralLarge3ForCausalLM",
|
||||
"PixtralForConditionalGeneration",
|
||||
"HYV3ForCausalLM",
|
||||
]:
|
||||
if server_args.speculative_draft_model_path is None:
|
||||
server_args.speculative_draft_model_path = server_args.model_path
|
||||
server_args.speculative_draft_model_revision = server_args.revision
|
||||
else:
|
||||
if model_arch not in [
|
||||
"MistralLarge3ForCausalLM",
|
||||
"PixtralForConditionalGeneration",
|
||||
]:
|
||||
logger.warning(
|
||||
"DeepSeek MTP does not require setting speculative_draft_model_path."
|
||||
)
|
||||
|
||||
if (
|
||||
not server_args.speculative_adaptive
|
||||
and server_args.speculative_num_steps is None
|
||||
):
|
||||
assert (
|
||||
server_args.speculative_eagle_topk is None
|
||||
and server_args.speculative_num_draft_tokens is None
|
||||
)
|
||||
|
||||
(
|
||||
server_args.speculative_num_steps,
|
||||
server_args.speculative_eagle_topk,
|
||||
server_args.speculative_num_draft_tokens,
|
||||
) = _auto_choose_speculative_params(server_args, model_arch)
|
||||
|
||||
if "trtllm_mha" in attention_backends_of(resolved_view(server_args)):
|
||||
if server_args.speculative_eagle_topk > 1:
|
||||
raise ValueError(
|
||||
"trtllm_mha backend only supports topk = 1 for speculative decoding."
|
||||
)
|
||||
|
||||
if server_args.speculative_use_rejection_sampling:
|
||||
# Resolved alias by now: NEXTN -> EAGLE, Gemma4 draft -> FROZEN_KV_MTP.
|
||||
# Only the EAGLE/EAGLE3 draft workers emit a target-vocab proposal that
|
||||
# the rejection-sampling kernel consumes; everything else (STANDALONE,
|
||||
# FROZEN_KV_MTP, NGRAM, DFLASH) is unsupported.
|
||||
if server_args.speculative_algorithm not in ("EAGLE", "EAGLE3"):
|
||||
raise NotImplementedError(
|
||||
"--speculative-use-rejection-sampling is only supported for "
|
||||
"EAGLE / EAGLE3 / NEXTN, not "
|
||||
f"speculative_algorithm={server_args.speculative_algorithm}."
|
||||
)
|
||||
if server_args.speculative_eagle_topk != 1:
|
||||
raise ValueError(
|
||||
"--speculative-use-rejection-sampling requires --speculative-eagle-topk=1."
|
||||
)
|
||||
if (
|
||||
server_args.speculative_accept_threshold_single != 1.0
|
||||
or server_args.speculative_accept_threshold_acc != 1.0
|
||||
):
|
||||
raise ValueError(
|
||||
"--speculative-use-rejection-sampling is incompatible with "
|
||||
"--speculative-accept-threshold-single / "
|
||||
"--speculative-accept-threshold-acc; rejection sampling ignores "
|
||||
"the accept thresholds."
|
||||
)
|
||||
if server_args.enable_deterministic_inference:
|
||||
raise ValueError(
|
||||
"--speculative-use-rejection-sampling is incompatible with "
|
||||
"--enable-deterministic-inference; the sampling kernel draws "
|
||||
"coins from the global RNG and is not batch-invariant."
|
||||
)
|
||||
|
||||
from sglang.srt.arg_groups.overrides import resolved_view
|
||||
|
||||
if (
|
||||
resolved_view(server_args).enable_multi_layer_eagle
|
||||
and server_args.speculative_eagle_topk != 1
|
||||
):
|
||||
raise ValueError(
|
||||
"--speculative-use-rejection-sampling with multi-layer EAGLE "
|
||||
"(--enable-multi-layer-eagle) requires --speculative-eagle-topk 1; "
|
||||
"rejection sampling is only implemented for the linear (topk=1) chain."
|
||||
)
|
||||
logger.info(
|
||||
"Rejection sampling is enabled for speculative decoding "
|
||||
"(speculative_use_rejection_sampling=True)."
|
||||
)
|
||||
|
||||
if (
|
||||
server_args.speculative_eagle_topk == 1
|
||||
and server_args.speculative_num_draft_tokens
|
||||
!= server_args.speculative_num_steps + 1
|
||||
):
|
||||
logger.warning(
|
||||
"speculative_num_draft_tokens is adjusted to speculative_num_steps + 1 when speculative_eagle_topk == 1"
|
||||
)
|
||||
server_args.speculative_num_draft_tokens = server_args.speculative_num_steps + 1
|
||||
|
||||
# topk > 1 + page_size > 1 needs the two-pass cascade draft-decode (shared prefix
|
||||
# pass + per-branch expand pass with prefix-tail dup). Only these backends implement
|
||||
# it; flashmla / trtllm_mla / cutlass_mla can't express the per-branch tree, so reject.
|
||||
_PAGE_TREE_SPEC_BACKENDS = ("flashinfer", "fa3", "triton")
|
||||
view = resolved_view(server_args)
|
||||
if (
|
||||
server_args.speculative_eagle_topk > 1
|
||||
and view.page_size > 1
|
||||
and view.attention_backend not in _PAGE_TREE_SPEC_BACKENDS
|
||||
):
|
||||
raise ValueError(
|
||||
f"speculative_eagle_topk > 1 with page_size > 1 is only supported on "
|
||||
f"{_PAGE_TREE_SPEC_BACKENDS}; got attention_backend="
|
||||
f"{view.attention_backend!r}. Use page_size == 1 or one of those backends."
|
||||
)
|
||||
|
||||
|
||||
def _handle_ngram(server_args: ServerArgs) -> None:
|
||||
if server_args.device not in ("cuda", "cpu"):
|
||||
raise ValueError(
|
||||
"Ngram speculative decoding only supports CUDA or CPU devices."
|
||||
)
|
||||
|
||||
_disable_overlap_schedule_for_cpu(server_args)
|
||||
|
||||
if server_args.max_running_requests is None:
|
||||
server_args.max_running_requests = 48
|
||||
logger.warning(
|
||||
"Max running requests is reset to 48 for speculative decoding. You can override this by explicitly setting --max-running-requests."
|
||||
)
|
||||
|
||||
server_args.enable_mixed_chunk = False
|
||||
server_args.speculative_eagle_topk = server_args.speculative_ngram_max_bfs_breadth
|
||||
if server_args.speculative_num_draft_tokens is None:
|
||||
server_args.speculative_num_draft_tokens = 12
|
||||
logger.warning(
|
||||
"speculative_num_draft_tokens is set to 12 by default for ngram speculative decoding. "
|
||||
"You can override this by explicitly setting --speculative-num-draft-tokens."
|
||||
)
|
||||
if server_args.speculative_num_steps is None:
|
||||
server_args.speculative_num_steps = (
|
||||
server_args.speculative_num_draft_tokens
|
||||
// server_args.speculative_eagle_topk
|
||||
)
|
||||
if server_args.speculative_ngram_external_corpus_path is not None:
|
||||
if server_args.speculative_ngram_external_sam_budget <= 0:
|
||||
raise ValueError(
|
||||
"--speculative-ngram-external-sam-budget must be positive when "
|
||||
"--speculative-ngram-external-corpus-path is set."
|
||||
)
|
||||
if server_args.speculative_ngram_external_corpus_max_tokens <= 0:
|
||||
raise ValueError(
|
||||
"--speculative-ngram-external-corpus-max-tokens must be positive when "
|
||||
"--speculative-ngram-external-corpus-path is set."
|
||||
)
|
||||
if (
|
||||
server_args.speculative_ngram_external_sam_budget
|
||||
> server_args.speculative_num_draft_tokens - 1
|
||||
):
|
||||
raise ValueError(
|
||||
"speculative_ngram_external_sam_budget must be less than or equal to "
|
||||
f"speculative_num_draft_tokens - 1 ({server_args.speculative_num_draft_tokens - 1})."
|
||||
)
|
||||
logger.warning(
|
||||
"The mixed chunked prefill are disabled because of "
|
||||
"using ngram speculative decoding."
|
||||
)
|
||||
|
||||
from sglang.srt.arg_groups.overrides import resolved_view
|
||||
|
||||
view = resolved_view(server_args)
|
||||
if (
|
||||
server_args.speculative_eagle_topk > 1
|
||||
and view.page_size > 1
|
||||
and view.attention_backend != "flashinfer"
|
||||
):
|
||||
raise ValueError(
|
||||
f"speculative_eagle_topk({server_args.speculative_eagle_topk}) > 1 "
|
||||
f"with page_size({view.page_size}) > 1 is unstable "
|
||||
"and produces incorrect results for paged attention backends. "
|
||||
"This combination is only supported for the 'flashinfer' backend."
|
||||
)
|
||||
if view.enable_dp_attention:
|
||||
# TODO: support dp attention for ngram speculative decoding
|
||||
raise ValueError(
|
||||
"Currently ngram speculative decoding does not support dp attention."
|
||||
)
|
||||
|
||||
|
||||
def _maybe_disable_adaptive(server_args: ServerArgs) -> None:
|
||||
from sglang.srt.speculative.adaptive_spec_params import (
|
||||
adaptive_unsupported_reason,
|
||||
)
|
||||
|
||||
reason = adaptive_unsupported_reason(server_args)
|
||||
if reason is not None:
|
||||
logger.warning(
|
||||
f"speculative_adaptive disabled: {reason}. "
|
||||
"Falling back to static speculative params."
|
||||
)
|
||||
server_args.speculative_adaptive = False
|
||||
|
||||
|
||||
def _init_adaptive_speculative_params(server_args: ServerArgs) -> None:
|
||||
from sglang.srt.speculative.adaptive_spec_params import (
|
||||
resolve_candidate_steps_from_config,
|
||||
)
|
||||
|
||||
candidate_steps = resolve_candidate_steps_from_config(
|
||||
cfg_path=server_args.speculative_adaptive_config,
|
||||
)
|
||||
|
||||
if server_args.speculative_eagle_topk is None:
|
||||
server_args.speculative_eagle_topk = 1
|
||||
|
||||
if server_args.speculative_num_steps is None:
|
||||
server_args.speculative_num_steps = candidate_steps[len(candidate_steps) // 2]
|
||||
|
||||
if server_args.speculative_num_steps not in candidate_steps:
|
||||
raise ValueError(
|
||||
f"--speculative-num-steps={server_args.speculative_num_steps} "
|
||||
f"is not in the adaptive config candidate_steps {candidate_steps}. "
|
||||
"Pass one of those values."
|
||||
)
|
||||
|
||||
server_args.speculative_num_draft_tokens = server_args.speculative_num_steps + 1
|
||||
|
||||
|
||||
def _auto_choose_speculative_params(server_args: ServerArgs, model_arch: str) -> tuple:
|
||||
"""
|
||||
Automatically choose the parameters for speculative decoding.
|
||||
|
||||
You can tune them on your own models and prompts with scripts/playground/bench_speculative.py
|
||||
"""
|
||||
if server_args.speculative_algorithm == "STANDALONE":
|
||||
return (3, 1, 4)
|
||||
if model_arch in ["LlamaForCausalLM"]:
|
||||
return (5, 4, 8)
|
||||
elif model_arch in [
|
||||
"DeepseekV32ForCausalLM",
|
||||
"DeepseekV3ForCausalLM",
|
||||
"DeepseekV2ForCausalLM",
|
||||
"GptOssForCausalLM",
|
||||
"Glm4MoeForCausalLM",
|
||||
"Glm4MoeLiteForCausalLM",
|
||||
"GlmMoeDsaForCausalLM",
|
||||
"BailingMoeForCausalLM",
|
||||
"BailingMoeV2ForCausalLM",
|
||||
"BailingMoeV2_5ForCausalLM",
|
||||
"MistralLarge3ForCausalLM",
|
||||
"PixtralForConditionalGeneration",
|
||||
"MiMoV2ForCausalLM",
|
||||
"MiMoV2FlashForCausalLM",
|
||||
]:
|
||||
return (3, 1, 4)
|
||||
elif model_arch in ["Grok1ForCausalLM", "Grok1VForCausalLM"]:
|
||||
return (5, 4, 8)
|
||||
else:
|
||||
return (3, 1, 4)
|
||||
@@ -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",
|
||||
]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,258 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import nullcontext
|
||||
from dataclasses import dataclass, replace
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Callable,
|
||||
Dict,
|
||||
Generator,
|
||||
List,
|
||||
Optional,
|
||||
Sequence,
|
||||
Union,
|
||||
)
|
||||
|
||||
from sglang.srt.layers.dp_attention import set_dp_buffer_len
|
||||
from sglang.srt.model_executor.forward_context import (
|
||||
forward_context,
|
||||
get_forward_context,
|
||||
)
|
||||
from sglang.srt.utils.nvtx_utils import operations_nvtx_range
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sglang.srt.model_executor.forward_batch_info import ForwardBatch
|
||||
from sglang.srt.model_executor.forward_context import ForwardContext
|
||||
|
||||
|
||||
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
|
||||
|
||||
# Each TBO child sub-batch dispatches against its own per-child backend
|
||||
# (children[i] has metadata init'd for sub-batch i; the parent's primary
|
||||
# has metadata for the full pre-split batch).
|
||||
child_ctx_a, child_ctx_b = _resolve_tbo_child_contexts()
|
||||
|
||||
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, child_ctx=child_ctx_a)
|
||||
executor_b = _StageExecutor("b", stages_b, inputs=inputs_b, child_ctx=child_ctx_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]
|
||||
|
||||
|
||||
def _resolve_tbo_child_contexts():
|
||||
"""Return (child_ctx_a, child_ctx_b) derived from the active TboAttnBackend,
|
||||
or (None, None) if the active backend is not a TBO dispatcher (e.g. a
|
||||
backend that handles TBO splitting internally like DeepSeek MHA's
|
||||
_resolve_attn_backend path)."""
|
||||
# Lazy import to avoid circular dependency at module load time.
|
||||
from sglang.srt.layers.attention.tbo_backend import TboAttnBackend
|
||||
|
||||
ctx = get_forward_context()
|
||||
backend = ctx.attn_backend
|
||||
if not isinstance(backend, TboAttnBackend):
|
||||
return None, None
|
||||
child_a, child_b = backend.children
|
||||
return (
|
||||
replace(ctx, attn_backend=child_a),
|
||||
replace(ctx, attn_backend=child_b),
|
||||
)
|
||||
|
||||
|
||||
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,
|
||||
child_ctx: Optional[ForwardContext] = None,
|
||||
):
|
||||
self._debug_name = debug_name
|
||||
self._stages = stages
|
||||
self._index = 0
|
||||
self._stage_state = _StateDict()
|
||||
self._stage_output = inputs
|
||||
# When set, every next() runs inside this ForwardContext so that
|
||||
# get_attn_backend() inside RadixAttention.forward resolves to the
|
||||
# per-child backend (with sub-batch metadata) instead of the TBO
|
||||
# parent's primary.
|
||||
self._child_ctx = child_ctx
|
||||
|
||||
# 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,
|
||||
)
|
||||
|
||||
ctx_mgr = (
|
||||
forward_context(self._child_ctx)
|
||||
if self._child_ctx is not None
|
||||
else nullcontext()
|
||||
)
|
||||
stage_range = operations_nvtx_range(
|
||||
debug_name=f"{self._debug_name}{self._index}",
|
||||
color="orange",
|
||||
)
|
||||
with ctx_mgr, stage_range:
|
||||
for op in stage:
|
||||
with operations_nvtx_range(
|
||||
debug_name=op.debug_name,
|
||||
color="yellow",
|
||||
):
|
||||
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)
|
||||
|
||||
|
||||
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,
|
||||
)
|
||||
@@ -0,0 +1,378 @@
|
||||
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
|
||||
]
|
||||
)
|
||||
elif layer_name == "DeepseekV4DecoderLayer":
|
||||
return OperationsStrategy.concat(
|
||||
[
|
||||
_compute_moe_deepseek_v4_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 DeepSeek V4 ---------------------------------------
|
||||
|
||||
|
||||
# DSV4 prefill TBO (EP / mori path). Cross-layer mHC fusion is disabled under
|
||||
# TBO, so each layer is self-contained: attn-side mHC pre+norm -> attn ->
|
||||
# ffn-side mHC pre+norm -> MoE (a2a dispatch/combine overlapped) -> mHC post.
|
||||
# The MoE ops are reused from self.mlp (DeepseekV2MoE) and decompose
|
||||
# forward_deepep; the layer-level op_mhc_* wrap DSV4's hc_pre / hc_post.
|
||||
def _compute_moe_deepseek_v4_layer_operations_strategy_tbo(
|
||||
layer: torch.nn.Module,
|
||||
forward_mode: ForwardMode,
|
||||
) -> OperationsStrategy:
|
||||
if forward_mode == ForwardMode.EXTEND:
|
||||
return _compute_moe_deepseek_v4_prefill(layer)
|
||||
else:
|
||||
# Decode TBO for DSV4 is not implemented yet (ATOM data: decode TBO
|
||||
# regresses; needs cuda-graph capture work). Prefill-only for now.
|
||||
raise NotImplementedError(
|
||||
f"DeepseekV4 TBO only supports prefill (EXTEND), got {forward_mode=}"
|
||||
)
|
||||
|
||||
|
||||
def _compute_moe_deepseek_v4_prefill(layer):
|
||||
from sglang.srt.layers.moe import get_moe_a2a_backend
|
||||
|
||||
if get_moe_a2a_backend().is_none():
|
||||
# Non-EP DP TP-MoE: overlap the DP all_gatherv (gather) + reduce_scatterv
|
||||
# (combine) with the other ubatch's attn+MoE compute (ATOM's DSV4 path).
|
||||
ops = [
|
||||
layer.op_mhc_prepare_attn,
|
||||
layer.self_attn.op_attn,
|
||||
layer.op_mhc_post_attn_pre_mlp,
|
||||
layer.op_gather_a,
|
||||
operations.YieldOperation(),
|
||||
layer.op_gather_b,
|
||||
layer.op_moe,
|
||||
layer.op_combine_a,
|
||||
operations.YieldOperation(),
|
||||
layer.op_combine_b,
|
||||
layer.op_mhc_postprocess,
|
||||
]
|
||||
else:
|
||||
# EP / mori a2a: reuse DeepseekV2MoE's deepep dispatch/combine ops.
|
||||
ops = [
|
||||
layer.op_mhc_prepare_attn,
|
||||
layer.self_attn.op_attn,
|
||||
layer.op_mhc_post_attn_pre_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_mhc_postprocess,
|
||||
]
|
||||
return OperationsStrategy(
|
||||
deep_gemm_num_sms=None,
|
||||
tbo_delta_stages=0,
|
||||
operations=ops,
|
||||
)
|
||||
|
||||
|
||||
# -------------------------------- 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(),
|
||||
],
|
||||
)
|
||||
@@ -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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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"]
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -0,0 +1,475 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
# 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.compilation.xpu_piecewise_backend import XPUPiecewiseBackend
|
||||
from sglang.srt.environ import envs
|
||||
from sglang.srt.platforms import current_platform
|
||||
from sglang.srt.utils.common import is_npu, is_xpu
|
||||
|
||||
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,
|
||||
):
|
||||
|
||||
if current_platform.is_out_of_tree():
|
||||
backend_cls = current_platform.get_piecewise_backend_cls()
|
||||
elif is_xpu():
|
||||
backend_cls = XPUPiecewiseBackend
|
||||
elif is_npu():
|
||||
backend_cls = NPUPiecewiseBackend
|
||||
else:
|
||||
backend_cls = CUDAPiecewiseBackend
|
||||
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
|
||||
|
||||
|
||||
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 = envs.SGLANG_CACHE_DIR.get()
|
||||
|
||||
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
|
||||
@@ -0,0 +1,61 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
# 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)
|
||||
self.configure_inductor()
|
||||
|
||||
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
|
||||
|
||||
def configure_inductor(self):
|
||||
"""Apply inductor-specific optimizations when using inductor compiler."""
|
||||
if self.compiler != "inductor":
|
||||
return
|
||||
|
||||
import torch._inductor.config as inductor_config
|
||||
|
||||
# Horizontal fusion for sibling ops with different shapes,
|
||||
# e.g. fusing q_norm + k_norm into a single triton kernel.
|
||||
if hasattr(inductor_config, "combo_kernels"):
|
||||
inductor_config.combo_kernels = True
|
||||
inductor_config.benchmark_combo_kernel = True
|
||||
@@ -0,0 +1,49 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
# 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()
|
||||
@@ -0,0 +1,203 @@
|
||||
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.model_executor.runner_backend_utils.tc_piecewise_cuda_graph import (
|
||||
is_in_tc_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_tc_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
|
||||
@@ -0,0 +1,57 @@
|
||||
"""torch.compile-internal phase markers used by the tc_piecewise backend.
|
||||
|
||||
Two pieces of state, both private to the torch.compile path (the
|
||||
``cuda_piecewise_backend`` FX backend and the runner that drives it):
|
||||
|
||||
* ``_in_torch_compile_warmup`` — true during the warmup-compile loop
|
||||
where we run the compiled callable to trigger inductor compilation
|
||||
but explicitly do **not** capture into a CUDA graph yet.
|
||||
``cuda_piecewise_backend`` reads this to short-circuit the capture
|
||||
branch.
|
||||
* ``_pcg_capture_stream`` — the CUDA stream on which the runner is
|
||||
performing capture, surfaced so the FX backend can use the same
|
||||
stream for its own ``torch.cuda.graph(...)`` calls.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import contextmanager
|
||||
|
||||
import torch
|
||||
|
||||
_in_torch_compile_warmup = False
|
||||
_pcg_capture_stream: torch.cuda.Stream | None = None
|
||||
|
||||
|
||||
def is_in_torch_compile_warmup() -> bool:
|
||||
"""True while inside the tc_piecewise warmup-compile pass. Strict subset of
|
||||
``torch.compiler.is_compiling()``.
|
||||
"""
|
||||
return _in_torch_compile_warmup
|
||||
|
||||
|
||||
@contextmanager
|
||||
def enable_torch_compile_warmup():
|
||||
"""Mark the enclosed scope as the tc_piecewise warmup-compile pass. The FX
|
||||
piecewise backend uses this to skip CUDA graph capture during warmup.
|
||||
"""
|
||||
global _in_torch_compile_warmup
|
||||
_in_torch_compile_warmup = True
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
_in_torch_compile_warmup = False
|
||||
|
||||
|
||||
def get_pcg_capture_stream() -> torch.cuda.Stream | None:
|
||||
return _pcg_capture_stream
|
||||
|
||||
|
||||
@contextmanager
|
||||
def set_pcg_capture_stream(stream: torch.cuda.Stream):
|
||||
global _pcg_capture_stream
|
||||
_pcg_capture_stream = stream
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
_pcg_capture_stream = None
|
||||
@@ -0,0 +1,510 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
# 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] = []
|
||||
# Newer torch Inductor reads ``shape_env.var_to_hint_override`` during
|
||||
# compilation; provide an empty mapping so this dummy shape env stays
|
||||
# compatible across torch versions (older torch never accesses it).
|
||||
self.var_to_hint_override: dict[Any, 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")
|
||||
@@ -0,0 +1,225 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
# 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.compile_phase import (
|
||||
get_pcg_capture_stream,
|
||||
is_in_torch_compile_warmup,
|
||||
)
|
||||
from sglang.srt.compilation.weak_ref_tensor import weak_ref_tensors
|
||||
from sglang.srt.utils import is_hip
|
||||
from sglang.srt.utils.common import print_warning_once
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
_is_hip = is_hip()
|
||||
|
||||
|
||||
@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_torch_compile_warmup():
|
||||
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)
|
||||
|
||||
# During normal capture (PiecewiseCudaGraphRunner.capture()),
|
||||
# set_pcg_capture_stream() guarantees a valid stream. However,
|
||||
# Dynamo may silently recompile on HIP/MLA serving batches whose
|
||||
# token count exceeds the captured range. The replacement backend
|
||||
# has no capture stream; fall back there instead of crashing while
|
||||
# preserving the original assertion on other platforms.
|
||||
stream = get_pcg_capture_stream()
|
||||
if _is_hip and stream is None:
|
||||
print_warning_once(
|
||||
"PCG capture stream is not set; likely a Dynamo runtime "
|
||||
"recompilation. Falling back to eager execution for this "
|
||||
"subgraph."
|
||||
)
|
||||
return entry.runnable(*args)
|
||||
assert (
|
||||
stream is not None
|
||||
), "PCG capture stream is not set, please check if runtime recompilation happened"
|
||||
|
||||
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.
|
||||
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
|
||||
@@ -0,0 +1,136 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
# 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)
|
||||
@@ -0,0 +1,85 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
# 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))
|
||||
@@ -0,0 +1,142 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
# 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)
|
||||
@@ -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
|
||||
@@ -0,0 +1,68 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
# 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)
|
||||
@@ -0,0 +1,83 @@
|
||||
"""torch.compile decoration helpers used by the decode-Full path under
|
||||
``--enable-torch-compile``.
|
||||
|
||||
``patch_model`` wraps the model forward with ``torch.compile`` for batch
|
||||
sizes that fall in the compile bucket list and returns the raw forward
|
||||
otherwise. ``set_torch_compile_config`` flips the inductor/dynamo config
|
||||
flags expected by that path.
|
||||
|
||||
Note: the prefill-tc_piecewise path (``TcPiecewiseCudaGraphBackend``) does NOT
|
||||
use ``patch_model`` — it goes through ``compilation/compile.py``'s
|
||||
``install_torch_compiled``. ``_to_torch`` here is duplicated by
|
||||
tc_piecewise's local ``_toggle_multi_platform_ops``; the duplication is kept
|
||||
because the two paths have different lifecycle requirements.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from contextlib import contextmanager
|
||||
|
||||
import torch
|
||||
|
||||
from sglang.srt.distributed.parallel_state import GroupCoordinator
|
||||
from sglang.srt.layers.utils import MultiPlatformOp
|
||||
from sglang.srt.utils import get_bool_env_var, is_hip
|
||||
from sglang.srt.utils.patch_torch import monkey_patch_torch_compile
|
||||
|
||||
_is_hip = is_hip()
|
||||
|
||||
|
||||
def _to_torch(model: torch.nn.Module, reverse: bool, num_tokens: int) -> None:
|
||||
for sub in model._modules.values():
|
||||
if isinstance(sub, MultiPlatformOp):
|
||||
if reverse:
|
||||
sub.leave_torch_compile()
|
||||
else:
|
||||
sub.enter_torch_compile(num_tokens=num_tokens)
|
||||
if isinstance(sub, torch.nn.Module):
|
||||
_to_torch(sub, reverse, num_tokens)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def patch_model(
|
||||
model: torch.nn.Module,
|
||||
enable_compile: bool,
|
||||
num_tokens: int,
|
||||
tp_group: GroupCoordinator,
|
||||
):
|
||||
"""Patch the model to make it compatible with torch.compile."""
|
||||
backup_ca_comm = None
|
||||
|
||||
try:
|
||||
if enable_compile:
|
||||
_to_torch(model, reverse=False, num_tokens=num_tokens)
|
||||
backup_ca_comm = tp_group.ca_comm
|
||||
yield torch.compile(
|
||||
torch.no_grad()(model.forward),
|
||||
mode=os.environ.get(
|
||||
"SGLANG_TORCH_COMPILE_MODE", "max-autotune-no-cudagraphs"
|
||||
),
|
||||
dynamic=_is_hip and get_bool_env_var("SGLANG_TORCH_DYNAMIC_SHAPE"),
|
||||
)
|
||||
else:
|
||||
yield model.forward
|
||||
finally:
|
||||
if enable_compile:
|
||||
_to_torch(model, reverse=True, num_tokens=num_tokens)
|
||||
tp_group.ca_comm = backup_ca_comm
|
||||
|
||||
|
||||
def set_torch_compile_config() -> None:
|
||||
import torch._dynamo.config
|
||||
import torch._inductor.config
|
||||
|
||||
torch._inductor.config.coordinate_descent_tuning = True
|
||||
torch._inductor.config.triton.unique_kernel_names = True
|
||||
torch._inductor.config.fx_graph_cache = True
|
||||
|
||||
torch._dynamo.config.accumulated_cache_size_limit = 1024
|
||||
if hasattr(torch._dynamo.config, "cache_size_limit"):
|
||||
torch._dynamo.config.cache_size_limit = 1024
|
||||
|
||||
monkey_patch_torch_compile()
|
||||
@@ -0,0 +1,30 @@
|
||||
from typing import Any, Union
|
||||
|
||||
import torch
|
||||
|
||||
from sglang.srt.utils.common import is_cuda, is_hip, is_musa, is_npu, is_xpu
|
||||
|
||||
if is_cuda() or is_hip() or is_musa() or is_xpu():
|
||||
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, XPU, 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")
|
||||
@@ -0,0 +1,112 @@
|
||||
from contextlib import ExitStack
|
||||
from typing import Any
|
||||
from unittest.mock import patch
|
||||
|
||||
import torch
|
||||
|
||||
from sglang.srt.compilation.compilation_counter import compilation_counter
|
||||
from sglang.srt.compilation.compile_phase import (
|
||||
get_pcg_capture_stream,
|
||||
is_in_torch_compile_warmup,
|
||||
)
|
||||
from sglang.srt.compilation.cuda_piecewise_backend import (
|
||||
CUDAPiecewiseBackend,
|
||||
weak_ref_tensors,
|
||||
)
|
||||
from sglang.srt.utils.common import print_warning_once
|
||||
|
||||
|
||||
class XPUPiecewiseBackend(CUDAPiecewiseBackend):
|
||||
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:
|
||||
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)
|
||||
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,
|
||||
)
|
||||
|
||||
if self.is_last_graph and not self.to_be_compiled_sizes:
|
||||
self.check_for_ending_compilation()
|
||||
|
||||
if is_in_torch_compile_warmup():
|
||||
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)
|
||||
|
||||
# During normal capture (PiecewiseCudaGraphRunner.capture()),
|
||||
# set_pcg_capture_stream() guarantees a valid stream. However,
|
||||
# Dynamo may silently recompile on serving batches whose token
|
||||
# count exceeds the captured range (e.g. chunked prefill running
|
||||
# at 8192 tokens when the capture grid tops out at 512). The
|
||||
# recompiled backend instance has no capture stream; fall back to
|
||||
# eager for that sub-graph instead of crashing the scheduler.
|
||||
# Mirrors the HIP fallback in CUDAPiecewiseBackend.__call__.
|
||||
stream = get_pcg_capture_stream()
|
||||
if stream is None:
|
||||
print_warning_once(
|
||||
"PCG capture stream is not set; likely a Dynamo runtime "
|
||||
"recompilation. Falling back to eager execution for this "
|
||||
"subgraph."
|
||||
)
|
||||
return entry.runnable(*args)
|
||||
|
||||
if self.compile_config.get_enable_debug_mode():
|
||||
entry.input_addresses = [
|
||||
x.data_ptr() for x in args if isinstance(x, torch.Tensor)
|
||||
]
|
||||
|
||||
xpugraph = torch.xpu.XPUGraph()
|
||||
|
||||
with ExitStack() as stack:
|
||||
if not self.is_first_graph:
|
||||
stack.enter_context(patch("gc.collect", lambda: None))
|
||||
stack.enter_context(patch("torch.xpu.empty_cache", lambda: None))
|
||||
|
||||
with torch.xpu.graph(
|
||||
xpu_graph=xpugraph, pool=self.graph_pool, stream=stream
|
||||
):
|
||||
output = entry.runnable(*args)
|
||||
if self.is_last_graph:
|
||||
output = weak_ref_tensors(output)
|
||||
|
||||
entry.output = weak_ref_tensors(output)
|
||||
entry.cudagraph = xpugraph
|
||||
|
||||
compilation_counter.num_cudagraph_captured += 1
|
||||
return output
|
||||
|
||||
if self.compile_config.get_enable_debug_mode():
|
||||
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
|
||||
@@ -0,0 +1,91 @@
|
||||
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.cohere2_moe import Cohere2MoeConfig
|
||||
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.interns2preview import InternS2PreviewConfig
|
||||
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.laguna import LagunaConfig
|
||||
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.locate_anything import LocateAnythingConfig
|
||||
from sglang.srt.configs.longcat_flash import LongcatFlashConfig
|
||||
from sglang.srt.configs.minicpmv4_6 import MiniCPMV4_6Config, MiniCPMV4_6VisionConfig
|
||||
from sglang.srt.configs.minimax_vl import MiniMaxM3VLConfig
|
||||
from sglang.srt.configs.nano_nemotron_vl import (
|
||||
NemotronH_Nano_Omni_Reasoning_V3_Config,
|
||||
NemotronH_Nano_VL_V2_Config,
|
||||
)
|
||||
from sglang.srt.configs.nemotron_h import NemotronHConfig, NemotronHPuzzleConfig
|
||||
from sglang.srt.configs.olmo3 import Olmo3Config
|
||||
from sglang.srt.configs.qwen3_5 import Qwen3_5Config, Qwen3_5MoeConfig
|
||||
from sglang.srt.configs.qwen3_asr import Qwen3ASRConfig
|
||||
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
|
||||
from sglang.srt.configs.step3p7 import Step3p7Config
|
||||
from sglang.srt.configs.unlimited_ocr import UnlimitedVLConfig
|
||||
from sglang.srt.configs.zaya import ZayaConfig
|
||||
|
||||
__all__ = [
|
||||
"AfmoeConfig",
|
||||
"BailingHybridConfig",
|
||||
"ExaoneConfig",
|
||||
"ChatGLMConfig",
|
||||
"DbrxConfig",
|
||||
"DeepseekVL2Config",
|
||||
"LongcatFlashConfig",
|
||||
"MultiModalityConfig",
|
||||
"KimiVLConfig",
|
||||
"MoonViTConfig",
|
||||
"Step3VLConfig",
|
||||
"Step3TextConfig",
|
||||
"Step3VisionEncoderConfig",
|
||||
"Olmo3Config",
|
||||
"KimiLinearConfig",
|
||||
"KimiK25Config",
|
||||
"LagunaConfig",
|
||||
"Qwen3NextConfig",
|
||||
"Qwen3_5Config",
|
||||
"Qwen3_5MoeConfig",
|
||||
"InternS2PreviewConfig",
|
||||
"DotsVLMConfig",
|
||||
"DotsOCRConfig",
|
||||
"FalconH1Config",
|
||||
"GraniteMoeHybridConfig",
|
||||
"Lfm2Config",
|
||||
"Lfm2MoeConfig",
|
||||
"Lfm2VlConfig",
|
||||
"LocateAnythingConfig",
|
||||
"MiniCPMV4_6Config",
|
||||
"MiniCPMV4_6VisionConfig",
|
||||
"NemotronHConfig",
|
||||
"NemotronHPuzzleConfig",
|
||||
"NemotronH_Nano_VL_V2_Config",
|
||||
"NemotronH_Nano_Omni_Reasoning_V3_Config",
|
||||
"JetNemotronConfig",
|
||||
"JetVLMConfig",
|
||||
"Step3p5Config",
|
||||
"MiniMaxM3VLConfig",
|
||||
"Step3p7Config",
|
||||
"Qwen3ASRConfig",
|
||||
"UnlimitedVLConfig",
|
||||
"ZayaConfig",
|
||||
]
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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
|
||||
from sglang.srt.runtime_context import get_parallel
|
||||
|
||||
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:
|
||||
|
||||
shape = Mamba2StateShape.create(
|
||||
tp_world_size=get_parallel().attn_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)
|
||||
@@ -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)
|
||||
@@ -0,0 +1,91 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
"""Cohere2Moe text config used by the Cohere Command-A Plus checkpoints."""
|
||||
|
||||
from transformers.configuration_utils import PreTrainedConfig
|
||||
from transformers.models.auto.configuration_auto import CONFIG_MAPPING
|
||||
|
||||
try:
|
||||
from huggingface_hub.dataclasses import strict
|
||||
except ImportError: # older huggingface_hub
|
||||
|
||||
def strict(cls): # type: ignore[misc]
|
||||
return cls
|
||||
|
||||
|
||||
@strict
|
||||
class Cohere2MoeConfig(PreTrainedConfig):
|
||||
model_type = "cohere2_moe"
|
||||
keys_to_ignore_at_inference = ["past_key_values"]
|
||||
|
||||
vocab_size: int = 256000
|
||||
hidden_size: int = 8192
|
||||
intermediate_size: int = 22528
|
||||
logit_scale: float = 0.0625
|
||||
num_hidden_layers: int = 40
|
||||
num_attention_heads: int = 64
|
||||
num_key_value_heads: int | None = None
|
||||
head_dim: int = 128
|
||||
hidden_act: str = "silu"
|
||||
max_position_embeddings: int = 8192
|
||||
initializer_range: float = 0.02
|
||||
layer_norm_eps: float = 1e-5
|
||||
use_cache: bool = True
|
||||
pad_token_id: int | None = 0
|
||||
bos_token_id: int | None = 5
|
||||
eos_token_id: int | list[int] | None = 255001
|
||||
tie_word_embeddings: bool = True
|
||||
rope_theta: float | int = 10000.0
|
||||
rope_scaling: dict | None = None
|
||||
attention_bias: bool = False
|
||||
attention_dropout: float = 0.0
|
||||
sliding_window: int | None = 4096
|
||||
num_experts_per_tok: int = 2
|
||||
num_experts: int = 8
|
||||
num_shared_experts: int = 0
|
||||
shared_expert_combination_strategy: str = "average"
|
||||
expert_selection_fn: str = "softmax"
|
||||
layer_types: list[str] | None = None
|
||||
first_k_dense_replace: int = 0
|
||||
prefix_dense_sliding_window_pattern: int = 1
|
||||
norm_topk_prob: bool = True
|
||||
prefix_dense_intermediate_size: int | None = None
|
||||
rms_norm_eps: float | None = None
|
||||
sliding_window_pattern: int = 4
|
||||
|
||||
def __post_init__(self, **kwargs):
|
||||
if self.num_key_value_heads is None:
|
||||
self.num_key_value_heads = self.num_attention_heads
|
||||
|
||||
if hasattr(self, "standardize_rope_params"):
|
||||
try:
|
||||
self.standardize_rope_params()
|
||||
self.validate_rope()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if self.layer_types is None:
|
||||
prefix_layers = [
|
||||
(
|
||||
"sliding_attention"
|
||||
if ((i + 1) % self.prefix_dense_sliding_window_pattern) != 0
|
||||
else "full_attention"
|
||||
)
|
||||
for i in range(self.first_k_dense_replace)
|
||||
]
|
||||
rest_layers = [
|
||||
(
|
||||
"sliding_attention"
|
||||
if ((i + 1) % self.sliding_window_pattern) != 0
|
||||
else "full_attention"
|
||||
)
|
||||
for i in range(self.num_hidden_layers - self.first_k_dense_replace)
|
||||
]
|
||||
self.layer_types = prefix_layers + rest_layers
|
||||
|
||||
super().__post_init__(**kwargs)
|
||||
|
||||
|
||||
try:
|
||||
CONFIG_MAPPING.register("cohere2_moe", Cohere2MoeConfig)
|
||||
except Exception:
|
||||
CONFIG_MAPPING._extra_content["cohere2_moe"] = Cohere2MoeConfig
|
||||
@@ -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,
|
||||
)
|
||||
@@ -0,0 +1,905 @@
|
||||
import math
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||
|
||||
import torch
|
||||
from PIL import Image, ImageOps
|
||||
from torchvision.transforms import InterpolationMode
|
||||
from torchvision.transforms import functional as TF
|
||||
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,
|
||||
)
|
||||
|
||||
DeepseekOCRImage = Union[Image.Image, torch.Tensor]
|
||||
|
||||
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."
|
||||
|
||||
|
||||
def get_image_size(img: DeepseekOCRImage) -> Tuple[int, int]:
|
||||
"""Return (width, height) for both PIL.Image and torch.Tensor (CHW)."""
|
||||
if isinstance(img, Image.Image):
|
||||
return img.size
|
||||
if isinstance(img, torch.Tensor):
|
||||
if img.ndim != 3:
|
||||
raise TypeError(f"Expected CHW image tensor, got shape {tuple(img.shape)}")
|
||||
return int(img.shape[-1]), int(img.shape[-2])
|
||||
raise TypeError(f"Unsupported image type: {type(img)}")
|
||||
|
||||
|
||||
def resize_image(img: DeepseekOCRImage, size: Tuple[int, int]) -> DeepseekOCRImage:
|
||||
"""Resize image to (width, height) for both PIL and tensor."""
|
||||
if isinstance(img, Image.Image):
|
||||
return img.resize(size, Image.BICUBIC)
|
||||
return TF.resize(
|
||||
img,
|
||||
[size[1], size[0]],
|
||||
interpolation=InterpolationMode.BICUBIC,
|
||||
antialias=True,
|
||||
).contiguous()
|
||||
|
||||
|
||||
def crop_image(
|
||||
img: DeepseekOCRImage, box: Tuple[int, int, int, int]
|
||||
) -> DeepseekOCRImage:
|
||||
"""Crop image with box=(left, upper, right, lower) for both PIL and tensor."""
|
||||
if isinstance(img, Image.Image):
|
||||
return img.crop(box)
|
||||
left, upper, right, lower = box
|
||||
return img[:, upper:lower, left:right].contiguous()
|
||||
|
||||
|
||||
def pad_image(
|
||||
img: DeepseekOCRImage,
|
||||
target_size: Tuple[int, int],
|
||||
fill_color: Tuple[int, int, int],
|
||||
) -> DeepseekOCRImage:
|
||||
"""Fit-and-center-pad image to target_size=(width, height).
|
||||
|
||||
Replaces ImageOps.pad for tensor inputs.
|
||||
"""
|
||||
if isinstance(img, Image.Image):
|
||||
return ImageOps.pad(img, target_size, color=fill_color)
|
||||
# tensor path: CHW format
|
||||
_, h, w = img.shape
|
||||
target_w, target_h = target_size
|
||||
scale = min(target_w / w, target_h / h)
|
||||
new_w = int(w * scale)
|
||||
new_h = int(h * scale)
|
||||
resized = TF.resize(
|
||||
img,
|
||||
[new_h, new_w],
|
||||
interpolation=InterpolationMode.BICUBIC,
|
||||
antialias=True,
|
||||
)
|
||||
pad_left = (target_w - new_w) // 2
|
||||
pad_top = (target_h - new_h) // 2
|
||||
if img.dtype == torch.uint8:
|
||||
fill_tensor = torch.tensor(
|
||||
list(fill_color), device=img.device, dtype=torch.uint8
|
||||
).view(3, 1, 1)
|
||||
else:
|
||||
fill_tensor = torch.tensor(
|
||||
[c / 255.0 for c in fill_color], device=img.device, dtype=img.dtype
|
||||
).view(3, 1, 1)
|
||||
result = fill_tensor.expand(3, target_h, target_w).clone()
|
||||
result[:, pad_top : pad_top + new_h, pad_left : pad_left + new_w] = resized
|
||||
return result.contiguous()
|
||||
|
||||
|
||||
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, img):
|
||||
if isinstance(img, torch.Tensor):
|
||||
x = img
|
||||
if x.dtype == torch.uint8:
|
||||
x = x.to(torch.float32).div(255)
|
||||
elif not x.is_floating_point():
|
||||
x = x.to(torch.float32)
|
||||
if self.normalize:
|
||||
|
||||
import torchvision.transforms as T
|
||||
|
||||
x = T.Normalize(self.mean, self.std)(x)
|
||||
return x
|
||||
x = self.transform(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 = get_image_size(image)
|
||||
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 = resize_image(image, (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 = crop_image(resized_img, box)
|
||||
processed_images.append(split_img)
|
||||
assert len(processed_images) == blocks
|
||||
if use_thumbnail and len(processed_images) != 1:
|
||||
thumbnail_img = resize_image(image, (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)
|
||||
|
||||
img_w, img_h = get_image_size(image)
|
||||
image_shapes.append((img_w, img_h))
|
||||
|
||||
if img_w <= 640 and img_h <= 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 = resize_image(image, (self.image_size, self.image_size))
|
||||
|
||||
global_view = pad_image(
|
||||
image,
|
||||
(self.base_size, self.base_size),
|
||||
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)
|
||||
@@ -0,0 +1,110 @@
|
||||
import logging
|
||||
import os
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from transformers import PretrainedConfig
|
||||
|
||||
from sglang.srt.layers.quantization.base_config import QuantizationConfig
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def try_detect_fp4_experts(model_path: str) -> Optional[bool]:
|
||||
"""True = mxfp4-packed (U8/I8/F4), False = converted FP8 (F8_E4M3),
|
||||
None when the header isn't readable (HF slug not cached yet, etc.).
|
||||
Caller falls back to user default. Pure read; never mutates env.
|
||||
"""
|
||||
from sglang.srt.model_loader.weight_utils import (
|
||||
probe_routed_expert_weight_dtype,
|
||||
)
|
||||
from sglang.srt.utils import find_local_repo_dir
|
||||
|
||||
if os.path.isdir(model_path):
|
||||
local_path = model_path
|
||||
else:
|
||||
local_path = find_local_repo_dir(model_path)
|
||||
if not local_path or not os.path.isdir(local_path):
|
||||
return None
|
||||
|
||||
try:
|
||||
dtype = probe_routed_expert_weight_dtype(local_path)
|
||||
except Exception as e:
|
||||
logger.warning("Failed to probe routed-expert dtype for %s: %s", model_path, e)
|
||||
return None
|
||||
if dtype is None:
|
||||
return None
|
||||
if dtype in ("U8", "I8", "F4"):
|
||||
return True
|
||||
if dtype == "F8_E4M3":
|
||||
return False
|
||||
logger.warning(
|
||||
"Unexpected routed-expert safetensors dtype=%s for DeepSeek V4", dtype
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class DeepSeekV4Config(PretrainedConfig):
|
||||
architectures: List[str]
|
||||
attention_bias: bool = False
|
||||
attention_dropout: float = 0.0
|
||||
bos_token_id: int = 0
|
||||
eos_token_id: int = 1
|
||||
ep_size: int = 1
|
||||
first_k_dense_replace: int = 0
|
||||
hidden_act: str = "silu"
|
||||
hidden_size: int = 4096
|
||||
index_head_dim: int = 128
|
||||
index_n_heads: int = 64
|
||||
index_topk: int = 512
|
||||
initializer_range: float = 0.02
|
||||
intermediate_size: int = 2048
|
||||
kv_lora_rank: int = 512
|
||||
max_position_embeddings: int = 65536
|
||||
model_type: str = "deepseek_v4"
|
||||
moe_intermediate_size: int = 2048
|
||||
moe_layer_freq: int = 1
|
||||
n_group: int = 8
|
||||
n_routed_experts: int = 256
|
||||
n_shared_experts: int = 1
|
||||
norm_topk_prob: bool = True
|
||||
|
||||
num_attention_heads: int = 64
|
||||
num_experts_per_tok: int = 6
|
||||
num_hidden_layers: int = 43
|
||||
num_key_value_heads: int = 1
|
||||
|
||||
q_lora_rank: int = 1024
|
||||
qk_nope_head_dim: int = 448
|
||||
qk_rope_head_dim: int = 64
|
||||
|
||||
quantization_config: QuantizationConfig = field(default_factory=QuantizationConfig)
|
||||
|
||||
rms_norm_eps: float = 1e-6
|
||||
|
||||
rope_scaling: Dict[str, float] = field(default_factory=dict)
|
||||
rope_theta: int = 10000
|
||||
|
||||
routed_scaling_factor: float = 1.5
|
||||
scoring_func: str = "sqrtsoftplus"
|
||||
|
||||
tie_word_embeddings: bool = False
|
||||
|
||||
topk_group: int = 8
|
||||
topk_method: str = "noaux_tc"
|
||||
|
||||
use_cache: bool = True
|
||||
v_head_dim: int = 512
|
||||
vocab_size: int = 129280
|
||||
o_lora_rank: int = 1024
|
||||
o_groups: int = 8
|
||||
window_size: int = 128
|
||||
|
||||
compress_rope_theta: int = 40000
|
||||
compress_ratios: List[int] = field(default_factory=list)
|
||||
|
||||
n_hash_layers: int = 3
|
||||
hc_mult: int = 4
|
||||
hc_sinkhorn_iters: int = 20
|
||||
hc_eps: float = 1e-6
|
||||
@@ -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)
|
||||
@@ -0,0 +1,23 @@
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
import torch
|
||||
|
||||
from sglang.srt.platforms import current_platform
|
||||
|
||||
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 or current_platform.is_out_of_tree():
|
||||
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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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,
|
||||
)
|
||||
from sglang.srt.runtime_context import get_parallel
|
||||
|
||||
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):
|
||||
|
||||
shape = Mamba2StateShape.create(
|
||||
tp_world_size=get_parallel().attn_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)
|
||||
)
|
||||
@@ -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
|
||||
from sglang.srt.runtime_context import get_parallel
|
||||
|
||||
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."""
|
||||
|
||||
shape = Mamba2StateShape.create(
|
||||
tp_world_size=get_parallel().attn_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)
|
||||
@@ -0,0 +1,23 @@
|
||||
from sglang.srt.configs.qwen3_5 import (
|
||||
Qwen3_5MoeConfig,
|
||||
Qwen3_5MoeTextConfig,
|
||||
Qwen3_5MoeVisionConfig,
|
||||
)
|
||||
|
||||
|
||||
class InternS2PreviewVisionConfig(Qwen3_5MoeVisionConfig):
|
||||
model_type = "intern_s2_preview"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
class InternS2PreviewConfig(Qwen3_5MoeConfig):
|
||||
model_type = "intern_s2_preview"
|
||||
sub_configs = {
|
||||
"vision_config": InternS2PreviewVisionConfig,
|
||||
"text_config": Qwen3_5MoeTextConfig,
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
@@ -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
|
||||
)
|
||||
@@ -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)
|
||||
@@ -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,
|
||||
)
|
||||
from sglang.srt.runtime_context import get_parallel
|
||||
|
||||
|
||||
@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:
|
||||
|
||||
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_parallel().attn_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)
|
||||
)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,163 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
# 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
|
||||
from sglang.srt.runtime_context import get_parallel
|
||||
|
||||
|
||||
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:
|
||||
|
||||
shape = KimiLinearStateShape.create(
|
||||
tp_world_size=get_parallel().attn_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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -0,0 +1,232 @@
|
||||
# coding=utf-8
|
||||
# Copyright 2023-2026 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
|
||||
"""Laguna (poolside/Laguna-XS.2) model configuration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Literal, Optional
|
||||
|
||||
from transformers.configuration_utils import PretrainedConfig
|
||||
from transformers.utils import logging
|
||||
|
||||
logger = logging.get_logger(__name__)
|
||||
|
||||
|
||||
def _first_not_none(*candidates: Any) -> Any:
|
||||
"""First non-None candidate. Unlike `a or b`, preserves falsy values."""
|
||||
return next((c for c in candidates if c is not None), None)
|
||||
|
||||
|
||||
def normalize_gating(value: Any) -> Literal["per-head", "per-element", "disabled"]:
|
||||
if value in (True, "per-head"):
|
||||
return "per-head"
|
||||
if value == "per-element":
|
||||
return "per-element"
|
||||
if value in (False, None, "disabled"):
|
||||
return "disabled"
|
||||
raise ValueError(
|
||||
"gating must be one of True, False, None, "
|
||||
'"per-head", "per-element", or "disabled"; '
|
||||
f"got {value!r}."
|
||||
)
|
||||
|
||||
|
||||
def _to_sglang_rope_scaling(rope_params: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||||
"""HF per-layer rope dict → SGLang `get_rope` `rope_scaling`. None means plain RoPE."""
|
||||
if not rope_params:
|
||||
return None
|
||||
rope_type = rope_params.get("rope_type") or rope_params.get("type")
|
||||
if rope_type in (None, "default"):
|
||||
return None
|
||||
|
||||
out: Dict[str, Any] = {"rope_type": rope_type}
|
||||
pass_through = (
|
||||
"factor",
|
||||
"original_max_position_embeddings",
|
||||
"beta_fast",
|
||||
"beta_slow",
|
||||
"extrapolation_factor",
|
||||
"truncate",
|
||||
"low_freq_factor",
|
||||
"high_freq_factor",
|
||||
"mscale",
|
||||
"mscale_all_dim",
|
||||
"short_factor",
|
||||
"long_factor",
|
||||
"short_mscale",
|
||||
"long_mscale",
|
||||
)
|
||||
for key in pass_through:
|
||||
if key in rope_params:
|
||||
out[key] = rope_params[key]
|
||||
if "attention_factor" in rope_params:
|
||||
# HF spells it attention_factor; SGLang's factory reads attn_factor.
|
||||
out["attn_factor"] = rope_params["attention_factor"]
|
||||
return out
|
||||
|
||||
|
||||
class LagunaConfig(PretrainedConfig):
|
||||
model_type = "laguna"
|
||||
keys_to_ignore_at_inference = ["past_key_values"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vocab_size: int = 100352,
|
||||
hidden_size: int = 2048,
|
||||
intermediate_size: int = 8192,
|
||||
num_hidden_layers: int = 40,
|
||||
num_attention_heads: int = 48,
|
||||
num_key_value_heads: int = 8,
|
||||
head_dim: int = 128,
|
||||
hidden_act: str = "silu",
|
||||
max_position_embeddings: int = 131072,
|
||||
initializer_range: float = 0.02,
|
||||
rms_norm_eps: float = 1e-6,
|
||||
use_cache: bool = True,
|
||||
tie_word_embeddings: bool = False,
|
||||
attention_bias: bool = False,
|
||||
attention_dropout: float = 0.0,
|
||||
gating: bool | str = True,
|
||||
sliding_window: int = 512,
|
||||
layer_types: Optional[List[str]] = None,
|
||||
mlp_layer_types: Optional[List[str]] = None,
|
||||
num_attention_heads_per_layer: Optional[List[int]] = None,
|
||||
num_experts: int = 256,
|
||||
num_experts_per_tok: int = 8,
|
||||
moe_intermediate_size: int = 512,
|
||||
shared_expert_intermediate_size: int = 512,
|
||||
moe_routed_scaling_factor: float = 1.0,
|
||||
moe_router_logit_softcapping: float = 0.0,
|
||||
moe_apply_router_weight_on_input: bool = False,
|
||||
# Per-layer-type rope dict; nested under "full_attention" / "sliding_attention".
|
||||
rope_parameters: Optional[Dict[str, Any]] = None,
|
||||
partial_rotary_factor: Optional[float] = None,
|
||||
rope_theta: Optional[float] = None,
|
||||
rope_scaling: Optional[Dict[str, Any]] = None,
|
||||
bos_token_id: Optional[int] = 2,
|
||||
eos_token_id: Optional[Any] = None,
|
||||
pad_token_id: Optional[int] = 9,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(
|
||||
tie_word_embeddings=tie_word_embeddings,
|
||||
bos_token_id=bos_token_id,
|
||||
eos_token_id=eos_token_id,
|
||||
pad_token_id=pad_token_id,
|
||||
**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_key_value_heads = num_key_value_heads
|
||||
self.head_dim = head_dim
|
||||
self.hidden_act = hidden_act
|
||||
self.max_position_embeddings = max_position_embeddings
|
||||
self.initializer_range = initializer_range
|
||||
self.rms_norm_eps = rms_norm_eps
|
||||
self.use_cache = use_cache
|
||||
self.attention_bias = attention_bias
|
||||
self.attention_dropout = attention_dropout
|
||||
self.gating = normalize_gating(gating)
|
||||
self.sliding_window = sliding_window
|
||||
|
||||
self.num_experts = num_experts
|
||||
self.num_experts_per_tok = num_experts_per_tok
|
||||
self.moe_intermediate_size = moe_intermediate_size
|
||||
self.shared_expert_intermediate_size = shared_expert_intermediate_size
|
||||
self.moe_routed_scaling_factor = moe_routed_scaling_factor
|
||||
self.moe_router_logit_softcapping = moe_router_logit_softcapping
|
||||
self.moe_apply_router_weight_on_input = moe_apply_router_weight_on_input
|
||||
|
||||
# Synthesise per-layer schedules when the caller omits them so the model
|
||||
# file can index by layer_id without per-call guards.
|
||||
self.layer_types = (
|
||||
list(layer_types)
|
||||
if layer_types
|
||||
else ["full_attention" for _ in range(num_hidden_layers)]
|
||||
)
|
||||
self.mlp_layer_types = (
|
||||
list(mlp_layer_types)
|
||||
if mlp_layer_types
|
||||
else (["dense"] + ["sparse"] * (num_hidden_layers - 1))
|
||||
)
|
||||
self.num_attention_heads_per_layer = (
|
||||
list(num_attention_heads_per_layer)
|
||||
if (num_attention_heads_per_layer)
|
||||
else [num_attention_heads] * num_hidden_layers
|
||||
)
|
||||
if len(self.num_attention_heads_per_layer) != num_hidden_layers:
|
||||
raise ValueError(
|
||||
"num_attention_heads_per_layer must have one entry per layer: "
|
||||
f"expected num_hidden_layers={num_hidden_layers}, "
|
||||
f"got {len(self.num_attention_heads_per_layer)}."
|
||||
)
|
||||
|
||||
# SGLang's hybrid-SWA core reads `swa_*` KV/head_dim from hf_text_config.
|
||||
# Per-layer Q-head count is read directly from num_attention_heads_per_layer.
|
||||
# DFlash draft configs can be all-SWA. In that case there is no full
|
||||
# layer geometry to expose, so use layer 0 for the default attention
|
||||
# fields and keep per-layer Q-head geometry explicit.
|
||||
full_idx = (
|
||||
self.layer_types.index("full_attention")
|
||||
if "full_attention" in self.layer_types
|
||||
else 0
|
||||
)
|
||||
self.num_attention_heads = self.num_attention_heads_per_layer[full_idx]
|
||||
self.swa_num_key_value_heads = num_key_value_heads
|
||||
self.swa_head_dim = head_dim
|
||||
self.swa_v_head_dim = head_dim
|
||||
|
||||
# Released checkpoint nests rope_parameters under layer-type keys.
|
||||
rp = rope_parameters if isinstance(rope_parameters, dict) else {}
|
||||
has_full_attention = "full_attention" in self.layer_types
|
||||
swa_rp = rp.get("sliding_attention") or {}
|
||||
full_rp = rp.get("full_attention") or (swa_rp if not has_full_attention else {})
|
||||
|
||||
# transformers v5 aliases `rope_scaling` ↔ `rope_parameters` on
|
||||
# PretrainedConfig — writing one clobbers the other. Keep the nested
|
||||
# form on those two slots (so HF's reference modeling code can index
|
||||
# rope_parameters[layer_type] when invoked via trust_remote_code) and
|
||||
# publish our SGLang-shaped flat rope dicts under different names.
|
||||
self.rope_parameters = rope_parameters
|
||||
|
||||
self.rope_theta = _first_not_none(
|
||||
full_rp.get("rope_theta"), rope_theta, 10000.0
|
||||
)
|
||||
self.partial_rotary_factor = _first_not_none(
|
||||
full_rp.get("partial_rotary_factor"), partial_rotary_factor, 1.0
|
||||
)
|
||||
self.full_rope_scaling = _first_not_none(
|
||||
_to_sglang_rope_scaling(full_rp), rope_scaling
|
||||
)
|
||||
|
||||
self.swa_rope_theta = _first_not_none(swa_rp.get("rope_theta"), self.rope_theta)
|
||||
self.swa_partial_rotary_factor = _first_not_none(
|
||||
swa_rp.get("partial_rotary_factor"), self.partial_rotary_factor
|
||||
)
|
||||
self.swa_rope_scaling = _to_sglang_rope_scaling(swa_rp)
|
||||
|
||||
# DeepSeek-style aliases consumed by cross-cutting infra outside this
|
||||
# model file: `lora/mem_pool.py` and `lora/utils.py` read
|
||||
# `n_routed_experts` / `n_shared_experts` / `first_k_dense_replace`,
|
||||
# `elastic_ep/expert_backup_*` reads `n_routed_experts`. The
|
||||
# hardcoded `n_shared_experts=1` and `norm_topk_prob=True` reflect
|
||||
# Laguna's fixed architecture (one shared expert, sigmoid-renormalized
|
||||
# top-k routing).
|
||||
self.n_routed_experts = num_experts
|
||||
self.n_shared_experts = 1
|
||||
self.routed_scaling_factor = moe_routed_scaling_factor
|
||||
self.norm_topk_prob = True
|
||||
self.first_k_dense_replace = (
|
||||
self.mlp_layer_types.index("sparse")
|
||||
if "sparse" in self.mlp_layer_types
|
||||
else num_hidden_layers
|
||||
)
|
||||
@@ -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,
|
||||
)
|
||||
from sglang.srt.runtime_context import get_parallel
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
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_parallel().attn_tp_size requires initialization, default to 1 if not available
|
||||
try:
|
||||
tp_size = get_parallel().attn_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
|
||||
@@ -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
|
||||
from sglang.srt.runtime_context import get_parallel
|
||||
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
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_parallel().attn_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
|
||||
@@ -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
|
||||
from sglang.srt.runtime_context import get_parallel
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
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_parallel().attn_tp_size requires initialization, default to 1 if not available
|
||||
try:
|
||||
tp_size = get_parallel().attn_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
|
||||
@@ -0,0 +1,72 @@
|
||||
"""Registry for linear attention hybrid models (softmax + linear attention).
|
||||
|
||||
External models can register themselves without modifying SGLang core files:
|
||||
|
||||
from sglang.srt.configs.linear_attn_model_registry import (
|
||||
register_linear_attn_model, LinearAttnModelSpec,
|
||||
)
|
||||
|
||||
register_linear_attn_model(LinearAttnModelSpec(
|
||||
config_class=MyLinearAttnConfig,
|
||||
backend_class_name="sglang.srt.layers.attention.linear.kda_backend.KDAAttnBackend",
|
||||
arch_names=["MyLinearAttnForCausalLM"],
|
||||
uses_mamba_radix_cache=True,
|
||||
support_mamba_cache=True,
|
||||
))
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
import logging
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class LinearAttnModelSpec:
|
||||
"""Specification for a hybrid (softmax + linear attention) model."""
|
||||
|
||||
config_class: type
|
||||
backend_class_name: str # fully-qualified class name, lazily imported
|
||||
arch_names: list[str] = field(default_factory=list)
|
||||
uses_mamba_radix_cache: bool = True
|
||||
support_mamba_cache: bool = True
|
||||
support_mamba_cache_extra_buffer: bool = False
|
||||
unwrap_text_config: bool = False # call get_text_config() before isinstance check
|
||||
|
||||
|
||||
_LINEAR_ATTN_MODEL_REGISTRY: list[LinearAttnModelSpec] = []
|
||||
|
||||
|
||||
def register_linear_attn_model(spec: LinearAttnModelSpec) -> None:
|
||||
_LINEAR_ATTN_MODEL_REGISTRY.append(spec)
|
||||
logger.info(
|
||||
"Registered linear attn model: config=%s, backend=%s, archs=%s",
|
||||
spec.config_class.__name__,
|
||||
spec.backend_class_name.rsplit(".", 1)[-1],
|
||||
spec.arch_names,
|
||||
)
|
||||
|
||||
|
||||
def get_linear_attn_config(hf_config: Any) -> Optional[tuple[LinearAttnModelSpec, Any]]:
|
||||
for spec in _LINEAR_ATTN_MODEL_REGISTRY:
|
||||
config = hf_config.get_text_config() if spec.unwrap_text_config else hf_config
|
||||
if isinstance(config, spec.config_class):
|
||||
return spec, config
|
||||
return None
|
||||
|
||||
|
||||
def get_linear_attn_spec_by_arch(arch_name: str) -> Optional[LinearAttnModelSpec]:
|
||||
for spec in _LINEAR_ATTN_MODEL_REGISTRY:
|
||||
if arch_name in spec.arch_names:
|
||||
return spec
|
||||
return None
|
||||
|
||||
|
||||
def import_backend_class(dotted_name: str) -> type:
|
||||
module_path, class_name = dotted_name.rsplit(".", 1)
|
||||
module = importlib.import_module(module_path)
|
||||
return getattr(module, class_name)
|
||||
@@ -0,0 +1,147 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
# 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
|
||||
remote_instance_weight_loader_transfer_engine_session_id: Optional[str] = None
|
||||
modelexpress_url: Optional[str] = None
|
||||
modelexpress_transport: str = "nixl"
|
||||
|
||||
# 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
|
||||
|
||||
# Inc-related loading options
|
||||
inc_save_path: Optional[str] = None
|
||||
inc_tuning_iters: Optional[int] = 0
|
||||
inc_disable_opt_rtn: Optional[bool] = 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}"
|
||||
)
|
||||
@@ -0,0 +1,63 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Adapted from https://huggingface.co/nvidia/LocateAnything-3B/blob/main/configuration_locateanything.py
|
||||
"""Config for nvidia/LocateAnything-3B.
|
||||
|
||||
LocateAnything is a multimodal grounding/detection model composed of a MoonViT
|
||||
vision encoder, an InternVL-style ``mlp1`` projector, and a Qwen2 language model
|
||||
backbone. The config is a composite that wraps a ``MoonViTConfig`` (vision) and a
|
||||
``Qwen2Config`` (text) plus the special token ids used for the grounding grammar
|
||||
(``<box>``/``<ref>``/coordinate tokens).
|
||||
"""
|
||||
|
||||
from typing import Optional, Union
|
||||
|
||||
from transformers.configuration_utils import PretrainedConfig
|
||||
from transformers.models.qwen2 import Qwen2Config
|
||||
|
||||
from sglang.srt.configs.kimi_vl_moonvit import MoonViTConfig
|
||||
|
||||
|
||||
class LocateAnythingConfig(PretrainedConfig):
|
||||
model_type = "locateanything"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vision_config: Optional[Union[dict, MoonViTConfig]] = None,
|
||||
text_config: Optional[Union[dict, Qwen2Config]] = None,
|
||||
image_token_index: int = 151665,
|
||||
box_start_token_id: int = 151668,
|
||||
box_end_token_id: int = 151669,
|
||||
ref_start_token_id: int = 151672,
|
||||
ref_end_token_id: int = 151673,
|
||||
coord_start_token_id: int = 151677,
|
||||
coord_end_token_id: int = 152677,
|
||||
none_token_id: int = 4064,
|
||||
mlp_connector_layers: int = 2,
|
||||
**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 = Qwen2Config()
|
||||
elif isinstance(text_config, dict):
|
||||
text_config = Qwen2Config(**text_config)
|
||||
self.text_config = text_config
|
||||
|
||||
self.image_token_index = image_token_index
|
||||
self.box_start_token_id = box_start_token_id
|
||||
self.box_end_token_id = box_end_token_id
|
||||
# ref_*_token_id and mlp_connector_layers are kept for round-trip
|
||||
# fidelity with the HF config; the box-grammar processor reads the box /
|
||||
# coord / none ids, and the projector hardcodes its 2-layer structure.
|
||||
self.ref_start_token_id = ref_start_token_id
|
||||
self.ref_end_token_id = ref_end_token_id
|
||||
self.coord_start_token_id = coord_start_token_id
|
||||
self.coord_end_token_id = coord_end_token_id
|
||||
self.none_token_id = none_token_id
|
||||
self.mlp_connector_layers = mlp_connector_layers
|
||||
|
||||
super().__init__(**kwargs)
|
||||
@@ -0,0 +1,124 @@
|
||||
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,
|
||||
oe_vocab_size_ratio=None,
|
||||
oe_neighbor_num=None,
|
||||
oe_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"
|
||||
if ngram_vocab_size_ratio is None:
|
||||
ngram_vocab_size_ratio = oe_vocab_size_ratio
|
||||
if emb_neighbor_num is None:
|
||||
emb_neighbor_num = oe_neighbor_num
|
||||
if emb_split_num is None:
|
||||
emb_split_num = oe_split_num
|
||||
self.oe_vocab_size_ratio = oe_vocab_size_ratio
|
||||
self.oe_neighbor_num = oe_neighbor_num
|
||||
self.oe_split_num = oe_split_num
|
||||
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
|
||||
@@ -0,0 +1,275 @@
|
||||
# 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)
|
||||
|
||||
@property
|
||||
def is_kda(self) -> bool:
|
||||
"""KDA per-K-channel gate vs GDN/Mamba2 per-head scalar gate. Selects
|
||||
the ReplaySSM ring ``g_cache`` layout ([.., L] scalar vs [.., L, K]
|
||||
per-K) and the gate-generic decode kernel's ``IS_KDA`` path."""
|
||||
return False
|
||||
|
||||
|
||||
@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
|
||||
# Number of key/group heads after TP sharding (== runtime `H` the packed
|
||||
# GDN kernels infer from `mixed_qkv`). Used by the GDN ReplaySSM ring
|
||||
# buffer (k_cache) to size/stride exactly like the kernel expects.
|
||||
num_k_heads_per_tp: int = 1
|
||||
|
||||
@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":
|
||||
# The q/k projections are sharded by `num_k_heads // tp` heads (the
|
||||
# ORIGINAL n_groups, before the conv head-shard extension below), so the
|
||||
# runtime `H` the packed kernels see equals divide(n_groups, tp). Only
|
||||
# meaningful (and only consumed) for the GDN ReplaySSM path, which
|
||||
# requires evenly divisible heads; fall back to ceil-div otherwise.
|
||||
num_k_heads_per_tp = (
|
||||
divide(n_groups, tp_world_size)
|
||||
if n_groups % tp_world_size == 0
|
||||
else -(-n_groups // tp_world_size)
|
||||
)
|
||||
# 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,
|
||||
num_k_heads_per_tp=num_k_heads_per_tp,
|
||||
)
|
||||
|
||||
|
||||
@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
|
||||
# Number of key heads after TP sharding (== runtime ``H`` the KDA packed
|
||||
# kernels infer from ``mixed_qkv``). Mirrors Mamba2StateShape; consumed by
|
||||
# the ReplaySSM ring (k_cache) to size/stride exactly like the kernel.
|
||||
num_k_heads_per_tp: int = 1
|
||||
|
||||
@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
|
||||
num_k_heads_per_tp = (
|
||||
divide(num_k_heads, tp_world_size)
|
||||
if num_k_heads % tp_world_size == 0
|
||||
else -(-num_k_heads // tp_world_size)
|
||||
)
|
||||
|
||||
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,
|
||||
num_k_heads_per_tp=num_k_heads_per_tp,
|
||||
)
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class KimiLinearCacheParams(BaseLinearStateParams):
|
||||
shape: KimiLinearStateShape
|
||||
|
||||
@property
|
||||
def is_kda(self) -> bool:
|
||||
return True
|
||||
@@ -0,0 +1,159 @@
|
||||
# Copyright 2026 The 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
|
||||
"""Sglang-side ``PretrainedConfig`` classes for MiniCPM-V 4.6.
|
||||
|
||||
Mirrors HF ref ``transformers/models/minicpmv4_6/configuration_minicpmv4_6.py``
|
||||
so we can register the configs ourselves while transformers main has not
|
||||
yet shipped native ``MiniCPMV4_6Config`` (lands 5.7+).
|
||||
"""
|
||||
|
||||
from typing import Any, Dict, Optional, Union
|
||||
|
||||
from transformers import AutoConfig, PretrainedConfig
|
||||
from transformers.models.auto import CONFIG_MAPPING
|
||||
|
||||
from sglang.srt.configs.qwen3_5 import Qwen3_5TextConfig
|
||||
|
||||
|
||||
class MiniCPMV4_6VisionConfig(PretrainedConfig):
|
||||
model_type = "minicpmv4_6_vision"
|
||||
base_config_key = "vision_config"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hidden_size: int = 1152,
|
||||
intermediate_size: int = 4304,
|
||||
num_hidden_layers: int = 27,
|
||||
num_attention_heads: int = 16,
|
||||
num_channels: int = 3,
|
||||
image_size: int = 980,
|
||||
patch_size: int = 14,
|
||||
hidden_act: str = "gelu_pytorch_tanh",
|
||||
layer_norm_eps: float = 1e-6,
|
||||
attention_dropout: float = 0.0,
|
||||
insert_layer_id: int = 6,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
super().__init__(**kwargs)
|
||||
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.image_size = image_size
|
||||
self.patch_size = patch_size
|
||||
self.hidden_act = hidden_act
|
||||
self.layer_norm_eps = layer_norm_eps
|
||||
self.attention_dropout = attention_dropout
|
||||
self.insert_layer_id = insert_layer_id
|
||||
|
||||
|
||||
def _resolve_text_config_class(model_type: Optional[str]) -> type:
|
||||
"""``model_type`` -> registered config class. sglang's ``Qwen3_5TextConfig``
|
||||
wins over the stock entry when both exist (it carries ``layers_block_type``
|
||||
etc. that the model code reads); ``AutoConfig.register`` doesn't replace
|
||||
existing entries so we have to short-circuit here. Note that
|
||||
``CONFIG_MAPPING.get`` returns ``None`` even on hit — go through
|
||||
``__getitem__`` to trigger the lazy class import.
|
||||
"""
|
||||
if model_type == Qwen3_5TextConfig.model_type:
|
||||
return Qwen3_5TextConfig
|
||||
if model_type and model_type in CONFIG_MAPPING:
|
||||
return CONFIG_MAPPING[model_type]
|
||||
raise KeyError(f"Unknown text_config model_type: {model_type!r}")
|
||||
|
||||
|
||||
def _build_text_config(
|
||||
text_config: Union[None, dict, PretrainedConfig],
|
||||
) -> PretrainedConfig:
|
||||
"""Coerce ``text_config`` into the right registered backbone class.
|
||||
|
||||
``AutoConfig.from_pretrained`` resolves the ``"text_config"`` entry of
|
||||
``sub_configs`` and hands us a pre-built ``PretrainedConfig``; manual
|
||||
construction in tests / examples passes a dict or ``None``.
|
||||
"""
|
||||
if text_config is None:
|
||||
return _resolve_text_config_class(Qwen3_5TextConfig.model_type)()
|
||||
if isinstance(text_config, PretrainedConfig):
|
||||
cls = _resolve_text_config_class(getattr(text_config, "model_type", None))
|
||||
if isinstance(text_config, cls):
|
||||
return text_config
|
||||
return cls(**text_config.to_dict())
|
||||
if isinstance(text_config, dict):
|
||||
cfg = dict(text_config)
|
||||
cls = _resolve_text_config_class(cfg.pop("model_type", None))
|
||||
return cls(**cfg)
|
||||
raise TypeError(f"Unsupported text_config type: {type(text_config)}")
|
||||
|
||||
|
||||
class MiniCPMV4_6Config(PretrainedConfig):
|
||||
model_type = "minicpmv4_6"
|
||||
# No type annotation: transformers 5+ wraps PretrainedConfig subclasses
|
||||
# with @dataclass(kw_only=True), and an annotated mutable default would be
|
||||
# rejected as a dataclass field. Matches qwen3_5/qwen3_vl/qwen3_omni.
|
||||
sub_configs = {
|
||||
"vision_config": MiniCPMV4_6VisionConfig,
|
||||
"text_config": AutoConfig,
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
text_config: Optional[Union[Dict[str, Any], PretrainedConfig]] = None,
|
||||
vision_config: Optional[Union[Dict[str, Any], PretrainedConfig]] = None,
|
||||
insert_layer_id: int = 6,
|
||||
image_size: int = 448,
|
||||
drop_vision_last_layer: bool = False,
|
||||
image_token_id: Optional[int] = None,
|
||||
video_token_id: Optional[int] = None,
|
||||
tie_word_embeddings: bool = False,
|
||||
downsample_mode: str = "16x",
|
||||
merge_kernel_size=(2, 2),
|
||||
merger_times: int = 1,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
super().__init__(tie_word_embeddings=tie_word_embeddings, **kwargs)
|
||||
|
||||
if isinstance(vision_config, dict):
|
||||
vc = dict(vision_config)
|
||||
vc.pop("model_type", None)
|
||||
self.vision_config = MiniCPMV4_6VisionConfig(**vc)
|
||||
elif vision_config is None:
|
||||
self.vision_config = MiniCPMV4_6VisionConfig()
|
||||
else:
|
||||
self.vision_config = vision_config
|
||||
|
||||
# Mirror the ref ``__post_init__``: keep ``insert_layer_id`` in sync on
|
||||
# both the top-level and the vision sub-config.
|
||||
self.vision_config.insert_layer_id = insert_layer_id
|
||||
self.patch_size = self.vision_config.patch_size
|
||||
|
||||
self.text_config = _build_text_config(text_config)
|
||||
|
||||
self.insert_layer_id = insert_layer_id
|
||||
self.image_size = image_size
|
||||
self.drop_vision_last_layer = drop_vision_last_layer
|
||||
self.image_token_id = image_token_id
|
||||
self.video_token_id = video_token_id
|
||||
self.downsample_mode = downsample_mode
|
||||
self.merge_kernel_size = tuple(merge_kernel_size)
|
||||
self.merger_times = merger_times
|
||||
|
||||
# ``MiniCPMBaseModel.__init__`` reads ``self.config.hidden_size`` (written
|
||||
# against flat 2.6/4.0/4.5 configs) and ``LogitsProcessor.__init__`` reads
|
||||
# ``config.vocab_size`` — proxy both to ``text_config`` so we don't have to
|
||||
# fork the base class / logits processor.
|
||||
@property
|
||||
def hidden_size(self) -> int:
|
||||
return self.text_config.hidden_size
|
||||
|
||||
@property
|
||||
def vocab_size(self) -> int:
|
||||
return self.text_config.vocab_size
|
||||
|
||||
|
||||
__all__ = ["MiniCPMV4_6Config", "MiniCPMV4_6VisionConfig"]
|
||||
@@ -0,0 +1,60 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from transformers.configuration_utils import PretrainedConfig
|
||||
from transformers.models.auto import CONFIG_MAPPING
|
||||
|
||||
|
||||
def _coerce_sub_config(
|
||||
sub_config: Optional[dict], default_model_type: str
|
||||
) -> Optional[PretrainedConfig]:
|
||||
"""Convert a config dict to a ``PretrainedConfig``.
|
||||
|
||||
Unknown ``model_type`` (e.g. M3's ``minimax_m2``, absent from
|
||||
``CONFIG_MAPPING``) falls back to ``PretrainedConfig`` so dict keys
|
||||
still become real attributes.
|
||||
"""
|
||||
if not isinstance(sub_config, dict):
|
||||
return sub_config
|
||||
model_type = sub_config.get("model_type", default_model_type)
|
||||
cls = CONFIG_MAPPING.get(model_type, PretrainedConfig)
|
||||
return cls(**sub_config)
|
||||
|
||||
|
||||
class MiniMaxVLBaseConfig(PretrainedConfig):
|
||||
def __init__(
|
||||
self,
|
||||
vision_config: Optional[dict] = None,
|
||||
text_config: Optional[dict] = None,
|
||||
image_token_index: int = 200025,
|
||||
video_token_index: int = 200026,
|
||||
image_seq_length: int = 576,
|
||||
process_image_mode: str = "dynamic_res",
|
||||
projector_hidden_act: str = "gelu",
|
||||
multimodal_projector_bias: bool = True,
|
||||
vision_feature_layer: int = -1,
|
||||
vision_feature_select_strategy: str = "full",
|
||||
img_token_compression_config: Optional[dict] = None,
|
||||
image_grid_pinpoints: Optional[str] = None,
|
||||
**kwargs,
|
||||
):
|
||||
self.vision_config = _coerce_sub_config(vision_config, "clip_vision_model")
|
||||
self.text_config = _coerce_sub_config(text_config, "mixtral")
|
||||
|
||||
self.image_token_index = image_token_index
|
||||
self.video_token_index = video_token_index
|
||||
self.image_seq_length = image_seq_length
|
||||
self.process_image_mode = process_image_mode
|
||||
self.projector_hidden_act = projector_hidden_act
|
||||
self.multimodal_projector_bias = multimodal_projector_bias
|
||||
self.vision_feature_layer = vision_feature_layer
|
||||
self.vision_feature_select_strategy = vision_feature_select_strategy
|
||||
self.img_token_compression_config = img_token_compression_config or {}
|
||||
self.image_grid_pinpoints = image_grid_pinpoints
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
class MiniMaxM3VLConfig(MiniMaxVLBaseConfig):
|
||||
model_type = "minimax_m3_vl"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,62 @@
|
||||
"""Named registry for model-config parsers.
|
||||
|
||||
Mirrors the ``LoadFormat.PRIVATE`` escape hatch in
|
||||
:mod:`sglang.srt.configs.load_config` but registry-shaped, so multiple
|
||||
plugins can coexist without colliding on a single private import path.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from abc import ABC, abstractmethod
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from transformers import PretrainedConfig
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ModelConfigParserBase(ABC):
|
||||
@abstractmethod
|
||||
def parse(
|
||||
self,
|
||||
model: str | Path,
|
||||
trust_remote_code: bool,
|
||||
revision: Optional[str] = None,
|
||||
**kwargs,
|
||||
) -> PretrainedConfig:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
_MODEL_CONFIG_PARSER_REGISTRY: dict[str, type[ModelConfigParserBase]] = {}
|
||||
|
||||
|
||||
def register_model_config_parser(name: str):
|
||||
"""Returned instances are freshly constructed on each call -- parsers
|
||||
should be stateless or carry only per-instance state."""
|
||||
|
||||
def _wrapper(cls):
|
||||
if not issubclass(cls, ModelConfigParserBase):
|
||||
raise ValueError("Model-config parser must subclass ModelConfigParserBase.")
|
||||
if name in _MODEL_CONFIG_PARSER_REGISTRY:
|
||||
logger.warning(
|
||||
"Model-config parser %r already registered; overwriting with %s",
|
||||
name,
|
||||
cls,
|
||||
)
|
||||
_MODEL_CONFIG_PARSER_REGISTRY[name] = cls
|
||||
logger.debug("Registered model-config parser %r -> %s", name, cls.__name__)
|
||||
return cls
|
||||
|
||||
return _wrapper
|
||||
|
||||
|
||||
def get_model_config_parser(name: str) -> ModelConfigParserBase:
|
||||
"""``"auto"`` is not handled here -- the caller must resolve it first."""
|
||||
if name not in _MODEL_CONFIG_PARSER_REGISTRY:
|
||||
raise ValueError(
|
||||
f"Unknown model-config parser {name!r}. "
|
||||
f"Registered: {sorted(_MODEL_CONFIG_PARSER_REGISTRY)}"
|
||||
)
|
||||
return _MODEL_CONFIG_PARSER_REGISTRY[name]()
|
||||
@@ -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
|
||||
@@ -0,0 +1,168 @@
|
||||
# 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,
|
||||
sound_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>",
|
||||
audio_context_token: str = "<so_embedding>",
|
||||
audio_start_token: str = "<so_start>",
|
||||
audio_end_token: str = "<so_end>",
|
||||
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,
|
||||
):
|
||||
# Round-trip: `to_dict()` emits `raw_vision_config` (V2's storage
|
||||
# name) but `from_dict()` rebuilds via this `vision_config` kwarg.
|
||||
# Without this alias, the V3->V2 alias rebuild in `get_config` loses
|
||||
# the vision config across the round-trip.
|
||||
if vision_config is None:
|
||||
vision_config = kwargs.pop("raw_vision_config", None)
|
||||
|
||||
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 = {}
|
||||
|
||||
# Audio (Parakeet) config: stored as a PretrainedConfig sub-object
|
||||
if sound_config is not None and isinstance(sound_config, dict):
|
||||
self.sound_config = PretrainedConfig.from_dict(sound_config)
|
||||
else:
|
||||
self.sound_config = sound_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
|
||||
self.audio_context_token = audio_context_token
|
||||
self.audio_start_token = audio_start_token
|
||||
self.audio_end_token = audio_end_token
|
||||
|
||||
# Dynamic resolution: from vision_config top-level
|
||||
self.min_num_patches = self.raw_vision_config.get("min_num_patches", 0)
|
||||
self.max_num_patches = self.raw_vision_config.get("max_num_patches", 0)
|
||||
self.dynamic_resolution = self.min_num_patches > 0
|
||||
|
||||
# Video temporal compression: from vision_config top-level
|
||||
self.video_temporal_patch_size = self.raw_vision_config.get(
|
||||
"video_temporal_patch_size", 1
|
||||
)
|
||||
self.separate_video_embedder = self.raw_vision_config.get(
|
||||
"separate_video_embedder", True
|
||||
)
|
||||
self.video_target_num_patches = self.raw_vision_config.get(
|
||||
"video_target_num_patches", 0
|
||||
)
|
||||
self.video_maintain_aspect_ratio = self.raw_vision_config.get(
|
||||
"video_maintain_aspect_ratio", True
|
||||
)
|
||||
|
||||
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,
|
||||
min_num_patches=self.min_num_patches,
|
||||
max_num_patches=self.max_num_patches,
|
||||
video_temporal_patch_size=self.video_temporal_patch_size,
|
||||
separate_video_embedder=self.separate_video_embedder,
|
||||
video_target_num_patches=self.video_target_num_patches,
|
||||
video_maintain_aspect_ratio=self.video_maintain_aspect_ratio,
|
||||
)
|
||||
return radio_config
|
||||
|
||||
|
||||
class NemotronH_Nano_Omni_Reasoning_V3_Config(NemotronH_Nano_VL_V2_Config):
|
||||
model_type = "NemotronH_Nano_Omni_Reasoning_V3"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
# Explicit __init__ prevents PretrainedConfig.__init_subclass__ from
|
||||
# replacing the parent's custom __init__ with a dataclass-generated one.
|
||||
super().__init__(*args, **kwargs)
|
||||
@@ -0,0 +1,559 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
# 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"""
|
||||
|
||||
import copy
|
||||
from typing import Any
|
||||
|
||||
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.runtime_context import get_parallel
|
||||
|
||||
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:
|
||||
|
||||
shape = Mamba2StateShape.create(
|
||||
tp_world_size=get_parallel().attn_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]
|
||||
|
||||
def get_nemotron_h_config_for_layer(self, layer_idx: int) -> "NemotronHConfig":
|
||||
return self
|
||||
|
||||
def get_mtp_config(self) -> "NemotronHConfig":
|
||||
return self
|
||||
|
||||
@property
|
||||
def max_n_routed_experts(self) -> int:
|
||||
return self.n_routed_experts
|
||||
|
||||
|
||||
class NemotronHPuzzleConfig(NemotronHConfig):
|
||||
model_type = "nemotron_h_puzzle"
|
||||
has_no_defaults_at_init = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
block_configs: list[dict[str, Any]],
|
||||
mtp_block_configs: list[dict[str, Any]] | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
self.block_configs = block_configs
|
||||
self.mtp_block_configs = mtp_block_configs
|
||||
|
||||
def get_nemotron_h_config_for_layer(self, layer_idx: int) -> NemotronHConfig:
|
||||
layer_config = copy.copy(self)
|
||||
for key, value in self.block_configs[layer_idx].items():
|
||||
setattr(layer_config, key, value)
|
||||
return layer_config
|
||||
|
||||
def get_mtp_config(self) -> NemotronHConfig:
|
||||
assert self.mtp_block_configs
|
||||
mtp_config = copy.copy(self)
|
||||
mtp_config.block_configs = self.mtp_block_configs
|
||||
return mtp_config
|
||||
|
||||
@property
|
||||
def max_n_routed_experts(self) -> int:
|
||||
block_n_routed_experts = [
|
||||
block["n_routed_experts"]
|
||||
for block in self.block_configs
|
||||
if block["block_type"] == "moe"
|
||||
]
|
||||
max_experts = max(block_n_routed_experts)
|
||||
assert max_experts > 0
|
||||
return max_experts
|
||||
@@ -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)
|
||||
]
|
||||
@@ -0,0 +1,76 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
# Copyright 2026 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/parakeet.py
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from transformers import ParakeetEncoderConfig, PretrainedConfig
|
||||
|
||||
|
||||
class ParakeetConfig(ParakeetEncoderConfig):
|
||||
def __init__(
|
||||
self,
|
||||
llm_hidden_size: int,
|
||||
projection_hidden_size: int,
|
||||
projection_bias: bool,
|
||||
sampling_rate: int,
|
||||
projection_eps: float = 1e-5,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
self.llm_hidden_size = llm_hidden_size
|
||||
self.projection_hidden_size = projection_hidden_size
|
||||
self.projection_bias = projection_bias
|
||||
self.sampling_rate = sampling_rate
|
||||
self.projection_eps = projection_eps
|
||||
|
||||
@staticmethod
|
||||
def from_hf_config(
|
||||
config: PretrainedConfig, *, llm_hidden_size: int, max_model_len: int
|
||||
) -> "ParakeetConfig":
|
||||
assert isinstance(config, PretrainedConfig)
|
||||
return ParakeetConfig(
|
||||
**config.to_dict(),
|
||||
scale_input=False,
|
||||
attention_bias=False,
|
||||
llm_hidden_size=llm_hidden_size,
|
||||
max_position_embeddings=max_model_len + 1,
|
||||
)
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class ExtractorConfig:
|
||||
feature_size: int
|
||||
sampling_rate: int
|
||||
subsampling_factor: int
|
||||
subsampling_conv_kernel_size: int
|
||||
subsampling_conv_stride: int
|
||||
hop_length: int = 160
|
||||
clip_duration_s: int = 30
|
||||
clip_min_duration_s: float = 0.1
|
||||
|
||||
@staticmethod
|
||||
def from_hf_config(config: PretrainedConfig) -> "ExtractorConfig":
|
||||
assert isinstance(config, PretrainedConfig)
|
||||
hop_length = int(getattr(config, "hop_length", ExtractorConfig.hop_length))
|
||||
return ExtractorConfig(
|
||||
feature_size=config.num_mel_bins,
|
||||
sampling_rate=config.sampling_rate,
|
||||
hop_length=hop_length,
|
||||
subsampling_factor=config.subsampling_factor,
|
||||
subsampling_conv_kernel_size=config.subsampling_conv_kernel_size,
|
||||
subsampling_conv_stride=config.subsampling_conv_stride,
|
||||
)
|
||||
@@ -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
|
||||
@@ -0,0 +1,138 @@
|
||||
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"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
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"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
class Qwen3_5MoeTextConfig(Qwen3_5TextConfig):
|
||||
model_type = "qwen3_5_moe_text"
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
# All Moe variant classes need explicit __init__ because the kw_only=True
|
||||
# dataclass decorator in transformers v5.5.3+ auto-generates __init__ for
|
||||
# subclasses, bypassing parent __init__ methods that set up attributes
|
||||
# (e.g. norm_topk_prob, rope_scaling) and convert sub-config dicts to objects.
|
||||
class Qwen3_5MoeConfig(Qwen3_5Config):
|
||||
model_type = "qwen3_5_moe"
|
||||
sub_configs = {
|
||||
"vision_config": Qwen3_5MoeVisionConfig,
|
||||
"text_config": Qwen3_5MoeTextConfig,
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
@@ -0,0 +1,168 @@
|
||||
import torch
|
||||
from transformers import (
|
||||
AutoConfig,
|
||||
AutoFeatureExtractor,
|
||||
AutoTokenizer,
|
||||
PretrainedConfig,
|
||||
ProcessorMixin,
|
||||
)
|
||||
|
||||
from sglang.srt.configs.qwen3_omni import Qwen3OmniMoeAudioEncoderConfig
|
||||
from sglang.srt.multimodal.customized_mm_processor_utils import (
|
||||
register_customized_processor,
|
||||
)
|
||||
from sglang.utils import logger
|
||||
|
||||
|
||||
class Qwen3ASRProcessor(ProcessorMixin):
|
||||
"""Minimal composite processor: WhisperFeatureExtractor + Qwen2Tokenizer.
|
||||
|
||||
AutoProcessor.from_pretrained() for Qwen3-ASR returns just a tokenizer,
|
||||
but SGLang's multimodal pipeline needs a processor that handles audio.
|
||||
"""
|
||||
|
||||
attributes = ["feature_extractor", "tokenizer"]
|
||||
feature_extractor_class = "WhisperFeatureExtractor"
|
||||
tokenizer_class = "AutoTokenizer"
|
||||
|
||||
def __init__(self, feature_extractor=None, tokenizer=None, **kwargs):
|
||||
super().__init__(feature_extractor=feature_extractor, tokenizer=tokenizer)
|
||||
|
||||
@classmethod
|
||||
def from_pretrained(cls, pretrained_model_name_or_path, **kwargs):
|
||||
trust_remote_code = kwargs.pop("trust_remote_code", True)
|
||||
feature_extractor = AutoFeatureExtractor.from_pretrained(
|
||||
pretrained_model_name_or_path,
|
||||
trust_remote_code=trust_remote_code,
|
||||
)
|
||||
tokenizer = AutoTokenizer.from_pretrained(
|
||||
pretrained_model_name_or_path,
|
||||
trust_remote_code=trust_remote_code,
|
||||
)
|
||||
return cls(feature_extractor=feature_extractor, tokenizer=tokenizer)
|
||||
|
||||
def _get_feat_extract_output_lengths(self, input_lengths):
|
||||
if not isinstance(input_lengths, torch.Tensor):
|
||||
input_lengths = torch.tensor(input_lengths)
|
||||
input_lengths_leave = input_lengths % 100
|
||||
feat_lengths = (input_lengths_leave - 1) // 2 + 1
|
||||
return ((feat_lengths - 1) // 2 + 1 - 1) // 2 + 1 + (input_lengths // 100) * 13
|
||||
|
||||
def __call__(self, text=None, audio=None, audio_kwargs=None, **kwargs):
|
||||
inputs = {}
|
||||
if audio is not None:
|
||||
audio_kwargs = audio_kwargs or {}
|
||||
audio_inputs = self.feature_extractor(
|
||||
audio,
|
||||
sampling_rate=self.feature_extractor.sampling_rate,
|
||||
return_attention_mask=True,
|
||||
return_tensors=kwargs.get("return_tensors"),
|
||||
**audio_kwargs,
|
||||
)
|
||||
inputs["input_features"] = audio_inputs["input_features"]
|
||||
if "attention_mask" in audio_inputs:
|
||||
inputs["feature_attention_mask"] = audio_inputs["attention_mask"]
|
||||
|
||||
if text is not None:
|
||||
text_inputs = self.tokenizer(
|
||||
text,
|
||||
return_tensors=kwargs.get("return_tensors"),
|
||||
padding=kwargs.get("padding", False),
|
||||
)
|
||||
input_ids = text_inputs["input_ids"]
|
||||
|
||||
# Expand the single <|audio_pad|> placeholder in the prompt to N
|
||||
# copies, where N is the audio encoder's output length for this clip.
|
||||
# Without this, the model only sees 1 audio token for hundreds of
|
||||
# feature frames and can't align audio embeddings with token positions.
|
||||
if audio is not None and "feature_attention_mask" in inputs:
|
||||
audio_pad_id = self.tokenizer.convert_tokens_to_ids("<|audio_pad|>")
|
||||
feat_lengths = inputs["feature_attention_mask"].sum(dim=-1)
|
||||
audio_token_counts = self._get_feat_extract_output_lengths(feat_lengths)
|
||||
expanded = []
|
||||
for seq_idx in range(input_ids.shape[0]):
|
||||
ids = input_ids[seq_idx].tolist()
|
||||
audio_idx = 0
|
||||
new_ids = []
|
||||
for tid in ids:
|
||||
if tid == audio_pad_id and audio_idx < len(audio_token_counts):
|
||||
n = int(audio_token_counts[audio_idx].item())
|
||||
new_ids.extend([audio_pad_id] * n)
|
||||
audio_idx += 1
|
||||
else:
|
||||
new_ids.append(tid)
|
||||
expanded.append(new_ids)
|
||||
max_len = max(len(s) for s in expanded)
|
||||
pad_id = self.tokenizer.pad_token_id or 0
|
||||
padded = [s + [pad_id] * (max_len - len(s)) for s in expanded]
|
||||
input_ids = torch.tensor(padded, dtype=torch.long)
|
||||
|
||||
inputs["input_ids"] = input_ids
|
||||
return inputs
|
||||
|
||||
|
||||
class Qwen3ASRThinkerConfig(PretrainedConfig):
|
||||
model_type = "qwen3_asr_thinker"
|
||||
sub_configs = {
|
||||
"audio_config": Qwen3OmniMoeAudioEncoderConfig,
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
audio_config=None,
|
||||
text_config=None,
|
||||
audio_token_id=151676,
|
||||
audio_start_token_id=151669,
|
||||
audio_end_token_id=151670,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
if isinstance(audio_config, dict):
|
||||
audio_config = Qwen3OmniMoeAudioEncoderConfig(**audio_config)
|
||||
elif audio_config is None:
|
||||
audio_config = Qwen3OmniMoeAudioEncoderConfig()
|
||||
self.audio_config = audio_config
|
||||
|
||||
from transformers.models.qwen3.configuration_qwen3 import (
|
||||
Qwen3Config as HFQwen3Config,
|
||||
)
|
||||
|
||||
if isinstance(text_config, dict):
|
||||
text_config = HFQwen3Config(**text_config)
|
||||
elif text_config is None:
|
||||
text_config = HFQwen3Config()
|
||||
|
||||
self.text_config = text_config
|
||||
|
||||
self.audio_token_id = audio_token_id
|
||||
self.audio_start_token_id = audio_start_token_id
|
||||
self.audio_end_token_id = audio_end_token_id
|
||||
|
||||
|
||||
@register_customized_processor(Qwen3ASRProcessor)
|
||||
class Qwen3ASRConfig(PretrainedConfig):
|
||||
model_type = "qwen3_asr"
|
||||
sub_configs = {
|
||||
"thinker_config": Qwen3ASRThinkerConfig,
|
||||
}
|
||||
|
||||
def __init__(self, thinker_config=None, **kwargs):
|
||||
if thinker_config is None:
|
||||
thinker_config = {}
|
||||
logger.info(
|
||||
"thinker_config is None. "
|
||||
"Initializing Qwen3-ASR thinker with default values"
|
||||
)
|
||||
if isinstance(thinker_config, dict):
|
||||
self.thinker_config = Qwen3ASRThinkerConfig(**thinker_config)
|
||||
else:
|
||||
self.thinker_config = thinker_config
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def get_text_config(self, decoder=False) -> PretrainedConfig:
|
||||
return self.thinker_config.text_config
|
||||
|
||||
|
||||
AutoConfig.register("qwen3_asr", Qwen3ASRConfig)
|
||||
AutoConfig.register("qwen3_asr_thinker", Qwen3ASRThinkerConfig)
|
||||
@@ -0,0 +1,306 @@
|
||||
# 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.runtime_context import get_parallel
|
||||
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.
|
||||
output_gate_type (`str`, *optional*, defaults to `None`):
|
||||
The gate activation function used by the linear attention output norm.
|
||||
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",
|
||||
output_gate_type=None,
|
||||
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.output_gate_type = output_gate_type
|
||||
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:
|
||||
|
||||
if _is_cpu:
|
||||
world_size = get_parallel().attn_tp_size
|
||||
adjust_tp_num_heads_if_necessary(self, world_size, False)
|
||||
|
||||
shape = Mamba2StateShape.create(
|
||||
tp_world_size=get_parallel().attn_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)
|
||||
)
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -0,0 +1,120 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
# 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,
|
||||
min_num_patches: int = 0,
|
||||
max_num_patches: int = 0,
|
||||
video_temporal_patch_size: int = 1,
|
||||
separate_video_embedder: bool = True,
|
||||
video_target_num_patches: int = 0,
|
||||
video_maintain_aspect_ratio: bool = True,
|
||||
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.min_num_patches = min_num_patches
|
||||
self.max_num_patches = max_num_patches
|
||||
self.video_temporal_patch_size = video_temporal_patch_size
|
||||
self.separate_video_embedder = separate_video_embedder
|
||||
self.video_target_num_patches = video_target_num_patches
|
||||
self.video_maintain_aspect_ratio = video_maintain_aspect_ratio
|
||||
self.drop_path_rate = drop_path_rate
|
||||
self.dropout = dropout
|
||||
super().__init__(**kwargs)
|
||||
@@ -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)
|
||||
@@ -0,0 +1,108 @@
|
||||
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,
|
||||
yarn_only_types: Optional[list[str]] = 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
|
||||
self.yarn_only_types = yarn_only_types or []
|
||||
# The upstream Step-3.5-Flash config has layer_types with 48 entries
|
||||
# but num_hidden_layers=45. The extra 3 are for MTP/nextn predict
|
||||
# layers (indices 45-47) used by Step3p5DecoderLayer during EAGLE
|
||||
# speculative decoding. Temporarily align num_hidden_layers to pass
|
||||
# the transformers v5.5.3+ validator, then restore the real value.
|
||||
real_num_hidden_layers = self.num_hidden_layers
|
||||
if layer_types is not None and len(layer_types) != self.num_hidden_layers:
|
||||
self.num_hidden_layers = len(layer_types)
|
||||
super().__init__(**kwargs)
|
||||
self.num_hidden_layers = real_num_hidden_layers
|
||||
@@ -0,0 +1,97 @@
|
||||
from typing import Optional, Union
|
||||
|
||||
from transformers.configuration_utils import PretrainedConfig
|
||||
|
||||
|
||||
class Step3p7VisionEncoderConfig(PretrainedConfig):
|
||||
model_type = "perception_encoder"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
width=1536,
|
||||
layers=47,
|
||||
heads=16,
|
||||
num_channels=3,
|
||||
image_size=728,
|
||||
patch_size=14,
|
||||
mlp_ratio=8960 / 1536,
|
||||
hidden_act="quick_gelu",
|
||||
layer_norm_eps=1e-5,
|
||||
use_cls_token=False,
|
||||
use_ln_pre=True,
|
||||
use_ln_post=False,
|
||||
use_abs_posemb=True,
|
||||
use_rope2d=True,
|
||||
ls_init_value=0.1,
|
||||
output_dim=None,
|
||||
pool_type="none",
|
||||
**kwargs,
|
||||
):
|
||||
self.width = width
|
||||
self.layers = layers
|
||||
self.heads = heads
|
||||
self.num_channels = num_channels
|
||||
self.patch_size = patch_size
|
||||
self.image_size = image_size
|
||||
self.mlp_ratio = mlp_ratio
|
||||
self.layer_norm_eps = layer_norm_eps
|
||||
self.hidden_act = hidden_act
|
||||
self.use_cls_token = use_cls_token
|
||||
self.use_ln_pre = use_ln_pre
|
||||
self.use_ln_post = use_ln_post
|
||||
self.use_abs_posemb = use_abs_posemb
|
||||
self.use_rope2d = use_rope2d
|
||||
self.ls_init_value = ls_init_value
|
||||
self.output_dim = output_dim
|
||||
self.pool_type = pool_type
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
||||
class Step3p7Config(PretrainedConfig):
|
||||
model_type = "step3p7"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vision_config: Optional[Union[dict, Step3p7VisionEncoderConfig]] = None,
|
||||
text_config: Optional[Union[dict, PretrainedConfig]] = None,
|
||||
understand_projector_stride: int = 2,
|
||||
projector_bias: bool = False,
|
||||
image_token_id: int = 128001,
|
||||
image_token_len: int = 169,
|
||||
patch_token_len: int = 81,
|
||||
im_start_token: str = "<im_start>",
|
||||
im_end_token: str = "<im_end>",
|
||||
im_patch_token: str = "<im_patch>",
|
||||
use_im_start_end: bool = True,
|
||||
vision_select_layer: int = -1,
|
||||
**kwargs,
|
||||
) -> None:
|
||||
if vision_config is None:
|
||||
vision_config = Step3p7VisionEncoderConfig()
|
||||
elif isinstance(vision_config, dict):
|
||||
vision_config = Step3p7VisionEncoderConfig(**vision_config)
|
||||
self.vision_config = vision_config
|
||||
|
||||
if text_config is None:
|
||||
from sglang.srt.configs.step3p5 import Step3p5Config
|
||||
|
||||
text_config = Step3p5Config()
|
||||
elif isinstance(text_config, dict):
|
||||
from sglang.srt.configs.step3p5 import Step3p5Config
|
||||
|
||||
text_config = Step3p5Config(**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
|
||||
self.image_token_len = image_token_len
|
||||
self.patch_token_len = patch_token_len
|
||||
self.im_start_token = im_start_token
|
||||
self.im_end_token = im_end_token
|
||||
self.im_patch_token = im_patch_token
|
||||
self.use_im_start_end = use_im_start_end
|
||||
self.vision_select_layer = vision_select_layer
|
||||
|
||||
super().__init__(**kwargs)
|
||||
@@ -0,0 +1,629 @@
|
||||
"""Standalone UNLIMITED-OCR configuration and HF processor."""
|
||||
|
||||
import math
|
||||
from typing import Any, Dict, List, Tuple
|
||||
|
||||
import torch
|
||||
from PIL import Image, ImageOps
|
||||
from transformers import (
|
||||
AutoConfig,
|
||||
AutoProcessor,
|
||||
PretrainedConfig,
|
||||
PreTrainedTokenizerFast,
|
||||
ProcessorMixin,
|
||||
)
|
||||
|
||||
from sglang.srt.configs.deepseek_ocr import (
|
||||
ImageTransform,
|
||||
MlpProjectorConfig,
|
||||
VisionEncoderConfig,
|
||||
VLChatProcessorOutput,
|
||||
find_closest_aspect_ratio,
|
||||
)
|
||||
from sglang.srt.multimodal.customized_mm_processor_utils import (
|
||||
register_customized_processor,
|
||||
)
|
||||
|
||||
|
||||
def dynamic_preprocess(
|
||||
image, min_num=2, max_num=32, image_size=640, use_thumbnail=False
|
||||
):
|
||||
"""Split an image into tiles based on the best-matching aspect ratio."""
|
||||
orig_width, orig_height = image.size
|
||||
aspect_ratio = orig_width / orig_height
|
||||
|
||||
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])
|
||||
|
||||
target_aspect_ratio = find_closest_aspect_ratio(
|
||||
aspect_ratio, target_ratios, orig_width, orig_height, image_size
|
||||
)
|
||||
|
||||
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]
|
||||
|
||||
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_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 UnlimitedOCRHFProcessor(ProcessorMixin):
|
||||
"""HuggingFace-style processor for UNLIMITED-OCR (OCR mode)."""
|
||||
|
||||
tokenizer_class = "PreTrainedTokenizerFast"
|
||||
attributes = ["tokenizer"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
tokenizer: PreTrainedTokenizerFast,
|
||||
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 = "unlimitedocr",
|
||||
mask_prompt: bool = True,
|
||||
ignore_id: int = -100,
|
||||
base_size: int = 1024,
|
||||
image_size: int = 640,
|
||||
crop_mode: bool = True,
|
||||
**kwargs,
|
||||
):
|
||||
"""Initialize tokenizer, image transform, and special tokens."""
|
||||
self.candidate_resolutions = candidate_resolutions
|
||||
self.base_size = base_size
|
||||
self.image_size = image_size
|
||||
self.crop_mode = crop_mode
|
||||
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
|
||||
)
|
||||
if type(tokenizer) is not PreTrainedTokenizerFast:
|
||||
tokenizer = PreTrainedTokenizerFast.from_pretrained(tokenizer.name_or_path)
|
||||
self.tokenizer = tokenizer
|
||||
self.tokenizer.padding_side = "left"
|
||||
|
||||
if tokenizer.pad_token is None:
|
||||
self.tokenizer.add_special_tokens({"pad_token": pad_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)
|
||||
|
||||
special_tokens = ["<|ref|>", "<|/ref|>", "<|det|>", "<|/det|>", "<|grounding|>"]
|
||||
special_tokens_dict = {"additional_special_tokens": special_tokens}
|
||||
self.tokenizer.add_special_tokens(special_tokens_dict)
|
||||
|
||||
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: str,
|
||||
pil_images,
|
||||
max_req_input_len=-1,
|
||||
base_size: int = None,
|
||||
image_size: int = None,
|
||||
crop_mode: bool = None,
|
||||
):
|
||||
"""Tokenize messages with embedded images and return processed tensors."""
|
||||
base_size = base_size or self.base_size
|
||||
image_size = image_size or self.image_size
|
||||
crop_mode = crop_mode if crop_mode is not None else self.crop_mode
|
||||
|
||||
tokenized_data = []
|
||||
masked_tokenized_data = []
|
||||
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=crop_mode,
|
||||
base_size=base_size,
|
||||
image_size=image_size,
|
||||
)
|
||||
|
||||
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 the beginning-of-sequence token ID."""
|
||||
return self.tokenizer.bos_token_id
|
||||
|
||||
@property
|
||||
def eos_id(self):
|
||||
"""Return the end-of-sequence token ID."""
|
||||
return self.tokenizer.eos_token_id
|
||||
|
||||
@property
|
||||
def pad_id(self):
|
||||
"""Return the padding token ID."""
|
||||
return self.tokenizer.pad_token_id
|
||||
|
||||
def encode(self, text: str, bos: bool = True, eos: bool = False):
|
||||
"""Encode text into token IDs with optional BOS/EOS."""
|
||||
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:
|
||||
"""Decode token IDs back into a string."""
|
||||
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,
|
||||
base_size: int = None,
|
||||
image_size: int = None,
|
||||
crop_mode: bool = None,
|
||||
**kwargs,
|
||||
):
|
||||
"""Process a single prompt with images into model-ready tensors."""
|
||||
base_size = base_size or self.base_size
|
||||
image_size = image_size or self.image_size
|
||||
crop_mode = crop_mode if crop_mode is not None else self.crop_mode
|
||||
|
||||
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,
|
||||
base_size=base_size,
|
||||
image_size=image_size,
|
||||
crop_mode=crop_mode,
|
||||
)
|
||||
|
||||
target_ids = torch.LongTensor(masked_tokenized_str)
|
||||
|
||||
has_images = len(images_list) > 0
|
||||
has_local_crops = []
|
||||
if len(images_spatial_crop) > 0:
|
||||
has_local_crops = [
|
||||
(crop[0] > 1 or crop[1] > 1).item() for crop in images_spatial_crop
|
||||
]
|
||||
|
||||
if len(images_list) == 0:
|
||||
images = torch.zeros((1, 3, image_size, image_size))
|
||||
else:
|
||||
images = torch.stack(images_list, dim=0)
|
||||
|
||||
images_spatial_crop = torch.stack([images_spatial_crop], dim=0)
|
||||
|
||||
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,
|
||||
base_size: int = None,
|
||||
image_size: int = None,
|
||||
crop_mode: bool = None,
|
||||
**kwargs,
|
||||
):
|
||||
"""Call the processor to tokenize text and images for inference."""
|
||||
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,
|
||||
base_size=base_size if base_size is not None else self.base_size,
|
||||
image_size=image_size if image_size is not None else self.image_size,
|
||||
crop_mode=crop_mode if crop_mode is not None else self.crop_mode,
|
||||
)
|
||||
return prepare
|
||||
|
||||
def find_all_indices(self, messages, target_value):
|
||||
"""Return all indices where target_value appears in messages."""
|
||||
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,
|
||||
base_size: int = None,
|
||||
image_size: int = None,
|
||||
):
|
||||
"""Tokenize text with <image> tags (OCR mode)."""
|
||||
base_size = base_size or self.base_size
|
||||
image_size = image_size or self.image_size
|
||||
|
||||
assert conversation.count(self.image_token) == len(images)
|
||||
text_splits: list[str] = 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):
|
||||
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]
|
||||
|
||||
if image_size <= 640 and not cropping:
|
||||
image = image.resize((image_size, image_size))
|
||||
if cropping:
|
||||
pad_size = base_size
|
||||
else:
|
||||
pad_size = image_size
|
||||
|
||||
global_view = ImageOps.pad(
|
||||
image,
|
||||
(pad_size, pad_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]))
|
||||
|
||||
num_queries = math.ceil(
|
||||
(image_size // self.patch_size) / self.downsample_ratio
|
||||
)
|
||||
num_queries_base = math.ceil(
|
||||
(base_size // self.patch_size) / self.downsample_ratio
|
||||
)
|
||||
if cropping:
|
||||
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)
|
||||
else:
|
||||
tokenized_image = (
|
||||
[self.image_token_id] * num_queries + [self.image_token_id]
|
||||
) * num_queries
|
||||
tokenized_image += [self.image_token_id]
|
||||
|
||||
tokenized_str += tokenized_image
|
||||
images_seq_mask += [True] * len(tokenized_image)
|
||||
num_image_tokens.append(len(tokenized_image))
|
||||
|
||||
tokenized_sep = self.encode(text_splits[-1], bos=False, eos=False)
|
||||
tokenized_str += tokenized_sep
|
||||
images_seq_mask += [False] * len(tokenized_sep)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
input_ids = torch.LongTensor(tokenized_str)
|
||||
target_ids = torch.LongTensor(masked_tokenized_str)
|
||||
images_seq_mask = torch.tensor(images_seq_mask, dtype=torch.bool)
|
||||
|
||||
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:
|
||||
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, base_size, base_size))
|
||||
images_spatial_crop = torch.zeros((1, 1), dtype=torch.long)
|
||||
images_crop = torch.zeros((1, 3, image_size, 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(
|
||||
(len(images_list), 3, image_size, image_size)
|
||||
).unsqueeze(1)
|
||||
|
||||
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 UnlimitedLanguageConfig(PretrainedConfig):
|
||||
"""Configuration for the UNLIMITED language model backbone."""
|
||||
|
||||
model_type = "unlimited_language"
|
||||
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,
|
||||
):
|
||||
"""Initialize language model configuration parameters."""
|
||||
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
|
||||
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=UnlimitedOCRHFProcessor)
|
||||
class UnlimitedVLConfig(PretrainedConfig):
|
||||
"""Top-level vision-language config for UNLIMITED-OCR models."""
|
||||
|
||||
model_type = "unlimited-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] = UnlimitedOCRHFProcessor
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
tile_tag: str = "tile_tag",
|
||||
global_view_pos: str = "head",
|
||||
candidate_resolutions: tuple[tuple[int, int]] = ((384, 384),),
|
||||
**kwargs,
|
||||
):
|
||||
"""Initialize UNLIMITED VL config with vision, projector, and language sub-configs."""
|
||||
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 = UnlimitedLanguageConfig(**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(UnlimitedVLConfig, UnlimitedOCRHFProcessor)
|
||||
|
||||
try:
|
||||
AutoConfig.register("unlimited-ocr", UnlimitedVLConfig)
|
||||
except ValueError:
|
||||
pass
|
||||
@@ -0,0 +1,350 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sglang.srt.utils import (
|
||||
log_debug_on_rank0,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
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")
|
||||
|
||||
if quant_config is not None and hasattr(quant_config, "group_size"):
|
||||
return [getattr(quant_config, "group_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) in [
|
||||
1,
|
||||
2,
|
||||
], "Only len(weight_block_size) in [1, 2] is supported"
|
||||
if len(weight_block_size) == 2:
|
||||
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=None):
|
||||
if head_dim is None:
|
||||
pad_size = (
|
||||
tp_size * 2
|
||||
if tp_size % 2 == 1 and weight_block_size is not None
|
||||
else tp_size
|
||||
)
|
||||
return pad_size
|
||||
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 resolve_head_dim(cfg, num_heads, is_text_config):
|
||||
# default getting head_dim by hidden_size and num_heads
|
||||
hidden_size = getattr(cfg, "hidden_size", getattr(cfg, "d_model", None))
|
||||
head_dim = hidden_size // num_heads if hidden_size else None
|
||||
# update head_dim if specified in model config
|
||||
if is_text_config:
|
||||
if hasattr(cfg.hf_config, "qk_head_dim"):
|
||||
head_dim = cfg.hf_config.qk_head_dim
|
||||
elif hasattr(cfg.hf_text_config, "head_dim"):
|
||||
head_dim = cfg.hf_text_config.head_dim
|
||||
elif hasattr(cfg.hf_config, "head_dim"):
|
||||
head_dim = cfg.hf_config.head_dim
|
||||
else:
|
||||
if hasattr(cfg, "head_dim"):
|
||||
head_dim = cfg.head_dim
|
||||
|
||||
return head_dim
|
||||
|
||||
|
||||
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:
|
||||
update_config(
|
||||
model_config, "linear_num_key_heads_cpu", linear_num_key_heads_cpu
|
||||
)
|
||||
update_config(
|
||||
model_config,
|
||||
"linear_num_value_heads_cpu",
|
||||
linear_num_value_heads_cpu,
|
||||
)
|
||||
else:
|
||||
update_config(
|
||||
model_config, "linear_num_key_heads", linear_num_key_heads_cpu
|
||||
)
|
||||
update_config(
|
||||
model_config, "linear_num_value_heads", linear_num_value_heads_cpu
|
||||
)
|
||||
|
||||
else:
|
||||
if is_post_update:
|
||||
update_config(
|
||||
model_config,
|
||||
"linear_num_key_heads_cpu",
|
||||
model_config.linear_num_key_heads,
|
||||
)
|
||||
update_config(
|
||||
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, "text_config")
|
||||
and hasattr(model_config.hf_config.text_config, attr_name)
|
||||
):
|
||||
attr_value = getattr(model_config.hf_config.text_config, attr_name)
|
||||
elif 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
|
||||
|
||||
origin_value = attr_value
|
||||
origin_name = "original_" + attr_name
|
||||
attr_value = pad_vocab_size(attr_value, intermediate_padding_size)
|
||||
if hasattr(model_config, "hf_config"):
|
||||
update_config(model_config.hf_config, attr_name, attr_value)
|
||||
update_config(model_config.hf_config, origin_name, origin_value)
|
||||
if hasattr(model_config, "hf_text_config"):
|
||||
update_config(model_config.hf_text_config, attr_name, attr_value)
|
||||
update_config(model_config.hf_text_config, origin_name, origin_value)
|
||||
if hasattr(model_config.hf_config, "text_config"):
|
||||
update_config(model_config.hf_config.text_config, attr_name, attr_value)
|
||||
update_config(
|
||||
model_config.hf_config.text_config, origin_name, origin_value
|
||||
)
|
||||
else:
|
||||
update_config(model_config, attr_name, attr_value)
|
||||
update_config(model_config, origin_name, origin_value)
|
||||
|
||||
return model_config
|
||||
|
||||
|
||||
def update_config(model_config, attr_name, new_value):
|
||||
config_name = model_config.__class__.__name__
|
||||
if hasattr(model_config, attr_name):
|
||||
old_value = getattr(model_config, attr_name)
|
||||
if old_value != new_value:
|
||||
log_debug_on_rank0(
|
||||
logger,
|
||||
f"Updating {config_name}.{attr_name} from {old_value} to {new_value}",
|
||||
)
|
||||
else:
|
||||
log_debug_on_rank0(logger, f"Setting {config_name}.{attr_name} to {new_value}")
|
||||
setattr(model_config, attr_name, new_value)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
for config in [model_config.hf_config, model_config.hf_text_config]:
|
||||
update_config(
|
||||
config,
|
||||
"original_num_attention_heads",
|
||||
model_config.num_attention_heads,
|
||||
)
|
||||
update_config(
|
||||
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
|
||||
):
|
||||
|
||||
if hasattr(model_config.hf_config, "qk_nope_head_dim") and hasattr(
|
||||
model_config.hf_config, "qk_rope_head_dim"
|
||||
):
|
||||
update_config(
|
||||
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 = resolve_head_dim(
|
||||
model_config, model_config.num_attention_heads, True
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
num_attention_heads = num_key_value_heads * query_heads_per_kv
|
||||
for config in [
|
||||
model_config,
|
||||
model_config.hf_config,
|
||||
model_config.hf_text_config,
|
||||
]:
|
||||
update_config(config, "num_key_value_heads", num_key_value_heads)
|
||||
update_config(config, "num_attention_heads", num_attention_heads)
|
||||
|
||||
adjust_tp_num_heads_if_necessary(model_config.hf_config, tp_size, True)
|
||||
if hasattr(model_config.hf_config, "text_config"):
|
||||
adjust_tp_num_heads_if_necessary(
|
||||
model_config.hf_config.text_config, tp_size, True
|
||||
)
|
||||
|
||||
intermediate_padding_size = tp_size * get_moe_padding_size(weight_block_size)
|
||||
for moe_intermediate_attr in [
|
||||
"moe_intermediate_size",
|
||||
"intermediate_size",
|
||||
"intermediate_size_mlp",
|
||||
"shared_expert_intermediate_size",
|
||||
]:
|
||||
model_config = update_intermediate_size(
|
||||
model_config, moe_intermediate_attr, intermediate_padding_size
|
||||
)
|
||||
|
||||
multimodal_config = [
|
||||
[
|
||||
model_config.hf_config,
|
||||
"vision_config",
|
||||
"siglip_vision_model",
|
||||
"num_attention_heads",
|
||||
],
|
||||
[model_config.hf_config, "vision_config", "qwen2_5_vl", "num_heads"],
|
||||
[model_config.hf_config, "vision_config", "qwen3_vl_moe", "num_heads"],
|
||||
[model_config.hf_config, "vision_config", "qwen3_vl", "num_heads"],
|
||||
[model_config.hf_config, "vision_config", "qwen3_5_moe", "num_heads"],
|
||||
[model_config.hf_config, "vision_config", "qwen3_5", "num_heads"],
|
||||
[model_config.hf_config, "vision_config", "mllama", "attention_heads"],
|
||||
[
|
||||
model_config.hf_config,
|
||||
"vision_config",
|
||||
"llama4_vision_model",
|
||||
"num_attention_heads",
|
||||
],
|
||||
]
|
||||
if hasattr(model_config.hf_config, "thinker_config"):
|
||||
multimodal_config.append(
|
||||
[
|
||||
model_config.hf_config.thinker_config,
|
||||
"vision_config",
|
||||
"qwen3_omni_moe_vision_encoder",
|
||||
"num_heads",
|
||||
]
|
||||
)
|
||||
multimodal_config.append(
|
||||
[
|
||||
model_config.hf_config.thinker_config,
|
||||
"audio_config",
|
||||
"qwen3_omni_moe_audio_encoder",
|
||||
"encoder_attention_heads",
|
||||
]
|
||||
)
|
||||
|
||||
for m_config, config_name, model_type, num_head_str in multimodal_config:
|
||||
if hasattr(m_config, config_name) and (
|
||||
m_config.model_type == model_type
|
||||
or getattr(m_config, config_name).model_type == model_type
|
||||
):
|
||||
num_heads = getattr(getattr(m_config, config_name), num_head_str)
|
||||
|
||||
update_config(
|
||||
getattr(m_config, config_name), "original_" + num_head_str, num_heads
|
||||
)
|
||||
if num_heads % tp_size != 0:
|
||||
from sglang.srt.layers.vocab_parallel_embedding import pad_vocab_size
|
||||
|
||||
multimodal_head_dim = resolve_head_dim(
|
||||
getattr(m_config, config_name), num_heads, False
|
||||
)
|
||||
pad_size = get_num_heads_padding_size(
|
||||
tp_size, weight_block_size, multimodal_head_dim
|
||||
)
|
||||
new_num_heads = pad_vocab_size(num_heads, pad_size)
|
||||
update_config(
|
||||
getattr(m_config, config_name), num_head_str, new_num_heads
|
||||
)
|
||||
setattr(
|
||||
m_config,
|
||||
config_name,
|
||||
update_intermediate_size(
|
||||
getattr(m_config, config_name),
|
||||
"intermediate_size",
|
||||
intermediate_padding_size,
|
||||
),
|
||||
)
|
||||
|
||||
# Pad projector_input_dim for Llama4 vision if needed
|
||||
if model_type == "llama4_vision_model":
|
||||
proj_inp_dim = getattr(m_config, config_name).projector_input_dim
|
||||
if proj_inp_dim % tp_size != 0:
|
||||
from sglang.srt.layers.vocab_parallel_embedding import (
|
||||
pad_vocab_size,
|
||||
)
|
||||
|
||||
update_config(
|
||||
getattr(m_config, config_name),
|
||||
"projector_input_dim",
|
||||
pad_vocab_size(proj_inp_dim, tp_size),
|
||||
)
|
||||
|
||||
return model_config
|
||||
@@ -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)
|
||||
@@ -0,0 +1,325 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# 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.
|
||||
# ==============================================================================
|
||||
"""Configuration class for Zyphra ZAYA1 series models."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, List, Optional
|
||||
|
||||
from transformers.configuration_utils import PretrainedConfig
|
||||
|
||||
from sglang.srt.runtime_context import get_parallel
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sglang.srt.configs.mamba_utils import Mamba2CacheParams
|
||||
|
||||
|
||||
class ZayaConfig(PretrainedConfig):
|
||||
"""HuggingFace configuration for ZAYA1 hybrid (CCA attention + MoE) models.
|
||||
|
||||
Mirrors the field set used by Zyphra/ZAYA1-base/config.json. Most fields
|
||||
are surfaced as constructor arguments so the same class can be instantiated
|
||||
either from a published checkpoint via ``AutoConfig.from_pretrained`` or
|
||||
programmatically in unit tests.
|
||||
"""
|
||||
|
||||
model_type = "zaya"
|
||||
keys_to_ignore_at_inference = ["past_key_values"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
cca: bool = True,
|
||||
num_query_groups: int = 2,
|
||||
use_cache: bool = True,
|
||||
attention_bias: bool = False,
|
||||
lm_head_bias: bool = False,
|
||||
vocab_size: int = 262272,
|
||||
hidden_size: int = 2048,
|
||||
ffn_hidden_size: int = 4096,
|
||||
num_hidden_layers: int = 80,
|
||||
num_experts: int = 16,
|
||||
num_attention_heads: int = 8,
|
||||
head_dim: int = 128,
|
||||
activation_func: str = "swiglu",
|
||||
max_position_embeddings: int = 32768,
|
||||
norm_epsilon: float = 1e-5,
|
||||
pad_token_id: int = 0,
|
||||
bos_token_id: int = 2,
|
||||
eos_token_id: int = 1,
|
||||
tie_word_embeddings: bool = True,
|
||||
rope_theta: float = 1_000_000.0,
|
||||
attention_dropout: float = 0.0,
|
||||
moe_router_topk: int = 1,
|
||||
normalization: str = "RMSNorm",
|
||||
zaya_mlp_expansion=256,
|
||||
zaya_use_mod: bool = True,
|
||||
zaya_high_prec: bool = True,
|
||||
zaya_use_eda: bool = True,
|
||||
add_bias_linear: bool = False,
|
||||
gated_linear_unit: bool = True,
|
||||
scale_residual_merge: bool = True,
|
||||
fused_add_norm: bool = False,
|
||||
residual_in_fp32: bool = True,
|
||||
apply_rope_fusion: bool = True,
|
||||
bias_activation_fusion: bool = True,
|
||||
activation_func_fp8_input_store: bool = False,
|
||||
sliding_window=None,
|
||||
rope_scaling=None,
|
||||
rope_parameters=None,
|
||||
partial_rotary_factor: float = 0.5,
|
||||
num_key_value_heads: int = 2,
|
||||
clamp_temp: bool = False,
|
||||
cca_time0: int = 2,
|
||||
cca_time1: int = 2,
|
||||
swa_layers=None,
|
||||
swa_rotary_base=None,
|
||||
zaya_layers=None,
|
||||
cca_num_q_heads=None,
|
||||
num_query_groups_list=None,
|
||||
ffn_hidden_size_list=None,
|
||||
kv_channels=None,
|
||||
_attn_implementation: str = "eager",
|
||||
**kwargs,
|
||||
):
|
||||
self.cca = cca
|
||||
self.use_cache = use_cache
|
||||
self.attention_bias = attention_bias
|
||||
self.lm_head_bias = lm_head_bias
|
||||
self.vocab_size = vocab_size
|
||||
self.hidden_size = hidden_size
|
||||
self.num_experts = num_experts
|
||||
|
||||
# ZAYA1-base ships a ``zaya_layers`` list whose entries are either the
|
||||
# literal string ``"a"`` (attention layer) or an integer (number of
|
||||
# experts in a MoE layer). When present it is the source of truth for
|
||||
# both the total layer count and the per-layer placement. The HF
|
||||
# config also carries a scalar ``num_hidden_layers`` that can disagree
|
||||
# with ``len(zaya_layers)`` for historical reasons, so the list takes
|
||||
# precedence whenever it is non-empty.
|
||||
self.zaya_layers = list(zaya_layers) if zaya_layers else None
|
||||
if self.zaya_layers:
|
||||
self.num_hidden_layers = len(self.zaya_layers)
|
||||
else:
|
||||
self.num_hidden_layers = num_hidden_layers
|
||||
|
||||
# When the per-layer lists are present, derive each active scalar
|
||||
# field from the first non-zero entry of the corresponding list.
|
||||
# This matches ZAYA1-base in practice: every attention layer shares
|
||||
# the same ``cca_num_q_heads`` (e.g. 8) and ``num_query_groups``
|
||||
# (e.g. 2), and every MoE layer shares the same ``ffn_hidden_size``
|
||||
# (e.g. 4096) and ``zaya_mlp_expansion`` (e.g. 256). When no list is
|
||||
# provided, the constructor argument is used unchanged.
|
||||
self.cca_num_q_heads_list = list(cca_num_q_heads) if cca_num_q_heads else None
|
||||
self.num_query_groups_list = (
|
||||
list(num_query_groups_list) if num_query_groups_list else None
|
||||
)
|
||||
self.ffn_hidden_size_list = (
|
||||
list(ffn_hidden_size_list) if ffn_hidden_size_list else None
|
||||
)
|
||||
if isinstance(zaya_mlp_expansion, (list, tuple)):
|
||||
self.zaya_mlp_expansion_list = list(zaya_mlp_expansion)
|
||||
zaya_mlp_expansion_scalar = next(
|
||||
(v for v in self.zaya_mlp_expansion_list if v), 256
|
||||
)
|
||||
else:
|
||||
self.zaya_mlp_expansion_list = None
|
||||
zaya_mlp_expansion_scalar = int(zaya_mlp_expansion)
|
||||
|
||||
if self.cca_num_q_heads_list:
|
||||
self.num_attention_heads = next(
|
||||
(v for v in self.cca_num_q_heads_list if v), num_attention_heads
|
||||
)
|
||||
else:
|
||||
self.num_attention_heads = num_attention_heads
|
||||
|
||||
if self.num_query_groups_list:
|
||||
self.num_query_groups = next(
|
||||
(v for v in self.num_query_groups_list if v), num_query_groups
|
||||
)
|
||||
else:
|
||||
self.num_query_groups = num_query_groups
|
||||
|
||||
if self.ffn_hidden_size_list:
|
||||
self.ffn_hidden_size = next(
|
||||
(v for v in self.ffn_hidden_size_list if v), ffn_hidden_size
|
||||
)
|
||||
else:
|
||||
self.ffn_hidden_size = ffn_hidden_size
|
||||
|
||||
self.zaya_mlp_expansion = zaya_mlp_expansion_scalar
|
||||
|
||||
# The HF config exposes the per-head dim as ``kv_channels``; accept
|
||||
# either spelling and keep both attributes in sync for downstream code.
|
||||
if head_dim is None and kv_channels is not None:
|
||||
head_dim = int(kv_channels)
|
||||
self.head_dim = head_dim
|
||||
self.kv_channels = kv_channels if kv_channels is not None else head_dim
|
||||
assert self.head_dim is not None, "head_dim is required for ZayaConfig"
|
||||
assert (
|
||||
self.num_query_groups == num_key_value_heads
|
||||
), "num_query_groups must equal num_key_value_heads for ZAYA1 checkpoints"
|
||||
self.num_key_value_heads = num_key_value_heads
|
||||
self.activation_func = activation_func
|
||||
self.max_position_embeddings = max_position_embeddings
|
||||
self.norm_epsilon = norm_epsilon
|
||||
self.normalization = normalization
|
||||
self.pad_token_id = pad_token_id
|
||||
self.bos_token_id = bos_token_id
|
||||
self.eos_token_id = eos_token_id
|
||||
self.tie_word_embeddings = tie_word_embeddings
|
||||
self.attention_dropout = attention_dropout
|
||||
self.moe_router_topk = moe_router_topk
|
||||
self.zaya_use_mod = zaya_use_mod
|
||||
self.zaya_high_prec = zaya_high_prec
|
||||
self.zaya_use_eda = zaya_use_eda
|
||||
self.add_bias_linear = add_bias_linear
|
||||
self.gated_linear_unit = gated_linear_unit
|
||||
self.scale_residual_merge = scale_residual_merge
|
||||
self.fused_add_norm = fused_add_norm
|
||||
self.residual_in_fp32 = residual_in_fp32
|
||||
self.apply_rope_fusion = apply_rope_fusion
|
||||
self.bias_activation_fusion = bias_activation_fusion
|
||||
self.activation_func_fp8_input_store = activation_func_fp8_input_store
|
||||
self.sliding_window = sliding_window
|
||||
self.partial_rotary_factor = partial_rotary_factor
|
||||
self.rope_theta = rope_theta
|
||||
self.rope_scaling = rope_scaling
|
||||
|
||||
if isinstance(rope_parameters, dict):
|
||||
rope_parameters_dict = dict(rope_parameters)
|
||||
elif isinstance(rope_scaling, dict):
|
||||
rope_parameters_dict = dict(rope_scaling)
|
||||
else:
|
||||
rope_parameters_dict = {"rope_type": "default"}
|
||||
if "type" in rope_parameters_dict:
|
||||
rope_parameters_dict.setdefault(
|
||||
"rope_type", rope_parameters_dict.pop("type")
|
||||
)
|
||||
rope_parameters_dict.setdefault("rope_theta", rope_theta)
|
||||
rope_parameters_dict.setdefault("partial_rotary_factor", partial_rotary_factor)
|
||||
self.rope_parameters = rope_parameters_dict
|
||||
|
||||
self.clamp_temp = clamp_temp
|
||||
self.cca_time0 = cca_time0
|
||||
self.cca_time1 = cca_time1
|
||||
self.swa_layers = swa_layers
|
||||
self.swa_rotary_base = swa_rotary_base
|
||||
self._attn_implementation = _attn_implementation
|
||||
|
||||
super().__init__(
|
||||
pad_token_id=pad_token_id,
|
||||
bos_token_id=bos_token_id,
|
||||
eos_token_id=eos_token_id,
|
||||
tie_word_embeddings=self.tie_word_embeddings,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
# -- Hybrid model interface (HybridReqToTokenPool / MambaPool) ----------
|
||||
|
||||
@property
|
||||
def full_attention_layer_ids(self) -> List[int]:
|
||||
if self.zaya_layers:
|
||||
return [i for i, lt in enumerate(self.zaya_layers) if lt == "a"]
|
||||
return [i for i in range(self.num_hidden_layers) if i % 2 == 0]
|
||||
|
||||
@property
|
||||
def linear_layer_ids(self) -> List[int]:
|
||||
return self.full_attention_layer_ids
|
||||
|
||||
@property
|
||||
def mamba_chunk_size(self) -> int:
|
||||
return 1
|
||||
|
||||
@property
|
||||
def mamba2_cache_params(self) -> Optional[Mamba2CacheParams]:
|
||||
from sglang.srt.configs.mamba_utils import (
|
||||
Mamba2CacheParams,
|
||||
Mamba2StateShape,
|
||||
mamba2_state_dtype,
|
||||
)
|
||||
|
||||
attn_layer_ids = self.linear_layer_ids
|
||||
if not attn_layer_ids:
|
||||
return None
|
||||
|
||||
# ``conv[0]`` (conv_qk left padding) is sized per TP rank because CCA
|
||||
# is head-parallel. ``conv[1]`` (prev_hs) carries the full hidden_state
|
||||
# and feeds the replicated val_proj1 / val_proj2, so it stays at full
|
||||
# ``hidden_size`` on every rank.
|
||||
#
|
||||
# Use the *global* TP world size -- the same accessor that
|
||||
# ``ZayaAttention`` / ``CCA`` use to split heads and over which
|
||||
# ``o_proj`` all-reduces -- so the cache shape and the per-rank
|
||||
# ``in_out_ch`` stay in lockstep. ZAYA1 asserts the attention-TP group
|
||||
# equals the global TP group (DP attention is unsupported), so the two
|
||||
# are always identical in practice.
|
||||
try:
|
||||
|
||||
tp_size = get_parallel().tp_size
|
||||
except (AssertionError, RuntimeError):
|
||||
tp_size = 1
|
||||
|
||||
in_out_ch_full = (
|
||||
self.num_attention_heads + self.num_key_value_heads
|
||||
) * self.head_dim
|
||||
assert in_out_ch_full % tp_size == 0, (
|
||||
f"CCA channels ({in_out_ch_full}) must be divisible by TP size "
|
||||
f"({tp_size}); both num_attention_heads and num_query_groups must "
|
||||
"be divisible by tp_size for ZAYA1 head-parallel attention."
|
||||
)
|
||||
in_out_ch_per_rank = in_out_ch_full // tp_size
|
||||
total_padding = (self.cca_time0 - 1) + (self.cca_time1 - 1)
|
||||
|
||||
shape = Mamba2StateShape(
|
||||
conv=[
|
||||
(in_out_ch_per_rank, total_padding),
|
||||
(self.hidden_size, 1),
|
||||
],
|
||||
temporal=(1, 1, 0),
|
||||
intermediate_size=in_out_ch_per_rank,
|
||||
conv_dim=in_out_ch_per_rank,
|
||||
ssm_state_size=0,
|
||||
num_heads=1,
|
||||
head_dim=1,
|
||||
state_size=0,
|
||||
conv_kernel=total_padding + 1,
|
||||
)
|
||||
|
||||
return Mamba2CacheParams(
|
||||
shape=shape,
|
||||
layers=attn_layer_ids,
|
||||
dtype=mamba2_state_dtype(self),
|
||||
)
|
||||
|
||||
|
||||
def register_zaya_config() -> None:
|
||||
"""Register :class:`ZayaConfig` with HuggingFace ``AutoConfig``.
|
||||
|
||||
Safe to call multiple times. ``AutoConfig.register`` raises ``ValueError``
|
||||
on duplicate registration, which is suppressed so importing this module
|
||||
stays idempotent.
|
||||
"""
|
||||
try:
|
||||
from transformers import AutoConfig
|
||||
|
||||
AutoConfig.register(ZayaConfig.model_type, ZayaConfig)
|
||||
except (ValueError, ImportError):
|
||||
# Either the installed ``transformers`` does not expose
|
||||
# ``AutoConfig.register``, or the "zaya" model type is already
|
||||
# registered – nothing to do in either case.
|
||||
pass
|
||||
|
||||
|
||||
register_zaya_config()
|
||||
@@ -0,0 +1,75 @@
|
||||
# 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 _is_azure_blob_url(url: str, connector_type: str) -> bool:
|
||||
"""Detect Azure Blob Storage URLs.
|
||||
|
||||
Matches ``az://...`` URLs and ``https://<account>.blob.core.windows.net/...``
|
||||
URLs, which are the two forms accepted by the ``blobfile`` library.
|
||||
"""
|
||||
if connector_type == "az":
|
||||
return True
|
||||
return connector_type == "https" and ".blob.core.windows.net" in url
|
||||
|
||||
|
||||
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)
|
||||
elif _is_azure_blob_url(url, connector_type):
|
||||
# Imported lazily so the optional ``blobfile`` dependency is only
|
||||
# required when an Azure URL is actually used.
|
||||
from sglang.srt.connector.azure import AzureBlobConnector
|
||||
|
||||
return AzureBlobConnector(url)
|
||||
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",
|
||||
]
|
||||
@@ -0,0 +1,127 @@
|
||||
# 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 _normalize_url(url: str) -> str:
|
||||
"""Strip trailing slash so blobfile glob/listdir behave consistently."""
|
||||
return url.rstrip("/")
|
||||
|
||||
|
||||
def list_files(
|
||||
bf,
|
||||
path: str,
|
||||
allow_pattern: Optional[list[str]] = None,
|
||||
ignore_pattern: Optional[list[str]] = None,
|
||||
) -> Tuple[str, list[str]]:
|
||||
"""List files from an Azure Blob Storage path and filter by pattern.
|
||||
|
||||
Args:
|
||||
bf: The ``blobfile`` module.
|
||||
path: An ``az://<account>/<container>/<prefix>`` or
|
||||
``https://<account>.blob.core.windows.net/<container>/<prefix>`` URL.
|
||||
allow_pattern: A list of fnmatch patterns of which files to keep.
|
||||
ignore_pattern: A list of fnmatch patterns of which files to drop.
|
||||
|
||||
Returns:
|
||||
A tuple ``(base_dir, files)`` where ``base_dir`` is the normalized
|
||||
prefix used as a directory anchor for relative paths, and ``files``
|
||||
is the list of full URLs matched by the patterns.
|
||||
"""
|
||||
base_dir = _normalize_url(path)
|
||||
files = [p for p in bf.glob(base_dir + "/**") if not bf.isdir(p)]
|
||||
|
||||
files = _filter_ignore(files, ["*/"])
|
||||
if allow_pattern is not None:
|
||||
files = _filter_allow(files, allow_pattern)
|
||||
if ignore_pattern is not None:
|
||||
files = _filter_ignore(files, ignore_pattern)
|
||||
|
||||
return base_dir, files
|
||||
|
||||
|
||||
class AzureBlobConnector(BaseFileConnector):
|
||||
"""File connector for Azure Blob Storage.
|
||||
|
||||
Accepts both ``az://<account>/<container>/<path>`` URLs and HTTPS URLs of
|
||||
the form ``https://<account>.blob.core.windows.net/<container>/<path>``.
|
||||
Uses the third-party ``blobfile`` package, which handles authentication via
|
||||
standard Azure credential chains (env vars, az CLI, managed identity).
|
||||
"""
|
||||
|
||||
def __init__(self, url: str) -> None:
|
||||
try:
|
||||
import blobfile as bf
|
||||
except ImportError as e:
|
||||
raise ImportError(
|
||||
"AzureBlobConnector requires the 'blobfile' package. "
|
||||
"Install it with `pip install blobfile`."
|
||||
) from e
|
||||
|
||||
super().__init__(url)
|
||||
self.bf = bf
|
||||
|
||||
def glob(self, allow_pattern: Optional[list[str]] = None) -> list[str]:
|
||||
_, files = list_files(self.bf, self.url, allow_pattern=allow_pattern)
|
||||
return files
|
||||
|
||||
def pull_files(
|
||||
self,
|
||||
allow_pattern: Optional[list[str]] = None,
|
||||
ignore_pattern: Optional[list[str]] = None,
|
||||
) -> None:
|
||||
"""Download files from Azure Blob Storage to ``self.local_dir``."""
|
||||
base_dir, files = list_files(self.bf, self.url, allow_pattern, ignore_pattern)
|
||||
if not files:
|
||||
return
|
||||
|
||||
for file in files:
|
||||
relative = file[len(base_dir) :].lstrip("/")
|
||||
destination_file = os.path.join(self.local_dir, relative)
|
||||
os.makedirs(Path(destination_file).parent, exist_ok=True)
|
||||
self.bf.copy(file, destination_file, overwrite=True)
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
# Pull *.safetensors locally first since runai_safetensors_weights_iterator
|
||||
# expects local files. blobfile does not provide a streaming safetensors
|
||||
# reader compatible with runai_model_streamer.
|
||||
self.pull_files(allow_pattern=["*.safetensors"])
|
||||
local_files = [
|
||||
os.path.join(root, f)
|
||||
for root, _, fs in os.walk(self.local_dir)
|
||||
for f in fs
|
||||
if f.endswith(".safetensors")
|
||||
]
|
||||
return runai_safetensors_weights_iterator(local_files)
|
||||
|
||||
def close(self):
|
||||
super().close()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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"))
|
||||
@@ -0,0 +1,14 @@
|
||||
# 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"
|
||||
|
||||
GIB_BYTES = 1073741824 # 1024**3
|
||||
@@ -0,0 +1,315 @@
|
||||
# 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.parser.reasoning_parser import ReasoningParser
|
||||
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:
|
||||
_enable_strict_thinking: bool = False
|
||||
|
||||
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()
|
||||
|
||||
@property
|
||||
def enable_strict_thinking(self):
|
||||
return self._enable_strict_thinking
|
||||
|
||||
@property
|
||||
def is_support_token_filter(self):
|
||||
return False
|
||||
|
||||
def set_token_filter(
|
||||
self, vocab_mask, token_ids, batch_idx, is_allowed=True, reset_vocab_mask=True
|
||||
):
|
||||
"""Set or clear specific tokens in the vocab mask. No-op by default."""
|
||||
pass
|
||||
|
||||
def init_strict_reasoning_grammar(self, reasoning: bool):
|
||||
"""Create a grammar object for strict token filtering only. Returns None by default."""
|
||||
return None
|
||||
|
||||
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,
|
||||
think_end_id: Optional[int] = 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:
|
||||
if server_args.enable_strict_thinking:
|
||||
raise ValueError(
|
||||
f"--enable-strict-thinking requires a grammar backend with "
|
||||
f"token filtering support, but XGrammar failed to initialize: "
|
||||
f"{e}. Cannot fall back to grammar_backend='none' with strict "
|
||||
f"thinking enabled."
|
||||
) from 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.override("grammar.import_fallback", 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":
|
||||
if server_args.enable_strict_thinking:
|
||||
raise ValueError(
|
||||
"--enable-strict-thinking requires a grammar backend that supports "
|
||||
"token filtering, but grammar_backend='none' was specified. Use "
|
||||
"--grammar-backend xgrammar or another backend that supports token "
|
||||
"filtering."
|
||||
)
|
||||
return None
|
||||
else:
|
||||
raise ValueError(f"Invalid grammar backend: {name}")
|
||||
|
||||
if server_args.reasoning_parser and think_end_id is not None:
|
||||
from sglang.srt.constrained.reasoner_grammar_backend import (
|
||||
ReasonerGrammarBackend,
|
||||
)
|
||||
|
||||
reasoning_parser = ReasoningParser(
|
||||
model_type=server_args.reasoning_parser,
|
||||
stream_reasoning=False,
|
||||
tokenizer=tokenizer,
|
||||
)
|
||||
|
||||
grammar_backend = ReasonerGrammarBackend(
|
||||
grammar_backend,
|
||||
reasoning_parser,
|
||||
tokenizer,
|
||||
enable_strict_thinking=server_args.enable_strict_thinking,
|
||||
)
|
||||
|
||||
return grammar_backend
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user