日記 2023-10
公開
2023-10-31 22:58

Lisp in Small Piecesの2章読み進め中。
Lisp2の実装。
あまりそそられない……ので手は動かさず、しかしコードの動きはちゃんと追ってみる。
最適化に利点あるっぽいけどユーザーが手で書く部分に関数名前空間が必要な理由はちょっとわからないなあ。
唯一わかるのは(defun foo (list) (list list))みたいなことができるとか。
逆に分かりづらくなるから好きではない。
引数名「list」に限ればそもそも「xs」使うし。


fossilのrepolistをsystemdで立ててみた。
ほとんどマニュアルに書いてる通り。
WorkingDirectoryをリポジトリファイル(.fossil)を置いてるとこにして、コマンドを /usr/bin/fossil server --port ポート --repolist --localhost . に変更。



Lisp in Small Piecesの1章インタープリタ実装終了。
併せて、Towards a portable and mobile Scheme interpreterのFast Interpreterも実装してみた。

これはevalが (expr, env)->value ではなく expr->(env->value) になる。
例えばsymbolの評価時は、(lookup env expr)の結果ではなく、(lambda (env) (lookup env expr))を返す。
で、一番外側のlambdaにenvを渡すと値が返ってくるように実装する。

syntaxのparseが事前に処理されるので、ちょっと速くなるのかな。



個人プロジェクトにちょこちょこfossil使ってみている。
様々なリポジトリのui(server)管理などを行うcentral server的なものが欲しくなる。

と思ったらfossil server --repolist /path/to/reposで行けた。
便利〜〜〜


fossilまた使ってみてる。
良さそうならgit全部置き換えてもいいかもなあ


最近、毎日少しずつ、Lisp in Small Piecesを読みながら単純なインタープリタから実装していってる。
まあここらへんはもうやる必要ないんだけど、精神統一の趣味というところ。
しかし記述がちょっとくどくて続けるか迷う。
「未定義のシンボル参照をエラーにしないとタイプミスとか発見しづらいよね」に数十行費やさないでくれ〜



何も書くことなかったら別に書かなくていいかな、と思ってしばらく日記止めていた。
けど別に書かない理由もないな、と思い直したので再開する。



過去絵アップと同時に見返してた。
描き始めてから5年、描いてた期間だと3年ぐらい経つのか。
いつか自分で気に入るレベルの絵が描けるようになったらいいな〜と思って始めたけど、もうそのゴールは達成しておる。
あとは際限がない上達への渇望という地獄を突き進むわけですね。


BitNet: Scaling 1-bit Transformers for Large Language Models
おおっ!?


最近は非公開な出来事が増えたので公開日記が滞っておる。
掲示板に書いてるように身体を動かして怪我を増やしております。


HackernewsのGapBuffers vs Ropesのスレッドで、10GBのJSONファイルをテキストエディタで開いて1MBの行を編集するのは珍しいことではない、と主張する人が何人かいるんだけど、状況が全くわからん。
それはテキストファイルじゃなくてクソ使いにくいデータベースだよ。sqliteとか使いなよ」という意見に完全に同意。
コードを全く書けないデータアナリストとかなのかな……?謎。


戦闘員派遣します 7巻まで読んだ。
アリスちゃんで新たな『扉』が開いた気がする。


夜中にこのすばよりみち3巻読んで高まり、本編16・17巻読み返してパワーアップし、勢いで同じ作者の「戦闘員、派遣します!」の1・2巻を読んだ。
朝になってた。


Cheney on the M.T.A.の解説を読んだメモ。

末尾呼び出し最適化・継続・GCを一気に解決する方法。
ただのC function callにコンパイルすると、末尾再帰ループでスタックがあふれる。
CPS変換して、driver loopに継続を返すようにすると、後述のスタックにオブジェクトを生成する方法が使えなくなる。

で、CPS変換して、なおかつ継続を普通にC関数呼び出ししてしまうようにする。
さらに、全てのオブジェクトを最初はスタックにアロケーションする。
するともちろんスタックが溢れるが、スタックを世代別GCのnurseryとみなしてやればよい。CPUキャッシュに乗りやすいので高速。
関数呼び出しの最初に割り当てるオブジェクトのサイズを計算する。
スタックが溢れるなら最後の継続を保存して、GCをかけて、setjmp/longjmpでdriver loopに戻る。
GCは、最後の継続から辿れるポインタを全てヒープに移動し、普通にポインタを書き換えてやれば良い。
CPSなので呼び出しの途中結果について考える必要は無く、GCが(世代別GCにしては)かなり単純になる。

継続・末尾呼び出し最適化が必要なSchemeをCにコンパイルする方法としては、シンプルで面白いと思う。
スタックを世代別GCのnurseryとしても使ってしまうことで、GCも比較的単純になるのはすごい。

ただヒープオブジェクトがスタック上のオブジェクトを参照するよう書き換える際は、ライトバリアが必要になる。
また、スタックに乗らないような巨大なベクタなどをアロケーションする場合を考慮すると、本当の処理は少し面倒そう。
さらに、末尾再帰に空間的な制約は無いんだけど、全ての関数呼び出しがスタックを必ず積むので、GC無しのループは不可能。ループ中にアロケーションしてなくても、minor GCの度に継続のヒープへのコピーが発生するから、若干major GCにも近づくことになる。
まあCとのやりとりも楽になるので、そういった処理はCで書く……ということになるか。


Arkamでペイントソフトちょっとずつ書いてたが、SDLのループなのでtopでCPU100%に張り付くのを見て気絶してしまった。
エネルギー節約太郎と呼んでくれ。



アレルギー検査の結果、ハウスダスト・ダニ・スギ・ヒノキ・ヨモギ・猫が該当した。
猫……
食べ物まったく無かった。意外。
今まで目の敵にしてたブタクサ、ごめん。


xorだけで計算するニューラルネットワークみたいなもの(xorネットワーク?)を作って、GAでうまいこと学習するアルゴリズム探せないかな?と思ってCでちょっと実験コード書いてみた。
アウトプットをどう扱っていいかわからないのと、やっぱBackpropagationつぇ〜な〜と身にしみたのでちょっと断念。
直接バイト列などの結果を出力するより確率のほうが強い、とはわかるんだけどね……どうにかEmbeddingの限界を超える方法出てこないかな。
あとGAで構造を求めるのは悪くないアイディアに思える。



Attentionもなんとか理解した……と思う。
TransformerのQ/K/Vでは無いけど、RNNでSeq2Seqに入れるやつ。

ただずっとモヤモヤしてるんだけど、Embeddingで固定された語彙をベクトルにして、隠れ状態を全て集めて行列演算して……ってエネルギー効率どうなんだ??
人間のニューロンを、発火タイミング(スパイク)ではなく実数値で摸倣してるんだけど、思いっきり生物学的に寄せるか、逆にニューロンから離れて確率と学習の部分だけ別のやり方へ取り出したほうが良さそうに思える。
まあ激頭良い人達がもう取り組んでたりするんだろうけど……


久々にNNのお勉強した。
RNN・LSTM・seq2seqの仕組みの概要を頭に入れた。
実装してないからちゃんと理解したとは胸を張って言えないけど、とりあえず紙の上で説明はできる。
次はいよいよAttention。

ところでSeq2SeqのEncoderやWord2Vecでは、直接の予測結果ではなく隠れ状態などの重みの方を使いまわすわけだけど、このテクニックって名前ついてないのかな。



まだ半袖なんだけど?
言及