晚上六点,“星焰食堂”的饭点高峰准时到来,大厨的锅铲刚碰到油锅,就发现配菜还没送到——洗菜工才从中央仓库往回搬货,客人干等了半小时。第二天,后厨改了一套规矩:五点前把常用食材从仓库搬进大冰柜,再把今晚最要紧的几样洗净切好放进砧板旁的冷藏小冰箱;洗菜、切菜、炒菜排成流水线,大厨炒这道菜时,下一道已在出餐口排队;新食材到货,旧批次立刻核对清走。从此饭点再没熄过火。 AI 大模型训练遇到的正是同样的“饭点问题”。GPU 是那个不能熄火的大厨,数据是食材;如果数据还躺在遥远的分布式存储里,GPU 就只能空转等菜。本课要讲的“数据预热与缓存策略”,就是一套让后厨在高峰前备好菜、按层级存好货、及时清旧货、流水线供菜的管理哲学。学完这套体系,你会明白如何让训练系统的 GPU 时刻锅铲不停,把等待 IO 的时间变成计算时间。

岗位关联

对 AI 从业者而言,数据加载与缓存是训练性能的隐形天花板。掌握这套体系,你能直接改善大模型与分布式训练的 GPU 利用率和吞吐量,是训练平台、存储系统与推理服务工程师的核心能力。

目录
  1. 可视化演示
  2. 数据预热——开餐前先备菜
  3. 多级缓存架构——后厨的储物分层
  4. 缓存淘汰策略——冰柜清理哲学
  5. 数据流水线——备菜一条龙
  6. 训练协同优化——让大厨不停火
  7. 缓存一致性——食材批次核对
  8. 监控与调优——店长仪表盘
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示多级缓存架构(GPU/RAM/SSD/网络/文件系统)、数据预取流水线与GPU计算重叠、FIFO与LRU缓存淘汰策略对比,理解训练数据缓存体系如何提升GPU利用率。

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

多级缓存架构 GPU显存:100ns延迟,80GB容量,最快访问 主机内存:us级延迟,256GB容量 SSD/分布式/文件系统:ms级延迟,PB级容量 latency = f(level) 多级缓存平衡速度与容量。

数据预热——开餐前先备菜

星焰食堂的店长发现,饭点最大的浪费不是菜不好吃,而是大厨在等菜。于是后厨立下规矩:每天五点前,把今晚菜单要用到的食材按顺序从仓库搬出、洗净切好,装进砧板旁的冷藏小冰箱。大厨一开火,手边就有料。这顿饭点,出餐速度翻了一倍——这就是“预热”的力量。

AI 训练里的“饭点高峰”就是 GPU 启动计算的那一刻。GPU 大厨每处理一个 batch 就要吃一口数据;如果样本还躺在分布式存储里,GPU 只能干等着磁盘转。数据预热(Data Preheating)就是在训练开始前、或每个 epoch 交替时,把接下来要用的样本从慢速存储提前搬到快速缓存中,让“菜”先到位。

预热机制通常分三步:训练启动时先扫描数据集清单,按访问顺序批量预取首批 batch 进入内存;每个 epoch 开头用后台线程预取下一轮的首批数据;训练中由预取器(prefetcher)跟着当前读取游标滚动加载。预加载时机很讲究——太早,数据会被淘汰策略挤掉;太晚,GPU 照样挨饿,一般以“下个 batch 开始前完成加载”为底线。缓存友好(cache-friendly)则强调数据排布:把同一 epoch 常一起用的样本放在相邻位置,让预取能一次顺序读一大块,而不是东一榔头西一棒子。

衡量预热做得好不好,看“就绪率”——开火瞬间,手边到底有多少料:

就绪率高了,大厨一开火就能连续炒菜。不过备菜得看后厨有多少地方放——这就引出第二招:多级缓存架构。

ReadyRate=NreadyNneeded×100%\displaystyle \text{ReadyRate} = \frac{N_{\text{ready}}}{N_{\text{needed}}} \times 100\%
  1. 分子 N_ready已提前进入缓存、GPU 可直接读取的样本数量。预热越充分,这个数越大。
    Nready\displaystyle N_{\text{ready}}
  2. 分母 N_needed下一个训练阶段(如首个 epoch)实际需要的样本总数量。
    Nneeded\displaystyle N_{\text{needed}}
  3. 比值含义就绪率越接近 100%,GPU 开火后等待数据的时间越短,启动越顺畅。
    ×100%\displaystyle \times 100\%

