1. 从SFT到RLHF:为什么光有监督微调还不够
很多人第一次接触大模型训练,脑子里装的都是“喂数据、调参、跑起来”这套逻辑。SFT(Supervised Fine-Tuning,监督微调)确实是最直观的一步:准备一批高质量的“问题-答案”对,拿预训练好的基座模型继续训练,让它学会按照人类期望的格式和风格来回答。这一步做完,模型已经能干活了,但你会发现一个很微妙的问题——它“能答”不等于“答得好”。
我举个实际例子。你拿一个经过SFT的模型去回答“帮我写一段Python快速排序”,它大概率能写出来,语法正确、逻辑通顺。但如果你问“我该不该辞职去创业”,SFT模型可能会给你一段四平八稳的分析,看起来面面俱到,实际上没有任何倾向性,甚至可能给出一个模棱两可、谁也不得罪的废话答案。问题出在哪?出在SFT的训练信号本身。
SFT的损失函数是交叉熵,它做的事情是:给定输入,最大化目标答案的似然概率。换句话说,它只关心“模型输出的token序列和标注答案的token序列有多接近”,并不关心这个答案整体上好不好、有没有用、是否安全、是否符合人类偏好。你标注的答案如果是“标准答案”,模型就学成标准答案的样子;你标注的答案如果是“平庸答案”,模型就学成平庸的样子。更麻烦的是,对于同一个问题,可能存在多个都合理的回答,但SFT只能告诉模型“这一个是对的”,无法告诉它“那一个更好”。
这就是RLHF(Reinforcement Learning from Human Feedback,基于人类反馈的强化学习)要解决的核心问题。RLHF不是替代SFT,而是在SFT的基础上加了一层“偏好对齐”。它的基本思路是:不再要求模型逐token模仿某个标准答案,而是让模型生成多个候选回答,由人类(或人类训练的奖励模型)来打分,告诉模型“A比B好”“C比D差”,然后模型根据这些偏好信号去调整自己的生成策略。这个过程更接近人类学习的方式——不是背标准答案,而是在反馈中逐渐理解什么是“好”。
从工程角度看,RLHF的典型流程分三步走。第一步是SFT,得到一个初步能用的模型,这一步是基础,不能跳过。第二步是训练奖励模型(Reward Model,RM),用人类标注的偏好数据(比如同一个prompt下两个回答的排序)来训练一个能自动打分的模型。第三步才是用强化学习算法(最常用的就是PPO)去优化策略模型,让它在奖励模型的打分下不断改进。这三步环环相扣,SFT提供初始策略,RM提供奖励信号,PPO负责策略更新。
那为什么是PPO而不是别的强化学习算法?这个问题值得单独拎出来说。强化学习算法有很多选择,比如A3C、TRPO、SAC、DPO等等。PPO(Proximal Policy Optimization,近端策略优化)之所以成为RLHF的主流选择,核心原因有三个:第一,它足够稳定,通过clip机制限制每次策略更新的幅度,避免模型在优化过程中“跑飞”;第二,它实现相对简单,不需要像TRPO那样计算复杂的二阶导数;第三,它在样本效率和训练稳定性之间取得了不错的平衡,适合大模型这种训练成本极高的场景。
不过我得说句实话,PPO在RLHF里的表现并不是完美的。它调参敏感、训练不稳定、对奖励模型的精度依赖极高,而且需要同时维护四个模型(策略模型、参考模型、奖励模型、价值模型),显存开销巨大。后来DPO(Direct Preference Optimization)的出现就是为了绕开PPO的复杂性,直接用偏好数据优化策略模型。但DPO也有自己的问题,比如对偏好数据的利用效率不如PPO充分,在某些任务上表现不如PPO稳定。所以到目前为止,PPO仍然是RLHF工业级落地的主流方案,尤其是在对生成质量要求极高的场景下。
注意:如果你只是想做一个小规模的偏好对齐实验,DPO可能是更省事的选择;但如果你要复现工业级RLHF流程,PPO仍然是绕不开的一环。
2. PPO的核心机制:从策略梯度到Clip约束
2.1 策略梯度到底在优化什么
要理解PPO,得先理解策略梯度(Policy Gradient)的基本思想。强化学习的核心目标是:找到一个策略(policy),使得在这个策略下,智能体获得的累积奖励期望最大。用数学语言说,就是最大化目标函数 J(θ) = E[τ~π_θ][R(τ)],其中τ是一条轨迹,R(τ)是这条轨迹的总奖励,π_θ是参数为θ的策略。
策略梯度的关键洞察是:我们不需要知道奖励函数的具体形式,只需要能够采样轨迹并计算奖励,就可以估计梯度。具体来说,策略梯度定理告诉我们,∇J(θ) = E[∇log π_θ(a|s) * A(s,a)],其中A(s,a)是优势函数(Advantage Function),衡量在状态s下采取动作a相对于平均水平的优劣。
这个公式的直觉解释是:如果一个动作带来的优势是正的(比平均水平好),就增加这个动作的概率;如果优势是负的,就降低它的概率。这就像训练一只狗——做对了给零食(增加行为概率),做错了不给(降低行为概率)。但问题在于,这个梯度估计的方差非常大,因为一条轨迹里的每个动作都会影响后续的所有奖励,导致信用分配(Credit Assignment)变得困难。
为了解决方差问题,实践中会引入几个技巧。第一个是使用优势函数而不是原始回报,优势函数通过减去一个基线(baseline)来降低方差。第二个是使用GAE(Generalized Advantage Estimation,广义优势估计),通过引入一个衰减因子λ来平衡偏差和方差。第三个是使用重要性采样(Importance Sampling),允许我们用旧策略采集的数据来更新新策略,提高样本效率。
2.2 重要性采样与PPO的Clip机制
重要性采样是PPO的核心前置知识。假设我们有一个旧策略π_old和一个新策略π_θ,我们想用旧策略采集的数据来估计新策略的梯度。重要性采样的做法是给每个样本乘以一个比率 r(θ) = π_θ(a|s) / π_old(a|s),这个比率衡量了新策略相对于旧策略对这个动作的偏好程度。
如果r(θ) > 1,说明新策略更倾向于选择这个动作;如果r(θ) < 1,说明新策略更不倾向于选择这个动作。理论上,只要r(θ)存在,我们就可以用旧数据来估计新策略的梯度。但实践中,如果r(θ)偏离1太远,估计的方差会爆炸,导致训练不稳定。
PPO的解决方案非常直接:把r(θ)限制在一个区间内,通常是[1-ε, 1+ε],其中ε是一个超参数(常用0.1或0.2)。具体来说,PPO的目标函数是:
L_CLIP(θ) = E[min(r(θ) * A, clip(r(θ), 1-ε, 1+ε) * A)]
这个公式的意思是:如果优势A是正的,我们希望增加r(θ),但最多增加到1+ε;如果优势A是负的,我们希望减小r(θ),但最多减小到1-ε。clip操作就像一个“刹车”,防止策略更新幅度过大。
我打个比方。假设你在教一个学生做题,他做对了一道题(优势为正),你想鼓励他继续这样做。但你不能一次给他太多奖励,否则他可能会过度拟合这道题,下次遇到类似的题反而不会做了。clip机制就是控制这个“奖励幅度”,让策略每次只更新一点点,保持稳定。
2.3 完整PPO损失函数的组成
在实际实现中,PPO的损失函数不只是clip部分,还包括价值函数损失和熵正则化项。完整的损失函数通常写成:
L_total = L_CLIP - c1 * L_VF + c2 * H
其中L_VF是价值函数的均方误差损失,用于训练价值模型(Critic);H是策略的熵,用于鼓励探索;c1和c2是权重系数,通常c1取0.5,c2取0.01左右。
价值函数的作用是估计状态的价值,从而计算优势函数。在PPO中,优势函数通常用GAE来计算,公式是:
A_t = δ_t + (γλ)δ_{t+1} + (γλ)^2 δ_{t+2} + ...
其中δ_t = r_t + γV(s_{t+1}) - V(s_t)是TD误差。GAE通过λ参数在偏差和方差之间做权衡:λ=0时退化为单步TD误差,偏差大但方差小;λ=1时退化为蒙特卡洛回报,方差大但偏差小。实践中常用λ=0.95。
熵正则化的作用是防止策略过早收敛到确定性策略。如果策略变得过于确定(熵接近0),模型就会失去探索能力,容易陷入局部最优。熵正则化通过给损失函数加一个熵的奖励项,鼓励策略保持一定的随机性。
实操心得:在RLHF场景下,熵正则化的系数通常设得比较小(0.01左右),因为大模型的输出空间极大,过强的熵正则化会导致生成质量下降。我试过把c2调到0.05,结果模型开始胡言乱语,生成一些语法正确但语义混乱的内容。
3. RLHF中PPO的工程实现细节
3.1 四个模型的角色与显存开销
RLHF中的PPO不是标准PPO,而是一个“定制版”。标准PPO只有一个策略模型和一个价值模型,但RLHF中需要同时维护四个模型:
| 模型 | 角色 | 是否训练 | 显存占用 |
|---|---|---|---|
| 策略模型(Actor) | 生成回答 | 是 | 最大 |
| 参考模型(Reference) | 计算KL惩罚 | 否 | 最大 |
| 奖励模型(Reward) | 打分 | 否 | 中等 |
| 价值模型(Critic) | 估计状态价值 | 是 | 中等 |
策略模型和参考模型通常是同一个基座模型,但参考模型的参数是冻结的,用于计算KL散度惩罚。KL惩罚的作用是防止策略模型偏离SFT模型太远,避免生成质量崩塌。奖励模型是第二步训练好的,用于给生成的回答打分。价值模型通常和策略模型共享部分参数,或者是一个独立的模型,用于估计每个token位置的状态价值。
显存开销是RLHF落地最大的工程挑战之一。假设你用一个7B模型做RLHF,策略模型需要14GB(FP16),参考模型需要14GB,奖励模型需要14GB,价值模型需要14GB,加起来就是56GB。这还没算上优化器状态、梯度、激活值等开销。实际训练时,通常需要80GB显存的A100,甚至多卡并行。
为了降低显存开销,实践中会采用几种策略。第一种是参数共享:让策略模型和价值模型共享底层参数,只在上层加一个价值头。第二种是量化:把参考模型和奖励模型量化到INT8,减少显存占用。第三种是卸载:把不参与当前计算的模型卸载到CPU内存,需要时再加载回GPU。第四种是使用LoRA(Low-Rank Adaptation)只训练低秩适配器,冻结大部分参数。
3.2 经验采集与奖励计算
PPO的训练流程是迭代式的。每一轮迭代分两个阶段:采样阶段和更新阶段。
采样阶段,策略模型根据当前参数生成一批回答。具体来说,对于每个prompt,策略模型生成一个完整的回答序列,同时记录每个token的对数概率(log probability)。然后,奖励模型给每个回答打一个标量分数,这个分数就是最终奖励。同时,还需要计算策略模型和参考模型在每个token上的对数概率差,作为KL惩罚项。
奖励的计算公式通常是:R = R_reward - β * KL,其中R_reward是奖励模型的打分,KL是策略模型和参考模型在每个token上的KL散度之和,β是KL惩罚系数。β的取值很关键:太小了策略模型会偏离太远,生成质量下降;太大了策略模型几乎不更新,训练没有效果。实践中β通常取0.01到0.1之间,需要根据具体任务调。
更新阶段,用采样得到的数据计算优势函数,然后优化PPO损失。这里有一个细节:优势函数的计算需要价值模型的估计,而价值模型也是在训练中不断更新的。所以每一轮迭代中,价值模型也会更新,但更新频率通常比策略模型低。
注意:KL惩罚的计算方式有两种,一种是逐token计算KL散度然后求和,另一种是计算整个序列的KL散度。前者更精细,后者更稳定。我实测下来,逐token计算在长序列上容易累积误差,建议用序列级别的KL或者对逐token KL做截断。
3.3 训练稳定性技巧
PPO在RLHF中的训练稳定性是一个大问题。我踩过的坑包括:奖励模型打分波动导致策略模型来回震荡、KL惩罚系数设置不当导致模型输出退化、学习率过大导致策略崩溃等。以下是我总结的几个稳定性技巧:
第一,奖励归一化。奖励模型的打分范围可能很大,直接用来计算优势函数会导致梯度爆炸。实践中会对奖励做归一化,比如减去均值除以标准差,或者用running mean/std来动态调整。
第二,价值模型预热。在正式训练策略模型之前,先用一批数据预训练价值模型,让它对状态价值有一个初步估计。否则,初期优势函数的估计误差会很大,导致策略更新方向错误。
第三,梯度裁剪。PPO本身有clip机制,但梯度裁剪仍然必要。通常把梯度范数裁剪到1.0或0.5,防止梯度爆炸。
第四,学习率预热和衰减。策略模型的学习率通常设得很小(1e-6到1e-5),并且需要预热。我试过直接用1e-5的学习率,结果模型在几百步后就崩溃了,输出全是重复的token。
第五,早停策略。如果KL散度超过某个阈值(比如10),就停止当前迭代的更新,避免策略偏离太远。
4. PPO代码实现:从伪代码到可运行示例
4.1 核心训练循环的伪代码
先看PPO在RLHF中的核心训练循环。以下是我常用的伪代码结构:
for iteration in range(num_iterations): # 采样阶段 prompts = sample_prompts(batch_size) responses, log_probs_old = policy_model.generate(prompts) rewards = reward_model.score(prompts, responses) kl_penalty = compute_kl(policy_model, reference_model, prompts, responses) rewards = rewards - beta * kl_penalty # 计算优势函数 values = value_model(prompts, responses) advantages = compute_gae(rewards, values, gamma, lambda_) # 更新阶段 for epoch in range(ppo_epochs): log_probs_new = policy_model.log_prob(prompts, responses) ratio = exp(log_probs_new - log_probs_old) surr1 = ratio * advantages surr2 = clip(ratio, 1-eps, 1+eps) * advantages policy_loss = -min(surr1, surr2).mean() value_loss = mse(value_model(prompts, responses), returns) entropy_loss = -policy_model.entropy(prompts).mean() total_loss = policy_loss + c1 * value_loss + c2 * entropy_loss total_loss.backward() clip_grad_norm_(policy_model.parameters(), max_norm=1.0) optimizer.step()这个伪代码省略了很多细节,比如mask处理、padding、分布式训练等,但核心逻辑是完整的。实际实现中,最复杂的部分不是PPO本身,而是如何高效地管理四个模型的前向传播和显存。
4.2 关键代码片段解析
先看优势函数的计算。GAE的实现需要注意mask处理,因为不同序列的长度不同,padding位置的奖励和值应该被忽略:
def compute_gae(rewards, values, gamma=0.99, lambda_=0.95): advantages = torch.zeros_like(rewards) last_gae = 0 for t in reversed(range(rewards.size(1))): if t == rewards.size(1) - 1: next_value = 0 else: next_value = values[:, t+1] delta = rewards[:, t] + gamma * next_value - values[:, t] last_gae = delta + gamma * lambda_ * last_gae advantages[:, t] = last_gae returns = advantages + values return advantages, returns再看KL惩罚的计算。这里有一个容易出错的点:KL散度应该用参考模型的概率除以策略模型的概率,而不是反过来。而且需要对每个token的KL做mask,只计算有效token:
def compute_kl(policy_log_probs, ref_log_probs, mask): kl = policy_log_probs - ref_log_probs kl = kl * mask return kl.sum(dim=-1) / mask.sum(dim=-1)最后看PPO损失的计算。注意clip操作是对ratio做的,不是对损失做的:
def ppo_loss(log_probs_new, log_probs_old, advantages, eps=0.2): ratio = torch.exp(log_probs_new - log_probs_old) surr1 = ratio * advantages surr2 = torch.clamp(ratio, 1-eps, 1+eps) * advantages return -torch.min(surr1, surr2).mean()4.3 超参数设置参考
以下是我在实际项目中常用的超参数配置,供参考:
| 超参数 | 推荐值 | 说明 |
|---|---|---|
| 学习率 | 1e-6 ~ 5e-6 | 策略模型,太大容易崩溃 |
| 价值模型学习率 | 1e-5 ~ 5e-5 | 通常比策略模型大 |
| batch size | 64 ~ 256 | 根据显存调整 |
| PPO epochs | 2 ~ 4 | 每批数据重复训练次数 |
| clip epsilon | 0.1 ~ 0.2 | 控制策略更新幅度 |
| gamma | 0.99 | 折扣因子 |
| lambda | 0.95 | GAE参数 |
| KL系数 beta | 0.01 ~ 0.1 | 控制偏离参考模型的程度 |
| 熵系数 c2 | 0.001 ~ 0.01 | 鼓励探索 |
| 价值损失系数 c1 | 0.5 | 价值函数损失权重 |
| 梯度裁剪 | 1.0 | 防止梯度爆炸 |
这些值不是绝对的,需要根据具体任务和模型规模调整。比如7B模型和70B模型的最优超参数可能差一个数量级。
5. 常见问题与排查技巧实录
5.1 训练不收敛或奖励不上升
这是最常见的问题。表现是:训练了几百步,奖励模型的打分没有明显提升,或者先上升后下降。排查思路如下:
第一,检查奖励模型的质量。如果奖励模型本身打分不准,PPO再努力也没用。可以先用一批人工标注的数据验证奖励模型的准确率,如果准确率低于70%,说明奖励模型需要重新训练。
第二,检查KL惩罚系数。如果beta太小,策略模型会偏离参考模型太远,生成质量下降;如果beta太大,策略模型几乎不更新。可以打印每轮迭代的KL散度,如果KL持续增长超过10,说明beta太小;如果KL始终接近0,说明beta太大。
第三,检查学习率。学习率过大是训练崩溃的头号原因。可以尝试把学习率降低一个数量级,看看是否稳定。
第四,检查优势函数的计算。如果优势函数全是正的或全是负的,说明价值模型估计有问题。可以打印优势函数的均值和标准差,正常情况下应该接近0,标准差在1左右。
5.2 显存溢出(OOM)
显存溢出是RLHF的另一个常见问题。排查和解决思路:
第一,减少batch size。这是最直接的方法,但会降低训练效率。
第二,使用梯度累积。用小batch size前向传播,累积多个batch的梯度后再更新,等效于大batch size。
第三,使用混合精度训练(FP16或BF16)。BF16比FP16更稳定,推荐优先使用。
第四,使用LoRA。只训练低秩适配器,冻结基座模型,显存占用可以减少60%以上。
第五,使用DeepSpeed ZeRO或FSDP。这些分布式训练框架可以把优化器状态、梯度、参数分片到多张卡上,大幅降低单卡显存。
第六,卸载参考模型和奖励模型到CPU。这两个模型不参与梯度更新,可以在需要时加载到GPU,不需要时卸载到CPU。
5.3 生成质量退化
生成质量退化表现为:模型开始输出重复的token、语法错误、或者答非所问。原因通常是策略模型偏离参考模型太远。解决方法:
第一,增大KL惩罚系数beta。这是最直接的方法。
第二,减小学习率。让策略模型每次更新幅度更小。
第三,使用早停。如果KL散度超过阈值,停止当前迭代的更新。
第四,检查奖励模型是否有漏洞。有些奖励模型会对特定模式给出高分,比如长回答、包含特定关键词的回答,导致策略模型学会“刷分”而不是真正提升质量。
5.4 常见问题速查表
| 问题 | 可能原因 | 解决方法 |
|---|---|---|
| 奖励不上升 | 奖励模型不准 | 重新训练奖励模型 |
| 奖励先升后降 | 策略偏离太远 | 增大beta,减小学习率 |
| 显存溢出 | batch太大 | 减小batch,用LoRA/ZeRO |
| 生成重复 | 熵太低 | 增大熵系数,检查KL |
| 训练崩溃 | 学习率太大 | 降低学习率,梯度裁剪 |
| KL爆炸 | beta太小 | 增大beta,早停 |
| 价值损失不降 | 价值模型学习率太小 | 增大价值模型学习率 |
实操心得:RLHF训练中,最耗时的不是训练本身,而是调试。我建议在正式训练之前,先用一个小模型(比如1B以下)跑通整个流程,确认奖励上升、KL可控、生成质量不退化,再切换到目标模型。这样可以节省大量时间和算力。
6. PPO与DPO的对比:什么时候该用哪个
6.1 DPO的核心思想
DPO(Direct Preference Optimization)是2023年提出的一种替代PPO的方法。它的核心洞察是:RLHF的优化目标可以转化为一个简单的分类损失,不需要显式训练奖励模型,也不需要PPO的复杂采样和优势估计。
DPO的损失函数是:
L_DPO = -E[log σ(β * (log π_θ(y_w|x)/π_ref(y_w|x) - log π_θ(y_l|x)/π_ref(y_l|x)))]
其中y_w是偏好回答(winner),y_l是拒绝回答(loser),π_ref是参考模型。这个损失函数的直觉是:增加偏好回答的概率,降低拒绝回答的概率,同时用参考模型做正则化。
DPO的优势很明显:实现简单(只需要一个损失函数)、训练稳定(不需要采样和优势估计)、显存占用小(只需要策略模型和参考模型)。但DPO也有缺点:对偏好数据的利用效率不如PPO充分,因为PPO可以在线采样,不断生成新的数据来训练,而DPO只能用固定的偏好数据集。
6.2 选择建议
| 维度 | PPO | DPO |
|---|---|---|
| 实现复杂度 | 高 | 低 |
| 训练稳定性 | 中 | 高 |
| 显存占用 | 高 | 中 |
| 样本效率 | 高(在线采样) | 低(离线数据) |
| 生成质量 | 高 | 中高 |
| 调参难度 | 高 | 低 |
| 适合场景 | 工业级落地 | 快速实验 |
我的建议是:如果你有充足的算力和工程能力,追求极致的生成质量,选PPO;如果你资源有限,想快速验证偏好对齐的效果,选DPO。两者不是对立的,可以结合使用,比如先用DPO做初步对齐,再用PPO做精细调优。
7. 从Transformer到RLHF:完整技术栈回顾
7.1 Transformer是这一切的基础
RLHF和PPO都是建立在大模型之上的,而大模型的核心架构是Transformer。理解Transformer是理解RLHF的前提。Transformer的核心是自注意力机制(Self-Attention),它允许模型在处理每个token时关注序列中的所有其他token,从而捕捉长距离依赖。
在RLHF场景下,Transformer的几个特性特别重要。第一,自回归生成:模型逐个token生成回答,每个token的生成依赖于之前的所有token。第二,对数概率:模型可以输出每个token的对数概率,这是PPO计算ratio和KL的基础。第三,可微性:整个生成过程是可微的,允许梯度反向传播。
如果你对Transformer还不熟悉,建议先补一下基础。不需要手写完整的Transformer,但至少要理解自注意力、位置编码、残差连接、层归一化这几个核心概念。这些概念在调试RLHF时经常用到,比如分析KL散度的分布、理解价值模型的输出等。
7.2 SFT、奖励模型、PPO的串联
整个RLHF流程可以概括为:SFT教模型“怎么答”,奖励模型教模型“什么是好答案”,PPO教模型“怎么答得更好”。三者缺一不可。
SFT阶段,用高质量的指令数据微调基座模型,让模型学会遵循指令、按照人类期望的格式回答。这个阶段的数据质量至关重要,通常需要几千到几万条标注数据。
奖励模型阶段,用偏好数据训练一个打分模型。偏好数据的格式是(prompt, chosen, rejected),奖励模型学习给chosen打高分、给rejected打低分。这个阶段的数据量通常比SFT更大,需要几万到几十万条偏好对。
PPO阶段,用奖励模型作为信号,优化策略模型。这个阶段需要同时维护四个模型,工程复杂度最高,但也是提升生成质量最关键的一步。
7.3 当前技术趋势
RLHF和PPO仍然是当前大模型对齐的主流方案,但技术演进很快。几个值得关注的方向:第一,DPO及其变体(如IPO、KTO、ORPO)在简化RLHF流程方面取得了不错的效果。第二,RLAIF(Reinforcement Learning from AI Feedback)用AI代替人类标注偏好数据,大幅降低了数据成本。第三,在线迭代式RLHF,把奖励模型和策略模型的训练交替进行,不断提升对齐效果。
不过无论技术怎么变,PPO的核心思想——用奖励信号指导策略优化,同时用KL约束防止偏离——仍然是理解大模型对齐的关键。掌握了PPO,再看其他方法,会发现它们只是在某些环节做了简化或替换,底层逻辑是相通的。
我在实际项目中最大的体会是:RLHF的难点不在算法本身,而在工程细节和数据质量。PPO的数学公式并不复杂,但要让它在实际训练中稳定收敛,需要大量的调试和经验积累。奖励模型的质量决定了RLHF的上限,如果奖励模型本身有偏差,PPO再强也只会放大这个偏差。所以如果你准备做RLHF,建议把70%的精力花在数据标注和奖励模型训练上,30%花在PPO调参上。这个比例听起来夸张,但实际做过的都知道,数据才是真正的瓶颈。