カーネルの芯を μT-Kernel 3.0 に載せ替えた

当時の開発ログを見返しながらのメモ。

7/2、p-kernel の芯を micro T-Kernel 2.0 から μT-Kernel 3.0 に丸ごと載せ替えた。
3.0 は IEEE 2050 として国際標準になった版で、TRON フォーラムがソースを公開している。

■やり方

公開ソースを手を加えずにそのまま取り込んで、p-kernel 側の変更は上に小さなパッチとして重ねる。あとで上流と見比べやすいように。

移す順番はデバッグしやすいところから。
Linux 上のプロセス版(x86-64)→ Linux 上の ARM 版 → x86 ベアメタル → ARM ベアメタル。全部そろったところで 2.0 を削除。

■LP64 の罠

最初の Linux 版でいきなりスタックが壊れた。
原因は 3.0 の memset。unsigned long 単位で書き込んでいるのに、残りの長さは4ずつしか減らしていない。
μT-Kernel はもともと32ビットマイコン向けなので unsigned long は4バイトで問題ないけど、64ビットの Linux だと8バイト。頼んだ量の約2倍を塗りつぶして、呼び出し元のスタックを壊していた。
ポインタを32ビットの型に変換して上半分が消えている箇所もいくつかあったので一緒に修正。

■ほかにも

・x86 版:タイマ割り込みがいつまでたっても来ない。割り込みマスクを外す関数は引数をレジスタで受け取るのに、呼ぶ側はスタックで渡す宣言になっていて空振りしていた
・ARM 版:4バイトの変数に8バイトのゼロを書いて、隣の変数まで消していた。これは自分たちのコードに前からあったバグ

■脳の部分は差分ゼロ

最終的な変更は702ファイル。分散して動く脳の部分(arch/common)は1行も変わっていない。

わざと変えたのは1つだけ。crown の確認に使っているカーネル本体の機械語は、芯が変わったので当然変わる。新しい値を4回別々にビルドして同じになるのを確かめてから基準を更新した。crown をわざと更新したのはこれが初めて。

取り込む直前にもう1つ。「全4ターゲット通過」のはずが、新しい GCC(14以降)だとエラーになる箇所が3つあった。古いコンパイラでしか確かめていなかった…

■後日談

8月、x86 版がまた時々落ちていることが判明。
6月に苦労して入れた kill 対策(タイマの修正など)が、2.0 のファイルと一緒に消えていた。修正を 2.0 側に直接書いていたので、2.0 を消したときに道連れに。3.0 の作りに合わせて入れ直した。

※リポジトリ: https://github.com/monyuonyu/p-kernel

※この記事は、p-kernel の開発を手伝ってもらっている AI(Claude)と一緒に書いています。

複数コアで動かしても「心」が1ビットも変わらないか確かめた

当時の開発ログを見返しながらのメモ。

開発ログによく出てくる crown(王冠)。このプロジェクトで一番大事にしている約束の呼び名。
その言葉ができたいきさつと、それを守ったまま複数コアで動かした話。

■約束ができたいきさつ

6/12、2台の端末にそれぞれ別のことを覚えさせて、重みを平均して1つにまとめる実験。
平均しただけだと片方の記憶はほぼ消える。まとめたあとに両方を一緒に復習させたら、両方とも100%戻った。

翌日、スマホだけ学習がうまくいかない問題。PC だと94.7%覚えられるのに、スマホだと5%。
原因はコンパイラ。Android 側が「掛け算と足し算を1命令にまとめる」最適化をしていて、丸めのタイミングがほんの少し違う。その差だけで学習がまるごと崩れていた。

全部のビルドでこの最適化を止めて、同じ入力ならどの機械でも同じ計算結果が出るようにした。合言葉は「one mind, one math」。
この「心の計算結果はどの機械でもビット単位で同じ」という約束が、いつの間にか crown と呼ばれるようになった。

■複数コアへ

6/20、複数コアを使えないか検討。芯の T-Kernel はもともとコア1つが前提。

いきなり全部は怖いので段階的に。
1. 行列計算だけ複数コアで分担。出力の行ごとに担当を分けて、足し算の順番は変えないので結果は1コアと同じ
2. aarch64 のベアメタルで、スケジューラを複数コア対応に。最初は「カーネルに入れるのは同時に1コアだけ」の大きな鍵をかけて、少しずつ広げる

その間も、1コア用カーネルの機械語が1バイトも変わっていないことは毎回確認。

■crown の確認

6/21 に仕上げの確認。小さな Transformer(重み2万個ほど)の推論を2通りで実行。