多级缓存架构——后厨的储物分层

星焰食堂的储物分三层:砧板旁的冷藏小冰箱最快、最贵,只放当天最常用的几样;后厨大冰柜容量大、速度中等,放过夜食材;中央仓库什么都有,但取一趟要跑半天。大厨取料永远先看小冰箱,没有再看冰柜,最后才派人跑仓库。

AI 训练系统的数据通路几乎一样。GPU/CPU 侧的内存(DRAM)是“小冰箱”,纳秒级取用;NVMe SSD 是“大冰柜”,毫秒级读取、容量可达 TB;分布式存储(如 Ceph、NFS、GPFS)是“中央仓库”,跨节点共享但延迟最高。训练框架与缓存中间件正是按这个层级逐级找数据。

设计核心是让每层只放该放的货:内存缓存放“热数据”,即当前 epoch 正反复读的样本;SSD 缓存放整个训练集或最近几个 epoch 的数据;分布式缓存负责跨节点共享,多个 worker 同时读同一份数据时只从仓库取一次。层级协同的关键是“缺哪层补哪层”——内存未命中就去 SSD 找,SSD 未命中再去分布式找,同时把取到的数据逐级回填。

平均取一趟料要多久?用 AMAT(平均访问时间)来算:

分层越多,取料路径越长。要让平均取料时间最短,除了层数合理,还得保证每层放的都是“最近还会用到”的货——这就轮到冰柜管理员的淘汰策略上场了。

AMAT=Tmem+mmem×(Tssd+mssd×Tremote)\displaystyle \text{AMAT} = T_{\text{mem}} + m_{\text{mem}} \times \big(T_{\text{ssd}} + m_{\text{ssd}} \times T_{\text{remote}}\big)
  1. T_mem内存命中时的读取耗时,纳秒级,是所有路径里最快的基准。
    Tmem\displaystyle T_{\text{mem}}
  2. m_mem内存层未命中率,也就是“小冰箱里没有”的概率。
    mmem\displaystyle m_{\text{mem}}
  3. 括号内未命中时去 SSD 层取,耗时 T_ssd;SSD 也未命中还要继续访问远程仓库,代价是 T_remote。
    Tssd+mssd×Tremote\displaystyle T_{\text{ssd}} + m_{\text{ssd}} \times T_{\text{remote}}
  4. 整体求和平均访问时间就是把“直接命中”和“各级未命中”的代价按概率加权加起来。
    AMAT\displaystyle \text{AMAT}

缓存淘汰策略——冰柜清理哲学

星焰食堂的冰柜容量有限,新进一批食材就必须清掉一批旧货。扔什么?管理员看两样:这货多久没被用了(时间维度),这货平时多常用(频率维度)。扔错了,客人点那道菜时就得现跑仓库,比不扔还亏。

缓存淘汰策略解决的就是“空间有限,给谁腾地方”的问题。训练数据的访问有强局部性:同一个 epoch 反复读同一批样本,epoch 一切换,旧样本基本不再需要。好的淘汰策略必须跟上这种访问节奏。

最常用的是 LRU(最近最少使用):每条数据记一个“最后访问时间”,淘汰时清掉最久没碰的那条,实现上常用哈希表加双向链表,访问时移到头部,淘汰时摘掉尾部。LFU(最不经常使用)则记录访问频次,淘汰频次最低的,适合少数样本被反复高频访问的场景,但会积累历史频次,导致新热点数据挤不进来。实际工程常做两者结合或近似实现。淘汰指标包括命中率、访问延迟、淘汰代价——被淘汰的数据如果马上又被访问,就得从下层重新取,这个代价叫“未命中惩罚”(miss penalty):

管理员懂了“扔错了要跑仓库重取”的代价,就会更谨慎地看日子保鲜。但无论怎么腾地方,货从仓库到砧板都要经过搬运与加工——这是数据流水线的活。

