如何在 CSP 考场上使用 AI 辅助编程?

· · 休闲·娱乐

教程续集已出:如何在 CSP 考场上训练 AI 并辅助编程?

众所周知,CSP 考场是断网环境。理论上是无法使用 AI 的。

然而,我们充分发扬人类智慧,既然 AI 是由固定的机器进行运算,从而进行输出的,那我们为何不脱离机器,由人脑直接模拟 AI 呢?

那么,理论成立,实践开始。

:::warning[声明]

  1. 本文章非常显然的,大量依赖于 AI,但是文章大致是没有问题的。
  2. 文章纯属整活,请不要在正式考试时尝试使用该方法。

:::

:::info[信息] 受笔者电脑限制,本文章中的模型使用 Qwen-0.6b,思考模式关闭。

:::

一. 背诵完整模型信息

这是最简单,也是最无脑的一步。

以 Qwen3-0.6b 为例(显然的,赛场上不可能用这么低智的模型):

  1. 背诵完整权重
部分 形状 个数
词嵌入表(同时兼作输出层,共用一份) 151936 × 1024 155582464
每一层(共 28 层) 15730944
注意力前的 RMSNorm 1024 1024
Wq(16 个头 × 128) 1024 × 2048 2097152
Wk(8 个头 × 128) 1024 × 1024 1048576
Wv(8 个头 × 128) 1024 × 1024 1048576
Wo 2048 × 1024 2097152
q_norm、k_norm 128 + 128 256
MLP 前的 RMSNorm 1024 1024
W_gate 1024 × 3072 3145728
W_up 1024 × 3072 3145728
W_down 3072 × 1024 3145728
28 层合计 440466432
最终 RMSNorm 1024 1024
总计 596049920
  1. 背诵架构

  1. 背诵分词器

至此,准备工作全部完成。你的大脑已经是一台没有显卡的 Qwen3-0.6B 了。

二. 读题,写提示词

开考,打开 T1。不妨假设它是 A+B Problem。

在草稿纸上写好提示词,套进对话模板:

<|im_start|>user
请用 C++ 解决下面这道题,只输出代码。
题目:输入两个整数 a 和 b(|a|,|b| ≤ 10^9),输出 a+b 的值。
输入样例:20 30
输出样例:50<|im_end|>
<|im_start|>assistant
<think>

</think>

三. 分词

模型不认字,只认编号。所以第一步,是把上面这段话切成 token。

分词分两步:

  1. 预切分:先按固定规则把文本切成小段。比如 a+b 会被切成 a 和 +b,因为一串字母前面允许带一个非字母字符。
  2. BPE 合并:每一小段先拆成字节,然后翻出背过的合并规则表,排名越靠前的规则越先用,能合就合,直到合不动为止。

以 a+b 为例:

小段 拆成字节 用到的合并规则 结果
a Ġ a 第 9 条:Ġ a(Ġ 代表空格) ' a',id 264
+b + b 第 34838 条:+ b '+b',id 35093

汉字也是一样的道理:「输」「出」各自先由 3 个字节合成一个字,再用第 65762 条规则合成「输出」这一个 token(id 66017)。

整段提示词最终切成 73 个 token:

