Files
2026-07-13 13:22:34 +08:00

149 lines
4.1 KiB
Python

"""
Benchmark for multi-part upload and download of artifacts.
"""
import hashlib
import json
import os
import pathlib
import tempfile
from concurrent.futures import ThreadPoolExecutor, as_completed
import pandas as pd
import psutil
from tqdm.auto import tqdm
import mlflow
from mlflow.environment_variables import (
MLFLOW_ENABLE_MULTIPART_DOWNLOAD,
MLFLOW_ENABLE_MULTIPART_UPLOAD,
)
from mlflow.utils.time import Timer
GiB = 1024**3
def show_system_info():
svmem = psutil.virtual_memory()
info = json.dumps(
{
"MLflow version": mlflow.__version__,
"MPU enabled": MLFLOW_ENABLE_MULTIPART_DOWNLOAD.get(),
"MPD enabled": MLFLOW_ENABLE_MULTIPART_UPLOAD.get(),
"CPU count": psutil.cpu_count(),
"Memory usage (total) [GiB]": svmem.total // GiB,
"Memory used [GiB]": svmem.used // GiB,
"Memory available [GiB]": svmem.available // GiB,
},
indent=2,
)
max_len = max(map(len, info.splitlines()))
print("=" * max_len)
print(info)
print("=" * max_len)
def md5_checksum(path):
file_hash = hashlib.sha256()
with open(path, "rb") as f:
while chunk := f.read(1024**2):
file_hash.update(chunk)
return file_hash.hexdigest()
def assert_checksum_equal(path1, path2):
assert md5_checksum(path1) == md5_checksum(path2), f"Checksum mismatch for {path1} and {path2}"
def yield_random_bytes(num_bytes):
while num_bytes > 0:
chunk_size = min(num_bytes, 1024**2)
yield os.urandom(chunk_size)
num_bytes -= chunk_size
def generate_random_file(path, num_bytes):
with open(path, "wb") as f:
for chunk in yield_random_bytes(num_bytes):
f.write(chunk)
def upload_and_download(file_size, num_files):
with tempfile.TemporaryDirectory() as tmpdir:
tmpdir = pathlib.Path(tmpdir)
# Prepare files
src_dir = tmpdir / "src"
src_dir.mkdir()
files = {}
with ThreadPoolExecutor() as pool:
futures = []
for i in range(num_files):
f = src_dir / str(i)
futures.append(pool.submit(generate_random_file, f, file_size))
files[f.name] = f
for fut in tqdm(
as_completed(futures),
total=len(futures),
desc="Generating files",
colour="#FFA500",
):
fut.result()
# Upload
with mlflow.start_run() as run:
with Timer() as t_upload:
mlflow.log_artifacts(str(src_dir))
# Download
dst_dir = tmpdir / "dst"
dst_dir.mkdir()
with Timer() as t_download:
mlflow.artifacts.download_artifacts(
artifact_uri=f"{run.info.artifact_uri}/", dst_path=dst_dir
)
# Verify checksums
with ThreadPoolExecutor() as pool:
futures = []
for f in dst_dir.rglob("*"):
if f.is_dir():
continue
futures.append(pool.submit(assert_checksum_equal, f, files[f.name]))
for fut in tqdm(
as_completed(futures),
total=len(futures),
desc="Verifying checksums",
colour="#FFA500",
):
fut.result()
return t_upload.elapsed, t_download.elapsed
def main():
# Uncomment the following lines if you're running this script outside of Databricks
# using a personal access token:
# mlflow.set_tracking_uri("databricks")
# mlflow.set_experiment("/Users/<username>/benchmark")
FILE_SIZE = 1 * GiB
NUM_FILES = 2
NUM_ATTEMPTS = 3
show_system_info()
stats = []
for i in range(NUM_ATTEMPTS):
print(f"Attempt {i + 1} / {NUM_ATTEMPTS}")
stats.append(upload_and_download(FILE_SIZE, NUM_FILES))
df = pd.DataFrame(stats, columns=["upload [s]", "download [s]"])
# show mean, min, max in markdown table
print(df.aggregate(["count", "mean", "min", "max"]).to_markdown())
if __name__ == "__main__":
main()