LLM を小さく速く動かすしくみ:量子化・蒸留・LoRA・MoE・KV キャッシュ
大規模言語モデル(LLM)は、何十億〜何千億個ものパラメーター(重み)を持っています。そのまま動かすには、何十〜何百 GB ものメモリーを持つ高価な GPU が何枚も必要です。ところが今では、数十億パラメーターのモデルがノートパソコンやスマートフォンでも動き、個人でも家庭用の GPU で LLM を自分のデータに合わせて学習させられるようになりました。それを可能にしたのが、この記事で説明する、モデルを小さく・速くする工夫です。
この記事は アテンションと Transformer のしくみ の続きです。Transformer の部品(アテンション・全結合層・KV キャッシュ)はそちらで説明したので、この記事では「その部品をどうやって軽くするか」に絞ります。
結論を先に書くと、工夫は次のように分けられます。
| 工夫 | 何を減らすか | 考え方 |
|---|---|---|
| 量子化 | 重みのメモリー | 重み 1 個を 32 ビットではなく、8 ビットや 4 ビットの整数で表す |
| 知識の蒸留 | モデルの大きさそのもの | 大きなモデル(先生)の出す確率をまねるように、小さなモデル(生徒)を学習させる |
| LoRA | 学習させる重みの数 | 元の重みは固定し、変化の分だけを 2 つの細長い行列の積で学習する |
| 専門家の混合(MoE) | 1 トークンあたりの計算 | 全結合層をいくつも用意し、トークンごとに一部だけを使う |
| KV キャッシュの工夫 | 生成中のメモリー | Key と Value を複数のヘッドで共有するなどして、保存する量を減らす |
説明の途中に出てくる計算例は、Python と NumPy で実際に動かした結果です。
環境
計算例を動かした環境です。
| 項目 | バージョン |
|---|---|
| Python | 3.13 |
| NumPy | 2.5 |
LLM に必要なメモリー
パラメーターの数 × 1 個あたりのビット数
モデルを動かすには、まずすべての重みをメモリーに置く必要があります。必要な大きさは「パラメーターの数 × 1 個あたりのビット数 ÷ 8」バイトです。70 億(7B)と 700 億(70B)のパラメーターを持つモデルで、1 個あたりのビット数を変えて計算してみます。
# パラメーターの数と、1 個あたりのビット数から、重みを置くのに必要なメモリーを計算する
for params in [7e9, 70e9]:
row = [f"{params * bits / 8 / 1024**3:.1f}" for bits in [32, 16, 8, 4]]
print(f"{params / 1e9:.0f}B", row)
実行結果:
7B ['26.1', '13.0', '6.5', '3.3']
70B ['260.8', '130.4', '65.2', '32.6']
左から 32・16・8・4 ビットのときの大きさ(GiB)です。7B のモデルは、32 ビットでは 26 GiB ですが、4 ビットにすれば 3.3 GiB になり、家庭用の GPU や、メモリーの多いノートパソコンに収まります。70B のモデルは、16 ビットでは 130 GiB で GPU が何枚も必要ですが、4 ビットなら 33 GiB です。
これは重みだけの大きさで、実際には後で説明する KV キャッシュなどのためのメモリーも必要です。学習させる場合はさらに、勾配や、最適化手法の記事 で説明した Adam の 2 つの平均の値も、重みと同じ数だけ持つ必要があり、重みの数倍のメモリーを使います。
浮動小数点数の形式:float16 と bfloat16
同じ 16 ビットでも得意なことが違う
学習も推論も、今は 32 ビットの浮動小数点数(float32)ではなく、16 ビットの形式を使うのがふつうです。16 ビットの形式には、ビットの配分の違う 2 種類があります。
| 形式 | 符号 | 指数部(表せる範囲) | 仮数部(細かさ) | 最大値 |
|---|---|---|---|---|
| float32 | 1 | 8 ビット | 23 ビット | 約 3.4 × 10³⁸ |
| float16 | 1 | 5 ビット | 10 ビット | 65,504 |
| bfloat16 | 1 | 8 ビット | 7 ビット | 約 3.4 × 10³⁸ |
float16 は細かさを優先した形式、bfloat16 は float32 と同じ範囲を表せるように指数部を残し、細かさを削った形式です。いくつかの値を変換して比べてみます。NumPy には bfloat16 がないので、float32 の下位 16 ビットを丸めて捨てる方法でまねします。
import numpy as np
def to_bfloat16(x):
"""float32 の下位 16 ビットを(いちばん近い値に丸めて)捨て、bfloat16 にする"""
b = np.asarray(x, dtype=np.float32).view(np.uint32)
b = ((b + 0x7FFF + ((b >> 16) & 1)) >> 16) << 16
return b.astype(np.uint32).view(np.float32)
x = np.array([0.1, 1 / 3, 1000.1, 70000.0, 1e-6], dtype=np.float32)
with np.errstate(over="ignore"):
fp16 = x.astype(np.float16)
print([f"{v:.6g}" for v in fp16])
print([f"{v:.6g}" for v in to_bfloat16(x)])
実行結果:
['0.0999756', '0.333252', '1000', 'inf', '1.01328e-06']
['0.100098', '0.333984', '1000', '70144', '9.98378e-07']
元の値は 0.1・1/3・1000.1・70000・0.000001 です。
- float16: 0.1 や 1/3 は bfloat16 より元の値に近い(細かい)が、70,000 は最大値の 65,504 を超えて inf(無限大)になってしまう
- bfloat16: 細かさは粗い(1/3 が 0.333984 になる)が、70,000 も 70,144 として表せる
学習中は、勾配や途中の値がときどき大きくなるので、範囲の狭い float16 ではあふれて学習が壊れることがあります。そのため、今の LLM の学習では bfloat16 がよく使われています。ニューラルネットワークは、1 つ 1 つの値の細かさにはそれほど敏感ではないので、細かさを削ってもほとんど困らないのです。
このサイトの 浮動小数点数(IEEE 754) では、10 進の数が浮動小数点数のビット列にどう丸められるかを、形式ごとに比べながら見られます。
量子化:重みを整数で表す
倍率と整数の組で表す
量子化は、重みを 8 ビットや 4 ビットの整数で表す方法です。いちばん基本の 対称な量子化では、次のようにします。
- 重みの絶対値の最大を求め、それが整数の最大(8 ビットなら 127、4 ビットなら 7)になるような倍率(scale)を決める
- 重み ÷ 倍率 を四捨五入して整数にし、整数と倍率だけを保存する
- 使うときは、整数 × 倍率 で元の値に戻す
四捨五入するので、戻した値には誤差が出ます。問題は、重みの中にたまに大きな値(外れ値)があると、倍率がそれに合わせて大きくなり、ほかの大多数の小さな重みが、ほんの数段階の整数でしか表せなくなることです。ある層の重み(256 × 256)に、外れ値を 1 つだけ入れて試してみます。
import numpy as np
rng = np.random.default_rng(0)
W = rng.normal(0, 0.02, (256, 256)).astype(np.float32) # ある層の重み
W[3, 7] = 1.5 # 外れ値の重みが 1 つだけある
def quantize(w, bits, axis=None):
"""対称な量子化:絶対値の最大が整数の最大になるように拡大して丸め、元の倍率で戻す"""
qmax = 2 ** (bits - 1) - 1 # 8 ビットなら 127、4 ビットなら 7
scale = np.abs(w).max(axis=axis, keepdims=True) / qmax
q = np.clip(np.round(w / scale), -qmax, qmax).astype(np.int8) # 保存するのは整数と倍率だけ
return q * scale
def rel_error(a, b):
return float(np.linalg.norm(a - b) / np.linalg.norm(a))
print(round(rel_error(W, quantize(W, 8)), 4), round(rel_error(W, quantize(W, 8, axis=1)), 4))
print(round(rel_error(W, quantize(W, 4)), 4), round(rel_error(W, quantize(W, 4, axis=1)), 4))
# 4 ビットで、64 個ずつのグループごとに倍率を持つ
groups = W.reshape(256, 4, 64)
print(round(rel_error(W, quantize(groups, 4, axis=2).reshape(256, 256)), 4))
# 層の出力で比べる
x = rng.normal(0, 1, (32, 256)).astype(np.float32)
y = x @ W
print(round(rel_error(y, x @ quantize(W, 8, axis=1)), 4), round(rel_error(y, x @ quantize(groups, 4, axis=2).reshape(256, 256)), 4))
実行結果:
0.1637 0.0119
0.9596 0.1333
0.107
0.0115 0.1072
値は、元の重みに対する誤差の大きさの割合(相対誤差)です。
| 量子化のしかた | 8 ビット | 4 ビット |
|---|---|---|
| 行列全体で倍率 1 つ | 16.4% | 96.0% |
| 行ごとに倍率を持つ | 1.2% | 13.3% |
| 64 個ずつのグループごとに倍率を持つ | — | 10.7% |
行列全体で倍率を 1 つにすると、外れ値の 1.5 に合わせて倍率が決まるため、標準偏差 0.02 の大多数の重みは、8 ビットでも数段階の整数でしか表せず、誤差は 16.4% になりました。4 ビットでは大多数の重みが 0 に丸められ、誤差は 96% と、ほとんど情報が残っていません。
倍率を行ごとに持つと、外れ値の影響はその行だけに閉じ込められ、8 ビットの誤差は 1.2% まで下がりました。4 ビットでは、64 個ずつの小さなグループごとに倍率を持つと、さらに 10.7% まで下がります。グループを小さくするほど正確になりますが、保存する倍率の数が増えます。
最後の行は、量子化した重みで層の出力を計算したときの誤差で、行ごとの 8 ビットで 1.2%、グループごとの 4 ビットで 10.7% と、重みの誤差とほぼ同じでした。
外れ値への対策
実際の LLM でも、モデルが大きくなると、一部の次元に極端に大きな値が現れることが 2022 年の研究で分かりました。そこで、次のような工夫が使われています。
| 方法 | 考え方 |
|---|---|
| LLM.int8() | 外れ値の現れる次元だけ 16 ビットのまま計算し、残りを 8 ビットにする |
| GPTQ | 重みを 1 列ずつ量子化し、そのたびに生じた誤差を、まだ量子化していない列で打ち消すように調整する |
| AWQ | 出力への影響が大きい重みを、量子化の前に拡大しておき、細かく表せるようにする |
こうした工夫によって、4 ビットまで量子化しても、元のモデルとほとんど変わらない性能を保てるようになりました。
知識の蒸留:大きなモデルの「迷い方」をまねる
温度を上げた確率には情報が多い
知識の蒸留は、学習済みの大きなモデル(先生)の出力をお手本にして、小さなモデル(生徒)を学習させる方法です。正解のラベルだけで学習するより、先生の出す確率をまねたほうが、小さなモデルでも性能が上がります。
先生の確率には、正解以外のクラスへの確率の配分という情報が含まれているからです。猫の写真に対して、先生が「トラ」にも少し確率を付けていれば、「猫はトラに少し似ていて、車にはまったく似ていない」ことを生徒に教えられます。この情報は、確率を Transformer の記事 で説明した 温度 T で割ってならすと、見えやすくなります。
import numpy as np
def softmax(z, T=1.0):
e = np.exp((z - z.max()) / T)
return e / e.sum()
classes = ["猫", "トラ", "犬", "車"]
teacher_logits = np.array([9.0, 6.0, 3.0, -2.0]) # 大きなモデル(先生)の点数
for T in [1, 4]:
print(T, np.round(softmax(teacher_logits, T), 3))
# 生徒の損失:正解ラベルとの交差エントロピー誤差と、温度 T での先生の確率との KL ダイバージェンスを混ぜる
def distill_loss(student_logits, label, T=4.0, alpha=0.5):
p_t, p_s = softmax(teacher_logits, T), softmax(student_logits, T)
kl = float((p_t * np.log(p_t / p_s)).sum())
ce = float(-np.log(softmax(student_logits)[label]))
return round(ce, 3), round(kl, 3), round(alpha * ce + (1 - alpha) * T**2 * kl, 3)
print(distill_loss(np.array([8.0, 1.0, 1.0, 1.0]), 0)) # 猫は当てたが、トラとの近さを知らない生徒
print(distill_loss(np.array([8.0, 5.0, 2.0, -2.0]), 0)) # 先生に似た点数を出す生徒
実行結果:
1 [0.95 0.047 0.002 0. ]
4 [0.568 0.268 0.127 0.036]
(0.003, 0.118, 0.949)
(0.051, 0.001, 0.035)
1・2 行目は、先生の確率を温度 1 と 4 で計算したものです。温度 1 では猫が 0.95 で、ほかのクラスの違いはほとんど見えませんが、温度 4 にすると、トラ 0.268・犬 0.127・車 0.036 と、「どのくらい似ているか」の順番がはっきり表れます。
3・4 行目は、2 人の生徒の損失で、左から、正解との交差エントロピー誤差、先生の確率との KL ダイバージェンス、2 つを混ぜた損失です。1 人目の生徒は猫を高い確率で当てていて、正解との誤差は 0.003 と小さいのですが、トラ・犬・車を同じ点数にしているので、先生との差(KL)が大きく、合計の損失は 0.949 になりました。2 人目の生徒は、正解との誤差は少し大きいものの、先生と同じ順番の点数を出しているので、合計の損失は 0.035 と小さくなります。蒸留では、正解を当てるだけでなく、先生の「迷い方」まで学ぶように生徒を学習させます。KL の項に T² を掛けているのは、温度を上げると勾配が 1/T² 倍に小さくなるのを補うためです。
2019 年の DistilBERT は、BERT を蒸留して、大きさを 40% 減らし、60% 速くしながら、言語理解の性能の 97% を保ちました。今の小型の LLM の多くも、大きな LLM が出した確率や文章を使って学習しています。
LoRA:変化の分だけを小さく学習する
重みの変化は低ランクで表せる
LLM を自分の仕事に合わせて追加で学習させる(ファインチューニング)とき、すべての重みを学習させると、重みと同じ数の勾配や Adam の値を持つ必要があり、メモリーが足りません。2021 年の LoRA(Low-Rank Adaptation)は、元の重み W₀ を固定したまま、重みの変化 ΔW を 2 つの細長い行列の積 B × A で表して、B と A だけを学習させます。
新しい重み = W₀(固定) + B(d × r) × A(r × d)
r(ランク)は 4〜64 くらいの小さな数です。ファインチューニングで必要になる重みの変化は、実は少ない数の向き(低いランク)で表せる、という観察に基づいています。d = 4096 の層で、学習させる数を比べてみます。
import numpy as np
d = 4096
for r in [4, 8, 16, 64]:
full, lora = d * d, 2 * d * r # ΔW をそのまま学習する場合と、B (d×r) と A (r×d) にする場合
print(r, f"{full:,}", f"{lora:,}", f"{lora / full:.2%}")
# 小さな層で LoRA を学習する:元の重み W0 は固定し、B と A だけを勾配降下法で動かす
rng = np.random.default_rng(0)
n, r = 64, 4
W0 = rng.normal(0, 0.1, (n, n))
delta_true = rng.normal(0, 0.1, (n, r)) @ rng.normal(0, 0.1, (r, n)) # 新しい仕事で必要な変化(実は低ランク)
X = rng.normal(0, 1, (512, n))
Y = X @ (W0 + delta_true) # 新しい仕事の正解
A = rng.normal(0, 0.01, (r, n))
B = np.zeros((n, r)) # B を 0 から始めるので、学習の最初は元のモデルと同じ出力
lr = 0.05
for step in range(1, 3001):
err = X @ (W0 + B @ A) - Y
grad = X.T @ err / len(X) # W 全体についての勾配
gB, gA = grad @ A.T, B.T @ grad # B と A についての勾配(連鎖律)
B -= lr * gB
A -= lr * gA
if step in [1, 300, 3000]:
print(step, round(float(np.linalg.norm(B @ A - delta_true) / np.linalg.norm(delta_true)), 4))
実行結果:
4 16,777,216 32,768 0.20%
8 16,777,216 65,536 0.39%
16 16,777,216 131,072 0.78%
64 16,777,216 524,288 3.12%
1 1.0
300 0.0557
3000 0.0
1〜4 行目は、ランク r、ΔW をそのまま学習する場合の数(1,677 万個)、LoRA で学習する数、その割合です。r = 8 なら、学習させる数は 0.39% で済みます。
5〜7 行目は、64 × 64 の小さな層で、ランク 4 の LoRA を学習させたときの、学習した B × A と本当の変化との差の割合です。B を 0 から始めているので、学習の最初は差が 1.0(何も学んでいない)で、300 回で 5.6%、3,000 回でほぼ 0 になりました。4,096 個の重みの変化を、512 個(64 × 4 × 2)の数だけで正確に学べたことになります。
LoRA の B × A は、学習が終わったら W₀ に足し込んでしまえるので、推論の速さは元のモデルと変わりません。仕事ごとに小さな B と A だけを保存しておき、切り替えて使うこともできます。2023 年の QLoRA は、元の重み W₀ を 4 ビットに量子化したうえで LoRA を学習させ、650 億パラメーターのモデルのファインチューニングを、48 GB の GPU 1 枚でできるようにしました。
専門家の混合(MoE):一部の層だけを使う
トークンごとに専門家を選ぶ
専門家の混合(MoE:Mixture of Experts)は、Transformer の全結合層を、いくつもの「専門家」(それぞれ別の全結合層)に置き換え、トークンごとに そのうちのいくつかだけを使う方法です。どの専門家を使うかは、ルーター(ゲート)と呼ばれる小さな層が、トークンのベクトルから点数を出して決めます。8 つの専門家から上位 2 つを選ぶ場合で試してみます。
import numpy as np
rng = np.random.default_rng(0)
d, n_experts, top_k = 16, 8, 2
experts = [rng.normal(0, 0.3, (d, d)) for _ in range(n_experts)] # 専門家(全結合層)を 8 つ用意する
W_gate = rng.normal(0, 1, (d, n_experts)) # どの専門家に送るかを決める層
def moe(x):
logits = x @ W_gate
chosen = np.argsort(-logits)[:top_k] # 点数の高い 2 つの専門家だけを使う
w = np.exp(logits[chosen] - logits[chosen].max()); w /= w.sum()
return sum(wi * np.tanh(x @ experts[i]) for wi, i in zip(w, chosen)), chosen, w
y, chosen, w = moe(rng.normal(0, 1, d))
print(chosen, np.round(w, 3))
tokens = rng.normal(0, 1, (10000, d))
counts = np.bincount([i for t in tokens for i in moe(t)[1]], minlength=n_experts)
print(counts, round(counts.max() / counts.mean(), 2)) # 専門家ごとに選ばれた回数と、偏りの大きさ
実行結果:
[4 2] [0.982 0.018]
[2512 2308 2906 2580 2474 2782 2304 2134] 1.16
1 行目は、あるトークンで選ばれた専門家(4 番と 2 番)と、その出力を混ぜる重み(0.982 と 0.018)です。8 つの専門家のうち 2 つしか計算しないので、全結合層の計算は 4 分の 1 で済みます。
2 行目は、1 万個のトークンでそれぞれの専門家が選ばれた回数と、いちばん多い専門家が平均の何倍選ばれたかです。ここでは乱数で作ったルーターなので 1.16 倍と比較的そろっていますが、学習中は、一部の専門家ばかりが選ばれて、ほかの専門家が学習されなくなる偏りが起きやすくなります。そのため、MoE の学習では、選ばれる回数がそろうように損失に項を足す(負荷分散)のがふつうです。
MoE の利点は、パラメーターの総数(知識を蓄える量)を増やしても、1 トークンあたりの計算は増えないことです。2024 年の Mixtral 8x7B は、総数約 470 億のパラメーターを持ちながら、1 トークンの処理に使うのは約 130 億だけです。ただし、使わない専門家も含めてすべての重みをメモリーに置く必要があるので、メモリーは減りません。
KV キャッシュ:生成中のメモリーを減らす
文脈が長いほど大きくなる
LLM が文章を 1 トークンずつ生成するとき、前のトークンの Key と Value は変わらないので、計算し直さずに保存して使い回します(KV キャッシュ、Transformer の記事 参照)。このキャッシュは、文脈が長くなるほど大きくなります。7B クラスのモデル(32 層・ヘッドの次元 128)で計算してみます。
# KV キャッシュの大きさ:Key と Value の 2 つ × 層の数 × KV のヘッドの数 × ヘッドの次元 × トークンの数 × 2 バイト
def kv_cache_gib(layers, kv_heads, head_dim, tokens, bytes_per=2):
return 2 * layers * kv_heads * head_dim * tokens * bytes_per / 1024**3
for kv_heads in [32, 8]: # ヘッドごとに K・V を持つ / 4 つのヘッドで 1 組の K・V を共有する(GQA)
print(kv_heads, [round(kv_cache_gib(32, kv_heads, 128, n), 2) for n in [4096, 32768, 131072]])
実行結果:
32 [2.0, 16.0, 64.0]
8 [0.5, 4.0, 16.0]
左から、Key と Value のヘッドの数、文脈が 4,096・32,768・131,072 トークンのときの KV キャッシュの大きさ(GiB)です。32 個のヘッドがそれぞれ Key と Value を持つと、13 万トークンの文脈では 64 GiB にもなり、16 ビットの重み(約 13 GiB)よりずっと大きくなります。
そこで、複数の Query のヘッドで 1 組の Key と Value を共有する GQA(Grouped-Query Attention)がよく使われます。4 つのヘッドで 1 組を共有して 8 組にすると、KV キャッシュは 4 分の 1 の 16 GiB になります。すべてのヘッドで 1 組だけを共有する方法は MQA(Multi-Query Attention)と呼ばれます。
ほかにも、KV キャッシュ自体を 8 ビットに量子化したり、メモリーをページ単位で管理して無駄をなくしたり(vLLM の PagedAttention)する工夫が使われています。また、小さなモデルで数トークン先まで下書きし、大きなモデルでまとめて確かめる 投機的デコーディングは、答えを変えずに生成を速くする方法として広く使われています。
LLM を軽くする工夫の流れ
この記事で紹介した手法を年代順に並べます。
| 年 | 手法 | ポイント |
|---|---|---|
| 2015 | 知識の蒸留(ヒントンら) | 温度を上げた先生の確率をまねて、小さなモデルを学習する |
| 2015 | Deep Compression | 枝刈り・量子化・符号化で、モデルを数十分の 1 に圧縮する |
| 2017 | 混合精度の学習・MoE の大規模化 | 16 ビットで学習する。トークンごとに一部の専門家だけを使う |
| 2019 | bfloat16 の普及・DistilBERT・MQA | 範囲の広い 16 ビット形式。蒸留した BERT。K・V を全ヘッドで共有 |
| 2021 | LoRA・Switch Transformer | 低ランクの変化だけを学習する。専門家を 1 つだけ選ぶ MoE |
| 2022 | LLM.int8()・GPTQ・投機的デコーディング | 外れ値を分けた 8 ビット化。誤差を打ち消しながら 4 ビット化。下書きと確認で速く生成 |
| 2023 | QLoRA・AWQ・GQA・PagedAttention | 4 ビットのモデルに LoRA。重要な重みを守る量子化。K・V をグループで共有。KV キャッシュのメモリー管理 |
| 2024 | Mixtral 8x7B | 公開された MoE の LLM。総数約 470 億のうち約 130 億だけを使う |
まとめ
- LLM の重みのメモリーは「パラメーターの数 × ビット数 ÷ 8」で、7B のモデルは 16 ビットで約 13 GiB、4 ビットで約 3.3 GiB
- 16 ビットの形式のうち、float16 は細かさを、bfloat16 は表せる範囲を優先している。学習ではあふれにくい bfloat16 がよく使われる
- 量子化は、重みを倍率と整数の組で表す。外れ値があると大多数の重みが粗くなるので、行やグループごとに倍率を持ち、外れ値を特別に扱う
- 知識の蒸留は、温度を上げた先生の確率をまねることで、正解以外のクラスへの「迷い方」まで小さなモデルに教える
- LoRA は元の重みを固定し、変化の分だけを 2 つの細長い行列の積で学習する。学習させる数は 1% 未満で済む
- MoE は、トークンごとに一部の専門家だけを使い、パラメーターの総数を増やしても計算を増やさない。ただしメモリーは減らない
- KV キャッシュは文脈の長さに比例して大きくなり、GQA で Key と Value をヘッドどうしで共有して減らす
参考
- G. Hinton, O. Vinyals and J. Dean, "Distilling the Knowledge in a Neural Network", 2015(arXiv:1503.02531)
- S. Han et al., "Deep Compression: Compressing Deep Neural Networks with Pruning, Trained Quantization and Huffman Coding", ICLR 2016(arXiv:1510.00149)
- N. Shazeer et al., "Outrageously Large Neural Networks: The Sparsely-Gated Mixture-of-Experts Layer", ICLR 2017(arXiv:1701.06538)
- P. Micikevicius et al., "Mixed Precision Training", ICLR 2018(arXiv:1710.03740)
- D. Kalamkar et al., "A Study of BFLOAT16 for Deep Learning Training", 2019(arXiv:1905.12322)
- V. Sanh et al., "DistilBERT, a distilled version of BERT: smaller, faster, cheaper and lighter", 2019(arXiv:1910.01108)
- N. Shazeer, "Fast Transformer Decoding: One Write-Head is All You Need", 2019(arXiv:1911.02150)
- W. Fedus et al., "Switch Transformers: Scaling to Trillion Parameter Models with Simple and Efficient Sparsity", JMLR 2022(arXiv:2101.03961)
- E. J. Hu et al., "LoRA: Low-Rank Adaptation of Large Language Models", ICLR 2022(arXiv:2106.09685)
- T. Dettmers et al., "LLM.int8(): 8-bit Matrix Multiplication for Transformers at Scale", NeurIPS 2022(arXiv:2208.07339)
- E. Frantar et al., "GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers", ICLR 2023(arXiv:2210.17323)
- Y. Leviathan et al., "Fast Inference from Transformers via Speculative Decoding", ICML 2023(arXiv:2211.17192)
- T. Dettmers et al., "QLoRA: Efficient Finetuning of Quantized LLMs", NeurIPS 2023(arXiv:2305.14314)
- J. Lin et al., "AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration", MLSys 2024(arXiv:2306.00978)
- J. Ainslie et al., "GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints", EMNLP 2023(arXiv:2305.13245)
- W. Kwon et al., "Efficient Memory Management for Large Language Model Serving with PagedAttention", SOSP 2023(arXiv:2309.06180)
- A. Q. Jiang et al., "Mixtral of Experts", 2024(arXiv:2401.04088)