---
title: "路径指标与优化目标"
description: "用 FLOPs、逻辑读写量和内存相关指标比较收缩序，并设置优化目标与切片大小限制。"
eyebrow: "基本概念"
---

## PathStats 的六项指标 {#six-metrics}

`PathStats` 根据网络结构和收缩序计算工作量与张量大小，不需要数值数组。其中三项累计整条路径的计算与数据访问量，另外三项描述与内存需求相关的张量大小。

字段名中的 `log10` 和 `log2` 表示保存的是对数。例如，`log10_flops=6` 表示 $10^6$ 次标量乘法，`log2_max_size=20` 表示最大结果包含 $2^{20}=1{,}048{,}576$ 个元素。比较同一项指标时，可以直接比较对数值：值越小，对应的工作量或张量大小越小。

设第 $s$ 个二元步骤的输入和结果为 $A_s$、$B_s$、$C_s$，$|X|$ 表示张量元素数，$F_s$ 表示该步的标量乘法次数。

| 字段 | 定义 | 回答的问题 |
| --- | --- | --- |
| `log10_flops` | $\log_{10}\sum_s F_s$ | 整条路径的标量乘法次数 |
| `log2_total_size` | $\log_2\sum_s\lvert C_s\rvert$ | 全部结果的写出元素数 |
| `log2_read_write` | $\log_2\sum_s(\lvert A_s\rvert+\lvert B_s\rvert+\lvert C_s\rvert)$ | 整条路径的逻辑读写量 |

读写量按每步读取两个输入、写出一个结果计数；原始输入按完整数组大小计入。因此，写出量与读写量不是同一项，也都不是硬件实测内存流量。

`log2_total_size` 将各步结果的大小累计相加。前一步的结果在用完后可以释放，所以这个累计量并不表示这些张量需要同时保存在内存中；同时保留的大小由下面的峰值指标描述。

### 单步指标计数 {#per-step-accounting}

本步 FLOPs 按参与二元收缩的 index 维度之积计数；结果大小是保留 index 的维度之积。完整路径指标采用对数表示，避免大型网络的线性计数溢出。

本步结果保留属于 `output` 的 index，以及仍被这两个输入以外的张量使用的 index；其余参与本步的 index 求和消去。

设 $I(X)$ 为本步输入张量 $X$ 的不同 index 集合，$d_i$ 为 index $i$ 的维度，则

$$
F_s=\prod_{i\in I(A_s)\cup I(B_s)}d_i,
\qquad |C_s|=\prod_{i\in I(C_s)}d_i.
$$

这个计数包含本步将被求和消去的 index。若执行器先做一元求和，实际运算步骤可能不同；这里统计的是给定二元收缩序的结构代价。复数标量乘法仍计为一次标量乘法，不折算成实数指令数。

## 三个内存相关指标 {#memory-related-metrics}

三项分别描述**最大的结果张量、单步输入与结果的总大小、执行过程中同时保留的张量总大小**。都以元素数计量，报告中保存其以 2 为底的对数。

| 指标 | 字段 | 计数范围 |
| --- | --- | --- |
| 最大结果张量 | `log2_max_size` | $\max_s\lvert C_s\rvert$，包括最后的输出 |
| 最大单步大小 | `log2_max_contraction_size` | $\max_s(\lvert A'_s\rvert+\lvert B'_s\rvert+\lvert C_s\rvert)$ |
| 峰值存活张量大小 | `log2_peak_size` | $\max_t L_t$，$L_t$ 为时刻 $t$ 尚未释放的张量元素数之和 |

$A'_s$、$B'_s$ 是经过一元求和和对角处理后的逻辑输入。峰值统计则计入完整输入；每步先计入新结果，再释放本步输入。

