Config Validation
aiperf kube validate performs client-side validation of one or more
AIPerfJob or AIPerfSweep YAML files against the Pydantic spec models that
generate the CRD schema, plus Kubernetes resource-naming rules. It dispatches per
document on the kind: field (validate.py SUPPORTED_KINDS = {AIPerfJob, AIPerfSweep}), routing to AIPerfJobSpec or AIPerfSweepSpec. It does not
contact the cluster — making it safe to run in CI, pre-commit hooks, and local
editors.
Two layers, same rules.
aiperf kube validateruns the same structural checks the apiserver enforces atkubectl apply. For the catalog of CELx-kubernetes-validationsrules (shorthand-vs-canonical, mutual exclusion,apiHost ⇒ apiPort, etc.) see CRD Validation Rules. Item-internal Pydantic validators (phase-name uniqueness, phase→dataset reference integrity, “seamless not on first phase”) run only at apply time on the operator side because CEL can’t see into opaque preserve-unknown array items — the client-sidevalidatecommand runs them via Pydantic, so it catches both layers in one pass.
When to use
Think of validate as the offline static check and preflight as the online
dynamic check. Both are cheap; run validate on every commit and preflight
before the first apply of a new job.
Typical integration points:
- CI gate — run
find recipes -name perf.yaml -print0 | xargs -0 aiperf kube validate --strictin a GitHub Action or GitLab job before merging changes to benchmark specs. - Pre-commit — catch typos in
spec.benchmarkfields before they reach the cluster (see Integration recipes). - IDE / Makefile target — add
make validate-jobsso contributors get fast feedback without spinning up a cluster.
CLI reference
Exit codes
Examples
Bare AIPerf configs
validate accepts two document shapes:
- a wrapped CR (
apiVersion+kind+metadata+spec), and - a bare AIPerf config — the Config-v2 shape with
models,endpoint,datasets, andphasesat the top level, which is whataiperf kube sweep --configconsumes.
A bare config is detected by the absence of apiVersion/kind, hoisted into
the equivalent CR spec (envelope keys such as sweep, multiRun, variables
stay at the spec level; everything else becomes spec.benchmark), and held to
the same contract. The kind is inferred from the presence of a sweep: block,
so a bare config with sweep: is checked against the AIPerfSweep
cardinality rule and one without it against AIPerfJob.
Every bare config emits a warning naming the contract that was applied:
Because a bare config has no CR wrapper, only the wrapper-only checks are
skipped: apiVersion/kind and metadata.name are not validated. Everything
else runs identically, including deployment-field and worker-count checks,
credential transport, and unknown-field detection. A document that nests
benchmark: explicitly may carry deployment fields (image, podTemplate,
…) as its siblings; those stay at the spec level and are checked there, so a
misspelled sibling is reported rather than silently dropped.
Under --strict this warning is not promoted to an error — --strict
governs unknown spec fields only.
What gets validated
validate runs the following checks on each file, in order. Structural errors
that make later checks impossible short-circuit the file (remaining checks are
skipped for that file only). Bare AIPerf configs skip steps 3 and 4 (see
Bare AIPerf configs), except for the
spec.benchmark-shape rule in step 3, which still applies.
- File reachability — the path exists, is a regular file, and passes the
shared
safe_read_template_pathsafety check. - YAML parse — the document is valid YAML and decodes to a mapping.
- Required top-level fields:
apiVersionmust equal the current operator API version (aiperf.nvidia.com/v1alpha1).kindmust be one ofAIPerfJoborAIPerfSweep. The kind selects which spec model the document is validated against; anAIPerfJobmust omitspec.sweepwhile anAIPerfSweeprequires it.metadatamust be a mapping with anamefield.specmust be a mapping.spec.benchmarkmust be a mapping containing at least one ofmodelsorendpoint.
- Kubernetes naming —
metadata.namemust:- be at most 253 characters (
K8S_NAME_MAX_LENGTH), and - match
K8S_NAME_PATTERN, the RFC 1123 label pattern^[a-z0-9]([a-z0-9-]*[a-z0-9])?$(lowercase alphanumerics and hyphens only; must start and end with an alphanumeric — dots are rejected).
- be at most 253 characters (
- Unknown field detection (warning by default, error with
--strict):- Top-level
specis compared againstKNOWN_SPEC_FIELDS(validate.py) — the deployment fieldsimage,imagePullPolicy,keepFailedPods,resourceMode,connectionsPerWorker,timeoutSeconds,ttlSecondsAfterFinished,resultsTtlDays,cancel,podTemplate,scheduling,skipEndpointCheck,failurePolicy; the envelope fieldsschemaVersion,sweep,multiRun,plot,variables,randomSeed,noSweepTable,childMetadata; plus the nestedbenchmarkblock. Stray top-level keys often mean a benchmark-config field was placed atspec.<x>instead ofspec.benchmark.<x>— the warning message says so explicitly. spec.benchmarkis compared againstCONFIG_FIELDS(kubernetes/spec_converter.py): everyBenchmarkConfigmodel field, its serialization aliases, plus the shorthand keysmodel,dataset,warmup,profiling.
- Top-level
AIPerfConfigconstruction —spec.benchmarkis fed throughAIPerfJobSpecConverter.to_aiperf_config(), which performs the same env-var and Jinja2 expansion as a local CLI file load, then validates the result against the Pydantic model. Type, range, and cross-field errors surface here.- Endpoint sanity — at least one model name must be present, and every
entry in
endpoint.urlsmust start withhttp://orhttps://. - Deployment-config extraction — top-level spec fields are materialised
into a
DeploymentConfigviato_deployment_config(). Catches malformedpodTemplate, invalidresourceMode, badschedulingblocks, etc. - Endpoint credential transport — credential-bearing endpoint fields
must have the matching Secret-backed pod environment (
AIPERF_INJECTED_API_KEY,AIPERF_INJECTED_HEADERS, orAIPERF_INJECTED_ENDPOINT_URLS). Literal secrets and plain-value environment variables fail before deployment. This check is skipped when steps 6–8 already produced an error, since it needs a well-formed config and deployment to inspect. - Worker-count calculation —
calculate_workers()must complete. It honours an explicitbenchmark.runtime.workersoverride, otherwise computesceil(max phase concurrency / connectionsPerWorker), always clamped to at least1. Unparsable concurrency and worker values fall back to1rather than raising, and aconnectionsPerWorkerthat is numeric but below1(including0,false, and subnormal floats such as1e-320) is neutralized to1for this step — step 8 has already reported it against the field’s>= 1bound, so re-reporting it here would only duplicate that error. This step therefore reports only a non-numericconnectionsPerWorker(Worker calculation failed: ...), where the arithmetic genuinely cannot proceed. - Kind/sweep cardinality and kind-specific spec validation —
spec.sweepmust be absent on anAIPerfJoband a non-empty mapping on anAIPerfSweep, mirroring each CRD’s CEL rule. Then the Config-v2 envelope is rendered (unknown top-level keys stripped first, so pydantic does not re-report them) before the completeAIPerfJobSpecorAIPerfSweepSpeccheck. Raw Jinja values therefore validate as their resolved numeric or structured types, while unknown variables and invalid rendered values still fail closed.
Note:
validateis intentionally conservative about what it considers “unknown”. Any key inCONFIG_FIELDS(everyBenchmarkConfigfield, its aliases, and the shorthand keys) is accepted underspec.benchmark, so newly added config fields do not require a docs update to this page.
JSON output schema
With -o json, a single JSON array is printed to stdout. Each element
corresponds to one input file, in the order given on the command line.
stdout carries nothing but that array — long paths and error strings are
never line-wrapped, and any diagnostics the validator logs are retargeted
to stderr — so the document is parseable when redirected or run in CI.
Every input file always appears in the array. No individual file can abort the
batch: a malformed value is reported against the file that carries it, and the
remaining files are still validated. jq -e recipes therefore always receive a
complete document, even when the first file on the command line is the broken
one.
Example — all files pass
Example — multiple errors
Unknown-field messages land in warnings by default and move into errors
under --strict:
Scripting tip — fail a CI job on any warning, not just errors:
Integration recipes
Pre-commit hook
Add to .pre-commit-config.yaml:
GitHub Actions step
In a matrix/monorepo setup, use JSON output to surface a compact report:
Makefile target
See also
production.md— production deployment guide, including the recommended CI pipeline.configuration.md— reference forspecandspec.benchmarkfields.aiperf kube preflight— live-cluster counterpart tovalidate.