🦙

ELYZAが公開した日本語LLM「ELYZA-japanese-Llama-2-7b」についての解説 : (1) 事前学習編

2023/09/12に公開9

はじめに

こんにちは。ELYZAの研究開発チームの佐々木 (@hikomimo)、中村 (@tyo_yo_)、堀江 (@eemon18)、平川 (@h__must__) です。

先日弊社株式会社ELYZAでは以下のようなリリースをさせていただきました。

上記のリリースには、Metaの「Llama 2」をベースとした以下のモデルが含まれます。

本記事では上記のうち日本語追加事前学習済みモデルに着目し、そのモチベーションや学習手順の詳細、また現時点で成功していない実験についても説明します。

Llama 2をベースに日本語の追加事前学習を実施したモチベーション

まず我々がフルスクラッチで日本語モデルを学習せずに、英語を主としたモデルから日本語を追加事前学習するに至った最大の理由は、日本語と比べたときの英語テキストデータの圧倒的な多さです。
近年英語を主としたモデルに関してはオープンなものでさえ非常に高い性能に至っていますが、その背景には超大規模な英語テキストデータの存在が挙げられると考えています。
LIMA: Less Is More for Alignment (Zhou+, arXiv2023)などでも言及されているように、LLMの知識や指示追従能力の源泉が事前学習済みモデルにあるとすれば、そのような超大規模なテキストデータの存在が欠かせないのではないかと思われます。
かたや日本語テキストデータはそれと比べると極めて少ないと言わざるを得ないため、日本語テキストデータのみでフルスクラッチで日本語モデルを学習しても、英語を主としたモデルの華々しい性能には及ばないのではないかと考えました。
そこで「巨人 (英語) の肩の上に立つ」という発想のもと、我々は英語を主としたモデルからの追加事前学習という選択をしました。

次に、我々がLlama 2をベースとして今回の取り組みを実施した理由は以下です。

  • 英語を主として2兆 (2Trillion) トークンという、超大規模な事前学習がなされている点
    • 日本語単体ではこのようなデータ量を準備することは恐らくほぼ不可能かと思われます。Llama 2はこのように超大規模なデータで事前学習されていることから、他の公開モデルに比べて優れた言語能力や常識的知識を備えていることを期待しました。
    • またこれにより、フルスクラッチで日本語を事前学習するのに比べてコストを抑えられるという期待もありました。
  • 近年の研究開発で培われた要素技術が動員されている点
    • SFT (Supervised Fine-Tuning) が実施されていることはもちろん、RLHF (Reinforcement Learning with Human Feedback)も施されています。
    • 更にSwiGLURMSNormRoPEといった近年提案された要素技術が動員されている点に着目しました。
    • 加えて、Llama 2で新たに提案されたGAtt (Ghost Attention) により、対話を繰り返す中での指示の忘却にも頑健になっていることが期待されます。
    • 以上を踏まえ、現状の選択肢の中では最善のベースモデルであると捉えました。
  • 前身となるLLaMAから多くの派生モデルが生まれた点
    • Llama 2の前身のLLaMAは商用利用不可なライセンスという前提がありつつも、Alpacaをはじめとした多くの派生モデルが研究開発されるといったように、世界的に多くの注目を浴びました。
    • Llama 2ではLLAMA 2 COMMUNITY LICENSEに記載の通りの条件付きとはいえ商用利用可能なライセンスとなったこともあり、これをベースとして派生モデルを構築する動きはLLaMAよりもさらに活発になることが期待されます。そのような研究開発コミュニティの盛り上がりにも期待を込め、我々もLlama 2に着目しました。
    • 現に、Hugging Face上でも多くのLlama 2からの派生モデルが登場しております。

追加事前学習の詳細

概要

今回の取り組みではLlama 2のうち、SFTやRLHFがすでに実施されたmeta-llama/Llama-2-7b-chat-hfをベースモデルとして採用しました。
この理由としては上記で述べたモチベーションの通り、英語を主として身につけられた指示追従能力や、出力の安全性を極力引き継ぎたかったためです。

