
Один из моих коллег сказал когда‑то, что «база данных — это хранилище, а не считалище!». Эту фразу я вспоминал регулярно, пока проводил свое маленькое исследование. Целью данной статьи является описание практического опыта эффективного решения одной из задач ML на существующих аппаратных ресурсах, без аренды/покупки дорогостоящих GPU.
Лирическое вступление
Скупость, лень и инакомыслие сподвигли меня к написанию этой статьи. Смеркалось, жаркий день бледнел неуловимо... Я неосторожно задумался о способах оптимизации векторного представления лексических токенов (WTE) в языковых моделях, когда ощутил легкое раздражение. Это была жадность....при мысли о необходимости подбора новой мощной видеокарты, которая возможно не подойдет к моему десктопу и придется менять все‑все... — к жадности подключилась лень. Чтобы как‑то переключиться от неприятных мыслей, я стал размышлять от обратного. Ведь мы хотим хранить и обрабатывать большой объем данных. Но с этой задачей человечество относительно успешно справляется и без GPU. У нас есть BigData, есть Spark RDD (Resilent Distributed Dataset). Да, в скорости обработки этот механизм будет явно уступать парку GPU, но разница в том, что эти ресурсы уже есть — у организаций свои платформы данных, у нас с вами — возможность поставить и использовать СУБД на обычный ноутбук. И тут я вспомнил про статью «С новым годом: GPT в 500 строках на SQL» и понял что это может быть решением.
Постановка задачи
Реализовать токенизацию для GPT исключительно на ресурсах Clickhouse
Комментарии к задаче
А почему именно токенизацию, а не WTE к примеру? Решил начать с относительно более простой задачи, а далее двигаться по всему треку обучения модели вплоть до инференса. Ну т. е. рассчитываю, что это будет цикл статей.
А почему только на ресурсах Clickhouse, что мешает использовать простенький python‑вский скриптик который все посчитает, а потом положит в БД? Потому как теряется смысл задумки. Скрипт будет выгружать содержимое таблиц/таблицы в память и далее обсчитывать. Если содержимое таблиц превышает размер оперативной памяти, то уже ОС начинает подключать swap, как следствие — деградация производительности. Сюда же добавим транзакционные издержки на извлечение данных.
Почему именно Clickhouse.
3.1) Объективно быстрая распределенная дисковая реляционная СУБД в сравнении с другими аналогами. Ну, и были мысли в дальнейшем исследовать механизм репликации Клика для организации распределенных вычислений.
3.2) Более богатый синтаксис SQL Clickhouse.
Коротко про токенизацию
В словаре Эллочки-людоедки из романа И. Ильфа и Е.Петрова было 30 слов (задумался, все‑таки слов, или выражений?). У моделей примерно также, только слов чуть побольше (50-100 тыс.) И это не слова в привычном понимании, а скорей минимальная единица текста которая часто встречается. Наиболее простое и более подробное объяснение встретил вот здесь
Шаг 1. Загрузка датасета для обучения
Используем kaggle. Взял достаточно несложный датасет с новостями (около 209 тыс. коротких новостей). Ближе к финалу статьи, понял что могу обрабатывать существенно большие объемы данных. Но для экспериментов — в самый раз.
Создал тестовую БД, с именем GPT. Вот DDL‑ки таблиц, которые мы будем использовать:
create table GPT.ds_news_category ( link String, headline String, category String, short_description String, authors String, date Date ) ENGINE = MergeTree PRIMARY KEY link; create table GPT.vocabulary ( token String ) ENGINE = MergeTree PRIMARY KEY token; create table GPT.pre-tokens ( word String, count UInt32 ) ENGINE = MergeTree PRIMARY KEY word;
Данные в БД загрузил через clickhouse-console вот так:
clickhouse-client --user=default --password=haha --query="INSERT INTO GPT.ds_news_category FORMAT JSONEachRow" < News_Category_Dataset_v3.json
Шаг 2. Предварительная подготовка токенов (нормализация)
Весь текст переведен в нижний регистр, убраны все знаки препинания. Для токенизации использовал три поля таблички датасета: headline, category, short_description
truncate TABLE GPT.pre_tokens; insert into GPT.pre_tokens (word,count) with words as ( with ds as ( select splitByWhitespace(lower(headline)) AS source from GPT.ds_news_category union all select splitByWhitespace(lower(category)) AS source from GPT.ds_news_category union all select splitByWhitespace(lower(short_description)) AS source from GPT.ds_news_category ) select replaceRegexpAll(arrayJoin(source),'[[:^alpha:]]','') as word from ds where length(word)>0 ) select distinct(word), count() from words group by word order by count() desc;
В итоге получилось примерно вот такое содержимое (всего порядка 112 тыс уникальных слов):

Обратите внимание, что мы сразу считаем частоту (поле count)
Шаг 3. Генерация словаря токенов
Ниже представлен код генерации словаря. Да, здесь есть отступление от подхода Byte-Pair Encoding который к примеру описан здесь. В описаниях используется итеративный подход, при котором на каждой итерации мы обсчитываем частоту следующего токена. Но я на предидущем шаге уже посчитал частоты уникальных слов и при токенизации ориентируюсь на них (отсортировал в порядке убывания частоты и взял первые 300 токенов). Если считаете это критичным, буду признателен если напишите в комментариях почему.
-- первый запуск insert into GPT.vocabulary (token) with letters as ( select splitByRegexp('',word) w from GPT.pre_tokens pt ) select distinct(arrayJoin(w)) from letters; -- второй запуск insert into GPT.vocabulary (token) with max_result as ( with letters as ( select ngrams(word,2) w from GPT.pre_tokens pt ) select arrayJoin(w) token, count() c from letters where token NOT IN (SELECT * FROM GPT.vocabulary) group by token having c>1 order by c desc limit 300 ) select token from max_result
Вот что получилось в итоге:

Итоги работы и планы
Производительность. На данном этапе замеров не производилось по банальной причине — Clickhouse все задачи выполнял субъективно за промежуток времени в районе 1–2 секунд. Как и упоминал в начале ресурсы моего десктопа достаточно скромны (Core i3, 2.4Ггц, 16 ГБ оперативной памяти). Здесь в планах существенно увеличить датасет.
Очень помогает синтаксис Clickhouse SQL. В классическом ANSI SQL код выглядел бы менее изящнее
Общий план амбициозен конечно — сделать полноценный GPT используя только существующие ресурсы и только на платформе Clickhouse. В том числе и в части взаимодействия с пользователем во время инференса модели, благо у Клика есть свой веб‑интерфейс.
