2023 war das Jahr, in dem ver­schie­dene Large Lan­guage Models (LLMs) im Bereich der gene­ra­ti­ven KI auf­ka­men. LLMs haben eine unglaub­li­che Leis­tung und ein unglaub­li­ches Poten­zial, aber ihre Pro­duk­tion ist eine stän­dige Her­aus­for­de­rung für die Nut­zer. Ein beson­ders häu­fig auf­tre­ten­des Pro­blem ist die Frage, wel­ches LLM man ver­wen­den sollte. Und noch spe­zi­fi­scher: Wie kann man ein LLM auf seine Genau­ig­keit hin bewer­ten? Dies ist beson­ders schwie­rig, wenn eine große Anzahl von Model­len zur Aus­wahl steht, ver­schie­dene Daten­sätze für die Feinabstimmung/RAG und eine Viel­zahl von Prompt-Engi­nee­ring-/Tu­ning-Tech­ni­ken zu berück­sich­ti­gen sind.

Um die­ses Pro­blem zu lösen, müs­sen wir DevOps Best Prac­ti­ces für LLMs ein­füh­ren. Ein Work­flow oder eine Pipe­line, die bei der Bewer­tung ver­schie­de­ner Modelle, Daten­sätze und Prompts hel­fen kann. Die­ser Bereich wird all­mäh­lich als LLMOPs/FMOPs bekannt. Einige der Para­me­ter, die in LLMOPs berück­sich­tigt wer­den kön­nen, sind unten in einem (extrem) ver­ein­fach­ten Ablauf dargestellt:

In die­sem Arti­kel ver­su­chen wir, die­ses Pro­blem anzu­ge­hen, indem wir eine Pipe­line auf­bauen, die ein Llama 7B-Modell fein­ab­stimmt, ein­setzt und eva­lu­iert. Sie kön­nen die­ses Bei­spiel auch ska­lie­ren, indem Sie es als Vor­lage für den Ver­gleich meh­re­rer LLMs, Daten­sätze und Prompts ver­wen­den. Für die­ses Bei­spiel wer­den wir die fol­gen­den Tools ver­wen­den, um diese Pipe­line zu erstellen:

  • Sage­Ma­ker Jump­Start: Sage­Ma­ker Jump­Start bie­tet stan­dard­mä­ßig ver­schie­dene FM/LLMs für die Fein­ab­stim­mung und den Ein­satz. Diese bei­den Pro­zesse kön­nen recht kom­pli­ziert sein, daher abs­tra­hiert Jump­Start die Ein­zel­hei­ten und ermög­licht es Ihnen, Ihren Daten­satz und Ihre Modell-Meta­da­ten anzu­ge­ben, um die Fein­ab­stim­mung und den Ein­satz durch­zu­füh­ren. In die­sem Fall wäh­len wir Llama 7B aus und füh­ren eine Fein­ab­stim­mung der Anwei­sun­gen durch, die von Anfang an unter­stützt wird. Eine tie­fer gehende Ein­füh­rung in die Jump­Start-Fein­ab­stim­mung fin­den Sie in die­sem Blog und in die­sem Llama-Code­bei­spiel, das wir als Refe­renz ver­wen­den werden.
  • Sage­Ma­ker Clarify/FMEval: Sage­Ma­ker Cla­rify bie­tet über die Sage­Ma­ker Stu­dio-Benut­zer­ober­flä­che und die Open-Source-Python-Biblio­thek FME­Val ein Tool zur Bewer­tung von Foun­da­tion-Model­len. Die Funk­tion ist mit einer Viel­zahl ver­schie­de­ner Algo­rith­men aus­ge­stat­tet, die unter­schied­li­che NLP-Domä­nen wie Tex­terzeu­gung und ‑zusam­men­fas­sung abde­cken. In die­sem Bei­spiel nut­zen wir die Biblio­thek, um das Llama-Modell für einen Zusam­men­fas­sungs-Anwen­dungs­fall zu eva­lu­ie­ren. Eine tie­fere Ein­füh­rung in die Biblio­thek fin­den Sie in mei­nem Ein­füh­rungs­ar­ti­kel hier.
  • Sage­Ma­ker Pipe­lines Schritt-Deko­ra­tor: Sage­Ma­ker Pipe­lines ist eine MLOPs-Funk­tion in Sage­Ma­ker, die Ihnen hilft, ML-Work­flows zu ope­ra­tio­na­li­sie­ren. Mit Pipe­lines kön­nen Sie ver­schie­dene Schritte und Para­me­ter defi­nie­ren, um Ihren ML-Work­flow auf­zu­bauen. Inner­halb von Pipe­lines gibt es eine Funk­tion, die als Schritt­de­ko­ra­tor bekannt ist und mit der Sie Python-Code in Funk­tio­nen umwan­deln kön­nen, die Sie als Pipe­line anein­an­der­rei­hen kön­nen. In die­sem Bei­spiel wer­den wir diese Funk­tion ver­wen­den, um Funk­tio­nen für das Trai­ning und die Aus­wer­tung mit den oben defi­nier­ten Tools zu erstel­len. Eine Ein­füh­rung in den Step Deco­ra­tor fin­den Sie in mei­nem Ein­stiegs­ar­ti­kel hier.

