Skip to content

SwinUNETR: fused attention + channels-last + bf16 for AMD MI300X inference#1

Open
nilapate wants to merge 3 commits into
amd-integrationfrom
nilapate/swin_unetr_opt
Open

SwinUNETR: fused attention + channels-last + bf16 for AMD MI300X inference#1
nilapate wants to merge 3 commits into
amd-integrationfrom
nilapate/swin_unetr_opt

Conversation

@nilapate

@nilapate nilapate commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Describe the change

Adds AMD MI300X inference optimizations to SwinUNETR.

Description

Adds ROCm-specific optimizations to SwinUNETR inference: fused scaled_dot_product_attention in WindowAttention, channels_last layout and bf16 autocast in SlidingWindowInferer, and maybe_mark_dynamic to bound torch.compile recompiles to ~2 per run. All changes gate on torch.version.hip is not None — strict no-op on CUDA and CPU builds.

PR Checklist

Commit quality

  • Commit title clearly states what this commit changes.
  • Commit message clearly explains why this commit is needed.
  • Commit message includes how this PR/commit was tested.

Change type and validation

  • Non-breaking change (fix or new feature that does not break existing functionality).
  • Breaking change (fix or new feature that changes existing functionality).
  • New tests added to cover the changes.
  • Integration tests passed locally by running ./runtests.sh -f -u --net --coverage.
  • Quick tests passed locally by running ./runtests.sh --quick --unittests --disttests.
  • In-line docstrings updated.
  • Documentation updated and make html tested in the docs/ folder.

nilapate added 2 commits July 1, 2026 06:51
…bf16

inferer.py (SlidingWindowInferer.__call__):
- channels_last_3d conversion on 5-D inputs on ROCm (torch.version.hip is not None)
- bf16 autocast override (bundle evaluator defaults to fp16; MI300X wedge uses bf16)
  applied only when autocast is already active (fp32 golden runs are untouched)

utils.py (sliding_window_inference):
- maybe_mark_dynamic(win_data, 0) before the compiled predictor on ROCm
  bounds torch.compile recompiles to ~2 over a full multi-image run

swin_unetr.py (WindowAttention):
- _forward_sdpa: fused scaled_dot_product_attention path (AOTriton EFFICIENT
  backend on ROCm; (b,H,n,n) intermediate never materializes in HBM)
- Gated on PYTORCH_MIOPEN_SUGGEST_NHWC env var (refactored in next commit)

Signed-off-by: Patel, Nilaykumar K <nilapate@amd.com>
Refactor WindowAttention to eliminate ~30 lines of duplicated scaffolding
(QKV projection, RPB lookup, output proj) shared between the two attention
paths. Only the 5-line attention kernel differs.

swin_unetr.py:
- Unified forward() with shared QKV + RPB + output-proj scaffolding
- _attn_explicit / _attn_sdpa: narrow kernel-only helpers
- Gate moved from os.environ.get("PYTORCH_MIOPEN_SUGGEST_NHWC") to
  torch.version.hip is not None, checked once in __init__ as self._use_sdpa
  (trace-time constant — no graph break under torch.compile)
- Drop unnecessary .clone() on relative_position_index (registered buffer,
  never written after __init__)
- Add use_sdpa: bool | None = None constructor param for explicit override
- Remove now-unused import os

inferers/utils.py:
- maybe_mark_dynamic gate: PYTORCH_MIOPEN_SUGGEST_NHWC env var replaced with
  torch.version.hip is not None (authoritative ROCm discriminant, no env-var
  in the compiled path)

Signed-off-by: Patel, Nilaykumar K <nilapate@amd.com>
@nilapate nilapate self-assigned this Jul 1, 2026
  - Add test_window_attention_sdpa() to validate numerical equivalence
  - Follows same pattern as SABlock, CABlock, CrossAttentionBlock tests
  - Validates WindowAttention SDPA vs explicit attention with atol=1e-4
  - Covers 3D medical imaging windows (7x7x7)

  Signed-off-by: Patel, Nilaykumar K <nilapate@amd.com>
Comment thread monai/inferers/utils.py
from __future__ import annotations

import itertools
import os

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes don't suggest this is needed - can you double check?


bias = (
self.relative_position_bias_table[
self.relative_position_index[:n, :n].reshape(-1) # type: ignore[operator]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you drop the .clone() intentionally? Wouldn't it cause side effects elsewhere?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants