2023 war das Jahr, in dem verschiedene Large Language Models (LLMs) im Bereich der generativen KI aufkamen. LLMs haben eine unglaubliche Leistung und ein unglaubliches Potenzial, aber ihre Produktion ist eine ständige Herausforderung für die Nutzer. Ein besonders häufig auftretendes Problem ist die Frage, welches LLM man verwenden sollte. Und noch spezifischer: Wie kann man ein LLM auf seine Genauigkeit hin bewerten? Dies ist besonders schwierig, wenn eine große Anzahl von Modellen zur Auswahl steht, verschiedene Datensätze für die Feinabstimmung/RAG und eine Vielzahl von Prompt-Engineering-/Tuning-Techniken zu berücksichtigen sind.
Um dieses Problem zu lösen, müssen wir DevOps Best Practices für LLMs einführen. Ein Workflow oder eine Pipeline, die bei der Bewertung verschiedener Modelle, Datensätze und Aufforderungen helfen kann. Dieser Bereich wird allmählich als LLMOPs/FMOPs bekannt. Einige der Parameter, die in LLMOPs berücksichtigt werden können, sind unten in einem (extrem) vereinfachten Ablauf dargestellt:
In diesem Artikel versuchen wir, dieses Problem anzugehen, indem wir eine Pipeline aufbauen, die ein Llama 7B-Modell feinabstimmt, einsetzt und evaluiert. Sie können dieses Beispiel auch skalieren, indem Sie es als Vorlage für den Vergleich mehrerer LLMs, Datensätze und Prompts verwenden. Für dieses Beispiel werden wir die folgenden Tools verwenden, um diese Pipeline zu erstellen:
- SageMaker JumpStart: SageMaker JumpStart bietet standardmäßig verschiedene FM/LLMs für die Feinabstimmung und den Einsatz. Diese beiden Prozesse können recht kompliziert sein, daher abstrahiert JumpStart die Einzelheiten und ermöglicht es Ihnen, Ihren Datensatz und Ihre Modell-Metadaten anzugeben, um die Feinabstimmung und den Einsatz durchzuführen. In diesem Fall wählen wir Llama 7B aus und führen eine Feinabstimmung der Anweisungen durch, die von Anfang an unterstützt wird. Eine ausführlichere Einführung in die JumpStart-Feinabstimmung finden Sie in diesem Blog und in diesem Llama-Codebeispiel, das wir als Referenz verwenden werden.
- SageMaker Clarify/FMEval: SageMaker Clarify bietet über die SageMaker Studio-Benutzeroberfläche und die Open-Source-Python-Bibliothek FMEVal ein Tool zur Bewertung von Foundation-Modellen. Die Funktion ist mit einer Vielzahl verschiedener Algorithmen ausgestattet, die unterschiedliche NLP-Domänen wie Texterzeugung und ‑zusammenfassung abdecken. In diesem Beispiel nutzen wir die Bibliothek, um das Llama-Modell für einen Zusammenfassungs-Anwendungsfall zu evaluieren.
- SageMaker Pipelines Schritt-Dekorator: SageMaker Pipelines ist eine MLOPs-Funktion in SageMaker, die Ihnen hilft, ML-Workflows zu operationalisieren. Mit Pipelines können Sie verschiedene Schritte und Parameter definieren, um Ihren ML-Workflow aufzubauen. Innerhalb von Pipelines gibt es eine Funktion, die als Schrittdekorator bekannt ist und mit der Sie Python-Code in Funktionen umwandeln können, die Sie als Pipeline aneinanderreihen können. In diesem Beispiel werden wir diese Funktion nutzen, um Funktionen für das Training und die Auswertung mit den oben definierten Tools zu erstellen.
Jetzt, wo wir es verstanden haben, können wir loslegen!
HINWEIS: Dieser Artikel setzt ein grundlegendes Verständnis von Python, LLMs und Amazon SageMaker voraus.
Inhaltsübersicht
- Einrichtung und Datensatzvorbereitung
- Aufbau von Pipelineschritten
- Pipeline-Ausführung
- Zusätzliche Ressourcen & Schlussfolgerung
1. Einrichtung & Datensatzvorbereitung
Für die Entwicklung werden wir in der neuen SageMaker Studio-Umgebung arbeiten (lokale Kernel-Unterstützung aktiviert). Wir werden eine ml.c5.18xlarge-Instanz mit einem Python3-Kernel verwenden.
Für unseren Anwendungsfall werden wir eine Feinabstimmung vornehmen und einen Zusammenfassungsanwendungsfall mit Llama evaluieren. Für unseren Datensatz werden wir den öffentlichen Dolly-Datensatz verwenden (Lizenz: cc-by-sa‑3.0). Wir können diesen Datensatz mithilfe der integrierten HuggingFace-Datensatzbibliothek abrufen und nach den Datenpunkten für die Zusammenfassung filtern. Außerdem erstellen wir einen Trainings- und einen Testdatensatz für die Feinabstimmung und Auswertung.
import datasets
# dolly dataset
dolly_dataset = load_dataset("databricks/databricks-dolly-15k", split="train")
# summarization use-case
summarization_dataset = dolly_dataset.filter(lambda example: example["category"] == "summarization")
summarization_dataset = summarization_dataset.remove_columns("category")
# train test split
train_and_test_dataset = summarization_dataset.train_test_split(test_size=0.1)
# local train dataset
train_and_test_dataset["train"].to_json("train.jsonl")
# test dataset
train_and_test_dataset["test"].to_json("test.jsonl")
Wir geben auch die Modell-Metadaten an JumpStart weiter, um das passende Llama 7B-Modell zu erhalten.
import sagemaker
model_id, model_version = "meta-textgeneration-llama-2-7b", "2.*"
Für die Schulung werden wir eine Feinabstimmung der Anweisungen vornehmen, d. h. wir erstellen eine Vorlage für die Aufforderung und die Antwort, oder in diesem Fall den Text und die Zusammenfassung. Folgendes Beispiel wird als Referenz für den Schulungsteil verwendet.
import json
template = {
"prompt": "Below is an instruction that describes a task, paired with an input that provides further context. "
"Write a response that appropriately completes the request.\n\n"
"### Instruction:\n{instruction}\n\n### Input:\n{context}\n\n",
"completion": " {response}",
}
with open("template.json", "w") as f:
json.dump(template, f)
Anschließend laden wir diese Dateien in einen gemeinsamen S3-Pfad hoch, um sie zu trainieren und später auch zu inferenzieren/auszuwerten.
from sagemaker.s3 import S3Uploader
import sagemaker
import random
output_bucket = sagemaker.Session().default_bucket()
local_data_file = "train.jsonl"
test_data_file = "test.jsonl"
train_data_location = f"s3://{output_bucket}/dolly_dataset"
test_data_location = f"s3://{output_bucket}/test_dataset"
S3Uploader.upload(local_data_file, train_data_location)
S3Uploader.upload("template.json", train_data_location)
S3Uploader.upload(test_data_file, test_data_location)
print(f"Training data: {train_data_location}")
print(f"Test data: {test_data_location}")
print(f"Output bucket: {output_bucket}")
Aufbau von Pipelinestufen
Pipeline- Aufbau
Um unsere Pipeline einzurichten, benötigen wir einige einzelne Dateien, die unsere Ausführungsumgebung definieren. Eine davon ist die Datei config.yaml, in der die Hardware für die Pipeline-Ausführung sowie alle anderen von Ihnen definierten Konfigurationen festgelegt werden. Die config-Datei verweist auch auf Ihre requirements.txt, die in der Pipeline-Umgebung installiert wird.
SchemaVersion: '1.0'
SageMaker:
PythonSDK:
Modules:
RemoteFunction:
InstanceType: ml.m5.xlarge
Dependencies: ./requirements.txt
IncludeLocalWorkDir: true
CustomFileFilter:
IgnoreNamePatterns: # files or directories to ignore
- "*.ipynb" # all notebook files
jsonlines
sagemaker
fmeval
Wir können dann auf diese Konfigurationsdatei als Umgebungsvariable verweisen:
import os
# Set path to config file
os.environ["SAGEMAKER_USER_CONFIG_OVERRIDE"] = os.getcwd()
Optional können Sie bei Pipelines auch Parameter definieren, die in Ihre Pipeline injiziert werden. In diesem Fall definieren wir den Hardware-Instanztyp für den Job, der die einzelnen Schritte ausführt:
import sagemaker
from sagemaker.workflow.function_step import step
from sagemaker.workflow.parameters import ParameterString
sagemaker_session = sagemaker.session.Session()
role = sagemaker.get_execution_role()
bucket = sagemaker_session.default_bucket()
region = sagemaker_session.boto_region_name
instance_type = ParameterString(name="TrainInstanceType",
default_value="ml.c5.18xlarge")
Schulung und Einsatz
Für den Schulungsschritt werden wir mit SageMaker JumpStart arbeiten, um unser Llama 7B-Modell zu optimieren. Wir übergeben der Funktion ein paar verschiedene Parameter:
- Trainingsdatenpfad: Dies ist der S3-Speicherort, der auf die Dateien train.jsonl und template.json verweist.
- Modell-Metadaten: JumpStart erkennt anhand der übergebenen Modell-ID und Version, welches Modell heruntergeladen werden soll. In diesem Fall geben wir das Modell Llama 7B an.
Sobald wir diese Parameter definiert haben, richten wir den JumpStart Estimator mit diesen Parametern ein. Optional können Sie auch modellspezifische Parameter für die Feinabstimmung definieren, abhängig von den Reglern, die Sie auf ihre Leistung testen möchten. Beachten Sie den Schrittdekorator mit der Funktion, der symbolisiert, dass es sich um einen SageMaker-Pipeline-Schritt und nicht um eine einfache Python-Funktion handelt.
# step one
@step(
name = "train-deploy",
instance_type = instance_type,
keep_alive_period_in_seconds=300
)
def train_deploy(train_data_path: str,
model_id: str = "meta-textgeneration-llama-2-7b",
model_version: str = "2.*") -> str:
import sagemaker
from sagemaker.jumpstart.estimator import JumpStartEstimator
# configure JumpStart Estimator
estimator = JumpStartEstimator(
model_id=model_id,
model_version=model_version,
environment={"accept_eula": "true"},
disable_output_compression=True,
)
estimator.set_hyperparameters(instruction_tuned="True", epoch="1", max_input_length="1024")
estimator.fit({"training": train_data_path})
## deploy fine-tuned model
finetuned_predictor = estimator.deploy()
endpoint_name = finetuned_predictor.endpoint_name
Nach dem Training stellen wir das fein abgestimmte Llama-Modell auf einem SageMaker Echtzeit-Endpunkt bereit, den wir für die Inferenz aufrufen können. Dieser Instanztyp ist standardmäßig sowohl für das Training als auch für die Inferenz ausgewählt, je nach dem von Ihnen gewählten LLM, aber Sie können dies anpassen, wenn Sie die Hardware selbst auswählen möchten.
Der Name des Endpunkts wird als Eingabe für den nächsten Schritt zurückgegeben, so dass wir die Inferenz und die Auswertung mit diesem Endpunkt durchführen können. Wenn Sie die Pipeline schließlich ausführen, sehen Sie für diesen Schritt einen erfolgreichen JumpStart-Schulungsauftrag und einen in der Studio-Benutzeroberfläche erstellten Endpunkt.
Jetzt, wo wir unseren Endpunkt haben, können wir die Stichprobeninferenz durchführen und die Ergebnisse auswerten.
Inference & Evaluation Step
In unserem zweiten Schritt definieren wir wieder bestimmte Parameter für unsere Funktion:
- Endpunkt Name: Wir führen vor der Auswertung eine Inferenz gegen diesen Endpunkt durch.
- S3_Test_Pfad: Zuvor hatten wir auch einen Testdatensatz getrennt von unserem Trainingsdatensatz in S3 übertragen. Wir werden den Datensatz von diesem Pfad abrufen und vor der Auswertung eine Inferenz durchführen.
Zunächst laden wir die S3-Datei „test.jsonl“ herunter, die das Boto3 Python SDK verwendet:
def evaluate(endpoint_name: str, output_bucket: str = output_bucket, test_data_file: str = "test.jsonl",
key_path: str = "test_dataset/test.jsonl") -> str:
# download S3 file
s3 = boto3.client("s3")
s3.download_file(output_bucket, key_path, test_data_file)
Aus dieser Datei wird dann eine neue JSONLines-Datei erstellt, für die wir unsere Bewertungsalgorithmen ausführen können. Aus Zeitgründen beschränken wir diese Stichprobe auf nur 20 Datenpunkte, aber Sie können den Testdatensatz nach Bedarf erweitern.
with jsonlines.open(input_file) as input_fh, jsonlines.open(output_file, "w") as output_fh:
for i, datapoint in enumerate(input_fh, start=1):
instruction = datapoint["instruction"]
context = datapoint["context"]
summary = datapoint["response"]
payload = prepare_payload(datapoint)
response = runtime.invoke_endpoint(EndpointName=endpoint_name, Body=json.dumps(payload),
ContentType=content_type, CustomAttributes='accept_eula=true')
result = json.loads(response['Body'].read().decode())[0]['generation']
line = {"instruction": instruction, "context": context, "summary": summary, "model_output": result}
output_fh.write(line)
# evaluate just 20 datapoints for example
if i == 20:
break
Nachdem dieser Teil der Funktion ausgeführt wurde, sollte er eine Datei „results.jsonl“ erzeugen, die einige verschiedene Parameter enthält:
- Dokument/Eingabe: Dies ist der Originaltext, der zusammengefasst werden muss.
- Grundwahrheit/Ist-Ausgabe: Dies ist die Zusammenfassung, die im Testdatensatz enthalten war; dies ist die Grundwahrheit, gegen die wir evaluieren.
- Modell Output: Dies ist die Inferenz, die wir mit unserem feinabgestimmten Llama 7B-Modell durchgeführt haben. Wir werden unseren Evaluierungsalgorithmus auf die Ground Truth-Werte und die Ergebnisse der Modellinferenz anwenden.
Da wir nun unseren Datensatz für die Auswertung haben, können wir die FMEval-Bibliothek importieren:
import fmeval
from fmeval.data_loaders.data_config import DataConfig
from fmeval.constants import MIME_TYPE_JSONLINES
from fmeval.eval_algorithms.summarization_accuracy import SummarizationAccuracy
Achten Sie darauf, dass wir den SummarizationAccuracy-Algorithmus abrufen, der Metriken wie Meteor, Rouge und Bert zurückgibt. Um die vollständige Implementierung dieser Algorithmen zu sehen, können Sie den Open-Source-Code unter diesem Link aufrufen. Im Allgemeinen hat jede dieser Metriken ihre eigenen Vor- und Nachteile, und Sie können auswählen, welche Metrik Sie für die Auswertung verwenden möchten.
- Rouge: Rouge‑N Scores, das im Wesentlichen nach N‑Gramm-Wortüberschneidungen zwischen der Grundwahrheit und der Zusammenfassung der Modellinferenz sucht.
- Meteor: Baut auf Rouge auf, um Stemming und Synonyme einzubeziehen. Dadurch werden mehr Ähnlichkeiten in Texten erfasst, falls die Wörter nicht genau übereinstimmen.
- BertScore: Vergleicht Wörter mit Hilfe von Kosinusähnlichkeit unter Verwendung von vortrainierten Einbettungen von BERT. Dies wird bei der Installation des Pakets heruntergeladen.
Für unsere Implementierung mit dem FMEval-Paket konfigurieren wir zunächst unseren Datensatz in einem FMEval-spezifischen DataConfig-Objekt und legen die Spalten für die Grundwahrheit und die Modellinferenz fest.
config = DataConfig(
dataset_name="dolly_summary_model_outputs",
dataset_uri="results.jsonl",
dataset_mime_type=MIME_TYPE_JSONLINES,
model_input_location="instruction",
target_output_location="summary",
model_output_location="model_output"
)
Dann instanziieren wir den SummarizationAccuracy-Algorithmus und führen eine Auswertung mit unserem DataConfig-Objekt aus.
eval_algo = SummarizationAccuracy()
eval_output = eval_algo.evaluate(dataset_config=config, save=True)
res = json.dumps(eval_output, default=vars, indent=4)
serialized_data = json.loads(res)
# print metrics to CW logs, realistically push to somewhere to visualize
for item in serialized_data:
for key, value in item.items():
print(f"Key: {key}, Value: {value}")
In diesem Fall schreiben wir die Metriken direkt in die CloudWatch-Protokolle. In einem realistischen Anwendungsfall können Sie diese in S3 oder ein Visualisierungstool wie QuickSight auslagern, um eine schönere Ansicht Ihrer Auswertung zu erhalten. Wenn Sie die CloudWatch-Protokolle für den zweiten Schritt nach der Pipeline-Ausführung überprüfen, werden Sie feststellen, dass die Metriken ausgegeben wurden.
3.Pipeline-Ausführung
Sobald die beiden Pipeline-Schritte definiert sind, können Sie sie einfach miteinander verketten, wie Sie es mit einfachen Python-Funktionen tun würden.
# stitch together pipelinefrom sagemaker.workflow.pipeline import Pipelineendpoint_name = train_deploy(train_data_location)eval_metrics = evaluate(endpoint_name)
Wir können dann ein Pipeline-Objekt definieren und eine Ausführung starten:
pipeline = Pipeline(
name="llm-train-eval-pipeline",
parameters=[
instance_type
],
steps=[
eval_metrics,
],
)
# execute Pipeline
pipeline.upsert(role_arn=role)
execution = pipeline.start()
execution.describe()
execution.wait()
You can view and monitor the Pipeline execution in the Studio UI, this Pipeline will take about 45 minutes to successfully complete.
4. Zusätzliche Ressourcen & Schlussfolgerung
Ich hoffe, dieser Artikel war eine nützliche Einführung in LLMOPs und den Aufbau einer Pipeline unter Verwendung verschiedener SageMaker-Funktionen. Mit der Ausweitung der LLM-Anwendungsfälle steigt auch der Bedarf an angemessenen Experimenten, um die ideale Konfiguration für Ihren LLM zu ermitteln. Dieses Beispiel kann erweitert werden, um mehrere LLMs, Datensätze, Eingabeaufforderungsvorlagen und mehr einzubeziehen. Bleiben Sie dran für weitere Inhalte im Bereich GenAI/LLM.
Wie immer danken wir Ihnen für das Interesse und freuen uns über Ihr Feedback.