ここで、元となったLlama 2の事前学習には日本語が0.1%程度 (20億トークン) しか含まれないという性質がありました。
このような少量の日本語のみで学習されていることがLlama 2で日本語を扱い難い根本原因だと捉え、我々は追加で日本語データを180億トークン追加で事前学習し、合計で200億トークンの日本語を学習することを目標としました。
事前学習に用いたのはOSCARWikipedia、またその他クロールデータといった一般的な日本語のテキストデータです。
なお、重複する文字列の削除やNGワードでのフィルタリングといった前処理は別途実施しました。

実験環境および学習時の詳細な設定

実験はほぼ全てABCIを利用しました。
その中でも特に、GPUとしてA100を搭載したノード (rt_AF) は実験期間中 (2023年8月) に空きがなかったため、V100が4枚載ったインスタンス (rt_F) の利用がほとんどとなっておりました。
実験用フレームワークとしてはDeepSpeedZeRO Stage3を使い、先述の日本語テキストデータ (180億トークン) を1エポック学習するためにかかった時間はrt_F × 64ノードで約1日〜1日半程度でした。
Llama 2 (7B) をベースとしたことで、このように直近利用し難いA100でなく比較的余裕のあるV100であっても比較的短時間で実験サイクルを回せるという点は、今回の取り組みを高速に進められた理由の1つです。

ELYZA-japanese-Llama2-7b および ELYZA-japanese-Llama2-7b-fast それぞれの学習時のlossは以下の図のとおりです。
語彙を追加したことによりfastモデルのlossが高めですが、それぞれ安定してlossが減少していることが伺えます。

学習時のハイパーパラメータは基本的にLlama 2論文のPretrainingに関する章 (2.2 Training Details) を参考にしており、以下のような設定となっています。

optim: "adamw_torch"
adam_beta1: 0.9
adam_beta2: 0.95
adam_epsilon: 1.0e-5
weight_decay: 0.1
# lr schedulerは使用していません
learning_rate: 3.0e-5

日本語語彙の追加 (ELYZA-japanese-Llama-2-7b-fast、ないし...-fast-instructのみ該当)

今回ベースとしたmeta-llama/Llama-2-7b-chat-hfには32,000トークンが含まれますが、それらの語彙は日本語に最適化されていないため、日本語を取り扱うためのコストは英語に比べて高くなっております。
具体的には「こんにちは」は こ/ん/に/ち/は、「東京都」は 東/京/都 のように1文字あたり1トークンに対応していたり、「肉」のような元のLlama 2事前学習時に低頻度であったと思しき漢字はバイト単位で3トークンで表現されていたり、といった形です。 (いっぽう「Hello」や「World」といった英単語はそれぞれ1トークンで表現されています)

そういった課題を解決するため、我々はLlama 2のTokenizerへの日本語語彙追加を実施しました。概観としては以下のようになっております。

具体的な手順としては以下のとおりです。

  1. 元のLlama 2のTokenizerとは独立に、別途日本語テキストのみで学習したBPE Tokenizerを学習します。ここでの語彙数としては15,000件としました。これにより、日本語を文字・バイト単位に細かく区切りがちなLlama 2のTokenizerと、出現頻度の多い日本語は単語単位などで適切に切れるTokenizerが得られます。
  2. 上記の2つのTokenizerはそれぞれBPE Tokenizerとなっているため、tokenizersライブラリの実装上、それぞれの vocab を組み合わせることで、それぞれのメリットを活かした語彙数45,043件のTokenizerが得られます。
  3. Tokenizerを組み合わせるのは上記で完了していますが、この時点ではモデルの embed_tokenslm_head は元の語彙数 (32,000件) に対応したままとなっているため、追加語彙への対応が必要となります。そこで我々は、 embed_tokenslm_head の元のトークンに該当するベクトル (例: 東/京/都 それぞれのベクトル) の平均を、追加したトークンに該当するベクトル (例: 東京都 のベクトル) の初期値として利用するという方針としました。ランダムな初期化での予備実験もしましたがあまり大きな変化は見られず、オリジナルのLlama 2の性能を継承することを期待してこのような方針で実験しました。また別の観点として、ランダムな初期化に比べてlossの下がり方が顕著であることも確認しています。
  4. 以上の手順を実施後に、先述の追加事前学習を行います。ここで手順3に記述したように、追加したトークンが「ある程度良い初期値を持った状態」から学習されることを期待しています。
