Mooncake/scripts/test_pub_tensor.py

289 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from dataclasses import dataclass
import json
import torch
from mooncake.store import MooncakeDistributedStore
from mooncake.store import ReplicateConfig
import os
import sys
import time
import argparse
import numpy as np
DEFAULT_MOONCAKE_CONFIG_PATH_ENV = "MOONCAKE_CONFIG_PATH"
DEFAULT_GLOBAL_SEGMENT_SIZE = 400 * 1024 * 1024 * 1024 # 4 GiB
DEFAULT_LOCAL_BUFFER_SIZE = 200 * 1024 * 1024 * 1024 # 2 MB
DEFAULT_MASTER_METRICS_PORT = 9003
DEFAULT_CHECK_SERVER = False
def verify_tensor_equality(original, received, rtol=0, atol=0, verbose=True):
"""
验证两个张量是否完全一致(逐元素精确比较)。
参数:
original: 原始张量 (numpy.ndarray 或 torch.Tensor)
received: 接收到的张量 (numpy.ndarray 或 torch.Tensor)
rtol: 相对容差(默认 0要求完全相等
atol: 绝对容差(默认 0要求完全相等
verbose: 是否打印详细信息(默认 True
返回:
bool: True 表示完全一致False 表示有差异
"""
# 转换为 NumPy 数组(处理 PyTorch 张量)
def to_numpy(x):
if isinstance(x, torch.Tensor):
if x.is_cuda:
x = x.cpu()
return x.detach().numpy()
elif isinstance(x, np.ndarray):
return x
else:
raise TypeError(f"Unsupported tensor type: {type(x)}")
try:
orig_np = to_numpy(original)
recv_np = to_numpy(received)
except Exception as e:
if verbose:
print(f"❌ Error converting tensors: {e}")
return False
# 检查形状
if orig_np.shape != recv_np.shape:
if verbose:
print(f"❌ Shape mismatch: original {orig_np.shape} vs received {recv_np.shape}")
return False
# 检查数据类型
if orig_np.dtype != recv_np.dtype:
if verbose:
print(f"❌ Dtype mismatch: original {orig_np.dtype} vs received {recv_np.dtype}")
return False
# 精确比较(逐元素)
if np.array_equal(orig_np, recv_np):
if verbose:
print("✅ Tensors are identical!")
return True
else:
# 找出第一个不一致的位置(用于调试)
diff_mask = orig_np != recv_np
diff_indices = np.where(diff_mask)
if len(diff_indices[0]) > 0:
first_diff_idx = tuple(idx[0] for idx in diff_indices)
orig_val = orig_np[first_diff_idx]
recv_val = recv_np[first_diff_idx]
if verbose:
print(f"❌ Tensors differ at index {first_diff_idx}")
print(f" Original: {orig_val}")
print(f" Received: {recv_val}")
print(f" Difference: {abs(orig_val - recv_val)}")
return False
def _parse_global_segment_size(value) -> int:
if isinstance(value, int):
return value
if isinstance(value, str):
s = value.strip().lower()
if s.endswith("gb"):
num = s[:-2].strip()
if not num:
raise ValueError(
"Invalid global_segment_size: missing number before 'gb'"
)
return int(num) * 1024 * 1024 * 1024
return int(s)
return int(value)
@dataclass
class MooncakeStoreConfig:
local_hostname: str
metadata_server: str
global_segment_size: int
local_buffer_size: int
protocol: str
device_name: str
master_server_address: str
master_metrics_port: int
check_server: bool
@staticmethod
def from_file() -> "MooncakeStoreConfig":
"""Load the config from a JSON file."""
file_path = os.getenv(DEFAULT_MOONCAKE_CONFIG_PATH_ENV)
try:
with open(file_path) as fin:
config = json.load(fin)
except Exception as e:
raise RuntimeError(f"Failed to load config from {file_path}: {str(e)}")
return MooncakeStoreConfig(
local_hostname=config.get("local_hostname"),
metadata_server=config.get("metadata_server"),
global_segment_size=_parse_global_segment_size(
config.get("global_segment_size", DEFAULT_GLOBAL_SEGMENT_SIZE)
),
# Zero copy interface does not need local buffer
local_buffer_size=DEFAULT_LOCAL_BUFFER_SIZE,
protocol=config.get("protocol", "tcp"),
device_name=config.get("device_name", ""),
master_server_address=config.get("master_server_address"),
master_metrics_port=config.get(
"master_metrics_port", DEFAULT_MASTER_METRICS_PORT
),
check_server=config.get("check_server", DEFAULT_CHECK_SERVER),
)
@staticmethod
def load_from_env() -> "MooncakeStoreConfig":
"""Load config from a file specified in the environment variable.
export MOONCAKE_MASTER=10.13.3.232:50051
export MOONCAKE_PROTOCOL="rdma"
export MOONCAKE_DEVICE=""
export MOONCAKE_TE_META_DATA_SERVER="P2PHANDSHAKE"
"""
# other required environment variables...
if not os.getenv("MOONCAKE_MASTER"):
raise ValueError("The environment variable 'MOONCAKE_MASTER' is not set.")
return MooncakeStoreConfig(
local_hostname=os.getenv("LOCAL_HOSTNAME", "localhost"),
metadata_server=os.getenv("MOONCAKE_TE_META_DATA_SERVER", "P2PHANDSHAKE"),
global_segment_size=_parse_global_segment_size(
os.getenv("MOONCAKE_GLOBAL_SEGMENT_SIZE", DEFAULT_GLOBAL_SEGMENT_SIZE)
),
# Zero copy interface does not need local buffer
local_buffer_size=DEFAULT_LOCAL_BUFFER_SIZE,
protocol=os.getenv("MOONCAKE_PROTOCOL", "tcp"),
device_name=os.getenv("MOONCAKE_DEVICE", ""),
master_server_address=os.getenv("MOONCAKE_MASTER"),
master_metrics_port=int(
os.getenv("MOONCAKE_MASTER_METRICS_PORT", DEFAULT_MASTER_METRICS_PORT)
),
check_server=bool(os.getenv("MOONCAKE_CHECK_SERVER", DEFAULT_CHECK_SERVER)),
)
def run_benchmark(num_iterations, num_elements):
store = MooncakeDistributedStore()
print("--- Mooncake Tensor Performance Benchmark ---")
print(f"Configuration:")
print(f" Num Elements: {num_elements}")
print(f" Iterations: {num_iterations}")
try:
config = MooncakeStoreConfig.load_from_env()
print(f" Hostname: {config.local_hostname}")
print(f" Metadata Server: {config.metadata_server}")
print(f" Master Address: {config.master_server_address}")
print(f" Protocol: {config.protocol}")
print("debug: config: ", config)
rc = store.setup(
config.local_hostname,
config.metadata_server,
config.global_segment_size,
config.local_buffer_size,
config.protocol,
config.device_name,
config.master_server_address,
)
print("debug: store: ", store)
if rc != 0:
print(f"Failed to setup mooncake store, error code: {rc}", file=sys.stderr)
sys.exit(1)
print("\nMooncake store setup successful.")
print("Preparing test data (this may take a moment)...")
key = "perf_tensor_0"
tensor = torch.randn(num_elements, dtype=torch.float32, device='cpu')
element_size = tensor.element_size()
tensor_size_in_bit = 8 * num_elements * element_size
# ----------------------------------------
# Test 1: batch_put_tensor
# ----------------------------------------
print(f"\n--- Benchmarking batch_put_tensor ({num_iterations} iterations) ---")
put_times = []
# Test default constructor
repconfig = ReplicateConfig()
repconfig.replica_num = 1
print(repconfig)
for i in range(num_iterations+1):
store.remove_all()
print("start to pub")
start_time = time.perf_counter()
results = store.pub_tensor(key, tensor, repconfig)
end_time = time.perf_counter()
# drop the first one, because it's not stable.
if i == 0:
continue
elapsed_time = end_time - start_time
put_times.append(elapsed_time)
# (total_bytes * 8 bits/byte) / (time * 1024^3 Giga) = Gbps
throughput_gbps = (tensor_size_in_bit * repconfig.replica_num) / (elapsed_time * (1024**3))
print(f" Iteration {i+1}: {elapsed_time:.4f} s ({throughput_gbps:.2f} Gbps)")
if put_times:
avg_put_time = np.mean(put_times)
avg_put_throughput = (tensor_size_in_bit * repconfig.replica_num) / (avg_put_time * (1024**3))
print(f"Average PUT Time: {avg_put_time:.4f} s")
print(f"Average PUT Throughput: {avg_put_throughput:.2f} Gbps")
else:
print("PUT test failed to complete.")
# ----------------------------------------
# Test 2: batch_get_tensor
# ----------------------------------------
print(f"\n--- Benchmarking batch_get_tensor ({num_iterations} iterations) ---")
print(" (Pre-populating data for GET test...)")
store.remove_all()
results = store.pub_tensor(key, tensor, repconfig)
get_times = []
start_time = time.perf_counter()
retrieved_tensor = store.get_tensor(key)
end_time = time.perf_counter()
if retrieved_tensor is None:
print(f" Iteration {i+1}: FAILED (Data not retrieved correctly)", file=sys.stderr)
elapsed_time = end_time - start_time
get_times.append(elapsed_time)
throughput_gbps = (tensor_size_in_bit * 8) / (elapsed_time * (1024**3))
print(f" Iteration {i+1}: {elapsed_time:.4f} s ({throughput_gbps:.2f} Gbps)")
verify_tensor_equality(tensor, retrieved_tensor)
print("\n✅ Benchmark finished.")
except Exception as e:
print(f"\n❌ An error occurred: {e}", file=sys.stderr)
sys.exit(1)
finally:
print("Cleaning up and closing store...")
store.remove_all()
store.close()
print("Store closed.")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Mooncake Tensor API Performance Benchmark")
parser.add_argument(
"-n", "--iterations",
type=int,
default=5,
help="Number of iterations for each test (default: 5)"
)
args = parser.parse_args()
elements = [32 * 1024 * 1024, 128 * 1024 * 1024, 512 * 1024 * 1024, 1 * 1024 * 1024 * 1024, 4 * 1024 * 1024 * 1024, 16 * 1024 * 1024 * 1024, 32 * 1024 * 1024 * 1024]
for element in elements:
run_benchmark(args.iterations, element)