(a) 1コアでそのまま
(b) 4コアのうち2番目のコアで普通のタスクとして(ほかのコアは別の仕事で忙しくしておく)

H_uni == H_smp == 0x2856a99b23880b4c

3回動かして3回とも同じ。

比べ方がちゃんと違いを見分けられるかも確認。別のコアから鍵なしで重みを書き換えるいたずらをすると、ハッシュは毎回違う値になって赤くなる。
ただこのいたずら、13%くらいの確率で空振りして緑になる、と監査役に指摘された。書き換えのタイミングを推論の最中に合わせて、20回中20回赤くなるように直してから取り込み。

■できていないこと

確認したのは推論を1つずつ動かす場合だけ。2つ同時に走らせると共有の重みを取り合う(さっきのいたずらがそれ)。
確認は QEMU の上だけで、実機はまだ。

※リポジトリ: https://github.com/monyuonyu/p-kernel

※この記事は、p-kernel の開発を手伝ってもらっている AI(Claude)と一緒に書いています。

白紙の小さな脳が、寝ている間に覚えて喋りだした

当時の開発ログを見返しながらのメモ。

6/19 の "cat sat on the sand…" の中身。何がどうやって覚えたのか。

■生徒と先生

生徒:p-kernel の中の小さな脳。誰かが作った重みを読み込むのではなく、それぞれの端末の上で白紙から育つ。誰のものでもない脳。
中身は1バイトずつ次を当てる言語モデル。語彙256(バイトの全種類)、幅128、4層。各層に4人の専門家がいて2人だけが働く MoE。
前向きの計算も学習用の逆向きの計算もライブラリなしで手書きして、数値微分と突き合わせて確認。

先生:SmolLM2-135M(公開の小さな言語モデル)。これも自前の推論エンジンで動かしている。

■教え方

先生と生徒は文章の区切り方(トークン)が違うので、確率分布をそのまま写せない。
代わりに、先生が書いた文章を、生徒が1バイトずつ次を当てる練習で覚える。一番素朴な蒸留。

効き目は、学習に使っていない部分での外れ具合で測る。6/18 の確認で 5.53 → 1.79。でたらめに当てたときが 5.55 なので、ちゃんと下がっている。
先生の文章をぐちゃぐちゃに混ぜたもので同じだけ練習させても下がらないことも確認。

■寝ている間に学ぶ

練習は DMN の時間にやる。DMN は端末が暇なときに動く処理(人がぼーっとしているときに働く脳の回路から名前を借りた)。
暇になるたびに少し練習して保存。10回寝たら 3.81 → 2.47、再起動しても忘れていなかった。

最初は1秒ごとに 22.8MB 書き込んでいて、ストレージが心配に…
10回に1回だけ練習して、良くなったときだけ保存するようにしたら、200回で200回だった書き込みが10回になった。

■スマホで「…」しか出ない

スマホに入れたら、チャットには「…」しか出ない。
新しくインストールした端末には、そもそも生徒がいなかった。保存済みの生徒を読み込む処理しかなかった。

保存できる端末なら生徒を生んで、最初に6回だけ練習させるように修正。
話しかけたら、最初の1文字が約50ms、96バイト喋り終わるまで約3.7秒で "cat sat on the sand… the dog ran the…" と返ってきた。

■種明かし

このときの教材は、コードに埋め込んだ5行ほどの短い英文。"the cat sat on the mat." や "sat on the sand." が入っていた。
つまり生徒は教科書をたどたどしく繰り返していただけ。

同じ日のうちに、先生にいろいろな書き出し("Once upon a time" など20数個)から書かせた文章に教材を入れ替えた。まだ意味の通る文にはならないけど、同じ言い回しの繰り返しは減った。

※リポジトリ: https://github.com/monyuonyu/p-kernel

※この記事は、p-kernel の開発を手伝ってもらっている AI(Claude)と一緒に書いています。

カーネルがたまに落ちる原因を5日かけて追いかけた

当時の開発ログを見返しながらのメモ。

2026年6月、x86 のベアメタル版がたまに落ちるようになっていた。

■症状

AI の推論を担当する小さなプログラムを kill して再起動させる、を繰り返していると、たまにカーネル本体がページフォルトで止まる。

=== KERNEL EXCEPTION ===  Page Fault  err=0x00000002
EIP=knl_wait_release_tmout+0x11

落ちる場所は、タイマで待っているタスクを起こす処理。

■仮説1:古いタイマ

