Yes, the first three filters would be used for the first input channel, then the second 3 filters for the second input channel, etc.
You can always manually verify it:
conv_1 = nn.Conv2d(3, 9, 3, 1, 1, groups=3, bias=False)
x = torch.randn(1, 3, 4, 4)
out_ref = conv_1(x)

x0, x1, x2 = x.split(1, dim=1)

out0 = F.conv2d(x0, conv_1.weight[0:1], stride=1, padding=1)
out1 = F.conv2d(x0, conv_1.weight[1:2], stride=1, padding=1)
out2 = F.conv2d(x0, conv_1.weight[2:3], stride=1, padding=1)

out3 = F.conv2d(x1, conv_1.weight[3:4], stride=1, padding=1)
out4 = F.conv2d(x1, conv_1.weight[4:5], stride=1, padding=1)
out5 = F.conv2d(x1, conv_1.weight[5:6], stride=1, padding=1)

out6 = F.conv2d(x2, conv_1.weight[6:7], stride=1, padding=1)
out7 = F.conv2d(x2, conv_1.weight[7:8], stride=1, padding=1)
out8 = F.conv2d(x2, conv_1.weight[8:9], stride=1, padding=1)

out = torch.cat((out0, out1, out2, out3, out4, out5, out6, out7, out8), dim=1)
print((out_ref-out).abs().max())
> tensor(1.1921e-07, grad_fn=<MaxBackward1>)




 Arturas_Druteika:

Below I declared 1 conv which is depthwise separable conv.


If I’m not mistaken a “depthwise-separable convolution” would mean a depthwise conv with in_channels=out_channels=groups followed by another convolution with a 1x1 kernel, so I would recommend to check the previously posted reference paper.