Qwen3-ASR 课程 · 第 10 课 · 路线第 5 站 · 形状篇

维数全景 —— 每一站的数字从哪来、是什么意思

⏱ 约 35 分钟 · 前置:向量流水线 + 推理全过程 · 本课目标:把全链路每个阶段的张量形状、每条轴、每个数字的含义一次理清——并讲透「投影器」的数学本质

你问到了最值得问的问题:每一站数字在变,变的到底是什么?这节课给你三样东西:① 一条贯穿全链路的「形状总线」;② 每个数字的含义标注;③ 「投影器」的数学本质(它比你想的简单,而且 0.6B 里它有个出人意料的细节)。全部用 0.6B 的真实 config 数值,10 秒音频贯穿。

一、先立框架:所有张量只有两条轴

全链路的每个张量,形状都写成 (A, B) 或 (A,),两条轴含义完全不同:

轴名字含义谁决定它
轴 0时间轴(序列长)「有多少个位置/帧/词元」——随你的音频时长变你(音频长度 × 采样参数)
轴 1特征轴(宽度)「每个位置用多少个数描述」——架构定死,与音频长短无关模型设计者(超参数)
两条轴上的「维」意义完全不同 时间轴的每一格有天然含义(第 i 帧就是第 i 个 10ms;第 j 个嵌入就是第 j 个 80ms)。特征轴的每一维大多没有名字——1024 维隐藏向量里的第 7 个数不代表任何人类概念;有意义的是方向与组合(两个向量接近 = 含义接近)。分清这两类,就不会问「第 37 维是什么意思」这种模型自己也答不上的问题。

二、全链路形状总线(10 秒音频,0.6B 真实值)

站变换输出形状每个数字的含义
采样16kHz × 10s(160,000,)每个数 = 某一 1/16000 秒时刻的声压
mel 前端25ms 窗/10ms 移 + 128 带 mel 滤波(998, 128)轴0:第 i 帧(覆盖第 i 个 25ms);轴1:第 k 个 mel 频带的能量——这条特征轴的每一维都有物理含义
AuT 编码器18 层注意力 + 8× 下采样(124, 896)轴0:124 个 80ms 片段;轴1:编码器学习空间的 896 个方向(从这站起,特征维没有人类名字了)
输出投影编码器内 896 → 1024(124, 1024)对齐 LLM 坐标系
+ 模板拼入 system/热词/语言前缀词元(≈150, 1024)124 行来自投影器,其余行来自词嵌入查表——两路来源,同一宽度
LLM 28 层注意力 + FFN ×28(≈150, 1024)形状完全不变!变的只是每行的内容:从「字面嵌入」变成「吸收了全部上下文的语义表示」
输出头末行 × 词表矩阵(151,936,)每个数 = 对应词元的得分(logits)
看形状变化的三个视角 ① 时间轴只缩不涨:160,000 → 998 → 124(两次压缩:频率分解 + 时间合并);② 特征轴阶梯上行:1 → 128(物理量)→ 896 → 1024(学习空间);③ LLM 内部形状冻结——28 层反复改写 (150,1024) 的内容而不改形状,最后只在输出头一次性展开到词表。

三、跟着 80ms 旅行:一个向量的完整一生

抽象的形状看累了?跟着一段具体的 80ms 声音(比如「明」字的声母开头)从头走一遍:

阶段它变成了什么这一步的意义
采样1,280 个声压值时间的原始切片
mel8 个帧向量,每个 128 维「这 80ms 里,每个频带各有多少能量」——有了频率视角
AuT 编码1 个 896 维向量这 8 帧被融合成 1 个「声音片段表示」;与前后片段交换过信息(注意力)
投影器1 个 1024 维向量翻译成语言模型认识的坐标
LLM 28 层1 个 1024 维向量(内容不断被改写)每过一层,它就多吸收一点上下文;最后它「知道自己支持的是『明』的声母」
输出头参与「明」位置的 logits 打分声音的旅程终点:变成一个确定的汉字

同一个 80ms,在六站里有六种「身份」:声压切片 → 频带能量 → 声音片段 → LLM 坐标 → 语义表示 → 打分依据。形状变化的本质,就是身份的层层翻译。

四、投影器的数学本质:换坐标系,不是变魔术

投影器做的唯一一件事,数学上叫线性映射(仿射变换):

y = x @ W + b

两个关键事实(都能用线性代数严格证明,这里给直觉):

① 方阵投影(1024→1024)不丢信息,只换坐标系。0.6B 的投影器恰好就是 1024→1024——形状没变,为什么还需要它?因为编码器和 LLM 是各自独立训练的,两套 1024 维空间的坐标系完全不同(同一个方向,在两边含义风马牛不相及)。投影器 = 一本翻译字典:可逆、无损,但没有它两边互相听不懂。

② 非方阵(896→1024)是升维,可能丢也可能不丢。丢不丢取决于秩:只要 W 列满秩,任何信息都还能找回(线性可逆);真正丢信息的是「降秩」映射。AuT 内部的 8× 下采样(998→124)才是真正有意的压缩——它赌的是「80ms 内 8 帧的信息可以融合」。

import numpy as np rng = np.random.default_rng(0) x = np.array([1.0, 0.5, -0.3]) # 一个 3 维输入(玩具版 1024 维) # 事实 1:方阵 = 换坐标系,信息无损 W = rng.normal(size=(3, 3)) + np.eye(3) * 2 # 保证可逆 y = x @ W x_back = y @ np.linalg.inv(W) print("方阵: x =", x, "-> y =", y.round(3), "-> 换回 =", x_back.round(3), "(分毫不差)") # 事实 2:非方阵 3->2 = 压缩,回不去 W2 = rng.normal(size=(3, 2)) y2 = x @ W2 print("非方阵: x =", x, "-> y =", y2.round(3), "(3 维 -> 2 维,原有信息已不可完整恢复)") # 事实 3:W 的每一列 = 输出坐标轴的配方 print("\nW 的第 0 列", W[:, 0].round(2), "= 输出第 0 维的混合配方:输出0 = 输入·该列")
AU T 内部的下采样 vs 投影器:别混 两者都改形状,但职责完全不同:下采样动时间轴(8 帧并 1,序列变短——省计算);投影器动特征轴(换坐标系——对齐空间)。记住口诀:时间归下采样,坐标系归投影器。

五、形状追踪器(可运行)

改音频时长,看全链路形状怎么联动——注意变的全在时间轴,特征轴纹丝不动:

def shapes(duration_s=10, fs=16000, win=400, hop=160, n_mels=128, enc_hidden=896, llm_hidden=1024, vocab=151936): samples = round(fs * duration_s) frames = 1 + (samples - win) // hop # 25ms 窗 / 10ms 移 embeds = frames // 8 # AuT 8 倍下采样 seq = embeds + 26 # + 模板/前缀词元(近似) print(f"{duration_s:>3}s: 采样({samples:>7,},) -> mel({frames}, {n_mels})" f" -> 嵌入({embeds}, {enc_hidden}) -> 对齐({embeds}, {llm_hidden})" f" -> LLM({seq}, {llm_hidden}) -> logits({seq}, {vocab})") for d in [3, 10, 30, 120]: shapes(d) print("\n观察:无论多长的音频,特征轴(第二维)永远不变;变的是时间轴(第一维)。")
checkpoint 现在再读一遍这条链,每个数字你都该能说出「它是谁」:(160,000,) 声压 → (998,128) 频带能量 → (124,896) 声音片段 → (124,1024) LLM 坐标 → (150,1024) 融入上下文 → (151,936,) 词表打分。哪一站说不出,回到对应小节再看一遍——这一课就是为「卡在形状」准备的。

检索练习

本课主读材料

去读(约 15 分钟)

Jay Alammar: The Illustrated Transformer(jalammar.github.io)——重读一遍,这次带着本课的「形状眼镜」:每张图问自己一句「这步的输入输出形状是什么、哪条轴变了」。

进阶可选:The Annotated Transformer(Harvard,逐行 PyTorch 实现原文)——线代基础够用,想看"形状在代码里怎么流动"时再读。

本课新术语已入 术语表:线性映射/仿射变换、张量形状(时间轴 × 特征轴)。

我是你的老师,别客气 如果某一站的形状你还是说不出「它是谁」,告诉我是哪一站——我可以为单独那一站再做一页展开(比如「为什么 mel 是 128 维」「896 这个奇怪数字是怎么选的」)。