GPUを使った分散処理で見落としがちなCPUボトルネックとtasksetによる解決法

GPUを使った分散処理で見落としがちなCPUボトルネックとtasksetによる解決法

こんにちは!

複数枚のGPUをつかった並列処理システムを設計しているときCPUについてはあまり考えないでシステムを設計してしまうことがあります。

「機械学習システムの主役はGPUなんだから、CPUなんて、あんまり気にしなくてよいのでは」

いいえ、そうでもないんです。

推論中のあるタイミングに急に動作が遅くなったりするときCPUが原因であることがけっこうあります。

概要(5分で分かる要点)

先日GPUを使った並列処理システムで、予期しないCPUボトルネックが発生し、パフォーマンスが大幅に低下する問題に遭遇しました。

複数のプロセスが異なるGPUを使用しているにも関わらず、処理が極端に遅くなる現象の原因は、処理パイプラインの一部に含まれるCPU集約的な計算処理でした。

問題の症状

  • 単一プロセス実行時:正常な速度
  • 複数プロセス並列実行時:処理時間が数倍に増加
  • GPUリソースに競合なし(nvidia-smiで確認済み)

根本原因

処理パイプラインにGPUに適さないCPU集約的な計算(データ前処理、統計変換など)が含まれており、複数プロセスが同じCPUコアを奪い合うことで、キャッシュ競合とコンテキストスイッチが頻発していました。

解決方法

tasksetコマンドで各プロセスを特定のCPUコアに固定
→これにより、複数プロセスで同じCPUコアを奪い合うことを回避
 うまいことやってよ!、って思いますが、
 複数のプロセスが CPU の1番目のコアを使おうとしていました
 こういう部分もちゃんと制御してやる必要があるんです!

# プロセス1をCPU 0-7に固定
taskset -c 0-7 python process1.py --gpu 0

# プロセス2をCPU 8-15に固定
taskset -c 8-15 python process2.py --gpu 1

適用のポイント

  • Intel Coreシリーズ:P/Eコアの特性を考慮してCPU集約的タスクはPコアに配置
  • Intel Xeonシリーズ:NUMAアーキテクチャを考慮し、同一ソケット内でコアを割り当て
  • システムプロセス用のコアを確保:すべてのコアを占有せず、1-2コアは空けておく

このごく簡単な設定により、並列実行時のパフォーマンスが劇的に改善し、ほぼ理想的な並列化効率を達成できました。

コロンブスの卵ですね。

さて、ざっくり版はここまでです。
以下、問題原因の特定やメカニズム、CPU特性に応じた設定方法について詳細編につづきます

詳細版:技術的背景と実装方法

さて、概要はあんなかんじですが、
ここからは問題詳細と具体的な実装方法について解説いたします。

問題発生と原因の特定

問題の発見と初期の混乱

ある日、私たちのチームが開発していたマルチGPU処理システムで奇妙な現象が観察されました。
このシステムは、大規模なデータセットに対して並列処理を行うために設計されたもので、複数のGPUを搭載したサーバー上で動作していました。ここではGPU2枚の話にシンプル化してご説明いたします。

単一のプロセスで処理を実行した場合、期待通りの処理速度で動作し、GPUの使用率も適切な水準を維持していました。しかし、2つのプロセスを異なるGPU(CUDA:0とCUDA:1)に割り当てて同時に実行すると、特定の処理ステップにおいて処理時間が極端に増加するという現象が発生しました。具体的には、単独実行時に数秒で完了していた処理が、並列実行時には数十秒、時には分単位の時間を要するようになったのです。

当初、開発チームはGPUリソースの競合を疑いました。
nvidia-smiコマンドを使用してGPUの状態を監視したところ、各プロセスは確実に異なるGPUを使用しており、GPUメモリの使用量も許容範囲内であることが確認されました。

原因の特定プロセス

GPUリソースに問題がないことが判明した後、私たちは処理パイプライン全体の詳細な分析に着手しました。

各処理ステップの実行時間を計測し、ボトルネックとなっている箇所を特定するため、プロファイリングツールを導入しました。

数日間の調査の結果、処理パイプラインの一部にCPU集約的な計算処理が含まれていることが判明しました。

この処理は全体の処理フローの中では全体の処理時間の数パーセント程度だったのですが、その内容を詳しく分析すると、大量の行列演算を含むデータの前処理段階で必要となる統計的な変換処理でした。具体的には、入力データの正規化、特徴量の抽出、そして複雑な数学的変換です。これらの処理は逐次的な性質を持ち、並列化が困難であったため、GPUではなくCPU用に書かれていました。

どうやらCPUで何かが起こってるということで、さらなる問題分析と問題の根本原因をスタディしました。

問題の根本原因 はCPUのリソース競合

問題の根本原因をさぐっていくと、どうやらCPUのリソース競合ということが最終的に発覚しましたので、以降はその解説をしていきたいとおもいます。

CPUアーキテクチャとコア数の理解

問題を深く理解するために、まず使用しているCPUのアーキテクチャとコア構成を確認する必要があります。

Intel Coreシリーズの典型的なコア構成

世代モデル総コア数総スレッド数PコアPコアスレッドEコアEコアスレッド
第12世代Core i9-12900K162481688
第13世代Core i9-13900K24328161616
第14世代Core i9-14900K24328161616

第12世代以降のIntel Coreシリーズでは、高性能なPコア(Performance-core)と省電力なEコア(Efficiency-core)のハイブリッド構成を採用しています。この構成では、CPU集約的なタスクをPコアに割り当てることが重要です。

Intel Xeonシリーズの特徴

シリーズモデルコア数スレッド数備考
Xeon Gold63541836
Xeon Gold63482856
Xeon Platinum83804080
Xeon Platinum8480+56112最大2ソケット対応

Xeonシリーズの大きな特徴は、マルチソケット構成のサポートです。

例えば、Xeon Platinum 8480+は最大2ソケット構成をサポートし、合計112コア224スレッドという強力な計算能力を実現でっきます

問題発生のメカニズム

CPUリソースの競合がどのように発生するのか、そしてなぜそれが深刻なパフォーマンス低下を引き起こすのかを詳しく解説します。

taskset適用前の状態

複数のCPU集約的なプロセスを同時に実行すると、以下のような状態が発生します

CPUリソースの競合状態

時間軸に沿って見ると、実際には以下のような激しいCPUコアの奪い合いが発生しています

結果: キャッシュ競合、コンテキストスイッチ頻発
処理速度が大幅に低下

taskset適用後の状態
tasksetコマンドを使用してCPUアフィニティを設定すると、状態は劇的に改善します

CPUリソースの分離状態

結果: キャッシュ競合なし、コンテキストスイッチ最小化
各プロセスが安定した処理速度を維持

なぜこのような違いが生まれるのか - 動作原理の詳細

Linuxスケジューラの動作原理

Linuxカーネルに実装されているCFS(Completely Fair Scheduler)は、システム全体の効率を最大化するよう設計されています。このスケジューラは、実行可能なすべてのプロセスに対して「公平に」CPU時間を配分しようとします。具体的には、各プロセスの実行時間を追跡し、最も実行時間が少ないプロセスを次に実行するよう選択します。

通常のワークロードでは、この仕組みは非常に効果的です。しかし、CPU集約的な複数のプロセスが同時に実行される場合、スケジューラは各プロセスを利用可能なすべてのCPUコアに動的に割り当て続けます。この「動的な割り当て」が、実は深刻なパフォーマンス問題の原因となります。

キャッシュ階層とその影響

CPUリソースの競合が深刻な問題となる理由を理解するために、CPUのキャッシュ階層について見てみましょう

CPUキャッシュの階層構造

現代のCPUは、メインメモリへのアクセス遅延を隠蔽するために、多層のキャッシュメモリを搭載しています

CPUキャッシュ階層の構造

