One simple but inefficient pattern I’ve used is to make an image of 1s.
Run the weight kernel over the image of 1s, with zero padding. The result is an weight-image where the value of each pixel is weight of the kernel that was valid at that location.
Then run the actual kernal over the actual image, with zero padding. to get the conv-image.
Then divide the conv-image by the weight image.
I’m pretty sure that’s equivalent.