周末你请朋友来家里吃火锅。客人一进门,你才冲到冰箱前翻找——肉还在冷冻层冻成一坨,解冻要半小时;白菜没洗,粉丝没泡。于是客人饿着肚子看你手忙脚乱,一桌菜断断续续上了四十分钟。这就是训练任务的“冷启动”:训练进程想要数据时,数据还躺在远端存储里,每次读取都要穿越网络,吞吐曲线像过山车,GPU 空转等待。 而会做饭的朋友会提前一小时把肉从冷冻层挪到冷藏解冻,把菜洗好切好,整整齐齐码在灶台边的备料盘里。客人一到,锅底一开,菜一碟接一碟端上桌,节奏从容。分布式存储里的“数据预热”,正是这套备料智慧在 AI 训练集群中的翻版:把训练即将用到的数据,提前从远端的“冷库”(对象存储/HDFS)搬运到本地的“备料盘”(SSD/内存缓存),让训练启动阶段不再经历漫长的 I/O 等待。本文我们从触发时机、缓存淘汰、加载器集成、实施流程、性能评估到生产实践,把这套“备料”功夫逐层拆开。

岗位关联

对 AI 从业者:掌握数据预热设计,是 AI 基础设施工程师解决大模型训练启动慢、吞吐抖动问题的核心技能,直接决定千卡集群的 GPU 利用效率与迭代速度。

目录
  1. 可视化演示
  2. 预热核心思维
  3. 触发时机策略
  4. 缓存淘汰算法
  5. 加载器集成
  6. 实施流程
  7. 性能评估
  8. 生产实践
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

\u4e09\u9636\u6bb5\u5c55\u793a\u6570\u636e\u4ece\u8fdc\u7aef\u5b58\u50a8\u5230GPU\u7684\u9884\u70ed\u7ba1\u9053\u3001\u7f13\u5b58\u547d\u4e2d\u7387\u53d8\u5316\u4e0e\u541e\u5410\u91cf\u5bf9\u6bd4\uff0c\u63ed\u793a\u9884\u70ed\u6d88\u9664I/O\u7b49\u5f85\u74f6\u9888\u7684\u6838\u5fc3\u673a\u5236\u3002

本节配有交互动效(进入视口后自动加载,约 3KB)。

\u6570\u636e\u9884\u70ed\u7ba1\u9053\u6d41\u7a0b NFS/HDFS\u8fdc\u7aef\u5b58\u50a8\u5230\u672c\u5730SSD\u9884\u53d6 \u672c\u5730\u7f13\u5b58\u547d\u4e2d\u540e\u76f4\u63a5\u9001\u5165GPU T=T_remote-T_local \u9884\u70ed\u6d88\u9664\u8bad\u7ec3\u8fc7\u7a0b\u4e2dI/O\u7b49\u5f85

预热核心思维

请客吃饭,最怕的不是菜不够好,而是客人坐下后你才开始解冻。训练任务也一样:GPU 是最贵的“客人”,它一启动就等着吃数据,可数据却放在远端冷库里。数据预热的核心思维,就是把“去冷库搬货”这件事从客人到家之后,挪到客人到家之前。

在分布式存储里,训练数据通常保存在对象存储或 HDFS 这类远端系统上,而计算节点的训练进程每次读一个样本,都要经历一次网络往返。本地 SSD 读延迟以微秒计,远端网络读延迟却是毫秒级,两者相差两三个数量级。数据预热就是在训练开始前,把第一批训练数据从远端拉取到本地缓存,让训练进程的第一口“饭”直接端上桌。

预热效果的度量核心是缓存命中率:训练请求的数据有多少比例在本地缓存中直接找到。命中率越高,平均读取延迟就越接近本地延迟。用公式描述:

这套收益模型告诉我们:预热不是玄学,而是把命中率从接近 0 抬到接近 1 的确定性工程。就像聪明的客人进门时,灶台上已经码好了五盘切好的菜——训练进程一启动就能满负荷运转,而不是先饿着肚子等第一口饭。

