Skip to content

[HiCache] Read host memory once per TP group before splitting the budget - #38157

Open
alilxxey wants to merge 1 commit into
sgl-project:mainfrom
alilxxey:fix/hicache-host-budget-snapshot
Open

alilxxey wants to merge 1 commit into
sgl-project:mainfrom
alilxxey:fix/hicache-host-budget-snapshot

Conversation

@alilxxey

@alilxxey alilxxey commented Sep 5, 2026 •

Copy link
Copy Markdown

Motivation

Fixes #38156.

host_memory_budget_bytes() (#35540) splits psutil.virtual_memory().available - 10 GiB by ranks_per_host() so that co-located ranks do not oversubscribe the host. The split is only right for a reading taken before any co-located rank allocates. Each rank reads available when it reaches the guard, and TP ranks reach it seconds apart: pinning a large host pool is slow and nothing synchronizes the ranks between pools. A rank that arrives after k peers have pinned their pools sees available reduced by k × pool while still dividing by the full rank count, so the peers' pools are charged twice and the guard raises ValueError: Not enough host memory available on a host with room for all of them. With a sidecar pool (DSA indexer, MiniMax index-K, mamba) this repeats on the second guard after the first pool is pinned everywhere. On a tp8 host with 1772 GiB and --hicache-size 176 (8 × 164 GiB KV + 8 × 33 GiB indexer, 86 GiB to spare) the start is intermittent; see the issue for the arithmetic.

Modifications

  • host_memory_sync_group(): the TP cpu_group of a multi-rank run, None otherwise. TP ranks of one pipeline stage build the same host pools in the same order, so a collective issued once per pool is matched on every rank; the world group gives no such guarantee (pipeline stages own different layers and build different pools), which is why [HiCache] Split the host-memory budget across co-located ranks #35540 avoided a collective there.
  • host_memory_budget_bytes(): reduce the reading to the group-wide MIN over that group before subtracting the reserve and splitting. The collective is a barrier, so no rank allocates before all of them have read, and the minimum is the latest reading, which already reflects every pool the group built earlier. Same pattern as sync_fixed_hicache_size() in the same constructor (PP-group MIN) and get_available_gpu_memory(distributed=True). Single-rank and non-distributed behavior is unchanged; the six guards that call the helper are untouched.
  • Unit tests (test_mem_pool_host.py, TestHostMemoryBudget): the budget is sized from the group-wide minimum with ReduceOp.MIN on an int64 tensor over the sync group; no collective and the local reading without a group; the sync group is the TP cpu_group for multi-rank runs and None for single-rank runs, before distributed init, and before the TP group is built. Also exercised end to end on a real 3-process gloo group with divergent per-rank readings over two pool rounds: every rank gets the same minimum-derived budget, and a single-process run with sglang's real _TP at world size 1 never issues the collective.

Known limits, unchanged by this PR: with several pipeline stages on one host the stages still race each other (the window shrinks to the stage); with a TP group spanning hosts the minimum may come from a peer host, which is conservative; ranks_per_host() still counts only the ranks of this process group, so data-parallel replicas launched as separate groups are not accounted for.

Accuracy Tests

Not applicable: this changes only the host-pool sizing guards, not model forward paths.

Checklist


CI States

Latest PR Test (Base): ❌ Run #33991104005
Latest PR Test (Extra): ❌ Run #33991103784
Latest PR Test (AMD ROCm 7.2): ❌ Run #33991103904

host_memory_budget_bytes() divides psutil's available host memory by the
number of co-located ranks (sgl-project#35540). Each rank read it when it reached a
pool's sizing guard, so a rank arriving after its peers had pinned their
pools saw that memory gone from `available` while still dividing by the
full rank count, and the guard rejected configurations that fit. With a
sidecar pool (DSA indexer, MiniMax index-K, mamba) the same happened again
on the second guard.

Reduce the reading to the TP group-wide minimum first. The collective is a
barrier, so no rank allocates before all of them have read, and the minimum
is the latest reading, which already reflects every pool the group built
earlier. TP ranks of one pipeline stage build the same host pools in the
same order, so the collective is matched on every rank; the world group
gives no such guarantee, which is why sgl-project#35540 avoided a collective there.
Single-rank and non-distributed runs keep the local reading.
@github-actions github-actions Bot added the hicache Hierarchical Caching for SGLang label Sep 5, 2026
@alphabetc1 alphabetc1 self-assigned this Sep 7, 2026
Venkat2811 added a commit to datacrunch-research/sglang that referenced this pull request Oct 3, 2026
… not headroom // ranks (upstream sgl-project#38157)

`host_memory_budget_bytes()` outside a budget scope returned
`free // ranks_per_host()`, where `free` is whatever host headroom is left at
the moment the rank asks. On an 8-GPU pod the ranks arrive at different times,
so a rank that checks after its peers have pinned their pools sees a smaller
`free`, divides it by 8 again, and is refused although every pool fits. Seen on
2026-10-01 in the Kimi-K3 L3 gates (one rank refused with "Requesting 24.77 GB
but only have 22.74 GB free"); the same recipe passed on other runs because it
is a race.

This commit returns the whole remaining headroom (clamped at 0) for the
explicit-size guard and updates the two unit tests that pinned the split. The
guard now rejects a single pool that cannot fit at all; it no longer tries to
catch co-located pools that jointly exceed the host, which the recipes size
explicitly. HiCache auto-sizing keeps its per-rank budget: the division moves
to its call site in hicache_auto_size.py, so the default-ratio path and its
tests are unchanged.

Upstream fix: sgl-project#38157 (issue sgl-project#38156) reads host memory once per TP group before
splitting the budget. It predates sgl-project#40135's rewrite of this file and does not
apply to v0.5.21, so this layer carries the one-line form instead. Drop it when
sgl-project#38157 or a successor merges.

Upstream: sgl-project#38157
Issue: sgl-project#38156

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Venkat2811 added a commit to datacrunch-research/sglang that referenced this pull request Oct 3, 2026
… not headroom // ranks (upstream sgl-project#38157)

`host_memory_budget_bytes()` outside a budget scope returned
`free // ranks_per_host()`, where `free` is whatever host headroom is left at
the moment the rank asks. On an 8-GPU pod the ranks arrive at different times,
so a rank that checks after its peers have pinned their pools sees a smaller
`free`, divides it by 8 again, and is refused although every pool fits. Seen on
2026-10-01 in the Kimi-K3 L3 gates (one rank refused with "Requesting 24.77 GB
but only have 22.74 GB free"); the same recipe passed on other runs because it
is a race.

This commit returns the whole remaining headroom (clamped at 0) for the
explicit-size guard and updates the two unit tests that pinned the split. The
guard now rejects a single pool that cannot fit at all; it no longer tries to
catch co-located pools that jointly exceed the host, which the recipes size
explicitly. HiCache auto-sizing keeps its per-rank budget: the division moves
to its call site in hicache_auto_size.py, so the default-ratio path and its
tests are unchanged.

Upstream fix: sgl-project#38157 (issue sgl-project#38156) reads host memory once per TP group before
splitting the budget. It predates sgl-project#40135's rewrite of this file and does not
apply to v0.5.21, so this layer carries the one-line form instead. Drop it when
sgl-project#38157 or a successor merges.

Upstream: sgl-project#38157
Issue: sgl-project#38156

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Venkat2811 added a commit to datacrunch-research/sglang that referenced this pull request Oct 3, 2026
… not headroom // ranks (upstream sgl-project#38157)

`host_memory_budget_bytes()` outside a budget scope returned
`free // ranks_per_host()`, where `free` is whatever host headroom is left at
the moment the rank asks. On an 8-GPU pod the ranks arrive at different times,
so a rank that checks after its peers have pinned their pools sees a smaller
`free`, divides it by 8 again, and is refused although every pool fits. Seen on
2026-10-01 in the Kimi-K3 L3 gates (one rank refused with "Requesting 24.77 GB
but only have 22.74 GB free"); the same recipe passed on other runs because it
is a race.

This commit returns the whole remaining headroom (clamped at 0) for the
explicit-size guard and updates the two unit tests that pinned the split. The
guard now rejects a single pool that cannot fit at all; it no longer tries to
catch co-located pools that jointly exceed the host, which the recipes size
explicitly.

The two places that use the budget to *size* a pool rather than to check one
keep this rank's share through the new host_memory_budget_bytes_per_rank():
HiCache auto-sizing (hicache_auto_size.py) and the unified page-envelope pool
when both --hicache-size and --hicache-ratio are 0 (pool_host/unified.py).
Their behaviour is unchanged.

Upstream fix: sgl-project#38157 (issue sgl-project#38156) reads host memory once per TP group before
splitting the budget. It predates sgl-project#40135's rewrite of this file and does not
apply to v0.5.21, so this layer carries the one-line form instead. Drop it when
sgl-project#38157 or a successor merges.

Upstream: sgl-project#38157
Issue: sgl-project#38156

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Venkat2811 added a commit to datacrunch-research/sglang that referenced this pull request Oct 3, 2026
… not headroom // ranks (upstream sgl-project#38157)

`host_memory_budget_bytes()` outside a budget scope returned
`free // ranks_per_host()`, where `free` is whatever host headroom is left at
the moment the rank asks. On an 8-GPU pod the ranks arrive at different times,
so a rank that checks after its peers have pinned their pools sees a smaller
`free`, divides it by 8 again, and is refused although every pool fits. Seen on
2026-10-01 in the Kimi-K3 L3 gates (one rank refused with "Requesting 24.77 GB
but only have 22.74 GB free"); the same recipe passed on other runs because it
is a race.

This commit returns the whole remaining headroom (clamped at 0) for the
explicit-size guard and updates the two unit tests that pinned the split. The
guard now rejects a single pool that cannot fit at all; it no longer tries to
catch co-located pools that jointly exceed the host, which the recipes size
explicitly.

The two places that use the budget to *size* a pool rather than to check one
keep this rank's share through the new host_memory_budget_bytes_per_rank():
HiCache auto-sizing (hicache_auto_size.py) and the unified page-envelope pool
when both --hicache-size and --hicache-ratio are 0 (pool_host/unified.py).
Their behaviour is unchanged.

Upstream fix: sgl-project#38157 (issue sgl-project#38156) reads host memory once per TP group before
splitting the budget. It predates sgl-project#40135's rewrite of this file and does not
apply to v0.5.21, so this layer carries the one-line form instead. Drop it when
sgl-project#38157 or a successor merges.

Upstream: sgl-project#38157
Issue: sgl-project#38156

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Venkat2811 added a commit to datacrunch-research/sglang that referenced this pull request Oct 3, 2026
… not headroom // ranks (upstream sgl-project#38157)

`host_memory_budget_bytes()` outside a budget scope returned
`free // ranks_per_host()`, where `free` is whatever host headroom is left at
the moment the rank asks. On an 8-GPU pod the ranks arrive at different times,
so a rank that checks after its peers have pinned their pools sees a smaller
`free`, divides it by 8 again, and is refused although every pool fits. Seen on
2026-10-01 in the Kimi-K3 L3 gates (one rank refused with "Requesting 24.77 GB
but only have 22.74 GB free"); the same recipe passed on other runs because it
is a race.

This commit returns the whole remaining headroom (clamped at 0) for the
explicit-size guard and updates the two unit tests that pinned the split. The
guard now rejects a single pool that cannot fit at all; it no longer tries to
catch co-located pools that jointly exceed the host, which the recipes size
explicitly.

The two places that use the budget to *size* a pool rather than to check one
keep this rank's share through the new host_memory_budget_bytes_per_rank():
HiCache auto-sizing (hicache_auto_size.py) and the unified page-envelope pool
when both --hicache-size and --hicache-ratio are 0 (pool_host/unified.py).
Their behaviour is unchanged.

Upstream fix: sgl-project#38157 (issue sgl-project#38156) reads host memory once per TP group before
splitting the budget. It predates sgl-project#40135's rewrite of this file and does not
apply to v0.5.21, so this layer carries the one-line form instead. Drop it when
sgl-project#38157 or a successor merges.

Upstream: sgl-project#38157
Issue: sgl-project#38156

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Venkat2811 added a commit to datacrunch-research/sglang that referenced this pull request Oct 3, 2026
… not headroom // ranks (upstream sgl-project#38157)

`host_memory_budget_bytes()` outside a budget scope returned
`free // ranks_per_host()`, where `free` is whatever host headroom is left at
the moment the rank asks. On an 8-GPU pod the ranks arrive at different times,
so a rank that checks after its peers have pinned their pools sees a smaller
`free`, divides it by 8 again, and is refused although every pool fits. Seen on
2026-10-01 in the Kimi-K3 L3 gates (one rank refused with "Requesting 24.77 GB
but only have 22.74 GB free"); the same recipe passed on other runs because it
is a race.

This commit returns the whole remaining headroom (clamped at 0) for the
explicit-size guard and updates the two unit tests that pinned the split. The
guard now rejects a single pool that cannot fit at all; it no longer tries to
catch co-located pools that jointly exceed the host, which the recipes size
explicitly.

The two places that use the budget to *size* a pool rather than to check one
keep this rank's share through the new host_memory_budget_bytes_per_rank():
HiCache auto-sizing (hicache_auto_size.py) and the unified page-envelope pool
when both --hicache-size and --hicache-ratio are 0 (pool_host/unified.py).
Their behaviour is unchanged.

Upstream fix: sgl-project#38157 (issue sgl-project#38156) reads host memory once per TP group before
splitting the budget. It predates sgl-project#40135's rewrite of this file and does not
apply to v0.5.21, so this layer carries the one-line form instead. Drop it when
sgl-project#38157 or a successor merges.

Upstream: sgl-project#38157
Issue: sgl-project#38156

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Venkat2811 added a commit to datacrunch-research/sglang that referenced this pull request Oct 3, 2026
… not headroom // ranks (upstream sgl-project#38157)

`host_memory_budget_bytes()` outside a budget scope returned
`free // ranks_per_host()`, where `free` is whatever host headroom is left at
the moment the rank asks. On an 8-GPU pod the ranks arrive at different times,
so a rank that checks after its peers have pinned their pools sees a smaller
`free`, divides it by 8 again, and is refused although every pool fits. Seen on
2026-10-01 in the Kimi-K3 L3 gates (one rank refused with "Requesting 24.77 GB
but only have 22.74 GB free"); the same recipe passed on other runs because it
is a race.

This commit returns the whole remaining headroom (clamped at 0) for the
explicit-size guard and updates the two unit tests that pinned the split. The
guard now rejects a single pool that cannot fit at all; it no longer tries to
catch co-located pools that jointly exceed the host, which the recipes size
explicitly.

The two places that use the budget to *size* a pool rather than to check one
keep this rank's share through the new host_memory_budget_bytes_per_rank():
HiCache auto-sizing (hicache_auto_size.py) and the unified page-envelope pool
when both --hicache-size and --hicache-ratio are 0 (pool_host/unified.py).
Their behaviour is unchanged.

Upstream fix: sgl-project#38157 (issue sgl-project#38156) reads host memory once per TP group before
splitting the budget. It predates sgl-project#40135's rewrite of this file and does not
apply to v0.5.21, so this layer carries the one-line form instead. Drop it when
sgl-project#38157 or a successor merges.

Upstream: sgl-project#38157
Issue: sgl-project#38156

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Venkat2811 added a commit to datacrunch-research/sglang that referenced this pull request Oct 3, 2026
… not headroom // ranks (upstream sgl-project#38157)

`host_memory_budget_bytes()` outside a budget scope returned
`free // ranks_per_host()`, where `free` is whatever host headroom is left at
the moment the rank asks. On an 8-GPU pod the ranks arrive at different times,
so a rank that checks after its peers have pinned their pools sees a smaller
`free`, divides it by 8 again, and is refused although every pool fits. Seen on
2026-10-01 in the Kimi-K3 L3 gates (one rank refused with "Requesting 24.77 GB
but only have 22.74 GB free"); the same recipe passed on other runs because it
is a race.

This commit returns the whole remaining headroom (clamped at 0) for the
explicit-size guard and updates the two unit tests that pinned the split. The
guard now rejects a single pool that cannot fit at all; it no longer tries to
catch co-located pools that jointly exceed the host, which the recipes size
explicitly.

The two places that use the budget to *size* a pool rather than to check one
keep this rank's share through the new host_memory_budget_bytes_per_rank():
HiCache auto-sizing (hicache_auto_size.py) and the unified page-envelope pool
when both --hicache-size and --hicache-ratio are 0 (pool_host/unified.py).
Their behaviour is unchanged.

Upstream fix: sgl-project#38157 (issue sgl-project#38156) reads host memory once per TP group before
splitting the budget. It predates sgl-project#40135's rewrite of this file and does not
apply to v0.5.21, so this layer carries the one-line form instead. Drop it when
sgl-project#38157 or a successor merges.

Upstream: sgl-project#38157
Issue: sgl-project#38156

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Venkat2811 added a commit to datacrunch-research/sglang that referenced this pull request Oct 3, 2026
… headroom // ranks (upstream sgl-project#38157)

Squash of the kimi-k3 integration PR #13 (layer 5b).

host_memory_budget_bytes() outside a budget scope returned free // ranks_per_host(), where free is whatever host headroom is left at the moment the rank asks. On an 8-GPU pod the ranks arrive at different times, so a rank that checks after its peers have pinned their pools divides a smaller headroom by 8 again and is refused although every pool fits (seen 2026-10-01 in the Kimi-K3 L3 gates: "Requesting 24.77 GB but only have 22.74 GB free"). The guard now checks a pool against the whole remaining headroom; the two unit tests that pinned the split are updated.

The two callers that size a pool rather than check one, HiCache auto-sizing (hicache_auto_size.py) and the unified page-envelope pool when both --hicache-size and --hicache-ratio are 0 (pool_host/unified.py), keep this rank's share through the new host_memory_budget_bytes_per_rank(); their behaviour is unchanged.

Upstream fix sgl-project#38157 (issue sgl-project#38156) predates sgl-project#40135's rewrite of this file and does not apply to v0.5.21. Retire this layer when sgl-project#38157 or a successor merges.

Upstream: sgl-project#38157
Issue: sgl-project#38156

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

This branch has not been deployed

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

Labels

hicache Hierarchical Caching for SGLang

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] HiCache host-memory sizing guard charges co-located ranks twice and rejects pools that fit (host_memory_budget_bytes, #35540)

2 participants