Skip to content

Add opt-in adaptive data-parallel search trials - #868

Open
alxmrs wants to merge 3 commits into
feature/resource-aware-local-searchfrom
feature/adaptive-search-data-parallel
Open

Add opt-in adaptive data-parallel search trials#868
alxmrs wants to merge 3 commits into
feature/resource-aware-local-searchfrom
feature/adaptive-search-data-parallel

Conversation

@alxmrs

@alxmrs alxmrs commented Sep 3, 2026

Copy link
Copy Markdown
Member

Summary

  • add an opt-in adaptive_data_parallel search resource policy
  • expand surviving candidates onto idle GPUs while preserving the configured local batch size and a fixed effective global batch
  • launch candidate-local torchrun jobs on local GPUs and across homogeneous nodes in an existing Slurm allocation
  • plan uniform placements across partially used nodes (for example, world size 8 as 4 ranks per node on a 2×6-GPU allocation) and filter out unplaceable custom world sizes
  • persist each rung's resource plan so controller retries keep identical training semantics
  • fail fast with recovery guidance if a local retry exposes fewer GPUs than its persisted plan requires
  • warn and fall back to the largest compatible world size when a candidate batch size cannot use the requested GPUs; never override the configured batch size

Scientific semantics

The planner computes gradient_accumulation_steps = effective_global_batch_size / (batch_size * world_size). A fixed-global-batch sampler partitions the same epoch-seeded optimizer batches across ranks and accumulation microsteps, so changing world size does not change the samples or optimizer-step count. Learning-rate and scheduler settings are therefore left unchanged. DDP synchronization is skipped on non-final accumulation microsteps with no_sync().

Candidate configs must use backend: auto, which lets the existing init_distributed_mode() machinery select single-process execution or initialize from torchrun environment variables. Fixed anchors remain single-GPU. The separately submitted Slurm-array executor rejects this policy because those jobs do not share an allocation.

Batch-size and placement warnings

If effective_global_batch_size is not divisible by batch_size * requested_world_size, the controller emits a warning with the candidate and configured batch size, preserves that choice, and selects the largest smaller compatible world size. If the batch size cannot realize the target even on one GPU, adaptive scaling is disabled for that candidate and its original accumulation setting is retained.

The allocation executor separately constrains plans to world sizes that can be distributed uniformly without exceeding the visible GPUs on any node. If topology rules out an otherwise compatible world size, the controller warns and selects the largest placeable alternative.

Testing

  • uv run python -m pytest tests/test_search_resources.py tests/test_samplers.py tests/test_search.py -q — 82 passed
  • uv run python -m pytest -m "not manual and not cuda" -n auto — 480 passed, 2 skipped, 10 xfailed
  • uvx pre-commit run --all-files — all hooks passed

Tests cover 1/2/4-rank optimizer-batch invariance, adaptive expansion, compatible and irreconcilable batch warnings, retry plan persistence, local multi-GPU launch construction, partially occupied multi-node placement, and Slurm/torchrun rendezvous construction. This machine has no CUDA/Slurm allocation, so the real 8- and 16-GPU smoke runs remain deployment checks.

Stack

