部署过大模型的工程师大概都经历过这种纠结:模型生成的回答语法通顺、语义连贯,但你总觉得某些关键位置“不太稳”。实体名可能拼错,数字可能对不上,逻辑链条中间断了一环。传统自回归推理只做一次前向就给出答案,遇到低置信度 token 时,模型既没有机会回头修改,也没有机制利用更深层的语义来校正早期误差。DeepMind 最近公开的一个研究方向,把注意力放在了“推理时回灌深层激活”上,思路很直接:在推理阶段,把深层已经编码好的激活反馈到更浅层,重新计算难度较高的 token,从而降低困惑度。它的意义不在于给出一个新训练 Loss,而在于重新分配推理期计算的优先级。
这篇文章不打算复述论文的每一处细节,而是想拆解这条技术路线的原理、收益边界、工程落地方式,以及最容易踩的坑。我会先讲清楚困惑度到底在衡量什么,再解释深层激活回灌的核心机制,然后给出一套可参考的概念性实现流程、评估方法和最佳实践。如果你正在做 LLM 应用落地、推理加速或者模型评测,这篇文章值得花十分钟读完。
1. 为什么一条“降低困惑度”的研究值得关注
放在三年前,如果有人说“推理时调整激活能降困惑度”,很多人会认为这是学术圈的自娱自乐。但今天不一样。随着模型规模增大,真正昂贵的不是训练,而是推理。每次让模型重新生成一遍答案,都要付出几十亿甚至上百亿参数的前向计算成本。业界常用的提高生成质量手段,包括多次采样、自一致性投票、思维链、重排序,本质上都是在“多算几遍”的基础上增加决策依据。问题在于,这些方法的计算分配非常粗糙:无论 token 是简单还是困难,都被一视同仁地重新计算。
DeepMind 这个方向真正值得关注的地方,是它试图把推理期的额外计算集中到“最不确定”的位置上。一个模型在生成Paris这个 token 的时候,可能已经非常确定;但在生成一段逻辑推理的中间步骤时,分布会明显平坦,熵值很高。传统解码策略对这两种情况使用同样的计算资源,显然浪费了。回灌深层激活的思路,是在检测到高不确定性 token 之后,利用深层的全局语义信息反向修正浅层表示,让模型对困难位置的预测更“笃定”。这相当于给推理过程加了一个“局部放大器”。
对 CSDN 读者来说,这个方向至少有三层价值。第一,它提供了一种不改变模型权重就能改善生成质量的新手段;第二,它把困惑度这个老指标重新拉回工程评测视野;第三,它让我们重新思考 token 级别的自回归生成究竟还有多少优化空间。无论 DeepMind 最终是否把这条路线落地成产品,它背后的“按需分配推理计算”思想,都会成为推理优化的长期趋势。
2. 困惑度究竟在衡量什么
困惑度(Perplexity,PPL)是语言模型最经典的评估指标之一。它的基础数学定义是:对一段文本计算每个 token 的平均负对数似然,再取指数。公式可以写成:
PPL = exp( - (1 / N) * sum(log P(token_i | context_i)) )其中 N 是 token 总数。通俗地说,模型在预测下一个词时如果平均“犹豫程度”越低,困惑度就越低。如果模型对每个位置都给出了接近 1 的预测概率,PPL 就接近 1;如果模型像抛硬币一样猜,PPL 就会很高。
但这里有一个老生常谈却容易被忽略的误区:困惑度低不等于回答正确。一个模型可能对“中国的首都是北京”给出极高的置信度,也可能对一句事实错误的话给出同样高的置信度。困惑度衡量的是模型内部的一致性,是“模型觉得自己有多确定”,而不是“事实是否正确”。不过,这并不意味着 PPL 没有价值。实践中的合理用法是:在同一个模型、同一组 prompt 下,比较不同解码策略的 PPL。当 PPL 显著下降时,说明新策略让模型在预测层面变得更稳定,这通常会带来更少的语法断裂、实体错乱和逻辑跳跃。
还有一个更细的观察:PPL 不应该只看整体平均值。生成一段 200 token 的回答时,可能前面 180 个 token 都很好,只有最后 20 个 token 出现了漂移。整体 PPL 被大量高置信 token 稀释后,看不出问题。更实用的做法是分区间计算 PPL,或者按 token 的熵值做分层统计。这样可以把“局部困惑度高”的问题暴露出来。DeepMind 这次选择困惑度作为观察指标,正是因为它是 token 级不确定性的直接反映,能比较敏感地看出深层激活回灌是否真的改善了预测分布。
3. DeepMind 的新思路:推理时回灌深层激活
要理解“回灌深层激活”,先要看一眼标准的 Transformer 前向计算过程。输入 token 经过 Embedding 层后,会依次穿过若干个 Transformer 层。每一层都会保留一份隐藏状态(hidden state)。最后一层的隐藏状态被映射为词表大小的 logits,再通过 Softmax 得到下一个 token 的概率分布。
在这个过程中,存在一个天然的不对称现象:浅层表示更多地编码局部语法和词法信息,深层表示则更多地编码全局语义和跨 token 依赖关系。当你预测一个长句中的某个中间 token 时,这个 token 的最终分布其实已经在深层表示里“见了更多上下文”,只是常规解码器没有显式利用这一点去修正早期层的信息。
回灌(feedback)要做的,就是把深层已经编码好的激活反馈给浅层。一个典型流程是:先做一次正常前向推理,拿到每个 token 的预测概率。如果某个 token 的置信度低于阈值,就提取该位置若干深层的隐藏状态,通过简单的线性映射或残差加法,注入到浅层对应位置。然后基于注入后的表示,重新做一次局部前向计算,得到修正后的预测分布。这个过程可以循环若干轮,每轮都让浅层表示向深层语义靠拢。
这里有一个直观类比:写论文初稿时,第一遍往往只求把内容铺出来,语言粗糙,逻辑松散。拿到整篇稿子后,你会把整篇文章的脉络记在心里,然后再回头看某一段写得不顺的地方,用“整篇文章想表达什么”来重写这一段。回灌深层激活就是在模拟这个“回读修改”的过程,只不过把“整篇文章的思想”编码成了深层激活向量。
需要特别区分的是,这个思路既不同于思维链(CoT),也不同于常见的自我修正(Self-Correction)。CoT 通过增加中间推理步骤来提升表现,它的额外计算发生在 token 序列层面。自我修正是生成完整回答后再让模型审阅并修订。而回灌深层激活的额外计算发生在表示空间内部,不增加 token 数量,也不要求模型“说一句反思的话”,而是直接调整内部 activation。这意味着它的计算开销更可控,也更适合对延迟不敏感的离线推理场景。
还要澄清一点:回灌不是把网络结构改成循环网络。它的核心思想是“推理阶段人为构建反馈路径”,而不是“训练时使用反馈连接”。这个设计上的差异很重要,因为它意味着任何已训练好的 Transformer 模型,理论上都可以在解码阶段套上这一层策略,不需要重新训练,也不需要微调。对于已经部署的模型来说,这是一个很友好的优化方向。
4. 核心技术机制拆解
4.1 标准自回归解码的流程
我们先明确基线。标准自回归解码每一步只做一次前向传播:
- 输入当前已有的 token 序列。
- 经过全部 Transformer 层,得到最后一层隐藏状态。
- 映射到词表空间,得到 logits。
- 根据解码策略(贪心、采样、束搜索),选出下一个 token。
- 拼接到序列末尾,继续下一次迭代。
这个流程的优点是简单、快。缺点也很明显:模型只能“一条路走到黑”,如果某个中间 token 选错了,后续所有生成结果都会受到影响。KV Cache 的存在略微缓解了重复计算问题,但并没有改变“只前向一次”的核心逻辑。
4.2 深层激活回灌的关键环节
在回灌流程中,标准解码被替换成“检测—提取—注入—重算”四个阶段。
第一阶段是低置信度检测。模型完成前向传播后,会得到当前位置的概率分布。通常可以用最大概率、熵值或者 margin(最大概率减次大概率)来判断这个 token 是否值得回灌。低于阈值的 token 才进入回灌流程,高置信度 token 直接解码,避免不必要的计算浪费。
第二阶段是深层激活提取。从最后一层(或倒数若干层)的隐藏状态中,取出当前 token 位置的激活向量。这个向量包含了模型对全局上下文的整合信息,是“回灌”的内容来源。选择哪一层作为信息源,会影响效果:太浅的层语义不足,最后一层又可能过于贴近输出层。实际工程中,往往需要尝试不同层组合。
第三阶段是注入。把深层的激活向量注入到选定的浅层位置。注入方式可以是简单相加、拼接后经过线性投影,或者通过一个小型 Gate 网络控制混合比例。从计算效率角度看,简单加法代价最低;从表达能力角度看,带 Gate 的线性组合更灵活。
第四阶段是局部重算。注入完成后,从浅层开始重新执行后续若干层的前向计算,得到新的 logits。这里不一定要重算全部层,只重算从注入层到最后一层的路径即可,这样可以控制计算量。重算后如果新分布的最大概率显著提高,说明回灌有效;如果变化不大,就停止循环,避免陷入无意义的反复。
4.3 回灌与 Test-Time Compute 的关系
业界把推理阶段的额外计算统称为 Test-Time Compute。近几年出现的多数方案,比如 Best-of-N、自一致性、Tree Search,本质上是“构建多个候选路径,再从中挑一个”。这类方案的计算量通常成倍增长。回灌深层激活的不同之处,在于它选择了另一个维度:不增加路径数量,而是在表示空间中迭代修正。它更接近数值优化中的“利用梯度信息修正当前解”,只不过这里的“梯度”被替换成了深层激活。
这样设计的好处是计算量的增长是线性的,而且只在低置信度 token 上发生。假设一个回答有 200 个 token,其中只有 20 个 token 低于置信度阈值,回灌的额外成本就只集中在这 20 个位置上,而不是翻倍重跑整个序列。这种“局部性”控制,让它比 Best-of-N 更适合预算受限的场景。
4.4 收益边界在哪里
从原理上推断,回灌深层激活最可能带来明显收益的场景,是那些对局部 token 的精确性要求高、且上下文依赖强的任务。比如代码生成:一个变量名在前面定义,后面引用时模型应该把它写对;错误 token 往往局部概率不高。长文档摘要、多跳问答、结构化数据生成也类似,因为这些任务里的正确 token 高度依赖全局上下文。反过来,如果一段文本就是简单的事实枚举,模型本身的置信度已经很高,那么回灌的空间就很有限。这个方向不是万能的,它的价值上限取决于“模型浅层表示和深层表示之间存在多少信息差”。
5. 概念性实现示例
下面用一个最小示例来说明标准解码和回灌解码的流程差异。注意,这段代码是概念性伪代码,目的是展示核心逻辑,不绑定任何具体深度学习框架。不同模型的层索引、隐藏状态接口、局部前向实现方式各不相同,实际接入时需要替换成对应框架的 API。
5.1 标准解码与回灌解码对比
# 文件路径:example_feedback_decode.py # 说明:概念性伪代码,用于展示推理时回灌深层激活的流程。 def standard_decode(model, input_ids, max_new_tokens): generated = input_ids for _ in range(max_new_tokens): logits = model(generated) next_id = argmax(logits[:, -1, :]) generated = concat(generated, next_id) return generated def feedback_decode(model, input_ids, max_new_tokens, shallow_layer=2, deep_layer=-1, confidence_threshold=0.6): generated = input_ids for _ in range(max_new_tokens): # 1. 正常前向,同时拿到 logits 和全部层的隐藏状态 logits, all_hidden = model.forward_with_hidden(generated) last_logits = logits[:, -1, :] max_prob = max(softmax(last_logits)) # 2. 置信度足够高时,直接解码,不做回灌 if max_prob >= confidence_threshold: next_id = argmax(last_logits) generated = concat(generated, next_id) continue # 3. 提取深层激活,并注入到浅层 deep_activation = all_hidden[deep_layer][:, -1, :] # 取最后一个 token 的深层激活 shallow_state = all_hidden[shallow_layer][:, -1, :] # 浅层当前状态 updated_state = combine_activation(shallow_state, deep_activation) # 4. 基于注入后的表示做一次局部前向,重新计算 logits new_logits = model.forward_from_layer(updated_state, start_layer=shallow_layer) next_id = argmax(new_logits[:, -1, :]) generated = concat(generated, next_id) return generated这段代码里有几个关键点。forward_with_hidden表示前向的同时返回所有层的隐藏状态,这是获取深层激活的基础。combine_activation是注入函数,实际实现中可以是简单的向量加法,也可以是线性投影。forward_from_layer表示从某个浅层开始重新执行前向,而不是从 Embedding 开始,这样可以在修正浅层表示的同时,避免重复计算前面的层。
5.2 回灌循环控制逻辑
一次回灌不一定就能解决问题。模型接收深层激活后,新分布可能仍然不够确定。这时可以设计一个循环,让回灌过程执行多轮,直到满足停止条件。但必须设置轮数上限和退出条件,否则会出现无意义的反复计算。
# 文件路径:feedback_loop.py # 说明:回灌循环控制,包含退出阈值和最大轮数限制。 def feedback_with_loop(model, shallow_state, deep_activation, max_rounds=3, exit_confidence=0.7): current_state = shallow_state best_id = None best_prob = -1.0 for round_idx in range(max_rounds): # 将深层激活注入当前状态 updated_state = combine_activation(current_state, deep_activation) new_logits = model.forward_from_layer(updated_state, start_layer=0) probs = softmax(new_logits[:, -1, :]) candidate_id = argmax(probs) candidate_prob = max(probs) # 如果这一轮没有取得进步,提前终止 if candidate_prob <= best_prob: break best_id = candidate_id best_prob = candidate_prob # 达到退出置信度,提前结束 if best_prob >= exit_confidence: break # 用更新后的状态作为下一轮的浅层状态 current_state = updated_state return best_id, best_prob这个循环有两个退出条件:一是置信度超过exit_confidence,说明回灌已经产生了稳定结果;二是新分布概率不再增长,说明继续迭代没有额外收益。这两种条件缺一不可。如果没有最大轮数限制,模型可能在两个 token 之间反复横跳,白白消耗计算资源。
5.3 实验配置示例
工程实现时,可以把回灌相关的参数抽取成配置文件,方便在不同任务上快速切换策略。以下是一个 YAML 配置示例,字段都做了注释,方便理解。
# feedback_decode_config.yaml model: name: "your-llm-model" max_length: 2048 feedback: enabled: true shallow_layer: 2 # 回灌目标层,也就是注入发生的位置 deep_layer: -1 # 深层激活来源层,-1 表示最后一层 inject_mode: "linear" # simple_add / linear / concat 三种可选 exit_confidence: 0.7 # 置信度超过该值则停止回灌 max_feedback_rounds: 3 # 每个 token 最多回灌几轮 low_conf_threshold: 0.6 # 只有概率低于该值的 token 才触发回灌 eval: dataset: "your_eval_set" batch_size: 8 save_results: trueshallow_layer和deep_layer是回灌质量最重要的两个开关。如果浅层选得太靠前,注入的信息会经过太多层传播,效果可能被稀释;如果选得太靠后,注入前后差异不大,回灌就失去了意义。inject_mode直接决定混合方式:simple_add最省算力,linear更稳,concat表达力更强但需要额外的投影矩阵。建议先用simple_add跑通完整流程,再逐步尝试其他模式。
5.4 困惑度评估脚本
完成一段生成后,可以用下面的脚本计算困惑度,用来对比标准解码和回灌解码的效果。脚本基于常见的 HuggingFace Transformers API 风格,同样以示意为主。
# 文件路径:eval_perplexity.py import math import torch def compute_perplexity(model, tokenizer, text): encodings = tokenizer(text, return_tensors="pt") input_ids = encodings.input_ids with torch.no_grad(): outputs = model(input_ids, labels=input_ids) loss = outputs.loss ppl = math.exp(loss.item()) return ppl def compare_decode_strategies(model, tokenizer, prompt): # standard_text = standard_decode(model, tokenizer(prompt).input_ids, max_new_tokens=128) # feedback_text = feedback_decode(model, tokenizer(prompt).input_ids, max_new_tokens=128) # 伪代码演示:需要先运行上面的解码函数,再计算 PPL # ppl_standard = compute_perplexity(model, tokenizer, standard_text) # ppl_feedback = compute_perplexity(model, tokenizer, feedback_text) # return ppl_standard, ppl_feedback pass评估的重点不是跑一次就下结论,而是要在多个任务、多个领域的数据集上做统计对比。单独一个 prompt 的 PPL 波动很大,不能说明问题。
6. 运行结果与效果验证方式
回灌深层激活是否有效,不能只看 PPL 数字下降了就高兴。PPL 是一个总体指标,它有可能被少数 token 的大幅改进拉低,而大多数 token 并没有实质变化。更稳妥的验证方式是把评估拆成三个层次。
第一层是分布层验证。计算标准解码和回灌解码各自的平均 PPL、PPL 中位数,以及低置信 token 集合的平均 PPL。重点观察低置信 token 集合的 PPL 是否显著下降。如果整体 PPL 下降主要是低置信 token 贡献的,说明回灌确实把算力用在了刀刃上;如果整体 PPL 没变化,只是低置信 token 的分布被强行改成了另一个低概率分布,那说明注入方式可能需要调整。
第二层是任务层验证。找一组自带标准答案的任务,比如多跳问答、代码生成、数学推理。用准确率或匹配率来对比两种解码策略。这一步回答的核心问题是:PPL 降低是否真的转化成了任务正确率的提升。如果 PPL 降了但任务准确率没变,甚至下降了,说明模型只是变得更“自信”但没有变得更“正确”,这类回灌策略需要警惕。
第三层是稳定性验证。同一个 prompt 用不同的随机种子生成多次,看回灌策略的输出方差。如果回灌之后的输出在不同采样条件下结果差异很大,说明它的修正路径不稳定,工程上难以接受。判断成功的标准可以写成:
- 同一测试集下,PPL 下降幅度是否一致。
- 低置信 token 比例是否减少。
- 下游任务指标是否同步改善。
- 生成的文本是否出现语法或逻辑层面的新错误。
如果运行失败,第一步应该看的不是模型,而是回灌配置。最常见的问题是deep_layer和shallow_layer选错,导致注入维度和隐藏状态维度不匹配,代码直接报错。其次是回灌阈值设置过高,导致绝大多数 token 都进入回灌流程,生成速度慢到不可用。这种情况下,优先降低max_feedback_rounds或者调高low_conf_threshold。
7. 常见误区与排查思路
这个方向听起来简单,但实践中容易被误解。以下几个误区值得单独说明。
第一个误区:回灌等于让模型无限循环。实际工程中,每一层前向都需要真实计算,无限循环意味着推理时间无限增长。必须设置明确的轮数上限和退出阈值。回灌的目标不是“算到天荒地老”,而是用最少的额外计算获得最大的分布改进。
第二个误区:困惑度降低就一定代表回答质量提升。PPL 衡量的是模型内部置信度,不能等同于事实验证。回灌能让模型更确定,但确定的不一定是对的。所以评估时务必结合下游任务指标。
第三个误区:所有场景都适合回灌。对实时交互场景来说,额外增加一次前向可能就超出了延迟预算。回灌更适合离线批量生成、RAG 后处理、知识库问答等延迟敏感度较低的场景。
第四个误区:回灌需要修改模型结构。实际上它只是在推理阶段调整计算流程,模型权重无需变动。这决定了它可以作为现有推理系统的外挂模块,不需要重新训练或微调。
下面是一张常见问题排查表:
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 程序报维度不匹配 | 深层激活与浅层状态维度不一致 | 检查模型隐藏层维度配置 | 在注入前加线性投影,或改用concat模式并配置投影层 |
| 生成速度大幅变慢 | 触发回灌的 token 比例过高 | 统计低置信 token 占比和回灌轮次 | 调低low_conf_threshold或减少max_feedback_rounds |
| PPL 下降但任务准确率不变 | 回灌只增强了模型自信,未改变正确分布 | 分别统计正确/错误样本的 PPL 变化 | 调整深层激活来源层,尝试不同注入方式 |
| 回灌前后预测完全一样 | 深层激活信息没有有效注入 | 打印注入前后激活向量的差异度 | 检查注入函数是否生效,尝试concat模式 |
| KV Cache 与局部重算冲突 | 重算时未同步更新缓存 | 查看推理引擎缓存机制 | 在局部前向时暂时禁用或重建 KV Cache |
| 显存峰值翻倍 | 需要同时保存全层激活 | 检查显存监控曲线 | 只保存需要回灌的层,避免保留全部中间状态 |
8. 工程落地与最佳实践
回灌深层激活如果要进入生产环境,不能只写一个能跑的 Python 脚本,还要考虑性能、可观测性和回滚方案。以下几点是我认为工程落地时优先级最高的内容。
第一,场景选择要克制。不是所有请求都需要回灌。可以从用户请求中抽取特征,比如是否包含多步指令、是否涉及长文本、是否属于高风险回答,动态决定是否开启回灌。更精细的做法是:对同一个请求先用标准解码生成,记录每个 token 的最低概率;只有当最低概率低于某个阈值时,才对这部分内容进行二次回灌生成。这相当于把回灌设计成一个“按需触发的救援模块”。
第二,控制计算预算。合理设置low_conf_threshold和max_feedback_rounds。经验上,low_conf_threshold设置在 0.4 到 0.7 之间比较常见,太低会导致回灌几乎不触发,太高会让每个 token 都进入回灌。max_feedback_rounds建议起步为 1,效果不足时再逐步增加。每一步多一轮,延迟就多一份,不要一开始就把参数拉满。
第三,优化显存占用。回灌需要访问中间层激活,这在推理引擎中并不总是零成本。如果模型有 32 层,而我们只需要第 2 层和第 32 层的激活,就应只对这两层做 hook,不要保存全部 32 层的隐藏状态。使用 PyTorch 的register_forward_hook或类似机制,可以精准提取目标层激活,显著降低显存开销。
第四,注入方式优先从简单的开始。先实现simple_add,验证方向是否可行,再尝试linear或concat。一些研究表明,深层激活中混杂的语义信息可能需要在注入前做归一化或缩放,否则会破坏浅层的局部表示。你可以在combine_activation里加入一个缩放系数,把深层激活乘以一个小于 1 的权重再相加,通常比直接相加更稳定。
第五,日志和监控必须到位。生产环境至少记录以下字段:触发回灌的 token 数量、每个 token 的回灌轮数、回灌前后的最大概率变化、PPL 变化幅度、额外延迟消耗。这些指标既能帮助你调参,也能在出现问题时快速定位是不是回灌策略导致的异常。建议把回灌策略做成可配置开关,线上可以通过配置中心动态切换,而不是每次调整都要改代码发布。
第六,灰度发布和回滚。回灌虽然不改变模型权重,但可能改变生成分布,影响用户看到的回答。上线前先在一个小流量桶内运行,比较开启和关闭回灌时的用户反馈、任务成功率、延迟分位数。一旦发现效果下降,应该能立刻通过开关回滚到标准解码,不要等到故障扩散再处理。这里尤其要遵循最小权限原则:回灌只应在授权处理的数据上使用,不能因为策略改变而越权访问或修改数据。
第七,与现有推理优化方案的关系。量化、投机采样、KV Cache 优化都属于通用加速手段,回灌是在它们之上增加的一层策略。需要留意的是,回灌的局部前向可能会绕过部分缓存逻辑,导致量化模型下的数值表现和浮点模型不完全一致。上线前需要重新跑一遍评测,确认量化没有放大回灌引入的偏差。
9. 总结与后续学习方向
DeepMind 这个研究方向真正有价值的地方,不是“困惑度降低”这个结果本身,而是它展示了推理阶段还有一块可以精细优化的计算空间。传统自回归解码把每次前向计算看成不可分割的原子操作,回灌深层激活则把它拆成了“哪里不确定就修哪里”。这种 token 级别的按需计算,和最近业界强调的推理加速、成本控制其实是一体两面的关系:加速是减少不必要计算,回灌是把节省下来的算力花在关键位置。
如果你对这个方向感兴趣,下一步可以做三件事。第一,选择一个开源模型,自己实现一个最小回灌流程,先跑通代码,再体验调参过程。第二,整理一个多领域评测集,包含代码、数学、摘要、问答等任务,用本文第五节给出的评估脚本对比标准解码和回灌解码的效果差异。第三,关注后续研究在停止策略和注入方式上的进展,这两个细节目前还有很大的优化空间,也是决定回灌能否从论文走向工程的关键。
最后提醒一句:不要把 PPL 当作万能指标。它适合做同一策略下的相对比较,不适合跨模型或跨任务的绝对评价。真正负责任的落地方式,是把 PPL 和下游任务指标、线上反馈、延迟成本放在一起综合判断。希望这篇文章能帮你把这条新的推理期优化路线理解得更清楚,也让你在评估下一代模型和推理框架时,多一个观察维度。