Signed-off-by: Allen Wu <allenwu2795@gmail.com> Co-authored-by: TRAE CLI <noreply@bytedance.com> Co-authored-by: Sy03 <1370724210@qq.com> Co-authored-by: TRAE CLI <traecli@bytedance.com>
152 lines
5.5 KiB
Python
152 lines
5.5 KiB
Python
"""Benchmark CosyVoice3-style CUDA stream handoff strategies."""
|
|
|
|
import argparse
|
|
import json
|
|
import statistics
|
|
import time
|
|
|
|
import torch
|
|
|
|
from vllm_omni.platforms import current_omni_platform
|
|
|
|
|
|
def run_case(
|
|
*,
|
|
device: torch.device,
|
|
steps: int,
|
|
warmup_steps: int,
|
|
producer_cycles: int,
|
|
estimator_cycles: int,
|
|
use_stream_dependencies: bool,
|
|
) -> dict[str, float | int | str]:
|
|
caller_stream = torch.cuda.Stream(device=device)
|
|
estimator_stream = torch.cuda.Stream(device=device)
|
|
input_tensor = torch.zeros(1024, device=device)
|
|
output_tensor = torch.empty_like(input_tensor)
|
|
submission_ms = []
|
|
|
|
def step(measure: bool) -> None:
|
|
with torch.cuda.stream(caller_stream):
|
|
torch.cuda._sleep(producer_cycles)
|
|
input_tensor.add_(1)
|
|
|
|
start = time.perf_counter_ns()
|
|
if use_stream_dependencies:
|
|
estimator_stream.wait_stream(caller_stream)
|
|
else:
|
|
caller_stream.synchronize()
|
|
|
|
with torch.cuda.stream(estimator_stream):
|
|
torch.cuda._sleep(estimator_cycles)
|
|
output_tensor.copy_(input_tensor)
|
|
|
|
if use_stream_dependencies:
|
|
caller_stream.wait_stream(estimator_stream)
|
|
else:
|
|
estimator_stream.synchronize()
|
|
|
|
if measure:
|
|
submission_ms.append((time.perf_counter_ns() - start) / 1e6)
|
|
|
|
for _ in range(warmup_steps):
|
|
step(measure=False)
|
|
caller_stream.synchronize()
|
|
estimator_stream.synchronize()
|
|
input_tensor.zero_()
|
|
torch.accelerator.reset_peak_memory_stats(device)
|
|
initial_allocated = torch.accelerator.memory_allocated(device)
|
|
|
|
start_event = torch.cuda.Event(enable_timing=True)
|
|
end_event = torch.cuda.Event(enable_timing=True)
|
|
wall_start = time.perf_counter()
|
|
with torch.cuda.stream(caller_stream):
|
|
start_event.record()
|
|
for _ in range(steps):
|
|
step(measure=True)
|
|
with torch.cuda.stream(caller_stream):
|
|
end_event.record()
|
|
end_event.synchronize()
|
|
wall_ms = (time.perf_counter() - wall_start) * 1e3
|
|
peak_delta_bytes = torch.accelerator.max_memory_allocated(device) - initial_allocated
|
|
|
|
expected = torch.full_like(output_tensor, steps)
|
|
torch.testing.assert_close(output_tensor, expected, rtol=0, atol=0)
|
|
return {
|
|
"mode": "wait_stream" if use_stream_dependencies else "synchronize",
|
|
"steps": steps,
|
|
"submission_median_ms": statistics.median(submission_ms),
|
|
"submission_p95_ms": statistics.quantiles(submission_ms, n=20)[18],
|
|
"submission_mean_ms": statistics.mean(submission_ms),
|
|
"gpu_elapsed_ms": start_event.elapsed_time(end_event),
|
|
"wall_elapsed_ms": wall_ms,
|
|
"peak_allocation_delta_bytes": peak_delta_bytes,
|
|
"final_value": int(output_tensor[0].item()),
|
|
}
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--device", default="cuda:0")
|
|
parser.add_argument("--steps", type=int, default=100)
|
|
parser.add_argument("--warmup-steps", type=int, default=10)
|
|
parser.add_argument("--repeats", type=int, default=7)
|
|
parser.add_argument("--producer-cycles", type=int, default=1_000_000)
|
|
parser.add_argument("--estimator-cycles", type=int, default=2_000_000)
|
|
return parser.parse_args()
|
|
|
|
|
|
def main() -> None:
|
|
args = parse_args()
|
|
device = torch.device(args.device)
|
|
current_omni_platform.set_device(device)
|
|
torch.manual_seed(0)
|
|
|
|
old_results = []
|
|
new_results = []
|
|
for repeat in range(args.repeats):
|
|
common_args = {
|
|
"device": device,
|
|
"steps": args.steps,
|
|
"warmup_steps": args.warmup_steps,
|
|
"producer_cycles": args.producer_cycles,
|
|
"estimator_cycles": args.estimator_cycles,
|
|
}
|
|
if repeat % 2 == 0:
|
|
old_results.append(run_case(**common_args, use_stream_dependencies=False))
|
|
new_results.append(run_case(**common_args, use_stream_dependencies=True))
|
|
else:
|
|
new_results.append(run_case(**common_args, use_stream_dependencies=True))
|
|
old_results.append(run_case(**common_args, use_stream_dependencies=False))
|
|
|
|
old_submission_ms = statistics.median(result["submission_median_ms"] for result in old_results)
|
|
new_submission_ms = statistics.median(result["submission_median_ms"] for result in new_results)
|
|
old_gpu_ms = statistics.median(result["gpu_elapsed_ms"] for result in old_results)
|
|
new_gpu_ms = statistics.median(result["gpu_elapsed_ms"] for result in new_results)
|
|
print(
|
|
json.dumps(
|
|
{
|
|
"device": torch.cuda.get_device_name(device),
|
|
"torch": torch.__version__,
|
|
"cuda": torch.version.cuda,
|
|
"repeats": args.repeats,
|
|
"warmup_steps": args.warmup_steps,
|
|
"producer_cycles": args.producer_cycles,
|
|
"estimator_cycles": args.estimator_cycles,
|
|
"old_submission_median_ms": old_submission_ms,
|
|
"new_submission_median_ms": new_submission_ms,
|
|
"submission_median_speedup": old_submission_ms / new_submission_ms,
|
|
"old_gpu_elapsed_median_ms": old_gpu_ms,
|
|
"new_gpu_elapsed_median_ms": new_gpu_ms,
|
|
"gpu_elapsed_change_percent": (new_gpu_ms - old_gpu_ms) / old_gpu_ms * 100,
|
|
"parity": all(result["final_value"] == args.steps for result in old_results + new_results),
|
|
"old_runs": old_results,
|
|
"new_runs": new_results,
|
|
},
|
|
indent=2,
|
|
)
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|