Skip to content

Commit c894d9c

Browse files
authored
feat(sglang): wire engine_args, add cuda13 build, ship MTP gallery demos (#9686)
Bring the sglang Python backend up to feature parity with vllm by adding the same engine_args:-map plumbing the vLLM backend already has. Any ServerArgs field (~380 in sglang 0.5.11) becomes settable from a model YAML, including the speculative-decoding flags needed for Multi-Token Prediction. Validation matches the vllm backend's: keys are checked against dataclasses.fields(ServerArgs), unknown keys raise ValueError with a difflib close-match suggestion at LoadModel time, and the typed ModelOptions fields keep their existing meaning with engine_args overriding them. Backend code: * backend/python/sglang/backend.py: add _apply_engine_args, import dataclasses/difflib/ServerArgs, call from LoadModel; rename Seed -> sampling_seed (sglang 0.5.11 renamed the SamplingParams field). * backend/python/sglang/test.py + test.sh + Makefile: six unit tests exercising the helper directly (no engine load required). Build / CI / backend gallery (cuda13 + l4t13 paths are now first-class): * backend/python/sglang/install.sh: add --prerelease=allow because sglang 0.5.11 hard-pins flash-attn-4 which only ships beta wheels; add --index-strategy=unsafe-best-match for cublas12 so the cu128 torch index wins over default-PyPI's cu130; new pyproject.toml-driven l4t13 install path so [tool.uv.sources] can pin torch/torchvision/ torchaudio/sglang to the jetson-ai-lab index without forcing every transitive PyPI dep through the L4T mirror's flaky proxy (mirrors the equivalent fix in backend/python/vllm/install.sh). * backend/python/sglang/pyproject.toml (new): L4T project spec with explicit-source jetson-ai-lab index. Replaces requirements-l4t13.txt for the l4t13 BUILD_PROFILE; other profiles still go through the requirements-*.txt pipeline via libbackend.sh's installRequirements. * backend/python/sglang/requirements-l4t13.txt: removed; superseded by pyproject.toml. * backend/python/sglang/requirements-cublas{12,13}{,-after}.txt: pin sglang>=0.5.11 (Gemma 4 floor); add cu130 torch index for cublas13 (new files) and cu128 torch index for cublas12 (default PyPI now ships cu130 torch wheels by default and breaks cu12 hosts). * backend/index.yaml: add cuda13-sglang and cuda13-sglang-development capability mappings + image entries pointing at quay.io/.../-gpu-nvidia-cuda-13-sglang. * .github/workflows/backend.yml: new cublas13 sglang matrix entry, mirroring vllm's cuda13 build. Model gallery + docs: * gallery/sglang.yaml: base sglang config template, mirrors vllm.yaml. * gallery/sglang-gemma-4-{e2b,e4b}-mtp.yaml: Gemma 4 MTP demos transcribed verbatim from the SGLang Gemma 4 cookbook MTP commands. * gallery/sglang-mimo-7b-mtp.yaml: MiMo-7B-RL with built-in MTP heads + online fp8 weight quantization, verified end-to-end on a 16 GB RTX 5070 Ti at ~88 tok/s. Uses mem_fraction_static: 0.7 because the MTP draft worker's vocab embedding is loaded unquantised and OOMs the static reservation at sglang's 0.85 default. * gallery/index.yaml: three new entries (gemma-4-e2b-it:sglang-mtp, gemma-4-e4b-it:sglang-mtp, mimo-7b-mtp:sglang). * docs/content/features/text-generation.md: new SGLang section with setup, engine_args reference, MTP demos, version requirements. * .agents/sglang-backend.md (new): agent one-pager covering the flat ServerArgs structure, the typed-vs-engine_args precedence, the speculative-decoding cheatsheet, and the mem_fraction_static gotcha documented above. * AGENTS.md: index entry for the new agent doc. Known limitation: the two Gemma 4 MTP gallery entries ship a recipe that doesn't yet run on stock libraries. The drafter checkpoints (google/gemma-4-{E2B,E4B}-it-assistant) declare model_type: gemma4_assistant / Gemma4AssistantForCausalLM, which neither transformers (<=5.6.0, including the SGLang cookbook's pinned commit 91b1ab1f... and main HEAD) nor sglang's own model registry (<=0.5.11) registers as of 2026-05-06. They will start working when HF or sglang upstream registers the architecture -- no LocalAI changes needed. The MiMo MTP demo and the non-MTP Gemma 4 paths work today on this build (verified on RTX 5070 Ti, 16 GB). Assisted-by: Claude:claude-opus-4-7 [Read] [Edit] [Bash] [WebFetch] [WebSearch] Signed-off-by: Richard Palethorpe <io@richiejp.com>
1 parent 048daa0 commit c894d9c

21 files changed

Lines changed: 732 additions & 21 deletions

.agents/sglang-backend.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Working on the SGLang Backend
2+
3+
The SGLang backend lives at `backend/python/sglang/backend.py` (async gRPC). It wraps SGLang's `Engine` (`sglang.srt.entrypoints.engine.Engine`) and translates LocalAI's gRPC `PredictOptions` into SGLang sampling params + outputs into `Reply.chat_deltas`. Structurally it mirrors `backend/python/vllm/backend.py` — keep them shaped the same so changes in one have an obvious analog in the other.
4+
5+
## `engine_args` is the universal escape hatch
6+
7+
A small fixed set of fields on `ModelOptions` is mapped to typed SGLang kwargs in `LoadModel` (model, quantization, load_format, gpu_memory_utilization → mem_fraction_static, trust_remote_code, enforce_eager → disable_cuda_graph, tensor_parallel_size → tp_size, max_model_len → context_length, dtype). **Everything else** flows through the `engine_args:` YAML map.
8+
9+
Validation happens in `_apply_engine_args`. Keys are checked against `dataclasses.fields(ServerArgs)` (`sglang.srt.server_args.ServerArgs` is a flat `@dataclass` with ~380 fields). Unknown keys raise `ValueError` at LoadModel time with a `difflib.get_close_matches` suggestion — same shape as the vLLM backend.
10+
11+
**Precedence:** typed `ModelOptions` fields populate `engine_kwargs` first, then `engine_args` overrides them. So a YAML that sets both `gpu_memory_utilization: 0.9` and `engine_args.mem_fraction_static: 0.5` ends up at `0.5`. Document this when answering "why didn't my YAML field stick?".
12+
13+
**ServerArgs is flat.** Unlike vLLM, where speculative decoding is nested under `engine_args.speculative_config: {...}`, SGLang exposes flat top-level fields: `speculative_algorithm`, `speculative_draft_model_path`, `speculative_num_steps`, `speculative_eagle_topk`, `speculative_num_draft_tokens`, `speculative_dflash_block_size`, etc. There is no `speculative_config:` dict. Same goes for compilation, kv-transfer, attention — all flat.
14+
15+
The canonical reference is `python/sglang/srt/server_args.py:ServerArgs` (line ~304). When SGLang adds new flags, no LocalAI code change is needed — they're automatically available via `engine_args:`. The validator picks them up because it introspects the live dataclass.
16+
17+
## Speculative decoding cheatsheet
18+
19+
`--speculative-algorithm` accepts `EAGLE`, `EAGLE3`, `NEXTN`, `STANDALONE`, `NGRAM`, `DFLASH`. `NEXTN` is silently rewritten to `EAGLE` in `ServerArgs.__post_init__` (`server_args.py:3286-3287`). MTP (Multi-Token Prediction) is the same EAGLE path with `num_steps=1, eagle_topk=1, num_draft_tokens=2` against a target whose architecture has multi-token heads (e.g. MiMo-7B-RL, DeepSeek-V3-MTP).
20+
21+
| Algorithm | Drafter requirement | Gallery demo target | Gallery demo drafter |
22+
|-----------|--------------------|---------------------|----------------------|
23+
| `NEXTN` / `EAGLE` (MTP) | Assistant drafter or built-in heads | google/gemma-4-E2B-it, google/gemma-4-E4B-it | google/gemma-4-E2B-it-assistant, google/gemma-4-E4B-it-assistant |
24+
| `EAGLE3` | EAGLE3 draft head | (no gallery entry yet) | e.g. jamesliu1/sglang-EAGLE3-Llama-3.1-Instruct-8B |
25+
| `DFLASH` | Block-diffusion drafter | (no gallery entry yet) | e.g. z-lab/Qwen3-4B-DFlash-b16 |
26+
| `STANDALONE` | Smaller LLM as drafter | (no gallery entry yet) | any smaller chat-tuned LLM in the same family |
27+
| `NGRAM` | None — uses prefix history | (no gallery entry yet) | n/a |
28+
29+
The Gemma 4 demos use `mem_fraction_static: 0.85` (cookbook default) and the cookbook's `num_steps=5, num_draft_tokens=6, eagle_topk=1` parameters. Other algorithms are reachable from any user YAML via `engine_args:` but don't have shipped demos yet — that's a deliberate gallery scope choice, not a backend limitation.
30+
31+
Gemma 4 support requires sglang built from a commit that includes [PR #21952](https://github.com/sgl-project/sglang/pull/21952). LocalAI's pinned release for cublas12 / cublas13 includes it. The `l4t13` (JetPack 7 / sbsa cu130) build floors at `sglang>=0.5.0` because the `pypi.jetson-ai-lab.io` mirror still ships only `0.5.1.post2` as of 2026-05-06 — Gemma 4 / MTP recipes are therefore not available on l4t13 until that mirror catches up. `backend.py` keeps backward compat with the 0.5.x → 0.5.11 `SamplingParams.seed``sampling_seed` rename via runtime detection.
32+
33+
Compatibility caveats per the SGLang docs: DFLASH and NGRAM are incompatible with `enable_dp_attention`; DFLASH requires `pp_size == 1`; STANDALONE is incompatible with `enable_dp_attention`; NGRAM is CUDA-only and disables the overlap scheduler.
34+
35+
### `mem_fraction_static` + quantization + MTP on consumer GPUs
36+
37+
When combining online weight quantization (`engine_args.quantization: fp8` / `awq` / etc.) with built-in-head MTP (`speculative_algorithm: EAGLE`/`NEXTN`) on a tight VRAM budget, sglang's default `mem_fraction_static: 0.85` will OOM during draft-worker init. The reason: sglang quantizes the **target** model's transformer blocks but loads the **MTP draft worker's vocab embedding** at the source dtype (typically bf16). For a 7 B-class model with a 150k-token vocab × 4096 hidden, that's another ~1.2 GiB allocated *after* the static pool is reserved. At 0.85 fraction on a 16 GB card there's no room left.
38+
39+
Workaround: drop `mem_fraction_static` to ~0.7 so the post-static heap can absorb the MTP embedding alloc + CUDA graph private pools. Verified end-to-end on MiMo-7B-RL + fp8 + MTP on a 16 GB RTX 5070 Ti (`gallery/sglang-mimo-7b-mtp.yaml`) at ~88 tok/s. Models with larger vocabs or more MTP layers (e.g. DeepSeek-V3-MTP) need an even smaller fraction.
40+
41+
This isn't documented anywhere upstream as of 2026-05-06 — the SGLang Gemma 4 cookbook uses 0.85 because their MTP path doesn't go through `eagle_worker_v2.py` for an embedding-bearing draft module. Don't blanket-apply 0.7 across all sglang YAMLs; only when MTP-with-built-in-heads + quantization combine.
42+
43+
## Tool-call and reasoning parsers stay on `Options[]`
44+
45+
ServerArgs has `tool_call_parser` and `reasoning_parser` fields, and the backend does pass them through to `Engine` so SGLang's own HTTP/OAI surface keeps working. But for the **LocalAI** request path the backend constructs fresh per-request parser instances in `_make_parsers` (`backend.py:286`) because the parsers are stateful — the streaming and non-streaming paths each need their own.
46+
47+
So the user-facing knob stays on `Options[]`:
48+
49+
```yaml
50+
options:
51+
- tool_parser:hermes
52+
- reasoning_parser:deepseek_r1
53+
```
54+
55+
Putting these in `engine_args:` will set them on `ServerArgs` but the LocalAI-level streaming `ChatDelta` will not pick them up. Don't recommend that path.
56+
57+
## What's missing today (out of scope, but worth tracking)
58+
59+
- `core/config/hooks_sglang.go` — there is no SGLang equivalent of `hooks_vllm.go`. The vLLM hook auto-selects parsers for known model families from `parser_defaults.json` and seeds production engine_args defaults. A symmetric hook for SGLang could reuse the same `parser_defaults.json` (the SGLang parser names are different but the family detection is shared) and seed defaults like `enable_metrics: true` or attention-backend choices.
60+
- `core/gallery/importers/sglang.go` — vLLM has an importer that resolves model architecture → parser defaults at gallery-import time. A matching importer for SGLang would let `local-ai install` populate sensible parsers automatically.
61+
62+
These should be a follow-up PR, not a blocker for the engine_args feature.

.github/workflows/backend.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,19 @@ jobs:
959959
dockerfile: "./backend/Dockerfile.python"
960960
context: "./"
961961
ubuntu-version: '2404'
962+
- build-type: 'cublas'
963+
cuda-major-version: "13"
964+
cuda-minor-version: "0"
965+
platforms: 'linux/amd64'
966+
tag-latest: 'auto'
967+
tag-suffix: '-gpu-nvidia-cuda-13-sglang'
968+
runs-on: 'arc-runner-set'
969+
base-image: "ubuntu:24.04"
970+
skip-drivers: 'false'
971+
backend: "sglang"
972+
dockerfile: "./backend/Dockerfile.python"
973+
context: "./"
974+
ubuntu-version: '2404'
962975
- build-type: 'cublas'
963976
cuda-major-version: "13"
964977
cuda-minor-version: "0"

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ LocalAI follows the Linux kernel project's [guidelines for AI coding assistants]
2424
| [.agents/coding-style.md](.agents/coding-style.md) | Code style, editorconfig, logging, documentation conventions |
2525
| [.agents/llama-cpp-backend.md](.agents/llama-cpp-backend.md) | Working on the llama.cpp backend — architecture, updating, tool call parsing |
2626
| [.agents/vllm-backend.md](.agents/vllm-backend.md) | Working on the vLLM / vLLM-omni backends — native parsers, ChatDelta, CPU build, libnuma packaging, backend hooks |
27+
| [.agents/sglang-backend.md](.agents/sglang-backend.md) | Working on the SGLang backend — `engine_args` validation against ServerArgs, speculative-decoding (EAGLE/EAGLE3/DFLASH/MTP) recipes, parser handling |
2728
| [.agents/testing-mcp-apps.md](.agents/testing-mcp-apps.md) | Testing MCP Apps (interactive tool UIs) in the React UI |
2829
| [.agents/api-endpoints-and-auth.md](.agents/api-endpoints-and-auth.md) | Adding API endpoints, auth middleware, feature permissions, user access control |
2930
| [.agents/debugging-backends.md](.agents/debugging-backends.md) | Debugging runtime backend failures, dependency conflicts, rebuilding backends |

backend/index.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@
287287
amd: "rocm-sglang"
288288
intel: "intel-sglang"
289289
nvidia-cuda-12: "cuda12-sglang"
290+
nvidia-cuda-13: "cuda13-sglang"
290291
nvidia-l4t-cuda-13: "cuda13-nvidia-l4t-arm64-sglang"
291292
cpu: "cpu-sglang"
292293
- &vllm-omni
@@ -1965,13 +1966,19 @@
19651966
amd: "rocm-sglang-development"
19661967
intel: "intel-sglang-development"
19671968
nvidia-cuda-12: "cuda12-sglang-development"
1969+
nvidia-cuda-13: "cuda13-sglang-development"
19681970
nvidia-l4t-cuda-13: "cuda13-nvidia-l4t-arm64-sglang-development"
19691971
cpu: "cpu-sglang-development"
19701972
- !!merge <<: *sglang
19711973
name: "cuda12-sglang"
19721974
uri: "quay.io/go-skynet/local-ai-backends:latest-gpu-nvidia-cuda-12-sglang"
19731975
mirrors:
19741976
- localai/localai-backends:latest-gpu-nvidia-cuda-12-sglang
1977+
- !!merge <<: *sglang
1978+
name: "cuda13-sglang"
1979+
uri: "quay.io/go-skynet/local-ai-backends:latest-gpu-nvidia-cuda-13-sglang"
1980+
mirrors:
1981+
- localai/localai-backends:latest-gpu-nvidia-cuda-13-sglang
19751982
- !!merge <<: *sglang
19761983
name: "cuda13-nvidia-l4t-arm64-sglang"
19771984
uri: "quay.io/go-skynet/local-ai-backends:latest-nvidia-l4t-cuda-13-arm64-sglang"
@@ -1997,6 +2004,11 @@
19972004
uri: "quay.io/go-skynet/local-ai-backends:master-gpu-nvidia-cuda-12-sglang"
19982005
mirrors:
19992006
- localai/localai-backends:master-gpu-nvidia-cuda-12-sglang
2007+
- !!merge <<: *sglang
2008+
name: "cuda13-sglang-development"
2009+
uri: "quay.io/go-skynet/local-ai-backends:master-gpu-nvidia-cuda-13-sglang"
2010+
mirrors:
2011+
- localai/localai-backends:master-gpu-nvidia-cuda-13-sglang
20002012
- !!merge <<: *sglang
20012013
name: "cuda13-nvidia-l4t-arm64-sglang-development"
20022014
uri: "quay.io/go-skynet/local-ai-backends:master-nvidia-l4t-cuda-13-arm64-sglang"

backend/python/sglang/Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ run: sglang
88
bash run.sh
99
@echo "sglang run."
1010

11+
.PHONY: test
12+
test: sglang
13+
@echo "Testing sglang..."
14+
bash test.sh
15+
@echo "sglang tested."
16+
1117
.PHONY: protogen-clean
1218
protogen-clean:
1319
$(RM) backend_pb2_grpc.py backend_pb2.py

backend/python/sglang/backend.py

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,18 @@
99
ReasoningParser so tool_calls and reasoning_content are emitted
1010
incrementally inside ChatDelta, which is a capability sglang exposes
1111
natively and vLLM does not.
12+
13+
Like the vLLM backend, this one accepts an arbitrary ``engine_args:``
14+
map in the model YAML; keys are validated against ``ServerArgs`` fields
15+
and forwarded to ``Engine(**kwargs)``. That covers speculative decoding
16+
(EAGLE/EAGLE3/DFLASH/NGRAM/STANDALONE plus MTP via NEXTN), attention
17+
backend selection, MoE knobs, hierarchical cache, and so on.
1218
"""
1319
import asyncio
1420
from concurrent import futures
1521
import argparse
22+
import dataclasses
23+
import difflib
1624
import signal
1725
import sys
1826
import os
@@ -38,6 +46,7 @@
3846
# are wrapped in try/except so older / leaner installs that omit them
3947
# still load the backend for plain text generation.
4048
from sglang.srt.entrypoints.engine import Engine
49+
from sglang.srt.server_args import ServerArgs
4150

4251
try:
4352
from sglang.srt.function_call.function_call_parser import FunctionCallParser
@@ -66,6 +75,19 @@
6675
HAS_TRANSFORMERS = False
6776

6877

78+
# sglang 0.5.11 renamed SamplingParams.seed -> sampling_seed (PR #21952).
79+
# Earlier 0.5.x releases (e.g. 0.5.1.post2 — the wheel still pinned by the
80+
# pypi.jetson-ai-lab.io sbsa/cu130 mirror used by the l4t13 build profile)
81+
# accept only `seed`. Detect the supported keyword once at import time so
82+
# both versions work without a hard pin floor.
83+
try:
84+
import inspect as _inspect
85+
from sglang.srt.sampling.sampling_params import SamplingParams as _SamplingParams
86+
_SEED_KEY = "sampling_seed" if "sampling_seed" in _inspect.signature(_SamplingParams).parameters else "seed"
87+
except Exception:
88+
_SEED_KEY = "sampling_seed"
89+
90+
6991
_ONE_DAY_IN_SECONDS = 60 * 60 * 24
7092
MAX_WORKERS = int(os.environ.get('PYTHON_GRPC_MAX_WORKERS', '1'))
7193

@@ -82,6 +104,37 @@ def _parse_options(self, options_list) -> Dict[str, str]:
82104
opts[key.strip()] = value.strip()
83105
return opts
84106

107+
def _apply_engine_args(self, engine_kwargs: dict, engine_args_json: str) -> dict:
108+
"""Merge user-supplied engine_args (JSON object) into the kwargs dict
109+
that will be forwarded to ``sglang.Engine`` (which constructs a
110+
``ServerArgs`` from them).
111+
112+
Mirrors ``backend/python/vllm/backend.py::_apply_engine_args`` but
113+
operates on the kwargs dict because sglang's ``Engine.__init__``
114+
accepts ``**kwargs`` directly rather than a pre-built dataclass.
115+
Validation happens against ``ServerArgs`` fields so a typo fails
116+
early with a close-match suggestion instead of producing a confusing
117+
``TypeError`` deep inside engine startup.
118+
"""
119+
if not engine_args_json:
120+
return engine_kwargs
121+
try:
122+
extra = json.loads(engine_args_json)
123+
except json.JSONDecodeError as e:
124+
raise ValueError(f"engine_args is not valid JSON: {e}") from e
125+
if not isinstance(extra, dict):
126+
raise ValueError(
127+
f"engine_args must be a JSON object, got {type(extra).__name__}"
128+
)
129+
valid = {f.name for f in dataclasses.fields(ServerArgs)}
130+
for key in extra:
131+
if key not in valid:
132+
suggestion = difflib.get_close_matches(key, valid, n=1)
133+
hint = f" did you mean {suggestion[0]!r}?" if suggestion else ""
134+
raise ValueError(f"unknown engine_args key {key!r}.{hint}")
135+
engine_kwargs.update(extra)
136+
return engine_kwargs
137+
85138
def _messages_to_dicts(self, messages) -> List[dict]:
86139
result: List[dict] = []
87140
for msg in messages:
@@ -137,6 +190,16 @@ async def LoadModel(self, request, context):
137190
if self.reasoning_parser_name:
138191
engine_kwargs["reasoning_parser"] = self.reasoning_parser_name
139192

193+
# engine_args from YAML overrides typed fields above so operators can
194+
# tune anything ServerArgs exposes (speculative decoding, attention
195+
# backend, MoE, hierarchical cache, …) without waiting on protobuf
196+
# changes.
197+
try:
198+
engine_kwargs = self._apply_engine_args(engine_kwargs, request.EngineArgs)
199+
except ValueError as err:
200+
print(f"engine_args error: {err}", file=sys.stderr)
201+
return backend_pb2.Result(success=False, message=str(err))
202+
140203
try:
141204
self.llm = Engine(**engine_kwargs)
142205
except Exception as err:
@@ -221,7 +284,7 @@ def _build_sampling_params(self, request) -> dict:
221284
"TopP": "top_p",
222285
"TopK": "top_k",
223286
"MinP": "min_p",
224-
"Seed": "seed",
287+
"Seed": _SEED_KEY,
225288
"StopPrompts": "stop",
226289
"StopTokenIds": "stop_token_ids",
227290
"IgnoreEOS": "ignore_eos",

backend/python/sglang/install.sh

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,32 @@ if [ "x${BUILD_PROFILE}" == "xcpu" ]; then
2323
EXTRA_PIP_INSTALL_FLAGS+=" --index-strategy=unsafe-best-match"
2424
fi
2525

26+
# cublas12 needs a cu128 torch index (see requirements-cublas12.txt) — without
27+
# unsafe-best-match uv falls through to default PyPI's cu130 torch wheel and
28+
# the resulting sgl-kernel can't load on our cu12 host libs.
29+
if [ "x${BUILD_PROFILE}" == "xcublas12" ]; then
30+
EXTRA_PIP_INSTALL_FLAGS+=" --index-strategy=unsafe-best-match"
31+
fi
32+
33+
# sglang 0.5.11 (Gemma 4 support) declares flash-attn-4 as a hard dep, but
34+
# upstream only publishes pre-release wheels (4.0.0b*). uv rejects
35+
# pre-releases by default — opt in for sglang specifically. Drop this once
36+
# flash-attn-4 4.0 stable lands.
37+
EXTRA_PIP_INSTALL_FLAGS+=" --prerelease=allow"
38+
2639
# JetPack 7 / L4T arm64 wheels are built for cp312 and shipped via
2740
# pypi.jetson-ai-lab.io. Bump the venv Python so the prebuilt sglang
28-
# wheel resolves cleanly. unsafe-best-match is required because the
29-
# jetson-ai-lab index lists transitive deps (e.g. decord) at older
30-
# versions only — without it uv refuses to fall through to PyPI for a
31-
# compatible wheel and resolution fails.
41+
# wheel resolves cleanly. The actual install on l4t13 goes through
42+
# pyproject.toml (see the elif branch below) so [tool.uv.sources] can
43+
# pin only torch/torchvision/torchaudio/sglang to the jetson-ai-lab
44+
# index — leaving PyPI as the path for transitive deps like
45+
# markdown-it-py / anthropic / propcache that the L4T mirror's proxy
46+
# 503s on. No --index-strategy flag here: the explicit index keeps the
47+
# scoping clean.
3248
if [ "x${BUILD_PROFILE}" == "xl4t13" ]; then
3349
PYTHON_VERSION="3.12"
3450
PYTHON_PATCH="12"
3551
PY_STANDALONE_TAG="20251120"
36-
EXTRA_PIP_INSTALL_FLAGS+=" --index-strategy=unsafe-best-match"
3752
fi
3853

3954
# sglang's CPU path has no prebuilt wheel on PyPI — upstream publishes
@@ -95,6 +110,27 @@ if [ "x${BUILD_TYPE}" == "x" ] || [ "x${FROM_SOURCE:-}" == "xtrue" ]; then
95110
fi
96111
uv pip install ${EXTRA_PIP_INSTALL_FLAGS:-} .
97112
popd
113+
# L4T arm64 (JetPack 7): drive the install through pyproject.toml so that
114+
# [tool.uv.sources] can pin torch/torchvision/torchaudio/sglang to the
115+
# jetson-ai-lab index, while everything else (transitive deps and
116+
# PyPI-resolvable packages like transformers / accelerate) comes from
117+
# PyPI. Bypasses installRequirements because uv pip install -r
118+
# requirements.txt does not honor sources — see
119+
# backend/python/sglang/pyproject.toml for the rationale. Mirrors the
120+
# equivalent path in backend/python/vllm/install.sh.
121+
elif [ "x${BUILD_PROFILE}" == "xl4t13" ]; then
122+
ensureVenv
123+
if [ "x${PORTABLE_PYTHON}" == "xtrue" ]; then
124+
export C_INCLUDE_PATH="${C_INCLUDE_PATH:-}:$(_portable_dir)/include/python${PYTHON_VERSION}"
125+
fi
126+
pushd "${backend_dir}"
127+
# Build deps first (matches installRequirements' requirements-install.txt
128+
# pass — sglang/sgl-kernel sdists need packaging/setuptools-scm in the
129+
# venv before they can build under --no-build-isolation).
130+
uv pip install ${EXTRA_PIP_INSTALL_FLAGS:-} -r requirements-install.txt
131+
uv pip install ${EXTRA_PIP_INSTALL_FLAGS:-} --requirement pyproject.toml
132+
popd
133+
runProtogen
98134
else
99135
installRequirements
100136
fi

0 commit comments

Comments
 (0)