MissPenalty=T下层−T本层\displaystyle \text{MissPenalty} = T_{\text{下层}} - T_{\text{本层}}
  1. T_本层在当前缓存层命中时读取数据的耗时,命中则成本很低。
    T本层\displaystyle T_{\text{本层}}
  2. T_下层被淘汰数据再次被访问时,必须从下一层(比如 SSD 或仓库)重新读取的耗时。
    T下层\displaystyle T_{\text{下层}}
  3. 差值一次错误淘汰造成的额外等待时间。淘汰策略的目标就是让所有错误淘汰的代价总和最小。
    T下层−T本层\displaystyle T_{\text{下层}} - T_{\text{本层}}

数据流水线——备菜一条龙

大厨不能等菜洗好才炒下一道。星焰后厨把洗菜、切菜、配菜、炒菜排成流水线:洗菜工洗下一道菜的原料,切菜工切下下道——大厨永远有菜可炒。哪怕每个环节单独看都不快,整条线却能一刻不停地产出。

训练数据流水线同样把三个环节并行起来:读取原始文件、解码与预处理、组装 batch。PyTorch DataLoader 里的 num_workers 就好比雇了多少名“备菜工”,大家同时处理不同样本,而不是一个人干完全部。

三个环节分别是:磁盘/网络读取原始字节;CPU 解码(图片解压、tokenize、数据增强等);打包成 batch 送进 GPU 显存。批处理生成通常由后台 worker 进程完成,主进程只负责“从队列取 batch、上送 GPU”。优化的要点是让三阶段吞吐对齐,别让某个环节独慢;用 prefetch_factor 控制预取队列长度,让出餐口永远有货;还要避免预处理和训练抢同一份 GPU 资源。整条线的产能,由最慢的环节说了算:

流水线跑顺了,大厨手边的菜源源不断。但菜到了砧板旁,还得保证大厨一伸手就能拿到——这就是训练协同优化要解决的事。

Throughput=1max⁡(Tread,Tpreprocess,Ttrain)\displaystyle \text{Throughput} = \frac{1}{\max\big(T_{\text{read}}, T_{\text{preprocess}}, T_{\text{train}}\big)}
  1. 分子 1把一次数据处理看作一个整体任务,产能用单位时间内完成的任务数表示。
    1\displaystyle 1
  2. 分母 max三个环节各自耗时中最大的那个,决定了整条线的节拍快慢。
    max⁡(Tread,Tpreprocess,Ttrain)\displaystyle \max\big(T_{\text{read}}, T_{\text{preprocess}}, T_{\text{train}}\big)
  3. 倒数关系最慢环节耗时越长,整体吞吐越小。优化应优先打击瓶颈环节。
    1max⁡(⋯ )\displaystyle \frac{1}{\max(\cdots)}

训练协同优化——让大厨不停火

流水线再顺,如果送菜的小哥总等大厨喊了才跑,大厨照样得停下锅铲干等。最好的配合是“大厨还没炒完这道菜,下一道已经在出餐口排队了”。后厨的默契,就是让供给永远贴着需求走。

训练协同优化的核心,正是让数据供给节奏贴着 GPU 消费节奏走,手段有三:预取(prefetch)、异步加载、缓冲队列。它们的目标只有一个——别让 GPU 因为等数据而闲置。

预取机制在当前 batch 送入计算的同时,后台立刻加载下一个 batch;异步加载把 IO 和预处理放到独立线程或进程,不阻塞主循环;缓冲队列用有界队列做“出餐口”,队列满就暂停生产,队列空就暂停消费,防止生产过快挤爆内存。这些手段共同拉高 GPU 利用率。工程上可以通过监控 GPU 的 SM 占用率或“等待 IO 时间占比”来验证效果:

大厨不停火,后厨才算真正高效。但有一件事会突然打乱节奏——仓库说“这批食材换了新批次”,旧批次还冰在冰柜里,怎么办?这就到了数据一致性的战场。

