Update final docs/porting_notes.md
Browse files- docs/porting_notes.md +125 -173
docs/porting_notes.md
CHANGED
|
@@ -1,173 +1,125 @@
|
|
| 1 |
-
#
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
|
| 11 |
-
|
|
| 12 |
-
|
|
| 13 |
-
|
|
| 14 |
-
|
|
| 15 |
-
|
|
| 16 |
-
|
|
| 17 |
-
|
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
##
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
##
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
- H:`torch.rand`×1
|
| 127 |
-
- 测试策略:从 PyTorch dump 随机序列 npz,两边读同一序列(仅测试,训练不改)。
|
| 128 |
-
|
| 129 |
-
### 来自 A 的 core 层结论(2026-07-26,M5' 必须遵守)
|
| 130 |
-
|
| 131 |
-
1. **EdgeLoss 的 RoIAlignRotated**:必须用
|
| 132 |
-
`ROIAlignRotated(49, spatial_scale, sampling_ratio, aligned=True, clockwise=True)`
|
| 133 |
-
(A 在 commit 312b3b7 加的 mmcv 语义参数;**默认参数是底座旧行为,禁止用默认值**)。
|
| 134 |
-
实测 JDet 原生 kernel 方向 = mmcv clockwise=True;out_size 7/49 与 mmcv rtol 1e-4 全过。
|
| 135 |
-
残留差异:aligned=True 时 mmcv 不做 1×1 最小 roi 钳制、JDet kernel 始终钳制(亚像素 roi 才受影响)。
|
| 136 |
-
2. **⚠️ nms_rotated 返回顺序**:jdet 的 keep 是**原始顺序**,mmcv 是**按分数降序**。
|
| 137 |
-
→ v3 head 的 `_predict_by_feat_single`(test_cfg: nms 后 `max_per_img=2000` 截断)
|
| 138 |
-
**必须先按 score 降序 sort 再截断**,否则截掉的是错误的框,mAP 静默劣化。
|
| 139 |
-
3. **LR 语义**:用 A 的 `LinearWarmupMultiStepLR`(与 mmengine 1440 点逐点相等);
|
| 140 |
-
注意 mmengine LinearLR 分母是 `end-begin-1`(第 499 iter 到顶),且 A 已把
|
| 141 |
-
scheduler.step 挪到 optimizer.step 之前(f(i) 对齐)。自己写任何调度相关代码别按 torch 直觉来。
|
| 142 |
-
4. GDLoss(A 修了 3 处底座语义差异)/ PSCCoder / box_iou_rotated 均已 parity 过,直接复用。
|
| 143 |
-
5. **core-stable(13:29)随附三条**:(a) jt 的 `.max()`/`.sum()` reduce 出 shape [1] 非标量,
|
| 144 |
-
`jt.stack` 两个会得 (2,1)——聚合标量用 `jt.concat`;(b) jittor 1.3.8.5 无
|
| 145 |
-
`meshgrid(indexing=...)`,手工 `x[None,:].expand(...)` 构造;(c) w==h 简并时 eigh
|
| 146 |
-
逐特征值梯度是子梯度(基底相关,与 torch 不逐位一致但 trace 一致)——parity 测试对
|
| 147 |
-
简并用例只比不变量(trace/行列式/loss 值),不比逐元素梯度。
|
| 148 |
-
6. `segment_anything()` 已在 A 的 loss 里 lazy import `jdet.models.sam`(我的签名)✓。
|
| 149 |
-
`RotatedSingleRoIExtractor` 落在 `models/roi_extractors/rotated_single_level.py`
|
| 150 |
-
(兼容 mmcv 旧参名 out_size/sample_num)。
|
| 151 |
-
7. targets/bids 的 JDet 侧约定:待 A 移植 v2 detector(其 M4)时定,COORD 通知后照抄。
|
| 152 |
-
|
| 153 |
-
### 已确认的良性点
|
| 154 |
-
|
| 155 |
-
- head 只 build `PSCCoder` + `DistanceAnglePointCoder`,无 assigner(铁律三已证实)
|
| 156 |
-
- MobileSAM 无 `nn.MultiheadAttention`(自定义 Attention,q/k/v 独立 Linear)→ 无 in_proj 拆包
|
| 157 |
-
- MobileSAM 的 timm 依赖仅 DropPath/to_2tuple/trunc_normal_,推理期均可平替
|
| 158 |
-
- TED 纯卷积,无自定义算子;`PixelShuffle(1)` 恒等(staging 已做防御性回退)
|
| 159 |
-
|
| 160 |
-
### epoch 开关清单(SetEpochInfoHook 注入,L2 测试点)
|
| 161 |
-
|
| 162 |
-
- `copy_paste_start_epoch=6`(D)、`edge_loss_start_epoch=6`(H L414)、
|
| 163 |
-
`joint_angle_start_epoch=1`(H L298:之前 angle detach)、
|
| 164 |
-
`label_assign_pseudo_label_switch_eopch=6`(D L403:切换 predict vs generate_pseudo_targets)
|
| 165 |
-
|
| 166 |
-
## ⚠️ 勘误(2026-07-26 16:40,来自 A 的实战 + B 审计确认)
|
| 167 |
-
|
| 168 |
-
**PLAN §8 的『`.detach()` → `.stop_grad()`』建议是错的。** jittor 的 `.stop_grad()`
|
| 169 |
-
是**就地打标记并返回自身**(非 torch detach 语义):把还要参与 loss 的变量本体标成
|
| 170 |
-
无梯度 → 前向全对、部分 loss 梯度静默变 0。要 detach 语义用 `.detach()`(返回新变量)
|
| 171 |
-
或 `.clone().stop_grad()`。仅对确定无其他梯度消费者的节点(转 numpy 前、buffer、
|
| 172 |
-
新建切片节点)可直接 stop_grad。B 侧 21 处使用已逐一审计安全(loss 梯度 parity
|
| 173 |
-
1.1e-5 佐证);A 侧修复 10 处误用(commit 1aa8ad8)。
|
|
|
|
| 1 |
+
# PyTorch to Jittor porting notes
|
| 2 |
+
|
| 3 |
+
This document records the implementation choices that are required for numeric
|
| 4 |
+
and gradient parity with the Point2RBox-v3 PyTorch reference.
|
| 5 |
+
|
| 6 |
+
## MobileSAM
|
| 7 |
+
|
| 8 |
+
The native Jittor implementation is in `python/jdet/models/sam/`:
|
| 9 |
+
|
| 10 |
+
| File | Component |
|
| 11 |
+
|---|---|
|
| 12 |
+
| `tiny_vit.py` | TinyViT image encoder, MBConv blocks and window attention |
|
| 13 |
+
| `prompt_encoder.py` | point/box/mask prompt encoding and random positional encoding |
|
| 14 |
+
| `transformer.py` | two-way transformer |
|
| 15 |
+
| `mask_decoder.py` | mask tokens, hypernetworks and IoU prediction |
|
| 16 |
+
| `sam.py` | preprocessing and mask postprocessing |
|
| 17 |
+
| `predictor.py` | `SamPredictor` and longest-side resize |
|
| 18 |
+
| `build.py` | `sam_model_registry['vit_t']` and converted weight loading |
|
| 19 |
+
|
| 20 |
+
The public API intentionally matches MobileSAM, so the loss code can use
|
| 21 |
+
`sam_model_registry['vit_t']` and `SamPredictor` without an adapter layer.
|
| 22 |
+
|
| 23 |
+
### Weight conversion
|
| 24 |
+
|
| 25 |
+
PyTorch state dictionaries are converted to a pickle mapping parameter names
|
| 26 |
+
to NumPy arrays:
|
| 27 |
+
|
| 28 |
+
```bash
|
| 29 |
+
python tools/convert_torch_weights.py mobile_sam.pt weights/mobile_sam.pkl
|
| 30 |
+
```
|
| 31 |
+
|
| 32 |
+
`num_batches_tracked` entries are ignored because Jittor BatchNorm does not use
|
| 33 |
+
them. TinyViT `Conv2d_BN` keeps the original `c` and `bn` child names so the
|
| 34 |
+
remaining keys load directly. The random positional-encoding matrix is a real
|
| 35 |
+
checkpoint value, not a disposable initialization buffer; loading code and
|
| 36 |
+
tests verify that it is restored.
|
| 37 |
+
|
| 38 |
+
TinyViT caches indexed attention biases in evaluation mode. The cache is
|
| 39 |
+
refreshed after loading weights, preventing stale random biases from surviving
|
| 40 |
+
checkpoint restoration.
|
| 41 |
+
|
| 42 |
+
### Tensor semantics
|
| 43 |
+
|
| 44 |
+
- PyTorch `transpose(a, b)` swaps two dimensions. Jittor's transpose API is
|
| 45 |
+
expressed as a full permutation in this port; all window partition/reverse
|
| 46 |
+
paths therefore use explicit `permute` orders.
|
| 47 |
+
- Point masking is written with `where` and mask multiplication instead of
|
| 48 |
+
in-place indexed assignment, preserving gradients.
|
| 49 |
+
- `repeat_interleave` in the mask decoder is implemented with explicit
|
| 50 |
+
reshape/expand/reshape operations.
|
| 51 |
+
- Predictor resizing uses PIL rather than torchvision and keeps the reference
|
| 52 |
+
longest-side coordinate transform.
|
| 53 |
+
|
| 54 |
+
The converted 439-tensor checkpoint reaches mask IoU 1.0000 against PyTorch in
|
| 55 |
+
CPU fp32 and at least 0.9982 on GPU in the repository tests.
|
| 56 |
+
|
| 57 |
+
## TED edge detector
|
| 58 |
+
|
| 59 |
+
TED is implemented in `python/jdet/models/edge/`. Its convolutional weights are
|
| 60 |
+
converted with:
|
| 61 |
+
|
| 62 |
+
```bash
|
| 63 |
+
python tools/convert_ted_weights.py ted.pth weights/ted.pkl
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
The output edge maps match the PyTorch implementation with maximum relative
|
| 67 |
+
error below 5.7e-6 in the validated CPU path.
|
| 68 |
+
|
| 69 |
+
## Jittor semantic differences
|
| 70 |
+
|
| 71 |
+
### Detach and in-place updates
|
| 72 |
+
|
| 73 |
+
`Var.stop_grad()` changes the variable itself; it is not equivalent to
|
| 74 |
+
PyTorch's `detach()`. Graph branches that require detached values use
|
| 75 |
+
`Var.detach()`. Loss-path mask updates are rebuilt out-of-place with `where` or
|
| 76 |
+
concatenation because indexed in-place writes can silently alter gradients.
|
| 77 |
+
|
| 78 |
+
### Batched linear algebra
|
| 79 |
+
|
| 80 |
+
Jittor's GPU `matmul` does not broadcast batch dimensions. Batch dimensions are
|
| 81 |
+
expanded explicitly before batched products. The covariance operations used by
|
| 82 |
+
Point2RBox are 2×2, so `python/jdet/ops/linalg2x2.py` provides closed-form
|
| 83 |
+
determinant, solve and symmetric eigendecomposition implementations without a
|
| 84 |
+
CuPy dependency.
|
| 85 |
+
|
| 86 |
+
Repeated eigenvalues do not define a unique eigenbasis. Degenerate tests compare
|
| 87 |
+
basis-independent quantities such as trace, determinant and reconstructed
|
| 88 |
+
matrices rather than individual eigenvectors.
|
| 89 |
+
|
| 90 |
+
### Reductions and grouping
|
| 91 |
+
|
| 92 |
+
Some Jittor reductions return shape `(1,)` rather than a scalar. Scalar lists
|
| 93 |
+
are concatenated instead of stacked. Instance grouping uses scatter reductions;
|
| 94 |
+
Python loops over tensors are avoided because they create one graph fragment per
|
| 95 |
+
element and can make dense batches stall.
|
| 96 |
+
|
| 97 |
+
### Normalization
|
| 98 |
+
|
| 99 |
+
Jittor's default GroupNorm computes variance as `E[x^2] - E[x]^2`. The port uses
|
| 100 |
+
the two-pass implementation in `python/jdet/models/utils/modules.py` to match
|
| 101 |
+
PyTorch and avoid cancellation. The detector explicitly redispatches
|
| 102 |
+
`backbone.train()` after Jittor's recursive mode switch so frozen stages and
|
| 103 |
+
`norm_eval=True` remain effective.
|
| 104 |
+
|
| 105 |
+
### Rotated geometry
|
| 106 |
+
|
| 107 |
+
- JDet's rotated RoIAlign angle direction matches mmcv
|
| 108 |
+
`clockwise=True`; Point2RBox uses `aligned=True` explicitly.
|
| 109 |
+
- JDet rotated NMS returns retained indices in input order. Prediction code sorts
|
| 110 |
+
by score before truncating to `max_per_img`.
|
| 111 |
+
- Rotated IoU polygon area is evaluated in centered coordinates. The shoelace
|
| 112 |
+
formula is translation invariant, while centering avoids false areas caused by
|
| 113 |
+
cancellation of large image-coordinate products.
|
| 114 |
+
- `torchvision.transforms.functional.resized_crop(..., antialias=True)` is
|
| 115 |
+
reproduced by explicit antialiased bilinear weights in the scale augmentation
|
| 116 |
+
path.
|
| 117 |
+
|
| 118 |
+
## Numeric validation
|
| 119 |
+
|
| 120 |
+
The repository stores fixed PyTorch goldens under `tests/parity/golden/`.
|
| 121 |
+
CPU fp32 checks use strict tolerances for losses and feature gradients. GPU
|
| 122 |
+
checks use looser aggregate tolerances because cuDNN algorithm selection and
|
| 123 |
+
TF32 can vary between processes even when the implementation is unchanged.
|
| 124 |
+
Config values, including the class-specific SAM mask filtering table, are
|
| 125 |
+
checked separately with zero tolerance.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|