I was able to get my code to work removing self.optimizer = optim.Adam() and self.criterion = nn.NLLLoss() in the constructor, as you suggested. I had to make them normal variables in the training method rather than class attributes. That’s a little bit of a bummer, but at least I have working code 
To answer your question:
The ‘layers’ tuple is one where each value in the tuple is the number of nodes for that corresponding layer:
layers = (n_nodes_layer1, n_nodes_layer2, ...)
So now, the below code works fine. I had to move the optimizer and the criterion into the training method as regular variables rather than as class attributes. It is less flexible this way, but works now:
class LSequential(nn.Sequential):
    def __init__(self, layers):
        super().__init__(self.init_modules(layers))

    def init_modules(self, layers):

        n_layers = len(layers)
        modules = OrderedDict()

        # Layer definitions for input and inner layers:
        for i in range(n_layers - 2):
            modules[f'fc{i}'] = nn.Linear(layers[i], layers[i + 1])
            modules[f'relu{i}'] = nn.ReLU()

        # Definition for output layer:
        modules['fc_out'] = nn.Linear(layers[-2], layers[-1])
        modules['smax_out'] = nn.LogSoftmax(dim=1)

        return modules