Сгенерировано с помощью gigaChat
Сгенерировано с помощью gigaChat

Один из моих коллег сказал когда‑то, что «база данных — это хранилище, а не считалище!». Эту фразу я вспоминал регулярно, пока проводил свое маленькое исследование. Целью данной статьи является описание практического опыта эффективного решения одной из задач ML на существующих аппаратных ресурсах, без аренды/покупки дорогостоящих GPU.

Лирическое вступление

Скупость, лень и инакомыслие сподвигли меня к написанию этой статьи. Смеркалось, жаркий день бледнел неуловимо... Я неосторожно задумался о способах оптимизации векторного представления лексических токенов (WTE) в языковых моделях, когда ощутил легкое раздражение. Это была жадность....при мысли о необходимости подбора новой мощной видеокарты, которая возможно не подойдет к моему десктопу и придется менять все‑все... — к жадности подключилась лень. Чтобы как‑то переключиться от неприятных мыслей, я стал размышлять от обратного. Ведь мы хотим хранить и обрабатывать большой объем данных. Но с этой задачей человечество относительно успешно справляется и без GPU. У нас есть BigData, есть Spark RDD (Resilent Distributed Dataset). Да, в скорости обработки этот механизм будет явно уступать парку GPU, но разница в том, что эти ресурсы уже есть — у организаций свои платформы данных, у нас с вами — возможность поставить и использовать СУБД на обычный ноутбук. И тут я вспомнил про статью «С новым годом: GPT в 500 строках на SQL» и понял что это может быть решением.

Постановка задачи

Реализовать токенизацию для GPT исключительно на ресурсах Clickhouse

Комментарии к задаче

  1. А почему именно токенизацию, а не WTE к примеру? Решил начать с относительно более простой задачи, а далее двигаться по всему треку обучения модели вплоть до инференса. Ну т. е. рассчитываю, что это будет цикл статей.

  2. А почему только на ресурсах Clickhouse, что мешает использовать простенький python‑вский скриптик который все посчитает, а потом положит в БД? Потому как теряется смысл задумки. Скрипт будет выгружать содержимое таблиц/таблицы в память и далее обсчитывать. Если содержимое таблиц превышает размер оперативной памяти, то уже ОС начинает подключать swap, как следствие — деградация производительности. Сюда же добавим транзакционные издержки на извлечение данных.

  3. Почему именно 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

Вот что получилось в итоге:

Итоги работы и планы

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

  2. Очень помогает синтаксис Clickhouse SQL. В классическом ANSI SQL код выглядел бы менее изящнее

  3. Общий план амбициозен конечно — сделать полноценный GPT используя только существующие ресурсы и только на платформе Clickhouse. В том числе и в части взаимодействия с пользователем во время инференса модели, благо у Клика есть свой веб‑интерфейс.