在智能工厂的仓库里,仓管员老周每天的工作是这样:货物还没到,他就先照着订单流程图,把每批箱子该放哪一格、该用什么顺序摆,全部写成一页“上架方案”。货物到了之后,工人只要照方案取货,运得又快又不打架。这台“会思考的仓库”里的老周,就是 AI 编译器 XLA 中的布局分配(Layout Assignment)模块——它在编译阶段替每个张量决定以哪种内存格式存放。摆放方式不同,搬运效率天差地别:选对布局,推理速度成倍提升;选错布局,硬件再强也白费。你不需要懂底层就能建立这样的直觉:内存就是仓库,布局就是上架顺序。

岗位关联

掌握布局分配,意味着你能解释同一个模型在 GPU 上为什么时快时慢,也拥有分析和调试推理引擎性能问题的关键视角。

目录
  1. 可视化演示
  2. 布局分配概念
  3. 内存布局格式
  4. 硬件偏好分析
  5. 布局转换代价
  6. 分配策略算法
  7. 编译器集成流程
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

通过NCHW与NHWC布局对比、编译流程定位和数据流传播三个模式,展示XLA如何自动选择最优内存布局提升硬件吞吐。

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

NCHW与NHWC内存布局对比 NCHW:通道优先,同通道像素连续存储 NHWC:空间优先,同位置多通道连续存储 Copy开销 = 2 × |tensor| 布局转换产生额外内存读写,需最小化Copy算子数量

布局分配概念

老周总说:上架顺序就是效率的命根子。他手里的上架方案,写着每批货到了之后放哪一排、哪一面朝外、哪一格挨着下一个工位。XLA 里的布局分配就扮演了老周这个角色:它不参与具体搬运,但它的每一行安排,都决定了后续所有工人跑步的路线。AI 编译器里的“货物”,就是张量。

张量自带逻辑形状,比如批量大小 \(N\)、通道数 \(C\)、高度 \(H\)、宽度 \(W\)。形状回答的是“数据长什么样”,而布局回答的是“数据在内存里按什么顺序排”。同样是形状为 [N, C, H, W] 的数据,既可以用 NCHW 顺序存放,也可以用 NHWC 顺序存放,内存中的地址顺序完全不同。布局分配任务,就是为计算图中的每一个张量挑选一个合适的布局。

在 XLA 编译器中,布局分配紧跟在 HLO 优化之后。HLO 是 XLA 的中间表示,图中的每个节点是一个算子,每个张量都带 Shape 描述。布局分配为这些 Shape 补充 Layout 信息,必要时插入拷贝或转置算子,保证下游每个算子都能按自己期望的布局读到数据。它做的是“排兵布阵”,而不是数值计算。

老周的方案一旦定下,所有工人都会严格遵守。对编译器来说,布局一旦定下,内存读写路径也就定了。编译期多花一点时间做规划,运行期就能少跑许多弯路,这就是“以编译时间换运行效率”的典型体现。

B(t)=ℓ,ℓ∈{NCHW,NHWC}\mathcal{B}(t) = \ell, \quad \ell \in \{\mathrm{NCHW}, \mathrm{NHWC}\}
通俗说明:布局分配就是一个“给仓库货物贴码”的函数,每个张量都会领到一张专属的码放单。
  1. 张量对象设计算图里有张量 \(t\),它是布局分配的输入对象。
    t∈Tt \in \mathcal{T}
  2. 布局集合XLA 支持的布局集合包含 NCHW、NHWC 等若干选项。
    ℓ∈L={NCHW,NHWC}\ell \in \mathcal{L} = \{\mathrm{NCHW}, \mathrm{NHWC}\}
  3. 映射动作布局分配把每个张量映射到一个布局上,记作映射函数。
    B(t)=ℓ\mathcal{B}(t) = \ell

内存布局格式

老周的仓库里进了一批四层收纳柜,每层放不同颜色的小件。他面临两种摆法:一是把所有红色小件集中放一区、所有绿色小件集中放另一区;二是按“位置”打包,每个坐标点上红绿蓝各一件。第一种思路像 NCHW,第二种像 NHWC。

