SLURM Job Arrays
SLURM Job Arrays
Use a SLURM job array to split a large set of source tasks across independent NeMo Curator pipeline runs. Each array task builds the same deterministic source-task list, and NeMo Curator keeps only the tasks assigned to that logical shard.
This topology differs from one multi-node Ray cluster:
An array task normally uses RayClient on one node. If you allocate multiple nodes to each array task, use SlurmRayClient inside that task.
Prerequisites
- A SLURM cluster with job-array support (for example,
sbatch --array=0-19). - A NeMo Curator environment visible from every compute node. This can be a shared source checkout, a container image, or a
.sqshimage file mounted through your site’s SLURM container integration. - Input, output, and checkpoint directories on a shared filesystem or mounted at the same path in every container.
- Source stages that emit deterministic task IDs. For the most common array workflows built around
FilePartitioningStage, this works out of the box. If a source stage is incompatible, NeMo Curator raises a clear error instead of silently sharding the wrong tasks. NeMo Curator also rejects source IDs beginning withrbecause ambiguous IDs cannot be assigned consistently across retries.
The repository includes these reference files:
array_pipeline.py is only a minimal example. Any NeMo Curator pipeline whose source stage supports deterministic task IDs can run under the same array configuration.
Run a JSONL array
The steps below show one bare-metal setup on a shared filesystem. The same array variables and checkpoint path also work with containerized SLURM launches, including NGC images and site-specific .sqsh files, through tutorials/slurm/submit_array.sh.
Prepare a source checkout and editable environment:
Set paths that are visible inside every array task, then submit 20 logical shards:
submit_array.sh defaults to JSONL input and output with one input file per source task. Override FILES_PER_PARTITION to group files before sharding.
Use a checkpoint directory dedicated to one logical array run. Reuse it for retries of that run, but choose a new directory for unrelated input, pipeline, or shard configuration.
Use Parquet
Select Parquet independently for input and output:
The example accepts jsonl or parquet for each file type. The array assignment mechanism is the same for both.
How source sharding works
For every source task, NeMo Curator computes:
The calculation converts the first 16 hexadecimal characters—the first 64 bits—of the SHA-256 digest to an integer before applying the modulo.
The source-stage adapter keeps only tasks whose logical shard matches the active array task. Hash-based assignment provides these properties:
- The same deterministic task ID returns to the same shard on retry.
- Shards do not require contiguous input-file ranges.
- Some shards can be empty when there are more shards than source tasks.
- Changing
total_shardsorminimum_shard_indexchanges assignment. Keep both values unchanged during retries.
Every array task still discovers the full deterministic source-task list before filtering. Job arrays are most useful when source discovery is inexpensive compared with downstream processing.
Array configuration
NeMo Curator attempts to enable source filtering when either a shard index or total shard count is available. A valid configuration requires both the shard index and total shard count; if either is missing, configuration raises an error instead of running with partial sharding.
Values are validated before source filtering:
total_shardsmust be greater than zero.minimum_shard_indexandshard_indexmust be non-negative.- The assignable range is
minimum_shard_indexthroughminimum_shard_index + total_shards - 1. - An index outside that range receives no source tasks and logs a warning.
For an array starting at 1 rather than 0, set the minimum explicitly:
Ordinary local and non-array runs are unchanged when no array configuration is present.
Choose the Ray client per array task
The bundled script uses one node per array task and starts RayClient. When an array task spans multiple nodes, it passes --slurm to array_pipeline.py, which selects SlurmRayClient:
For multi-node tasks, SLURM_NODEID=0 is the driver. Only the driver writes shard-completion metadata and stops the Ray cluster. See Multi-Node Ray on SLURM for port broadcasting and shared-filesystem requirements.
Completion and failure tracking
Pass the same checkpoint_path to every shard. Pipeline.run() creates the logical run configuration and one completion manifest per successful shard:
run.json records the original minimum_shard_index and total_shards. NeMo Curator atomically creates or validates this file, preventing a retry from silently changing the logical assignment.
Retries occur at shard granularity. A FailedTask does not cause only that individual task to be resubmitted.
Source stages cannot emit FailedTask. A source failure lacks a stable upstream identity that guarantees recovery, so NeMo Curator raises an error. Raise the source exception and leave the shard incomplete instead.
Each attempt uses a fresh failed-task directory derived from the SLURM job, array task, restart count, and logical shard. Old failure manifests remain available for inspection but cannot make a later successful attempt appear failed. If you set NEMO_CURATOR_FAILED_TASKS_DIR yourself, keep it attempt-scoped.
For source-level completion counters, per-writer LMDB files, and at-least-once sink behavior, see Resumable Processing.
Retry incomplete shards
Run retry discovery after all submissions for the logical run have finished:
Each output line contains:
For example, 1-2,5-10,99 0 0 100 means those logical shards are incomplete and the original run used 100 shards starting at 0. Preserve all four values when resubmitting:
An empty result means every logical shard has a completion manifest. A missing run.json is an error: use the original checkpoint path.
Work within cluster array limits
If the cluster limits the number of tasks in one submission, keep a larger logical TOTAL_SHARDS and submit it in windows:
If the scheduler also rejects high physical array indices, reuse 0-999 and apply offsets:
Keep MINIMUM_SHARD_INDEX=0 in both windowing modes. SHARD_INDEX_OFFSET changes the logical shard exported by the submit script; MINIMUM_SHARD_INDEX changes the assignment range itself.
For retry discovery on a cluster with a maximum physical array size, let the helper split missing shards and compute offsets:
--max-array-size requires --format fields because each output submission can have a different offset. The limit is not stored in run.json; it is a scheduler property, not part of logical task assignment.
Operational checklist
- Keep input task identity,
TOTAL_SHARDS, andMINIMUM_SHARD_INDEXstable across retries. - Reuse the checkpoint path for one logical run; use a fresh path for new work.
- Wait for every initial/windowed submission to finish before discovering retries.
- Make output naming deterministic or writes idempotent because incomplete shards can replay work.
- Monitor empty-shard warnings when logical shards outnumber source tasks.