Add XPU profiler activity support in benchmark code (#12981)

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
This commit is contained in:
Kalyan Kumar
2026-03-04 23:22:56 -08:00
committed by GitHub
co-authored by gemini-code-assist[bot]
parent 2bdd89a6cd
commit c1df359b44
5 changed files with 98 additions and 25 deletions
+45 -21
View File
@@ -57,7 +57,7 @@ import multiprocessing
import os import os
import time import time
from types import SimpleNamespace from types import SimpleNamespace
from typing import Tuple from typing import Optional, Tuple
import numpy as np import numpy as np
import torch import torch
@@ -79,8 +79,6 @@ from sglang.srt.speculative.spec_info import SpeculativeAlgorithm
from sglang.srt.utils import ( from sglang.srt.utils import (
configure_logger, configure_logger,
get_bool_env_var, get_bool_env_var,
is_cuda_alike,
is_xpu,
kill_process_tree, kill_process_tree,
maybe_reindex_device_id, maybe_reindex_device_id,
require_mlp_sync, require_mlp_sync,
@@ -90,15 +88,6 @@ from sglang.srt.utils import (
) )
from sglang.srt.utils.hf_transformers_utils import get_tokenizer from sglang.srt.utils.hf_transformers_utils import get_tokenizer
profile_activities = [torch.profiler.ProfilerActivity.CPU] + [
profiler_activity
for available, profiler_activity in [
(is_cuda_alike(), torch.profiler.ProfilerActivity.CUDA),
(is_xpu(), torch.profiler.ProfilerActivity.XPU),
]
if available
]
def start_profile(profile_activities, profile_record_shapes=False, rank_print=print): def start_profile(profile_activities, profile_record_shapes=False, rank_print=print):
""" """
@@ -118,6 +107,8 @@ def start_profile(profile_activities, profile_record_shapes=False, rank_print=pr
activities.append(torch.profiler.ProfilerActivity.CPU) activities.append(torch.profiler.ProfilerActivity.CPU)
if "GPU" in profile_activities: if "GPU" in profile_activities:
activities.append(torch.profiler.ProfilerActivity.CUDA) activities.append(torch.profiler.ProfilerActivity.CUDA)
if "XPU" in profile_activities:
activities.append(torch.profiler.ProfilerActivity.XPU)
if activities: if activities:
profiler = torch.profiler.profile( profiler = torch.profiler.profile(
activities=activities, activities=activities,
@@ -179,6 +170,8 @@ class BenchArgs:
profile_activities: Tuple[str] = ("CPU", "GPU") profile_activities: Tuple[str] = ("CPU", "GPU")
profile_stage: str = "all" profile_stage: str = "all"
profile_filename_prefix: str = "profile" profile_filename_prefix: str = "profile"
profile_start_step: Optional[int] = None
profile_steps: Optional[int] = None
@staticmethod @staticmethod
def add_cli_args(parser: argparse.ArgumentParser): def add_cli_args(parser: argparse.ArgumentParser):
@@ -217,8 +210,8 @@ class BenchArgs:
type=str, type=str,
nargs="+", nargs="+",
default=["CPU", "GPU"], default=["CPU", "GPU"],
choices=["CPU", "GPU", "CUDA_PROFILER"], choices=["CPU", "GPU", "CUDA_PROFILER", "XPU"],
help="Profiler activities: CPU, GPU, CUDA_PROFILER. If CPU/GPU, use torch profiler. If CUDA_PROFILER, use CUDA profiler.", help="Profiler activities: CPU, GPU, XPU, CUDA_PROFILER. If CPU/GPU/XPU, use torch profiler. If CUDA_PROFILER, use CUDA profiler.",
) )
parser.add_argument( parser.add_argument(
"--profile-stage", "--profile-stage",
@@ -234,14 +227,32 @@ class BenchArgs:
help="Prefix of the profiling file names. The full profiling result file(s) be " help="Prefix of the profiling file names. The full profiling result file(s) be "
'"[profile_filename_prefix]_batch[batch_size]_input[input_len]_output[output_len].trace.json.gz"', '"[profile_filename_prefix]_batch[batch_size]_input[input_len]_output[output_len].trace.json.gz"',
) )
parser.add_argument(
"--profile-start-step",
type=int,
default=None,
help="Decode step at which to start profiling (0-indexed). If not specified, defaults to output_len // 2.",
)
parser.add_argument(
"--profile-steps",
type=int,
default=None,
help="Number of decode steps to profile starting from profile-start-step. If not specified, profiles only one step.",
)
@classmethod @classmethod
def from_cli_args(cls, args: argparse.Namespace): def from_cli_args(cls, args: argparse.Namespace):
# use the default value's type to cast the args into correct types. # use the default value's type to cast the args into correct types.
attrs = [(attr.name, type(attr.default)) for attr in dataclasses.fields(cls)] attrs = [(attr.name, type(attr.default)) for attr in dataclasses.fields(cls)]
return cls( result = {}
**{attr: attr_type(getattr(args, attr)) for attr, attr_type in attrs} for attr, attr_type in attrs:
) value = getattr(args, attr)
# Handle None values - don't try to cast them
if value is None or attr_type == type(None):
result[attr] = value
else:
result[attr] = attr_type(value)
return cls(**result)
def load_model(server_args, port_args, gpu_id, tp_rank): def load_model(server_args, port_args, gpu_id, tp_rank):
@@ -525,6 +536,8 @@ def latency_test_run_once(
profile_filename_prefix, profile_filename_prefix,
profile_stage, profile_stage,
tp_rank, tp_rank,
profile_start_step=None,
profile_steps=None,
): ):
max_batch_size = model_runner.max_total_num_tokens // (input_len + output_len) max_batch_size = model_runner.max_total_num_tokens // (input_len + output_len)
if batch_size > max_batch_size: if batch_size > max_batch_size:
@@ -582,12 +595,17 @@ def latency_test_run_once(
measurement_results["prefill_throughput"] = throughput measurement_results["prefill_throughput"] = throughput
decode_latencies = [] decode_latencies = []
profile_step_of_interest = output_len // 2 # Determine profiling start step and end step
profile_start = (
profile_start_step if profile_start_step is not None else (output_len // 2)
)
profile_end = profile_start + (profile_steps if profile_steps is not None else 1)
enable_profile_decode = profile and profile_stage in ["all", "decode"] enable_profile_decode = profile and profile_stage in ["all", "decode"]
profiler = None
for i in range(output_len - 1): for i in range(output_len - 1):
synchronize(device) synchronize(device)
profiler = None # Start profiler at the specified step
if enable_profile_decode and i == profile_step_of_interest: if enable_profile_decode and i == profile_start:
profiler = start_profile( profiler = start_profile(
profile_activities, profile_activities,
profile_record_shapes=profile_record_shapes, profile_record_shapes=profile_record_shapes,
@@ -599,7 +617,8 @@ def latency_test_run_once(
synchronize(device) synchronize(device)
latency = time.perf_counter() - tic latency = time.perf_counter() - tic
if enable_profile_decode and i == profile_step_of_interest: # Stop profiler after the specified number of steps
if enable_profile_decode and profiler is not None and i >= profile_end - 1:
trace_filename = _create_torch_profiler_filename( trace_filename = _create_torch_profiler_filename(
profile_filename_prefix, batch_size, input_len, output_len, "decode" profile_filename_prefix, batch_size, input_len, output_len, "decode"
) )
@@ -611,6 +630,7 @@ def latency_test_run_once(
trace_filename=trace_filename, trace_filename=trace_filename,
stage="decode", stage="decode",
) )
profiler = None
tot_latency += latency tot_latency += latency
throughput = batch_size / latency throughput = batch_size / latency
@@ -686,6 +706,8 @@ def latency_test(
profile_filename_prefix="", profile_filename_prefix="",
profile_stage="all", profile_stage="all",
tp_rank=tp_rank, tp_rank=tp_rank,
profile_start_step=None,
profile_steps=None,
) )
rank_print("Benchmark ...") rank_print("Benchmark ...")
@@ -736,6 +758,8 @@ def latency_test(
bench_args.profile_filename_prefix, bench_args.profile_filename_prefix,
bench_args.profile_stage, bench_args.profile_stage,
tp_rank, tp_rank,
bench_args.profile_start_step,
bench_args.profile_steps,
) )
if ret is not None: if ret is not None:
result_list.append(ret) result_list.append(ret)
+26 -3
View File
@@ -730,6 +730,14 @@ async def async_request_profile(api_url: str) -> RequestFuncOutput:
# stop_profile doesn't need any parameters # stop_profile doesn't need any parameters
body = {} body = {}
print(f"async_request_profile {api_url=} {body=}") print(f"async_request_profile {api_url=} {body=}")
# Add optional profiling parameters if provided
if (
hasattr(args, "profile_start_step")
and args.profile_start_step is not None
):
body["start_step"] = str(args.profile_start_step)
if hasattr(args, "profile_steps") and args.profile_steps is not None:
body["num_steps"] = str(args.profile_steps)
async with session.post(url=api_url, json=body) as response: async with session.post(url=api_url, json=body) as response:
if response.status == 200: if response.status == 200:
output.success = True output.success = True
@@ -1312,8 +1320,10 @@ async def benchmark(
if is_multi_turn: if is_multi_turn:
outputs = [x for output in outputs for x in output] outputs = [x for output in outputs for x in output]
# Stop profiler # Stop profiler (only if profile_steps was not provided, as it auto-stops)
if profile: if profile and not (
hasattr(args, "profile_steps") and args.profile_steps is not None
):
if pd_separated: if pd_separated:
if pd_profile_urls: if pd_profile_urls:
await _call_profile_pd(pd_profile_urls, "stop") await _call_profile_pd(pd_profile_urls, "stop")
@@ -2016,7 +2026,20 @@ if __name__ == "__main__":
type=str, type=str,
nargs="+", nargs="+",
default=["CPU", "GPU"], default=["CPU", "GPU"],
choices=["CPU", "GPU", "CUDA_PROFILER"], choices=["CPU", "GPU", "CUDA_PROFILER", "XPU"],
help="Profiler activities to capture: CPU, GPU, XPU, CUDA_PROFILER.",
)
parser.add_argument(
"--profile-start-step",
type=int,
default=None,
help="Start profiling after this many forward steps. Useful for warmup.",
)
parser.add_argument(
"--profile-steps",
type=int,
default=None,
help="Number of steps to profile. If specified, profiling stops automatically after this many steps.",
) )
parser.add_argument("--profile-num-steps", type=int, default=None) parser.add_argument("--profile-num-steps", type=int, default=None)
parser.add_argument("--profile-by-stage", action="store_true", default=False) parser.add_argument("--profile-by-stage", action="store_true", default=False)
+3
View File
@@ -26,6 +26,7 @@ def run_profile(
profile_by_stage: bool = False, profile_by_stage: bool = False,
merge_profiles: bool = False, merge_profiles: bool = False,
profile_prefix: Optional[str] = None, profile_prefix: Optional[str] = None,
start_step: Optional[int] = None,
) -> str: ) -> str:
if output_dir is None: if output_dir is None:
output_dir = PROFILER_DIR output_dir = PROFILER_DIR
@@ -57,6 +58,8 @@ def run_profile(
"merge_profiles": merge_profiles, "merge_profiles": merge_profiles,
"profile_prefix": profile_prefix, "profile_prefix": profile_prefix,
} }
if start_step is not None:
json_data["start_step"] = str(start_step)
response = requests.post(url=url + "/start_profile", json=json_data) response = requests.post(url=url + "/start_profile", json=json_data)
response.raise_for_status() response.raise_for_status()
@@ -154,6 +154,8 @@ class SchedulerProfilerMixin:
"CPU": torch.profiler.ProfilerActivity.CPU, "CPU": torch.profiler.ProfilerActivity.CPU,
"GPU": torch.profiler.ProfilerActivity.CUDA, "GPU": torch.profiler.ProfilerActivity.CUDA,
} }
if hasattr(torch.profiler.ProfilerActivity, "XPU"):
activity_map["XPU"] = torch.profiler.ProfilerActivity.XPU
torchprof_activities = [ torchprof_activities = [
activity_map[a] for a in activities if a in activity_map activity_map[a] for a in activities if a in activity_map
] ]
@@ -94,6 +94,8 @@ class BenchArgs:
skip_warmup: bool = False skip_warmup: bool = False
show_report: bool = False show_report: bool = False
profile: bool = False profile: bool = False
profile_activities: Tuple[str] = ("CPU", "GPU")
profile_start_step: Optional[int] = None
profile_steps: int = 5 profile_steps: int = 5
profile_by_stage: bool = False profile_by_stage: bool = False
profile_prefix: Optional[str] = None profile_prefix: Optional[str] = None
@@ -141,6 +143,20 @@ class BenchArgs:
parser.add_argument("--skip-warmup", action="store_true") parser.add_argument("--skip-warmup", action="store_true")
parser.add_argument("--show-report", action="store_true") parser.add_argument("--show-report", action="store_true")
parser.add_argument("--profile", action="store_true") parser.add_argument("--profile", action="store_true")
parser.add_argument(
"--profile-activities",
type=str,
nargs="+",
default=("CPU", "GPU"),
choices=["CPU", "GPU", "XPU"],
help="Profiler activities: CPU, GPU, XPU. use torch profiler.",
)
parser.add_argument(
"--profile-start-step",
type=int,
default=BenchArgs.profile_start_step,
help="Start profiling after this many forward steps. Useful for warmup.",
)
parser.add_argument( parser.add_argument(
"--profile-steps", type=int, default=BenchArgs.profile_steps "--profile-steps", type=int, default=BenchArgs.profile_steps
) )
@@ -394,6 +410,8 @@ def run_one_case(
result_filename: str, result_filename: str,
tokenizer: PreTrainedTokenizer | AutoProcessor, tokenizer: PreTrainedTokenizer | AutoProcessor,
profile: bool = False, profile: bool = False,
profile_activities: Tuple[str] = ("CPU", "GPU"),
profile_start_step: Optional[int] = None,
profile_steps: int = BenchArgs.profile_steps, profile_steps: int = BenchArgs.profile_steps,
profile_by_stage: bool = False, profile_by_stage: bool = False,
profile_prefix: Optional[str] = BenchArgs.profile_prefix, profile_prefix: Optional[str] = BenchArgs.profile_prefix,
@@ -523,10 +541,11 @@ def run_one_case(
profile_link: str = run_profile( profile_link: str = run_profile(
url=url, url=url,
num_steps=profile_steps, num_steps=profile_steps,
activities=["CPU", "GPU"], profile_activities=profile_activities,
output_dir=profile_output_dir, output_dir=profile_output_dir,
profile_by_stage=profile_by_stage, profile_by_stage=profile_by_stage,
profile_prefix=profile_prefix, profile_prefix=profile_prefix,
start_step=profile_start_step,
) )
# Get metrics before the request (for cache hit rate calculation) # Get metrics before the request (for cache hit rate calculation)
@@ -918,6 +937,8 @@ def run_benchmark_internal(
parallel_batch=bench_args.parallel_batch, parallel_batch=bench_args.parallel_batch,
cache_hit_rate=bench_args.cache_hit_rate, cache_hit_rate=bench_args.cache_hit_rate,
profile=bench_args.profile, profile=bench_args.profile,
profile_activities=bench_args.profile_activities,
profile_start_step=bench_args.profile_start_step,
profile_steps=bench_args.profile_steps, profile_steps=bench_args.profile_steps,
profile_by_stage=bench_args.profile_by_stage, profile_by_stage=bench_args.profile_by_stage,
profile_prefix=profile_prefix, profile_prefix=profile_prefix,