GPUUtil=TcomputeTcompute+TwaitIO×100%\displaystyle \text{GPUUtil} = \frac{T_{\text{compute}}}{T_{\text{compute}} + T_{\text{waitIO}}} \times 100\%
  1. T_computeGPU 真正执行矩阵运算、前向反向传播的时间。
    Tcompute\displaystyle T_{\text{compute}}
  2. T_waitIOGPU 因等数据而空转等待的时间。
    TwaitIO\displaystyle T_{\text{waitIO}}
  3. 比值计算时间占总耗时的比例,越接近 100%,说明大厨越忙、锅铲越不停。
    TcomputeTcompute+TwaitIO×100%\displaystyle \frac{T_{\text{compute}}}{T_{\text{compute}} + T_{\text{waitIO}}} \times 100\%

缓存一致性——食材批次核对

星焰食堂的仓库偶尔会换供应商,旧批次食材口味不同,甚至可能变质。如果冰柜里还冻着旧批次,做出来的菜就跟菜单对不上号。所以每次开餐前,管理员都要核对每样食材的“批次号”,对不上的全部清掉重进。

训练数据一样会“换批次”:数据集升级、标签修正、新数据增量加入。训练节点上的缓存却可能还存着旧版本样本,新旧混杂会让模型在“脏数据”上训练,结果不可复现,log loss 莫名其妙地跳。这个问题只能靠缓存一致性机制来解决。

业界做法是给每个数据块打版本号。读取时校验缓存版本与元数据中心(如分布式文件系统的 manifest)是否一致,不一致就使缓存失效(invalidation),重新从仓库拉新数据。更新策略分两种:主动失效——版本变更时广播通知所有节点清缓存;惰性校验——每次读取带上版本号比对,发现过期才更新。后者更通用,代价是多一次元数据查询。脏数据的处理原则是“读时校验、坏即丢弃”,绝不让旧样本混进新 epoch:

批次核对清楚了,后厨才能稳定出菜。但“出菜快不快、扔菜亏不亏”不能靠拍脑袋——店长得有一块仪表盘,时刻盯着数据变化。这就是监控与调优的功课。

valid(d)  ⟺  vcache(d)=vmaster(d)\displaystyle \text{valid}(d) \iff v_{\text{cache}}(d) = v_{\text{master}}(d)
  1. v_cache(d)缓存中数据块 d 的版本号,在写入缓存那一刻记录。
    vcache(d)\displaystyle v_{\text{cache}}(d)
  2. v_master(d)元数据中心记录的主版本号,数据每次更新都会递增。
    vmaster(d)\displaystyle v_{\text{master}}(d)
  3. 相等判断只有两者相等,缓存中的数据才合法;一旦不等,该数据即视为脏数据,必须重新拉取。
    valid(d)  ⟺  ⋯\displaystyle \text{valid}(d) \iff \cdots

监控与调优——店长仪表盘

星焰食堂的店长每天盯三块表:出餐速度(吞吐量)、客人等位时间(IO 等待)、冰柜周转情况(缓存命中率)。哪块不对劲,就调整备菜量或冰柜分区。数据不会说谎,后厨效率全靠这面仪表盘撑起来。

训练系统同样要监控三类核心指标:缓存命中率、数据加载吞吐量、GPU 利用率,再由此推断瓶颈位置。只有把“数据通路”变成可观测的,才知道该调哪个旋钮,而不是瞎猜。

命中率监控统计每层缓存的 hit/miss,长期偏低说明容量或预热策略有问题;吞吐量分析观察每秒加载的样本数(samples/s),与 GPU 消耗速度对比;瓶颈定位通过阶段耗时统计或火焰图,判断是“读取慢、预处理慢还是传输慢”。参数调优路径一般从缓存容量与淘汰策略开始,再调 worker 数量和预取队列长度,最后调数据排布方式。常用工具有 PyTorch Profiler、TensorBoard 自定义计数、GPU DCGM 指标等。命中率公式我们其实已经见过了,它是整个体系最核心的仪表:

店长盯着仪表盘微调备菜节奏,后厨效率肉眼可见地提升。从备菜(预热)、分层存储(多级缓存)、清旧货(淘汰)、流水线、协同配合、版本校验到监控调优,整套体系让星焰食堂在饭点高峰也能行云流水。你的 AI 训练系统,也该是这样一座不停火的后厨。

