Skip to content

guidellm.benchmark.schemas

Benchmark schemas for performance measurement and result analysis.

This module consolidates the complete benchmark schema ecosystem, providing both base abstractions for benchmark execution and domain-specific implementations for generative AI tasks. It exports core configuration objects, accumulator interfaces for real-time metric collection, benchmark result containers with statistical summaries, and reporting utilities. The schemas support flexible scheduling strategies, comprehensive metric tracking including latency and throughput distributions, and multi-modal generative benchmarks for text, image, video, and audio generation tasks.

BenchmarkAccumulatorT = TypeVar('BenchmarkAccumulatorT', bound='BenchmarkAccumulator[Any, Any]') module-attribute

Generic type variable for benchmark accumulator implementations

BenchmarkT = TypeVar('BenchmarkT', bound='Benchmark') module-attribute

Generic type variable for benchmark result implementations

Benchmark

Bases: StandardBaseDict, ABC, Generic[BenchmarkAccumulatorT]

Compile and expose final benchmark execution metrics.

Defines the interface for benchmark result implementations capturing comprehensive performance metrics including latency distributions, throughput measurements, and concurrency patterns. Subclasses implement compilation logic to transform accumulated metrics and scheduler state into structured results with statistical summaries.

Source code in src/guidellm/benchmark/schemas/base.py
class Benchmark(StandardBaseDict, ABC, Generic[BenchmarkAccumulatorT]):
    """
    Compile and expose final benchmark execution metrics.

    Defines the interface for benchmark result implementations capturing
    comprehensive performance metrics including latency distributions, throughput
    measurements, and concurrency patterns. Subclasses implement compilation
    logic to transform accumulated metrics and scheduler state into structured
    results with statistical summaries.
    """

    @property
    @abstractmethod
    def start_time(self) -> float:
        """
        :return: Benchmark start timestamp in seconds since epoch
        """

    @property
    @abstractmethod
    def end_time(self) -> float:
        """
        :return: Benchmark completion timestamp in seconds since epoch
        """

    @property
    @abstractmethod
    def duration(self) -> float:
        """
        :return: Benchmark execution duration in seconds
        """

    @property
    @abstractmethod
    def request_latency(self) -> StatusDistributionSummary:
        """
        :return: Statistical distribution of request latencies
        """

    @property
    @abstractmethod
    def request_throughput(self) -> StatusDistributionSummary:
        """
        :return: Statistical distribution of throughput measurements
        """

    @property
    @abstractmethod
    def request_concurrency(self) -> StatusDistributionSummary:
        """
        :return: Statistical distribution of concurrent request counts
        """

    @classmethod
    @abstractmethod
    def compile(
        cls, accumulator: BenchmarkAccumulatorT, scheduler_state: SchedulerState
    ) -> Any:
        """
        Transform accumulated metrics into final benchmark results.

        :param accumulator: Accumulator instance with collected metrics and state
        :param scheduler_state: Scheduler's final state after execution completion
        :return: Compiled benchmark instance with complete statistical results
        """

duration abstractmethod property

Returns:

Type Description
float

Benchmark execution duration in seconds

end_time abstractmethod property

Returns:

Type Description
float

Benchmark completion timestamp in seconds since epoch

request_concurrency abstractmethod property

Returns:

Type Description
StatusDistributionSummary

Statistical distribution of concurrent request counts

request_latency abstractmethod property

Returns:

Type Description
StatusDistributionSummary

Statistical distribution of request latencies

request_throughput abstractmethod property

Returns:

Type Description
StatusDistributionSummary

Statistical distribution of throughput measurements

start_time abstractmethod property

Returns:

Type Description
float

Benchmark start timestamp in seconds since epoch

compile(accumulator, scheduler_state) abstractmethod classmethod

Transform accumulated metrics into final benchmark results.

Parameters:

Name Type Description Default
accumulator BenchmarkAccumulatorT

Accumulator instance with collected metrics and state

required
scheduler_state SchedulerState

Scheduler's final state after execution completion

required

Returns:

Type Description
Any

Compiled benchmark instance with complete statistical results

Source code in src/guidellm/benchmark/schemas/base.py
@classmethod
@abstractmethod
def compile(
    cls, accumulator: BenchmarkAccumulatorT, scheduler_state: SchedulerState
) -> Any:
    """
    Transform accumulated metrics into final benchmark results.

    :param accumulator: Accumulator instance with collected metrics and state
    :param scheduler_state: Scheduler's final state after execution completion
    :return: Compiled benchmark instance with complete statistical results
    """

BenchmarkAccumulator

Bases: StandardBaseDict, ABC, Generic[RequestT, ResponseT]

Track and accumulate benchmark metrics during scheduler execution.

Maintains incremental metric estimates as requests are processed, enabling real-time progress monitoring and efficient metric compilation. Subclasses implement specific metric calculation strategies based on request/response characteristics and scheduler state evolution.

Source code in src/guidellm/benchmark/schemas/base.py
class BenchmarkAccumulator(StandardBaseDict, ABC, Generic[RequestT, ResponseT]):
    """
    Track and accumulate benchmark metrics during scheduler execution.

    Maintains incremental metric estimates as requests are processed, enabling
    real-time progress monitoring and efficient metric compilation. Subclasses
    implement specific metric calculation strategies based on request/response
    characteristics and scheduler state evolution.
    """

    config: BenchmarkConfig = Field(
        description="Benchmark execution configuration and constraints",
    )

    @abstractmethod
    def update_estimate(
        self,
        response: ResponseT | None,
        request: RequestT,
        info: RequestInfo,
        scheduler_state: SchedulerState,
    ):
        """
        Incrementally update metrics with completed request data.

        :param response: Backend response data if request succeeded
        :param request: Request instance submitted to backend
        :param info: Request timing, status, and execution metadata
        :param scheduler_state: Current scheduler state with queue and concurrency info
        """
        ...

update_estimate(response, request, info, scheduler_state) abstractmethod

Incrementally update metrics with completed request data.

Parameters:

Name Type Description Default
response ResponseT | None

Backend response data if request succeeded

required
request RequestT

Request instance submitted to backend

required
info RequestInfo

Request timing, status, and execution metadata

required
scheduler_state SchedulerState

Current scheduler state with queue and concurrency info

required
Source code in src/guidellm/benchmark/schemas/base.py
@abstractmethod
def update_estimate(
    self,
    response: ResponseT | None,
    request: RequestT,
    info: RequestInfo,
    scheduler_state: SchedulerState,
):
    """
    Incrementally update metrics with completed request data.

    :param response: Backend response data if request succeeded
    :param request: Request instance submitted to backend
    :param info: Request timing, status, and execution metadata
    :param scheduler_state: Current scheduler state with queue and concurrency info
    """
    ...

BenchmarkConfig

Bases: StandardBaseDict

Encapsulate execution parameters and constraints for benchmark runs.

Defines comprehensive configuration including scheduler strategy, constraint sets, transient phase handling, metric sampling preferences, and execution metadata. Coordinates profile, request, backend, and environment configurations to enable reproducible benchmark execution with precise control over metric collection.

Source code in src/guidellm/benchmark/schemas/base.py
class BenchmarkConfig(StandardBaseDict):
    """
    Encapsulate execution parameters and constraints for benchmark runs.

    Defines comprehensive configuration including scheduler strategy, constraint
    sets, transient phase handling, metric sampling preferences, and execution
    metadata. Coordinates profile, request, backend, and environment configurations
    to enable reproducible benchmark execution with precise control over metric
    collection.
    """

    id_: str = Field(
        default_factory=lambda: str(uuid.uuid4()),
        description="Unique identifier for this benchmark execution",
    )
    run_id: str = Field(
        description="Identifier grouping related benchmark runs in a series",
    )
    run_index: int = Field(
        description="Zero-based index of this run within the benchmark series",
    )
    strategy: SchedulingStrategy = Field(
        description="Scheduler strategy controlling request execution patterns",
    )
    constraints: dict[str, dict[str, Any]] = Field(
        description="Constraint definitions applied to scheduler strategy execution",
    )
    sample_size: int | None = Field(
        default=None,
        description=(
            "Maximum number of requests per status group to retain full data for. "
            "None keeps all, 0 strips all, N > 0 uses reservoir sampling."
        ),
    )
    warmup: TransientPhaseConfig = Field(
        default_factory=TransientPhaseConfig,
        description="Warmup phase configuration excluding initial transient period",
    )
    cooldown: TransientPhaseConfig = Field(
        default_factory=TransientPhaseConfig,
        description="Cooldown phase configuration excluding final transient period",
    )
    prefer_response_metrics: bool = Field(
        default=True,
        description="Prioritize response-based metrics over request-based metrics",
    )
    slo: GoodputSLO | None = Field(
        default=None,
        description=(
            "Per-request latency objectives defining which requests count "
            "toward goodput. None disables goodput measurement"
        ),
    )
    confidence: float | None = Field(
        default=0.95,
        gt=0.0,
        lt=1.0,
        description=(
            "Two-sided confidence level for the intervals reported alongside "
            "request-level metrics. None reports those metrics without intervals"
        ),
    )
    warnings: list[WarningRuleArgs] = Field(
        default_factory=default_warning_rules,
        description="Post-benchmark warning checks and their thresholds",
    )
    profile: dict[str, Any] = Field(
        description="Profile instance coordinating multi-strategy execution",
    )
    requests: dict[str, Any] = Field(
        description="Request generation configuration and dataset metadata",
    )
    backend: dict[str, Any] = Field(
        description="Backend connection parameters and service configuration",
    )
    environment: dict[str, Any] = Field(
        description="Execution environment details and system metadata",
    )

GenerativeAudioMetricsSummary

Bases: StandardBaseDict

Audio-specific metric summaries for generative benchmarks.

Tracks token, sample count, duration, and byte-level metrics across input, output, and total usage for audio generation workloads.

Source code in src/guidellm/benchmark/schemas/metrics.py
class GenerativeAudioMetricsSummary(StandardBaseDict):
    """
    Audio-specific metric summaries for generative benchmarks.

    Tracks token, sample count, duration, and byte-level metrics across input,
    output, and total usage for audio generation workloads.
    """

    tokens: GenerativeMetricsSummary | None = Field(
        description="Audio token count metrics and distributions"
    )
    samples: GenerativeMetricsSummary | None = Field(
        description="Sample count metrics and distributions"
    )
    seconds: GenerativeMetricsSummary | None = Field(
        description="Duration metrics in seconds and distributions"
    )
    bytes: GenerativeMetricsSummary | None = Field(
        description="Byte size metrics and distributions"
    )

    @classmethod
    def compile(
        cls,
        successful: list[GenerativeRequestStats],
        incomplete: list[GenerativeRequestStats],
        errored: list[GenerativeRequestStats],
    ) -> GenerativeAudioMetricsSummary:
        """
        Compile audio metrics summary from request statistics.

        :param successful: Successfully completed request statistics
        :param incomplete: Incomplete/cancelled request statistics
        :param errored: Failed request statistics
        :return: Compiled audio metrics summary
        """
        return GenerativeAudioMetricsSummary(
            tokens=GenerativeMetricsSummary.compile(
                property_name="audio_tokens",
                successful=successful,
                incomplete=incomplete,
                errored=errored,
            ),
            samples=GenerativeMetricsSummary.compile(
                property_name="audio_samples",
                successful=successful,
                incomplete=incomplete,
                errored=errored,
            ),
            seconds=GenerativeMetricsSummary.compile(
                property_name="audio_seconds",
                successful=successful,
                incomplete=incomplete,
                errored=errored,
            ),
            bytes=GenerativeMetricsSummary.compile(
                property_name="audio_bytes",
                successful=successful,
                incomplete=incomplete,
                errored=errored,
            ),
        )

compile(successful, incomplete, errored) classmethod

Compile audio metrics summary from request statistics.

Parameters:

Name Type Description Default
successful list[GenerativeRequestStats]

Successfully completed request statistics

required
incomplete list[GenerativeRequestStats]

Incomplete/cancelled request statistics

required
errored list[GenerativeRequestStats]

Failed request statistics

required

Returns:

Type Description
GenerativeAudioMetricsSummary

Compiled audio metrics summary

Source code in src/guidellm/benchmark/schemas/metrics.py
@classmethod
def compile(
    cls,
    successful: list[GenerativeRequestStats],
    incomplete: list[GenerativeRequestStats],
    errored: list[GenerativeRequestStats],
) -> GenerativeAudioMetricsSummary:
    """
    Compile audio metrics summary from request statistics.

    :param successful: Successfully completed request statistics
    :param incomplete: Incomplete/cancelled request statistics
    :param errored: Failed request statistics
    :return: Compiled audio metrics summary
    """
    return GenerativeAudioMetricsSummary(
        tokens=GenerativeMetricsSummary.compile(
            property_name="audio_tokens",
            successful=successful,
            incomplete=incomplete,
            errored=errored,
        ),
        samples=GenerativeMetricsSummary.compile(
            property_name="audio_samples",
            successful=successful,
            incomplete=incomplete,
            errored=errored,
        ),
        seconds=GenerativeMetricsSummary.compile(
            property_name="audio_seconds",
            successful=successful,
            incomplete=incomplete,
            errored=errored,
        ),
        bytes=GenerativeMetricsSummary.compile(
            property_name="audio_bytes",
            successful=successful,
            incomplete=incomplete,
            errored=errored,
        ),
    )

GenerativeBenchmark

Bases: Benchmark[GenerativeBenchmarkAccumulator]

Complete generative AI benchmark results with specialized metrics.

Encapsulates comprehensive performance data from scheduler-driven generative workload executions including request-level statistics, token/latency distributions, throughput analysis, and concurrency patterns. Provides computed fields for temporal analysis and status-grouped request details for detailed post-execution reporting.

Source code in src/guidellm/benchmark/schemas/benchmark.py
class GenerativeBenchmark(Benchmark[GenerativeBenchmarkAccumulator]):
    """Complete generative AI benchmark results with specialized metrics.

    Encapsulates comprehensive performance data from scheduler-driven generative
    workload executions including request-level statistics, token/latency distributions,
    throughput analysis, and concurrency patterns. Provides computed fields for temporal
    analysis and status-grouped request details for detailed post-execution reporting.
    """

    type_: Literal["generative_benchmark"] = "generative_benchmark"  # type: ignore[assignment]

    config: BenchmarkConfig = Field(
        description="Configuration parameters for this benchmark execution",
    )
    scheduler_state: SchedulerState = Field(
        description="Final state of the scheduler after benchmark completion",
    )
    scheduler_metrics: SchedulerMetrics = Field(
        description="Scheduler timing and performance statistics",
    )
    metrics: GenerativeMetrics = Field(
        description="Performance metrics and statistical distributions",
    )
    requests: StatusBreakdown[
        list[GenerativeRequestStats],
        list[GenerativeRequestStats],
        list[GenerativeRequestStats],
        None,
    ] = Field(
        description=(
            "Request details grouped by status: successful, incomplete, errored"
        ),
    )
    warnings: list[BenchmarkWarning] = Field(
        default_factory=list,
        description="Post-benchmark warnings for a missed arrival schedule",
    )

    @computed_field  # type: ignore[prop-decorator]
    @property
    def start_time(self) -> float:
        """
        :return: Benchmark start time in seconds since epoch
        """
        return self.scheduler_metrics.measure_start_time

    @computed_field  # type: ignore[prop-decorator]
    @property
    def end_time(self) -> float:
        """
        :return: Benchmark end time in seconds since epoch
        """
        return self.scheduler_metrics.measure_end_time

    @computed_field  # type: ignore[prop-decorator]
    @property
    def duration(self) -> float:
        """
        :return: Total benchmark execution duration in seconds
        """
        return self.end_time - self.start_time

    @computed_field  # type: ignore[prop-decorator]
    @property
    def warmup_duration(self) -> float:
        """
        :return: Warmup phase duration in seconds
        """
        return (
            self.scheduler_metrics.measure_start_time
            - self.scheduler_metrics.request_start_time
        )

    @computed_field  # type: ignore[prop-decorator]
    @property
    def cooldown_duration(self) -> float:
        """
        :return: Cooldown phase duration in seconds
        """
        return (
            self.scheduler_metrics.request_end_time
            - self.scheduler_metrics.measure_end_time
        )

    @property
    def request_latency(self) -> StatusDistributionSummary:
        """
        :return: Statistical distribution of request latencies across all requests
        """
        return self.metrics.request_latency

    @property
    def request_throughput(self) -> StatusDistributionSummary:
        """
        :return: Statistical distribution of throughput measured in requests per second
        """
        return self.metrics.requests_per_second

    @property
    def request_concurrency(self) -> StatusDistributionSummary:
        """
        :return: Statistical distribution of concurrent requests throughout execution
        """
        return self.metrics.request_concurrency

    @classmethod
    def compile(
        cls,
        accumulator: GenerativeBenchmarkAccumulator,
        scheduler_state: SchedulerState,
    ) -> GenerativeBenchmark:
        """
        Compile final benchmark results from accumulated execution state.

        :param accumulator: Accumulated benchmark state with request statistics
        :param scheduler_state: Final scheduler state after execution completion
        :return: Compiled generative benchmark instance with complete metrics
        """
        metrics = GenerativeMetrics.compile(accumulator)
        scheduler_metrics = SchedulerMetrics.compile(accumulator, scheduler_state)
        warnings = BenchmarkWarningAnalyzer(accumulator.config.warnings).analyze(
            scheduler_metrics=scheduler_metrics,
            metrics=metrics,
        )
        return GenerativeBenchmark(
            config=accumulator.config,
            scheduler_state=scheduler_state,
            scheduler_metrics=scheduler_metrics,
            metrics=metrics,
            requests=StatusBreakdown(
                successful=accumulator.completed.get_sampled(),
                incomplete=accumulator.incomplete.get_sampled(),
                errored=accumulator.errored.get_sampled(),
                total=None,
            ),
            warnings=warnings,
        )

cooldown_duration property

Returns:

Type Description
float

Cooldown phase duration in seconds

duration property

Returns:

Type Description
float

Total benchmark execution duration in seconds

end_time property

Returns:

Type Description
float

Benchmark end time in seconds since epoch

request_concurrency property

Returns:

Type Description
StatusDistributionSummary

Statistical distribution of concurrent requests throughout execution

request_latency property

Returns:

Type Description
StatusDistributionSummary

Statistical distribution of request latencies across all requests

request_throughput property

Returns:

