GPU 算力价格与供给监控
目标
- 做什么:两端各一套证据。成本端把「实际成交价、市场化报价、标准云报价、 可用供给」四类放进同一个口径框架,识别 GPU 算力从紧缺、平衡到宽松的边际拐点, 覆盖 B200 / H200 SXM / H100 SXM 三个 MVP SKU;需求端把推理 token 的量与价 放进另一套口径,回答「算力这么贵,下游用量和付费跟不跟得上」。 成交价的趋势窗口固定 90 天;供给与 token 两条序列补不回去,一律出全部历史。
- 不做什么:不覆盖 GPU 芯片二级市场买卖价、云厂商股票、推理毛利率与单位 token 成本地板(要人工吞吐表,尚未启用);不给买卖建议、目标价、仓位; 不在脚本里下结论;不用前值冒充最新值。
- 给谁用:需要判断算力成本与产能松紧的研究员、算力采购方、AI 基础设施投资人。
适用边界
- 时区与结算:所有日期按 UTC。Ornn 的日度指数是 T-1 结算,所以「今天」的跨平台中枢 结构性地不含 Ornn——这不是故障,报告里要说明锚定的是哪一天。
- 单位:成本端一律 USD / GPU·hour(整机报价除以
node_gpu_count后入库,换算过程 存在unit_basis里可审计);需求端一律 USD / Mtok 与 tokens/day。 两套单位不许出现在同一个减法里——把它们接起来要等成本地板(P1,未启用)。 - SKU 粒度:canonical id 到 SKU 级(
H100 SXM而不是H100)。同日 Runpod 的 SXM/NVL/PCIe 实测差 35%,合并会让「价格中枢」随混样比例漂移。 - 历史不同龄:Ornn 是唯一有历史接口的源,免费层直接给滚动 3 个月日度历史, 上线当天就有 90 天曲线;Vast 与 Runpod 只回当下快照,OpenRouter 的日榜同样没有 历史接口,序列只能从首次采集当天开始往后长,补不回去。报告里必须讲清这个不对称。
- 窗口只管成交价:90 天窗口的前提是「掉出窗口的点明天还能重新取回来」, 这只对 Ornn 成立。可用供给、固定篮子真价格、混合价这三条一律出全部历史—— 它们一旦被窗口滚过去,掐掉的那一段是永久丢失的,而且序列越早的一段越珍贵 (首次采集日就是它的绝对起点)。供给面板会标出「全部历史 · 起止 · 天数」, 写报告时不要把它们和旁边那张 90 天成交价曲线当同一个时间轴读。 固定篮子与混合价只出数不出图:仪表盘给最新值与窗口变化,走势和两者之差 (mix_shift)由正文用数字讲。
- 脑 / 手边界:脚本做采集、单位换算、分位数、变化率、折价、供给指数、评分算术、渲染。 「这是不是真拐点、这个信号采不采信、缺的这块影响多大」全部由模型判断。
- token 历史是另一条序列:日度模型级补不回去,但厂商级周度能回到 2025-09-01。
两者口径不同,只有后者能回答"过去一年买家结构怎么搬的"——结构效应指数首跑
100 → 44.8,光结构迁移一项就把均价拉低约 55%。不要把它的百分比和日度
mix_shift放进同一个句子里比大小。 - token 侧的三条硬边界:①OpenRouter 是 merchant API 层的偏斜样本,不是全市场
(first-party 订阅、超大厂内部推理、企业直签合同都在外面);②spend 是按挂牌价计的
名义支出,不是实际账单——缓存命中只按约 12% 计费而命中率不可观测;
③
reasoning/cached/tool_calls三个字段源侧全为 0,token 通胀拆不了。 这三句写报告时必须出现,完整口径见references/token_taxonomy.md。 - 冷启动:供给序列不足 21 天时评分会拒绝出数(
usable=false);token 序列不足 7 天时mix_shift与三分解同样不出数。这是正确状态,不要自己心算一个近似值填进去。 - 告警未校准:阈值是起始配置,默认
record_only只入库不推送。积累 2–3 个月历史后才谈校准。
领域方法论
四层价格 + 一层供给
价格分四层:①市场成交价(Ornn OCPI)、②即时 Offer、③市场聚合报价(对 Offer 求分位数)、 ④标准云报价(on-demand / spot / preemptible / committed)。供给不是第五种价格, 是横向指标,用来回答「价格在动,是需求变了还是供给变了」。
②和③是同一批数据的两个视图,不是两个独立信号。 评分时把两者都算进价格维度,
等于把 Vast 的同一批 offer 数了两遍。完整口径定义、八条可比性规则、折价的分子分母、
代际溢价怎么算,见 references/pricing_taxonomy.md。
可比性优先于精确性
跨平台比价出错,九成不是算错,而是把两个不同口径的数放进了同一个减法。 八条硬规则里最容易踩的三条:同 price_type(承诺期价不是按需价)、 同 market_segment(Runpod 的 secure 和 community 是两个市场)、 同源集合(跨平台中枢在源上下线那天会跳,实测能凭空「跌」25%)。
一致性才是拐点
单一价格信号下行不算拐点。确认型宽松要求价格类 ≥3 个信号 + 供给类 ≥2 个信号, 连续 ≥10 个真实采到数的日子,中间不允许缺口。同样的门槛反向用于确认型收紧—— 告警必须双边:实测 2026-05~08 三个月 OCPI-B200 是 +23%、H100 +5%,方向是收紧, 单边告警在这种行情下会长期零触发。
参照系:不只看三个主力 SKU
A100 SXM4 与 RTX 5090 也进库(Ornn 免费层同样给 90 天历史,取它们零额外成本),
但不进首页三型号同屏。它们的用处是回答一个主力 SKU 自己答不了的问题:
高端在涨,是某一代 GPU 结构性紧缺,还是整条算力曲线都在抬? 如果连消费级的
RTX 5090 都在同步上行,那更像整体需求在抬。
需求端:量和价要一起看,价还要拆成两半
token 侧不做 V × P 那种指数——按用量加权的平均单价 ≡ 总支出 ÷ 总 token 数,
乘出来是恒等式,不可证伪。真正有信息量的是把价格拆开,并行跑两个指数:
- 混合价(当期权重)= 名义 spend ÷ 付费 token。它会被购买结构迁移污染, 单看没有意义——没有任何一家降价,它也能跌。
- 固定篮子真价格(锁基期权重的 Laspeyres)。篮子成员是「模型家族 × 变体」, 家族 = 剥掉日期后缀的 slug,所以家族内的版本升级降价算真降价,跨家族迁移不算。
两者之差就是 mix_shift,即"往便宜模型迁移"贡献了多少百分点。这是本 skill 唯一的原创信息:市场上争论 token 价格跌是不是 AI 见顶的人,没有一个把它拆开。
量的主轴只用 paid:实测零价 token 占 40.1%,其中免费档只有 7.8%,主体是匿名 stealth 模型在免费放量,它进出榜单就能让总量凭空跳一大截。
需求端还要看「谁在消费」
模型 × 变体答的是钱花在哪个模型上,答不了谁在花。调用方维度补上后一半: 实测榜上 20 个应用吃掉当日 38.0% 的 token,却只占 9% 的请求——agentic 应用单次调用 10 万量级的 token,比长尾对话高一到两个数量级。token 增长里有多少来自"更多人用"、 多少来自"每次用得更重",这是唯一能分辨的抓手。
三条硬边界:份额的分母是榜上应用的合计而不是全站(榜单名次不连续,两头不靠);
榜上合计占全站的比例是下界,剩下那部分混着未上榜应用、不露出的名次和没有 app
归属的直连流量,拆不开;这一路没有 spend,只能答"谁吃 token 最多",
答不了"谁花钱最多"。完整口径见 references/token_taxonomy.md §五之四。
价格降了,先分辨成因
价格跌 + offer 份额涨 + 可用 GPU 涨 = 供给释放,这是真宽松;
价格跌但供给没动 = 更可能是需求走弱或单源促销;
价格跌只发生在一个源 = 先怀疑口径,再怀疑市场。
完整的读法、评分与告警怎么读、什么时候该说「不知道」,见 references/signal_reading.md。
工作流程
- 首次部署先回填:
python scripts/backfill.py。两路历史一次拉满——Ornn 的 90 天 日度成交价,以及 OpenRouter 的厂商级周度量(51 个已结算周,回到 2025-09-01, 落进token_volume_history)。逐条报出实际覆盖到哪天、多少个点、有没有缺口。 之后这两条历史由每日采集自动续上:Ornn 自带--history-days 120重拉, OpenRouter 周度量在采集 openrouter 时全量重取并幂等 upsert(最后一个未结算周 会被丢弃,所以结构史最新点天然停在上一完整周)。backfill 只在补采集空洞时再跑。 注意 token 的历史与日度序列不是一条线:口径不同(实测同日 各厂商比值 1.42–1.75,非常数倍),只能看份额与增速,不能读绝对水平。 - 采集:
python scripts/collect.py。逐源独立,单源失败不阻断其它源, 成败与耗时写进gpu_collect_runs。入库前跑一遍跨日离群检测,命中的行 标quality_flag=suspicious并在摘要里列出来(打标不丢弃)。 - 算指标:
python scripts/metrics.py --output evidence/gpu-<date>.json。 产出确定性证据包——变化率、分位数、报价分散度、折价、供给指数与广度、 评分分项、确认型拐点、源健康度,以及需求端的token_market段 (量的三条线、双价格指数、mix_shift、名义 spend 与它的缓存敏感性带、 日度模型构成,以及调用方维度token_market.apps)。 触发的告警同时写进gpu_alerts;token 侧本轮不设告警,序列没长出来之前 阈值无从谈起。 - 读证据:先看
source_health判断今天数据完不完整,再看每个型号的cross_platform_median.anchor_date/anchor_basis确认锚在哪一天什么口径, 然后逐条检查changes.*.usable。usable=false的百分比不许进结论。 - 判断:按
references/signal_reading.md的读法,分辨价格变动的成因, 决定评分和告警采不采信,给出整体结论与三个型号各自的状态。需求端按第七节那张 成因矩阵读:GPU 涨价 + token 量涨 = 真需求拉动;GPU 涨价 + 量平 = 供给收缩。 混合价单独下跌不算降价,必须和固定篮子一起看。 - 写报告:按
references/report_template.md写 Markdown 到reports/gpu-<report_date>.md。report_date固定取开始生成报告时的本地执行日期, 不从obs_date、evidence.asof或任一anchor_date推导;frontmatter 的date与正文标题也用这个执行日期。另写data_asof: <evidence.asof>保留数据截止日, 面板里的真实最新数据仍按各自anchor_date解释。 需求端单独一小节,frontmatter 里对应verdict.token与verdict.panels.tokens。 frontmatter 的verdict块是仪表盘唯一认的判断契约,必须填;正文写仪表盘表达不了的 推理,不要重抄标准报价矩阵——那是仪表盘的活。 - 出仪表盘:
python scripts/render_report_html.py --evidence evidence/gpu-<data_asof>.json --input reports/gpu-<report_date>.md。 HTML 默认同样输出为reports/gpu-<report_date>.html,页头并列显示报告日与数据截止日。
补录 CoreWeave / Nebius 挂牌价时,编辑 config/attested_prices.yaml(每条必须带 as_of 与
source_url),再重跑 collect 与 metrics。动手前先读 references/source_notes.md 的
「2026-08-25 核对到的东西」——CoreWeave 报整机价要除以 8、Nebius 报的本来就是单卡价,
这是抄错概率最高的地方。条目超过 30 天自动降级 stale,退出核心指标。
数据获取(脚本抓手)
scripts/collect.py —— 每日采集
python scripts/collect.py # 采全部启用的源
python scripts/collect.py --sources ornn,runpod # 只采指定源
python scripts/collect.py --sources openrouter # 只采需求端的 token 量价
python scripts/collect.py --date 2026-08-25 # 指定观测日
python scripts/collect.py --history-days 120 # Ornn 回填窗口
python scripts/collect.py --dry-run # 采但不写库
输出一份 JSON 摘要:每个源的 status(ok / empty / failed)、行数(价格 / 供给 /
token 三类分开计)、耗时、未映射的原始标识、降级说明。全部源都失败才返回非 0。
openrouter 这一路两个接口一起取:rankings 拿模型 × 变体级的日度 token 量,
/api/v1/models 拿挂牌价,按「剥掉日期后缀的 base + variant」join。join 键少了
variant 就会错得很离谱——:batch / :free 条目与标准条目共用同一个
canonical_slug(实测 69 处碰撞),标准流量会被按半价甚至零价计,spend 差 63%。
另外给 spend 前 10 名额外拉一次 /endpoints,量化默认价与各 provider 的价差带。
第三路 rankings/apps?view=day 补的是调用方维度——谁在消费这些 token。它只给
app_id、token 与请求数,不拆模型也没有价,落进 token_app_observations 单独一张表。
两个必须记住的事实:total_tokens 是字符串;返回的 20 行不是前 20 名
(实测 rank 跳过 2/3/4/15/16/18,有名次不公开露出),所以合计只是下界。
这一路是补强,取不到只降级成一条 note,不拖垮 token 主路。
scripts/backfill.py —— 历史回填与覆盖体检
python scripts/backfill.py # 回填至今 90 天
python scripts/backfill.py --days 120 # 无 key 时 Ornn 会被钳到滚动 3 个月
python scripts/backfill.py --report-only # 不取数,只看库里现在覆盖到哪
只有 has_history_api: true 的源会被回填;其余源会在 no_history_api 里列出
原因,而不是假装回填过。coverage 段逐条给出首末日期、点数与缺口天数,
缺口如实报出,不做插值。
scripts/metrics.py —— 指标与证据包
python scripts/metrics.py --output evidence/gpu-2026-08-25.json
python scripts/metrics.py --date 2026-08-25 --window 90
python scripts/metrics.py --no-persist-alerts # 只算不写 gpu_alerts
--window 只作用于成交价序列与 window_high / 回撤这类窗口内统计。供给与 token
的序列读到底、出到底,调大调小 --window 都不会改变它们画出来的长度。
默认会把当天触发的告警落进 gpu_alerts(先清当天旧行再写,避免调完阈值后
旧告警赖着不走)。确认型拐点不落新表,每次从观测重算,所以口径改了能直接重跑。
scripts/validate.py —— 跨日离群检测(被 collect.py 调用)
采集器里的守卫拦的是「这条数据本身不对」;这里拦的是「数据长得正常、只是跟
自己的历史对不上」。命中打 quality_flag=suspicious,指标层排除出核心中枢,
但行留在库里可追溯。偏离倍数贴近 2/4/8/16 时单独点名为单位换算错——那是
整机价忘了除以卡数、或者多除了一遍的签名。阈值在 config/thresholds.yaml
的 validation 块,冷启动期(历史点不足)一律不标。
scripts/render_report_html.py —— 单页 Dashboard
python scripts/render_report_html.py --evidence evidence/gpu-2026-08-25.json
python scripts/render_report_html.py --evidence … --input reports/gpu-2026-08-25.md
python scripts/render_report_html.py --evidence … --output docs/index.html
python scripts/render_report_html.py --evidence … --report-date 2026-08-30
自包含单页,无外部依赖,用 claude 主题的暖色纸面。按 PRD §5 的硬约束渲染: 三型号同屏、无 GPU selector、无时间范围切换;成交价固定 90 天窗口, 可用供给与 token 量价出全部历史(见「窗口只管成交价」)。token 那块只有量的 构成图,价格是指标格不是曲线;块内顺序是指标格 → 一年结构史 → 日度总量构成 → 量的分层 → 调用方。面板依次是判断区、 成交价趋势、标准报价矩阵、供给趋势、token 需求、数据源状态;中间四块各占整行, 每块底部带一行 ≤100 字的异动说明。
证据全部由脚本从 evidence 渲染,判断全部来自 --input 报告 frontmatter 的
verdict 块(顶部结论 + 三个型号的状态 + 四块面板的异动说明 verdict.panels)。
报告日期来自 frontmatter 的 date,未传报告或未写 date 时才退回本地执行日;
它不读取 evidence 的观测日期来命名产物。data_asof 只用于定位证据包与展示数据截止日。
不传 --input 也能出图,判断区会显示「报告未提供整体判断」、面板说明整行不出现——
脚本不会替模型编结论。异动说明超过 100 字会在输出摘要的
panel_notes_over_limit 里点名,但不截断。
scripts/daily_update.py —— 给 cron 用的流水线
python scripts/daily_update.py # collect → metrics → 写 snapshot
python scripts/daily_update.py --skip-collect
环境变量
只有 ALPHA_PG_URL 是必需的(走 shared/data/db_core.py 统一契约)。三个 P0 源
全部匿名可用,一把 key 都不用配就能跑通;ORNN_API_KEY(解锁 3 个月以外的历史与
更多 SKU)/ VAST_API_KEY / RUNPOD_API_KEY 都是可选项,设了限频更宽松。
各源的实测契约、字段陷阱与降级策略见 references/source_notes.md。
数据表
| 表 | 装什么 |
|---|---|
gpu_price_observations |
标准化后的价格观测,幂等键含 obs_date/source/gpu/price_type/segment/region |
gpu_supply_observations |
供给观测:offer 数与份额、可用 GPU、库存档位、region 覆盖 |
gpu_collect_runs |
每源每次采集的成败、耗时、行数、未映射标识 |
gpu_alerts |
触发的告警,键 (obs_date, gpu_model, rule_id),供跨日追溯 |
token_model_observations |
需求端量价,键 (obs_date, source, model_slug, variant),带 coverage_scope / price_basis 两个判据字段 |
token_volume_history |
厂商级周度历史量,键 (week_start, source, author),口径独立不与日度相减 |
token_app_observations |
调用方(应用)日度量,键 (obs_date, source, app_id),只有 token 与请求数、没有 spend |
配置
| 文件 | 管什么 |
|---|---|
config/gpu_catalog.yaml |
canonical SKU 与各源别名映射、代际溢价对 |
config/sources.yaml |
端点、认证、冻结的查询口径、质量过滤、样本量门槛 |
config/thresholds.yaml |
评分权重与压缩尺度、告警规则与阈值、确认型拐点门槛 |
config/attested_prices.yaml |
人工核对的 CoreWeave / Nebius / Crusoe 挂牌价 |
config/token_basket.yaml |
token 价格指数的篮子(基期、成员粒度、在场权重下限)、缓存敏感性假设、覆盖率与集中度守卫、日度构成图的 top_n 与排名依据 |
改 sources.yaml 里的 query 会改变 query_fingerprint,历史序列从此断成两段——
指标层不会拿不同指纹的观测相减。改之前先在 references/source_notes.md 记一笔。
输出规范
结构:按 references/report_template.md 的七个小节,顺序不变。
正文 900–1600 字,表格不计入。
文风:像跟懂行的人当面把事讲清楚——句子通顺、有逻辑衔接,给判断时把话说透。 不要模板腔,不要成段堆套话。同一维度的多个条目用 bullet,一条一项, 每条用完整通顺的话写完,不要退化成「字段A - 字段B - 字段C」式的横杠拼接; 结构化对照(平台 × GPU × 价格)才用表格。
数据呈现:GPU 价格两位小数、单位 USD/GPU·hour;token 价格三位小数、 单位 USD/Mtok;token 量用 T / B 缩写;百分比一位小数带正负号; 每个数字都要能在 evidence 里找到出处。两套单位不许出现在同一个减法里。
必须做到:
- 缺失就写「暂无数据 / 采集失败」,不用前值冒充最新值。
- 不可比的变化率写「不可比 + 原因」,不许填近似值。
- 状态用文字表达,不能只靠颜色。
- 标出锚定日与它比日历日晚几天。
- 说「可能是 X」时讲清凭什么,以及什么证据会推翻它。
- 写到需求端就得把三句边界说出来:merchant API 层的偏斜样本而非全市场、 spend 是名义挂牌价口径而非实际账单、reasoning/cached 拿不到所以 token 通胀拆不了。
示例
输入:「出一份今天的 GPU 算力监控日报」
做法:collect.py → metrics.py → 读证据 → 写 reports/gpu-<date>.md。
冷启动第一天的正确产出长这样(真实运行结果):
顶部判断写「暂不定论」,因为供给序列只有 1 天历史,评分三个型号全部
usable=false,blockers 是「在场供给信号 0 个」。 价格侧可以讲:锚定 2026-08-24(Ornn T-1 结算,比日历日晚 1 天), H100 SXM 7D +9.5%、B200 7D +6.2%、H200 SXM 7D −13.4%, 三个型号里只有 H200 触发了快速降价告警,且模式是record_only。 标准报价层有三家(Runpod 直采,CoreWeave / Nebius 人工核对于 2026-08-25), 折价维度因此有数:CoreWeave 的 spot 折价 H100 −60.0% / H200 −58.5% / B200 −50.4%, B200 折价最小说明闲置最少,跟 Runpod 那边 B200 库存 Low 互相印证—— 这是当天唯一一个有交叉验证的判断,可以写实。
边界示例:用户问「H100 现在多少钱」这类单点查询,直接跑 collect + metrics 后 报锚定日的跨平台中枢和各源报价即可,不用写完整日报。