Well you can have the variable in the decorator but you want to obviously want to put the value into it in the forward function. Or in your case in the encode function.
This should work:
You have your decorator like I said + the shape_before_pooling variable you wanted
class Vanilla_3DVAE(nn.Module):
    def __init__(self):
        super(Vanilla_3DVAE, self).__init__()
        self.nn_1 = nn.Sequential(
            nn.Conv3d(8, 16, 4, 2, 1 ),        # -4 from dimensions
            nn.ReLU(),
            nn.AvgPool3d(2),            # /2
            nn.BatchNorm3d(16),
            nn.Conv3d(16, 32, 4, 2, 1 ),       # -4
            nn.ReLU())
        self.adaptive_pool = nn.Sequential(
            nn.AdaptiveAvgPool3d( 1 ),  # dimension -> 1x1x1
            nn.BatchNorm3d(32))
        self.nn_2 = nn.Sequential(....)
        self.mu_fc = (...)
        self.logvar_fc = (...)
        self.decoder_nn = (...)

        self.pre_pool_shape = None        #this is new

And then have you forward, encode and decode looks like this:
    def encode(self, x):
        encoded = self.nn_1(x)
        self.shape_before_pooling = encoded.shape        #this is new
        adap_pooled = self.adaptive_pool(encoded)        #this is new
        encoded_flat = self.flatten(adap_pooled)        #this is different
        encoded_flat = self.nn_2(encoded_flat)
        mu = self.mu_fc(encoded_flat)
        logvar = self.logvar_fc(encoded_flat)
        return mu, logvar

    def decode(self, z):
        z_4d = self.restore_dim(z)
        return self.decoder_nn(z_4d)

    def forward(self, x):
        mu,logvar = self.encode(x)
        z = self.reparametrize(mu, logvar)
        decoded = self.decode(z)
        return (decoded, z, mu, logvar)

I am fairly certain this should work.