Price trainer-rank memory profiles by packed tokens, not sharing ratio - #950
Merged
Merged
Conversation
bradhilton
deployed
to
trainer-rank-gpu-validation
September 24, 2026 14:17 — with
GitHub Actions
Active
bradhilton
deployed
to
trainer-rank-gpu-validation
September 24, 2026 15:03 — with
GitHub Actions
Active
bradhilton
deployed
to
trainer-rank-gpu-validation
September 24, 2026 15:24 — with
GitHub Actions
Active
bradhilton
deployed
to
trainer-rank-gpu-validation
September 24, 2026 16:12 — with
GitHub Actions
Active
bradhilton
deployed
to
trainer-rank-gpu-validation
September 24, 2026 16:43 — with
GitHub Actions
Active
bradhilton
deployed
to
trainer-rank-gpu-validation
September 24, 2026 17:19 — with
GitHub Actions
Active
bradhilton
had a problem deploying
to
trainer-rank-gpu-validation
September 24, 2026 18:03 — with
GitHub Actions
Failure
bradhilton
had a problem deploying
to
trainer-rank-gpu-validation
September 24, 2026 18:32 — with
GitHub Actions
Failure
bradhilton
deployed
to
trainer-rank-gpu-validation
September 24, 2026 19:07 — with
GitHub Actions
Active
bradhilton
deployed
to
trainer-rank-gpu-validation
September 24, 2026 20:01 — with
GitHub Actions
Active
bradhilton
deployed
to
trainer-rank-gpu-validation
September 24, 2026 20:35 — with
GitHub Actions
Active
A memory profile learned at a lower logical/packed ratio was scaled up by the ratio gap, so a profile observed without prefix sharing priced a shared plan by its logical tokens. Measured forward+backward peaks are flat per packed token across sharing ratios 1-8 for GDN-MoE, attention-MoE, dense attention and dense GDN models, so apply profiled bytes per packed token in both the required and retained estimates. Sharing-ratio trust windows are unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Review follow-up. Profiles are per packed token, so memory that grows with logical rows is priced with outputs: the head's int64 copy of wide (2-D) labels, and the training gradient of dense logits/hidden-state outputs for requests not marked no_grad. Also remove the estimator logical_tokens argument from the landing harness and two tests that still passed it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Second review round: moving logical-sized label copies and output gradients into output_bytes made profile learning subtract bytes that forward-only observations never held. Instead, keep main's output accounting and the estimator's logical_tokens argument, and skip the sharing-ratio extrapolation only when every request mix is single-target or output-free, the case the production reports and controlled measurements cover. Wide labels and dense or top-k outputs keep the conservative extrapolation. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Third review round: key wide labels by their normalized shape (a flattened token axis with trailing target dimensions is not single-target), restrict packed pricing to fully grad-enabled signatures (no_grad GDN branch states are not charged), and charge 128 B for each logical row beyond the profile's observed sharing to cover the head's per-row label, position and row-match buffers. Tests cover the excluded mixes and the wide-label key. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Fourth review round: saved head indices and masks stay live until backward, so retained memory also charges logical rows beyond the profile's sharing. Packed pricing now requires a per-packed rate of at least the per-row charge times the profile ratio (and the retained row charge requires the same of the retained rate), keeping more-shared layouts no more expensive so lower-bound pruning stays sound. All-grad signatures with several slot groups qualify. The scope test pins exact values for packed and extrapolated signatures, ratios above one, mixed grad modes and both guards. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Fifth review round and a high-branch H200 measurement. Retained memory now falls back to the logical/packed extrapolation below the rate floor instead of dropping head rows. Costs are truncated once. Packed pricing requires per-packed rates of at least twice the per-row charge times the profile ratio, so fewer packed rows always cost less despite rounding. GDN branch states grow with segments, not packed rows: 101 twenty-token branches on a 5,100-token prompt (ratio 72.6, 8 layers) raised the warm peak per packed token from 323 KB to 378 KB, about 3.9 MB per extra segment. Packed pricing now charges one layer's recurrent state and convolution history per grad segment under full recompute (every GDN layer otherwise). The split lower bound passes no segments, so pruning stays sound. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sixth review round. The per-segment GDN charge assumes backward recomputes one layer at a time, which holds only for ART's default full/uniform/1 recompute; block or multi-layer full recompute, selective recompute, and no recompute keep more GDN states and activations live, including across split subforwards. The measurements and production reports cover only that default, so packed pricing now requires it and every other mode keeps the logical/packed extrapolation. The flag is recorded in planner reports and restored on replay. Tests pin the per-layer segment bytes by hand and the fallback for other recompute modes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Seventh review round. Megatron requires an explicit recompute method and layer count under full recompute and skips recompute in eval mode, so packed pricing now checks the exact full/uniform/1 settings and that every model chunk is training, each time it prices. Planner reports record the computed value and replay restores it. Test stubs declare ART's explicit settings, and a detector test covers selective, none, block, two-layer uniform, unset and eval mode against the extrapolated pricing. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Eighth review round. Megatron gates recompute on the decoder's training mode and config, which can diverge from the outer chunk, so the one-layer check now requires every submodule to be training and reads recompute settings from the live decoder config (stored fields only for stub models). Replay rejects a recorded flag that is not a bool. Tests cover a decoder in eval under a training chunk and show a recorded flag switches single-target pricing between packed and extrapolated. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Each request's head buffers are separate allocations rounded up to 512 B blocks, so duplicate one-token requests could exceed the 128 B row charge. Charge eight blocks per logical row beyond the profile's sharing; the rate floor scales with it, so lower bounds stay sound. Recompute detection now reads the decoder's training flag, which is what Megatron checks, instead of walking every submodule. Tests cover the live decoder config and replay of an unrecorded recompute mode. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
On an H200, a duplicate one-token request peaked at nine 512 B blocks (4,616 B) at the end of head forward, above 4 KiB x 1.1. Charge twelve blocks per row, and add a CUDA test that runs the real head and backward for 20,001 duplicates and checks the per-request peak against the charge. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Packed pricing now assumes an explicit, agreed invariant: model memory scales with packed rows, and head plus caller loss memory peaks at no more than 6 KiB per logical token. The estimate is B*max(P, L/(8r)) + 6 KiB*L plus GDN segment states, so the logical charge no longer vanishes at the profile's own density, and sharing beyond 8x the observed ratio is priced as if it were 8x. Single-target requests under 64 tokens keep the logical extrapolation. The rate floor is gone: it could switch the charge off where sharing is highest. CUDA tests run the real head with 061's CISPO loss and a fixed per-request caller, and check a 4,096 -> 64 token calibration transition against the estimator. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
bradhilton
force-pushed
the
dalinar/planner-sharing-ratio
branch
from
September 24, 2026 22:22
95e7ed3 to
058547e
Compare
bradhilton
deployed
to
trainer-rank-gpu-validation
September 24, 2026 22:22 — with
GitHub Actions
Active
The short-request gate was a request-mix key, which gave any batch with a short request its own profile signature: it started cold on the static estimate instead of main's calibrated extrapolation. Carry the gate as a signature flag excluded from identity, so those batches share the profile and price exactly as on main. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
bradhilton
deployed
to
trainer-rank-gpu-validation
September 24, 2026 22:44 — with
GitHub Actions
Active
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Memory profiles were extrapolated by the prefix-sharing ratio:
bytes_per_token * max(packed, logical / profile.logical_per_packed). So a profile learned without sharing priced a shared plan by its logical tokens. For grad-enabled single-target training requests of at least 64 tokens, this PR prices the profiled model memory by packed tokens, charges head and caller memory per logical token, and extrapolates sharing at most 8x beyond what the profile observed. Everything else keeps the existing extrapolation.Evidence
Production report. Caladan 061 (Qwen3.6-35B-A3B, 1×H200, ART
a655c051), planner reportf3f203ed. The first multi-request batch (16,205 packed / 109,065 logical tokens) came right after a single-request calibration step (logical_per_packed = 1.0). The planner predicted a 56.3 GiB peak against 6.9 GiB observed. Its required estimate was 66.5 GiB with 67.7 GiB available, so it was admitted with 1.7% headroom. Across all shared steps in that run, observed peaks fit66.6 MiB + 459,782 B/packed token.Controlled measurement. One H200, 8 layers, random weights, 16,384 packed tokens with 512-token suffixes, sharing ratios 1/2/4/8, 3 shuffled repeats. The table shows peak bytes per packed token after warm-up:
High-branch GDN measurement (same setup, 1 H200, 8 layers, raw log retained). This compares 101 twenty-token branches on a 5,100-token prompt (sharing ratio 72.6, 101 segments) with a single 7,119-token request. The warm peak per packed token rises from 323 KB to 378 KB (+17%), about 3.9 MB per extra segment. That matches one GDN layer's recurrent state (≈4.3 MB per segment), the amount live at a time under full recompute, and is now charged per segment.
The dense rows come from a retained raw log. The MoE rows were transcribed from a live log that was lost when the cluster auto-downed, so treat them as provisional. Ratios 2 and 4 are also flat. The only deviations are the first two calls of each run (cold allocation and two micro-batches), which aren't sharing effects.
One consequence of the old pricing: under a ratio-1 profile, memory-minimal (full-sharing) layouts cost as much as unshared ones, so the split ladder couldn't use sharing to fit a budget.
Changes
Supported invariant
Packed pricing assumes:
B.Callers heavier than item 2 are unsupported, and the
forward_micro_batchesdocstring says so.main's own assumption, that memory scales with logical rows at the profile's density, is likewise unenforced. The invariant was agreed with both private reviewers before implementation.Pricing
Eligibility (
_packed_priced(signature, one_layer_recompute)), all of:full/uniform/ 1, read at pricing time from the live decoder config, with the decoder in training mode (Megatron checks the decoder's own mode and skips recompute in eval mode). A chunk without a GPT decoder falls back to the constructor settings and requires every submodule to be training.True.target:singleorinactive.There is no rate floor. An earlier floor made eligibility depend on the learned ratio, which could switch the logical charge off exactly where sharing is highest.
Short requests. A batch containing a single-target request shorter than 64 tokens sets
short_requestson its memory signature and keeps the logical extrapolation. A duplicate short request adds no packed row, and per-request caller memory can outgrow a few row charges. The flag is excluded from signature identity, so such a batch shares the profile learned from longer requests and is priced exactly as onmain, rather than starting cold.Required =
B·max(P, L/(8r)) + 6 KiB·L + grad_segments·GDN_state:Pis packed rows,Lactive logical rows andrthe profile's sharing ratio.lower ≤ exactholds for split pruning; lower bounds pass zero segments.8r, so the model term grows atB/(8r)per logical token rather thanmain'sB/r.Retained, inside the existing trust windows (ratio ≤
8r,P≤ 8x the profile's):R·P + 6 KiB·L. Outside them it falls back to required, as before. The existing split-search guard for retained trust crossings stays.The 6 KiB charge (12 allocator blocks). Each request's head buffers are separate allocations rounded up to 512 B blocks. On an H200, through the real head projection path (a stub bf16 output layer) and backward, a duplicate one-token request peaked at nine blocks plus 8 B (4,616 B) at the end of head forward, and 3,080 B stayed live into backward. The slope was the same at 20,000 and 100,000 duplicates. 061's CISPO caller saves one or two tensors per request (512 B–1 KiB, plus about 5 B per token).
Cost. These figures come from the formula with the measured rates:
B·P).main: 171 GiB.The recompute flag is recorded in planner reports and restored on replay. Reports written before this change fail replay as incomplete rank fields, as with earlier new fields.
_request_mix_keyalso classifies labels by their normalized shape, matching_forward_item: an input of shape(1, N)with targets(N, K)istarget:(K,), nottarget:single.Width probes still assume up to two GDN segments per request (an upper bound). Under packed pricing that adds a per-segment charge to the cheap feasibility bound, so a probe can reject a width the exact plan would admit. This is conservative: it can never admit more.
Why the scope. Private review found memory that grows with logical rows or segments outside the head and caller budget: wide-label index copies, backward gradients of grad-enabled
logits/hidden_states, and GDN branch states inno_gradforwards. The measured and production evidence covers grad-enabled single-target training, so only that path changes.Tests
no_grad, mixed grad, dense output, wide labels) at profile ratio 2;(N,)and(1, N)inputs, withinactiveunaffected. After calibrating on a 64-token request, a 63-token batch and a mixed batch reuse that profile (none pre-seeded) and price atmain's extrapolation.L/(8r)is monotone, and learning more sharing never cheapens a plan at or below the older ratio.main's path keepmain's expectations.Validation
mainafter Apply trainer-rank memory floors per context-parallel rank #951 merged.CUDA_VISIBLE_DEVICES=""), fulltests/unit/test_trainer_rank_*.pyplustest_prefix_tree_packing.pyon the current head: 1,248 passed, 8 CUDA-only skips. The three failures also fail on currentmainin isolation: the live-graph collective test hits its wall-clock "hung" deadline, andtest_native_checkpoint_gather_after_recoveryfails a different pair of variants on each run.loss.backward()(grad-enabled,target_tokensonly), and the 061 reports come from grad-enabled training. This covers GDN models with up to 21 segments per batch.🤖 Generated with Claude Code