Skip to content

Commit 719e9a0

Browse files
authored
Merge branch 'dev' into precommit_autofixing
2 parents 6c05372 + 7fe412b commit 719e9a0

8 files changed

Lines changed: 179 additions & 54 deletions

File tree

.github/workflows/codeql-analysis.yml

Lines changed: 66 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,67 +9,110 @@
99
# the `language` matrix defined below to confirm you have the correct set of
1010
# supported CodeQL languages.
1111
#
12-
name: "CodeQL"
12+
name: "CodeQL Advanced"
1313

1414
on:
1515
push:
1616
branches: [ dev, main ]
1717
pull_request:
18-
# The branches below must be a subset of the branches above
1918
branches: [ dev ]
2019
schedule:
21-
- cron: '18 1 * * 0'
20+
- cron: '0 2 * * 1' # 2AM Monday
21+
22+
env:
23+
PYTHON_VER: '3.11'
24+
PYTORCH_VER: '2.8.0'
25+
BUILD_MONAI: 1
26+
PIP_EXTRA_INDEX_URL: "https://download.pytorch.org/whl/cpu" # forces CPU PyTorch installation, should be faster
2227

2328
jobs:
2429
analyze:
25-
name: Analyze
30+
name: Analyze (${{ matrix.language }})
31+
# Runner size impacts CodeQL analysis time. To learn more, please see:
32+
# - https://gh.io/recommended-hardware-resources-for-running-codeql
33+
# - https://gh.io/supported-runners-and-hardware-resources
34+
# - https://gh.io/using-larger-runners (GitHub.com only)
35+
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
2636
runs-on: ubuntu-latest
2737
permissions:
38+
# required for all workflows
39+
security-events: write
40+
41+
# required to fetch internal or private CodeQL packs
42+
packages: read
43+
44+
# only required for workflows in private repositories
2845
actions: read
2946
contents: read
30-
security-events: write
3147

3248
strategy:
3349
fail-fast: false
3450
matrix:
35-
language: [ 'cpp', 'python' ]
36-
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
37-
# Learn more about CodeQL language support at https://git.io/codeql-language-support
38-
51+
include:
52+
- language: actions
53+
build-mode: none
54+
- language: c-cpp
55+
build-mode: none # TODO: get Cpp building working, autobuild doesn't work and manual fails for inexplicable reasons.
56+
- language: python
57+
build-mode: none
58+
# CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift'
59+
# Use `c-cpp` to analyze code written in C, C++ or both
60+
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
61+
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
62+
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
63+
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
64+
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
65+
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
3966
steps:
4067
- name: Checkout repository
4168
uses: actions/checkout@v7
4269

70+
# Add any setup steps before running the `github/codeql-action/init` action.
71+
# This includes steps like installing compilers or runtimes (`actions/setup-node`
72+
# or others). This is typically only required for manual builds.
73+
# - name: Setup runtime (example)
74+
# uses: actions/setup-example@v1
75+
4376
# Initializes the CodeQL tools for scanning.
4477
- name: Initialize CodeQL
4578
uses: github/codeql-action/init@v4
4679
with:
4780
languages: ${{ matrix.language }}
81+
build-mode: ${{ matrix.build-mode }}
4882
# If you wish to specify custom queries, you can do so here or in a config file.
4983
# By default, queries listed here will override any specified in a config file.
5084
# Prefix the list here with "+" to use these queries and those in the config file.
51-
# queries: ./path/to/local/query, your-org/your-repo/queries@main
5285

53-
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
54-
# If this step fails, then you should remove it and run the build manually (see below)
55-
# - name: Autobuild
56-
# uses: github/codeql-action/autobuild@v2
86+
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
87+
# queries: security-extended,security-and-quality
5788

89+
# If the analyze step fails for one of the languages you are analyzing with
90+
# "We were unable to automatically build your code", modify the matrix above
91+
# to set the build mode to "manual" for that language. Then modify this step
92+
# to build your code.
5893
# ℹ️ Command-line programs to run using the OS shell.
59-
# 📚 https://git.io/JvXDl
60-
61-
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
62-
# and modify them (or add more) to build your code if your project
63-
# uses a compiled language
94+
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
95+
- name: Set up Python ${{ env.PYTHON_VER }}
96+
if: matrix.language == 'c-cpp' && matrix.build-mode == 'manual'
97+
uses: actions/setup-python@v6
98+
with:
99+
python-version: ${{ env.PYTHON_VER }}
100+
cache: 'pip'
64101

