Decathlon 如何用 Chronos-2 在 AWS 上规模化预测每周需求

2026-08-29 41 预计阅读时间: 1 分钟
来源: aws.amazon.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:11 分钟

对拥有数万种商品、覆盖多个大洲的零售商来说,需求预测真正难的部分不是“训练一个模型”,而是让预测系统稳定地跑起来:数据要按周整理,推理要覆盖大量 SKU,跨区域结果要能落地,成本和运维复杂度还必须可控。

Decathlon 的实践说明,Chronos-2 可以被部署到 AWS 上,服务大规模、周期性的需求预测任务。根据案例摘要,该方案将预测准确度提高了 11–15 个百分点,并把每周推理成本控制在大约 0.03 美元,同时只使用 CPU 实例运行。

零售需求预测的瓶颈不只是模型

体育用品零售通常同时面对几类不确定性:

  • 商品数量大,预测对象从畅销品到长尾 SKU 都要覆盖;
  • 不同国家和地区的销售节奏不同,季节性不能简单复用;
  • 预测周期以周为单位,系统需要定期重跑,而不是偶尔离线分析;
  • 预测结果要服务补货、库存和门店运营,延迟与成本会直接影响可用性。

因此,选型时不能只看单个时间序列的误差。更实际的指标包括:一次任务能覆盖多少 SKU、是否能在 CPU 上运行、批量推理是否容易自动化、输入输出格式是否稳定,以及每周运行的总成本。

Chronos-2 在这个场景中的价值,可以理解为把通用时间序列预测能力放进一个可批量调用的预测流程。它并不意味着业务可以跳过数据治理:缺失周、异常销量、门店关闭、促销和库存断货等问题,仍然需要在模型前后处理。

把“每周预测”设计成一个小而稳定的流水线

一个可维护的流程通常可以拆成四步:

  1. 从数据仓库读取 SKU、地区、周起始日期和销量;
  2. 将每个序列补齐到统一的周粒度,并按时间切分历史窗口;
  3. 批量调用 Chronos-2 推理服务,生成未来若干周的预测;
  4. 写回带有版本、运行时间和模型标识的结果表,供补货系统使用。

下面的示例展示了一个可以直接改造的客户端。示例假设团队已经在 AWS 上部署了一个内部 Chronos-2 推理端点,端点接收 JSON,并返回 forecast 数组。DRY_RUN=1 时不发起网络请求,可以先用真实 CSV 检查分组和请求格式。

运行前准备一个 weekly_sales.csv

sku,region,week_start,demand
A100,FR,2025-01-06,18
A100,FR,2025-01-13,21
A100,FR,2025-01-20,19
B200,DE,2025-01-06,7
B200,DE,2025-01-13,9
B200,DE,2025-01-20,8

安装依赖并执行:

python -m venv .venv
source .venv/bin/activate
pip install pandas requests
DRY_RUN=1 python forecast_batch.py \
  --input weekly_sales.csv \
  --output forecasts.jsonl \
  --horizon 4

forecast_batch.py

import argparse
import json
import os
from datetime import datetime, timezone

import pandas as pd
import requests


def build_requests(csv_path: str, horizon: int):
    df = pd.read_csv(csv_path, parse_dates=["week_start"])
    required = {"sku", "region", "week_start", "demand"}
    missing = required - set(df.columns)
    if missing:
        raise ValueError(f"missing columns: {sorted(missing)}")

    df = df.sort_values(["sku", "region", "week_start"])
    for (sku, region), group in df.groupby(["sku", "region"], sort=False):
        # 生产环境应在这里补齐缺失周,并明确缺失值与真实 0 的区别。
        values = group["demand"].astype(float).tolist()
        yield {
            "sku": str(sku),
            "region": str(region),
            "history": values,
            "horizon": horizon,
            "frequency": "W",
        }


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--input", required=True)
    parser.add_argument("--output", required=True)
    parser.add_argument("--horizon", type=int, default=4)
    args = parser.parse_args()

    endpoint = os.environ.get("CHRONOS_ENDPOINT")
    dry_run = os.environ.get("DRY_RUN", "0") == "1"
    session = requests.Session()
    run_at = datetime.now(timezone.utc).isoformat()

    with open(args.output, "w", encoding="utf-8") as out:
        for item in build_requests(args.input, args.horizon):
            if dry_run:
                result = {
                    "sku": item["sku"],
                    "region": item["region"],
                    "status": "request-ready",
                    "request": item,
                    "run_at": run_at,
                }
            else:
                if not endpoint:
                    raise RuntimeError("set CHRONOS_ENDPOINT or use DRY_RUN=1")
                response = session.post(endpoint, json=item, timeout=60)
                response.raise_for_status()
                prediction = response.json()
                result = {
                    "sku": item["sku"],
                    "region": item["region"],
                    "forecast": prediction["forecast"],
                    "run_at": run_at,
                    "model": "chronos-2",
                }
            out.write(json.dumps(result, ensure_ascii=False) + "\n")


