
Один из моих коллег сказал когда‑то, что «база данных — это хранилище, а не считалище!». Эту фразу я вспоминал регулярно, пока проводил свое маленькое исследование. Целью данной статьи является описание практического опыта эффективного решения одной из задач 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. В том числе и в части взаимодействия с пользователем во время инференса модели, благо у Клика есть свой веб‑интерфейс.