Решение линейных уравнений на графическом процессоре с помощью NumPy и PyTorch - PullRequest
2 голосов
/ 30 мая 2020

Я пытаюсь решить множество линейных уравнений как можно быстрее. Чтобы найти самый быстрый способ, я протестировал NumPy и PyTorch , каждый на ЦП и на моем графическом процессоре GeForce 1080 (используя Numba для NumPy) . Результаты меня очень смутили.

Это код, который я использовал с Python 3.8:

import timeit

import torch
import numpy
from numba import njit


def solve_numpy_cpu(dim: int = 5):
    a = numpy.random.rand(dim, dim)
    b = numpy.random.rand(dim)

    for _ in range(1000):
        numpy.linalg.solve(a, b)


def solve_numpy_njit_a(dim: int = 5):
    njit(solve_numpy_cpu, dim=dim)


@njit
def solve_numpy_njit_b(dim: int = 5):
    a = numpy.random.rand(dim, dim)
    b = numpy.random.rand(dim)

    for _ in range(1000):
        numpy.linalg.solve(a, b)


def solve_torch_cpu(dim: int = 5):
    a = torch.rand(dim, dim)
    b = torch.rand(dim, 1)

    for _ in range(1000):
        torch.solve(b, a)


def solve_torch_gpu(dim: int = 5):
    torch.set_default_tensor_type("torch.cuda.FloatTensor")
    solve_torch_cpu(dim=dim)


def main():
    for f in (solve_numpy_cpu, solve_torch_cpu, solve_torch_gpu, solve_numpy_njit_a, solve_numpy_njit_b):
        time = timeit.timeit(f, number=1)
        print(f"{f.__name__:<20s}: {time:f}")


if __name__ == "__main__":
    main()

И вот результаты:

solve_numpy_cpu     : 0.007275
solve_torch_cpu     : 0.012244
solve_torch_gpu     : 5.239126
solve_numpy_njit_a  : 0.000158
solve_numpy_njit_b  : 1.273660

Самый медленный - это PyTorch с ускорением CUDA. Я подтвердил, что PyTorch использует мой графический процессор с

import torch
torch.cuda.is_available()
torch.cuda.get_device_name(0)

, возвращающим

True
'GeForce GTX 1080'

Я могу отстать от этого, на ЦП PyTorch медленнее, чем NumPy. Я не могу понять, почему PyTorch на GPU работает намного медленнее. Не так важно, но на самом деле еще больше сбивает с толку то, что декоратор njit в Numba снижает производительность на порядок, до тех пор, пока вы больше не будете использовать синтаксис @ decorator.

Это это моя установка? Иногда я получаю странное сообщение о том, что файл страницы / подкачки windows недостаточно велик. Если я выбрал совершенно непонятный путь к решению линейных уравнений на GPU, я был бы счастлив, если бы меня направили в другом направлении.


Edit

Итак, я сосредоточился на Numba и немного изменил мои тесты. Как было предложено @ max9111, я переписал функции для приема ввода и вывода вывода, потому что, в конце концов, это то, для чего кто-то захочет их использовать. Теперь я также выполняю первый прогон компиляции для ускоренной функции Numba, чтобы последующее время было более справедливым. Наконец, я проверил производительность по размеру матрицы и построил график результатов.

TL / DR: до размеров матрицы 500x500 ускорение Numba не имеет значения для numpy.linalg.solve .

Вот код:

import time
from typing import Tuple

import numpy
from matplotlib import pyplot
from numba import jit


@jit(nopython=True)
def solve_numpy_njit(a: numpy.ndarray, b: numpy.ndarray) -> numpy.ndarray:
    parameters = numpy.linalg.solve(a, b)
    return parameters


def solve_numpy(a: numpy.ndarray, b: numpy.ndarray) -> numpy.ndarray:
    parameters = numpy.linalg.solve(a, b)
    return parameters


def get_data(dim: int) -> Tuple[numpy.ndarray, numpy.ndarray]:
    a = numpy.random.random((dim, dim))
    b = numpy.random.random(dim)
    return a, b


def main():
    a, b = get_data(10)
    # compile numba function
    p = solve_numpy_njit(a, b)

    matrix_size = [(x + 1) * 10 for x in range(50)]
    non_accelerated = []
    accelerated = []
    results = non_accelerated, accelerated

    for j, each_matrix_size in enumerate(matrix_size):
        for m, f in enumerate((solve_numpy, solve_numpy_njit)):
            average_time = -1.
            for k in range(5):
                time_start = time.time()
                for i in range(100):
                    a, b = get_data(each_matrix_size)
                    p = f(a, b)
                d_t = time.time() - time_start
                print(f"{each_matrix_size:d} {f.__name__:<30s}: {d_t:f}")
                average_time = (average_time * k + d_t) / (k + 1)
            results[m].append(average_time)

    pyplot.plot(matrix_size, non_accelerated, label="not numba")
    pyplot.plot(matrix_size, accelerated, label="numba")
    pyplot.legend()
    pyplot.show()


if __name__ == "__main__":
    main()

И вот результаты (время выполнения в зависимости от длины края матрицы):

Performance comparison of regular and Numba accelerated solving of linear equations


Edit 2

Видя, что Numba не имеет большого значения в моем случае, я вернулся к тестированию PyTorch. И действительно, он выглядит примерно в 4 раза быстрее, чем Numpy, даже без использования устройства CUDA.

Вот код, который я использовал:

import time
from typing import Tuple

import numpy
import torch
from matplotlib import pyplot


def solve_numpy(a: numpy.ndarray, b: numpy.ndarray) -> numpy.ndarray:
    parameters = numpy.linalg.solve(a, b)
    return parameters


def get_data(dim: int) -> Tuple[numpy.ndarray, numpy.ndarray]:
    a = numpy.random.random((dim, dim))
    b = numpy.random.random(dim)
    return a, b


def get_data_torch(dim: int) -> Tuple[torch.tensor, torch.tensor]:
    a = torch.rand(dim, dim)
    b = torch.rand(dim, 1)
    return a, b


def solve_torch(a: torch.tensor, b: torch.tensor) -> torch.tensor:
    parameters, _ = torch.solve(b, a)
    return parameters


def experiment_numpy(matrix_size: int, repetitions: int = 100):
    for i in range(repetitions):
        a, b = get_data(matrix_size)
        p = solve_numpy(a, b)


def experiment_pytorch(matrix_size: int, repetitions: int = 100):
    for i in range(repetitions):
        a, b = get_data_torch(matrix_size)
        p = solve_torch(a, b)


def main():
    matrix_size = [x for x in range(5, 505, 5)]
    experiments = experiment_numpy, experiment_pytorch
    results = tuple([] for _ in experiments)

    for i, each_experiment in enumerate(experiments):
        for j, each_matrix_size in enumerate(matrix_size):
            time_start = time.time()
            each_experiment(each_matrix_size, repetitions=100)
            d_t = time.time() - time_start
            print(f"{each_matrix_size:d} {each_experiment.__name__:<30s}: {d_t:f}")
            results[i].append(d_t)

    for each_experiment, each_result in zip(experiments, results):
        pyplot.plot(matrix_size, each_result, label=each_experiment.__name__)

    pyplot.legend()
    pyplot.show()


if __name__ == "__main__":
    main()

И вот результат (время выполнения против длина края матрицы): Performance comparison of NumPy and PyTorh solving of linear equations

Так что пока я буду придерживаться torch.solve. Однако остается исходный вопрос:

Как я могу использовать свой графический процессор, чтобы решать линейные уравнения еще быстрее?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...