V2 独立版愿景

能耗统计与碳排放

HeteroFlow 是本站的异构算力与大模型调度产品。能耗统计与碳排放。能耗统计与碳排放 能耗统计与碳排放是 HeteroFlow 的可观测性子系统:以 Agent 心跳上报的 GPU 实测瞬时功耗 为唯一数据源头,服务端对相邻心跳间隔做时间积分(Σ 功率×Δt),把"某个时刻多少瓦"的功率曲线沉淀为"一共耗了多少电"的长期账本——任务级与节点级两个…

能耗统计与碳排放

能耗统计与碳排放是 HeteroFlow 的可观测性子系统:以 Agent 心跳上报的 GPU 实测瞬时功耗为唯一数据源头,服务端对相邻心跳间隔做时间积分(Σ 功率×Δt),把"某个时刻多少瓦"的功率曲线沉淀为"一共耗了多少电"的长期账本——任务级与节点级两个作用域各自累计;碳排放按部署方配置的电网排放因子从能耗换算得出,推理服务另有 Token/J 能效指标。

这个模块的底色是诚实计量:功耗是实测值,没有实测来源的场景(老版本 Agent、厂商管理工具不提供功耗读数)一律按"未知"处理,界面显示"—"而不是 0;失联造成的数据缺口如实记入覆盖率而非悄悄补值;碳排放因子未配置时对外不展示绝对值。所有口径都围绕一条原则展开——宁可偏低,不可虚高;宁可空白,不可编数。

功能总览

能力一句话说明状态
任务级能耗心跳功耗按相邻间隔积分成 kWh,按占卡关系归属到任务已实现
节点级能耗节点累计能耗 + 逐卡/整机功耗趋势曲线已实现
推理能效 Token/J任务组 token 产出 ÷ 组能耗焦耳,不可计算显示"—"已实现
功耗采集多厂商适配6 家针对性解析 + 3 家通用文本兜底,昇腾未覆盖不猜已适配(国产卡无真机)
诚实口径未知≠0(null 语义)、覆盖率披露、Δt 上限防失联灌入已实现
碳排放因子环境变量 / config.yaml 两级配置,随响应带来源与配置状态已实现
碳排可见性分级因子未配置时:平台运维可见(带非权威标注)、对外隐藏已实现
共卡任务能耗分摊一卡多任务按份额分摊(算力>显存>等分),守恒且标注估算实验性(共卡功耗行为待真机验证)
CPU 功耗估算趋势利用率×TDP 线性估算,单独成图、不计入总量已实现
仪表盘能耗汇总按查看者可见节点聚合,无数据整键省略不显示 0已实现

采集与积分机制

Agent 端:功耗采集

数据链路的第一环在 Agent 的 GPU 检测器里(agent/pkg/detection/power.go)。各厂商管理工具的功耗输出形态分三类,解析集中在一个文件,厂商检测器只留一行调用,便于一眼看出"哪些厂商真的采到了功耗、哪些没有":

形态示例解析函数接入厂商
A. 显式查询列nvidia-smi --query-gpu=power.drawparsePowerValueNVIDIA(nvidia.go:209)
B. 表格单元格 "Pwr:Usage/Cap"昆仑芯 "88W / 400W"、寒武纪 "106 W/ 250 W"parsePowerPair昆仑芯(kunlun.go:197)、寒武纪(cambricon.go:152)
C. 缩进 key:value海光/天数 "Usage : 44 W"parsePowerField壁仞(biren.go:185)、海光(hygon.go:235)、天数智芯(iluvatar.go:172)、寒武纪 info 形态(cambricon.go:210)
自由文本兜底"Power Draw : 123.45 W"parsePowerFromText沐曦/燧原/摩尔线程(detection.go:333,无可信逐字样例,按通用正则兜底)

两个关键防御:

  • 上限词过滤(power.go:73):兜底解析服务的厂商输出里普遍同时存在当前功耗与功耗上限两类字段,且上限行经常排在前面——取错方向会"把一张空载的卡记成满载",产生一个看起来合理的错数,能耗与碳排整体虚高。解析时整行含 cap/limit/max/min/tdp/rated 等词一律跳过,明确写了 draw/usage/current/avg 的行优先级最高,与出现位置无关。
  • 可用性判据看 MetricsAvailable 不看值:功耗在本轮真正解析命中时才在 metrics_available 里登记 "power",心跳的 gpu_stats 里也才携带 power_watts 键(detection/types.go:89-96)——键缺失 = 功耗未知。驱动异常返回的 "N/A"、"[Not Supported]" 一律判为未命中,不写 0。

