These gradients should be used to update the loss before doing the final .backward():
    optimizer_g = optim.SGD(list(encoder.parameters()), lr=0.1, momentum=0.9, weight_decay=5e-4)
    optimizer_f = optim.SGD(list(classifier.parameters()), lr=0.1, momentum=0.9, weight_decay=5e-4)
    ...
    loss = criterion(outs, labels)
    # Do you have one loss or one for each?
    # If so, give only the corresponding loss for each model and remove the allow_unused
    grads_f = autograd.grad(loss, classifier.parameters(), create_graph=True, allow_unused=True)
    grads_g = autograd.grad(loss, encoder.parameters(), create_graph=True, allow_unused=True)

    # I guess the product in the formula is a dot product?
    grad_prod = 0.
    for gf, gg in zip(grads_f, grads_g):
        grad_prod += (gf * gg).sum()

    final_loss = loss - alpha * grad_prod

    optimizer_g.zero_grad()
    optimizer_f.zero_grad()
    loss.backward()
    optimizer_g.step()
    optimizer_f.step()