chuchichaestli 0.0.1.dev18__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- chuchichaestli/__about__.py +1 -0
- chuchichaestli/__init__.py +17 -0
- chuchichaestli/debug/__init__.py +92 -0
- chuchichaestli/debug/memory_usage.py +99 -0
- chuchichaestli/debug/visitor.py +137 -0
- chuchichaestli/diffusion/__init__.py +23 -0
- chuchichaestli/diffusion/ddpm/__init__.py +28 -0
- chuchichaestli/diffusion/ddpm/base.py +161 -0
- chuchichaestli/diffusion/ddpm/bbdm.py +152 -0
- chuchichaestli/diffusion/ddpm/cfg_ddpm.py +191 -0
- chuchichaestli/diffusion/ddpm/ddim.py +103 -0
- chuchichaestli/diffusion/ddpm/ddpm.py +136 -0
- chuchichaestli/diffusion/ddpm/indi.py +145 -0
- chuchichaestli/diffusion/ddpm/prior_grad.py +141 -0
- chuchichaestli/diffusion/ddpm/shift_ddpm.py +139 -0
- chuchichaestli/diffusion/distributions.py +83 -0
- chuchichaestli/models/__init__.py +1 -0
- chuchichaestli/models/activations.py +30 -0
- chuchichaestli/models/attention/__init__.py +29 -0
- chuchichaestli/models/attention/attention_gate.py +111 -0
- chuchichaestli/models/attention/self_attention.py +65 -0
- chuchichaestli/models/downsampling.py +52 -0
- chuchichaestli/models/maps.py +33 -0
- chuchichaestli/models/resnet.py +111 -0
- chuchichaestli/models/unet/__init__.py +5 -0
- chuchichaestli/models/unet/blocks.py +183 -0
- chuchichaestli/models/unet/time_embeddings.py +183 -0
- chuchichaestli/models/unet/unet.py +282 -0
- chuchichaestli/models/upsampling.py +52 -0
- chuchichaestli-0.0.1.dev18.dist-info/METADATA +720 -0
- chuchichaestli-0.0.1.dev18.dist-info/RECORD +33 -0
- chuchichaestli-0.0.1.dev18.dist-info/WHEEL +4 -0
- chuchichaestli-0.0.1.dev18.dist-info/licenses/LICENSE +674 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.1.dev18"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""This file is part of Chuchichaestli.
|
|
2
|
+
|
|
3
|
+
Chuchichaestli is free software: you can redistribute it and/or modify
|
|
4
|
+
it under the terms of the GNU General Public License as published by
|
|
5
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
6
|
+
(at your option) any later version.
|
|
7
|
+
|
|
8
|
+
Chuchichaestli is distributed in the hope that it will be useful,
|
|
9
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
+
GNU General Public License for more details.
|
|
12
|
+
|
|
13
|
+
You should have received a copy of the GNU General Public License
|
|
14
|
+
along with Chuchichaestli. If not, see <http://www.gnu.org/licenses/>.
|
|
15
|
+
|
|
16
|
+
Developed by the Intelligent Vision Systems Group at ZHAW.
|
|
17
|
+
"""
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Debugging utilities package.
|
|
2
|
+
|
|
3
|
+
This file is part of Chuchichaestli.
|
|
4
|
+
|
|
5
|
+
Chuchichaestli is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU General Public License as published by
|
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
(at your option) any later version.
|
|
9
|
+
|
|
10
|
+
Chuchichaestli is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU General Public License
|
|
16
|
+
along with Chuchichaestli. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
Developed by the Intelligent Vision Systems Group at ZHAW.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def as_bytes(byte_str: str = "MB") -> int:
|
|
23
|
+
"""Convert bytes strings to integers."""
|
|
24
|
+
match byte_str.lower():
|
|
25
|
+
case "kib":
|
|
26
|
+
return 1 << 10
|
|
27
|
+
case "mib":
|
|
28
|
+
return 1 << 20
|
|
29
|
+
case "gib":
|
|
30
|
+
return 1 << 30
|
|
31
|
+
case "tib":
|
|
32
|
+
return 1 << 40
|
|
33
|
+
case "kb":
|
|
34
|
+
return 1_000
|
|
35
|
+
case "mb":
|
|
36
|
+
return 1_000_000
|
|
37
|
+
case "gb":
|
|
38
|
+
return 1_000_000_000
|
|
39
|
+
case "tb":
|
|
40
|
+
return 1_000_000_000_000
|
|
41
|
+
case _:
|
|
42
|
+
return 1
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def cli_pbar(
|
|
46
|
+
r_fill: float,
|
|
47
|
+
prefix: str | list = "",
|
|
48
|
+
postfix: str | list = "",
|
|
49
|
+
bar_length: int = 60,
|
|
50
|
+
fill_symbol: str = "#",
|
|
51
|
+
empty_symbol: str = "-",
|
|
52
|
+
float_fmt: str = "{:.2f}",
|
|
53
|
+
int_fmt: str = "{:4d}",
|
|
54
|
+
) -> str:
|
|
55
|
+
"""Construct/print a progressbar of given relative length, optionally with labels.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
r_fill (float): rate of progress, a number between 0 and 1
|
|
59
|
+
prefix (str | list): label before the progressbar
|
|
60
|
+
postfix (str | list): label after the progressbar
|
|
61
|
+
bar_length (int): maximum length of the progressbar; automatically determined if negative
|
|
62
|
+
fill_symbol (str): symbol indicating the progressbar's filled status
|
|
63
|
+
empty_symbol (str): symbol indicating the progressbar's empty status
|
|
64
|
+
float_fmt (str): string format for floats in prefix
|
|
65
|
+
int_fmt (str): string format for ints in prefix
|
|
66
|
+
"""
|
|
67
|
+
if isinstance(prefix, list | tuple):
|
|
68
|
+
for i, p in enumerate(prefix):
|
|
69
|
+
if isinstance(p, float):
|
|
70
|
+
prefix[i] = float_fmt.format(p)
|
|
71
|
+
elif isinstance(p, int):
|
|
72
|
+
prefix[i] = int_fmt.format(p)
|
|
73
|
+
if not isinstance(prefix[i], str):
|
|
74
|
+
prefix[i] = f"{prefix[i]}"
|
|
75
|
+
prefix = " ".join(prefix)
|
|
76
|
+
if isinstance(postfix, list | tuple):
|
|
77
|
+
for i, p in enumerate(postfix):
|
|
78
|
+
if isinstance(p, float):
|
|
79
|
+
postfix[i] = float_fmt.format(p)
|
|
80
|
+
elif isinstance(p, int):
|
|
81
|
+
postfix[i] = int_fmt.format(p)
|
|
82
|
+
if not isinstance(postfix[i], str):
|
|
83
|
+
postfix[i] = f"{postfix[i]}"
|
|
84
|
+
postfix = " ".join(postfix)
|
|
85
|
+
bar = fill_symbol * int(r_fill * bar_length) + empty_symbol * int(
|
|
86
|
+
(1 - r_fill) * bar_length
|
|
87
|
+
)
|
|
88
|
+
if bar_length > 0:
|
|
89
|
+
line = f"{prefix} [{bar}] {postfix}"
|
|
90
|
+
else:
|
|
91
|
+
line = f"{prefix}\t{postfix}"
|
|
92
|
+
return line
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Memory debugging utils.
|
|
2
|
+
|
|
3
|
+
This file is part of Chuchichaestli.
|
|
4
|
+
|
|
5
|
+
Chuchichaestli is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU General Public License as published by
|
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
(at your option) any later version.
|
|
9
|
+
|
|
10
|
+
Chuchichaestli is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU General Public License
|
|
16
|
+
along with Chuchichaestli. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
Developed by the Intelligent Vision Systems Group at ZHAW.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import torch
|
|
22
|
+
from torch import nn
|
|
23
|
+
from chuchichaestli.debug.visitor import Visitor, Hook, HookDirection
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class CudaMemoryStatsVisitor(Visitor):
|
|
27
|
+
"""Visitor that collects memory statistics on CUDA GPUs."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, **kwargs):
|
|
30
|
+
"""Initialize the visitor."""
|
|
31
|
+
if "hook_default" in kwargs:
|
|
32
|
+
hook_default = kwargs.pop("hook_default")
|
|
33
|
+
else:
|
|
34
|
+
hook_default = Hook(self._hook, HookDirection.Forward)
|
|
35
|
+
super().__init__(hook_default=hook_default, **kwargs)
|
|
36
|
+
|
|
37
|
+
def _hook(self, module: nn.Module, _input: torch.Tensor, _output: torch.Tensor):
|
|
38
|
+
"""Hook that collects the memory statistics."""
|
|
39
|
+
mem_dict = torch.cuda.memory_stats()
|
|
40
|
+
mem_dict["module"] = module._get_name()
|
|
41
|
+
self._memory_stats.append(mem_dict)
|
|
42
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
def has_cuda(verbose: bool = False) -> bool:
|
|
45
|
+
"""Check if CUDA is built and available."""
|
|
46
|
+
is_avail = torch.cuda.is_available()
|
|
47
|
+
is_built = torch.backends.cuda.is_built()
|
|
48
|
+
is_cuda = is_avail and is_built
|
|
49
|
+
if verbose:
|
|
50
|
+
if not is_built:
|
|
51
|
+
if not is_avail:
|
|
52
|
+
print(
|
|
53
|
+
"CUDA not available most likely because you do not have a CUDA-enabled "
|
|
54
|
+
"device on this machine."
|
|
55
|
+
)
|
|
56
|
+
else:
|
|
57
|
+
print(
|
|
58
|
+
"CUDA not available because the current PyTorch install was not built with "
|
|
59
|
+
"CUDA enabled."
|
|
60
|
+
)
|
|
61
|
+
return is_cuda
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class MPSMemoryAllocationVisitor(Visitor):
|
|
65
|
+
"""Visitor that collects memory statistics on Apple systems."""
|
|
66
|
+
|
|
67
|
+
def __init__(self, **kwargs):
|
|
68
|
+
"""Initialize the visitor."""
|
|
69
|
+
if "hook_default" in kwargs:
|
|
70
|
+
hook_default = kwargs.pop("hook_default")
|
|
71
|
+
else:
|
|
72
|
+
hook_default = Hook(self._hook, HookDirection.Forward)
|
|
73
|
+
super().__init__(hook_default=hook_default, **kwargs)
|
|
74
|
+
|
|
75
|
+
def _hook(self, module: nn.Module, _input: torch.Tensor, _output: torch.Tensor):
|
|
76
|
+
"""Hook that collects the memory statistics."""
|
|
77
|
+
allocated = torch.mps.current_allocated_memory()
|
|
78
|
+
mem_dict = {"allocated": allocated, "module": module._get_name()}
|
|
79
|
+
self._memory_stats.append(mem_dict)
|
|
80
|
+
|
|
81
|
+
@staticmethod
|
|
82
|
+
def has_mps(verbose: bool = False) -> bool:
|
|
83
|
+
"""Check if MPS is built and available."""
|
|
84
|
+
is_avail = torch.backends.mps.is_available()
|
|
85
|
+
is_built = torch.backends.mps.is_built()
|
|
86
|
+
is_mps = is_avail and is_built
|
|
87
|
+
if verbose:
|
|
88
|
+
if not is_avail:
|
|
89
|
+
if not is_built:
|
|
90
|
+
print(
|
|
91
|
+
"MPS not available because the current PyTorch install was not built with "
|
|
92
|
+
"MPS enabled."
|
|
93
|
+
)
|
|
94
|
+
else:
|
|
95
|
+
print(
|
|
96
|
+
"MPS not available because the current MacOS version is not 12.3+ and/or "
|
|
97
|
+
"you do not have an MPS-enabled device on this machine."
|
|
98
|
+
)
|
|
99
|
+
return is_mps
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Visitor pattern implementation for PyTorch models.
|
|
2
|
+
|
|
3
|
+
This file is part of Chuchichaestli.
|
|
4
|
+
|
|
5
|
+
Chuchichaestli is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU General Public License as published by
|
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
(at your option) any later version.
|
|
9
|
+
|
|
10
|
+
Chuchichaestli is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU General Public License
|
|
16
|
+
along with Chuchichaestli. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
Developed by the Intelligent Vision Systems Group at ZHAW.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from dataclasses import dataclass
|
|
22
|
+
from enum import Enum
|
|
23
|
+
|
|
24
|
+
from collections.abc import Callable
|
|
25
|
+
from typing import Any
|
|
26
|
+
|
|
27
|
+
from torch import nn
|
|
28
|
+
from chuchichaestli.debug import as_bytes, cli_pbar
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class HookDirection(Enum):
|
|
32
|
+
"""Enum that represents the direction of a hook."""
|
|
33
|
+
|
|
34
|
+
Forward: str = "forward"
|
|
35
|
+
Backward: str = "backward"
|
|
36
|
+
Both: str = "both"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class Hook:
|
|
41
|
+
"""Represents a hook (function to call and direction to attach the hook)."""
|
|
42
|
+
|
|
43
|
+
fn: Callable[[nn.Module, Any], Any]
|
|
44
|
+
direction: HookDirection
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class Visitor:
|
|
48
|
+
"""Generic implementation of a Visitor."""
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
hook_default: Hook | None = None,
|
|
53
|
+
hook_map: dict[object, Hook] = {},
|
|
54
|
+
max_depth: int | None = None,
|
|
55
|
+
_memory_stats: list = [],
|
|
56
|
+
):
|
|
57
|
+
"""Initialize the Visitor.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
hook_default: The default hook to use.
|
|
61
|
+
hook_map: A mapping from layer types to hooks.
|
|
62
|
+
max_depth: The maximum depth to visit.
|
|
63
|
+
_memory_stats: Precached memory statistics history.
|
|
64
|
+
"""
|
|
65
|
+
self.hook_default = hook_default
|
|
66
|
+
self.hook_map = hook_map
|
|
67
|
+
self.max_depth = max_depth
|
|
68
|
+
self._memory_stats = _memory_stats
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def memory_stats(self):
|
|
72
|
+
"""Return the collected memory statistics."""
|
|
73
|
+
return self._memory_stats
|
|
74
|
+
|
|
75
|
+
def unlink(self):
|
|
76
|
+
"""Unlink hooks and maps."""
|
|
77
|
+
self.hook_default = None
|
|
78
|
+
self.hook_map = {}
|
|
79
|
+
|
|
80
|
+
def visit(self, visitee, depth: int = 0, caller: nn.Module | None = None):
|
|
81
|
+
"""Function called when visiting an object."""
|
|
82
|
+
# print(visitee, depth, caller)
|
|
83
|
+
if self.max_depth and depth > self.max_depth:
|
|
84
|
+
return
|
|
85
|
+
for layer in visitee.modules():
|
|
86
|
+
if caller is layer:
|
|
87
|
+
return
|
|
88
|
+
layer_type = type(layer)
|
|
89
|
+
hook = self.hook_map.setdefault(layer_type, self.hook_default)
|
|
90
|
+
match hook.direction:
|
|
91
|
+
case HookDirection.Forward:
|
|
92
|
+
layer.register_forward_hook(hook.fn)
|
|
93
|
+
case HookDirection.Backward:
|
|
94
|
+
layer.register_backward_hook(hook.fn)
|
|
95
|
+
case HookDirection.Both:
|
|
96
|
+
layer.register_forward_hook(hook.fn)
|
|
97
|
+
layer.register_backward_hook(hook.fn)
|
|
98
|
+
self.visit(layer, depth=depth + 1, caller=layer)
|
|
99
|
+
|
|
100
|
+
def report(
|
|
101
|
+
self,
|
|
102
|
+
unit: str = "MB",
|
|
103
|
+
with_bar: bool = True,
|
|
104
|
+
bar_length: int = 60,
|
|
105
|
+
verbose: bool = True,
|
|
106
|
+
) -> list[str] | None:
|
|
107
|
+
"""Report the memory statistics for the visited module(s).
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
unit (str): The byte unit; one in [KB, MB, GB, TB, KiB, MiB, GiB, TiB].
|
|
111
|
+
with_bar (bool): Add simple horizontal percentage bars.
|
|
112
|
+
bar_length (int): The length of the percentage bars in number of characters.
|
|
113
|
+
verbose (bool): Directly print lines to stdout.
|
|
114
|
+
"""
|
|
115
|
+
if not hasattr(self, "memory_stats"):
|
|
116
|
+
return []
|
|
117
|
+
divisor = as_bytes(unit)
|
|
118
|
+
mem_stats = self.memory_stats.copy()
|
|
119
|
+
if not mem_stats:
|
|
120
|
+
return []
|
|
121
|
+
total_alloc = sum(module["allocated"] for module in mem_stats)
|
|
122
|
+
for stat in mem_stats:
|
|
123
|
+
stat["rel_alloc"] = stat["allocated"] / total_alloc
|
|
124
|
+
stat["allocated"] /= divisor
|
|
125
|
+
total_alloc /= divisor
|
|
126
|
+
|
|
127
|
+
lines = []
|
|
128
|
+
module_name_length = max([len(stat["module"]) for stat in mem_stats])
|
|
129
|
+
|
|
130
|
+
for stat in mem_stats:
|
|
131
|
+
prefix = [stat["module"].ljust(module_name_length), stat["allocated"], unit]
|
|
132
|
+
postfix = [stat["rel_alloc"] * 100, "%"]
|
|
133
|
+
line = cli_pbar(stat["rel_alloc"], prefix, postfix, bar_length=bar_length)
|
|
134
|
+
lines.append(line)
|
|
135
|
+
if verbose:
|
|
136
|
+
print("\n".join(lines))
|
|
137
|
+
return lines
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Diffusion schedulers sub-module.
|
|
2
|
+
|
|
3
|
+
This file is part of Chuchichaestli.
|
|
4
|
+
|
|
5
|
+
Chuchichaestli is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU General Public License as published by
|
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
(at your option) any later version.
|
|
9
|
+
|
|
10
|
+
Chuchichaestli is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU General Public License
|
|
16
|
+
along with Chuchichaestli. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
Developed by the Intelligent Vision Systems Group at ZHAW.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from chuchichaestli.diffusion.ddpm import DDPM, InDI, PriorGrad, CFGDDPM, BBDM, DDIM
|
|
22
|
+
|
|
23
|
+
__all__ = ["DDPM", "InDI", "PriorGrad", "CFGDDPM", "BBDM", "DDIM"]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Implementation of DDPM and its variants.
|
|
2
|
+
|
|
3
|
+
This file is part of Chuchichaestli.
|
|
4
|
+
|
|
5
|
+
Chuchichaestli is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU General Public License as published by
|
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
(at your option) any later version.
|
|
9
|
+
|
|
10
|
+
Chuchichaestli is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU General Public License
|
|
16
|
+
along with Chuchichaestli. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
Developed by the Intelligent Vision Systems Group at ZHAW.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from chuchichaestli.diffusion.ddpm.ddpm import DDPM
|
|
22
|
+
from chuchichaestli.diffusion.ddpm.indi import InDI
|
|
23
|
+
from chuchichaestli.diffusion.ddpm.prior_grad import PriorGrad
|
|
24
|
+
from chuchichaestli.diffusion.ddpm.cfg_ddpm import CFGDDPM
|
|
25
|
+
from chuchichaestli.diffusion.ddpm.bbdm import BBDM
|
|
26
|
+
from chuchichaestli.diffusion.ddpm.ddim import DDIM
|
|
27
|
+
|
|
28
|
+
__all__ = ["DDPM", "InDI", "PriorGrad", "CFGDDPM", "BBDM", "DDIM"]
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""Base class for diffusion processes.
|
|
2
|
+
|
|
3
|
+
This file is part of Chuchichaestli.
|
|
4
|
+
|
|
5
|
+
Chuchichaestli is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU General Public License as published by
|
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
(at your option) any later version.
|
|
9
|
+
|
|
10
|
+
Chuchichaestli is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU General Public License
|
|
16
|
+
along with Chuchichaestli. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
|
|
18
|
+
Developed by the Intelligent Vision Systems Group at ZHAW.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from abc import ABC, abstractmethod
|
|
22
|
+
from typing import Any
|
|
23
|
+
from collections.abc import Generator
|
|
24
|
+
|
|
25
|
+
import torch
|
|
26
|
+
|
|
27
|
+
from chuchichaestli.diffusion.distributions import DistributionAdapter
|
|
28
|
+
|
|
29
|
+
SCHEDULES = {
|
|
30
|
+
# A simple linear schedule from `beta_start` to `beta_end`
|
|
31
|
+
"linear": lambda beta_start, beta_end, num_timesteps, device: torch.linspace(
|
|
32
|
+
beta_start, beta_end, num_timesteps, device=device
|
|
33
|
+
),
|
|
34
|
+
# Scaled linear schedule to account for different step sizes
|
|
35
|
+
"linear_scaled": lambda beta_start, beta_end, num_timesteps, device: torch.linspace(
|
|
36
|
+
(1000 / num_timesteps) * beta_start,
|
|
37
|
+
(1000 / num_timesteps) * beta_end,
|
|
38
|
+
num_timesteps,
|
|
39
|
+
device=device,
|
|
40
|
+
),
|
|
41
|
+
# A quadratic growth schedule from `beta_min` to `beta_max`
|
|
42
|
+
"squared": lambda beta_min, beta_max, num_timesteps, device: beta_min
|
|
43
|
+
+ (
|
|
44
|
+
(beta_max - beta_min)
|
|
45
|
+
* (
|
|
46
|
+
torch.arange(num_timesteps, dtype=torch.float32, device=device)
|
|
47
|
+
/ num_timesteps
|
|
48
|
+
)
|
|
49
|
+
** 2
|
|
50
|
+
),
|
|
51
|
+
# A sigmoid-shaped schedule to achieve a smoother transition from `beta_start` to `beta_end`
|
|
52
|
+
"sigmoid": lambda beta_start, beta_end, num_timesteps, device: (
|
|
53
|
+
torch.sigmoid(torch.linspace(-6, 6, num_timesteps, device=device))
|
|
54
|
+
* (beta_end - beta_start)
|
|
55
|
+
+ beta_start
|
|
56
|
+
),
|
|
57
|
+
# A cosine-shaped schedule for smoother transitions, emphasizing the start and the end
|
|
58
|
+
"cosine": lambda beta_min, beta_max, num_timesteps, device: beta_min
|
|
59
|
+
+ (
|
|
60
|
+
(beta_max - beta_min)
|
|
61
|
+
* (
|
|
62
|
+
1
|
|
63
|
+
- torch.cos(
|
|
64
|
+
(
|
|
65
|
+
torch.arange(num_timesteps, dtype=torch.float32, device=device)
|
|
66
|
+
/ num_timesteps
|
|
67
|
+
)
|
|
68
|
+
* torch.pi
|
|
69
|
+
/ 2
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
),
|
|
73
|
+
# An exponential schedule to smoothly increase the beta value over time
|
|
74
|
+
"exponential": lambda beta_min, beta_max, num_timesteps, device: beta_min
|
|
75
|
+
* (
|
|
76
|
+
(beta_max / beta_min)
|
|
77
|
+
** (
|
|
78
|
+
torch.arange(num_timesteps, dtype=torch.float32, device=device)
|
|
79
|
+
/ num_timesteps
|
|
80
|
+
)
|
|
81
|
+
),
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
SAMPLERS = {
|
|
86
|
+
"DDIM": "ddim",
|
|
87
|
+
"default": "default",
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class DiffusionProcess(ABC):
|
|
92
|
+
"""Base class for diffusion processes."""
|
|
93
|
+
|
|
94
|
+
def __init__(
|
|
95
|
+
self,
|
|
96
|
+
timesteps: int,
|
|
97
|
+
device: str = "cpu",
|
|
98
|
+
noise_distribution: DistributionAdapter | None = None,
|
|
99
|
+
*args,
|
|
100
|
+
**kwargs,
|
|
101
|
+
) -> None:
|
|
102
|
+
"""Initialize the diffusion process."""
|
|
103
|
+
self.num_time_steps = timesteps
|
|
104
|
+
self.device = device
|
|
105
|
+
self.noise_distribution = noise_distribution
|
|
106
|
+
|
|
107
|
+
@abstractmethod
|
|
108
|
+
def noise_step(
|
|
109
|
+
self, x_t: torch.Tensor, *args, **kwargs
|
|
110
|
+
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
|
111
|
+
"""Noise step for the diffusion process.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
x_t: Tensor of shape (batch_size, *).
|
|
115
|
+
*args: Additional arguments.
|
|
116
|
+
**kwargs: Additional keyword arguments.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Tuple of the sampled tensor, noise tensor and timesteps.
|
|
120
|
+
"""
|
|
121
|
+
pass
|
|
122
|
+
|
|
123
|
+
@abstractmethod
|
|
124
|
+
def generate(
|
|
125
|
+
self,
|
|
126
|
+
model: Any,
|
|
127
|
+
condition: torch.Tensor,
|
|
128
|
+
n: int = 1,
|
|
129
|
+
yield_intermediate: bool = False,
|
|
130
|
+
*args,
|
|
131
|
+
**kwargs,
|
|
132
|
+
) -> Generator[torch.Tensor, None, torch.Tensor]:
|
|
133
|
+
"""Sample from the diffusion process.
|
|
134
|
+
|
|
135
|
+
Samples n times the number of conditions from the diffusion process.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
model: Model to use for sampling.
|
|
139
|
+
condition: Tensor to condition generation on. For unconditional generation, supply a zero-tensor of the sample shape.
|
|
140
|
+
n: Number of samples to generate (batch size).
|
|
141
|
+
yield_intermediate: Yield intermediate results. This turns the function into a generator.
|
|
142
|
+
*args: Additional arguments.
|
|
143
|
+
**kwargs: Additional keyword
|
|
144
|
+
"""
|
|
145
|
+
pass
|
|
146
|
+
|
|
147
|
+
def sample_timesteps(self, n: int) -> torch.Tensor:
|
|
148
|
+
"""Sample timesteps for the diffusion process."""
|
|
149
|
+
return torch.randint(0, self.num_time_steps, (n,), device=self.device)
|
|
150
|
+
|
|
151
|
+
def sample_noise(self, shape: torch.Size) -> torch.Tensor:
|
|
152
|
+
"""Sample noise for the diffusion process.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
shape: Shape of the noise tensor.
|
|
156
|
+
mean: Mean of the noise tensor.
|
|
157
|
+
scale: Scale of the noise tensor.
|
|
158
|
+
"""
|
|
159
|
+
if self.noise_distribution is not None:
|
|
160
|
+
return self.noise_distribution(shape)
|
|
161
|
+
return torch.randn(shape, device=self.device)
|