# Native SGLang on Linux GPU environments
Use this path when Linux and the NVIDIA GPU are already available inside the
current environment, but there is no Docker daemon. The main example is an
ordinary AutoDL container instance. AutoDL documents that these instances are
themselves Docker containers and do not support running Docker inside; its
bare-metal offering is a different product:
.
If the provider lets you select a custom OCI image while creating the instance,
use the [GPU cloud image guide](gpu-platforms.md) instead. If you control a
Docker daemon with NVIDIA GPU passthrough, use the
[Docker GPU guide](linux-nvidia.md). The [installation chooser](README.md)
explains the distinction.
FrontierAgent and SGLang are separate native processes connected through an
OpenAI-compatible loopback endpoint:
```text
provider container or Linux environment
├── SGLang http://127.0.0.1:30000/v1
└── FrontierAgent native calls the local endpoint
```
This does not create another OS sandbox. On a managed service, the provider's
outer container remains the host boundary; approved FrontierAgent commands can
access files available to the current user inside that instance.
## Prerequisites
- Linux with `nvidia-smi` able to enumerate the assigned GPU;
- a Python environment with a Qwen3.5-compatible SGLang installation;
- the system `libnuma` runtime (`libnuma1` on Debian/Ubuntu), required by
prebuilt SGLang GPU kernels;
- Python 3.12 and `uv` for FrontierAgent;
- enough persistent disk for the SGLang environment, image-independent runtime
dependencies, and model weights.
Prefer a provider image that already includes a compatible SGLang/CUDA/PyTorch
stack. If SGLang must be installed, follow its current native installation guide
for the CUDA version exposed by the provider instead of replacing the provider's
driver: . Keep SGLang in a
separate environment from FrontierAgent and point `SGLANG_PYTHON` at that
environment's Python executable.
## Automated quick start
For a Linux x86_64 host or provider container where `nvidia-smi` already works,
the repository helper automates the conservative path:
```bash
# Add --install-system-deps if the image does not already provide libnuma.
./scripts/run-linux-gpu.sh --install-system-deps --setup-only
./scripts/run-linux-gpu.sh smoke
./scripts/run-linux-gpu.sh tui -- --cwd /path/to/project
```
On first use it:
1. inventories the GPU and reads the host driver from `nvidia-smi`;
2. selects the newest compatible reviewed track from
`config/sglang/compatibility.json`;
3. installs the exact SGLang pin into `.venv-sglang`, separate from
FrontierAgent's `.venv`;
4. creates `.env.sglang` from the runnable 0.8B smoke profile when absent;
5. runs the native doctor before starting a model.
The script does not install or replace the NVIDIA driver or a system CUDA
toolkit. SGLang/PyTorch wheels supply CUDA userspace; the host driver determines
which reviewed track is safe. `nvcc --version` is therefore not used to choose
the wheel. System packages are modified only when
`--install-system-deps` is explicit.
An existing `.env.sglang` is preserved. Select a candidate only while creating
a new file, for example `--profile 5090`; 35B candidates still require an
explicit checkpoint and physical-GPU certification. If a profile sets
`SGLANG_PYTHON`, the helper treats that environment as operator-managed and
does not install packages into it. Lifecycle-only commands (`status`, `logs`,
and `down`) never run installers, so recovery remains available offline.
### Select SGLang from the NVIDIA driver
Do not install an unpinned latest SGLang before checking the host driver. The
Python wheels carry their own CUDA/PyTorch user-space stack, while the kernel
driver still comes from the host. For the Qwen3.5 profile, use these
conservative native tracks. They are also stored in
[`config/sglang/compatibility.json`](../../config/sglang/compatibility.json),
which the native doctor reads:
| NVIDIA Linux driver | CUDA wheel family | SGLang pin | Qwen3.5 status |
|---|---|---|---|
| below 525 | unsupported | none | upgrade the host/provider image |
| 580 or newer | CUDA 13.x | `0.5.17` | **official recommended default**; verified on a 32 GB RTX 5090 |
| 525-579 | CUDA 12.x | `0.5.10.post1` | compatibility hint only; best-effort native fallback observed on a 140 GB L20X and a 32 GB RTX 5090 with driver 570 |
Choose `0.5.17` whenever the driver is 580 or newer; this cu13 path is the
official recommendation. The `0.5.10.post1` pin remains only as a compatibility
hint for hosts stuck below that boundary, not as a production-default promise:
it cannot use
`--language-only`, so the unused vision encoder stays resident. The native
cu12 path has passed a 32 GB RTX 5090 chain test when paired with a CUDA 12.9
JIT toolkit; this does not certify the combined cu12 Docker image.
This boundary follows NVIDIA's
[CUDA minor-version compatibility range](https://docs.nvidia.com/deploy/cuda-compatibility/minor-version-compatibility.html):
CUDA 12.x supports drivers 525 through 579, while CUDA 13.x requires driver 580
or newer. The [SGLang 0.5.10 release](https://github.com/sgl-project/sglang/releases/tag/v0.5.10)
added Qwen3.5 and its
[patched PyPI package](https://pypi.org/project/sglang/0.5.10.post1/) uses CUDA
12.9/PyTorch 2.9.1. SGLang 0.5.11 moved its default packages and images to CUDA
13/PyTorch 2.11; later releases may additionally publish explicitly named
CUDA 12 builds. Recheck the exact wheel/image metadata when advancing either
track instead of inferring CUDA solely from the SGLang version.
For example, a driver 595 container should use:
```bash
nvidia-smi --query-gpu=driver_version --format=csv,noheader
python3 -m venv .venv-sglang
.venv-sglang/bin/python -m pip install --upgrade pip 'sglang==0.5.17'
# Debian/Ubuntu minimal containers may omit this SGLang kernel dependency.
apt-get update
apt-get install -y libnuma1
```
Install SGLang with `pip`, not `uv`, unless the host has `uv` 0.12.0 or newer.
Every SGLang release in the compatibility matrix depends on a prerelease
`flash-attn-4` — 0.5.17 requires `flash-attn-4>=4.0.0b18`, and the only
non-prerelease version on PyPI is an unrelated 0.0.1 placeholder. `pip` accepts
that because the specifier itself names a prerelease, but `uv` before 0.12.0
refuses transitive prereleases outright and fails with `No solution found`. On
an older `uv`, name the package at the top level so its explicit mode applies:
```bash
uv pip install --python .venv-sglang/bin/python \
--prerelease=explicit 'sglang==0.5.17' 'flash-attn-4>=4.0.0b4'
```
`./scripts/run-linux-gpu.sh` already does this for the environment it manages.
Record the selection in `.env.sglang`. The native doctor verifies both the
exact pin and the CUDA 12/13 driver boundary:
```dotenv
SGLANG_PYTHON=.venv-sglang/bin/python
SGLANG_EXPECTED_VERSION=0.5.17
SGLANG_EXTRA_ARGS=--max-running-requests 1 --language-only
```
A container on driver 550 installs `sglang==0.5.10.post1` instead, and must
drop `--language-only`:
```dotenv
SGLANG_EXPECTED_VERSION=0.5.10.post1
SGLANG_EXTRA_ARGS=--max-running-requests 1
```
SGLang and FlashInfer compile some kernels lazily. The machine-readable track
therefore records a matching JIT toolkit as well as the wheel family: cu12
needs CUDA 12.9 or newer within the CUDA 12 family, while cu13 needs CUDA 13.
The sampling path also needs the `curand.h` development header. The doctor
checks the effective `CUDA_HOME`/`nvcc`, initializes CUDA with the selected
Python, runs a small BF16 matrix multiplication, and verifies these headers.
The bootstrap deliberately does not install or replace a CUDA toolkit.
On a Debian/Ubuntu provider container using the cu12 RTX 5090 path, the
corresponding packages are commonly:
```bash
apt-get update
apt-get install -y \
cuda-nvcc-12-9 cuda-cudart-dev-12-9 cuda-crt-12-9 libcurand-dev-12-9
export CUDA_HOME=/usr/local/cuda-12.9
export CUDA_PATH="$CUDA_HOME"
export PATH="$CUDA_HOME/bin:$PATH"
```
Package names and toolkit locations are image-specific. Prefer a provider or
custom image whose driver, CUDA userspace, and JIT toolkit already agree over
repairing a running container.
`SGLANG_EXPECTED_VERSION` is an assertion, not an installer. Leave it empty
when using a provider-managed environment whose exact compatible patch version
you intentionally do not control; the doctor still checks the CUDA-family
boundary for versions it can identify.
The `0.5.10.post1` profile must also omit `--language-only`. In that release the
flag enables encoder disaggregation and requires separate `--encoder-urls`;
the standalone Qwen3.5 server should load the complete checkpoint instead.
`--language-only` in the repository's newer 0.5.17 profile has different
deployment expectations and must not be copied blindly across this boundary.
## Configure an RTX 5090 chain test
Two native chains have been verified on a 32 GB RTX 5090: driver 595.71.05 with
SGLang `0.5.17` (cu13) on 2026-08-16, and driver 570.195.03 with SGLang
`0.5.10.post1` plus a CUDA 12.9 JIT toolkit (cu12) on 2026-08-15. The cu12 run
also passed non-greedy sampling and an end-to-end agent file-tool task. The
combined GPU Docker images have not been certified on those cards yet.
```bash
git clone https://github.com/ApodexAI/FrontierAgent.git
cd FrontierAgent
uv sync --python 3.12 --extra dev
cp config/sglang/35b-5090.env.example .env.sglang
chmod 600 .env.sglang
```
Edit `.env.sglang` for the provider environment:
```dotenv
# Python from the environment where `import sglang` succeeds.
SGLANG_PYTHON=/path/to/sglang-environment/bin/python
# Pin selected from the driver matrix above; doctor checks the installed value.
# Use 0.5.17 on driver 580+, or 0.5.10.post1 on driver 525-579.
SGLANG_EXPECTED_VERSION=0.5.17
# Use the provider's persistent data disk, not a small container system disk.
# AutoDL convention:
SGLANG_DOWNLOAD_DIR=/root/autodl-tmp/huggingface
# Keep the unauthenticated API private to this instance.
SGLANG_NATIVE_HOST=127.0.0.1
```
Align `HF_HUB_CACHE` and `SGLANG_DOWNLOAD_DIR` so tokenizer, processor,
configuration, and weight files do not split across the system disk and data
disk. `HF_HOME` is their parent; Hugging Face normally appends `/hub` to it.
After one successful online download, an anonymous deployment can avoid Hub
metadata rate limits on restart:
```dotenv
HF_HOME=/root/autodl-tmp/huggingface
HF_HUB_CACHE=/root/autodl-tmp/huggingface/hub
SGLANG_DOWNLOAD_DIR=/root/autodl-tmp/huggingface/hub
HF_HUB_OFFLINE=1
```
Do not enable offline mode before the full snapshot has been downloaded.
The supplied 5090 profile configures `moe_wna16`, `bfloat16`, the
`qwen3_coder` tool parser, the `qwen3` reasoning parser, text-only loading,
32K context, and one running request. The quantization and dtype are pinned
rather than left at `auto` because the alternatives fail late, during kernel
compilation, without naming the cause: `gptq` rejects the MoE structure
outright, `gptq_marlin` reads fp16 scales its kernel then refuses against bf16
activations, and `float16` triggers a Triton branch-type assertion. The
profile deliberately leaves the model source blank: set the final
published `SGLANG_MODEL_ID`, or use `SGLANG_LOCAL_MODEL_PATH` for a checkpoint.
Treat higher context and concurrency as later capability tests.
## Diagnose, start, and verify
```bash
./scripts/run-sglang-native.py doctor
./scripts/run-sglang-native.py up
./scripts/run-sglang-native.py smoke
```
The native doctor checks the Linux environment, direct GPU visibility, a real
CUDA BF16 operation, the selected SGLang Python, matching `nvcc` and JIT
headers, model source, tensor parallel size, token budgets, cache-disk space,
loopback binding, and port availability. It does not require or probe Docker.
The server runs in its own process group. State is stored under:
```text
.apodex/sglang-native/server.pid
.apodex/sglang-native/server.log
```
Useful lifecycle commands:
```bash
./scripts/run-sglang-native.py status
./scripts/run-sglang-native.py logs
./scripts/run-sglang-native.py down
```
The launcher validates that a saved PID still belongs to
`sglang.launch_server` before sending a signal. It will use a healthy endpoint
that was started separately, but it will not claim ownership of or stop that
external process.
## Run FrontierAgent
```bash
./scripts/run-sglang-native.py tui
```
This starts SGLang if necessary and runs the ReAct workflow using FrontierAgent's
Linux native runtime. The launcher derives the following settings from the same
`.env.sglang` file, so a second `.env` does not need to duplicate them:
```dotenv
OPENAI_BASE_URL=http://127.0.0.1:30000/v1
OPENAI_MODEL=local-model
OPENAI_CONTEXT_WINDOW=32768
OPENAI_MAX_INPUT_TOKENS=27000
OPENAI_MAX_TOKENS=4096
```
Arguments after `tui` are forwarded to FrontierAgent. For example:
```bash
./scripts/run-sglang-native.py tui --cwd /root/autodl-tmp/project
```
## Troubleshooting
| Symptom | Likely cause | Action |
|---|---|---|
| `docker: command not found` | expected on an ordinary managed container | use this native launcher, not `docker/run-sglang.sh` |
| `sglang is not importable` | wrong Python environment selected | set `SGLANG_PYTHON` to the Python where `import sglang` succeeds |
| selected native track requires driver 580+ | driver is in the conservative CUDA 12 range | install `sglang==0.5.10.post1` or upgrade the host driver |
| installed version does not match `SGLANG_EXPECTED_VERSION` | the selected environment drifted | reinstall the configured pin or deliberately update the config and compatibility matrix |
| `requires at least one encoder urls` | `--language-only` was copied to SGLang 0.5.10 | remove `--language-only` for a standalone full-model server |
| `GPTQ Method does not support MoE` | `SGLANG_QUANTIZATION=gptq` against a MoE checkpoint | set `SGLANG_QUANTIZATION=moe_wna16` |
| `moe_wna16_marlin_gemm assumes hidden_states.dtype == w1_scale.dtype` | `gptq_marlin` reads fp16 scales but the activations are bf16 | set `SGLANG_QUANTIZATION=moe_wna16`, not `gptq_marlin` |
| `Mismatched type for col0 between then block` | `SGLANG_DTYPE` disagrees with the checkpoint's declared dtype | set `SGLANG_DTYPE=bfloat16` for the Qwen3.5 GPTQ Int4 checkpoint |
| `libtorchcodec` or `libavutil.so.57` fails to load | FFmpeg runtime is absent | ignore under `--language-only`; install the FFmpeg development libraries only if multimodal input is needed |
| CUDA/PyTorch symbol error | provider CUDA stack and SGLang wheels disagree | select a compatible provider image or follow SGLang's native install matrix |
| CUDA error 804 (`forward compatibility was attempted`) | a container forward-compat `libcuda` shadows the host driver on unsupported hardware | prefer a driver-compatible image; otherwise inspect the loaded `libcuda.so.1` and restore the provider's host-driver library path |
| CUDA error 35 during graph capture or JIT | the wheel track and effective `nvcc`/CUDA runtime use different CUDA families | set `CUDA_HOME`, `CUDA_PATH`, and `PATH` to the toolkit matching the selected track, then restart |
| `SM 12.x requires CUDA >= 12.9` | an RTX 5090 cu12 JIT selected CUDA 12.8 or older | install/select a CUDA 12.9 toolkit; do not switch the JIT compiler to CUDA 13 on a driver below 580 |
| `curand.h: No such file or directory` | the first non-greedy request reached a sampling JIT without CUDA development headers | install the matching `libcurand-dev` package and rerun smoke |
| `libnuma.so.1: cannot open shared object file` | minimal container omits the NUMA runtime | install `libnuma1` on Debian/Ubuntu (equivalent package on other distributions) |
| `No solution found` while installing SGLang | `uv` before 0.12.0 refuses SGLang's prerelease `flash-attn-4` dependency | install with `pip`, upgrade `uv`, or add `--prerelease=explicit` with `flash-attn-4` named at the top level |
| model download fills `/` | cache is on the container system disk | set `SGLANG_DOWNLOAD_DIR` to the persistent data disk |
| Hugging Face returns HTTP 429 during restart | anonymous metadata requests are rate-limited | set `HF_TOKEN`, or after a complete download align `HF_HUB_CACHE`/`SGLANG_DOWNLOAD_DIR` and set `HF_HUB_OFFLINE=1` |
| port 30000 is occupied | prior or unrelated server is running | run `status`; stop the known process or select another `SGLANG_PORT` |
| startup OOM | weights, GDN state, KV pool, or CUDA graphs exceed VRAM | reduce context/concurrency; if necessary add `--disable-cuda-graph` |
| smoke health passes but tool call fails | parser/model integration problem | inspect `logs`; verify `moe_wna16`, `qwen3_coder`, and the checkpoint revision |
After correcting `CUDA_HOME` or the effective compiler, remove only the stale
SGLang JIT caches created by the wrong toolkit (commonly the `tvm-ffi` and
`flashinfer` cache directories) before retrying. Do not copy a hard-coded
`LD_LIBRARY_PATH` from another provider: the host-driver library location is
image-specific.
Do not install NVIDIA Container Toolkit inside an ordinary provider container.
That toolkit configures a Docker daemon on a GPU host; this native path already
receives the GPU directly from the provider.