Skip to content

guidellm.benchmark.profiles.sweep

Adaptive sweep benchmark profile.

SweepProfile

Bases: Profile

Discover optimal rate range through adaptive multi-strategy execution.

Automatically discovers optimal rate range by executing synchronous and throughput strategies first, then interpolating rates for async strategies to comprehensively sweep the performance space.

Source code in src/guidellm/benchmark/profiles/sweep.py
@ProfileFactory.register("sweep")
class SweepProfile(Profile):
    """
    Discover optimal rate range through adaptive multi-strategy execution.

    Automatically discovers optimal rate range by executing synchronous and
    throughput strategies first, then interpolating rates for async strategies
    to comprehensively sweep the performance space.
    """

    args: SweepProfileArgs

    def __init__(
        self,
        args: SweepProfileArgs,
        random_seed: int,
        constraints: MutableMapping[str, ConstraintInitializer | Any] | None,
        **kwargs: Any,
    ):
        super().__init__(args, random_seed, constraints, **kwargs)
        self.args = args
        self.synchronous_rate = -1.0
        self.throughput_rate = -1.0
        self.async_rates: list[float] = []
        self.measured_rates: list[float] = []

    @property
    def strategy_types(self) -> list[str]:
        """
        :return: Strategy types for the complete sweep sequence
        """
        types = ["synchronous", "throughput"]
        types += [self.args.strategy_type] * (self.args.sweep_size - len(types))
        return types

    def next_strategy(
        self,
        prev_strategy: SchedulingStrategy | None,
        prev_benchmark: Benchmark | None,
    ) -> (
        AsyncConstantStrategy
        | AsyncPoissonStrategy
        | SynchronousStrategy
        | ThroughputStrategy
        | None
    ):
        """
        Generate next strategy in adaptive sweep sequence.

        Executes synchronous and throughput strategies first to measure baseline
        rates, then generates interpolated rates for async strategies. If a
        failure constraint is triggered during the async phase, all remaining
        higher rates are skipped.

        :param prev_strategy: Previously completed strategy instance
        :param prev_benchmark: Benchmark results from previous strategy execution
        :return: Next strategy in sweep sequence, or None if complete
        :raises ValueError: If strategy_type is neither 'constant' nor 'poisson'
        """
        if prev_strategy is None:
            return SynchronousStrategy()

        if prev_strategy.type_ == "synchronous":
            self.synchronous_rate = prev_benchmark.request_throughput.successful.mean

            return ThroughputStrategy(
                max_concurrency=self.args.max_concurrency,
                rampup_duration=self.args.rampup_duration,
            )

        if prev_strategy.type_ == "throughput":
            self.throughput_rate = prev_benchmark.request_throughput.successful.mean
            if self.synchronous_rate <= 0 and self.throughput_rate <= 0:
                raise RuntimeError(
                    "Invalid rates in sweep; aborting. "
                    "Were there any successful requests?"
                )
            self.measured_rates = list(
                np.linspace(
                    self.synchronous_rate,
                    self.throughput_rate,
                    self.args.sweep_size - 1,
                )
            )[1:]  # don't rerun synchronous

        # Stop escalation if a constraint with stopping_scope='all' triggered
        # during the async phase. Throughput is excluded because it intentionally
        # pushes beyond sustainable load. Synchronous never reaches here.
        if (
            prev_strategy.type_ != "throughput"
            and self._should_stop_escalating(prev_benchmark)  # type: ignore[arg-type]
        ):
            return None

        next_index = (
            len(self.completed_strategies) - 1 - 1
        )  # subtract synchronous and throughput
        next_rate = (
            self.measured_rates[next_index]
            if next_index < len(self.measured_rates)
            else None
        )

        if next_rate is None or next_rate <= 0:
            # Stop if we don't have another valid rate to run
            return None

        if self.args.strategy_type == "constant":
            return AsyncConstantStrategy(
                rate=next_rate, max_concurrency=self.args.max_concurrency
            )
        if self.args.strategy_type == "poisson":
            return AsyncPoissonStrategy(
                rate=next_rate,
                max_concurrency=self.args.max_concurrency,
                random_seed=self.random_seed,
            )
        raise ValueError(f"Invalid strategy type: {self.args.strategy_type}")

strategy_types property

Returns:

Type Description
list[str]

Strategy types for the complete sweep sequence

next_strategy(prev_strategy, prev_benchmark)