Type Description
StatusDistributionSummary

Statistical distribution of throughput measured in requests per second

start_time property

Returns:

Type Description
float

Benchmark start time in seconds since epoch

warmup_duration property

Returns:

Type Description
float

Warmup phase duration in seconds

compile(accumulator, scheduler_state) classmethod

Compile final benchmark results from accumulated execution state.

Parameters:

Name Type Description Default
accumulator GenerativeBenchmarkAccumulator

Accumulated benchmark state with request statistics

required
scheduler_state SchedulerState

Final scheduler state after execution completion

required

Returns:

Type Description
GenerativeBenchmark

Compiled generative benchmark instance with complete metrics

Source code in src/guidellm/benchmark/schemas/benchmark.py
@classmethod
def compile(
    cls,
    accumulator: GenerativeBenchmarkAccumulator,
    scheduler_state: SchedulerState,
) -> GenerativeBenchmark:
    """
    Compile final benchmark results from accumulated execution state.

    :param accumulator: Accumulated benchmark state with request statistics
    :param scheduler_state: Final scheduler state after execution completion
    :return: Compiled generative benchmark instance with complete metrics
    """
    metrics = GenerativeMetrics.compile(accumulator)
    scheduler_metrics = SchedulerMetrics.compile(accumulator, scheduler_state)
    warnings = BenchmarkWarningAnalyzer(accumulator.config.warnings).analyze(
        scheduler_metrics=scheduler_metrics,
        metrics=metrics,
    )
    return GenerativeBenchmark(
        config=accumulator.config,
        scheduler_state=scheduler_state,
        scheduler_metrics=scheduler_metrics,
        metrics=metrics,
        requests=StatusBreakdown(
            successful=accumulator.completed.get_sampled(),
            incomplete=accumulator.incomplete.get_sampled(),
            errored=accumulator.errored.get_sampled(),
            total=None,
        ),
        warnings=warnings,
    )

GenerativeBenchmarkAccumulator

Bases: BenchmarkAccumulator[GenerationRequest, GenerationResponse]

Primary accumulator for generative benchmark execution metrics and statistics.

Orchestrates real-time metric collection across timing, scheduler, concurrency, and generative performance dimensions. Maintains separate accumulators for completed, errored, and incomplete requests while tracking overall metrics. Integrates with scheduler state to monitor warmup/cooldown phases and compute time-weighted statistics for throughput and latency analysis.

Source code in src/guidellm/benchmark/schemas/accumulator.py
class GenerativeBenchmarkAccumulator(
    BenchmarkAccumulator[GenerationRequest, GenerationResponse]
):
    """
    Primary accumulator for generative benchmark execution metrics and statistics.

    Orchestrates real-time metric collection across timing, scheduler, concurrency, and
    generative performance dimensions. Maintains separate accumulators for completed,
    errored, and incomplete requests while tracking overall metrics. Integrates with
    scheduler state to monitor warmup/cooldown phases and compute time-weighted
    statistics for throughput and latency analysis.
    """

    timings: GenerativeBenchmarkTimings = Field(
        default_factory=GenerativeBenchmarkTimings,
        description="Timing phases and transitions during benchmark execution",
    )
    completed: GenerativeRequestsAccumulator = Field(
        default_factory=GenerativeRequestsAccumulator,
        description="Accumulator for completed requests",
    )
    errored: GenerativeRequestsAccumulator = Field(
        default_factory=GenerativeRequestsAccumulator,
        description="Accumulator for errored requests",
    )
    incomplete: GenerativeRequestsAccumulator = Field(
        default_factory=GenerativeRequestsAccumulator,
        description="Accumulator for incomplete requests",
    )
    scheduler_metrics: SchedulerMetricsAccumulator = Field(
        default_factory=SchedulerMetricsAccumulator,
        description="Running metrics for scheduler state",
    )
    concurrency_metric: RunningMetricStats = Field(
        default_factory=RunningMetricStats,
        description="Accumulated request concurrency statistics",
    )
    total_metrics: GenerativeMetricsAccumulator = Field(
        default_factory=GenerativeMetricsAccumulator,
        description="Running metrics for all requests",
    )
    completed_metrics: GenerativeMetricsAccumulator = Field(
        default_factory=GenerativeMetricsAccumulator,
        description="Running metrics for completed requests",
    )
    errored_metrics: GenerativeMetricsAccumulator = Field(
        default_factory=GenerativeMetricsAccumulator,
        description="Running metrics for errored requests",
    )
    incomplete_metrics: GenerativeMetricsAccumulator = Field(
        default_factory=GenerativeMetricsAccumulator,
        description="Running metrics for incomplete requests",
    )

    def model_post_init(self, __context):
        """
        Initialize child accumulators with config values after model construction.

        Propagates sample_size from config to child request accumulators to ensure
        consistent sampling behavior across completed, errored, and incomplete request
        collections. This ensures the --metrics-sample-size option functions correctly.
        """
        super().model_post_init(__context)

        # Propagate sample_size from config to child accumulators
        self.completed.sample_size = self.config.sample_size
        self.errored.sample_size = self.config.sample_size
        self.incomplete.sample_size = self.config.sample_size

    def update_estimate(
        self,
        response: GenerationResponse | None,
        request: GenerationRequest,
        info: RequestInfo,
        scheduler_state: SchedulerState,
    ):
        """
        Update all benchmark metrics with a completed request.

        Processes request completion by updating timing phases, concurrency metrics,
        scheduler statistics, and generative performance metrics. Routes request to
        appropriate status-specific accumulator (completed/errored/incomplete) and
        updates aggregate totals. Cancelled requests that never started are ignored.

        :param response: Generation response with output and metrics, or None
        :param request: Original generation request with input data
        :param info: Request execution information and timing
        :param scheduler_state: Current scheduler state for phase tracking
        """
        self.timings.update_estimate(info, scheduler_state, self.config)
        duration = self.timings.duration
        elapsed_time_last_update = self.timings.elapsed_time_last_update
        self.concurrency_metric.update_estimate(
            value=scheduler_state.processing_requests,
            duration=duration,
            elapsed=elapsed_time_last_update,
        )

        requests_accumulator: GenerativeRequestsAccumulator
        metrics_accumulator: GenerativeMetricsAccumulator

        if info.status == "completed":
            requests_accumulator = self.completed
            metrics_accumulator = self.completed_metrics
        elif info.status == "errored":
            requests_accumulator = self.errored
            metrics_accumulator = self.errored_metrics
        elif info.status == "cancelled" and info.timings.resolve_start is not None:
            requests_accumulator = self.incomplete
            metrics_accumulator = self.incomplete_metrics
        else:
            # Not a terminal status or cancelled before starting
            # Do not include in requests or metrics
            return

        stats = requests_accumulator.update_estimate(
            response, request, info, self.config.prefer_response_metrics
        )
        metrics_accumulator.update_estimate(stats, duration)
        self.total_metrics.update_estimate(stats, duration)
        self.scheduler_metrics.update_estimate(scheduler_state, stats)

model_post_init(__context)

Initialize child accumulators with config values after model construction.

Propagates sample_size from config to child request accumulators to ensure consistent sampling behavior across completed, errored, and incomplete request collections. This ensures the --metrics-sample-size option functions correctly.

Source code in src/guidellm/benchmark/schemas/accumulator.py
def model_post_init(self, __context):
    """
    Initialize child accumulators with config values after model construction.

    Propagates sample_size from config to child request accumulators to ensure
    consistent sampling behavior across completed, errored, and incomplete request
    collections. This ensures the --metrics-sample-size option functions correctly.
    """
    super().model_post_init(__context)

    # Propagate sample_size from config to child accumulators
    self.completed.sample_size = self.config.sample_size
    self.errored.sample_size = self.config.sample_size
    self.incomplete.sample_size = self.config.sample_size

update_estimate(response, request, info, scheduler_state)

Update all benchmark metrics with a completed request.

Processes request completion by updating timing phases, concurrency metrics, scheduler statistics, and generative performance metrics. Routes request to appropriate status-specific accumulator (completed/errored/incomplete) and updates aggregate totals. Cancelled requests that never started are ignored.

Parameters:

Name Type Description Default
response GenerationResponse | None

Generation response with output and metrics, or None

required
request GenerationRequest

Original generation request with input data

required
info RequestInfo

Request execution information and timing

required
scheduler_state SchedulerState

Current scheduler state for phase tracking

required
Source code in src/guidellm/benchmark/schemas/accumulator.py
def update_estimate(
    self,
    response: GenerationResponse | None,
    request: GenerationRequest,
    info: RequestInfo,
    scheduler_state: SchedulerState,
):
    """
    Update all benchmark metrics with a completed request.

    Processes request completion by updating timing phases, concurrency metrics,
    scheduler statistics, and generative performance metrics. Routes request to
    appropriate status-specific accumulator (completed/errored/incomplete) and
    updates aggregate totals. Cancelled requests that never started are ignored.

    :param response: Generation response with output and metrics, or None
    :param request: Original generation request with input data
    :param info: Request execution information and timing
    :param scheduler_state: Current scheduler state for phase tracking
    """
    self.timings.update_estimate(info, scheduler_state, self.config)
    duration = self.timings.duration
    elapsed_time_last_update = self.timings.elapsed_time_last_update
    self.concurrency_metric.update_estimate(
        value=scheduler_state.processing_requests,
        duration=duration,
        elapsed=elapsed_time_last_update,
    )

    requests_accumulator: GenerativeRequestsAccumulator
    metrics_accumulator: GenerativeMetricsAccumulator

    if info.status == "completed":
        requests_accumulator = self.completed
        metrics_accumulator = self.completed_metrics
    elif info.status == "errored":
        requests_accumulator = self.errored
        metrics_accumulator = self.errored_metrics
    elif info.status == "cancelled" and info.timings.resolve_start is not None:
        requests_accumulator = self.incomplete
        metrics_accumulator = self.incomplete_metrics
    else:
        # Not a terminal status or cancelled before starting
        # Do not include in requests or metrics
        return

    stats = requests_accumulator.update_estimate(
        response, request, info, self.config.prefer_response_metrics
    )
    metrics_accumulator.update_estimate(stats, duration)
    self.total_metrics.update_estimate(stats, duration)
    self.scheduler_metrics.update_estimate(scheduler_state, stats)

GenerativeBenchmarkMetadata

Bases: StandardBaseModel

Versioning and environment metadata for generative benchmark reports.

Source code in src/guidellm/benchmark/schemas/report.py
class GenerativeBenchmarkMetadata(StandardBaseModel):
    """
    Versioning and environment metadata for generative benchmark reports.
    """

    # Make sure to update version when making breaking changes to report schema
    version: Literal[2] = Field(
        description=(
            "Version of the benchmark report schema, increments "
            "whenever there is a breaking change to the output format"
        ),
        default=2,
    )
    guidellm_version: str = Field(
        description="Version of the guidellm package used for the benchmark",
        default_factory=lambda: version("guidellm"),
    )
    python_version: str = Field(
        description="Version of Python interpreter used during the benchmark",
        default_factory=platform.python_version,
    )
    platform: str = Field(
        description="Operating system platform where the benchmark was executed",
        default_factory=platform.platform,
    )

GenerativeBenchmarkTimings

Bases: StandardBaseModel

Tracks timing phases and transitions during benchmark execution.

Monitors timestamps throughout benchmark execution including request submission, measurement period boundaries (warmup/active/cooldown), and completion events. Provides duration calculations and phase status determination based on configured warmup and cooldown periods.

Source code in src/guidellm/benchmark/schemas/accumulator.py
class GenerativeBenchmarkTimings(StandardBaseModel):
    """
    Tracks timing phases and transitions during benchmark execution.

    Monitors timestamps throughout benchmark execution including request submission,
    measurement period boundaries (warmup/active/cooldown), and completion events.
    Provides duration calculations and phase status determination based on configured
    warmup and cooldown periods.
    """

    request_start: float | None = Field(
        description="Timestamp when the first request was sent", default=None
    )
    measure_start: float | None = Field(
        description="Timestamp when measurement period started", default=None
    )
    measure_end: float | None = Field(
        description="Timestamp when measurement period ended", default=None
    )
    request_end: float | None = Field(
        description="Timestamp when the last request was completed", default=None
    )
    current_update: float | None = Field(
        description="Most recent timestamp observed during execution", default=None
    )
    current_request: float | None = Field(
        description="Most recent request completion timestamp observed", default=None
    )
    last_update: float | None = Field(
        description="Previous timestamp observed before the current one", default=None
    )
    last_request: float | None = Field(
        description="Previous request completion timestamp before the current one",
        default=None,
    )

    @property
    def status(self) -> Literal["pending", "warmup", "active", "cooldown"]:
        """
        :return: Current execution phase based on timing thresholds
        """
        if self.request_start is None or self.current_update is None:
            return "pending"

        if self.measure_start is None or self.current_update <= self.measure_start:
            return "warmup"

        if self.measure_end is not None and self.current_update >= self.measure_end:
            return "cooldown"

        return "active"

    @property
    def duration(self) -> float:
        """
        :return: Elapsed time since measurement or request start in seconds
        """
        if self.request_start is None or self.current_update is None:
            return 0.0

        return self.current_update - self.request_start

    @property
    def elapsed_time_last_update(self) -> float:
        """
        :return: Time elapsed between the last two update timestamps in seconds
        """
        if self.current_update is None or self.last_update is None:
            return 0.0

        return self.current_update - self.last_update

    @property
    def elapsed_time_last_request(self) -> float:
        """
        :return: Time elapsed between the last two request completions in seconds
        """
        if self.current_request is None or self.last_request is None:
            return 0.0

        return self.current_request - self.last_request

    @property
    def finalized_request_start(self) -> float:
        """
        :return: Finalized timestamp from the current state for when requests started
        """
        return self.request_start or -1.0

    @property
    def finalized_measure_start(self) -> float:
        """
        :return: Finalized timestamp from the current state for when measurement started
        """
        return self.measure_start or self.finalized_request_start

    @property
    def finalized_measure_end(self) -> float:
        """
        :return: Finalized timestamp from the current state for when measurement ended
        """
        return self.measure_end or self.finalized_request_end

    @property
    def finalized_request_end(self) -> float:
        """
        :return: Finalized timestamp from the current state for when requests ended
        """
        return self.request_end or self.current_request or -1.0

    def update_estimate(
        self,
        info: RequestInfo,
        scheduler_state: SchedulerState,
        config: BenchmarkConfig,
    ):
        """
        Update timing estimates based on request info and scheduler state.

        Advances timing markers through benchmark phases (warmup to active to cooldown)
        based on configured thresholds. Updates current/last timestamps for updates and
        request completions, determining measurement period boundaries.

        :param info: Request information containing timing data
        :param scheduler_state: Current scheduler state with progress metrics
        :param config: Benchmark configuration with warmup/cooldown settings
        """
        # First update non terminal timestamps
        self.request_start = scheduler_state.start_requests_time
        self.last_update = self.current_update
        if (current_time := info.timings.last_reported) is not None:
            self.current_update = (
                current_time
                if self.current_update is None
                else max(self.current_update, current_time)
            )

        # Next update measurement period timestamps, if available and possible
        warmup_active, measure_start = config.warmup.compute_transition_time(
            info=info, state=scheduler_state, period="start"
        )
        if not warmup_active:
            # No warmup, set measure_start to first request start
            self.measure_start = self.request_start
        elif measure_start is not None:
            self.measure_start = measure_start
        cooldown_active, measure_end = config.cooldown.compute_transition_time(
            info=info, state=scheduler_state, period="end"
        )
        if cooldown_active and measure_end is not None:
            self.measure_end = measure_end

        # Update last request terminal timestamps, if request is terminal
        if info.status in {"completed", "errored", "cancelled"}:
            self.last_request = self.current_request
            if info.completed_at is not None and (
                self.current_request is None or info.completed_at > self.current_request
            ):
                self.current_request = info.completed_at

        # Finally, update request stop timestamps, if at that stage and available
        if scheduler_state.end_processing_time is not None and self.request_end is None:
            self.request_end = (
                scheduler_state.progress.stop_time
                or self.current_request
                or scheduler_state.end_processing_time
            )
            if self.measure_end is None:
                # No cooldown triggered, set measure_end to request_end
                self.measure_end = self.request_end

duration property

Returns:

Type Description
float

Elapsed time since measurement or request start in seconds

elapsed_time_last_request property

Returns:

Type Description
float

Time elapsed between the last two request completions in seconds

elapsed_time_last_update property

Returns:

Type Description
float

Time elapsed between the last two update timestamps in seconds

finalized_measure_end property

Returns:

Type Description
float

Finalized timestamp from the current state for when measurement ended

finalized_measure_start property

Returns:

Type Description
float

Finalized timestamp from the current state for when measurement started

finalized_request_end property

Returns:

Type Description
float

Finalized timestamp from the current state for when requests ended

finalized_request_start property

Returns:

Type Description
float

Finalized timestamp from the current state for when requests started

status property

Returns:

Type Description
Literal['pending', 'warmup', 'active', 'cooldown']

Current execution phase based on timing thresholds

update_estimate(info, scheduler_state, config)

Update timing estimates based on request info and scheduler state.

Advances timing markers through benchmark phases (warmup to active to cooldown) based on configured thresholds. Updates current/last timestamps for updates and request completions, determining measurement period boundaries.

Parameters:

Name Type Description Default
info RequestInfo

Request information containing timing data

required
scheduler_state SchedulerState

Current scheduler state with progress metrics

required
config BenchmarkConfig

Benchmark configuration with warmup/cooldown settings

