Последние два месяца я потратил на создание приложения на основе модели большого языка (LLM). Это был захватывающий, интеллектуально стимулирующий и временами разочаровывающий опыт. Все мое представление о быстром инжиниринге — и о том, что возможно с LLM, — изменилось в ходе проекта.

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

Для контекста, вот TL;DR проекта, из которого мы будем учиться:

  • Я и моя команда создали VoxelGPT, приложение, которое объединяет LLM с языком запросов компьютерного зрения FiftyOne, чтобы обеспечить поиск в наборах данных изображений и видео с помощью естественного языка. VoxelGPT также отвечает на вопросы о самом FiftyOne.
  • VoxelGPT имеет открытый исходный код (как и FiftyOne!). Весь код доступен на GitHub.
  • Вы можете бесплатно попробовать VoxelGPT на сайте gpt.fiftyone.ai.
  • Если вам интересно, как мы создали VoxelGPT, вы можете узнать больше об этом на TDS здесь.

Теперь я разделил быстрые инженерные уроки на четыре категории:

  1. Общие уроки
  2. Техники подсказок
  3. "Примеры"
  4. Инструмент

Общие уроки

Наука? Инженерия? Черная магия?

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

В компьютерном зрении каждый набор данных имеет собственную схему, типы меток и имена классов. Цель VoxelGPT состояла в том, чтобы иметь возможность работать с любым набором данных компьютерного зрения, но мы начали только с одного набора данных: MS COCO. Фиксация всех дополнительных степеней свободы позволила нам в первую очередь зафиксировать способность LLM писать синтаксически правильные запросы.

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

Какие модели использовать?

Люди говорят, что одной из наиболее важных характеристик больших языковых моделей является их относительная взаимозаменяемость. Теоретически вы должны иметь возможность заменить один LLM на другой без существенного изменения соединительной ткани.

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

  • Некоторые модели имеют гораздо более короткую длину контекста, чем другие. Переход на модель с более коротким контекстом может потребовать серьезного рефакторинга.
  • Открытый исходный код — это здорово, но LLM с открытым исходным кодом не так эффективны (пока), как модели GPT. Кроме того, если вы развертываете приложение с помощью LLM с открытым исходным кодом, вам необходимо убедиться, что контейнер, в котором запущена модель, имеет достаточно памяти и хранилища. Это может оказаться более проблематичным (и более дорогим), чем просто использование конечных точек API.
  • Если вы начнете использовать GPT-4, а затем переключитесь на GPT-3.5 из-за стоимости, вы можете быть шокированы падением производительности. Для сложных задач генерации кода и логических выводов GPT-4 НАМНОГО лучше.

Где использовать LLM?

Большие языковые модели эффективны. Но то, что они могут выполнять определенные задачи, не означает, что вам нужно — или даже следует — использовать их для этих задач. Лучше всего думать о LLM как о активаторах. LLM — это не ПОЛНОЕ решение: они лишь его часть. Не ожидайте, что большие языковые модели сделают все.

Например, может случиться так, что используемый вами LLM может (при идеальных обстоятельствах) генерировать вызовы API в правильном формате. Но если вы знаете, как должна выглядеть структура вызова API, и действительно заинтересованы в заполнении разделов вызова API (имена переменных, условия и т. д.), то просто используйте LLM для выполнения этих задач и используйте выходные данные LLM (должным образом обработанные) для самостоятельного создания структурированных вызовов API. Это будет дешевле, эффективнее и надежнее.

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

LLM предвзяты

Языковые модели являются одновременно механизмами логического вывода и хранилищами знаний. Зачастую аспект хранилища знаний LLM может представлять большой интерес для пользователей — многие люди используют LLM в качестве замены поисковым системам! К настоящему времени любой, кто использовал LLM, знает, что он склонен к выдумыванию ложных «фактов» — явление, называемое галлюцинациями.

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

В нашем случае мы пытались заставить GPT-3.5 определить соответствующие ViewStage (конвейеры логических операций), необходимые для преобразования запроса пользователя на естественном языке в действительный запрос FiftyOne Python. Проблема заключалась в том, что GPT-3.5 знал о ViewStage `Match` и `FilterLabels`, которые существовали в FiftyOne в течение некоторого времени, но его обучающие данные невключали недавно добавленные функции, в которых `SortBySimilarity` ViewStage можно использовать для поиска изображений, напоминающих текстовое приглашение.

Мы попытались передать определение `SortBySimilarity`, подробности о его использовании и примеры. Мы даже пытались указать GPT-3.5, что он НЕ ДОЛЖЕН использовать ViewStages «Match» или «FilterLabels», иначе он будет оштрафован. Что бы мы ни пробовали, LLM по-прежнему ориентировался на то, что знал, был ли это правильный выбор или нет. Мы боролись с инстинктами LLM!

В итоге нам пришлось решать эту проблему в постобработке.

Болезненная постобработка неизбежна

Какими бы хорошими ни были ваши примеры; какими бы строгими ни были ваши подсказки — большие языковые модели неизменно будут галлюцинировать, давать вам неправильно отформатированные ответы и закатывать истерику, когда не понимают входной информации. Наиболее предсказуемым свойством LLM является непредсказуемость их результатов.

Я потратил безбожное количество времени на написание подпрограмм для сопоставления с образцом и исправления галлюцинаторного синтаксиса. В итоге файл постобработки содержал почти 1600 строк кода Python!

Некоторые из этих подпрограмм были такими же простыми, как добавление круглых скобок или замена «и» и «или» на «&» и «|». в логических выражениях. Некоторые подпрограммы были гораздо более сложными, например, проверка имен сущностей в ответах LLM, преобразование одной ViewStage в другую при выполнении определенных условий, проверка правильности количества и типов аргументов методов.

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

  1. Напишите свой собственный анализатор ошибок, используя абстрактные синтаксические деревья (модуль Python ast).
  2. Если результаты синтаксически неверны, передайте сгенерированное сообщение об ошибке в LLM и повторите попытку.

Этот подход не подходит для более коварного случая, когда синтаксис допустим, но результаты неверны. Если у кого-то есть хорошее предложение по этому поводу (помимо AutoGPT и подходов в стиле «покажи свою работу»), пожалуйста, дайте мне знать!

Методы подсказок

Чем больше тем лучше

Чтобы создать VoxelGPT, я использовал, казалось бы, все техники подсказок на свете:

  • «Вы эксперт»
  • «Ваша задача»
  • "Вы должны"
  • «Вы будете наказаны»
  • «Вот правила»

Никакая комбинация таких фраз не гарантирует определенный тип поведения. Умного подсказывания недостаточно.

При этом, чем больше этих методов вы используете в подсказке, тем больше вы подталкиваете LLM в правильном направлении!

Примеры › Документация

К настоящему времени общеизвестно (и здравый смысл!), что как примеры, так и другая контекстуальная информация, такая как документация, могут помочь получить лучшие ответы от большой языковой модели. Я обнаружил, что это относится к VoxelGPT.

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

Модульность ›› Монолит

Чем больше вы сможете разбить общую проблему на более мелкие подзадачи, тем лучше. Вместо подачи схемы набора данных и списка сквозных примеров гораздо эффективнее определить отдельные этапы выбора и вывода (подсказки выбора-вывода) и на каждом шаге вводить только релевантную информацию.

Это предпочтительнее по трем причинам:

  1. LLM лучше справляются с одной задачей за раз, чем с несколькими задачами одновременно.
  2. Чем меньше шагов, тем легче дезинфицировать входы и выходы.
  3. Для вас как инженера важно понять логику вашего приложения. Цель LLM не в том, чтобы превратить мир в черный ящик. Это для включения новых рабочих процессов.

Примеры

Сколько мне нужно?

Большая часть быстрой разработки заключается в том, чтобы выяснить, сколько примеров вам нужно для данной задачи. Это весьма специфично для конкретной проблемы.

Для некоторых задач (эффективная генерация запросов и ответы на вопросы на основе документации FiftyOne) нам удалось обойтись без никаких примеров. Для других (выбор тегов, актуальна ли история чата и распознавание именованных сущностей для классов меток) нам просто нужно было несколько примеров, чтобы выполнить работу. Наша основная задача логического вывода, однако, имеет почти 400 примеров (и это по-прежнему является ограничивающим фактором для общей производительности), поэтому мы передаем только наиболее релевантные примеры во время логического вывода.

