Пирамида отказоустойчивости системы

Разработчик Гуннар Морлинг в 2022 году представил пирамиду ревью кода. По аналогии с ней появилась пирамида отказоустойчивости системы. Она делит отказоустойчивость на уровни и предлагает ответить на ряд важных вопросов по каждому из уровней. Пирамида отказоустойчивости системы помогает лучше понимать и реализовывать эту концепцию. 

Пирамида отказоустойчивости системы (CodeReliant.io)

Пирамида отказоустойчивости системы (CodeReliant.io)

Инфраструктура

В основе пирамиды лежит инфраструктура. В неё входят физические аппаратные средства, на которых работают системы. А также серверы, сети, центры обработки данных, системы электропитания и многое другое. Какие резервы встроены в вашу инфраструктуру? Инвестиции в избыточную инфраструктуру могут уменьшить количество точек отказа и повысить отказоустойчивость.

Ниже приведены примеры вопросов, которые могут задавать себе инженеры:

  • Имеет ли система достаточную избыточность ?

  • Существует ли стратегия резервного копирования?

  • Насколько устойчива настройка сети к сбоям?

  • Есть ли единые точки отказа в настройке инфраструктуры?

  • Что произойдет, если на центр обработки данных или облачный регион обрушится торнадо?

Проектирование системы

В программной инженерии проектирование системы связано с высокоуровневой архитектурой и структурой программы. Как правило, это компромисс между функциональностью и удовлетворением нефункциональных требований.

Некоторые ключевые аспекты проектирования системы:

  • Архитектура — это общие компоненты системы и их взаимодействие. Архитектура может быть распределённая или монолитная, может содержать модель клиент-сервер, микросервисы и т.д.

  • Разделение — разбиение системы на модули и компоненты. Это то, как делятся обязанности и функциональность.

  • Интерфейсы — это то, как компоненты общаются и взаимодействуют друг с другом через API, вызовы функций, протоколы и т. д.

  • Масштабируемость. Учитывает ли система рост числа пользователей, трафика, объёма данных и т.д. Это влияет на такие вещи, как горизонтальное и вертикальное масштабирование.

  • Безопасность — существуют ли механизмы контроля доступа, шифрования, обфускации, аудита и т.д.

  • Надежность. Отказоустойчивость может достигаться за счет избыточности, аварийного переключения, плавной деградации и т. д.

  • Производительность — оптимизация скорости, отклика и эффективности.

  • Ремонтопригодность. Позволяет ли проект вносить обновления и изменения без серьезных сбоев/изменений в сервисе.

Основные цели — разбить систему на логические части, определить взаимосвязи и взаимодействия и добиться компромисса по функционалу, качеству и производительности. Этот высокоуровневый план реализуется через другие более низкоуровневые планы.

Данные

Когда дело доходит до вопросов о данных, то здесь нет универсального решения, как улучшить отказоустойчивость. Но можно задать несколько вопросов, которые помогут избежать неожиданных проблем. Проблемы с данными опасны тем, что представляют угрозу всему бизнесу.

Ниже приведен список таких вопросов. На них надо ответить, прежде чем вы приступите к написанию отказоустойчивого программного обеспечения:

  • Должны ли данные реплицироваться в нескольких местах: хосты, регионы, облака?

  • Существуют ли механизмы для обеспечения атомарности и согласованности транзакций?

  • Как система разрешает конфликты?

  • Какие меры безопасности существуют для защиты целостности данных?

  • Есть ли риск потери данных?

  • Какую технологию баз данных мы должны использовать? (вопрос на 1 миллион долларов)

Fault Tolerance (отказоустойчивость)

Базовые возможности отказоустойчивости закладываются на уровне проектирования системы. Но многие компании не могут позволить себе включить эти механизмы в свою архитектуру. Если не делать это заранее, то в будущем могут возникнуть проблемы с надежностью системы.

Общая отказоустойчивость системы складывается из таких механизмов как: избыточность, автоматическое переключение при отказе, деградация и повторные попытки. Их стоит включить в первоначальный проект системы.

Архитектура, в которой с самого начала заложено обеспечение устойчивости, дает системе возможность справляться с неизбежными сбоями и перегрузками.

Вопросы:

  • Какие сбои или неисправности наиболее вероятны?

  • Как мы можем создать избыточность в системе? Например: дублировать серверы, создавать резервы, делать развертывание в нескольких регионах.

  • Как будет деградировать система при перегрузке?

  • Можем ли мы отказаться от второстепенной работы?

  • Как система будет реагировать на сбои компонентов? Нужны ли нам проверки работоспособности и автоматический перезапуск?

  • Какая автоматическая отказоустойчивость необходима? Насколько она должна быть активной?

  • Как мы можем изолировать сбои и не дать им превратиться в каскадные сбои между компонентами?

  • Какие запасные варианты/значения мы можем реализовать в случае сбоя частей системы?

  • Как мы можем реализовать повторные попытки/отсрочку для временных ошибок?

  • Должны ли мы установить сроки, чтобы избежать ненужной работы?

  • Как будет защищена целостность системы, если поврежденный компонент необходимо отключить?

  • Как можно вносить системные изменения и обновления без простоев?

