Skip to content

axial attention blocks - #611

Open
amogh-gulati wants to merge 16 commits into
mainfrom
axial_attention
Open

axial attention blocks#611
amogh-gulati wants to merge 16 commits into
mainfrom
axial_attention

Conversation

@amogh-gulati

@amogh-gulati amogh-gulati commented Mar 2, 2026

Copy link
Copy Markdown
Collaborator

#600

This branch tracks the development of axial and full attention in unet blocks for samudra v1

training run with axial attention at bottleneck : https://wandb.ai/ocean_emulators/default/runs/ps6jz798

training run vanilla samudra v1: https://wandb.ai/ocean_emulators/default/runs/c7of0p5t

Full attention at bottleneck with bigger attention block : https://wandb.ai/ocean_emulators/default/runs/xku4fh3o


fix made:
Changed unetbackbone to capture encoder skip tensors at the end of each encoder stage instead of coreblock. Earlier skip paths were not using the output from the attention layer instead they were just being forwarded after the convnext layer.

This branch enables optional U-Net attention in two forms: axial attention and full attention. Axial attention applies self-attention separately along the two spatial axes, first treating each longitude column as a sequence over height and then each latitude row as a sequence over width. Two axis-wise passes are applied sequentially with residual connections.

Full attention instead flattens the whole H×W feature map into one sequence of spatial tokens and attends over all token pairs directly.

In both cases, the channel dimension is treated as the embedding dimension for each spatial token, and multi-head attention splits those channels before the outputs are projected back and added residually to the feature map.

The branch also leaves an space for models at the bottleneck for future transformer-like block families, so the bottleneck can later host attention, transformer, or MaxViT-style blocks without another config redesign.


@amogh-gulati
amogh-gulati marked this pull request as draft March 2, 2026 16:43
@alxmrs

alxmrs commented Mar 9, 2026

Copy link
Copy Markdown
Member

Lmk when this is no longer a draft PR and I'll be happy to review it!

@amogh-gulati
amogh-gulati marked this pull request as ready for review March 9, 2026 18:56
@amogh-gulati

Copy link
Copy Markdown
Collaborator Author

Lmk when this is no longer a draft PR and I'll be happy to review it!

should be good now!

@amogh-gulati
amogh-gulati requested a review from alxmrs March 9, 2026 18:58

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0f0549ffd2

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +736 to +738
decoder_attention_configs,
reversed(self.ch_width),
strict=True,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Build decoder attention with decoder output channel widths

The decoder attention blocks are instantiated with reversed(self.ch_width), which starts at the bottleneck width, but in UNetBackbone each decoder attention block is applied after a decoder core block whose output channels are already reduced. With the default widths [200, 250, 300, 400], the first decoder attention is built for 400 channels but receives a 300-channel tensor, causing an immediate runtime error (e.g., GroupNorm channel mismatch) whenever any non-final decoder attention entry is enabled.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This may be related to the block creation order that I pointed out above.

@alxmrs alxmrs left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think there is a mistake that Codex and I caught related to the order of creating blocks on the decode side of the unet. I also provide some design suggestions for this feature. In general, the Axial and full transformers implementation are excellent. Here are some notes to help improve the PR for the next iteration.

@@ -0,0 +1,42 @@
# yaml-language-server: $schema=../schemas/SamudraConfig.json

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: We may have just renamed our Samudra version to v2.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I'm using the base v1 configs to run experiments with attention to keep the changes isolated, should I use v2 instead?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yes, I think we should base these off of v2.

Comment thread configs/samudra_om4_v1_attention/model.yaml
@@ -0,0 +1,3 @@
# Samudra OM4 V1 — Attention

Use [model.yaml](/scratch/ag11542/ocean/Ocean_Emulator/Ocean_Emulator/configs/samudra_om4_v1_attention/model.yaml) as the base example and [train.yaml](/scratch/ag11542/ocean/Ocean_Emulator/Ocean_Emulator/configs/samudra_om4_v1_attention/train.yaml) to launch training.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Would love to have a high level explanation for the experiments in the README. In fact, what you wrote in the PR comment should be fine to re-purpose for this.