用四维张量来理解:\(N\) 是样本数,\(C\) 是通道数,\(H\) 是高度,\(W\) 是宽度。NCHW 布局把通道放在内存维度的前面,让同一通道的像素连续存放;NHWC 布局把宽度放在最前,让同一空间位置的不同通道连续存放。哪种更好,取决于下游硬件怎么取数。

从步长角度看,NCHW 中宽度 \(W\) 的步长是 1,也就是相邻地址单元正好是相邻像素;而 NHWC 中通道 \(C\) 的步长是 1。步长为 1 的维度意味着硬件可以顺序读取一整段地址,对缓存和批量加载特别友好。XLA 内部把布局表达为维度顺序,比如默认布局让最后一个维度连续,等价于这里的 NHWC 风格。

两种摆法没有绝对的优劣,只看下游流水线喜欢哪一种。老周知道,工人的取货路线决定仓库效率,而布局的选择,决定硬件访存的路线。

offsetNCHW(n,c,h,w)=((n×C+c)×H+h)×W+w\mathrm{offset}_{\mathrm{NCHW}}(n,c,h,w) = ((n \times C + c) \times H + h) \times W + w
通俗说明:这个公式给出 NCHW 布局下,某个像素在内存中的地址偏移,相当于从仓库入口走到目标货位的距离。
  1. 最内层偏移宽度方向每个像素占一个地址,所以先乘宽度 \(W\)。
    w,0≤w<Ww, \quad 0 \le w < W
  2. 高度方向累加每一行有 \(W\) 个像素,因此行号 \(h\) 贡献 \(h \times W\) 个地址。
    h×W+wh \times W + w
  3. 通道方向累加每个通道占 \(H \times W\) 个位置,通道号 \(c\) 贡献 \(c \times H \times W\)。
    (c×H+h)×W+w(c \times H + h) \times W + w
  4. 批量方向累加每个样本占 \(C \times H \times W\) 个位置,最后加上 \(n \times C \times H \times W\)。
    ((n×C+c)×H+h)×W+w((n \times C + c) \times H + h) \times W + w

硬件偏好分析

老周发现,老师傅老李习惯按颜色整摞地搬,因为他面前有四个分色料斗;新来的小张则喜欢按位置成套地拿,因为他的推车上每组格子都配好了全套颜色。硬件也是这样:不同计算单元对数据连续性有完全不同的口味。

CPU 与 GPU 在读取内存时,都以缓存行或事务为单位。连续访问意味着硬件一次能搬运一整段有用数据,而跨步访问会浪费带宽——搬回来的缓存行里只有一小块是需要的。因此,一个算子如果在一个布局下能让核心循环顺序读取内存,它的吞吐量会显著高于另一种布局。

不同后端也各有偏好。卷积算子常用 cuDNN,在 NCHW 上往往有深度优化;而移动端推理引擎中,NHWC 更容易把卷积、归一化、激活融合成一个算子,从而减少中间张量的读写。XLA 在给每个算子选布局时,会参考这些后端的约束与偏好,再把选择结果传递下去。

老李和小张谁更快?答案是“看货怎么摆”。硬件偏好决定了最优布局的候选名单,而布局分配要做的,就是从这份名单里选出全局最划算的安排。

η=BusedBloaded\eta = \frac{B_{\mathrm{used}}}{B_{\mathrm{loaded}}}
通俗说明:这是访存效率公式,分子是真正用到的字节数,分母是硬件实际从内存搬进来的字节数。效率越接近 1,布局越契合硬件。
  1. 有效字节\(B_{\mathrm{used}}\) 表示计算真正需要的连续数据量。
    Bused=本次计算所需字节B_{\mathrm{used}} = \text{本次计算所需字节}
  2. 加载字节\(B_{\mathrm{loaded}}\) 表示硬件缓存行实际拉取的数据总量。
    Bloaded=缓存行大小×缓存行数量B_{\mathrm{loaded}} = \text{缓存行大小} \times \text{缓存行数量}
  3. 效率比值两者相除得到访存利用率,跨步访问会让分母变大。
    η=BusedBloaded\eta = \frac{B_{\mathrm{used}}}{B_{\mathrm{loaded}}}

布局转换代价

如果老周中途发现某条产线需要改变码放方式,就得调一队工人把整批货重新堆一遍。重堆的时间没人买单,但它真实存在。布局转换也是如此:当一个算子的输出布局和下一个算子期望的输入布局不一致时,编译器必须插入一个转置或拷贝算子。