Тесты и наблюдаемость

Этот уровень связан с двумя вопросами:

  • Как вы проверяете устойчивость?

  • Как быстро можно выявить и устранить проблемы?

Комплексное тестирование системы включает проверку на всех уровнях, от компьютера разработчика до взаимодействия с клиентом. Вот что сюда может входить:

  • Всестороннее тестирование на разных уровнях подтверждает надежность; например, модульное, интеграционное, e2e и функциональное тестирование снижают подверженность ошибкам.

  • Тестирование производительности и нагрузочное тестирование могут помочь выявить узкие места.

  • Хаос-инженеринг — целенаправленные сбои для проверки отказоустойчивости и проверки гипотез об отказоустойчивости системы.

Надежные инструменты мониторинга и наблюдения обеспечивают обзор состояния системы в режиме реального времени. Это позволяет командам быстро обнаруживать и диагностировать аномалии. Алерты уведомляют инженеров, когда ключевые показатели производительности превышают допустимые пороговые значения. Инженеры должны разобраться и устранить последствия ещё до того, как сбои повлияют на пользователей.

Подход Obeservability позволяет отслеживать основные системные показатели, объединять данные журналов и отслеживать индикаторы работоспособности компонентов. Сопоставление метрик и логов позволяет быстро анализировать основные причины проблем. Для обзора показателей надежности и оценки состояния системы используются дашборды. 

Заключение

Пирамида отказоустойчивости системы помогает сформировать представление о надежности. Следование этим принципам на ключевых уровнях позволяет создавать системы, которые могут лучше противостоять нагрузкам, сбоям и отказам. Это сокращает время простоя и защищает клиентов от хаоса реального мира. Насколько устойчивы ваши системы?


Если вас интересуют SRE-практики, приходите на курс SRE: data-driven подход к управлению надежностью систем. Это курс для опытных инженеров эксплуатации и разработчиков, который помогает освоить SRE-подход и внедрить его в своей компании. Вы научитесь:

  • выбирать и мониторить SRE-метрики (SLO, SLI, error budget) для своего сервиса.

  • реализовывать мониторинг, опознавать и решать проблемы с инфраструктурой;

  • настраивать alerting и healthcheck;

  • использовать разные методы деплоймента, разбираться в инструментах для этого.

На практических занятиях вы станете SRE для сервиса покупки билетов в кинотеатр. Решая предложенные кейсы, вы получите представление, чем занимается SRE в реальности и сможете организовать и возглавить пожарную команду в своей компании.

Старт потока — 22 августа.

Ознакомиться с программой и оставить заявку можно 👉 на нашем сайте.


ссылка на оригинал статьи https://habr.com/ru/companies/southbridge/articles/751536/

Всегда ли хорош Index Only Scan?

Среди применяемых в PostgreSQL методов доступа к данным Index Only Scan стоит особняком, считаясь у многих разработчиков «волшебной пилюлей» для ускорения работы запроса — мол, «Index Scan — плохо, Index Only Scan — хорошо, как только получим его в плане — все станет замечательно«.

Как минимум, это утверждение неверно. Как максимум, при определенных условиях может вызвать проблемы чуть ли не на ровном месте.

Как PostgreSQL хранит данные

Для начала, вспомним, что в PostgreSQL данные таблицы и индексы для нее лежат одинаково, хотя и в разных файлах. Организация физического хранения данных в PostgreSQL, в предельно упрощенном виде выглядит так:

  • каждая таблица или индекс — отдельный файл данных (pg_class.relfilenode)

  • каждый файл делится на физические сегменты, не превышающие 1GB

  • каждый сегмент состоит из последовательности страниц данных по (обычно) 8KB

  • страница данных содержит непосредственно набор записей

  • дополнительно к файлу данных используются два файла с картами: Free Space Map (FSM) и Visibility Map (VM)

Вот как раз с последней и могут возникать проблемы:

Карта может отражать реальные данные с запаздыванием в том смысле, что мы уверены, что в случаях, когда установлен бит, известно, что условие верно, но если бит не установлен, оно может быть верным или неверным. Биты карты видимости устанавливаются только при очистке, а сбрасываются при любых операциях, изменяющих данные на странице.

Как работает Index Scan

Для поиска необходимой записи Index Scan:

  • переходит по записям структуры btree-дерева в файле индекса, пока не дойдет до «листа», указывающего на файл таблицы

  • извлекает из файла таблицы необходимую запись, удостоверяясь в ее «видимости» для текущей транзакции (по значениям xmin/xmax)

