Große Sprachmodelle (LLM) sind seit der Veröffentlichung von ChatGPT zum Schlagwort in der Softwareentwicklung geworden. Seine Fähigkeit, natürliche Gespräche mit Menschen zu führen, ist nur die Spitze des Eisbergs. Durch Tools wie LangChain oder Semantic Kernel erweitert, haben LLMs das Potenzial, die Art und Weise, wie Benutzer mit Software interagieren, völlig zu verändern. Mit anderen Worten: LLMs können Synergien zwischen Funktionalitäten und Datenquellen schaffen und eine effizientere und intuitivere Benutzererfahrung bieten.
Viele Menschen nutzen beispielsweise bereits KI-basierte Tools zur Inhaltserstellung für ihre nächsten viralen Videos. Eine typische Videoproduktionspipeline umfasst Skripterstellung, Logistik, Storyboarding, Bearbeitung und Marketing, um nur einige zu nennen. Um den Prozess zu rationalisieren, könnte ein LLM den Erstellern von Inhalten beim Recherchieren beim Schreiben von Drehbüchern helfen, Requisiten für die Dreharbeiten kaufen, Storyboards auf der Grundlage des Drehbuchs erstellen (möglicherweise ist eine stabile Verbreitung für die Bilderzeugung erforderlich), den Bearbeitungsprozess erleichtern und auffällige Titel schreiben /Videobeschreibungen, um Aufrufe in sozialen Medien zu erzielen. LLMs sind der Kern, der all dies orchestriert. Bei der Integration von LLM in ein Softwareprodukt können jedoch mehrere Bedenken bestehen:
Wenn ich die API von OpenAI verwende, werde ich dann zu sehr von diesem Dienst abhängig? Was ist, wenn sie den Preis erhöhen? Was passiert, wenn sich die Serviceverfügbarkeit ändert?
Mir gefällt nicht, wie OpenAI Inhalte zensiert oder nicht konstruktives Feedback zu bestimmten Benutzereingaben gibt. (Oder umgekehrt: Mir gefällt nicht, wie die OpenAI-Zensur bestimmte Dinge ignoriert, die in meinem Anwendungsfall sensibel sind.)
Welche ChatGPT- Alternativen habe ich, wenn meine Kunden eine private Cloud- oder lokale Bereitstellung bevorzugen?
Ich möchte einfach die Kontrolle haben. Ich muss das LLM anpassen und möchte es günstig haben.
Aus diesem Grund frage ich mich, ob es ein Open-Source-Äquivalent zu den GPT-Modellen von OpenAI geben könnte. Glücklicherweise teilen die wunderbaren Open-Source-Communities bereits einige vielversprechende Lösungen. Ich habe beschlossen, Alpaca-Lora auszuprobieren, eine Parameter-effiziente Feinabstimmungsmethode für das Training Ihres eigenen LLM. In diesem Blogbeitrag geht es um den Prozess, die Probleme, auf die ich gestoßen bin, wie ich sie gelöst habe und was als nächstes passieren könnte. Wenn Sie die Technik auch für das Training Ihres eigenen LLM nutzen möchten, hoffe ich, dass die Informationen hilfreich sein können.
Lass uns anfangen!
LLaMA ist eine Sammlung von Foundation-Language-Modellen von Meta AI. Diese autoregressiven Modelle haben Parameterzahlen im Bereich von 7B bis 65B und können nur für Forschungszwecke verwendet werden. Meta hat kürzlich LLaMA 2 veröffentlicht, das mit einigen Einschränkungen kommerziell genutzt werden kann. Das Besondere an der LLaMA-Modellfamilie ist ihr Trainingsumfang und ihre Leistung. Meta gibt an, dass LLaMA anhand öffentlich verfügbarer Datensätze trainiert wird. Seine 13B-Variante erreicht bei den meisten Benchmarks eine mit GPT-3 (einem 175B-Modell) vergleichbare Leistung.
Alpaca ist Stanfords Versuch, ein auf LLaMA basierendes LLM zu entwickeln, das Anweisungen folgt. Es funktioniert so:
Bitten Sie ein Sprachmodell, Dreiergruppen aus Befehl, Eingabe und Antwort zu generieren.
Nutzen Sie die generierten Daten zur Feinabstimmung von LLaMA.
Nach der Feinabstimmung ist LLaMA in der Lage, den Anweisungen des Benutzers zu folgen und entsprechend zu reagieren.
Hier ist ein Vergleich zwischen der Reaktion von Vanilla LLaMA und der fein abgestimmten LLaMA-Reaktion:
{ "instruction": "Answer user's question.", "input": "Can you put a medium voltage circuit breaker in a low voltage switchgear? Why? Explain in detail.", "vanilla LLaMA": { "answer": "Yes, you can put a medium voltage circuit breaker in a low voltage switchgear. The medium voltage circuit breaker can be put in a low voltage switchgear because the medium voltage circuit breaker can be put in a low voltage switchgear because the medium voltage circuit breaker can be put in a low voltage switchgear because the medium voltage circuit breaker can be put....(repetition continues)" }, "fine-tuned LLaMA": { "answer": "No, it is not possible to put a medium voltage circuit breaker in a low voltage switchgear. This is because low voltage switchgear is designed to handle voltages up to 1 kV, while medium voltage switchgear is designed to handle voltages from 1 kV to 38 kV. The circuit breakers, contacts, and other components in low voltage switchgear are not suitable for the higher voltages and currents of medium voltage switchgear. Using a medium voltage circuit breaker in a low voltage switchgear could result in damage to the components and could pose a safety hazard." } }
Wie Sie sehen, verändert die Feinabstimmung das LLM-Verhalten ziemlich drastisch. Das Vanilla-Modell steckt in einer Wiederholungsschleife fest. Auch wenn das optimierte Modell keine hundertprozentig korrekte Antwort lieferte, lautet die Antwort zumindest klar „Nein“. Die Feinabstimmung ist ein notwendiger Schritt zur Herstellung eines nutzbaren LLM. In vielen Fällen reicht die Bereitstellung eines fein abgestimmten Open-Source-LLM aus. In einigen maßgeschneiderten Geschäftsanwendungsfällen kann es jedoch vorzuziehen sein, Modelle anhand domänenspezifischer Datensätze zu verfeinern.
Der größte Nachteil von Alpakas ist ihr Ressourcenbedarf. Auf der GitHub-Seite heißt es:
Naiverweise erfordert die Feinabstimmung eines 7B-Modells etwa 7 x 4 x 4 = 112 GB VRAM.
Das ist mehr VRAM, als eine A100 80-GB-GPU verarbeiten kann. Mit LoRA können wir die VRAM-Anforderung umgehen.
LoRA funktioniert so:
Das zusätzliche Gewicht hat einige besondere Eigenschaften. Inspiriert von dieser Arbeit haben Edward Hu et al. zeigte, dass man für ein ursprüngliches Modellgewicht $W_o\in R^{d \times k}$ ein fein abgestimmtes Gewicht $W_o'=W_o+BA$ für nachgelagerte Aufgaben erzeugen kann, wobei $B\in R^{d \times r}$ , $A \in R^{r \times k}$ und $r\ll min(d, k)$ ist der „intrinsische Rang“ des Adaptergewichts. Es ist wichtig, einen richtigen $r$ für das Adaptergewicht festzulegen, da ein kleinerer $r$ die Modellleistung verringert und ein größerer $r$ die Größe des Adaptergewichts ohne proportionale Leistungssteigerung erhöht.
Diese Technik ähnelt der verkürzten SVD, die eine Matrix annähert, indem sie sie in mehrere kleinere Matrizen zerlegt und nur einige wenige größte Singulärwerte beibehält. Unter der Annahme $W_o\in R^{100 \times 100}$ würde eine vollständige Feinabstimmung 10.000 Parameter ändern. Eine LoRA-Feinabstimmung mit $r=8$ würde das feinabgestimmte Gewicht in jeweils zwei Teile zerlegen: $B\in R^{100 \times 8}$ und $A\in R^{8 \times 100}$ Teil enthält 800 Parameter (insgesamt 1600 Parameter). Die Anzahl der trainierbaren Parameter wird um das 6,25-fache reduziert.
Nach der Transformation des Modells mit LoRA haben wir ein Modell erhalten, das nur etwa 1 % trainierbare Gewichte aufweist, dessen Leistung jedoch in bestimmten Bereichen erheblich verbessert ist. Dies würde es uns ermöglichen, 7B- oder 13B-Modelle auf besser zugänglicher Hardware wie RTX 4090 oder V100 zu trainieren.
Ich habe das Experiment in der Huawei Cloud mit einer GPU-beschleunigten VM-Instanz ( p2s.2xlarge
, 8vCPU, 64 GB RAM, 1x V100 32 GB VRAM) durchgeführt. Es ist bekannt, dass V100 den Datentyp bfloat16 nicht unterstützt und sein Tensorkern int8 nicht unterstützt Beschleunigung. Diese beiden Grenzwerte können das Training mit gemischter Präzision verlangsamen und zu einem numerischen Überlauf während des Trainings mit gemischter Präzision führen. Wir werden dies für die spätere Diskussion berücksichtigen.
finetune.py
und generate.py
sind der Kern des Projekts. Das erste Skript optimiert LLaMA-Modelle und das zweite Skript verwendet das optimierte Modell, um mit Benutzern zu chatten. Schauen wir uns zunächst den Hauptablauf von finetune.py
an:
model = LlamaForCausalLM.from_pretrained( base_model, # name of a huggingface compatible LLaMA model load_in_8bit=True, torch_dtype=torch.float16, device_map=device_map, )
tokenizer = LlamaTokenizer.from_pretrained(base_model) tokenizer.pad_token_id = ( 0 # unk. we want this to be different from the eos token ) tokenizer.padding_side = "left" # Allow batched inference
Bereiten Sie basierend auf der Trainingsvorlage Modelleingaben mit zwei Funktionen vor: tokenize
und generate_and_tokenize_prompt
.
Erstellen Sie ein an LoRA angepasstes Modell mit PEFT von Huggingface
config = LoraConfig( r=lora_r, # the lora rank lora_alpha=lora_alpha, # a weight scaling factor, think of it like learning rate target_modules=lora_target_modules, # transformer modules to apply LoRA to lora_dropout=lora_dropout, bias="none", task_type="CAUSAL_LM", ) model = get_peft_model(model, config)
trainer = transformers.Trainer( model=model, train_dataset=train_data, eval_dataset=val_data, args=transformers.TrainingArguments( ...
Das ist ziemlich einfach.
Am Ende erstellt das Skript einen Modellordner mit Prüfpunkten, Adaptergewichtungen und Adapterkonfiguration.
Schauen wir uns als Nächstes den Hauptablauf von generate.py
an :
model = LlamaForCausalLM.from_pretrained( base_model, device_map={"": device}, torch_dtype=torch.float16, ) model = PeftModel.from_pretrained( model, lora_weights, device_map={"": device}, torch_dtype=torch.float16, )
generation_config = GenerationConfig( temperature=temperature, top_p=top_p, top_k=top_k, num_beams=num_beams, **kwargs, ) generate_params = { "input_ids": input_ids, "generation_config": generation_config, "return_dict_in_generate": True, "output_scores": True, "max_new_tokens": max_new_tokens, }
if stream_output: # streaming ... # Without streaming with torch.no_grad(): generation_output = model.generate( input_ids=input_ids, generation_config=generation_config, return_dict_in_generate=True, output_scores=True, max_new_tokens=max_new_tokens, ) s = generation_output.sequences[0] output = tokenizer.decode(s) yield prompter.get_response(output)
gr.Interface( ...
In der README.md
des Projekts heißt es, dass die folgenden Feinabstimmungseinstellungen zu einem LLaMA 7B mit einer Leistung führen, die mit der von Stanford-Alpakas vergleichbar ist. Ein „offizielles“ Alpaka-Lora-Gewicht wurde auf Huggingface geteilt.
python finetune.py \ --base_model='decapoda-research/llama-7b-hf' \ --num_epochs=10 \ --cutoff_len=512 \ --group_by_length \ --output_dir='./lora-alpaca' \ --lora_target_modules='[q_proj,k_proj,v_proj,o_proj]' \ --lora_r=16 \ --micro_batch_size=8
Meiner Erfahrung nach ergab sich jedoch kein brauchbares Modell. Bei der Ausführung auf einem V100 treten die folgenden verblüffenden Probleme auf:
load_in_8bit
führt zu einem Datentypfehler.decapoda-research/llama-7b-hf
verwendete offenbar den falschen Tokenizer. Sein Pad-Token, Bos-Token und EOS-Token unterscheiden sich vom offiziellen Tokenizer von LLaMA.training loss = 0.0
und eval loss = NaN
.
Nachdem ich herumgestöbert und zahlreiche VM-Stunden verschwendet hatte, fand ich die notwendigen Änderungen, damit das Training auf einem einzelnen V100 funktioniert.
... # do not use decapoda-research/llama-7b-hf as base_model. use a huggingface LLaMA model that was properly converted and has a correct tokenizer, eg, yahma/llama-7b-hf or huggyllama/llama-7b. # decapoda-research/llama-7b-hf is likely to cause overflow/underflow on V100. train loss goes to 0 and eval loss becomes NaN. using yahma/llama-7b-hf or huggyllama/llama-7b somehow mitigates this issue model = LlamaForCausalLM.from_pretrained( base_model, load_in_8bit=True, # only work for 7B LLaMA. On a V100, set True to save some VRAM at the cost of slower training; set False to speed up training at the cost of more VRAM / smaller micro batch size torch_dtype=torch.float16, device_map=device_map, ) ... # comment out the following line if load_in_8bit=False model = prepare_model_for_int8_training(model) ... # set legacy=False to avoid unexpected tokenizer behavior. make sure no tokenizer warning was raised during tokenizer instantiation tokenizer = LlamaTokenizer.from_pretrained(base_model, legacy=False) ... # the following binding script results in invalid adapter. simply comment them out old_state_dict = model.state_dict model.state_dict = ( lambda self, *_, **__: get_peft_model_state_dict( self, old_state_dict() ) ).__get__(model, type(model)) ... # if load_in_8bit=True, need to cast data type during training with torch.autocast('cuda'): trainer.train(resume_from_checkpoint=resume_from_checkpoint)
Nachdem diese Änderungen vorgenommen wurden, erzeugen diese Trainingsargumente ein verwendbares Modell.
{ "args": [ "--base_model", "yahma/llama-7b-hf", "--num_epochs=10", "--data_path", "yahma/alpaca-cleaned", "--output_dir", "./your output dir", "--cutoff_len=512", "--lora_target_modules=[q_proj,k_proj,v_proj,o_proj]", "--lora_r=16", "--micro_batch_size=2", "--batch_size=16", "--wandb_project", "your wandb project name" ], }
Beachten Sie, dass das Trainingsskript nicht nur das „beste“ Modell (Prüfpunkt mit dem geringsten Auswertungsverlust) speichert, sondern auch einige Prüfpunkte. Ich habe wandb verwendet, um Trainings-/Evaluierungsverluste für die weitere Analyse zu protokollieren.
Ich werfe dem fein abgestimmten Modell einige zufällige Fragen. Es gibt einige vernünftige Antworten.
Hier ist ein Beispiel:
Anleitung: Beantworten Sie die Frage des Benutzers sorgfältig.
Eingabe: Was passiert, wenn Sie einen Schaltwagen mit Leistungsschalter ausfahren?
Reaktion: Wenn ein Schaltwagen mit Leistungsschalter „herausgefahren“ wird, bedeutet dies, dass alle Leistungsschalter im Wagen geöffnet wurden und der Wagen sich in einem stromlosen Zustand befindet. Dies geschieht normalerweise, wenn der Lkw gewartet, repariert oder inspiziert wird. Sobald die Leistungsschalter ausgefahren sind, kann am Stapler sicher gearbeitet werden und er kann wieder mit Strom versorgt werden, sobald die erforderlichen Wartungs- oder Reparaturarbeiten abgeschlossen sind.
In diesem Beispiel wird das interne Wissen des LLM abgefragt. Das Model schien viel über Schaltanlagen zu wissen. Als ich jedoch versuchte, seine Denkfähigkeit empirisch zu testen, kam ich zu einem sehr seltsamen Ergebnis:
Anweisung:
Beantworten Sie Fragen anhand der folgenden Informationen zu einem Projekt. Das Projekt Chinese-Llama-2 zielt darauf ab, das Verständnis, die Generierung und die Übersetzungsfähigkeiten des großen Sprachmodells Llama-2 in chinesischer Sprache zu verbessern. Mit der Anwendung von Methoden wie LoRA-Feinabstimmung, vollständiger Parameteranweisungs-Feinabstimmung und sekundärem Vortraining laden wir Sie herzlich ein, die zugehörigen Datensätze, Trainingsleitfäden und Modellparameter herunterzuladen und zu nutzen.
Input: Welche Methoden werden in diesem Projekt verwendet?
Antwort: Das Projekt Chinese-Llama-2 zielt darauf ab, das Verständnis, die Generierung und die Übersetzungsfähigkeiten des großen Sprachmodells Llama-2 in chinesischer Sprache zu verbessern. Mit der Anwendung von Methoden wie LoRA-Feinabstimmung, vollständiger Parameter-Anweisungs-Feinabstimmung und sekundärem Vortraining laden wir Sie herzlich ein, die zugehörigen Datensätze, Trainingsleitfäden und Modellparameter herunterzuladen und zu nutzen.</s>
Wie Sie sehen, wiederholt das Model die Aufforderung gerne. Ich nenne es das Prompt-Repeater- Modell, und andere Entwickler haben dieses Verhalten ebenfalls in den Repo-Problemen gemeldet. Die Fähigkeit, Fragen zu beantworten, wird durch sorgfältiger gestaltete Eingabeaufforderungen nur noch besser. Dieses Verhalten ist in einem Produktionssystem nicht das, was wir wollen, da wir keine sofortige Wirksamkeit über verschiedene Modelle hinweg garantieren können. Die Modelle müssen weniger empfindlich auf Eingabeaufforderungen reagieren. Wir wollen die Leistung dieses LLM irgendwie verbessern.
In der nächsten Sitzung werde ich diskutieren, was dieses Problem verursacht hat und wie man die Ergebnisse bei der Feinabstimmung verbessern kann.
Hier sind drei Dinge, die ich versucht habe, um das Ergebnis zu optimieren:
Maskieren Sie Verluste bei Eingabeaufforderungen (hilft, Wiederholungen von Eingabeaufforderungen zu vermeiden)
Deaktivieren Sie die Option group-by-length
(hilft, die Leistung zu verbessern, lässt die Verlustkurve glatter aussehen)
Vertrauen Sie der Bewertungsverlustkurve nicht. Verwenden Sie einen Prüfpunkt, der einen geringeren Trainingsverlust aufweist, auch wenn sein Bewertungsverlust höher sein könnte als der des „besten“ Prüfpunkts. (hilft, die Leistung zu verbessern, da der Bewertungsverlust hier nicht die beste Matrix ist)
Lassen Sie uns diese 3 Punkte einzeln erklären.
Ich suchte nach den Ursachen für prompte Wiederholungen, bis ich diesen Beitrag und die offizielle Lora-Weights-Commit-Nachricht fand. Sie schlugen vor, Eingabeaufforderungen bei der Verlustberechnung auszuschließen. Grundsätzlich möchten Sie das Modell nicht dazu ermutigen, Eingabeaufforderungstoken auszugeben. Das Ausblenden der Eingabeaufforderungen während des Trainings würde das Modell nicht dazu ermutigen, Eingabeaufforderungs-Tokens zu wiederholen. Die folgende Tabelle erklärt dies: Von den drei Trainingsläufen ist stoic-star-6
der einzige Lauf, bei dem Eingabeaufforderungen während des Trainings nicht ausgeblendet wurden. Sein Trainingsverlust ist daher zu Beginn höher. Ich vermute, dass, wenn a) Eingabeaufforderungen bei der Verlustberechnung nicht ausgeblendet werden und b) das Training unzureichend ist, das Modell eher dazu neigt, Eingabeaufforderungen zu wiederholen, als Anweisungen zu befolgen.
Im Quellcode erfolgt die Verlustmaskierung durch das Setzen von Prompt-Tokens auf -100:
Token mit auf
-100
gesetzten Indizes werden ignoriert (maskiert), der Verlust wird nur für die Token mit Labels in[0, ..., config.vocab_size]
berechnet.
group-by-length
Mit der Option group-by-length
kann der Huggingface- Trainer
Eingaben ähnlicher Länge in Stapel gruppieren. Dies hilft, VRAM-Nutzung beim Auffüllen von Eingabesequenzen zu sparen. Allerdings würde dies die Probenvarianz innerhalb einer einzelnen Charge erheblich verringern. Während des Trainingsprozesses ziehen wir es im Allgemeinen vor, das Modell einer Vielzahl von Trainingsbeispielen auszusetzen. Wenn Sie group-by-length
auf False
setzen, wird die Stichprobenvariation verringert. Es verursacht auch Verlustschwankungen während des Trainings (z. B. haben zwei aufeinanderfolgende Chargen gepolsterte Längen von 10 und 50. Die kürzere Charge hat einen geringeren Verlust und die längere Charge einen höheren Verlust. Dies führt zu einer oszillierenden Verlustkurve, wie in der Abbildung dargestellt unten).
Da andererseits group-by-length
die Stichprobenvariation innerhalb der Charge verringert, vermute ich, dass dadurch auch die Modellleistung beeinträchtigt werden könnte. Die folgende Abbildung vergleicht den Trainingsverlust mit oder ohne group-by-length
. Es ist klar, dass der durchschnittliche Verlust für den Lauf peach-violet-19
höher ist, bei dem group-by-length
aktiviert ist.
Ich habe festgestellt, dass Trainingsverlust und Bewertungsverlust bei allen meinen Läufen unterschiedlich ausfallen.
Hier ist ein Beispiel:
In diesem Beispiel liegt der optimale Prüfpunkt um epoch=2
herum. Wenn Sie den optimalen Prüfpunkt laden, wie im ursprünglichen Repo vorgeschlagen, erhalten Sie höchstwahrscheinlich ein Prompt-Repeater- Modell, wie in der vorherigen Sitzung erwähnt.
Durch das Laden eines Modellkontrollpunkts gegen Ende des Trainings erhielt ich ein leistungsfähigeres Modell. Ich habe die gleiche Frage wie zuvor gestellt und das Modell hat die richtige Antwort zurückgegeben:
Anweisung:
Beantworten Sie Fragen anhand der folgenden Informationen zu einem Projekt. Das Projekt Chinese-Llama-2 zielt darauf ab, das Verständnis, die Generierung und die Übersetzungsfähigkeiten des großen Sprachmodells Llama-2 in chinesischer Sprache zu verbessern. Mit der Anwendung von Methoden wie LoRA-Feinabstimmung, vollständiger Parameteranweisungs-Feinabstimmung und sekundärem Vortraining laden wir Sie herzlich ein, die zugehörigen Datensätze, Trainingsleitfäden und Modellparameter herunterzuladen und zu nutzen.
Input: Welche Methoden werden in diesem Projekt verwendet?
Antwort: Das Chinese-Llama-2-Projekt verwendet Methoden wie LoRA-Feinabstimmung, vollständige Parameter-Anweisungs-Feinabstimmung und sekundäres Vortraining.</s>
Aber gemessen am Bewertungsverlust sollte dieses Modell schlechter abschneiden. Was verursacht das Dilemma?
Ich denke, dass der Bewertungsverlust keine gute Matrix zur Messung der Leistung großer Sprachmodelle ist. LLaMA verwendet CrossEntropyLoss
für Trainings- und Bewertungsverluste:
# modelling_llama.py from transformers library ... # forward function under LlamaForCausalLM class if labels is not None: # Shift so that tokens < n predict n shift_logits = logits[..., :-1, :].contiguous() shift_labels = labels[..., 1:].contiguous() # Flatten the tokens loss_fct = CrossEntropyLoss() loss = loss_fct(shift_logits.view(-1, self.config.vocab_size), shift_labels.view(-1))
Beim Testen mit einem Bewertungssatz könnte ein Modell dieselbe Antwort mit unterschiedlichem Wortlaut liefern:
{ "evaluation prompt": "What is 1 + 3?" "evaluation answer": "4." "prediction answer": "The answer is 4." }
Beide Antworten sind richtig, aber wenn die Vorhersageantwort nicht genau mit der Bewertungsantwort übereinstimmt, ist der Bewertungsverlust hoch. In diesem Fall benötigen wir eine bessere Bewertungsmatrix, um die Leistung des Modells zu messen. Um die richtige Auswertung kümmern wir uns später. Nehmen wir zunächst an, dass das beste Modell dasjenige mit dem geringsten Trainingsverlust ist.
Ich habe versucht, ein 13B-Modell auf V100 zu optimieren. Während V100 sowohl int8- als auch fp16-Training auf einem 7B-Modell verarbeiten kann, kann es int8-Training auf einem 13B-Modell einfach nicht verarbeiten. Wenn load_int_8bit = True
ist, erzeugt das 13B-Modell training_loss = 0.0
. Wir können einige Debugging-Tools verwenden, um zu verstehen, warum dies geschieht ( Spoiler-Alarm: Es wird durch Überlauf/Unterlauf verursacht).
Ich habe das DebugUnderflowOverflow
Tool von Huggingface verwendet, um Parameter während des Trainings zu überprüfen. Im ersten Vorwärtsdurchlauf wurden inf/nan-Werte erkannt:
Genauer gesagt hat DebugUnderflowOverflow
negative Unendlichkeitswerte in der zweiten Eingabe von LlamaDecoderLayer
abgefangen, wie in der Abbildung unten gezeigt. Die 2. Eingabe ist attention_mask
. Ich bin etwas tiefer gegangen und habe herausgefunden, dass die attention_mask
sehr große negative Werte für Füllelemente haben soll. Zufälligerweise stehen die negativen Unendlichkeitswerte am Anfang jeder Sequenz. Diese Beobachtung lässt mich glauben, dass auf dieser Ebene negative Unendlichkeitswerte auftreten sollen. Weitere Untersuchungen zeigten auch, dass die Unendlichkeitswerte in den nächsten Schichten nicht zu weiteren Unendlichkeitswerten führten. Daher ist ein Überlauf bei LlamaDecoderLayer
höchstwahrscheinlich nicht die Hauptursache für abnormale Trainingsverluste.
Als nächstes habe ich die Ausgaben jeder Ebene überprüft. Es war sehr deutlich, dass die Ausgaben der letzten Schichten überlaufen, wie in der Abbildung unten gezeigt. Ich glaube, dass dies an der begrenzten Präzision der int-8-Gewichte liegt (oder am begrenzten Bereich von float16
. Es ist wahrscheinlich, dass bfloat16
dieses Problem vermeiden könnte).
Um das Überlaufproblem zu lösen, habe ich während des Trainings float16 verwendet. V100 verfügt nicht über genügend VRAM, um ein 13B-Modell zu trainieren, es sei denn, es wurden einige Tricks angewendet. Hugging Face DeepSpeed bietet verschiedene Methoden, wie z. B. CPU-Offloading, um die Trainings-VRAM-Nutzung zu reduzieren. Der einfachste Trick besteht jedoch darin, das Gradienten-Checkpointing zu aktivieren, indem Sie model.gradient_checkpointing_enable()
aufrufen, bevor das Training beginnt.
Beim Gradienten-Checkpointing wird die Trainingsgeschwindigkeit zugunsten einer geringeren VRAM-Nutzung geopfert. Typischerweise wurden während des Vorwärtsdurchlaufs Aktivierungen berechnet und im Speicher gespeichert, um sie während des Rückwärtsdurchlaufs zu verwenden. Dies beansprucht zusätzlichen Speicher. Beim Gradienten-Checkpointing werden Aktivierungen jedoch nicht während des Vorwärtsdurchlaufs gespeichert, sondern während des Rückwärtsdurchlaufs neu berechnet, wodurch VRAM gespart wird. Hier ist ein schöner Artikel über diese Technik.
Ich konnte Llama 13B mit float16 und aktiviertem Gradienten-Checkpointing trainieren:
python finetune.py \ --base_model=yahma/llama-13b-hf \ --num_epochs=10 \ --output_dir 'your/output/dir' \ --lora_target_modules='[q_proj,k_proj,v_proj,o_proj]' \ --cutoff_len=1024 \ --lora_r=16 \ --micro_batch_size=4 \ --batch_size=128 \ --wandb_project 'alpaca_lora_13b' \ --train_on_inputs=False
Das 13B-Modell kann einige erweiterte Aufgaben wie die Erkennung von Namensentitäten bewältigen. Ich verwende für den Test eine Beispielaufforderung und dies ist die genaue Antwort des 13B-Modells:
Alles gut! Das ist ein aufregender Anfang. Das Modell ermöglicht es uns, komplexe Anwendungen mit LangChain zu erstellen.
Derzeit fehlen uns noch Tools zur automatischen Modellbewertung. Wir können Language Model Evaluation Harness verwenden, um unsere Modelle anhand vieler Testfälle zu bewerten oder sogar unsere eigenen Testfälle zu erstellen. Es ist das gleiche Tool, das Hugging Face für sein Open LLM Leaderboard verwendet. Während die Bewertung ein entscheidender Aspekt der LLM-Entwicklung ist, konzentriert sich dieser Artikel ausschließlich auf den Ausbildungsprozess. Ich werde die Bewertung möglicherweise in einem zukünftigen Artikel besprechen.
In diesem Artikel haben wir das Konzept der Large Foundation Models (LFMs) und mehrere Feinabstimmungsmethoden vorgestellt, die dafür sorgen, dass sich LFMs wie gewünscht verhalten. Anschließend konzentrierten wir uns auf LoRA, eine parametereffiziente Methode zur Feinabstimmung von LFM, und erklärten den Feinabstimmungscode sowie Techniken zur Leistungsverbesserung. Schließlich gingen wir noch einen Schritt weiter und trainierten erfolgreich ein Llama 13B-Modell auf einer V100-GPU. Obwohl beim Training des 13B-Modells einige Probleme auftraten, stellten wir fest, dass diese Probleme durch Hardwareeinschränkungen verursacht wurden, und stellten Lösungen vor. Am Ende haben wir ein fein abgestimmtes LLM erhalten, das funktioniert, aber wir haben die Leistung des LLM noch nicht quantitativ bewertet.
Über den Autor
Hallo! Mein Name ist Wei. Ich bin ein engagierter Problemlöser, Senior AI Specialist und Analytics-Projektleiter bei ABB sowie Google Developer Expert für maschinelles Lernen . Ich habe einen Master-Abschluss in Maschinenbau von der University of Minnesota Twin Cities und einen BS-Abschluss in Maschinenbau von der University of Illinois in Urbana-Champaign.
Mein Tech-Stack konzentriert sich auf Python-/C#-Programmierung, Computer Vision, maschinelles Lernen, Algorithmen und Mikrodienste, aber ich habe auch ein breites Spektrum an Interessen wie Spieleentwicklung (Unity), Front-/Back-End-Entwicklung, technische Führung, Basteln mit Einplatinencomputern und Robotik.
Ich hoffe, dass dieser Artikel den Menschen irgendwie helfen kann. Vielen Dank fürs Lesen und viel Spaß beim Lösen des Problems!