MLX
sglang
quantization
zml
cuda
apple-silicon
macos
inference
eschamoe
qwen3
qwen3-moe
mixture-of-experts
Instructions to use EschaLabs/escha-runtime-qwen3moe with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- MLX
How to use EschaLabs/escha-runtime-qwen3moe with MLX:
# Download the model from the Hub pip install huggingface_hub[hf_xet] huggingface-cli download --local-dir escha-runtime-qwen3moe EschaLabs/escha-runtime-qwen3moe
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- LM Studio
- Atomic Chat
File size: 23,262 Bytes
90cf55e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 | #!/usr/bin/env bash
# Serve a Qwen3.6 / Qwen3.5 -35B-A3B 2-bit eschamoe export with escha-sglang, OpenAI-compatible,
# usable by any OpenAI-compatible client. eschamoe quant is auto-detected from config.json.
#
# Linux x86-64 + NVIDIA CUDA 12.x only (native or WSL2). See README.md.
#
# Required: set MODEL to your downloaded model dir (a flat folder of safetensors +
# config + tokenizer β the model repo has no nested subfolder). Everything else has
# safe defaults for a 24 GB card.
# MODEL=./Qwen3.6-35B-A3B-Escha-W2 SERVED_NAME=escha-qwen36-35b-a3b-w2 \
# bash serve.sh
set -euo pipefail
# ---- paths ----------------------------------------------------------------------------------
MODEL=${MODEL:?set MODEL=/path/to/your/eschamoe export dir}
SERVED_NAME=${SERVED_NAME:-escha-qwen36-35b-a3b-w2} # opencode/LM Studio must use this id verbatim
VENV=${VENV:-} # optional: path to your venv; if set, we activate it
ESCHA_SRC=${ESCHA_SRC:-} # optional: path to escha *source* tree's src/ (only if not pip-installed)
# ---- server ---------------------------------------------------------------------------------
HOST=${HOST:-127.0.0.1} # 0.0.0.0 to serve other machines β do this behind a VPN or tunnel and
# set API_KEY; never open the raw port to the internet.
PORT=${PORT:-30000}
API_KEY=${API_KEY:-} # REQUIRED if HOST=0.0.0.0 / exposed: clients must send this as the
# Bearer key. Empty = no auth (fine only for localhost).
MEM=${MEM:-0.78} # mem-fraction-static. 0.78 is the right default for a 24 GB card at the
# CTXLEN below: mem-fraction-static sizes the pool the KV cache is
# carved from, so too LOW is the common failure β 0.70 dies at startup
# with "Not enough memory. Please try to increase --mem-fraction-static"
# (measured, 4090 + CTXLEN 32768). Lower it only together with CTXLEN;
# ~0.60-0.64 is fine for short-generation throughput work.
CTXLEN=${CTXLEN:-32768} # KV is cheap on this hybrid model; raise once it works (see guide Β§2.5)
CHUNK=${CHUNK:-2048} # chunked-prefill-size
MAXREQ=${MAXREQ:-0} # max concurrent requests; 0 = auto. Lower (e.g. 4) frees Mamba pool for KV.
# NOTE: this is CLAMPED by the mamba req-slot pool β
# effective = min(MAXREQ, MAXMAMBA // ratio), ratio ~= 4 with the radix
# cache ON (RADIX=1) and ~= 1 with RADIX=0. So MAXREQ=8 MAXMAMBA=8 RADIX=1
# silently runs only ~2 concurrent. For bs 16: RADIX=0 MAXMAMBA=16, or keep
# radix and set MAXMAMBA=64. A clamp is logged at startup ("max_num_reqs ...").
MAXMAMBA=${MAXMAMBA:-0} # --max-mamba-cache-size; 0 = auto. Raise (e.g. 32) for deeper prefix cache /
# to lift the MAXREQ clamp above.
MAMBA_RATIO=${MAMBA_RATIO:-} # --mamba-full-memory-ratio; empty = default (0.9). Lower (e.g. 0.6) frees the
# pool for spec + CUDA-graph capture headroom. Set when SPEC=1 + GRAPHS=1.
# ---- behavior -------------------------------------------------------------------------------
GRAPHS=${GRAPHS:-1} # 1 = CUDA graphs ON (DEFAULT, 2026-07-23). This hybrid MoE launches many
# small kernels per token (30 GDN layers + 40 MoE layers + W8A16 GEMVs),
# so eager decode is launch-bound: graphs are worth ~4.4x (measured
# 16 GB sm_120: 27.7 -> 122 tok/s bs1) and captured cleanly on every
# card tested (sm_89 4090, sm_120 5060 Ti / 5090). Set GRAPHS=0 only
# to debug a capture failure β expect ~4x slower decode there.
CUDA_GRAPH_BS=${CUDA_GRAPH_BS:-"1 2 4 8 16"} # batch sizes to capture when GRAPHS=1. With SPEC=1 keep
# max(bs) * DRAFT_TOKENS <= 64 (escham_moe verify-op limit) β e.g. "1 2 4 8" at
# draft=4 for an 8-user pool.
THINK=${THINK:-1} # 1 = thinking-ON + --reasoning-parser qwen3 (simplest, always works).
# 0 = thinking-OFF (faster; clean `content`): patches the chat
# template default to non-thinking AND drops the reasoning parser.
# A per-request chat_template_kwargs:{"enable_thinking":false} is
# honoured on a THINK=1 server β `content` is populated and tool
# calls are parsed, so THINK=0 is not required for tool-calling
# clients that disable thinking. With thinking ON, the reasoning
# arrives in `reasoning_content` and the answer in `content`:
# read both.
RADIX=${RADIX:-1} # 1 = prefix caching ON (MambaRadixCache) β multi-turn agent reuse.
ATTN_BACKEND=${ATTN_BACKEND:-} # full-attention backend override. Empty = sglang default (fine on
# Ada/4090). REQUIRED on consumer Blackwell (RTX 50 / sm_120): the
# default resolves to flashinfer but the fork asserts triton/trtllm_mha
# /fa4 for hybrid-GDN on Blackwell -> set ATTN_BACKEND=triton.
TOOL_PARSER=${TOOL_PARSER:-qwen3_coder} # matches <function=..>/<parameter=..> XML. Verify w/ curl.
REASONING_PARSER=${REASONING_PARSER:-qwen3}
ENABLE_CLP=${ENABLE_CLP:-0} # 1 = --enable-custom-logit-processor (per-request thinking_budget cap etc.)
JSON_WS=${JSON_WS:-1} # 1 = --constrained-json-disable-any-whitespace (DEFAULT).
# With permissive whitespace, a json_schema `integer`/`number`
# property that follows a string property can take a spurious
# leading `-`: the grammar allows a "\n " branch after the
# colon, and from that off-distribution state the sign/digit
# choice is near-tied. Measured on a 5080: 2-3 of 3 identical
# greedy requests returned a NEGATIVE population for Tokyo --
# schema-valid, so no client validation catches it. Disabling
# any-whitespace removed it 0/6 and 0/3. Compact JSON is what
# structured-output consumers want anyway. `JSON_WS=0` opts out.
DETERMINISTIC=${DETERMINISTIC:-0} # 1 = --enable-deterministic-inference. Greedy is NOT
# bit-reproducible across requests by default (fp16 accumulation
# order): long reasoning chains diverge once a near-tie flips.
# Set 1 for eval reproducibility; expect a throughput cost.
# NOT AVAILABLE on consumer Blackwell (RTX 50 / sm_120): the
# deterministic attention kernel asks for 104 KB of shared memory
# per block, above the sm_120 limit, and the server exits at
# startup. Works on Ampere / Ada / Hopper.
# ---- speculative decoding: native MTP / NEXTN (self-draft) ----------------------------------
# Large single-stream + low-concurrency speedup (bs1 ~1.55-1.77x; still +1.1x at 8 concurrent; turns
# net-NEGATIVE only above ~12 concurrent -> the right default for a <=8-person team). The draft is the
# model's OWN pretrained MTP head dequantized to fp16 (NOT a 2-bit quant β quantizing the draft collapses
# acceptance); accept-len ~3.5 at draft=4. Evidence: docs/SOTA/serving_perf_notes.md (2026-06-29).
SPEC=${SPEC:-0} # 1 = enable MTP/NEXTN speculative decode. Needs DRAFT; pair with GRAPHS=1.
# NOTE: the published W2 model repo does NOT include an MTP draft export
# (the mtp.* head in the checkpoint is not servable as a draft), so
# SPEC=1 is unusable with that release as shipped β all published
# serving numbers are non-speculative.
DRAFT=${DRAFT:-} # MTP draft dir (fp16, the *-MTP-draft export). REQUIRED when SPEC=1.
STEPS=${STEPS:-3} # --speculative-num-steps (draft=4 / steps=3 is the measured sweet spot)
TOPK=${TOPK:-1} # --speculative-eagle-topk
DRAFT_TOKENS=${DRAFT_TOKENS:-4} # --speculative-num-draft-tokens (M = MAXREQ * this must stay <= 64)
MAMBA_SCHED=${MAMBA_SCHED:-} # --mamba-scheduler-strategy. AUTO -> extra_buffer when SPEC=1 + radix ON
# (this hybrid-SSM arch REQUIRES extra_buffer + Spec-V2 to run radix +
# spec together; otherwise the default no_buffer is used).
# ---- environment the escha stack needs ------------------------------------------------------
[[ -n "$VENV" && -f "$VENV/bin/activate" ]] && source "$VENV/bin/activate"
PYBIN=$(command -v python)
TORCH_LIB=$("$PYBIN" -c 'import torch,os;print(os.path.join(os.path.dirname(torch.__file__),"lib"))')
export LD_LIBRARY_PATH="$TORCH_LIB${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" # the reference codec_ext needs libc10
[[ -n "$ESCHA_SRC" ]] && export PYTHONPATH="$ESCHA_SRC${PYTHONPATH:+:$PYTHONPATH}"
export SGLANG_MAMBA_CONV_DTYPE=float16 # must match --dtype float16 or causal_conv1d crashes
export SGLANG_DISABLE_CUDNN_CHECK=1
# These qwen3_5_moe Escha checkpoints are TEXT-ONLY (the vision tower is in the quant `ignore`
# list β the checkpoint has zero visual.* tensors). Default to NOT instantiating the vision tower:
# it saves ~0.8 GB VRAM on every card, and prevents an image request from silently decoding through
# a random-init tower (garbage instead of an error). Override SGLANG_VLM_TEXT_ONLY=0 only if you
# ever serve a checkpoint that actually ships vision weights.
export SGLANG_VLM_TEXT_ONLY=${SGLANG_VLM_TEXT_ONLY:-1}
export PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True # correct var name β cuts reserved-vs-allocated ~1.3GB
export PYTORCH_ALLOC_CONF=expandable_segments:True # (alias on newer torch; keep both)
export HF_HUB_OFFLINE=1
# ---- int8-as-stored profile -----------------------------------------------------------------
# The runtime can keep lm_head/embed/attn/GDN/shared-expert INT8 in VRAM ("int8-as-stored",
# served via the w8a16 kernel) instead of dequantizing to fp16 at load. The right choice depends
# on CONCURRENCY, not just VRAM. At bs1 decode is memory-bandwidth-bound and int8 wins, by a
# card-dependent margin (measured 2026-07-27): +21β24% tok/s on an RTX 5090, +35β42% on an
# RTX 3090 β and β2.2 GB VRAM either way. Past the crossover (between concurrency 4 and 8)
# decode turns compute-bound and it inverts: β26% aggregate at bs16 on the 5090, to β52% on
# other cards. Pick with one knob:
# INT8=auto (default) β VRAM-threshold rule: ON at β€ SGLANG_INT8_AUTO_VRAM_GB (24), OFF above.
# INT8=on β force ON: single-user / latency-sensitive serving on ANY card size
# (e.g. a 32 GB RTX 5090 serving one user leaves ~20% single-stream on the
# table under auto).
# INT8=off β force OFF: batched / high-concurrency throughput serving on >24 GB cards.
INT8=${INT8:-auto}
case "$INT8" in
on) for _v in LM_HEAD EMBED ATTN GDN SHEXP; do export "SGLANG_INT8_${_v}=1"; done ;;
off) for _v in LM_HEAD EMBED ATTN GDN SHEXP; do export "SGLANG_INT8_${_v}=0"; done ;;
auto) : ;; # leave the five SGLANG_INT8_* vars as the caller set them (or unset -> AUTO)
*) echo "[serve] ERROR: INT8 must be auto|on|off (got '$INT8')" >&2; exit 1 ;;
esac
# The fused MoE decode kernel is the default, always-preferred path (auto-detected; falls back
# to the slower per-expert sorted dispatch only if the fused op/kernel build is unavailable).
# DEFAULT-ON fail-loud (2026-07-23): the silent fallback is ~40x slower β exactly what a
# benchmarking or production user must not hit unknowingly. Set ESCHA_MOE_REQUIRE_FUSED=0
# only while debugging a box where the fused path genuinely cannot run.
# Renamed 2026-07-25: DI_ESCHA_MOE_REQUIRE_FUSED -> ESCHA_MOE_REQUIRE_FUSED (every public
# knob is ESCHA_*). The old name is still honored, with a notice, so existing launch
# scripts keep working.
if [[ -n "${DI_ESCHA_MOE_REQUIRE_FUSED:-}" && -z "${ESCHA_MOE_REQUIRE_FUSED:-}" ]]; then
echo "[serve] DI_ESCHA_MOE_REQUIRE_FUSED is deprecated; use ESCHA_MOE_REQUIRE_FUSED"
ESCHA_MOE_REQUIRE_FUSED="$DI_ESCHA_MOE_REQUIRE_FUSED"
fi
export ESCHA_MOE_REQUIRE_FUSED=${ESCHA_MOE_REQUIRE_FUSED:-1}
# Mixed-bit (K2 gate_up / K3 down) fused epilogue. Worth +10% single-user decode
# on this model β measured 2026-07-25 on an RTX 4090 (sm_89) with the shipped
# wheel: 207.4-208.6 -> 228.5-228.8 tok/s at bs1, identical answers (arithmetic,
# recall, long-context needle). It was opt-in only so that older kernel builds
# without the capability probe would not trip; the probe
# (`escham_moe_prehad_k3_available`) is present in this build and auto-falls-back
# when absent, so ON is the right default. `ESCHA_ENABLE_K3_FUSED_EPI=0` opts out.
export ESCHA_ENABLE_K3_FUSED_EPI=${ESCHA_ENABLE_K3_FUSED_EPI:-1}
# Other passthrough knobs: ESCHA_FORCE_SORTED_MOE=1 forces the slower validated fallback
# (debug/parity only); ESCHA_ROUTE=lovelace|blackwell overrides the kernel's auto-selected
# launch geometry (bit-identical either way; blackwell can win even on pre-sm_100 cards).
# ptxas discovery (rewritten 2026-07-25 after two field reports).
#
# * Triton β which torch already pulls in β SHIPS a matching ptxas at
# site-packages/triton/backends/nvidia/bin/ptxas. Probe that FIRST: a CUDA
# toolkit is NOT a requirement of this runtime, and the old error message
# sent users off to install one they did not need.
# * Validate whatever the caller exported. A common shell profile sets
# TRITON_PTXAS_PATH to the CUDA *bin directory*; `[[ -x <dir> ]]` is TRUE for
# directories, so the old check accepted it and triton then died mid-startup
# with `PermissionError: [Errno 13] Permission denied: /usr/local/cuda-12.8/bin`.
# Require a regular file (-f) AND executable (-x); if a directory was given,
# try <dir>/ptxas before discarding it.
_ptxas_ok() { [[ -n "${1:-}" && -f "$1" && -x "$1" ]]; }
if ! _ptxas_ok "${TRITON_PTXAS_PATH:-}"; then
if [[ -n "${TRITON_PTXAS_PATH:-}" ]]; then
if _ptxas_ok "${TRITON_PTXAS_PATH%/}/ptxas"; then
TRITON_PTXAS_PATH="${TRITON_PTXAS_PATH%/}/ptxas"
echo "[serve] TRITON_PTXAS_PATH was a directory; using $TRITON_PTXAS_PATH"
else
echo "[serve] ignoring TRITON_PTXAS_PATH='$TRITON_PTXAS_PATH' (not an executable file)"
TRITON_PTXAS_PATH=""
fi
fi
if [[ -z "${TRITON_PTXAS_PATH:-}" ]]; then
for p in \
"$(python -c 'import os,triton;print(os.path.join(os.path.dirname(triton.__file__),"backends","nvidia","bin","ptxas"))' 2>/dev/null)" \
/usr/local/cuda*/bin/ptxas \
"$(command -v ptxas 2>/dev/null)"; do
_ptxas_ok "$p" && { TRITON_PTXAS_PATH="$p"; break; }
done
fi
export TRITON_PTXAS_PATH
fi
: "${TRITON_PTXAS_PATH:?no usable ptxas found. It normally ships inside the venv with triton (a torch dependency) β check that \`python -c \"import triton\"\` works. Otherwise point TRITON_PTXAS_PATH at a ptxas BINARY (not its directory).}"
# Only if you hit it: the first time triton compiles a kernel (during warmup AND during CUDA-graph
# capture), the multi-GB scheduler must fork() to spawn ptxas. Under the default overcommit heuristic
# that fork can fail -> the server dies with a triton JIT "Cannot allocate memory" crash. Setting
# vm.overcommit_memory=1 fixes it. Many boxes never hit this (a warm triton cache, or enough free RAM
# to satisfy the fork), so it is advisory, not mandatory. In a container the sysctl is often read-only
# ("sysctl: setting key ...: Read-only file system") β then it is the HOST's job, not something you
# can set from inside; the server usually starts fine there anyway.
# (Advisory phrased WITHOUT the literal errno string so log-watchers grepping for the real
# crash message don't false-positive on this note β friction 2026-07-23 F4.)
if [[ "$(cat /proc/sys/vm/overcommit_memory 2>/dev/null)" == "0" ]]; then
echo "[serve] note: if startup dies with a triton JIT memory-allocation failure at kernel-" >&2
echo "[serve] compile time, set 'sudo sysctl -w vm.overcommit_memory=1' (persist via" >&2
echo "[serve] /etc/sysctl.d/99-escha.conf). In a read-only container this must be set" >&2
echo "[serve] on the host; otherwise it's safe to ignore." >&2
fi
# Warn when the GPU is already substantially occupied (a foreign server / another model on a
# shared box is the #1 cause of an immediate, confusing pool-allocation OOM at startup).
_USED_MB=$(nvidia-smi --query-gpu=memory.used --format=csv,noheader,nounits 2>/dev/null | head -1 | tr -dc '0-9')
if [[ -n "$_USED_MB" && "$_USED_MB" -gt 1024 ]]; then
echo "[serve] WARNING: GPU already has ${_USED_MB} MiB in use by other processes (nvidia-smi)." >&2
echo "[serve] The static pool sizing assumes a free GPU β expect an allocation failure" >&2
echo "[serve] if another server is resident. Check: nvidia-smi" >&2
fi
# ---- assemble flags -------------------------------------------------------------------------
GRAPH_ARGS=(--disable-piecewise-cuda-graph)
[[ "$GRAPHS" == "1" ]] && GRAPH_ARGS+=(--cuda-graph-bs $CUDA_GRAPH_BS) || GRAPH_ARGS+=(--disable-cuda-graph)
POOL_ARGS=()
[[ "$MAXREQ" != "0" ]] && POOL_ARGS+=(--max-running-requests "$MAXREQ")
[[ "$MAXMAMBA" != "0" ]] && POOL_ARGS+=(--max-mamba-cache-size "$MAXMAMBA")
[[ -n "$MAMBA_RATIO" ]] && POOL_ARGS+=(--mamba-full-memory-ratio "$MAMBA_RATIO")
RADIX_ARGS=(); [[ "$RADIX" == "0" ]] && RADIX_ARGS+=(--disable-radix-cache)
ATTN_ARGS=(); [[ -n "$ATTN_BACKEND" ]] && ATTN_ARGS+=(--attention-backend "$ATTN_BACKEND")
AUTH_ARGS=(); [[ -n "$API_KEY" ]] && AUTH_ARGS+=(--api-key "$API_KEY")
# speculative MTP/NEXTN draft (the model's own self-draft head)
SPEC_ARGS=()
if [[ "$SPEC" == "1" ]]; then
: "${DRAFT:?SPEC=1 requires DRAFT=/path/to/<model>-MTP-draft (the fp16 NEXTN head, NOT a 2-bit quant)}"
[[ -d "$DRAFT" ]] || { echo "[serve] ERROR: SPEC=1 but DRAFT dir not found: $DRAFT" >&2; exit 1; }
SPEC_ARGS+=(--speculative-algorithm NEXTN --speculative-draft-model-path "$DRAFT"
--speculative-num-steps "$STEPS" --speculative-eagle-topk "$TOPK"
--speculative-num-draft-tokens "$DRAFT_TOKENS")
if [[ "$GRAPHS" == "1" ]]; then # enforce the escham_moe verify-op limit: max running batch * draft <= 64
maxbs=0; for b in $CUDA_GRAPH_BS; do (( b > maxbs )) && maxbs=$b; done
if (( maxbs * DRAFT_TOKENS > 64 )); then
echo "[serve] WARNING: max cuda-graph bs ($maxbs) * draft_tokens ($DRAFT_TOKENS) > 64 β escham_moe only" >&2
echo "[serve] captures M<=64; trim CUDA_GRAPH_BS (e.g. \"1 2 4 8\" at draft=4)." >&2
fi
fi
# radix cache + spec on this hybrid (mamba) arch needs the extra-buffer scheduler + Spec-V2.
if [[ "$RADIX" != "0" ]]; then
export SGLANG_ENABLE_SPEC_V2=1
MAMBA_SCHED=${MAMBA_SCHED:-extra_buffer}
echo "[serve] radix+spec: exporting SGLANG_ENABLE_SPEC_V2=1, --mamba-scheduler-strategy $MAMBA_SCHED" >&2
fi
fi
SCHED_ARGS=(); [[ -n "$MAMBA_SCHED" ]] && SCHED_ARGS+=(--mamba-scheduler-strategy "$MAMBA_SCHED")
CLP_ARGS=(); [[ "$ENABLE_CLP" == "1" ]] && CLP_ARGS+=(--enable-custom-logit-processor)
if [[ "$HOST" != "127.0.0.1" && -z "$API_KEY" ]]; then
echo "[serve] WARNING: HOST=$HOST is exposed but API_KEY is empty β no auth. Set API_KEY." >&2
fi
REASON_ARGS=(); TEMPLATE_ARGS=()
if [[ "$THINK" == "1" ]]; then
REASON_ARGS+=(--reasoning-parser "$REASONING_PARSER")
else
# thinking-OFF: generate a patched chat template (default-closed <think>) from the model's own.
NOTHINK="${TMPDIR:-/tmp}/escha_nothink_$(basename "$MODEL").jinja"
"$PYBIN" - "$MODEL" "$NOTHINK" <<'PY'
import json, os, sys
mdir, out = sys.argv[1], sys.argv[2]
src = os.path.join(mdir, "chat_template.jinja")
tmpl = open(src).read() if os.path.exists(src) else json.load(open(os.path.join(mdir,"tokenizer_config.json")))["chat_template"]
# Flip the qwen "enable_thinking" default from ON to OFF. Two common phrasings handled:
cands = [
("{%- if enable_thinking is defined and enable_thinking is false %}\n {{- '<think>\\n\\n</think>\\n\\n' }}\n {%- else %}\n {{- '<think>\\n' }}\n {%- endif %}",
"{%- if enable_thinking is defined and enable_thinking is true %}\n {{- '<think>\\n' }}\n {%- else %}\n {{- '<think>\\n\\n</think>\\n\\n' }}\n {%- endif %}"),
# froggeric/Qwen-Fixed-Chat-Templates (v21.x) hoists the toggle into a single
# default-assignment near the top instead of an inline if/else at the generation
# prompt, so the block above is absent and THINK=0 has nothing to patch. Flipping the
# default is exactly equivalent here (ns_state.thinking is seeded from it).
("{%- set enable_thinking = enable_thinking if enable_thinking is defined else true %}",
"{%- set enable_thinking = enable_thinking if enable_thinking is defined else false %}"),
]
patched = tmpl
for old, new in cands:
if old in patched: patched = patched.replace(old, new); break
else:
sys.exit("THINK=0: could not find the enable_thinking block to patch in this model's chat "
"template. Serve with THINK=1, or hand-patch the template and pass it via --chat-template.")
open(out, "w").write(patched)
print(out)
PY
TEMPLATE_ARGS+=(--chat-template "$NOTHINK") # no reasoning parser when thinking is pre-closed
fi
echo "[serve] MODEL=$MODEL NAME=$SERVED_NAME $HOST:$PORT MEM=$MEM CTX=$CTXLEN GRAPHS=$GRAPHS THINK=$THINK RADIX=$RADIX SPEC=$SPEC"
echo "[serve] tool-call-parser=$TOOL_PARSER ptxas=$TRITON_PTXAS_PATH"
[[ "$SPEC" == "1" ]] && echo "[serve] MTP/NEXTN: draft=$DRAFT steps=$STEPS topk=$TOPK draft_tokens=$DRAFT_TOKENS cuda_graph_bs='$CUDA_GRAPH_BS'"
JSON_ARGS=()
[[ "$JSON_WS" == "1" ]] && JSON_ARGS+=(--constrained-json-disable-any-whitespace)
DET_ARGS=()
[[ "$DETERMINISTIC" == "1" ]] && DET_ARGS+=(--enable-deterministic-inference)
exec python -m sglang.launch_server \
--model-path "$MODEL" \
--served-model-name "$SERVED_NAME" \
--host "$HOST" --port "$PORT" \
--dtype float16 \
--mem-fraction-static "$MEM" \
--context-length "$CTXLEN" \
--chunked-prefill-size "$CHUNK" \
--sampling-backend pytorch \
--trust-remote-code \
--tool-call-parser "$TOOL_PARSER" \
"${REASON_ARGS[@]}" \
"${TEMPLATE_ARGS[@]}" \
"${POOL_ARGS[@]}" \
"${RADIX_ARGS[@]}" \
"${ATTN_ARGS[@]}" \
"${AUTH_ARGS[@]}" \
"${SPEC_ARGS[@]}" \
"${SCHED_ARGS[@]}" \
"${CLP_ARGS[@]}" \
"${GRAPH_ARGS[@]}" \
"${JSON_ARGS[@]}" \
"${DET_ARGS[@]}" \
--log-level info \
"$@" # forward any extra sglang flag verbatim
|