转置的本质是逐元素地把数据从旧顺序搬到新顺序。假设一个张量有 \(N \times C \times H \times W\) 个元素,那么一次布局转换至少要对每个元素做一次读写。现代 GPU 虽然内存带宽很大,但数据量也极大;一次百万级元素的转置,可能比一个小算子本身还贵。

更糟糕的是转换链问题:若布局在 A 到 B 再到 C 的过程中换来换去,就可能出现 A 转 B、B 转 A 的重复劳动。XLA 的布局分配会尽量避免这种低效循环,把转换次数压到最少。正因为转换有真实成本,“选对了理想布局”不等于“整体更快”,还要看路途中多花的搬运钱。

老周的经验是:能不重堆就不重堆,必须重堆就一次堆到位。布局分配算法也遵循同样的原则,把转换成本当作一条硬约束,写进最终决策的总账单里。

Ctrans=S×αC_{\mathrm{trans}} = S \times \alpha
通俗说明:一次布局转换的总代价等于元素个数乘以每个元素的搬运成本。元素越多,转换越昂贵。
  1. 元素总数四维张量的元素个数是各维度大小相乘。
    S=N×C×H×WS = N \times C \times H \times W
  2. 单位搬运成本每个元素从旧地址搬到新地址,需要一次读和一次写,对应单位成本 \(\alpha\)。
    α=Tread+Twrite\alpha = T_{\mathrm{read}} + T_{\mathrm{write}}
  3. 总成本搬运总量乘以单位成本,得到一次转换的总开销。
    Ctrans=S×αC_{\mathrm{trans}} = S \times \alpha

分配策略算法

老周做最终决定前,会画一张大表:每个工位都有自己的偏好得分,每两段工序之间又有搬运费。他要找的,不是某一段最快,而是整条流水线总耗时最低。布局分配算法做的就是这件事——它不是给单个算子挑最优布局,而是给整张计算图挑一组全局最优布局。

算法通常从约束传播开始:先收集每个算子的硬约束,比如某个算子库只支持 NCHW,那这个算子输出张量的布局就被锁死。再收集软偏好,比如某个算子用 NHWC 会快 10%,但不强制。然后结合代价模型,在硬约束的框架下为软偏好寻找性价比最高的布局组合。

代价模型把每个算子的执行时间估算为布局的函数,把每条边上可能发生的转置成本也折算进去。最后,问题变成一个最优化问题:在所有满足硬约束的布局组合中,找总代价最小的一组。由于计算图可能很大,XLA 会使用启发式搜索,而不是暴力枚举全部组合。

全局优化带来的收益有时反直觉:为了让某个算子用上最爱的布局,结果却要付出巨大转置代价,反而不划算。老周明白,聪明不是每段路都跑最快,而是整条路线一气呵成。

min⁡ℓ  ∑iOpCosti(ℓ)+∑(u,v)∈ETransCost(ℓu,ℓv)\min_{\boldsymbol{\ell}} \; \sum_{i} \mathrm{OpCost}_i(\boldsymbol{\ell}) + \sum_{(u,v)\in E} \mathrm{TransCost}(\ell_u, \ell_v)
通俗说明:布局分配的总目标是让“所有算子的执行耗时”加上“所有布局转换耗时”的总和最小。这就像老周追求整厂效率最优,而不只是某一个工位最快。
  1. 算子偏好成本第 \(i\) 个算子在布局向量 \(\boldsymbol{\ell}\) 下的估算执行时间。
    ∑iOpCosti(ℓ)\sum_{i} \mathrm{OpCost}_i(\boldsymbol{\ell})
  2. 转换成本图中每条边 \((u,v)\) 上,如果两个相邻张量布局不同,就要付出搬运费。
    ∑(u,v)∈ETransCost(ℓu,ℓv)\sum_{(u,v)\in E} \mathrm{TransCost}(\ell_u, \ell_v)
  3. 全局最小化在所有可行布局组合里,选择让上述两项之和最小的那个。
    min⁡ℓ  (算子成本+转换成本)\min_{\boldsymbol{\ell}} \; \left( \text{算子成本} + \text{转换成本} \right)

编译器集成流程