重要な特性

  • L1/L2キャッシュ: 各CPUコア専用で、そのコアで実行されるプロセスのデータを保持
  • L3キャッシュ: 同一CPUダイ内のすべてのコアで共有(Intel Coreの第12世代以降ではP/Eコアで分離される場合あり、Xeonではソケット単位)
  • アクセス速度の差: L1キャッシュ(4ns)からメインメモリ(100ns)まで、25倍以上の速度差

キャッシュ競合による性能劣化のメカニズム

この階層構造を理解した上で、複数プロセスが同じCPUコアを奪い合う場合に何が起きるかを見てみましょう:

1. キャッシュの汚染と無効化

プロセスがCPUコアを切り替える際の動作を時系列で追うと以下のようになります。

このように、プロセスがコア間を移動すると、キャッシュの局所性が失われ、低速なメインメモリへのアクセスがふえて性能が大幅に低下します

2. キャッシュスラッシングの発生

また、頻繁なプロセス切り替えが発生すると、以下のような悪循環に陥ります

キャッシュスラッシング状態
メモリ帯域幅の競合

さらに、CPU集約的な処理が大量のデータを扱う場合、メモリ帯域幅の競合も発生します。キャッシュミスが増加すると、メインメモリへのアクセスが増加し、メモリ帯域幅も圧迫されます

  • 理論帯域幅: DDR4-3200で約51.2GB/s
  • キャッシュヒット時: メモリ帯域使用率 5%程度
  • キャッシュミス多発時: メモリ帯域使用率 70%以上

複数プロセスが同時にキャッシュミスを起こすと、メモリコントローラでの競合も発生し、さらなる遅延が生じます。

こういった要因が組み合わさることで、単純に2つのプロセスを実行した場合でも、処理時間が2倍以上に増加する現象が発生してしまうことがあります。

ということで、tasksetによるCPUアフィニティの設定は、これら問題をさくっと一発で解決する、シンプルかつ効果的なアプローチとなります。

Xeonシリーズにおける考慮事項

さて、Intel Xeonシリーズを使用する場合、特にマルチソケット構成では、NUMA(Non-Uniform Memory Access)アーキテクチャを意識しないと沼にはまることになります。

NUMAアーキテクチャとは

NUMAは、複数のCPUソケットを搭載したシステムにおけるメモリアクセスの最適化技術です。各CPUソケットは自身専用のメモリコントローラとローカルメモリを持ち、これらの組み合わせを「NUMAノード」と呼びます。

重要な特性として、CPUは自身のNUMAノードに属するローカルメモリへのアクセスは高速(約70-100ns)ですが、他のNUMAノードのメモリ(リモートメモリ)へのアクセスは、QPI(QuickPath Interconnect)やUPI(Ultra Path Interconnect)を経由する必要があるため、1.5~2倍のレイテンシ(約150-200ns)が発生します。

なぜNUMA最適化が重要なのか

NUMA構成を無視してプロセスを配置すると、以下の問題が発生し沼ります。

  1. メモリアクセスの不均衡
    プロセスがSocket 0のCPUで実行されているが、メモリがSocket 1に配置されている場合、すべてのメモリアクセスがUPIリンクを経由することになり、大幅な性能低下を引き起こします。
  2. メモリ帯域幅の競合
    複数のプロセスが異なるソケットから同一のメモリバンクにアクセスすると、UPIリンクの帯域幅(最大約41.6GB/s)がボトルネックとなります。ローカルメモリの帯域幅(DDR4-2933で約140GB/s)と比較すると、大幅に制限されます。
  3. キャッシュコヒーレンシのオーバーヘッド
    異なるソケット間でデータを共有する際、キャッシュの一貫性を保つためのスヌープ処理が発生し、レイテンシがさらに増加します。

2ソケットXeonシステムのNUMA構成

ということで、XEONの場合尾は、プロセスをNUMAノード単位で割り当てることが重要となります

# プロセス1:NUMA Node 0のCPUとメモリを使用
numactl --cpunodebind=0 --membind=0 taskset -c 0-27 python process1.py --gpu 0

# プロセス2:NUMA Node 1のCPUとメモリを使用
numactl --cpunodebind=1 --membind=1 taskset -c 28-55 python process2.py --gpu 1

NUMAノードをまたぐメモリアクセスは、ローカルメモリアクセスと比較して約1.5~2倍のレイテンシが発生します。そのため、各プロセスが使用するCPUコアとメモリを同一のNUMAノード内に配置することで、メモリアクセスのレイテンシを最小化し、パフォーマンスを大幅に向上させることができます。

問題解決へ

さて、いろいろCPUについて調べて、この問題を解決法がみえてきました。
解決法はシンプルで、どのプロセスをどのコアに割り振るか、を制御するという方法です。そのためにtasksetコマンドを活用することができます。

tasksetは、Linuxにおいてプロセスを特定のCPUコアセットに固定(アフィニティの設定)するための強力なツールです。

それでぇあ、それぞれの環境において、どのようにtasksetを使うかみていきましょう。

Intel Coreシリーズでの実装例

Intel Core i9-13900K(8 Pコア + 16 Eコア)を使用する場合の実装例

# Pコアの確認(通常0-15がPコア+HTT)
lscpu --all --extended | grep "Core"

# プロセス1:Pコアの前半を使用
taskset -c 0-7 python heavy_process1.py --gpu 0

# プロセス2:Pコアの後半を使用  
taskset -c 8-15 python heavy_process2.py --gpu 1

# 軽量なバックグラウンドタスクはEコアへ
taskset -c 16-31 python background_task.py

Intel Xeonシリーズでの実装例

Xeon Gold 6348(28コア×2ソケット)を使用する場合

# NUMAノードの確認
numactl --hardware

# プロセス1:Socket 0のコアを使用(NUMA最適化)
numactl --cpunodebind=0 --membind=0 \
  taskset -c 0-27 python process1.py --gpu 0

# プロセス2:Socket 1のコアを使用(NUMA最適化)
numactl --cpunodebind=1 --membind=1 \
  taskset -c 28-55 python process2.py --gpu 1

Windows Subsystem for Linux (WSL)環境での実装

WSL環境でも同様の最適化が可能です。

@echo off
setlocal enabledelayedexpansion

rem システムのCPU情報を取得
for /f "tokens=*" %%a in ('wsl -e lscpu ^| grep "Model name"') do set CPU_MODEL=%%a
echo CPU Model: %CPU_MODEL%

rem CPUコア数を確認(WSL経由)
for /f "tokens=*" %%a in ('wsl -e nproc') do set TOTAL_CORES=%%a
echo Total CPU cores: %TOTAL_CORES%

rem NUMAノード数を確認
for /f "tokens=*" %%a in ('wsl -e lscpu ^| grep "NUMA node(s)" ^| awk "{print $3}"') do set NUMA_NODES=%%a
echo NUMA nodes: %NUMA_NODES%

rem CPUモデルに応じた最適な設定を選択
if "%NUMA_NODES%"=="2" (
    echo Detected dual-socket system, using NUMA optimization
    
    rem Socket 0のコア範囲を計算
    set /a CORES_PER_SOCKET=%TOTAL_CORES%/2
    set /a SOCKET0_END=%CORES_PER_SOCKET%-1
    
    rem Socket 1のコア範囲を計算
    set /a SOCKET1_START=%CORES_PER_SOCKET%
    set /a SOCKET1_END=%TOTAL_CORES%-1
    
    rem NUMA最適化付きでワーカーを起動
    start "Worker 1 (NUMA 0)" wsl -e bash -ic "numactl --cpunodebind=0 --membind=0 taskset -c 0-%SOCKET0_END% python worker.py --gpu 0 --worker-id 1"
    start "Worker 2 (NUMA 1)" wsl -e bash -ic "numactl --cpunodebind=1 --membind=1 taskset -c %SOCKET1_START%-%SOCKET1_END% python worker.py --gpu 1 --worker-id 2"
) else (
    echo Detected single-socket system, using simple core partitioning
    
    rem CPUコアを2つのグループに分割
    set /a HALF_CORES=%TOTAL_CORES%/2
    set /a SECOND_HALF_START=%HALF_CORES%
    set /a SECOND_HALF_END=%TOTAL_CORES%-1
    
    rem シンプルなコア分割でワーカーを起動
    start "Worker 1" wsl -e bash -ic "taskset -c 0-%HALF_CORES% python worker.py --gpu 0 --worker-id 1"
    start "Worker 2" wsl -e bash -ic "taskset -c %SECOND_HALF_START%-%SECOND_HALF_END% python worker.py --gpu 1 --worker-id 2"
)

