Thanks for the tip, I will try that! Although ideally I’d prefer to solve it without relying on multiprocessing workers.
What I have tried is defining an MultiEmbedding class as:
class MultiEmbedding(nn.Module):
    def __init__(self, modules):
        super().__init__()
        self.emb = modules
    
    def forward(self, X):
        x = torch.cat([m(X[:,i].flatten()) for i, m in enumerate(self.emb)], dim=1)
        return x

which is initialized with a nn.ModuleList. In the initialization of the main module, I then convert this submodule to TorchScript
self.emb = torch.jit.script(MultiEmbedding(modules))

and I then have no more loop in the forward() of the main module. Instead of passing multiply integer arrays to the different embedding layers, all integer-encoded categorical variables are combined in one array and passed directly to the TorchScript MultiEmbedding.
This yields a small performance increase, but the TF/Keras equivalent is still more than 2x faster.
The only other idea I had would be to (in the future) combine vmap (the integer indices) and NestedTensor (the various combined embedding matrices) in a new nn.Module …