Support symmetric memory pre-allocation to avoid fragmentation (#17089)

This commit is contained in:
Nicolas Castet
2026-01-23 17:57:04 +08:00
committed by GitHub
parent b23470e95a
commit 48e9daadff
7 changed files with 100 additions and 1 deletions
+1
View File
@@ -41,6 +41,7 @@ SGLang supports various environment variables that can be used to configure its
| `SGLANG_MM_BUFFER_SIZE_MB` | Size of preallocated GPU buffer (in MB) for multi-modal feature hashing optimization. When set to a positive value, temporarily moves features to GPU for faster hash computation, then moves them back to CPU to save GPU memory. Larger features benefit more from GPU hashing. Set to `0` to disable. | `0` | | `SGLANG_MM_BUFFER_SIZE_MB` | Size of preallocated GPU buffer (in MB) for multi-modal feature hashing optimization. When set to a positive value, temporarily moves features to GPU for faster hash computation, then moves them back to CPU to save GPU memory. Larger features benefit more from GPU hashing. Set to `0` to disable. | `0` |
| `SGLANG_MM_PRECOMPUTE_HASH` | Enable precomputing of hash values for MultimodalDataItem | `false` | | `SGLANG_MM_PRECOMPUTE_HASH` | Enable precomputing of hash values for MultimodalDataItem | `false` |
| `SGLANG_NCCL_ALL_GATHER_IN_OVERLAP_SCHEDULER_SYNC_BATCH` | Enable NCCL for gathering when preparing mlp sync batch under overlap scheduler (without this flag gloo is used for gathering) | `false` | | `SGLANG_NCCL_ALL_GATHER_IN_OVERLAP_SCHEDULER_SYNC_BATCH` | Enable NCCL for gathering when preparing mlp sync batch under overlap scheduler (without this flag gloo is used for gathering) | `false` |
| `SGLANG_SYMM_MEM_PREALLOC_GB_SIZE` | Size of preallocated GPU buffer (in GB) for NCCL symmetric memory pool to limit memory fragmentation. Only have an effect when server arg `--enable-symm-mem` is set. | `4` |
## DeepGEMM Configuration (Advanced Optimization) ## DeepGEMM Configuration (Advanced Optimization)
+3
View File
@@ -453,6 +453,9 @@ class Envs:
# TokenizerManager # TokenizerManager
SGLANG_REQUEST_STATE_WAIT_TIMEOUT = EnvInt(4) SGLANG_REQUEST_STATE_WAIT_TIMEOUT = EnvInt(4)
# Symmetric Memory
SGLANG_SYMM_MEM_PREALLOC_GB_SIZE = EnvInt(-1)
# Aiter # Aiter
SGLANG_USE_AITER_FP8_PER_TOKEN = EnvBool(False) SGLANG_USE_AITER_FP8_PER_TOKEN = EnvBool(False)
# fmt: on # fmt: on
+1 -1
View File
@@ -551,6 +551,7 @@ class Scheduler(
self.max_req_input_len, self.max_req_input_len,
self.random_seed, self.random_seed,
self.device, self.device,
self.forward_stream,
_, _,
_, _,
_, _,
@@ -971,7 +972,6 @@ class Scheduler(
if self.device == "cpu": if self.device == "cpu":
self.default_stream.synchronize = lambda: None # No-op for CPU self.default_stream.synchronize = lambda: None # No-op for CPU
self.forward_stream: CudaStream = self.device_module.Stream()
self.forward_stream_ctx: CudaStreamContext = self.device_module.stream( self.forward_stream_ctx: CudaStreamContext = self.device_module.stream(
self.forward_stream self.forward_stream
) )
+1
View File
@@ -400,6 +400,7 @@ class TpModelWorker(BaseTpWorker):
self.max_req_input_len, self.max_req_input_len,
self.random_seed, self.random_seed,
self.device, self.device,
self.model_runner.forward_stream,
self.model_runner.req_to_token_pool.size, self.model_runner.req_to_token_pool.size,
self.model_runner.req_to_token_pool.max_context_len, self.model_runner.req_to_token_pool.max_context_len,
self.model_runner.token_to_kv_pool.size, self.model_runner.token_to_kv_pool.size,
@@ -58,6 +58,9 @@ from sglang.srt.distributed import (
set_mscclpp_all_reduce, set_mscclpp_all_reduce,
set_torch_symm_mem_all_reduce, set_torch_symm_mem_all_reduce,
) )
from sglang.srt.distributed.device_communicators.pynccl_allocator import (
use_symmetric_memory,
)
from sglang.srt.distributed.parallel_state import monkey_patch_vllm_parallel_state from sglang.srt.distributed.parallel_state import monkey_patch_vllm_parallel_state
from sglang.srt.elastic_ep.elastic_ep import ElasticEPStateManager from sglang.srt.elastic_ep.elastic_ep import ElasticEPStateManager
from sglang.srt.environ import envs from sglang.srt.environ import envs
@@ -365,6 +368,9 @@ class ModelRunner(ModelRunnerKVCacheMixin):
# Get memory before model loading # Get memory before model loading
min_per_gpu_memory = self.init_torch_distributed() min_per_gpu_memory = self.init_torch_distributed()
# Init forward stream for overlap schedule
self.forward_stream = torch.get_device_module(self.device).Stream()
# CPU offload # CPU offload
set_offloader(create_offloader_from_server_args(server_args, dp_rank=dp_rank)) set_offloader(create_offloader_from_server_args(server_args, dp_rank=dp_rank))
@@ -593,6 +599,8 @@ class ModelRunner(ModelRunnerKVCacheMixin):
# Initialize piecewise CUDA graph # Initialize piecewise CUDA graph
self.init_piecewise_cuda_graphs() self.init_piecewise_cuda_graphs()
self.prealloc_symmetric_memory_pool()
def init_routed_experts_capturer(self): def init_routed_experts_capturer(self):
if not self.server_args.disable_shared_experts_fusion and hasattr( if not self.server_args.disable_shared_experts_fusion and hasattr(
self.model, "num_fused_shared_experts" self.model, "num_fused_shared_experts"
@@ -2483,6 +2491,27 @@ class ModelRunner(ModelRunnerKVCacheMixin):
logger.error(f"IPC weight update failed: {e}") logger.error(f"IPC weight update failed: {e}")
return False, str(e) return False, str(e)
def prealloc_symmetric_memory_pool(self):
# PyTorch mempools never de-fragment memory in OOM scenarios, so we need to pre-allocate a large chunk of memory to limit fragmentation.
if (
self.is_draft_worker
or not self.server_args.enable_symm_mem
or envs.SGLANG_SYMM_MEM_PREALLOC_GB_SIZE.get() <= 0
):
return
# Memory allocation is tied to a cuda stream, use the forward stream
with torch.get_device_module(self.device).stream(self.forward_stream):
logger.info(
f"Pre-allocating symmetric memory pool with {envs.SGLANG_SYMM_MEM_PREALLOC_GB_SIZE.get()} GiB"
)
with use_symmetric_memory(get_tp_group()):
torch.empty(
(envs.SGLANG_SYMM_MEM_PREALLOC_GB_SIZE.get() * 1024 * 1024 * 1024,),
dtype=torch.uint8,
device=self.device,
)
def _model_load_weights_direct(model, named_tensors: List[Tuple[str, torch.Tensor]]): def _model_load_weights_direct(model, named_tensors: List[Tuple[str, torch.Tensor]]):
params_dict = dict(model.named_parameters()) params_dict = dict(model.named_parameters())
+11
View File
@@ -1036,6 +1036,17 @@ class ServerArgs:
if model_config.is_multimodal: if model_config.is_multimodal:
self.adjust_mem_fraction_for_vlm(model_config) self.adjust_mem_fraction_for_vlm(model_config)
# If symm mem is enabled and prealloc size is not set, set it to 4GB
if (
self.enable_symm_mem
and not envs.SGLANG_SYMM_MEM_PREALLOC_GB_SIZE.is_set()
):
envs.SGLANG_SYMM_MEM_PREALLOC_GB_SIZE.set(4)
logger.warning(
"Symmetric memory is enabled, setting symmetric memory prealloc size to 4GB as default."
"Use environment variable SGLANG_SYMM_MEM_PREALLOC_GB_SIZE to change the prealloc size."
)
def _generate_cuda_graph_batch_sizes(self): def _generate_cuda_graph_batch_sizes(self):
""" """
Generate the list of batch sizes for CUDA graph capture based on cuda_graph_max_bs. Generate the list of batch sizes for CUDA graph capture based on cuda_graph_max_bs.
+54
View File
@@ -207,5 +207,59 @@ class TestDeepseekV3FP4CutlassMoE(CustomTestCase):
self.assertGreater(metrics["accuracy"], 0.935) self.assertGreater(metrics["accuracy"], 0.935)
class TestDeepseekV3FP4SymmetricMemory(CustomTestCase):
@classmethod
def setUpClass(cls):
cls.model = FULL_DEEPSEEK_V3_FP4_MODEL_PATH
cls.base_url = DEFAULT_URL_FOR_TEST
other_args = [
"--tp",
"4",
"--attention-backend",
"trtllm_mla",
"--moe-runner-backend",
"flashinfer_trtllm",
"--quantization",
"modelopt_fp4",
"--kv-cache-dtype",
"fp8_e4m3",
"--model-loader-extra-config",
'{"enable_multithread_load": true,"num_threads": 64}',
"--enable-symm-mem",
]
cls.process = popen_launch_server(
cls.model,
cls.base_url,
timeout=SERVER_LAUNCH_TIMEOUT,
other_args=other_args,
)
@classmethod
def tearDownClass(cls):
kill_process_tree(cls.process.pid)
def test_a_gsm8k(
self,
): # Append an "a" to make this test run first (alphabetically) to warm up the server
args = SimpleNamespace(
num_shots=8,
data_path=None,
num_questions=1319,
parallel=1319,
max_new_tokens=512,
host="http://127.0.0.1",
port=int(self.base_url.split(":")[-1]),
)
metrics = run_eval_few_shot_gsm8k(args)
print(f"{metrics=}")
if is_in_ci():
write_github_step_summary(
f"### test_gsm8k (deepseek-v3-fp4)\n" f'{metrics["accuracy"]=:.3f}\n'
)
self.assertGreater(metrics["accuracy"], 0.935)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()