首页
/ vendored golang-jwt v5 技术指南:在 moby 仓库中理解 JWT 的签发、解析与安全验证

vendored golang-jwt v5 技术指南:在 moby 仓库中理解 JWT 的签发、解析与安全验证

2026-09-07 17:31:30作者:伍霜盼Ellen

导读

本文以 moby 仓库中 vendored 的第三方 Go 库 golang-jwt/jwt v5 README 为主体,系统讲解 JSON Web Token(JWT)的完整生命周期:三段式结构、HMAC/RSA/ECDSA 等签名算法、令牌签发与解析验证 API、v5 独有的可插拔验证选项,以及针对 alg 混淆等经典攻击的安全防线。读完本文,你将能够结合仓库内的源码实现,在 moby 这样的大型容器系统中看懂并写出安全、可审计的 JWT 生成与校验代码。

说明:golang-jwt/jwt 是 dgrijalva/jwt-go 的社区维护继任者,moby 将其以 v5.3.1 版本作为间接依赖 vendored 于仓库中(见 go.mod)。除 JWT 本身外,本文也说明它如何在 moby 的依赖组件中实际被消费。

先理解 JWT:签名后的三段式 JSON 对象

按 README 中的定义,JWT 本质上是"一个做了签名的、携带实用信息的 JSON 对象",最常见的用途是认证,比如作为 OAuth 2.0 中的 Bearer token。一个完整的 JWT 由三个部分构成,彼此用 . 分隔:

  • Header(头部):一个 JSON 对象,先经 base64url 编码。它携带验证第三段(签名)所需的信息,例如签名所用的算法(alg)与所使用的密钥。
  • Claims(声明段):位于中间,是"真正有价值的部分",包含你关心的业务数据。保留字段(Reserved Claims)的定义与自行扩展字段的规范见 RFC 7519。
  • Signature(签名):对前两段拼接结果的签名,同样做 base64url 编码。

moby 中 vendored 的 token.go 把这三个概念直接建模为结构体字段:

type Token struct {
	Raw       string         // Raw contains the raw token.  Populated when you [Parse] a token
	Method    SigningMethod  // Method is the signing method used or to be used
	Header    map[string]any // Header is the first segment of the token in decoded form
	Claims    Claims         // Claims is the second segment of the token in decoded form
	Signature []byte         // Signature is the third segment of the token in decoded form
	Valid     bool           // Valid specifies if the token is valid
}

对 token 编码的关键操作在 token.go 的 EncodeSegment:使用 base64.RawURLEncoding——即 JWT 规范要求的"无填充 base64url 编码"。

库的能力边界(What's in the box)

README 明确给出该库的能力范围:支持 JWT 的解析与验证,也支持生成与签名。默认内置以下签名算法族(每种算法族都由独立文件实现):

算法族 alg 名称 仓库实现文件 密钥类型
HMAC-SHA HS256 / HS384 / HS512 hmac.go []byte 对称密钥
RSA RS256 / RS384 / RS512 rsa.go 公钥/私钥对
RSA-PSS PS256 / PS384 / PS512 rsa_pss.go 公钥/私钥对
ECDSA ES256 / ES384 / ES512 ecdsa.go 椭圆曲线密钥对
Ed25519 (见 ed25519.go 同左 Ed25519 密钥对
无签名 none none.go 需显式放行,见下文安全章节

同时,README 强调库"预留了钩子(hooks)以支持你注册自己的签名算法",这一扩展点在"扩展机制"章节详述。

在 moby 中定位这个依赖:版本与消费方式

尽管 README 面向所有 Go 项目,但它在当前仓库里扮演的具体角色可以通过两条证据确认:

  1. 版本锚点:仓库根 go.mod 声明 github.com/golang-jwt/jwt/v5 v5.3.1 // indirect——moby 并未直接编写 JWT 逻辑,而是作为传递依赖被引入。
  2. 真实消费场景:vendored 的 GitHub Actions 缓存客户端 cache.go 使用 new(jwt.Parser).ParseUnverified(token, jwt.MapClaims{}) 解析 GitHub 下发的 Actions Cache token,再通过类型断言 tk.Claims.(jwt.MapClaims) 读取 subaud 等声明字段以构建 HTTP 请求头。注意此处刻意使用 ParseUnverified——它只解析不做签名校验,因为上层请求已被 TLS 与 Actions 服务端保证,这正是 README 文档的典型用场景之一("you know the signature is valid (since it has already been or will be checked elsewhere in the stack)")。

这也为我们展示了真实工程取舍:验证签名很昂贵、且需要密钥分发;当安全边界已由其他层(TLS、私网)保证时,仅解析是合法的性能优化,但必须知道自己正在做什么。

安装与导入

对于普通 Go 工程,README 给出的安装步骤如下:

go get -u github.com/golang-jwt/jwt/v5

然后在代码中导入:

import "github.com/golang-jwt/jwt/v5"

需要注意:

  • 自 v4.0.0 起项目引入 Go Modules 支持,但保持对旧 v3.x.y 标签及上游 github.com/dgrijalva/jwt-go 的兼容(见 MIGRATION_GUIDE.md);而 v5.0.0 对 token 验证逻辑做了重大改进,并非完全向后兼容,从旧版升级需参照迁移指南。
  • 支持 Go 版本的策略与官方 release policy 对齐:只支持"尚有不超过两个新大版本发布"的 Go 主版本;不在不支持的 Go 版本上构建,因为旧版本携带不会被修复的安全漏洞。
  • 安全通告:部分旧版 Go 的 crypto/elliptic 存在安全问题,建议至少升级到 Go 1.15。

签发一个 JWT:New → Claims → SignedString

生成令牌只需两步:构造携带 claims 的 Token,再调用 SignedString 计算签名。基于 token.go 的实现,NewNewWithClaims 会初始化默认头部——自动填充 typ: "JWT"alg: <方法名>

// 自定义声明:嵌入标准声明,获得 iss/sub/aud/exp/nbf/iat/jti 的结构化支持
type MyClaims struct {
	Username string `json:"username"`
	jwt.RegisteredClaims
}

func signToken(username string) (string, error) {
	claims := MyClaims{
		Username: username,
		RegisteredClaims: jwt.RegisteredClaims{
			Issuer:    "moby-registry",
			Subject:   username,
			Audience:  jwt.ClaimStrings{"container-service"},
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(30 * time.Minute)),
			IssuedAt:  jwt.NewNumericDate(time.Now()),
		},
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString([]byte("your-secret-key"))
}

几个由源码佐证的细节:

  • jwt.NewWithClaims(method SigningMethod, claims Claims, opts ...TokenOption) *Tokentoken.go)要求 claims 实现 Claims 接口。
  • SignedString 内部先 json.Marshal 头部与 claims 生成签名串 header.payload,调用 Method.Sign 后把签名 base64url 编码拼成完整 token(token.go)。
  • HMAC 对称密钥类型必须是 []byte。源码 hmac.go 对非 []byte 密钥直接返回 ErrInvalidKeyType。注释特别提醒:不要用 ASCII 可读字符串直接转字节做密钥,应使用 crypto/rand 等密码学随机源,以保证足够熵。
  • 时间戳统一由 NumericDate(见下)管理,默认截断精度为秒。

解析与验证:Parse → Keyfunc → Validator

验证是 v5 重新打磨的重点。最简入口是包级函数 jwt.Parse,它等价于 NewParser(...).Parse(...)parser.go),内部默认使用空 MapClaims;更常用的带自定义 claims 版本:

