Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions docs/docs/advanced_features/server_arguments.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2306,6 +2306,24 @@ Please consult the documentation below and [server_args.py](https://github.com/s
<td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.05)"}}>Number of layers per group in offloading.</td>
<td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.02)"}}>`-1`</td>
<td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.05)"}}>Type: int</td>
</tr>
<tr>
<td style={{padding: "9px 12px", fontWeight: 500, backgroundColor: "rgba(255,255,255,0.02)"}}>`--ple-offload-embedding`</td>
<td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.05)"}}>Keep the Qwen4-Exp PLE n-gram embedding table in host memory instead of device memory; the gather kernel reads it from a host pointer. Enabled by default for BF16 Qwen4-Exp on CUDA; `--no-ple-offload-embedding` disables it.</td>
<td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.02)"}}>`None`</td>
<td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.05)"}}>Type: bool</td>
</tr>
<tr>
<td style={{padding: "9px 12px", fontWeight: 500, backgroundColor: "rgba(255,255,255,0.02)"}}>`--ple-offload-backend`</td>
<td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.05)"}}>Host storage for the offloaded PLE table: `pinned` (CPU pinned memory) or `file` (a sparse file-backed mmap under `--ple-offload-dir`, read directly by the gather kernel). Use `file` on unified-memory devices such as GB10 / DGX Spark, where pinned host memory comes out of the same pool as the weights; it requires a device that reports `cudaDevAttrPageableMemoryAccessUsesHostPageTables`.</td>
<td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.02)"}}>`pinned`</td>
<td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.05)"}}>Type: str</td>
</tr>
<tr>
<td style={{padding: "9px 12px", fontWeight: 500, backgroundColor: "rgba(255,255,255,0.02)"}}>`--ple-offload-dir`</td>
<td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.05)"}}>Directory for the file-backed PLE table when `--ple-offload-backend` is `file`. The file is sparse and reused across restarts; keep it on fast local storage (NVMe).</td>
<td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.02)"}}>`$SGLANG_CACHE_DIR/ple/<model path>`</td>
<td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.05)"}}>Type: str</td>
</tr>
<tr>
<td style={{padding: "9px 12px", fontWeight: 500, backgroundColor: "rgba(255,255,255,0.02)"}}>`--offload-num-in-group`</td>
Expand Down
25 changes: 25 additions & 0 deletions docs/docs/references/environment_variables.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,31 @@ SGLang supports various environment variables that can be used to configure its
<td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.05)"}}>Cache directory for model weights and other data. Also the default root for compiled-kernel caches: Triton, Inductor, FlashInfer, the CUDA driver and DeepGEMM are pointed under it unless their own env vars (`TRITON_CACHE_DIR`, `TORCHINDUCTOR_CACHE_DIR`, `FLASHINFER_WORKSPACE_BASE`, `CUDA_CACHE_PATH`, `SGLANG_DG_CACHE_DIR`) are set explicitly</td>
<td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.02)"}}><code>~/.cache/sglang</code></td>
</tr>
<tr>
<td style={{padding: "9px 12px", fontWeight: 500, backgroundColor: "rgba(255,255,255,0.02)"}}><code>SGLANG_QWEN4_PLE_FILE_DIR</code></td>
<td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.05)"}}>Default directory for the file-backed Qwen4-Exp PLE table (`--ple-offload-backend file`); one subdirectory per model path is created under it</td>
<td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.02)"}}><code>{SGLANG_CACHE_DIR}/ple</code></td>
</tr>
<tr>
<td style={{padding: "9px 12px", fontWeight: 500, backgroundColor: "rgba(255,255,255,0.02)"}}><code>SGLANG_QWEN4_PLE_FILE_PREFETCH</code></td>
<td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.05)"}}>Hint the page cache (`posix_fadvise(WILLNEED)`) with the rows a prefill-sized PLE gather is about to read from the file-backed table. Set to `0` to disable</td>
<td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.02)"}}><code>1</code></td>
</tr>
<tr>
<td style={{padding: "9px 12px", fontWeight: 500, backgroundColor: "rgba(255,255,255,0.02)"}}><code>SGLANG_QWEN4_PLE_FILE_SKIP_DEVICE_CHECK</code></td>
<td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.05)"}}>Skip the load-time check that the device reads pageable host memory through the host page tables (`cudaDevAttrPageableMemoryAccessUsesHostPageTables`) before using the file-backed PLE table; only for devices that cannot be queried</td>
<td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.02)"}}><code>0</code></td>
</tr>
<tr>
<td style={{padding: "9px 12px", fontWeight: 500, backgroundColor: "rgba(255,255,255,0.02)"}}><code>SGLANG_QWEN4_PLE_FILE_RSS_BUDGET_GB</code></td>
<td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.05)"}}>Cap (GiB) on the resident set of the file-backed PLE mapping. A row fault maps a whole page-cache folio, so the mapping otherwise creeps towards the full table and consumes the free memory that sizes the KV pool; over the cap the pages are dropped with `MADV_DONTNEED` and stay in the page cache. Set to `0` to disable</td>
<td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.02)"}}><code>8.0</code></td>
</tr>
<tr>
<td style={{padding: "9px 12px", fontWeight: 500, backgroundColor: "rgba(255,255,255,0.02)"}}><code>SGLANG_QWEN4_PLE_FILE_RSS_INTERVAL_S</code></td>
<td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.05)"}}>How often the file-backed PLE mapping's resident set is compared against `SGLANG_QWEN4_PLE_FILE_RSS_BUDGET_GB`</td>
<td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.02)"}}><code>30.0</code></td>
</tr>
<tr>
<td style={{padding: "9px 12px", fontWeight: 500, backgroundColor: "rgba(255,255,255,0.02)"}}><code>SGLANG_PREFETCH_BLOCK_SIZE_MB</code></td>
<td style={{padding: "9px 12px", backgroundColor: "rgba(255,255,255,0.05)"}}>Block size (in MB) for sequential checkpoint prefetch reads that warm the OS page cache before workers load weights via mmap</td>
Expand Down
6 changes: 6 additions & 0 deletions python/sglang/srt/configs/qwen4_exp.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ def __init__(
ngram_vocab_size_base=20000000,
make_ngram_vocab_size_divisible_by=128,
ple_offload_embedding=False,
ple_offload_backend="pinned",
ple_offload_dir=None,
ple_embedding_dtype=None,
index_share_for_mtp_iteration=True,
rope_parameters=None,
Expand Down Expand Up @@ -70,6 +72,10 @@ def __init__(
self.ngram_vocab_size_base = ngram_vocab_size_base
self.make_ngram_vocab_size_divisible_by = make_ngram_vocab_size_divisible_by
self.ple_offload_embedding = ple_offload_embedding
# Host storage for the offloaded table: "pinned" or "file" (a sparse
# file-backed mmap for unified-memory devices); see --ple-offload-backend.
self.ple_offload_backend = ple_offload_backend
self.ple_offload_dir = ple_offload_dir
# "float8_e4m3fn" keeps fp8 PLE tables fp8-resident; text_config-scoped.
self.ple_embedding_dtype = ple_embedding_dtype
# MTP draft decode steps reuse the draft-extend indexer selection
Expand Down
12 changes: 12 additions & 0 deletions python/sglang/srt/environ.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,18 @@ class Envs:
# Bitwise-exact, shape-guarded Qwen4 PLE decode fusion. Unsupported inputs
# and phases fall back to the original implementation.
SGLANG_ENABLE_QWEN4_PLE_FUSION = EnvBool(True)
# --ple-offload-backend file: where the sparse, file-backed PLE table lives
# (deterministic name, reused across restarts), whether prefill-sized
# gathers hint the page cache first, and an escape hatch for the device
# attribute check (pageable host memory reachable through host page tables).
SGLANG_QWEN4_PLE_FILE_DIR = EnvStr(lambda: _default_cache_subdir("ple"))
SGLANG_QWEN4_PLE_FILE_PREFETCH = EnvBool(True)
SGLANG_QWEN4_PLE_FILE_SKIP_DEVICE_CHECK = EnvBool(False)
# Faulting rows in maps whole page-cache folios, so the mapping creeps
# towards full residency (~45 KB/token) and eats the free memory that
# sizes the KV pool. Cap its resident set; 0 disables the trim.
SGLANG_QWEN4_PLE_FILE_RSS_BUDGET_GB = EnvFloat(8.0)
SGLANG_QWEN4_PLE_FILE_RSS_INTERVAL_S = EnvFloat(30.0)
# Select the FP8 (deep_gemm) tokenwise QSA indexer; only the BF16 reference
# path is ported, so setting this fails loudly instead of degrading.
SGLANG_QWEN_DSA_USE_FP8_INDEXER = EnvBool(False)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,27 @@ def load_model_with_memory_saver(
model_config.hf_text_config.ple_offload_embedding = (
server_args.ple_offload_embedding
)
model_config.hf_text_config.ple_offload_backend = (
server_args.ple_offload_backend
)
if server_args.ple_offload_backend != "file":
model_config.hf_text_config.ple_offload_dir = (
server_args.ple_offload_dir
)
else:
from sglang.srt.models.qwen4_exp_ple_table import (
check_file_backend_supported,
default_ple_table_dir,
)

model_config.hf_text_config.ple_offload_dir = (
server_args.ple_offload_dir
or default_ple_table_dir(server_args.model_path)
)
if server_args.ple_offload_embedding and device == "cuda":
check_file_backend_supported(
torch.cuda.current_device() if torch.cuda.is_available() else 0
)

enable_cpu_backup = server_args.enable_weights_cpu_backup or (
is_draft_worker and server_args.enable_draft_weights_cpu_backup
Expand Down
43 changes: 33 additions & 10 deletions python/sglang/srt/models/qwen4_exp.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@
Qwen3_5LinearDecoderLayer,
)
from sglang.srt.models.qwen3_vl import Qwen3VLForConditionalGeneration
from sglang.srt.models.qwen4_exp_ple_table import (
allocate_ple_host_table,
make_ple_file_prefetcher,
make_ple_file_rss_trimmer,
)
from sglang.srt.runtime_context import get_parallel
from sglang.srt.utils import logger

Expand Down Expand Up @@ -748,7 +753,7 @@ def _gather_ple_embedding_from_pinned_kernel(


class Qwen4ExpPinnedHostEmbedding(VocabParallelEmbedding):
"""PLE table read directly from pinned host memory.
"""PLE table read directly from host memory (pinned, or a file-backed mmap).

The table stays in its checkpoint storage dtype (fp8 with a per-tensor
weight_scale for fp8 checkpoints, bf16 otherwise); gathers emit bf16.
Expand All @@ -773,7 +778,13 @@ class Qwen4ExpPinnedHostEmbedding(VocabParallelEmbedding):
"num_added_embeddings_per_partition",
)

def __init__(self, embedding: VocabParallelEmbedding) -> None:
def __init__(
self,
embedding: VocabParallelEmbedding,
*,
backend: str = "pinned",
table_dir: Optional[str] = None,
) -> None:
nn.Module.__init__(self)
if not isinstance(embedding.quant_method, UnquantizedEmbeddingMethod):
raise NotImplementedError(
Expand All @@ -795,15 +806,23 @@ def __init__(self, embedding: VocabParallelEmbedding) -> None:
self.quant_method = None

source_weight = embedding.weight
cpu_weight = nn.Parameter(
torch.empty(
source_weight.shape,
dtype=source_weight.dtype,
device="cpu",
pin_memory=True,
host_table = allocate_ple_host_table(
shape=source_weight.shape,
dtype=source_weight.dtype,
backend=backend,
table_dir=table_dir,
# Each TP rank holds a different vocabulary shard of the same shape.
tag=(
f"rows{self.shard_indices.org_vocab_start_index}"
f"-{self.shard_indices.org_vocab_end_index}"
),
requires_grad=False,
)
# Only the file backend has anything to prefetch (rows live on storage).
self._file_prefetcher = make_ple_file_prefetcher(host_table)
# ... and only it needs its resident set bounded: a fault maps a whole
# folio, so the mapping would otherwise creep towards the full table.
self._file_rss_trimmer = make_ple_file_rss_trimmer(host_table)
cpu_weight = nn.Parameter(host_table, requires_grad=False)
for name, value in vars(source_weight).items():
setattr(cpu_weight, name, value)
cpu_weight.weight_loader = self.weight_loader
Expand Down Expand Up @@ -846,6 +865,8 @@ def gather(

flat_ids = input_ids.reshape(-1).long()
if flat_ids.numel():
if self._file_prefetcher is not None:
self._file_prefetcher.enqueue(flat_ids)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are global IDs, but the file contains only this rank's shard. Please filter to the local range and subtract the shard start before prefetching.

_gather_ple_embedding_from_pinned_kernel[(flat_ids.numel(),)](
self.weight.data_ptr(),
flat_ids,
Expand Down Expand Up @@ -893,7 +914,9 @@ def __init__(
)
if config.ple_offload_embedding:
self.ple_embedding.ngram_embedding = Qwen4ExpPinnedHostEmbedding(
self.ple_embedding.ngram_embedding
self.ple_embedding.ngram_embedding,
backend=getattr(config, "ple_offload_backend", "pinned"),
table_dir=getattr(config, "ple_offload_dir", None),
)
self.short_conv_dilation = self.ple_embedding.ngram_size
self.short_conv_state_len = (
Expand Down
Loading
Loading