References:
Axial Attention in Multidimensional Transformers (Ho et al., 2019)
https://arxiv.org/abs/1912.12180
"""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is a good docstring.

Comment on lines +306 to +310
num_heads: int = 8,
qkv_bias: bool = True,
attn_drop: float = 0.0,
proj_drop: float = 0.0,
axis: Literal["height", "width"] = "height",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's omit default arguments, if possible.

Comment thread src/ocean_emulators/config.py
Comment thread src/ocean_emulators/config.py Outdated
"""Configuration for a single attention block in the U-Net."""

attention_type: AttentionType = Field(
default="axial",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🐑 Let's make "full" attention the default. WDYT?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

agreed

Comment thread src/ocean_emulators/config.py Outdated
Comment on lines +308 to +315
transformer: TransformerBottleneckConfig | None = Field(
default=None,
description="Reserved transformer bottleneck settings for future implementation.",
)
maxvit: MaxViTBottleneckConfig | None = Field(
default=None,
description="Reserved MaxViT bottleneck settings for future implementation.",
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's omit these for now. Happy to add notes or TODOs here that these will come later.

Comment on lines +736 to +738
decoder_attention_configs,
reversed(self.ch_width),
strict=True,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This may be related to the block creation order that I pointed out above.

BottleneckBlockType = Literal["attention", "transformer", "maxvit"]


class AttentionBlockConfig(BaseConfig):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

In an ideal world, this would be rolled into BlockConfig. Maybe, to accomplish this, we would update the BlockConfig to be a union type, so we could pass context specific arguments depending on if the block was a CNN or Attention.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm imagining something similar to how the ModelConfig is done today. This is a bit tricky, and I (or I bet, Jesse) would be happy to meet to try to work through the pattern with you on this. I admit, the implementation path for this is fuzzy in my head.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

What if we model each stage as an ordered list of components plus an explicit skip-capture point, instead of fixing every stage to has the same internal ordering?
In the current CNN-style U-Net, the natural order is coreblock then scale change but for a ViT-style stage with patch embedding is tokenize first (scale change), then transform.

(maybe transform keeps the spatial size consistent and transition changes it)

encoder_stages:

  • skip_after_component: 1
    components:

    • kind: transform
      block_type: convnext
    • kind: transform
      attention_type: axial
    • kind: transition
      transition_type: avg_pool
  • skip_after_component: 0
    components:

    • kind: transition
      patch_size: 4
      embed_dim: 256
    • kind: transform
      block_type: transformer

but this makes the skip logic pretty messy

alternative, a shape-preserving StageBlock abstraction that can contain one or more stage-preserving operations, such as a core block, an attention block, or both, and then keep ViTStageBlock as a separate stage type for the cases where tokenization/downsampling is coupled with the stage computation to keep skips simplified, VIT is the only one where we change shape before the feature mixing instead of after. It also keeps the skip logic cleaner, since the backbone would still only need to reason about one declared stage output per level

@amogh-gulati amogh-gulati Apr 9, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Right now, the conv or ConvNeXt block is the main block for each U-Net level. The attention block is just an extra step added after that main block. For the experiments with each level as a conv-based stage with optional attention on top the current config seems to let us experiment with axial and full attention on different coarse resolutions and if we dont need attention it does not pollute the config.

I think Convnext + attention blocks will be a more natural fit for the coreblock moving forward (as they define a stage and the current attention block do not, they are just a optional bolt on) and for patchify + transformer based design a new model like fomo might be the most apt way to experiment

@alxmrs let me know what you think here, happy to discuss this more

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Mind if we discuss this over video chat after our weekly meeting? I'd like to understand what you're proposing a bit better (and ask some questions).

@alxmrs alxmrs left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Another review pass. Sorry I missed your responses from earlier this week.

@@ -0,0 +1,42 @@
# yaml-language-server: $schema=../schemas/SamudraConfig.json

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yes, I think we should base these off of v2.

Comment on lines +399 to +401
num_heads: int = 8,
attn_drop: float = 0.0,
proj_drop: float = 0.0,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is it possible to have these values be set by our config system? I'd prefer not to use default kwargs.

return x


class FullAttention(nn.Module):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

In the future, I might slightly update this module to support both self and cross attention; since this PR doesn't need it, this is good for now.


def __init__(
self,
channels: int,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Happy to punt on in/out channels; this is now made more complicated with "prog_channels" and "boundary_channels" in a WIP feature.

Comment on lines +139 to +142
if decoder_attention_blocks is not None:
attention_block = decoder_attention_blocks[i]
if attention_block is not None:
layers.append(attention_block)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

In my understanding, this will break the "U" shaped symmetry of the network. Mirroring means reversing the order. This is only coming up now because we only ever had one kind of block.

If there is a convention for not having a strict reflection in symmetry, then I'm ok with this; I do want to check first.

BottleneckBlockType = Literal["attention", "transformer", "maxvit"]


class AttentionBlockConfig(BaseConfig):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Mind if we discuss this over video chat after our weekly meeting? I'd like to understand what you're proposing a bit better (and ask some questions).

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

Labels

None yet

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

2 participants