欢迎光临
我们一直在努力

批大小翻倍吞吐没翻倍:被补零吃掉的那部分算力

版权与内容来源声明
本文为原创整理。文中涉及官方文档、开源仓库、论文与公开报道的内容,均在附表 A 中标注来源;引用官方原文保持原样,不作改写。文中命令、版本号与界面截图以本文成文时的实测/核验结果为准,标注「待验证」的部分请以你本地环境实际输出为判断依据。本文不推荐任何不合规的软件获取方式,也不对任何收益结果作承诺。转载请注明出处。

摘要:截至 2026-10-28,我用 1024 段自造文本(20 到 603 token,中位数 55)把「批内补到最长」这笔账算了一遍:批大小从 8 加到 256,有效 token 占比只从 38.20% 掉到 32.97%;同一批语料按长度排序后再切批,256 这一档能回到 72.32%,64 这一档能到 89.71%。另一支脚本量出 [128, 603, 384] 的 float32 激活峰值是 226.13 MB,正好等于 2 × 128 × 603 × 384 × 4 字节。把两个数放在一起,「批大小翻倍、吞吐不翻倍」就不需要解释了:翻倍的时候,有效部分和补出来的无效部分一起翻倍,比值不动,显存线性涨。

先分清三块开销:权重、激活、补零后的无效 token

一次向量化调用只有三块开销,其中只有一块随批大小变——而第三块不是独立的内存项,它是个乘数,会把前两块一起放大。

权重:模型的参数,加载一次就常驻,跟这一批送进来 1 条还是 256 条没有关系。它决定的是「你能不能把模型装下」,不决定「一批能塞多少」。

激活:这一批数据在模型内部流过的中间张量。它的形状里带批大小 B 和序列长度 L,所以它随 B 涨。这是唯一一块你能用批大小去调的开销。

补零:一个批次里的文本长度参差不齐,框架会把短的补到批内最长,好让它们拼成一个规整矩阵。补出来的位置就是无效 token:它要占内存,要走完整个前向计算,但不产出任何有用的向量。

三者的差别一句话说完:权重是常数,激活是变量,补零是把变量的系数改大的那只手。多数人卡住的地方在于把补零当成「一点点填充」,而它实际改的是 L——激活的第二个维度。

开销由什么决定换批大小时怎么变常见误判
权重 参数量 × 每参数字节 不变,常驻 以为「批大一点更划算」,其实摊薄的是常数项
激活 B × L × 维度 × 同时存活的份数 随 B 线性上涨 用平均长度估,忽略批内最长那条
补零无效 token 批内最长减去每条真实长度 与 B 无关,但它决定上面那行的 L 只统计真实 token,看不见补出来的算力

这不是我猜的默认行为,是框架文档自己写的。Hugging Face 的 tokenizer 文档在 padding 参数下写着:True or 'longest': Pad to the longest sequence in the batch。也就是说,你不显式指定 max_length 的时候,补零长度就是「这一批里最长的那条」,每批都不一样。

补零之后,L 会比你想象的大得多。sentence-transformers 的《Computing Embeddings》在讲解输入长度时写了两句关键的话。一句是 For transformer models like BERT, RoBERTa, DistilBERT etc., the runtime and memory requirement grows quadratic with the input length. 意思是 L 被补大到 10 倍,注意力那部分中间量按百倍量级走,不是 10 倍。另一句是 Longer texts will be truncated to the first model.max_seq_length tokens,说的是模型有一个硬上限,超了会被静默截断。

这两句话合起来给出一个不好受的结论:L 有一个被模型卡死的天花板,而补零会把你往天花板上推。你送进去的 128 条短句里只要混进一条接近上限的长块,这一批的 L 就整体抬到上限附近,其余 127 条全部按最贵的方式算一遍。

这就是「批大小翻倍、吞吐没翻倍」的第一层原因。它不是框架实现得不好,而是「按批内最长补零」这个默认策略在长度离散时必然产生的代价。

每块怎么估:两个乘法加一个除法

三块开销分别对应两次乘法和一次除法,纸笔五分钟能算完,不必等它跑崩了再回头找原因。

乘法一,权重:显存 = 参数量 × 每参数字节。FP32 是 4 字节,7B 参数就是 28 GB;换成半精度是 14 GB。这个数跟批大小完全无关,你唯一能做的是选模型、选精度。

