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

Model Context Protocol完全実装ガイド 2025- 仕様変遷から最新Streamable HTTPまでの全て

Model Context Protocol完全実装ガイド 2025- 仕様変遷から最新Streamable HTTPまでの全て

こんにちは! 現在、LLM業界で破竹の勢いでひろまっているMCPについて、本日はとくに実装面について解説していきたいとおもいます。 MCP、MCPとひとくちにいっていますが、実は短期間でけっこう「標準」とよばれる仕様が変化しておりますので、仕様のバリエーションを順を追って解説しつつ、実際に実装をしていきたいとおもいます。 さて、MCPですが、2024年後半、Anthropicが発表したModel Context Protocol(MCP)は、AI分野における重要な転換点となりました。 従来、各AIベンダーが独自に実装していたツール呼び出し機能(tool useと呼びます)を標準化し、AIモデルと外部システムの連携を統一的に扱える仕組みを提供しました 本記事で、MCPの誕生から現在に至るまでの技術的変遷を詳細に追いながら、2025年時点での最適な実装方法を完全なソースコードと共に解説します。特に、仕様の変化に振り回されがちな実装者の視点から、なぜ現在の形に収束したのか、そして今後どのような実装アプローチを取るべきかを明確にしていきます。 第1章 MCPが解決しようとした問題

By Qualiteg プロダクト開発部
【出展報告】ASCII STARTUP TechDay 2025

【出展報告】ASCII STARTUP TechDay 2025

こんにちは! 本日、「ASCII STARTUP TechDay 2025」に出展してまいりましたのでレポートさせていただきます! ASCII STARTUP TechDay 2025 ASCII STARTUP TechDay 2025は、2025年11月17日(月)に東京・浅草橋ヒューリックホール&カンファレンスで開催された、ディープテック・スタートアップのエコシステム構築をテーマにした展示交流・カンファレンスイベントです。 秋の展示会は本当にいいですね 本日はとてもよいお天気で、涼しくて、展示会にはピッタリの気候で朝からルンルンでした。しかも午後からの展示会ということで、気持ちに余裕をもって朝の業務をこなしていたところ、けっこうすぐに昼前になり、あわてて現場へ。 浅草橋は当社からもわりと近いという立地の良さを甘く見ておりましたが💦、なんとか予定時刻前に到着しました。やっぱり、都心開催は本当にありがたいですね。 会場へ急いでいると、おなかが「ぐ~」と鳴り 「そういえば、朝食まだだったわ」 とおもったところに、なんと私の大好きなエッセンさん🍞のトラックがあるで

By Qualiteg ビジネス開発本部 | マーケティング部
サブスクビジネス完全攻略 第1回~『アープがさぁ...』『チャーンがさぁ...』にもう困らない完全ガイド

サブスクビジネス完全攻略 第1回~『アープがさぁ...』『チャーンがさぁ...』にもう困らない完全ガイド

なぜサブスクリプションモデルが世界を変えているのか、でもAI台頭でSaaSは終わってしまうの? こんにちは! Qualitegコンサルティングです! 新規事業戦略コンサルタントとして日々クライアントと向き合う中で、ここ最近特に増えているのがSaaSビジネスに関する相談です。興味深いのは、その背景にある動機の多様性です。純粋に収益モデルを改善したい企業もあれば、 「SaaS化を通じて、うちもデジタルネイティブ企業として見られたい」 という願望を持つ伝統的な大企業も少なくありません。 SaaSという言葉が日本のビジネスシーンに本格的に浸透し始めたのは2010年代前半。それから約15年が経ち、今やSaaSは「先進的な企業の証」のように扱われています。 まず SaaSは「サーズ」と読みます。 (「サース」でも間違ではありません、どっちもアリです) ほかにも、 MRR、ARR、アープ、チャーンレート、NRR、Rule of 40…… こうした横文字が飛び交う経営会議に、戸惑いながらも「乗り遅れてはいけない」と焦る新規事業担当者の姿をよく目にします。 しかし一方で、2024

By Qualiteg コンサルティング
ASCII STARTUP TechDay 2025に出展します!

ASCII STARTUP TechDay 2025に出展します!

株式会社Qualitegは、2025年11月17日(月)に東京・浅草橋ヒューリックホール&カンファレンスで開催される「ASCII STARTUP TechDay 2025」に出展いたします。 イベント概要 「ASCII STARTUP TechDay 2025」は、日本のディープテックエコシステムを次のレベルへ押し上げ、新産業を創出するイノベーションカンファレンスです。ディープテック・スタートアップの成長を支えるエコシステムの構築、そして成長・発展を目的に、学術、産業、行政の垣根を越えて知を結集する場として開催されます。 開催情報 * 日時:2025年11月17日(月)13:00~18:00 * 会場:東京・浅草橋ヒューリックホール&カンファレンス * 住所:〒111-0053 東京都台東区浅草橋1-22-16ヒューリック浅草橋ビル * アクセス:JR総武線「浅草橋駅(西口)」より徒歩1分 出展内容 当社ブースでは、以下の3つの主要サービスをご紹介いたします。 1.

By Qualiteg ニュース