首页
/ Prototiller:Protobuf Editions 迁移的批量重构工具——设计文档与需求解析

Prototiller:Protobuf Editions 迁移的批量重构工具——设计文档与需求解析

2026-09-04 15:57:31作者:姚月梅Lane

本文基于 Prototiller 设计文档目录 及其收录的三份历史设计文档,梳理 Prototiller 的定位、Editions 迁移工具链的总体设计(janitor / adopter / updater)、作为输入协议的 ChangeSpec 推荐 schema,以及 Edition Zero 从 proto2/proto3 迁移时的各类特征(feature)转换规则。读完后,你将理解 Protobuf 团队如何用一套"机械、可增量、语义无副作用"的批量重构工具,把任意 legacy syntax 文件安全地升级到 Editions 模型。

Prototiller 是什么:定位与文档性质

目录说明 对这批文档的定性是:历史设计文档(historical design documents),描述的是 Prototiller 的实现计划。Prototiller 被定位为 Protobuf 的"批量重构瑞士军刀"(mass refactoring Swiss army knife),类比构建系统界的 Buildozer,其用途包括:

  • 在 Google 内部(google3)支撑 LSC(Large-Scale Change,大规模变更);
  • 允许内部和外部用户安全地批量修改 .proto 文件,尤其是把 .proto 文件更新到符合 Protobuf Editions 的形态。

目录说明同时给出三条阅读须知,读本文时应保持同样的心态:

  1. 这些文件代表文档首次发布时的状态,后续可能有更新,阅读时应假设其可能已过时;
  2. 它们纯粹具有历史价值,不应被视为当前实现状态的文档
  3. 各文档中署名的作者是原始版本的作者,文档在其最后一次经手之后可能已有变动。

该目录收录三份文档:

Editions 工具链总体设计:janitor、adopter 与 updater

Editions Tooling 是三者中最早的一份,回答了"为什么需要工具链"以及"工具链长什么样"。

动机:机械化、增量化的升级

Protobuf Editions 要引入新的语义,但极度强调机械化、增量化的可升级性,以避免 proto2 与 proto3 的"双系统问题"。第一个 edition(很可能是 "2023")会引入收敛语义(converged semantics),允许 proto2 和 proto3 所允许的一切:任何非 editions 文件都可以以最少的人工干预变成 editions 文件

工具链的设计目标有两条:

  • 让 editions 空间内**无法自动化的大规模变更工作,被限制在"修 generated code 的使用处"和"翻转特定字段上的 feature"**这两件事上;
  • 给外部用户最无痛的迁移体验,即"运行这个工具,提交结果"。

文档设定了一个前提:内部设计文档 Protochangifier Backend Design Doc(未对外公开)已作为后端集成进 protoc,因此工具可以随 protoc 一起发布。由于这些工具必须知道一个 edition 的完整定义才能工作,文档认为这"近乎硬性地把工具与 protoc 绑定在了一起"。

三个工具

  1. features janitor(特征清道夫)protoc 的一个模式,输入一个 .proto 文件,产出一个 ProtoChangeSpec,描述如何增删 feature,使清理后的文件显式 feature 更少,但语义不变
  2. editions adopter(采纳器):另一个 protoc 模式,产出把 proto2/proto3 文件带入 editions 模式(从某个特定 edition 起步)的 ProtoChangeSpec
  3. editions upgrader(升级器):adopter 的泛化,接收一个 editions 文件,产出将其带入更新 edition 的 ProtoChangeSpec

这三个工具本质上都说 ProtoChangeSpec,但文档同时建议提供就地(in-place)版本,因为对外部 OSS 用户来说"对整棵项目原子地跑一次工具"更有用。

Janitor 的启发式最小化算法

Janitor 的典型作用,是把一个到处散落重复 feature 的文件:

edition = "2023";
message Foo {
  optional string a = 1 [features.(pb.cpp).string_type = VIEW];
  optional string b = 2 [features.(pb.cpp).string_type = VIEW];
  optional string c = 3 [features.(pb.cpp).string_type = VIEW];
  optional string d = 4 [features.(pb.cpp).string_type = VIEW];
  optional string e = 5 [features.(pb.cpp).string_type = VIEW];
}
message Bar {
  optional string a = 1 [features.(pb.cpp).string_type = VIEW];
  optional string b = 2;
  optional string c = 3;
  optional string d = 4;
  optional string e = 5;
}

