|
# |
|
# Muna |
|
# Copyright © 2026 NatML Inc. All Rights Reserved. |
|
# |
|
|
|
# /// script |
|
# requires-python = ">=3.11" |
|
# dependencies = ["muna", "modal", "truss", "typer"] |
|
# /// |
|
|
|
""" |
|
Cold-start Time To First Token (CTTFT) benchmark for Gemma 4 26B A4B. |
|
|
|
Measures the wall-clock time from a request hitting a cold system to the first |
|
generated token across two axes: the inference `--runtime` (muna | sglang) and |
|
the `--provider` (modal | baseten | ssh). Weights are pre-staged out of band (a |
|
Modal Volume for Modal, a preload command for SSH, the Baseten Delivery Network |
|
for Baseten) so the measurement captures container/process boot + model load + |
|
prefill + first token, not a one-off weight download from Hugging Face. |
|
|
|
For Modal we run with `single_use_containers=True` so every boot lands on a |
|
fresh container. For SSH each boot is a fresh process and the page cache is |
|
dropped between boots (by default) for true cold-disk reads. |
|
|
|
Reproduce with: |
|
|
|
uv run --env-file .env cttft.py --provider modal |
|
|
|
NOTE: This test requires an `HF_TOKEN` in your environment, to download the |
|
weights. |
|
""" |
|
|
|
from __future__ import annotations |
|
from collections.abc import Callable, Iterator |
|
from contextlib import contextmanager |
|
from enum import StrEnum |
|
from json import dumps |
|
from math import ceil, floor |
|
from modal import enable_output, App, Image, Secret, Volume |
|
from muna import Muna |
|
from muna.beta.openai import Message |
|
from os import environ |
|
from pathlib import Path |
|
from requests import post |
|
from rich.console import Console |
|
from rich.table import Table |
|
from statistics import mean |
|
from string import Template |
|
from subprocess import run |
|
from sys import version_info |
|
from tempfile import TemporaryDirectory |
|
from time import monotonic |
|
from typer import run as typer_run, Exit, Option |
|
from typing import Annotated |
|
|
|
console = Console() |
|
|
|
DEFAULT_TAG = "@google/gemma-4-26b-a4b-it" |
|
|
|
class Runtime(StrEnum): |
|
""" |
|
Inference runtime under test. |
|
""" |
|
muna = "muna" |
|
sglang = "sglang" |
|
|
|
class Provider(StrEnum): |
|
""" |
|
Compute provider. |
|
""" |
|
modal = "modal" |
|
baseten = "baseten" |
|
ssh = "ssh" |
|
|
|
def main( |
|
runtime: Annotated[list[Runtime] | None, Option( |
|
"--runtime", "-r", |
|
help="Inference runtime to benchmark; repeatable. Defaults to ALL runtimes." |
|
)] = None, |
|
provider: Annotated[list[Provider] | None, Option( |
|
"--provider", "-p", |
|
help="Compute provider to benchmark; repeatable. Defaults to ALL providers." |
|
)] = None, |
|
boots: Annotated[int, Option( |
|
"--boots", "-n", |
|
min=1, |
|
help="Number of cold boots per (runtime, provider)." |
|
)] = 5, |
|
prompt: Annotated[str, Option( |
|
"--prompt", |
|
help="Fixed minimal prompt sent on every cold boot." |
|
)] = "What is the capital of France?", |
|
max_tokens: Annotated[int, Option( |
|
"--max-tokens", |
|
min=1, |
|
help="Tokens to request; CTTFT only needs the first token." |
|
)] = 1, |
|
tag: Annotated[str, Option( |
|
"--tag", |
|
help="Muna predictor tag (for muna runtimes)." |
|
)] = DEFAULT_TAG, |
|
ssh_host: Annotated[str | None, Option( |
|
"--ssh-host", |
|
help="SSH target for the ssh provider (alias or user@host)." |
|
)] = None, |
|
ssh_command: Annotated[str | None, Option( |
|
"--ssh-command", |
|
help="Remote command timed per boot. Supports $prompt/$max_tokens/$tag placeholders." |
|
)] = None, |
|
ssh_preload_command: Annotated[str | None, Option( |
|
"--ssh-preload-command", |
|
help="Optional remote command run once (untimed) before boots to stage weights/cache." |
|
)] = None, |
|
ssh_port: Annotated[int | None, Option( |
|
"--ssh-port", |
|
help="SSH port (defaults to the system ssh default)." |
|
)] = None, |
|
ssh_identity: Annotated[Path | None, Option( |
|
"--ssh-identity", |
|
help="SSH identity (private key) file." |
|
)] = None, |
|
drop_caches: Annotated[bool, Option( |
|
"--drop-caches/--no-drop-caches", |
|
help="Drop the page cache before each ssh boot for true cold-disk reads (needs passwordless sudo)." |
|
)] = True, |
|
output_dir: Annotated[Path, Option( |
|
"--output-dir", |
|
help="Directory for results/<metric>__<target>.json output." |
|
)] = Path("results"), |
|
json_output: Annotated[bool, Option( |
|
"--json/--no-json", |
|
help="Also echo raw result JSON to stdout." |
|
)] = False, |
|
quiet: Annotated[bool, Option( |
|
"--quiet", "-q", |
|
help="Suppress non-essential output." |
|
)] = False, |
|
) -> None: |
|
""" |
|
Measure cold-start time to first token across `--boots` cold boots. |
|
|
|
Benchmarks the Cartesian product of selected runtimes and providers, writing |
|
one results file per (runtime, provider) so distribution statistics |
|
(p50/p90) can be computed later. |
|
""" |
|
runtimes = runtime or list(Runtime) |
|
providers = provider or list(Provider) |
|
if Provider.ssh in providers: |
|
if not ssh_host or not ssh_command: |
|
console.print("[red]error:[/] --provider ssh requires --ssh-host and --ssh-command.") |
|
raise Exit(code=1) |
|
if len(runtimes) != 1: |
|
console.print( |
|
"[red]error:[/] --provider ssh uses a single model-agnostic --ssh-command; " |
|
"select exactly one --runtime (it is only a results label)." |
|
) |
|
raise Exit(code=1) |
|
output_dir.mkdir(parents=True, exist_ok=True) |
|
records: dict[str, dict] = { } |
|
for prov in providers: |
|
for rt in runtimes: |
|
session = _make_session( |
|
rt, prov, |
|
tag=tag, |
|
prompt=prompt, |
|
max_tokens=max_tokens, |
|
ssh_host=ssh_host, |
|
ssh_command=ssh_command, |
|
ssh_preload_command=ssh_preload_command, |
|
ssh_port=ssh_port, |
|
ssh_identity=ssh_identity, |
|
drop_caches=drop_caches, |
|
) |
|
label = f"{rt.value}-on-{prov.value}" |
|
if session is None: |
|
console.print(f"[yellow]skip[/] {label}: not implemented") |
|
continue |
|
samples: list[float] = [] |
|
with session as boot_once: |
|
for index in range(boots): |
|
elapsed = boot_once() |
|
samples.append(elapsed) |
|
print(f"{label} boot {index + 1}/{boots}: {elapsed:.2f}s") |
|
record = { |
|
"metric": "cttft", |
|
"target": label, |
|
"runtime": rt.value, |
|
"provider": prov.value, |
|
"tag": tag, |
|
"config": { |
|
"boots": boots, |
|
"prompt": prompt, |
|
"max_tokens": max_tokens, |
|
"batch_size": 1, |
|
}, |
|
"summary": _summarize(samples), |
|
"samples": samples, |
|
} |
|
output = output_dir / f"cttft__{label}.json" |
|
output.write_text(dumps(record, indent=2)) |
|
records[label] = record |
|
if not quiet and records: |
|
_render(records, output_dir) |
|
if json_output: |
|
console.print_json(dumps(records)) |
|
raise Exit() |
|
|
|
def _make_session( |
|
runtime: Runtime, |
|
provider: Provider, |
|
*, |
|
tag: str, |
|
prompt: str, |
|
max_tokens: int, |
|
ssh_host: str | None, |
|
ssh_command: str | None, |
|
ssh_preload_command: str | None, |
|
ssh_port: int | None, |
|
ssh_identity: Path | None, |
|
drop_caches: bool, |
|
): |
|
""" |
|
Return a session context manager for `(runtime, provider)`, or `None` if the |
|
combination is not implemented (skipped with a warning by the caller). |
|
""" |
|
match (runtime, provider): |
|
case (Runtime.muna, Provider.modal): |
|
return _muna_modal_session(tag=tag, prompt=prompt, max_tokens=max_tokens) |
|
case (Runtime.sglang, Provider.modal): |
|
return _sglang_modal_session(prompt=prompt, max_tokens=max_tokens) |
|
case (_, Provider.ssh): |
|
return _ssh_session( |
|
host=ssh_host, |
|
command=ssh_command, |
|
preload_command=ssh_preload_command, |
|
port=ssh_port, |
|
identity=ssh_identity, |
|
drop_caches=drop_caches, |
|
prompt=prompt, |
|
max_tokens=max_tokens, |
|
tag=tag, |
|
) |
|
case _: |
|
return None # baseten remains a documented stub (see _profile_*_on_baseten) |
|
|
|
@contextmanager |
|
def _muna_modal_session( |
|
*, |
|
tag: str, |
|
prompt: str, |
|
max_tokens: int, |
|
) -> Iterator[Callable[[], float]]: |
|
""" |
|
Session for a compiled Muna model on Modal. |
|
|
|
Preloads the model weights onto a Modal volume (untimed) so cold boots read |
|
weights from the volume instead of downloading them from Hugging Face, then |
|
yields a `boot_once` that times one fresh-container cold boot. |
|
`single_use_containers=True` retires the container after every input, so each |
|
boot is genuinely cold. |
|
""" |
|
# Pre-load the model weights on a Modal volume |
|
preload_app = App("muna-benchmark-preload") |
|
volume = Volume.from_name("muna-benchmark", create_if_missing=True, version=2) |
|
image = (Image |
|
.from_registry(f"python:{version_info.major}.{version_info.minor}-slim-bookworm") # use smallest Python image possible |
|
.uv_pip_install("muna") |
|
) |
|
secret = Secret.from_dict({ "MUNA_ACCESS_KEY": environ["MUNA_ACCESS_KEY"] }) |
|
def _preload(): |
|
muna = Muna() |
|
muna.predictions.create(tag, inputs={ }) # Muna preloads but doesn't execute compiled model |
|
preload_fn = preload_app.function( |
|
image=image, |
|
serialized=True, |
|
volumes={ "/muna": volume }, |
|
env={ "MUNA_HOME": "/muna" }, |
|
secrets=[secret], |
|
timeout=900, |
|
)(_preload) |
|
with enable_output(), preload_app.run(): |
|
preload_fn.remote() |
|
print("Preloaded model weights on Modal volume") |
|
# Benchmark on a B200, one fresh container per cold boot |
|
benchmark_app = App("muna-benchmark") |
|
def _benchmark(tag: str, prompt: str, max_tokens: int): |
|
muna = Muna() |
|
message = Message(role="user", content=prompt) |
|
stream = muna.predictions.stream( |
|
tag, |
|
inputs={ "messages": [message], "max_output_tokens": max_tokens }, |
|
acceleration="local_gpu" |
|
) |
|
next(stream) # block until the first token is produced |
|
benchmark_fn = benchmark_app.function( |
|
image=image, |
|
serialized=True, |
|
cpu=32, |
|
gpu="b200", |
|
volumes={ "/muna": volume }, |
|
env={ "MUNA_HOME": "/muna" }, |
|
secrets=[secret], |
|
timeout=300, |
|
single_use_containers=True, |
|
)(_benchmark) |
|
with enable_output(), benchmark_app.run(): |
|
def boot_once() -> float: |
|
start = monotonic() |
|
benchmark_fn.remote(tag, prompt, max_tokens) |
|
return monotonic() - start |
|
yield boot_once |
|
|
|
@contextmanager |
|
def _sglang_modal_session( |
|
*, |
|
prompt: str, |
|
max_tokens: int, |
|
) -> Iterator[Callable[[], float]]: |
|
""" |
|
Session for SGLang on Modal. |
|
|
|
Mirrors `_muna_modal_session`: preloads the gated weights onto a Modal volume |
|
(untimed), then yields a `boot_once` that times one cold boot of a fresh |
|
single-use container. |
|
""" |
|
repo = "google/gemma-4-26B-A4B-it" |
|
model_dir = "/sglang/gemma-4-26b-a4b-it" |
|
# Preload the model weights on a Modal volume |
|
preload_app = App("sglang-benchmark-preload") |
|
volume = Volume.from_name("sglang-benchmark", create_if_missing=True, version=2) |
|
preload_image = (Image |
|
.from_registry(f"python:{version_info.major}.{version_info.minor}-slim-bookworm") |
|
.uv_pip_install("huggingface_hub[hf_transfer]") |
|
.env({ "HF_HUB_ENABLE_HF_TRANSFER": "1" }) |
|
) |
|
secret = Secret.from_dict({ "HF_TOKEN": environ["HF_TOKEN"] }) |
|
def _preload(): |
|
from huggingface_hub import snapshot_download |
|
snapshot_download(repo, local_dir=model_dir) # download the gated weights once |
|
preload_fn = preload_app.function( |
|
image=preload_image, |
|
serialized=True, |
|
volumes={ "/sglang": volume }, |
|
secrets=[secret], |
|
timeout=900, |
|
)(_preload) |
|
with enable_output(), preload_app.run(): |
|
preload_fn.remote() |
|
print("Preloaded model weights on Modal volume") |
|
# Benchmark on a B200, one fresh container per cold boot |
|
benchmark_app = App("sglang-benchmark") |
|
image = Image.from_registry( |
|
"lmsysorg/sglang:v0.5.13", |
|
add_python=f"{version_info.major}.{version_info.minor}" |
|
) |
|
def _benchmark(model_dir: str, prompt: str, max_tokens: int): |
|
import sglang as sgl |
|
engine = sgl.Engine(model_path=model_dir) |
|
engine.generate(prompt, sampling_params={ "max_new_tokens": max_tokens }) |
|
benchmark_fn = benchmark_app.function( |
|
image=image, |
|
serialized=True, |
|
cpu=32, |
|
gpu="b200", |
|
volumes={ "/sglang": volume }, |
|
timeout=300, |
|
single_use_containers=True, |
|
)(_benchmark) |
|
with enable_output(), benchmark_app.run(): |
|
def boot_once() -> float: |
|
start = monotonic() |
|
benchmark_fn.remote(model_dir, prompt, max_tokens) |
|
return monotonic() - start |
|
yield boot_once |
|
|
|
@contextmanager |
|
def _ssh_session( |
|
*, |
|
host: str, |
|
command: str, |
|
preload_command: str | None, |
|
port: int | None, |
|
identity: Path | None, |
|
drop_caches: bool, |
|
prompt: str, |
|
max_tokens: int, |
|
tag: str, |
|
) -> Iterator[Callable[[], float]]: |
|
""" |
|
Session for a runtime on a baremetal box reached over SSH. |
|
|
|
Shells out to the system `ssh` binary (no extra deps). On enter it runs the |
|
optional preload once (untimed, mirroring Modal staging weights to a volume). |
|
Each boot is a fresh remote process; the page cache is dropped (untimed, |
|
unless disabled) beforehand so the timed command reads weights from disk. |
|
Commands are templated with `$prompt`/`$max_tokens`/`$tag`. |
|
""" |
|
base = ["ssh", "-o", "BatchMode=yes"] |
|
if port is not None: |
|
base += ["-p", str(port)] |
|
if identity is not None: |
|
base += ["-i", str(identity)] |
|
def fill(template: str) -> str: |
|
return Template(template).safe_substitute(prompt=prompt, max_tokens=max_tokens, tag=tag) |
|
if preload_command: |
|
run([*base, host, fill(preload_command)], check=True) # untimed: populate weight/predictor cache once |
|
def boot_once() -> float: |
|
if drop_caches: |
|
# untimed: evict weights from the page cache -> true cold-disk read |
|
run([*base, host, "sync && echo 1 | sudo tee /proc/sys/vm/drop_caches > /dev/null"], check=True) |
|
start = monotonic() |
|
run([*base, host, fill(command)], check=True) # process cold-start: load + first token + exit |
|
return monotonic() - start |
|
yield boot_once |
|
|
|
def _profile_muna_on_baseten(): |
|
pass |
|
|
|
def _profile_sglang_on_baseten(): |
|
""" |
|
Profile the Cold-start Time to First Token (CTTFT) of SGLang on Baseten (B10). |
|
|
|
Deploys a custom SGLang Docker server whose weights are delivered by the |
|
Baseten Delivery Network (BDN), so cold boots read weights from Baseten's |
|
in-cluster cache instead of downloading from Hugging Face. The Truss config |
|
is generated procedurally and pushed via the programmatic API (no CLI). |
|
|
|
Stub: not yet wired into the session/loop model (Baseten access pending). |
|
""" |
|
from truss import login, push |
|
from truss.base.truss_config import ( |
|
BaseImage, DockerServer, Resources, |
|
TrussConfig, Weights, WeightsSource |
|
) |
|
login(environ["BASETEN_API_KEY"]) |
|
config = TrussConfig( |
|
model_name="gemma4-sglang-cttft", |
|
resources=Resources(accelerator="B200"), |
|
base_image=BaseImage(image="lmsysorg/sglang:v0.5.13"), |
|
docker_server=DockerServer( |
|
start_command=( |
|
"python3 -m sglang.launch_server --model-path /models/gemma " |
|
"--served-model-name gemma-4 --host 0.0.0.0 --port 8000" |
|
), |
|
server_port=8000, |
|
predict_endpoint="/v1/chat/completions", |
|
readiness_endpoint="/health", |
|
liveness_endpoint="/health", |
|
), |
|
weights=Weights([ |
|
WeightsSource( |
|
source="hf://google/gemma-4-26B-A4B-it@main", # TODO: pin to a commit SHA |
|
mount_location="/models/gemma", |
|
#auth_secret_name="hf_access_token", # Baseten secret holding the HF token |
|
allow_patterns=["*.safetensors", "*.json", "tokenizer*"], |
|
) |
|
]), |
|
) |
|
# Push the procedurally-generated Truss (no model.py needed for a docker_server) |
|
with TemporaryDirectory() as directory: |
|
config.write_to_yaml_file(Path(directory) / "config.yaml") |
|
deployment = push( |
|
directory, |
|
remote="baseten", |
|
publish=True, |
|
model_name=config.model_name |
|
) |
|
deployment.wait_for_active(timeout_seconds=30 * 60) # blocks through BDN mirroring + boot |
|
predict_url = deployment._baseten_service.predict_url |
|
print(f"Deployed SGLang on Baseten: {predict_url}") |
|
# Fire one request to confirm the endpoint serves |
|
response = post( |
|
predict_url, |
|
headers={ "Authorization": f"Api-Key {environ['BASETEN_API_KEY']}" }, |
|
json={ "messages": [{ "role": "user", "content": "What is the capital of France?" }], "max_tokens": 1 }, |
|
) |
|
print(response.json()) |
|
|
|
def _percentile(ordered: list[float], pct: float) -> float: |
|
if len(ordered) == 1: |
|
return ordered[0] |
|
rank = (len(ordered) - 1) * pct / 100 |
|
low, high = floor(rank), ceil(rank) |
|
if low == high: |
|
return ordered[int(rank)] |
|
return ordered[low] * (high - rank) + ordered[high] * (rank - low) |
|
|
|
def _summarize(samples: list[float]) -> dict[str, float | int]: |
|
if not samples: |
|
return { "n": 0 } |
|
ordered = sorted(samples) |
|
return { |
|
"n": len(ordered), |
|
"min": ordered[0], |
|
"p50": _percentile(ordered, 50), |
|
"p90": _percentile(ordered, 90), |
|
"max": ordered[-1], |
|
"mean": mean(ordered), |
|
} |
|
|
|
def _render(records: dict[str, dict], output_dir: Path) -> None: |
|
table = Table(show_header=True, header_style="bold cyan", box=None) |
|
table.add_column("Target", style="bold green") |
|
table.add_column("n", style="white", justify="right") |
|
table.add_column("p50 (s)", style="white", justify="right") |
|
table.add_column("p90 (s)", style="white", justify="right") |
|
table.add_column("mean (s)", style="white", justify="right") |
|
for name, record in records.items(): |
|
summary = record["summary"] |
|
if summary.get("n"): |
|
table.add_row( |
|
name, |
|
str(summary["n"]), |
|
f"{summary['p50']:.2f}", |
|
f"{summary['p90']:.2f}", |
|
f"{summary['mean']:.2f}", |
|
) |
|
else: |
|
table.add_row(name, "0", "[red]no samples[/]", "", "") |
|
console.print(table) |
|
console.print(f"[cyan]CTTFT[/] -> wrote results to [bold]{output_dir}[/].") |
|
|
|
if __name__ == "__main__": |
|
typer_run(main) |