GPUアクセラレーションによるプライベート情報検索:技術的課題と最適化実践

1. GPUアクセラレーションにおけるプライベート情報検索の技術的背景

プライベート情報検索(PIR)技術は理論から実用段階へと進化しています。アップルのプライベートビジョンサーチシステムが示したように、従来のPIRスキームは商業価値を証明しました。現代の格子ベース準同型暗号(HE)はPIRに数学的基盤を提供しますが、計算複雑度がボトルネックに。GB級データベースでの単一クエリに数秒かかるのは、データベース全体に対する暗号演算が必要なためです。

GPUアクセラレーションはこの課題解決の新機軸を提供します。NVIDIA RTX 5090の例では、31.5 TOPSの整数演算スループットと1660GB/sのメモリ帯域幅が、PIRの中核演算である数論変換(NTT)と行列乗算(GEMM)に適しています。しかし実装には3つの課題があります:

  1. 計算特性の異質性 PIRプロトコルはExpandQuery、RowSel、ColTorの3段階からなり、それぞれ異なる計算パターンを示します。ExpandQueryとColTorはNTT主体、RowSelは並列GEMM演算です。

  2. メモリ壁問題 クライアントのバッチ処理でスループットが向上しますが、RTX 5090の96MB L2キャッシュを超えるとDRAMアクセスが激増し、性能が急落します。

  3. データ配置の競合 HE演算にはNTT最適化配置(p-major)が必要ですが、RowSelのGEMMにはtiling最適化配置(major/k-major)が必要で、性能損失が50%にもなります。

実装ヒント:SEALやHElibなどの既存HEライブラリを単純適用するのはNG。PIRonGPUなどのオープンソース実装はバッチ処理最適化が不十分で、スループットが理論値の1%未満になるケースがあります。

2. PIRプロトコルの三段階計算特性

2.1 クエリ拡張フェーズ(ExpandQuery)

ExpandQueryはクライアントの単一暗号文をN個に拡張します。各ノードで実行されるSubs演算の核心:

struct CipherText Subs(struct CipherText ct, struct EvalKey evk) {
    // 1. 数論変換
    float* poly_data = inverseNTT(ct.a);
    
    // 2. 数字分解(Dcp)
    float* decomposed = (float*)malloc(5 * sizeof(float));
    for(int i=0; i<5; i++) {
        decomposed[i] = (poly_data >> (i*22)) & 0x3FFFFF; // 基数z=2^22分解
    }
    
    // 3. evkとの累積乗算
    float result = 0;
    for(int i=0; i<5; i++) {
        result += forwardNTT(decomposed[i]) * evk.coeff[i];
    }
    return createCT(result);
}

メモリアクセス特性:

  • 段階的指数増加:k層目で2^k個の暗号文処理
  • 一時ワークセット:Dcpで5倍(z=2^22, ℓ=5)のデータ膨張
  • キー共有:同層ノードでeval_key(evk)を共有

2.2 行選択フェーズ(RowSel)

RowSelは暗号化行列乗算の実装:

void matrixMul(float* M_out, float* Min0, float* Min1, int p, int m, int k, int n) {
    #pragma omp parallel for collapse(2)
    for(int i=0; i<p; i++) {
        for(int j=0; j<n; j++) {
            float sum = 0;
            for(int l=0; l<k; l++) {
                sum += Min0[i*m*k + l] * Min1[l*n + j];
            }
            M_out[i*m*n + j] = sum;
        }
    }
}

バッチ処理32クエリ時の次元規模:

  • p: 4N (多項式点数)
  • m: 64 (2×32)
  • k: D0 (データベース行数)
  • n: D1 (データベース列数)

算術強度が0.9→13.8 Ops/Byteに向上し、メモリバンド幅制限から計算制限へ転換。

2.3 列トーナメントフェーズ(ColTor)

ColTorはトーナメント形式で目標列を抽出します。ExpandQueryと対称的ですが、2つの相違点:

  1. 双多項式処理:暗号文のa/b部分を同時に処理しワークセットが2倍
  2. RGSW暗号文使用:外部積(External Product)演算が必要

3. フェーズ認識型ハイブリッド実行モデル

3.1 キャッシュ容量壁現象

バッチ処理規模32時のワークセット増加:

木の深さ 単一クエリワークセット バッチ32ワークセット
1 64KB 2MB
5 1MB 32MB
10 32MB 1GB