位置 id token 位置 id token 位置 id token
0 151644 '<|im_start|>' 1 872 'user' 2 198 '\n'
3 14880 '请' 4 11622 '用' 5 356 ' C'
6 1027 '++' 7 85122 ' 解' 8 99351 '决'
9 100431 '下面' 10 43288 '这' 11 44793 '道'
12 33872 '题' 13 3837 ',' 14 91680 '只'
15 66017 '输出' 16 46100 '代码' 17 8997 '。\n'
18 106367 '题目' 19 5122 ':' 20 31196 '输入'
21 100369 '两个' 22 63431 '整' 23 8863 '数'
24 264 ' a' 25 58143 ' 和' 26 293 ' b'
27 9909 '(' 28 91 '\|' 29 64 'a'
30 91 '\|' 31 11 ',' 32 91 '\|'
33 65 'b' 34 91 '\|' 35 37294 ' ≤'
36 220 ' ' 37 16 '1' 38 15 '0'
39 61 '^' 40 24 '9' 41 48272 '),'
42 66017 '输出' 43 264 ' a' 44 35093 '+b'
45 43589 ' 的' 46 25511 '值' 47 8997 '。\n'
48 31196 '输入' 49 90885 '样' 50 26355 '例'
51 5122 ':' 52 17 '2' 53 15 '0'
54 220 ' ' 55 18 '3' 56 15 '0'
57 198 '\n' 58 66017 '输出' 59 90885 '样'
60 26355 '例' 61 5122 ':' 62 20 '5'
63 15 '0' 64 151645 '<|im_end|>' 65 198 '\n'
66 151644 '<|im_start|>' 67 77091 'assistant' 68 198 '\n'
69 151667 '<think>' 70 271 '\n\n' 71 151668 '</think>'
72 271 '\n\n'

注意第 43、44 位:题目里的 a+b 被切成了 ' a' 和 '+b',加号和 b 粘在了一起。

而等会儿模型自己写代码时,' +' 是单独的一个 token(id 488)。

四. 推理

大模型是 一个 token 一个 token 往外蹦的:每生成一个 token,都要把整个模型完整跑一遍,然后把新 token 接到末尾,再跑一遍。

整道题一共要生成 47 个 token。全写出来略有难度,所以我们随便挑一步完整跑一遍:

模型已经写到 std::cout << a,下一个 token 该写什么?

#include <iostream>

int main() {
    int a, b;
    std::cin >> a >> b;
    std::cout << a█

此时输入 token 是 ' a',位于第 104 位(前面 73 个提示词 + 已生成的 31 个 token)。

1. 查表

' a' 的 id 是 264,到背过的词嵌入表里取出第 264 行,就是它的初始向量 h:

h = E[264] = [-0.02441,\ 0.02454,\ -0.0603,\ -0.002625,\ 0.006042,\ \dots]\quad(\text{共 } 1024 \text{ 维})

2. 第 0 层

每一层都做两件事:注意力 和 MLP。每件事之前先做一次 RMSNorm,做完把结果加回 h。

① RMSNorm:把向量缩放到统一尺度

\begin{aligned} \textstyle\sum x_i^2 &= (-0.02441)^2 + 0.02454^2 + \cdots + 0.03345^2 = 0.675059 \\ \mathrm{rms} &= \sqrt{0.675059 / 1024 + 10^{-6}} = 0.0256951 \\ y_0 &= \frac{-0.02441}{0.0257} \times 0.1357 = -0.129 \end{aligned}

1024 维都这么算一遍。

② 投影出 Q、K、V(以第 3 个头为例)

q_0 = \sum_i y_i\, W_Q[384, i] = (-0.129)(-0.01123) + 0.6751 \times 0.0009575 + (-1.384)(-0.04248) + \cdots = 0.8037 **③ RoPE:按位置旋转,让模型知道谁在前谁在后** 当前位置 $p = 104$,第 $j = 0$ 对维度的旋转角 $\varphi = p \cdot \theta^{-j/64} = 104$(弧度): $$ \begin{aligned} q'_0 &= \hat q_0 \cos\varphi - \hat q_{64} \sin\varphi = 13.78 \times (-0.9469) - 0.3321 \times (-0.3216) = -12.94 \\ q'_{64} &= \hat q_{64} \cos\varphi + \hat q_0 \sin\varphi = 0.3321 \times (-0.9469) + 13.78 \times (-0.3216) = -4.745 \end{aligned} $$ 记得把计算器调到弧度制。 **④ 注意力打分:给前面每个 token 打一个分** $$ s(104) = \frac{q' \cdot k'_{104}}{\sqrt{128}} = \frac{(-12.94)(-1.61) + 0.4983 \times 1.207 + \cdots}{11.3137} = 44.58 $$ 对位置 0 到 104 共 105 个位置都打一遍分,再做 softmax,得分最高的三个位置是: | 位置 | token | 打分 | 权重 | |---|---|---|---| | 104 | `' a'` | 44.5767 | 98.29% | | 103 | `' <<'` | 40.2828 | 1.34% | | 66 | `'<\|im_start\|>'` | 38.7625 | 0.29% | 第 0 层的这个头几乎只看自己,还处在「认字」阶段。 **⑤ 汇总 V,输出投影,加回残差** $$ \begin{aligned} o_0 &= 0.9829 \times 0.02077 + 0.01342 \times (-0.04681) + \cdots = 0.0202 \\ \mathrm{attn}_0 &= \sum_j o_j\, W_O[0, j] = -0.3562 \\ h_0 &\leftarrow -0.02441 + (-0.3562) = -0.3806 \end{aligned} $$ **⑥ MLP(SwiGLU)** 再做一次 RMSNorm,然后中间层的 3072 个神经元各算一遍。以这一层最活跃的第 1376 号神经元为例: $$ \begin{aligned} g_{1376} &= 1.362, \quad u_{1376} = 1.982 \\ \mathrm{SiLU}(g) &= \frac{1.362}{1 + e^{-1.362}} = \frac{1.362}{1.2561} = 1.085 \\ \mathrm{act}_{1376} &= 1.085 \times 1.982 = 2.15 \end{aligned} $$ 3072 个神经元都算完,乘上 $W_\text{down}$,加回 $h$: $$ h_0 \leftarrow -0.3806 + (-0.146) = -0.5265 $$ 至此,第 0 层结束。是不是非常简单? #### 3. 剩下的 27 层 流程完全一样,只是权重不同。每层的关键信息如下: - **注意力最关注**:除位置 0 以外,16 个头里注意力最集中的那个 token。位置 0 是模型堆放「不需要的注意力」的地方,不计入。 - **回看题目**:16 个头对题目正文注意力最集中的那个 token。 - **此刻最想说**:把这一层的结果直接接到输出层,看看模型此刻最想写什么(logit lens)。 | 层 | 注意力最关注 | 回看题目 | 此刻最想说 | `' +'` 排名 | |---|---|---|---|---| | 0 | `' a'` 98% | `':'` 7% | `' subject'` 8% | 48076 | | 1 | `' <<'` 85% | `'\n'` 4% | `'category'` 13% | 14635 | | 2 | `' a'` 91% | `' a'` 25% | `'category'` 18% | 9134 | | 3 | `' >>'` 44% | `'+b'` 5% | `'field'` 9% | 369 | | 4 | `' a'` 71% | `' a'` 11% | `'field'` 23% | 23 | | 5 | `' <<'` 28% | `'。\n'` 1% | `'play'` 9% | 18 | | 6 | `' a'` 71% | `' a'` 27% | `'file'` 8% | 8 | | 7 | `' <<'` 63% | `'+b'` 9% | `'value'` 25% | 14 | | 8 | `' a'` 88% | `' 的'` 20% | `'field'` 22% | 9 | | 9 | `' a'` 71% | `' a'` 10% | `'field'` 36% | 9 | | 10 | `' a'` 50% | `'2'` 4% | `'field'` 10% | 4 | | 11 | `' a'` 91% | `'+b'` 34% | `'value'` 20% | 4 | | 12 | `' a'` 29% | `'2'` 18% | `'value'` 61% | 3 | | 13 | `' <<'` 72% | `'+b'` 47% | `'value'` 46% | 2 | | 14 | `' a'` 96% | `' a'` 31% | `' +'` 64% | 1 | | 15 | `' <<'` 69% | `'+b'` 40% | `' +'` 88% | 1 | | 16 | `'+b'` 39% | `'+b'` 39% | `' +'` 95% | 1 | | 17 | `' <<'` 55% | `'+b'` 42% | `' +'` 100% | 1 | | 18 | `' <<'` 73% | `'+b'` 22% | `' +'` 100% | 1 | | 19 | `'+b'` 72% | `'+b'` 72% | `' +'` 100% | 1 | | 20 | `' <<'` 72% | `'+b'` 51% | `' +'` 100% | 1 | | 21 | `'+b'` 47% | `'+b'` 47% | `' +'` 100% | 1 | | 22 | `'+b'` 33% | `'+b'` 33% | `' +'` 100% | 1 | | 23 | `' <<'` 52% | `'+b'` 23% | `' +'` 100% | 1 | | 24 | `'cout'` 44% | `'+b'` 16% | `' +'` 100% | 1 | | 25 | `' a'` 18% | `'+b'` 7% | `' +'` 100% | 1 | | 26 | `' a'` 95% | `'+b'` 4% | `' +'` 100% | 1 | | 27 | `' a'` 14% | `'。\n'` 0% | `' +'` 100% | 1 | #### 4. 输出层 28 层算完,最后再做一次 RMSNorm,然后拿结果 $y$ 和词嵌入表的 **每一行** 做点积,给 151936 个 token 各打一个分(logit): $$ \begin{aligned} \mathrm{rms} &= \sqrt{135681 / 1024 + 10^{-6}} = 11.5109 \\ \mathrm{logit}(+) &= y \cdot E[488] = 2.369 \times 0.03809 + 27.39 \times 0.03491 + \cdots = 31.1220 \\ \mathrm{logit}(\lt\lt) &= y \cdot E[1115] = 2.369 \times 0.01819 + 27.39 \times (-0.004242) + \cdots = 23.5995 \\ \mathrm{logit}(-) &= y \cdot E[481] = 2.369 \times 0.03491 + 27.39 \times 0.04736 + \cdots = 15.4544 \end{aligned} $$ 再用 softmax 把分数变成概率: $$ p(t) = \frac{e^{\mathrm{logit}(t)}}{\sum_{t'} e^{\mathrm{logit}(t')}}, \qquad \ln \sum_{t'} e^{\mathrm{logit}(t')} = 31.1226 $$ $$ p(+) = e^{31.1220 - 31.1226} = 99.95\% $$ | 名次 | token | logit | 概率 | |---|---|---|---| | 1 | `' +'` | 31.1220 | 99.95% | | 2 | `' <<'` | 23.5995 | 0.054% | | 3 | `' >>'` | 17.9996 | 0.00020% | | 4 | `' +='` | 17.6070 | 0.00014% | | 5 | `'+b'` | 15.6283 | 0.000019% | 取概率最大的那个,下一个 token 就是 `' +'`。 ### 五. 重复 47 次 把 `' +'` 接到末尾,以它为输入再跑一遍,得到 `' b'`;再接上,再跑……直到模型输出 `<|im_end|>`,生成结束。 完整的 47 步如下(「第几层想好」:从这一层开始,第一名就是最终答案,之后不再改变): | 步 | 输入 | 输出 | 概率 | 第二名 | 第几层想好 | |---|---|---|---|---|---| | 0 | `'\n\n'` | ```` '```' ```` | 99.98% | ``` '``' ``` 0.01% | 18 | | 1 | ```` '```' ```` | `'cpp'` | 99.99% | `'c'` 0.01% | 22 | | 2 | `'cpp'` | `'\n'` | 100.00% | `'\n\n'` 0.00% | 19 | | 3 | `'\n'` | `'#include'` | 99.21% | `'int'` 0.63% | 23 | | 4 | `'#include'` | `' <'` | 99.96% | `'<iostream'` 0.03% | 23 | | 5 | `' <'` | `'iostream'` | 99.89% | `'bits'` 0.05% | 20 | | 6 | `'iostream'` | `'>\n\n'` | 50.83% | `'>\n'` 49.15% | 27 | | 7 | `'>\n\n'` | `'int'` | 99.21% | `'using'` 0.73% | 23 | | 8 | `'int'` | `' main'` | 100.00% | `' a'` 0.00% | 18 | | 9 | `' main'` | `'()'` | 99.99% | `'()\n'` 0.00% | 18 | | 10 | `'()'` | `' {\n'` | 100.00% | `' {'` 0.00% | 17 | | 11 | `' {\n'` | `' '` | 100.00% | `' '` 0.00% | 18 | | 12 | `' '` | `' int'` | 99.32% | `' std'` 0.56% | 20 | | 13 | `' int'` | `' a'` | 100.00% | `' x'` 0.00% | 24 | | 14 | `' a'` | `','` | 96.88% | `' ='` 3.10% | 21 | | 15 | `','` | `' b'` | 100.00% | `' sum'` 0.00% | 21 | | 16 | `' b'` | `';\n'` | 90.05% | `','` 9.93% | 20 | | 17 | `';\n'` | `' '` | 100.00% | `' \n'` 0.00% | 19 | | 18 | `' '` | `' std'` | 98.20% | `' int'` 1.05% | 22 | | 19 | `' std'` | `'::'` | 99.99% | `' ::'` 0.01% | 22 | | 20 | `'::'` | `'cin'` | 97.46% | `'cout'` 2.33% | 22 | | 21 | `'cin'` | `' >>'` | 99.72% | `'.tie'` 0.23% | 24 | | 22 | `' >>'` | `' a'` | 100.00% | `' std'` 0.00% | 25 | | 23 | `' a'` | `' >>'` | 99.99% | `' <<'` 0.01% | 23 | | 24 | `' >>'` | `' b'` | 100.00% | `' std'` 0.00% | 23 | | 25 | `' b'` | `';\n'` | 99.96% | `';'` 0.02% | 20 | | 26 | `';\n'` | `' '` | 100.00% | `' \n'` 0.00% | 19 | | 27 | `' '` | `' std'` | 95.27% | `' int'` 4.71% | 22 | | 28 | `' std'` | `'::'` | 100.00% | `' ::'` 0.00% | 21 | | 29 | `'::'` | `'cout'` | 99.96% | `'cin'` 0.03% | 20 | | 30 | `'cout'` | `' <<'` | 99.99% | `' >>'` 0.00% | 21 | | 31 | `' <<'` | `' a'` | 97.33% | `' ('` 2.53% | 26 | | 32 | `' a'` | `' +'` | 99.95% | `' <<'` 0.05% | 14 | | 33 | `' +'` | `' b'` | 100.00% | `' '` 0.00% | 22 | | 34 | `' b'` | `' <<'` | 99.37% | `';\n'` 0.63% | 23 | | 35 | `' <<'` | `' std'` | 99.30% | `" '\\"` 0.46% | 22 | | 36 | `' std'` | `'::'` | 100.00% | `' ::'` 0.00% | 21 | | 37 | `'::'` | `'endl'` | 99.90% | `' endl'` 0.09% | 20 | | 38 | `'endl'` | `';\n'` | 99.99% | `';'` 0.01% | 20 | | 39 | `';\n'` | `' '` | 99.14% | `'}\n'` 0.86% | 23 | | 40 | `' '` | `' return'` | 100.00% | `'\treturn'` 0.00% | 16 | | 41 | `' return'` | `' '` | 100.00% | `' ('` 0.00% | 19 | | 42 | `' '` | `'0'` | 100.00% | `'1'` 0.00% | 22 | | 43 | `'0'` | `';\n'` | 100.00% | `';'` 0.00% | 19 | | 44 | `';\n'` | `'}\n'` | 100.00% | `'}'` 0.00% | 21 | | 45 | `'}\n'` | ```` '```' ```` | 99.98% | ``` '``' ``` 0.02% | 21 | | 46 | ```` '```' ```` | `'<\|im_end\|>'` | 100.00% | `' �'` 0.00% | 27 | 最后,把 token 拼一起,就得到了最终的答案。我们 AC 了 A + B Problem!!!! ------------------ 看什么看,快去算你的紫题啊。