TensorFlow 版本兼容性

本文档旨在为需要在不同 TensorFlow 版本之间保持向后兼容性(无论是代码还是数据)的用户,以及希望在保持兼容性的前提下修改 TensorFlow 的开发者提供指导。

语义化版本控制 2.0

TensorFlow 的公共 API 主要遵循语义化版本控制 2.0 (semver)。每个 TensorFlow 发布版本都采用 MAJOR.MINOR.PATCH 的形式。例如,TensorFlow 1.2.3 版本中,MAJOR(主版本号)为 1,MINOR(次版本号)为 2,PATCH(修订号)为 3。各数字的变更含义如下:

  • MAJOR(主版本号):可能包含不兼容的向后变更。适用于先前主版本的代码和数据不一定适用于新版本。然而,在某些情况下,现有的 TensorFlow 图和检查点(Checkpoint)可以迁移到较新的版本;有关数据兼容性的详细信息,请参阅图和检查点的兼容性

  • MINOR(次版本号):向后兼容的功能、速度改进等。适用于先前次版本且仅依赖非实验性公共 API 的代码和数据,在新版本中将继续保持不变。关于什么是公共 API 以及什么不是,请参阅涵盖范围。请注意,TensorFlow 有时会在新的次版本中进行破坏性变更,但预期影响较小。有关此类变更的示例,请参阅 https://github.com/tensorflow/tensorflow/releases 中过往次版本的“破坏性变更(Breaking Changes)”章节。

  • PATCH(修订号):向后兼容的错误修复。

例如,1.0.0 版本相对于 0.12.1 版本引入了向后不兼容的变更。然而,1.1.1 版本与 1.0.0 版本是向后兼容的。

涵盖范围

只有 TensorFlow 的公共 API 在次版本和修订版本之间保持向后兼容。公共 API 包含:

  • 所有在 tensorflow 模块及其子模块中记录的 Python 函数和类,以下情况除外:

    • 私有符号:任何名称以 _ 开头的函数、类等。
    • 实验性符号和 tf.contrib 符号,详细信息请参阅下方内容

    请注意,examples/tools/ 目录中的代码无法通过 tensorflow Python 模块访问,因此不属于兼容性保证范围。

    如果某个符号可以通过 tensorflow Python 模块或其子模块访问,但未被记录,则它被视为公共 API 的一部分。

  • 兼容性 API(在 Python 中为 tf.compat 模块)。在主版本发布时,我们可能会发布实用工具和额外的端点来帮助用户过渡到新的主版本。这些 API 符号已被弃用且不再受支持(即我们不会添加任何功能,除修复漏洞外也不会修复错误),但它们确实属于我们的兼容性保证范围。

  • TensorFlow C API

  • 以下 Protocol Buffer 文件

TensorFlow Lite 的独立版本号

目前,TensorFlow Lite 作为 TensorFlow 的一部分分发。但是,我们保留在未来以不同于其他 TensorFlow API 的时间表发布 TensorFlow Lite API 变更的权利,甚至可能将 TensorFlow Lite 移至独立的源代码分发和/或不同于 TensorFlow 的源代码仓库。

因此,我们为 TensorFlow Lite 使用与 TensorFlow 不同的版本号(tensorflow/lite/version.h 中的 TFLITE_VERSION_STRINGtensorflow/lite/c/c_api.h 中的 TfLiteVersion(),而 TensorFlow 对应的是 tensorflow/core/public/release_version.h 中的 TF_VERSION_STRINGtensorflow/c/c_api.h 中的 TF_Version())。目前,这两个版本号恰好相同,但未来可能会出现偏差;例如,我们可能会在不增加 TensorFlow 主版本号的情况下增加 TensorFlow Lite 的主版本号,反之亦然。

TensorFlow Lite 版本号涵盖的 API 范围由以下公共 API 组成:

实验性符号不在涵盖范围内;详情请参阅下方内容

TensorFlow Lite 扩展 API 的独立版本号

TensorFlow Lite 提供了 C API,用于通过“自定义算子(custom ops)”(即在图中提供用户定义的算子)或“委托(delegates)”(即将图或图的一部分计算委托给自定义后端)来扩展 TensorFlow Lite 解释器。我们将这些 API 统称为“TensorFlow Lite 扩展 API”,它们对 TensorFlow Lite 实现的部分细节有更深层次的依赖。

