mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-28 07:32:52 +00:00
Savings (components/sales/Savings, lib/savings): the tokens a team uses a month, by model or by spend, at the providers' list rates, on Hanzo Cloud moved to an Enso SKU plus the members on Team, and on the reader's own GPUs on all month. Every rate is read when the page opens: the price list (GET /v1/pricing, its on-demand GPU hour too), the plan list (Team's member month) and the model list (what Hanzo bills a provider's model). The volumes, people and GPUs are the reader's, and the page says it opens with an example. Each figure says what it assumes; the GPU figure leaves the agreement out and says so, with where it meets list and that sizing is done with the customer. /calculator carries the same calculator. Your own GPUs: Zen 6's prefill as Hanzo measured it on one DGX Spark (lib/data/zen6-spark.json, scripts/zen6.py, from the enterprise-story branch) and what a Zen 6 Max rack is made of, from NVIDIA's pages (Rack, with Zen 6 Max's page). That branch's Cloud against Enterprise board is left off /benchmarks: it prints a 96.0% the page's gate refuses. Proof shows six of the data room's case studies. Partners are the programmes that exist: affiliates, White-label and the agency's plans as the plan list sells them, and the clouds Hanzo deploys on, captioned as vendors, not partners. No company is called a partner: none is on record.
69 lines
3.2 KiB
Python
69 lines
3.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Zen 6 on one DGX Spark, as the box's own files and counters hold it.
|
|
|
|
python3 scripts/zen6.py [metrics URL]
|
|
|
|
Writes lib/data/zen6-spark.json from two things the box holds: ~/bench_zen6_results.json (the prefill sweep of
|
|
2026-09-20 on the Spark, bench_zen6_matrix.py) and the vLLM server's own /metrics counters (serve-spark-zen6.sh,
|
|
:18310, the server the benchmark's arm Z asks), read now. The counters are cumulative since the server started and
|
|
include every request the benchmark's lanes made, up to four at once, so TTFT carries queueing and decode is a rate
|
|
under that load: neither is a quiet-box number, and the file says so. A quiet-box decode rate has not been measured.
|
|
The server not answering keeps the file as it is.
|
|
"""
|
|
import hashlib
|
|
import json
|
|
import os
|
|
import re
|
|
import sys
|
|
import time
|
|
import urllib.request
|
|
|
|
HOME = os.path.expanduser("~")
|
|
OUT = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "lib", "data", "zen6-spark.json")
|
|
URL = sys.argv[1] if len(sys.argv) > 1 else "http://127.0.0.1:18310/metrics"
|
|
SWEEP = os.path.join(HOME, "bench_zen6_results.json")
|
|
|
|
try:
|
|
text = urllib.request.urlopen(URL, timeout=10).read().decode()
|
|
except OSError as e:
|
|
print(f"zen6: kept the file ({URL}: {e})")
|
|
sys.exit(0)
|
|
|
|
|
|
def metric(name, **labels):
|
|
total = 0.0
|
|
for line in text.splitlines():
|
|
m = re.match(rf"^{re.escape(name)}\{{([^}}]*)\}} ([0-9.e+-]+)$", line)
|
|
if m and all(f'{k}="{v}"' in m.group(1) for k, v in labels.items()):
|
|
total += float(m.group(2))
|
|
return total
|
|
|
|
|
|
reqs = metric("vllm:e2e_request_latency_seconds_count")
|
|
gen = metric("vllm:generation_tokens_total")
|
|
sweep = json.load(open(SWEEP))["spark"]
|
|
out = {
|
|
"generated": time.strftime("%Y-%m-%d", time.gmtime()),
|
|
"box": "NVIDIA DGX Spark (GB10), 121 GiB unified memory, shared with the benchmark's other jobs",
|
|
"model": "zen6, NVFP4 with FP8 attention, weights 21.6 GiB (serve-spark-zen6.sh)",
|
|
"sweep": {
|
|
"file": "bench_zen6_results.json", "sha256": hashlib.sha256(open(SWEEP, "rb").read()).hexdigest(), "measured": "2026-09-20",
|
|
"what": "Cold prefill of a prompt of about N tokens on a quiet box (bench_zen6_matrix.py); decode was not timed (16 tokens).",
|
|
"prefill": [{"tokens": r["target_tokens"], "ms": r["cold_prefill_ms"], "tok_s": r["cold_prefill_tok_s"]} for r in sweep],
|
|
},
|
|
"load": {
|
|
"what": "vLLM's own counters since the server started, requests of the benchmark's lanes, up to 4 in flight (--max-num-seqs 4).",
|
|
"read": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), "url": URL,
|
|
"requests": int(reqs),
|
|
"prompt_tokens_mean": metric("vllm:prompt_tokens_total") / reqs,
|
|
"output_tokens_mean": gen / reqs,
|
|
"prefill_s_mean": metric("vllm:request_prefill_time_seconds_sum") / reqs,
|
|
"ttft_s_mean": metric("vllm:time_to_first_token_seconds_sum") / metric("vllm:time_to_first_token_seconds_count"),
|
|
"decode_tok_s_per_request": gen / metric("vllm:request_decode_time_seconds_sum"),
|
|
"draft_acceptance": metric("vllm:spec_decode_num_accepted_tokens_total") / metric("vllm:spec_decode_num_draft_tokens_total"),
|
|
},
|
|
}
|
|
json.dump(out, open(OUT, "w"), indent=1)
|
|
open(OUT, "a").write("\n")
|
|
print(json.dumps(out["load"]))
|