This is intentionally based on #865 and contains only the adaptive data-parallel follow-up. It can be retargeted to main after #865 merges.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0b8d6e175c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +104 to +108
if world_size % per_node:
raise ValueError(
f"world_size={world_size} cannot be placed on homogeneous "
f"{per_node}-GPU nodes"
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Choose a placeable world size for multi-node trials

On allocations where the per-node GPU count does not divide the planned world size, this aborts even though the trial fits. For example, the default allowed sizes select world_size=8 for one survivor in a two-node, six-GPU-per-node allocation, but eight processes could run as four per node; this branch instead raises because 8 % 6 != 0. Account for node topology while planning, or launch a uniform subset of each node's GPUs rather than requiring every participating node to be full.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

🤖 Addressed in cd88672. The Slurm allocation executor now plans only uniformly placeable world sizes and computes an explicit (nodes, processes_per_node) placement. On a 2×6-GPU allocation, world size 8 now launches as 2 nodes × 4 ranks instead of requiring divisibility by all six GPUs per node. Impossible custom world sizes are filtered during planning, with a warning and fallback to the largest placeable compatible size.

@alxmrs
alxmrs force-pushed the feature/adaptive-search-data-parallel branch from 0b8d6e1 to 4d3ce68 Compare September 3, 2026 20:21
@alxmrs

alxmrs commented Sep 3, 2026

Copy link
Copy Markdown
Member Author

🤖 A real W&B observability failure surfaced in the Torch run: SuccessiveHalving.train_task() adds the full timestamped search run_id as a tag. Our generated ID was 73 characters, over W&B's 64-character tag limit, so every worker logged Failed to initialize wandb and continued training untracked. The run ID is already represented by wandb.group and experiment.search.run_id; I recommend omitting it from tags (while retaining search, the stable search slug, and candidate slug). I have applied this on the experiment integration branch and added a regression assertion.

@alxmrs
alxmrs force-pushed the feature/adaptive-search-data-parallel branch from 4d3ce68 to c90c8ba Compare September 3, 2026 23:39
@alxmrs
alxmrs force-pushed the feature/adaptive-search-data-parallel branch from c90c8ba to cd88672 Compare September 3, 2026 23:56
@alxmrs

alxmrs commented Sep 3, 2026

Copy link
Copy Markdown
Member Author

🤖 Addressed the W&B observability failure reported in the experiment. The fix landed in the stacked base PR (#865) as bf9a1e2 and is inherited here: the potentially long search run ID is no longer added as a W&B tag, while the complete ID remains available through the W&B group and structured experiment.search.run_id metadata. A regression assertion verifies that the long run ID is absent from tags. This matches the useful part of the experiment worktree fix without importing its cluster-specific QoS, container, or NCCL settings.

@alxmrs
alxmrs force-pushed the feature/adaptive-search-data-parallel branch 2 times, most recently from 8b49218 to 7a84d2a Compare September 4, 2026 01:18
@alxmrs
alxmrs force-pushed the feature/resource-aware-local-search branch from f088b69 to 652d674 Compare September 4, 2026 01:18
@alxmrs
alxmrs force-pushed the feature/adaptive-search-data-parallel branch from 7a84d2a to be7ed07 Compare September 4, 2026 01:19
@alxmrs

alxmrs commented Sep 4, 2026

Copy link
Copy Markdown
Member Author

🤖 A final retry-safety review found and fixed a possible local deadlock in be7ed07. If a persisted adaptive plan requires more GPUs than a retry exposes, the local device pool now fails immediately with recovery guidance instead of waiting forever. The docs also explain how to derive effective_global_batch_size from the scientific baseline, using the experiment migration pattern (batch_size × accumulation × world_size). Focused combined coverage passes (83 tests), and this branch is restacked on the current #865.

@alxmrs

alxmrs commented Sep 4, 2026

Copy link
Copy Markdown
Member Author

@codex will you review this PR?

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: be7ed070e8

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

).stdout.splitlines()
if not hosts:
raise RuntimeError(f"Slurm returned no hosts for {node_list!r}")
node_rank = int(os.environ["SLURM_NODEID"])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Use the step task rank for torchrun

When concurrent srun --exclusive steps receive a subset of a larger allocation, SLURM_NODEID is the node's job-allocation index rather than a dense rank within this step. A two-node trial placed on allocation nodes 2 and 3 therefore passes node ranks 2 and 3 to torchrun --nnodes=2, so rendezvous cannot complete. Use the step's dense SLURM_PROCID, as the existing launchers do in scripts/slurm_apptainer_train.sbatch:461 and scripts/empireai_nvl72_train.sbatch:224.

Useful? React with 👍 / 👎.

Comment thread src/samudra/train.py
Comment on lines +1196 to +1197
if adaptive_batching:
train_batch_sampler = FixedGlobalBatchSampler.from_datasets(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve batch-normalization behavior across adaptive rungs

For candidates containing BatchNorm—including the shipped Samudra configurations—enabling this adaptive path does not preserve the claimed training trajectory. At world_size=1 the model uses ordinary per-local-batch statistics, while distributed rungs convert it to SyncBatchNorm in Trainer.__init__, whose statistics cover local_batch_size * world_size; gradient accumulation and the fixed sampler cannot make those forward-pass statistics equivalent. Consequently, expanding a survivor changes its optimization semantics despite holding the effective global batch fixed; either reject such models for adaptive scaling or keep normalization behavior invariant.

Useful? React with 👍 / 👎.

Comment on lines +275 to +280
complete = len(indices) // self.global_batch_size
optimizer_batches.extend(
indices[start : start + self.global_batch_size]
for start in range(
0,
complete * self.global_batch_size,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Carry equivalence-group tails into optimizer updates

When training has multiple resolution groups, computing complete independently for every group discards up to effective_global_batch_size - 1 samples per group, whereas the previous sampler allowed homogeneous microbatches from different groups to participate in one accumulation cycle. For example, two 48-sample groups with a global batch of 64 previously provide three optimizer batches but this sampler provides only two; if every group is smaller than 64, construction fails even though their combined microbatches are sufficient. This changes both epoch sample coverage and optimizer-step counts, so preserve group-homogeneous microsteps while carrying their tails across accumulation cycles.

AGENTS.md reference: AGENTS.md:L18-L20

Useful? React with 👍 / 👎.

@alxmrs

alxmrs commented Sep 4, 2026

Copy link
Copy Markdown
Member Author

🤖 Final experiment-integration review at be7ed070: I merged this exact head into the Perceiver experiment branch and the focused search suites pass (51 tests). I found no new adaptive world-size or fixed-global-batch correctness issue. The remaining stack-level blocker is inherited from #865: allocation/controller interruption leaves pooled searches durably marked running with no reconcile/resume path (reproduced again on Torch job 16975829). Separately, real 2° runs showed that allocation-aware GPU packing can still yield 7–36% model duty when storage is starved; I am preparing a small stacked documentation PR recommending a one-rung utilization smoke and CPU/storage-aware max_concurrent selection. I view that as operational guidance, not a blocker in this PR.

@alxmrs

alxmrs commented Sep 5, 2026

Copy link
Copy Markdown
Member Author

🤖 Empirical follow-up: the exact be7ed070 head is now exercised in a completed one-rung Torch search. With the unchanged flat-channel 2° Zarr staged once to node-local SSD, moment16-local at batch 16 / accumulation 2 / eight loader workers sustained 78% mean SM utilization over one-second samples and completed/published normally. This validates the local resource pool under the tested one-GPU plan. It also exposed a deployment caveat rather than a new #868 correctness issue: train and validation each paid ~200 seconds of loader-process startup while importing from a congested shared Python environment. Stacked documentation PR #873 records how to distinguish steady-state from whole-job utilization and recommends node-local data plus container/environment placement and enough rung duration to amortize startup.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

1 participant