65-
- name: Build
102+
- name: Run manual build steps
103+
if: matrix.language == 'c-cpp' && matrix.build-mode == 'manual'
104+
shell: bash
66105
run: |
67106
rm -rf /opt/hostedtoolcache/{node,go,Ruby,Java*}
68107
ls -al /opt/hostedtoolcache
69-
rm -rf /usr/share/dotnet/
108+
sudo rm -rf /usr/share/dotnet/
70109
python -m pip install -U pip wheel wheel-stub
71-
python -m pip install .[all,testing]
72-
BUILD_MONAI=1 ./runtests.sh --build
110+
python -m pip install torch==${PYTORCH_VER} torchvision
111+
python -m pip install --user --upgrade pip wheel
112+
python monai/config/print_dependencies.py build-system | xargs pip install --no-build-isolation
113+
python -m pip install --no-build-isolation .
73114
74115
- name: Perform CodeQL Analysis
75116
uses: github/codeql-action/analyze@v4
117+
with:
118+
category: "/language:${{matrix.language}}"

monai/networks/nets/dints.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,20 +36,23 @@
3636
__all__ = ["DiNTS", "TopologyConstruction", "TopologyInstance", "TopologySearch"]
3737

3838

39-
@torch.jit.interface
40-
class CellInterface(torch.nn.Module):
41-
"""interface for torchscriptable Cell"""
39+
# TODO: added temporarily for PyTorch 2.14 warnings, remove when factoring out deprecated Torchscript components
40+
with warnings.catch_warnings():
41+
warnings.simplefilter("ignore")
4242

43-
def forward(self, x: torch.Tensor, weight: torch.Tensor | None) -> torch.Tensor: # type: ignore
44-
pass
43+
@torch.jit.interface
44+
class CellInterface(torch.nn.Module):
45+
"""interface for torchscriptable Cell"""
4546

47+
def forward(self, x: torch.Tensor, weight: torch.Tensor | None) -> torch.Tensor: # type: ignore
48+
pass
4649

47-
@torch.jit.interface
48-
class StemInterface(torch.nn.Module):
49-
"""interface for torchscriptable Stem"""
50+
@torch.jit.interface
51+
class StemInterface(torch.nn.Module):
52+
"""interface for torchscriptable Stem"""
5053

51-
def forward(self, x: torch.Tensor) -> torch.Tensor: # type: ignore
52-
pass
54+
def forward(self, x: torch.Tensor) -> torch.Tensor: # type: ignore
55+
pass
5356

5457

5558
class StemTS(StemInterface):

monai/networks/nets/swin_unetr.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ def __init__(
9898
hyena_omega_0: float = 10.0,
9999
hyena_l_cache: int = 32,
100100
hyena_short_conv_fft_chunks: int = 0,
101+
use_flash_attention: bool = False,
101102
) -> None:
102103
"""
103104
Args:
@@ -149,6 +150,7 @@ def __init__(
149150
hyena_omega_0: SIREN frequency. Default 10.0 (stable).
150151
hyena_l_cache: SIREN coordinate-grid cache size per spatial dim.
151152
hyena_short_conv_fft_chunks: channel chunk size for the FFT short conv (0 = no chunking).
153+
use_flash_attention: use flash attention (scaled dot product attention) at inference.
152154
153155
Examples::
154156
@@ -224,6 +226,7 @@ def __init__(
224226
hyena_omega_0=hyena_omega_0,
225227
hyena_l_cache=hyena_l_cache,
226228
hyena_short_conv_fft_chunks=hyena_short_conv_fft_chunks,
229+
use_flash_attention=use_flash_attention,
227230
)
228231

229232
self.encoder1 = UnetrBasicBlock(
@@ -513,6 +516,7 @@ def __init__(
513516
qkv_bias: bool = False,
514517
attn_drop: float = 0.0,
515518
proj_drop: float = 0.0,
519+
use_flash_attention: bool = False,
516520
) -> None:
517521
"""
518522
Args:
@@ -522,12 +526,17 @@ def __init__(
522526
qkv_bias: add a learnable bias to query, key, value.
523527
attn_drop: attention dropout rate.
524528
proj_drop: dropout rate of output.
529+
use_flash_attention: if True, use ``torch.nn.functional.scaled_dot_product_attention`` for the
530+
windowed attention. Equivalent to the default path but faster at inference; only used when
531+
autograd is disabled (e.g. under ``torch.no_grad()`` or ``torch.inference_mode()``, not
532+
``eval()`` alone) and the module is not scripted.
525533
"""
526534

527535
super().__init__()
528536
self.dim = dim
529537
self.window_size = window_size
530538
self.num_heads = num_heads
539+
self.use_flash_attention = use_flash_attention
531540
head_dim = dim // num_heads
532541
self.scale = head_dim**-0.5
533542
mesh_args = torch.meshgrid.__kwdefaults__
@@ -584,12 +593,26 @@ def forward(self, x, mask):
584593
b, n, c = x.shape
585594
qkv = self.qkv(x).reshape(b, n, 3, self.num_heads, c // self.num_heads).permute(2, 0, 3, 1, 4)
586595
q, k, v = qkv[0], qkv[1], qkv[2]
587-
q = q * self.scale
588-
attn = q @ k.transpose(-2, -1)
589596
relative_position_bias = self.relative_position_bias_table[
590597
self.relative_position_index.clone()[:n, :n].reshape(-1) # type: ignore[operator]
591598
].reshape(n, n, -1)
592599
relative_position_bias = relative_position_bias.permute(2, 0, 1).contiguous()
600+
if self.use_flash_attention and not torch.jit.is_scripting() and not torch.is_grad_enabled():
601+
# additive bias combines the relative position bias and, for shifted windows, the attention mask
602+
if mask is not None:
603+
nw = mask.shape[0]
604+
bias = relative_position_bias.view(1, 1, self.num_heads, n, n) + mask.reshape(1, nw, 1, n, n)
605+
bias = bias.expand(b // nw, nw, self.num_heads, n, n).reshape(b, self.num_heads, n, n)
606+
else:
607+
bias = relative_position_bias.unsqueeze(0)
608+
x = torch.nn.functional.scaled_dot_product_attention(
609+
q, k, v, attn_mask=bias.to(q.dtype), dropout_p=0.0, scale=self.scale
610+
)
611+
x = x.transpose(1, 2).reshape(b, n, c)
612+
return self.proj_drop(self.proj(x))
613+
614+
q = q * self.scale
615+
attn = q @ k.transpose(-2, -1)
593616
attn = attn + relative_position_bias.unsqueeze(0)
594617
if mask is not None:
595618
nw = mask.shape[0]
@@ -628,6 +651,7 @@ def __init__(
628651
act_layer: str = "GELU",
629652
norm_layer: type[LayerNorm] = nn.LayerNorm,
630653
use_checkpoint: bool = False,
654+
use_flash_attention: bool = False,
631655
) -> None:
632656
"""
633657
Args:
@@ -643,6 +667,7 @@ def __init__(
643667
act_layer: activation layer.
644668
norm_layer: normalization layer.
645669
use_checkpoint: use gradient checkpointing for reduced memory usage.
670+
use_flash_attention: use flash attention (scaled dot product attention) at inference.
646671
"""
647672

648673
super().__init__()
@@ -660,6 +685,7 @@ def __init__(
660685
qkv_bias=qkv_bias,
661686
attn_drop=attn_drop,
662687
proj_drop=drop,
688+
use_flash_attention=use_flash_attention,
663689
)
664690

665691
self.drop_path = DropPath(drop_path) if drop_path > 0.0 else nn.Identity()
@@ -924,6 +950,7 @@ def __init__(
924950
hyena_omega_0: float = 10.0,
925951
hyena_l_cache: int = 32,
926952
hyena_short_conv_fft_chunks: int = 0,
953+
use_flash_attention: bool = False,
927954
) -> None:
928955
"""
929956
Args:
@@ -946,6 +973,7 @@ def __init__(
946973
hyena_use_chunked_fft, hyena_use_fft_short_conv, hyena_omega_0, hyena_l_cache,
947974
hyena_short_conv_fft_chunks: forwarded to :class:`HyenaTransformerBlock`. See its
948975
docstring for semantics.
976+
use_flash_attention: use flash attention (scaled dot product attention) at inference.
949977
"""
950978

951979
super().__init__()
@@ -996,6 +1024,7 @@ def __init__(
9961024
drop_path=drop_path[i] if isinstance(drop_path, list) else drop_path,
9971025
norm_layer=norm_layer,
9981026
use_checkpoint=use_checkpoint,
1027+
use_flash_attention=use_flash_attention,
9991028
)
10001029
for i in range(depth)
10011030
]
@@ -1079,6 +1108,7 @@ def __init__(
10791108
hyena_omega_0: float = 10.0,
10801109
hyena_l_cache: int = 32,
10811110
hyena_short_conv_fft_chunks: int = 0,
1111+
use_flash_attention: bool = False,
10821112
) -> None:
10831113
"""
10841114
Args:
@@ -1112,6 +1142,7 @@ def __init__(
11121142
hyena_use_chunked_fft, hyena_use_fft_short_conv, hyena_omega_0, hyena_l_cache,
11131143
hyena_short_conv_fft_chunks: HyenaND configuration. See
11141144
:class:`monai.networks.blocks.HyenaTransformerBlock` for semantics.
1145+
use_flash_attention: use flash attention (scaled dot product attention) at inference.
11151146
"""
11161147

11171148
super().__init__()
@@ -1193,6 +1224,7 @@ def __init__(
11931224
hyena_omega_0=hyena_omega_0,
11941225
hyena_l_cache=hyena_l_cache,
11951226
hyena_short_conv_fft_chunks=hyena_short_conv_fft_chunks,
1227+
use_flash_attention=use_flash_attention,
11961228
)
11971229
if i_layer == 0:
11981230
self.layers1.append(layer)

monai/transforms/croppad/array.py

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,24 @@ def compute_pad_width(self, spatial_shape: Sequence[int]) -> tuple[tuple[int, in
342342
return spatial_pad.compute_pad_width(spatial_shape)
343343

344344

345+
def _to_int_list(data: Sequence[int] | int | NdarrayOrTensor) -> list[int]:
346+
"""Coerce an ROI spec (scalar, sequence, tensor or ndarray) to a list of Python ints."""
347+
if isinstance(data, (str, bytes)):
348+
raise TypeError("ROI specs must be integers or sequences of integers, not strings.")
349+
return [int(i) for i in ensure_tuple(data)]
350+
351+
352+
def _broadcast_int_pair(
353+
a: Sequence[int] | int | NdarrayOrTensor, b: Sequence[int] | int | NdarrayOrTensor
354+
) -> tuple[list[int], list[int]]:
355+
"""Coerce a pair of ROI specs to two equal-length int lists, broadcasting a scalar to match."""
356+
list_a, list_b = _to_int_list(a), _to_int_list(b)
357+
n = max(len(list_a), len(list_b))
358+
if len(list_a) not in (1, n) or len(list_b) not in (1, n):
359+
raise ValueError(f"ROI specs must have matching lengths or be scalar, got {len(list_a)} and {len(list_b)}.")
360+
return (list_a * n if len(list_a) == 1 else list_a), (list_b * n if len(list_b) == 1 else list_b)
361+
362+
345363
class Crop(InvertibleTransform, LazyTransform):
346364
"""
347365
Perform crop operations on the input image.
@@ -379,31 +397,22 @@ def compute_slices(
379397
roi_slices: list of slices for each of the spatial dimensions.
380398
381399
"""
382-
roi_start_t: torch.Tensor
383-
384400
if roi_slices:
385401
if not all(s.step is None or s.step == 1 for s in roi_slices):
386402
raise ValueError(f"only slice steps of 1/None are currently supported, got {roi_slices}.")
387403
return ensure_tuple(roi_slices)
388404
else:
389405
if roi_center is not None and roi_size is not None:
390-
roi_center_t = convert_to_tensor(data=roi_center, dtype=torch.int16, wrap_sequence=True, device="cpu")
391-
roi_size_t = convert_to_tensor(data=roi_size, dtype=torch.int16, wrap_sequence=True, device="cpu")
392-
_zeros = torch.zeros_like(roi_center_t)
393-
half = torch.divide(roi_size_t, 2, rounding_mode="floor")
394-
roi_start_t = torch.maximum(roi_center_t - half, _zeros)
395-
roi_end_t = torch.maximum(roi_start_t + roi_size_t, roi_start_t)
406+
centers, sizes = _broadcast_int_pair(roi_center, roi_size)
407+
starts = [max(c - s // 2, 0) for c, s in zip(centers, sizes)]
408+
ends = [st + s for st, s in zip(starts, sizes)]
396409
else:
397410
if roi_start is None or roi_end is None:
398411
raise ValueError("please specify either roi_center, roi_size or roi_start, roi_end.")
399-
roi_start_t = convert_to_tensor(data=roi_start, dtype=torch.int16, wrap_sequence=True)
400-
roi_start_t = torch.maximum(roi_start_t, torch.zeros_like(roi_start_t))
401-
roi_end_t = convert_to_tensor(data=roi_end, dtype=torch.int16, wrap_sequence=True)
402-
roi_end_t = torch.maximum(roi_end_t, roi_start_t)
403-
# convert to slices (accounting for 1d)
404-
if roi_start_t.numel() == 1:
405-
return ensure_tuple([slice(int(roi_start_t.item()), int(roi_end_t.item()))])
406-
return ensure_tuple([slice(int(s), int(e)) for s, e in zip(roi_start_t.tolist(), roi_end_t.tolist())])
412+
starts, ends = _broadcast_int_pair(roi_start, roi_end)
413+
starts = [max(s, 0) for s in starts]
414+
# clamp each end to its own start so no slice has negative width
415+
return ensure_tuple(slice(s, max(e, s)) for s, e in zip(starts, ends))
407416

408417
def __call__( # type: ignore[override]
409418
self, img: torch.Tensor, slices: tuple[slice, ...], lazy: bool | None = None

0 commit comments

Comments
 (0)