echo All workers started successfully

CPUアーキテクチャに応じた動的な設定を行うバッチファイルの例

まとめと今後の展望

GPUを活用した分散処理システムの開発において、GPU側の最適化に注目することは当然重要ですが、処理パイプライン全体を俯瞰し、隠れたボトルネックを見つけ出すことも同様に重要だと気づかされました。

特にCPUについては

「GPUとの帯域幅が充分あって、そこそこの性能のCPUのせときゃいいでしょ」

くらいの認識で、あまり深く考えてこなかったので今回の件は本当に学びになりました。

今回の事例のように処理パイプラインの一部に含まれていたCPU集約的な処理が、複数プロセスの並列実行時に深刻なボトルネックとなるシーンは結構あるんじゃないかとおもいます。

tasksetコマンドを使用したCPUアフィニティの設定は、こういう問題に対するシンプルかつ効果的な解決策でした。

特にIntel XeonシリーズのようなマルチソケットシステムでNUMA最適化と組み合わせることで、劇的なパフォーマンス向上を実現できそうです。

一方、Intel Coreシリーズの最新世代をつかうときはPコアとEコアのハイブリッド構成を理解し、適切にタスクを配置することが重要になります。

当社GPUサーバー群は現状基本インテルアーキテクチャを採用してますが、
AMDのチップレット設計、ARMサーバーの普及など、今後は、なにがあるかわかりませんのでこういうCPUの動作原理もちゃんとおさえて今後のものづくりに生かしていきたいとおもいます!

それでは、最後までお読みいただきありがとうございました!
また次回お会いしましょう!

Read more

Claude Fable5 完全ガイド — 公式ドキュメントから読み解くモデル仕様とClaude Code運用ポイント

Claude Fable5 完全ガイド — 公式ドキュメントから読み解くモデル仕様とClaude Code運用ポイント

