From 5f02195fd4b8034fb9adbf67738ad5c3b0b27334 Mon Sep 17 00:00:00 2001 From: speriaswamy-amd Date: Fri, 14 Aug 2026 16:14:20 -0400 Subject: [PATCH] feat(parsers): add pydantic schemas for the health suite configs Second pilot suite for AIMVT-276. One config file (mi300_health_config.json) serves seven tests, each reading a different top-level section -- agfhc, rvs, transferbench -- so the three models here are independent rather than nested under a shared root. AIMVT-276. --- cvs/parsers/config_schemas/__init__.py | 5 + cvs/parsers/config_schemas/health.py | 283 +++++++++++++++++++++++++ 2 files changed, 288 insertions(+) create mode 100644 cvs/parsers/config_schemas/health.py diff --git a/cvs/parsers/config_schemas/__init__.py b/cvs/parsers/config_schemas/__init__.py index edabe50ad..6512cfa18 100644 --- a/cvs/parsers/config_schemas/__init__.py +++ b/cvs/parsers/config_schemas/__init__.py @@ -9,8 +9,13 @@ validation, so an existing customer config is never rejected on account of them. """ +from .health import AgfhcConfigFile, RvsConfigFile, RvsTestConfig, TransferBenchConfigFile from .rccl import RcclConfigFile __all__ = [ + "AgfhcConfigFile", "RcclConfigFile", + "RvsConfigFile", + "RvsTestConfig", + "TransferBenchConfigFile", ] diff --git a/cvs/parsers/config_schemas/health.py b/cvs/parsers/config_schemas/health.py new file mode 100644 index 000000000..1fe700188 --- /dev/null +++ b/cvs/parsers/config_schemas/health.py @@ -0,0 +1,283 @@ +"""Config schemas for the health suite sections: agfhc, transferbench, rvs. + +One config file serves several tests, each reading a different top-level +section, so the three models here are independent rather than nested under a +common root. + +Numeric thresholds are typed ``str`` because they ship as JSON strings and are +only coerced with ``float()`` at the comparison site; typing them as numbers +would change the on-disk contract. +""" + +from typing import List, Optional + +from pydantic import BaseModel, ConfigDict, Field + + +class AgfhcConfigFile(BaseModel): + """Schema for the agfhc section, read by agfhc_cvs, csp_qual_agfhc and install_agfhc. + + Requirements differ per test: agfhc_cvs needs hbm_test_duration and never + reads log_dir, while csp_qual_agfhc is the reverse. + """ + + model_config = ConfigDict(extra="allow") + + path: str = Field( + description="Directory containing the installed agfhc binary. Every testcase runs 'sudo {path}/agfhc'.", + examples=["/opt/amd/agfhc"], + ) + package_tar_ball: str = Field( + description="Path to the AGFHC tarball that install_agfhc copies to each node and extracts.", + examples=["/home/{user-id}/PACKAGES/agfhc-mi300x_1.22.0_ub2204.tar.bz2"], + ) + install_dir: str = Field( + description="Staging directory the tarball is copied into and extracted in. Created if absent.", + examples=["/home/{user-id}/INSTALL/agfhc/"], + ) + log_dir: str = Field( + description=( + "Destination for AGFHC run logs, passed as -o. Must be a non-NFS local filesystem. Wiped and " + "recreated at the start of each run. Read by csp_qual_agfhc only." + ), + examples=["/root/agfhc_logs"], + ) + hbm_test_duration: str = Field( + description=( + "Duration of the HBM test as HH:MM:SS. Drives both the agfhc -t hbm:d= argument and the exec " + "timeout, which is this duration plus 120 seconds. Read by agfhc_cvs only." + ), + examples=["00:01:30"], + ) + + +class TransferBenchResults(BaseModel): + """Minimum acceptable bandwidths, in GB/s, compared against parsed TransferBench output.""" + + model_config = ConfigDict(extra="allow") + + gpu_to_gpu_a2a_rtotal: str = Field( + description="Floor for every per-column RTotal bandwidth in the all-to-all run.", + examples=["320.0"], + ) + avg_gpu_to_gpu_p2p_unidir_bw: str = Field( + description="Floor for the 'Averages (During UniDir)' row of the peer-to-peer run.", + examples=["33.9"], + ) + avg_gpu_to_gpu_p2p_bidir_bw: str = Field( + description="Floor for the 'Averages (During BiDir)' row of the peer-to-peer run.", + examples=["43.9"], + ) + best_gpu0_bw: str = Field( + description="Floor for the GPU00 column of the 'Best' summary row in the scaling run.", + examples=["480.0"], + ) + local_read_32_cu: str = Field( + alias="32_cu_local_read", + description="Floor for local read in the 32-CU schmoo row.", + examples=["1650"], + ) + local_write_32_cu: str = Field( + alias="32_cu_local_write", + description="Floor for local write in the 32-CU schmoo row.", + examples=["1250.0"], + ) + local_copy_32_cu: str = Field( + alias="32_cu_local_copy", + description="Floor for local copy in the 32-CU schmoo row.", + examples=["1250.0"], + ) + rem_read_32_cu: str = Field( + alias="32_cu_rem_read", + description="Floor for remote read in the 32-CU schmoo row.", + examples=["48.0"], + ) + rem_write_32_cu: str = Field( + alias="32_cu_rem_write", + description="Floor for remote write in the 32-CU schmoo row.", + examples=["48.0"], + ) + rem_copy_32_cu: str = Field( + alias="32_cu_rem_copy", + description="Floor for remote copy in the 32-CU schmoo row.", + examples=["48.0"], + ) + bytes_to_transfer: Optional[str] = Field( + default=None, + description="Not read by any code path. TransferBench payload sizes are set by the mode, not this key.", + examples=["268435456"], + ) + path: Optional[str] = Field( + default=None, + description="Not read by any code path; the tests use the section-level transferbench.path instead.", + examples=["/home/{user-id}/INSTALL/TransferBench"], + ) + + +class TransferBenchConfigFile(BaseModel): + """Schema for the transferbench section, read by transferbench_cvs and install_transferbench.""" + + model_config = ConfigDict(extra="allow") + + path: str = Field( + description="Directory containing the built TransferBench binary, invoked as '{path}/TransferBench '.", + examples=["/home/{user-id}/INSTALL/TransferBench"], + ) + git_install_path: str = Field( + description="Parent directory the repository is cloned into, as '{git_install_path}/TransferBench'.", + examples=["/home/{user-id}/INSTALL/"], + ) + git_url: str = Field( + description="Git remote cloned by install_transferbench.", + examples=["https://github.com/ROCm/TransferBench.git"], + ) + git_tag: str = Field( + description=( + "Git tag checked out after clone, then verified with 'git describe --tags --exact-match'. " + "Omitting it fails the install with a explicit error." + ), + examples=["v1.67.00"], + ) + rocm_path: str = Field( + default="", + description=( + "ROCm installation path. Leave empty to auto-detect from /opt/rocm or /opt/rocm/core-*. Note that " + "the literal string '' cannot be used as the auto-detect sentinel: placeholder resolution " + "aborts the run before the sentinel is ever examined." + ), + examples=["/opt/rocm"], + ) + results: TransferBenchResults = Field(description="Minimum acceptable bandwidths per TransferBench mode.") + + +class RvsTestConfig(BaseModel): + """One entry of the rvs.tests list, selected by name.""" + + model_config = ConfigDict(extra="allow") + + name: str = Field( + description="Selector matched against the hardcoded test function names.", + examples=["level_config", "mem_test", "gst_single", "iet_stress", "pebb_single", "pbqt_single", "babel_stream"], + ) + config_file: Optional[str] = Field( + default=None, + description=( + "RVS .conf filename resolved under config_path_default, preferring a device-specific subdirectory. " + "Omitted for level_config, which uses 'rvs -r ' instead of '-c '." + ), + examples=["mem.conf", "gst_single.conf"], + ) + description: Optional[str] = Field( + default=None, + description="Human-readable label used in log lines. Falls back to the entry's own name.", + examples=["Memory Test"], + ) + timeout: int = Field( + default=9000, + description=( + "Per-test exec timeout in seconds. The default is 9000 for individual modules but 7200 for the " + "level_config entry." + ), + ) + fail_regex_pattern: str = Field( + default=r"\[ERROR\s*\]", + description=( + "Single alternation-joined regex searched case-insensitively in the output of an individual module; " + "a match on any node fails the test. The default is a bare-minimum fallback that is far weaker than " + "every shipped value, so omitting this key silently downgrades failure detection." + ), + examples=[r"FAIL|\[ERROR\s*\]|RVS-ERROR"], + ) + fail_regex_patterns: List[str] = Field( + default_factory=list, + description=( + "Plural form used only by the level_config run, where each pattern is searched separately. An empty " + "list makes the LEVEL test pass unconditionally, so populate it whenever level_config is enabled." + ), + examples=[["peqt false", "RVS-ERROR"]], + ) + expected_pass: Optional[bool] = Field( + default=None, + description="Not read by any code path. Pass and fail are decided entirely by the regex fields.", + examples=[True], + ) + + +class RvsConfigFile(BaseModel): + """Schema for the rvs section, read by rvs_cvs and install_rvs.""" + + model_config = ConfigDict(extra="allow") + + path: str = Field( + description=( + "Directory containing the rvs binary. install_rvs rewrites this value in place once it has detected " + "the real ROCm path." + ), + examples=["/opt/rocm/bin"], + ) + git_install_path: str = Field( + description="Staging directory for the pre-built RVS tarball download. No git clone is performed.", + examples=["/home/{user-id}/INSTALL/rvs"], + ) + rocm_path: str = Field( + default="", + description=( + "ROCm installation path used by the installer only. Leave empty to auto-detect from /opt/rocm or " + "/opt/rocm/core-*." + ), + examples=["/opt/rocm"], + ) + rocm_runtime_lib_path: str = Field( + default="", + description=( + "Optional colon-separated directories prepended to LD_LIBRARY_PATH for every rvs invocation and for " + "the install-time ldd check. Use this when the rvs binary was built against a newer ROCm than " + "/opt/rocm points at and needs to load amd_smi or rocm_smi from a side-by-side install. Leave empty " + "for default loader behaviour." + ), + examples=["/home/{user-id}/install/lib:/home/{user-id}/install/lib/rocm_sysdeps"], + ) + config_path_default: str = Field( + description=( + "Base directory for RVS .conf files. A device-specific subdirectory is preferred when one matching " + "the detected GPU exists, otherwise the base directory is used." + ), + examples=["/opt/rocm/share/rocm-validation-suite/conf"], + ) + config_path_mi300x: str = Field( + description=( + "First-choice directory probed for gst_single.conf during install verification. Rewritten in place " + "with the detected ROCm path. Not read at test time, where device directories are discovered " + "dynamically." + ), + examples=["/opt/rocm/share/rocm-validation-suite/conf/MI300X"], + ) + rvs_test_level: int = Field( + default=4, + ge=0, + le=5, + description=( + "RVS test level, passed as 'rvs -r '. 0 runs the individual module tests and skips the LEVEL " + "test; 1 to 5 run the LEVEL config test when RVS is at least 1.3.0, falling back to the individual " + "tests otherwise. Values outside 0-5 fall back to 4." + ), + ) + tests: List[RvsTestConfig] = Field( + description=( + "Per-module test definitions, selected by name. If the level_config entry is absent, a hardcoded " + "fallback is substituted." + ), + ) + git_url: Optional[str] = Field( + default=None, + description=( + "Not read by any code path. The installer uses the ROCm apt package and falls back to a hardcoded " + "tarball URL." + ), + examples=["https://github.com/ROCm/ROCmValidationSuite.git"], + ) + nfs_install: Optional[str] = Field( + default=None, + description="Not read by any code path in the RVS tests.", + examples=["True"], + )