required
Source code in src/guidellm/benchmark/schemas/accumulator.py
def update_estimate(
    self,
    info: RequestInfo,
    scheduler_state: SchedulerState,
    config: BenchmarkConfig,
):
    """
    Update timing estimates based on request info and scheduler state.

    Advances timing markers through benchmark phases (warmup to active to cooldown)
    based on configured thresholds. Updates current/last timestamps for updates and
    request completions, determining measurement period boundaries.

    :param info: Request information containing timing data
    :param scheduler_state: Current scheduler state with progress metrics
    :param config: Benchmark configuration with warmup/cooldown settings
    """
    # First update non terminal timestamps
    self.request_start = scheduler_state.start_requests_time
    self.last_update = self.current_update
    if (current_time := info.timings.last_reported) is not None:
        self.current_update = (
            current_time
            if self.current_update is None
            else max(self.current_update, current_time)
        )

    # Next update measurement period timestamps, if available and possible
    warmup_active, measure_start = config.warmup.compute_transition_time(
        info=info, state=scheduler_state, period="start"
    )
    if not warmup_active:
        # No warmup, set measure_start to first request start
        self.measure_start = self.request_start
    elif measure_start is not None:
        self.measure_start = measure_start
    cooldown_active, measure_end = config.cooldown.compute_transition_time(
        info=info, state=scheduler_state, period="end"
    )
    if cooldown_active and measure_end is not None:
        self.measure_end = measure_end

    # Update last request terminal timestamps, if request is terminal
    if info.status in {"completed", "errored", "cancelled"}:
        self.last_request = self.current_request
        if info.completed_at is not None and (
            self.current_request is None or info.completed_at > self.current_request
        ):
            self.current_request = info.completed_at

    # Finally, update request stop timestamps, if at that stage and available
    if scheduler_state.end_processing_time is not None and self.request_end is None:
        self.request_end = (
            scheduler_state.progress.stop_time
            or self.current_request
            or scheduler_state.end_processing_time
        )
        if self.measure_end is None:
            # No cooldown triggered, set measure_end to request_end
            self.measure_end = self.request_end

GenerativeBenchmarksReport

Bases: StandardBaseModel

Container for multiple benchmark results with load/save functionality.

Aggregates multiple generative benchmark executions into a single report, providing persistence through JSON and YAML file formats. Enables result collection, storage, and retrieval across different execution sessions with automatic file type detection and path resolution.

Attributes:

Name Type Description
DEFAULT_FILE str

Default filename used when saving to or loading from a directory

Source code in src/guidellm/benchmark/schemas/report.py
class GenerativeBenchmarksReport(StandardBaseModel):
    """Container for multiple benchmark results with load/save functionality.

    Aggregates multiple generative benchmark executions into a single report,
    providing persistence through JSON and YAML file formats. Enables result
    collection, storage, and retrieval across different execution sessions with
    automatic file type detection and path resolution.

    :cvar DEFAULT_FILE: Default filename used when saving to or loading from a directory
    """

    DEFAULT_FILE: ClassVar[str] = "benchmarks.json"

    metadata: GenerativeBenchmarkMetadata = Field(
        description="Metadata about the benchmark report and execution environment",
        default_factory=GenerativeBenchmarkMetadata,
    )
    config: BenchmarkScenario = Field(
        description="Benchmark arguments used for all benchmarks in the report"
    )
    benchmarks: list[GenerativeBenchmark] = Field(
        description="List of completed benchmarks in the report",
        default_factory=list,
    )
    conclusions: list[dict[str, Any]] = Field(
        default_factory=list,
        description=(
            "What each profile concluded once every strategy it planned has "
            "run, for profiles that answer a question rather than execute a "
            "fixed sequence. Empty when no profile in the run computed one"
        ),
    )

    def save_file(
        self,
        path: str | Path | None = None,
        type_: Literal["json", "yaml"] | None = None,
    ) -> Path:
        """
        Save report to file in JSON or YAML format.

        :param path: File path or directory for saving, defaults to current directory
            with DEFAULT_FILE name
        :param type_: File format override ('json' or 'yaml'), auto-detected from
            extension if None
        :return: Resolved path to the saved file
        :raises ValueError: If file type is unsupported or cannot be determined
        """
        file_path = GenerativeBenchmarksReport._resolve_path(
            path if path is not None else Path.cwd()
        )
        file_path.parent.mkdir(parents=True, exist_ok=True)
        file_type = type_ or file_path.suffix.lower()[1:]
        model_dict = self.model_dump(mode="json")
        if file_type == "json":
            save_str = json.dumps(model_dict)
        elif file_type in ["yaml", "yml"]:
            save_str = yaml.dump(model_dict)
        else:
            raise ValueError(f"Unsupported file type: {file_type} for {file_path}.")

        with file_path.open("w") as file:
            file.write(save_str)

        return file_path

    @classmethod
    def load_file(
        cls, path: str | Path, type_: Literal["json", "yaml"] | None = None
    ) -> GenerativeBenchmarksReport:
        """
        Load report from JSON or YAML file.

        :param path: File path or directory containing DEFAULT_FILE to load from
        :param type_: File format override ('json' or 'yaml'), auto-detected from
            extension if None
        :return: Loaded report instance with benchmarks and configuration
        :raises ValueError: If file type is unsupported or cannot be determined
        :raises FileNotFoundError: If specified file does not exist
        """
        file_path = GenerativeBenchmarksReport._resolve_path(path)
        file_type = type_ or file_path.suffix.lower()[1:]

        with file_path.open("r") as file:
            if file_type == "json":
                model_dict = json.loads(file.read())
            elif file_type in ["yaml", "yml"]:
                model_dict = yaml.safe_load(file)
            else:
                raise ValueError(f"Unsupported file type: {file_type} for {file_path}.")

        return GenerativeBenchmarksReport.model_validate(model_dict)

    @classmethod
    def _resolve_path(cls, path: str | Path) -> Path:
        """
        Resolve input to file path, converting directories to DEFAULT_FILE location.

        :param path: String or Path to resolve, directories append DEFAULT_FILE
        :return: Resolved file path
        """
        resolved = Path(path) if not isinstance(path, Path) else path

        if resolved.is_dir():
            resolved = resolved / GenerativeBenchmarksReport.DEFAULT_FILE

        return resolved

load_file(path, type_=None) classmethod

Load report from JSON or YAML file.

Parameters:

Name Type Description Default
path str | Path

File path or directory containing DEFAULT_FILE to load from

required
type_ Literal['json', 'yaml'] | None

File format override ('json' or 'yaml'), auto-detected from extension if None

None

Returns:

Type Description
GenerativeBenchmarksReport

Loaded report instance with benchmarks and configuration

Raises:

Type Description
ValueError

If file type is unsupported or cannot be determined

FileNotFoundError

If specified file does not exist

Source code in src/guidellm/benchmark/schemas/report.py
@classmethod
def load_file(
    cls, path: str | Path, type_: Literal["json", "yaml"] | None = None
) -> GenerativeBenchmarksReport:
    """
    Load report from JSON or YAML file.

    :param path: File path or directory containing DEFAULT_FILE to load from
    :param type_: File format override ('json' or 'yaml'), auto-detected from
        extension if None
    :return: Loaded report instance with benchmarks and configuration
    :raises ValueError: If file type is unsupported or cannot be determined
    :raises FileNotFoundError: If specified file does not exist
    """
    file_path = GenerativeBenchmarksReport._resolve_path(path)
    file_type = type_ or file_path.suffix.lower()[1:]

    with file_path.open("r") as file:
        if file_type == "json":
            model_dict = json.loads(file.read())
        elif file_type in ["yaml", "yml"]:
            model_dict = yaml.safe_load(file)
        else:
            raise ValueError(f"Unsupported file type: {file_type} for {file_path}.")

    return GenerativeBenchmarksReport.model_validate(model_dict)

save_file(path=None, type_=None)

Save report to file in JSON or YAML format.

Parameters:

Name Type Description Default
path str | Path | None

File path or directory for saving, defaults to current directory with DEFAULT_FILE name

None
type_ Literal['json', 'yaml'] | None

File format override ('json' or 'yaml'), auto-detected from extension if None

None

Returns:

Type Description
Path

Resolved path to the saved file

Raises:

Type Description
ValueError

If file type is unsupported or cannot be determined

Source code in src/guidellm/benchmark/schemas/report.py
def save_file(
    self,
    path: str | Path | None = None,
    type_: Literal["json", "yaml"] | None = None,
) -> Path:
    """
    Save report to file in JSON or YAML format.

    :param path: File path or directory for saving, defaults to current directory
        with DEFAULT_FILE name
    :param type_: File format override ('json' or 'yaml'), auto-detected from
        extension if None
    :return: Resolved path to the saved file
    :raises ValueError: If file type is unsupported or cannot be determined
    """
    file_path = GenerativeBenchmarksReport._resolve_path(
        path if path is not None else Path.cwd()
    )
    file_path.parent.mkdir(parents=True, exist_ok=True)
    file_type = type_ or file_path.suffix.lower()[1:]
    model_dict = self.model_dump(mode="json")
    if file_type == "json":
        save_str = json.dumps(model_dict)
    elif file_type in ["yaml", "yml"]:
        save_str = yaml.dump(model_dict)
    else:
        raise ValueError(f"Unsupported file type: {file_type} for {file_path}.")

    with file_path.open("w") as file:
        file.write(save_str)

    return file_path

GenerativeImageMetricsSummary

Bases: StandardBaseDict

Image-specific metric summaries for generative benchmarks.

Tracks token, image count, pixel, and byte-level metrics across input, output, and total usage for image generation workloads.

Source code in src/guidellm/benchmark/schemas/metrics.py
class GenerativeImageMetricsSummary(StandardBaseDict):
    """
    Image-specific metric summaries for generative benchmarks.

    Tracks token, image count, pixel, and byte-level metrics across input, output,
    and total usage for image generation workloads.
    """

    tokens: GenerativeMetricsSummary | None = Field(
        description="Image token count metrics and distributions"
    )
    images: GenerativeMetricsSummary | None = Field(
        description="Image count metrics and distributions"
    )
    pixels: GenerativeMetricsSummary | None = Field(
        description="Pixel count metrics and distributions"
    )
    bytes: GenerativeMetricsSummary | None = Field(
        description="Byte size metrics and distributions"
    )

    @classmethod
    def compile(
        cls,
        successful: list[GenerativeRequestStats],
        incomplete: list[GenerativeRequestStats],
        errored: list[GenerativeRequestStats],
    ) -> GenerativeImageMetricsSummary:
        """
        Compile image metrics summary from request statistics.

        :param successful: Successfully completed request statistics
        :param incomplete: Incomplete/cancelled request statistics
        :param errored: Failed request statistics
        :return: Compiled image metrics summary
        """
        return GenerativeImageMetricsSummary(
            tokens=GenerativeMetricsSummary.compile(
                property_name="image_tokens",
                successful=successful,
                incomplete=incomplete,
                errored=errored,
            ),
            images=GenerativeMetricsSummary.compile(
                property_name="image_count",
                successful=successful,
                incomplete=incomplete,
                errored=errored,
            ),
            pixels=GenerativeMetricsSummary.compile(
                property_name="image_pixels",
                successful=successful,
                incomplete=incomplete,
                errored=errored,
            ),
            bytes=GenerativeMetricsSummary.compile(
                property_name="image_bytes",
                successful=successful,
                incomplete=incomplete,
                errored=errored,
            ),
        )

compile(successful, incomplete, errored) classmethod

Compile image metrics summary from request statistics.

Parameters:

Name Type Description Default
successful list[GenerativeRequestStats]

Successfully completed request statistics

required
incomplete list[GenerativeRequestStats]

Incomplete/cancelled request statistics

required
errored list[GenerativeRequestStats]

Failed request statistics

required

Returns:

Type Description
GenerativeImageMetricsSummary

Compiled image metrics summary

Source code in src/guidellm/benchmark/schemas/metrics.py
@classmethod
def compile(
    cls,
    successful: list[GenerativeRequestStats],
    incomplete: list[GenerativeRequestStats],
    errored: list[GenerativeRequestStats],
) -> GenerativeImageMetricsSummary:
    """
    Compile image metrics summary from request statistics.

    :param successful: Successfully completed request statistics
    :param incomplete: Incomplete/cancelled request statistics
    :param errored: Failed request statistics
    :return: Compiled image metrics summary
    """
    return GenerativeImageMetricsSummary(
        tokens=GenerativeMetricsSummary.compile(
            property_name="image_tokens",
            successful=successful,
            incomplete=incomplete,
            errored=errored,
        ),
        images=GenerativeMetricsSummary.compile(
            property_name="image_count",
            successful=successful,
            incomplete=incomplete,
            errored=errored,
        ),
        pixels=GenerativeMetricsSummary.compile(
            property_name="image_pixels",
            successful=successful,
            incomplete=incomplete,
            errored=errored,
        ),
        bytes=GenerativeMetricsSummary.compile(
            property_name="image_bytes",
            successful=successful,
            incomplete=incomplete,
            errored=errored,
        ),
    )

GenerativeMetrics

Bases: StandardBaseDict

Comprehensive metrics for generative AI benchmarks.

Aggregates request statistics, token metrics, timing distributions, and domain-specific measurements across text, image, video, and audio modalities. Provides detailed statistical summaries including distribution analysis for throughput, latency, concurrency, and resource utilization metrics across successful, incomplete, and errored requests.