func verifyToken(tokenString string, hmacSecret []byte) (*MyClaims, error) {
	claims := &MyClaims{}
	token, err := jwt.ParseWithClaims(tokenString, claims, func(t *jwt.Token) (any, error) {
		// Keyfunc 收到"已解析但未验证"的 Token,可按 header 中的 kid 等选取密钥
		return hmacSecret, nil
	}, jwt.WithValidMethods([]string{"HS256"}), // 强制算法白名单
		jwt.WithIssuer("moby-registry"),
		jwt.WithAudience("container-service"),
		jwt.WithExpirationRequired(),
	)
	if err != nil {
		return nil, err
	}
	if !token.Valid {
		return nil, errors.New("invalid token")
	}
	return claims, nil
}

Keyfunc:密钥选择回调

Keyfunc 是解析期由调用方提供的回调,签名 func(*Token) (any, error)。它接收已经解析、但尚未验证签名*Token,因此可以利用 header 中的 kid 等信息动态选择用于校验的密钥。返回值既可以是单个密钥,也可以是 VerificationKeySet(一组公钥/密钥),用于多密钥轮换场景——解析器会逐一尝试直至签名匹配(parser.go)。若未提供 keyFunc 或 keyfunc 返回错误,解析器直接判定 token "不可验证"(ErrTokenUnverifiable)。

验证三阶段

ParseWithClaims 的完整执行链(parser.go)可分为清晰的三个阶段,顺序即安全顺序:

  1. 解析分段(ParseUnverified):校验 token 恰好含两个 . 分隔符、恰好三段(splitToken 在发现多余分隔符时直接拒绝,防止恶意输入引发多余开销,见 parser.go);base64url 解码 header 与 claims;根据 header 的 alg 字段查找注册的 SigningMethod。任何一步失败都会返回形如 ErrTokenMalformedErrTokenUnverifiable 的错误。
  2. 算法白名单与签名验证:若配置了 WithValidMethods,先校验 token.Method.Alg() 必须在白名单内(parser.go),再调用 Method.Verify 验证签名。这里密钥类型必须与算法匹配——以 HMAC 为例,Verify 会先断言密钥是 []byte,否则返回 ErrInvalidKeyType;随后用 hmac.New + hmac.Equal(恒定时间比较)复算签名并比对(hmac.go)。
  3. Claims 验证:若未调用 WithoutClaimsValidation(),则把 claims 交给内部 Validator 做时间与保留字段校验;全部通过后 token.Valid = true

错误处理:哨兵错误 + 可组合上下文

v5 将错误系统重构为一组可用 errors.Is 判定的哨兵错误(errors.go),其中包括:

ErrInvalidKeyErrInvalidKeyTypeErrHashUnavailableErrTokenMalformedErrTokenUnverifiableErrTokenSignatureInvalidErrTokenRequiredClaimMissingErrTokenInvalidAudienceErrTokenExpiredErrTokenUsedBeforeIssuedErrTokenInvalidIssuerErrTokenInvalidSubjectErrTokenNotValidYetErrTokenInvalidIdErrTokenInvalidClaimsErrInvalidType

判定过期之类语义可写成:

if errors.Is(err, jwt.ErrTokenExpired) {
	// 引导用户重新登录
}

底层 newError 会像 fmt.Errorf("%w: ...") 一样在哨兵错误之上叠加人类可读的上下文(如 "token is unverifiable: no keyfunc was provided"),并支持 Go 1.20 的多 %w 展开(joinedError.Unwrap() []error),claims 校验多路失败时可同时拿到全部原因。注意:签名无效 / token 过期这类"带语义的错误"仍建议通过 errors.Is 而非字符串匹配来区分

Claims 体系:三种声明形态与自定义声明

RFC 7519 规定实现至少能提供 expiatnbfisssubaud 六个保留声明。v5 用接口统一抽象(claims.go):

type Claims interface {
	GetExpirationTime() (*NumericDate, error)
	GetIssuedAt() (*NumericDate, error)
	GetNotBefore() (*NumericDate, error)
	GetIssuer() (string, error)
	GetSubject() (string, error)
	GetAudience() (ClaimStrings, error)
}

这一设计把"验证逻辑"与"存储形态"彻底解耦:claims 可以是结构体、map,甚至数据库记录,只要提供上述 getter。

