Streaming¶
streaming
¶
Block streaming for low-RAM transformer inference on Apple Silicon.
LoraFuser
module-attribute
¶
LoraFuser = Callable[[list[tuple[str, mx.array]], int, list['BlockLoraSource']], list[tuple[str, mx.array]]]
Callable signature for fusing LoRA deltas into a block's weight list.
Implementations receive the bound weights as [(param_name, array), ...],
the block index being bound, and the list of :class:BlockLoraSource to
fuse (already filtered by :meth:BlockLoraSource.has_block). They return
the fused weight list (same shape; arrays may be replaced).
BlockLoraSource
¶
BlockLoraSource(lora_path: str | Path, block_prefix: str, strength: float = 1.0, key_mapper: Callable[[str], str | None] | None = None)
Per-block LoRA A/B matrices indexed by block index.
Streams LoRA A/B pairs from a memory-mapped safetensors file and indexes them by block index so a fuser can apply the matching delta to a streamed block on the fly.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lora_path
|
str | Path
|
Path to LoRA safetensors. |
required |
block_prefix
|
str
|
Same prefix as the streamer (e.g.
|
required |
strength
|
float
|
LoRA fusion strength (default 1.0). |
1.0
|
key_mapper
|
Callable[[str], str | None] | None
|
Optional callable that remaps raw safetensors keys
to the model's state-dict naming (e.g. ComfyUI/diffusers →
MLX). Returning |
None
|
BlockStreamer
¶
BlockStreamer(weight_paths: str | Path | Iterable[str | Path], block_prefix: str, lora_fuser: LoraFuser | None = None)
Stream transformer block weights from mmap'd safetensors.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
weight_paths
|
str | Path | Iterable[str | Path]
|
One or more |
required |
block_prefix
|
str
|
State-dict key prefix that identifies block
weights, e.g. |
required |
lora_fuser
|
LoraFuser | None
|
Optional callable to fuse LoRA deltas into the
bound weights. See :data: |
None
|
Notes
After construction, :attr:block_count returns the number of
distinct block indices found, and :meth:block_keys(i) lists
the per-block parameter names that will be bound by
:meth:bind. The mmap'd dict is held until :meth:close is
called.
block_count
property
¶
Number of distinct block indices discovered in the safetensors.
block_keys
¶
Per-block parameter names (without the {prefix}{idx}. part).
bind
¶
bind(block: Module, idx: int, evict_previous: int | None = None, lora_sources: list[BlockLoraSource] | None = None) -> None
Load block idx's weights into block in-place.
After this returns, block's parameters reference the
safetensors-mapped arrays for index idx. A subsequent
:meth:bind to a different idx rebinds them, releasing the
previous arrays from the block's parameter tree.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
block
|
Module
|
Target module. Its parameter tree must match the
keys returned by :meth: |
required |
idx
|
int
|
Block index to load. |
required |
evict_previous
|
int | None
|
If given, drop the cached array references for that block index from the internal weight dict before binding. The streamer holds refs to every block's weights; without eviction, those refs prevent GC even after the bound block is replaced. For streaming inference, pass the previously-bound index so peak resident memory stays at ~one block. |
None
|
lora_sources
|
list[BlockLoraSource] | None
|
Optional LoRA sources to fuse into the bound
weights. Requires a |
None
|
block_streaming
¶
Block streaming for low-RAM inference on Apple Silicon.
Stream transformer block weights from a memory-mapped safetensors file
into a single shared block module, so peak resident memory stays at
~1 block instead of ~num_blocks.
The pattern fits any model whose state-dict has block-indexed keys of
the form f"{prefix}{i}.{rest}" (e.g.
"transformer.transformer_blocks.0.attn.q_proj.weight"). It works
because:
- Apple Silicon has unified memory, so there is no host-to-device copy.
mx.load(path)memory-maps safetensors lazily — opening a 20 GB file costs ~40 MB RSS until individual arrays are touched.- MLX has a single command queue, so per-block
mx.clear_cache()+ dropping references keeps the resident set bounded without explicit stream/event coordination.
Typical memory profile for a 22 B bf16 transformer with 48 blocks: without streaming ~22 GB resident; with streaming ~1 block (~460 MB) + non-block params + mmap metadata ≈ ~1 GB.
LoRA fusion is decoupled: pass a lora_fuser callable to
:class:BlockStreamer and it will be invoked per block. The fuser
receives the bound weights and a list of :class:BlockLoraSource
objects, and returns the fused weight list. This keeps quantization-
aware fusion strategies out of arsenal.
LoraFuser
module-attribute
¶
LoraFuser = Callable[[list[tuple[str, mx.array]], int, list['BlockLoraSource']], list[tuple[str, mx.array]]]
Callable signature for fusing LoRA deltas into a block's weight list.
Implementations receive the bound weights as [(param_name, array), ...],
the block index being bound, and the list of :class:BlockLoraSource to
fuse (already filtered by :meth:BlockLoraSource.has_block). They return
the fused weight list (same shape; arrays may be replaced).
BlockLoraSource
¶
BlockLoraSource(lora_path: str | Path, block_prefix: str, strength: float = 1.0, key_mapper: Callable[[str], str | None] | None = None)
Per-block LoRA A/B matrices indexed by block index.
Streams LoRA A/B pairs from a memory-mapped safetensors file and indexes them by block index so a fuser can apply the matching delta to a streamed block on the fly.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lora_path
|
str | Path
|
Path to LoRA safetensors. |
required |
block_prefix
|
str
|
Same prefix as the streamer (e.g.
|
required |
strength
|
float
|
LoRA fusion strength (default 1.0). |
1.0
|
key_mapper
|
Callable[[str], str | None] | None
|
Optional callable that remaps raw safetensors keys
to the model's state-dict naming (e.g. ComfyUI/diffusers →
MLX). Returning |
None
|
BlockStreamer
¶
BlockStreamer(weight_paths: str | Path | Iterable[str | Path], block_prefix: str, lora_fuser: LoraFuser | None = None)
Stream transformer block weights from mmap'd safetensors.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
weight_paths
|
str | Path | Iterable[str | Path]
|
One or more |
required |
block_prefix
|
str
|
State-dict key prefix that identifies block
weights, e.g. |
required |
lora_fuser
|
LoraFuser | None
|
Optional callable to fuse LoRA deltas into the
bound weights. See :data: |
None
|
Notes
After construction, :attr:block_count returns the number of
distinct block indices found, and :meth:block_keys(i) lists
the per-block parameter names that will be bound by
:meth:bind. The mmap'd dict is held until :meth:close is
called.
block_count
property
¶
Number of distinct block indices discovered in the safetensors.
block_keys
¶
Per-block parameter names (without the {prefix}{idx}. part).
bind
¶
bind(block: Module, idx: int, evict_previous: int | None = None, lora_sources: list[BlockLoraSource] | None = None) -> None
Load block idx's weights into block in-place.
After this returns, block's parameters reference the
safetensors-mapped arrays for index idx. A subsequent
:meth:bind to a different idx rebinds them, releasing the
previous arrays from the block's parameter tree.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
block
|
Module
|
Target module. Its parameter tree must match the
keys returned by :meth: |
required |
idx
|
int
|
Block index to load. |
required |
evict_previous
|
int | None
|
If given, drop the cached array references for that block index from the internal weight dict before binding. The streamer holds refs to every block's weights; without eviction, those refs prevent GC even after the bound block is replaced. For streaming inference, pass the previously-bound index so peak resident memory stays at ~one block. |
None
|
lora_sources
|
list[BlockLoraSource] | None
|
Optional LoRA sources to fuse into the bound
weights. Requires a |
None
|