From 97c2f34700d7b8776783508fdc0769ffcbaa5719 Mon Sep 17 00:00:00 2001 From: Gahow Wang Date: Fri, 17 Jul 2026 00:32:01 +0800 Subject: [PATCH] Add Qwen30 prefill fidelity experiment --- .../experiment-card.md | 54 ++ runs/frontier-phase-factorial-v0/fleet.toml | 24 + .../jobs_full.toml | 241 +++++++ .../jobs_smoke.toml | 21 + .../mock-phase-factorial.png | Bin 0 -> 118977 bytes .../plot_mock_phase_factorial.py | 45 ++ .../qwen30_prefill_client.py | 216 ++++++ .../remote-sync-marker/README.md | 6 + .../run_frontier_qwen30_prefill_surface.py | 400 +++++++++++ .../run_qwen30_prefill_real_config.sh | 135 ++++ .../frontier_surface_frozen.json | 647 ++++++++++++++++++ .../frontier_surface_frozen.json | 647 ++++++++++++++++++ .../frontier_surface_frozen.json | 647 ++++++++++++++++++ .../test_phase_factorial.py | 39 ++ 14 files changed, 3122 insertions(+) create mode 100644 runs/frontier-phase-factorial-v0/experiment-card.md create mode 100644 runs/frontier-phase-factorial-v0/fleet.toml create mode 100644 runs/frontier-phase-factorial-v0/jobs_full.toml create mode 100644 runs/frontier-phase-factorial-v0/jobs_smoke.toml create mode 100644 runs/frontier-phase-factorial-v0/mock-phase-factorial.png create mode 100644 runs/frontier-phase-factorial-v0/plot_mock_phase_factorial.py create mode 100644 runs/frontier-phase-factorial-v0/qwen30_prefill_client.py create mode 100644 runs/frontier-phase-factorial-v0/remote-sync-marker/README.md create mode 100644 runs/frontier-phase-factorial-v0/run_frontier_qwen30_prefill_surface.py create mode 100644 runs/frontier-phase-factorial-v0/run_qwen30_prefill_real_config.sh create mode 100644 runs/frontier-phase-factorial-v0/simulator-tp1/frontier_surface_frozen.json create mode 100644 runs/frontier-phase-factorial-v0/simulator-tp2/frontier_surface_frozen.json create mode 100644 runs/frontier-phase-factorial-v0/simulator-tp4/frontier_surface_frozen.json create mode 100644 runs/frontier-phase-factorial-v0/test_phase_factorial.py diff --git a/runs/frontier-phase-factorial-v0/experiment-card.md b/runs/frontier-phase-factorial-v0/experiment-card.md new file mode 100644 index 0000000..f291ab7 --- /dev/null +++ b/runs/frontier-phase-factorial-v0/experiment-card.md @@ -0,0 +1,54 @@ +# 实验 EXP-SIMFID-PHASE-FACTORIAL:prefill-only 是否是 simulator ranking 的容易区间? + +> **状态:** 已批准,运行中(用户于 2026-07-17 明确要求先完成 235B mixed 与 30B prefill-only) + +## Claim 与决策 + +- **Parent claim:** Frontier 的 config-ranking fidelity 由 workload execution phase 决定;prefill-only 可能比 decode/mixed 更容易由 isolated operator profiles 组合。 +- **目的:** 用跨 model 的 phase factorial 区分 phase-complexity explanation 与 model/runtime/profile-specific explanation。 +- **Competing hypotheses:** H-phase:prefill-only 是低状态反馈的 compatibility envelope,因此 30B prefill-only 也能达到低-regret ranking,而 decode/mixed 更容易失真;H-stack:30B 的失败主要来自 FA3/CUDA-graph/routing/profile composition 等 stack-specific mismatch,因此即使 prefill-only 也可能失败;H-margin:mixed 的 differential 可以错误,但只要 topology margin 足够仍会选对 top set。 +- **事前预测:** 若 H-phase 成立,30B prefill-only 满足 regret ≤5%、Kendall τ-b ≥0.8,并明显优于同模型 mixed;若 H-stack 成立,30B prefill-only 仍过不了 gate;若 H-margin 成立,235B mixed 可保持 top set,但会漏掉 MNS/MBT pair directions。 +- **判定规则:** 30B prefill-only fail → “prefill-only 是充分条件”被否证,下一步优先做 same-model execution-context ablation;30B prefill-only pass 且新增 decode-heavy mixed fail → 支持 phase hypothesis;30B prefill-only 与 235B mixed 都 pass → phase 不能单独解释,转向 margin-aware compatibility envelope。 + +## Setup + +- **自变量:** model×phase:已有 Qwen3-235B-A22B-FP8 mixed;新增 Qwen3-30B-A3B BF16 prefill-only。 +- **控制变量:** dash0 H20、community vLLM 与各自 frozen Frontier profile、同一 config 内 real/sim 的 request shape、arrival lattice、SLO、prefix policy、MNS/MBT/TP 与随机种子。 +- **30B system context:** community vLLM 0.20.0+cu129,BF16 weights/activation/KV,TP∈{1,2,4},MNS∈{8,16,32,64},MBT=8192,chunked prefill on,prefix off;real 保留 runtime 默认 CUDA graph,Frontier profile-only 不做 E2E calibration。 +- **30B workload:** fixed ISL=2048、OSL=1,64 个不同 token-chain prompts,uniform open-loop QPS;fresh server per `(config, rate, round)`,target-rate warmup 与 measured requests 分离。 +- **30B SLO:** TTFT≤1256 ms,至少 61/64 requests 通过;primary score 为最大共同 tested feasible req/s / 实际 TP GPUs。 +- **235B baseline:** 已冻结 `ISL=2048, OSL=128`、8 configs、68 个 fresh-server anchors;primary sensitivity TTFT≤1256 ms、TPOT≤150 ms。 +- **Baselines:** real community vLLM;Frontier same-stack profile-only;historical Qwen30 mixed profile-only;historical frozen per-TP calibration 只作为 upper bound,不参与本 case 拟合。 +- **Metrics:** top set、worst tie-break regret、Kendall τ-b、exact/non-tied pair direction、anchor confusion、absolute capacity、TTFT p50/p95、real trial variance与GPU-hour。 + +## 预期产物与 review + +- **预期数据:** 30B frozen simulator surface;real config-rate anchors;两模型 phase comparison table;failure mechanism breakdown。 +- **Figure prototype:** `mock-phase-factorial.png`;x=model×phase,左轴=worst regret,右侧 annotation=τ-b;虚线是 5% regret gate。mock 只表达可区分趋势,不进入结论。 +- **人工 review:** 已批准。用户要求 smoke 通过后推进实验;先做这两个 case,再根据 hypothesis verdict 扩展。 +- **Review 意见:** 不把 existing 235B mixed top-set match 隐藏掉;不把“全 config 并列”算作成功 hit;30B prefill-only 必须使用相同 primary ranking objective。 + +## Benchmark design audit + +| Risk | Verdict | 处理 | +|---|---|---| +| Selective benchmarking | PASS for initial screen | 同时报已有 235B mixed success和内部 pairwise failure;后续 expansion 由预注册 verdict 触发 | +| Simplified workload | NEEDS EVIDENCE | fixed-shape 只用于 phase isolation,不外推 trace-faithful mixed | +| Calibration=evaluation | PASS | 新 case 不用 serving E2E 数据拟合 scale | +| Missing significance | NEEDS EVIDENCE until run | boundary anchors做独立 fresh-server repeat,保留 disagreement | +| Relative-only result | PASS by design | 同时报 req/s/GPU、TTFT distribution、rank/regret | + +## 复现信息 + +- **Code:** AITuner branch `codex/fidelity-prefix-pilot-20260714`;Frontier upstream `d9cfeb6d8791fbf2f295dd9744c56a666171776e` + frozen known patches。 +- **Environment:** 只使用 dash0 8×H20;Qwen30 venv `/tmp/wjh/venvs/vllm-0.20.0-cu129-profiler-v1`;model `/home/admin/cpfs/wjh/models/Qwen/Qwen3-30B-A3B`。 +- **产物路径:** local/remote `runs/frontier-phase-factorial-v0/`;raw GPU artifacts 由 fleet harvest,condensed JSON/CSV 进入结果目录。 +- **已知 deviation:** 235B 为 FP8/vLLM0.10.2/FlashInfer eager,30B 为 BF16/vLLM0.20/FA3/default CUDA graph;因此跨模型只检验 hypothesis consistency,causal phase claim 最终仍需 same-model phase pair。 + +## 结果 + +- **观察事实:** 235B fixed-shape mixed 已完成:real/sim TP4 top set exact match,worst regret=0、τ-b=0.8944;但 20 个 real non-tie pairs 只保持 16 个,10/34 anchors false-infeasible,TP8 MNS×MBT interaction 被漏掉。30B prefill-only 待运行。 +- **异常:** 无。 +- **Interpretation 与剩余 alternatives:** 强版本“mixed 必然失败”已被 235B top-set result 削弱;仍可能存在 phase-dependent error magnitude,由 topology margin 掩盖。 +- **Claim update:** unchanged,等待 30B prefill-only。 +- **下一步:** freeze 30B simulator surface → guided real anchors → joint verdict;只有判定需要时扩展 decode-heavy 235B 或 trace-shaped 30B prefill。 diff --git a/runs/frontier-phase-factorial-v0/fleet.toml b/runs/frontier-phase-factorial-v0/fleet.toml new file mode 100644 index 0000000..76a9e93 --- /dev/null +++ b/runs/frontier-phase-factorial-v0/fleet.toml @@ -0,0 +1,24 @@ +version = 1 + +[paths] +state_dir = "runs/frontier-phase-factorial-v0/fleet-state" +artifacts_dir = "runs/frontier-phase-factorial-v0/fleet-artifacts" + +[ssh] +connect_timeout_sec = 10 + +[scheduler] +gpu_free_memory_mb = 1024 +gpu_free_utilization_pct = 10 +prefer_pack = true + +[sync] +mode = "scp" +local_path = "runs/frontier-phase-factorial-v0/remote-sync-marker" + +[[hosts]] +name = "dash0" +ssh_alias = "dash0" +enabled = true +sync_remote_path = "/home/admin/cpfs/wjh/aituner/phase-factorial-sync-marker" +fleet_root = "/home/admin/cpfs/wjh/aituner/gpu-fleet-phase-factorial-v0" diff --git a/runs/frontier-phase-factorial-v0/jobs_full.toml b/runs/frontier-phase-factorial-v0/jobs_full.toml new file mode 100644 index 0000000..c67f255 --- /dev/null +++ b/runs/frontier-phase-factorial-v0/jobs_full.toml @@ -0,0 +1,241 @@ +version = 1 + +[[jobs]] +name = "qwen30-prefill-real-tp1-mns8-20260717-v1" +gpus = 1 +gpu_model = "H20" +hosts = ["dash0"] +command = "cd /home/admin/cpfs/wjh/aituner/aituner-qwen30-vllm020-profile-v1/runs/frontier-phase-factorial-v0 && timeout --signal=TERM --kill-after=30s 7200 bash run_qwen30_prefill_real_config.sh" +artifacts = ["artifacts/real-tp1-mns8-v1"] + +[jobs.env] +HOME = "/tmp/wjh" +XDG_CACHE_HOME = "/tmp/wjh/.cache" +VLLM_CACHE_ROOT = "/tmp/wjh/.cache/vllm" +TP = "1" +MNS = "8" +RATES = "4 8 16 32 64" +SERVER_PORT = "8720" +OUTPUT_ROOT = "/home/admin/cpfs/wjh/aituner/gpu-fleet-phase-factorial-v0/artifacts/real-tp1-mns8-v1" +VENV_ROOT = "/tmp/wjh/venvs/vllm-0.20.0-cu129-profiler-v1" +MODEL_ROOT = "/home/admin/cpfs/wjh/models/Qwen/Qwen3-30B-A3B" + +[[jobs]] +name = "qwen30-prefill-real-tp1-mns16-20260717-v1" +gpus = 1 +gpu_model = "H20" +hosts = ["dash0"] +command = "cd /home/admin/cpfs/wjh/aituner/aituner-qwen30-vllm020-profile-v1/runs/frontier-phase-factorial-v0 && timeout --signal=TERM --kill-after=30s 7200 bash run_qwen30_prefill_real_config.sh" +artifacts = ["artifacts/real-tp1-mns16-v1"] + +[jobs.env] +HOME = "/tmp/wjh" +XDG_CACHE_HOME = "/tmp/wjh/.cache" +VLLM_CACHE_ROOT = "/tmp/wjh/.cache/vllm" +TP = "1" +MNS = "16" +RATES = "4 8 16 32 64" +SERVER_PORT = "8721" +OUTPUT_ROOT = "/home/admin/cpfs/wjh/aituner/gpu-fleet-phase-factorial-v0/artifacts/real-tp1-mns16-v1" +VENV_ROOT = "/tmp/wjh/venvs/vllm-0.20.0-cu129-profiler-v1" +MODEL_ROOT = "/home/admin/cpfs/wjh/models/Qwen/Qwen3-30B-A3B" + +[[jobs]] +name = "qwen30-prefill-real-tp1-mns32-20260717-v1" +gpus = 1 +gpu_model = "H20" +hosts = ["dash0"] +command = "cd /home/admin/cpfs/wjh/aituner/aituner-qwen30-vllm020-profile-v1/runs/frontier-phase-factorial-v0 && timeout --signal=TERM --kill-after=30s 7200 bash run_qwen30_prefill_real_config.sh" +artifacts = ["artifacts/real-tp1-mns32-v1"] + +[jobs.env] +HOME = "/tmp/wjh" +XDG_CACHE_HOME = "/tmp/wjh/.cache" +VLLM_CACHE_ROOT = "/tmp/wjh/.cache/vllm" +TP = "1" +MNS = "32" +RATES = "4 8 16 32 64" +SERVER_PORT = "8722" +OUTPUT_ROOT = "/home/admin/cpfs/wjh/aituner/gpu-fleet-phase-factorial-v0/artifacts/real-tp1-mns32-v1" +VENV_ROOT = "/tmp/wjh/venvs/vllm-0.20.0-cu129-profiler-v1" +MODEL_ROOT = "/home/admin/cpfs/wjh/models/Qwen/Qwen3-30B-A3B" + +[[jobs]] +name = "qwen30-prefill-real-tp1-mns64-20260717-v1" +gpus = 1 +gpu_model = "H20" +hosts = ["dash0"] +command = "cd /home/admin/cpfs/wjh/aituner/aituner-qwen30-vllm020-profile-v1/runs/frontier-phase-factorial-v0 && timeout --signal=TERM --kill-after=30s 7200 bash run_qwen30_prefill_real_config.sh" +artifacts = ["artifacts/real-tp1-mns64-v1"] + +[jobs.env] +HOME = "/tmp/wjh" +XDG_CACHE_HOME = "/tmp/wjh/.cache" +VLLM_CACHE_ROOT = "/tmp/wjh/.cache/vllm" +TP = "1" +MNS = "64" +RATES = "4 8 16 32 64" +SERVER_PORT = "8723" +OUTPUT_ROOT = "/home/admin/cpfs/wjh/aituner/gpu-fleet-phase-factorial-v0/artifacts/real-tp1-mns64-v1" +VENV_ROOT = "/tmp/wjh/venvs/vllm-0.20.0-cu129-profiler-v1" +MODEL_ROOT = "/home/admin/cpfs/wjh/models/Qwen/Qwen3-30B-A3B" + +[[jobs]] +name = "qwen30-prefill-real-tp2-mns8-20260717-v1" +gpus = 2 +gpu_model = "H20" +hosts = ["dash0"] +command = "cd /home/admin/cpfs/wjh/aituner/aituner-qwen30-vllm020-profile-v1/runs/frontier-phase-factorial-v0 && timeout --signal=TERM --kill-after=30s 7200 bash run_qwen30_prefill_real_config.sh" +artifacts = ["artifacts/real-tp2-mns8-v1"] + +[jobs.env] +HOME = "/tmp/wjh" +XDG_CACHE_HOME = "/tmp/wjh/.cache" +VLLM_CACHE_ROOT = "/tmp/wjh/.cache/vllm" +TP = "2" +MNS = "8" +RATES = "4 8 16 32 64" +SERVER_PORT = "8724" +OUTPUT_ROOT = "/home/admin/cpfs/wjh/aituner/gpu-fleet-phase-factorial-v0/artifacts/real-tp2-mns8-v1" +VENV_ROOT = "/tmp/wjh/venvs/vllm-0.20.0-cu129-profiler-v1" +MODEL_ROOT = "/home/admin/cpfs/wjh/models/Qwen/Qwen3-30B-A3B" + +[[jobs]] +name = "qwen30-prefill-real-tp2-mns16-20260717-v1" +gpus = 2 +gpu_model = "H20" +hosts = ["dash0"] +command = "cd /home/admin/cpfs/wjh/aituner/aituner-qwen30-vllm020-profile-v1/runs/frontier-phase-factorial-v0 && timeout --signal=TERM --kill-after=30s 7200 bash run_qwen30_prefill_real_config.sh" +artifacts = ["artifacts/real-tp2-mns16-v1"] + +[jobs.env] +HOME = "/tmp/wjh" +XDG_CACHE_HOME = "/tmp/wjh/.cache" +VLLM_CACHE_ROOT = "/tmp/wjh/.cache/vllm" +TP = "2" +MNS = "16" +RATES = "4 8 16 32 64" +SERVER_PORT = "8725" +OUTPUT_ROOT = "/home/admin/cpfs/wjh/aituner/gpu-fleet-phase-factorial-v0/artifacts/real-tp2-mns16-v1" +VENV_ROOT = "/tmp/wjh/venvs/vllm-0.20.0-cu129-profiler-v1" +MODEL_ROOT = "/home/admin/cpfs/wjh/models/Qwen/Qwen3-30B-A3B" + +[[jobs]] +name = "qwen30-prefill-real-tp2-mns32-20260717-v1" +gpus = 2 +gpu_model = "H20" +hosts = ["dash0"] +command = "cd /home/admin/cpfs/wjh/aituner/aituner-qwen30-vllm020-profile-v1/runs/frontier-phase-factorial-v0 && timeout --signal=TERM --kill-after=30s 7200 bash run_qwen30_prefill_real_config.sh" +artifacts = ["artifacts/real-tp2-mns32-v1"] + +[jobs.env] +HOME = "/tmp/wjh" +XDG_CACHE_HOME = "/tmp/wjh/.cache" +VLLM_CACHE_ROOT = "/tmp/wjh/.cache/vllm" +TP = "2" +MNS = "32" +RATES = "4 8 16 32 64" +SERVER_PORT = "8726" +OUTPUT_ROOT = "/home/admin/cpfs/wjh/aituner/gpu-fleet-phase-factorial-v0/artifacts/real-tp2-mns32-v1" +VENV_ROOT = "/tmp/wjh/venvs/vllm-0.20.0-cu129-profiler-v1" +MODEL_ROOT = "/home/admin/cpfs/wjh/models/Qwen/Qwen3-30B-A3B" + +[[jobs]] +name = "qwen30-prefill-real-tp2-mns64-20260717-v1" +gpus = 2 +gpu_model = "H20" +hosts = ["dash0"] +command = "cd /home/admin/cpfs/wjh/aituner/aituner-qwen30-vllm020-profile-v1/runs/frontier-phase-factorial-v0 && timeout --signal=TERM --kill-after=30s 7200 bash run_qwen30_prefill_real_config.sh" +artifacts = ["artifacts/real-tp2-mns64-v1"] + +[jobs.env] +HOME = "/tmp/wjh" +XDG_CACHE_HOME = "/tmp/wjh/.cache" +VLLM_CACHE_ROOT = "/tmp/wjh/.cache/vllm" +TP = "2" +MNS = "64" +RATES = "4 8 16 32 64" +SERVER_PORT = "8727" +OUTPUT_ROOT = "/home/admin/cpfs/wjh/aituner/gpu-fleet-phase-factorial-v0/artifacts/real-tp2-mns64-v1" +VENV_ROOT = "/tmp/wjh/venvs/vllm-0.20.0-cu129-profiler-v1" +MODEL_ROOT = "/home/admin/cpfs/wjh/models/Qwen/Qwen3-30B-A3B" + +[[jobs]] +name = "qwen30-prefill-real-tp4-mns8-20260717-v1" +gpus = 4 +gpu_model = "H20" +hosts = ["dash0"] +command = "cd /home/admin/cpfs/wjh/aituner/aituner-qwen30-vllm020-profile-v1/runs/frontier-phase-factorial-v0 && timeout --signal=TERM --kill-after=30s 7200 bash run_qwen30_prefill_real_config.sh" +artifacts = ["artifacts/real-tp4-mns8-v1"] + +[jobs.env] +HOME = "/tmp/wjh" +XDG_CACHE_HOME = "/tmp/wjh/.cache" +VLLM_CACHE_ROOT = "/tmp/wjh/.cache/vllm" +TP = "4" +MNS = "8" +RATES = "4 8 16 32 64" +SERVER_PORT = "8728" +OUTPUT_ROOT = "/home/admin/cpfs/wjh/aituner/gpu-fleet-phase-factorial-v0/artifacts/real-tp4-mns8-v1" +VENV_ROOT = "/tmp/wjh/venvs/vllm-0.20.0-cu129-profiler-v1" +MODEL_ROOT = "/home/admin/cpfs/wjh/models/Qwen/Qwen3-30B-A3B" + +[[jobs]] +name = "qwen30-prefill-real-tp4-mns16-20260717-v1" +gpus = 4 +gpu_model = "H20" +hosts = ["dash0"] +command = "cd /home/admin/cpfs/wjh/aituner/aituner-qwen30-vllm020-profile-v1/runs/frontier-phase-factorial-v0 && timeout --signal=TERM --kill-after=30s 7200 bash run_qwen30_prefill_real_config.sh" +artifacts = ["artifacts/real-tp4-mns16-v1"] + +[jobs.env] +HOME = "/tmp/wjh" +XDG_CACHE_HOME = "/tmp/wjh/.cache" +VLLM_CACHE_ROOT = "/tmp/wjh/.cache/vllm" +TP = "4" +MNS = "16" +RATES = "4 8 16 32 64" +SERVER_PORT = "8729" +OUTPUT_ROOT = "/home/admin/cpfs/wjh/aituner/gpu-fleet-phase-factorial-v0/artifacts/real-tp4-mns16-v1" +VENV_ROOT = "/tmp/wjh/venvs/vllm-0.20.0-cu129-profiler-v1" +MODEL_ROOT = "/home/admin/cpfs/wjh/models/Qwen/Qwen3-30B-A3B" + +[[jobs]] +name = "qwen30-prefill-real-tp4-mns32-20260717-v1" +gpus = 4 +gpu_model = "H20" +hosts = ["dash0"] +command = "cd /home/admin/cpfs/wjh/aituner/aituner-qwen30-vllm020-profile-v1/runs/frontier-phase-factorial-v0 && timeout --signal=TERM --kill-after=30s 7200 bash run_qwen30_prefill_real_config.sh" +artifacts = ["artifacts/real-tp4-mns32-v1"] + +[jobs.env] +HOME = "/tmp/wjh" +XDG_CACHE_HOME = "/tmp/wjh/.cache" +VLLM_CACHE_ROOT = "/tmp/wjh/.cache/vllm" +TP = "4" +MNS = "32" +RATES = "4 8 16 32 64" +SERVER_PORT = "8730" +OUTPUT_ROOT = "/home/admin/cpfs/wjh/aituner/gpu-fleet-phase-factorial-v0/artifacts/real-tp4-mns32-v1" +VENV_ROOT = "/tmp/wjh/venvs/vllm-0.20.0-cu129-profiler-v1" +MODEL_ROOT = "/home/admin/cpfs/wjh/models/Qwen/Qwen3-30B-A3B" + +[[jobs]] +name = "qwen30-prefill-real-tp4-mns64-20260717-v1" +gpus = 4 +gpu_model = "H20" +hosts = ["dash0"] +command = "cd /home/admin/cpfs/wjh/aituner/aituner-qwen30-vllm020-profile-v1/runs/frontier-phase-factorial-v0 && timeout --signal=TERM --kill-after=30s 7200 bash run_qwen30_prefill_real_config.sh" +artifacts = ["artifacts/real-tp4-mns64-v1"] + +[jobs.env] +HOME = "/tmp/wjh" +XDG_CACHE_HOME = "/tmp/wjh/.cache" +VLLM_CACHE_ROOT = "/tmp/wjh/.cache/vllm" +TP = "4" +MNS = "64" +RATES = "4 8 16 32 64" +SERVER_PORT = "8731" +OUTPUT_ROOT = "/home/admin/cpfs/wjh/aituner/gpu-fleet-phase-factorial-v0/artifacts/real-tp4-mns64-v1" +VENV_ROOT = "/tmp/wjh/venvs/vllm-0.20.0-cu129-profiler-v1" +MODEL_ROOT = "/home/admin/cpfs/wjh/models/Qwen/Qwen3-30B-A3B" diff --git a/runs/frontier-phase-factorial-v0/jobs_smoke.toml b/runs/frontier-phase-factorial-v0/jobs_smoke.toml new file mode 100644 index 0000000..5741e2d --- /dev/null +++ b/runs/frontier-phase-factorial-v0/jobs_smoke.toml @@ -0,0 +1,21 @@ +version = 1 + +[[jobs]] +name = "qwen30-prefill-real-smoke-tp1-mns8-20260717-v1" +gpus = 1 +gpu_model = "H20" +hosts = ["dash0"] +command = "cd /home/admin/cpfs/wjh/aituner/aituner-qwen30-vllm020-profile-v1/runs/frontier-phase-factorial-v0 && timeout --signal=TERM --kill-after=30s 1800 bash run_qwen30_prefill_real_config.sh" +artifacts = ["artifacts/real-smoke-tp1-mns8-v1"] + +[jobs.env] +HOME = "/tmp/wjh" +XDG_CACHE_HOME = "/tmp/wjh/.cache" +VLLM_CACHE_ROOT = "/tmp/wjh/.cache/vllm" +TP = "1" +MNS = "8" +RATES = "4" +SERVER_PORT = "8718" +OUTPUT_ROOT = "/home/admin/cpfs/wjh/aituner/gpu-fleet-phase-factorial-v0/artifacts/real-smoke-tp1-mns8-v1" +VENV_ROOT = "/tmp/wjh/venvs/vllm-0.20.0-cu129-profiler-v1" +MODEL_ROOT = "/home/admin/cpfs/wjh/models/Qwen/Qwen3-30B-A3B" diff --git a/runs/frontier-phase-factorial-v0/mock-phase-factorial.png b/runs/frontier-phase-factorial-v0/mock-phase-factorial.png new file mode 100644 index 0000000000000000000000000000000000000000..6c3c2bd88f563c1b9d2cc11c85993338748a9b6a GIT binary patch literal 118977 zcmZU*1z1&Ew?Dj5L}?Td1!<+EK}s4#N(oWv4yC(G8c78SX+a4|0cns1LAnGaHqs?X z=Qq~5_kGX(KOfIY-0Z#9Tyu=^t4XNxR-Wv@BE>X zx&i+Za(tlf_{7H4(Z#^t1ogyL-a ztB?LPA+JeF_Nj39rarXkmb4|9e+Ug_uu;6n6`ICnZrC{G!T)uP)T2oK#WQhtj=!fR z+{KnAH}m#4t7`)idCXFOr6f}J@GRa+q`7h-%2Zr>cn)1e7e1M z_V|HXbpojqN?L9E5+w zm6eqwPL;-4l=q!yR)#;Ld^=+poBR7qWansE*5UHZHYTe=A|rz`GwB(WQniO>6O55- zk@B*85zS#KAt7Pd>;pGFhLVsDd_CQ{^XV@BzN)mb5G-7WF&7ut4SwreU#)-Tm9UG5 zP#PE*T*1X{-kNLYvY8U@Fy?M)ZEb98`*h{Q$J+4{qsz|Uv3^8ENwY^Q)AeQb4$$o*0-c2&V!Ab&6epzPMQx*Et|&fgYSYonCZAF;o%v- zEi2=7gf&_0Nx6thNJ^Spub2)hD&qJa&p{|b^ylQzLA%QSQZONP+hDd5?Uf6d5=`ih z+;oz71I-YertUfJkZF_U#3&rhnXCceHMPD)4!3B)D)@VrPjM=keS zety2biAmu1cX#JfT&5b)tzlOwC@j=;DEKVJ;f`k)7O+05Vk;>rg;R-WvN=ojq>6;e z#V}+%E6}ucbPV|T@s6;tFsJJu{k2~;&o{rqCi7QhfWL@?xvPB6MMV#0E~*u1l8%gw z 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�>i&(9JUV*8#Q4t5<0aV{)-W6{CTPTitV(Xye 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
z zQ;MgQ;`LZs8bLjfK>~C%Ww25U$pVA90m-2Rb=KlgCELT+SK)Uc3Jd;AuydUS`+^^| zu2;Yz0?#dsgNP0uq9BpF5$1LA>YWx4Xk{`AX2xp2-=>G1m zvG8NCPa$0k(kp^*MKa{_t>CCA7BGQ8*kLdDxf*tUUqmu{!@K~Y>Vk@41`jew=@B&S z?c29R0)#~%e|a29JBc_Ru3yKC#k>Y!5kL7F6PLTH(g~-BTnna*mJI{gs4X9Bsstykj=(Mu6Re7``Sw zUzgz*)5G*Y_Pf4ztrq59t4{GekT*^%AU`9|Ow~AfkF#s=v8&|+3ZvWWc$W?IB%>M1D%J`-Q#x&*UHg zbbzY1fOiT6Xld!`<-#A?-)&#tEr$M6T+J@rn2<+kSL0Ird$Vwmt<1#K` z?|iRhqSalLej-yk8M-mOL}~(c#G3<;2RE$W0>t_O;Im+11Cw3Yt@}q7K&s4It!z4h zIDS7ZT!v(|l~Nb)8TkFQwAo>ZM+XLd&8Xctr z4h-50L|x(ofhW*`K;V{0zVcNJ1&Qtj)723#GjZ;*TrF{Can*~uq*s42NsDu;&Wh`v zI3BXF37N;2VO>I?sl%?QQ@)1&Mp5?3D#SK^#*PB=y zq|n1KYziL;e~%m!OK4aa#2!)uJa`%pkYDI2Ln0#hY;YZH#Yr?KleL4 zFiXRzU)KiT*nUb_@9~dgWWLP#rHkIyuF(U#eAcV+)!t}f577JpV-{=> zrtZ|bw%+HNH@&__I!3wBKu-ED#t5Mk!PHM~}dNX6v;10(=fs z0@kzuxAjcLF}iim^SG`A`OE8AGzfp|M1M^cqxoJeJvsVb-u_X}p7E};C*jww5WVq1 zSJa*#?)=C$#(ivI;57&F1Kjm@?U^l^0o0?Dz53fJL8Kd*#FZw<}M z5F5*gMMkUMglMi^&P=J4RMzUS+nogOOe@Awx5H05inS5dq^N9TKCcyu=ro7Uhf$z1 z!nQ{bkm7_vFAq0A#?KWX?wHK9VA=NrIW)Xh6!&g3w3OK`{bYY*iQ9ynrG^N%97RJ< zQEa2Pg1Sq1v##Z&!Glk&^I7e+j^x>+7GvzIB);#)bJd}oRx=9Ui1=R4IFfP}k8@O2 z7ki2fbH!(v3t93BQTwA#ZJ$&M5s=7%FYYRUc8@_|yoSR;{I4&F6;}ZnD+dEsT_h-w z462DA8~oumJN=l0Ii%^|=Ic`9OUxpSH8&T2}&iV;jfXDvH}5 z7Iwsv`vfpqOv&CL|9hI3;OErGm8kXM?`kM_#|PI==U5vYSjAJ1h`u;|#5CaAa-gtU zrvFQMgysdu`0tj@B)>k?oj)s*Aagsra^(4{kY(hUTo24r1VUDeDTmkN5g7MXbB;xvW<%ffwvLgQ(Yuz#A5P$ zd9~`+&~{_{usdR6&Umgqqmt@-%`1+fCy^I6Hm46WDyawFU3QK|CG`28Ur<=@T1PWw z8>=xqyCBwcvG%+U&oB7zR70zD9BdP;-weQ#ts;t@{KwH4X3rXfCOMqjpuhEN5QsQ>z5@z;OJ!=RIp;)pnMQ!y{3Ty zfzC`#rIHq=?6qG7=RQBy<}Osd^E|vKh%d+b<(pPa)@`>>7G^{JcY6$R&X;17mQ{2e zb}q0K(dw8Exi=^%S$bBawBHh9hsBa5%H7yAtw0%gD zVDU{^l3^GA$RJb8Im@(N+rj>ya;x(bwoi6EG}GimKjG~1!qaJFrI?A?bvZ?jmwpD1 z>0iC8z#P!MrmMenHEw}!N# ze7waEpV}{P;R!i_MWdb@-yb6aX+Rnwb>45(8UixD?D`i%bsjj{1=F-!!Uvc@5l+=s zP)1G;4PQe~S+LCan1lppE9cP6%w0Vt)~be4ugDyVWyaP?@ek+pUM}3JbJX>!6RjZ9 zD7bQMv2);q2Ci|1baiHL${LD#7z^bhBjeu-P-wt^93?8ldUk9wQ}0Nxoy#mCA(sTL zdf#^PAmSz?quWZxYtm`r+pDMG=Ojx$3C^A3Xqo|X(6zfN?q?pL9>eta0N>%BJ9$6A5PSxX^I2Cto`uwZy->(6g- zmV&Ey**)>nmNj*s*KiPN68ODKXoDb+_fv%{BqcLd`-^D=GyXd^E01c%ZZhtEC8i;e z#sYFueCSYV?uH^q9ttFd)6h^>Q4z_y2UW-9{Cr*`Xp!2e10^3l`dS%(77v6At{xV0 zDPOL$-t~u1&1h46yV)G?)}KbT6}?ijq+^xB>mtnn(# zIDeRG|2Un;?ai|bY4zhx0;K+(Mqxk7BJ_SKtq#=DLnRWEb55JhCtem9qu9n7bbPb* ztgirAV?D}JfJlo&)q1+5C1RE{>*L2Yz`gJ$z*`A9riGysZ^3)lh%diMLA4(zA~$~etA8U)I#CoF{wX*C65V7$LS4z&`UZ9WMv?DNa3%0e!&6e$LP{+dDOczYh8AO|A%_x3Jdx`fvR4!o1-GvI5g zAvv-bt8Xhvi=25xBmg)Jv2UV|dkB09VMkV7gO>d8vmQe?E-NE*T}Y_S0Ahrmx+Q>jGQhpw%5%_ z<%}8i@_>CtBZ}V2!aM-XD-!%uD~}k*eLH72}{t-Zh2FzhNMMGpGcoZ#mmz+fq-?vS}u1Vx9w&RRBNOjYAJRHc2*~cFT zb{?#x`#9IRn|{`IF_FVBnVOE54?1%yj&y6L@5bc4LUIEuE=(i84?+Hsl}`Q;K7lu} zIDWqg9COB7=y`*kib6|4drV<}y{wz~wQnyp6~cmN%IqWdvaMM~VQXoR*fuRdN{qK| z-I9~!;gR1Ws#F6lfGf<3V34~4&X*g^%z=Q&0~(KPW&m4;9`uVyl11?c2)7&|59({p zXWQcTz{&Qxb90i8tqi>OpouRROm4?os8kEGF)YB+zp-YUAj6Trrc+AQ$K?@qKMKVk zYN&pXT6u4M7_{2QHd~pJ{`S~%^^S=d6B!w+`Y8B#vTF9 zKr|ssYGK~R_K&Vhd$G|Ka$O>e9&r|8O@BNmRkHibxX(S{je$HJg@XTjGX={P47EaH z^J58y*;rkY*g@4#8BaTL<6{x4xPBE)=}7&i8kGK$=Io}h_T-V-#Jr&u6RZ?Ghp^a!mZlU(!3%C^{EmRY@zZxAb&wq2>HOh~{`-y8Mvw~{u01Rg?HM}~3Q@A-;bX{{ZN9JX4;G?|!F1j{reE?p&nuKYLL1&9_2 zxae7xq6d%Jh@c*!3dUSASY}t4ghtIY7{0x~T)g=*7-jl-<;o=tNhoW|!=npQoI4zEyt@FW2);gp*zQhXjT3u(@RNSy7zXb>}gDS(S2@QR%G_oN?4|9H4QOjgw@tncszNZUx3kmvPe6!E&rI5b9%G&i?nNGCaRv>>XU7!%o%Zo(e z@PFc6GXY`}s+;1gXxZSMP-FdoblHAJQ;w)_UdEqn% z2jBV~Lnq3^_N7oAZ}rEh(+{A-Pq7ZO5kZbn*)bzO4K+3OptqL(`>O%HJXR?7VG8q9dB$q&g^QW z$!ATp+t#>l!>Fz9!rtewCs5pyFLrHWA^tGF!oY93ldD!BZFEwneDK|z>9?%LlQz12 z&$~wm#&1Uv`acVpb-SfJl@19~rI6qeHF&Y~BXmKAt$r~e66d`ddyp@XYddw$KIb8ercp*;XE=F;%f4_CiEFxqyQnZUEfy2i z5+nEP%^}skj9kV+M^WYw?(&<0w&Kkd^wSF%!o!r_yaw+-a~Id<^h`*Dh5qQ^Rs+3x z-*t=i&mor{8%L{Tk1UNKeeAT#zUtgi_=*}E@&=94jfqd^^kHT?Rks80K7Zfxl=u z(M9u)ndD!}T;(EK{DV(A`X~vy{8G~Ywe$&{#>n^OEoA$>(Anj%%#)vqwzfeeSVlY% z+WNsvy@O-$0^z`MiiADEwTck>_wJEPNJwaWw#2{&BpC(%aR7V74)dUW`7$ENs^t4EU0)r^YS+YcK@V3tT}oj+4G#FXS*>{i~? z$w0uVR1U?NU0`w!osE5U?cTm#UhaH3?xg8h7Ku z=3n?MiU}=ALUT=t%LvZt;Lg{;%OB}D0SdAXOIAeTr&ZCBuC5skRyqH; zP4|W{40{>$1|Ksj`A$DU zPry>!Ie?n7foU($s*ZuclavhD_|AdYt&r6>OO*qz5ZzXW;|YjFp5Ap+s5Qg+BwU-S zv3CXVY)D0HHm#*HS3{vSVRhHy@RzlSTy0hik`IbF?P#SHiQ2!%+|<@LWGme|n9h~Y zlBH!=yc_>Yzw>&*ZaMN|LWQ9L#%I)(S%LrC>5NMi z=)J^FJt*|vdxx(y3k^qkJjDyDXQ5s3i-ng27DHN+#|Z@2XdoV=Q37P6Ev?B1c()%Q z4<4DX;rEd6wCd3fj$6K8ulgqoR|vgpZh+5*%(uLh6((s+WNc}+lY|ZF*x%7$`loDa zGzBHXrTDX3{u@%;8nz$v*)BfmFF64S-53rXlSqApr=eG7^+|}dc>Jb#HY`M@8D7R| zc(!0i6kx#E-dz~ivT|}v^O~O}`okzxp{XZHGn^nFGCRbChew4nXD+tO(Cg>tyDdG6)2tW_89K*-zdKp}+&yA=E zbPt@v33IHVDfnf%y5jT07={(NDyL76WjO21kNHLs@}prLLL-Jc_5B|K<140yPdS`B zBj~iwIuIraTzv8~kg!n|dEnPuO8-a!-kD>zbm;vQuKef+oWsQafR7kZ5dbiLgO87| z52vK8>;}j}>7b?K-yib{58!quf00MRqIh=%OEZ2H*_q|XFfoOpM5pl1MqV2bK4grM zaI{g%fHL&_Lq(&R*Ib?&GQ^IPr1sp5mS5B*zy)TQktX=l4#ox%f4L>q^0s2xUyI zzQ?P5E#2t}u632d_0NhM8S8|-aUgyZ#0F3aJKiPA0u7Ho`NSZo5-pN{wX=DK;@T-p5MQJ2Zx7GgGvWH8_I#O3JqXF2PQrJ zcSySyklAUxdK5*NTrrz#HVPa7jTlXDV*r6$D7g`Lh?ywA4IxdG4o{(n$1k^P4=;ks zf`UONJQ@iE$T$P7!id#upH^xf;X`)}pZISK^Xf!VSXnMSH_4t!>c zSyYDVpJfYGakb?j{a|BXy3aii&qkc)vI6eYP^xAYmu(G^_`%wxD+9EcjWj^xT+2;w z!Ud|9#Ga|X%~9Znu2b2kuksOgO)mZgB?)6CYev0sild09Ziy zR?M7g`ldMqeUs}vPmG}wG875o1n_(z@#1gXB=#S8&KNW7eROzAv1{DSU6t|v=(qdK zq~~*B8VHM>;YR!F>I99C2^0@3ka#tmQMAG`1A2z$BKq1LF929&fPcf0qyW zuG8VWE`dc}a1CO8j$$!oza`v23_TB70f@YMxf6P5@^dtiE#JI;2g;|O)uxFz{GP-Q z1%q6PZXw=yW`_jG1|tm#TXDbT%mUM&I@@7n0SWp|e<^uvDDM3w^vZ#50$#x1a;G-< z@8h44PsRFSkv^P}4XOp4@KSK+0Epp^c0ia$2ylmNwJod>tf<4Fh|qdpzg{8QH)peB zCFyfG`dQ6?g^&;GQ{Qh#MuU$XaBfAj@1E9p!BfbRWz6O7e8$FWTLi}O)_L|>`)1Vf zD{O4nfp3LNqzKq_1I%c{`cg9IZip20Ya2wAoSfsGb%i5PD zjh#}6uEglxqPCHajR?JPBMHCN3i2hf2p#@?Gj)1yG>BHhUehU~7Nvt6%2I`*E-ug! zwDs{W$OVIt1KBkG9+}f5%T_FD#h9(z0g3peaXul+qS(YwhHT)iCl}jSRE~0eS@!0i z*Y^m#N(1s5>F?4C#2RIxzYxOF7&)>*22`dV>cHkk$`x)aii%?O(45*I805LzRs2jt z05QyrK#>F4F}gYe|4&9q`50O=n>%Uno4waYGaNS3>q2--S+h596teMp7_mLsv1n8# z^P4cxk*B-X-FW<*vOGBinj@b^k*1`<=$4$69!b_qw0T@UT(oLtIx9PuM|SIAC}xbA z10TL1n{&F8h)(Iw&l_zS^g}#~QfI8+>m&U$9;5<$qaAXdrUvR$f%3ytBnf#56ZX`e zt5Y}V)33;eqUTi*?6}_a;n<(SbDW3mm!c&~hpob^KM<5Z{`~%huN$x3&jy_1v5{C~ zW*ACPn?y$ORrTS-D(UPdhmB;Ak9w*s%T3#iqZ>CF?Vo-w zwj`nzyoBa*g)Da1XyTMT2Vqd5fLor2iVG=yHmq6jl5}LAbWVI1iuOFf>G!$YNpPA( zylnl}{~tRff1i~dzV>lvtCZwku|dP%t(K2Q?p`&1HjIjzX^RwDU(t&Vww~#3^$UdG zW}jQ|LX(js=p>Z9hGVo#UOctqocR?VbW5+>WYM1N!w;i~(G!s(%M-wXqODPVl@_Wn z#y^U`qso~8apT~ay8u1Eh!zJWYSftqGFPw>i)TXv>BEA_*>gBpaI0NQ*&b=9ez4NC zQ=(Iq^IpSrCzbWqUQoO4@DRU2%}2D)fuQnJm)z*zsf8JR{W8lZ)%KO@wkEY6Sd*aT zEANu6sbCJPON?30>Y0vM6i~+qy!YbMSyVZhGpEx5N9%S*;}vx?!`wj#T9>*lMjvA; z0wCc{0osNA**vwJ&;KBksFJOvkaGy!%++ae)*6kq-s<9i#^dl96ya8t(O-pQCR+Yd z051FN%|};X?K;}39a+jSYrj$#n>e7~PQjjbR}6UMbwZ7=QwHiIR|Na$yg2Hu)8^$a z`tMI1U=nJ_R0iVv_RSlCkqM;92=g^*t3}s83kznU>QQqWoBs zbM)xT*`&eK??<EMR`hq@R!92hmX^eYDBNDhp>_OkD4fZsTYcCV#6|ZxgFXp2B_iLAT?hY?vRqp=VW6yOm?Q zdLZB-zv6kOWKi!Qop0APXUKKhDVArMsB4iVF|ukv)DzIwTrU)Y2}sFXie5v1<3#;F z=zMx#Jj0nH!dmV_Xo}`_M(J3XnX9>xH@#lmDLXGJ%<>A`#*x|&uaw70&EcBLH`0q_ z7zP8~7=afhdje=Sm5Jg9Il- zbwIfPz9ngiKLfAfW=1Nj%#ofMS`XBU(4spGTcKuJ`mPEickZ+zVGj$;)%<{Eau*5! zgpYang_SP%RwrYOhjT`#@nV`xcT0*7MLg-2aqk3I%6}ZzRwZ(ei-E1~sT!y|4Pbt5 z@7E!D;Nz2JoCE=ee!3`hWlqHdS|n($uOk@2G(!SE5kfJ5vVod<7Cjs)eDdBi8Me~r zLUu)DpSYy-?*q|51`^ zjRxLcd*y)@pbBL73!SXa1h*K|MI!Ws?nRfir&%7cwZ@f~yQl{*a1w)mm$7?2uo;$z z*$HDgYsj5W-Zuk$xMozxBr$kQY#8f6JZjCbtvuI%m z*OObm%gqBXQ3uZU>QNXVnRM-8fPDQW~8>kDy3%oPUjm#8%2?e3}z2w zpWJY}A}__1iRs`oFKk6Xn<-1n3)_l=NoM75O_K2vBCG&451y_T`5UJ{v$J(C@1?lq z;VMk!@>)HpWY3JzirlAMAA;P4sAVD-S;e<;7ECl5aP=TI)2z-625R7-s1ZGQ2>R}-}*1?Lg3Z-+( zL4nH5GA?v5`5VGWT_n-1InK~z#xp_oR=J+1&c{dj)nOfv&--)-{XsP@`OiY1E|EN> zc*w173m@;njHlL}QE_!efYLOVeClbq-+tU(WPmsB7uXEuSqFCcnGWgw)0!vmvp~o; z8gP{BH}0Y<#zYEE0pEX4my;AOr{L*=!ZuC>R-n8x!kL8k0yGd9f3%s}jbHUuhiq!Q zuXnGgTA%i(@U?kHl8rnlPG-~!1oER@0C-qeN*3P1jq5}dklq5^X~Z7gguZFYkqvVm z4^J282>I2M!oY#%N?Kpw2C59NJWAt#QPbM3WOhcSZ`$%Q#s?#GmO|cx70XLzbDQ+U zf5v|+3anUW5anm^`Ft@)H8&ME4@*$?A}n6YT*Dx`u5eiZ6$lXXSZ@E0>)-6_WYLWd zoy4bMG|9%)YB9^Dhh0MIp}iu=rpm)y${pv_``q6avt$xkex-BjP)mcSdfON}g>Cc= zKy>c`D9u;gZXtH|b@=n>K$02O$+ybOHFv0+Y5nK2KX=f~^bE?5i^m5q-kUs8j+( z!4$-6^n&RV^5a{O)B*6dKkcan6EYA*zrl0~`fnv@0~hP$|KS7Oh;r;XFUqvWXWOb$ z4u|nI@sZe2h6hK8EVkv(AQ`P-=4#Fdf*$v7!-}~lI6}U~)AiZDLE>z%qvyDq%Ezg_ zbkOBUz@6VnX5qb;|EFLXXQP>GPM^1F-GL_xiHe$oq!6GT@t$SNQzL21FrtH^N}z0? z2d+FH3ru9+Mrs*rpGkB3*3TVtR|fe{3{RBf zUIsvu(hZ~cn7WfMYa=}`GV1Sw&|-iiAtabwI#9nvZ{fV<^7u zj&DqW8In2L$o%M$R6ls=t^{{Q7sJnMy2$lNhKbY~jAYPbY|7sGUnqXzZ#A8sH6l z#^0VO>;I)Q;Mn9-dOHEV(N{F`NG+lG@iF% z3eJ~{VE>Eb&=#)cgd4#=9gxh8l!_aDCXyd6G_x}dC?GWM3FB9x&c*$Ku-KFc-%=}B z(Xi4*-b(4NN3N{ zfzcM2L6BKhK~AJKGEtCQHPVM>&t(zQMkw$Ux&Sa2@L~8*kbiLS&8xGd0f%x=J6B9c zdOWBP@rNr7sX6=yRF_RhM(7YdS*$XAw^x8L3(vSJS<^S8;xXT_S5c7tpB(@ztT1n< zi+LnKN&u`y2!lLHB#7K))QJ+*I(QSH+6G5LAb7@NQc|A4)CWt9w=3PBe?;c*vK}s? zh9TQm0*#|lozqi&CjKxt>US;i6?t`s0`)eJK?Xdm(JkA#0d-xIOHp3+{?J%v=e*!t z>H$$^o;E)hr9V`pBnLrlq9f0Xnu=P^`J2SW?X_Ms3m%QZ?ROD277udyARj&qSW@FX zNh>M>Bt;&E2V?-;zIA~PsZwPuchMN1ghgZh7PG#ZMie0f@k>O23cMg=4C%*PU+VB} ze+%ID>Q^K?gYX3@6Tkctik8X<=M$?;N+dj9=mWqxY>QX|#FF)@2`h$sU$0oV(Wnxc zG~cvBz_@9O84WZZWHv@4E_raFAZv1Yc#1f5w^mo*g1;Ej05ZYbafvFoHugST7}1MW zNpCv-sFNk@bbV6ljRH~J09^?qBVL{zS1o7Sn7LAg3zRC#~A%d5c+wMpfsMtL$<9!^qB{m;2A*f&4cs2-1QC#i?2iMg=f5 z)cnk0cRcCpQtC~AySmOgcej2_=^y^dj>(eLf4$=dh`$sQ!;=Q0a`)!y^ku$O!i_zm zYulY0lL&-3*jg*A01a>Iiwl!|&H69CNnb-c01t(>?{no5S*BVNs3x?v;a0FpMc#9s zs2F&#ITHAYroSS;vbywYZTI@dd+jXY5yCWl(Qlvca5@$SB?gV2Jc-W??sYBVQ*Kue zEM|P$X3HI)3RHBMHaPXc^s)W4!F#j%s!U4wzP>`K95*)rzFi>ka$nU->}DEYwf>(I zshn4(s0`eXg6h3ofUhFDONgO_=op)sybFBD0pOXlr>;Aml4LFvGS}E%FIm%w3M8`3 zYXqhrFIa+*xAtMz4Nw^iY^0udTE?vR-TG*}3X%);>ismcQ4{|*k$#f{ZNAcBK1;BX z*$0A{=1-#LFV8NocDrJet2=LXubdPmNx=5K`cN+)J$lp&E_A>_S;6)L`6Z1--mrI; z-ffx;oUc!xJn6tTgu`QN{|^w4hF&+TQNZERXBB|#r%6j$^oPV|v8Q?rxL$gH28HjD zc!noW33c&^DuW_0>iSJV5_G@cQ4d$e#Cexvpgk(W^}7%Pa7WxhxCY$<0C!}V(}2$E z!Nw?9s(3jFz2>86R6gmb)3|L0;zqS{6Jqey>%%G_RK{n#K15dF!oqor7p5u_ZLV>G z4?5M?#Th6RT7P>Z_ar8VgTbGA^u}_W2UcXly!@^CzNULfa4uVT`W4KsD^8u~>Vr^3 zZjtihLiNW1sAuo3;Fb~P9*0i|01kkh6hB_xBZfx2&)zto8Me2$?Mw#Q3DP1)dn67S z)TGH7BAz?8Fc5)Xt_Q(1Wp1F>UJ{M66vAC*3+y~alo^6Xw==vaZLaXZrk)j-VeuNXzdGZIR`q8e7E-yGKJ9a_>4TPkYrXhvt&FTo zN(MZ*2DA1`!ckqJ*YBdC+#Zd&R}`9gD}M?OR=WxH7P6%LudpaUrl>R!u=vQ5Jup2x z4#%8p8XsqX`wnh%duBWQlOlUv4Bg5Gk}H=^*}j@gC~O-R9l)x9Bmkafk=oNHo3G;7 zmidl*xh$#Utln5~9(Vd(6y^4zv}2upI#%<}PoDm=LZ#ACbpm9KUI-$}37hQuTsGN$ z3#9=Q|M7e5**ZM2|*OD_d7AA0-3wruagbC#7`8<+GI zFg#1KN%0>bi%{=p=yRHdmh7Csgrq3 z>|0x`%Zg2x#I=OZO;C9&*&agSOWs@HMon#YoL$2veJ028k2gPnNAM9~ov>o+2Ed$h zlns5oy|5+r784WG$rT*XepJ5Zm|2Bf!2s>5i5H#Bg+9&(VqJ@7W9;XO&X9VW%9i83 zRXQ&L5JzWILcILKc_61U=rF0{AP00DH-O(_v|CgTT=^vl&TJB5giT}0YabNB4Kr^+(+;*-8C`fQ>DZ72c+vF#Pxa+j~@1 zDFkCiEL!uM{l_1M{7rbY4$A++u?RPm4Wj(_dE|LCY)9bJ5D>POmtX>``VOp)2VFDN zD>|2|MhT#~M&v1syD|X0f=z=H00I3kuN%cUfk}iV2owMfLBPBXe&muQN%w zO%#||Nw)7Uh%WN$tPH+6-ZFpf!c3U~bZwUZIJyr1jehUTyZ`N*mtO}Jybs&Rc->_= zdaK9rF|(8|pEIhu>KTenaopR7dGIv+sg8;3zFCfn{cEf1x78)_xA&dOJW_iZlQCOF z{t(>*@#fN?%Yy6!(c`^=#$46nu}W$JP?~!lA8sR&;ggMnO*tY@a9E+R|= zTzz2-+qztZ9>1(a`#zF~O@CkDILiDL1Vh)M^f!I9<+%ZMKYas8HUZgd|fKK-2 z&SBcFUYD@(>0M7XhdZYBrqFvwS9;*HyPYA5rN)~{`SR`7gzA@dH6PIXXCJtOkcF-( z9y2gfyO20j@BQ@L`eqqF0OQlu$6h|ZJ^gfmWan5S@-hk^hMtp4%n@lN^uWd^dbBfz z_&qDU+vr?YVSmj_@8;tifc(hMf&;?@kVTGLA5;591~@x|4pT3`WdOEqAS~^CC&Ov^ z1~wJT^A9QfY)WQI%gp<}%Y|p6{F7Sl^|}9y@rFmfXmSfar}mCfQ)&U1py9Rax{4~{ zHj42v(jF`q0B(GPz>Nq*42FAHnikeK&je5ghv}aERk##{Z8BwdOqKZ)Iu$ffy^6qNIMn&Fj9GlYu`3DOFMh z&Y+d7XSQ-oH8v@4t{~K~MWoPi@={qZ%d_;at0TNBxe7^6n1IT%>DTPreKp02=`Q>H zlMT!>NE4@oXSqY=?fJb0Js(_<4yS7=Y212nWJ@U^mV ziJs0P5S4s^_!5l-3ldz#11sn2B?uL5^STLxsWT0t^(U<; z{PgiF46`%#%`V5UmUNQ`(1DVus3x7N<#}!*e<6V7L1~&LqMbeGnEPKSsOyz@?njuA zMZfy98gK^D^eENrznS6;5|{%R5QoSe57aY?xzws`Z-6Q3MUj?}fG)gW_BS!h)1}2O z>&xaa4ZkU7;iWgn0C)cHO#@fRkO)%7nVSJ+>UgR?aYSa?0Wf<#yOEVNFVsjE`EHnH zAw<-z6^Kwq`+QJ9g8pF=OqQxVqITdntT9e29@ywj#k&;FY-s&q3$u>wd_rhaP1nnUi=h)f?mqtPdGNkJl0z=z`57@ zJjj8%8>k-4mq-2uuoO2>P(9JvGX%PEaKs-e^RHzVZBqAVho~b)3$HCVLHNXIO*{BVA>fNeA+2gTsc+sIzDF z&;ev`iY&y_-~>K!suU}X2UL^Brdv$YW_(*>vMZ~1q;Pt6~p;BX%%+^ zb=86-2bKtKmx}$paJ(>j&S%NxE@Mpb)16(eM%t=B&UKH>e8m3@oT;sUd2k3-B57om z#uZ8{>?^rr>on8!kW@?xHVa*0mT%wScCJkaH_A1BOBslSv%kZJ2TKEI#=xmL-}YEa z3P~A*7_c*laSF}{vVss?rbvPnsAD5hF3k-0Sl}C`u$Dl&J(HTbjhiUdxnvHfwQr>O z&|F87CrOY;9=rrPlgH7ucSwHMchq_+Gu`}qh5b2DSrLN00_C8xDBQU+_j}h;hKjHA z%&g=SC=SsG;{eEhE$HN`g{K9d36R5hB~vZFA(&IBU7Ijg?{S5K!g;9&qP3f0_Oz+` zavhf70n=xH1Phz`3p?Q!L6RWm=jV|Hp`vRoSD=4Eq8va*m-3;S53xqZKb6P& z73_HV0QnaKKgVHFFX9J;zx%7vM*BpFymQibJ4CPLKcDKfm*L~ScWowW$}cB$?1srMqUet7uj>7zl!ekj4G4b^n4K@ z*8oCO_J4(RJ$H9bgmgl4MIT}Q=U!l5dobn{2LVwzgvHyI z?XQoXvd$VGY5NQXULi`V330GvrO1JPPr{*^C_}5H6R7%9YwDxHQk9cuF)eN>uo?VF z=PzC~gjL%DqAt_z(a5?-ul=pm3yHBL^rEg-g6m@2BZlq@a73Xw#hZY9krMV)pukIJ zC#2n<4mGV&ZF3&u)u;Zaca+;7N4#G7@7pCm4Xhk5*~!qqdn$#bOk5TJd%61Agh;E$ zn@UI7o9(fsj{B}+G?7UHmB^m(r& zbP!`ez>WfJnDS7bwC{y;3T`T7(T&(j)DTR74q~zJ+JMDS_7UrN*oTVND-Af=!4)<@or z&b#s&4rdbOG`&KD)#m5MIaenYgGM63;($mRC9i?oU+K21$u>R&|D2ux7i7a^b-nv0 zvTO(*59WQ0{jbcRd#bt5ZuH@fa$DQlVqsYmJnRV>$5Drw)=bQgT%je9C4sV03MW?FCjMohKp5hMkB4q9}m5;JSnpn2a0&*q%~!Se}`wn>}|}y*1Yc zBLM&RkooXEcOMvHI$FJwxvTJ(@`Kc9psm6yZOdfbN%cEH8Cu4-&qm%sX)+Y}mJeDI zS?ffy9xf<%j2}e)9FJ_}aqQP2(c&6^qKM30N;R=LiFC}Yqw^{rxlV4&pS-3P9wM`v z(Y|w9TpT%QE6Y6_^r@tOf7u^@nhGurWl5D@>el66_F`(viVWIquT{-plSw8s{ z!a%ac9OG6PA3}vr$q_z$g~4)<`TYuHXRNZfSa7(?nEoXG_x`mRY6;F5V4G`y_OyIl z`&M^YH}(PlQ1eB%|7$pRsc?aPNjn#*ZO^+>9 z$XL&%armPej?>%bR+z*9DA?XAL97l3IhtOgVr7Q|{-89pv)=xR-zG~rYTWrE7{N}46a(m`-VWO#Qa%!^BAReG zxOtYkWY<980MZD!w7H!A{*doUD(y*h(Fnd!8U%ZRvXQ(%p4RqmBS-wJa_`EfeWti(bzGbUTE|-Sd z_q3#?=Jt%$g&M?Qu21gwk)TvWDfZ*g4LAgBV$}kn-x>h-@~~2ixPfDmz1ZuZ`z0(*YDc3(2*Zh{Zu<2kafy zhMMAe2(*yzK|;kq5_3`pJ|hLvT9`go3QRC3Ou<;mgG<1L->eG_j1PfB3;c39Vb<5` zfme6Fz|fvt(?55|;8Wa&DdvM!wM5-}GqD?d{?E%9cgD8~!G%$r3st-^F)pF`W;tW9 zBGxuP0*Kyi{yT{XE!=WvklvjLNwEZtJ#?xdIl4sgDj7) zG&Xh3usID2;hi$j`4@{I2M4kCPEIz1mviLU!IFR+7d?*z#k(Rca~lX zFr^Kn4&6(xA1}A=7`+Xo1-;W*el*u`G>1<*w{NPiV@c%wX*s%g^~fc@#~@)zVqMvfy#+?e*#!_wJ}zURi2> zjgS4K)mQ+^DX9x@$5orY5H`=)7RMI>04DiXToI?CRfaf)r{_*H`{&}5)<(=N=XC~e z{Vek0q$G=8ICQ_ALYQsUx|!HX(OM_EGyDB>a(5H}tgmi@1<%GjKvI$df{{TE?fN@} zB^MG1;Ke~Y)ey?22ZDdPfv?G&LI|jQDV8kmKe)Rvx7|rxH3YX2V!bkI&5$n9s{;8% z1-IyGVanNEsgg`eZ~=GsY5&!8#^nQ)@)gy{>x`)4nUA;0$yk3Y=sYnwsH3ySp7KTQ z^COUT*177Ls{{}f2ao|k?_E}?(FEn{3y38%dgL>Bfk}Htx~r>R^tq@0y#?AVs@B8$ ze`EVn(&^4NKYgBis0R*IW#>Y#f>M0sTi{J@L>QbHb7DN$<#m%0bb4_5gsqVPNz=Fe zz6e^u6>*f_538SUfS;b6O#6o_MbF0_(RvUdiHsnS-?T6kW^8mYEBNFyky2#A29lu`-^h?IbYbcaX?N=mmPprmv+ z(jg6-ly~Zw^o_boBk_DF2EcJ1ySPWYjxU^ z?qVXrNX1OPFlbYO#sFqmK``tn>IU0k!>6;Ve}9ZzgcIsvy3yl}^5a84P^?2TwG4iB z!Payel4}aLq8*?uD(AfU(^3K>fkEUpc0_mS?oq-0v;Y%RD`ozpc4xK9#FtU0ty|x@cl_txzBFCX{8II??`CZ$v3aegkg; zFSsMwWtbTPw|nS($I_Oatp%3mb@aeE_v8(mJScuxU|;tW+B@e0<4j-YqxIM?*PnsK z0`jq~P*w|l?}Aog-)#QT)UdH@aaDM!{FO=u6=gjX*l!d|6EN=;J;^sv_a1S=}Zy+P;+)I~-5_X5bhsHY(DSC(RoTw&SJgK+!rQFuU?N#+* zXu*!=MVzHD*VHR89Xk!$*3)soaei51LzL9%c*o?`Xt(BTT->EAYc7X8$NS z=~w&Iax>9JNcI0q=1EbIP!NCC@yilbBk;#UtHTMY|B2WTLn7ddfG7vnl~SvPWlD_l z7W%$9IXV9tc~5+RQN>E5_u=gpW9-wk%L(UcAdLzn*4u3rd2KE8(w_IX{VI*iqCXSg zgVwRRJh6#GF&zl?mOIdfp}OEPA?{V8W{^7va^K7A)>#%w_Uk$SBajrs`}LB-ZUMjo zK%LV!AI&&{DIKDXd>|VBHv%-t)F$NOMC%Q(U!R(+oT${iH~a$zSnxxPnN@k$A!2j7 zQ$xbfvg7>0Yl3g08ocY56aO-EmsB1U-<^v=n+tbI42^CR5uKMpn^wWv73zbjJThYy z1C{U7BpDhw`XTWSs&^Rk)lQlLA+a;E0WO^t-)nAqI2F1>=O?0v?E|saYFOT2XL1x~ zfn3+aVW%Jv&<*cJe{xG-c2gsh#!Ja;tVK$YMZ*xDwAhc*W8jmxVQ>CL>70*5{IA&= zrs0ED$NFFSNzp8#k@7qh%!n*d!YOD`Lj*u;2+sv*(dB~68?&AFAY&NLY>@~F$-s+cI}o@x4>`^{;W9+3kPKc$$>oDu2Et|%EEV=37GoI*7q8Lz}s z;YmT5JMpX7Afl%;n)4{zf=iaOPu$GT1G!h2^@R5kL&x!^TH1k9#>1OGlaU*jnPQ$E zIlR7d8R~-2rEP+mz;-HJ5Ao^3#0Wvqz#J(PRK5r@6)+o6BV9n!0wGK~|A+?f46=e3 zRC2fP!6!X7E7~PB#Zpz8>RuvP+3G^3(aL`%+}*3k+F954qK{qCv3Yz!6X&6^QUPm& zDD*FPS}!PrC$4BJaM8R%HU-ttIa9M^J-}OEYfam_kN#QGQ7VGDzLxkSop*4gJqD+W zm#=TxiOv_{j>9392spBS$B&q%asw-L`zAuKB9iUSBF~}UY}?&>67#I#PB=bZ^%@Dx z)xB<`eU$5aLnimpt3A4b4wGGz+92-Mw-X_tuYd{CK3@;13pl7jGf_^u^f$7e*Elz% z`w2JfUL`m4;jpT&500-x0Wk%+sovWeb%QIxEAro#&VaD78Ff9|SLRrZtl575sF=#W z!T>li9~_i~Z9bLs$oqkm91=?3nJS|8VJjZokBPv3@coh#JN1x@ zMD$QSn{_+dtG*??kfjk;^3pNk9ptbSJDN*WB0o?DzzLKVQG)iQNu7_O!wabi6>V)? zNj#)@h})9iZ0`vVVcVzCZF6Y$4d`I)ynX>Ech__Z7_YzAR7I7U+C@J8gEJ)Tni&rv zKMuyA22g*I<%w}L`255;+_OY57P^Kajyld8J3buR2Rg>Cu%g}Jt$Y04zXTUv9NTOE zcF!EO=3Ne@5M+BV2|A2TnaxHX&HM7F9qwVyY8NIA4`sozK~9XCTH5vZwkoY8o(C*- zUR|p*%!nsh5_^aH-BVtb=Un>8dY}>jC52;bD)Y5JhugIRj#Ql)w%;++BHMq7_8OXd zRb|)yW|iKKnu;YHUrKEViZ+B9!g;uJ?j!o=*WKVb5c z=|i{m&*TS6LBByIHC1eHiL!t>(+=l3U8`ts%M=15HFvOJ=Lmg)dCU2K3_JB2h$d}y z5wY=A<-j>KJya%*)Xj!&@M>DD>S;c-F0kk;of-pinn<)W_!{vTL0tz!$YEv2i%Crf z#SU;C3wUc-qVB)`sp|3l#2M6gXt8R{UzPOv(IHfne780zo+AzO|n=FK0+ zzq=uHVr#ECv9*KlY{F@gfRn-Y)&9pO`~!z;zHu4z!wECqcHi()0BAqNoCP{4i=WK?t=og#e(y7b% zAjAAl+5^y8n8mIx&ACY(ZoWvI#(%5)F?89`B_s4mh>XY(gni$kzb^e;q!j8f*t=R0 zS4!Z>8xIkouO6-ve{{)lf_iBthq}++*eB0KkGiJ=S4mBgK$;kS*>(Gjban~fcNrvX zr$j^V?v}2~U5v$QiK*?-tZX21$pmJAs?+(8Ut}a)GbgD#AsyIE!xc9r&IDZk?p5%5r>=X zaS)6Gfi{I8Ns%04z-PfQmm0{cDuU2uh9V7y&cA`6Ycj=zM*^$cJElGe)7AfjyR`3h z8XONd=?LCF^v?*zoW!o<<&}d+qY`Er*v~!2n&QMPVT}cVC<=>&TR$%z6p{KB1>ZBxUD=Nti#;5x-v-k9rwjc?4YHWm^-Ng>Jh@ zUCoWm8W2-^9i8(;dht@@QWG&NbdHHRd>CZb!4kn!`evnmvD-FLQf)~8I)fRn@4*=j zLXS@h+bSRDU`qc%AxkKf*tS@%UjPk|gzFbr;J+IW zVbO&`!H@22g<AY6?>!&69cw#>~I3zRP34XUw5VCH0-_^$RMd_8T8;mu620-hYR zM26*!6&N#K3WyAe8T$*iBIx!VwXoeNP@Kf0+n?pm9P1DW zfc)?nG06v)C{XukE?lomhnXj(j?i@1I zp;8A}NpQAMXnid4_O|W-76+gZjNV?`#?daoS6BNLySi@W|0P7z@jemDin>N&?BcWM zgTDOG76&TD%VK+&WliX6gXi~}bVMd3B|%cR3=Ec0|7cxGj3pTgigGG@dqW|F>-Fo` zW}KF%5+wU$@) z{G&;w5lqgzn@MxLI;VF|Mk~nF8Sf`HT4uHR?~rkwB(_a9qF~349BF4^ zU^rcPvznfp+yB!ie8>dND7~)>2s6O`T?TKPT~R~yD{S;%1V}2zVwu{Ap2HHs9gW!E z;G-UfxrN;5!aWkn@;1IgBgZ-Y{2NfNs3>xUBMUtt4iP#6{!mu7jh*8iFTPt%jo88A zVK$=Y-Hp*CcQ~5AK^sxlk^^G~h*a-FNLQHkqpX@(BA)3j+3a?f60 z{b8Idq)EbSB!NiNKS2j|sbKs93TO*f)#kuuSA;eiLjSko2C2;4YX?@WL}T4|6Z#mT z13LveL|{JT7jogdqDh{)Wwf)(v~Cw|uQ_^er9-GiE)zWdt~%ob&q{N4@fP4xkKirm z3CR|BO&H8d`O6QZ)0jDnX-pKZgz?+o>LPdor^~l2!Rl8dWsW!XJI+9#1p%PLD?X@8 z4xDaR1sJv#kd z^U!l@b5Ah7jSO6{$}9z!I(c@szYaMRPh&)g&X(yd->kc8jCT199bAkh=p=xIg92&M<025a45`Fm#D^+@b)wSJAGs&w~d9SOM=% z`dfJs51oGa0@uOQVgtZqs6nH-)J9TF3?zTAy3I3nvTRFNQ8PDi@7+slVG2adODP1* zHuJ?J4czFFiv=mh6haHV3j7RS#LxcGh8nL+gYZb#?~QmQCDM~;=npc4mkAJzfk`oT z-atK@S6}f8lf1f!qLV^voG zM|H1DJ(~drS^H+WnkfIUuzE1B_h)KifyknE*5VSxbV0UJ!ST^P?uDMv0b%eT%5gI# z+_gEFk3MjyaA>#xx38QD@odU@6+~serCx|IR5!Urd9+%dvY+Ec`~Wo7NEa*8R}HbZ z57?&JW9{W9CzLjdd%M82^@@hECg`dPv0=ZQU7MC@L~_n2RUv)o(& zgJq>sI`?|6eQJPTL2N?lHqA=Q^}g-TwxvVk?Xx7e&L^}Z#-7nSQHO{I?}sjBVoWr$#-EG{HwiV zZ1iK0YtfmytEBTT$vycW+4Xy zIY_FJvivqL#r>VGp^!UxvE2gVOPBCJ0nz0+Q)@9 z;5qSQUfwpW8X|aroXUmz%i*{8q=qF1G(4z5?rA8-2CcYY%r2T)AM8;H33Ao)vVyh1 zLumoUoR!)lD61yE`3ac`Y{67ecbW;0396X2@*I3-E{>tPgGDfpAAxziul$Eol#kco z&m6HZew56CuKIBuy4@}(H(YEjT){IaG-Oeya;q~uW*sRj_LlD2*u5bykPxv3Al<%@ zavNOW^9Rmv_l%FBe@-LN3zoOQ-n)w)VF&)Q43t$cvMpG$(bi@JIYI?DKDbH5K!^bH zQ$(f;qi@}99gw4t1cm(`ZOhk&EnVR&xeuLD3ep2ld-B<>-;qb3v@Z^Q28K;2@E#4% zqCWt?qzc`6pojiqSx3G$Ny>RG0Vs1~W^x{#wL7**rLZL3$eSgK(N8moD?+7!&NIS_ zgEFC)=6Eh812xl~LIxPgaY$A9L?b{7mvhK`G^YpM+fpCUPZ64ceHWP8p|PvXmnUc` z>V|nE>KQdW!Ue~=bm6jiL6BK={VazcQ()|-$+K{i-#OpSuI-1I2w8<2gSHU;u?Z^V zfT*?BIX8q)xM)*qCIa$y5-#HI|FseT|50v-O$=wP8a zayY?Y2h!YrMM4ZUoO0AMoqL&liZ;Jg9ZPb$G{h!)7;9e|k-qFh z2FeeX+u}7%0MD3G!_;Ne~*hspt124<^&K4dr&lvf{;0Y6Zr@JEz3#24!aqn^Sl2a zzmzE8!S%6w%`+EnoEIdZJ$3;j{1K0os;KNzHfW_{4Da0Lksjtxcq0Y~;up@POfFUuedo z+$jLcu5^0o^x4?6@3CXI*PL>RfZ?kO^6I2P|y!*OdVfZ)}>#E%u(J_gm!%REn7ENCO^#DImUd2KTN)HSTOcT3KO`R+3k80sv`+S9+D~G6k=|`)abw((_lGPIQTLUy%Ck zU<~Z3AYqa49^adaX~EGDY=1AXIg$j=E(=6OWj{PzwPT?s?uKS$bC++DLN-i8F(;&cRL)W_g{2=3@xY0uBG{7$IUJb}grFwFdy?33mcrdT8W=XYe> zhKWFphA)tOJf-FVVajEh(gwS##aTbtGw$S2P7z}3McAilOOuzKe&47HG=l~M$NEj>UZc~G|2ih7No7zZ~z&06s@<8QI9w?(Vf-TsnI;ngG z3&O}i5Bp86MGo`J)i`OKI!|{dv>M=2N8RDrrzl6S2Zn-H!p}*l834Ym8&Us5c2#R- zxHJ3z(|Zyt{?inkhylyUr-;I6N4;_B`P{}cE8tpCVEIvO4W0Mq7;&h!z#Z&--Uweb z^u6ntZSZmanoc5}xCy0bumNbI(?Ii|1kdQX;IZl>Y21wy<>Qq+Ek1UtvtbSyKia81*gY^Hr~oPi^8sMn ztZAtzeLW@<2%VGu>4mkbW-bx~ zy%3zoMp2D<$uLc=a{0!aF>+iEF3P*N{Pyckm?l4!+{{3v?tOpfc zq9vWI0HO@^OC(7JWuRQ2QN)>)SpE#fax9!?EE8eLtANB(rhXSe7P54u8+d*B$tqDF z-KwrrO&9{%`~9M)<21!BwyH%N7e8hMrm9O}v=2QuTV~K{L&{%Gb+sEFNtV@>B>>?f zG(gZA{4)%bXoZsrF*&9RU(!>{33@A}J=g{hO#Vh(Jt!D) z43+^IqQmVt7ONm#3|0uSIKo+Z3FOiZfdlJuz>Mr3n}EBAt$p{YW>Qw{Jv*Mo%8*YB zaZGCr40jqQKah-TqRFNd~6D;=i~{xy61nBcI}|b@?fSE^p!| zI`19A1+qw%(}n84X!sR&ck;Xh1P4DNl>TmymA3MQVey3pgP(J0=f+ebs3hZ+`tQ$+ zf^=r^E7b)-r_Ju?w(uu|%FDl7z!vay&_aW&17u1ysmiLl=+FF;=vlmN>;6br9B8#I zIZpPA9S-+0DJp9%K*=rP+bH2#OIW$g);ymtNMKzShhU<987$kzs2BL02{O8Jit-EL z>$Oo+*3b>0dP@vTN;M%~0P3=^86_%kyT#1OVaB9h2c^iVtO;PSPZWrZX5iQvY6i#= zggCNDDLSouG!mf)fKPlnp(ub->hju+fyqOn=l{YxJ$Ii9ff@nZSK$RW{)G&_jFHXQ z6vTca=Uc~k14d70TbaXp+#CdVGo9t{_OKxvM+s;#fVY)pFk+iOKm!!N$raMQQZT>o z%K6^JKWa42zf4Kl2n=x(4mBe9-1-r7Xg0vY|8Y%npO8m%i0Xd9T0FmoxB50EW<_=GeYy6CCKoPKS

