はじめに:なぜマルチステージアーキテクチャなのか
ECサイトのトップページに初めて訪れたユーザーに対して、「この人は何を好むのか」をリアルタイムで推定するのは至難の業です。特に商品カタログが数百万点に達すると、全アイテムを逐次評価することは事実上不可能になります。
本記事で紹介するシステムは、Two-Towerモデルで候補を高速に絞り込み(Retrieval)、DLRMランカーで精密にスコアリング(Ranking)、最終再順序付け(Reranking) を行う、典型的なマルチステージ構造を採用しています。単なる構造説明ではなく、実際に1300万インタラクションのデータを用いてKubeflow、NVIDIA Triton、FAISS、Feast、ElastiCacheなどを駆使し、エンドツーエンドでデプロイした過程を重点的に解説します。
本記事は元記事 Deploying a Multistage Multimodal Recommender System on Amazon EKS の内容を、日本の開発者コミュニティ(Qiita / Zenn)向けに再構成したものです。

システムアーキテクチャ:4段階パイプライン
システム全体は大きく4つの段階で構成されます。
- Two-Towerモデルでユーザー-アイテムマッチングスコアを計算 → 候補群生成
- Bloom Filterでユーザーが既に見たアイテムを除去
- DLRMランカーがユーザー、アイテム、コンテキスト(デバイス、時間など)特徴を統合しクリック確率を予測
- 最終再順序付け(Softmax Sampling) で多様性を確保
主要コンポーネント
| コンポーネント | 役割 | 備考 |
|---|---|---|
| Kubeflow Pipelines | 全学習 & 日次ファインチューニングワークフロー管理 | Kubernetes ベース |
| NVIDIA Merlin (NVTabular + Triton) | GPU 高速化特徴量エンジニアリング & モデルサービング | マルチステージグラフを単一アンサンブルモデルとして |
| FAISS | ANN インデックスによる候補検索 | Approximate Nearest Neighbor |
| Feast(オフライン/オンライン Feature Store) | ユーザー/アイテム特徴管理 | ElastiCache for Valkey バックエンド |
| Amazon EKS | コンテナ化された ML ワークロードオーケストレーション | Karpenter + HPA オートスケーリング |
データフロー(リクエスト処理)
1. リクエスト到着 (user_id, device_type, timestamp)
2. context_preprocessor → 欠損値補完、時間特徴生成
3. feast_user_lookup → ユーザー特徴取得(オンラインストア)
4. nvt_user_transform → NVTabular 変換
5. query_tower → ユーザー埋め込み生成
6. faiss_retrieval → Top-K 候補検索
7. filter_seen_items → Bloom Filter で既視アイテム除去
8. feast_item_lookup + nvt_item_transform + multimodal_embedding_lookup → アイテム特徴 + CLIP/SBERT 埋め込み
9. unroll_features → ユーザー/コンテキスト特徴を候補数分だけタイリング
10. dlrm_ranking → スコア計算
11. softmax_sampling → 多様性モードに応じて Top-K 選択
12. item_id_decoder → 元の item_id にマッピングして応答
このプロセスでは 14のモデルが1つの Triton Ensemble として動作します。特に注目すべき点は、各モデルが Python バックエンド(特徴変換、フィルタリング)または TensorFlow バックエンド(クエリタワー、ランカー)で実装され、Triton が DAG(有向非巡回グラフ)として実行を管理していることです。

