chore(ci): add throughput and hpu support to data extractor

Now throughput results can be fetched.
HPU backend is supported for integer formatting
This commit is contained in:
David Testé
2025-10-27 12:59:42 +01:00
committed by David Testé
parent b02a3b16ff
commit fd6323b311
7 changed files with 166 additions and 43 deletions

View File

@@ -89,12 +89,12 @@ parser.add_argument(
dest="bench_type", dest="bench_type",
choices=["latency", "throughput"], choices=["latency", "throughput"],
default="latency", default="latency",
help="Compute and append number of operations per second and" help="Fetch results for latency or throughput benchmarks",
"operations per dollar",
) )
parser.add_argument( parser.add_argument(
"--backend", "--backend",
dest="backend", dest="backend",
choices=["cpu", "gpu", "hpu"],
default="cpu", default="cpu",
help="Backend on which benchmarks have run", help="Backend on which benchmarks have run",
) )

View File

@@ -10,6 +10,7 @@ class Backend(enum.StrEnum):
CPU = "cpu" CPU = "cpu"
GPU = "gpu" GPU = "gpu"
HPU = "hpu"
@staticmethod @staticmethod
def from_str(backend_name): def from_str(backend_name):
@@ -18,6 +19,8 @@ class Backend(enum.StrEnum):
return Backend.CPU return Backend.CPU
case "gpu": case "gpu":
return Backend.GPU return Backend.GPU
case "hpu":
return Backend.HPU
case _: case _:
raise NotImplementedError raise NotImplementedError
@@ -83,7 +86,7 @@ class CoreCryptoOperation(enum.StrEnum):
KeySwitch = "KS" KeySwitch = "KS"
PBS = "PBS" PBS = "PBS"
MultiBitPBS = "MB-PBS" MultiBitPBS = "MB-PBS"
KeyswitchPBS = "KS - PBS" KeySwitchPBS = "KS - PBS"
KeySwitchMultiBitPBS = "KS - MB-PBS" KeySwitchMultiBitPBS = "KS - MB-PBS"
@staticmethod @staticmethod
@@ -96,7 +99,7 @@ class CoreCryptoOperation(enum.StrEnum):
case "multi_bit_pbs" | "multi_bit_deterministic_pbs": case "multi_bit_pbs" | "multi_bit_deterministic_pbs":
return CoreCryptoOperation.MultiBitPBS return CoreCryptoOperation.MultiBitPBS
case "ks_pbs": case "ks_pbs":
return CoreCryptoOperation.KeyswitchPBS return CoreCryptoOperation.KeySwitchPBS
case "multi_bit_ks_pbs" | "multi_bit_deterministic_ks_pbs": case "multi_bit_ks_pbs" | "multi_bit_deterministic_ks_pbs":
return CoreCryptoOperation.KeySwitchMultiBitPBS return CoreCryptoOperation.KeySwitchMultiBitPBS
case _: case _:
@@ -119,7 +122,7 @@ class CoreCryptoOperation(enum.StrEnum):
return "pbs" return "pbs"
case CoreCryptoOperation.MultiBitPBS: case CoreCryptoOperation.MultiBitPBS:
return "pbs" return "pbs"
case CoreCryptoOperation.KeyswitchPBS: case CoreCryptoOperation.KeySwitchPBS:
return "ks-pbs" return "ks-pbs"
case CoreCryptoOperation.KeySwitchMultiBitPBS: case CoreCryptoOperation.KeySwitchMultiBitPBS:
return "ks-pbs" return "ks-pbs"
@@ -236,6 +239,21 @@ class ErrorFailureProbability(enum.IntEnum):
) )
class BenchType(enum.Enum):
Latency = 0
Throughput = 1
@staticmethod
def from_str(bench_type):
match bench_type.lower():
case "latency":
return BenchType.Latency
case "throughput":
return BenchType.Throughput
case _:
raise ValueError(f"BenchType '{bench_type}' not supported")
class ParamsDefinition: class ParamsDefinition:
""" """
Represents a parameter definition for specific cryptographic settings. Represents a parameter definition for specific cryptographic settings.
@@ -449,7 +467,7 @@ class BenchDetails:
match self.layer: match self.layer:
case Layer.Integer: case Layer.Integer:
op_name_index = 2 if parts[1] == "cuda" else 1 op_name_index = 2 if parts[1] in ["cuda", "hpu"] else 1
if parts[op_name_index] == "signed": if parts[op_name_index] == "signed":
op_name_index += 1 op_name_index += 1
self.sign_flavor = SignFlavor.Signed self.sign_flavor = SignFlavor.Signed
@@ -470,7 +488,7 @@ class BenchDetails:
case Layer.CoreCrypto: case Layer.CoreCrypto:
self.operation_name = parts[2] if parts[1] == "cuda" else parts[1] self.operation_name = parts[2] if parts[1] == "cuda" else parts[1]
case Layer.HLApi: case Layer.HLApi:
if parts[1] == "cuda": if parts[1] in ["cuda", "hpu"]:
self.operation_name = "::".join(parts[2:-1]) self.operation_name = "::".join(parts[2:-1])
else: else:
self.operation_name = "::".join(parts[1:-1]) self.operation_name = "::".join(parts[1:-1])