Jetzt, da wir unse­ren Stack ver­ste­hen, kön­nen wir loslegen!

Inhalts­ver­zeich­nis

  1. Ein­rich­tung & Datensatzvorbereitung
  2. Auf­bau von Pipelineschritten
  3. Pipe­line-Aus­füh­rung
  4. Schluss­fol­ge­rung

1 Ein­rich­tung & Datensatzvorbereitung

Für die Ent­wick­lung wer­den wir in der neuen Sage­Ma­ker Stu­dio-Umge­bung arbei­ten (lokale Ker­nel-Unter­stüt­zung akti­viert). Wir wer­den eine ml.c5.18xlarge-Instanz mit einem Python3-Ker­nel verwenden.

Für unse­ren Anwen­dungs­fall wer­den wir eine Fein­ab­stim­mung vor­neh­men und einen Zusam­men­fas­sungs­an­wen­dungs­fall mit Llama eva­lu­ie­ren. Für unse­ren Daten­satz wer­den wir den öffent­li­chen Dolly-Daten­satz ver­wen­den (Lizenz: cc-by-sa‑3.0). Wir kön­nen die­sen Daten­satz mit­hilfe der inte­grier­ten Hug­ging­Face-Daten­satz­bi­blio­thek abru­fen und nach den Daten­punk­ten für die Zusam­men­fas­sung fil­tern. Außer­dem erstel­len wir einen Trai­nings- und einen Test­da­ten­satz für die Fein­ab­stim­mung 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-Meta­da­ten an Jump­Start wei­ter, um das rich­tige Llama 7B-Modell zu erhalten.

import sagemaker

model_id, model_version = "meta-textgeneration-llama-2-7b", "2.*"

Für die Schu­lung wer­den wir eine Fein­ab­stim­mung der Anwei­sun­gen vor­neh­men, d. h. wir berei­ten eine Vor­lage für die Auf­for­de­rung und die Ant­wort vor, oder in die­sem Fall den Text und die Zusam­men­fas­sung. Wir wer­den das fol­gende Bei­spiel als Refe­renz für den Schu­lungs­teil verwenden.

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 gemein­sa­men S3-Pfad hoch, um sie zu trai­nie­ren 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}")

2 Auf­bau von Pipelineschritten

