Skip to content

Commit 11959c8

Browse files
committed
Add RTX 5090 (SM 12.0, 32GB) serve profile: tuned launcher, x86 CI image, compose, README section
1 parent 27ec55c commit 11959c8

5 files changed

Lines changed: 219 additions & 14 deletions

File tree

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: build-rtx5090-image
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- "container/Dockerfile.rtx5090"
8+
- "serve-profiles/rtx5090/**"
9+
- ".github/workflows/build-rtx5090-image.yml"
10+
workflow_dispatch:
11+
12+
env:
13+
REGISTRY: ghcr.io
14+
IMAGE: ghcr.io/${{ github.repository_owner }}/ornith-15-35b-nvfp4-w4a16-sm121-sglang
15+
16+
jobs:
17+
build:
18+
runs-on: ubuntu-latest
19+
permissions:
20+
contents: read
21+
packages: write
22+
steps:
23+
- uses: actions/checkout@v4
24+
25+
- name: "Set up Docker Buildx"
26+
uses: docker/setup-buildx-action@v3
27+
28+
- name: "Log in to GHCR"
29+
uses: docker/login-action@v3
30+
with:
31+
registry: ${{ env.REGISTRY }}
32+
username: ${{ github.actor }}
33+
password: ${{ secrets.GITHUB_TOKEN }}
34+
35+
- name: "Build and push tag rtx5090"
36+
uses: docker/build-push-action@v6
37+
with:
38+
context: .
39+
file: container/Dockerfile.rtx5090
40+
push: true
41+
tags: ${{ env.IMAGE }}:rtx5090
42+
cache-from: type=gha
43+
cache-to: type=gha,mode=max

README.md

Lines changed: 60 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,53 @@ If you omit `MODEL_PATH`, the entrypoint defaults to pulling
9999
`r0b0tlab/Ornith-1.5-35B-A3B-NVFP4-W4A16` from Hugging Face at boot
100100
(`HF_HOME` is a named volume, so the download persists across restarts).
101101

102-
The image bakes the **pre-warmed FlashInfer JIT caches** (CUTLASS FP4 GEMM
102+
The GB10 image bakes the **pre-warmed FlashInfer JIT caches** (CUTLASS FP4 GEMM
103103
sm121 modules + autotune tables + Triton cache) from the validated runs —
104104
first boot does zero JIT compilation. On a cold system, first-use JIT with
105105
the model resident can OOM (see [Known runtime notes](#known-runtime-notes)).
106106

107+
### RTX 5090 profile (Blackwell SM 12.0, 32 GB, x86-64)
108+
109+
A second, x86 image and tuned profile target a single RTX 5090:
110+
111+
```bash
112+
hf download r0b0tlab/Ornith-1.5-35B-A3B-NVFP4-W4A16 \
113+
--local-dir ./models/ornith-15-35b-a3b-nvfp4-w4a16-B
114+
115+
docker compose -f docker-compose.rtx5090.yml up -d
116+
# or bare:
117+
docker run --rm --gpus all --ipc host -p 8000:8000 \
118+
-v $PWD/models/ornith-15-35b-a3b-nvfp4-w4a16-B:/models/ckpt:ro \
119+
-e MODEL_PATH=/models/ckpt \
120+
ghcr.io/r0b0tlab/ornith-15-35b-nvfp4-w4a16-sm121-sglang:rtx5090
121+
```
122+
123+
Differences vs the GB10 profile ([`serve-profiles/rtx5090/serve.sh`](serve-profiles/rtx5090/serve.sh)):
124+
125+
| Knob | GB10 (121 GB unified) | RTX 5090 (32 GB) | Why |
126+
|---|---|---|---|
127+
| `--mem-fraction-static` | 0.80 | **0.92** | dedicated 32 GB part; OS doesn't share VRAM |
128+
| `--max-running-requests` | default (48/105) | **4** | mamba state cache is the binding constraint at 32 GB |
129+
| `--max-mamba-cache-size` | default (528) | **24** | ~1.4 GB state cache → 4 × 32K contexts fit |
130+
| context / KV | 32768 × 105 reqs | 32768 × 4 reqs | ~2.5 GB FP8 KV at this occupancy |
131+
| MTP (EAGLE K=1) | on | on; `MTP=0` env to disable | draft costs ~3.6 GB; disabling reclaims it for KV |
132+
133+
Memory budget ≈ 21.1 GB weights + 3.6 GB draft + 1.1 GB graphs + 1.4 GB
134+
mamba + 2.5 GB KV ≈ 29.7 GB. First boot on a cold cache JIT-compiles the
135+
sm_120 CUTLASS FP4 kernels (~10–20 min; runs in host RAM on a discrete-GPU
136+
box — safe, unlike unified-memory parts). `docker-compose.rtx5090.yml`
137+
mounts a persistent volume for the caches so subsequent boots are warm.
138+
If capture OOMs, lower `MAX_RUNNING_REQUESTS`/`CONTEXT_LENGTH` first.
139+
140+
The `rtx5090` tag is built by CI ([workflow](.github/workflows/build-rtx5090-image.yml))
141+
from [container/Dockerfile.rtx5090](container/Dockerfile.rtx5090), which
142+
pip-installs the same pinned stack (sglang `5a7b26c63`, torch 2.13.0+cu130,
143+
triton 3.7.1, flashinfer-python 0.6.17 + cubins) on x86 Ubuntu 24.04.
144+
**Note:** validated end-to-end on GB10/SM121; the 5090 profile is sized from
145+
the measured memory ledger and the vendor's SM 12.0 support envelope, but was
146+
not run on physical 5090 hardware before publication — report issues if
147+
first-boot JIT behaves differently on sm_120 discrete parts.
148+
107149
### Serving without the container
108150

109151
SGLang `0.5.6.post3.dev9218+g5a7b26c63`, FlashInfer 0.6.17,
@@ -203,22 +245,26 @@ It scored GSM8K 70.00% vs this recipe's 76.25% and decoded at 38.6 tok/s vs
203245

204246
```
205247
├── README.md
206-
├── docker-compose.yml # one-command launch
207-
├── container/ # Dockerfile, serve.sh entrypoint, build script
248+
├── docker-compose.yml # GB10/SM121 one-command launch
249+
├── docker-compose.rtx5090.yml # RTX 5090 (SM 12.0, 32 GB) launch
250+
├── container/ # GB10 Dockerfile + serve.sh + build script
251+
│ └── Dockerfile.rtx5090 # x86 CI-built image (same pinned stack)
252+
├── serve-profiles/
253+
│ └── rtx5090/serve.sh # tuned launcher for a single 32 GB card
208254
├── eval/
209-
│ ├── run_quality_set.py # run the 200-question suite (resumable)
210-
│ ├── rescore.py # fixed-scorer summaries from raw rows
211-
│ ├── answer_extract.py # GSM8K answer extraction (the fixed scorer)
212-
│ └── data/ # quality-200.jsonl + raw rows for 4 runs
255+
│ ├── run_quality_set.py # run the 200-question suite (resumable)
256+
│ ├── rescore.py # fixed-scorer summaries from raw rows
257+
│ ├── answer_extract.py # GSM8K answer extraction (the fixed scorer)
258+
│ └── data/ # quality-200.jsonl + raw rows for 4 runs
213259
├── quantization/
214-
│ ├── w4a16-nvfp4-std.yaml # the shipped recipe (candidate B)
215-
│ ├── w4a16-expert-4o6.yaml # experts-only 4/6 alternative (candidate A)
216-
│ ├── quant-cand-custom.py # CPU-first quantize driver
217-
│ └── reattach-mtp.py # BF16 MTP re-attachment
260+
│ ├── w4a16-nvfp4-std.yaml # the shipped recipe (candidate B)
261+
│ ├── w4a16-expert-4o6.yaml # experts-only 4/6 alternative (candidate A)
262+
│ ├── quant-cand-custom.py # CPU-first quantize driver
263+
│ └── reattach-mtp.py # BF16 MTP re-attachment
218264
└── audits/
219-
├── audit_checkpoint.py # scale pairing / MTP hashes / key closure
220-
├── cosine_probe.py # NVFP4 dequant cosine vs BF16 source
221-
└── results/ # audit JSON outputs
265+
├── audit_checkpoint.py # scale pairing / MTP hashes / key closure
266+
├── cosine_probe.py # NVFP4 dequant cosine vs BF16 source
267+
└── results/ # audit JSON outputs
222268
```
223269

224270
## Credits and attribution

container/Dockerfile.rtx5090

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# x86-64 / RTX 5090 (SM 12.0) image for Ornith-1.5-35B-A3B-NVFP4-W4A16.
2+
# Pinned to the exact stack the checkpoint was validated with on GB10:
3+
# sglang 0.5.6.post3.dev9218+g5a7b26c63 (git), torch 2.13.0+cu130,
4+
# triton 3.7.1, flashinfer-python 0.6.17 (+ flashinfer-cubin for sm_120
5+
# cubins so FP4 GEMMs JIT without a full CUDA toolkit).
6+
# Built by CI (no GPU needed to assemble); first boot on the GPU completes
7+
# any remaining JIT with the model resident-safe (discrete-GPU host RAM).
8+
FROM ubuntu:24.04
9+
ENV DEBIAN_FRONTEND=noninteractive
10+
RUN apt-get update && apt-get install -y --no-install-recommends \
11+
python3.12 python3-venv ninja-build git ca-certificates curl \
12+
&& rm -rf /var/lib/apt/lists/* \
13+
&& ln -sf /usr/bin/python3.12 /usr/local/bin/python \
14+
&& python -m venv /opt/venv
15+
ENV PATH=/opt/venv/bin:$PATH \
16+
HOME=/home/sglang \
17+
HF_HOME=/home/sglang/.cache/huggingface
18+
19+
# Pinned runtime (order matters: torch first, then flashinfer, then sglang).
20+
RUN pip install --no-cache-dir torch==2.13.0 triton==3.7.1 \
21+
&& pip install --no-cache-dir flashinfer-python==0.6.17 flashinfer-cubin==0.2.7 \
22+
--find-links https://flashinfer.ai/whl/flashinfer-cubin/ \
23+
|| pip install --no-cache-dir flashinfer-python==0.6.17
24+
RUN pip install --no-cache-dir "sglang @ git+https://github.com/sgl-project/sglang@5a7b26c63" \
25+
&& pip install --no-cache-dir transformers accelerate
26+
27+
RUN useradd -m -u 1001 sglang && mkdir -p /home/sglang/.cache \
28+
&& chown -R sglang:sglang /home/sglang /opt/venv
29+
USER sglang
30+
WORKDIR /home/sglang
31+
32+
EXPOSE 8000
33+
HEALTHCHECK --interval=30s --timeout=10s --start-period=1200s --retries=5 \
34+
CMD curl -sf http://127.0.0.1:8000/health || exit 1
35+
36+
COPY --chmod=755 serve-profiles/rtx5090/serve.sh /usr/local/bin/serve.sh
37+
ENTRYPOINT ["/usr/local/bin/serve.sh"]

docker-compose.rtx5090.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
services:
2+
ornith-nvfp4-rtx5090:
3+
image: ghcr.io/r0b0tlab/ornith-15-35b-nvfp4-w4a16-sm121-sglang:rtx5090
4+
container_name: ornith-nvfp4-rtx5090
5+
ipc: host
6+
shm_size: 16g
7+
environment:
8+
MODEL_PATH: /models/ornith-15-35b-a3b-nvfp4-w4a16-B
9+
CONTEXT_LENGTH: "32768"
10+
MEM_FRACTION_STATIC: "0.92"
11+
MAX_RUNNING_REQUESTS: "4"
12+
MAX_MAMBA_CACHE_SIZE: "24"
13+
# MTP: "0" to disable the EAGLE draft (reclaims ~3.6 GB for KV)
14+
PORT: "8000"
15+
volumes:
16+
- ${MODEL_DIR:-./models/ornith-15-35b-a3b-nvfp4-w4a16-B}:/models/ornith-15-35b-a3b-nvfp4-w4a16-B:ro
17+
# Persist HF downloads AND the JIT caches (first-boot sm_120 CUTLASS
18+
# compile ~10-20 min; warm boots thereafter).
19+
- sglang-cache:/home/sglang/.cache
20+
ports:
21+
- "8000:8000"
22+
deploy:
23+
resources:
24+
reservations:
25+
devices:
26+
- driver: nvidia
27+
count: all
28+
capabilities: [gpu]
29+
restart: unless-stopped
30+
31+
volumes:
32+
sglang-cache:

serve-profiles/rtx5090/serve.sh

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#!/usr/bin/env bash
2+
# RTX 5090 (Blackwell SM 12.0, 32 GB GDDR7, x86-64) serve profile for
3+
# Ornith-1.5-35B-A3B-NVFP4-W4A16.
4+
#
5+
# Memory budget on one 32 GB card (measured on the validated GB10 runs,
6+
# scaled to a dedicated-GPU part):
7+
# quantized target weights ~21.1 GB
8+
# BF16 MTP draft (EAGLE) ~3.6 GB (disable with MTP=0 to reclaim)
9+
# CUDA graphs (verify+draft) ~1.1 GB
10+
# mamba state cache (24 slots)~1.4 GB (hybrid GDN layers own no KV)
11+
# FP8 KV for 4x32768 ctx ~2.5 GB
12+
# => ~29.7 GB @ mem-fraction 0.92; lower MAX_RUNNING_REQUESTS/CONTEXT_LENGTH
13+
# if you OOM during graph capture.
14+
#
15+
# First boot on a cold cache JIT-compiles the FlashInfer CUTLASS FP4 GEMMs
16+
# for sm_120 (10-20 min, runs in system RAM on a discrete-GPU host — safe,
17+
# unlike unified-memory parts). Mount /home/sglang/.cache to persist it.
18+
set -euo pipefail
19+
MODEL_PATH="${MODEL_PATH:-r0b0tlab/Ornith-1.5-35B-A3B-NVFP4-W4A16}"
20+
CONTEXT_LENGTH="${CONTEXT_LENGTH:-32768}"
21+
MEM_FRACTION_STATIC="${MEM_FRACTION_STATIC:-0.92}"
22+
MAX_RUNNING_REQUESTS="${MAX_RUNNING_REQUESTS:-4}"
23+
MAX_MAMBA_CACHE_SIZE="${MAX_MAMBA_CACHE_SIZE:-24}"
24+
PORT="${PORT:-8000}"
25+
26+
MTP_ARGS=(
27+
--speculative-algorithm EAGLE
28+
--speculative-num-steps 1
29+
--speculative-eagle-topk 1
30+
--speculative-num-draft-tokens 2
31+
)
32+
if [ "${MTP:-1}" = "0" ]; then MTP_ARGS=(); fi
33+
34+
exec python -m sglang.launch_server \
35+
--model-path "$MODEL_PATH" \
36+
--served-model-name Ornith-1.5-35B-A3B \
37+
--trust-remote-code \
38+
--attention-backend triton \
39+
--moe-runner-backend marlin \
40+
--context-length "$CONTEXT_LENGTH" \
41+
--kv-cache-dtype fp8_e4m3 \
42+
--mem-fraction-static "$MEM_FRACTION_STATIC" \
43+
--max-running-requests "$MAX_RUNNING_REQUESTS" \
44+
--max-mamba-cache-size "$MAX_MAMBA_CACHE_SIZE" \
45+
"${MTP_ARGS[@]}" \
46+
--host 0.0.0.0 --port "$PORT" \
47+
${EXTRA_ARGS:-}

0 commit comments

Comments
 (0)