h=HhitHhit+Mmiss×100%\displaystyle h = \frac{H_{\text{hit}}}{H_{\text{hit}} + M_{\text{miss}}} \times 100\%
  1. H_hit一段时间内直接从缓存取到数据的次数。
    Hhit\displaystyle H_{\text{hit}}
  2. M_miss缓存未命中、必须访问下层的次数。
    Mmiss\displaystyle M_{\text{miss}}
  3. 比值命中次数占总请求的比例。如果监控曲线明显下滑,往往意味着预热没跟上或淘汰太激进。
    h=⋯\displaystyle h = \cdots

AI 应用场景

大模型预训练的数据流水线优化

阶段:数据加载与预处理、训练执行

解决的问题:传统 DataLoader 同步加载数据时,GPU 大量时间空转等待 IO。通过多级缓存预热与异步预取,把数据读取、tokenize、掩码生成与 GPU 计算重叠,能显著缩短每次迭代的空档期。

具体案例:某千亿参数 LLM 训练任务中,平台工程师将 DataLoader 的 num_workers 调至 32、prefetch_factor 设为 8,并把原始语料按访问顺序预取进内存映射缓存,训练吞吐提升约 40%,GPU 利用率从 62% 升到 88%。

强化学习阶段的缓存一致性管理

阶段:数据版本管理与缓存失效

解决的问题:RLHF 微调阶段频繁更换偏好数据集,若各训练节点的缓存还留着旧标签样本,模型会在“新旧混杂”的脏数据上训练,导致奖励曲线异常。数据平台需要一套版本校验机制保证所有节点读到同一版本的数据。

具体案例:某对话模型团队在每周更新偏好数据后,通过 manifest 版本号比对触发各节点惰性失效旧缓存,并预热新批次数据到内存,训练日志中的 loss 尖峰随即消失,实验可复现性大幅提升。

多机多卡共享数据缓存

阶段:分布式缓存与存储优化

解决的问题:多个训练节点共用同一份数据集,各自独立从 NAS 读取,导致存储 IO 被打满、训练变慢。用分布式缓存层让节点间共享已读数据块,可以把“每个节点读一遍”变成“全网只读一遍”。

具体案例:某 8 节点 64 卡分布式训练集群接入共享缓存后,数据命中率稳定在 80% 以上,存储带宽需求下降约 5 倍,checkpoint 加载时间从 12 分钟缩至 3 分钟。

Python 演示

python87 行
import timefrom collections import OrderedDict  class CacheLayer:    """通用缓存层,支持 LRU 淘汰与命中统计"""     def __init__(self, capacity, name):        self.capacity = capacity        self.name = name        self.store = OrderedDict()        self.hit_count = 0        self.miss_count = 0     def get(self, key):        if key in self.store:            self.store.move_to_end(key)            self.hit_count += 1            return self.store[key]        self.miss_count += 1        return None     def put(self, key, value):        if key in self.store:            self.store.move_to_end(key)        self.store[key] = value        if len(self.store) > self.capacity:            self.store.popitem(last=False)     def hit_rate(self):        total = self.hit_count + self.miss_count        return self.hit_count / total if total else 0.0  class Prefetcher:    """数据预热器:提前把后续样本装入指定缓存层"""     def __init__(self, data, look_ahead=8, enabled=True):        self.data = data        self.look_ahead = look_ahead        self.enabled = enabled        self.pointer = 0     def prefetch(self, cache):        if not self.enabled:            return        end = min(self.pointer + self.look_ahead, len(self.data))        for idx in range(self.pointer, end):            if idx not in cache.store:                cache.put(idx, self.data[idx])        self.pointer = end  def run_epoch(data, cache, prefetcher, batch_size=8, epochs=2):    """模拟训练:按 batch 消费数据,同时预取后续样本"""    for _ in range(epochs):        for start in range(0, len(data), batch_size):            prefetcher.prefetch(cache)            for idx in range(start, min(start + batch_size, len(data))):                sample = cache.get(idx)                if sample is None:                    sample = data[idx]  # 未命中则到仓库取                    cache.put(idx, sample)            time.sleep(0.002)  # 模拟一次前向传播  def main():    data = ["sample-%02d" % i for i in range(32)]     warm_cache = CacheLayer(32, "warm-memory")    cold_cache = CacheLayer(32, "cold-memory")     for idx in range(8):  # 训练前预热首批样本        warm_cache.put(idx, data[idx])     prefetch_on = Prefetcher(data, look_ahead=8, enabled=True)    prefetch_off = Prefetcher(data, look_ahead=8, enabled=False)     run_epoch(data, warm_cache, prefetch_on)    run_epoch(data, cold_cache, prefetch_off)     print("预热+预取 命中率: %.2f%%" % (warm_cache.hit_rate() * 100))    print("无预热     命中率: %.2f%%" % (cold_cache.hit_rate() * 100))  if __name__ == "__main__":    main()
目的:模拟“训练前预热 + 训练中预取”与“无预热”两种数据加载策略,对比缓存命中率,直观呈现数据预热和预取带来的提升。
  1. self.store = OrderedDict()缓存主体,有序字典天然适合实现 LRU 的“移头部、摘尾部”。
  2. def get(self, key)命中时把键移到末尾表示“刚用过”,同时累加命中计数。
  3. def put(self, key, value)写入缓存,容量满就弹出队头,即淘汰最久未使用的数据。
  4. def hit_rate(self)用累计命中数与总请求数计算命中率,是监控仪表盘的核心指标。
  5. def prefetch(self, cache)预取器核心:按游标把后续样本提前装入 cache,且不污染命中统计。
  6. warm_cache.put(idx, data[idx])开训前预热首批样本,对应“五点前备菜”的动作。
  7. sample = data[idx]未命中时从“仓库”取原始样本,并回填缓存。
  8. prefetcher.prefetch(cache)每个 batch 消费前先预取下一批,对应“出餐口永远有菜”。

