Files
2026-07-13 13:17:40 +08:00

166 lines
5.2 KiB
Python

import argparse
import time
from typing import Dict
import numpy as np
import torch
from benchmark import (
Benchmark,
BenchmarkMetric,
RuntimeEnvSetupTracker,
collect_dataset_stats,
benchmark_py_modules,
)
from torchvision.models import ResNet50_Weights, resnet50
import ray
from ray.data import ActorPoolStrategy
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument(
"--data-directory",
help=(
"Name of the S3 directory in the air-example-data-2 "
"bucket to load data from."
),
)
parser.add_argument(
"--data-format",
choices=["parquet", "raw"],
help="The format of the data. Can be either parquet or raw.",
)
parser.add_argument(
"--smoke-test",
action="store_true",
default=False,
)
parser.add_argument(
"--chaos-test",
action="store_true",
default=False,
)
return parser.parse_args()
def main(args):
data_directory: str = args.data_directory
data_format: str = args.data_format
smoke_test: bool = args.smoke_test
chaos_test: bool = args.chaos_test
data_url = f"s3://anonymous@air-example-data-2/{data_directory}"
print(f"Running GPU batch prediction with data from {data_url}")
# Largest batch that can fit on a T4.
INFERENCE_BATCH_SIZE = 900
device = "cpu" if smoke_test else "cuda"
weights = ResNet50_Weights.DEFAULT
model = resnet50(weights=weights)
model_ref = ray.put(model)
# Get the preprocessing transforms from the pre-trained weights.
transform = weights.transforms()
start_time = time.time()
if data_format == "raw":
if smoke_test:
data_url += "/dog_1.jpg"
ds = ray.data.read_images(data_url, size=(256, 256))
elif data_format == "parquet":
if smoke_test:
data_url += "/8cc8856e16c343829ef320fef4b353b1_000000.parquet"
ds = ray.data.read_parquet(data_url)
# Preprocess the images using standard preprocessing
def preprocess(image_batch: Dict[str, np.ndarray]) -> Dict[str, np.ndarray]:
tensor_batch = torch.as_tensor(image_batch["image"], dtype=torch.float)
# (B, H, W, C) -> (B, C, H, W). This is required for the torchvision transform.
# https://pytorch.org/vision/main/models/generated/torchvision.models.resnet50.html#torchvision.models.ResNet50_Weights # noqa
tensor_batch = tensor_batch.permute(0, 3, 1, 2)
transformed_batch = transform(tensor_batch).numpy()
return {"image": transformed_batch}
class Predictor:
def __init__(self, model):
self.model = ray.get(model)
self.model.eval()
self.model.to(device)
def __call__(self, batch: Dict[str, np.ndarray]) -> Dict[str, np.ndarray]:
with torch.inference_mode():
output = self.model(torch.as_tensor(batch["image"], device=device))
return {"predictions": output.cpu().numpy()}
start_time_without_metadata_fetching = time.time()
if smoke_test:
compute = ActorPoolStrategy(size=4)
num_gpus = 0
else:
compute = ActorPoolStrategy(min_size=1, max_size=10)
num_gpus = 1
ds = ds.map_batches(preprocess, batch_size="auto")
ds = ds.map_batches(
Predictor,
batch_size=INFERENCE_BATCH_SIZE,
compute=compute,
num_gpus=num_gpus,
fn_constructor_kwargs={"model": model_ref},
)
total_images = 0
# NOTE: We're iterating over ref-bundles to avoid pulling blocks into the
# driver, therefore making it a factor impacting benchmark performance
for bundle in ds.iter_internal_ref_bundles():
total_images += bundle.num_rows()
end_time = time.time()
total_time = end_time - start_time
throughput = total_images / (total_time)
total_time_without_metadata_fetch = end_time - start_time_without_metadata_fetching
throughput_without_metadata_fetch = total_images / (
total_time_without_metadata_fetch
)
print("Total time (sec): ", total_time)
print("Throughput (img/sec): ", throughput)
print("Total time w/o metadata fetching (sec): ", total_time_without_metadata_fetch)
print(
"Throughput w/o metadata fetching (img/sec): ",
throughput_without_metadata_fetch,
)
if chaos_test:
dead_nodes = [node["NodeID"] for node in ray.nodes() if not node["Alive"]]
assert dead_nodes
print(f"Total chaos killed: {dead_nodes}")
# For structured output integration with internal tooling
results = collect_dataset_stats(ds)
results = {
BenchmarkMetric.RUNTIME: total_time,
BenchmarkMetric.THROUGHPUT: throughput,
"data_directory": data_directory,
"data_format": data_format,
"total_time_s_wo_metadata_fetch": total_time_without_metadata_fetch,
"throughput_images_s_wo_metadata_fetch": throughput_without_metadata_fetch,
}
results["runtime_env_setup"] = RuntimeEnvSetupTracker.collect()
return results
if __name__ == "__main__":
args = parse_args()
ray.init(runtime_env={"py_modules": benchmark_py_modules()})
benchmark = Benchmark()
benchmark.run_fn("batch-inference", main, args)
benchmark.write_result()