收敛为把公共 feature 上提到最近可承载的作用域:

edition = "2023";
message Foo {
  option features.(pb.cpp).string_type = VIEW;
  optional string a = 1;
  optional string b = 2;
  optional string c = 3;
  optional string d = 4;
  optional string e = 5;
}
message Bar {
  optional string a = 1 [features.(pb.cpp).string_type = VIEW];
  optional string b = 2;
  optional string c = 3;
  optional string d = 4;
  optional string e = 5;
}

文档坦承"真正做最小化是非线性的",因此给出了一个启发式草图:

  1. 每个可以显式出现在 AST 节点上的 feature,对该节点要么是关键(critical)的,要么仅用于分组。例如 string_type 对字段是关键,但对 message 不是;
  2. 把 feature(包括 edition 默认值)显式传播到每一个节点;
  3. 对每个 feature f,对每个 f 非关键、但其(递归)内部含有 f 关键节点(按 DFS 序)的节点 n:把 fn 上设为其直接子节点中占多数(plurality)的值,并从那些子节点上删除显式 f;平局时若 edition 默认值在多数派之中则取默认值,否则随机选;
  4. 重复到根节点后,删除所有"从根出发、不跨越另一个非默认显式 feature 即可到达"的显式 feature——即被 edition 默认值所蕴含的那些。

文档明确指出:构造出该算法不最优的例子很容易,但那不重要;它存在的目的只是在保持等价的前提下让文件更漂亮,而且由构造可证它满足"语义 no-op"要求。

Adopter 与 Updater:三步 no-op 变换

adopter 只是 updater 的特例——把 proto2/proto3 也看作"edition"(edition 本质是一组默认值)。把旧 edition("old")更新到新 edition("new",不一定更新)的完整流程:

  1. 补齐显式 feature:凡是顶层尚未显式设置的 feature,都设为 "old" 给出的默认值;只设置在最外层未显式指定该 feature 的作用域上(例如文件级 feature 就全部在文件级显式化;非文件级的 message 级 feature 则放在每个顶层 message 上)。由于 edition = "old"; 本就蕴含这些值,这一步是 no-op;
  2. 改 edition 声明:把文件的 edition 从 "old" 改为 "new"。由于所有可能显式的 feature 都已是显式,这一步同样是 no-op;
  3. 运行 feature janitor:显式传播所有 feature,然后按 "new" edition 清理(janitor 偏好 edition 默认值)。由于 janitor 本身是 no-op,整条链路语义不变。

这三步的巧妙之处在于:"先固化旧默认 → 换 edition 标签 → 再按新默认去冗余",每一步单独看都保持语义等价,从而整体升级是安全的。

与 protoc 绑定的 UX 设计

工具随 protoc 分发的用户体验约定(适用于所有打包进 protoc 的 Protochangifier 工具):

  • --change_spec=changespec.pb:让 protoc 对传入的 .proto 文件应用一个 changespec,例如 protoc --change_spec=spec.pb --change_out=foo-changed.proto foo.proto,改动写入 foo-changed.proto--change_out 可以与输入同文件(就地更新),也可以省略(改动打印到 stdout)。这是 Protochangifier 的核心入口;
  • 每个分析项有一个形如 --my_analysis(如 --janitor)的 flag,可选参数:若给出路径(如 --janitor=spec.pb)则把 changespec 输出到该路径;若不给,则直接就地应用改动,无需再走 --change_spec

文档还讨论了"为什么不做成独立二进制":从分发和用户教育角度,"这就是编译器的一部分"更容易被找到;团队预期会持续基于 Protochangifier 产出新的迁移工具,让用户学会"所有分析长一个样"很重要。文中以 Rust 的 rustfix 为例:它虽是独立二进制,但通过 cargo fix 暴露——cargo 才是 Rust 的用户界面;把工具放进"瑞士军刀"里才能放在用户面前。

ChangeSpec 输入协议:面向 Editions 的推荐 schema

Prototiller Requirements for Editions 从原始设计 Protochangifier Semantic Actions(未对外公开)出发——该设计让 Prototiller 消费一个描述"对输入 .proto 文件要做什么改动"的 Protobuf 消息——规定了一个只满足 Editions 需要、但保留未来扩展性的接口变体。

总需求有两条:

  • 动作必须覆盖以下 Editions 升级工作流:
    • 把文件升级到某个特定 edition,无论它当前是 syntax 模式还是 editions 模式,且更新 feature 的方式保证 no-op——这是最高优先级工作流
    • "清理"某个文件的 feature:即运行简单算法,求出每一层级必须存在的最小 feature 集合;
    • 修改某个语法元素上的 feature;
  • 动作既要能精确到特定语法元素(供 Schema Consumers 把变更规格与 .proto 文件一起 check in 的场景),也要通用(一份或一组变更规格即可驱动大规模变更)。

文档随后给出一份推荐的 Protobuf schema(注意:这只是建议,Prototiller 项目所有者可以按实现需要修改;约束性的只有需求,schema 只是需求的图示):

syntax = "proto2";

package prototiller;

// This is the proto that Prototiller accepts as input.
message ChangeSpec {
  // Actions to execute on the file.
  repeated Action actions = 1;

  // Some changes may result in a wireformat break; changing field type is
  // usually unsafe. By default, Prototiller does not allow such changes,
  // users can set allow_unsafe_wire_format_changes to true to force the change.
  optional bool allow_unsafe_wire_format_changes = 2 [default = false];
  optional bool allow_unsafe_text_format_changes = 3 [default = false];
  optional bool allow_unsafe_json_format_changes = 4 [default = false];
}

// A single action. See messages below for description of their
// semantics.
message Action {
  oneof kind {
    UpgradeEdition upgrade_edition = 20;
    CleanUpFeatures clean_up_features = 21;
    ModifyFeature modify_feature = 22;
  }
}

// Upgrades the edition of a file to a specified edition.
// Treats syntax mode as being a weird, special edition that cannot be
// upgraded to.
//
// This action is always safe.
message UpgradeEdition {
  // The edition to upgrade to.
  optional string edition = 1;
}

// Cleans up features in a file, such that there are as few explicitly set
// features as necessary.
//
// This action is always safe.
message CleanUpFeatures {}

// Modifies a specific feature on all syntax elements that match and which can
// host that particular feature.
//
// Prototiller must be aware of which changes affect wire format, so that it
// can flag them as unsafe.
message ModifyFeature {
  // The name of the feature to modify.
  repeated proto2.UninterpretedOption.NamePart feature = 1;

  // A pattern for matching paths to syntax elements to modify.
  //
  // Elements of this field can either be identifiers, or the string "*", which
  // matches all identifiers. Thus, ["foo", "Bar"] matches the message foo.Bar,
  // ["foo", "Bar", "*"] matches all fields and nested types of foo.Bar
  // (recursively), and ["*"] matches all elements of a file.
  repeated string path_pattern = 2;

  // The value to set the feature to. If not set, this means that the
  // feature should be deleted.
  oneof value {
    int64 int_value = 20;
    double double_value = 21;
    // ... and so on.
  }
}

几个关键设计点:

  • ChangeSpec 顶部的三个 allow_unsafe_* 开关默认都是 false:改变字段类型之类可能破坏 wireformat 的操作,Prototiller 默认拒绝,只有用户显式打开对应开关(wire format / text format / json format)才强制执行。这把"安全性"做成了协议的显式一等公民;
  • Action 用 oneof 承载三种动作,且 UpgradeEditionCleanUpFeatures 在注释中被明确标记为"always safe"——正好对应前文最高优先级工作流和 janitor 能力;
  • UpgradeEdition 把 syntax 模式视为一种"无法被升级到的古怪特殊 edition",即 legacy syntax 文件是升级的起点而非终点;
  • ModifyFeature.path_pattern 的匹配语义:数组元素可以是标识符或通配符 "*"["foo", "Bar"] 匹配 message foo.Bar["foo", "Bar", "*"] 递归匹配 foo.Bar 的全部字段与嵌套类型;["*"] 匹配文件内所有元素。这同时满足了"精确到某个元素"与"一份规格驱动全库"两种需求;
  • ModifyFeature.value 未设置即表示删除该 feature;而 feature 字段复用 proto2.UninterpretedOption.NamePart,使得扩展 feature(如 features.(pb.cpp).string_type)也能按路径寻址。

文档的 Alternatives Considered 还解释了两处取舍:

  • 为什么不把 feature 清理作为其他动作的隐式副作用:因为期望能在各处激进地单独运行它,甚至嵌入 Cider 等 IDE 的"保存时格式化";
  • 为什么不让 ModifyFeature 一次作用于整个文件的所有元素:它的设计目标是让 SchemaConsumers 对自己 import 的 .proto 文件做细粒度控制——有时想把整个文件所有字段上的某个 feature 抹掉,有时只关心少数字段。简单的模式匹配两者都能支持。

