Skip to content

Add ndd.compile.invariant - #6429

Merged
rostan-t merged 4 commits into
NVIDIA:mainfrom
rostan-t:ndd-compile-invariant
Jul 30, 2026
Merged

Add ndd.compile.invariant#6429
rostan-t merged 4 commits into
NVIDIA:mainfrom
rostan-t:ndd-compile-invariant

Conversation

@rostan-t

Copy link
Copy Markdown
Collaborator

Category:

New feature (non-breaking change which adds functionality)

Description:

For an operator to be captured in transparent pipelining, we require all of its arguments to be either outputs of another captured operator or constants. We statically detect invariant function parameters, local variables and closure cells. However, it is not tractable to extend this static analysis to e.g. module globals or attributes.

This PR introduces ndd.compile.invariant, which allows marking any variable as invariant in order for it to be blindly trusted during capture in transparent pipelining. This function returns a proxy object that tries to be as close as possible to the original object and is undistinguishable from it in most cases:

>>> x = [1, 2, 3]
>>> y = ndd.compile.invariant(x); print(y)
[1, 2, 3]
>>> y.append(4)
>>> print(x)
[1, 2, 3, 4]
>>> y + [5]
[1, 2, 3, 4, 5]
>>> isinstance(y, list)
True
>>> type(y)
<class 'nvidia.dali.experimental.dynamic.compile._invariant._Invariant_list'>

Objects marked with ndd.compile.invariant also propagate their invariant property to their attributes, allowing for such cases:

args = parser.parse()
args = ndd.compile.invariant(args)

...

for jpegs, labels in reader.next_epoch(batch_size=32, compile=True):
    images = ndd.decoders.image(
        jpegs,
        device=args.device,
        output_type=types.RGB,
        hw_decoder_load=args.hw_load,
        preallocate_width_hint=args.width_hint,
        preallocate_height_hint=args.height_hint,
    )

Additional information:

Affected modules and functionalities:

Dynamic mode, transparent pipelining

Key points relevant for the review:

Two things to review:

  • ndd.compile.invariant itself
  • Its integration in ndd and transparent pipelining

Tests:

  • Existing tests apply
  • New tests added
    • Python tests
    • GTests
    • Benchmark
    • Other
  • N/A

Checklist

Documentation

  • Existing documentation applies
  • Documentation updated
    • Docstring
    • Doxygen
    • RST
    • Jupyter
    • Other
  • N/A

DALI team only

Requirements

  • Implements new requirements
  • Affects existing requirements
  • N/A

REQ IDs: N/A

JIRA TASK: DALI-4818

Signed-off-by: Rostan Tabet <rtabet@nvidia.com>
@rostan-t rostan-t added the dynamic mode Related to dynamic mode label Jul 22, 2026
Comment thread dali/test/python/experimental_mode/test_invariant.py Dismissed
@greptile-apps