Tˉ=h×Tcache+(1−h)×Tremote\displaystyle \bar{T} = h \times T_{cache} + (1-h) \times T_{remote}
平均读取延迟 = 命中率 × 本地延迟 + 未命中率 × 远端延迟。命中率越高,延迟越接近本地读。
  1. 命中率 h$h$ 表示训练请求在本地缓存中直接命中的比例,取值 $0 \le h \le 1$。
    h=NhitNtotal\displaystyle h = \frac{N_{hit}}{N_{total}}
  2. 两段加权每次读取要么命中本地(快), 要么未命中访问远端(慢), 平均延迟按比例加权。
    Tˉ=h×Tcache+(1−h)×Tremote\displaystyle \bar{T} = h \times T_{cache} + (1-h) \times T_{remote}
  3. 极端校验$h=1$ 时全部本地读,$h=0$ 时每次都要访问远端。
    Tˉ(h=1)=Tcache,Tˉ(h=0)=Tremote\displaystyle \bar{T}(h=1) = T_{cache} \quad,\quad \bar{T}(h=0) = T_{remote}

触发时机策略

备料太早,菜放在灶台上会蔫、会坏,还占着宝贵的备料盘;备料太晚,客人来了还没下锅。预热触发时机,本质上是一场与时间的博弈。

工程上有四种常见触发时机。作业启动触发:训练任务提交后、GPU 资源还在分配时,预热线程立刻开工,让搬运与调度并行;定时周期预取:每隔固定周期扫描热点数据并同步到本地,适合数据缓慢增长的场景;版本变更触发:新数据集发布、旧数据作废时,立即按新版本清单重新预热;数据窗口对齐:最精细的一种,读取训练 sampler 的访问顺序,只预热“下一个窗口”即将使用的数据。

预热启动后,命中率随时间的变化可以用指数饱和模型描述。预热速度越快、窗口越短,命中率的爬升就越陡峭:

触发时机决定了缓存“新鲜度”和“时效性”的平衡。就像经验丰富的厨师会掐着客人进门的时间点开始热锅——预热模块需要与调度器和数据版本管理协同,确保备料动作落在数据恰好要被消费的窗口上。

h(t)=1−e−λt\displaystyle h(t) = 1 - e^{-\lambda t}
预热持续时间越长,缓存命中率按指数逼近 100%,λ 越大(预热越快),曲线越早进入高位平台。
  1. 速率 λ$\lambda$ 表示预热拉取的有效速率,越大意味着单位时间搬入缓存的数据越多。
    λ=BnetDhot\displaystyle \lambda = \frac{B_{net}}{D_{hot}}
  2. 时间推进$t$ 是自预热启动起持续的时间,随着 $t$ 增大,指数项衰减到 0。
    e−λt→0\displaystyle e^{-\lambda t} \to 0
  3. 渐近线命中率最终趋近 1, 但永远差一点,工程上取 95% 即视为预热完成。
    h(t)≈1\displaystyle h(t) \approx 1

缓存淘汰算法

备料盘就这么大,冰箱里几十种食材不可能全摆在灶台上。放不下的时候,倒掉哪盘菜?这是每个后厨都要回答的问题,也是每个缓存系统都要回答的问题。

LRU(Least Recently Used) 是绝大多数缓存的首选:最久没被用过的菜先倒掉。它的直觉是“最近吃过的菜大概率还会吃”。LFU(Least Frequently Used) 则按访问次数淘汰:三个月前被疯狂点过、最近没人理的菜先清走,适合访问频率稳定的场景。LRU-K 是两者的融合:记录每个数据块最近 K 次访问的时间戳,只有访问间隔足够短才保留,能有效对抗“偶尔用一次”的假热点。容量水位机制则保证缓存不会失控:写入达到高水位时触发批量清理,保留一定空闲空间应对突发请求。

LRU-K 的保留判定可以写成:

淘汰算法直接决定缓存里躺着的是“硬菜”还是“剩菜”。一个好的 LRU-K 实现,就像一位记得每位客人偏好的老主厨:真正受欢迎的菜永远留在备料盘上,而无人问津的边角料很快被清走,给新菜腾出位置。

