English translation is not available yet. Showing Russian content.

Написать RDMA-читалку для KV cache

ТЕХНИЧЕСКОЕ ЗАДАНИЕ: Написать RDMA-читалку для KV cache

1. Цель задачи

Разработать низкоуровневый компонент (RDMA-читалку) для передачи KV cache между узлами распределённой LLM-системы через InfiniBand с использованием Remote Direct Memory Access (RDMA). Цель — обеспечить латентность передачи 1 GB данных менее 50 микросекунд, что критично для синхронизации кэша при инференсе больших моделей. Вы научитесь работать с libibverbs, регистрацией памяти и односторонними RDMA-операциями (RDMA Read).

Ключевой результат Рабочая программа на C, которая по RDMA Read получает KV cache заданного размера с удалённого узла и замеряет latency передачи.

2. Исходные данные

Перед началом необходимо иметь:

Что нужноОткуда взять
Два Linux-сервера с InfiniBand (или один с SoftRoCE)Физический кластер / облачный инстанс с IB / локальная виртуализация
RDMA-совместимое сетевое оборудование (HCA)Mellanox ConnectX-5 или выше / эмуляция SoftRoCE
Установленный RDMA-стек (rdma-core, libibverbs, librdmacm)Пакетный менеджер (apt, yum) или сборка из исходников
Компилятор C (gcc/clang) и makeСистема сборки
Тестовый KV cache (бинарный файл размером 1 GB)Сгенерировать случайными данными (dd if=/dev/urandom of=kv_cache.bin bs=1M count=1024)
Измеритель времени (clock_gettime с CLOCK_MONOTONIC)Стандартная библиотека POSIX

Если нет реального InfiniBand — симулируем:

  1. Установите SoftRoCE (RXE) на одном Linux-хосте:
    sudo apt install rdma-core librdmacm-dev libibverbs-dev
    sudo modprobe rdma_rxe
    sudo rxe_add enp0s3  # замените на ваш интерфейс
    
  2. Проверьте, что появилось устройство rxe0 (команда ibv_devinfo).
  3. Используйте loopback (127.0.0.1) для тестов — RDMA будет работать через SoftRoCE, но latency будет выше (цель <50 мкс может не достигаться, но код будет рабочим).

3. Технологический стек

КомпонентИнструментыНазначение
Язык программированияC (C11)Низкоуровневый доступ к RDMA, минимальные накладные расходы
RDMA APIlibibverbs (ibv_*)Регистрация памяти, создание QP, RDMA Read
Управление соединениямиlibrdmacm (rdma_*)Установка RC-соединения между узлами
СборкаMakefile / CMakeАвтоматизация компиляции
Профилированиеperf, ibv_asyncwatchИзмерение latency, мониторинг ошибок
Генерация данныхdd, /dev/urandomСоздание тестового KV cache
Тестированиеbash-скрипты, pingpongПроверка корректности и производительности

4. Этапы выполнения

Этап 1: Настройка окружения и проверка RDMA-стека (1 час)

Действия

  1. Установите RDMA-пакеты на обоих узлах (или на одном для SoftRoCE):
    sudo apt update && sudo apt install -y rdma-core librdmacm-dev libibverbs-dev ibverbs-utils perftest
    
  2. Проверьте устройства InfiniBand:
    ibv_devinfo
    
    Убедитесь, что видно хотя бы одно устройство (например, mlx5_0 или rxe0).
  3. Проверьте соединение между узлами через RDMA (если два физических узла):
    # На сервере:
    ib_send_bw -d mlx5_0 -p 12345
    # На клиенте:
    ib_send_bw -d mlx5_0 -p 12345 <server_ip>
    
    Убедитесь, что bandwidth тест проходит.
  4. Создайте тестовый KV cache (1 GB):
    dd if=/dev/urandom of=kv_cache.bin bs=1M count=1024
    
  5. Напишите Makefile для сборки проекта:
    CC = gcc
    CFLAGS = -Wall -O2 -march=native
    LDFLAGS = -libverbs -lrdmacm -lpthread
    all: rdma_kv_reader rdma_kv_server
    rdma_kv_reader: rdma_kv_reader.c
        $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
    rdma_kv_server: rdma_kv_server.c
        $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
    clean:
        rm -f rdma_kv_reader rdma_kv_server
    

Ожидаемый результат этапа Рабочее RDMA-соединение между узлами (или loopback), скомпилированный каркас программы.

Этап 2: Разработка RDMA-сервера (регистрация памяти и ожидание запросов) (2 часа)