老周的方案要落到仓库系统,需要一套完整流程:订单图先进系统,上架规划在开工前完成,规划结果再下发给每个工人。XLA 的编译流程也是三层串联:HLO 图先进来,布局分配在中间把关,最后交给代码生成阶段。

在 XLA 中,布局分配以 HLO 的 Shape 为载体工作。它给每个张量的 Shape 附加 Layout 信息,并把带有布局约束的优化结果一路传递到后端。后端在生成 LLVM IR 或 GPU 指令时,会根据这些布局信息决定 load 和 store 的地址计算方法,最终让硬件按最顺畅的顺序搬运数据。

这个流程对推理性能的影响非常直接:选对布局后,内存事务数减少,访存带宽利用率提高;选错布局,即使浮点算力再高,也会被内存瓶颈卡住。业界常见的推理优化报告里,布局调整带来的加速常常在 20% 到数倍之间,这还不包括算子融合带来的额外收益。

老周的方案从纸张变成全厂执行的指令,只需要一次编译期的提前规划。布局分配同样如此:它在编译阶段默默布局,在每次推理运行时享受回报。

speedup=TbeforeTafter\mathrm{speedup} = \frac{T_{\mathrm{before}}}{T_{\mathrm{after}}}
通俗说明:加速比就是优化前的推理耗时除以优化后的推理耗时。比值大于 1,表示布局优化让模型跑得更快。
  1. 优化前耗时采用默认布局时,单次推理的端到端耗时。
    TbeforeT_{\mathrm{before}}
  2. 优化后耗时经过布局分配调整后,单次推理的端到端耗时。
    TafterT_{\mathrm{after}}
  3. 加速比用前者除以后者,得到布局优化带来的倍率提升。
    speedup=TbeforeTafter\mathrm{speedup} = \frac{T_{\mathrm{before}}}{T_{\mathrm{after}}}

AI 应用场景

移动端推理引擎布局优化

阶段:模型编译与硬件适配

解决的问题:移动端 GPU 对内存访问模式极其敏感,传统 NCHW 布局在部分手机上缓存命中率低、融合算子难以展开,导致推理耗电高、延迟大。

具体案例:某 App 上线人脸检测模型,原始 ONNX 模型使用 NCHW 布局。推理引擎团队在编译阶段将模型转为 NHWC 布局,并在首个卷积前插入一次布局转换,后续卷积和激活函数在 NHWC 下连续访存,端到端延迟降低约 30%。

数据中心大规模 Transformer 推理

阶段:编译器后端优化

解决的问题:BERT 等 Transformer 模型在 GPU 上默认布局无法同时满足多头注意力和全连接层的访存偏好,导致小 batch 推理时带宽利用率低。

具体案例:ML 编译器团队借助 XLA 的布局分配,为投影层选择 NHWC 布局,使注意力点乘阶段可以连续读取 \(C\) 维数据,同时避免在每一层都做转置;优化后单卡吞吐提升 25%。

Python 演示