保留(x)  ⟺  tnow−tx(K)<τ\displaystyle \text{保留}(x) \iff t_{now} - t_{x}^{(K)} < \tau
数据块 x 的最近第 K 次访问距今小于阈值 τ 时保留,否则驱逐。K 越大,对“稳定热点”的要求越严格。
  1. K 次历史$t_{x}^{(K)}$ 是数据块 $x$ 最近第 $K$ 次被访问的时间戳,缓存只记录最近的 K 次。
    tx(1)<tx(2)<⋯<tx(K)\displaystyle t_{x}^{(1)} < t_{x}^{(2)} < \cdots < t_{x}^{(K)}
  2. 窗口比较$t_{now} - t_{x}^{(K)}$ 表示第 K 次访问距今多久,越短说明访问历史越紧凑。
    Δ=tnow−tx(K)\displaystyle \Delta = t_{now} - t_{x}^{(K)}
  3. 保留决策当 $\Delta$ 小于阈值 $\tau$ 时保留,否则说明访问太过稀疏,不值得占缓存。
    Δ<τ\displaystyle \Delta < \tau

加载器集成

备料再足,如果点菜系统和后厨对不上,菜还是会晚。训练框架中的数据加载器(DataLoader)就是“点菜系统”,预热模块必须与它无缝配合,才能确保备的料恰好是客人点的菜。

以 PyTorch 为例,DataLoader 通过 sampler 决定每个 batch 的样本索引。预热模块可以注册到 sampler 或者 DataLoader 的迭代器上:每当训练迭代器请求下一个 batch,预热线程就提前把后续几个 batch 的索引解析成 key,并发出预取请求。缓存预取负责把这些 key 对应的数据从远端批量拉入本地;批量组织则把数据按训练所需的 tensor 形状和 batch size 打包,避免训练侧重复转换;流水并行让预热、加载、计算三个环节像餐厅传菜一样滚动推进,互不阻塞。

预取质量可以用预取命中率衡量——预取进来的数据到底有多少真的被训练用到了:

加载器与预热模块深度集成后,训练循环就像是配合默契的传菜流水线:取样器刚报出菜名,后厨已经把料备好下锅。训练进程永远不空等,GPU 的利用率自然被拉满。

pprefetch=Nhit_prefetchNprefetch\displaystyle p_{prefetch} = \frac{N_{hit\_prefetch}}{N_{prefetch}}
预取命中率 = 预取数据中被训练实际命中的数量 ÷ 预取总数量。比例高说明预取精准,比例低说明白白浪费了带宽。
  1. 分子含义$N_{hit\_prefetch}$ 是预取进缓存且之后被训练请求命中的数据块数量。
    Nhit_prefetch=∑k=1Nprefetch1[k∈cache]\displaystyle N_{hit\_prefetch} = \sum_{k=1}^{N_{prefetch}} \mathbb{1}[k \in cache]
  2. 分母含义$N_{prefetch}$ 是预热模块发出的预取请求总数,包括最终没用上的部分。
    Nprefetch≥Nhit_prefetch\displaystyle N_{prefetch} \ge N_{hit\_prefetch}
  3. 诊断价值$p_{prefetch}$ 过低说明预取时机或数据窗口判断失准,需要回溯 sampler 顺序。
    pprefetch∈[0,1]\displaystyle p_{prefetch} \in [0, 1]

实施流程

好餐厅的后厨都有一套标准备料流程:列采购清单、从冷库搬货、检查食材新鲜度、坏了的重新采购、记录耗时。数据预热的落地实施,同样是一条标准作业流程。

清单生成是第一步:扫描训练数据集的 manifest 或 shard 列表,按访问热度排序,生成待预热的数据块清单。多级缓存写入通常是分层推进的:远端存储→本地 SSD→内存页缓存,每层按访问频率决定是否继续向上回填,让热数据离 CPU/GPU 更近。校验重试保证数据正确性:每个数据块拉取后计算哈希与远端校验,失败的分片自动进入重试队列,超过重试上限则上报告警。监控反馈把预热进度、失败率、已用带宽实时上报给调度系统,动态调整预热节奏。

预热总耗时是这个流程的关键预算,它决定了任务调度器需要为训练预留多少“提前量”:

这套流程把“提前备料”从一次性的灵光一现,变成了可审计、可重试、可监控的例行公事。正如成熟的餐厅每天按 SOP 备料,成熟的数据平台每次训练启动前,都按同一套流程把数据搬运到位。

