#pytorch #stride
Вопрос:
Я знаю, что обычно размер пакета равен нулю по оси, и я полагаю, что на это есть причина: базовая память для каждого элемента в пакете непрерывна.
Моя модель вызывает функцию, которая становится проще, если у меня есть другое измерение на первой оси, так что я могу использовать x[k]
вместо x[:, k]
.
Результаты арифметических операций, по-видимому, сохраняют ту же компоновку памяти
x = torch.ones(2,3,4).transpose(0,1)
y = torch.ones_like(x)
u = (x 1)
v = (x y)
print(x.stride(), u.stride(), v.stride())
Когда я создаю дополнительные переменные, я создаю их с torch.zeros
помощью, а затем транспонирую, чтобы наибольший шаг также шел к оси 1.
напр.
a,b,c = torch.zeros(
(3, x.shape[1], ADDITIONAL_DIM, x.shape[0]) x.shape[2:]
).transpose(1,2)
Создаст три тензора с одинаковым размером пакета x.shape[1]
.
С точки зрения локализации памяти имело бы какое-то значение, если бы
a,b,c = torch.zeros(
(x.shape[1], 3, ADDITIONAL_DIM, x.shape[0]) x.shape[2:]
).permute(1,2,0, ...)
вместо.
Должен ли я вообще беспокоиться об этом?
Комментарии:
1. Итак, ваш вопрос заключается в том, есть ли какая-либо разница между выполнением перестановки, а затем работой с
x[k]
ней ; и работойx[:, k]
без необходимости перестановки, верно?2. Точно, а также для внутренне построенных массивов
Ответ №1:
TLDR; Фрагменты, по-видимому, содержат меньше информации… но на самом деле разделяйте идентичный буфер хранения с исходным тензором. Поскольку перестановка не влияет на базовую компоновку памяти, обе операции по существу эквивалентны.
Эти два по сути одинаковы, базовый буфер хранения данных остается неизменным, меняются только метаданные , т. е. то, как вы взаимодействуете с этим буфером (шаги и форма).
Давайте рассмотрим простой пример:
>>> x = torch.ones(2,3,4).transpose(0,1)
>>> x_ptr = x.data_ptr()
>>> x.shape, x.stride(), x_ptr
(3, 2, 4), (4, 12, 1), 94674451667072
Мы сохранили указатель данных для нашего «базового» тензора в x_ptr
:
- Нарезка на второй оси:
>>> y = x[:, 0] >>> y.shape, y.stride(), x_ptr == y.data_ptr() (3, 4), (4, 1), True
Как вы можете видеть,
x
иx[:, k]
разделяли одно и то же хранилище. - Перестановка первых двух осей, а затем нарезка на первой:
>>> z = x.permute(1, 0, 2)[0] >>> z.shape, z.stride(), x_ptr == z.data_ptr() (3, 4), (4, 1), True
И здесь вы снова замечаете, что
x.data_ptr
это то жеz.data_ptr
самое, что и .
На самом деле, вы даже можете перейти от y
представления к x
представлению с помощью torch.as_strided
:
>>> torch.as_strided(y, size=x.shape, stride=x.stride())
tensor([[[1., 1., 1., 1.],
[1., 1., 1., 1.]],
[[1., 1., 1., 1.],
[1., 1., 1., 1.]],
[[1., 1., 1., 1.],
[1., 1., 1., 1.]]])
То же самое с z
:
>>> torch.as_strided(z, size=x.shape, stride=x.stride())
Оба вернут копию x
because torch.as_strided
, выделив память для вновь созданного тензора. Эти две строки были просто для иллюстрации того , как мы все еще можем «вернуться» к x
фрагменту x
, к которому мы можем восстановить видимое содержимое, изменив метаданные тензора.
Комментарии:
1. Да, я все это знаю, мой вопрос касается производительности. Меня особенно интересует, как pytorch будет распараллеливать операции и производительность передачи памяти с использованием различных компоновок памяти.
2. Ну, я знаю это на первый взгляд, я хочу знать о производительности в распределенных вычислениях, например, в графическом процессоре, без необходимости писать две реализации и бенчмарки
3. В любом из двух методов выполняемые операции будут использовать одинаковую схему хранения памяти, так что да, производительность одинакова.
4. И что лучше-выделять и назначать с помощью срезов
a[:] = x y
или использоватьa = (x y).type(a.dtype)
?5. Почему бы просто не
a = x y
сделать это ? Во всяком случае, оба они идентичны.