При создании примеров старайтесь следовать двум рекомендациям:

  1. Будьте как можно более всеобъемлющим. Если у вас есть конечное пространство возможностей, попробуйте дать LLM хотя бы один пример для каждого случая. Для VoxelGPT мы старались иметь по крайней мере один пример для каждого синтаксически правильного способа использования каждого ViewStage — и, как правило, несколько примеров для каждого, чтобы LLM мог выполнять сопоставление с образцом.
  2. Будьте максимально последовательными. Если вы разбиваете задачу на несколько подзадач, убедитесь, что примеры соответствуют от одной задачи к другой. Вы можете повторно использовать примеры!

Синтетические примеры

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

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

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

  1. Используйте LLM для создания примеров. Вы можете попросить LLM изменить свой язык или даже подражать стилю потенциальных пользователей! Это не сработало для нас, но я убежден, что это может сработать для многих приложений.
  2. Программно генерируйте примеры — потенциально со случайностью — на основе элементов самого входного запроса. Для VoxelGPT это означает создание примеров на основе полей в пользовательском наборе данных. Мы находимся в процессе включения этого в наш конвейер, и результаты, которые мы видели до сих пор, были многообещающими.

Инструменты

LangChain

LangChain популярен по одной причине: библиотека позволяет легко соединять входы и выходы LLM сложными способами, абстрагируя кровавые детали. Модули Models и Prompts особенно хороши.

При этом LangChain определенно находится в стадии разработки: все их модули Memories, Indexes и Chains имеют значительные ограничения. Вот лишь некоторые из проблем, с которыми я столкнулся при попытке использовать LangChain:

  1. Загрузчики документов и разделители текста: в LangChain загрузчики документов должны преобразовывать данные из файлов разных форматов в текст, а разделители текста должны разбивать текст на семантически значимые фрагменты. VoxelGPT отвечает на вопросы о документации FiftyOne, извлекая наиболее важные фрагменты документов и направляя их в подсказку. Чтобы генерировать осмысленные ответы на вопросы о документации FiftyOne, мне пришлось создавать собственные загрузчики и разделители, поскольку LangChain не обеспечивал необходимой гибкости.
  2. Vectorstores: LangChain предлагает интеграцию Vectorstore и Retrievers на основе Vectorstore, чтобы помочь найти нужную информацию для включения в подсказки LLM. Это здорово в теории, но реализациям не хватает гибкости. Мне пришлось написать собственную реализацию с ChromaDB, чтобы заблаговременно передавать векторы встраивания и не пересчитывать их каждый раз, когда я запускаю приложение. Мне также пришлось написать собственный ретривер для реализации необходимой мне предварительной фильтрации.
  3. Ответы на вопросы с источниками: при построении ответов на вопросы по документам FiftyOne я пришел к разумному решению, используя цепочку RetrievalQA LangChain. Когда я хотел добавить источники, я думал, что это будет так же просто, как заменить эту цепочку на RetrievalQAWithSourcesChain LangChain. Однако плохие методы подсказок означали, что эта цепочка демонстрировала некоторые неудачные действия, такие как галлюцинации о Майкле Джексоне. В очередной раз пришлось брать дело в свои руки.

Что все это значит? Может быть проще просто собрать компоненты самостоятельно!

Векторные базы данных

Векторный поиск может быть включен 🔥🔥🔥, но это не значит, что он НУЖЕН для вашего проекта. Сначала я реализовал нашу аналогичную процедуру поиска примеров с помощью ChromaDB, но, поскольку у нас были только сотни примеров, я в конечном итоге переключился на поиск точных ближайших соседей. Мне нужно было самому заниматься фильтрацией всех метаданных, но в результате процедура стала быстрее с меньшим количеством зависимостей.

ТикТокен

Добавить TikToken в уравнение было невероятно просто. Всего TikToken добавил в проект ‹10 строк кода, но позволил нам быть гораздо более точными при подсчете токенов и попытке уместить как можно больше информации в длину контекста. Это единственная истинная легкая задача, когда дело доходит до инструментов.

Заключение

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

  1. Разбейте проблему на части; создать решение
  2. Рассматривайте LLM как инструменты, а не как комплексные решения
  3. Используйте инструменты только тогда, когда они облегчают вашу жизнь
  4. Займитесь экспериментами!

Иди строй что-нибудь крутое!