采集还有一个容易忽略的细节:某一拍没采到时,Agent 不清掉上一轮的旧值(power.go:209 的 setPowerMetric 在未命中时什么都不做)——metrics_available 才是"本轮真正采到了什么"的权威声明,界面判断"这块卡支持功耗采集吗"看的是这个列表里有没有 "power",而不是功耗值是否非零("卡真的不耗电"在物理上不存在,非零值无法区分"采到了"与"残留旧值")。

昇腾未覆盖是如实留白:npu-smi 的功耗字段名在公开资料里有多种写法,没有可信的逐字样例就不猜格式(power.go:20 注释),对应节点的能耗区会显示"无功耗数据"。

服务端:解析与曲线落库

心跳到达服务端后,每张卡的功耗由 gpuPowerFromStat(energy_accrual.go:35)解析:键缺失、值不是数值类型、值为负(物理上不可能,按脏数据处理)三种情况都返回 nil = 未知。这个 nil 一路保持到落库——node_metrics 表的 gpu_power_watts 聚合列刻意可空且无默认值(postgres/schema.go:486):NULL 表示这一拍功耗未知,与 0 W 严格区分,"默认 0"会让能耗积分把"没采到"当成"真的不耗电",在界面上画出一条贴地的假曲线。

同一拍心跳写三个地方用同一个采样时刻 sampleAt(node_heartbeat.go:1084):监控曲线(node_metrics.timestamp)、能耗账本(energy_accumulators.last_sample_at)、GPU 健康样本——各取一次 time.Now() 会让 Δt 带上处理耗时,积分出的能耗与曲线对不齐。

积分:Σ 功率×Δt

积分编排入口是 accrueHeartbeatEnergy(energy_accrual.go:77),在心跳处理主流程中以旁路方式调用(node_heartbeat.go:1117):任何一步失败只记日志、不影响心跳成功——心跳失败会让节点被判离线,代价远大于少算一拍能耗。它做两件事:

1. 节点级累计:本拍全部已知功耗之和(sumKnownPower,energy_integrate.go:113)。部分卡支持功耗、部分不支持时按已知部分求和——偏低但不虚高。全部未知时连累计行都不建,避免给一台永远采不到功耗的机器留一行全 0 的账。

2. 任务级累计:按"这一拍每张卡被哪个任务占用"分摊。独占卡整卡归该任务;共卡按份额分摊(见下文)。任务归属复用调度器 GPU 记账的同一套持卡关系(GPUHoldingTaskStatuses),不另立标准——否则会出现"监控曲线算这张卡属于任务 A、能耗账本算它属于任务 B"的错位。

积分本身是纯函数 integrateEnergyInterval(energy_integrate.go:75):

能耗增量(Wh) = 功率(W) × 计入秒数 ÷ 3600

四种边界全部有明确定义:功耗未知 → 不积分也不记缺口("未知"不是"0 W",这一拍不参与任何累计);首拍 → 没有区间可积,只确立时间起点;Δt ≤ 0(多实例时钟偏差/重复投递)→ 丢弃,不倒扣能耗;Δt 超上限 → 钳制见下。

Δt 上限是防虚高的关键设计(energy_integrate.go:18-31):积分用的是上一拍到本拍的实际间隔,节点失联 3 小时再回来时这一拍的 Δt 是 10800 秒——按当时功耗一乘,凭空灌进 0.9 kWh 假能耗(那段时间卡可能根本没在跑,甚至整机断电)。上限默认 60 秒(ENERGY_MAX_GAP_SECONDS 可调),把单拍注入压在一个心跳量级内。超限处理是"钳制"而非"整段丢弃":计 60 秒能耗,剩余时间如实记入 gap_seconds——短暂抖动时整段丢弃会让长训练任务能耗明显偏低,钳制保留了一个心跳量级的真实功耗,误差有界且被缺口披露。

落库与并发:读-CAS-写

累计量落在 energy_accumulators 表(postgres/schema.go:506):一个作用域(task/node)一行,energy_wh、covered_seconds、gap_seconds、sample_count、首末采样时刻。为什么不从 node_metrics 现算:node_metrics 有保留期(默认 7 天,物理删除),而"这个训练任务一共耗了多少电"必须在任务结束后长期可查——曲线受保留期限制,累计量单独落账。

