facebookresearch / facebookresearch/sam2

BUG: VideoPredictor produced different outputs with the same model weight

Open
#698 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Jupyter Notebook
Stars
19.9k
Forks
2.5k
PR merge metrics
No merged PRs in 30d

Description

I noticed that even if loading the same model weight, sam2's VideoPredictor could produce very different results.

The minimal code snippet to reproduce the bug:

In [sam2/notebooks/video_predictor_example.ipynb](https://github.com/facebookresearch/sam2/blob/main/notebooks/video_predictor_example.ipynb), add the following code block right above the section title "Initialize the inference state":

```python
ann_frame_idx = 0 # the frame index we interact with
ann_obj_id = 1 # give a unique id to each object we interact with (it can be any integers)
# Let's add a positive click at (x, y) = (210, 350) to get started
points = np.array([[210, 350]], dtype=np.float32)
# for labels, `1` means positive click and `0` means negative click
labels = np.array([1], np.int32)

def run_one_inference(predictor):
inference_state = predictor.init_state(video_path=video_dir)
predictor.reset_state(inference_state)
_, _, out_mask_logits = predictor.add_new_points_or_box(
inference_state=inference_state,
frame_idx=ann_frame_idx,
obj_id=ann_obj_id,
points=points,
labels=labels,
)
return out_mask_logits[0]

# silently run one inference
predictor1 = build_sam2_video_predictor(model_cfg, ckpt_path=None, device=device)
run_one_inference(predictor1) # comment out this line will make mask1 and mask2 identical
predictor1.load_state_dict(torch.load(sam2_checkpoint)['model'],strict=True)
mask1 = run_one_inference(predictor1)

predictor2 = build_sam2_video_predictor(model_cfg, ckpt_path=sam2_checkpoint, device=device)
mask2 = run_one_inference(predictor2)

print(torch.allclose(mask1,mask2))
```

In theory, mask1 and mask2 should be the same, as predictor1 and predictor2 loaded the same weight, but mask1 and mask2 are very different.

Image

I have used the following function to check whether predictor1 and predictor2 are the same, and they seem to be the same.
```python
import inspect
def diff_non_state_attributes(m1, m2, ignore_private=True):
"""
Compare all attributes of m1 and m2 that are not in state_dict.
This will help find persistent state or caches that differ.
"""
sd_keys = set(m1.state_dict().keys())

def collect_attrs(model):
attrs = {}
for name, val in inspect.getmembers(model):
# Ignore methods, modules, and state_dict entries
if ignore_private and name.startswith("_"):
continue
if callable(val) or isinstance(val, torch.nn.Module):
continue
if name in sd_keys:
continue
# We skip Tensors already in state_dict
attrs[name] = val
return attrs

attrs1 = collect_attrs(m1)
attrs2 = collect_attrs(m2)

diffs = []
for k in sorted(set(attrs1.keys()) | set(attrs2.keys())):
v1 = attrs1.get(k, "")
v2 = attrs2.get(k, "")
if isinstance(v1, torch.Tensor) and isinstance(v2, torch.Tensor):
if not torch.equal(v1, v2):
diffs.append((k, f"TENSOR diff, norm={torch.norm(v1 - v2).item():.3e}"))
else:
if v1 != v2:
diffs.append((k, f"{v1} != {v2}"))
return diffs

def diff_state(m1, m2, rtol=1e-5, atol=1e-7):
# Map to know which keys are params vs buffers
p1 = {k: v for k, v in m1.named_parameters()}
b1 = {k: v for k, v in m1.named_buffers()}

sd1 = m1.state_dict() # includes params + buffers
sd2 = m2.state_dict()

diffs = []
for k in sd1.keys():
if k not in sd2:
diffs.append((k, 'missing_in_m2', None))
continue
t1, t2 = sd1[k], sd2[k]
if t1.shape != t2.shape:
diffs.append((k, 'shape_mismatch', (t1.shape, t2.shape)))
continue
# allclose check
if not torch.allclose(t1, t2, rtol=rtol, atol=atol):
kind = 'param' if k in p1 else ('buffer' if k in b1 else 'unknown')
diffs.append((k, kind, torch.norm((t1 - t2).float()).item()))
return diffs

def pretty_print_diffs(diffs):
if not diffs:
print("No differences (within tolerance).")
return
print("Differences:")
for k, kind, info in diffs:
if info is None:
print(f" {k:50s} [{kind}]")
elif isinstance(info, tuple):
print(f" {k:50s} [{kind}] shape {info[0]} vs {info[1]}")
else:
print(f" {k:50s} [{kind}] ||Δ||₂ = {info:.3e}")

diffs = diff_state(predictor1,predictor2)
pretty_print_diffs(diffs)
diffs = diff_non_state_attributes(predictor1,predictor2,ignore_private=True)
if not diffs:
print("No non-state attributes differ.")
else:
print("Differences in non-state attributes:")
for k, info in diffs:
print(f" {k}: {info}")
```
I have no idea what happens, and have been stuck with this bug for one week.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.