我们保留在未来对这些 API 进行更改(可能包括不向后兼容的更改)的权利,其时间表独立于其他 TensorFlow Lite API。因此,我们为 TensorFlow Lite 扩展 API 使用与 TensorFlow Lite 或 TensorFlow 不同的版本号(如前文所述)。我们在 TensorFlow Lite 2.15 版本中引入了一些新的 API 来获取 TensorFlow Lite 扩展 API 的版本(tensorflow/lite/version.h 中的 TFLITE_EXTENSION_APIS_VERSION_STRINGtensorflow/lite/c/c_api.h 中的 TfLiteExtensionApisVersion())。目前,TensorFlow Lite 扩展 API 的版本号与 TensorFlow 和 TensorFlow Lite 的版本号相同,但未来可能会出现偏差;例如,我们可能会在不增加 TensorFlow Lite 主版本号的情况下增加 TensorFlow Lite 扩展 API 的主版本号,反之亦然。

TensorFlow Lite 扩展 API 版本号涵盖的 API 范围由以下公共 API 组成:

同样,实验性符号不在涵盖范围内;详情请参阅下方内容

涵盖的内容

TensorFlow 的某些部分可能会在任何时候发生不向后兼容的更改。这些包括:

  • 实验性 API:为了促进开发,我们免除了某些明确标记为“实验性(experimental)”的 API 符号的兼容性保证。具体来说,以下内容不享受任何兼容性保证:

    • tf.contrib 模块或其子模块中的任何符号;
    • 名称包含 experimentalExperimental 的任何符号(模块、函数、参数、属性、类、常量、类型、包等);或
    • 完全限定名称包含本身即为实验性的模块、类或包的任何符号。这包括任何名为 experimental 的 Protocol Buffer 的字段和子消息。
  • 其他语言:非 Python 和 C 语言的 TensorFlow API,例如:

    以及非 Java/Kotlin、C、Objective-C 和 Swift 语言的 TensorFlow Lite API,特别是:

  • 组合算子的细节:Python 中的许多公共函数会展开为图中的多个原始算子,这些细节将作为 GraphDef 的一部分保存到磁盘上。这些细节在次版本更新中可能会发生变化。特别是,检查图之间完全匹配的回归测试很可能会在次版本更新时失效,尽管图的行为本身应该保持不变,现有的检查点仍能正常工作。

  • 浮点数值细节:算子计算出的具体浮点值可能会随时发生变化。用户应仅依赖近似精度和数值稳定性,而不是计算出的具体位。次版本和修订版本中数值公式的更改应带来相当或更好的精度,但需注意,在机器学习中,特定公式的精度提高可能会导致整个系统的精度下降。

  • 随机数:计算出的具体随机数可能会随时发生变化。用户应仅依赖近似正确的分布和统计强度,而不是计算出的具体位。详情请参阅随机数生成指南。

  • 分布式 TensorFlow 中的版本偏差:不支持在单个集群中运行两个不同版本的 TensorFlow。不保证线路协议的向后兼容性。

  • 错误:如果当前的实现明显有误(即与文档矛盾,或者由于错误导致定义明确的预期行为未被正确实现),我们保留进行向后不兼容的行为(而非 API)更改的权利。例如,如果某个优化器声称实现了某种众所周知的优化算法,但由于错误而不符合该算法,我们将修复该优化器。我们的修复可能会破坏依赖于错误行为进行收敛的代码。我们将在发布说明中注明此类更改。

  • 未使用的 API:我们保留对我们发现没有记录用法的 API 进行向后不兼容更改的权利(通过 GitHub 搜索对 TensorFlow 的使用情况进行审计)。在进行此类更改之前,我们将通过 announce@ 邮件列表宣布我们的意图,并提供解决破坏(如果适用)的说明,然后等待两周,让社区有机会分享他们的反馈。

  • 错误行为:我们可能会用非错误行为替换错误。例如,我们可能会更改一个函数以计算结果而不是引发错误,即使该错误已被记录。我们还保留更改错误消息文本的权利。此外,除非文档中为特定错误条件指定了异常类型,否则错误的类型可能会发生变化。

SavedModels、图和检查点的兼容性

SavedModel 是 TensorFlow 程序中推荐使用的序列化格式。SavedModel 包含两个部分:一个或多个编码为 GraphDefs 的图,以及一个检查点。图描述了算子的数据流,检查点则包含图中变量保存的张量值。

许多 TensorFlow 用户创建 SavedModel,并在更高版本的 TensorFlow 中加载和执行它们。根据 semver,使用一个 TensorFlow 版本编写的 SavedModel 可以在相同主版本号的更高版本 TensorFlow 中加载和评估。

