Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
780e112
[modular] auto offload: tunable memory reserve, OOM retry, non-disrup…
yiyixuxu Jul 27, 2026
d4b4edd
[modular] drop stale "dynamic mode" wording from the offload test com…
yiyixuxu Jul 28, 2026
34036c1
Record what the auto-offloader does
yiyixuxu Jul 28, 2026
24460ae
Group the offload record table by decision
Jul 29, 2026
b755e08
Split display helpers into components_manager_utils; drop activation …
yiyixuxu Jul 29, 2026
d2eb134
Trim the offload record to consumed fields; hook reads available memo…
yiyixuxu Jul 29, 2026
09d5ec7
Make offload record events pure moves
yiyixuxu Jul 29, 2026
d3c1e9d
Drop OffloadRecord.summary(): the table is the record
yiyixuxu Jul 29, 2026
b360420
Address review: discover entry points via an apply_forward_hook marker
yiyixuxu Jul 29, 2026
b313f79
Inline the OOM-retry helper into wrap_forward, its only caller
yiyixuxu Jul 29, 2026
0e512dd
Shrink ComponentsManager to its used surface
yiyixuxu Jul 29, 2026
77615c0
link issue to TODO
yiyixuxu Jul 29, 2026
ba14c2a
Address review: record at call sites, one device rule, fewer moving p…
yiyixuxu Jul 30, 2026
3f56149
Overhaul the ComponentsManager test suite around real behavior
yiyixuxu Jul 30, 2026
29bd6e0
Add slow modular-pipeline integration tests with declared offload beh…
yiyixuxu Jul 30, 2026
e20dc87
Restore ModularPipelineOffloadTesterMixin to its original form
yiyixuxu Jul 31, 2026
0750134
add set_offload_strategy, Peak column in offload record, and offloadi…
yiyixuxu Jul 31, 2026
9a1c508
make style
yiyixuxu Jul 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 134 additions & 15 deletions docs/source/en/modular_diffusers/components_manager.md

Large diffs are not rendered by default.

22 changes: 3 additions & 19 deletions docs/source/zh/modular_diffusers/components_manager.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -55,13 +55,13 @@ pipe.load_components()
pipe2 = ModularPipeline.from_pretrained("YiYiXu/modular-demo-auto", components_manager=comp, collection="test2")
```

使用 [`~ModularPipeline.null_component_names`] 属性来识别需要加载的任何组件,使用 [`~ComponentsManager.get_components_by_names`] 检索它们,然后调用 [`~ModularPipeline.update_components`] 来添加缺失的组件。
使用 [`~ModularPipeline.null_component_names`] 属性来识别需要加载的任何组件,使用 [`~ComponentsManager.get_one`] 检索它们,然后调用 [`~ModularPipeline.update_components`] 来添加缺失的组件。

```py
pipe2.null_component_names
['text_encoder', 'text_encoder_2', 'tokenizer', 'tokenizer_2', 'image_encoder', 'unet', 'vae', 'scheduler', 'controlnet']

