OK, I think I have it figured out.  A couple of things I was doing wrong:

optimizer.step updates the parameters, I do not need to update the parameters manually
the NLLLoss function does work fine on mini-batches.  The input to the function is the output from the model (for the mini batch) and the actual labels.  This issue was the actual labels array.  This must be a 1D tensor array of indexes of the classifications - not an array of boolean 1 and 0.  This info is in the documentation.

Anyways, here is my updated code:
   model = torch.nn.Sequential(
            torch.nn.Linear(num_features, 200),
            torch.nn.ReLU(),
            torch.nn.Linear(200, num_classes),
            torch.nn.LogSoftmax()
        )
        model.double()

        loss_function = torch.nn.NLLLoss()

        learning_rate = self.modelParameters.learning_rate
        optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)

        for epoch in range(self.modelParameters.num_epochs):

            epoch_cost = 0.
            num_minibatches = int(num_samples / self.modelParameters.minibatch_size)
            seed = seed + 1
            minibatches = self.randomMiniBatch(X_train, Y_train, self.modelParameters.minibatch_size, seed)

            for minibatch in minibatches:
                (minibatch_X, minibatch_Y) = minibatch

                x = Variable(torch.from_numpy(minibatch_X.transpose()), requires_grad=False)
                y_predictions = model(x)

                # the "target" is the actual Y labels rolled into the index values.
                # One column for each training example, the value is the index of the class
                y_actual_class_index  = Variable(torch.from_numpy(minibatch_Y.argmax(axis=0).astype(np.int64)), requires_grad=False)

                loss = loss_function(y_predictions, y_actual_class_index)

                optimizer.zero_grad()
                loss.backward()
                optimizer.step()

                epoch_cost += loss / num_minibatches