2024年初頭のLLMとプログラミング
まず最初にお伝えしておきますが、この記事はLLMを振り返るためのものではありません。2023年が人工知能にとって特別な一年だったことは明らかで、それを改めて語ることにあまり意味はないでしょう。むしろこの記事は、一人のプログラマーとしての証言として書きたいと思います。ChatGPTの登場以来、そしてその後はローカルで動作するLLMも使うようになってから、私はこの新しい技術を徹底的に活用してきました。目的はコードを書くスピードを上げることですが、それだけではありません。労力をかける価値のないプログラミングの側面に、精神的なエネルギーを浪費したくないという意図もあります。知的におもしろくもない細かな仕様についてドキュメントを探して費やす膨大な時間。しばしば理由もなく過度に複雑化されたAPIを学ぶための努力。数時間で捨てることになる、その場しのぎのプログラムを書くこと。そういったことは、もうやりたくないのです。とりわけ今は、Googleがスパムの海と化し、その中からわずかな有用な情報を探し出さねばならない状況ですから。
一方で、私は決してプログラミングの初心者ではありません。自力でコードを書くことができますし、実際にそうすることもよくあります。次第に、LLMを使って高水準なコードを書くことが増えてきました。とりわけPythonではそうですが、Cではあまり使いません。LLMに関する個人的な経験で印象的なのは、いつ使えばよく、いつ使えばむしろ足を引っ張るのかを正確に見極められるようになったことです。また、LLMはWikipediaやYouTubeに散在する動画講座に少し似ているとも感じています。意志と能力、そして規律のある人を助けますが、すでに遅れを取っている人にとっては利益は限定的です。少なくとも当初は、すでに優位に立つ人だけが恩恵を受けることになるのではないかと懸念しています。
では、一つひとつ順を追って見ていきましょう。
全知か、オウムか
この機械学習における新たな革新と進歩の波において、最も懸念すべき現象の一つは、AIの専門家たちが自らの知識の限界を認める能力に乏しいことです。ヒトはニューラルネットワークを発明し、さらに決定的なこととして、ニューラルネットワークのパラメータを自動で最適化するアルゴリズムを発明しました。ハードウェアはますます大規模なモデルを学習できるようになり、処理すべきデータに関する統計的知識(事前分布)と、逐次近似のための膨大な試行錯誤を通じて、他よりもうまく機能するアーキテクチャが発見されてきました。しかし結局のところ、ニューラルネットワークは依然として極めて不透明なままです。
LLMのそうした創発的な能力の一部を説明できないという事態を前にすれば、科学者にはもっと慎重な姿勢が期待されてもよさそうなものです。しかし実際には、多くの人がLLMをひどく過小評価し、結局のところ多少進歩したマルコフ連鎖にすぎず、せいぜい学習データで見たものを極めて限定的に言い換えて吐き出す程度のことしかできないと主張しました。やがて、この「オウム」という見方は証拠を前にしてほぼ全面的に撤回されることになりました。
同時に、熱狂する多くの人々はLLMに現実には存在しない超自然的な力を付与しました。残念ながら、LLMにできるのはせいぜい学習時に見たデータが張る空間の中で補間することまでです。とはいえ、それだけでも十分すごいことです。実際には、その補間能力にも限りがあります(それでも驚異的で、予想外のものでもあります)。もし今日の最大のLLMが、見てきたすべてのコードが囲む空間の中で連続的に補間できたら、どんなに素晴らしいでしょう。真に新しいものを生み出せなかったとしても、プログラマーの99%を置き換えることができるはずです。現実は、いつものようにもっと控えめです。LLMは確かに、そのままの形では見たことのないプログラムを書くことができ、学習データの中に一定の頻度で現れた異なるアイデアを混ぜ合わせる一定の能力を示します。同時に、この能力には現時点では深い限界があることも明らかで、繊細な推論が求められる場面ではLLMは壊滅的に失敗します。それでもLLMは、AIの黎明から今日に至るまでの最大の成果であることに変わりありません。これは否定しがたいことのように思えます。
愚かだが、物知り
たしかにLLMにできる推論はせいぜい初歩的なもので、しばしば不正確で、存在しない事実についての幻覚(ハルシネーション)が散りばめられていることも少なくありません。しかし、彼らは膨大な知識を持っています。プログラミングの分野でも、質の高いデータが利用可能な他の分野でも、LLMは多くのことを知っている愚かな博識家のようなものです。そんな相手とペアプログラミングをするのは悲惨でしょう(私にとっては、そもそもペアプログラミング自体が悲惨なのですが)。的外れなアイデアを出され、自分の考えを通すために絶えず戦わなければなりません。しかし、この博識な愚か者が自由に使え、尋ねたことすべてに答えてくれるとなれば話は変わります。現在のLLMが私たちを知識の道の先へ連れて行ってくれることはありませんが、あまり詳しくないテーマに取り組む際には、まったくの無知の状態から、自力で先に進めるだけの知識がある状態まで引き上げてくれることがよくあります。
プログラミングの分野に限って言えば、この能力は20年から30年前ならほとんど関心を集めなかったかもしれません。当時は二つほどのプログラミング言語と古典的なアルゴリズム、そして十個ほどの基本的なライブラリを知っていれば十分でした。残りは自分自身で補うものでした。知性、専門性、設計力です。それらがあれば、あなたはエキスパートプログラマーであり、ほぼ何でもできる存在でした。やがて、フレームワークやプログラミング言語、あらゆる種類のライブラリが爆発的に増加するのを私たちは目にしてきました。しばしばまったく不必要で正当化できない複雑さの爆発ですが、現実は現実としてそこにあります。そして、そのような状況においては何でも知っている愚か者は貴重な味方となるのです。
一例を挙げましょう。私は機械学習の実験を少なくとも一年間Kerasで進めてきました。その後、さまざまな理由からPyTorchに切り替えました。embeddingや残差ネットワーク(residual network)が何であるかはすでに知っていましたが、PyTorchのドキュメントを一から順に学ぶ気にはなりませんでした(KerasはChatGPTが存在しない時代に学んだので、そうやって習得したのですが)。LLMがあれば、Torchを使ったPythonコードを書くのはとても簡単でした。組み立てたいモデルについて明確なイメージを持ち、正しい質問を投げかけるだけでよかったのです。
具体例を見てみましょう
ここで言っているのは、「クラスXのYをするメソッドは何?」といった簡単なことではありません。もしそれだけなら、LLMに懐疑的な人々に同意したくなるかもしれません。より高性能なモデルができることは、はるかに精緻です。数年前なら、まさに魔法としか思えなかったでしょう。私はGPT-4にこう伝えることができます。いいかい、これがPyTorchで実装したニューラルネットワークのモデルだ。バッチはこうなっている。バッチを出力する関数がニューラルネットワークの入力と互換になるようにテンソルをリサイズしたいし、こういう形で表現したい。リシェイプに必要なコードを示してくれないか、と。GPT-4がコードを書き、私がしたことはPythonのCLIでテンソルが本当に自分に必要な次元になっているか、データのレイアウトが正しいかをテストすることだけでした。
別の例を挙げましょう。少し前、ESP32ベースのデバイス向けにBLEクライアントを実装する必要がありました。調べた結果、クロスプラットフォームのBluetoothプログラミング用バインディングはどれも多かれ少なかれ使い物にならないことがわかりました。解決策はシンプルで、macOSのネイティブAPIを使ってObjective-Cでコードを書くことでした。そこで私は同時に二つの問題に直面することになりました。一つは、Objective-Cの煩雑なBLE APIを学ぶこと。私から見るとナンセンスなパターンに満ちており(私はミニマリストで、その種のAPIは私が「良い設計」と考えるものの対極にあります)、もう一つはObjective-Cでのプログラミング自体を思い出すことでした。最後にObjective-Cでプログラムを書いたのは十年前のことで、イベントループやメモリ管理などの詳細をすっかり忘れていました。
最終的な成果がこちらのコードです。決して美しいとは言えませんが、やるべきことはきちんとこなします。極めて短時間で書き上げました。そうでなければ不可能だったでしょう。
https://github.com/antirez/freakwan/blob/main/osx-bte-cli/SerialBTE.m
コードは、やりたいのにやり方がよくわからずうまく動かなかった部分をChatGPTにコピー&ペーストし、LLMに何が問題でどう解決すべきかを説明してもらうことで、ほぼ書き上げました。確かにLLMがそのコードの多くを書いたわけではありませんが、執筆を大幅に加速させたこともまた事実です。ChatGPTなしでできたかといえば、もちろんできたでしょう。しかし、もっと時間がかかったという事実が最も興味深いわけではありません。本当のところ、割に合わないのでそもそも試みすらしなかっただろうということです。この点は極めて重要です。私のプロジェクトにとって副次的なそのプログラムを書くための労力と利益の比率は、見合わないものだったのです。さらに、この作業はプログラム自体よりも有用な副次的効果をもたらしました。そのプロジェクトのために、私は自作の行編集ライブラリの一つであるlinenoiseを、多重化(multiplexing)に対応するよう改良したのです。
もう一つの例は、コードを書くことよりもデータの解釈に関するものです。オンラインで見つけた畳み込みニューラルネットワーク(convolutional neural network)を使ってPythonスクリプトを組み立てたいと思ったのですが、ドキュメントがかなり不足していました。このネットワークはONNX形式だったのが幸いで、入出力のリストとそれぞれに割り当てられた名前を簡単に抽出できました。このconvnetについて私が知っていたのは、画像内の特定の特徴を検出するということだけでした。入力画像の形式やサイズはわからず、とりわけネットワークの出力は想像していたよりもはるかに複雑でした(二値分類器だと思っていました。観測した画像は正常か、それとも問題があるか、出力は二つだろうと。ところが数百もあったのです)。私はまず、ONNXネットワークのメタデータ出力をChatGPTにコピー&ペーストしました。そして、このネットワークについてわずかに知っていることをアシスタントに説明します。ChatGPTは入力がどのように構成されているかを推測し、出力はおそらく潜在的な欠陥に対応する画像領域を示す正規化されたボックスであり、別の出力がそれらの欠陥の尤度を示しているのだろうと仮説を立てました。数分間のやり取りの後、ネットワークの推論が可能なPythonスクリプトと、元の画像を入力に適したテンソルに変換するのに必要なコードなどが手に入りました。そのセッションで印象的だったのは、テスト画像に対する生の出力値(基本的にはlogits)を観察したことでChatGPTが最終的にネットワークの動作を「理解」したことでした。一連の浮動小数点数が、出力の正確な詳細や正規化、ボックスが中心基準なのか左上隅基準なのかといったことを特定するための文脈を提供したのです。
使い捨てのプログラム
先ほど語ったような事例を、数十件でも記録することはできます。しかし、それはあまり意味がありません。多少の違いはあれ、同じ物語の繰り返しにすぎないからです。私には問題があり、LLMがでたらめを言っていないか*自分で検証できる*何かを素早く知る必要があります。そうした場合、私は知識への欲求を加速させるためにLLMを使うのです。
しかし、LLMにすべてのコードを書かせる場合もあります。たとえば、多少なりとも使い捨てのプログラムを書く必要があるときです。たとえば次のようなものです。
https://github.com/antirez/simple-language-model/blob/main/plot.py
小さなニューラルネットワークの学習中に損失曲線を可視化する必要がありました。学習中にPyTorchのプログラムが出力するCSVファイルの形式をGPT-4に見せ、コマンドラインで複数のCSVファイルを指定した場合には、同じ実験の学習損失と検証損失の曲線ではなく、異なる実験間での検証損失の曲線を比較するようにしてほしいと頼みました。上記がGPT-4によって生成された結果です。合計三十秒でできました。
同様に、AirBnBのCSVレポートを読み込み、私のアパートを月と年ごとに集計するプログラムが必要でした。さらに、清掃費用と予約ごとの宿泊数を考慮して、年間の月ごとの平均賃料を統計するものです。このプログラムは私にとって極めて有用です。同時に、これを書くのは退屈の極みで、面白い要素はまったくありません。そこでCSVファイルの一部を抜き出してGPT-4にコピー&ペーストし、解決すべき問題をLLMに伝えました。プログラムは一発で動きました。以下に全文をお見せします。
import pandas as pd
pd.set_option('display.max_rows', None)
df = pd.read_csv('listings.csv')
reservations = df[df['Type'] == 'Reservation']
reservations['Start Date'] = pd.to_datetime(reservations['Start Date'])
reservations['Year'] = reservations['Start Date'].dt.year
reservations['Month'] = reservations['Start Date'].dt.month
reservations['Nightly Rate'] = (reservations['Amount'] - reservations['Cleaning Fee']) / reservations['Nights']
all_listings = reservations['Listing'].unique()
all_years = reservations['Year'].unique()
all_months = range(1, 13)
index = pd.MultiIndex.from_product([all_listings, all_years, all_months], names=['Listing', 'Year', 'Month'])
all_data = pd.DataFrame(index=index).reset_index()
merged_data = pd.merge(all_data, reservations, on=['Listing', 'Year', 'Month'], how='left')
average_nightly_rates = merged_data.groupby(['Listing', 'Year', 'Month'])['Nightly Rate'].mean().fillna(0)
print(average_nightly_rates)散在して無秩序なデータを正確にどうグループ化するかを理解するには、簡単な推論が必要です。これはLLMが見た言語に近いものを吐き出すだけで解ける問題ではありません。GPT-4は学習中に類似のプログラムを確かに見てきたでしょうが、私が求めた通りの方法で、しかもその特定の形式のCSVファイルにおいてグループ化しなければならないプログラムは、ほぼ確実に見たことがないはずです。これは(私見では)LLMが学習データ中の異なるプログラムが張る空間の中で、ある程度補間できることを示しています。
この種のプログラムを書くことは、私の時間を賢明に使っているとは言えないと思います。LLMが代わりにやってくれるという事実は、私のコード生産能力をかなり直接的に加速させてくれます。重要なことに集中できるのです。
うまくいかないとき:システムプログラミング
LLMとプログラミングでの成功にもかかわらず、Cでプログラムを書くときには、LLMをより便利なドキュメントの形としてしか使わないことがほとんどです。私はシステムプログラミングの専門家ですが、その文脈ではLLMが複雑な推論をできて初めて役に立ちます。しかし、そのシナリオではほぼ常に失敗するのです。
まずは今回の実験のプロンプトから始めましょう。
Bloomフィルタの、ちゃんと動く小規模で良質なC実装の要件を概説してください。ハッシュ関数の重要な側面に焦点を当ててください。次に、それを良質なCで書いてください。また、例としての実装は十万個の要素を格納できるサイズで、誤検出率が最大5%になるようにしてください。追加される要素はヌル終端文字列です。
出力は次の通りです。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#define N 100000 // Number of elements
#define P 0.05 // False positive probability
#define M (int)(-(N * log(P)) / (log(2) * log(2))) // Size of bit array
#define K (int)(M / N * log(2)) // Number of hash functions
unsigned char bitArray[M / 8 + 1];
void setBit(int n) {
bitArray[n / 8] |= 1 << (n % 8);
}
int getBit(int n) {
return bitArray[n / 8] & (1 << (n % 8));
}
// Basic hash functions (for demonstration purposes)
int hash1(char *str) {
int hash = 5381;
int c;
while ((c = *str++))
hash = ((hash << 5) + hash) + c;
return abs(hash % M);
}
int hash2(char *str) {
unsigned long hash = 0;
int c;
while ((c = *str++))
hash = c + (hash << 6) + (hash << 16) - hash;
return abs(hash % M);
}
void add(char *str) {
setBit(hash1(str));
setBit(hash2(str));
// Add more hash functions if needed
}
int is_in(char *str) {
return getBit(hash1(str)) && getBit(hash2(str));
// Check more hash functions if used
}
int main() {
char *testStr = "testString";
add(testStr);
if (is_in(testStr)) {
printf("%s is probably in the set.\n", testStr);
} else {
printf("%s is definitely not in the set.\n", testStr);
}
return 0;
}GPT-4が生成した実装は、決して素晴らしいものではありません。Bloomフィルタはかなり普及しており、そんなに珍しいデータ構造でもありません。しかし、まともなBloomフィルタを書くには、より高い抽象化能力が求められることは明らかです。たとえば、同じ文字列をN回効果的にハッシュし、そのハッシュが十分に無相関であることを確実にするという問題があります。
しかし、GPT-4にハッシュ関数を修正してN個の無相関な出力を生成するように明示的に頼むと、提案される解決策はかなりまともになります。
unsigned int hash(const char* str, unsigned int hash_id) {
unsigned int hash = 5381;
int c;
while ((c = *str++)) {
hash = ((hash << 5) + hash) + c + hash_id;
}
return hash % BF_SIZE;
}もし自力でこのアイデアを思いついていたなら、単一のハッシュ関数を使って一度にK個のビットを立てる形で、Bloomフィルタを違った書き方にしていたはずです。
GPT-4は、適切でより汎用的なハッシュ関数を書くというタスクを単独では実行できました。しかしBloomフィルタを書くというより大きなプロジェクトの中では、十分な推論能力を示すことに失敗し、代わりに二つの異なるが似たようなハッシュ関数を吐き出しました。これは、出来の悪いBloomフィルタ解説記事の例として出てきそうなものです。
大規模言語モデルの現在の弱い推論能力に、このテーマに関する情報源の不足の可能性、さらには質の低い情報源が多数存在する可能性が相まって、このような貧弱な結果を招いています。しかし、これは孤立した事例ではありません。アルゴリズムやシステムプログラミングの領域でLLMを使おうとした数少ない機会でも、結果は極めて貧弱でした。求められる推論能力が、LLMがPythonプログラミングの文脈で示すように見える能力を上回らない場合でさえそうです。
同時に、GPT-4は(別のセッションで)自身が生成した関数を逆コンパイルし、その目的を正確に理解することもできます。
https://chat.openai.com/share/f54b64d7-d19a-4be4-8f10-08553aa812f1
したがって、システムプログラミングの文脈では使い道がないと言うのは、明らかに不正確です。
興味深く、ほぼ予想通りなことですが、こうした場合、小規模モデルと大規模モデルの差は確かに大きいものです。二つのローカルモデルがどうなるか見てみましょう。
たとえば、MistralのMoEであるMixtralは、同じ質問に対して次のように答えました。
unsigned int hash(const char* str, unsigned int hash_id) {
unsigned int hash = 5381;
int c;
while ((c = *str++)) {
hash = ((hash << 5) + hash) + c;
}
return hash + hash_id;
}明らかに、最後に`hash_id`を足すだけというのは極めて貧弱な解決策です。Mixtralは多くの用途で優れたモデルですが、もともと弱いLLMの推論能力に関しては、これまでのところ原則は確かに「大きいほど良い」です。
これはローカルLLMであるdeepseek-coderとのチャットで、より高い精度でモデルを使うだけのRAMがないため4ビットに量子化しています。それでも340億パラメータという規模で、この件に関する推論能力はかなり高度に見えます。チャットログは次の通りです(llama.cppで実行)。
同じデータに対してN個の異なるハッシュを出力すべきハッシュ関数があります。しかし、hash_idの値を変えても生成されるハッシュの分布が非常に悪いように見えます。
unsigned int hash(const char* str, unsigned int hash_id) { unsigned int hash = 5381; int c; while ((c = *str++)) { hash = ((hash << 5) + hash) + c; } return hash + hash_id; }もしかして使っているソルティング戦略が貧弱なのでしょうか? どう変えればよいでしょうか?
[Deep-seek coderの返答]
あなたの実装で考えられる問題の一つは、最後に`hash_id`を単純に足していることです。これは分布の悪化を招く可能性があります。特に`hash_id`が大きすぎたり、均等に分布していなかったりする場合です。
`hash_id`を混ぜ込むためにビット演算を使うことを検討してください。XORを使った例を次に示します。
unsigned int hash(const char* str, unsigned int hash_id) {
unsigned int hash = 5381;
int c;
while ((c = *str++)) {
hash = ((hash << 5) + hash) ^ c + hash_id; // Using XOR to mix in the hash_id
}
return (hash ^ hash_id); // Mixing the hash_id one more time at the end
}これは単に足し算するよりも`hash_id`をハッシュ全体に広げるので、分布がより均一でない状態になるのを防げます。[中略… 実際の返答にはさらに有益な情報が含まれていました]
MacBook M1 Maxで動作するモデルとしては悪くありません。足し算をXORで混ぜ合わせてもいます。この場合、解決すべき問題について私が手がかりを与えたことでモデルは確かに助けられましたが、問題の真の原因を特定し、有効な代替案を提案したのはモデル自身です。上記のようなことは、どんな書籍やドキュメント、Google検索でも得られるものではありません。そして、原始的であれ、補間の結果であれ、どう捉えようと、モデルは何らかの推論を行っています。少なくともこの特定のケースで、推論を問題の起源の特定とその潜在的な解決策と捉えるならばです。LLMについてどう考えようと、プログラマーにとって役に立たないと言うのは極めて軽率です。
しかし同時に、ここ数ヶ月の私の経験が示すのは、システムプログラミングにおいては、すでに経験豊富なプログラマーである場合、LLMが許容できる解決策を提示することはほぼないということです。別の実例をお見せしましょう。私の現在のプロジェクトであるggufflibは、GGUF形式のファイルを読み書きするライブラリを書くもので、これはllama.cppが量子化されたモデルを読み込む際の形式です。当初、量子化エンコーディングがどのように機能するかを理解するために(速度上の理由から、各量子のビットは巧妙な方法で格納されています)ChatGPTを使ってみましたが、結局llama.cppのコードをリバースエンジニアリングすることにしました。その方がはるかに速かったのです。システムプログラマーをまともに支援できるLLMであれば、データエンコーディングの「struct」宣言とデコード関数を見れば、データ形式のドキュメントを再構築できるはずです。llama.cppの関数はGPT-4のコンテキストに十分収まるほど小さかったのですが、出力はまったく役に立ちませんでした。こうした場合、やり方は昔と同じです。紙とペンを用意し、コードを読み、デコーダが抽出したビットがどこに格納されるかを確認するのです。
上記のユースケースについてもう少し詳しく説明しますので、よければご自身でも試してみてください。llama.cppの実装から次の構造体があります。
// 6-bit quantization
// weight is represented as x = a * q
// 16 blocks of 16 elements each
// Effectively 6.5625 bits per weight
typedef struct {
uint8_t ql[QK_K/2]; // quants, lower 4 bits
uint8_t qh[QK_K/4]; // quants, upper 2 bits
int8_t scales[QK_K/16]; // scales, quantized with 8 bits
ggml_fp16_t d; // super-block scale
} block_q6_K;そして、非量子化を行うために使われる次の関数があります。
void dequantize_row_q6_K(const block_q6_K * restrict x, float * restrict y, int k) {
assert(k % QK_K == 0);
const int nb = k / QK_K;
for (int i = 0; i < nb; i++) {
const float d = GGML_FP16_TO_FP32(x[i].d);
const uint8_t * restrict ql = x[i].ql;
const uint8_t * restrict qh = x[i].qh;
const int8_t * restrict sc = x[i].scales;
for (int n = 0; n < QK_K; n += 128) {
for (int l = 0; l < 32; ++l) {
int is = l/16;
const int8_t q1 = (int8_t)((ql[l + 0] & 0xF) | (((qh[l] >> 0) & 3) << 4)) - 32;
const int8_t q2 = (int8_t)((ql[l + 32] & 0xF) | (((qh[l] >> 2) & 3) << 4)) - 32;
const int8_t q3 = (int8_t)((ql[l + 0] >> 4) | (((qh[l] >> 4) & 3) << 4)) - 32;
const int8_t q4 = (int8_t)((ql[l + 32] >> 4) | (((qh[l] >> 6) & 3) << 4)) - 32;
y[l + 0] = d * sc[is + 0] * q1;
y[l + 32] = d * sc[is + 2] * q2;
y[l + 64] = d * sc[is + 4] * q3;
y[l + 96] = d * sc[is + 6] * q4;
}
y += 128;
ql += 64;
qh += 32;
sc += 8;
}
}
}GPT-4にフォーマットの概要を書かせようとすると、重みの位置に応じて「ql」の下位/上位4ビットにブロックがどう格納されるかを明確に説明するのに苦労します。このブログ記事のために、より単純にデータがどのように格納されるかを示す関数を書かせてみてもいました(言葉では説明できなくてもコードなら書けるかもしれないと考えたのです)。生成された関数は多くの点で壊れており、インデックスは間違っており、6ビットから8ビットへの符号拡張も間違っており(単にuint8_tにキャストしているだけです)といった具合でした。
ちなみに、最終的に私自身が書いたコードがこちらです。
} else if (tensor->type == GGUF_TYPE_Q6_K) {
uint8_t *block = (uint8_t*)tensor->weights_data;
uint64_t i = 0; // i-th weight to dequantize.
while(i < tensor->num_weights) {
float super_scale = from_half(*((uint16_t*)(block+128+64+16)));
uint8_t *L = block;
uint8_t *H = block+128;
int8_t *scales = (int8_t*)block+128+64;
for (int cluster = 0; cluster < 2; cluster++) {
for (uint64_t j = 0; j < 128; j++) {
f[i] = (super_scale * scales[j/16]) *
((int8_t)
((((L[j%64] >> (j/64*4)) & 0xF) |
(((H[j%32] >> (j/32*2)) & 3) << 4)))-32);
i++;
if (i == tensor->num_weights) return f;
}
L += 64;
H += 32;
scales += 8;
}
block += 128+64+16+2; // Go to the next block.
}
}上記の関数からは、このコードの実際の貢献部分である、llama.cppのQ6_Kエンコーディングで使われている正確な形式を文書化した長いコメントを削除しています。もしGPTがこれを代わりにやってくれたら非常に有用だったでしょうし、これは数ヶ月の問題にすぎないと確信しています。なぜなら、この種のタスクはブレークスルーなしでも、少しのスケーリングだけで到達可能な範囲にあるからです。
視点を整理する
言いたくはありませんが、事実です。今日のプログラミングの大部分は、同じことをわずかに形を変えて吐き出すことの繰り返しです。高度な推論は求められません。LLMはこの点でかなり得意ですが、コンテキストの最大サイズという強い制約は残ります。これはプログラマーに本当に考えさせるべきことです。この種のプログラムを書くことに価値はあるのでしょうか。もちろん報酬は得られますし、かなり高額です。しかしLLMがその一部を担えるのであれば、五年後や十年後にいるべき最良の場所ではないかもしれません。
そして、LLMには何らかの推論能力があるのか、それともすべてはハッタリなのでしょうか。記号論者の言うように、「シニフィアン」が実際には存在しない意味の印象を与えているために、時に推論しているように見えるだけなのかもしれません。LLMと十分に向き合ってきた人々は、その限界を認めつつも、そうではないと確信しています。彼らがこれまでに見たものを混ぜ合わせる能力は、単語をランダムに吐き出すことをはるかに超えています。学習の大部分が事前学習中に次のトークンを予測する形で行われたとしても、この目標がモデルに何らかの抽象モデルの構築を強制するのです。このモデルは弱く、まだらで、不完全ですが、私たちが観察していることを観察する限り、存在しなければなりません。数学的な確信すら疑わしく、最高の専門家たちがしばしば対立する立場にいるのであれば、自分の目で見たことを信じるのが賢明なアプローチに思えます。
最後に、今日プログラミングにLLMを使わないことにどんな意味があるでしょうか。LLMに正しい質問をすることは、基礎的なスキルです。実践が少なければ少ないほど、AIのおかげで仕事を改善できる可能性も低くなります。そして、問題を記述する能力を磨くことは、他の人間と話す際にも役立ちます。私たちの言いたいことを時々理解してくれないのはLLMだけではありません。うまく伝えられないことは大きな制約であり、多くのプログラマーは特定の分野では非常に有能であるにもかかわらず、コミュニケーションが非常に下手です。そして今やGoogleは使い物になりません。圧縮されたドキュメントの形としてさえLLMを使うのは良い考えです。私自身は、これからも大いに活用し続けます。私は、あまり知られていない通信プロトコルの詳細や、自分がいかに優れているかを示したい誰かが書いたライブラリの入り組んだメソッドを学ぶことが、かつて好きだったことはありません。私には「ジャンク知識」のように思えます。LLMは日々、そうしたものから私をますます解放してくれています。
記事をランダムに読む