if __name__ == "__main__":
    main()

接入实际服务时,可以这样设置端点:

export CHRONOS_ENDPOINT="https://forecast.internal.example/v1/predict"
DRY_RUN=0 python forecast_batch.py \
  --input weekly_sales.csv \
  --output forecasts.jsonl \
  --horizon 4

这里的 URL 和返回协议是示例假设,需要根据团队在 AWS 上的部署方式替换。端点可以是一个由 SageMaker、ECS、EC2 或其他内部服务承载的 HTTP 服务;关键是把批量调度、重试、超时和结果版本化放在模型调用之外处理。

CPU-only 运行的工程含义

每周运行一次的预测任务,通常不需要持续占用 GPU。案例摘要中提到,Decathlon 的每周推理可以在 CPU-only 实例上运行,成本约为 0.03 美元。这类成本优势来自几个工程决策的组合,而不只是模型本身:

  • 只在固定调度窗口启动计算资源;
  • 按批次处理多个 SKU,而不是每个 SKU 启动一个服务;
  • 使用合理的历史窗口和预测跨度,避免传输无用数据;
  • 让推理服务无状态化,便于重试和水平扩展;
  • 通过结果表保留运行批次,避免重复计算造成隐性成本。

一个简单的 AWS 调度入口可以是 EventBridge Scheduler 触发 ECS task、AWS Batch job 或 EC2 上的脚本。下面是偏伪代码的 shell 入口,展示需要记录的运行信息:

#!/usr/bin/env bash
set -euo pipefail

RUN_ID="$(date -u +%Y%m%dT%H%M%SZ)"
INPUT="s3://retail-demand/weekly/${RUN_ID:0:8}/weekly_sales.csv"
OUTPUT="s3://retail-demand/forecasts/run_id=${RUN_ID}/forecasts.jsonl"

aws s3 cp "$INPUT" /tmp/weekly_sales.csv

CHRONOS_ENDPOINT="${CHRONOS_ENDPOINT:?CHRONOS_ENDPOINT is required}" \
python forecast_batch.py \
  --input /tmp/weekly_sales.csv \
  --output /tmp/forecasts.jsonl \
  --horizon 4

aws s3 cp /tmp/forecasts.jsonl "$OUTPUT"
echo "completed run_id=${RUN_ID} output=${OUTPUT}"

生产环境还应该补上幂等键、失败重试、指标上报和访问控制。特别是跨洲运行时,数据所在区域、网络出口、时区和数据驻留要求都可能改变整体设计。

不要只看平均准确率

“准确度提高 11–15 个百分点”是很有吸引力的结果,但落地时需要确认比较口径。建议至少同时记录:

  • 按 SKU、地区和商品类别拆分的 WAPE、MAE 或其他业务指标;
  • 长尾商品与高销量商品的表现;
  • 促销周、断货周和新品的误差;
  • 预测区间覆盖率,以及极端需求下的偏差;
  • 单次运行耗时、失败率和每周实际成本。

还要明确“提升多少个百分点”对应的是哪个基线、哪个时间窗口和哪些商品集合。模型在整体平均值上改善,并不保证每个地区都改善。对于补货系统,库存成本、缺货成本和过期风险往往比单一误差指标更接近最终业务价值。

采用建议:先做一条可回放的基准链路

如果团队准备评估 Chronos-2 或类似基础模型,可以按下面的顺序推进:

  1. 选取一个地区和一组有代表性的 SKU,固定训练、验证和回放窗口;
  2. 先把周粒度数据、缺失值规则和异常处理写成可重复脚本;
  3. 用 CPU-only 环境测量批量推理时间与真实成本;
  4. 与现有基线在相同数据切分上比较,而不是只比较一次线上结果;
  5. 把模型版本、输入快照、运行批次和输出结果一起保存;
  6. 通过灰度结果观察库存和缺货指标,再扩大到更多国家和商品。

Chronos-2 的实践重点不是“用一个更大的模型替代所有系统”,而是把通用预测能力嵌入一条成本可控、可以每周可靠运行的供应链流水线。对于大规模零售场景,模型效果、批量推理效率和运维简单度必须一起评估。


相关推荐