Qwen3.8-27B-SGLang-DGX-Spark README

The project's README from github.com/MiaAI-Lab/Qwen3.8-27B-SGLang-DGX-Spark. As one markdown file: README.md.

Qwen3.8 27B on SGLang for DGX Spark

SGLang Model arch

by Mia's AI Lab

Buy Me a Coffee at ko-fi.com Follow Mia on X

Opinionated, ready-to-run scripts to serve Qwen3.8-27B with SGLang in Docker on an NVIDIA DGX Spark (GB10, aarch64). Three swap-in serving modes — EAGLE/MTP, DSpark, or DFlash2 — with every tuning choice measured on-device instead of guessed.

DSpark and DFlash2 are faster on code. Versus MTP, DSpark gives the essay back; DFlash2 does not. Everyday chat on the same streamed probe comes out a DFlash2 win once tokens are counted right, and the long-essay probe is on the MTP side of the table. All measured numbers, ranges, counting notes and caveats live in Measured on this box — one place, nothing repeated.

The launch flags start from the SGLang cookbook's DGX Spark cell (NVFP4 + DSpark), then pin choices measured on this box: GDN bf16 (cookbook float32 was −3%), extra_buffer_lazy, mem 0.90, chunk 8192, DSpark block 7 / 8 draft tokens, torch.compile + decode graphs, X5 cpuset.


Requirements

Component Detail
Hardware NVIDIA DGX Spark / GB10 (aarch64, SM121; 128 GB unified memory)
Docker With NVIDIA Container Toolkit / GPU passthrough working (docker run --gpus all)
SGLang image lmsysorg/sglang:qwen38-27b for MTP/DSpark (model-specific build from the cookbook; multi-arch incl. arm64). lmsysorg/sglang:dev-qwen38-27b-dflash2 for DFlash2 (official multi-arch dev tag, digest-pinned and pulled by start-dflash.sh, ~14 GB)
CLI tools docker, curl
Hugging Face token HF_TOKEN defined in ~/.bashrc (picked up automatically; higher rate limits)

There is no separate download step: the container pulls the checkpoint into ./.cache/huggingface on first start (~24 GB for the default NVFP4 BF16-head repo; the packed-FP4 twin is ~1.7 GB smaller on disk). The cookbook cites ~16.5 GB for the NVFP4 LM weights alone before the MTP head; the dense lm_head adds ~1.7 GB on disk / ~3.2 GB at runtime.

Quick start (ships as native 262K, 10 concurrent)

# 1. Copy the sample config once (creates ./.env if you don't have one)
cp .env.sample .env

# 2. Start the server
./start-dspark.sh    # DSpark — code ~51.5; default chat ~23; long essay ~18
# ./start.sh         # MTP — code ~34.5; default chat ~21; long essay ~24
# ./start-dflash.sh  # DFlash2, NVFP4 target — code ~50.9; essay ~25.4; chat ~29–67 (streamed)
#                    #   (bf16 base: DF_TARGET=bf16 — unbenched on this box)

# 3. Use it
curl http://127.0.0.1:8888/v1/models

# 4. Stop it
./stop.sh

.env.sample ships with YARN=0, CONTEXT_LENGTH=262144 (native) and MAX_CONCURRENT_REQUESTS=10 — so a fresh clone serves 262K context, YaRN off, 10 concurrent after just the cp above. .env is the live config (plain VAR=value lines read by start.sh): shell exports of the same names win, .env fills the gaps, start.sh defaults apply last. Changes only take effect on the next launch — ./stop.sh && ./start-dspark.sh (or ./start.sh for MTP). For anything above native context (e.g. 1M) or a different concurrency, see Long context & concurrency. Note: DSpark cannot use YaRN / context > 262144 on this build (ditto DFlash2 — same draft-config leak).

