苏州园科生态建设集团网站,行业网站建设分析,做婚纱的网站,h5自助建站系统上一节内容#xff1a;【LLM基础教程】从序列切分到上下文窗口01_为什么序列建模必须切分数据 本节也是对沐神课程的进一步理解#xff1a;53 语言模型【动手学深度学习v2】 在上一节中我们看到#xff1a;序列切分的目的#xff0c;是把一条很长的文本#xff0c;转化…上一节内容【LLM基础教程】从序列切分到上下文窗口01_为什么序列建模必须切分数据本节也是对沐神课程的进一步理解53 语言模型【动手学深度学习v2】 在上一节中我们看到序列切分的目的是把一条很长的文本转化为模型可以处理的固定长度监督样本。 那么接下来的问题就是这些长度为T TT的子序列应该如何从原始文本中取出来 不同的切分方式实际上对应着不同的建模假设与工程取舍。在介绍具体策略之前我们先明确一个统一的训练设定。之后我们依次介绍三种最常见的策略。零、统一的训练设定固定长度的 next-token prediction 为了便于分析下面所有切分策略都基于同一个基本假设模型每次处理一段固定长度为T TT的token序列。1. 输入与输出的构造方式 在语言建模任务中我们通常采用next-token prediction作为训练目标。具体来说输入长度为T TT的 token 序列标签将输入序列整体右移 1 位长度仍为T TT例如对于原始 token 序列( x 1 , x 2 , x 3 , x 4 , x 5 , x 6 , ⋯ ) (x_1, x_2, x_3, x_4, x_5, x_6, \cdots)(x1,x2,x3,x4,x5,x6,⋯)我们可以构造一个训练样本输入[ x 1 , x 2 , x 3 , x 4 , x 5 ] [x_1, x_2, x_3, x_4, x_5][x1,x2,x3,x4,x5]输出[ x 2 , x 3 , x 4 , x 5 , x 6 ] [x_2, x_3, x_4, x_5, x_6][x2,x3,x4,x5,x6]这种“输入一段上下文、预测下一步 token”的训练方式正是语言模型中最经典、也最通用的next-token prediction形式。 在这一设定下序列切分的差异就体现在这些长度为T TT的输入片段是如何从原始长序列中取出的。(1) 为什么训练形式是「序列 → 序列」 从表面上看这种训练方式有一个容易让人困惑的地方输入是一个序列输出为什么也是一个序列而不是单个 token 答案在于**语言模型的预测目标本身就是“逐时间步定义的”。**也就是说这并不是人为设计的约定而是由自回归建模方式自然决定的。 在自回归语言模型中序列的联合概率通过链式法则分解为P ( x 1 , ⋯ , x T ) ∏ t 1 T P ( x t ∣ x t ) P(x_1, \cdots, x_T) \prod_{t1}^TP(x_t|x_{t})P(x1,⋯,xT)t1∏TP(xt∣xt) 这意味着模型在时间步t tt模型的目标是预测P ( x t ∣ x t ) P(x_t|x_{t})P(xt∣xt)。也就是说每一个时间步都对应着一个独立且明确的预测任务。(2) 一次前向传播对应T TT次预测 当模型接收一整段长度为T TT的输入序列时它并不是“等看完再预测”而是会在内部并行地产生一组预测结果P ( x t 1 ∣ x ≤ t ) , t 1 , 2 , ⋯ , T P(x_{t1}|x_{\le t}), t1,2,\cdots, TP(xt1∣x≤t),t1,2,⋯,T 从实现角度看模型会为序列中的每一个位置生成一个隐状态每个位置的隐状态都会输出一个对“下一个 token”的预测分布因此把这T TT个时间步的预测结果一次性组织成一个序列输出就成为最自然、也是最高效的训练方式。2. 工程视角下的三点收益 将训练目标设计为“序列 → 序列”在工程上有非常直接的好处充分利用监督信号一段长度为T TT的真实文本可以同时提供T TT个预测目标训练与推理形式一致训练阶段和自回归生成阶段遵循同样的“逐 token 预测”逻辑高度并行化所有时间步的预测可以在一次前向传播中完成显著提升训练效率 因此语言模型“输出也是一个序列”并不是人为增加的复杂设计而是由自回归建模方式和模型结构共同决定的自然结果。一、理论上的滑动窗口stride 11. 最直接、也最“完整”的切分方式在所有切分策略中滑动窗口(sliding window)是最直观的一种窗口长度固定为T TT每次向前移动 1 个 token即 stride 1从序列开头一直滑到结尾形式化地说对于原始序列( x 1 , x 2 , ⋯ , x N ) (x_1, x_2, \cdots, x_N)(x1,x2,⋯,xN)可以构造出如下训练样本( x 1 , ⋯ , x T ) , ( x 2 , ⋯ , x T 1 ) , ⋯ (x_1, \cdots, x_T), (x_2, \cdots, x_{T1}),\cdots(x1,⋯,xT),(x2,⋯,xT1),⋯ 对于长度为N NN的原始序列使用窗口大小为T TT、步长stride为 1的滑动窗口切分会得到N − T N-TN−T个序列训练样本。 此外可以看到样本高度重叠、数据最密集、信息利用最充分。2. 为什么它是“理论上的理想方案” 这种方式具有一个非常重要的性质它最大限度地保留了局部上下文信息。几乎每一个 token都会出现在多个不同的上下文窗口中模型可以充分学习到条件概率P ( x t ∣ P x t ) P(x_t|P_{xt})P(xt∣Pxt)的局部统计规律因此从数据利用率的角度看滑动窗口通常被视为理论上的理想上界。局限性虽然滑动窗口在理论上很优雅但在工程实践中很少直接使用样本数量巨大计算和存储成本极高同一 token 会被重复计算多次GPU 并行效率低因此真实训练中通常采用更高效的近似方案。3. 工程上的现实问题 尽管理论上的滑动窗口stride 1能够完整覆盖所有可能的 next-token 监督信号但在工程实践中它会带来一系列非常现实、且难以忽视的问题。相邻样本高度重叠信息冗余严重相邻滑动窗口样本之间重叠的是T − 1 T-1T−1个 token这意味着新增的计算往往只引入极少量的“新信息”。模型在连续的多次前向传播中反复处理几乎相同的上下文。样本数量线性增长训练规模迅速膨胀对于长度为N NN的原始序列窗口大小为T TT时滑动窗口stride 1会生成N − T N-TN−T个序列级训练样本。在大规模语料场景下N NN往往以百万token计N − T ≈ N N-T \approx NN−T≈N这意味着训练样本数几乎与原始语料长度等量增长数据加载、shuffle、batch 组织的开销显著增加。计算与存储成本难以接受将上述两点结合起来滑动窗口策略会在多个层面放大资源消耗计算成本大量高度相似的样本导致重复的前向与反向计算显存 / 内存压力更大的 batch 中包含大量冗余 token降低有效 token 吞吐率存储与 I/O 开销若提前生成并保存切分后的样本数据体积会急剧膨胀在大模型训练中这类冗余会直接转化为可观的时间与硬件成本。因此在实际训练系统中滑动窗口更多是一种“分析基准”而不是直接采用的工程方案。二、随机采样Random Sampling1. 为什么需要随机采样在理论上**滑动窗口stride1**可以生成最完整的训练集但在工程实践中它的代价过高样本数量随序列长度线性膨胀相邻样本高度重叠token 被重复计算存储、IO 与计算成本都难以接受因此实际系统中更常见的做法是不构造完整训练集而是在“潜在完整样本空间”中进行随机采样。随机采样的目标并不是“制造更多样本”而是避免全量滑动窗口带来的资源消耗在多个 epoch 中不断改变子序列的对齐方式提升训练过程的随机性与泛化能力2. 核心思想 与滑动窗口stride1会生成完整训练集不同随机采样方式并不会生成所有长度为T TT的子序列而是在完整训练集上随机抽取若干子序列用于训练 为此引入了一个关键设计随机偏移量offsetoffset ∼ U ( 0 , T − 1 ) \text{offset} \sim \mathbf{U}(0, T-1)offset∼U(0,T−1)这一步的作用是随机改变整条序列的“切分起点”避免模型在每个 epoch 中总是从x 1 x_1x1开始看到序列此时子序列的划分形式变为( x offset 1 , x offset 2 , ⋯ , x offset T ) (x_{\text{offset}1}, x_{\text{offset}2}, \cdots, x_{\text{offset}T})(xoffset1,xoffset2,⋯,xoffsetT)需要特别强调的是随机采样并不是在子序列内部打乱 token而是随机选择子序列的起点。3. 样本之间的关系不连续、不保证顺序在随机采样中每个样本都是从原始长序列中独立截取的一段长度为T TT的子序列batch 内的样本在原始序列中不一定相邻甚至毫无关系例如假设这一轮训练随机抽取了 3 个样本其起点分别为样本 A 起点 3样本 B 起点 10样本 C 起点 6对应的子序列为A : ( x 3 , x 4 , ⋯ , x T 2 ) B : ( x 10 , x 11 , ⋯ , x T 9 ) C : ( x 6 , x 7 , ⋯ , x T 5 ) A: (x_3, x_4, \cdots, x_{T2}) \\ B: (x_{10}, x_{11}, \cdots, x_{T9}) \\ C: (x_6, x_7, \cdots, x_{T5})A:(x3,x4,⋯,xT2)B:(x10,x11,⋯,xT9)C:(x6,x7,⋯,xT5)可以看到A 与 B 在原序列中相距很远C 与 A 有部分重叠但并非相邻样本batch 内的样本整体呈现为乱序、非连续状态这正是随机采样希望达到的效果。4. 代码实现基于随机偏移的无重叠子序列采样 下面的实现展示了一个典型的随机采样 DataLoader。在这里参数batch_size指定了每个小批量中子序列样本的数目 参数num_steps是每个子序列中预定义的时间步数。Step 1随机偏移避免固定切分边界offsetrandom.randint(0,num_steps-1)trimmedcorpus[offset:] 这里随机跳过前offset个 token防止模型每一轮训练都看到相同的切分方式。Step 2计算可生成的子序列数量无重叠num_subseqs(len(trimmed)-1)//num_steps 之所以减 1是因为语言模型采用next-token prediction需要为每个输入 token 提供一个对应的标签。Step 3确定所有子序列的起点等距、不重叠initial_positionslist(range(0,num_subseqs*num_steps,num_steps)) 每个子序列的起点是等距的(KaTeX parse error: Expected EOF, got _ at position 12: T\text{num_̲steps})。这些起点为0, T, 2T, 3T, ... 例如生成的子序列索引[0:5], [5:10], [10:15], ...因此生成的子序列满足完全不重叠每个字序列之间相距num_steps不共享任何 token每个样本都是从 corpus 中不相交的片段截出来的序列:a b c d e f g h i j k l m n o 子序列:└─────┘ └─────┘ └─────┘444Step 4: 打乱子序列起点实现随机采样importrandom random.shuffle(initial_positions) 随机采样不是按顺序取片段而是随机选择片段。这样做保证 batch 内的句子不连续、不相关。例如原序列x 1 , x 2 , ⋯ , x 14 , x 15 x_1, x_2, \cdots, x_{14}, x_{15}x1,x2,⋯,x14,x15设定num_steps 5则可切分的无重叠子序列起点为[0, 5, 10]对应的子序列为起点 0 →[x1, x2, x3, x4, x5]起点 5 →[x6, x7, x8, x9, x10]起点 10 →[x11, x12, x13, x14, x15]现在进行随机打乱[10, 0, 5]Batch1 可能拿的是[x11, x12, x13, x14, x15]Batch2 可能是[x1, x2, x3, x4, x5]Batch3 可能是[x6,x7, x8, x9, x10]可以看出非常随机不受原始顺序影响。Step 5: 构造 batch并生成 X / Ytrimmed corpus └── 划分 → num_subseqs可切出的所有子序列数量 └── 按 batch_size 分组 └── 得到 num_batches 个 batch num_subseqs总子序列数 batch_size每个 batch 样本数 num_batches总子序列数/每个 batch 样本数计算batch个数吐槽一下csdn这里的latex公式无论怎么打都不能正常显示我是最讨厌用截图来敷衍的。大家将就一下。每个 batch 取batch_size个起点每个起点对应一个长度为num_steps的子序列foriinrange(0,batch_size*num_batches,batch_size):batch_positionsinitial_positions[i:ibatch_size]这里的batch_positions是从initial_positions中取出的一个 batch 里的起始位置集合。例如initial_indices [200, 40, 600, 20, 480, 300, ...] # 已打乱 batch_size 2 第一次 batch - [200, 40] 第二次 batch - [600, 20] ...构造输入序列 X 与标签序列 Ydefget_subseq(start):returntrimmed[start:startnum_steps]X[get_subseq(pos)forposinbatch_positions]Y[get_subseq(pos1)forposinbatch_positions]完整实现importrandomimporttorchdefseq_data_iter_random(corpus,batch_size,num_steps): 随机采样方式的小批量序列生成器 corpus: 整个语料token 序列 batch_size: 每个 batch 中的样本数量 num_steps: 每个子序列的长度 # ---- 1. 随机偏移 ----offsetrandom.randint(0,num_steps-1)trimmedcorpus[offset:]# ---- 2. 计算可切分的子序列数量 ----num_subseqs(len(trimmed)-1)//num_steps initial_positionslist(range(0,num_subseqs*num_steps,num_steps))# ---- 3. 打乱所有子序列的起点 ----random.shuffle(initial_positions)defget_subseq(start):returntrimmed[start:startnum_steps]# ---- 4. 构造 batch ----num_batchesnum_subseqs//batch_sizeforiinrange(0,batch_size*num_batches,batch_size):batch_positionsinitial_positions[i:ibatch_size]X[get_subseq(pos)forposinbatch_positions]Y[get_subseq(pos1)forposinbatch_positions]yieldtorch.tensor(X),torch.tensor(Y)例如我们生成一个从0 00到34 3434的序列。 假设批量大小为2 22(batch_size2)时间步数为5 55(num_steps5)这意味着可以生成⌊ 35 − 1 5 6 ⌋ \lfloor \frac{35-1}{5} 6\rfloor⌊535−16⌋个“特征标签”子序列对。 因为每个小批量中的样本为2我们只能得到3个小批量。my_seqlist(range(35))batch_count0forX,Yinseq_data_iter_random(my_seq,batch_size2,num_steps5):print(fbatch{batch_count1})print(X: ,X,\nY:,Y)batch_count1batch 1 X: tensor([[15, 16, 17, 18, 19], [ 0, 1, 2, 3, 4]]) Y: tensor([[16, 17, 18, 19, 20], [ 1, 2, 3, 4, 5]]) batch 2 X: tensor([[20, 21, 22, 23, 24], [25, 26, 27, 28, 29]]) Y: tensor([[21, 22, 23, 24, 25], [26, 27, 28, 29, 30]]) batch 3 X: tensor([[ 5, 6, 7, 8, 9], [10, 11, 12, 13, 14]]) Y: tensor([[ 6, 7, 8, 9, 10], [11, 12, 13, 14, 15]])5. 为什么随机采样要这样设计 从建模假设上看随机采样并不要求不同子序列之间保持原始文本中的连续性。这是因为在语言模型的训练目标中每一个长度为T TT的子序列本身就已经构成了一个完整、独立的监督样本。 具体来说语言模型采用的是next-token prediction训练形式X [t0, t1, t2, t3, t4] Y [t1, t2, t3, t4, t5] 在这一设定下模型在每一个时间步t tt只关心P ( t i 1 ∣ t ≤ i ) P(t_{i1}∣t_{≤i})P(ti1∣t≤i)监督信号完全来自子序列内部不依赖于相邻子序列是否在原始语料中连续。 因此从训练目标的角度看只要子序列内部保持 token 的顺序关系不同子序列之间是否相邻并不会影响 next-token prediction 的正确性。在这一前提下随机采样的设计带来了非常明确的工程优势高度并行化每个子序列都是独立样本可以在 GPU 上同时计算不需要维护跨序列状态。便于打乱与随机化通过打乱子序列起点可以有效减弱位置偏置position bias使模型不会过度依赖特定位置模式。计算与存储效率更高子序列之间不重叠避免了同一个 token 在大量样本中被重复计算从而显著降低计算成本。从这个角度看“不重叠”并不是信息利用不足而是一种有意识的工程取舍在不改变训练目标的前提下用更少的重复计算获得足够多、足够随机的监督信号。这也是为什么在实际的语言模型训练系统中随机采样往往比stride1的滑动窗口更常用。三、顺序采样Sequential Sampling 在语言模型尤其是 RNN / LSTM / 早期 Transformer中序列的连续性本身就是一种重要信号句法结构是跨 token 延续的语义状态会随时间逐步演化隐状态hidden state天然假设时间连续因此与完全打乱的随机采样不同顺序采样试图最大程度保留原始语料的时间结构。1. 顺序采样的动机 随机采样强调样本独立性和并行效率但它刻意破坏了时间上的连续性。而在某些场景尤其是 RNN / LSTM中我们希望**模型在 batch 内仍然能够看到连续的上下文片段。**顺序采样正是为此而设计的。核心思想让模型看到语料中“连续”的序列片段尽可能保持上下文的自然衔接。顺序采样的特点可以概括为不打乱语料的整体顺序batch 内的每一行是一段连续时间序列行与行之间相互独立其本质是在保持 batch 并行计算的前提下尽量保留时间维度上的连续性。2. 与随机采样的根本区别随机采样 把语料视为一个“token 池”子序列之间不要求相关性顺序采样 把语料视为一条“长时间序列”子序列是这条序列的切片与随机采样不同顺序采样中整个 corpus 会被按顺序切成若干子片段。这也是为什么顺序采样更常用于RNN / LSTM 的语言模型需要跨 batch 传递隐状态的训练方式分析模型对长期依赖的建模能力3. 顺序采样的实现Step 1随机偏移Random Offset# ---- 1. 随机偏移 ----offsetrandom.randint(0,num_steps)为什么需要 offset如果每个 epoch 都从 corpus[0] 开始切分那么每一轮训练看到的子序列切分位置完全相同模型容易记住“切分边界”而不是语言规律泛化能力会明显下降随机偏移的作用是打破固定切分边界让同一个 token 在不同 epoch 中出现在不同位置在不破坏顺序结构的前提下引入随机性在顺序采样中offset 可以取到num_steps闭区间因为我们后面是先构造大段序列再 reshape不会产生越界问题。Step2计算可用于 batch 的 token 数num_tokens((len(corpus)-offset-1)//batch_size)*batch_size 这一行是纯工程约束但非常关键。len(corpus) - offset - 1可用的 token 数其中- offset是跳过随机起点之前的 token- 1为构造 Y右移一位预留空间// batch_size按 batch_size 整除避免最后出现“凑不齐一个 batch” 的情况* batch_size重新计算出可整除的 token 数顺序采样的一个重要特点是我们宁愿丢掉少量尾部数据也要保证 batch 内结构严格一致。Step3构造 X 和 Y严格对齐的 next-token predictionXstorch.tensor(corpus[offset:offsetnum_tokens])Ystorch.tensor(corpus[offset1:offset1num_tokens]) 这是语言模型中最经典、也是最“干净”的训练格式X输入[t0, t1, t2, ..., tn-1]Y标签[t1, t2, t3, ..., tn]二者长度完全一致只是整体右移一位。这正是next-token prediction的数学实现形式P ( x t 1 ∣ x ≤ t ) P(x_{t1} \mid x_{\le t})P(xt1∣x≤t)。Step 4reshape 成 batch_size 行的“并行长序列”Xs,YsXs.reshape(batch_size,-1),Ys.reshape(batch_size,-1) 此时每一行是一条时间上连续的长序列不同行之间是相互独立、并行训练的样本 可以把它理解为我们从一条超长时间轴中“抽取了 batch_size 条并行时间轨道”Step 5按 num_steps 切出连续的训练片段num_batchesXs.shape[1]//num_stepsforiinrange(0,num_steps*num_batches,num_steps):XXs[:,i:inum_steps]YYs[:,i:inum_steps]yieldX,Y这里切出来的每一个(X, Y)在行内是连续的 token在列方向保持严格对齐在 batch 之间时间顺序不被打乱这一步确保了模型在训练时看到的是“自然语言原本的时间展开方式”。完整实现defseq_data_iter_sequential(corpus,batch_size,num_steps):#save使用顺序分区生成一个小批量子序列# 从随机偏移量开始划分序列offsetrandom.randint(0,num_steps)num_tokens((len(corpus)-offset-1)//batch_size)*batch_size Xstorch.tensor(corpus[offset:offsetnum_tokens])Ystorch.tensor(corpus[offset1:offset1num_tokens])Xs,YsXs.reshape(batch_size,-1),Ys.reshape(batch_size,-1)num_batchesXs.shape[1]//num_stepsforiinrange(0,num_steps*num_batches,num_steps):XXs[:,i:inum_steps]YYs[:,i:inum_steps]yieldX,Y my_seqlist(range(35))forX,Yinseq_data_iter_sequential(my_seq,batch_size2,num_steps5):print(X: ,X,\nY:,Y)输出X:tensor([[1,2,3,4,5],[17,18,19,20,21]])Y:tensor([[2,3,4,5,6],[18,19,20,21,22]])X:tensor([[6,7,8,9,10],[22,23,24,25,26]])Y:tensor([[7,8,9,10,11],[23,24,25,26,27]])X:tensor([[11,12,13,14,15],[27,28,29,30,31]])Y:tensor([[12,13,14,15,16],[28,29,30,31,32]]) 输出示例清楚地说明每一行内部是严格连续的 token不同行是并行的独立样本batch 之间保持时间顺序推进同一行在不同 batch 中是连续的。这正是顺序采样的设计目标时间连续性不是体现在 batch 内而是体现在 batch 之间四、三种切分方式的对比与取舍 在理论上如果对一个长度为n nn的序列进行建模最“彻底”的方式是采用滑动窗口stride 1在每一个时间步都构造一个训练样本。 但在实际训练系统中无论是随机采样还是顺序采样生成的X XX样本通常都是互不重叠的而不是每个时间步都生成一个样本。 这是一个工程取舍问题而不是建模正确性的问题。1. 整体对比方式是否重叠样本数量计算效率使用场景滑动窗口是极多低小数据、分析验证随机采样否适中高大规模训练顺序采样否适中高保留上下文重叠样本不是“更正确”而是一种成本极高的策略。 在大规模训练中不重叠 批量并行往往性价比更高。1. 理论滑动窗口 VS 实际训练切分 以序列长度为n nnnum_steps5为例方式样本长度样本数量是否重叠优缺点理论滑动窗口5n − 5 1 n - 5 1n−51是最大化训练样本数量但训练效率低常规 seq / random5⌊ n / 5 ⌋ \lfloor n / 5 \rfloor⌊n/5⌋否高效、课并行、上下文连续、训练稳定 可以看到两者的本质差异并不在于样本形式而在于是否允许重叠。2. 滑动窗口采样的样本数量是如何膨胀的 假设序列长度为n nnx 0 , x 1 , ⋯ , x n − 1 x_0, x_1, \cdots, x_{n-1}x0,x1,⋯,xn−1 我们希望每个样本长度为num_steps并用于预测下一个 token或下一个num_steps个 token滑动窗口方式步长 1每个样本长度 num_steps样本KaTeX parse error: Expected EOF, got _ at position 42: … x_{i\text{num_̲steps}-1}]对应的KaTeX parse error: Expected EOF, got _ at position 46: … x_{i\text{num_̲steps}}]如果num_steps 5那么第一条样本i0就是X [ 0 ] [ x 0 , x 1 , ⋯ , x 4 ] Y [ 0 ] [ x 1 , x 2 , ⋯ , x 5 ] X[0] [x_0, x_1, \cdots, x_4] \\ Y[0] [x_1, x_2, \cdots, x_5]X[0][x0,x1,⋯,x4]Y[0][x1,x2,⋯,x5]第二条样本滑动一步X [ 1 ] [ x 1 , x 2 , ⋯ , x 5 ] Y [ 1 ] [ x 2 , x 3 , ⋯ , x 6 ] X[1] [x_1, x_2, \cdots, x_5] \\ Y[1] [x_2, x_3, \cdots, x_6]X[1][x1,x2,⋯,x5]Y[1][x2,x3,⋯,x6]…依此类推最终可生成的样本数量为n - num_steps但是相邻样本之间共享 num_steps − 1 个 token高度重叠。3. 为什么实际 batch 训练中不构造n − 1 n-1n−1个样本在真实训练中我们通常不会采用stride 1的滑动窗口而是一次取num_steps个时间步作为一个样本在 batch 维度上并行训练多条序列轨道具体来说若序列长度为n nn每个样本长度为num_steps最多可以生成KaTeX parse error: Expected EOF, got _ at position 22: …r n / \text{num_̲steps} \rfloor个互不重叠的样本。这样做的好处避免重复计算滑动窗口会反复计算几乎相同的 token 组合显著提升 GPU 利用率不重叠样本更适合批量并行训练更稳定减少高度相关样本带来的梯度冗余更贴合现代模型训练范式Transformer / GPT 并不依赖跨样本隐状态⚠️ 如果希望引入更多上下文覆盖可以使用步长 num_steps的滑动窗口但这一定会带来更高的时间与显存成本。