AllenBi21 commited on
Commit
ea2769b
·
verified ·
1 Parent(s): 295ee70

screw_sitr5_ingraph_50k: in-graph fine-tuned SITR ACT (snapshot step 7200) + TS encoder export + code (sitr branch, bf16 flag, feature_policy in-graph loading)

Browse files
act_policy/policy.py CHANGED
@@ -16,6 +16,7 @@ from torch import Tensor, nn
16
 
17
  from .config import ACTConfig
18
  from .model import ACT, FlowMatchingHead
 
19
  from .tactile_fusion_branch import TactileFusionBranch
20
 
21
 
@@ -74,10 +75,15 @@ class ACTPolicy(nn.Module):
74
  self.tactile_norm = _Normalizer(config.tactile_dim)
75
  self.action_norm = _Normalizer(config.action_dim)
76
 
77
- # In-graph tactile-fusion encoder (joint-trained or frozen).
78
- self.tactile_branch: TactileFusionBranch | None = None
 
 
79
  if config.tactile_image_keys:
80
- self.tactile_branch = TactileFusionBranch(config)
 
 
 
81
 
82
  # ImageNet stats for the torchvision-pretrained ResNet backbone.
83
  self.register_buffer("image_mean", torch.tensor([0.485, 0.456, 0.406]).view(1, 3, 1, 1))
 
16
 
17
  from .config import ACTConfig
18
  from .model import ACT, FlowMatchingHead
19
+ from .sitr_branch import SITRBranch
20
  from .tactile_fusion_branch import TactileFusionBranch
21
 
22
 
 
75
  self.tactile_norm = _Normalizer(config.tactile_dim)
76
  self.action_norm = _Normalizer(config.action_dim)
77
 
78
+ # In-graph tactile encoder (joint-trained or frozen). Modality "sitr"
79
+ # gets the raw-single-frame SITR branch; anything else the
80
+ # tactile-fusion (HTT) branch.
81
+ self.tactile_branch: nn.Module | None = None
82
  if config.tactile_image_keys:
83
+ if config.tactile_encoder_modality == "sitr":
84
+ self.tactile_branch = SITRBranch(config)
85
+ else:
86
+ self.tactile_branch = TactileFusionBranch(config)
87
 
88
  # ImageNet stats for the torchvision-pretrained ResNet backbone.
89
  self.register_buffer("image_mean", torch.tensor([0.485, 0.456, 0.406]).view(1, 3, 1, 1))
