A good reason for sharing code between the actor and the critic is that both need to understand the environment. The model basically does this…
shared_features = understanding_environment(input)
action = actor(shared_features)
value = critic(shared_features)

shared_features is used in two different submodules and backpropagation simply adds the gradients that come from each submodule.
The puzzling code sums the losses.
loss = torch.cat(policy_losses).sum() + torch.cat(value_losses).sum()
loss.backward()

PyTorch knows how to distribute the added loss correctly to each submodule when calculating the gradients.
If the logic of backpropagation is fuzzy for you, can I suggest watching a lecture 75 by Andrej Karpathy that goes over the details.