Source code in src/guidellm/benchmark/schemas/metrics.py
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
class GenerativeMetrics(StandardBaseDict):
    """
    Comprehensive metrics for generative AI benchmarks.

    Aggregates request statistics, token metrics, timing distributions, and
    domain-specific measurements across text, image, video, and audio modalities.
    Provides detailed statistical summaries including distribution analysis for
    throughput, latency, concurrency, and resource utilization metrics across
    successful, incomplete, and errored requests.
    """

    # Request stats
    request_totals: StatusBreakdown[int, int, int, int] = Field(
        description="Request counts by status: successful, incomplete, errored, total"
    )
    requests_per_second: StatusDistributionSummary = Field(
        description="Distribution of requests per second across benchmark execution"
    )
    request_concurrency: StatusDistributionSummary = Field(
        description="Distribution of concurrent request counts during execution"
    )
    request_latency: StatusDistributionSummary = Field(
        description="Distribution of request latencies for completed requests"
    )
    request_dispatch_delay: StatusDistributionSummary | None = Field(
        default=None,
        description=(
            "Distribution of delays between a request's targeted start and the "
            "time it was dispatched. Grows when the scheduler cannot keep up "
            "with the configured rate. None when the strategy does not define "
            "an arrival schedule"
        ),
    )
    request_scheduled_latency: StatusDistributionSummary | None = Field(
        default=None,
        description=(
            "Distribution of request latencies measured from each request's "
            "targeted start rather than its dispatch, including scheduler "
            "delay. None when the strategy does not define an arrival "
            "schedule"
        ),
    )
    turn_predecessor_delay: StatusDistributionSummary | None = Field(
        default=None,
        description=(
            "Portion of dispatch delay spent waiting on a predecessor that "
            "was still running past the targeted start. Zero for requests "
            "with no predecessor. None when the strategy does not define an "
            "arrival schedule"
        ),
    )
    turn_scheduling_delay: StatusDistributionSummary | None = Field(
        default=None,
        description=(
            "Portion of dispatch delay after the predecessor had finished, "
            "including deserializer, queue, and worker lag. None when the "
            "strategy does not define an arrival schedule"
        ),
    )
    root_dispatch_delay: DistributionSummary | None = Field(
        default=None,
        description=(
            "Dispatch delay for the first request of each conversation. "
            "Later turns are omitted. None when no first turn has a dispatch delay"
        ),
    )
    request_streaming_iterations_count: StatusDistributionSummary = Field(
        description="Distribution of stream iterations for completed requests"
    )

    # General token stats
    prompt_token_count: StatusDistributionSummary = Field(
        description="Distribution of prompt token counts by request status"
    )
    output_token_count: StatusDistributionSummary = Field(
        description="Distribution of output token counts by request status"
    )
    total_token_count: StatusDistributionSummary = Field(
        description="Distribution of total token counts by request status"
    )
    time_to_first_token_ms: StatusDistributionSummary = Field(
        description="Distribution of first token latencies in milliseconds"
    )
    time_to_first_output_token_ms: StatusDistributionSummary = Field(
        description=(
            "Distribution of first content (non-reasoning) token latencies "
            "in milliseconds"
        )
    )
    time_per_output_token_ms: StatusDistributionSummary = Field(
        description="Distribution of average time per output token in milliseconds"
    )
    inter_token_latency_ms: StatusDistributionSummary = Field(
        description="Distribution of inter-token latencies in milliseconds"
    )
    time_to_last_round_trip_ms: StatusDistributionSummary = Field(
        description=(
            "Distribution of websocket last-round-trip latencies in milliseconds "
            "(last received token minus last sent packet)"
        )
    )
    avg_round_trip_time_ms: StatusDistributionSummary = Field(
        description=(
            "Distribution of approximate websocket average round-trip times in "
            "milliseconds (mean received minus mean sent)"
        )
    )
    prompt_tokens_per_second: StatusDistributionSummary = Field(
        description="Distribution of prompt token processing rates"
    )
    output_tokens_per_second: StatusDistributionSummary = Field(
        description="Distribution of output token generation rates"
    )
    tokens_per_second: StatusDistributionSummary = Field(
        description="Distribution of total token throughput including prompt and output"
    )
    output_tokens_per_iteration: StatusDistributionSummary = Field(
        description="Distribution of output tokens generated per streaming iteration"
    )
    iter_tokens_per_iteration: StatusDistributionSummary = Field(
        description=(
            "Distribution of output tokens (without first) generated per "
            "streaming iteration"
        )
    )

    # Domain specific stats
    text: GenerativeTextMetricsSummary = Field(
        description="Text-specific metrics for tokens, words, and characters"
    )
    image: GenerativeImageMetricsSummary = Field(
        description="Image-specific metrics for tokens, images, pixels, and bytes"
    )
    video: GenerativeVideoMetricsSummary = Field(
        description="Video-specific metrics for tokens, frames, duration, and bytes"
    )
    audio: GenerativeAudioMetricsSummary = Field(
        description="Audio-specific metrics for tokens, samples, duration, and bytes"
    )
    tool_call: GenerativeToolCallMetricsSummary = Field(
        description="Tool call metrics for tokens and call counts"
    )

    # Multi-turn stats, populated only when requests span more than one turn
    turns: list[GenerativeTurnMetrics] | None = Field(
        default=None,
        description=(
            "Per-turn-position distributions for multi-turn workloads, one "
            "entry per observed turn index in ascending order. None when every "
            "request is at turn 0"
        ),
    )

    # Goodput stats, populated only when latency objectives are configured
    slo_attainment: float | None = Field(
        default=None,
        description=(
            "Fraction of requests meeting every configured latency objective, "
            "between 0.0 and 1.0. Errored requests count as non-conforming. "
            "Requests cancelled at the measurement boundary, and requests whose "
            "objectives cannot be evaluated, are excluded from both numerator "
            "and denominator. None when no objectives are configured"
        ),
    )
    slo_determined_requests: int = Field(
        default=0,
        description=(
            "Number of requests slo_attainment is averaged over: successful "
            "requests whose objectives could all be evaluated, plus errored "
            "requests, which count as non-conforming"
        ),
    )
    request_goodput: StatusDistributionSummary | None = Field(
        default=None,
        description=(
            "Distribution of objective-conforming requests per second, computed "
            "over the same measurement window as requests_per_second so the two "
            "are directly comparable. None when no objectives are configured"
        ),
    )

    @classmethod
    def _compile_goodput(
        cls,
        slo: GoodputSLO | None,
        successful: list[GenerativeRequestStats],
        errored: list[GenerativeRequestStats],
        start_time: float,
        end_time: float,
    ) -> tuple[float | None, int, StatusDistributionSummary | None]:
        """
        Compile goodput attainment and rate against configured latency objectives.

        Errored requests count against attainment. A request that failed did not
        deliver a response within its latency objective, and on a saturated
        server overload usually surfaces as timeouts and errors rather than as
        slow successes; scoring only successful requests would report a level
        that is mostly failing as fully conforming. Incomplete requests are
        excluded instead, because they were cancelled at the measurement
        boundary by the run's own duration limit rather than by the server.

        Attainment is a ratio over the measured request population and so is not
        affected by how much of the measurement window the server spent filling
        its pipeline. The rate shares the window convention of
        ``requests_per_second`` and therefore shares its sensitivity to that
        ramp, which is why callers comparing load levels should prefer
        attainment.

        :param slo: Configured latency objectives, or None to skip goodput
        :param successful: Successful request statistics within the window
        :param errored: Errored request statistics within the window
        :param start_time: Measurement window start timestamp
        :param end_time: Measurement window end timestamp
        :return: Tuple of (attainment ratio, determined request count,
            conforming request rate). The ratio and rate are None when no
            objectives are configured or none could be evaluated
        """
        if slo is None:
            return None, 0, None

        conforming: list[GenerativeRequestStats] = []
        determined = 0
        for stats in successful:
            verdict = slo.is_conforming(
                ttft_ms=stats.time_to_first_token_ms,
                tpot_ms=stats.inter_token_latency_ms,
                e2el_ms=(
                    stats.request_latency * 1000.0
                    if stats.request_latency is not None
                    else None
                ),
            )
            if verdict is None:
                continue
            determined += 1
            if verdict:
                conforming.append(stats)

        # An errored request produced no conforming response, so it lowers
        # attainment without needing its latencies to be measurable.
        determined += len(errored)

        if determined == 0:
            # Every request lacked a measurement for at least one objective, so
            # there is no population to average over. Reporting 0.0 here would
            # read as "nothing met the objectives" rather than "the objectives
            # do not apply to this workload".
            return None, 0, None

        goodput = StatusDistributionSummary.rate_distribution_from_timings_function(
            function=lambda req: req.request_end_time,
            successful=conforming,
            incomplete=[],
            errored=[],
            start_time=start_time,
            end_time=end_time,
        )

        return len(conforming) / determined, determined, goodput

    @classmethod
    def compile(cls, accumulator: GenerativeBenchmarkAccumulator) -> GenerativeMetrics:
        """
        Compile comprehensive generative metrics from benchmark accumulator.

        :param accumulator: Benchmark accumulator with completed request statistics
        :return: Compiled generative metrics with all distributions and summaries
        :raises ValueError: If measure_start and measure_end/request_end are not set
        """
        start_time = accumulator.timings.finalized_measure_start
        end_time = accumulator.timings.finalized_measure_end

        if start_time == -1.0 or end_time == -1.0:
            raise ValueError(
                "Cannot compile GenerativeMetrics: "
                "No measurement start or end times available."
            )

        successful = accumulator.completed.get_within_range(start_time, end_time)
        incomplete = accumulator.incomplete.get_within_range(start_time, end_time)
        errored = accumulator.errored.get_within_range(start_time, end_time)

        # Intervals are reported only for metrics recorded once per request, so
        # that each value is one observation. Token-weighted metrics and derived
        # rate distributions are left without them; see docs/en/guides/metrics.md.
        uncertainty = (
            None
            if accumulator.config.confidence is None
            else SampleUncertainty(confidence=accumulator.config.confidence)
        )

        # Schedule-relative metrics describe lag against an arrival schedule.
        # Closed-loop strategies derive each target from the system's own
        # responses, so a delay measured against them is circular rather than a
        # coordinated-omission correction, and is reported as None instead.
        dispatch_delay: StatusDistributionSummary | None = None
        scheduled_latency: StatusDistributionSummary | None = None
        predecessor_delay: StatusDistributionSummary | None = None
        scheduling_delay: StatusDistributionSummary | None = None
        if accumulator.config.strategy.defines_arrival_schedule:
            dispatch_delay = StatusDistributionSummary.from_values_function(
                function=lambda req: req.request_dispatch_delay,
                successful=successful,
                incomplete=incomplete,
                errored=errored,
                uncertainty=uncertainty,
            )
            scheduled_latency = StatusDistributionSummary.from_values_function(
                function=lambda req: req.request_scheduled_latency,
                successful=successful,
                incomplete=incomplete,
                errored=errored,
                uncertainty=uncertainty,
            )
            predecessor_delay = StatusDistributionSummary.from_values_function(
                function=lambda req: req.turn_predecessor_delay,
                successful=successful,
                incomplete=incomplete,
                errored=errored,
            )
            scheduling_delay = StatusDistributionSummary.from_values_function(
                function=lambda req: req.turn_scheduling_delay,
                successful=successful,
                incomplete=incomplete,
                errored=errored,
            )

        # First-token and inter-token latencies are windowed by when the first
        # token arrived rather than by request end. Filter once so the
        # aggregate and per-turn distributions cover the same requests.
        first_token_requests = (
            accumulator.completed.get_within_range(
                start_time, end_time, end_func=lambda req: req.first_token_iteration
            ),
            accumulator.incomplete.get_within_range(
                start_time, end_time, end_func=lambda req: req.first_token_iteration
            ),
            accumulator.errored.get_within_range(
                start_time, end_time, end_func=lambda req: req.first_token_iteration
            ),
        )
        after_first_token_requests = (
            accumulator.completed.get_within_range(
                start_time, end_time, start_func=lambda req: req.first_token_iteration
            ),
            accumulator.incomplete.get_within_range(
                start_time, end_time, start_func=lambda req: req.first_token_iteration
            ),
            accumulator.errored.get_within_range(
                start_time, end_time, start_func=lambda req: req.first_token_iteration
            ),
        )

        slo_attainment, slo_determined, request_goodput = cls._compile_goodput(
            slo=accumulator.config.slo,
            successful=successful,
            errored=errored,
            start_time=start_time,
            end_time=end_time,
        )

        return GenerativeMetrics(
            slo_attainment=slo_attainment,
            slo_determined_requests=slo_determined,
            request_goodput=request_goodput,
            # Request stats
            request_totals=StatusBreakdown(
                successful=len(successful),
                incomplete=len(incomplete),
                errored=len(errored),
                total=(len(successful) + len(incomplete) + len(errored)),
            ),
            requests_per_second=StatusDistributionSummary.rate_distribution_from_timings_function(
                function=lambda req: req.request_end_time,
                successful=successful,
                incomplete=incomplete,
                errored=errored,
                start_time=start_time,
                end_time=end_time,
            ),
            request_concurrency=StatusDistributionSummary.concurrency_distribution_from_timings_function(
                function=(
                    lambda req: (
                        (req.request_start_time, req.request_end_time)
                        if req.request_start_time is not None
                        and req.request_end_time is not None
                        else None
                    )
                ),
                successful=successful,
                incomplete=incomplete,
                errored=errored,
                start_time=start_time,
                end_time=end_time,
            ),
            request_latency=StatusDistributionSummary.from_values_function(
                function=lambda req: req.request_latency or 0.0,
                successful=successful,
                incomplete=incomplete,
                errored=errored,
                uncertainty=uncertainty,
            ),
            request_dispatch_delay=dispatch_delay,
            request_scheduled_latency=scheduled_latency,
            turn_predecessor_delay=predecessor_delay,
            turn_scheduling_delay=scheduling_delay,
            root_dispatch_delay=root_dispatch_delay_distribution(
                [*successful, *incomplete, *errored],
            ),
            request_streaming_iterations_count=StatusDistributionSummary.from_values_function(
                function=lambda req: req.info.timings.request_iterations or 0.0,
                successful=successful,
                incomplete=incomplete,
                errored=errored,
                uncertainty=uncertainty,
            ),
            # General token stats
            prompt_token_count=StatusDistributionSummary.from_values_function(
                function=lambda req: req.prompt_tokens or 0.0,
                successful=successful,
                incomplete=incomplete,
                errored=errored,
                uncertainty=uncertainty,
            ),
            output_token_count=StatusDistributionSummary.from_values_function(
                function=lambda req: req.output_tokens or 0.0,
                successful=successful,
                incomplete=incomplete,
                errored=errored,
                uncertainty=uncertainty,
            ),
            total_token_count=StatusDistributionSummary.from_values_function(
                function=lambda req: req.total_tokens or 0.0,
                successful=successful,
                incomplete=incomplete,
                errored=errored,
                uncertainty=uncertainty,
            ),
            # TODO: Need to evaluate closed=False vs closed=True for first-token
            # latencies. See github.com/vllm-project/guidellm/issues/1078
            time_to_first_token_ms=StatusDistributionSummary.from_values_function(
                function=lambda req: req.time_to_first_token_ms or 0.0,
                successful=first_token_requests[0],
                incomplete=first_token_requests[1],
                errored=first_token_requests[2],
                uncertainty=uncertainty,
            ),
            time_to_last_round_trip_ms=StatusDistributionSummary.from_values_function(
                function=lambda req: req.time_to_last_round_trip_ms or 0.0,
                successful=successful,
                incomplete=incomplete,
                errored=errored,
                uncertainty=uncertainty,
            ),
            avg_round_trip_time_ms=StatusDistributionSummary.from_values_function(
                function=lambda req: req.avg_round_trip_time_ms or 0.0,
                successful=successful,
                incomplete=incomplete,
                errored=errored,
                uncertainty=uncertainty,
            ),
            time_to_first_output_token_ms=StatusDistributionSummary.from_values_function(
                function=lambda req: req.time_to_first_output_token_ms or 0.0,
                successful=accumulator.completed.get_within_range(
                    start_time,
                    end_time,
                    end_func=lambda req: req.first_output_token_iteration,
                ),
                incomplete=accumulator.incomplete.get_within_range(
                    start_time,
                    end_time,
                    end_func=lambda req: req.first_output_token_iteration,
                ),
                errored=accumulator.errored.get_within_range(
                    start_time,
                    end_time,
                    end_func=lambda req: req.first_output_token_iteration,
                ),
                uncertainty=uncertainty,
            ),
            time_per_output_token_ms=StatusDistributionSummary.from_values_function(
                function=lambda req: (
                    req.time_per_output_token_ms or 0.0,
                    req.output_tokens or 0.0,
                ),
                successful=successful,
                incomplete=incomplete,
                errored=errored,
            ),
            inter_token_latency_ms=StatusDistributionSummary.from_values_function(
                function=lambda req: (
                    req.inter_token_latency_ms or 0.0,
                    (req.output_tokens or 1.0) - 1.0,
                ),
                successful=after_first_token_requests[0],
                incomplete=after_first_token_requests[1],
                errored=after_first_token_requests[2],
            ),
            prompt_tokens_per_second=StatusDistributionSummary.rate_distribution_from_timings_function(
                function=lambda req: req.prompt_tokens_timing,
                successful=successful,
                incomplete=incomplete,
                errored=errored,
                start_time=start_time,
                end_time=end_time,
            ),
            output_tokens_per_second=StatusDistributionSummary.rate_distribution_from_timings_function(
                function=lambda req: req.output_tokens_timings,
                successful=successful,
                incomplete=incomplete,
                errored=errored,
                start_time=start_time,
                end_time=end_time,
            ),
            tokens_per_second=StatusDistributionSummary.rate_distribution_from_timings_function(
                function=lambda req: req.total_tokens_timings,
                successful=successful,
                incomplete=incomplete,
                errored=errored,
                start_time=start_time,
                end_time=end_time,
            ),
            output_tokens_per_iteration=StatusDistributionSummary.from_values_function(
                function=lambda req: [
                    tokens for (_timing, tokens) in req.output_tokens_timings
                ],
                successful=successful,
                incomplete=incomplete,
                errored=errored,
            ),
            iter_tokens_per_iteration=StatusDistributionSummary.from_values_function(
                function=lambda req: [
                    tokens for (_timing, tokens) in req.iter_tokens_timings
                ],
                successful=successful,
                incomplete=incomplete,
                errored=errored,
            ),
            # Domain-specific stats
            text=GenerativeTextMetricsSummary.compile(
                successful=successful, incomplete=incomplete, errored=errored
            ),
            image=GenerativeImageMetricsSummary.compile(
                successful=successful, incomplete=incomplete, errored=errored
            ),
            video=GenerativeVideoMetricsSummary.compile(
                successful=successful, incomplete=incomplete, errored=errored
            ),
            audio=GenerativeAudioMetricsSummary.compile(
                successful=successful, incomplete=incomplete, errored=errored
            ),
            tool_call=GenerativeToolCallMetricsSummary.compile(
                successful=successful, incomplete=incomplete, errored=errored
            ),
            # Multi-turn stats
            turns=GenerativeTurnMetrics.compile_by_turn(
                requests=(successful, incomplete, errored),
                first_token_requests=first_token_requests,
                after_first_token_requests=after_first_token_requests,
            ),
        )

compile(accumulator) classmethod

Compile comprehensive generative metrics from benchmark accumulator.

Parameters:

Name Type Description Default
accumulator GenerativeBenchmarkAccumulator

Benchmark accumulator with completed request statistics

required

Returns:

Type Description
GenerativeMetrics

Compiled generative metrics with all distributions and summaries

Raises:

Type Description
ValueError

If measure_start and measure_end/request_end are not set