存储接口刻意做成"读-CAS 写"而不是一条 UPSERT 聚合 SQL(storage/interface.go:590):Δt 需要上一拍的采样时刻,它就存在累计行里;把积分算式写进 SQL,memory 实现就无法与 PostgreSQL 实现共用同一份算式。现在的分工是 Δt 与增量由 Go 侧纯函数计算(可单测钉住性质),存储层只负责原子累加,以 last_sample_at 做乐观并发防护:Accrue 只在库里仍是调用方读到的那个时刻才累加;不匹配说明别的实例已消费过这一区间,本次调用什么都不做——宁可漏算一拍,也不能把同一区间的能耗加两遍。多实例部署下同一节点的心跳可能落到不同实例上,这条 CAS 语义保证能耗与碳排不会翻倍。memory 与 PostgreSQL 两套实现行为一致(interface.go:57),能耗是功能特性,不是只有 PostgreSQL 才有的运维扩展。

共卡分摊(实验性)

一张物理 GPU 可以同时被多个任务持有——软件分片、MPS、推理显存比例共卡、训练借卡都是共卡来源。而硬件只给整卡功耗(nvidia-smi/NVML 不提供 MIG 实例级功耗),所以任务级能耗在共卡时只能按份额分摊估算(energy_shared_card.go)。分摊规则:

  • 份额维度按优先级取所有参与者都声明了的最高维度:算力份额 > 显存份额 > 等分——一个声明了 0.3 算力、另一个什么都没声明的组合没有可比口径,硬凑权重就是编数,退到下一维度;
  • 权重在参与者之间归一化,Σ份额 == 整卡功耗(守恒):声明之和不足 1 时,空闲部分的功耗也按比例记给这些任务——卡是它们开着的;
  • sleeping 的推理实例已释放物理显存,卡上还有非 sleeping 持卡者时其权重为 0;
  • 分摊部分单独累计(shared_energy_wh 列,DDL 031),API 据此标注归属口径:exclusive(每拍都独占)/ shared_estimated(至少一拍共卡,该部分为估算)/ unknown(累计行建于口径上线前,归属不可考)。判据用秒数而非能耗——共卡那几拍若恰好整卡 0 W,能耗为 0 但它确实共过卡。

此能力为实验性:分摊算法有单测钉住守恒与优先级性质,但真实 GPU 共卡下的功耗行为尚未经真机验证。

曲线查询的窗口规则

积分解决"总量",曲线回答"过程"。任务级曲线(taskPowerSeries,energy_api.go:330)取任务运行区间 ∩ 监控保留期的窗口,与任务 GPU 指标接口共享同一套窗口规则;过滤按逐卡监控明细里的占用关系(含共卡持卡者)。三条口径值得单独指出:

  • 功耗未知的采样点直接跳过,不入列也不补 0——补 0 会让曲线出现假的谷值;
  • CPU 估算点只在该拍确实有本任务占卡时给出,否则那是节点上别人的 CPU 负载;且它用的仍是整机 CPU 利用率,平台没有按任务的 CPU 计量,这一点连同"估算"口径一起写进随响应下发的说明里;
  • 节点级响应里的"整机合计"曲线直接用入库的聚合列(各卡已知功耗之和),该拍全未知时不入列;曲线点用 GPU 索引 -1 表示"整机合计",不与任何一张实体卡混淆。

汇总查询(仪表盘/任务组)还有一个行数语义:SumByScopeIDs 只汇总真实存在的行,并返回实际命中的行数,调用方据此区分"总量为 0"与"一条数据都没有"——后者在界面上是"—"不是 0 kWh。

推理能效 Token/J

推理任务额外计算能效指标(taskTokenEfficiency,energy_api.go:407):分子是用量事件表里该任务组的 prompt+completion token 总数,分母是该组全部任务的能耗之和(焦耳)。因为推理网关的用量事件只记录到 task_group_id 这一层,Token/J 的权威作用域因此是任务组——单副本组下等价于单任务能效。能耗为 0、token 数为 0、任务不属于任何组、存储后端不支持用量查询,四种情况都返回带原因的"不可计算",界面显示"—"并附具体原因:0 token/J 的字面含义是"耗了电但一个 token 都没产出",与"没有数据"完全是两回事,不能混用。