我们对受支持的 SavedModel 提供额外保证。如果一个 SavedModel 是在 TensorFlow 主版本 N仅使用非弃用、非实验性、非兼容性 API 创建的,我们称之为在版本 N 中受支持的 SavedModel。任何在 TensorFlow 主版本 N 中受支持的 SavedModel 均可在 TensorFlow 主版本 N+1 中加载和执行。但是,构建或修改此类模型所需的功能可能已不再可用,因此该保证仅适用于未经修改的 SavedModel。

我们将努力尽可能长时间地保持向后兼容性,以便序列化文件能够长期使用。

GraphDef 兼容性

图通过 GraphDef Protocol Buffer 进行序列化。为了促进图的向后不兼容更改,每个 GraphDef 都有一个独立于 TensorFlow 版本的版本号。例如,GraphDef 版本 17 弃用了 inv 算子,改为使用 reciprocal。其语义如下:

  • 每个 TensorFlow 版本都支持一个 GraphDef 版本区间。此区间在修订版本中保持不变,仅在次版本发布时扩大。仅在 TensorFlow 的主版本发布时(且仅与为 SavedModel 保证的版本支持对齐),才会放弃对某个 GraphDef 版本的支持。

  • 新创建的图将被分配最新的 GraphDef 版本号。

  • 如果某个 TensorFlow 版本支持图的 GraphDef 版本,它将以与生成它的 TensorFlow 版本相同的行为加载和评估(除上述浮点数值细节和随机数外),无论 TensorFlow 的主版本号如何。特别是,只要 GraphDef 得到支持,与一个 TensorFlow 版本中的检查点文件兼容的 GraphDef(如 SavedModel 中的情况)将在后续版本中保持与该检查点的兼容性。

    注意,这仅适用于 GraphDef(和 SavedModel)中序列化的图:读取检查点的代码可能无法读取由运行不同版本 TensorFlow 的相同代码生成的检查点。

  • 如果 GraphDef上限在某个(次)版本中提高到 X,则至少需要六个月时间下限才会提高到 X。例如(此处使用假设的版本号):

    • TensorFlow 1.2 可能支持 GraphDef 版本 4 到 7。
    • TensorFlow 1.3 可能会添加 GraphDef 版本 8,并支持版本 4 到 8。
    • 至少六个月后,TensorFlow 2.0.0 可能会放弃对版本 4 到 7 的支持,仅保留版本 8。

    请注意,由于 TensorFlow 的主版本发布间隔通常超过 6 个月,因此上述针对受支持 SavedModel 的保证远强于 GraphDef 的 6 个月保证。

最后,当放弃对某个 GraphDef 版本的支持时,我们将尝试提供工具,将图自动转换为更新且受支持的 GraphDef 版本。

扩展 TensorFlow 时的图和检查点兼容性

本节仅在对 GraphDef 格式进行不兼容更改(例如添加算子、删除算子或更改现有算子的功能)时才相关。前述章节对大多数用户已足够。

向后兼容性和部分向前兼容性

我们的版本控制方案有三个要求:

  • 向后兼容性:支持加载使用旧版本 TensorFlow 创建的图和检查点。
  • 向前兼容性:支持在图或检查点的生产者在消费者之前升级到更新版本的 TensorFlow 的场景。
  • 支持以不兼容的方式演进 TensorFlow。例如,删除算子、添加属性以及删除属性。

注意,虽然 GraphDef 版本机制与 TensorFlow 版本是分开的,但对 GraphDef 格式的向后不兼容更改仍受语义化版本控制的限制。这意味着功能只能在 TensorFlow 的 MAJOR 版本之间(例如 1.72.0)删除或更改。此外,向前兼容性在修订版本(例如 1.x.11.x.2)内强制执行。

为了实现向后和向前兼容性,并了解何时强制执行格式更改,图和检查点包含描述其生成时间的元数据。以下章节详细说明了 TensorFlow 的实现以及演进 GraphDef 版本的指导原则。

独立的数据版本方案

图和检查点有不同的数据版本。这两种数据格式的演进速度彼此不同,与 TensorFlow 的演进速度也不同。这两个版本控制系统都在 core/public/version.hcore/public/release_version.h 中定义。每当添加新版本时,头文件中都会添加一条说明,详细说明变更内容和日期。

数据、生产者和消费者