Source code in src/guidellm/benchmark/schemas/metrics.py
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
@classmethod
def compile(cls, accumulator: GenerativeBenchmarkAccumulator) -> GenerativeMetrics:
    """
    Compile comprehensive generative metrics from benchmark accumulator.

    :param accumulator: Benchmark accumulator with completed request statistics
    :return: Compiled generative metrics with all distributions and summaries
    :raises ValueError: If measure_start and measure_end/request_end are not set
    """
    start_time = accumulator.timings.finalized_measure_start
    end_time = accumulator.timings.finalized_measure_end

    if start_time == -1.0 or end_time == -1.0:
        raise ValueError(
            "Cannot compile GenerativeMetrics: "
            "No measurement start or end times available."
        )

    successful = accumulator.completed.get_within_range(start_time, end_time)
    incomplete = accumulator.incomplete.get_within_range(start_time, end_time)
    errored = accumulator.errored.get_within_range(start_time, end_time)

    # Intervals are reported only for metrics recorded once per request, so
    # that each value is one observation. Token-weighted metrics and derived
    # rate distributions are left without them; see docs/en/guides/metrics.md.
    uncertainty = (
        None
        if accumulator.config.confidence is None
        else SampleUncertainty(confidence=accumulator.config.confidence)
    )

    # Schedule-relative metrics describe lag against an arrival schedule.
    # Closed-loop strategies derive each target from the system's own
    # responses, so a delay measured against them is circular rather than a
    # coordinated-omission correction, and is reported as None instead.
    dispatch_delay: StatusDistributionSummary | None = None
    scheduled_latency: StatusDistributionSummary | None = None
    predecessor_delay: StatusDistributionSummary | None = None
    scheduling_delay: StatusDistributionSummary | None = None
    if accumulator.config.strategy.defines_arrival_schedule:
        dispatch_delay = StatusDistributionSummary.from_values_function(
            function=lambda req: req.request_dispatch_delay,
            successful=successful,
            incomplete=incomplete,
            errored=errored,
            uncertainty=uncertainty,
        )
        scheduled_latency = StatusDistributionSummary.from_values_function(
            function=lambda req: req.request_scheduled_latency,
            successful=successful,
            incomplete=incomplete,
            errored=errored,
            uncertainty=uncertainty,
        )
        predecessor_delay = StatusDistributionSummary.from_values_function(
            function=lambda req: req.turn_predecessor_delay,
            successful=successful,
            incomplete=incomplete,
            errored=errored,
        )
        scheduling_delay = StatusDistributionSummary.from_values_function(
            function=lambda req: req.turn_scheduling_delay,
            successful=successful,
            incomplete=incomplete,
            errored=errored,
        )

    # First-token and inter-token latencies are windowed by when the first
    # token arrived rather than by request end. Filter once so the
    # aggregate and per-turn distributions cover the same requests.
    first_token_requests = (
        accumulator.completed.get_within_range(
            start_time, end_time, end_func=lambda req: req.first_token_iteration
        ),
        accumulator.incomplete.get_within_range(
            start_time, end_time, end_func=lambda req: req.first_token_iteration
        ),
        accumulator.errored.get_within_range(
            start_time, end_time, end_func=lambda req: req.first_token_iteration
        ),
    )
    after_first_token_requests = (
        accumulator.completed.get_within_range(
            start_time, end_time, start_func=lambda req: req.first_token_iteration
        ),
        accumulator.incomplete.get_within_range(
            start_time, end_time, start_func=lambda req: req.first_token_iteration
        ),
        accumulator.errored.get_within_range(
            start_time, end_time, start_func=lambda req: req.first_token_iteration
        ),
    )

    slo_attainment, slo_determined, request_goodput = cls._compile_goodput(
        slo=accumulator.config.slo,
        successful=successful,
        errored=errored,
        start_time=start_time,
        end_time=end_time,
    )

    return GenerativeMetrics(
        slo_attainment=slo_attainment,
        slo_determined_requests=slo_determined,
        request_goodput=request_goodput,
        # Request stats
        request_totals=StatusBreakdown(
            successful=len(successful),
            incomplete=len(incomplete),
            errored=len(errored),
            total=(len(successful) + len(incomplete) + len(errored)),
        ),
        requests_per_second=StatusDistributionSummary.rate_distribution_from_timings_function(
            function=lambda req: req.request_end_time,
            successful=successful,
            incomplete=incomplete,
            errored=errored,
            start_time=start_time,
            end_time=end_time,
        ),
        request_concurrency=StatusDistributionSummary.concurrency_distribution_from_timings_function(
            function=(
                lambda req: (
                    (req.request_start_time, req.request_end_time)
                    if req.request_start_time is not None
                    and req.request_end_time is not None
                    else None
                )
            ),
            successful=successful,
            incomplete=incomplete,
            errored=errored,
            start_time=start_time,
            end_time=end_time,
        ),
        request_latency=StatusDistributionSummary.from_values_function(
            function=lambda req: req.request_latency or 0.0,
            successful=successful,
            incomplete=incomplete,
            errored=errored,
            uncertainty=uncertainty,
        ),
        request_dispatch_delay=dispatch_delay,
        request_scheduled_latency=scheduled_latency,
        turn_predecessor_delay=predecessor_delay,
        turn_scheduling_delay=scheduling_delay,
        root_dispatch_delay=root_dispatch_delay_distribution(
            [*successful, *incomplete, *errored],
        ),
        request_streaming_iterations_count=StatusDistributionSummary.from_values_function(
            function=lambda req: req.info.timings.request_iterations or 0.0,
            successful=successful,
            incomplete=incomplete,
            errored=errored,
            uncertainty=uncertainty,
        ),
        # General token stats
        prompt_token_count=StatusDistributionSummary.from_values_function(
            function=lambda req: req.prompt_tokens or 0.0,
            successful=successful,
            incomplete=incomplete,
            errored=errored,
            uncertainty=uncertainty,
        ),
        output_token_count=StatusDistributionSummary.from_values_function(
            function=lambda req: req.output_tokens or 0.0,
            successful=successful,
            incomplete=incomplete,
            errored=errored,
            uncertainty=uncertainty,
        ),
        total_token_count=StatusDistributionSummary.from_values_function(
            function=lambda req: req.total_tokens or 0.0,
            successful=successful,
            incomplete=incomplete,
            errored=errored,
            uncertainty=uncertainty,
        ),
        # TODO: Need to evaluate closed=False vs closed=True for first-token
        # latencies. See github.com/vllm-project/guidellm/issues/1078
        time_to_first_token_ms=StatusDistributionSummary.from_values_function(
            function=lambda req: req.time_to_first_token_ms or 0.0,
            successful=first_token_requests[0],
            incomplete=first_token_requests[1],
            errored=first_token_requests[2],
            uncertainty=uncertainty,
        ),
        time_to_last_round_trip_ms=StatusDistributionSummary.from_values_function(
            function=lambda req: req.time_to_last_round_trip_ms or 0.0,
            successful=successful,
            incomplete=incomplete,
            errored=errored,
            uncertainty=uncertainty,
        ),
        avg_round_trip_time_ms=StatusDistributionSummary.from_values_function(
            function=lambda req: req.avg_round_trip_time_ms or 0.0,
            successful=successful,
            incomplete=incomplete,
            errored=errored,
            uncertainty=uncertainty,
        ),
        time_to_first_output_token_ms=StatusDistributionSummary.from_values_function(
            function=lambda req: req.time_to_first_output_token_ms or 0.0,
            successful=accumulator.completed.get_within_range(
                start_time,
                end_time,
                end_func=lambda req: req.first_output_token_iteration,
            ),
            incomplete=accumulator.incomplete.get_within_range(
                start_time,
                end_time,
                end_func=lambda req: req.first_output_token_iteration,
            ),
            errored=accumulator.errored.get_within_range(
                start_time,
                end_time,
                end_func=lambda req: req.first_output_token_iteration,
            ),
            uncertainty=uncertainty,
        ),
        time_per_output_token_ms=StatusDistributionSummary.from_values_function(
            function=lambda req: (
                req.time_per_output_token_ms or 0.0,
                req.output_tokens or 0.0,
            ),
            successful=successful,
            incomplete=incomplete,
            errored=errored,
        ),
        inter_token_latency_ms=StatusDistributionSummary.from_values_function(
            function=lambda req: (
                req.inter_token_latency_ms or 0.0,
                (req.output_tokens or 1.0) - 1.0,
            ),
            successful=after_first_token_requests[0],
            incomplete=after_first_token_requests[1],
            errored=after_first_token_requests[2],
        ),
        prompt_tokens_per_second=StatusDistributionSummary.rate_distribution_from_timings_function(
            function=lambda req: req.prompt_tokens_timing,
            successful=successful,
            incomplete=incomplete,
            errored=errored,
            start_time=start_time,
            end_time=end_time,
        ),
        output_tokens_per_second=StatusDistributionSummary.rate_distribution_from_timings_function(
            function=lambda req: req.output_tokens_timings,
            successful=successful,
            incomplete=incomplete,
            errored=errored,
            start_time=start_time,
            end_time=end_time,
        ),
        tokens_per_second=StatusDistributionSummary.rate_distribution_from_timings_function(
            function=lambda req: req.total_tokens_timings,
            successful=successful,
            incomplete=incomplete,
            errored=errored,
            start_time=start_time,
            end_time=end_time,
        ),
        output_tokens_per_iteration=StatusDistributionSummary.from_values_function(
            function=lambda req: [
                tokens for (_timing, tokens) in req.output_tokens_timings
            ],
            successful=successful,
            incomplete=incomplete,
            errored=errored,
        ),
        iter_tokens_per_iteration=StatusDistributionSummary.from_values_function(
            function=lambda req: [
                tokens for (_timing, tokens) in req.iter_tokens_timings
            ],
            successful=successful,
            incomplete=incomplete,
            errored=errored,
        ),
        # Domain-specific stats
        text=GenerativeTextMetricsSummary.compile(
            successful=successful, incomplete=incomplete, errored=errored
        ),
        image=GenerativeImageMetricsSummary.compile(
            successful=successful, incomplete=incomplete, errored=errored
        ),
        video=GenerativeVideoMetricsSummary.compile(
            successful=successful, incomplete=incomplete, errored=errored
        ),
        audio=GenerativeAudioMetricsSummary.compile(
            successful=successful, incomplete=incomplete, errored=errored
        ),
        tool_call=GenerativeToolCallMetricsSummary.compile(
            successful=successful, incomplete=incomplete, errored=errored
        ),
        # Multi-turn stats
        turns=GenerativeTurnMetrics.compile_by_turn(
            requests=(successful, incomplete, errored),
            first_token_requests=first_token_requests,
            after_first_token_requests=after_first_token_requests,
        ),
    )

GenerativeMetricsAccumulator

Bases: StandardBaseModel

Accumulates generative model performance metrics during execution.

Tracks token throughput, latency characteristics, and request timing for generative workloads. Maintains running statistics for input/output tokens, time-to-first-token, inter-token latency, and streaming patterns for comprehensive performance analysis.

Source code in src/guidellm/benchmark/schemas/accumulator.py
class GenerativeMetricsAccumulator(StandardBaseModel):
    """
    Accumulates generative model performance metrics during execution.

    Tracks token throughput, latency characteristics, and request timing for generative
    workloads. Maintains running statistics for input/output tokens,
    time-to-first-token, inter-token latency, and streaming patterns for comprehensive
    performance analysis.
    """

    requests: RunningMetricStats = Field(
        default_factory=RunningMetricStats,
        description="Accumulated request count statistics",
    )
    request_latency: RunningMetricStats = Field(
        default_factory=RunningMetricStats,
        description="Accumulated request latency statistics",
    )
    prompt_tokens: RunningMetricStats = Field(
        default_factory=RunningMetricStats,
        description="Accumulated input token count statistics",
    )
    output_tokens: RunningMetricStats = Field(
        default_factory=RunningMetricStats,
        description="Accumulated output token count statistics",
    )
    total_tokens: RunningMetricStats = Field(
        default_factory=RunningMetricStats,
        description="Accumulated total token count statistics",
    )
    time_to_first_token_ms: RunningMetricStats = Field(
        default_factory=RunningMetricStats,
        description="Accumulated time to first token statistics in milliseconds",
    )
    time_to_last_round_trip_ms: RunningMetricStats = Field(
        default_factory=RunningMetricStats,
        description="Accumulated websocket last round-trip latency in milliseconds",
    )
    avg_round_trip_time_ms: RunningMetricStats = Field(
        default_factory=RunningMetricStats,
        description="Accumulated websocket average round-trip time in milliseconds",
    )
    time_to_first_output_token_ms: RunningMetricStats = Field(
        default_factory=RunningMetricStats,
        description="Accumulated time to first content token stats in ms",
    )
    time_per_output_token_ms: RunningMetricStats = Field(
        default_factory=RunningMetricStats,
        description="Accumulated time per output token statistics in milliseconds",
    )
    inter_token_latency_ms: RunningMetricStats = Field(
        default_factory=RunningMetricStats,
        description="Accumulated inter-token latency statistics in milliseconds",
    )
    streaming_iterations: RunningMetricStats = Field(
        default_factory=RunningMetricStats,
        description="Accumulated streaming iteration count statistics",
    )
    output_tokens_by_iteration: RunningMetricStats = Field(
        default_factory=RunningMetricStats,
        description="Accumulated output tokens per iteration statistics",
    )
    iter_tokens_by_iteration: RunningMetricStats = Field(
        default_factory=RunningMetricStats,
        description="Accumulated iteration tokens per iteration statistics",
    )

    def update_estimate(self, stats: GenerativeRequestStats, duration: float):
        """
        Update generative metrics with completed request statistics.

        Incorporates token counts, latency measurements, and streaming characteristics
        from a completed request into running metric accumulators with time-weighted
        calculations.

        :param stats: Request statistics containing token and latency measurements
        :param duration: Current benchmark duration for time-weighted metrics
        """
        self.requests.update_estimate(1.0, duration=duration)
        self.prompt_tokens.update_estimate(stats.prompt_tokens, duration=duration)
        self.output_tokens.update_estimate(stats.output_tokens, duration=duration)
        self.total_tokens.update_estimate(stats.total_tokens, duration=duration)
        self.request_latency.update_estimate(stats.request_latency, duration=duration)
        self.time_to_first_token_ms.update_estimate(
            stats.time_to_first_token_ms, duration=duration
        )
        self.time_to_last_round_trip_ms.update_estimate(
            stats.time_to_last_round_trip_ms, duration=duration
        )
        self.avg_round_trip_time_ms.update_estimate(
            stats.avg_round_trip_time_ms, duration=duration
        )
        self.time_to_first_output_token_ms.update_estimate(
            stats.time_to_first_output_token_ms, duration=duration
        )
        self.time_per_output_token_ms.update_estimate(
            stats.time_per_output_token_ms,
            count=int(stats.output_tokens or 0),
            duration=duration,
        )
        self.inter_token_latency_ms.update_estimate(
            stats.inter_token_latency_ms,
            count=int((stats.output_tokens or 1) - 1),
            duration=duration,
        )
        self.streaming_iterations.update_estimate(
            stats.token_iterations, duration=duration
        )
        self.output_tokens_by_iteration.update_estimate(
            stats.output_tokens_per_iteration,
            count=int(stats.token_iterations or 0),
            duration=duration,
        )
        self.iter_tokens_by_iteration.update_estimate(
            stats.iter_tokens_per_iteration,
            count=int((stats.token_iterations or 1) - 1),
            duration=duration,
        )

update_estimate(stats, duration)

Update generative metrics with completed request statistics.

Incorporates token counts, latency measurements, and streaming characteristics from a completed request into running metric accumulators with time-weighted calculations.

Parameters:

Name Type Description Default
stats GenerativeRequestStats

Request statistics containing token and latency measurements

required
duration float

Current benchmark duration for time-weighted metrics

required
Source code in src/guidellm/benchmark/schemas/accumulator.py
def update_estimate(self, stats: GenerativeRequestStats, duration: float):
    """
    Update generative metrics with completed request statistics.

    Incorporates token counts, latency measurements, and streaming characteristics
    from a completed request into running metric accumulators with time-weighted
    calculations.

    :param stats: Request statistics containing token and latency measurements
    :param duration: Current benchmark duration for time-weighted metrics
    """
    self.requests.update_estimate(1.0, duration=duration)
    self.prompt_tokens.update_estimate(stats.prompt_tokens, duration=duration)
    self.output_tokens.update_estimate(stats.output_tokens, duration=duration)
    self.total_tokens.update_estimate(stats.total_tokens, duration=duration)
    self.request_latency.update_estimate(stats.request_latency, duration=duration)
    self.time_to_first_token_ms.update_estimate(
        stats.time_to_first_token_ms, duration=duration
    )
    self.time_to_last_round_trip_ms.update_estimate(
        stats.time_to_last_round_trip_ms, duration=duration
    )
    self.avg_round_trip_time_ms.update_estimate(
        stats.avg_round_trip_time_ms, duration=duration
    )
    self.time_to_first_output_token_ms.update_estimate(
        stats.time_to_first_output_token_ms, duration=duration
    )
    self.time_per_output_token_ms.update_estimate(
        stats.time_per_output_token_ms,
        count=int(stats.output_tokens or 0),
        duration=duration,
    )
    self.inter_token_latency_ms.update_estimate(
        stats.inter_token_latency_ms,
        count=int((stats.output_tokens or 1) - 1),
        duration=duration,
    )
    self.streaming_iterations.update_estimate(
        stats.token_iterations, duration=duration
    )
    self.output_tokens_by_iteration.update_estimate(
        stats.output_tokens_per_iteration,
        count=int(stats.token_iterations or 0),
        duration=duration,
    )
    self.iter_tokens_by_iteration.update_estimate(
        stats.iter_tokens_per_iteration,
        count=int((stats.token_iterations or 1) - 1),
        duration=duration,
    )

GenerativeMetricsSummary

Bases: StandardBaseDict

Statistical summaries for input, output, and total metrics.

Provides distribution summaries across successful, incomplete, and errored requests for absolute values, per-second rates, and concurrency levels.

Source code in src/guidellm/benchmark/schemas/metrics.py
class GenerativeMetricsSummary(StandardBaseDict):
    """
    Statistical summaries for input, output, and total metrics.

    Provides distribution summaries across successful, incomplete, and errored
    requests for absolute values, per-second rates, and concurrency levels.
    """

    input: StatusDistributionSummary | None = Field(
        description="Distribution of input metric values"
    )
    input_per_second: StatusDistributionSummary | None = Field(
        description="Distribution of input metric rates per second"
    )
    input_concurrency: StatusDistributionSummary | None = Field(
        description="Distribution of concurrent input metric values"
    )

    output: StatusDistributionSummary | None = Field(
        description="Distribution of output metric values"
    )
    output_per_second: StatusDistributionSummary | None = Field(
        description="Distribution of output metric rates per second"
    )
    output_concurrency: StatusDistributionSummary | None = Field(
        description="Distribution of concurrent output metric values"
    )

    total: StatusDistributionSummary | None = Field(
        description="Distribution of total metric values (input + output)"
    )
    total_per_second: StatusDistributionSummary | None = Field(
        description="Distribution of total metric rates per second"
    )
    total_concurrency: StatusDistributionSummary | None = Field(
        description="Distribution of concurrent total metric values"
    )

    @classmethod
    def compile(
        cls,
        property_name: str,
        successful: list[GenerativeRequestStats],
        incomplete: list[GenerativeRequestStats],
        errored: list[GenerativeRequestStats],
    ) -> GenerativeMetricsSummary | None:
        """
        Compile metrics summary from request statistics for a specific property.

        :param property_name: Name of the property to extract from request metrics
        :param successful: Successfully completed request statistics
        :param incomplete: Incomplete or cancelled request statistics
        :param errored: Failed request statistics
        :return: Compiled metrics summary or None if no data available
        """
        successful_metrics = cls.extract_property_metrics_for_summary(
            successful, property_name
        )
        incomplete_metrics = cls.extract_property_metrics_for_summary(
            incomplete, property_name
        )
        errored_metrics = cls.extract_property_metrics_for_summary(
            errored, property_name
        )

        return cls.compile_timed_metrics(
            successful=successful_metrics,
            incomplete=incomplete_metrics,
            errored=errored_metrics,
        )

    @classmethod
    def compile_timed_metrics(
        cls,
        successful: list[TimedMetricTypeAlias],
        incomplete: list[TimedMetricTypeAlias],
        errored: list[TimedMetricTypeAlias],
    ) -> GenerativeMetricsSummary | None:
        """
        Compile metrics summary from timed metric tuples.

        :param successful: Timed metrics from successful requests
        :param incomplete: Timed metrics from incomplete requests
        :param errored: Timed metrics from errored requests
        :return: Compiled metrics summary or None if no data available
        """

        def _compile_metric_distributions(
            metrics_by_status: dict[StatusTypes, list[TimedMetricTypeAlias]],
            value_index: int,
        ) -> tuple[
            StatusDistributionSummary | None,
            StatusDistributionSummary | None,
            StatusDistributionSummary | None,
            dict[StatusTypes, list[float]],
            dict[StatusTypes, list[tuple[float, float]]],
            dict[StatusTypes, list[tuple[float, float, float]]],
        ]:
            """Helper to compile value, rate, and concurrency distributions."""
            # Filter out None values instead of coercing to 0.0 so we can
            # distinguish "metric not applicable" (None) from "metric is
            # zero" (e.g. errored tool call requests with tool_call_count=0).
            value_lists: dict[StatusTypes, list[float]] = {
                status: [
                    float(val)
                    for metric in metrics
                    if metric is not None
                    for val in [metric[value_index]]
                    if val is not None
                ]
                for status, metrics in metrics_by_status.items()
            }

            # No data at all for this value index — skip distributions.
            if all(len(vl) == 0 for vl in value_lists.values()):
                return None, None, None, value_lists, {}, {}

            value_dist = StatusDistributionSummary.from_values(
                successful=value_lists["successful"],
                incomplete=value_lists["incomplete"],
                errored=value_lists["errored"],
            )

            rate_lists: dict[StatusTypes, list[tuple[float, float]]] = {
                status: [
                    (  # type: ignore[misc]
                        metric[_TIMED_METRIC_END_TIME_INDEX],
                        float(metric[value_index] or 0.0),
                    )
                    for metric in metrics
                    if metric is not None
                ]
                for status, metrics in metrics_by_status.items()
            }
            rate_dist = StatusDistributionSummary.rate_distribution_from_timings(
                successful=rate_lists["successful"],
                incomplete=rate_lists["incomplete"],
                errored=rate_lists["errored"],
            )

            concurrency_lists: dict[StatusTypes, list[tuple[float, float, float]]] = {
                status: [
                    (  # type: ignore[misc]
                        metric[_TIMED_METRIC_START_TIME_INDEX],
                        metric[_TIMED_METRIC_END_TIME_INDEX],
                        float(metric[value_index] or 0.0),
                    )
                    for metric in metrics
                    if metric is not None
                ]
                for status, metrics in metrics_by_status.items()
            }
            concurrency_dist = (
                StatusDistributionSummary.concurrency_distribution_from_timings(
                    successful=concurrency_lists["successful"],
                    incomplete=concurrency_lists["incomplete"],
                    errored=concurrency_lists["errored"],
                )
            )

            return (
                value_dist,
                rate_dist,
                concurrency_dist,
                value_lists,
                rate_lists,
                concurrency_lists,
            )

        metrics_by_status: dict[StatusTypes, list[TimedMetricTypeAlias]] = {
            "successful": successful,
            "incomplete": incomplete,
            "errored": errored,
        }

        # Calculate input distributions
        (
            input_value_dist,
            input_rate_dist,
            input_concurrency_dist,
            input_value_lists,
            input_rate_lists,
            input_concurrency_lists,
        ) = _compile_metric_distributions(
            metrics_by_status, _TIMED_METRIC_INPUT_VALUE_INDEX
        )

        # Calculate output distributions
        (
            output_value_dist,
            output_rate_dist,
            output_concurrency_dist,
            output_value_lists,
            output_rate_lists,
            output_concurrency_lists,
        ) = _compile_metric_distributions(
            metrics_by_status, _TIMED_METRIC_OUTPUT_VALUE_INDEX
        )

        # Calculate total distributions if both input and output have data
        if input_value_dist is not None and output_value_dist is not None:
            total_value_dist = StatusDistributionSummary.from_values(
                successful=(
                    input_value_lists["successful"] + output_value_lists["successful"]
                ),
                incomplete=(
                    input_value_lists["incomplete"] + output_value_lists["incomplete"]
                ),
                errored=input_value_lists["errored"] + output_value_lists["errored"],
            )
            total_rate_dist = StatusDistributionSummary.rate_distribution_from_timings(
                successful=(
                    input_rate_lists["successful"] + output_rate_lists["successful"]
                ),
                incomplete=(
                    input_rate_lists["incomplete"] + output_rate_lists["incomplete"]
                ),
                errored=input_rate_lists["errored"] + output_rate_lists["errored"],
            )
            total_concurrency_dist = (
                StatusDistributionSummary.concurrency_distribution_from_timings(
                    successful=(
                        input_concurrency_lists["successful"]
                        + output_concurrency_lists["successful"]
                    ),
                    incomplete=(
                        input_concurrency_lists["incomplete"]
                        + output_concurrency_lists["incomplete"]
                    ),
                    errored=(
                        input_concurrency_lists["errored"]
                        + output_concurrency_lists["errored"]
                    ),
                )
            )
        else:
            total_value_dist = None
            total_rate_dist = None
            total_concurrency_dist = None

        return GenerativeMetricsSummary(
            input=input_value_dist,
            input_per_second=input_rate_dist,
            input_concurrency=input_concurrency_dist,
            output=output_value_dist,
            output_per_second=output_rate_dist,
            output_concurrency=output_concurrency_dist,
            total=total_value_dist,
            total_per_second=total_rate_dist,
            total_concurrency=total_concurrency_dist,
        )

    @classmethod
    def extract_property_metrics_for_summary(
        cls, stats_list: list[GenerativeRequestStats], property_name: str
    ) -> list[TimedMetricTypeAlias]:
        """
        Extract timed metrics for a specific property from request statistics.

        :param stats_list: List of request statistics to extract from
        :param property_name: Name of the property to extract from metrics
        :return: List of tuples containing
            (start_time, end_time, input_value, output_value)
        """
        return [
            (
                stats.request_start_time,
                stats.request_end_time,
                getattr(stats.input_metrics, property_name),
                getattr(stats.output_metrics, property_name),
            )
            for stats in stats_list
            if (
                stats.request_start_time
                and stats.request_end_time
                and (
                    getattr(stats.input_metrics, property_name) is not None
                    or getattr(stats.output_metrics, property_name) is not None
                )
            )
        ]

