Skip to content

guidellm.benchmark.outputs.plot

Plot output formatter for benchmark results.

This module provides the GenerativeBenchmarkerPlot class which exports benchmark reports to a static PNG image format containing comprehensive performance visualization charts.

GenerativeBenchmarkerPlot

Bases: GenerativeBenchmarkerOutput

Plot output formatter for benchmark results.

Generates a high-quality dashboard visualization of LLM benchmark results, enforcing light theme and saving to a PNG image file.

Source code in src/guidellm/benchmark/outputs/plot.py
@GenerativeBenchmarkerOutput.register("plot")
class GenerativeBenchmarkerPlot(GenerativeBenchmarkerOutput):
    """
    Plot output formatter for benchmark results.

    Generates a high-quality dashboard visualization of LLM benchmark results,
    enforcing light theme and saving to a PNG image file.
    """

    output_path: Path = Field(
        default_factory=Path.cwd,
        description=(
            "Path where the PNG plot file will be saved, defaults to current directory"
        ),
    )
    dpi: int = Field(
        default=100,
        description="Resolution of the output image in Dots Per Inch.",
    )

    @classmethod
    def from_args(cls, args: BenchmarkOutputArgs) -> GenerativeBenchmarkerPlot:
        """
        Create a plot output formatter from output arguments.

        :param args: Output configuration with path and dpi
        :return: Configured plot output formatter
        """
        if not isinstance(args, PlotBenchmarkOutputArgs):
            raise ValueError(f"Expected PlotBenchmarkOutputArgs, got {type(args)}")

        return cls(output_path=args.path, dpi=args.dpi)

    async def finalize(self, report: GenerativeBenchmarksReport) -> Path:
        """
        Save the benchmark report as a static PNG plot visualization.

        :param report: The completed benchmark report
        :return: Path to the saved PNG plot file
        """
        output_path = self.output_path
        if output_path.is_dir():
            output_path = output_path / "benchmarks.png"

        output_path.parent.mkdir(parents=True, exist_ok=True)

        if not report.benchmarks:
            fig, ax = plot.plt.subplots(figsize=(10, 8), facecolor="white")
            ax.text(0.5, 0.5, "No benchmark data available", fontsize=14, ha="center")
            ax.set_facecolor("white")
            plot.plt.savefig(
                output_path, facecolor="white", bbox_inches="tight", dpi=self.dpi
            )
            plot.plt.close(fig)
            return output_path

        points = _build_points(report.benchmarks)

        _set_plot_theme(self.dpi)

        fig, axs = plot.plt.subplots(4, 2, figsize=(16, 22), facecolor="white")
        fig.suptitle(
            "GuideLLM Benchmark Performance Visualization",
            fontsize=18,
            color="black",
            weight="bold",
            y=0.985,
        )

        for ax in axs.flat:
            _style_axis(ax)

        for ax, plot_function in zip(axs.flat, _PLOT_FUNCTIONS, strict=True):
            plot_function(ax, points)

        fig.align_ylabels(axs[:, 0])
        fig.align_ylabels(axs[:, 1])
        plot.plt.tight_layout(rect=(0.04, 0.03, 0.96, 0.965), h_pad=3.0, w_pad=2.5)
        fig.savefig(output_path, facecolor="white", bbox_inches="tight", dpi=self.dpi)
        plot.plt.close(fig)

        return output_path

finalize(report) async

Save the benchmark report as a static PNG plot visualization.

Parameters:

Name Type Description Default
report GenerativeBenchmarksReport

The completed benchmark report

required

Returns:

Type Description
Path

Path to the saved PNG plot file

