SGLang: Serving LLM yang Cepat dan Model Pemrograman untuk Generasi Terstruktur
SGLang adalah dua hal dalam satu paket: sebuah runtime serving berthroughput tinggi dan sebuah bahasa frontend untuk menulis program LLM yang terstruktur dan multi-langkah. Runtime-nya menarik karena RadixAttention, yang secara otomatis menggunakan ulang key-value cache antar request yang berbagi prefix, dan frontend-nya memungkinkan Anda mengekspresikan fork, output terbatas, dan alur kendali tanpa menjahit prompt secara manual. Tutorial ini membahas kedua pilar tersebut dengan satu contoh ekstraksi-dan-penalaran yang koheren dan dapat Anda adaptasi untuk beban kerja nyata.
Apa Itu SGLang
Memisahkan kedua lapisan ini sejak awal akan membantu, karena kebanyakan kebingungan tentang SGLang berasal dari mencampuradukkan keduanya.
Runtime adalah server yang Anda jalankan sekali lalu Anda query melalui HTTP. Ia menangani batching, penjadwalan, dan pengelolaan KV-cache di GPU. Anda bisa menggunakannya lewat API yang kompatibel dengan OpenAI tanpa pernah menyentuh bahasa frontend sama sekali. Bahasa frontend adalah DSL Python (tertanam sebagai decorator dan fungsi pembantu) untuk mendeskripsikan program generasi. Ia mengompilasi program Anda menjadi serangkaian panggilan terhadap sebuah backend, yang bisa berupa runtime SGLang lokal, atau penyedia eksternal seperti OpenAI. Di frontend inilah fitur seperti fork paralel dan decoding terbatas menjadi nyaman digunakan.Dua pilar
Pilar 1 — runtime dengan RadixAttention. Continuous batching menjaga GPU tetap sibuk dengan menambah dan mengeluarkan request dari batch yang berjalan pada level token, alih-alih menunggu seluruh batch selesai. RadixAttention menambahkan penggunaan ulang prefix-cache secara otomatis: runtime memelihara sebuah radix tree berisi urutan token yang pernah dilihat beserta KV cache-nya, sehingga ketika sebuah request baru berbagi prefix (prompt sistem, blok few-shot, dokumen bersama) runtime melewati komputasi ulangnya. Pilar 2 — bahasa frontend. Alih-alih membangun satu string besar lalu mem-parsing respons, Anda menyusun sebuah program: menambahkan pesan, memanggilsgl.gen untuk mengisi slot, bercabang dengan s.fork, dan membatasi output dengan pilihan, regex, atau skema JSON. Frontend melacak state dan dapat menjalankan cabang-cabang independen secara paralel.
Bedanya dengan vLLM
Jika Anda pernah memakai vLLM, runtime ini akan terasa akrab: keduanya melakukan continuous batching, tensor parallelism, dan menyediakan API yang kompatibel dengan OpenAI. Perbedaan praktisnya adalah RadixAttention pada SGLang membuat beban kerja dengan prefix bersama (prompting few-shot, agen yang memakai ulang prompt sistem panjang, percabangan gaya tree-of-thought) menjadi lebih murah secara nyata, dan SGLang menyertakan bahasa frontend untuk mengorkestrasi program multi-langkah. vLLM fokus menjadi mesin serving; SGLang adalah mesin serving plus sebuah model pemrograman. Kami tidak akan menjelaskan ulang PagedAttention di sini.
Instalasi dan Catatan Perangkat Keras
Instalasi paling sederhana menarik runtime, frontend, dan dependensi umum.
# Disarankan: instal semuanya (runtime + frontend + ekstra)
pip install "sglang[all]"
Hanya frontend, jika Anda hanya ingin DSL terhadap backend remote
pip install "sglang[openai]"
Verifikasi
python -c "import sglang as sgl; print(sgl.version)"
Catatan perangkat keras yang perlu diingat:
- Runtime menargetkan GPU NVIDIA dengan CUDA terkini. Ampere (A100) dan yang lebih baru (L4, L40S, H100) adalah jalur mulus; sebagian kernel mengasumsikan sm80 atau lebih tinggi.
- Memori menentukan seberapa banyak KV cache yang bisa Anda tampung dan dengan demikian seberapa banyak RadixAttention dapat menggunakan ulang. Lebih banyak VRAM bebas berarti radix tree lebih besar dan tingkat cache-hit lebih tinggi.
- Dukungan AMD ROCm ada tetapi kurang teruji; periksa matriks dukungan terkini sebelum berkomitmen.
- Untuk multi-GPU, tensor parallelism (
--tp) memecah bobot model antar GPU pada satu node.
# Pemeriksaan kewarasan lingkungan secara cepat
python -c "import torch; print('cuda', torch.cuda.isavailable(), 'gpus', torch.cuda.devicecount())"
nvidia-smi --query-gpu=name,memory.total,memory.used --format=csv
Menjalankan Server
Server cukup satu perintah. Mulailah dengan model kecil untuk memastikan instalasi, lalu naikkan skalanya.
python -m sglang.launchserver \
--model-path meta-llama/Llama-3.1-8B-Instruct \
--port 30000 \
--host 0.0.0.0
Flag yang umum Anda butuhkan:
# Multi-GPU dengan tensor parallelism (4 GPU pada satu node)
python -m sglang.launchserver \
--model-path meta-llama/Llama-3.1-70B-Instruct \
--tp 4 \
--port 30000
Kuantisasi agar model lebih besar muat di GPU yang lebih kecil
python -m sglang.launchserver \
--model-path meta-llama/Llama-3.1-8B-Instruct \
--quantization fp8 \
--port 30000
Atur panjang konteks dan anggaran memori KV-cache
python -m sglang.launchserver \
--model-path meta-llama/Llama-3.1-8B-Instruct \
--context-length 8192 \
--mem-fraction-static 0.85 \
--port 30000
Beberapa catatan tentang flag ini:
--tp Nmembutuhkan N GPU yang terlihat; kombinasikan denganCUDAVISIBLEDEVICESuntuk menambatkan perangkat tertentu.--quantizationmenerima nilai sepertifp8,awq, dangptq. Bobot terkuantisasi mengurangi memori dan dapat menaikkan throughput dengan biaya kualitas yang biasanya kecil.--context-lengthmenetapkan panjang urutan maksimum; menaikkannya memperbesar jejak KV-cache per request.--mem-fraction-staticmemesan sebagian VRAM untuk pool KV statis. Turunkan jika Anda mengalami out-of-memory saat startup; naikkan untuk memberi RadixAttention lebih banyak ruang cache.
Setelah berjalan, pemeriksaan kesehatan memastikan kesiapan:
curl http://localhost:30000/health
Query lewat API yang Kompatibel dengan OpenAI
Server mengekspos /v1/chat/completions dan /v1/completions. Klien OpenAI apa pun bisa dipakai; cukup arahkan ke base URL lokal.
curl http://localhost:30000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "meta-llama/Llama-3.1-8B-Instruct",
"messages": [
{"role": "system", "content": "Anda asisten yang ringkas."},
{"role": "user", "content": "Sebutkan tiga penyebab GPU OOM saat serving."}
],
"temperature": 0.2,
"maxtokens": 256
}'
Endpoint yang sama dari klien Python OpenAI:
from openai import OpenAI
client = OpenAI(baseurl="http://localhost:30000/v1", apikey="EMPTY")
resp = client.chat.completions.create(
model="meta-llama/Llama-3.1-8B-Instruct",
messages=[
{"role": "system", "content": "Anda asisten yang ringkas."},
{"role": "user", "content": "Ringkas RadixAttention dalam satu kalimat."},
],
temperature=0.2,
maxtokens=128,
)
print(resp.choices[0].message.content)
Streaming bekerja sama seperti pada OpenAI:
stream = client.chat.completions.create(
model="meta-llama/Llama-3.1-8B-Instruct",
messages=[{"role": "user", "content": "Jelaskan continuous batching secara singkat."}],
stream=True,
)
for chunk in stream:
delta = chunk.choices[0].delta.content
if delta:
print(delta, end="", flush=True)
Endpoint Generate Native dan Klien SGLang
Selain permukaan OpenAI, server memiliki endpoint /generate native yang mengekspos parameter sampling khas SGLang secara langsung.
curl http://localhost:30000/generate \
-H "Content-Type: application/json" \
-d '{
"text": "Tiga pilar observability adalah",
"samplingparams": {"temperature": 0.0, "maxnewtokens": 64}
}'
Untuk memakai bahasa frontend terhadap runtime ini, arahkan klien SGLang ke endpoint server.
import sglang as sgl
Ikat frontend ke runtime lokal
backend = sgl.RuntimeEndpoint("http://localhost:30000")
sgl.setdefaultbackend(backend)
Mulai dari sini, fungsi-fungsi frontend di bawah akan dieksekusi terhadap runtime tersebut.
Bahasa Frontend Secara Mendalam
Frontend mengubah program generasi menjadi fungsi Python biasa. Blok bangunan utamanya:
@sgl.functionmendekorasi sebuah fungsi yang argumen pertamanya adalah state programs.s += ...menambahkan teks atau peran pesan ke state.sgl.user(...),sgl.assistant(...),sgl.system(...)menandai peran chat.sgl.gen("name", ...)mengisi slot bernama dengan menghasilkan token; hasilnya dibaca kembali lewats["name"].s.fork(n)membuat salinan paralel dari state untuk cabang-cabang independen.
Contoh minimal:
import sglang as sgl
@sgl.function
def explain(s, topic):
s += sgl.system("Anda penulis teknis yang presisi.")
s += sgl.user(f"Jelaskan {topic} dalam dua kalimat.")
s += sgl.assistant(sgl.gen("answer", maxtokens=120, temperature=0.2))
state = explain.run(topic="RadixAttention")
print(state["answer"])
Output terbatas: pilihan, regex, dan skema JSON
Decoding terbatas membatasi token yang boleh dipancarkan model, sehingga output cukup andal untuk di-parse.
@sgl.function
def classify(s, ticket):
s += sgl.system("Klasifikasikan tiket dukungan.")
s += sgl.user(ticket)
# Model harus memilih tepat satu dari label berikut
s += sgl.assistant(sgl.gen("label", choices=["billing", "bug", "feature", "other"]))
state = classify.run(ticket="Aplikasi crash saat saya mengunggah PDF.")
print(state["label"]) # salah satu dari empat pilihan, dijamin
Batasan regex mengunci bentuk permukaan sebuah nilai:
@sgl.function
def extractphone(s, text):
s += sgl.user(f"Ekstrak nomor telepon dari: {text}")
s += sgl.assistant(
sgl.gen("phone", regex=r"\+?\d{1,3}[- ]?\d{3,4}[- ]?\d{3,4}")
)
Untuk record terstruktur, batasi ke skema JSON agar output langsung ter-parse pada percobaan pertama:
import json
productschema = json.dumps({
"type": "object",
"properties": {
"name": {"type": "string"},
"price": {"type": "number"},
"instock": {"type": "boolean"},
},
"required": ["name", "price", "instock"],
})
@sgl.function
def torecord(s, description):
s += sgl.user(f"Konversi ke JSON: {description}")
s += sgl.assistant(sgl.gen("record", maxtokens=128, jsonschema=productschema))
state = torecord.run(description="Mug Helix berharga 12.50 dan tersedia.")
record = json.loads(state["record"])
print(record["name"], record["price"], record["instock"])
Fork dan paralelisme
s.fork menjalankan cabang-cabang independen yang dapat dieksekusi runtime secara bersamaan. Karena semua cabang berbagi prefix yang sama, RadixAttention menghitung prefix itu sekali dan menggunakan ulang KV cache-nya di seluruh fork.
@sgl.function
def multiperspective(s, question):
s += sgl.system("Jawab dengan hati-hati.")
s += sgl.user(question)
forks = s.fork(3)
for f in forks:
f += sgl.assistant(sgl.gen("view", maxtokens=100, temperature=0.8))
# Gabungkan tiga draf independen kembali ke parent
drafts = [f["view"] for f in forks]
s += sgl.user("Berikut tiga draf:\n" + "\n---\n".join(drafts))
s += sgl.user("Tulis satu jawaban yang dikonsolidasikan.")
s += sgl.assistant(sgl.gen("final", maxtokens=160))
state = multiperspective.run(question="Bagaimana cara menentukan ukuran KV cache untuk layanan chat?")
print(state["final"])
Contoh Koheren: Ekstraksi Terstruktur Multi-Langkah
Mari satukan semua bagian menjadi satu program yang menyerupai tugas agentic nyata: membaca laporan insiden, memutuskan apakah perlu eskalasi, mengekstrak field terstruktur, dan menghasilkan catatan remediasi. Ini adalah pola branch-solve-merge dengan prefix bersama.
import json
import sglang as sgl
sgl.setdefaultbackend(sgl.RuntimeEndpoint("http://localhost:30000"))
incidentschema = json.dumps({
"type": "object",
"properties": {
"service": {"type": "string"},
"severity": {"type": "string", "enum": ["low", "medium", "high"]},
"rootcause": {"type": "string"},
},
"required": ["service", "severity", "rootcause"],
})
@sgl.function
def triageincident(s, report):
# Prefix bersama: instruksi sistem dipakai ulang di setiap request.
# RadixAttention meng-cache ini sekali saja.
s += sgl.system(
"Anda asisten SRE. Bersikaplah presisi dan hindari spekulasi."
)
s += sgl.user(f"Laporan insiden:\n{report}")
# Langkah 1: keputusan routing yang terbatas
s += sgl.assistant(
"Perlu eskalasi: "
- sgl.gen("escalate", choices=["yes", "no"])
)
# Langkah 2: ekstrak field terstruktur dengan skema JSON
s += sgl.user("Ekstrak field terstruktur sebagai JSON.")
s += sgl.assistant(sgl.gen("fields", maxtokens=200, jsonschema=incidentschema))
# Langkah 3: bercabang hanya ketika eskalasi diperlukan
if s["escalate"] == "yes":
s += sgl.user("Buat catatan remediasi singkat untuk engineer on-call.")
s += sgl.assistant(sgl.gen("note", maxtokens=180))
reporttext = (
"Pada 02:14 checkout-api mengembalikan 500 selama ~8 menit. "
"Log error menunjukkan kehabisan connection pool ke DB orders."
)
state = triageincident.run(report=reporttext)
print("escalate:", state["escalate"])
print("fields:", json.loads(state["fields"]))
if state["escalate"] == "yes":
print("note:", state["note"])
Yang membuatnya efisien: prompt sistem dan laporan adalah prefix bersama. Jika Anda menjalankan program ini atas sekumpulan insiden yang berbagi instruksi yang sama, token instruksi dihitung sekali lalu dipakai ulang. Slot escalate yang terbatas menjamin keluaran yes/no yang dapat di-parse, dan slot skema JSON menjamin field ter-parse tanpa pembersihan string defensif.
Mengapa RadixAttention Membantu Beban Kerja Prefix Bersama
Biaya inferensi LLM didominasi oleh atensi terhadap KV cache. Ketika banyak request berbagi token awal — prompt sistem panjang, blok few-shot, dokumen hasil retrieval — menghitung ulang token-token itu untuk setiap request adalah pekerjaan yang terbuang. RadixAttention menyimpan KV cache berkunci urutan token dalam radix tree, sehingga request baru yang cocok dengan prefix yang sudah ada menggunakan ulang KV ter-cache dan hanya menghitung sufiks barunya.
Inilah sebabnya prompting few-shot dan loop agen paling diuntungkan: contoh few-shot atau instruksi tetap milik agen membentuk prefix panjang yang stabil dan dipakai ulang pada setiap panggilan. fork pada frontend membuatnya eksplisit — cabang-cabang berbagi prefix secara konstruktif — tetapi manfaatnya juga berlaku untuk request API biasa yang kebetulan berbagi teks awal.
Membatch Banyak Request dengan runbatch
Ketika Anda punya banyak input untuk program yang sama, runbatch mengirimkannya bersama-sama sehingga runtime dapat menjadwalkan dan meng-cache lintas input.
reports = [
{"report": "Latensi DB melonjak ke 4d selama jendela backup malam."},
{"report": "Frontend 404 setelah deploy terakhir; path aset berubah."},
{"report": "CPU layanan auth 95% di bawah trafik normal."},
]
states = triageincident.runbatch(
reports,
progressbar=True,
)
for st in states:
print(st["escalate"], json.loads(st["fields"])["severity"])
Karena setiap item berbagi prompt sistem yang sama, RadixAttention memakai ulang prefix itu di seluruh batch, dan continuous batching menjaga GPU tetap jenuh.
Multimodal dan Embeddings (Ringkas)
Model vision. SGLang melayani model vision-language (seperti model gaya Llava dan gaya Qwen-VL) melalui server yang sama. Jalankan dengan path model multimodal, lalu lewatkan gambar dalam konten pesan chat memakai format image-URL OpenAI. Frontend mengekspossgl.image(...) untuk menyisipkan gambar ke dalam program.
@sgl.function
def describe(s, imagepath):
s += sgl.user(sgl.image(imagepath) + "Deskripsikan gambar ini dalam satu kalimat.")
s += sgl.assistant(sgl.gen("caption", maxtokens=64))
Embeddings. Jalankan server dengan --is-embedding untuk model embedding, lalu panggil endpoint /v1/embeddings (kompatibel dengan OpenAI) untuk memperoleh vektor bagi retrieval atau clustering.
python -m sglang.launchserver \
--model-path BAAI/bge-large-en-v1.5 \
--is-embedding --port 30000
Benchmarking dan Catatan Berimbang SGLang-vs-vLLM
SGLang menyertakan harness benchmarking. Jalankan terhadap server yang aktif untuk mengukur throughput dan latensi pada distribusi request yang dipilih.
python -m sglang.benchserving \
--backend sglang \
--host 127.0.0.1 --port 30000 \
--dataset-name random \
--num-prompts 1000 \
--random-input-len 1024 \
--random-output-len 256
Baca output sebagai distribusi, bukan angka tunggal: perhatikan throughput request, throughput token output, serta persentil time-to-first-token dan latensi per token (p50/p99).
Pandangan berimbang SGLang vs vLLM:
- Untuk beban kerja dengan banyak pembagian prefix — prompting few-shot, agen dengan instruksi tetap yang panjang, percabangan gaya tree — RadixAttention SGLang sering menang pada throughput dan latensi karena melewati komputasi ulang.
- Untuk beban kerja yang tiap request-nya pada dasarnya unik dengan sedikit prefix bersama, kedua runtime lebih berdekatan; keduanya mengandalkan continuous batching dan kernel atensi yang efisien, dan pemenangnya bergantung pada model, perangkat keras, dan versi.
- Jika Anda butuh model pemrograman untuk generasi multi-langkah, terbatas, atau paralel, frontend SGLang adalah pembeda nyata dan mengurangi kode perekat aplikasi.
- Benchmark pada trafik Anda sendiri. Angka sintetis jarang berpindah mulus ke bentuk prompt produksi.
Praktik Terbaik
- Letakkan teks yang stabil dan dapat dipakai ulang (prompt sistem, contoh few-shot, dokumen bersama) di depan setiap prompt agar RadixAttention dapat meng-cache-nya. Konten variabel diletakkan paling akhir.
- Gunakan decoding terbatas (
choices,regex,jsonschema) setiap kali Anda berniat mem-parsing output. Ini menghilangkan satu kelas bug pembersihan string yang rapuh. - Utamakan
runbatchuntuk beban kerja offline atau massal; ia memberi penjadwal dan cache materi terbanyak untuk dikerjakan. - Setel
--mem-fraction-staticsecara sengaja. Terlalu rendah membuang kapasitas cache; terlalu tinggi berisiko OOM pada konteks panjang. Pantaunvidia-smisaat uji beban. - Jaga struktur program frontend tetap deterministik. Bercabanglah pada output terbatas (seperti yes/no di atas) alih-alih teks bebas, agar alur kendali tetap dapat diprediksi.
- Pisahkan tanggung jawab: gunakan API kompatibel OpenAI untuk integrasi chat sederhana, dan beralih ke bahasa frontend hanya ketika Anda benar-benar butuh fork, batasan, atau orkestrasi multi-langkah.
- Pin versi SGLang di produksi dan jalankan ulang
benchservingsetelah upgrade; perubahan kernel dan penjadwal dapat menggeser performa.
Kesimpulan dan Poin Utama
SGLang paling baik dipahami sebagai sebuah runtime serving dan model pemrograman yang kebetulan dirilis bersama. RadixAttention pada runtime mengubah prefix bersama dari biaya berulang menjadi biaya sekali bayar, yang persis bentuk beban kerja few-shot dan agentic. Bahasa frontend memungkinkan Anda menulis beban kerja itu sebagai program Python yang mudah dibaca dengan fork, output terbatas, dan alur kendali multi-langkah, alih-alih mengelola string prompt secara manual.
Poin utama:
- Dua pilar: runtime continuous-batching dengan prefix caching RadixAttention, dan DSL frontend untuk generasi terstruktur.
- Instal dengan
pip install "sglang[all]"; jalankan denganpython -m sglang.launchserver; query lewat API kompatibel OpenAI atau endpoint/generatenative. @sgl.function,sgl.gen, pembantu peran,s.fork, dan decoding terbatas pada frontend membuat program multi-langkah ringkas dan andal.- RadixAttention paling terbayar ketika request berbagi prefix panjang; letakkan teks stabil paling depan untuk memaksimalkan cache hit.
- Gunakan
runbatchuntuk pekerjaan massal dansglang.benchservinguntuk memvalidasi performa pada trafik Anda sendiri sebelum menyimpulkan SGLang versus vLLM.