以下の内容はhttps://msyksphinz.hatenablog.com/より取得しました。


FPGA開発日記 カテゴリ別インデックス

続きを読む

XiangShanの実装しているaBTB (Ahead-BTB)について学ぶ

XiangShan の aBTB (Ahead-BTB) は、公開スライドを見る限り、BP1/S1 段に置かれた大容量の先行BTBである。BPU全体は3-stage prediction pipelineで構成される。

  • BP1: uBTB + aBTB
  • BP3: mBTB + RAS + TAGE + ITTAGE

が置かれている。つまり aBTB は、精密な最終判定器というより、フロントエンド初段で先読みして fetch を止めないための predictor である。

容量は 1K entries / 4-way と明示されている。

https://tutorial.xiangshan.cc/micro25/slides/Microarchitecture Design Philosophy.pdf

この構成から読めるのは、BP1 はレイテンシ最優先、BP3 は精度優先 という役割分担である。aBTB は BP1 側にいるので、「分岐を厳密に解く」より “次にどこを取りに行くべきかを先に出す” のが主目的となる。

1. uBTBとaBTBの違い

Predictor Index Tag 予測
uBTB (left) A A B
aBTB (right) A B C
現在フェッチしている A で引く
  index = A
  tag   = A
  pred  = B

A を見た瞬間に「A の中の最初の taken branch の飛び先は B」を返す
現在フェッチしている A で引く
  index     = A
  ただし tag = B
  pred      = C

A の時点で「次に来るはずの B が正しいなら、 その次の有力候補は C」を返す
  • uBTB: Fetch Block A を見て、A にある分岐を検出し、次の Fetch Block B を返す
    • このブロックから次へ、という構造
  • aBTB: 同じく A で引くが、照合対象は B 側に置き、さらに先の C を返す
    • つまり aBTB は A→B→C という 1 段先行パイプライン を内部化した予測器となっている。
    • このブロックから、次のブロックをカギにして、さらにその先へ (Aheadは、Tag=B / Prediction=C)となる。

2. なぜ Tag が B になるのか

通常のBTBなら、「今見ているブロックのPC」 で引いて、「そのブロック内の分岐ターゲット」 を返せばよい。ところが aBTB は 将来のフェッチブロック C を先に返したい。すると A だけでは情報が足りない。そこで、

  • A を index に使って候補セットを引く
  • その中で “本当に次が B か” を tag で確認する
  • 一致したら C を prediction として出す

という構成になりなる。

+------------------------------+
| set/index key : A            |
| tag           : B            |
| payload       : C address    |
+------------------------------+

つまり aBTB は、単一ブロックに対するターゲット表 ではなく、フェッチブロック列 A→B→C の相関を圧縮して持つ表 となる。これは classic な ahead predictor の考え方そのものですが、XiangShan の公開資料ではそれを BP1 の S1 predictor として実用的に組み込んでいる。

TAGEについて学ぶ (4. Lookup時に使うタグについて)

前回の続き:msyksphinz.hatenablog.com

TAGEの実装(gem5)を読むと、FoldedHistory がテーブル数ぶん存在し、さらに tag 用が2本ある。

1. GHRは1本だが、foldはテーブルごとに必要

GHRは1本のビット列だが、テーブル $T_i$ はそれぞれ異なる履歴長 $L_i$ を参照する。したがって

  • fold(GHR, L1)fold(GHR, L2) は別物

であり、テーブルごとに圧縮履歴を持つのは自然である。

2. なぜ毎回 fold を計算しないのか

C++モデルであっても、分岐予測は非常に高頻度で呼ばれる。毎回 GHR の長いビット列を走査して fold を計算するとコストが高い。そこで gem5 は

  • 分岐結果が入るたびに、各テーブルの fold 値を増分更新
  • 予測時は保存してある fold 値を参照

という構成を取る(スループット重視)。

3. tag 用 fold が2本あるのは何のためか

gem5のタグ生成(gtag())は次の形を取る(メモの通り)。

tag = (pc>>instShiftAmt) ^comp0 ^ (comp1<<1);
tag&= (1<<tagWidth)-1;

ここで comp0 = computeTags[0][i].compcomp1 = computeTags[1][i].comp は、同じGHR窓を

  • 幅 $w$ と
  • 幅 $w−1$

で折り畳んだ2種類の指紋である。

idx衝突は不可避なので、tagの役割は「同じidxに来た別文脈を弾く」ことになる。しかし長履歴を短い tag 幅に圧縮すると偏りが出やすく、tag衝突(偽ヒット)が増える。そこで、2種類の折り畳みを混ぜて tag を“ハッシュ的”にし、偽ヒットを減らす。

