diff --git a/.github/workflows/pythonapp-min.yml b/.github/workflows/pythonapp-min.yml index 3e4f7b24a4..0d147ca264 100644 --- a/.github/workflows/pythonapp-min.yml +++ b/.github/workflows/pythonapp-min.yml @@ -124,7 +124,7 @@ jobs: strategy: fail-fast: false matrix: - pytorch-version: ['2.4.1', '2.5.1', '2.6.0', '2.7.1'] + pytorch-version: ['2.5.1', '2.6.0', '2.7.1', '2.8.0'] timeout-minutes: 40 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index 536ae6d18a..c68c879231 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -94,7 +94,7 @@ jobs: - if: runner.os == 'windows' name: Install torch cpu from pytorch.org (Windows only) run: | - python -m pip install torch==2.4.1 torchvision==0.19.1+cpu --index-url https://download.pytorch.org/whl/cpu + python -m pip install torch==2.5.1 torchvision==0.20.1+cpu --index-url https://download.pytorch.org/whl/cpu - if: runner.os == 'Linux' name: Install itk pre-release (Linux only) run: | @@ -103,7 +103,7 @@ jobs: - name: Install the dependencies run: | python -m pip install --user --upgrade pip wheel - python -m pip install torch==2.4.1 torchvision==0.19.1 + python -m pip install torch==2.5.1 torchvision==0.20.1 cat "requirements-dev.txt" python -m pip install -r requirements-dev.txt python -m pip list @@ -155,7 +155,7 @@ jobs: # install the latest pytorch for testing # however, "pip install monai*.tar.gz" will build cpp/cuda with an isolated # fresh torch installation according to pyproject.toml - python -m pip install torch>=2.4.1 torchvision + python -m pip install torch>=2.5.1 torchvision - name: Check packages run: | pip uninstall monai diff --git a/monai/apps/detection/metrics/coco.py b/monai/apps/detection/metrics/coco.py index a856f14fb8..d1347d76b8 100644 --- a/monai/apps/detection/metrics/coco.py +++ b/monai/apps/detection/metrics/coco.py @@ -457,7 +457,7 @@ def _compute_statistics(self, results_list: list[dict[int, dict[str, np.ndarray] dt_ignores = np.concatenate([r["dtIgnore"][:, 0:max_det] for r in results], axis=1)[:, inds] self.check_number_of_iou(dt_matches, dt_ignores) gt_ignore = np.concatenate([r["gtIgnore"] for r in results]) - num_gt = np.count_nonzero(gt_ignore == 0) # number of ground truth boxes (non ignored) + num_gt = int(np.count_nonzero(gt_ignore == 0)) # number of ground truth boxes (non ignored) if num_gt == 0: logger.warning(f"WARNING, no gt found for coco metric for class {cls_i}") continue @@ -523,13 +523,12 @@ def _compute_stats_single_threshold( recall = 0 # array where precision values nearest to given recall th are saved - precision = np.zeros((num_recall_th,)) + precision = [0.0] * num_recall_th # save scores for corresponding recall value in here th_scores = np.zeros((num_recall_th,)) # numpy is slow without cython optimization for accessing elements # use python array gets significant speed improvement pr = pr.tolist() - precision = precision.tolist() # smooth precision curve (create box shape) for i in range(len(tp) - 1, 0, -1): diff --git a/monai/apps/detection/utils/box_coder.py b/monai/apps/detection/utils/box_coder.py index d0f3adf71d..6424ffa229 100644 --- a/monai/apps/detection/utils/box_coder.py +++ b/monai/apps/detection/utils/box_coder.py @@ -210,7 +210,10 @@ def decode_single(self, rel_codes: Tensor, reference_boxes: Tensor) -> Tensor: offset = reference_boxes.shape[-1] pred_boxes = [] - boxes_cccwhd = convert_box_mode(reference_boxes, src_mode=StandardMode, dst_mode=CenterSizeMode) + boxes_cccwhd: torch.Tensor = convert_box_mode( + reference_boxes, src_mode=StandardMode, dst_mode=CenterSizeMode + ) # type: ignore[assignment] + for axis in range(self.spatial_dims): whd_axis = boxes_cccwhd[:, axis + self.spatial_dims] ctr_xyz_axis = boxes_cccwhd[:, axis] diff --git a/monai/apps/generation/maisi/networks/diffusion_model_unet_maisi.py b/monai/apps/generation/maisi/networks/diffusion_model_unet_maisi.py index e990b5fc98..4eac17b870 100644 --- a/monai/apps/generation/maisi/networks/diffusion_model_unet_maisi.py +++ b/monai/apps/generation/maisi/networks/diffusion_model_unet_maisi.py @@ -358,8 +358,9 @@ def _apply_down_blocks(self, h, emb, context, down_block_additional_residuals): def _apply_up_blocks(self, h, emb, context, down_block_res_samples): for upsample_block in self.up_blocks: - res_samples = down_block_res_samples[-len(upsample_block.resnets) :] - down_block_res_samples = down_block_res_samples[: -len(upsample_block.resnets)] + idx: int = -len(upsample_block.resnets) # type: ignore + res_samples = down_block_res_samples[idx:] + down_block_res_samples = down_block_res_samples[:idx] h = upsample_block(hidden_states=h, res_hidden_states_list=res_samples, temb=emb, context=context) return h diff --git a/monai/data/box_utils.py b/monai/data/box_utils.py index 1010e10b2f..a982b01427 100644 --- a/monai/data/box_utils.py +++ b/monai/data/box_utils.py @@ -811,9 +811,9 @@ def _box_inter_union( # compute size for the intersection region for the NxM combinations wh = (rb - lt + TO_REMOVE).clamp(min=0) # (N,M,spatial_dims) - inter = torch.prod(wh, dim=-1, keepdim=False) # (N,M) + inter: torch.Tensor = torch.prod(wh, dim=-1, keepdim=False) # (N,M) - union = area1[:, None] + area2 - inter + union: torch.Tensor = area1[:, None] + area2 - inter # type: ignore return inter, union @@ -981,7 +981,7 @@ def box_pair_giou(boxes1: NdarrayOrTensor, boxes2: NdarrayOrTensor) -> NdarrayOr wh = (rb - lt + TO_REMOVE).clamp(min=0) # (N,spatial_dims) enclosure = torch.prod(wh, dim=-1, keepdim=False) # (N,) - giou_t = iou - (enclosure - union) / (enclosure + torch.finfo(COMPUTE_DTYPE).eps) + giou_t: torch.Tensor = iou - (enclosure - union) / (enclosure + torch.finfo(COMPUTE_DTYPE).eps) # type: ignore giou_t = giou_t.to(dtype=box_dtype) # (N,spatial_dims) if torch.isnan(giou_t).any() or torch.isinf(giou_t).any(): raise ValueError("Box GIoU is NaN or Inf.") diff --git a/monai/data/dataset.py b/monai/data/dataset.py index 691425994d..e5842bfa7a 100644 --- a/monai/data/dataset.py +++ b/monai/data/dataset.py @@ -1353,7 +1353,7 @@ def __len__(self) -> int: return len(self.dataset) def randomize(self, data: Any | None = None) -> None: - self._seed = self.R.randint(MAX_SEED, dtype="uint32") + self._seed = int(self.R.randint(MAX_SEED, dtype="uint32")) def __getitem__(self, index: int): self.randomize() diff --git a/monai/data/image_dataset.py b/monai/data/image_dataset.py index 6c8ddcf8de..1135b59565 100644 --- a/monai/data/image_dataset.py +++ b/monai/data/image_dataset.py @@ -97,7 +97,7 @@ def __len__(self) -> int: return len(self.image_files) def randomize(self, data: Any | None = None) -> None: - self._seed = self.R.randint(MAX_SEED, dtype="uint32") + self._seed = int(self.R.randint(MAX_SEED, dtype="uint32")) def __getitem__(self, index: int): self.randomize() diff --git a/monai/data/image_reader.py b/monai/data/image_reader.py index 003ec2cf0b..515bf38a39 100644 --- a/monai/data/image_reader.py +++ b/monai/data/image_reader.py @@ -580,7 +580,7 @@ def _combine_dicom_series(self, data: Iterable, filenames: Sequence[PathLike]): shape = first_array.shape spacing = getattr(first_slice, "PixelSpacing", [1.0] * len(shape)) prev_pos = getattr(first_slice, "ImagePositionPatient", (0.0, 0.0, 0.0))[2] - stack_array = [first_array] + stack_array_list: list = [first_array] for idx in range(1, len(slices)): slc_array = self._get_array_data(slices[idx][0], slices[idx][1]) slc_shape = slc_array.shape @@ -592,22 +592,24 @@ def _combine_dicom_series(self, data: Iterable, filenames: Sequence[PathLike]): warnings.warn(f"the list contains slices that have different shapes {shape} and {slc_shape}.") average_distance += abs(prev_pos - slc_pos) prev_pos = slc_pos - stack_array.append(slc_array) + stack_array_list.append(slc_array) if len(slices) > 1: average_distance /= len(slices) - 1 spacing.append(average_distance) if self.to_gpu: - stack_array = cp.stack(stack_array, axis=-1) + stack_array = cp.stack(stack_array_list, axis=-1) else: - stack_array = np.stack(stack_array, axis=-1) + stack_array = np.stack(stack_array_list, axis=-1) + + del stack_array_list[:] stack_metadata = self._get_meta_dict(first_slice) stack_metadata["spacing"] = np.asarray(spacing) if hasattr(slices[-1][0], "ImagePositionPatient"): stack_metadata["lastImagePositionPatient"] = np.asarray(slices[-1][0].ImagePositionPatient) stack_metadata[MetaKeys.SPATIAL_SHAPE] = shape + (len(slices),) else: - stack_array = stack_array[0] + stack_array = stack_array_list[0] stack_metadata = self._get_meta_dict(first_slice) stack_metadata["spacing"] = np.asarray(spacing) stack_metadata[MetaKeys.SPATIAL_SHAPE] = shape diff --git a/monai/networks/nets/diffusion_model_unet.py b/monai/networks/nets/diffusion_model_unet.py index 11196bb343..5604d9de1e 100644 --- a/monai/networks/nets/diffusion_model_unet.py +++ b/monai/networks/nets/diffusion_model_unet.py @@ -1795,8 +1795,9 @@ def forward( # 6. up for upsample_block in self.up_blocks: - res_samples = down_block_res_samples[-len(upsample_block.resnets) :] - down_block_res_samples = down_block_res_samples[: -len(upsample_block.resnets)] + idx: int = -len(upsample_block.resnets) # type: ignore + res_samples = down_block_res_samples[idx:] + down_block_res_samples = down_block_res_samples[:idx] h = upsample_block(hidden_states=h, res_hidden_states_list=res_samples, temb=emb, context=context) # 7. output block diff --git a/monai/networks/nets/dints.py b/monai/networks/nets/dints.py index 129e0925d3..43c0cd031b 100644 --- a/monai/networks/nets/dints.py +++ b/monai/networks/nets/dints.py @@ -492,7 +492,7 @@ def forward(self, x: torch.Tensor): inputs = [] for d in range(self.num_depths): # allow multi-resolution input - _mod_w: StemInterface = self.stem_down[str(d)] + _mod_w: StemInterface = self.stem_down[str(d)] # type: ignore[assignment] x_out = _mod_w.forward(x) if self.node_a[0][d]: inputs.append(x_out) @@ -505,7 +505,7 @@ def forward(self, x: torch.Tensor): start = False _temp: torch.Tensor = torch.empty(0) for res_idx in range(self.num_depths - 1, -1, -1): - _mod_up: StemInterface = self.stem_up[str(res_idx)] + _mod_up: StemInterface = self.stem_up[str(res_idx)] # type: ignore[assignment] if start: _temp = _mod_up.forward(outputs[res_idx] + _temp) elif self.node_a[blk_idx + 1][res_idx]: @@ -680,7 +680,7 @@ def forward(self, x: list[torch.Tensor]) -> list[torch.Tensor]: outputs = [torch.tensor(0.0, dtype=x[0].dtype, device=x[0].device)] * self.num_depths for res_idx, activation in enumerate(self.arch_code_a[blk_idx].data): if activation: - mod: CellInterface = self.cell_tree[str((blk_idx, res_idx))] + mod: CellInterface = self.cell_tree[str((blk_idx, res_idx))] # type: ignore[assignment] _out = mod.forward(x=inputs[self.arch_code2in[res_idx]], weight=None) outputs[self.arch_code2out[res_idx]] = outputs[self.arch_code2out[res_idx]] + _out inputs = outputs @@ -782,12 +782,10 @@ def __init__( for blk_idx in range(self.num_blocks): for res_idx in range(len(self.arch_code2out)): if self.arch_code_a[blk_idx, res_idx] == 1: + cell_inter: Cell = self.cell_tree[str((blk_idx, res_idx))] # type: ignore self.ram_cost[blk_idx, res_idx] = np.array( - [ - op.ram_cost + self.cell_tree[str((blk_idx, res_idx))].preprocess.ram_cost - for op in self.cell_tree[str((blk_idx, res_idx))].op.ops[: self.num_cell_ops] - ] - ) + [op.ram_cost + cell_inter.preprocess.ram_cost for op in cell_inter.op.ops[: self.num_cell_ops]] + ) # type: ignore # define cell and macro architecture probabilities self.log_alpha_c = nn.Parameter( diff --git a/monai/networks/nets/efficientnet.py b/monai/networks/nets/efficientnet.py index 4e6c327b23..e9b7675144 100644 --- a/monai/networks/nets/efficientnet.py +++ b/monai/networks/nets/efficientnet.py @@ -416,8 +416,10 @@ def set_swish(self, memory_efficient: bool = True) -> None: """ self._swish = Act["memswish"]() if memory_efficient else Act["swish"](alpha=1.0) - for sub_stack in self._blocks: - for block in sub_stack: + sub_stack: nn.Sequential + block: MBConvBlock + for sub_stack in self._blocks: # type: ignore[assignment] + for block in sub_stack: # type: ignore[assignment] block.set_swish(memory_efficient) def forward(self, inputs: torch.Tensor): diff --git a/monai/networks/nets/segresnet_ds.py b/monai/networks/nets/segresnet_ds.py index 098e490511..8f575f4793 100644 --- a/monai/networks/nets/segresnet_ds.py +++ b/monai/networks/nets/segresnet_ds.py @@ -219,9 +219,9 @@ def _forward(self, x: torch.Tensor) -> list[torch.Tensor]: x = self.conv_init(x) for level in self.layers: - x = level["blocks"](x) + x = level["blocks"](x) # type: ignore outputs.append(x) - x = level["downsample"](x) + x = level["downsample"](x) # type: ignore if self.head_module is not None: outputs = self.head_module(outputs) @@ -407,12 +407,12 @@ def _forward(self, x: torch.Tensor) -> Union[None, torch.Tensor, list[torch.Tens i = 0 for level in self.up_layers: - x = level["upsample"](x) + x = level["upsample"](x) # type: ignore x += x_down.pop(0) - x = level["blocks"](x) + x = level["blocks"](x) # type: ignore if len(self.up_layers) - i <= self.dsdepth: - outputs.append(level["head"](x)) + outputs.append(level["head"](x)) # type: ignore i = i + 1 outputs.reverse() @@ -508,12 +508,13 @@ def forward( # type: ignore outputs: list[torch.Tensor] = [] outputs_auto: list[torch.Tensor] = [] + level: nn.ModuleDict x_ = x if with_point: if with_label: x_ = x.clone() i = 0 - for level in self.up_layers: + for level in self.up_layers: # type: ignore x = level["upsample"](x) x = x + x_down[i] x = level["blocks"](x) @@ -526,7 +527,7 @@ def forward( # type: ignore x = x_ if with_label: i = 0 - for level in self.up_layers_auto: + for level in self.up_layers_auto: # type: ignore x = level["upsample"](x) x = x + x_down[i] x = level["blocks"](x) diff --git a/monai/networks/nets/spade_diffusion_model_unet.py b/monai/networks/nets/spade_diffusion_model_unet.py index a9609b1d39..adf321ec88 100644 --- a/monai/networks/nets/spade_diffusion_model_unet.py +++ b/monai/networks/nets/spade_diffusion_model_unet.py @@ -961,8 +961,9 @@ def forward( # 6. up for upsample_block in self.up_blocks: - res_samples = down_block_res_samples[-len(upsample_block.resnets) :] - down_block_res_samples = down_block_res_samples[: -len(upsample_block.resnets)] + idx: int = -len(upsample_block.resnets) # type: ignore + res_samples = down_block_res_samples[idx:] + down_block_res_samples = down_block_res_samples[:idx] h = upsample_block(hidden_states=h, res_hidden_states_list=res_samples, seg=seg, temb=emb, context=context) # 7. output block diff --git a/monai/transforms/compose.py b/monai/transforms/compose.py index 4513e26678..e984c4f26a 100644 --- a/monai/transforms/compose.py +++ b/monai/transforms/compose.py @@ -271,7 +271,7 @@ def set_random_state(self, seed: int | None = None, state: np.random.RandomState for _transform in self.transforms: if not isinstance(_transform, Randomizable): continue - _transform.set_random_state(seed=self.R.randint(MAX_SEED, dtype="uint32")) + _transform.set_random_state(seed=int(self.R.randint(MAX_SEED, dtype="uint32"))) return self def randomize(self, data: Any | None = None) -> None: diff --git a/monai/transforms/croppad/dictionary.py b/monai/transforms/croppad/dictionary.py index 462412686a..d35c40465e 100644 --- a/monai/transforms/croppad/dictionary.py +++ b/monai/transforms/croppad/dictionary.py @@ -694,7 +694,7 @@ def __call__( lazy_ = self.lazy if lazy is None else lazy for key in self.key_iterator(dict(data)): - self.cropper.set_random_state(seed=self.sub_seed) + self.cropper.set_random_state(seed=int(self.sub_seed)) for i, im in enumerate(self.cropper(data[key], lazy=lazy_)): ret[i][key] = im return ret diff --git a/monai/transforms/intensity/array.py b/monai/transforms/intensity/array.py index ed0a1ad9ac..0421d34492 100644 --- a/monai/transforms/intensity/array.py +++ b/monai/transforms/intensity/array.py @@ -900,27 +900,28 @@ def __call__(self, img: NdarrayOrTensor) -> NdarrayOrTensor: """ Apply the transform to `img`, assuming `img` is a channel-first array if `self.channel_wise` is True, """ - img = convert_to_tensor(img, track_meta=get_track_meta()) + img_t: torch.Tensor = convert_to_tensor(img, track_meta=get_track_meta()) # type: ignore[assignment] dtype = self.dtype or img.dtype + img_len = len(img_t) if self.channel_wise: - if self.subtrahend is not None and len(self.subtrahend) != len(img): - raise ValueError(f"img has {len(img)} channels, but subtrahend has {len(self.subtrahend)} components.") - if self.divisor is not None and len(self.divisor) != len(img): - raise ValueError(f"img has {len(img)} channels, but divisor has {len(self.divisor)} components.") + if self.subtrahend is not None and len(self.subtrahend) != img_len: + raise ValueError(f"img has {img_len} channels, but subtrahend has {len(self.subtrahend)} components.") + if self.divisor is not None and len(self.divisor) != img_len: + raise ValueError(f"img has {img_len} channels, but divisor has {len(self.divisor)} components.") - if not img.dtype.is_floating_point: - img, *_ = convert_data_type(img, dtype=torch.float32) + if not img_t.dtype.is_floating_point: + img_t, *_ = convert_data_type(img_t, dtype=torch.float32) - for i, d in enumerate(img): - img[i] = self._normalize( # type: ignore + for i, d in enumerate(img_t): + img_t[i] = self._normalize( # type: ignore d, sub=self.subtrahend[i] if self.subtrahend is not None else None, div=self.divisor[i] if self.divisor is not None else None, ) else: - img = self._normalize(img, self.subtrahend, self.divisor) + img_t = self._normalize(img_t, self.subtrahend, self.divisor) # type: ignore[assignment] - out = convert_to_dst_type(img, img, dtype=dtype)[0] + out = convert_to_dst_type(img_t, img_t, dtype=dtype)[0] return out @@ -2764,7 +2765,7 @@ def __init__(self, dtype: DtypeLike = "float32") -> None: self.dtype = dtype def __call__(self, mask: NdarrayOrTensor): - instance_mask = convert_data_type(mask, np.ndarray)[0] + instance_mask: np.ndarray = convert_data_type(mask, np.ndarray)[0] # type: ignore[assignment] h_map = instance_mask.astype(self.dtype, copy=True) v_map = instance_mask.astype(self.dtype, copy=True) diff --git a/monai/transforms/spatial/array.py b/monai/transforms/spatial/array.py index 9113bea15b..f02616b299 100644 --- a/monai/transforms/spatial/array.py +++ b/monai/transforms/spatial/array.py @@ -1740,6 +1740,8 @@ def __call__( """ lazy_ = self.lazy if lazy is None else lazy + _device: torch.device | None + if not lazy_: if grid is None: # create grid from spatial_size if spatial_size is None: @@ -1749,10 +1751,10 @@ def __call__( grid_ = grid _dtype = self.dtype or grid_.dtype grid_: torch.Tensor = convert_to_tensor(grid_, dtype=_dtype, track_meta=get_track_meta()) # type: ignore - _device = grid_.device # type: ignore + _device = torch.device(grid_.device) # type: ignore spatial_dims = len(grid_.shape) - 1 else: - _device = self.device + _device = self.device # type: ignore[assignment] spatial_dims = len(spatial_size) # type: ignore _b = TransformBackends.TORCH affine: torch.Tensor @@ -3309,6 +3311,7 @@ def __call__(self, array: NdarrayOrTensor) -> MetaTensor: **self.pad_kwargs, ) patches = list(zip(*patch_iterator)) + patched_image: NdarrayOrTensor patched_image = np.stack(patches[0]) if isinstance(array, np.ndarray) else torch.stack(patches[0]) locations = np.stack(patches[1])[:, 1:, 0] # only keep the starting location @@ -3516,7 +3519,7 @@ def __call__(self, img: torch.Tensor, randomize: bool = True) -> torch.Tensor: if self._do_transform: input_shape = img.shape[1:] - target_shape = np.round(np.array(input_shape) * self.zoom_factor).astype(np.int_) + target_shape = tuple(np.round(np.array(input_shape) * self.zoom_factor).astype(np.int_).tolist()) resize_tfm_downsample = Resize( spatial_size=target_shape, size_mode="all", mode=self.downsample_mode, anti_aliasing=False diff --git a/monai/transforms/utility/array.py b/monai/transforms/utility/array.py index 8491e4739c..18a0f7f32f 100644 --- a/monai/transforms/utility/array.py +++ b/monai/transforms/utility/array.py @@ -941,7 +941,7 @@ def __call__( data = where(in1d(img, select_labels), True, False).reshape(img.shape) if merge_channels or self.merge_channels: - return data.any(0)[None] + return data.any(0)[None] # type: ignore return data diff --git a/monai/transforms/utils.py b/monai/transforms/utils.py index be5fdd512b..b50508962f 100644 --- a/monai/transforms/utils.py +++ b/monai/transforms/utils.py @@ -508,7 +508,7 @@ def map_classes_to_indices( img_flat: NdarrayOrTensor | None = None if image is not None: check_non_lazy_pending_ops(image, name="map_classes_to_indices") - img_flat = ravel((image > image_threshold).any(0)) + img_flat = ravel((image > image_threshold).any(0)) # type: ignore # assuming the first dimension is channel channels = len(label) diff --git a/monai/transforms/utils_pytorch_numpy_unification.py b/monai/transforms/utils_pytorch_numpy_unification.py index 8f22d00674..66756f4476 100644 --- a/monai/transforms/utils_pytorch_numpy_unification.py +++ b/monai/transforms/utils_pytorch_numpy_unification.py @@ -216,7 +216,7 @@ def floor_divide(a: NdarrayOrTensor, b) -> NdarrayOrTensor: if isinstance(a, torch.Tensor): return torch.floor_divide(a, b) else: - return np.floor_divide(a, b) + return np.asarray(np.floor_divide(a, b)) def unravel_index(idx, shape) -> NdarrayOrTensor: diff --git a/monai/utils/misc.py b/monai/utils/misc.py index 369333f122..4e05e9c85a 100644 --- a/monai/utils/misc.py +++ b/monai/utils/misc.py @@ -102,6 +102,10 @@ def _strtobool(val: str) -> bool: NP_MAX = np.iinfo(np.uint32).max MAX_SEED = NP_MAX + 1 # 2**32, the actual seed should be in [0, MAX_SEED - 1] for uint32 +# Environment variable must be set to enable determinism for algorithms (alternative value is ":16:8"). +# This needs to be here to ensure it's set before deterministic algorithms are used/initialised. +os.environ["CUBLAS_WORKSPACE_CONFIG"] = os.environ.get("CUBLAS_WORKSPACE_CONFIG", ":4096:8") + def zip_with(op, *vals, mapfunc=map): """ @@ -374,23 +378,16 @@ def set_determinism( for func in additional_settings: func(seed) - if torch.backends.flags_frozen(): - warnings.warn("PyTorch global flag support of backends is disabled, enable it to set global `cudnn` flags.") - torch.backends.__allow_nonbracketed_mutation_flag = True + with torch.backends.__allow_nonbracketed_mutation(): # FIXME: better method without accessing private member + if seed is not None: + torch.backends.cudnn.deterministic = True + torch.backends.cudnn.benchmark = False + else: # restore the original flags + torch.backends.cudnn.deterministic = _flag_deterministic + torch.backends.cudnn.benchmark = _flag_cudnn_benchmark - if seed is not None: - torch.backends.cudnn.deterministic = True - torch.backends.cudnn.benchmark = False - else: # restore the original flags - torch.backends.cudnn.deterministic = _flag_deterministic - torch.backends.cudnn.benchmark = _flag_cudnn_benchmark if use_deterministic_algorithms is not None: - if hasattr(torch, "use_deterministic_algorithms"): # `use_deterministic_algorithms` is new in torch 1.8.0 - torch.use_deterministic_algorithms(use_deterministic_algorithms) - elif hasattr(torch, "set_deterministic"): # `set_deterministic` is new in torch 1.7.0 - torch.set_deterministic(use_deterministic_algorithms) - else: - warnings.warn("use_deterministic_algorithms=True, but PyTorch version is too old to set the mode.") + torch.use_deterministic_algorithms(use_deterministic_algorithms) def list_to_dict(items): diff --git a/monai/visualize/img2tensorboard.py b/monai/visualize/img2tensorboard.py index fd328f2c7a..df922b1eca 100644 --- a/monai/visualize/img2tensorboard.py +++ b/monai/visualize/img2tensorboard.py @@ -200,13 +200,14 @@ def plot_2d_or_3d_image( if d.ndim >= 4: spatial = d.shape[-3:] d = d.reshape([-1] + list(spatial)) - if d.shape[0] == 3 and max_channels == 3 and has_tensorboardx and isinstance(writer, SummaryWriterX): # RGB + d_chans = d.shape[0] # type: ignore + if d_chans == 3 and max_channels == 3 and has_tensorboardx and isinstance(writer, SummaryWriterX): # RGB # move the expected frame dim to the end as `T` dim for video d = np.moveaxis(d, frame_dim, -1) writer.add_video(tag, d[None], step, fps=max_frames, dataformats="NCHWT") return # scale data to 0 - 255 for visualization - max_channels = min(max_channels, d.shape[0]) + max_channels = min(max_channels, d_chans) d = np.stack([rescale_array(i, 0, 255) for i in d[:max_channels]], axis=0) # will plot every channel as a separate GIF image add_animated_gif(writer, f"{tag}_HWD", d, max_out=max_channels, frame_dim=frame_dim, global_step=step) diff --git a/monai/visualize/utils.py b/monai/visualize/utils.py index 88c9a0d66a..e79fbba847 100644 --- a/monai/visualize/utils.py +++ b/monai/visualize/utils.py @@ -116,15 +116,15 @@ def matshow3d( if channel_dim is not None: # move the expected dim to construct frames with `B` dim vol = np.moveaxis(vol, frame_dim, -4) # type: ignore - vol = vol.reshape((-1, vol.shape[-3], vol.shape[-2], vol.shape[-1])) + vol = vol.reshape((-1, vol.shape[-3], vol.shape[-2], vol.shape[-1])) # type: ignore[assignment] else: vol = np.moveaxis(vol, frame_dim, -3) # type: ignore - vol = vol.reshape((-1, vol.shape[-2], vol.shape[-1])) + vol = vol.reshape((-1, vol.shape[-2], vol.shape[-1])) # type: ignore[assignment] vmin = np.nanmin(vol) if vmin is None else vmin vmax = np.nanmax(vol) if vmax is None else vmax # subsample every_n-th frame of the 3D volume - vol = vol[:: max(every_n, 1)] + vol = vol[:: max(every_n, 1)] # type: ignore[assignment] if not frames_per_row: frames_per_row = int(np.ceil(np.sqrt(len(vol)))) # create the grid of frames diff --git a/tests/data/test_persistentdataset_dist.py b/tests/data/test_persistentdataset_dist.py index ab36979f5e..c4ac8e3fdf 100644 --- a/tests/data/test_persistentdataset_dist.py +++ b/tests/data/test_persistentdataset_dist.py @@ -21,7 +21,7 @@ from monai.data import PersistentDataset, json_hashing from monai.transforms import Transform -from tests.test_utils import DistCall, DistTestCase +from tests.test_utils import DistCall, DistTestCase, skip_if_windows class _InplaceXform(Transform): @@ -33,6 +33,7 @@ def __call__(self, data): return data +@skip_if_windows class TestDistDataset(DistTestCase): def setUp(self): self.tempdir = tempfile.mkdtemp() @@ -57,6 +58,7 @@ def test_mp_dataset(self): self.assertEqual(items, [[[]], [[0]], [[0, 1]], [[0, 1, 2]], [[0, 1, 2, 3]]]) +@skip_if_windows class TestDistCreateDataset(DistTestCase): def setUp(self): self.tempdir = tempfile.mkdtemp() diff --git a/tests/integration/test_reg_loss_integration.py b/tests/integration/test_reg_loss_integration.py index 47d2a8df80..6a759b036d 100644 --- a/tests/integration/test_reg_loss_integration.py +++ b/tests/integration/test_reg_loss_integration.py @@ -19,6 +19,7 @@ from parameterized import parameterized from monai.losses import BendingEnergyLoss, GlobalMutualInformationLoss, LocalNormalizedCrossCorrelationLoss +from monai.utils import set_determinism from tests.test_utils import SkipIfBeforePyTorchVersion TEST_CASES = [ @@ -33,14 +34,11 @@ class TestRegLossIntegration(unittest.TestCase): def setUp(self): - torch.backends.cudnn.deterministic = True - torch.backends.cudnn.benchmark = False - torch.manual_seed(0) + set_determinism(0) self.device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu:0") def tearDown(self): - torch.backends.cudnn.deterministic = False - torch.backends.cudnn.benchmark = True + set_determinism(None) @parameterized.expand(TEST_CASES) @SkipIfBeforePyTorchVersion((1, 9)) diff --git a/tests/integration/test_seg_loss_integration.py b/tests/integration/test_seg_loss_integration.py index 6713e7bba9..507abefc33 100644 --- a/tests/integration/test_seg_loss_integration.py +++ b/tests/integration/test_seg_loss_integration.py @@ -21,6 +21,7 @@ from monai.losses import DiceLoss, FocalLoss, GeneralizedDiceLoss, TverskyLoss from monai.networks import one_hot +from monai.utils import set_determinism TEST_CASES = [ [DiceLoss, {"to_onehot_y": True, "squared_pred": True, "smooth_nr": 1e-4, "smooth_dr": 1e-4}, {}], @@ -49,14 +50,11 @@ class TestSegLossIntegration(unittest.TestCase): def setUp(self): - torch.backends.cudnn.deterministic = True - torch.backends.cudnn.benchmark = False - torch.manual_seed(0) + set_determinism(0) self.device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu:0") def tearDown(self): - torch.backends.cudnn.deterministic = False - torch.backends.cudnn.benchmark = True + set_determinism(None) @parameterized.expand(TEST_CASES) def test_convergence(self, loss_type, loss_args, forward_args): diff --git a/tests/utils/test_evenly_divisible_all_gather_dist.py b/tests/utils/test_evenly_divisible_all_gather_dist.py index cea8921544..f63d448bf1 100644 --- a/tests/utils/test_evenly_divisible_all_gather_dist.py +++ b/tests/utils/test_evenly_divisible_all_gather_dist.py @@ -17,9 +17,10 @@ import torch.distributed as dist from monai.utils import evenly_divisible_all_gather -from tests.test_utils import DistCall, DistTestCase, assert_allclose +from tests.test_utils import DistCall, DistTestCase, assert_allclose, skip_if_windows +@skip_if_windows class DistributedEvenlyDivisibleAllGather(DistTestCase): @DistCall(nnodes=1, nproc_per_node=2) def test_data(self): diff --git a/tests/utils/test_set_determinism.py b/tests/utils/test_set_determinism.py index 2507741eb4..76564fb0f2 100644 --- a/tests/utils/test_set_determinism.py +++ b/tests/utils/test_set_determinism.py @@ -42,7 +42,8 @@ def test_values(self): self.assertEqual(seed, get_seed()) a = np.random.randint(seed) b = torch.randint(seed, (1,)) - # tset when global flag support is disabled + + # test when global flag support is disabled torch.backends.disable_global_flags() set_determinism(seed=seed) c = np.random.randint(seed) @@ -60,12 +61,23 @@ def setUp(self): @SkipIfBeforePyTorchVersion((1, 8)) # beta feature @skip_if_no_cuda - def test_algo(self): + def test_algo_not_deterministic(self): + """ + Test `avg_pool3d_backward_cuda` correctly raises an exception since it lacks a deterministic implementation. + """ with self.assertRaises(RuntimeError): x = torch.randn(20, 16, 50, 44, 31, requires_grad=True, device="cuda:0") y = torch.nn.AvgPool3d((3, 2, 2), stride=(2, 1, 2))(x) y.sum().backward() + @skip_if_no_cuda + def test_algo_cublas_env(self): + """ + Test `torch.mm` does not raise an exception with the CUBLAS_WORKSPACE_CONFIG environment variable correctly set. + """ + x = torch.rand(5, 5, device="cuda:0") + _ = torch.mm(x, x) + def tearDown(self): set_determinism(None, use_deterministic_algorithms=False)