2026年 01月 31日

reload した後 h2oの古いプロセスが残り続ける場合

h2o は graceful restart をするのだが、稀に古いプロセスがずっと残ることがあって困ってた。(正確には start_server が restart させていて、h2o 自体は graceful shutdown しているだけ)

一例としてはこういう感じ (これは単に SIGHUP 直後なだけだけど、ときどき reload するとプロセスが残っていく)

 $  sudo systemctl status h2o
● h2o.service - H2O HTTP Server
     Loaded: loaded (/etc/systemd/system/h2o.service; enabled; preset: enabled)
     Active: active (running) since Sat 2026-01-10 17:36:22 JST; 2 weeks 4 days ago
    Process: 902234 ExecReload=/bin/kill -s HUP $MAINPID (code=exited, status=0/SUCCESS)
   Main PID: 1288 (perl)
      Tasks: 23 (limit: 2274)
     Memory: 53.0M (peak: 1015.0M swap: 12.2M swap peak: 95.1M)
        CPU: 1h 38min 58.048s
     CGroup: /system.slice/h2o.service
             ├─  1288 perl -x /usr/local/share/h2o/start_server --pid-file=/var/run/h2o.pid --port=0.0.0.0:80 "--port=[::]:80" --port=0.0.0.0:u443 "--port=[::]:u443" --port=0.0.0.0:443 "--port=[::]:443" -- /usr/local/bin/h2o -c /srv/www/h2o.conf.yaml
             ├─897714 /usr/local/bin/h2o -c /srv/www/h2o.conf.yaml
             ├─897716 neverbleed
             ├─897735 perl -x /usr/local/share/h2o/annotate-backtrace-symbols
             ├─902237 /usr/local/bin/h2o -c /srv/www/h2o.conf.yaml
             ├─902238 neverbleed
             └─902257 perl -x /usr/local/share/h2o/annotate-backtrace-symbols

結論: http*-graceful-shutdown-timeout を設定しよう

まず以下のような設定をし忘れないように。この例だと30��で強制切断。デフォルトが0(無効)

http2-graceful-shutdown-timeout: 30
http3-graceful-shutdown-timeout: 30

メモ: タイムアウトの仕組み

HTTP/2 (lib/http2/connection):
  1. initiate_graceful_shutdown → 最初のGOAWAY送信
  2. 1000ms後 → graceful_shutdown_resend_goaway → 2回目のGOAWAY送信
  3. もし http2-graceful-shutdown-timeout > 0なら、その時間後に強制終了
HTTP/3 (lib/http3/server.c):
  1. 同様に1000ms後に2回目のGOAWAY
  2. もし http3-graceful-shutdown-timeout > 0なら、その時間後に強制終了
HTTP/1 (lib/http1.c):
  1. Keep-Alive をオフにしてレスポンス終了時 close

とにかくタイムアウトは全部設定しておこう!

調査

ただこの2つを設定していても接続が残るケースが発生して悩んだ

$  sudo ss -tapn | grep 902501                                                                                                                                                                                                              
ESTAB      0      0                              127.0.0.1:54080                                 127.0.0.1:5001  users:(("h2o",pid=902501,fd=66))                                                                                             
ESTAB      0      0                              127.0.0.1:46754                                 127.0.0.1:3000  users:(("h2o",pid=902501,fd=72))                                                                                             
CLOSE-WAIT 1      0                              127.0.0.1:52768                                 127.0.0.1:80    users:(("h2o",pid=902501,fd=74))                                                                                               

CLOSE-WAIT が残っている。

→ 最終的にこれは grafana の websocket への接続がずっと残っているというものだった。ブラウザの grafana タブを閉じたらプロセスが消えた。なんで CLOSE-WAIT?

websocket の proxy.timeout.io はデフォルト30秒らしいけど、通信が続くと一生切断されないのかも?

2026年 01月 30日

モールス(時系列データ)の機械学習

機械に機械学習のモデルを設計してもらう(何もわからない)の続きで、モールスの機械学習をちまちま諦めずにやってる。

PCEN (Per-Channel Energy Normalization)

音声データは非常にダイナミックレンジが広いので、なんらかの方法でノーマライズしないと、いざ実世界の、スケール 40dBぐらい違うデータを推論しようとしてもうまくいかない。