乘法二,激活:峰值 ≈ B × L × d × 4 × k。d 是隐藏维度,4 是 FP32 的字节数,k 是「同一时刻活着的份数」——一份激活加一份中间拷贝,k 就是 2。k 不是你该背下来的常数,是你实现里同时存在的张量数量,第 4 章会用一个脚本把它标定出来。

除法,有效占比:有效 token 占比 = 每条真实长度之和 ÷ 每批「批大小 × 批内最长」之和。它衡量的是「你付了多少钱,买到多少有效计算」。

要估的量算式代入一组数容易错在哪
权重占用 参数量 × 4 字节(FP32) 7B 参数 → 28 GB 把它摊到每条样本上算,越算越乱
激活峰值 B × L × d × 4 × k 128 × 603 × 384 × 4 × 2 = 226 MB L 用平均值,k 靠感觉拍
补零后的总算力 B × 批内最长 × 批数 1024 条按 128 切,每批都按 603 算 只统计真实 token,账面上永远「很便宜」
有效占比 Σ真实长度 ÷ Σ补零后长度 宽分布语料 33% 换成「条每秒」这个指标就再也看不见它
单条实际成本 峰值 ÷ 有效 token 数 226 MB ÷ 33% 分母用了补零后的 token,成本被低估 3 倍

拿最后一行的除法举个例子。同样一批 1024 条文本,按 128 切,L 取 603,峰值 226 MB 是确定的;但有效占比只有 33%,等于你为每 1 个有效 token 付了 3 个 token 的内存和算力。如果这批语料换成等长的 252 token,L 只有 252,峰值降到 95 MB 以内,有效占比 98.5%,同样的钱买到的东西多了三倍。

这里有一条必须说清的边界:上面这个乘法只算了线性项。注意力那部分的中间量是 B × 头数 × L 的平方量级,官方文档已经写明它随输入长度二次增长,所以本文这套算式给的是下界,不是峰值上限。真实模型上你量到的数会比公式大,大多少取决于有多少层、多少个头、用的什么注意力实现。

再给一个反例,说明这套账最常错在哪。有人看平均长度是 55,按 128 的批大小估出 128 × 55 × 384 × 4 × 2 ≈ 20.6 MB,觉得一张 24 GB 的卡能开到批大小几千。实际上一批里混进一条 603 token 的长块,峰值就是 226 MB,是估算值的 11 倍。平均值在这里是个有害的指标,因为它把「一条长的拖死全批」这件事平均掉了。

判据就立在这里:**估激活用批内最长,不用平均值;估吞吐用有效占比,不用条每秒。**两个指标换错一个,后面的容量规划就全歪。

三档批大小各适合什么

批大小不是一个「越大越好」的旋钮,它只在长度同质时才是旋钮;长度离散时,它只是把浪费按比例放大。

先把三组语料的数字摆出来,都是 1024 条、同一套切批逻辑、只用 tiktoken 数真实长度;脚本与完整输出放在第 5 章,可以逐行对:

  • 等长语料,长度 252 到 258,中位数 254:批大小从 8 加到 256,有效占比 99.05% → 98.48%,几乎不动。
  • 窄分布,长度 180 到 265,中位数 223,最长是中位数的 1.2 倍:87.38% → 84.09%,掉了 3.3 个百分点。
  • 宽分布,六成是 20 到 60 token 的短句、四成是 300 到 600 token 的长块,实测 20 到 603,中位数 55,最长是中位数的 11 倍:38.20% → 32.97%,掉了 5.2 个百分点。

关键读法在第三行。宽分布下,批大小才 8 的时候有效占比就已经只有 38% 了。原因是这个混合比例下,8 条里出现一条长块的概率接近 98%,一条长的就把整批的 L 拉到 603,剩下 7 条短句全部陪着补到 603。批再往上加,也只是让更多短句一起陪跑。

所以「批大小翻倍吞吐没翻倍」的机制不是「批太大」,而是「批里混了长度差 11 倍的样本」。翻倍的那一刻,分子(有效 token)和分母(补零后 token)同步翻倍,比值不动,吞吐不动,显存翻倍。第 4 章的实测会把这个「显存翻倍」量成 16.00 倍,一分不少。

