Prhub

#1683 [tml] Inkling model support

原始 PR 作者 Zhichenzzz 合并时间 2026-08-04 04:59 文件变更 38 提交数 52 评论 19 代码增减 +3925 / -95

执行摘要

Inkling 模型全参数 RL 支持,含多模态与启动器

PR body 声明目标是 “Inkling model + full-parameter RL support”,并链接 lmsys day-0 博客、sglang 支持 PR(sgl-project/sglang#31681)与 Megatron-LM 适配 PR(radixark/Megatron-LM#68),说明要同时打通 serving 与训练两端。该 PR 让 miles 用户可以直接用 Inkling / Inkling-Small 跑 GRPO 训练(全参数模式),任务覆盖 dapo_math 文本与 geo3k 视觉;同时它是 #2122 原生 LoRA 支持的基础——body 明确写 “The native LoRA layer is stacked on top in #2122”。

值得精读。重点看三处设计:ops.py 与 sglang serving bit-identical 的 fp32 内核对齐策略、layers.py 在 CP/SP 下对 sconv 与相对位置偏置的处理、model.py 的异构 Dense/MoE 层结构与两个 provider 入口。Review 中发现的三个正确性 bug(fp32 masters 顺序、no_grad 范围、tower 同步 gate 失效)是多模态 + 权重同步组合场景的典型陷阱,可作为后续模型接入的检查清单。

讨论亮点

“Optimizer fp32 mains still hold init values here, so the first step() wipes the loaded adapter. Call optimizer.reload_model_params() after this.” —— yushengsu-thu 在 miles/backends/megatron_utils/model.py 指出 LoRA adapter 加载与分布式优化器 fp32 master 初始化顺序错位;对应提交 “Reload optimizer masters” 在加载后刷新 masters。该问题出现在拆分前的中间版本,LoRA 代码随后整体迁往 #2122。

“no_grad covers _scatter too, so embeddings get zero grad on mm batches. Wrap the towers only.” —— yushengsu-thu 在 mm_towers.py 抓到隐蔽梯度 bug:no_grad 把 embedding 克隆也包进去,多模态 batch 的 embedding 梯度恒为零。提交 “Keep the MM scatter in the autograd graph” 将上下文限定在 tower 前向。

“This bypasses inkling_fixed.jinja and drops tool_calls / thinking-effort / end-sampling tokens, so tool and thinking flows train off-template.” —— yushengsu-thu 指出手写 renderer 缺工具调用、推理努力与 end-sampling token,训练会 off-template;最终改为路由到 inkling_fixed.jinja 并退役手写 renderer。

“This arg is never registered, so the gate is always False: geo3k rollouts get garbage towers after the first offload.” —— yushengsu-thu 指出 args.inkling_mm_towers 在 CLI 折叠为 provider 后已不存在,tower 权重重同步门控失效;修复改为按 provider 路径判定。

yueming-yuan 建议 “suggest not using .sh launch script, and merging the 4 Inkling scripts into 1 script (or 2 scripts if needed) and using args to select the config”,Zhichenzzz 回复 “agreed”,最终合并为 run_inkling.py 单入口并折叠 per-slice .sh 包装。

yueming-yuan 在 issue 中提出 “have some thoughts on lora checkpoint; will discuss offline”,涉及 LoRA checkpoint 的后续设计,随 LoRA 拆分转入 #2122 的线下讨论。

Zhichenzzz 通过三轮 CI 复跑把 4 个 h200 shard 失败定位为 main 侧回归而非本 PR:症状均为 dp-attention e2e 中 abort_request 无法触达引擎,指向 #2012(dp-aware routing),并论证 “These tests never ran on post-#2012 main”,排除了本 PR 引入回归的可能。

实现拆解

  1. 模型插件层(新增)miles_plugins/models/inkling/ 目录下,model.pyInklingExtra 从 HF text_config 读取 Inkling 专有配置(SWA/全局注意力头数、d_rel、sconv、MoE 参数等),build_inkling_config 构造 Megatron TransformerConfigget_inkling_block_spec 利用 dense_mlp_idxmoe_layer_freq 构造前 Dense 后 MoE 的混合层结构,并通过 hetereogenous_dist_checkpoint 支持异构分片 checkpoint。layers.py 实现 InklingSelfAttention(qkv + 相对位置偏置一次投影、SWA 局部窗口、可选残差卷积 sconv)与 InklingRouter / InklingSharedExperts / InklingMoELayer / InklingDenseMLPinkling_model_providerinkling_mm_model_provider 两个 provider 入口分别服务纯文本与多模态。

  2. 自定义算子层ops.py 集中 CP/SP 辅助(cp_worldcp_all_gatherinkling_sp_residual_convseqlens_from_packed)与一组与 sglang serving bit-identical 的 fp32 精度对齐 Triton 算子(_swiglu_fwd/bwd_kernel_sconv_fwd/bwd_kernel),以及 flex / TE / FA4 三种注意力后端。CP 要求 --allgather-cp(contiguous sharding),provider 入口显式断言,避免 zigzag 布局下 sconv 与 rel-bias 跨分片语义错误。

  3. 多模态管线mm_processor.pyInklingTrainProcessor 做 token 级 chat render(每个 image/audio part 插入哨兵 token)并抽取媒体;image_processing.py 实现图像 patch 化(numba/numpy 双实现,与 serving 数值一致);audio_processing.py 实现 dmel 提取(Slaney mel 尺度 + hann 窗 STFT);vision_encoder.pyHMLPPatchEncoder 做层次化 patch 折叠;mm_towers.pywire_mm_towers 把冻结的 HF vision/audio tower 以 decoder_input scatter 方式接入模型 forward,并把 no_grad 限定在 tower 前向,避免 _scatter 脱离 autograd 图(review 中发现的梯度 bug,后续已修复)。

  4. 权重同步与转换megatron_to_hf/inkling.py 处理 Megatron 权重到 HF 的转换(qkvr 反分片、w13 重交织、共享专家与路由 gate 的跨调用累积组装 _PieceAccumulator);mbridge/inkling.py 处理 HF 到 Megatron 的映射;update_weight/common.py 抽出 is_routed_expert_param 谓词,修正共享专家嵌套 ModuleList 被误判为 expert-parallel 的问题(对现有非嵌套模型是 no-op);update_weight_from_tensor.py 增加 _mm_tower_named_tensors 把冻结 tower 权重纳入基础同步(该 gate 曾因 CLI 参数折叠失效,已修复);model.py_has_loadable_ckpt 修复 checkpoint 加载门控,只跳过“目录存在但无 iter_*/release”的情况,避免 HF 加载模式被静默跳过。

  5. 启动器与测试/文档配套scripts/run_inkling.py 统一启动器(模型注册表 Inkling / Inkling-Small / 4-layer 切片,_get_parallel_config 只放行已验证的 TP/PP/EP 组合;colocate 与 --fully-async 两种拓扑;配置 NVMe 优化器状态流式与 torch_memory_saver offload)。新增 tests/e2e/megatron/model_scripts/test_inkling_small_4layer_ci.py 4 层 CI 冒烟测试(全参数版),并配套 docs/models/thinkingmachines/inkling.md 文档。

文件 模块 状态 重要度
miles_plugins/models/inkling/ops.py 核心算子 added 9.17
miles_plugins/models/inkling/layers.py 模型层 added 9.07
miles_plugins/models/inkling/model.py 模型装配 added 9.07
miles_plugins/models/inkling/mm_processor.py 多模态 added 9.18
miles/backends/megatron_utils/megatron_to_hf/inkling.py 权重转换 added 9.05
scripts/run_inkling.py 启动脚本 added 8.98
miles/backends/training_utils/mm_data.py 数据管线 modified 7.46
tests/e2e/megatron/model_scripts/test_inkling_small_4layer_ci.py CI 测试 added 7.01

关键符号

build_inkling_config get_inkling_spec inkling_model_provider inkling_mm_model_provider InklingSelfAttention.forward inkling_sp_residual_conv cp_all_gather seqlens_from_packed wire_mm_towers convert_inkling_to_hf is_routed_expert_param _has_loadable_ckpt

关键源码片段

miles_plugins/models/inkling/ops.py core-logic

整个插件最核心的算子层:集中 CP/SP 辅助(cp_world、cp_all_gather、inkling_sp_residual_conv、seqlens_from_packed)与与 sglang serving bit-identical 的 fp32 Triton 算子(swiglu、sconv)及三种注意力后端;精度对齐策略直接决定训练 - 服务一致性。

# miles_plugins/models/inkling/ops.py
# Inkling 自定义算子集中地:CP/SP 辅助函数与精度对齐的 fp32 Triton 内核。
# Inkling 的残差卷积(sconv)与相对位置偏置都要求看到序列左上下文,
# 因此 CP 必须使用 contiguous sharding(--allgather-cp),而 miles 默认的
# zigzag / load-balanced 布局不满足该前提,provider 入口会 assert 掉。def cp_world():
    """返回 (cp_size, cp_rank, cp_group),cp_size 为 1 表示未启用上下文并行。"""
    cp = ps.get_context_parallel_world_size()
    if cp <= 1:
        return 1, 0, None
    return cp, ps.get_context_parallel_rank(), ps.get_context_parallel_group()
​
​
def cp_all_gather(x, group, world):
    """按 CP rank 顺序 all-gather [s, ...] -> [s * world, ...]。    前提是 contiguous CP 分片:rank r 持有 token [r*s, (r+1)*s)。
    """
    xs = [torch.empty_like(x) for _ in range(world)]
    torch.distributed.all_gather(xs, x.contiguous(), group=group)
    return torch.cat(xs, dim=0)
​
​
def inkling_sp_residual_conv(config, conv, x_sbh, seqlens):
    """残差深度卷积(逐 token 因果)作用于 [s, b, h]。    SP/CP 下序列被分片,本地 shard 的因果卷积会丢失左上下文,
    因此先 gather 全序列再做卷积,最后切回本 rank 的 slice;
    由于是残差路径,该通信开销可接受,且结果与单卡完全一致。
    seqlens 必须是全序列的 segment 长度(按 packed 序列切分)。
    """
    sp = getattr(config, "sequence_parallel", False) and ps.get_tensor_model_parallel_world_size() > 1
    cp, cp_rank, cp_group = cp_world()
    x = gather_from_sequence_parallel_region(x_sbh, tensor_parallel_output_grad=False) if sp else x_sbh
    if cp > 1:
        x = cp_all_gather(x, cp_group, cp)
    s, b, h = x.shape
    x = conv(x.reshape(s * b, h), seqlens).reshape(s, b, h)
    if cp > 1:
        sloc = s // cp
        x = x[cp_rank * sloc : (cp_rank + 1) * sloc]
    return scatter_to_sequence_parallel_region(x) if sp else x
miles_plugins/models/inkling/layers.py core-logic

Inkling 的 Transformer 层实现:双全局 /SWA 注意力 + 相对位置偏置 + 残差卷积 sconv、sigmoid router + 共享专家联合重归一化、MoE/Dense 混合 MLP;forward 中处理 CP all-gather 与 packed 序列切分。

# miles_plugins/models/inkling/layers.py
# InklingSelfAttention:qkv 额外输出相对位置偏置 r,SWA 层用局部窗口 + 残差卷积(sconv),
# 全局层用完整注意力;CP 下先 all-gather k/v 到全序列再做 sconv 与注意力。
    def forward(self, hidden_states, attention_mask, inference_context=None,
                rotary_pos_emb=None, attention_bias=None, packed_seq_params=None, **kw):
        if hidden_states.dtype != self.config.params_dtype:
            hidden_states = hidden_states.to(self.config.params_dtype)
        sq = hidden_states.shape[0]
        x = hidden_states.reshape(sq, -1)
        qkvr, _ = self.linear_qkv(x) # 一次投影出 q、k、v、r 四部分
        T = qkvr.shape[0]
        cp, cp_rank, cp_group = _cp_world()
        q, k, v, r = qkvr.split(
            [self.nh_l * self.hd, self.nkv_l * self.hd, self.nkv_l * self.hd, self.nh_l * self.d_rel], dim=-1
        )
        if cp > 1:
            # CP 下先 all-gather k/v 到全序列,再做 sconv 与注意力:
            # 因果卷积需要左上下文,本地 shard 会截断
            k = _cp_all_gather(k, cp_group, cp)
            v = _cp_all_gather(v, cp_group, cp)
            T_full = k.shape[0]
            seqlens = _seqlens_from_packed(packed_seq_params, T_full)
            self.config.inkling._seqlens = seqlens
            if self.k_sconv is not None:
                k = self.k_sconv(k, seqlens)
                v = self.v_sconv(v, seqlens)
            q = self.q_norm(q.reshape(-1, self.hd)).reshape(T, self.nh_l, self.hd)
            k = self.k_norm(k.reshape(-1, self.hd)).reshape(T_full, self.nkv_l, self.hd)
            v = v.reshape(T_full, self.nkv_l, self.hd)
            r = r.reshape(T, self.nh_l, self.d_rel)
            if self.attn_backend == "flex":
                out = self._seg_cp_flex(q, k, v, r, seqlens, cp_rank * T, T_full)
            else:
                out = self._seg_cp(q, k, v, r, seqlens, cp_rank * T, T_full)
        else:
            seqlens = _seqlens_from_packed(packed_seq_params, T)
            self.config.inkling._seqlens = seqlens
            if self.k_sconv is not None:
                k = self.k_sconv(k, seqlens)
                v = self.v_sconv(v, seqlens)
            q = self.q_norm(q.reshape(-1, self.hd)).reshape(T, self.nh_l, self.hd)
            k = self.k_norm(k.reshape(-1, self.hd)).reshape(T, self.nkv_l, self.hd)
            v = v.reshape(T, self.nkv_l, self.hd)
            r = r.reshape(T, self.nh_l, self.d_rel)
            out = self._attend(q, k, v, r, seqlens)
        proj_out, proj_bias = self.linear_proj(out.unsqueeze(1))
        if self.attn_sconv is not None:
            # 残差路径上的输出卷积:SP/CP 下需要先 gather 全序列再取本 rank slice
            proj_out = _sp_residual_conv(self.config, self.attn_sconv, proj_out, seqlens)
        return proj_out, proj_bias
miles_plugins/models/inkling/model.py core-logic

模型装配入口:从 HF text_config 构建 Megatron TransformerConfig(InklingExtra 挂在 cfg.inkling),提供 layer/block spec、纯文本与多模态两个 provider 入口,并处理 embed norm、fp32 residual、全局 scale 冻结等训练细节。

# miles_plugins/models/inkling/model.py
# Inkling 专有配置:从 HF text_config 读出并挂到 cfg.inkling。
# 调试开关默认走生产配方,MILES_INKLING_* 环境变量可覆盖(单卡内核调试用)。
class InklingExtra:
    def __init__(self, t: dict):
        self.num_attention_heads = t["num_attention_heads"]
        self.num_key_value_heads = t["num_key_value_heads"]
        self.head_dim = t["head_dim"]
        # SWA 层与全局注意力层使用不同的头数 / 头维
        self.swa_num_attention_heads = t["swa_num_attention_heads"]
        self.swa_num_key_value_heads = t["swa_num_key_value_heads"]
        self.swa_head_dim = t["swa_head_dim"]
        self.sliding_window_size = t["sliding_window_size"]
        self.d_rel = t["d_rel"] # 相对位置偏置的维度
        self.rel_extent = t.get("rel_extent", 1024)
        self.local_layer_ids = set(t["local_layer_ids"]) # 哪些层用 SWA
        self.sconv_kernel_size = t["sconv_kernel_size"]
        self.use_sconv = t["use_sconv"]
        self.dense_mlp_idx = int(t.get("dense_mlp_idx", 0)) # 前几层用 Dense MLP
        self.n_routed_experts = t["n_routed_experts"]
        self.num_experts_per_tok = t["num_experts_per_tok"]
        self.n_shared_experts = t["n_shared_experts"]
        self.attn_backend = os.environ.get("MILES_INKLING_ATTN_BACKEND", "flex") # flex | te | fa4
        self.sconv_impl = os.environ.get("MILES_INKLING_SCONV_IMPL", "triton") # triton | torch
        self.sconv_packed = os.environ.get("MILES_INKLING_SCONV_PACKED", "0") == "1"
        self.freeze_global_scale = os.environ.get("MILES_INKLING_FREEZE_GLOBAL_SCALE", "all")
​
​
def build_inkling_config(text_cfg: dict, tp=1, ep=1, pp=1, bf16=True, sp=False,
                         etp=1, cp=1, varlen=True, permute_fusion=False,
                         fp32_residual=False, pp_first_stage_layers=None,
                         pp_last_stage_layers=None) -> TransformerConfig:
    inter = text_cfg["intermediate_size"]
    ns = text_cfg["n_shared_experts"]
    denseI = int(text_cfg.get("dense_intermediate_size", inter))
    cfg = TransformerConfig(
        hidden_dropout=0.0, # 曾有 bug:dropout 未透传,默认 0.1 导致 embedding 10% 通道被置零
        attention_dropout=0.0,
        num_layers=text_cfg["num_hidden_layers"],
        hidden_size=text_cfg["hidden_size"],
        num_attention_heads=text_cfg["num_attention_heads"],
        num_query_groups=text_cfg["num_key_value_heads"],
        kv_channels=text_cfg["head_dim"],
        ffn_hidden_size=denseI,
        tensor_model_parallel_size=tp,
        pipeline_model_parallel_size=pp,
        expert_model_parallel_size=ep,
        sequence_parallel=sp,
        context_parallel_size=cp,
        variable_seq_lengths=varlen,
        fp32_residual_connection=fp32_residual,
        num_moe_experts=text_cfg["n_routed_experts"],
        moe_router_topk=text_cfg["num_experts_per_tok"],
        moe_ffn_hidden_size=inter,
        moe_shared_expert_intermediate_size=inter if ns > 0 else None,
        moe_router_score_function="sigmoid", # 与 serving 对齐的 sigmoid 路由
        moe_router_pre_softmax=True,
        moe_router_load_balancing_type="seq_aux_loss",
        moe_aux_loss_coeff=0.0, # 训练侧关闭 aux loss
        moe_router_bias_update_rate=0.0,
        moe_router_dtype="fp32",
        moe_grouped_gemm=True,
        moe_token_dispatcher_type="alltoall",
        add_bias_linear=False,
        normalization="RMSNorm",
        layernorm_epsilon=text_cfg["rms_norm_eps"],
        qk_layernorm=True, # q/k norm + 相对位置偏置
        bf16=bf16,
        params_dtype=torch.bfloat16 if bf16 else torch.float32,
        gated_linear_unit=True,
        pipeline_dtype=torch.bfloat16 if bf16 else torch.float32,
    )
    cfg.inkling = InklingExtra(text_cfg)
    cfg.moe_activation_in_fp32 = True # 与 sglang serving 保持 fp32 语义对齐
    cfg.moe_combine_in_fp32 = True
    _didx = cfg.inkling.dense_mlp_idx
    # 前 dense_mlp_idx 层 Dense、其余 MoE 的混合结构,
    # heterogeneus dist checkpoint 处理两种层的分片差异
    cfg.moe_layer_freq = [0] * _didx + [1] * (cfg.num_layers - _didx)
    cfg.hetereogenous_dist_checkpoint = True
    return cfg

评论区精华

LoRA adapter 加载后 optimizer fp32 masters 过期 正确性

yushengsu-thu 在 model.py 的 diff 上指出:load_inkling_lora_adapter 写 adapter 参数发生在 load_checkpoint 填充分布式优化器 fp32 masters 之后,首个 step() 会用旧 init 值覆盖刚加载的 adapter。

结论:提交 “Reload optimizer masters after loading a LoRA adapter” 在加载后刷新 fp32 masters;该问题出现在 LoRA 拆分前的中间版本,随后 LoRA 代码整体迁往 #2122。 · 已解决

no_grad 覆盖 _scatter 导致 embedding 零梯度 正确性

yushengsu-thu 在 mm_towers.py 指出:wire_mm_towers 的 no_grad 上下文不仅覆盖冻结 tower 前向,还覆盖了 _scatter 对 embedding 输出的 clone,导致多模态 batch 的 embedding 梯度恒为零。

结论:提交 “Keep the MM scatter in the autograd graph” 将 no_grad 限定在 tower 前向,_scatter 保持在 autograd 图中。 · 已解决

MM tower 权重同步 gate 读到未注册参数 正确性

yushengsu-thu 指出:update_weight_from_tensor.py 的 _mm_tower_named_tensors 用 args.inkling_mm_towers 做门控,但该 CLI 参数已在插件配置折叠进 provider 后移除,gate 恒为 False,geo3k rollout 第一次 offload 后 tower 权重错乱。

结论:提交 “Key the MM tower re-sync off the provider path” 改为按 provider 路径判定。 · 已解决

手写 chat renderer 绕过 inkling_fixed.jinja 设计

yushengsu-thu 指出:手写 python renderer 丢失 tool_calls、thinking-effort 与 end-sampling token,工具调用与思考流会 off-template 训练。

结论:提交 “Render Inkling chats through the fixed template” 将 apply_chat_template 路由到 inkling_fixed.jinja 并退役手写 renderer。 · 已解决

Inkling 启动脚本合并建议 设计

yueming-yuan 建议 “merging the 4 Inkling scripts into 1 script (or 2 scripts if needed) and using args to select the config”;Zhichenzzz 回复 “agreed”。

结论:最终合并为 scripts/run_inkling.py 单入口,fully-async 也以 --fully-async 标志折叠进同一脚本,并删除 per-slice 的 .sh 包装。 · 已解决

h200 CI 失败归因:#2012 的 main 侧回归 question

Zhichenzzz 三轮 CI 复跑后判定 4 个 h200 shard 失败均为 dp-attention e2e 的 abort_request 不可达(60/60 重试失败),并论证这些测试在 post-#2012 main 上从未跑过,根因在 #2012(dp-aware routing)而非本 PR。

结论:该结论被接受;本 PR 的 GPU 套件成为 main 侧 dp-aware routing 回归的探测器。 · 已解决

LoRA checkpoint 设计线下讨论 question

yueming-yuan 在 issue 评论 “have some thoughts on lora checkpoint; will discuss offline”,涉及 LoRA checkpoint 的后续设计。

结论:随 LoRA 代码拆分转入 #2122 的线下讨论,本 PR 不阻塞。 · 待处理

风险与影响

  1. checkpoint 加载门控回归:合并期 _has_loadable_ckpt 曾两次出错——先是对未设置 --load 的情况误判跳过,再是对 HF 目录(Bridge 模式)静默跳过 load_checkpoint,导致训练在 provider 初始化权重上进行。最终修复依赖 load_checkpoint 自身 dispatch,但与 _has_loadable_ckpt 存在重复判定,后续改动易再次踩坑。

  2. kernel 精度对齐风险:flex 训练后端与 fa4/服务端并非 bit-identical,CI 只能靠 metric-history gate(--ci-disable-kl-checker + ppo_kl 门控)而非零 KL 精确断言;swiglu、sconv、注意力三处 fp32 语义必须与 sglang 保持一致,任一算子换算顺序变化都会造成训练-服务不一致。

  3. CP 布局约束cp_all_gather 假定 contiguous CP sharding,provider 仅通过 assert 强制 --allgather-cp;若未来引入 zigzag CP 的其他模型复用这些 ops,会静默算错或崩溃。

  4. 权重转换器假设megatron_to_hf/inkling.py 的 qkvr 反分片明确不支持 nkv < tp 的 kv 头复制场景(注释说明训练侧保持 nkv >= tp),非标准 parallel 配置下会断言失败;专家权重假定 ETP = 1。

  5. CI 覆盖有限:e2e 只覆盖 4 层 Inkling-Small 切片(约 33 GB),无法覆盖 66 层 975B 的 EP16、fully-async、多模态 tower 同步等生产路径;分布式(非 colocate)引擎的 tower 同步仍未完全支持。

用户/任务:可直接启动 Inkling 与 Inkling-Small 的全参数 GRPO 训练(dapo_math 文本、geo3k 视觉),支持 colocate 与 fully-async 两种拓扑、NVMe 优化器状态流式与 torch_memory_saver offload。

系统:新增完整的模型插件体系(kimi_k3 风格目录)、Megatron↔HF 转换器与多模态数据扩展 _expand_inkling_sample;权重同步核心路径(update_weight/common.pyupdate_weight_from_tensor.py)的改动对所有模型生效,其中共享专家排除逻辑对非嵌套模型为 no-op。Provider 路径下的 checkpoint 加载行为也被收紧。

团队:本 PR 是 Inkling 家族支持的第一块基石,后续 #2122(LoRA)、#2013(TITO 会话)直接建立在它之上;其 h200 GPU 套件还暴露了 main 侧 dp-aware routing 回归(#2012),成为重要的回归探测器。

checkpoint 加载门控回归 与 sglang kernel 精度对齐要求高 CP 强制 allgather-cp CI 仅覆盖 4 层切片 权重同步核心路径改动

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论