TAGEについて学ぶ(3. lookupの方法を理解する)

前回の続き:msyksphinz.hatenablog.com

更新則に入る前に、まず「予測時に何が起きているか」を整理する。これは TAGE の理解に必須である。

1. 入力:PCとGHR

  • PC:予測対象分岐のアドレス(通常は下位のアラインメントビットを除く)
  • GHR:過去の分岐結果列(グローバル履歴)

2. テーブルごとの index / tag 生成

各テーブル $T_i$ は履歴長 $L_i$ を持つ(幾何級数)。まず履歴を固定幅に圧縮し、PCと混ぜて index と tag を作る。

  • $G_i=\text{fold}(GHR,L_i)$(圧縮履歴)
  • $idx_i = \text{hash}(PC, G_i)$
  • $tag_i = \text{hash2}(PC, G_i)$

圧縮履歴(fold)は、長い履歴をそのまま利用できないための実装上の工夫である。

3. テーブル参照とヒット判定

テーブル $T_i$ から idx_i のエントリを読み出し、タグ一致を確認する。

  • 読み出し:Ti[idx_i] -> {stored_tag, pred_ctr, u}
  • ヒット:stored_tag == tag_i

ヒットしたテーブルは「このPC+履歴文脈に対応するエントリが存在する」と解釈される。

4. provider / alternate の選択

TAGEの合成の要点は “選択” である。

  • provider:ヒットした中で最長履歴(最大 i)
  • alternate:次点のヒット(無ければ base=T0)

予測は原則 provider の pred_ctr で決めるが、実用実装では provider が弱い場合や新規割当直後などに alternate を選ぶ規則を持つことがある。ここが更新則の設計と密接に結びつく。

TAGEについて学ぶ(2. gshare / O-GEHL / TAGE の構成をまとめてみる)

前回の続き:msyksphinz.hatenablog.com

ここでは、三者の関係を「同じ粒度の図」で並べ、差分を固定する。

gshare:単一テーブル、PC ⊕ GHR

  • 履歴長は実質1種類(GHR長)
  • テーブルは1つ
  • 合成は不要

O-GEHL:多履歴長、全テーブル参照、加算合成

  • 履歴長 LiL_iLi は幾何級数で増える
  • 予測のたびに全テーブルのスコアを集めて加算
  • 合成は adder tree(実装上の高速化)

TAGE:多履歴長、タグヒットによる選択合成

  • テーブルを「読む」ことはするが、合成は加算ではない
  • ヒットした中で最長履歴(最も具体的文脈)を採用するのが基本
  • provider/alternate の概念が、後段の更新設計と結びつく(次回以降)

要点は一行で言える。

  • gshare:単発参照
  • O-GEHL:全部参照して加算
  • TAGE:タグ一致で選択

TAGEについて学ぶ (1. TAGEが解く問題)

分岐予測器は、短い履歴相関(直近の局所性)と長い履歴相関(長距離依存)を同時に扱う必要がある。この要請は自然に「複数コンポーネント(複数テーブル)を持つ予測器」を導くが、そこで重要になるのがコンポーネント出力の合成(combining function)である。

adder tree(加算器ツリー)による合成

GEHL / O-GEHL 系は、履歴長の異なる複数テーブルが出力する 符号付きカウンタ値(スコア)を加算し、合計の符号で taken/not-taken を決める。加算を高速に行うため、加算器を木構造に組む(adder tree)。

  • 各テーブル TiT_iTi は ci(signed counter)を返す
  • 合計 $S = \sum_i c_i$
  • $S \ge 0$ なら taken、$S < 0$ なら not-taken

この合成は「重み付き投票」に相当し、履歴長ごとの寄与を統合できる一方、毎予測で複数テーブル参照と加算が必要になる。

O-GEHLの設計観:幾何級数履歴長

O-GEHLは、履歴長を幾何級数(geometric series)で配置する。短い履歴のテーブルに容量を割きつつ、長い履歴相関も取り込めるため、限られたストレージ予算で精度を引き上げる設計になっている。

TAGEの立場:幾何級数履歴長を “タグ付き選択” で使う

TAGE(TAgged GEometric history length)は、幾何級数履歴長という設計観は共有しつつ、合成関数を「加算」ではなく タグ付きテーブルのヒット/ミスによる“選択”へ置き換える。

  • T0:タグなしのベース予測器(bimodal)
  • T1..TN:タグ付きテーブル群(履歴長は幾何級数)
  • 予測は原則として「ヒットした中で最長履歴のテーブル」を採用(ヒット無しならT0)