greptile-apps Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces ndd.compile.invariant, a proxy-based mechanism for marking values as invariant to enable transparent pipelining capture of arguments whose stability cannot be proven from source (module globals, attributes, etc.). The proxy forwards all dunder and non-dunder operations to the wrapped value while propagating the invariant property to attribute accesses.

  • New compile/_invariant.py: Implements the _InvariantProxy base class, the _DunderForwarder descriptor, proxy type caching, and helpers (unwrap_invariant, unwrap_invariants, unwrap_invariant_args).
  • Integration across operator pipeline: All key DALI entry points (Batch.__init__, Tensor.__init__, Operator.__init__, ExternalSource, _op_builder, _ops, _batch2tensor) unwrap invariant proxies before internal processing.
  • _source_analysis.py: _Classifier is updated so that when source code is unavailable (e.g., exec'd code), the classifier still accepts CompiledBatch and explicitly-invariant arguments, while rejecting all others (previously the whole classify would abort).

Confidence Score: 5/5

Safe to merge. The proxy design is sound, all operator entry points correctly unwrap invariant proxies before internal processing, and the _Classifier gracefully degrades when source is unavailable.

The invariant proxy correctly delegates dunders and attribute accesses, handles None-sentinel values throughout the pipeline, and the _value_matches / _matches split cleanly separates the tracing and replay responsibilities. The only finding is a local variable name reuse in _DunderForwarder.call that has no runtime impact.

Files Needing Attention: No files require special attention.

Important Files Changed

Filename Overview
dali/python/nvidia/dali/experimental/dynamic/compile/_invariant.py New module implementing the invariant proxy. Proxy type caching, dunder forwarding, descriptor handling, and recursive container unwrapping are all correct. Minor variable-shadowing style issue in _DunderForwarder.call for the setitem branch.
dali/python/nvidia/dali/experimental/dynamic/_source_analysis.py _Classifier now accepts None module_info, falling back to node=None for all positional/keyword args so that CompiledBatch and explicitly-invariant values are still captured even from exec'd or unavailable source. Logic is correct and covered by test_invariant_marker_without_source.
dali/python/nvidia/dali/experimental/dynamic/_compile.py _value_matches correctly handles invariant vs plain, invariant vs invariant (trusted), and plain vs plain. record() matching replaces the old single-expression equality with per-element _value_matches calls. _wire_compile_graph unwraps invariants before CompileRef check and None-filters correctly.
dali/python/nvidia/dali/experimental/dynamic/_batch.py unwrap_invariant_args added at all relevant entry points (Batch.init, Batch.broadcast, batch(), as_batch()); unwrap_invariants applied before np.array() calls on sample data.
dali/python/nvidia/dali/experimental/dynamic/_op_builder.py unwrap_invariant applied at batch_size, device resolution, rng, and tensor-arg conversion points. The init_args/call_args loop refactor correctly preserves None-filtering semantics.
dali/test/python/experimental_mode/test_invariant.py New unit tests cover None/int/str/list/type values, attribute propagation, method re-binding, dunder forwarding, recursive unwrapping, and ndd API integration.
dali/test/python/experimental_mode/test_compile_invariants.py New compile-integration tests cover invariant in expressions, as a parameter, in a list, from exec'd code, mixed with globals (expect_captured=False), and removal-of-marker RuntimeError.

Sequence Diagram

sequenceDiagram
    participant User
    participant invariant as ndd.compile.invariant
    participant Proxy as _InvariantProxy
    participant Classifier as _Classifier
    participant CompileCtx as CompileContext

    User->>invariant: invariant(value)
    invariant->>Proxy: _make_proxy_type(type(value))
    Proxy-->>User: proxy wrapping value

    Note over User,CompileCtx: Tracing phase (first iteration)
    User->>CompileCtx: "ndd.rotate(images, angle=proxy)"
    CompileCtx->>Classifier: classify(inputs, kwargs)
    Classifier->>Classifier: _capture_arg(node, proxy)
    Classifier->>Classifier: _is_explicit_invariant(proxy) → True
    Classifier-->>CompileCtx: "kwargs[angle] = proxy (invariant)"
    CompileCtx->>CompileCtx: record() → store node with invariant kwargs

    Note over User,CompileCtx: Replay phase (subsequent iterations)
    User->>CompileCtx: "ndd.rotate(images, angle=proxy2)"
    CompileCtx->>CompileCtx: get_compiled_result()
    CompileCtx->>CompileCtx: _matches(proxy2, proxy_expected)
    CompileCtx->>CompileCtx: _value_matches → both invariant → True
    CompileCtx-->>User: cached compiled result

    Note over User,CompileCtx: Error case: marker removed
    User->>CompileCtx: "ndd.rotate(images, angle=plain_value)"
    CompileCtx->>CompileCtx: _value_matches(plain, invariant) → RuntimeError
Loading

Reviews (3): Last reviewed commit: "Test ndd.compile.invariant in transparen..." | Re-trigger Greptile

Comment thread dali/python/nvidia/dali/experimental/dynamic/compile/_invariant.py
Comment thread dali/python/nvidia/dali/experimental/dynamic/_compile.py
Comment thread dali/python/nvidia/dali/experimental/dynamic/_batch.py
Comment thread .gitignore
Comment thread dali/test/python/experimental_mode/test_invariant.py
raise RuntimeError(
"An argument marked with ndd.compile.invariant when captured must remain marked."
)
return 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.

Do we care if actual == expected?

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.

Marking a variable with ndd.compile.invariant asserts that the value won't change. This allows us to avoid to check for equality, which can be quite expensive for some types.

Comment thread dali/test/python/experimental_mode/test_invariant.py
Comment thread dali/test/python/experimental_mode/test_invariant.py
@rostan-t

Copy link
Copy Markdown
Collaborator Author

!build

@dali-automaton

Copy link
Copy Markdown
Collaborator

CI MESSAGE: [59986947]: BUILD STARTED

@dali-automaton

Copy link
Copy Markdown
Collaborator

CI MESSAGE: [59986947]: BUILD PASSED

rostan-t added 3 commits July 29, 2026 08:24
Signed-off-by: Rostan Tabet <rtabet@nvidia.com>
Signed-off-by: Rostan Tabet <rtabet@nvidia.com>
Signed-off-by: Rostan Tabet <rtabet@nvidia.com>
@rostan-t

Copy link
Copy Markdown
Collaborator Author

!build

@rostan-t
rostan-t force-pushed the ndd-compile-invariant branch from 81eb017 to 5d8f694 Compare July 29, 2026 08:26
@dali-automaton

Copy link
Copy Markdown
Collaborator

CI MESSAGE: [60098020]: BUILD STARTED

@rostan-t
rostan-t requested a review from mdabek-nvidia July 29, 2026 10:26
@dali-automaton

Copy link
Copy Markdown
Collaborator

CI MESSAGE: [60098020]: BUILD PASSED

@rostan-t
rostan-t merged commit 1fe9a52 into NVIDIA:main Jul 30, 2026
8 checks passed
@rostan-t
rostan-t deleted the ndd-compile-invariant branch July 30, 2026 15:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dynamic mode Related to dynamic mode

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants