Как отслеживать и сравнивать артефакты LLM

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

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

Для классического машинного обучения большинство обсуждаемых функций можно найти на многих популярных платформах машинного обучения, таких как AzureML, SageMaker, Neptune и т. д. Для отслеживания разработки LLM одним из лучших вариантов является MLflow — популярный проект машинного обучения с открытым исходным кодом. . Благодаря очень активному участию в проекте MLflow, многие полезные функции для разработки LLM стали общедоступными. По некоторым причинам на момент написания статьи отсутствует подробное руководство по облегчению внедрения. Этот пост призван восполнить пробел.

Простое демонстрационное приложение

Давайте проверим некоторые последние лучшие практики, итеративно создав очень простое приложение на основе LLM. Всем нравятся хорошие песни, так почему бы не создать приложение на эту тему. Наше приложение будет представлять собой «детектор песен», который предложит название песни с учетом некоторых известных текстов.

На следующих занятиях основное внимание уделяется не тому, как создать приложение, а тому, как его

правильно отслеживать процесс разработки. Примеры, приведенные в этом руководстве, предназначены для платформы Databricks, но принцип и большая часть кода могут быть легко адаптированы для mlflow vanilla или для других платформ. Занятия будут организованы следующим образом:

  • Минимальная настройка для аутентификации
  • Простое создание модели
  • Модели журналов и конфигурации
  • Зарегистрируйте несколько запусков или оценок
  • Сравнить прогоны
  • Сравните с человеческой оценкой
  • Наборы данных журналов

Минимальная настройка аутентификации

Во-первых, нам нужно получить openai_api_key и openai_url из экземпляра OpenAI.

Рекомендуется создавать и хранить эти секреты в хранилище ключей. Для простоты и без ущерба для безопасности мы создадим секрет и область секретности непосредственно с помощью databricks cli. Если у вас не установлен databrikcs cli, проверьте (официальное руководство по установке). Приведенный ниже код был протестирован с помощью Databricks CLI v).200.2. Для более ранних версий может потребоваться небольшая модификация синтаксиса.

databricks secrets create-scope openai < -p your_databricks_profile >
  1. Создайте секретную область: databricks secrets create-scope opinai <-p your_databricks_profile>t.

2. Создайте секреты: databricks secrets put-secret openai openai_api_key <-p your_databricks_profile>.

3. Введите секретные значения (для openai_api_key): xxxxxxx .

4. Повторите шаги 2 и 3 для секрета openai_url.

Если в вашей компании есть частный экземпляр OpenAI, вам следует использовать openai_url и openai_api_key этого экземпляра для защиты ваших данных.

Для локальной разработки на локальном компьютере мы можем создать файл .env для хранения секретов. Содержимое файла .env должно быть следующим:

OPENAI_API_KEY=xxxxx
OPENAI_API_URL=xxxxx

Обратите внимание: пользователям VSCode в зависимости от вашей версии может потребоваться добавить export VARIABLE=XXXX в .env.

Запуск модели

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

def find_song(lyrics: str):
    response = openai.ChatCompletion.create(
    engine="gpt-35", # replace this value with the deployment name you chose when you deployed the associated model.
    messages = [{"role":"system","content":"You are an expert on english song, you will answer only the song's name and nothing else, and say sorry if the lyrics is not existed."},
                {"role":"user","content":f"`{lyrics}` is lyrics of the song: "}],
    temperature=0,
    max_tokens=3 # dont' be alerted by this value, it's for demo purpose only
    )

    return response["choices"][0]["message"]["content"]

Мы можем локально протестировать функцию:

import os
import openai

from dotenv import load_dotenv, find_dotenv
from backend import find_song

load_dotenv(find_dotenv())


openai.api_type = "azure"
openai.api_version = "2023-05-15"
openai.api_base = os.getenv("OPENAI_API_URL")
openai.api_key = os.getenv("OPENAI_API_KEY")

lyrics = "Hey Jude, don't make it bad. Take a sad song and make it better."
print("Lyrics: ", lyrics)
print("Answer: ", find_song(lyrics))

Результат:

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

Возможно, вы уже заметили, что я намеренно вношу некоторые проблемы в параметры модели, поэтому улучшить производительность модели здесь не составляет большого труда. В реальных проектах итерации рабочего решения — это долгий путь с множеством проб и ошибок. Таким образом, более актуальным является вопрос: как отслеживать процесс разработки? Мы рассмотрим это, используя встроенные mlflow функции Databricks.

Журнал моделей и конфигураций

Чтобы зарегистрировать модель, мы можем использовать функцию mlflow.pyfunc.log_model() следующим образом:

import mlflow

experiment_name = "/Shared/llm-song-finder/"
mlflow.set_experiment(experiment_name)

with mlflow.start_run():
    model_info = mlflow.pyfunc.log_model(
        python_model=find_song,
        artifact_path="model",
        pip_requirements=["openai"]
    )

Модель будет зарегистрирована в папке model и может быть загружена позже с помощью функции mlflow.pyfunc.load_model().

model = mlflow.pyfunc.load_model(model_info.model_uri)
lyrics = "Hey Jude, don't make it bad. Take a sad song and make it better."
print("Lyrics: ", lyrics)
print("Answer: ", model.predict(lyrics))

Зарегистрировать несколько запусков или оценок

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

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

import openai

openai.api_key = dbutils.secrets.get(scope = "openai", key = "openai_api_key")
openai.api_base = dbutils.secrets.get(scope = "openai", key = "openai_url")
openai.api_type = 'azure'
openai.api_version = '2023-05-15' # this may change in the future

def find_song(
    lyrics: str,
    temperature: float = 0.5,
    max_tokens: int = 256,
    ):
    response = openai.ChatCompletion.create(
    engine="gpt-35", # replace this value with the deployment name you chose when you deployed the associated model.
    messages = [{"role":"system","content":"You are an expert on english song, you will answer only the song's name and nothing else, and say sorry if the lyrics is not existed."},
                {"role":"user","content":f"`{lyrics}` is lyrics of the song: "}],
    temperature=temperature,
    max_tokens=max_tokens,
   )

    return response["choices"][0]["message"]["content"]

Учитывая эту простую функцию find_song(), давайте оценим ее с помощью различных подсказок и параметров.

import mlflow
import pandas as pd
import itertools


def generate_input(
    lyrics: str,
    temperature: float = 0.5,
    max_tokens: int = 256,
):
    return {
            "lyrics": lyrics,
            "temperature": temperature,
            "max_tokens": max_tokens,
    }

TEMPERATURE = [0, 1]
MAX_TOKENS=[3, 10]

param_list = [TEMPERATURE, MAX_TOKENS]

combinations = [p for p in itertools.product(*param_list)] # generates 12 combinations
#data=pd.read_csv("queries.csv")
data = pd.DataFrame({"Queries": lyrics_df["Lyrics"]})

experiment_name = "/Shared/llm-song-finder/"
mlflow.set_experiment(experiment_name)

with mlflow.start_run(nested=True):
    for idx,comb in enumerate(combinations):
        with mlflow.start_run(run_name="EVALUATE_PROMPT_"+str(idx), nested=True):
            mlflow.log_params({'temperature':comb[0],'max_tokens':comb[1],'llm_model':'gpt-3.5-turbo'})
            mlflow.log_text
            data['input'] = data['Queries'].apply(lambda x:generate_input(x,comb[0],comb[1]))
            data['temperature'] = comb[0]
            data['max_tokens'] = comb[1]
            data['result'] = data['input'].apply(lambda x:find_song(**x))
            mlflow.log_table(data, artifact_file="eval_results.json")

Для каждого прогона мы получим следующую таблицу с результатами:

Результаты каждого запуска будут регистрироваться в файле eval_results.json как артефакт.

Мы можем найти все зарегистрированные запуски на странице эксперимента:

Сравнить прогоны

Интересная часть возможности протоколировать все прогоны — это возможность их сравнивать. `mlflow` предоставляет очень удобный способ сравнения запусков.

Выбрав все соответствующие прогоны на вкладке evalution на панели experiment, мы можем сравнить производительность этих прогонов.

Сравнение можно проводить по метрикам, параметрам или артефактам, используя общий ключ группировки, например Queries. Ниже приведен пример:

Как показано, мы можем выбрать раскрывающееся меню Compare, чтобы выбрать показатель, который хотим сравнить. Сделав это, мы увидим, что в нашем случае max_tokens является фактором, объясняющим усечение текста. Или (здесь не показано) «температура» будет определять, будет ли название нашей песни заключено в кавычку или нет.

Сравните с человеческой оценкой

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

experiment_name = "/Shared/llm-song-finder/"
mlflow.set_experiment(experiment_name)

data = pd.DataFrame({"Queries": lyrics_df["Lyrics"]})

with mlflow.start_run(run_name="human-answer"):
    mlflow.log_params({'llm_model':'human-answer'})
    data['result'] = lyrics_df["Songs"]
    mlflow.log_table(data, artifact_file="eval_results.json")

Совет здесь заключается в том, чтобы рассматривать основную истину (человеческую маркировку) как предсказание другой модели (человеческой модели). Используя тот же способ регистрации модели, мы можем зарегистрировать «человеческую модель». Затем мы можем сравнить прогнозы двух моделей, используя прогон mlflow для сравнения мощностей.

Ввод данных

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

Начиная с версии 2.4.0 MLflow представлен API отслеживания наборов данных, который обеспечивает удобный способ регистрации наборов данных.

import mlflow.data
import pandas as pd
from mlflow.data.pandas_dataset import PandasDataset


dataset_source_path = "/dbfs/FileStore/tables/ground_truth_llm_lyrics_demo.csv"
lyrics_df = pd.read_csv(dataset_source_path, sep=";")
# Construct an MLflow PandasDataset from the Pandas DataFrame, and specify the path
# as the source
dataset: PandasDataset = mlflow.data.from_pandas(lyrics_df, source=dataset_source_path)

experiment_name = "/Shared/llmops_experiment/"
mlflow.set_experiment(experiment_name)

with mlflow.start_run(run_name="log-input"):
    # Log the dataset to the MLflow Run. Specify the "validating" context to indicate that the
    # dataset is used for model validating
    mlflow.log_input(dataset, context="validating")

Указатель (URL-адрес, путь и т. д.) на набор данных и его профиль будут зарегистрированы на сервере отслеживания mlflow и могут быть получены позже.

# Retrieve the run, including dataset information
run = mlflow.get_run(mlflow.last_active_run().info.run_id)
dataset_info = run.inputs.dataset_inputs[0].dataset
print(f"Dataset name: {dataset_info.name}")
print(f"Dataset digest: {dataset_info.digest}")
print(f"Dataset profile: {dataset_info.profile}")
print(f"Dataset schema: {dataset_info.schema}")

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

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

Заключение

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

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

Особая благодарность LutzOfficial за конструктивные комментарии!