image-20260621001849628

看一下超参数,n_embd是词的维度大小(hidden_size?),有12个头,12层

interactive_conditional_samples.py

交互式采样器,也就是跟用户交互过程,从业务理解开始深入到算法流程中似乎挺好的。

参数:要注意的主要为

nsamples:针对固定输入,要输出多少个采样。

batch_size:一次放进模型,让模型并行产生多少采样。所以这个batch_size必须整除nsamples。

(模型一次产生一个token,这个token是从概率分布中采样出来的,如果采样数多的话就类似于多次随机采样过程拿到了多个结果,batch_size只是为了将采样分到多个batch中来生成)

image-20260621000040689

在这里被encoder编码到token index(一系列int,后续在model会转为embedding)

然后context就是重复batch_size个这个sequence

如图,外层迭代会让模型一次获得batch_size的采样。

image-20260621000153935

然后前面就是加载encoder和超参数、恢复ckpt模型、设置采样器了。

n_ctx是超参数中的模型上下文长度,length就是输出长度,目前不太清楚,可能要看后面代码。

image-20260621000233382

sample.py

image-20260621003733825

image-20260621003742009

可以看到有past拼接的痕迹,所以大概length就是输出的seq长度。

image-20260621004322603

每个step根据model生成的东西返回

image-20260621004340966

返回之后将presents拼接到past

以及将temp、top_k、top_p应用到logits(也就是词表中每个token的计算出来的真实值)之后

使用tf.multinomial,对logits进行softmax,然后取到samples

image-20260621004551320

然后就是定义tf的生成loop了,

(具体的形状分析似乎还得先看model.py输出了什么形状)

从后续的分析来看,model返回的logits形状为

(batch_size, seq_len, n_vocab)

从这里的step()中取到:n_vocab之前的

然后body()取到seq_len维度的最后一个seq意思是这次的out

所以形状就是

(batch_size, n_vocab)

然后采样之后就是

(batch_size)个词了

然后prev其实是上一个生成的sequence或者是最开始的一整串sequence,

实际上后续的每次迭代过程中prev都只是一个batch_size的tokens,past才是过往上下文。output是拼接输出。

model.py

补充:注意到之前两个步骤中的context的形状已经是[batch_size, sequence_len]了

1. 将index->embedding

image-20260621012835416

如图,wte就是word token embedding,wpe是word position embedding

gather就是以X为index提取wte的向量。

positions_for是计算当前的X序列在整个context中的位置。没有past的话就不用偏移,如果有的话要偏移。

返回后的形状似乎是这样的:(batch_size, seq_len)

位置算法看小函数-position - Aquacolor

得出来h是相加的(batch_size, seq_len, n_embed)

2. Transformer的外层框架代码

image-20260621104840320

后面这几个就是维护presents和pasts以及做block attention、logits的方法了,在这里要追踪形状的话还得进入到block attention去看看

大概是block->attn这样的调用链,在仔细查看attn之前,我们自底向上一下(此前行文似乎有一些问题,自顶向下看结果细节不太清楚,应该自底向上)

3. attention

conv1d

image-20260621143133104

在attention的第一步就conv1d,首先传入的x就是h

形状(batch_size, seq_len, n_embed), n_state = n_embed

image-20260621143223245

从这里来看就是x跟w乘了并且+b,然后reshape这个nx->nf

也就是说返回的形状是

present KV

(batch_size, seq_len, n_embed*3)

image-20260621144112407

然后该行是将这个形状先用split切分成

(batch_size, seq_len, n_embed) * 3,然后split_heads将每个整块的Q,K,V切分成

(batch_size, heads, seq_len, n_embed / heads) 也就是多头注意力了

image-20260621144815898

104行我们看到在1的维度上新增维度,那么present形状为(batch_size, 2, heads, seq_len, n_embed / heads)

这时就能解答此前关于present形状的问题了,由于这个present在这里计算之后就没有修改,是直接返回到model了

image-20260621144938977

那么stack之后就是

(batch_size, n_layer, 2, heads, seq_len, n_embed / heads)

image-20260621145029961

解答了sample.py中past_shape这个问题

拼接和其余

image-20260621145609616

attn中剩下的这些就是把pastk,v拼接到当前kv的前面了。然后注意力、合并头、继续conv1d...

逻辑注意点

在这里靠kv串一下主线,sample利用past存储每个阶段产生的k,v

model进行分层block处理,每层递进使用h,但是pk,pv是相同的,不过因为h递进了,所以当前产生的k,v是不同的。

最后就是在attn里面拼接过往的k,v了。

既然最关键的各种处理流程我们都知道了,那我们就知道每次输出的东西为什么是这样了。

相比于模块化的难懂的代码这个gpt-2本身代码信息密度还是很高的。

这之后分析更多的细节,比如norm、logits计算、softmax想必就不难了(偷懒中...)。

运行一下看看

单sample

image-20260621154858585

4个sample

image-20260621154959158

image-20260621155036747

xx所以居然是生成多个sample序列我还以为是一次生成多个sample然后每个子节点再生成n个sample(似乎会爆炸)

image-20260621155133385

那batch的意义估计就是类似于目前的multi-request的那种batch处理了,也挺好。