库内置两种标准实现:

  • RegisteredClaimsregistered_claims.go):RFC 保留字段的结构化 Go 版本,字段含 Issuer(iss)、Subject(sub)、Audience(aud, 类型 ClaimStrings)、ExpiresAt(exp)、NotBefore(nbf)、IssuedAt(iat)、ID(jti),JSON tag 均带 omitempty。它的典型用途是嵌入到自定义声明类型中(如前面的 MyClaims);单独使用则无法解析 token 中的私有字段。
  • MapClaimsmap_claims.go):基于 map[string]any 的惰性实现,适合只关心个别字段、或字段形态不可预知的场景——moby vendored 的 go-actions-cache/cache.go 对 Actions Cache token 就采用了这种形态。

值得注意的类型细节(types.go):

  • NumericDate:RFC 7519 定义的数字日期类型,内嵌 time.TimeNewNumericDate 会按全局 TimePrecision(默认 time.Second)截断;MarshalJSON 输出秒或毫秒精度的时间戳。保留字面精度兼容需求时,可调整 TimePrecision
  • ClaimStrings:因 aud 既可以是字符串也可以是字符串数组,该类型两种形态都能反序列化;序列化方向由全局 MarshalSingleStringAsArray 控制(默认 true,恒输出数组)。

若要添加应用级校验,请不要覆写旧版 Valid()(v5 已将其从接口移除,见迁移章节),而是让自定义 claims 实现 ClaimsValidator 接口的 Validate() error。校验器会把它附加到标准校验之后执行,且不可能借此绕过标准校验

精调验证:ParserOption 一览(v5 核心特性)

v5 最大的变化之一,是把曾经分散在 Valid() 各实现里的校验逻辑全部收拢进独立的 Validator,并开放了大量函数式选项 ParserOptionparser_option.go)。所有选项都可直接追加到 Parse / ParseWithClaims 的变参中:

ParserOption 作用 默认行为
WithValidMethods(methods []string) 限定允许的 alg 算法白名单,防算法混淆攻击 不校验,接受任意已注册算法
WithLeeway(d time.Duration) 时间类声明(exp/nbf/iat)允许的时钟偏差窗口 0
WithIssuedAt() 校验 iat 不得晚于当前时间(+leeway) 不校验 iat(RFC 视其为纯信息性字段)
WithExpirationRequired() 强制要求存在 exp exp 可选
WithNotBeforeRequired() 强制要求存在 nbf nbf 可选
WithAudience(aud ...string) 要求 aud 含指定任一受众(缺失 aud 即失败) 不校验
WithAllAudiences(aud ...string) 要求 aud 包含全部指定受众(内部去重) 不校验
WithIssuer(iss string) 要求 iss 等于指定值 不校验
WithSubject(sub string) 要求 sub 等于指定值 不校验
WithTimeFunc(f func() time.Time) 注入当前时间来源,主要用于测试 time.Now
WithJSONNumber() claims 解码使用 json.Decoder.UseNumber(),避免大数精度丢失 json.Unmarshal,有一定性能代价故默认关闭
WithoutClaimsValidation() 跳过 claims 校验(危险,需明确知晓后果) 执行 claims 校验
WithPaddingAllowed() 容忍带 = padding 的非标准 base64url token(部分大厂签发过此类"不标准但真实存在"的 token) 关闭
WithStrictDecoding() 按 RFC 4648 §3.5 要求 trailing padding bits 为零的严格解码 关闭

其中 WithLeeway 是处理分布式时钟漂移的标准手段;WithTimeFunc 则把"当前时间"注入解耦出来,让时间相关用例可测试(迁移指南明确建议:处理时钟偏移请用 WithLeeway 而非 WithTimeFunc)。WithAudience/WithIssuer/WithSubject 的语义均按 RFC 的"aud 等字段可选"原则做了安全化处理——一旦你指定了期望值,缺失该声明同样判定失败。

安全红线:算法白名单、密钥类型绑定与 alg=none

