Настоящий материал представляет собой обзор пятой главы книги Chris Fregly, «AI Systems Performance Engineering: Optimizing Model Training and Inference Workloads with GPUs, CUDA, and PyTorch», O'Reilly Media, 2026 (Copyright © 2026 Flux Capacitor, LLC).
Прямые цитаты из книги выделены отдельными блоками с пометкой «Цитата из книги». Остальной текст — систематизированное изложение содержания главы, сопоставление с актуальными данными о платформе, проверка прогнозов по открытым источникам, а также дополнительные замечания, выходящие за пределы текста главы. Цитаты приводятся в ограниченном объёме в целях обзора и критического анализа.
Пятая глава книги Fregly посвящена подсистеме хранения и конвейеру данных — тому слою, который определяет, будет ли GPU получать данные с достаточной скоростью. В главе последовательно рассматривается выбор накопителей, шаблоны доступа, тюнинг NVMe и файловых систем, технология GPUDirect Storage, Fire-Flyer File System от DeepSeek, распределённые файловые системы, конвейер DataLoader, инструменты DALI и NeMo Curator, а также методика непрерывного профилирования и разбор, что ограничивает — сеть или вычисления. Ниже последовательно изложены ключевые положения главы с уточнениями по актуальным данным.
«Подача данных на GPU так же важна для AI-нагрузок, как и сами вычисления. Рассмотрим сценарий с моделью на 100 триллионов параметров, обучаемой на тысячах GPU. Такая модель может обрабатывать миллиарды обучающих образцов, включая токены, изображения, аудио и видео. Это означает, что огромный объём данных должен считываться из хранилища и подаваться на GPU как можно быстрее. Если конвейер хранения медленный, GPU будут голодать и простаивать. Это приведёт к низкой утилизации, несмотря на все сложные коммуникационные оптимизации, которые мы обсуждали».
В главе подчёркивается, что подсистема хранения — это не «инфраструктурная деталь», а полноценный участник конвейера производительности. Пока данные не поступили на GPU, вычисления не начнутся. Чем мощнее ускоритель, тем острее становится этот разрыв: современные GPU способны обрабатывать данные быстрее, чем большинство хранилищ могут их подавать.
Практическое следствие, отмечаемое в главе: узкое место почти всегда смещается в сторону подсистемы хранения по мере роста вычислительной мощности GPU. Инженер, который смотрит только на FLOPS и загрузку SM, рискует пропустить главный источник простоя — ожидание данных.
Отдельно в главе указывается расчёт: для 72 GPU Blackwell NVL72, каждому из которых требуется 200 МБ/с обучающих данных, совокупная пропускная способность хранилища должна составлять 14–20 ГБ/с только для поддержания загрузки. Это нижняя граница; мультимодальные нагрузки требуют больше.
«Крупные задачи обучения моделей обычно требуют чтения огромных наборов данных. Обычно это порядка миллиардов или даже триллионов обучающих образцов для больших языковых моделей. Это диапазон терабайт текстовых данных для языковых моделей и петабайт изображений для визуальных моделей. В масштабе ваша система хранения должна стабильно обеспечивать массивную пропускную способность, чтобы успевать за тысячами и миллионами GPU, работающими месяцами».
«Размещение NVMe SSD внутри стоек — или использование NVMe over Fabrics (NVMe-oF) с топологиями коммутаторов на уровне стойки — минимизирует сетевые переходы и повышает стабильность производительности».
В главе формулируется иерархия локальности данных: локальный NVMe на том же узле → NVMe-oF в пределах стойки → сетевое хранилище (NFS, Lustre, GPFS) → объектное хранилище (S3). Каждый переход на уровень ниже добавляет задержку и снижает пропускную способность.
Практическое следствие: для распределённого обучения рекомендуется шардировать набор данных по узлам. Если у вас 100 ТБ данных и 10 узлов, вы можете предварительно разбить их на 10 частей по 10 ТБ и разместить на локальном хранилище каждого узла. Тогда загрузчик данных каждого узла читает только свой 10-ТБ шард, и сетевой трафик минимизируется. PyTorch DistributedSampler координирует процесс так, чтобы каждый ранг получал уникальный срез в каждой эпохе.
Отдельно отмечается необходимость масштабировать пропускную способность хранилища пропорционально числу GPU. Если каждый GPU требует 200 МБ/с, то на 72 GPU нужно 14 ГБ/с, на 720 GPU — 140 ГБ/с. Это определяет требования к архитектуре хранилища.
Данные должны располагаться как можно ближе к вычислениям. Иерархия: локальный NVMe → NVMe-oF в стойке → сетевое хранилище → объектное хранилище. Шардирование набора данных по узлам минимизирует сетевой трафик.
72 GPU × 200 МБ/с = 14,4 ГБ/с минимальной совокупной пропускной способности. Для мультимодальных нагрузок — в разы больше. Границы масштабирования: до 10 узлов — один NFS-сервер может справиться, свыше — параллельные ФС или объектные хранилища с кэшированием.
Локальный NVMe даёт минимальную задержку. NVMe-oF в стойке — компромисс между локальностью и гибкостью. Сетевое хранилище требует тщательного тюнинга (размеры блоков, кэширование, число серверов).
«GPU чрезвычайно быстро обрабатывают данные, но предпочитают, чтобы данные читались крупными непрерывными блоками. Аналогично, хранилище обеспечивает гораздо более высокую пропускную способность при крупных последовательных чтениях, чем при мелких случайных. Поэтому при подготовке наборов данных или схемы хранения старайтесь организовывать последовательный доступ насколько это возможно».
«Например, при обучении на изображениях избегайте хранения миллионов отдельных файлов изображений, так как это приведёт к множеству случайных обращений по всему диску. Рассмотрите вместо этого хранение их в нескольких крупных бинарных файлах (например, Arrow, TFRecord, Parquet), файлах базы данных, tar-архивах WebDataset или эквивалентах».
В главе последовательный доступ описывается как фундаментальное требование. NVMe SSD при последовательном чтении выдаёт 6–7 ГБ/с; при случайном — в 3–10 раз меньше. Для GPU разница ещё более критична, поскольку время ожидания данных напрямую влияет на утилизацию.
Практическое следствие: упаковка мелких образцов в крупные бинарные файлы — не «оптимизация», а базовое требование для эффективного I/O. WebDataset, TFRecord, Arrow, Parquet и .bin-форматы решают эту задачу. При работе с объектными хранилищами (S3) объединение мелких объектов в крупные сокращает количество GET-запросов на порядки.
Отдельно в главе указывается на важность размера блока чтения. Чтение по 1 МБ даёт пропускную способность на порядок выше, чем по 4 КБ, за счёт амортизации накладных расходов на системный вызов и инициализацию DMA. Рекомендуется настраивать read_ahead_kb в /sys/block/<device>/queue/ — например, до 4 МБ для потокового чтения крупных файлов.
NVMe SSD при последовательном чтении выдаёт 6–7 ГБ/с, при случайном — в 3–10 раз меньше
Последовательный доступ — фундаментальное требование. Случайные чтения снижают пропускную способность NVMe в 3–10 раз. Упаковка мелких образцов в крупные бинарные файлы (WebDataset, TFRecord, Arrow, Parquet) переводит нагрузку в последовательный режим.
Чтение блоками по 1 МБ даёт пропускную способность на порядок выше, чем по 4 КБ. NVMe SSD: 6–7 ГБ/с последовательно, в разы меньше при случайном доступе. Для объектных хранилищ: объединение мелких объектов в крупные сокращает количество GET-запросов на порядки.
Настраивайте read_ahead_kb для потокового чтения (например, до 4 МБ). Для случайного доступа используйте параллельные потоки с pread() или асинхронный io_uring.
«Современный Linux использует многопоточный планировщик блочного I/O blk-mq, который распределяет I/O по ядрам CPU. Для быстрых NVMe SSD вам может потребоваться настроить глубину очереди и количество очередей отправки. Обычно значения по умолчанию подходят, но если вы знаете, что ваша нагрузка сильно последовательна, вы можете использовать планировщик none».
«Устаревший планировщик completely fair queueing (CFQ) устарел. Современные ядра используют none или mq-deadline по умолчанию для NVMe. Эту настройку можно проверить через /sys/block/<device>/queue/scheduler».
В главе описываются три варианта планировщика для NVMe: none (минимальные накладные расходы, для низколатентных нагрузок), mq-deadline (компромисс с fairness), BFQ (для интерактивных задач). Рекомендация для AI-нагрузок — none или mq-deadline.
Практическое следствие: проверка и установка планировщика выполняются через /sys/block/nvme0n1/queue/scheduler. Для большинства AI-нагрузок значение none даёт минимальные накладные расходы.
Отдельно в главе рассматриваются опции монтирования. Для XFS и EXT4 рекомендуется noatime, чтобы отключить обновление времени доступа при каждом чтении. Для NFS — увеличенные размеры блоков (rsize=1048576, wsize=1048576), кэширование атрибутов (actimeo=60) и кэширование записей каталогов (lookupcache=pos).
Также отмечается, что кэш страниц Linux (page cache) может быть как другом, так и врагом. Если набор данных помещается в ОЗУ — это бесплатное ускорение. Если нет — кэш начинает «трэшиться», и CPU тратит время на управление кэшем вместо полезной работы. Для наборов данных, которые заведомо не помещаются в ОЗУ, рекомендуется использовать O_DIRECT для обхода кэша страниц.
| Планировщик | Когда использовать | Накладные расходы |
|---|---|---|
none | Низколатентные NVMe-нагрузки, крупные последовательные чтения | Минимальные |
mq-deadline | Смешанные нагрузки, где важен fairness | Низкие |
BFQ | Десктопы, интерактивные задачи | Средние |
NFS-сервер — частый кандидат в узкое место. Один сервер с одним NIC на 25 Гбит/с насытится уже при 8–10 GPU. Для кластеров побольше используйте несколько NFS-серверов с разделением данных по партициям или переходите на параллельную файловую систему.
Для NVMe рекомендуется планировщик none или mq-deadline. Устаревший CFQ не используется. Опции монтирования noatime и увеличенные блоки NFS снижают накладные расходы.
NVMe: read_ahead_kb по умолчанию 128 КБ; для потокового чтения рекомендуется 2–4 МБ. NFS: rsize/wsize до 1 МБ, actimeo=60, lookupcache=pos.
Проверяйте текущий планировщик через /sys/block/nvme0n1/queue/scheduler. Для данных, не помещающихся в ОЗУ, используйте O_DIRECT с выравниванием буфера и смещения по размеру блока устройства.
«GDS — это технология, позволяющая GPU читать данные напрямую с устройств хранения, или через сетевой стек хранения, без создания дополнительных копий в памяти CPU. Обычно, когда GPU хочет прочитать данные с NVMe SSD, данные сначала идут с SSD в память CPU. Затем вызов CUDA копирует данные из памяти CPU в память GPU».
«С GDS GPU может инициировать прямой доступ к памяти (DMA) к SSD или NIC, чтобы переместить данные в собственную память HBM. Это обходит дополнительную копию через путь CPU. GDS поддерживает локальные NVMe-устройства и удалённое хранилище с использованием NVMe-oF».
В главе GDS описывается как технология, устраняющая промежуточное звено — буфер в памяти хоста (bounce buffer). Обычный путь данных: NVMe → память CPU → CUDA-копия → память GPU. С GDS: NVMe → память GPU напрямую. CPU по-прежнему управляет (настраивает дескрипторы, инициализирует передачу), но не участвует в копировании.
Практическое следствие: GDS максимально эффективен, когда CPU перегружен копированиями (множество memcpy на высокой скорости). Если CPU справляется легко, прирост будет скромным — но GDS всё равно освободит циклы процессора для предобработки данных.
В главе тезис о 10–20% прироста пропускной способности приводится как консервативная нижняя оценка. Опубликованные бенчмарки демонстрируют более широкий разброс: VAST Data сообщает о 162 ГиБ/с на DGX A100 при прямом пути против ~33 ГБ/с через традиционный, Meta — о 3,8-кратном ускорении обучения за счёт GDS, в тестах OCI на gdsio — до 2,7–3,5× при крупных блоках. Фактический выигрыш зависит от загрузки CPU: чем сильнее CPU перегружен копированиями, тем выше эффект от GDS.
Отдельно указываются требования: современный GPU NVIDIA, драйвер с поддержкой nvidia-fs, CUDA Toolkit и совместимая файловая система. Поддерживаются локальные NVMe и NVMe-oF на XFS/EXT4 с O_DIRECT, NFS over RDMA, параллельные ФС (BeeGFS, WekaFS, IBM Storage Scale, VAST).
Приложение использует библиотеку cuFile с функциями cuFileRead, cuFileWrite и асинхронными вариантами cuFileReadAsync, cuFileWriteAsync для интеграции с CUDA-стримами.
Сравнение путей передачи данных от хранилища к GPU: традиционный DMA через host-память (bounce buffer) и прямой GPU pull с GPUDirect Storage
«NVIDIA поставляет инструмент gdsio для измерения пропускной способности GDS между диском и GPU. Это чрезвычайно полезно. При использовании GDS нередко можно увидеть улучшения порядка 10–20% по пропускной способности или более — особенно в сценариях с ограниченным CPU».
Пример бенчмарка gdsio: путь через CPU vs прямой GDS
gdsio: путь через CPU даёт 8,0 ГБ/с и 1,25 мс задержки; с GDS — 9,6 ГБ/с (+20%) и 1,00 мс (−20%). Такой прирост типичен для систем, где CPU был перегружен копированиями.
GDS максимально эффективен, когда CPU уже перегружен копированиями (множество memcpy на высокой скорости). Если CPU справляется легко, прирост будет скромным — но GDS всё равно освободит циклы процессора для предобработки данных.
GDS устраняет промежуточное звено — буфер в памяти хоста. GPU инициирует прямой DMA с NVMe или NIC в HBM. CPU управляет, но не копирует.
gdsio для измерения: 8,0 ГБ/с и 1,25 мс без GDS; 9,6 ГБ/с и 1,00 мс с GDS. Тезис «10–20%» — консервативная нижняя оценка; опубликованные бенчмарки показывают разброс от 20% до 3,8× в зависимости от нагрузки CPU. Требования: Ampere или новее, драйвер с nvidia-fs, совместимая ФС.
Используйте библиотеку cuFile для прямого чтения. Асинхронные варианты cuFileReadAsync/cuFileWriteAsync позволяют интегрировать дисковый I/O в CUDA-стримы для перекрытия с вычислениями.
«DeepSeek создала собственную файловую систему Fire-Flyer File System (3FS) с нуля. Она родилась из наблюдения, что AI-нагрузки выполняют огромное количество случайных чтений. Эти случайные чтения делают традиционное кэширование данных неэффективным — и даже контрпродуктивным — для LLM-обучения и инференса. Устраняя кэширование и используя прямой файловый I/O, 3FS гарантирует, что каждый запрос идёт прямо к NVMe SSD и избегает бесполезного управления кэшем».
В главе 3FS описывается как пример файловой системы, спроектированной специально под AI-нагрузки. Ключевые решения: прямой I/O без кэша страниц, RDMA-фабрика (InfiniBand или RoCE), шардирование и репликация метаданных по узлам для масштабируемости.
Практическое следствие: 3FS достигает пропускной способности в несколько терабайт в секунду за счёт координации I/O между узлами. Официальный README DeepSeek сообщает о 6,6 ТиБ/с (≈ 7,3 ТБ/с в десятичных байтах) на кластере из 180 storage-узлов, каждый с 2×200 Гбит/с InfiniBand и 16 × 14 ТиБ NVMe, плюс 500+ клиентских узлов. Эта цифра достигнута с фоновым трафиком обучения. Конфигурация кластера в книге (68 узлов, 10 × 16 ТБ NVMe, два NIC по 100 Гбит/с) отличается от официальной; при такой конфигурации физический потолок пропускной способности составлял бы около 1,7 ТБ/с, что не позволяет достичь 6,6 ТиБ/с. Дополнительно в официальных материалах DeepSeek указаны результаты GraySort (110,5 ТиБ за 30 минут 14 секунд, то есть 3,66 ТиБ/мин) и до 40 ГиБ/с на клиент в KVCache. Сравнение с Ceph (~1,1 ТБ/с) приводится в книге как авторская оценка, а не как официальное заявление DeepSeek.
Четыре ключевых компонента 3FS соединены через RDMA-фабрику (InfiniBand или RoCE) для минимизации участия CPU и копирования на стороне хоста
Если файловая система реализована через FUSE в пространстве пользователя, она не может обеспечить путь GDS. Для прямого DMA в память GPU нужен клиент ядра, интегрированный с nvidia-fs: NVMe, NVMe-oF, BeeGFS, WekaFS, IBM Storage Scale или VAST.
3FS — пример файловой системы, спроектированной под AI-нагрузки. Прямой I/O без кэша страниц, RDMA-фабрика, шардирование метаданных. Заявленная пропускная способность: 6,6 ТиБ/с.
6,6 ТиБ/с (подтверждено официальным README DeepSeek). Конфигурация кластера в книге (68 узлов, 10 × 16 ТБ NVMe, два NIC по 100 Гбит/с) отличается от официальной (180 storage-узлов, 2×200 Гбит/с IB, 16 × 14 ТиБ NVMe). Сравнение с Ceph (~1,1 ТБ/с) — авторская оценка книги. Дополнительно DeepSeek: GraySort 110,5 ТиБ за 30:14, до 40 ГиБ/с на клиент в KVCache.
FUSE не даёт GDS. Для прямого DMA в HBM нужен kernel-клиент с nvidia-fs. Если вам нужен GDS — выбирайте NVMe, NVMe-oF, BeeGFS, WekaFS, IBM Storage Scale или VAST.
«При обучении на нескольких узлах распространённая схема — использование общей файловой системы, такой как NFS-сервер, или параллельной файловой системы типа Lustre, GPFS, Ceph и т.д. С этими системами все узлы могут получать доступ к одному и тому же набору данных. Хотя это удобно, такие файловые системы могут стать узким местом, если их неправильно настроить».
«Хотя NFS прост в настройке, один NFS-сервер легко может стать узким местом по пропускной способности, если многие узлы читают одновременно. Если вам приходится использовать NFS, убедитесь, что сервер имеет несколько быстрых NIC. Также стоит рассмотреть использование нескольких NFS-серверов для разделения набора данных, чтобы каждый сервер обрабатывал свою партицию данных».
В главе рассматриваются параллельные файловые системы (Lustre, GPFS) и объектные хранилища (S3). Для Lustre описывается механизм страйпинга файлов по Object Storage Targets (OST): если каждый OST даёт 500 МБ/с, четыре OST обеспечат теоретический пик 2 ГБ/с на один файл.
Практическое следствие: для облачных кластеров Amazon FSx for Lustre предлагает готовый управляемый Lustre с кэшированием поверх S3. Однако в главе отмечается необходимость проверять реальный прирост производительности и обосновывать дополнительные затраты на кэш.
Для объектного хранилища S3 в главе рекомендуется предварительная загрузка данных на локальный NVMe перед стартом обучения, кэширующий слой (FSx for Lustre, Alluxio) или потоковое чтение с range-запросами и многопоточными GET. Использование специализированных инструментов (s5cmd, AWS S3 C++ SDK) позволяет получить максимальную производительность.
Отдельно рассматривается сжатие данных. Хранение сжатых данных снижает нагрузку на I/O за счёт дополнительной CPU- или GPU-работы на декодирование. Современные GPU (Blackwell) имеют встроенный движок декомпрессии (LZ4, Snappy, Deflate), который распаковывает данные прямо в HBM, не занимая SM. Это позволяет снизить нагрузку на CPU и ускорить подачу данных.
NFS-сервер — частый кандидат в узкое место. Один сервер с одним NIC на 25 Гбит/с насытится уже при 8–10 GPU. Для кластеров побольше используйте несколько NFS-серверов с разделением данных по партициям или переходите на параллельную файловую систему.
Если время распаковки превышает сэкономленное время чтения — сжатие не окупается. Всегда измеряйте обе стороны уравнения. Современные GPU с аппаратной декомпрессией сдвигают баланс в сторону сжатия.
NFS подходит для небольших кластеров (до 8–10 GPU на сервер). Для больших — параллельные ФС (Lustre, GPFS) или объектные хранилища с кэшированием. Сжатие данных снижает нагрузку на I/O, если декодирование не становится новым узким местом.
Lustre: страйпинг по 4 OST с 500 МБ/с каждый даёт 2 ГБ/с на файл. S3: задержка 20–50 мс на GET-запрос. Рекомендуется предварительная загрузка или кэширующий слой.
Используйте lfs setstripe для Lustre. Для S3 — s5cmd, AWS S3 C++ SDK, многопоточные range-запросы. Для сжатия — форматы с аппаратной поддержкой (nvJPEG, Decompression Engine на Blackwell).
«В дополнение к сырому I/O хранилища, конвейер предобработки и загрузки данных на CPU (или GPU) является критической частью общей производительности AI-нагрузки. Хорошо настроенный конвейер данных гарантирует, что GPU никогда не простаивают в ожидании новых данных. Также важно, чтобы нужный объём CPU-работы выполнялся параллельно для подачи данных на GPU».
«Использование нескольких рабочих процессов/потоков. Как упоминалось, фреймворки типа PyTorch DataLoader позволяют указать num_workers. Каждый воркер работает параллельно, чтобы получать и предобрабатывать данные. Обычно это отдельные процессы, чтобы избежать проблем с GIL Python. Главный процесс асинхронно получает батчи из очереди воркеров».
В главе описывается архитектура загрузчика данных в PyTorch: несколько воркеров (процессов, а не потоков, чтобы обойти GIL) параллельно читают и предобрабатывают данные, а главный процесс асинхронно получает готовые батчи из очереди. Рекомендуется использовать pin_memory=True для закрепления буферов в физической памяти и persistent_workers=True для сохранения воркеров между эпохами.
Практическое следствие: параметр num_workers определяет параллелизм загрузки. Слишком мало — GPU простаивает; слишком много — воркеры конкурируют за CPU и I/O. Для 72-ядерного Grace CPU можно задействовать больше воркеров, но с учётом закона убывающей отдачи.
Отдельно в главе рассматривается prefetch_factor — количество батчей, которые каждый воркер готовит заранее. По умолчанию 2. Если I/O имеет пиковые всплески, рекомендуется увеличить до 4–8. В очереди одновременно до num_workers × prefetch_factor готовых батчей.
Также отмечается, что предобработка может быть перенесена на GPU с помощью таких библиотек, как NVIDIA DALI, что снижает нагрузку на CPU и позволяет распараллелить декодирование и аугментацию с вычислениями.
| Момент | CPU / воркеры | GPU |
|---|---|---|
| T₀ | Загружает батч N, предобрабатывает | Вычисляет батч N−1 |
| T₁ | Загружает батч N+1 | Копирует батч N (H2D DMA), вычисляет |
| T₂ | Загружает батч N+2 | Вычисляет батч N+1 |
Воркеры PyTorch DataLoader — это процессы, а не потоки. Это не случайно: GIL Python делает многопоточную загрузку данных неэффективной для CPU-интенсивных задач. Если вы пишете свой загрузчик на чистом Python с тяжёлыми циклами — это красный флаг. Используйте векторизованные библиотеки (Hugging Face Tokenizers на Rust, TorchText на C++).
Для изоляции DataLoader временно установите num_workers=0 — это отключит предвыборку и покажет чистую стоимость загрузки без фонового планирования потоков. Но учтите: без GPU-ядер накладные расходы на запуск тоже снижаются, поэтому «чистая» скорость загрузки будет выглядеть заниженной.
DataLoader должен работать в нескольких процессах (не потоках) из-за GIL. pin_memory=True и persistent_workers=True — базовые настройки. num_workers и prefetch_factor подбираются эмпирически.
pin_memory=True улучшает производительность на 10–20% за счёт ускорения H2D-передач. prefetch_factor=2 по умолчанию; рекомендуется 4–8 при пиковых нагрузках.
Сравнивайте время загрузки и время вычислений. Если загрузка занимает 40 мс, а вычисления — 20 мс, GPU простаивает 67% времени. После оптимизации загрузка может упасть до 5 мс.
«Для сложной или тяжёлой предобработки данных NVIDIA предоставляет Data Loading Library (DALI). DALI ускоряет обработку данных, либо перемещая её на GPU, либо используя оптимизированный C++ код на CPU. Это особенно полезно для изображений и видео, где декодирование и аугментация могут выиграть от GPU-ускорения».
«NeMo Curator помогает подготавливать крупные мультимодальные наборы данных для обучения LLM. Он полезен при работе с терабайтами данных из разных источников. Curator поддерживает такие этапы обработки, как очистка, токенизация и перемешивание».
В главе DALI описывается как инструмент переноса декодирования и аугментации на GPU. Конвейер DALI определяется декларативно как граф операторов. Используются собственные пулы потоков и очереди — без GIL. Для задач классификации, детекции и сегментации есть готовые конвейеры.
Практическое следствие: при использовании DALI загрузка CPU падает с 800% (8 ядер на 100%) до 200% (2 ядра для чтения файлов), а декодирование и аугментация выполняются на GPU параллельно с вычислениями. Однако если использовать DALI только для декодирования JPEG, а потом передавать сырые пиксели обратно на CPU для аугментаций — дополнительные копии H2D-D2H съедят весь выигрыш.
NeMo Curator описывается как фреймворк для офлайн-подготовки данных к обучению LLM. Он выполняет очистку, фильтрацию по качеству, дедупликацию, токенизацию, перемешивание и упаковку в крупные файлы (.bin + .idx). Результат — подготовленный набор данных, который обучающий конвейер читает без парсинга JSON и токенизации на лету.
Отдельно в главе отмечается, что предобработка данных до обучения экономит время и позволяет использовать более простые и быстрые загрузчики во время обучения.
Если использовать DALI только для декодирования JPEG, а потом передавать сырые пиксели обратно на CPU для аугментаций и сборки батчей — дополнительные копии H2D-D2H съедят весь выигрыш. Держите данные на GPU на всём пути: декодирование → аугментация → обучение. Альтернатива — TorchVision V2 transforms на GPU или кастомные CUDA-ядра.
Подготавливайте данные до обучения. Офлайн-предобработка стоит времени один раз, а ускоряет каждый запуск. Рассмотрите хранение N перемешанных копий для N эпох — это сэкономит на перемешивании во время выполнения ценой дискового пространства.
DALI переносит декодирование и аугментацию на GPU, снижая нагрузку на CPU. NeMo Curator выполняет офлайн-подготовку данных для LLM, упаковывая их в крупные файлы.
DALI снижает загрузку CPU с 800% до 200% при обработке изображений. Curator упаковывает данные в .bin + .idx, которые читаются memory-mappable.
Держите данные на GPU на всём пути: декодирование → аугментация → обучение. Офлайн-предобработка через NeMo Curator ускоряет каждый запуск обучения.
«Инженерия производительности — это итеративный процесс. Чтобы гарантировать, что ваше распределённое приложение обучения или инференса остаётся эффективным по мере масштабирования или модификации, вы должны принять непрерывный рабочий процесс профилирования и тюнинга. Это означает регулярный сбор данных о производительности, выявление узких мест, применение оптимизаций и повторное измерение».
«Со временем обновления аппаратного и программного обеспечения изменят оптимальные настройки, поэтому вам нужно непрерывно профилировать и настраивать. Чтобы опережать это, команды, ориентированные на производительность, часто поддерживают панели мониторинга производительности для отслеживания таких метрик, как samples/sec с течением времени».
В главе описывается методология непрерывного профилирования: установить базовый уровень, профилировать multi-GPU запуск для выявления узких мест, углубиться в конкретные ядра при необходимости, определить причину, применить исправления, повторно измерить после каждого изменения. Рекомендуется менять одну-две вещи за раз, чтобы понимать, что именно помогло.
Практическое следствие: автоматизированные ночные запуски профилирования позволяют ловить регрессии, вызванные обновлениями драйверов, изменениями кода или ростом набора данных. Панели мониторинга с метриками samples/sec помогают отслеживать производительность во времени.
Отдельно в главе рассматриваются инструменты: iostat, iotop, nvme-cli для уровня ОС и устройств; Nsight Systems для сквозной временной шкалы; Nsight Compute для эффективности конкретных ядер; PyTorch Profiler для трейсинга операций; DCGM для метрик GPU.
Также в главе описывается методика диагностики, что ограничивает систему — сеть или вычисления: изменение размера батча при фиксированном объёме коммуникаций. Если пропускная способность сети остаётся неизменной при уменьшении батча — нагрузка ограничена сетью. Если падает — вычислениями.
| Инструмент | Что показывает | Уровень |
|---|---|---|
iostat, iotop | Загрузка дисков, IOPS, задержки | ОС |
nvme-cli | Состояние NVMe-устройств, SMART | Устройство |
Nsight Systems (nsys) | Сквозная временная шкала: CPU + GPU + I/O | Система |
Nsight Compute (ncu) | Эффективность конкретных CUDA-ядер | Ядро |
| PyTorch Profiler | Трейс операций, DataLoader, H2D-копирования | Фреймворк |
| DCGM | Метрики GPU: загрузка, память, температура | GPU |
Профилирование — итеративный процесс. Меняйте одну-две вещи за раз. Регулярные ночные запуски ловят регрессии. Панели мониторинга отслеживают производительность во времени.
8 GPU дают только 5-кратное ускорение (вместо 8-кратного) — эффективность 62,5%, нужно искать узкое место. При ограничении сетью пропускная способность сети не меняется при уменьшении батча.
Используйте Nsight Systems для сквозной шкалы, Nsight Compute для ядер, iostat для дисков. Для диагностики сети и вычислений — эксперимент с размером батча.
«Чтобы понять, что ограничивает систему — вычисления или коммуникации — можно изменить соотношение вычислений к коммуникациям и посмотреть, как это повлияет на достигнутую пропускную способность сети. Рассмотрим профилирование обратного прохода обучения. В данный момент ваш gradient all-reduce использует только 60 ГБ/с из 100 ГБ/с NIC».
«Если achieved network utilization упадёт ниже 60 ГБ/с, скажем, до 40 ГБ/с, значит GPU не успевают производить данные достаточно быстро. В этом случае сеть простаивает в ожидании данных от GPU. Следовательно, вычисления — ограничивающий фактор, а не сеть».
В главе описывается простой и эффективный метод диагностики: зафиксировать объём коммуникаций (all-reduce зависит от числа параметров, а не от размера батча) и изменять размер батча. Если при уменьшении батча пропускная способность сети не меняется — нагрузка ограничена сетью. Если падает — вычислениями.
Практическое следствие: этот метод не требует сложных профилировщиков и может быть выполнен с помощью стандартных инструментов мониторинга. Достаточно измерить пропускную способность NIC или NVLink при разных размерах батча.
Также в главе отмечается важность измерения времени простоя GPU в ожидании данных. Сравнение времени загрузки и времени вычислений показывает, какая часть времени GPU простаивает.
Изменение размера батча при фиксированном объёме all-reduce
Метод диагностики: зафиксировать объём коммуникаций (all-reduce зависит от числа параметров) и изменять размер батча. Если NIC не меняется — нагрузка ограничена сетью; если падает — вычислениями.
Пример: all-reduce использует 60 ГБ/с из 100 ГБ/с NIC. Если при уменьшении батча пропускная способность остаётся 60 ГБ/с — сеть. Если падает до 40 ГБ/с — вычисления.
Для диагностики не нужны сложные профилировщики. Достаточно измерить пропускную способность NIC или NVLink при разных размерах батча.
Пятая глава книги Fregly писалась в 2025 году. С тех пор многое изменилось. Ниже представлено сопоставление прогнозов главы с открытыми данными и уточнение цифр.
В главе указано 14 ГБ/с для 72 GPU. Это соответствует действительности: 72 × 200 МБ/с = 14,4 ГБ/с. Рекомендация использовать none или mq-deadline для NVMe соответствует современным практикам. Тезис о 10–20% прироста от GDS является консервативной нижней оценкой; опубликованные бенчмарки показывают более широкий разброс в зависимости от загрузки CPU.
Книга написана в момент, когда GB200 NVL72 был актуальной платформой. Blackwell добавляет встроенный движок декомпрессии (LZ4, Snappy, Deflate), который распаковывает данные прямо в HBM, не занимая SM. Это меняет баланс в пользу сжатия данных на диске — декодирование перестаёт быть узким местом. В главе этот аспект упоминается, но без деталей о новых возможностях Blackwell.
Цифра 6,6 ТиБ/с подтверждается официальным README DeepSeek. Однако конфигурация кластера в книге (68 узлов, 10 × 16 ТБ NVMe, два NIC по 100 Гбит/с) отличается от официальной (180 storage-узлов, 2×200 Гбит/с InfiniBand, 16 × 14 ТиБ NVMe, 500+ клиентских узлов); при книжной конфигурации физический потолок составлял бы ~1,7 ТБ/с, что не позволяет достичь 6,6 ТиБ/с. Цифра подтверждена, конфигурация — нет. Сравнение с Ceph (~1,1 ТБ/с) — авторская оценка книги, а не заявление DeepSeek. Дополнительно DeepSeek сообщает: GraySort 110,5 ТиБ за 30 минут 14 секунд, до 40 ГиБ/с на клиент в KVCache.
Рекомендации по pin_memory=True, persistent_workers=True и подбору num_workers подтверждаются практикой. Прирост производительности на 10–20% при включении pin_memory — реалистичная оценка. prefetch_factor=4–8 рекомендуется для нагрузок с пиковым I/O.
DALI активно используется для CV-задач, NeMo Curator — для подготовки LLM-датасетов. Снижение загрузки CPU с 800% до 200% при использовании DALI — типичный результат. NeMo Curator упаковывает данные в .bin + .idx, которые читаются memory-mappable.
«Если конвейер хранения медленный, GPU будут голодать и простаивать».
Подтверждён. Практика показывает, что плохо настроенный DataLoader может тратить впустую 30–50% времени GPU. Оптимизация конвейера ввода-вывода часто даёт больше, чем алгоритмические оптимизации ядер.
«GPU предпочитают, чтобы данные читались крупными непрерывными блоками».
Подтверждён. Упаковка мелких файлов в крупные форматы (WebDataset, TFRecord, Arrow, Parquet) стала стандартом. NVMe SSD при последовательном чтении выдаёт 6–7 ГБ/с, при случайном — в 3–10 раз меньше.
«При использовании GDS нередко можно увидеть улучшения порядка 10–20% по пропускной способности».
Подтверждён как консервативная нижняя оценка. Пример gdsio в главе показывает 8,0 → 9,6 ГБ/с (+20%) и 1,25 → 1,00 мс (−20%). Опубликованные бенчмарки показывают более широкий разброс: VAST Data — 162 ГиБ/с на DGX A100 против ~33 ГБ/с традиционного пути; Meta — 3,8× ускорение обучения; OCI — до 2,7–3,5×. Выигрыш зависит от загрузки CPU.
«3FS достигает пропускной способности в несколько терабайт в секунду».
Подтверждён. DeepSeek сообщает о 6,6 ТиБ/с. Однако конфигурация кластера в книге (68 узлов, 10 × 16 ТБ NVMe, два NIC по 100 Гбит/с) отличается от официальной (180 storage-узлов, 2×200 Гбит/с IB, 16 × 14 ТиБ NVMe). Сравнение с Ceph (~1,1 ТБ/с) — авторская оценка книги.
«Если файловая система реализована через FUSE в пространстве пользователя, она не может обеспечить путь GDS».
Подтверждён с оговоркой. В целом верно: GDS требует kernel-клиента с nvidia-fs. Однако существуют гибридные решения, где FUSE используется для метаданных, а данные идут через kernel-клиент. Это редкие конфигурации, и для большинства случаев утверждение остаётся справедливым.
Глава описывает принципы и инструменты, но оставляет за скобками несколько аспектов, которые проявились в реальной эксплуатации.
Глава упоминает, что современные GPU добавляют on-die Decompression Engine (LZ4, Snappy, Deflate), но не детализирует, насколько это меняет баланс. На Blackwell декомпрессия выполняется аппаратно и не занимает SM, что делает хранение данных в сжатом виде гораздо более привлекательным. Если раньше распаковка на CPU могла стать узким местом, то теперь она практически бесплатна с точки зрения GPU-циклов. Это позволяет снизить требования к пропускной способности хранилища без потери производительности.
Глава упоминает NVMe-oF, но не рассматривает топологию коммутаторов и их влияние на производительность. В реальных развёртываниях выбор между RoCEv2 и InfiniBand, настройка PFC/ECN, размеры буферов и глубина очередей на коммутаторах могут влиять на пропускную способность сильнее, чем выбор накопителей. Для больших кластеров рекомендуется выделенная сеть хранения.
Глава фокусируется на аппаратных и файловых системах, но не рассматривает программно-определяемые хранилища (Ceph, MinIO, WekaFS). Некоторые из них (WekaFS) поддерживают GDS и могут быть хорошим выбором для гибридных и облачных развёртываний. Стоит изучить их как альтернативу традиционным NFS и Lustre.
Глава не рассматривает экономику хранения. NVMe SSD стоят значительно дороже HDD и объектных хранилищ. Для больших наборов данных часто применяется многоуровневая стратегия: «горячие» данные на NVMe, «тёплые» на HDD или сетевом хранилище, «холодные» в объектном хранилище. Оптимизация не только скорости, но и стоимости хранения — часть работы performance-инженера.
Глава не затрагивает вопросы безопасности: шифрование данных, изоляцию I/O между арендаторами, контроль доступа. В мультитенантных кластерах эти аспекты могут влиять на выбор хранилища и его настройку. NVMe-oF с шифрованием, SED (Self-Encrypting Drives) и контроль доступа — темы, требующие отдельного рассмотрения.
Глава упоминает Amazon S3 и FSx for Lustre, но не рассматривает специфику других облачных хранилищ (Google Cloud Storage, Azure Blob Storage). У каждого есть свои особенности: разные модели консистентности, разные лимиты на запросы, разные API. При выборе облачного хранилища для AI-нагрузок стоит тестировать их реальную производительность, а не полагаться на маркетинговые цифры.
num_workers, pin_memory, persistent_workers, prefetch_factor — эти параметры нужно настраивать эмпирически. Для 72-ядерного Grace CPU можно использовать больше воркеров, но с учётом закона убывающей отдачи.read_ahead_kb для потокового чтения.none или mq-deadline. Опции монтирования noatime. O_DIRECT для данных, не помещающихся в ОЗУ.nvidia-fs и совместимая ФС.pin_memory=True, persistent_workers=True, подбор num_workers и prefetch_factor.iostat, DCGM. Меняйте по одному параметру за раз.Пятая глава книги Fregly — систематический разбор подсистемы хранения и конвейера данных для AI-нагрузок. Ключевая идея: GPU не могут работать быстрее, чем им подают данные. Оптимизация ввода-вывода — от выбора накопителей до настройки DataLoader — может дать больше, чем оптимизация ядер. GDS, 3FS, DALI, NeMo Curator — это инструменты, которые позволяют выжать максимум из подсистемы хранения. По мере роста моделей и кластеров роль эффективного I/O будет только возрастать.
Проверка прогнозов показывает: книга оказалась точной в своих предсказаниях. GDS даёт 10–30% прироста, 3FS показывает multi-TB/s, DataLoader-настройки работают. Аппаратная декомпрессия на Blackwell меняет баланс в пользу сжатия данных. Главный вывод остаётся неизменным: поддерживайте конвейер заполненным — от диска до HBM.
Источники: Chris Fregly, «AI Systems Performance Engineering» (O'Reilly, 2026), Глава 5 — цитаты приводятся в ограниченном объёме в целях обзора и критического анализа. Документация NVIDIA GDS, NVIDIA DALI, NVIDIA NeMo Curator; DeepSeek 3FS; VAST Data; Meta; Oracle Cloud Infrastructure; MLPerf Storage; Uptime Institute; технические отчёты DeepSeek-V3.
This material is a review of Chapter 5 of Chris Fregly, «AI Systems Performance Engineering: Optimizing Model Training and Inference Workloads with GPUs, CUDA, and PyTorch», O'Reilly Media, 2026 (Copyright © 2026 Flux Capacitor, LLC).
Direct quotes from the book are marked with separate blocks labeled «Quote from the book». The rest of the text is a systematized exposition of the chapter's content, a comparison with current data about the platform, verification of forecasts against open sources, and additional observations that go beyond the chapter's text. Quotes are provided in limited volume for review and critical analysis purposes.
Chapter 5 of Fregly's book is dedicated to the storage subsystem and data pipeline — the layer that determines whether the GPU will receive data fast enough. The chapter consistently examines storage selection, access patterns, NVMe and filesystem tuning, GPUDirect Storage, DeepSeek's Fire-Flyer File System, distributed filesystems, the DataLoader pipeline, DALI and NeMo Curator tools, as well as continuous profiling methodology and diagnosis of what limits the system — network or compute. The key points of the chapter are presented below, along with clarifications based on current data.
«Feeding data to the GPUs is as important as the compute itself for AI workloads. Consider a scenario with a 100-trillion-parameter model training on thousands of GPUs. Such a model might process billions of training samples, including tokens, images, audio, video, etc. This means that an enormous amount of data must be read from storage and fed to the GPUs as quickly as possible. If the storage pipeline is slow, the GPUs will starve and sit idle. This results in low utilization despite the sophisticated communication optimizations that we've discussed».
The chapter emphasizes that the storage subsystem is not an «infrastructure detail» but a full participant in the performance pipeline. Until data reaches the GPU, computation cannot begin. The more powerful the accelerator, the sharper this gap becomes: modern GPUs can process data faster than most storage systems can deliver it.
The practical consequence noted in the chapter: the bottleneck almost always shifts toward the storage subsystem as GPU compute power grows. An engineer who looks only at FLOPS and SM utilization risks missing the main source of idle time — waiting for data.
The chapter separately provides the calculation: for 72 Blackwell GPUs in an NVL72 rack, each requiring 200 MB/s of training data, aggregate storage throughput must be 14–20 GB/s just to maintain load. This is the lower bound; multimodal workloads require more.
«Large model training jobs usually need to read huge datasets. It's common to have on the order of billions or even trillions of training samples for large language models. This is in the range of terabytes of text data for language models and petabytes of images for vision models. At ultra scale, your storage system must consistently provide massive throughput to keep up with the thousands and millions of GPUs potentially running for months at a time».
«Colocating NVMe SSDs within racks—or using NVMe over Fabrics (NVMe-oF) with rack-local switch topologies—minimizes network hops and improves performance consistency».
The chapter formulates a data locality hierarchy: local NVMe on the same node → NVMe-oF within the rack → network storage (NFS, Lustre, GPFS) → object storage (S3). Each transition down a level adds latency and reduces bandwidth.
Practical consequence: for distributed training, sharding the dataset across nodes is recommended. If you have 100 TB of data and 10 nodes, you can pre-split it into 10 parts of 10 TB each and place them on the local storage of each node. Then each node's data loader reads only its own 10-TB shard, and network traffic is minimized. PyTorch DistributedSampler coordinates the process so each rank gets a unique slice per epoch.
The chapter separately notes the need to scale storage throughput proportionally to the number of GPUs. If each GPU requires 200 MB/s, then 72 GPUs need 14 GB/s, and 720 GPUs need 140 GB/s. This determines storage architecture requirements.
Data should be located as close to compute as possible. Hierarchy: local NVMe → NVMe-oF in the rack → network storage → object storage. Sharding the dataset across nodes minimizes network traffic.
72 GPUs × 200 MB/s = 14.4 GB/s minimum aggregate throughput. For multimodal workloads — many times more. Scaling boundaries: up to 10 nodes — a single NFS server may suffice; beyond — parallel filesystems or object storage with caching.
Local NVMe provides minimum latency. NVMe-oF in the rack is a compromise between locality and flexibility. Network storage requires careful tuning (block sizes, caching, number of servers).
«GPUs are extremely fast at crunching data, but they prefer that the data be read in large contiguous chunks for efficiency. Similarly, storage measures much higher throughput for large, sequential reads than for small, random reads. As such, when preparing your datasets or storage layout, try to arrange for sequential access as much as possible».
«For instance, when training with images, avoid storing millions of individual image files since this will lead to lots of random seeks all over the disk. Consider, instead, storing them in a few large binary (e.g., Arrow, TFRecord, or Parquet) files, database files, WebDataset tar files, or equivalents».
The chapter describes sequential access as a fundamental requirement. An NVMe SSD delivers 6–7 GB/s sequentially; randomly, it delivers 3–10× less. For GPUs the difference is even more critical, since data wait time directly affects utilization.
Practical consequence: packing small samples into large binary files is not an «optimization» but a basic requirement for efficient I/O. WebDataset, TFRecord, Arrow, Parquet, and .bin formats solve this task. When working with object storage (S3), merging small objects into large ones cuts the number of GET requests by orders of magnitude.
The chapter separately notes the importance of read block size. Reading in 1 MB chunks gives an order of magnitude more throughput than 4 KB, by amortizing syscall and DMA initialization overhead. It recommends tuning read_ahead_kb in /sys/block/<device>/queue/ — for example, to 4 MB for streaming reads of large files.
NVMe SSD delivers 6–7 GB/s sequentially, 3–10× less randomly
Sequential access is a fundamental requirement. Random reads reduce NVMe throughput by 3–10×. Packing small samples into large binary files (WebDataset, TFRecord, Arrow, Parquet) shifts the load to sequential mode.
Reading in 1 MB blocks gives an order of magnitude more throughput than 4 KB. NVMe SSD: 6–7 GB/s sequentially, many times less randomly. For object storage: merging small objects into large ones cuts GET requests by orders of magnitude.
Tune read_ahead_kb for streaming reads (e.g., up to 4 MB). For random access, use parallel threads with pread() or asynchronous io_uring.
«Modern Linux uses a multiqueue block I/O scheduler, blk-mq, that spreads I/O across the CPU cores. For fast NVMe SSDs, you might need to tune the queue depths and number of submission queues. Usually the defaults are fine, but if you know that your workload is heavily sequential, you might use the "none" I/O scheduler».
«The legacy completely fair queueing (CFQ) scheduler is obsolete. Modern kernels use the none or mq-deadline multiqueue scheduler by default for NVMe. This setting can be checked using /sys/block/<device>/queue/scheduler».
The chapter describes three scheduler options for NVMe: none (minimal overhead, for low-latency workloads), mq-deadline (compromise with fairness), BFQ (for interactive tasks). The recommendation for AI workloads is none or mq-deadline.
Practical consequence: checking and setting the scheduler is done through /sys/block/nvme0n1/queue/scheduler. For most AI workloads, the value none gives minimal overhead.
The chapter separately examines mount options. For XFS and EXT4, noatime is recommended to disable access-time updates on every read. For NFS — increased block sizes (rsize=1048576, wsize=1048576), attribute caching (actimeo=60), and directory-entry caching (lookupcache=pos).
It also notes that the Linux page cache can be both a friend and a foe. If the dataset fits in RAM — this is free acceleration. If not — the cache starts thrashing, and the CPU spends time managing the cache instead of doing useful work. For datasets that definitely don't fit in RAM, using O_DIRECT to bypass the page cache is recommended.
| Scheduler | When to use | Overhead |
|---|---|---|
none | Low-latency NVMe workloads, large sequential reads | Minimal |
mq-deadline | Mixed workloads where fairness matters | Low |
BFQ | Desktops, interactive tasks | Medium |
The NFS server is a frequent bottleneck candidate. A single server with a single 25 Gb/s NIC will saturate at 8–10 GPUs. For larger clusters, use multiple NFS servers with partitioned data or move to a parallel filesystem.
For NVMe, the none or mq-deadline scheduler is recommended. The obsolete CFQ is not used. Mount options noatime and larger NFS blocks reduce overhead.
NVMe: read_ahead_kb defaults to 128 KB; for streaming reads 2–4 MB is recommended. NFS: rsize/wsize up to 1 MB, actimeo=60, lookupcache=pos.
Check the current scheduler via /sys/block/nvme0n1/queue/scheduler. For data that doesn't fit in RAM, use O_DIRECT with buffer and offset alignment to the device block size.
«GDS is a feature that allows GPUs to read data directly from storage devices, or through the network storage stack, without creating extra copies in CPU memory. Normally, when a GPU wants to read data from an NVMe SSD, the data first goes from SSD to CPU memory. Then a CUDA call copies the data from CPU memory to GPU memory».
«With GDS, the GPU can initiate a direct memory access (DMA) against the SSD or NIC to move the data into its own HBM memory. This bypasses the extra copy through the CPU's path. GDS supports local NVMe devices and remote storage using NVMe-oF».
The chapter describes GDS as a technology that eliminates the intermediate link — the bounce buffer in host memory. The usual data path: NVMe → CPU memory → CUDA copy → GPU memory. With GDS: NVMe → GPU memory directly. The CPU still orchestrates (sets up descriptors, initiates transfers), but doesn't participate in copying.
Practical consequence: GDS is most effective when the CPU is already overloaded with copies (many high-speed memcpy calls). If the CPU is coping easily, gains will be modest — but GDS will still free CPU cycles for data preprocessing.
The chapter presents the 10–20% throughput gain thesis as a conservative lower bound. Published benchmarks show a wider spread: VAST Data reports 162 GiB/s on DGX A100 on the direct path versus ~33 GB/s through the traditional one, Meta — a 3.8× training speedup with GDS, in OCI tests on gdsio — up to 2.7–3.5× with large blocks. The actual gain depends on CPU load: the more the CPU is overloaded with copies, the higher the GDS effect.
Requirements are separately noted: a modern NVIDIA GPU, a driver with nvidia-fs support, the CUDA Toolkit, and a compatible filesystem. Supported: local NVMe and NVMe-oF on XFS/EXT4 with O_DIRECT, NFS over RDMA, parallel filesystems (BeeGFS, WekaFS, IBM Storage Scale, VAST).
The application uses the cuFile library with cuFileRead, cuFileWrite and asynchronous cuFileReadAsync, cuFileWriteAsync for integration with CUDA streams.
Comparison of data paths from storage to GPU: traditional DMA through host memory (bounce buffer) and direct GPU pull with GPUDirect Storage
«NVIDIA provides a tool called gdsio to benchmark GDS throughput between disk and GPU. This is super useful. When using GDS, it's not uncommon to see improvements on the order of 10%–20% in throughput or more—especially in CPU-constrained scenarios».
Example gdsio benchmark: CPU path vs direct GDS
gdsio benchmark: the CPU path gives 8.0 GB/s and 1.25 ms latency; with GDS — 9.6 GB/s (+20%) and 1.00 ms (−20%). Such a gain is typical for systems where the CPU was overloaded with copies.
GDS is most effective when the CPU is already overloaded with copies (many high-speed memcpy calls). If the CPU is coping easily, gains will be modest — but GDS will still free CPU cycles for data preprocessing.
GDS eliminates the intermediate link — the bounce buffer in host memory. The GPU initiates direct DMA from NVMe or NIC to HBM. The CPU orchestrates but doesn't copy.
gdsio for measurement: 8.0 GB/s and 1.25 ms without GDS; 9.6 GB/s and 1.00 ms with GDS. The «10–20%» thesis is a conservative lower bound; published benchmarks show a spread from 20% to 3.8× depending on CPU load. Requirements: Ampere or newer, driver with nvidia-fs, compatible filesystem.
Use the cuFile library for direct reads. Asynchronous variants cuFileReadAsync/cuFileWriteAsync allow disk I/O to be integrated into CUDA streams for overlap with compute.
«DeepSeek created a custom, open source filesystem called Fire-Flyer File System (3FS) from the ground up. It was born out of their observation that AI workloads perform massive numbers of random reads. These random reads make conventional read data caching ineffective—and even counterproductive—for LLM training and inference workloads. By eliminating caching and employing direct file I/O, 3FS ensures that every request goes straight to the NVMe SSD device and avoids wasteful cache management».
The chapter describes 3FS as an example of a filesystem designed specifically for AI workloads. Key decisions: direct I/O without page cache, RDMA fabric (InfiniBand or RoCE), metadata sharding and replication across nodes for scalability.
Practical consequence: 3FS achieves multi-terabyte-per-second throughput by coordinating I/O across nodes. The official DeepSeek README reports 6.6 TiB/s (≈ 7.3 TB/s in decimal bytes) on a cluster of 180 storage nodes, each with 2×200 Gb/s InfiniBand and 16 × 14 TiB NVMe, plus 500+ client nodes. This figure is achieved with background training traffic. The cluster configuration in the book (68 nodes, 10 × 16 TB NVMe, two 100 Gb/s NICs) differs from the official one; with such a configuration, the physical throughput ceiling would be about 1.7 TB/s, which does not allow reaching 6.6 TiB/s. Additionally, DeepSeek official materials report GraySort results (110.5 TiB in 30 minutes 14 seconds, i.e., 3.66 TiB/min) and up to 40 GiB/s per client in KVCache. The comparison with Ceph (~1.1 TB/s) is given in the book as the author's estimate, not as an official DeepSeek statement.
The four key components of 3FS are connected through an RDMA fabric (InfiniBand or RoCE) to minimize CPU involvement and host-side copies
If a filesystem is implemented via FUSE in user space, it cannot provide a GDS path. Direct DMA to GPU memory needs a kernel client integrated with nvidia-fs: NVMe, NVMe-oF, BeeGFS, WekaFS, IBM Storage Scale, or VAST.
3FS is an example of a filesystem designed for AI workloads. Direct I/O without page cache, RDMA fabric, metadata sharding. Claimed throughput: 6.6 TiB/s.
6.6 TiB/s (confirmed by the official DeepSeek README). The cluster configuration in the book (68 nodes, 10 × 16 TB NVMe, two 100 Gb/s NICs) differs from the official one (180 storage nodes, 2×200 Gb/s IB, 16 × 14 TiB NVMe). The comparison with Ceph (~1.1 TB/s) is the book author's estimate. Additionally DeepSeek: GraySort 110.5 TiB in 30:14, up to 40 GiB/s per client in KVCache.
FUSE does not provide GDS. For direct DMA to HBM, a kernel client with nvidia-fs is needed. If you need GDS — choose NVMe, NVMe-oF, BeeGFS, WekaFS, IBM Storage Scale, or VAST.
«When training on multiple nodes, a common setup is to use a shared filesystem like an NFS server, or a parallel filesystem like Lustre, GPFS, Ceph, etc. With these systems, all nodes can access the same dataset. While convenient, these filesystems can become a bottleneck if not configured properly».
«Though it's simple to set up, a single NFS server can easily become a throughput bottleneck if many nodes are reading at once. If you must use NFS, ensure the server has multiple fast NICs. You should also consider using multiple NFS servers to split up the dataset so that each server handles a partition of data».
The chapter examines parallel filesystems (Lustre, GPFS) and object storage (S3). For Lustre, the mechanism of striping files across Object Storage Targets (OST) is described: if each OST delivers 500 MB/s, four OSTs give a theoretical peak of 2 GB/s per file.
Practical consequence: for cloud clusters, Amazon FSx for Lustre offers a managed Lustre with caching on top of S3. However, the chapter notes the need to verify actual performance improvement and justify the additional cache costs.
For S3 object storage, the chapter recommends pre-loading data to local NVMe before training starts, a caching layer (FSx for Lustre, Alluxio), or streaming with range requests and multithreaded GETs. Specialized tools (s5cmd, AWS S3 C++ SDK) allow maximum performance.
Data compression is examined separately. Storing compressed data reduces I/O load at the cost of extra CPU or GPU work for decoding. Modern GPUs (Blackwell) have a built-in decompression engine (LZ4, Snappy, Deflate) that unpacks data directly into HBM without occupying SMs. This reduces CPU load and accelerates data delivery.
The NFS server is a frequent bottleneck candidate. A single server with a single 25 Gb/s NIC will saturate at 8–10 GPUs. For larger clusters, use multiple NFS servers with partitioned data or move to a parallel filesystem.
If decoding time exceeds the saved read time, compression doesn't pay off. Always measure both sides of the equation. Modern GPUs with hardware decompression shift the balance in favor of compression.
NFS is suitable for small clusters (up to 8–10 GPUs per server). For larger ones — parallel filesystems (Lustre, GPFS) or object storage with caching. Data compression reduces I/O load if decoding doesn't become the new bottleneck.
Lustre: striping across 4 OSTs with 500 MB/s each gives 2 GB/s per file. S3: 20–50 ms latency per GET request. Pre-loading or a caching layer is recommended.
Use lfs setstripe for Lustre. For S3 — s5cmd, AWS S3 C++ SDK, multithreaded range requests. For compression — formats with hardware support (nvJPEG, Decompression Engine on Blackwell).
«In addition to raw storage I/O, the preprocessing and data loading pipeline on the CPU (or GPU) is a critical part of overall AI workload performance. A well-tuned data pipeline ensures that GPUs are never idle waiting for new data. Additionally, it's important that the right amount of CPU work is being done in parallel to feed the GPU beast».
«Use multiple worker processes/threads. As mentioned, frameworks like PyTorch DataLoader let you specify num_workers. Each worker runs in parallel to fetch and preprocess data. Usually these are separate processes to avoid Python GIL issues. The main process asynchronously fetches batches from the worker processes using a queue».
The chapter describes the PyTorch data loader architecture: multiple workers (processes, not threads, to bypass the GIL) read and preprocess data in parallel, while the main process asynchronously receives ready batches from the queue. It recommends pin_memory=True to pin buffers in physical memory and persistent_workers=True to keep workers alive between epochs.
Practical consequence: the num_workers parameter controls loading parallelism. Too few — the GPU idles; too many — workers contend for CPU and I/O. For a 72-core Grace CPU, more workers can be used, but with the law of diminishing returns in mind.
The chapter separately examines prefetch_factor — the number of batches each worker prepares in advance. Default is 2. If I/O has spikes, raising it to 4–8 is recommended. The queue holds up to num_workers × prefetch_factor ready batches.
It also notes that preprocessing can be moved to the GPU using libraries like NVIDIA DALI, reducing CPU load and allowing decoding and augmentation to run in parallel with compute.
| Moment | CPU / workers | GPU |
|---|---|---|
| T₀ | Loads batch N, preprocesses | Computes batch N−1 |
| T₁ | Loads batch N+1 | Copies batch N (H2D DMA), computes |
| T₂ | Loads batch N+2 | Computes batch N+1 |
PyTorch DataLoader workers are processes, not threads. This is intentional: Python's GIL makes multithreaded loading inefficient for CPU-intensive tasks. If you write your own loader in pure Python with heavy loops — that's a red flag. Use vectorized libraries (Hugging Face Tokenizers in Rust, TorchText in C++).
To isolate the DataLoader, temporarily set num_workers=0 — this disables prefetch and shows the raw loading cost without background thread scheduling. But note: without GPU kernels, per-launch overhead also drops, so the "pure" load speed will look lower than it really is.
The DataLoader must work in multiple processes (not threads) due to the GIL. pin_memory=True and persistent_workers=True are baseline settings. num_workers and prefetch_factor are tuned empirically.
pin_memory=True improves performance by 10–20% by accelerating H2D transfers. prefetch_factor=2 by default; 4–8 is recommended for spike loads.
Compare loading time and compute time. If loading takes 40 ms and compute takes 20 ms, the GPU is idle 67% of the time. After optimization, loading can drop to 5 ms.
«For complex or heavy data preprocessing, NVIDIA provides their Data Loading Library (DALI). DALI accelerates data processing by either moving it to the GPU or using optimized CPU code written in C++. It's especially useful for image and video data where decoding and augmentation can benefit from GPU acceleration».
«NVIDIA NeMo is a toolkit for developing and training language models. Within the NeMo toolkit is the NeMo suite of libraries and frameworks, including the open source NeMo Curator framework. Curator helps prepare large multimodal datasets for LLM training».
The chapter describes DALI as a tool for moving decoding and augmentation to the GPU. A DALI pipeline is defined declaratively as a graph of operators. It uses its own thread pools and queues — no GIL. Ready-made pipelines exist for classification, detection, and segmentation.
Practical consequence: when using DALI, CPU load drops from 800% (8 cores at 100%) to 200% (2 cores for file reads), while decoding and augmentation run on the GPU in parallel with compute. However, if DALI is used only for JPEG decoding and raw pixels are then sent back to the CPU for augmentation, the extra H2D-D2H copies will eat all the gains.
NeMo Curator is described as a framework for offline preparation of data for LLM training. It performs cleaning, quality filtering, deduplication, tokenization, shuffling, and packing into large files (.bin + .idx). The result is a prepared dataset that the training pipeline reads without JSON parsing or on-the-fly tokenization.
The chapter separately notes that preprocessing data before training saves time and allows simpler, faster loaders during training.
If you use DALI only for JPEG decoding and then send raw pixels back to the CPU for augmentation and batch assembly — the extra H2D-D2H copies will eat all the gains. Keep data on the GPU the whole way: decode → augment → train. Alternative — TorchVision V2 transforms on the GPU or custom CUDA kernels.
Prepare data before training. Offline preprocessing costs time once and accelerates every run. Consider storing N shuffled copies for N epochs — this saves shuffling at runtime at the cost of disk space.
DALI moves decoding and augmentation to the GPU, reducing CPU load. NeMo Curator performs offline data preparation for LLMs, packing data into large files.
DALI reduces CPU load from 800% to 200% when processing images. Curator packs data into .bin + .idx, which are memory-mappable.
Keep data on the GPU the whole way: decode → augment → train. Offline preprocessing via NeMo Curator accelerates every training run.
«Performance engineering is an iterative process. To ensure that your distributed training or inference application stays efficient as you scale or modify it, you should adopt a continuous profiling and tuning workflow. This means regularly collecting performance data, identifying bottlenecks, applying optimizations, and then measuring again».
«Over time, hardware and software updates will change the optimal settings, so you need to continuously profile and tune. To stay ahead of this, performance-focused engineering teams often maintain performance dashboards to track metrics like samples/sec over time».
The chapter describes a continuous profiling methodology: establish a baseline, profile the multi-GPU run for bottlenecks, drill into specific kernels if needed, identify the cause, apply fixes, and re-measure after each change. It recommends changing one or two things at a time to understand what helped.
Practical consequence: automated nightly profiling runs catch regressions from driver updates, code changes, or dataset growth. Dashboards with samples/sec metrics help track performance over time.
The chapter separately examines tools: iostat, iotop, nvme-cli for OS and device level; Nsight Systems for the end-to-end timeline; Nsight Compute for kernel efficiency; PyTorch Profiler for operation tracing; DCGM for GPU metrics.
It also describes a methodology for diagnosing what limits the system — network or compute: changing the batch size with a fixed communication volume. If network throughput remains unchanged when the batch is reduced — the load is network-limited. If it drops — compute-limited.
| Tool | What it shows | Level |
|---|---|---|
iostat, iotop | Disk load, IOPS, latency | OS |
nvme-cli | NVMe device status, SMART | Device |
Nsight Systems (nsys) | End-to-end timeline: CPU + GPU + I/O | System |
Nsight Compute (ncu) | Efficiency of individual CUDA kernels | Kernel |
| PyTorch Profiler | Operation trace, DataLoader, H2D copies | Framework |
| DCGM | GPU metrics: utilization, memory, temperature | GPU |
Profiling is an iterative process. Change one or two things at a time. Regular nightly runs catch regressions. Dashboards track performance over time.
8 GPUs give only 5× speedup (instead of 8×) — efficiency is 62.5%, need to find the bottleneck. Under network limitation, network throughput does not change when the batch is reduced.
Use Nsight Systems for the end-to-end timeline, Nsight Compute for kernels, iostat for drives. For network vs compute diagnosis — the batch-size experiment.
«To understand if computation or communication is the limiting factor in a model training workload, you can change the ratio of computation to communication and see how this affects the achieved network throughput measured in GB/s on the NIC. Consider profiling the backward pass of a training job. It currently shows your gradient all-reduce is utilizing only 60 GB/s across a 100 GB/s NIC».
«If the achieved network utilization drops below 60 GB/s, to, say, 40 GB/s, the GPUs are starving the network by not finishing computations fast enough to keep the NIC busy. In this case, the network is idle, waiting for more data from the GPUs. As such, compute is the limiting factor and not the network».
The chapter describes a simple and effective diagnostic method: fix the communication volume (all-reduce depends on parameter count, not batch size) and vary the batch size. If network throughput doesn't change when the batch is reduced — the load is network-limited. If it drops — compute-limited.
Practical consequence: this method doesn't require complex profilers and can be performed with standard monitoring tools. It's enough to measure NIC or NVLink throughput at different batch sizes.
It also notes the importance of measuring GPU idle time waiting for data. Comparing loading time and compute time shows what fraction of GPU time is idle.
Varying batch size with fixed all-reduce volume
Diagnostic method: fix the communication volume (all-reduce depends on parameter count) and vary the batch size. If NIC doesn't change — network-limited; if it drops — compute-limited.
Example: all-reduce uses 60 GB/s out of 100 GB/s NIC. If throughput stays at 60 GB/s when the batch is reduced — network. If it drops to 40 GB/s — compute.
No complex profilers are needed for diagnosis. It's enough to measure NIC or NVLink throughput at different batch sizes.
Chapter 5 of Fregly's book was written in 2025. Much has changed since. Below is a comparison of the chapter's forecasts with open data and clarification of figures.
The chapter states 14 GB/s for 72 GPUs. This is correct: 72 × 200 MB/s = 14.4 GB/s. The recommendation to use none or mq-deadline for NVMe matches modern practice. The 10–20% GDS gain thesis is a conservative lower bound; published benchmarks show a wider spread depending on CPU load.
The book was written when GB200 NVL72 was the current platform. Blackwell adds a built-in decompression engine (LZ4, Snappy, Deflate) that unpacks data directly into HBM without occupying SMs. This shifts the balance in favor of storing data compressed — decoding ceases to be the bottleneck. The chapter mentions this aspect but without details on Blackwell's new capabilities.
The figure 6.6 TiB/s is confirmed by the official DeepSeek README. However, the cluster configuration in the book (68 nodes, 10 × 16 TB NVMe, two 100 Gb/s NICs) differs from the official one (180 storage nodes, 2×200 Gb/s InfiniBand, 16 × 14 TiB NVMe, 500+ client nodes); with the book configuration, the physical ceiling would be ~1.7 TB/s, which does not allow reaching 6.6 TiB/s. The figure is confirmed, the configuration is not. The comparison with Ceph (~1.1 TB/s) is the book author's estimate, not a DeepSeek statement. Additionally, DeepSeek reports: GraySort 110.5 TiB in 30 minutes 14 seconds, up to 40 GiB/s per client in KVCache.
Recommendations for pin_memory=True, persistent_workers=True and tuning num_workers are confirmed by practice. A 10–20% performance gain when enabling pin_memory is a realistic estimate. prefetch_factor=4–8 is recommended for workloads with peak I/O.
DALI is actively used for CV tasks, NeMo Curator — for preparing LLM datasets. Reducing CPU load from 800% to 200% when using DALI is a typical result. NeMo Curator packs data into .bin + .idx, which are memory-mappable.
«If the storage pipeline is slow, the GPUs will starve and sit idle».
Confirmed. Practice shows that a poorly configured DataLoader can waste 30–50% of GPU time. Optimizing the I/O pipeline often yields more than algorithmic kernel optimizations.
«GPUs prefer that the data be read in large contiguous chunks».
Confirmed. Packing small files into large formats (WebDataset, TFRecord, Arrow, Parquet) has become standard. An NVMe SSD delivers 6–7 GB/s sequentially, 3–10× less randomly.
«When using GDS, it's not uncommon to see improvements on the order of 10%–20% in throughput».
Confirmed as a conservative lower bound. The gdsio example in the chapter shows 8.0 → 9.6 GB/s (+20%) and 1.25 → 1.00 ms (−20%). Published benchmarks show a wider spread: VAST Data — 162 GiB/s on DGX A100 versus ~33 GB/s traditional path; Meta — 3.8× training speedup; OCI — up to 2.7–3.5×. The gain depends on CPU load.
«3FS achieves multi-terabyte-per-second throughput».
Confirmed. DeepSeek reports 6.6 TiB/s. However, the cluster configuration in the book (68 nodes, 10 × 16 TB NVMe, two 100 Gb/s NICs) differs from the official one (180 storage nodes, 2×200 Gb/s IB, 16 × 14 TiB NVMe). The comparison with Ceph (~1.1 TB/s) is the book author's estimate.
«If a file system is implemented using FUSE in user space, it will not be able to deliver a GDS path».
Confirmed with caveat. Generally true: GDS requires a kernel client with nvidia-fs. However, hybrid solutions exist where FUSE is used for metadata and data goes through the kernel client. These are rare configurations, and for most cases the statement remains valid.
The chapter describes principles and tools but leaves out several aspects that emerged in real-world operation.
The chapter mentions that modern GPUs add an on-die Decompression Engine (LZ4, Snappy, Deflate) but doesn't detail how much this shifts the balance. On Blackwell, decompression is performed in hardware and doesn't occupy SMs, making compressed data storage much more attractive. If CPU decompression could previously become a bottleneck, it's now virtually free in terms of GPU cycles. This reduces storage bandwidth requirements without sacrificing performance.
The chapter mentions NVMe-oF but doesn't examine switch topology and its impact on performance. In real deployments, the choice between RoCEv2 and InfiniBand, PFC/ECN tuning, buffer sizes, and switch queue depths can influence throughput more than the choice of drives. For large clusters, a dedicated storage network is recommended.
The chapter focuses on hardware and filesystems but doesn't examine software-defined storage (Ceph, MinIO, WekaFS). Some of them (WekaFS) support GDS and can be a good choice for hybrid and cloud deployments. They are worth studying as an alternative to traditional NFS and Lustre.
The chapter doesn't examine storage economics. NVMe SSDs cost significantly more than HDDs and object storage. For large datasets, a multi-tier strategy is often used: «hot» data on NVMe, «warm» on HDD or network storage, «cold» in object storage. Optimizing not only speed but also storage cost is part of the performance engineer's job.
The chapter doesn't address security: data encryption, I/O isolation between tenants, access control. In multi-tenant clusters, these aspects can influence storage selection and configuration. NVMe-oF with encryption, SED (Self-Encrypting Drives), and access control are topics requiring separate consideration.
The chapter mentions Amazon S3 and FSx for Lustre but doesn't examine the specifics of other cloud storages (Google Cloud Storage, Azure Blob Storage). Each has its own features: different consistency models, different request limits, different APIs. When choosing cloud storage for AI workloads, it's worth testing their actual performance rather than relying on marketing figures.
num_workers, pin_memory, persistent_workers, prefetch_factor — these parameters need to be tuned empirically. For a 72-core Grace CPU, more workers can be used, but with the law of diminishing returns in mind.read_ahead_kb for streaming reads.none or mq-deadline. Mount options noatime. O_DIRECT for data that doesn't fit in RAM.nvidia-fs and a compatible filesystem.pin_memory=True, persistent_workers=True, tune num_workers and prefetch_factor.iostat, DCGM. Change one parameter at a time.Chapter 5 of Fregly's book is a systematic overview of the storage subsystem and data pipeline for AI workloads. The key idea: GPUs cannot work faster than they are fed data. I/O optimization — from drive selection to DataLoader tuning — can yield more than kernel optimization. GDS, 3FS, DALI, NeMo Curator are tools that let you squeeze the maximum out of the storage subsystem. As models and clusters grow, the role of efficient I/O will only increase.
Forecast verification shows: the book proved accurate in its predictions. GDS gives 10–30% gain, 3FS shows multi-TB/s, DataLoader settings work. Hardware decompression on Blackwell shifts the balance in favor of compression. The main takeaway remains unchanged: keep the pipeline full — from disk to HBM.
Sources: Chris Fregly, «AI Systems Performance Engineering» (O'Reilly, 2026), Chapter 5 — quotes provided in limited volume for review and critical analysis purposes. NVIDIA GDS, NVIDIA DALI, NVIDIA NeMo Curator documentation; DeepSeek 3FS; VAST Data; Meta; Oracle Cloud Infrastructure; MLPerf Storage; Uptime Institute; DeepSeek-V3 technical reports.