본문으로 건너뛰기

[SGLang] 멀티 백엔드: OpenAI, Anthropic, VertexAI, LiteLLM 통합

들어가며

LLM 기반 애플리케이션을 만들 때 가장 먼저 부딪히는 문제는 프로바이더마다 API가 다르다는 것이다. OpenAI는 chat.completions.create, Anthropic은 messages.create, Google은 generate_content를 사용한다. SGLang은 이 차이를 BaseBackend 추상 클래스 하나로 통일한다. 프론트엔드 DSL 코드는 어떤 백엔드를 쓰든 동일하게 작성할 수 있다.

이 글에서는 python/sglang/lang/backend/ 디렉토리의 6개 파일을 분석하며, 각 백엔드가 어떻게 공통 인터페이스를 구현하는지 살펴본다.

백엔드 추상화 구조도

┌─────────────────────────────────────────────────────┐
│                   SGLang Frontend                   │
│            (StreamExecutor / DSL Program)            │
└──────────────────────┬──────────────────────────────┘
                       │ generate() / select() / generate_stream()
                       ▼
              ┌─────────────────┐
              │   BaseBackend   │  ← 추상 인터페이스
              └────────┬────────┘
        ┌──────┬───────┼───────┬──────────┐
        ▼      ▼       ▼       ▼          ▼
    ┌──────┐┌──────┐┌───────┐┌───────┐┌──────────────┐
    │OpenAI││Anthro││Vertex ││LiteLLM││RuntimeEndpnt │
    │ .py  ││pic.py││AI.py  ││ .py   ││    .py       │
    └──┬───┘└──┬───┘└──┬────┘└──┬────┘└──────┬───────┘
       │       │       │        │             │
       ▼       ▼       ▼        ▼             ▼
    OpenAI  Anthropic Google  100+개       SGLang SRT
    API     API       API     프로바이더   로컬 서버

모든 백엔드는 BaseBackend를 상속하고, 프론트엔드의 StreamExecutor는 백엔드 종류를 알 필요 없이 동일한 메서드를 호출한다.

핵심 코드 분석

BaseBackend: 공통 인터페이스

python/sglang/lang/backend/base_backend.py에 정의된 추상 클래스는 모든 백엔드가 구현해야 할 계약을 정의한다.

class BaseBackend:
    def __init__(self) -> None:
        self.support_concate_and_append = False
        self.chat_template = get_chat_template("default")

    def get_model_name(self):
        raise NotImplementedError()

    def generate(
        self,
        s: StreamExecutor,
        sampling_params: SglSamplingParams,
    ):
        raise NotImplementedError()

    def generate_stream(
        self,
        s: StreamExecutor,
        sampling_params: SglSamplingParams,
    ):
        raise NotImplementedError()

    def select(
        self,
        s: StreamExecutor,
        choices: List[str],
        temperature: float,
        choices_method: Optional[ChoicesSamplingMethod] = None,
    ) -> ChoicesDecision:
        raise NotImplementedError()

핵심 메서드 3개(generate, generate_stream, select)는 NotImplementedError를 발생시켜 하위 클래스에서 반드시 구현하도록 강제한다. 그 외 cache_prefix, fork_program, fill_image 등은 기본적으로 no-op이며, 지원하는 백엔드만 오버라이드한다.

support_concate_and_append 플래그는 KV cache 수준의 연산 지원 여부를 나타내며, RuntimeEndpointTrue로 설정한다.

OpenAI 백엔드: Chat/Completion 이중 모드

python/sglang/lang/backend/openai.pyOpenAI 클래스는 Chat 모델과 Instruct 모델을 모두 지원한다.

class OpenAI(BaseBackend):
    def __init__(
        self,
        model_name: str,
        is_chat_model: Optional[bool] = None,
        chat_template: Optional[ChatTemplate] = None,
        is_azure: bool = False,
        *args, **kwargs,
    ):
        super().__init__()
        if is_azure:
            self.client = openai.AzureOpenAI(*args, **kwargs)
        else:
            self.client = openai.OpenAI(*args, **kwargs)

        self.model_name = model_name
        self.tokenizer = tiktoken.encoding_for_model(model_name)
        self.logit_bias_int = create_logit_bias_int(self.tokenizer)

Azure OpenAI도 is_azure=True 한 줄로 전환할 수 있다. tiktoken을 사용해 tokenizer를 초기화하고, 정수 제약 생성(dtype=int) 시 사용할 logit bias를 미리 계산한다.

OpenAI 백엔드의 독특한 기능은 API speculative execution이다. 여러 sgl.gen 호출을 하나의 API 요청으로 묶어 보내고, 응답을 패턴 매칭으로 분리한다.

def spec_pattern_match(self, comp):
    for i, term in enumerate(self.spec_format):
        text = term["text"]
        if text != "":
            if comp.startswith(text):
                comp = comp[len(text):]
            else:
                return False
        else:
            pos = comp.find(term["stop"])
            if pos != -1:
                term["text"] = comp[:pos]
                comp = comp[pos:]
            else:
                if i == len(self.spec_format) - 1:
                    term["text"] = comp
                else:
                    return False
    return True

이 메서드는 speculative execution의 결과를 stop token 기준으로 각 변수에 분배한다. 매칭 실패 시 최대 spec_max_num_tries(기본 3회)까지 재시도한다.

Anthropic 백엔드: system 메시지 분리

python/sglang/lang/backend/anthropic.py의 구현은 가장 간결하다. Anthropic API가 system prompt를 별도 파라미터로 받기 때문에 메시지 전처리가 필요하다.