Pipe­line-Ein­rich­tung
Um unsere Pipe­line ein­zu­rich­ten, benö­ti­gen wir einige ein­zelne Dateien, die unsere Aus­füh­rungs­um­ge­bung defi­nie­ren. Eine davon ist die Datei config.yaml, in der die Hard­ware für die Pipe­line-Aus­füh­rung sowie alle ande­ren von Ihnen defi­nier­ten Kon­fi­gu­ra­tio­nen fest­ge­legt wer­den. Die con­fig-Datei ver­weist auch auf Ihre requirements.txt, die in der Pipe­line-Umge­bung instal­liert 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ön­nen dann auf diese Kon­fi­gu­ra­ti­ons­da­tei als Umge­bungs­va­ria­ble verweisen:

    import os
    
    # Set path to config file
    os.environ["SAGEMAKER_USER_CONFIG_OVERRIDE"] = os.getcwd()

    Optio­nal kön­nen Sie bei Pipe­lines auch Para­me­ter defi­nie­ren, die in Ihre Pipe­line inji­ziert wer­den. In die­sem Fall defi­nie­ren wir den Hard­ware-Instanz­typ für den Job, der die ein­zel­nen 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") 

    Schritt Trai­ning & Ein­satz
    Für den Trai­nings­schritt wer­den wir mit Sage­Ma­ker Jump­Start arbei­ten, um unser Llama 7B-Modell zu opti­mie­ren. Wir über­ge­ben der Funk­tion ein paar ver­schie­dene Parameter:

    • Pfad der Trai­nings­da­ten: Dies ist der S3-Spei­cher­ort, der auf die Dateien train.jsonl und template.json verweist.
    • Modell-Meta­da­ten: Jump­Start erkennt anhand der über­ge­be­nen Modell-ID und Ver­sion, wel­ches Modell her­un­ter­ge­la­den wer­den soll. In die­sem Fall geben wir das Modell Llama 7B an.

    Sobald wir diese defi­niert haben, rich­ten wir den Jump­Start Esti­ma­tor mit die­sen Para­me­tern ein. Optio­nal kön­nen Sie auch modell­spe­zi­fi­sche Para­me­ter für die Fein­ab­stim­mung defi­nie­ren, abhän­gig von den Reg­lern, die Sie auf ihre Leis­tung tes­ten möch­ten. Beach­ten Sie den Schritt­de­ko­ra­tor mit der Funk­tion, der sym­bo­li­siert, dass es sich um einen Sage­Ma­ker-Pipe­line-Schritt und nicht um eine ein­fa­che Python-Funk­tion 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
        return endpoint_name

    Nach dem Trai­ning stel­len wir das fein abge­stimmte Llama-Modell auf einem Sage­Ma­ker Echt­zeit-End­punkt bereit, den wir für die Infe­renz auf­ru­fen kön­nen. Stan­dard­mä­ßig wird die­ser Instanz­typ sowohl für das Trai­ning als auch für die Infe­renz aus­ge­wählt, abhän­gig von dem von Ihnen gewähl­ten LLM, aber Sie kön­nen dies anpas­sen, wenn Sie die Hard­ware selbst aus­wäh­len möchten.

    Wir geben den End­punkt­na­men als Ein­gabe für unse­ren nächs­ten Schritt zurück, so dass wir Infe­renz und Aus­wer­tung mit die­sem End­punkt durch­füh­ren kön­nen. Wenn Sie die Pipe­line schließ­lich aus­füh­ren, sehen Sie für die­sen Schritt einen erfolg­rei­chen Jump­Start-Schu­lungs­auf­trag und einen in der Stu­dio-Benut­zer­ober­flä­che erstell­ten Endpunkt.

    Jetzt, da wir unse­ren End­punkt haben, kön­nen wir mit der Infe­renz von Bei­spie­len und der Aus­wer­tung die­ser Ergeb­nisse fortfahren.

    Schritt Infe­renz und Aus­wer­tung
    In unse­rem zwei­ten Schritt legen wir erneut bestimmte Para­me­ter für unsere Funk­tion fest:

    • End­point Name: Wir füh­ren vor der Aus­wer­tung eine Infe­renz gegen die­sen End­punkt durch.
    • S3_Test_Pfad: Wir hat­ten zuvor auch einen Test­da­ten­satz getrennt von unse­rem Trai­nings­da­ten­satz in S3 über­tra­gen. Wir wer­den den Daten­satz von die­sem Pfad abru­fen und vor der Aus­wer­tung eine Infe­renz durchführen.

    Zunächst laden wir die S3-Datei „test.jsonl“ her­un­ter, 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 die­ser Datei wird dann eine neue JSON­Lines-Datei erstellt, für die wir unsere Bewer­tungs­al­go­rith­men aus­füh­ren kön­nen. Aus Zeit­grün­den beschrän­ken wir diese Stich­probe auf nur 20 Daten­punkte, aber Sie kön­nen den Test­da­ten­satz 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

    Nach­dem die­ser Teil der Funk­tion aus­ge­führt wurde, sollte er eine Datei „results.jsonl“ erzeu­gen, die einige ver­schie­dene Para­me­ter enthält:

    • Dokument/Eingabe: Dies ist der Ori­gi­nal­text, der zusam­men­ge­fasst wer­den muss.
    • Grund­wahr­hei­t/Ist-Aus­gabe: Dies ist die Zusam­men­fas­sung, die im Test­da­ten­satz ent­hal­ten war; dies ist die Grund­wahr­heit, gegen die wir evaluieren.
    • Modell-Aus­gabe: Dies ist die Infe­renz, die wir mit unse­rem fein abge­stimm­ten Llama 7B-Modell durch­ge­führt haben. Wir wer­den unse­ren Bewer­tungs­al­go­rith­mus mit den Wer­ten der Grund­wahr­heit und den Ergeb­nis­sen der Modell­in­fe­renz durchführen.

    Da wir nun unse­ren Daten­satz für die Aus­wer­tung haben, kön­nen wir die FME­val-Biblio­thek 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

    Beach­ten Sie, dass wir den Sum­ma­riza­tio­nAc­cu­racy-Algo­rith­mus abru­fen, der Metri­ken wie Meteor, Rouge und Bert zurück­gibt. Um die voll­stän­dige Imple­men­tie­rung die­ser Algo­rith­men zu sehen, kön­nen Sie den Open-Source-Code unter die­sem Link auf­ru­fen. Im All­ge­mei­nen hat jede die­ser Metri­ken ihre eige­nen Vor- und Nach­teile, und Sie kön­nen aus­wäh­len, wel­che Metrik Sie für die Aus­wer­tung ver­wen­den möchten.

    • Rouge: Rouge‑N Scores, das im Wesent­li­chen nach N‑Gramm-Wort­über­schnei­dun­gen zwi­schen der Grund­wahr­heit und der Zusam­men­fas­sung der Modell­in­fe­renz sucht.
    • Meteor: Baut auf Rouge auf, um Stem­ming und Syn­onyme ein­zu­be­zie­hen. Dadurch wer­den mehr Ähn­lich­kei­ten in Tex­ten erfasst, falls die Wör­ter nicht genau übereinstimmen.
    • BertS­core: Ver­gleicht Wör­ter unter Ver­wen­dung von Cosi­nus-Ähn­lich­keit, wäh­rend es vor­trai­nierte Ein­bet­tun­gen von BERT ver­wen­det; dies wird bei der Instal­la­tion des Pakets heruntergeladen.

    Für einen tie­fe­ren Ein­blick in jede die­ser Metri­ken ver­weise ich auf den fol­gen­den Artikel.

    Für unsere Imple­men­tie­rung mit dem FME­val-Paket kon­fi­gu­rie­ren wir zunächst unse­ren Daten­satz in einem FME­val-spe­zi­fi­schen Dat­a­Con­fig-Objekt und legen die Spal­ten für die Grund­wahr­heit und die Modell­in­fe­renz 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"
        )

    Anschlie­ßend instan­zi­ie­ren wir den Sum­ma­riza­tio­nAc­cu­racy-Algo­rith­mus und füh­ren eine Aus­wer­tung mit unse­rem Dat­a­Con­fig-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 die­sem Fall schrei­ben wir die Metri­ken direkt in die Cloud­Watch-Pro­to­kolle. In einem rea­lis­ti­schen Anwen­dungs­fall kön­nen Sie diese in S3 oder ein Visua­li­sie­rungs­tool wie Quick­Sight aus­la­gern, um eine schö­nere Ansicht Ihrer Aus­wer­tung zu erhal­ten. Wenn Sie die Cloud­Watch-Pro­to­kolle für den zwei­ten Schritt nach der Pipe­line-Aus­füh­rung über­prü­fen, wer­den Sie fest­stel­len, dass die Metri­ken aus­ge­ge­ben wurden.

    3 Pipe­line-Aus­füh­rung

    Sobald die bei­den Pipe­line-Schritte defi­niert sind, kön­nen Sie sie ein­fach mit­ein­an­der ver­ket­ten, wie Sie es mit ein­fa­chen Python-Funk­tio­nen tun würden.

      # stitch together pipeline
      from sagemaker.workflow.pipeline import Pipeline
      
      endpoint_name = train_deploy(train_data_location)
      eval_metrics = evaluate(endpoint_name)

      Wir kön­nen dann ein Pipe­line-Objekt defi­nie­ren und eine Aus­füh­rung 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()

      Sie kön­nen die Aus­füh­rung der Pipe­line in der Stu­dio-Benut­zer­ober­flä­che anzei­gen und über­wa­chen. Es dau­ert etwa 45 Minu­ten, bis diese Pipe­line erfolg­reich abge­schlos­sen ist.

      4 Schluss­fol­ge­rung

      Ich hoffe, die­ser Arti­kel war eine nütz­li­che Ein­füh­rung in LLMOPs und den Auf­bau einer Pipe­line unter Ver­wen­dung ver­schie­de­ner Sage­Ma­ker-Funk­tio­nen. Mit der Erwei­te­rung der LLM-Anwen­dungs­fälle steigt auch der Bedarf an ange­mes­se­nen Expe­ri­men­ten, um die ideale Kon­fi­gu­ra­tion für Ihren LLM zu ermit­teln. Die­ses Bei­spiel kann erwei­tert wer­den, um meh­rere LLMs, Daten­sätze, Ein­ga­be­auf­for­de­rungs­vor­la­gen und mehr ein­zu­be­zie­hen. Blei­ben Sie dran für wei­tere Inhalte im Bereich GenAI/LLM.

      Quelle: medium.com