如何在 CSP 考场上使用 AI 辅助编程?
_colin1112_
·
·
休闲·娱乐
教程续集已出:如何在 CSP 考场上训练 AI 并辅助编程?
众所周知,CSP 考场是断网环境。理论上是无法使用 AI 的。
然而,我们充分发扬人类智慧,既然 AI 是由固定的机器进行运算,从而进行输出的,那我们为何不脱离机器,由人脑直接模拟 AI 呢?
那么,理论成立,实践开始。
:::warning[声明]
- 本文章非常显然的,大量依赖于 AI,但是文章大致是没有问题的。
- 文章纯属整活,请不要在正式考试时尝试使用该方法。
:::
:::info[信息]
受笔者电脑限制,本文章中的模型使用 Qwen-0.6b,思考模式关闭。
:::
一. 背诵完整模型信息
这是最简单,也是最无脑的一步。
以 Qwen3-0.6b 为例(显然的,赛场上不可能用这么低智的模型):
- 背诵完整权重
| 部分 |
形状 |
个数 |
| 词嵌入表(同时兼作输出层,共用一份) |
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 |
- 背诵架构
- 背诵分词器
- 256 个单字节 token:任何文本拆到字节,都能用它们表示。
- 151387 条合并规则:规定哪两段先合并、哪两段后合并,顺序也得背。比如题目里的
a+b,就被切成了 ' a' 和 '+b' 两个 token。
- 由此得到 151643 个普通 token(256 + 151387)。
- 26 个特殊 token,比如
<|im_start|>、<|im_end|>、<think>。
- 对话模板:题目要先套进这个格式,模型才知道哪段是用户说的、该从哪里开始回答。
至此,准备工作全部完成。你的大脑已经是一台没有显卡的 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。
分词分两步:
- 预切分:先按固定规则把文本切成小段。比如
a+b 会被切成 a 和 +b,因为一串字母前面允许带一个非字母字符。
- 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!!!!
------------------
看什么看,快去算你的紫题啊。