class Anthropic(BaseBackend):
    def __init__(self, model_name, *args, **kwargs):
        super().__init__()
        self.model_name = model_name
        self.chat_template = get_chat_template("claude")
        self.client = anthropic.Anthropic(*args, **kwargs)

    def generate(self, s: StreamExecutor, sampling_params: SglSamplingParams):
        if s.messages_:
            messages = s.messages_
        else:
            messages = [{"role": "user", "content": s.text_}]

        if messages and messages[0]["role"] == "system":
            system = messages.pop(0)["content"]
        else:
            system = ""

        ret = self.client.messages.create(
            model=self.model_name,
            system=system,
            messages=messages,
            **sampling_params.to_anthropic_kwargs(),
        )
        comp = ret.content[0].text
        return comp, {}

첫 번째 메시지가 system role이면 이를 추출하여 별도 system 파라미터로 전달한다. select 메서드는 구현하지 않아, Anthropic 백엔드에서는 sgl.select(choice selection)를 사용할 수 없다.

RuntimeEndpoint: SGLang 네이티브 서버 연결

python/sglang/lang/backend/runtime_endpoint.py는 SGLang 자체 서빙 엔진(SRT)과 HTTP로 통신하는 백엔드다. 다른 백엔드와 달리 KV cache 조작, logprob 반환, 정규식 제약 생성 등 풀 기능을 지원한다.

class RuntimeEndpoint(BaseBackend):
    def __init__(
        self,
        base_url: str,
        api_key: Optional[str] = None,
        verify: Optional[str] = None,
        chat_template_name: Optional[str] = None,
    ):
        super().__init__()
        self.support_concate_and_append = True
        self.base_url = base_url

        res = http_request(self.base_url + "/get_model_info",
                           api_key=self.api_key, verify=self.verify)
        self._assert_success(res)
        self.model_info = res.json()

초기화 시 서버의 /get_model_info 엔드포인트를 호출하여 모델 정보를 가져오고, 모델 경로 기반으로 chat template을 자동 매칭한다.

select 구현에서는 logprob 기반 선택을 지원한다.

def select(self, s, choices, temperature, choices_method):
    assert temperature <= 1e-5
    # 1. 공통 prefix 캐싱
    data = {"text": s.text_, "sampling_params": {"max_new_tokens": 0}}
    obj = self._generate_http_request(s, data)
    prompt_len = obj["meta_info"]["prompt_tokens"]

    # 2. 각 choice의 logprob 계산
    data = {
        "text": [s.text_ + c for c in choices],
        "sampling_params": {"max_new_tokens": 0, "temperature": 0},
        "return_logprob": True,
        "logprob_start_len": max(prompt_len - 2, 0),
    }
    obj = self._generate_http_request(s, data)
    normalized_prompt_logprobs = [
        compute_normalized_prompt_logprobs(r["meta_info"]["input_token_logprobs"])
        for r in obj
    ]

RuntimeEndpoint는 또한 Runtime 래퍼 클래스와 함께 사용된다. Runtime은 SGLang 서버를 별도 프로세스로 자동 실행하고, health check를 거쳐 RuntimeEndpoint를 생성한다.

class Runtime:
    def __init__(self, log_level="error", launch_timeout=300.0, *args, **kwargs):
        from sglang.srt.entrypoints.http_server import launch_server
        from sglang.srt.server_args import ServerArgs

        self.server_args = ServerArgs(*args, log_level=log_level, **kwargs)
        # ...
        proc = ctx.Process(target=launch_server, args=(self.server_args,))
        proc.start()
        self.pid = proc.pid
        atexit.register(self.shutdown)

atexit.register(self.shutdown)으로 프로그램 종료 시 서버 프로세스를 자동 정리한다.

LiteLLM 백엔드: 100개 이상 프로바이더 통합

python/sglang/lang/backend/litellm.pyLiteLLM 클래스는 LiteLLM 라이브러리를 래핑하여 100개 이상의 LLM 프로바이더를 SGLang에서 사용할 수 있게 한다.

class LiteLLM(BaseBackend):
    def __init__(self, model_name, chat_template=None, api_key=None,
                 organization=None, base_url=None, timeout=600,
                 max_retries=litellm.num_retries, default_headers=None):
        super().__init__()
        self.model_name = model_name
        self.chat_template = chat_template or get_chat_template_by_model_path(model_name)
        self.client_params = {
            "api_key": api_key, "organization": organization,
            "base_url": base_url, "timeout": timeout,
            "max_retries": max_retries, "default_headers": default_headers,
        }

generategenerate_stream만 구현하며, select는 지원하지 않는다. litellm.completionclient_params를 통째로 전달하는 단순한 구조다.

백엔드 간 기능 비교 테이블

기능 OpenAI Anthropic VertexAI LiteLLM RuntimeEndpoint
generate O O O O O
generate_stream O O O O O
select (choice) O (Instruct만) X X X O
Chat Template 자동 매칭 claude 고정 default 고정 자동 매칭 자동 매칭
이미지 입력 X X O X O
dtype 제약 생성 O (int, str) X X X O (int, float, str, bool)
Speculative Exec O X X X X
KV Cache 조작 X X X X O
Azure 지원 O (is_azure) X X X X
Token Usage 추적 O X X X X
Logprob 반환 X X X X O
에러 재시도 O (3회) X X O (설정 가능) X

RuntimeEndpoint가 가장 많은 기능을 지원하고, 외부 API 백엔드들은 각 프로바이더의 API 제약에 따라 기능이 제한된다.

관련 포스트

  • SGLang Chat Template 관리: Jinja 템플릿과 모델별 대화 포맷 (본 시리즈 다음 글)

참고

댓글

관련 포스트

SGLang 의 다른글