Схема работы Index Scan

Схема работы Index Scan

Как работает Index Only Scan

Index Only Scan может быть выбран в качестве способа извлечения записей в случае чтения запросом только полей, которые присутствуют в самом индексе — его ключах или INCLUDE-списке.

По этой причине использование SELECT * FROM ... почти всегда «ломает» возможность использования Index Only Scan.

Для поиска необходимой записи Index Only Scan:

  • переходит по записям структуры btree-дерева в файле индекса, пока не дойдет до «листа», указывающего на файл таблицы

  • проверяет бит «видимости всех записей» нужной страницы с помощью Visibility Map

  • если бит не взведен, ровно как «обычный» Index Scan, извлекает из файла таблицы необходимую запись, удостоверяясь в ее «видимости» для текущей транзакции (по значениям xmin/xmax)

Схема работы Index Only Scan

Схема работы Index Only Scan

Понятно, что со временем для большинства страниц данных в базе бит полной видимости будет взведен, и «в среднем» проверка одного бита VM существенно дешевле, чем извлечение целой записи из файла таблицы.

Проблемы Index Only Scan

Однако, в силу алгоритма заполнения VM, существуют ситуации, когда Index Only Scan всегда придется извлекать запись из таблицы, постоянно проигрывая «обычному» Index Scan на необходимости проверки данных VM.

Процитирую документацию еще раз:

Биты карты видимости устанавливаются только при очистке, а сбрасываются при любых операциях, изменяющих данные на странице.

Из этого предложения следует ровно два паттерна, когда Index Only Scan будет заведомо неэффективен:

  • извлечение «свежевставленных» записей, которые еще не успел пройти [auto]VACUUM

  • наличие даже редких UPDATE/DELETE по произвольно разбросанным по страницам записям

Рассмотрим на простом примере «типа-мониторинга», когда у нас есть метрика (m) и метка времени (ts):

-- создаем исходную таблицу с 1M случайных записей CREATE TABLE ios_orig AS SELECT   (random() * 1e3)::integer m                -- метрика , now() - (random() * '1 msec'::interval) ts -- метка времени FROM   generate_series(1, 1e6);  -- копируем данные в тестовую таблицу CREATE TABLE ios_test AS   TABLE ios_orig;  -- создаем индекс, по которому будем искать CREATE INDEX ON ios_test(m, ts);

Попробуем отобрать часть записей по условию, четко ложащемуся на индекс:

EXPLAIN (ANALYZE, BUFFERS, COSTS OFF) SELECT   m , ts FROM   ios_test WHERE   m > 900 ORDER BY m;

Heap Fetches как признак беды

… и получаем время выполнения дольше 80ms:

Index Only Scan using ios_test_m_ts_idx on ios_test (actual time=0.032..75.224 rows=99094 loops=1)   Index Cond: (m > 900)   Heap Fetches: 99094   Buffers: shared hit=99478 Planning Time: 0.084 ms Execution Time: 80.838 ms

Обратите внимание на строку Heap Fetches — ровно она показывает нам количество записей, которые пришлось «достать» из файла таблицы.

Поскольку в нашем случае записи размазаны «ровным слоем», то пришлось вычитывать по отдельной странице данных для каждой из них.

Index Only Scan vs Index Scan

Давайте попробуем заставить PostgreSQL воспользоваться «обычным» Index Scan:

SET enable_indexonlyscan = FALSE;
Index Scan using ios_test_m_ts_idx on ios_test (actual time=0.028..64.810 rows=99094 loops=1)   Index Cond: (m > 900)   Buffers: shared hit=99478 Planning Time: 0.091 ms Execution Time: 70.010 ms

Мы вычитали ровно столько же данных, но сделали это почти на 15% быстрее! А ведь вся разница — лишь в необходимости проверки VM.

Когда Index Only Scan все-таки выигрывает

Давайте все-таки приведем Visibility Map в порядок — для для этого нам необходимо прогнать VACUUM:

VACUUM ios_test; RESET enable_indexonlyscan;
Index Only Scan using ios_test_m_ts_idx on ios_test (actual time=0.026..15.500 rows=99094 loops=1)   Index Cond: (m > 900)   Heap Fetches: 0   Buffers: shared hit=400 Planning Time: 0.080 ms Execution Time: 20.831 ms

Вот теперь все отлично Heap Fetches: 0, а время выполнения запроса сократилось в 4 раза!


Подведем промежуточные итоги:

Exec Time

Buffers

Index Only Scan + bad VM

80.838 ms

99 478

Index Scan

70.010 ms

99 478

Index Only Scan + good VM

20.831 ms

400

То есть при актуальном состоянии VM мы получаем кратный прирост производительности запроса, а ее неактуальное состояние мы можем контролировать с помощью метрики Heap Fetches.

А чтобы вы могли быстро понять причину подобной проблемы в своем запросе, мы добавили в наш сервис анализа планов explain.tensor.ru в набор автоматических рекомендаций еще и эту проверку:

Heap Fetches > 10% всех записей — крайне подозрительно, а уж 100% — и подавно!» title=»Heap Fetches > 10% всех записей — крайне подозрительно, а уж 100% — и подавно!» width=»970″ height=»230″ data-src=»https://habrastorage.org/getpro/habr/upload_files/c87/1a1/a52/c871a1a52c97deadbc2950c6cb9c3814.png»/></p>
<div><figcaption>Heap Fetches > 10% всех записей — крайне подозрительно, а уж 100% — и подавно!</figcaption></div>
</figure>
<hr/>
<p>А в завершение еще несколько слов о том, как можно себе все сломать и снова починить.</p>
<h2>Рулим VACUUM’ом</h2>
<p>Итак, мы выяснили, что пока по данным еще не успел пройти процесс <a href=autovacuum (или пока мы не выполним VACUUM вручную), они очень «дорого» обходятся Index Only Scan.

В этом достаточно просто убедиться, докинув в нашу таблицу еще столько же данных:

INSERT INTO ios_test   TABLE ios_orig;
Index Only Scan using ios_test_m_ts_idx on ios_test (actual time=0.036..86.064 rows=198188 loops=1)   Index Cond: (m > 900)   Heap Fetches: 99102   Buffers: shared hit=99660 Planning Time: 0.133 ms Execution Time: 96.159 ms

Записей теперь отбирается вдвое больше, но в Heap Fetches попала только «новая» часть — поэтому в наших интересах заставить VACUUM выполняться как можно чаще на нашей таблице.

Для этого существует несколько способов, игнорирование которых может вызвать проблемы:

  • autovacuum_naptime — этот параметр определяет как часто должен стартовать autovacuum-процесс

    Если вдруг вы решили сэкономить на «паразитной» активности PostgreSQL и выкрутили значение до '1d' (раз в день), а потом массово что-то записали в таблицу, то будете получать Heap Fetches все это время.

    Поэтому нелишним может оказаться «ручной» запуск VACUUM после каких-то больших импортов или по таймеру — например, мы таким способом по ночам актуализируем данные в секции прошедших суток.

  • autovacuum_vacuum_threshold и autovacuum_vacuum_insert_threshold (плюс autovacuum_vacuum_scale_factor и autovacuum_vacuum_insert_scale_factor) отвечают за автоматический запуск VACUUM на таблице после изменения/вставки определенного количества (или процента) записей

    Они могут быть определены как для всего сервера в целом, так и заданы для определенных, наиболее «горячих», таблиц с помощью команды

    ALTER TABLE ... SET (   autovacuum_vacuum_threshold = ... );


ссылка на оригинал статьи https://habr.com/ru/companies/tensor/articles/751458/

Security Week 2331: аппаратная уязвимость в процессорах AMD

На прошлой неделе исследователь Тавис Орманди из команды Google Information Security опубликовал подробности о новой уязвимости в процессорах AMD поколения Zen 2. Эта аппаратная проблема связана с ошибкой в логике работы процессоров, которую AMD, к счастью, решает обновлением микрокода. Исправление уже вышло, но пока только для серверных процессоров EPYC, где эта проблема наиболее актуальна. Патчи для десктопных и мобильных процессоров Zen 2, которые выпускались в период с 2019 по 2022 год, AMD обещает выпустить до конца 2023 года.

Пример на скриншоте выше максимально коротко описывает уязвимость. На GitHub также выложен работающий Proof of Concept. При удачном стечении обстоятельств Zenbleed позволяет извлекать информацию, хранящуюся в регистрах процессора, надежно и быстро: до 30 килобайт в секунду на каждое ядро. Хотя PoC написан под Linux, эксплуатация уязвимости возможна в любой операционной системе.

В своей публикации Тавис Орманди объясняет суть уязвимости. Ключевой ошибкой оказалась особенность работы vzeroupper — инструкции, предназначенной для обнуления «верхней части» 256-битных векторных регистров YMM. Эти регистры появились в 2008 году вместе с расширенным набором инструкций AVX и с тех пор широко используются в процессорах x86-64. В качестве примера использования регистров YMM и инструкции vzeroupper Орманди приводит функцию strlen из библиотеки glibc. При спекулятивном выполнении инструкции и последующем откате регистр остается в «неопределенном» состоянии, что и открывает возможность чтения данных, которые записывают в регистровый файл другие процессы. Орманди, в частности, показал вариант, при котором вредоносная программа может перехватывать пароли и ключи шифрования других пользователей в системе.