档位批大小适用场景前置条件本文实测的有效占比
小批 1 到 16 在线查询编码,单条延迟敏感 不需要分桶 宽分布 38.20%(批大小 8)
中批 32 到 64 离线索引构建的默认档 按长度分桶后 分桶后 89.71% 到 95.40%
大批 128 到 256 长度已经同质的批量任务 L 已经统一,或显式设了 max_length 等长语料 98.48% 到 98.53%

这张表最容易被忽略的是中批那一行的「前置条件」。把 32 到 64 当默认档,理由不是它算力利用率最高,而是它在「分桶成本」和「补零浪费」之间取了个平衡点——本文窄分布语料上这一档是 84.79% 到 85.22%,宽分布排序之后是 89.71% 到 95.40%。

还有一个反直觉的数字要摆出来:按长度排序之后,批大小 256 只有 72.32%,反而比批大小 64 的 89.71% 低 17 个百分点。原因在切法上:排序后按 256 一条硬切,一段里必然跨过「短句组」和「长块组」的分界,段内又变回混批;按 64 一条切,一段大多落在同一组里,段内长度差被压住了。分桶解决的是「批内同质」,批大小决定的是「一段里还剩多少样本」——两者互相牵制,所以排序之后再把批拉到 256,等于把刚解决的问题又请回来。

三条判断可以直接拿去用:

长度分布的最长值除以中位数小于 1.2,说明你的语料本来就同质,批大小直接取 64,不必折腾分桶;这个比例落在 1.2 到 3 之间,先上分桶,批大小保持 32 到 64;比例超过 3,说明短句和长块在同一个池子里,这时候调批大小没有意义,得先决定「要不要把它们放在同一个索引里」。

用一个最小脚本量出峰值内存

峰值就写在算式里,实测系数 1.00,脚本六十来行,不用显卡也能把账对平。

长度取第 5 章那批宽分布语料的中位数 55 与最大值 603,维度取 384(这是常见的小型句向量模型输出维度),构造 [B, L, 384] 的 float32 激活数组,再复制一份当中间缓冲,用 tracemalloc 量峰值。

🧪 实测环境:Python 3.13.12 / macOS / numpy 2.5.4(脚本:work/exp1028/e03_mem.py,在项目根目录下运行)

"""e03_mem.py —— [B, L, 384] float32 激活 + 一份中间拷贝,峰值内存怎么随 B、L 走。

tracemalloc 量的是 Python 侧追踪到的分配峰值(numpy 缓冲区的分配走它自己的
trace domain,也在统计范围内,正文有说明)。每档重复 3 次取中位数。
"""
import gc
import statistics
import tracemalloc

import numpy as np

D = 384
BS = [1, 8, 32, 128]
LS = [55, 603] # 取自实验 A 宽分布语料的中位数与最大值
COPIES = 2 # 一份激活 + 一份中间拷贝

def peak_mb(B, L, with_weight=False):
gc.collect()
tracemalloc.start()
w = np.zeros((D, 3072), dtype=np.float32) if with_weight else None
x = np.zeros((B, L, D), dtype=np.float32)
y = x.copy()
y += 1.0
_, peak = tracemalloc.get_traced_memory()
tracemalloc.stop()
del x, y, w
return peak / 1024 / 1024

def theory_mb(B, L):
return B * L * D * 4 * COPIES / 1024 / 1024

def med(B, L, **kw):
return statistics.median(peak_mb(B, L, **kw) for _ in range(3))

print("激活峰值(float32,d=384,一次激活 + 一次拷贝)")
print(f"{'B':>4}{'L=55 峰值':>12}{'理论':>9}{'系数':>7}"
f"{'L=603 峰值':>13}{'理论':>9}{'系数':>7}")
grid = {}
for B in BS:
a = med(B, LS[0])
b = med(B, LS[1])
grid[B] = (a, b)
print(f"{B:>4}{a:>12.2f}{theory_mb(B, LS[0]):>9.2f}{a / theory_mb(B, LS[0]):>7.2f}"
f"{b:>13.2f}{theory_mb(B, LS[1]):>9.2f}{b / theory_mb(B, LS[1]):>7.2f}")