なお、2つのTokenizerを組み合わせる際の具体的なソースコードはこちらの通りです。
from copy import deepcopy
import json

# 元となるtokenizer.jsonを読み込む
with open("/path/to/original/tokenizer.json") as f:
    original = json.load(f)

# 追加したいtokenizer.jsonを読み込む
with open("/path/to/append/tokenizer.json") as f:
    append = json.load(f)


def merge_tokenizer(data1: dict, data2: dict):
    vocab1 = data1["model"]["vocab"]
    vocab2 = data2["model"]["vocab"]
    
    merges1 = data1["model"]["merges"]
    merges2 = data2["model"]["merges"]
    
    # 出力用の変数を定義
    vocab_out = deepcopy(vocab1)
    data_out = deepcopy(data1)

    # merge前の最大idxを取得
    idx = max(vocab_out.values())
    
    # vocab2のうちvocab1にないものを、idxをインクリメントしつつvocab_outに追加
    for token in vocab2.keys():
        if token not in vocab1:
            idx += 1
            vocab_out[token] = idx
        
    # vocab_out中の全てのtokenについて、それをある位置で区切ったときの左右それぞれの要素がいずれもvocab_outに含まれる場合、merges_outに追加
    # 参考: https://github.com/huggingface/transformers/pull/17199
    merges_out = []
    for candidate, piece_id in vocab_out.items():
        for i in range(1, len(candidate)):
            left, right = candidate[:i], candidate[i:]
    
            left_id = vocab_out.get(left, None)
            right_id = vocab_out.get(right, None)
    
            if left_id is not None and right_id is not None:
                merges_out += [f"{left} {right}"]

    data_out["model"]["vocab"] = vocab_out
    data_out["model"]["merges"] = merges_out

    with open("/path/to/merged/tokenizer.json", "w") as f:
        json.dump(data_out, f, ensure_ascii=False, indent=2)

# 上で定義した関数により、元のtokenizerと追加したいtokenizerをmerge
merge_tokenizer(data1=original, data2=append)

上記のような手順を踏むことで、日本語のテキストを扱うために必要なトークン量が削減され、推論速度としてはおよそ1.8倍に大きく向上しました。
また副次的な効果として学習も効率化し、元のTokenizerで300億トークンに相当するテキストが160億トークンで表現されました。
そのため結果として、 ELYZA-japanese-Llama-2-7b-fastではELYZA-japanese-Llama-2-7bが追加事前学習時に見ている180億トークンに比べて少ない160億トークンで学習しつつも、それに比べて約1.66倍のテキスト量を見たこととなっています。

今回の取り組みでは試行錯誤の結果上記のような手順で上手く日本語語彙追加することに成功しましたが、その過程では学習が上手く行かなかったり性能劣化したりと、失敗した実験もいくつかあり一筋縄ではいかない施策でした。また、より良い手法も引き続き検討する予定です。

継続学習 (Continual Learning) の検証

本項目で検証した内容は性能の都合上、公開したモデルでは未反映となっております

今回我々はLlama 2をベースとしましたが、日本語のみで追加事前学習をしてしまうと元々のLlama 2が持っている指示追従能力などを失ってしまう懸念 (破滅的忘却) があったため、継続学習 (Continual Learning) の検証も実施しました。

検証にあたっての大まかな方針としては、Fine-tuned Language Models are Continual Learners (Scialom+, EMNLP2022)を参考としました。
こちらの論文では、あるモデルに新しいタスクを追加学習する際、過去に学習していたタスクをそれぞれ一定量 (論文中では1%) あわせて学習することにより、それらのタスクを忘れずに新しいタスクを学習できたと報告されております。