Source code in src/guidellm/benchmark/outputs/plot.py
async def finalize(self, report: GenerativeBenchmarksReport) -> Path:
    """
    Save the benchmark report as a static PNG plot visualization.

    :param report: The completed benchmark report
    :return: Path to the saved PNG plot file
    """
    output_path = self.output_path
    if output_path.is_dir():
        output_path = output_path / "benchmarks.png"

    output_path.parent.mkdir(parents=True, exist_ok=True)

    if not report.benchmarks:
        fig, ax = plot.plt.subplots(figsize=(10, 8), facecolor="white")
        ax.text(0.5, 0.5, "No benchmark data available", fontsize=14, ha="center")
        ax.set_facecolor("white")
        plot.plt.savefig(
            output_path, facecolor="white", bbox_inches="tight", dpi=self.dpi
        )
        plot.plt.close(fig)
        return output_path

    points = _build_points(report.benchmarks)

    _set_plot_theme(self.dpi)

    fig, axs = plot.plt.subplots(4, 2, figsize=(16, 22), facecolor="white")
    fig.suptitle(
        "GuideLLM Benchmark Performance Visualization",
        fontsize=18,
        color="black",
        weight="bold",
        y=0.985,
    )

    for ax in axs.flat:
        _style_axis(ax)

    for ax, plot_function in zip(axs.flat, _PLOT_FUNCTIONS, strict=True):
        plot_function(ax, points)

    fig.align_ylabels(axs[:, 0])
    fig.align_ylabels(axs[:, 1])
    plot.plt.tight_layout(rect=(0.04, 0.03, 0.96, 0.965), h_pad=3.0, w_pad=2.5)
    fig.savefig(output_path, facecolor="white", bbox_inches="tight", dpi=self.dpi)
    plot.plt.close(fig)

    return output_path

from_args(args) classmethod

Create a plot output formatter from output arguments.

Parameters:

Name Type Description Default
args BenchmarkOutputArgs

Output configuration with path and dpi

required

Returns:

Type Description
GenerativeBenchmarkerPlot

Configured plot output formatter

Source code in src/guidellm/benchmark/outputs/plot.py
@classmethod
def from_args(cls, args: BenchmarkOutputArgs) -> GenerativeBenchmarkerPlot:
    """
    Create a plot output formatter from output arguments.

    :param args: Output configuration with path and dpi
    :return: Configured plot output formatter
    """
    if not isinstance(args, PlotBenchmarkOutputArgs):
        raise ValueError(f"Expected PlotBenchmarkOutputArgs, got {type(args)}")

    return cls(output_path=args.path, dpi=args.dpi)

PlotBenchmarkOutputArgs

Bases: BenchmarkOutputArgs

Model for Plot benchmark output arguments.

Defines parameters for generating static image visualizations, enforcing image output suffix.

Source code in src/guidellm/benchmark/outputs/plot.py
@BenchmarkOutputArgs.register("plot")
class PlotBenchmarkOutputArgs(BenchmarkOutputArgs):
    """Model for Plot benchmark output arguments.

    Defines parameters for generating static image visualizations, enforcing
    image output suffix.
    """

    kind: Literal["plot"] = Field(
        default="plot",
        description="Type identifier for the plot configuration.",
    )
    path: Path = Field(
        default_factory=lambda: settings.default_results_dir / "benchmarks.png",
        description="The file to save the output plot to.",
    )
    dpi: int = Field(
        default=100,
        description="Resolution of the output image in Dots Per Inch.",
    )

    @field_validator("path", mode="after")
    @classmethod
    def validate_plot_suffix(cls, v: Path) -> Path:
        """Ensures the output file path ends with a supported plotting format extension.

        If the suffix is missing, it defaults to .png.
        If an unsupported suffix is provided, it raises a ValueError.
        """
        if not v.suffix:
            return v.with_suffix(".png")
        suffix = v.suffix.lower()
        if suffix in _ALLOWED_PLOT_SUFFIXES:
            return v
        raise ValueError(
            f"Plot output type {suffix} is not supported: valid types are "
            f"{', '.join(sorted(_ALLOWED_PLOT_SUFFIXES))}"
        )

validate_plot_suffix(v) classmethod

Ensures the output file path ends with a supported plotting format extension.

If the suffix is missing, it defaults to .png. If an unsupported suffix is provided, it raises a ValueError.