print()
print(f"B 从 8 到 128(16 倍),L=603 峰值 "
f"{grid[8][1]:.2f} → {grid[128][1]:.2f} MB,"
f"倍率 {grid[128][1] / grid[8][1]:.2f}x")
print(f"L 从 55 到 603({603 / 55:.2f} 倍),B=128 峰值 "
f"{grid[128][0]:.2f} → {grid[128][1]:.2f} MB,"
f"倍率 {grid[128][1] / grid[128][0]:.2f}x")

print()
print("同一脚本加一块常驻权重(384×3072 float32 ≈ 4.5 MB,占位用,不是真实模型)")
w8 = med(8, 603, with_weight=True)
w128 = med(128, 603, with_weight=True)
print(f"B=8 峰值 {w8:.2f} MB")
print(f"B=128 峰值 {w128:.2f} MB")
print(f"两者比值 {w128 / w8:.2f}x,小于纯激活的 "
f"{grid[128][1] / grid[8][1]:.2f}x —— 常驻部分不随 batch 变,只是被摊薄了")
print(f"固定补到 512 的对照:B=128 峰值 {med(128, 512):.2f} MB")

激活峰值(float32,d=384,一次激活 + 一次拷贝)
B L=55 峰值 理论 系数 L=603 峰值 理论 系数
1 0.16 0.16 1.00 1.77 1.77 1.00
8 1.29 1.29 1.00 14.13 14.13 1.00
32 5.16 5.16 1.00 56.53 56.53 1.00
128 20.63 20.62 1.00 226.13 226.12 1.00

B 从 8 到 128(16 倍),L=603 峰值 14.13 → 226.13 MB,倍率 16.00x
L 从 55 到 603(10.96 倍),B=128 峰值 20.63 → 226.13 MB,倍率 10.96x

同一脚本加一块常驻权重(384×3072 float32 ≈ 4.5 MB,占位用,不是真实模型)
B=8 峰值 18.63 MB
B=128 峰值 230.63 MB
两者比值 12.38x,小于纯激活的 16.00x —— 常驻部分不随 batch 变,只是被摊薄了
固定补到 512 的对照:B=128 峰值 192.00 MB

四行读法,每行对应一个判断。

**第一,系数是 1.00,不是 0.9 也不是 1.2。**六格数据里「实测 ÷ 理论」全是 1.00,说明这块账没有隐藏开销:numpy 的数组缓冲区是整块分配的,不像 Python 对象那样每个都带一堆头部。所以「B × L × d × 4 × k」可以直接当公式用,k 取 2。

**第二,两条都是直线。**批大小从 8 到 128 是 16 倍,峰值 14.13 → 226.13 MB 也是 16.00 倍;长度从 55 到 603 是 10.96 倍,同批大小下峰值 20.63 → 226.13 MB 也是 10.96 倍。线性关系不需要拟合,除法一除就出来了。

**第三,常驻的权重会把比例摊薄。**加一块 4.5 MB 的占位权重之后,批大小 8 的峰值是 18.63 MB,批大小 128 是 230.63 MB,比值 12.38 倍,小于纯激活的 16.00 倍。这解释了另一个常见错觉:模型越大,你越感觉不到「批大小把内存拉爆」,因为常数项把比例压住了——但压住的只是你的感受,不是绝对增量,增量仍然是 212 MB。

**第四,固定补到 512 是一个「稳定但不省」的选择。**批大小 128、固定长度 512 时峰值 192.00 MB,比动态补零最坏情况的 226.13 MB 小,但比按中位数补的 20.63 MB 大 9.3 倍。它的价值在于每批峰值可预测,不在省内存。这也是把它写进配置时最该想清楚的一点:你换的是稳定性,不是成本。

关于 tracemalloc 这个工具,有一条必须写进正文的限制。它的文档原话是 The tracemalloc module is a debug tool to trace memory blocks allocated by Python.,并且写明 tracemalloc uses the domain 0 to trace memory allocations made by Python. C extensions can use other domains to trace other resources.。numpy 2.5.4 把数组分配放在了自己注册的 domain 里(np.lib.tracemalloc_domain,本次实测值是 389047),而 get_traced_memory() 返回的是所有 domain 的合计,所以上面这些数能跟理论值对上。