comp_dict = comp.get_components_by_names(names=pipe2.null_component_names)
comp_dict = {name: comp.get_one(name=name) for name in pipe2.null_component_names}
pipe2.update_components(**comp_dict)
```

Expand All@@ -87,30 +87,14 @@ comp.remove("text_encoder_139917733042864")

### get_one

[`~ComponentsManager.get_one`] 方法返回单个组件,并支持对 `name` 参数进行模式匹配。如果多个组件匹配,[`~ComponentsManager.get_one`] 会返回错误。

| 模式 | 示例 | 描述 |
|-------------|----------------------------------|-------------------------------------------|
| exact | `comp.get_one(name="unet")` | 精确名称匹配 |
| wildcard | `comp.get_one(name="unet*")` | 名称以 "unet" 开头 |
| exclusion | `comp.get_one(name="!unet")` | 排除名为 "unet" 的组件 |
| or | `comp.get_one(name="unet|vae")` | 名称为 "unet" 或 "vae" |
[`~ComponentsManager.get_one`] 方法按 `name` 精确匹配并返回单个组件。如果多个组件匹配,[`~ComponentsManager.get_one`] 会返回错误。

[`~ComponentsManager.get_one`] 还通过 `collection` 参数或 `load_id` 参数过滤组件。

```py
comp.get_one(name="unet", collection="sdxl")
```

### get_components_by_names

[`~ComponentsManager.get_components_by_names`] 方法接受一个名称列表,并返回一个将名称映射到组件的字典。这在 [`ModularPipeline`] 中特别有用,因为它们提供了所需组件名称的列表,并且返回的字典可以直接传递给 [`~ModularPipeline.update_components`]。

```py
component_dict = comp.get_components_by_names(names=["text_encoder", "unet", "vae"])
{"text_encoder": component1, "unet": component2, "vae": component3}
```

## 重复检测

建议使用 [`ComponentSpec`] 加载模型组件,以分配具有唯一 id 的组件,该 id 编码了它们的加载参数。这允许 [`ComponentsManager`] 自动检测并防止重复的模型实例,即使不同的对象代表相同的底层检查点。
Expand Down
995 changes: 476 additions & 519 deletions src/diffusers/modular_pipelines/components_manager.py

Large diffs are not rendered by default.

76 changes: 76 additions & 0 deletions src/diffusers/modular_pipelines/components_manager_utils.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
# Copyright 2026 The HuggingFace Team. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Display helpers for `ComponentsManager`: human-readable sizes, text tables, per-layer value summaries."""

from __future__ import annotations

from typing import Any


def format_size(num_bytes: int | None) -> str:
"""Bytes as a human-readable size, so that both a 20GB transformer and a KB-sized test model read sensibly."""
if num_bytes is None:
return "-"
for unit in ("B", "KB", "MB"):
if abs(num_bytes) < 1024:
return f"{num_bytes:.0f} {unit}" if unit == "B" else f"{num_bytes:.2f} {unit}"
num_bytes /= 1024
return f"{num_bytes:.2f} GB"


def format_table(headers: list[str], rows: list[list[str]]) -> list[str]:
"""
Align `headers` and `rows` into text-table lines (header first), each column as wide as its longest cell and cells
joined by " | ". The last column is not padded, so a long final cell (a reason, a load id) runs free without
stretching the table. Separator lines are the caller's to add — `len(lines[0])` is the table width.
"""
widths = [len(header) for header in headers]
for row in rows:
for index, cell in enumerate(row[:-1]):
widths[index] = max(widths[index], len(cell))

def line(cells: list[str]) -> str:
# rstrip so a row whose last cell is empty does not end in "| " + trailing space
return " | ".join([cell.ljust(width) for cell, width in zip(cells[:-1], widths)] + [cells[-1]]).rstrip()

return [line(headers), *(line(row) for row in rows)]


def summarize_dict_by_value_and_parts(d: dict[str, Any]) -> dict[str, Any]:
"""
Summarize a dict with dot-separated keys by grouping keys that share a value under their longest common prefix.

For example IP-Adapter scales per attention processor: {
'down_blocks.1.attentions.1.transformer_blocks.0.attn2.processor': [0.6],
'down_blocks.1.attentions.1.transformer_blocks.1.attn2.processor': [0.6],
'up_blocks.1.attentions.0.transformer_blocks.0.attn2.processor': [0.3],
} becomes {'down_blocks.1.attentions.1.transformer_blocks': [0.6], 'up_blocks': [0.3]}.
"""
value_to_keys: dict[Any, list[str]] = {}
for key, value in d.items():
hashable = tuple(value) if isinstance(value, list) else value
value_to_keys.setdefault(hashable, []).append(key)

summary = {}
for keys in value_to_keys.values():
split_keys = [key.split(".") for key in keys]
common_parts = []
for parts in zip(*split_keys):
if len(set(parts)) != 1:
break
common_parts.append(parts[0])
value = d[keys[0]]
summary[".".join(common_parts)] = list(value) if isinstance(value, list) else value
return summary
1 change: 1 addition & 0 deletions src/diffusers/utils/accelerate_utils.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -45,4 +45,5 @@ def wrapper(self, *args, **kwargs):
self._hf_hook.pre_forward(self)
return method(self, *args, **kwargs)

wrapper._is_forward_entry_point = True # lets hook machinery statically discover decorated entry points
return wrapper
Loading
Loading