File size: 2,826 Bytes
0d80452
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
#!/usr/bin/env python

import pytest

pytest.importorskip("datasets", reason="datasets is required (install lerobot[dataset])")

import torch  # noqa: E402

from lerobot.utils.collate import lerobot_collate_fn  # noqa: E402


def test_lerobot_collate_preserves_messages_and_drops_raw_language():
    batch = [
        {
            "index": torch.tensor(0),
            "messages": [{"role": "assistant", "content": "a"}],
            "message_streams": ["low_level"],
            "target_message_indices": [0],
            "language_persistent": [{"content": "raw"}],
            "language_events": [],
        },
        {
            "index": torch.tensor(1),
            "messages": [{"role": "assistant", "content": "b"}],
            "message_streams": ["low_level"],
            "target_message_indices": [0],
            "language_persistent": [{"content": "raw"}],
            "language_events": [],
        },
    ]

    out = lerobot_collate_fn(batch)

    assert out["index"].tolist() == [0, 1]
    assert out["messages"][0][0]["content"] == "a"
    assert out["messages"][1][0]["content"] == "b"
    assert out["message_streams"] == [["low_level"], ["low_level"]]
    assert out["target_message_indices"] == [[0], [0]]
    assert "language_persistent" not in out
    assert "language_events" not in out


def test_lerobot_collate_passes_through_standard_batch():
    """On a non-language batch, the collate must match ``default_collate``.

    Guards against silent regressions: ``lerobot_train.py`` only opts into
    ``lerobot_collate_fn`` when the dataset declares language columns, but
    if a future change ever wires it in unconditionally we want the
    behavior to remain a transparent pass-through for ordinary tensor
    batches.
    """
    from torch.utils.data._utils.collate import default_collate

    batch = [
        {
            "observation.image": torch.zeros(3, 4, 4),
            "action": torch.tensor([0.0, 1.0]),
            "index": torch.tensor(0),
        },
        {
            "observation.image": torch.ones(3, 4, 4),
            "action": torch.tensor([2.0, 3.0]),
            "index": torch.tensor(1),
        },
    ]

    custom = lerobot_collate_fn(batch)
    expected = default_collate(batch)

    assert custom.keys() == expected.keys()
    for key in expected:
        assert torch.equal(custom[key], expected[key]), f"key={key} diverged"


def test_lerobot_collate_drops_none_samples():
    """Recipes that yielded no target message return ``None`` — those samples
    must be filtered out, and an entirely-``None`` batch must collapse to ``None``.
    """
    batch = [None, {"index": torch.tensor(0)}, None]
    out = lerobot_collate_fn(batch)
    assert out is not None
    assert out["index"].tolist() == [0]

    assert lerobot_collate_fn([None, None]) is None