手机在地铁里嗖一下刷出高清视频卫星在下行链路里回传照片这些看起来稀松平常的画面背后都有一个容易被人忽略的功臣信道编码。信道编码做的事情就是给原始数据加冗余让它在噪声干扰后还能被正确还原。这几年通信圈里谈得最多的新编码不是Turbo也不是LDPC而是极化码。极化码是理论上可证明达到香农极限的构造性编码方案并且已经写进了5G标准的控制信道和广播信道。这篇文章想把极化码到底是什么、它怎么个“极化”法、编码和译码的实用算法一次性讲透。适合刚接触信道编码的研究生、做通信算法落地的工程师以及想搞明白5G底层技术原理的硬件或软件开发者。我不打算堆公式尽量用直觉和例子讲清楚原理但该给的递推关系、参数计算和实现细节也会给到位。我相信很多人第一次听到“极化码”这三个字脑子里冒出来的问题是极化到底极化了什么这是一段“从一个信道复制出许多关联子信道再把它们分成好与坏”的过程。用大白话说同一个物理信道通过巧妙的线性变换被拆成了一批容量不同的虚拟信道其中一部分容量趋近于1另一部分趋近于0。我们只挑好的信道传有效信息差信道统统填固定比特这就是极化码的全部思想。你可能会问真有这么简单确实核心思想就这么简单难的是怎么证明、怎么构造、怎么编解码。这篇文章就沿着这条线往下挖。1. 什么是极化码从一道“分裂术”说起1.1 极化的物理直觉为什么要把信道撕开先做一个思想实验。假设你有一条信道传输过程中有噪声误码率大概是10%。你直接用这条信道传数据100个比特里大约会有10个出错。现在换一种玩法把两个这样的信道拼起来一个传原始数据一个传原始数据的异或值。接收端拿到两路结果后可以根据它们做联合判断。你很快会发现这两路组合后的等效信道一个“变好了”一个“变差了”。其中一个等效信道的容量比原来的单信道更高另一个更低。这就是最朴素的信道极化直觉——不是物理上的信道变了而是经过变换后信息的“承载条件”被重新分配了。如果只做一次两个信道的合并效果微乎其微。但如果我们把这种合并递归地做下去每次把上一轮得出来的等效信道当成新的“基础信道”再次配对、合并、分解情况就完全不同了。经过足够多轮之后这些等效信道的容量会朝着两个极端收拢要么非常接近1要么非常接近0。这个现象被严格证明过而且是可构造的不是统计意义上的神秘现象。换句话讲我们可以在发送端就把哪些位置适合传信息精确选出来哪些位置纯粹浪费能量一目了然。1.2 与Turbo码、LDPC码的本质区别很多接触过信道编码的人都是从卷积码、Turbo码或者LDPC码入门的。这些码都有一个共同点它们是通过迭代译码逼近香农限的也就是说理论上没有证明“一定能达到”只是实践中表现非常好。极化码不同它从一开始就是“構造出来的最优码”不是靠大数定律碰运气而是在数学上给出了可证明的逼近路径。这带来一个非常实际的差异短码场景下Turbo码和LDPC码的性能抖动很大因为迭代译码需要足够长的码块才能“滚”起来。而极化码在码长几百比特时就已经表现出清晰的极化趋势性能可控错误平层也更稳定。这就是为什么5G标准里控制信道选了极化码而不是LDPC码——控制信道的载荷往往只有几十到几百比特恰恰是极化码的舒适区。1.3 极化码实际上帮我们解决了什么在工程上极化码主要解决两个问题。第一个是“在极短码长下把错误率压下来”。5G的物理下行控制信道里一个用户的调度信息可能只有几十比特这么短的数据块Turbo码根本发挥不出来LDPC码也优势有限。第二个问题是“译码复杂度可控”。极化码的标准逐比特译码SC复杂度只有O(N log N)码长增大时不会爆炸式增长非常适合硬件实现。当然SC译码性能在中等码长下不够好后面我会讲怎么用SCL算法弥补。2. 信道极化的核心机制从合并到分裂的数学直觉2.1 两信道合并一个最小可运行案例先别急着看N1024的复杂结构我们把问题压到最小两条一样的二进制输入信道W(y|x)输入是0或1输出是一个带噪声的观测值。我们把两个输入合并成一组第一路放原始比特u1第二路放u1和u2的异或值。接收端得到两组观测y1、y2。现在观察两件事对u1来说它的信息被重复表达了两遍一遍直接来一遍藏在异或里。所以估计u1时有两份证据可以参考这个等效信道变好了。对u2来说它必须先猜出u1才能从异或里解出自己的信息。万一u1猜错了u2也跟着错这个等效信道变差了。这就是最小单元的信道分裂。一次分裂后两个等效信道的容量分别为I(W^)和I(W^-)一个是原信道容量的某种提升一个是某种下降。关键来了把这两个等效信道当作下一轮的基础信道继续配对合并下一轮得到四个信道其中两个从W^派生两个从W^-派生容量继续向两端分化。2.2 递归变换为什么N要取2的幂次看到这里你就会明白极化码的码长N天然是2的幂次因为每一次变换都是“两两配对”的递归操作。N8就是把三个比特的操作串起来N256就是八个比特的操作串起来。实际工程里如果需要任意码长就得用速率匹配打孔或缩短来调整这一点后面单讲。递归变换本质上是在做这样一个运算对长度为N的输入向量u先做某种比特重排再与一个固定矩阵相乘。这个矩阵用二进制表示时有非常规则的结构码长N对应log2 N阶的克罗内克幂。很多资料里把这个写成G F⊗n。这里F是2x2的极化和矩阵[[1,0],[1,1]]。第一次看到这个矩阵的人容易懵不知道为什么是上三角有个0。你可以这么理解矩阵的乘法规则恰好实现了“第一路直接传第二路传异或”这个逻辑。2.3 好信道与坏信道从容量到可靠性度量每做完一轮递归我们需要定量评估N个子信道各自的好坏才能决定哪些位置放数据位哪些位置放冻结位。评估方法有很多种最经典的是计算每个子信道的巴氏参数Z(W)它表示这个信道的错误概率上界。Z值接近0是好信道接近1是坏信道。另外一个做法是算容量I(W)I值接近1是好信道。两套度量标准各有使用场景工程上用巴氏参数更多因为它计算方便、对硬件友好。实际构造时通常不是每个码长都重新算一遍而是用密度进化或高斯近似来估计子信道的可靠度顺序。对同一码长这个顺序是固定的预计算后存成一张表就行。这也是极化码工程化非常讨喜的地方不像LDPC码要现场生成或存储大校验矩阵极化码只需要一张可靠度序列表。2.4 一个容易混淆的概念极化码与极性码我见过不止一次讨论时把“极化码”和“极性码”当成一回事。严格说“极性码”是另一类采用极性判决的编码方式两者英文也不同。中文翻译里一字之差但算法核心完全不同。如果你看到资料里把Polar Codes译成“极性码”就要留个心眼确认上下文再往下读。大部分中文标准文献和3GPP文档里Polar Codes统一译作“极化码”。在阅读早期论文或某些老博客时这种翻译不统一确实会干扰理解。3. 极化码的编码实现从信息位选择到矩阵运算3.1 编码过程只有三步极化码的编码器结构简单到让人怀疑选定可靠度最高的K个位置放信息比特其余N-K个位置放已知的冻结比特然后乘上生成矩阵得到码字。三步走完没有迭代没有反馈。在这个意义上极化码的编码器甚至比很多线性分组码还简单。具体到实现第一步根据码长N和可靠度序列表确定信息位集合A。第二步把K个待发送的信息比特填入位置集合A其余位置置0。第三步输出序列x u · G其中G是N×N的生成矩阵。生成矩阵在二进制域做乘法实际操作中经常用异或操作替代矩阵乘法避免真的做矩阵运算浪费资源。因为G的结构是递归的编码可以用蝶形结构实现和FFT的思路非常接近。熟悉FFT的读者看到编码蝶形图应该会有似曾相识的感觉。3.2 信息位选择一张预计算可靠度表既然每种子信道可靠度可以预计算实际编码器里根本不需要实时算可靠度。以一个码长为256的极化码为例工程上会把256个子信道按可靠度从高到低排好序编译成一张静态表存进ROM。编码时根据当前码率R K/N直接取前K个可靠度最高的位置就行。这里有个细节容易踩坑不同信噪比下可靠度顺序会略有变化。但大量仿真表明在正常工作信噪比区间内这个顺序变化很小工程上完全可以用固定的排序表。某些实现里会有“按信噪比切换序列表”的选项但我测试下来收益非常有限反而增加了存储和选择的复杂度。对于大多数场景我建议直接用固定表省事性能也不差。3.3 速率匹配打孔与缩短的基本策略实际问题里码长不可能是2的幂次比如5G控制信道里常见码长72、144等这就需要速率匹配。思路无非两种打孔删掉部分码字比特和缩短把某些比特置已知值后不发送。两种操作都会改变极化结构所以不能随便删要结合可靠度表重新计算哪些位置被打掉。常见做法是“按可靠度从低到高打孔”优先删掉那些本来就是最不可靠的极化子信道对应的输出比特尽量减少对有效信息位的伤害。另一种技术叫“比特反转缩短”因为缩短比特在生成矩阵里对应特殊位置接收端可以据此恢复信息。工程实现里速率匹配函数几乎都是和编码器打包在一起的使用5G协议栈时直接调用现成模块就行。如果自己从零实现我的建议是先做设置长度匹配再考虑打孔因为缩短的补偿逻辑相对清晰排查起来容易。3.4 手算一个N8的编码例子理论说太多容易晕直接手算一个N4的案例。假定可靠度排序后信息位集合A {3, 4}位置从1数起。也就是说u [0, 0, u3, u4]其中前两个是冻结位第三四个比特承载信息。生成矩阵G4 F⊗2具体值是[1 0 0 0] [1 0 1 0] [1 1 0 0] [1 1 1 1]注意我写的是行向量左乘的约定也就是x u · G4。假设信息位u31u40u全向量为[0,0,1,0]则x [0,0,1,0] · G4 [1,1,0,0]。这个结果就是码字。接收端拿到y后按SC译码逐位恢复u冻结位直接当已知处理只对u3、u4做判决。整个过程没有任何循环迭代这就是极化码编码的缩影。如果你想验证更大码长可以用Python的NumPy把克罗内克幂算出来再和蝶形编码的结果对比这能帮你快速确认自己的实现有没有问题。4. 译码算法深度拆解SC与SCL的演进之路4.1 SC译码逐比特推断的贪心策略SC译码的全称是Successive Cancellation也就是逐比特消除。它的核心逻辑非常直观按照从u1到uN的顺序一个个推断每个比特的值。推断某个比特时前面所有比特的硬判决结果都已经拿到后面比特则当作未知。这样一来问题就变成了一个带先验条件的条件概率计算每一步只需要算一个后验概率或对数似然比。为什么是“消除”因为每推断出一个比特就从接收观测里把它带来的贡献减掉减少后续判断的不确定性。这个过程和剥洋葱很像一层一层往里剥。SC译码的复杂度是O(N log N)得益于蝶形结构的LLR传播每次只需要交换邻近节点的信息。从工程角度看SC译码非常适合流水线实现极简版本甚至可以用少量的寄存器和加法器完成。4.2 SC译码的致命短板错误传播SC译码虽然简单但有一个绕不开的问题早期比特一旦判错后面的比特全部受影响。你可以想象一条流水线前面一个工位出错后面所有工位都基于错误的上游结果继续做工。这种错误传播效应在中等码长和高码率下格外明显导致SC译码的性能比最大似然译码差出一大截。解决思路有两条路可走。一条路是改进译码器结构另一条路是“多开几个候选分支最后再挑”。后者就是SCL算法也是当前工程应用的主流方案。4.3 SCL译码从单条路径到多条路径并行SCLSuccessive Cancellation List译码的核心改动非常朴素SC一次只保留一条路径也就是每个比特只做一个硬判决SCL则是每步对0和1两种假设都保留路径路径总数超过上限L时按照路径度量剪掉最差的。最后从幸存路径里挑一条度量最优的作为输出。路径度量怎么算关键是要定义一个可累积的代价函数。常用做法是在对数域累加每个比特判决的置信度也就是沿路径计算一个似然和。在实际实现中路径度量往往用定点数存为了避免数值下溢会把LLR做饱和处理。路径数L通常取8、16或32取值越大性能越接近最大似然译码但复杂度也线性增长。硬件实现里L8是常见起步值性能相比SC通常有1dB以上的增益。4.4 CRC辅助极化码让SCL再上一层楼SCL保留L条候选路径后如何判断哪条最好常规做法是选度量最小的那条。但如果我们在编码前给信息比特加一段CRC校验译码结束时就可以对所有候选路径做CRC校验优先挑通过校验的路径。这等于给SCL装上了“纠错导航”效果非常显著。这个组合方案叫CA-SCLCRC-Aided Successive Cancellation List正是5G控制信道采用的译码方案。加入CRC会损失少量编码效率比如原来传100比特信息现在要拿出8比特做CRC有效载荷就变成92比特。但换来的是误码率断崖式下降尤其在短码场景这种交换非常划算。从工程实测经验看CRC长度选8或16比较常见。选太短校验能力不足选太长信息位挤压严重码率受损。4.5 译码器实现中的一些细节实现SCL译码器时最繁琐的不是路径度量计算而是路径复制和内存管理。当路径分裂时需要把旧路径的中间状态完整复制出来这就涉及大量的内存拷贝操作。优化做法是“惰性复制”只有分裂点才真正分配新内存否则各路径共享节点数据省下大量存储和带宽。我到目前为止测试过的几个硬件实现里这套思路都能省掉至少30%的存储资源。码长256、列表大小8的CA-SCL译码器在定点化后LLR位宽取6比特就够用。实测中从浮点切到6比特定点误码率损失通常在0.1dB以内对大多数场景是可接受的。再低到5比特会开始出现明显性能退化建议不要为了省资源把位宽压过头。5. 工程实战怎么选参数、怎么调通、怎么避坑5.1 极化码、LDPC码、Turbo码怎么选如果你在做一个完整的通信系统免不了要面对“这里到底用极化码还是LDPC码”的选择。根据我实际项目里的经验可以给一个相对清晰的判断逻辑码长在几千比特以上的中长码、高吞吐数据信道优先选LDPC因为它的并行化译码更有利于高吞吐码长在几十到几百比特的控制信道、广播信道选极化码因为它短码性能稳定且译码延迟可控如果兼容5G标准那就别纠结了物理层协议已经把极化码和LDPC码的用途划分得明明白白。表格列一下对比项极化码LDPC码Turbo码短码性能优秀一般一般中长码性能良好需SCL优秀良好译码延迟串行结构延迟较低并行度高适合高吞吐迭代次数多延迟偏高构造复杂度序列表预计算后极低矩阵构造较复杂交织器设计复杂5G物理层应用控制信道、广播信道数据信道未沿用5.2 仿真调试时的常见坑第一个坑信息位集合算错。很多初学实现里直接照搬论文里的可靠度表格而不确认表格的索引顺序是不是从0开始导致白噪声信道下误码率高得离谱。调试方法很简单在无噪声条件下先跑一遍编解码如果能够完全恢复说明编码器结构基本正确问题大概率出在可靠度表或LLR符号上。第二个坑LLR符号约定不一致。编码器里把比特0映射为11映射为-1和把比特0映射为-11映射为1始终是个容易搞混的点。LLR符号约定一旦反了性能就会断崖式恶化。我遇到过不止一次排查半天最后发现只是符号反了。建议在代码里用常量定义映射关系并写一个单比特闭环测试从源头锁死。第三个坑路径度量的数值范围。SCL译码里路径度量是一个累加量码长N增大时度量绝对值也会变大定点实现下很容易溢出。解决方案是定期把所有路径度量减去当前最小值做归一化处理。这个操作几乎不影响结果但能彻底解决溢出隐患。5.3 从零实现一个最小极化码系统如果你想把理论吃透动手实现一个最小系统是比较快的路径。我的建议顺序先用Python或C语言写一个SC译码器码长从N16开始验证无噪情况下编解码闭环再扩展可靠度计算逻辑用蒙特卡洛仿真对比误码率曲线然后加CRC和SCL观察增益变化最后才是硬件化或针对具体平台优化。每一步都做闭环验证不要一次性写完再debug。特别是SC改SCL时路径分裂、剪枝、校验三个逻辑点都容易出问题建议分步验证。我在带新人时经常强调先把N16、32的极小例子彻底跑通再上大码长。很多人一上来就奔着N1024去出了问题根本没法定位。5.4 硬件实现时怎么压资源和延迟硬件实现极化码译码器时主要有两个方向低延迟优先型和中低资源型。低延迟优先型会把蝶形网络全部展开用流水线级联的方式把译码时延压到极致代价是存储和寄存器消耗大。中低资源型则尽量复用计算单元把蝶形节点分时复用延迟会大一些但资源只有前者的三分之一左右。实际项目中选择哪个方向取决于你的目标时钟和码率。我的经验是先用C模型把算法跑通测出参考性能再做定点仿真确定LLR位宽最后再开始写RTL。顺序如果反过来往往会在位宽和时序上反复返工。这条经验适用于大部分通信算法的硬件实现不仅仅是极化码。做一个稍微有点反直觉的提醒极化码的编码器在硬件里经常被单独做成一个可复用模块因为它不依赖外部存储或校验矩阵纯组合逻辑加少量寄存器就能完成。这在某些异构多模系统里非常有吸引力——不需要为每个码长准备单独的矩阵存储一张可靠度表加一套蝶形逻辑就搞定所有码长前提是码长是2的幂次。写到这里我个人在实际项目中体会最深的一点是极化码的入门门槛其实很低编码器的简单程度甚至让人觉得“就这”但真正把性能做出来功夫全在可靠度表的理解、SCL路径管理以及CRC配合的细节上。如果你正在自己的项目里验证极化码我建议先拿N256左右的码长把仿真曲线跑出来再去碰大码长或任意码长匹配。这样既能快速建立起对极化现象的感觉又不会在调试初期就被各种参数细节淹没。