Twarm=DhotBnet\displaystyle T_{warm} = \frac{D_{hot}}{B_{net}}
预热耗时 = 热点数据总量 ÷ 有效网络带宽。知道这个值,才能判断预热是否来得及、要不要提前启动。
  1. 数据量$D_{hot}$ 是待预热的热点数据总量,通常取首个 epoch 或首个窗口需要的数据。
    Dhot=Nshard×Sshard\displaystyle D_{hot} = N_{shard} \times S_{shard}
  2. 有效带宽$B_{net}$ 是集群到远端存储的有效带宽,考虑并发数和网络拥塞会低于理论峰值。
    Bnet=min⁡(Blink,Nworker×bper)\displaystyle B_{net} = \min(B_{link}, N_{worker} \times b_{per})
  3. 工期预算$T_{warm}$ 就是预热的“工期”, 调度器据此决定提前多少时间触发预热。
    Twarm=DhotBnet\displaystyle T_{warm} = \frac{D_{hot}}{B_{net}}

性能评估

怎么判断这顿饭请得成不成功?客人等多久吃上第一道菜、上菜节奏稳不稳、高峰期有没有断档。数据预热的效果,同样要用这三个维度的数字说话。

预热耗时是成本侧指标:花在训练前搬运数据的时间,不能超过训练真正节省下来的时间,否则就是负收益。命中率曲线反映预热过程的质量:理想曲线应该在训练启动前就爬升到高位,而不是训练跑了一半才慢慢涨。训练启动时间是收益侧最直观的指标:从训练进程开始到达到目标吞吐(frist goodput)的耗时,预热方案通常能把这段从几十分钟压到几分钟。稳态吞吐量则考察长期表现:预热是否只是把启动变快了,还是会带来持续稳定的吞吐提升。

从 Amdahl 定律看,预热只加速了训练耗时中属于 I/O 的那一部分:

评估的意义在于校准预期。预热不是银弹——如果训练的瓶颈其实在数据预处理或模型计算,那再快的备料也上不了菜。用数据说话,才能知道该在备料上花多大力气。

S=1(1−p)+pSio\displaystyle S = \frac{1}{(1-p) + \dfrac{p}{S_{io}}}
整体加速比受限于非 I/O 部分。p 是 I/O 耗时占比,S_io 是预热带来的 I/O 加速倍数。即使 I/O 提速百倍,整体加速也被 (1-p) 封顶。
  1. I/O 占比$p$ 是冷启动场景下 I/O 等待占总耗时的比例,训练算子越重,$p$ 越小。
    p=TioTio+Tcompute\displaystyle p = \frac{T_{io}}{T_{io} + T_{compute}}
  2. I/O 加速倍数$S_{io}$ 是预热后 I/O 阶段提速的倍数,由命中率决定,趋近 $T_{remote}/T_{cache}$。
    Sio=TremoteTcache≈102∼103\displaystyle S_{io} = \frac{T_{remote}}{T_{cache}} \approx 10^2 \sim 10^3
  3. 整体上限当 $S_{io}$ 非常大时,分母趋近 $(1-p)$, 加速比被非 I/O 部分锁死。
    Smax⁡=11−p\displaystyle S_{\max} = \frac{1}{1-p}

生产实践

开一家长期经营的餐厅,光会备一顿饭不够。备料盘多大合适?如何避免备了一堆没人点的菜?停水停电时怎么办?这些生产实践问题,决定数据预热方案能否在真实集群里站稳脚跟。

容量规划的关键是匹配“重建时间”:如果本地缓存整体丢失,重新预热需要多久?缓存容量至少要覆盖“消费速率 × 重建时间”的数据量,否则缓存一丢就会造成长时间断供。避免污染同样重要:超大文件或只读一次的数据如果进入缓存,会像一盘没人动过的大份沙拉占着备料盘,把真正热的小菜挤出去,工程上常对这类数据采取流式直读、不写缓存。弹性伸缩指缓存池能够按训练任务的 worker 数量动态扩缩,多机共享缓存池避免每台机器重复拉取同一份数据。故障恢复则要求缓存丢失后能自动降级为远端直读,同时触发增量重建,而不是让训练硬等。

容量与重建的约束关系,可以写成:

生产实践的本质,是把“这次饭请得不错”变成“这家餐厅永远经得起高峰”。容量、污染、弹性、故障,每一环都是想在 AI 基础设施领域走远的人必须补上的课。

