Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split preactivation from FNOBlock.forward() #214

Merged
merged 9 commits into from
Sep 13, 2023

Conversation

m4e7
Copy link
Contributor

@m4e7 m4e7 commented Sep 8, 2023

Mainly, refactor FNOBlock.forward() for readability:

  • Create a separate method FNOBlock.forward_with_preactivation() where it is assumed preactivation=True for clarity of code flow. Likewise, assume preactivation=False in the new method FNOBlock.forward_with_postactivation(). The old method forward() now dispatches to one of these.
  • In multiple places, we were checking self.output_scaling_factors and calling resample like boilerplate. Refactor this check and call (with a no-op return if scaling_factors is None and output_shape is None).
  • Additionally, refactor out output_scaling_factors validation, which was being duplicated across 4 files.

Reviewer(s): @JeanKossaifi @zongyi-li

Create a separate method `FNOBlock.forward_with_preactivation()`
where it is assumed `preactivation=True` for clarity of code flow.
In multiple places, we were checking `self.output_scaling_factors` and calling
`resample` like boilerplate. Refactor this check and call (with a no-op return
if `scaling_factors=None`).

Additionally, refactor out `output_scaling_factors` validation, which was being
duplicated across 4 files.

def validate_output_scaling_factor_1d(
output_scaling_factor: Optional[Union[Number, List[Number]]],
order: int,
Copy link
Member

Choose a reason for hiding this comment

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

Why do you call them 1d and 2d? They are both N-D, where order = n_dim = N.
Given this I think we should merge them and just have n_layers=1 by default, what do you think?

Copy link
Contributor Author

@m4e7 m4e7 Sep 11, 2023

Choose a reason for hiding this comment

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

Why do you call them 1d and 2d?

That's just based on their return value. validate_*_2d returns a list of shapes, while validate_*_1d returns just a shape.

I think we should merge them and just have n_layers=1 by default

That sounds fine, as long as a singleton "list" of shapes doesn't confuse any of the models.

Copy link
Member

Choose a reason for hiding this comment

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

I see - how about we call them validate_output_scaling_factor_single_layer and validate_output_scaling_factor_multiple_layers?

A mouthful and a pain to write but self-explanatory and never used by end users most likely?

Copy link
Member

Choose a reason for hiding this comment

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

Ideally we should never have to do both, makes me think there is a deficiency in the code - we should either check at model level or layer level, probably not both

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I made an allowance for non-positive values of n_layers to return a single layer instead of a list of layers (for a whole module). This is only used in DomainPadding.


if init_std == "auto":
init_std = 1 / (in_channels * out_channels)
else:
init_std = 0.02
init_std = init_std
Copy link
Member

Choose a reason for hiding this comment

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

Thanks, I kept forgetting to update this!

Copy link
Member

@JeanKossaifi JeanKossaifi left a comment

Choose a reason for hiding this comment

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

Thanks, it's clearer! Just found the 1d/2d helpers a little confusing but I like the changes otherwise.

neuralop/layers/fno_block.py Outdated Show resolved Hide resolved
and (self.mlp is not None)
or (index < (self.n_layers - index))
):
if (self.mlp is not None) or (index < (self.n_layers - index)):
x = self.non_linearity(x)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The order of operations (i.e. and , or) in this expression confused me, but I assumed this should only be applied in the preactivation=False case.

neuralop/utils.py Outdated Show resolved Hide resolved
return x

def resample(self, x, index, output_shape):
"""Resamples input if scaling factors are available for this block."""
if self.output_scaling_factor is None:
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't you check both self.output_scaling_factor and output_shape vs x.shape?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point. I've updated FNOBlock.resample() to defer first to arg output_shape to be passed to resample. AFAICT, when output_shape is not None in resample.py:resample() the other args are ignored (I passed dummy args res_scale=1, axis=None, so it should be fine).

@JeanKossaifi
Copy link
Member

This is great, thanks for all the changes @m4e7! Merging.

@JeanKossaifi JeanKossaifi merged commit 8b41d9f into neuraloperator:main Sep 13, 2023
1 check passed
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.

None yet

2 participants