For loops add the Python overhead in each iteration and call into the dispatcher for a small workload.
A lot of operations are using vectorization inside, which won’t be fully used.
Note that this is not PyTorch-specific and can also be seen in e.g. numpy:
def fun1(x):
    out = []
    for x_ in x:
        out.append(np.sqrt(x_))
    out = np.stack(out)
    return out

def fun2(x):
    out = np.sqrt(x)
    return out

x = np.random.rand(1000, 100)
%timeit out1 = fun1(x)
%timeit out2 = fun2(x)