Coverage for widitapp/apps.py: 100.00%

71 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2026-09-22 07:22 +0000

1from typing import Sequence 

2from pathlib import Path 

3from cluey import Cluey, main, tool, method 

4 

5 

6class WiDiTApp(Cluey): 

7 @method 

8 def model( 

9 self, 

10 checkpoint: Path = None, 

11 dim: int = 2, 

12 unet: bool = False, 

13 in_channels: int = 1, 

14 out_channels: int = None, 

15 use_diffusion: bool = True, 

16 use_conditioning: bool = None, 

17 verbose: bool = False, 

18 preset: str = "", 

19 hidden_size: int = 768, 

20 depth: int = 12, 

21 num_heads: int = 12, 

22 patch_size: int = 2, 

23 window_size: int = 4, 

24 mlp_ratio: float = 4.0, 

25 filters: int = 64, 

26 kernel: int = 3, 

27 layers: int = 4, 

28 use_flash_attention: bool = True, 

29 timestep_embed_dim: int = None, 

30 **kwargs, 

31 ): 

32 if "spatial_dim" in kwargs: 

33 dim = kwargs.pop("spatial_dim") 

34 if "spatial_dims" in kwargs: 

35 dim = kwargs.pop("spatial_dims") 

36 

37 if use_conditioning is None: 

38 use_conditioning = use_diffusion 

39 

40 if out_channels is None: 

41 out_channels = 1 + int(use_diffusion) 

42 

43 # If a checkpoint is provided, load the model from the checkpoint instead of building a new one 

44 if checkpoint is not None: 

45 from widit import load_model 

46 

47 model = load_model(checkpoint) 

48 

49 # TODO: Add some checks to make sure the loaded model is compatible with the provided parameters (e.g. in_channels, out_channels, etc.) 

50 

51 return model 

52 

53 if unet: 

54 from widit import Unet 

55 

56 return Unet( 

57 in_channels=in_channels, 

58 out_channels=out_channels, 

59 filters=filters, 

60 kernel_size=kernel, 

61 layers=layers, 

62 spatial_dim=dim, 

63 use_conditioning=use_conditioning, 

64 timestep_embed_dim=timestep_embed_dim, 

65 ) 

66 

67 from widit import WiDiT, PRESETS 

68 

69 model_kwargs: dict = {} 

70 

71 if preset: 

72 assert preset in PRESETS, f"Model '{preset}' not in PRESETS: {list(PRESETS.keys())}" 

73 instantiator = PRESETS[preset] 

74 else: 

75 instantiator = WiDiT 

76 model_kwargs = dict( 

77 spatial_dim=dim, 

78 hidden_size=hidden_size, 

79 depth=depth, 

80 num_heads=num_heads, 

81 patch_size=patch_size, 

82 ) 

83 

84 model = instantiator( 

85 in_channels=in_channels, 

86 out_channels=out_channels, 

87 use_conditioning=use_conditioning, 

88 window_size=window_size, 

89 mlp_ratio=mlp_ratio, 

90 use_flash_attention=use_flash_attention, 

91 timestep_embed_dim=timestep_embed_dim, 

92 **model_kwargs 

93 ) 

94 

95 if verbose: 

96 total = sum(p.numel() for p in model.parameters()) 

97 print(f"Model: {instantiator}") 

98 print(f"Model Summary:\n{model}") 

99 print(f"Model has {total:,} parameters") 

100 trainable = sum(p.numel() for p in model.parameters() if p.requires_grad) 

101 print(f"Model has {trainable:,} trainable parameters") 

102 

103 return model 

104 

105 @method 

106 def datasets( 

107 self, 

108 **kwargs, 

109 ) -> tuple: 

110 """ Returns training and validation datasets """ 

111 raise NotImplementedError("Datasets method not yet implemented.") 

112 

113 @method("datasets") 

114 def dataloaders( 

115 self, 

116 num_workers:int = 4, 

117 batch_size:int = 1, 

118 **kwargs, 

119 ): 

120 from torch.utils.data import DataLoader 

121 

122 training_dataset, validation_dataset = self.datasets(**kwargs) 

123 training_dataloader = DataLoader( 

124 training_dataset, 

125 batch_size=batch_size, 

126 shuffle=True, 

127 drop_last=False, 

128 num_workers=num_workers, 

129 pin_memory=True, 

130 ) 

131 

132 validation_dataloader = None 

133 if validation_dataset is not None: 

134 validation_dataloader = DataLoader( 

135 validation_dataset, 

136 batch_size=batch_size, 

137 shuffle=False, 

138 drop_last=False, 

139 num_workers=num_workers, 

140 pin_memory=True, 

141 ) 

142 

143 return training_dataloader, validation_dataloader 

144 

145 @method 

146 def loss(self, loss_fn: str = "mse", use_diffusion: bool = True): 

147 """Build a supervised loss, or an optional extra diffusion image loss. 

148 

149 Override with ``@method`` to expose custom loss options to ``train``. 

150 Return a module accepting (prediction, target). For diffusion these 

151 are the unclipped clean-image estimate and clean target; the returned 

152 loss is added to the standard diffusion objective. None adds nothing. 

153 """ 

154 from .training import build_loss_fn 

155 

156 return None if use_diffusion else build_loss_fn(loss_fn) 

157 

158 @method 

159 def metrics(self, use_diffusion: bool = True): 

160 """Return named validation modules accepting (prediction, target). 

161 

162 Names omit the ``val/`` prefix. Each module returns a scalar batch 

163 mean. Diffusion metrics use the unclipped clean-image estimate. 

164 Extend with ``@method("super")`` and ``super().metrics(**kwargs)``. 

165 """ 

166 import torch 

167 

168 if use_diffusion: 

169 return {} 

170 return {"mse": torch.nn.MSELoss(), "smoothl1": torch.nn.SmoothL1Loss()} 

171 

172 @tool("model", "dataloaders", "loss", "metrics") 

173 def train( 

174 self, 

175 epochs: int = 40, 

176 log_every: int = 100, 

177 learning_rate: float = 1e-4, 

178 results_dir: Path = "./results", 

179 use_diffusion: bool = True, 

180 wandb: bool = False, 

181 wandb_project: str = "", 

182 run_name: str = "", 

183 **kwargs, 

184 ): 

185 """ Train the model """ 

186 

187 from .training import train 

188 

189 model = self.model(use_diffusion=use_diffusion, **kwargs) 

190 

191 wandb_project = wandb_project or str(self.__class__.__name__) 

192 

193 training_dataloader, validation_dataloader = self.dataloaders(**kwargs) 

194 criterion = self.loss(use_diffusion=use_diffusion, **kwargs) 

195 loss_kwargs = ( 

196 {"diffusion_loss_fn": criterion} 

197 if use_diffusion else {"loss_fn": criterion} 

198 ) 

199 train( 

200 model=model, 

201 training_dataloader=training_dataloader, 

202 validation_dataloader=validation_dataloader, 

203 results_dir=results_dir, 

204 use_diffusion=use_diffusion, 

205 learning_rate=learning_rate, 

206 epochs=epochs, 

207 log_every=log_every, 

208 run_name=run_name, 

209 wandb_logging=wandb, 

210 wandb_project=wandb_project, 

211 metrics=self.metrics(use_diffusion=use_diffusion, **kwargs), 

212 **loss_kwargs, 

213 ) 

214 

215 @main 

216 def predict( 

217 self, 

218 **kwargs, 

219 ): 

220 """ Makes predictions """ 

221 raise NotImplementedError("Prediction not yet implemented.")