Опасность Zenbleed в реальных условиях остается неизвестной. Специалисты из Cloudflare в своем обзоре допускают теоретическую возможность проведения атаки даже через браузер. Уязвимость в процессорах Zen 2 имеет общие черты с атаками класса Spectre: все они так или иначе полагаются на особенности спекулятивного выполнения инструкций, то есть фундаментальную фичу современных процессоров. Но есть важное отличие: в атаках Spectre секретные данные «вытаскиваются» по сторонним каналам, крайне медленно и с ошибками. Zenbleed позволяет считывать информацию из регистров напрямую и с высокой скоростью.

Отдельным интересным моментом является способ обнаружения уязвимости: Орманди нашел Zenbleed с помощью фаззинга. В своей публикации он обсуждает особенности применения данного метода для поиска аппаратных уязвимостей и предлагает способы организации подобного тестирования. Например, одновременный запуск одного и того же кода на двух разных процессорах. Если код при этом ведет себя по-разному, это может указывать на аномалию.

Что еще произошло:

Эксперты «Лаборатории Касперского» опубликовали отчет по эволюции таргетированных атак за второй квартал 2023 года.

Серьезная уязвимость (CVE-2023-30799, рейтинг CVSS 9,1 балла) закрыта в роутерах Mikrotik.

Команда Google Threat Analysis Group выпустила очередной отчет об уязвимостях zero-day (это проблемы, которые активно эксплуатируются на момент обнаружения) с данными за 2022 год. Всего за этот год была выявлена 41 уязвимость такого типа против рекордных 69 в 2021 году. 40% из этих уязвимостей являлись вариантами ранее обнаруженных проблем. Особое внимание эксперты уделили уязвимостям, которые к zero-day не относятся, но имеют похожие последствия из-за задержки в выпуске патча. В качестве примера исследователи приводят уязвимости в Android. Например, одна проблема была найдена в ноябре 2022 года, а закрыта только в апреле 2023-го. В промежутке между этими датами уязвимость использовалась в реальных атаках.

Apple в конце этого года начнет требовать у разработчиков объяснения при использовании определенных API. Целью этого нововведения является ограничение возможностей идентификации пользователя по уникальному набору характеристик его устройства, установленного ПО и подобного.


ссылка на оригинал статьи https://habr.com/ru/companies/kaspersky/articles/751564/

Монетизация фреймворков

Сап, хабр! Надеюсь это приветствие не оскорбило какую-нибудь группу местных сторожил, я просто тут новенький. И поговорить хотел бы не о себе, а про денежки.

Как начинающий разработчик я пытаюсь влить в себя максимум информации о технологиях, которые меня сейчас интересуют, и тут всё отлично, никакого дефицита в информации не испытываю, ее огромное количество. Скорее не хватает часов в сутках, чтобы всё успеть охватить. Но это что касается разработки, а вот вопрос заработка стеснительные разрабы как-то не особо любят обсуждать, полагаю из-за неких суеверий. Но ок, показывать карман — это исключительное право каждого.

В таком случае никому не будет обидно, если мой вопрос будет построен на основе открытых данных о доходах известного проекта, которые автор не пытается скрывать. Речь пойдет о «современном и полностью асинхронном фреймворке для Telegram Bot API, написанном на Python с использованием asyncio» Aiogram, который вряд ли нуждается в представлении для тех, кто делает вещи на Python. Для остальных же исчерпывающее описание было в кавычках. Так вот, по самым скромным оценкам этим фреймворком пользуются десятки тысяч разработчиков, которые успешно монетизируют его решения, и иногда зарабатывают не то чтобы прилично, а даже совсем наоборот (по слухам). А что же заработал автор проекта Олександр Онуфрийчук?

Задавшись этим вопросом я зашел на официальный сайт проекта и нашел ссылку financial contributors, пройдя по которой обнаружил заветные цифры о финансовой поддержке проекта.

Это все мы

Это все мы

И, честно говоря, цифры меня немного удивили. Так как если им верить, получается, что поклонников у полезного инструмента гораздо меньше, чем у самой средней барышни в Тик-Токе, и финансово его поддержало не более 48 человек. А общий бюджет проекта составляет всего лишь 875,11 американских президентов, из которых за последний год получено лишь 60.

Общий бюджет aiogram

Общий бюджет aiogram

И вопрос у меня такой: действительно ли дело не в деньгах и автор aiogram развивает его исходя из более высоких побуждений, нежели простая корысть? Или же я просто не понимаю модели монетизации таких проектов? Возможно такие проекты изначально не имеют цели по извлечению прибыли и создаются для уплотнения личного резюме, и повышения ценности как специалиста?

Спасибо за ваши мнения и внимание.


ссылка на оригинал статьи https://habr.com/ru/articles/751576/

Мы создали большой диалоговый датасет