Source code in src/guidellm/benchmark/outputs/plot.py
@field_validator("path", mode="after")
@classmethod
def validate_plot_suffix(cls, v: Path) -> Path:
    """Ensures the output file path ends with a supported plotting format extension.

    If the suffix is missing, it defaults to .png.
    If an unsupported suffix is provided, it raises a ValueError.
    """
    if not v.suffix:
        return v.with_suffix(".png")
    suffix = v.suffix.lower()
    if suffix in _ALLOWED_PLOT_SUFFIXES:
        return v
    raise ValueError(
        f"Plot output type {suffix} is not supported: valid types are "
        f"{', '.join(sorted(_ALLOWED_PLOT_SUFFIXES))}"
    )

plot_generation_latency_vs_request_rate(ax, points)

Plot per-token generation latency against observed request rate.

Parameters:

Name Type Description Default
ax Axes

Matplotlib axis to draw on

required
points Sequence[_BenchmarkPoint]

Benchmark points extracted from the report

required
Source code in src/guidellm/benchmark/outputs/plot.py
def plot_generation_latency_vs_request_rate(
    ax: plot.Axes,
    points: Sequence[_BenchmarkPoint],
) -> None:
    """
    Plot per-token generation latency against observed request rate.

    :param ax: Matplotlib axis to draw on
    :param points: Benchmark points extracted from the report
    """
    sorted_points = _sort_by_request_rate(points)
    request_rate = _series(sorted_points, lambda point: point.request_rate)

    _plot_line(
        ax,
        request_rate,
        _series(sorted_points, lambda point: point.tpot_median_ms),
        log_x=True,
        log_y=True,
        marker="o",
        label="TPOT Median",
        color=_PURPLE,
        linewidth=2,
    )
    _plot_line(
        ax,
        request_rate,
        _series(sorted_points, lambda point: point.tpot_p95_ms),
        log_x=True,
        log_y=True,
        marker="^",
        linestyle="--",
        label="TPOT p95",
        color=_PURPLE,
        alpha=0.7,
    )
    _plot_line(
        ax,
        request_rate,
        _series(sorted_points, lambda point: point.itl_median_ms),
        log_x=True,
        log_y=True,
        marker="s",
        label="ITL Median",
        color=_GREEN,
        linewidth=2,
    )
    _plot_line(
        ax,
        request_rate,
        _series(sorted_points, lambda point: point.itl_p95_ms),
        log_x=True,
        log_y=True,
        marker="d",
        linestyle="--",
        label="ITL p95",
        color=_GREEN,
        alpha=0.7,
    )
    _set_log_scale(ax, "x", request_rate)
    _set_log_scale(ax, "y", _series(sorted_points, lambda point: point.tpot_p95_ms))
    _set_title(
        ax,
        "2. Generation Speed (TPOT & ITL) vs. Request Rate\n(lower is better)",
    )
    ax.set_xlabel("Request Rate (RPS, Log Scale)")
    ax.set_ylabel("Latency (ms/token, Log Scale)")
    _show_legend(ax)

plot_latency_breakdown_at_peak_throughput(ax, points)

Plot latency component medians and p95s for the highest-throughput benchmark.

Parameters:

Name Type Description Default
ax Axes

Matplotlib axis to draw on

required
points Sequence[_BenchmarkPoint]

Benchmark points extracted from the report

