User-Defined Output Files (artifacts.user_files)
User-Defined Output Files (artifacts.user_files)
artifacts.user_files lets you declare arbitrary templated output files that are
materialized into the run directory before the benchmark begins. Files are rendered
with jinja2 against the user variables: block plus a small set of system-injected
names.
AIPerfJob accepts the same block — artifacts is part of the shared
benchmark: config envelope. aiperf kube profile --config serializes the spec
with camelCase aliases before submitting it, so snake_case authoring in an AIPerf
YAML is fine; in hand-written CR YAML use artifacts.userFiles, because the
apiserver prunes keys the CRD schema does not advertise. content is a typeless
x-kubernetes-preserve-unknown-fields node in both CRDs, so every legal shape —
including a bare string for format: text — is accepted at admission, and the
operator’s Pydantic validators enforce the format/content pairing on reconcile.
Where the files are written
The two execution paths reach the same result through different code:
Kubernetes service containers boot from the controller-rendered BenchmarkRun
(aiperf service --benchmark-run) and deliberately skip the resolver chain, so
that seeds, synthesized defaults, and artifact identity stay identical across
pods. The declared entries therefore travel inside the serialized run itself and
are rendered from that data — nothing is re-resolved in-pod. Only the
system_controller container writes them: it owns the run directory that the
results sidecar serves and the operator harvests, while worker pods mount their
own private /results volume.
Do not put secrets in
content. The serialized run is stored in a Kubernetes ConfigMap, so anything you write intocontentis readable by any principal withget configmapsin the benchmark namespace, and it is echoed into the harvested artifacts. Pass credentials throughpodTemplate.envFromSecretsinstead; see Configuration.
Quickstart
Result in the run directory:
Files land directly in the run directory — there is no extra per-run wrapper. The
run directory is whatever artifacts.dir resolves to: the explicit
--artifact-dir / artifacts.dir value, or ./artifacts/<auto-generated-name>
when neither was set. In-cluster the operator pins artifacts.dir to the
/results container mount, so the files are harvested alongside the standard
exports and appear under
/api/v1/results/<namespace>/<name>/runs/<epoch>/ and in aiperf kube results.
Schema
Each entry is:
Format/content compatibility:
format: jsonorformat: yamlrequires structuredcontent(dict/list/scalar).format: textrequires stringcontent.
Rendered scalars are coerced in json/yaml output. A leaf like "{{ isl }}"
with isl: 1024 is written as the number 1024, not the string "1024"; true/false
become booleans. Strings containing no Jinja2 markers are passed through untouched, so a
hand-written "1024" stays a string. format: text never coerces.
Dict keys are not rendered. Jinja2 expressions only resolve in string values,
not in dict keys. content: {"{{ model }}": "x"} writes a file with the literal key
"{{ model }}", not the resolved model name. Put templated values where they belong
— in values — or pre-flatten the dict before passing it to AIPerf.
Templating context
Inside content, you can reference:
1. User-declared variables — anything you put in the top-level variables: block of your config. Variables may reference each other (in any YAML order); cross-references are resolved in dependency order (a Kahn-style topological pass) at config-load time, so a derived variable like total_concurrency: "{{ concurrency_per_gpu * deployment_gpu_count }}" already holds its computed value by the time user_files rendering runs. Cycles raise ConfigurationError naming the participating variables.
2. System-injected names (stable API):
Collision rule: if a user variables: key shadows an injected name, the injected name wins and a WARNING is logged when the render context is built at run start. Rename your variable.
Errors
These are all fatal — the benchmark does not start.
Config-load failures reject the YAML (and, for an AIPerfJob, the CR submitted by
aiperf kube profile) before anything reaches the cluster. Run-start failures are
raised by the process executing the benchmark: locally that aborts aiperf profile, and in-cluster it aborts the system_controller container before the
benchmark begins, which the operator surfaces as status.phase=Failed.
Use cases
- Sidecar metadata — produce an
input_config.jsonfor downstream tooling that expects the dynamo-style deployment-shape file. - Run notes — write a
notes.mdsummarizing what this run is for, who triggered it. - Manifests — emit a manifest a downstream pipeline will read.
Limitations (v1)
- Pre-run only. Files render before the benchmark starts. Post-run files that include results are tracked as a future extension.
- Files always overwrite. No
overwrite: falsesafety net. - No
required: false. Every declared file must materialize successfully or the run aborts. - Strict undefined. A typo in
{{ varaibles_name }}is a hard error, not a silent empty string.