PCEN はなんかそういう圧縮してスケールする正規化をモデルの中に組込むというやつ。ちゃんと実装できているかはわかなないが挙動からするとできているのだろう……

もう少し実践的なデモ

実際にオーディオストリームから広域をFFTしつつ、一部をクロップして連続推論するというデモをつくった (マイク入力に対応)。

「しばらく動かす���推論がエラーで止まる」という現象が発生してこまった。onnx のエラーが出るのだけど、WASM のポインタアドレスが throw されてくるのでまるで情報がない。

Python 側では起きないので、ずっとJS側の実装でメモリ管理がおかしいことを疑っていたが、結局 Claude Opus 君が「モデルのマスク管理がおかしいです!」ということに気付いてくれてなおすことができた。ひさびさに Claude に感動したわ。Python 側で再現しなかったのは長時間連続のストリーム推論することがないからだった。

先読みの実装

当初から「先読み」を実装していたつもりだったのに、この期に及んで、まったく先読みできていなかったことが発覚。わたくし衝撃です。先読みせずにそこそこ精度が出てたのはすごいが…… 一応文字が確定するタイミングでCTC発火するように調整していたので、非因果的なのは長点・短点・単語間空白などを予測するヘッドだけだったはずではある。機械学習、こういう間違った実装されてても、ある程度はなんとかなっちゃうことがあるので本当に怖い。

ということで、時系列データの場合とにかく因果性というのが問題になって、常に頭を悩ませる。こんがらがってくる。ラベル自体をオフセットするのか? アテンションに未来を見せるのか? そして、コーディングエージェントは時系列というものが苦手なので、人間がちゃんと理解してテストを書かせてないとダメなのだ…… 普通につらい。

前回も言ったけどコーディングエージェントはとにかくテストを書きたがらない。いくら言ってもダメ。AGENTS で指定しても意味あるのは初回ぐらい。テストで動作を保証する発想が一切ない。ゴミスクリプトをチョチョッっと作って実行してはゴミ掃除もせず放置する。

2026年 01月 28日

WebAudio-Signal-Generator

むか〜し書いた WebAudio-Signal-Generatorをモダンにした。AngularJS 1.2 + Bootstrap 3 だったのを Vue3 + Vanilla CSS に。というかそもそも ScriptProcessorNode 使っててマジで古かった。

ブラウンノイズ こんな感じで設定をURLに反映するようにして、リンクを貼れるように。

まぁもうこんなの欲しいとおもったときにLLMに書かせりゃいいという気はするけど

2026年 01月 25日

機械に機械学習のモデルを設計してもらう(何もわからない)

主に Gemini を使って、LLM主体でモデルの設計と学習をなるべくさせてみるということに数日とりくんでた。

自分が知らないことをLLMにやらせてみようというのを時々やっている。

課題

モールス符号の音響受信をさせる。7年前にKerasを使って頑張って似たようなことをやった覚えがある。その情報は渡さずに「今こういうのやるならどういうモデルがいいかな」という相談からはじめた。

Gemini いわく「 CTC損失を用いたStreaming Conformer (CNN + Transformer)」がいいのではということだった。よくわからないが全く理解しないままこの方向ですすんでみることにする。(賢いあなたは、わからないことがあったらその時点で必ず解消しましょう)。

リアルタイム音声認識ということでこれを提案してきたのだろう……

できたもの

https://cho45.github.io/morse-decoder-2026/demo.html

一応計画の段階で「最終的にブラウザでリアルタイムにストリーム処理できること」は入れてたので、いろいろ問題はありつつも動くものはできた。

pytorch でモデルを作り、最終的に onnx に変換して onnx web で動いている (wasm)。WebGPU でも動きはするけど、なんかちゃんとやってないのでパフォーマンスがでない。

「SNRいくつまでデコードができる」みたいなことを言いたいが、SNRの定義がなんともいえない (信号のある瞬間の振幅基準) ので難しい。このSNRで-15dBぐらいまでギリデコードができる。もうちょっと頑張ってほしいけど……

モデル構造

こんな感じ。CNN + Transformer のことを Conformerというらしい。まんなかの4層がそれ。ただのモールス復号のモデルにしてはデカいはずだけど、もっと少ない状態だとまったくうまくいかなかった。

