<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>J&apos;Log</title>
    <description>J&apos;Log — a blog by Zhejian Peng on ranking, recommendation, and Grok Coding RL.</description>
    <link>https://jazzikp.github.io/</link>
    <atom:link href="https://jazzikp.github.io/feed.xml" rel="self" type="application/rss+xml" />
    <pubDate>Sun, 30 Aug 2026 02:40:57 +0000</pubDate>
    <lastBuildDate>Sun, 30 Aug 2026 02:40:57 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <item>
        <title>Pre-Norm vs. Post-Norm: The Identity Path That Stabilizes LLMs</title>
        <description>
&lt;div data-lang-panel=&quot;en&quot;&gt;

  &lt;blockquote&gt;
    &lt;p&gt;&lt;strong&gt;The central idea:&lt;/strong&gt; Pre-Norm matters because every Transformer block retains a residual route that normalization does not transform. That direct identity path lets gradients cross many layers. It mainly improves the optimization stability of deep models; it does &lt;strong&gt;not&lt;/strong&gt; imply that Pre-Norm always has greater final expressive power.&lt;/p&gt;
  &lt;/blockquote&gt;

  &lt;h2 id=&quot;the-one-line-difference&quot;&gt;The one-line difference&lt;/h2&gt;

  &lt;p&gt;Let \(F_l\) denote either attention or the feed-forward network, and let \(N\) denote LayerNorm or RMSNorm. The two arrangements differ by one line:&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;Pre-Norm&lt;/strong&gt;&lt;/p&gt;

\[x_{l+1}=x_l+F_l(N(x_l))\]

  &lt;p&gt;&lt;strong&gt;Post-Norm&lt;/strong&gt;&lt;/p&gt;

\[x_{l+1}=N(x_l+F_l(x_l))\]

  &lt;p&gt;In Pre-Norm, only the branch computation sees normalized input. The residual stream \(x_l\) reaches the next layer unchanged. In Post-Norm, the residual is still present, but the sum must pass through normalization before becoming \(x_{l+1}\).&lt;/p&gt;

  &lt;p&gt;&lt;img src=&quot;/img/pre-vs-post-norm-paths.webp&quot; alt=&quot;Pre-Norm preserves a direct identity gradient path, while every Post-Norm path passes through normalization.&quot; loading=&quot;lazy&quot; width=&quot;960&quot; height=&quot;470&quot; /&gt;&lt;/p&gt;

  &lt;p&gt;This diagram is the argument in its most useful form: &lt;strong&gt;Pre-Norm has an unmodified blue highway through depth. Post-Norm has a residual connection, but no unmodified identity highway.&lt;/strong&gt;&lt;/p&gt;

  &lt;h2 id=&quot;why-the-identity-path-changes-backpropagation&quot;&gt;Why the identity path changes backpropagation&lt;/h2&gt;

  &lt;p&gt;Differentiate one block. Pre-Norm gives&lt;/p&gt;

\[\frac{\partial x_{l+1}}{\partial x_l}
=I+J_{F_l}J_N.\]

  &lt;p&gt;The \(I\) term is explicit. When the learned branch is small, as it often is near initialization, the block Jacobian is close to identity:&lt;/p&gt;

\[\frac{\partial x_{l+1}}{\partial x_l}\approx I.\]

  &lt;p&gt;A gradient can therefore travel through dozens of blocks without depending entirely on the attention, MLP, or normalization Jacobians.&lt;/p&gt;

  &lt;p&gt;Post-Norm instead gives&lt;/p&gt;

\[\frac{\partial x_{l+1}}{\partial x_l}
=J_N(I+J_{F_l}).\]

  &lt;p&gt;Every backward route includes \(J_N\). Across a deep stack, the gradient repeatedly encounters those normalization Jacobians. Their products can rescale and rotate it, and LayerNorm projects out particular shift and scale directions. The residual addition has not disappeared; what disappeared is the &lt;strong&gt;untransformed identity route&lt;/strong&gt;.&lt;/p&gt;

  &lt;p&gt;That distinction is more precise than saying “Post-Norm cuts the residual connection.” It does not. It makes normalization unavoidable on the path between successive residual states.&lt;/p&gt;

  &lt;h2 id=&quot;what-xiong-et-al-actually-established&quot;&gt;What Xiong et al. actually established&lt;/h2&gt;

  &lt;p&gt;&lt;a href=&quot;https://proceedings.mlr.press/v119/xiong20b.html&quot;&gt;Xiong et al. (2020), &lt;em&gt;On Layer Normalization in the Transformer Architecture&lt;/em&gt;&lt;/a&gt;, analyzed the models at initialization using mean-field arguments. Their key observation was a layerwise imbalance:&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;In &lt;strong&gt;Post-LN&lt;/strong&gt;, parameter gradients near the output are large at initialization. Applying the full target learning rate immediately can make the first updates unstable.&lt;/li&gt;
    &lt;li&gt;In &lt;strong&gt;Pre-LN&lt;/strong&gt;, gradient magnitudes are better behaved across depth, so their translation and BERT experiments could train without learning-rate warmup.&lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;This explains why learning-rate warmup is especially important for classical Post-Norm Transformers: warmup makes the early updates small while the optimizer and network leave the fragile initialization regime.&lt;/p&gt;

  &lt;p&gt;Two qualifications matter:&lt;/p&gt;

  &lt;ol&gt;
    &lt;li&gt;This is an analysis of initialization under simplifying assumptions, not a theorem that every Post-Norm run diverges.&lt;/li&gt;
    &lt;li&gt;Modern Pre-Norm LLM training still commonly uses warmup. Warmup also helps adaptive-optimizer state, large batches, mixed precision, and other parts of the training system.&lt;/li&gt;
  &lt;/ol&gt;

  &lt;p&gt;The defensible claim is therefore &lt;strong&gt;“Pre-Norm is less sensitive and easier to optimize deeply,”&lt;/strong&gt; not “Pre-Norm never needs warmup.”&lt;/p&gt;

  &lt;h2 id=&quot;a-runnable-stability-stress-test&quot;&gt;A runnable stability stress test&lt;/h2&gt;

  &lt;p&gt;The code accompanying this article trains the same small 12-layer causal Transformer three ways:&lt;/p&gt;

  &lt;ol&gt;
    &lt;li&gt;Pre-Norm at a constant learning rate of \(2\times10^{-3}\);&lt;/li&gt;
    &lt;li&gt;Post-Norm at the same constant learning rate;&lt;/li&gt;
    &lt;li&gt;Post-Norm with a linear warmup lasting two thirds of the run (120 steps in the default 180-step experiment) to the same target rate.&lt;/li&gt;
  &lt;/ol&gt;

  &lt;p&gt;Everything else—data, initialization seed, optimizer, depth, width, and number of updates—is held fixed. The task is deliberately simple: given an arithmetic token sequence, predict the next token. There is no gradient clipping.&lt;/p&gt;

  &lt;p&gt;&lt;img src=&quot;/img/pre-vs-post-norm-stability.webp&quot; alt=&quot;Loss and global gradient-norm curves from the reproducible Pre-Norm and Post-Norm stress test.&quot; loading=&quot;lazy&quot; width=&quot;1484&quot; height=&quot;600&quot; /&gt;&lt;/p&gt;

  &lt;p&gt;On my CPU run, the results were:&lt;/p&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;Configuration&lt;/th&gt;
        &lt;th style=&quot;text-align: right&quot;&gt;Final loss&lt;/th&gt;
        &lt;th style=&quot;text-align: right&quot;&gt;Maximum global gradient norm&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;Pre-Norm, no warmup&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;0.00165&lt;/strong&gt;&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;1.004&lt;/strong&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;Post-Norm, no warmup&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;4.21205&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;6.960&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;Post-Norm, 120-step warmup&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;0.01738&lt;/strong&gt;&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;2.759&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

  &lt;p&gt;The result is intentionally not “Post-Norm is incapable of learning.” Warmup rescues it. The result is that &lt;strong&gt;Pre-Norm tolerates the aggressive target learning rate immediately&lt;/strong&gt;, while this particular Post-Norm run does not.&lt;/p&gt;

  &lt;p&gt;This is a pedagogical stress test, not a reproduction of Xiong et al.: the dataset is synthetic, the model is tiny, and a different seed or hyperparameter can move the stability boundary. The useful exercise is to sweep &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--layers&lt;/code&gt; and the learning rate and observe how much tuning each topology needs.&lt;/p&gt;

  &lt;h3 id=&quot;run-it-with-jax-on-cpu-or-gpu&quot;&gt;Run it with JAX on CPU or GPU&lt;/h3&gt;

  &lt;p&gt;The complete script lives beside this article:&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&quot;https://github.com/jazzikp/jazzikp.github.io/blob/main/_posts/pre_norm_vs_post_norm_demo.py&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_posts/pre_norm_vs_post_norm_demo.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;/img/pre-vs-post-norm-stability.csv&quot;&gt;raw measurements used for the figure&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;From the repository root:&lt;/p&gt;

  &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# CPU, including Apple Silicon. The full run takes about 26 seconds here.&lt;/span&gt;
python &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; pip &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--upgrade&lt;/span&gt; jax flax optax matplotlib pillow
python _posts/pre_norm_vs_post_norm_demo.py &lt;span class=&quot;nt&quot;&gt;--device&lt;/span&gt; cpu

&lt;span class=&quot;c&quot;&gt;# NVIDIA GPU on Linux with JAX&apos;s CUDA 13 wheel.&lt;/span&gt;
python &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; pip &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--upgrade&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;jax[cuda13]&quot;&lt;/span&gt; flax optax matplotlib pillow
python _posts/pre_norm_vs_post_norm_demo.py &lt;span class=&quot;nt&quot;&gt;--device&lt;/span&gt; gpu

&lt;span class=&quot;c&quot;&gt;# Prefer a JAX GPU backend when present, otherwise use CPU.&lt;/span&gt;
python _posts/pre_norm_vs_post_norm_demo.py &lt;span class=&quot;nt&quot;&gt;--device&lt;/span&gt; auto
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;

  &lt;p&gt;The script writes both &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;img/pre-vs-post-norm-stability.webp&lt;/code&gt; and the underlying CSV. It is JAX end to end: the model is written with Flax, gradients come from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jax.value_and_grad&lt;/code&gt;, updates come from Optax, and the training step is compiled with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jax.jit&lt;/code&gt;. On macOS, the supported default is Apple Silicon &lt;strong&gt;CPU&lt;/strong&gt;; the JAX project does not currently ship an official macOS GPU backend. The architectural switch itself is only this:&lt;/p&gt;

  &lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pre_norm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attention&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;attention_norm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mlp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mlp_norm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attention_norm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attention&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mlp_norm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mlp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;

  &lt;p&gt;The compiled update is likewise ordinary JAX:&lt;/p&gt;

  &lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;loss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gradients&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;jax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value_and_grad&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;loss_fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;updates&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;optimizer_state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;optimizer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gradients&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;optimizer_state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;optax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;apply_updates&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;updates&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;

  &lt;p&gt;The full default run is already small enough for CPU use. For a faster smoke test, reduce the work:&lt;/p&gt;

  &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;python _posts/pre_norm_vs_post_norm_demo.py &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--device&lt;/span&gt; cpu &lt;span class=&quot;nt&quot;&gt;--layers&lt;/span&gt; 6 &lt;span class=&quot;nt&quot;&gt;--steps&lt;/span&gt; 30
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;

  &lt;p&gt;That shorter setting checks that the program and selected backend work; it is not guaranteed to reproduce the full stability separation.&lt;/p&gt;

  &lt;h2 id=&quot;why-pre-norm-is-not-simply-better&quot;&gt;Why Pre-Norm is not simply “better”&lt;/h2&gt;

  &lt;p&gt;Pre-Norm buys optimization stability by leaving the residual stream itself unconstrained inside the stack. Expanding the recurrence shows the trade-off:&lt;/p&gt;

\[x_L=x_0+\sum_{l=0}^{L-1}F_l(N(x_l)).\]

  &lt;p&gt;Every layer writes directly into the same accumulating stream. As \(\lVert x_l\rVert\) grows, a new update can become a smaller fraction of the existing state. Later blocks can approach identity transformations and contribute less than their depth suggests. Later work describes related symptoms as representation collapse, residual-stream growth, massive activations, or the “curse of depth.” The exact growth law depends on initialization, training, correlations, and residual scaling; it is not universally exponential.&lt;/p&gt;

  &lt;p&gt;Post-Norm has the opposite appeal: it re-normalizes the state after every residual addition, tightly controlling forward scale. Its cost is making normalization unavoidable in backpropagation. That is why several newer architectures try to keep the best property of each design rather than treating either as universally optimal:&lt;/p&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;Design&lt;/th&gt;
        &lt;th&gt;What it tries to preserve&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;strong&gt;Pre-Norm&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;Clean identity path and robust optimization&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;strong&gt;Post-Norm&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;Controlled block-output scale&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;strong&gt;DeepNorm / residual scaling&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;Post-Norm quality with bounded updates&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;strong&gt;NormFormer&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;More balanced layerwise gradients in Pre-Norm&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;strong&gt;Peri-LN / sandwich norms&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;Identity residual path plus bounded sublayer output&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;strong&gt;Final norm&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;A stable scale before the language-model head&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

  &lt;h2 id=&quot;the-final-norm-has-a-different-job&quot;&gt;The final norm has a different job&lt;/h2&gt;

  &lt;p&gt;A typical modern Pre-Norm LLM also applies a final normalization:&lt;/p&gt;

\[z=N_{\mathrm{final}}(x_L),\qquad
\mathrm{logits}=W_{\mathrm{vocab}}z.\]

  &lt;p&gt;This does not undo the identity-path benefit. The block-level residual stream remains clean throughout depth; normalization is applied once before the output head.&lt;/p&gt;

  &lt;p&gt;Ignoring epsilon and affine parameters, LayerNorm and RMSNorm are approximately zero-homogeneous:&lt;/p&gt;

\[N(c x)=N(x),\qquad c&amp;gt;0.\]

  &lt;p&gt;The final norm therefore acts as a &lt;strong&gt;scale anchor&lt;/strong&gt;: simply inflating \(\lVert x_L\rVert\) cannot inflate the logits. This is separate from the role of the internal Pre-Norms:&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;Internal Pre-Norm:&lt;/strong&gt; stabilize each branch input and preserve the identity gradient path.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Final norm:&lt;/strong&gt; stabilize the scale presented to the language-model head.&lt;/li&gt;
  &lt;/ul&gt;

  &lt;h2 id=&quot;the-design-rule-behind-the-literature&quot;&gt;The design rule behind the literature&lt;/h2&gt;

  &lt;p&gt;The Pre-Norm versus Post-Norm literature can be condensed into two simultaneous requirements:&lt;/p&gt;

  &lt;ol&gt;
    &lt;li&gt;&lt;strong&gt;Keep a clean residual identity path&lt;/strong&gt; so gradients can cross depth.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Control the size of updates written into that path&lt;/strong&gt; so the residual stream does not overwhelm later layers.&lt;/li&gt;
  &lt;/ol&gt;

  &lt;p&gt;Plain Pre-Norm solves the first requirement extremely well, which is why it became the default for deep LLM optimization. It only partially solves the second. DeepNorm, NormFormer, Peri-LN, residual scaling, and related designs are attempts to add scale control without losing the identity highway.&lt;/p&gt;

  &lt;h2 id=&quot;takeaway&quot;&gt;Takeaway&lt;/h2&gt;

  &lt;p&gt;&lt;strong&gt;Pre-Norm is important because it changes the optimization geometry of a deep Transformer.&lt;/strong&gt; In&lt;/p&gt;

\[x_{l+1}=x_l+F_l(N(x_l)),\]

  &lt;p&gt;the derivative always contains an identity term. That gives the gradient a direct route across depth and makes large models less sensitive to their earliest updates.&lt;/p&gt;

  &lt;p&gt;The benefit is primarily &lt;strong&gt;trainability&lt;/strong&gt;, not a guarantee of superior final representation. Post-Norm can work—and may sometimes produce stronger representations—when warmup, initialization, or residual scaling controls its optimization. The modern goal is not merely “put normalization first,” but:&lt;/p&gt;

  &lt;blockquote&gt;
    &lt;p&gt;&lt;strong&gt;Preserve the identity path, then control what each learned branch writes into it.&lt;/strong&gt;&lt;/p&gt;
  &lt;/blockquote&gt;

  &lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;

  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&quot;https://proceedings.mlr.press/v119/xiong20b.html&quot;&gt;Xiong et al., 2020. &lt;em&gt;On Layer Normalization in the Transformer Architecture.&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://aclanthology.org/2020.emnlp-main.463/&quot;&gt;Liu et al., 2020. &lt;em&gt;Understanding the Difficulty of Training Transformers.&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/2110.09456&quot;&gt;Shleifer et al., 2021. &lt;em&gt;NormFormer: Improved Transformer Pretraining with Extra Normalization.&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/2203.00555&quot;&gt;Wang et al., 2022. &lt;em&gt;DeepNet: Scaling Transformers to 1,000 Layers.&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/2502.02732&quot;&gt;Kim et al., 2025. &lt;em&gt;Peri-LN: Revisiting Normalization Layer in the Transformer Architecture.&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/2502.05795&quot;&gt;Sun et al., 2025. &lt;em&gt;The Curse of Depth in Large Language Models.&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;

&lt;/div&gt;

&lt;div data-lang-panel=&quot;zh&quot; hidden=&quot;&quot;&gt;

  &lt;blockquote&gt;
    &lt;p&gt;&lt;strong&gt;核心观点：&lt;/strong&gt; Pre-Norm 之所以重要，是因为每个 Transformer block 都保留了一条归一化不会变换的 residual 通路。这条直接的 identity 路径让梯度能穿越许多层。它主要改善深度模型的优化稳定性；这&lt;strong&gt;并不&lt;/strong&gt;意味着 Pre-Norm 总有更强的最终表达能力。&lt;/p&gt;
  &lt;/blockquote&gt;

  &lt;h2 id=&quot;section&quot;&gt;一行之差&lt;/h2&gt;

  &lt;p&gt;令 \(F_l\) 表示 attention 或 feed-forward network，令 \(N\) 表示 LayerNorm 或 RMSNorm。两种安排只差一行：&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;Pre-Norm&lt;/strong&gt;&lt;/p&gt;

\[x_{l+1}=x_l+F_l(N(x_l))\]

  &lt;p&gt;&lt;strong&gt;Post-Norm&lt;/strong&gt;&lt;/p&gt;

\[x_{l+1}=N(x_l+F_l(x_l))\]

  &lt;p&gt;在 Pre-Norm 里，只有分支计算看到归一化后的输入。residual stream \(x_l\) 原封不动到达下一层。在 Post-Norm 里，residual 仍然在，但求和必须先经过归一化才能成为 \(x_{l+1}\)。&lt;/p&gt;

  &lt;p&gt;&lt;img src=&quot;/img/pre-vs-post-norm-paths.webp&quot; alt=&quot;Pre-Norm 保留一条直接的 identity 梯度路径，而每条 Post-Norm 路径都经过归一化。&quot; loading=&quot;lazy&quot; width=&quot;960&quot; height=&quot;470&quot; /&gt;&lt;/p&gt;

  &lt;p&gt;这张图是论证最有用的形式：&lt;strong&gt;Pre-Norm 有一条贯穿深度、未经修改的蓝色高速路。Post-Norm 有 residual 连接，但没有未经修改的 identity 高速路。&lt;/strong&gt;&lt;/p&gt;

  &lt;h2 id=&quot;identity-&quot;&gt;为什么 identity 路径会改变反向传播&lt;/h2&gt;

  &lt;p&gt;对一个 block 求导。Pre-Norm 给出&lt;/p&gt;

\[\frac{\partial x_{l+1}}{\partial x_l}
=I+J_{F_l}J_N.\]

  &lt;p&gt;\(I\) 项是显式的。当学到的分支较小时——初始化附近常常如此——block Jacobian 接近 identity：&lt;/p&gt;

\[\frac{\partial x_{l+1}}{\partial x_l}\approx I.\]

  &lt;p&gt;因此梯度可以穿越数十个 block，而不必完全依赖 attention、MLP 或归一化的 Jacobian。&lt;/p&gt;

  &lt;p&gt;Post-Norm 则给出&lt;/p&gt;

\[\frac{\partial x_{l+1}}{\partial x_l}
=J_N(I+J_{F_l}).\]

  &lt;p&gt;每条反向路径都包含 \(J_N\)。在深层堆叠里，梯度反复撞上这些归一化 Jacobian。它们的乘积会对其重新缩放和旋转，LayerNorm 还会投影掉特定的平移和缩放方向。residual 加法并没有消失；消失的是&lt;strong&gt;未经变换的 identity 通路&lt;/strong&gt;。&lt;/p&gt;

  &lt;p&gt;这个区分比说“Post-Norm 切断了 residual 连接”更精确。它没有切断。它只是让归一化在相继 residual 状态之间的路径上变得不可避免。&lt;/p&gt;

  &lt;h2 id=&quot;xiong-et-al-&quot;&gt;Xiong et al. 实际建立了什么&lt;/h2&gt;

  &lt;p&gt;&lt;a href=&quot;https://proceedings.mlr.press/v119/xiong20b.html&quot;&gt;Xiong et al. (2020), &lt;em&gt;On Layer Normalization in the Transformer Architecture&lt;/em&gt;&lt;/a&gt; 用平均场论证分析了初始化时的模型。他们的关键观察是逐层不平衡：&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;在 &lt;strong&gt;Post-LN&lt;/strong&gt; 中，靠近输出的参数梯度在初始化时很大。立刻用满目标学习率，最初几次更新会不稳定。&lt;/li&gt;
    &lt;li&gt;在 &lt;strong&gt;Pre-LN&lt;/strong&gt; 中，梯度幅度在深度上更规矩，所以他们的翻译和 BERT 实验可以在没有 learning-rate warmup 的情况下训练。&lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;这解释了为什么 learning-rate warmup 对经典 Post-Norm Transformer 特别重要：warmup 让早期更新保持较小，同时 optimizer 和网络离开脆弱的初始化区间。&lt;/p&gt;

  &lt;p&gt;有两点限定很重要：&lt;/p&gt;

  &lt;ol&gt;
    &lt;li&gt;这是在简化假设下对初始化的分析，不是每条 Post-Norm 运行都会发散的定理。&lt;/li&gt;
    &lt;li&gt;现代 Pre-Norm LLM 训练仍然普遍使用 warmup。Warmup 也有助于 adaptive-optimizer 状态、大 batch、mixed precision 以及训练系统的其他部分。&lt;/li&gt;
  &lt;/ol&gt;

  &lt;p&gt;因此站得住脚的说法是 &lt;strong&gt;“Pre-Norm 更不敏感、更容易深度优化”&lt;/strong&gt;，而不是“Pre-Norm 从不需要 warmup”。&lt;/p&gt;

  &lt;h2 id=&quot;section-1&quot;&gt;一个可运行的稳定性压力测试&lt;/h2&gt;

  &lt;p&gt;本文配套代码用三种方式训练同一个小型 12 层 causal Transformer：&lt;/p&gt;

  &lt;ol&gt;
    &lt;li&gt;Pre-Norm，恒定学习率 \(2\times10^{-3}\)；&lt;/li&gt;
    &lt;li&gt;Post-Norm，相同的恒定学习率；&lt;/li&gt;
    &lt;li&gt;Post-Norm，线性 warmup 持续整个 run 的三分之二（默认 180-step 实验中为 120 steps），到达相同的目标学习率。&lt;/li&gt;
  &lt;/ol&gt;

  &lt;p&gt;其他一切——数据、初始化 seed、optimizer、深度、宽度和更新次数——都固定。任务故意很简单：给定一个算术 token 序列，预测下一个 token。没有 gradient clipping。&lt;/p&gt;

  &lt;p&gt;&lt;img src=&quot;/img/pre-vs-post-norm-stability.webp&quot; alt=&quot;可复现的 Pre-Norm 与 Post-Norm 压力测试的 loss 和全局 gradient-norm 曲线。&quot; loading=&quot;lazy&quot; width=&quot;1484&quot; height=&quot;600&quot; /&gt;&lt;/p&gt;

  &lt;p&gt;在我的 CPU 运行中，结果是：&lt;/p&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;配置&lt;/th&gt;
        &lt;th style=&quot;text-align: right&quot;&gt;最终 loss&lt;/th&gt;
        &lt;th style=&quot;text-align: right&quot;&gt;最大全局 gradient norm&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;Pre-Norm，无 warmup&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;0.00165&lt;/strong&gt;&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;1.004&lt;/strong&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;Post-Norm，无 warmup&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;4.21205&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;6.960&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;Post-Norm，120-step warmup&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;0.01738&lt;/strong&gt;&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;2.759&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

  &lt;p&gt;结果故意不是“Post-Norm 学不会”。Warmup 救了它。结果是 &lt;strong&gt;Pre-Norm 能立刻容忍激进的目标学习率&lt;/strong&gt;，而这次特定的 Post-Norm 运行不能。&lt;/p&gt;

  &lt;p&gt;这是教学用的压力测试，不是对 Xiong et al. 的复现：数据集是合成的，模型很小，换一个 seed 或超参数就能移动稳定性边界。有用的练习是扫描 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--layers&lt;/code&gt; 和学习率，观察每种拓扑需要多少调参。&lt;/p&gt;

  &lt;h3 id=&quot;jax--cpu--gpu-&quot;&gt;用 JAX 在 CPU 或 GPU 上运行&lt;/h3&gt;

  &lt;p&gt;完整脚本就在本文旁边：&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&quot;https://github.com/jazzikp/jazzikp.github.io/blob/main/_posts/pre_norm_vs_post_norm_demo.py&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_posts/pre_norm_vs_post_norm_demo.py&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;/img/pre-vs-post-norm-stability.csv&quot;&gt;图中使用的原始测量数据&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;从仓库根目录：&lt;/p&gt;

  &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# CPU, including Apple Silicon. The full run takes about 26 seconds here.&lt;/span&gt;