実践的な実装テクニックと注意点
1. コールドスタート対策:Feature Masking の威力
新規ユーザーや匿名ユーザーに静的なデフォルトリストを表示する代わりに、学習データの5%をランダムにマスキングして OOV(Out-Of-Vocabulary)埋め込みを学習させています。以下がその核となるコードです。
import cudf
import cupy
import os
ANONYMOUS_USER = -1
OOV_GENDER = -1
OOV_TOP_CATEGORY = -1
OOV_DEVICE = -1
masked_train_dir = "./masked_train"
os.makedirs(masked_train_dir, exist_ok=True)
for i in range(train_days):
day = cudf.read_parquet(f"./train_day_{i:02d}.parquet")
n = len(day)
# 5% の確率でユーザー・カテゴリ情報をマスキング
user_mask = cupy.random.random(n) < 0.05
day.loc[user_mask, "user_id"] = ANONYMOUS_USER
day.loc[user_mask, "gender"] = OOV_GENDER
day.loc[user_mask, "top_category"] = OOV_TOP_CATEGORY
# 5% の確率でデバイスタイプをマスキング
device_mask = cupy.random.random(n) < 0.05
day.loc[device_mask, "device_type"] = OOV_DEVICE
day.to_parquet(f"{masked_train_dir}/train_day_{i:02d}.parquet", index=False)
これにより、モデルは 「未知のユーザー」という状況でもコンテキスト(デバイス、時間)に依存して妥当な推薦を行えるようになります。日本の EC サイトでも、新規ユーザーオンボーディングフェーズでこの手法を適用すれば離脱率を大幅に低減できるでしょう。
2. パフォーマンスボトルネックの解消:インメモリキャッシング
プロファイリングの結果、feast_item_lookup が リクエストあたり 195ms を消費し、全体レイテンシの 52% を占めていることが判明しました。解決策はシンプルです。
import numpy as np
class ItemFeatureCache:
def __init__(self, feast_client, item_ids):
self.cache = {}
for item_id in item_ids:
features = feast_client.get_online_features(
features=["item:feature1", "item:feature2"],
entity_rows=[{"item_id": item_id}]
).to_dict()
self.cache[item_id] = np.array([
features["item:feature1"][0],
features["item:feature2"][0]
])
def lookup(self, item_id):
return self.cache.get(item_id, self.default_vector)
結果: feast_item_lookup レイテンシ 99.7% 改善、全体レイテンシ 54% 改善、スループット 310% 向上。アイテム属性が頻繁に変わらない環境では、このトレードオフは十分許容範囲です。
3. オートスケーリング戦略
EKS 上で Triton Inference Server を運用する際、Kubernetes HPA + Karpenter の組み合わせを採用しました。HPA は 30秒平均キューの待機時間をカスタムメトリクスとして使用します。
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: triton-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: triton-inference-server
minReplicas: 1
maxReplicas: 10
metrics:
- type: Pods
pods:
metric:
name: triton_queue_latency_ms
target:
type: AverageValue
averageValue: 100 # 100ms 超過でスケールアウト
この方式の利点は、GPU インスタンスが不足している場合に Karpenter が自動的に新規ノードをプロビジョニングする点です。ブラックフライデーなどトラフィックが急増するシナリオで特に有用です。

限界と今後の展望
このシステムは明らかにプロダクションレベルに近いですが、いくつかの改善点も存在します。
- コンテキスト特徴がランカーにのみ使用 → 検索段階でもコンテキストを反映すれば、より関連性の高い候補を抽出可能
- セッションベースモデルの欠如 → 現在は
top_categoryで短期関心を近似しているが、Transformer ベースのセッションエンコーダに置き換えればより洗練される - データバージョニング & 実験追跡の不足 → Git コミットでは再現可能だが、データスナップショットが固定されていない。MLflow や Weights & Biases の導入が必要
- オンラインモニタリングの不在 → パフォーマンスドリフトが検出できない。TFDV などのツールでデータ検証パイプラインを追加すべき
次のステップとしておすすめの学習リソース:
- SAP Sapphire 2026 マイクロソフトとSAPが描くAIベースエンタープライズの未来 で解説されている MLOps 戦略も併せてご覧ください。
- 推薦システムのセキュリティ面に興味があれば、WhatsAppがRustで30億デバイスのセキュリティを強化した方法 も参考になります。
まとめ: マルチステージ推薦システムは、モデル性能だけで完成するものではありません。特徴ストア、キャッシング戦略、オートスケーリング、MLOps パイプラインまで、インフラ全体を考慮した設計が不可欠です。本記事が皆さんのプロジェクトに実践的な示唆を提供できれば幸いです。