著者 | ファリード・カーン 編纂者:岳陽 LLaMA 3[1]は、Mistral[2]に次ぐ最も有望なオープンソースモデルの一つであり、様々なタスクに対応できる強力な能力を備えています。以前、私はLLaMAアーキテクチャに基づいて、230万以上のパラメータを持つ大規模言語モデル(LLM)をゼロから構築する方法を詳しく説明した記事を公開しました。LLaMA-3がリリースされたので、このモデルをよりシンプルな方法で再構築します。 このブログ投稿では GPU を使用する必要はありませんが、サイズが 15 GB を超える複数のファイルを読み込むため、少なくとも 17 GB の RAM が必要です。 練習を容易にするために、私はこのブログから1行ずつコピーして貼り付けるという面倒なプロセスを回避し、すべての操作コードと詳細な手順を含むノートブックファイルを含むコードリポジトリをGitHub [3] に作成しました。 230万以上のパラメータを持つ大規模言語モデル(LLM)をゼロから構築する方法を学びたいですか?このガイド[4]を参考に、大規模モデルをゼロから構築する旅に出かけましょう。 目次01 前提条件の概要 02 LLaMA 2とLLaMA 3の違い 03 LLaMA 3アーキテクチャの探究
04 開発環境を構成する 05. プロジェクトの組織ロジックを明確にする 06. 入力データに対して単語分割を実行します。 07 各トークンの埋め込みを作成する 08. RMSNormを使った正規化 09. 主要な要素 (クエリ、キー、値) に注意してください。 10 RoPEの実装 11 自己注意を実装する 12 マルチヘッドアテンションの実装 13. SwiGLU活性化関数を実装する 14. 上記のモデルコンポーネントを統合する 15. 証人モデルが入力に基づいて出力を生成する方法。 01 前提条件の概要この記事はオブジェクト指向プログラミング(OOP)については扱いません。Pythonの基本的な構文を理解していれば十分です。ただし、このブログ記事をスムーズに読むには、ニューラルネットワークとTransformerのアーキテクチャの基礎的な理解が不可欠です。この2点が、この記事を学習するための必須条件です。 02 LLaMA 2とLLaMA 3の違い技術的な詳細に入る前に、LLaMA 3のアーキテクチャはLLaMA 2と全く同じであることを理解しておくことが重要です。そのため、LLaMA 3の技術的な詳細をまだ調べていない方でも、このブログ記事を読んでいただければ全く問題ありません。また、LLaMA 2のアーキテクチャに馴染みのない方もご安心ください。興味のあるすべての読者の皆様がこの記事からメリットを得られるよう、技術的な詳細の概要を説明します。 LLaMA 2 と LLaMA 3 に関する重要なポイントは次のとおりです。 03 LLaMA 3アーキテクチャの探究コードを書き始める前に、LLaMA 3アーキテクチャを理解することが不可欠です。読者がこの概念をより明確に理解できるように、オリジナルのTransformerアーキテクチャとLLaMA 2/3、およびMistralの類似点と相違点を視覚的に示した比較表を掲載しています。 ラジェシュ・カヴァディキより それでは、LLaMA 3 のコア要素のいくつかの詳細を見ていきましょう。 3.1 RMSNormを用いた前正規化LLaMA 2 のアプローチに従って、LLaMA 3 は RMSNorm と呼ばれる手法を採用して、各 Transformer サブレイヤーの入力データを正規化します。 大きな試験の準備をしていて、分厚い教科書が手元にあると想像してみてください。教科書は複数の章に分かれています。各章はそれぞれ異なるトピックを扱っていますが、全体のテーマを理解する上で特に重要な章もあります。教科書全体を読む前に、各章の重要性を評価してみることにしました。すべての章に同じ時間をかけるのではなく、核となる章にエネルギーを集中させたいのです。 ChatGPTのような大規模言語モデル(LLM)と同様に、 RMSnormを事前正規化に使用することは、各章の重要度に応じて「重み付け」するようなものです。本のテーマを理解する上で重要な章には高い「重み付け」が与えられ、それほど重要でない章には低い「重み付け」が与えられます。 そのため、教材を深く理解する前に、各章の重要度に基づいて学習計画を調整します。重要度の高い章には、より多くの時間と労力を割き、その中核となる概念を包括的かつ深く理解できるようにします。 「二乗平均平方根レイヤー正規化」(https://arxiv.org/abs/1910.07467) 同様に、RMSNormを事前正規化に用いることで、大規模言語モデル(LLM)が文脈とその意味を理解する上でテキストのどの部分がより重要かを特定するのに役立ちます。RMSNormは、重要な要素に高い重みを、そうでない要素に低い重みを割り当てることで、文脈を正確に解釈するために最も必要な部分にモデルが注意を集中するように導きます。このトピックに興味のある読者は、RMSNormの具体的な実装について、こちら[5]で詳しく知ることができます。 3.2 SwiGLU活性化関数LLaMA は PaLM モデルからインスピレーションを得て、SwiGLU アクティベーション関数を導入しています。 あなたが教師で、生徒に複雑なトピックを説明しようとしているところを想像してみてください。大きなホワイトボードに要点を書き、説明を分かりやすくするために図を描いています。しかし、時には手書きが汚かったり、図が完璧でなかったりするかもしれません。これは、生徒が教材を理解するのを著しく困難にする可能性があります。 それぞれの重要度に応じて、文字の大きさとスタイルを自動調整する「魔法のペン」を持っていると想像してみてください。重要なポイントは大きくはっきりと書き、より目立たせます。重要度の低いポイントは小さく書きますが、それでも読みやすいように書きます。 SwiGLUは、ChatGPTのような大規模言語モデル(LLM)において「魔法のペン」のような役割を果たします。テキストを生成する前に、SwiGLUは文脈との関連性に基づいて各単語またはフレーズの重要度を調整します。「魔法のペン」が文章を書く際にフォントサイズとスタイルを調整するように、SwiGLUも各単語またはフレーズの重要度を調整します。 「SwiGLU: GLUバリアントによるTransformerの改良」(https://kikaben.com/swiglu-2020/) このように、大規模言語モデルはテキストを生成する際に重要な部分を強調表示し、それらの内容がテキスト全体の理解にさらに貢献するようにすることができます。このように、SwiGLUは大規模言語モデルがより明確で理解しやすいテキストを生成するのを支援します。これはまるで「魔法のペン」がホワイトボード上で生徒にとってより明確な内容を作成するのを支援するのと同様です。SwiGLUの詳細については、関連論文[6]を参照してください。 3.3 回転エンコーディング(RoPE)Rotary Embeddings (RoPE) は、LLaMA 3 で使用される位置埋め込み方法です。 教室でグループディスカッションを企画し、生徒に座席を割り当てる必要があると想像してみてください。従来の方法では、生徒は固定席に座り、列と行に座席を配置します。しかし、状況によっては、生徒がより自由に動き回り、交流できるような、より柔軟な座席配置を設計したい場合があります。 RoPEは、特別な座席配置のようなもので、各生徒が他の生徒との相対的な位置関係を維持しながら、回転したり姿勢を変えたりすることができます。生徒は固定された場所に縛られることなく、円を描くように動くことができるため、よりスムーズな交流が可能になります。 このシナリオでは、各生徒はテキストシーケンス内の単語またはトークンを表し、その位置はシーケンス内の位置に対応します。RoPEでは、生徒が回転したり位置を変更したりできるのと同様に、テキストシーケンス内の単語の位置埋め込みも、相対的な位置に基づいて動的に調整できます。 そのため、 RoPEはテキスト処理の過程で、位置エンコーディングを単純に固定された静的な要素とみなすのではなく、回転の概念を巧みに取り入れることで、表現をより柔軟で多様なものにし、テキストシーケンス内の単語間の変化する関係をより正確に把握できるようにします。この柔軟性により、ChatGPTなどのモデルは、自然で流暢、かつ論理的に一貫したテキストコンテンツをより深く理解・生成する能力が高まります。これは、教室で動的な座席配置を採用することで、よりインタラクティブな議論を促進できるのと同じです。その背後にある数学的原理を理解するには、RoPEの関連論文[7]を参照してください。 3.4 バイトペアエンコーディング(BPE)アルゴリズムLLaMA 3はOpenAIが提供するtiktokenライブラリのバイトペアエンコーディング(BPE)を使用していますが、LLaMA 2のBPEセグメンテーションメカニズムはsentencepieceライブラリに基づいています。両者には微妙な違いはあるものの、現時点での主な課題はBPEが実際に何であるかを理解することです。 簡単な例から始めましょう。「ab」、「bc」、「bcd」、「cde」という単語を含むテキストコーパスがあるとします。コーパス内のすべての単語を個々の文字からなる語彙に分解すると、{「a」、「b」、「c」、「d」、「e」}となります。 次に、テキストコーパス内の各文字の出現頻度を計算します。この例では、統計は{"a": 1, "b": 3, "c": 3, "d": 2, "e": 1}となります。
この手法は、大規模言語モデル(LLM)の性能を大幅に向上させ、一般的でない単語や語彙に含まれない単語を効果的に処理できます。TikToken BPEとSentencepiece BPEの主な違いは、TikToken BPEは既知の完全な単語を盲目的に分割しないことです。例えば、「hugging」が語彙に既に存在する場合、そのまま残り、["hug", "ging"]のように分割されることはありません。 04 環境設定いくつかのPythonライブラリを使用します。「モジュールが見つかりません」というエラーを防ぐため、以下のコマンドを実行して事前にこれらのライブラリをインストールすることをお勧めします。 必要なライブラリをインストールしたら、次は関連ファイルをダウンロードします。llama-3-8Bモデルのアーキテクチャを再現するため、HuggingFaceプラットフォームにアカウントを登録する必要があります。また、llama-3は制限付きモデルであるため、コンテンツにアクセスする前に利用規約に同意する必要があります。 具体的な手順は以下のとおりです。
上記の2つの手順を完了すると、必要なファイルがダウンロードされます。ダウンロードには2つの方法があります。
LLaMA-3 構成ファイルのダウンロード
このコードユニットを実行すると、トークンの入力を求められます。ログインに問題が発生した場合は、もう一度お試しください。その際、「token as git credential」オプションのチェックを外してください。その後は、簡単なPythonスクリプトを実行するだけで、llama-3-8Bアーキテクチャの3つのメインファイルを正常にダウンロードできます。 必要なファイルをすべてダウンロードしたら、このブログで使用する Python ライブラリをインポートする必要があります。 次に、ダウンロードした各ファイルの具体的な目的を理解する必要があります。 05. プロジェクトの組織ロジックを明確にする私たちの目標はllama-3を正確に再現することなので、入力テキストに関わらず意味のある出力が得られるはずです。例えば、「太陽の色は何ですか?」という質問を入力した場合、期待される答えは当然「白」です。しかし、これを実現するには通常、大規模なデータセットを用いて大規模言語モデル(LLM)を学習する必要があり、これは多くの場合非常に多くの計算量を必要とするため、現実的ではありません。 しかし、Metaはllama-3アーキテクチャファイル(より正確には、事前学習済みモデルの重み)を一般公開しています。これは私たちがダウンロードしたファイルなので、モデルを自分で学習させたり、アーキテクチャを再現するために大規模なデータセットを収集したりする必要はありません。準備はすべて整ったので、あとはこれらのコンポーネントを適切な場所で正しく使用するだけです。 それでは、これらのドキュメントを 1 つずつ調べて、それぞれの重要な役割を見てみましょう。 `tokenizer.model` — 前述の通り、LLaMA-3はTikTokライブラリのバイトペアエンコーディング(BPE)トークン化技術を使用しています。この技術は、LLaMA-2で使用されたデータセットの7倍にあたる15兆トークンを含む大規模なデータセットで学習されました。それでは、このファイルを読み込んで、その秘密を探ってみましょう。 ` ランダムに10個の項目を選択して表示すると、これらはすべてBPEアルゴリズムを用いて慎重に構築された文字列であることがわかります。これは、前述の例と非常によく似ています。ここで、辞書のキーはBPEアルゴリズムの学習プロセスにおけるバイトシーケンスを表し、辞書の値は出現頻度によって決定されるマージランクを反映しています。 ファイルsolidated.00.pthには重要な詳細が含まれています。それは、Llama-3-8Bモデルがトレーニング中に学習したすべてのパラメータ(モデル重みとも呼ばれます)が格納されているということです。これらのパラメータは、トークンのエンコード方法、アテンション重みの計算方法、フィードフォワードニューラルネットワーク変換の実行方法、そして最終的な出力の正規化方法など、モデルの動作の詳細を明らかにします。 Transformerアーキテクチャに精通している方であれば、クエリ行列やキー行列といった概念は容易に理解できるでしょう。後ほど、これらのモデルレイヤー/重みを用いて、Llama-3アーキテクチャ内でこれらの行列を構築する方法を説明します。 params.json — このファイルには、次のようなさまざまなパラメータの特定の値が記録された多くの情報が含まれています。 これらの値は、Llama-3アーキテクチャを段階的に再現するのに役立ちます。これらの値は、アテンションヘッドの数や埋め込みベクトルの次元など、モデルアーキテクチャの主要なパラメータを詳細に記録します。 ここで、後続の手順で使用するために、これらのデータ値を適切に保存しましょう。 トークナイザーモデル、キーの重みを含むアーキテクチャモデル、そして詳細な設定パラメータが揃ったので、最後のステップを除いてすべて準備完了です。さあ、最も基本的な部分から始めて、独自のLlama-3モデルを構築してみましょう! 06. 入力データに対して単語分割を実行します。このステップの主なタスクは、入力テキスト情報をトークン形式に変換することです。このステップの鍵となるのは、まず一連の特殊なトークンを生成することです。これらの特殊なトークンは、ナビゲーションマーカーのように、単語分割後のテキストに埋め込まれます。これにより、単語分割器は特定の条件や指示を認識して処理することができ、プロセス全体にとって不可欠な要素となります。 次に、入力テキスト内の様々な種類の部分文字列を識別するための様々なパターンを定義し、テキスト分割ルールを策定します。具体的な手順を見てみましょう。 このツールは、入力テキストから単語、短縮形、数字(最大3桁) 、および空白文字以外の文字で構成される文字列を抽出できます。ニーズに合わせてカスタマイズできます。 TikTokenのBPEアルゴリズムを使用して、3つのパラメータ( t0okenizer_model 、 tokenize_breaker 、 special_tokens )を受け入れるシンプルなトークン化関数を作成する必要があります。この関数は、入力テキストを必要に応じてエンコードまたはデコードします。 エンコード関数が正しく動作することを確認するために、まず「Hello World」をテストテキストとして入力します。関数はまずテキストをエンコードし、数値列に変換します。次に、これらの数値を元のテキストにデコードし、最終的に「hello world!」を取得します。このプロセスにより、関数が正しく動作することが証明されます。それでは、入力コンテンツの単語分割を始めましょう。 特殊な用語を使用して、入力テキスト「生命、宇宙、そして万物についての究極の問いに対する答えは何か」のエンコードを開始します (翻訳者注: これは 07 各トークンの埋め込みを作成する入力ベクトルの長さを確認すると、その長さは次のようになります。 現在、入力ベクトルの次元は(17x1)です。次のステップは、分割された各単語を対応する埋め込み表現に変換することです。これにより、元の(17x1)トークンは(17x4096)次元の埋め込み行列に拡張されます。つまり、各トークンは長さ4096の埋め込みベクトルを持つことになります。 注意すべき点は、これらの埋め込みベクトルが正規化されていないことです。正規化を行わないと、深刻な悪影響が生じる可能性があります。次のセクションでは、入力ベクトルの正規化処理について説明します。 08. RMSNormを使った正規化入力ベクトルが正規化されていることを確認するために、前述の RMSnorm 式を使用して処理します。 「二乗平均平方根レイヤー正規化」(https://arxiv.org/abs/1910.07467) 正規化されていない埋め込みベクトルを正規化するために、layer_0 の注目重みを使用します。layer_0 を選択した理由は、LLaMA-3 Transformer アーキテクチャの最初の層を扱っているためです。 ベクトルを正規化するだけで他の操作は実行しないため、ベクトルの次元は変化しません。 09. 主要な要素 (クエリ、キー、値) に注意してください。まず、モデルからクエリ、キー、値、および出力ベクトルを読み込みます。 ベクトル次元からわかるように、ダウンロードしたモデルの重みは、複数のアテンションヘッドを同時に処理できる並列処理または並列学習を使用しているため、単一のアテンションヘッド向けに設計されたものではありません。しかし、これらの行列を分解することで、単一のアテンションヘッドにのみ適用できるようになりました。 ここで、 最初のレイヤーの最初のアテンションヘッドのクエリ重みマトリックスは、次のように取得できます。 各トークンに対応するクエリ ベクトルを計算するには、トークンの埋め込みベクトルにクエリの重みを掛ける必要があります。 クエリ ベクトル自体はプロンプト テキスト内の特定の位置を識別できないため、RoPE テクノロジを使用してこれらのベクトルがその位置を認識できるようにします。 10 RoPEの実装クエリベクトルを 2 つのグループに分割し、各グループの回転角度を調整して区別します。 これは、サイズ[17x64x2]のベクトルを処理することを意味します。基本的には、各プロンプト語に含まれる128単位(長さ128)のクエリ情報を64個のペアに分割します。各クエリペアは、m*θの角度で回転します。ここで、mはシーケンス内のトークンの位置です。 ベクトルの回転演算を実行するには、複素数のドット積を使用します。 セグメンテーションプロセスが完了したら、次にセグメント化されたデータに対して頻度計算を実行します。 これで、各トークンのクエリ部分に対応する複素数値が割り当てられました。次に、これらのクエリを複素数に変換し、シーケンス内のそれぞれの位置に基づいてドット積演算を使用して回転を実行します。 回転したベクトルを取得した後、以前の複素数を実数として再解釈することで、元々ペア形式で表現されていたクエリ ベクトルを復元できます。 次に、回転されたデータをマージして、[17x128]の形状を持つ新しい回転クエリベクトルを取得します。ここで、17はトークンの総数、128はクエリベクトルの次元を表します。 キーベクトルはクエリベクトルと同様に処理されますが、キーベクトルも128次元であることに注意してください。キーベクトルの重みは計算量を最小限に抑えるために4つのアテンションヘッド間で共有されるため、重みの数はクエリベクトルの4分の1になります。クエリベクトルと同様に、キーベクトルも位置情報を組み込むために回転処理が行われ、これによりモデルによる配列位置の理解が向上します。 これで、各トークンの回転されたクエリとキーが取得されました。どちらも [17x128] です。 11 自己注意を実装するクエリ行列とキー行列を乗算することで、各トークンと他のトークンとの関連度に対応するスコア(類似度スコア)のセットが得られます。具体的には、これらのスコアは各トークンのクエリベクトルとキーベクトルの関係を表します。 [17x17] この図形は注目スコア (qk_per_token) を表します。17 という数字はキューテキストに含まれるトークンの数を示します。 クエリキースコアをマスクする必要があります(注:これは、アテンションウェイトを計算する際に、クエリマトリックスとキーマトリックス間の一致スコアまたは関連スコアを指します)。モデルのトレーニング中、モデルが予測に過去の情報のみを使用するようにするため、将来のトークンのクエリキースコアをマスクします。この戦略により、推論中に将来のすべてのトークンのクエリキースコアがゼロに設定されます。 次に、各トークンのクエリベクトルとキーベクトルに対してマスキング操作を実行する必要があります。次に、ソフトマックス関数を適用して、得られたスコアを確率値に変換します。これにより、モデルの語彙から最も可能性の高いトークンまたはトークンシーケンスを選択できるようになり、モデルの予測がより理解しやすくなり、言語生成や分類などのアプリケーションに適したものになります。 自己注意機構は値行列で完結します。同様に、計算リソースを節約するために、値行列の重みは4つの注意ヘッドごとに共有されます。最終的に、値重み行列は[8x128x4096]の形状になります。 クエリ行列やキー行列と同様に、特定の方法を通じて第 1 層と第 1 アテンション ヘッドの値行列を取得できます。 価値行列の重みを用いて各トークンの注目度を計算し、最終的に[17x128]の形状の行列を得ます。ここで、17はプロンプトテキストに含まれるトークンの総数、128は単一トークンの価値ベクトルの次元です。 最終的なアテンション マトリックスを取得するには、次の乗算演算を実行するだけです。 これで、第 1 層と第 1 注意ヘッドの注意値、つまり実際には自己注意値を取得しました。 12 マルチヘッドアテンションの実装上記の計算手順は、ループ処理を通じて第 1 層のすべてのアテンション ヘッドに対して繰り返されます。 これで、第1層の32個のアテンションヘッドすべてに対するQKVアテンション行列が計算されました。次に、これらのアテンションスコアを[17x4096]の大きな行列に統合します。 レイヤー 0 のアテンション (Transformer モデル全体のシーケンスの理解と処理のプロセスの最初のステップである可能性があります) では、最後のステップは、重み行列を使用して積み重ねられた QKV 行列を乗算することです。 これで、注目メカニズムによって処理された埋め込み値が得られ、これらの変更が元のトークン埋め込みに追加されるはずです。 次に、埋め込まれた値の変化を正規化し、それをフィードフォワードニューラルネットワークに送り込んでさらに処理します。 13. SwiGLU活性化関数を実装する先ほど紹介した SwiGLU 活性化関数についてある程度理解できたので、ここで先ほど説明した式を適用します。 SwiGLU: GLUバリアントによるTransformerの改良 (https://kikaben.com/swiglu-2020/) 14. 上記のモデルコンポーネントを統合するすべての準備ができたので、コードをマージしてさらに 31 個のモデル レイヤーを構築する必要があります。 15. 証人モデルがテキスト入力に基づいて出力を生成する仕組みこれで、モデルが次のトークンを予測するための基礎となる最終的な埋め込み表現が得られました。この埋め込みの構造は通常のトークン埋め込みと一致しており、[17x4096]、つまり17個のトークンで構成され、各トークンの埋め込みベクトルの次元は4096です。 次に、取得した埋め込み表現を特定のトークン値に変換し直し、抽象表現からテキスト コンテンツへのデコード プロセスを完了します。 後続のコンテンツを予測する際には、前のトークンの埋め込み表現を基に、最も可能性の高い次のトークン値を推測します。 トークン ID の文字列を読み取り可能なテキストに変換するには、トークン ID を特定の文字または単語にマッピングするデコード プロセスを実行する必要があります。 したがって、入力は「生命、宇宙、そして万物についての究極の問いに対する答えは」であり、モデル出力は「42」となり、これが正解です。 読者の皆様は、様々なプロンプトテキストをぜひ試してみてください。変更が必要なのは、この2行のコードだけです。それ以外は変更ありません。 読んでいただきありがとうございます!このブログを楽しんで、新しいことを学んでいただけたら嬉しいです! ファリード・カーン データサイエンスの修士号を取得し、AIについて執筆しています https://www.linkedin.com/in/fareed-khan-dev/ 終わり 参考文献 [1]https://llama.meta.com/llama3/ [2]https://mistral.ai/ [3]https://github.com/FareedKhan-dev/Building-llama3-from-scratch [4]https://levelup.gitconnected.com/building-a-million-parameter-llm-from-scratch-using-python-f612398f06c2 [5]https://github.com/bzhangGo/rmsnorm/blob/master/rmsnorm_torch.py [6]https://arxiv.org/pdf/2002.05202v1.pdf [7]https://arxiv.org/pdf/2104.09864v4.pdf [8]https://huggingface.co/join?next=%2Fmeta-llama%2FMeta-Llama-3-8B [9]https://huggingface.co/meta-llama/Meta-Llama-3-8B [10]https://huggingface.co/meta-llama/Meta-Llama-3-8B/tree/main/original [11]https://huggingface.co/settings/tokens オリジナルリンク: https://levelup.gitconnected.com/building-llama-3-from-scratch-with-python-e0cf4dbbc306 |
目次01 前提条件の概要02 LLaMA 2とLLaMA 3の違い03 LLaMA 3アーキテクチャの探究3.1 RMSNormを用いた前正規化3.2 SwiGLU活性化関数3.3 回転エンコーディング(RoPE)3.4 バイトペアエンコーディング(BPE)アルゴリズム04 環境設定05. プロジェクトの組織ロジックを明確にする06. 入力データに対して単語分割を実行します。07 各トークンの埋め込みを作成する08. RMSNormを使った正規化09. 主要な要素 (クエリ、キー、値) に注意してください。10 RoPEの実装11 自己注意を実装する12 マルチヘッドアテンションの実装13. SwiGLU活性化関数を実装する14. 上記のモデルコンポーネントを統合する15. 証人モデルがテキスト入力に基づいて出力を生成する仕組み |