アテンションと Transformer のしくみ:Query・Key・Value から GPT・BERT まで
ChatGPT のような大規模言語モデル(LLM)は、文章を受け取って「次に来る単語の確率」を出し、その確率に従って 1 語ずつ文章を書き足していくプログラムです。その中身のほとんどは、2017 年に発表された Transformer というモデルの部品でできていて、Transformer の中心にあるのが アテンション(注意機構)です。
この記事は 画像分類 AI のしくみ の続きとして、画像ではなく文章を扱うモデルのしくみを説明します。ニューラルネットワーク・ソフトマックス・交差エントロピー誤差・勾配降下法は画像分類の記事で説明したので、この記事では「単語どうしの関係をどう計算するか」に絞ります。画像分類の記事の最後に出てきた Vision Transformer(ViT)も、この記事で説明するしくみをそのまま画像に使ったものです。
結論を先に書くと、Transformer は次の 3 つの工夫でできています。
- アテンションで単語どうしの関係を計算する: 各単語が「自分に関係の深い単語」を内積で探し、その単語の情報を重み付きで集める
- すべての単語を同時に処理する: 前から 1 語ずつ読む RNN と違って並列に計算できるので、大量のデータで大きなモデルを学習できる。語順は 位置エンコーディングで別に教える
- 同じブロックを何段も重ねる: アテンションと全結合層の組を、残差接続と層正規化でつないで重ねる
説明の途中に出てくる計算例は、Python と NumPy で実際に動かした結果です。
環境
計算例を動かした環境です。
| 項目 | バージョン |
|---|---|
| Python | 3.13 |
| NumPy | 2.5 |
言語モデルとは:次の単語の確率を出す関数
文章をトークンに分けて番号にする
コンピューターは文字をそのまま計算できないので、まず文章を トークンと呼ばれる短い単位に区切り、それぞれに番号を付けます。トークンは単語のこともあれば、単語の一部や 1 文字のこともあります。よく使われる BPE(バイト対符号化)は、学習用の文章の中でよく一緒に現れる文字の組を順につなげて、数万種類の語彙を作る方法です。この記事では、分かりやすいようにトークンを「単語」と呼びます。
埋め込み:単語の番号をベクトルに変える
番号のままでは「猫」と「犬」が似ていることを表せないので、各単語を数百〜数千個の数の並び(ベクトル)に置き換えます。これを 埋め込み(embedding)と呼びます。埋め込みは「語彙の数 × 次元の数」の表で、単語の番号の行を取り出すだけです。表の値は、ほかの重みと一緒に学習で決まります。
言語モデルの仕事
言語モデルは、それまでの単語の列を受け取り、語彙のすべての単語について「次にこの単語が来る確率」を出す関数です。画像分類のモデルが「猫 0.9・犬 0.1」と画像の種類の確率を出していたのと同じ形で、クラスの数が語彙の数(数万)になったものと考えられます。
文章を書くときは、確率に従って 1 語選び、その単語を入力の後ろに付け足して、また次の単語の確率を出す、をくり返します。
RNN の限界:1 語ずつ順番に読む
Transformer より前は、文章を扱うのに RNN(再帰型ニューラルネットワーク)や、その改良版の LSTM が使われていました。RNN は単語を前から 1 つずつ読み、それまでの内容を 1 本のベクトル(隠れ状態)にまとめながら進みます。
この方法には 2 つの弱点があります。
| 弱点 | 内容 |
|---|---|
| 遠くの単語の情報が薄れる | 文の最初の単語の情報は、何十回もの更新を経てようやく文末に届く。途中で上書きされて薄れやすい |
| 並列に計算できない | 10 語目を処理するには 9 語目の結果が必要なので、GPU で多くの計算を同時に進められない |
2014 年に、RNN による翻訳モデルに アテンションを足す方法が発表されました(Bahdanau ら)。訳文の単語を 1 つ出すたびに、原文のすべての単語の隠れ状態を見直して、関係の深い単語の情報を直接取りにいくしくみです。これで遠くの単語の情報が薄れる問題は軽くなりましたが、RNN が 1 語ずつ進むことは変わりませんでした。
2017 年の論文「Attention Is All You Need」は、題名どおり RNN をなくし、アテンションだけで文章を処理する Transformer を提案しました。
アテンション:関係の深い単語から情報を集める
Query・Key・Value の 3 つのベクトル
アテンションでは、各単語の埋め込みから、3 種類のベクトルを作ります。作り方は、埋め込みに 3 つの重み行列 W_Q・W_K・W_V をそれぞれ掛けるだけです。
| ベクトル | 役割 | たとえると |
|---|---|---|
| Query(クエリ) | 自分がどんな情報を探しているか | 図書館で調べたいことを書いたメモ |
| Key(キー) | 自分がどんな情報を持っているか | 本の背表紙のラベル |
| Value(バリュー) | 実際に渡す情報 | 本の中身 |
ある単語の Query と、文中の各単語の Key の 内積を計算すると、向きが似ているほど大きな値になります。これを「どれくらい関係が深いか」の点数(スコア)とします。点数をソフトマックスで合計 1 の重みに変え、その重みで各単語の Value を足し合わせたものが、その単語の出力です。
式で書くと次のとおりです(d_k は Key の次元の数)。
Attention(Q, K, V) = softmax(Q Kᵀ / √d_k) V
文中の単語どうしでこの計算をするものを、とくに セルフアテンション(自己注意)と呼びます。
3 語の文で計算してみる
「猫 が 鳴く」の 3 語に 4 次元の埋め込みを適当に決め、2 次元の Query・Key・Value を作ってアテンションを計算してみます。重み行列も手で決めた値で、学習したものではありません。
import numpy as np
# 3 つの単語の埋め込み(4 次元)
X = np.array([
[1.0, 0.0, 1.0, 0.0], # 猫
[0.0, 1.0, 0.0, 1.0], # が
[1.0, 1.0, 0.0, 0.0], # 鳴く
])
W_Q = np.array([[1, 0], [0, 1], [1, 0], [0, 0]], dtype=float)
W_K = np.array([[1, 0], [0, 0], [1, 1], [0, 1]], dtype=float)
W_V = np.array([[1, 0], [0, 1], [0, 0], [0, 1]], dtype=float)
Q, K, V = X @ W_Q, X @ W_K, X @ W_V
scores = Q @ K.T / np.sqrt(K.shape[1])
weights = np.exp(scores) / np.exp(scores).sum(axis=1, keepdims=True)
print(np.round(scores, 3))
print(np.round(weights, 3))
print(np.round(weights @ V, 3))
実行結果:
[[2.828 0. 1.414]
[0.707 0.707 0. ]
[2.121 0.707 0.707]]
[[0.768 0.045 0.187]
[0.401 0.401 0.198]
[0.673 0.164 0.164]]
[[0.955 0.277]
[0.599 1. ]
[0.836 0.491]]
1 つ目の表がスコア、2 つ目がソフトマックスで変えた重み、3 つ目が出力です。行は Query 側の単語(上から猫・が・鳴く)、列は Key 側の単語です。
3 行目を見ると、「鳴く」は重みの 0.673 を「猫」に向けています。「鳴く」の出力は、自分の Value よりも「猫」の Value を多く混ぜたベクトルになり、「何が鳴くのか」という情報を持つようになります。実際のモデルでは、W_Q・W_K・W_V の値を学習で決めることで、「動詞は主語を見る」「代名詞は指している名詞を見る」のような関係を、モデルが自分で見つけます。
√d_k で割る理由:ソフトマックスが偏りすぎないように
スコアを √d_k で割っているのには理由があります。Query と Key の各成分が平均 0・分散 1 のばらばらな値だとすると、その内積の分散は次元の数 d_k になり、次元が大きいほどスコアの幅が広がります。乱数で確かめてみます。
import numpy as np
rng = np.random.default_rng(0)
for d in [4, 64, 512]:
q = rng.standard_normal((10000, d))
k = rng.standard_normal((10000, d))
dots = (q * k).sum(axis=1)
print(d, round(float(dots.std()), 2), round(float((dots / np.sqrt(d)).std()), 2))
def softmax(z):
e = np.exp(z - z.max())
return e / e.sum()
s = np.array([1.0, 0.5, -0.5, -1.0])
print(np.round(softmax(s), 3))
print(np.round(softmax(s * np.sqrt(512)), 3))
実行結果:
4 2.0 1.0
64 8.08 1.01
512 22.58 1.0
[0.509 0.309 0.114 0.069]
[1. 0. 0. 0.]
左の列が次元の数、中央が内積の標準偏差、右が √d で割ったあとの標準偏差です。内積の標準偏差は √d(2・8・22.6)で大きくなりますが、√d で割るとどの次元でも 1 前後にそろいます。
下の 2 行は、同じ並びのスコアをそのままソフトマックスに入れた場合と、√512 倍に広げてから入れた場合です。スコアの幅が広いと、重みがほぼ 1 つの単語に集中してしまいます。こうなると、ほかの単語の重みの勾配がほぼ 0 になり、学習が進みにくくなります。√d_k で割るのはこれを防ぐためです。
マルチヘッドアテンション:いくつもの見方を並べる
1 組の W_Q・W_K・W_V で計算できる関係は 1 種類だけです。そこで Transformer は、この組を何組も用意して並べて計算します。それぞれを ヘッドと呼び、まとめて マルチヘッドアテンションと呼びます。
- あるヘッドは直前の単語を見て、別のヘッドは主語と動詞の関係を見る、のように、ヘッドごとに違う関係を学べる
- 各ヘッドの出力をつなげて、もう 1 つの重み行列 W_O を掛けて元の次元に戻す
- 元の論文のモデルでは、512 次元を 8 つのヘッドに分け、各ヘッドは 64 次元の Query・Key・Value で計算した
このサイトの アテンション(Transformer)の可視化 では、「直前の語を見る」「文法の役割を見る」「意味の近さを見る」の 3 つのヘッドを手で設計し、Query・Key・Value からスコア・重み・出力までを 1 段ずつ行列の色で見られます。ヘッドを切り替えると、同じ文でも注目する単語が変わる様子を確かめられます。
位置エンコーディング:語順を教える
アテンションだけでは語順が分からない
アテンションの計算は、単語の並べ方を入れ替えても、出力が同じように入れ替わるだけです。つまり、このままでは「猫が犬を追う」と「犬が猫を追う」を区別できません。そこで、各単語の埋め込みに、文中の位置を表すベクトルを足してから入力します。これを 位置エンコーディングと呼びます。
sin と cos で位置を表す
元の論文では、位置 pos と次元の番号 i から、次のような sin と cos の値を作りました(d は埋め込みの次元の数)。
PE(pos, 2i) = sin(pos / 10000^(2i/d))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d))
前のほうの次元は速く、後ろのほうの次元はゆっくり回る、時計の針がいくつも並んだようなベクトルです。64 次元で計算してみます。
import numpy as np
def positional_encoding(n, d):
pos = np.arange(n)[:, None]
i = np.arange(0, d, 2)[None, :]
angle = pos / 10000 ** (i / d)
pe = np.zeros((n, d))
pe[:, 0::2] = np.sin(angle)
pe[:, 1::2] = np.cos(angle)
return pe
pe = positional_encoding(50, 64)
print(np.round(pe[:3, :4], 3))
# 位置 10 との内積(似ている度合い)
sims = pe @ pe[10]
print([round(float(sims[p]), 1) for p in [10, 11, 12, 15, 20, 40]])
実行結果:
[[ 0. 1. 0. 1. ]
[ 0.841 0.54 0.682 0.732]
[ 0.909 -0.416 0.997 0.071]]
[32.0, 30.9, 28.3, 23.5, 21.1, 17.1]
1 つ目の表は位置 0・1・2 のベクトルの最初の 4 次元です。2 行目は位置 10 のベクトルと、位置 10・11・12・15・20・40 のベクトルとの内積で、位置が離れるほど小さくなっています。位置エンコーディングを足しておけば、アテンションの内積の中で「近くにある単語かどうか」も計算に入るようになります。
最近の LLM の多くは、足し算ではなく、Query と Key のベクトルを位置に応じて回転させる RoPE(回転位置埋め込み)を使っています。こちらは、内積が 2 つの単語の「位置の差」だけで決まるように作られています。
Transformer のブロック:アテンションと全結合層を重ねる
1 つのブロックの中身
Transformer は、次の部品でできた ブロックを何段も重ねたものです。
- マルチヘッドアテンションで、ほかの単語から情報を集める
- 全結合層(フィードフォワード層)で、単語ごとに情報を加工する。次元をいったん 4 倍ほどに広げ、活性化関数を通して元に戻す
- それぞれの部品の前後を 残差接続と 層正規化でつなぐ
アテンションが「単語どうしで情報をやり取りする」部品、全結合層が「単語ごとに考える」部品で、この 2 つを交互にくり返す形です。モデルの重みの大部分は、実は全結合層にあります。
残差接続と層正規化で深くしても学習できるようにする
残差接続は、画像分類の記事の ResNet で説明したものと同じで、部品の出力に入力をそのまま足すしくみです。勾配が足し算の道を通って入力側まで届きやすくなり、何十段も重ねても学習できます。
層正規化(layer normalization)は、単語ごとのベクトルを、平均 0・標準偏差 1 にそろえる処理です。段を重ねるうちに値が大きくなりすぎたり小さくなりすぎたりするのを防ぎます。
import numpy as np
x = np.array([2.0, -1.0, 0.5, 6.5])
y = (x - x.mean()) / np.sqrt(x.var() + 1e-5)
print(round(float(x.mean()), 3), round(float(x.std()), 3))
print(np.round(y, 3), round(float(y.mean()), 3), round(float(y.std()), 3))
実行結果:
2.0 2.806
[ 0. -1.069 -0.535 1.604] 0.0 1.0
平均 2・標準偏差 2.806 のベクトルが、平均 0・標準偏差 1 にそろいました(分母の 1e-5 は 0 で割らないための小さな値です)。実際の層正規化では、このあとに学習で決まる倍率とずらす量を掛け合わせて、必要な大きさに戻せるようにしています。
エンコーダーとデコーダー:BERT と GPT の違い
元の Transformer は翻訳のためのモデル
元の論文の Transformer は翻訳のモデルで、2 つの部分に分かれていました。
| 部分 | 役割 |
|---|---|
| エンコーダー | 原文を読み、各単語のベクトルを作る。アテンションは文中のすべての単語を見られる |
| デコーダー | 訳文を 1 語ずつ書く。アテンションはそれまでに書いた単語だけを見る。さらに、エンコーダーの出力を見るアテンション(クロスアテンション)で原文の情報を取りにいく |
元の論文では、エンコーダーとデコーダーをそれぞれ 6 段重ね、英語からドイツ語・フランス語への翻訳で、それまでの RNN などのモデルを上回る精度を、より短い学習時間で出しました。
因果マスク:未来の単語を見せない
文章を書くデコーダーは、次の単語を予測するときに、まだ書いていない後ろの単語を見てはいけません。そこで、スコアの表のうち「自分より後ろの単語」の位置を −∞ にしてからソフトマックスを取ります。e^(−∞) = 0 なので、その単語の重みは 0 になります。これを 因果マスクと呼びます。
import numpy as np
scores = np.array([
[2.0, 1.0, 0.5, 1.5],
[1.0, 2.0, 1.0, 0.5],
[0.5, 1.0, 2.0, 1.0],
[1.5, 0.5, 1.0, 2.0],
])
mask = np.triu(np.ones((4, 4), dtype=bool), k=1) # 自分より後ろの単語
masked = np.where(mask, -np.inf, scores)
weights = np.exp(masked) / np.exp(masked).sum(axis=1, keepdims=True)
print(np.round(weights, 3))
実行結果:
[[1. 0. 0. 0. ]
[0.269 0.731 0. 0. ]
[0.14 0.231 0.629 0. ]
[0.276 0.102 0.167 0.455]]
重みの表の右上の三角形がすべて 0 になり、1 語目は自分だけ、2 語目は 1・2 語目だけ、のように、前の単語だけを見るようになりました。因果マスクのおかげで、1 つの文の「すべての位置での次の単語の予測」を一度にまとめて計算して学習できます。RNN と違い、学習のときには文の長さぶんの計算を並列に進められます。
エンコーダーだけの BERT、デコーダーだけの GPT
2018 年には、Transformer の片方だけを使い、大量の文章で事前に学習するモデルが登場しました。
| モデル | 使う部分 | 事前学習の問題 | 得意なこと |
|---|---|---|---|
| BERT(Google) | エンコーダー | 文中の単語の 15% を隠し、前後の単語から当てる | 文の分類・検索・質問の答えの位置を探す、など文を読む仕事 |
| GPT(OpenAI) | デコーダー(因果マスクあり) | 前の単語から次の単語を当てる | 文章の続きを書く |
今の ChatGPT などの LLM は、GPT と同じデコーダーだけの形です。
学習:次の単語を当てる問題を大量に解く
損失は交差エントロピー誤差
言語モデルの学習は、文章の各位置で「次の単語」を当てる分類問題です。損失は画像分類と同じ交差エントロピー誤差で、正解の単語に付けた確率 p の −log(p) を、すべての位置で平均します。
言語モデルでは、損失の指数をとった パープレキシティもよく使われます。「平均して何個の候補で迷っているか」を表す値で、小さいほど良いモデルです。
import numpy as np
# 「吾輩 は 猫 で ある」の各位置で、正解の次の単語に付いた確率
p_true = np.array([0.20, 0.60, 0.90, 0.75])
loss = -np.log(p_true).mean()
print(round(float(loss), 3), round(float(np.exp(loss)), 3))
# 語彙 50,000 語から当てずっぽうに選んだとき
print(round(float(np.log(50000)), 3))
実行結果:
0.628 1.874
10.82
4 つの位置の確率から、損失は 0.628、パープレキシティは約 1.87 になりました。「平均して 2 つ弱の候補で迷っている」ということです。50,000 語の語彙からでたらめに選ぶモデルなら、損失は log(50000) ≒ 10.82、パープレキシティは 50,000 です。学習の最初は、損失がこのあたりから下がっていきます。
正解を用意しなくてよい
この学習のよいところは、正解のラベルを人が付けなくてよいことです。文章さえあれば、「次の単語」が自動的に正解になります。そのため、インターネット上の大量の文章をそのまま学習に使えます。2020 年の GPT-3 は、1,750 億個のパラメーターを持つモデルを、数千億トークンの文章で学習しました。モデルの大きさ・データの量・計算量を増やすほど損失が規則的に下がること(スケーリング則)が分かり、モデルの大型化が進みました。
指示に従うように仕上げる
次の単語を当てる学習だけでは、「質問に答える」「頼まれたことをする」のが得意とは限りません。ChatGPT の元になった InstructGPT(2022 年)では、事前学習のあと、人が書いたお手本の回答で追加の学習(ファインチューニング)をし、さらに人が回答の良し悪しを比べた結果を使った強化学習(RLHF)で仕上げました。
文章の生成:温度とサンプリング
確率の高い単語を選ぶだけでは単調になる
学習したモデルで文章を書くときは、出てきた確率から次の単語を 1 つ選びます。毎回いちばん確率の高い単語を選ぶと、同じ言い回しのくり返しになりやすいので、ふつうは確率に従ってランダムに選びます(サンプリング)。
温度:確率の分布をとがらせる・ならす
選ぶ前に、ソフトマックスに入れる点数を 温度 T で割ると、分布の形を調整できます。「庭で__が鳴いている」の空欄の候補 5 語で試してみます。
import numpy as np
words = ["猫", "犬", "鳥", "魚", "机"]
logits = np.array([3.0, 2.0, 1.0, 0.5, -1.0])
for t in [0.5, 1.0, 2.0]:
p = np.exp(logits / t) / np.exp(logits / t).sum()
print(t, np.round(p, 3))
p = np.exp(logits) / np.exp(logits).sum()
order = np.argsort(-p)
cum = np.cumsum(p[order])
keep = order[: np.searchsorted(cum, 0.9) + 1] # top-p(p = 0.9)
print([words[i] for i in keep], np.round(cum, 3))
実行結果:
0.5 [0.862 0.117 0.016 0.006 0. ]
1.0 [0.624 0.229 0.084 0.051 0.011]
2.0 [0.417 0.253 0.154 0.12 0.056]
['猫', '犬', '鳥'] [0.624 0.853 0.937 0.989 1. ]
温度を 0.5 に下げると「猫」の確率が 0.862 まで上がり、ほぼ決まった答えを返すようになります。2 に上げると分布がならされ、「机」のような変わった単語も選ばれやすくなります。
最後の行は top-p サンプリングです。確率の高い順に足していき、合計が p(ここでは 0.9)を超えたところまでの単語だけを候補に残します。ここでは「猫・犬・鳥」の 3 語が残り、まずありえない「魚・机」は選ばれなくなります。
アテンションの弱点:長い文章で計算が膨らむ
アテンションは、すべての単語の組についてスコアを計算するので、文の長さが n 語なら n × n 個の計算が必要です。長さが 2 倍になると、スコアの表は 4 倍になります。
for n in [1_000, 8_000, 128_000]:
print(n, f"{n * n:,}", round(n * n * 2 / 1024**3, 3))
実行結果:
1000 1,000,000 0.002
8000 64,000,000 0.119
128000 16,384,000,000 30.518
左から、トークンの数、スコアの表の要素の数、1 つの値を 2 バイト(16 ビットの浮動小数点数)で持ったときの大きさ(GiB)です。これは 1 つのヘッド・1 つの層ぶんです。12.8 万トークンでは表 1 枚で 30 GiB を超えるので、そのままでは扱えません。
これを解決するために、次のような工夫が使われています。
| 工夫 | 内容 |
|---|---|
| FlashAttention | 表全体をメモリーに置かず、小さな区画ごとに計算して GPU のメモリーの読み書きを減らす。計算の結果は同じ |
| KV キャッシュ | 文章を 1 語ずつ生成するとき、前の単語の Key と Value は変わらないので、保存して使い回す |
| 見る範囲を絞る | 近くの単語だけを見るヘッドを混ぜるなど、計算する組を減らす |
Transformer と言語モデルの流れ
この記事で紹介した手法を年代順に並べます。研究者や企業の動きは AI 開発のキープレイヤー年表 にまとめています。
| 年 | 手法・モデル | ポイント |
|---|---|---|
| 1997 | LSTM | 遠くの情報を覚えておける RNN |
| 2014 | Seq2Seq | RNN のエンコーダーとデコーダーで翻訳する |
| 2014 | アテンション(Bahdanau ら) | 訳語を出すたびに原文の関係の深い単語を見直す |
| 2017 | Transformer | RNN をなくし、アテンションだけで文章を処理する |
| 2018 | GPT | デコーダーだけのモデルを、次の単語を当てる問題で事前学習する |
| 2018 | BERT | エンコーダーだけのモデルを、隠した単語を当てる問題で事前学習する |
| 2019 | GPT-2 | 15 億パラメーター。追加の学習なしでいろいろな仕事をこなす |
| 2020 | GPT-3・スケーリング則 | 1,750 億パラメーター。例をいくつか見せるだけで新しい仕事をこなす |
| 2020 | Vision Transformer | 画像をパッチに区切って Transformer で処理する |
| 2021 | RoPE | Query と Key を回転させて位置を表す |
| 2022 | FlashAttention | メモリーの読み書きを減らしてアテンションを速くする |
| 2022 | InstructGPT・ChatGPT | 人の評価を使った強化学習(RLHF)で、指示に従うように仕上げる |
まとめ
- 言語モデルは、それまでの単語から次の単語の確率を出す関数で、確率に従って 1 語ずつ選んで文章を書く
- 単語は番号を経て埋め込みのベクトルに変えてから計算する
- アテンションは、Query と Key の内積で関係の深さを測り、ソフトマックスの重みで Value を足し合わせる
- スコアを √d_k で割るのは、次元が大きいときにソフトマックスが 1 つの単語に偏りすぎないようにするため
- マルチヘッドアテンションは、違う種類の関係を並べて計算する
- アテンションは語順を区別できないので、位置エンコーディングで位置の情報を足す
- Transformer のブロックは、アテンションと全結合層を残差接続と層正規化でつないだもので、これを何段も重ねる
- 因果マスクで後ろの単語を見えなくしたデコーダーが GPT、すべての単語を見るエンコーダーが BERT の形
- 学習は次の単語を当てる交差エントロピー誤差の最小化で、文章さえあれば正解が自動で決まる
- 生成のときは、温度や top-p で確率の分布を調整してからサンプリングする
- アテンションの計算量は文の長さの 2 乗で増えるので、FlashAttention や KV キャッシュなどの工夫が使われている
参考
- S. Hochreiter and J. Schmidhuber, "Long Short-Term Memory", Neural Computation 1997
- I. Sutskever et al., "Sequence to Sequence Learning with Neural Networks", NeurIPS 2014(arXiv:1409.3215)
- D. Bahdanau et al., "Neural Machine Translation by Jointly Learning to Align and Translate", ICLR 2015(arXiv:1409.0473)
- R. Sennrich et al., "Neural Machine Translation of Rare Words with Subword Units", ACL 2016(arXiv:1508.07909)
- J. L. Ba et al., "Layer Normalization", 2016(arXiv:1607.06450)
- A. Vaswani et al., "Attention Is All You Need", NeurIPS 2017(arXiv:1706.03762)
- A. Radford et al., "Improving Language Understanding by Generative Pre-Training", 2018
- J. Devlin et al., "BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding", NAACL 2019(arXiv:1810.04805)
- A. Holtzman et al., "The Curious Case of Neural Text Degeneration", ICLR 2020(arXiv:1904.09751)
- J. Kaplan et al., "Scaling Laws for Neural Language Models", 2020(arXiv:2001.08361)
- T. Brown et al., "Language Models are Few-Shot Learners", NeurIPS 2020(arXiv:2005.14165)
- J. Su et al., "RoFormer: Enhanced Transformer with Rotary Position Embedding", 2021(arXiv:2104.09864)
- T. Dao et al., "FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness", NeurIPS 2022(arXiv:2205.14135)
- L. Ouyang et al., "Training language models to follow instructions with human feedback", NeurIPS 2022(arXiv:2203.02155)