Действия

  1. Создайте файл rdma_kv_server.c.
  2. Инициализируйте контекст RDMA:
    • Получите device list через ibv_get_device_list.
    • Откройте устройство (ibv_open_device).
    • Создайте Protection Domain (ibv_alloc_pd).
  3. Зарегистрируйте память для KV cache:
    struct ibv_mr *mr = ibv_reg_mr(pd, kv_cache, size, IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_READ);
    
    Где kv_cache — буфер, загруженный из файла.
  4. Создайте Completion Channel и CQ:
    struct ibv_comp_channel *channel = ibv_create_comp_channel(ctx);
    struct ibv_cq *cq = ibv_create_cq(ctx, 10, NULL, channel, 0);
    
  5. Создайте Queue Pair (QP) типа RC:
    struct ibv_qp_init_attr attr = {
        .send_cq = cq,
        .recv_cq = cq,
        .qp_type = IBV_QPT_RC,
        .cap = { .max_send_wr = 1, .max_recv_wr = 1, .max_send_sge = 1, .max_recv_sge = 1 }
    };
    struct ibv_qp *qp = ibv_create_qp(pd, &attr);
    
  6. Переведите QP в состояние RTR и RTS (используйте rdma_connect или ручной обмен через сокеты). Для простоты используйте librdmacm:
    • Создайте rdma_cm_id и слушайте на порту.
    • При подключении получите remote key и адрес.
  7. Передайте клиенту rkey и адрес буфера (через TCP или встроенный протокол).
  8. Ожидайте завершения RDMA-операций через ibv_poll_cq.

Ожидаемый результат этапа Сервер, который регистрирует 1 GB буфер и ожидает RDMA Read запросов от клиента.

Этап 3: Разработка RDMA-клиента (читалка) (2 часа)

Действия

  1. Создайте файл rdma_kv_reader.c.
  2. Инициализируйте RDMA-контекст (аналогично серверу, но без регистрации большого буфера — достаточно маленького для приёма).
  3. Подключитесь к серверу через rdma_connect.
  4. Получите remote key (rkey) и remote address от сервера (через TCP-канал или как часть handshake).
  5. Зарегистрируйте локальный буфер для приёма данных (также 1 GB):
    struct ibv_mr *local_mr = ibv_reg_mr(pd, local_buf, size, IBV_ACCESS_LOCAL_WRITE);
    
  6. Постройте scatter-gather element (SGE):
    struct ibv_sge sge = {
        .addr = (uintptr_t)local_buf,
        .length = size,
        .lkey = local_mr->lkey
    };
    
  7. Постройте RDMA Read Work Request (WR):
    struct ibv_send_wr wr = {
        .wr_id = 1,
        .sg_list = &sge,
        .num_sge = 1,
        .opcode = IBV_WR_RDMA_READ,
        .send_flags = IBV_SEND_SIGNALED,
        .wr.rdma = {
            .remote_addr = remote_addr,
            .rkey = remote_rkey
        }
    };
    
  8. Отправьте WR на QP (ibv_post_send).
  9. Дождитесь completion (poll CQ) и замерьте время:
    struct timespec start, end;
    clock_gettime(CLOCK_MONOTONIC, &start);
    // post_send
    // poll CQ
    clock_gettime(CLOCK_MONOTONIC, &end);
    double latency = (end.tv_sec - start.tv_sec) * 1e9 + (end.tv_nsec - start.tv_nsec); // ns
    
  10. Проверьте корректность — сравните полученные данные с оригинальным файлом (например, md5sum).

Ожидаемый результат этапа Клиент, который выполняет RDMA Read 1 GB с сервера и выводит latency.

Этап 4: Оптимизация и измерение latency (1 час)

Действия

  1. Запустите тест на физическом InfiniBand (или SoftRoCE) и запишите latency.
  2. Оптимизируйте параметры:
    • Увеличьте размер MTU (если возможно) до 4 KB.
    • Используйте IBV_SEND_INLINE для маленьких сообщений (не применимо для 1 GB).
    • Настройте количество WQE (max_send_wr) и размер CQ.
    • Попробуйте ibv_fork_init() для оптимизации fork-безопасности.
  3. Повторите замеры 10 раз, вычислите среднее, медиану, p99.
  4. Сравните с целевым значением (<50 мкс). Если не достигается, проанализируйте узкие места:
    • Используйте perf stat для подсчёта циклов.
    • Проверьте, не происходит ли копирование данных (RDMA Read должен быть zero-copy).
  5. Документируйте результаты в отчёте.

Ожидаемый результат этапа Измеренная latency и рекомендации по дальнейшей оптимизации.

Этап 5: Интеграция с KV cache (формат данных) и тестирование (1 час)