以[上一页的三矩阵乘法](/docs/tensor-network#what-a-path-decides)为例，输入大小为 $2\times3$、$3\times4$、$4\times5$：

| 指标（元素数，不取对数） | $(AB)C$ | $A(BC)$ |
| --- | --- | --- |
| 最大结果张量 | 10 | 15 |
| 最大单步大小 | 38 | 47 |
| 峰值存活张量大小 | 46 | 53 |

两条收缩序的输入总计都是 38 个元素。第一步分别产生 8 或 15 个元素，因此峰值为 $38+8=46$ 或 $38+15=53$。对于 $(AB)C$，最终输出的 10 个元素比第一步的 8 个更大，所以最大结果张量为 10。

将元素数乘以每个元素的字节数，可估算相应张量的数据大小。例如 46 个 `float64` 元素占 $46\times8=368$ 字节。这仍不等于进程 RSS 或显存占用：转置临时数组、内存分配器和后端工作区没有计入。

`target_size` 限制每个 slice 的二元收缩结果元素数；单张量网络另检查最终输出。因此，它限制的是 `log2_max_size` 所描述的中间张量大小。它不涵盖所有一元临时数组，不直接约束 `log2_peak_size`，也不等于 RSS 或显存。

如果数值执行同时并行多个 slice，总驻留内存可能明显高于单个 slice 的路径指标。评估内存时还要记录 dtype、线程数、并发 slice 数和实测 RSS。

## 用指标定义优化目标 {#definition}

路径指标描述计算量和中间张量大小；优化目标决定如何比较不同路径。`PlannerObjective` 用完整路径的 FLOPs $F(P)$ 与逻辑读写量 $R(P)$ 计算目标值：

$$
J(P)=w_F F(P)+w_R R(P).
$$

默认权重为 `flops_weight=1`、`read_write_weight=64`。报告中的 `planner_objective_score_log2` 保存 $\log_2 J(P)$。

例如，两个候选的 $(F,R)$ 分别为 $(1000,100)$ 和 $(1200,50)$。只比较 FLOPs 时，第一个候选较好；使用默认权重时，二者目标值分别为 7400 和 4400，第二个候选较好，因为它减少了逻辑读写量。各项指标保留这些具体差别，目标值则给出本次搜索采用的排序依据。

| 权重 | 目标标签 | 比较内容 |
| --- | --- | --- |
| (1, 64) | flops\_read\_write | FLOPs 与逻辑读写量的加权和 |
| (1, 0) | total\_flops | 只比较总 FLOPs |
| (0, 1) | total\_read\_write | 只比较总逻辑读写量 |

权重必须有限、非负，并且不能同时为 0。权重 64 是目标函数中的相对系数，不是字节数、缓存行大小或实测带宽。

### 在 Python 中指定权重 {#usage}

```python
from arctn import arctn_schedule

# 只按 FLOPs 优化；省略这两个权重参数时使用默认值 (1, 64)
report = arctn_schedule(
    inputs, output, size_dict,
    flops_weight=1.0, read_write_weight=0.0,
)
```

改变权重可能改变候选路径的排名、局部改进的接受结果和最终返回路径。

| 报告字段 | 含义 |
| --- | --- |
| planner\_objective | 本次调用使用的目标标签；权重分别由 flops\_weight 和 read\_write\_weight 字段报告 |
| planner\_objective\_score\_log2 | 最终收缩序的加权目标值的 log2；启用切片时计入全部切片 |
| log10\_flops / log2\_read\_write | 返回路径在未切片网络上的指标；启用切片后，全部切片的总量分别读取 sliced\_log10\_flops\_total 和 planner\_log2\_read\_write |

### 搜索中的目标函数 {#one-objective-per-call}

权重在寻路入口确定。各生成器可以采用不同的局部启发式，但完整候选、树改进和最终选择使用同一个 `PlannerObjective`。

局部启发式用于产生候选，不等于完整路径的目标值。例如，随机贪心的一步局部代价用于选择下一对张量，而多次搜索返回的完整路径仍按 $J(P)$ 比较。

## 优化目标与切片约束 {#objective-versus-constraints}

优化目标用来比较方案的好坏，`target_size` 则规定单片结果的大小上限。一个方案即使 FLOPs 很低，只要不满足请求的大小限制，就不能作为满足约束的结果返回。搜索中使用的惩罚项不能替代最终的大小检查。

指定切片 index 后，切片数量等于这些 index 维度的乘积。ArcTN 按“单片工作量 × 切片数量”比较切片方案。使用以 2 为底的对数表示时，总目标值的对数等于单片目标值的对数加上 `log2_n_slices`。

$$
\begin{aligned}
N_{\mathrm{slice}}&=\prod_{i\in S}d_i,\\
J_{\mathrm{sliced}}&=N_{\mathrm{slice}}J_{\mathrm{one\ slice}}.
\end{aligned}
$$

$S$ 是切片 index 集合。取对数后：

$$
\log_2 J_{\mathrm{sliced}}=\log_2 J_{\mathrm{one\ slice}}+\log_2 N_{\mathrm{slice}}.
$$

该结构模型计入各片的二元收缩，不包含跨片结果求和的额外加法。

`log2_peak_size`、`log2_max_contraction_size` 等指标仍会报告，但它们不是上述加权目标的组成项。切片限制的详细说明见[切片与 target_size](/docs/slicing#target-size)。

## 计算与比较路径指标 {#recompute-and-compare}

已有网络和 SSA path 时，用 `simulate_path` 校验路径并计算 `PathStats`，无需提供数值数组，也不重新搜索：

```rust
let stats = arctn::simulate_path(&net, &ssa_path)?;
println!("{:?}", stats);
```

跨方法或跨库比较时，应在相同网络和维度下，用同一套程序计算路径指标。各库对 FLOPs、读写量以及一元操作的统计方式可能不同；切片后的单片指标也不能直接与全部切片总量比较。测试数据的组成与用途见[测试集](/docs/benchmarks)。

### 空路径的报告值 {#empty-path}

单张量网络可以使用空路径，没有二元收缩步骤。此时，Rust 的 `log10_flops`、`log2_total_size` 和 `log2_read_write` 用负无穷表示对应工作量为零，`log2_max_contraction_size` 为 0；`log2_max_size` 取最终输出大小，`log2_peak_size` 仍计入完整输入。

Python 报告同样使用 `-inf` 表示空路径的零工作量，包括 `log10_flops`、`log2_total_size`、`log2_read_write` 和目标值；请求切片时，`sliced_log10_flops_total` 也为 `-inf`。这是零工作量取对数后的表示，不表示搜索或执行失败。
