language : Japanese | English

ぼっち大好き

トップ 短編小説PNY 貯蓄率とFIREまでの年数 二次関数と虚数i コピックメイキング 絶対音感 Windowsのスリープ設定 昔のソフトウェア オープンソース Visual Studio C / C++ / MFC 備忘録 Visual Studio Community と Pro 備忘録 自作LLM 備忘録 自作Tokenizer 備忘録 自作パソコン 備忘録 日記 作者


自作Tokenizer 備忘録

1. はじめに

自作LLMの話の続きです。

llama2Tokenizerなどというのは、llama2でしか本当に使われてなくて、Transformerがllama2でもTokenizerは別物だったりすることがほとんどです。例えば、GPTNeoXTokenizerFastだったり、GPTNeoXTokenizerだったり、PreTrainedTokenizerFastが用いられています。自作LLM備忘録で書いたように、llama2Tokenizerでは、UTF-8で3バイト以上の文字になるのに、いったん2バイトの文字にMergeされる必要がありますが、3バイト中2バイトだけをTokenizer.jsonのMergeリストに記載するのは、テキストファイルとしてはだめだからです。

そこで、他のGPTQなどのTokenizerはどうしているのかというと、UTF-8文字の断片であってもテキストとしてファイル記述・あるいはコンソールに表現できるように、UTF-8の1バイトを2バイト文字で表す方法を採用しています。これが最近よく使われる、謎UTF-8エンコーディングです。変換規則はいろいろ解析したところ、下記のようになることがわかりました。ちな、pszSrcTextUTF8はUTF8ストリーム、pszDestTextはUTF-16又はUTF-32としており、後者は単純にUnicodeポイント(U+XXXX)を示しています。

		for (intptr_t iSrc = 0; iSrc < nUTF8Len && iDest < nDestTextLen - 1; iSrc++) {
			if (0x00 <= pszSrcTextUTF8[iSrc] && pszSrcTextUTF8[iSrc] <= 0x20) {
				pszDestText[iDest] = pszSrcTextUTF8[iSrc] + 0x0100; // 0x0100-0x0120
				iDest++;
			}
			else if (0x21 <= pszSrcTextUTF8[iSrc] && pszSrcTextUTF8[iSrc] <= 0x7F) {
				pszDestText[iDest] = pszSrcTextUTF8[iSrc];
				iDest++;
			}
			else if (0x80 <= pszSrcTextUTF8[iSrc] && pszSrcTextUTF8[iSrc] <= 0xA0) {
				pszDestText[iDest] = pszSrcTextUTF8[iSrc] + 0x00A2; // 0x0122-0x142
				iDest++;
			}
			else if (pszSrcTextUTF8[iSrc] == 0xAD) {
				pszDestText[iDest] = pszSrcTextUTF8[iSrc] + 0x0096; // 0x0143
				iDest++;
			}
			else if (0xA1 <= pszSrcTextUTF8[iSrc] && pszSrcTextUTF8[iSrc] <= 0xBF) {
				pszDestText[iDest] = pszSrcTextUTF8[iSrc];
				iDest++;
			}
			else if (0xC2 <= pszSrcTextUTF8[iSrc] && pszSrcTextUTF8[iSrc] <= 0xDF) {
				pszDestText[iDest] = pszSrcTextUTF8[iSrc];
				iDest++;
			}
			else if (0xE0 <= pszSrcTextUTF8[iSrc] && pszSrcTextUTF8[iSrc] <= 0xEF) {
				pszDestText[iDest] = pszSrcTextUTF8[iSrc];
				iDest++;
			}
			else if (0xF0 <= pszSrcTextUTF8[iSrc] && pszSrcTextUTF8[iSrc] <= 0xF4) {
				pszDestText[iDest] = pszSrcTextUTF8[iSrc];
				iDest++;
			}
			else {
				break;
			}
		}

この変換規則を使うと、次のようにエンコードされます。