?{_cM@IHAgIRoj(~T?AeR8ISxH)wjn1x6NAwh%2x8!Ugcb3OwW1Cz20hB=D-49v zBU60&6>VrY#LX|03;<5>(FcQ*r0@=WV3+`DbUo}xcSxhPRZL#q2>T%#37HS@)If?s z&qO-4;ogas^5&blK@>so>u#{ugw}sH!Q(~Bq$mfW3669=0{8y|$!CWUOL~7#V%`&p z9_3+f)4S0S4j2w>bfV%R82~tt-*_!1Bk1Uw2(zGKOV6)}9q3FD*C0wTB4m0|==194 zS|-x0Bl5Sv)!qECUOZ&TM>r!$Lb;g>pr8q7fG^(@a%MIpjL5jK2^_C@LqXa%s+$AL z^a1ZVA#q_i)_7tRd)t=wn@L7x_I6hbG7o}|6B~gWL;-n&9y$z!QasK_2lEDpdz)I| zSnpNu(#?Jc-n4Z9y6W-UBZ!9jV|bgndg-RiM-F|PCWLV#31Vh3+i^eaafX8CU;*<^_;khpLDZaZ48{gDg6TYx$r_o@0a#f#LY6Ow+LkAsnh9 zL!oe6WQDB`G)uPQs^Cb0b5R4|ur*A;0LcXkr25-dg|Wuc3UR;kh0B=tJ$RHgH1PqJ{-hwu(e~9aIY{j)r6-FR@JXyR)ESe>|H7v2f)Ue z(J@cDDNsy~!xJ3sey#z`cZ9XpBqE+LS`66DiZ=f2+iS3XVKNzVZy>~Xvk_^qCwLWH z!tPvhmGm4!vXj~!l80&m{VVs%GFdSKxx_&-RpREiKaV%Ua$F~iTUDr}7(EX_@CLpu z(3Uqhxgl%w@ASzTXC|AM&j?)+VlbXyM1&<^BSaLvpvLF~a3~5QHIaC>A5Y^?Lr1o; zunXHCu#jJkw`X5GZ22_m;P6ww)UicQin>cmqd;8Dtm*qV?r#D$kvEM>v0foas2=2p zyPlv|V6%=sZ0;itp1o6zRv3e+;p$fNmZKqq?ca&-+DnlIF$Kh)s*B^UEi=Oj4ktHE z+d`h|3J3`SR{$quIWP9X{W2MT5^xqy4i?Ub&O+e0KR^-;e(s7_eT9ZAOJm6L-KNg- z)*vb8NfQQ70s>5zB|Nl&;Uo#*qN0`kky$7ezG#`NcMC!;b8m$p4jZdgTBNCAhtv}A z#_jsQhPt>RW?}=TQG`*}BXWlSsfwHEnP|jE*t>A;PFK~;gxqS>K;Er#wiZZ+K^g(> zh~jK%4hve9h~f)ip-~jES6Mdb?sHeX8aCYo=E_c+gp5toxsyIdEU=8Zg|~ zO;0UkJFtWc|H5BEXe(LWl3bGtJ&}yoQ1+KS=*`p=MM18m>0%$Kt3doS2 zFGJXfM7&b*TY8XrB^i#JC2j&h0{_t9eC&Y(d8i~?Ce!im-~91JbAHwj2+>lrs2@sI zyjyJxLa*TZirANaY1P5uNZJ}J+y*TquoTB@lsolC7wdOkLxGnym{(ss;l5YVml^~t zrmdL>mkJfLkGkcv=ZJ z90D_T3z3BQI=MbLpqizhmE@TTw@QVC9H*EY{T@q~sij1K?KjI1p@6iIthEBnRy~A3(;WEpt4779WkIa`^wmb(E$G`8- zVY;UQQAjcpe>b;3!hOZd1{MDQVtw|Nv#3$Ni2=n{`rVW0pV_6{4oe6sJJ+T6%WHxF zaA|IIgt~Xf@!}Aw{Y<|Zj--FO!AE^9Z#CU+^FPKj+ zhE5EkIEMt@L6s^7Ku@uxLJ|WxG%hgKRyIkw}qHW`idtPCDgXG5R! z;J`kXL)hZGDd3|V{fI5_SmM@h$>}){oVAloKR}TYD{7<(w?ckjF%1{pLX$VZkSJc2 zeFs#Ygb0GLhPER}l_Z`#Day4FhkKVC`Jw<-9cTffK1$z6%m1nLzCrXRIS>GP7o*NL z(PCFmsxS3}2|XE0{x-)Wk&X;4;fx8QR>pEQlY-!sH!AJV4Jx`0|YTMzJ`ObwHqI%`Gd+ zx)1Zi20bVi5i#C|iF!j$j6X&fZ!^C?(qJ0lQ1)@c1-W}iQt97yTTDA4SDYw9k?1jq zh`s>zA(bsS@4*)(w6l5+L3BqCieG^O@y;aRMrx?TX|nq?U^8vC5C+Z?lJ9+R4`H(wjJ66 z{crEX^JYxVV9Nz0hQ+>LZb)zySpVSgwKAe`I}ikpo4bgd3eGV|kQDb@F=llDUziSb zUB_si7~cHf6q|s^T+l$nPd+g*soiM@0t+BWeABs=78ag-J&5`_mNC}^=CJ@4|4C%9E7FS*-_zOTg0FD5ELjXn=Kc7RO0StHHtX|*EsM|ox8~4MD3VU@sy~Z_y3y3@z zUd6W=+>qW@f8^8m7Vgv9`|sR8Dv?Ww_nG;jE?E(0J4&L+>AnMcat2hGA<2yYp8>7BG;D(N5bee9D5#b83D`_ z0uHZ?XNdX=F0hjic_aouSi1(mRxfNb9d}nva{f(b;cy|xyL^$09?sy|PlDLIz-fdM zHp-O;;}4ruEe=pYVXzGO6ga7Nd{7MqxwcOuuAPMY+5bs(&7Zvkfhb9JH3X1$V)E`T z%M8$OxUGy6RI^c|pJND+XGe4@%hBBg=B0%JS7rMatsquCT7RgnuCD4P!>$kXG;&Gy z!*lO|s2@aQhx?Hox$o{^igXc9GKc*$Vp9AWp2FwUgi*zLPcH^k#T$K=OQjxheP@2P zpVqQA%?ZLQVuJWqfX*^QlQ@=RrUiI)XoFn}nsK_?<=+!L+xt}xQ%W>AL-)J@ffUU z>4~ke40&8*=)GejfdjJmKAyEHFz>-*=Yk{tsP#5Ie|p{)THKgt??$n z&hed1);%hsIG7Q#W>itsTX^Evkwmuli!iGI4B*PFKX-|sr9OPb1mT^v{I9?lxO+DO zP>E48dNTLEdri24={0FP3F&&(<_wqHGELnZ^zLvSNSB0NO(&u9{aB{+58FfE>G<&` zz`hq#Fcw3`IIY z3lwNPvB#VG3Ezhq7?dPrJ#4YYM)RjOTQe$Yj*(`I_m_VX)ZlW%pH_EGz{Yfs581+G zg5vY!HUI#o(;Ov1EwjB{4OqNON`*EjU;oHk4^8d;$Sx2N6fT$T-?rPh9xgbjn+@m% zX$YVff#5KcJ8xtn;N5`9AU(uFccrO@LRjlxi@PiUiven_4wyp+-__CF*^9_KK6X30 zb~H`epa@54jCzhwaB&+Z9qEStQ}RW_V2sD zls34swNT_&*z4o%pf|4WldxM?!8T4h_Jqtp{IS%7zzcrRW zr=&f_v@zUizwF`s#DYT4A~Lh*w)xU~q3w-fHvf}6ej*ovspCw?_w!dDetLzJY3RJq@E6S2CLRmz&3s!ZlS*gRD7%N` zU4o#20R-Vqp=&;fVZOxKUgDCowFe`JoSA5UPgO>*44m`GrJ|NGy8PI-8}^SWNV z4k{ zMa-u3a-}4y)?L5fTZN`+$rjn6tQTVf8q(w_hi(GJlkJSmZEI49+gCV_9 z%TMqj;1Z2@qJ9(PQD*6w`)Pvl;Yl#-q!oJ#WaJFeE-yxJP8I;7!`N?oqj?X_bp3k$ z;;NN>Px%+PWfj1;47sfDhQ>QPJF_=;%FC=IBqT!U4D-=Lv~n98q1V%c|BQCdsMmGaiTmDNt0jw##fJ+r0O>&t?UOS7fhdg4o^(}opQW2Wy+ zLg=>7mckdcZX{fW%_E8-C%QK}(+rt{`+KSJdmUZksM1Vp7krI+vpP^?5lqU!th5H)t<89~a z1FrT)oPiOgkgU+|1cz{vF1du0KZpiLPUejN*<*)07=zr0+E}-6LHemU`<7~|rPx!J z%ej!gA#v(|5qqurF~r`7v#G}~Lf=neA-P&7O|e4Bboqi-z*x_0XG71RP&?0%hI6Q2sS&l1Xvgy?6}->eS>1P0QMc>NuM zkq*_@bzPIV@S_lruLSAPvm|`By8NVf;_DpVz{XD4XP)0RZbQye5^#_JSGohd@37;0 zFf;%Y6_yW^ z;{$&RUVG(&ZQ47lsqWQ4>GR`}^0hV3N}o7gW+bAL_!WuI{x4GtDvun#^4n2gmL^~@ zR+9Hx_(D<>>~4|&En3vJk9>tt%@7aj|2I3O7YCP&_>mti)3vEN_ing7PQIfIP|cpI zK+N~`A0?;IUYi$EDj;1Iu(Z{m#l_e!o9_U3X(_?Jh4~!B4jIrOxlwZGc5Lic9=1J% zzyjPd^(kT7@&VH*S9yMOAwhY?5wE#J%-qUW(_r*`6dz2esz=A!9wzjsd}RIz;0+FMf>yc!nl>wMt6oP`V}p|HjOA4)MVWSd_U8 zaNvYD+Yu)9-MS*~+{l=W_#rYI1pzJ0!ldTEpDsZXqXv2kc3nRO<^!tx`-ono#Bq~1 z5#(pzxvC1o{{{rB$`E*rS$9-8sPm-kuV9yl-Cv69CZ&gKXeb#dMZdx&9ULPs&HvWm z?rqN30UQ%?y>$OnD}6C%R@hf$$%f>Ar##QVc?HSu4p2VcSu29>XE5NJW7J9_A$2jL z2g^}#SNj$3QnLJS%uQ|ZBxtF?B-}-V1<=(Gs8ph#h$TElB1K>vnL1!SoMW6{DX%ui zy_ZMXw^$UANdHkrV!TYY$6RRG@q2yB5EXZcks_ucKcQ9F!;M0cQQXPY5fp=|{VC{z z!iJ5YSl{%d_P5+~OuzH)Z;d4ye|Kl+B3Mgdf$QrZ^Ha}4BA7O8oi=uMQ}y#dHR+Y^ z(1k*{OB@ozaP;sZGMu*Wa6m!&C9C*bfR2N0)YxOj3ip5I$@G0)MKhttFzM zXaEBFsq6BI_?nuUfEpNS?3{{#JAWDEbb}J02DH~7eSN0@lfJXJC->{M_&}af69QF% z)5dJWvYY!N8;Dw)lEcx3Vg9-*LKJ;c!tW8{(`ziCbj*HS${l?Idh0K#n$ScI zV9=dkZG!7y`0V!Nzw`Unryqf|KV zEAXZ_okmDV=uTvo*VF3zX6QhgB#3hpgr1(%JfU^+Z)gfvi#DZ#quBB!vq=ttu~n^* zT$Jkv%?*QickKs--)%#cq(K6mryTAr0%_pNp^5@jN!s0Cy*d$@HhT5a(QqpHNDSVU z+LLDYQRr2K)8w@H5%32{EUERdEi>}RXdtJuNz$5C4+8uGRbfaCoy}K`8FfX<|HAJT}8L9y&KDh+p@p~75PSD!cuG!^&4x3|w+yIyF)s;87`jv`WbLw(q{2Tp;=~r0aGhN>eSBtjjDF&2vJm z+(%E(GO2JFO`3_qsL{Cz19Ax&WIlmJ65qVrkTab8fExrH z@|-W4-T@4{f4i+}S5R44c$T4tnkOUJ@H7nMH9k^*ge@a~N=)h!T&PTuC}Y9;-XOxg zTi@jmvmky^7}Sx7`i+myeB{Z`=h=9$v3^8s-(G|F(+LDy>+4?u6(ki{I*@vo37tO> zeGjSM1_3xg{!Dw-tbv##tG%s*_!}6R5l1%M>ORWeD!=^GK4`6^GyhkJEIger`f67U z4%38XYe~6nh=|V@ZXzj!)Iotu6T*_)S-00LfIVaPh#+R+C6z4U5OF9Oa>~G*!CGJ# z0u+57=6?^%ciM+5@SsHcEtpb`nPx(%2MEt6>*%V14D0NZmUeLxQM7<&3Ia3+5U&+j^#{{TXn~xXi6qzOMZgA^CBw!jcU<2KG<_+txABoiu6?FaE&P7TMQ%tnCp=NsGdvU zUJ4xv!lce09;Xb*>(eB`?jFlzuS~A#)GH6e2q3jSsk2jI1rF1=88|iz3GU^v>a9WR zD!u=|5(0s?{6<|-wMkhM(QG>91t`>QN>djTKpKj8aRfqaVF*+N@Z@6nWiE?@omE5h zWrTMG1BsX0Rd7$cv&zFD-UII|!rx!hPQ{ELAonXuMaQxi1d3%kol3aC0}}b>T?tol zBobG~b{=f|u)%^~%ZFoim)S`xtsVS8LkP$cX~~ zbC>su<%h7#$|OTSL_M=JFKvSWn3a~8|4xt$FLj2YVyO$sWRDmDDtk}@!4*Xr8;Bw8 zp;dCoN%>RG?wE4Nc5Nb;XqC!eyd4V{1Yt{5@?f!6f3K*b{ zH0-x&=oEvu-yI0>&X%^To>P;mK`|)4ddE`ywP5d@g!xddx{_FLonqzi3muY$d){$z zoO7>F5tBY83RXW85OPt49(PtMFubsh?uu4mI7*+lVPf5>bggkB#(w!gm;TOn^K#63 zalEtr;kX}JeQcE>>S^8JF%1rChcmlBLBOe3nJoWvkD5>Zb>8xNomj3sy3Qr7J9b8| zxhysFOw#O(YiZNap<`L4sy?QI_R4;Hrd2K>n`jhq@p~ZXD6sR5UV(CU9MjdK@o^45redm3rxmPW1 zbo@{E*2!)O6QU}{eRZRI7MD$WLY{=2BiPEH2{*iC*@vsW3QcA zE>x@)H&81#L5^~nOAh{8r<<*OE$A`WGTR-uhmzn@_$(-nQi6QcVUlEEBfnNP*UUNs zz17!_D&_kT${AlziBgEJ=G39%47zxGMfLTlg7?Jr^y2AjnkW>3N|=XS*$peW=UVr-h1ACFQxqaZvZGpBv)3Wic=Gq($No z(xzS!^_a+5Md6f$_Zre#BQ35hUw zykvPE)=9j|V{(NJ0F1nWUS06Di+8OSUU0g$L(gFD_p-bMc^r?2=9@7>mWbVc&VHJu zV`r&-N3x;aVwsopSZ`k9i#EzyN3-}BYBb)vPEr{ASsqEI=kbTjT56#w-lLi-Mp=qht)V; zP5;(>G?S&Jqsu_PsI>gypADTiZ5Z)KW#F3+k3CG?cD^@}C&*awl`OsZ#nsC%_RhNy zvemO|NYZPKgx8(@UiG%^_oYj0wH9Aq3&iXW$4~5Ml}r8*?phdR#79+3lY7MPjsQ!L z%c@DKp+#79Yy%4*VN(HVVJSAb9U2~7rih?DXe3D+|36HDAW$EA0s|1 zt2BM;%R)K0pa%~bhc;*j4lOx3*8P6};S_6QJzH1WdH(sN390d%g@8#p!(T zyo%*=_f?A(2hYpB!wnYr*@#(ct;}M{&-t1;H=~`u$d#q^uLmN}E|-N#KEFI)yv{t; z7_IsUd%pXlpyjj3>uoqasD(Q*DJdGt({h)qv;&WpJtS}D81f>^+hzGqY+JISkxVQY z%%LL4h2kJhy;KEuxS;B>vWK^%9R zw4f`mFj+zRqnAmitDGfI-wryhSLRvs#R2aW>ORLT3E8){Wy<7YGLt_=juf1WkFsRu z2cGvD{th0pbd0rPzh(E(O5~TZO6DJnKwIf9SB1^t(23LC)urOm}0djz8z0okF>+%aFrT03EZyJc$Dw zM2#&i9KD>6L2o{X&d-3(*w1)T&z~kEWoz5p8He?*IKRiZH#(fR4YnFfjbW;Nt)*b? zf%F{JH2k!2r&To{AEH8ESD#IYx*GGjmP^9hm+SFqg(#Mb!|xMNs5tdsVbT#oUsD1H z#Ajm&^H}Q)%=@RuTOQ@izwuR%?vq^oY-4mpR(_;txD{p5Km0>EOjoVfO_>~pvT3+u zNRGgnTF;2c$oQJu&uT)$rf4EiG79%SGy~bV{dE*rzj}y_D?QcjETuE<%!*!GF8h38 zwRaF#u)AFAGVEk=f3ip(E3>3;p5eJ5(f=dxUY~~Y>4cdroxoExPLgNh^{kO7N3`T7;N#z$Qsri4526_iU0| zc#-AUHJu5vLZf5H?J2u=3$=LXQ*-ymQIu4kYK=k*DzfzE#rOUF;0?)d99jzPT@+w*wm!`!p-ghUH>tpgaE;%v=Xj%?ge-T>R2wyStJ;69W3Y?3Peab7&uSoh^+Uc{ z8uLZ;FJR)8n~WTuL)}+v5K>FE7amLuF76!6Q*FL>=&1c%nx?CA+WV+%XES53_h%_5 zTHq!@x3e)_+zP!e!)eshUJ3H9tMK8T^1I?ihleeDPR<_aEu{bvJ1sL4rX1f5q3v#3z;^v+(`A3g5z!-N?s#pTmn~Y~A@n^Z2iAcC4INwL#t&O6E&A zo9A$`M)J&_M=YtAi0_J9IZ4)*FC^}IuifeI zA6-YUxBZ6QuvJyXSJAqpT$w6xNJHSvZsg`fn3!5;%4g53DKv#WuaQsPmn|Z_Q)c)= zPa4i~q$asPE?yN!dqs0J62H2g&2Y=G=ehmFQf$xLK-#je#l|xu!_z(V*p%*7C3x9q zE*xx`ixlZ;;hy6vw|M$CX@u6Z#)gj+g?eh}y?8;ec!8{RbExvkcHvQnmEDB*65g80 zZGQSY)E>s_1&=kB&C+_gQ&o9O?-cTj`@%!w{^u7qFRD-94}IIKhxRQp@;kTI347M9 zWOI3B&wApG_e2-BKoWnVkW1o)-hpUi+lA`_hV(f<#QwrEjoqB|)haq3K@V{bU48s9 z?doRZhZqB@%kZ#%PJdUl->m8_FM8`|a_Oh630++MtC8e36zaa{gOlG`6)9$=&+{2f z6Kxv?SR0Tg@-uHMIb8PkIKBDcm*U;vg|fu^tweC{kSWLs(ikibKs+P!FQ zhbO6$8TZ8Gyv$z=s62`g+m`Y7o(-Y^##_S?iSD|fz^=T|r zT)@?nwM7#tCo?;R9m&cevsG-|a?^gW*8ALFUy6!vZ^h-5yd#lAWI^FE?ZLhwuK!nb zsIpM9)NA^ZU~N9wYT)DJh>80*{A8`>f;fUlx=eeQ@6IkY71zrVhHl(3<&j0UmY4jz zOzM$+@>egb*dgnSK7c#1xE4WswkL4=V z;>S`I8ca28N@T(g6Os$ydv9%~^m@w{PSr|T4ab<%4Sdn_&i4}Pgwcw#eOgbaukG~~ zFxGy+vAJE;2-UpB=Ah+(Jww~~Mc?@7n~q}7k#&XwEobs03G$n7`CvQY+81AEWf3DC z)$1|ueVK6Mahf1C#i$##70bTqu8!N2pwmf#OrHCej`JTH*u`_rVN2$I_6!Jc8WIH}qf^u7s$~&>_v4$>XK&a-~H}7RHxz zjkIYS8?l#2C}*boM;X)l%^x^B>A6Y1y>g1w(F~2UG!hBWf_)I{pI={ZGv;#9{2_<2 zkLBZz*g%!}AHqzm^|6+Xf4nF>zQitFV=+zaGyS!%ts0GM=yZ*!J^p~_!Hre9`^kD* zr{KPzW9TgS1B$?BN+Q3|Tdgfs8*?@4b6xDsS~tFHPZ(}Ajj+_s-8GJ|ca%L`W$>oP zLOs>+Kl$RRJv3)pObBOYb;=7$?dxEJA@7!!KWn)%_eG=P8y*j`kvEfGZ(e=Ne&rX5 z0iMNmWufbdZp8INhEPi8(+DZXp-0fEMxNP2dB|(EkV`h}?W59d%f9Q*cQ_looXgD7 z`+X4>^d{W7ENQV&sN2qr_;W;;a0uLT`_j(}56AoEQ@=ygLZK3xF5A(b-U4OD=&7EC zKt`A18kbv5*}u++D_-Y$3Du_Qmy=&u56{R-aCO(1W9Q2E4o73^iy6mDOE$c4Q96ttKOThoGmh^W{HE z+CH6|&b^)gx9WQnPVhBsyg{<9bY52?sLP&||FfUE;LHSZcy5-{E&J#8jpda-f0;9- zJ|fS-U|6o+X=u1T9}W-ktqIlT)V3w^>!DPuHuTw1M;xI3m+|o15wi-xm%CAaag8dj) zm&@G{Lyn%qTJ-PwVb9H-W~N)jIDl=n%izh+6*QIFP%tCOIBtq2pJS z5sgBjetQ1%xPJ-Y3+DeND=ph{L2sdalx1H=$IaX2Z<=aXP~USyJE>%{bVFO z68v>W;?JtyrBfuAu*T9Dd2w&SKV$;C$9kDSa#H9agMF>)aT|G0S~wu^{~EMKab9O- zF*|5_*UM1Huht_%cr^CjAesy+a~xbZyAK~k4g<9~dWL-R-fYGM8KWaVV9#wnF70AN zHl_GKztB$tPfyO_MeW8|O7IM}IsCY+E6l>0Ud8Up+u&NC7uf%|5DGQaQx0L8<}MoO zu6%sC-ISL18Mi0pvK>pjrFL+uA-M+>1&WgSg{`_Y?JP>`$Ag~A{U}tI`9EKJUFC+u zq2&?IqVTNK=eC-j04SBXazEj3nQE^nU$AfZ1&Se`DoeC%q8$y zlH5hM$CK}3SpLobtL!_&qDX^oM_qJR5qFIUih)51Du@Kh2#YcVMFf<*WF%)KV}eyc zaRf1dpyVtdS-=&MJOm{UhzW-vNs{F6H0tj6-23O={^6rI^mMeP9wPUQ^VI_1~8 zhW*U4u@dsqqY~&kkW;?IlvUk#_MYkU(yvzoj%-k@7>>wJIbu}bKwimPxApI95i8}S z*g?&Z*y6&1y}Pp=a@=;+I@0Y911p5--D6+#a`0ADw>76EMs3fJuX7a4SIa-p6{pIR z*p-)$A<{kLnW{YQ005QASLFiML0mVcaX~hPcID+m%4(OCP}aWPOke#r;cJ8~4(1I$ zI^{ml^MIx8n_k4zo2_$a=4zt%R+30U+>G(sy5(7^FnB9oCPLRO zNu2TZNKP1Ltn}~gt2tgUuWFd}o;$6&WceViVwnF@;zZDE`LiyJ8?1(EcfAq{My1)_+MShi&gERb^k!P-TkG4W>bmVHoktca7fdsS`-@0(|ct|3}NGq;_FV)!|1g2tIc5 z8sp@MXL37llz1OvLz6OuW;t2kn8NG+mysh0Sz<*`dK#OB+~Ha~dc^v*d<36lRoPZf zI4sv|+gDo(sUKN4(Kf?Q_%mCt{H^ajt(EL^ud$3dm~vNYkGfd=J_ch5Z`Z`k+d zzzTJjdHag1wfk1T(50B)NhS8Ny?B_mTCC|s@|KPrO7)WI!|BUUuM9%(AKfw;`TUZ6 zO5)n<-FY{aEFWnvy_SD;ao&CU*Wz?CkD9nd%ojm9?Lcy@`KsHzhvz7#9eZ!Dh`-56 zS2Pb|7-Miq1J~x|yoMqZtF-tS`1j2NTQk20#>Tn%JI}-jUb}dv`X^S)8xC53qJrYP zf+Lt%ewOW>6<4^QH|WKhU0bH1J3#lp&30($MJlHA>q_Aq4sGtGPVS4R0NGyj<%F@ITdd5gY_eQ|l-wL;y2_oZ2w?I&n(bL95I29D zAYzEnbsSdc-1w24RmA@9-_4{J#)fAfy+ZN%XcZ=K_YHr>)>vYcnSNU(#9Hgl(e`vh zeo9Q0&&7-}pU(a!K7I}V=#KJREt{C`_1<2&eNKr3_;IbXB)hWWZNb{ruH1%acMEiV z-!u-6j&yE)-(yG8yL=wkR2txKla`yb*I-mg_D8k{yf4)`U=?1iq3#lqUp#=25Su4) zTj^qpZE=N7b6r!naF%`KIMi?wb86~XlAZ*I{)d|OiT%E#w}bFwBn@qYN2+aQGneyr z(WFOA4q0tVo=%+7Jhyv#*Gf`jV`PKH8#1#b+~y?W7@>t&+gYbd$NZtwdlo%=XCDG> zTtmz%f3|w#$4RH9AqUGl_##n45)Vc)GauP>)#LTPp^(h(zjWqH(k1nf{rM!30GzQbk;}7 zA>>$oqUFiT{{> z#e}BErMh zTldA^r+3pT5QXoLPK#Lub)33xXK3C*Y`7|spNNIH;zl+m)r~`HOAT&a3a{Q*de@`0UYr z_+EUH+j6X>bZdybt}KtZH!iDP${RftV|A#O9jlthc2ddm0bkeJD%J969QS=xQ|f)2 za((@J4UH#d>Zi6OJy9`A*S~R8%KX6r3PM7qOx1yS^Y_PugKFQ0I^8H}-Bi_On4K2d z?Nf54$cVa^Uc@i5W6S;-rV9dys+X_Q8(i1k?W}ZHSDm_PbD#S|! z?XQS~e*CVQDw$brka_!|O~0k2;FNDt^?E3ay+brBcTIZuxdvTq^PFGA!}!H_unvFU zfB3~1mQVrN^jf~B$qBn2orvlpOZ7a``XFZiPZQROv3Qs_(+LlPx>>#mDkjPGyx3LY zldM%w+KzK)tmN!!+x+(s_tlqg>Yd?Ih)Z^r>{0r*J%C{eO4d|STneejQ zlx(c}%l8kFNWZOmlOcbPNsu}Dl>#%Zf{PCzOL50%hor{Oq1{GR#v(k8 z4F_bLxx`YHXoROEt|az-F-qNw=l)XRxNy6yh`2nwo4=s8_moN4e&NlL1szyEprcc(+L1CA*vT@PU1{uqid z6m><#zrVN@uU)r}i-L=2ivhV}>mjjZqo21Q-D<^JV3gA#gr}^2Q$N? zqcbmET6xgm*}3=c-*;x41)(0PZ%FOuSB>619wBg7#;+7Y;(I7M*{0VHmCZX*s2w(~ z?O7(1xPvm4$Yw&qX^pkxk5xl@M+p%$+D(xUWYHvATy$23uqPs)nbM5B)xoCrf1l~j z@o)idZth0(S|U>*rsh<9v)Z&78Jk=bXw!vP8vh)(=MUI4U;G7S7pJ2;sBu@WTv3|l zvz73jFAzL(L~0{pyO*!GpBO_V$^O2kSXyPeZhnSKS+*_m2{lzz>ckrKb&QJgrvAO- zZMGKX%UxZ1jE|wAp|~AgYYK4h`{k_}d*4Vcg1LOu?br2k{mkp$@{{~CW9(OvzOl`U zko@@{`$@=zSOpxraG_w_@z}9r@pIv}d|v-9pVE}mT+En-UT<$?lKcc#K$YT*_O-cCcutYf(^P zN7YK#iAU)H|MC6@D*JB7QRAb5Wtb*xNhCx0p4FsLg$a8`;^}?NWFAq`vq(N{uH5C9 zhJ5=P8_%AH4ke)X}G(XPe@1u2ZC0w6GBja zAA3rACa&2;)c$SSiCsP#XAL~!v0f9PRxmR+$0s79g`QT8w@*nY*VWZ2-HaA z<$K($K1wfM75#Z+g#SVP=Xo)1ZpE!Z3Nte^G+S(|X0%SQM`m}J#MBLm;YKKa)$SY| z8ObD8e;y=6E&H3}2ih{DYn>Y+#m-)_7nwgp^QeV7y zVVbKGX_?-9TKKhzat^3HGYZj(4j-q$4=tSkv9AIbnNk#d8Sn)Zw{Xa z(*or&f1s(-!0W`D1|GdzhI+~jAyunoVDJzvyOPT0d-CvkY{O*o-0M8MkY#LQ;)(=f)ySh+dK-6<{A(KX zJ#Jk*K8V>F(WWEC-&~%$rO3P04Q^b{qw#sbo@!Rf3l8BlUEV{7XrZPm z_XI&ra`??VS}-G3G^KSMClvBl#FcNwQI!bOO*TDazpj6o6={w6$lC_PsPnCd!Plkvu!=$*|Al{^GVM>?r5$V>gIx3o3wsa8+wZeEz zhZ)h@RouFfRkJk^#U>{LS$0z6l-dkg)~!RG`eicV8#HqtmOWjf#%1UiYYpxok-`-4 z0&wH%)~#ud(kE3^BpHkFp-LxDwqjPGHA<$aBP|NvuTSab%nTzsJTx>06}k9KGL%T} zi@T=e*>tE66^wuVDrEmwQVPvWsY=89q;$t9I59g0J7#%+mbigl=$tt22*VuD;I$>h zf5T0_O_wdDdkq(!pbDY=w!TY~f~GhKuJ}H>B)VL$i1DqdscA;_%s~npHnsKq+#1jAdJ_+L}pM7tP{3>tB34Cnl0RW3uJ>@P+6H@=`!4<-5@m`iIO4C z)Rq(||MoH%*554y9O<%wBzlur_}JmUpFS00ob6x3OAWhsFZuTVQzWx@!1idKn&rtpV3Ho6@qHCXwF+Wf+OWA;)yIy!3V>NMQk+` z4VTW@`C#)?;~*XI&{;kK+J4WK|ht$7S-S+B8iQqkO@)>N<`-EG``Wt~eg2NQ43oTBh* z=Xk(xGknsjP1RLYf{)+iNXJL{_U@IFaQo8Ov$QzPTLB0B-4GAx`J5v_%2W&0DO*{`+yTefXWeDg-5+k3oT(|ZevG#^l!K732S|MkLj&xqbWBMjLry=PHt z$a%8=^@&w$Si;C9sG`@DE=BnOtr7quNsAdN+D*%d4HDUpY(#W(X{$(<1$}g&;Ab1G zp#U&Gc8l`_uT<$%^!I)-#MZ#r$ia~T*#IRf^w86xl&LxM-v*YYy7c%=_hW-bkkO3M zxd;}-+MCw=VGjx|QpXBY;3`g1!=j@bnqeWT2oE~S?(TC=PVUn97=fjv@}8!nyrso= zv3XIMS6gQ#$^ZUi`&4!^a}1m&tb;aHNm`9447BQf7Vmya%S`7~G9LY(nWNIurJaX& zNPD->6xbMT2Bj7Yz!xKlyQAnJvSKfEMu(>g=I6bAkT*n)!&*5zH`8B6BISmZrrU)I zSbl=2hk;#k!1#=8gK*9Pt8s_deDE#U^xM*^$QW^SKL1jMlJRl%Q?=ZeH%6;J(8nBK8MWJpOId zJ`KL(*iJWP7SB;PZ{3=(Yz`&ifG?KT7vt#F*sRUf7p;nDUdaKNX)Z3Vepr^ai>ubu zEUZjSNEomPyPk$&VC}{S(0s|7>51r&rqaciY9xcBK(-&j(7y~CHB0N-%0m*<8OHxdXtl#{{ zUAf2&r>LJkeaiqATxx!_oErDQu85+KJ>}#LM@IONrxpzyl;)xQo{Za1wv9=B%A@mpT*Pf1+_9?=597;A-&B#{fLF;jD>;uB>`}&qTdwF|5 zBLGEQT$~d_C&xSl_;p}j?C8;RuwOManwFzu){%rY92aB>h)2Hwpp%K}3??Qf#9qyX z-2%+LpFUMt<%;K{*-8D(`#(s}-IwjtGG;^LoP8mzhvi@ts7iO-p6I00q;1gkjIa>& z4e~^6hPv|_m=-i|>+J*x(Nt4=H}3(JRh=h)T?E9ko_f53BprgOLOU?hv-0;2VP27U z?zpuszzap*)XlYuCy+T7Z4+iVDfYxoUMVSk06Ubhp1H{(<gwjUwbS-ASzLNpUX z`u%})!!iRH0yWG?c6RpWjT;{W(ONgg94qQico25??gd(#QLav{kN;i@tm{B?e9&s_ zdlBkyiWT+P(4L z2wfH#n?El9{})sregPY@CDRb?PXDYj6|6QDJP$WSjbpbfIED;l(JqI}wT>;e*1uwc z`EOUpp|Yfs{s^RYBP@BOkDKeR_GCeqtv+K;UZPh}qnf(SYxIClT2K5X8+8C`!A2`|tfJ(u?N>(`ngT^({9Vgi~iW}Nn7 zk6FG~G3gG9_6S4(sdR-CC-|@&prLzT(jz<1u3M+y(X6NJ@#DQx7^{J%*g!dX`7PVG zC-*?$MW@u;BivZK)VmM?BuZ3WCiEM%vI7AhUx#oW!V<`99{0d9}!ZujC4K185V z67F|kQW1%mvK}v-!V@7ipN~!4CF#D<`N#hLq+H7;xwHDNn(+&M$?(pDDDYJS^{{2f zjx?-+{tbTLE+gc1EwzWkUyCW+VIoQ*ndGlN>UQMaX~eZ6w0wXw>O8EP)LtPbG?nBUn@E zZlx$sv>}tpM@=TCr$vc~P3A$p`N#MO11B$={<&#*fA$Ci&*brMK4%*ZMc=^^_{XkY_KJcCF~*cWqVsV6xEKK@huSufxKDRRqu~GYEA%pr^f9)yv@9&O z2WfB|7Ux!2n5m*5ph_?llNi!~GDpA=)I@oqi4Se+IuR&X(9yAKxLMlq!=DU~n~1+M z4&wa^km*`GDP{(f^Xg!J*N3S|b5ql!hVm8GYi?JY672}>=6Fc5lZV|*zPxH*PqMm^ zrCZKkd`n>TNwXmpYm-wzpz*>`Zd)*Bj2%M62pR-4pe)GnOIn&|$n@SgxX=I(X^uvL zFTQ=ceJU994vvmfSh%ICp+S_nT!6!~Zx6%o8^9wQOge6H9F8i>bJB|8*NF^%Hn-xE zF6SMXsG0frZCP791|PpR&k^I_%E`$Y0#y+PbFdn9k;Pb8*4hTYdf58K>2$CXnO3Ag+B}+$NDWTzJjcn?Nj@nw~)uXYLri3 zU)*h%rdQZ(+4(eiPxV~^Be_E7=%L521(;0$>S-G+Zg5BqbV(yDwvd7BaJEzBr{3P} z*z#DM@Lh)iluXX0YY{`l$5(p+7$S1lrMibw^juYjfoRupOb6$c|F9<0ug? zDee2kKL8d8`$4w;u$%P-6VZ*UIkaY#_f`*e|L#`YUGOElmYMPj1)b+I(kC8Q8fURS z5Vd#nTEwnl+Zt5yD+`MVOvQj(G$NK*INbp9M=&dnVx^@9E{6=BwQr}Ren_t~C@3gu zTm0~$VWpn3=g(zLusaV8LFPjMD-ZXF$P1rT=?DJtYn%25V?)bRv%F`VB0i>RrH&6r z*9^z3AZe&}EKbnv%UsVJf-KFEI(oFdz1TgoRvMeiIWNNZS0h$NX>q3Vlwi!gckG)IH|JtZY76TU8pHDhh#yQbND21x&*&D@nm#tn#vpl33Rk0M4o9r z1A_|tNkT%w-K@nch?PsXXRB!sVOwp`j1rv%(7SFOb|McZ8|61psD!hHZce#bG=Z9V zc&djydeDvn!8F{dI@$&~6rTd12QS4*(Lu$iu;E394()yt=t76R{V8IkCJQ#mo`QQsd6QPN?qb=^65<1he+{-@nEuCR#&9fi?tm zdq-m?_i}}C6Pjvzza3jeN(so)Z6W-f#>@8f_rcvjNwOxII-})L$1bm~ZfH0q@<0`; z-x{FH3a3`ATCL&v7FLo%Eu9_UgAsq+yr8tsB0eroL)ay=qrfqi+z+#t=@N|${~F`0 zD!cq*pAm9@voe4B#5Y)U3KeW}>Sj9Z&tuTB{W%TNXLNM#-xswXw(%OT7v-X$lraiD zKvG-`NY6Xq4J(J-0K+j@eTaaF-2ATLGgJ1H5U@>|c!uui&FGX6PlO!^t*LRqO^gal z?Grb4I8NpJ&D0v2W}V}evhP?dd|Jjkv_x|oa_BIN5X;;NP!aUM-uzE_R>IIx2?N}@ z6|NW_4iRnt%WIns4Z*~wPP79WDp9|VkAu$lqa9tlb}fywvuH9=W`WYLr z^@u9AS@JGjFzn9-{&73(Hn*Oz46cXW_?muyWp{w`h7@(M*esvUMe$pj>oE&^lXP^j z4UkN7vuiJ=BK*#s&tF}k#bF<)#g*UX6h!VvT@clXwU=W*JX>PjN|HBTiX9plB8&zi zYN0PE&6!g=;SfD08vy&{^#eDMMoF*iCIRk9mU^tkn(KdC#V-a4`*>T}YY=>)E>jZf zT|7}WouLV-8QM9ohWPcIoSX_5lzM{712>%YJe1m^Gc=u%u+{1Og%F`sa^UhAu?V|?IO#-VcF%If;I``IuXX~U3ugiYOkeF?<=qLZHMRY;aq7@eKO1o`~ao<5++dZ8NoLA^&z5fxn*Qzh|LR2WYL|E^C|6^*gdSGc9G=skOrMa?+d+7 zTagDGUSAGV)DfP;n@O@oPtf1ALJUTOpo++A%~$wm-oxS2TfZeA3|f7(j3L2(VB@6% z=Yg_1kFr%zV!ZBkP*LEbz&O7KuBLe|Uaw$B#X4XAmo^qazduzCHj>^Q`1b{#x2JpJ z1t_Mbadst>S|u}|AJB>^>SxXngJ$t4eqeshKzmj!hwnEdO0#o=R3ca$YgkK~+k^T9 zcqyPY%;I5jg0Q2>?MG|0H^Oatv6F*s%GB{o4X-_? zhf#jznVgiQD6{x2mYnc(r9Xd1zI`;&Z;8?jE+ZDAgo;$aJwTAXR|5iYyr4>Od$l{= zM@pu74TdMdgJ0y0Pe9`j3^7@oVw>tx$<@#Dx6n(km5Q!2k!KLR% ze=ad8si>_aJFJ8zg96xSFP-EcYCcb^Vd3LS~(c?|2sk`W}mH|CI z)opqX7xD5q_#mDKNN*5=)vwVad_+$)=aFT1QLczxR}zGJjsf2h2RX3BZ{Xu$Zz(8= zYX-LOZDAz@uNf7Rgy9&ufCZ52`0+Tg9-1C2to`*@hYkk3@f+7R{r%+v#Oy~Mb$rp$ znxe^vDq44_Y~3OjK`zK@@D$h@>LA91aj0AWP)e7ufwdkcCMJ_$@CJbuH6a>oD)InS zY$D*q02t*~^cL4jgpXl6^2)BI98mh*!QgJISEd?T7gx9%BWTnqc1t&}yZZ<|oINFW zyt&Awe5bF|I+J)EV8$A@y9mu?SG{bnpC(-M^DRf1{dK0`Se2>p_zg_^iOBuUnc!|D zo`9B3Qm}tE7691-zMPIUOooR9$UbDhurGh00R%Z|eK(|gV^AN=gOe3NVye>GSbZSQ zx9gW_fOjwPkp9k`Mx-~PEmoR1_Qn*5yY$5n%r}HSCfv>~-%*g5T?f0(QvdP@Z~i+5 n{QqPWh#L~u|2iiOomgsCY|gYEzXktHB2g4h{+S{7*QNgf%S~~} literal 0 HcmV?d00001 diff --git a/runs/frontier-phase-factorial-v0/plot_mock_phase_factorial.py b/runs/frontier-phase-factorial-v0/plot_mock_phase_factorial.py new file mode 100644 index 0000000..fdfc37d --- /dev/null +++ b/runs/frontier-phase-factorial-v0/plot_mock_phase_factorial.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +"""Render the preregistered phase-factorial hypothesis schematic (mock data).""" + +from __future__ import annotations + +from pathlib import Path + +import matplotlib + +matplotlib.use("Agg") +import matplotlib.pyplot as plt + + +def main() -> None: + labels = ("235B\nprefill-only", "235B\nmixed", "30B\nprefill-only", "30B\nmixed") + x = range(len(labels)) + phase_hypothesis = (1.0, 18.0, 2.0, 28.0) + stack_hypothesis = (1.0, 4.0, 24.0, 30.0) + + fig, ax = plt.subplots(figsize=(8.6, 4.8), constrained_layout=True) + ax.plot(x, phase_hypothesis, marker="o", lw=2, label="H-phase (mock)") + ax.plot(x, stack_hypothesis, marker="s", lw=2, label="H-stack (mock)") + ax.axhline(5, color="black", ls="--", lw=1.2, label="5% regret gate") + ax.set_xticks(list(x), labels) + ax.set_ylabel("Worst selected-config regret (%) — MOCK DATA") + ax.set_title("Schematic only: predictions that distinguish phase vs stack explanations") + ax.set_ylim(0, 35) + ax.grid(axis="y", alpha=0.25) + ax.legend() + ax.text( + 0.01, + 0.98, + "MOCK DATA / NOT A RESULT", + transform=ax.transAxes, + va="top", + color="crimson", + weight="bold", + ) + output = Path(__file__).with_name("mock-phase-factorial.png") + fig.savefig(output, dpi=180) + print(output) + + +if __name__ == "__main__": + main() diff --git a/runs/frontier-phase-factorial-v0/qwen30_prefill_client.py b/runs/frontier-phase-factorial-v0/qwen30_prefill_client.py new file mode 100644 index 0000000..de40676 --- /dev/null +++ b/runs/frontier-phase-factorial-v0/qwen30_prefill_client.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python3 +"""Open-loop fixed-shape prefill-only workload for one real offered-load anchor.""" + +from __future__ import annotations + +import argparse +import concurrent.futures +import hashlib +import http.client +import json +import math +import time +from pathlib import Path +from typing import Any + + +TTFT_SLO_MS = 1256.0 +TARGET_PASS_RATE = 0.95 + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser() + parser.add_argument("--host", default="127.0.0.1") + parser.add_argument("--port", type=int, required=True) + parser.add_argument("--served-model", required=True) + parser.add_argument("--model-path", type=Path, required=True) + parser.add_argument("--rate", type=float, required=True) + parser.add_argument("--requests", type=int, default=64) + parser.add_argument("--input-tokens", type=int, default=2048) + parser.add_argument("--timeout-seconds", type=float, default=900.0) + parser.add_argument("--output", type=Path, required=True) + return parser.parse_args() + + +def percentile(values: list[float], fraction: float) -> float | None: + if not values: + return None + ordered = sorted(values) + index = min(len(ordered) - 1, max(0, math.ceil(fraction * len(ordered)) - 1)) + return ordered[index] + + +def run_request( + *, + request_index: int, + scheduled_at: float, + benchmark_start: float, + args: argparse.Namespace, + prompt_ids: list[int], +) -> dict[str, Any]: + delay = scheduled_at - time.perf_counter() + if delay > 0: + time.sleep(delay) + admitted = time.perf_counter() + record: dict[str, Any] = { + "request_index": request_index, + "scheduled_s": scheduled_at - benchmark_start, + "admitted_s": admitted - benchmark_start, + "admission_lag_ms": (admitted - scheduled_at) * 1000.0, + "success": False, + } + connection = http.client.HTTPConnection(args.host, args.port, timeout=args.timeout_seconds) + body = { + "model": args.served_model, + "prompt": prompt_ids, + "min_tokens": 1, + "max_tokens": 1, + "ignore_eos": True, + "temperature": 0, + "stream": True, + "stream_options": {"include_usage": True}, + "return_token_ids": True, + } + try: + started = time.perf_counter() + connection.request( + "POST", + "/v1/completions", + body=json.dumps(body, separators=(",", ":")).encode(), + headers={"Content-Type": "application/json"}, + ) + response = connection.getresponse() + if response.status != 200: + raise RuntimeError( + f"HTTP {response.status}: {response.read().decode(errors='replace')}" + ) + first_token_at = None + streamed_tokens = 0 + usage = None + while True: + raw = response.readline() + if not raw: + break + line = raw.decode(errors="replace").strip() + if not line.startswith("data:"): + continue + data = line[5:].strip() + if data == "[DONE]": + break + payload = json.loads(data) + if payload.get("usage"): + usage = payload["usage"] + emitted = 0 + for choice in payload.get("choices") or []: + token_ids = choice.get("token_ids") or [] + emitted += len(token_ids) if token_ids else int(bool(choice.get("text"))) + if emitted: + first_token_at = first_token_at or time.perf_counter() + streamed_tokens += emitted + finished = time.perf_counter() + if first_token_at is None or usage is None: + raise RuntimeError("missing streaming token or usage") + prompt_tokens = int(usage["prompt_tokens"]) + completion_tokens = int(usage["completion_tokens"]) + if prompt_tokens != args.input_tokens or completion_tokens != 1: + raise RuntimeError(f"usage mismatch: {prompt_tokens}+{completion_tokens}") + ttft = (first_token_at - started) * 1000.0 + record.update( + { + "success": True, + "prompt_tokens": prompt_tokens, + "completion_tokens": completion_tokens, + "streamed_token_count": streamed_tokens, + "ttft_ms": ttft, + "e2e_ms": (finished - started) * 1000.0, + "slo_pass": ttft <= TTFT_SLO_MS, + } + ) + except Exception as error: # Failed requests remain in the SLO denominator. + record["error"] = f"{type(error).__name__}: {error}" + record["slo_pass"] = False + finally: + connection.close() + return record + + +def main() -> None: + args = parse_args() + if args.rate <= 0 or args.requests <= 0 or args.input_tokens <= 0: + raise ValueError("rate, requests, and input tokens must be positive") + from transformers import AutoTokenizer + + tokenizer = AutoTokenizer.from_pretrained(args.model_path, trust_remote_code=True) + excluded = set(tokenizer.all_special_ids) + candidates = [ + token_id for token_id in range(tokenizer.vocab_size) if token_id not in excluded + ] + if len(candidates) < args.requests + 1: + raise RuntimeError("tokenizer has too few non-special token IDs") + base_id = candidates[0] + prompts = [ + [candidates[index + 1], *([base_id] * (args.input_tokens - 1))] + for index in range(args.requests) + ] + prompt_hash = hashlib.sha256( + "\n".join(",".join(map(str, prompt)) for prompt in prompts).encode() + ).hexdigest() + + benchmark_start = time.perf_counter() + 2.0 + with concurrent.futures.ThreadPoolExecutor(max_workers=args.requests) as pool: + futures = [ + pool.submit( + run_request, + request_index=index, + scheduled_at=benchmark_start + index / args.rate, + benchmark_start=benchmark_start, + args=args, + prompt_ids=prompts[index], + ) + for index in range(args.requests) + ] + requests = [future.result() for future in futures] + requests.sort(key=lambda row: int(row["request_index"])) + completed = [row for row in requests if row["success"]] + passed = sum(bool(row["slo_pass"]) for row in requests) + ttfts = [float(row["ttft_ms"]) for row in completed] + pass_rate = passed / len(requests) + payload = { + "schema": "qwen30-prefill-rate-anchor-v1", + "workload": { + "offered_request_rate": args.rate, + "request_count": args.requests, + "input_tokens": args.input_tokens, + "output_tokens": 1, + "prefix_caching": False, + "arrival": "open_loop_uniform", + "last_scheduled_arrival_s": (args.requests - 1) / args.rate, + "prompt_vector_sha256": prompt_hash, + }, + "summary": { + "completed": len(completed), + "failed": len(requests) - len(completed), + "ttft_p50_ms": percentile(ttfts, 0.50), + "ttft_p95_ms": percentile(ttfts, 0.95), + "ttft_max_ms": max(ttfts) if ttfts else None, + "admission_lag_max_ms": max( + float(row["admission_lag_ms"]) for row in requests + ), + "slo": { + "ttft_threshold_ms": TTFT_SLO_MS, + "passed": passed, + "pass_rate": pass_rate, + "feasible": pass_rate >= TARGET_PASS_RATE, + }, + }, + "requests": requests, + } + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n") + print(json.dumps(payload["summary"], sort_keys=True), flush=True) + if len(completed) != args.requests: + raise SystemExit(2) + + +if __name__ == "__main__": + main() diff --git a/runs/frontier-phase-factorial-v0/remote-sync-marker/README.md b/runs/frontier-phase-factorial-v0/remote-sync-marker/README.md new file mode 100644 index 0000000..9dfd754 --- /dev/null +++ b/runs/frontier-phase-factorial-v0/remote-sync-marker/README.md @@ -0,0 +1,6 @@ +# Phase-factorial fleet sync marker + +Experiment source is synchronized through Git into the clean dash0 checkout +`/home/admin/cpfs/wjh/aituner/aituner-qwen30-vllm020-profile-v1`. This small +directory exists only to satisfy the fleet orchestrator's explicit sync phase +without copying local simulator caches or raw metrics to the GPU host. diff --git a/runs/frontier-phase-factorial-v0/run_frontier_qwen30_prefill_surface.py b/runs/frontier-phase-factorial-v0/run_frontier_qwen30_prefill_surface.py new file mode 100644 index 0000000..10249d7 --- /dev/null +++ b/runs/frontier-phase-factorial-v0/run_frontier_qwen30_prefill_surface.py @@ -0,0 +1,400 @@ +#!/usr/bin/env python3 +"""Freeze the Qwen30 fixed-shape prefill-only Frontier surface.""" + +from __future__ import annotations + +import argparse +import csv +import hashlib +import importlib.util +import json +import math +import os +import subprocess +import sys +import time +from dataclasses import asdict, dataclass +from pathlib import Path +from typing import Any + + +MODEL = "qwen3-a3b-30b-moe" +RATES = (4.0, 8.0, 16.0, 32.0, 64.0) +TTFT_SLO_MS = 1256.0 +TARGET_PASS_RATE = 0.95 +NUM_BLOCKS = {1: 20080, 2: 76537, 4: 191727} + + +@dataclass(frozen=True) +class Config: + tp: int + mns: int + + @property + def name(self) -> str: + return f"tp{self.tp}_mns{self.mns}" + + +GRID = tuple(Config(tp, mns) for tp in (1, 2, 4) for mns in (8, 16, 32, 64)) + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser() + parser.add_argument("--frontier-source", type=Path, required=True) + parser.add_argument("--replayserve-root", type=Path, required=True) + parser.add_argument("--profile-root", type=Path, required=True) + parser.add_argument("--python-deps", type=Path, required=True) + parser.add_argument("--output-root", type=Path, required=True) + parser.add_argument("--requests", type=int, default=64) + parser.add_argument("--rate", type=float, action="append") + parser.add_argument("--config", action="append") + parser.add_argument("--timeout-seconds", type=float, default=900.0) + parser.add_argument("--resume", action="store_true") + return parser.parse_args() + + +def sha256(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as source: + for chunk in iter(lambda: source.read(1 << 20), b""): + digest.update(chunk) + return digest.hexdigest() + + +def write_json(path: Path, payload: Any) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + temporary = path.with_suffix(path.suffix + ".tmp") + temporary.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n") + os.replace(temporary, path) + + +def load_module(name: str, path: Path): + spec = importlib.util.spec_from_file_location(name, path) + if spec is None or spec.loader is None: + raise ImportError(path) + module = importlib.util.module_from_spec(spec) + sys.modules[name] = module + spec.loader.exec_module(module) + return module + + +def write_trace(path: Path, *, requests: int, rate: float) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + fields = [ + "arrived_at", + "num_prefill_tokens", + "num_decode_tokens", + "session_id", + "block_hash_ids", + ] + with path.open("w", newline="") as output: + writer = csv.DictWriter(output, fieldnames=fields) + writer.writeheader() + for request_id in range(requests): + writer.writerow( + { + "arrived_at": f"{request_id / rate:.12f}", + "num_prefill_tokens": 2048, + "num_decode_tokens": 1, + "session_id": request_id, + "block_hash_ids": "|".join( + str(request_id * 128 + block + 1) for block in range(128) + ), + } + ) + + +def profile_paths(root: Path) -> dict[str, Path]: + paths = { + "linear": root / "linear_op.csv", + "attention": root / "attention.csv", + "moe": root / "moe.csv", + "manifest": root / "manifest.json", + } + missing = [str(path) for path in paths.values() if not path.is_file()] + if missing: + raise FileNotFoundError(missing) + return paths + + +def validate_profile(paths: dict[str, Path]) -> dict[str, Any]: + manifest = json.loads(paths["manifest"].read_text()) + expected = manifest["outputs"] + for filename in ("linear_op.csv", "attention.csv", "moe.csv"): + actual = sha256(paths[{"linear_op.csv": "linear", "attention.csv": "attention", "moe.csv": "moe"}[filename]]) + if actual != expected[filename]: + raise ValueError(f"profile hash mismatch for {filename}") + with paths["attention"].open(newline="") as source: + rows = list(csv.DictReader(source)) + coverage = {} + for tp in (1, 2, 4): + exact = [ + row + for row in rows + if int(row["num_tensor_parallel_workers"]) == tp + and row["is_prefill"].lower() == "true" + and row.get("is_true_mixed_batch", "").lower() != "true" + and int(float(row["total_tokens"])) == 2048 + ] + if len(exact) != 1: + raise ValueError(f"expected one exact TP{tp} 2048-token prefill row, got {len(exact)}") + coverage[str(tp)] = {"exact_prefill_2048_rows": len(exact), "profile_batch_size": int(exact[0]["batch_size"])} + return {"manifest": manifest, "attention": coverage} + + +def knobs(config: Config, paths: dict[str, Path], cache: Path) -> dict[str, Any]: + return { + "simulation_mode": "online", + "sys_arch": "co-location", + "num_replicas": 1, + "cluster_scheduler": "sticky_round_robin", + "model_name": MODEL, + "device": "h20", + "network_device": "h20_dgx", + "attn_tensor_parallel_size": config.tp, + "attn_data_parallel_size": 1, + "moe_tensor_parallel_size": config.tp, + "moe_expert_parallel_size": 1, + "num_pipeline_stages": 1, + "replica_scheduler": "vllm_v1", + "decode_cuda_graph_mode": "none", + "batch_size_cap": config.mns, + "max_tokens_in_batch": 8192, + "long_prefill_token_threshold": 0, + "block_size": 16, + "num_blocks_mode": "explicit", + "num_blocks": NUM_BLOCKS[config.tp], + "gpu_memory_utilization": 0.92, + "non_kv_cache_overhead_bytes": 0, + "trace_max_tokens": 40960, + "cache_dir": str(cache), + "enable_dummy_mode": False, + "linear_op_input_file": str(paths["linear"]), + "atten_input_file": str(paths["attention"]), + "moe_input_file": str(paths["moe"]), + "prediction_max_prefill_chunk_size": 18000, + "prediction_max_batch_size": 128, + "prediction_max_tokens_per_request": 32768, + "no_cache": False, + "skip_cpu_overhead_modeling": True, + "enable_prefix_caching": False, + "enable_chunked_prefill": True, + } + + +def find_metrics(run_dir: Path) -> tuple[Path, Path]: + systems = list((run_dir / "metrics").rglob("system_metrics.json")) + requests = list((run_dir / "metrics").rglob("request_metrics.csv")) + if len(systems) != 1 or len(requests) != 1: + raise RuntimeError(f"metric pair mismatch: {len(systems)}/{len(requests)}") + return systems[0], requests[0] + + +def score(system_path: Path, request_path: Path, expected_requests: int) -> dict[str, Any]: + system = json.loads(system_path.read_text()) + metadata = system["simulation_metadata"] + if int(metadata["completed_requests"]) != expected_requests: + raise ValueError("Frontier completion count mismatch") + with request_path.open(newline="") as source: + rows = list(csv.DictReader(source)) + if len(rows) != expected_requests: + raise ValueError("request metric count mismatch") + values = [] + passed = 0 + for row in rows: + if int(float(row["request_num_prefill_tokens"])) != 2048: + raise ValueError("prefill shape drift") + if int(float(row["request_num_decode_tokens"])) != 1: + raise ValueError("decode shape drift") + ttft = float(row["ttft"]) + if not math.isfinite(ttft) or ttft < 0: + raise ValueError("invalid TTFT") + values.append(ttft) + passed += int(ttft <= TTFT_SLO_MS) + ordered = sorted(values) + pass_rate = passed / expected_requests + return { + "ttft_p50_ms": ordered[math.ceil(0.50 * len(ordered)) - 1], + "ttft_p95_ms": ordered[math.ceil(0.95 * len(ordered)) - 1], + "ttft_max_ms": max(ordered), + "passed": passed, + "pass_rate": pass_rate, + "feasible": pass_rate >= TARGET_PASS_RATE, + "throughput_requests_per_second": float(system["throughput_metrics"]["requests_per_second"]), + } + + +def main() -> None: + args = parse_args() + args.frontier_source = args.frontier_source.resolve() + args.replayserve_root = args.replayserve_root.resolve() + args.profile_root = args.profile_root.resolve() + args.python_deps = args.python_deps.resolve() + args.output_root = args.output_root.resolve() + rates = tuple(args.rate or RATES) + selected = list(GRID) + if args.config: + wanted = set(args.config) + selected = [config for config in GRID if config.name in wanted] + if {config.name for config in selected} != wanted: + raise ValueError(f"unknown configs: {wanted - {config.name for config in selected}}") + paths = profile_paths(args.profile_root) + coverage = validate_profile(paths) + builder = load_module( + "qwen30_prefill_frontier_builder", + args.replayserve_root / "tools/run_frontier_sweep.py", + ) + frontier_head = subprocess.run( + ["git", "-C", str(args.frontier_source), "rev-parse", "HEAD"], + check=True, + text=True, + stdout=subprocess.PIPE, + ).stdout.strip() + traces = {} + for rate in rates: + trace = args.output_root / "traces" / f"r{rate:g}.csv" + write_trace(trace, requests=args.requests, rate=rate) + traces[rate] = trace + + config_results = [] + for config in selected: + loads = [] + config_knobs = knobs(config, paths, args.output_root / "cache") + for rate in rates: + run_dir = args.output_root / "runs" / config.name / f"r{rate:g}" + result_path = run_dir / "result.json" + if args.resume and result_path.is_file(): + loads.append(json.loads(result_path.read_text())) + continue + run_dir.mkdir(parents=True, exist_ok=True) + command = builder.build_frontier_command( + python_bin="/usr/bin/python3", + trace_file=traces[rate], + metrics_root=run_dir / "metrics", + run_id=f"qwen30_prefill_{config.name}_r{rate:g}", + knobs=config_knobs, + ) + write_json(run_dir / "command.json", command) + environment = os.environ.copy() + pythonpath = [str(args.python_deps), str(args.frontier_source)] + if environment.get("PYTHONPATH"): + pythonpath.append(environment["PYTHONPATH"]) + environment.update( + { + "PYTHONPATH": ":".join(pythonpath), + "CUDA_VISIBLE_DEVICES": "", + "NVIDIA_VISIBLE_DEVICES": "void", + "WANDB_DISABLED": "true", + "VIDUR_DISABLE_WANDB": "1", + "FRONTIER_LOG_LEVEL": "WARNING", + "PYTHONDONTWRITEBYTECODE": "1", + } + ) + started = time.time() + with (run_dir / "stdout.log").open("w") as stdout, ( + run_dir / "stderr.log" + ).open("w") as stderr: + completed = subprocess.run( + command, + cwd=args.frontier_source, + env=environment, + stdout=stdout, + stderr=stderr, + timeout=args.timeout_seconds, + check=False, + ) + if completed.returncode != 0: + raise RuntimeError( + f"Frontier failed for {config.name} rate={rate}: {completed.returncode}" + ) + system_path, request_path = find_metrics(run_dir) + result = { + "status": "completed", + "config": asdict(config) | {"name": config.name}, + "offered_request_rate": rate, + "offered_request_rate_per_gpu": rate / config.tp, + "request_count": args.requests, + "elapsed_seconds": time.time() - started, + "trace_sha256": sha256(traces[rate]), + "request_metrics_sha256": sha256(request_path), + "score": score(system_path, request_path, args.requests), + } + write_json(result_path, result) + loads.append(result) + print( + json.dumps( + { + "config": config.name, + "rate": rate, + "pass_rate": result["score"]["pass_rate"], + "feasible": result["score"]["feasible"], + }, + sort_keys=True, + ), + flush=True, + ) + config_results.append({"config": asdict(config) | {"name": config.name}, "loads": loads}) + + capacities = [] + for item in config_results: + feasible = [ + load["offered_request_rate"] + for load in item["loads"] + if load["score"]["feasible"] + ] + capacity = max(feasible) if feasible else None + capacities.append( + { + "config": item["config"], + "maximum_tested_feasible_request_rate": capacity, + "maximum_tested_feasible_request_rate_per_gpu": ( + capacity / item["config"]["tp"] if capacity is not None else None + ), + "lower_censored": capacity is None, + "upper_censored": capacity == max(rates) if capacity is not None else False, + } + ) + capacities.sort( + key=lambda row: ( + -(row["maximum_tested_feasible_request_rate_per_gpu"] or -1), + row["config"]["name"], + ) + ) + full = selected == list(GRID) and rates == RATES and args.requests == 64 + manifest = { + "schema": "frontier-qwen30-prefill-surface-v1", + "status": "frozen_before_real" if full else "partial_not_decision_bearing", + "contract": { + "rates": rates, + "requests_per_anchor": args.requests, + "input_tokens": 2048, + "output_tokens": 1, + "ttft_slo_ms": TTFT_SLO_MS, + "target_pass_rate": TARGET_PASS_RATE, + "prefix_caching": False, + "arrival": "open_loop_uniform", + }, + "frontier": { + "source": str(args.frontier_source), + "git_head": frontier_head, + "git_status_short": subprocess.run( + ["git", "-C", str(args.frontier_source), "status", "--short"], + check=True, + text=True, + stdout=subprocess.PIPE, + ).stdout, + }, + "profiles": { + "root": str(args.profile_root), + "coverage": coverage, + "sha256": {name: sha256(path) for name, path in paths.items()}, + }, + "config_results": config_results, + "capacity": capacities, + } + write_json(args.output_root / "frontier_surface_frozen.json", manifest) + print(args.output_root / "frontier_surface_frozen.json") + + +if __name__ == "__main__": + main() diff --git a/runs/frontier-phase-factorial-v0/run_qwen30_prefill_real_config.sh b/runs/frontier-phase-factorial-v0/run_qwen30_prefill_real_config.sh new file mode 100644 index 0000000..a406d51 --- /dev/null +++ b/runs/frontier-phase-factorial-v0/run_qwen30_prefill_real_config.sh @@ -0,0 +1,135 @@ +#!/usr/bin/env bash + +set -euo pipefail + +OUTPUT_ROOT="${OUTPUT_ROOT:?OUTPUT_ROOT is required}" +TP="${TP:?TP is required}" +MNS="${MNS:?MNS is required}" +RATES="${RATES:-4 8 16 32 64}" +SERVER_PORT="${SERVER_PORT:?SERVER_PORT is required}" +VENV_ROOT="${VENV_ROOT:-/tmp/wjh/venvs/vllm-0.20.0-cu129-profiler-v1}" +MODEL_ROOT="${MODEL_ROOT:-/home/admin/cpfs/wjh/models/Qwen/Qwen3-30B-A3B}" +SERVED_MODEL="qwen3-30b-prefill-only" +SERVER_PID="" + +mkdir -p "${OUTPUT_ROOT}/logs" "${OUTPUT_ROOT}/provenance" +exec > >(tee -a "${OUTPUT_ROOT}/logs/controller.log") 2>&1 + +cleanup() { + if [[ -n "${SERVER_PID}" ]] && kill -0 "${SERVER_PID}" 2>/dev/null; then + kill -TERM -- "-${SERVER_PID}" 2>/dev/null || true + for _ in $(seq 1 30); do + kill -0 "${SERVER_PID}" 2>/dev/null || break + sleep 1 + done + kill -KILL -- "-${SERVER_PID}" 2>/dev/null || true + fi + SERVER_PID="" +} +trap cleanup EXIT INT TERM + +IFS=',' read -r -a GPU_IDS <<< "${CUDA_VISIBLE_DEVICES:-}" +if [[ "${#GPU_IDS[@]}" -ne "${TP}" ]]; then + echo "ERROR: expected ${TP} allocated GPUs, got ${CUDA_VISIBLE_DEVICES:-unset}" >&2 + exit 1 +fi +read -r -a RATE_ARRAY <<< "${RATES}" + +echo "QWEN30_PREFILL_REAL_LAUNCH_ECHO host=$(hostname) gpus=${CUDA_VISIBLE_DEVICES} model=${MODEL_ROOT} runtime=vLLM-0.20.0+cu129 dtype=BF16 config=TP${TP}_MNS${MNS}_MBT8192 rates=${RATES// /,} rounds=2 requests=64 shape=ISL2048_OSL1 arrivals=uniform prefix=off cuda_graph=runtime_default isolation=fresh_server_per_anchor output=${OUTPUT_ROOT}" +date -u +"START_UTC=%Y-%m-%dT%H:%M:%SZ" +sha256sum run_qwen30_prefill_real_config.sh qwen30_prefill_client.py \ + > "${OUTPUT_ROOT}/provenance/source.sha256" +"${VENV_ROOT}/bin/python" - "${TP}" "${MNS}" "${RATES}" \ + > "${OUTPUT_ROOT}/provenance/contract.json" <<'PY' +import importlib.metadata as metadata +import json +import platform +import sys + +tp, mns, rates = sys.argv[1:] +print(json.dumps({ + "python": platform.python_version(), + "torch": metadata.version("torch"), + "transformers": metadata.version("transformers"), + "vllm": metadata.version("vllm"), + "config": {"tp": int(tp), "mns": int(mns), "mbt": 8192}, + "rates": [float(value) for value in rates.split()], + "rounds": 2, + "requests_per_anchor": 64, + "anchor_isolation": "fresh_server_per_rate_per_round", + "target_rate_warmup_requests": "min(32, max(4, ceil(rate * 2)))", + "input_tokens": 2048, + "output_tokens": 1, + "ttft_slo_ms": 1256.0, + "target_pass_rate": 0.95, +}, indent=2, sort_keys=True)) +PY +nvidia-smi --query-gpu=index,name,uuid,driver_version --format=csv,noheader \ + > "${OUTPUT_ROOT}/provenance/gpus.csv" +sha256sum "${MODEL_ROOT}/config.json" > "${OUTPUT_ROOT}/provenance/model.sha256" + +export TOKENIZERS_PARALLELISM=false +export VLLM_USE_V1=1 +export TORCH_CUDA_ARCH_LIST=9.0 +export HF_HUB_OFFLINE=1 +export TRANSFORMERS_OFFLINE=1 + +for ROUND in 1 2; do + ROUND_ROOT="${OUTPUT_ROOT}/round${ROUND}" + mkdir -p "${ROUND_ROOT}/logs" "${ROUND_ROOT}/results" + ORDERED_RATES=("${RATE_ARRAY[@]}") + if [[ "${ROUND}" -eq 2 ]]; then + ORDERED_RATES=() + for ((index=${#RATE_ARRAY[@]}-1; index>=0; index--)); do + ORDERED_RATES+=("${RATE_ARRAY[index]}") + done + fi + for RATE in "${ORDERED_RATES[@]}"; do + KEY="$(printf 'r%.2f' "${RATE}" | tr '.' 'p')" + SERVER_LOG="${ROUND_ROOT}/logs/server_${KEY}.log" + setsid "${VENV_ROOT}/bin/vllm" serve "${MODEL_ROOT}" \ + --host 127.0.0.1 --port "${SERVER_PORT}" --served-model-name "${SERVED_MODEL}" \ + --tensor-parallel-size "${TP}" --gpu-memory-utilization 0.92 \ + --max-model-len 40960 --max-num-batched-tokens 8192 --max-num-seqs "${MNS}" \ + --no-enable-prefix-caching --enable-chunked-prefill --no-enable-log-requests \ + > "${SERVER_LOG}" 2>&1 & + SERVER_PID=$! + READY=0 + for _ in $(seq 1 120); do + if curl -fsS --max-time 2 "http://127.0.0.1:${SERVER_PORT}/v1/models" \ + > "${ROUND_ROOT}/results/models_${KEY}.json" 2>/dev/null; then + READY=1 + break + fi + if ! kill -0 "${SERVER_PID}" 2>/dev/null; then + tail -200 "${SERVER_LOG}" + exit 1 + fi + sleep 3 + done + if [[ "${READY}" -ne 1 ]]; then + tail -200 "${SERVER_LOG}" + exit 1 + fi + + WARMUP_REQUESTS="$("${VENV_ROOT}/bin/python" - "${RATE}" <<'PY' +import math +import sys +print(min(32, max(4, math.ceil(float(sys.argv[1]) * 2.0)))) +PY +)" + "${VENV_ROOT}/bin/python" qwen30_prefill_client.py --port "${SERVER_PORT}" \ + --served-model "${SERVED_MODEL}" --model-path "${MODEL_ROOT}" --rate "${RATE}" \ + --requests "${WARMUP_REQUESTS}" \ + --output "${ROUND_ROOT}/results/warmup_${KEY}.json" + "${VENV_ROOT}/bin/python" qwen30_prefill_client.py --port "${SERVER_PORT}" \ + --served-model "${SERVED_MODEL}" --model-path "${MODEL_ROOT}" --rate "${RATE}" \ + --requests 64 --output "${ROUND_ROOT}/results/${KEY}.json" + cleanup + done +done + +find "${OUTPUT_ROOT}" -type f ! -path '*/provenance/artifacts.sha256' -print0 \ + | sort -z | xargs -0 sha256sum > "${OUTPUT_ROOT}/provenance/artifacts.sha256" +date -u +"END_UTC=%Y-%m-%dT%H:%M:%SZ" +echo "QWEN30_PREFILL_REAL_CONFIG_COMPLETE" diff --git a/runs/frontier-phase-factorial-v0/simulator-tp1/frontier_surface_frozen.json b/runs/frontier-phase-factorial-v0/simulator-tp1/frontier_surface_frozen.json new file mode 100644 index 0000000..0dafcdd --- /dev/null +++ b/runs/frontier-phase-factorial-v0/simulator-tp1/frontier_surface_frozen.json @@ -0,0 +1,647 @@ +{ + "capacity": [ + { + "config": { + "mns": 16, + "name": "tp1_mns16", + "tp": 1 + }, + "lower_censored": false, + "maximum_tested_feasible_request_rate": 8.0, + "maximum_tested_feasible_request_rate_per_gpu": 8.0, + "upper_censored": false + }, + { + "config": { + "mns": 32, + "name": "tp1_mns32", + "tp": 1 + }, + "lower_censored": false, + "maximum_tested_feasible_request_rate": 8.0, + "maximum_tested_feasible_request_rate_per_gpu": 8.0, + "upper_censored": false + }, + { + "config": { + "mns": 64, + "name": "tp1_mns64", + "tp": 1 + }, + "lower_censored": false, + "maximum_tested_feasible_request_rate": 8.0, + "maximum_tested_feasible_request_rate_per_gpu": 8.0, + "upper_censored": false + }, + { + "config": { + "mns": 8, + "name": "tp1_mns8", + "tp": 1 + }, + "lower_censored": false, + "maximum_tested_feasible_request_rate": 8.0, + "maximum_tested_feasible_request_rate_per_gpu": 8.0, + "upper_censored": false + } + ], + "config_results": [ + { + "config": { + "mns": 8, + "name": "tp1_mns8", + "tp": 1 + }, + "loads": [ + { + "config": { + "mns": 8, + "name": "tp1_mns8", + "tp": 1 + }, + "elapsed_seconds": 20.451046466827393, + "offered_request_rate": 4.0, + "offered_request_rate_per_gpu": 4.0, + "request_count": 64, + "request_metrics_sha256": "f0cfb072afcc2fc82a92c59ddb4e8cfc1e3201e15848f37785eeb9fdd975e779", + "score": { + "feasible": true, + "pass_rate": 1.0, + "passed": 64, + "throughput_requests_per_second": 4.0197002243768605, + "ttft_max_ms": 171.58529929215405, + "ttft_p50_ms": 171.58529929215405, + "ttft_p95_ms": 171.58529929215405 + }, + "status": "completed", + "trace_sha256": "0cef631f351f5afd880172d0467d62d9813de5ba36fd3795f91afe3256450c05" + }, + { + "config": { + "mns": 8, + "name": "tp1_mns8", + "tp": 1 + }, + "elapsed_seconds": 22.0544171333313, + "offered_request_rate": 8.0, + "offered_request_rate_per_gpu": 8.0, + "request_count": 64, + "request_metrics_sha256": "cfebfbd8aa5d9603158d8ff4e786119433b4c0d705f733ba242da46a6a6fdfb0", + "score": { + "feasible": true, + "pass_rate": 1.0, + "passed": 64, + "throughput_requests_per_second": 7.742175153637853, + "ttft_max_ms": 570.3133207139617, + "ttft_p50_ms": 391.4107605377559, + "ttft_p95_ms": 516.4107605377559 + }, + "status": "completed", + "trace_sha256": "c49314cbe4e4b07a9335b57e861ec5b6541ea4cc061ca9abf923453276c419fd" + }, + { + "config": { + "mns": 8, + "name": "tp1_mns8", + "tp": 1 + }, + "elapsed_seconds": 18.859471321105957, + "offered_request_rate": 16.0, + "offered_request_rate_per_gpu": 16.0, + "request_count": 64, + "request_metrics_sha256": "03578ba406b3299e70ba7975e6b9872d381a5f4bd4fbc3c8f6a8ba49e9b53f2e", + "score": { + "feasible": false, + "pass_rate": 0.3125, + "passed": 20, + "throughput_requests_per_second": 8.916224898348922, + "ttft_max_ms": 3318.8404181099845, + "ttft_p50_ms": 1814.2305264490028, + "ttft_p95_ms": 3193.8404181099845 + }, + "status": "completed", + "trace_sha256": "2504b6340f764e726110e48251f8017a2f9ba67b44b8af7d3a838bf674823d33" + }, + { + "config": { + "mns": 8, + "name": "tp1_mns8", + "tp": 1 + }, + "elapsed_seconds": 16.640390872955322, + "offered_request_rate": 32.0, + "offered_request_rate_per_gpu": 32.0, + "request_count": 64, + "request_metrics_sha256": "0b761b5f42e65b694eb34735433d43b473ed67415fb4e17b89894fc840b645fc", + "score": { + "feasible": false, + "pass_rate": 0.203125, + "passed": 13, + "throughput_requests_per_second": 9.07297385348011, + "ttft_max_ms": 5147.666503402199, + "ttft_p50_ms": 2739.3537469047046, + "ttft_p95_ms": 4962.713638565687 + }, + "status": "completed", + "trace_sha256": "5508a3ce590f7d1b0b3c211a2d4f2131886a7ae2d85cafa0c436a8e3029675f1" + }, + { + "config": { + "mns": 8, + "name": "tp1_mns8", + "tp": 1 + }, + "elapsed_seconds": 18.142091274261475, + "offered_request_rate": 64.0, + "offered_request_rate_per_gpu": 64.0, + "request_count": 64, + "request_metrics_sha256": "078209f2f56f21243384bdc70add105bf4942ee44ec8cc9573cad399a4a10e03", + "score": { + "feasible": false, + "pass_rate": 0.140625, + "passed": 9, + "throughput_requests_per_second": 9.07297385348011, + "ttft_max_ms": 6100.791503402199, + "ttft_p50_ms": 3208.1037469047046, + "ttft_p95_ms": 5853.338638565687 + }, + "status": "completed", + "trace_sha256": "8987c3ebd4a7db983dc0e99e44710cd44a9d5d21cdb8e6b58878b160918593d9" + } + ] + }, + { + "config": { + "mns": 16, + "name": "tp1_mns16", + "tp": 1 + }, + "loads": [ + { + "config": { + "mns": 16, + "name": "tp1_mns16", + "tp": 1 + }, + "elapsed_seconds": 19.96323037147522, + "offered_request_rate": 4.0, + "offered_request_rate_per_gpu": 4.0, + "request_count": 64, + "request_metrics_sha256": "f0cfb072afcc2fc82a92c59ddb4e8cfc1e3201e15848f37785eeb9fdd975e779", + "score": { + "feasible": true, + "pass_rate": 1.0, + "passed": 64, + "throughput_requests_per_second": 4.0197002243768605, + "ttft_max_ms": 171.58529929215405, + "ttft_p50_ms": 171.58529929215405, + "ttft_p95_ms": 171.58529929215405 + }, + "status": "completed", + "trace_sha256": "0cef631f351f5afd880172d0467d62d9813de5ba36fd3795f91afe3256450c05" + }, + { + "config": { + "mns": 16, + "name": "tp1_mns16", + "tp": 1 + }, + "elapsed_seconds": 18.554351568222046, + "offered_request_rate": 8.0, + "offered_request_rate_per_gpu": 8.0, + "request_count": 64, + "request_metrics_sha256": "cfebfbd8aa5d9603158d8ff4e786119433b4c0d705f733ba242da46a6a6fdfb0", + "score": { + "feasible": true, + "pass_rate": 1.0, + "passed": 64, + "throughput_requests_per_second": 7.742175153637853, + "ttft_max_ms": 570.3133207139617, + "ttft_p50_ms": 391.4107605377559, + "ttft_p95_ms": 516.4107605377559 + }, + "status": "completed", + "trace_sha256": "c49314cbe4e4b07a9335b57e861ec5b6541ea4cc061ca9abf923453276c419fd" + }, + { + "config": { + "mns": 16, + "name": "tp1_mns16", + "tp": 1 + }, + "elapsed_seconds": 25.70284128189087, + "offered_request_rate": 16.0, + "offered_request_rate_per_gpu": 16.0, + "request_count": 64, + "request_metrics_sha256": "03578ba406b3299e70ba7975e6b9872d381a5f4bd4fbc3c8f6a8ba49e9b53f2e", + "score": { + "feasible": false, + "pass_rate": 0.3125, + "passed": 20, + "throughput_requests_per_second": 8.916224898348922, + "ttft_max_ms": 3318.8404181099845, + "ttft_p50_ms": 1814.2305264490028, + "ttft_p95_ms": 3193.8404181099845 + }, + "status": "completed", + "trace_sha256": "2504b6340f764e726110e48251f8017a2f9ba67b44b8af7d3a838bf674823d33" + }, + { + "config": { + "mns": 16, + "name": "tp1_mns16", + "tp": 1 + }, + "elapsed_seconds": 20.056548833847046, + "offered_request_rate": 32.0, + "offered_request_rate_per_gpu": 32.0, + "request_count": 64, + "request_metrics_sha256": "0b761b5f42e65b694eb34735433d43b473ed67415fb4e17b89894fc840b645fc", + "score": { + "feasible": false, + "pass_rate": 0.203125, + "passed": 13, + "throughput_requests_per_second": 9.07297385348011, + "ttft_max_ms": 5147.666503402199, + "ttft_p50_ms": 2739.3537469047046, + "ttft_p95_ms": 4962.713638565687 + }, + "status": "completed", + "trace_sha256": "5508a3ce590f7d1b0b3c211a2d4f2131886a7ae2d85cafa0c436a8e3029675f1" + }, + { + "config": { + "mns": 16, + "name": "tp1_mns16", + "tp": 1 + }, + "elapsed_seconds": 11.15293574333191, + "offered_request_rate": 64.0, + "offered_request_rate_per_gpu": 64.0, + "request_count": 64, + "request_metrics_sha256": "078209f2f56f21243384bdc70add105bf4942ee44ec8cc9573cad399a4a10e03", + "score": { + "feasible": false, + "pass_rate": 0.140625, + "passed": 9, + "throughput_requests_per_second": 9.07297385348011, + "ttft_max_ms": 6100.791503402199, + "ttft_p50_ms": 3208.1037469047046, + "ttft_p95_ms": 5853.338638565687 + }, + "status": "completed", + "trace_sha256": "8987c3ebd4a7db983dc0e99e44710cd44a9d5d21cdb8e6b58878b160918593d9" + } + ] + }, + { + "config": { + "mns": 32, + "name": "tp1_mns32", + "tp": 1 + }, + "loads": [ + { + "config": { + "mns": 32, + "name": "tp1_mns32", + "tp": 1 + }, + "elapsed_seconds": 10.411258935928345, + "offered_request_rate": 4.0, + "offered_request_rate_per_gpu": 4.0, + "request_count": 64, + "request_metrics_sha256": "f0cfb072afcc2fc82a92c59ddb4e8cfc1e3201e15848f37785eeb9fdd975e779", + "score": { + "feasible": true, + "pass_rate": 1.0, + "passed": 64, + "throughput_requests_per_second": 4.0197002243768605, + "ttft_max_ms": 171.58529929215405, + "ttft_p50_ms": 171.58529929215405, + "ttft_p95_ms": 171.58529929215405 + }, + "status": "completed", + "trace_sha256": "0cef631f351f5afd880172d0467d62d9813de5ba36fd3795f91afe3256450c05" + }, + { + "config": { + "mns": 32, + "name": "tp1_mns32", + "tp": 1 + }, + "elapsed_seconds": 10.355234384536743, + "offered_request_rate": 8.0, + "offered_request_rate_per_gpu": 8.0, + "request_count": 64, + "request_metrics_sha256": "cfebfbd8aa5d9603158d8ff4e786119433b4c0d705f733ba242da46a6a6fdfb0", + "score": { + "feasible": true, + "pass_rate": 1.0, + "passed": 64, + "throughput_requests_per_second": 7.742175153637853, + "ttft_max_ms": 570.3133207139617, + "ttft_p50_ms": 391.4107605377559, + "ttft_p95_ms": 516.4107605377559 + }, + "status": "completed", + "trace_sha256": "c49314cbe4e4b07a9335b57e861ec5b6541ea4cc061ca9abf923453276c419fd" + }, + { + "config": { + "mns": 32, + "name": "tp1_mns32", + "tp": 1 + }, + "elapsed_seconds": 10.098335266113281, + "offered_request_rate": 16.0, + "offered_request_rate_per_gpu": 16.0, + "request_count": 64, + "request_metrics_sha256": "03578ba406b3299e70ba7975e6b9872d381a5f4bd4fbc3c8f6a8ba49e9b53f2e", + "score": { + "feasible": false, + "pass_rate": 0.3125, + "passed": 20, + "throughput_requests_per_second": 8.916224898348922, + "ttft_max_ms": 3318.8404181099845, + "ttft_p50_ms": 1814.2305264490028, + "ttft_p95_ms": 3193.8404181099845 + }, + "status": "completed", + "trace_sha256": "2504b6340f764e726110e48251f8017a2f9ba67b44b8af7d3a838bf674823d33" + }, + { + "config": { + "mns": 32, + "name": "tp1_mns32", + "tp": 1 + }, + "elapsed_seconds": 9.8981294631958, + "offered_request_rate": 32.0, + "offered_request_rate_per_gpu": 32.0, + "request_count": 64, + "request_metrics_sha256": "0b761b5f42e65b694eb34735433d43b473ed67415fb4e17b89894fc840b645fc", + "score": { + "feasible": false, + "pass_rate": 0.203125, + "passed": 13, + "throughput_requests_per_second": 9.07297385348011, + "ttft_max_ms": 5147.666503402199, + "ttft_p50_ms": 2739.3537469047046, + "ttft_p95_ms": 4962.713638565687 + }, + "status": "completed", + "trace_sha256": "5508a3ce590f7d1b0b3c211a2d4f2131886a7ae2d85cafa0c436a8e3029675f1" + }, + { + "config": { + "mns": 32, + "name": "tp1_mns32", + "tp": 1 + }, + "elapsed_seconds": 9.754717826843262, + "offered_request_rate": 64.0, + "offered_request_rate_per_gpu": 64.0, + "request_count": 64, + "request_metrics_sha256": "078209f2f56f21243384bdc70add105bf4942ee44ec8cc9573cad399a4a10e03", + "score": { + "feasible": false, + "pass_rate": 0.140625, + "passed": 9, + "throughput_requests_per_second": 9.07297385348011, + "ttft_max_ms": 6100.791503402199, + "ttft_p50_ms": 3208.1037469047046, + "ttft_p95_ms": 5853.338638565687 + }, + "status": "completed", + "trace_sha256": "8987c3ebd4a7db983dc0e99e44710cd44a9d5d21cdb8e6b58878b160918593d9" + } + ] + }, + { + "config": { + "mns": 64, + "name": "tp1_mns64", + "tp": 1 + }, + "loads": [ + { + "config": { + "mns": 64, + "name": "tp1_mns64", + "tp": 1 + }, + "elapsed_seconds": 9.84917163848877, + "offered_request_rate": 4.0, + "offered_request_rate_per_gpu": 4.0, + "request_count": 64, + "request_metrics_sha256": "f0cfb072afcc2fc82a92c59ddb4e8cfc1e3201e15848f37785eeb9fdd975e779", + "score": { + "feasible": true, + "pass_rate": 1.0, + "passed": 64, + "throughput_requests_per_second": 4.0197002243768605, + "ttft_max_ms": 171.58529929215405, + "ttft_p50_ms": 171.58529929215405, + "ttft_p95_ms": 171.58529929215405 + }, + "status": "completed", + "trace_sha256": "0cef631f351f5afd880172d0467d62d9813de5ba36fd3795f91afe3256450c05" + }, + { + "config": { + "mns": 64, + "name": "tp1_mns64", + "tp": 1 + }, + "elapsed_seconds": 10.197595357894897, + "offered_request_rate": 8.0, + "offered_request_rate_per_gpu": 8.0, + "request_count": 64, + "request_metrics_sha256": "cfebfbd8aa5d9603158d8ff4e786119433b4c0d705f733ba242da46a6a6fdfb0", + "score": { + "feasible": true, + "pass_rate": 1.0, + "passed": 64, + "throughput_requests_per_second": 7.742175153637853, + "ttft_max_ms": 570.3133207139617, + "ttft_p50_ms": 391.4107605377559, + "ttft_p95_ms": 516.4107605377559 + }, + "status": "completed", + "trace_sha256": "c49314cbe4e4b07a9335b57e861ec5b6541ea4cc061ca9abf923453276c419fd" + }, + { + "config": { + "mns": 64, + "name": "tp1_mns64", + "tp": 1 + }, + "elapsed_seconds": 10.00463604927063, + "offered_request_rate": 16.0, + "offered_request_rate_per_gpu": 16.0, + "request_count": 64, + "request_metrics_sha256": "03578ba406b3299e70ba7975e6b9872d381a5f4bd4fbc3c8f6a8ba49e9b53f2e", + "score": { + "feasible": false, + "pass_rate": 0.3125, + "passed": 20, + "throughput_requests_per_second": 8.916224898348922, + "ttft_max_ms": 3318.8404181099845, + "ttft_p50_ms": 1814.2305264490028, + "ttft_p95_ms": 3193.8404181099845 + }, + "status": "completed", + "trace_sha256": "2504b6340f764e726110e48251f8017a2f9ba67b44b8af7d3a838bf674823d33" + }, + { + "config": { + "mns": 64, + "name": "tp1_mns64", + "tp": 1 + }, + "elapsed_seconds": 10.153925895690918, + "offered_request_rate": 32.0, + "offered_request_rate_per_gpu": 32.0, + "request_count": 64, + "request_metrics_sha256": "0b761b5f42e65b694eb34735433d43b473ed67415fb4e17b89894fc840b645fc", + "score": { + "feasible": false, + "pass_rate": 0.203125, + "passed": 13, + "throughput_requests_per_second": 9.07297385348011, + "ttft_max_ms": 5147.666503402199, + "ttft_p50_ms": 2739.3537469047046, + "ttft_p95_ms": 4962.713638565687 + }, + "status": "completed", + "trace_sha256": "5508a3ce590f7d1b0b3c211a2d4f2131886a7ae2d85cafa0c436a8e3029675f1" + }, + { + "config": { + "mns": 64, + "name": "tp1_mns64", + "tp": 1 + }, + "elapsed_seconds": 10.055675268173218, + "offered_request_rate": 64.0, + "offered_request_rate_per_gpu": 64.0, + "request_count": 64, + "request_metrics_sha256": "078209f2f56f21243384bdc70add105bf4942ee44ec8cc9573cad399a4a10e03", + "score": { + "feasible": false, + "pass_rate": 0.140625, + "passed": 9, + "throughput_requests_per_second": 9.07297385348011, + "ttft_max_ms": 6100.791503402199, + "ttft_p50_ms": 3208.1037469047046, + "ttft_p95_ms": 5853.338638565687 + }, + "status": "completed", + "trace_sha256": "8987c3ebd4a7db983dc0e99e44710cd44a9d5d21cdb8e6b58878b160918593d9" + } + ] + } + ], + "contract": { + "arrival": "open_loop_uniform", + "input_tokens": 2048, + "output_tokens": 1, + "prefix_caching": false, + "rates": [ + 4.0, + 8.0, + 16.0, + 32.0, + 64.0 + ], + "requests_per_anchor": 64, + "target_pass_rate": 0.95, + "ttft_slo_ms": 1256.0 + }, + "frontier": { + "git_head": "d9cfeb6d8791fbf2f295dd9744c56a666171776e", + "git_status_short": " M frontier/config/config.py\n M frontier/entities/request.py\n M frontier/events/cluster_schedule_event.py\n M frontier/execution_time_predictor/sklearn_execution_time_predictor.py\n M frontier/metrics/constants.py\n M frontier/metrics/metrics_store.py\n M frontier/profiling/common/layers/rotary_embedding.py\n M frontier/profiling/moe/moe_impl.py\n M frontier/profiling/moe/moe_vllm_kernel.py\n M frontier/scheduler/cluster_scheduler/__init__.py\n M frontier/scheduler/cluster_scheduler/base_cluster_scheduler.py\n M frontier/scheduler/cluster_scheduler/cluster_scheduler_registry.py\n M frontier/scheduler/cluster_scheduler/sticky_lor_cluster_scheduler.py\n M frontier/scheduler/replica_scheduler/base_replica_scheduler.py\n M frontier/scheduler/replica_scheduler/vllm_v1_engine_replica_scheduler.py\n M frontier/scheduler/replica_stage_scheduler/replica_stage_schduler.py\n M frontier/simulator.py\n M frontier/types/cluster_scheduler_type.py\n?? data/profiling/compute/h20/\n?? frontier/scheduler/cluster_scheduler/prefix_lor_cluster_scheduler.py\n?? runs/\n?? tests/unit/test_attn_prefill_prediction_fallback.py\n", + "source": "/tmp/replayserve-frontier-rs1b" + }, + "profiles": { + "coverage": { + "attention": { + "1": { + "exact_prefill_2048_rows": 1, + "profile_batch_size": 1 + }, + "2": { + "exact_prefill_2048_rows": 1, + "profile_batch_size": 1 + }, + "4": { + "exact_prefill_2048_rows": 1, + "profile_batch_size": 1 + } + }, + "manifest": { + "attention_tp_coverage": [ + 1, + 2, + 4 + ], + "environment_contract": { + "dtype": "bfloat16", + "frontier_commit": "d9cfeb6d8791fbf2f295dd9744c56a666171776e", + "hardware": "NVIDIA H20", + "model": "Qwen3-30B-A3B", + "tensor_parallel_sizes": [ + 1, + 2, + 4 + ], + "vllm_source_commit": "88d34c6409e9fb3c7b8ca0c04756f061d2099eb1", + "vllm_version": "0.20.0" + }, + "inputs": { + "/home/gahow/phd/aituner/runs/frontier-qwen30-vllm020-profile-v1/fleet-artifacts/qwen30-vllm020-allreduce-full-tp2-20260716-v1-dispatch-aware-20260716T140743025781Z/artifacts/artifacts/allreduce-full-tp2-v1/raw/allreduce-tp2.json": "97c3c76b5a04e95bd9192423c2b891667c668f39cc0dfecbd097d749939f2d0a", + "/home/gahow/phd/aituner/runs/frontier-qwen30-vllm020-profile-v1/fleet-artifacts/qwen30-vllm020-allreduce-full-tp4-20260716-v1-dispatch-aware-20260716T141106009788Z/artifacts/artifacts/allreduce-full-tp4-v1/raw/allreduce-tp4.json": "809df9baa6f468cf12bf0c99827475acc67894dd9f3f948976590b665fac0e76", + "/home/gahow/phd/aituner/runs/frontier-qwen30-vllm020-profile-v1/fleet-artifacts/qwen30-vllm020-flashattn-kv-full-tp1-20260716-v2-20260716T135132587012Z/artifacts/artifacts/flashattn-kv-full-v2-tp1/raw/flashattn-tp1.json": "dcb4c1bf7e76b9c765f78ddd2b8a734f2d7ba2adac13ce017689a8a77fe69a27", + "/home/gahow/phd/aituner/runs/frontier-qwen30-vllm020-profile-v1/fleet-artifacts/qwen30-vllm020-flashattn-kv-full-tp2-20260716-v2-20260716T135134194295Z/artifacts/artifacts/flashattn-kv-full-v2-tp2/raw/flashattn-tp2.json": "43ce042556ba887c8860614b43ccf0f564e5cebc1a0cffbce299d0acb9fa8d07", + "/home/gahow/phd/aituner/runs/frontier-qwen30-vllm020-profile-v1/fleet-artifacts/qwen30-vllm020-flashattn-kv-full-tp4-20260716-v2-20260716T135135197200Z/artifacts/artifacts/flashattn-kv-full-v2-tp4/raw/flashattn-tp4.json": "84eef31bcad0f556907a093318a420959d14fdc94474823d11f659704bdfec73", + "/home/gahow/phd/aituner/runs/frontier-qwen30-vllm020-profile-v1/fleet-artifacts/qwen30-vllm020-frontier-linear-full-20260716-v2-max-tokens-20260716T144444676943Z/artifacts/artifacts/frontier-linear-full-v2/profiles/compute/h20/qwen3-a3b-30b-moe/linear_op.csv": "67666cb0a4901b74599d468df2e31bcaa2a11a7842cc0cefba24ffce62508e0c", + "/home/gahow/phd/aituner/runs/frontier-qwen30-vllm020-profile-v1/fleet-artifacts/qwen30-vllm020-moe-full-20260716-v1-local-shard-20260716T141334565164Z/artifacts/artifacts/moe-full-v1/raw/moe-full.json": "588f6ad0d69c9636d1b852e3df0a12d13cfe731f050ea7ec7aea457cceefbde8", + "/home/gahow/phd/aituner/runs/frontier-qwen30-vllm020-profile-v1/fleet-artifacts/qwen30-vllm020-router-full-20260716-v3-tp-context-20260716T145446098505Z/artifacts/artifacts/router-full-v3/raw/router.json": "1962972e983bff3e06a721ef4ae4ec65728ff669681497a4a7e7f769b88b4931" + }, + "outputs": { + "allreduce.json": "b38d14f990578d668523d25b107aceed433da5020d8ada3b6e44d3562261a3b3", + "attention.csv": "76dcb767cebb4ec1c4e24bd04d93ddd48b5d271986ebfb51a197ab33e1b3d87d", + "attention_true_mixed_fused.csv": "43ef4be90bddc9aeac6dbbe339feec24162cd1f2129a08fbd959e6ee4eaf5f60", + "linear_op.csv": "67666cb0a4901b74599d468df2e31bcaa2a11a7842cc0cefba24ffce62508e0c", + "moe.csv": "0e4dcba72918a1c4cf4e96ced31ee3829248a19ad54553cebef14417725808b0" + }, + "profile_id": "qwen3-30b-a3b-bf16-vllm020-h20-tp1-2-4-fused-mixed-total-conserving", + "projection_contract": { + "allreduce": "Frozen exact runtime measurements; base profile-only comparison keeps the historical Frontier CC backend fixed to isolate compute profile fidelity", + "attention": "Pure prefill/extend/decode FA3 core plus separately measured KV update; input/output reshape assumed zero; exported mean is used as median target; true mixed rows use a total-conserving compatibility projection", + "attention_true_mixed": "The directly measured fused total is preserved in diagnostics. Frontier's two targets are projected by the same-TP pure prefill/decode reference ratio, with projected prefill + decode exactly equal to the fused total; the split is a schema compatibility attribution, not an observation", + "linear": "Frontier profiler using vLLM 0.20 CUDA operators", + "moe": "Replicated gate and fused top-k plus TP-local modular expert kernel; expert measurement already includes prepare/finalize so shuffling is zero" + }, + "row_counts": { + "allreduce": 24, + "attention_frontier_compatible": 132, + "attention_true_mixed_fused_diagnostic": 30, + "linear": 36, + "moe": 72 + }, + "schema_version": "frontier_qwen30_vllm020_frozen_profile.v2" + } + }, + "root": "/home/gahow/phd/aituner/runs/frontier-qwen30-vllm020-profile-v1/frozen/profile-v2", + "sha256": { + "attention": "76dcb767cebb4ec1c4e24bd04d93ddd48b5d271986ebfb51a197ab33e1b3d87d", + "linear": "67666cb0a4901b74599d468df2e31bcaa2a11a7842cc0cefba24ffce62508e0c", + "manifest": "af40545e75aff55c6333cd2d5379ccf042a5a0b7d7fc7df4f745ce256cb290eb", + "moe": "0e4dcba72918a1c4cf4e96ced31ee3829248a19ad54553cebef14417725808b0" + } + }, + "schema": "frontier-qwen30-prefill-surface-v1", + "status": "partial_not_decision_bearing" +} diff --git a/runs/frontier-phase-factorial-v0/simulator-tp2/frontier_surface_frozen.json b/runs/frontier-phase-factorial-v0/simulator-tp2/frontier_surface_frozen.json new file mode 100644 index 0000000..34e2063 --- /dev/null +++ b/runs/frontier-phase-factorial-v0/simulator-tp2/frontier_surface_frozen.json @@ -0,0 +1,647 @@ +{ + "capacity": [ + { + "config": { + "mns": 16, + "name": "tp2_mns16", + "tp": 2 + }, + "lower_censored": false, + "maximum_tested_feasible_request_rate": 16.0, + "maximum_tested_feasible_request_rate_per_gpu": 8.0, + "upper_censored": false + }, + { + "config": { + "mns": 32, + "name": "tp2_mns32", + "tp": 2 + }, + "lower_censored": false, + "maximum_tested_feasible_request_rate": 16.0, + "maximum_tested_feasible_request_rate_per_gpu": 8.0, + "upper_censored": false + }, + { + "config": { + "mns": 64, + "name": "tp2_mns64", + "tp": 2 + }, + "lower_censored": false, + "maximum_tested_feasible_request_rate": 16.0, + "maximum_tested_feasible_request_rate_per_gpu": 8.0, + "upper_censored": false + }, + { + "config": { + "mns": 8, + "name": "tp2_mns8", + "tp": 2 + }, + "lower_censored": false, + "maximum_tested_feasible_request_rate": 16.0, + "maximum_tested_feasible_request_rate_per_gpu": 8.0, + "upper_censored": false + } + ], + "config_results": [ + { + "config": { + "mns": 8, + "name": "tp2_mns8", + "tp": 2 + }, + "loads": [ + { + "config": { + "mns": 8, + "name": "tp2_mns8", + "tp": 2 + }, + "elapsed_seconds": 264.1678559780121, + "offered_request_rate": 4.0, + "offered_request_rate_per_gpu": 2.0, + "request_count": 64, + "request_metrics_sha256": "e5f6527532682ee5aca3f423367d14eba063f30e63f46d2ba0ed602db4f6eb17", + "score": { + "feasible": true, + "pass_rate": 1.0, + "passed": 64, + "throughput_requests_per_second": 4.032196934357252, + "ttft_max_ms": 122.24062760264154, + "ttft_p50_ms": 122.24062760264154, + "ttft_p95_ms": 122.24062760264154 + }, + "status": "completed", + "trace_sha256": "0cef631f351f5afd880172d0467d62d9813de5ba36fd3795f91afe3256450c05" + }, + { + "config": { + "mns": 8, + "name": "tp2_mns8", + "tp": 2 + }, + "elapsed_seconds": 10.15904188156128, + "offered_request_rate": 8.0, + "offered_request_rate_per_gpu": 4.0, + "request_count": 64, + "request_metrics_sha256": "8dd3bedcbf76d30bf4285e10b886f95bef9ae8e5d41f4bb50a7650b322b8eb83", + "score": { + "feasible": true, + "pass_rate": 1.0, + "passed": 64, + "throughput_requests_per_second": 8.00276032449276, + "ttft_max_ms": 122.24062760264154, + "ttft_p50_ms": 122.24062760264154, + "ttft_p95_ms": 122.24062760264154 + }, + "status": "completed", + "trace_sha256": "c49314cbe4e4b07a9335b57e861ec5b6541ea4cc061ca9abf923453276c419fd" + }, + { + "config": { + "mns": 8, + "name": "tp2_mns8", + "tp": 2 + }, + "elapsed_seconds": 10.447325944900513, + "offered_request_rate": 16.0, + "offered_request_rate_per_gpu": 8.0, + "request_count": 64, + "request_metrics_sha256": "38c1b13c1f5fbdabd7f79b82ccb1fb37fce6840bcf03614c988d7e4882814506", + "score": { + "feasible": true, + "pass_rate": 1.0, + "passed": 64, + "throughput_requests_per_second": 13.174042777348715, + "ttft_max_ms": 1048.2973400621365, + "ttft_p50_ms": 643.9195307403329, + "ttft_p95_ms": 962.8278877316862 + }, + "status": "completed", + "trace_sha256": "2504b6340f764e726110e48251f8017a2f9ba67b44b8af7d3a838bf674823d33" + }, + { + "config": { + "mns": 8, + "name": "tp2_mns8", + "tp": 2 + }, + "elapsed_seconds": 10.053350687026978, + "offered_request_rate": 32.0, + "offered_request_rate_per_gpu": 16.0, + "request_count": 64, + "request_metrics_sha256": "cb3b7b5a2fcf63cd01304b3b530bb21c0a9bb1c8f5e06d489e3863afd0b4d2f3", + "score": { + "feasible": false, + "pass_rate": 0.390625, + "passed": 25, + "throughput_requests_per_second": 13.539443740959392, + "ttft_max_ms": 2851.9297930156335, + "ttft_p50_ms": 1510.0519836938302, + "ttft_p95_ms": 2758.1797930156335 + }, + "status": "completed", + "trace_sha256": "5508a3ce590f7d1b0b3c211a2d4f2131886a7ae2d85cafa0c436a8e3029675f1" + }, + { + "config": { + "mns": 8, + "name": "tp2_mns8", + "tp": 2 + }, + "elapsed_seconds": 9.744416952133179, + "offered_request_rate": 64.0, + "offered_request_rate_per_gpu": 32.0, + "request_count": 64, + "request_metrics_sha256": "a4474c98a68171ada0fb007c36fcf69e9e2dc13f06c74889591e23f93418bd5a", + "score": { + "feasible": false, + "pass_rate": 0.265625, + "passed": 17, + "throughput_requests_per_second": 13.539443740959392, + "ttft_max_ms": 3773.8047930156335, + "ttft_p50_ms": 1995.3684369244447, + "ttft_p95_ms": 3622.636520081023 + }, + "status": "completed", + "trace_sha256": "8987c3ebd4a7db983dc0e99e44710cd44a9d5d21cdb8e6b58878b160918593d9" + } + ] + }, + { + "config": { + "mns": 16, + "name": "tp2_mns16", + "tp": 2 + }, + "loads": [ + { + "config": { + "mns": 16, + "name": "tp2_mns16", + "tp": 2 + }, + "elapsed_seconds": 9.402071952819824, + "offered_request_rate": 4.0, + "offered_request_rate_per_gpu": 2.0, + "request_count": 64, + "request_metrics_sha256": "e5f6527532682ee5aca3f423367d14eba063f30e63f46d2ba0ed602db4f6eb17", + "score": { + "feasible": true, + "pass_rate": 1.0, + "passed": 64, + "throughput_requests_per_second": 4.032196934357252, + "ttft_max_ms": 122.24062760264154, + "ttft_p50_ms": 122.24062760264154, + "ttft_p95_ms": 122.24062760264154 + }, + "status": "completed", + "trace_sha256": "0cef631f351f5afd880172d0467d62d9813de5ba36fd3795f91afe3256450c05" + }, + { + "config": { + "mns": 16, + "name": "tp2_mns16", + "tp": 2 + }, + "elapsed_seconds": 9.398229360580444, + "offered_request_rate": 8.0, + "offered_request_rate_per_gpu": 4.0, + "request_count": 64, + "request_metrics_sha256": "8dd3bedcbf76d30bf4285e10b886f95bef9ae8e5d41f4bb50a7650b322b8eb83", + "score": { + "feasible": true, + "pass_rate": 1.0, + "passed": 64, + "throughput_requests_per_second": 8.00276032449276, + "ttft_max_ms": 122.24062760264154, + "ttft_p50_ms": 122.24062760264154, + "ttft_p95_ms": 122.24062760264154 + }, + "status": "completed", + "trace_sha256": "c49314cbe4e4b07a9335b57e861ec5b6541ea4cc061ca9abf923453276c419fd" + }, + { + "config": { + "mns": 16, + "name": "tp2_mns16", + "tp": 2 + }, + "elapsed_seconds": 9.70562195777893, + "offered_request_rate": 16.0, + "offered_request_rate_per_gpu": 8.0, + "request_count": 64, + "request_metrics_sha256": "38c1b13c1f5fbdabd7f79b82ccb1fb37fce6840bcf03614c988d7e4882814506", + "score": { + "feasible": true, + "pass_rate": 1.0, + "passed": 64, + "throughput_requests_per_second": 13.174042777348715, + "ttft_max_ms": 1048.2973400621365, + "ttft_p50_ms": 643.9195307403329, + "ttft_p95_ms": 962.8278877316862 + }, + "status": "completed", + "trace_sha256": "2504b6340f764e726110e48251f8017a2f9ba67b44b8af7d3a838bf674823d33" + }, + { + "config": { + "mns": 16, + "name": "tp2_mns16", + "tp": 2 + }, + "elapsed_seconds": 9.598989009857178, + "offered_request_rate": 32.0, + "offered_request_rate_per_gpu": 16.0, + "request_count": 64, + "request_metrics_sha256": "cb3b7b5a2fcf63cd01304b3b530bb21c0a9bb1c8f5e06d489e3863afd0b4d2f3", + "score": { + "feasible": false, + "pass_rate": 0.390625, + "passed": 25, + "throughput_requests_per_second": 13.539443740959392, + "ttft_max_ms": 2851.9297930156335, + "ttft_p50_ms": 1510.0519836938302, + "ttft_p95_ms": 2758.1797930156335 + }, + "status": "completed", + "trace_sha256": "5508a3ce590f7d1b0b3c211a2d4f2131886a7ae2d85cafa0c436a8e3029675f1" + }, + { + "config": { + "mns": 16, + "name": "tp2_mns16", + "tp": 2 + }, + "elapsed_seconds": 9.697861671447754, + "offered_request_rate": 64.0, + "offered_request_rate_per_gpu": 32.0, + "request_count": 64, + "request_metrics_sha256": "a4474c98a68171ada0fb007c36fcf69e9e2dc13f06c74889591e23f93418bd5a", + "score": { + "feasible": false, + "pass_rate": 0.265625, + "passed": 17, + "throughput_requests_per_second": 13.539443740959392, + "ttft_max_ms": 3773.8047930156335, + "ttft_p50_ms": 1995.3684369244447, + "ttft_p95_ms": 3622.636520081023 + }, + "status": "completed", + "trace_sha256": "8987c3ebd4a7db983dc0e99e44710cd44a9d5d21cdb8e6b58878b160918593d9" + } + ] + }, + { + "config": { + "mns": 32, + "name": "tp2_mns32", + "tp": 2 + }, + "loads": [ + { + "config": { + "mns": 32, + "name": "tp2_mns32", + "tp": 2 + }, + "elapsed_seconds": 9.397430419921875, + "offered_request_rate": 4.0, + "offered_request_rate_per_gpu": 2.0, + "request_count": 64, + "request_metrics_sha256": "e5f6527532682ee5aca3f423367d14eba063f30e63f46d2ba0ed602db4f6eb17", + "score": { + "feasible": true, + "pass_rate": 1.0, + "passed": 64, + "throughput_requests_per_second": 4.032196934357252, + "ttft_max_ms": 122.24062760264154, + "ttft_p50_ms": 122.24062760264154, + "ttft_p95_ms": 122.24062760264154 + }, + "status": "completed", + "trace_sha256": "0cef631f351f5afd880172d0467d62d9813de5ba36fd3795f91afe3256450c05" + }, + { + "config": { + "mns": 32, + "name": "tp2_mns32", + "tp": 2 + }, + "elapsed_seconds": 9.3956880569458, + "offered_request_rate": 8.0, + "offered_request_rate_per_gpu": 4.0, + "request_count": 64, + "request_metrics_sha256": "8dd3bedcbf76d30bf4285e10b886f95bef9ae8e5d41f4bb50a7650b322b8eb83", + "score": { + "feasible": true, + "pass_rate": 1.0, + "passed": 64, + "throughput_requests_per_second": 8.00276032449276, + "ttft_max_ms": 122.24062760264154, + "ttft_p50_ms": 122.24062760264154, + "ttft_p95_ms": 122.24062760264154 + }, + "status": "completed", + "trace_sha256": "c49314cbe4e4b07a9335b57e861ec5b6541ea4cc061ca9abf923453276c419fd" + }, + { + "config": { + "mns": 32, + "name": "tp2_mns32", + "tp": 2 + }, + "elapsed_seconds": 9.647802591323853, + "offered_request_rate": 16.0, + "offered_request_rate_per_gpu": 8.0, + "request_count": 64, + "request_metrics_sha256": "38c1b13c1f5fbdabd7f79b82ccb1fb37fce6840bcf03614c988d7e4882814506", + "score": { + "feasible": true, + "pass_rate": 1.0, + "passed": 64, + "throughput_requests_per_second": 13.174042777348715, + "ttft_max_ms": 1048.2973400621365, + "ttft_p50_ms": 643.9195307403329, + "ttft_p95_ms": 962.8278877316862 + }, + "status": "completed", + "trace_sha256": "2504b6340f764e726110e48251f8017a2f9ba67b44b8af7d3a838bf674823d33" + }, + { + "config": { + "mns": 32, + "name": "tp2_mns32", + "tp": 2 + }, + "elapsed_seconds": 9.694743156433105, + "offered_request_rate": 32.0, + "offered_request_rate_per_gpu": 16.0, + "request_count": 64, + "request_metrics_sha256": "cb3b7b5a2fcf63cd01304b3b530bb21c0a9bb1c8f5e06d489e3863afd0b4d2f3", + "score": { + "feasible": false, + "pass_rate": 0.390625, + "passed": 25, + "throughput_requests_per_second": 13.539443740959392, + "ttft_max_ms": 2851.9297930156335, + "ttft_p50_ms": 1510.0519836938302, + "ttft_p95_ms": 2758.1797930156335 + }, + "status": "completed", + "trace_sha256": "5508a3ce590f7d1b0b3c211a2d4f2131886a7ae2d85cafa0c436a8e3029675f1" + }, + { + "config": { + "mns": 32, + "name": "tp2_mns32", + "tp": 2 + }, + "elapsed_seconds": 9.594672203063965, + "offered_request_rate": 64.0, + "offered_request_rate_per_gpu": 32.0, + "request_count": 64, + "request_metrics_sha256": "a4474c98a68171ada0fb007c36fcf69e9e2dc13f06c74889591e23f93418bd5a", + "score": { + "feasible": false, + "pass_rate": 0.265625, + "passed": 17, + "throughput_requests_per_second": 13.539443740959392, + "ttft_max_ms": 3773.8047930156335, + "ttft_p50_ms": 1995.3684369244447, + "ttft_p95_ms": 3622.636520081023 + }, + "status": "completed", + "trace_sha256": "8987c3ebd4a7db983dc0e99e44710cd44a9d5d21cdb8e6b58878b160918593d9" + } + ] + }, + { + "config": { + "mns": 64, + "name": "tp2_mns64", + "tp": 2 + }, + "loads": [ + { + "config": { + "mns": 64, + "name": "tp2_mns64", + "tp": 2 + }, + "elapsed_seconds": 9.398636817932129, + "offered_request_rate": 4.0, + "offered_request_rate_per_gpu": 2.0, + "request_count": 64, + "request_metrics_sha256": "e5f6527532682ee5aca3f423367d14eba063f30e63f46d2ba0ed602db4f6eb17", + "score": { + "feasible": true, + "pass_rate": 1.0, + "passed": 64, + "throughput_requests_per_second": 4.032196934357252, + "ttft_max_ms": 122.24062760264154, + "ttft_p50_ms": 122.24062760264154, + "ttft_p95_ms": 122.24062760264154 + }, + "status": "completed", + "trace_sha256": "0cef631f351f5afd880172d0467d62d9813de5ba36fd3795f91afe3256450c05" + }, + { + "config": { + "mns": 64, + "name": "tp2_mns64", + "tp": 2 + }, + "elapsed_seconds": 9.495105981826782, + "offered_request_rate": 8.0, + "offered_request_rate_per_gpu": 4.0, + "request_count": 64, + "request_metrics_sha256": "8dd3bedcbf76d30bf4285e10b886f95bef9ae8e5d41f4bb50a7650b322b8eb83", + "score": { + "feasible": true, + "pass_rate": 1.0, + "passed": 64, + "throughput_requests_per_second": 8.00276032449276, + "ttft_max_ms": 122.24062760264154, + "ttft_p50_ms": 122.24062760264154, + "ttft_p95_ms": 122.24062760264154 + }, + "status": "completed", + "trace_sha256": "c49314cbe4e4b07a9335b57e861ec5b6541ea4cc061ca9abf923453276c419fd" + }, + { + "config": { + "mns": 64, + "name": "tp2_mns64", + "tp": 2 + }, + "elapsed_seconds": 9.54387378692627, + "offered_request_rate": 16.0, + "offered_request_rate_per_gpu": 8.0, + "request_count": 64, + "request_metrics_sha256": "38c1b13c1f5fbdabd7f79b82ccb1fb37fce6840bcf03614c988d7e4882814506", + "score": { + "feasible": true, + "pass_rate": 1.0, + "passed": 64, + "throughput_requests_per_second": 13.174042777348715, + "ttft_max_ms": 1048.2973400621365, + "ttft_p50_ms": 643.9195307403329, + "ttft_p95_ms": 962.8278877316862 + }, + "status": "completed", + "trace_sha256": "2504b6340f764e726110e48251f8017a2f9ba67b44b8af7d3a838bf674823d33" + }, + { + "config": { + "mns": 64, + "name": "tp2_mns64", + "tp": 2 + }, + "elapsed_seconds": 9.192227840423584, + "offered_request_rate": 32.0, + "offered_request_rate_per_gpu": 16.0, + "request_count": 64, + "request_metrics_sha256": "cb3b7b5a2fcf63cd01304b3b530bb21c0a9bb1c8f5e06d489e3863afd0b4d2f3", + "score": { + "feasible": false, + "pass_rate": 0.390625, + "passed": 25, + "throughput_requests_per_second": 13.539443740959392, + "ttft_max_ms": 2851.9297930156335, + "ttft_p50_ms": 1510.0519836938302, + "ttft_p95_ms": 2758.1797930156335 + }, + "status": "completed", + "trace_sha256": "5508a3ce590f7d1b0b3c211a2d4f2131886a7ae2d85cafa0c436a8e3029675f1" + }, + { + "config": { + "mns": 64, + "name": "tp2_mns64", + "tp": 2 + }, + "elapsed_seconds": 9.294084072113037, + "offered_request_rate": 64.0, + "offered_request_rate_per_gpu": 32.0, + "request_count": 64, + "request_metrics_sha256": "a4474c98a68171ada0fb007c36fcf69e9e2dc13f06c74889591e23f93418bd5a", + "score": { + "feasible": false, + "pass_rate": 0.265625, + "passed": 17, + "throughput_requests_per_second": 13.539443740959392, + "ttft_max_ms": 3773.8047930156335, + "ttft_p50_ms": 1995.3684369244447, + "ttft_p95_ms": 3622.636520081023 + }, + "status": "completed", + "trace_sha256": "8987c3ebd4a7db983dc0e99e44710cd44a9d5d21cdb8e6b58878b160918593d9" + } + ] + } + ], + "contract": { + "arrival": "open_loop_uniform", + "input_tokens": 2048, + "output_tokens": 1, + "prefix_caching": false, + "rates": [ + 4.0, + 8.0, + 16.0, + 32.0, + 64.0 + ], + "requests_per_anchor": 64, + "target_pass_rate": 0.95, + "ttft_slo_ms": 1256.0 + }, + "frontier": { + "git_head": "d9cfeb6d8791fbf2f295dd9744c56a666171776e", + "git_status_short": " M frontier/config/config.py\n M frontier/entities/request.py\n M frontier/events/cluster_schedule_event.py\n M frontier/execution_time_predictor/sklearn_execution_time_predictor.py\n M frontier/metrics/constants.py\n M frontier/metrics/metrics_store.py\n M frontier/profiling/common/layers/rotary_embedding.py\n M frontier/profiling/moe/moe_impl.py\n M frontier/profiling/moe/moe_vllm_kernel.py\n M frontier/scheduler/cluster_scheduler/__init__.py\n M frontier/scheduler/cluster_scheduler/base_cluster_scheduler.py\n M frontier/scheduler/cluster_scheduler/cluster_scheduler_registry.py\n M frontier/scheduler/cluster_scheduler/sticky_lor_cluster_scheduler.py\n M frontier/scheduler/replica_scheduler/base_replica_scheduler.py\n M frontier/scheduler/replica_scheduler/vllm_v1_engine_replica_scheduler.py\n M frontier/scheduler/replica_stage_scheduler/replica_stage_schduler.py\n M frontier/simulator.py\n M frontier/types/cluster_scheduler_type.py\n?? data/profiling/compute/h20/\n?? frontier/scheduler/cluster_scheduler/prefix_lor_cluster_scheduler.py\n?? runs/\n?? tests/unit/test_attn_prefill_prediction_fallback.py\n", + "source": "/tmp/replayserve-frontier-rs1b" + }, + "profiles": { + "coverage": { + "attention": { + "1": { + "exact_prefill_2048_rows": 1, + "profile_batch_size": 1 + }, + "2": { + "exact_prefill_2048_rows": 1, + "profile_batch_size": 1 + }, + "4": { + "exact_prefill_2048_rows": 1, + "profile_batch_size": 1 + } + }, + "manifest": { + "attention_tp_coverage": [ + 1, + 2, + 4 + ], + "environment_contract": { + "dtype": "bfloat16", + "frontier_commit": "d9cfeb6d8791fbf2f295dd9744c56a666171776e", + "hardware": "NVIDIA H20", + "model": "Qwen3-30B-A3B", + "tensor_parallel_sizes": [ + 1, + 2, + 4 + ], + "vllm_source_commit": "88d34c6409e9fb3c7b8ca0c04756f061d2099eb1", + "vllm_version": "0.20.0" + }, + "inputs": { + "/home/gahow/phd/aituner/runs/frontier-qwen30-vllm020-profile-v1/fleet-artifacts/qwen30-vllm020-allreduce-full-tp2-20260716-v1-dispatch-aware-20260716T140743025781Z/artifacts/artifacts/allreduce-full-tp2-v1/raw/allreduce-tp2.json": "97c3c76b5a04e95bd9192423c2b891667c668f39cc0dfecbd097d749939f2d0a", + "/home/gahow/phd/aituner/runs/frontier-qwen30-vllm020-profile-v1/fleet-artifacts/qwen30-vllm020-allreduce-full-tp4-20260716-v1-dispatch-aware-20260716T141106009788Z/artifacts/artifacts/allreduce-full-tp4-v1/raw/allreduce-tp4.json": "809df9baa6f468cf12bf0c99827475acc67894dd9f3f948976590b665fac0e76", + "/home/gahow/phd/aituner/runs/frontier-qwen30-vllm020-profile-v1/fleet-artifacts/qwen30-vllm020-flashattn-kv-full-tp1-20260716-v2-20260716T135132587012Z/artifacts/artifacts/flashattn-kv-full-v2-tp1/raw/flashattn-tp1.json": "dcb4c1bf7e76b9c765f78ddd2b8a734f2d7ba2adac13ce017689a8a77fe69a27", + "/home/gahow/phd/aituner/runs/frontier-qwen30-vllm020-profile-v1/fleet-artifacts/qwen30-vllm020-flashattn-kv-full-tp2-20260716-v2-20260716T135134194295Z/artifacts/artifacts/flashattn-kv-full-v2-tp2/raw/flashattn-tp2.json": "43ce042556ba887c8860614b43ccf0f564e5cebc1a0cffbce299d0acb9fa8d07", + "/home/gahow/phd/aituner/runs/frontier-qwen30-vllm020-profile-v1/fleet-artifacts/qwen30-vllm020-flashattn-kv-full-tp4-20260716-v2-20260716T135135197200Z/artifacts/artifacts/flashattn-kv-full-v2-tp4/raw/flashattn-tp4.json": "84eef31bcad0f556907a093318a420959d14fdc94474823d11f659704bdfec73", + "/home/gahow/phd/aituner/runs/frontier-qwen30-vllm020-profile-v1/fleet-artifacts/qwen30-vllm020-frontier-linear-full-20260716-v2-max-tokens-20260716T144444676943Z/artifacts/artifacts/frontier-linear-full-v2/profiles/compute/h20/qwen3-a3b-30b-moe/linear_op.csv": "67666cb0a4901b74599d468df2e31bcaa2a11a7842cc0cefba24ffce62508e0c", + "/home/gahow/phd/aituner/runs/frontier-qwen30-vllm020-profile-v1/fleet-artifacts/qwen30-vllm020-moe-full-20260716-v1-local-shard-20260716T141334565164Z/artifacts/artifacts/moe-full-v1/raw/moe-full.json": "588f6ad0d69c9636d1b852e3df0a12d13cfe731f050ea7ec7aea457cceefbde8", + "/home/gahow/phd/aituner/runs/frontier-qwen30-vllm020-profile-v1/fleet-artifacts/qwen30-vllm020-router-full-20260716-v3-tp-context-20260716T145446098505Z/artifacts/artifacts/router-full-v3/raw/router.json": "1962972e983bff3e06a721ef4ae4ec65728ff669681497a4a7e7f769b88b4931" + }, + "outputs": { + "allreduce.json": "b38d14f990578d668523d25b107aceed433da5020d8ada3b6e44d3562261a3b3", + "attention.csv": "76dcb767cebb4ec1c4e24bd04d93ddd48b5d271986ebfb51a197ab33e1b3d87d", + "attention_true_mixed_fused.csv": "43ef4be90bddc9aeac6dbbe339feec24162cd1f2129a08fbd959e6ee4eaf5f60", + "linear_op.csv": "67666cb0a4901b74599d468df2e31bcaa2a11a7842cc0cefba24ffce62508e0c", + "moe.csv": "0e4dcba72918a1c4cf4e96ced31ee3829248a19ad54553cebef14417725808b0" + }, + "profile_id": "qwen3-30b-a3b-bf16-vllm020-h20-tp1-2-4-fused-mixed-total-conserving", + "projection_contract": { + "allreduce": "Frozen exact runtime measurements; base profile-only comparison keeps the historical Frontier CC backend fixed to isolate compute profile fidelity", + "attention": "Pure prefill/extend/decode FA3 core plus separately measured KV update; input/output reshape assumed zero; exported mean is used as median target; true mixed rows use a total-conserving compatibility projection", + "attention_true_mixed": "The directly measured fused total is preserved in diagnostics. Frontier's two targets are projected by the same-TP pure prefill/decode reference ratio, with projected prefill + decode exactly equal to the fused total; the split is a schema compatibility attribution, not an observation", + "linear": "Frontier profiler using vLLM 0.20 CUDA operators", + "moe": "Replicated gate and fused top-k plus TP-local modular expert kernel; expert measurement already includes prepare/finalize so shuffling is zero" + }, + "row_counts": { + "allreduce": 24, + "attention_frontier_compatible": 132, + "attention_true_mixed_fused_diagnostic": 30, + "linear": 36, + "moe": 72 + }, + "schema_version": "frontier_qwen30_vllm020_frozen_profile.v2" + } + }, + "root": "/home/gahow/phd/aituner/runs/frontier-qwen30-vllm020-profile-v1/frozen/profile-v2", + "sha256": { + "attention": "76dcb767cebb4ec1c4e24bd04d93ddd48b5d271986ebfb51a197ab33e1b3d87d", + "linear": "67666cb0a4901b74599d468df2e31bcaa2a11a7842cc0cefba24ffce62508e0c", + "manifest": "af40545e75aff55c6333cd2d5379ccf042a5a0b7d7fc7df4f745ce256cb290eb", + "moe": "0e4dcba72918a1c4cf4e96ced31ee3829248a19ad54553cebef14417725808b0" + } + }, + "schema": "frontier-qwen30-prefill-surface-v1", + "status": "partial_not_decision_bearing" +} diff --git a/runs/frontier-phase-factorial-v0/simulator-tp4/frontier_surface_frozen.json b/runs/frontier-phase-factorial-v0/simulator-tp4/frontier_surface_frozen.json new file mode 100644 index 0000000..82a1227 --- /dev/null +++ b/runs/frontier-phase-factorial-v0/simulator-tp4/frontier_surface_frozen.json @@ -0,0 +1,647 @@ +{ + "capacity": [ + { + "config": { + "mns": 16, + "name": "tp4_mns16", + "tp": 4 + }, + "lower_censored": false, + "maximum_tested_feasible_request_rate": 16.0, + "maximum_tested_feasible_request_rate_per_gpu": 4.0, + "upper_censored": false + }, + { + "config": { + "mns": 32, + "name": "tp4_mns32", + "tp": 4 + }, + "lower_censored": false, + "maximum_tested_feasible_request_rate": 16.0, + "maximum_tested_feasible_request_rate_per_gpu": 4.0, + "upper_censored": false + }, + { + "config": { + "mns": 64, + "name": "tp4_mns64", + "tp": 4 + }, + "lower_censored": false, + "maximum_tested_feasible_request_rate": 16.0, + "maximum_tested_feasible_request_rate_per_gpu": 4.0, + "upper_censored": false + }, + { + "config": { + "mns": 8, + "name": "tp4_mns8", + "tp": 4 + }, + "lower_censored": false, + "maximum_tested_feasible_request_rate": 16.0, + "maximum_tested_feasible_request_rate_per_gpu": 4.0, + "upper_censored": false + } + ], + "config_results": [ + { + "config": { + "mns": 8, + "name": "tp4_mns8", + "tp": 4 + }, + "loads": [ + { + "config": { + "mns": 8, + "name": "tp4_mns8", + "tp": 4 + }, + "elapsed_seconds": 244.95408463478088, + "offered_request_rate": 4.0, + "offered_request_rate_per_gpu": 1.0, + "request_count": 64, + "request_metrics_sha256": "c0c3c34bea43dae2477b10152b07632e5d03968f47cdfd70ad5b676e2502af4c", + "score": { + "feasible": true, + "pass_rate": 1.0, + "passed": 64, + "throughput_requests_per_second": 4.039607508408214, + "ttft_max_ms": 93.12333977685495, + "ttft_p50_ms": 93.12333977685405, + "ttft_p95_ms": 93.12333977685495 + }, + "status": "completed", + "trace_sha256": "0cef631f351f5afd880172d0467d62d9813de5ba36fd3795f91afe3256450c05" + }, + { + "config": { + "mns": 8, + "name": "tp4_mns8", + "tp": 4 + }, + "elapsed_seconds": 9.85124945640564, + "offered_request_rate": 8.0, + "offered_request_rate_per_gpu": 2.0, + "request_count": 64, + "request_metrics_sha256": "b5c6ba0cd3b2a81a12fb93b2cad6f80341f9352c110385046490f59d7933a5d5", + "score": { + "feasible": true, + "pass_rate": 1.0, + "passed": 64, + "throughput_requests_per_second": 8.032004183533672, + "ttft_max_ms": 93.12333977685495, + "ttft_p50_ms": 93.12333977685495, + "ttft_p95_ms": 93.12333977685495 + }, + "status": "completed", + "trace_sha256": "c49314cbe4e4b07a9335b57e861ec5b6541ea4cc061ca9abf923453276c419fd" + }, + { + "config": { + "mns": 8, + "name": "tp4_mns8", + "tp": 4 + }, + "elapsed_seconds": 9.70260500907898, + "offered_request_rate": 16.0, + "offered_request_rate_per_gpu": 4.0, + "request_count": 64, + "request_metrics_sha256": "c40f10c895f2fc45671f7ff7c6c08f30c20854f0ff4fe3e6719934a7b08b0112", + "score": { + "feasible": true, + "pass_rate": 1.0, + "passed": 64, + "throughput_requests_per_second": 15.412041282697286, + "ttft_max_ms": 314.4210260695206, + "ttft_p50_ms": 239.37774488651664, + "ttft_p95_ms": 309.403713596319 + }, + "status": "completed", + "trace_sha256": "2504b6340f764e726110e48251f8017a2f9ba67b44b8af7d3a838bf674823d33" + }, + { + "config": { + "mns": 8, + "name": "tp4_mns8", + "tp": 4 + }, + "elapsed_seconds": 9.855336904525757, + "offered_request_rate": 32.0, + "offered_request_rate_per_gpu": 8.0, + "request_count": 64, + "request_metrics_sha256": "9f77ee40d102bdf14d213132ef10b4a7f4175908ffee411ef30ad3a5f1e4b16f", + "score": { + "feasible": false, + "pass_rate": 0.625, + "passed": 40, + "throughput_requests_per_second": 16.898601280793688, + "ttft_max_ms": 1850.42236218991, + "ttft_p50_ms": 1017.6783445024347, + "ttft_p95_ms": 1787.92236218991 + }, + "status": "completed", + "trace_sha256": "5508a3ce590f7d1b0b3c211a2d4f2131886a7ae2d85cafa0c436a8e3029675f1" + }, + { + "config": { + "mns": 8, + "name": "tp4_mns8", + "tp": 4 + }, + "elapsed_seconds": 9.947782754898071, + "offered_request_rate": 64.0, + "offered_request_rate_per_gpu": 16.0, + "request_count": 64, + "request_metrics_sha256": "efb43cc32f2f7129082e47d89239f08b10c6e7e6dd4a4f0b299d2364cb781af0", + "score": { + "feasible": false, + "pass_rate": 0.390625, + "passed": 25, + "throughput_requests_per_second": 17.184897883039092, + "ttft_max_ms": 2771.0751922610093, + "ttft_p50_ms": 1468.937931419684, + "ttft_p95_ms": 2661.056949107159 + }, + "status": "completed", + "trace_sha256": "8987c3ebd4a7db983dc0e99e44710cd44a9d5d21cdb8e6b58878b160918593d9" + } + ] + }, + { + "config": { + "mns": 16, + "name": "tp4_mns16", + "tp": 4 + }, + "loads": [ + { + "config": { + "mns": 16, + "name": "tp4_mns16", + "tp": 4 + }, + "elapsed_seconds": 9.648890733718872, + "offered_request_rate": 4.0, + "offered_request_rate_per_gpu": 1.0, + "request_count": 64, + "request_metrics_sha256": "c0c3c34bea43dae2477b10152b07632e5d03968f47cdfd70ad5b676e2502af4c", + "score": { + "feasible": true, + "pass_rate": 1.0, + "passed": 64, + "throughput_requests_per_second": 4.039607508408214, + "ttft_max_ms": 93.12333977685495, + "ttft_p50_ms": 93.12333977685405, + "ttft_p95_ms": 93.12333977685495 + }, + "status": "completed", + "trace_sha256": "0cef631f351f5afd880172d0467d62d9813de5ba36fd3795f91afe3256450c05" + }, + { + "config": { + "mns": 16, + "name": "tp4_mns16", + "tp": 4 + }, + "elapsed_seconds": 9.353798151016235, + "offered_request_rate": 8.0, + "offered_request_rate_per_gpu": 2.0, + "request_count": 64, + "request_metrics_sha256": "b5c6ba0cd3b2a81a12fb93b2cad6f80341f9352c110385046490f59d7933a5d5", + "score": { + "feasible": true, + "pass_rate": 1.0, + "passed": 64, + "throughput_requests_per_second": 8.032004183533672, + "ttft_max_ms": 93.12333977685495, + "ttft_p50_ms": 93.12333977685495, + "ttft_p95_ms": 93.12333977685495 + }, + "status": "completed", + "trace_sha256": "c49314cbe4e4b07a9335b57e861ec5b6541ea4cc061ca9abf923453276c419fd" + }, + { + "config": { + "mns": 16, + "name": "tp4_mns16", + "tp": 4 + }, + "elapsed_seconds": 9.39468502998352, + "offered_request_rate": 16.0, + "offered_request_rate_per_gpu": 4.0, + "request_count": 64, + "request_metrics_sha256": "c40f10c895f2fc45671f7ff7c6c08f30c20854f0ff4fe3e6719934a7b08b0112", + "score": { + "feasible": true, + "pass_rate": 1.0, + "passed": 64, + "throughput_requests_per_second": 15.412041282697286, + "ttft_max_ms": 314.4210260695206, + "ttft_p50_ms": 239.37774488651664, + "ttft_p95_ms": 309.403713596319 + }, + "status": "completed", + "trace_sha256": "2504b6340f764e726110e48251f8017a2f9ba67b44b8af7d3a838bf674823d33" + }, + { + "config": { + "mns": 16, + "name": "tp4_mns16", + "tp": 4 + }, + "elapsed_seconds": 9.29756498336792, + "offered_request_rate": 32.0, + "offered_request_rate_per_gpu": 8.0, + "request_count": 64, + "request_metrics_sha256": "9f77ee40d102bdf14d213132ef10b4a7f4175908ffee411ef30ad3a5f1e4b16f", + "score": { + "feasible": false, + "pass_rate": 0.625, + "passed": 40, + "throughput_requests_per_second": 16.898601280793688, + "ttft_max_ms": 1850.42236218991, + "ttft_p50_ms": 1017.6783445024347, + "ttft_p95_ms": 1787.92236218991 + }, + "status": "completed", + "trace_sha256": "5508a3ce590f7d1b0b3c211a2d4f2131886a7ae2d85cafa0c436a8e3029675f1" + }, + { + "config": { + "mns": 16, + "name": "tp4_mns16", + "tp": 4 + }, + "elapsed_seconds": 9.34343934059143, + "offered_request_rate": 64.0, + "offered_request_rate_per_gpu": 16.0, + "request_count": 64, + "request_metrics_sha256": "efb43cc32f2f7129082e47d89239f08b10c6e7e6dd4a4f0b299d2364cb781af0", + "score": { + "feasible": false, + "pass_rate": 0.390625, + "passed": 25, + "throughput_requests_per_second": 17.184897883039092, + "ttft_max_ms": 2771.0751922610093, + "ttft_p50_ms": 1468.937931419684, + "ttft_p95_ms": 2661.056949107159 + }, + "status": "completed", + "trace_sha256": "8987c3ebd4a7db983dc0e99e44710cd44a9d5d21cdb8e6b58878b160918593d9" + } + ] + }, + { + "config": { + "mns": 32, + "name": "tp4_mns32", + "tp": 4 + }, + "loads": [ + { + "config": { + "mns": 32, + "name": "tp4_mns32", + "tp": 4 + }, + "elapsed_seconds": 9.296925783157349, + "offered_request_rate": 4.0, + "offered_request_rate_per_gpu": 1.0, + "request_count": 64, + "request_metrics_sha256": "c0c3c34bea43dae2477b10152b07632e5d03968f47cdfd70ad5b676e2502af4c", + "score": { + "feasible": true, + "pass_rate": 1.0, + "passed": 64, + "throughput_requests_per_second": 4.039607508408214, + "ttft_max_ms": 93.12333977685495, + "ttft_p50_ms": 93.12333977685405, + "ttft_p95_ms": 93.12333977685495 + }, + "status": "completed", + "trace_sha256": "0cef631f351f5afd880172d0467d62d9813de5ba36fd3795f91afe3256450c05" + }, + { + "config": { + "mns": 32, + "name": "tp4_mns32", + "tp": 4 + }, + "elapsed_seconds": 9.345365285873413, + "offered_request_rate": 8.0, + "offered_request_rate_per_gpu": 2.0, + "request_count": 64, + "request_metrics_sha256": "b5c6ba0cd3b2a81a12fb93b2cad6f80341f9352c110385046490f59d7933a5d5", + "score": { + "feasible": true, + "pass_rate": 1.0, + "passed": 64, + "throughput_requests_per_second": 8.032004183533672, + "ttft_max_ms": 93.12333977685495, + "ttft_p50_ms": 93.12333977685495, + "ttft_p95_ms": 93.12333977685495 + }, + "status": "completed", + "trace_sha256": "c49314cbe4e4b07a9335b57e861ec5b6541ea4cc061ca9abf923453276c419fd" + }, + { + "config": { + "mns": 32, + "name": "tp4_mns32", + "tp": 4 + }, + "elapsed_seconds": 9.350512504577637, + "offered_request_rate": 16.0, + "offered_request_rate_per_gpu": 4.0, + "request_count": 64, + "request_metrics_sha256": "c40f10c895f2fc45671f7ff7c6c08f30c20854f0ff4fe3e6719934a7b08b0112", + "score": { + "feasible": true, + "pass_rate": 1.0, + "passed": 64, + "throughput_requests_per_second": 15.412041282697286, + "ttft_max_ms": 314.4210260695206, + "ttft_p50_ms": 239.37774488651664, + "ttft_p95_ms": 309.403713596319 + }, + "status": "completed", + "trace_sha256": "2504b6340f764e726110e48251f8017a2f9ba67b44b8af7d3a838bf674823d33" + }, + { + "config": { + "mns": 32, + "name": "tp4_mns32", + "tp": 4 + }, + "elapsed_seconds": 9.300485372543335, + "offered_request_rate": 32.0, + "offered_request_rate_per_gpu": 8.0, + "request_count": 64, + "request_metrics_sha256": "9f77ee40d102bdf14d213132ef10b4a7f4175908ffee411ef30ad3a5f1e4b16f", + "score": { + "feasible": false, + "pass_rate": 0.625, + "passed": 40, + "throughput_requests_per_second": 16.898601280793688, + "ttft_max_ms": 1850.42236218991, + "ttft_p50_ms": 1017.6783445024347, + "ttft_p95_ms": 1787.92236218991 + }, + "status": "completed", + "trace_sha256": "5508a3ce590f7d1b0b3c211a2d4f2131886a7ae2d85cafa0c436a8e3029675f1" + }, + { + "config": { + "mns": 32, + "name": "tp4_mns32", + "tp": 4 + }, + "elapsed_seconds": 9.292456150054932, + "offered_request_rate": 64.0, + "offered_request_rate_per_gpu": 16.0, + "request_count": 64, + "request_metrics_sha256": "efb43cc32f2f7129082e47d89239f08b10c6e7e6dd4a4f0b299d2364cb781af0", + "score": { + "feasible": false, + "pass_rate": 0.390625, + "passed": 25, + "throughput_requests_per_second": 17.184897883039092, + "ttft_max_ms": 2771.0751922610093, + "ttft_p50_ms": 1468.937931419684, + "ttft_p95_ms": 2661.056949107159 + }, + "status": "completed", + "trace_sha256": "8987c3ebd4a7db983dc0e99e44710cd44a9d5d21cdb8e6b58878b160918593d9" + } + ] + }, + { + "config": { + "mns": 64, + "name": "tp4_mns64", + "tp": 4 + }, + "loads": [ + { + "config": { + "mns": 64, + "name": "tp4_mns64", + "tp": 4 + }, + "elapsed_seconds": 9.294773817062378, + "offered_request_rate": 4.0, + "offered_request_rate_per_gpu": 1.0, + "request_count": 64, + "request_metrics_sha256": "c0c3c34bea43dae2477b10152b07632e5d03968f47cdfd70ad5b676e2502af4c", + "score": { + "feasible": true, + "pass_rate": 1.0, + "passed": 64, + "throughput_requests_per_second": 4.039607508408214, + "ttft_max_ms": 93.12333977685495, + "ttft_p50_ms": 93.12333977685405, + "ttft_p95_ms": 93.12333977685495 + }, + "status": "completed", + "trace_sha256": "0cef631f351f5afd880172d0467d62d9813de5ba36fd3795f91afe3256450c05" + }, + { + "config": { + "mns": 64, + "name": "tp4_mns64", + "tp": 4 + }, + "elapsed_seconds": 9.34990382194519, + "offered_request_rate": 8.0, + "offered_request_rate_per_gpu": 2.0, + "request_count": 64, + "request_metrics_sha256": "b5c6ba0cd3b2a81a12fb93b2cad6f80341f9352c110385046490f59d7933a5d5", + "score": { + "feasible": true, + "pass_rate": 1.0, + "passed": 64, + "throughput_requests_per_second": 8.032004183533672, + "ttft_max_ms": 93.12333977685495, + "ttft_p50_ms": 93.12333977685495, + "ttft_p95_ms": 93.12333977685495 + }, + "status": "completed", + "trace_sha256": "c49314cbe4e4b07a9335b57e861ec5b6541ea4cc061ca9abf923453276c419fd" + }, + { + "config": { + "mns": 64, + "name": "tp4_mns64", + "tp": 4 + }, + "elapsed_seconds": 9.346213579177856, + "offered_request_rate": 16.0, + "offered_request_rate_per_gpu": 4.0, + "request_count": 64, + "request_metrics_sha256": "c40f10c895f2fc45671f7ff7c6c08f30c20854f0ff4fe3e6719934a7b08b0112", + "score": { + "feasible": true, + "pass_rate": 1.0, + "passed": 64, + "throughput_requests_per_second": 15.412041282697286, + "ttft_max_ms": 314.4210260695206, + "ttft_p50_ms": 239.37774488651664, + "ttft_p95_ms": 309.403713596319 + }, + "status": "completed", + "trace_sha256": "2504b6340f764e726110e48251f8017a2f9ba67b44b8af7d3a838bf674823d33" + }, + { + "config": { + "mns": 64, + "name": "tp4_mns64", + "tp": 4 + }, + "elapsed_seconds": 9.498731136322021, + "offered_request_rate": 32.0, + "offered_request_rate_per_gpu": 8.0, + "request_count": 64, + "request_metrics_sha256": "9f77ee40d102bdf14d213132ef10b4a7f4175908ffee411ef30ad3a5f1e4b16f", + "score": { + "feasible": false, + "pass_rate": 0.625, + "passed": 40, + "throughput_requests_per_second": 16.898601280793688, + "ttft_max_ms": 1850.42236218991, + "ttft_p50_ms": 1017.6783445024347, + "ttft_p95_ms": 1787.92236218991 + }, + "status": "completed", + "trace_sha256": "5508a3ce590f7d1b0b3c211a2d4f2131886a7ae2d85cafa0c436a8e3029675f1" + }, + { + "config": { + "mns": 64, + "name": "tp4_mns64", + "tp": 4 + }, + "elapsed_seconds": 9.300755977630615, + "offered_request_rate": 64.0, + "offered_request_rate_per_gpu": 16.0, + "request_count": 64, + "request_metrics_sha256": "efb43cc32f2f7129082e47d89239f08b10c6e7e6dd4a4f0b299d2364cb781af0", + "score": { + "feasible": false, + "pass_rate": 0.390625, + "passed": 25, + "throughput_requests_per_second": 17.184897883039092, + "ttft_max_ms": 2771.0751922610093, + "ttft_p50_ms": 1468.937931419684, + "ttft_p95_ms": 2661.056949107159 + }, + "status": "completed", + "trace_sha256": "8987c3ebd4a7db983dc0e99e44710cd44a9d5d21cdb8e6b58878b160918593d9" + } + ] + } + ], + "contract": { + "arrival": "open_loop_uniform", + "input_tokens": 2048, + "output_tokens": 1, + "prefix_caching": false, + "rates": [ + 4.0, + 8.0, + 16.0, + 32.0, + 64.0 + ], + "requests_per_anchor": 64, + "target_pass_rate": 0.95, + "ttft_slo_ms": 1256.0 + }, + "frontier": { + "git_head": "d9cfeb6d8791fbf2f295dd9744c56a666171776e", + "git_status_short": " M frontier/config/config.py\n M frontier/entities/request.py\n M frontier/events/cluster_schedule_event.py\n M frontier/execution_time_predictor/sklearn_execution_time_predictor.py\n M frontier/metrics/constants.py\n M frontier/metrics/metrics_store.py\n M frontier/profiling/common/layers/rotary_embedding.py\n M frontier/profiling/moe/moe_impl.py\n M frontier/profiling/moe/moe_vllm_kernel.py\n M frontier/scheduler/cluster_scheduler/__init__.py\n M frontier/scheduler/cluster_scheduler/base_cluster_scheduler.py\n M frontier/scheduler/cluster_scheduler/cluster_scheduler_registry.py\n M frontier/scheduler/cluster_scheduler/sticky_lor_cluster_scheduler.py\n M frontier/scheduler/replica_scheduler/base_replica_scheduler.py\n M frontier/scheduler/replica_scheduler/vllm_v1_engine_replica_scheduler.py\n M frontier/scheduler/replica_stage_scheduler/replica_stage_schduler.py\n M frontier/simulator.py\n M frontier/types/cluster_scheduler_type.py\n?? data/profiling/compute/h20/\n?? frontier/scheduler/cluster_scheduler/prefix_lor_cluster_scheduler.py\n?? runs/\n?? tests/unit/test_attn_prefill_prediction_fallback.py\n", + "source": "/tmp/replayserve-frontier-rs1b" + }, + "profiles": { + "coverage": { + "attention": { + "1": { + "exact_prefill_2048_rows": 1, + "profile_batch_size": 1 + }, + "2": { + "exact_prefill_2048_rows": 1, + "profile_batch_size": 1 + }, + "4": { + "exact_prefill_2048_rows": 1, + "profile_batch_size": 1 + } + }, + "manifest": { + "attention_tp_coverage": [ + 1, + 2, + 4 + ], + "environment_contract": { + "dtype": "bfloat16", + "frontier_commit": "d9cfeb6d8791fbf2f295dd9744c56a666171776e", + "hardware": "NVIDIA H20", + "model": "Qwen3-30B-A3B", + "tensor_parallel_sizes": [ + 1, + 2, + 4 + ], + "vllm_source_commit": "88d34c6409e9fb3c7b8ca0c04756f061d2099eb1", + "vllm_version": "0.20.0" + }, + "inputs": { + "/home/gahow/phd/aituner/runs/frontier-qwen30-vllm020-profile-v1/fleet-artifacts/qwen30-vllm020-allreduce-full-tp2-20260716-v1-dispatch-aware-20260716T140743025781Z/artifacts/artifacts/allreduce-full-tp2-v1/raw/allreduce-tp2.json": "97c3c76b5a04e95bd9192423c2b891667c668f39cc0dfecbd097d749939f2d0a", + "/home/gahow/phd/aituner/runs/frontier-qwen30-vllm020-profile-v1/fleet-artifacts/qwen30-vllm020-allreduce-full-tp4-20260716-v1-dispatch-aware-20260716T141106009788Z/artifacts/artifacts/allreduce-full-tp4-v1/raw/allreduce-tp4.json": "809df9baa6f468cf12bf0c99827475acc67894dd9f3f948976590b665fac0e76", + "/home/gahow/phd/aituner/runs/frontier-qwen30-vllm020-profile-v1/fleet-artifacts/qwen30-vllm020-flashattn-kv-full-tp1-20260716-v2-20260716T135132587012Z/artifacts/artifacts/flashattn-kv-full-v2-tp1/raw/flashattn-tp1.json": "dcb4c1bf7e76b9c765f78ddd2b8a734f2d7ba2adac13ce017689a8a77fe69a27", + "/home/gahow/phd/aituner/runs/frontier-qwen30-vllm020-profile-v1/fleet-artifacts/qwen30-vllm020-flashattn-kv-full-tp2-20260716-v2-20260716T135134194295Z/artifacts/artifacts/flashattn-kv-full-v2-tp2/raw/flashattn-tp2.json": "43ce042556ba887c8860614b43ccf0f564e5cebc1a0cffbce299d0acb9fa8d07", + "/home/gahow/phd/aituner/runs/frontier-qwen30-vllm020-profile-v1/fleet-artifacts/qwen30-vllm020-flashattn-kv-full-tp4-20260716-v2-20260716T135135197200Z/artifacts/artifacts/flashattn-kv-full-v2-tp4/raw/flashattn-tp4.json": "84eef31bcad0f556907a093318a420959d14fdc94474823d11f659704bdfec73", + "/home/gahow/phd/aituner/runs/frontier-qwen30-vllm020-profile-v1/fleet-artifacts/qwen30-vllm020-frontier-linear-full-20260716-v2-max-tokens-20260716T144444676943Z/artifacts/artifacts/frontier-linear-full-v2/profiles/compute/h20/qwen3-a3b-30b-moe/linear_op.csv": "67666cb0a4901b74599d468df2e31bcaa2a11a7842cc0cefba24ffce62508e0c", + "/home/gahow/phd/aituner/runs/frontier-qwen30-vllm020-profile-v1/fleet-artifacts/qwen30-vllm020-moe-full-20260716-v1-local-shard-20260716T141334565164Z/artifacts/artifacts/moe-full-v1/raw/moe-full.json": "588f6ad0d69c9636d1b852e3df0a12d13cfe731f050ea7ec7aea457cceefbde8", + "/home/gahow/phd/aituner/runs/frontier-qwen30-vllm020-profile-v1/fleet-artifacts/qwen30-vllm020-router-full-20260716-v3-tp-context-20260716T145446098505Z/artifacts/artifacts/router-full-v3/raw/router.json": "1962972e983bff3e06a721ef4ae4ec65728ff669681497a4a7e7f769b88b4931" + }, + "outputs": { + "allreduce.json": "b38d14f990578d668523d25b107aceed433da5020d8ada3b6e44d3562261a3b3", + "attention.csv": "76dcb767cebb4ec1c4e24bd04d93ddd48b5d271986ebfb51a197ab33e1b3d87d", + "attention_true_mixed_fused.csv": "43ef4be90bddc9aeac6dbbe339feec24162cd1f2129a08fbd959e6ee4eaf5f60", + "linear_op.csv": "67666cb0a4901b74599d468df2e31bcaa2a11a7842cc0cefba24ffce62508e0c", + "moe.csv": "0e4dcba72918a1c4cf4e96ced31ee3829248a19ad54553cebef14417725808b0" + }, + "profile_id": "qwen3-30b-a3b-bf16-vllm020-h20-tp1-2-4-fused-mixed-total-conserving", + "projection_contract": { + "allreduce": "Frozen exact runtime measurements; base profile-only comparison keeps the historical Frontier CC backend fixed to isolate compute profile fidelity", + "attention": "Pure prefill/extend/decode FA3 core plus separately measured KV update; input/output reshape assumed zero; exported mean is used as median target; true mixed rows use a total-conserving compatibility projection", + "attention_true_mixed": "The directly measured fused total is preserved in diagnostics. Frontier's two targets are projected by the same-TP pure prefill/decode reference ratio, with projected prefill + decode exactly equal to the fused total; the split is a schema compatibility attribution, not an observation", + "linear": "Frontier profiler using vLLM 0.20 CUDA operators", + "moe": "Replicated gate and fused top-k plus TP-local modular expert kernel; expert measurement already includes prepare/finalize so shuffling is zero" + }, + "row_counts": { + "allreduce": 24, + "attention_frontier_compatible": 132, + "attention_true_mixed_fused_diagnostic": 30, + "linear": 36, + "moe": 72 + }, + "schema_version": "frontier_qwen30_vllm020_frozen_profile.v2" + } + }, + "root": "/home/gahow/phd/aituner/runs/frontier-qwen30-vllm020-profile-v1/frozen/profile-v2", + "sha256": { + "attention": "76dcb767cebb4ec1c4e24bd04d93ddd48b5d271986ebfb51a197ab33e1b3d87d", + "linear": "67666cb0a4901b74599d468df2e31bcaa2a11a7842cc0cefba24ffce62508e0c", + "manifest": "af40545e75aff55c6333cd2d5379ccf042a5a0b7d7fc7df4f745ce256cb290eb", + "moe": "0e4dcba72918a1c4cf4e96ced31ee3829248a19ad54553cebef14417725808b0" + } + }, + "schema": "frontier-qwen30-prefill-surface-v1", + "status": "partial_not_decision_bearing" +} diff --git a/runs/frontier-phase-factorial-v0/test_phase_factorial.py b/runs/frontier-phase-factorial-v0/test_phase_factorial.py new file mode 100644 index 0000000..6e30aa5 --- /dev/null +++ b/runs/frontier-phase-factorial-v0/test_phase_factorial.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 + +from __future__ import annotations + +import importlib.util +import sys +from pathlib import Path + + +ROOT = Path(__file__).parent + + +def load(name: str): + path = ROOT / name + spec = importlib.util.spec_from_file_location(path.stem, path) + assert spec and spec.loader + module = importlib.util.module_from_spec(spec) + sys.modules[spec.name] = module + spec.loader.exec_module(module) + return module + + +def test_percentile() -> None: + client = load("qwen30_prefill_client.py") + assert client.percentile([4.0, 1.0, 3.0, 2.0], 0.50) == 2.0 + assert client.percentile([4.0, 1.0, 3.0, 2.0], 0.95) == 4.0 + assert client.percentile([], 0.95) is None + + +def test_grid_and_trace(tmp_path: Path) -> None: + surface = load("run_frontier_qwen30_prefill_surface.py") + assert len(surface.GRID) == 12 + assert {config.tp for config in surface.GRID} == {1, 2, 4} + trace = tmp_path / "trace.csv" + surface.write_trace(trace, requests=3, rate=4.0) + lines = trace.read_text().splitlines() + assert len(lines) == 4 + assert lines[1].split(",")[:3] == ["0.000000000000", "2048", "1"] + assert lines[3].split(",")[:3] == ["0.500000000000", "2048", "1"]