Те, кто когда-нибудь хотел обучить своего диалогового чат-бота, непременно сталкивались с отсутствием датасетов с адекватными диалогами. В открытом доступе, в основном, лишь наборы комментариев с Пикабу и Хабра, парсинг телеграм чатов, и диалоги из литературы. Мягко говоря, всё это «не очень». Поэтому, мы решили использовать ChatGPT для генерации подходящего датасета. На его создание ушло около недели времени и 70 долларов, заплаченных за токены OpenAI API.

Ссылка на датасет: ссылка

Мы решили, что неплохо было бы как-то влиять описанием личности персонажа на ход диалога. Был вручную написан список из ста кратких описаний личностей, половина из которых были мужчинами, а вторая половина женщинами. Внимание уделялось ее увлечениям, интересам и любимым занятиям. Формат у описаний был следующий:

Ты парень, программист. Увлекаешься эзотерикой и конспирологией. Пишешь фантастику. Любишь природу.
Ты девушка, воспитатель детского сада. Увлекаешься чтением классики. Пишешь стихи. Любишь природу.

Далее, мы придумали для каждой из личностей некоторое количество тем (5-10), которые ей могли бы быть интересны и сгенерировали диалоги для каждой личности по этим темам. Если бы мы на этом остановились, то личности бы придерживались только нескольких заданных в описании тематик. Поэтому, для каждой личности были созданы дополнительные сэмплы на темы из общего списка повседневных тем (более 100).

Все диалоги начинаются с реплики собеседника, и имеют четное количество фраз.

Кроме того было еще несколько отдельных промптов, для обобщенных образов мужчины/женщины, отличающихся не своими увлечениями, а скорее целями, которые они преследуют в диалогах.

Вот список этих промптов:

  1. Ты очень умный парень, и хочешь помочь своему другу полезными советами.

  2. Ты парень, консультант по разным вопросам. Ты очень умный. Любишь помогать собеседнику.

  3. Ты умный мужчина-профессор, с научным взглядом на мир. Твои ответы продуманны, и полезны.

  4. Ты очень позитивный парень — оптимист, и хочешь поднять настроение другу.

  5. Ты парень, и твоя задача поддерживать и углублять тему диалога, демонстрируя прекрасную осведомленность в тематике.

  6. Ты парень и у тебя всегда есть собственное мнение на любую тему.

  7. Ты парень, и хочешь узнать о собеседнике как можно больше информации, поэтому расспрашиваешь о его предпочтениях согласно тематике диалога.

  8. Ты любопытный парень, который хочет обучиться чему-то новому, поэтому постоянно задаешь вопросы, и углубленно интересуешься тематикой разговора.

  9. Ты одинокий парень, и твоя цель соблазнить девушку-собеседника, флиртовать с ней.

  10. Ты влюбленный в собеседницу парень. Ты готов для нее на всё, и хочешь, чтобы она был счастлива.

  11. Ты очень умная девушка, и хочешь помочь своему другу полезными советами.

  12. Ты девушка, и твоя задача поддерживать и углублять тему диалога, демонстрируя прекрасную осведомленность в тематике.

  13. Ты девушка, склонная к философии, и эзотерическому взгляду на мир. Ты любишь цитировать Кастанеду, и успокаивать собеседника мудрыми фразами.

  14. Ты любопытная девушка, которая хочет обучиться чему-то новому, поэтому постоянно задаешь вопросы, и углубленно интересуешься тематикой разговора.

  15. Ты влюбленная в собеседника девушка. Ты готова для него на всё, и хочешь, чтобы он был счастлив.

  16. Ты умная девушка-профессор, с научным взглядом на мир. Твои ответы продуманны, и полезны.

  17. Ты очень позитивная девушка оптимистка, и хочешь поднять настроение другу.

  18. Ты девушка, и хочешь узнать о собеседнике как можно больше информации, поэтому расспрашиваешь о его предпочтениях согласно тематике диалога.

  19. Ты одинокая девушка, и твоя цель соблазнить собеседника, флиртовать с ним.

  20. Ты девушка и у тебя всегда есть собственное мнение на любую тему.

  21. Ты всегда позитивная оптимистка.

  22. Ты девушка, говоришь со своим любимым парнем.

  23. Ты прикольная девушка.

  24. Ты заботливая жена, говоришь со своим мужем.

Диалоги для этих промптов были сгенерированы на гораздо более широкий список тем (более 400).

В датасете также присутствует переработанный TolokaPersonaChatRus от Яндекса, формат промпта там идёт от первого лица, например:

У меня любимая работа. Я уважаю людей. У меня есть животное. У меня хороший друг. Я люблю кофе.

Есть ещё небольшое количество сэмплов с решением проблем, так что, обученные на датасете модели будут понимать следующий промпт:

