
オンデバイスAIで音楽の続きを書く
RollTabはiPhone / iPad向けの無料アプリで、MIDIキーボードの入力を受けて演奏の続きを生成します。Edwardssonは「GitHub Copilotのピアノ版」と表現しています。
RollTabの基本情報
1年と14回の実験で定まった設計
Edwardssonが「MIDIピアノを携帯に接続して、弾いた曲の続きをAIに補完させる」と思いついたのは、1年近く前でした。思いついてから14回の実験を重ね、ようやく記事にできる出来になったと本人は語っています。 モデル構造そのものはごく標準的で、RMSNorm・回転位置埋め込み(RoPE)・因果マスク付きのセルフアテンション・SwiGLU/MLPブロックを組み合わせた自己回帰モデルです。
目新しい構造は含まれていません。 大きく改善したのは音符の表し方を決め直したこと、学習データを徹底して整理したこと、DPOによる仕上げの学習を加えたことの3つでした。
"TL;DR: I trained a 125M-parameter transformer to autocomplete piano performances in real time (~108 notes/sec on an iPhone 15)."/"The biggest improvements came from finding the right MIDI representation, cleaning the training data aggressively, and adding DPO post-training."/"Almost a year ago, I started tinkering with an idea: connect my MIDI piano to my phone, play something, and have AI autocomplete the song for me."/"Think GitHub Copilot, but for piano."/"Fourteen experiments later, it is finally at a point where I am happy enough with it to write about."/"The architecture is essentially a fairly standard decoder-only transformer: RMSNorm, rotary positional embeddings, causal self-attention, SwiGLU/MLP blocks, and autoregressive generation."(TL;DR/導入/Model 節)— SimEdw's Blog より公式情報を見る →
オンデバイスAIの生成速度を左右した音符の表し方
効いたのはモデルの大きさではなく、MIDIをどうトークンに落とすかという表現の設計でした。 Edwardssonは表現の見直しを、データの整理やDPOと同じく大きな改善点にカウントしています。
素直な表現が遅くて不安定だった理由
最も単純な作り方は、MIDIのイベントごとにトークンを1つ作る表現。しかしピッチ(音の高さ)と強さを直接トークンに含めると語彙が急増します。ピッチ128種類・強さ128段階を素朴に組み合わせるだけで、音の開始と終了で最大16,512トークンになる計算です。
よく使われる改良として、文法を定めて1つの音を役割ごとに分ける表現があります。[NOTE_ON, PITCH, VELOCITY]のように分ければ語彙は小さくなり、生成のたびにあり得ない選択肢を除いておけば文法に反した出力がなくなるからです。
ところがEdwardssonのモデルでは、生成が途中でずれていきました。音の終わりを伝える合図を出し忘れて鍵盤が鳴りっぱなしになったり、いまどの音が鳴っているのか見失ったりする不具合で、リアルタイムに近い速さで実行する小さいモデルには致命的でした。
そこで試した[NOTE, PITCH, VELOCITY, DURATION]は音の長さを明示するのでずれませんでしたが、遅くなりました。1つの音を出すのに推論をおよそ4回回すことになり、使うことができる文脈もすぐ埋まってしまったためです。
"There are 128 MIDI pitches and 128 velocity values, so the naive combined note-on vocabulary has up to: 128 * 128 + 128 = 16,512 tokens just for note-on and note-off."/"A common improvement is to factor the representation with a grammar"/"You can enforce the grammar during generation by masking invalid next tokens."/"I tried note-on/note-off style representations, but my models tended to drift."/"They would forget to emit note-off, leave hanging notes, or lose track of active state."/"That was especially bad for my target: a small model running close to real time on a laptop or phone."/"This worked better musically, but it was slow."/"One musical note took roughly four autoregressive transformer steps."/"It also burns through the context window quickly."(How Do You Tokenize Music? 節)— SimEdw's Blog より公式情報を見る →
1トークン=1音符という決着
最終的に落ち着いた表現がNOTE(pitch, delta_onset, duration, velocity)です。時間を進める専用のイベントは無くし、無音は次の音符が持つ「直前の音が鳴った時刻から経過した時間」で表します。
和音は経過時間ゼロの音符をピッチ順に並べて表します。同じCメジャーの和音(ドミソ)をCEGと書く曲もEGCと書く曲もあるので、順序を定めておかなければ、同じ和音なのに書き方が異なるだけで不正解と見なされてしまいます。
実装上は1つの音符が5つのカテゴリ値(種別・ピッチ・経過・長さ・強さ)を持ち、それぞれの埋め込みを合算して1つのトークンにします。出力側はカテゴリ値ごとに別の予測器を置き、後ろの予測器が先に定まった値を見られるよう、あいだに小さなデコーダを挟みます。
重いTransformer本体は、音符ごとに1回しか実行されません。 回数が少ないぶん毎秒108音という速さが出ます。
ペダルにも専用のイベントを作らず、前処理の段階で各音の長さへ反映させます。ペダルを踏んだまま鍵盤を離した音は、ペダルを上げた時点まで延ばす処理にしています。 踏み方の細かな情報は失われますが、モデルが予測する対象は、ピッチ・開始・長さ・強さの4つだけになります。
"The representation I eventually settled on was: NOTE(pitch, delta_onset, duration, velocity) There is no separate TIME_SHIFT event in the final version."/"Silence is represented by delta_onset on the next note: the time since the previous note onset."/"We sort by pitch so that during training we don't get penalized when one song encoded a C-major chord as CEG and another as EGC."/"Each field gets its own embedding."/"There is a small nested decoder between the fields, so later fields can condition on earlier predicted fields."/"But the expensive transformer backbone runs only once per note, not once per field."/"Instead, sustain is baked into note duration during preprocessing."/"If the key is released while the sustain pedal is down, the note is extended to the pedal-up time."/"This loses the explicit pedal gesture, but it makes the modeling problem much simpler: the model only has to predict pitch, onset, duration, and velocity."(The final representation 節/Sustain Pedal 節)— SimEdw's Blog より公式情報を見る →
小さいモデルはデータを増やすより整える
学習データはパブリックドメインの古いクラシックが中心で、最終的に数十万件のMIDIファイル・およそ3億の音符イベントになりました。ただ、Edwardssonが手をかけたのは量を増やすことではなく、混ざりものを落とすことでした。
データを5倍に増やしても改善なし
Edwardssonはデータをおよそ5倍まで増やす実験もしていますが、モデルはかえって悪くなりました。 量を追加するより整理と選別のほうが効果があった、と総括しています。
選別には複数の条件を使っています。指紋とは曲ごとに計算しておく短い識別用の値のこと。ピアノ中心の素材だけを選んでトラックが極端に複雑なファイルを減らし、音の詰まり具合や高さ・時間の偏りで選別したうえで、指紋を照合して重複を落とします。
この指紋は全体の移調や一律のテンポ変更では変わりません。したがって移調やテンポ違いの別バージョンも同じ曲と見なして、重複として落とすことができます。
本番でモデルが受け取る入力は整ったMIDIファイルではなくその場で弾いた演奏なので、学習データにわざと揺らぎを加えて増やす処理(データ拡張)も採用しています。 中身は全体の移調・テンポの一律変更・長さと強さの揺らぎに加え、弾き出しとして与える数音から音符をわざと落とす処理です。
Edwardssonによると、本人はピアノが下手なので弾くと音が早まったり遅れたり強すぎたりするとのこと。データ拡張はタイミングや強さが乱れた演奏に備えるための処理です。
"I searched through a lot of publicly available datasets and collections, focusing mostly on older classical music in the public domain."/"The final dataset contained a few hundred thousand MIDI files, representing roughly 300 million note events."/"deduplicated by fingerprints that ignore global transposition and uniform tempo changes"/"I tried scaling the dataset to roughly 5x the size, hoping it would improve performance, but the resulting models were worse."/"Cleaning and selecting the data mattered more than simply adding more of it."/"Augmentation was important because the live input is not a pristine MIDI file."/"It is me playing piano, badly enough that notes might be slightly early, late, too hard, etc."(Dataset 節/Augmentation 節)— SimEdw's Blog より公式情報を見る →
検証損失が良くても演奏が良いとは限らない
音楽の続きに唯一の正解はありません。検証用に取り分けた曲が正解として示せる「次の1音」は1つだけですが、音楽として成立する続きはほかにもたくさんあります。 交差エントロピー(予測と正解のずれを表す学習の指標)は音楽のルールを覚えさせるには役立っても、続き全体の出来を測る目安にはなりません。
スケジュールドサンプリング(学習中にモデルが出した予測を入力へ混ぜる手法)を各要素の間に50%入れた設定と、入れない設定も比較しています。検証損失は入れた設定のほうが悪かった(2.9998対2.9495)のに、Geminiによる聞き比べでは入れた設定が64.3%対35.7%で勝ちました。
"Still, the training objective has an important limitation: music continuation does not have a single correct answer."/"A held-out song only gives the model one "correct" next note, even though there are often many continuations that would work musically."/"Cross-entropy is useful for learning the mechanics of music, but not a great proxy for how good a full continuation sounds."/"So during training I sometimes fed the model its own predicted pitch instead."/"Validation loss ↓ scheduled 50% 2.9998 without scheduled 2.9495 Gemini preference ↑ scheduled 50% 64.3% without scheduled 35.7%"/"Scheduled sampling hurt validation loss, but improved rollout quality."(Training 節/Scheduled Sampling 節)— SimEdw's Blog より公式情報を見る →
DPOが最も効いた仕上げの学習
事前学習のあとで最も差が出た工程が、DPO(二つある出力のうち好ましいほうを教えて調整する仕上げの学習)です。たまに良い続きを出す程度だったモデルが、かなり安定して出せるようになったとEdwardssonは書いています。
評価をAIに任せる工夫
自動指標(繰り返しの検出・ピッチのエントロピー・音の密度など)は明らかな失敗を拾えても、最良のモデルを選ぶには足りませんでした。そのため、Gemini 3.5 Flashに二つを聞き比べさせる対戦評価へ切り替えています。
点数を直接付けさせると判定がぶれる一方、「AとBのどちらが良いか」というプロンプトを入力すると安定しました。 どちらを先に聞かせるかで判定が偏らないよう、すべての比較で順番を入れ替えた両方向の判定を取っています。 Edwardssonが200曲で試したところ、AとBを入れ替えても70%は同じ判定でした。
評価軸も途中で二つに分けています。当初はGeminiの判定が続きの部分だけの聞こえ方に偏り、直前の演奏から自然につながっているかがほとんど反映されていませんでした。 そこで直前の演奏とのつながりを見る点数と、その部分だけを聞いたときの音楽としての良さを見る点数を別々に付け、DPOではつながりのほうを主な判断材料にしています。
"These metrics were useful for catching obvious failures, but they were not good enough to select the best model."/"Eventually I used Gemini 3.5 Flash for pairwise evaluation."/"Asking it to give a single absolute score was inconsistent."/"Asking instead, "given A and B, which continuation is better?" worked much better, especially when I mirrored every comparison to reduce position bias."/"In one test over 200 songs, Gemini gave the same preference 70% of the time after reversing A and B."/"Initially, Gemini overindexed on how good a continuation sounded in isolation, rather than how well it followed from the prompt."/"a continuation score, measuring how well the output follows from the prompt, and a sounds-good score, measuring its musical quality in isolation."/"I used the continuation score as the primary signal for DPO."(Evaluation 節)— SimEdw's Blog より公式情報を見る →
選好率24.55%から69.05%へ
選好率はAIに二つを聞き比べさせたときに良いほうとして選ばれた割合です。EdwardssonはDPOの設定を変えながら、この割合がどこまで上昇するかを調べました。
| 設定 | 選好率 |
|---|---|
| 事前学習のみ | 24.55% |
| β = 0.01 | 61.08% |
| β = 0.03 | 57.14% |
| β = 0.10 | 38.10% |
| 合議データセット(β = 0.03) | 69.05% |
βは元のモデルからどれだけ離れてよいかを定める設定で、0.10まで上げると強すぎてかえって悪化しています。 一番良かったのは、β=0.03に合議データセットを組み合わせた場合です。
合議データセットとは、Geminiに何度判定させても同じ結果になったペアだけを残したデータです。 モデルは事前学習の時点で音楽の仕組みをひととおり掴んでいて、足りなかったのは「良い続きとは何か」だけだったとEdwardssonは推測しています。
"DPO made the biggest difference after pretraining."/"It took the model from occasionally producing a good continuation to doing it much more reliably."/"After DPO, more than 69% of continuations were preferred over the base model in my pairwise evaluation."/"The β value controls how strongly DPO penalizes moving away from the base model."/"In my sweep, β=0.01 and β=0.03 improved the model, while β=0.10 pushed too hard and made it worse."/"I also tried a "consensus" dataset: instead of trusting every noisy preference judgment, I only kept preference pairs where the evaluator agreed consistently."/"pretrained base 24.55% β = 0.01 61.08% β = 0.03 57.14% β = 0.10 38.10% consensus(β = 0.03) 69.05%"/"My gut feeling is that the base model had already learned a reasonable mental model of music, just not what makes a good continuation."(DPO 節)— SimEdw's Blog より公式情報を見る →
デバイスへの組み込み工程と残った課題
最後はアプリへ組み込む工程の話です。その工程では、学習したモデルをiPhoneで動作させるために、Core ML形式への書き出しと文脈の処理にもう一手間かかります。
Core MLの量子化と直近384音の文脈
PyTorchのモデルをCore MLへ書き出し、重みをINT8へ量子化(数値の細かさを落として軽くする処理)しています。 ただし初回起動は、Appleのランタイムがデバイスに合わせて最適化する分だけ遅くなります。
学習時の文脈は最大512音までなので、長いセッションを支えられるよう上限に近づくたびに直近384音だけを残して組み直します。KVキャッシュは作り直しになりますが、モデルが十分速いので大きな問題にはなっていません。
RoPEなら理屈の上ではもっと綺麗なやり方もあります。ただCore MLがQ・K・Vを直接触らせてくれないため見送っています。
"I exported the PyTorch model to Core ML and quantized the weights to INT8."/"The first launch is still annoyingly slow while Apple's runtime optimizes the model for the available hardware."/"The model was only trained with contexts of up to 512 notes, but I wanted to support longer sessions."/"Whenever the context gets close to the limit, I keep the most recent 384 notes, rebuild the context from those, and continue from there."/"This means rebuilding the KV cache, but the model is fast enough that it hasn't been a major problem."/"Unfortunately Core ML does not expose Q, K, and V directly."(Packaging it 節)— SimEdw's Blog より公式情報を見る →
うまくいかなかったことも公開されている
効いた工夫・効かなかった工夫
| 効いた | 効かなかった |
|---|---|
| 1音を1トークンにまとめる表現 | 音の開始/終了を分ける表現(生成がずれる) |
| データの重複排除と品質フィルタ | データを約5倍へ増やすこと |
| DPO(合議データセット・β=0.03) | Mirostat(繰り返しは減少するが一貫性を失う) |
| スケジュールドサンプリング50% | 局所的な補助損失の追加 |
| 対戦形式のAI評価 | 点数を直接付けさせるAI評価 |
同じ部分を繰り返してしまうループは、モデルを大きくしても勝手に消えてはくれなかったと本人が書いています。 データを広げてもノイズが多ければ結果はむしろ悪化し、モデルが出した予測を教師にして学習し直す手法も効きませんでした。
未解決の課題もEdwardsson本人が認めていて、ループがときどき発生することと短いプロンプトが苦手なことに加え、修正したいところがまだたくさんあるとしています。 実測では、弾き出しに与える音が4音だと最も難しく、8音でましになり、16〜32音あればかなり安定しました。
デバイスで完結させるときに最初に削るべきなのは、モデルの大きさではなく推論の回数です。 1音につきTransformerを1回しか回さない表現へ切り替えたことで毎秒108音が出ており、125Mを64Mへ落とす検討はその後の話になっています。 ご自身のパソコンでモデルを実行する実例はM2 MacでのローカルLLMコーディング、最先端モデルの約75%にをご覧ください。
"Four-note prompts were the hardest: there simply was not much musical context to work with."/"Eight notes worked better, while 16–32 note prompts were substantially more reliable because the model had enough structure to infer what was happening."/"Note-on/note-off drifted too much for small real-time models."/"Grammar-masked token streams were valid but slow."/"Broader data made results worse when the data was noisy."/"Bigger models helped, but did not magically solve loops."/"Mirostat reduced repetition but often made outputs incoherent."/"Extra local auxiliary losses made training slower without clear listening wins."/"Absolute scalar Gemini ratings were worse than pairwise judging."/"Validation loss alone missed important differences in rollout quality."/"Born-again networks (retraining a model on its own soft predictions) didn't improve quality here."/"It is still far from perfect."/"It loops occasionally, short prompts are difficult, and there is plenty I want to improve."(Evaluation 節/What Did Not Work 節/Conclusion 節)— SimEdw's Blog より公式情報を見る →
FAQ
- RollTabはネットワークなしで動作しますか?
- 動作します。PyTorchのモデルをCore MLへ書き出し、重みをINT8に量子化してデバイス内だけで動作させているとのこと。ただし初回起動は、Appleのランタイムがデバイスに合わせて最適化する分だけ遅くなります。
- どれくらいの速さで音を出力できるのですか?
- iPhone 15で毎秒およそ108音です。人が生演奏で必要とする速度をはるかに超えているとのこと。この速度は1音を1トークンにまとめた表現の設計から来ています。
- 学習データは多いほど良かったのですか?
- 多いほど良いとは限りませんでした。数十万件のMIDIファイル・約3億の音符イベントで学習していて、データ量をおよそ5倍に増やすとかえって悪化したとのこと。Edwardssonは、量を増やすよりも整理と選別のほうが効果があったと総括しています。
- DPOでどれくらい良くなったのですか?
- 対戦形式の評価では選好される割合が24.55%から69.05%へ上昇しました。評価を担当するGeminiに何度判定させても同じ結果になったペアだけを残す合議データセットと、β=0.03の組み合わせが最良だったとのこと。βを0.10まで上げると強すぎてかえって悪化しています。
- モデルは大きいほど良かったのですか?
- 差はありましたが決定的ではありません。33M・64M・125Mの3サイズを学習し、64Mは33Mにほぼ常に勝った一方で125Mの優位は大きくなかったとのこと。アプリの容量と遅延を減らす目的で、Edwardssonは現在64Mを125Mの品質に近づけようとしています。



