# llm.c 筆記 (2) 層歸一化 ![image](https://hackmd.io/_uploads/HycPoL5g0.png) [出處](https://blog.csdn.net/sinat_34072381/article/details/106173365) ``` 在 NLP 領域中,BN 有怎樣的缺點? ``` ![image](https://hackmd.io/_uploads/rJY8jU9gR.png) ![image](https://hackmd.io/_uploads/B1tRcI9eR.png) ![image](https://hackmd.io/_uploads/rkGeo8qeA.png) ![image](https://hackmd.io/_uploads/rk8zoL5x0.png) ``` void layernorm_forward(float* out, float* mean, float* rstd, float* inp, float* weight, float* bias, int B, int T, int C) { // 參考: https://pytorch.org/docs/stable/generated/torch.nn.LayerNorm.html // inp 和 out 均為 (B,T,C) 的活性化張量 // mean 和 rstd 為 (B,T) 緩衝區,在反向傳播中使用 // 在輸入的每個位置 (b,t),C 維的活性化向量會被正規化,然後進行縮放和平移 float eps = 1e-5f; for (int b = 0; b < B; b++) { for (int t = 0; t < T; t++) { // 定位到輸入位置 inp[b,t,:] float* x = inp + b * T * C + t * C; // 計算平均值 float m = 0.0f; for (int i = 0; i < C; i++) { m += x[i]; } m = m / C; // 計算變異數(無偏校正) float v = 0.0f; for (int i = 0; i < C; i++) { float xshift = x[i] - m; v += xshift * xshift; } v = v / C; // 計算反向標準差 (rstd) float s = 1.0f / sqrtf(v + eps); // 定位到輸出位置 out[b,t,:] float* out_bt = out + b * T * C + t * C; for (int i = 0; i < C; i++) { float n = (s * (x[i] - m)); // 正規化 float o = n * weight[i] + bias[i]; // 縮放和平移 out_bt[i] = o; // 寫入 } // 快取平均值和反向標準差以供之後的反向傳播使用 mean[b * T + t] = m; rstd[b * T + t] = s; } } } ``` ``` float* x = inp + b * T * C + t * C; ``` ``` x 是一個指向 float 的指標,它指向了 inp 陣列中某個特定的位置。 具體來說,這個位置是根據批次索引 b、時間步索引 t、 以及每個時間步特徵數 C 計算出來的。 這裡 inp 是一個一維陣列,但它實際上表示一個三維數據結構,維度為 (B, T, C)。 x 指針基於計算得到的偏移量指向 inp 陣列的開始位置, 這個位置對應於第 b 個批次和第 t 個時間步的第一個特徵。 ``` ``` 解釋 x[i] 當你使用 x[i],這是從 x 指向的位置開始,訪問第 i 個元素。 因為 x 是一個指向浮點數的指標, x[i] 就是訪問該指針位置後第 i 個浮點數。 換句話說,x[i] 是訪問三維數據結構中的一個具體元素, 即在第 b 個批次、第 t 個時間步的第 i 個特徵。 這種訪問方式使得處理在一個連續內存塊中存儲的多維數據變得簡單而高效。 這種指針和偏移的使用是 C++ 在處理多維數據時常見的一種技術, 特別是在性能敏感的應用如數據科學和深度學習領域。 ``` ``` encoder_forward 中的 inp 是整數陣列,為何這裡的inp變成float? ``` ``` 在不同的编程上下文和应用中, 数组 inp 的数据类型可能会有所不同, 这取决于它所用于的具体功能和处理的数据类型。 整数数组 在某些情况下,如处理数字编码或特定的数据结构 (例如,在处理图像数据时可能使用整数数组存储像素值), inp 可能是一个整数数组。整数通常用于表示离散数据, 或者在需要精确整数计算时使用。 浮点数组 在其他情况下,尤其是在涉及到计算机视觉、机器学习或深度学习的应用中, inp 可能是一个浮点数组。浮点数用于表示连续值和进行科学计算, 因为它们可以表示非整数值以及非常大或非常小的数。 在深度学习中,使用浮点数可以执行诸如梯度下降这样的优化算法, 这些算法需要非常精细的数值精度来更新权重和偏置。 示例中的 inp 在您提到的 layernorm_forward 函数的上下文中, inp 被定义为浮点数组。 这是因为层标准化通常用于神经网络中, 其中的数据点(激活、权重更新等)需要以浮点数进行处理以保持计算的精度和灵活性。 浮点数允许进行小数点后的运算, 这对于神经网络训练中的许多数学运算是必需的, 比如计算均值、方差以及后续的反向传播。 如果在另一个上下文中(比如编码器的实现),inp 用作整数数组, 可能是因为该应用的数据和操作不需要浮点计算的精度, 或者数据本身就是离散的,如字符编码或索引。 总之,inp 数组的数据类型取决于其用途和所需的计算类型。 在实际应用中,选择正确的数据类型对于程序的性能和结果的准确性至关重要。 ``` ``` void layernorm_backward(float* dinp, float* dweight, float* dbias, float* dout, float* inp, float* weight, float* mean, float* rstd, int B, int T, int C) { for (int b = 0; b < B; b++) { for (int t = 0; t < T; t++) { float* dout_bt = dout + b * T * C + t * C; float* inp_bt = inp + b * T * C + t * C; float* dinp_bt = dinp + b * T * C + t * C; float mean_bt = mean[b * T + t]; float rstd_bt = rstd[b * T + t]; // 首先:兩次 reduce 操作 float dnorm_mean = 0.0f; float dnorm_norm_mean = 0.0f; for (int i = 0; i < C; i++) { float norm_bti = (inp_bt[i] - mean_bt) * rstd_bt; float dnorm_i = weight[i] * dout_bt[i]; dnorm_mean += dnorm_i; dnorm_norm_mean += dnorm_i * norm_bti; } dnorm_mean = dnorm_mean / C; dnorm_norm_mean = dnorm_norm_mean / C; // 然後再次迭代並累積所有梯度 for (int i = 0; i < C; i++) { float norm_bti = (inp_bt[i] - mean_bt) * rstd_bt; float dnorm_i = weight[i] * dout_bt[i]; // 對偏差的梯度貢獻 dbias[i] += dout_bt[i]; // 對權重的梯度貢獻 dweight[i] += norm_bti * dout_bt[i]; // 對輸入的梯度貢獻 float dval = 0.0f; dval += dnorm_i; // 第一項 dval -= dnorm_mean; // 第二項 dval -= norm_bti * dnorm_norm_mean; // 第三項 dval *= rstd_bt; // 最終縮放 dinp_bt[i] += dval; } } } } ```