Skip to content

Add typed tensor artifact outputs to V2 - #524

Open
jmccaffrey-nv wants to merge 3 commits into
mainfrom
dev/jmccaffrey/v2-tensor-artifacts
Open

Add typed tensor artifact outputs to V2#524
jmccaffrey-nv wants to merge 3 commits into
mainfrom
dev/jmccaffrey/v2-tensor-artifacts

Conversation

@jmccaffrey-nv

@jmccaffrey-nv jmccaffrey-nv commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • add model-neutral typed tensor artifact schemas and outputs to the V2 session/result contracts
  • add a generation-aware ModelOutputSink path that receives complete model-step channel batches independently of UI presentation and the legacy metrics sink
  • persist requested artifacts as transactional NumPy files with a machine-readable commit manifest

Contract details

  • artifact names are unique across the complete result-channel batch for a model step
  • reset generations use latest-generation-wins semantics; newer results clear buffered older output and late stale results are ignored
  • schemas, ranks, NumPy-compatible dtypes, and concatenated/non-concatenated shapes are validated before a batch is accepted
  • --tensor-artifact-dir fails before application/window startup when an application declares no artifact schemas
  • the sink creates and validates its destination during open, before generation starts
  • tensor_artifacts.json records completeness plus each declared artifact’s emitted state, dimension names, concatenation axis, path, dtype, and shape
  • only successful model execution commits buffered arrays and a complete: true manifest; failed execution discards buffered arrays and leaves the manifest incomplete
  • declared artifacts remain optional; a successful run removes stale files for declared artifacts it did not emit
  • publication stages backups for every declared artifact and restores the prior artifact set if array replacement, stale-file removal, or final manifest promotion fails

Validation

  • python -m pytest flashdreams/test_v2 -m ci_cpu -q — 276 passed
  • pre-commit run --files <changed files> — passed, including Ruff and ty

Stack

This is the model-neutral prerequisite for #520. It contains no LingBot-specific code, tests, documentation, or assets. PR #520 will be rebased onto this branch and reduced to the LingBot model plus its V2 adapter.

@copy-pr-bot

copy-pr-bot Bot commented Aug 26, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@jmccaffrey-nv
jmccaffrey-nv marked this pull request as ready for review August 26, 2026 14:45
@greptile-apps

greptile-apps Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR adds typed tensor-artifact schemas and a generation-aware model-output sink to runtime V2.

  • Routes complete model-step result batches independently of UI presentation and legacy metrics.
  • Persists declared artifacts as NumPy files with a machine-readable transactional manifest.
  • Adds CLI wiring, lifecycle handling, validation, documentation, and focused tests.

Confidence Score: 5/5

The PR appears safe to merge because no blocking failure remains.

No blocking failure remains.

Important Files Changed

Filename Overview
flashdreams/flashdreams/runtime_v2/tensor_artifact_output_sink.py Implements typed artifact validation, generation buffering, NumPy persistence, and transactional manifest publication.
flashdreams/flashdreams/runtime_v2/session_runner.py Adds generation-aware delivery of complete model-result batches and commit-aware sink shutdown.
flashdreams/flashdreams/runtime_v2/tensor_artifact.py Defines validated, model-neutral tensor artifact schemas and outputs.
flashdreams/flashdreams/runtime_v2/cli.py Adds tensor-artifact output selection and rejects applications without declared schemas before startup.
flashdreams/test_v2/test_tensor_artifact_output_sink.py Covers schema validation, generation handling, successful persistence, stale-file cleanup, and publication rollback.

Sequence Diagram

sequenceDiagram
    participant CLI
    participant Runner as Session Runner
    participant Model
    participant Sink as Tensor Artifact Sink
    CLI->>Sink: Construct with output directory
    Runner->>Sink: open(session description)
    Sink->>Sink: Publish incomplete manifest
    loop Model steps
        Model-->>Runner: Complete channel batch
        Runner->>Sink: write(generation, results)
        Sink->>Sink: Validate and buffer artifacts
    end
    Runner->>Sink: "close(commit=true)"
    Sink->>Sink: Stage arrays, backups, and complete manifest
    Sink->>Sink: Publish arrays and manifest
Loading

Reviews (4): Last reviewed commit: "Address tensor artifact review feedback" | Re-trigger Greptile

Comment thread flashdreams/flashdreams/runtime_v2/tensor_artifact_output_sink.py
f"dimensions from {tuple(first.shape)} to {tuple(tensor.shape)}."
)