常见误区

误区把内存和 SSD 缓存调到最大,命中率就会一直涨,训练一定会变快。我上次把缓存翻了一倍,重启预热却更慢了。

正解缓存收益遵循边际递减。训练数据通常是“流式消费”,一份样本只在当前 epoch 热,过了就不再需要;容量过大反而拖长预热时间,还增加缓存扫描与淘汰的管理开销。真正该做的是让“该热的留在上层、该走的及时走”。

为何常见:人们常把 Web 读多写少场景的经验套到训练上,忽略了训练数据一次性的访问特征。

误区命中率不是 100% 就说明缓存设计失败,我要把所有数据都塞进内存,让每次读取都命中。

正解训练场景中每个样本通常只被当前 epoch 访问,一次遍历后就不再需要。强行追求 100% 命中,意味着缓存里堆满了“永远不会再读”的旧数据,纯属浪费。目标不是盲目追高命中率,而是让热数据集中在最快的层、冷数据早点让位。

为何常见:把命中率当成唯一 KPI,忘记了缓存的意义是“减少总访问代价”,而不是“每次都不跑仓库”。

误区既然要预热,我就写个脚本把训练集全部 load 进内存,反正内存大,一次到位。

正解数据集可能几个 TB,内存根本装不下,而且全量读取耗时极长,把真正要用的热样本反而挤出了缓存。预热的核心是按访问顺序预取“最近需要的部分”,并配合淘汰策略滚动更新——好比后厨只备今晚菜单的菜,而不是把整座仓库搬进厨房。

为何常见:从“预热”字面意思望文生义,以为预热就是全量加载,忽略了容量与局部性。

误区教程都在讲 LRU,所以生产环境直接上 LRU 就对了,LFU 是过时的方法。

正解两者各有适用场景。LRU 对突发热点反应快、实现简单,但遇到“周期性扫描”会频繁抖动;LFU 适合少数样本被长期高频访问的模式,但历史频次会让新热点挤不进来。工程上常做混合或自适应策略,按访问分布动态切换。

为何常见:例题只讲 LRU 的简洁性,让人误以为它是唯一标准答案,忽略了真实负载的多样性。

面试题

Q1. 缓存“命中”指的是下列哪种情况?

单选题 · easy

  • A.数据直接从缓存层取到← 正确答案
  • B.数据要从仓库存储层取
  • C.数据被写入缓存就完成
  • D.缓存空间被全部清空