我们区分以下几种数据版本信息:

  • 生产者(producers):生成数据的二进制文件。生产者有一个版本(producer)和一个它们兼容的最低消费者版本(min_consumer)。
  • 消费者(consumers):消费数据的二进制文件。消费者有一个版本(consumer)和一个它们兼容的最低生产者版本(min_producer)。

每条版本化数据都有一个 VersionDef versions 字段,记录了生成数据的 producer、与之兼容的 min_consumer 以及不允许使用的 bad_consumers 版本列表。

默认情况下,当生产者生成数据时,数据会继承生产者的 producermin_consumer 版本。如果已知特定消费者版本包含错误且必须避免,可以设置 bad_consumers。如果满足以下所有条件,消费者即可接受数据:

  • consumer >= 数据的 min_consumer
  • 数据的 producer >= 消费者的 min_producer
  • consumer 不在数据的 bad_consumers 列表中

由于生产者和消费者来自相同的 TensorFlow 代码库,core/public/version.h 包含一个主数据版本,该版本根据上下文被视为 producerconsumer,以及 min_consumermin_producer(分别由生产者和消费者需要)。具体来说:

  • 对于 GraphDef 版本,我们有 TF_GRAPH_DEF_VERSIONTF_GRAPH_DEF_VERSION_MIN_CONSUMERTF_GRAPH_DEF_VERSION_MIN_PRODUCER
  • 对于检查点版本,我们有 TF_CHECKPOINT_VERSIONTF_CHECKPOINT_VERSION_MIN_CONSUMERTF_CHECKPOINT_VERSION_MIN_PRODUCER

向现有算子添加带有默认值的新属性

遵循以下指南仅在算子集未更改的情况下才能实现向前兼容性。

  1. 如果需要向前兼容性,在使用 SavedModelBuilder 类的 tf.saved_model.SavedModelBuilder.add_meta_graph_and_variablestf.saved_model.SavedModelBuilder.add_meta_graph 方法,或者 tf.estimator.Estimator.export_saved_model 导出模型时,请将 strip_default_attrs 设置为 True
  2. 这会在生成/导出模型时剥离具有默认值的属性。这确保了当使用默认值时,导出的 tf.MetaGraphDef 不包含新的算子属性。
  3. 拥有此控制权可以让过时的消费者(例如,滞后于训练二进制文件的服务二进制文件)继续加载模型并防止模型服务中断。

演进 GraphDef 版本

本节解释如何使用此版本控制机制对 GraphDef 格式进行不同类型的更改。

添加算子

同时将新算子添加到消费者和生产者中,且不更改任何 GraphDef 版本。这种更改自动向后兼容,且不会影响向前兼容性计划,因为现有的生产者脚本不会突然使用新功能。

添加算子并切换现有的 Python 包装器以使用它

  1. 实现新的消费者功能并增加 GraphDef 版本。
  2. 如果可以让包装器仅在以前无法工作的情况下使用新功能,则现在可以更新包装器。
  3. 更改 Python 包装器以使用新功能。不要增加 min_consumer,因为不使用此算子的模型不应中断。

删除或限制算子的功能

  1. 修复所有生产者脚本(而非 TensorFlow 本身),使其不再使用被禁用的算子或功能。
  2. 增加 GraphDef 版本,并实现新的消费者功能,禁止新版本及以上版本的 GraphDef 使用已删除的算子或功能。如果可能,让 TensorFlow 停止生成具有被禁用功能的 GraphDefs。为此,添加 REGISTER_OP(...).Deprecated(deprecated_at_version, message)
  3. 为了向后兼容,等待一个主版本发布。
  4. min_producer 增加到 (2) 中的 GraphDef 版本,并完全删除该功能。

更改算子的功能

  1. 添加一个名为 SomethingV2 或类似的新相似算子,并经历添加它以及切换现有 Python 包装器以使用它的过程。为确保向前兼容性,在更改 Python 包装器时使用 compat.py 中建议的检查。
  2. 删除旧算子(由于向后兼容性,只能在主版本更改时进行)。
  3. min_consumer 增加以排除使用旧算子的消费者,将旧算子作为 SomethingV2 的别名添加回去,并经历切换现有 Python 包装器以使用它的过程。
  4. 经历删除 SomethingV2 的过程。

禁止单个不安全的消费者版本

  1. 提高 GraphDef 版本,并将错误版本添加到所有新 GraphDefsbad_consumers 中。如果可能,仅针对包含特定算子或类似内容的 GraphDefs 添加到 bad_consumers
  2. 如果现有的消费者具有该错误版本,请尽快将其淘汰。