この枠組みは “PPM-like(最長一致を選択)” と呼ばれる系統に属する。TAGEの論文は、従来PPM-likeタグ予測器の弱点を 更新(update policy)に求め、更新・割当(allocation)を再設計することを重要な貢献としている。

UDP: Utility-Driven Fetch Directed Instruction Prefetching の論文を読む (3. UDPのアイデア)

前回の続き:

msyksphinz.hatenablog.com

UFTQとUDPのポイント

上のジレンマ(timeliness↔汚染、しかもoff-pathは一概に捨てられない)を崩すために、UDP論文は2つの解決方法を提案している。

UDP: off-pathでも“有用な候補だけ”を学習して出す

UFTQが「どれだけ先読みするか(FTQ深さ)」を調整するのに対し、UDPは prefetch candidate(候補)単位で“出す/捨てる”を学習して、特に off-pathでのunuseful(汚染)を削る。

基本方針はシンプルで、

  • on-path中のcandidateは常に有用なので従来通り出す
  • off-path中のcandidateは、学習済みで有用だと分かったものだけ出す
  • on-path:最終的に「正しかった」制御フロー(正解経路)上でフェッチ/実行される命令・フェッチブロック
  • off-path:分岐予測が外れた結果として一時的にフェッチされる「間違った」制御フロー(誤予測経路)上の命令・フェッチブロック

off-path判定(“今、当たってない可能性が高い”を検知)

  • UDPは、分岐予測器(TAGE)の confidence (High/Medium/Low) を使い、confidence counter を更新して off-path を推定する。
    • Low: +2、Medium: +1、High: +0
    • counterが 閾値を超えるとoff-path扱いに切り替える
  • branch recovery と BTB resteer のたびに counter をリセットする。
  • さらに例外として、taken予測した分岐PCがBTB miss の場合も off-path扱いにする。

useful-set(有用だったoff-path候補の集合)でゲートする

  • off-pathモードでは、各prefetch candidateについて useful-set membership をチェックし、set内に存在し、かつ命令キャッシュに無い場合にだけprefetchを発行する。
  • useful-setの学習は「off-pathで出したprefetchが、後でon-pathで本当に役に立った」場合だけに限定する:
    • on-path demandがoff-path prefetchにヒットしたときだけ useful-set に挿入する。
    • off-pathで消費されただけのものを学習すると“off-path専用の無駄”を学習してしまうため、それは学習に使わないと明記している。
  • このuseful-setは実際にはハードウェアとしてはBloomFilterとして実装されるとなっている。

on-path demandで「off-path prefetchが役に立った」ことを検出する仕掛け:Seniority-FTQ

  • UDPは Seniority-FTQ を追加し、FTQから消えた off-path prefetch candidate block を保持する。
  • backendがretireする命令の line address を照合し、候補に一致したら「このoff-path prefetchはuseful」と判定して useful-set を更新する。
  • flush時はflush pointまでのブロックをSenior-FTQから削除する。
  • これはROB相当の巨大な追跡ではなく、fetch block単位・candidateを含むブロックだけなので小さくできる、と説明している。

useful-setの実装:Bloom filter + super-line圧縮

  • useful-setはmembershipテストができれば良いので Bloom filter で実装する。
  • 連続するラインがまとめて有用になる傾向を使い、挿入前に super-line(2本/4本連続など)へ圧縮する:
    • 直近8個の候補をバッファし、連続アドレスを検出してまとめる
    • 1/2/4本の3種類に対応するBloom filterを用意し、lookup結果に応じて1/2/4本分のprefetchを発行
    • Bloom filterサイズ例:1-block=16k bits、2-block=1k bits、4-block=1k bits
  • Bloom filterの具体:
    • false positive率は 1%、hash関数は 6個UDP_Utility-Driven_Fetch_Direct…
    • hashは並列計算、banked SRAMからbitを読んで全ビット1ならmembership成立(レイテンシ1〜6 cycles)
  • フィルタ運用:Bloom filterが満杯になり、unuseful ratioが0.75に達したらクリアする。

必要なハードウェア(概念的)

  • off-path推定用:TAGE confidenceの入力、confidence counter、閾値判定、リセット制御(branch recovery / BTB resteer)
  • 学習用:Seniority-FTQ(off-path candidate block保持+retire時照合+flush時削除)
  • useful-set保持:Bloom filter(3本:1/2/4-block)+super-line生成バッファ
  • 付随カウンタ:unuseful ratio追跡(0.75でクリア)



以上の内容はhttps://msyksphinz.hatenablog.com/より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14