但这套账有三个边界:一是它只覆盖 Python 进程内的分配,显卡上的显存不在其中;二是 C 扩展自己申请内存、又没有注册 domain 的部分看不见;三是框架的 workspace、推理后端内部的临时缓冲也不在内。所以这些数是「进程内 CPU 缓冲的账」,不是「显存精确值」。想知道显存,把同一套算式里的 d、k、L 换成本地实现的实际值,再用显卡侧的内存统计去标定系数——这一步本文没有实测,别把这里的 1.00 直接搬到卡上。

峰值内存速算卡:本章这套算式做成了一张四格表(批大小、长度、维度、份数填进去出峰值),另附 tracemalloc 的计量模板,跟脚本里的写法一一对应。放在资料包里,扫码即可获取:

配套资料获取

按现象排障:现象 → 原因 → 处置

五种现象对应五个不同的处置,其中只有一种该动批大小——把批大小当通用解法,是这类问题最常见的返工来源。

先把补零这笔账跑出来。下面这支脚本用 tiktoken 数真实 token,再模拟「批内补到批内最长」,三组语料(等长、窄分布、宽分布)各 1024 条,批按固定种子打乱后顺序切分,与常见的打乱式取批一致。

🧪 实测环境:Python 3.13.12 / macOS / tiktoken 0.14.0(cl100k_base 编码;脚本:work/exp1028/e03_pad.py,在项目根目录下运行)

"""e03_pad.py —— 批内补 0 到「批内最长」,有效 token 占比随批大小怎么掉。

自造三组语料(等长 / 窄分布 / 宽分布),长度全部用 tiktoken cl100k_base 实测,
不用估算值。批按固定 seed 打乱后顺序切分,与 DataLoader(shuffle=True) 的切法一致。
无计时读数,输出可复现。
"""
import random
import statistics

import tiktoken

enc = tiktoken.get_encoding("cl100k_base")

WORDS = [
"向量检索", "批处理", "显存占用", "切片长度", "重排阶段", "召回条数",
"索引构建", "查询改写", "上下文窗口", "余弦相似度", "倒排表", "缓存命中",
"并发路数", "补零长度", "序列长度", "吞吐表现", "响应时间", "采样比例",
"分块策略", "向量维度",
]
WLEN = {w: len(enc.encode(w)) for w in WORDS}

N = 1024
BATCHES = [8, 16, 32, 64, 128, 256]

def make_text(target, rng):
"""按目标 token 数拼词,实际长度最后用编码器回测。"""
parts, n = [], 0
while n < target:
w = rng.choice(WORDS)
parts.append(w)
n += WLEN[w]
return "".join(parts)

def build(targets, seed):
rng = random.Random(seed)
texts = [make_text(t, rng) for t in targets]
lens = [len(enc.encode(t)) for t in texts]
return lens

rng_t = random.Random(20261028)
corpora = {}
corpora["等长"] = build([252] * N, 1)
corpora["窄分布"] = build([rng_t.randint(180, 260) for _ in range(N)], 2)
wide = [rng_t.randint(20, 60) if rng_t.random() < 0.6
else rng_t.randint(300, 600) for _ in range(N)]
corpora["宽分布"] = build(wide, 3)

def effective_ratio(lens, B):
"""按批切分,批内补到最长,返回有效 token / 补零后 token。"""
eff = pad = 0
for i in range(0, len(lens), B):
chunk = lens[i:i + B]
m = max(chunk)
eff += sum(chunk)
pad += m * len(chunk)
return eff / pad

def shuffled(lens, seed):
out = list(lens)
random.Random(seed).shuffle(out)
return out

print("语料长度(tiktoken cl100k_base 实测,单位 token)")
print(f"{'语料':<8}{'样本':>6}{'最短':>7}{'中位数':>8}{'最长':>7}{'最长/中位':>10}")
stats = {}
for name, lens in corpora.items():
med = statistics.median(lens)
stats[name] = (med, max(lens))
print(f"{name:<8}{len(lens):>6}{min(lens):>7}{med:>8.0f}{max(lens):>7}"
f"{max(lens) / med:>9.1f}x")

