Skip to content

GB10 TP3: DeepSeek V4.1 Flash export at 0572fd12 fails kernel tuning with a 0-byte scratch limit; 7.1-7.9 GiB available, below the 8 GiB floor #270

Open

Description

@javieratomic

What happens

On three GB10 hosts (DGX Spark class, unified memory, one rank per host), an export of DeepSeek V4.1 Flash with tensor parallelism 3 at 0572fd12 stops 17.3 to 18.9 s into "Prepare and tune kernels" on every rank. Rank 0 runs python -m ignition export deepseek-v4.1-flash --ranks 3 --rendezvous <rank 0>:<port> --config <file>, and each peer runs python -m ignition join <rank 0>:<port> --rank <1|2> --ranks 3 --gpu 0 --config <file>. The file sets only [paths] kernel_cache and core_cache; everything else is a recipe default. Each rank reports:

b12x.preparation.types.CandidateResourceIneligibleError: no eligible configurations for moe.decode within the session scratch limit of 0 bytes

RecordedPreparationSession._reserve_scratch (ignition/preparation/tuning.py:387) bounds the scratch of kernel candidates with candidate_scratch_limit (ignition/preparation/host_memory.py:153). On an integrated GPU that limit is MemAvailable minus the greater of the 8 GiB floor (_HEADROOM_BYTES) and the plan's headroom (1 GiB here), minus 1 GiB of tuning growth and the persistent bytes of plans not yet materialized (envelope.pending_persistent_nbytes), clamped at zero; the minimum across ranks applies. With the limit configured as 0, the second requirements() call (tuning.py:434) reaches FlashInfer's candidate_memory_envelope, which raises because no moe.decode configuration fits. Both Ignition functions are the same at bbad44fe; what differs is the memory available at that point, measured below on bbad44fe plus #122.

MemAvailable was sampled every 2 s on each host. The comparison export is bf5ca911, the head of #122 from 10 to 12 November: one commit on bbad44fe, a preparation-only change that makes the 8 GiB floor a setting, run with --compile-headroom 6G; it completed. The setting changes the limit, not what is allocated before it. "Before graph catalog" is the last sample before the steep fall, which begins within one sample of the start of "Construct graph catalog" (phase starts are timestamped in the peers' logs and summed from phase durations on rank 0). "Tuning, before the reservation" is the samples from the start of "Prepare and tune kernels" to the scratch reservation (_reserve_scratch): at 0572fd12 to the failure, at bf5ca911 and f25a0386 to a 1.1 to 1.2 GiB step in MemAvailable 13 to 17 s after the phase start, taken as the arena allocation.

Export Rank Before graph catalog (MiB) Tuning, before the reservation (MiB)
0572fd12, default 0 19691 7314 to 7590
0572fd12, default 1 20350 7812 to 8083
0572fd12, default 2 20389 7756 to 7970
bf5ca911, 6 GiB 0 19644 11492 to 11588
bf5ca911, 6 GiB 1 20529 12012 to 12280
bf5ca911, 6 GiB 2 20531 12082 to 12195

Every host at 0572fd12 is below the 8192 MiB floor, so the limit is 0 whatever the pending bytes are; a limit above zero needs more than 9216 MiB plus those bytes, 1133 to 1902 MiB more than the hosts had. Each host has 3902 to 4468 MiB less available before the reservation at 0572fd12 than at bf5ca911. Swap use changed by at most 26 MiB on any host between the two columns.

