@adelie/gpu-model-serving 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
package/README.md CHANGED
@@ -1,3 +1,31 @@
1
- # Temporary Holding Version
1
+ # GPU Model Serving
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ An Adelie plugin that teaches an agent to **stand up an OpenAI-compatible LLM endpoint on a GPU box** — a rented instance or your own machine — and to survive the parts that normally cost a day.
4
+
5
+ ## What it installs
6
+
7
+ One skill, `gpu-model-serving`, plus its references:
8
+
9
+ | File | Contents |
10
+ | --- | --- |
11
+ | `skills/gpu-model-serving/SKILL.md` | The job in order: instance and disk layout, weights, environment, `serve.sh`, detached start, verification, hand-over — and the toolchain rules that actually bite. |
12
+ | `reference/sglang-serve.md` | Environment setup, a complete working `serve.sh` for a 27B 4-bit model + draft model on one 32GB card, flag-by-flag notes, memory arithmetic, startup and restart behaviour. |
13
+ | `reference/pitfalls.md` | The failure list: symptom → cause → fix, 14 entries, from NVFP4 checkpoint loading and CCCL header checks to JIT build OOM. |
14
+ | `reference/verify.md` | The five checks that decide whether the service is done — health, model list, tool call, stream, prefix cache — plus client wiring and the restart checklist. |
15
+ | `reference/gpu-cloud-console.md` | Operating the rented instance: console actions, the single exposed port, the public port that changes per boot, driving the web terminal, and prepaid billing that stops the instance at zero. |
16
+
17
+ ## When it fires
18
+
19
+ The skill is loaded when a task involves self-hosting a model: deploying or moving an inference service, choosing quantization or serving flags, diagnosing a server that will not start, or wiring an agent framework to a local endpoint. It is written for the single-GPU, 24–48GB case that most agent workloads land on, and scales by arithmetic rather than by hope.
20
+
21
+ ## Install
22
+
23
+ From the Adelie **Plugins** page, import from npm: `@adelie/gpu-model-serving`. The same string works from a URL — `https://www.npmjs.com/package/@adelie/gpu-model-serving` — or point the importer at the registry tarball. The plugin installs under the name `gpu-model-serving`.
24
+
25
+ ## Provenance
26
+
27
+ Distilled from a real deployment: **算家云 RTX 5090 ×1 (32GB, sm_120), SGLang 0.5.21, a 27B NVFP4 model with a DFlash2 draft model**, serving tool-calling agent traffic with prefix caching on, verified end to end (health, `/v1/models`, `tool_calls`, streaming SSE, `#cached-token`, speculative accept length). Every entry in `pitfalls.md` is a failure that happened on the way there.
28
+
29
+ ## License
30
+
31
+ Apache-2.0.
package/icon.svg ADDED
@@ -0,0 +1,7 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
2
+ <rect x="3" y="4" width="18" height="7" rx="2" />
3
+ <rect x="3" y="13" width="18" height="7" rx="2" />
4
+ <path d="M7 7.5h.01M7 16.5h.01" />
5
+ <path d="M11 7.5h5M11 16.5h3" />
6
+ <path d="M17.5 16.5l1.2-1.6 1.3 3 1.2-1.4" />
7
+ </svg>
package/package.json CHANGED
@@ -1,6 +1,32 @@
1
1
  {
2
2
  "name": "@adelie/gpu-model-serving",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Stand up an OpenAI-compatible LLM endpoint on a rented or owned GPU box: pick the instance, fetch and quantize weights, serve with SGLang — speculative decoding, prefix caching and tool calling for agent workloads — then verify it and expose the port.",
5
+ "license": "Apache-2.0",
6
+ "keywords": [
7
+ "gpu",
8
+ "llm",
9
+ "sglang",
10
+ "inference",
11
+ "serving",
12
+ "openai-compatible",
13
+ "quantization",
14
+ "speculative-decoding"
15
+ ],
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/lmliheng/Adelie.git",
19
+ "directory": "plugins/gpu-model-serving"
20
+ },
21
+ "files": [
22
+ "plugin.json",
23
+ "icon.svg",
24
+ "skills",
25
+ "hooks",
26
+ "LICENSE",
27
+ "README.md"
28
+ ],
29
+ "publishConfig": {
30
+ "access": "public"
31
+ }
32
+ }
package/plugin.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "description": "Stand up an OpenAI-compatible LLM endpoint on a rented or owned GPU box: pick the instance, fetch and quantize weights, serve with SGLang — speculative decoding, prefix caching and tool calling for agent workloads — then verify it and expose the port. Carries the CUDA/JIT traps that cost hours the first time (CCCL header checks, MAX_JOBS OOM, flashinfer cubin version skew, NVFP4 checkpoints, mamba state cache).",
3
+ "description_zh": "在租用或自有的 GPU 机器上部署 OpenAI 兼容的模型服务:选实例、拉权重与量化、用 SGLang 开投机解码/前缀缓存/tool calling 服务 agent 负载,再验证并开放端口。附带第一次部署会耗掉数小时的 CUDA/JIT 坑(CCCL 头文件校验、MAX_JOBS 内存打爆、flashinfer cubin 版本错位、NVFP4 checkpoint、mamba 状态缓存)。",
4
+ "short_description": "Self-host an LLM endpoint on a GPU box with SGLang.",
5
+ "short_description_zh": "在 GPU 机器上用 SGLang 自部署模型接口。",
6
+ "version": "2026.10.30.1",
7
+ "category": "software-development"
8
+ }
@@ -0,0 +1,52 @@
1
+ ---
2
+ name: gpu-model-serving
3
+ description: Stand up an OpenAI-compatible LLM endpoint on a rented or owned GPU box — choose the instance and disk layout, fetch and quantize the weights, serve with SGLang (speculative decoding, prefix caching, tool calling), then verify it and expose the port. Use when a task needs a self-hosted model service, especially one GPU of 24–48GB serving an agent workload.
4
+ ---
5
+
6
+ # GPU Model Serving
7
+
8
+ You are standing up a **self-hosted, OpenAI-compatible LLM endpoint** on a GPU machine — rented by the hour from a cloud, or a box you already have. The deliverable is a URL like `http://<host>:<port>/v1` plus a model name, good enough that an agent framework can point at it and call tools.
9
+
10
+ Two things make this harder than it looks, and both are scheduling, not modelling: the **build toolchain** (CUDA headers, nvcc, JIT kernel compilation) and the **memory budget** of a single card. Read [`reference/pitfalls.md`](reference/pitfalls.md) before you start typing; every entry there is a real failure that costs 10–60 minutes when it first appears.
11
+
12
+ ## The shape of the job
13
+
14
+ 1. **Pick the machine and where the bytes live.** A single 24–48GB card runs a 27B-class model at 4-bit with room for a draft model and KV cache. Put weights, virtualenv and scripts on a **persistent data disk**, never on the small system disk — a stopped/restarted instance keeps the data disk and loses nothing else, and re-downloading 20–30GB is the slowest thing in the whole job.
15
+ 2. **Get the weights.** Prefer an already-quantized checkpoint (NVFP4 / AWQ / FP8) over quantizing yourself. Check the file set: an index plus its shards, the tokenizer, and a chat template that advertises `tools`. **Delete or move aside zero-byte leftovers** — a stale empty `*.index.json` or shard makes the loader crash on read.
16
+ 3. **Build the environment.** One virtualenv with the torch build matching the driver, `sglang[all]`, and a CUDA toolkit from pip. Pin nvcc deliberately (see *Toolchain* below).
17
+ 4. **Write a `serve.sh`** that exports the environment and ends in `exec sglang serve …`. Launch it detached: `setsid nohup ./serve.sh > serve.log 2>&1 < /dev/null &`. The first start compiles kernels and can take 20–60 minutes; every later start reuses the cache and takes minutes.
18
+ 5. **Verify before wiring clients** — `/health`, `/v1/models`, a tool call, a streamed response, a repeated prefix. [`reference/verify.md`](reference/verify.md) has the exact commands and what each answer should look like.
19
+ 6. **Expose the port and hand over** the base URL, the model name, the concurrency limit, and the cost per hour.
20
+
21
+ ## Toolchain — the part that actually bites
22
+
23
+ - The pip-installed CUDA packages must be on the environment's path: `CUDA_HOME`/`CUDA_PATH` at `<venv>/…/nvidia/cu13` (with its `bin` on `PATH`), `LD_LIBRARY_PATH` and `LIBRARY_PATH` at `<venv>/…/nvidia/cu13/lib`. Without this, `deep_gemm` asserts `cuda_home is not None` and sgl_kernel fails to load `libnvrtc`.
24
+ - pip ships `libcudart.so.13` but no `libcudart.so`; linkers ask for the latter. Symlink `libcudart.so`, `libcublas.so`, `libcublasLt.so` in that lib directory.
25
+ - **CCCL refuses a nvcc that does not exactly match the CUDA headers** (`#error "CUDA compiler and CUDA toolkit headers are incompatible"`). If you cannot make them match, set `#define CCCL_DISABLE_CTK_COMPATIBILITY_CHECK 1` at the top of every `cuda_toolkit.h` in the venv — there is more than one copy, and the second one only shows up when SGLang's own JIT kernels compile. Find them all first: `grep -rln "headers are incompatible" <venv>/lib/python3.*/site-packages`.
26
+ - **Cap the compile parallelism.** `ninja` defaults to one compiler per core plus two; a 27B CUTLASS/FP4 template can take gigabytes each, and a 60GB container gets OOM-killed mid-build (`ninja` exits 137, the log only says `Received sigquit from a child process`). `export MAX_JOBS=3` in `serve.sh` fixes it; the JIT cache keeps whatever already compiled.
27
+ - flashinfer's cubin package lags the runtime, and the version check is fatal. `FLASHINFER_DISABLE_VERSION_CHECK=1`; the venv's flashinfer cache (`~/.cache/sglang/.cache/flashinfer`) is worth keeping between restarts.
28
+
29
+ ## Serving choices for an agent workload
30
+
31
+ - **Speculative decoding** is a large win on a single card: a small draft model plus `--speculative-algorithm` (e.g. `DFLASH`, `EAGLE`) and 4–8 draft tokens. Expect an accept length of ~3–4; watch `accept len` in the decode log line.
32
+ - **Prefix caching** matters more than raw throughput when your client sends the same system prompt and tool schemas every turn. A repeat request should show `#cached-token:` close to the prompt length in the log.
33
+ - **Tool calling and reasoning** are parser settings, not model settings: `--tool-call-parser` (e.g. `qwen3_coder`), `--reasoning-parser` (e.g. `qwen3`). With a reasoning parser the chain of thought arrives in `reasoning_content` and the answer in `content`; without it your client sees raw think-tags.
34
+ - **Concurrency is usually not what you set.** Hybrid (mamba/GDN) models cap `max_running_requests` by the state cache and say so in the log: `max_running_requests is capped to 1 by the mamba state cache (max_mamba_cache_size=8, 5 state slots per request)`. Either raise `--max-mamba-cache-size` and accept a smaller KV pool, or tell the user it serialises.
35
+ - Budget the card explicitly: `--mem-fraction-static` sizes the KV pool off what is left; `--kv-cache-dtype fp8_e4m3`, `--context-length` and `--cuda-graph-max-bs-decode` trade context, concurrency and speed against each other. Read the two lines `KV Cache is allocated … #tokens:` and `available_gpu_mem` from the log and report them as the real limit.
36
+
37
+ The complete working example — every flag, every export, and the memory arithmetic — is in [`reference/sglang-serve.md`](reference/sglang-serve.md).
38
+
39
+ ## Rented GPU clouds
40
+
41
+ The console, the port mapping and the billing are where a rented instance differs from your own box, and where an agent usually needs help: the container listens on one fixed port, the public port is assigned per boot, the port switch turns itself off when the instance stops, and a depleted balance stops the instance without warning. [`reference/gpu-cloud-console.md`](reference/gpu-cloud-console.md) walks through it with the 算家云 (suanjiayun) console as the concrete case, including how to drive its web terminal when SSH is password-only.
42
+
43
+ ## Reporting
44
+
45
+ Give the user: the base URL and model name; what you verified (and what you could not); the measured concurrency and context limits with the reason; the hourly cost and the remaining balance; and the exact commands to restart the service after a reboot. If a client needs configuration, hand them the `base_url` / `model` pair and note that the placeholder API key can be any string when the server has no `--api-key`.
46
+
47
+ ## Reference
48
+
49
+ - [`reference/pitfalls.md`](reference/pitfalls.md) — the failure list: symptom → cause → fix.
50
+ - [`reference/sglang-serve.md`](reference/sglang-serve.md) — environment setup, the full `serve.sh`, flag-by-flag notes, memory math, restart behaviour.
51
+ - [`reference/verify.md`](reference/verify.md) — verification commands and expected answers, prefix-cache evidence, client wiring.
52
+ - [`reference/gpu-cloud-console.md`](reference/gpu-cloud-console.md) — renting and operating the instance: console, ports, web terminal, billing.
@@ -0,0 +1,42 @@
1
+ # Rented GPU clouds — console, ports, terminal, billing
2
+
3
+ The concrete case is **算家云 (suanjiayun.com)**, but the shape repeats on most Chinese GPU-rental consoles: a web console for the instance, a container you reach through a browser terminal, one exposed port, a persistent data disk, and prepaid billing.
4
+
5
+ ## The instance
6
+
7
+ - Console: `https://suanjiayun.com/container/#/instanceMine` — the instance list; each row carries 立即关机/开机, WebShell, SSH/SFTP, 文件管理, 开放端口, 更多.
8
+ - Storage layout inside the container:
9
+ - `/` — small system disk (~30GB), wiped on a system reset. Never put weights here.
10
+ - `/root/sj-tmp` — the data disk (~50GB), **survives stop/restart and reset**. Keep models, the venv and `serve.sh` here.
11
+ - Memory is a cgroup cap of the rented size (`cat /sys/fs/cgroup/memory.max`, e.g. 60GiB) while `free` reports the **host's** memory. Never size `MAX_JOBS` or a cache from `free`.
12
+ - SSH: `ssh <account>@<region>.suanjiayun.com -p 2020` — **password-only**; a pubkey is refused, so a scripted shell must go through the web terminal (below) or an interactive login.
13
+
14
+ ## Ports — read this twice
15
+
16
+ - The container must listen on **8080**; the platform forwards that one port.
17
+ - The forwarding is configured per instance under **开放端口 → 对外开放端口** (a switch, saved immediately), and the public address is revealed by **获取地址 / 新页面访问**, e.g. `http://<region>.suanjiayun.com:51197` → your OpenAI base URL is `http://<region>.suanjiayun.com:51197/v1`.
18
+ - **The public port is randomly assigned and can change on every boot** (observed: 51197 → 51476 across one stop/start), and **stopping the instance turns the port switch off**. So after every power-on: reopen the switch, read the new address, and update anything that hard-codes it (client configs, model registries, bookmarks).
19
+
20
+ ## The web terminal (WebShell)
21
+
22
+ The WebShell tab is an xterm in an iframe. To drive it from a script:
23
+
24
+ 1. `document.querySelector('iframe').contentDocument`, then the terminal's hidden input is `.xterm-helper-textarea`.
25
+ 2. `focus()` it and dispatch a synthetic `ClipboardEvent('paste')` carrying the command as `text/plain` — synthetic `keydown` alone does not insert text.
26
+ 3. To run it, dispatch synthetic `keydown`/`keyup` for `Enter` (keyCode 13).
27
+ 4. Read output from `.xterm-rows` `innerText` after setting `.xterm-viewport.scrollTop = scrollHeight` — only the visible ~60 lines exist, so `grep`/`tail` inside the container first.
28
+ 5. If the terminal freezes, reload the iframe (`iframe.contentWindow.location.reload()`); anything started with `setsid nohup` keeps running.
29
+
30
+ Long commands can lose characters or fail to submit on the first Enter — send a second bare Enter script rather than retyping.
31
+
32
+ ## Billing
33
+
34
+ - Hourly, prepaid: an RTX 5090 ×1 instance billed **¥2.92/hour**.
35
+ - When the balance reaches zero the platform **stops the instance automatically** ("已关机 / 已暂停计费") and the public address dies with it. A negative balance blocks starting again until it is topped up.
36
+ - Consequences for an agent: check the balance before promising an always-on service, tell the user the hourly rate and roughly how long the remaining balance lasts, and remember that a stop also disables the port switch.
37
+ - Because a stopped instance loses nothing on the data disk, the restart ritual is short:
38
+ 1. top up and power on;
39
+ 2. **开放端口 → 对外开放端口**: switch on, read the new address;
40
+ 3. in WebShell: `cd /root/sj-tmp; setsid nohup ./serve.sh > serve.log 2>&1 < /dev/null &`;
41
+ 4. wait ~4 minutes (warm JIT cache) and re-verify;
42
+ 5. update every client that held the old address.
@@ -0,0 +1,85 @@
1
+ # Failure list — symptom → cause → fix
2
+
3
+ In the order they show up. Search this file by the error string before doing anything clever.
4
+
5
+ ## 1. Checkpoint will not load: `assert param_data.shape == loaded_weight.shape`
6
+
7
+ **Cause:** the serving build does not know this quantization layout (here: an NVFP4/ModelOpt checkpoint on sglang 0.5.15).
8
+ **Fix:** upgrade (`sglang[all]==0.5.21` loaded it unchanged). If no version knows it, re-quantize or pick another checkpoint — do not patch the loader.
9
+
10
+ ## 2. `AssertionError: extra_buffer_lazy unsupported with spec`
11
+
12
+ **Cause:** a lazy mamba radix-cache strategy under speculative decoding.
13
+ **Fix:** `--mamba-radix-cache-strategy extra_buffer`.
14
+
15
+ ## 3. `unrecognized arguments: --mm-feature-transport`
16
+
17
+ **Cause:** the flag does not exist in the installed version.
18
+ **Fix:** upgrade, or drop it. Do not leave an unknown flag in `serve.sh` — the process exits before loading anything.
19
+
20
+ ## 4. `ImportError: libnvrtc.so.13: cannot open shared object file`
21
+
22
+ **Cause:** the CUDA runtime inside the venv is not on the loader path.
23
+ **Fix:** `export LD_LIBRARY_PATH=<venv>/lib/python3.*/site-packages/nvidia/cu13/lib:$LD_LIBRARY_PATH`.
24
+
25
+ ## 5. `deep_gemm: AssertionError: assert cuda_home is not None`
26
+
27
+ **Cause:** no toolkit for the JIT to call — a container has no system CUDA.
28
+ **Fix:** `export CUDA_HOME=$CS/nvidia/cu13`, `CUDA_PATH=$CUDA_HOME`, `PATH=$CUDA_HOME/bin:$PATH` **and** `LIBRARY_PATH=$CUDA_HOME/lib`.
29
+
30
+ ## 6. `flashinfer-cubin 0.6.12 != flashinfer 0.6.18` (fatal version check)
31
+
32
+ **Cause:** the runtime and its prebuilt cubins are versioned separately, and the matching cubin often is not on PyPI yet.
33
+ **Fix:** `FLASHINFER_DISABLE_VERSION_CHECK=1`.
34
+
35
+ ## 7. `#error "CUDA compiler and CUDA toolkit headers are incompatible, please check your include paths"`
36
+
37
+ **Cause:** CCCL inside the venv requires nvcc and `cuda_runtime.h` to be the *same* minor version (here nvcc 13.2 vs headers 13.0).
38
+ **Fix:** prepend `#define CCCL_DISABLE_CTK_COMPATIBILITY_CHECK 1` to every `cuda/std/__cccl/cuda_toolkit.h` in the venv. Verify with a full-line match — the file already contains `#ifndef CCCL_DISABLE_CTK_COMPATIBILITY_CHECK`, so a substring test always says "already fixed":
39
+
40
+ ```bash
41
+ f=<venv>/…/nvidia/cu13/include/cccl/cuda/std/__cccl/cuda_toolkit.h
42
+ grep -q '^#define CCCL_DISABLE_CTK_COMPATIBILITY_CHECK 1' "$f" || sed -i '1i #define CCCL_DISABLE_CTK_COMPATIBILITY_CHECK 1' "$f"
43
+ ```
44
+
45
+ ## 8. `ptxas ... Unsupported .version 9.2` / `Unsupported .target sm_120`
46
+
47
+ **Cause:** you "fixed" #7 by downgrading nvcc to match the headers. The older toolchain cannot emit code for a newer card.
48
+ **Fix:** keep nvcc at the newest version and disable the check instead (#7). Never downgrade below the card's architecture support.
49
+
50
+ ## 9. `ld: cannot find -lcudart`
51
+
52
+ **Cause:** pip ships versioned library names only (`libcudart.so.13`, never `libcudart.so`).
53
+ **Fix:** symlink `libcudart.so` (and `libcublas.so`, `libcublasLt.so` if the same error repeats) in the venv's `nvidia/cu13/lib`, and export `LIBRARY_PATH` to that directory.
54
+
55
+ ## 10. Draft/auxiliary model directory crashes the loader
56
+
57
+ **Cause:** zero-byte leftovers — truncated shards, an empty `model.safetensors.index.json`, empty tokenizer files — a half-finished download that the loader trusts and then fails on.
58
+ **Fix:** move every zero-byte file out of the directory; keep only the files the manifest actually names (e.g. `config.json` + a single `model.safetensors` for a draft model). Check with `find <dir> -size 0`.
59
+
60
+ ## 11. `Received sigquit from a child process. It usually means the child failed.`
61
+
62
+ **Cause:** this line is a symptom, never the reason. The scheduler dies because a compile or a child process died.
63
+ **Fix:** read upward: `grep -inE 'Killed|error:|Traceback|ninja:|CalledProcessError' serve.log | tail -20`. Expect #12 or #13.
64
+
65
+ ## 12. The same CCCL error, after you already fixed it — from `sgl_kernel_jit_speculative_sampling_*`
66
+
67
+ **Cause:** there is more than one vendored CCCL in a venv. The flashinfer copy is patched by #7; SGLang's own JIT (speculative sampling, attention) compiles against `nvidia/cu13/include/cccl/…` and stops on the same check.
68
+ **Fix:** apply #7 to *every* copy found by `grep -rln "headers are incompatible" <venv>/lib/python3.*/site-packages`.
69
+
70
+ ## 13. JIT build OOMs: `RuntimeError: Ninja build failed` with several `Killed` and exit status 137
71
+
72
+ **Cause:** ninja parallelises to `nproc+2` compilers; each CUTLASS/FP4 template can take gigabytes. A rented instance's memory is a cgroup cap (check `/sys/fs/cgroup/memory.max`), even though `free` inside the container prints the *host's* total and looks enormous.
73
+ **Fix:** `export MAX_JOBS=3` (flashinfer's `jit/cpp_ext.py` honours it) — or whatever keeps peak usage under the cap. Already-compiled kernels stay cached, so a restart continues from where it died.
74
+
75
+ ## 14. Only one port is reachable from outside
76
+
77
+ **Cause:** the platform exposes a fixed container port (算家云: **8080**) and forwards it to a random public port; nothing else is reachable.
78
+ **Fix:** serve on the exposed port, and do not run anything else on it. A temporary `python3 -m http.server 8080` to read logs is a useful trick **only** while the model server is down — kill it before starting SGLang, and remember the public port can change on the next boot (`gpu-cloud-console.md`).
79
+
80
+ ## Working habits that saved the most time
81
+
82
+ - **Read the log, not the terminal.** Long output through a web terminal is painful; `grep`/`tail` on the server first, and only then look at the screen. The reason for a crash is always above the last line.
83
+ - **Start detached** (`setsid nohup … &`) so a browser reload or a dropped SSH session cannot kill a 20-minute build.
84
+ - **Cache is an asset.** `~/.cache/sglang` holds every JIT artifact and autotune result; a warm restart is ~4 minutes instead of ~40.
85
+ - **Verify with the API, not the log.** "ready to roll" plus a green `/health` still does not prove tool calls, streaming or prefix caching work — see `verify.md`.
@@ -0,0 +1,124 @@
1
+ # Deploying with SGLang — the working setup
2
+
3
+ Everything here was run on **one RTX 5090 (32GB, sm_120), 15 vCPU, 60GB RAM**, serving a 27B 4-bit model with a draft model for speculative decoding, Python 3.10. Scale the numbers, keep the shape.
4
+
5
+ ## 1. Environment
6
+
7
+ ```bash
8
+ apt-get install -y python3-venv || true
9
+ cd /root/sj-tmp # the persistent data disk
10
+ python3 -m venv venv && . venv/bin/activate
11
+ pip install -U pip
12
+ pip install "sglang[all]==0.5.21" # 0.5.15 could not load the NVFP4 checkpoint
13
+ pip install nvidia-cuda-nvcc==13.2.86 # pin the toolchain; see pitfalls 7 & 8
14
+ ```
15
+
16
+ `torch 2.13.0+cu130` came in with `sglang[all]`. The pip CUDA packages land in the venv at `lib/python3.<x>/site-packages/nvidia/cu13/`, which is the toolkit you point everything at.
17
+
18
+ Three fixes to apply once, after installing:
19
+
20
+ ```bash
21
+ CS=venv/lib/python3.10/site-packages
22
+ # (a) linker names pip does not ship
23
+ ln -sf libcudart.so.13 $CS/nvidia/cu13/lib/libcudart.so
24
+ ln -sf libcublas.so.13 $CS/nvidia/cu13/lib/libcublas.so 2>/dev/null || true
25
+ ln -sf libcublasLt.so.13 $CS/nvidia/cu13/lib/libcublasLt.so 2>/dev/null || true
26
+ # (b) find every CCCL copy that guards the toolkit-header check, then disable the check
27
+ grep -rln "headers are incompatible" $CS | tee /tmp/cccl.txt
28
+ while read -r f; do
29
+ grep -q '^#define CCCL_DISABLE_CTK_COMPATIBILITY_CHECK 1' "$f" || \
30
+ sed -i '1i #define CCCL_DISABLE_CTK_COMPATIBILITY_CHECK 1' "$f"
31
+ done < /tmp/cccl.txt
32
+ ```
33
+
34
+ ## 2. `serve.sh`
35
+
36
+ ```bash
37
+ #!/bin/bash
38
+ cd /root/sj-tmp
39
+ . venv/bin/activate
40
+
41
+ export MAX_JOBS=3 # ninja parallelism -> container RAM
42
+ export FLASHINFER_DISABLE_VERSION_CHECK=1 # vendored cubin lags the runtime
43
+ CS=/root/sj-tmp/venv/lib/python3.10/site-packages
44
+ export LD_LIBRARY_PATH=$CS/nvidia/cu13/lib:$LD_LIBRARY_PATH
45
+ export LIBRARY_PATH=$CS/nvidia/cu13/lib:$LIBRARY_PATH
46
+ export CUDA_HOME=$CS/nvidia/cu13
47
+ export CUDA_PATH=$CUDA_HOME
48
+ export PATH=$CUDA_HOME/bin:$PATH
49
+
50
+ M=/root/sj-tmp/models/<target-model>
51
+ D=/root/sj-tmp/models/<draft-model>
52
+
53
+ exec sglang serve \
54
+ --model-path "$M" \
55
+ --served-model-name qwen3.8-27b-uncensored \
56
+ --quantization modelopt_fp4 \
57
+ --trust-remote-code \
58
+ --speculative-algorithm DFLASH \
59
+ --speculative-draft-model-path "$D" \
60
+ --speculative-num-draft-tokens 8 \
61
+ --context-length 122880 \
62
+ --max-mamba-cache-size 8 \
63
+ --mamba-radix-cache-strategy extra_buffer \
64
+ --mamba-ssm-dtype bfloat16 \
65
+ --kv-cache-dtype fp8_e4m3 \
66
+ --attention-backend triton \
67
+ --chunked-prefill-size 1024 \
68
+ --mm-feature-transport cpu \
69
+ --cuda-graph-max-bs-decode 1 \
70
+ --mem-fraction-static 0.86 \
71
+ --max-running-requests 4 \
72
+ --reasoning-parser qwen3 \
73
+ --tool-call-parser qwen3_coder \
74
+ --host 0.0.0.0 --port 8080
75
+ ```
76
+
77
+ Start it detached, and keep it that way — the service must survive the terminal that launched it:
78
+
79
+ ```bash
80
+ cd /root/sj-tmp
81
+ setsid nohup ./serve.sh > serve.log 2>&1 < /dev/null &
82
+ ```
83
+
84
+ ## 3. Flags, and why each one is there
85
+
86
+ | Flag | Why |
87
+ | --- | --- |
88
+ | `--quantization modelopt_fp4` | The checkpoint is NVFP4 (ModelOpt). Wrong value → shape assertion while loading. |
89
+ | `--speculative-algorithm DFLASH` + `--speculative-draft-model-path` | A small draft model proposes tokens; the target verifies them. Cheapest latency win on one card. |
90
+ | `--speculative-num-draft-tokens 8` | 4–8 is the useful range; watch `accept len` in the decode log. |
91
+ | `--mamba-radix-cache-strategy extra_buffer` | Hybrid GDN models need this variant for prefix caching; the `*_lazy` strategies assert under speculative decoding. |
92
+ | `--mamba-ssm-dtype bfloat16` | Halves the mamba state size — the thing that caps concurrency. |
93
+ | `--kv-cache-dtype fp8_e4m3` | Doubles the tokens the KV pool holds at a small quality cost. |
94
+ | `--attention-backend triton` | Avoids a flashinfer attention path on sm_120; the JIT then compiles less. |
95
+ | `--chunked-prefill-size 1024` | Bounds the prefill burst so long prompts do not spike memory. |
96
+ | `--mm-feature-transport cpu` | Multimodal feature transport off the GPU; only exists from 0.5.21. |
97
+ | `--cuda-graph-max-bs-decode 1` | Capture CUDA graphs only up to this batch; more concurrency needs a bigger value and more memory. |
98
+ | `--mem-fraction-static 0.86` | Fraction of the card the server may use. Everything else (KV pool) is sized from what remains. |
99
+ | `--reasoning-parser qwen3` | Splits `…` into `reasoning_content`. |
100
+ | `--tool-call-parser qwen3_coder` | Turns the model's tool-call syntax into OpenAI `tool_calls`. |
101
+ | `--host 0.0.0.0 --port 8080` | The port the platform exposes; binding to `127.0.0.1` makes it unreachable from outside. |
102
+
103
+ ## 4. Memory arithmetic (read these four log lines)
104
+
105
+ ```
106
+ Load weight end. elapsed=32.91 s, type=… quant=modelopt_fp4, quant_algo=NVFP4, avail mem=11.89 GB
107
+ Mamba Cache is allocated. max_mamba_cache_size: 8, conv_state 0.02GB, ssm_state 0.63GB …
108
+ KV Cache is allocated. dtype torch.float8_e4m3fn, #tokens: 45259, K size: 0.22 GB, V size: 0.22 GB
109
+ max_running_requests is capped to 1 by the mamba state cache …
110
+ max_total_num_tokens=45259, chunked_prefill_size=1024, max_running_requests=1, context_len=122880, available_gpu_mem=3.18 GB
111
+ ```
112
+
113
+ Derived rules of thumb for a 32GB card serving 27B@4bit + draft:
114
+
115
+ - mamba (state) cache: `ssm_state` ≈ **79MB per slot**, and each request takes ~5 slots → 8 slots = 1 request, 16 ≈ 2, 24 ≈ 3. Raising concurrency is a direct trade against KV tokens.
116
+ - KV pool ≈ 45k tokens here; a 122880 context window means one long conversation does not fit. If you need more effective context, raise `--mem-fraction-static`, drop `--kv-cache-dtype` to the default, or add SGLang's hierarchical cache (HiCache) so host RAM becomes a second-level prefix cache.
117
+ - `--cuda-graph-max-bs-decode` should be at least the concurrency you intend, or larger batches run without a captured graph.
118
+
119
+ ## 5. Startup timeline and restart behaviour
120
+
121
+ - Cold start: weights ~30–40s, then **FlashInfer/`sgl_kernel` JIT compilation — the long pole**. With `MAX_JOBS=3` and a 60GB container a first start ran well past 20 minutes; a rebuild after changing compiler flags is faster but still minutes.
122
+ - Artifacts are cached in `~/.cache/sglang` (`jit/` and `flashinfer/`), plus flashinfer's own `~/.cache/sglang/.cache/flashinfer/<version>/…`. **Do not wipe them** — a restart with a warm cache reaches “The server is fired up and ready to roll!” in about 4 minutes.
123
+ - If you suspect a hung build, check progress cheaply: `ps -eo comm | grep -cE 'nvcc|cicc'` and `find ~/.cache/sglang -name '*.so' | wc -l`.
124
+ - The service writes only to `serve.log`; auditing a failed start is `grep -inE 'Killed|error:|Traceback|ninja:' serve.log | tail -20` — the reason is always *above* the last line.
@@ -0,0 +1,100 @@
1
+ # Verification
2
+
3
+ A model server is done when five things answer correctly. Run them in this order; stop at the first failure and read `serve.log`.
4
+
5
+ Set `BASE=http://<host>:<public-port>`.
6
+
7
+ ## 1. The process is alive and listening
8
+
9
+ ```bash
10
+ curl -s -o /dev/null -w '%{http_code}\n' $BASE/health # 200
11
+ # from inside the container, if the public address looks dead:
12
+ curl -s -o /dev/null -w 'local=%{http_code}\n' http://127.0.0.1:8080/health
13
+ ```
14
+
15
+ Local 200 but a dead public address means the **port mapping**, not the server.
16
+
17
+ ## 2. The model is registered
18
+
19
+ ```bash
20
+ curl -s $BASE/v1/models
21
+ # {"object":"list","data":[{"id":"<served-model-name>",…,"max_model_len":122880}]}
22
+ ```
23
+
24
+ ## 3. Tool calling works
25
+
26
+ ```bash
27
+ cat > req.json <<'JSON'
28
+ {"model":"<served-model-name>",
29
+ "messages":[{"role":"system","content":"Use the provided tools when needed."},
30
+ {"role":"user","content":"北京今天天气怎么样?请调用工具查询。"}],
31
+ "tools":[{"type":"function","function":{"name":"get_weather","description":"查询指定城市的当前天气",
32
+ "parameters":{"type":"object","properties":{"city":{"type":"string"}},"required":["city"]}}}],
33
+ "tool_choice":"auto","temperature":0,"max_tokens":256}
34
+ JSON
35
+ curl -s -H 'Content-Type: application/json' -d @req.json $BASE/v1/chat/completions
36
+ ```
37
+
38
+ Expect `"finish_reason":"tool_calls"` and a parsed call:
39
+
40
+ ```json
41
+ "tool_calls":[{"id":"call_…","type":"function",
42
+ "function":{"name":"get_weather","arguments":"{\"city\": \"北京\"}"}}]
43
+ ```
44
+
45
+ With a reasoning parser the chain of thought sits in `reasoning_content` and the visible answer in `content`; a model that only emits think-tags means `--reasoning-parser` is missing or wrong for this family.
46
+
47
+ ## 4. Streaming works
48
+
49
+ ```bash
50
+ curl -sN -H 'Content-Type: application/json' -d @req.json $BASE/v1/chat/completions | head -c 800
51
+ ```
52
+
53
+ Expect `data: {...}` chunks whose deltas accumulate, then a final chunk with `usage`, then `data: [DONE]`.
54
+
55
+ ## 5. Prefix caching works
56
+
57
+ Send the same request a second time (any long, identical prefix) and read the server log — no client change is needed:
58
+
59
+ ```bash
60
+ grep -oE '#cached-token: [0-9]+' serve.log | tail -4
61
+ ```
62
+
63
+ A cached-token count close to the prompt length (`#cached-token: 320` out of 347) proves the radix cache is hitting. Zero on a genuine repeat means the strategy or the prompt identity is wrong.
64
+
65
+ ## 6. Know your real limits
66
+
67
+ ```bash
68
+ grep -nE 'capped|KV Cache is allocated|max_total_num_tokens' serve.log | tail -6
69
+ ```
70
+
71
+ Report these to the user verbatim: the effective `max_running_requests`, the KV token pool, and the reason for any cap. A single-GPU hybrid model commonly caps concurrency at 1 because of the state cache — say so instead of letting requests pile up silently.
72
+
73
+ ## Wiring a client
74
+
75
+ Any OpenAI-compatible client works:
76
+
77
+ ```python
78
+ from openai import OpenAI
79
+ client = OpenAI(base_url=f"{BASE}/v1", api_key="sk-local") # placeholder: the server has no --api-key
80
+ client.chat.completions.create(model="<served-model-name>",
81
+ messages=[{"role": "user", "content": "hi"}])
82
+ ```
83
+
84
+ If the runtime is Adelie itself, register the endpoint so it appears in the model picker — the placeholder key is fine:
85
+
86
+ ```bash
87
+ penguin config model add --root <data root> \
88
+ --provider custom --model-id <served-model-name> \
89
+ --base-url $BASE/v1 --client-type openai \
90
+ --context-window <context-length> --no-vision --api-key sk-local
91
+ ```
92
+
93
+ Re-run the same command with a new `--base-url` to move the entry when the public port changes.
94
+
95
+ ## Restart checklist
96
+
97
+ 1. Power the instance on; reopen the exposed port and read the (possibly new) public address.
98
+ 2. `cd /root/sj-tmp; setsid nohup ./serve.sh > serve.log 2>&1 < /dev/null &`
99
+ 3. Wait until the log says `The server is fired up and ready to roll!` (minutes with a warm JIT cache).
100
+ 4. Repeat steps 1–5 above, then update every client with the new address.