Ccache≥Rconsume×Trebuild\displaystyle C_{cache} \ge R_{consume} \times T_{rebuild}
缓存容量 ≥ 数据消费速率 × 缓存重建耗时。这样即使缓存瞬间清空,也能在重建完成前撑住训练的关键窗口。
  1. 消费速率$R_{consume}$ 是训练过程消费数据的速率,单位通常是 GB/s。
    Rconsume=BbatchTstep\displaystyle R_{consume} = \frac{B_{batch}}{T_{step}}
  2. 重建耗时$T_{rebuild}$ 是从远端重新拉满缓存需要的时间,取决于 $D_{hot}$ 和带宽。
    Trebuild=CcacheBnet\displaystyle T_{rebuild} = \frac{C_{cache}}{B_{net}}
  3. 安全条件容量大到能覆盖一个完整重建周期,缓存故障就不会打断训练稳态。
    Ccache≥Rconsume×Trebuild\displaystyle C_{cache} \ge R_{consume} \times T_{rebuild}

AI 应用场景

大模型预训练启动的数据预热

阶段:数据准备 → 训练启动

解决的问题:千亿 token 的数据集存放在远端对象存储中,训练启动时每个 worker 都从远端拉取首批数据,网络往返造成 GPU 大面积空等,启动时间长达数十分钟到数小时。预热把首个 epoch 所需数据提前拉入本地 NVMe 缓存,启动时间可以缩短一个数量级。

具体案例:某千卡集群在训练 70B 参数模型时,原本 cold start 需要约 40 分钟才能达到目标吞吐。引入预热模块后,调度器在 GPU 分配的同时启动预热,数据提前 10 分钟搬入本地缓存,训练启动时间降到 8 分钟,GPU 空闲开销降低约 80%。

数据集周期更新的增量预热

阶段:数据版本管理 → 训练迭代

解决的问题:训练数据每天从业务侧增量更新,若每次版本更新都全量重新预热,会白白消耗大量带宽和时间。通过版本变更触发机制,只对新增和变更的 shard 做增量预热,命中率保持稳定。

具体案例:推荐系统训练数据每日更新 5%,平台基于 manifest diff 生成增量清单,预热模块只拉取变更分片,其余数据继续命中旧缓存,整体命中率维持在 95% 以上,带宽消耗降低约 90%。

多机共享缓存池的弹性预热

阶段:多机多卡训练 → 数据并行

解决的问题:数据并行训练中每个 worker 读取同一份数据,若各自从远端拉取,同一数据被重复读取多遍,浪费大量带宽。通过共享缓存池(如 Alluxio 或 JuiceFS),预热一次、多机共享,显著降低重复 I/O。

具体案例:某推荐模型训练使用 64 个 worker,共享缓存池预热后,跨节点缓存命中率达到 70% 以上,集群出口带宽占用下降为原来的三分之一,训练吞吐提升约 1.8 倍。

Python 演示

python77 行
import timefrom collections import OrderedDict  class RemoteStore:    def __init__(self, size):        self._items = {i: "sample-{:04d}".format(i) for i in range(size)}     def read(self, key):        time.sleep(0.002)  # 模拟一次远端网络往返        return self._items[key]  class PrefetchCache:    def __init__(self, store, capacity):        self.store = store        self.capacity = capacity        self._table = OrderedDict()        self.hit_count = 0        self.miss_count = 0     def prefetch(self, keys):        # 训练前批量拉取, 不统计命中        for key in keys:            if key in self._table:                continue            if len(self._table) >= self.capacity:                self._table.popitem(last=False)  # LRU: 淘汰最久未用            self._table[key] = self.store.read(key)     def load(self, key):        if key in self._table:            self._table.move_to_end(key)            self.hit_count += 1            time.sleep(0.00005)  # 本地缓存读取开销极小            return self._table[key]        self.miss_count += 1        value = self.store.read(key)        if len(self._table) >= self.capacity:            self._table.popitem(last=False)        self._table[key] = value        return value     def hit_rate(self):        total = self.hit_count + self.miss_count        return self.hit_count / total if total else 0.0  def train_startup(keys, cache):    # 模拟训练启动阶段依次读取首批数据    start = time.perf_counter()    for key in keys:        cache.load(key)    return time.perf_counter() - start  def main():    store = RemoteStore(size=1000)    keys = list(range(200))     cold_cache = PrefetchCache(store, capacity=256)    t_cold = train_startup(keys, cold_cache)     warm_cache = PrefetchCache(store, capacity=256)    t0 = time.perf_counter()    warm_cache.prefetch(keys)    t_warmup = time.perf_counter() - t0    t_warm = train_startup(keys, warm_cache)     print("cold: critical path = {:.3f}s, hit rate = {:.1%}".format(t_cold, cold_cache.hit_rate()))    print("warm: warmup = {:.3f}s(before training), critical path = {:.4f}s, hit rate = {:.1%}".format(        t_warmup, t_warm, warm_cache.hit_rate()))    print("critical path speedup = {:.0f}x".format(t_cold / t_warm))  if __name__ == "__main__":    main()
目的:用一个小型 LRU 缓存模拟冷启动与预热启动的差异,直观对比训练关键路径上的耗时和命中率。
  1. self._table[key] = self.store.read(key)预热时把远端数据取回并写入本地缓存,模拟“食材从冷库搬到备料盘”
  2. self._table.popitem(last=False)缓存满时按 LRU 淘汰最久没用的数据,给新数据腾出位置
  3. if key in self._table:训练读取时先查缓存,命中则跳过远端 I/O,这是预热的直接收益点
  4. warm_cache.prefetch(keys)训练开始前批量拉取首批数据,发生在关键路径之外
  5. t_warm = train_startup(keys, warm_cache)预热后再测关键路径耗时,与冷启动形成对比