Generate next strategy in adaptive sweep sequence.

Executes synchronous and throughput strategies first to measure baseline rates, then generates interpolated rates for async strategies. If a failure constraint is triggered during the async phase, all remaining higher rates are skipped.

Parameters:

Name Type Description Default
prev_strategy SchedulingStrategy | None

Previously completed strategy instance

required
prev_benchmark Benchmark | None

Benchmark results from previous strategy execution

required

Returns:

Type Description
AsyncConstantStrategy | AsyncPoissonStrategy | SynchronousStrategy | ThroughputStrategy | None

Next strategy in sweep sequence, or None if complete

Raises:

Type Description
ValueError

If strategy_type is neither 'constant' nor 'poisson'

Source code in src/guidellm/benchmark/profiles/sweep.py
def next_strategy(
    self,
    prev_strategy: SchedulingStrategy | None,
    prev_benchmark: Benchmark | None,
) -> (
    AsyncConstantStrategy
    | AsyncPoissonStrategy
    | SynchronousStrategy
    | ThroughputStrategy
    | None
):
    """
    Generate next strategy in adaptive sweep sequence.

    Executes synchronous and throughput strategies first to measure baseline
    rates, then generates interpolated rates for async strategies. If a
    failure constraint is triggered during the async phase, all remaining
    higher rates are skipped.

    :param prev_strategy: Previously completed strategy instance
    :param prev_benchmark: Benchmark results from previous strategy execution
    :return: Next strategy in sweep sequence, or None if complete
    :raises ValueError: If strategy_type is neither 'constant' nor 'poisson'
    """
    if prev_strategy is None:
        return SynchronousStrategy()

    if prev_strategy.type_ == "synchronous":
        self.synchronous_rate = prev_benchmark.request_throughput.successful.mean

        return ThroughputStrategy(
            max_concurrency=self.args.max_concurrency,
            rampup_duration=self.args.rampup_duration,
        )

    if prev_strategy.type_ == "throughput":
        self.throughput_rate = prev_benchmark.request_throughput.successful.mean
        if self.synchronous_rate <= 0 and self.throughput_rate <= 0:
            raise RuntimeError(
                "Invalid rates in sweep; aborting. "
                "Were there any successful requests?"
            )
        self.measured_rates = list(
            np.linspace(
                self.synchronous_rate,
                self.throughput_rate,
                self.args.sweep_size - 1,
            )
        )[1:]  # don't rerun synchronous

    # Stop escalation if a constraint with stopping_scope='all' triggered
    # during the async phase. Throughput is excluded because it intentionally
    # pushes beyond sustainable load. Synchronous never reaches here.
    if (
        prev_strategy.type_ != "throughput"
        and self._should_stop_escalating(prev_benchmark)  # type: ignore[arg-type]
    ):
        return None

    next_index = (
        len(self.completed_strategies) - 1 - 1
    )  # subtract synchronous and throughput
    next_rate = (
        self.measured_rates[next_index]
        if next_index < len(self.measured_rates)
        else None
    )

    if next_rate is None or next_rate <= 0:
        # Stop if we don't have another valid rate to run
        return None

    if self.args.strategy_type == "constant":
        return AsyncConstantStrategy(
            rate=next_rate, max_concurrency=self.args.max_concurrency
        )
    if self.args.strategy_type == "poisson":
        return AsyncPoissonStrategy(
            rate=next_rate,
            max_concurrency=self.args.max_concurrency,
            random_seed=self.random_seed,
        )
    raise ValueError(f"Invalid strategy type: {self.args.strategy_type}")

SweepProfileArgs

Bases: ProfileArgs

Pydantic model for sweep profile creation arguments.

Source code in src/guidellm/benchmark/profiles/sweep.py
@ProfileArgs.register("sweep")
class SweepProfileArgs(ProfileArgs):
    """Pydantic model for sweep profile creation arguments."""

    kind: Literal["sweep"] = Field(
        default="sweep",
        description="Profile type discriminator for sweep scheduling",
    )
    sweep_size: int = Field(
        default=10,
        description="Number of strategies to generate for the sweep",
        ge=2,
    )
    strategy_type: Literal["constant", "poisson"] = Field(
        default="constant",
        description="Type of strategy to use for the asynchronous sweep",
    )
    max_concurrency: PositiveInt | None = Field(
        default=512,
        description="Maximum concurrent requests to schedule",
    )