act_policy/sitr_branch.py ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """In-graph SITR encoder branch for ACT.
2
+
3
+ Counterpart of ``TactileFusionBranch`` for ``tactile_encoder_modality="sitr"``.
4
+ SITR's released convention: raw single frames, no background subtraction, no
5
+ frame pairs. The per-episode bg tensor is still accepted (and ignored) so the
6
+ dataset/policy interface stays identical to the HTT in-graph path.
7
+
8
+ Preprocessing mirrors tools/encode_sitr.py: /255 -> replicate to 3 channels ->
9
+ bilinear 224x224 -> normalize with SITR's training stats -> forward_encoder,
10
+ cls token. With ``tactile_image_group="tactile_imgs"`` the frames arrive
11
+ pre-resized to 224x224 and the resize is a no-op.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import os
16
+ import sys
17
+
18
+ import torch
19
+ import torch.nn as nn
20
+ import torch.nn.functional as F
21
+
22
+ from .config import ACTConfig
23
+
24
+ GSRL_ROOT = os.environ.get("GSRL_ROOT", "/home/allenbi/projects_25/gsrl")
25
+ DEFAULT_SITR_CKPT = os.path.join(GSRL_ROOT, "checkpoints/SITR_B18.pth")
26
+
27
+ # SITR training normalization (gsrl/dataloaders.py), applied on [0,1] input.
28
+ SITR_MEAN = (-1.2223, -1.8114, -1.7090)
29
+ SITR_STD = (11.7932, 12.7956, 13.6452)
30
+
31
+
32
+ class SITRBranch(nn.Module):
33
+ """SITR_base (ViT-B/16) run per finger frame, cls-token readout.
34
+
35
+ Inputs at forward:
36
+ frames: (B, n_fingers, T, H, W) uint8 grayscale — only frames[:, :, -1]
37
+ (the current frame) is encoded; pass ``tactile_image_pair=1``.
38
+ bg: ignored (SITR consumes raw frames).
39
+
40
+ Output: (B, n_fingers * 768) float32 tactile vector.
41
+ """
42
+
43
+ def __init__(self, config: ACTConfig):
44
+ super().__init__()
45
+ if not config.tactile_image_keys:
46
+ raise ValueError("SITRBranch requires non-empty tactile_image_keys.")
47
+ if int(config.tactile_encoder_feature_dim) != 768:
48
+ raise ValueError(
49
+ "SITR_base emits 768-d cls features; set --tactile-encoder-feature-dim 768."
50
+ )
51
+ if GSRL_ROOT not in sys.path:
52
+ sys.path.insert(0, GSRL_ROOT)
53
+ from models.networks import SITR_base # noqa: E402
54
+
55
+ model = SITR_base(num_calibration=0) # calibration-free, as in encode_sitr.py
56
+ ckpt_path = config.tactile_encoder_ckpt or DEFAULT_SITR_CKPT
57
+ ckpt = torch.load(ckpt_path, map_location="cpu", weights_only=False)
58
+ state = ckpt.get("state_dict", ckpt) if isinstance(ckpt, dict) else ckpt
59
+ missing, unexpected = model.load_state_dict(state, strict=False)
60
+ print(f"[sitr-branch] loaded {ckpt_path} missing={len(missing)} unexpected={len(unexpected)}")
61
+ # forward_encoder never touches the decoder/contrastive heads; keep them
62
+ # out of the optimizer.
63
+ for p in model.decoder_pred.parameters():
64
+ p.requires_grad = False
65
+ for p in model.contrastive_head.parameters():
66
+ p.requires_grad = False
67
+ self.model = model
68
+
69
+ self.register_buffer("image_mean", torch.tensor(SITR_MEAN).view(1, 3, 1, 1))
70
+ self.register_buffer("image_std", torch.tensor(SITR_STD).view(1, 3, 1, 1))
71
+ self.target_hw = tuple(config.tactile_encoder_target_hw)
72
+ self.feature_dim = int(config.tactile_encoder_feature_dim)
73
+ self.n_fingers = len(config.tactile_image_keys)
74
+
75
+ if config.tactile_encoder_freeze:
76
+ for p in self.model.parameters():
77
+ p.requires_grad = False
78
+
79
+ def forward(self, frames: torch.Tensor, bg: torch.Tensor | None = None) -> torch.Tensor:
80
+ B, n_fingers, T, H, W = frames.shape
81
+ x = frames[:, :, -1] # (B, nF, H, W)
82
+ x = (x.float() if x.dtype == torch.uint8 else x) / 255.0
83
+ x = x.reshape(B * n_fingers, 1, H, W).expand(-1, 3, -1, -1)
84
+ if (H, W) != self.target_hw:
85
+ x = F.interpolate(x, size=self.target_hw, mode="bilinear", align_corners=False)
86
+ x = (x - self.image_mean) / self.image_std
87
+ f = self.model.forward_encoder(x, c=None)[:, 0, :] # cls (B*nF, 768)
88
+ return f.reshape(B, n_fingers * self.feature_dim)
qistee_integration/feature_policy.py CHANGED
@@ -75,7 +75,14 @@ def load_feature_policy(checkpoint: str, device: torch.device) -> FeaturePolicy:
75
  f"{FINGER_COUNT * FEATURE_DIM} — is this a T3/SITR checkpoint? "
76
  "(HTT in-graph checkpoints belong to htt_policy.py)")
77
  policy = FeaturePolicy(config)
78
- policy.load_state_dict(payload["model"], strict=True)
 
 
 
 
 
 
 
79
  return policy.to(device).eval()
80
 
81
 
 
75
  f"{FINGER_COUNT * FEATURE_DIM} — is this a T3/SITR checkpoint? "
76
  "(HTT in-graph checkpoints belong to htt_policy.py)")
77
  policy = FeaturePolicy(config)
78
+ # Feature checkpoints (screw_t3_50k / screw_sitr5_50k) have no tactile_branch.
79
+ # In-graph checkpoints (screw_sitr5_ingraph_50k) carry the fine-tuned encoder
80
+ # under tactile_branch.* — at deploy those weights live in the run's exported
81
+ # TorchScript encoder instead, so drop them here. Their tactile_norm was never
82
+ # initialized (identity), hence the same predict path is exact for both.
83
+ state = {k: v for k, v in payload["model"].items()
84
+ if not k.startswith("tactile_branch.")}
85
+ policy.load_state_dict(state, strict=True)
86
  return policy.to(device).eval()
87
 
88
 
screw_sitr5_ingraph_50k/DEPLOY.md ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # screw_sitr5_ingraph_50k — SITR fine-tuned IN-GRAPH ACT (screw task)
2
+
3
+ `2026-08-12` · **snapshot of a still-running training** — `act_best.pt` here is
4
+ the best-val checkpoint at global step 7,200 (train-val L1 0.2652, already below
5
+ every other SITR/HTT condition's all-time best). It will be replaced by the
6
+ final best when the run stops (patience 5 × 200 steps on val).
7
+
8
+ ## What it is
9
+
10
+ Same 50K protocol as the other `screw_*_50k` runs (bs=32, lr 2e-5 warmup 5K →
11
+ cosine 2e-6, chunk 30, CVAE, val = last 2 episodes), but the SITR encoder
12
+ (`SITR_base`, ViT-B/16, calibration-free) runs **inside the graph** on raw
13
+ single frames and is **fine-tuned jointly** (0.1× LR, i.e. 2e-6). Training
14
+ forward/backward ran under bf16 autocast; the checkpoint is ordinary fp32.
15
+ Naive/full setting: all 5 fingers, full 768-d cls per finger → 3840-d tactile.
16
+
17
+ Unlike the frozen-feature `screw_sitr5_50k`, the encoder weights here differ
18
+ from `SITR_B18.pth` — you MUST use this folder's TorchScript export, not
19
+ `exported/sitr_feature_encoder.ts.pt`.
20
+
21
+ ## Files
22
+
23
+ | File | Meaning |
24
+ |---|---|
25
+ | `act_best.pt` | full checkpoint (model incl. fine-tuned encoder + config + optimizer + scheduler) |
26
+ | `sitr_ingraph_feature_encoder.ts.pt` | TorchScript of the FINE-TUNED encoder; contract identical to `exported/*.ts.pt`: input (B,3,224,224) float in [0,1], SITR normalization baked in, output (B,768) cls |
27
+ | `args.json` | training invocation |
28
+
29
+ ## Robot loop (identical to the T3/SITR feature policies)
30
+
31
+ Raw frames only — NO background capture, NO frame pairs, finger order 0..4
32
+ (camera order, same as training):
33
+
34
+ ```python
35
+ from feature_policy import load_feature_policy, TSFeatureEncoder # repo ≥ this commit
36
+ policy = load_feature_policy("screw_sitr5_ingraph_50k/act_best.pt", device)
37
+ encoder = TSFeatureEncoder("screw_sitr5_ingraph_50k/sitr_ingraph_feature_encoder.ts.pt", device)
38
+ ...
39
+ feats = encoder(hand.raw_frame()) # (5,240,320) uint8 -> (1,3840)
40
+ chunk = policy.predict_action_chunk(qpos_t, feats)[0]
41
+ ```
42
+
43
+ Requires the updated `qistee_integration/feature_policy.py` (this commit): it
44
+ drops the `tactile_branch.*` weights when loading in-graph checkpoints (they
45
+ live in the TS encoder at deploy). This is exact, not approximate: in-graph
46
+ checkpoints never initialize `tactile_norm` (identity), and the split path was
47
+ validated bit-identical (feature maxdiff 0.0, action-chunk maxdiff 0.0 vs the
48
+ reference in-graph `ACTPolicy` on training frames).
49
+
50
+ Alternative (tactile_act repo on the machine): `ACTInference.from_checkpoint`
51
+ loads `act_best.pt` directly — needs `act_policy/sitr_branch.py` (this commit)
52
+ plus the gsrl repo for the `SITR_base` class definition and `SITR_B18.pth`
53
+ (constructor init only; weights are immediately overwritten from the ACT
54
+ checkpoint). The TorchScript path avoids the third-party dependency entirely.
55
+
56
+ ## Caveat carried over from the feature-SITR analysis
57
+
58
+ Fine-tuning does NOT add a reference-frame mechanism: the encoder still
59
+ consumes raw frames, so fingertip-camera appearance drift between the training
60
+ data and the rig shifts features with nothing to anchor them (HTT's bg-capture
61
+ anchors it; SITR-in-graph has no bg). Same deployment interpretation guardrails
62
+ as `screw_sitr5_50k` / `screw_t3_50k` (see FEATURE_PREPROCESSING.md).
screw_sitr5_ingraph_50k/act_best.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fe4b6e0fda53a53c8a2e1060bdc548edffbb8106304cd2a7da5aa5898d3dc937
3
+ size 1537168989
screw_sitr5_ingraph_50k/args.json ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "data": "./episodes/screwing",
3
+ "smoke": false,
4
+ "episode_glob": "episode_*.hdf5",
5
+ "state_dim": 22,
6
+ "action_dim": 22,
7
+ "image_keys": [],
8
+ "image_shape": [
9
+ 3,
10
+ 240,
11
+ 320
12
+ ],
13
+ "tactile_dim": 0,
14
+ "tactile_keys": [],
15
+ "tactile_image_keys": [
16
+ "raw_0",
17
+ "raw_1",
18
+ "raw_2",
19
+ "raw_3",
20
+ "raw_4"
21
+ ],
22
+ "tactile_image_pair": 1,
23
+ "tactile_image_group": "tactile_imgs",
24
+ "tactile_bg_group": "tactile_bg_224",
25
+ "tactile_encoder_modality": "sitr",
26
+ "tactile_encoder_ckpt": "/home/allenbi/projects_25/gsrl/checkpoints/SITR_B18.pth",
27
+ "tactile_encoder_pretrain_config": "/home/allenbi/projects_25/tactile_fusion/config/model/pretrain.yaml",
28
+ "tactile_encoder_feature_dim": 768,
29
+ "tactile_encoder_target_hw": [
30
+ 224,
31
+ 224
32
+ ],
33
+ "tactile_encoder_lr_scale": 0.1,
34
+ "freeze_tactile_encoder": false,
35
+ "state_history": 1,
36
+ "tactile_history": 1,
37
+ "delta_actions": false,
38
+ "state_indices": null,
39
+ "action_indices": null,
40
+ "chunk_size": 30,
41
+ "n_action_steps": 30,
42
+ "no_vae": false,
43
+ "use_flow_matching": false,
44
+ "fm_hidden_dim": 512,
45
+ "fm_n_mlp_layers": 3,
46
+ "fm_time_embed_dim": 128,
47
+ "fm_inference_steps": 10,
48
+ "lr": 2e-05,
49
+ "lr_backbone": 1e-05,
50
+ "weight_decay": 0.0001,
51
+ "lr_warmup_steps": 5000,
52
+ "lr_warmup_init": 1e-06,
53
+ "lr_cosine": true,
54
+ "lr_min": 2e-06,
55
+ "batch_size": 32,
56
+ "num_workers": 4,
57
+ "bf16": true,
58
+ "steps": 50000,
59
+ "log_every": 200,
60
+ "save_every": 10000,
61
+ "out": "./checkpoints",
62
+ "device": "cuda",
63
+ "resume": null,
64
+ "run_name": "screw_sitr5_ingraph_50k",
65
+ "tb_dir": "./runs",
66
+ "no_tb": false,
67
+ "no_stats": false,
68
+ "stats_batch_size": 32,
69
+ "stats_num_workers": 4,
70
+ "val_episodes": 2,
71
+ "val_every": 200,
72
+ "val_batches": 0,
73
+ "patience": 5
74
+ }
screw_sitr5_ingraph_50k/sitr_ingraph_feature_encoder.ts.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a9b05621e199620a49c63d6ceadd3d64cd7f3b19360af0f50f1f0a034b342f9e
3
+ size 346358387
train.py CHANGED
@@ -65,7 +65,9 @@ def parse_args() -> argparse.Namespace:
65
  help="HDF5 group for per-episode bg images. Use 'tactile_bg_224' alongside "
66
  "the resized tactile_imgs.")
67
  p.add_argument("--tactile-encoder-modality", type=str, default="9dtact",
68
- choices=["9dtact", "gsmini"], help="Tactile-fusion modality.")
 
 
69
  p.add_argument("--tactile-encoder-ckpt", type=str,
70
  default="/home/allenbi/projects_25/tactile_fusion/checkpoints/pretrain_joint_alignscratch/"
71
  "pretrain_multimodal_20260521_005341/checkpoint_step_34000.pth")
@@ -123,6 +125,9 @@ def parse_args() -> argparse.Namespace:
123
 
124
  p.add_argument("--batch-size", type=int, default=8)
125
  p.add_argument("--num-workers", type=int, default=4)
 
 
 
126
  p.add_argument("--steps", type=int, default=100_000)
127
  p.add_argument("--log-every", type=int, default=50)
128
  p.add_argument("--save-every", type=int, default=5000)
@@ -480,7 +485,9 @@ def main() -> None:
480
  if step >= args.steps or early_stop:
481
  break
482
  batch = move_to(batch, args.device)
483
- loss, info = policy(batch)
 
 
484
  optimizer.zero_grad(set_to_none=True)
485
  loss.backward()
486
  optimizer.step()
 
65
  help="HDF5 group for per-episode bg images. Use 'tactile_bg_224' alongside "
66
  "the resized tactile_imgs.")
67
  p.add_argument("--tactile-encoder-modality", type=str, default="9dtact",
68
+ choices=["9dtact", "gsmini", "sitr"],
69
+ help="In-graph tactile encoder: tactile-fusion modality, or 'sitr' "
70
+ "for the raw-single-frame SITR branch (SITR_base, 768-d cls).")
71
  p.add_argument("--tactile-encoder-ckpt", type=str,
72
  default="/home/allenbi/projects_25/tactile_fusion/checkpoints/pretrain_joint_alignscratch/"
73
  "pretrain_multimodal_20260521_005341/checkpoint_step_34000.pth")
 
125
 
126
  p.add_argument("--batch-size", type=int, default=8)
127
  p.add_argument("--num-workers", type=int, default=4)
128
+ p.add_argument("--bf16", action="store_true",
129
+ help="Run the training forward/backward under torch.autocast(bfloat16). "
130
+ "Weights, optimizer, checkpoints and validation stay fp32.")
131
  p.add_argument("--steps", type=int, default=100_000)
132
  p.add_argument("--log-every", type=int, default=50)
133
  p.add_argument("--save-every", type=int, default=5000)
 
485
  if step >= args.steps or early_stop:
486
  break
487
  batch = move_to(batch, args.device)
488
+ with torch.autocast(device_type="cuda" if "cuda" in args.device else "cpu",
489
+ dtype=torch.bfloat16, enabled=args.bf16):
490
+ loss, info = policy(batch)
491
  optimizer.zero_grad(set_to_none=True)
492
  loss.backward()
493
  optimizer.step()