PYNQ-Z1 · 段階5a → 5b · 2026-09-18

壁に届いた

前の資料の最後で、行列積だけ速くしても意味がないと分かりました。 そこで attention を回路にしたら 15.6 倍になり、 softmax は帯域を 1 バイトも食いませんでした。 4 本の回路はどれも 2.0 GB/s の壁に張り付いています。

そして最後に、それまでの測り方が間違っていたことが分かりました。 結論の向きは変わりませんでしたが、数字は全部引き直しています(6 節)。

2026-09-21 追記 — もう一度、同じ種類の間違いをしていた

公開後に 比較相手の CPU 実装を -O2(NEON 無効)で焼いていたことが分かりました。 -mfpu=neon を足すと同じコードが 1.78 倍速くなります。 そのため本文の「CPU の何倍」はすべて 1.8 倍ほど過大でした。 数字はこのページ全体で訂正済みです(attention は 28 倍 → 15.6 倍)。

6 節で「測定式を疑え」と書いた直後に、今度は比較相手の作り込みで同じ偏りをやりました。 自分が作ったものは丁寧に測り、比べる相手は雑に測る——これが 2 回続いたということです。

01宿題は「残りの 78%」だった

the 78% that was still on the CPU

前の資料の 最後に足した実測が、計画の前提を壊しました。行列積を回路にして 14.2 ms まで縮めても、attention を CPU に残すと文脈 512 で 46 ms。 系全体の 78% を CPU が占めてしまい、せっかくの回路がほとんど効きません。

だから段階5でやることは一つでした。attention も回路にする。

1 トークンにかかる時間(文脈 512・同じ時間目盛り) 行列積(PL) attention(CPU) attention(PL) 小物(CPU) attention は CPU 段階4 まで 46.2 ms 15.7 tok/s attention も PL 段階5b 20.6 ms 14.20 + 2.97 + 3.47 48.5 tok/s 0 20 40 60 ms 行列積の緑は同じ長さのまま。赤だった部分が 15.6 分の 1 になったのがこの資料の内容
図1 / 縮めたのは赤いところ。 緑(行列積)は段階4 で作った回路のままで、長さは1ミリも変わっていません。 変わったのは attention が 46.2 ms から 2.97 ms になったことだけです。 それだけで系全体が 3 分の 1 になります。 いちばん長い棒から手をつける——速くする仕事はいつもこれです。

02attention は2相の回路になる

two phases, one memory in between

行列積の回路は単純でした。流れてくる重みに次々と掛けて足すだけで、 後ろを振り返る必要がありません。attention はそうはいきません。

まず K を全部流してスコアを作り、そのスコアを重みとしてV をもう一度流す。 K を読み終わるまで V の計算が始められないので、途中にスコアの置き場が要ります。 ここが構造上いちばん大きな違いです。

1 グループ = 1 層の 1 KV ヘッドぶん T q 192 B K ── T × 64 B V ── T × 64 B 出力 195 語 192 + 分母 3 第1相 ── K を流してスコアを作る K を受ける 8 B / 拍 掛け算 24 個 → 加算木 3 段 累算 → clamp 8 bit のスコア スコア置き場 s8 BRAM 2048 語 × 3 ヘッド + 最大値 mx(3 個) 相が変わる。最大値が確定するまで 12 サイクル待つ 第2相 ── V を流して重みをかける 置き場から s8 を読む mx − s8 = 距離 d ★ exp の表 256 語 これが softmax 掛け算 24 個 × V 累算器 192 個 + 分母 3 個 掛け算器は第1相 24 個 + 第2相 24 個 = 48 個。DSP は 220 個あるので 2 割ちょっと ★ の表引きが softmax。DDR には一切触らない ── 6 行下の図4 へ 割り算はしない。分子 192 語と分母 3 語を出して下流に渡す(1 グループに 3 回しかない)
図2 / 置き場をはさんだ2つの相。 左から右へ2回流れます。1回目(K)でスコアを作って置き場に貯め、2回目(V)でそれを読みながら重みとして使う。 行列積の回路には無かった「途中で貯める」がここで初めて要りました。 掛け算器は合わせて 48 個で、チップの DSP 220 個のうち 2 割ちょっとです。
やり方を変えた ── 実機の前に必ずシミュレータ

段階5a では、この回路のたった1サイクルのずれを実機で追いかけて丸一日使いました。 しかも3 回とも違う原因を自信を持って断言し、2 回は間違っていました。 真因は「データが 2 段なのに有効フラグを 3 段で受けていた」——数え間違いです。

段階5b では順番を変えました。Vivado のシミュレータで先に通す。 文脈長を 1・2・3・5・8・12・16・64 と振り、上流のデータを 1 割の確率で欠かし、 下流を 4 回に 1 回詰まらせて、1,560 語を numpy と突き合わせます。 一発で通り、実機でも一発で一致しました。 論理の誤りはシミュレータで、タイミングと帯域だけ実機で——この切り分けです。