こんにちは! 2026年6月に登場した Claude Fable 5 は、公開直後の輸出規制による一時停止、グローバル再展開、そしてサブスクリプション枠からの離脱と、わずか1か月でめまぐるしい動きを見せています。 当ブログでもその時々の状況を追ってきました。 まず全体像は ついに一般公開、Claude Mythos 5 / Fable 5 を実務視点で読み解く で、公開直後の停止騒動は 公開から3日で停止──Fable 5/Mythos 5 をめぐる米政府指令が示した、AI の新しい可用性リスク で、料金と今後の見通しは Claude Fable 5 はこれからどうなる? 経緯・コスト・今後の見通し で扱っています。 本記事は、それらを踏まえた「実務で使うための決定版ガイド」です。 とくに 2026年7月12日(日本時間7月13日)を境にサブスクリプション枠から外れ、使用クレジットを有効化しないと使えなくなる (この期限は当初2026年7月7日とされていましたが、のちに5日間延長されて7月12日になりました。

By Qualiteg プロダクト開発部
AI時代のデータ漏洩防止の要諦とテクノロジー:第2回 従来型DLPを超えて、AI-DLPが解決すべき本質的課題

AI時代のデータ漏洩防止の要諦とテクノロジー:第2回 従来型DLPを超えて、AI-DLPが解決すべき本質的課題

こんにちは! 前回の記事では、AI時代のデータ漏洩防止における技術的な基礎として、HTTPSインターセプトの仕組みと限界について詳しく解説しました。プロキシサーバーによるSSL/TLS通信の復号化、中間CA証明書の運用、そして証明書ピンニングという技術的制約まで、企業がWeb通信を監視する際の技術的な現実を明らかにしました。 しかし、これらのプロキシ技術は、実は既存のDLP製品でも広く採用されている一般的な手法です。メール監視、ファイル転送の制御、Webアクセスの監査など、従来型のデータ漏洩防止においても、HTTPSインターセプトは中核的な役割を果たしてきました。 では、なぜAI時代において新たにDLPを考え直す必要があるのでしょうか。 前回にひきつづき、従来型DLPでは対応できないAI固有の課題と、AI-DLPとして新たに考慮すべき要素に焦点を当て、より本質的な議論を展開していきます。 1. AI時代が要求する新たなDLP要件 従来のDLP製品は、クレジットカード番号や社会保障番号といった定型的なパターンの検出において優れた実績を持っています。これらの技術は今後も重要な

By Qualiteg プロダクト開発部, Qualiteg コンサルティング
Claude Fable 5はこれからどうなる? 経緯・コスト・今後の見通しをファクトベースで整理する

Claude Fable 5はこれからどうなる? 経緯・コスト・今後の見通しをファクトベースで整理する

こんにちは! 2026年7月2日(日本時間)、日本からもClaude Fable 5が再び利用できるようになりました。 2026年6月に大きな注目を集めて登場し、わずか3日で米政府の指令により停止、そして7月1日(米国時間)に復活したAnthropicの最上位モデル「Claude Fable 5」。 復活と同時に 「サブスクで使えるのは7月7日まで」 という条件が付いたことで、利用者の間ではコストへの懸念の声も見られます。 本記事では、憶測と事実を切り分けながら、 (1)これまでの経緯、 (2)確定している料金体系、 (3)実際のコスト試算、 (4)今後の見通し、 の4点を整理します。確定情報(ファクト)と筆者の推測は明確に区別して書きます。 ※本記事の日付は、特記のない限りAnthropicの発表に基づく米国時間を基準としています。 なお当ブログでは、Fable 5 / Mythos 5についてリリース直後の技術解説、米政府指令による停止が示した可用性リスクの考察、Fable 5の安全分類器がClaude Code上で実際にどう振る舞ったかの体験記を公開してきました。

By Qualiteg コンサルティング
モデルを「壊さずに」ドメインを広げる ― XLM-RoBERTa 継続学習の設計ノート

モデルを「壊さずに」ドメインを広げる ― XLM-RoBERTa 継続学習の設計ノート

こんにちは、Qualiteg研究部です。 今日は「すでに完成している強いモデルを、壊さずに広げる」という、地味だけど実務でとても大事なテーマを取り上げたいと思います。 機械学習に取り組んでいると、 「一度しっかり仕上げたモデルを、新しい用途やデータに合わせてもう少し広げたい」 そんな場面はよく出てきます。 今回ご紹介するNER(固有表現抽出)のシーンに限らず、いろいろなタスクで共通する悩みではないでしょうか。 ところが、ここで素朴に追加学習をかけると、せっかくの強みがあっさり崩れてしまう。 私たちは、PII(個人特定情報や要配慮情報)を検出・マスキングするエンジン(PII-FI)を構築する際、実際にそれを経験しました。 Precision(適合率)が 0.83 から 0.17 まで転げ落ちる、なんてことも本当に起きるんです。 PII検出では、ドメイン(分野)ごとに検出したいPII型の種類や求められる精度が異なる場合があります。そこで1つのエンジンといっても、対応ドメインを広げていくたびに(そのドメインに適応させるための)追加学習が求められることがあります。 本稿は、そう

By Qualiteg 研究部