Skip to content

TrainerRank MoE memory admission: backward-recompute OOM and profile calibration failures #848

Description

@bradhilton

September 12 update: ART #891 is merged at a3a248a; its tree equals the reviewed 75d984c tree. It accounts for the identified FC2 grouped-LoRA workspace in the cold/fallback estimate. The exact bounded pressure successor refused before execution, whereas the original admitted case OOMed. This is a specific correction, not a complete general memory bound. PR two-H200/source CI and merged-main Prek passed; the image built/prewarmed but its smoke could not provision an H200. Caladan .art-revision remains 1cefd5c at current main15cdc48.

Remaining active work: instrument actual allocator lifetimes around one unpressured cold forward. The private composed diagnostic has CPU evidence, but independent review found incomplete error/event/source joins in its offline reader; correction is required before native use. No new GPU memory attribution is claimed. Evidence: /home/brad/.local/share/schulman/art848-workspace-composed-20260912-memory and art848-composed-review-20260912-LK6qsJ. General admission, calibration, warm pricing, #869 and #870 remain distinct.


Earlier record (preserved; status above is current):

Status reconciliation — September 12, 2026 (Schulman)

Partially addressed; general admission correctness remains open and actively investigated. The merged output-lifetime/admission-evidence, inactive-request, and #889 selected-layout/check-consistency fixes address specific defects. They do not qualify a general memory bound. The private warm pricing and cache-release candidates remain distinct from current shared adoption.

New independently verified cold-admission counterexample: one H200, Qwen3.6-35B-A3B, TP/CP/DP/EP1, a fixed no-gradient auxiliary forward with 45,981 packed / 172,687 logical tokens and 14 hidden-output requests. The test deliberately held 68,451,041,280 bytes (63.75 GiB) of private CUDA ballast. The original candidate admitted 4,092,810,444 required bytes against 4,652,784,333 available bytes. That exact admitted native forward raised CUDA OutOfMemoryError; ART preserved it as the explicit cause of TrainerRankMemoryError. Actor PID/thread/admission, original check, actual forward, final container replay and cleanup are joined. This is an observed OOM after admission, not a counterfactual inferred from an earlier peak.

The failure ran on the retained private _impl89cd2194; independent AST comparison confirms that its cold arithmetic, availability check and OOM wrapper match current ART1cefd5c1 / _impleb6073bd. No warm-profile term or backward cache-release helper was exercised. The selected pricing observation agrees numerically with the original check but remains a separate observation. There were zero backwards/optimizer updates, no completed output and no measured post-failure weight-equality claim. The intentional pressure case does not establish failure of ordinary unpressured execution or a universal reserve coefficient.

The earlier identical-package attempt failed on a model-download HTTP503 before model/Trainer/forward initialization; it remains separately preserved. The one infrastructure retry above is complete. All four owned Kubernetes UIDs and host process groups are independently absent; actual outer OS wait and native container exit were1. No resource remains allocated for this test.

Private durable evidence: /home/brad/.local/share/schulman/art848-pressure-attempt02-evaluation-20260912-root/ (result SHA e7b70f0ec81024a6d21d5816f9cca78a4147976e2e975b117138ca9146ef0ede); independent review /home/brad/.local/share/schulman/art848-pressure-attempt02-review-20260912-memory/, manifest 4f6a7c4c71ee5b151abe24432071a003073325ab0f95c0b4620f8f2f788247bb. Raw captures remain private. Next: trace the actual failed allocation and derive a narrowly justified cold workspace term or pre-execution refusal; avoid tuning a generic coefficient from a single peak. Shared policy changes remain held pending behavior/scope review.

Owner: Schulman and subagents. Keep calibration overestimation, warm underpricing, #869's historical witness and #870's physical backward headroom separate. No art.megatron or API change is proposed by this evidence.


Historical report (preserved):

Found by the expanded cost-model calibration campaign (Qwen3.5-35B-A3B, TP1 × CP1 × EP1, one H200 141 GB, bf16, active LoRA slot, dev/trainer_rank_landing_acceptance.py --phase cost-calibrate). Two related memory-admission failures on this MoE model at CP1; both cells are fine at CP2/CP4.

1. Cold admission lets a forward run that then hard-OOMs. cal-grpo-g16 (106,432 logical tokens, no_sharing layout = 106k packed tokens) was admitted by the cold static estimate and died with a CUDA out-of-memory inside the expert grouped-LoRA GEMM on the first forward:

torch.OutOfMemoryError: CUDA out of memory. Tried to allocate 3.25 GiB. GPU 0 has a total capacity of 139.81 GiB of which 2.31 GiB is free ... 134.11 GiB is allocated by PyTorch
  src/art/megatron/lora.py:1161 _expert_grouped_lora_forward
  src/art/megatron/kernels/cute_grouped_lora_quack.py:326 _varlen_quack_gemm

The contract is a TrainerRankMemoryError refusal before execution, never an OOM. The cold estimate does not account for MoE expert-path working memory (grouped GEMM / LoRA intermediates over top-8 routed rows), which for a 256-expert model with 70 GB of bf16 weights leaves far less headroom than the dense estimate assumes.

2. After one observation, everything is refused. cal-grpo-g4x4 (73,696 logical tokens): the first no_sharing warm-up ran (37 s, peak 129.3 GB), and every later forward in the process was refused with "forward is predicted to exceed available memory; unable to find a feasible split: every rung of the bounded ladder ...", including uniform_depth_2 at 21,487 packed tokens (less than a third of the observed forward). The retained-memory profile learned from the single 129 GB observation makes the predictor refuse layouts that plainly fit, and the best-effort splitting ladder (#831) finds no rung feasible either, so the cell produced no measurable rows.

Evidence: scratch/trainer_rank_cost_calibration/lattice35-ep1-synth-20260904-0007/{evidence.jsonl,tp1-cp1-ep1-etp1-cal-grpo-g16-0-g0.log,tp1-cp1-ep1-etp1-cal-grpo-g4x4-0-g0.log} on the (auto-downed) cluster; local copies with the calibration artifacts. Peaks observed on the same model at CP1: cal-grpo-g8 (53k tokens) 113 GB unshared / 74 GB shared, so the admissible envelope on one H200 ends somewhere between 53k and 74k unshared tokens.

Relates to the "planner-driven head chunking / memory margins" follow-up and the cold static estimate ignoring sharding; MoE adds an expert-path term the estimate lacks. For the calibration campaign the two CP1 cells are recorded as unmeasurable on this class (the CP2/CP4/EP shapes cover the same workloads).

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions