Узнайте, как использовать библиотеку реестра моделей Snowpark ML и векторизованные пользовательские функции Python Snowflake для автоматизации развертывания больших языковых моделей HuggingFace (LLMS) для пользователей Snowflake SQL.

23 августа 2023 г.: На момент написания этой статьи реестр моделей Snowpark ML находился в режиме частной предварительной версии. Если вы уже являетесь клиентом Snowflake, обратитесь к своей команде по работе с учетными записями Snowflake для получения доступа или же вы можете зарегистрировать пробную учетную запись здесь.

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

Но почему меня это должно волновать?

Создание модели подтверждения концепции (POC) теперь стало проще, чем когда-либо, благодаря новейшим моделям и библиотекам LLM. Вы можете получить представление о том, как быстро можно построить модель, специфичную для предметной области, ознакомившись с последним курсом DeepLearning.ai по генеративному искусственному интеллекту с LLM (настоятельно рекомендуется!).

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

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

Мы продемонстрируем решение с тем же гипотетическим сценарием, который мы описали в наших недавних сообщениях в блоге. В этом конкретном вымышленном сценарии наша цель — классифицировать текстовые отзывы клиентов на основе различных аспектов нашего бизнеса, используя предварительно обученную модель HuggingFace Facebook/bart-large-mnli.

Управление версиями модели

Управление версиями моделей имеет решающее значение для приложений генеративного ИИ по нескольким причинам:

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

Snowflake уже предоставляет набор библиотек для добавления этих преимуществ в ваше приложение генеративного искусственного интеллекта.

Для подробного ознакомления с функциональностью реестра моделей Snowpark ML мы настоятельно рекомендуем ознакомиться с сообщением в блоге Эйлона Штайнера. Он предоставляет всеобъемлющий обзор и ценную информацию по теме.

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

import os
from snowflake.ml.registry import model_registry
from snowflake.ml.model import custom_model
from snowflake.ml.model import model_signature
from transformers import AutoTokenizer, AutoModelForSequenceClassification, pipeline
import pandas as pd

# 1. Reference an already-created registry.
registry = model_registry.ModelRegistry(
   session=session,
   database_name="<your_database_name>",
   schema_name='MODEL_REGISTRY'
)

# Loading the model and the tokenizer
model = AutoModelForSequenceClassification.from_pretrained('Facebook/bart-large-mnli')
tokenizer = AutoTokenizer.from_pretrained('Facebook/bart-large-mnli')

# Save the model locally
ARTIFACTS_DIR = "/tmp/facebook-bart-large-mnli/"
os.makedirs(os.path.join(ARTIFACTS_DIR, "model"), exist_ok=True)
os.makedirs(os.path.join(ARTIFACTS_DIR, "tokenizer"), exist_ok=True)
model.save_pretrained(os.path.join(ARTIFACTS_DIR, "model"))
tokenizer.save_pretrained(os.path.join(ARTIFACTS_DIR, "tokenizer"))

# 2. Create a custom class for the model
class FacebookBartLargeMNLICustom(custom_model.CustomModel):
   def __init__(self, context: custom_model.ModelContext) -> None:
       super().__init__(context)

       self.model = AutoModelForSequenceClassification.from_pretrained(self.context.path("model"))
       self.tokenizer = AutoTokenizer.from_pretrained(self.context.path("tokenizer"))
       self.candidate_labels = ['customer support', 'product experience', 'account issues']

   @custom_model.inference_api
   def predict(self, X: pd.DataFrame) -> pd.DataFrame:
       def _generate(input_text: str) -> str:
           classifier = pipeline(
               "zero-shot-classification",
               model=self.model,
               tokenizer=self.tokenizer
           )

           result = classifier(input_text, self.candidate_labels)
           if 'scores' in result and 'labels' in result:
               category_idx = pd.Series(result['scores']).idxmax()
               return result['labels'][category_idx]

           return None

       res_df = pd.DataFrame({"output": pd.Series.apply(X["input"], _generate)})
       return res_df

# 3. Logging the model
model = FacebookBartLargeMNLICustom(custom_model.ModelContext(models={}, artifacts={
   "model":os.path.join(ARTIFACTS_DIR, "model"),
   "tokenizer":os.path.join(ARTIFACTS_DIR, "tokenizer")
}))

model_id = registry.log_model(
   model_name='Facebook/bart-large-mnli', # Set the model name
   model_version='100', # Set the model version
   model=model,
   conda_dependencies=[
       "transformers==4.18.0"
   ],
   tags={
       'deploy':'1' # Tag the model for deployment
   },
   signatures={
       "predict": model_signature.ModelSignature(
           inputs=[model_signature.FeatureSpec(name="input", dtype=model_signature.DataType.STRING)],
           outputs=[model_signature.FeatureSpec(name="output", dtype=model_signature.DataType.STRING)],
       )
   }
)

