Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 13:28:58 +08:00

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()