Input:
ぼっちが好きな人は、なぜぼっちが好きなのですか。
Encode:
109416:ãģ¼ / 120240:ãģ£ãģ¡ / 29295:ãģĮ / 53901:好 / 112495:ãģįãģª / 116537:人ãģ¯ / 124563:ãĢģãģª / 112371:ãģľ / 109416:ãģ¼ / 120240:ãģ£ãģ¡ / 29295:ãģĮ / 53901:好 / 112495:ãģįãģª / 16144:ãģ® / 112130:ãģ§ãģĻãģĭ / 1811:ãĢĤ

0xADの場合だけ、なぜか+0x0096をして、0x0143としなければならないところが落とし穴みたいな感じ。0xADが特殊な文字だとは思えず、理由はわかりませんが、探せばあるのでしょう。具体的には、「魔法少女」の「魔」の字は%E9%AD%94なので、0xADのバイトを0x143にプリエンコードして、U+9B54となり、結局「éŃĶæ³ķ」と1文字が6バイトとなります。

ポストデコード時は逆の処理をすればよいですね。

2. 実際使ってみてわかること

これ使っていてわかることは、UTF-8バイトストリームを、文字境界で区切らず、UTF-8文字の途中で区切ってしまうことが結構あることに気づきます。それでも、これはとてもよく機能します。従来型の単語ベクトルや文字という概念はそこにはなく、ただのバイトストリームを圧縮して流しているようなものです。

つまり、マルチヘッドアテンションとは言いつつ、「これ」は「Apple」を指しているとかいう概念はもはやなくて、単にそれっぽく話すように重み付けをしているだけなのです。ここまでくるともはや、LLMという名称が正しいのか謎です。単にバイトストリームモデルであるならば、LBMですし、中に「言語」の概念は入っておらず、RoPEを除いてはどこも科学的でなく、むしろオカルト式推論機です。従来の単語ベクトルを用いた「言語モデル」をもつ科学式推論機より、単語や文字の概念がなく単にバイトストリームを用いたオカルト式推論機の方が、より人間らしく機能したというのは、驚きでもあります。

しかし、よく考えてみれば、人が言語を学習するのに言語モデルを学ぶ必要はなく、生まれた時から日本語のストリームを浴びることによって、単語や文字の概念がなくとも勝手に日本語を学びます。文字や単語の概念は、小学校以降の後付けの話で、言語構造をあらわすのに便利ですが、そもそもそのような構造化が必要ないのです。

このバイトストリームというのは何らかの規則性があればテキストでなくて波形でも良いわけで、実際それは機能しています。音声波形でもいいし、画像でも良いし、地震波形でも良いのです。

3. オカルト式地震予知機へ

科学は、これほど大量のリソースを投資してきたにもかかわらず、地震の予知(未来の地震の波形)についてあまりにも無力です。それは物理モデルが未解明なことが多すぎるのも原因ですが、観測網だけは大量に整えられました。

一方で、犬やナマズや深海魚は、精度が低くとも地震を予知することがあります。当然犬やナマズや深海魚は、物理モデルの話を知りませんが、大量のバイトストリーム(振動、匂い、磁気など)を浴びることによって、それらを可能にしています。つまり、オカルト式地震予知機は、科学地震予知機よりも実現性が高く、しかもそれが犬よりは精度がよいのであれば、あとは学習データの量とファインチューニングの問題となるわけです。断層がとか、そういうことは一切考える必要がなく、それはむしろノイズになります。

4. 「~のような気がする」の重要性

人々や動物は、よく科学的根拠がないのに「~のような気がする」ことがあります。それは大量のバイトストリームを浴びることによって得られた産物であり、無意識のうちにそれに基づいて行動しますが、現在ではオカルトの領域になります。それはハルシネーションがひどく、正答率が低いという問題も抱えています。ただ、ハルシネーションが低く、正答率が高いのであれば、科学とするのならば、科学とオカルトの境界はあいまいであるという気がしてきます。ちな、絶対音感はどっちですか……。そんなこと言っている時間があるならはやくオカルト式地震予知機を作ったらどうですか……。


(C)2000-2025 くず All rights reserved.