Что такое идеальная библиотека для двухточечной связи?

Шаблоны проектирования

Автор | Юань Цзиньхуэй

При обсуждении требований к сетевой передаче распределенных сред глубокого обучения в предыдущей статье «Борьба со сложностью программной системы: надлежащее наслоение, нет-нет-нет» мы разделили несколько уровней абстракции. Нижний уровень абстракции — двухточечный. (точка-точка).


В этой статье мы обсудим, как должна выглядеть идеальная библиотека для двухточечной связи? Если такой библиотеки пока нет, то почему бы вместе не сделать опенсорсный проект в этой области?

1

Что такое двухточечная связь?

Что такое двухточечная связь? Определение в Википедии:In telecommunications, a point-to-point connection refers to a communications connection between two communication endpoints or nodes.Короче говоря, это передача один к одному, только с одним отправителем и только одним получателем.

Почему важна передача «точка-точка»? Потому что двухточечная передача является базовой единицей, используемой для построения любого сложного режима передачи на верхнем уровне.

Например, кольцевая всесвертка или древовидная сведение, обычно используемые в распределенном обучении глубокому обучению, собраны на основе самых основных функций передачи точка-точка; библиотека передачи точка-точка также может быть инкапсулирована в более пользовательский интерфейс. Дружественный и простой в использовании интерфейс, такой как различные библиотеки удаленного вызова процедур (RPC), также реализованы на основе двухточечной передачи.

Заранее в этой статье представлена ​​только передача данных от процессора к процессору.В реальных проектах более сложная передача от процессора к процессору.Самый простой из них — это GPUDirect RDMA, который согласуется с программированием RDMA на процессоре, но поддерживает только данные.ГП центрального уровня , иначе он должен быть в режиме gpu-cpu-net-cpu-gpu.

2

Что такое библиотека двухточечной связи?

На самом деле API сетевого программирования, предоставляемый на уровне операционной системы, является двухточечным, таким как сокет, а базовая библиотека RDMA сама по себе является API двухточечного соединения.

Зачем вам все еще нужна библиотека? Основная цель состоит в том, чтобы сделать его более простым в использовании и более общим без потери производительности, скрыть различные базовые программные интерфейсы, предоставить согласованные API-интерфейсы для приложений верхнего уровня и добиться согласованного опыта программирования, такого как сокеты TCP/IP или сети RDMA. различные программные интерфейсы, но мы надеемся, что программы, написанные прикладной программой верхнего уровня, вызывают базовые возможности передачи через согласованный интерфейс.

ZeroMQ (zeromq.org/) представляет собой библиотеку связи точка-точка с широким набором приложений (разумеется, она также поддерживает некоторые функции многосторонней связи), что упрощает программирование Socket и обладает высокой производительностью, облегчая написание высокопроизводительной сети Приложения.

3

Зачем создавать новую библиотеку двухточечной связи?

Существующие библиотеки однорангового транспорта имеют различные проблемы. ZeroMQ не поддерживает RDMA и не подходит для сценариев глубокого обучения.

В OneFlow есть модуль CommNet, который поддерживает и Socket, и RDMA.Интерфейс и реализация удовлетворительны, но они недостаточно независимы и глубоко связаны с системой OneFlow, что делает неудобным использование в других проектах. Facebook построил TensorPipe для проекта PyTorch, который поддерживает сокеты и RDMA.От определения интерфейса до реализации он очень близок к моему воображению, но некоторое недовольство все же есть.Надеюсь, вы поймете это после прочтения всей статьи.

4

Каковы характеристики идеальной коммуникационной библиотеки «точка-точка»?

Эти три момента можно извлечь из базового механизма передачи, требований приложений верхнего уровня и опыта работы с существующими библиотеками связи точка-точка:

  • Простое программирование, легкое соответствие различным приложениям верхнего уровня, включая инкапсуляцию в RPC, используемые в средах глубокого обучения, таких как OneFlow, и даже используемые в общих примитивах кластерной связи в высокопроизводительных вычислениях и глубоком обучении (all-reduce, широковещательная рассылка и т. д.);
  • Высокая производительность: производительность без копирования, низкая задержка, высокая пропускная способность;
  • Нижний уровень поддерживает сокеты TCP/IP и передачу RDMA.

