Skip to content

Commit 80bac2d

Browse files
authored
feat: enable single-GPU auto-fit with tiered parameter placement (#1942)
1 parent dbb6112 commit 80bac2d

5 files changed

Lines changed: 405 additions & 327 deletions

File tree

docs/backend.md

Lines changed: 61 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -126,32 +126,70 @@ Direct ("immediately") LoRA application cannot patch row-split tensors; with
126126
explicit `--lora-apply-mode immediately` skips the split tensors with a
127127
warning.
128128

129-
## Automatic placement (`--auto-fit`)
129+
## Automatic placement (`--auto-fit on|off`)
130130

131-
`--auto-fit` derives the `diffusion` / `te` / `vae` placements from the model
132-
metadata and the per-device memory budgets, then feeds them into the same
133-
backend assignment mechanism described above (the chosen specs are printed).
134-
`--backend` and `--params-backend` are ignored while auto-fit is enabled.
131+
`--auto-fit` requires `on` or `off` and defaults to `on` when omitted.
132+
Explicit `--backend` or `--params-backend` assignments disable auto-fit,
133+
regardless of argument order, even with `--auto-fit on`.
134+
135+
When enabled, auto-fit uses one GPU for `diffusion` / `te` / `vae` computation. It chooses
136+
the GPU with the largest available memory budget (the first device on a tie),
137+
then derives parameter placements from the model metadata and the remaining
138+
memory budgets. The chosen backend specifications are printed.
135139

136140
```shell
137-
sd-cli -m model.safetensors -p "a cat" --auto-fit
138-
sd-cli -m model.safetensors -p "a cat" --auto-fit --max-vram cuda0=8,cuda1=14
139-
sd-cli -m model.safetensors -p "a cat" --auto-fit --split-mode row
141+
sd-cli -m model.safetensors -p "a cat" --auto-fit on
142+
sd-cli -m model.safetensors -p "a cat" --auto-fit on --max-vram cuda0=8,cuda1=14
143+
sd-cli -m model.safetensors -p "a cat" --auto-fit off
140144
```
141145

142146
Budgets reuse `--max-vram`: a positive per-device value caps what auto-fit
143147
plans with on that device, a negative value means "free memory minus that many
144148
GiB", and with no budget set each device's free memory minus a 512 MiB margin
145-
is used. (The same values still drive graph-cut segmented execution for
146-
modules that end up on a single device.)
147-
148-
When everything fits resident, components are simply spread across the
149-
available GPUs. When it does not, auto-fit switches to time-share mode: the
150-
heavy components get `disk` params residency (loaded for their phase, freed
151-
after), and a component too large for any single device is split across all
152-
GPUs with the layer/row split mechanism (`--split-mode` selects which, layer
153-
by default). Components that fit nowhere fall back to the CPU. If a VAE decode
154-
still runs out of memory, tiling is enabled and the decode retried once.
149+
is used. These resolved GPU budgets, including the safety margin, also drive
150+
the runner's graph-cut capacity checks.
151+
152+
Components are considered in `diffusion`, `te`, `vae` order so that repeatedly
153+
used diffusion weights have priority. Each component's weights use the first
154+
storage location with enough remaining budget:
155+
156+
1. The main GPU, leaving estimated space for computation and weight staging.
157+
2. CPU RAM, reserving the larger of 2 GiB or 10% of available RAM for other work.
158+
3. Another GPU, choosing the one with the largest remaining budget that fits.
159+
4. Disk, reloading weights on demand.
160+
161+
GPU cache space follows the same component priority. Before a lower-priority
162+
component can become permanently resident, the planner leaves room for the full
163+
weights and estimated compute space of higher-priority offloaded components.
164+
If offloaded diffusion already needs the entire main GPU budget, TE and VAE also
165+
use offloaded parameters. Their GPU copies can then be released after their
166+
phases, leaving more room to reuse diffusion weights across sampling steps.
167+
CPU parameter residency allows GPU weight caching; it does not force every
168+
weight to be copied again at every step.
169+
170+
RAM and GPU budgets are shared across components. Each component uses a single
171+
parameter backend; several other GPUs' capacities are not combined to store
172+
one component. If available RAM cannot be queried, RAM residency is skipped.
173+
Other GPUs store weights only: weights are copied to the main GPU for execution.
174+
Auto-fit does not select multi-GPU layer/row computation, so `--split-mode` does
175+
not change its placements. Use explicit backend assignments for multi-GPU
176+
computation.
177+
178+
For example, a diffusion model whose full weights exceed the main GPU's budget
179+
can use `--backend diffusion=cuda0 --params-backend diffusion=cpu` when RAM is
180+
sufficient. Automatic graph segmentation can then load the required weights
181+
for each segment and reclaim idle GPU copies. `--disable-segmented-compute`
182+
still disables segmentation.
183+
184+
Initial compute reserves are estimates (2 GiB for diffusion and text encoders,
185+
1 GiB for VAE); higher-priority placements also leave staging space for the
186+
largest weight tensor of each lower-priority offloaded component. Actual segment
187+
weights, compute buffers and caches must
188+
still fit the runner's capacity checks. Offloading weights does not guarantee
189+
that every resolution or frame count will fit, and auto-fit does not change a
190+
component to CPU computation solely because its full weights exceed VRAM.
191+
If a VAE decode fails, auto-fit retries with spatial tiling; supported video
192+
decoders try temporal tiling first and can then add spatial tiling.
155193

156194
## Modules
157195

@@ -203,7 +241,7 @@ sd-cli -m model.safetensors -p "a cat" --backend cuda0 --params-backend disk
203241

204242
This runs all modules on `cuda0`, reloads parameters from the model file as needed, and releases those parameter buffers after use.
205243

206-
`disk` is never selected implicitly. If `--params-backend` is not set, parameters use the runtime backend.
244+
Outside `--auto-fit`, `disk` is never selected implicitly. If `--params-backend` is not set, parameters use the runtime backend.
207245

208246
Per-module assignments can be mixed:
209247

@@ -252,4 +290,7 @@ The example CLI/server still accepts these older CPU placement flags as compatib
252290

253291
Because this default is inserted first, later explicit `--params-backend` entries can still override it, for example `--offload-to-cpu --params-backend te=disk` keeps non-TE parameters on CPU and reloads TE parameters from disk.
254292

255-
Library callers should set `backend` and `params_backend` directly. The old CPU/offload fields are no longer part of the C API. Explicit `--backend` and `--params-backend` assignments are preferred for new commands.
293+
Library callers should set `backend` and `params_backend` directly. `sd_ctx_params_init()`
294+
enables `auto_fit` by default; nonempty `backend` or `params_backend` assignments disable it.
295+
The old CPU/offload fields are no longer part of the C API. Explicit `--backend` and
296+
`--params-backend` assignments are preferred for new commands.

examples/common/common.cpp

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -548,12 +548,6 @@ ArgOptions SDContextParams::get_options() {
548548
"--eager-load",
549549
"load all params into the params backend at model-load time instead of lazily on first use (defaults to false)",
550550
true, &eager_load},
551-
{"",
552-
"--auto-fit",
553-
"pick the diffusion/te/vae device placements automatically from the model size and the per-device "
554-
"memory budgets (--max-vram; defaults to free memory minus a small margin). Overrides --backend and "
555-
"--params-backend; may split modules across GPUs (--split-mode still selects layer or row)",
556-
true, &auto_fit},
557551
{"",
558552
"--force-sdxl-vae-conv-scale",
559553
"force use of conv scale on sdxl vae",
@@ -596,6 +590,23 @@ ArgOptions SDContextParams::get_options() {
596590
true, &vae_conv_direct},
597591
};
598592

593+
auto on_auto_fit_arg = [&](int argc, const char** argv, int index) {
594+
if (++index >= argc) {
595+
LOG_ERROR("--auto-fit requires 'on' or 'off'");
596+
return -1;
597+
}
598+
const std::string arg = argv[index];
599+
if (arg == "on") {
600+
auto_fit = true;
601+
} else if (arg == "off") {
602+
auto_fit = false;
603+
} else {
604+
LOG_ERROR("invalid --auto-fit value '%s'; expected 'on' or 'off'", argv[index]);
605+
return -1;
606+
}
607+
return 1;
608+
};
609+
599610
auto on_type_arg = [&](int argc, const char** argv, int index) {
600611
if (++index >= argc) {
601612
return -1;
@@ -667,6 +678,12 @@ ArgOptions SDContextParams::get_options() {
667678
};
668679

669680
options.manual_options = {
681+
{"",
682+
"--auto-fit",
683+
"on|off (default: on). Use one GPU for diffusion/te/vae computation and place weights on that GPU, "
684+
"RAM, another GPU, or disk in that order, according to available memory (--max-vram limits GPU budgets). "
685+
"Disabled by explicit --backend or --params-backend; uses automatic graph segmentation when needed",
686+
on_auto_fit_arg},
670687
{"",
671688
"--type",
672689
"weight type (examples: f32, f16, q4_0, q4_1, q5_0, q5_1, q8_0, q2_K, q3_K, q4_K). "

examples/common/common.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ struct SDContextParams {
158158
std::string params_backend;
159159
std::string split_mode;
160160
std::string model_args;
161-
bool auto_fit = false;
161+
bool auto_fit = true;
162162
std::string rpc_servers;
163163
std::string effective_backend;
164164
std::string effective_params_backend;

0 commit comments

Comments
 (0)