RTX 5090での測定ではL2キャッシュの60%(約57.6MB)超えでDRAMトラフィックが3倍以上に。

3.2 オペレーションレベルとフェーズレベルカーネル比較

オペレーションレベル

  • 個別演算(NTT/Dcp)を独立カーネル化
  • 中間結果をDRAMに書き戻し

フェーズレベル

  • 全演算を単一カーネルに融合
  • 中間データをレジスタ/SMEM保持

測定性能比較(バッチ32):

拡張タイプ カーネルタイプ DRAMトラフィック 実行時間
シャローエクスパンション オペレーションレベル 12GB 38ms
フェーズレベル 15GB 52ms
ディープエクスパンション オペレーションレベル 142GB 210ms
フェーズレベル 71GB 134ms

3.3 動的切り替え戦略

void executeOptimalKernel(int stage_depth, int batch_size) {
    size_t working_set = calculateMemoryUsage(stage_depth, batch_size);
    
    if (working_set < L2_CACHE_SAFE_ZONE) {  // 80MBの安全マージン
        launchOperationLevelKernels();
    } else {
        launchStageLevelKernel();
    }
}

4. RowSel向け配置最適化技術

4.1 データ配置競合分析

従来のHEライブラリのNTT最適化配置:

[p][m][k] : p-major (4N点連続)

効率的なGEMMには:

[m][p][k] : m-major (出力行列)
[k][p][m] : k-major (入力行列)

RTX 5090での測定結果:

配置タイプ 計算効率 L2キャッシュヒット率
p-major 41% 68%
m-major 83% 92%
k-major 79% 89%

4.2 転置配置GEMM設計

  1. オンライン転置パイプライン
__global__ void optimizedGEMM(float* A, float* B, float* C) {
    __shared__ float tileA[TILE_DIM][TILE_DIM+1];  // バンク競合回避
    __shared__ float tileB[TILE_DIM][TILE_DIM+1];
    
    for (int block=0; block<K; block += TILE_DIM) {
        // 協調的データ転置読み込み
        cooperativeLoadTranspose(A, tileA);
        cooperativeLoadTranspose(B, tileB);
        __syncthreads();
        
        // 転置行列演算
        computeTile(tileA, tileB, C);
    }
}
  1. ハイブリッド精度演算
  • 入力:FP32 (HE精度要件)
  • 中間:FP64 (整数溢れ防止)
  • 出力:FP32

RTX 5090で1.7TFLOPS達成(理論ピークの85%)

5. マルチGPU拡張スキーム

5.1 データベースシャーディング

2次元シャーディングによる行/列スケーリング:

GPU0: DB[0:D0/2][0:D1/2]   GPU1: DB[0:D0/2][D1/2:D1]
GPU2: DB[D0/2:D0][0:D1/2]   GPU3: DB[D0/2:D0][D1/2:D1]

NVLinkによる高速データ交換で1/4データ保持。

5.2 GPU間同期最適化

CUDAグラフによるトーナメント構造最適化:

cudaGraph_t buildReductionGraph() {
    cudaGraph_t graph;
    cudaGraphCreate(&graph, 0);
    
    addLocalTournament(graph, gpu0);
    addMergeNode(graph, gpu0, gpu1);
    addFinalComputation(graph, gpu0);
    
    return graph;
}

6. 実測性能と最適化推奨

2GBデータベースでの結果:

最適化項目 スループット改善 遅延削減
ハイブリッド実行モデル 4.2× 61%
転置GEMM 3.1× 68%
マルチGPU拡張(4×) 3.8× 73%
総合最適化 305.7× 96%

導入推奨事項:

  1. 最適バッチ処理量
int optimalBatch = (0.6 * L2_CACHE_SIZE) / (DECOMPOSE_FACTOR * STAGE1_DATA_SIZE);
  1. カーネル選択ヒューリスティック
#define STAGE_THRESHOLD 80MB

bool useStageLevel(int depth, int batch) {
    return (64KB * (1 << depth) * batch) > STAGE_THRESHOLD;
}
  1. メモリ割当戦略
  • cudaMallocAsyncによるパイプラインバッファ確保
  • cudaMemAdviseReadMostlyによる読み取り最適化

パフォーマンストラップ:

  • 256バイトのメモリアラインメント
  • デフォルトストリームでの同期回避
  • 32スレッド超時のSMEMパディング

タグ: GPU プライベート情報検索 準同型暗号 並列計算 メモリ最適化

7月22日 05:51 投稿