Blog

2025.05.02

Research

大規模言語モデルの次期バージョン PLaMo 2 におけるコード性能向上の試みについて

Shogo Murai

背景

Preferred Networks(以下PFN)とグループ会社のPreferred Elements(以下PFE)では2024年10月からGENIAC 第2期を開始し、その中で高性能かつ軽量なLLMの開発を行っています。以前の記事でも書いたとおり、PLaMo 2 では以前のモデル (PLaMo 100B など) に比べてコーディングのベンチマークで高い性能を達成することができました。今回の記事では、PLaMo 2 でコーディングの性能を上げるために行った試みについて紹介します。

上記にあるように、この開発は経済産業省及び国立研究開発法人新エネルギー・産業技術総合開発機構(NEDO)が実施する、国内の生成AIの開発力を強化するためのプロジェクト「GENIAC(Generative AI Accelerator Challenge)」の支援を受けて実施しました。

手法

主に次のような方法をとりました。

  • 既存コードデータの使用(既存データセットの収集、コードデータを含むデータセットからのフィルタリング)
  • LLM による合成データの生成

データ収集

[1] などで行われているように、インターネット上のデータからコードに関するものを抽出することでデータセットを生成しました。ここでは、プログラムのコードを含むデータをその解説テキストとともに取り込むことで、LLM にコードを自然言語の説明付きで教え込むことを狙っています。

PLaMo 100B の日本語データセット と同様に、CommonCrawl のデータを前処理することでコードに関連の深いと考えられるデータを抽出しています。今回は、次のような方法を取りました。

  1. フィルタリングによる非関連データの除去: CommonCrawl データに含まれるすべての HTML をパースしてテキストを抽出するのはコストがかかるため、簡単なフィルタを使ってコードに関連する可能性が低いと思われるデータを除去します。具体的には、コードデータを示すのに良く使われる <pre> <code> タグを含まないようなデータを除去しました。
  2. フィルタを通過したデータをテキスト (Markdown) に変換
  3. fasttext モデルによりデータがコードデータかどうかを分類: このモデルは、予め LLM によりラベルを付けたデータにより学習しました。

データ投入方法の工夫

コードデータを学習に含める際に、データをそのままの形で学習に使うのではなく、同じような機能を持つコードデータを明示的に並べることで性能が上がる場合があるということを発見しました。

例えば、以下のコードは同じ機能の関数を実装しています。

def compute_square_sum(n):
    ans = 0
    for i in range(1, n + 1):
        ans += i
    return ans
def compute_square_sum(n: int) -> int:
    return sum(i for i in range(1, n + 1))

こういったコードデータから、次のようなデータを作ることができます。

All of the source code shown below will have the same behavior.

def compute_square_sum(n):
    ans = 0
    for i in range(1, n + 1):
        ans += i
    return ans

def compute_square_sum(n: int) -> int:
    return sum(i for i in range(1, n + 1))

このような集約データを使うことにより、LLM に与えられたコード群が同様の意味を持つということを理解させることができ、学習時にそれぞれのコードを別々に与えるよりも良い結果を得ることができたのではないかと考えています。

(なおここで示したコードの例は、今回の記事を書くにあたって手法の概念を示すために用意したものです。)

合成データの生成

複数の論文 [2][3] において、LLM により合成したデータを学習に使用することが有効であることが示されています。
我々もこれらの論文の手法に倣い、オープンソースの LLM を使用して学習用データを生成することにしました。

既存のコードデータに基づいたデータ生成

既存のコードデータを LLM により加工することでデータ生成を行いました。具体的には、LLM によってコードデータにコメントを付けたり、コードの意味をなるべく保ちつつ LLM によって言い換えたデータを生成させたりしました。

例えば、次のような Python コードを考えます。

import argparse

parser = argparse.ArgumentParser()
parser.add_argument("input", type=str)
parser.add_argument("--first-n-lines", type=int)
args = parser.parse_args()

with open(args.input, "r") as f:
    lines = []
    for line in f.readlines():
        lines.append(line.rstrip())

if args.first_n_lines:
    lines = lines[:args.first_n_lines]

lines.sort()
print("\n".join(lines))

このコードを LLM に与えることで、次のような説明文が得られます。

This Python script reads a file specified by the user, optionally limits the number of lines read, sorts those lines, and prints them. It uses the `argparse` module to handle command-line arguments for the input file and the number of lines to consider. The script reads the file, strips newline characters from each line, optionally truncates the list to the specified number of lines, sorts the remaining lines, and prints them joined by newline characters.

この説明文から再度コードを生成させることで、次のようなコードが得られます。このようにして、より理解しやすく学習に有用なコードデータを生成できると考えられます。

import argparse

def main():
    # Set up argument parser
    parser = argparse.ArgumentParser(description='Read a file, optionally limit the number of lines, sort them, and print.')
    parser.add_argument('file', type=str, help='The input file to read')
    parser.add_argument('-n', '--lines', type=int, default=None, help='The number of lines to consider (optional)')

    # Parse command-line arguments
    args = parser.parse_args()

    try:
        # Read the file
        with open(args.file, 'r', encoding='utf-8') as file:
            lines = file.readlines()

        # Strip newline characters
        lines = [line.rstrip('\n') for line in lines]

        # Optionally limit the number of lines
        if args.lines is not None:
            lines = lines[:args.lines]

        # Sort the lines
        lines.sort()

        # Print the sorted lines joined by newline characters
        print('\n'.join(lines))

    except FileNotFoundError:
        print(f"Error: The file '{args.file}' was not found.")
    except Exception as e:
        print(f"An error occurred: {e}")