Чтобы соответствовать этим требованиям, эта коммуникационная библиотека технически реализует следующие четыре пункта:

  • модель программирования, ориентированная на сообщения;
  • неблокирующий интерфейс;
  • нулевая копия;
  • Дружелюбен как к маленьким, так и к большим новостям.

Ниже мы более подробно обсудим, почему они имеют решающее значение.

5

модель программирования, ориентированная на сообщения

И Socket, и ZeroMQ абстрагируют путь связи точка-точка в канал.Отправитель записывает данные в канал через функцию отправки следующим образом, а получатель считывает данные из канала через функцию recv (во входном параметре функции Здесь мы намеренно опускаем конечные адреса отправителя и получателя, такие как файловый дескриптор Socket).

int64_t send(void* in_buf, int64_t size);
int64_t recv(void* out_buf, int64_t size);

Коммуникационная библиотека не заботится о конкретном содержании передачи, и она рассматривается как последовательность байтов (то есть сериализация и десериализация являются обязанностью приложения верхнего уровня), а коммуникационная библиотека заботится только о размере объема передачи (размер байтов). Предполагая, что обе стороны коммуникации заранее знают размер суммы передачи, то есть размер, отправитель предварительно выделит размер in_buf, поместит отправляемое содержимое в in_buf и вызовет функцию отправки; получатель также предварительно выделит out_buf размера size и вызовет функцию recv для получения данных. Обратите внимание, что здесь мы предполагаем, что буферы in_buf и out_buf во входных параметрах управляются пользователем.

Чтобы упростить программирование для пользователей, интерфейс должен быть полностью ориентирован на сообщения, а не на поток байтов.. То есть независимо от того, сколько данных передается, функции отправки и приема должны выполнять задачу «в одно время», когда они возвращаются, так что пользователю нужно вызывать функцию только один раз каждый раз, когда есть необходимость в передаче. , независимо от того, разделяет ли нижележащий уровень данные на несколько сегментов для передачи..

ZeroMQ соответствует этой семантике, и режим блокировки в программировании сокетов также соответствует этой семантике.При программировании сокетов в режиме блокировки функция не вернется, пока передача данных не будет завершена. Однако программирование сокетов в неблокирующем режиме не соответствует этой семантике. Когда операционная система не может завершить передачу данных за один раз, она завершит часть данных и вернет количество фактически переданных байтов. Пользователь может нужно снова вызвать send и recv для передачи позже.

6

неблокирующий режим вызова

Взяв в качестве примера программирование сокетов, в режиме блокировки функция вернется только тогда, когда данные будут фактически переданы, но время передачи зависит от объема передачи и полосы пропускания передачи, и ожидание завершения передачи может занять много времени. , Внутри поток, вызывающий send и recv, может только спать и не может обрабатывать другие вещи.Для повышения пропускной способности системы может потребоваться запустить и управлять многими потоками.

В неблокирующем режиме при вызове send и recv, если система не может выполнить задачу передачи за один раз, она также скопирует часть данных из пространства пользователя в пространство ядра (хотя время выполнения этой копии очень короткое, но нам нужно обратить внимание на его существование ), и возвращает количество данных, переданных на этот раз, подсказывая пользователю, что «передача не завершена, пожалуйста, позвоните еще раз в подходящее время, чтобы продолжить передачу».

Вышеупомянутые два режима недостаточно удобны для пользователя, лучший способ,Библиотека передачи — это сервис, ориентированный на потребности верхнего уровня.Приложение верхнего уровня возвращает задачу сразу же после передачи ее библиотеке передачи.Когда передача завершена, приложение верхнего уровня может быть уведомлено.. Для этого API может быть адаптирован для:

void send(void* in_buf, int64_t size, Callback done);
void recv(void* out_buf, int64_t size, Callback done);

То есть каждая функция добавляет входную функцию обратного вызова, send и recv возвращаются немедленно, а пользовательская функция обратного вызова будет выполняться после завершения передачи данных.

Конечно, с этим неблокирующим программным интерфейсом очень легко поддерживать режим блокировки, приложив немного усилий.

7

нулевая копия