031 バイトを 3 回使う

why attention is denser than matmul

ここが意外なところでした。attention は行列積より「濃い」計算です。

行列積では、読んだ重み 1 バイトは 1 回しか使いません。使ったら捨てます。 だから 1 バイトにつき掛け算 1 回。壁の高さがそのまま計算の上限になります。

attention は違います。このモデル(SmolLM2-135M)は GQA という構造で、 K と V の 1 ヘッドを、q の 3 ヘッドが共有しています。 つまり読んだ K の 1 バイトは、3 つの別々の計算に同時に配れる。

行列積(段階3・4) 重み 1 バイト DDR から読む 掛け算 1 回 使ったら捨てる attention(段階5) K/V 1 バイト DDR から読む(1 回だけ) q ヘッド 0 掛け算 q ヘッド 1 掛け算 q ヘッド 2 掛け算 1 回読んで 3 回使う 実測 ── 1 バイトあたりの掛け算の回数 行列積 1.00 attention 2.99
図3 / GQA は回路にとって「おまけ」だった。 GQA はもともと KV キャッシュのメモリを減らすための工夫で、速さのためのものではありません。 ところが「壁は帯域」という世界では、1 回読んで 3 回使えること自体が 3 倍の性能になります。 実測 2.99 は理論値 3.00 のほぼちょうど。 モデル側の設計が、たまたま回路側に都合が良かったという話です。

04softmax はただで付く

the exponent never leaves the chip

softmax は CPU では重い処理です。実測で attention 46 ms のうち 6.9 ms。 指数関数を文脈の長さぶん計算するからです。

回路では、これが表引き 1 回になります。スコアを 8 bit に丸めてあるので、 取りうる値は 256 通りしかありません。256 通りぶんの答えを先に焼いておけばいい。 exp という演算そのものが回路から消えます。

そして肝心なのは、この表がチップの中にあることです。 DDR との間をやりとりするデータは 1 バイトも増えません。

チップの外 / 中 DDR KV キャッシュ K を読む V を読む 結果 195 語 境界を越えるのは この 3 本だけ ここは全部チップの中(BRAM と LUT) スコア置き場 + 最大値 mx mx − s8 ★ exp の表 256 語 LUT に畳まれた softmax はこの 3 つだけ 境界を越える矢印は 1 本も増えない = DDR の読み量が変わらない = 時間が増えない 実測 ── 同じ条件・同じ測り方で、softmax の有無だけを変えた(HP 1 本) softmax なし 1.01 softmax あり 1.00 GB/s
図4 / 境界を越える矢印が増えなければ、時間は増えない。 これは「当然」で片付けられる話ですが、当然で済ませずに 1 変数だけ変えて実測しました。 同じビットストリームを HP 1 本で動かして 1.01 と 1.00——差は測定のばらつきの範囲です。 CPU で 6.9 ms かかっていた処理が、回路では 0 ms になったということです。
小さな工夫 ── 分母をどこに置くか

softmax には割り算が要りますが、回路ではやっていません。 分子 192 語と分母 3 語を出して、割るのは下流に任せます。 1 グループに 3 回しかない割り算のために回路を持つのは割に合わないからです。

ただ、出力を 192 語から 195 語に増やすのが意外と厄介でした。 出力段には 192 個の値から 1 つ選ぶ巨大な選択回路があり、ここは 1 クロックの余裕が 0.436 ns しかありません。2 択を 1 つ足すだけで落ちかねない。 そこで分母を、選択回路のアドレス計算式にちょうど出てくる空き番地 (192・200・208)に置きました。式を変えずに 3 語増やせて、回路は 1 段も深くなっていません。

052 本にしたら 1.46 しか出なかった

when the number refuses to make sense

ここまでは順調でした。HP ポートを 2 本にして帯域を倍にする—— 段階3・4 でやった通りのことをやるだけのはずでした。

ところが 1.46 GB/s しか出ません。 1 本が 1.00 なので 1.46 倍。行列積のときは 1.72 倍伸びたのに、です。 照合は全部一致しているので、回路は正しく動いています。速度だけが合わない。

ここで回路を疑うと沼にはまる

すぐに理由は思いつきました。attention の回路は、出力の 195 語を吐く間だけ上流を止めます。 行列積の回路は一度も止まりません。これが効いているに違いない——と。

でも数が合いませんでした。止まるのは 1 グループ 8,217 拍のうち 216 拍で、 たかだか 2.6% です。26% の説明にはなりません。

そこで回路をいじる前に、文脈長を 128・512・2048 と振って測り、 「1 グループあたり止まる時間 S」と「1 回の測定あたりの固定費 P」の 2 つを未知数にして連立方程式を解きました。答えはこうです。

S = 216 サイクル、P = 0.82 ms。 3 条件ともぴたりと合いました。 S は設計した停止時間そのもの。問題は回路ではなく、P という存在を忘れていたことでした。

06測り方が間違っていた

measure the slope, not the ratio