答案:A。命中就是数据在缓存里、直接取用,好比大厨从砧板旁小冰箱拿到菜。B 是未命中要跑仓库;C 是写入动作;D 是清缓存,都与命中含义无关。

Q2. 关于内存缓存的正确描述是?

单选题 · easy

  • A.容量大但读取速度慢
  • B.存取速度快容量较小← 正确答案
  • C.价格便宜且寿命长久
  • D.读写耗电比磁盘更高

答案:B。内存相当于后厨的冷藏小冰箱,速度最快但容量有限、成本高。A 描述的是仓库,C 和 D 均不符合内存特性。

Q3. LRU 淘汰策略的核心依据是什么?

单选题 · mid

  • A.被访问过的次数最少
  • B.最后访问时间最久远← 正确答案
  • C.数据的体积最为庞大
  • D.写入缓存时间最接近

答案:B。LRU 全称 Least Recently Used,淘汰“最久没被用过”的数据,像冰柜管理员扔掉最没销路的旧货。A 是 LFU 的标准;C 和 D 与 LRU 无关。

Q4. 训练前做数据预热的主要目的是?

单选题 · mid

  • A.扩大训练数据样本总量
  • B.减少 GPU 等待数据耗时← 正确答案
  • C.降低模型参数量级水平
  • D.提升网络带宽使用上限

答案:B。预热是把数据提前搬进缓存,让 GPU 一开火就有料,减少 IO 等待。它不改变数据量、模型参数量,也不直接提升带宽上限。

Q5. 数据流水线的整体吞吐量由什么决定?

单选题 · mid

  • A.全部环节耗时加总
  • B.最慢环节单点耗时← 正确答案
  • C.最快环节的平均值
  • D.环节数量除以总耗时

答案:B。流水线所有环节并行,整体节拍由最慢的环节决定,正如出餐口速度取决于最慢的洗菜工或切菜工。A 是串行耗时;C、D 忽略了瓶颈约束。

Q6. 下列哪些属于多级缓存的层级?

多选题 · mid

  • A.内存缓存层← 正确答案
  • B.SSD 缓存层← 正确答案
  • C.分布式存储层← 正确答案
  • D.模型参数缓存层

答案:ABC。多级缓存指内存、SSD、分布式存储这三级“小冰箱—大冰柜—仓库”的分层结构。模型参数缓存属于模型并行范畴,不是数据缓存层级。

Q7. LFU 算法在实际使用中的一个典型问题是?

单选题 · hard

  • A.实现依赖复杂硬件
  • B.历史记录阻碍新热点← 正确答案
  • C.无法统计访问频率
  • D.只适合极小缓存场景

答案:B。LFU 记录长期访问频次,老数据频次高占着位置,新热点频次刚起步挤不进来,就像冰柜里旧食材总因“历史销量好”占着地方。A、C、D 均不构成 LFU 的典型短板。

Q8. 常见的缓存失效策略有哪些?

多选题 · hard

  • A.主版本变更广播清缓存← 正确答案
  • B.每次读取校验数据版本← 正确答案
  • C.定期重启服务器清空
  • D.训练前完全禁用缓存

答案:AB。主动广播失效和惰性版本校验是两种标准做法:前者变更时通知各节点,后者读取时核对 manifest 批次。重启清空和禁用缓存属于绕过问题,不是机制设计。

Q9. 数据预取(prefetch)机制的作用是?

单选题 · easy

  • A.压缩数据节省存储空间
  • B.后台提前加载后续数据← 正确答案
  • C.对数据进行加密传输
  • D.删除掉多余的训练样本

答案:B。预取让当前 batch 计算的同时,后台加载下一批数据,像送菜小哥提前把下一道菜端到出餐口。A、C、D 都是无关的数据处理动作。

Q10. 下列哪些手段可以提升 GPU 利用率?

多选题 · hard

  • A.适当增大缓存容量← 正确答案
  • B.结合预取异步加载← 正确答案
  • C.延长数据缓冲队列← 正确答案
  • D.削减全部训练数据

答案:ABC。增大缓存让热数据留在内存,预取与异步加载让供给贴着消费走,缓冲队列平滑生产消费节奏,都能减少 GPU 空转。削减训练数据只会降低模型质量,与利用率无关。