碳排放计算

计算公式与因子配置

碳排放(kgCO₂)= 累计能耗(kWh)× 电网排放因子(kgCO₂/kWh),换算在纯函数 carbonKgFromKWh(energy_integrate.go:237)。因子取值三级优先(resolveEmissionFactor,energy_integrate.go:197):

1. 环境变量 ENERGY_GRID_EMISSION_FACTOR(配错了 WARN 后回落 config,不是直接废掉 config);

2. config.yaml 的 energy 节(K90):

energy:
    emission_factor_kgco2_per_kwh: 0.5703
    emission_source: 生态环境部 2024 年发布的 2022 年度全国电网平均排放因子

3. 内置占位默认值 0.55。

每个能耗响应都随数值返回 emission_factor 对象(因子值 + 来源说明 + 是否由部署方显式配置),界面必须展示。

占位默认值的诚实口径

0.55 是量级参考的占位默认值,不是任何机构发布的权威因子——中国全国电网平均排放因子由生态环境部/国家发改委按年度发布,不同年份、不同口径取值不同。平台不内置"精确到小数点的权威数值",那会把一个需要引用来源与年份的监管口径伪装成系统常量。配套防线:只配置了来源说明、没配置数值时,来源文案会自动追加"注意:仅配置了来源说明,排放因子数值仍是平台占位默认值",不得声称已配置——否则界面会把占位数值当成有来源的权威值展示。

可见性三档(D8)

碳排放绝对值的展示按用户决策分级(carbonVisibility,web/src/utils/energyFormat.ts:106),判据只看"因子是否已配置"与"查看者是否平台运维"两项,任务详情/节点监控/仪表盘三处界面共用一套规则避免口径漂移:

档位条件界面行为
public因子已由部署方显式配置(带来源)所有能看到能耗的人可见碳排数值与来源
internal因子为占位默认值,查看者是平台运维(role=admin)可见数值,但带醒目的"非权威 · 仅内部参考"标注,因子提示注明"占位默认值"
hidden因子为占位默认值,查看者是租户成员不展示碳排放绝对值(卡片不渲染),脚注显示"碳排放未展示:平台尚未配置有出处的电网排放因子",只展示能耗与趋势

要对外展示碳排放,必须由部署方配置有出处的电网排放因子——平台不自行编造权威数值。

页面功能介绍

能耗展示由共用组件 EnergyPanel(web/src/components/common/EnergyPanel.tsx)承载,三条展示口径写死在组件里:null 一律显示"—"(formatter 层统一处理,组件里不允许出现 ?? 0);GPU 功耗(实测)与 CPU 功耗(估算)分成两张图——放一张图会让估算曲线和实测曲线视觉等价,用户没有理由知道其中一条是猜的;排放因子口径随数值展示。

数字本身的格式化也防着一类误读:小数位按量级自适应(energyFormat.ts:20),固定两位会把 0.0003 kWh 显示成 "0.00 kWh"——又一种"把有数据显示成没有"的形态;大数少小数、小数多小数,保证有值就看得见。图表遵循可视化规范:同一张图只画瓦特单轴;两张以上系列必有图例;曲线颜色按 GPU 索引固定取色(同一张卡在任何筛选状态下都是同一个颜色,色随实体不随排名);超过 8 张卡的节点折叠多余系列;"整机合计"是派生量,用中性灰虚线,不与任何一张实体卡的身份色冲突。

任务详情页的能耗区(/tasks 详情面板)

任务详情内嵌能耗区,每 20 秒自动刷新(TaskPage.tsx:205)。