P の正体は、測定プログラム自身です。転送を仕掛けて完了を待つ処理に 1 チャネル対あたり 0.7〜0.8 ms かかっていました。 8 ms の測定なら、それだけで 1 割が消えます。

そして、これは段階1 から全部の測定に乗っていました。

0 4 MB 8 MB 0 3 6 ms 転送量 旧 ── 全量 ÷ 全時間 = 原点と結んだ傾き 1.47 GB/s(低すぎる) 新 ── 2 点を結んだ傾き 2.00 GB/s(正味) 3.6 ms 5.7 ms 切片 1.5 ms 測定の固定費 直線は原点を通らない。だから 1 点だけ測って原点と結ぶと、必ず低く出る
図5 / 直線が原点を通ると思い込んでいた。 測定は y = 転送量 ÷ 帯域 + 固定費 という直線です。 1 点しか測らずに「全量 ÷ 全時間」で割るのは、原点とその 1 点を結んだ傾きを読むことで、 切片のぶんだけ必ず低くなります。2 点測って差を取れば、切片は引き算で消えます。 中学校で習う一次関数そのもので、恥ずかしい種類の間違いです。

全部測り直しました。結論の向きは一つも変わりませんでしたが、数字はこうなります。

測定旧(1 点)新(傾き)意味
段階2 ・ 計算なし・HP 1 本1.131.143道幅ちょうど 100.0%
段階2 ・ 計算なし・HP 2 本1.972.009DDR 理論 2.1 の 95.7%
段階3 ・ INT8 MAC 16 個1.911.982壁の 98.7%
段階4 ・ 三値1.831.8949.47 G重み/s
段階5b ・ attention + softmax1.461.99壁の 99.3%

いちばん驚いたのは 1 行目です。HP 1 本の回路は、道幅ちょうど 100.0% で動いていました。 「98.7% だから 1.3% はどこかで損している」と 2 週間思っていたのですが、 損していたのは測定プログラムの中だけでした。

教訓

数字が合わないとき、まず疑うべきは測定式です。 今回たまたま助かったのは、回路を作り直す前に文脈長を振って連立方程式を解いたからです。 あのまま「影の累算器を足せば停止が消えるはずだ」と作り始めていたら、 2 週間かけて 2.6% しか改善しないものを作って、それでも数字が合わずに途方に暮れていました。

074 本とも壁に張り付いた

four different circuits, one number

測り直したうえで、これまで作った回路を全部並べます。

道幅 2.286 DDR の壁 2.009 段階2 ・ 計算なし 2.009 段階3 ・ INT8 1.982 段階4 ・ 三値 1.894 段階5b ・ attention 1.990 0 1.0 2.0 GB/s 中身はまるで違う。捨てるだけ/掛け算 16 個/三値 80 個/掛け算 48 個+指数表 ── 出てくる数字は同じ
図6 / 何を作っても同じ数字になる、ということ。 4 本の棒は、捨てるだけの回路・掛け算 16 個・三値 80 個・ 掛け算 48 個+指数表——中身はまったく別物です。 それでも出てくる数字はほぼ同じ。これが「律速は帯域であって演算器の数ではない」ということの、 いちばん素直な見え方です。青い線(道幅)にはまだ余裕があるのに、 橙の線(DDR)で全部止まっています。
段階5b の回路値
読み出し(HP 2 本)1.99 GB/s
DDR の壁に対して99.3%
掛け算5.95 G MAC/s
1 バイトあたり2.99 MAC
LUT12,820 / 53,200 (24%)
DSP108 / 220 (49%)
タイミングの余裕+0.201 ns
文脈 512 の attention(1 トークン)2.97 ms ── CPU の 15.6 倍

08作らないと決めたもの

the optimisation that is not worth it

段階5b には、やる予定だった 3 つ目の項目がありました。 出力の 195 語を吐く間の停止(2.6%)を消すという改良です。 累算器をもう 1 組持って、出力しながら次のグループを受ければ消せます。

やめました。理由は測定が教えてくれました。 HP 1 本の回路は 97.4% で動いていて、これは停止から計算した理論値そのものです。 ところが HP 2 本ではすでに壁の 99.3%。停止を消しても、DDR がそれ以上出してくれません。

計画の前提が、そのまま判断になった

「律速は面積ではなく帯域」——この計画の出発点にあった読みが、 ここでは作らない理由になりました。 192 個の 32 bit 累算器をもう 1 組(レジスタ 6,144 個 × 2 組)積んでも、 壁が許さないのだから意味がありません。

チップにはまだ LUT が 76%、DSP が 51% 余っています。 余っているのに使い道がない、というのがこのボードの現状です。

09ここまでで言えること・言えないこと

what is measured and what is not

言えること。すべて実機での実測です。

言えないこと。ここは正直に書いておきます。

次は本物の重みを流すことです。 ここまでは「箱が正しく速く動くか」の話でした。 次は、その箱から日本語が出てくるかどうかの話になります。