package bench import "time" // Capacity is what one replica was measured to serve, kept so the measurement // outlives the terminal it was printed in. // // The conclusion of a run, not the run. The ramp itself — every concurrency // level, its latencies, its errors — is worth reading once, while deciding // whether the run was sound; it is not worth carrying around afterwards, and // storing it would grow without bound as a model is re-benchmarked. What a // later reader needs is the number and enough provenance to judge whether it is // still true. type Capacity struct { Model string `json:"model"` Profile string `json:"profile"` // MaxConcurrencyAtSLO is the capacity: the highest concurrency that still // met the target. Zero means even one request did not, which is a real // result and not a missing one — it says this hardware cannot serve this // model at this SLO at all. MaxConcurrencyAtSLO int `json:"max_concurrency_at_slo"` // ThroughputTokPerSec and the latencies are taken at that level, not at the // peak. Peak throughput arrives long after latency has become unusable, so // reporting it would overstate what the hardware can serve. ThroughputTokPerSec float64 `json:"throughput_tok_per_sec"` TTFTp99 time.Duration `json:"ttft_p99"` TPOTp99 time.Duration `json:"tpot_p99"` // SuggestedTargetWaiting and SuggestedKVCeiling are the queue depth and // cache pressure seen while the SLO was still being met, which is what // admission should treat as healthy for this model. Admission currently // applies one global default to every model; these are the per-model // numbers it should be using instead. SuggestedTargetWaiting int `json:"suggested_target_waiting"` SuggestedKVCeiling float64 `json:"suggested_kv_ceiling"` // SLO is recorded because capacity is meaningless without it. The same // replica has a different capacity against a 200ms first-token target than // against a 2s one, and a number stored without the target it was measured // against cannot be compared with anything. SLO SLO `json:"slo"` // Target is the endpoint the load actually went to, kept so a profile // measured against the gateway is never mistaken for one measured against // a replica. Only the second says anything about the hardware. Target string `json:"target"` MeasuredAt time.Time `json:"measured_at"` // Stale is not a field. Whether a measurement still holds depends on // whether the hardware and the engine build have changed since, which this // package cannot know; MeasuredAt is given so the reader can decide. } // Capacity summarizes a completed run for storage. func (r Result) Capacity(profile string) Capacity { return Capacity{ Model: r.Model, Profile: profile, MaxConcurrencyAtSLO: r.MaxConcurrencyAtSLO, ThroughputTokPerSec: r.ThroughputTokPerSec, TTFTp99: r.TTFTp99, TPOTp99: r.TPOTp99, SuggestedTargetWaiting: r.SuggestedTargetWaiting, SuggestedKVCeiling: r.SuggestedKVCeiling, SLO: r.SLO, Target: r.Target, MeasuredAt: r.RanAt, } } // Usable reports whether this measurement says anything. // // A run that never met the SLO, or one stopped before it finished a level, // concludes at zero. That is worth recording — it is the answer to "can this // run here at all" — but a caller sizing a model against it would compute a // replica count of infinity, so it has to be asked about rather than used. func (c Capacity) Usable() bool { return c.MaxConcurrencyAtSLO > 0 }