print()
print("批内补零后的有效 token 占比(%,其余是补出来的无效 token)")
head = f"{'批大小':<7}{'等长':>9}{'窄分布':>9}{'宽分布':>9}{'宽分布+按长度排序':>18}"
print(head)
rows = []
for B in BATCHES:
eq = effective_ratio(shuffled(corpora["等长"], 7), B)
nar = effective_ratio(shuffled(corpora["窄分布"], 7), B)
wid = effective_ratio(shuffled(corpora["宽分布"], 7), B)
srt = effective_ratio(sorted(corpora["宽分布"]), B)
rows.append((B, eq, nar, wid, srt))
print(f"{B:<7}{eq * 100:>9.2f}{nar * 100:>9.2f}{wid * 100:>9.2f}{srt * 100:>18.2f}")

print()
b0 = rows[0]
b1 = rows[–1]
print(f"宽分布:批大小 8 → 256,有效占比 {b0[3] * 100:.2f}% → {b1[3] * 100:.2f}%,"
f"掉了 {(b0[3] – b1[3]) * 100:.2f} 个百分点")
print(f"宽分布按长度排序后:同一批大小下从 {b1[3] * 100:.2f}% 回到 {b1[4] * 100:.2f}%")
print(f"实验 B 取用的两档长度:中位数 {stats['宽分布'][0]:.0f} / 最大值 {stats['宽分布'][1]}")

语料长度(tiktoken cl100k_base 实测,单位 token)
语料 样本 最短 中位数 最长 最长/中位
等长 1024 252 254 258 1.0x
窄分布 1024 180 223 265 1.2x
宽分布 1024 20 55 603 11.0x

批内补零后的有效 token 占比(%,其余是补出来的无效 token)
批大小 等长 窄分布 宽分布 宽分布+按长度排序
8 99.05 87.38 38.20 99.08
16 98.85 85.99 35.44 98.47
32 98.70 85.22 34.11 95.40
64 98.55 84.79 33.34 89.71
128 98.53 84.37 33.07 80.15
256 98.48 84.09 32.97 72.32

宽分布:批大小 8 → 256,有效占比 38.20% → 32.97%,掉了 5.23 个百分点
宽分布按长度排序后:同一批大小下从 32.97% 回到 72.32%
实验 B 取用的两档长度:中位数 55 / 最大值 603

输出里最后两行是关键:宽分布语料从批大小 8 到 256,有效占比只掉 5.23 个百分点;而按长度排序后再切批,同一个批大小能从 32.97% 回到 72.32%。加批大小买不到的东西,排序能买到。

排障表如下,按「先看现象,再看原因,最后才动手」的顺序读:

现象大概率原因处置
批大小从 32 加到 128,条每秒只涨一成,显存涨约 4 倍 有效占比没变(本文宽分布 33%),补零把增量吃掉了 先算有效占比;低于 85% 就别动批大小,去分桶
显存峰值远超「平均长度 × 批大小」的估算 峰值由批内最长那条决定,不是由平均决定 用长度分布的 p99 设 max_length,同时统计被截断的条数
每条耗时抖动五到十倍 单批耗时由批内最长决定,批与批之间长度差太大 按长度分桶,桶内最长与中位数控制在 1.2 倍内
检索质量下降,长文档答非所问 超过 max_seq_length 被静默截断,只留了前一段 先切块再向量化,切块长度对齐模型上限
加机器、加并发都不见快 单批已经吃满,算力空转在补出来的位置上 改 padding 策略与分桶,不是加卡

这张表里最值得记住的是第一行和最后一行。第一行告诉你「调批大小」在长度离散时是个无效动作;最后一行告诉你,「加机器」在有效占比低的时候是拿钱买同样的浪费。

收尾给三条可执行的判断,都是本文实测数据支撑的:

**一、先量最长值 ÷ 中位数。**这个比值小于 1.2,说明语料同质,批大小直接取 64,本文等长语料在这一档的有效占比是 98.5%,再往上调没有收益。**二、比值在 1.2 到 3 之间,先分桶再谈批大小。**分桶之后批大小停在 32 到 64,本文分桶后 64 档是 89.71%。**三、比值超过 3,先决定短句和长块要不要进同一个索引。**本文宽分布是 11 倍,这种语料上批大小怎么调都是在给补零付钱。

停手条件也划一条:有效占比到 85% 以上、峰值不超过可用内存的六成,两个条件同时满足就停手。再往下调,换来的通常不是吞吐,而是尾部批次的抖动和更难复现的容量事故。