Приведенный выше код относится к объекту сеанса, который представляет объект Python сеанса Snowpark. Здесь вы можете найти инструкции о том, как его создать. Для получения дополнительной информации о библиотеке реестра моделей Snowpark ML обратитесь к официальной документации.

После завершения модель можно загрузить на локальное устройство, указав имя и желаемую версию.

model_reference = model_registry.ModelReference(
 registry=registry,
 model_name="Facebook/bart-large-mnli",
 model_version="100"
)

model = model_reference.load_model()
model.predict(pd.DataFrame({"input":["The interface gets frozen very often"]}))

Модельное обслуживание

Еще один важный компонент, связанный с компонентом инфраструктуры вашего приложения генеративного искусственного интеллекта, — это предоставление доступа к модели. Существует несколько методов для выполнения этой задачи в контексте анализа данных. Однако предоставление вашей модели в виде пользовательской функции (UDF) позволяет пользователям SQL легко получать доступ к моделям LLM.

В другой из наших недавних статей мы продемонстрировали, как пользователи SQL могут легко получить доступ к моделям LLM через векторизованные пользовательские функции Snowflake Python, реализовав следующий код (этот код предполагает, что объект классификатора был загружен на сцену, а ссылка на стадию предоставлена УДФ):

# Caching the model
import cachetools
import sys

@cachetools.cached(cache={})
def read_model():
  import_dir = sys._xoptions.get("snowflake_import_directory")
  if import_dir:
      # Load the model
      return joblib.load(f'{import_dir}/bart-large-mnli.joblib'

# Create the vectorized UDF where to store the model
from snowflake.snowpark.functions import pandas_udf
from snowflake.snowpark.types import StringType, PandasSeriesType
@pandas_udf( 
      name='{your db}.{your schema}.get_review_classification',
      session=session,
      is_permanent=True,
      replace=True,
      imports=[
          f'@{your db}.{your schema}.{your model stage}/bart-large-mnli.joblib'
      ],
      input_types=[PandasSeriesType(StringType())],
      return_type=PandasSeriesType(StringType()),
      stage_location='@{your db}.{your schema}.{}',
      packages=['cachetools==4.2.2', 'transformers==4.14.1']
  )
def get_review_classification(sentences: pd.Series) -> pd.Series:
  # Classify using the available categories
  candidate_labels = ['customer support', 'product experience', 'account issues']
  classifier = read_model()
 
  # Make the inferance
  predictions = []
  for sentence in sentences:
      result = classifier(sentence, candidate_labels)
      if 'scores' in result and 'labels' in result:
          category_idx = pd.Series(result['scores']).idxmax()
          predictions.append(result['labels'][category_idx])
      else:
          predictions.append(None)
  return pd.Series(predictions)

Этот подход позволяет нам получить доступ к LLM, просто вызывая UDF в любом операторе SQL.

WITH cs AS (
  SELECT value AS customer_review
  FROM (
  VALUES
      ('Nobody was able to solve my issues with the system'),
      ('The interface gets frozen very often'),
      ('I was charged two in the same period')
  ) AS t(value)
)
SELECT
  cs.customer_review,
 {your database}.{your schema}.get_review_classification(
      customer_review::VARCHAR
  ) AS category_prediction
FROM cs;

Автоматизация развертывания модели

Наконец, мы можем использовать оба метода для разработки процесса автоматизации развертывания версий модели LLM без негативного влияния на опыт наших потребителей SQL. Представьте себе жизненный цикл проекта генеративного ИИ в Snowflake следующим образом:

  1. Ваша команда по науке о данных создает POC для генеративного искусственного интеллекта.
  2. После внутреннего утверждения первой версии модели она регистрируется в официальном реестре проекта с использованием реестра моделей Snowpark ML.
  3. Используя векторизованный UDF Snowflake Python, LLM предоставляется внутренним пользователям SQL вашей компании. Но вместо ссылки на конкретную версию модели мы создаем хранимую процедуру, которая извлекает самую последнюю версию из реестра проекта и заново создает пользовательскую функцию Snowflake.
  4. Всякий раз, когда новая версия создается и добавляется в реестр проекта, мы выполняем процедуру развертывания, чтобы обеспечить обновление содержимого UDF.

Этот процесс может гарантировать, что потребители SQL всегда будут иметь доступ к последней версии модели, даже не осознавая этого!

Вы можете использовать приведенный ниже код, чтобы создать эту процедуру хранилища и развернуть новую версию с помощью библиотек реестра модели Snowpark ML.

CREATE OR REPLACE PROCEDURE {your database}.{your schema}.DEPLOY_REVIEW_CLASSIFICATION()
 RETURNS STRING
 LANGUAGE PYTHON
 RUNTIME_VERSION = '3.8'
 PACKAGES = (
   'snowflake-snowpark-python',
   'snowflake-ml-python',
   'transformers==4.18.0'
 )
 HANDLER = 'deploy'
 EXECUTE AS CALLER
AS
$$

from snowflake.ml.registry import model_registry
from snowflake.snowpark.types import PandasDataFrame, StringType, PandasSeriesType, PandasSeries
from snowflake.snowpark.functions import col, udf, parse_json, lit
import numpy as np
import pandas as pd

def deploy(session):
   # 1. Get a reference to the model registry
   registry = model_registry.ModelRegistry(
       session=session,
       database_name="<your database>",
       schema_name='MODEL_REGISTRY'
   )

   # 2. Read the information about the model to be deployed.
   model_name, model_version, err = get_model_to_deploy(registry)
   if err is not None:
       return err

   # 3. Get a reference to the model
   reference = model_registry.ModelReference(
       registry=registry,
       model_name=model_name,
       model_version=model_version)
  
   model = reference.load_model()

   # 4. Create the prediction function
   @udf(
       name='{your database}.{your schema}.GET_REVIEW_CLASSIFICATION',
       is_permanent = True,
       session=session,
       stage_location = '@"<your database>"."<your schema>".REVIEW_CLASSIFICATION',
       replace=True,
       input_types=[PandasSeriesType(StringType())],
       return_type=PandasSeriesType(StringType()),
       packages=[
           'snowflake-snowpark-python',
           'snowflake-ml-python',
           'transformers==4.18.0'
       ]
   )
   def get_review_classification(sentences: pd.Series) -> pd.Series:
       # Make the inferance
       predictions = []
       for sentence in sentences:
           result = model.predict(pd.DataFrame({"input":[sentence]}))
           predictions.append(result['output'][0])
       return pd.Series(predictions)

   return f'Model {model_name} successfully deployed'

def get_model_to_deploy(registry):
   try:
      # Get the model to deploy
       model_list = registry.list_models()
       model_to_deploy = (
         model_list
         .filter(parse_json(model_list["TAGS"]).getField("deploy") == lit('1'))
         .select(
           col("NAME"),
           col("VERSION"))
         .to_pandas()
       )
      
       if len(model_to_deploy) == 0:
           None, None, "No model deployed yet"

       return  model_to_deploy["NAME"][0], model_to_deploy["VERSION"][0], None
  
   except Exception as e:
       return None, None, str(e)

$$;

Некоторые подробности о процедуре хранения:

  • Библиотека реестра моделей Snowpark ML уже предоставляет метод для развертывания модели как UDF Snowflake. Я рекомендую вам использовать этот подход, чтобы лучше контролировать, какие характеристики модели используются для развертывания, а также характеристики развернутой пользовательской функции, включая имя, пакеты и тип.
  • Важно внимательно относиться к версии Python и пакетам, используемым как локально, так и в процедуре хранения и UDF, поскольку несовпадение версий пакетов является распространенной проблемой при использовании этого подхода.
  • Чтобы определить подходящую модель для развертывания, мы используем тег «model». Однако библиотека также предоставляет возможность настройки методологии повышения прав с помощью тегов и метрик.
  • Мы рекомендуем использовать хранилище, оптимизированное для Snowpark, для выполнения процедуры хранилища развертывания. Загрузка моделей LLM — это операция, требующая большого объема памяти, которая может привести к сбою на традиционных складах Snowflake.

Теперь каждый раз, когда вы вызываете процедуру сохранения, пользовательская функция будет автоматически обновляться, чтобы ссылаться на последнюю модель. Это означает, что вам не нужно будет вручную изменять какие-либо ссылки на UDF. Довольно круто, правда?

Краткое содержание

В этой статье показано, как использовать библиотеку реестра моделей Snowpark ML, хранимые процедуры Snowflake и векторизованную UDF-функцию Snowflake pandas для автоматизации развертывания LLM, который предоставляет пользователям SQL беспрепятственный и непрерывный доступ к моделям в вашей организации.

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

В нашей следующей статье мы покажем, как добавить в это решение тонко настроенный компонент с помощью процедур хранения Snowflake и складов, оптимизированных для Snowpark. Следите за обновлениями!

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

Наконец, я хотел бы выразить благодарность команде Snowpark ML и ребятам из Streamlit за помощь в подготовке содержания этой статьи и предоставление нам раннего доступа к предварительной версии библиотеки. Всегда приятно наблюдать, как сообщество LLM в Снежинке расширяется и набирает обороты.

Я Фабиан Эрнандес, архитектор данных в Infostrux Solutions. Спасибо, что прочитали мой пост в блоге. Вы можете подписаться на меня в LinkedIn и на Infostrux Medium Blogs, чтобы получать самые интересные новости Data Engineering и Snowflake. Пожалуйста, сообщите нам свои мысли об этом подходе в разделе комментариев.