CUDA graph: opt-in per-buffer + per-shape decode CG memory logging
Attribute decode CUDA-graph GPU memory at capture time, gated by
SGLANG_LOG_CG_BUFFERS=1 (off by default, startup-only, rank0):
1. Per-runner static input/output buffer table (name/shape/dtype/MB)
across the 3 EAGLE families (target-verify, draft, draft-extend),
flagging buffers that share_buffers() aliased onto an earlier family.
2. Per-shape graph-pool growth deltas in the capture loop + a per-family
TOTAL, isolating the shared activation pool from per-shape outputs.
Also fix --enable-profile-cuda-graph dumping all families to one
overwritten cuda_graph_runner_memory_usage.pickle: now writes a unique
cuda_graph_mem_<family>.pickle per runner.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -21,7 +21,7 @@ import inspect
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass, fields, is_dataclass
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple, Union
|
from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple, Union
|
||||||
|
|
||||||
@@ -95,6 +95,13 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
_has_foreach_copy = hasattr(torch, "_foreach_copy_")
|
_has_foreach_copy = hasattr(torch, "_foreach_copy_")
|
||||||
|
|
||||||
|
# Opt-in (SGLANG_LOG_CG_BUFFERS=1): dump the per-buffer CUDA-graph memory
|
||||||
|
# breakdown at capture time. Startup-only, rank0; off by default.
|
||||||
|
_LOG_CG_BUFFERS = get_bool_env_var("SGLANG_LOG_CG_BUFFERS")
|
||||||
|
# Process-wide data_ptr -> first runner label, so the dump can flag buffers that
|
||||||
|
# share_buffers() coalesced into one physical allocation across the EAGLE families.
|
||||||
|
_cg_buffer_owner: Dict[int, str] = {}
|
||||||
|
|
||||||
|
|
||||||
def _grouped_foreach_copy_(dsts: List[torch.Tensor], srcs: List[torch.Tensor]) -> None:
|
def _grouped_foreach_copy_(dsts: List[torch.Tensor], srcs: List[torch.Tensor]) -> None:
|
||||||
"""Call torch._foreach_copy_ grouped by (dst_dtype, src_dtype) pairs."""
|
"""Call torch._foreach_copy_ grouped by (dst_dtype, src_dtype) pairs."""
|
||||||
@@ -501,6 +508,57 @@ def set_global_graph_memory_pool(val):
|
|||||||
global_graph_memory_pool = val
|
global_graph_memory_pool = val
|
||||||
|
|
||||||
|
|
||||||
|
def _named_buffer_tensors(buffers) -> List[Tuple[str, torch.Tensor]]:
|
||||||
|
"""Flatten a *InputBuffers dataclass into (name, tensor) pairs, mirroring
|
||||||
|
share_buffers()'s traversal (recurses into dict / dataclass fields)."""
|
||||||
|
out: List[Tuple[str, torch.Tensor]] = []
|
||||||
|
for f in fields(buffers):
|
||||||
|
b = getattr(buffers, f.name)
|
||||||
|
if b is None:
|
||||||
|
continue
|
||||||
|
if is_dataclass(b):
|
||||||
|
b = vars(b)
|
||||||
|
if isinstance(b, dict):
|
||||||
|
for k, v in b.items():
|
||||||
|
if torch.is_tensor(v):
|
||||||
|
out.append((f"{f.name}.{k}", v))
|
||||||
|
elif torch.is_tensor(b):
|
||||||
|
out.append((f.name, b))
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def log_input_buffer_sizes(label: str, buffers) -> None:
|
||||||
|
"""One-time, rank0 dump of one runner's static CUDA-graph input/output
|
||||||
|
buffers: per-field shape/dtype/MB, flagging buffers that share_buffers()
|
||||||
|
aliased onto an earlier runner's allocation. Gated by SGLANG_LOG_CG_BUFFERS."""
|
||||||
|
if not _LOG_CG_BUFFERS:
|
||||||
|
return
|
||||||
|
rows = _named_buffer_tensors(buffers)
|
||||||
|
rows.sort(key=lambda nt: -nt[1].numel() * nt[1].element_size())
|
||||||
|
unique_mb = 0.0
|
||||||
|
total_mb = 0.0
|
||||||
|
lines = []
|
||||||
|
for name, t in rows:
|
||||||
|
mb = t.numel() * t.element_size() / 2**20
|
||||||
|
total_mb += mb
|
||||||
|
owner = _cg_buffer_owner.get(t.data_ptr())
|
||||||
|
if owner is None:
|
||||||
|
_cg_buffer_owner[t.data_ptr()] = label
|
||||||
|
unique_mb += mb
|
||||||
|
tag = ""
|
||||||
|
else:
|
||||||
|
tag = f" <alias of {owner}>"
|
||||||
|
lines.append(
|
||||||
|
f" {name:<40s} {str(tuple(t.shape)):<22s} "
|
||||||
|
f"{str(t.dtype).replace('torch.', ''):<10s} {mb:9.2f} MB{tag}"
|
||||||
|
)
|
||||||
|
log_info_on_rank0(
|
||||||
|
logger,
|
||||||
|
f"[CG buffers] {label}: static input/output buffers "
|
||||||
|
f"(new={unique_mb:.2f} MB, incl-aliased={total_mb:.2f} MB)\n" + "\n".join(lines),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CudaGraphRunner:
|
class CudaGraphRunner:
|
||||||
"""A CudaGraphRunner runs the forward pass of a model with cuda graph and torch.compile."""
|
"""A CudaGraphRunner runs the forward pass of a model with cuda graph and torch.compile."""
|
||||||
|
|
||||||
@@ -629,6 +687,7 @@ class CudaGraphRunner:
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
self.buffers.share_buffers()
|
self.buffers.share_buffers()
|
||||||
|
log_input_buffer_sizes(str(self.capture_forward_mode), self.buffers)
|
||||||
|
|
||||||
self.tbo_plugin = TboCudaGraphRunnerPlugin()
|
self.tbo_plugin = TboCudaGraphRunnerPlugin()
|
||||||
|
|
||||||
@@ -732,8 +791,18 @@ class CudaGraphRunner:
|
|||||||
torch.cuda.memory._record_memory_history()
|
torch.cuda.memory._record_memory_history()
|
||||||
return profile_context
|
return profile_context
|
||||||
|
|
||||||
|
def _cuda_graph_mem_snapshot_path(self):
|
||||||
|
# Per-runner filename so the 3 EAGLE families don't overwrite one file.
|
||||||
|
label = str(
|
||||||
|
getattr(self, "capture_forward_mode", None) or type(self).__name__
|
||||||
|
)
|
||||||
|
for ch in ".:/ ":
|
||||||
|
label = label.replace(ch, "_")
|
||||||
|
return f"cuda_graph_mem_{label}.pickle"
|
||||||
|
|
||||||
def _post_process_after_profile(self, prof_context):
|
def _post_process_after_profile(self, prof_context):
|
||||||
torch.cuda.memory._dump_snapshot(f"cuda_graph_runner_memory_usage.pickle")
|
snapshot_path = self._cuda_graph_mem_snapshot_path()
|
||||||
|
torch.cuda.memory._dump_snapshot(snapshot_path)
|
||||||
torch.cuda.memory._record_memory_history(enabled=None)
|
torch.cuda.memory._record_memory_history(enabled=None)
|
||||||
log_message = (
|
log_message = (
|
||||||
"Sorted by CUDA Time:\n"
|
"Sorted by CUDA Time:\n"
|
||||||
@@ -744,7 +813,7 @@ class CudaGraphRunner:
|
|||||||
+ prof_context.key_averages(group_by_input_shape=True).table(
|
+ prof_context.key_averages(group_by_input_shape=True).table(
|
||||||
sort_by="cpu_time_total", row_limit=10
|
sort_by="cpu_time_total", row_limit=10
|
||||||
)
|
)
|
||||||
+ "\n\nMemory Usage is saved to cuda_graph_runner_memory_usage.pickle\n"
|
+ f"\n\nMemory Usage is saved to {snapshot_path}\n"
|
||||||
)
|
)
|
||||||
logger.info(log_message)
|
logger.info(log_message)
|
||||||
|
|
||||||
@@ -759,14 +828,20 @@ class CudaGraphRunner:
|
|||||||
self.model_runner.gpu_id,
|
self.model_runner.gpu_id,
|
||||||
empty_cache=False,
|
empty_cache=False,
|
||||||
)
|
)
|
||||||
|
is_rank0 = get_tensor_model_parallel_rank() == 0
|
||||||
|
cg_label = str(
|
||||||
|
getattr(self, "capture_forward_mode", None) or type(self).__name__
|
||||||
|
)
|
||||||
|
capture_start_avail = avail_mem
|
||||||
|
prev_avail = avail_mem
|
||||||
# Reverse the order to enable better memory sharing across cuda graphs.
|
# Reverse the order to enable better memory sharing across cuda graphs.
|
||||||
capture_range = (
|
capture_range = (
|
||||||
tqdm.tqdm(list(reversed(self.capture_bs)))
|
tqdm.tqdm(list(reversed(self.capture_bs)))
|
||||||
if get_tensor_model_parallel_rank() == 0
|
if is_rank0
|
||||||
else reversed(self.capture_bs)
|
else reversed(self.capture_bs)
|
||||||
)
|
)
|
||||||
for i, bs in enumerate(capture_range):
|
for i, bs in enumerate(capture_range):
|
||||||
if get_tensor_model_parallel_rank() == 0:
|
if is_rank0:
|
||||||
avail_mem = get_available_gpu_memory(
|
avail_mem = get_available_gpu_memory(
|
||||||
self.model_runner.device,
|
self.model_runner.device,
|
||||||
self.model_runner.gpu_id,
|
self.model_runner.gpu_id,
|
||||||
@@ -791,6 +866,33 @@ class CudaGraphRunner:
|
|||||||
self.graphs[key] = graph
|
self.graphs[key] = graph
|
||||||
self.output_buffers[key] = output_buffers
|
self.output_buffers[key] = output_buffers
|
||||||
|
|
||||||
|
if _LOG_CG_BUFFERS and is_rank0:
|
||||||
|
after = get_available_gpu_memory(
|
||||||
|
self.model_runner.device,
|
||||||
|
self.model_runner.gpu_id,
|
||||||
|
empty_cache=False,
|
||||||
|
)
|
||||||
|
log_info_on_rank0(
|
||||||
|
logger,
|
||||||
|
f"[CG capture] {cg_label} bs={bs:>4d}: "
|
||||||
|
f"+{max(prev_avail - after, 0.0):6.3f} GB this shape "
|
||||||
|
f"(graph pool / attn workspace / outputs), avail={after:.3f} GB",
|
||||||
|
)
|
||||||
|
prev_avail = after
|
||||||
|
|
||||||
|
if _LOG_CG_BUFFERS and is_rank0:
|
||||||
|
final_avail = get_available_gpu_memory(
|
||||||
|
self.model_runner.device,
|
||||||
|
self.model_runner.gpu_id,
|
||||||
|
empty_cache=False,
|
||||||
|
)
|
||||||
|
log_info_on_rank0(
|
||||||
|
logger,
|
||||||
|
f"[CG capture] {cg_label} TOTAL non-static-buffer growth: "
|
||||||
|
f"{max(capture_start_avail - final_avail, 0.0):.3f} GB "
|
||||||
|
f"across {len(self.capture_bs)} shapes",
|
||||||
|
)
|
||||||
|
|
||||||
# Trigger CUDA graph capture for specific shapes.
|
# Trigger CUDA graph capture for specific shapes.
|
||||||
# Capture the large shapes first so that the smaller shapes
|
# Capture the large shapes first so that the smaller shapes
|
||||||
# can reuse the memory pool allocated for the large shapes.
|
# can reuse the memory pool allocated for the large shapes.
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from sglang.srt.model_executor.cuda_graph_runner import (
|
|||||||
DeepEPCudaGraphRunnerAdapter,
|
DeepEPCudaGraphRunnerAdapter,
|
||||||
get_batch_sizes_to_capture,
|
get_batch_sizes_to_capture,
|
||||||
get_global_graph_memory_pool,
|
get_global_graph_memory_pool,
|
||||||
|
log_input_buffer_sizes,
|
||||||
model_capture_mode,
|
model_capture_mode,
|
||||||
set_global_graph_memory_pool,
|
set_global_graph_memory_pool,
|
||||||
set_is_extend_in_batch,
|
set_is_extend_in_batch,
|
||||||
@@ -157,6 +158,7 @@ class EAGLEDraftCudaGraphRunner:
|
|||||||
global_num_tokens_for_logprob_gpu=global_num_tokens_for_logprob_gpu,
|
global_num_tokens_for_logprob_gpu=global_num_tokens_for_logprob_gpu,
|
||||||
)
|
)
|
||||||
self.buffers.share_buffers()
|
self.buffers.share_buffers()
|
||||||
|
log_input_buffer_sizes("eagle_draft", self.buffers)
|
||||||
|
|
||||||
# Capture
|
# Capture
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from sglang.srt.model_executor.cuda_graph_runner import (
|
|||||||
LogitsProcessorOutput,
|
LogitsProcessorOutput,
|
||||||
get_batch_sizes_to_capture,
|
get_batch_sizes_to_capture,
|
||||||
get_global_graph_memory_pool,
|
get_global_graph_memory_pool,
|
||||||
|
log_input_buffer_sizes,
|
||||||
model_capture_mode,
|
model_capture_mode,
|
||||||
set_global_graph_memory_pool,
|
set_global_graph_memory_pool,
|
||||||
set_is_extend_in_batch,
|
set_is_extend_in_batch,
|
||||||
@@ -211,6 +212,7 @@ class EAGLEDraftExtendCudaGraphRunner:
|
|||||||
global_num_tokens_for_logprob_gpu=global_num_tokens_for_logprob_gpu,
|
global_num_tokens_for_logprob_gpu=global_num_tokens_for_logprob_gpu,
|
||||||
)
|
)
|
||||||
self.buffers.share_buffers()
|
self.buffers.share_buffers()
|
||||||
|
log_input_buffer_sizes("eagle_draft_extend", self.buffers)
|
||||||
|
|
||||||
# Capture
|
# Capture
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user