At bbad44fe with the default floor, two exports on these hosts completed kernel tuning (4268.2 s and 404.2 s; #242 comment, #122 comment). Those reports give no MemAvailable at the reservation; at the 11492 to 11588 MiB of the lowest host at bf5ca911, the default floor leaves a limit of 2276 to 2372 MiB less the pending bytes.

Why it matters

With recipe defaults, an export at 0572fd12 stops at a point that two exports at bbad44fe passed on the same hosts, and the error reports a 0-byte limit without the memory found or required. The failure was observed in one export at 0572fd12; an export with the same ignition/ plus #122 reached the same memory level on rank 0's host (the --compile-headroom 4G export under "Possible direction"), and the shortfall exceeds the spread of the samples. #73 reports the same 8 GiB plus 1 GiB threshold stopping kernel tuning on GB10 for a GLM-5.3-Flash TP2 export with MTP; its 9,216 MiB MemoryError is the one tuning_limits raises.

Where the memory goes

When tuning starts, ranks 1 and 2 log the growth of torch.cuda.memory_allocated while each graph was constructed, summed by graph kind ("Graph operand allocation by operation", ignition/recipes/deepseek41/bootstrap.py, same code in both commits). The two ranks report the same bytes:

Graph kind bf5ca911 (MiB) 0572fd12 (MiB)
prefill 125.6 2665.6
speculative 2700.2 3434.2
vision 0.0 789.6
resume 7.0 85.7
decode 78.5 38.4
Total, with init, restore and snapshot 2911.4 7013.5

The per-kind rows do not compare, and the totals compare to within about 151 MiB. At bbad44fe one ScratchPool backed two GraphBufferPools (bootstrap.py:414-418) whose cursor each graph and prefill layer forked into overlapping storage, so a pool chunk is charged to the first graph that grows it, and the shared suffix and window-staging buffers (bootstrap.py:429, :432-434; 63 and 88 MiB in the table below) are allocated before the counter starts. The total is 4102 MiB larger at 0572fd12, about 3950 MiB after that offset; both are within the 3902 to 4468 MiB by which MemAvailable is lower before the reservation. The part of the fall that the logged growth does not cover is 5088 to 5619 MiB on every host in both commits, taking for rank 0 the growth that ranks 1 and 2 logged. When tuning started at 0572fd12, ranks 1 and 2 reported 9645 MiB allocated and 9666 MiB reserved, so cached free blocks held 21 MiB.

That pool was replaced by compiler-planned temporaries in 4a24d78, merged through 2b38bf9 and e5e7881. 4a24d78 reports DeepSeek V4.1 on TP4 and states that "no matched serving-memory comparison was run". At 0572fd12 the example operands that operator declarations keep for tuning (declaration tensors) are alive across the reservation: finalizers registered through register_finalizer (tuning.py:746-753) release them after all tuning jobs (tuning.py:596-597), and the recipe's own releases (bootstrap.py:580-588) follow session.tune.

Allocation sites that are per decoder or per layer at 0572fd12 and came from the shared pool at bbad44fe, with sizes computed from tensor shapes in the code (10 prefill capacities in 2 modes, 64 packed verification shapes with a batch sum of 683, 40 layers), not measured:

Allocation at 0572fd12 0572fd12 (MiB) bbad44fe (MiB)
Verification window staging, one (40, batch, 136 × 528) byte buffer per verification decoder (components/ring_commit.py:60-64); bbad44fe shares one 1871 88
128-row suffix Scratch for each of 18 packed prefill decoders (recipes/deepseek41/decoder.py:263-264); bbad44fe shares one 1141 63
Compressor operands per KV-source layer and decoder (recipes/deepseek41/attention.py:151-167) 672 20
Prefill window cache per layer and decoder (attention.py:308-309) 456 2
Indexer packed queries per selector (components/mxfp4_indexer.py:62-65) 297 0.3
VisionScratch and PreparedVision operands per patch capacity (bootstrap.py:519-525) not sized not sized

Restricted to prefill decoders, the suffix, compressor, window cache and indexer sites compute to about 2485 MiB, of the 2666 MiB logged for prefill. Verification staging plus 76 MiB of compressor and indexer operands of verification decoders compute to 1947 MiB, of the 3434 MiB logged for speculative; the rest was not itemised.

Not measured

  • pending_persistent_nbytes at the reservation on either commit.
  • MemAvailable at this point at bbad44fe with the default floor.
  • Which tensors make up each graph kind's storage; the per-site sizes are computed from shapes.
  • Why MemAvailable falls a further 214 to 276 MiB on every host in the 17 s of tuning before the reservation at 0572fd12 (96 to 268 MiB at bf5ca911).
  • Whether the contents of the kernel cache change the scratch that tuning requires.
  • Other models, tensor-parallel widths and hosts.

Possible direction

  • Give the per-decoder and per-layer declarations above shared backing through declaration_tensor roles (ignition/preparation/examples.py). 07e94ca made that helper reuse one allocation per role across capacities because "Retaining separate declaration tensors for every graph capacity can exhaust device memory before tuning"; in this recipe it covers MXFP8 activation staging and MLA norm-quantization outputs, none of the sites above.
  • Release the declaration tensors that the reservation and candidate measurement do not use before the reservation, instead of after all tuning jobs.
  • Report the memory found and required when the limit is zero. tuning_limits already raises with both numbers (host_memory.py:185-187), but it runs after _reserve_scratch (tuning.py:512 against :503).

#122's --compile-headroom gets past the failure but leaves the host short of memory. With --compile-headroom 4G on f25a0386 (#122 on 0572fd12, ignition/ otherwise identical), rank 0's host had 7256 to 7483 MiB before the reservation, within about 110 MiB of the 7314 to 7590 MiB of the 0572fd12 export and against 11492 to 11588 MiB at bf5ca911. The export passed the reservation; those samples give a limit of 2136 to 2363 MiB less the pending bytes on that host. The floor is an input to the scratch limit, the tuning limits and compiler admission, not a level the host is held above: during kernel tuning that host fell to 3381 MiB available (56 of 2186 samples below 4096 MiB) and its swap use grew from 628 to 4456 MiB. That host also runs processes outside the export, and which pages moved to swap was not examined. An earlier 6 GiB export of bf5ca911, not the one in the table, spent 4271.5 s in kernel tuning and is reported in the #122 comment with a minimum of 7018 MiB on rank 0's host and swap growing from 374 to about 3000 MiB during kernel tuning; the two exports differ in floor and in kernels measured, so they do not isolate the effect of the graph operand storage. The 4 GiB export completed in 9527 s: kernel tuning took 4611.9 s, all 38 forwards compiled in 1848.1 s with one compiler worker per host, and the core was written. After kernel tuning that host's lowest sample was 6768 MiB, and its swap use peaked at 5609 MiB during compilation.

Environment

  • Three GB10 hosts (DGX Spark class, unified memory, MemTotal 121.6 GiB), one rank per host.
  • Checkpoint deepseek-ai/DeepSeek-V4.1-Flash-1017 (revision 1bae0e10), recipe defaults, compile_workers unset.
  • Page caches dropped before each export; MemAvailable 115.8 to 116.6 GiB before the start. The kernel cache directory held results of earlier bbad44fe-based exports. vm.min_free_kbytes is 45154 on rank 0's host.
  • 0572fd12 (failure), bf5ca911 with --compile-headroom 6G, f25a0386 with --compile-headroom 4G; FlashInfer as pinned by each tree (7b35510b at 0572fd12, 85ea6e76 at bbad44fe).

Activity

  1. javieratomic commented on Nov 12, 2026

    @javieratomic
    ContributorAuthor

    Measured at the scratch reservation: 0572fd12 and bbad44fe, default floor

    The start of the export was repeated for both commits on the same three hosts with recipe defaults (no --compile-headroom), page caches dropped before each, up to the scratch reservation. On every rank, a wrapper around RecordedPreparationSession._reserve_scratch recorded the meminfo counters, the torch allocator counters and every live CUDA tensor storage the garbage collector tracks (de-duplicated, grouped by dtype and shapes, with referrers for a sample of each group) before the original method ran, plus the arguments and result of each candidate_scratch_limit call inside it. 0572fd12 failed with the reported error on every rank; bbad44fe passed the reservation.

    Limit arithmetic

    candidate_scratch_limit was called once per rank with pools=1, headroom_bytes 1 GiB and pending_persistent_bytes 198,653,960 (189.5 MiB), the same on every rank in both commits. MemAvailable read immediately after the call reproduces each result to the byte as MemAvailable − 8192 MiB − 1024 MiB − 189.5 MiB, clamped at zero.

    Commit Rank MemAvailable at the call (MiB) Limit (MiB) Session limit (MiB)
    bbad44fe 0 11520.5 2115.1 2115.1
    bbad44fe 1 12124.6 2719.1 2115.1
    bbad44fe 2 12014.2 2608.7 2115.1
    0572fd12 0 7271.3 0 (−2134.2 before the clamp) 0
    0572fd12 1 7873.7 0 (−1531.7) 0
    0572fd12 2 7999.2 0 (−1406.3) 0

    At bbad44fe the reservation required 1282.8 MiB (candidate_scratch_bytes) against the 2115.1 MiB minimum, and MemAvailable fell 1087 to 1114 MiB across _reserve_scratch. At 0572fd12 a limit above zero needed more than 9405.5 MiB, 1406 to 2134 MiB more than the ranks had. MemAvailable at the call is 4015 to 4251 MiB lower at 0572fd12, and the export process's VmRSS on rank 0 is 4034 MiB higher. torch.cuda.memory_allocated and memory_reserved at 0572fd12 are 9645.9 and 9666.0 MiB on all three ranks. The allocator counters do not compare across the two commits (about 100951 MiB allocated at bbad44fe, with the same weights and caches alive), so the comparison below uses storages.

    Where the memory goes, measured

    Tracked CUDA storage is 4273.1 MiB larger at 0572fd12 on every rank. Storages of the same byte size in both inventories cancel; that includes the KV, indexer-cache and weight storages, which changed owner or view layout but not size. Rank 0, with the computed sizes from the issue; ranks 1 and 2 give the same bytes and counts:

    Allocation at 0572fd12 Measured 0572fd12 (MiB, storages) Computed Measured bbad44fe (MiB) Computed
    Verification window staging, (40, batch, 71808) uint8 1870.9 (64; batch sum 683) 1871 87.7 (1 storage) 88
    128-row suffix Scratch of 18 packed prefill decoders 1139.1 (matched by dtype, shape and count: every field of 128 KiB or more has at least 18 storages of its shape; 2.0 MiB of smaller fields are in groups not listed individually) 1141 no separate storage 63
    Compressor operands 637.7 (546, FP32 and BF16 (tokens, 512) with PreparedCompressor owners); smaller shapes not listed 672 no separate storage 20
    Prefill window cache 456.3 (800 = 20 decoders × 40 layers) 456 no separate storage 2
    Indexer packed queries and scales 270.3 (2044; 998 + 998 of 128 rows); smaller row counts not listed 297 no separate storage 0.3
    VisionScratch and PreparedVision operands, 5 patch capacities 780.2 (matched by dtype and shape) 784 (from VisionScratch.allocate; not sized in the issue) no separate storage –
    Total 5154.5 87.7

    At bbad44fe only the staging buffer has a storage of its own; in the code the other sites take cursors of the shared GraphBufferPool, so they cannot be sized separately there. The vision row compares with the 789.6 MiB logged for vision in the issue's graph-kind table.

    Not in the issue's table:

    • Present only at bbad44fe, or larger there, 1519.4 MiB, including speculative sampling buffers (probabilities, top-p workspaces, logits and log-probability keys, about 982 MiB), PreparedTableRequests.token_map vectors (161.8 MiB; 166 storages against 2) and per-graph page tables (about 270 MiB).
    • New or larger at 0572fd12 besides the sites, 548.1 MiB, including the 224-row draft scratch (about 163 MiB) and three more 40 MiB prefill scratch storages (9 against 6).
    • Groups too small to be listed individually: 188.5 MiB at 0572fd12 against 11.0 MiB at bbad44fe.

    Sites +5066.8 MiB, the rest of the listed storage −971.3 MiB and the unlisted groups +177.5 MiB add up to the +4273.1 MiB within rounding, against MemAvailable 4015 to 4251 MiB lower at the call.

    Still not measured

    • Referrers were searched two levels deep within an 8 s budget that ran out on every rank. Many storages are attributed only to generic containers, so the suffix and vision rows are matched by dtype, shape and count.
    • The compressor and indexer operands beyond the listed groups (computed 34 and 26 MiB more).
    • The per-site sizes at bbad44fe other than the staging buffer.
    • The fall in MemAvailable before the reservation. At 0572fd12 it fell 30 to 239 MiB between the start of the inventory and the limit call, but the inventory itself ran in that interval.
  2. added
    area:memoryMemory budgets, allocation lifetimes, scratch sharing, and capacity.
    area:preparationIgnition checkpoint loading, tuning, compilation, capture, and export.
    bugSomething isn't working
    model:deepseek-v4.1Affected recipe or documented reproducer: deepseek-v4.1. Impact may be broader.
    priority:P2Normal bug priority: workflow failure or diagnosis with narrower impact or an available workaround.
    triage:investigateNeeds reproduction, diagnosis, or a fix; no complete verified resolution is established.
    on Nov 13, 2026
  3. original-el8 commented on Nov 13, 2026

    @original-el8
    Collaborator

    Categorized as a P2 preparation-memory investigation. The default export reaches kernel tuning below the headroom floor, so candidate scratch is bounded to zero. #122 supplies a configurable floor, but its successful lower-floor export does not explain or remove the additional graph-catalog memory reported here.

    Track the live allocations between catalog construction and scratch reservation, including pending persistent plans, on each rank. Keep #242's compiler-admission failure separate from this earlier tuning failure. A successful reduced-floor run must also report swap and memory pressure; it is not evidence that the default allocation budget is repaired.

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

    area:memoryMemory budgets, allocation lifetimes, scratch sharing, and capacity.area:preparationIgnition checkpoint loading, tuning, compilation, capture, and export.bugSomething isn't workingmodel:deepseek-v4.1Affected recipe or documented reproducer: deepseek-v4.1. Impact may be broader.priority:P2Normal bug priority: workflow failure or diagnosis with narrower impact or an available workaround.triage:investigateNeeds reproduction, diagnosis, or a fix; no complete verified resolution is established.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions