even better more structured throughput scheduling
Validate Operations / validate-operations (push) Has been cancelled
Validate Operations / validate-operations (push) Has been cancelled
plot adjustments validation adjustments
This commit is contained in:
@@ -6,10 +6,14 @@ from math import ceil
|
||||
import matplotlib
|
||||
matplotlib.use("Agg")
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.colors import to_rgba
|
||||
from matplotlib.legend_handler import HandlerBase
|
||||
from matplotlib.lines import Line2D
|
||||
from matplotlib.patches import Patch
|
||||
from matplotlib.path import Path as PlotPath
|
||||
from matplotlib.patheffects import AbstractPathEffect
|
||||
from matplotlib.transforms import Affine2D, IdentityTransform, TransformedPath
|
||||
from matplotlib.text import Text
|
||||
from matplotlib.transforms import Affine2D, Bbox, IdentityTransform, TransformedPath
|
||||
|
||||
|
||||
PALETTE = {
|
||||
@@ -25,11 +29,12 @@ PALETTE = {
|
||||
TEXT_COLOR = "#222222"
|
||||
MATRIX_TEXT_COLOR = "#1D2530"
|
||||
ERROR_COLOR = PALETTE["red"][1]
|
||||
DAGGER_COLOR = "#AA1C1C"
|
||||
GRID_COLOR = "#E6E9EC"
|
||||
LINE_COLOR = "#C9CED3"
|
||||
BAND_COLOR = "#F7F9FA"
|
||||
COMPILER_STYLES = {"raptor": PALETTE["sage"], "pimcomp": PALETTE["sand"]}
|
||||
METRIC_STYLES = {"latency": PALETTE["blue"], "throughput": PALETTE["sage"]}
|
||||
ARCH_STYLES = {"arch-a": (*PALETTE["blue"], ""), "arch-b": (*PALETTE["sage"], "//")}
|
||||
NETWORK_STYLES = {"vgg8": PALETTE["blue"], "resnet18": PALETTE["sage"],
|
||||
"resnet34": PALETTE["sand"], "googlenet": PALETTE["purple"],
|
||||
"yolo11n": PALETTE["red"]}
|
||||
@@ -38,6 +43,8 @@ ARCH_NAMES = {"arch-a": "Arch-A", "arch-b": "Arch-B"}
|
||||
MODEL_NAMES = {"vgg8": "VGG-8", "resnet18": "ResNet-18", "resnet34": "ResNet-34",
|
||||
"googlenet": "GoogLeNet", "yolo11n": "YOLO11n"}
|
||||
MODE_NAMES = {"latency": "Single-inference", "throughput": "Throughput"}
|
||||
COMPACT_MODE_NAMES = {"latency": "Latency-mode", "throughput": "Throughput-mode"}
|
||||
_USE_COLORS = True
|
||||
|
||||
|
||||
class CenteredHatch(AbstractPathEffect):
|
||||
@@ -47,11 +54,14 @@ class CenteredHatch(AbstractPathEffect):
|
||||
hatch = gc.get_hatch() or ""
|
||||
base_gc = renderer.new_gc()
|
||||
base_gc.copy_properties(gc)
|
||||
base_gc.set_hatch("".join(symbol for symbol in hatch if symbol not in "./\\"))
|
||||
base_gc.set_hatch("".join(symbol for symbol in hatch if symbol not in "./\\") or None)
|
||||
renderer.draw_path(base_gc, tpath, affine, rgbFace)
|
||||
base_gc.restore()
|
||||
|
||||
bounds = affine.transform_path(tpath).get_extents()
|
||||
clip_bounds = bounds if gc.get_clip_rectangle() is None else Bbox.intersection(bounds, gc.get_clip_rectangle())
|
||||
if clip_bounds is None:
|
||||
return
|
||||
center_x, center_y = (bounds.x0 + bounds.x1) / 2, (bounds.y0 + bounds.y1) / 2
|
||||
vertices = []
|
||||
diagonal = 2 ** -0.5
|
||||
@@ -61,13 +71,16 @@ class CenteredHatch(AbstractPathEffect):
|
||||
continue
|
||||
spacing = renderer.points_to_pixels(12 / count)
|
||||
half_span = (abs(dy) * bounds.width + abs(dx) * bounds.height) / 2
|
||||
half_length = (abs(dx) * bounds.width + abs(dy) * bounds.height) / 2
|
||||
extent = ceil(half_span / spacing)
|
||||
for step in range(-extent, extent + 1):
|
||||
x = center_x - dy * step * spacing
|
||||
y = center_y + dx * step * spacing
|
||||
vertices.extend(((x - dx * half_length, y - dy * half_length),
|
||||
(x + dx * half_length, y + dy * half_length)))
|
||||
y_limits = sorted(((clip_bounds.y0 - y) / dy, (clip_bounds.y1 - y) / dy))
|
||||
start = max((clip_bounds.x0 - x) / dx, y_limits[0])
|
||||
end = min((clip_bounds.x1 - x) / dx, y_limits[1])
|
||||
if start < end:
|
||||
vertices.extend(((x + dx * start, y + dy * start),
|
||||
(x + dx * end, y + dy * end)))
|
||||
line_gc = renderer.new_gc()
|
||||
line_gc.copy_properties(gc)
|
||||
line_gc.set_hatch(None)
|
||||
@@ -81,17 +94,22 @@ class CenteredHatch(AbstractPathEffect):
|
||||
spacing = renderer.points_to_pixels(12 / hatch.count("."))
|
||||
x_extent = ceil(bounds.width / (2 * spacing))
|
||||
y_extent = ceil(bounds.height / (2 * spacing))
|
||||
centers = [(center_x + x * spacing, center_y + y * spacing)
|
||||
for x in range(-x_extent, x_extent + 1)
|
||||
for y in range(-y_extent, y_extent + 1)]
|
||||
# Trim geometry too: SVG importers can discard clip paths.
|
||||
dots = [PlotPath.unit_circle().transformed(
|
||||
Affine2D().scale(renderer.points_to_pixels(0.9)).translate(
|
||||
center_x + x * spacing, center_y + y * spacing)).clip_to_bbox(clip_bounds)
|
||||
for x in range(-x_extent, x_extent + 1)
|
||||
for y in range(-y_extent, y_extent + 1)]
|
||||
line_gc.set_linewidth(0)
|
||||
renderer.draw_markers(line_gc, PlotPath.unit_circle(),
|
||||
Affine2D().scale(renderer.points_to_pixels(0.9)),
|
||||
PlotPath(centers), IdentityTransform(), gc.get_hatch_color())
|
||||
renderer.draw_path(line_gc, PlotPath.make_compound_path(*dots),
|
||||
IdentityTransform(), gc.get_hatch_color())
|
||||
line_gc.restore()
|
||||
|
||||
|
||||
def configure_style() -> None:
|
||||
def configure_style(*, colors: bool = True, anonymize: bool = False) -> None:
|
||||
global _USE_COLORS
|
||||
_USE_COLORS = colors
|
||||
COMPILER_NAMES["raptor"] = "Anonimized" if anonymize else "Raptor"
|
||||
plt.rcParams.update({
|
||||
"font.family": "DejaVu Sans",
|
||||
"axes.spines.top": False,
|
||||
@@ -109,6 +127,14 @@ def configure_style() -> None:
|
||||
})
|
||||
|
||||
|
||||
class TextLegendHandler(HandlerBase):
|
||||
def create_artists(self, legend, original, xdescent, ydescent, width, height, fontsize, transform):
|
||||
return [Text(width / 2 - xdescent, height / 2 - ydescent, original.get_text(),
|
||||
color=original.get_color(), fontproperties=original.get_fontproperties(),
|
||||
math_fontfamily=original.get_math_fontfamily(), ha="center", va="center",
|
||||
transform=transform)]
|
||||
|
||||
|
||||
def add_legend(fig, handles) -> None:
|
||||
"""Use the fewest balanced legend rows that fit the figure width."""
|
||||
renderer = fig.canvas.get_renderer()
|
||||
@@ -123,6 +149,7 @@ def add_legend(fig, handles) -> None:
|
||||
ordered = [handle for column in zip(*grid) for handle in column]
|
||||
legend = fig.legend(handles=[handle if handle is not None else Patch(visible=False) for handle in ordered],
|
||||
labels=[handle.get_label() if handle is not None else "" for handle in ordered],
|
||||
handler_map={Text: TextLegendHandler()},
|
||||
loc="outside upper center", ncols=columns, frameon=False, fontsize=9.5,
|
||||
handlelength=1.7, columnspacing=1.5, handletextpad=0.6, labelspacing=0.4)
|
||||
for handle, patch, text in zip(ordered, legend.legend_handles, legend.get_texts()):
|
||||
@@ -197,7 +224,7 @@ def fit_bar_widths(fig) -> None:
|
||||
fig.set_size_inches(fig.get_figwidth() * scale, fig.get_figheight())
|
||||
|
||||
|
||||
def fit_value_labels(fig) -> None:
|
||||
def fit_value_labels(fig, *, lower_limit: float | None = None) -> None:
|
||||
"""Fit common vertical limits to the data and rendered value labels."""
|
||||
for ax in fig.axes:
|
||||
for text in ax.texts:
|
||||
@@ -215,18 +242,39 @@ def fit_value_labels(fig) -> None:
|
||||
labels.append((y, max(0, anchor - extent.y0 + 3) / bounds.height,
|
||||
max(0, extent.y1 - anchor + 3) / bounds.height))
|
||||
limits.append(ax.dataLim.intervaly if ax.has_data() else ax.get_ylim())
|
||||
lower = min(0, *(lower for lower, _ in limits))
|
||||
lower = min(0, *(lower for lower, _ in limits)) if lower_limit is None else lower_limit
|
||||
upper = max(0, *(upper for _, upper in limits))
|
||||
if lower == upper:
|
||||
upper = lower + 1
|
||||
for _ in range(12):
|
||||
lower = min([lower, *((y - below * upper) / (1 - below) for y, below, _ in labels)])
|
||||
if lower_limit is None:
|
||||
lower = min([lower, *((y - below * upper) / (1 - below) for y, below, _ in labels)])
|
||||
upper = max([upper, *((y - above * lower) / (1 - above) for y, _, above in labels)])
|
||||
for ax in fig.axes:
|
||||
ax.set_ylim(lower, upper)
|
||||
|
||||
|
||||
def grayscale_figure(fig) -> None:
|
||||
def gray(color):
|
||||
red, green, blue, alpha = to_rgba(color)
|
||||
level = 0.2126 * red + 0.7152 * green + 0.0722 * blue
|
||||
return level, level, level, alpha
|
||||
|
||||
for artist in fig.findobj():
|
||||
if isinstance(artist, Patch):
|
||||
artist.set_facecolor(gray(artist.get_facecolor()))
|
||||
artist.set_edgecolor(gray(artist.get_edgecolor()))
|
||||
elif isinstance(artist, Line2D):
|
||||
artist.set_color(gray(artist.get_color()))
|
||||
artist.set_markerfacecolor(gray(artist.get_markerfacecolor()))
|
||||
artist.set_markeredgecolor(gray(artist.get_markeredgecolor()))
|
||||
elif isinstance(artist, Text):
|
||||
artist.set_color(gray(artist.get_color()))
|
||||
|
||||
|
||||
def save_figure(fig, basename: str, output_dir: Path) -> None:
|
||||
if not _USE_COLORS:
|
||||
grayscale_figure(fig)
|
||||
for patch in fig.findobj(Patch):
|
||||
hatch = patch.get_hatch() or ""
|
||||
if any(symbol in hatch for symbol in "./\\"):
|
||||
|
||||
Reference in New Issue
Block a user