入力はスペクトログラムを特定ピーク中心で切り出したものになっている。つまりベースバンド波形ではなく画像として見てる。別途ピーク検出と切り出しを行ってからこのモデルに入れる想定。位相が不要だから画像として扱うほうがいいっぽい?

しかし疑問点

モデルの内容には、ほぼ口を出さずにやれるところまでやったけど、そもそもいくつか疑問があるので、今更訊いた。

なぜ入力にメルスペクトログラム?

(意���: 特定周波数付近を切り出して渡しているので、通常のスペクトログラムで十分だし実用上は楽なのでは?)

A:「メルスペクトログラムは、一般的な音声認識(ASR)モデルの標準的な入力形式であり、情報の圧縮とノイズ耐性に優れているため採用されています。狭帯域にクリップする場合、メル尺度による圧縮の恩恵は薄れるため、本アプリにおいては線形スペクトログラムの方が解像度を維持でき、より合理的である可能性が高いです。」

「リアルタイム音声認識」にひっぱられてあんまり考えずに決めてるな……

出力がCTCなのはなぜ?

Q: 「今回のモールス学習の場合、かなり厳密にラベルがつけられるはずですよね。そして実際そうしているし Signal Head として出力もしている。それでもなおCTCを使う理由は?」

A:「Signal Head による物理的な信号認識を補助としつつ、CTC を使うことで「文字」としての文脈や区切りを柔軟に扱い、デコード時の頑健性を高めるためです。」

Q:「今回の場合ラベルの柔軟性というよりは出力の簡略化の側面がつよい?」

A:「はい、その通りです。Signal Headのように「全フレームを分類」するのではなく、CTCを使うことで「重複除去とblank削除」という単純なルールだけで、複雑な後処理なしに最終的なテキストを直接得られるという、出力の簡略化と利便性の側面が非常に強いです。」

フーン……

出力に CTC 以外に Signal Logits と Boundary Logits がある。これらはCTC出力からだけの学習で一切上手くいかなかったため、苦肉の策として追加した出力になっている。(ここは自分の指示……)

  • Signal Logits: モールスの「短点」「長点」「符号間空白」「文字間空白」「単語間空白」「ブランク」を予想させる -- モールスの形状を強制的に学習させる
  • Boundary Logits: モールスにおいて、符号が確定する瞬間に1になる。つまり文字符号の後の空白1ユニットの直後に発火する -- CTCが発火すべきポイントを間接的に学習させる意図 --- これそのままCTCへの入力に使ったら楽や~んと思ったけど、それだとCTCがチートして、Boundary Logits が高かったら適当に文字出すようになってしまって一敗

これが正しいことかはわからないが、Gemini 的には学習を進めるうえで有効ですとはいっていた。まぁ結果としてうまくはいったけど、これのせいでモデルサイズが大きくなっている面がある気がする。

得られたもの

やはり自分で正解知らないことをボーっとやらせるのは非常に難しい。結局急がば回れになることが多い…… わかってるんだよ。わかってるけどわからないまま作りたいんだ!

機械学習の修正���スク投げると、いくら事前に指示をしていてもテストを書かないし実行してくれない。最終的には「平均的なエンジニアはテストを書かず本体のコードだけに集中している。そこから学んでいるので自分もそういう傾向にあるのだ」という開きなおりをされた。

2026年 01月 20日

このサイト開くと、便利情報として右上に IPv4 か IPv6 か表示するようにしてあるんだけど、ここに今繋いでる HTTP のバージョンも出すようにしてみた。

/.ip としてシンプルな情報を出すエンドポイントが前からあるのでここに足しただけ

      "/.ip":
        header.set: "Cache-Control: private"
        mruby.handler: |
          lambda do |env|
            ipv6 = env["REMOTE_ADDR"].include?(":")
            proto = {"HTTP/2" => "h2", "HTTP/3" => "h3"}.fetch(env["SERVER_PROTOCOL"], "")
            [200, {'content-type' => 'text/plain'}, [ "#{ipv6 ? 'IPv6' : 'IPv4'} #{proto}" ]]
          end


