Spaces:
Running
Running
Commit ·
4649014
1
Parent(s): 9916edb
Finalize InferScale-Sim
Browse files- README.md +160 -348
- app.js +75 -45
- docs/architecture.md +1 -1
- docs/design.md +47 -0
- docs/methodology.md +5 -5
- docs/research.md +3 -3
- docs/validation.md +1 -1
- index.html +0 -0
- scripts/release_check.py +22 -3
- styles.css +537 -209
README.md
CHANGED
|
@@ -11,426 +11,238 @@ short_description: Interactive LLM serving simulator and SLO planner
|
|
| 11 |
|
| 12 |
# InferScale-Sim
|
| 13 |
|
| 14 |
-
|
| 15 |
|
| 16 |
-
|
| 17 |
|
| 18 |
-
>
|
| 19 |
|
| 20 |
-
|
| 21 |
|
| 22 |
-
|
| 23 |
-
> InferScale-Sim ships with **analytical reference latency profiles**, not measured GPU calibration data. Queueing, scheduling, cache, transfer, SLO, and design-space behavior is simulated live. Absolute L4/A10G/A100 milliseconds must not be presented as empirical hardware benchmarks.
|
| 24 |
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
-
|
| 28 |
|
| 29 |
-
|
| 30 |
|
| 31 |
-
##
|
| 32 |
|
| 33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
-
|
| 36 |
-
- log-normal prompt/output-length distributions
|
| 37 |
-
- exact **CSV/JSON trace replay** using supplied arrival times and token lengths
|
| 38 |
-
- shared seeds for controlled A/B experiments
|
| 39 |
-
- stateful multi-turn agent sessions with ordered turn dependencies and explicit tool-call gaps
|
| 40 |
|
| 41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
-
|
| 44 |
-
arrival_time,prompt_tokens,output_tokens
|
| 45 |
-
0.000,512,64
|
| 46 |
-
0.137,233,41
|
| 47 |
-
0.284,1024,128
|
| 48 |
-
```
|
| 49 |
-
|
| 50 |
-
### Serving loop
|
| 51 |
-
|
| 52 |
-
- static batching baseline
|
| 53 |
-
- continuous batching with FCFS
|
| 54 |
-
- shortest-job-first scheduling
|
| 55 |
-
- least-slack/SLO-aware scheduling
|
| 56 |
-
- chunked prefill
|
| 57 |
-
- paged KV-cache accounting and VRAM admission control
|
| 58 |
-
- controlled exact-prefix reuse
|
| 59 |
-
- colocated or prefill/decode-disaggregated topology
|
| 60 |
-
- independent P/D worker counts and accelerator profiles
|
| 61 |
-
- explicit serialized KV-transfer model
|
| 62 |
-
- FP16 / INT8 / INT4 weight-footprint scenarios
|
| 63 |
-
|
| 64 |
-
### Metrics
|
| 65 |
-
|
| 66 |
-
- TTFT, TPOT, E2E, and queue-latency percentiles
|
| 67 |
-
- request and output-token throughput
|
| 68 |
-
- **goodput**: SLO-compliant completed requests per simulated second
|
| 69 |
-
- TTFT/E2E SLO attainment
|
| 70 |
-
- peak KV usage and virtual utilization
|
| 71 |
-
- prefix-cache hit rate / saved prefill work
|
| 72 |
-
- P/D transfer latency / transfer volume / role utilization
|
| 73 |
-
- heuristic bottleneck diagnoses with explicit simulator provenance
|
| 74 |
-
- cross-turn KV hit rate, recomputed history tokens, routing locality, HBM/host GB-seconds, TTL/pressure evictions, and explicit host-tier transfer latency for stateful sessions
|
| 75 |
-
- online tool-gap prediction MAE, oracle-action agreement, and pre/post-shift adaptation diagnostics
|
| 76 |
-
- online agent-role transition accuracy, prefix-prefetch precision/coverage, wrong-step transfer volume, prefix-cache residency, and pre/post-shift execution-learning diagnostics
|
| 77 |
-
|
| 78 |
-
## Interactive experiments
|
| 79 |
-
|
| 80 |
-
### Serving Lab
|
| 81 |
-
|
| 82 |
-
Run one workload through a colocated or P/D system and inspect request-level behavior, queue/KV timelines, latency distributions, prefix reuse, and bottleneck diagnostics.
|
| 83 |
-
|
| 84 |
-
### Scheduler Arena
|
| 85 |
-
|
| 86 |
-
Run every colocated scheduler against the **same deterministic workload** and compare goodput, SLO attainment, tail latency, KV pressure, and unfinished work.
|
| 87 |
-
|
| 88 |
-
### Capacity Planner
|
| 89 |
-
|
| 90 |
-
Perform repeated SLO-constrained binary search for the highest sustainable offered rate. A rate passes only if **every repetition** meets the requested SLO attainment and drains all generated requests. The search exposes mean attainment, worst repetition, target, and min/max range.
|
| 91 |
-
|
| 92 |
-
### Modern Serving Lab
|
| 93 |
-
|
| 94 |
-
Compare the same workload across:
|
| 95 |
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
|
|
|
|
|
|
|
|
|
| 100 |
|
| 101 |
-
|
| 102 |
|
| 103 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
|
| 105 |
-
|
| 106 |
|
| 107 |
-
|
| 108 |
-
- **efficiency:** maximize goodput per accelerator while minimizing p95 TTFT
|
| 109 |
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
1. **Session run:** inspect one retention/routing policy on a generated program trace.
|
| 117 |
-
2. **Session Policy Arena:** replay the exact same program trace through simple stateless/retention baselines.
|
| 118 |
-
3. **TTL frontier:** sweep KV retention time under common random numbers and expose the non-dominated trade-off between p95 turn TTFT and mean resident KV.
|
| 119 |
-
4. **Agent Memory Lab:** compare HBM retention, bounded affinity, host-memory offload, and an explicitly labeled oracle gap-aware tiering upper bound.
|
| 120 |
-
5. **Finite-HBM budget stress:** derive per-replica KV budgets from the trace's unconstrained working set and rerun tiering policies under deliberate memory pressure.
|
| 121 |
-
6. **Affinity Frontier:** sweep the queue-delay slack that bounded-affinity routing is willing to pay for cache locality.
|
| 122 |
-
7. **Predictive Tiering Lab:** replace the clairvoyant gap-aware policy with online global/per-tool EWMA predictors that learn only from completed tool calls, then compare them on a non-stationary program trace.
|
| 123 |
-
8. **Adaptation-rate sweep:** vary the EWMA update rate on the exact same shifted trace to expose the stability-versus-reactivity trade-off.
|
| 124 |
-
|
| 125 |
-
Agent mode intentionally uses a serial service station per replica. The existing Serving Lab models batching; Agent Sessions isolates program dependencies, cache locality, tool gaps, and state-management decisions so those effects are interpretable rather than conflated.
|
| 126 |
-
|
| 127 |
-
### Predictive tiering without future leakage
|
| 128 |
-
|
| 129 |
-
The adaptive KV policy does **not** read the sampled future tool duration when it decides whether to retain state in HBM or offload it. Retained adaptive state is also bounded by the configured KV safety TTL, so a bad short-gap prediction cannot pin HBM indefinitely. Each completed tool call emits a later observation event; only then is its duration added to the online predictor. Two transparent predictors are available:
|
| 130 |
-
|
| 131 |
-
- a global EWMA over all completed tools;
|
| 132 |
-
- a per-tool EWMA that falls back to the global estimate during warm-up.
|
| 133 |
-
|
| 134 |
-
The Predictive Tiering Lab deliberately changes the duration distribution of slower external tools partway through one shared trace. It compares fixed TTL, unconditional host offload, global prediction, per-tool prediction, and an explicitly labeled oracle upper bound. Prediction error and serving outcomes are shown together so a lower forecasting error is not automatically treated as a better systems policy.
|
| 135 |
-
|
| 136 |
-
### Execution Learning
|
| 137 |
-
|
| 138 |
-
Model dynamic multi-agent workflows as a sequence of reusable agent roles rather than only a sequence of tool durations. The default synthetic workflow has structured transitions among `planner`, `retriever`, `reasoner`, `verifier`, and `writer`, then deliberately changes its transition matrix partway through the study.
|
| 139 |
-
|
| 140 |
-
The simulator maintains a bounded shared HBM cache for each role's reusable static prefix. A first-order transition model is updated **only after the next role is observed**. The learned matrix can now be rolled forward for several steps without reading the future trace. Single-run policies include learn-only, cumulative/decayed top-1 prefetch, multi-step top-k prefetch, a utility-aware multi-step planner, and explicitly labeled oracle information bounds.
|
| 141 |
-
|
| 142 |
-
The utility-aware planner estimates, for each candidate role:
|
| 143 |
-
|
| 144 |
-
```text
|
| 145 |
-
expected discounted reuse benefit
|
| 146 |
-
- host-to-HBM transfer time
|
| 147 |
-
- forecast-weighted recomputation cost of prefixes that would be evicted
|
| 148 |
-
```
|
| 149 |
|
| 150 |
-
|
| 151 |
|
| 152 |
-
|
| 153 |
|
| 154 |
-
|
| 155 |
-
- **Confidence Threshold Study:** trade prefetch coverage against precision, wrong-step traffic, cache pollution, and TTFT;
|
| 156 |
-
- **Forgetting-Rate Study:** vary how quickly old transitions are forgotten after the workflow changes;
|
| 157 |
-
- **Prefetch Planning Study:** compare one-step, multi-step top-k, utility-aware multi-step, and a clairvoyant future-set information bound;
|
| 158 |
-
- **Forecast Horizon Study:** sweep how many future workflow steps the online planner rolls forward;
|
| 159 |
-
- **Cache Budget Study:** compare top-1, multi-step, and utility-aware planning under multiple working-set-relative HBM budgets.
|
| 160 |
|
| 161 |
-
|
| 162 |
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
**
|
| 172 |
-
|
| 173 |
-
-
|
| 174 |
-
|
| 175 |
-
- continuous FCFS vs chunked prefill
|
| 176 |
-
- continuous FCFS vs least-slack/SLO-aware scheduling
|
| 177 |
-
|
| 178 |
-
Baseline and treatment use **common random numbers**: each repetition gets the same seed and therefore the same generated workload. InferScale reports paired mean deltas, relative effects, treatment win rate, and a **95% bootstrap interval over paired deltas**.
|
| 179 |
-
|
| 180 |
-
**Model-uncertainty stress testing** then perturbs analytical prefill, decode, and transfer timing scales jointly for both alternatives. The purpose is not to assign a probability distribution to real GPUs; it asks a narrower scientific question:
|
| 181 |
-
|
| 182 |
-
> Does the qualitative conclusion survive plausible multiplicative error in the analytical reference profile?
|
| 183 |
-
|
| 184 |
-
This makes profile uncertainty visible rather than allowing one uncalibrated proxy to silently determine the winner.
|
| 185 |
-
|
| 186 |
-
### Research Summary
|
| 187 |
-
|
| 188 |
-
This is the feature-freeze/consolidation layer. It turns nominal single-trace comparisons into repeated evidence and gives the analytical latency model a direct path to external falsification.
|
| 189 |
-
|
| 190 |
-
**Robust policy ranking** repeats the Execution Learning policy comparison across matched workload seeds. For every deployable policy InferScale reports:
|
| 191 |
-
|
| 192 |
-
- median and mean p95 step TTFT;
|
| 193 |
-
- a 95% bootstrap interval over seed-level TTFT;
|
| 194 |
-
- fraction of seeds on which the policy wins TTFT;
|
| 195 |
-
- **Pareto stability**: fraction of seeds on which it is non-dominated over TTFT, unused speculative prefetch, and mean prefix-HBM residency;
|
| 196 |
-
- worst-seed TTFT;
|
| 197 |
-
- regret to a bounded offline serving oracle.
|
| 198 |
-
|
| 199 |
-
The offline reference is deliberately constrained. On every complete trace it exhaustively evaluates the deployable candidates plus clairvoyant future-set plans for forecast horizons `H=1..5` and prefetch widths `K=1..3`, all under the same cache capacity and transfer bandwidth. The best full-trace outcome becomes the per-seed information upper bound. It is **not** presented as proof of globally optimal action scheduling.
|
| 200 |
-
|
| 201 |
-
**Measurement import and held-out calibration** accepts:
|
| 202 |
-
|
| 203 |
-
- current `vllm bench serve` JSON results;
|
| 204 |
-
- SGLang `bench_serving` JSON/JSONL results;
|
| 205 |
-
- explicit InferScale validation-case bundles.
|
| 206 |
-
|
| 207 |
-
Artifacts are normalized into simulator cases. A simple robust calibration fits global prefill/decode timing multipliers on training cases only, then reports held-out residuals and MAPE. With fewer than three cases, InferScale falls back to resubstitution and marks that weaker validation mode explicitly. Calibration can correct global timing bias; it does not prove unseen-model, unseen-hardware, or scheduler fidelity.
|
| 208 |
-
|
| 209 |
-
**Markdown report export** turns the robust policy study and optional held-out calibration into a portable research artifact with protocol, ranking, oracle definition, validation results, and interpretation guardrails.
|
| 210 |
-
|
| 211 |
-
`reports/reference_consolidation.md` is generated from the default analytical-reference configuration across 12 matched seeds. It is a simulator reference result, **not** measured GPU evidence.
|
| 212 |
-
|
| 213 |
-
The public demo ships no invented empirical benchmark truth. External measurement data must be supplied by the user or collected independently.
|
| 214 |
|
| 215 |
## Architecture
|
| 216 |
|
| 217 |
```text
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
|
| 224 |
-
|
| 225 |
-
|
|
| 226 |
-
|
| 227 |
-
|
|
| 228 |
-
|
| 229 |
-
|
|
| 230 |
-
|
| 231 |
-
|
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
| | | |
|
| 239 |
-
| colocated simulator P/D simulator |
|
| 240 |
-
| | |
|
| 241 |
-
| prefill -> transfer -> decode
|
| 242 |
-
| | | |
|
| 243 |
-
| +-----------------------+------------------+ |
|
| 244 |
-
| v |
|
| 245 |
-
| metrics / search / research / agent sessions / online prediction |
|
| 246 |
-
+------------------------------------------------------------------+
|
| 247 |
-
```
|
| 248 |
-
|
| 249 |
-
The discrete-event engine never sleeps for simulated compute time. If the analytical model predicts a 40 ms operation, virtual time advances by 0.040 seconds immediately.
|
| 250 |
-
|
| 251 |
-
## Core modeling choices
|
| 252 |
-
|
| 253 |
-
### Goodput
|
| 254 |
-
|
| 255 |
-
```text
|
| 256 |
-
goodput = requests satisfying TTFT and E2E SLOs / simulated makespan
|
| 257 |
-
```
|
| 258 |
-
|
| 259 |
-
Raw throughput can reward overload; goodput penalizes requests that complete too late to satisfy the configured objective.
|
| 260 |
-
|
| 261 |
-
### KV-cache model
|
| 262 |
-
|
| 263 |
-
Per-token KV bytes are approximated as:
|
| 264 |
-
|
| 265 |
-
```text
|
| 266 |
-
2 x layers x KV heads x head dimension x 2 bytes
|
| 267 |
-
```
|
| 268 |
-
|
| 269 |
-
for K and V with FP16 KV state. Paged allocation rounds live request state to configurable token blocks. With prefix reuse enabled, the shared prefix is represented once as persistent KV state and request allocations contain only the uncached suffix plus generated tokens.
|
| 270 |
-
|
| 271 |
-
### P/D transfer model
|
| 272 |
-
|
| 273 |
-
After prefill, newly computed prompt KV crosses a serialized reference link:
|
| 274 |
-
|
| 275 |
-
```text
|
| 276 |
-
transfer_time = base_latency + KV_bytes / interconnect_bandwidth
|
| 277 |
-
```
|
| 278 |
-
|
| 279 |
-
The link is intentionally simple and explicit. It does not claim to reproduce NCCL, NIXL, RDMA, PCIe, or NVLink behavior.
|
| 280 |
-
|
| 281 |
-
### Analytical latency model
|
| 282 |
-
|
| 283 |
-
`AnalyticalLatencyModel` uses model architecture, accelerator peak FP16 compute, memory bandwidth, quantization footprint, batch/context shape, and conservative efficiency factors in a roofline-style proxy. Prefill and decode are modeled separately.
|
| 284 |
-
|
| 285 |
-
The research stress test can multiplicatively perturb prefill/decode/transfer timing to expose conclusions that are sensitive to analytical-model error.
|
| 286 |
-
|
| 287 |
-
## Stateful agent-session model
|
| 288 |
-
|
| 289 |
-
Agent-session mode follows a program-level event trace:
|
| 290 |
-
|
| 291 |
-
```text
|
| 292 |
-
session arrival -> turn 1 -> tool gap -> turn 2 -> ... -> completion
|
| 293 |
-
```
|
| 294 |
-
|
| 295 |
-
When a turn becomes ready, it is routed either to the least-loaded replica or to a replica already holding that session's KV state. A cache hit prefills only newly appended tokens; a miss recomputes the accumulated history. KV can be evicted immediately, retained until the session ends, retained under a TTL, or moved to a modeled host-memory tier. Host offload/restore pays an explicit bandwidth + base-latency transfer cost. A bounded-affinity router only follows cached state while the estimated queue imbalance stays under a configurable slack; this avoids treating cache locality as unconditionally preferable. Per-replica capacity uses the same analytical KV-byte model as the request simulator and falls back to LRU-style pressure eviction when the retained working set exceeds the configured budget.
|
| 296 |
-
|
| 297 |
-
The public app reports **HBM GB-seconds** and **host GB-seconds** in addition to peak/mean occupancy and transfer volume. This makes both retaining and offloading state visible rather than treating cache hits or a host tier as free.
|
| 298 |
-
|
| 299 |
-
This module is inspired by recent work on agentic serving, including AGENTSERVESIM's program/tool-gap/session-routing abstraction and CacheTTL's observation that retention across variable tool gaps is a latency-vs-memory decision. It is intentionally smaller and does not claim their measured-system fidelity.
|
| 300 |
-
|
| 301 |
-
### Host-tier offload and bounded affinity
|
| 302 |
-
|
| 303 |
-
For `offload` retention, idle session KV is moved from the modeled HBM tier into finite host memory. The reference transfer model is explicit:
|
| 304 |
-
|
| 305 |
-
```text
|
| 306 |
-
host_transfer = base_latency + KV_size / host_bandwidth
|
| 307 |
-
```
|
| 308 |
-
|
| 309 |
-
If the next agent turn returns before offload has completed, the remaining transfer time becomes exposed before restore. Host residency, offloaded/restored bytes, and p95 transfer latency are reported separately from HBM residency.
|
| 310 |
-
|
| 311 |
-
`bounded_affinity` is a transparent middle ground between strict session affinity and least-load routing. InferScale follows the replica holding the session KV only while its estimated extra queue penalty is below `affinity_slack_ms`. This is meant to study the locality/load-balance tension, not reproduce a particular production scheduler.
|
| 312 |
-
|
| 313 |
-
`gap_aware` tiering uses the **realized simulated tool gap** to retain short-gap state in HBM and offload longer-gap state. Because a real serving system does not know the future perfectly, InferScale labels this policy as an **oracle upper bound** rather than a deployable predictor.
|
| 314 |
-
|
| 315 |
-
## Empirical validation and calibration
|
| 316 |
-
|
| 317 |
-
The public project does **not** ship invented "measured" GPU results. There are two explicit paths for external measurements.
|
| 318 |
-
|
| 319 |
-
For already-normalized InferScale cases:
|
| 320 |
-
|
| 321 |
-
```bash
|
| 322 |
-
python scripts/validate_measurements.py my_measured_cases.json --output validation_report.json
|
| 323 |
-
```
|
| 324 |
-
|
| 325 |
-
For serving-engine artifacts:
|
| 326 |
-
|
| 327 |
-
```bash
|
| 328 |
-
python scripts/import_measurements.py vllm-result.json --source auto --output measurement_cases.json
|
| 329 |
-
python scripts/calibrate_profiles.py measurement_cases.json --holdout 0.33 --output calibration_result.json
|
| 330 |
-
```
|
| 331 |
-
|
| 332 |
-
The importer recognizes vLLM serving JSON fields including request throughput/goodput and percentile TTFT/E2E metrics, plus SGLang `bench_serving` JSON/JSONL fields such as `request_throughput`, `p95_ttft_ms`, and `p95_e2e_latency_ms`. Token lengths and finite request rate are taken from the artifact when present; missing model/accelerator/precision metadata falls back to an explicit base `SimulationConfig`.
|
| 333 |
-
|
| 334 |
-
Calibration fits robust global prefill/decode scale factors on training cases, then evaluates held-out cases through the normal simulator. `examples/validation_cases.schema.json` is a schema/template only; its placeholder values are not benchmark data.
|
| 335 |
|
| 336 |
## Run locally
|
| 337 |
|
| 338 |
-
The Python simulator has **zero runtime dependencies** beyond Python 3.10+.
|
| 339 |
-
|
| 340 |
```bash
|
| 341 |
-
|
| 342 |
source .venv/bin/activate
|
|
|
|
| 343 |
pip install -e '.[dev]'
|
| 344 |
|
| 345 |
pytest -q
|
| 346 |
-
ruff check src tests scripts
|
| 347 |
python scripts/release_check.py
|
| 348 |
```
|
| 349 |
|
| 350 |
-
|
| 351 |
|
| 352 |
```bash
|
| 353 |
-
python
|
| 354 |
```
|
| 355 |
|
| 356 |
-
|
| 357 |
|
| 358 |
-
|
| 359 |
-
python scripts/import_measurements.py result.json --source auto
|
| 360 |
-
python scripts/calibrate_profiles.py measurement_cases.json --holdout 0.33
|
| 361 |
-
```
|
| 362 |
|
| 363 |
-
|
| 364 |
|
| 365 |
```bash
|
| 366 |
-
python scripts/
|
| 367 |
```
|
| 368 |
|
| 369 |
-
|
| 370 |
|
| 371 |
```bash
|
| 372 |
-
python
|
| 373 |
```
|
| 374 |
|
| 375 |
-
|
| 376 |
|
| 377 |
-
|
|
|
|
|
|
|
| 378 |
|
| 379 |
-
|
| 380 |
|
| 381 |
-
```
|
| 382 |
-
|
| 383 |
-
|
| 384 |
```
|
| 385 |
|
| 386 |
-
|
| 387 |
|
| 388 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 389 |
|
| 390 |
```bash
|
| 391 |
-
python scripts/sync_web_python.py
|
| 392 |
pytest -q
|
| 393 |
python scripts/release_check.py
|
|
|
|
|
|
|
|
|
|
| 394 |
```
|
| 395 |
|
| 396 |
-
|
| 397 |
|
| 398 |
-
|
| 399 |
|
| 400 |
-
|
| 401 |
-
- **SGLang: Efficient Execution of Structured Language Model Programs** (NeurIPS 2024) - RadixAttention motivates reusable-prefix experiments. https://arxiv.org/abs/2312.07104
|
| 402 |
-
- **TokenSim** (2025) - extensible scheduling and memory-management simulation. https://arxiv.org/abs/2503.08415
|
| 403 |
-
- **Revati** (2026) - GPU-free time-warp emulation of serving control logic. https://arxiv.org/abs/2601.00397
|
| 404 |
-
- **LLMServingSim 2.0** (2026) - heterogeneous/disaggregated infrastructure and runtime interactions. https://arxiv.org/abs/2602.23036
|
| 405 |
-
- **Frontier** (May 2026) - P/D and Attention-FFN disaggregation, runtime optimizations, stateful workloads, and Pareto exploration. https://arxiv.org/abs/2605.21312
|
| 406 |
-
- **AgentServeSim** (June 2026) - hardware-aware simulation for multi-turn agent serving, tool gaps, routing, and KV residency. https://arxiv.org/abs/2606.09613
|
| 407 |
-
- **Vanguard / Load Testing for Machine Learning Model Serving Systems at Scale** (June 2026) - trace replay, reproducibility analysis, bootstrap intervals, and open-loop generation to avoid coordinated omission. https://arxiv.org/abs/2606.22013
|
| 408 |
-
- **When Does Disaggregation Pay? / HeteroPanacea** (August 2026) - heterogeneous stage specialization and cross-stack disaggregated design exploration. https://arxiv.org/abs/2608.03741
|
| 409 |
|
| 410 |
-
|
| 411 |
|
| 412 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 413 |
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|-- py/inferscale/ generated browser mirror
|
| 418 |
-
|-- tests/ deterministic unit tests
|
| 419 |
-
|-- scripts/ runner, validation, release tooling
|
| 420 |
-
|-- examples/ workload / validation schemas
|
| 421 |
-
|-- reports/ analytical-reference consolidation artifacts
|
| 422 |
-
|-- docs/ architecture, methodology, research notes
|
| 423 |
-
|-- index.html HF Static Space entry point
|
| 424 |
-
|-- app.js UI, studies, charts, export tooling
|
| 425 |
-
|-- worker.mjs Pyodide Web Worker bridge
|
| 426 |
-
`-- styles.css
|
| 427 |
-
```
|
| 428 |
|
| 429 |
-
|
| 430 |
|
| 431 |
-
|
| 432 |
|
| 433 |
-
|
|
|
|
|
|
|
| 434 |
|
| 435 |
## License
|
| 436 |
|
|
|
|
| 11 |
|
| 12 |
# InferScale-Sim
|
| 13 |
|
| 14 |
+
A browser-based research workbench for studying LLM serving systems without provisioning a GPU.
|
| 15 |
|
| 16 |
+
The simulator is written in Python. Hugging Face serves a static site; Pyodide runs the same `src/inferscale` package inside a Web Worker on the visitor's CPU. There is no backend service, model download, API key, or server-side accelerator.
|
| 17 |
|
| 18 |
+
> **Timing scope.** The bundled L4/A10G/A100 profiles are analytical references, not measured hardware benchmarks. Queueing, scheduling, KV-cache, transfer, SLO, and policy behavior is simulated live. Hardware claims require imported measurements and held-out validation.
|
| 19 |
|
| 20 |
+
## Research question
|
| 21 |
|
| 22 |
+
InferScale asks how serving-policy choices interact with workload shape and resource constraints:
|
|
|
|
| 23 |
|
| 24 |
+
- when continuous batching or chunked prefill changes tail latency;
|
| 25 |
+
- when prefill/decode disaggregation helps enough to justify extra accelerators and KV transfer;
|
| 26 |
+
- when reusable prefixes improve goodput versus consume scarce cache capacity;
|
| 27 |
+
- how stateful agent sessions change KV-retention and routing decisions;
|
| 28 |
+
- whether online prediction improves serving outcomes, not merely prediction accuracy;
|
| 29 |
+
- which conclusions remain stable across matched workload seeds and timing uncertainty.
|
| 30 |
|
| 31 |
+
The motivation is practical. Microsoft's [Vidur](https://arxiv.org/abs/2405.05465) reported an example LLaMA2-70B configuration search that took roughly one CPU-hour through simulation versus an estimated 42,000 GPU-hours (~$218K) using deployment-based exploration.
|
| 32 |
|
| 33 |
+
## What is implemented
|
| 34 |
|
| 35 |
+
### Stateless serving
|
| 36 |
|
| 37 |
+
- open-loop constant, Poisson, bursty, and exact trace-replay workloads;
|
| 38 |
+
- static and continuous batching;
|
| 39 |
+
- FCFS, shortest-job-first, least-slack/SLO-aware scheduling;
|
| 40 |
+
- chunked prefill;
|
| 41 |
+
- paged KV-cache accounting and memory admission;
|
| 42 |
+
- controlled shared-prefix reuse;
|
| 43 |
+
- colocated and prefill/decode-disaggregated topologies;
|
| 44 |
+
- independent P/D worker counts, accelerator profiles, and serialized KV-transfer cost;
|
| 45 |
+
- TTFT, TPOT, E2E, queueing, throughput, goodput, SLO attainment, and KV telemetry.
|
| 46 |
|
| 47 |
+
### Stateful sessions
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
+
- multi-turn session dependencies separated by sampled tool gaps;
|
| 50 |
+
- HBM retention, TTL expiry, host-memory offload/restore, and pressure eviction;
|
| 51 |
+
- least-load, strict-affinity, and bounded-affinity routing;
|
| 52 |
+
- cross-turn cache hits, history recomputation, HBM/host GB-seconds, and session SLOs;
|
| 53 |
+
- online global/per-tool EWMA tool-gap prediction under a controlled distribution shift.
|
| 54 |
|
| 55 |
+
### Execution learning
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
|
| 57 |
+
- online first-order role-transition learning with no future-trace lookahead;
|
| 58 |
+
- cumulative and decayed transition models;
|
| 59 |
+
- confidence-gated top-1 prefetch;
|
| 60 |
+
- multi-step transition rollout and top-k prefetch;
|
| 61 |
+
- utility-aware prefetch using transfer and forecast-weighted eviction costs;
|
| 62 |
+
- Brier score, log loss, ECE, reliability diagrams, future-role recall@K, prefetch utilization, and speculative-transfer waste;
|
| 63 |
+
- confidence, forgetting-rate, forecast-horizon, and cache-budget studies.
|
| 64 |
|
| 65 |
+
### Research consolidation
|
| 66 |
|
| 67 |
+
- common-random-number A/B experiments;
|
| 68 |
+
- paired bootstrap intervals;
|
| 69 |
+
- timing-model sensitivity analysis;
|
| 70 |
+
- repeated-seed policy ranking;
|
| 71 |
+
- Pareto stability across latency, HBM residency, and unused speculative transfer;
|
| 72 |
+
- a bounded full-trace serving oracle over a declared policy/action-plan family;
|
| 73 |
+
- per-policy regret to that bounded reference;
|
| 74 |
+
- vLLM/SGLang-style measurement import, calibration, and held-out validation hooks;
|
| 75 |
+
- Markdown/JSON/CSV/PNG research-artifact export.
|
| 76 |
|
| 77 |
+
## Reference result
|
| 78 |
|
| 79 |
+
The repository includes a deterministic 12-seed analytical-reference consolidation study in [`reports/reference_consolidation.md`](reports/reference_consolidation.md).
|
|
|
|
| 80 |
|
| 81 |
+
| Policy | Median p95 TTFT | TTFT wins | Pareto stability | Median oracle regret |
|
| 82 |
+
|---|---:|---:|---:|---:|
|
| 83 |
+
| Top-1 decayed | 6,474.9 ms | 41.7% | 75.0% | 59.3 ms |
|
| 84 |
+
| No prefetch | 6,490.7 ms | 41.7% | **91.7%** | **34.7 ms** |
|
| 85 |
+
| Utility-aware multi-step | 6,507.5 ms | 8.3% | 58.3% | 133.1 ms |
|
| 86 |
+
| Multi-step top-k | 6,640.3 ms | 8.3% | 83.3% | 215.0 ms |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
|
| 88 |
+
`Top-1 decayed` is the robust TTFT winner under the configured ranking protocol, while `No prefetch` is Pareto-stable on more seeds and has lower median regret to the bounded oracle. The point is not to collapse the study to one score: prediction quality, latency, speculative transfer, and HBM pressure can prefer different policies.
|
| 89 |
|
| 90 |
+
The bounded oracle searches 19 candidate plans per seed and has median p95 TTFT **6,408.6 ms** in this reference study. It is an information upper bound over that declared family, not a proof of globally optimal cache scheduling.
|
| 91 |
|
| 92 |
+
## Interface map
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
|
| 94 |
+
The public Space is organized as a research tool rather than a product dashboard:
|
| 95 |
|
| 96 |
+
| Section | Purpose |
|
| 97 |
+
|---|---|
|
| 98 |
+
| **Serving** | Inspect one workload and request-level behavior. |
|
| 99 |
+
| **Schedulers** | Compare colocated schedulers on an identical trace. |
|
| 100 |
+
| **Capacity** | Find the highest repeated SLO-compliant offered load. |
|
| 101 |
+
| **P/D + Cache** | Compare colocated/P-D serving with and without prefix reuse. |
|
| 102 |
+
| **Design Space** | Sweep bounded configurations and inspect performance/efficiency Pareto fronts. |
|
| 103 |
+
| **A/B Studies** | Run paired bootstrap and timing-sensitivity experiments. |
|
| 104 |
+
| **Stateful Sessions** | Study KV retention, host offload, affinity, and tool-gap prediction. |
|
| 105 |
+
| **Execution Model** | Study transition learning, multi-step prefetch, calibration, and cache pressure. |
|
| 106 |
+
| **Evidence** | Run repeated-seed consolidation and import external measurements. |
|
| 107 |
+
| **Methods** | Read the simulator assumptions and research lineage. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
|
| 109 |
## Architecture
|
| 110 |
|
| 111 |
```text
|
| 112 |
+
Hugging Face Static Space
|
| 113 |
+
|
|
| 114 |
+
| serves HTML / CSS / JS / Python sources
|
| 115 |
+
v
|
| 116 |
+
Browser
|
| 117 |
+
|
|
| 118 |
+
+-- UI + Chart.js
|
| 119 |
+
|
|
| 120 |
+
+-- Web Worker
|
| 121 |
+
|
|
| 122 |
+
+-- Pyodide
|
| 123 |
+
|
|
| 124 |
+
+-- src/inferscale
|
| 125 |
+
|
|
| 126 |
+
+-- discrete-event simulation
|
| 127 |
+
+-- research protocols
|
| 128 |
+
+-- calibration / reporting
|
| 129 |
+
```
|
| 130 |
+
|
| 131 |
+
The browser mirror under `py/inferscale/` is generated from `src/inferscale/`; `scripts/release_check.py` fails if the copies diverge.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
|
| 133 |
## Run locally
|
| 134 |
|
|
|
|
|
|
|
| 135 |
```bash
|
| 136 |
+
python3 -m venv .venv
|
| 137 |
source .venv/bin/activate
|
| 138 |
+
python -m pip install -U pip
|
| 139 |
pip install -e '.[dev]'
|
| 140 |
|
| 141 |
pytest -q
|
|
|
|
| 142 |
python scripts/release_check.py
|
| 143 |
```
|
| 144 |
|
| 145 |
+
For the static UI, serve the repository root with any local HTTP server:
|
| 146 |
|
| 147 |
```bash
|
| 148 |
+
python -m http.server 8000
|
| 149 |
```
|
| 150 |
|
| 151 |
+
The deployed Space needs no secrets.
|
| 152 |
|
| 153 |
+
## Command-line examples
|
|
|
|
|
|
|
|
|
|
| 154 |
|
| 155 |
+
Run a single simulation:
|
| 156 |
|
| 157 |
```bash
|
| 158 |
+
python scripts/run_simulation.py examples/balanced.json
|
| 159 |
```
|
| 160 |
|
| 161 |
+
Import a serving-benchmark artifact:
|
| 162 |
|
| 163 |
```bash
|
| 164 |
+
python scripts/import_measurements.py path/to/benchmark.json --format auto
|
| 165 |
```
|
| 166 |
|
| 167 |
+
Calibrate analytical timing scales and evaluate held-out cases:
|
| 168 |
|
| 169 |
+
```bash
|
| 170 |
+
python scripts/calibrate_profiles.py measurements.json --holdout 0.25
|
| 171 |
+
```
|
| 172 |
|
| 173 |
+
Generate a research report:
|
| 174 |
|
| 175 |
+
```bash
|
| 176 |
+
python scripts/generate_report.py robust-policy-study.json \
|
| 177 |
+
--calibration calibration_result.json
|
| 178 |
```
|
| 179 |
|
| 180 |
+
## Repository layout
|
| 181 |
|
| 182 |
+
```text
|
| 183 |
+
src/inferscale/ canonical Python simulator and research code
|
| 184 |
+
py/inferscale/ browser mirror loaded by Pyodide
|
| 185 |
+
index.html static workbench structure
|
| 186 |
+
styles.css project-specific design system
|
| 187 |
+
app.js UI, charts, exports, worker orchestration
|
| 188 |
+
worker.mjs Pyodide bootstrap and Python action bridge
|
| 189 |
+
scripts/ release, import, calibration, report utilities
|
| 190 |
+
tests/ deterministic unit/integration coverage
|
| 191 |
+
docs/ architecture, methodology, validation, research notes
|
| 192 |
+
examples/ workload and validation schemas
|
| 193 |
+
reports/ reference analytical study
|
| 194 |
+
```
|
| 195 |
+
|
| 196 |
+
## Validation
|
| 197 |
+
|
| 198 |
+
The release suite checks both simulation behavior and deployment invariants, including:
|
| 199 |
+
|
| 200 |
+
- stateless and P/D smoke tests;
|
| 201 |
+
- trace replay;
|
| 202 |
+
- prefix reuse and design-space Pareto logic;
|
| 203 |
+
- stateful-session, host-tier, HBM-pressure, and affinity experiments;
|
| 204 |
+
- predictive tiering and execution-learning studies;
|
| 205 |
+
- paired bootstrap, timing sensitivity, repeated-seed consolidation, oracle regret;
|
| 206 |
+
- measurement import/calibration/report generation;
|
| 207 |
+
- source/browser Python parity;
|
| 208 |
+
- HF metadata and public provenance guardrails;
|
| 209 |
+
- DOM-reference integrity and chart-export controls.
|
| 210 |
+
|
| 211 |
+
Run:
|
| 212 |
|
| 213 |
```bash
|
|
|
|
| 214 |
pytest -q
|
| 215 |
python scripts/release_check.py
|
| 216 |
+
python -m compileall -q src scripts
|
| 217 |
+
node --check app.js
|
| 218 |
+
node --check worker.mjs
|
| 219 |
```
|
| 220 |
|
| 221 |
+
## Design and implementation choices
|
| 222 |
|
| 223 |
+
The interface deliberately avoids a product/SaaS visual language. It uses one restrained accent, flat surfaces, square geometry, system typography, and editorial hierarchy instead of gradients, floating cards, badge-heavy status UI, or decorative marketing sections. The rationale and reusable tokens are documented in [`docs/design.md`](docs/design.md).
|
| 224 |
|
| 225 |
+
The simulator core is dependency-light Python rather than a simulation framework. Events, requests, schedulers, caches, predictors, and studies remain inspectable from source and testable outside the browser.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 226 |
|
| 227 |
+
## Limitations
|
| 228 |
|
| 229 |
+
1. Bundled device timings are analytical reference profiles.
|
| 230 |
+
2. The agent-session simulator intentionally isolates state/routing effects from the full dynamic-batching model used in stateless serving.
|
| 231 |
+
3. Transfer models are simplified serialized bandwidth + base-latency abstractions, not packet/NCCL/NIXL simulators.
|
| 232 |
+
4. Oracle policies are bounded information references over declared action families.
|
| 233 |
+
5. Calibration can correct global timing bias; it does not establish fidelity on unseen models, hardware, schedulers, or workload regimes.
|
| 234 |
|
| 235 |
+
These limitations are surfaced in the UI and exported reports rather than hidden.
|
| 236 |
+
|
| 237 |
+
## Research lineage
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 238 |
|
| 239 |
+
InferScale is informed by work on serving simulation, disaggregation, stateful agent workloads, and predictive cache management. See [`docs/research.md`](docs/research.md) for the annotated list and the precise distinction between implemented abstractions and cited systems.
|
| 240 |
|
| 241 |
+
Key starting points include:
|
| 242 |
|
| 243 |
+
- [Vidur](https://arxiv.org/abs/2405.05465) — predictive profiling and workload-aware serving simulation.
|
| 244 |
+
- [SGLang / RadixAttention](https://arxiv.org/abs/2312.07104) — structured prefix reuse and scheduling motivation.
|
| 245 |
+
- Recent 2026 work discussed in `docs/research.md` on P/D disaggregation, GPU-free emulation, agent-session serving, KV tiering, routing locality, and predictive prefetch.
|
| 246 |
|
| 247 |
## License
|
| 248 |
|
app.js
CHANGED
|
@@ -32,14 +32,14 @@ let measurementFileContent = "";
|
|
| 32 |
let traceRequests = [];
|
| 33 |
|
| 34 |
const COLORS = {
|
| 35 |
-
blue: "#
|
| 36 |
-
blue2: "#
|
| 37 |
-
steel: "#
|
| 38 |
-
green: "#
|
| 39 |
-
amber: "#
|
| 40 |
-
red: "#
|
| 41 |
-
purple: "#
|
| 42 |
-
gray: "#
|
| 43 |
grid: "rgba(140,155,175,.14)",
|
| 44 |
};
|
| 45 |
|
|
@@ -135,12 +135,19 @@ function stamp() {
|
|
| 135 |
const pad = (v) => String(v).padStart(2, "0");
|
| 136 |
return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
|
| 137 |
}
|
| 138 |
-
function showToast(message) {
|
| 139 |
const toast = $("toast");
|
| 140 |
toast.textContent = message;
|
|
|
|
| 141 |
toast.classList.add("show");
|
| 142 |
if (toastTimer) clearTimeout(toastTimer);
|
| 143 |
-
toastTimer = setTimeout(() =>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
}
|
| 145 |
async function copyText(text, message) {
|
| 146 |
try {
|
|
@@ -210,7 +217,7 @@ Chart.defaults.color = "#929dab";
|
|
| 210 |
Chart.defaults.borderColor = COLORS.grid;
|
| 211 |
Chart.defaults.font.family = getComputedStyle(document.body).fontFamily;
|
| 212 |
Chart.defaults.font.size = 13;
|
| 213 |
-
Chart.defaults.animation.duration =
|
| 214 |
|
| 215 |
function commonChartOptions() {
|
| 216 |
return { responsive: true, maintainAspectRatio: false };
|
|
@@ -394,7 +401,7 @@ $("runBtn").addEventListener("click", async () => {
|
|
| 394 |
} catch (error) {
|
| 395 |
state.textContent = "Error";
|
| 396 |
state.className = "tag bad";
|
| 397 |
-
|
| 398 |
} finally {
|
| 399 |
button.textContent = "Run simulation";
|
| 400 |
button.disabled = false;
|
|
@@ -435,7 +442,7 @@ $("arenaBtn").addEventListener("click", async () => {
|
|
| 435 |
button.disabled = true;
|
| 436 |
button.textContent = "Comparing...";
|
| 437 |
try { renderArena((await callPython("compare", { config: configFromUI() })).rows); }
|
| 438 |
-
catch (error) {
|
| 439 |
finally { button.disabled = false; button.textContent = "Compare schedulers"; }
|
| 440 |
});
|
| 441 |
const arenaHeaders = ["Scheduler", "Goodput", "SLO attainment", "p95 TTFT", "p95 E2E", "KV peak", "Unfinished", "Diagnosis"];
|
|
@@ -509,7 +516,7 @@ $("capacityBtn").addEventListener("click", async () => {
|
|
| 509 |
} catch (error) {
|
| 510 |
state.textContent = "Error";
|
| 511 |
state.className = "tag bad";
|
| 512 |
-
|
| 513 |
} finally {
|
| 514 |
button.disabled = false;
|
| 515 |
button.textContent = "Find sustainable capacity";
|
|
@@ -563,7 +570,7 @@ $("topologyBtn").addEventListener("click", async () => {
|
|
| 563 |
button.disabled = true;
|
| 564 |
button.textContent = "Comparing...";
|
| 565 |
try { renderTopology((await callPython("topology_compare", { config: configFromUI() })).rows); }
|
| 566 |
-
catch (error) {
|
| 567 |
finally { button.disabled = false; button.textContent = "Compare 4 scenarios"; }
|
| 568 |
});
|
| 569 |
const topologyHeaders = ["Scenario", "GPU instances", "Goodput", "Goodput / GPU", "SLO attainment", "p95 TTFT", "p95 E2E", "p95 KV transfer", "Cache hit", "Prefill saved", "Diagnosis"];
|
|
@@ -629,7 +636,7 @@ $("designBtn").addEventListener("click", async () => {
|
|
| 629 |
button.disabled = true;
|
| 630 |
button.textContent = "Exploring...";
|
| 631 |
try { renderDesign(await callPython("design_space", { config: configFromUI(), include_disaggregated: $("includePd").checked })); }
|
| 632 |
-
catch (error) {
|
| 633 |
finally { button.disabled = false; button.textContent = "Explore design space"; }
|
| 634 |
});
|
| 635 |
const designHeaders = ["Candidate", "Perf Pareto", "Efficiency Pareto", "SLO pass", "GPU instances", "Goodput", "Goodput / GPU", "p95 TTFT", "p95 E2E", "KV peak", "Diagnosis"];
|
|
@@ -712,7 +719,7 @@ $("pairedStudyBtn").addEventListener("click", async () => {
|
|
| 712 |
} catch (error) {
|
| 713 |
$("pairedState").textContent = "Error";
|
| 714 |
$("pairedState").className = "tag bad";
|
| 715 |
-
|
| 716 |
} finally {
|
| 717 |
button.disabled = false;
|
| 718 |
button.textContent = "Run paired study";
|
|
@@ -765,7 +772,7 @@ $("robustStudyBtn").addEventListener("click", async () => {
|
|
| 765 |
} catch (error) {
|
| 766 |
$("robustState").textContent = "Error";
|
| 767 |
$("robustState").className = "tag bad";
|
| 768 |
-
|
| 769 |
} finally {
|
| 770 |
button.disabled = false;
|
| 771 |
button.textContent = "Stress-test selected hypothesis";
|
|
@@ -863,7 +870,7 @@ $("agentRunBtn").addEventListener("click", async () => {
|
|
| 863 |
$("agentRunState").textContent = "Running...";
|
| 864 |
$("agentRunState").className = "tag neutral";
|
| 865 |
try { renderAgentRun(await callPython("agent_simulate", { config: agentConfigFromUI() })); }
|
| 866 |
-
catch (error) { $("agentRunState").textContent = "Error"; $("agentRunState").className = "tag bad";
|
| 867 |
finally { button.disabled = false; button.textContent = "Run stateful session simulation"; }
|
| 868 |
});
|
| 869 |
$("agentRunCopyJson").addEventListener("click", () => { if (lastAgentRun) copyText(JSON.stringify(lastAgentRun, null, 2), "Agent-session JSON copied"); });
|
|
@@ -889,7 +896,7 @@ function renderAgentCompare(result) {
|
|
| 889 |
$("agentCompareBtn").addEventListener("click", async () => {
|
| 890 |
const button = $("agentCompareBtn"); button.disabled = true; button.textContent = "Comparing...";
|
| 891 |
try { renderAgentCompare(await callPython("agent_compare", { config: agentConfigFromUI() })); }
|
| 892 |
-
catch (error) {
|
| 893 |
finally { button.disabled = false; button.textContent = "Compare 4 policies"; }
|
| 894 |
});
|
| 895 |
$("agentCompareCopy").addEventListener("click", () => { if (lastAgentCompare) copyText(tableText(agentCompareHeaders, agentCompareTableRows(lastAgentCompare)), "Agent policy table copied"); });
|
|
@@ -919,7 +926,7 @@ function renderAgentTtl(result) {
|
|
| 919 |
$("agentTtlBtn").addEventListener("click", async () => {
|
| 920 |
const button = $("agentTtlBtn"); button.disabled = true; button.textContent = "Sweeping TTL...";
|
| 921 |
try { renderAgentTtl(await callPython("agent_ttl_sweep", { config: agentConfigFromUI() })); }
|
| 922 |
-
catch (error) {
|
| 923 |
finally { button.disabled = false; button.textContent = "Run TTL sweep"; }
|
| 924 |
});
|
| 925 |
$("agentTtlCopy").addEventListener("click", () => { if (lastAgentTtl) copyText(tableText(agentTtlHeaders, agentTtlTableRows(lastAgentTtl)), "TTL sweep copied"); });
|
|
@@ -948,7 +955,7 @@ function renderAgentMemory(result) {
|
|
| 948 |
$("agentMemoryCompareBtn").addEventListener("click", async () => {
|
| 949 |
const button = $("agentMemoryCompareBtn"); button.disabled = true; button.textContent = "Comparing policies...";
|
| 950 |
try { renderAgentMemory(await callPython("agent_memory_compare", { config: agentConfigFromUI() })); }
|
| 951 |
-
catch (error) {
|
| 952 |
finally { button.disabled = false; button.textContent = "Compare memory policies"; }
|
| 953 |
});
|
| 954 |
$("agentMemoryCopy").addEventListener("click", () => { if (lastAgentMemory) copyText(tableText(agentMemoryHeaders, agentMemoryTableRows(lastAgentMemory)), "Memory-policy table copied"); });
|
|
@@ -978,7 +985,7 @@ function renderAgentBudget(result) {
|
|
| 978 |
$("agentBudgetBtn").addEventListener("click", async () => {
|
| 979 |
const button = $("agentBudgetBtn"); button.disabled = true; button.textContent = "Stressing HBM budget...";
|
| 980 |
try { renderAgentBudget(await callPython("agent_memory_sweep", { config: agentConfigFromUI() })); }
|
| 981 |
-
catch (error) {
|
| 982 |
finally { button.disabled = false; button.textContent = "Stress HBM budget"; }
|
| 983 |
});
|
| 984 |
$("agentBudgetCopy").addEventListener("click", () => { if (lastAgentBudget) copyText(tableText(agentBudgetHeaders, agentBudgetTableRows(lastAgentBudget)), "HBM budget table copied"); });
|
|
@@ -1007,7 +1014,7 @@ function renderAgentAffinity(result) {
|
|
| 1007 |
$("agentAffinityBtn").addEventListener("click", async () => {
|
| 1008 |
const button = $("agentAffinityBtn"); button.disabled = true; button.textContent = "Sweeping affinity...";
|
| 1009 |
try { renderAgentAffinity(await callPython("agent_affinity_sweep", { config: agentConfigFromUI() })); }
|
| 1010 |
-
catch (error) {
|
| 1011 |
finally { button.disabled = false; button.textContent = "Run affinity sweep"; }
|
| 1012 |
});
|
| 1013 |
$("agentAffinityCopy").addEventListener("click", () => { if (lastAgentAffinity) copyText(tableText(agentAffinityHeaders, agentAffinityTableRows(lastAgentAffinity)), "Affinity sweep copied"); });
|
|
@@ -1081,7 +1088,7 @@ function predictivePayload() {
|
|
| 1081 |
$("predictiveCompareBtn").addEventListener("click", async () => {
|
| 1082 |
const button = $("predictiveCompareBtn"); button.disabled = true; button.textContent = "Running predictive study...";
|
| 1083 |
try { renderPredictiveStudy(await callPython("agent_predictive_tiering", predictivePayload())); }
|
| 1084 |
-
catch (error) {
|
| 1085 |
finally { button.disabled = false; button.textContent = "Compare predictive policies"; }
|
| 1086 |
});
|
| 1087 |
$("predictiveCopy").addEventListener("click", () => { if (lastPredictiveStudy) copyText(tableText(predictiveHeaders, predictiveTableRows(lastPredictiveStudy)), "Predictive-tiering table copied"); });
|
|
@@ -1114,7 +1121,7 @@ $("predictiveAlphaBtn").addEventListener("click", async () => {
|
|
| 1114 |
const button = $("predictiveAlphaBtn"); button.disabled = true; button.textContent = "Sweeping adaptation rate...";
|
| 1115 |
const payload = predictivePayload();
|
| 1116 |
try { renderPredictiveAlpha(await callPython("agent_adaptive_alpha_sweep", payload)); }
|
| 1117 |
-
catch (error) {
|
| 1118 |
finally { button.disabled = false; button.textContent = "Sweep adaptation rate"; }
|
| 1119 |
});
|
| 1120 |
$("predictiveAlphaCopy").addEventListener("click", () => { if (lastPredictiveAlpha) copyText(tableText(predictiveAlphaHeaders, predictiveAlphaTableRows(lastPredictiveAlpha)), "Adaptation-rate table copied"); });
|
|
@@ -1225,7 +1232,7 @@ $("execRunBtn").addEventListener("click", async () => {
|
|
| 1225 |
button.disabled = true; button.textContent = "Running execution model...";
|
| 1226 |
$("execRunState").textContent = "Running..."; $("execRunState").className = "tag neutral";
|
| 1227 |
try { renderExecutionRun(await callPython("execution_learning_run", { config: executionConfigFromUI() })); }
|
| 1228 |
-
catch (error) { $("execRunState").textContent = "Error"; $("execRunState").className = "tag bad";
|
| 1229 |
finally { button.disabled = false; button.textContent = "Run execution-learning simulation"; }
|
| 1230 |
});
|
| 1231 |
$("execRunCopyJson").addEventListener("click", () => { if (lastExecutionRun) copyText(JSON.stringify(lastExecutionRun, null, 2), "Execution-learning JSON copied"); });
|
|
@@ -1251,7 +1258,7 @@ function renderExecutionCompare(result) {
|
|
| 1251 |
$("execCompareBtn").addEventListener("click", async () => {
|
| 1252 |
const button = $("execCompareBtn"); button.disabled = true; button.textContent = "Comparing policies...";
|
| 1253 |
try { renderExecutionCompare(await callPython("execution_prefetch_study", { config: executionConfigFromUI() })); }
|
| 1254 |
-
catch (error) {
|
| 1255 |
finally { button.disabled = false; button.textContent = "Compare 4 policies"; }
|
| 1256 |
});
|
| 1257 |
$("execCompareCopy").addEventListener("click", () => { if (lastExecutionCompare) copyText(tableText(execCompareHeaders, execCompareTableRows(lastExecutionCompare)), "Execution policy table copied"); });
|
|
@@ -1286,7 +1293,7 @@ function renderExecutionThreshold(result) {
|
|
| 1286 |
$("execThresholdBtn").addEventListener("click", async () => {
|
| 1287 |
const button = $("execThresholdBtn"); button.disabled = true; button.textContent = "Sweeping threshold...";
|
| 1288 |
try { renderExecutionThreshold(await callPython("execution_threshold_sweep", { config: executionConfigFromUI() })); }
|
| 1289 |
-
catch (error) {
|
| 1290 |
finally { button.disabled = false; button.textContent = "Sweep confidence threshold"; }
|
| 1291 |
});
|
| 1292 |
$("execThresholdCopy").addEventListener("click", () => { if (lastExecutionThreshold) copyText(tableText(execThresholdHeaders, execThresholdTableRows(lastExecutionThreshold)), "Execution threshold table copied"); });
|
|
@@ -1319,7 +1326,7 @@ function renderExecutionDecay(result) {
|
|
| 1319 |
$("execDecayBtn").addEventListener("click", async () => {
|
| 1320 |
const button = $("execDecayBtn"); button.disabled = true; button.textContent = "Sweeping decay...";
|
| 1321 |
try { renderExecutionDecay(await callPython("execution_decay_sweep", { config: executionConfigFromUI() })); }
|
| 1322 |
-
catch (error) {
|
| 1323 |
finally { button.disabled = false; button.textContent = "Sweep transition decay"; }
|
| 1324 |
});
|
| 1325 |
$("execDecayCopy").addEventListener("click", () => { if (lastExecutionDecay) copyText(tableText(execDecayHeaders, execDecayTableRows(lastExecutionDecay)), "Execution decay table copied"); });
|
|
@@ -1355,7 +1362,7 @@ function renderExecutionPlanning(result) {
|
|
| 1355 |
$("execPlanningBtn").addEventListener("click", async () => {
|
| 1356 |
const button = $("execPlanningBtn"); button.disabled = true; button.textContent = "Comparing planners...";
|
| 1357 |
try { renderExecutionPlanning(await callPython("execution_planning_study", { config: executionConfigFromUI() })); }
|
| 1358 |
-
catch (error) {
|
| 1359 |
finally { button.disabled = false; button.textContent = "Compare planning policies"; }
|
| 1360 |
});
|
| 1361 |
$("execPlanningCopy").addEventListener("click", () => { if (lastExecutionPlanning) copyText(tableText(execPlanningHeaders, execPlanningTableRows(lastExecutionPlanning)), "Planning table copied"); });
|
|
@@ -1389,7 +1396,7 @@ function renderExecutionHorizon(result) {
|
|
| 1389 |
$("execHorizonBtn").addEventListener("click", async () => {
|
| 1390 |
const button = $("execHorizonBtn"); button.disabled = true; button.textContent = "Sweeping horizons...";
|
| 1391 |
try { renderExecutionHorizon(await callPython("execution_horizon_sweep", { config: executionConfigFromUI() })); }
|
| 1392 |
-
catch (error) {
|
| 1393 |
finally { button.disabled = false; button.textContent = "Sweep forecast horizon"; }
|
| 1394 |
});
|
| 1395 |
$("execHorizonCopy").addEventListener("click", () => { if (lastExecutionHorizon) copyText(tableText(execHorizonHeaders, execHorizonTableRows(lastExecutionHorizon)), "Horizon table copied"); });
|
|
@@ -1417,7 +1424,7 @@ function renderExecutionBudget(result) {
|
|
| 1417 |
$("execBudgetBtn").addEventListener("click", async () => {
|
| 1418 |
const button = $("execBudgetBtn"); button.disabled = true; button.textContent = "Sweeping cache budget...";
|
| 1419 |
try { renderExecutionBudget(await callPython("execution_budget_sweep", { config: executionConfigFromUI() })); }
|
| 1420 |
-
catch (error) {
|
| 1421 |
finally { button.disabled = false; button.textContent = "Sweep cache budget"; }
|
| 1422 |
});
|
| 1423 |
$("execBudgetCopy").addEventListener("click", () => { if (lastExecutionBudget) copyText(tableText(execBudgetHeaders, execBudgetTableRows(lastExecutionBudget)), "Cache budget table copied"); });
|
|
@@ -1470,7 +1477,7 @@ $("consRunBtn").addEventListener("click", async () => {
|
|
| 1470 |
renderConsolidation(await callPython("consolidation_study", {
|
| 1471 |
config: executionConfigFromUI(), repetitions: num("consSeeds"), bootstrap_samples: num("consBootstrap"),
|
| 1472 |
}));
|
| 1473 |
-
} catch (error) {
|
| 1474 |
finally { button.disabled = false; button.textContent = "Run robust policy study"; }
|
| 1475 |
});
|
| 1476 |
$("consCopy").addEventListener("click", () => { if (lastConsolidation) copyText(tableText(consHeaders, consTableRows(lastConsolidation)), "Robust policy table copied"); });
|
|
@@ -1486,7 +1493,7 @@ $("measurementFile").addEventListener("change", async (event) => {
|
|
| 1486 |
$("measurementCalibrateBtn").disabled = !runtimePill.classList.contains("ready");
|
| 1487 |
} catch (error) {
|
| 1488 |
measurementFileContent = ""; $("measurementCalibrateBtn").disabled = true;
|
| 1489 |
-
$("measurementStatus").textContent = "File read failed";
|
| 1490 |
}
|
| 1491 |
});
|
| 1492 |
|
|
@@ -1529,7 +1536,7 @@ $("measurementCalibrateBtn").addEventListener("click", async () => {
|
|
| 1529 |
cases: imported.cases, holdout_fraction: num("measurementHoldout"), seed: num("seed"),
|
| 1530 |
});
|
| 1531 |
renderCalibration(imported, calibrated);
|
| 1532 |
-
} catch (error) {
|
| 1533 |
finally { button.disabled = false; button.textContent = "Import and calibrate"; }
|
| 1534 |
});
|
| 1535 |
$("measurementCopy").addEventListener("click", () => { if (lastCalibration) copyText(tableText(measurementHeaders, measurementTableRows(lastCalibration)), "Validation table copied"); });
|
|
@@ -1541,7 +1548,7 @@ $("consReportBtn").addEventListener("click", async () => {
|
|
| 1541 |
try {
|
| 1542 |
const result = await callPython("research_report", { robust: lastConsolidation, calibration: lastCalibration });
|
| 1543 |
downloadTextFile(`inferscale_research-consolidation_${stamp()}.md`, result.markdown, "text/markdown;charset=utf-8");
|
| 1544 |
-
} catch (error) {
|
| 1545 |
finally { button.disabled = false; button.textContent = "Download Markdown report"; }
|
| 1546 |
});
|
| 1547 |
|
|
@@ -1612,7 +1619,7 @@ $("traceFile").addEventListener("change", async (event) => {
|
|
| 1612 |
const file = event.target.files?.[0];
|
| 1613 |
if (!file) return;
|
| 1614 |
try { await loadTraceFile(file); }
|
| 1615 |
-
catch (error) { traceRequests = []; $("traceStatus").textContent = "Trace rejected";
|
| 1616 |
});
|
| 1617 |
$("traceClearBtn").addEventListener("click", () => {
|
| 1618 |
traceRequests = [];
|
|
@@ -1641,13 +1648,36 @@ $("prefixCache").addEventListener("change", syncConditionalControls);
|
|
| 1641 |
$("arrival").addEventListener("change", syncConditionalControls);
|
| 1642 |
syncConditionalControls();
|
| 1643 |
|
| 1644 |
-
|
| 1645 |
-
|
| 1646 |
-
|
| 1647 |
-
|
| 1648 |
-
|
| 1649 |
-
|
| 1650 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1651 |
});
|
| 1652 |
}
|
| 1653 |
|
|
|
|
| 32 |
let traceRequests = [];
|
| 33 |
|
| 34 |
const COLORS = {
|
| 35 |
+
blue: "#78a1db",
|
| 36 |
+
blue2: "#5f86bf",
|
| 37 |
+
steel: "#91a0b2",
|
| 38 |
+
green: "#69c39a",
|
| 39 |
+
amber: "#d2b36a",
|
| 40 |
+
red: "#d97f89",
|
| 41 |
+
purple: "#9288c7",
|
| 42 |
+
gray: "#687585",
|
| 43 |
grid: "rgba(140,155,175,.14)",
|
| 44 |
};
|
| 45 |
|
|
|
|
| 135 |
const pad = (v) => String(v).padStart(2, "0");
|
| 136 |
return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
|
| 137 |
}
|
| 138 |
+
function showToast(message, kind = "info", timeoutMs = 2200) {
|
| 139 |
const toast = $("toast");
|
| 140 |
toast.textContent = message;
|
| 141 |
+
toast.classList.toggle("error", kind === "error");
|
| 142 |
toast.classList.add("show");
|
| 143 |
if (toastTimer) clearTimeout(toastTimer);
|
| 144 |
+
toastTimer = setTimeout(() => {
|
| 145 |
+
toast.classList.remove("show", "error");
|
| 146 |
+
}, timeoutMs);
|
| 147 |
+
}
|
| 148 |
+
function reportError(context, error) {
|
| 149 |
+
console.error(context, error);
|
| 150 |
+
showToast(`${context}: ${error.message}`, "error", 5200);
|
| 151 |
}
|
| 152 |
async function copyText(text, message) {
|
| 153 |
try {
|
|
|
|
| 217 |
Chart.defaults.borderColor = COLORS.grid;
|
| 218 |
Chart.defaults.font.family = getComputedStyle(document.body).fontFamily;
|
| 219 |
Chart.defaults.font.size = 13;
|
| 220 |
+
Chart.defaults.animation.duration = 0;
|
| 221 |
|
| 222 |
function commonChartOptions() {
|
| 223 |
return { responsive: true, maintainAspectRatio: false };
|
|
|
|
| 401 |
} catch (error) {
|
| 402 |
state.textContent = "Error";
|
| 403 |
state.className = "tag bad";
|
| 404 |
+
reportError("Simulation failed", error);
|
| 405 |
} finally {
|
| 406 |
button.textContent = "Run simulation";
|
| 407 |
button.disabled = false;
|
|
|
|
| 442 |
button.disabled = true;
|
| 443 |
button.textContent = "Comparing...";
|
| 444 |
try { renderArena((await callPython("compare", { config: configFromUI() })).rows); }
|
| 445 |
+
catch (error) { reportError("Scheduler comparison failed", error); }
|
| 446 |
finally { button.disabled = false; button.textContent = "Compare schedulers"; }
|
| 447 |
});
|
| 448 |
const arenaHeaders = ["Scheduler", "Goodput", "SLO attainment", "p95 TTFT", "p95 E2E", "KV peak", "Unfinished", "Diagnosis"];
|
|
|
|
| 516 |
} catch (error) {
|
| 517 |
state.textContent = "Error";
|
| 518 |
state.className = "tag bad";
|
| 519 |
+
reportError("Capacity search failed", error);
|
| 520 |
} finally {
|
| 521 |
button.disabled = false;
|
| 522 |
button.textContent = "Find sustainable capacity";
|
|
|
|
| 570 |
button.disabled = true;
|
| 571 |
button.textContent = "Comparing...";
|
| 572 |
try { renderTopology((await callPython("topology_compare", { config: configFromUI() })).rows); }
|
| 573 |
+
catch (error) { reportError("Topology comparison failed", error); }
|
| 574 |
finally { button.disabled = false; button.textContent = "Compare 4 scenarios"; }
|
| 575 |
});
|
| 576 |
const topologyHeaders = ["Scenario", "GPU instances", "Goodput", "Goodput / GPU", "SLO attainment", "p95 TTFT", "p95 E2E", "p95 KV transfer", "Cache hit", "Prefill saved", "Diagnosis"];
|
|
|
|
| 636 |
button.disabled = true;
|
| 637 |
button.textContent = "Exploring...";
|
| 638 |
try { renderDesign(await callPython("design_space", { config: configFromUI(), include_disaggregated: $("includePd").checked })); }
|
| 639 |
+
catch (error) { reportError("Design-space sweep failed", error); }
|
| 640 |
finally { button.disabled = false; button.textContent = "Explore design space"; }
|
| 641 |
});
|
| 642 |
const designHeaders = ["Candidate", "Perf Pareto", "Efficiency Pareto", "SLO pass", "GPU instances", "Goodput", "Goodput / GPU", "p95 TTFT", "p95 E2E", "KV peak", "Diagnosis"];
|
|
|
|
| 719 |
} catch (error) {
|
| 720 |
$("pairedState").textContent = "Error";
|
| 721 |
$("pairedState").className = "tag bad";
|
| 722 |
+
reportError("Paired study failed", error);
|
| 723 |
} finally {
|
| 724 |
button.disabled = false;
|
| 725 |
button.textContent = "Run paired study";
|
|
|
|
| 772 |
} catch (error) {
|
| 773 |
$("robustState").textContent = "Error";
|
| 774 |
$("robustState").className = "tag bad";
|
| 775 |
+
reportError("Sensitivity study failed", error);
|
| 776 |
} finally {
|
| 777 |
button.disabled = false;
|
| 778 |
button.textContent = "Stress-test selected hypothesis";
|
|
|
|
| 870 |
$("agentRunState").textContent = "Running...";
|
| 871 |
$("agentRunState").className = "tag neutral";
|
| 872 |
try { renderAgentRun(await callPython("agent_simulate", { config: agentConfigFromUI() })); }
|
| 873 |
+
catch (error) { $("agentRunState").textContent = "Error"; $("agentRunState").className = "tag bad"; reportError("Agent session simulation failed", error); }
|
| 874 |
finally { button.disabled = false; button.textContent = "Run stateful session simulation"; }
|
| 875 |
});
|
| 876 |
$("agentRunCopyJson").addEventListener("click", () => { if (lastAgentRun) copyText(JSON.stringify(lastAgentRun, null, 2), "Agent-session JSON copied"); });
|
|
|
|
| 896 |
$("agentCompareBtn").addEventListener("click", async () => {
|
| 897 |
const button = $("agentCompareBtn"); button.disabled = true; button.textContent = "Comparing...";
|
| 898 |
try { renderAgentCompare(await callPython("agent_compare", { config: agentConfigFromUI() })); }
|
| 899 |
+
catch (error) { reportError("Agent policy comparison failed", error); }
|
| 900 |
finally { button.disabled = false; button.textContent = "Compare 4 policies"; }
|
| 901 |
});
|
| 902 |
$("agentCompareCopy").addEventListener("click", () => { if (lastAgentCompare) copyText(tableText(agentCompareHeaders, agentCompareTableRows(lastAgentCompare)), "Agent policy table copied"); });
|
|
|
|
| 926 |
$("agentTtlBtn").addEventListener("click", async () => {
|
| 927 |
const button = $("agentTtlBtn"); button.disabled = true; button.textContent = "Sweeping TTL...";
|
| 928 |
try { renderAgentTtl(await callPython("agent_ttl_sweep", { config: agentConfigFromUI() })); }
|
| 929 |
+
catch (error) { reportError("TTL sweep failed", error); }
|
| 930 |
finally { button.disabled = false; button.textContent = "Run TTL sweep"; }
|
| 931 |
});
|
| 932 |
$("agentTtlCopy").addEventListener("click", () => { if (lastAgentTtl) copyText(tableText(agentTtlHeaders, agentTtlTableRows(lastAgentTtl)), "TTL sweep copied"); });
|
|
|
|
| 955 |
$("agentMemoryCompareBtn").addEventListener("click", async () => {
|
| 956 |
const button = $("agentMemoryCompareBtn"); button.disabled = true; button.textContent = "Comparing policies...";
|
| 957 |
try { renderAgentMemory(await callPython("agent_memory_compare", { config: agentConfigFromUI() })); }
|
| 958 |
+
catch (error) { reportError("Memory policy comparison failed", error); }
|
| 959 |
finally { button.disabled = false; button.textContent = "Compare memory policies"; }
|
| 960 |
});
|
| 961 |
$("agentMemoryCopy").addEventListener("click", () => { if (lastAgentMemory) copyText(tableText(agentMemoryHeaders, agentMemoryTableRows(lastAgentMemory)), "Memory-policy table copied"); });
|
|
|
|
| 985 |
$("agentBudgetBtn").addEventListener("click", async () => {
|
| 986 |
const button = $("agentBudgetBtn"); button.disabled = true; button.textContent = "Stressing HBM budget...";
|
| 987 |
try { renderAgentBudget(await callPython("agent_memory_sweep", { config: agentConfigFromUI() })); }
|
| 988 |
+
catch (error) { reportError("HBM budget study failed", error); }
|
| 989 |
finally { button.disabled = false; button.textContent = "Stress HBM budget"; }
|
| 990 |
});
|
| 991 |
$("agentBudgetCopy").addEventListener("click", () => { if (lastAgentBudget) copyText(tableText(agentBudgetHeaders, agentBudgetTableRows(lastAgentBudget)), "HBM budget table copied"); });
|
|
|
|
| 1014 |
$("agentAffinityBtn").addEventListener("click", async () => {
|
| 1015 |
const button = $("agentAffinityBtn"); button.disabled = true; button.textContent = "Sweeping affinity...";
|
| 1016 |
try { renderAgentAffinity(await callPython("agent_affinity_sweep", { config: agentConfigFromUI() })); }
|
| 1017 |
+
catch (error) { reportError("Affinity sweep failed", error); }
|
| 1018 |
finally { button.disabled = false; button.textContent = "Run affinity sweep"; }
|
| 1019 |
});
|
| 1020 |
$("agentAffinityCopy").addEventListener("click", () => { if (lastAgentAffinity) copyText(tableText(agentAffinityHeaders, agentAffinityTableRows(lastAgentAffinity)), "Affinity sweep copied"); });
|
|
|
|
| 1088 |
$("predictiveCompareBtn").addEventListener("click", async () => {
|
| 1089 |
const button = $("predictiveCompareBtn"); button.disabled = true; button.textContent = "Running predictive study...";
|
| 1090 |
try { renderPredictiveStudy(await callPython("agent_predictive_tiering", predictivePayload())); }
|
| 1091 |
+
catch (error) { reportError("Predictive tiering study failed", error); }
|
| 1092 |
finally { button.disabled = false; button.textContent = "Compare predictive policies"; }
|
| 1093 |
});
|
| 1094 |
$("predictiveCopy").addEventListener("click", () => { if (lastPredictiveStudy) copyText(tableText(predictiveHeaders, predictiveTableRows(lastPredictiveStudy)), "Predictive-tiering table copied"); });
|
|
|
|
| 1121 |
const button = $("predictiveAlphaBtn"); button.disabled = true; button.textContent = "Sweeping adaptation rate...";
|
| 1122 |
const payload = predictivePayload();
|
| 1123 |
try { renderPredictiveAlpha(await callPython("agent_adaptive_alpha_sweep", payload)); }
|
| 1124 |
+
catch (error) { reportError("Adaptation-rate sweep failed", error); }
|
| 1125 |
finally { button.disabled = false; button.textContent = "Sweep adaptation rate"; }
|
| 1126 |
});
|
| 1127 |
$("predictiveAlphaCopy").addEventListener("click", () => { if (lastPredictiveAlpha) copyText(tableText(predictiveAlphaHeaders, predictiveAlphaTableRows(lastPredictiveAlpha)), "Adaptation-rate table copied"); });
|
|
|
|
| 1232 |
button.disabled = true; button.textContent = "Running execution model...";
|
| 1233 |
$("execRunState").textContent = "Running..."; $("execRunState").className = "tag neutral";
|
| 1234 |
try { renderExecutionRun(await callPython("execution_learning_run", { config: executionConfigFromUI() })); }
|
| 1235 |
+
catch (error) { $("execRunState").textContent = "Error"; $("execRunState").className = "tag bad"; reportError("Execution-learning run failed", error); }
|
| 1236 |
finally { button.disabled = false; button.textContent = "Run execution-learning simulation"; }
|
| 1237 |
});
|
| 1238 |
$("execRunCopyJson").addEventListener("click", () => { if (lastExecutionRun) copyText(JSON.stringify(lastExecutionRun, null, 2), "Execution-learning JSON copied"); });
|
|
|
|
| 1258 |
$("execCompareBtn").addEventListener("click", async () => {
|
| 1259 |
const button = $("execCompareBtn"); button.disabled = true; button.textContent = "Comparing policies...";
|
| 1260 |
try { renderExecutionCompare(await callPython("execution_prefetch_study", { config: executionConfigFromUI() })); }
|
| 1261 |
+
catch (error) { reportError("Execution prefetch study failed", error); }
|
| 1262 |
finally { button.disabled = false; button.textContent = "Compare 4 policies"; }
|
| 1263 |
});
|
| 1264 |
$("execCompareCopy").addEventListener("click", () => { if (lastExecutionCompare) copyText(tableText(execCompareHeaders, execCompareTableRows(lastExecutionCompare)), "Execution policy table copied"); });
|
|
|
|
| 1293 |
$("execThresholdBtn").addEventListener("click", async () => {
|
| 1294 |
const button = $("execThresholdBtn"); button.disabled = true; button.textContent = "Sweeping threshold...";
|
| 1295 |
try { renderExecutionThreshold(await callPython("execution_threshold_sweep", { config: executionConfigFromUI() })); }
|
| 1296 |
+
catch (error) { reportError("Execution threshold sweep failed", error); }
|
| 1297 |
finally { button.disabled = false; button.textContent = "Sweep confidence threshold"; }
|
| 1298 |
});
|
| 1299 |
$("execThresholdCopy").addEventListener("click", () => { if (lastExecutionThreshold) copyText(tableText(execThresholdHeaders, execThresholdTableRows(lastExecutionThreshold)), "Execution threshold table copied"); });
|
|
|
|
| 1326 |
$("execDecayBtn").addEventListener("click", async () => {
|
| 1327 |
const button = $("execDecayBtn"); button.disabled = true; button.textContent = "Sweeping decay...";
|
| 1328 |
try { renderExecutionDecay(await callPython("execution_decay_sweep", { config: executionConfigFromUI() })); }
|
| 1329 |
+
catch (error) { reportError("Execution decay sweep failed", error); }
|
| 1330 |
finally { button.disabled = false; button.textContent = "Sweep transition decay"; }
|
| 1331 |
});
|
| 1332 |
$("execDecayCopy").addEventListener("click", () => { if (lastExecutionDecay) copyText(tableText(execDecayHeaders, execDecayTableRows(lastExecutionDecay)), "Execution decay table copied"); });
|
|
|
|
| 1362 |
$("execPlanningBtn").addEventListener("click", async () => {
|
| 1363 |
const button = $("execPlanningBtn"); button.disabled = true; button.textContent = "Comparing planners...";
|
| 1364 |
try { renderExecutionPlanning(await callPython("execution_planning_study", { config: executionConfigFromUI() })); }
|
| 1365 |
+
catch (error) { reportError("Execution planning study failed", error); }
|
| 1366 |
finally { button.disabled = false; button.textContent = "Compare planning policies"; }
|
| 1367 |
});
|
| 1368 |
$("execPlanningCopy").addEventListener("click", () => { if (lastExecutionPlanning) copyText(tableText(execPlanningHeaders, execPlanningTableRows(lastExecutionPlanning)), "Planning table copied"); });
|
|
|
|
| 1396 |
$("execHorizonBtn").addEventListener("click", async () => {
|
| 1397 |
const button = $("execHorizonBtn"); button.disabled = true; button.textContent = "Sweeping horizons...";
|
| 1398 |
try { renderExecutionHorizon(await callPython("execution_horizon_sweep", { config: executionConfigFromUI() })); }
|
| 1399 |
+
catch (error) { reportError("Forecast horizon sweep failed", error); }
|
| 1400 |
finally { button.disabled = false; button.textContent = "Sweep forecast horizon"; }
|
| 1401 |
});
|
| 1402 |
$("execHorizonCopy").addEventListener("click", () => { if (lastExecutionHorizon) copyText(tableText(execHorizonHeaders, execHorizonTableRows(lastExecutionHorizon)), "Horizon table copied"); });
|
|
|
|
| 1424 |
$("execBudgetBtn").addEventListener("click", async () => {
|
| 1425 |
const button = $("execBudgetBtn"); button.disabled = true; button.textContent = "Sweeping cache budget...";
|
| 1426 |
try { renderExecutionBudget(await callPython("execution_budget_sweep", { config: executionConfigFromUI() })); }
|
| 1427 |
+
catch (error) { reportError("Cache budget study failed", error); }
|
| 1428 |
finally { button.disabled = false; button.textContent = "Sweep cache budget"; }
|
| 1429 |
});
|
| 1430 |
$("execBudgetCopy").addEventListener("click", () => { if (lastExecutionBudget) copyText(tableText(execBudgetHeaders, execBudgetTableRows(lastExecutionBudget)), "Cache budget table copied"); });
|
|
|
|
| 1477 |
renderConsolidation(await callPython("consolidation_study", {
|
| 1478 |
config: executionConfigFromUI(), repetitions: num("consSeeds"), bootstrap_samples: num("consBootstrap"),
|
| 1479 |
}));
|
| 1480 |
+
} catch (error) { reportError("Robust policy study failed", error); }
|
| 1481 |
finally { button.disabled = false; button.textContent = "Run robust policy study"; }
|
| 1482 |
});
|
| 1483 |
$("consCopy").addEventListener("click", () => { if (lastConsolidation) copyText(tableText(consHeaders, consTableRows(lastConsolidation)), "Robust policy table copied"); });
|
|
|
|
| 1493 |
$("measurementCalibrateBtn").disabled = !runtimePill.classList.contains("ready");
|
| 1494 |
} catch (error) {
|
| 1495 |
measurementFileContent = ""; $("measurementCalibrateBtn").disabled = true;
|
| 1496 |
+
$("measurementStatus").textContent = "File read failed"; reportError("Measurement file failed", error);
|
| 1497 |
}
|
| 1498 |
});
|
| 1499 |
|
|
|
|
| 1536 |
cases: imported.cases, holdout_fraction: num("measurementHoldout"), seed: num("seed"),
|
| 1537 |
});
|
| 1538 |
renderCalibration(imported, calibrated);
|
| 1539 |
+
} catch (error) { reportError("Measurement calibration failed", error); }
|
| 1540 |
finally { button.disabled = false; button.textContent = "Import and calibrate"; }
|
| 1541 |
});
|
| 1542 |
$("measurementCopy").addEventListener("click", () => { if (lastCalibration) copyText(tableText(measurementHeaders, measurementTableRows(lastCalibration)), "Validation table copied"); });
|
|
|
|
| 1548 |
try {
|
| 1549 |
const result = await callPython("research_report", { robust: lastConsolidation, calibration: lastCalibration });
|
| 1550 |
downloadTextFile(`inferscale_research-consolidation_${stamp()}.md`, result.markdown, "text/markdown;charset=utf-8");
|
| 1551 |
+
} catch (error) { reportError("Report generation failed", error); }
|
| 1552 |
finally { button.disabled = false; button.textContent = "Download Markdown report"; }
|
| 1553 |
});
|
| 1554 |
|
|
|
|
| 1619 |
const file = event.target.files?.[0];
|
| 1620 |
if (!file) return;
|
| 1621 |
try { await loadTraceFile(file); }
|
| 1622 |
+
catch (error) { traceRequests = []; $("traceStatus").textContent = "Trace rejected"; reportError("Trace load failed", error); }
|
| 1623 |
});
|
| 1624 |
$("traceClearBtn").addEventListener("click", () => {
|
| 1625 |
traceRequests = [];
|
|
|
|
| 1648 |
$("arrival").addEventListener("change", syncConditionalControls);
|
| 1649 |
syncConditionalControls();
|
| 1650 |
|
| 1651 |
+
const tabButtons = [...document.querySelectorAll(".tab")];
|
| 1652 |
+
function activateTab(tab) {
|
| 1653 |
+
for (const item of tabButtons) {
|
| 1654 |
+
const active = item === tab;
|
| 1655 |
+
item.classList.toggle("active", active);
|
| 1656 |
+
item.setAttribute("aria-selected", active ? "true" : "false");
|
| 1657 |
+
item.tabIndex = active ? 0 : -1;
|
| 1658 |
+
}
|
| 1659 |
+
document.querySelectorAll(".tab-panel").forEach((panel) => {
|
| 1660 |
+
panel.classList.remove("active");
|
| 1661 |
+
panel.setAttribute("aria-hidden", "true");
|
| 1662 |
+
});
|
| 1663 |
+
const targetPanel = $(tab.dataset.tab);
|
| 1664 |
+
targetPanel.classList.add("active");
|
| 1665 |
+
targetPanel.setAttribute("aria-hidden", "false");
|
| 1666 |
+
setTimeout(() => Object.values(charts).forEach((chart) => chart.resize()), 20);
|
| 1667 |
+
}
|
| 1668 |
+
for (const tab of tabButtons) {
|
| 1669 |
+
tab.addEventListener("click", () => activateTab(tab));
|
| 1670 |
+
tab.addEventListener("keydown", (event) => {
|
| 1671 |
+
if (!["ArrowLeft", "ArrowRight", "Home", "End"].includes(event.key)) return;
|
| 1672 |
+
event.preventDefault();
|
| 1673 |
+
const current = tabButtons.indexOf(tab);
|
| 1674 |
+
let next = current;
|
| 1675 |
+
if (event.key === "ArrowLeft") next = (current - 1 + tabButtons.length) % tabButtons.length;
|
| 1676 |
+
if (event.key === "ArrowRight") next = (current + 1) % tabButtons.length;
|
| 1677 |
+
if (event.key === "Home") next = 0;
|
| 1678 |
+
if (event.key === "End") next = tabButtons.length - 1;
|
| 1679 |
+
tabButtons[next].focus();
|
| 1680 |
+
activateTab(tabButtons[next]);
|
| 1681 |
});
|
| 1682 |
}
|
| 1683 |
|
docs/architecture.md
CHANGED
|
@@ -104,7 +104,7 @@ without rewriting workload generation, scheduling, cache logic, P/D orchestratio
|
|
| 104 |
|
| 105 |
## Agent memory tiering
|
| 106 |
|
| 107 |
-
Stateful
|
| 108 |
|
| 109 |
```text
|
| 110 |
turn completes
|
|
|
|
| 104 |
|
| 105 |
## Agent memory tiering
|
| 106 |
|
| 107 |
+
Stateful Stateful Sessions has an additional memory path that is independent from the stateless/P-D simulator:
|
| 108 |
|
| 109 |
```text
|
| 110 |
turn completes
|
docs/design.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Interface design notes
|
| 2 |
+
|
| 3 |
+
InferScale is a research workbench, not a SaaS product. The UI is intentionally quiet so the experiment is the visual focus.
|
| 4 |
+
|
| 5 |
+
## Design rules
|
| 6 |
+
|
| 7 |
+
- **One job per screen.** Controls define an experiment; results explain that experiment. Secondary actions stay visually subordinate.
|
| 8 |
+
- **One accent color.** Blue marks the selected tab, primary action, and key chart series. Status colors are reserved for pass/fail/warning semantics.
|
| 9 |
+
- **Flat hierarchy.** Panels and charts use thin borders and square geometry. Avoid nested rounded cards, glass effects, gradients, glowing shadows, and decorative badges.
|
| 10 |
+
- **Editorial type.** System sans-serif for prose and controls; monospace only for evidence/provenance snippets. No decorative display font is required.
|
| 11 |
+
- **Spacing over decoration.** Use consistent 8/12/16/24 px rhythms before adding borders or backgrounds.
|
| 12 |
+
- **Labels are literal.** Prefer `Scheduler comparison`, `Cache budget`, and `Held-out calibration` over promotional names such as “arena”, “command center”, or “insights”.
|
| 13 |
+
- **Status is text, not chip soup.** Runtime/pass/fail state should be legible without turning every fact into a badge.
|
| 14 |
+
- **Progressive disclosure.** Prefix, trace, P/D, predictive, and calibration controls appear only when relevant.
|
| 15 |
+
- **No company chrome.** No promotional footer, testimonials, pricing-style cards, or fake product navigation.
|
| 16 |
+
- **Motion has a job.** Only the runtime header retracts after initialization; charts do not animate. Reduced-motion preferences are honored.
|
| 17 |
+
|
| 18 |
+
## Tokens
|
| 19 |
+
|
| 20 |
+
The public CSS defines a small neutral palette:
|
| 21 |
+
|
| 22 |
+
```text
|
| 23 |
+
background #080c11
|
| 24 |
+
surface #0c1219
|
| 25 |
+
inset surface #090e14
|
| 26 |
+
border #27313d
|
| 27 |
+
text #e3e8ee
|
| 28 |
+
muted text #929dab
|
| 29 |
+
accent #6f96cf
|
| 30 |
+
success #68c394
|
| 31 |
+
error #df7d89
|
| 32 |
+
warning #d2b36a
|
| 33 |
+
```
|
| 34 |
+
|
| 35 |
+
Corners are 0–2 px for most surfaces and controls. The interface does not use gradients or pill-shaped containers.
|
| 36 |
+
|
| 37 |
+
## Maintenance checklist
|
| 38 |
+
|
| 39 |
+
Before shipping a UI change:
|
| 40 |
+
|
| 41 |
+
1. Does the element help configure, inspect, compare, or export an experiment?
|
| 42 |
+
2. Can an existing treatment be reused instead of introducing another card/button/badge style?
|
| 43 |
+
3. Is the copy literal and specific?
|
| 44 |
+
4. Is information duplicated between a heading, kicker, chip, and paragraph?
|
| 45 |
+
5. Does the layout still work at 720 px and 1100 px breakpoints?
|
| 46 |
+
6. Is the control keyboard-focusable and does focus remain visible?
|
| 47 |
+
7. Does `scripts/release_check.py` still pass the public-design guardrails?
|
docs/methodology.md
CHANGED
|
@@ -134,7 +134,7 @@ The perturbation distribution is deliberately described as a **sensitivity analy
|
|
| 134 |
|
| 135 |
Agent mode is a separate program-level discrete-event model. Session arrivals are open-loop. Each session receives a deterministic number of turns, token increments, outputs, and tool gaps from a seeded trace. A later turn cannot become ready until the prior turn completes and its tool gap elapses.
|
| 136 |
|
| 137 |
-
Each simulated replica is a serial analytical service station in this mode. This is deliberate: dynamic batching remains covered by Serving
|
| 138 |
|
| 139 |
1. **cross-turn reuse:** a resident KV entry means the next turn prefills only appended tokens;
|
| 140 |
2. **tool-gap residency:** retained KV occupies memory while the program waits outside the model;
|
|
@@ -201,7 +201,7 @@ This sequencing is important: directly updating the predictor at LLM-turn comple
|
|
| 201 |
|
| 202 |
## Non-stationary predictive-tiering study
|
| 203 |
|
| 204 |
-
The Predictive
|
| 205 |
|
| 206 |
The study compares:
|
| 207 |
|
|
@@ -231,7 +231,7 @@ The EWMA alpha sweep replays the exact same shifted trace for every candidate al
|
|
| 231 |
|
| 232 |
## Online execution-transition learning
|
| 233 |
|
| 234 |
-
Execution
|
| 235 |
|
| 236 |
A synthetic workflow is generated from a first-order role-transition matrix over five agent roles plus an `END` state. At `shift_fraction` of the arrival horizon a second transition matrix becomes active. Every candidate in a controlled study replays the exact same workflows.
|
| 237 |
|
|
@@ -276,14 +276,14 @@ This distinction matters because multi-step prefetch can be useful even when a p
|
|
| 276 |
|
| 277 |
### Controlled studies
|
| 278 |
|
| 279 |
-
The
|
| 280 |
|
| 281 |
Oracle next-role and oracle future-set candidates are **clairvoyant information bounds**, not guaranteed serving-performance upper bounds. Perfect future information may still produce poor global resource behavior when multiple workflows share a small cache and serialized transfer path.
|
| 282 |
|
| 283 |
|
| 284 |
## Research consolidation and robust policy ranking
|
| 285 |
|
| 286 |
-
The final research layer deliberately stops adding new serving mechanisms and asks whether policy conclusions survive workload variation. `repeated_seed_policy_study` generates matched workflow traces across multiple deterministic seeds and replays every deployable Execution
|
| 287 |
|
| 288 |
- median and mean p95 step TTFT;
|
| 289 |
- a bootstrap interval over seed-level TTFT;
|
|
|
|
| 134 |
|
| 135 |
Agent mode is a separate program-level discrete-event model. Session arrivals are open-loop. Each session receives a deterministic number of turns, token increments, outputs, and tool gaps from a seeded trace. A later turn cannot become ready until the prior turn completes and its tool gap elapses.
|
| 136 |
|
| 137 |
+
Each simulated replica is a serial analytical service station in this mode. This is deliberate: dynamic batching remains covered by Serving section, while Stateful Sessions isolates four stateful effects:
|
| 138 |
|
| 139 |
1. **cross-turn reuse:** a resident KV entry means the next turn prefills only appended tokens;
|
| 140 |
2. **tool-gap residency:** retained KV occupies memory while the program waits outside the model;
|
|
|
|
| 201 |
|
| 202 |
## Non-stationary predictive-tiering study
|
| 203 |
|
| 204 |
+
The Predictive tiering experiment generates one common program trace and replays it across fixed and adaptive policies. At a configured fraction of the arrival horizon, the duration scale of slower external tools (`database` and `remote_api`) is multiplied by a user-selected factor. Faster local-style tools remain unchanged.
|
| 205 |
|
| 206 |
The study compares:
|
| 207 |
|
|
|
|
| 231 |
|
| 232 |
## Online execution-transition learning
|
| 233 |
|
| 234 |
+
Execution Model models a different reuse object from Stateful Sessions. Stateful Sessions tracks one user's growing cross-turn KV state. Execution Model tracks reusable *static agent prefixes* (for example planner/retriever/reasoner system context) that can be shared across workflows.
|
| 235 |
|
| 236 |
A synthetic workflow is generated from a first-order role-transition matrix over five agent roles plus an `END` state. At `shift_fraction` of the arrival horizon a second transition matrix becomes active. Every candidate in a controlled study replays the exact same workflows.
|
| 237 |
|
|
|
|
| 276 |
|
| 277 |
### Controlled studies
|
| 278 |
|
| 279 |
+
The confidence-threshold sweep varies when one-step learned predictions are acted on. The transition-decay sweep varies transition decay. The prefetch-planning comparison compares top-1, multi-step top-k, utility-aware multi-step, and a clairvoyant realized-future-set information bound. The forecast-horizon sweep varies rollout depth on a common trace. The prefix-cache budget sweep runs top-1, multi-step, and utility-aware planners at multiple shared-prefix working-set budgets.
|
| 280 |
|
| 281 |
Oracle next-role and oracle future-set candidates are **clairvoyant information bounds**, not guaranteed serving-performance upper bounds. Perfect future information may still produce poor global resource behavior when multiple workflows share a small cache and serialized transfer path.
|
| 282 |
|
| 283 |
|
| 284 |
## Research consolidation and robust policy ranking
|
| 285 |
|
| 286 |
+
The final research layer deliberately stops adding new serving mechanisms and asks whether policy conclusions survive workload variation. `repeated_seed_policy_study` generates matched workflow traces across multiple deterministic seeds and replays every deployable Execution Model policy on each seed. It reports:
|
| 287 |
|
| 288 |
- median and mean p95 step TTFT;
|
| 289 |
- a bootstrap interval over seed-level TTFT;
|
docs/research.md
CHANGED
|
@@ -68,7 +68,7 @@ Vanguard highlights two methodology points that directly influence InferScale:
|
|
| 68 |
1. **open-loop replay** avoids coordinated omission by scheduling requests independently of response time;
|
| 69 |
2. repeated load-test analysis benefits from reproducibility statistics and bootstrap confidence intervals.
|
| 70 |
|
| 71 |
-
InferScale's generated workloads are open-loop, trace replay preserves externally supplied arrivals, and
|
| 72 |
|
| 73 |
Source: https://arxiv.org/abs/2606.22013
|
| 74 |
|
|
@@ -143,7 +143,7 @@ Source: https://arxiv.org/abs/2604.26968
|
|
| 143 |
|
| 144 |
CacheScout argues that future KV reuse in multi-agent systems is governed by execution semantics rather than recency alone. It learns agent execution transitions online without requiring predefined workflow graphs or offline training, then uses the learned model for cache eviction and proactive prefetching.
|
| 145 |
|
| 146 |
-
InferScale inspiration: the **Execution
|
| 147 |
|
| 148 |
Source: https://arxiv.org/abs/2608.14624
|
| 149 |
|
|
@@ -159,7 +159,7 @@ Source: https://arxiv.org/abs/2605.06472
|
|
| 159 |
|
| 160 |
A recent runtime-layer proposal formalizes agent-aware serving policies around observe/score/predict/act primitives and demonstrates online transition learning plus between-step KV prefetch as one concrete policy family.
|
| 161 |
|
| 162 |
-
InferScale inspiration: keep observation and action timing explicit. In Execution
|
| 163 |
|
| 164 |
Source: https://arxiv.org/abs/2605.27744
|
| 165 |
|
|
|
|
| 68 |
1. **open-loop replay** avoids coordinated omission by scheduling requests independently of response time;
|
| 69 |
2. repeated load-test analysis benefits from reproducibility statistics and bootstrap confidence intervals.
|
| 70 |
|
| 71 |
+
InferScale's generated workloads are open-loop, trace replay preserves externally supplied arrivals, and A/B Studies use repeated paired simulations with bootstrap intervals.
|
| 72 |
|
| 73 |
Source: https://arxiv.org/abs/2606.22013
|
| 74 |
|
|
|
|
| 143 |
|
| 144 |
CacheScout argues that future KV reuse in multi-agent systems is governed by execution semantics rather than recency alone. It learns agent execution transitions online without requiring predefined workflow graphs or offline training, then uses the learned model for cache eviction and proactive prefetching.
|
| 145 |
|
| 146 |
+
InferScale inspiration: the **Execution Model** lab implements a deliberately smaller online transition learner over synthetic agent roles. The model is updated only after a transition is observed; learned forecasts can drive one-step or multi-step static-prefix prefetch into a bounded HBM cache. InferScale also measures calibration and downstream action quality separately. It does not reproduce CacheScout's vLLM integration, survival policy, or production critical-path design.
|
| 147 |
|
| 148 |
Source: https://arxiv.org/abs/2608.14624
|
| 149 |
|
|
|
|
| 159 |
|
| 160 |
A recent runtime-layer proposal formalizes agent-aware serving policies around observe/score/predict/act primitives and demonstrates online transition learning plus between-step KV prefetch as one concrete policy family.
|
| 161 |
|
| 162 |
+
InferScale inspiration: keep observation and action timing explicit. In Execution Model, the prediction is made before the next role is revealed, the transition model is updated only when that role becomes observable, and the oracle policy remains separately tagged as a clairvoyant upper bound.
|
| 163 |
|
| 164 |
Source: https://arxiv.org/abs/2605.27744
|
| 165 |
|
docs/validation.md
CHANGED
|
@@ -84,7 +84,7 @@ The calibration script fits only on the training cases and reports baseline/cali
|
|
| 84 |
- radix-tree prefix-cache eviction/scheduling
|
| 85 |
- speculative decoding
|
| 86 |
- Attention-FFN disaggregation
|
| 87 |
-
- production-fidelity agentic serving or dynamic batching inside
|
| 88 |
- real tool execution or production agent-runtime integration
|
| 89 |
- claims that the bounded offline oracle is globally optimal
|
| 90 |
- claims that simulator bootstrap intervals quantify real-hardware error
|
|
|
|
| 84 |
- radix-tree prefix-cache eviction/scheduling
|
| 85 |
- speculative decoding
|
| 86 |
- Attention-FFN disaggregation
|
| 87 |
+
- production-fidelity agentic serving or dynamic batching inside Stateful Sessions
|
| 88 |
- real tool execution or production agent-runtime integration
|
| 89 |
- claims that the bounded offline oracle is globally optimal
|
| 90 |
- claims that simulator bootstrap intervals quantify real-hardware error
|
index.html
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
scripts/release_check.py
CHANGED
|
@@ -90,9 +90,28 @@ if "Download PNG" not in index or ".chart-download" not in app:
|
|
| 90 |
errors.append("chart PNG export controls are missing")
|
| 91 |
if "Worst repetition" not in index or "Target" not in index:
|
| 92 |
errors.append("capacity evidence columns are missing")
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
|
| 97 |
# Every $("id") lookup in app.js should resolve to a static DOM id.
|
| 98 |
app_ids = set(re.findall(r'\$\("([A-Za-z0-9_-]+)"\)', app))
|
|
|
|
| 90 |
errors.append("chart PNG export controls are missing")
|
| 91 |
if "Worst repetition" not in index or "Target" not in index:
|
| 92 |
errors.append("capacity evidence columns are missing")
|
| 93 |
+
required_ui_ids = [
|
| 94 |
+
"traceFile", "pairedStudyBtn", "robustStudyBtn", "agentRunBtn", "agentCompareBtn", "agentTtlBtn",
|
| 95 |
+
"agentMemoryCompareBtn", "agentBudgetBtn", "agentAffinityBtn", "predictiveCompareBtn", "predictiveAlphaBtn",
|
| 96 |
+
"execRunBtn", "execCompareBtn", "execThresholdBtn", "execDecayBtn", "execPlanningBtn", "execHorizonBtn",
|
| 97 |
+
"execBudgetBtn", "consRunBtn", "measurementCalibrateBtn", "consReportBtn", "exportBtn", "agentRunJson",
|
| 98 |
+
]
|
| 99 |
+
for expected_id in required_ui_ids:
|
| 100 |
+
if f'id="{expected_id}"' not in index:
|
| 101 |
+
errors.append(f"UI is missing required control: {expected_id}")
|
| 102 |
+
|
| 103 |
+
# Design guardrails for the public research-workbench UI.
|
| 104 |
+
css = (ROOT / "styles.css").read_text()
|
| 105 |
+
if "gradient(" in css.lower():
|
| 106 |
+
errors.append("UI should not use decorative CSS gradients")
|
| 107 |
+
if re.search(r"border-radius:\s*(?:999|9999|50)%?px?", css, re.IGNORECASE):
|
| 108 |
+
errors.append("UI contains pill-style over-rounding")
|
| 109 |
+
if "feature freeze" in index.lower() or "feature freeze" in README.lower():
|
| 110 |
+
errors.append("public UI/docs should not mention the release process")
|
| 111 |
+
if 'class="subtitle"' in index:
|
| 112 |
+
errors.append("topbar should remain a compact tool header, not a product masthead")
|
| 113 |
+
if re.search(r"\.section-kicker\s*\{[^}]*text-transform:\s*uppercase", css, re.DOTALL | re.IGNORECASE):
|
| 114 |
+
errors.append("section labels should not use repetitive AI-style uppercase kickers")
|
| 115 |
|
| 116 |
# Every $("id") lookup in app.js should resolve to a static DOM id.
|
| 117 |
app_ids = set(re.findall(r'\$\("([A-Za-z0-9_-]+)"\)', app))
|
styles.css
CHANGED
|
@@ -1,18 +1,20 @@
|
|
| 1 |
:root {
|
| 2 |
-
--bg: #
|
| 3 |
-
--surface: #
|
| 4 |
-
--surface-
|
| 5 |
-
--surface-
|
| 6 |
-
--line: #
|
| 7 |
-
--line-soft: #
|
| 8 |
-
--text: #
|
|
|
|
| 9 |
--muted: #929dab;
|
| 10 |
-
--faint: #
|
| 11 |
-
--accent: #
|
| 12 |
-
--accent-
|
| 13 |
-
--good: #
|
| 14 |
-
--danger: #
|
| 15 |
-
--amber: #
|
|
|
|
| 16 |
}
|
| 17 |
|
| 18 |
* { box-sizing: border-box; }
|
|
@@ -21,314 +23,640 @@ body {
|
|
| 21 |
margin: 0;
|
| 22 |
background: var(--bg);
|
| 23 |
color: var(--text);
|
| 24 |
-
font-family:
|
| 25 |
font-size: 16px;
|
|
|
|
|
|
|
| 26 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
|
|
|
|
| 28 |
.topbar {
|
| 29 |
position: fixed;
|
| 30 |
inset: 0 0 auto 0;
|
| 31 |
z-index: 80;
|
| 32 |
-
height:
|
| 33 |
padding: 0 24px;
|
| 34 |
display: flex;
|
| 35 |
align-items: center;
|
| 36 |
justify-content: space-between;
|
| 37 |
gap: 16px;
|
| 38 |
-
background:
|
| 39 |
border-bottom: 1px solid var(--line);
|
| 40 |
-
transition: transform
|
| 41 |
}
|
| 42 |
-
body.runtime-ready .topbar { transform: translateY(-
|
| 43 |
body.runtime-ready .topbar:hover,
|
| 44 |
body.runtime-ready .topbar:focus-within { transform: translateY(0); }
|
| 45 |
-
.brand-wrap {
|
| 46 |
-
.brand { font-size:
|
| 47 |
-
.subtitle { margin-top: 2px; color: var(--faint); font-size: 12px; }
|
| 48 |
.runtime-pill {
|
| 49 |
display: inline-flex;
|
| 50 |
align-items: center;
|
| 51 |
-
gap:
|
| 52 |
color: var(--muted);
|
| 53 |
-
|
| 54 |
-
background: var(--surface-2);
|
| 55 |
-
border-radius: 4px;
|
| 56 |
-
padding: 6px 9px;
|
| 57 |
-
font-size: 11.5px;
|
| 58 |
white-space: nowrap;
|
| 59 |
}
|
| 60 |
-
.runtime-pill .dot {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
.runtime-pill.ready .dot { background: var(--good); }
|
| 62 |
.runtime-pill.error .dot { background: var(--danger); }
|
| 63 |
|
|
|
|
| 64 |
.shell {
|
| 65 |
-
width: min(
|
| 66 |
margin: 0 auto;
|
| 67 |
-
padding:
|
| 68 |
-
transition: padding-top
|
| 69 |
}
|
| 70 |
-
body.runtime-ready .shell { padding-top:
|
| 71 |
|
| 72 |
.intro {
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
gap: 46px;
|
| 76 |
-
align-items: end;
|
| 77 |
-
padding: 16px 0 18px;
|
| 78 |
-
}
|
| 79 |
-
.eyebrow, .section-kicker {
|
| 80 |
-
color: var(--accent-2);
|
| 81 |
-
font-size: 10.5px;
|
| 82 |
-
line-height: 1.25;
|
| 83 |
-
text-transform: uppercase;
|
| 84 |
-
letter-spacing: .1em;
|
| 85 |
-
font-weight: 760;
|
| 86 |
}
|
| 87 |
.intro h1 {
|
| 88 |
-
|
| 89 |
-
margin: 8px 0 9px;
|
| 90 |
color: #edf1f5;
|
| 91 |
-
font-size: clamp(
|
| 92 |
-
line-height: 1.
|
| 93 |
-
letter-spacing: -.
|
| 94 |
font-weight: 720;
|
| 95 |
}
|
| 96 |
-
.intro p {
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
.reference-note {
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
|
|
|
| 106 |
font-size: 12.5px;
|
| 107 |
line-height: 1.55;
|
| 108 |
}
|
|
|
|
| 109 |
|
|
|
|
| 110 |
.tabs {
|
| 111 |
display: flex;
|
| 112 |
-
gap:
|
| 113 |
-
|
|
|
|
| 114 |
overflow-x: auto;
|
| 115 |
-
border-bottom: 1px solid var(--line
|
| 116 |
-
|
| 117 |
}
|
| 118 |
.tab {
|
|
|
|
| 119 |
border: 0;
|
| 120 |
border-bottom: 2px solid transparent;
|
| 121 |
-
color: var(--muted);
|
| 122 |
background: transparent;
|
| 123 |
-
|
|
|
|
| 124 |
cursor: pointer;
|
| 125 |
-
font-
|
| 126 |
-
font-
|
| 127 |
white-space: nowrap;
|
| 128 |
}
|
| 129 |
-
.tab:hover { color: var(--text); }
|
| 130 |
.tab.active { color: var(--text); border-bottom-color: var(--accent); }
|
| 131 |
.tab-panel { display: none; }
|
| 132 |
.tab-panel.active { display: block; }
|
| 133 |
|
| 134 |
-
|
| 135 |
-
.
|
| 136 |
-
.
|
| 137 |
-
.
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
.tag {
|
| 143 |
display: inline-flex;
|
| 144 |
align-items: center;
|
| 145 |
-
border: 1px solid var(--line);
|
| 146 |
-
background: var(--surface-2);
|
| 147 |
color: var(--muted);
|
| 148 |
-
|
| 149 |
-
|
|
|
|
| 150 |
font-size: 11.5px;
|
| 151 |
white-space: nowrap;
|
| 152 |
}
|
| 153 |
-
.tag.good { color: var(--good);
|
| 154 |
-
.tag.bad { color: var(--danger);
|
| 155 |
|
| 156 |
-
|
| 157 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 158 |
width: 100%;
|
| 159 |
-
margin-top: 7px;
|
| 160 |
height: 40px;
|
|
|
|
| 161 |
border: 1px solid var(--line);
|
| 162 |
-
|
|
|
|
| 163 |
color: var(--text);
|
| 164 |
-
|
| 165 |
-
padding: 0 9px;
|
| 166 |
outline: none;
|
| 167 |
font-size: 14px;
|
| 168 |
}
|
| 169 |
-
select:
|
| 170 |
-
|
| 171 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
.field-grid.two { grid-template-columns: 1fr 1fr; }
|
| 173 |
.field-grid.four { grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
| 174 |
-
.subcontrols {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
.subcontrols .section-kicker { margin-bottom: 10px; }
|
| 176 |
-
hr { border: 0; border-top: 1px solid var(--line-soft); margin:
|
| 177 |
-
.section-kicker { margin-bottom: 10px; }
|
| 178 |
.hidden { display: none !important; }
|
|
|
|
| 179 |
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
cursor: pointer;
|
| 185 |
-
transition:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 186 |
}
|
| 187 |
-
button.primary { width: 100%; border: 1px solid #668fd5; background: #4f7fce; color: white; margin-top: 11px; }
|
| 188 |
-
button.primary:hover:not(:disabled) { background: #5b8bd8; }
|
| 189 |
-
button.secondary { border: 1px solid var(--line); background: #0b1016; color: #c4ccd7; }
|
| 190 |
button.secondary:hover:not(:disabled),
|
| 191 |
.mini-button:hover:not(:disabled),
|
| 192 |
.chart-expand:hover,
|
| 193 |
-
.chart-download:hover {
|
| 194 |
-
|
| 195 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
.button-row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 8px; }
|
| 197 |
-
.action-stack
|
| 198 |
-
.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
.checkline input { accent-color: var(--accent); }
|
| 200 |
|
| 201 |
-
|
| 202 |
-
.empty-state
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 210 |
.metric strong.metric-small { font-size: 14.5px; line-height: 1.35; }
|
| 211 |
-
.metric.emphasis {
|
| 212 |
|
| 213 |
-
.diagnostic-card {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 214 |
.diagnostic-head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; }
|
| 215 |
-
.diagnostic-head span { color: var(--muted); font-size: 11.5px;
|
| 216 |
.diagnostic-head strong { font-size: 16px; }
|
| 217 |
-
.diagnostic-card p { margin: 8px 0 0; color:
|
| 218 |
.diagnostic-card .diagnostic-action { color: var(--muted); }
|
| 219 |
-
.evidence-row { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
|
| 220 |
-
.evidence-row span {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 221 |
|
| 222 |
-
.
|
| 223 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 224 |
.chart-card.full { margin-top: 10px; }
|
| 225 |
-
.chart-head {
|
| 226 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 227 |
.chart-actions { display: flex; gap: 6px; }
|
| 228 |
-
.chart-expand,
|
| 229 |
-
.chart-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 230 |
.chart-body.large { height: 350px; }
|
| 231 |
-
.
|
| 232 |
-
.chart-card.chart-expanded
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 233 |
body.chart-open { overflow: hidden; }
|
| 234 |
canvas { width: 100% !important; height: 100% !important; max-height: none; }
|
| 235 |
|
| 236 |
-
|
| 237 |
-
.
|
| 238 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 239 |
.table-toolbar > div { display: flex; gap: 6px; }
|
| 240 |
-
.table-wrap {
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 246 |
tbody tr:last-child td { border-bottom: 0; }
|
| 247 |
-
tbody tr:hover td { background: #
|
| 248 |
.pass { color: var(--good); }
|
| 249 |
.fail { color: var(--danger); }
|
| 250 |
-
.best-label,
|
| 251 |
-
.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 252 |
|
| 253 |
-
|
| 254 |
-
.
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 259 |
.wide-method { grid-column: 1 / -1; }
|
| 260 |
-
.paper-grid {
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 265 |
.toast.show { opacity: 1; transform: translateY(0); }
|
| 266 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 267 |
@media (max-width: 1100px) {
|
| 268 |
-
.
|
| 269 |
-
.
|
| 270 |
-
.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 271 |
.metric-grid { grid-template-columns: repeat(3, 1fr); }
|
| 272 |
.paper-grid { grid-template-columns: 1fr 1fr; }
|
| 273 |
}
|
| 274 |
-
|
|
|
|
|
|
|
| 275 |
@media (max-width: 720px) {
|
| 276 |
-
.topbar { padding: 0
|
| 277 |
-
.
|
| 278 |
-
.
|
| 279 |
-
.shell { width: calc(100% - 20px); padding-top: 88px; }
|
| 280 |
body.runtime-ready .shell { padding-top: 20px; }
|
| 281 |
-
.intro { padding-top:
|
| 282 |
-
.
|
| 283 |
-
.field-grid.two,
|
| 284 |
-
.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 285 |
.paper-grid { grid-template-columns: 1fr; }
|
| 286 |
.panel-title-row { flex-direction: column; }
|
| 287 |
-
.arena-title-row .compact
|
| 288 |
-
.
|
|
|
|
|
|
|
| 289 |
.wide-method { grid-column: auto; }
|
| 290 |
.chart-card.chart-expanded { inset: 6px; }
|
| 291 |
}
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
/* Research-oriented readability and trace controls */
|
| 295 |
-
.tab { font-size: 13.5px; }
|
| 296 |
-
button.primary, button.secondary { font-size: 13.5px; }
|
| 297 |
-
input[type="file"] {
|
| 298 |
-
width: 100%; margin-top: 8px; color: #c4ccd7; font-size: 12.5px;
|
| 299 |
-
border: 1px solid var(--line); background: #090e14; padding: 9px; border-radius: 4px;
|
| 300 |
}
|
| 301 |
-
input[type="file"]::file-selector-button {
|
| 302 |
-
border: 1px solid #425168; background: #111925; color: #d4dbe4; padding: 7px 10px;
|
| 303 |
-
margin-right: 10px; border-radius: 3px; cursor: pointer;
|
| 304 |
-
}
|
| 305 |
-
.trace-row { display: flex; justify-content: space-between; align-items: center; gap: 10px; margin-top: 10px; color: #b8c1cc; font-size: 12.5px; }
|
| 306 |
-
.control-help { margin: 10px 0 0; color: var(--muted); font-size: 12px; line-height: 1.55; }
|
| 307 |
-
.research-grid { display: grid; grid-template-columns: 390px minmax(0, 1fr); gap: 14px; align-items: start; }
|
| 308 |
-
.research-controls { padding: 20px; position: sticky; top: 16px; }
|
| 309 |
-
.research-results { display: grid; gap: 14px; min-width: 0; }
|
| 310 |
-
.research-panel { padding: 20px; }
|
| 311 |
-
.research-secondary { width: 100%; margin-top: 10px; }
|
| 312 |
-
.study-summary { border-left: 2px solid #516d92; background: #0a1017; padding: 11px 13px; color: #b9c3cf; font-size: 13px; line-height: 1.6; margin-bottom: 10px; }
|
| 313 |
-
.study-summary strong { color: #e1e7ee; }
|
| 314 |
-
.research-chart { height: 310px; }
|
| 315 |
-
@media (max-width: 1100px) { .research-grid { grid-template-columns: 1fr; } .research-controls { position: static; } }
|
| 316 |
-
|
| 317 |
-
/* Stateful agent-session experiments */
|
| 318 |
-
.agent-layout { display: grid; grid-template-columns: 420px minmax(0, 1fr); gap: 14px; align-items: start; }
|
| 319 |
-
.agent-controls { padding: 20px; position: sticky; top: 16px; }
|
| 320 |
-
.agent-results { display: grid; gap: 14px; min-width: 0; }
|
| 321 |
-
.metric-grid.six-agent { grid-template-columns: repeat(6, minmax(0, 1fr)); }
|
| 322 |
-
@media (max-width: 1250px) { .metric-grid.six-agent { grid-template-columns: repeat(3, 1fr); } }
|
| 323 |
-
@media (max-width: 1100px) { .agent-layout { grid-template-columns: 1fr; } .agent-controls { position: static; } }
|
| 324 |
-
@media (max-width: 720px) { .metric-grid.six-agent { grid-template-columns: 1fr 1fr; } }
|
| 325 |
-
.field-disabled { opacity: .55; }
|
| 326 |
-
.metric-grid.eight-agent { grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
| 327 |
-
.stacked-actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
|
| 328 |
-
.stacked-actions .compact { min-width: 168px; }
|
| 329 |
-
.horizontal-actions { margin: 10px 0 14px; justify-content: flex-start; }
|
| 330 |
-
.predictive-controls { margin-top: 12px; }
|
| 331 |
-
@media (max-width: 1250px) { .metric-grid.eight-agent { grid-template-columns: repeat(2, 1fr); } }
|
| 332 |
-
@media (max-width: 960px) { .field-grid.four { grid-template-columns: 1fr 1fr; } }
|
| 333 |
-
@media (max-width: 720px) { .metric-grid.eight-agent { grid-template-columns: 1fr 1fr; } .field-grid.four { grid-template-columns: 1fr; } .stacked-actions { width: 100%; } .stacked-actions .compact { width: 100% !important; } }
|
| 334 |
-
.experiment-subsection { margin-top: 18px; padding-top: 18px; border-top: 1px solid var(--line); }
|
|
|
|
| 1 |
:root {
|
| 2 |
+
--bg: #080c11;
|
| 3 |
+
--surface: #0c1219;
|
| 4 |
+
--surface-raised: #101720;
|
| 5 |
+
--surface-inset: #090e14;
|
| 6 |
+
--line: #27313d;
|
| 7 |
+
--line-soft: #1c2530;
|
| 8 |
+
--text: #e3e8ee;
|
| 9 |
+
--text-soft: #c2cad4;
|
| 10 |
--muted: #929dab;
|
| 11 |
+
--faint: #6f7b8a;
|
| 12 |
+
--accent: #6f96cf;
|
| 13 |
+
--accent-strong: #4d79b8;
|
| 14 |
+
--good: #68c394;
|
| 15 |
+
--danger: #df7d89;
|
| 16 |
+
--amber: #d2b36a;
|
| 17 |
+
--focus: rgba(111, 150, 207, 0.24);
|
| 18 |
}
|
| 19 |
|
| 20 |
* { box-sizing: border-box; }
|
|
|
|
| 23 |
margin: 0;
|
| 24 |
background: var(--bg);
|
| 25 |
color: var(--text);
|
| 26 |
+
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
| 27 |
font-size: 16px;
|
| 28 |
+
line-height: 1.45;
|
| 29 |
+
text-rendering: optimizeLegibility;
|
| 30 |
}
|
| 31 |
+
button, input, select { font: inherit; }
|
| 32 |
+
button, a, input, select { -webkit-tap-highlight-color: transparent; }
|
| 33 |
+
button:focus-visible, a:focus-visible, input:focus-visible, select:focus-visible {
|
| 34 |
+
outline: 2px solid var(--accent);
|
| 35 |
+
outline-offset: 2px;
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
.skip-link {
|
| 39 |
+
position: fixed;
|
| 40 |
+
left: 12px;
|
| 41 |
+
top: 10px;
|
| 42 |
+
z-index: 200;
|
| 43 |
+
transform: translateY(-160%);
|
| 44 |
+
padding: 8px 10px;
|
| 45 |
+
background: var(--text);
|
| 46 |
+
color: var(--bg);
|
| 47 |
+
text-decoration: none;
|
| 48 |
+
}
|
| 49 |
+
.skip-link:focus { transform: translateY(0); }
|
| 50 |
|
| 51 |
+
/* Runtime header --------------------------------------------------------- */
|
| 52 |
.topbar {
|
| 53 |
position: fixed;
|
| 54 |
inset: 0 0 auto 0;
|
| 55 |
z-index: 80;
|
| 56 |
+
height: 58px;
|
| 57 |
padding: 0 24px;
|
| 58 |
display: flex;
|
| 59 |
align-items: center;
|
| 60 |
justify-content: space-between;
|
| 61 |
gap: 16px;
|
| 62 |
+
background: rgba(8, 12, 17, 0.98);
|
| 63 |
border-bottom: 1px solid var(--line);
|
| 64 |
+
transition: transform 150ms ease;
|
| 65 |
}
|
| 66 |
+
body.runtime-ready .topbar { transform: translateY(-52px); }
|
| 67 |
body.runtime-ready .topbar:hover,
|
| 68 |
body.runtime-ready .topbar:focus-within { transform: translateY(0); }
|
| 69 |
+
.brand-wrap { min-width: 0; }
|
| 70 |
+
.brand { font-size: 14px; font-weight: 700; letter-spacing: -0.01em; }
|
|
|
|
| 71 |
.runtime-pill {
|
| 72 |
display: inline-flex;
|
| 73 |
align-items: center;
|
| 74 |
+
gap: 8px;
|
| 75 |
color: var(--muted);
|
| 76 |
+
font-size: 12px;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
white-space: nowrap;
|
| 78 |
}
|
| 79 |
+
.runtime-pill .dot {
|
| 80 |
+
width: 7px;
|
| 81 |
+
height: 7px;
|
| 82 |
+
border-radius: 50%;
|
| 83 |
+
background: var(--amber);
|
| 84 |
+
}
|
| 85 |
.runtime-pill.ready .dot { background: var(--good); }
|
| 86 |
.runtime-pill.error .dot { background: var(--danger); }
|
| 87 |
|
| 88 |
+
/* Page frame ------------------------------------------------------------- */
|
| 89 |
.shell {
|
| 90 |
+
width: min(1500px, calc(100% - 44px));
|
| 91 |
margin: 0 auto;
|
| 92 |
+
padding: 86px 0 46px;
|
| 93 |
+
transition: padding-top 150ms ease;
|
| 94 |
}
|
| 95 |
+
body.runtime-ready .shell { padding-top: 28px; }
|
| 96 |
|
| 97 |
.intro {
|
| 98 |
+
padding: 13px 0 16px;
|
| 99 |
+
max-width: 1040px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
}
|
| 101 |
.intro h1 {
|
| 102 |
+
margin: 0 0 10px;
|
|
|
|
| 103 |
color: #edf1f5;
|
| 104 |
+
font-size: clamp(26px, 2.4vw, 34px);
|
| 105 |
+
line-height: 1.1;
|
| 106 |
+
letter-spacing: -0.035em;
|
| 107 |
font-weight: 720;
|
| 108 |
}
|
| 109 |
+
.intro p {
|
| 110 |
+
margin: 0;
|
| 111 |
+
max-width: 980px;
|
| 112 |
+
color: var(--muted);
|
| 113 |
+
font-size: 15px;
|
| 114 |
+
line-height: 1.65;
|
| 115 |
+
}
|
| 116 |
+
.intro-meta {
|
| 117 |
+
margin-top: 10px !important;
|
| 118 |
+
color: var(--faint) !important;
|
| 119 |
+
font-size: 12.5px !important;
|
| 120 |
+
letter-spacing: 0.005em;
|
| 121 |
+
}
|
| 122 |
.reference-note {
|
| 123 |
+
margin: 2px 0 0;
|
| 124 |
+
padding: 10px 0;
|
| 125 |
+
border-top: 1px solid var(--line-soft);
|
| 126 |
+
border-bottom: 1px solid var(--line-soft);
|
| 127 |
+
color: #aaa596;
|
| 128 |
font-size: 12.5px;
|
| 129 |
line-height: 1.55;
|
| 130 |
}
|
| 131 |
+
.reference-note strong { color: #c6bea9; }
|
| 132 |
|
| 133 |
+
/* Navigation ------------------------------------------------------------- */
|
| 134 |
.tabs {
|
| 135 |
display: flex;
|
| 136 |
+
gap: 0;
|
| 137 |
+
margin: 12px 0 18px;
|
| 138 |
+
padding: 0;
|
| 139 |
overflow-x: auto;
|
| 140 |
+
border-bottom: 1px solid var(--line);
|
| 141 |
+
scrollbar-width: thin;
|
| 142 |
}
|
| 143 |
.tab {
|
| 144 |
+
flex: 0 0 auto;
|
| 145 |
border: 0;
|
| 146 |
border-bottom: 2px solid transparent;
|
|
|
|
| 147 |
background: transparent;
|
| 148 |
+
color: var(--muted);
|
| 149 |
+
padding: 12px 11px 10px;
|
| 150 |
cursor: pointer;
|
| 151 |
+
font-size: 13.5px;
|
| 152 |
+
font-weight: 620;
|
| 153 |
white-space: nowrap;
|
| 154 |
}
|
| 155 |
+
.tab:hover { color: var(--text-soft); }
|
| 156 |
.tab.active { color: var(--text); border-bottom-color: var(--accent); }
|
| 157 |
.tab-panel { display: none; }
|
| 158 |
.tab-panel.active { display: block; }
|
| 159 |
|
| 160 |
+
/* Layout ---------------------------------------------------------------- */
|
| 161 |
+
.workspace,
|
| 162 |
+
.agent-layout,
|
| 163 |
+
.research-grid {
|
| 164 |
+
display: grid;
|
| 165 |
+
grid-template-columns: 410px minmax(0, 1fr);
|
| 166 |
+
gap: 16px;
|
| 167 |
+
align-items: start;
|
| 168 |
+
}
|
| 169 |
+
.planner-grid { grid-template-columns: 370px minmax(0, 1fr); }
|
| 170 |
+
.results-column,
|
| 171 |
+
.agent-results,
|
| 172 |
+
.research-results { min-width: 0; display: grid; gap: 14px; }
|
| 173 |
+
|
| 174 |
+
.panel {
|
| 175 |
+
background: var(--surface);
|
| 176 |
+
border: 1px solid var(--line);
|
| 177 |
+
border-radius: 2px;
|
| 178 |
+
}
|
| 179 |
+
.controls-panel,
|
| 180 |
+
.agent-controls,
|
| 181 |
+
.research-controls {
|
| 182 |
+
padding: 20px;
|
| 183 |
+
position: sticky;
|
| 184 |
+
top: 14px;
|
| 185 |
+
}
|
| 186 |
+
.result-panel,
|
| 187 |
+
.wide-panel,
|
| 188 |
+
.research-panel { padding: 20px; }
|
| 189 |
+
.panel-title-row {
|
| 190 |
+
display: flex;
|
| 191 |
+
align-items: flex-start;
|
| 192 |
+
justify-content: space-between;
|
| 193 |
+
gap: 18px;
|
| 194 |
+
margin-bottom: 16px;
|
| 195 |
+
}
|
| 196 |
+
.panel-title-row h2 {
|
| 197 |
+
margin: 0;
|
| 198 |
+
font-size: 19px;
|
| 199 |
+
line-height: 1.25;
|
| 200 |
+
letter-spacing: -0.018em;
|
| 201 |
+
font-weight: 700;
|
| 202 |
+
}
|
| 203 |
+
.panel-title-row p { margin: 5px 0 0; }
|
| 204 |
+
|
| 205 |
+
/* Small labels: intentionally plain, not eyebrow/kicker styling. */
|
| 206 |
+
.section-kicker {
|
| 207 |
+
margin: 0 0 9px;
|
| 208 |
+
color: #a4afbc;
|
| 209 |
+
font-size: 12.5px;
|
| 210 |
+
line-height: 1.3;
|
| 211 |
+
font-weight: 650;
|
| 212 |
+
letter-spacing: 0;
|
| 213 |
+
text-transform: none;
|
| 214 |
+
}
|
| 215 |
+
.panel-title-row .section-kicker { display: none; }
|
| 216 |
.tag {
|
| 217 |
display: inline-flex;
|
| 218 |
align-items: center;
|
|
|
|
|
|
|
| 219 |
color: var(--muted);
|
| 220 |
+
padding: 2px 0;
|
| 221 |
+
border: 0;
|
| 222 |
+
background: transparent;
|
| 223 |
font-size: 11.5px;
|
| 224 |
white-space: nowrap;
|
| 225 |
}
|
| 226 |
+
.tag.good { color: var(--good); }
|
| 227 |
+
.tag.bad { color: var(--danger); }
|
| 228 |
|
| 229 |
+
/* Controls --------------------------------------------------------------- */
|
| 230 |
+
label {
|
| 231 |
+
display: block;
|
| 232 |
+
position: relative;
|
| 233 |
+
color: #b9c2cd;
|
| 234 |
+
font-size: 12.5px;
|
| 235 |
+
font-weight: 620;
|
| 236 |
+
}
|
| 237 |
+
select,
|
| 238 |
+
input[type="number"] {
|
| 239 |
width: 100%;
|
|
|
|
| 240 |
height: 40px;
|
| 241 |
+
margin-top: 7px;
|
| 242 |
border: 1px solid var(--line);
|
| 243 |
+
border-radius: 2px;
|
| 244 |
+
background: var(--surface-inset);
|
| 245 |
color: var(--text);
|
| 246 |
+
padding: 0 10px;
|
|
|
|
| 247 |
outline: none;
|
| 248 |
font-size: 14px;
|
| 249 |
}
|
| 250 |
+
select:hover,
|
| 251 |
+
input[type="number"]:hover { border-color: #374352; }
|
| 252 |
+
select:focus,
|
| 253 |
+
input[type="number"]:focus {
|
| 254 |
+
border-color: var(--accent);
|
| 255 |
+
box-shadow: 0 0 0 2px var(--focus);
|
| 256 |
+
}
|
| 257 |
+
.unit {
|
| 258 |
+
position: absolute;
|
| 259 |
+
right: 9px;
|
| 260 |
+
bottom: 12px;
|
| 261 |
+
color: var(--faint);
|
| 262 |
+
font-size: 10.5px;
|
| 263 |
+
pointer-events: none;
|
| 264 |
+
}
|
| 265 |
+
.field-grid { display: grid; gap: 11px; margin-bottom: 11px; }
|
| 266 |
.field-grid.two { grid-template-columns: 1fr 1fr; }
|
| 267 |
.field-grid.four { grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
| 268 |
+
.subcontrols {
|
| 269 |
+
margin: 13px 0 5px;
|
| 270 |
+
padding: 13px;
|
| 271 |
+
border: 1px solid var(--line-soft);
|
| 272 |
+
background: var(--surface-inset);
|
| 273 |
+
}
|
| 274 |
.subcontrols .section-kicker { margin-bottom: 10px; }
|
| 275 |
+
hr { border: 0; border-top: 1px solid var(--line-soft); margin: 18px 0; }
|
|
|
|
| 276 |
.hidden { display: none !important; }
|
| 277 |
+
.field-disabled { opacity: 0.5; }
|
| 278 |
|
| 279 |
+
input[type="file"] {
|
| 280 |
+
width: 100%;
|
| 281 |
+
margin-top: 8px;
|
| 282 |
+
padding: 8px;
|
| 283 |
+
border: 1px solid var(--line);
|
| 284 |
+
border-radius: 2px;
|
| 285 |
+
background: var(--surface-inset);
|
| 286 |
+
color: var(--text-soft);
|
| 287 |
+
font-size: 12.5px;
|
| 288 |
+
}
|
| 289 |
+
input[type="file"]::file-selector-button {
|
| 290 |
+
margin-right: 10px;
|
| 291 |
+
padding: 7px 10px;
|
| 292 |
+
border: 1px solid #3b4858;
|
| 293 |
+
border-radius: 2px;
|
| 294 |
+
background: var(--surface-raised);
|
| 295 |
+
color: var(--text-soft);
|
| 296 |
+
cursor: pointer;
|
| 297 |
+
}
|
| 298 |
+
.trace-row {
|
| 299 |
+
display: flex;
|
| 300 |
+
align-items: center;
|
| 301 |
+
justify-content: space-between;
|
| 302 |
+
gap: 10px;
|
| 303 |
+
margin-top: 10px;
|
| 304 |
+
color: var(--text-soft);
|
| 305 |
+
font-size: 12.5px;
|
| 306 |
+
}
|
| 307 |
+
.control-help,
|
| 308 |
+
.tiny-note {
|
| 309 |
+
margin: 9px 0 0;
|
| 310 |
+
color: var(--muted);
|
| 311 |
+
font-size: 12px;
|
| 312 |
+
line-height: 1.55;
|
| 313 |
+
}
|
| 314 |
+
|
| 315 |
+
button.primary,
|
| 316 |
+
button.secondary {
|
| 317 |
+
min-height: 41px;
|
| 318 |
+
border-radius: 2px;
|
| 319 |
+
font-size: 13.5px;
|
| 320 |
+
font-weight: 680;
|
| 321 |
cursor: pointer;
|
| 322 |
+
transition: background-color 110ms ease, border-color 110ms ease, color 110ms ease;
|
| 323 |
+
}
|
| 324 |
+
button.primary {
|
| 325 |
+
width: 100%;
|
| 326 |
+
margin-top: 11px;
|
| 327 |
+
border: 1px solid #6489c0;
|
| 328 |
+
background: var(--accent-strong);
|
| 329 |
+
color: #fff;
|
| 330 |
+
}
|
| 331 |
+
button.primary:hover:not(:disabled) { background: #5885c5; }
|
| 332 |
+
button.secondary {
|
| 333 |
+
border: 1px solid var(--line);
|
| 334 |
+
background: transparent;
|
| 335 |
+
color: var(--text-soft);
|
| 336 |
}
|
|
|
|
|
|
|
|
|
|
| 337 |
button.secondary:hover:not(:disabled),
|
| 338 |
.mini-button:hover:not(:disabled),
|
| 339 |
.chart-expand:hover,
|
| 340 |
+
.chart-download:hover {
|
| 341 |
+
border-color: #465466;
|
| 342 |
+
background: var(--surface-raised);
|
| 343 |
+
color: var(--text);
|
| 344 |
+
}
|
| 345 |
+
button:disabled { opacity: 0.4; cursor: not-allowed; }
|
| 346 |
+
.compact {
|
| 347 |
+
width: auto !important;
|
| 348 |
+
min-width: 160px;
|
| 349 |
+
padding: 0 16px;
|
| 350 |
+
margin-top: 0 !important;
|
| 351 |
+
}
|
| 352 |
.button-row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 8px; }
|
| 353 |
+
.action-stack,
|
| 354 |
+
.stacked-actions {
|
| 355 |
+
display: flex;
|
| 356 |
+
flex-wrap: wrap;
|
| 357 |
+
justify-content: flex-end;
|
| 358 |
+
align-items: flex-end;
|
| 359 |
+
gap: 8px;
|
| 360 |
+
}
|
| 361 |
+
.stacked-actions .compact { min-width: 166px; }
|
| 362 |
+
.horizontal-actions { margin: 10px 0 14px; justify-content: flex-start; }
|
| 363 |
+
.checkline { display: flex; align-items: center; gap: 7px; color: var(--muted); font-size: 12.5px; font-weight: 600; }
|
| 364 |
.checkline input { accent-color: var(--accent); }
|
| 365 |
|
| 366 |
+
/* Results ---------------------------------------------------------------- */
|
| 367 |
+
.empty-state {
|
| 368 |
+
min-height: 330px;
|
| 369 |
+
display: grid;
|
| 370 |
+
place-content: center;
|
| 371 |
+
padding: 28px;
|
| 372 |
+
text-align: center;
|
| 373 |
+
color: var(--muted);
|
| 374 |
+
}
|
| 375 |
+
.empty-state.small { min-height: 210px; }
|
| 376 |
+
.empty-state h3 { margin: 0 0 6px; color: var(--text-soft); font-size: 16px; font-weight: 650; }
|
| 377 |
+
.empty-state p { max-width: 620px; margin: 0; font-size: 13.5px; line-height: 1.6; }
|
| 378 |
+
|
| 379 |
+
/* Metrics read as a strip, not a grid of product cards. */
|
| 380 |
+
.metric-grid {
|
| 381 |
+
display: grid;
|
| 382 |
+
grid-template-columns: repeat(6, minmax(0, 1fr));
|
| 383 |
+
gap: 0;
|
| 384 |
+
margin-bottom: 13px;
|
| 385 |
+
border: 1px solid var(--line-soft);
|
| 386 |
+
background: var(--surface-inset);
|
| 387 |
+
}
|
| 388 |
+
.metric-grid.four { grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
| 389 |
+
.metric-grid.six-agent { grid-template-columns: repeat(6, minmax(0, 1fr)); }
|
| 390 |
+
.metric-grid.eight-agent { grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
| 391 |
+
.metric {
|
| 392 |
+
min-height: 84px;
|
| 393 |
+
padding: 13px 14px;
|
| 394 |
+
border: 0;
|
| 395 |
+
border-right: 1px solid var(--line-soft);
|
| 396 |
+
background: transparent;
|
| 397 |
+
}
|
| 398 |
+
.metric:last-child { border-right: 0; }
|
| 399 |
+
.metric span { display: block; margin-bottom: 8px; color: var(--muted); font-size: 11.5px; }
|
| 400 |
+
.metric strong { font-size: 20px; line-height: 1.2; letter-spacing: -0.025em; }
|
| 401 |
.metric strong.metric-small { font-size: 14.5px; line-height: 1.35; }
|
| 402 |
+
.metric.emphasis { box-shadow: inset 0 2px 0 var(--accent); background: rgba(111, 150, 207, 0.055); }
|
| 403 |
|
| 404 |
+
.diagnostic-card {
|
| 405 |
+
margin-bottom: 13px;
|
| 406 |
+
padding: 14px 15px;
|
| 407 |
+
border: 1px solid var(--line);
|
| 408 |
+
background: transparent;
|
| 409 |
+
}
|
| 410 |
.diagnostic-head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; }
|
| 411 |
+
.diagnostic-head span { color: var(--muted); font-size: 11.5px; }
|
| 412 |
.diagnostic-head strong { font-size: 16px; }
|
| 413 |
+
.diagnostic-card p { margin: 8px 0 0; color: var(--text-soft); font-size: 13.5px; line-height: 1.55; }
|
| 414 |
.diagnostic-card .diagnostic-action { color: var(--muted); }
|
| 415 |
+
.evidence-row { display: flex; flex-wrap: wrap; gap: 6px 12px; margin-top: 10px; }
|
| 416 |
+
.evidence-row span {
|
| 417 |
+
padding: 0;
|
| 418 |
+
border: 0;
|
| 419 |
+
background: transparent;
|
| 420 |
+
color: #8390a0;
|
| 421 |
+
font: 11px/1.4 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
| 422 |
+
}
|
| 423 |
|
| 424 |
+
.study-summary {
|
| 425 |
+
margin-bottom: 11px;
|
| 426 |
+
padding: 9px 0 9px 12px;
|
| 427 |
+
border-left: 2px solid #526f96;
|
| 428 |
+
background: transparent;
|
| 429 |
+
color: #b9c3cf;
|
| 430 |
+
font-size: 13px;
|
| 431 |
+
line-height: 1.6;
|
| 432 |
+
}
|
| 433 |
+
.study-summary strong { color: var(--text); }
|
| 434 |
+
.warnings,
|
| 435 |
+
.planner-note {
|
| 436 |
+
margin-top: 10px;
|
| 437 |
+
padding: 9px 0 9px 11px;
|
| 438 |
+
border-left: 2px solid #756542;
|
| 439 |
+
background: transparent;
|
| 440 |
+
color: #bbb5a7;
|
| 441 |
+
font-size: 12.5px;
|
| 442 |
+
line-height: 1.55;
|
| 443 |
+
}
|
| 444 |
+
.planner-note { border-left-color: #496284; color: var(--muted); }
|
| 445 |
+
.muted { color: var(--muted); font-size: 13.5px; line-height: 1.55; }
|
| 446 |
+
|
| 447 |
+
/* Charts ----------------------------------------------------------------- */
|
| 448 |
+
.chart-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
| 449 |
+
.chart-card {
|
| 450 |
+
position: relative;
|
| 451 |
+
min-height: 300px;
|
| 452 |
+
border: 1px solid var(--line);
|
| 453 |
+
border-radius: 0;
|
| 454 |
+
background: var(--surface-inset);
|
| 455 |
+
}
|
| 456 |
.chart-card.full { margin-top: 10px; }
|
| 457 |
+
.chart-head {
|
| 458 |
+
min-height: 43px;
|
| 459 |
+
display: flex;
|
| 460 |
+
align-items: center;
|
| 461 |
+
justify-content: space-between;
|
| 462 |
+
gap: 12px;
|
| 463 |
+
padding: 7px 11px 7px 13px;
|
| 464 |
+
border-bottom: 1px solid var(--line-soft);
|
| 465 |
+
}
|
| 466 |
+
.chart-title { color: var(--text-soft); font-size: 12.5px; font-weight: 650; }
|
| 467 |
.chart-actions { display: flex; gap: 6px; }
|
| 468 |
+
.chart-expand,
|
| 469 |
+
.chart-download,
|
| 470 |
+
.mini-button {
|
| 471 |
+
height: 31px;
|
| 472 |
+
padding: 0 10px;
|
| 473 |
+
border: 1px solid var(--line);
|
| 474 |
+
border-radius: 2px;
|
| 475 |
+
background: transparent;
|
| 476 |
+
color: var(--muted);
|
| 477 |
+
cursor: pointer;
|
| 478 |
+
font-size: 11.5px;
|
| 479 |
+
}
|
| 480 |
+
.chart-body { height: 260px; padding: 11px 12px 12px; }
|
| 481 |
.chart-body.large { height: 350px; }
|
| 482 |
+
.research-chart { height: 310px; }
|
| 483 |
+
.chart-card.chart-expanded {
|
| 484 |
+
position: fixed;
|
| 485 |
+
inset: 14px;
|
| 486 |
+
z-index: 100;
|
| 487 |
+
min-height: 0;
|
| 488 |
+
margin: 0;
|
| 489 |
+
border-color: #4a5665;
|
| 490 |
+
background: #080d13;
|
| 491 |
+
box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.78);
|
| 492 |
+
}
|
| 493 |
+
.chart-card.chart-expanded .chart-body { height: calc(100vh - 78px); }
|
| 494 |
body.chart-open { overflow: hidden; }
|
| 495 |
canvas { width: 100% !important; height: 100% !important; max-height: none; }
|
| 496 |
|
| 497 |
+
/* Tables ----------------------------------------------------------------- */
|
| 498 |
+
.table-toolbar {
|
| 499 |
+
display: flex;
|
| 500 |
+
align-items: center;
|
| 501 |
+
justify-content: space-between;
|
| 502 |
+
gap: 12px;
|
| 503 |
+
margin-top: 16px;
|
| 504 |
+
color: #a8b2bf;
|
| 505 |
+
font-size: 12.5px;
|
| 506 |
+
}
|
| 507 |
.table-toolbar > div { display: flex; gap: 6px; }
|
| 508 |
+
.table-wrap {
|
| 509 |
+
overflow: auto;
|
| 510 |
+
margin-top: 7px;
|
| 511 |
+
border-top: 1px solid var(--line);
|
| 512 |
+
border-bottom: 1px solid var(--line);
|
| 513 |
+
}
|
| 514 |
+
table { width: 100%; min-width: 900px; border-collapse: collapse; font-size: 13.5px; }
|
| 515 |
+
th,
|
| 516 |
+
td {
|
| 517 |
+
padding: 10px 11px;
|
| 518 |
+
text-align: right;
|
| 519 |
+
border-bottom: 1px solid var(--line-soft);
|
| 520 |
+
white-space: nowrap;
|
| 521 |
+
}
|
| 522 |
+
th:first-child,
|
| 523 |
+
td:first-child { text-align: left; }
|
| 524 |
+
th {
|
| 525 |
+
position: sticky;
|
| 526 |
+
top: 0;
|
| 527 |
+
z-index: 1;
|
| 528 |
+
background: #090e14;
|
| 529 |
+
color: #95a1af;
|
| 530 |
+
font-size: 11.5px;
|
| 531 |
+
font-weight: 650;
|
| 532 |
+
text-transform: none;
|
| 533 |
+
letter-spacing: 0;
|
| 534 |
+
}
|
| 535 |
+
td { color: #c8d0da; }
|
| 536 |
tbody tr:last-child td { border-bottom: 0; }
|
| 537 |
+
tbody tr:hover td { background: #0f161e; }
|
| 538 |
.pass { color: var(--good); }
|
| 539 |
.fail { color: var(--danger); }
|
| 540 |
+
.best-label,
|
| 541 |
+
.pareto-label {
|
| 542 |
+
display: inline;
|
| 543 |
+
margin-left: 7px;
|
| 544 |
+
padding: 0;
|
| 545 |
+
border: 0;
|
| 546 |
+
color: #9db6d8;
|
| 547 |
+
font-size: 11px;
|
| 548 |
+
text-transform: none;
|
| 549 |
+
}
|
| 550 |
|
| 551 |
+
/* Methodology: editorial layout instead of cards-in-cards. */
|
| 552 |
+
.method-grid {
|
| 553 |
+
display: grid;
|
| 554 |
+
grid-template-columns: 1fr 1fr;
|
| 555 |
+
column-gap: 34px;
|
| 556 |
+
row-gap: 0;
|
| 557 |
+
}
|
| 558 |
+
.prose.panel {
|
| 559 |
+
padding: 22px 0 24px;
|
| 560 |
+
border: 0;
|
| 561 |
+
border-top: 1px solid var(--line);
|
| 562 |
+
background: transparent;
|
| 563 |
+
}
|
| 564 |
+
.prose h2 { margin: 0 0 10px; font-size: 19px; letter-spacing: -0.015em; }
|
| 565 |
+
.prose p { margin: 0; color: var(--muted); font-size: 14px; line-height: 1.7; }
|
| 566 |
+
.prose code { color: #a9bfdc; }
|
| 567 |
+
.formula {
|
| 568 |
+
margin-top: 14px;
|
| 569 |
+
padding: 9px 11px;
|
| 570 |
+
border-left: 2px solid #435a79;
|
| 571 |
+
background: var(--surface-inset);
|
| 572 |
+
color: #b7c3d2;
|
| 573 |
+
font: 12.5px/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
| 574 |
+
}
|
| 575 |
.wide-method { grid-column: 1 / -1; }
|
| 576 |
+
.paper-grid {
|
| 577 |
+
display: grid;
|
| 578 |
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
| 579 |
+
gap: 0 22px;
|
| 580 |
+
margin-top: 18px;
|
| 581 |
+
}
|
| 582 |
+
.paper-grid > div {
|
| 583 |
+
padding: 11px 0 13px;
|
| 584 |
+
border: 0;
|
| 585 |
+
border-top: 1px solid var(--line-soft);
|
| 586 |
+
background: transparent;
|
| 587 |
+
}
|
| 588 |
+
.paper-grid strong { display: block; margin-bottom: 5px; font-size: 12.5px; }
|
| 589 |
+
.paper-grid span { display: block; color: var(--muted); font-size: 11.5px; line-height: 1.55; }
|
| 590 |
+
|
| 591 |
+
/* Experiment sections ---------------------------------------------------- */
|
| 592 |
+
.experiment-subsection { margin-top: 20px; padding-top: 20px; border-top: 1px solid var(--line); }
|
| 593 |
+
.predictive-controls { margin-top: 12px; }
|
| 594 |
+
.research-secondary { width: 100%; margin-top: 10px; }
|
| 595 |
+
|
| 596 |
+
.toast {
|
| 597 |
+
position: fixed;
|
| 598 |
+
right: 20px;
|
| 599 |
+
bottom: 20px;
|
| 600 |
+
z-index: 130;
|
| 601 |
+
max-width: min(480px, calc(100vw - 40px));
|
| 602 |
+
padding: 10px 12px;
|
| 603 |
+
border: 1px solid #465466;
|
| 604 |
+
border-radius: 2px;
|
| 605 |
+
background: #141c25;
|
| 606 |
+
color: #d6dde5;
|
| 607 |
+
font-size: 12.5px;
|
| 608 |
+
opacity: 0;
|
| 609 |
+
transform: translateY(8px);
|
| 610 |
+
pointer-events: none;
|
| 611 |
+
transition: opacity 140ms ease, transform 140ms ease;
|
| 612 |
+
}
|
| 613 |
+
.toast.error { border-color: #70424a; color: #efb0b8; }
|
| 614 |
.toast.show { opacity: 1; transform: translateY(0); }
|
| 615 |
|
| 616 |
+
/* Responsive ------------------------------------------------------------- */
|
| 617 |
+
@media (max-width: 1250px) {
|
| 618 |
+
.metric-grid.six-agent { grid-template-columns: repeat(3, 1fr); }
|
| 619 |
+
.metric-grid.eight-agent { grid-template-columns: repeat(2, 1fr); }
|
| 620 |
+
}
|
| 621 |
@media (max-width: 1100px) {
|
| 622 |
+
.workspace,
|
| 623 |
+
.planner-grid,
|
| 624 |
+
.agent-layout,
|
| 625 |
+
.research-grid { grid-template-columns: 1fr; }
|
| 626 |
+
.controls-panel,
|
| 627 |
+
.agent-controls,
|
| 628 |
+
.research-controls { position: static; }
|
| 629 |
.metric-grid { grid-template-columns: repeat(3, 1fr); }
|
| 630 |
.paper-grid { grid-template-columns: 1fr 1fr; }
|
| 631 |
}
|
| 632 |
+
@media (max-width: 960px) {
|
| 633 |
+
.field-grid.four { grid-template-columns: 1fr 1fr; }
|
| 634 |
+
}
|
| 635 |
@media (max-width: 720px) {
|
| 636 |
+
.topbar { padding: 0 14px; }
|
| 637 |
+
.runtime-pill { max-width: 180px; }
|
| 638 |
+
.shell { width: calc(100% - 20px); padding-top: 80px; }
|
|
|
|
| 639 |
body.runtime-ready .shell { padding-top: 20px; }
|
| 640 |
+
.intro { padding-top: 8px; }
|
| 641 |
+
.tabs { margin-top: 8px; }
|
| 642 |
+
.field-grid.two,
|
| 643 |
+
.field-grid.four,
|
| 644 |
+
.chart-grid,
|
| 645 |
+
.method-grid { grid-template-columns: 1fr; }
|
| 646 |
+
.metric-grid,
|
| 647 |
+
.metric-grid.four,
|
| 648 |
+
.metric-grid.six-agent,
|
| 649 |
+
.metric-grid.eight-agent { grid-template-columns: 1fr 1fr; }
|
| 650 |
+
.metric { border-bottom: 1px solid var(--line-soft); }
|
| 651 |
.paper-grid { grid-template-columns: 1fr; }
|
| 652 |
.panel-title-row { flex-direction: column; }
|
| 653 |
+
.arena-title-row .compact,
|
| 654 |
+
.stacked-actions .compact { width: 100% !important; }
|
| 655 |
+
.action-stack,
|
| 656 |
+
.stacked-actions { width: 100%; align-items: stretch; }
|
| 657 |
.wide-method { grid-column: auto; }
|
| 658 |
.chart-card.chart-expanded { inset: 6px; }
|
| 659 |
}
|
| 660 |
+
@media (prefers-reduced-motion: reduce) {
|
| 661 |
+
*, *::before, *::after { scroll-behavior: auto !important; transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 662 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|