04. ハードウェア対応の畳み込みカーネル
03で「フィルタをグループ化すると畳み込みを GEMM にまとめられる」と述べました。このページでは、その GEMM を GPU のテンソルコアで実際にどう計算するか ——論文の核心である 2段階ブロックアルゴリズム を掘り下げます。
1. 畳み込みは Toeplitz 行列の積
まず出発点。離散畳み込みは Toeplitz 行列との行列積と数学的に等価 です。長さ の因果 FIR フィルタ を、長さ の入力 (通常 )に適用した出力は、
これを行列の形に書くと、フィルタ係数が斜めに並ぶ 下三角 Toeplitz 行列 との積になります。
StripedHyena 2 の演算子(SE/MR/LI)はいずれも grouped depthwise convolution ですが、従来 CNN 向けの実装(im2col・Winograd)はこれに最適化されていません。長いフィルタは FFT ベースになりますが、ハードウェア利用率が低い。そこで論文は、filter grouping を活かした direct な multi-pass ブロックアルゴリズム を採用します。
2. ブロック畳み込み:長い系列を分割する
古典的なデジタル信号処理の ブロック畳み込み(block convolution) を使います。入力と出力を サイズ のチャンク に分割し、 の部分ブロックごとに掛けます。
ここで は 番目の入力・出力チャンク、 は Toeplitz を に分割した部分行列です。ポイントは、フィルタのサポート(長さ )を超えた部分ブロックはすべてゼロになり、スキップできる こと。 のとき、これが大きな効率になります。
3. 2段階ブロックアルゴリズム
SE・MR では です。さらに (フィルタ長がブロックの2倍以内)なら、特に効率的な 2段階(two-stage)アルゴリズム が使えます。このとき Toeplitz は、対角ブロック部() と オフ対角部() の2つだけに分解できます。
具体例として (系列長)、(フィルタ長)、(ブロックサイズ)を取ると、
- は「現在のチャンク に整列するフィルタ点」を担当。
- は「前のチャンク からはみ出してくる(spillover)点」、つまりチャンク境界をまたぐ係数を担当。
各出力チャンクは、たった2つの行列積で計算できます。
という条件が、「 より先のオフ対角ブロックは出てこない」ことを保証しています。
4. テンソルコアへ:GEMM 化と再利用
この分解が GPU で速い理由は3つあります。
- は全チャンク・全チャネルで不変 — フィルタが決まれば は固定。一度オンチップメモリ(共有メモリ)にロードすれば、すべてのチャンクで再利用できます。
- 03 のグループ化と組み合わさる — グループ内の全チャネルが同じ を共有するので、小さな GEMV ではなく 大きな GEMM にまとめられます。テンソルコアのサイズ と を揃えれば、 は 2つのフル GEMM として実行できます。
- 「現在チャンク」と「前チャンク」を並列/パイプライン化 — 第1段()と第2段()は独立に走らせられます。
論文の前進カーネル(Algorithm 1)を擬似コードで示すと、こうなります。
入力 v, q, k を ℓ_b × d_g のブロックに分割
for ブロック i = 0 .. ⌈ℓ/ℓ_b⌉ − 1:
v_i, q_i, k_i, H_0, H_1 をオンチップメモリにロード
y_i = H_0 · v_i # 第1 GEMM(対角・block-diagonal)
if i > 0:
y_i += H_1 · v_{i-1} # 第2 GEMM(オフ対角・spillover)
y_i = q_i ⊙ y_i # ゲーティング
return y
GPU のテンソルコアは 密な行列積(GEMM) に特化したユニットで、桁違いのスループットを出します。depthwise 畳み込みを素朴に書くと小さな行列ベクトル積(GEMV)の山になり、テンソルコアを活かせません。「グループ化 → GEMM 化 → テンソルコア」 という一連の co-design が、StripedHyena 2 の速さの正体です。これは 03 の filter grouping と一体の工夫です。
5. 実測:演算子レベルのスループット
この2段階アプローチの効果は実測でも明確です。Hyena-MR(フィルタ長 128)を、素朴な PyTorch 畳み込み(F.conv1d)と比べると、2段階ブロックカーネルは レイテンシ・スループットともに大幅に改善 します。
さらに演算子レベルで他の主要演算子と比較すると、Hyena-SE・Hyena-MR は、最適化された MHA(FlashAttention2・SDPA)や Mamba2・xLSTM・DeltaNet を上回るスループット を、バッチサイズ1・幅 4096 の設定で達成します。
6. 計算コストと chunk 方向の並列化
03 のグループ化は「チャネル方向」に GEMM をまとめる方法でした。論文の付録では、グループ化なしでも GEMM 化する別アプローチ——チャンク方向に並列化 する方法も示されています。
各出力チャンクは で、 は全チャンクで不変です。そこで GPU カーネルは次の手順を取ります。
- フィルタ preload: をオンチップメモリ(共有メモリ)に一度だけロードし、全チャンクで再利用。
- チャンク読み込み:現在チャンク (と前チャンク )をレジスタ/共有メモリへ。
- テンソルコア GEMM: と を計算し、 に累積。
- 書き戻し: をグローバルメモリへ。
計算コスト(cost model) も明快です。各チャンクは の GEMM を2回、すなわち FLOPS。系列全体( チャンク)では、
フィルタが短い()ため 以降のオフ対角ブロックが現れず、この見積もりで収まります。系列長 に対して線形(チャンク数に比例)であり、Attention の と対照的です。
7. 逆伝播:two-pass backward カーネル
学習には逆伝播が必要ですが、フィルタの勾配計算は「全体での累積(global accumulation)」 を要します(同じフィルタが全位置で共有されるため)。これを1つのカーネルに詰め込むと非効率なので、論文は 2段(two-pass)の back-to-back カーネル で実装します。
- 1段目:forward と同じブロック構造を保ったまま、ブロックごとに部分的なフィルタ勾配を累積。
- 2段目:部分勾配を reduction(集約) して最終的なフィルタ勾配を得る。
ポイントは、1段目で部分勾配を coalesced(連続した)形式 で書き出すこと。これにより2段目を単純なベクトル化 reduction にできます。
また、Toeplitz 因子 は その場で効率的に生成(materialize) します。前述の2段階分解で見た Toeplitz 行列を、メモリ上に陽に展開せず、行・列インデックスの差からフィルタ係数を引くだけ で構築できます(Triton カーネルの例)。
import triton
import triton.language as tl
@triton.jit
def load_toeplitz(h_ptr, FILTER_LEN: tl.constexpr, CHUNK_SIZE: tl.constexpr):
# 行 r と列 c のインデックス差 (r - c) が、そのセルが参照するフィルタ係数の添字
r = tl.arange(FILTER_LEN - 1, CHUNK_SIZE + FILTER_LEN - 1)[None, :]
c = tl.arange(0, CHUNK_SIZE)[:, None]
idx = r - c
mask = (idx >= 0) & (idx < FILTER_LEN) # フィルタ範囲外は 0 埋め
return tl.load(h_ptr + idx, mask=mask, other=0.0)
「インデックス差がフィルタ係数の添字になる」のは、まさに Toeplitz 行列が 対角方向に同じ値が並ぶ 性質そのものです。陽に行列を作らずインデックス計算で済むので、メモリと帯域を節約できます。
8. まとめ
- 畳み込みは Toeplitz 行列の積。SE/MR は なら 2段階(+) に分解できる。
- という 2つの GEMM に落ち、 の再利用とグループ化で テンソルコアをフル活用。
- 結果、Hyena-SE/MR は MHA・Mamba2・xLSTM・DeltaNet を上回るスループット。
ここまでは1台の GPU 内の話でした。100 万トークンを 複数 GPU に分散 して学習するには、また別の工夫が要ります。最後の 05. 長系列の分散学習 で、context parallelism と FFT 畳み込みを見ていきます。