В приведенном выше обсуждении мы предполагали, что память in_buf и out_buf управляется приложением верхнего уровня.Например, in_buf выделяется перед вызовом send, а in_buf может быть освобожден после возврата из функции send. Однако обратите внимание, что в неблокирующем режиме, даже если отправка возвращает данные, возможно, они не были отправлены, поэтому коммуникационная библиотека должна обратиться за частью памяти внутри функции отправки и скопировать данные in_buf в эту управляемую память. коммуникационной библиотекой, чтобы коммуникационная библиотека всегда могла использовать эту управляемую ею память до тех пор, пока данные не будут фактически переданы, а затем освобождены.

Однако описанная выше схема имеет некоторые недостатки, например, при каждой передаче данных коммуникационной библиотеке приходится выделять дополнительную память того же размера, что и буфер, переданный пользователем, требуется время для выделения памяти и копирования данных из буфер прикладной программы в коммуникационную библиотеку.Это также требует времени для управляемых буферов, а также увеличивает использование памяти.

Более идеальный способ: хотя in_buf выделяется приложением верхнего уровня, право собственности на память буфера передается коммуникационной библиотеке в момент вызова функции отправки, и in_buf не может быть освобожден сразу после функция отправки возвращает, потому что send отправляет in_buf используется непосредственно в процессе.Когда передача действительно завершена, память in_buf может быть освобождена в callback-функции done.

Точно так же, даже если out_buf выделяется коммуникационной библиотекой, право собственности на out_buf передается приложению верхнего уровня в момент выполнения функции обратного вызова, введенной recv, вместо копирования out_buf в буфер, управляемый приложением.

Мы обсудили некоторые из более общих требований выше, и нам нужно завершить некоторые детали ниже.

8

Как два конца связи согласовывают объем передачи?

Ранее мы предполагали, что и отправитель, и получатель уже знали размер передаваемых данных, то есть значение параметра size, Это предположение нереалистично, но и не возмутительно.

Прежде всего, каждый раз, когда есть запрос на передачу, хотя объем передачи не одинаков, отправитель должен знать размер объема передачи, но получатель может этого не знать. Во-вторых, перед каждой передачей реальных данных отправитель может сначала отправить значение размера, чтобы получатель знал реальный размер передаваемых данных и мог заранее выделить память.

Следует отметить, что обе стороны должны сообщить размер суммы передачи, то есть значение размера, до передачи реальных данных.Значение этого размера также передается через отправку/прием.Необходимо сообщить, что чем-то похож на процесс начальной загрузки.

Предполагая, что мы хотим отправить данные из A в B один раз, мы должны вызвать пару send/recv как минимум 3 раза, чтобы завершить это, как показано на следующем рисунке:

Первый раз от A к B вызывается send и recv соответственно, A передает размер в B, а B выделяет память (alloc) для out_buf по размеру после получения. После того, как B выделяет память, второй обмен данными происходит от B к A. B отправляет сигнал «пожалуйста, начните» к A. Этот сигнал очень короткий и имеет фиксированную длину, и не требует от A и B согласования для выделения памяти. Когда B получает сигнал «начни пожалуйста», может начаться третья коммуникация, передавая реальные данные от A к B.

Что не так с приведенной выше схемой?

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

Во-вторых, send и recv должны использоваться парами, а отправитель и получатель должны вызываться в одном и том же ритме, но получатель вызывает recv только один раз, и во второй раз он терпит неудачу. Однако, когда отправитель определяет потребность в передаче, получатель пассивен, он не знает, когда ему нужно вызвать recv, приведенная выше спецификация не подходит для использования.

Как это сделать?

Для первого вопроса могут быть разработаны два режима передачи для коротких сообщений и длинных данных.Для передачи данных, длина которых меньше определенного порога, они могут быть отправлены напрямую без согласования между двумя сторонами.Отправитель может предположить, что получатель может получить его успешно, и отправитель. Также предполагается, что получатель должен был вызвать recv заранее, чтобы соединиться с отправкой. При передаче длинных данных это необходимо делать через три вышеуказанных вызова.

Что касается второй проблемы, коммуникационная библиотека всегда заранее готовится к тому, откуда и когда нужно отправить короткое сообщение, то есть заранее подготавливает фиксированное количество вызовов recv. Это нелегко понять. Друзья, знакомые с асинхронным программированием Grpc или программированием RDMA, должны быть знакомы с этим. Каждый коммуникационный процесс при запуске заранее готовит несколько PostRecvRequest и потребляет один RecvRequest каждый раз, когда он связан с отправкой в ​​другое место. И вовремя добавить новый RecvRequest.

