[diffusion] refactor: hand out pinned host memory per layer (#35867)

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Mick
2026-08-22 09:37:31 +08:00
committed by GitHub
co-authored by Claude Opus 5
parent b26695a26e
commit 0be2a209ac
2 changed files with 395 additions and 70 deletions
@@ -209,6 +209,8 @@ class LayerwiseOffloadManager:
resident_layers: int = 0,
initialize: bool = True,
residency_policy: str = RESIDENCY_POLICY_LEADING,
pin_budget: HostPinBudget | None = None,
pin_component_name: str = "layerwise offload",
) -> None:
self.model = model
self.layers_attr_str = layers_attr_str
@@ -217,6 +219,13 @@ class LayerwiseOffloadManager:
# mps shares physical memory with the CPU and has no pinned host memory
# or CUDA-style copy streams
self.pin_cpu_memory = bool(pin_cpu_memory and not self._synchronous_mps)
# asked per layer rather than for the whole component; see
# _plan_pinned_layers
# A missing budget is not a licence to ignore host memory: without one
# every layer looked affordable and the copies-do-not-fit check below
# was never reached. A private budget reads the same host limit.
self._pin_budget = pin_budget if pin_budget is not None else HostPinBudget()
self._pin_component_name = pin_component_name
# an explicit MPS zero avoids staging the next layer alongside the
# active one; MPS has no transfer overlap to recover from that cost
self.prefetch_size = (
@@ -276,7 +285,7 @@ class LayerwiseOffloadManager:
# layer_idx -> {name: tensor still viewing the checkpoint file}
# Weights left on their mapping rather than copied into host memory, so
# the page cache decides what stays resident. Used when the copies would
# not fit; see _keep_weights_on_their_mapping.
# not fit; see _plan_layer_hosting.
self._mapped_cpu_weights: Dict[int, Dict[str, torch.Tensor]] = {}
self._mapped_bytes = 0
# mps keeps the original CPU tensor for each layer instead of building a
@@ -390,47 +399,127 @@ class LayerwiseOffloadManager:
self._finalize_initialization()
def _keep_weights_on_their_mapping(self, layer_groups: Dict) -> bool:
"""Whether to leave file-backed weights on their mapping.
def _layer_byte_totals(
self, layer_groups: Dict
) -> Tuple[Dict[int, int], Dict[int, int]]:
"""Per layer: (all weight bytes, the subset that are checkpoint views)."""
totals: Dict[int, int] = {}
mapped: Dict[int, int] = {}
for layer_idx, dtype_to_params in layer_groups.items():
total = 0
from_mapping = 0
for weights in dtype_to_params.values():
for _, weight in weights:
tensor = self._to_local_tensor(weight)
nbytes = tensor.untyped_storage().nbytes()
total += nbytes
if self._mapped_regions.holds(tensor):
from_mapping += nbytes
totals[layer_idx] = total
mapped[layer_idx] = from_mapping
return totals, mapped
Copying them into host memory buys pinning, and pinning is what lets the
copy stream run ahead of compute -- worth 1.03 s against 1.90 s per step
on a measured Wan2.1 run, so it is not given up lightly.
def _plan_layer_hosting(self, layer_groups: Dict) -> Dict[int, str]:
"""Where each layer's weights live on the host: pinned, pageable or mapped.
It is given up when the copies do not fit. H3's DiT is 61.73 GiB of
weights, of which 50.53 GiB arrive as views into the checkpoint; on a
32 GiB host the copy cannot be made at all, and the choice is between a
mapping and not running. Above that, page-cache residency makes the read
nearly as fast as pinned -- 12.38 GB/s against 13.39 measured -- and what
is lost is the overlap, not the bandwidth.
Pinning is what lets the copy stream run ahead of compute; a pageable
or mapped source transfers synchronously however it is requested.
The budget used to be asked for the whole component at once, so a DiT
larger than the whole spendable budget pinned nothing at all. Asking
per layer spends what there is.
A layer that misses the budget falls back the way it always did, to a
pageable copy, and only stays on its mapping when those copies do not
fit either. The order matters: a pageable copy transfers synchronously,
since the driver stages it through its own pinned buffer, so it buys
none of the overlap -- but it is guaranteed resident, where a mapping can
be dropped and re-read from disk.
Which layers get pinned matters only through how often each is read.
A streamed layer is transferred once per denoise step; a resident one is
transferred once per stage, so a pin on it is worth about 1/steps of the
same pin on a streamed layer. Streamed layers therefore get the budget
first, in streamed order, which is also deterministic. Unpinning a
resident layer costs one possibly-faulting arming copy per request and
buys a whole layer's worth of per-step overlap.
"""
if not self._mapped_regions.available:
return False
mapped_bytes = sum(
tensor.untyped_storage().nbytes()
for dtype_to_params in layer_groups.values()
for weights in dtype_to_params.values()
for _, weight in weights
for tensor in (self._to_local_tensor(weight),)
if self._mapped_regions.holds(tensor)
)
if mapped_bytes <= 0:
return False
# The copies land in host memory on top of the page cache already holding
# these bytes, so the room needed is the copy itself plus a reserve.
if not host_copies_would_not_fit(mapped_bytes):
return False
available = host_memory_available_bytes()
logger.info(
"Layerwise offload: leaving %.2f GiB of weights on the checkpoint "
"mapping -- copying them into host memory needs more than the "
"%.2f GiB available. The page cache decides what stays resident, so "
"reads may come from disk; the copies cannot be pinned, so they run "
"on the compute stream instead of ahead of it.",
mapped_bytes / 1024**3,
available / 1024**3,
)
return True
totals, mapped = self._layer_byte_totals(layer_groups)
pinned_bytes = 0
hosting: Dict[int, str] = {}
pin_order: List[int] = []
spendable = self._pin_budget.spendable_bytes if self._pin_budget else 0
streamed = [idx for idx in self._streamed_order if idx in totals]
resident = [idx for idx in sorted(totals) if idx not in set(streamed)]
for layer_idx in streamed + resident:
layer_bytes = totals[layer_idx]
if self.pin_cpu_memory and pinned_bytes + layer_bytes <= spendable:
hosting[layer_idx] = "pinned"
pinned_bytes += layer_bytes
pin_order.append(layer_idx)
else:
hosting[layer_idx] = "pageable"
def anonymous_new_bytes() -> int:
# What this plan adds, net, to anonymous memory. A store buffer
# that replaces an anonymous original -- the non-view share, such
# as a fused qkv -- is a wash: the original is freed when its
# parameter is rebound. The net cost of hosting a layer off its
# mapping is therefore only the checkpoint-view share it copies in,
# and a layer left on the mapping adds nothing.
return sum(
mapped[idx] for idx, where in hosting.items() if where != "mapped"
)
unpinned = [idx for idx, where in hosting.items() if where != "pinned"]
# The pins are booked but not yet allocated, so what the plan adds has
# to be weighed as one sum against the live reading. Asking about any
# one tier alone counts the same free bytes twice, and the error only
# ever says "fits".
if unpinned and host_copies_would_not_fit(anonymous_new_bytes()):
for layer_idx in unpinned:
if mapped[layer_idx]:
hosting[layer_idx] = "mapped"
# If the pins alone still do not fit, pins are what there is to
# give back. The tail of the pin order holds the least valuable
# ones, so they go first.
while pin_order and host_copies_would_not_fit(anonymous_new_bytes()):
layer_idx = pin_order.pop()
hosting[layer_idx] = "mapped" if mapped[layer_idx] else "pageable"
pinned_bytes -= totals[layer_idx]
if host_copies_would_not_fit(anonymous_new_bytes()):
logger.warning(
"Layerwise offload: %s adds %.2f GiB of anonymous host "
"memory that no mapping can absorb, and %.2f GiB is "
"available. Expect the host to be the limit.",
self._pin_component_name,
anonymous_new_bytes() / 1024**3,
host_memory_available_bytes() / 1024**3,
)
if pinned_bytes and self._pin_budget is not None:
self._pin_budget.request(
component_name=self._pin_component_name, weight_bytes=pinned_bytes
)
if unpinned:
counts = {where: 0 for where in ("pinned", "pageable", "mapped")}
for where in hosting.values():
counts[where] += 1
logger.info(
"Layerwise offload: %s pins %d of %d layers (%.2f GiB of %.2f GiB "
"spendable). Of the rest, %d are copied into pageable host memory "
"and %d stay on the checkpoint mapping. Pinning every layer would "
"need %.2f GiB.",
self._pin_component_name,
counts["pinned"],
len(totals),
pinned_bytes / 1024**3,
spendable / 1024**3,
counts["pageable"],
counts["mapped"],
sum(totals.values()) / 1024**3,
)
return hosting
def _initialize_layer_weights(self) -> None:
self._named_parameters = dict(self.model.named_parameters())
@@ -451,7 +540,7 @@ class LayerwiseOffloadManager:
local_tensor.dtype, []
).append((name, tensor))
keep_mapping = self._keep_weights_on_their_mapping(layer_groups)
layer_hosting = self._plan_layer_hosting(layer_groups)
# 2. concat and offload (in pinned memory)
for layer_idx, dtype_to_params in layer_groups.items():
@@ -460,11 +549,14 @@ class LayerwiseOffloadManager:
self._mapped_cpu_weights[layer_idx] = {}
self._weight_metadata[layer_idx] = {}
hosting = layer_hosting.get(layer_idx, "pinned")
pin_this_layer = hosting == "pinned"
for dtype, weights in dtype_to_params.items():
contiguous_weights: List[Tuple[str, torch.Tensor, torch.Tensor]] = []
for name, weight in weights:
local_weight = self._to_local_tensor(weight)
if keep_mapping and self._mapped_regions.holds(local_weight):
if hosting == "mapped" and self._mapped_regions.holds(local_weight):
# Already a view into the checkpoint. Copying it would
# add a second copy of bytes the page cache holds
# anyway, and that copy is what does not fit.
@@ -499,7 +591,7 @@ class LayerwiseOffloadManager:
size=local_weight.shape,
stride=local_weight.stride(),
dtype=dtype,
pin_memory=self.pin_cpu_memory,
pin_memory=pin_this_layer,
)
cpu_tensor.copy_(local_weight)
self._strided_cpu_weights[layer_idx][name] = cpu_tensor
@@ -531,7 +623,7 @@ class LayerwiseOffloadManager:
# create concatenated CPU buffer (in pinned memory)
cpu_buffer = torch.empty(
total_numel, dtype=dtype, pin_memory=self.pin_cpu_memory
total_numel, dtype=dtype, pin_memory=pin_this_layer
)
# offload weights to the buffer
@@ -1239,20 +1331,20 @@ class LayerwiseOffloadableModuleMixin:
# Pinning these weights is what lets the copy stream run ahead of
# compute, but pinned pages are the ones the kernel cannot reclaim,
# so a component only gets them while the budget lasts.
pin_cpu_memory = server_args.pin_cpu_memory
if pin_cpu_memory and pin_budget is not None:
pin_cpu_memory = pin_budget.request(
component_name=f"{component_name or type(self).__name__}.{layer_name}",
weight_bytes=module_weight_bytes(module_list),
)
# so they are handed out only while the budget lasts. The budget goes
# to the manager rather than being spent here, because it is asked
# per layer: a component too large to pin whole can still pin part of
# itself. See _plan_layer_hosting.
pin_component_name = f"{component_name or type(self).__name__}.{layer_name}"
manager = LayerwiseOffloadManager(
model=self,
layers_attr_str=layer_name,
num_layers=num_layers,
enabled=True,
pin_cpu_memory=pin_cpu_memory,
pin_cpu_memory=server_args.pin_cpu_memory,
pin_budget=pin_budget,
pin_component_name=pin_component_name,
prefetch_size=prefetch_size,
resident_layers=resident_layers,
initialize=False,
@@ -1560,15 +1652,38 @@ def configure_layerwise_offload_modules(
)
def _default_num_inference_steps() -> int:
from sglang.multimodal_gen.registry import get_pipeline_config_classes
from sglang.multimodal_gen.registry import (
get_model_info,
get_pipeline_config_classes,
)
sampling_cls = None
pipeline_class_name = server_args.pipeline_class_name
if not pipeline_class_name:
if pipeline_class_name:
config_classes = get_pipeline_config_classes(pipeline_class_name)
if config_classes is not None:
sampling_cls = config_classes[1]
else:
# The override is normally unset. Resolve the pipeline the way
# build_pipeline does -- a cache hit by now -- because falling
# back to 1 here turns the benefit ranking into a bare-bytes
# ranking, and a once-per-request encoder can then outrank the
# stepped DiT for the pin budget.
model_path = getattr(server_args, "model_path", None)
if model_path:
model_info = get_model_info(
model_path,
backend=getattr(server_args, "backend", None),
model_id=getattr(server_args, "model_id", None),
)
if model_info is not None:
sampling_cls = model_info.sampling_param_cls
if sampling_cls is None:
return 1
config_classes = get_pipeline_config_classes(pipeline_class_name)
if config_classes is None:
steps = getattr(sampling_cls(), "num_inference_steps", None)
if not steps:
return 1
return max(1, int(config_classes[1]().num_inference_steps))
return max(1, int(steps))
default_steps = _default_num_inference_steps()
@@ -429,6 +429,43 @@ def test_layerwise_pipeline_selection_uses_dit_group(monkeypatch):
assert is_layerwise_offloaded_module(layerwise_module)
def test_pin_budget_ranks_by_steps_resolved_from_model_index(monkeypatch):
monkeypatch.setattr(
layerwise_offload_mod.torch, "get_device_module", lambda: _FakeDeviceModule
)
monkeypatch.setattr(layerwise_offload_mod.current_platform, "device_type", "cpu")
import sglang.multimodal_gen.registry as registry_mod
class _Sampling:
num_inference_steps = 50
monkeypatch.setattr(
registry_mod,
"get_model_info",
lambda *args, **kwargs: SimpleNamespace(sampling_param_cls=_Sampling),
)
# Same byte size on purpose: with the steps resolved, the stepped DiT
# outranks the encoder; with the silent steps=1 fallback the ranking
# ties and stable sort keeps the encoder first.
text_encoder = _NestedEncoderDummyModel()
transformer = _NestedDummyModel()
modules = {"text_encoder": text_encoder, "transformer": transformer}
configured = configure_layerwise_offload_modules(
modules,
_server_args(model_path="/models/minimax-h3"),
component_names=["text_encoder", "transformer"],
)
assert configured == ["transformer", "text_encoder"], (
"the pipeline is resolved from model_index when no override is set, "
"so the stepped DiT must claim the pin budget before the "
"once-per-request encoder"
)
def test_layerwise_configuration_filters_by_component_name(monkeypatch):
monkeypatch.setattr(
layerwise_offload_mod.torch, "get_device_module", lambda: _FakeDeviceModule
@@ -1243,30 +1280,76 @@ class _FileBackedBlock(torch.nn.Module):
class _FileBackedModel(torch.nn.Module):
def __init__(self, path: pathlib.Path, num_blocks: int = 1) -> None:
super().__init__()
self.blocks = torch.nn.ModuleList(
[
_FileBackedBlock(path.with_name(f"{path.name}.{i}"))
for i in range(num_blocks)
]
)
# one _FileBackedBlock weight: 64 float32
_BLOCK_BYTES = 64 * 4
class _MixedBlock(torch.nn.Module):
"""A block that is half checkpoint view, half anonymous memory -- the shape
of a layer whose qkv was fused at load while the rest stayed mapped."""
def __init__(self, path: pathlib.Path) -> None:
super().__init__()
self.blocks = torch.nn.ModuleList([_FileBackedBlock(path)])
path.write_bytes(b"\x00" * (64 * 4))
mapped = torch.from_file(str(path), shared=True, size=64, dtype=torch.float32)
self.weight = torch.nn.Parameter(mapped.reshape(8, 8), requires_grad=False)
self.fused = torch.nn.Parameter(
torch.zeros(8, 8, dtype=torch.float32), requires_grad=False
)
def _mapped_manager(tmp_path, monkeypatch, *, available_gib):
class _MixedModel(torch.nn.Module):
def __init__(self, path: pathlib.Path, num_blocks: int) -> None:
super().__init__()
self.blocks = torch.nn.ModuleList(
[_MixedBlock(path.with_name(f"{path.name}.{i}")) for i in range(num_blocks)]
)
# one _MixedBlock: 64 float32 mapped + 64 float32 anonymous
_MIXED_BLOCK_BYTES = 2 * 64 * 4
def _mapped_manager(
tmp_path,
monkeypatch,
*,
available_gib=None,
available_bytes=None,
num_blocks=1,
pin_budget_bytes=None,
):
monkeypatch.setattr(
layerwise_offload_mod.torch, "get_device_module", lambda: _FakeDeviceModule
)
monkeypatch.setattr(layerwise_offload_mod.current_platform, "device_type", "cpu")
# Two bindings for one fact: the budget module's copy is what decides
# whether the copies fit, and layerwise_offload's own is what the log reports.
available_bytes = int(available_gib * 1024**3)
for module in (host_memory_budget, layerwise_offload_mod):
monkeypatch.setattr(
module, "host_memory_available_bytes", lambda: available_bytes
)
model = _FileBackedModel(tmp_path / "weights.bin")
if available_bytes is None:
available_bytes = int(available_gib * 1024**3)
monkeypatch.setattr(
host_memory_budget, "host_memory_available_bytes", lambda: available_bytes
)
model = _FileBackedModel(tmp_path / "weights.bin", num_blocks=num_blocks)
return LayerwiseOffloadManager(
model=model,
layers_attr_str="blocks",
num_layers=1,
num_layers=num_blocks,
enabled=True,
pin_cpu_memory=True,
pin_budget=(
host_memory_budget.HostPinBudget(available_bytes=pin_budget_bytes)
if pin_budget_bytes is not None
else None
),
prefetch_size=1,
)
@@ -1275,7 +1358,9 @@ def test_weights_stay_on_the_mapping_when_copies_do_not_fit(tmp_path, monkeypatc
if not pathlib.Path("/proc/self/maps").exists():
pytest.skip("needs /proc to tell a mapping from anonymous memory")
# the reserve alone exceeds this, so no copy can be afforded
manager = _mapped_manager(tmp_path, monkeypatch, available_gib=0.001)
manager = _mapped_manager(
tmp_path, monkeypatch, available_gib=0.001, pin_budget_bytes=0
)
assert manager._mapped_cpu_weights[0], "expected the weight to stay mapped"
assert manager._weight_metadata[0]["blocks.0.weight"]["mapped"] is True
assert not manager._consolidated_cpu_weights.get(0)
@@ -1292,7 +1377,9 @@ def test_weights_are_copied_when_they_fit(tmp_path, monkeypatch):
def test_a_mapped_weight_is_not_written_back(tmp_path, monkeypatch):
if not pathlib.Path("/proc/self/maps").exists():
pytest.skip("needs /proc to tell a mapping from anonymous memory")
manager = _mapped_manager(tmp_path, monkeypatch, available_gib=0.001)
manager = _mapped_manager(
tmp_path, monkeypatch, available_gib=0.001, pin_budget_bytes=0
)
before = manager._mapped_cpu_weights[0]["blocks.0.weight"].clone()
manager._gpu_layers.add(0)
manager.sync_layer_to_cpu(0)
@@ -1331,10 +1418,133 @@ def test_the_mapped_store_survives_the_placeholder(tmp_path, monkeypatch):
), "the byte counter and the store must describe the same weights"
def test_only_the_layers_the_budget_covers_are_pinned(tmp_path, monkeypatch):
if not pathlib.Path("/proc/self/maps").exists():
pytest.skip("needs /proc to tell a mapping from anonymous memory")
# The pin budget covers two of the four layers. Available host memory sits
# above the copy reserve by less than all four layers but more than the
# two pins, so the rest is demoted to the mapping and the pins stand.
budget = 2 * 1024**3 + 2 * _BLOCK_BYTES
manager = _mapped_manager(
tmp_path,
monkeypatch,
available_bytes=4 * 1024**3 + 3 * _BLOCK_BYTES,
pin_budget_bytes=budget,
num_blocks=4,
)
pinned = {i for i in range(4) if manager._consolidated_cpu_weights.get(i)}
mapped = {i for i in range(4) if manager._mapped_cpu_weights.get(i)}
assert pinned == {0, 1}, "the layers the budget covers, taken in index order"
assert mapped == {2, 3}
assert not (pinned & mapped), "a layer is in one store or the other"
def test_pins_are_given_back_when_they_do_not_fit_the_host(tmp_path, monkeypatch):
if not pathlib.Path("/proc/self/maps").exists():
pytest.skip("needs /proc to tell a mapping from anonymous memory")
# Four fully mapped layers. The budget covers two pins, but each pin copies
# its whole layer off the mapping and the host only has room for one plus
# the reserve: the plan must give the second pin back rather than allocate
# more than the machine has.
available = 4 * 1024**3 + int(1.5 * _BLOCK_BYTES)
manager = _mapped_manager(
tmp_path,
monkeypatch,
available_bytes=available,
pin_budget_bytes=2 * 1024**3 + 2 * _BLOCK_BYTES,
num_blocks=4,
)
on_mapping = {i for i in range(4) if manager._mapped_cpu_weights.get(i)}
assert on_mapping == {1, 2, 3}, (
"two pins add two layers of anonymous memory against room for 1.5, so "
"the least valuable pin (layer 1) is given back"
)
pinned = {i for i in range(4) if manager._consolidated_cpu_weights.get(i)}
assert pinned == {0}
def test_replacing_an_anonymous_original_is_not_charged_as_new(tmp_path, monkeypatch):
if not pathlib.Path("/proc/self/maps").exists():
pytest.skip("needs /proc to tell a mapping from anonymous memory")
# Half of each layer is a fused anonymous tensor whose store buffer
# replaces it -- a wash. Only the mapped half is a net addition, so a
# host with room for the mapped halves alone must still pin every layer.
block = _MIXED_BLOCK_BYTES
available = 4 * 1024**3 + int(2.5 * block)
monkeypatch.setattr(
layerwise_offload_mod.torch, "get_device_module", lambda: _FakeDeviceModule
)
monkeypatch.setattr(layerwise_offload_mod.current_platform, "device_type", "cpu")
monkeypatch.setattr(
host_memory_budget, "host_memory_available_bytes", lambda: available
)
model = _MixedModel(tmp_path / "weights.bin", num_blocks=4)
manager = LayerwiseOffloadManager(
model=model,
layers_attr_str="blocks",
num_layers=4,
enabled=True,
pin_cpu_memory=True,
pin_budget=host_memory_budget.HostPinBudget(
available_bytes=2 * 1024**3 + 3 * block
),
prefetch_size=1,
)
pinned = {i for i in range(4) if 0 in manager._consolidated_cpu_weights}
on_mapping = {i for i in range(4) if manager._mapped_cpu_weights.get(i)}
assert not on_mapping, (
"the net addition is three pinned mapped-halves plus one pageable "
"mapped-half (2 blocks) against room for 2.5 -- charging the replaced "
"anonymous originals as new would demote, then strip every pin"
)
assert all(manager._consolidated_cpu_weights.get(i) for i in range(4))
def test_every_layer_is_pinned_when_the_budget_covers_them(tmp_path, monkeypatch):
if not pathlib.Path("/proc/self/maps").exists():
pytest.skip("needs /proc to tell a mapping from anonymous memory")
manager = _mapped_manager(
tmp_path,
monkeypatch,
available_gib=64,
pin_budget_bytes=64 * 1024**3,
num_blocks=4,
)
assert not any(manager._mapped_cpu_weights.get(i) for i in range(4))
assert all(manager._consolidated_cpu_weights.get(i) for i in range(4))
def test_no_layer_is_pinned_when_the_budget_is_spent(tmp_path, monkeypatch):
if not pathlib.Path("/proc/self/maps").exists():
pytest.skip("needs /proc to tell a mapping from anonymous memory")
manager = _mapped_manager(
tmp_path, monkeypatch, available_bytes=0, pin_budget_bytes=0, num_blocks=4
)
assert all(manager._mapped_cpu_weights.get(i) for i in range(4))
def test_an_unpinnable_layer_is_still_copied_when_the_copies_fit(tmp_path, monkeypatch):
if not pathlib.Path("/proc/self/maps").exists():
pytest.skip("needs /proc to tell a mapping from anonymous memory")
# no pinned budget at all, but plenty of host memory: a pageable copy is
# guaranteed resident where a mapping can be dropped and re-read
manager = _mapped_manager(
tmp_path,
monkeypatch,
available_gib=64,
pin_budget_bytes=0,
num_blocks=4,
)
assert not any(manager._mapped_cpu_weights.get(i) for i in range(4))
assert all(manager._consolidated_cpu_weights.get(i) for i in range(4))
def test_mapped_weights_are_visible_to_checksums(tmp_path, monkeypatch):
if not pathlib.Path("/proc/self/maps").exists():
pytest.skip("needs /proc to tell a mapping from anonymous memory")
manager = _mapped_manager(tmp_path, monkeypatch, available_gib=0.001)
manager = _mapped_manager(
tmp_path, monkeypatch, available_gib=0.001, pin_budget_bytes=0
)
names = {name for name, _ in manager.iter_cpu_weights()}
assert "blocks.0.weight" in names