required
Source code in src/guidellm/benchmark/outputs/plot.py
def plot_latency_breakdown_at_peak_throughput(
    ax: plot.Axes,
    points: Sequence[_BenchmarkPoint],
) -> None:
    """
    Plot latency component medians and p95s for the highest-throughput benchmark.

    :param ax: Matplotlib axis to draw on
    :param points: Benchmark points extracted from the report
    """
    peak_point = max(points, key=lambda point: point.total_tps)
    labels = ["TTFT", "TTFOT", "ITL", "TPOT", "E2E"]
    medians = [
        peak_point.ttft_median_ms,
        peak_point.ttfo_median_ms,
        peak_point.itl_median_ms,
        peak_point.tpot_median_ms,
        peak_point.e2e_median_ms,
    ]
    p95s = [
        peak_point.ttft_p95_ms,
        peak_point.ttfo_p95_ms,
        peak_point.itl_p95_ms,
        peak_point.tpot_p95_ms,
        peak_point.e2e_p95_ms,
    ]
    x_positions = list(range(len(labels)))
    bar_width = 0.38

    ax.bar(
        [position - bar_width / 2 for position in x_positions],
        medians,
        width=bar_width,
        color=_BLUE,
        label="Median",
    )
    ax.bar(
        [position + bar_width / 2 for position in x_positions],
        p95s,
        width=bar_width,
        color=_RED,
        alpha=0.8,
        label="p95",
    )
    _set_log_scale(ax, "y", p95s)
    ax.set_xticks(x_positions)
    ax.set_xticklabels(labels)
    _set_title(
        ax,
        "6. Latency Breakdown at Peak Throughput\n(lower is better)",
    )
    ax.set_xlabel("Latency Component")
    ax.set_ylabel("Latency (ms, Log Scale)")
    _show_legend(ax)

plot_latency_vs_request_rate(ax, points)

Plot TTFT and end-to-end latency against observed request rate.

Parameters:

Name Type Description Default
ax Axes

Matplotlib axis to draw on

required
points Sequence[_BenchmarkPoint]

Benchmark points extracted from the report

required
Source code in src/guidellm/benchmark/outputs/plot.py
def plot_latency_vs_request_rate(
    ax: plot.Axes,
    points: Sequence[_BenchmarkPoint],
) -> None:
    """
    Plot TTFT and end-to-end latency against observed request rate.

    :param ax: Matplotlib axis to draw on
    :param points: Benchmark points extracted from the report
    """
    sorted_points = _sort_by_request_rate(points)
    request_rate = _series(sorted_points, lambda point: point.request_rate)

    _plot_line(
        ax,
        request_rate,
        _series(sorted_points, lambda point: point.ttft_median_ms),
        log_x=True,
        log_y=True,
        marker="o",
        label="TTFT Median",
        color=_BLUE,
        linewidth=2,
    )
    _plot_line(
        ax,
        request_rate,
        _series(sorted_points, lambda point: point.ttft_p95_ms),
        log_x=True,
        log_y=True,
        marker="^",
        linestyle="--",
        label="TTFT p95",
        color=_BLUE,
        alpha=0.7,
    )
    _plot_line(
        ax,
        request_rate,
        _series(sorted_points, lambda point: point.e2e_median_ms),
        log_x=True,
        log_y=True,
        marker="s",
        label="E2E Median",
        color=_RED,
        linewidth=2,
    )
    _plot_line(
        ax,
        request_rate,
        _series(sorted_points, lambda point: point.e2e_p95_ms),
        log_x=True,
        log_y=True,
        marker="d",
        linestyle="--",
        label="E2E p95",
        color=_RED,
        alpha=0.7,
    )
    _set_log_scale(ax, "x", request_rate)
    _set_log_scale(ax, "y", _series(sorted_points, lambda point: point.e2e_p95_ms))
    _set_title(ax, "1. Latency (TTFT & E2E) vs. Request Rate\n(lower is better)")
    ax.set_xlabel("Request Rate (RPS, Log Scale)")
    ax.set_ylabel("Latency (ms, Log Scale)")
    _show_legend(ax)

plot_latency_vs_throughput(ax, points)

Plot first-token latency against token throughput to expose the saturation knee.

Parameters:

Name Type Description Default
ax Axes

Matplotlib axis to draw on

required
points Sequence[_BenchmarkPoint]

Benchmark points extracted from the report