常见误区

误区我觉得预热就是把整个数据集都塞进内存,100TB 的训练集就配 100TB 的缓存,这样肯定最快。

正解缓存容量受物理内存和 SSD 限制,而且全量预热耗时长,很多冷数据会被后续访问挤掉,白白浪费预热带宽。工程上只预热热点窗口(如首个 epoch 或前几个 batch),让 LRU 等淘汰算法决定哪些数据值得长期驻留。

为何常见:人们容易把“备料”理解为“把冰箱搬空”,忽略了备料盘有限、食材会过季的常识。

误区上次任务已经把这批数据预热好了,这次重启训练任务应该直接命中缓存,不用再预热。

正解缓存是易失的,节点重启、缓存淘汰、数据版本更新都会让旧缓存失效。必须先校验缓存中的数据版本和哈希,确认有效后才复用,否则会读到过期数据或白白等待。

为何常见:把缓存当成了持久存储,忽视了缓存与底层存储生命周期不同的本质。

误区提前两小时开始预热,肯定比提前五分钟效果好,反正数据早晚要用。

正解预热太早,数据长期占用缓存可能被其他任务的访问挤掉,或者因数据版本更新作废,等于白忙一场。应该让预热结束时间对齐数据访问窗口,提前量只覆盖网络波动和重试余量。

为何常见:线性思维,以为“多备无患”,却没意识到缓存是共享且会被淘汰的稀缺资源。

误区我要把缓存命中率做到 100%,不达 100% 就说明预热做得不够好。

正解数据访问中本就存在一次性冷数据,追逐最后几个百分点的命中率需要数倍容量和带宽,边际收益极低。更合理的目标是保证关键路径(训练启动窗口)命中率接近 100%,全局命中率在 90% 左右即可。

为何常见:用单一指标替代综合性能评估,忽略了容量、带宽和收益的平衡。

误区只要把整个数据集全量预热进缓存,shuffle 怎么打乱顺序都无所谓,反正都在本地。

正解当缓存装不下整个数据集时,访问顺序决定了哪些数据先被请求。如果预热数据与 sampler 的随机顺序错位,训练当前窗口会大量 miss,而缓存里躺着的却是后面才用到的数据,形成“假预热”。

为何常见:只关注数据本身,忽略了训练访问的时间局部性,而后者才是预热命中的关键。

面试题

Q1. 数据预热的直接目的是什么?

单选题 · easy

  • A.减少训练时的数据读取等待← 正确答案
  • B.提升模型的最终预测准确率
  • C.减小模型训练所需的总参数量
  • D.提高分布式存储的写入带宽

答案:A。预热把数据从远端搬到本地缓存,命中本地缓存后读取等待大幅缩短。准确率、参数量、写入带宽都不是预热的作用对象,这些指标与模型结构和存储写入路径相关。

Q2. 数据预热中“热”数据指的是什么?

