vLLMでモデルをロードするとrandomやnp.randomのシード値が内部で設定されてしまうので注意が必要
はじめに
この記事では、個人的に遭遇したvLLMを使う際に乱数シードが意図せず固定されてしまう問題と、その回避策についてまとめます。
※この記事の情報は記事執筆時点(2025/1/8)のものであり、その後のライブラリ更新等で状況が変わっている可能性があります。
ざっくりしたまとめ
先に問題と解決策をざっくりとまとめます。
問題
- vLLMでは
LLM
クラスでモデルをロードすると同時に、引数seed
で渡した値でrandom
やnp.random
のシード値が設定される - 引数を指定しないとデフォルトで
seed=0
が使われるため、ユーザーの気づきにくいところで乱数シードが固定されてしまう -
random
やnp.random
のseedはグローバルに影響するので、vLLMの推論以外の部分の乱数の挙動も固定されてしまう - その結果、vLLMによる推論を含んだコード中の
random
やnp.random
を使用する処理が、何回スクリプトを再実行しても意図せず全く同じ結果を返してしまう
解決策
- モデルのロード時に引数として毎回異なる
seed
値を渡す - モデルのロード後、乱数シードを改めて上書きする
以下、より詳細に解説します。
seed
の挙動について
vLLMにおけるモデル初期化時の引数vLLMでモデルをロードして初期化する時、多くの場合でLLM
クラスを使うと思います。このクラスの初期化時の引数とデフォルト値は以下のドキュメントに記載があります。
この中に、seed
という引数があります。引数の説明は以下のようになっており、指定しなかった場合のデフォルト値は0となっています。
- seed – The seed to initialize the random number generator for sampling.
このseed
の値は、内部で最終的に以下のようにrandom
やnp.random
のシード値として利用されます。
このように、引数seed
を省略すると意図せず気付きにくいところで乱数シードが0に設定されてしまいます。
これらのシード値はvLLMの推論に関係ないその他のrandom
やnp.random
の挙動にも影響してしまいます。これは恐らく出力結果の再現性のための仕様だと思いますが、ユーザーが意図しないところで固定されてしまうので問題になる場合があります。
問題となり得る実際のシナリオ
実際に問題になり得るシナリオとして、LLMによるデータ合成の場面を考えてみます。
よく使われるデータ合成の手法として、LLMに何らかのシードテキスト(例えばWikipediaの記事)を与え、それを参考に質問と応答を作成させ合成対話データとするというものがあります。
これをvLLMで簡単に実装しようとすると、抽象的には以下のような実装になると思います。
import random
from vllm import LLM, SamplingParams
# 1) モデルをロード
model = LLM(model="model_name")
# 2) シードテキストのデータセットをロード
seed_dataset = load_seed_dataset("seed_text_dataset")
# 3) ランダムにシードテキストを選択し、バッチサイズ分のプロンプトを作成
def prepare_prompts_from_seeds(batch_size, dataset):
prompts = []
for _ in range(batch_size):
seed_text = random.choice(dataset)
prompt = format_prompt(seed_text)
prompts.append(prompt)
return prompts
# 4) バッチ推論
prompts = prepare_prompts_from_seeds(100, seed_dataset)
outputs = model.generate(prompts, sampling_params=SamplingParams(temperature=0.7, max_tokens=256))
# 5) 結果を取り出してデータセットに追加
synthesized_data = []
for prompt, output in zip(prompts, outputs):
qa_pair = extract_qa_pair_from_output(output)
synthesized_data.append(qa_pair)
# 6) 結果を保存
save_dataset(synthesized_data)
このコードの中では、明示的な乱数シードの指定はしていません。そのため、直感的にはこれを複数回実行すると毎回違うseed_text
が得られ、それぞれ異なる結果が得られるように思えます。
しかし、上述したように内部的にseed=0
が乱数シードとしてモデルロード時に設定されているため、このスクリプトを何回繰り返し実行してもrandom.choice
で選択されるseed_text
は毎回同じものになり、最終的に得られる出力も全く同じものになってしまいます。
例えば、大量にあるシードテキストのデータセットを元に一定件数データを作成し終わった後、もう一度同じスクリプトを実行して多くのデータを作成しようとしても、全く同じデータセットが出来てしまいます。
回避策
回避策は非常にシンプルで、LLM
クラスのインスタンスを初期化する際にseed
の引数に毎回異なる値が渡されるようにすれば良いです。例えば以下のように現在時刻から秒単位で異なるシード値を渡すことが出来ます。
import time
from vllm import LLM
# 現在時刻を利用して一意のシード値を生成し利用
seed_value = int(time.time())
model = LLM(model="model_name", seed=seed_value)
あるいは、同様にシード値を生成し改めてrandom.seed(seed_value)
のように設定しても良いでしょう。
まとめ
この記事では、個人的に遭遇したvLLMを使う際に乱数シードが意図せず固定されてしまう問題と、その回避策についてまとめました。
特に合成データを作る際に遭遇し得る問題かと思います。この記事が同様の問題に遭遇した方の一助になれば幸いです。
Discussion