python71 行
# 简化版布局分配器: 为计算图上的每个算子挑选内存布局 class Tensor:    """张量只记录形状和名称"""    def __init__(self, name, shape):        self.name = name        self.shape = shape  # (N, C, H, W) 四元组     def element_count(self):        total = 1        for d in self.shape:            total *= d        return total  class LayoutAssigner:    LAYOUTS = ('NCHW', 'NHWC')     def __init__(self, op_pref, transpose_cost=1):        self.op_pref = op_pref      # {'op': {'Layout': 开销}}        self.transpose_cost = transpose_cost     def assign(self, graph):        """对图做贪心布局分配, 返回 (布局字典, 总开销)        graph: 边表, 每项为 (op_name, input_tensor, output_tensor)        """        assigned = {}        total_cost = 0        for op_name, inp, out in graph:            best_layout = None            best_expense = None            # 候选布局只有两个, 直接遍历            for cand in self.LAYOUTS:                trans = 0                # 输入来自上个算子, 可能已是别的布局                # 如果布局不同就多一次数据搬运                if assigned.get(inp, 'NCHW') != cand:                    trans = inp.element_count() * self.transpose_cost                pref = self.op_pref.get(op_name, {}).get(cand, 0)                expense = trans + pref                if best_expense is None or expense < best_expense:                    best_expense = expense                    best_layout = cand            assigned[out] = best_layout            total_cost += best_expense        return assigned, total_cost  def main():    # 两个卷积共享中间张量 b    a = Tensor('a', (1, 3, 224, 224))    b = Tensor('b', (1, 64, 112, 112))    c = Tensor('c', (1, 64, 112, 112))    graph = [        ('conv1', a, b),        ('conv2', b, c),    ]    # 算子偏好: conv1 喜欢 NCHW, conv2 喜欢 NHWC    op_pref = {        'conv1': {'NCHW': 100, 'NHWC': 900},        'conv2': {'NCHW': 900, 'NHWC': 100},    }    assigner = LayoutAssigner(op_pref, transpose_cost=20)    layouts, cost = assigner.assign(graph)    for tensor, layout in layouts.items():        print(tensor.name, '->', layout)    print('总开销 =', cost)  if __name__ == '__main__':    main()
目的:用贪心策略模拟 XLA 布局分配,展示“算子偏好 + 转置代价”如何决定最终布局选择。
  1. trans = inp.element_count() * self.transpose_cost输入布局不匹配时,按元素总数估算一次转置的搬运开销
  2. pref = self.op_pref.get(op_name, {}).get(cand, 0)读取当前算子对候选布局的执行偏好
  3. expense = trans + pref把搬运代价和算子偏好合并成候选布局的总开销
  4. if best_expense is None or expense < best_expense遍历候选布局,选出当前算子代价最低的一项
  5. assigned[out] = best_layout记录输出张量的布局,作为下游算子的输入约束

常见误区

误区“布局分配应该是在模型跑起来之后,每次推理都先给数据做一次转置拷贝,不然数据顺序怎么变?”

正解布局分配是编译期的决策过程,它只负责“选择布局”,并不亲自搬运数据。只有在图中真正出现“上游输出布局与下游期望布局不一致”时,编译器才会插入一个显式转置算子。运行时的转置是结果,不是布局分配本身。

为何常见:因为在调试计算图时经常看到 transpose 算子,初学者容易把这层“显式物理操作”误认为是布局分配的全部。

误区“既然大家都说 cuDNN 对 NCHW 优化好,那 NCHW 就是永远的赢家,其他布局都不值得考虑。”

正解布局优劣完全依赖硬件后端的访问模式和算子实现。GPU 上的深度卷积库可能在 NCHW 上做了极致优化,而移动端推理引擎却常常用 NHWC 换取消缓存连续与算子融合。脱离硬件谈布局,就像脱离仓库谈上架方案。

为何常见:看到某个基准测试里 NCHW 赢了一次,就把局部结论推广成绝对规律,忽略了访存模式和硬件上下文。

误区“编译器已经知道我最快的布局是 NHWC,那就把所有算子的布局都设成 NHWC,性能不就最大化了吗?”

正解改变布局需要插入数据搬运算子。如果全图强制使用同一种布局,某些算子可能因此付出大量转置代价,整体反而变慢。布局分配的真正目标是“算子收益减去转换成本后净收益最大”,而不是让每个算子都跑到自己的峰值。

为何常见:人脑容易把“单点最优”和“全局最优”混为一谈,忽略了相邻算子之间的数据传递成本。

误区“布局分配是卷积专用的优化,矩阵乘法、归一化、注意力这些算子不需要关心布局。”

正解布局影响一切张量算子。矩阵乘法中的行主序与列主序、注意力机制中的 \(QK^T\) 读取模式、归一化层对通道维的规约方式,全都依赖数据在内存中的排列。布局分配是通用优化,不是卷积专属。

为何常见:讲解布局时总拿卷积举例,因为视觉模型最容易感知到 NCHW 与 NHWC 的差异,从而让人误以为其他算子不受影响。

面试题

Q1. 布局分配的主要目标是什么?

单选题 · easy

  • A.让每个操作使用最优内存布局← 正确答案
  • B.把所有张量都转成 NCHW
  • C.在运行时反复转置数据
  • D.减少参数量以压缩网络

答案:A。布局分配的核心是为每个张量选择合适的布局,使整体访存效率最高。它不是把一切统一成 NCHW,也不是在运行时反复转置,更与参数量压缩无关。好比老周的目标不是让所有货都摆同一种方式,而是让整条流水线最顺。

Q2. 在 NHWC 布局中,内存步长最小的维度是?

