---
title: "使用 Heavy 优化收缩序"
description: "调用 Heavy，读取收缩路径、目标值与耗时，并保存收缩序。"
eyebrow: "应用教程"
---

请先完成[快速开始中的张量网络输入](/docs/quick-start#network)，准备示例使用的网络结构、维度和数值数组。

## 运行 Heavy 并保留报告 {#run}

安装包含 Light / Heavy 的完整 wheel 后，沿用[快速开始](/docs/quick-start#network)中的网络，或提供自己的 `inputs`、`output` 和 `size_dict`。

将网络定义和下面的调用保存为 `heavy_example.py`，启动时设置线程数：

```bash
RAYON_NUM_THREADS=24 python heavy_example.py
```

### 取得返回结果 {#levels}

`arctn_schedule` 优化收缩序，不执行数值数组。返回的 `report` 是 Python 字典，包含路径、指标和可选切片信息。

```python
from arctn import arctn_schedule

report = arctn_schedule(
    inputs, output, size_dict,
    preset="heavy", seed=17,
    use_ssa=True,
)

path = report["path"]
print(report["log10_flops"])
print(report["log2_read_write"])
print(report["planner_objective_score_log2"])
```

Heavy 使用较大的搜索预算来寻找更好的路径。它仍是启发式搜索：返回的是本次运行已完成候选中的最佳结果，不保证局部最优、全局最优，也不保证每次都优于 Light。

## 读取返回结果 {#read}

默认目标为 $F+64R$。比较相同网络、相同权重下的搜索结果时，读取 `planner_objective_score_log2`；只看 FLOPs 不能反映这一加权目标。

### 路径与目标字段 {#path-fields}

| 字段 | 含义 |
| --- | --- |
| `path`、`path_format` | 最终路径与编码；默认 `linear-v1`，上例为 `ssa-v1` |
| `planner_objective` | 优化目标名称 |
| `flops_weight`、`read_write_weight` | 本次调用的目标权重 |
| `planner_objective_score_log2` | 最终目标值以 2 为底的对数；有切片时计入全部切片 |
| `log10_flops`、`log2_read_write` | 最终路径在**未切片网络**上的计算量与读写量，分别以 10 和 2 为底取对数 |
| `log2_max_size` | 未切片路径的最大二元收缩结果元素数，以 2 为底的对数 |
| `log2_max_contraction_size` | 未切片路径中，单步逻辑输入与结果元素数之和的最大值，以 2 为底的对数 |
| `log2_total_size`、`log2_peak_size` | 未切片路径的总写出量与活跃元素峰值，以 2 为底的对数 |
| `wall_s` | Light / Heavy 编译库记录的搜索耗时，单位为秒；不包含随后执行数组的时间 |

`path` 中的整数表示张量编号，按 `path_format` 指定的格式读取；`sliced_legs` 中的整数则是 index 的内部编号。两种路径格式见[收缩树与路径](/docs/tensor-network#what-a-path-decides)。

路径指标由网络结构和维度计算，不是实测的执行时间或进程内存。计算公式见[路径指标与优化目标](/docs/path-metrics)。

### 切片字段 {#slicing-fields}

上例未指定 `target_size`。需要限制单片中间张量大小时，按[控制中间张量](/docs/tutorial-slicing#fixed)设置切片参数，再读取下列字段：

| 字段 | 含义 |
| --- | --- |
| `sliced_legs` | 切片 index 的内部整数编号 |
| `log2_n_slices` | 切片数量以 2 为底的对数 |
| `sliced_log10_flops_total` | 全部切片的总 FLOPs，以 10 为底的对数 |
| `sliced_log2_max_size` | 单片最大二元收缩结果元素数，以 2 为底的对数 |
| `planner_log2_read_write` | 全部切片的总读写量，以 2 为底的对数 |
| `target_size` | 请求的每片结果元素数上限 |
| `memory_constraint_metric` | 大小约束的定义 |
| `max_intermediate_log2_elements_per_slice` | 最终单片最大结果元素数，以 2 为底的对数 |

`sliced_legs` 列出切哪些 index，其长度不是切片数量。例如，一个维度为 4 的 index 对应 4 个 slice，此时 `log2_n_slices` 为 2。

未提供 `target_size` 时，`sliced_legs` 为空，`log2_n_slices` 为 0，部分 `sliced_*` 字段不返回，`max_intermediate_log2_elements_per_slice` 为 `None`。指定 `target_size` 也不一定需要切片；`bool(report["sliced_legs"])` 表示是否实际选择了切片 index。

检查大小约束时使用**单片**大小，而不是未切片的 `log2_max_size`。比较总工作量时使用 `sliced_log10_flops_total` 和 `planner_log2_read_write`；`planner_objective_score_log2` 已按本次权重计算全部切片的目标值。

## 设置并行线程与读取耗时 {#repeats}

上例只调用一次 Heavy。`RAYON_NUM_THREADS=24` 对应 3 个独立搜索起点，共享 24 个工作线程，不需要再写外层种子循环。`seed=17` 指定基础种子，其余种子由库派生。线程设置不表示预留了 24 个物理核；8、16、32 线程的对应关系见[推荐线程配置](/docs/parallelism#recommended-threads)。

读取搜索耗时：

```python
print("搜索耗时（秒）:", report["wall_s"])
```

内部候选列表、各阶段记录和独立起点报告不属于公开返回字段。若需要测量整次 Python 调用的耗时，可在调用前后使用 `time.perf_counter()`；它与编译库记录的搜索耗时范围不同。

## 保存收缩序 {#artifacts}

已有 `report` 时，通过 `ArcTNExecutionPlan.from_schedule` 保存网络、SSA 收缩序与切片信息，不再次搜索：

```python
from arctn import ArcTNExecutionPlan

plan = ArcTNExecutionPlan.from_schedule(
    report, inputs=inputs, output=output, size_dict=size_dict,
)
plan.save("plan.json")
```

### 后续执行与复用 {#paths}

加载文件、执行收缩及更新数组的例子见[保存与复用收缩序](/docs/tutorial-reuse#save-load)。若要将路径交给其他工具，见[路径与保存形式](/docs/execution-plan#representations)。
