Градиентный кэш - это простой метод для неограниченного масштабирования контрастного обучения, далеко за пределы ограничения памяти GPU/TPU. Это означает, что обучение, которое использовалось для приема тяжелого аппаратного обеспечения, например, 8 V100, может быть проведен на одном графическом процессоре. Кроме того, кэш градиента позволяет пользователям заменить графический процессор/TPU Big RAM на гораздо более экономичные системы с низким уровнем оперативной памяти.
В этом репо содержится общая реализация градиентного кэша, описанного в нашей бумаге, масштабируется глубоко контрастным размером партии обучения при ограниченной настройке памяти. Оба фреймворки Pytorch и Jax поддерживаются.
@inproceedings{gao2021scaling,
title={Scaling Deep Contrastive Learning Batch Size under Memory Limited Setup},
author={Luyu Gao, Yunyi Zhang, Jiawei Han, Jamie Callan},
booktitle ={Proceedings of the 6th Workshop on Representation Learning for NLP},
year={2021},
}
НОВЫЕ: Теперь мы поддерживаем JAX и TPU!
Градиентный кэш также был интегрирован в плотный отрывок (DPR). Проверьте наш инструментарий GC-DPR.
Сначала установите желаемый бэкэнд глубокого обучения, либо Pytorch, либо JAX. Чтобы установить GradCache, клонируйте это репо и запустите PIP.
git clone https://github.com/luyug/GradCache
cd GradCache
pip install .
Для развития,
pip install --editable .
Функциональные функции кэширования градиента реализованы в классе GradCache . Если вы разрабатываете новый проект вместо исправления старого, также проверьте наш функциональный подход для усилий по сокращению подхода.
Для пользователя JAX/FLAX взгляните на простую функцию поезда здесь.
Метод класса __init__ определяет кэш и имеет несколько функциональных параметров *_fn для легкой настройки поведения модели. В качестве альтернативы вы также можете подкладывать gradcache.
grad_cache.GradCache(
models: List[nn.Module],
chunk_sizes: Union[int, List[int]],
loss_fn: Callable[..., Tensor],
split_input_fn: Callable[[Any, int], Any] = None,
get_rep_fn: Callable[..., Tensor] = None,
fp16: bool = False,
scaler: GradScaler = None,
)
Модели - список моделей энкодеров, которые будут обновляться с помощью градиента.
chunk_sizes - целое число, указывающее размер чанка. Или список целых чисел размера чанка для каждой модели. Это управляет для каждой модели по размеру подразделения для запуска переходного прохода и должен быть установлен на основе доступной памяти GPU. Слишком маленькое значение оставит GPU под использованием.
HOSS_FN - Функция потерь, которая принимает тензоры представления числа, равное количеством моделей в models и произвольным номером аргументов ключевых слов. Он должен вычислить потери на основе входных тензоров и ни в коем случае не изменять отношения входных тензоров на графике Autograd, на которые впоследствии полагаются на создание градиента.
split_input_fn - необязательная функция, которая разделяла вводную модель в виде кусков на основе определенной chunk_sizes. Если не предоставлено, этот класс будет стараться изо всех сил, чтобы разделить входные данные поддерживаемых типов. См. Функция split_inputs .
get_rep_fn - необязательная функция, которая принимает общий выход модели и тензоры возврата. Если не предоставлен, общий вывод предполагается, что является тензором представления.
FP16 - Если это правда, запустите смешанную точную тренировку, которая также требует, чтобы Scaler также был установлен.
Scaler - объект Gradscaler для автоматического смешанного обучения точности.
Чтобы запустить шаг к кэшированному градиенту компьютерного компьютера, вызовите функцию cache_step ,
cache_step(
*model_inputs,
no_sync_except_last: bool = False,
**loss_kwargs
)
Запустите один шаг кеша градиента. После возврата функции обновления вычисляются для каждой модели в self.models Модели с градиентом заполнены на весах, как будто model_inputs запускаются в виде огромной единой партии на достаточно большом оборудовании. Вызов объекта GradCache с __call__ также будет вызывать эту функцию.
MODEL_INPUTS - Список входов в каждую модель энкодера. Должен быть в том же порядке, что и self.models .
NO_SYNC_EXCEPT_LAST -Если TRUE, при распределенной настройке, для каждой модели только запускает уменьшение градиента по процессам для перехода последнего подразделения. Это может пригодиться при работе с а) большой моделью и/или б) не тривиальным числом подборов.
LOSS_KWARGS - Дополнительные аргументы ключевых слов с функцией потерь loss_fn . Это предназначено для включения гибких вычислений потерь (благодаря динамическому графику в Pytorch), таком как снижение, взвешивание и т. Д. Потенциально, используя loss_kwargs , вы можете включить выходы из тех моделей энкодеров, не отслеживаемых кешем.
Возврат - Потеря, текущий тензор потерь потери шагов (отдельно от графика).
model(x)model(*x)model(**x)model(*x[0], **x[1])Другой общий вход не полностью поддерживается, мы выполняем модельный вызов, используя следующую эвристику,
model(*x)model(**x)model(*x[0], **x[1]) Чтобы запустить с ними, split_input_fn должен быть указан во время инициализации кэша, чтобы разбить эти входы на более мелкие партии. В некоторых редких случаях вам также может потребоваться переопределить get_input_tensors , когда его эвристика не может взять достаточно тензоров, которые охватывают все устройства CUDA, которые держат некоторые тензоры на входе.
Скажем, мы хотим изучить встраиваемое пространство ярлыков и текста. Рассмотрим следующие четыре пары. (На практике у вас будет гораздо больше и более длинных текстовых записей.)
labels = ['fruit', 'meat', 'school', 'company']
texts = [
'this is an apple',
'steak should be cooked medium rare',
'cmu is pittsburgh',
'apple sells laptop'
]
Инициализировать наши модели энкодера,
from transformers import AutoTokenizer, AutoModel
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
encoder1 = AutoModel.from_pretrained("bert-base-uncased").cuda()
encoder2 = AutoModel.from_pretrained("bert-base-uncased").cuda()
Инициализировать объект GradCache,
from grad_cache import GradCache
from grad_cache.loss import SimpleContrastiveLoss
loss_fn = SimpleContrastiveLoss()
gc = GradCache(
models=[encoder1, encoder2],
chunk_sizes=2,
loss_fn=loss_fn,
get_rep_fn=lambda v: v.pooler_output
)
Здесь мы используем аргумент GET_REP_FN , чтобы указать функцию, которая принимает общий выход модели HuggingFace, и возвращаем фактический тензор представления.
Создать ввод модели,
xx = tokenizer(tt, return_tensors='pt', padding=True)
yy = tokenizer(tt2, return_tensors='pt', padding=True)
Запустить шаг кеша,
gc(xx, yy, reduction='mean')
Здесь мы используем reduction='mean' в качестве disper_kwargs для контроля потери потерь. С определенным optimizer , полное обновление градиента может быть сделано как,
optimizer.zero_grad()
gc(xx, yy, reduction='mean')
optimizer.step()
Это естественно обрабатывается (магией) динамическим графом. Вы передаете мелкие копии той же модели энкодера методу gradcache init.
tied_encoder = AutoModel.from_pretrained("bert-base-uncased").cuda()
gc = GradCache(
models=[tied_encoder , tied_encoder],
chunk_sizes=2,
loss_fn=loss_fn,
get_rep_fn=lambda v: v.pooler_output
)
Под капотом будут зарегистрированы отдельные крючки для правильного расчета градиента.
Мы ожидаем, что перекрестная процесс передачи представлений будет обработана с помощью loss_fn .
from grad_cache.loss import DistributedContrastiveLoss
loss_fn_dist = DistributedContrastiveLoss()
Должным образом оберните модели энкодера для уменьшения градиента,
encoder1_ddp = DistributedDataParallel(
encoder1, device_ids=[local_rank], output_device=local_rank, find_unused_parameters=True)
encoder2_ddp = DistributedDataParallel(
encoder2, device_ids=[local_rank], output_device=local_rank, find_unused_parameters=True)
Вы можете инициализировать кэш. Использовать распределенные потери и модели DDP,
gc = GradCache(
models=[encoder1_ddp, encoder2_ddp],
chunk_sizes=2,
loss_fn=loss_fn_dist,
get_rep_fn=lambda v: v.pooler_output
)
Запустить шаг кеша,
gc(xx, yy, no_sync_except_last=True, reduction='mean')
Установите no_sync_except_last=True чтобы избежать ненужного уменьшения градиента.
Если вы разрабатываете новый проект, мы рекомендуем также проверить декораторы, которые мы предоставили для создания функций более высокого порядка для кеша.
grad_cache.functional.cached(func: Callable[..., Tensor])
Декоратор, который принимает функцию модельного вызова в кэшированную совместимую версию.
Func - функция, которая вызывает модель и возвращается тензор представления.
Возврат - функция, которая возвращает 1) представление тензоров листьев для построения кэша, 2) Функция закрытия для 2 -го вперед и кэшированного назад. Позвоните 2) с 1) в качестве аргумента после того, как вызовет назад по тензору потери.
grad_cache.functional.cat_input_tensor(func: Callable[..., Tensor])
Декоратор, который объединяет позиционные и ключевые аргументы списка типов [Tensor] в один тензор в 0 -м измерении. Это может пригодиться, имея дело с результатами тензоров представления из нескольких кэшированных вперед.
Func - функция потери
Возврат - Декорированная функция потерь для кэшированных результатов.
grad_cache.functional.gather_input_tensor(func: Callable[..., Tensor], axis=0)
Декоратор, который All-Gater-позиционный и ключевой слов аргументы тензора типа и объединяют их на оси. Предназначен для использования для создания распределенной контрастной потери обучения.
Func - функция потери
Возврат - Декорированная функция потерь для распределенного обучения.
Функциональные декораторы очень полезны, если ваш загрузчик данных излучает небольшие партии, из которых вы можете построить большую партию. Скажем, вы также хотите сделать автоматическую смешанную точность, мы сначала определяем функцию вызова моделя и функцию потерь,
from grad_cache.functional import cached, cat_input_tensor
import torch
import torch.nn.functional as F
from torch.cuda.amp import autocast
@cached
@autocast()
def call_model(model, input):
return model(**input).pooler_output
@cat_input_tensor
@autocast()
def contrastive_loss(x, y):
target = torch.arange(0, y.size(0), int(y.size(0) / x.size(0)), device=x.device)
scores = torch.matmul(x, y.transpose(0, 1))
return F.cross_entropy(scores, target=target)
Скажем, у вас есть loader данных, излучающий небольшие партии кортежей (xx, yy) размера (m * n), и вы хотите тренироваться, агрегируя 16 небольших партий, чтобы получить партию (16m * 16n),
cache_x = []
cache_y = []
closures_x = []
closures_y = []
for step, sub_batch in enumerate(loader):
xx, yy = sub_batch
rx, cx = call_model(bert, xx)
ry, cy = call_model(bert, yy)
cache_x.append(rx)
cache_y.append(ry)
closuresx.append(cx)
closuresy.append(cy)
if (step + 1) % 16 == 0:
loss = contrastive_loss(cache_x, cache_y)
scaler.scale(loss).backward()
for f, r in zip(closuresx, cache_x):
f(r)
for f, r in zip(closuresy, cache_y):
f(r)
cache_x = []
cache_y = []
closures_x = []
closures_y = []
scaler.step(optimizer)
scaler.update()
optimizer.zero_grad()
Запуск распределенного многопроцессного обучения требует: 1) (все) сбора представлений на разных устройствах и 2) (все-кратно-восстановительные) градиенты на разных устройствах. Оба шага будут происходить за пределами кэшированных украшенных функций.
Последнее легко достичь путем обертывания энкодеров, например, bert , в DistributedDataParallel .
bert = DistributedDataParallel(
bert, device_ids=[local_rank], output_device=local_rank, find_unused_parameters=True)
Первый требует дополнительного распределенного OPS в функции потерь, что должно выполняться в соответствии с исходным определением потерь. Например,
from torch import distributed as dist
from grad_cache.functional import cat_input_tensor, gather_input_tensor
@cat_input_tensor
@gather_input_tensor
@autocast()
def contrastive_loss(x, y):
target = torch.arange(0, y.size(0), int(y.size(0) / x.size(0)), device=x.device)
scores = torch.matmul(x, y.transpose(0, 1))
# scale the loss as DistributedDataParallel will do mean reduce
return F.cross_entropy(scores, target=target) * dist.get_world_size()
grad_cache/grad_cache.py - определить класс GradCache. Код составляет менее 300 строк, включая комментарии. Для развития мы призываем вас прочитать это.
grad_cache/funcation.py - определить декораторы для создания функции более высокого порядка для кэширования градиента из обычных функций вызова модели и функций потерь.