compile(property_name, successful, incomplete, errored) classmethod

Compile metrics summary from request statistics for a specific property.

Parameters:

Name Type Description Default
property_name str

Name of the property to extract from request metrics

required
successful list[GenerativeRequestStats]

Successfully completed request statistics

required
incomplete list[GenerativeRequestStats]

Incomplete or cancelled request statistics

required
errored list[GenerativeRequestStats]

Failed request statistics

required

Returns:

Type Description
GenerativeMetricsSummary | None

Compiled metrics summary or None if no data available

Source code in src/guidellm/benchmark/schemas/metrics.py
@classmethod
def compile(
    cls,
    property_name: str,
    successful: list[GenerativeRequestStats],
    incomplete: list[GenerativeRequestStats],
    errored: list[GenerativeRequestStats],
) -> GenerativeMetricsSummary | None:
    """
    Compile metrics summary from request statistics for a specific property.

    :param property_name: Name of the property to extract from request metrics
    :param successful: Successfully completed request statistics
    :param incomplete: Incomplete or cancelled request statistics
    :param errored: Failed request statistics
    :return: Compiled metrics summary or None if no data available
    """
    successful_metrics = cls.extract_property_metrics_for_summary(
        successful, property_name
    )
    incomplete_metrics = cls.extract_property_metrics_for_summary(
        incomplete, property_name
    )
    errored_metrics = cls.extract_property_metrics_for_summary(
        errored, property_name
    )

    return cls.compile_timed_metrics(
        successful=successful_metrics,
        incomplete=incomplete_metrics,
        errored=errored_metrics,
    )

compile_timed_metrics(successful, incomplete, errored) classmethod

Compile metrics summary from timed metric tuples.

Parameters:

Name Type Description Default
successful list[TimedMetricTypeAlias]

Timed metrics from successful requests

required
incomplete list[TimedMetricTypeAlias]

Timed metrics from incomplete requests

required
errored list[TimedMetricTypeAlias]

Timed metrics from errored requests

required

Returns:

Type Description
GenerativeMetricsSummary | None

Compiled metrics summary or None if no data available

Source code in src/guidellm/benchmark/schemas/metrics.py
@classmethod
def compile_timed_metrics(
    cls,
    successful: list[TimedMetricTypeAlias],
    incomplete: list[TimedMetricTypeAlias],
    errored: list[TimedMetricTypeAlias],
) -> GenerativeMetricsSummary | None:
    """
    Compile metrics summary from timed metric tuples.

    :param successful: Timed metrics from successful requests
    :param incomplete: Timed metrics from incomplete requests
    :param errored: Timed metrics from errored requests
    :return: Compiled metrics summary or None if no data available
    """

    def _compile_metric_distributions(
        metrics_by_status: dict[StatusTypes, list[TimedMetricTypeAlias]],
        value_index: int,
    ) -> tuple[
        StatusDistributionSummary | None,
        StatusDistributionSummary | None,
        StatusDistributionSummary | None,
        dict[StatusTypes, list[float]],
        dict[StatusTypes, list[tuple[float, float]]],
        dict[StatusTypes, list[tuple[float, float, float]]],
    ]:
        """Helper to compile value, rate, and concurrency distributions."""
        # Filter out None values instead of coercing to 0.0 so we can
        # distinguish "metric not applicable" (None) from "metric is
        # zero" (e.g. errored tool call requests with tool_call_count=0).
        value_lists: dict[StatusTypes, list[float]] = {
            status: [
                float(val)
                for metric in metrics
                if metric is not None
                for val in [metric[value_index]]
                if val is not None
            ]
            for status, metrics in metrics_by_status.items()
        }

        # No data at all for this value index — skip distributions.
        if all(len(vl) == 0 for vl in value_lists.values()):
            return None, None, None, value_lists, {}, {}

        value_dist = StatusDistributionSummary.from_values(
            successful=value_lists["successful"],
            incomplete=value_lists["incomplete"],
            errored=value_lists["errored"],
        )

        rate_lists: dict[StatusTypes, list[tuple[float, float]]] = {
            status: [
                (  # type: ignore[misc]
                    metric[_TIMED_METRIC_END_TIME_INDEX],
                    float(metric[value_index] or 0.0),
                )
                for metric in metrics
                if metric is not None
            ]
            for status, metrics in metrics_by_status.items()
        }
        rate_dist = StatusDistributionSummary.rate_distribution_from_timings(
            successful=rate_lists["successful"],
            incomplete=rate_lists["incomplete"],
            errored=rate_lists["errored"],
        )

        concurrency_lists: dict[StatusTypes, list[tuple[float, float, float]]] = {
            status: [
                (  # type: ignore[misc]
                    metric[_TIMED_METRIC_START_TIME_INDEX],
                    metric[_TIMED_METRIC_END_TIME_INDEX],
                    float(metric[value_index] or 0.0),
                )
                for metric in metrics
                if metric is not None
            ]
            for status, metrics in metrics_by_status.items()
        }
        concurrency_dist = (
            StatusDistributionSummary.concurrency_distribution_from_timings(
                successful=concurrency_lists["successful"],
                incomplete=concurrency_lists["incomplete"],
                errored=concurrency_lists["errored"],
            )
        )

        return (
            value_dist,
            rate_dist,
            concurrency_dist,
            value_lists,
            rate_lists,
            concurrency_lists,
        )

    metrics_by_status: dict[StatusTypes, list[TimedMetricTypeAlias]] = {
        "successful": successful,
        "incomplete": incomplete,
        "errored": errored,
    }

    # Calculate input distributions
    (
        input_value_dist,
        input_rate_dist,
        input_concurrency_dist,
        input_value_lists,
        input_rate_lists,
        input_concurrency_lists,
    ) = _compile_metric_distributions(
        metrics_by_status, _TIMED_METRIC_INPUT_VALUE_INDEX
    )

    # Calculate output distributions
    (
        output_value_dist,
        output_rate_dist,
        output_concurrency_dist,
        output_value_lists,
        output_rate_lists,
        output_concurrency_lists,
    ) = _compile_metric_distributions(
        metrics_by_status, _TIMED_METRIC_OUTPUT_VALUE_INDEX
    )

    # Calculate total distributions if both input and output have data
    if input_value_dist is not None and output_value_dist is not None:
        total_value_dist = StatusDistributionSummary.from_values(
            successful=(
                input_value_lists["successful"] + output_value_lists["successful"]
            ),
            incomplete=(
                input_value_lists["incomplete"] + output_value_lists["incomplete"]
            ),
            errored=input_value_lists["errored"] + output_value_lists["errored"],
        )
        total_rate_dist = StatusDistributionSummary.rate_distribution_from_timings(
            successful=(
                input_rate_lists["successful"] + output_rate_lists["successful"]
            ),
            incomplete=(
                input_rate_lists["incomplete"] + output_rate_lists["incomplete"]
            ),
            errored=input_rate_lists["errored"] + output_rate_lists["errored"],
        )
        total_concurrency_dist = (
            StatusDistributionSummary.concurrency_distribution_from_timings(
                successful=(
                    input_concurrency_lists["successful"]
                    + output_concurrency_lists["successful"]
                ),
                incomplete=(
                    input_concurrency_lists["incomplete"]
                    + output_concurrency_lists["incomplete"]
                ),
                errored=(
                    input_concurrency_lists["errored"]
                    + output_concurrency_lists["errored"]
                ),
            )
        )
    else:
        total_value_dist = None
        total_rate_dist = None
        total_concurrency_dist = None

    return GenerativeMetricsSummary(
        input=input_value_dist,
        input_per_second=input_rate_dist,
        input_concurrency=input_concurrency_dist,
        output=output_value_dist,
        output_per_second=output_rate_dist,
        output_concurrency=output_concurrency_dist,
        total=total_value_dist,
        total_per_second=total_rate_dist,
        total_concurrency=total_concurrency_dist,
    )

extract_property_metrics_for_summary(stats_list, property_name) classmethod

Extract timed metrics for a specific property from request statistics.

Parameters:

Name Type Description Default
stats_list list[GenerativeRequestStats]

List of request statistics to extract from

required
property_name str

Name of the property to extract from metrics

required

Returns:

Type Description
list[TimedMetricTypeAlias]

List of tuples containing (start_time, end_time, input_value, output_value)

Source code in src/guidellm/benchmark/schemas/metrics.py
@classmethod
def extract_property_metrics_for_summary(
    cls, stats_list: list[GenerativeRequestStats], property_name: str
) -> list[TimedMetricTypeAlias]:
    """
    Extract timed metrics for a specific property from request statistics.

    :param stats_list: List of request statistics to extract from
    :param property_name: Name of the property to extract from metrics
    :return: List of tuples containing
        (start_time, end_time, input_value, output_value)
    """
    return [
        (
            stats.request_start_time,
            stats.request_end_time,
            getattr(stats.input_metrics, property_name),
            getattr(stats.output_metrics, property_name),
        )
        for stats in stats_list
        if (
            stats.request_start_time
            and stats.request_end_time
            and (
                getattr(stats.input_metrics, property_name) is not None
                or getattr(stats.output_metrics, property_name) is not None
            )
        )
    ]

GenerativeRequestsAccumulator

Bases: StandardBaseModel

Manages request statistics collection with optional reservoir sampling.

Collects detailed request statistics while optionally sampling to limit memory usage in long-running benchmarks. Supports configurable sampling rates and selective data retention (clearing request arguments and/or outputs for non-sampled requests).

Source code in src/guidellm/benchmark/schemas/accumulator.py
class GenerativeRequestsAccumulator(StandardBaseModel):
    """
    Manages request statistics collection with optional reservoir sampling.

    Collects detailed request statistics while optionally sampling to limit memory usage
    in long-running benchmarks. Supports configurable sampling rates and selective data
    retention (clearing request arguments and/or outputs for non-sampled requests).
    """

    sample_size: int | None = Field(
        default=None,
        description=(
            "Maximum number of requests in each status group to retain full data "
            "(prompt, output, tool calls) for. Lightweight stats are always kept. "
            "None keeps all, 0 strips all, N > 0 uses reservoir sampling."
        ),
    )
    requests_stats: list[GenerativeRequestStats] = Field(
        description="List of generative request statistics", default_factory=list
    )
    samples: list[int] | None = Field(
        description="Indices of sampled generative requests", default=None
    )
    clear_nonsampled_request_args: bool = Field(
        default=True,
        description=(
            "Whether to clear request arguments and outputs for non-sampled requests"
        ),
    )
    clear_nonsampled_outputs: bool = Field(
        default=True,
        description=(
            "Whether to clear outputs for non-sampled requests while keeping args"
        ),
    )

    def get_sampled(self) -> list[GenerativeRequestStats]:
        """
        Retrieve the list of sampled request statistics.

        :return: List of sampled generative request statistics
        """
        if self.samples is None:
            return self.requests_stats

        return [self.requests_stats[ind] for ind in self.samples]

    def get_within_range(
        self,
        start_time: float,
        end_time: float,
        start_func: Callable[[GenerativeRequestStats], float | None] = lambda s: (
            s.request_start_time
        ),
        end_func: Callable[[GenerativeRequestStats], float | None] = lambda s: (
            s.request_end_time
        ),
        closed: bool = False,
    ) -> list[GenerativeRequestStats]:
        """
        Retrieve request statistics within a specified time range.

        If the request has no start time we treat it
        as an instantaneous event at the end time.

        :param start_time: Start timestamp for filtering (events must end after this)
        :param end_time: End timestamp for filtering (events must start before this)
        :param start_func: Function to extract the start timestamp from a request stat
        :param end_func: Function to extract the end timestamp from a request stats
        :param closed: If True, events must be strictly enclosed within the time range.
                       If False, include events that partially overlap the time range.
        :return: List of request statistics within the time range
        """
        within_range = []
        for stats in self.requests_stats:
            start_event = start_func(stats)
            end_event = end_func(stats)

            # Skip if we don't know when the event ended
            if end_event is None:
                continue

            # Treat instantaneous events as starting exactly when they end
            actual_start = start_event if start_event is not None else end_event

            if closed:
                # Fully enclosed
                is_in_range = actual_start >= start_time and end_event <= end_time
            else:
                # Partial overlap
                is_in_range = end_event >= start_time and actual_start <= end_time

            if is_in_range:
                within_range.append(stats)

        return within_range

    def update_estimate(
        self,
        response: GenerationResponse | None,
        request: GenerationRequest,
        info: RequestInfo,
        prefer_response_metrics: bool,
    ) -> GenerativeRequestStats:
        """
        Record request statistics and apply reservoir sampling if configured.

        Compiles statistics from the completed request and adds to the collection.
        Uses reservoir sampling algorithm to maintain uniform sample distribution when
        enabled, clearing non-sampled request data to manage memory.

        :param response: Generation response containing output and metrics
        :param request: Original generation request with input data
        :param info: Request execution information and timing
        :param prefer_response_metrics: Whether to prefer metrics from response
        :return: Compiled request statistics
        """
        stats = self.compile_stats(response, request, info, prefer_response_metrics)

        current_index = len(self.requests_stats)
        self.requests_stats.append(stats)

        if self.sample_size is None:
            # Keeping all requests, don't need to sample
            self.samples = None
        elif self.sample_size <= 0:
            # Not keeping any requests, clear out unnecessary memory usage for current
            self.clear_stats_data(stats)
        elif self.sample_size >= len(self.requests_stats):
            # Add directly to samples, haven't filled yet
            if self.samples is None:
                self.samples = []
            self.samples.append(current_index)
        elif self.sample_size / len(self.requests_stats) >= random.random():
            # Sampling logic: choose to replace with decreasing probability s / n
            # where s is sample size, n is current number of requests.
            # If chosen, choose random existing sample to replace.
            # P(new item in samples)  = s / n
            # P(prev item in samples) = P(item was in samples) * P(not replaced)
            # P(prev item in samples) =
            #    P(before replacement) * P(new item selected) * P(chosen from samples)
            # P(prev item in samples) = (s / (n - 1)) * (s / n) * (1 / s) = s / n
            # P(prev item in samples) = P(new item in samples)
            if self.samples is None:
                self.samples = []
            replace_index = random.randrange(len(self.samples))
            self.clear_stats_data(self.samples[replace_index])
            self.samples[replace_index] = current_index

        return stats

    def clear_stats_data(self, stats: GenerativeRequestStats | int):
        if isinstance(stats, int):
            stats = self.requests_stats[stats]

        if self.clear_nonsampled_request_args:
            stats.request_args = None
        if self.clear_nonsampled_outputs:
            stats.output = None
            stats.reasoning_output = None
            stats.tool_calls = None

    @classmethod
    def compile_stats(
        cls,
        response: GenerationResponse | None,
        request: GenerationRequest,
        info: RequestInfo,
        prefer_response_metrics: bool,
    ) -> GenerativeRequestStats:
        """
        Compile statistics from request, response, and execution info.

        :param response: Generation response with output and metrics, or None
        :param request: Original generation request with input data
        :param info: Request execution information and timing
        :param prefer_response_metrics: Whether to prefer metrics from response
        :return: Compiled generative request statistics
        """
        # Extract the first request for arguments if multi-turn
        first_request: GenerationRequest
        if isinstance(request, GenerationRequest):
            first_request = request
        else:
            # Multi-turn request: extract first item
            first_item = request[0]
            first_request = (
                first_item[0] if isinstance(first_item, tuple) else first_item
            )

        if response is None:
            response = GenerationResponse(
                request_id=info.request_id,
                request_args=None,
            )

        return response.compile_stats(
            request=first_request,
            info=info,
            prefer_response=prefer_response_metrics,
        )