Наконец, некоторые друзья могут быть озадачены, почему приемник должен заранее знать размер при передаче длинных данных. Это в основном предназначено для предварительного выделения памяти, чтобы гарантировать, что передача данных может быть успешной, и нет необходимости повторно выделять память во время процесса передачи, а также может быть достигнуто нулевое копирование.

В противном случае, если память не выделена заранее, необходимо постоянно выделять память в соответствии с фактическими потребностями в процессе передачи.Если выделение не удалось, процесс передачи необходимо прервать из-за нехватки ресурсов памяти.Конечно , нулевая копия не может быть достигнута.

9

Дизайн API

Из приведенного выше обсуждения кажется, что только интерфейс отправки/получения может удовлетворить все потребности, которые могут удовлетворить потребности в передаче коротких сообщений и длинных данных.

Однако в дополнение к этому API отправителю и получателю приходится иметь дело со сложной логикой: получателю всегда нужно заранее подготовить некоторый RecvRequest, а при передаче длинных данных отправителю и получателю необходимо согласовать туда-сюда несколько раз. С точки зрения разработки базовой библиотеки мы надеемся максимально упростить работу пользователя и скрыть детали, не связанные с требованиями. Глядя на это с этой точки зрения, просто отправить/получить недостаточно.

Для коротких сообщений мы надеемся, что отправитель может отправить их напрямую, а коммуникационная библиотека гарантирует, что получатель готов вызвать recv.Это recv не нужно явно вызывать пользователем, то есть в сценарии короткого сообщения recv API не нужен. Пользователю нужно только предоставить функцию обратного вызова для коммуникационной библиотеки после получения короткого сообщения.Всякий раз, когда получатель получает короткое сообщение, он вызывает соответствующую функцию обратного вызова для обработки короткого сообщения.

Если бизнесу требуется несколько типов коротких сообщений, вы можете классифицировать короткие сообщения и предоставить соответствующий тип функции обратного вызова для каждого отдельного типа коротких сообщений.

Для второго и третьего сообщения о передаче длинных данных получателю необходимо один раз вызвать send и recv, а отправителю нужно один раз вызвать send, но детали этих вызовов должны быть прозрачны для пользователя. Все эти операции могут быть выполнены нижним уровнем коммуникационной библиотеки, а интерфейс пользовательского программирования может быть объединен в одностороннюю операцию чтения, вызываемую получателем, и приложению отправителя не нужно ничего делать, конечно, после передача данных завершена, необходимо вызвать указанную пользователем функцию обратного вызова для обработки полученных данных.

То есть минимальный API библиотеки связи точка-точка может быть в следующем виде:

void send(void* in_buf, int64_t size);
void read(void* out_buf, int64_t size, Callback done);

Обратите внимание, что в фактической реализации интерфейсу чтения на самом деле нужен токен, идентифицирующий местоположение данных отправителя, через который правильные данные могут быть удаленно считаны.

10

Дизайн OneFlow CommNet

CommNet для выполнения функций OneFlow требует двух наиболее важных абстракций: Eager Message и RMA Read. В текущей реализации Massage используется для передачи ActorMsg, а RMA Read используется для передачи фактического содержимого regst.

Настройки нетерпеливого сообщения:

  • Точка-точка, каждое сообщение соответствует отправителю и получателю
  • Отправитель отправляет сообщение, а получатель получает соответствующее сообщение в будущем
  • Отправитель отправляет сообщения непосредственно получателю без предварительного согласования.
  • Получатель принимает сообщение безоговорочно
  • Отправитель может предположить, что передача будет успешной, а получатель обязательно получит сообщение в будущем.
  • Получатель обрабатывает сообщения, опрашивая или регистрируя обратные вызовы.
  • С абстракцией соединения или без нее, в абстракции без установления соединения отправитель использует идентификатор получателя в качестве параметра отправки, а в абстракции с подключением отправителю необходимо заранее установить соединение с получателем и использовать идентификатор соединения в качестве параметра отправки.
  • Для разных сообщений, отправляемых одним и тем же потоком одному и тому же получателю или одному и тому же соединению, необходимо убедиться, что порядок, полученный получателем, согласуется с отправленным порядком.
  • Само сообщение представляет собой блок данных фиксированного размера или динамического размера, нет необходимости заботиться о протоколе верхнего уровня.
  • Обычно предназначен для обработки небольших блоков данных.
  • Основными показателями обычно являются задержка и пропускная способность.