区块内容背后 API
累计能耗卡片该任务 kWh,hint 显示采样次数;共卡时显示"含共卡分摊估算 X kWh"或"共卡归属不可考"并黄色警示GET /tasks/{id}/energy totals.energy_kwh
碳排放卡片kgCO₂(可见性三档控制渲染);hint 显示因子值与配置状态同上 totals.carbon_kg_co2 + emission_factor
推理能效卡片Token/J(仅推理任务),不可计算时显示具体原因(无调用量/无能耗数据/不属于任务组)而非 0同上 token_efficiency
数据覆盖率卡片覆盖率百分比,hint 显示缺口或已积分时长;低于 90% 黄色警示并显示"有 X 的时间没有功耗数据……实际能耗高于此处显示的数字"同上 totals.coverage_ratio / gap_seconds
归属口径横幅attribution 为 shared_estimated/unknown 时展示"共卡分摊估算"/"归属不可考"徽章与服务端原文说明同上 attribution + attribution_note
GPU 逐卡功耗图(实测)任务所占各卡功耗曲线,色随 GPU 实体固定;空态如实列出三种原因(agent 不上报/管理工具不提供/超出保留期)同上 power_points(来自 node_metrics.gpu_data,受保留期限制)
CPU 功耗趋势图(估算)独立成图带"估算"角标,脚注注明"不计入任务/节点能耗总量,也不参与碳排放与 Token/J 计算"与 TDP 基数同上 cpu_estimate
脚注GPU-only 口径说明、碳排因子来源(未配置时注明"口径未配置,数值仅供量级参考")、采样区间同上 note / emission_factor.source

节点监控页的能耗区(/nodes/monitor)

节点监控页右侧面板内嵌,随时间范围选择器联动(1 小时~7 天),自动刷新开关默认开启、每 30 秒刷新曲线与能耗(NodeMonitorPage.tsx:110、136-139)。

区块内容背后 API
累计能耗/碳排放/覆盖率卡片节点级累计(自节点首次上报功耗起长期累计,不受曲线保留期影响)GET /nodes/{id}/energy?hours= totals
GPU 逐卡功耗图(实测)每张卡独立系列 + 中性灰虚线"整机合计"(各卡已知功耗之和)同上 power_points + total_power_points
CPU 功耗趋势图(估算)与任务侧同款、同口径声明同上 cpu_estimate
区块标题因子未配置且查看者为租户成员时标题只写"能耗"不提"碳排放"前端 energySectionTitle 判定

仪表盘能耗汇总

首页 Dashboard 的统计里携带 energy_summary(GET /api/v1/dashboard/stats):按查看者可见节点集合聚合的能耗与碳排口径(dashboard.go:422,与集群统计用同一份节点集合,租户视角天然只含自己看得见的机器)。后端没有能耗数据时该键整体省略,前端与"下发了但 energy_kwh 为 null"同样显示"—"——两种都是没有数据,都不能显示成 0 kWh。

后端逻辑概念表

概念定义代码出处
energy_accumulators能耗累计表,一个作用域一行,长期保留(不复用 node_metrics 现算的原因:那张表有保留期)core/pkg/storage/postgres/schema.go:506
scope / scope_id累计作用域:task(按任务)与 node(按节点)两类core/pkg/types/energy.go:29-36
gpuPowerFromStat心跳单卡功耗解析:键缺失/非数值/负值 → nil=未知,绝不退化成 0core/pkg/api/energy_accrual.go:35
sumKnownPower已知功耗求和:部分卡未知按已知部分算(偏低不虚高),全未知返回 nilcore/pkg/api/energy_integrate.go:113
integrateEnergyInterval积分纯函数:W×秒÷3600,四种边界(未知/首拍/时间倒流/超上限)全部显式core/pkg/api/energy_integrate.go:75
Δt 上限相邻心跳可计入能耗的最大间隔,默认 60s,ENERGY_MAX_GAP_SECONDS 可调;防失联期间单拍灌入数小时假能耗core/pkg/api/energy_integrate.go:31
covered/gap_seconds诚实度量:真正积分的秒数与被上限截掉的秒数,覆盖率=covered/(covered+gap)core/pkg/storage/postgres/schema.go:499-501
EnergyStore.Accrue原子累加 + last_sample_at CAS:并发区间已消费则什么都不做,宁可漏一拍不加两遍core/pkg/storage/interface.go:602-611
attributeTaskPower按持卡关系把各卡功耗分给任务:独占整卡、共卡按份额、未知卡不分摊core/pkg/api/energy_shared_card.go:270
splitCardPower共卡分摊:份额归一化保证各任务之和==整卡功耗(守恒)core/pkg/api/energy_shared_card.go:164
attribution任务能耗归属口径三值:exclusive/shared_estimated/unknown,shared_* 列 NULL=早于口径上线不可考core/pkg/api/energy_api.go:139-168
resolveEmissionFactor排放因子三级优先级(env > config > 占位 0.55),只写来源不写数值不得声称已配置core/pkg/api/energy_integrate.go:197
tokensPerJoule推理能效:组 token 总数÷组能耗焦耳;能耗 0/token 0/负数都返回 nil 显示"—"core/pkg/api/energy_integrate.go:254
estimateCPUWattsCPU 功耗估算:利用率×TDP 线性模型(TDP 默认 150W 可配),不进累计不参与碳排core/pkg/api/energy_integrate.go:299
carbonVisibility碳排可见性三档判据(public/internal/hidden),三处界面共用web/src/utils/energyFormat.ts:106