Проблема: [Описание проблемы] Решение:  

В конце, нужно было добавить фактологичности, поэтому, мы взяли несколько инструкционных наборов данных ru_turbo_alpaca_evol_instruct, ru_turbo_saiga, ru_instruct_gpt4 и сильно профильтровав, добавили в наш датасет необходимые сэмплы. Если кто-то будет использовать его, то просьба учитывать, что он более ориентирован на ведение диалога, а не на инструкционную часть.

Кроме того, мы обучили на нашем датасете FRED-T5 от Сбера, и получили неплохую диалоговую модель.

Датасет

Т.к модель предобучалась с использованием денойзеров, мы подавали на вход данные в следующем формате.

Для диалогов:

<SC6>Описание личности. Продолжи диалог: Собеседник: Реплика собеседника Ты: <extra_id_0> 

Для инструкций:

<SC6>[Инструкция с приглашением для ответа] <extra_id_0> 

Обучение

Код файтюнинга модели:

import json from typing import Optional import logging from dataclasses import dataclass, field import tqdm import torch import torch.optim from torch.utils.data import Dataset import transformers from transformers import TrainingArguments, Trainer from transformers import HfArgumentParser from pynvml import *   def print_gpu_utilization():     nvmlInit()     handle = nvmlDeviceGetHandleByIndex(0)     info = nvmlDeviceGetMemoryInfo(handle)     logger.info(f"GPU memory occupied: {info.used // 1024 ** 2} MB.")   def load_samples(dataset_path, tokenizer):     samples = []     with open(dataset_path, 'r') as f:         for sample in tqdm.tqdm(json.load(f)):             try:                 seed = '<SC6>' + sample['input'] + '<extra_id_0>'                 reply = '<extra_id_0>' + sample['output']                 input_tokens = tokenizer.encode(seed, add_special_tokens=False, truncation=True, max_length=1024)                 output_tokens = tokenizer.encode(reply, add_special_tokens=False)                 if len(input_tokens) < 768 and len(output_tokens) < 768:                     samples.append({'input_tokens': input_tokens, 'output_tokens': output_tokens})             except Exception as ex:                 print(ex)      return samples   class SFTDataset(Dataset):     def __init__(self, samples, tokenizer):         self.tokenizer = tokenizer         self.max_input_len = 0         self.max_output_len = 0         self.samples = []          self.bos_token_id = tokenizer.encode('<s>', add_special_tokens=False)[0]         self.eos_token_id = tokenizer.encode('</s>', add_special_tokens=False)[0]         self.pad_token_id = tokenizer.encode('<pad>', add_special_tokens=False)[0]          for sample in samples:             input_ids = sample['input_tokens']             output_ids = sample['output_tokens'] + [self.eos_token_id]             self.samples.append((input_ids, output_ids))             self.max_input_len = max(self.max_input_len, len(input_ids))             self.max_output_len = max(self.max_output_len, len(output_ids))      def __len__(self):         return len(self.samples)      def __getitem__(self, index: int):         input_ids, output_ids = self.samples[index]          input_npad = self.max_input_len - len(input_ids)         attention_mask = [1] * len(input_ids) + [0] * input_npad         input_ids = input_ids + input_npad * [self.pad_token_id]          output_npad = self.max_output_len - len(output_ids)         labels = output_ids + output_npad * [-100]          return {'input_ids': torch.LongTensor(input_ids), 'attention_mask': attention_mask,                 'labels': torch.LongTensor(labels)}   @dataclass class ModelArguments:     model_name_or_path: Optional[str] = field(metadata={"help": "The model checkpoint for weights initialization."})   @dataclass class DataTrainingArguments:     dataset_path: Optional[str] = field(metadata={"help": "Путь к датасету с диалогами"})   if __name__ == '__main__':     parser = HfArgumentParser((ModelArguments, DataTrainingArguments, TrainingArguments))      model_args, data_args, training_args = parser.parse_args_into_dataclasses()      verbose = training_args.local_rank in (-1, 0)      # Setup logging     logging.basicConfig(         format="%(asctime)s - %(levelname)s - %(name)s - %(message)s",         datefmt="%m/%d/%Y %H:%M:%S",         handlers=[logging.StreamHandler(sys.stdout)],     )      log_level = training_args.get_process_log_level()     logger = logging.getLogger(__name__)     logger.setLevel(logging.INFO)     transformers.utils.logging.set_verbosity(log_level)     transformers.utils.logging.enable_default_handler()     transformers.utils.logging.enable_explicit_format()      logger.info(         f"Process rank: {training_args.local_rank}, device: {training_args.device}, n_gpu: {training_args.n_gpu}"         + f"distributed training: {bool(training_args.local_rank != -1)}, 16-bits training: {training_args.fp16}"     )     logger.info(f"Training/evaluation parameters {training_args}")      rank0 = training_args.local_rank in (-1, 0)      device = training_args.device     logger.info('device={}'.format(device))      pretrained_model_name = model_args.model_name_or_path      logger.info('Loading pretrained model "%s"', pretrained_model_name)     tokenizer = transformers.AutoTokenizer.from_pretrained(pretrained_model_name)     model = transformers.T5ForConditionalGeneration.from_pretrained(pretrained_model_name, torch_dtype=torch.bfloat16)     model.to(device)      tokenizer.add_special_tokens({'bos_token': '<s>', 'eos_token': '</s>', 'pad_token': '<pad>'})      if rank0:         print_gpu_utilization()         logger.info('\nTokenizer:')         for token in '<s> </s> <pad>'.split():             logger.info('token "{}" id={}'.format(token, str(tokenizer.encode(token, add_special_tokens=False))))      logger.info('Loading dataset "{}"...'.format(data_args.dataset_path))     train_samples = load_samples(data_args.dataset_path, tokenizer)     logger.info('Train samples: {}'.format(len(train_samples)))      train_dataset = SFTDataset(train_samples, tokenizer)     trainer = Trainer(         model=model,         args=training_args,         train_dataset=train_dataset,         tokenizer=tokenizer,         data_collator=None,     )      try:         logger.info('Start training...')         train_result = trainer.train(resume_from_checkpoint=True)          if rank0:             metrics = train_result.metrics             trainer.log_metrics("train", metrics)             trainer.save_metrics("train", metrics)     except KeyboardInterrupt:         print('!!! Ctrl+C !!!')      logger.info('Saving the model and tokenizer')     trainer.save_model(output_dir=training_args.output_dir)     tokenizer.save_pretrained(training_args.output_dir) 

