Зміст
На склад привезли одну скриню на пів тонни й сказали: «це ваш набір даних, навчіть модель». Навантажувач не бере. Відкрити цілком не можна — розсиплеться. Полагодити одну биту коробку всередині неможливо, не розібравши весь вантаж. Так виглядає «датасет 400 GB в одному JSON»: ноутбук падає по пам’яті, навчання «на всьому одразу» здається нереальним, а питання «скільки потрібно RAM» звучить як «скільки складу потрібно під увесь контейнерний порт».
Справжні конвеєри машинного навчання працюють інакше. Дані ріжуть на палети — шарди; всередині палети лежать коробки — по одному запису на рядок у форматі JSON Lines (JSONL); на конвеєр до відеоприскорювача (GPU) їдуть уже не палети цілком, а невеликі партії прикладів — батчі. Розмір усього набору й обсяг, що одночасно живе в оперативній пам’яті чи відеопам’яті, — різні величини. Нижче — як влаштований цей шлях від одного рядка до кроку навчання, і де його зазвичай плутають.
Матеріал продовжує серію про інженерію датасетів з акцентом на фізичний устрій зберігання і завантаження в навчання. Дизайн корпусу інструкцій — у лонгріді про донавчання з учителем (SFT); підготовка корпусу для пошуку — в розділі про дані RAG. Тут — склади, палети й конвеєр.
Ключові висновки
JSON — один документ. JSONL — потік незалежних об’єктів, по одному на рядок. Для великих наборів майже завжди потрібен другий.
Шард — частина набору, а не «особливий формат файлу». Шард може бути .jsonl, .jsonl.gz, Parquet чи іншим контейнером.
Розмір набору ≠ RAM. Терабайт можна навчати, читаючи шарди потоком і тримаючи в пам’яті лише поточний батч.
Модель не «читає JSON». Рядок стає токенами, токени збираються в батч, батч іде на GPU. JSONL — спосіб зберігання й обміну, не мова моделі.
Для бюджету навчання важливіші токени, ніж гігабайти файлу. Один гігабайт JSONL дає різний обсяг токенів залежно від мови, полів і токенізатора.
JSONL зручний як формат обміну й первинного завантаження. Для колонкової аналітики й вибіркового читання полів частіше виграє Parquet або спеціалізовані контейнери на кшталт WebDataset.
Навчальна, перевірна й тестова вибірки — різні каталоги з власними шардами. Одна «каша» з файлів без маніфесту ламає порівняння прогонів — про це в опорній статті серії.
Один великий JSON — як скриня, яку не можна вивезти
JSON (JavaScript Object Notation) зручний: його читає людина, його розуміють майже всі мови, ним віддають відповіді API й конфігурації. Невеликий документ з об’єктами, масивами, рядками й числами — нормальний вибір.
Проблема починається, коли «невеликий документ» стає єдиним контейнером на сотні гігабайтів. Тоді це вже не файл конфігурації, а моноліт:
- парсер часто хоче побачити документ цілком або тримає в пам’яті величезний масив;
- потокова обробка незручна: не можна просто «взяти наступний запис»;
- паралельна обробка впирається в один файл і блокування;
- пошкодження середини файлу може зробити нечитабельним усе інше.
На складі це та сама скриня на пів тонни: формат знайомий, логістика неможлива. Для навчання й ETL-конвеєрів потрібна інша домовленість про те, як лежать приклади.
JSONL: один рядок — одна коробка
JSON Lines (JSONL; також NDJSON, Newline Delimited JSON) — домовленість: кожен рядок файлу — окремий, самодостатній JSON-об’єкт. Між об’єктами немає спільної обгортки-масиву.
{"id":1,"text":"Привіт"}
{"id":2,"text":"Як справи?"}
{"id":3,"text":"До побачення"}
Порівняйте з класичним JSON-масивом:
JSON: [ {...}, {...}, {...} ] ← один документ
JSONL: {...}\n{...}\n{...}\n ← потік незалежних записів
Для машинного навчання це збігається з інтуїцією «один приклад — один запис»: діалог для SFT, мітка класу, пара «запит — відповідь». Типовий запис для набору інструкцій може виглядати так (схему полів розбираємо в статті про корпус SFT):
{"messages":[{"role":"user","content":"Що таке Python?"},{"role":"assistant","content":"Python — мова програмування..."}]}
Читати такий файл можна потоком, не завантажуючи весь набір:
import json
with open("dataset.jsonl", encoding="utf-8") as f:
for line in f:
item = json.loads(line)
process(item)
Чому це зручно на практиці:
- низький пік пам’яті при послідовному читанні;
- просте дописування нових прикладів у кінець файлу;
- фільтрація й вибірка без розбору гігантського масиву;
- природна нарізка на файли для розподіленої обробки.
Це не «JSON без квадратних дужок». Це інший контракт: межі запису = межі рядка, об’єкти незалежні.
Датасет — не файл, а набір прикладів
Датасет у навчанні — це узгоджений набір прикладів під задачу, а не «файл, який завантажили». Один запис може бути текстом, парою питання–відповідь, зображенням із посиланням, розміткою класу чи списком повідомлень. Обсяг легко йде в мільйони записів і сотні гігабайтів або терабайти.
Поки команда думає «у нас є data.json», вона думає про контейнер. Щойно з’являється контракт «що вважається одним прикладом» і «як перевірити якість зрізу», з’являється продукт даних — у дусі опорного лонгріду серії. Фізичний устрій на диску (JSONL і шарди) обслуговує цей продукт: його можна версіонувати, копіювати частинами й подавати в завантажувач.
Шард — палета, а не «формат»
Шард (shard) — частина великого набору. Коли один dataset.jsonl виростає до сотень гігабайтів, його ріжуть:
dataset.jsonl → dataset/
(500 GB) ├── shard-00000.jsonl
├── shard-00001.jsonl
├── shard-00002.jsonl
└── ...
Усі шарди разом — це датасет. Кожен рядок усередині шарда — як і раніше один приклад. Порядок записів і спосіб розкладки по файлах можуть впливати на навчання, якщо ви не перемішуєте дані свідомо.
Критична відмінність: JSONL — формат запису; шард — спосіб нарізки. Один шард може бути .jsonl, .jsonl.gz, Parquet, TFRecord або архівом WebDataset. Плутати «у нас шарди» з «у нас JSONL» — усе одно що плутати «палета» з «картонна коробка».
Універсального «правильного» розміру шарда немає. Трапляються десятки мегабайтів, сотні мегабайтів, 1–10 GB. Компроміс залежить від числа воркерів, швидкості диска й мережі, лімітів об’єктного сховища на число об’єктів, зручності повторної обробки й вартості лістингу мільйонів дрібних файлів. Розмір обирають під конкретний конвеєр, а не за магічним числом із чужого репозиторію.
Навіщо великі набори ріжуть на шарди
Палети на складі потрібні не з любові до нумерації файлів.
Пам’ять. Не потрібно тримати весь набір у RAM: читаєте поточний шард або навіть вікно всередині нього, обробляєте, звільняєте буфер.
Паралелізм. Різні процеси або прискорювачі можуть читати різні шарди одночасно:
воркер 1 → shard-00000
воркер 2 → shard-00001
воркер 3 → shard-00002
воркер 4 → shard-00003
Зберігання й перенесення. Копіювати, бекапити й перезаливати зручніше частинами. Повторне очищення стосується лише «брудних» шардів, а не всього терабайта.
Відмовостійкість. Пошкоджений шард можна перезібрати; решта набору лишається робочою.
Розподілене навчання. У схемах із паралелізмом за даними різні воркери часто бачать різні зрізи потоку. Конкретна розкладка залежить від фреймворку: «один шард = один GPU» — не правило, а окремий випадок.
Нарізка даних не дорівнює нарізці моделі. Поділ набору на шарди й паралелізм моделі (шари на різних пристроях) — різні осі масштабування.
Стиснення: коли палету обгортають плівкою
Текст JSONL добре стискається: повторюються ключі полів, схожі конструкції, службові слова. Часто шарди зберігають як shard-00000.jsonl.gz: менше місця на диску й менше трафіку при вивантаженні з об’єктного сховища. Плата — CPU на розпакування й гірший випадковий доступ порівняно з деякими бінарними колонковими форматами.
Типовий компроміс продакшену: зберігати стиснутими шарди на холодному/теплому шарі, а в гарячому завантажувачі вирішувати, чи тримати кеш розпакованих шматків. Якщо вузьке місце — диск і мережа, стиснення майже завжди окупається. Якщо вузьке місце — уже насичені ядра CPU на розпакуванні при крихітних шардах, має сенс укрупнити файли або змінити формат.
Від рядка на полиці до кроку на GPU
JSONL — вихідне представлення. Модель на навчанні працює з числами. Спрощений шлях:
flowchart TB
raw[Сирі дані] --> clean[Очищення й фільтри]
clean --> jsonl[JSONL]
jsonl --> shards[Шарди]
shards --> loader[Data loader]
loader --> shuffle[Перемішування]
shuffle --> tok[Токенізатор]
tok --> batch[Батчі]
batch --> gpu[GPU: forward / loss / backward]
Токен — фрагмент тексту (ціле слово, шматок слова, знак), якому токенізатор зіставив номер. Модель не «читає» фразу «Привіт, світе!» як людина: вона бачить послідовність ідентифікаторів. Тому гігабайт JSONL ≠ фіксована кількість токенів: мова, службові поля JSON, довжина відповідей і словник токенізатора змінюють бюджет. Для оцінки вартості й тривалості прогону рахують токени й кроки, а не лише розмір теки.
Батч — група прикладів (або обрізаних послідовностей), які обробляються за один крок. Датасет — усі приклади; батч — те, що зараз на конвеєрі в GPU. Розмір батча й довжина послідовності разом тиснуть на відеопам’ять: грубо кажучи, зростає добуток «скільки послідовностей × яка довжина». Один шард при цьому може містити тисячі записів і багато батчів — шард ≠ батч.
Епоха — один повний прохід навчальним набором (усіма шардами train в узгодженому порядку або з перемішуванням). Прочитали умовно всі 100 шардів — формально пройшли епоху; деталі порядку залежать від завантажувача.
Сценарій «1 TB на машині з обмеженою RAM» тоді виглядає так:
1 TB набору
→ 1000 шардів × ~1 GB
→ читаємо шматок шарда
→ приклади → токени → батч на GPU
→ звільняємо буфер → наступний батч
Склад як і раніше повний. На конвеєрі в кожен момент — одна партія коробок, не весь порт.
Перемішування: щоб конвеєр не годували однією полицею підряд
Якщо в наборі спочатку йдуть сто тисяч текстів про автомобілі, потім про медицину, потім про код, модель за епоху бачить неприродні «сезони». Градієнти зміщуються пачками домену. Тому дані перемішують: на рівні порядку шардів, буферизованого перемішування записів або дорожчих схем повного перемішування.
Повне перемішування величезного набору дороге за пам’яттю й введенням-виведенням. На практиці часто комбінують: випадковий порядок шардів + буфер перемішування всередині потоку. Якість перемішування — компроміс із ресурсами, а не галочка «завжди ідеально».
JSONL чи Parquet: що на якій полиці
| Характеристика | JSONL | Parquet |
|---|---|---|
| Читабельність людиною | висока | низька |
| Простота налагодження | висока | середня |
| Потокова обробка за записами | зручна | можлива |
| Стиснення | добре (особливо gzip) | зазвичай дуже сильне |
| Колонкове читання полів | ні | так |
| Типова роль | обмін, первинне завантаження, вивантаження SFT | аналітика, озера даних, вибіркові колонки |
JSONL часто лишається мовою обміну: його легко відкрити, полагодити один рядок, прогнати скриптом, віддати розмітнику. Parquet виграє, коли потрібно читати три колонки з широкої таблиці на терабайтах або щільно упакувати типізовані поля. В одному конвеєрі обидва формати сусідять: сирий і розмічений обмін у JSONL, вітрини й ознаки — у Parquet. Твердження «JSONL оптимальний для будь-якого великого набору» хибне так само, як «Parquet завжди зручніший для ручного виправлення одного запису».
Як скласти каталог під свій проєкт
Мінімальна дисципліна складу — рознести сировину, очищення й розрізи навчання:
dataset/
├── raw/
├── cleaned/
├── deduplicated/
├── train/
│ ├── shard-00000.jsonl.gz
│ ├── shard-00001.jsonl.gz
│ └── ...
├── validation/
│ └── shard-00000.jsonl.gz
└── test/
└── ...
Каталог raw не чіпають навчанням. cleaned / deduplicated — відтворювані стадії. Каталоги train / validation / test — різні сімейства з погляду витоків: оціночні шарди не повинні «випадково» потрапити в навчання. Маніфест версії (хеш файлів, схема, дата) прив’язують до кожного прогону — інакше порівняння метрик безглузде; рамка — в інженерії датасетів.
Типовий контур в експлуатації:
сировина → очищення → дедуп → фільтри → JSONL → шарди → стиснення
→ об’єктне сховище → завантажувач → перемішування → токенізатор → батчі → GPU
Для LLM шлях той самий: інтернет і документи стають сировиною, потім JSONL і шарди, потім токени. JSONL не «формат думки моделі»; шарди не шар архітектури мережі. Це інфраструктура обсягу. Коли обсяг і задача взагалі виправдовують донавчання, а не лише промпт і пошук — див. коли потрібне донавчання.
Приклад на мільйон записів
Умовний набір: 1 000 000 записів, у середньому ~5 KB на запис → порядку 5 GB до накладних витрат. Розкладка:
100 шардів × ~10 000 записів
dataset/
├── shard-00000.jsonl
├── ...
└── shard-00099.jsonl
Далі кожен шард годує завантажувач: записи → токени → батчі → GPU. Якщо батч — 32 послідовності, один шард на 10 000 записів дасть сотні кроків, а не «один крок на шард». Саме тут ламається інтуїція «файл = порція навчання».
Мінімальний інструментарій на Python: потокове читання, запис, підрахунок рядків, нарізка на N файлів, gzip, пропуск битих рядків із логом. Навіть маленький скрипт нарізки вже перетворює моноліт на палети:
import json
from pathlib import Path
def shard_jsonl(src: Path, out_dir: Path, rows_per_shard: int = 10_000) -> None:
out_dir.mkdir(parents=True, exist_ok=True)
shard_idx, n_in_shard = 0, 0
out = None
try:
with src.open(encoding="utf-8") as f:
for line in f:
if n_in_shard == 0:
if out:
out.close()
out = (out_dir / f"shard-{shard_idx:05d}.jsonl").open(
"w", encoding="utf-8"
)
shard_idx += 1
out.write(line if line.endswith("\n") else line + "\n")
n_in_shard = (n_in_shard + 1) % rows_per_shard
finally:
if out:
out.close()
У проді додадуться перевірка схеми, маніфест хешів і заборона на «тихе» переписування вже опублікованого шарда без нової версії.
Типові помилки
- «JSONL — це просто JSON без дужок». Ні: це потік незалежних об’єктів із межею за рядком; парсери й контракти інші.
- «Шард — особливий формат». Ні: це частина набору; всередині може бути що завгодно з прийнятих контейнерів.
- «Набір 1 TB ⇒ потрібно 1 TB RAM». Ні: потрібен потік і бюджет під батч плюс буфери.
- «Один шард = один батч». Ні: шард зазвичай містить багато батчів.
- «Один шард = один GPU». Не обов’язково: залежить від завантажувача й стратегії паралелізму.
- «JSONL завжди найкращий формат для великого набору». Ні: для колонок і аналітики частіше беруть Parquet або доменні контейнери.
- Навчальна й тестова вибірки впереміш в одній теці без маніфесту. Отримуєте красиві цифри й невідтворюваний витік — див. рамку сімейств даних у серії.
Що зробити сьогодні
- Відкрийте свій поточний «датасет». Якщо це один величезний JSON або CSV — оцініть вартість нарізки в JSONL-шарди й запишіть цільовий розмір шарда під число воркерів.
- Напишіть маніфест із п’яти полів: число записів, число шардів, хеш списку файлів, схема одного запису, дата збірки.
- Рознесіть навчальну, перевірну й тестову вибірки по каталогах, навіть якщо перевірна поки маленька: звичка дешевша за витік.
- Порахуйте токени на вибірці з 1000 рядків вашим токенізатором — порівняйте з гігабайтами й перестаньте планувати навчання лише за розміром теки.
Часті запитання (FAQ)
Чи потрібно завжди використовувати JSONL для навчання LLM?
Ні. JSONL зручний для обміну й багатьох SFT-вивантажень. У претрейні й великих корпусах трапляються власні бінарні формати, WebDataset, Parquet і суміші. Обирайте за вузьким місцем: налагодження запису, колонки, мережа, CPU розпакування.
Який розмір шарда обрати?
Такий, щоб число файлів не вибухало лістинг сховища, один шард оброблявся розумний час, а воркери не простоювали в очікуванні гігантського файлу. Часто починають із сотень МБ – кількох GB і вимірюють throughput завантажувача.
Чи шардування — те саме, що паралелізм за даними?
Ні. Шардування — про зберігання й читання частин набору. Паралелізм за даними (data parallel) — про те, як кілька пристроїв рахують градієнти за своїми частками батча й синхронізуються. Вони часто зустрічаються разом, але це різні шари.
Чому не можна судити про обсяг навчання лише за розміром JSONL на диску?
Тому що службові поля, мова, шаблони діалогів і токенізатор змінюють число токенів. Два файли по 10 GB можуть дати кратно різний бюджет навчання.
Куди дивитися далі в серії?
До опорного огляду інженерії датасетів і дизайну корпусу SFT. Наступні логічні сателіти — контракти полів запису й схеми метаданих, а також розрізи навчання/оцінки й витоки.
Далі в серії
Фізичний устрій без контракту полів легко перетворюється на «кожен шард зі своєю схемою». Наступний вузол черги — схема запису, метадані й контракти (dataset-schema-contracts-2026), потім — явні розрізи й боротьба з витоками (dataset-splits-leakage-2026).



Коментарі