ただしLlama 2がSFTで利用した具体的なデータは公開されていなかったこともあり、我々は以下のデータセットを用いて継続学習しました。

今回は上記のようにタスク個別というより事前学習・対訳も含む継続学習の実施としたため、上記論文の設定とはやや異なりますが 追加事前学習:継続学習=97.5:2.5 という比率で学習しました。
しかしながら結果として、継続学習をしない場合に比べて指示学習後の性能が悪化していたため、公開したモデルでは継続学習は未反映となっております。

この理由のひとつとして考えられるのは、継続学習に英語を含めたことで、本来日本語を出力すべき場面で英語を出力するような傾向が残ってしまった点かと思われます。
例えば以下の例では、日本語で指示をされているにも関わらず出力中に potentialityWhite Collar work field といった英語を含めてしまっています。

(指示)

小学生に向けて説明してください。

大規模言語AIは、膨大な言語データを大量演算することで、人間並もしくは人間を超える精度で言語を扱うことができる技術で、2018年10月に登場してから各国にて研究が盛んに行われており、急速に技術が進歩しています。この大規模言語AIは、量と質を兼ね備えたデータセットと最新技術を扱える技術力・ノウハウを掛け合わせることで精度が上がり、主にホワイトカラー領域において大幅な業務効率化を創り出すポテンシャルがあると世界中で注目を集めています。しかし、他のAI技術と同様に、海外と比較して日本では大規模言語AIの理解が遅れており、結果として実用化も遅れています。ELYZAは、日本における大規模言語AIの活用を海外に劣らぬよう少しでも前に進めることができればという想いで、日本語における大規模言語AIを独自開発し、主に大企業との社会実装もいち早く行ってまいりました。

(出力)

大規模言語AIとは、大量の文章や音声データを高速に処理することで、人間以上の能力で言語を理解や生成する技術です。2018年に発表されて以来、多くの国で研究が進むほどそのpotentialityが認識されています。特に、事務作業等を支援するWhite Collar work fieldにおいて、大幅なproductivity improvementを可能にすると評価されています。一方で、日本では比較的遅れている状態です。ELYZAは、日本でのbig language AIの活用をworld-class levelに引き上げるべく、独自開発した日本語 big language AIとsocial implementationをearly stageに行っています。

継続学習については、英語の能力をどの程度保っているかといった検証も含め、今後引き続き実施予定です。

おわりに

本記事ではELYZA-japanese-Llama-2-7bシリーズの事前学習について解説をさせていただきました。
現時点ではパラメータ数7B (70億)までのモデルについて公開しておりますが、弊社ではすでにパラメータ数13B (130億)、70B (700億)パラメータのモデルの開発にも着手しております。
今後の記事では、モデルとあわせて公開したELYZA-tasks-100という評価用データセットについての解説などを予定しております。

株式会社ELYZAは、「未踏の領域で、あたりまえを創る」という理念のもと、日本語の大規模言語モデルに焦点を当て、企業との共同研究やクラウドサービスの開発を行なっております。
先端技術の研究開発とコンサルティングによって、企業成長に貢献する形で言語生成AIの導入実装を推進します。

ここまでお読みいただき、ありがとうございました。
ELYZAではAIエンジニア、AIコンサルタントなど、様々な職種で一緒に事業を前に進めてくれる仲間を募集しています。
少しでも興味を持っていただけた方は、ぜひカジュアル面談にお越しください。
日本語Llama 2をはじめとするLLM開発について話しませんか?
https://chillout.elyza.ai/elyza-japanese-llama2-7b

ELYZAの募集一覧はこちらを御覧ください。
https://open.talentio.com/r/1/c/elyza/homes/2507

株式会社 ELYZA

Discussion

Shohei TanakaShohei Tanaka

とても参考になる記事をご執筆いただきありがとうございます。一点質問があるのですが、バッチサイズについてはどのような設定になっているのでしょうか?

Akira Sasaki (hikomimo)Akira Sasaki (hikomimo)

