Swin Transformer从源码审计到生产落地:选型避坑与工程实践
接到一个内部评审需求要把现有图像分类服务从 ResNet 迁移到 Swin Transformer 上。团队第一反应是去 GitHub 拉下官方 microsoft/Swin-Transformer看 README跑一遍推理然后接进主干。这个路线本身没错但如果要做成生产级改造只跑跑 Demo 远远不够。我花了一周时间把这个仓库从模型源码、依赖管理、配置系统、预训练权重分发到下游检测/分割生态整体做了一次工程治理层面的源码审计结论比预想复杂得多。这篇文章会把这些结论摊开来讲我会先从仓库定位谈起再到模型核心源码的模块化程度、相对位置索引这些容易被改坏的地方然后是依赖、配置、测试、权重管理这些工程治理体检项最后给几条真正可落地的选型路径并附上我自己做最小推理组件时的记录。无论你是算法工程师、平台开发还是负责技术选型的人这篇都能当一份直接抄作业的审计笔记。1. 打开仓库前先理清 Swin-Transformer 的定位与治理边界1.1 仓库本体、官方扩展与第三方生态的边界很多人容易把“Swin-Transformer”理解成一个独立的模型算法库实际去看仓库就会发现它更像是一篇论文的“研究代码发布包”而不是一个“产品级模型服务仓库”。以我审计时 pin 住的 master 分支为例根目录核心内容大致可以分成这么几块models/swin_transformer.py整个 Swin Transformer 分类 backbone 的核心实现也是绝大多数团队做源码阅读、代码裁剪、算子移植时最关注的文件configs/swin/*.yaml分类任务的训练配置包含不同尺寸模型Tiny / Small / Base / Large的超参数组合main.py和utils/分类任务的训练、验证、吞吐测试入口以及学习率、优化器、日志等辅助逻辑data/ImageNet 数据加载、mixup / cutmix 等增强逻辑README 中的“下沉”说明物体检测和实例分割需要使用 mmdetection语义分割需要使用 mmsegmentationSwin backbone 以第三方扩展方式集成进去。这带来一个治理上的边界问题你引入的到底是“官方模型定义”还是“官方训练系统”还是“官方生态方案”三者的维护方式、风险等级完全不同。如果只是要 backbone 的forward输出做图像分类那models/swin_transformer.py单独拿出来即可如果要做 ImageNet 级别的训练复现那配置、数据增强、混合精度、分布式训练这些全部要跟着一起看如果要做检测分割等于同时引入了另一个大型开源项目需要评估的是 mmdetection/mmsegmentation 的工程治理水平而不仅仅是微软这个仓库本身。另外还要提醒一个名字混淆问题。市面上常见的 SwinIR、Swin-Unet、Swin-Transformer-V2 都不是这个仓库它们分属不同的仓库和团队。审计时一定要先确认对方说的“Swin Transformer”到底是 V1 分类、V1 检测集成、还是 V2 系列。不同版本之间的window_size、相对位置编码实现、预训练权重格式都有差异跨仓库拼装经常会在加载权重时直接翻车。1.2 License 只是入场券模型卡与权重分发才是法律风险点再从工程治理最常被忽略的 License 说起。官方仓库的代码采用 MIT License意味着商用、改写、嵌入闭源产品整体上是宽松的。但这个“开源”并不自动等同于“所有产物都可以无限制使用”。企业做合规审计时需要把三样东西分开看仓库源码的 LicenseMIT风险低预训练权重的使用条款官方的 ImageNet 预训练权重通常附带有研究/非商用限制或单独的使用声明具体要看所在 Release 页面或 model card。不要假设“代码 MIT权重就一定能放进商业产品”依赖项的 LicenseSwin 官方仓库重度依赖 timm而 timm 本身是 Apache-2.0但如果下游接的是 OpenMMLab 体系里面的检测/分割代码又涉及 Apache-2.0 和部分可能存在的额外限制。要纳入企业软件成分审计SCA的其实是整棵依赖树。从项目活跃度看Swin Transformer V1 作为论文配套代码维护节奏早已过了高频迭代期。它的“稳定”不是因为工程成熟而是因为研究代码在上线一段时间后自然进入低维护状态。因此选型时不要指望“官方会持续修复 issue”更常见的做法是社区通过 timm、mmpretrain 等派生项目持续吸收和演进。理解这一点后面所有审计结论都会顺很多。2. 从模型源码反推工程意图模块划分与可维护性体检2.1 层层嵌套的 SwinTransformer 骨架结构核心文件models/swin_transformer.py是一份信息密度非常高的代码基本做到了“论文里有什么代码里就有什么”。我按模块拆了一下它的类结构大致长这样PatchEmbed把输入图像切 patch 并做线性投影官方用nn.Conv2d一步完成WindowAttention带相对位置偏置的窗口化自注意力SwinTransformerBlock单个 Swin Block内部会处理 shift window 的 roll 和 maskPatchMerging把 2×2 邻域拼接并降维实现空间下采样BasicLayer把一个 stage 内若干个 SwinTransformerBlock 串起来并在 stage 末尾调用 PatchMergingSwinTransformer顶层模型组合 patch embed、多层 BasicLayer、最后的 norm 和分类头。这种“论文直译”式设计在学术仓库里是优点它让读过论文的人能快速对号入座。但对工程治理来说缺点也很明显类之间相互依赖顺序复杂你很难单独拎出SwinTransformerBlock去做单测因为上层给它传进来的参数经常是层层透传的。顶层模型构造函数的参数几乎覆盖了所有训练变体这是考证一个骨干网络源码成熟度的重要观察点class SwinTransformer(nn.Module): def __init__( self, pretrain_img_size224, patch_size4, in_chans3, embed_dim96, depths[2, 2, 6, 2], num_heads[3, 6, 12, 24], window_size7, mlp_ratio4.0, qkv_biasTrue, qk_scaleNone, drop_rate0.0, attn_drop_rate0.0, drop_path_rate0.1, norm_layernn.LayerNorm, apeFalse, patch_normTrue, out_indices(0, 1, 2, 3), frozen_stages-1, use_checkpointFalse, ): super().__init__() # 内部按 stages 构造 BasicLayer这个参数列表本身就是一份很好的“代码即配置”文档embed_dim决定第一阶段的通道数depths决定每个 stage 有多少个 blocknum_heads与每个阶段维度对应window_size是窗口大小mlp_ratio是 MLP 隐藏层缩放比例drop_path_rate在模型内部被根据 depths 线性切分成每个 block 对应的随机深度衰减率。工程上容易忽略的是out_indices、frozen_stages、use_checkpoint这三个参数。out_indices让模型在 forward 过程中返回指定 stage 的特征这是给检测、分割使用多层特征金字塔准备的frozen_stages用于冻结前几个 stage 的骨干参数做迁移学习和微调省显存use_checkpoint则是把每个 block 包裹进torch.utils.checkpoint.checkpoint里用重计算换显存。但它们都有一个共同问题官方 training 脚本和下游框架并不一定记得透传这些参数部署时若不同步配置行为会产生隐性差异。2.2 窗口注意力里最容易被改坏的那段相对位置索引如果要给 Swin Transformer 源码的危险程度排个名我会把相对位置索引的计算和掩码生成放在第一位。很多从旧代码迁移到新版本的团队问题都出在这里。为了生成索引表官方代码有一段非常数学化的写法coords_h torch.arange(window_size[0]) coords_w torch.arange(window_size[1]) coords torch.stack(torch.meshgrid([coords_h, coords_w], indexingij)) coords_flatten torch.flatten(coords, 1) # 2, Wh*Ww relative_coords coords_flatten[:, :, None] - coords_flatten[:, None, :] relative_coords relative_coords.permute(1, 2, 0).contiguous() relative_coords[:, :, 0] window_size[0] - 1 relative_coords[:, :, 1] window_size[1] - 1 relative_coords[:, :, 0] * 2 * window_size[1] - 1 relative_position_index relative_coords.sum(-1) # Wh*Ww, Wh*Ww self.register_buffer(relative_position_index, relative_position_index)这里把二维相对坐标转换成一维索引的技巧非常精妙但也非常脆弱。一旦有人“优化”了中间的变量计算或者把window_size从元组改成了单个 int再用旧的预训练权重去加载偏置表relative_position_bias_table和索引表长度就会对不上甚至不会报错只会出现结果明显变差的情况。我见过最隐蔽的一次问题是有人把这段代码里的torch.meshgrid默认行为改动后行列顺序变了窗口内注意力计算依然能跑但精度直接掉到接近随机。这种问题在 ImageNet 上跑完整验证集能发现但如果你只拿几张测试图看 loss根本察觉不到。另一个工程治理层面的观察点这段代码没有类型标注且大量使用类似x、B_、N的变量。对于需要二次开发的生产代码我建议维护者不要直接照抄而是加上类型注解和中间 shape 的断言否则后人很容易在改动window_partition与window_reverse时引入 B/H/W/C 维度错位。2.3 drop_path、use_checkpoint、ape 这些训练后门参数如何处理源码里有几个参数训练阶段很有用但上线推理时必须处理干净否则会成为隐性事故点。第一个是drop_path_rate。DropPath 本质上是正则化手段训练时随机把某个 Block 的残差分支整体置零。推理时为了保证确定性应该关闭或让 DropPath 模块在eval()模式下不做任何随机操作。官方实现里 DropPath 通常支持training状态判断但如果有人把它写成无状态随机就会导致同一张图两次推理结果不同。第二个是ape即 absolute position embedding。这个选项在 Swin 里默认是 False因为窗口相对位置偏置已经提供了位置信息。但如果你把某个研究型 config 中apeTrue的模型直接搬到推理服务就会额外引入一个绝对位置编码参数。编码表的长度和训练时的pretrain_img_size强相关输入分辨率一旦变化就可能需要插值导致精度波动。第三个是use_checkpoint。梯度检查点是为训练省显存设计的它会在反向传播时重算前向激活。推理阶段如果忘记关闭不仅白白增加计算量某些模型服务框架在 trace 时还会因为 checkpoint 包裹层而产生额外子图导致序列化出来的模型比预期大或导出失败。审计源码时我通常要求团队把训练配置和部署配置彻底分离训练时用的调度参数、正则参数、重计算参数不要直接塞进 inference 配置文件。3. 工程治理全项审计版本、依赖、配置与 CI 的体检结果3.1 requirements 未锁版本是复现和治理的双刃剑Swin-Transformer 官方仓库虽然提供了requirements.txt但基本没有做环境锁版本。这意味着你 clone 下来安装时拉到的 PyTorch、timm、torchvision 都是“当时最新”很可能和作者跑实验时不一致。我最担心的不是 PyTorch 大版本变化而是timm小版本漂移。Swin 官方在模型实现和训练增强里都依赖了timm.models.layers不同 timm 版本之间DropPath、trunc_normal_、to_2tuple的实现虽然大体稳定但数据增强模块比如 mixup、cutmix、RandAugment 的参数行为经常在悄悄变化。也许你训练出来的模型只在 top-1 差 0.1%但复现实验时就会发现结果抖动到无法解释。工程治理上的建议非常简单无论你选择官方仓库还是 timm 路线代码目录里都要有一个 lock 住的环境文件至少锁住 torch、torchvision、timm 三个包的大版本或 commit。不要依赖“README 里写的要求”要依赖你实际验证过的那套组合。官方另一个让我不太舒服的点是没有提供setup.py或标准的 wheel 构建。大多数使用者是直接把仓库目录加入PYTHONPATH这会导致代码版本和模型权重版本之间缺乏强绑定关系。一个仓库有多个 commit每个 commit 对应的模型行为可能不同如果没有工具把“代码版本 权重版本 配置版本”三者一起固化审计台账就是缺失的。3.2 配置系统虽灵活但缺少 schema 校验官方仓库用 YAML 配置训练参数命令行再通过--cfg指定路径。YAML 的好处是直观能一眼看到模型尺寸、drop path、输入分辨率、优化器参数。但坏处是它只是一个“自由格式字典”没有 schema 校验。我在审计 config 文件时发现同一个参数在不同 config 里有不同的默认值或书写风格。比如模型的drop_path_rateSwin-T 和 Swin-B 本身就应该不同这是合理的但如果团队在 config 上叠了命令行覆盖比如--batch-size 256 --data-path /datasets/imagenet那 config 文件本身就不再是最终训练状态的唯一权威。你必须依赖训练日志里的“最终 config dump”才能完整复现。如果你所在公司有平台团队接入 Swin 前最好做一层配置标准化把官方 YAML 转换为内部统一 schema至少校验embed_dim、depths、num_heads、window_size这几个字段的合法性和相互关系。否则算法同学随手把 depths 从[2,2,6,2]改成[2,2,6,4]可能只是训练变慢了检测任务的分辨率却不匹配。3.3 测试与 CI 覆盖情况建议收敛自己的冒烟测试对学术仓库谈 CI/CD 覆盖率有时候有点苛刻但作为工程选型评审这个问题绕不开。官方仓库的主要“测试”是 ImageNet 分类验证集和下游检测/分割任务的 benchmark而不是针对代码模块的单元测试。也就是说如果你改动了PatchMerging中某个拼接顺序只要不是结构性错误本地的快速单测不一定能发现你必须跑完整个验证集才能看到精度下降。这个反馈周期非常长不利于做内部二次开发。我处理开源算法库的通用原则是不指望上游测试覆盖到我们的改动但要自己在仓库入口补一个“冒烟测试集”。对 Swin 来说最小冒烟测试至少应该包括随机输入 shape(2, 3, 224, 224)forward 输出 shape 为(2, 1000)或预期 num_classes加载官方预训练权重后用同一张标准化图片比较官方脚本和内部 wrapper 的 logits 是否一致对window_size不能整除的输入尺寸确认 padding 行为是否符合预期导出 ONNX 或 TorchScript 后用同一输入比较原始模型和导出模型的输出误差。补完这几个测试你改源码的信心会高很多。3.4 多任务仓库的权重管理困境审计时还要注意权重的组织方式。官方仓库在训练完成后并不会像成熟 MLOps 平台那样生成标准化的 model card通常只产出.pth文件里面是 PyTorch state_dict。一个典型的 state dict 里既有model.前缀也可能因为torch.nn.DataParallel/DistributedDataParallel包过一层而带module.前缀。这直接导致 load 权重时经常需要写strip_prefix逻辑。从模型治理角度看我建议把官方权重导入内部模型注册中心时统一做三件事一是去掉module.前缀统一为裸模型 state dict二是保留一份和权重对应的 config 快照三是记录权重来源 URL 和 SHA256。否则半年后你想回溯某个线上模型是谁训的、怎么训的会发现无从查起。Swin 这类研究仓库不会替你做这些事工程化补全责任完全在集成方。4. 实测接入中的六个暗坑与排查思路4.1 输入尺寸不整除 window_sizepadding 后输出坐标漂移Swin 不是严格意义上“任意分辨率”都能直接用的 Backbone。窗口注意力要求空间尺寸能够被window_size整除至少在做窗口划分时要能处理余数。官方 forward 里确实做了 padding 处理。遇到输入尺寸不能整除时会在右侧和底部补零然后再做窗口划分。对纯分类任务来说最后接全局池化padding 的影响相对可控但它在检测、分割场景会带来两个问题第一feature map 的尺寸和未 padding 时的理论尺寸不一致neck 部分如果按固定 stride 对齐坐标会偏移第二padding 区域会参与 attention 计算虽然 mask 能遮住部分非法的注意力位置但它毕竟改变了实际计算的有效区域大小。我们的排查经验是进入 Swin Backbone 前要么由预处理统一做 resize 和 center crop保证送到网络里的宽高就是训练时设计好的值要么模型内部做强制对齐到window_size整数倍。不要在 neck 或检测头上假设空间尺寸是固定的除非你完全控制了上游输入。4.2 自定义分类头后 load_state_dict 报错先查 key 前缀很多团队不会真的用官方默认的分类头而是把自己的业务分类器替换上去。于是会遇到一个标准错误state_dict里的head.weight形状不匹配。这个问题的排查并不复杂但新手很容易走偏。拿到官方权重后你应当先打印 key 列表看是否有module.前缀再看最后一层分类头 key 是否叫head.weight、head.bias。如果你把官方源码里的head属性改成了别的名字或者你加了额外多层就需要在加载时先把分类头相关 key 过滤掉或者用strictFalse加载后单独初始化业务头。比较稳妥的做法是加载权重时只加载 Backbone 部分即所有不以head.开头的 key业务头完全随机初始化。Swin 的 Backbone 特征质量足够好即使只是把 pool 后输出接到一个小 MLP 上也能在数据量不大的情况下快速收敛。不要为了“加载完整权重”而强行让形状对齐最后突然发现 head 没加载上。另一个容易被 ignore 的坑是如果你要用frozen_stages冻结部分层state_dict里冻结层的 buffer比如relative_position_index依然会参与model.state_dict()但不会有梯度。这时如果加载脚本写得不严谨可能会把训练模式产生的num_batches_tracked一起带进推理模型几乎无害但会让模型文件越来越脏。4.3 timm 版本漂移导致的数据增强行为和下载行为不一致官方训练代码中很多增强来自 timm而 timm 是一个快速演进的库。同样的RandAugment配置在 timm 0.4.x 和 0.9.x 之间不仅有 API 差异默认超参也可能变化。复现官方 ImageNet 实验时一个比较省心的办法是看官方环境里的timm0.4.12之类字样但不要只看 requirements 的宽松版本号最好把环境完整哈希锁下来。如果只在推理阶段用 Swin风险相对小因为推理只依赖模型定义里的timm.models.layers.DropPath等基础层这些层的 API 长期稳定。但如果你用timm.create_model(swin_tiny_patch4_window7_224, pretrainedTrue)走的是 timm 自己的权重而不是微软官方权重两者可能来自不同训练流程精度和预处理上的细微差异需要自己验证。我的做法是业务关键模型直接用官方权重或我们自己微调的权重timm 只作为代码参考和快速实验入口。4.4 混合精度切换apex 好跑但部署时不如原生 amp官方训练代码早期提供了 apex 的--amp-opt-level O1这种写法。问题是 apex 在部分新版本的 CUDA / PyTorch 环境下编译会遇到各种各样的问题有的机器甚至因为 kernel 版本不兼容直接无法安装。从工程治理角度我建议新项目直接走 PyTorch 原生torch.cuda.amp/torch.autocast不要在 2025 年还把一个研究代码里的 apex 依赖带进生产线。原因是 apex 不是 PyTorch 官方模块版本跟随策略、二进制分发策略和团队内部的容器构建策略常常冲突。切换混合精度的过程中最容易出的问题是LayerNorm和 attention softmax 在 FP16 下的数值稳定性。Swin 里LayerNorm一般保留 FP32PyTorch 原生 amp 会处理好自动白名单但在自定义实现中如果手动混用了 half 和 float可能出现 loss 不降或输出 NaN 的情况。建议在切换后跑一个固定种子的长训练小步验证不要只看推理阶段精度。4.5 checkpoint 重计算与 DDP 的相互作用如果训练环境里同时开启use_checkpointTrue和DistributedDataParallel要注意 checkpoint 函数的重计算逻辑会在反向传播时多执行一次前向这会让 forward 被调用两次。模型内的随机操作如果 handle 不好DDP 的广播随机种子和数据并行行为可能产生细微不一致。另外梯度检查点会明显降低训练吞吐因为重计算前向带来的额外开销不可忽略。官方仓库中use_checkpoint主要是为了在 Swin-Large 这种深度模型上把 batch size 撑大并不是免费的午餐。做工程排期时不要因为在 config 里把这个参数设为 true 就以为“显存降低且速度不变”实测往往速度下降 20% 到 40%。部署模型时我建议不仅要把use_checkpointFalse还要确认你的推理框架没有把这个参数误从训练 checkpoint 里恢复。如果模型一开始以 training mode 保存部署端直接eval()可能不够代码路径里仍然会走 checkpoint 逻辑只是不产生梯度。4.6 ONNX 导出的大小写与动态轴问题Swin 模型不是完全 ONNX-friendly 的网络。它内部有torch.roll、meshgrid、permute、动态 mask 等操作导出过程中不同 PyTorch 版本生成的算子版本差异很大。我的经验是先把输入尺寸固定比如(1, 3, 224, 224)导出成功后再考虑动态 batch不要一上来就开动态宽高。如果想支持动态宽高你要额外处理relative_position_indexbuffer 和 padding 逻辑。因为不同输入尺寸下padding 产生的 mask shape 会动态变化部分推理引擎无法在编译期推导出固定的注意力 mask会把部分计算退化成多个小算子性能反而下降。作为替代如果只做 GPU 推理很多场景可以直接用 TorchScript 的trace或直接用原模型加torch.inference_mode()省去 ONNX 转换的麻烦。只有当需要 TensorRT 或 CPU 推理优化时才需要认真评估导出的子图结构和算子兼容。5. 落地选型对照三条路线怎么选5.1 官方仓库直用适合算法研究不适合做产品骨架如果你的目标是理解 Swin 原理、跑通论文实验、在标准 ImageNet benchmark 上验证想法官方仓库是最合适的。它和论文对应关系最强相对位置索引、shift window mask 等核心实现都是忠实原文的。但如果你的目标是产品化比如把 Swin 作为线上图像理解服务的特征提取器官方仓库不是一个好的工程骨架。它没有模型版本管理、没有推理服务封装、没有高性能批处理工具、没有对输入输出格式的严格约束。直接把main.py训练逻辑复用过来做在线推理你会发现自己要补很多工程代码。我们会评估选型对象时常常问一句话我们引入的是“参考实现”还是“基础组件”官方仓库更接近前者它是所有后续工程化的“标准答案”需要有人去翻译成后者的形态。5.2 timm/mmpretrain适合作为服务内模型组件timm 是我个人最推荐的 Swin 落地中间层。它把官方模型实现做了统一封装提供一致的create_model、get_pretrained_cfg等接口。你不再需要关心相对位置索引初始化、to_2tuple这类琐碎细节模型定义和预训练权重管理都被 timm 标准化了。timm 的 Swin 模型命名也比较清晰比如swin_tiny_patch4_window7_224一眼能看出 patch size、window size 和输入分辨率。基于 timm 构建服务你可以省掉很多手写加载逻辑并且能比较方便地在不同 backbone 之间切换这对算法团队的实验效率很重要。风险在于timm 的模型实现有时候会为统一接口做出一定抽象和官方原版存在细节差异。如果业务和某个特定官方 checkpoint 强绑定还是要做一次 logits 级一致性测试。另外timm 自己维护了 pretrained 权重但部分权重 download 走的是 Hugging Face 或外部 URL内网部署时要提前缓存好权重文件。5.3 检测/分割场景从 mmdet/mmseg 接入的姿势如果业务不是图像分类而是物体检测、实例分割、语义分割最顺滑的路线是走 OpenMMLab 体系。Swin Backbone 在 mmdetection、mmsegmentation、mmpretrain 里都被广泛支持你可以通过 config 里的backbonedict(typeSwinTransformer)直接使用。这里必须认识到接 mmdetection 意味着你同时把整个 OpenMMLab 的抽象层引入项目注册机制、config 继承机制、环境版本要求都会成为你工程治理的一部分。OpenMMLab 的优点是算法覆盖全、文档多、社区活跃缺点是对 MMCV/MMEngine 版本很敏感版本不匹配会直接导致TypeError或者算子缺失。我的建议是检测分割项目不要直接依赖 GitHub master而是选择一个发布版或者一个确定 commit用内部镜像把mmcv、mmdet、mmseg和 Swin 相关的 config 文件一起锁住。这样既享受生态快捷性又不至于某次自动升级把 backbone 配置破坏掉。5.4 选型决策清单与评分项下面这张表是我实际评审时用的“Swin 落地路线决策表”可以按自己团队的现状给每个维度打分路线典型适用场景工程改造成本稳定性风险生态依赖官方仓库算法研究、论文复现、对比实验高需自行工程化官方低维护仓库变化慢timm、torchvisiontimm图像分类、embedding 抽取、迁移学习低接口统一中timm 演进快timm、Hugging Face 权重源mmdet/mmseg检测、分割、多任务中高版本绑定明显中OpenMMLab 版本更替频繁MMEngine、MMCV自行裁剪强定制化、高性能生产服务高需自己维护偏高后续同步官方困难自己收口如果你的团队算法能力较强但没有专职平台组我会建议优先选 timm 作为服务内组件如果要做检测/分割并且已有 OpenMMLab 经验可以继续用 mmdet/mmseg但一定锁版本如果只是复现论文直接官方仓库。6. 剥离成最小推理组件的一次实操记录6.1 剥掉 timm 依赖后保留的层有哪些我在一次内部推理服务改造中没有直接依赖官方仓库而是从官方源码里裁出了最小模型文件。目标很简单推理容器只需要 torch 和 torchvision不把 timm 等训练依赖全带进来。官方swin_transformer.py依赖了一些来自timm.layers的工具函数主要是DropPath、to_2tuple、trunc_normal_。我裁剪时把这些小函数直接本地化实现总共约 50 行代码。核心保留类为WindowAttentionSwinTransformerBlockPatchMergingBasicLayerPatchEmbedSwinTransformer这样做的理由是官方源码本身没有强依赖检测、分割模块只有 timm