Yes, your use case is possible and the gradients are also valid using your architecture:
class Encoder(nn.Module):
    def __init__(self, **kwargs):
        super().__init__()
        self.model = nn.Sequential(
            nn.Linear(784, 128),
            nn.ReLU(),
            nn.Linear(128, 32),
            nn.ReLU(),
        )

    def forward(self, features):
        return self.model(features)
    
class Decoder(nn.Module):
    def __init__(self, **kwargs):
        super().__init__()
        self.model = nn.Sequential(
            nn.Linear(32, 128),
            nn.ReLU(),
            nn.Linear(128, 784),
            nn.ReLU(),
        )
        
    def forward(self, features):
        return self.model(features)

#  use gpu if available
device = 'cpu'

# create a model from `AE` autoencoder class
# load it to the specified device, either gpu or cpu
decoder = Decoder().to(device)
encoder = Encoder().to(device)

inputs = torch.randn(1, 28*28)
# compute reconstructions
encoded = encoder(inputs)
decoded = decoder(encoded)
decoded.mean().backward()        

for name, param in encoder.named_parameters():
    print(name, param.grad.abs().sum())

> 
model.0.weight tensor(29.0105)
model.0.bias tensor(0.0458)
model.2.weight tensor(1.8911)
model.2.bias tensor(0.0586)
    
for name, param in decoder.named_parameters():
    print(name, param.grad.abs().sum())
    
>
model.0.weight tensor(0.5203)
model.0.bias tensor(0.1350)
model.2.weight tensor(4.4251)
model.2.bias tensor(0.5115)