API 参考

接口说明
GET /api/v1/tasks/{id}/energy任务能耗+碳排+Token/J+逐卡功耗曲线+CPU 估算+归属口径;鉴权与任务 GPU 指标接口同口径(enforceTenantAccess + 资源归属)
GET /api/v1/nodes/{id}/energy?hours=节点累计能耗+逐卡/整机功耗趋势+CPU 估算;hours 缺省 24、上限为监控保留期;节点可见性与节点详情接口同判据(实现上走节点详情接口的后缀分发,与节点其它子资源同批做法)
GET /api/v1/dashboard/stats含 energy_summary 键(可见节点聚合;无数据整键省略)

配置项:

配置默认说明
ENERGY_MAX_GAP_SECONDS60相邻心跳可计入能耗的最大间隔(秒)
ENERGY_GRID_EMISSION_FACTOR0.55(占位)电网排放因子(kgCO₂/kWh)
ENERGY_GRID_EMISSION_FACTOR_SOURCE诚实占位文案因子来源与年份,界面原样展示
ENERGY_CPU_TDP_WATTS150CPU 估算的 TDP 基数(瓦),按机型配置
config.yaml energy 节—emission_factor_kgco2_per_kwh / emission_source,优先级介于环境变量与占位默认之间
METRICS_RETENTION_HOURS168(7 天)功耗曲线保留期(只影响曲线,不影响累计总量)

已知边界

1. 功耗采集厂商覆盖不全:NVIDIA/壁仞/昆仑芯/寒武纪/海光/天数智芯有针对性解析;沐曦/燧原/摩尔线程走通用文本兜底,能否命中取决于实际输出里是否有当前功耗行;昇腾未覆盖(字段格式多样无可信样例,不猜)。国产卡功耗采集全部无真机端到端验证,属"已适配"口径。

2. 能耗依赖 Agent 上报实测功耗:老版本 Agent 心跳不带 power_watts 键、厂商管理工具不提供功耗读数、驱动返回 N/A 的卡,对应时间段就是没有数据——显示"—",不是 0。没有实测来源的场景不会得到估算值(CPU 估算除外且单独标注)。

3. 曲线与总量的保留期差异:功耗曲线来自监控时间序列,受保留期限制(默认 7 天);累计总量独立存储、长期保留。早于保留期的区间没有曲线但总量仍在。

4. 能耗只统计 GPU:CPU 功耗无实测来源(Agent 不读 RAPL/IPMI),只有单独标注"估算"的趋势线,不计入任务/节点能耗总量,也不参与碳排放与 Token/J 计算。CPU 估算用的是整机利用率,平台没有按任务的 CPU 计量。

5. 共卡分摊是估算(实验性):硬件只提供整卡功耗,共卡期间的分摊按份额估算并如实标注 attribution;共卡期间的功耗曲线是整卡实测读数,不是本任务分得的份额。真实 GPU 共卡功耗行为待真机验证。早于分摊口径上线的累计行归属不可考(attribution=unknown)。

6. 失联期间能耗偏低而非补值:Δt 超上限的时间记入 gap_seconds,界面按覆盖率披露"实际能耗高于此处显示的数字"——不会虚高,也不会悄悄补一个估计值。

7. Token/J 的权威作用域是任务组:用量事件只记录到 task_group_id 维度,单副本组下等价于单任务能效;任务不属于任何组、或存储后端不支持用量查询(memory 模式)时不可计算,显示"—"。

8. 碳排放绝对值默认不对外:部署方未配置有出处的因子时,租户用户看不到碳排放数值(只有平台运维在内部界面可见带非权威标注的参考值)——这是刻意的诚实设计,不是功能缺失。

<!-- 状态标注说明: 已实现=代码+测试钉住 / 已适配=解析逻辑对齐厂商工具输出但无真机 / 实验性=算法有测试钉住但关键行为未经真机验证 -->