この辺の実装がどのようになっているのかを勉強したい。ChatGPTにお願いしながら、読み進めていくことにする。
Hashed Perceptronのテーブルの定義
using bits = champsim::data::bits; // saves some typing constexpr static std::size_t NTABLES = 16; // this many tables constexpr static bits MAX_HIST{232}; // maximum history length constexpr static bits MIN_HIST{3}; // minimum history length (for table 1; table 0 is biases) constexpr static std::size_t TABLE_SIZE = 1 << 12; // 12-bit indices for the tables constexpr static bits TABLE_INDEX_BITS{champsim::msl::lg2(TABLE_SIZE)}; constexpr static int THRESHOLD = 1; constexpr static std::array<bits, NTABLES> history_lengths = { bits{}, MIN_HIST, bits{4}, bits{6}, bits{8}, bits{10}, bits{14}, bits{19}, bits{26}, bits{36}, bits{49}, bits{67}, bits{91}, bits{125}, bits{170}, MAX_HIST}; // geometric global history lengths // tables of 8-bit weights std::array<std::array<champsim::msl::sfwcounter<8>, TABLE_SIZE>, NTABLES> tables{}; // words that store the global history using history_type = folded_shift_register<TABLE_INDEX_BITS>; std::array<history_type, NTABLES> ghist_words = []() { decltype(ghist_words) retval; std::transform(std::cbegin(history_lengths), std::cend(history_lengths), std::begin(retval), [](const auto len) { return history_type{len}; }); return retval; }();
テーブル数とサイズ、閾値などの定数
constexpr static std::size_t NTABLES = 16;
分割して並列に持つパーセプトロン・テーブル(重みテーブル)の数は16である。つまり、最終的に16個の小さなテーブルからそれぞれ重みを取り出して合算する設計となっている。
グローバル履歴の長さを設定
constexpr static bits MAX_HIST{232}; constexpr static bits MIN_HIST{3};
MAX_HIST = 232:グローバル履歴として最大232ビットまで使用することを想定している。MIN_HIST = 3:最小で3ビットの履歴を使用するテーブル(table1)が存在する。table0はバイアス用であるため履歴長は0である。
テーブル0から15まですべて異なる「履歴長」を持たせるが、最小は3ビット、最大は232ビットまでの設定となっている。
各テーブルのサイズを決定
constexpr static std::size_t TABLE_SIZE = 1 << 12; // 4096 エントリ constexpr static bits TABLE_INDEX_BITS{champsim::msl::lg2(TABLE_SIZE)};
各テーブルのサイズは212 = 4096要素である。
TABLE_INDEX_BITSはlg2(TABLE_SIZE)であるため、この場合は12となる。
つまり「インデックスを12ビット分使用する」ことを意味する。
したがって、上記のget_index()で生成されるインデックス(XOR結果)は、最終的に0~4095の範囲に収まる。
しきい値の設定
constexpr static int THRESHOLD = 1;
パーセプトロン合計値youtを判定する閾値は1に設定されている。 合計が1以上ならtaken、0以下ならnot takenと予測する。
history_lengths:テーブルごとの履歴長を決める
constexpr static std::array<bits, NTABLES> history_lengths = { bits{}, MIN_HIST, bits{4}, bits{6}, bits{8}, bits{10}, bits{14}, bits{19}, bits{26}, bits{36}, bits{49}, bits{67}, bits{91}, bits{125}, bits{170}, MAX_HIST };
16個の配列の集合で、各インデックス(0~15)に対応する「そのテーブルで参照に用いるグローバル履歴長」をビット数で示している。
history_lengths[0] = bits{}→ 長さ0(バイアスのみ)history_lengths[1] = MIN_HIST = bits{3}→ 3ビット履歴history_lengths[2] = bits{4}→ 4ビット履歴- …
history_lengths[15] = MAX_HIST = bits{232}→ 232ビットすべての履歴
中身を見ると単純な線形増加ではなく、3, 4, 6, 8, 10, 14, 19, 26, 36, 49, 67, 91, 125, 170, 232といった幾何級数的に増加する履歴長となっている。 小さいテーブル(浅い履歴)から大きいテーブル(深い履歴)へ段階的に幅を持たせることで、短い履歴だけで済むパターンから非常に長い依存を必要とするパターンまでをカバーして精度を向上させる工夫となっている。
tables:8ビット重みの2次元配列
std::array<std::array<champsim::msl::sfwcounter<8>, TABLE_SIZE>, NTABLES> tables{};
テーブル数(16)、内側が各テーブルのエントリ数(4096)の2次元配列を構成している。
champsim::msl::sfwcounter<8>は「8ビット幅の符号付きまたは飽和カウンタ」である。
1つのエントリが−128~+127(または0~255)のいずれかで、更新時に飽和するようなカウンタとなっている。
tables[i][j]で「テーブルiのインデックスjに対応する重み」を表し、予測時にはこの重み値を合算していく。