README 用两条 SECURITY NOTICE 强调了同类教训:

  1. 务必验证 alg 是否符合预期。 历史上有名的 JWT 库漏洞(如把 RS256 公钥文件当 HS256 对称密钥使用的算法混淆攻击)正是源于此。本库的设计对策是"要求密钥类型与 alg 匹配":以 []byte 使用 HMAC、以 RSA 公钥使用 RS/PS 等,类型不符直接报 ErrInvalidKeyType。但 README 仍建议你在使用层再走一步——即通过 jwt.WithValidMethods([]string{...}) 显式限定算法集,而不是依赖隐式匹配。库文档将其列为**强烈推荐(strongly encouraged)**的安全姿势。
  2. alg=none(无签名 JWT)的防护:为防误用 Unsecured JWT,RFC 7519 §6 允许但必须显式选择。本库的默认行为是拒绝任何 alg=none token,除非你显式传入常量 jwt.UnsafeAllowNoneSignatureType 作为 key。这个命名本身就是在提醒你它的危险性。

此外,解析失败要区分"格式错误/无法验证/签名错误"等阶段(对应 ErrTokenMalformed/ErrTokenUnverifiable/ErrTokenSignatureInvalid),在 keyfunc 返回空 key 集合或 len(keys)==0 时同样以 ErrTokenUnverifiable 终止(parser.go),避免空密钥造成的静默通过。

扩展机制:注册你自己的 SigningMethod 与 Keyfunc

README 明确声明该库对外发布了扩展所需的全部零件——集成第三方签名服务(如云厂商 KMS、HSM 硬件安全模块)或实现额外标准,只需两步:

  1. 实现 SigningMethod 接口signing_method.go),三个方法:
type SigningMethod interface {
	Verify(signingString string, sig []byte, key any) error // 签名有效返回 nil
	Sign(signingString string, key any) ([]byte, error)     // 返回签名或错误
	Alg() string                                            // 返回 alg 标识符,如 "HS256"
}
  1. 注册工厂函数:调用 jwt.RegisterSigningMethod(alg, func() SigningMethod)(通常在包的 init() 中执行)。注册表由 sync.RWMutex 保护的全局 map 构成;GetSigningMethod(alg) 按名取实现,GetAlgorithms() 可枚举全部已注册算法。解析器在读到 header 中的 alg 时正是通过 GetSigningMethod 找到对应验签实现(parser.go)——查不到即报 "signing method (alg) is unavailable"。

此外还有 jwt.Keyfunc 层面的扩展点(例如支持 JWKS 标准作为 Keyfunc 的第三方库)。README 给出了社区扩展参考,均为第三方独立维护、非本项目承诺(免责声明),下表仅列用途:

扩展 用途
GCP 集成 Google Cloud 多种签名工具(AppEngine、IAM API、Cloud KMS)
AWS 集成 AWS KMS
JWKS 提供 RFC 7517 JWKS 格式的 jwt.Keyfunc 支持
TPM 集成可信平台模块(TPM)

从 v4 迁移到 v5:主要破坏性变更速览

v5 引入对核心机制的大规模重构(完整内容见仓库内 MIGRATION_GUIDE.md),以下几点对升级影响最大:

  1. Claims 接口全面重构:旧的 Claims.Valid() error 被移除,改为 6 个 getter(见上文)。原本三种 claims 各自重复实现校验逻辑、难以维护,现在验证统一收敛到 Validator。需要脱离 Parser 单独校验时,可独立使用 jwt.NewValidator(jwt.WithLeeway(5*time.Second)) 后调用其 Validate(claims)
  2. StandardClaims 被移除:v4 已废弃的旧类型在 v5 中删除,替换为 RegisteredClaims。绝大多数自定义 claims 用户只要改为嵌入 RegisteredClaims 即可无感迁移;从零实现的自定义类型需补齐 getter。
  3. 应用级校验改为 ClaimsValidator:旧的"覆写 Valid() 以追加应用校验"极易误关标准校验;新机制中标准校验永远执行,Validate() error 的结果只做追加。
  4. Token.Signaturestring 改为 []byte,且保存的是解码后的签名;EncodeSegment/DecodeSegment 由全局函数改为 Token/Parser 的方法,编码开关(严格解码、允许 padding)相应变成解析选项。自定义签名方法的开发者会受影响。
  5. 错误体系从简单的 fmt.Errorf 转为哨兵错误 + 多错误展开,迁移时应把 err.Error() == "..." 之类比较改写为 errors.Is(err, jwt.ErrXXX)
  6. 对多数用户而言,仅把导入路径改为 github.com/golang-jwt/jwt/v5 即可编译通过;旧版本用户还可参考仓库内 VERSION_HISTORY.md 中的全部破坏性变更列表。