Edition Zero 迁移:具体转换规则全解

Prototiller Requirements for Edition Zero 是最实操的一份:Edition Zero 用 feature 统一 proto2 与 proto3,要迁移内部仓库(并协助 OSS 迁移),Prototiller 负责把 legacy syntax 升级到新模型。文档的关键论断是:由于 edition zero 的 feature 就是这样推导出来的,从 proto2/proto3 到 edition zero 一定存在一个 no-op 变换——但细节相当复杂,取决于很多因素。

文档还提到一个基准:当时已有一个临时脚本作为占位实现,覆盖了相当多的规则,但无法处理 extension 或 oneof 里的 group;它连同它的 golden 测试可以作为 Prototiller 的有用基准(这一点在当前仓库中仍有呼应,见文末"仓库中的佐证")。

Feature 优化阶段

降低大规模变更摩擦的关键一环是 feature 优化:如果某个 feature 不是让升级成为 no-op 所必需的,就不该添加它,而应依赖 edition 默认值以利于后续变更;同时应通过把 feature 规格合并到更高层级(例如把字段 feature 折叠成文件级默认)来最小化变更总量。

前端特征转换(Frontend Feature Transformations)

Field Presence

field_presence 默认值为 EXPLICIT,对应 proto2/proto3 的 optional 行为;LEGACY_REQUIRED 对应 proto2 required 字段;IMPLICIT 对应非 optional 的 proto3 字段。为最小化变更,应利用文件级默认。

转换示例 1(proto2 起点):

// 迁移前
syntax = "proto2";
message Foo {
  optional string bar = 1;
  required string baz = 2;
}
// 迁移后
edition = "2023";

message Foo {
  string bar = 1;
  string baz = 2 [
    features.field_presence = LEGACY_REQUIRED];
}

转换示例 2(proto3 起点,mixed presence,用文件级默认 + 字段级例外):

// 迁移前
syntax = "proto3";

message Foo {
  optional string bar = 1;
  string baz = 2;
  string bam = 3;
}
// 迁移后
edition = "2023";
features.field_presence = IMPLICIT;

message Foo {
  string bar = 1 [features.field_presence = EXPLICIT];
  string baz = 2;
  string bam = 3;
}

转换示例 3(proto3 起点,全是 optional——此时默认 EXPLICIT 已覆盖,什么都不用写):

// 迁移前
syntax = "proto3";

message Foo {
  optional string bar = 1;
  optional string baz = 2;
}
// 迁移后
edition = "2023";

message Foo {
  string bar = 1;
  string baz = 2;
}

Enum Type

enum_type 默认 OPEN,对应 proto3 行为;CLOSED 对应典型 proto2 行为。同样应优先用文件级默认。

// 迁移前(proto2)
syntax = "proto2";

enum Foo {
  VALUE1 = 0;
  VALUE2 = 1;
}
// 迁移后
edition = "2023";
features.enum_type = CLOSED;

enum Foo {
  VALUE1 = 0;
  VALUE2 = 1;
}

而 proto3 起点则完全无需 feature:

// 迁移前(proto3)
syntax = "proto3";

enum Foo {
  VALUE1 = 0;
  VALUE2 = 1;
}
// 迁移后(与迁移前等价,只换 edition 声明)
edition = "2023";

enum Foo {
  VALUE1 = 0;
  VALUE2 = 1;
}

Repeated Field Encoding

repeated_field_encoding 默认 PACKED(proto3 行为);EXPANDED 对应 proto2 的默认。proto2 和 proto3 都可以用 packed 字段 option 覆盖默认——这些覆盖在迁移中都要被替换。最小化变更在这里更复杂,因为可能存在"多数 repeated 字段都被手动覆盖"的文件。

转换示例 1(proto2,默认 EXPANDED + 个别 PACKED 例外):

// 迁移前
syntax = "proto2";

message Foo {
  repeated int32 bar = 1;
  repeated int32 baz = 2 [packed = true];
  repeated int32 bam = 3;
}
// 迁移后
edition = "2023";
features.repeated_field_encoding = EXPANDED;

message Foo {
  repeated int32 bar = 1;
  repeated int32 baz = 2 [
    features.repeated_field_encoding = PACKED];
  repeated int32 bar = 3;
}

转换示例 2(proto3,packed = false 例外):