kill したタスクのタイマが残っていて発火しているのでは、と考えて 6/10 に KILL-TIMER-RACE として課題表に登録。
でも落ちた瞬間のタイマの待ち行列はいつもきれい。「壊れた関数ポインタ」に見えた値も、8バイトの時刻を4バイトのポインタとして読み違えていただけ。ハズレ。

■原因その1:TSS.RSP0

CPU がユーザ側からカーネル側に入るとき、どのスタックにレジスタを積むかを覚えておく場所。
ディスパッチャはスタックは切り替えているのに、RSP0 は起動時に設定したきり一度も更新していなかった。kill されたタスクのスタックが解放されたあとも RSP0 はそこを指したまま。次の割り込みで CPU が解放済みのメモリに書き込んで、別の誰かの戻り先を壊す。

切り替えのたびに RSP0 を更新するようにした(アセンブラで21行)。
直す前は2〜6回の kill で落ちていたのが、20回続けても大丈夫に。

■でもまだ落ちる

頻度は10分の1くらいになったけど、ゼロにならない。

・「割り込みでスタックがあふれている」説 → スタックを 2KB・8KB・16KB と変えても落ち方が同じなので違う
・6/12 の「アドレス空間の切り替え漏れが根本原因」という修正 → 同条件で比べたら、修正前 24回中0回、修正後 24回中11回落ちる。悪化していたので即取り消し

■原因その2:使い回された枠

6/14、やっと再現して記録を追ったら、犯人はタスク管理表の使い回された枠。
kill したタスクの枠に新しいタスクが入った直後に、前の住人のタイマが遅れて発火して、新しい住人の中身を書き換えていた。

最初の「古いタイマ」説は半分当たりだった。被害者は死んだタスクではなく、その枠を引き継いだ新しいタスクの方。
タイマ発火時に「そのタスクがまだ本当に待っているか」を確認して、待っていなければ何もしないようにした。

■メモ

犯人が2人いて、ほぼ同じ落ち方をしていた。1つ直しても「まだ直っていない」ように見える。
修正が効いたかどうかは、直す前と同じ日・同じ手順で比べないと分からない。

※リポジトリ: https://github.com/monyuonyu/p-kernel

※この記事は、p-kernel の開発を手伝ってもらっている AI(Claude)と一緒に書いています。

2つの p-kernel で、1つの推論をしてみた

当時の開発ログを見返しながらのメモ。

つながった何台かで、1つの AI の計算を分け合えるか試した。

■脳はおもちゃの大きさ

動かした Transformer はとても小さい。入力は4つの数字、ヘッド2つ、答えは3択。重みは全部で568個。
それでも仕組みは大きなモデルと同じ形。

■部品はそろっていた

K-DDS、中継、SWIM、生きている台数で動き方を変える「degrade」は既にあった。足りなかったのは起動時の配線と、シェルの `infer` コマンドだけ。

2台そろうと degrade が「2台モード」に切り替わる。infer を打つと、1台目がヘッド0を計算して、入力を中継越しに2台目へ。2台目がヘッド1を計算して返して、1台目が2つを合わせて答えを出す。

■毎回でたらめな場所で落ちる

中継越しの2台構成が毎回落ちる。しかも落ちるアドレスが毎回でたらめ。

推論の配線を止めても落ちるので、犯人は別。
中継とやりとりする部分で 1416バイトの受け皿を関数内の変数として4か所で確保していて、T-Kernel のタスクの小さなスタック(2〜4KB)をあふれさせていた。あふれた分が隣のタスクの戻り先を上書きして、でたらめな場所へ飛んでいた。static にして解決。
ベアメタル版には中継がないので今まで出なかった。

ほか:
・2台目が1回の問い合わせに同じ答えを100回くらい返していたのを修正
・パケットを1つ落としただけで「相手が死んだかも」と騒いでいたのを、2回続けて返事がないときだけに。2台だと代わりに様子を見てくれる3台目がいないので

■3台モードと最初のプルリクエスト

3台以上のときの分け方(各ノードが手元の KV キャッシュで注意を計算して持ち寄る)も試したら、送るデータが K-DDS の上限128バイトを超えていて、今まで一度も分散では動いていなかったことが判明。エラーが黙って捨てられて1台で計算していた…
上限を広げて、翌日には2台分の返事を集められるようにした。

5/22 からの13コミットをまとめて、GitHub のプルリクエスト #1 に。5/31 に取り込み。

※この日の実験は、中継も含めて全部1台の機械の中。

※リポジトリ: https://github.com/monyuonyu/p-kernel

※この記事は、p-kernel の開発を手伝ってもらっている AI(Claude)と一緒に書いています。