Skip to content

Latest commit

 

History

History
177 lines (141 loc) · 8.95 KB

File metadata and controls

177 lines (141 loc) · 8.95 KB

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:

Path Who writes the files
Local (aiperf profile, local multi-run subprocess runner) The config resolver chain (ArtifactDirResolver), which derives the render identity from the resolved artifact dir.
Kubernetes (AIPerfJob, AIPerfSweep children) The controller pod's system_controller container, before the benchmark starts, via aiperf.kubernetes.user_files.materialize_serialized_run_user_files.

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 into content is readable by any principal with get configmaps in the benchmark namespace, and it is echoed into the harvested artifacts. Pass credentials through podTemplate.envFromSecrets instead; see Configuration.

Quickstart

variables:
  isl: 1024
  osl: 512

benchmark:
  artifacts:
    user_files:
      - path: input_config.json
        format: json                 # optional; inferred from content type
        content:
          isl: "{{ isl }}"
          osl: "{{ osl }}"
          endpoint: "{{ endpoint_url }}"
          model: "{{ model }}"

      - path: meta/notes.md          # subdirectories allowed
        content: |
          Run {{ job_name }} started at {{ epoch }}.
          Targeting {{ model }} @ {{ endpoint_url }}.

  models:
    - my-org/my-model
  endpoint:
    type: chat
    urls: ["http://my-frontend:8000"]
  datasets:
    - name: main
      type: synthetic
  phases:
    - name: profiling
      type: concurrency
      concurrency: 10
      requests: 100

Result in the run directory:

{artifact_dir}/               # the resolved artifacts.dir for this run
├── input_config.json
├── meta/
│   └── notes.md
└── ... (standard AIPerf artifacts)

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:

Field Type Required Description
path string yes Output path relative to the run directory. Subdirectories OK. Absolute paths and any segment equal to .. are rejected.
format json | yaml | text no Serialization format. If omitted: text when content is a string, json otherwise.
content structured or string yes Templated value. Dict/list/scalar for json/yaml; string for text. Jinja2 expressions in any string leaf are rendered.

Format/content compatibility:

  • format: json or format: yaml requires structured content (dict/list/scalar).
  • format: text requires string content.

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

Name Type Meaning
epoch str Run epoch identifier (e.g. "1714000000"). Locally: wall-clock seconds captured at run start, unless the run-directory basename is itself epoch-shaped (9-10 digits, optionally with a 6-digit uid suffix) or the literal legacy, in which case that basename is used. Under the operator: the same epoch key the run's PVC directory is named after, so {{ epoch }} matches the /api/v1/results/<ns>/<name>/runs/<epoch>/ path the artifacts land under.
job_name str Locally: run-directory basename (or the parent directory name when epoch came from the basename). Under the operator: the AIPerfJob name.
namespace str Locally: value of AIPERF_NAMESPACE, empty string when unset. Under the operator: the CR's namespace.
model str First model name from benchmark.models / benchmark.model; empty string when none.
endpoint_url str First entry of benchmark.endpoint.urls; empty string when none.
artifact_dir str Absolute path to the run directory.

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.

Failure Cause Where you see it
Path validation Absolute path, .. segment, empty path, control chars Config load (pydantic ValidationError)
Format/content mismatch e.g. format: json with content: "string" Config load (pydantic ValidationError)
Undefined variable Template references a name not in context Run start (UserFileError); message names the file path and the variable
Path escape Resolved path is not inside the run directory Run start (UserFileError)
Write failure Disk full, permission denied, etc. Run start (UserFileError); message includes resolved path and OS error

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.json for downstream tooling that expects the dynamo-style deployment-shape file.
  • Run notes — write a notes.md summarizing 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: false safety 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.