// 迁移前
syntax = "proto3";

message Foo {
  repeated int32 bar = 1;
  repeated int32 baz = 2 [packed = false];
}
// 迁移后
edition = "2023";

message Foo {
  repeated int32 bar = 2;
  repeated int32 baz = 2 [
    features.repeated_field_encoding = EXPANDED];
}

转换示例 3(proto2,全部是"默认"情形:packed = true 与默认 PACKED 一致,string 字段本就不 packed):

// 迁移前
syntax = "proto2";

message Foo {
  repeated int32 x = 1 [packed = true];
  // Strings are never packed.
  repeated string z = 1;
  repeated string w = 2;
}
// 迁移后:无任何显式 feature
edition = "2023";

message Foo {
  repeated int32 x = 1;
  repeated string z = 1;
  repeated string w = 2;
}

Message Encoding

message_encoding 用于取代 proto2 独有的 group 语法(对应值 DELIMITED),默认恒为 LENGTH_PREFIXED。这个转换在一般情况下比较别扭:group 定义允许出现在字段可出现的任何地方,而 message 定义不允许。基本变换是:在最外层作用域新建一个与字段同名的 message 类型,把原字段改名为小写并使用该类型。

转换示例 1(普通字段位置的 group):

// 迁移前
syntax = "proto2";

message Foo {
  optional group Bar = 1 {
    optional int32 x = 1;
  }
  optional Bar baz = 2;
}
// 迁移后
edition = "2023";

message Foo {
  message Bar {
    int32 x = 1;
  }
  Bar bar = 1 [features.message_encoding = DELIMITED];
  Bar baz = 2;
}

转换示例 2(oneof 内的 group——正是前文所说临时脚本覆盖不了的难点区域之一):

// 迁移前
syntax = "proto2";

message Foo {
  oneof foo {
    group Bar = 1 {
      optional int32 x = 1;
    }
  }
}
// 迁移后
edition = "2023";

message Foo {
  message Bar {
    int32 x = 1;
  }
  oneof foo {
    Bar bar = 1 [
      features.message_encoding = DELIMITED];
  }
}

JSON Format

json_format 是个例外项:至少在 edition zero 中它只影响 proto 文件的前端构建ALLOW(proto3 行为)启用对字段名的全部 JSON 映射冲突检查,除非设置了 deprecated_legacy_json_field_conflictsLEGACY_BEST_EFFORT(proto2 行为)关闭这些检查。理想的最小变换是:除"设置了 deprecated_legacy_json_field_conflicts"或"存在 JSON 映射冲突"的情况外一律切到 ALLOW,其余情形回退到 LEGACY_BEST_EFFORT。备选方案是:如果 Prototiller 处理困难,可以事后做一次大规模变更,删掉所有能通过构建的 LEGACY_BEST_EFFORT 实例。

转换示例 1(proto2,无冲突字段):

// 迁移前
syntax = "proto2";

message Foo {
  optional string bar = 1;
  optional string baz = 2;
}
// 迁移后
edition = "2023";

message Foo {
  string bar = 1;
  string baz = 2;
}

转换示例 2(proto3 起点):

// 迁移前
syntax = "proto3";

message Foo {
  string bar = 1;
  string baz = 2;
}
// 迁移后
edition = "2023";
features.field_presence = IMPLICIT;

message Foo {
  string bar = 1;
  string baz = 2;
}

转换示例 3(proto2,存在 JSON 名冲突,仅告警级):

// 迁移前
syntax = "proto2";

message Foo {
  // Warning only
  string bar = 1;
  string bar_ = 2;
}
// 迁移后:回退到 LEGACY_BEST_EFFORT
edition = "2023";
features.json_format = LEGACY_BEST_EFFORT;

message Foo {
  string bar = 1;
  string bar_ = 2;
}

转换示例 4(proto3 + deprecated_legacy_json_field_conflictsjson_name 可以随之删除):

// 迁移前
syntax = "proto3";

message Foo {
  option
  deprecated_legacy_json_field_conflicts = true;
  string bar = 1;
  string baz = 2 [json_name = "bar"];
}
// 迁移后
edition = "2023";
features.field_presence = IMPLICIT;
features.json_format = LEGACY_BEST_EFFORT;

message Foo {
  string bar = 1;
  string baz = 2;
}

后端特征转换(Backend Feature Transformations)

文档首先提出一个"控膨胀"原则:理想情况下应检查一个 proto 文件是否真的会被用来生成目标语言的代码;如果不是,就没有理由添加后端特定 feature