反过来,如果你现在的动作是「把批从 32 调到 128 试试能不能快一点」,那这一步大概率是白做的——先把长度分布打出来,看一眼最长值除以中位数,这一步五分钟,比调参一整天有用。

批大小与分桶速查表:本章这张排障表和分桶阈值的判断顺序,整理成了一页打印版,含「长度分布 → 该不该分桶 → 批大小取多少」的三段判断。放在资料包里,扫码即可获取:

配套资料获取

附表 A:本文引用事实与出处对照表

事实出处本文位置
True 或 'longest' 表示补到批内最长序列,原文为 True or 'longest': Pad to the longest sequence in the batch (or no padding if only a single sequence is provided). Hugging Face Transformers 文档《Tokenizer》→ PreTrainedTokenizer.__call__ 的 padding 参数说明,Hugging Face 发布,https://huggingface.co/docs/transformers/main/en/main_classes/tokenizer 第 1 章
'max_length' 表示补到指定的最大长度,原文为 'max_length': Pad to a maximum length specified with the argument max_length or to the maximum acceptable input length for the model if that argument is not provided. 同上 第 2、4 章
运行时与内存需求随输入长度二次增长,原文为 For transformer models like BERT, RoBERTa, DistilBERT etc., the runtime and memory requirement grows quadratic with the input length. sentence-transformers 文档《Computing Embeddings》→ Input Sequence Length 一节,https://sbert.net/examples/sentence_transformer/applications/computing-embeddings/README.html 第 1、2 章
超出上限的文本会被截断到前 model.max_seq_length 个 token,原文为 Longer texts will be truncated to the first model.max_seq_length tokens 同上 第 1、5 章
tracemalloc 追踪的是 Python 分配的内存块,原文为 The tracemalloc module is a debug tool to trace memory blocks allocated by Python. Python 3 标准库文档《tracemalloc — Trace memory allocations》,Python Software Foundation,https://docs.python.org/3/library/tracemalloc.html 第 4 章
domain 0 只对应 Python 侧的分配,C 扩展可用其他 domain,原文为 tracemalloc uses the domain 0 to trace memory allocations made by Python. C extensions can use other domains to trace other resources. 同上,Filter.domain 与 Trace.domain 条目 第 4 章
三组 1024 条语料的有效占比(等长 99.05% → 98.48%、窄分布 87.38% → 84.09%、宽分布 38.20% → 32.97%、宽分布排序后 99.08% → 72.32%),以及长度统计(宽分布最短 20、中位 55、最长 603) 本文实跑,work/exp1028/e03_pad.py,输出见第 5 章代码块 第 3、5 章
[B, L, 384] float32 激活加一份拷贝的峰值(1.77 到 226.13 MB)、实测系数 1.00、加常驻权重后比值 12.38 倍、固定补 512 时 192.00 MB 本文实跑,work/exp1028/e03_mem.py,输出见第 4 章代码块 第 2、4 章

写在最后:这篇用到的资料

写这篇文章时,把相关的官方文档和源码又翻了一遍,顺手也整理了几份配套的东西:

  • 大模型学习路线图:从零基础到能自己动手做 Agent,按阶段说明每一步该学什么、哪些可以先跳过
  • 《LangChain + LangGraph + MCP 智能体开发实战》视频课:7 个模块,从私有化部署、Embedding+RAG 到 MCP+Agent 全流程
  • AI 大模型知识库(在线可查):Agent Skills 从入门到落地、Claude Skills 完全指南等专题,按目录浏览即可
  • 640 套 AI 大模型行业报告 + 经典 PDF 书籍:看行业落地案例和别人怎么做的时候用得上
  • 大模型零基础到精通教学视频:跟着敲一遍,比只读文档快得多

资料是我自己整理的,放在下面这个码上,扫码即可获取:

配套资料获取
课程目录:私有化部署与 LangChain
课程总目录:7 个模块
在线知识库:Agent Skills 创建全流程
课程目录:LangGraph 开发智能体
在线知识库:Claude Skills 完全指南

添加时备注「AI」,优先通过。

资料按「先路线、再动手、最后查漏」的顺序整理好了,建议先看学习路线那一份,照着它挑一条适合自己当前基础的路径再往下看。

赞(0)
未经允许不得转载:171主机测评 » 批大小翻倍吞吐没翻倍:被补零吃掉的那部分算力
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址