コメントありがとうございます!
今回の追加事前学習では、1GPUあたりのバッチサイズは16としております。
そのうえでV100が4枚載ったインスタンスを64ノード利用しているため、合計のバッチサイズは16×4×64=4,096となっておりました。

Shohei TanakaShohei Tanaka

ご返信ありがとうございます!
バッチサイズはかなり大きく設定してあるのですね、参考になります。

abeqabeq

有益な記事のご提供をありがとうございます。
open-interpreterのllamaモデルに差し替えて使うことはできますか?

Akira Sasaki (hikomimo)Akira Sasaki (hikomimo)

現時点では未検証ですが、Open InterpreterのREADMEによるとHugging Face上のモデルを指定可能とはなっているので、性能がどうなるかは不明ですが試す余地はありそうですね…!

abeqabeq

返信ありがとうございます。
ビジネスにつなげるための試行錯誤の段階ではできるだけコストは抑えたい気持ちがあるので、普通に従量制課金のOpenAIのkeyを要求する仕組みばかりの中でOpen IntepreterとLlamaモデルはかなり感動しました。でも現状のLlamaモデルだと期待する反応が得られなかったので、この向上が期待できる選択肢は試してみたいと思います。特に日本語ネイティブとしては日本語が得意なモデルを使うことでより良い結果が得られるのでないかと思います。
あとOpen IntepreterはWindows上でも比較的簡単に動かすことができたので、これに対応することで評価する人も多くなることが期待できるのではないかと思います。
自分のような作るところは手が出せないけど、使うことができれば、使ってみてまたいろいろな使い途が思いつく、みたいな評価者が増えることは全体ではいいことだと思うので、よろしくお願いします。

sitosito

とても興味深い記事ありがとうございます。
現在Elyza-japanese-Llama2-7b-fastモデルを独自ドメインのデータでQLoRAによる追加事前学習を行い触っていますが、とても精度の良いモデルだと驚いております。

QLoRAでの追加事前学習実施後のモデルの推論速度が大幅に低下してしまったという問題もあり、本記事で紹介されていたTokenizerのVocab拡張に取り組んでみたいと思い、「日本語語彙の追加 (ELYZA-japanese-Llama-2-7b-fast、ないし...-fast-instructのみ該当)」のソースコードを参考に独自ドメインのTokenizerの学習及びElyza-japanese-Llama2-7b-fastモデルのTokenizerとの組み合わせまでは実施しました。vocab_sizeは68622となっております。

このvocabを拡張したTokenizerを利用してQLoRAによる学習を実行しようとするとcudaにてエラーが発生してしまい、うまく学習を回すことができません。

../aten/src/ATen/native/cuda/Indexing.cu:1146: indexSelectLargeIndex: block: [219,0,0], thread: [63,0,0] Assertion `srcIndex < srcSelectDimSize` failed.

Tokenizerを組み合わせるのは上記で完了していますが、この時点ではモデルの embed_tokens や lm_head は元の語彙数 (32,000件) に対応したままとなっているため、追加語彙への対応が必要となります。

Tokenizerの語彙を増やした場合のモデルに対する対応の手法を、可能な範囲でお教えいただけないでしょうか。

Akira Sasaki (hikomimo)Akira Sasaki (hikomimo)

こちらのエラー自体の原因は恐らくモデル側をtokenizerに合わせてresizeしていないためかと思われますので、以下をモデルに適用することで直りそうに見受けられます。
https://huggingface.co/docs/transformers/main_classes/model#transformers.PreTrainedModel.resize_token_embeddings

また、追加語彙のembeddingの初期値は以下のようなコードで設定しておりますので参考にしていただければと思います…!
https://huggingface.co/elyza/ELYZA-japanese-Llama-2-7b-fast-instruct/discussions/2#650bb5231f2949b99e62e1e5

sitosito

お返事ありがとうございます
モデルのリサイズにて学習実行できました!ありがとうございます!
追加語彙のembedding初期値についてもコードのご提示ありがとうございます。
こちらもご参考にさせていただきます!!