Модель обучалась 80 часов на RTX 4090 с параметры обучения:
Optimizer: Adafactor
Learning rate: 1e-4 (0.0001)
Lr scheduler type: constant
Batch Size: 1
Gradient accumulation steps: 35
Num epochs: 3
Bf16: true

Лосс

Запуск и использование

Ниже представлен код скрипта инференса модели FRED-T5, который был обучен на нашем датасете.

Диалог:

import torch import transformers  use_cuda = torch.cuda.is_available() device = torch.device("cuda" if use_cuda else "cpu") t5_tokenizer = transformers.GPT2Tokenizer.from_pretrained("SiberiaSoft/SiberianPersonaFred") t5_model = transformers.T5ForConditionalGeneration.from_pretrained("SiberiaSoft/SiberianPersonaFred")   while True:     print('-'*80)     dialog = []     while True:         msg = input('H:> ').strip()         if len(msg) == 0:             break         msg = msg[0].upper() + msg[1:]         dialog.append('Собеседник: ' + msg)         # В начале ставится промпт персонажа.         prompt = '<SC6>Ты парень, консультант по разным вопросам. Ты очень умный. Любишь помогать собеседнику. Продолжи диалог:' + '\n'.join(dialog) + '\nТы: <extra_id_0>'          input_ids = t5_tokenizer(prompt, return_tensors='pt').input_ids         out_ids = t5_model.generate(input_ids=input_ids.to(device), do_sample=True, temperature=0.9, max_new_tokens=512, top_p=0.85,                                       top_k=2, repetition_penalty=1.2)         t5_output = t5_tokenizer.decode(out_ids[0][1:])         if '</s>' in t5_output:             t5_output = t5_output[:t5_output.find('</s>')].strip()          t5_output = t5_output.replace('<extra_id_0>', '').strip()         t5_output = t5_output.split('Собеседник')[0].strip()         print('B:> {}'.format(t5_output))         dialog.append('Ты: ' + t5_output) 

Инструкции:

from transformers import AutoTokenizer, AutoModelForSeq2SeqLM import torch  device = torch.device("cuda" if torch.cuda.is_available() else "cpu") tokenizer = AutoTokenizer.from_pretrained("SiberiaSoft/SiberianPersonaFred") model = AutoModelForSeq2SeqLM.from_pretrained("SiberiaSoft/SiberianPersonaFred").to(device).eval()   def generate(prompt):   data = tokenizer('<SC6>' + prompt + '<extra_id_0>', return_tensors="pt").to(device)   output_ids = model.generate(       **data,  do_sample=True, temperature=0.9, max_new_tokens=512, top_p=0.95, top_k=5, repetition_penalty=1.03, no_repeat_ngram_size=2   )[0]   out = tokenizer.decode(output_ids.tolist(), skip_special_tokens=True)   return out  while 1:   print(generate(input(":> ")))  

Модель доступна для скачивания вот тут: ссылка

Скриншоты диалогов

Фидбек привествуется!


ссылка на оригинал статьи https://habr.com/ru/articles/751580/