·
Last changedVersion:
This version passed the hardening gate. The skill content was retained without changes. Classified findings before hardening: 2. Classified findings closed: 0. Classified findings remaining: 2. Unclassified scanner findings remaining: 0. Review findings closed: 0. Review findings remaining: 10.
1
1
1
1
Expand | File | Group | Subtype | Severity | Status |
|---|---|---|---|---|---|
SKILL.md | Prompt Injection | Direct prompt injection | Medium | Open | |
scripts/requirements.txt | Supply Chain & Install-Time Trust | Dependency or plugin compromise | Low | Open |
2 findings
Guide AI agents in writing, reviewing, and migrating code that uses DALI's imperative dynamic-mode API, nvidia.dali.experimental.dynamic (ndd).
nvidia.dali.experimental.dynamic as ndd and write code as direct ndd calls in ordinary Python; do not use pipeline-mode APIs such as Pipeline, @pipeline_def, pipe.build(), or pipe.run().batch_size to next_epoch(...).batch_size to random ops; there is no pipeline-level batch size to inherit.device="gpu" instead of pipeline-mode "mixed", Batch.tensors[...] for sample selection, and Batch.slice[...] for per-sample slicing..torch() to convert a tensor or batch to a PyTorch tensor. Use pad=True for batches with variable shapes.nvidia.dali.experimental.dynamic..torch().Dynamic mode is DALI's imperative Python API. It lets code call DALI operators directly from normal Python control flow instead of building and running a pipeline graph.
Supports __dlpack__, __cuda_array_interface__, __array__, arithmetic operators.
Batch has no __getitem__ -- batch[i] raises TypeError because indexing is ambiguous (sample selection vs. per-sample slicing). Use the explicit APIs instead:
| Intent | Method | Returns |
|---|---|---|
Get sample i |
|
|
Get subset of samples |
|
|
Slice within each sample |
|
|
Sample-wise slicing |
|
|
.tensors[] picks which samples. .slice indexes inside each sample.
The .slice[] API accepts batches of indices, allowing the user to mix and match batches and
scalar values, e.g.:
PyTorch conversion:
batch.torch() -- works for uniform shapes; raises for ragged batchesbatch.torch(pad=True) -- zero-pads ragged batches to max shape (use for variable-length audio, detection boxes, etc.)batch.torch(copy=None) is the default (avoids copy if possible)__dlpack__ -- use ndd.as_tensor(batch) first for DLPack consumers. ndd.as_tensor supports pad as well.Tensor.torch(copy=False) is default (no copy)Iteration: for sample in batch: yields Tensors.
Readers are stateful objects -- create once, reuse across epochs. This matters because readers track internal state like shuffle order and shard position.
Key points:
labels.torch().to(device)).ndd.readers.File(...), ndd.readers.COCO(...), ndd.readers.TFRecord(...)batch_size goes to next_epoch(), not to the reader constructornext_epoch(batch_size=N) yields tuples of Batch; next_epoch() without batch_size yields tuples of Tensornext_epoch() must be fully consumed before calling next_epoch() againSharded reading for distributed training:
device="gpu" (NOT "mixed"). The "mixed" keyword is a pipeline-mode concept for implicit CPU-to-GPU transfer; in dynamic mode, passing device="gpu" triggers the same hardware-accelerated decode path..cpu() before passing to a GPU model -- .torch() gives you a GPU tensor directly. .cpu() is only needed for consumers requiring host memory (numpy, __array__).Default mode is eager -- async execution in a background thread, returns immediately.
No .evaluate() needed in most cases. Any data consumption (.torch(), __dlpack__, __array__, .shape, property access, iteration) triggers evaluation automatically.
For debugging, switch to synchronous mode so errors surface at the exact call site rather than later in the async queue:
Modes (increasing synchronicity): deferred < eager < sync_cpu < sync_full
Use EvalMode.sync_full for debugging instead of scattering .evaluate() calls -- it's cleaner and catches all issues at once. sync_cpu is often sufficient and lighter than sync_full.
Controls DALI's internal worker threads for CPU operators. Defaults to CPU affinity count or DALI_NUM_THREADS env var. Unrelated to Python-level threading.
Two approaches (use one, not both):
When rng= is passed to a random op, the explicit RNG overrides the default seed. Thread-local: each thread has independent random state.
Random ops need an explicit batch_size when working with batches -- there is no pipeline-level batch size to inherit.
Dynamic mode has no pipeline-level checkpoint. Checkpoints aggregate the state of individual stateful objects: readers and RNG instances. Stateless ops (decoders, resize, rotate, normalize, ...) are not part of a checkpoint.
Restoring is the symmetric operation -- build a fresh reader and RNG, then load + register. The loaded state is applied to each object at register time:
Key rules:
enable_checkpointing=True. Registering an already-iterated reader without it raises RuntimeError; if the reader has not been iterated yet, register enables it retroactively.next_epoch call. The prefetch thread starts on first iteration and the snapshot queue is locked after that. set_state (or a register from a loaded checkpoint) on an already-iterated reader raises RuntimeError.enable_checkpointing=True is incompatible with compile=True. Calling reader.next_epoch(..., compile=True) on a checkpointing-enabled reader raises NotImplementedError.register(op) uses sequential keys (__op_0, __op_1, ...) so the registration order must match between save and restore. Type tags catch cross-type swaps but not reorders of compatible types. Prefer register(op, name).ndd.checkpoint.current() returns the Checkpoint bound to the current thread-local EvalContext. It's shared across calls -- call ckpt.clear() if reusing the default context for unrelated runs.save/load take a Python format string with a single {seq} placeholder (e.g. "ckpt_{seq:04d}.json"). save picks the next free sequence; load picks the highest matching one on disk.deserialize rejects payloads from a different checkpoint format version -- no automatic upgrade.Checkpoint per thread.Manual get_state / set_state is also available directly on each Reader and RNG -- the Checkpoint aggregator is built on top of it. Use the manual API only when integrating with an external checkpoint system.
| Wrong | Right | Why |
|---|---|---|
|
|
|
|
|
|
|
|
|
| Let consumption trigger eval |
|
|
| Avoids wasteful D2H + H2D round-trip |
Recreate reader each epoch |
| Readers are stateful -- create once, reuse |
|
| Reader classes are PascalCase |
| Exhaust iterator or create new reader | Iterator must be fully consumed before next |
No |
| No pipeline-level batch size to inherit |
| Register the freshly built reader before the first iteration | Reader state can only be applied before the prefetch thread starts |
Restoring into a reader built without | Pass | Backend doesn't keep snapshots otherwise |
Spelling out default argument values | Skip default argument values | Very high Python-side overhead, especially when the argument accepts Tensors/Batches. Skipping arguments uses a fast path, actually passing a sentinel value. |
| Pipeline Mode | Dynamic Mode |
|---|---|
| Direct function calls in a loop |
|
|
|
|
|
|
Pipeline-level |
|
Pipeline-level |
|
Pipeline-level |
|
|
|
|
|
|
|
|
|
Dynamic mode is more flexible than pipeline mode, but can have slightly worse performance. For maximum throughput, prefer pipeline mode.
EvalMode.sync_cpu or EvalMode.sync_full.next_epoch() iterator is fully consumed.Get started with chainctlchainctl skill install public/nvidia/dali-dynamic-mode:latest
Bring Chainguard's hardening pipeline to your first-party skills — custom rulesets, automated remediation, full audit trail.
skills.chainreg.biz/public/github.com/nvidia/skills/dali-dynamic-mode:latest
Apache-2.0
BENCHMARK.md
HARDENING.md
LICENSE
SKILL.md
evals/evals.json
evals/files/pipeline_to_convert.py
scripts/requirements.txt
skill-card.md
skill.oms.sig