I think the difference in results between DDP and DP might have to do with the fact that DP computes the loss and grads on the entire batch, whereas DDP computes loss and grads on individual minibatches and then averages the grads. As a result, if there is some computation where f(x + y) != f(x) + f(y), DDP might provide different results.
A simple example simulating this:
from torch import nn
import torch

import torch
from torch.autograd import Function

class MyFunc(Function):
    @staticmethod
    def forward(ctx, inp):
        return inp

    @staticmethod
    def backward(ctx, inp):
        return inp * inp

loss = nn.CrossEntropyLoss()
input = torch.randn(4, 5, requires_grad=True)
target = torch.empty(4, dtype=torch.long).random_(5)

# Simulate DP
b1 = input[0:2]
b2 = input[2:4]

b1 = MyFunc.apply(b1)
b2 = MyFunc.apply(b2)
output = loss(torch.cat((b1, b2), 0), target)
output.backward()
print (input.grad)
grad1 = input.grad
input.grad = None

# Simulate DDP
b1 = input[0:2]
b2 = input[2:4]
t1 = target[0:2]
t2 = target[2:4]
b1 = MyFunc.apply(b1)
output = loss(b1, t1)
output.backward()
b2 = MyFunc.apply(b2)
output = loss(b2, t2)
output.backward()
print (input.grad/2)
grad2 = input.grad/2
print (grad1 == grad2)