OffloadManager¶
The OffloadManager class provides full control over the offloading lifecycle.
Obtain an instance with get_offload_manager()
rather than constructing one directly.
OffloadManager
¶
Simplified offload manager API.
Provides a high-level interface for weight offloading with automatic phase management, profiling, and module patching capabilities.
external_compile_active
property
¶
Whether the active run uses external_compile with no compile_fn.
Describes the effective live run as resolved at offload() —
mutating config.external_compile afterwards cannot flip it (that
field is re-read only by the next offload()). False for eager
offload and for FlexTensor-driven compile_fn runs, which do not need
the caller to compile each unit.
strategy_replan_status
property
¶
strategy_replan_status: StrategyReplanStatus
Passive snapshot of the current replan lifecycle.
Reading this never arms or advances the tail, so it is safe both as a
preflight check before :meth:request_strategy_replan and as an outcome
check after the tail finishes. Field semantics are documented on
:class:~flextensor.StrategyReplanStatus; for the preflight-then-drive
pattern see docs/how-to/torch-compile.md.
can_request is required by both :meth:request_strategy_replan
variants but sufficient only for the default one: the CUDA-graph variant
(manual_update_state=True) adds requirements — offload_timing=
'cuda_graph' plus external CUDA timing events — not reflected here.
init
¶
init(config: OffloadConfig | None = None, *, _shared_offload_owner_token: object | None = None) -> None
set_config
¶
set_config(config: OffloadConfig) -> None
Set configuration for the offload manager.
Most fields take effect on the next phase transition or on the
operation that reads them. The fields in
:data:_TENSOR_MANAGER_ONESHOT_FIELDS are baked into the active
:class:TensorManager when it is constructed at the first
offload() call — _initialize_tensor_manager does not run
again — so changing them here cannot affect the live manager. To
honor a new value, release() the current manager and
re-offload().
skip_discovery is the one field that raises rather than warns:
the live TensorManager would keep its captured value while
self.config.skip_discovery reported the new one, and
offload_block()'s guard reads the live config — so a True→False
flip would silently permit manual blocks while the manager stays in
skip-mode and never captured their tensor mappings. Every other
one-shot field emits a UserWarning naming it.
| PARAMETER | DESCRIPTION |
|---|---|
config
|
OffloadConfig instance with settings
TYPE:
|
| RAISES | DESCRIPTION |
|---|---|
RuntimeError
|
If |
| WARNS | DESCRIPTION |
|---|---|
UserWarning
|
If any other one-shot field differs from the value
the active |
offload
¶
offload(model: Module, config: OffloadConfig | None = None, compile_fn: Callable[[Module], Module] | None = None) -> Module
Offload modules in the model based on config patterns.
Patches modules matching config.include_patterns (and applies
config.exclude_patterns) to automatically manage tensor transfers.
Supports wildcards in include/exclude patterns.
| PARAMETER | DESCRIPTION |
|---|---|
model
|
PyTorch model to patch
TYPE:
|
config
|
OffloadConfig to use for offload. Set
TYPE:
|
compile_fn
|
Optional per-unit compiler. When given, this activates the
compiled-offload path (no
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Module
|
A wrapper around the model that tracks state transitions |
Examples:
Basic eager offload (see docs/quick-start.md for a runnable
end-to-end example). With the default skip_discovery=False,
discovery_iters + profiling_iters forwards drive the
state machine into INFERENCE (the post-forward hook
transitions on the Nth call); the extra iteration below runs
the first real inference forward::
import torch
import flextensor as ft
model = MyModel().to("cpu").eval()
config = ft.OffloadConfig(include_patterns=["embed", "layers.*", "head"])
proxy = ft.offload(model, config)
om = ft.get_offload_manager()
x = torch.randn(batch, seq, dim, device="cuda", dtype=torch.bfloat16)
with torch.no_grad():
for _ in range(om.iters_before_inference + 1):
proxy(x) # drives discovery -> profile -> inference
Per-unit torch.compile via compile_fn (preferred; see
docs/how-to/torch-compile.md). Whole-model torch.compile(proxy)
is not supported across phase transitions::
def compile_fn(module: torch.nn.Module) -> torch.nn.Module:
return torch.compile(module, fullgraph=True)
proxy = ft.offload(model, config, compile_fn=compile_fn)
om = ft.get_offload_manager()
with torch.no_grad():
for _ in range(om.iters_before_inference):
proxy(x) # discovery + compiled view-profile -> INFERENCE
For external per-unit compile after INFERENCE, set
OffloadConfig(external_compile=True), drive
om.iters_before_inference eager forwards, torch.compile each
offloaded unit, then :meth:request_strategy_replan.
offload_from_state
¶
offload_from_state(model: Module, state: TensorManagerState, config: OffloadConfig | None = None, *, allow_strategy_replan: bool = True) -> Module
Adopt a saved state and return the offloaded model proxy.
If this method raises, cleanup preserves storage ownership and removes FlexTensor modifications where possible, but does not roll back storage placement. The input model is not guaranteed usable and must be discarded.
offload_block
¶
Create an offload block context manager.
| PARAMETER | DESCRIPTION |
|---|---|
name
|
Name of the offload block (used for tracking)
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Any
|
OffloadBlock context manager (Trap, WarmupTrap, TrapInfer, …) |
| RAISES | DESCRIPTION |
|---|---|
RuntimeError
|
If |
Examples:
Note
Compiled offload is driven by auto-patched forwards that call
pre_compute/post_compute directly. Manual offload_block stays
on the eager trap path (:class:~flextensor.trap_tensor_mode.TrapInferDirect
/ :class:~flextensor.trap_tensor_mode.TrapInfer); do not wrap an
auto-patched compiled unit or transfers will double-schedule.
offload_units
¶
Return outermost offloaded modules as (qualified_name, module) pairs.
Same set compile_fn wraps: nested patched modules are omitted so
each graph would read one rolling slot. Order matches patch order;
shared modules are identity-deduplicated (first path wins). Available
after :meth:offload; the set is stable once the manager is in
INFERENCE.
To torch.compile these units, enable external_compile=True (or
pass compile_fn) and compile only after INFERENCE. Prefer
module.compile(...) so the object in the tree stays the same;
torch.compile(module) returns a new wrapper and is not written
back until a later replace helper exists.
| RETURNS | DESCRIPTION |
|---|---|
tuple[str, Module]
|
Immutable |
...
|
or nothing was patched. |
resident_units
¶
Return outermost modules that are not offload units and contain none.
These subtrees stay GPU-resident at serving time and may be compiled
without compiled-offload custom ops. The root, containers that hold
offload units, registry containers without an executable forward
(ModuleList, ModuleDict), and children inside an offload unit
are omitted.
Compile only after INFERENCE: earlier phases can swap the live module
tree. Resident-only compile does not require external_compile or
:meth:request_strategy_replan.
Boundaries are the complement of :meth:offload_units live identities,
so a compile_fn replacement that dropped patch markers is still
omitted, and a second parent of a shared offload unit is not listed as
resident.
| RETURNS | DESCRIPTION |
|---|---|
tuple[str, Module]
|
Immutable |
...
|
order, identity-deduplicated. Empty when no model is bound. |
get_gpu_memory_usage
¶
get_gpu_memory_usage() -> GPUMemoryUsage
Get GPU memory usage by FlexTensor in inference mode.
Returns the memory used by GPU transfer blocks and unmapped tensors that were moved to GPU. This method should be called after the manager has transitioned to inference mode.
| RETURNS | DESCRIPTION |
|---|---|
GPUMemoryUsage
|
Memory breakdown with per-component bytes and MB values.
See
TYPE:
|
| RAISES | DESCRIPTION |
|---|---|
RuntimeError
|
If called before the manager has transitioned to inference mode. |
Examples:
>>> om = get_offload_manager()
>>> config = OffloadConfig(include_patterns=["layers.*"])
>>> model = om.offload(model, config=config)
>>> # Run discovery and profiling iterations (path-aware count)
>>> for _ in range(om.iters_before_inference):
... model(input)
>>> # Now in inference mode, get memory usage
>>> usage = om.get_gpu_memory_usage()
>>> print(f"FlexTensor GPU memory: {usage.total_mb:.2f} MB")
>>> print(f" Transfer blocks: {usage.blocks_mb:.2f} MB")
>>> print(f" Unmapped tensors: {usage.unmapped_tensors_mb:.2f} MB")
save_profile
¶
Save current offload profile to directory.
| PARAMETER | DESCRIPTION |
|---|---|
profile_directory
|
Directory to save profile to. If None, uses config.profile_storage_dir.
TYPE:
|
| RAISES | DESCRIPTION |
|---|---|
RuntimeError
|
If tensor manager is not initialized or no profile data exists. |
ValueError
|
If no directory is provided and config.profile_storage_dir is not set, or if config.profile_read_only is True. |
load_profile
¶
Load offload profile from directory and restore state to model.
| PARAMETER | DESCRIPTION |
|---|---|
profile_directory
|
Directory containing the profile. If None, uses config.profile_storage_dir.
TYPE:
|
model
|
Model to restore state to. If None, uses the current model.
TYPE:
|
| RAISES | DESCRIPTION |
|---|---|
RuntimeError
|
If no model is available to restore state to. |
FileNotFoundError
|
If profile directory or file doesn't exist. |
ValueError
|
If no directory is provided and config.profile_storage_dir is not set. |
clear_profiling_durations
¶
Clear all accumulated duration measurements.
Wipes every duration sample collected so far. Tensor-to-layer mappings (discovery data) are not affected.
| RAISES | DESCRIPTION |
|---|---|
RuntimeError
|
if called before :func: |
.. note::
With OffloadConfig(enabled=False) the active manager is
:class:NoOpTensorManager: this method is a no-op because
no durations are ever recorded.
suspend_profiling
¶
Suppress profiling recording and (in PROFILING phase) freeze the iteration counter.
While suspended:
record_all()(the PROFILING recorder) is a complete no-op — neither tensor IDs nor duration samples are stored, so a paused warmup pass cannot widen per-layer tensor sets on data-dependent models (MoE / conditional branches / mixed-batch shapes).record_duration()skips duration recording.record_tensors()(the DISCOVERY recorder) is not affected — discovery's tensor-to-layer mapping is a hard prerequisite for every later phase.- In DISCOVERY the iteration counter still advances (discovery durations are unused).
- In PROFILING the counter is paused so suppressed passes don't
consume the
profiling_itersbudget.
Suspensions are reference-counted: each call must be matched by a
:meth:resume_profiling, and recording only resumes once every
outstanding suspension has been released. Independent callers can
therefore bracket their own sections without accidentally resuming
someone else's suspension.
| RAISES | DESCRIPTION |
|---|---|
RuntimeError
|
if called before :func: |
.. note::
With OffloadConfig(enabled=False) the active manager is
:class:NoOpTensorManager: these methods are no-ops and the
profiling-iteration counter is not frozen.
resume_profiling
¶
Release one outstanding :meth:suspend_profiling call.
Recording only resumes once every outstanding suspension has been released.
| RAISES | DESCRIPTION |
|---|---|
RuntimeError
|
if called before :func: |
pause_profiling
¶
Context-manager form of :meth:suspend_profiling / :meth:resume_profiling.
| RAISES | DESCRIPTION |
|---|---|
RuntimeError
|
if called before :func: |
collect_offload_timing
¶
collect_offload_timing() -> OffloadTimingReport | None
Collect aggregate offload timing from the durable measure store.
Flushes any pending eager pass before draining (see
:meth:TensorManager.collect_offload_timing). Under CUDA-graph replay,
call :meth:update_offload_timing (or :meth:update_state during a
manual replan) after each replay so passes are published before
collect — this method does not re-finalize the last replay (that would
duplicate it).
Must be called after all forward passes have completed (e.g. after
pipe() returns or at shutdown). Calling this drains the durable
measure store, so the next call returns data only for passes since
:meth:reset_offload_timing / this collect. Retention before drain is
capped by
:data:~flextensor.offload_timing.OFFLOAD_TIMING_MEASURE_MAX_PASSES.
| RETURNS | DESCRIPTION |
|---|---|
OffloadTimingReport | None
|
class: |
OffloadTimingReport | None
|
per-trap min/max/avg/std for transfer, compute, and wait, or |
OffloadTimingReport | None
|
|
OffloadTimingReport | None
|
|
OffloadTimingReport | None
|
|
OffloadTimingReport | None
|
|
reset_offload_timing
¶
Start a fresh durable offload-timing measure window.
Clears accumulated measure passes and the collector's rolling log
window. Drops any pending eager pass on the collector so it cannot
be published into this new window by a later on_pass_start. Call
before a measure segment you care about (serving → shutdown
:meth:collect_offload_timing).
update_offload_timing
¶
Store the current offload-timing pass into the collector.
Measurement only — does not advance phase or rebuild strategy.
Needed under CUDA graphs: each graph.replay() overwrites the same
captured event handles, so call this after every replay you want to
keep (or :meth:update_state does it when replan used
manual_update_state=True).
| RETURNS | DESCRIPTION |
|---|---|
bool
|
|
bool
|
|
bool
|
pass / capturing) — callers must not advance a measure replan |
bool
|
counter after |
request_strategy_replan
¶
Remeasure under the current runtime and rebuild the offload strategy.
manual_update_state=True— CUDA graphs: rungraph.replay()then :meth:update_statefor the returned count (recapture graphs after).manual_update_state=False— external compile: run that many model forwards; the forward hook calls :meth:update_statefor you.
Needed after external torch.compile (external_compile=True),
after compile_fn with profile_mode='getter', or after CUDA-graph
capture. Not required for default compile_fn + profile_mode='view'.
| PARAMETER | DESCRIPTION |
|---|---|
manual_update_state
|
When True, caller drives each measure step
(e.g.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
int
|
Measure (and warmup) forwards / replays to drive, or |
int
|
nothing was armed. Both paths arm only while |
int
|
attr: |
int
|
they warn and leave the current tail untouched. |
update_state
¶
Advance the phase state machine by one iteration.
In INFERENCE, phase transitions are finished, but when a replan
tail is armed this method still advances it:
- CUDA-graph (
request_strategy_replan(manual_update_state=True)) — :meth:update_offload_timingthen advance the measure counter; the last call rebuilds from graph budgets. - Compiled / external-compile — advance the passive warm→measure
tail via :meth:
CompiledOffload.on_forward(usually from the forward hook; no explicit call needed).
| PARAMETER | DESCRIPTION |
|---|---|
replay_generation
|
Forwarded to :meth:
TYPE:
|
No-op in NOT_INITIALIZED.
When profiling is suspended:
- During DISCOVERY the iteration counter still advances (discovery durations are unused and cleared at the transition anyway).
- During PROFILING the counter is paused so that suppressed passes
don't consume the
profiling_itersbudget.
Compiled-profile compile-warmup model forwards (while
profile_compile_warm_remaining > 0) likewise do not advance the
PROFILING counter, so the logged profiling_iters measured samples
are collected in full after warmup.
release
¶
Release this manager and its TensorManager resources.
| PARAMETER | DESCRIPTION |
|---|---|
preserve_model
|
Restore loader-managed tensor values before
teardown. Set to
TYPE:
|