required
Source code in src/guidellm/benchmark/outputs/plot.py
def plot_latency_vs_throughput(
    ax: plot.Axes, points: Sequence[_BenchmarkPoint]
) -> None:
    """
    Plot first-token latency against token throughput to expose the saturation knee.

    :param ax: Matplotlib axis to draw on
    :param points: Benchmark points extracted from the report
    """
    sorted_points = _sort_by_throughput(points)
    throughput = _series(sorted_points, lambda point: point.total_tps)

    _plot_line(
        ax,
        throughput,
        _series(sorted_points, lambda point: point.ttft_p95_ms),
        log_y=True,
        marker="o",
        color=_RED,
        linewidth=2,
        label="p95 TTFT",
    )
    _plot_line(
        ax,
        throughput,
        _series(sorted_points, lambda point: point.ttft_median_ms),
        log_y=True,
        marker="s",
        color=_BLUE,
        linewidth=2,
        label="Median TTFT",
    )
    _set_log_scale(ax, "y", _series(sorted_points, lambda point: point.ttft_p95_ms))
    _set_title(
        ax,
        "4. Knee Plot: Latency vs. Throughput\n"
        "(lower latency + higher throughput is better)",
    )
    ax.set_xlabel("Throughput (Total Tokens/sec)")
    ax.set_ylabel("Latency (ms)")
    _show_legend(ax)

plot_request_status_counts(ax, points)

Plot request status counts by concurrency level.

Parameters:

Name Type Description Default
ax Axes

Matplotlib axis to draw on

required
points Sequence[_BenchmarkPoint]

Benchmark points extracted from the report

required
Source code in src/guidellm/benchmark/outputs/plot.py
def plot_request_status_counts(
    ax: plot.Axes, points: Sequence[_BenchmarkPoint]
) -> None:
    """
    Plot request status counts by concurrency level.

    :param ax: Matplotlib axis to draw on
    :param points: Benchmark points extracted from the report
    """
    sorted_points = _sort_by_concurrency(points)
    x_positions = list(range(len(sorted_points)))
    successful = _series(sorted_points, lambda point: point.successful_requests)
    incomplete = _series(sorted_points, lambda point: point.incomplete_requests)
    errored = _series(sorted_points, lambda point: point.errored_requests)

    ax.bar(x_positions, successful, color=_BLUE, label="Successful")
    ax.bar(
        x_positions, incomplete, bottom=successful, color=_ORANGE, label="Incomplete"
    )
    errored_bottom = [
        successful_count + incomplete_count
        for successful_count, incomplete_count in zip(
            successful, incomplete, strict=True
        )
    ]
    ax.bar(x_positions, errored, bottom=errored_bottom, color=_RED, label="Errored")
    ax.set_xticks(x_positions)
    ax.set_xticklabels(
        [
            _label_for_concurrency(point, index)
            for index, point in enumerate(sorted_points)
        ],
        rotation=35,
        ha="right",
    )
    _set_title(
        ax, "5. Request Status Counts vs. Concurrency\n(more complete is better)"
    )
    ax.set_xlabel("Concurrency (Active Requests)")
    ax.set_ylabel("Requests")
    _show_legend(ax)

plot_throughput_efficiency_vs_concurrency(ax, points)

Plot total token throughput per concurrent request.

Parameters:

Name Type Description Default
ax Axes

Matplotlib axis to draw on

required
points Sequence[_BenchmarkPoint]

Benchmark points extracted from the report

required
Source code in src/guidellm/benchmark/outputs/plot.py
def plot_throughput_efficiency_vs_concurrency(
    ax: plot.Axes,
    points: Sequence[_BenchmarkPoint],
) -> None:
    """
    Plot total token throughput per concurrent request.

    :param ax: Matplotlib axis to draw on
    :param points: Benchmark points extracted from the report
    """
    sorted_points = _sort_by_concurrency(points)
    concurrency = _series(sorted_points, lambda point: point.concurrency)
    efficiency = [
        point.total_tps / point.concurrency if point.concurrency > 0.0 else 0.0
        for point in sorted_points
    ]

    _plot_line(
        ax,
        concurrency,
        efficiency,
        log_x=True,
        marker="o",
        color=_GREEN,
        linewidth=2,
        label="Tokens/s per Active Request",
    )
    _set_log_scale(ax, "x", concurrency)
    _set_title(
        ax,
        "7. Throughput Efficiency vs. Concurrency\n(higher is better)",
    )
    ax.set_xlabel("Concurrency (Active Requests, Log Scale)")
    ax.set_ylabel("Total Tokens/sec per Active Request")
    _show_legend(ax)

