Deep Implementation

Deploy SGLang, benchmark structured output

SGLang promises faster structured output through RadixAttention and compressed finite state machines. I deployed it, measured the overhead of JSON-constrained generation, and compared it against unconstrained output on the same model.

Yesterday on day 46, I deployed vLLM and mapped the TTFT-throughput tradeoff curve. Today I wanted to do the same with SGLang, but with a twist: structured output. In production, many LLM calls need to return valid JSON matching a schema. The question is how much performance that constraint costs, and whether SGLang's approach to constrained decoding changes the answer.

What makes SGLang different

SGLang (from the LMSYS team at UC Berkeley) is built on a few ideas that differentiate it from vLLM:

Deploying SGLang

The setup is similar to vLLM. SGLang also exposes an OpenAI-compatible API:

# Install
pip install "sglang[all]"

# Launch the server
python -m sglang.launch_server \
    --model-path meta-llama/Llama-3.1-8B-Instruct \
    --dtype float16 \
    --context-length 4096 \
    --mem-fraction-static 0.88 \
    --port 8001

The --mem-fraction-static flag is SGLang's equivalent of vLLM's --gpu-memory-utilization. It controls how much VRAM to pre-allocate for the KV cache pool. I used 0.88 to leave similar headroom as my vLLM deployment.

On startup, SGLang logs the radix cache configuration and the number of available token slots. It also reports the maximum number of concurrent requests, which depends on the model size and available KV cache memory.

Baseline: unconstrained generation

First, I ran the same benchmark as yesterday against SGLang to establish a baseline. Same prompt, same output length, same concurrency sweep:

For unconstrained generation, SGLang and vLLM are in the same ballpark. The differences are within measurement noise for most configurations. Both engines are mature and well-optimized for the standard serving workload.

Structured output: the JSON schema benchmark

Now the interesting part. I defined a JSON schema for a structured extraction task:

schema = {
    "type": "object",
    "properties": {
        "name": {"type": "string"},
        "age": {"type": "integer"},
        "skills": {
            "type": "array",
            "items": {"type": "string"}
        },
        "experience_years": {"type": "number"},
        "is_senior": {"type": "boolean"}
    },
    "required": ["name", "age", "skills", "experience_years", "is_senior"]
}

The request uses the response_format parameter to enforce JSON output:

payload = {
    "model": "meta-llama/Llama-3.1-8B-Instruct",
    "messages": [
        {"role": "system", "content": "Extract structured data from the text."},
        {"role": "user", "content": "John is a 35-year-old senior engineer ..."}
    ],
    "response_format": {
        "type": "json_schema",
        "json_schema": {"name": "person", "schema": schema}
    },
    "max_tokens": 256,
}

Measuring the structured output overhead

I ran 200 requests with and without the JSON schema constraint at concurrency=16 and compared:

The per-token decode speed dropped by about 9% with constrained decoding. But total request time was actually lower because the structured output produces fewer tokens. JSON is dense: a structured response has less fluff than a free-form answer with explanation.

The compressed FSM advantage

SGLang's compressed FSM means that deterministic sequences like {"name": " are generated in a single step without running the model for each character. The FSM knows these tokens are forced by the grammar, so it emits them directly. This is a meaningful speedup for schemas with long key names or fixed structures. In my tests, about 15-20% of output tokens were grammar-forced and skipped model inference entirely.

Where constrained decoding gets expensive

The overhead is not constant. It depends on the schema complexity:

The key factor is how many tokens in the vocabulary are valid at each decode step. For unconstrained generation, all tokens are valid (or at least all tokens above the sampling threshold). For constrained generation, the FSM mask can eliminate 99%+ of the vocabulary. Computing and applying this mask takes CPU time, and if it is not overlapped with GPU computation, it becomes the bottleneck.

RadixAttention and prefix caching

One benefit of SGLang's RadixAttention became clear during the structured output benchmark: system prompt reuse. All 200 requests shared the same system prompt ("Extract structured data from the text."). SGLang cached the KV values for this prefix and reused them across all requests.

I verified this by checking the server logs, which report cache hit rates. With the shared system prompt, the radix cache hit rate was about 40% of input tokens across the benchmark. This translates directly to lower TTFT because those tokens do not need to be re-prefilled.

For workloads with long, shared system prompts (common in production, where system prompts can be hundreds of tokens), this is a significant advantage. The longer the shared prefix relative to the unique user message, the bigger the win.

SGLang vs. vLLM: when to use which

After benchmarking both, here is my working mental model:

Structured output is not free, but it is cheaper than most people think. The real cost is not the constraint itself. It is the tokens you waste when you do not use constraints and the model rambles, apologizes, or wraps your JSON in markdown code fences.

With both serving engines benchmarked, the next step is to compare them against compiled approaches. On day 48, I will put TensorRT-LLM up against eager PyTorch and see what compilation buys you.