compile_stats(response, request, info, prefer_response_metrics) classmethod

Compile statistics from request, response, and execution info.

Parameters:

Name Type Description Default
response GenerationResponse | None

Generation response with output and metrics, or None

required
request GenerationRequest

Original generation request with input data

required
info RequestInfo

Request execution information and timing

required
prefer_response_metrics bool

Whether to prefer metrics from response

required

Returns:

Type Description
GenerativeRequestStats

Compiled generative request statistics

Source code in src/guidellm/benchmark/schemas/accumulator.py
@classmethod
def compile_stats(
    cls,
    response: GenerationResponse | None,
    request: GenerationRequest,
    info: RequestInfo,
    prefer_response_metrics: bool,
) -> GenerativeRequestStats:
    """
    Compile statistics from request, response, and execution info.

    :param response: Generation response with output and metrics, or None
    :param request: Original generation request with input data
    :param info: Request execution information and timing
    :param prefer_response_metrics: Whether to prefer metrics from response
    :return: Compiled generative request statistics
    """
    # Extract the first request for arguments if multi-turn
    first_request: GenerationRequest
    if isinstance(request, GenerationRequest):
        first_request = request
    else:
        # Multi-turn request: extract first item
        first_item = request[0]
        first_request = (
            first_item[0] if isinstance(first_item, tuple) else first_item
        )

    if response is None:
        response = GenerationResponse(
            request_id=info.request_id,
            request_args=None,
        )

    return response.compile_stats(
        request=first_request,
        info=info,
        prefer_response=prefer_response_metrics,
    )

get_sampled()

Retrieve the list of sampled request statistics.

Returns:

Type Description
list[GenerativeRequestStats]

List of sampled generative request statistics

Source code in src/guidellm/benchmark/schemas/accumulator.py
def get_sampled(self) -> list[GenerativeRequestStats]:
    """
    Retrieve the list of sampled request statistics.

    :return: List of sampled generative request statistics
    """
    if self.samples is None:
        return self.requests_stats

    return [self.requests_stats[ind] for ind in self.samples]

get_within_range(start_time, end_time, start_func=lambda s: s.request_start_time, end_func=lambda s: s.request_end_time, closed=False)

Retrieve request statistics within a specified time range.

If the request has no start time we treat it as an instantaneous event at the end time.

Parameters:

Name Type Description Default
start_time float

Start timestamp for filtering (events must end after this)

required
end_time float

End timestamp for filtering (events must start before this)

required
start_func Callable[[GenerativeRequestStats], float | None]

Function to extract the start timestamp from a request stat

lambda s: request_start_time
end_func Callable[[GenerativeRequestStats], float | None]

Function to extract the end timestamp from a request stats

lambda s: request_end_time
closed bool

If True, events must be strictly enclosed within the time range. If False, include events that partially overlap the time range.

False

Returns:

Type Description
list[GenerativeRequestStats]

List of request statistics within the time range

Source code in src/guidellm/benchmark/schemas/accumulator.py
def get_within_range(
    self,
    start_time: float,
    end_time: float,
    start_func: Callable[[GenerativeRequestStats], float | None] = lambda s: (
        s.request_start_time
    ),
    end_func: Callable[[GenerativeRequestStats], float | None] = lambda s: (
        s.request_end_time
    ),
    closed: bool = False,
) -> list[GenerativeRequestStats]:
    """
    Retrieve request statistics within a specified time range.

    If the request has no start time we treat it
    as an instantaneous event at the end time.

    :param start_time: Start timestamp for filtering (events must end after this)
    :param end_time: End timestamp for filtering (events must start before this)
    :param start_func: Function to extract the start timestamp from a request stat
    :param end_func: Function to extract the end timestamp from a request stats
    :param closed: If True, events must be strictly enclosed within the time range.
                   If False, include events that partially overlap the time range.
    :return: List of request statistics within the time range
    """
    within_range = []
    for stats in self.requests_stats:
        start_event = start_func(stats)
        end_event = end_func(stats)

        # Skip if we don't know when the event ended
        if end_event is None:
            continue

        # Treat instantaneous events as starting exactly when they end
        actual_start = start_event if start_event is not None else end_event

        if closed:
            # Fully enclosed
            is_in_range = actual_start >= start_time and end_event <= end_time
        else:
            # Partial overlap
            is_in_range = end_event >= start_time and actual_start <= end_time

        if is_in_range:
            within_range.append(stats)

    return within_range

update_estimate(response, request, info, prefer_response_metrics)

Record request statistics and apply reservoir sampling if configured.

Compiles statistics from the completed request and adds to the collection. Uses reservoir sampling algorithm to maintain uniform sample distribution when enabled, clearing non-sampled request data to manage memory.

Parameters:

Name Type Description Default
response GenerationResponse | None

Generation response containing output and metrics

required
request GenerationRequest

Original generation request with input data

required
info RequestInfo

Request execution information and timing

required
prefer_response_metrics bool

Whether to prefer metrics from response

required

Returns:

Type Description
GenerativeRequestStats

Compiled request statistics

Source code in src/guidellm/benchmark/schemas/accumulator.py
def update_estimate(
    self,
    response: GenerationResponse | None,
    request: GenerationRequest,
    info: RequestInfo,
    prefer_response_metrics: bool,
) -> GenerativeRequestStats:
    """
    Record request statistics and apply reservoir sampling if configured.

    Compiles statistics from the completed request and adds to the collection.
    Uses reservoir sampling algorithm to maintain uniform sample distribution when
    enabled, clearing non-sampled request data to manage memory.

    :param response: Generation response containing output and metrics
    :param request: Original generation request with input data
    :param info: Request execution information and timing
    :param prefer_response_metrics: Whether to prefer metrics from response
    :return: Compiled request statistics
    """
    stats = self.compile_stats(response, request, info, prefer_response_metrics)

    current_index = len(self.requests_stats)
    self.requests_stats.append(stats)

    if self.sample_size is None:
        # Keeping all requests, don't need to sample
        self.samples = None
    elif self.sample_size <= 0:
        # Not keeping any requests, clear out unnecessary memory usage for current
        self.clear_stats_data(stats)
    elif self.sample_size >= len(self.requests_stats):
        # Add directly to samples, haven't filled yet
        if self.samples is None:
            self.samples = []
        self.samples.append(current_index)
    elif self.sample_size / len(self.requests_stats) >= random.random():
        # Sampling logic: choose to replace with decreasing probability s / n
        # where s is sample size, n is current number of requests.
        # If chosen, choose random existing sample to replace.
        # P(new item in samples)  = s / n
        # P(prev item in samples) = P(item was in samples) * P(not replaced)
        # P(prev item in samples) =
        #    P(before replacement) * P(new item selected) * P(chosen from samples)
        # P(prev item in samples) = (s / (n - 1)) * (s / n) * (1 / s) = s / n
        # P(prev item in samples) = P(new item in samples)
        if self.samples is None:
            self.samples = []
        replace_index = random.randrange(len(self.samples))
        self.clear_stats_data(self.samples[replace_index])
        self.samples[replace_index] = current_index

    return stats

GenerativeTextMetricsSummary

Bases: StandardBaseDict

Text-specific metric summaries for generative benchmarks.

Tracks token, word, and character-level metrics across input, output, and total usage for text generation workloads.

Source code in src/guidellm/benchmark/schemas/metrics.py
class GenerativeTextMetricsSummary(StandardBaseDict):
    """
    Text-specific metric summaries for generative benchmarks.

    Tracks token, word, and character-level metrics across input, output, and
    total usage for text generation workloads.
    """

    tokens: GenerativeMetricsSummary | None = Field(
        description="Token count metrics and distributions"
    )
    words: GenerativeMetricsSummary | None = Field(
        description="Word count metrics and distributions"
    )
    characters: GenerativeMetricsSummary | None = Field(
        description="Character count metrics and distributions"
    )

    @classmethod
    def compile(
        cls,
        successful: list[GenerativeRequestStats],
        incomplete: list[GenerativeRequestStats],
        errored: list[GenerativeRequestStats],
    ) -> GenerativeTextMetricsSummary:
        """
        Compile text metrics summary from request statistics.

        :param successful: Successfully completed request statistics
        :param incomplete: Incomplete/cancelled request statistics
        :param errored: Failed request statistics
        :return: Compiled text metrics summary
        """
        return GenerativeTextMetricsSummary(
            tokens=GenerativeMetricsSummary.compile(
                property_name="text_tokens",
                successful=successful,
                incomplete=incomplete,
                errored=errored,
            ),
            words=GenerativeMetricsSummary.compile(
                property_name="text_words",
                successful=successful,
                incomplete=incomplete,
                errored=errored,
            ),
            characters=GenerativeMetricsSummary.compile(
                property_name="text_characters",
                successful=successful,
                incomplete=incomplete,
                errored=errored,
            ),
        )

compile(successful, incomplete, errored) classmethod

Compile text metrics summary from request statistics.

Parameters:

Name Type Description Default
successful list[GenerativeRequestStats]

Successfully completed request statistics

required
incomplete list[GenerativeRequestStats]

Incomplete/cancelled request statistics

required
errored list[GenerativeRequestStats]

Failed request statistics

required

Returns:

Type Description
GenerativeTextMetricsSummary

Compiled text metrics summary

Source code in src/guidellm/benchmark/schemas/metrics.py
@classmethod
def compile(
    cls,
    successful: list[GenerativeRequestStats],
    incomplete: list[GenerativeRequestStats],
    errored: list[GenerativeRequestStats],
) -> GenerativeTextMetricsSummary:
    """
    Compile text metrics summary from request statistics.

    :param successful: Successfully completed request statistics
    :param incomplete: Incomplete/cancelled request statistics
    :param errored: Failed request statistics
    :return: Compiled text metrics summary
    """
    return GenerativeTextMetricsSummary(
        tokens=GenerativeMetricsSummary.compile(
            property_name="text_tokens",
            successful=successful,
            incomplete=incomplete,
            errored=errored,
        ),
        words=GenerativeMetricsSummary.compile(
            property_name="text_words",
            successful=successful,
            incomplete=incomplete,
            errored=errored,
        ),
        characters=GenerativeMetricsSummary.compile(
            property_name="text_characters",
            successful=successful,
            incomplete=incomplete,
            errored=errored,
        ),
    )

GenerativeVideoMetricsSummary

Bases: StandardBaseDict

Video-specific metric summaries for generative benchmarks.

Tracks token, frame count, duration, and byte-level metrics across input, output, and total usage for video generation workloads.

Source code in src/guidellm/benchmark/schemas/metrics.py
class GenerativeVideoMetricsSummary(StandardBaseDict):
    """
    Video-specific metric summaries for generative benchmarks.

    Tracks token, frame count, duration, and byte-level metrics across input,
    output, and total usage for video generation workloads.
    """

    tokens: GenerativeMetricsSummary | None = Field(
        description="Video token count metrics and distributions"
    )
    frames: GenerativeMetricsSummary | None = Field(
        description="Frame count metrics and distributions"
    )
    seconds: GenerativeMetricsSummary | None = Field(
        description="Duration metrics in seconds and distributions"
    )
    bytes: GenerativeMetricsSummary | None = Field(
        description="Byte size metrics and distributions"
    )

    @classmethod
    def compile(
        cls,
        successful: list[GenerativeRequestStats],
        incomplete: list[GenerativeRequestStats],
        errored: list[GenerativeRequestStats],
    ) -> GenerativeVideoMetricsSummary:
        """
        Compile video metrics summary from request statistics.

        :param successful: Successfully completed request statistics
        :param incomplete: Incomplete/cancelled request statistics
        :param errored: Failed request statistics
        :return: Compiled video metrics summary
        """
        return GenerativeVideoMetricsSummary(
            tokens=GenerativeMetricsSummary.compile(
                property_name="video_tokens",
                successful=successful,
                incomplete=incomplete,
                errored=errored,
            ),
            frames=GenerativeMetricsSummary.compile(
                property_name="video_frames",
                successful=successful,
                incomplete=incomplete,
                errored=errored,
            ),
            seconds=GenerativeMetricsSummary.compile(
                property_name="video_seconds",
                successful=successful,
                incomplete=incomplete,
                errored=errored,
            ),
            bytes=GenerativeMetricsSummary.compile(
                property_name="video_bytes",
                successful=successful,
                incomplete=incomplete,
                errored=errored,
            ),
        )

compile(successful, incomplete, errored) classmethod

Compile video metrics summary from request statistics.

Parameters:

Name Type Description Default
successful list[GenerativeRequestStats]

Successfully completed request statistics

required
incomplete list[GenerativeRequestStats]

Incomplete/cancelled request statistics

required
errored list[GenerativeRequestStats]

Failed request statistics

required

Returns:

Type Description
GenerativeVideoMetricsSummary

Compiled video metrics summary

Source code in src/guidellm/benchmark/schemas/metrics.py
@classmethod
def compile(
    cls,
    successful: list[GenerativeRequestStats],
    incomplete: list[GenerativeRequestStats],
    errored: list[GenerativeRequestStats],
) -> GenerativeVideoMetricsSummary:
    """
    Compile video metrics summary from request statistics.

    :param successful: Successfully completed request statistics
    :param incomplete: Incomplete/cancelled request statistics
    :param errored: Failed request statistics
    :return: Compiled video metrics summary
    """
    return GenerativeVideoMetricsSummary(
        tokens=GenerativeMetricsSummary.compile(
            property_name="video_tokens",
            successful=successful,
            incomplete=incomplete,
            errored=errored,
        ),
        frames=GenerativeMetricsSummary.compile(
            property_name="video_frames",
            successful=successful,
            incomplete=incomplete,
            errored=errored,
        ),
        seconds=GenerativeMetricsSummary.compile(
            property_name="video_seconds",
            successful=successful,
            incomplete=incomplete,
            errored=errored,
        ),
        bytes=GenerativeMetricsSummary.compile(
            property_name="video_bytes",
            successful=successful,
            incomplete=incomplete,
            errored=errored,
        ),
    )

RunningMetricStats

Bases: StandardBaseModel

Maintains running statistics for a metric stream without storing all samples.

Accumulates count, sum, time-weighted sum, and duration to compute mean, rate, and time-weighted statistics incrementally. Efficient for real-time metric tracking during long-running benchmarks where storing individual samples is impractical.

Source code in src/guidellm/benchmark/schemas/accumulator.py
class RunningMetricStats(StandardBaseModel):
    """
    Maintains running statistics for a metric stream without storing all samples.

    Accumulates count, sum, time-weighted sum, and duration to compute mean, rate,
    and time-weighted statistics incrementally. Efficient for real-time metric tracking
    during long-running benchmarks where storing individual samples is impractical.
    """

    count: int = Field(description="Number of samples accumulated", default=0)
    value_sum: float = Field(description="Total sum of accumulated values", default=0.0)
    time_weighted_sum: float = Field(
        description="Time-weighted sum of accumulated values", default=0.0
    )
    duration: float = Field(
        description="Total duration over which values were accumulated", default=0.0
    )
    last_value: float | None = Field(
        description="Most recent value added to the accumulator", default=None
    )

    @property
    def mean(self) -> float | None:
        """
        :return: Arithmetic mean of accumulated values, or None if no samples
        """
        if self.count <= 0:
            return None

        return self.value_sum / self.count

    @property
    def time_weighted_mean(self) -> float | None:
        """
        :return: Time-weighted mean considering duration between samples, or None
        """
        if self.duration <= 0.0:
            return None

        return self.time_weighted_sum / self.duration

    @property
    def rate_per_item(self) -> float | None:
        """
        :return: Average value per accumulated item, or None if no samples
        """
        if self.count <= 0:
            return None

        return self.value_sum / self.count

    @property
    def rate_per_second(self) -> float | None:
        """
        :return: Average value per second of duration, or None if no duration
        """
        if self.duration <= 0.0:
            return None

        return self.value_sum / self.duration

    def update_estimate(
        self,
        value: float | None,
        count: int = 1,
        duration: float | None = None,
        elapsed: float | None = None,
    ):
        """
        Incorporate a new metric value into running statistics.

        Updates count, sum, and time-weighted statistics using the new value and timing
        information. Time-weighted calculations use the previous value over the elapsed
        interval to capture sustained metric behavior.

        :param value: New metric value to accumulate
        :param count: Number of occurrences this value represents
        :param duration: Total duration to set, overriding incremental elapsed updates
        :param elapsed: Time elapsed since last update for time-weighted calculations
        """
        if value is not None:
            self.count += count
            self.value_sum += value * count

        if elapsed is not None:
            self.time_weighted_sum += (self.last_value or 0.0) * elapsed

        self.duration = (
            duration if duration is not None else (self.duration + (elapsed or 0.0))
        )
        self.last_value = value

mean property

Returns:

Type Description
float | None

Arithmetic mean of accumulated values, or None if no samples

rate_per_item property

Returns:

Type Description
float | None

Average value per accumulated item, or None if no samples

rate_per_second property

Returns:

Type Description
float | None

Average value per second of duration, or None if no duration

time_weighted_mean property

Returns:

Type Description
float | None

Time-weighted mean considering duration between samples, or None

update_estimate(value, count=1, duration=None, elapsed=None)

Incorporate a new metric value into running statistics.

Updates count, sum, and time-weighted statistics using the new value and timing information. Time-weighted calculations use the previous value over the elapsed interval to capture sustained metric behavior.

