ABEJAでデータサイエンティストをしている真鍋です。
弊社は経済産業省と国立研究開発法人新エネルギー・産業技術総合開発機構(NEDO)が実施する、国内における生成AIの開発力強化を目的としたプロジェクト「GENIAC(Generative AI Accelerator Challenge)の1期に続き、2期にも採択され、そこで大規模言語モデルの開発を進めています。 www.abejainc.com
1月の記事で、50B以下で高性能な日本語モデルとして、ABEJA Qwen2.5 32B-Japanese v0.1を公開しました。
今回は、さらにパラメータ数を抑えた10B以下のモデルをベータ版として公開できる段階に到達したので、ABEJA-Qwen2.5-7b-Japanese-v0.1としてHuggingface上に公開しました。
公開したモデルは、Alibaba社が開発したQwen2.5-7B-Instructをベースとしており、ライセンスもベースモデルと同様に、apache2.0として商用利用OKな形で使っていただけます。
このブログでは概要及び性能について簡単にまとめています。

GENIAC2期におけるABEJAの取り組み
GENIAC2期では、小型化(パラメータ数を抑える)しつつも「一部のタスクを高性能に実行できるモデル」を開発しています。
具体的には「50B以下に抑えたモデル(32B)」と「さらに軽量な10B以下のモデル(7B)」の2種類を作っており、それぞれQwen2.5をベースモデルとして採用しています。
ベースモデルに対して継続事前学習及びPost-Trainingで日本語及び一部能力を向上させるモデルを目指しています。また、大型モデルの出力を小型モデルに学習させる、蒸留への挑戦も行なっています。
蒸留については、「出力自体を直接学習する」蒸留と、「大きなモデルの合成データで学習を行う」蒸留がありますが、前者の蒸留を行なっています。小規模言語モデル (SLM) のサーベイ論文 においても、前者の定義がなされており、今回の学習では、特にホワイトボックスの知識蒸留を実施した形になります。
(手前味噌ですが、昨年12月にSLM系の論文読み会を開催 した際に私が上記論文を紹介した際のスライドを抜粋しています。毎月データサイエンス部ではこのようなテック系のイベントを開催しておりますので、ご興味を持っていただけたら、ぜひ参加いただけたらと思っています!(次回は5月2日 (金) です)
今回公開したモデル
今回公開したベータ版モデルは、Qwen2.5 7B-Instructをベースとしたモデルで、蒸留でABEJA Qwen2.5 32B-Japanese v0.1の出力を学習するようにチューニングしています。
32Bの時のように、ベースモデルに対してデータセットを追加で学習させる継続事前学習も行いましたが、結果として蒸留モデルの方が性能がよかったため、そちらを公開しています。
現在事後学習を実施し、さらなる精度向上のために開発メンバー一同奮闘しておりますが、指示追従性能をあげるためにChatVectorを導入して、現時点でも実用性あるものにしています。
32Bでも適用していますが、ChatVectorはモデルマージの手法の一つで、InstructモデルとInstruct前のモデルのパラメータの差分を、学習後のモデルに適用することで指示追従・Chat性能を得られるというものです。 今回でいうと、Qwen社のオリジナルのQwen2.5 7b-InstructとQwen2.5 7bを用いて作りました。
モデルはHuggingfaceにアップロードしており、以下のリンクからご利用いただけます。 huggingface.co
32B同様、この状態でも公開する価値があると判断し、ベータ版としてリリースに至りました。
モデルの評価
今回のモデルは、Japanese-MT-Benchで評価を行っています。
Japanese-MT-Bench
| Model | スコア |
|---|---|
| microsoft-phi-4 | 8.13 |
| google-gemma-3-12b-it | 7.91 |
| Qwen2.5-14B-Instruct | 7.71 |
| ABEJA-Quen2.5-7B-Japanese-v0.1 (今回公開した蒸留モデル) | 7.26 |
| ABEJA-Quen2.5-7B-Japanese (継続事前学習モデル) | 7.21 |
| anthropic.claude-3-haiku | 7.13 |
| CohereForAI-aya-expanse-8b | 6.91 |
| Qwen2.5-7B-Instruct | 6.86 |
| gpt-3.5-turbo | 6.82 |
| microsoft-Phi-3-medium-128k-instruct | 6.38 |
| Meta-Llama-3-8B-Instruct | 6.21 |
評価の補足として、ABEJA-Qwen2.5-7B-Japanese-v0.1については弊社環境にて3回実行した上での平均評価を算出し、評価モデルはnejumi leaderboard3と同様に、GPT-4o-0513を使用しています。 その他モデルについてはnejumi leaderboard3のスコアを引用しています。
スコアまとめ・考察
主に10B前後の有名どころの商用モデルとQwenの元モデルを比較対象にしています。
やはり1つのポイントは、蒸留モデル、継続事前学習モデルともに、GPT-3.5のベンチマークスコアを7B規模で超えられたことかと思っています。
一方で、32Bの継続事前学習においては、元の32Bモデルだけでなく、72Bモデルのスコアも上回っていたのですが、上記のように、7Bで14Bを上回る、という結果にはなりませんでした。
とはいえ、phi-3であれば10B以上でもスコアを上回っていることは、個人的には朗報でした。(phi-4は強すぎますね…)
また、元の7Bモデルだけでなく、10B以下の同等のモデルについてもスコアで上回っており、10B以下ではトップクラスの性能になったのでは無いかと思われます。
蒸留モデルと継続事前学習モデルについては、そこまで大きな差ではないものの、同じパラメータ数であることも考慮し、より性能の高い蒸留モデルを公開する運びとなりました。
出力の例として、スコアが伸びなかった例、7Bでもスコアが良かった例を示していきます。
スコアが伸びなかった例
Question: 8, Turn: 1
Question: バイナリツリーがすべての頂点が0個または2個の子を持つ場合、それは完全であると言います。B_nをn個の頂点を持つ完全なバイナリツリーの数とします。B_nを見つける関数を実装してください。
Answer: "もちろんです、その問題を解決するための関数をPythonで実装してみましょう。完全なバイナリツリーの性質を活用して、再帰的に数えることができます。完全なバイナリツリーの頂点数
nが2のべき乗(つまりn = 2^kでkは整数)の場合は、その数は1つだけです。それ以外の場合は、部分木の数を再帰的に計算します。以下にその関数を実装します:
def B(n): if n == 1: return 1 # 1つの頂点しかない場合、それは1つの完全なバイナリツリー elif n < 2: return 0 # 0または1未満の頂点を持つ完全なバイナリツリーは存在しない else: # nが2のべき乗かどうかを確認 if (n & (n - 1)) == 0: return 1 # 2のべき乗の場合は1つだけ else: # それ以外の場合は、部分木の数を再帰的に計算 return B(n // 2) * B(n - n // 2 - 1) + B(n // 2 - 1) * B(n - n // 2) # 例の使用方法 print(B(4)) # 2 print(B(5)) # 2 print(B(6)) # 3 print(B(7)) # 3 print(B(8)) # 1この関数は、完全なバイナリツリーの数を計算します。ただし、
B(n)の計算は複雑さが高いため、大きなnの値に対しては計算が遅くなる可能性があります。"
これはmtbench評価を3回試行した内の1回の結果ですが、スコアとしては、3回とも2で、伸び悩む結果となりました。
評価としても、「特に、2のべき乗に関する部分や再帰的な計算方法が間違っている」ことや、「計算の効率性も低い」こと、「完全なバイナリツリーの数は2のべき乗とは関係ない」ことなどが記されています。
この問題は32B-Japaneseで評価した際は動的計画法を用いるアプローチを回答しており、一般性や効率性には欠けるものの、正しい回答を導けているとして、6点を出せていました。下記は、nejumi LLMリーダーボード3における、ABEJA-Qwen2.5-32B-Japanese-v0.1での出力結果です。
この問題を解くためには、まず完全なバイナリツリーの性質を理解し、その後再帰的な関数を用いてB_nを計算する方法を実装します。
完全なバイナリツリーの性質
完全なバイナリツリーは、各ノードが0個または2個の子を持つという性質を持ちます。したがって、n個の頂点を持つ完全なバイナリツリーを考えるとき、その根ノードは2つの部分木を持ち、それぞれが完全なバイナリツリーである必要があります。これらの部分木の頂点数をそれぞれmとn-m-1とすると、mは0からn-1までの範囲を取ります。
再帰関数の定義
B_nを計算するための再帰関数を定義します。B_nは、n個の頂点を持つ完全なバイナリツリーの数を表します。再帰関数の基本ケースと再帰ケースを以下のように定義します。
- 基本ケース: n = 0のとき、空のツリーが1つだけ存在するため、B_0 = 1です。
- 再帰ケース: n > 0のとき、B_nは以下の式で計算できます。 [ B_n = \sum{m=0}^{n-1} B_m \times B{n-m-1} ] ここで、mは左の部分木の頂点数を表し、n-m-1は右の部分木の頂点数を表します。
Pythonでの実装
以下は、B_nを計算するPythonの関数の実装例です。
def B(n, memo={}): if n in memo: return memo[n] if n == 0: return 1 if n == 1: return 0 # 1つの頂点だけでは完全なバイナリツリーは存在しない result = 0 for m in range(n): result += B(m, memo) * B(n - m - 1, memo) memo[n] = result return result # 例: 5個の頂点を持つ完全なバイナリツリーの数を計算 print(B(5)) # 出力: 2説明
- memo: メモ化用の辞書を使用して、再帰の過程で既に計算された値を保存し、計算時間を短縮します。
- 基本ケース: n = 0のとき、空のツリーが1つだけ存在するため、B_0 = 1です。n = 1のとき、1つの頂点だけでは完全なバイナリツリーは存在しないため、B_1 = 0です。
- 再帰ケース: n > 1のとき、左の部分木の頂点数mを0からn-1まで変化させ、それぞれの部分木の組み合わせの数を計算し、その積の和を計算します。
この関数を使用することで、任意のnに対してB_nを効率的に計算できます。
7Bでもスコアが伸びた例
Question: 35, Turn: 1
Question: バスには最初に何人かが乗っていました。最初のバス停で、乗車人数の半分が下車し、さらに4人が乗り込みました。次のバス停では、6人が下車し、さらに8人が乗り込みました。3つ目の停留所へ向かう乗車人数の合計が25人だったとしたら、バスには最初に何人が乗っていたのでしょうか?
Answer: "この問題を段階的に考えてみましょう。
最初の状況を ( x ) 人とします。つまり、最初にバスには ( x ) 人が乗っていました。
最初のバス停で、乗車人数の半分が下車します。つまり、( \frac{x}{2} ) 人が下車し、残りは ( \frac{x}{2} ) 人になります。さらに4人が乗車します。したがって、この停留所を通過した時点で、バスには ( \frac{x}{2} + 4 ) 人が乗っています。
次のバス停では、6人が下車し、さらに8人が乗車します。したがって、この停留所を通過した時点で、バスには ( \left( \frac{x}{2} + 4 \right) - 6 + 8 ) 人が乗っています。これを整理すると、( \frac{x}{2} + 6 ) 人が乗ることになります。
3つ目の停留所では、バスには25人が乗っていることが分かっています。つまり、 [ \frac{x}{2} + 6 = 25 ]
この方程式を解くと: [ \frac{x}{2} = 19 ] [ x = 38 ]
したがって、バスには最初に38人が乗っていたということになります。"
これは、3回とも10点満点を叩き出した質問の1つでした。一方で、nejumi LLMリーダーボード3における、Qwen2.5-7B-Instructの出力を見てみると、間違ってしまっており、スコアは-1となっていました。
他の数学の問題は正しく解けているものも多いので、あくまでも一例ではありますが、コードや数学のデータセットも学習させた32B継続事前学習モデルを蒸留した結果として、数学の性能は向上しており、蒸留の効果が一定あったと言える例なのではないでしょうか。
この問題を解くために、逆から計算していきましょう。
3つ目の停留所で乗車人数の合計が25人になったとします。
- 3つ目の停留所で8人が乗り込み、6人が下車した後、25人が乗車しています。
- つまり、3つ目の停留所で乗車する直前は、25 - 8 + 6 = 23人でした。
2つ目の停留所で、乗車人数の半分が下車し、さらに4人が乗り込みました。この状況から逆算します。
- 2つ目の停留所で下車した人数は23 × 2 = 46人で、その前に4人が乗り込んだので、2つ目の停留所で乗車する直前は46 - 4 = 42人でした。
1つ目の停留所で、最初の乗車人数の半分が下車し、さらに4人が乗り込みました。この状況から逆算します。
- 1つ目の停留所で下車した人数は42 × 2 = 84人で、その前に4人が乗り込んだので、最初の乗車人数は84 - 4 = 80人でした。
したがって、バスには最初に80人が乗っていたはずです。
なお、今回はPost-TrainingではなくChatVectorによる簡易的な指示追従性能実現のため、フォーマット通り答えられているかのスコア影響の大きいJaster系のベンチマークについては対象外としています。
学習の取り組み概要
今回公開したモデルでは、上記のとおり、1月に公開した32Bの継続事前学習モデルを教師モデルとして、蒸留を行なっています。また、今回公開するに至らなかったものの継続事前学習モデルにおいても、32Bの学習設定や、データセットを踏襲して、継続事前学習を行いました。詳細は、1月に公開した32Bの記事 をご参照いただければ幸いです。
今回も継続事前学習同様、NVIDIAのNeMoフレームワークを使用していますが、蒸留においては、NeMoだけでなく、NeMo-Alignerも使用しています。苦労したポイントとして、蒸留における並列処理周りで一部カスタム化している箇所があるのですが、詳細は別記事に譲ります。
AzureのCycleCloud上で、SlurmとSingularityによるクラスター環境を構築しています。 計算リソースとしては、H100x8を1ノードとしてを8ノード(合計64ノード)で学習をしました。
現時点での課題
32B同様に、稀に出力に中国語が混ざる問題が見受けられており、事後学習の中での解消を目指しています。
まとめ
今回のモデルはまだ開発途中ではありますが、実用性の高い精度が得られていると感じています。ぜひ使ってみて、フィードバックをいただけると嬉しいです。 今後は、よりタスク特化で性能を向上させることに加え、先述の中国語混ざり問題などの改善にも取り組んでいきます。 また、事後学習モデルについても、後日公開できたらと考えています。
本モデルは、経済産業省とNEDOが実施する、国内の生成AIの開発力強化を目的としたプロジェクト「GENIAC(Generative AI Accelerator Challenge)」で開発されたものです。
We Are Hiring!
ABEJAは、テクノロジーの社会実装に取り組んでいます。 技術はもちろん、技術をどのようにして社会やビジネスに組み込んでいくかを考えるのが好きな方は、下記採用ページからエントリーください! (新卒の方やインターンシップのエントリーもお待ちしております!)