Привет, Хабр! Меня зовут Анна Щеникова, я аналитик в Центре RnD в МТС Диджитал. Ко мне часто приходят задачи, где нужно использовать open-source LLM. Сразу же встает вопрос: а как адаптировать имеющуюся модель под конкретный кейс?
Мы выделяем четыре уровня адаптации. Для этого смотрим, какие потребуются навыки для решения этой задачи, сколько времени и человекочасов займет разработка. Поняв требуемый уровень, мы можем поставить себе дедлайны на проверку гипотезы и запланировать действия, если задача не решится выбранным способом. Ниже я расскажу, как мы разделяем разные уровни адаптации, что делаем на каждом из них и когда переходим на следующий.
Допустим, у нас есть задача: сгенерировать суммаризацию по тексту, код по описанию, сделать перевод и так далее. С чего мы начинаем? Как вариант, идем в ChatGPT, отправляем запрос и мгновенно получаем решение. Не факт, что хорошее, но это какой-никакой ответ на наш вопрос.
Тут мы с нуля используем готовые продукты. В технологиях GenAI у вас есть выбор между облачными продуктами, например, от OpenAI или open-source моделями (Mistral, LLama или Qwen), которые скачиваются и ставятся локально.
У каждого варианта есть свои плюсы и минусы:
OpenAI и Anthropic все время соревнуются между собой в качестве и выдают очень хороший результат, а вот open-source пока еще от них отстает. Например, недавно вышел релиз GPT-4o-mini, и у OpenAI снова появилось дешевое и топовое решение. Более того, совсем недавно вышла еще одна версию старшей модели GPT-4о с ценами, сниженными почти в два раза. Конкуренты тоже не сидят на месте: в лидерборде появились Gemini-1.5-Pro-Exp-0801 — новая модель на уровне с GPT-4o, а Claude 3.5 Sonnet не сильно отстает от GPT-4o-mini.
Увы, чаще всего готовых решений не хватает и модели приходится дополнительно адаптировать.
На этом уровне мы помогаем модели решать задачи с помощью промптов. Для этого перебираем разные варианты и ищем, какой из них даст самый качественный инференс.
Промпт-инжиниринг — это управление поведением модели с помощью специфического запроса. Например, с системным сообщением для спецификации поведения («ты топовый маркетолог») и с сообщением от пользователя для определения задачи («придумай маркетинговую стратегию для повышения продаж кредитных карт»).
Он включает много техник и приемов, которые можно посмотреть тут:
Руководство по промпт-инжинирингу: все про промпты, параметры генерации и области применения. Максимально просто и доступно.
Как найти общий язык с LLM | Наталья Бруй и Алена Шувар | True Tech Day 2.0 — наши коллеги из MTS AI рассказали про задачи и возможности промпт-инжиниринга и поделились опытом jailbreak LLM.
Вот так корректируется «личность» модели:
А вот так меняются данные и добавляются требования:
В этом примере видно, что без системной настройки мы получаем слишком подробный ответ. Стоит нам добавить требование в базовый промпт, как модель даст максимально релевантный короткий ответ.
Еще одна классная техника — chain-of-thoughts (CoT). Она безумно помогает в работе. С ее помощью можно заставить модель генерировать сложные рассуждения с использованием промежуточных шагов или решить задачу поэтапно, а не всю целиком:
Например, мы хотим создать отчет по практике на тему «Использование генеративных ИИ-моделей в задаче суммаризации». Можем отправить запрос в LLM без изменений и получим:
Отчет по практике на тему «Использование генеративных ИИ-моделей в задаче суммаризации»В рамках практики было исследовано применение генеративных искусственных интеллект-моделей (ГИИ) для задачи автоматической суммаризации текста. Задача суммаризации состоит в создании краткого и информативного обзора основных идей и фактов, содержащихся в исходном тексте. Генеративные модели представляют собой мощный инструмент для автоматического формирования текстовых выводов на основе входных данных.
Целью данной практики было изучение возможностей и эффективности генеративных ИИ-моделей в контексте задачи суммаризации текста. Основной акцент делался на сравнении различных подходов и моделей, их применимости и результативности.
Выбор моделей: в работе были выбраны различные генеративные модели, такие как Transformer-based модели (например, GPT, BERT), Seq2Seq модели с механизмом внимания (Attention) и другие.
Предобработка данных: исходные тексты подвергались предварительной обработке для подготовки к использованию моделями. Это включало токенизацию, очистку от шума и приведение к стандартному формату.
Обучение и тестирование: модели обучались на размеченных данных, затем их результаты сравнивались с реальными суммаризациями для оценки качества.
Оценка качества: для оценки качества сгенерированных суммаризаций использовались метрики ROUGE (Recall-Oriented Understudy for Gisting Evaluation), BLEU (Bilingual Evaluation Understudy) и другие.
Практика показала, что генеративные ИИ-модели демонстрируют высокий потенциал в задаче автоматической суммаризации текста. Однако эффективность моделей может сильно варьироваться в зависимости от выбора конкретной модели, размера и качества обучающей выборки, а также от параметров обучения.
Преимущества: генеративные модели способны улавливать семантическую информацию и создавать качественные и краткие суммаризации, сохраняя ключевые аспекты текста.
Ограничения: некоторые модели могут страдать от проблемы «затухающего градиента», требуя больших объемов данных для обучения, что может сделать их применение затратным и трудоемким процессом.
Использование генеративных ИИ-моделей в задаче суммаризации текста представляет собой перспективный исследовательский и практический направление. В дальнейшем планируется расширение экспериментов на более широкий спектр текстовых данных и моделей для получения более точных и обобщенных результатов.
Так как мы не обучали модель специально, в ответе нет подробного описания каждой из частей и есть фактические ошибки. Разобьем инференс на четыре этапа:
Попросим сгенерировать введение с указанием цели, задач и уровня актуальности решения.
Отдельно предложим написать основную часть с указанием методологии. Для генерации классного саммари можно использовать не только относительно маленькие обученные модели типа T5, но и более продвинутые подходы. Например, подход Retrieval: разделим текст на блоки и последовательно подадим их один за другим. После первого блока попросим сгенерировать черновик саммари, а следующие блоки будем использовать для улучшения. Или можно интегрировать CoT: так вместо суммаризации всего текста сразу LLM будет суммаризировать по отдельности каждый из блоков, а потом объединит их в единое саммари.
Отдельно сформируем выводы и результаты. Попросим модель указать, что мы сравнили использование классических моделей для суммаризации с промпт подходами, использовали не только стандартные метрики, но и, например, SBS-оценку человеком. И выяснили что промпт-подход лучше, но не когда нам нужно саммари по определенной структуре.
Создаем заключение из сгенерированных ответов.
Техника CoT помогает решать задачи генерации объемных текстов (написание отчета) или подробного анализа больших данных (суммаризация книги). Классный пример: в этой статье CoT использовался для сбора данных и решения задач по математике.
Есть много других полезных техник. В Few-shot inference перед самим запросом ставится пример ожидаемого результата, что дает более точные ответы:
Few-shot отличается и от single-shot, и от zero-shot. В single-shot мы уточняем ожидаемый результат, а в zero-shot отправляем запрос напрямую.
Все эти техники можно использовать для простых диалогов с ChatGPT. Они позволяют быстро получить желаемый результат. Надо учитывать, что промптинг — это итеративная история, на которую может уйти много попыток. Если нужный ответ никак не получается, нужно идти дальше.
Есть крутой и перспективный фреймворк Langchain, который позволяет использовать LLM-модели на полную мощь. Он поддерживает разные функции: промпты, индексы цепочки, агенты и работу с памятью. В этой статье я остановлюсь на агентах, потому что они связывают все остальные компоненты в единый пайплайн.
Агенты — это глубокая промптовая настройка, которая «имитирует» у модели мыслительный процесс и дополнительно улучшает собственные ответы с помощью разных инструментов. Другими словами, промптами мы заставляем модель рассуждать. Это уже сложнее, чем шаги в методе CoT.
Как же оформляется промпт, чтобы модель могла «думать, как человек»? Агент задает LLM мыслительный процесс, заставляя ее генерировать цепочки вида «мысль — действие — результат». Покажу на примере:
Пользователь задал вопрос модели: «Сколько будет 2+2?»
У нас есть текущее состояние: запрос 2+2.
Мысль. Запрос можно посчитать калькулятором.
Действие. Считаем на калькуляторе 2+2.
Результат. Мы получаем новое текущее состояние: 4.
Мысль. Этого достаточно, чтобы ответить пользователю.
Действие. Возвратить ответ
Финальный ответ: 4.
Агент повторяет цикл из «состояние — мысль — действие», пока не дойдет до финального ответа, а LLM реализует шаги с помощью промптов.
Давайте разберем работу агента на практике:
У нас есть LLM, которую мы хотим использовать в этой задаче. Пусть это будет ChatGPT. Мы отправляем ему вопрос и получаем ответ:
llm = ChatOpenAI(
model_name= "gpt-4o",
max_tokens= 1024,
verbose=True,
api_key=os.getenv("API_KEY"),
base_url=os.getenv("API_BASE")
)
Мы можем добавить модели функциональности с помощью дополнительных инструментов (tools): калькулятора, выхода в интернет, генерации картинок по тексту и так далее. Эти функции принимают на вход данные и возвращают результат. В нашем случае подключим serpapi для выхода в интернет и калькулятор в утилите llm-math:
extra_tools = ["serpapi", "llm-math"]
tools = load_tools(extra_tools, llm=self.llm)
У нас есть промпты, которые позволяют имитировать мысленный процесс. Для этого мы просим, чтобы модель сначала генерировала цепочки рассуждений в виде «мысль — действие — результат», а потом возвращала json-разметку для совершения какого-то действия (action, вызов инструмента из tools):
SYSTEM_PROMPT = “You are helpful assistant”
AGENT_CHAIN_FORMAT_INSTRUCTIONS = """
TOOLS:
------
{tools}
Use a json blob to specify a tool by providing an action key (tool name) and an action_input key (tool input).
Valid "action" values: "Final Answer" or tool_names {tool_names}
Provide only ONE action per $JSON_BLOB, as shown:
```
{{
"action": $TOOL_NAME,
"action_input": $INPUT
}}
```
Follow this format:
Question: input question to answer
Thought: consider previous and subsequent steps
Action:
```
$JSON_BLOB
```
Observation: action result
... (repeat Thought/Action/Observation N times)
Thought: I know what to respond
Action:
```
{{
"action": "Final Answer",
"action_input": "Final response to human"
}}
```
Reminder to ALWAYS respond with a valid json blob of a single action. Use tools if necessary.
RETURN ONLY THIS JSON BLOB and nothing else!"""
HUMAN = '''{input}
{agent_scratchpad}
(remember return only json blob)'''
prompt = ChatPromptTemplate.from_messages(
[
("system", SYSTEM_PROMPT + AGENT_CHAIN_FORMAT_INSTRUCTIONS),
MessagesPlaceholder("chat_history", optional=True),
("human", HUMAN),
]
)
return prompt
Все это объединяется в одну структуру и становится агентом, который умеет парсить json-разметку и запускать необходимые действия (tools) в зависимости от запроса пользователя:
memory = ChatMessageHistory(session_id="test-session")
agent = create_structured_chat_agent(llm, tools, prompt=prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools, return_intermediate_steps=True)
agent = RunnableWithMessageHistory(
agent_executor,
lambda session_id: memory,
input_messages_key="input",
history_messages_key="chat_history")
def _generate_answer(self, text, chat_id):
res = self.agent.invoke({"input": text}, config={"configurable": {"session_id": chat_id}})
return res["output"]
Полный код можно найти по ссылке на google colab.
RAG — это специфическая утилита для работы разными с источниками. Для ответов она использует релевантный вопросу контекст. Например, может выйти в интернет или в базу знаний с нашим запросом, найти подходящие ссылки и сформировать по ним точный ответ:
Структура работы RAG выглядит так:
Пользователь задает вопрос.
Вопрос перенаправляется в хранилище знаний, где с помощью алгоритма происходит поиск подходящей информации для ответа.
Формируется перечень полезной информации (релевантных знаний, например, нужных документов).
И вопрос, и релевантные знания отправляются в LLM.
На основе вопроса и релевантных знаний из базы данных LLM создает ответ.
При работе с RAG всегда возникают две сложности:
как сделать поиск? Есть более простые решения, такие как Elastic Search. Они дают хорошее качество поиска на легких запросах. Более сложные — Vector Search и Knowledge graph — лучше работают с семантическим поиском;
какую использовать LLM? Практика показывает, что быстро получить хорошее качество RAG можно только на GPT-моделях. Open-source (Mistral, LLama) придется дополнительно настраивать.
Время адаптации растягивается в зависимости от выбранных типов поиска и LLM. Чем сложнее их адаптировать, тем больше времени уходит на настройку пайплайна под бизнес-требования.
Полезные ссылкиAgents from LangСhain: быстрый старт работы с агентами.
AgentСhain: github-репозиторий, где мультимодальность реализуется через агент. По ссылке много видеопримеров.
Свой чат по документам на python с помощью LangChain и RAG: относительно простая видеоинструкция.
Создание приложения RAG с нуля с использованием Python, LangChain и API OpenAI: еще один туториал, но уже более сложный.
Документация Langchain: подробно рассказывается про функции LangChain.
Что такое RAG: обзорная статья на Хабре.
К этому уровню мы подходим, когда попробовали все предыдущие, но так и не смогли выполнить свою задачу. Промпт-инжиниринга и интеграции агентов недостаточно, а open-source LLM плохо работают в агентах. Проявляются такие симптомы:
не хватает фактологической точности, то есть соответствия заранее определенным фактам. Например, искажается числовая информация (на вход было 15%, а модель сгенерировала 25%), перевираются факты (мы написали, что 2+2=4, а модель выдает, что 2+2=5) и т.д. Как бы мы ни настраивали наши промпты, результат будет нестабилен. Сегодня фактологическая точность 90%, завтра меняются условия инференса (окружение, карточки), и она падает до 50%. Да, даже если зафиксировать соответствующие гиперпараметры. Плавали, знаем.
Пример из жизни. С помощью различных техник Prompt-engineering (CoT, обьяснение терминов) на одном проекте по суммаризации я добилась фактологической точности около 90%. Но на следующий день у меня что-то случилось с докер-контейнером, и модель вместо адекватного саммари генерировала слово «живело» без остановки. Мы долго искали, в чем проблема, а потом еще три недели тюнили промпты, чтобы вернуть те заветные 90%. Через три недели ситуация повторилась, и результат снова пришлось развивать с нуля;
модель галлюцинирует. Отвечает русскими словами на латинице, переходит на английский язык, неправильно интерпретирует входную информацию и генерирует несвязный текст. Это можно пофиксить промптами, но часто их все равно не хватает;
отсутствуют специфичные навыки. Иногда нужно интегрировать модель в RAG, но она не может найти ответ по контексту. Мне приходилось обучать модель ответам на вопросы, иначе она плохо справлялась с двумя противоречащими друг другу документами. Например, у нас были разные даты публикации и модель не знала, что нужно использовать информацию из более позднего документа;
не может справиться с определенной задачей. Например, модель хорошо справляется с запросами на английском языке, а ее надо заставить говорить на русском. В этом случае промптами можно добиться небольшого успеха, но скорее всего вы не получите ожидаемый результат.
Полностью исправить эти проблемы не получится: фактологическая точность никогда не станет 100%, периодически будут проскакивать галлюцинации. Тут поможет только дообучение моделей. И это нормально. Если вам нужно идеальное решение, стандартные ML-алгоритмы подойдут лучше.
Например, у нас есть open-source LLama3 8B. Она уже умеет классно разговаривать в чате с пользователем, но не может решать специфичную задачу напрямую.
Эта модель прошла несколько этапов:
претрейн, где модели на обучение дали информацию из почти всего интернета, чтобы сформировать общее представление о мире;
Supervised Fine-tuning (SFT), где ее научили структуре чата с пользователем;
Reinforcement Learning from Human Feedback (RLHF), где ее натренировали отвечать качественно.
Мы берем модель и обучаем ее с помощью разных техник решать нужную задачу с помощью разных техник:
Есть бесконечное множество подходов, но вот самые популярные:
Full Fine-tuning: обучение модели целиком, то есть всех весов. Дает лучшее качество на конкретной задаче, но ведет к катастрофическому забыванию всего остального. А еще отнимает много ресурсов;
Low-Rank Adaptation (LoRA): обучение маленьких копий некоторых слоев модели. Тратится меньше ресурсов и можно подобрать гиперпараметры и получить результат на уровне Full Fine-tuning. Хотя в этой статье говорят, что Lora learns less and forget less.
Для дообучения нужны четыре составляющие:
данные;
тренировочный пайплайн (например, LoRA);
оценка качества;
сама модель.
Гайд, как использовать LoRa в python через Peft и transformers.
Статья на Хабре про разные методы обучения.
Статья на Medium про сложности и проблемы у LLM.
Чтобы лучше понять разницу между подходами, мы посчитали время на создание Proof-of-Concept (PoC). Для каждого уровня требуется свое количество ресурсов:
Самый сложный и длительный этап — последний. Проще не связываться с дообучением, а искать более быстрый и простой подход. Но не факт, что это решение будет лучшим. Если вы застряли на одном этапе дольше, чем требуется для проверки гипотезы, стоит признать: текущий метод не работает. Нужно усложнять решение или комбинировать техники с разных подходов — например, обучение и агентов. В любом случае не зацикливайтесь на одном уровне и не пытайтесь выжать из него невозможное.
Если вы сталкиваетесь с адаптацией моделей, то помните:
так или иначе придется адаптировать как облачные, так и опенсорс-модели;
часть возникающих проблем можно решить промпт-инжинирингом. Главное — не переусердствовать и вовремя остановиться;
с помощью агентов можно добавить новые функции, которых нет по умолчанию;
дообучение — сложный и дорогой процесс. Он необходим, если вы не можете справиться с задачей на предыдущих уровнях адаптации;
лучшее решение для специфичных задач — не зацикливаться на одном подходе, а комбинировать разные.
На этом у меня все, но я готова отвечать на ваши вопросы. Спасибо, что читали!