单选题 · easy

  • A.批次维度 N
  • B.通道维度 C← 正确答案
  • C.高度维度 H
  • D.宽度维度 W

答案:B。NHWC 把通道放在最后,所以相邻内存地址对应同一空间位置的不同通道,通道维步长为 1。若想验证,可以想象仓库里每个货位同时摆红绿蓝三件小件,连续三个地址正好是一套颜色组合。

Q3. 哪些因素会影响硬件对布局的偏好?

多选题 · mid

  • A.数据访问的对齐方式← 正确答案
  • B.缓存行利用效率← 正确答案
  • C.算子是否支持融合← 正确答案
  • D.编译器的版本号

答案:ABC。硬件偏好由访存连续性、对齐、缓存行利用率以及算子融合能力决定。编译器版本号不决定偏好,只是工具版本差异;真正影响性能的是数据怎么摆放、硬件怎么搬运。

Q4. 把一个 NCHW 张量转变成 NHWC,XLA 会在图中插入什么?

单选题 · mid

  • A.一个转置或拷贝算子← 正确答案
  • B.一个卷积融合算子
  • C.一个批量归一化算子
  • D.一个元素加法算子

答案:A。布局不同意味着内存顺序不同,必须通过转置或拷贝算子逐元素搬运。卷积融合、归一化、加法都不负责改变数据排列顺序。就像换货架摆放方式必须重搬,而不是顺手继续干活。

Q5. 布局转换的主要开销来自哪里?

单选题 · mid

  • A.逐元素的数据搬运拷贝← 正确答案
  • B.卷积的权重初始化
  • C.梯度下降的迭代
  • D.归一化的均值方差

答案:A。转置要把每个元素从旧地址读出来再写到新地址,读写次数与元素总数成正比。权重初始化、梯度迭代、均值方差都发生在训练阶段,与布局转换的运行时数据搬运无关。

Q6. XLA 布局分配常采用哪些策略?

多选题 · hard

  • A.约束传播与偏好收集← 正确答案
  • B.代价模型全局决策← 正确答案
  • C.硬件后端特性匹配← 正确答案
  • D.随机扰动实验

答案:ABC。布局分配先收集每个算子的硬约束和软偏好,再用代价模型对候选布局做全局评估,并匹配硬件后端能力。随机扰动没有方向,无法保证找到低代价布局,不是编译器常用策略。

Q7. XLA 编译流程中布局分配位于哪个阶段?

单选题 · easy

  • A.HLO 优化阶段← 正确答案
  • B.前端语法解析
  • C.权重随机初始
  • D.反向传播过程

答案:A。布局分配作用在 HLO 图上,属于编译优化阶段;它既不在前端解析阶段,也不涉及训练时的权重初始化和反向传播。老周是在仓库开工前做规划,而不是在收货时才临时想。

Q8. 以下哪个做法最可能降低布局转换代价?

单选题 · mid

  • A.让前后算子共用布局← 正确答案
  • B.每次推理都换布局
  • C.把张量切得更碎
  • D.禁止使用所有布局

答案:A。前后算子共用一种布局,就不需要额外插入转置算子,搬运成本降为零。频繁换布局、切碎张量都不解决顺序问题;禁止所有布局更是不可行。这就像让相邻工位共用同一套货架规格,省去中间重搬环节。

Q9. 关于内存布局与硬件性能,以下哪些说法是正确的?

多选题 · hard

  • A.连续访问能提高缓存效率← 正确答案
  • B.跨步访问会浪费带宽← 正确答案
  • C.布局转换一定没有代价
  • D.布局只要正确就一定最优

答案:AB。连续访问能最大化缓存行利用率,跨步访问会拉低有效带宽。布局转换有真实搬运成本,而且“正确”只是前提,不代表性能最优。仓库里货没有摆错不代表摆得最顺手,效率和正确是两回事。

Q10. 算子偏好布局与输入已有布局冲突时,最优选择取决于?

单选题 · extreme

  • A.总成本的最小化比较← 正确答案
  • B.单个算子速度最快
  • C.编译器版本新旧
  • D.训练数据集大小

答案:A。此时需要把算子收益和转置成本一起算总账,选择总成本最低的方案。只看单算子速度会忽略转换开销;编译器版本和数据集大小不影响这张计算图的布局取舍。这是全局优化思想的典型考题。