def close(self) -> None:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it intentional that a failed run writes its artifacts? run_session's finally (session_runner.py:224) closes every sink regardless of how the run ended, and close() here persists whatever chunks it managed to buffer before re-raising the failure.

For a streaming model that emits one chunk per step, a run that dies at step 6 of 10 leaves an actions.npy with the right name, the right dtype, and a shape that looks perfectly reasonable, nothing in the file says how many chunks were supposed to be in it. The metrics sink and the MP4 writer don't really have this problem, since a short JSON or a short video is obviously short, but a tensor isn't self-describing.

Would it make sense to either skip the write when the run failed, or record somewhere the consumer can see what the file actually contains?

@jmccaffrey-nv jmccaffrey-nv Aug 27, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was not intentional, and the lifecycle now distinguishes commit from cleanup. ModelOutputSink.close(commit=False) is used when model execution fails, so the tensor sink discards buffered chunks. It writes an incomplete manifest during open() and changes it to complete: true only after successful array publication. An end-to-end regression emits one chunk, fails on the next step, and verifies that no partial .npy file is committed.

Evaluated and implemented by GPT-5.6 Sol via Codex.

if not outputs:
return

self._output_dir.mkdir(parents=True, exist_ok=True)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mkdir happens in close(), so if --tensor-artifact-dir points at something that's already a file, or a directory the process can't write to, the run fails after the whole rollout has finished. open() on line 37 already receives the session and could mkdir(parents=True, exist_ok=True) there instead.

Asking because cli.py:80 already fails fast on the other half of this flag, it rejects the directory before window startup when the application declares no artifacts, so the intent seems to be to catch bad input early.

@jmccaffrey-nv jmccaffrey-nv Aug 27, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed and moved this validation to open(). The sink now creates the directory and atomically writes its initial incomplete manifest before generation, so a path that is already a file or cannot be written fails at startup. The invalid-file destination is covered by a focused regression.

Evaluated and implemented by GPT-5.6 Sol via Codex.

name: str
"""Stable name used to route and persist the artifact."""

dimension_names: tuple[str, ...]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dimension_names is doing one functional job here, giving the expected rank so TensorArtifactOutput can check it. The names themselves never reach the output: np.save with allow_pickle=False stores dtype and shape only, so someone loading actions.npy sees (64, 16) and has to read the integration source to find out which axis is which.

Since the API goes to the trouble of declaring them, would it be worth writing them out too? Like a sidecar JSON, or .npz with the names attached? Otherwise they're documentation that only exists on the producing side.

@jmccaffrey-nv jmccaffrey-nv Aug 27, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. I kept the .npy format for compatibility and added a tensor_artifacts.json sidecar manifest. It preserves each declared artifact dimension name and concatenation axis, and records emitted state, path, dtype, and shape. The manifest also acts as the completeness marker. CLI, sink, and documentation tests verify the consumer-visible metadata.

Evaluated and implemented by GPT-5.6 Sol via Codex.

@jarcherNV jarcherNV left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall LGTM. I left a few comments but I don't think any are blocking. Up to you if you think they are worth addressing or not.

@jmccaffrey-nv

jmccaffrey-nv commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator Author

Review follow-up: current head 0f0eb2a addresses both Greptile findings and all three Jesse Archer suggestions. Successful runs remove omitted declared artifact files and publish a complete schema manifest; failed model execution discards buffered arrays and leaves an incomplete marker; destination validation happens before generation; and dimension metadata is consumer-visible. Publication now stages backups and restores overwritten or removed prior artifacts if any array or final-manifest publication step fails. After rebasing on current main, the complete V2 CPU suite passes with 276 tests, and pre-commit passes including Ruff and ty.

Evaluated and implemented by GPT-5.6 Sol via Codex.

Signed-off-by: Jonathan McCaffrey <jmccaffrey@nvidia.com>
Signed-off-by: Jonathan McCaffrey <jmccaffrey@nvidia.com>
@jmccaffrey-nv
jmccaffrey-nv force-pushed the dev/jmccaffrey/v2-tensor-artifacts branch from 5a6a109 to b1be7c9 Compare August 27, 2026 22:20
Comment thread flashdreams/flashdreams/runtime_v2/tensor_artifact_output_sink.py Outdated
@jmccaffrey-nv
jmccaffrey-nv force-pushed the dev/jmccaffrey/v2-tensor-artifacts branch from b1be7c9 to 0f0eb2a Compare August 27, 2026 22:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants