极化码能不能真正走向工程,关键不在于如何编码,而在于怎么把固定码长的编码结果塞进千变万化的传输资源里。这个适配过程就是速率匹配,而速率匹配里最绕不开的一环,就是打孔、缩短、删余这一整套操作。很多初学者一看到“准均匀打孔(QUP)”这个名词就头疼,其实把它拆开看,就是一个“怎么有章法地丢掉一部分比特”的问题。这篇文章我会从极化码的基本原理讲起,把打孔、缩短、删余的区别说透,再把QUP的设计思路和工程实现细节完整梳理一遍,最后附上我实际调仿真和协议栈时的踩坑记录。不管是做物理层算法的、写协议代码的,还是准备面试的,这篇文章都值得你花二十分钟看完。
1. 从一个工程问题说起:极化码怎么适配任意码长
极化码(Polar Code)从被5G NR采纳为控制信道编码方案那天起,就一直顶着“第一个被理论证明可达香农极限的编码”的光环。光环归光环,工程落地的时候,第一个难题就摆在眼前:信道极化天生只对2的整数次幂码长有效,也就是16、32、64、128、256、512、1024这种长度。可是实际无线通信里,物理资源块数量千变万化,控制信息的比特数也在动态变化,你不可能每次调度都刚好凑出一个2的幂次码长。
这时候就需要速率匹配(Rate Matching)。
1.1 极化码的工作原理简述
要理解速率匹配为什么重要,先得知道极化码在做什么。极化码的核心思想是“信道极化”:把N个独立的二进制输入信道,通过递归的“合并-分裂”操作,变成N个相互关联的虚拟子信道。这个过程会让一部分子信道的容量趋近于1,成为“好信道”;另一部分子信道的容量趋近于0,成为“坏信道”。
好消息信道用来传信息比特,坏信道全部填0,也就是冻结比特。编码的时候,信息比特和冻结比特组成一个长度为N的输入向量u,乘以生成矩阵G_N,得到码字x = u · G_N,x的长度也是N,这就是一个完整的极化码码字。
这里有个关键点:不同位置的子信道可靠性不一样。哪些位置放信息位、哪些位置放冻结位,是由子信道可靠性排序决定的。这个排序贯穿了整个速率匹配设计,后面讲QUP还会反复提到它。
1.2 速率匹配问题的本质
假设编码器生成了一个N位的码字,但物理层只能给你E个比特的资源位置去发送,这个E大于N或者小于N都有可能。速率匹配要做的事,就是从N个码字比特里,选出E个比特送出去,接收端再用某种方式把没送出去的N-E个比特“脑补”回来,然后送入一个N长度的译码器完成译码。
如果E大于N,说明资源比码字多,那就要重复发送一部分比特,这个操作叫重复(Repetition)。如果E小于N,说明资源不够,必须删掉N-E个比特,删的方式就是打孔或缩短。所以速率匹配本质上是一个映射函数:输入是N个码字比特,输出是E个待发送比特,同时要保证接收端能正确恢复出N个位置的软信息。
这个问题的难点在于:删掉哪些比特,对译码性能影响最小?随便删是不行的,因为每个码字比特在译码因子图里的“地位”完全不同。
1.3 三种手段的概览
速率匹配的三种基本手段——打孔(Puncturing)、缩短(Shortening)、重复(Repetition),名字看起来接近,实际上在接收端的处理方式天差地别。简单理解:
- 打孔:被删掉的比特不发送,接收端对它们的值是“完全未知”,译码时LLR初始化为0。
- 缩短:被删掉的比特虽然不发送,但接收端“事先知道”它们的值(通常是约定好的固定比特),LLR初始化为一个极大值。
- 重复:发送端把某些比特多发送一份或多份,接收端把多份信息合并,等效获得分集增益。
这三种手段的取舍,直接决定了整个编码方案的性能上限。接下来我一个个拆开讲。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 打孔、缩短、删余:看似相似,接收端待遇完全不同
很多人被“删余”这个词搞懵了。先澄清一下:在中文通信文献里,“删余”和“打孔”对应的其实是同一个英文单词Puncturing。“删余”这个叫法是从纠错码老文献里传下来的,最早用在卷积码和Turbo码里,意思是按规则删掉一部分编码输出比特。到了极化码时代,大家更习惯叫“打孔”,因为“打孔”这个动作描述更形象。你可以理解为一个叫法更老派,一个叫法更现代,说的是同一件事。
真正要区分的是打孔和缩短,这两个才是让你性能差出好几个dB的关键。
2.1 打孔:发送端不发送,接收端当完全未知
先看打孔。假设码长N=8,目标长度E=6,需要删掉2个码字比特。打孔的方式就是随便选2个位置不发送。接收端拿到6个位置的软信息后,把这6个值填进对应的译码器输入位置,剩下的2个打孔位置填0。
这里“填0”是什么意思?在LLR体系里,LLR=0表示P(bit=0) = P(bit=1),也就是完全等概率,没有携带任何信息。译码器拿到这些位置后,会认为“这两个位置的值不确定,但也不是完全不可能”,于是把这部分不确定性通过因子图传播到其他位置上。换句话说,打孔等于让译码器用“擦除”的方式处理缺失信息。
这样做的好处是灵活:打孔位置可以任意选,不需要事先约定具体值。坏处也明显:被打孔的比特在编码时本来是参与校验约束的,现在这个约束在接收端变成了不确定性,约束强度大幅削弱。如果打孔位置恰好对应了某个信息比特最关键的校验路径,哪怕只打孔很少的比特,也会让误块率急剧上升。
2.2 缩短:发送端不发送,接收端当事先已知
缩短的思路完全不同。缩短位置放的是“接收端已知的比特”。怎么做到已知?发送端在编码前就把这些位置对应的输入设为固定值,比如0。因为编码是线性的,码字里对应位置的值也是可以预先确定的。发送端删掉这些码字比特,接收端在对应位置不填0,而是填一个LLR幅值极大、符号表示确定值的信息。
这里的关键差异出现了:缩短位置的信息不是“等概率”,而是“已知确定值”,LLR初始化为一个很大的正值或负值,比如±100或±10000(具体看实现)。译码器拿到这个值后,会认为“这个位置就是1(或0),一点不确定性都没有”。这样一来,缩短不仅没有删掉约束,反而等于给译码器额外塞了一条确定性的先验信息。
所以缩短对性能的伤害通常远小于打孔。副作用是:缩短位置必须满足“固定值”约束,不能随便选位置,需要和冻结比特的分布配合。你不能把缩短位置选在必须携带信息比特的位置上,否则信息就全丢了。
2.3 打孔位置怎么选:低可靠优先
既然打孔会丢失约束,那打孔位置就要尽量选在“对信息位影响最小”的地方。极化码有个很好的特性:每个码字比特对信息位的贡献不同,子信道可靠性高的位置承载了更多信息。所以工程上的通用原则是:打孔优先选择子信道可靠性低的位置。因为可靠性低的位置本来传递的信息就少,打掉它对译码影响最小。
但要注意一个容易搞反的点:缩短是优先选可靠性高的位置,打孔是优先选可靠性低的位置。乍一听很反直觉,但逻辑其实是通的——缩短位置虽然不发送,但接收端知道值,高可靠位置的行为依然可以被完整保护;打孔位置接收端完全未知,如果打的是高可靠位置,相当于把信息位最关键的保护约束直接擦除,那基本等于自杀。
2.4 三种手段对照表
| 维度 | 打孔 Puncturing | 缩短 Shortening | 重复 Repetition |
|---|---|---|---|
| 英文名 | Puncturing | Shortening | Repetition |
| 适用场景 | E < N,从码字中删减 | E < N,从码字中删减 | E > N,需要扩展 |
| 接收端对删减位的先验 | 完全未知,LLR=0 | 事先已知,LLR=极大值 | 重复位直接合并 |
| 删减位置的选取偏好 | 优先选低可靠子信道 | 优先选高可靠子信道 | 不涉及删减 |
| 对译码约束的损失 | 损失较大,约束变成不确定性 | 几乎无损失,反而增加确定性信息 | 无损失,近似获得分集 |
| 实现复杂度 | 低 | 中,需要固定值约束 | 低 |
| 典型应用 | 低码率场景的极化码打孔 | 高码率场景的极化码缩短 | 5G NR中E>N时的比特选择 |
3. 为什么需要QUP:从“随便删”到“准均匀”的设计演进
打孔和缩短的原理清楚了,接下来就是怎么构造具体方案。如果只是简单地从开头或结尾连续删掉N-E个比特,会带来一个很麻烦的问题:被打孔的位置过于集中,会导致译码因子图中局部约束大面积缺失,某些信息比特对应的译码路径整个“塌陷”掉。这就是准均匀打孔QUP登场的背景。
3.1 朴素方案的问题:局部的灾难性损失
我举个实际例子。N=256,E=192,需要删掉64个比特。如果直接采取最简单的方案,把前64个码字比特全部打掉,会怎样?打孔位置集中在码字起始段。极化码编码后,码字起始段对应的子信道大部分是冻结位所在的位置,理论上影响不算大,但问题在于,打孔位置过于集中会让因子图左侧一大部分节点完全失去信息输入,这些节点在迭代译码时相当于“死节点”,不仅自己不收敛,还会通过置换网络把不确定性扩散到整个因子图。实际仿真出来的性能就是误块率曲线出现明显的“地板效应”,到了高信噪比反而降不下去。
这就暴露出一个关键设计原则:打孔位置的分布要尽量均匀,而不是集中在某个连续区间。打孔位置均匀分布时,不确定性会平均地摊到整个因子图上,各个校验约束都只是稍微变弱了一点,不会出现某个局部完全死掉的情况。整体性能自然会比集中打孔好得多。
3.2 QUP的核心思想:均匀分布 + 可靠优先
QUP,全称Quasi-Uniform Puncturing,中文叫准均匀打孔。名字里的“Quasi”很巧妙,因为真正做到完全均匀又同时兼顾子信道可靠性,这两个目标有时候是冲突的。QUP的工程智慧就是:在“尽可能均匀”和“优先低可靠”之间做一个折中,生成的打孔序列既不会聚集,又不会打在要害上。
设计思路可以拆成两条原则:
- 第一条原则:低可靠优先。在所有候选位置里,优先选子信道可靠性低的位置,因为打掉它们损失最小。
- 第二条原则:间隔均匀。已选中的打孔位置之间的距离要尽量均匀,避免局部密集。
这两条原则在极端情况下会冲突。比如最低可靠的几个位置恰好挤在一起,你全选了它们,均匀性就很差。QUP的做法是设置一个最小间隔约束,保证相邻打孔位置之间的距离不低于某个阈值。阈值和打孔率有关,打孔率越高,最小间隔越小,两者动态平衡。
3.3 一种常见QUP构造方法与伪代码
理解了原则,构造算法就很清晰了。下面给出一种基于公开文献思想整理的通用构造流程,我不保证它和哪篇论文逐字一致,但核心逻辑是完全可复现的。
python复制def generate_qup_pattern(n, p, reliability):
"""
生成极化码准均匀打孔序列。
n : 码长
p : 打孔数量
reliability : 长度为n的数组,reliability[i]表示位置i的子信道可靠度
约定数值越大越可靠
返回打孔位置集合punctured_positions
"""
# 1. 按可靠度升序排序,优先考虑低可靠位置
sorted_pos = [pos for pos, _ in sorted(enumerate(reliability), key=lambda x: x[1])]
# 2. 计算理想平均间隔
interval = n / p
# 3. 贪心选择:保证位置间最小距离不小于 interval * 0.8
selected = []
min_dist = max(1, int(interval * 0.8))
for pos in sorted_pos:
if len(selected) >= p:
break
if all(abs(pos - x) >= min_dist for x in selected):
selected.append(pos)
# 4. 如果贪心一次没有选满,放宽距离约束补选
if len(selected) < p:
relaxed_dist = max(1, min_dist - 1)
for pos in sorted_pos:
if len(selected) >= p:
break
if pos not in selected:
if all(abs(pos - x) >= relaxed_dist for x in selected):
selected.append(pos)
return sorted(selected)
这段代码干了三件事:先按可靠度升序排队,再按最小距离约束逐个挑选,最后如果选不够就打补丁式地放宽距离再选一轮。实际工程中,你可以把min_dist调成interval * 0.6或者interval * 0.9,这会直接影响打孔序列的分布形态,需要根据码长和码率做一点微调。
3.4 均匀性的收益:把损失摊薄
为什么均匀分布能带来这么大的收益?我再用因子图的角度解释一次。极化码译码是基于置信传播或SCL的,信息在因子图里的传播依赖各个节点的软信息叠加。如果某个区域的节点全部是打孔位,那么这个区域就成了信息传播的“死区”,周围节点的信息无法通过这个区域流动。而均匀打孔相当于在图上稀疏地凿了一些洞,每个洞虽然也会削弱一部分信息流,但整体网络结构还是完整的,信息可以绕过这些洞继续流动。宏观表现就是:均匀打孔的误块率性能,在高码率和高打孔率场景下,比连续打孔好0.5到1.5dB,这在物理层里已经是相当大的增益了。
我实际测试过一个N=1024、E=600的打孔方案,随机打孔和QUP打孔的BLER差异在目标误块率1%附近相差接近1dB。这个差距对于链路预算紧张的场景,意味着覆盖半径明显缩小,所以QUP不是锦上添花,而是必需的。
4. QUP在5G NR物理层中的落地形态
QUP听起来很学术,但5G NR标准里的极化码速率匹配方案,本质上就是QUP思想的工程化产物。标准里没有直接叫“QUP”这个名字,但你去翻TS 38.212的5.3.1.2小节,看到的子块交织和比特选择机制,处处体现着“均匀删减+可靠优先”的设计逻辑。
4.1 从QUP到NR速率匹配的工程化演进
5G NR的极化码速率匹配,在设计时面对的条件比纯理论QUP复杂得多。它需要同时支持任意码长E、任意信息比特数K、和不同码率场景,还要兼容HARQ增量冗余。纯理论QUP每次都要按码长和打孔数现算一个打孔序列,这对协议栈实现不友好,因为序列本身的状态太多。
NR的工程化思路是:先把N个码字比特做一次子块交织,把一个长序列“洗”成32个子块的均匀交错结构;再做比特选择,按特定规则从交织后的序列中依次取E个比特。这个设计相当于把QUP的“均匀性”固化到了交织器里,打孔/缩短时只要按顺序截取片段,就能天然获得接近均匀的删减分布。这就是算法的工程化魅力:不显式计算打孔序列,而是用交织器结构隐式解决问题。
4.2 编码侧完整流程:子块交织与比特选择
NR的极化码速率匹配流程非常清晰,我按协议逻辑梳理成四步:
第一步:确定码长N。信息比特数K给定后,N取满足N >= K的最小2的幂次,允许的最大值是1024。这一步是极化编码的前提。
第二步:极化编码。N个输入比特包括K个信息位和N-K个冻结位,信息位位置由子信道可靠性排序确定,编码后得到N个码字比特d_0, d_1, ..., d_{N-1}。
第三步:子块交织。把N个码字比特按每32个一组,看作N/32行、32列的矩阵,按列读取的方式交织,得到交织后的序列y_0, y_1, ..., y_{N-1}。这一步是“准均匀”的物理载体——交织后,原来在长码字中分散位置的低可靠比特会被重新分布在整个序列里。
第四步:比特选择。根据E与N的关系分情况处理:
- 若E >= N,直接循环重复取比特,
e_k = y_{(k mod N)},对应重复模式。 - 若E < N,先判断码率
K/E与7/16的关系。当码率低于7/16时,采用打孔模式,从y的起始位置连续取E个比特作为输出;当码率高于等于7/16时,采用缩短模式,从y的尾部位置倒推连续取E个比特作为输出。
核心就一句话:码率低时用打孔策略,删掉末尾高可靠位置的比特;码率高时用缩短策略,删掉开头低可靠位置的比特。方向的选择依据,就是前面讲过的“低可靠优先打孔、高可靠优先缩短”原则。
4.3 解码侧处理:LLR初始化的关键细节
编码侧定了输出哪些比特,解码侧要做的事情是“复原”缺失的信息。发送端传过来的E个软信息先经过解交织放回N个位置上,剩下的N-E个位置,按照模式不同做不同处理:
打孔模式下,被删掉的是末尾N-E个位置,接收端把它们的LLR全部初始化为0。0的意思是等概率,译码器会把这个位置当成完全未知的信息节点。这一点不能搞错,有人会把打孔位的LLR设成一个很小的正值,比如0.01,这就等于给译码器塞了一条错误先验,误码率会明显劣化。正确做法就是严格的0。
缩短模式下,被删掉的是开头N-E个位置,接收端知道这些位置上的比特值是什么。因为编码时这些位置对应的输入都被强制置为固定比特0,所以码字输出也是确定值,LLR初始化为一个幅值很大的量,比如±100甚至±1000,具体取决于译码器定点位宽。符号方向要注意对齐约定,通常约定LLR正数表示0、负数表示1,你把方向搞反了,整个译码就废了。
4.4 从QUP到NR:同一思想的两代实现
如果把NR的速率匹配方案和文献里的经典QUP放在一起对比,能看到清晰的演进脉络:
| 对比点 | 经典QUP | 5G NR速率匹配 |
|---|---|---|
| 均匀性来源 | 打孔序列显式约束间隔 | 子块交织器结构保证 |
| 位置选择依据 | 子信道可靠性排序 | 比特选择方向(开头/末尾) |
| 对码率的适配 | 同一序列不区分场景 | 7/16阈值区分打孔/缩短 |
| 接收端处理 | 打孔位LLR=0 | 打孔位LLR=0,缩短位LLR=极大值 |
| 工程实现友好性 | 需要离线存储大量序列 | 协议固定流程,无需存储 |
可以看出,NR方案并没有完全抛弃QUP,而是把QUP的核心设计目标用另一种更工程化的方式实现了。理解经典QUP,能帮你更好地理解NR标准为什么这么设计。
5. 工程调试实录:常见问题、排查思路与几个评价指标
最后这部分是我个人调试极化码速率匹配时的实战记录。协议标准不会告诉你这些坑,但你在仿真和代码移植时几乎一定会碰到。
5.1 性能突然变差的排查思路
最常见的现象:BLER曲线在中高信噪比区域掉不下去,或者有1-2dB的明显性能损失,和论文里对不上。这种问题90%出在打孔位置与信息位发生了冲突,或者缩短位置选错了方向。
我的排查习惯是:先用一个固定信噪比做单帧仿真,把发送端和接收端的N个位置分别打印出来,检查信息位集合、打孔/缩短位置集合、冻结位集合三者的关系。对于打孔模式,打孔位置应该和低可靠冻结位高度重叠;对于缩短模式,缩短位置应该在信息位集合之外。如果发现打孔位置动了信息位的奶酪,立刻检查子信道可靠性排序表和码长N是否匹配。
另一个隐蔽的坑是:子块交织器和解交织器的读写方向搞反。交织是“按行写入、按列读出”,解交织是“按列写入、按行读出”,反过来之后序列顺序完全错乱,表现就是BER不收敛。
5.2 LLR初始化的符号方向与幅值
LLR初始化错误是最容易让新手崩溃的问题。打孔位必须初始化成0,不能给微小偏置,这一点前面说过。缩短位必须是一个幅值很大的确定值,幅值太小等于给了译码器一个“我不确定”的错误暗示。
还有一个细节很多人会忽略:缩短位的LLR符号方向要跟随你的比特映射约定。如果你约定0映射为正LLR,缩短位置的值就必须初始化成正的大数,1映射为负LLR则相反。假设你的解调器输出是高电平映射正LLR,缩短位的LLR方向和解调方向不一致,整条链路就废了。我建议在设计阶段就把LLR符号约定写成一个统一的宏定义,所有模块共用,避免各写各的然后互相看不顺眼。
幅值的经验值:16位定点实现里,打孔位LLR=0,缩短位LLR=±512到±1024就够用了。用32位浮点仿真时,给个±1000就非常接近无穷大的实际效果。再大没必要,反而可能在高阶迭代中出现数值溢出风险。
5.3 打孔序列的离线计算与存储优化
如果你的系统需要兼容经典QUP方案而不是走NR标准流程,打孔序列的存储是一个容易被低估的问题。码长从32到1024,打孔数量从1到N-1,全排列组合的序列数量非常大,全部在线计算会拖慢DSP的启动时间,全部离线存储又会消耗大量内存。
工程上常用的折中方案是:按码长和打孔率分段存储。比如N=1024时,打孔率每5%存一条序列,其余打孔率由相邻两条序列内插得到。内插不是简单取并集,而是优先保留低可靠位置的打孔点,再均匀补充其余位置。这样内存占用能下降80%以上,性能损失可以控制在0.1dB以内。
另外一个经验:打孔序列的生成最好在编译期就做好,通过查表方式烧进固件,运行时只做索引计算。别在算法内部写generate_qup_pattern这种函数,每次调用都排序,每帧调用一次的实时开销相当可观。
5.4 验证速率匹配正确性的两个较快方法
验证速率匹配实现是否正确,不需要跑完整链路。有两个快方法我一直在用:
第一个方法:构造全零信息序列。当信息比特全为0时,极化码编码后整个码字理论上也是全0(冻结位本身是0,信息位也是0,线性变换结果全是0)。这时候速率匹配不管怎么打孔缩短,输出比特全是0。如果某个输出比特不是0,说明发送端的速率匹配流程有问题,定位起来非常快。
第二个方法:做收发闭环的CRC校验。发送一个随机信息序列,经过编码、速率匹配、加噪、解调、LLR初始化、速率解匹配、译码,最后看CRC是否通过。不通过时,分别在LLR初始化前后把N个位置的软信息打印出来,人工检查打孔/缩短位置的LLR是否符合预期。这一步能快速把问题锁定在“速率匹配环节”还是“译码器本身”。
5.5 几个工程经验总结
再分享几条零散但实用的经验:
第一,打孔/缩短模式切换阈值7/16是针对AWGN信道优化的,如果你的应用场景是衰落信道或短包场景,这个阈值可能需要微调。我自己在短包(K=40左右)场景下试过,把阈值调到0.3附近性能更好,但代价是某些中间码率区域的BLER会有波动,需要仔细验证。
第二,别忽视交织器的边界效应。N不是32的整数倍时(极化码里N是2的幂次,N=32时没问题,但N=1024也同样是32的倍数,理论上不会有边界问题),子块交织的读写索引容易算错。我建议写个小的单元测试,构造有序序列走一遍交织再解交织,验证恢复原序。
第三,接收端LLR初始化和解调器输出要放在一起联调。单独看速率匹配模块一切正常,联调后性能垮掉,往往是解调器输出的LLR经过了缩放,破坏了你对“大数当已知、零当未知”的假设。解决办法是做一个链路级归一化处理,确保进译码器之前所有位置的LLR幅值处于同一量级。
第四,如果你想复现文献里的QUP性能对比曲线,务必保持子信道可靠性排序表一致。不同文献用的排序算法、量化精度不一样,排序表差异会导致打孔序列不同,最终性能曲线对不上。我用的是5G NR标准里基于极化权重的排序方式,复现经典论文结果时单独生成过一套高斯近似排序表,两套表对应两套曲线,不能混用。
做极化码速率匹配这几年,我最大的体会是:这个模块看起来只是“选比特、删比特”,没什么技术含量,但它和子信道可靠性、码率调度、译码器实现全都耦合在一起。一旦出错,外在表现可能是五花八门的,排查起来却都指向同一个地方——你对“删掉的位置在接收端意味着什么”这个问题理解得够不够透。搞懂了这一层,QUP也好,NR速率匹配也好,说到底都只是同一件事的不同实现方式。