Удаленный доступ к памяти (RMA) Чтение настроек:

  • Точка-точка, каждая операция соответствует локальному и удаленному концам.
  • Локальный конец инициирует операцию, результатом которой является чтение части данных в удаленном адресном пространстве из локальной памяти.
  • Удаленный конец должен заранее сгенерировать токен доступа, а локальный конец должен передать токен для доступа к данным в диапазоне адресов, зарегистрированном удаленным концом при создании токена. Прежде чем операция будет инициирована, локальный конец и удаленный конец должны обменяться маркерами доступа любым другим способом.
  • Один доступ к локальному концу может прочитать любой диапазон данных в области, соответствующей маркеру доступа, и данные в одном и том же месте могут быть прочитаны любое количество раз.
  • В процессе чтения удаленный конец не должен участвовать
  • Локальный конец обрабатывает событие завершения передачи, опрашивая или регистрируя обратные вызовы.
  • Локальный конец считает, что удаленная память всегда доступна
  • Обычно предназначен для обработки больших блоков данных.
  • Ключевым показателем обычно является пропускная способность/пропускная способность.

11

обсуждать

Почему второе и третье сообщение должны быть абстрагированы в одностороннюю операцию чтения, почему бы не позволить отправителю явно вызывать отправку или запись? В этом вызове нет необходимости, время его выполнения должно определяться получателем, и он должен выполняться автоматически, и нет необходимости предоставлять его интерфейсу приложения верхнего уровня.

На самом деле, друзья, знакомые с программированием RDMA, должны быть хорошо знакомы с тем фактом, что в RDMA предусмотрена отправка, нет интерфейса recv, а односторонние операции, такие как запись и чтение, выполняются одновременно. в качестве библиотеки связи точка-точка требуется только чтение, достаточно односторонней операции. Рациональность предложенного нами API программирования можно дополнительно проверить, обратившись к дизайну интерфейса программирования RDMA.

На самом деле, некоторые исследователи MPI предлагали аналогичные конструкции интерфейсов, например, чтобы устранить недостатки существующих интерфейсов MPI, группа ученых, изучающих следующее поколение MPI, опубликовала статью под названием «К миллионам взаимодействующих потоков (*Это ты сделал. В это время Иллинойс. quota/listed/C101…протокола (да, в распределенном дизайне TensorFlow тоже есть эта концепция), отдельное спасибо Yan Jiakun за рассказ об этой статье.

Приведенное выше обсуждение касается разработки API с точки зрения потребностей приложения верхнего уровня. Конечно, при разработке API также необходимо учитывать базовую реализацию. Например, модель программирования epoll, ориентированная на сокеты, отличается от модели программирования epoll, ориентированной на сокеты. модель программирования RDMA.Наша коммуникационная библиотека должна поддерживать эти различные механизмы передачи, дизайн API также должен учитывать сложность программирования при использовании различных механизмов передачи.

Мы узнали, что RDMA сам по себе уже обеспечивает односторонние операции отправки и чтения. Было бы более естественно использовать RDMA для поддержки API, предложенного в этой статье. например, для передачи RDMA требуется память с блокировкой страниц.Для передачи данных переменной длины накладные расходы на выделение памяти с блокировкой страниц в режиме онлайн относительно высоки.Как решить эту проблему не просто. У epoll нет полностью соответствующей концепции, поэтому использование epoll для реализации этой коммуникационной библиотеки может потребовать дополнительной работы.

В следующей статье мы обсудим дополнительные способы реализации этой коммуникационной библиотеки с использованием RDMA и epoll.

Заглавное изображение от TheDigitalArtist, Pixabay

Добро пожаловать, чтобы загрузить и испытать новое поколение среды глубокого обучения OneFlow с открытым исходным кодом:

GitHub.com/oneflow-Inc…