直接関係ないけど、h2o からproxy.reverse.url のバックエンドに直接こういう変数を入れたカスタムしたヘッダを送るのはあんまり簡単でなさそう? http_request 使うぐらい?

2026年 01月 19日

Chromeの開発者ツールでそのプロトコルが選ばれた理由が表示されることに気付いた

ずーーーと「なんでh3にならんのかな〜」ということをやってたけど、この開発者ツールのプロトコルの部分にホバーすると理由が出るということにようやく気付いた。ただ出ないこともある

HTTPS レコードで勝った場合

`Chrome` used a `HTTP/3` connection due to the `DNS record` indicating `HTTP/3` support, which won a race against establishing a connection using a different `HTTP` version.

HTTPSレコードちゃんと使ってるんだ。

ただレースして勝ったから使っていると書いてある。従来のTCP接続も同時に投げているようだ。

おそらく初回接続だけで、次からは Alt-Svc 優先になると思われる。(シークレットウィンドウで初回アクセスするとこれになる)

Alt-Svc ヘッダで勝った場合

`Chrome` used a `HTTP/3` connection induced by an `Alt-Svc` header without racing against establishing a connection using a different `HTTP` version.

2回目移行の接続。こっちはレースしないで決め打ちで繋ぐんだなあ

microdata → JSON-LD

JSON-LD 全然好きではないが、microdata でやっていた構造情報を JSON-LD にのせかえてみた。どっちにしろたいした情報はないが……

microdata だと display: none な要素を使ってメタデータを埋めこむ必要がしばしばあって、これがDOMの構成要素を増やす要因になっていて気になっていた。

最近のChromeだとDOM要素多いとパフォーマンス悪化するぞと脅してくる。

HTMLとしては非常にシンプルになって気持ちよいけど、やっぱ microdata のほうが好きだなあ。

2026年 01月 15日

プログラミング言語はコミュニケーションのためのもの

プログラムろくに書いたことない人はプログラミング言語をシンプルに誤解してて、機械語と区別がついていないのだろうと思う。

プログラミング言語はたまたま最終的に機械語に変換できるだけで、前提は人間同士(自分自身も含む)のコミュニケーション言語であるという理解がない。形式言語という形で意図を正確に伝えられるのがプログラミング言語であって、そうすると、たまたま実行可能にしやすいだけ。

LLMとのやりとりで自然言語を通してプログラミング言語を生成するのは、この意図が正確に反映されたかを、人間がレビューする必要性があるからにほかならない。

「ちゃんとできたか」を確認するのは最終的にはその要求をしている主体だ。一方で、人間は要求・要件を自然言語で100%正確に漏れなく曖昧さなく書くことはできない。なぜなら自然言語だから。やってみたいことを書き出したら矛盾することすらある。

プログラミングは、単なる実装(機械への命令)というより、曖昧な人間の意思を形式言語でミラーして具現化する行為であって、これはLLMを通じても別に変わらない。

とにかく本質的には人間は自分が欲しいものを、そもそも自分自身でも正確に理解��ていないところにある。「AIが無限に強くなれば言わなくてもやってくれる」という期待は、自分は自分が欲しいものを正確に理解しているという根本的な錯覚に基いている。

たとえAIが神になっても祈る側の願いが不定な以上は結果は定まら��、祈りと矛盾しないが意図しない結果がうまれる。この手の話は神話の時代からあり、上記の通り人間の埋め込みバグなので解決することはない。

だからこそ一度コードという形で曖昧さをなくし、テスト可能にし、自分の要求がなんだったのかを矛盾なく記述する必要がある。

ScreenToGif

Windows でスクリーンレコード気軽にとるなら ScreenToGif がよさげ?

名前からしてデフォルトでは anigif 出力なんだけど、WebM 形式にするとエンコーダーを選べて、普通に AVIF 出力ができる。録音はそもそもされないみたい。


macOS だと何がいいんだろうな。Kap なのかな。今度使ってみる。

簡易並列テストランナー

複数のテストを並列実行でマルチカラムで表示するやつをGeminiに書いてもらった。依存なし180行 で必要十分なのができて満足

人間が実行したときは途中経過を見せて、tty 経由じゃなければ最後まで無言でやってFAILしたものだけログをすべて出すというようにしてる。(Agentが実行するとき対策)