Legacy Closed Enum

Java 与 C++ 默认把"用在 proto2 message 中的 proto3 enum"当作 closed enum 处理。内部的 cc_open_enum 字段 option 可以覆盖这一行为,但使用面极窄,未必值得考虑。虽然 enum 类型行为仍应由 Enum Type 决定,但对于使用 proto3 message 的 proto2 文件(反向则被禁止),需要添加此 feature。

转换示例:

// 迁移前
syntax = "proto2";

import "some_proto3_file.proto"

enum Proto2Enum {
  BAR = 0;
}

message Foo {
  optional Proto3Enum bar = 1;
  optional Proto2Enum baz = 2;
}
// 迁移后
edition = "2023";
import "third_party/protobuf/cpp_features.proto"
import "third_party/protobuf/java_features.proto"
import "some_proto3_file.proto"

features.enum_type = CLOSED;

message Foo {
  Proto3Enum bar = 1 [
    features.(pb.cpp).legacy_closed_enum = true,
    features.(pb.java).legacy_closed_enum = true];
  Proto2Enum baz = 2;
}

注意扩展 feature 的写法 features.(pb.cpp).legacy_closed_enum——这正是 ModifyFeature.feature 复用 UninterpretedOption.NamePart 所要支持的多段命名。

UTF8 Validation

该 feature 在设计文档撰写时尚待审批(对应内部文档 Editions Zero Feature: utf8_validation,未对外公开)——这体现了这批文档"历史快照"的属性。

其他转换:Reserved 标识符语法

除 feature 外,edition zero 还有一类非 feature 变更。按内部 PEP Protobuf Change Proposal: Reserved Identifiers(未对外公开),reserved 字段从字符串改为标识符。文档认为这"应该是个琐碎变更",但若文件里含有非法标识符的字符串,存在歧义:它们今天会被忽略,但可能是我们不想盲目删除的笔误。因此变换策略是:合法标识符直接转换,非法的留下注释而非删除。

转换示例 1:

// 迁移前
syntax = "proto2";

message Foo {
  reserved "bar", "baz";
}
// 迁移后
edition = "2023";

message Foo {
  reserved bar, baz;
}

转换示例 2(非法标识符保留为注释):

// 迁移前
syntax = "proto2";

message Foo {
  reserved "bar", "1";
}
// 迁移后
edition = "2023";

message Foo {
  reserved bar;
  /*reserved "1";*/
}

仓库中的佐证与阅读边界

以下仓库证据可以帮助定位 Prototiller 在 protobuf 仓库中的实际足迹(均为只读查看路径):

  • upb_generator/minitable/BUILD 中依赖了 //third_party/prototiller/transformer(第 105 行附近),说明 prototiller 的 transformer 曾作为 Bazel 构建依赖参与 golden 文件变换的校验;
  • src/google/protobuf/compiler/java/doc_comment.cc 第 131 行附近存在一条以 "TODO: Remove once prototiller can avoid making..." 开头的注释——从源码结构看,代码生成器中留有等待 prototiller 能力提升后可移除的过渡性处理,说明该工具与 codegen 行为改进存在联动;
  • editions/golden/edition2023_transform/ 下收录了 proto2/proto3 输入经变换到 edition 2023 后的 golden 产物(如 proto2.protoproto3.prototest_messages_proto3.proto 等),配合 editions/input/edition2023_transform/ 的输入与 editions/generated_files_test.cc 的校验测试,正呼应了 Edition Zero 需求文档中"临时脚本及其 golden 测试可作为 Prototiller 有用基准"的说法——仓库用 input/golden 对加回归测试的方式,把变换正确性固化了下来;
  • editions/BUILD 中大量 compile_edition_defaults 目标以 protoc_minimal 编译各 edition 的默认值,佐证了 Editions 工具链"必须知道一个 edition 的完整定义"这一设计前提在构建层面确实存在。

最后重申阅读边界:本仓库中 Prototiller 相关代码并未开源(//third_party/prototiller/transformer 为外部依赖),ChangeSpec schema 也只是需求文档中的推荐图示而非最终接口;上述三份文档描述的是 2022–2023 年间的实现计划。若要在当前代码库上实施 editions 迁移,应以 editions/ 目录下的现行实现、golden 文件与测试为准,把这些设计文档作为理解"为什么这样设计"的历史参考。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
987
504
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384