Действия

  1. Определите формат KV cache (например, последовательность пар ключ-значение с заголовками). Для простоты используйте плоский массив байт.
  2. Модифицируйте сервер для загрузки реального KV cache из файла (если не сделано ранее).
  3. Напишите скрипт для автоматического запуска:
    # server.sh
    ./rdma_kv_server -p 12345 -f kv_cache.bin
    # reader.sh
    ./rdma_kv_reader -s <server_ip> -p 12345 -o received.bin
    
  4. Проверьте целостность:
    md5sum kv_cache.bin received.bin
    
  5. Проведите нагрузочное тестирование — 100 последовательных запросов, замерьте latency каждого.

Ожидаемый результат этапа Рабочая связка сервер-клиент с проверкой корректности и статистикой latency.

5. Критерии приемки (Definition of Done)

  • Код компилируется без ошибок и предупреждений (флаги -Wall -Wextra).
  • Сервер и клиент обмениваются данными через RDMA Read.
  • Latency передачи 1 GB данных < 50 мкс на реальном InfiniBand (на SoftRoCE допускается > 50 мкс, но код должен быть рабочим).
  • Данные, полученные клиентом, идентичны оригинальному файлу (проверка md5).
  • Программа корректно обрабатывает ошибки (неверный адрес, потеря соединения).
  • Время выполнения одной передачи не превышает 100 мс (с учётом накладных расходов).
  • Присутствует Makefile и README с инструкцией по запуску.
  • Код использует только libibverbs и librdmacm (без сторонних RDMA-библиотек).
  • Реализован замер времени с точностью до наносекунд.
  • Поддерживается передача произвольного размера (не только 1 GB) через аргументы командной строки.

6. Ожидаемый результат

Основной артефакт Исходный код на C (два файла: rdma_kv_server.c, rdma_kv_reader.c) и Makefile.

Содержание

  • Реализация RDMA-сервера: регистрация памяти, ожидание подключения, обработка RDMA Read.
  • Реализация RDMA-клиента: подключение, получение remote key, выполнение RDMA Read, замер latency.
  • Скрипты для запуска (опционально).

Дополнительные результаты

  • Отчёт с результатами измерений latency (среднее, медиана, p99) для разных размеров (1 MB, 10 MB, 100 MB, 1 GB).
  • График зависимости latency от размера (если есть возможность построить).
  • Анализ узких места (например, влияние регистрации памяти, размера MTU).

7. Возможные сложности и их решение

СложностьРешение
Нет физического InfiniBandИспользовать SoftRoCE (RXE) на одном хосте; latency будет выше, но код будет рабочим.
Ошибка ibv_reg_mr для больших буферов (не хватает памяти)Увеличить max_locked_memory через ulimit -l unlimited или настроить /etc/security/limits.conf.
RDMA Read не завершается (CQ пуст)Проверить, что QP переведён в RTS; использовать ibv_poll_cq с таймаутом; включить асинхронные события.
Несоответствие remote keyУбедиться, что rkey передан корректно (endianness, целостность).
Высокая latency (>50 мкс)Проверить использование IBV_SEND_SIGNALED; уменьшить количество WQE; использовать ibv_fork_init().
Segmentation fault при работе с буферамиПроверить выравнивание адресов (страницы); использовать posix_memalign.

8. Бюджет времени (оценка)

ЭтапВремя
Этап 1: Настройка окружения1 час
Этап 2: Разработка сервера2 часа
Этап 3: Разработка клиента2 часа
Этап 4: Оптимизация и замеры1 час
Этап 5: Интеграция и тестирование1 час
Итого7 часов

Примечание Для первого раза с незнакомым RDMA API рекомендуется заложить дополнительно 2-3 часа на отладку.

9. Связанные вопросы из базы знаний

ВопросТема
12Основы InfiniBand и RDMA
45Регистрация памяти в libibverbs
78Создание Queue Pair и переходы состояний
112RDMA Read vs RDMA Write
156Zero-copy передача данных
203Оптимизация latency в RDMA
267Использование librdmacm для установки соединения
334Обработка completion events
401Сравнение InfiniBand и Ethernet (RoCE)
512KV cache в распределённом инференсе LLM

10. Чек-лист самопроверки

  • Я проверил, что код компилируется с -Wall -Wextra без ошибок.
  • Я протестировал передачу 1 GB данных и убедился, что md5 совпадает.
  • Я измерил latency минимум 10 раз и записал среднее значение.
  • Я убедился, что программа корректно обрабатывает ошибки (неверный порт, отсутствие устройства).
  • Я написал README с инструкцией по сборке и запуску, включая настройку SoftRoCE.
  • Я проверил, что latency на физическом InfiniBand (если доступен) < 50 мкс.
  • Я освобождаю все ресурсы (pd, mr, qp, cq, device) при завершении.