axial attention blocks - #611
Conversation
1f0973d to
c287675
Compare
|
Lmk when this is no longer a draft PR and I'll be happy to review it! |
should be good now! |
There was a problem hiding this comment.
💡 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".
| decoder_attention_configs, | ||
| reversed(self.ch_width), | ||
| strict=True, |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
This may be related to the block creation order that I pointed out above.
alxmrs
left a comment
There was a problem hiding this comment.
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 | |||
There was a problem hiding this comment.
Nit: We may have just renamed our Samudra version to v2.
There was a problem hiding this comment.
I'm using the base v1 configs to run experiments with attention to keep the changes isolated, should I use v2 instead?
There was a problem hiding this comment.
Yes, I think we should base these off of v2.
| @@ -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. | |||
There was a problem hiding this comment.
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 | ||
| """ |
| num_heads: int = 8, | ||
| qkv_bias: bool = True, | ||
| attn_drop: float = 0.0, | ||
| proj_drop: float = 0.0, | ||
| axis: Literal["height", "width"] = "height", |
There was a problem hiding this comment.
Let's omit default arguments, if possible.
| """Configuration for a single attention block in the U-Net.""" | ||
|
|
||
| attention_type: AttentionType = Field( | ||
| default="axial", |
There was a problem hiding this comment.
🐑 Let's make "full" attention the default. WDYT?
| 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.", | ||
| ) |
There was a problem hiding this comment.
Let's omit these for now. Happy to add notes or TODOs here that these will come later.
| decoder_attention_configs, | ||
| reversed(self.ch_width), | ||
| strict=True, |
There was a problem hiding this comment.
This may be related to the block creation order that I pointed out above.
| BottleneckBlockType = Literal["attention", "transformer", "maxvit"] | ||
|
|
||
|
|
||
| class AttentionBlockConfig(BaseConfig): |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
- kind: transform
-
skip_after_component: 0
components:- kind: transition
patch_size: 4
embed_dim: 256 - kind: transform
block_type: transformer
- kind: transition
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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).
88ab902 to
8c23fc2
Compare
… of the coreblock
alxmrs
left a comment
There was a problem hiding this comment.
Another review pass. Sorry I missed your responses from earlier this week.
| @@ -0,0 +1,42 @@ | |||
| # yaml-language-server: $schema=../schemas/SamudraConfig.json | |||
There was a problem hiding this comment.
Yes, I think we should base these off of v2.
| num_heads: int = 8, | ||
| attn_drop: float = 0.0, | ||
| proj_drop: float = 0.0, |
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
Happy to punt on in/out channels; this is now made more complicated with "prog_channels" and "boundary_channels" in a WIP feature.
| if decoder_attention_blocks is not None: | ||
| attention_block = decoder_attention_blocks[i] | ||
| if attention_block is not None: | ||
| layers.append(attention_block) |
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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).
#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.