This should be doable without destructure (which can be slow, as it allocates O(params) memory when restructuring) or FastChain.
However, it’s not clear to me that two calls to gradient will be slower than one in this case, so if you haven’t timed that already now is a good time (no need to do any optimization). If it does turn out to be slower, you can use something like the following to update both sets of parameters independently:
p = union(px, pk)
g = Flux.gradient(()->loss2(Gx, Gk, in_x, in_k, y), p)
Flux.Optimise.update!(optsx, px, g)
Flux.Optimise.update!(optsk, pk, g)
update! will only look at gradients for the parameters you pass it.