从源码看解析主链路:一次 token 的完整旅程

把 README 的抽象落到代码上,parser.go 的主干流程可以浓缩为下图式的调用链:

Parse/ParseWithClaims (入口)
  └─ ParseUnverified          ① 分段 + base64url/JSON 解码 + 按 alg 查找 SigningMethod
  └─ validMethods 白名单检查   ② alg 不在白名单 → ErrTokenSignatureInvalid
  └─ keyFunc(token)            ③ 取验签密钥;缺 keyfunc/空 key 集 → ErrTokenUnverifiable
  └─ Method.Verify(...)        ④ 签名验证(VerificationKeySet 则逐个尝试)
  └─ validator.Validate(...)   ⑤ 时间/issuer/audience/subject 校验(可配 leeway)
  └─ token.Valid = true

若干容易被忽略、但源码中确实存在的工程细节:

  • ParseUnverified 明确带警告注释:"Don't use this method unless you know what you're doing",仅适合签名在别处已被验证、只需提取字段的场景(如 cache.go 的用法)。
  • splitToken 对额外 . 段落的拒绝parser.go):strings.Cut 截出前两段后,若剩余还有 . 则整体拒绝——这是对"畸形输入带来额外解析开销"这类 DoS 面的防御。
  • base64url 解码的可配置性DecodeSegmentparser.go):默认 base64.RawURLEncoding(无 padding、非严格);WithPaddingAllowed= 并切换 URLEncodingWithStrictDecoding 再叠加 Strict()

合规、项目状态与版本策略

README 声明该库最后一次对照 RFC 7519(2015-05) 做合规审查,已知差异仅一处:alg=none 只有在显式提供 jwt.UnsafeAllowNoneSignatureType 时才被接受(前面已述)。库被判定为 production ready,API 视为稳定,除大版本外几乎不做向后不兼容变更,并遵循 Semantic Versioning 2.0.0:PR 合入 main 后定期打 tag(moby 中的 v5.3.1 即为这种语义化版本流转的实例)。仓库内还附带命令行工具 cmd/jwt(位于上游项目,README 建议用它做 token 创建/解析调试)以及实现层面的 example;作为读者可直接翻阅本仓库 vendored 目录下的 doc.goSECURITY.md 与各算法族实现文件作进一步研读。

小结:把文档主张与仓库代码对应起来

贯穿全文可以看到,README 的每个核心承诺都能在 vendored 源码中找到落点:三段式 token 对应 Token 结构体;"HMAC/RSA/RSA-PSS/ECDSA 并支持自定义"对应各算法族文件与 RegisterSigningMethod 注册表;"验证逻辑可精细配置"对应 Validator 与一整套 ParserOption;"密钥类型必须匹配 alg"对应各类 Verify 入口的强类型断言;而 alg=none 防护、ParseUnverified 的醒目警告与哨兵错误体系,则共同构成了一套把安全决定权留给调用方、同时默认不给不安全姿势开绿灯的 API 设计。当你下次在 moby 的依赖树或自己的服务里看到 JWT 时,可以从这份 README 出发、再落回 parser.gotoken.goparser_option.go 三个文件快速定位实现细节。

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

项目优选

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