はじめに:なぜマルチステージアーキテクチャなのか

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)向けに再構成したものです。

Kubernetes cluster diagram showing multi-stage recommender system pipeline with Kubeflow and NVIDIA Triton

システムアーキテクチャ:4段階パイプライン

システム全体は大きく4つの段階で構成されます。

  1. Two-Towerモデルでユーザー-アイテムマッチングスコアを計算 → 候補群生成
  2. Bloom Filterでユーザーが既に見たアイテムを除去
  3. DLRMランカーがユーザー、アイテム、コンテキスト(デバイス、時間など)特徴を統合しクリック確率を予測
  4. 最終再順序付け(Softmax Sampling) で多様性を確保

主要コンポーネント

コンポーネント役割備考
Kubeflow Pipelines全学習 & 日次ファインチューニングワークフロー管理Kubernetes ベース
NVIDIA Merlin (NVTabular + Triton)GPU 高速化特徴量エンジニアリング & モデルサービングマルチステージグラフを単一アンサンブルモデルとして
FAISSANN インデックスによる候補検索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(有向非巡回グラフ)として実行を管理していることです。

NVIDIA Triton Inference Server ensemble model execution flow on Amazon EKS Development Concept Image

実践的な実装テクニックと注意点

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 が自動的に新規ノードをプロビジョニングする点です。ブラックフライデーなどトラフィックが急増するシナリオで特に有用です。

Data pipeline architecture diagram for ecommerce recommender system with feature store and Bloom filter Developer Related Image

限界と今後の展望

このシステムは明らかにプロダクションレベルに近いですが、いくつかの改善点も存在します。

  1. コンテキスト特徴がランカーにのみ使用 → 検索段階でもコンテキストを反映すれば、より関連性の高い候補を抽出可能
  2. セッションベースモデルの欠如 → 現在は top_category で短期関心を近似しているが、Transformer ベースのセッションエンコーダに置き換えればより洗練される
  3. データバージョニング & 実験追跡の不足 → Git コミットでは再現可能だが、データスナップショットが固定されていない。MLflow や Weights & Biases の導入が必要
  4. オンラインモニタリングの不在 → パフォーマンスドリフトが検出できない。TFDV などのツールでデータ検証パイプラインを追加すべき

次のステップとしておすすめの学習リソース:

まとめ: マルチステージ推薦システムは、モデル性能だけで完成するものではありません。特徴ストア、キャッシング戦略、オートスケーリング、MLOps パイプラインまで、インフラ全体を考慮した設計が不可欠です。本記事が皆さんのプロジェクトに実践的な示唆を提供できれば幸いです。

本コンテンツは、信頼性の高い情報源をもとにAIツールを活用して作成され、編集者によるレビューを経て公開されています。専門家によるアドバイスの代替となるものではありません。