View File

@@ -1,7 +1,7 @@
import argparse import argparse
import pathlib import pathlib
from benchmark_specs import Backend, Layer, PBSKind from benchmark_specs import Backend, BenchType, Layer, PBSKind
class UserConfig: class UserConfig:
@@ -31,6 +31,8 @@ class UserConfig:
self.bench_date = input_args.bench_date self.bench_date = input_args.bench_date
self.time_span_days = input_args.time_span_days self.time_span_days = input_args.time_span_days
self.bench_type = BenchType.from_str(input_args.bench_type.lower())
self.layer = Layer.from_str(input_args.layer.lower()) self.layer = Layer.from_str(input_args.layer.lower())
self.pbs_kind = PBSKind.from_str(input_args.pbs_kind) self.pbs_kind = PBSKind.from_str(input_args.pbs_kind)
self.grouping_factor = input_args.grouping_factor self.grouping_factor = input_args.grouping_factor

View File

@@ -4,7 +4,14 @@ import os
import pathlib import pathlib
import psycopg2 import psycopg2
from benchmark_specs import BenchDetails, Layer, OperandType, PBSKind from benchmark_specs import (
BenchDetails,
Backend,
BenchType,
Layer,
OperandType,
PBSKind,
)
from config import UserConfig from config import UserConfig
from exceptions import NoDataFound from exceptions import NoDataFound
@@ -179,10 +186,14 @@ class PostgreConnector:
filters.append(f"b.name = '{branch}'") filters.append(f"b.name = '{branch}'")
name_suffix = f"\\{name_suffix}" name_suffix = f"\\{name_suffix}"
if backend == "cpu": match backend:
filters.append(f"test.name LIKE '{layer}::%{name_suffix}'") case Backend.CPU:
elif backend == "gpu": filters.append(f"test.name LIKE '{layer}::%{name_suffix}'")
filters.append(f"test.name LIKE '{layer}::cuda::%{name_suffix}'") case Backend.GPU:
filters.append(f"test.name LIKE '{layer}::cuda::%{name_suffix}'")
case Backend.HPU:
name_suffix = f"_mean"
filters.append(f"test.name LIKE '{layer}::hpu::%{name_suffix}'")
if version: if version:
filters.append(f"pv.name = '{version}'") filters.append(f"pv.name = '{version}'")
@@ -212,8 +223,11 @@ class PostgreConnector:
] ]
filters.append("({})".format(" OR ".join(conditions))) filters.append("({})".format(" OR ".join(conditions)))
# Throughput is not supported yet match user_config.bench_type:
filters.append("test.name NOT SIMILAR TO '%::throughput::%'") case BenchType.Latency:
filters.append("test.name NOT SIMILAR TO '%::throughput::%'")
case BenchType.Throughput:
filters.append("test.name LIKE '%::throughput::%'")
select_parts = ( select_parts = (
"SELECT", "SELECT",

View File

@@ -18,13 +18,12 @@ import argparse
import datetime import datetime
import formatter import formatter
import sys import sys
from formatter import (CSVFormatter, GenericFormatter, MarkdownFormatter, from formatter import CSVFormatter, GenericFormatter, MarkdownFormatter, SVGFormatter
SVGFormatter)
import config import config
import connector import connector
import regression import regression
from benchmark_specs import Backend, Layer, OperandType, PBSKind, RustType from benchmark_specs import BenchType, Layer, OperandType, RustType
import utils import utils
@@ -87,7 +86,7 @@ parser.add_argument(
parser.add_argument( parser.add_argument(
"--backend", "--backend",
dest="backend", dest="backend",
choices=["cpu", "gpu"], choices=["cpu", "gpu", "hpu"],
default="cpu", default="cpu",
help="Backend on which benchmarks have run", help="Backend on which benchmarks have run",
) )
@@ -118,6 +117,13 @@ parser.add_argument(
default=30, default=30,
help="Numbers of days prior of `bench_date` we search for results in the database", help="Numbers of days prior of `bench_date` we search for results in the database",
) )
parser.add_argument(
"--bench-type",
dest="bench_type",
choices=["latency", "throughput"],
default="latency",
help="Type of benchmark to filter against",
)
parser.add_argument( parser.add_argument(
"--regression-profiles", "--regression-profiles",
dest="regression_profiles", dest="regression_profiles",
@@ -212,13 +218,17 @@ def perform_hardware_comparison(
results.append(res) results.append(res)
match user_config.bench_type:
case BenchType.Latency:
conversion_func = utils.convert_latency_value_to_readable_text
case BenchType.Throughput:
conversion_func = utils.convert_throughput_value_to_readable_text
output_filename = "".join( output_filename = "".join(
[user_config.output_file, "_", hw, "_", operand_type.lower(), ".csv"] [user_config.output_file, "_", hw, "_", operand_type.lower(), ".csv"]
) )
csv_formatter = CSVFormatter(layer, user_config.backend, user_config.pbs_kind) csv_formatter = CSVFormatter(layer, user_config.backend, user_config.pbs_kind)
formatted_data = csv_formatter.format_data( formatted_data = csv_formatter.format_data(res, conversion_func)
res, utils.convert_value_to_readable_text
)
utils.write_to_csv( utils.write_to_csv(
csv_formatter.generate_csv(formatted_data), csv_formatter.generate_csv(formatted_data),
output_filename, output_filename,
@@ -289,12 +299,18 @@ def perform_data_extraction(
print(f"Failed to fetch benchmark data: {err}") print(f"Failed to fetch benchmark data: {err}")
sys.exit(2) sys.exit(2)
match user_config.bench_type:
case BenchType.Latency:
conversion_func = utils.convert_latency_value_to_readable_text
case BenchType.Throughput:
conversion_func = utils.convert_throughput_value_to_readable_text
generic_formatter = GenericFormatter( generic_formatter = GenericFormatter(
layer, user_config.backend, user_config.pbs_kind, user_config.grouping_factor layer, user_config.backend, user_config.pbs_kind, user_config.grouping_factor
) )
formatted_results = generic_formatter.format_data( formatted_results = generic_formatter.format_data(
res, res,
utils.convert_value_to_readable_text, conversion_func,
) )
file_suffix = f"_{operand_type.lower()}" file_suffix = f"_{operand_type.lower()}"

View File

@@ -6,10 +6,18 @@ import xml.dom.minidom
from collections.abc import Callable from collections.abc import Callable
import svg import svg
from benchmark_specs import (ALL_RUST_TYPES, Backend, BenchDetails, from benchmark_specs import (
CoreCryptoOperation, ErrorFailureProbability, ALL_RUST_TYPES,
Layer, NoiseDistribution, OperandType, PBSKind, Backend,
RustType) BenchDetails,
CoreCryptoOperation,
ErrorFailureProbability,
Layer,
NoiseDistribution,
OperandType,
PBSKind,
RustType,
)
from py_markdown_table.markdown_table import markdown_table from py_markdown_table.markdown_table import markdown_table
@@ -259,12 +267,6 @@ class GenericFormatter:
f"{prefix}_if_then_else_parallelized", f"{prefix}_if_then_else_parallelized",
] ]
case Backend.GPU: case Backend.GPU:
match operand_type:
case OperandType.CipherText:
prefix = "cuda"
case OperandType.PlainText:
prefix = "cuda_scalar"
operations = [ operations = [
f"{prefix}_neg", f"{prefix}_neg",
f"{prefix}_add", f"{prefix}_add",
@@ -280,6 +282,42 @@ class GenericFormatter:
f"{prefix}_ilog2", f"{prefix}_ilog2",
f"{prefix}_if_then_else", f"{prefix}_if_then_else",
] ]
case Backend.HPU:
operations = [
f"{prefix}_neg",
(
f"{prefix}_add"
if operand_type == OperandType.CipherText
else f"{prefix}_adds"
),
(
f"{prefix}_mul"
if operand_type == OperandType.CipherText
else f"{prefix}_muls"
),
f"{prefix}_cmp_eq",
f"{prefix}_cmp_gt",
f"{prefix}_max",
f"{prefix}_bw_and",
(
f"{prefix}_div"
if operand_type == OperandType.CipherText
else f"{prefix}_divs"
),
(
f"{prefix}_shift_l"
if operand_type == OperandType.CipherText
else f"{prefix}_shifts_l"
),
(
f"{prefix}_rot_l"
if operand_type == OperandType.CipherText
else f"{prefix}_rots_l"
),
f"{prefix}_lead0",
f"{prefix}_ilog2",
f"{prefix}_if_then_else",
]
case _: case _:
raise NotImplementedError( raise NotImplementedError(
f"backend '{self.backend}' not supported yet for integer formatting" f"backend '{self.backend}' not supported yet for integer formatting"
@@ -309,7 +347,7 @@ class GenericFormatter:
first_column_header = "Operation \\ Size" first_column_header = "Operation \\ Size"
# Adapt list to plaintext benchmarks results. # Adapt list to plaintext benchmarks results.
if operand_type == OperandType.PlainText: if operand_type == OperandType.PlainText and self.backend != Backend.HPU:
if self.backend == Backend.CPU: if self.backend == Backend.CPU:
div_name = f"{prefix}_div_parallelized" div_name = f"{prefix}_div_parallelized"
rem_name = f"{prefix}_rem_parallelized" rem_name = f"{prefix}_rem_parallelized"
@@ -319,7 +357,7 @@ class GenericFormatter:
operations.insert(8, div_name) operations.insert(8, div_name)
operations.insert(9, rem_name) operations.insert(9, rem_name)
operations.pop(7) # Remove div_rem_parallelized operations.pop(7)
display_names.insert( display_names.insert(
8, 8,
@@ -404,8 +442,6 @@ class GenericFormatter:
NoiseDistribution.TUniform, NoiseDistribution.TUniform,
] ]
operation_displays = [op.value for op in OPERATIONS_DISPLAYS]
sorted_results = self._build_results_dict( sorted_results = self._build_results_dict(
supported_pfails, supported_pfails,
noise_distributions, noise_distributions,
@@ -435,6 +471,12 @@ class GenericFormatter:
) and param_definition.pbs_kind != PBSKind.MultiBit: ) and param_definition.pbs_kind != PBSKind.MultiBit:
# Skip this operation since a multi-bit operation cannot be done with any other parameters type. # Skip this operation since a multi-bit operation cannot be done with any other parameters type.
continue continue
elif (
formatted_name == CoreCryptoOperation.PBS
or formatted_name == CoreCryptoOperation.KeySwitchPBS
) and param_definition.pbs_kind != PBSKind.Classical:
# Skip this operation since a classical operation cannot be done with any other parameters type.
continue
grouping_factor = param_definition.grouping_factor grouping_factor = param_definition.grouping_factor
if ( if (
@@ -443,10 +485,7 @@ class GenericFormatter:
): ):
continue continue
if ( if param_definition.details["variation"] not in ["", "BENCH"]:
param_definition.details["variation"]
or param_definition.details["trailing_details"]
):
continue continue
try: try:
@@ -484,7 +523,7 @@ OPERATIONS_DISPLAYS = [
# CoreCryptoOperation.KeySwitch, # Uncomment this line to get keyswitch in the tables # CoreCryptoOperation.KeySwitch, # Uncomment this line to get keyswitch in the tables
CoreCryptoOperation.PBS, CoreCryptoOperation.PBS,
CoreCryptoOperation.MultiBitPBS, CoreCryptoOperation.MultiBitPBS,
CoreCryptoOperation.KeyswitchPBS, CoreCryptoOperation.KeySwitchPBS,
CoreCryptoOperation.KeySwitchMultiBitPBS, CoreCryptoOperation.KeySwitchMultiBitPBS,
] ]

View File

@@ -10,8 +10,11 @@ SECONDS_IN_NANO = 1e9
MILLISECONDS_IN_NANO = 1e6 MILLISECONDS_IN_NANO = 1e6
MICROSECONDS_IN_NANO = 1e3 MICROSECONDS_IN_NANO = 1e3
THOUSAND_ELEMENTS = 1e3
MILLION_ELEMENTS = 1e6
def convert_value_to_readable_text(value: int, max_digits: int = 3) -> str:
def convert_latency_value_to_readable_text(value: int, max_digits: int = 3) -> str:
""" """
Convert timing in nanoseconds to the highest unit usable. Convert timing in nanoseconds to the highest unit usable.
@@ -40,6 +43,37 @@ def convert_value_to_readable_text(value: int, max_digits: int = 3) -> str:
return f"{round(converted_parts[0], rounding_digit)} {converted_parts[1]}" return f"{round(converted_parts[0], rounding_digit)} {converted_parts[1]}"
def convert_throughput_value_to_readable_text(value: int, max_digits: int = 3):
"""
Convert timing in elements per second to the highest unit usable.
:param value: timing value
:type value: int
:param max_digits: number of digits to keep in the final representation of the value
:type max_digits: int, optional
:return: human-readable value with unit
:rtype:str
"""
if value > MILLION_ELEMENTS:
converted_parts = (value / MILLION_ELEMENTS), "M.ops/s"
elif value > THOUSAND_ELEMENTS:
converted_parts = (value / THOUSAND_ELEMENTS), "k.ops/s"
else:
converted_parts = value, "ops/s"
if converted_parts[0] > 0:
power_of_10 = math.floor(math.log10(converted_parts[0]))
rounding_digit = max_digits - (power_of_10 + 1)
else:
rounding_digit = None
if converted_parts[0] >= 100.0:
rounding_digit = None
return f"{round(converted_parts[0], rounding_digit)} {converted_parts[1]}"
def convert_gain_to_text(value: float) -> str: def convert_gain_to_text(value: float) -> str:
""" """
Convert gains as :class:`float` to :class:`str` Convert gains as :class:`float` to :class:`str`