python &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; pip &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--upgrade&lt;/span&gt; jax flax optax matplotlib pillow
python _posts/pre_norm_vs_post_norm_demo.py &lt;span class=&quot;nt&quot;&gt;--device&lt;/span&gt; cpu

&lt;span class=&quot;c&quot;&gt;# NVIDIA GPU on Linux with JAX&apos;s CUDA 13 wheel.&lt;/span&gt;
python &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; pip &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--upgrade&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;jax[cuda13]&quot;&lt;/span&gt; flax optax matplotlib pillow
python _posts/pre_norm_vs_post_norm_demo.py &lt;span class=&quot;nt&quot;&gt;--device&lt;/span&gt; gpu

&lt;span class=&quot;c&quot;&gt;# Prefer a JAX GPU backend when present, otherwise use CPU.&lt;/span&gt;
python _posts/pre_norm_vs_post_norm_demo.py &lt;span class=&quot;nt&quot;&gt;--device&lt;/span&gt; auto
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;

  &lt;p&gt;脚本会写出 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;img/pre-vs-post-norm-stability.webp&lt;/code&gt; 和底层 CSV。它是端到端的 JAX：模型用 Flax 写，梯度来自 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jax.value_and_grad&lt;/code&gt;，更新来自 Optax，训练 step 用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jax.jit&lt;/code&gt; 编译。在 macOS 上，支持的默认是 Apple Silicon &lt;strong&gt;CPU&lt;/strong&gt;；JAX 项目目前没有发布官方的 macOS GPU backend。架构切换本身只有这些：&lt;/p&gt;

  &lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pre_norm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attention&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;attention_norm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mlp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mlp_norm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attention_norm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attention&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mlp_norm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mlp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;

  &lt;p&gt;编译后的更新同样是普通的 JAX：&lt;/p&gt;

  &lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;loss&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;gradients&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;jax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value_and_grad&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;loss_fn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;updates&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;optimizer_state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;optimizer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;gradients&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;optimizer_state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;optax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;apply_updates&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;updates&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;

  &lt;p&gt;完整默认运行已经小到可以在 CPU 上用。若要更快的冒烟测试，减少工作量：&lt;/p&gt;

  &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;python _posts/pre_norm_vs_post_norm_demo.py &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--device&lt;/span&gt; cpu &lt;span class=&quot;nt&quot;&gt;--layers&lt;/span&gt; 6 &lt;span class=&quot;nt&quot;&gt;--steps&lt;/span&gt; 30
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;

  &lt;p&gt;这个更短的设置用来检查程序和所选 backend 能工作；不保证能复现完整的稳定性分离。&lt;/p&gt;

  &lt;h2 id=&quot;pre-norm-&quot;&gt;为什么 Pre-Norm 并不只是“更好”&lt;/h2&gt;

  &lt;p&gt;Pre-Norm 通过让 residual stream 本身在堆叠内部不受约束来换取优化稳定性。展开递推式可以看出权衡：&lt;/p&gt;

\[x_L=x_0+\sum_{l=0}^{L-1}F_l(N(x_l)).\]

  &lt;p&gt;每一层都直接写入同一条累积的 stream。随着 \(\lVert x_l\rVert\) 增大，一次新更新可能只占现有状态的更小比例。后面的 block 可能接近 identity 变换，贡献比其深度所暗示的更少。后续工作把相关症状描述为 representation collapse、residual-stream 增长、massive activations，或“深度诅咒”。精确的增长规律取决于初始化、训练、相关性和 residual scaling；它并非普遍指数增长。&lt;/p&gt;

  &lt;p&gt;Post-Norm 有相反的吸引力：它在每次 residual 加法之后重新归一化状态，严格控制前向尺度。代价是让归一化在反向传播中不可避免。这就是为什么若干更新的架构试图保留每种设计的最佳性质，而不是把任何一种当作普遍最优：&lt;/p&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;设计&lt;/th&gt;
        &lt;th&gt;试图保留什么&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;strong&gt;Pre-Norm&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;干净的 identity 路径和稳健的优化&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;strong&gt;Post-Norm&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;受控的 block 输出尺度&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;strong&gt;DeepNorm / residual scaling&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;带有有界更新的 Post-Norm 质量&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;strong&gt;NormFormer&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;Pre-Norm 中更平衡的逐层梯度&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;strong&gt;Peri-LN / sandwich norms&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;Identity residual 路径加上有界的 sublayer 输出&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;&lt;strong&gt;Final norm&lt;/strong&gt;&lt;/td&gt;
        &lt;td&gt;language-model head 之前的稳定尺度&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

  &lt;h2 id=&quot;final-norm-&quot;&gt;Final norm 有不同的职责&lt;/h2&gt;

  &lt;p&gt;典型的现代 Pre-Norm LLM 还会施加一次最终归一化：&lt;/p&gt;

\[z=N_{\mathrm{final}}(x_L),\qquad
\mathrm{logits}=W_{\mathrm{vocab}}z.\]

  &lt;p&gt;这并不会抵消 identity 路径的好处。block 级 residual stream 在整个深度上保持干净；归一化只在 output head 之前施加一次。&lt;/p&gt;

  &lt;p&gt;忽略 epsilon 和仿射参数，LayerNorm 和 RMSNorm 近似为零次齐次：&lt;/p&gt;

\[N(c x)=N(x),\qquad c&amp;gt;0.\]

  &lt;p&gt;因此 final norm 充当 &lt;strong&gt;尺度锚&lt;/strong&gt;：单纯放大 \(\lVert x_L\rVert\) 无法放大 logits。这与内部 Pre-Norm 的作用是分开的：&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;内部 Pre-Norm：&lt;/strong&gt; 稳定每个分支输入并保留 identity 梯度路径。&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Final norm：&lt;/strong&gt; 稳定呈现给 language-model head 的尺度。&lt;/li&gt;
  &lt;/ul&gt;

  &lt;h2 id=&quot;section-2&quot;&gt;文献背后的设计规则&lt;/h2&gt;

  &lt;p&gt;Pre-Norm 与 Post-Norm 的文献可以浓缩为两个同时要满足的要求：&lt;/p&gt;

  &lt;ol&gt;
    &lt;li&gt;&lt;strong&gt;保持干净的 residual identity 路径&lt;/strong&gt;，让梯度能穿越深度。&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;控制写入该路径的更新大小&lt;/strong&gt;，让 residual stream 不会压倒后面的层。&lt;/li&gt;
  &lt;/ol&gt;

  &lt;p&gt;朴素 Pre-Norm 把第一个要求解决得非常好，这就是它成为深度 LLM 优化默认选择的原因。它只部分解决了第二个。DeepNorm、NormFormer、Peri-LN、residual scaling 以及相关设计，都是在不丢失 identity 高速路的前提下加入尺度控制的尝试。&lt;/p&gt;

  &lt;h2 id=&quot;section-3&quot;&gt;要点&lt;/h2&gt;

  &lt;p&gt;&lt;strong&gt;Pre-Norm 之所以重要，是因为它改变了深度 Transformer 的优化几何。&lt;/strong&gt; 在&lt;/p&gt;

\[x_{l+1}=x_l+F_l(N(x_l)),\]

  &lt;p&gt;导数始终包含一个 identity 项。这给梯度一条穿越深度的直接通路，并让大模型对最早的更新不那么敏感。&lt;/p&gt;

  &lt;p&gt;好处主要是 &lt;strong&gt;可训练性&lt;/strong&gt;，而不是最终表示更优的保证。当 warmup、初始化或 residual scaling 控制了其优化时，Post-Norm 可以工作——有时还可能产生更强的表示。现代目标不仅仅是“把归一化放在前面”，而是：&lt;/p&gt;

  &lt;blockquote&gt;
    &lt;p&gt;&lt;strong&gt;保留 identity 路径，然后控制每个学到的分支写入其中的内容。&lt;/strong&gt;&lt;/p&gt;
  &lt;/blockquote&gt;

  &lt;h2 id=&quot;section-4&quot;&gt;参考文献&lt;/h2&gt;

  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&quot;https://proceedings.mlr.press/v119/xiong20b.html&quot;&gt;Xiong et al., 2020. &lt;em&gt;On Layer Normalization in the Transformer Architecture.&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://aclanthology.org/2020.emnlp-main.463/&quot;&gt;Liu et al., 2020. &lt;em&gt;Understanding the Difficulty of Training Transformers.&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/2110.09456&quot;&gt;Shleifer et al., 2021. &lt;em&gt;NormFormer: Improved Transformer Pretraining with Extra Normalization.&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/2203.00555&quot;&gt;Wang et al., 2022. &lt;em&gt;DeepNet: Scaling Transformers to 1,000 Layers.&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/2502.02732&quot;&gt;Kim et al., 2025. &lt;em&gt;Peri-LN: Revisiting Normalization Layer in the Transformer Architecture.&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://arxiv.org/abs/2502.05795&quot;&gt;Sun et al., 2025. &lt;em&gt;The Curse of Depth in Large Language Models.&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;

&lt;/div&gt;
</description>
        <pubDate>Sun, 16 Aug 2026 00:00:00 +0000</pubDate>
        <link>https://jazzikp.github.io/2026/08/16/pre-norm-vs-post-norm/</link>
        <guid isPermaLink="true">https://jazzikp.github.io/2026/08/16/pre-norm-vs-post-norm/</guid>
        
        <category>LLM</category>
        
        <category>Transformer</category>
        
        <category>Normalization</category>
        
        <category>JAX</category>
        
        
      </item>
    
      <item>
        <title>Kimi K3: An In-Depth Look at KDA</title>
        <description>
&lt;div data-lang-panel=&quot;en&quot;&gt;

  &lt;p&gt;Kimi K3 (&lt;a href=&quot;https://arxiv.org/abs/2607.24653&quot;&gt;arXiv:2607.24653&lt;/a&gt;) is a 2.8T-parameter MoE with 104B active parameters, 93 layers, and a 1M-token context. Two architectural bets carry the report:&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;Kimi Delta Attention (KDA)&lt;/strong&gt; — how information moves along the &lt;strong&gt;sequence&lt;/strong&gt;&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Attention Residuals (AttnRes)&lt;/strong&gt; — how information moves along &lt;strong&gt;depth&lt;/strong&gt;&lt;/li&gt;
  &lt;/ul&gt;

  &lt;h2 id=&quot;part-1--kimi-delta-attention&quot;&gt;Part 1 — Kimi Delta Attention&lt;/h2&gt;

  &lt;h3 id=&quot;what-kda-is-replacing&quot;&gt;What KDA is replacing&lt;/h3&gt;

  &lt;p&gt;Softmax attention keeps every token it has ever seen: generating token \(t\) compares the query against \(t\) cached keys, so per-token cost and KV cache both grow with the context. At 1M tokens that cache &lt;em&gt;is&lt;/em&gt; the deployment bill.&lt;/p&gt;

  &lt;p&gt;Linear attention takes the other side of the trade. Drop the softmax and the whole history collapses into one fixed-size matrix \(\mathbf{S} \in \mathbb{R}^{d_k \times d_v}\) carried from token to token:&lt;/p&gt;

\[\mathbf{S}_t = \mathbf{S}_{t-1} + k_t v_t^\top, \qquad o_t = \mathbf{S}_t^\top q_t\]

  &lt;p&gt;Constant memory, constant work per token, unbounded context. The catch is that the state never gets bigger, so &lt;strong&gt;everything in KDA is about what to write into it and what to erase.&lt;/strong&gt;&lt;/p&gt;

  &lt;h3 id=&quot;the-state-is-an-associative-memory&quot;&gt;The state is an associative memory&lt;/h3&gt;

  &lt;p&gt;Read the update again as a memory. Writing \(kv^\top\) stores the pair \((k, v)\); reading is a dot product against every key at once:&lt;/p&gt;

\[\mathbf{S}^\top q = \sum_i (k_i^\top q)\, v_i\]

  &lt;p&gt;If the keys are orthonormal, querying \(k_1\) returns exactly \(v_1\) — a \(d_k \times d_v\) matrix holds \(d_k\) clean slots. Reality is messier in two ways.&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;Keys collide.&lt;/strong&gt; With \(k_3\) at 45° to \(k_1\), storing \((k_1, v_1)\) then \((k_3, v_2)\) and reading at \(k_1\) gives \(v_1 + 0.707\,v_2\). Crosstalk.&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;Rewrites pile up.&lt;/strong&gt; Store &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x = 1&lt;/code&gt;, later &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x = 3&lt;/code&gt;; plain accumulation hands back &lt;em&gt;both&lt;/em&gt;:&lt;/p&gt;

  &lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;write (k1,v1), (k2,v2), then (k1,v1&apos;)     read at k1
  plain accumulation   -&amp;gt;  v1 + v1&apos;      (stale value still there)
  delta rule, β = 1    -&amp;gt;  v1&apos;           (old value erased)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;

  &lt;p&gt;A fixed-size memory that only ever adds becomes an increasingly blurry average of everything. Two fixes exist, and KDA uses both.&lt;/p&gt;

  &lt;h3 id=&quot;fix-1--forgetting-one-rate-per-channel&quot;&gt;Fix 1 — forgetting, one rate per channel&lt;/h3&gt;

  &lt;p&gt;Multiply the state down before each write:&lt;/p&gt;

\[\mathbf{S}_t = \operatorname{Diag}(\alpha_t)\,\mathbf{S}_{t-1} + k_tv_t^\top\]

  &lt;p&gt;Mamba-2 and Gated DeltaNet use a &lt;strong&gt;scalar&lt;/strong&gt; \(\alpha_t\): the whole memory fades at one rate. KDA makes \(\alpha_t \in (0,1)^{d_k}\) &lt;strong&gt;channel-wise&lt;/strong&gt; — one retention factor per key dimension, chosen per token. Row \(j\) of the state fades at its own speed, so the same layer can hold a fast, local channel next to one that keeps information for thousands of tokens:&lt;/p&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;per-step log-decay \(g\)&lt;/th&gt;
        &lt;th style=&quot;text-align: right&quot;&gt;retention \(\alpha = e^{g}\)&lt;/th&gt;
        &lt;th style=&quot;text-align: right&quot;&gt;half-life&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;\(-0.0001\)&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;0.99990&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;6931 tokens&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;\(-0.1\)&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;0.90484&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;7 tokens&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

  &lt;p&gt;This is also where K3 gets its sense of position. Decay makes “20 tokens ago” measurably weaker than “2 tokens ago”, so the model needs &lt;strong&gt;no positional encoding at all&lt;/strong&gt; (NoPE) — and a model with no RoPE frequencies has nothing to retune when the context window grows from 8K to 1M.&lt;/p&gt;

  &lt;h3 id=&quot;fix-2--the-delta-rule-or-writing-by-correction&quot;&gt;Fix 2 — the delta rule, or writing by correction&lt;/h3&gt;

  &lt;p&gt;Instead of asking what to add, ask what the memory currently gets &lt;em&gt;wrong&lt;/em&gt;. The memory should satisfy \(\mathbf{S}^\top k_t = v_t\), so define a loss and take one gradient step:&lt;/p&gt;

\[\mathcal{L}(\mathbf{S}) = \tfrac{1}{2}\lVert \mathbf{S}^\top k_t - v_t \rVert^2,
\quad
\nabla_{\mathbf{S}}\mathcal{L} = k_t(\mathbf{S}^\top k_t - v_t)^\top\]

\[\mathbf{S}_t = \mathbf{S}_{t-1} - \beta_t \nabla_{\mathbf{S}}\mathcal{L}
= \left(\mathbf{I} - \beta_t k_tk_t^\top\right)\mathbf{S}_{t-1} + \beta_t k_tv_t^\top\]

  &lt;p&gt;That is the delta rule, and &lt;strong&gt;a linear-attention layer is doing online gradient descent on its own memory at inference time&lt;/strong&gt;, with \(\beta_t\) as the learning rate.&lt;/p&gt;

  &lt;p&gt;The geometry is the part worth keeping. Because KDA L2-normalises its keys, \(\lVert k_t\rVert = 1\) and \(\mathbf{I} - \beta_tk_tk_t^\top\) is a scaling along \(k_t\) and the identity everywhere else. It &lt;strong&gt;erases only what was stored at this key&lt;/strong&gt; and leaves the rest of the memory untouched:&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;\(\beta_t = 1\) — full overwrite: whatever was at \(k_t\) is gone, \(v_t\) takes its place&lt;/li&gt;
    &lt;li&gt;\(\beta_t = 0\) — no write at all&lt;/li&gt;
    &lt;li&gt;in between — a partial correction toward \(v_t\) (with \(\beta = 0.5\) the numbers above give \(0.25\,v_1 + 0.5\,v_1&apos;\))&lt;/li&gt;
  &lt;/ul&gt;

  &lt;h3 id=&quot;kda--both-and-here-is-the-whole-layer&quot;&gt;KDA = both, and here is the whole layer&lt;/h3&gt;

\[\mathbf{S}_t=\left(\mathbf{I}-\beta_t k_t k_t^{\top}\right) \operatorname{Diag}(\alpha_t) \mathbf{S}_{t-1}+\beta_t k_t v_t^{\top},
\qquad \tilde{o}_t=\mathbf{S}_t^{\top} q_t\]

  &lt;p&gt;Decay first, then the correction. Everything on the right is produced from the token itself:&lt;/p&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;term&lt;/th&gt;
        &lt;th&gt;what it is&lt;/th&gt;
        &lt;th&gt;how it is produced&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;\(q_t,k_t\)&lt;/td&gt;
        &lt;td&gt;probe / address&lt;/td&gt;
        &lt;td&gt;ShortConv → Swish → &lt;strong&gt;L2Norm&lt;/strong&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;\(v_t\)&lt;/td&gt;
        &lt;td&gt;payload&lt;/td&gt;
        &lt;td&gt;ShortConv → Swish&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;\(\beta_t \in (0,1)\)&lt;/td&gt;
        &lt;td&gt;write strength&lt;/td&gt;
        &lt;td&gt;\(\operatorname{Sigmoid}(\mathbf{W}_\beta x_t)\)&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;\(\alpha_t \in (0,1)^{d_k}\)&lt;/td&gt;
        &lt;td&gt;per-channel retention&lt;/td&gt;
        &lt;td&gt;low-rank logits + per-head bias, then Eq. below&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

  &lt;p&gt;The ShortConv gives each token a short local window before it becomes a key or value; the L2Norm is what makes the erase step a clean projection rather than an arbitrary rescaling.&lt;/p&gt;

  &lt;p&gt;The update is easiest to implement in its equivalent &lt;strong&gt;decay → predict → correct → read&lt;/strong&gt; form:&lt;/p&gt;

  &lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;decayed_state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;row_scale&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;alpha_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;prediction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transpose_matrix_vector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;decayed_state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vector_sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prediction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;matrix_add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;decayed_state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;matrix_scale&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;beta_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;outer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;output&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transpose_matrix_vector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;query_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;

  &lt;p&gt;This version exposes the idea more clearly than constructing \(\mathbf I-\beta kk^\top\): the layer forgets rows of its state, asks what value the decayed state predicts for the current key, and writes only the residual error.&lt;/p&gt;

  &lt;h3 id=&quot;run-the-complete-tutorial-on-a-cpu&quot;&gt;Run the complete tutorial on a CPU&lt;/h3&gt;

  &lt;p&gt;The &lt;a href=&quot;/examples/kda_cpu_tutorial.py&quot;&gt;complete dependency-free Python tutorial&lt;/a&gt; uses only the standard library. It is not Kimi K3 and it is not a fast production kernel; it is a small numerical laboratory for the equations in this article. From the repository root:&lt;/p&gt;

  &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;python3 examples/kda_cpu_tutorial.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;

  &lt;p&gt;It runs five experiments:&lt;/p&gt;

  &lt;ol&gt;
    &lt;li&gt;writes an association, repeats it, and shows that the delta rule does not double-count it;&lt;/li&gt;
    &lt;li&gt;overwrites the same key with a new value and demonstrates the effect of \(\beta\);&lt;/li&gt;
    &lt;li&gt;applies different retention factors to different state rows;&lt;/li&gt;
    &lt;li&gt;verifies the UT chunk form against token-by-token KDA;&lt;/li&gt;
    &lt;li&gt;verifies associative segment composition for KDA Context Parallelism and compares the old and new decay maps.&lt;/li&gt;
  &lt;/ol&gt;

  &lt;p&gt;I ran it on CPU. The two ways of evaluating a chunk agreed to floating-point precision:&lt;/p&gt;

  &lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Maximum output difference:      8.882e-16
Maximum final-state difference: 6.661e-16
Maximum segment difference:     4.441e-16
All numerical equivalence checks passed.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;

  &lt;p&gt;I also ran 100 random recurrence-versus-chunk tests and 100 random segment-composition tests; all passed. These checks validate the educational implementation’s algebra, not the speed or low-precision numerical behavior of FlashKDA’s CUDA kernel.&lt;/p&gt;

  &lt;h3 id=&quot;big-o-what-becomes-linear-and-what-does-not&quot;&gt;Big-O: what becomes linear, and what does not&lt;/h3&gt;

  &lt;p&gt;Let \(T\) be sequence length, \(H\) the number of heads, and \(d_k,d_v\) the key and value dimensions per head. Ignoring the Q/K/V projections shared by both designs, causal softmax attention and recurrent KDA scale as follows:&lt;/p&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;mechanism&lt;/th&gt;
        &lt;th style=&quot;text-align: right&quot;&gt;sequence work&lt;/th&gt;
        &lt;th style=&quot;text-align: right&quot;&gt;decode cache&lt;/th&gt;
        &lt;th style=&quot;text-align: right&quot;&gt;next token&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;softmax&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;\(O(HT^2d_k)\)&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;\(O(HT(d_k+d_v))\)&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;\(O(HT(d_k+d_v))\)&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;KDA&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;\(O(HTd_kd_v)\)&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;\(O(Hd_kd_v)\)&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;\(O(Hd_kd_v)\)&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

  &lt;p&gt;When \(H,d_k,d_v\) are fixed architectural constants, KDA is \(O(T)\) over a sequence and \(O(1)\) in persistent cache with respect to context length. “Constant” does not mean free: every new token still reads and updates an entire \(d_k\times d_v\) state per head.&lt;/p&gt;

  &lt;p&gt;Kimi K3 uses \(H=96\) and \(d_k=d_v=128\), so one KDA layer carries&lt;/p&gt;

\[96\times128\times128=1{,}572{,}864\]

  &lt;p&gt;state elements per sequence: about 3 MiB in BF16. Across 69 KDA layers that is about 207 MiB before tensor-parallel sharding, convolution histories, allocators, and saved prefix checkpoints. Its size does not grow when the context goes from 1K to 1M tokens.&lt;/p&gt;

  &lt;p&gt;The production chunk kernel has the per-head attention cost reported in Kimi Linear:&lt;/p&gt;

\[6Td_h^2+3TCd_h+TC^2,\]

  &lt;p&gt;where \(C\) is chunk size and \(d_h=d_k=d_v\). This remains \(O(T)\) for fixed \(C\) and \(d_h\). The tutorial’s explicit Python loops are deliberately readable rather than optimized; timing them would measure Python overhead, not FlashKDA.&lt;/p&gt;

  &lt;p&gt;The whole Kimi K3 model is also not constant-cache. Its 24 MLA layers still keep sequence-growing latent KV caches and perform global attention. The 69 KDA layers remove that growth from roughly three quarters of the attention stack.&lt;/p&gt;

  &lt;h3 id=&quot;why-use-kda&quot;&gt;Why use KDA?&lt;/h3&gt;

  &lt;p&gt;KDA is useful when generation is long enough that repeatedly reading a growing KV cache dominates cost:&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;Fixed recurrent memory.&lt;/strong&gt; Most layers replace per-token KV entries with one state matrix.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Corrective writes.&lt;/strong&gt; The delta rule overwrites an association instead of accumulating stale and duplicate values.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Different memory timescales.&lt;/strong&gt; Channel-wise \(\alpha\) lets one head maintain fast-forgetting and slow-retaining features simultaneously.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Implicit order and recency.&lt;/strong&gt; The ordered, data-dependent transitions change when token order changes, so the model can use NoPE in its global layers.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Exact GPU-friendly reformulation.&lt;/strong&gt; Training uses chunkwise matrix multiplication without changing the mathematical recurrence.&lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;The price is finite capacity. A fixed state can blur unrelated facts or lose exact token details, which is why Kimi K3 retains periodic global MLA rather than using KDA everywhere.&lt;/p&gt;

  &lt;h3 id=&quot;chunkwise-parallel-form-step-by-step&quot;&gt;Chunkwise parallel form, step by step&lt;/h3&gt;

  &lt;p&gt;The recurrent loop is ideal for decoding one token at a time, but poor for training: token \(r\) needs the state produced by token \(r-1\), so a literal implementation launches one small matrix update after another. Chunkwise KDA does &lt;strong&gt;not&lt;/strong&gt; remove the recurrence. It packages most of the work inside a fixed-size chunk into dense matrix multiplications, while passing one state sequentially between chunks.&lt;/p&gt;

  &lt;h4 id=&quot;lay-out-one-chunk&quot;&gt;1. Lay out one chunk&lt;/h4&gt;

  &lt;p&gt;For a chunk of \(C\) tokens, stack rows as&lt;/p&gt;

\[\mathbf Q,\mathbf K\in\mathbb R^{C\times d_k},\qquad
\mathbf V,\mathbf O\in\mathbb R^{C\times d_v},\qquad
\mathbf S_{\mathrm{in}}\in\mathbb R^{d_k\times d_v}.\]

  &lt;p&gt;For positions \(1\le i\le j\le C\), define the channel-wise cumulative retention&lt;/p&gt;

\[\gamma^{i\to j}=\prod_{r=i}^{j}\alpha^r,
\qquad
\gamma^j=\gamma^{1\to j}.\]

  &lt;p&gt;Every product is elementwise over the \(d_k\) key channels. Let \(\mathbf\Gamma\in\mathbb R^{C\times d_k}\) stack \(\gamma^1,\ldots,\gamma^C\) row by row.&lt;/p&gt;

  &lt;h4 id=&quot;see-what-the-recurrence-is-hiding&quot;&gt;2. See what the recurrence is hiding&lt;/h4&gt;

  &lt;p&gt;Write one token transition as&lt;/p&gt;

\[\mathbf T_r=(\mathbf I-\beta_r k_rk_r^\top)\operatorname{Diag}(\alpha_r),
\qquad
\mathbf B_r=\beta_rk_rv_r^\top.\]

  &lt;p&gt;After the first \(r\) tokens of the chunk,&lt;/p&gt;

\[\mathbf S^r=
\underbrace{\mathbf T_r\mathbf T_{r-1}\cdots\mathbf T_1}_{\mathbf P^r}
\mathbf S_{\mathrm{in}}
+
\sum_{i=1}^{r}
\mathbf T_r\cdots\mathbf T_{i+1}\mathbf B_i.\]

  &lt;p&gt;The newest transition is on the left. This equation explains the difficulty: every write is transformed by all later decays and delta erasures. Computing those products separately for every output would repeat the same work.&lt;/p&gt;

  &lt;h4 id=&quot;turn-relative-decay-into-querykey-scaling&quot;&gt;3. Turn relative decay into query/key scaling&lt;/h4&gt;

  &lt;p&gt;Consider a query at row \(i\) reading a key written at row \(j\le i\). The retention between them is&lt;/p&gt;

\[\frac{\gamma^i}{\gamma^j}
=
\prod_{r=j+1}^{i}\alpha^r.\]

  &lt;p&gt;Therefore its decayed similarity can be written as&lt;/p&gt;

\[q_i^\top\operatorname{Diag}\!\left(\frac{\gamma^i}{\gamma^j}\right)k_j
=
(\gamma^i\odot q_i)^\top(k_j/\gamma^j).\]

  &lt;p&gt;All such scores appear in one matrix multiplication:&lt;/p&gt;

\[\mathbf A=
\operatorname{Tril}\!\left[
(\mathbf Q\odot\mathbf\Gamma)
(\mathbf K/\mathbf\Gamma)^\top
\right]
\in\mathbb R^{C\times C}.\]

  &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Tril&lt;/code&gt; removes future positions but &lt;strong&gt;keeps the diagonal&lt;/strong&gt;. For \(i=j\), the ratio is one: an output reads \(\mathbf S_i\) after its own write, so token \(i\) must be allowed to read its own pseudo-value.&lt;/p&gt;

  &lt;h4 id=&quot;fold-the-delta-rule-dependencies-with-the-ut-transform&quot;&gt;4. Fold the delta-rule dependencies with the UT transform&lt;/h4&gt;

  &lt;p&gt;Decay scaling alone is not enough, because a delta write stores the residual after accounting for previous writes. Define&lt;/p&gt;

\[\mathbf L=
\operatorname{StrictTril}\!\left[
\operatorname{Diag}(\boldsymbol\beta)
(\mathbf\Gamma\odot\mathbf K)
(\mathbf K/\mathbf\Gamma)^\top
\right]
\in\mathbb R^{C\times C}.\]

  &lt;p&gt;The strict lower triangle contains how each earlier key changes the prediction seen by a later key. The UT transform solves that causal system:&lt;/p&gt;

\[\mathbf M=(\mathbf I+\mathbf L)^{-1}\operatorname{Diag}(\boldsymbol\beta),\]

\[\mathbf W=\mathbf M(\mathbf\Gamma\odot\mathbf K)
\in\mathbb R^{C\times d_k},
\qquad
\mathbf U=\mathbf M\mathbf V
\in\mathbb R^{C\times d_v}.\]

  &lt;p&gt;Because \(\mathbf I+\mathbf L\) is lower triangular with ones on its diagonal, this is a forward substitution rather than a general matrix inverse. The compact &lt;strong&gt;pseudo-values&lt;/strong&gt; are&lt;/p&gt;

\[\widetilde{\mathbf V}
=
\mathbf U-\mathbf W\mathbf S_{\mathrm{in}}
\in\mathbb R^{C\times d_v}.\]

  &lt;p&gt;The two terms have direct meanings: \(\mathbf U\) contains mutually corrected current-chunk values, while \(\mathbf W\mathbf S_{\mathrm{in}}\) subtracts what the incoming state already predicts. Thus each row of \(\widetilde{\mathbf V}\) is the effective residual that token contributes after all earlier in-chunk corrections.&lt;/p&gt;

  &lt;h4 id=&quot;produce-every-output-in-the-chunk&quot;&gt;5. Produce every output in the chunk&lt;/h4&gt;

  &lt;p&gt;Once \(\widetilde{\mathbf V}\) is known,&lt;/p&gt;

\[\boxed{
\mathbf O=
\underbrace{(\mathbf\Gamma\odot\mathbf Q)\mathbf S_{\mathrm{in}}}_{\text{memory from earlier chunks}}
+
\underbrace{\mathbf A\widetilde{\mathbf V}}_{\text{writes in this chunk}}
}\]

  &lt;p&gt;has shape \(C\times d_v\). The first term decays and queries the state entering the chunk. The second is a causal weighted sum of corrected writes from the current chunk. This is Kimi K3 Eq. 4.&lt;/p&gt;

  &lt;p&gt;The state handed to the next chunk is computed from the same pseudo-values:&lt;/p&gt;

\[\boxed{
\mathbf S_{\mathrm{out}}
=
\operatorname{Diag}(\gamma^C)\mathbf S_{\mathrm{in}}
+
(\mathbf\Delta\odot\mathbf K)^\top\widetilde{\mathbf V}
}\]

  &lt;p&gt;where row \(i\) of \(\mathbf\Delta\) is the retention &lt;em&gt;after&lt;/em&gt; that write,&lt;/p&gt;

\[\Delta_i=\prod_{r=i+1}^{C}\alpha^r,\]

  &lt;p&gt;with an empty product of one for the last token. The incoming state experiences all \(C\) decays; a write at position \(i\) experiences only later decays.&lt;/p&gt;

  &lt;h4 id=&quot;what-is-actually-parallel&quot;&gt;6. What is actually parallel?&lt;/h4&gt;

  &lt;p&gt;The chunks remain recurrent: \(\mathbf S_{\mathrm{out}}\) from chunk \(t\) is \(\mathbf S_{\mathrm{in}}\) for chunk \(t+1\). Inside a chunk, the expensive query-key, output, and state-update work is expressed as matrix multiplication. A causal triangular solve remains in the UT transform, so “parallel within a chunk” means &lt;strong&gt;the bulk of the arithmetic is tiled dense work&lt;/strong&gt;, not that every operation is independent.&lt;/p&gt;

  &lt;p&gt;The &lt;a href=&quot;/examples/kda_cpu_tutorial.py&quot;&gt;CPU tutorial’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;official_naive_chunk_kda&lt;/code&gt;&lt;/a&gt; implements this sequence explicitly: cumulative log-decay, lower-triangular UT solve, \(\mathbf W/\mathbf U\), pseudo-values, causal scores, outputs, and final state. Against token-by-token recurrence it produced maximum output and state differences of \(8.882\times10^{-16}\) and \(6.661\times10^{-16}\). The chunkwise algorithm is an exact algebraic rewrite in real arithmetic; different accumulation order and BF16 storage explain small production-kernel differences.&lt;/p&gt;

  &lt;h3 id=&quot;what-k3-changed-relative-to-kimi-linear&quot;&gt;What K3 changed relative to Kimi Linear&lt;/h3&gt;

  &lt;p&gt;KDA comes from Kimi Linear (&lt;a href=&quot;https://arxiv.org/abs/2510.26692&quot;&gt;arXiv:2510.26692&lt;/a&gt;). K3 makes two edits, both small on paper and both about hardware.&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;1. Lower-bounded decay.&lt;/strong&gt; Look again at \(\mathbf{K}/\mathbf{\Gamma}\): a &lt;em&gt;reciprocal&lt;/em&gt; of a product of numbers below 1. Kimi Linear’s decay came from a negative softplus, \(g = -e^{A}\operatorname{Softplus}(z) \in (-\infty, 0)\), so that reciprocal can explode and overflow. K3 bounds it with a scaled sigmoid:&lt;/p&gt;

\[g_t = g_{\min}\operatorname{Sigmoid}(e^{A_h}z_t) \in (g_{\min}, 0), \qquad g_{\min} = -5\]

  &lt;p&gt;Now every step retains at least \(e^{-5} \approx 0.0067\), cumulative log-decay over a 16-token tile stays in \((-80, 0)\), and the rescaling factor is at most \(e^{80} \approx 5.5\times10^{34}\) — comfortably inside BF16’s \(3.4\times10^{38}\). The payoff is concrete: Kimi Linear had to compute the diagonal tiles with explicit position-pair arithmetic, the main intra-chunk bottleneck. With a bounded range &lt;strong&gt;every tile, diagonal included, becomes a dense tensor-core matmul.&lt;/strong&gt; A numerical-stability bound bought a kernel rewrite.&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;2. Full-rank output gate.&lt;/strong&gt; The low-rank gate becomes an input-dependent full-rank projection, so each token can decide channel by channel how much of the recurrent read to let through:&lt;/p&gt;

\[y_t=\mathbf{W}_o\left[\operatorname{Sigmoid}(\mathbf{W}_g x_t) \odot \operatorname{RMSNorm}(\tilde{o}_t)\right]\]

  &lt;h3 id=&quot;kda-does-not-work-alone-31-with-gated-mla&quot;&gt;KDA does not work alone: 3:1 with Gated MLA&lt;/h3&gt;

  &lt;p&gt;A fixed state is lossy by construction, so K3 interleaves exact attention. Each block is &lt;strong&gt;3 KDA layers + 1 Gated MLA layer&lt;/strong&gt;, and one extra MLA closes the backbone:&lt;/p&gt;

  &lt;p&gt;\(23 \times (3\,\text{KDA} + 1\,\text{MLA}) + 1\,\text{MLA} = 93\) layers, i.e. &lt;strong&gt;69 KDA and 24 MLA&lt;/strong&gt;.&lt;/p&gt;

  &lt;p&gt;Only those 24 layers keep a cache that grows with the sequence — against 61 full-attention layers in K2. The MLA layers are NoPE too, and carry the same full-rank output gate.&lt;/p&gt;

  &lt;p&gt;The division of labour is clean. The 69 KDA layers give recency-weighted, position-aware mixing out of a fixed \(d_k \times d_v\) state at constant cost per token; the 24 MLA layers keep a latent cache that grows with \(T\) and buy back what a finite state cannot hold — exact access to any earlier token.&lt;/p&gt;

  &lt;p&gt;At the Kimi Linear scale the hybrid cut KV-cache usage by up to &lt;strong&gt;75%&lt;/strong&gt; and reached up to &lt;strong&gt;6× decoding throughput at 1M context&lt;/strong&gt;, while beating full MLA on quality under a matched recipe.&lt;/p&gt;

  &lt;h3 id=&quot;the-state-is-small--but-it-is-serial&quot;&gt;The state is small — but it is serial&lt;/h3&gt;

  &lt;p&gt;Most of K3’s KDA engineering follows from one sentence: &lt;em&gt;the state is cheap to move and impossible to skip ahead in.&lt;/em&gt;&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;FlashKDA&lt;/strong&gt; — a CUTLASS chunkwise kernel that overlaps intra-chunk math with cross-chunk state propagation, so the SMs are not idle during the serial hand-off. It also serves prefill, as a backend of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;flash-linear-attention&lt;/code&gt;.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;KDA Context Parallelism (KCP)&lt;/strong&gt; — the interesting one. Vanilla linear attention is a plain sum, so every rank can start from \(\mathbf{S}=\mathbf{0}\) and the results add up. KDA cannot: in \(\mathbf{S}_t = \mathbf{M}_t\mathbf{S}_{t-1} + \beta_tk_tv_t^\top\) with \(\mathbf{M}_t = (\mathbf{I}-\beta_tk_tk_t^\top)\operatorname{Diag}(\alpha_t)\), the incoming state is &lt;em&gt;transformed&lt;/em&gt;, not just added to. So each rank computes two local quantities — its segment’s cumulative transition \(\mathbf{M}\), and the state its own tokens generate from zero — and one all-gather plus a prefix scan composes them exactly. Messages stay fixed-size at any context length, which is what makes 1M-token training affordable.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Prefix caching&lt;/strong&gt; — KDA state checkpoints land at 512-token boundaries in the same paged pool as the MLA KV cache; a prefix is reusable only if &lt;em&gt;both&lt;/em&gt; restore at the same boundary.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Speculative decoding&lt;/strong&gt; — the state updates in place, so a rejected draft cannot be rolled back. K3 caches the drafts’ much smaller projected inputs and replays the accepted prefix on-chip.&lt;/li&gt;
  &lt;/ul&gt;

  &lt;h3 id=&quot;what-to-remember-about-kda&quot;&gt;What to remember about KDA&lt;/h3&gt;

  &lt;ol&gt;
    &lt;li&gt;A fixed-size matrix used as an associative memory, read with \(\mathbf{S}^\top q\).&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Channel-wise decay&lt;/strong&gt; — every key channel picks its own forgetting rate, which also encodes position (hence NoPE, hence painless 1M extension).&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;The delta rule&lt;/strong&gt; — write by erasing what was stored at this key first; it is one step of online gradient descent, and \(\beta_t\) is the learning rate.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Chunkwise form&lt;/strong&gt; — the exact same recurrence expressed as matmuls; K3’s bounded decay pushes the last stubborn tile onto tensor cores.&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Hybrid by design&lt;/strong&gt; — 3:1 with Gated MLA, because a finite state should not be asked to do exact recall.&lt;/li&gt;
  &lt;/ol&gt;

  &lt;h2 id=&quot;part-2--attention-residuals&quot;&gt;Part 2 — Attention Residuals&lt;/h2&gt;

  &lt;p&gt;KDA fixes the sequence axis. AttnRes (&lt;a href=&quot;https://arxiv.org/abs/2603.15031&quot;&gt;arXiv:2603.15031&lt;/a&gt;) runs the same argument down the depth axis.&lt;/p&gt;

  &lt;h3 id=&quot;the-problem&quot;&gt;The problem&lt;/h3&gt;

  &lt;p&gt;A PreNorm residual looks innocent:&lt;/p&gt;

\[h_l = h_{l-1} + f_l(h_{l-1}) = h_0 + \sum_{i=1}^{l} f_i(h_{i-1})\]

  &lt;p&gt;Every earlier layer is added with &lt;strong&gt;weight 1&lt;/strong&gt;. Depth is an RNN: all history is crushed into one vector. Hidden-state magnitude grows with depth, so each new layer is a smaller and smaller fraction of the stream — PreNorm dilution. Early information cannot be fetched back on demand.&lt;/p&gt;

  &lt;p&gt;Sequence modeling had the same bottleneck, and softmax attention replaced the RNN. AttnRes does that &lt;strong&gt;for depth&lt;/strong&gt;: a standard residual is depth-wise &lt;em&gt;linear&lt;/em&gt; attention; AttnRes is depth-wise &lt;em&gt;softmax&lt;/em&gt; attention.&lt;/p&gt;

  &lt;h3 id=&quot;three-residuals&quot;&gt;Three residuals&lt;/h3&gt;

  &lt;p&gt;&lt;strong&gt;Standard.&lt;/strong&gt; Each layer sees only \(h_{l-1}\), with fixed mixing weights and one hidden state travelling between layers — \(N = 1\) below.&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;Full AttnRes.&lt;/strong&gt; Every layer output becomes a key/value, and each layer picks among them:&lt;/p&gt;

\[h_l = \sum_{i=0}^{l-1} \alpha_{i \to l}\, v_i, \qquad
\alpha_{i \to l} = \mathrm{softmax}_i\big(w_l^\top \mathrm{RMSNorm}(k_i)\big)\]

  &lt;ul&gt;
    &lt;li&gt;\(w_l \in \mathbb{R}^d\): one &lt;strong&gt;learned pseudo-query per layer&lt;/strong&gt;, decoupled from that layer’s forward pass, so the mix is content-dependent&lt;/li&gt;
    &lt;li&gt;RMSNorm on the keys stops large-magnitude layers from dominating; queries start at &lt;strong&gt;zero&lt;/strong&gt;, so training begins as a uniform average and does not spike&lt;/li&gt;
    &lt;li&gt;Compute \(O(L^2 d)\), store \(O(Ld)\). The real cost is that pipeline parallelism must ship every layer output across stages&lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;&lt;strong&gt;Block AttnRes&lt;/strong&gt; (what ships). Split \(L\) layers into \(N\) blocks: inside a block an ordinary residual, collapsed to one block vector; across blocks Full AttnRes over the \(N\) summaries plus the embedding, with the unfinished block exposing a &lt;strong&gt;partial sum&lt;/strong&gt;. Traffic drops from \(O(Ld)\) to \(O(Nd)\). \(N = L\) is Full, \(N = 1\) is Standard, and empirically &lt;strong&gt;\(N \approx 8\) recovers most of Full&lt;/strong&gt;. Kimi Linear 48B used 6 layers per block → 9 blocks + embedding = 10 depth sources, for &amp;lt; 4% training overhead and &amp;lt; 2% decode latency.&lt;/p&gt;

  &lt;h3 id=&quot;experiments&quot;&gt;Experiments&lt;/h3&gt;

  &lt;p&gt;&lt;strong&gt;Scaling law.&lt;/strong&gt; Five sizes, each with Baseline / Block (\(N=8\)) / Full, all under the baseline’s hyperparameters — a deliberately conservative test. AttnRes is lower loss along the whole compute curve. Largest size: &lt;strong&gt;Baseline 1.719, Block 1.693, Full 1.692&lt;/strong&gt;, and at 5.6 PFLOP/s-days the baseline needs about &lt;strong&gt;25% more compute&lt;/strong&gt; to match Block. Full is the ceiling; Block is “almost the same, and you can actually train it.”&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;48B / 3B active, 1.4T tokens.&lt;/strong&gt; Validation loss is lower throughout and the gap widens during decay. Baseline output magnitude grows monotonically with depth, while Block &lt;em&gt;resets&lt;/em&gt; at block boundaries; gradients even out too, since the softmax makes depth sources compete instead of dumping everything on the earliest layers.&lt;/p&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt; &lt;/th&gt;
        &lt;th style=&quot;text-align: right&quot;&gt;Baseline&lt;/th&gt;
        &lt;th style=&quot;text-align: right&quot;&gt;AttnRes&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;MMLU&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;73.5&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;74.6&lt;/strong&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;GPQA-Diamond&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;36.9&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;44.4&lt;/strong&gt; (+7.5)&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;Math&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;53.5&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;57.1&lt;/strong&gt; (+3.6)&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;HumanEval&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;59.1&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;62.2&lt;/strong&gt; (+3.1)&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;C-Eval&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;79.6&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;82.5&lt;/strong&gt;&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

  &lt;p&gt;Knowledge moves a little. &lt;strong&gt;Multi-step reasoning and code move a lot&lt;/strong&gt; — consistent with later layers being able to pull earlier representations on demand.&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;16-layer ablation&lt;/strong&gt; (loss, lower is better): Baseline PreNorm 1.766, DenseFormer 1.767, mHC 1.747, &lt;strong&gt;Full AttnRes 1.737&lt;/strong&gt;, input-independent mixing 1.749, sigmoid instead of softmax 1.741, no RMSNorm 1.743, Block \(S=4\) 1.746. Fixed mixing is clearly worse than learned softmax — &lt;strong&gt;content-dependent depth selection is doing real work&lt;/strong&gt;.&lt;/p&gt;

  &lt;h3 id=&quot;takeaway&quot;&gt;Takeaway&lt;/h3&gt;

  &lt;p&gt;K3 grew depth (K2 had 61 layers; K3 has 93). A unit-weight residual starts to look like an RNN on that axis, exactly as a growing KV cache is the wrong answer on the sequence axis. So both axes get the same treatment: &lt;strong&gt;selective, data-dependent retrieval instead of uniform accumulation&lt;/strong&gt; — KDA across tokens, AttnRes across layers. Together with Stable LatentMoE on the width axis, that is where the reported ~2.5× scaling-efficiency gain over K2 comes from.&lt;/p&gt;

  &lt;p&gt;Papers: &lt;a href=&quot;https://arxiv.org/abs/2607.24653&quot;&gt;Kimi K3&lt;/a&gt; · &lt;a href=&quot;https://arxiv.org/abs/2510.26692&quot;&gt;Kimi Linear&lt;/a&gt; · &lt;a href=&quot;https://arxiv.org/abs/2603.15031&quot;&gt;Attention Residuals&lt;/a&gt;. Code: &lt;a href=&quot;https://github.com/fla-org/flash-linear-attention&quot;&gt;flash-linear-attention&lt;/a&gt;.&lt;/p&gt;

&lt;/div&gt;

&lt;div data-lang-panel=&quot;zh&quot; hidden=&quot;&quot;&gt;

  &lt;p&gt;Kimi K3（&lt;a href=&quot;https://arxiv.org/abs/2607.24653&quot;&gt;arXiv:2607.24653&lt;/a&gt;）是 2.8T 参数的 MoE，激活 104B，93 层，1M 上下文。报告最核心的两个架构选择：&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;Kimi Delta Attention（KDA）&lt;/strong&gt; —— 信息沿 &lt;strong&gt;序列&lt;/strong&gt; 方向怎么流&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Attention Residuals（AttnRes）&lt;/strong&gt; —— 信息沿 &lt;strong&gt;深度&lt;/strong&gt; 方向怎么流&lt;/li&gt;
  &lt;/ul&gt;

  &lt;h2 id=&quot;kimi-delta-attention&quot;&gt;第一部分 —— Kimi Delta Attention&lt;/h2&gt;

  &lt;h3 id=&quot;kda-&quot;&gt;KDA 要替掉什么&lt;/h3&gt;

  &lt;p&gt;Softmax attention 把见过的每个 token 都留着：生成第 \(t\) 个 token 要和 \(t\) 个缓存 key 比较，单 token 开销和 KV cache 都随上下文增长。到 1M 上下文，这块 cache 就是部署成本本身。&lt;/p&gt;

  &lt;p&gt;线性注意力走另一条路：去掉 softmax，整段历史被压进一个固定大小的矩阵 \(\mathbf{S} \in \mathbb{R}^{d_k \times d_v}\)，在 token 之间传递：&lt;/p&gt;

\[\mathbf{S}_t = \mathbf{S}_{t-1} + k_t v_t^\top, \qquad o_t = \mathbf{S}_t^\top q_t\]

  &lt;p&gt;常数内存、常数单步计算、上下文无上限。代价是状态永远不会变大，所以 &lt;strong&gt;KDA 的全部问题就是：往里写什么、擦掉什么。&lt;/strong&gt;&lt;/p&gt;

  &lt;h3 id=&quot;section&quot;&gt;状态就是一块联想记忆&lt;/h3&gt;

  &lt;p&gt;把更新式当成记忆来读。写入 \(kv^\top\) 就是存下 \((k, v)\)；读取是一次对所有 key 的点积：&lt;/p&gt;

\[\mathbf{S}^\top q = \sum_i (k_i^\top q)\, v_i\]

  &lt;p&gt;key 正交时，用 \(k_1\) 查询正好取回 \(v_1\) —— 一个 \(d_k \times d_v\) 的矩阵相当于 \(d_k\) 个干净的槽位。现实有两处不干净。&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;key 会撞。&lt;/strong&gt; 设 \(k_3\) 与 \(k_1\) 夹角 45°，先存 \((k_1, v_1)\) 再存 \((k_3, v_2)\)，用 \(k_1\) 读回来是 \(v_1 + 0.707\,v_2\)，串味了。&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;改写会堆积。&lt;/strong&gt; 先写 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x = 1&lt;/code&gt;，后写 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x = 3&lt;/code&gt;，纯累加会把两个都还给你：&lt;/p&gt;

  &lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;依次写 (k1,v1), (k2,v2), (k1,v1&apos;)      用 k1 读
  纯累加        -&amp;gt;  v1 + v1&apos;          （旧值还在）
  delta rule, β = 1 -&amp;gt;  v1&apos;           （旧值被擦掉）
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;

  &lt;p&gt;只加不减的定容记忆，最后就是一份越来越糊的平均。解法有两个，KDA 两个都用。&lt;/p&gt;

  &lt;h3 id=&quot;section-1&quot;&gt;解法一 —— 遗忘，而且每个通道一个速率&lt;/h3&gt;

  &lt;p&gt;每次写入前先把状态乘小：&lt;/p&gt;

\[\mathbf{S}_t = \operatorname{Diag}(\alpha_t)\,\mathbf{S}_{t-1} + k_tv_t^\top\]

  &lt;p&gt;Mamba-2 和 Gated DeltaNet 用 &lt;strong&gt;标量&lt;/strong&gt; \(\alpha_t\)：整块记忆按同一速率衰减。KDA 把它做成 &lt;strong&gt;channel-wise&lt;/strong&gt; 的 \(\alpha_t \in (0,1)^{d_k}\) —— 每个 key 维度一个保留系数，逐 token 决定。状态的第 \(j\) 行按自己的速度衰减，于是同一层里可以既有快通道，也有能记住几千 token 的慢通道：&lt;/p&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;单步 log-decay \(g\)&lt;/th&gt;
        &lt;th style=&quot;text-align: right&quot;&gt;保留率 \(\alpha = e^{g}\)&lt;/th&gt;
        &lt;th style=&quot;text-align: right&quot;&gt;半衰期&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;\(-0.0001\)&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;0.99990&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;6931 tokens&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;\(-0.1\)&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;0.90484&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;7 tokens&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

  &lt;p&gt;K3 的位置信息也从这里来。衰减让「20 个 token 之前」明显弱于「2 个 token 之前」，于是模型 &lt;strong&gt;完全不需要位置编码&lt;/strong&gt;（NoPE）—— 没有 RoPE 频率，上下文从 8K 拉到 1M 时也就没有东西要重调。&lt;/p&gt;

  &lt;h3 id=&quot;delta-rule&quot;&gt;解法二 —— delta rule：按误差改写&lt;/h3&gt;

  &lt;p&gt;不要问该加什么，要问这块记忆现在 &lt;em&gt;错&lt;/em&gt; 在哪。记忆应该满足 \(\mathbf{S}^\top k_t = v_t\)，那就定义损失、走一步梯度：&lt;/p&gt;

\[\mathcal{L}(\mathbf{S}) = \tfrac{1}{2}\lVert \mathbf{S}^\top k_t - v_t \rVert^2,
\quad
\nabla_{\mathbf{S}}\mathcal{L} = k_t(\mathbf{S}^\top k_t - v_t)^\top\]

\[\mathbf{S}_t = \mathbf{S}_{t-1} - \beta_t \nabla_{\mathbf{S}}\mathcal{L}
= \left(\mathbf{I} - \beta_t k_tk_t^\top\right)\mathbf{S}_{t-1} + \beta_t k_tv_t^\top\]

  &lt;p&gt;这就是 delta rule。换句话说，&lt;strong&gt;线性注意力层在推理时对自己的记忆做在线梯度下降&lt;/strong&gt;，\(\beta_t\) 就是学习率。&lt;/p&gt;

  &lt;p&gt;几何意义更值得记住。KDA 对 key 做了 L2 归一化，\(\lVert k_t\rVert = 1\)，所以 \(\mathbf{I} - \beta_tk_tk_t^\top\) 只沿 \(k_t\) 方向缩放，其余方向是恒等。它 &lt;strong&gt;只擦掉这个 key 上存过的东西&lt;/strong&gt;，不动记忆里的其他内容：&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;\(\beta_t = 1\) —— 完全覆盖：\(k_t\) 上原来的值消失，换成 \(v_t\)&lt;/li&gt;
    &lt;li&gt;\(\beta_t = 0\) —— 不写&lt;/li&gt;
    &lt;li&gt;中间值 —— 朝 \(v_t\) 部分修正（上面的例子取 \(\beta = 0.5\) 得到 \(0.25\,v_1 + 0.5\,v_1&apos;\)）&lt;/li&gt;
  &lt;/ul&gt;

  &lt;h3 id=&quot;kda--&quot;&gt;KDA = 两个都要，整层长这样&lt;/h3&gt;

\[\mathbf{S}_t=\left(\mathbf{I}-\beta_t k_t k_t^{\top}\right) \operatorname{Diag}(\alpha_t) \mathbf{S}_{t-1}+\beta_t k_t v_t^{\top},
\qquad \tilde{o}_t=\mathbf{S}_t^{\top} q_t\]

  &lt;p&gt;先衰减，再修正。右边所有量都由当前 token 算出来：&lt;/p&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;项&lt;/th&gt;
        &lt;th&gt;是什么&lt;/th&gt;
        &lt;th&gt;怎么来的&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;\(q_t,k_t\)&lt;/td&gt;
        &lt;td&gt;探针 / 地址&lt;/td&gt;
        &lt;td&gt;ShortConv → Swish → &lt;strong&gt;L2Norm&lt;/strong&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;\(v_t\)&lt;/td&gt;
        &lt;td&gt;内容&lt;/td&gt;
        &lt;td&gt;ShortConv → Swish&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;\(\beta_t \in (0,1)\)&lt;/td&gt;
        &lt;td&gt;写入强度&lt;/td&gt;
        &lt;td&gt;\(\operatorname{Sigmoid}(\mathbf{W}_\beta x_t)\)&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;\(\alpha_t \in (0,1)^{d_k}\)&lt;/td&gt;
        &lt;td&gt;每通道保留率&lt;/td&gt;
        &lt;td&gt;低秩 logits + 每头 bias，再走下面的映射&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

  &lt;p&gt;ShortConv 让每个 token 在变成 key / value 之前先看一个小局部窗口；L2Norm 则保证擦除那一步是干净的投影，而不是任意缩放。&lt;/p&gt;

  &lt;p&gt;把更新式写成等价的 &lt;strong&gt;衰减 → 预测 → 修正 → 读取&lt;/strong&gt; 最容易看懂：&lt;/p&gt;

  &lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;decayed_state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;row_scale&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;alpha_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;prediction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transpose_matrix_vector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;decayed_state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vector_sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;prediction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;matrix_add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;decayed_state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;matrix_scale&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;beta_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;outer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)))&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;output&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transpose_matrix_vector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;query_t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;

  &lt;p&gt;这种写法比显式构造 \(\mathbf I-\beta kk^\top\) 更直观：先按行遗忘，再看衰减后的状态对当前 key 预测出什么，只把预测误差写回去。&lt;/p&gt;

  &lt;h3 id=&quot;cpu-&quot;&gt;在 CPU 上运行完整教程&lt;/h3&gt;

  &lt;p&gt;&lt;a href=&quot;/examples/kda_cpu_tutorial.py&quot;&gt;完整的零依赖 Python 教程&lt;/a&gt; 只用标准库。它不是 Kimi K3，也不是高性能 kernel，而是本文公式的一个小型数值实验室。在仓库根目录运行：&lt;/p&gt;

  &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;python3 examples/kda_cpu_tutorial.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;

  &lt;p&gt;脚本会做五组实验：&lt;/p&gt;

  &lt;ol&gt;
    &lt;li&gt;写入同一个关联两次，验证 delta rule 不会重复累加；&lt;/li&gt;
    &lt;li&gt;用新值覆盖同一个 key，并展示 \(\beta\) 的作用；&lt;/li&gt;
    &lt;li&gt;给状态的不同行施加不同保留率；&lt;/li&gt;
    &lt;li&gt;把 UT chunk 形式和逐 token KDA 对齐；&lt;/li&gt;
    &lt;li&gt;验证 KDA Context Parallelism 的分段结合律，并比较新旧衰减映射。&lt;/li&gt;
  &lt;/ol&gt;

  &lt;p&gt;我在 CPU 上实际运行过。chunk 的两种算法只差浮点舍入误差：&lt;/p&gt;

  &lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Maximum output difference:      8.882e-16
Maximum final-state difference: 6.661e-16
Maximum segment difference:     4.441e-16
All numerical equivalence checks passed.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;

  &lt;p&gt;我还跑了 100 组随机的递推-vs-chunk 测试，以及 100 组随机的分段组合测试，全部通过。它们验证的是教学实现的代数，不代表 FlashKDA CUDA kernel 的速度或低精度数值行为。&lt;/p&gt;

  &lt;h3 id=&quot;big-o&quot;&gt;Big-O：哪部分变成线性，哪部分没有&lt;/h3&gt;

  &lt;p&gt;记 \(T\) 为序列长度，\(H\) 为 head 数，\(d_k,d_v\) 为单 head 的 key/value 维度。忽略两种方案共有的 Q/K/V 投影，因果 softmax attention 和递推 KDA 的规模如下：&lt;/p&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt;机制&lt;/th&gt;
        &lt;th style=&quot;text-align: right&quot;&gt;序列计算&lt;/th&gt;
        &lt;th style=&quot;text-align: right&quot;&gt;decode cache&lt;/th&gt;
        &lt;th style=&quot;text-align: right&quot;&gt;下一 token&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;softmax&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;\(O(HT^2d_k)\)&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;\(O(HT(d_k+d_v))\)&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;\(O(HT(d_k+d_v))\)&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;KDA&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;\(O(HTd_kd_v)\)&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;\(O(Hd_kd_v)\)&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;\(O(Hd_kd_v)\)&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

  &lt;p&gt;当 \(H,d_k,d_v\) 是固定的架构常数时，KDA 对序列长度是 \(O(T)\)，持久化 cache 对上下文长度是 \(O(1)\)。“常数”不等于免费：每生成一个 token，仍要读写每个 head 的整个 \(d_k\times d_v\) 状态。&lt;/p&gt;

  &lt;p&gt;Kimi K3 取 \(H=96\)、\(d_k=d_v=128\)，所以每个 KDA 层、每条序列的状态有&lt;/p&gt;

\[96\times128\times128=1{,}572{,}864\]

  &lt;p&gt;个元素，BF16 下约 3 MiB。69 个 KDA 层合计约 207 MiB，还没算张量并行切分方式、卷积历史、内存分配和 prefix checkpoint。上下文从 1K 拉到 1M，这块状态本身不会继续长。&lt;/p&gt;

  &lt;p&gt;Kimi Linear 给出的生产 chunk kernel 单 head attention 计算量是&lt;/p&gt;

\[6Td_h^2+3TCd_h+TC^2,\]

  &lt;p&gt;其中 \(C\) 是 chunk size，\(d_h=d_k=d_v\)。固定 \(C\) 和 \(d_h\) 后仍是 \(O(T)\)。教程里的显式 Python 循环是为了可读性，不是为了速度；给它计时，测到的主要是 Python 开销，不是 FlashKDA。&lt;/p&gt;

  &lt;p&gt;整个 Kimi K3 也不是常数 cache：24 个 MLA 层仍然保存随序列增长的 latent KV cache，并执行全局注意力。69 个 KDA 层只是把大约四分之三 attention stack 的增长拿掉。&lt;/p&gt;

  &lt;h3 id=&quot;kda&quot;&gt;为什么要用 KDA？&lt;/h3&gt;

  &lt;p&gt;当生成足够长、反复读取不断增大的 KV cache 成为主要成本时，KDA 很有价值：&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;固定大小的递推记忆。&lt;/strong&gt; 大部分层用一个状态矩阵取代逐 token KV。&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;按误差修正。&lt;/strong&gt; delta rule 覆盖旧关联，而不是把重复值和过期值一直累加。&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;多种记忆时标。&lt;/strong&gt; channel-wise \(\alpha\) 让同一 head 同时拥有快忘和慢记的特征。&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;隐式顺序与近因性。&lt;/strong&gt; 有序、随数据变化的转移在 token 顺序改变时也会改变，所以全局层可以使用 NoPE。&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;精确而适合 GPU 的改写。&lt;/strong&gt; 训练时用 chunkwise 矩阵乘，但数学递推没有改变。&lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;代价是容量有限。固定状态会把无关事实混在一起，也可能丢失精确 token 细节，所以 Kimi K3 没有把所有层都换成 KDA，而是保留周期性的全局 MLA。&lt;/p&gt;

  &lt;h3 id=&quot;chunkwise-parallel-form&quot;&gt;Chunkwise parallel form：一步一步推&lt;/h3&gt;

  &lt;p&gt;逐 token 递推很适合 decoding，但不适合训练：第 \(r\) 个 token 必须等第 \(r-1\) 个 token 产出状态，直接实现就是连续发射很多小矩阵更新。Chunkwise KDA &lt;strong&gt;没有消灭递推&lt;/strong&gt;；它把一个固定大小 chunk 里的大部分工作整理成稠密矩阵乘，只在 chunk 之间顺序传递一个状态。&lt;/p&gt;

  &lt;h4 id=&quot;chunk-&quot;&gt;1. 先把一个 chunk 排成矩阵&lt;/h4&gt;

  &lt;p&gt;一个 chunk 有 \(C\) 个 token，按行堆叠：&lt;/p&gt;

\[\mathbf Q,\mathbf K\in\mathbb R^{C\times d_k},\qquad
\mathbf V,\mathbf O\in\mathbb R^{C\times d_v},\qquad
\mathbf S_{\mathrm{in}}\in\mathbb R^{d_k\times d_v}.\]

  &lt;p&gt;对位置 \(1\le i\le j\le C\)，定义逐通道累积保留率&lt;/p&gt;

\[\gamma^{i\to j}=\prod_{r=i}^{j}\alpha^r,
\qquad
\gamma^j=\gamma^{1\to j}.\]

  &lt;p&gt;这些乘法都在 \(d_k\) 个 key 通道上逐元素进行。令 \(\mathbf\Gamma\in\mathbb R^{C\times d_k}\) 逐行堆叠 \(\gamma^1,\ldots,\gamma^C\)。&lt;/p&gt;

  &lt;h4 id=&quot;section-2&quot;&gt;2. 看清递推里藏着什么&lt;/h4&gt;

  &lt;p&gt;把单 token 转移写成&lt;/p&gt;

\[\mathbf T_r=(\mathbf I-\beta_r k_rk_r^\top)\operatorname{Diag}(\alpha_r),
\qquad
\mathbf B_r=\beta_rk_rv_r^\top.\]

  &lt;p&gt;走完 chunk 的前 \(r\) 个 token 后，&lt;/p&gt;

\[\mathbf S^r=
\underbrace{\mathbf T_r\mathbf T_{r-1}\cdots\mathbf T_1}_{\mathbf P^r}
\mathbf S_{\mathrm{in}}
+
\sum_{i=1}^{r}
\mathbf T_r\cdots\mathbf T_{i+1}\mathbf B_i.\]

  &lt;p&gt;最新的转移在最左边。困难也写在式子里：每次写入都会被后续所有衰减和 delta 擦除再次变换。如果给每个输出单独算这些乘积，会反复做大量相同工作。&lt;/p&gt;

  &lt;h4 id=&quot;querykey-&quot;&gt;3. 把相对衰减变成 query/key 缩放&lt;/h4&gt;

  &lt;p&gt;位置 \(i\) 的 query 读取位置 \(j\le i\) 写下的 key，两者之间的保留率是&lt;/p&gt;

\[\frac{\gamma^i}{\gamma^j}
=
\prod_{r=j+1}^{i}\alpha^r.\]

  &lt;p&gt;所以带衰减的相似度可以写成&lt;/p&gt;

\[q_i^\top\operatorname{Diag}\!\left(\frac{\gamma^i}{\gamma^j}\right)k_j
=
(\gamma^i\odot q_i)^\top(k_j/\gamma^j).\]

  &lt;p&gt;所有位置对的分数一次矩阵乘就能得到：&lt;/p&gt;

\[\mathbf A=
\operatorname{Tril}\!\left[
(\mathbf Q\odot\mathbf\Gamma)
(\mathbf K/\mathbf\Gamma)^\top
\right]
\in\mathbb R^{C\times C}.\]

  &lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Tril&lt;/code&gt; 去掉未来位置，但 &lt;strong&gt;保留对角线&lt;/strong&gt;。当 \(i=j\) 时比例是 1；输出读取的是当前 token 已经写入后的 \(\mathbf S_i\)，所以 token \(i\) 必须能读自己的伪 value。&lt;/p&gt;

  &lt;h4 id=&quot;ut-transform--delta-rule-&quot;&gt;4. 用 UT transform 折叠 delta-rule 依赖&lt;/h4&gt;

  &lt;p&gt;只有衰减缩放还不够，因为 delta 写入的是扣除先前预测后的残差。定义&lt;/p&gt;

\[\mathbf L=
\operatorname{StrictTril}\!\left[
\operatorname{Diag}(\boldsymbol\beta)
(\mathbf\Gamma\odot\mathbf K)
(\mathbf K/\mathbf\Gamma)^\top
\right]
\in\mathbb R^{C\times C}.\]

  &lt;p&gt;严格下三角部分描述「更早的 key 如何改变后续 key 看到的预测」。UT transform 解这个因果系统：&lt;/p&gt;

\[\mathbf M=(\mathbf I+\mathbf L)^{-1}\operatorname{Diag}(\boldsymbol\beta),\]

\[\mathbf W=\mathbf M(\mathbf\Gamma\odot\mathbf K)
\in\mathbb R^{C\times d_k},
\qquad
\mathbf U=\mathbf M\mathbf V
\in\mathbb R^{C\times d_v}.\]

  &lt;p&gt;因为 \(\mathbf I+\mathbf L\) 是对角线为 1 的下三角矩阵，这一步实际用前向代入，不是通用矩阵求逆。紧凑的 &lt;strong&gt;伪 value&lt;/strong&gt; 是&lt;/p&gt;

\[\widetilde{\mathbf V}
=
\mathbf U-\mathbf W\mathbf S_{\mathrm{in}}
\in\mathbb R^{C\times d_v}.\]

  &lt;p&gt;两个项都有直接含义：\(\mathbf U\) 包含 chunk 内彼此修正后的当前 value，\(\mathbf W\mathbf S_{\mathrm{in}}\) 则扣掉入口状态已经能够预测的部分。于是 \(\widetilde{\mathbf V}\) 的每一行，都是算入所有更早 chunk 内修正后，这个 token 真正贡献的残差。&lt;/p&gt;

  &lt;h4 id=&quot;chunk--1&quot;&gt;5. 一次算出 chunk 里的所有输出&lt;/h4&gt;

  &lt;p&gt;得到 \(\widetilde{\mathbf V}\) 后，&lt;/p&gt;

\[\boxed{
\mathbf O=
\underbrace{(\mathbf\Gamma\odot\mathbf Q)\mathbf S_{\mathrm{in}}}_{\text{来自更早 chunk 的记忆}}
+
\underbrace{\mathbf A\widetilde{\mathbf V}}_{\text{当前 chunk 的写入}}
}\]

  &lt;p&gt;形状是 \(C\times d_v\)。第一项对 chunk 入口状态做衰减后查询；第二项是当前 chunk 中修正后写入的因果加权和。这就是 Kimi K3 的 Eq. 4。&lt;/p&gt;

  &lt;p&gt;传给下一个 chunk 的状态也从同一组伪 value 得到：&lt;/p&gt;

\[\boxed{
\mathbf S_{\mathrm{out}}
=
\operatorname{Diag}(\gamma^C)\mathbf S_{\mathrm{in}}
+
(\mathbf\Delta\odot\mathbf K)^\top\widetilde{\mathbf V}
}\]

  &lt;p&gt;其中 \(\mathbf\Delta\) 的第 \(i\) 行是该次写入 &lt;em&gt;之后&lt;/em&gt; 经历的保留率，&lt;/p&gt;

\[\Delta_i=\prod_{r=i+1}^{C}\alpha^r,\]

  &lt;p&gt;最后一个 token 后面没有衰减，空乘积按 1 处理。入口状态经历全部 \(C\) 次衰减；位置 \(i\) 的写入只经历它后面的衰减。&lt;/p&gt;

  &lt;h4 id=&quot;section-3&quot;&gt;6. 到底哪里并行？&lt;/h4&gt;

  &lt;p&gt;chunk 之间仍然递推：第 \(t\) 个 chunk 的 \(\mathbf S_{\mathrm{out}}\) 就是第 \(t+1\) 个 chunk 的 \(\mathbf S_{\mathrm{in}}\)。chunk 内部则把昂贵的 query-key、输出和状态更新整理成矩阵乘。UT transform 里仍有一次因果三角求解，所以「chunk 内并行」的准确含义是 &lt;strong&gt;绝大部分算术变成可分块的稠密计算&lt;/strong&gt;，而不是每一步都完全独立。&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;/examples/kda_cpu_tutorial.py&quot;&gt;CPU 教程里的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;official_naive_chunk_kda&lt;/code&gt;&lt;/a&gt; 显式走完这条链：累积 log-decay、下三角 UT 求解、\(\mathbf W/\mathbf U\)、伪 value、因果分数、输出和最终状态。它和逐 token 递推相比，最大输出误差与状态误差分别是 \(8.882\times10^{-16}\) 和 \(6.661\times10^{-16}\)。因此 chunkwise 在实数算术下是精确代数改写；生产 kernel 的微小差异来自累加顺序和 BF16 状态存储。&lt;/p&gt;

  &lt;h3 id=&quot;kimi-lineark3-&quot;&gt;相对 Kimi Linear，K3 改了什么&lt;/h3&gt;

  &lt;p&gt;KDA 来自 Kimi Linear（&lt;a href=&quot;https://arxiv.org/abs/2510.26692&quot;&gt;arXiv:2510.26692&lt;/a&gt;）。K3 改了两处，纸面上都很小，但都是冲着硬件去的。&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;1. 有下界的衰减。&lt;/strong&gt; 再看 \(\mathbf{K}/\mathbf{\Gamma}\)：一串小于 1 的数连乘之后取 &lt;em&gt;倒数&lt;/em&gt;。Kimi Linear 用负 softplus，\(g = -e^{A}\operatorname{Softplus}(z) \in (-\infty, 0)\)，倒数可以炸掉溢出。K3 改成带缩放的 sigmoid：&lt;/p&gt;

\[g_t = g_{\min}\operatorname{Sigmoid}(e^{A_h}z_t) \in (g_{\min}, 0), \qquad g_{\min} = -5\]

  &lt;p&gt;于是每步至少保留 \(e^{-5} \approx 0.0067\)，16 token 的小 tile 上累积 log-decay 落在 \((-80, 0)\)，缩放因子最大 \(e^{80} \approx 5.5\times10^{34}\)，稳稳在 BF16 的 \(3.4\times10^{38}\) 之内。收益很实在：Kimi Linear 的对角 tile 必须走显式的 position-pair 计算，这正是 chunk 内的主要瓶颈；范围有界之后，&lt;strong&gt;包括对角在内的每个 tile 都变成稠密的 Tensor Core 矩阵乘&lt;/strong&gt;。一个数值稳定性的下界，换来一次 kernel 重写。&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;2. 全秩输出门。&lt;/strong&gt; 低秩门换成随输入变化的全秩投影，每个 token 可以逐通道决定放多少递推读出的内容出去：&lt;/p&gt;

\[y_t=\mathbf{W}_o\left[\operatorname{Sigmoid}(\mathbf{W}_g x_t) \odot \operatorname{RMSNorm}(\tilde{o}_t)\right]\]

  &lt;h3 id=&quot;kda--gated-mla-31-&quot;&gt;KDA 不单独工作：和 Gated MLA 3:1 混合&lt;/h3&gt;

  &lt;p&gt;定容状态天生有损，所以 K3 周期性插入精确注意力。每个 block 是 &lt;strong&gt;3 层 KDA + 1 层 Gated MLA&lt;/strong&gt;，backbone 末尾再补一层 MLA：&lt;/p&gt;

  &lt;p&gt;\(23 \times (3\,\text{KDA} + 1\,\text{MLA}) + 1\,\text{MLA} = 93\) 层，也就是 &lt;strong&gt;69 层 KDA + 24 层 MLA&lt;/strong&gt;。&lt;/p&gt;

  &lt;p&gt;只有这 24 层带着随序列增长的 cache —— 对比 K2 的 61 层全注意力。MLA 层同样是 NoPE，并带上同样的全秩输出门。&lt;/p&gt;

  &lt;p&gt;分工很清楚：69 层 KDA 用固定的 \(d_k \times d_v\) 状态提供偏近期、带位置感的混合，单 token 开销恒定；24 层 MLA 留一份随 \(T\) 增长的 latent cache，买回有限状态装不下的东西 —— 对任意早先 token 的精确访问。&lt;/p&gt;

  &lt;p&gt;在 Kimi Linear 的规模上，这种混合把 KV cache 用量最多降了 &lt;strong&gt;75%&lt;/strong&gt;，1M 上下文下 decoding 吞吐最高 &lt;strong&gt;6 倍&lt;/strong&gt;，同时在相同配方下质量还优于全 MLA。&lt;/p&gt;

  &lt;h3 id=&quot;section-4&quot;&gt;状态很小 —— 但它是串行的&lt;/h3&gt;

  &lt;p&gt;K3 在 KDA 上的工程，基本都能由一句话推出来：&lt;em&gt;状态搬运便宜，但没法跳着算。&lt;/em&gt;&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;FlashKDA&lt;/strong&gt; —— 基于 CUTLASS 的 chunkwise kernel，把 chunk 内计算和跨 chunk 的状态传递重叠起来，串行交接时 SM 不空转；它同时服务 prefill，是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;flash-linear-attention&lt;/code&gt; 的一个后端。&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;KDA Context Parallelism（KCP）&lt;/strong&gt; —— 最有意思的一块。普通线性注意力是纯加法，各 rank 从 \(\mathbf{S}=\mathbf{0}\) 出发再求和就行。KDA 不行：\(\mathbf{S}_t = \mathbf{M}_t\mathbf{S}_{t-1} + \beta_tk_tv_t^\top\)，其中 \(\mathbf{M}_t = (\mathbf{I}-\beta_tk_tk_t^\top)\operatorname{Diag}(\alpha_t)\)，入口状态是被 &lt;em&gt;变换&lt;/em&gt;，不只是被加。于是每个 rank 本地算两样东西 —— 本段的累积转移 \(\mathbf{M}\)，以及本地 token 从零生成的状态 —— 一次 all-gather 加一次前缀扫描就能精确拼起来。通信量与上下文长度无关，这才让 1M token 的训练付得起。&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Prefix cache&lt;/strong&gt; —— KDA 状态 checkpoint 按 512 token 边界写入，和 MLA KV cache 共用同一套分页池；两边都能在同一边界恢复，这段前缀才可复用。&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;投机解码&lt;/strong&gt; —— 状态原地更新，草稿被拒绝没法回滚。K3 改成缓存草稿那份小得多的投影输入，在片上重放已接受的前缀。&lt;/li&gt;
  &lt;/ul&gt;

  &lt;h3 id=&quot;kda--1&quot;&gt;KDA 记住这五点&lt;/h3&gt;

  &lt;ol&gt;
    &lt;li&gt;一个定容矩阵当联想记忆用，读取就是 \(\mathbf{S}^\top q\)。&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;channel-wise 衰减&lt;/strong&gt; —— 每个 key 通道自己挑遗忘速率，顺带编码位置（所以 NoPE，所以 1M 扩展不用改任何位置编码）。&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;delta rule&lt;/strong&gt; —— 写之前先擦掉这个 key 上的旧值；本质是一步在线梯度下降，\(\beta_t\) 就是学习率。&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;chunkwise 形式&lt;/strong&gt; —— 同一个递推的精确矩阵乘写法；K3 的有界衰减把最后一块难啃的对角 tile 也推上了 Tensor Core。&lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;混合是设计的一部分&lt;/strong&gt; —— 和 Gated MLA 按 3:1 交错，因为不该指望有限状态做精确召回。&lt;/li&gt;
  &lt;/ol&gt;

  &lt;h2 id=&quot;attention-residuals&quot;&gt;第二部分 —— Attention Residuals&lt;/h2&gt;

  &lt;p&gt;KDA 解决序列这条轴，AttnRes（&lt;a href=&quot;https://arxiv.org/abs/2603.15031&quot;&gt;arXiv:2603.15031&lt;/a&gt;）把同一套论证搬到深度这条轴。&lt;/p&gt;

  &lt;h3 id=&quot;section-5&quot;&gt;问题&lt;/h3&gt;

  &lt;p&gt;PreNorm residual 看起来无害：&lt;/p&gt;

\[h_l = h_{l-1} + f_l(h_{l-1}) = h_0 + \sum_{i=1}^{l} f_i(h_{i-1})\]

  &lt;p&gt;前面每一层都以 &lt;strong&gt;权重 1&lt;/strong&gt; 加进去。深度方向像 RNN：历史被压成一个向量。hidden 幅度随深度涨，新层占的份额越来越小 —— 这就是 PreNorm dilution。早期信息没法按内容再取回来。&lt;/p&gt;

  &lt;p&gt;序列建模当年也是这个瓶颈，softmax attention 换掉了 RNN。AttnRes 对 &lt;strong&gt;深度&lt;/strong&gt; 做同一件事：标准 residual 是深度方向的 &lt;em&gt;线性&lt;/em&gt; 注意力，AttnRes 是深度方向的 &lt;em&gt;softmax&lt;/em&gt; 注意力。&lt;/p&gt;

  &lt;h3 id=&quot;residual&quot;&gt;三种 residual&lt;/h3&gt;

  &lt;p&gt;&lt;strong&gt;Standard。&lt;/strong&gt; 每层只看见 \(h_{l-1}\)，混合权重固定，层间只传一个 hidden —— 也就是下面的 \(N = 1\)。&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;Full AttnRes。&lt;/strong&gt; 每一层的 output 都变成 key/value，每层自己去挑：&lt;/p&gt;

\[h_l = \sum_{i=0}^{l-1} \alpha_{i \to l}\, v_i, \qquad
\alpha_{i \to l} = \mathrm{softmax}_i\big(w_l^\top \mathrm{RMSNorm}(k_i)\big)\]

  &lt;ul&gt;
    &lt;li&gt;\(w_l \in \mathbb{R}^d\)：每层一个 &lt;strong&gt;学出来的 pseudo-query&lt;/strong&gt;，和本层 forward 解耦，所以混合随内容变&lt;/li&gt;
    &lt;li&gt;key 上的 RMSNorm 防止幅度大的层独占权重；query &lt;strong&gt;零初始化&lt;/strong&gt;，开训接近均匀平均，不会一上来就炸&lt;/li&gt;
    &lt;li&gt;计算 \(O(L^2 d)\)，存储 \(O(Ld)\)。真正的痛点是 pipeline 并行下每层 output 都要跨 stage 传&lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;&lt;strong&gt;Block AttnRes&lt;/strong&gt;（真正上生产的那个）。把 \(L\) 层切成 \(N\) 个 block：block 内还是普通 residual，压成一个 block 向量；block 间只对 \(N\) 个摘要 + embedding 做 Full AttnRes，当前没走完的 block 再多给一个 &lt;strong&gt;partial sum&lt;/strong&gt;。通信从 \(O(Ld)\) 降到 \(O(Nd)\)。\(N = L\) 是 Full，\(N = 1\) 是 Standard，经验上 &lt;strong&gt;\(N \approx 8\) 就能拿回 Full 的大部分收益&lt;/strong&gt;。Kimi Linear 48B 每 block 6 层 → 9 个 block + embedding = 10 个 depth source，训练开销 &amp;lt; 4%，decode 延迟 &amp;lt; 2%。&lt;/p&gt;

  &lt;h3 id=&quot;section-6&quot;&gt;实验&lt;/h3&gt;

  &lt;p&gt;&lt;strong&gt;Scaling law。&lt;/strong&gt; 五个规模，每档 Baseline / Block（\(N=8\)）/ Full，超参一律按 baseline 选 —— 对 AttnRes 更苛刻。AttnRes 整条 compute 曲线 loss 都更低。最大档：&lt;strong&gt;Baseline 1.719，Block 1.693，Full 1.692&lt;/strong&gt;；在 5.6 PFLOP/s-days 上，baseline 大约要多 &lt;strong&gt;25% 算力&lt;/strong&gt; 才追平 Block。Full 是上限，Block 是「几乎一样准，而且真能训」。&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;48B / 3B 激活，1.4T token。&lt;/strong&gt; val loss 全程更低，decay 阶段差距拉大。baseline 的 output magnitude 随深度单调涨，Block 在 block 边界被「重置」；梯度也更均匀，softmax 让各 depth source 相互竞争，而不是把大梯度全砸在最浅几层。&lt;/p&gt;

  &lt;table&gt;
    &lt;thead&gt;
      &lt;tr&gt;
        &lt;th&gt; &lt;/th&gt;
        &lt;th style=&quot;text-align: right&quot;&gt;Baseline&lt;/th&gt;
        &lt;th style=&quot;text-align: right&quot;&gt;AttnRes&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td&gt;MMLU&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;73.5&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;74.6&lt;/strong&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;GPQA-Diamond&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;36.9&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;44.4&lt;/strong&gt;（+7.5）&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;Math&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;53.5&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;57.1&lt;/strong&gt;（+3.6）&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;HumanEval&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;59.1&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;62.2&lt;/strong&gt;（+3.1）&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;C-Eval&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;79.6&lt;/td&gt;
        &lt;td style=&quot;text-align: right&quot;&gt;&lt;strong&gt;82.5&lt;/strong&gt;&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

  &lt;p&gt;知识类小涨，&lt;strong&gt;多步推理和代码涨最多&lt;/strong&gt; —— 和「后面的层能按需把前面的表示捞回来」一致。&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;16 层消融&lt;/strong&gt;（loss，越小越好）：Baseline PreNorm 1.766，DenseFormer 1.767，mHC 1.747，&lt;strong&gt;Full AttnRes 1.737&lt;/strong&gt;，固定混合 1.749，用 sigmoid 代替 softmax 1.741，去掉 RMSNorm 1.743，Block \(S=4\) 1.746。固定混合明显差于学出来的 softmax —— &lt;strong&gt;内容相关的深度选择是真的在干活&lt;/strong&gt;。&lt;/p&gt;

  &lt;h3 id=&quot;section-7&quot;&gt;小结&lt;/h3&gt;

  &lt;p&gt;K3 把深度加上去了（K2 是 61 层，K3 是 93 层）。权重全是 1 的 residual 在这条轴上开始像 RNN，就像不断增长的 KV cache 在序列这条轴上是错的答案。于是两条轴用同一套办法：&lt;strong&gt;用有选择、随内容变的检索，取代均匀累加&lt;/strong&gt; —— 序列上是 KDA，深度上是 AttnRes。再加上宽度方向的 Stable LatentMoE，这就是报告里相对 K2 约 2.5 倍 scaling 效率提升的来源。&lt;/p&gt;

  &lt;p&gt;论文：&lt;a href=&quot;https://arxiv.org/abs/2607.24653&quot;&gt;Kimi K3&lt;/a&gt; · &lt;a href=&quot;https://arxiv.org/abs/2510.26692&quot;&gt;Kimi Linear&lt;/a&gt; · &lt;a href=&quot;https://arxiv.org/abs/2603.15031&quot;&gt;Attention Residuals&lt;/a&gt;。代码：&lt;a href=&quot;https://github.com/fla-org/flash-linear-attention&quot;&gt;flash-linear-attention&lt;/a&gt;。&lt;/p&gt;

&lt;/div&gt;
</description>
        <pubDate>Sun, 09 Aug 2026 00:00:00 +0000</pubDate>
        <link>https://jazzikp.github.io/2026/08/09/kimi-k3-attention-residuals/</link>
        <guid isPermaLink="true">https://jazzikp.github.io/2026/08/09/kimi-k3-attention-residuals/</guid>
        
        <category>Kimi</category>
        
        <category>KDA</category>
        
        <category>Attention</category>
        
        <category>LLM</category>
        
        
      </item>
    
      <item>
        <title>WIP-Rec System</title>
        <description>
&lt;div data-lang-panel=&quot;en&quot;&gt;

  &lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML&quot; async=&quot;&quot;&gt;&lt;/script&gt;

  &lt;h2 id=&quot;guild-to-build-a-great-rec-system&quot;&gt;Guild to Build a Great Rec System&lt;/h2&gt;
  &lt;p&gt;I have worked at TikTok for two year and half in video recommendation system. TikTok has one of the best recommendation system in the world. I have learned a lot from my colleagues and I want to share my knowledge with you. I want to break down this series in 11 posts:&lt;/p&gt;
  &lt;ol&gt;
    &lt;li&gt;Major Component in Rec System&lt;/li&gt;
    &lt;li&gt;Modeling Target&lt;/li&gt;
    &lt;li&gt;Training Data&lt;/li&gt;
    &lt;li&gt;Loss Optimization&lt;/li&gt;
    &lt;li&gt;Feature Engineering&lt;/li&gt;
    &lt;li&gt;Hyperparameter Tuning&lt;/li&gt;
    &lt;li&gt;Model Structure&lt;/li&gt;
    &lt;li&gt;Reinforcement Learning in Rec System&lt;/li&gt;
    &lt;li&gt;Federated Learning in Rec System&lt;/li&gt;
    &lt;li&gt;AutoML and Application in Rec System&lt;/li&gt;
    &lt;li&gt;Other&lt;/li&gt;
  &lt;/ol&gt;

  &lt;p&gt;Today we are going to discuss our first topic: &lt;strong&gt;Major Component in Rec System&lt;/strong&gt;.&lt;/p&gt;

  &lt;h2 id=&quot;major-component-in-rec-system&quot;&gt;Major Component in Rec System&lt;/h2&gt;
  &lt;p&gt;In today’s industrial rec system, there are four major components: &lt;strong&gt;Recall&lt;/strong&gt;,
&lt;strong&gt;Rough Ranking&lt;/strong&gt;, &lt;strong&gt;Fine Ranking&lt;/strong&gt;, and &lt;strong&gt;Mix-Ranker&lt;/strong&gt;. This design are used by most
of the companies in recommendation and ranking system. Of course, some adjustment 
are made to fit the specific business needs. For example, TikTok user publish 
millions of videos and our FYP has to select 8 videos out of the pool in real time,
thus computation cost and latency is a major concern. However, for some retail
E-commerce such as DoorDash or Samsclub.com, the number of viable products are much smaller
thus, thus the candidate pool is much smaller, so a rough sort might not be necessary.
In this case, a recall + Fine will do the trick just fine.&lt;/p&gt;

  &lt;p&gt;In this post, I will give introduction to each component, and discuss some
design concerns.&lt;/p&gt;

  &lt;h3 id=&quot;recall&quot;&gt;Recall&lt;/h3&gt;
  &lt;p&gt;The goal for recall is to select a small subset of candidates from a large pool.
This is usually achieved through &lt;strong&gt;Multi-Way Recall&lt;/strong&gt;.
&lt;strong&gt;Multi-Way Recall&lt;/strong&gt; builds multiple recall models or rules to select candidates. 
Some commonly used model targets or rules are:&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;Recall recent videos&lt;/li&gt;
    &lt;li&gt;Recall popular videos&lt;/li&gt;
    &lt;li&gt;Recall Regional Content&lt;/li&gt;
    &lt;li&gt;Embedding Based Recall
      &lt;ul&gt;
        &lt;li&gt;I2I: Item 2 Item&lt;/li&gt;
        &lt;li&gt;U2I: User 2 Item&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;Approximate Nearest Neighbor Recall&lt;/li&gt;
  &lt;/ul&gt;

  &lt;h2 id=&quot;reference&quot;&gt;Reference&lt;/h2&gt;
  &lt;p&gt;https://zhuanlan.zhihu.com/p/388603950&lt;/p&gt;

&lt;/div&gt;

&lt;div data-lang-panel=&quot;zh&quot; hidden=&quot;&quot;&gt;

  &lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML&quot; async=&quot;&quot;&gt;&lt;/script&gt;

  &lt;h2 id=&quot;section&quot;&gt;构建优秀推荐系统指南&lt;/h2&gt;
  &lt;p&gt;我在 TikTok 做了两年半视频推荐系统。TikTok 拥有世界上最好的推荐系统之一。我从同事那里学到了很多，想和你分享这些知识。这个系列拆成 11 篇：&lt;/p&gt;
  &lt;ol&gt;
    &lt;li&gt;推荐系统的主要组件&lt;/li&gt;
    &lt;li&gt;建模目标&lt;/li&gt;
    &lt;li&gt;训练数据&lt;/li&gt;
    &lt;li&gt;损失优化&lt;/li&gt;
    &lt;li&gt;特征工程&lt;/li&gt;
    &lt;li&gt;超参数调优&lt;/li&gt;
    &lt;li&gt;模型结构&lt;/li&gt;
    &lt;li&gt;推荐系统中的强化学习&lt;/li&gt;
    &lt;li&gt;推荐系统中的联邦学习&lt;/li&gt;
    &lt;li&gt;AutoML 及其在推荐系统中的应用&lt;/li&gt;
    &lt;li&gt;其他&lt;/li&gt;
  &lt;/ol&gt;

  &lt;p&gt;今天讨论第一个主题：&lt;strong&gt;推荐系统的主要组件&lt;/strong&gt;。&lt;/p&gt;

  &lt;h2 id=&quot;section-1&quot;&gt;推荐系统的主要组件&lt;/h2&gt;
  &lt;p&gt;当今工业推荐系统有四个主要组件：&lt;strong&gt;召回&lt;/strong&gt;、
&lt;strong&gt;粗排&lt;/strong&gt;、&lt;strong&gt;精排&lt;/strong&gt; 和 &lt;strong&gt;混排&lt;/strong&gt;。大多数做推荐和排序的公司都用这套设计。当然会按具体业务做调整。例如 TikTok 用户每天发数百万视频，FYP 必须实时从池里选出 8 条，计算成本和延迟是主要约束。但对 DoorDash 或 Samsclub.com 这类零售电商，可行商品少得多，候选池也小，粗排往往没必要。这时召回 + 精排就够了。&lt;/p&gt;

  &lt;p&gt;这篇文章介绍每个组件，并讨论一些设计上的取舍。&lt;/p&gt;

  &lt;h3 id=&quot;section-2&quot;&gt;召回&lt;/h3&gt;
  &lt;p&gt;召回的目标是从大池里选出一小撮候选。
通常靠 &lt;strong&gt;多路召回&lt;/strong&gt;。
&lt;strong&gt;多路召回&lt;/strong&gt; 用多套召回模型或规则来选候选。
常见目标或规则有：&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;召回最近视频&lt;/li&gt;
    &lt;li&gt;召回热门视频&lt;/li&gt;
    &lt;li&gt;召回地域内容&lt;/li&gt;
    &lt;li&gt;基于 embedding 的召回
      &lt;ul&gt;
        &lt;li&gt;I2I: Item 2 Item&lt;/li&gt;
        &lt;li&gt;U2I: User 2 Item&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;Approximate Nearest Neighbor 召回&lt;/li&gt;
  &lt;/ul&gt;

  &lt;h2 id=&quot;section-3&quot;&gt;参考&lt;/h2&gt;
  &lt;p&gt;https://zhuanlan.zhihu.com/p/388603950&lt;/p&gt;

&lt;/div&gt;
</description>
        <pubDate>Sat, 14 Oct 2023 00:00:00 +0000</pubDate>
        <link>https://jazzikp.github.io/2023/10/14/%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E6%80%BB%E7%BB%93/</link>
        <guid isPermaLink="true">https://jazzikp.github.io/2023/10/14/%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E6%80%BB%E7%BB%93/</guid>
        
        <category>RecSys</category>
        
        <category>推荐系统</category>
        
        
      </item>
    
      <item>
        <title>Stanford - CS224U, Natural Language Understanding</title>
        <description>
&lt;div data-lang-panel=&quot;en&quot;&gt;

  &lt;!-- Add math equation API --&gt;

  &lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML&quot; async=&quot;&quot;&gt;&lt;/script&gt;

  &lt;p&gt;CS224U: Natural Language Understanding is the first course I took in the Stanford AI Certification Program.
This blog is a short summary of the material and what I took away from it.&lt;/p&gt;

  &lt;p&gt;The instructors for recent terms have been &lt;a href=&quot;https://web.stanford.edu/~cgpotts/&quot;&gt;Prof. Christopher Potts&lt;/a&gt; and &lt;a href=&quot;https://nlp.stanford.edu/~wcmac/&quot;&gt;Bill MacCartney&lt;/a&gt;, and this year was no exception. Both are major figures in the field; Apple’s Siri is one of the products Bill led. Because of COVID-19, the course went fully online for remote students and Stanford on-campus students alike, taught over Zoom. Combined enrollment was around 120+. I teamed with a Stanford undergrad on the homework. The final project was me, that undergrad, and a CS PhD at Northwestern. The final project became optional because of the pandemic.&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;https://web.stanford.edu/class/cs224u/&quot;&gt;Course website&lt;/a&gt;&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;http://onlinehub.stanford.edu/cs224u-natural-language-understanding&quot;&gt;2019 course videos&lt;/a&gt;&lt;/p&gt;

  &lt;h2 id=&quot;lecture-1-introduction-and-course-overview&quot;&gt;Lecture 1, Introduction and course overview&lt;/h2&gt;

  &lt;p&gt;&lt;a href=&quot;https://web.stanford.edu/class/cs224u/materials/cs224u-2020-intro-handout.pdf&quot;&gt;Lecture 1 slides&lt;/a&gt;&lt;/p&gt;

  &lt;h3 id=&quot;nlp-vs-nlu&quot;&gt;NLP VS. NLU&lt;/h3&gt;
  &lt;p&gt;In the first lecture the instructors gave a short intro and then contrasted &lt;strong&gt;NLP&lt;/strong&gt; (Natural Language Processing)
with &lt;strong&gt;NLU&lt;/strong&gt; (Natural Language Understanding). My take is that NLU is a sub-area of NLP:
NLU focuses on machine understanding of language, NLP on processing it. The course covers &lt;em&gt;Grounded Language 
Understanding&lt;/em&gt;, a classic NLU problem. They used the &lt;em&gt;Pragmatic Color Describer&lt;/em&gt; to introduce &lt;strong&gt;Grounding&lt;/strong&gt;.
When you describe a color in language, the words you pick depend on the surrounding context. Enter our orange cats:&lt;/p&gt;

  &lt;p&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;/img/cat-light-orange.webp&quot; alt=&quot;A light orange cat&quot; width=&quot;200&quot; height=&quot;200&quot; /&gt;&lt;/p&gt;

  &lt;p&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;/img/cat-deep-orange.webp&quot; alt=&quot;A deep orange cat&quot; width=&quot;200&quot; height=&quot;267&quot; /&gt;&lt;/p&gt;

  &lt;p&gt;When describing the two orange cats we can distinguish them as the lighter orange cat vs the darker one. If the comparison cat is not orange, we do not need to specify the shade; we just say orange cat. For example:&lt;/p&gt;

  &lt;p&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;/img/cat-compare.webp&quot; alt=&quot;The two cats side by side&quot; width=&quot;400&quot; height=&quot;300&quot; /&gt;&lt;/p&gt;

  &lt;p&gt;When we use contrastive language we assume the listener shares our understanding of color shades. That shared understanding is &lt;strong&gt;Grounding&lt;/strong&gt;.
More on the &lt;em&gt;Pragmatic Color Describer&lt;/em&gt; later.&lt;/p&gt;

  &lt;h3 id=&quot;a-brief-history-of-nlu&quot;&gt;A brief history of NLU&lt;/h3&gt;
  &lt;p&gt;The instructors gave a short history of NLU&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;1966: Eliza&lt;/li&gt;
    &lt;li&gt;1988: Latent Semantic Analysis&lt;/li&gt;
    &lt;li&gt;January 2011: IBM Watson beats Jeopardy! Champions&lt;/li&gt;
    &lt;li&gt;October 2011: Apple Siri launches in beta&lt;/li&gt;
    &lt;li&gt;April 2014: Microsoft Cortana demoed&lt;/li&gt;
    &lt;li&gt;May 2016: Google Assistant&lt;/li&gt;
  &lt;/ul&gt;

  &lt;h3 id=&quot;cs224u-topics&quot;&gt;CS224u topics&lt;/h3&gt;
  &lt;p&gt;They then listed the NLU topics for the term: eight topics, two lectures each. Plenty of material.&lt;/p&gt;
  &lt;ol&gt;
    &lt;li&gt;Vector-space models&lt;/li&gt;
    &lt;li&gt;Sentiment analysis&lt;/li&gt;
    &lt;li&gt;Relation extraction&lt;/li&gt;
    &lt;li&gt;Natural Language Inference&lt;/li&gt;
    &lt;li&gt;Grounding&lt;/li&gt;
    &lt;li&gt;Contextual word representations&lt;/li&gt;
    &lt;li&gt;Adversarial testing&lt;/li&gt;
    &lt;li&gt;Methods and metrics&lt;/li&gt;
  &lt;/ol&gt;

  &lt;p&gt;The professor then sketched progress in each area and recommended &lt;em&gt;SUPERINTELLIGENCE - NICK BOSTROM&lt;/em&gt;.
I found it worthwhile: it is about the future of AI and the social issues that might follow. Little technical content, simple language.
A decent English-practice read if you have time.&lt;/p&gt;

  &lt;p&gt;Finally they pointed to a few starter tutorials: setting up the course virtualenv, intro to PyTorch and NumPy, etc. Strongly recommend following the setup tutorial and cloning the course GitHub; there is a lot of useful material.&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;https://nbviewer.jupyter.org/github/cgpotts/cs224u/blob/master/setup.ipynb&quot;&gt;CS224U virtual environment setup tutorial&lt;/a&gt;&lt;/p&gt;

  &lt;h2 id=&quot;lecture-2-distributed-word-representations&quot;&gt;Lecture 2, Distributed word representations&lt;/h2&gt;
  &lt;p&gt;This lecture covers the most basic but still central idea in NLP: word vectors. Prof. Potts started from co-occurrence matrices and went through GloVe, Word2Vec, and related methods.&lt;/p&gt;

  &lt;p&gt;Vector representations are a core idea in deep learning; the running joke is “everything can be an embedding.” In retail, every product and every store can be a high-dimensional vector. You can study product relationships the same way. Alibaba, Walmart, and Amazon have all done substantial work here. A Product2Vec paper if you want to dig in (
&lt;a href=&quot;https://arxiv.org/pdf/2005.10402.pdf&quot;&gt;Studying Product Competition Using Representation Learning&lt;/a&gt;).&lt;/p&gt;

  &lt;p&gt;Word vector representations are now the default effective method in the field, but they rest on a deep linguistic idea. It goes back to the question: &lt;strong&gt;what defines the meaning of a word?&lt;/strong&gt; The answer here is that the other words that appear with it in a sentence define its meaning.&lt;/p&gt;

  &lt;p&gt;&lt;em&gt;You shall know a word by the company it keeps. - Firth (1957)&lt;/em&gt;&lt;/p&gt;

  &lt;p&gt;&lt;em&gt;“distributional statements can cover all of the material of a language without requiring support from other types of information. - Firth (1957)&lt;/em&gt;&lt;/p&gt;

  &lt;p&gt;The simplest co-occurrence matrix captures meaning by counting how often words appear together.&lt;/p&gt;

  &lt;h3 id=&quot;how-to-build-a-co-occurrence-matrix&quot;&gt;How to build a co-occurrence matrix&lt;/h3&gt;
  &lt;p&gt;Building a co-occurrence matrix has two main parameters.&lt;/p&gt;
  &lt;ol&gt;
    &lt;li&gt;Window Size: the range around a center word in which co-occurrence is counted&lt;/li&gt;
    &lt;li&gt;Scaling: used to adjust the co-occurrence values&lt;/li&gt;
  &lt;/ol&gt;

  &lt;p&gt;Example from a line of Shakespeare:
“For thy sweet love remembered such wealth brings That then I scorn to change my state with kings.”&lt;/p&gt;

  &lt;p&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;820&quot; height=&quot;167&quot; src=&quot;/img/cooccurrence.webp&quot; alt=&quot;Co-occurrence matrix&quot; /&gt;&lt;/p&gt;

  &lt;p&gt;Choice of these two parameters follows this logic&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;Larger window size and smoother scaling capture more &lt;strong&gt;semantic information&lt;/strong&gt;&lt;/li&gt;
    &lt;li&gt;Smaller window size and heavier scaling capture more &lt;strong&gt;syntactic information&lt;/strong&gt;&lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;Compute the co-occurrence matrix over a huge collection of sentences and documents; the counts of how often each word appears with every other word give the simplest word vectors. Each row in the figure below can be treated as a word embedding. Raw counts are not enough; next we look at statistical methods that improve vector quality. Window-based co-occurrence has an obvious flaw: words like the, and, or, is co-occur with almost everything but carry little meaning. Later we cover reweighting methods such as PMI and PPMI that mitigate this.&lt;/p&gt;

  &lt;p&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;820&quot; height=&quot;329&quot; src=&quot;/img/word_word.webp&quot; alt=&quot;Word-by-word design matrix&quot; /&gt;&lt;/p&gt;

  &lt;h3 id=&quot;how-to-compare-two-vectors&quot;&gt;How to compare two vectors&lt;/h3&gt;
  &lt;p&gt;Once words are vectors we need systematic ways to compare them. The course covers:&lt;/p&gt;
  &lt;ol&gt;
    &lt;li&gt;Euclidean distance&lt;/li&gt;
    &lt;li&gt;Euclidean distance with L-2&lt;/li&gt;
    &lt;li&gt;Cosine distance&lt;/li&gt;
    &lt;li&gt;Matching-based methods&lt;/li&gt;
    &lt;li&gt;KL divergence&lt;/li&gt;
  &lt;/ol&gt;

  &lt;h3 id=&quot;euclidean-distance&quot;&gt;Euclidean distance&lt;/h3&gt;
  &lt;p&gt;Distance between n-dimensional vectors u, v:&lt;/p&gt;

\[{euclidean(u, v)=\sqrt{\sum_{i=1}^{n}{|u_i - v_i|^2}}}\]

  &lt;p&gt;Euclidean distance is the straight-line distance between two points. If two vectors point the same way but have different magnitudes, the Euclidean distance is still large. For word vectors direction usually matters more than magnitude; vectors that point the same way tend to have similar meaning, so Euclidean distance is not a good comparison method.&lt;/p&gt;

  &lt;h3 id=&quot;euclidean-distance-with-l-2-norm&quot;&gt;Euclidean distance with L-2 norm&lt;/h3&gt;
  &lt;p&gt;If we L2-normalize the vectors, every vector has magnitude 1. Euclidean distance then ignores magnitude and only reflects direction.&lt;/p&gt;

  &lt;h3 id=&quot;cosine-distance&quot;&gt;Cosine distance&lt;/h3&gt;
  &lt;p&gt;Cosine distance measures only the angle between vectors; magnitude is irrelevant. It is one of the most common ways to compare word vectors in NLP.&lt;/p&gt;

  &lt;p&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;/img/cosine_sim.webp&quot; alt=&quot;Cosine similarity between two vectors&quot; width=&quot;300&quot; height=&quot;258&quot; /&gt;&lt;/p&gt;

\[{cosine(u, v)=1 - {\frac{\sum_{i=1}^{n}{u_i \times v_i}}{||u||_2 \times ||v||_2}}}\]

  &lt;p&gt;One thing worth noting: ranking by Euclidean distance after L2 normalization is the same as ranking by cosine distance. The numeric values differ, but the order when comparing multiple vectors is identical. In word-vector applications they can be treated as equivalent.&lt;/p&gt;

  &lt;h3 id=&quot;matching-based-methods&quot;&gt;Matching-based methods&lt;/h3&gt;
  &lt;p&gt;Matching-based methods are not used much in NLU. The course only mentioned them briefly; I just list the formulas here.&lt;/p&gt;

  &lt;p&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;820&quot; height=&quot;625&quot; src=&quot;/img/matching_dist.webp&quot; alt=&quot;Matching-based distance measures&quot; /&gt;&lt;/p&gt;

  &lt;h3 id=&quot;kl-divergence&quot;&gt;KL divergence&lt;/h3&gt;
  &lt;p&gt;KL divergence is a way to compare two probability distributions. For a deeper dive see &lt;a href=&quot;https://towardsdatascience.com/light-on-math-machine-learning-intuitive-guide-to-understanding-kl-divergence-2b382ca2b2a8&quot;&gt;KL divergence&lt;/a&gt;.&lt;/p&gt;

\[D(p || q) = \sum_{i=1}^{n} {p_ilog(\frac{p_i}{q_i})}\]

  &lt;p&gt;In NLU, to compare two word vectors we first turn each vector into a probability distribution that sums to 1, then apply KL divergence. The lecture gave this example. A, B, and C have already been turned into distributions that sum to 1. The method only works if the vectors can be interpreted as distributions; negative values make it inapplicable.&lt;/p&gt;

  &lt;p&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;820&quot; height=&quot;537&quot; src=&quot;/img/KL.webp&quot; alt=&quot;KL divergence&quot; /&gt;&lt;/p&gt;

  &lt;h3 id=&quot;normalizing-the-co-occurrence-matrix&quot;&gt;Normalizing the co-occurrence matrix&lt;/h3&gt;
  &lt;p&gt;We have covered how to build a co-occurrence matrix and how it yields word vectors. Frequency-based vectors have a serious problem: raw frequency is not very meaningful for language understanding. A simple example: &lt;em&gt;妖魔鬼怪&lt;/em&gt; vs &lt;em&gt;魑魅魍魉&lt;/em&gt;. The two idioms mean essentially the same thing, but &lt;em&gt;妖魔鬼怪&lt;/em&gt; appears far more often in student essays because &lt;em&gt;魑魅魍魉&lt;/em&gt; is hard to write and you lose points if you get it wrong. Frequency is therefore a poor signal; we can reduce its effect with normalization. L2 normalization and converting the vector to a probability distribution are two options. Next is a common method: Observed/Expected.&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;Observed/Expected&lt;/strong&gt; is&lt;/p&gt;

  &lt;p&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;820&quot; height=&quot;543&quot; src=&quot;/img/oe.webp&quot; alt=&quot;Observed over expected weighting&quot; /&gt;&lt;/p&gt;

  &lt;p&gt;How should we interpret the expected count? It is the expected frequency if the row word and column word were independent. In the figure, keep appears 60 times and tabs 21 times. If they were independent, the probability they co-occur would be P(keep) * P(tabs) = 60/101 * 21/101. Observed/Expected compares the actual count with that independence assumption.&lt;/p&gt;

  &lt;p&gt;From Observed/Expected we also get &lt;strong&gt;Pointwise Mutual Information&lt;/strong&gt; (PMI) and &lt;strong&gt;Positive PMI&lt;/strong&gt; (PPMI). PMI is log(Observed/Expected); PPMI sets every negative PMI (from the log) to 0. Note that log(0) is undefined in math, but in this setting we take log(0)=0.&lt;/p&gt;

  &lt;h3 id=&quot;tf-idf&quot;&gt;TF-IDF&lt;/h3&gt;
  &lt;p&gt;TF: Term frequency: P(word | document)
IDF: Inverse document frequency (IDF)&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;https://nlp.stanford.edu/IR-book/html/htmledition/inverse-document-frequency-1.html&quot;&gt;Detailed explanation of IDF&lt;/a&gt;&lt;/p&gt;

  &lt;p&gt;The professor also mentioned t-test weighting, pairwise distance matrices, etc., but they are not used much in this course so I will not expand on them.&lt;/p&gt;

  &lt;h3 id=&quot;dimensionality-reduction&quot;&gt;Dimensionality reduction&lt;/h3&gt;
  &lt;p&gt;Co-occurrence matrices are huge; a word representation can easily have thousands or tens of thousands of dimensions. Using them raw does not extract meaning well, so we need dimensionality reduction. The course covers four families of methods:&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;Latent Semantic Analysis
      &lt;ul&gt;
        &lt;li&gt;Singular value decomposition (SVD)&lt;/li&gt;
        &lt;li&gt;Principal Components Analysis (PCA)&lt;/li&gt;
        &lt;li&gt;Non-negative Matrix Factorization (NMF)&lt;/li&gt;
        &lt;li&gt;Probabilistic LSA (PLSA; Hofmann 1999)&lt;/li&gt;
        &lt;li&gt;Latent Dirichlet Allocation (LDA; Blei et al. 2003)&lt;/li&gt;
        &lt;li&gt;t-SNE (van der Maaten and Hinton 2008)&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;Autoencoders&lt;/li&gt;
    &lt;li&gt;GloVe&lt;/li&gt;
    &lt;li&gt;Word2Vec&lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;The methods under LSA are less common in NLP today, and SVD and PCA are basic dimensionality-reduction techniques, so I will not go into them. We focus on &lt;strong&gt;Autoencoder&lt;/strong&gt;, &lt;strong&gt;GloVe&lt;/strong&gt;, and &lt;strong&gt;Word2Vec&lt;/strong&gt;.&lt;/p&gt;

  &lt;h3 id=&quot;autoencoder&quot;&gt;Autoencoder&lt;/h3&gt;
  &lt;p&gt;An autoencoder is a deep neural architecture used for dimensionality reduction. The lecture showed a simple autoencoder:&lt;/p&gt;

  &lt;p&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;820&quot; height=&quot;577&quot; src=&quot;/img/Autoencoder.webp&quot; alt=&quot;Autoencoder architecture&quot; /&gt;&lt;/p&gt;

  &lt;p&gt;The main difference from a usual network is that a standard net needs an input X and a target Y. Training an autoencoder only needs X, because the input and the target are both X. The objective is to make the output X_hat equal the input X. The code below is the autoencoder in the figure; input_dim_ and output_dim_ are equal (the dimension of X).&lt;/p&gt;

  &lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;torch&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;torch.nn&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;define_graph&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Sequential&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Linear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;input_dim_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hidden_dim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Tanh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Linear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hidden_dim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;output_dim_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;

  &lt;h3 id=&quot;glove&quot;&gt;GloVe&lt;/h3&gt;
  &lt;p&gt;&lt;a href=&quot;https://www.aclweb.org/anthology/D14-1162.pdf&quot;&gt;GloVe&lt;/a&gt; is a landmark result in NLP; almost every NLP course covers it. Here is a short look at how it works. The GloVe objective is to learn word vectors whose dot products are proportional to their co-occurrence probabilities.&lt;/p&gt;

  &lt;p&gt;Before GloVe there were two main families of word-vector methods: 1. &lt;strong&gt;Matrix Factorization Methods&lt;/strong&gt; based on the co-occurrence matrix (everything we discussed above). 2. Shallow window-based methods that use a local context window. That family includes the skip-gram model and the continuous bag-of-words model. Both families have clear weaknesses. In the first, high-frequency words such as the, is, and do not represent meaning well. The second family only sees a local window, so it cannot fully exploit a large corpus.&lt;/p&gt;

&lt;/div&gt;

&lt;div data-lang-panel=&quot;zh&quot; hidden=&quot;&quot;&gt;

  &lt;!-- Add math equation API --&gt;

  &lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML&quot; async=&quot;&quot;&gt;&lt;/script&gt;

  &lt;p&gt;CS224U: Natural Language Understanding 是我在Stanford AI Certification Program 里的第一节课。
这篇Blog对知识点和学习心得做一个简单的总结。&lt;/p&gt;

  &lt;p&gt;最近几学期这门课的教授一直是&lt;a href=&quot;https://web.stanford.edu/~cgpotts/&quot;&gt;Prof. Christopher Potts&lt;/a&gt;和&lt;a href=&quot;https://nlp.stanford.edu/~wcmac/&quot;&gt;Bill MacCartney&lt;/a&gt;，今年也不例外。两位都是真*巨佬，苹果的Siri就是Bill领导的产品之一。这学期因为COVID-19疫情的原因，无论是我们Online的学生还是Stanford的在校学生，课程都变成Online了，通过Zoom授课。这学期Online＋在校学生大概有120+人。我这学期作业是跟一个Stanford本科的白人小哥一组，final project 是我跟白人小哥哥加一个在 Northwestern 的CS PhD。然而个final project因为疫情也变成optional的了。&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;https://web.stanford.edu/class/cs224u/&quot;&gt;课程官网&lt;/a&gt;&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;http://onlinehub.stanford.edu/cs224u-natural-language-understanding&quot;&gt;2019年课程视频&lt;/a&gt;&lt;/p&gt;

  &lt;h2 id=&quot;lecture-1-introduction-and-course-overview-1&quot;&gt;Lecture 1, Introduction and course overview&lt;/h2&gt;

  &lt;p&gt;&lt;a href=&quot;https://web.stanford.edu/class/cs224u/materials/cs224u-2020-intro-handout.pdf&quot;&gt;Lecture 1 slides&lt;/a&gt;&lt;/p&gt;

  &lt;h3 id=&quot;nlp-vs-nlu-1&quot;&gt;NLP VS. NLU&lt;/h3&gt;
  &lt;p&gt;第一节课，老师简单做了一些自我介绍，然后介绍了一个&lt;strong&gt;NLP&lt;/strong&gt;(Natural Language Processing)
和&lt;strong&gt;NLU&lt;/strong&gt;(Natural Language Understanding)的不同。我的理解是NLU是NLP下的一个小分支，
NLU强调的机器对语言的理解，而NLP则强调对语言的处理。门课会涉及到&lt;em&gt;Grounded Language 
Understanding&lt;/em&gt;，是一个典型的NLU问题。这门课用 &lt;em&gt;Pragmatic Color Describer&lt;/em&gt; 来简单介绍了&lt;strong&gt;Grounding&lt;/strong&gt;的概念。
当我用语言描述一个颜色的时候，我们所用的描述性词汇取决于周围的环境。有请我们的橘猫登场:&lt;/p&gt;

  &lt;p&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;/img/cat-light-orange.webp&quot; alt=&quot;A light orange cat&quot; width=&quot;200&quot; height=&quot;200&quot; /&gt;&lt;/p&gt;

  &lt;p&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;/img/cat-deep-orange.webp&quot; alt=&quot;A deep orange cat&quot; width=&quot;200&quot; height=&quot;267&quot; /&gt;&lt;/p&gt;

  &lt;p&gt;我们描述两只橘猫的时候，就可以用浅一点的那个只橘猫VS深色的橘猫来作区分。然而当对比的猫不是橘色的话，我们就需要描述橘色的深浅，只需要说橘猫即可。比如这样:&lt;/p&gt;

  &lt;p&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;/img/cat-compare.webp&quot; alt=&quot;The two cats side by side&quot; width=&quot;400&quot; height=&quot;300&quot; /&gt;&lt;/p&gt;

  &lt;p&gt;当我们使用对比描述语言的时候，我们假设说话对象对颜色深浅有跟我们一样的理解。这个理解就是&lt;strong&gt;Grounding&lt;/strong&gt;。
有关&lt;em&gt;Pragmatic Color Describer&lt;/em&gt;的具体内容后面会详细讲。&lt;/p&gt;

  &lt;h3 id=&quot;a-brief-history-of-nlu-1&quot;&gt;A brief history of NLU&lt;/h3&gt;
  &lt;p&gt;这部分老师简单介绍了一下NLU的发展历史&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;1966: Eliza&lt;/li&gt;
    &lt;li&gt;1988: Latent Semantic Analysis&lt;/li&gt;
    &lt;li&gt;2011年1月: IBM Watson beats Jeopardy! Champions&lt;/li&gt;
    &lt;li&gt;2011年10月: Apple Siri launches in beta&lt;/li&gt;
    &lt;li&gt;2014年4月: Microsoft Cortana demoed&lt;/li&gt;
    &lt;li&gt;2016年5月: Google Assistant&lt;/li&gt;
  &lt;/ul&gt;

  &lt;h3 id=&quot;cs224u-topics-1&quot;&gt;CS224u topics&lt;/h3&gt;
  &lt;p&gt;随后介绍了一下这学期会cover的NLU topics，八个topic，每个topic两个lecture。内容还是相当丰富的。&lt;/p&gt;
  &lt;ol&gt;
    &lt;li&gt;Vector-space models&lt;/li&gt;
    &lt;li&gt;Sentiment analysis&lt;/li&gt;
    &lt;li&gt;Relation extraction&lt;/li&gt;
    &lt;li&gt;Natural Language Inference&lt;/li&gt;
    &lt;li&gt;Grounding&lt;/li&gt;
    &lt;li&gt;Contextual word representations&lt;/li&gt;
    &lt;li&gt;Adversarial testing&lt;/li&gt;
    &lt;li&gt;Methods and metrics&lt;/li&gt;
  &lt;/ol&gt;

  &lt;p&gt;随后教授介绍了各个方向的发展，并且介绍了一个本书&lt;em&gt;SUPERINTELLIGENCE - NICK BOSTROM&lt;/em&gt;,
我读着觉得不错，主要讲的就是对AI的未来发展，和可能带来的相应的社会问题。没什么技术内容，用词简单，
有时间的朋友可以读读，当做练习英文也不错。&lt;/p&gt;

  &lt;p&gt;最后教授介绍了一下几个入门教程，包括设置课程的虚拟环境，PyTorch和Numpy的入门，等。强烈建议跟着教程设置一下虚拟环境，并克隆这门课的Github，有很多有用的东西。&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;https://nbviewer.jupyter.org/github/cgpotts/cs224u/blob/master/setup.ipynb&quot;&gt;CS224U虚拟环境设置教程&lt;/a&gt;&lt;/p&gt;

  &lt;h2 id=&quot;lecture-2-distributed-word-representations-1&quot;&gt;Lecture 2, Distributed word representations&lt;/h2&gt;
  &lt;p&gt;这节课讲的就是NLP里面最基础，但相当重要的Word Vector。 Prof. Potts从最开始的Co-occurrence矩阵，讲到到GloVe和Word2Vec等方法。&lt;/p&gt;

  &lt;p&gt;Vector representation的思想，是深度学习里的一个核心思想，我们就经常开玩笑说，“万物皆可Embedding”。比如在零售业，每一个商品，每一个门店，都可以用一个高维向量来表示。同理我们也可以通过这种向量来研究商品之间的关系。中国零售大佬阿里巴巴，和美国零售Walmart，Amazon都在这方面有深入的研究，这里附上一篇有关Product2Vec的论文，有兴趣的同学可以读一下(
&lt;a href=&quot;https://arxiv.org/pdf/2005.10402.pdf&quot;&gt;Studying Product Competition Using Representation Learning&lt;/a&gt;)。&lt;/p&gt;

  &lt;p&gt;现在Word vector representation已经是学术界默认的有效方法，然而其背后有着对语言学深刻的理解。这要追溯到这个语言学问题：&lt;strong&gt;什么定义了一个词的意思？&lt;/strong&gt; 在这里的理解是，与这个词一同出现在一个句子的其他词定义了这个词的意义。&lt;/p&gt;

  &lt;p&gt;&lt;em&gt;You shall know a word by the company it keeps. - Firth (1957)&lt;/em&gt;&lt;/p&gt;

  &lt;p&gt;&lt;em&gt;“distributional statements can cover all of the material of a language without requiring support from other types of information. - Firth (1957)&lt;/em&gt;&lt;/p&gt;

  &lt;p&gt;最简单的Co-occurrence矩阵通过计算词汇同时出现的次数来反应词义信息。&lt;/p&gt;

  &lt;h3 id=&quot;co-occurrence&quot;&gt;如何构建Co-occurrence矩阵&lt;/h3&gt;
  &lt;p&gt;Co-occurrence矩阵的构建有两个主要参数。&lt;/p&gt;
  &lt;ol&gt;
    &lt;li&gt;Window Size: 以一个单词为中心，前后考虑co-occurrence的范围&lt;/li&gt;
    &lt;li&gt;Scaling: 用于调整co-occurrence的数值&lt;/li&gt;
  &lt;/ol&gt;

  &lt;p&gt;这里用莎士比亚的一句诗来举例:
“For thy sweet love remembered such wealth brings That then I scorn to change my state with kings.”&lt;/p&gt;

  &lt;p&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;820&quot; height=&quot;167&quot; src=&quot;/img/cooccurrence.webp&quot; alt=&quot;Co-occurrence matrix&quot; /&gt;&lt;/p&gt;

  &lt;p&gt;这两个参数的选取，主要遵循以下逻辑&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;更大的window size和更平滑的scaling能获取更多的&lt;strong&gt;semantic information&lt;/strong&gt;(语义信息)&lt;/li&gt;
    &lt;li&gt;更小的window size和更多的scaling能获取更多的&lt;strong&gt;syntactic information&lt;/strong&gt;(句子结构信息)&lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;用海量的句子，文章来计算co-occurrence矩阵，每个词与其他词一起出现的次数就能构成最简单的word vector。下图的每一行都可以看做一个word embedding。当然简单的计数并不能达到我们想要的效果，接下来会讲如何通过统计方法提高word vector的质量。然而基于窗口构建的co-occurrence有一个明显的缺点，像the, and, or, is等词汇跟其他词co-occurrence的频率非常高，但是包含词义的信息却非常少。后面会讲到一些reweighting的方法，比如PMI，PPMI，来改善这个情况。&lt;/p&gt;

  &lt;p&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;820&quot; height=&quot;329&quot; src=&quot;/img/word_word.webp&quot; alt=&quot;Word-by-word design matrix&quot; /&gt;&lt;/p&gt;

  &lt;h3 id=&quot;section&quot;&gt;如何比较两个向量&lt;/h3&gt;
  &lt;p&gt;单词变成向量之后，我们需要系统的比较两个向量的方法。这门课介绍了以下几个方法：&lt;/p&gt;
  &lt;ol&gt;
    &lt;li&gt;Euclidean distance&lt;/li&gt;
    &lt;li&gt;Euclidean distance with L-2&lt;/li&gt;
    &lt;li&gt;Cosine distance&lt;/li&gt;
    &lt;li&gt;Matching-based methods&lt;/li&gt;
    &lt;li&gt;KL divergence&lt;/li&gt;
  &lt;/ol&gt;

  &lt;h3 id=&quot;euclidean-distance-1&quot;&gt;Euclidean distance(欧几里得距离)&lt;/h3&gt;
  &lt;p&gt;n维向量u, v之间的距离:&lt;/p&gt;

\[{euclidean(u, v)=\sqrt{\sum_{i=1}^{n}{|u_i - v_i|^2}}}\]

  &lt;p&gt;Euclidean distance描述的是两点之间的直线距离。如果两个向量方向一致，大小不同的话，欧式距离也会非常大。通常word vector的方向比大小更重要，方向一致的word vector通常有着相近的词意，所以Euclidean distance不是比较word vector的合适方法。&lt;/p&gt;

  &lt;h3 id=&quot;euclidean-distance-with-l-2-norm-1&quot;&gt;Euclidean distance with L-2 norm&lt;/h3&gt;
  &lt;p&gt;如果我们对向量进行归一化，那么向量大小都会变为1，这是用在用Euclidean distance就能排除向量大小的影响，从而只考虑向量方向的影响。&lt;/p&gt;

  &lt;h3 id=&quot;cosine-distance-1&quot;&gt;Cosine distance&lt;/h3&gt;
  &lt;p&gt;Cosine distance只测量向量角度，向量大小完全无关。Cosine distance是NLP界最常用的word vector比较方法之一。&lt;/p&gt;

  &lt;p&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;/img/cosine_sim.webp&quot; alt=&quot;Cosine similarity between two vectors&quot; width=&quot;300&quot; height=&quot;258&quot; /&gt;&lt;/p&gt;

\[{cosine(u, v)=1 - {\frac{\sum_{i=1}^{n}{u_i \times v_i}}{||u||_2 \times ||v||_2}}}\]

  &lt;p&gt;这里有点值得注意的是, euclidean distance + L-2 Normalization的排序是等同于cosine distance的。这两种方法的数值不完全相同，但是用来比较多个向量距离的时候排序是一样的。在word vector应用领域，可以认为是等价的。&lt;/p&gt;

  &lt;h3 id=&quot;matching-based-methods-1&quot;&gt;Matching-based methods&lt;/h3&gt;
  &lt;p&gt;Matching based在NLU领域用的并不多，这门课只是简单
提了一下，我在这就简单列一下公式。&lt;/p&gt;

  &lt;p&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;820&quot; height=&quot;625&quot; src=&quot;/img/matching_dist.webp&quot; alt=&quot;Matching-based distance measures&quot; /&gt;&lt;/p&gt;

  &lt;h3 id=&quot;kl-divergence-1&quot;&gt;KL divergence&lt;/h3&gt;
  &lt;p&gt;KL divergence本身是一种比较两个概率分部的方法。想具体了解KL divergence的同学可以看这篇&lt;a href=&quot;https://towardsdatascience.com/light-on-math-machine-learning-intuitive-guide-to-understanding-kl-divergence-2b382ca2b2a8&quot;&gt;KL divergence&lt;/a&gt;。&lt;/p&gt;

\[D(p || q) = \sum_{i=1}^{n} {p_ilog(\frac{p_i}{q_i})}\]

  &lt;p&gt;在NLU的运用中，如果我们要比较两个word vector我们首先要吧word vector变成一个和为1的概率分布，然后用KL divergence来比较距离。课上给出了这么一个例子。A, B, C都是变成和为1的概率分布了。使用KL的前提是，向量能被理解为概率分布，如果向量里面有负值，那么这个方法就不适用。&lt;/p&gt;

  &lt;p&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;820&quot; height=&quot;537&quot; src=&quot;/img/KL.webp&quot; alt=&quot;KL divergence&quot; /&gt;&lt;/p&gt;

  &lt;h3 id=&quot;co-occurrence-1&quot;&gt;标准化Co-occurrence矩阵&lt;/h3&gt;
  &lt;p&gt;我们已经简单讲了如何构建co-occurrence矩阵，并且通过co-occurrence矩阵引申出word vector的概念。然而靠词出现的频率产生的word vector有一个很严重的问题，就是词出现频率在语言理解里的意义并不大。举一个很简单的例子，&lt;em&gt;妖魔鬼怪&lt;/em&gt;VS&lt;em&gt;魑魅魍魉&lt;/em&gt;。这两个成语的意思基本一样，但是大家写作文的时候可能&lt;em&gt;妖魔鬼怪&lt;/em&gt;出现的频率会高很多，因为&lt;em&gt;魑魅魍魉&lt;/em&gt;实在太难写了，写错还要扣分不是。所以频率并不是一个非常好的方法，我们可以通过标准化的方法来减弱频率的影响。L-2 normalization和把向量转化为概率的方法。下面会讲一个常用的方法Observe/Expected。&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;Observed/Expected&lt;/strong&gt;公式如下&lt;/p&gt;

  &lt;p&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;820&quot; height=&quot;543&quot; src=&quot;/img/oe.webp&quot; alt=&quot;Observed over expected weighting&quot; /&gt;&lt;/p&gt;

  &lt;p&gt;这里该怎么理解这这个expected的计算呢？expected可以理解为在行和列的词都是独立事件的时候，这个词出现频率的期望值。在这张图给出的例子中，keep这个词出现了60次，tabs出现了21次。如果这两个词出现是独立事件的话，那么keep, tabs一起出现的概率应该是P(keep) * P(tabs) = 60/101 * 21/101。Observed/Expected是在比较实际情况和假设为独立事件的时候的不同。&lt;/p&gt;

  &lt;p&gt;在Observed/Expected的基础上，我们还可以得到&lt;strong&gt;Pointwise Mutual Information&lt;/strong&gt;(PMI)和&lt;strong&gt;Positive PMI&lt;/strong&gt;(PPMI)。PMI是log(Observed/Expected)，而PPMI则是把所有PMI中因为log产生的负数改为0。值得注意的是数学上log(0)是没有定义的，但是在这个应用场景下log(0)=0。&lt;/p&gt;

  &lt;h3 id=&quot;tf-idf-1&quot;&gt;TF-IDF&lt;/h3&gt;
  &lt;p&gt;TF: Term frequency: P(word | document)
IDF: Inverse document frequency (IDF)&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;https://nlp.stanford.edu/IR-book/html/htmledition/inverse-document-frequency-1.html&quot;&gt;IDF详细解释&lt;/a&gt;&lt;/p&gt;

  &lt;p&gt;教授还提到了t-test weighting，pairwise distance matrices等，但这些在这门课中用到的并不多，就不在这展开了。&lt;/p&gt;

  &lt;h3 id=&quot;dimensionality-reduction-1&quot;&gt;Dimensionality reduction&lt;/h3&gt;
  &lt;p&gt;Co-occurrence矩阵非常大，一个word representation可能会高达上千，上万维度，如果直接用的话并不能很好的提取词义信息，所以这里要用到降维的方法。这门课介绍了四种方法:&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;Latent Semantic Analysis
      &lt;ul&gt;
        &lt;li&gt;Singular value decomposition (SVD)&lt;/li&gt;
        &lt;li&gt;Principal Components Analysis (PCA)&lt;/li&gt;
        &lt;li&gt;Non-negative Matrix Factorization (NMF)&lt;/li&gt;
        &lt;li&gt;Probabilistic LSA (PLSA; Hofmann 1999)&lt;/li&gt;
        &lt;li&gt;Latent Dirichlet Allocation (LDA; Blei et al. 2003)&lt;/li&gt;
        &lt;li&gt;t-SNE (van der Maaten and Hinton 2008)&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;Autoencoders&lt;/li&gt;
    &lt;li&gt;GloVe&lt;/li&gt;
    &lt;li&gt;Word2Vec&lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;这里LSA下面的方法在如今的NLP领域已经不那么常用了，而且SVD和PCA是比较基础的降维方法，这里就不展开讲。我们主要看看&lt;strong&gt;Autoencoder&lt;/strong&gt;, &lt;strong&gt;GloVe&lt;/strong&gt;, 和&lt;strong&gt;Word2Vec&lt;/strong&gt;。&lt;/p&gt;

  &lt;h3 id=&quot;autoencoder-1&quot;&gt;Autoencoder&lt;/h3&gt;
  &lt;p&gt;Autoencoder是一类用来降维结构的深度神经网络结构。老师在lecture中给出了一个简单的Autoencoder模型的结构&lt;/p&gt;

  &lt;p&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;820&quot; height=&quot;577&quot; src=&quot;/img/Autoencoder.webp&quot; alt=&quot;Autoencoder architecture&quot; /&gt;&lt;/p&gt;

  &lt;p&gt;Autoencoder结构与其他神经网络不一样的地方主要是，训练寻常的神经网络，我们需要一个输入X，和输出Y。但是训练Autoencoder， 我们只需要输入X即可，因为这里的输入和输出都是X。优化的目的是让输出层的X_hat等于输入层的X。以下就是图中描述的Autoencoder，这里的input_dim_和output_dim_是相等的都是X向量的维度。&lt;/p&gt;

  &lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;torch&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;torch.nn&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;define_graph&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Sequential&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Linear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;input_dim_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hidden_dim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Tanh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;nn&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Linear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hidden_dim&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;output_dim_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;

  &lt;h3 id=&quot;glove-1&quot;&gt;GloVe&lt;/h3&gt;
  &lt;p&gt;&lt;a href=&quot;https://www.aclweb.org/anthology/D14-1162.pdf&quot;&gt;GloVe&lt;/a&gt;是NLP界一个里程碑级别的成就，基本上所有NLP相关的课程都会讲到这个，我们就来简单看看GloVe就是怎么实现的。GloVe的优化目标是学习一组word vector使其点乘和他们co-occurrence的概率成正比。&lt;/p&gt;

  &lt;p&gt;在GloVe之前，生成word vector两大类方法是: 1. 基于co-occurrence矩阵的&lt;strong&gt;Matrix Factorization Methods&lt;/strong&gt;，咱之前讨论的都是这一类方法。2: 基于local context window的Shallow Window-Based Methods。这类方法包括skip-gram model和continuous bag-of-words model。然而这两类方法都有明显的缺陷。第一类中有些高频的词汇，例如the, is, and等并不能很好的代表词意。第二类方法是我是基于local window，所以无法完全利用庞大的语料数据。&lt;/p&gt;

&lt;/div&gt;
</description>
        <pubDate>Mon, 27 Jul 2020 00:00:00 +0000</pubDate>
        <link>https://jazzikp.github.io/2020/07/27/CS224U/</link>
        <guid isPermaLink="true">https://jazzikp.github.io/2020/07/27/CS224U/</guid>
        
        <category>NLU</category>
        
        <category>CS224U</category>
        
        <category>Stanford</category>
        
        
      </item>
    
      <item>
        <title>Stanford - CS224n, NLP with Deep learning</title>
        <description>
&lt;div data-lang-panel=&quot;en&quot;&gt;

  &lt;!-- Add math equation API --&gt;

  &lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML&quot; async=&quot;&quot;&gt;&lt;/script&gt;

  &lt;h2 id=&quot;lecture-1-introduction-and-word-vectors&quot;&gt;Lecture 1, Introduction and Word Vectors&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&quot;http://web.stanford.edu/class/cs224n/&quot;&gt;Course Website&lt;/a&gt;&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;http://onlinehub.stanford.edu/cs224&quot;&gt;Lecture Video&lt;/a&gt;&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;http://web.stanford.edu/class/cs224n/slides/cs224n-2019-lecture01-wordvecs1.pdf&quot;&gt;Lecture slids&lt;/a&gt;&lt;/p&gt;

  &lt;h3 id=&quot;what-do-we-hope-to-learn&quot;&gt;What do we hope to learn?&lt;/h3&gt;
  &lt;ul&gt;
    &lt;li&gt;
      &lt;p&gt;RNN&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;Attention&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;PyTorch&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;Word meaning, dependency parsing, machine translation, question answering&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;Traditional NLP use &lt;strong&gt;one-hot&lt;/strong&gt; encoding for words.
The problem is that the one-hot matrix can be very big.
There is no natural notion of similarity of one-hot vectors because they are orthogonal.&lt;/p&gt;

  &lt;p&gt;We can use a word similarity tabl, yet the table will be very large (500,000 words X 500,000 words)!&lt;/p&gt;

  &lt;h3 id=&quot;denotational-semantics-vs-distributional-semantics&quot;&gt;Denotational Semantics VS. Distributional Semantics&lt;/h3&gt;

  &lt;ul&gt;
    &lt;li&gt;
      &lt;p&gt;Denotational Semantics: signifier (symbol) ⟺ signified (idea or thing)&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;Distributional Semantics: A word’s meaning is given by the words that frequently appear close-by&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ul&gt;

  &lt;h3 id=&quot;word2vec&quot;&gt;Word2vec&lt;/h3&gt;
  &lt;p&gt;Idea:&lt;/p&gt;

  &lt;ol&gt;
    &lt;li&gt;
      &lt;p&gt;We have a large corpus of text&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;Every word in a fixed vocabulary is represented by a &lt;strong&gt;vector&lt;/strong&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;Go through each position t in the text, which has a center word c and context (“outside”) words o&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;Use the &lt;strong&gt;similarity of the word vectors&lt;/strong&gt; for c and o to calculate the probability of o given c (or vice versa)&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;&lt;strong&gt;Keep adjusting the word vectors&lt;/strong&gt; to maximize this probability&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;

  &lt;p&gt;Example windows and process for computing \({P(w_{t+j} | w_t)}\)
&lt;img src=&quot;/img/CS224n-SS1.webp&quot; alt=&quot;CS224n course schedule&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;820&quot; height=&quot;264&quot; /&gt;&lt;/p&gt;

  &lt;p&gt;We want to maximize the overall log likelihood of getting a correct prediction of context words within a window of fixed size m given center word \(w_j\). The &lt;strong&gt;objective function&lt;/strong&gt; is:&lt;/p&gt;

\[Likelihood = L(\theta)=\prod_{t=1}^{T} \prod_{-m \leq j \leq m \atop j \neq 0} P\left(w_{t+j} | w_{t} ; \theta\right)\]

  &lt;p&gt;Then the &lt;strong&gt;objective function&lt;/strong&gt; is the average negative log likelihood.&lt;/p&gt;

\[{ J(\theta)=-\frac{1}{T} \log L(\theta)=-\frac{1}{T} \sum_{t=1}^{T} \sum_{-m \leq j \leq m \atop j \neq 0} \log P\left(w_{t+j} | w_{t} ; \theta\right) }\]

  &lt;p&gt;Question: How to calculate \({P(w_{t+j} | w_t)}\) ? 
Answer: Use two vectors per word \(w\)&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;
      &lt;p&gt;\(v_w\) when w is center word&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;\(u_w\) when w is a context word&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;Then for a center word c and a context word o:&lt;/p&gt;

\[P(o | c)=\frac{\exp \left(u_{o}^{T} v_{c}\right)}{\sum_{w \in V} \exp \left(u_{w}^{T} v_{c}\right)}\]

  &lt;p&gt;&lt;img src=&quot;/img/CS224n-SS2.webp&quot; alt=&quot;CS224n assignment overview&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;820&quot; height=&quot;615&quot; /&gt;&lt;/p&gt;

  &lt;h3 id=&quot;hw1&quot;&gt;HW1&lt;/h3&gt;
  &lt;p&gt;Got problem using virtual environment. The nltk can not find reuters. Deactivate virtual environment solved this.
&lt;a href=&quot;https://github.com/jazzikp/jazzikp.github.io/blob/master/_posts/CS224n/exploring_word_vectors.ipynb&quot;&gt;Link to HW1&lt;/a&gt;&lt;/p&gt;

  &lt;h2 id=&quot;lecture-2-word-vectors-and-word-senses&quot;&gt;Lecture 2, Word Vectors and Word Senses&lt;/h2&gt;

  &lt;h3 id=&quot;idf-weighting&quot;&gt;idf-weighting&lt;/h3&gt;

  &lt;h2 id=&quot;lecture-5-dependency-parsing&quot;&gt;Lecture 5, Dependency Parsing&lt;/h2&gt;

  &lt;p&gt;After two lectures of mathematical background in deep learning, we can finally started to learn some NLP stuff.&lt;/p&gt;

  &lt;h3 id=&quot;two-views-of-linguistic-structure&quot;&gt;1. Two views of linguistic structure:&lt;/h3&gt;

  &lt;p&gt;&lt;strong&gt;Phrase structure&lt;/strong&gt;: organizes words into nested constituents.&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;Can represent the grammar with CFG rules&lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;&lt;strong&gt;Constituency&lt;/strong&gt; = phrase structure grammar = context-free grammars (CFGs)&lt;/p&gt;

  &lt;p&gt;NP: noun phrase&lt;/p&gt;

  &lt;p&gt;PP: preposition&lt;/p&gt;

  &lt;p&gt;Det: Determinant&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;Some of the grammar rules
      &lt;ul&gt;
        &lt;li&gt;NP -&amp;gt; Det N (the cat, a dog)&lt;/li&gt;
        &lt;li&gt;NP -&amp;gt; Det (Adj) N&lt;/li&gt;
        &lt;li&gt;NP -&amp;gt; Det(Adj) N PP&lt;/li&gt;
        &lt;li&gt;PP -&amp;gt; PP NP&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;&lt;strong&gt;Dependency structure&lt;/strong&gt;&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;Dependency structure shows which words depend on (modify or are arguments of ) which other words.&lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;Why do we need sentence structure? We, human, need a certain sentence structure in order to convey complex meaning.&lt;/p&gt;

  &lt;h3 id=&quot;example-of-prepositional-phrase-attachment-ambiguity&quot;&gt;Example of prepositional phrase attachment ambiguity&lt;/h3&gt;

  &lt;p&gt;This is an example of why NLP is hard. We human are communicate through languages, but language alone will not do it. We talk to people
with the assumption that the other person have similar knowledge as ourself. This is “Common Sense”.&lt;/p&gt;

  &lt;p&gt;Here is an example of ambiguous sentences:
&lt;em&gt;San Jose cops kill man with knife&lt;/em&gt;&lt;/p&gt;

  &lt;p&gt;There are two meaning for this one sentences. First, cops stabbed and killed a man using knife.
Second, cops killed a man who is holding a knife. According to our common sense, we tend to believe that the second is true.
The meaning of a sentence is depended on the dependency structure of words. This is also why human languages are very different 
from programming languages.&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;Catalan numbers: ${ C_n = (2n)! / [(n+1)!n!]}$&lt;/li&gt;
    &lt;li&gt;An exponentially growing series, which arises in many tree-like contexts. CS228: Prob. Graphic Model.&lt;/li&gt;
  &lt;/ul&gt;

  &lt;h3 id=&quot;coordination-scope-ambiguity&quot;&gt;Coordination Scope Ambiguity&lt;/h3&gt;

  &lt;p&gt;&lt;em&gt;Shuttle veteran and longtime NASA executive Fred Gregory appointed to board.&lt;/em&gt;&lt;/p&gt;

  &lt;p&gt;Either Fred Gregory is a shuttle veteran and a longtime NASA executive, or two people where one is shuttle veteran and the other is NASA executive.&lt;/p&gt;

  &lt;h3 id=&quot;adjectival-modifier-ambiguity&quot;&gt;Adjectival Modifier Ambiguity&lt;/h3&gt;
  &lt;p&gt;This is a graduate level class, so prof. actually showed this example in his lecture!!!&lt;/p&gt;

  &lt;p&gt;&lt;em&gt;Student get first hand job experience&lt;/em&gt;&lt;/p&gt;

  &lt;h3 id=&quot;verb-phrase-vp-attachment-ambiguity&quot;&gt;Verb Phrase (VP) attachment ambiguity&lt;/h3&gt;

  &lt;h3 id=&quot;the-rise-of-annotated-data-universal-dependencies-treebands&quot;&gt;The rise of annotated data, Universal Dependencies Treebands&lt;/h3&gt;
  &lt;p&gt;Build a tree band seems a lot slower and less useful than building a grammar. But treebank have following advantages:&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;Reusability of the labor
      &lt;ul&gt;
        &lt;li&gt;Many parsers, part-of-speech taggers, etc. can be build on it&lt;/li&gt;
        &lt;li&gt;Valuable resource for linguistics&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;Broad coverage, not just a few intuitions&lt;/li&gt;
    &lt;li&gt;Frequencies and distributional information&lt;/li&gt;
    &lt;li&gt;A way to evaluate systems&lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;Dependency can cross over:
&lt;strong&gt;I will give a talk tomorrow on bootstrapping&lt;/strong&gt;&lt;/p&gt;

  &lt;p&gt;&lt;em&gt;on bootstrapping&lt;/em&gt; cross over talk.&lt;/p&gt;

  &lt;h2 id=&quot;lecture-7-vanishing-gradients-fancy-rnns&quot;&gt;Lecture 7, Vanishing Gradients, Fancy RNNs&lt;/h2&gt;

  &lt;p&gt;Topics: Vanishing (exploding) gradient, LSTM, GRU, bidirectional, Multi-layers&lt;/p&gt;

&lt;/div&gt;

&lt;div data-lang-panel=&quot;zh&quot; hidden=&quot;&quot;&gt;

  &lt;!-- Add math equation API --&gt;

  &lt;script src=&quot;https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML&quot; async=&quot;&quot;&gt;&lt;/script&gt;

  &lt;h2 id=&quot;lecture-1-&quot;&gt;Lecture 1, 引言与词向量&lt;/h2&gt;
  &lt;p&gt;&lt;a href=&quot;http://web.stanford.edu/class/cs224n/&quot;&gt;课程网站&lt;/a&gt;&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;http://onlinehub.stanford.edu/cs224&quot;&gt;讲座视频&lt;/a&gt;&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;http://web.stanford.edu/class/cs224n/slides/cs224n-2019-lecture01-wordvecs1.pdf&quot;&gt;讲座幻灯片&lt;/a&gt;&lt;/p&gt;

  &lt;h3 id=&quot;section&quot;&gt;我们希望学到什么？&lt;/h3&gt;
  &lt;ul&gt;
    &lt;li&gt;
      &lt;p&gt;RNN&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;Attention&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;PyTorch&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;词义、依存句法分析、机器翻译、问答&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;传统 NLP 使用 &lt;strong&gt;one-hot&lt;/strong&gt; 编码表示词。
问题是 one-hot 矩阵会非常大。
one-hot 向量之间没有自然的相似性概念，因为它们是正交的。&lt;/p&gt;

  &lt;p&gt;我们可以用词相似度表，但这个表会非常大（500,000 词 X 500,000 词）！&lt;/p&gt;

  &lt;h3 id=&quot;vs-&quot;&gt;指称语义 VS. 分布语义&lt;/h3&gt;

  &lt;ul&gt;
    &lt;li&gt;
      &lt;p&gt;指称语义：能指（符号）⟺ 所指（概念或事物）&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;分布语义：一个词的含义由其经常出现在附近的词给出&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ul&gt;

  &lt;h3 id=&quot;word2vec-1&quot;&gt;Word2vec&lt;/h3&gt;
  &lt;p&gt;思路：&lt;/p&gt;

  &lt;ol&gt;
    &lt;li&gt;
      &lt;p&gt;我们有一个大型文本语料库&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;固定词表中的每个词用一个 &lt;strong&gt;向量&lt;/strong&gt; 表示&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;遍历文本中的每个位置 t，该位置有中心词 c 和上下文（“外部”）词 o&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;用 c 和 o 的 &lt;strong&gt;词向量相似度&lt;/strong&gt; 来计算给定 c 时 o 的概率（或反过来）&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;&lt;strong&gt;不断调整词向量&lt;/strong&gt; 以最大化这个概率&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;

  &lt;p&gt;计算 \({P(w_{t+j} | w_t)}\) 的窗口示例与过程
&lt;img src=&quot;/img/CS224n-SS1.webp&quot; alt=&quot;CS224n 课程安排&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;820&quot; height=&quot;264&quot; /&gt;&lt;/p&gt;

  &lt;p&gt;我们希望最大化在给定中心词 \(w_j\) 时，固定窗口大小 m 内正确预测上下文词的总体对数似然。&lt;strong&gt;目标函数&lt;/strong&gt; 是：&lt;/p&gt;

\[Likelihood = L(\theta)=\prod_{t=1}^{T} \prod_{-m \leq j \leq m \atop j \neq 0} P\left(w_{t+j} | w_{t} ; \theta\right)\]

  &lt;p&gt;然后 &lt;strong&gt;目标函数&lt;/strong&gt; 是平均负对数似然。&lt;/p&gt;

\[{ J(\theta)=-\frac{1}{T} \log L(\theta)=-\frac{1}{T} \sum_{t=1}^{T} \sum_{-m \leq j \leq m \atop j \neq 0} \log P\left(w_{t+j} | w_{t} ; \theta\right) }\]

  &lt;p&gt;问题：如何计算 \({P(w_{t+j} | w_t)}\) ？ 
答案：每个词 \(w\) 使用两个向量&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;
      &lt;p&gt;\(v_w\) 当 w 是中心词时&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;\(u_w\) 当 w 是上下文词时&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;然后对于中心词 c 和上下文词 o：&lt;/p&gt;

\[P(o | c)=\frac{\exp \left(u_{o}^{T} v_{c}\right)}{\sum_{w \in V} \exp \left(u_{w}^{T} v_{c}\right)}\]

  &lt;p&gt;&lt;img src=&quot;/img/CS224n-SS2.webp&quot; alt=&quot;CS224n 作业概览&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;820&quot; height=&quot;615&quot; /&gt;&lt;/p&gt;

  &lt;h3 id=&quot;hw1-1&quot;&gt;HW1&lt;/h3&gt;
  &lt;p&gt;使用虚拟环境时遇到问题。nltk 找不到 reuters。停用虚拟环境后解决了。
&lt;a href=&quot;https://github.com/jazzikp/jazzikp.github.io/blob/master/_posts/CS224n/exploring_word_vectors.ipynb&quot;&gt;HW1 链接&lt;/a&gt;&lt;/p&gt;

  &lt;h2 id=&quot;lecture-2-&quot;&gt;Lecture 2, 词向量与词义&lt;/h2&gt;

  &lt;h3 id=&quot;idf-weighting-1&quot;&gt;idf-weighting&lt;/h3&gt;

  &lt;h2 id=&quot;lecture-5-&quot;&gt;Lecture 5, 依存句法分析&lt;/h2&gt;

  &lt;p&gt;经过两讲深度学习的数学背景后，我们终于可以开始学一些 NLP 内容了。&lt;/p&gt;

  &lt;h3 id=&quot;section-1&quot;&gt;1. 语言结构的两种观点：&lt;/h3&gt;

  &lt;p&gt;&lt;strong&gt;短语结构&lt;/strong&gt;：将词组织成嵌套的成分。&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;可以用 CFG 规则表示语法&lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;&lt;strong&gt;Constituency&lt;/strong&gt; = 短语结构语法 = 上下文无关文法 (CFGs)&lt;/p&gt;

  &lt;p&gt;NP: 名词短语&lt;/p&gt;

  &lt;p&gt;PP: 介词&lt;/p&gt;

  &lt;p&gt;Det: 限定词&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;部分语法规则
      &lt;ul&gt;
        &lt;li&gt;NP -&amp;gt; Det N (the cat, a dog)&lt;/li&gt;
        &lt;li&gt;NP -&amp;gt; Det (Adj) N&lt;/li&gt;
        &lt;li&gt;NP -&amp;gt; Det(Adj) N PP&lt;/li&gt;
        &lt;li&gt;PP -&amp;gt; PP NP&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;&lt;strong&gt;依存结构&lt;/strong&gt;&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;依存结构显示哪些词依赖于（修饰或作为论元）哪些其他词。&lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;为什么需要句子结构？我们人类需要一定的句子结构来传达复杂含义。&lt;/p&gt;

  &lt;h3 id=&quot;section-2&quot;&gt;介词短语附着歧义的例子&lt;/h3&gt;

  &lt;p&gt;这是 NLP 困难的一个例子。我们人类通过语言交流，但仅靠语言不够。我们与人交谈时假设对方拥有与我们相似的知识。这就是“常识”。&lt;/p&gt;

  &lt;p&gt;下面是一个歧义句子的例子：
&lt;em&gt;San Jose cops kill man with knife&lt;/em&gt;&lt;/p&gt;

  &lt;p&gt;这句话有两种含义。第一，警察用刀刺死了一个人。
第二，警察杀了一个拿着刀的人。根据我们的常识，我们倾向于相信第二种是真的。
句子的含义取决于词的依存结构。这也是人类语言与编程语言非常不同的原因。&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;Catalan 数： ${ C_n = (2n)! / [(n+1)!n!]}$&lt;/li&gt;
    &lt;li&gt;一个指数增长的序列，出现在许多树状上下文中。CS228: Prob. Graphic Model.&lt;/li&gt;
  &lt;/ul&gt;

  &lt;h3 id=&quot;section-3&quot;&gt;并列范围歧义&lt;/h3&gt;

  &lt;p&gt;&lt;em&gt;Shuttle veteran and longtime NASA executive Fred Gregory appointed to board.&lt;/em&gt;&lt;/p&gt;

  &lt;p&gt;要么 Fred Gregory 既是航天飞机老兵又是长期 NASA 高管，要么是两个人，一个是航天飞机老兵，另一个是 NASA 高管。&lt;/p&gt;

  &lt;h3 id=&quot;section-4&quot;&gt;形容词修饰歧义&lt;/h3&gt;
  &lt;p&gt;这是研究生课程，所以教授在讲座中真的展示了这个例子！！！&lt;/p&gt;

  &lt;p&gt;&lt;em&gt;Student get first hand job experience&lt;/em&gt;&lt;/p&gt;

  &lt;h3 id=&quot;vp-&quot;&gt;动词短语 (VP) 附着歧义&lt;/h3&gt;

  &lt;h3 id=&quot;universal-dependencies-&quot;&gt;标注数据的兴起，Universal Dependencies 树库&lt;/h3&gt;
  &lt;p&gt;构建树库看起来比构建语法慢得多且用处更小。但树库有以下优点：&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;劳动的可重用性
      &lt;ul&gt;
        &lt;li&gt;可以在其上构建许多解析器、词性标注器等&lt;/li&gt;
        &lt;li&gt;对语言学来说是宝贵资源&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;覆盖面广，不仅仅是一些直觉&lt;/li&gt;
    &lt;li&gt;频率和分布信息&lt;/li&gt;
    &lt;li&gt;一种评估系统的方法&lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;依存可以交叉：
&lt;strong&gt;I will give a talk tomorrow on bootstrapping&lt;/strong&gt;&lt;/p&gt;

  &lt;p&gt;&lt;em&gt;on bootstrapping&lt;/em&gt; 跨越了 talk。&lt;/p&gt;

  &lt;h2 id=&quot;lecture-7--rnn&quot;&gt;Lecture 7, 梯度消失，花式 RNN&lt;/h2&gt;

  &lt;p&gt;主题：梯度消失（爆炸）、LSTM、GRU、双向、多层&lt;/p&gt;

&lt;/div&gt;
</description>
        <pubDate>Wed, 03 Apr 2019 00:00:00 +0000</pubDate>
        <link>https://jazzikp.github.io/2019/04/03/CS224n/</link>
        <guid isPermaLink="true">https://jazzikp.github.io/2019/04/03/CS224n/</guid>
        
        <category>NLP</category>
        
        <category>CS224n</category>
        
        <category>Stanford</category>
        
        
      </item>
    
      <item>
        <title>Python for Absolute Newbies</title>
        <description>
&lt;div data-lang-panel=&quot;en&quot;&gt;

  &lt;p&gt;这篇文章会从安装 Python3.7 开始讲起。
接下来的主题会渐渐集中讲一些 Data Science 的一些应用和建模方法。
使用 macOS Mojave.&lt;/p&gt;

  &lt;h2 id=&quot;zen-of-python&quot;&gt;Zen of Python&lt;/h2&gt;

  &lt;p&gt;Python was designed as a successor to the &lt;a href=&quot;https://en.wikipedia.org/wiki/ABC_(programming_language)&quot;&gt;ABC language&lt;/a&gt; by
&lt;a href=&quot;https://en.wikipedia.org/wiki/Guido_van_Rossum&quot;&gt;Guido van Rossum&lt;/a&gt;. 
Python was initially developed as a “hobby” project that would keep him occuiped during the Christmas break! I mean who would spend their Christmas writing an &lt;a href=&quot;https://en.wikipedia.org/wiki/Interpreter_(computing)&quot;&gt;interpreter&lt;/a&gt;!&lt;/p&gt;

  &lt;p&gt;Eventually, Python become one of the top used programming language today. Python can be used for all kinds of projects from small personal project to large application, and from web development to scientific computation. Here are some famous projects used Python, according to this &lt;a href=&quot;https://www.hartmannsoftware.com/Blog/Articles_from_Software_Fans/Most-Famous-Software-Programs-Written-in-Python&quot;&gt;article&lt;/a&gt;&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;YouTube&lt;/li&gt;
    &lt;li&gt;Google search engine also used Python for its mainframe&lt;/li&gt;
    &lt;li&gt;Instagram&lt;/li&gt;
    &lt;li&gt;Reddit&lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;Let’s start this tutorial with the Zen of Python:&lt;/p&gt;

  &lt;p&gt;Type this into the Mac terminal.&lt;/p&gt;

  &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;python
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; import this
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;

  &lt;h2 id=&quot;install-python37&quot;&gt;Install Python3.7&lt;/h2&gt;

  &lt;p&gt;Let’s see how to install Python3.6. Mac come with default python2.7 installed. We want to use Python3.7 here.&lt;/p&gt;

  &lt;p&gt;I found the easiest way here is to install through &lt;a href=&quot;https://repo.anaconda.com/archive/Anaconda3-2018.12-MacOSX-x86_64.pkg&quot;&gt;Anaconda Distribution&lt;/a&gt; (Click this link to download directly!)&lt;/p&gt;

  &lt;p&gt;You can also install through &lt;a href=&quot;https://www.python.org/&quot;&gt;python.org&lt;/a&gt;.&lt;/p&gt;

  &lt;p&gt;Please also install VS Code come with Anaconda. I will talk about Visual Studio Code later.
If you installed through python.org, please also install Visual Studio Code separately.&lt;/p&gt;

  &lt;p&gt;Once you have Python3.7 installed, you should be able to see this in terminal&lt;/p&gt;
  &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;~ » python3.7
Python 3.7.0 &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;v3.7.0:1bf9cc5093, Jun 26 2018, 23:26:24&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Clang 6.0 &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;clang-600.0.57&lt;span class=&quot;o&quot;&gt;)]&lt;/span&gt; on darwin
Type &lt;span class=&quot;s2&quot;&gt;&quot;help&quot;&lt;/span&gt;, &lt;span class=&quot;s2&quot;&gt;&quot;copyright&quot;&lt;/span&gt;, &lt;span class=&quot;s2&quot;&gt;&quot;credits&quot;&lt;/span&gt; or &lt;span class=&quot;s2&quot;&gt;&quot;license&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;more information.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;

  &lt;p&gt;Congrats! You know have the latest version of Python ready!&lt;/p&gt;

  &lt;h2 id=&quot;virtual-environment&quot;&gt;Virtual Environment&lt;/h2&gt;

  &lt;p&gt;Do use a virtual environment, PLEASE!!! Virtual environment allow you to separate your developing environment without interfering with the system.&lt;/p&gt;

  &lt;p&gt;I want you to have the best programming habit from the beginning. I learned this the hard way.&lt;/p&gt;

  &lt;p&gt;Lets try to setup virtual environment using virtualenv.&lt;/p&gt;

  &lt;p&gt;First go to home directory and create a directory Environment to store all your virtual environments&lt;/p&gt;
  &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;~ » &lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; ~
~ » &lt;span class=&quot;nb&quot;&gt;mkdir &lt;/span&gt;Environment
~ » &lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;Environment
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;

  &lt;p&gt;Then install virtualenv and create a new virtual environment named it python_tutorial&lt;/p&gt;

  &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;~ » pip &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;virtualenv
~ » virtualenv ~/Environment/python_tutorial
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;

  &lt;p&gt;Activate python_tutorial. Now type which python3. It will return the path to the virtual environment.&lt;/p&gt;

  &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;~ » &lt;span class=&quot;nb&quot;&gt;source&lt;/span&gt; ~/Environment/python_tutorial/bin/activate
~ » which python3
/Users/zhejianpeng/Environment/python_tutorial/bin/python3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;

  &lt;p&gt;Deactivate python_tutorial environment&lt;/p&gt;

  &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;~ » deactivate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;

  &lt;p&gt;Now every time before you start coding, remember to activate the virtual environment. It’s recommended to create a short alias of the activation command and add it to ~/.bash_profile. You don’t have to type the long version.&lt;/p&gt;

  &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;~ » code ~/.bash_profile
&lt;span class=&quot;c&quot;&gt;# User Define Alias, add following line to your bash_profile&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;alias &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;tutorial&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;source ~/Environment/python_tutorial/bin/activate&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;

  &lt;p&gt;Here you need to install code command. It’s very easy. Open VS code you installed previously, and type &lt;kbd&gt;shift&lt;/kbd&gt; + &lt;kbd&gt;command&lt;/kbd&gt; + &lt;kbd&gt;p&lt;/kbd&gt;. Then type &lt;img src=&quot;/img/vscode-command-palette.webp&quot; alt=&quot;The VS Code command palette&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;820&quot; height=&quot;116&quot; /&gt;&lt;/p&gt;

  &lt;p&gt;Now you have the basic environment setup!!! Let’s start coding&lt;/p&gt;

  &lt;h2 id=&quot;hello-world&quot;&gt;Hello World&lt;/h2&gt;

&lt;/div&gt;

&lt;div data-lang-panel=&quot;zh&quot; hidden=&quot;&quot;&gt;

  &lt;p&gt;这篇文章会从安装 Python3.7 开始讲起。
接下来的主题会渐渐集中讲一些 Data Science 的一些应用和建模方法。
使用 macOS Mojave.&lt;/p&gt;

  &lt;h2 id=&quot;python-&quot;&gt;Python 之禅&lt;/h2&gt;

  &lt;p&gt;Python 被设计为 &lt;a href=&quot;https://en.wikipedia.org/wiki/ABC_(programming_language)&quot;&gt;ABC 语言&lt;/a&gt; 的继任者，由 &lt;a href=&quot;https://en.wikipedia.org/wiki/Guido_van_Rossum&quot;&gt;Guido van Rossum&lt;/a&gt; 开发。
Python 最初是作为一个“业余”项目开发的，好让他在圣诞假期有事可做！谁会在圣诞节写一个 &lt;a href=&quot;https://en.wikipedia.org/wiki/Interpreter_(computing)&quot;&gt;解释器&lt;/a&gt; 啊！&lt;/p&gt;

  &lt;p&gt;最终，Python 成为当今使用最广泛的编程语言之一。Python 可用于各种项目，从小个人项目到大型应用，从 Web 开发到科学计算。根据这篇 &lt;a href=&quot;https://www.hartmannsoftware.com/Blog/Articles_from_Software_Fans/Most-Famous-Software-Programs-Written-in-Python&quot;&gt;文章&lt;/a&gt;，以下是一些使用 Python 的著名项目：&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;YouTube&lt;/li&gt;
    &lt;li&gt;Google 搜索引擎的主机也使用了 Python&lt;/li&gt;
    &lt;li&gt;Instagram&lt;/li&gt;
    &lt;li&gt;Reddit&lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;我们从 Python 之禅开始这个教程：&lt;/p&gt;

  &lt;p&gt;在 Mac 终端中输入以下内容。&lt;/p&gt;

  &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;python
&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; import this
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;

  &lt;h2 id=&quot;python37&quot;&gt;安装 Python3.7&lt;/h2&gt;

  &lt;p&gt;看看如何安装 Python3.6。Mac 默认安装了 python2.7。我们这里要用 Python3.7。&lt;/p&gt;

  &lt;p&gt;我发现最简单的方法是通过 &lt;a href=&quot;https://repo.anaconda.com/archive/Anaconda3-2018.12-MacOSX-x86_64.pkg&quot;&gt;Anaconda Distribution&lt;/a&gt; 安装（点击此链接直接下载！）&lt;/p&gt;

  &lt;p&gt;也可以通过 &lt;a href=&quot;https://www.python.org/&quot;&gt;python.org&lt;/a&gt; 安装。&lt;/p&gt;

  &lt;p&gt;请同时安装 Anaconda 自带的 VS Code。我稍后会讲 Visual Studio Code。
如果通过 python.org 安装，请单独安装 Visual Studio Code。&lt;/p&gt;

  &lt;p&gt;安装好 Python3.7 后，终端里应该能看到这个&lt;/p&gt;
  &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;~ » python3.7
Python 3.7.0 &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;v3.7.0:1bf9cc5093, Jun 26 2018, 23:26:24&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;Clang 6.0 &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;clang-600.0.57&lt;span class=&quot;o&quot;&gt;)]&lt;/span&gt; on darwin
Type &lt;span class=&quot;s2&quot;&gt;&quot;help&quot;&lt;/span&gt;, &lt;span class=&quot;s2&quot;&gt;&quot;copyright&quot;&lt;/span&gt;, &lt;span class=&quot;s2&quot;&gt;&quot;credits&quot;&lt;/span&gt; or &lt;span class=&quot;s2&quot;&gt;&quot;license&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;more information.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;

  &lt;p&gt;恭喜！你现在已经准备好最新版本的 Python 了！&lt;/p&gt;

  &lt;h2 id=&quot;section&quot;&gt;虚拟环境&lt;/h2&gt;

  &lt;p&gt;一定要用虚拟环境，拜托！！！虚拟环境可以隔离你的开发环境，不会干扰系统。&lt;/p&gt;

  &lt;p&gt;我希望你从一开始就养成最好的编程习惯。这是我吃过亏才学到的。&lt;/p&gt;

  &lt;p&gt;我们用 virtualenv 来设置虚拟环境。&lt;/p&gt;

  &lt;p&gt;首先进入主目录，创建一个 Environment 目录来存放所有虚拟环境&lt;/p&gt;
  &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;~ » &lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; ~
~ » &lt;span class=&quot;nb&quot;&gt;mkdir &lt;/span&gt;Environment
~ » &lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;Environment
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;

  &lt;p&gt;然后安装 virtualenv 并创建一个名为 python_tutorial 的新虚拟环境&lt;/p&gt;

  &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;~ » pip &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;virtualenv
~ » virtualenv ~/Environment/python_tutorial
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;

  &lt;p&gt;激活 python_tutorial。现在输入 which python3。它会返回虚拟环境的路径。&lt;/p&gt;

  &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;~ » &lt;span class=&quot;nb&quot;&gt;source&lt;/span&gt; ~/Environment/python_tutorial/bin/activate
~ » which python3
/Users/zhejianpeng/Environment/python_tutorial/bin/python3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;

  &lt;p&gt;停用 python_tutorial 环境&lt;/p&gt;

  &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;~ » deactivate
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;

  &lt;p&gt;现在每次开始编码前，记得激活虚拟环境。建议为激活命令创建一个短别名，并添加到 ~/.bash_profile。这样就不用输入长命令了。&lt;/p&gt;

  &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;~ » code ~/.bash_profile
&lt;span class=&quot;c&quot;&gt;# User Define Alias, add following line to your bash_profile&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;alias &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;tutorial&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;source ~/Environment/python_tutorial/bin/activate&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;  &lt;/div&gt;

  &lt;p&gt;这里需要安装 code 命令。很简单。打开之前安装的 VS Code，输入 &lt;kbd&gt;shift&lt;/kbd&gt; + &lt;kbd&gt;command&lt;/kbd&gt; + &lt;kbd&gt;p&lt;/kbd&gt;。然后输入 &lt;img src=&quot;/img/vscode-command-palette.webp&quot; alt=&quot;VS Code 命令面板&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; width=&quot;820&quot; height=&quot;116&quot; /&gt;&lt;/p&gt;

  &lt;p&gt;现在基本环境已经设置好了！！！开始编码吧&lt;/p&gt;

  &lt;h2 id=&quot;hello-world-1&quot;&gt;Hello World&lt;/h2&gt;

&lt;/div&gt;
</description>
        <pubDate>Wed, 03 Apr 2019 00:00:00 +0000</pubDate>
        <link>https://jazzikp.github.io/2019/04/03/Python_For_Absolute_Newbies/</link>
        <guid isPermaLink="true">https://jazzikp.github.io/2019/04/03/Python_For_Absolute_Newbies/</guid>
        
        <category>Python</category>
        
        <category>Data Science</category>
        
        
      </item>
    
  </channel>
</rss>
