Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ explicit = true

[tool.uv.sources]
torch = { index = "pytorch-cpu" }
guidellm = { workspace = true }

# ************************************************
# ********** Project Metadata **********
Expand Down Expand Up @@ -75,8 +76,8 @@ dependencies = [

[project.optional-dependencies]
# Meta Extras
all = ["guidellm[perf,tokenizers,audio,vision]"]
recommended = ["guidellm[perf,tokenizers]"]
all = ["guidellm[perf,tokenizers,audio,vision,ui]"]
recommended = ["guidellm[perf,tokenizers,ui]"]
# Feature Extras
perf = ["orjson", "msgpack", "msgspec", "uvloop"]
tokenizers = ["tiktoken", "blobfile", "mistral-common"]
Expand Down Expand Up @@ -137,6 +138,9 @@ dev = [
# link checking
"mkdocs-linkcheck~=1.0.6",
]
ui = [
"plotly>=5.24.0",
]

[dependency-groups]
dev = ["guidellm[dev]"]
Expand Down
6 changes: 3 additions & 3 deletions src/guidellm/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ def benchmark():
default=BenchmarkGenerativeTextArgs.get_default("outputs"),
help=(
"The filename.ext for each of the outputs to create or the "
"alises (json, csv) for the output files to create with "
"alises (json, csv, html) for the output files to create with "
"their default file names (benchmark.[EXT])"
),
)
Expand Down Expand Up @@ -512,8 +512,8 @@ def run(**kwargs): # noqa: C901
"--output-formats",
multiple=True,
type=str,
default=("console", "json"),
help="Output formats for benchmark results (e.g., console, json, csv).",
default=("console", "json"), # ("console", "json", "html", "csv")
help="Output formats for benchmark results (e.g., console, json, html, csv).",
)
def from_file(path, output_path, output_formats):
asyncio.run(reimport_benchmarks_report(path, output_path, output_formats))
Expand Down
2 changes: 2 additions & 0 deletions src/guidellm/benchmark/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from .outputs import (
GenerativeBenchmarkerConsole,
GenerativeBenchmarkerCSV,
GenerativeBenchmarkerHTML,
GenerativeBenchmarkerOutput,
)
from .profiles import (
Expand Down Expand Up @@ -71,6 +72,7 @@
"GenerativeBenchmarkTimings",
"GenerativeBenchmarkerCSV",
"GenerativeBenchmarkerConsole",
"GenerativeBenchmarkerHTML",
"GenerativeBenchmarkerOutput",
"GenerativeBenchmarksReport",
"GenerativeConsoleBenchmarkerProgress",
Expand Down
2 changes: 1 addition & 1 deletion src/guidellm/benchmark/entrypoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,7 @@ async def benchmark_generative_text(
async def reimport_benchmarks_report(
file: Path,
output_path: Path | None,
output_formats: OutputFormatT = ("console", "json", "csv"),
output_formats: OutputFormatT = ("console", "json", "html", "csv"),
) -> tuple[GenerativeBenchmarksReport, dict[str, Any]]:
"""
Load and re-export an existing benchmarks report in specified output formats.
Expand Down
4 changes: 3 additions & 1 deletion src/guidellm/benchmark/outputs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Output formatters for benchmark results.

Provides output formatter implementations that transform benchmark reports into
various file formats including JSON, CSV, and console display. All formatters
various file formats including JSON, CSV, HTML, and console display. All formatters
extend the base GenerativeBenchmarkerOutput interface, enabling dynamic resolution
and flexible output configuration for benchmark result persistence and analysis.
"""
Expand All @@ -11,12 +11,14 @@

from .console import GenerativeBenchmarkerConsole
from .csv import GenerativeBenchmarkerCSV
from .html import GenerativeBenchmarkerHTML
from .output import GenerativeBenchmarkerOutput
from .serialized import GenerativeBenchmarkerSerialized

__all__ = [
"GenerativeBenchmarkerCSV",
"GenerativeBenchmarkerConsole",
"GenerativeBenchmarkerHTML",
"GenerativeBenchmarkerOutput",
"GenerativeBenchmarkerSerialized",
]
25 changes: 25 additions & 0 deletions src/guidellm/benchmark/outputs/html/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""Plotly-based HTML output generation for GuideLLM benchmarks."""

from guidellm.benchmark.outputs.html.data_builder import (
Bucket,
TabularDistributionSummary,
build_benchmarks,
build_run_info,
build_ui_data,
build_workload_details,
)
from guidellm.benchmark.outputs.html.plotly_output import (
GenerativeBenchmarkerHTML,
)
from guidellm.benchmark.outputs.html.theme import PlotlyTheme

__all__ = [
"Bucket",
"GenerativeBenchmarkerHTML",
"PlotlyTheme",
"TabularDistributionSummary",
"build_benchmarks",
"build_run_info",
"build_ui_data",
"build_workload_details",
]
5 changes: 5 additions & 0 deletions src/guidellm/benchmark/outputs/html/components/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""HTML component generators for Plotly-based reports."""

from guidellm.benchmark.outputs.html.components.base import PlotlyComponentBase

__all__ = ["PlotlyComponentBase"]
60 changes: 60 additions & 0 deletions src/guidellm/benchmark/outputs/html/components/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""Base classes for HTML component generation."""

from abc import ABC, abstractmethod
from typing import Any

import plotly.graph_objects as go

from guidellm.benchmark.outputs.html.theme import PlotlyTheme


class PlotlyComponentBase(ABC):
"""Abstract base class for Plotly-based HTML components."""

def __init__(self, theme: PlotlyTheme | None = None):
"""Initialize the component.

Args:
theme: Optional PlotlyTheme instance. If None, uses default theme.
"""
self.theme = theme or PlotlyTheme()

@abstractmethod
def generate(self, data: dict[str, Any]) -> str | go.Figure:
"""Generate the component output.

Args:
data: Data dictionary containing component-specific data.

Returns:
Either an HTML string or a Plotly Figure object.
"""
...

def _apply_theme_to_figure(self, fig: go.Figure) -> go.Figure:
"""Apply theme to a Plotly figure.

Args:
fig: Plotly figure to style.

Returns:
Styled figure.
"""
layout_updates = self.theme.get_base_layout()
fig.update_layout(**layout_updates)
return fig

def _create_figure(self, **layout_kwargs: Any) -> go.Figure:
"""Create a new figure with theme applied.

Args:
**layout_kwargs: Additional layout parameters to merge with theme.

Returns:
New Plotly figure with theme applied.
"""
fig = go.Figure()
base_layout = self.theme.get_base_layout()
base_layout.update(layout_kwargs)
fig.update_layout(**base_layout)
return fig
32 changes: 32 additions & 0 deletions src/guidellm/benchmark/outputs/html/components/footer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""Footer component for HTML reports."""

from typing import Any

from guidellm.benchmark.outputs.html.components.base import PlotlyComponentBase


class FooterComponent(PlotlyComponentBase):
"""Generates the page footer."""

def generate(self, _data: dict[str, Any] | None = None) -> str:
"""Generate footer HTML.

Args:
_data: Optional data dictionary (not used for footer).

Returns:
HTML string for the footer section.
"""
return """
<div class="footer">
<p>
Generated by <a
href="https://github.com/vllm-project/guidellm"
target="_blank">GuideLLM</a>
- Guidance Platform for Deploying and Managing Large Language Models
</p>
<p style="margin-top: 0.5rem; font-size: 0.75rem;">
Powered by <a href="https://plotly.com/" target="_blank">Plotly</a>
</p>
</div>
"""
59 changes: 59 additions & 0 deletions src/guidellm/benchmark/outputs/html/components/header.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""Header component for HTML reports."""

from typing import Any

from guidellm.benchmark.outputs.html.components.base import PlotlyComponentBase
from guidellm.utils.functions import safe_format_timestamp


class HeaderComponent(PlotlyComponentBase):
"""Generates the page header with run metadata."""

def generate(self, data: dict[str, Any]) -> str:
"""Generate header HTML.

Args:
data: Dictionary containing:
- model: Dict with 'name' key
- timestamp: ISO format timestamp or datetime
- dataset: Dict with 'name' key (optional)
- task: Task name (optional)

Returns:
HTML string for the header section.
"""
model_name = data.get("model", {}).get("name", "N/A")
timestamp = safe_format_timestamp(
data.get("timestamp"),
format_="%B %d %Y at %H:%M:%S",
)
dataset_name = data.get("dataset", {}).get("name", "N/A")
task = data.get("task", "N/A")

return f"""
<div class="header">
<h1>GuideLLM Benchmark Report</h1>
<div class="info-row">
<div class="info-item">
<div class="info-label">Model</div>
<div class="info-value">{model_name}</div>
</div>
<div class="info-item">
<div class="info-label">Timestamp</div>
<div class="info-value">{timestamp}</div>
</div>
<div class="info-item">
<div class="info-label">Dataset</div>
<div class="info-value">{dataset_name}</div>
</div>
{
f'''<div class="info-item">
<div class="info-label">Task</div>
<div class="info-value">{task}</div>
</div>'''
if task != "N/A"
else ""
}
</div>
</div>
"""
Loading
Loading