if __name__ == '__main__':
    main()

(なおここで示したコードの例は、今回の記事を書くにあたって手法の概念を示すために用意したものです。)

主にプロンプトに基づくデータ生成

[2] では、そのタイトルにもあるように、教科書的なデータを生成させて学習に使用することで LLM の性能を向上させています。我々も、同様に品質の良いデータを大量に生成することを試みました。方法としては主に [3] の手法に倣いました。

  • [3] の手法では、プロンプトの一部にコードの抜粋を含めることで、多様なデータを生成できるようにしています。
  • [3] では主に問題・解答がペアとなるデータ (instruction data) を作成していますが、教科書的データや通常のソフトウェアのようなコードデータも生成させるために、プロンプトを変更した生成も実施しました。
  • 加えて、ランダムに選んだ「トピック」となる単語をプロンプトに含めることで、より多様な設定でのデータが生成できるようにしました。
  • 他にも、生成させたデータをもとにより複雑なデータを作らせる、といった試みも行いました。

また [4] では、LLM を使ってあるテーマにまつわる重要なトピック(例えば「Python の学習」というテーマなら、構文やライブラリといったトピックが考えられます)を取り出すという方法が提案されています。我々も、これに倣ったデータ生成を一部で取り入れました。

コードの実行によるデータのフィルタリング

コードはインタプリタで実行することで正確な実行結果を確認することができます。LLM によって生成されたデータはしばしば誤りを含みますが、生成されたデータをインタプリタを使って評価することで、誤ったデータを減らすことができると考えられます。例えば [5] では、モデルにテストコードも生成させ、そのテストが通るようなコードデータを使ってそのモデルに fine tuning を施すという方法で性能を向上させています。我々も同様に、コードとテストを同時に生成させ、テストが通るデータのみをフィルタリングするということをデータの一部で行いました。

データ生成のためのシステム

GENIAC の開発に割り当てられている計算資源 (GPU) の量は期間中を通してほぼ一定ですが、実際に必要となる GPU の量には変動があります。例えば、データの品質の検証のために多数の予備実験(学習)を回している際には必要な GPU 数は多くなりますが、そうでない場合には GPU 資源に空きが出る場合もあります。一方で、計算資源の無駄を防ぐため、実際の GPU の使用率は常に高く保つことが望ましいです。

そこで合成コードデータの生成では、空いている GPU の分だけ LLM の推論サーバーを起動し、これらサーバーが対応できる分だけのデータ生成要求を行うことで、重要な実験を妨げず、GPU の使用率を高く保ちながら合成データを増やせるようにしました。
また、プロンプトを LLM に与えてデータを生成する場合、プロンプトを事前に生成してからバッチ処理で LLM にプロンプトを与えて出力を得る、という方法をとる場合が多いです。一方で、今回は GPU の空き状況に応じてデータ生成の速度が変化してくるため、実際に使える GPU が多すぎる場合にはデータを生成するためのプロンプトが枯渇してしまう、あるいは使える GPU が少なすぎて用意したプロンプトがまったく使い切れないというケースも考えられます。今回の場合は、「主にプロンプトに基づくデータ生成」で紹介したような手法で、事実上無限通りのプロンプトを生成直前に必要に応じて用意することで、そのような問題を回避することができました(そのためこの仕組みを「無限生成くん」と呼んでいます)。またプロンプトをランダムに生成することで、どのプロンプトに対する応答が得られたのかを集中管理する必要がなくなり、特に分散処理の手間を大きく軽減することができました。
この仕組みを使うことで、最終的に 350GB 以上のデータを生成することができました(これは検証用に生成したデータを含むため、すべてを学習に投入したわけではありません)。

最後に

今回は、GENIAC 第 2 期におけるコーディング性能向上の試みについて紹介しました。今後も引き続きコーディングの性能向上を目指していきたいと考えています。

仲間募集中

PFN/PFEでは今後もLLMの開発を継続して行っていきます。開発は今回紹介した以外にも多岐にわたります。我々はこれらの課題に情熱をもって挑戦していく仲間を募集しています。

これらの仕事に興味がある方はぜひご応募よろしくお願いします。
https://www.preferred.jp/ja/careers/

参考文献

[1] B. Hui, et al. Qwen2.5-Coder Technical Report. Arxiv. 10.48550/arXiv.2409.12186 (2024).
[2] S. Gunasekar, et al. Textbooks Are All You Need. Arxiv. 10.48550/arXiv.2306.11644 (2023).
[3] Y. Wei, et al. Magicoder: Empowering Code Generation with OSS-Instruct. Arxiv. 10.48550/arXiv.2312.02120 (2023).
[4] B. Adler, et al. Nemotron-4 340B Technical Report. Arxiv. 10.48550/arXiv.2406.11704 (2024).
[5] Y. Wei, et al. SelfCodeAlign: Self-Alignment for Code Generation. Arxiv. 10.48550/arXiv.2410.24198 (2024).

  • Twitter
  • Facebook