Parameters:

Name Type Description Default
value float | None

New metric value to accumulate

required
count int

Number of occurrences this value represents

1
duration float | None

Total duration to set, overriding incremental elapsed updates

None
elapsed float | None

Time elapsed since last update for time-weighted calculations

None
Source code in src/guidellm/benchmark/schemas/accumulator.py
def update_estimate(
    self,
    value: float | None,
    count: int = 1,
    duration: float | None = None,
    elapsed: float | None = None,
):
    """
    Incorporate a new metric value into running statistics.

    Updates count, sum, and time-weighted statistics using the new value and timing
    information. Time-weighted calculations use the previous value over the elapsed
    interval to capture sustained metric behavior.

    :param value: New metric value to accumulate
    :param count: Number of occurrences this value represents
    :param duration: Total duration to set, overriding incremental elapsed updates
    :param elapsed: Time elapsed since last update for time-weighted calculations
    """
    if value is not None:
        self.count += count
        self.value_sum += value * count

    if elapsed is not None:
        self.time_weighted_sum += (self.last_value or 0.0) * elapsed

    self.duration = (
        duration if duration is not None else (self.duration + (elapsed or 0.0))
    )
    self.last_value = value

SchedulerMetrics

Bases: StandardBaseDict

Scheduler timing and performance statistics.

Tracks overall benchmark timing, request counts by status, and detailed internal scheduler performance metrics including queue times, processing delays, and request execution statistics. Used to analyze scheduler efficiency and identify bottlenecks in request processing pipelines.

Source code in src/guidellm/benchmark/schemas/metrics.py
class SchedulerMetrics(StandardBaseDict):
    """Scheduler timing and performance statistics.

    Tracks overall benchmark timing, request counts by status, and detailed internal
    scheduler performance metrics including queue times, processing delays, and
    request execution statistics. Used to analyze scheduler efficiency and identify
    bottlenecks in request processing pipelines.
    """

    # Overall timings for the scheduler
    start_time: float = Field(
        description="Unix timestamp when the benchmark run started"
    )
    request_start_time: float = Field(
        description="Unix timestamp when first request was made"
    )
    measure_start_time: float = Field(
        description="Unix timestamp when measurement period started"
    )
    measure_end_time: float = Field(
        description="Unix timestamp when measurement period ended"
    )
    request_end_time: float = Field(
        description="Unix timestamp when last request completed"
    )
    end_time: float = Field(description="Unix timestamp when the benchmark run ended")

    # Request details tracked by the scheduler
    requests_made: StatusBreakdown[int, int, int, int] = Field(
        description="Request counts by status: successful, incomplete, errored, total"
    )

    # Scheduler internal performance timings
    generation_delay: DistributionSummary = Field(
        default_factory=lambda: DistributionSummary.from_values([]),
        description=(
            "Distribution of time between attempting to yield a conversation "
            "from the request generator and actually yielding it (seconds)"
        ),
    )
    queued_time_avg: float = Field(
        description="Avg time requests spent in the queue (seconds)"
    )
    resolve_start_delay_avg: float = Field(
        description="Avg delay before worker begins resolving req after dequeue (sec)"
    )
    resolve_targeted_start_delay_avg: float = Field(
        description="Avg delay to targeted resolve start time (seconds)"
    )
    request_start_delay_avg: float = Field(
        description="Avg delay before request starts after resolve (seconds)"
    )
    request_targeted_start_delay_avg: float = Field(
        description="Avg delay to targeted request start time (seconds)"
    )
    request_time_avg: float = Field(description="Avg request execution time (seconds)")
    resolve_end_delay_avg: float = Field(
        description="Avg delay after request completes before resolve ends (seconds)"
    )
    resolve_time_avg: float = Field(
        description="Avg total resolve time including request (seconds)"
    )
    finalized_delay_avg: float = Field(
        description="Avg delay from resolve end to request finalization (seconds)"
    )
    processed_delay_avg: float = Field(
        description="Avg delay from finalization to processing completion (seconds)"
    )
    dataset_incomplete: bool = Field(
        default=False,
        description=(
            "True when the request iterator stopped before it was exhausted. "
            "False when the scheduler registered requests_exhausted"
        ),
    )
    strategy_type: str = Field(
        default="",
        description="Type identifier of the strategy that ran this benchmark",
    )

    @classmethod
    def compile(
        cls,
        accumulator: GenerativeBenchmarkAccumulator,
        scheduler_state: SchedulerState,
    ) -> SchedulerMetrics:
        """
        Compile scheduler metrics from accumulator and scheduler state.

        :param accumulator: Benchmark accumulator containing timing and metric data
        :param scheduler_state: Scheduler state with execution timing information
        :return: Compiled scheduler metrics with performance statistics
        """
        return SchedulerMetrics(
            # Overall timings for the scheduler
            start_time=scheduler_state.start_time,
            request_start_time=accumulator.timings.finalized_request_start,
            measure_start_time=accumulator.timings.finalized_measure_start,
            measure_end_time=accumulator.timings.finalized_measure_end,
            request_end_time=accumulator.timings.finalized_request_end,
            end_time=scheduler_state.end_time or -1.0,
            # Request details tracked by the scheduler
            requests_made=accumulator.scheduler_metrics.requests_made,
            # Scheduler internal performance timings
            generation_delay=DistributionSummary.from_values(
                scheduler_state.generation_delay_samples
            ),
            queued_time_avg=accumulator.scheduler_metrics.queued_time.mean or -1.0,
            resolve_start_delay_avg=(
                accumulator.scheduler_metrics.resolve_start_delay.mean or -1.0
            ),
            resolve_targeted_start_delay_avg=(
                accumulator.scheduler_metrics.resolve_targeted_start_delay.mean or -1.0
            ),
            request_start_delay_avg=(
                accumulator.scheduler_metrics.request_start_delay.mean or -1.0
            ),
            request_targeted_start_delay_avg=(
                accumulator.scheduler_metrics.request_targeted_start_delay.mean or -1.0
            ),
            request_time_avg=accumulator.scheduler_metrics.request_time.mean or -1.0,
            resolve_end_delay_avg=(
                accumulator.scheduler_metrics.resolve_end_delay.mean or -1.0
            ),
            resolve_time_avg=accumulator.scheduler_metrics.resolve_time.mean or -1.0,
            finalized_delay_avg=(
                accumulator.scheduler_metrics.finalized_delay.mean or -1.0
            ),
            processed_delay_avg=(
                accumulator.scheduler_metrics.processed_delay.mean or -1.0
            ),
            dataset_incomplete=(
                "requests_exhausted" not in scheduler_state.scheduler_constraints
            ),
            strategy_type=accumulator.config.strategy.type_,
        )

compile(accumulator, scheduler_state) classmethod

Compile scheduler metrics from accumulator and scheduler state.

Parameters:

Name Type Description Default
accumulator GenerativeBenchmarkAccumulator

Benchmark accumulator containing timing and metric data

required
scheduler_state SchedulerState

Scheduler state with execution timing information

required

Returns:

Type Description
SchedulerMetrics

Compiled scheduler metrics with performance statistics

Source code in src/guidellm/benchmark/schemas/metrics.py
@classmethod
def compile(
    cls,
    accumulator: GenerativeBenchmarkAccumulator,
    scheduler_state: SchedulerState,
) -> SchedulerMetrics:
    """
    Compile scheduler metrics from accumulator and scheduler state.

    :param accumulator: Benchmark accumulator containing timing and metric data
    :param scheduler_state: Scheduler state with execution timing information
    :return: Compiled scheduler metrics with performance statistics
    """
    return SchedulerMetrics(
        # Overall timings for the scheduler
        start_time=scheduler_state.start_time,
        request_start_time=accumulator.timings.finalized_request_start,
        measure_start_time=accumulator.timings.finalized_measure_start,
        measure_end_time=accumulator.timings.finalized_measure_end,
        request_end_time=accumulator.timings.finalized_request_end,
        end_time=scheduler_state.end_time or -1.0,
        # Request details tracked by the scheduler
        requests_made=accumulator.scheduler_metrics.requests_made,
        # Scheduler internal performance timings
        generation_delay=DistributionSummary.from_values(
            scheduler_state.generation_delay_samples
        ),
        queued_time_avg=accumulator.scheduler_metrics.queued_time.mean or -1.0,
        resolve_start_delay_avg=(
            accumulator.scheduler_metrics.resolve_start_delay.mean or -1.0
        ),
        resolve_targeted_start_delay_avg=(
            accumulator.scheduler_metrics.resolve_targeted_start_delay.mean or -1.0
        ),
        request_start_delay_avg=(
            accumulator.scheduler_metrics.request_start_delay.mean or -1.0
        ),
        request_targeted_start_delay_avg=(
            accumulator.scheduler_metrics.request_targeted_start_delay.mean or -1.0
        ),
        request_time_avg=accumulator.scheduler_metrics.request_time.mean or -1.0,
        resolve_end_delay_avg=(
            accumulator.scheduler_metrics.resolve_end_delay.mean or -1.0
        ),
        resolve_time_avg=accumulator.scheduler_metrics.resolve_time.mean or -1.0,
        finalized_delay_avg=(
            accumulator.scheduler_metrics.finalized_delay.mean or -1.0
        ),
        processed_delay_avg=(
            accumulator.scheduler_metrics.processed_delay.mean or -1.0
        ),
        dataset_incomplete=(
            "requests_exhausted" not in scheduler_state.scheduler_constraints
        ),
        strategy_type=accumulator.config.strategy.type_,
    )

SchedulerMetricsAccumulator

Bases: StandardBaseModel

Tracks scheduler-level timing and overhead metrics during execution.

Monitors request lifecycle timing from queuing through completion, capturing delays at each stage: queue time, worker start delays, request processing time, and finalization overhead. Provides insight into scheduler efficiency and bottleneck identification in request orchestration.

Source code in src/guidellm/benchmark/schemas/accumulator.py
class SchedulerMetricsAccumulator(StandardBaseModel):
    """
    Tracks scheduler-level timing and overhead metrics during execution.

    Monitors request lifecycle timing from queuing through completion, capturing delays
    at each stage: queue time, worker start delays, request processing time, and
    finalization overhead. Provides insight into scheduler efficiency and bottleneck
    identification in request orchestration.
    """

    requests_made: StatusBreakdown[int, int, int, int] = Field(
        description="Request counts by status: successful, incomplete, errored, total",
        default_factory=lambda: StatusBreakdown[int, int, int, int](
            successful=0, errored=0, incomplete=0, total=0
        ),
    )
    # Timings flow:
    # Request scheduling: queued->dequeued->scheduled_at->resolve_start->
    # Request processing: request_start->*_iteration->request_end->
    # Request finalizing: resolve_end->finalized->accumulation update processed
    queued_time: RunningMetricStats = Field(
        default_factory=RunningMetricStats,
        description="Running stats for time requests spent in the queue",
    )
    resolve_start_delay: RunningMetricStats = Field(
        default_factory=RunningMetricStats,
        description=(
            "Running stats for delay before worker begins resolving req after dequeue"
        ),
    )
    resolve_targeted_start_delay: RunningMetricStats = Field(
        default_factory=RunningMetricStats,
        description=(
            "Running stats for delay from targeted start to actual worker start"
        ),
    )
    request_start_delay: RunningMetricStats = Field(
        default_factory=RunningMetricStats,
        description="Running stats for delay after resolve til request start",
    )
    request_targeted_start_delay: RunningMetricStats = Field(
        default_factory=RunningMetricStats,
        description=(
            "Running stats for delay from targeted start to actual request start"
        ),
    )
    request_time: RunningMetricStats = Field(
        default_factory=RunningMetricStats,
        description="Running stats for request processing time",
    )
    resolve_end_delay: RunningMetricStats = Field(
        default_factory=RunningMetricStats,
        description="Running stats for delay after request end till worker resolves",
    )
    resolve_time: RunningMetricStats = Field(
        default_factory=RunningMetricStats,
        description="Running stats for time for worker to resolve requests",
    )
    finalized_delay: RunningMetricStats = Field(
        default_factory=RunningMetricStats,
        description="Running stats for delay after resolve til finalized in scheduler",
    )
    processed_delay: RunningMetricStats = Field(
        default_factory=RunningMetricStats,
        description=(
            "Running stats for delay from finalized til request being "
            "processed by accumulation"
        ),
    )

    def update_estimate(
        self, scheduler_state: SchedulerState, stats: GenerativeRequestStats
    ):
        """
        Update scheduler metrics with completed request timing data.

        Extracts timing information from request statistics to update running metrics
        for each scheduler lifecycle stage. Validates that required timing markers are
        present before processing.

        :param scheduler_state: Current scheduler state with request counts
        :param stats: Completed request statistics with detailed timing information
        :raises ValueError: If required timing markers are missing
        """
        # Update request counts
        self.requests_made.successful = scheduler_state.successful_requests
        self.requests_made.errored = scheduler_state.errored_requests
        self.requests_made.incomplete = scheduler_state.cancelled_requests
        self.requests_made.total = (
            scheduler_state.successful_requests
            + scheduler_state.errored_requests
            + scheduler_state.cancelled_requests
        )

        # All requests must have queued, dequeued, resolve_end, and finalized timings
        timings: RequestTimings = stats.info.timings
        if any(
            timing is None
            for timing in [
                timings.queued,
                timings.dequeued,
                timings.resolve_end,
                timings.finalized,
            ]
        ):
            raise ValueError(
                "Required timings 'queued', 'dequeued', 'resolve_end', and "
                "'finalized' must not be None"
            )

        # Store validated non-None timings for type safety
        queued: float = timings.queued  # type: ignore[assignment]
        dequeued: float = timings.dequeued  # type: ignore[assignment]
        resolve_end: float = timings.resolve_end  # type: ignore[assignment]
        finalized: float = timings.finalized  # type: ignore[assignment]

        # Update timing metrics in occurrence order
        self.queued_time.update_estimate(value=dequeued - queued)

        if timings.scheduled_at is not None and timings.resolve_start is not None:
            self.resolve_start_delay.update_estimate(
                value=timings.resolve_start - timings.scheduled_at
            )

        if timings.targeted_start is not None and timings.resolve_start is not None:
            self.resolve_targeted_start_delay.update_estimate(
                value=timings.resolve_start - timings.targeted_start
            )

        if timings.resolve_start is not None and timings.request_start is not None:
            self.request_start_delay.update_estimate(
                value=timings.request_start - timings.resolve_start
            )

        if timings.targeted_start is not None and timings.request_start is not None:
            self.request_targeted_start_delay.update_estimate(
                value=timings.request_start - timings.targeted_start
            )

        if timings.request_start is not None and timings.request_end is not None:
            self.request_time.update_estimate(
                value=timings.request_end - timings.request_start
            )

        if timings.request_end is not None:
            self.resolve_end_delay.update_estimate(
                value=resolve_end - timings.request_end
            )

        if timings.resolve_start is not None:
            self.resolve_time.update_estimate(value=resolve_end - timings.resolve_start)

        self.finalized_delay.update_estimate(value=finalized - resolve_end)
        self.processed_delay.update_estimate(value=time.time() - finalized)

update_estimate(scheduler_state, stats)

Update scheduler metrics with completed request timing data.

Extracts timing information from request statistics to update running metrics for each scheduler lifecycle stage. Validates that required timing markers are present before processing.

Parameters:

Name Type Description Default
scheduler_state SchedulerState

Current scheduler state with request counts

required
stats GenerativeRequestStats

Completed request statistics with detailed timing information

required

Raises:

Type Description
ValueError

If required timing markers are missing

Source code in src/guidellm/benchmark/schemas/accumulator.py
def update_estimate(
    self, scheduler_state: SchedulerState, stats: GenerativeRequestStats
):
    """
    Update scheduler metrics with completed request timing data.

    Extracts timing information from request statistics to update running metrics
    for each scheduler lifecycle stage. Validates that required timing markers are
    present before processing.

    :param scheduler_state: Current scheduler state with request counts
    :param stats: Completed request statistics with detailed timing information
    :raises ValueError: If required timing markers are missing
    """
    # Update request counts
    self.requests_made.successful = scheduler_state.successful_requests
    self.requests_made.errored = scheduler_state.errored_requests
    self.requests_made.incomplete = scheduler_state.cancelled_requests
    self.requests_made.total = (
        scheduler_state.successful_requests
        + scheduler_state.errored_requests
        + scheduler_state.cancelled_requests
    )

    # All requests must have queued, dequeued, resolve_end, and finalized timings
    timings: RequestTimings = stats.info.timings
    if any(
        timing is None
        for timing in [
            timings.queued,
            timings.dequeued,
            timings.resolve_end,
            timings.finalized,
        ]
    ):
        raise ValueError(
            "Required timings 'queued', 'dequeued', 'resolve_end', and "
            "'finalized' must not be None"
        )

    # Store validated non-None timings for type safety
    queued: float = timings.queued  # type: ignore[assignment]
    dequeued: float = timings.dequeued  # type: ignore[assignment]
    resolve_end: float = timings.resolve_end  # type: ignore[assignment]
    finalized: float = timings.finalized  # type: ignore[assignment]

    # Update timing metrics in occurrence order
    self.queued_time.update_estimate(value=dequeued - queued)

    if timings.scheduled_at is not None and timings.resolve_start is not None:
        self.resolve_start_delay.update_estimate(
            value=timings.resolve_start - timings.scheduled_at
        )

    if timings.targeted_start is not None and timings.resolve_start is not None:
        self.resolve_targeted_start_delay.update_estimate(
            value=timings.resolve_start - timings.targeted_start
        )

    if timings.resolve_start is not None and timings.request_start is not None:
        self.request_start_delay.update_estimate(
            value=timings.request_start - timings.resolve_start
        )

    if timings.targeted_start is not None and timings.request_start is not None:
        self.request_targeted_start_delay.update_estimate(
            value=timings.request_start - timings.targeted_start
        )

    if timings.request_start is not None and timings.request_end is not None:
        self.request_time.update_estimate(
            value=timings.request_end - timings.request_start
        )

    if timings.request_end is not None:
        self.resolve_end_delay.update_estimate(
            value=resolve_end - timings.request_end
        )

    if timings.resolve_start is not None:
        self.resolve_time.update_estimate(value=resolve_end - timings.resolve_start)

    self.finalized_delay.update_estimate(value=finalized - resolve_end)
    self.processed_delay.update_estimate(value=time.time() - finalized)