start-dflash.sh is self-contained but runs a different image: the official multi-arch lmsysorg/sglang:dev-qwen38-27b-dflash2 — the tag the SGLang cookbook maps to DGX Spark — pinned by its index digest (sha256:616a3e97…; upstream build 5f55db35e on branch dflash2-pin-1cf2b8c-nccl, 2026-08-22) and pulled from Docker Hub on first run (~14 GB compressed; no git, no local build). That image carries DFlash2 (sglang #35371) and the quantized-head selector (#35496), so both NVFP4 exports work, including the packed-FP4 lm_head (DF_TARGET=nvfp4-fp4). No released tag ships DFlash2 yet (v0.5.18 was tagged later but does not contain the commit), hence the dev tag: a CI build of an upstream dev branch, from the same publisher as qwen38-27b, with its content frozen by the digest pin; like every engine here it runs under start.sh's --privileged --network host container. First boot then pulls the ~2.6 GB draft if missing. The earlier self-built image and its patch/ builder were retired 2026-09-05 (last carried by commit 751e29e); an already-built local image still runs via IMAGE=lmsysorg/sglang:qwen38-27b-dflash2.

All start scripts are idempotent: if the container is already running they say so and exit; if a stopped container exists they remove it first. ./stop.sh stops whichever engine is up.

Scripts

Script What it does
start.sh Launches the SGLang container (docker run -d, host network, --shm-size 32g), streams logs to .sglang.log, records the container ID in .sglang.pid, and polls http://127.0.0.1:8888/v1/models until the server is ready. EAGLE/MTP speculative decoding (SPEC_STEPS/SPEC_TOPK/SPEC_DRAFT = 3/1/4). Monitoring on by default: Prometheus --enable-metrics + --enable-cache-report (per issue #3).
start-dspark.sh Same service, DSpark instead of EAGLE: block-7 / unquant draft, torch.compile + decode-graph caps, --num-continuous-decode-steps 2, mem 0.90. Thin wrapper (EXTRA_ARGS → start.sh). Code 51.5 vs MTP 34.5 — see Measured on this box for all numbers.
start-dflash.sh Same service, DFlash2 block-diffusion draft (default z-lab/Qwen3.8-27B-DFlash2@50307d4, pinned — same draft as the incoai/… mirror; DRAFT_MODEL/DRAFT_REVISION env-overridable). Default target: NVFP4 BF16-head RadixArk/Qwen3.8-27B-NVFP4-BF16-LMHead with --mem-fraction-static 0.90 (dense lm_head, so DFLASH's selector uses the native dense path). Packed-FP4 head: DF_TARGET=nvfp4-fp4 (the image's quantized-head selector handles it in place via lm_head.quant_method.apply; a dequant-once approach hard-rebooted this box). DF_TARGET=bf16 selects Qwen/Qwen3.8-27B. Pulls the official digest-pinned image on first run if missing. Measured numbers: Measured on this box.
bench/ndec.py Two-call net-decode A/B (LRUCache + essay, thinking off). How the DSpark vs MTP numbers above were measured. Run twice; trust the second; treat code deltas <15% as noise.
bench/bench.sh Essay / tool-call wall-time bench + 16K TTFT probe (includes prefill). Different clock from ndec.py.
bench/ab-image.sh One-session comparison harness. Sides: A DFlash2 on main's self-built image, B DFlash2 on the official pinned image, M EAGLE/MTP on the stock image. A vs B isolates the image; B vs M is the DFlash2-vs-MTP engine question, and the summary prints that ratio. Boots each side, verifies the engine and selector from .sglang.log, throws away one warmup pass (cold start wrecks the first two-call delta), runs ndec.py n times, and reports per-side medians, per-boot values and pairwise deltas. SEQ="A B B A" or "M B B M" interleaves to cancel drift; MODEL_PATH= retargets every side at one checkpoint; LABEL= names the output; DRY_RUN=1 prints the plan. Artifacts land in bench/_ab-<label>-<stamp>/ (untracked).
stop.sh Stops the serving engine (idempotent; also cleans up any experiment processes still alive). Leaves the stopped container in place for docker logs post-mortem.

Runtime artifacts: .sglang.log (server log), .sglang.pid (container ID), .cache/ (HF + Triton caches). All are git-ignored.

Whitelisted for tracking: start.sh, start-dspark.sh, stop.sh, start-dflash.sh, bench/, docs/brainstorms/*.md, docs/plans/*.md, README.md, CHANGELOG.md, .env.sample, LICENSE, .gitignore. Experiment scripts and analysis docs stay untracked by design.

Which engine to use

EAGLE/MTP and DSpark are the same NVFP4 27B on the same lmsysorg/sglang:qwen38-27b image — only the speculative decoder changes. DFlash2 (start-dflash.sh) defaults to the same NVFP4 weights, on the official dev-qwen38-27b-dflash2 image (see Scripts / Configuration below) because no released tag ships DFlash2 yet. DFlash2 can also target the bf16 base (DF_TARGET=bf16) — not benched.

Performance numbers for all three engines live in the single table in Measured on this box — below is engine selection only (no repeated tok/s):

./start-dspark.sh (DSpark block-7) ./start.sh (EAGLE/MTP 3/1/4) ./start-dflash.sh (DFlash2, NVFP4)
Best for agents, code, tools, normal chat (default here) long-form writing code AND long-form writing (essay holds; chat is a win too)
Memory ~24 GB BF16-head target + ~2.7 GB draft, mem 0.90 ~24 GB BF16-head target (in-checkpoint MTP), mem 0.95 NVFP4 BF16-head: ~24 GB target + ~2.6 GB draft, mem 0.90; packed-FP4 ~22 GB; full bf16: 52 GB weights

DSpark/MTP columns are live 2026-08-18 evening; DFlash2 on 2026-08-19 (n=5, same single boot, same probes) — numbers and caveats are in Measured on this box. Take-aways, with that caveat firmly in mind: DFlash2 on NVFP4 ties DSpark on code (inside the <15% noise band), beats MTP on the long essay, and beats both on every short-chat condition once counted from completion_tokens (the earlier ~9.2 “tok/s” reading was an SSE event-counting artifact — see the counting note in Measured on this box). Watchpoints: mem-fraction-static 0.95 + DFlash2 wedged the box once (hard reboot; see Logs & troubleshooting) — root-caused and fixed (see the DFlash2 bullet); the current 0.90 + 16-concurrent profile boot-validated 2026-08-19 with no reboot (concurrency ladder in Measured on this box). bf16 target is entirely unmeasured. Do not compare these to sparkDash fill-to-max streams.

Tuning history worth knowing: every Tier A (kernel-path) and Tier B (config/host) experiment measured zero net gain — these configs are the local optimum on this box. DSpark block-7 is the code peak; block-5 trades −16% code for +8% prose if you want it (DSPARK_EXTRA, see Configuration). Local-only write-ups: TIER_A_RESULTS.md, TIER_B_RESULTS.md, TIER_C_RESULTS.md, DS4F.md, KIMI.md, GROK.md, HANDOFF.md.

Configuration

Defaults live at the top of start.sh:

Variable Default Notes
YARN 0 0 off / 1 on for CONTEXT_LENGTH > 262144; implicitly on at exactly 1000000. Factor = round(CONTEXT_LENGTH/262144)
CONTEXT_LENGTH 262144 Range 262144..1000000 (native..1M). Combined with YARN=1 for values above native; 1M auto-enables YaRN even with YARN=0. Invalid values abort at startup
MAX_CONCURRENT_REQUESTS 10 Sizes --max-mamba-cache-size = concurrency × 4 slots and passes --max-running-requests
SPEC_STEPS / SPEC_TOPK / SPEC_DRAFT 3 / 1 / 4 MTP chain drafting; topk=1 requires SPEC_DRAFT = SPEC_STEPS + 1 (validated at launch). Sweep the pair on your box and pin the winner — 3/1/4 is the measured peak here
CHUNKED_PREFILL 8192 Prefill chunk tokens. Cookbook DGX Spark cell uses 2048; we keep 8192 (prefill/TTFT, not decode tok/s).
CPUSET 5-9,15-19 Docker --cpuset-cpus pin to GB10's Cortex-X5 cores (A725s are 0-4, 10-14). Empty = no pinning
MAMBA_SKIP_DECODE_LOCK 0 1 sets SGLANG_OPT_MAMBA_SKIP_DECODE_LOCK in the container — frees one GDN state slot per request (S 4→3)
PREFILL_CUDA_GRAPH 0 1 drops --disable-prefill-cuda-graph. Info: this build auto-disables prefill graphs on this model anyway (GDN layers ≠ standard GQA)
EXTRA_ARGS — Free-form extra SGLang flags, appended last (argparse last-wins, so they can override built-ins). The experiment hatch: EXTRA_ARGS="--fp4-gemm-runner-backend triton" ./start.sh
QUANT nvfp4 nvfp4 → RadixArk/Qwen3.8-27B-NVFP4-BF16-LMHead (dense BF16 lm_head; cookbook-measured export), nvfp4-fp4 → RadixArk/Qwen3.8-27B-NVFP4 (packed FP4 lm_head), fp8 → Qwen/Qwen3.8-27B-FP8, bf16 → Qwen/Qwen3.8-27B. All fit in the Spark's 128 GB.
(shell overrides) — Any variable above can also be set as a shell env var, or put in .env
SERVED_MODEL_NAME qwen3.8-27b-sglang Name clients use in API requests
IMAGE lmsysorg/sglang:qwen38-27b Cookbook-pinned image for this model
CONTAINER_NAME qwen3.8-27b-sglang Also used by stop.sh
PORT 8888 Listens on 0.0.0.0 via host networking

The shipped .env/.env.sample match the start.sh defaults above, so a fresh clone serves native 262K context, YaRN off, 10 concurrent out of the box. Raise context above 262K with YARN=1 + CONTEXT_LENGTH (see below).

start-dspark.sh adds a couple of knobs (shell-env or .env, optional):

Variable Default Notes
DSPARK_EXTRA — Extra SGLang flags appended AFTER the base DSpark stack, for per-boot experiments without editing the script. E.g. DSPARK_EXTRA="--speculative-dspark-block-size 5 --speculative-num-draft-tokens 6" ./start-dspark.sh (prose-tuned block; see below)
IMAGE lmsysorg/sglang:qwen38-27b env override (IMAGE=tag ./start-dspark.sh) to run a patched derivative image; roll back by not setting it.

Note: --cuda-graph-max-bs is a deprecated alias in this build; the DSpark stack uses --cuda-graph-max-bs-decode 4.

start-dflash.sh knobs (shell-env; it doesn't otherwise change start.sh/.env behavior except the model-path override):

Variable Default Notes
DF_TARGET nvfp4 nvfp4 (default) → RadixArk/Qwen3.8-27B-NVFP4-BF16-LMHead (+--mem-fraction-static 0.90; dense head, native DFLASH selector); nvfp4-fp4 → packed-FP4 RadixArk/Qwen3.8-27B-NVFP4 (uses the image's quantized-head selector); bf16 → Qwen/Qwen3.8-27B.
DF_EXTRA — Extra SGLang flags appended AFTER the base DFlash2 stack (last-wins). E.g. DF_EXTRA="--mem-fraction-static 0.90" ./start-dflash.sh
IMAGE lmsysorg/sglang@sha256:616a3e97… (= dev-qwen38-27b-dflash2) The official image, pinned by its multi-arch index digest; pulled on first run and aliased locally as lmsysorg/sglang:dev-qwen38-27b-dflash2 (an existing local tag of that name is never re-pointed; the container runs by digest regardless). Override with IMAGE=<ref> — a locally present image is used as-is (e.g. IMAGE=lmsysorg/sglang:qwen38-27b-dflash2 for the retired self-built image). To bump the pin: docker buildx imagetools inspect lmsysorg/sglang:dev-qwen38-27b-dflash2 prints the index digest → update IMAGE_DIGEST in the script → re-validate on the box. If the pull ever 404s, upstream moved the tag and Docker Hub dropped the old index — re-pin the same way.
DRAFT_MODEL / DRAFT_REVISION z-lab/Qwen3.8-27B-DFlash2 / 50307d4… Pinned DFlash2 draft (same weights as the incoai/… mirror). DRAFT_MODEL=incoai/Qwen3.8-27B-DFlash2 DRAFT_REVISION=dedf8df… reproduces the original n=5 baseline; DRAFT_REVISION='' follows a branch head.

DFlash2-specific stack facts (so nobody re-learns them on a crash): extra_buffer_lazy was rejected by DFLASH (AssertionError) on the self-built image → the script forces --mamba-radix-cache-strategy extra_buffer (the official image includes upstream #34763, which adds lazy support for DFLASH — untested here, so the override stays); DFLASH only supports speculative_num_steps == 1 (engine auto-overrides start.sh's MTP 3); --enable-dp-attention and the overlap scheduler are off in this path; the draft (incoai/z-lab/…-DFlash2) is a block-diffusion drafter, not a token LLM, so EAGLE knobs (topk, num_steps) don't apply.

Long context & concurrency (up to 1M, 10 concurrent)

All long-context and concurrency handling is driven by three variables in .env (or as shell exports):

Variable Meaning
YARN 1 = enable YaRN rope scaling — required for any CONTEXT_LENGTH > 262144; 0 = off (sensible only at/below 262144)
CONTEXT_LENGTH desired context in tokens, range 262144..1000000
MAX_CONCURRENT_REQUESTS parallel requests; also sets --max-running-requests, and sizes the GDN pool = value × 4 slots

Step by step — 1M context with 10 concurrent (goes above the shipped default):

cp .env.sample .env                      # once, if you have no .env yet
nano .env                                # make sure these are set:
#  YARN=1
#  CONTEXT_LENGTH=1000000
#  MAX_CONCURRENT_REQUESTS=10
./stop.sh && ./start.sh                  # relaunch so new values apply
# verify after boot:
grep -E "context_len|max_running_requests" .sglang.log
expect: context_len=1000000, max_running_requests=10, mamba pool 40 slots
You want YARN CONTEXT_LENGTH MAX_CONCURRENT_REQUESTS YaRN factor
1M + 10 concurrent 1 1000000 10 4.0 (also auto-on)
512K + 10 concurrent 1 524288 10 2.0
768K + 10 concurrent 1 786432 10 3.0
native 262K + 10 concurrent 0 262144 10 —
1M + 2 concurrent 1 1000000 2 4.0

Rules of thumb:

Qwen3.8-27B-SGLang-DGX-Spark (MiaAI-Lab/Qwen3.8-27B-SGLang-DGX-Spark)

Searching Database…

这是什么?一套开箱即用、带明确取舍的 Docker 脚本,在 NVIDIA DGX Spark(GB10,aarch64)上通过 SGLang 部署 Qwen3.8-27B 模型,提供三种可互换的投机解码模式 — EAGLE/MTP、DSpark 和 DFlash2 — 每种模式都经过实机实测调优,而不是凭感觉猜测。
这是下载即用的应用,还是我自己运行的脚本?它是一个包含 shell 脚本和 Docker 配置的仓库(start.sh、start-dspark.sh、start-dflash.sh、stop.sh),由你自己运行,以在容器中启动 SGLang 推理服务器;没有打包好的应用或库。
怎么把它跑起来?把 .env.sample 复制为 .env(默认原生 262K 上下文、关闭 YaRN、10 路并发),然后为 EAGLE/MTP 运行 ./start.sh,为 DSpark 运行 ./start-dspark.sh,或为 DFlash2 运行 ./start-dflash.sh;首次启动时各自会拉取对应的 Docker 镜像和模型权重。
开始之前需要什么硬件和工具?一台 NVIDIA DGX Spark / GB10 机器(aarch64、SM121、128 GB 统一内存),装有 Docker 和 NVIDIA Container Toolkit(docker run --gpus all 可正常工作),还需要 docker 和 curl 命令行工具,以及在 ~/.bashrc 中设置为 HF_TOKEN 的 Hugging Face 令牌。
这些不同模式有什么坑?在此构建版本上,DSpark 和 DFlash2 无法使用 YaRN,也不能超出原生 262144 token 的上下文(草案配置限制相同);而且官方 SGLang 发布版尚未支持 DFlash2,所以 DFlash2 需要单独拉取一个约 14 GB 的 dev 标签镜像。
它需要特定 GPU,还是任何 NVIDIA 显卡都行?它是专门为 DGX Spark 的 GB10 芯片(aarch64、SM121、128 GB 统一内存)构建和调优的,并绑定到其特定的 CPU 核心布局 — 不是“任何 NVIDIA GPU 都能跑”的通用方案。
这三种服务模式的实际区别是什么?在代码任务上,DSpark 和 DFlash2 比 EAGLE/MTP 更快;长文写作上 MTP 占优,DSpark 也能达到 MTP 的长文质量,DFlash2 则不行。综合来看,DFlash2 是代码和长文写作的最佳单一选择;在正确统计 token 的前提下,日常对话也更推荐 DFlash2。
模型下载要占多少磁盘空间?默认的 NVFP4 BF16 头权重约 24 GB,打包 FP4 的变体要小约 1.7 GB,完整 BF16 权重约 52 GB;DFlash2 的 Docker 镜像本身大约 14 GB。
为什么要把容器绑定到特定 CPU 核心?GB10 混合了高性能的 Cortex-X5 核心和较慢的 A725 能效核心;实测显示,把调度器/分词器绑定到十个 X5 核心(核心 5-9、15-19)上,相比落在较慢的核心上,解码速度可提升 2-7%。
上下文窗口能扩展到 262K token 以上吗?EAGLE/MTP 可以,通过 YaRN(设置 YARN=1 和 CONTEXTLENGTH)最多可验证到 1M token;但在此构建版本上,DSpark 和 DFlash2 无法超过原生 262144 token 的上下文。
Creator
MiaAI-Lab
Size
0.2 MB
Stars
474
Updated
CommitSep 09
Age
2mo
Commits · 90 Days