单选题 · easy

  • A.被训练频繁访问的数据← 正确答案
  • B.存放在高温机房的数据
  • C.数据文件体积特别大的
  • D.刚刚上传到远端的数据

答案:A。“热”对应访问热度,即被训练频繁读取的数据,如同后厨里常被点的菜。高温机房、体积大、新上传都与访问频次无关,不构成热数据的定义。

Q3. LRU 缓存淘汰时优先移除哪类数据?

单选题 · easy

  • A.最久没有被访问的数据← 正确答案
  • B.最近刚被访问过的数据
  • C.被访问次数最少的数据
  • D.未来最有可能用到的数据

答案:A。LRU 全称 Least Recently Used,淘汰“最久未使用”的数据,直觉是最近用过的可能还会用。访问次数最少是 LFU 的规则,预测未来最优是理论算法而非 LRU。

Q4. LRU-K 算法中的 K 表示什么?

单选题 · mid

  • A.缓存容纳的数据块总数
  • B.记录每个块的最近 K 次访问← 正确答案
  • C.预热启动时的并发线程数
  • D.远端存储节点的数量规模

答案:B。LRU-K 记录每个数据块最近 K 次访问的时间戳,用第 K 次访问的间隔判断是否为稳定热点。缓存容量、并发线程、存储节点数都是干扰项,它们与 K 的含义无关。

Q5. 关于预热触发时机的选择,下列说法正确的是?

单选题 · mid

  • A.越早越好,方便数据充分预热
  • B.与训练数据访问窗口对齐最佳← 正确答案
  • C.只在训练结束后定时触发
  • D.无需考虑数据版本是否变化

答案:B。预热太早会被其他数据淘汰或失效,正确做法是让预热结束时间对齐训练访问窗口。训练结束后触发失去意义,数据版本更新时必须重新预热,不能忽略。

Q6. 以下哪些属于合理的预热触发时机?

多选题 · mid

  • A.训练作业启动阶段← 正确答案
  • B.数据集版本发布更新后← 正确答案
  • C.定时同步热点数据← 正确答案
  • D.缓存空间满了之后

答案:ABC。作业启动、版本更新、定时周期都是有效触发时机。缓存空间满了之后应该触发的是缓存淘汰清理,而不是预热,两者方向相反。

Q7. 预取(prefetch)与按需加载的关键区别是什么?

单选题 · mid

  • A.预取在数据被请求前提前加载← 正确答案
  • B.按需加载不再访问远端存储
  • C.预取会降低所有场景的吞吐
  • D.二者在时序行为上完全相同

答案:A。预取的核心是“在请求到达之前”把数据准备好,把 I/O 从关键路径移到两侧;按需加载是请求到了才去远端取。按需加载仍需访问远端,预取在命中的场景显著提升吞吐而非降低。

Q8. 以下哪些属于评估数据预热效果的常用指标?

多选题 · hard

  • A.缓存命中率← 正确答案
  • B.训练启动时间← 正确答案
  • C.稳态吞吐量← 正确答案
  • D.模型参数量

答案:ABC。命中率反映预热质量,启动时间反映关键路径收益,稳态吞吐反映长期表现,三者共同构成预热评估体系。模型参数量与训练精度相关,与数据预热和 I/O 性能无关。

Q9. 平均读延迟满足 T = h×T_cache + (1-h)×T_remote,当 h=0.9 时说明什么?

单选题 · hard

  • A.延迟主要由本地缓存决定← 正确答案
  • B.延迟完全等于远端延迟
  • C.延迟与命中率完全无关
  • D.延迟等于两端延迟之和

答案:A。h=0.9 意味着 90% 的请求走本地缓存,只有 10% 走远端,加权平均后本地延迟主导。若完全等于远端延迟需要 h=0,延迟与命中率有关,也不是简单求和。

Q10. 以下哪些措施能有效预防缓存污染?

多选题 · extreme

  • A.大文件采用流式读取跳过缓存← 正确答案
  • B.单次访问数据不写入缓存← 正确答案
  • C.用 LRU-K 识别稳定热点← 正确答案
  • D.把缓存容量提高到无限大

答案:ABC。污染是低频或超大数据的进入挤占热点空间。大文件流式直读、单次访问数据不落缓存、用 LRU-K 过滤稀疏访问,都是有效手段。无限容量不现实,也不治本。