SGLang 플러그인 시스템
SGLang 플러그인 시스템 (SGLang Plugin System)
이 문서는 하드웨어 벤더와 개발자가 메인 저장소 코드를 수정하지 않고 SGLang을 확장하는 방법을 설명해요. 프레임워크는 두 가지 플러그인 유형을 제공하며, 둘 다 Python의 표준 setuptools entry_points를 통해 발견돼요.
출처: 문서
본문
| 플러그인 유형 | 엔트리 포인트 그룹 | 목적 |
|---|---|---|
| 하드웨어 플랫폼 플러그인 | sglang.srt.platformssglang.multimodal\_gen.platforms |
커스텀 하드웨어 플랫폼 등록 (디바이스 연산, KV 캐시 풀, 어텐션 백엔드, 그래프 캡처, 컴파일 백엔드 등) |
| 일반 플러그인 | sglang.srt.pluginssglang.multimodal\_gen.plugins |
모든 함수/메서드에 훅 삽입 (before/after/around/replace), 또는 전체 클래스 대체 |
원칙 (Principles)
- 비간섭적 (Non-intrusive): OOT 플랫폼이 활성화되지 않으면 내장 플랫폼이 폴백으로 유지돼요.
- 설치 시 발견 (Install-time discovery): 플러그인은 설치 후 Python 엔트리 포인트에서 발견돼요.
- 환경 변수 제어:
SGLANG_PLATFORM은 SRT 플랫폼을 선택하고,SGLANG_DIFFUSION_PLATFORM_OVERRIDE는 diffusion 플랫폼을 선택하며,SGLANG_PLUGINS는 두 훅 그룹 중 하나에서 일반 플러그인을 필터링해요.
현재 범위 (Current scope)
플랫폼 플러그인 시스템은 out-of-tree (OOT) 하드웨어 플랫폼을 대상으로 해요. Diffusion 지원은 실험적이며 아래 문서화된 지점들만 다루고, 모든 디바이스별 분기를 다루지는 않아요.
아키텍처 (Architecture)
런타임별 플랫폼 인터페이스 (Runtime-specific platform interfaces)
SRT와 diffusion은 sglang.srt.platforms 및 sglang.multimodal_gen.runtime.platforms에 별도의 플랫폼 베이스 클래스와 플랫폼 정체성 유형을 가져요. 두 런타임을 모두 지원하는 패키지는 별도의 플랫폼 클래스를 정의하고 각 런타임의 PlatformEnum.OOT 값을 사용해야 해요.
각 플랫폼 엔트리 포인트는 인자 없는 활성화 콜백으로 해석돼요:
def activate() -> str | None:
"""이 하드웨어가 사용 가능할 때 플랫폼 클래스 qualname을 반환."""
프로바이더가 실행될 수 있으면 정규화된 클래스 이름을, 그렇지 않으면 None을 반환하세요. 활성화를 import-safe하게 유지하세요: current_platform에 접근하거나 런타임/디바이스 상태를 초기화하지 마세요. 필요한 백엔드 설정은 플랫폼의 init_backend() 메서드에 두고, 플랫폼 인터페이스로 표현할 수 없는 훅만 일반 플러그인에 남겨두세요.
명시적 선택은 엔트리 포인트 메타데이터를 열거하고 선택된 콜백만 임포트해요. 자동 선택은 설치된 플랫폼 콜백을 호출해 어떤 프로바이더가 활성인지 결정해요.
Diffusion은 cpu, cuda, rocm, xpu, mps, npu, musa를 보유해요. 자동 발견은 중복 엔트리 포인트 이름도 거부하며, 명시적 선택은 선택된 이름만 검증하고 관련 없는 프로바이더를 임포트하지 않아요.
SRT 선택 (SRT selection)
current_platform은 sglang.srt.platforms의 lazy singleton이에요. 첫 접근 시 다음 우선순위 체인으로 활성 플랫폼을 해석해요:
entry_points("sglang.srt.platforms") → 이름으로 ALL 플러그인 열거 (메타데이터만)
│
├─ SGLANG_PLATFORM 설정 (front-loading 필터):
│ ├─ 발견된 이름 없음 → RuntimeError
│ ├─ activate()가 non-None 반환 → 해당 플랫폼 로드
│ └─ activate()가 None 반환 → RuntimeError (하드웨어 사용 불가)
│
└─ SGLANG_PLATFORM 미설정 (자동 발견, 전부 활성화):
├─ 0 활성화 + SGLANG_USE_CPU_ENGINE=1 → 폴백 CpuSRTPlatform
├─ 0 활성화 + CUDA 사용 가능 → 폴백 CudaSRTPlatform
├─ 0 활성화 + ROCm 사용 가능 → 폴백 RocmSRTPlatform
├─ 0 활성화 + XPU 사용 가능 → 폴백 XpuSRTPlatform
├─ 0 활성화 + 위 모두 아님 → 폴백 기본 SRTPlatform
├─ 1 활성화 → 사용
└─ N 활성화 → RuntimeError (SGLANG_PLATFORM 설정 필요)
Diffusion 선택 (Diffusion selection)
SGLang Diffusion은 다음 순서로 플랫폼을 해석해요:
SGLANG_DIFFUSION_PLATFORM_OVERRIDE가cpu,cuda,rocm,mps,npu,musa중 하나를 가리키면 하드웨어 프로빙 없이 그 내장 플랫폼을 선택해요. XPU는 기존 선택기 동작을 유지하며 자동 전용으로 남아요.- 다른 이름을 포함하면 일치하는
sglang.multimodal_gen.platforms엔트리 포인트만 로드해요. 알 수 없는 이름이나None을 반환하는 콜백은 오류예요. - 설정되지 않으면 설치된 diffusion 플랫폼 플러그인을 활성화해요. 활성 플러그인이 없으면 내장 감지로 계속되고, 하나가 선택되면 그 플러그인을 선택하며, 여러 개 활성화되면 선택기를 설정하라는 오류를 발생시켜요. 활성화 콜백이 예외를 던지면 폴백하지 않고 시작을 중단해, 손상된 벤더 런타임이 내장 플랫폼에서 조용히 작업을 실행하지 못하게 해요.
- 내장 플랫폼을 순서대로 시도해요: MPS, XPU, ROCm, CUDA, NPU, MUSA, 그 다음 CPU.
따라서 기존 오버라이드 변수는 지원되는 내장 별칭과 OOT 엔트리 포인트 이름을 위한 유일한 명시적 선택기예요.
선택은 프로세스에서 어떤 것이든 current_platform을 처음 건드릴 때 늦게 해석되므로 호출 지점이 필요 없고 모든 프로세스에서 자동으로 일어나요. SGLang Diffusion은 또한 선택한 플랫폼을 제공한 배포(distribution)를 기록하고 다른 모든 설치된 플랫폼 패키지의 훅을 건너뛰어요.
필수 플랫폼 초기화 vs 선택적 훅 (Required platform initialization vs. optional hooks)
두 메커니즘은 실패 의미가 다르며, 어느 것을 사용하는지가 중요해요:
| 플랫폼 콘트랙트 | 일반 플러그인 | |
|---|---|---|
| 엔트리 포인트 그룹 | sglang.multimodal_gen.platforms |
sglang.multimodal_gen.plugins |
| 전달 | Platform 서브클래스의 메서드 |
대상을 monkey-patch하는 훅 |
| 활성화 | lazy current_platform + 각 워커의 guarded init_backend() |
load_plugins()로 등록 후, 프로세스당 명시적 apply_plugin_hooks() |
| 실패 | 시작 중단 | 플러그인이 선택된 플랫폼의 배포에 있으면 시작 중단, 그 외에는 로깅 |
하드웨어가 올바르게 동작하기 위해 필요한 모든 것은 Platform 서브클래스에 있어야 하므로, 손상된 플랫폼이 조용히 서빙할 수 없어요. Platform 인터페이스에 필요한 부분이 없을 때만 일반 플러그인을 사용하고, 그 격차를 보고해 주세요.
선택된 플랫폼 자체의 배포에 포함된 플러그인은 그 플랫폼의 콘트랙트 일부로 취급돼요: 로드 실패, 콜백 실행 실패, 등록한 훅 적용 실패는 플랫폼을 반쯤 초기화한 채로 두지 않고 시작을 중단해요. 다른 설치된 패키지의 플러그인은 최선 노력(best-effort)으로 유지되므로 손상된 타사가 서버를 다운시키지 못해요. 명시적 SGLANG_PLUGINS 허용 목록은 선택된 플랫폼 패키지의 것을 포함해 모든 일반 플러그인을 비활성화할 수 있어요. 따라서 필수 하드웨어 설정은 init_backend()에 있어야 해요.
플러그인 로딩 흐름 (Plugin Loading Flow)
각 런타임에는 프로세스 로컬 훅 레지스트리가 있어요. SRT는 단일 load_plugins() 활성화 단계를 유지해요. Diffusion은 점으로 구분된 훅 대상 해석이 대상의 전체 모듈 그래프를 임포트할 수 있기 때문에 등록(load_plugins())과 대상 해석(apply_plugin_hooks())을 분리해요. 두 런타임 모두 SGLANG_PLUGINS를 존중해요.
로더는 다음 SRT 엔트리 포인트에서 호출돼요:
| 호출 지점 | 프로세스 | 시점 |
|---|---|---|
cli/serve.py serve() |
Main | prepare\_server\_args() 전 |
launch\_server.py **main** |
Main | prepare\_server\_args() 전 |
engine.py \_launch\_subprocesses() |
Main | server\_args.check\_server\_args() 전 |
scheduler.py run\_scheduler\_process() |
Subprocess | Scheduler() 생성 전 |
참고: Diffusion 플러그인 등록과 훅 적용은 각각 프로세스당 한 번 실행돼요. 생성된 서브프로세스는 빈 인터프리터에서 시작해 자신의 훅을 확립해요 — 부모가 패치한 어떤 것도 spawn 경계를 넘지 못해요. SRT의
load_plugins()는 두 단계를 한 번의 호출로 수행해요.
load_plugins()
├── _get_excluded_dists() → 건너뛸 dists 계산 (SGLANG_PLATFORM 통해)
├── load_plugins_by_group("sglang.srt.plugins", → entry_points 발견, SGLANG_PLUGINS로 필터
│ excluded_dists=...) 선택되지 않은 플랫폼 패키지의 플러그인 건너뜀
├── for each plugin: → _current_plugin_source 컨텍스트 var 설정
│ func() 부작용 (셧로 매핑 훅 등록)
└── HookRegistry.apply_hooks() → 대상 monkey-patch
Diffusion 런처는 apply_plugin_hooks()를 호출하며, 필요하면 먼저 등록을 수행한 후 대상을 해석하고 패치해요. 스케줄러 자식은 runtime/managers/worker_bootstrap.py에서 더 엄격한 라이프사이클을 사용해요:
spawn unpickles SchedulerProcessSpec (stdlib 타입 + opaque ServerArgs bytes)
-> initialize_current_platform()
-> load_plugins() # 콜백 발견 및 훅 등록만
-> apply_plugin_hooks() # 여기부터 대상 임포트 허용
-> materialize ServerArgs
-> resolve and invoke the patched run_scheduler_process
HTTP 자식도 runtime.launch_server를 임포트하거나 ServerArgs를 materialize하기 전에 플러그인 등록과 훅 적용을 끝내요. 이는 서버 모듈 그래프 임포트 중에 만들어진 클래스나 등록에 클래스 대체와 같은 훅 효과를 소급 적용할 수 없기 때문에 중요해요.
부트스트랩 모듈은 mp.Process 대상이며 모듈 스코프에서 diffusion 런타임 모듈을 임포트하지 않아요. ServerArgs는 ServerArgsPayload 안에서 직렬화되므로 multiprocessing unpickler가 대상 시작 전에 파이프라인 구성 모듈을 임포트할 수 없어요. 워커는 항상 임베딩 애플리케이션의 전역 multiprocessing 설정과 무관한 로컬 spawn 컨텍스트를 사용해요.
이 순서는 백엔드 초기화가 플러그인 콜백 임포트, 훅 대상 해석, ServerArgs materialization, 워커 임포트보다 앞서도록 해요. 플랫폼 활성화 모듈은 필연적으로 자신의 init_backend() 전에 로드되므로 활성화 모듈은 import-safe로 유지되어야 해요. 필수 동작에는 플랫폼 메서드와 레지스트리를 선호하세요.
각 활성화 단계는 프로세스당 한 번만 lock 뒤에서 실행돼요: 플러그인 콜백에서 재진입 호출은 반환하고 바깥 호출이 마치도록 하며, 다른 스레드는 기다리며, 실패는 치명적이에요. 콜백은 활성화를 두 번째 스레드에 넘겨 조인하면 안 돼요 — 교착 상태가 발생해요.
오프라인 스크립트와 spawn 경계 (Offline scripts and the spawn boundary)
spawn은 대상의 인자를 unpickle하기 전에 모든 자식에서 실행 스크립트의 모듈 스코프를 다시 실행하므로, 오프라인 스크립트 자신의 임포트가 그 자식의 플랫폼 초기화보다 앞서 실행되고 ServerArgsPayload가 도움이 될 수 없어요. 지원되는 스크립트 레이아웃은:
from sglang.multimodal_gen import DiffGenerator # a proxy: imports nothing yet
if __name__ == "__main__":
generator = DiffGenerator.from_pretrained(model_path="...")
sglang.multimodal_gen 파사드의 DiffGenerator는 lazy proxy이므로 모듈 스코프에서 바인딩해도 diffusion 임포트 비용이 없고 자식은 깨끗한 모듈 테이블로 initialize_current_platform()에 도달해요. 모듈 스코프는 envs와 runtime.platforms(및 runtime.platforms.plugins)에도 열려 있어요. 이 모듈들은 import-safe로 유지되므로 플러그인이 백엔드 초기화 전에 Platform을 서브클래스화하고 훅을 등록할 수 있어요. 같은 파사드의 SamplingParams와 PipelineConfig 같은 다른 모든 것은 if __name__ == "__main__": 가드 안이나 그것을 사용하는 함수 안에 넣어야 해요. 이미 임포트된 런타임 모듈을 발견한 자식은 경고로 그 이름을 알려줘요: 훅 적용이 그 모듈을 패치할 수는 있지만, 임포트 중에 만들어진 클래스와 등록은 이미 도달 범위를 벗어났어요.
플러그인 유형 1: 하드웨어 플랫폼 플러그인 (Hardware Platform Plugin)
설명 (Description)
하드웨어 플랫폼 플러그인은 SRT SRTPlatform 서브클래스, diffusion Platform 서브클래스 또는 둘 다를 등록해요. 선택된 클래스는 해당 런타임에 특정 하드웨어 백엔드와 상호작용하는 방법을 알려줘요.
SRT 퀵스타트 (SRT quick start)
1. 최소 패키지 생성:
my_platform_plugin/
├── pyproject.toml
└── my_platform_plugin/
├── __init__.py # activate() function
├── device.py # MyDeviceMixin
└── platform.py # MySRTPlatform
2. pyproject.toml:
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
[project]
name = "my-platform-plugin"
version = "0.1.0"
[project.entry-points."sglang.srt.platforms"]
my_device = "my_platform_plugin:activate"
3. __init__.py — 활성화 함수:
def activate():
"""활성화할 정규화된 클래스 이름을 반환하거나, 건너뛰려면 None을 반환."""
if _my_device_is_available():
return "my_platform_plugin.platform.MySRTPlatform"
return None
4. device.py — 디바이스 믹스인:
from sglang.srt.platforms.device_mixin import DeviceMixin, PlatformEnum
class MyDeviceMixin(DeviceMixin):
_enum = PlatformEnum.OOT
device_name = "my_device"
device_type = "my_device" # torch device type
def set_device(self, device) -> None: ...
def get_device_name(self, device_id=0) -> str: ...
def get_device_total_memory(self, device_id=0) -> int: ...
def get_current_memory_usage(self, device=None) -> float: ...
def get_device_capability(self, device_id=0): ...
def get_torch_distributed_backend_str(self) -> str: ...
5. platform.py — SRT 플랫폼:
from sglang.srt.platforms.interface import SRTPlatform
from my_platform_plugin.device import MyDeviceMixin
class MySRTPlatform(SRTPlatform, MyDeviceMixin):
def get_default_attention_backend(self) -> str: ...
def support_cuda_graph(self) -> bool: ...
# ... 필요에 따라 다른 메서드 오버라이드
6. 설치 및 검증:
pip install -e my_platform_plugin/
python -c "from sglang.srt.platforms import current_platform; print(current_platform)"
SRT 플랫폼 인터페이스 참조 (SRT platform interface reference)
정체성 조회 (Identity Queries, from DeviceMixin)
| 메서드 | 기본값 | 설명 |
|---|---|---|
is\_cuda() |
\_enum 기반 |
NVIDIA CUDA 플랫폼인지 여부 |
is\_rocm() |
\_enum 기반 |
AMD ROCm 플랫폼인지 여부 |
is\_npu() |
\_enum 기반 |
Huawei NPU 플랫폼인지 여부 |
is\_cpu() |
\_enum 기반 |
CPU 전용 플랫폼인지 여부 |
is\_xpu() |
\_enum 기반 |
Intel XPU 플랫폼인지 여부 |
is\_musa() |
\_enum 기반 |
Moore Threads MUSA 플랫폼인지 여부 |
is\_cuda\_alike() |
CUDA+ROCM+MUSA | 하드웨어가 CUDA 유사 API를 지원하면 True |
is\_out\_of\_tree() |
OOT에 True |
\_enum = PlatformEnum.OOT 기반 자동 감지 |
디바이스 연산 (Device Operations, from DeviceMixin)
[Active] 로 표시된 메서드는
current_platform을 통해 SGLang 코어가 호출해요 — OOT 구현이 즉시 적용돼요. [Planned] 로 표시된 메서드는 예약된 인터페이스예요 — SGLang 코어는 여전히 하드코딩된 호출(예:torch.cuda.empty_cache())을 사용해요. OOT 구현은 코어가 향후 PR에서 마이그레이션될 때까지 적용되지 않아요.
| 메서드 | 기본값 | 상태 | 설명 |
|---|---|---|---|
get\_device(local\_rank) |
raise NotImplementedError |
Planned | 주어진 로컬 랭크의 torch.device 반환 |
set\_device(device) |
raise NotImplementedError |
Planned | 현재 디바이스 설정 |
get\_device\_name(device\_id) |
raise NotImplementedError |
Planned | 사람이 읽을 수 있는 디바이스 이름 |
get\_device\_uuid(device\_id) |
raise NotImplementedError |
Planned | 고유 디바이스 식별자 |
get\_device\_capability(device\_id) |
raise NotImplementedError |
Planned | DeviceCapability(major, minor) 반환. 해당 없으면 None |
empty\_cache() |
pass |
Planned | 캐시된 디바이스 메모리 해제 |
synchronize() |
pass |
Planned | 디바이스 연산 동기화 |
get\_device\_total\_memory(device\_id) |
raise NotImplementedError |
Active | 바이트 단위 총 디바이스 메모리 |
get\_available\_memory(device\_id) |
raise NotImplementedError |
Planned | (free\_bytes, total\_bytes) 반환 |
get\_current\_memory\_usage(device) |
raise NotImplementedError |
Active | 바이트 단위 현재 최고 메모리 사용량 |
is\_pin\_memory\_available(device=None) |
False |
Active | 대상 디바이스에 pinned 호스트 메모리 사용 가능 여부 |
get\_torch\_distributed\_backend\_str() |
raise NotImplementedError |
Planned | 분산 백엔드 문자열 (예: "nccl", "hccl") |
get\_communicator\_class() |
None |
Planned | 플랫폼별 커뮤니케이터 클래스 |
inference\_mode() |
torch.inference\_mode(True) |
Planned | 추론 모드 컨텍스트 매니저 반환 |
seed\_everything(seed) |
Set random/np/torch seeds | Planned | 재현성을 위한 난수 시드 설정 |
verify\_quantization(quant) |
pass |
Planned | 양자화 방법 지원 검증 |
get\_cpu\_architecture() |
Auto-detect x86/arm | Planned | CPU 아키텍처 감지 (CpuArchEnum) |
유형 (Types, from DeviceMixin)
| 유형 | 설명 |
|---|---|
PlatformEnum |
플랫폼 유형 열거: CUDA, ROCM, CPU, XPU, MUSA, NPU, TPU, MPS, OOT, UNSPECIFIED |
CpuArchEnum |
CPU 아키텍처: X86, ARM, UNSPECIFIED |
DeviceCapability |
비교 지원이 있는 NamedTuple(major, minor). 메서드: as\_version\_str(), to\_int() |
기능 플래그 (Capability Flags, from SRTPlatform)
| 메서드 | 기본값 | 설명 |
|---|---|---|
support\_cuda\_graph() |
False |
디바이스 그래프 캡처 지원 여부 (일반 CUDA graph) |
support\_piecewise\_cuda\_graph() |
False |
piecewise CUDA graph (torch.compile backend) 지원 여부 |
supports\_fp8() |
False |
FP8 양자화 지원 여부 |
서브시스템 팩토리 메서드 (Subsystem Factory Methods, from SRTPlatform)
| 메서드 | 기본값 | 설명 |
|---|---|---|
get\_default\_attention\_backend() |
raise NotImplementedError |
기본 어텐션 백엔드 이름 |
get\_graph\_runner\_cls() |
raise NotImplementedError |
Graph Runner 클래스 |
get\_mha\_kv\_pool\_cls() |
raise NotImplementedError |
MHA KV 캐시 풀 클래스 |
get\_mla\_kv\_pool\_cls() |
raise NotImplementedError |
MLA KV 캐시 풀 클래스 |
get\_dsa\_kv\_pool\_cls() |
raise NotImplementedError |
DSA KV 캐시 풀 클래스 (DeepSeek V3.2) |
get\_paged\_allocator\_cls() |
raise NotImplementedError |
Paged 할당기 클래스 |
get\_quantization\_config(quantization) |
raise NotImplementedError |
특정 양자화 방식의 하드웨어별 양자화 구성 반환, 미지원이면 오류 발생 또는 기본 구성을 쓰려면 None 반환 |
get\_piecewise\_backend\_cls() |
raise NotImplementedError |
Piecewise 컴파일 백엔드 클래스 |
get\_compile\_backend(mode) |
"inductor" |
컴파일 백엔드 문자열 |
get\_dispatch\_key\_name() |
"native" |
BaseFusedOp (fused-op) 디스패치 키 이름 |
라이프사이클 훅 (Lifecycle Hooks, from SRTPlatform)
| 메서드 | 호출 시점 | 목적 |
|---|---|---|
apply\_server\_args\_defaults(server\_args) |
ServerArgs 파싱 후, **post\_init**에서 |
플랫폼별 기본값 설정 |
init\_backend() |
각 워커에서, 모델 생성 전 | 일회성 백엔드 초기화 |
플랫폼 및 플러그인 환경 변수 (Platform and plugin environment variables)
| 변수 | 설명 |
|---|---|
SGLANG\_PLATFORM |
엔트리_포인트 이름으로 플랫폼 플러그인 선택 (예: kunlun, demo\_cuda). 설정되면 오직 이름이 지정된 플러그인의 activate()만 호출되고(front-loading 필터) 다른 플러그인은 건드리지 않아요. 또한 선택되지 않은 플랫폼 패키지의 일반 플러그인(sglang.srt.plugins)은 의존성 임포트를 피하기 위해 자동으로 건너뛰어요. 여러 플러그인이 활성화될 때 필요해요. 이름을 찾지 못하거나 플러그인의 하드웨어가 사용 불가하면 오류가 발생해요. |
SGLANG\_PLUGINS |
어느 훅 그룹에서든 로드할 일반 플러그인 이름의 쉼표 구분 허용 목록이에요. SGLANG\_PLATFORM이 설정되지 않았을 때 자동 SRT 플랫폼 발견도 필터링해요. 명시적 플랫폼 선택은 이를 무시해요. |
diffusion 지원 추가 (Add diffusion support)
패키지는 SRT, SGLang Diffusion 또는 둘 다 지원할 수 있어요. Diffusion은 인자 없는 Platform 서브클래스와 별도의 플랫폼·훅 엔트리 포인트를 사용해요:
[project.entry-points."sglang.multimodal_gen.platforms"]
my_device = "my_platform_plugin:activate_diffusion"
[project.entry-points."sglang.multimodal_gen.plugins"]
my_device = "my_platform_plugin:register_hooks"
활성화를 import-safe하게 유지하고 백엔드가 사용 가능할 때만 정규화된 클래스 이름을 반환하세요:
def activate_diffusion() -> str | None:
try:
import my_device_runtime
except ImportError:
return None
if not my_device_runtime.is_available():
return None
return "my_platform_plugin.diffusion_platform.MyDiffusionPlatform"
참조된 클래스는 인자 없이 생성 가능해야 해요:
from sglang.multimodal_gen.runtime.platforms import Platform, PlatformEnum
class MyDiffusionPlatform(Platform):
_enum = PlatformEnum.OOT
device_name = "my_device"
device_type = "my_device"
dispatch_key = "PrivateUse1"
def get_dispatch_key_name(self) -> str:
return "my_device"
dispatch_key를 직접 torch.library 등록에서 사용하는 PyTorch 디스패치 키로 설정하세요. CustomOp 구현을 선택하는 get_dispatch_key_name()과는 분리해 두세요. 나머지 콘트랙트를 의존성 순서대로 구성하세요:
ServerArgs를 구성하기 전에get_device_name(),get_device_total_memory(),get_available_gpu_memory()를 구현하세요.- 워커 바인딩 전에
get_device()와get_local_torch_device()를 구현하세요. - 지원 작업 부하에 대해 분산 초기화, 어텐션 선택, custom-op 구현을 구성하세요.
- 엔드투엔드 작업 부하를 실행하고 모델별 커널, 컴파일, 나머지 디바이스 패밀리 분기를 감사하세요.
파이프라인 모듈이 구성되기 전에 각 워커에서 한 번 실행되는 init_backend()에서 custom-op 구현을 등록하세요:
class MyDiffusionPlatform(Platform):
...
def init_backend(self) -> None:
from my_platform_plugin.ops import rms_norm_forward
from sglang.multimodal_gen.runtime.layers.custom_op import CustomOp
from sglang.multimodal_gen.runtime.layers.layernorm import RMSNorm
CustomOp.register_oot_forward(
RMSNorm, fn=rms_norm_forward, platform_key="my_device"
)
init_backend()는 워커 구현이 임포트되기 전에 워커 프로세스당 최대 한 번 실행돼요. 예외가 발생하면 시작이 중단되고 해당 프로세스에서 실패한 초기화가 재시도되지 않아요. 등록과 다른 백엔드 부작용이 부분적으로만 되돌릴 수 있기 때문이에요. custom-op 디스패치는 백엔드 초기화가 완료된 후 각 연산이 구성될 때 해석돼요. 따라서 선택된 콜러블은 첫 번째 컴파일 호출에서 바뀌는 대신 연산이 컴파일될 때 안정적으로 유지돼요.
함수는 정상 인자 전에 연산 인스턴스를 받아요. 등록은 정확한 연산 클래스와 get_dispatch_key_name()이 반환한 값과 일치해요. 등록이 없으면 SGLang은 연산에서 일치하는 forward_<key>() 구현을 찾고, 그 다음 forward_oot()로 폴백하며, 그 기반 구현은 forward_native()을 호출해요. 예를 들어 "cuda"를 반환하면 CUDA 호환 OOT 플랫폼이 CUDA 플랫폼 정체성을 보고하지 않고도 연산별 forward_cuda() 구현을 재사용할 수 있어요.
sglang.multimodal_gen.plugins 엔트리 포인트는 Platform 인터페이스가 다루지 않는 지점을 위해 남아 있어요. 그 콜백은 스케줄러나 모델 구성 전에 런처와 워커에서 실행되므로, 랭크 인지 초기화를 위해 GPUWorker.init_device_and_model()에 BEFORE 또는 AROUND 훅을 사용하세요. 선택된 것과 다른 플랫폼 배포의 훅은 건너뛰어져요. 그것들이 플랫폼의 배포에 포함되므로 로드, 콜백, 훅 적용 어느 단계의 실패든 init_backend()와 마찬가지로 시작을 중단해요.
Diffusion 플랫폼 콘트랙트 (Diffusion platform contract)
Active 인터페이스는 current_platform을 통해 호출돼요. Compatibility 인터페이스는 유지되는 어댑터예요. 목록에 없는 Platform 메서드는 안정적인 OOT 콘트랙트가 아니에요.
| 상태 | 영역 | 인터페이스 |
|---|---|---|
| Active | 초기화 | init_backend() |
| Active | 구성 | apply_server_args_defaults() |
| Active | 디바이스와 메모리 | get_device(), set_device(), get_local_torch_device(), get_device_name(), get_device_uuid(), get_device_total_memory(), get_available_gpu_memory(), get_device_capability() |
| Active | 분산 | get_torch_distributed_backend_str(), supports_distributed_device_id(), get_all_to_all_communicator_cls(), get_cpu_architecture() |
| Active | 디스패치와 모델 | dispatch_key (get_torch_library_dispatch_key()가 읽음), get_dispatch_key_name(), get_attn_backend_cls_str(), verify_model_arch(), optimize_vae() |
| Active | 실행 | get_compile_backend(), get_compile_options(), inference_mode(), seed_everything(), enable_dit_layerwise_offload_by_default() |
| Compatibility | 커뮤니케이터 | get_device_communicator_cls()는 기존 플랫폼 서브클래스를 위해 get_all_to_all_communicator_cls()가 사용하는 폴백으로 유지돼요. 새 OOT 플랫폼은 후자를 오버라이드해야 해요. |
패키지를 설치하고, 엔트리 포인트 메타데이터를 새로고침하기 위해 Python을 재시작하고, 명시적 선택을 검증하세요:
pip install -e my_platform_plugin/
SGLANG_DIFFUSION_PLATFORM_OVERRIDE=my_device python -c \
"from sglang.multimodal_gen.runtime.platforms import current_platform; print(current_platform)"
Diffusion 플랫폼 제한 사항 (Diffusion platform limitations)
- SRT와 diffusion은 별도의 플랫폼 클래스가 필요해요.
- 프로세스당 외부 플랫폼 하나만 활성화될 수 있어요. 여러 콜백이 활성화되면 런타임 선택기를 설정하세요.
get_all_to_all_communicator_cls()는all_to_all_4D()만 제어하며, 모든 collective, 그래프 캡처, 동기화 경로는 제어하지 않아요.get_device_communicator_cls()는 호환성을 위해 유지돼요.- 컴파일 설정은
build_torch_compile_kwargs()호출자에게만 영향을 줘요. 정적@torch.compile데코레이터와 다른 직접 컴파일 경로를 감사하세요. - 명시적 어텐션 선택기는
AttentionBackendEnum이름만 받아요. 커스텀 백엔드 반환은 아무것도 선택되지 않았을 때 동작하며, 이름으로 선택하려면 훅이나 다운스트림 패치가 필요해요. - 디바이스 패밀리 분기는 인터페이스 밖에 있어요. 각 지원 작업 부하에는 네이티브 폴백 또는 실행 가능한 미지원 기능 오류가 필요해요.
- diffusion
PlatformEnum.OOT은 외부 프로바이더를 식별해요. 내장 정체성 술어는 활성화된 모든 분기를 감사한 후에만 오버라이드하세요.
플러그인 유형 2: 일반 플러그인 (General Plugin)
설명 (Description)
일반 함수 플러그인은 커스텀 플랫폼 없이 SRT 또는 diffusion에 동작을 주입해요. 두 런타임은 별도의 엔트리 포인트 그룹과 훅 레지스트리를 가져요:
# SRT 플러그인
from sglang.srt.plugins.hook_registry import HookType, plugin_hook
# Diffusion 플러그인
from sglang.multimodal_gen.runtime.platforms.plugins import HookType, plugin_hook
SRT 플러그인은 sglang.srt.plugins 아래에, diffusion 플러그인은 sglang.multimodal_gen.plugins 아래에 등록하고, 위에서 일치하는 모듈에서 훅 API를 임포트하세요. 각 런타임은 자신의 레지스트리만 적용하므로 다른 런타임의 API로 등록된 훅은 절대 실행되지 않아요. 아래 예시는 SRT를 사용해요.
diffusion 훅 API는 플랫폼 발견과 함께 sglang.multimodal_gen.runtime.platforms.plugins에 있어요. sglang.multimodal_gen.plugins는 그 엔트리 포인트 그룹이며 Python 모듈 경로가 아니에요.
사용 사례:
- 관측성 (Observability): 모든 함수에 로깅, 메트릭, 트레이싱 추가
- 동작 수정 (Behavior modification): 함수 인자 또는 반환 값 수정
- 성능 프로파일링 (Performance profiling): 중요 함수에 타이밍 추가
- A/B 테스트: 런타임 시 구현 교체
퀵스타트 (Quick Start)
1. 최소 패키지 생성:
my_general_plugin/
├── pyproject.toml
└── my_general_plugin/
└── __init__.py # register() function
2. pyproject.toml:
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
[project]
name = "my-general-plugin"
version = "0.1.0"
[project.entry-points."sglang.srt.plugins"]
my_plugin = "my_general_plugin:register"
3. __init__.py — 훅 등록:
from sglang.srt.plugins.hook_registry import HookRegistry, HookType
def register():
"""load_plugins()가 호출하는 엔트리 포인트."""
HookRegistry.register(
"sglang.srt.managers.scheduler.Scheduler.__init__",
my_hook,
HookType.AROUND,
)
def my_hook(original_fn, self, *args, **kwargs):
result = original_fn(self, *args, **kwargs)
print(f"Scheduler initialized! gpu_id={self.gpu_id}")
return result
4. 설치 및 실행:
pip install -e my_general_plugin/
sglang serve --model-path <model> [options]
# 로그에서 "Scheduler initialized!" 찾기
훅 유형 (Hook Types)
HookRegistry는 네 가지 훅 유형을 지원해요:
| 훅 유형 | 시그니처 | 설명 |
|---|---|---|
| BEFORE | fn(\*args, \*\*kwargs) -> (args, kwargs) | None |
원본 전에 실행. 인자를 그대로 두려면 None 반환, 수정하려면 (args, kwargs) 반환. |
| AFTER | fn(result, \*args, \*\*kwargs) -> new\_result | None |
원본 후에 실행. 결과를 유지하려면 None, 교체하려면 새 값 반환. |
| AROUND | fn(original\_fn, \*args, \*\*kwargs) -> result |
원본을 감싸요. original\_fn을 직접 호출해야 해요. 실행에 대한 완전한 제어. |
| REPLACE | fn(\*args, \*\*kwargs) -> result 또는 class |
원본 함수나 클래스를 완전히 대체. 클래스 대상은 교체 클래스를 직접 전달 — setattr로 치환되어 isinstance()/issubclass() 의미를 보존. |
참고: 훅으로 클래스를 받는 것은
REPLACE만 가능해요.BEFORE/AFTER/AROUND에 클래스를 전달하면 등록 시TypeError가 발생해요.
등록 API (Registration API)
훅은 명령형 API 또는 데코레이터 API로 등록할 수 있어요:
# --- Imperative API ---
from sglang.srt.plugins.hook_registry import HookRegistry, HookType
def my_timer(original_fn, *args, **kwargs):
start = time.perf_counter()
result = original_fn(*args, **kwargs)
print(f"Elapsed: {time.perf_counter() - start:.3f}s")
return result
HookRegistry.register(
"sglang.srt.managers.scheduler.Scheduler.get_next_batch_to_run",
my_timer,
HookType.AROUND,
)
# --- Decorator API ---
from sglang.srt.plugins.hook_registry import plugin_hook, HookType
@plugin_hook(
"sglang.srt.managers.scheduler.Scheduler.get_next_batch_to_run",
type=HookType.AROUND,
)
def my_timer(original_fn, *args, **kwargs):
start = time.perf_counter()
result = original_fn(*args, **kwargs)
print(f"Elapsed: {time.perf_counter() - start:.3f}s")
return result
# --- Class replacement (REPLACE) ---
from sglang.srt.plugins.hook_registry import plugin_hook, HookType
from sglang.srt.managers.scheduler import Scheduler
@plugin_hook(
"sglang.srt.managers.scheduler.Scheduler",
type=HookType.REPLACE,
)
class MyScheduler(Scheduler):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
print("Enhanced scheduler initialized!")
훅 대상 해석 (Hook Target Resolution)
대상 경로는 정규화된 점 표기법을 사용해요. 두 형식 모두 지원돼요:
- 점 표기 (Dotted):
sglang.srt.managers.scheduler.Scheduler.__init__ - 엔트리 포인트 스타일:
sglang.srt.managers.scheduler:Scheduler.__init__(콜론을 점으로 처리)
일반 SRT 훅 대상 (Common SRT Hook Targets)
| 대상 | 설명 |
|---|---|
sglang.srt.server\_args.ServerArgs.add\_cli\_args |
커스텀 CLI 인자 추가 |
sglang.srt.server\_args.ServerArgs.**post\_init** |
파싱 후 ServerArgs 수정 |
sglang.srt.server\_args.ServerArgs.check\_server\_args |
검증 추가/완화 |
sglang.srt.managers.scheduler.Scheduler.**init** |
커스텀 스케줄러 상태 |
sglang.srt.managers.scheduler.Scheduler.get\_next\_batch\_to\_run |
커스텀 스케줄링 정책 |
sglang.srt.managers.scheduler.Scheduler.run\_batch |
프로파일링 / 검사 |
sglang.srt.managers.scheduler.Scheduler.process\_batch\_result |
커스텀 메트릭 |
sglang.srt.managers.tp\_worker.TpModelWorker.**init** |
커스텀 워커 상태 |
sglang.srt.managers.tp\_worker.TpModelWorker.forward\_batch\_generation |
포워드 패스 래핑 |
파일 참조 (File Reference)
| 파일 | 설명 |
|---|---|
sglang/srt/platforms/device\_mixin.py |
DeviceMixin 베이스 클래스와 SRT 플랫폼 정체성 유형 |
sglang/srt/platforms/interface.py |
SRTPlatform 베이스 클래스 (DeviceMixin 확장) |
sglang/srt/platforms/\_\_init\_\_.py |
current\_platform lazy singleton + 발견 로직 |
sglang/multimodal\_gen/runtime/platforms/interface.py |
Diffusion Platform 베이스 클래스 |
sglang/multimodal\_gen/runtime/platforms/\_\_init\_\_.py |
Diffusion current\_platform lazy singleton과 내장 폴백 순서 |
sglang/multimodal\_gen/runtime/platforms/plugins.py |
Diffusion 플러그인 등록, 명시적 훅 적용 단계, 훅 레지스트리 |
sglang/multimodal\_gen/runtime/managers/worker\_bootstrap.py |
런타임 훅 해석 전에 백엔드를 초기화하는 import-neutral 프로세스 사양과 spawn 대상 |
sglang/srt/plugins/\_\_init\_\_.py |
load\_plugins() + load\_plugins\_by\_group() |
sglang/srt/plugins/hook\_registry.py |
HookRegistry, HookType, plugin\_hook 데코레이터 |