ba4be087d5
Create PR to main with cherry-pick from release / cherry-pick (push) Failing after 0s
CICD NeMo / pre-flight (push) Failing after 0s
CICD NeMo / configure (push) Has been skipped
Build, validate, and release Neural Modules / pre-flight (push) Failing after 1s
CICD NeMo / code-linting (push) Has been skipped
Build, validate, and release Neural Modules / release (push) Has been skipped
Build, validate, and release Neural Modules / release-summary (push) Has been cancelled
CICD NeMo / cicd-test-container-build (push) Has been cancelled
CICD NeMo / cicd-import-tests (push) Has been cancelled
CICD NeMo / L0_Setup_Test_Data_And_Models (push) Has been cancelled
CICD NeMo / cicd-main-unit-tests (push) Has been cancelled
CICD NeMo / cicd-main-speech (push) Has been cancelled
CICD NeMo / Nemo_CICD_Test (push) Has been cancelled
CICD NeMo / Coverage (e2e) (push) Has been cancelled
CICD NeMo / Coverage (unit-test) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
CICD NeMo / cicd-wait-in-queue (push) Has been cancelled
321 lines
10 KiB
Python
321 lines
10 KiB
Python
# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
import logging
|
|
import os
|
|
from argparse import ArgumentParser, RawDescriptionHelpFormatter
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
from paramiko import AutoAddPolicy, SSHClient
|
|
from paramiko.sftp_client import SFTPClient
|
|
from scripts.tts_comparison_report.reporting import (
|
|
DUMMY_TASK_ID,
|
|
SUPPORTED_BENCHMARK_NAMES,
|
|
TEMPLATES_DIR,
|
|
BaseStorage,
|
|
BucketStructure,
|
|
LocalStorage,
|
|
Orchestrator,
|
|
Renderer,
|
|
S3Client,
|
|
S3Config,
|
|
SFTPStorage,
|
|
)
|
|
|
|
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
|
logging.captureWarnings(True)
|
|
|
|
|
|
_REMOTE_PASSWORD: str = "REMOTE_PASSWORD"
|
|
_S3_ACCESS_KEY_ID: str = "S3_ACCESS_KEY_ID"
|
|
_S3_SECRET_ACCESS_KEY: str = "S3_SECRET_ACCESS_KEY"
|
|
|
|
_DEFAULT_BENCHMARK_NAMES: str = ",".join(SUPPORTED_BENCHMARK_NAMES)
|
|
|
|
|
|
def _create_argparser() -> ArgumentParser:
|
|
parser = ArgumentParser(
|
|
description="Script for generating MagpieTTS evaluation comparison reports",
|
|
formatter_class=RawDescriptionHelpFormatter,
|
|
)
|
|
parser.add_argument(
|
|
"--baseline_name",
|
|
type=str,
|
|
required=True,
|
|
help="Name of the baseline model that will be used in report.",
|
|
)
|
|
parser.add_argument(
|
|
"--baseline_path",
|
|
type=str,
|
|
required=True,
|
|
help="Path to the generated evaluation bucket for the baseline model.",
|
|
)
|
|
parser.add_argument(
|
|
"--candidate_name",
|
|
type=str,
|
|
required=True,
|
|
help="Name of the candidate model that will be used in report.",
|
|
)
|
|
parser.add_argument(
|
|
"--candidate_path",
|
|
type=str,
|
|
required=True,
|
|
help="Path to the generated evaluation bucket for the candidate model.",
|
|
)
|
|
parser.add_argument(
|
|
"--benchmarks",
|
|
type=str,
|
|
default=_DEFAULT_BENCHMARK_NAMES,
|
|
help="Comma-separated list of benchmarks used in the evaluation report.",
|
|
)
|
|
parser.add_argument(
|
|
"--s3_endpoint",
|
|
type=str,
|
|
required=True,
|
|
help="S3 endpoint URL used for uploading the audio report.",
|
|
)
|
|
parser.add_argument(
|
|
"--s3_bucket",
|
|
type=str,
|
|
required=True,
|
|
help="Name of the S3 bucket where the audio report HTML and audio files will be uploaded.",
|
|
)
|
|
parser.add_argument(
|
|
"--s3_region",
|
|
type=str,
|
|
required=True,
|
|
help="AWS region name for the S3 client.",
|
|
)
|
|
parser.add_argument(
|
|
"--remote_hostname",
|
|
type=str,
|
|
default=None,
|
|
help="Name of the remote host, if the generated buckets are located there.",
|
|
)
|
|
parser.add_argument(
|
|
"--remote_username",
|
|
type=str,
|
|
default=None,
|
|
help="Name of the user on the remote host.",
|
|
)
|
|
parser.add_argument(
|
|
"--task_id",
|
|
type=str,
|
|
default=DUMMY_TASK_ID,
|
|
help="Jira task number associated with this report.",
|
|
)
|
|
parser.add_argument(
|
|
"--results_subdir",
|
|
type=str,
|
|
default="results",
|
|
help="Subdirectory inside the bucket root that contains evaluation outputs produced by `magpietts_inference`.",
|
|
)
|
|
parser.add_argument(
|
|
"--audio_report",
|
|
action='store_true',
|
|
help="Generate additional report with side-by-side audio comparison.",
|
|
)
|
|
parser.add_argument(
|
|
"--audio_report_benchmarks",
|
|
type=str,
|
|
default="libritts_test_clean,riva_hard_digits,riva_hard_letters",
|
|
help="Comma-separated list of benchmarks to include in the audio report.",
|
|
)
|
|
parser.add_argument(
|
|
"--samples_per_benchmark",
|
|
type=int,
|
|
default=30,
|
|
help="Number of samples per benchmark in the audio report.",
|
|
)
|
|
return parser
|
|
|
|
|
|
def _get_benchmarks_list(benchmarks: str) -> list[str]:
|
|
return [x.strip() for x in benchmarks.split(",") if x.strip()]
|
|
|
|
|
|
def _validate_benchmarks(benchmarks: list[str]) -> None:
|
|
if not benchmarks:
|
|
raise ValueError("Empty list of benchmark names was provided.")
|
|
|
|
supported_set = set(SUPPORTED_BENCHMARK_NAMES)
|
|
|
|
for name in benchmarks:
|
|
if name not in supported_set:
|
|
raise ValueError(f"Unknown benchmark name: '{name}'.")
|
|
|
|
|
|
def _validate_audio_report_benchmarks(
|
|
benchmarks: list[str],
|
|
audio_report_benchmarks: list[str],
|
|
) -> None:
|
|
if not audio_report_benchmarks:
|
|
raise ValueError("Empty list of benchmark names was provided for the audio report.")
|
|
|
|
supported_set = set(benchmarks)
|
|
|
|
for name in audio_report_benchmarks:
|
|
if name not in supported_set:
|
|
raise ValueError(f"Benchmark name for audio report '{name}' is not included in evaluation benchmarks.")
|
|
|
|
|
|
def main() -> None:
|
|
"""Parse CLI arguments, generate comparison reports, and upload them to S3.
|
|
|
|
This function serves as the command-line entry point for the report
|
|
generation workflow. It validates user input, initializes storage and S3
|
|
clients, runs the report orchestrator, and logs the resulting report URLs.
|
|
|
|
Raises:
|
|
ValueError: If required environment variables are missing or CLI
|
|
arguments are invalid.
|
|
RuntimeError: If report generation or upload does not complete
|
|
successfully.
|
|
"""
|
|
logger = logging.getLogger(__name__)
|
|
|
|
parser = _create_argparser()
|
|
args = parser.parse_args()
|
|
|
|
bucket_structure = BucketStructure()
|
|
bucket_structure.eval_output_subdir = args.results_subdir
|
|
baseline_path = Path(args.baseline_path).resolve()
|
|
candidate_path = Path(args.candidate_path).resolve()
|
|
task_id = args.task_id
|
|
|
|
storage: BaseStorage
|
|
s3_client: Optional[S3Client] = None
|
|
ssh_client: Optional[SSHClient] = None
|
|
sftp: Optional[SFTPClient] = None
|
|
eval_report_url: Optional[str] = None
|
|
audio_report_url: Optional[str] = None
|
|
audio_report_benchmarks: Optional[list[str]] = None
|
|
|
|
s3_key_id = os.getenv(_S3_ACCESS_KEY_ID)
|
|
s3_secret_key = os.getenv(_S3_SECRET_ACCESS_KEY)
|
|
|
|
if s3_key_id is None or s3_secret_key is None:
|
|
raise ValueError(
|
|
f"Environment variables '{_S3_ACCESS_KEY_ID}' and '{_S3_SECRET_ACCESS_KEY}' "
|
|
"must be set for uploading reports to S3."
|
|
)
|
|
|
|
s3_cfg = S3Config(
|
|
bucket=args.s3_bucket,
|
|
endpoint_url=args.s3_endpoint,
|
|
region_name=args.s3_region,
|
|
)
|
|
s3_client = S3Client(
|
|
cfg=s3_cfg,
|
|
aws_access_key_id=s3_key_id,
|
|
aws_secret_access_key=s3_secret_key,
|
|
)
|
|
|
|
benchmarks = _get_benchmarks_list(args.benchmarks)
|
|
_validate_benchmarks(benchmarks)
|
|
|
|
if args.audio_report:
|
|
audio_report_benchmarks = _get_benchmarks_list(args.audio_report_benchmarks)
|
|
_validate_audio_report_benchmarks(benchmarks, audio_report_benchmarks)
|
|
|
|
if args.samples_per_benchmark <= 0:
|
|
raise ValueError("Number of samples per benchmark for the audio report must be greater than 0.")
|
|
|
|
if task_id == DUMMY_TASK_ID:
|
|
logger.warning("\nWARNING: It is recommended to assign the evaluation report to a specific ticket!")
|
|
|
|
if baseline_path == candidate_path:
|
|
logger.warning(
|
|
"\nWARNING: Baseline and candidate paths are identical. "
|
|
"Comparison report is not meaningful in this case!"
|
|
)
|
|
|
|
logger.info(f"\nComparing baseline '{args.baseline_name}' against candidate '{args.candidate_name}'")
|
|
|
|
try:
|
|
if args.remote_hostname is not None or args.remote_username is not None:
|
|
if args.remote_username is None:
|
|
raise ValueError("'remote_username' must be provided when using remote access.")
|
|
|
|
if args.remote_hostname is None:
|
|
raise ValueError("'remote_hostname' must be provided when using remote access.")
|
|
|
|
remote_password = os.getenv(_REMOTE_PASSWORD)
|
|
|
|
if remote_password is None:
|
|
raise ValueError(f"Environment variable '{_REMOTE_PASSWORD}' is not set.")
|
|
|
|
logger.info(f"\nSetting remote connection with host: {args.remote_hostname}")
|
|
|
|
ssh_client = SSHClient()
|
|
ssh_client.set_missing_host_key_policy(policy=AutoAddPolicy())
|
|
ssh_client.connect(
|
|
hostname=args.remote_hostname,
|
|
username=args.remote_username,
|
|
password=remote_password,
|
|
)
|
|
sftp = ssh_client.open_sftp()
|
|
storage = SFTPStorage(sftp)
|
|
|
|
else:
|
|
storage = LocalStorage()
|
|
|
|
renderer = Renderer(templates_dir=TEMPLATES_DIR)
|
|
|
|
orchestrator = Orchestrator(
|
|
bucket_structure=bucket_structure,
|
|
storage=storage,
|
|
s3_client=s3_client,
|
|
renderer=renderer,
|
|
logger=logger,
|
|
)
|
|
eval_report_url, audio_report_url = orchestrator.run(
|
|
baseline_name=args.baseline_name,
|
|
candidate_name=args.candidate_name,
|
|
baseline_path=baseline_path,
|
|
candidate_path=candidate_path,
|
|
benchmarks=benchmarks,
|
|
generate_audio_report=args.audio_report,
|
|
audio_report_benchmarks=audio_report_benchmarks,
|
|
samples_per_benchmark=args.samples_per_benchmark,
|
|
task_id=task_id,
|
|
)
|
|
|
|
finally:
|
|
if sftp is not None:
|
|
sftp.close()
|
|
|
|
if ssh_client is not None:
|
|
ssh_client.close()
|
|
|
|
if s3_client is not None:
|
|
s3_client.close()
|
|
|
|
if eval_report_url is None:
|
|
raise RuntimeError("Failed to generate evaluation report and upload it to S3.")
|
|
|
|
if args.audio_report and audio_report_url is None:
|
|
raise RuntimeError("Failed to upload audio report to S3 and create URL.")
|
|
|
|
if audio_report_url is not None:
|
|
logger.info(f"\nAudio report is available at:\n{audio_report_url}")
|
|
|
|
logger.info(f"\nEvaluation report is available at:\n{eval_report_url}")
|
|
|
|
logger.info("\nSave the links and open in your browser!\n")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|