約6万件の日本語テキストをベクトル化をGPUとCPU処理で比較してみた
約6万件の日本語テキストのベクトル化を試みる機会がありました。
今回、2つの日本語対応モデル(multilingual-e5-large と ruri-base)を使い、CPU(c7i.xlarge)とGPU(g4dn.xlarge)のEC2インスタンス上でDocker環境を構築、その性能を比較した結果を紹介します。
実行環境とデータ
検証に使用したリソースおよび入力データは以下の通りです。
インスタンス構成
GPU環境:
- インスタンス: g4dn.xlarge (4 vCPU / 16 GiB / カスタム Intel Cascade Lake / NVIDIA Tesla T4)
- AMI: Deep Learning OSS Nvidia Driver AMI GPU PyTorch 2.9 (Amazon Linux 2023) 20260128 (ami-00cef3bb12d4413ab)
CPU環境:
- インスタンス: c7i.xlarge (4 vCPU / 8 GiB / 第4世代 Intel Xeon Scalable - Sapphire Rapids)
- AMI: Amazon Linux 2023 AMI 2023.10.20260120.4 (ami-055a9df0c8c9f681c)
入力データ
- 内容: 記事要約JSON(タイトル、要約、ID)
- レコード数: 59,768 件
- ファイル容量: 約32.0 MB
- 平均サイズ: 約560 bytes / 件
性能比較結果
multilingual-e5-large モデル
多言語対応の高性能モデル(1024次元、391Mパラメータ)での比較結果:
| 項目 | CPU (c7i.xlarge) | GPU (g4dn.xlarge) | 比較結果 |
|---|---|---|---|
| 処理時間 | 約7時間 (推定) | 27分51秒 | 約15倍 高速 |
| スループット | 143 記事/分 | 2,145 記事/分 | - |
| コスト | $1.25 (7h × $0.178) | $0.24 (0.46h × $0.526) | 81% 削減 |
- CPU版は1,000記事処理時点でのスループット(143記事/分)から全体の処理時間を推定
- 単価: c7i.xlarge $0.178/h、g4dn.xlarge $0.526/h(us-west-2 オンデマンド)
ruri-base モデル
日本語特化の軽量モデル(768次元、199Mパラメータ)での比較結果:
| 項目 | CPU (c7i.xlarge) | GPU (g4dn.xlarge) | 比較結果 |
|---|---|---|---|
| 処理時間 | 38分19秒 | 21分43秒 | 1.76倍 高速 |
| スループット | 1,560 記事/分 | 2,752 記事/分 | - |
| コスト | $0.11 (0.64h × $0.178) | $0.19 (0.36h × $0.526) | 73% 増加 |
コストと所要時間の比較
| モデル | 環境 | 処理時間 | コスト |
|---|---|---|---|
| ruri-base | CPU (c7i.xlarge) | 38分19秒 | $0.11 |
| ruri-base | GPU (g4dn.xlarge) | 21分43秒 | $0.19 |
| multilingual-e5-large | CPU (c7i.xlarge) | 約7時間 | $1.25 |
| multilingual-e5-large | GPU (g4dn.xlarge) | 27分51秒 | $0.24 |
参考: モデルロード時間
- ruri-base: 6.8秒
- multilingual-e5-large: 58.4秒(約8.6倍の差)
※Dockerのキャッシュを利用して、メモリへの展開時間のみ計測しました(CPU環境)。処理時間には含まれていません。
まとめ
今回の検証により、利用するモデルに合わせた適切なインスタンスの選定の重要性が確認できました。
当面は費用対効果を優先し、通常のFargateなどの実行環境での利用を予定しているため、CPU処理で実用的な性能を発揮できた ruri-base をベクトル化のモデルとして採用予定です。
今後の多言語対応や、よりベクトル精度の高いモデルとして multilingual-e5-large を利用することになった場合、GPU対応インスタンスや実行環境を活用したいと思います。
参考情報
今回の検証に利用したDockerとPythonコードです。
AWS公式が提供するDeep Learning AMI(Nvidia Driver版)を利用することで、特別なドライバ設定やCUDAのインストール作業なしに、Docker環境でGPUを活用できました。--gpus all フラグを指定するだけで、コンテナ内からGPUリソースにアクセス可能です。
また、sentence-transformers ライブラリはGPU/CPUを自動判定するため、同一のPythonコードでCPU環境とGPU環境の両方で動作します。環境の違いはDockerfileのベースイメージと実行時の --gpus フラグのみで吸収できるため、開発・検証がスムーズに行えました。
multilingual-e5-large の実装
Dockerfile の比較
GPU版 (g4dn.xlarge用):
# CUDA対応のPyTorchイメージ
FROM pytorch/pytorch:2.5.1-cuda12.4-cudnn9-runtime
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
TZ=Asia/Tokyo
WORKDIR /app
RUN pip install --no-cache-dir sentence-transformers boto3
COPY vectorize-simple.py .
CMD ["python", "-u", "vectorize-simple.py"]
CPU版 (c7i.xlarge用):
# CPU専用のPyTorchイメージ
FROM pytorch/pytorch:2.5.1-cpu
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
TZ=Asia/Tokyo
WORKDIR /app
RUN pip install --no-cache-dir sentence-transformers boto3
COPY vectorize-simple.py .
CMD ["python", "-u", "vectorize-simple.py"]
注: 今回の検証ではCUDAイメージを使用しましたが、CPU専用イメージ(
pytorch:2.5.1-cpu)を使用することで、Intel MKLなどのCPU最適化により、さらに性能が向上する可能性があります。
Pythonコード (CPU/GPU共通)
sentence-transformers はCUDAを自動検出するため、コード側での特別な処理は不要でした。
import json
import os
from datetime import datetime
from sentence_transformers import SentenceTransformer
import boto3
# 環境変数から設定を取得
bucket = os.environ['S3_BUCKET']
input_key = os.environ['INPUT_KEY']
output_key = os.environ['OUTPUT_KEY']
s3 = boto3.client('s3')
# S3から入力ファイルをダウンロード
print('Downloading input file...')
s3.download_file(bucket, input_key, '/tmp/articles_ja.json')
# モデルロード(GPUがあれば自動的に使用される)
model_name = os.environ.get('MODEL_NAME', 'intfloat/multilingual-e5-large')
print(f'Loading model: {model_name}...')
model = SentenceTransformer(model_name)
# 記事データ読み込み
print('Loading articles...')
with open('/tmp/articles_ja.json', 'r') as f:
articles = json.load(f)
print(f'Processing {len(articles)} articles...')
# ベクトル化処理(1件ずつ処理、100件ごとに進捗表示)
with open('/tmp/vectors_ja.jsonl', 'w') as f:
for i, article in enumerate(articles):
text = f"{article['title']} {article['summary']}"
vector = model.encode(text).tolist()
result = {
'article_id': article['id'],
'vector': vector,
'dimension': len(vector),
'model': model_name,
'timestamp': datetime.utcnow().isoformat()
}
f.write(json.dumps(result, ensure_ascii=False) + '\n')
if (i + 1) % 100 == 0:
print(f'Processed {i + 1}/{len(articles)} articles')
# S3にアップロード
print('Uploading results...')
s3.upload_file('/tmp/vectors_ja.jsonl', bucket, output_key)
print('Done!')
ポイント:
SentenceTransformerが自動的にGPU/CPUを判定- デバイス指定コード不要
- 同じコードでCPU/GPU両環境で動作
- 100件ごとに進捗ログを出力してスループット測定を可能に
実行コマンド
GPU版 (g4dn.xlarge):
docker run --gpus all --rm \
-e S3_BUCKET=your-bucket-name \
-e INPUT_KEY=articles_ja.json \
-e OUTPUT_KEY=vectors_ja_gpu_${TIMESTAMP}.jsonl \
vectorize-gpu
CPU版 (c7i.xlarge):
docker run --rm \
-e S3_BUCKET=your-bucket-name \
-e INPUT_KEY=articles_ja.json \
-e OUTPUT_KEY=vectors_ja_cpu_${TIMESTAMP}.jsonl \
vectorize-cpu
差分: GPU版のみ --gpus all フラグが必要です。
ruri-base の実装
日本語特化モデル ruri-base を使用する場合、日本語形態素解析のための追加パッケージが必要でした。
Dockerfile
GPU版:
FROM pytorch/pytorch:2.5.1-cuda12.4-cudnn9-runtime
# 日本語形態素解析用パッケージを追加
RUN pip install --no-cache-dir sentence-transformers boto3 protobuf fugashi unidic-lite
COPY vectorize-ruri-base.py /app/vectorize-ruri-base.py
WORKDIR /app
CMD ["python", "vectorize-ruri-base.py"]
CPU版:
FROM pytorch/pytorch:2.5.1-cuda11.8-cudnn9-runtime
# 日本語形態素解析用パッケージを追加
RUN pip install --no-cache-dir sentence-transformers boto3 protobuf fugashi unidic-lite
COPY vectorize-ruri-base.py /app/vectorize-ruri-base.py
WORKDIR /app
CMD ["python", "vectorize-ruri-base.py"]
追加パッケージ:
protobuf: モデル設定ファイルの読み込みfugashi: 日本語形態素解析器(MeCabのPythonバインディング)unidic-lite: 日本語辞書(軽量版、約45MB)
Pythonコード (CPU/GPU共通)
import json
import os
from datetime import datetime
from sentence_transformers import SentenceTransformer
import boto3
bucket = os.environ['S3_BUCKET']
input_key = os.environ['INPUT_KEY']
output_key = os.environ['OUTPUT_KEY']
s3 = boto3.client('s3')
print('Downloading input file...')
s3.download_file(bucket, input_key, '/tmp/articles_ja.json')
print('Loading model: cl-nagoya/ruri-base...')
model = SentenceTransformer('cl-nagoya/ruri-base')
print('Loading articles...')
with open('/tmp/articles_ja.json', 'r') as f:
articles = json.load(f)
print(f'Processing {len(articles)} articles...')
with open('/tmp/vectors_ja.jsonl', 'w') as f:
for i, article in enumerate(articles):
# ruriモデルは "文章: " プレフィックスを推奨
text = f"文章: {article['title']} {article['summary']}"
vector = model.encode(text).tolist()
result = {
'article_id': article['id'],
'vector': vector,
'dimension': len(vector),
'model': 'cl-nagoya/ruri-base',
'timestamp': datetime.utcnow().isoformat()
}
f.write(json.dumps(result, ensure_ascii=False) + '\n')
if (i + 1) % 100 == 0:
print(f'Processed {i + 1}/{len(articles)} articles')
print('Uploading results...')
s3.upload_file('/tmp/vectors_ja.jsonl', bucket, output_key)
print('Done!')
ポイント:
- ruriモデルは入力テキストに
"文章: "プレフィックスを付けることを推奨 - それ以外はmultilingual-e5-largeと同じ構造
実行コマンド
GPU版:
docker run --gpus all --rm \
-e S3_BUCKET=your-bucket-name \
-e INPUT_KEY=articles_ja.json \
-e OUTPUT_KEY=vectors_ja_ruri_gpu_${TIMESTAMP}.jsonl \
vectorize-ruri-gpu
CPU版:
docker run --rm \
-e S3_BUCKET=your-bucket-name \
-e INPUT_KEY=articles_ja.json \
-e OUTPUT_KEY=vectors_ja_ruri_cpu_${TIMESTAMP}.jsonl \
vectorize-ruri-cpu