plot_token_throughput_mix_vs_request_rate(ax, points)

Plot input and output token throughput mix against request rate.

Parameters:

Name Type Description Default
ax Axes

Matplotlib axis to draw on

required
points Sequence[_BenchmarkPoint]

Benchmark points extracted from the report

required
Source code in src/guidellm/benchmark/outputs/plot.py
def plot_token_throughput_mix_vs_request_rate(
    ax: plot.Axes,
    points: Sequence[_BenchmarkPoint],
) -> None:
    """
    Plot input and output token throughput mix against request rate.

    :param ax: Matplotlib axis to draw on
    :param points: Benchmark points extracted from the report
    """
    sorted_points = _sort_by_request_rate(points)
    request_rate = _series(sorted_points, lambda point: point.request_rate)
    input_tps = _series(sorted_points, lambda point: point.input_tps)
    output_tps = _series(sorted_points, lambda point: point.output_tps)
    x_data, input_data = _filter_series(request_rate, input_tps, log_x=True)
    _x_data, output_data = _filter_series(request_rate, output_tps, log_x=True)

    if x_data and input_data and output_data:
        ax.stackplot(
            x_data,
            input_data,
            output_data,
            labels=["Input Tokens/s", "Output Tokens/s"],
            colors=[_BLUE, _PINK],
            alpha=0.75,
        )
        _plot_line(
            ax,
            request_rate,
            _series(sorted_points, lambda point: point.total_tps),
            log_x=True,
            color=_ORANGE,
            linewidth=2,
            label="Total Tokens/s",
        )

    _set_log_scale(ax, "x", request_rate)
    _set_title(
        ax,
        "8. Token Throughput Mix vs. Request Rate\n(higher is better)",
    )
    ax.set_xlabel("Request Rate (RPS, Log Scale)")
    ax.set_ylabel("Throughput (Tokens/sec)")
    _show_legend(ax)

plot_token_throughput_vs_concurrency(ax, points)

Plot total, input, and output token throughput against request concurrency.

Parameters:

Name Type Description Default
ax Axes

Matplotlib axis to draw on

required
points Sequence[_BenchmarkPoint]

Benchmark points extracted from the report

required
Source code in src/guidellm/benchmark/outputs/plot.py
def plot_token_throughput_vs_concurrency(
    ax: plot.Axes,
    points: Sequence[_BenchmarkPoint],
) -> None:
    """
    Plot total, input, and output token throughput against request concurrency.

    :param ax: Matplotlib axis to draw on
    :param points: Benchmark points extracted from the report
    """
    sorted_points = _sort_by_concurrency(points)
    concurrency = _series(sorted_points, lambda point: point.concurrency)

    _plot_line(
        ax,
        concurrency,
        _series(sorted_points, lambda point: point.total_tps),
        log_x=True,
        log_y=True,
        marker="o",
        label="Total Tokens/s",
        color=_ORANGE,
        linewidth=2.5,
    )
    _plot_line(
        ax,
        concurrency,
        _series(sorted_points, lambda point: point.input_tps),
        log_x=True,
        log_y=True,
        marker="s",
        label="Input Tokens/s",
        color=_BLUE,
        alpha=0.8,
    )
    _plot_line(
        ax,
        concurrency,
        _series(sorted_points, lambda point: point.output_tps),
        log_x=True,
        log_y=True,
        marker="d",
        label="Output Tokens/s",
        color=_PINK,
        alpha=0.8,
    )
    _set_log_scale(ax, "x", concurrency)
    _set_log_scale(ax, "y", _series(sorted_points, lambda point: point.total_tps))
    _set_title(ax, "3. Token Throughput vs. Concurrency\n(higher is better)")
    ax.set_xlabel("Concurrency (Active Requests, Log Scale)")
    ax.set_ylabel("Throughput (Tokens/sec, Log Scale)")
    _show_legend(ax)