Skip to content

Layout dm

Public LayoutDM conversion and inference APIs.

ConditionType

Bases: StrEnum

Canonical condition names used by layout generation interfaces.

Source code in lib/laygen/src/laygen/common/conditions.py
 9
10
11
12
13
14
15
16
17
18
19
20
21
class ConditionType(StrEnum):
    """Canonical condition names used by layout generation interfaces."""

    unconditional = auto()
    label = auto()
    label_size = auto()
    completion = auto()
    refinement = auto()
    text = auto()
    content_image = auto()
    relation = auto()
    hierarchical = auto()
    retrieval = auto()

LayoutDMCondition dataclass

Strong and weak token constraints for conditional LayoutDM sampling.

Source code in models/layout-dm/src/layout_dm/conditioning.py
16
17
18
19
20
21
22
23
24
25
26
@dataclass
class LayoutDMCondition:
    """Strong and weak token constraints for conditional LayoutDM sampling."""

    input_ids: Int[torch.Tensor, "batch tokens"]
    mask: Bool[torch.Tensor, "batch tokens"]
    type: Literal["c", "cwh", "partial", "refinement"]
    num_element: Int[torch.Tensor, "batch"] | None = None
    original_input_ids: Int[torch.Tensor, "batch tokens"] | None = None
    weak_mask: Bool[torch.Tensor, "batch tokens"] | None = None
    weak_logits: Float[torch.Tensor, "batch tokens vocab"] | None = None

LayoutDMConfig

Bases: ConfigMixin

Serializable LayoutDM architecture and tokenizer configuration.

Parameters:

Name Type Description Default
dataset_name DatasetName | str

Dataset name or alias used to initialize labels.

required
id2label dict[int | str, str] | None

Optional persisted label-id mapping.

None
max_seq_length int

Maximum number of layout elements.

25
num_bin_bboxes int

Number of bins per bounding-box attribute.

32
var_order str

Per-element token order.

'c-x-y-w-h'
shared_bbox_vocab str

Bounding-box vocabulary sharing mode.

'x-y-w-h'
bbox_quantization str

Bounding-box quantization mode.

'kmeans'
special_tokens tuple[str, ...]

Special token names. mask must be last for LayoutDM.

('pad', 'mask')
cluster_centers dict[str, list[float]] | None

Optional bbox cluster centers stored with tokenizer files.

None
cluster_centers_path str | None

Optional local path to released cluster centers.

None
hidden_size int

Transformer hidden size.

464
num_attention_heads int

Number of attention heads.

8
num_hidden_layers int

Number of transformer layers.

4
intermediate_size int

Feed-forward hidden size.

1856
dropout float

Transformer dropout probability.

0.0
timestep_type str | None

Timestep-conditioning type.

'adalayernorm'
num_timesteps int

Number of diffusion timesteps.

100
q_type str

Diffusion transition type.

'constrained'
att_1 float

Initial keep probability schedule value.

0.99999
att_T float

Final keep probability schedule value.

9e-06
ctt_1 float

Initial mask probability schedule value.

9e-06
ctt_T float

Final mask probability schedule value.

0.99999

Examples:

>>> cfg = LayoutDMConfig(dataset_name="publaynet")
>>> cfg.vocab_size > cfg.num_categories
True
Source code in models/layout-dm/src/layout_dm/configuration_layout_dm.py
 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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
class LayoutDMConfig(ConfigMixin):
    """Serializable LayoutDM architecture and tokenizer configuration.

    Args:
        dataset_name: Dataset name or alias used to initialize labels.
        id2label: Optional persisted label-id mapping.
        max_seq_length: Maximum number of layout elements.
        num_bin_bboxes: Number of bins per bounding-box attribute.
        var_order: Per-element token order.
        shared_bbox_vocab: Bounding-box vocabulary sharing mode.
        bbox_quantization: Bounding-box quantization mode.
        special_tokens: Special token names. ``mask`` must be last for LayoutDM.
        cluster_centers: Optional bbox cluster centers stored with tokenizer files.
        cluster_centers_path: Optional local path to released cluster centers.
        hidden_size: Transformer hidden size.
        num_attention_heads: Number of attention heads.
        num_hidden_layers: Number of transformer layers.
        intermediate_size: Feed-forward hidden size.
        dropout: Transformer dropout probability.
        timestep_type: Timestep-conditioning type.
        num_timesteps: Number of diffusion timesteps.
        q_type: Diffusion transition type.
        att_1: Initial keep probability schedule value.
        att_T: Final keep probability schedule value.
        ctt_1: Initial mask probability schedule value.
        ctt_T: Final mask probability schedule value.

    Examples:
        >>> cfg = LayoutDMConfig(dataset_name="publaynet")
        >>> cfg.vocab_size > cfg.num_categories
        True
    """

    config_name = "layout_dm_config.json"

    @register_to_config
    def __init__(
        self,
        *,
        dataset_name: DatasetName | str,
        id2label: dict[int | str, str] | None = None,
        max_seq_length: int = 25,
        num_bin_bboxes: int = 32,
        var_order: str = "c-x-y-w-h",
        shared_bbox_vocab: str = "x-y-w-h",
        bbox_quantization: str = "kmeans",
        special_tokens: tuple[str, ...] = ("pad", "mask"),
        cluster_centers: dict[str, list[float]] | None = None,
        cluster_centers_path: str | None = None,
        hidden_size: int = 464,
        num_attention_heads: int = 8,
        num_hidden_layers: int = 4,
        intermediate_size: int = 1856,
        dropout: float = 0.0,
        timestep_type: str | None = "adalayernorm",
        num_timesteps: int = 100,
        q_type: str = "constrained",
        att_1: float = 0.99999,
        att_T: float = 0.000009,
        ctt_1: float = 0.000009,
        ctt_T: float = 0.99999,
    ) -> None:
        """Initialize a serializable LayoutDM configuration."""
        self.dataset_name = str(normalize_dataset_name(dataset_name))
        raw_id2label = id2label or id2label_for_dataset(self.dataset_name)
        self.id2label = {int(k): v for k, v in raw_id2label.items()}

        self.max_seq_length = max_seq_length
        self.num_bin_bboxes = num_bin_bboxes
        self.var_order = var_order
        self.shared_bbox_vocab = shared_bbox_vocab
        self.bbox_quantization = bbox_quantization
        self.special_tokens = tuple(special_tokens)
        self.cluster_centers = cluster_centers
        self.cluster_centers_path = cluster_centers_path

        self.hidden_size = hidden_size
        self.num_attention_heads = num_attention_heads
        self.num_hidden_layers = num_hidden_layers
        self.intermediate_size = intermediate_size
        self.dropout = dropout
        self.timestep_type = timestep_type
        self.num_timesteps = num_timesteps
        self.q_type = q_type

        self.att_1 = att_1
        self.att_T = att_T
        self.ctt_1 = ctt_1
        self.ctt_T = ctt_T

    @property
    def label2id(self) -> dict[str, int]:
        """Return the inverse label-name to id mapping."""
        return {v: k for k, v in self.id2label.items()}

    @property
    def num_categories(self) -> int:
        """Return the number of dataset categories."""
        return len(self.id2label)

    @property
    def num_bbox_tokens(self) -> int:
        """Return the number of bounding-box vocabulary tokens."""
        return self.num_bin_bboxes * len(self.shared_bbox_vocab.split("-"))

    @property
    def num_special_tokens(self) -> int:
        """Return the number of special tokens."""
        return len(self.special_tokens)

    @property
    def vocab_size(self) -> int:
        """Return the full tokenizer vocabulary size."""
        return self.num_categories + self.num_bbox_tokens + self.num_special_tokens

    @property
    def pad_token_id(self) -> int:
        """Return the full vocabulary id of the padding token."""
        return (
            self.num_categories
            + self.num_bbox_tokens
            + self.special_tokens.index("pad")
        )

    @property
    def mask_token_id(self) -> int:
        """Return the full vocabulary id of the mask token."""
        return (
            self.num_categories
            + self.num_bbox_tokens
            + self.special_tokens.index("mask")
        )

    @property
    def num_attributes_per_element(self) -> int:
        """Return the number of tokens used for each layout element."""
        return len(self.var_order.split("-"))

    @property
    def max_token_length(self) -> int:
        """Return the flattened token sequence length."""
        return self.max_seq_length * self.num_attributes_per_element

    @property
    def bbox_slices(self) -> dict[str, tuple[int, int]]:
        """Return full-vocabulary slices for bbox attributes."""
        slices: dict[str, tuple[int, int]] = {}
        for i, key in enumerate(("x", "y", "w", "h")):
            start = self.num_categories + i * self.num_bin_bboxes
            slices[key] = (start, start + self.num_bin_bboxes)
        return slices

label2id property

label2id: dict[str, int]

Return the inverse label-name to id mapping.

num_categories property

num_categories: int

Return the number of dataset categories.

num_bbox_tokens property

num_bbox_tokens: int

Return the number of bounding-box vocabulary tokens.

num_special_tokens property

num_special_tokens: int

Return the number of special tokens.

vocab_size property

vocab_size: int

Return the full tokenizer vocabulary size.

pad_token_id property

pad_token_id: int

Return the full vocabulary id of the padding token.

mask_token_id property

mask_token_id: int

Return the full vocabulary id of the mask token.

num_attributes_per_element property

num_attributes_per_element: int

Return the number of tokens used for each layout element.

max_token_length property

max_token_length: int

Return the flattened token sequence length.

bbox_slices property

bbox_slices: dict[str, tuple[int, int]]

Return full-vocabulary slices for bbox attributes.

__init__

__init__(
    *,
    dataset_name: DatasetName | str,
    id2label: dict[int | str, str] | None = None,
    max_seq_length: int = 25,
    num_bin_bboxes: int = 32,
    var_order: str = "c-x-y-w-h",
    shared_bbox_vocab: str = "x-y-w-h",
    bbox_quantization: str = "kmeans",
    special_tokens: tuple[str, ...] = ("pad", "mask"),
    cluster_centers: dict[str, list[float]] | None = None,
    cluster_centers_path: str | None = None,
    hidden_size: int = 464,
    num_attention_heads: int = 8,
    num_hidden_layers: int = 4,
    intermediate_size: int = 1856,
    dropout: float = 0.0,
    timestep_type: str | None = "adalayernorm",
    num_timesteps: int = 100,
    q_type: str = "constrained",
    att_1: float = 0.99999,
    att_T: float = 9e-06,
    ctt_1: float = 9e-06,
    ctt_T: float = 0.99999,
) -> None

Initialize a serializable LayoutDM configuration.

Source code in models/layout-dm/src/layout_dm/configuration_layout_dm.py
 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
@register_to_config
def __init__(
    self,
    *,
    dataset_name: DatasetName | str,
    id2label: dict[int | str, str] | None = None,
    max_seq_length: int = 25,
    num_bin_bboxes: int = 32,
    var_order: str = "c-x-y-w-h",
    shared_bbox_vocab: str = "x-y-w-h",
    bbox_quantization: str = "kmeans",
    special_tokens: tuple[str, ...] = ("pad", "mask"),
    cluster_centers: dict[str, list[float]] | None = None,
    cluster_centers_path: str | None = None,
    hidden_size: int = 464,
    num_attention_heads: int = 8,
    num_hidden_layers: int = 4,
    intermediate_size: int = 1856,
    dropout: float = 0.0,
    timestep_type: str | None = "adalayernorm",
    num_timesteps: int = 100,
    q_type: str = "constrained",
    att_1: float = 0.99999,
    att_T: float = 0.000009,
    ctt_1: float = 0.000009,
    ctt_T: float = 0.99999,
) -> None:
    """Initialize a serializable LayoutDM configuration."""
    self.dataset_name = str(normalize_dataset_name(dataset_name))
    raw_id2label = id2label or id2label_for_dataset(self.dataset_name)
    self.id2label = {int(k): v for k, v in raw_id2label.items()}

    self.max_seq_length = max_seq_length
    self.num_bin_bboxes = num_bin_bboxes
    self.var_order = var_order
    self.shared_bbox_vocab = shared_bbox_vocab
    self.bbox_quantization = bbox_quantization
    self.special_tokens = tuple(special_tokens)
    self.cluster_centers = cluster_centers
    self.cluster_centers_path = cluster_centers_path

    self.hidden_size = hidden_size
    self.num_attention_heads = num_attention_heads
    self.num_hidden_layers = num_hidden_layers
    self.intermediate_size = intermediate_size
    self.dropout = dropout
    self.timestep_type = timestep_type
    self.num_timesteps = num_timesteps
    self.q_type = q_type

    self.att_1 = att_1
    self.att_T = att_T
    self.ctt_1 = ctt_1
    self.ctt_T = ctt_T

LayoutDMDenoiser

Bases: ModelMixin, ConfigMixin

Diffusers-compatible LayoutDM denoiser.

Parameters:

Name Type Description Default
vocab_size int

Size of the LayoutDM tokenizer vocabulary.

required
max_token_length int

Flattened token sequence length.

required
hidden_size int

Transformer hidden size.

464
num_attention_heads int

Number of attention heads.

8
num_hidden_layers int

Number of transformer layers.

4
intermediate_size int

Feed-forward hidden size.

1856
dropout float

Dropout probability.

0.0
timestep_type Literal['adalayernorm', 'adalayernorm_abs'] | None

Timestep-conditioning type.

'adalayernorm'

Examples:

>>> model = LayoutDMDenoiser(vocab_size=10, max_token_length=5, hidden_size=8,
...     num_attention_heads=2, num_hidden_layers=1, intermediate_size=16)
>>> model.config.vocab_size
10
Source code in models/layout-dm/src/layout_dm/modeling_layout_dm.py
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
class LayoutDMDenoiser(ModelMixin, ConfigMixin):
    """Diffusers-compatible LayoutDM denoiser.

    Args:
        vocab_size: Size of the LayoutDM tokenizer vocabulary.
        max_token_length: Flattened token sequence length.
        hidden_size: Transformer hidden size.
        num_attention_heads: Number of attention heads.
        num_hidden_layers: Number of transformer layers.
        intermediate_size: Feed-forward hidden size.
        dropout: Dropout probability.
        timestep_type: Timestep-conditioning type.

    Examples:
        >>> model = LayoutDMDenoiser(vocab_size=10, max_token_length=5, hidden_size=8,
        ...     num_attention_heads=2, num_hidden_layers=1, intermediate_size=16)
        >>> model.config.vocab_size
        10
    """

    config_name = "denoiser_config.json"

    @register_to_config
    def __init__(
        self,
        *,
        vocab_size: int,
        max_token_length: int,
        hidden_size: int = 464,
        num_attention_heads: int = 8,
        num_hidden_layers: int = 4,
        intermediate_size: int = 1856,
        dropout: float = 0.0,
        timestep_type: Literal["adalayernorm", "adalayernorm_abs"]
        | None = "adalayernorm",
    ) -> None:
        """Initialize the categorical transformer denoiser."""
        super().__init__()
        self.transformer = CategoricalTransformer(
            vocab_size=vocab_size,
            max_token_length=max_token_length,
            hidden_size=hidden_size,
            num_attention_heads=num_attention_heads,
            num_hidden_layers=num_hidden_layers,
            intermediate_size=intermediate_size,
            dropout=dropout,
            timestep_type=timestep_type,
        )
        self.apply(_init_layoutdm_weights)

    def forward(
        self,
        input_ids: Int[torch.Tensor, "batch tokens"],
        timesteps: Int[torch.Tensor, "batch"],
    ) -> LayoutDMDenoiserOutput:
        """Predict token logits for noised LayoutDM sequences."""
        return LayoutDMDenoiserOutput(
            logits=self.transformer(input_ids, timestep=timesteps)["logits"]
        )

    def predict_start_log_probs(
        self,
        input_ids: Int[torch.Tensor, "batch tokens"],
        timesteps: Int[torch.Tensor, "batch"],
    ) -> Float[torch.Tensor, "batch tokens vocab"]:
        """Predict log probabilities for the denoised start sequence."""
        logits = self(input_ids=input_ids, timesteps=timesteps).logits[:, :, :-1]
        log_pred = F.log_softmax(logits.double(), dim=-1).float()
        zero_mask = torch.full(
            (*log_pred.shape[:2], 1),
            -70.0,
            device=log_pred.device,
            dtype=log_pred.dtype,
        )
        return torch.cat((log_pred, zero_mask), dim=-1).clamp(-70.0, 0.0)

__init__

__init__(
    *,
    vocab_size: int,
    max_token_length: int,
    hidden_size: int = 464,
    num_attention_heads: int = 8,
    num_hidden_layers: int = 4,
    intermediate_size: int = 1856,
    dropout: float = 0.0,
    timestep_type: Literal[
        "adalayernorm", "adalayernorm_abs"
    ]
    | None = "adalayernorm",
) -> None

Initialize the categorical transformer denoiser.

Source code in models/layout-dm/src/layout_dm/modeling_layout_dm.py
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
@register_to_config
def __init__(
    self,
    *,
    vocab_size: int,
    max_token_length: int,
    hidden_size: int = 464,
    num_attention_heads: int = 8,
    num_hidden_layers: int = 4,
    intermediate_size: int = 1856,
    dropout: float = 0.0,
    timestep_type: Literal["adalayernorm", "adalayernorm_abs"]
    | None = "adalayernorm",
) -> None:
    """Initialize the categorical transformer denoiser."""
    super().__init__()
    self.transformer = CategoricalTransformer(
        vocab_size=vocab_size,
        max_token_length=max_token_length,
        hidden_size=hidden_size,
        num_attention_heads=num_attention_heads,
        num_hidden_layers=num_hidden_layers,
        intermediate_size=intermediate_size,
        dropout=dropout,
        timestep_type=timestep_type,
    )
    self.apply(_init_layoutdm_weights)

forward

forward(
    input_ids: Int[Tensor, "batch tokens"],
    timesteps: Int[Tensor, "batch"],
) -> LayoutDMDenoiserOutput

Predict token logits for noised LayoutDM sequences.

Source code in models/layout-dm/src/layout_dm/modeling_layout_dm.py
177
178
179
180
181
182
183
184
185
def forward(
    self,
    input_ids: Int[torch.Tensor, "batch tokens"],
    timesteps: Int[torch.Tensor, "batch"],
) -> LayoutDMDenoiserOutput:
    """Predict token logits for noised LayoutDM sequences."""
    return LayoutDMDenoiserOutput(
        logits=self.transformer(input_ids, timestep=timesteps)["logits"]
    )

predict_start_log_probs

predict_start_log_probs(
    input_ids: Int[Tensor, "batch tokens"],
    timesteps: Int[Tensor, "batch"],
) -> Float[torch.Tensor, "batch tokens vocab"]

Predict log probabilities for the denoised start sequence.

Source code in models/layout-dm/src/layout_dm/modeling_layout_dm.py
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
def predict_start_log_probs(
    self,
    input_ids: Int[torch.Tensor, "batch tokens"],
    timesteps: Int[torch.Tensor, "batch"],
) -> Float[torch.Tensor, "batch tokens vocab"]:
    """Predict log probabilities for the denoised start sequence."""
    logits = self(input_ids=input_ids, timesteps=timesteps).logits[:, :, :-1]
    log_pred = F.log_softmax(logits.double(), dim=-1).float()
    zero_mask = torch.full(
        (*log_pred.shape[:2], 1),
        -70.0,
        device=log_pred.device,
        dtype=log_pred.dtype,
    )
    return torch.cat((log_pred, zero_mask), dim=-1).clamp(-70.0, 0.0)

LayoutDMDenoiserOutput dataclass

Bases: BaseOutput

Denoiser output containing token logits.

Source code in models/layout-dm/src/layout_dm/modeling_layout_dm.py
120
121
122
123
124
@dataclass
class LayoutDMDenoiserOutput(BaseOutput):
    """Denoiser output containing token logits."""

    logits: Float[torch.Tensor, "batch tokens vocab"]

LayoutDMPipeline

Bases: DiffusionPipeline

Generate layouts with a converted LayoutDM denoiser and scheduler.

Parameters:

Name Type Description Default
denoiser LayoutDMDenoiser

LayoutDM denoiser model.

required
scheduler LayoutDMScheduler

Discrete diffusion scheduler.

required
tokenizer LayoutDMTokenizer

Structured layout tokenizer.

required
processor LayoutDMProcessor | None

Optional input processor. A default processor is created when omitted.

None

Examples:

>>> from collections.abc import Mapping, Sequence

from pathlib import Path >>> path = Path(".cache/layout-dm/converted/layoutdm-rico25") >>> path.exists() # doctest: +SKIP True >>> pipe = LayoutDMPipeline.from_pretrained(path) # doctest: +SKIP >>> out = pipe(batch_size=1, seed=0, num_inference_steps=1) # doctest: +SKIP >>> out.bbox.shape[-1] # doctest: +SKIP 4

Source code in models/layout-dm/src/layout_dm/pipeline_layout_dm.py
 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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
class LayoutDMPipeline(DiffusionPipeline):
    """Generate layouts with a converted LayoutDM denoiser and scheduler.

    Args:
        denoiser: LayoutDM denoiser model.
        scheduler: Discrete diffusion scheduler.
        tokenizer: Structured layout tokenizer.
        processor: Optional input processor. A default processor is created
            when omitted.

    Examples:
        >>> from collections.abc import Mapping, Sequence
    from pathlib import Path
        >>> path = Path(".cache/layout-dm/converted/layoutdm-rico25")
        >>> path.exists()  # doctest: +SKIP
        True
        >>> pipe = LayoutDMPipeline.from_pretrained(path)  # doctest: +SKIP
        >>> out = pipe(batch_size=1, seed=0, num_inference_steps=1)  # doctest: +SKIP
        >>> out.bbox.shape[-1]  # doctest: +SKIP
        4
    """

    model_cpu_offload_seq = "denoiser"

    def __init__(
        self,
        denoiser: LayoutDMDenoiser,
        scheduler: LayoutDMScheduler,
        tokenizer: LayoutDMTokenizer,
        processor: LayoutDMProcessor | None = None,
    ) -> None:
        """Initialize and register LayoutDM pipeline modules."""
        super().__init__()
        self.register_modules(
            denoiser=denoiser, scheduler=scheduler, tokenizer=tokenizer
        )
        self.tokenizer = tokenizer
        self.processor = processor or LayoutDMProcessor(tokenizer)
        self.denoiser.eval()

    @torch.no_grad()
    def __call__(
        self,
        *,
        batch_size: int = 1,
        seed: int | None = None,
        generator: torch.Generator | None = None,
        condition_type: ConditionType | str = ConditionType.unconditional,
        labels: Int[torch.Tensor, "batch elements"]
        | Int[np.ndarray, "batch elements"]
        | Sequence[ArrayLikeInput]
        | None = None,
        bbox: Float[torch.Tensor, "batch elements 4"]
        | Float[np.ndarray, "batch elements 4"]
        | Sequence[ArrayLikeInput]
        | None = None,
        mask: Bool[torch.Tensor, "batch elements"]
        | Bool[np.ndarray, "batch elements"]
        | Sequence[ArrayLikeInput]
        | None = None,
        num_elements: int | list[int] | Int[torch.Tensor, "batch"] | None = None,
        box_format: BoxFormat | str = BoxFormat.xywh,
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
        num_inference_steps: int | None = None,
        sampling: SamplingMode | str = SamplingMode.random,
        temperature: float = 1.0,
        top_k: int = 5,
        top_p: float = 0.9,
        output_type: Literal["dataclass", "dict"] = "dataclass",
        return_intermediates: bool = False,
        **model_kwargs: str | int | float | bool | None,
    ) -> LayoutGenerationOutput | dict[str, Shaped[torch.Tensor, "..."]]:
        """Run unconditional or conditional layout generation.

        Args:
            batch_size: Number of layouts generated for unconditional sampling.
            seed: Optional seed used only when ``generator`` is omitted.
            generator: Optional torch generator. Takes precedence over ``seed``.
            condition_type: Canonical condition type or supported source alias.
            labels: Optional labels used by conditional modes.
            bbox: Optional boxes used by conditional modes.
            mask: Optional valid-element mask for conditional inputs.
            num_elements: Reserved compatibility argument.
            box_format: Format of conditional input boxes.
            normalized: Whether conditional boxes are already normalized.
            canvas_size: Pixel canvas size used when ``normalized=False``.
            num_inference_steps: Optional shortened diffusion step count.
            sampling: Sampling strategy.
            temperature: Random sampling temperature.
            top_k: Top-k value for top-k modes.
            top_p: Top-p value for top-p modes.
            output_type: ``"dataclass"`` or ``"dict"``.
            return_intermediates: Whether to return sampling trajectory data.
            **model_kwargs: Reserved compatibility keyword arguments.

        Returns:
            ``LayoutGenerationOutput`` by default, or a dictionary when
            ``output_type="dict"``.

        Raises:
            ValueError: If a conditional mode is missing ``bbox`` or ``labels``,
                or if ``output_type`` is unsupported.
        """
        _ = (num_elements, model_kwargs)
        if generator is None and seed is not None:
            generator = torch.Generator(device=self.device).manual_seed(seed)
        canonical = normalize_condition_type(condition_type)
        condition = None
        if canonical is not ConditionType.unconditional:
            missing_inputs = [
                name
                for name, value in (("bbox", bbox), ("labels", labels))
                if value is None
            ]
            if missing_inputs:
                message = (
                    f"bbox and labels are required for condition_type={condition_type}"
                )
                raise ValueError(message)

            processor_inputs = {
                "bbox": bbox,
                "labels": labels,
                "mask": mask,
                "box_format": box_format,
                "normalized": normalized,
                "canvas_size": canvas_size,
            }
            processed = self.processor(**processor_inputs)
            decoded_input = self.tokenizer.decode_layout(processed["input_ids"])
            condition = build_condition(
                self.tokenizer,
                cond_type=canonical,
                bbox=decoded_input["bbox"],
                labels=decoded_input["labels"],
                mask=decoded_input["mask"],
            )
            batch_size = condition.input_ids.shape[0]
        sampling_config = LayoutDMSamplingConfig(
            name=sampling,
            temperature=temperature,
            top_k=top_k,
            top_p=top_p,
            num_inference_steps=num_inference_steps,
        )
        self.scheduler.set_timesteps(num_inference_steps, device=self.device)
        sample = self.scheduler.initial_sample(
            batch_size,
            self.tokenizer.config.max_token_length,
            device=self.device,
            condition=condition,
        )
        trajectory = [] if return_intermediates else None
        previous_timestep = self.scheduler.config.num_timesteps
        for timestep in self.scheduler.timesteps:
            timestep_batch = torch.full(
                (batch_size,),
                int(timestep.item()),
                device=self.device,
                dtype=torch.long,
            )
            input_ids = log_onehot_to_index(sample)
            logits = self.denoiser(input_ids=input_ids, timesteps=timestep_batch).logits
            out = self.scheduler.step(
                logits,
                timestep_batch,
                sample,
                previous_timestep=previous_timestep,
                sampling=sampling_config,
                condition=condition,
                generator=generator,
            )
            sample = out.prev_sample
            previous_timestep = int(timestep.item())
            if trajectory is not None:
                trajectory.append(log_onehot_to_index(sample).detach().cpu())
        sequences = log_onehot_to_index(sample).detach().cpu()
        decoded = self.tokenizer.decode_layout(sequences)
        output = LayoutGenerationOutput(
            bbox=decoded["bbox"],
            labels=decoded["labels"],
            mask=decoded["mask"],
            id2label=self.tokenizer.config.id2label,
            sequences=sequences,
            trajectory=trajectory,
            intermediates={"condition_type": str(canonical)}
            if return_intermediates
            else None,
        )
        if output_type == "dict":
            return dict(output)
        if output_type != "dataclass":
            raise ValueError(f"Unsupported output_type: {output_type}")

        return output

    generate = __call__

    def save_pretrained(
        self, save_directory: str | Path, **kwargs: LayoutDMPipelineKwarg
    ) -> None:
        """Save the pipeline and tokenizer to a Diffusers directory."""
        super().save_pretrained(save_directory, **kwargs)

    @classmethod
    def from_pretrained(
        cls, pretrained_model_name_or_path: str | Path, **kwargs: LayoutDMPipelineKwarg
    ) -> "LayoutDMPipeline":
        """Load a LayoutDM pipeline from a local directory or Hub repo.

        Args:
            pretrained_model_name_or_path: Diffusers pipeline directory or Hub id.
            **kwargs: Additional arguments forwarded to Diffusers.

        Returns:
            Loaded pipeline with a matching ``LayoutDMProcessor``.
        """
        tokenizer = LayoutDMTokenizer.from_pretrained(pretrained_model_name_or_path)
        kwargs.setdefault("tokenizer", tokenizer)
        pipe = super().from_pretrained(pretrained_model_name_or_path, **kwargs)
        pipe.processor = LayoutDMProcessor(pipe.tokenizer)
        return pipe

__init__

__init__(
    denoiser: LayoutDMDenoiser,
    scheduler: LayoutDMScheduler,
    tokenizer: LayoutDMTokenizer,
    processor: LayoutDMProcessor | None = None,
) -> None

Initialize and register LayoutDM pipeline modules.

Source code in models/layout-dm/src/layout_dm/pipeline_layout_dm.py
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
def __init__(
    self,
    denoiser: LayoutDMDenoiser,
    scheduler: LayoutDMScheduler,
    tokenizer: LayoutDMTokenizer,
    processor: LayoutDMProcessor | None = None,
) -> None:
    """Initialize and register LayoutDM pipeline modules."""
    super().__init__()
    self.register_modules(
        denoiser=denoiser, scheduler=scheduler, tokenizer=tokenizer
    )
    self.tokenizer = tokenizer
    self.processor = processor or LayoutDMProcessor(tokenizer)
    self.denoiser.eval()

__call__

__call__(
    *,
    batch_size: int = 1,
    seed: int | None = None,
    generator: Generator | None = None,
    condition_type: ConditionType
    | str = ConditionType.unconditional,
    labels: Int[Tensor, "batch elements"]
    | Int[ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    bbox: Float[Tensor, "batch elements 4"]
    | Float[ndarray, "batch elements 4"]
    | Sequence[ArrayLikeInput]
    | None = None,
    mask: Bool[Tensor, "batch elements"]
    | Bool[ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    num_elements: int
    | list[int]
    | Int[Tensor, "batch"]
    | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    num_inference_steps: int | None = None,
    sampling: SamplingMode | str = SamplingMode.random,
    temperature: float = 1.0,
    top_k: int = 5,
    top_p: float = 0.9,
    output_type: Literal["dataclass", "dict"] = "dataclass",
    return_intermediates: bool = False,
    **model_kwargs: str | int | float | bool | None,
) -> (
    LayoutGenerationOutput
    | dict[str, Shaped[torch.Tensor, "..."]]
)

Run unconditional or conditional layout generation.

Parameters:

Name Type Description Default
batch_size int

Number of layouts generated for unconditional sampling.

1
seed int | None

Optional seed used only when generator is omitted.

None
generator Generator | None

Optional torch generator. Takes precedence over seed.

None
condition_type ConditionType | str

Canonical condition type or supported source alias.

unconditional
labels Int[Tensor, 'batch elements'] | Int[ndarray, 'batch elements'] | Sequence[ArrayLikeInput] | None

Optional labels used by conditional modes.

None
bbox Float[Tensor, 'batch elements 4'] | Float[ndarray, 'batch elements 4'] | Sequence[ArrayLikeInput] | None

Optional boxes used by conditional modes.

None
mask Bool[Tensor, 'batch elements'] | Bool[ndarray, 'batch elements'] | Sequence[ArrayLikeInput] | None

Optional valid-element mask for conditional inputs.

None
num_elements int | list[int] | Int[Tensor, 'batch'] | None

Reserved compatibility argument.

None
box_format BoxFormat | str

Format of conditional input boxes.

xywh
normalized bool

Whether conditional boxes are already normalized.

True
canvas_size tuple[int, int] | None

Pixel canvas size used when normalized=False.

None
num_inference_steps int | None

Optional shortened diffusion step count.

None
sampling SamplingMode | str

Sampling strategy.

random
temperature float

Random sampling temperature.

1.0
top_k int

Top-k value for top-k modes.

5
top_p float

Top-p value for top-p modes.

0.9
output_type Literal['dataclass', 'dict']

"dataclass" or "dict".

'dataclass'
return_intermediates bool

Whether to return sampling trajectory data.

False
**model_kwargs str | int | float | bool | None

Reserved compatibility keyword arguments.

{}

Returns:

Type Description
LayoutGenerationOutput | dict[str, Shaped[Tensor, '...']]

LayoutGenerationOutput by default, or a dictionary when

LayoutGenerationOutput | dict[str, Shaped[Tensor, '...']]

output_type="dict".

Raises:

Type Description
ValueError

If a conditional mode is missing bbox or labels, or if output_type is unsupported.

Source code in models/layout-dm/src/layout_dm/pipeline_layout_dm.py
 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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
@torch.no_grad()
def __call__(
    self,
    *,
    batch_size: int = 1,
    seed: int | None = None,
    generator: torch.Generator | None = None,
    condition_type: ConditionType | str = ConditionType.unconditional,
    labels: Int[torch.Tensor, "batch elements"]
    | Int[np.ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    bbox: Float[torch.Tensor, "batch elements 4"]
    | Float[np.ndarray, "batch elements 4"]
    | Sequence[ArrayLikeInput]
    | None = None,
    mask: Bool[torch.Tensor, "batch elements"]
    | Bool[np.ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    num_elements: int | list[int] | Int[torch.Tensor, "batch"] | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    num_inference_steps: int | None = None,
    sampling: SamplingMode | str = SamplingMode.random,
    temperature: float = 1.0,
    top_k: int = 5,
    top_p: float = 0.9,
    output_type: Literal["dataclass", "dict"] = "dataclass",
    return_intermediates: bool = False,
    **model_kwargs: str | int | float | bool | None,
) -> LayoutGenerationOutput | dict[str, Shaped[torch.Tensor, "..."]]:
    """Run unconditional or conditional layout generation.

    Args:
        batch_size: Number of layouts generated for unconditional sampling.
        seed: Optional seed used only when ``generator`` is omitted.
        generator: Optional torch generator. Takes precedence over ``seed``.
        condition_type: Canonical condition type or supported source alias.
        labels: Optional labels used by conditional modes.
        bbox: Optional boxes used by conditional modes.
        mask: Optional valid-element mask for conditional inputs.
        num_elements: Reserved compatibility argument.
        box_format: Format of conditional input boxes.
        normalized: Whether conditional boxes are already normalized.
        canvas_size: Pixel canvas size used when ``normalized=False``.
        num_inference_steps: Optional shortened diffusion step count.
        sampling: Sampling strategy.
        temperature: Random sampling temperature.
        top_k: Top-k value for top-k modes.
        top_p: Top-p value for top-p modes.
        output_type: ``"dataclass"`` or ``"dict"``.
        return_intermediates: Whether to return sampling trajectory data.
        **model_kwargs: Reserved compatibility keyword arguments.

    Returns:
        ``LayoutGenerationOutput`` by default, or a dictionary when
        ``output_type="dict"``.

    Raises:
        ValueError: If a conditional mode is missing ``bbox`` or ``labels``,
            or if ``output_type`` is unsupported.
    """
    _ = (num_elements, model_kwargs)
    if generator is None and seed is not None:
        generator = torch.Generator(device=self.device).manual_seed(seed)
    canonical = normalize_condition_type(condition_type)
    condition = None
    if canonical is not ConditionType.unconditional:
        missing_inputs = [
            name
            for name, value in (("bbox", bbox), ("labels", labels))
            if value is None
        ]
        if missing_inputs:
            message = (
                f"bbox and labels are required for condition_type={condition_type}"
            )
            raise ValueError(message)

        processor_inputs = {
            "bbox": bbox,
            "labels": labels,
            "mask": mask,
            "box_format": box_format,
            "normalized": normalized,
            "canvas_size": canvas_size,
        }
        processed = self.processor(**processor_inputs)
        decoded_input = self.tokenizer.decode_layout(processed["input_ids"])
        condition = build_condition(
            self.tokenizer,
            cond_type=canonical,
            bbox=decoded_input["bbox"],
            labels=decoded_input["labels"],
            mask=decoded_input["mask"],
        )
        batch_size = condition.input_ids.shape[0]
    sampling_config = LayoutDMSamplingConfig(
        name=sampling,
        temperature=temperature,
        top_k=top_k,
        top_p=top_p,
        num_inference_steps=num_inference_steps,
    )
    self.scheduler.set_timesteps(num_inference_steps, device=self.device)
    sample = self.scheduler.initial_sample(
        batch_size,
        self.tokenizer.config.max_token_length,
        device=self.device,
        condition=condition,
    )
    trajectory = [] if return_intermediates else None
    previous_timestep = self.scheduler.config.num_timesteps
    for timestep in self.scheduler.timesteps:
        timestep_batch = torch.full(
            (batch_size,),
            int(timestep.item()),
            device=self.device,
            dtype=torch.long,
        )
        input_ids = log_onehot_to_index(sample)
        logits = self.denoiser(input_ids=input_ids, timesteps=timestep_batch).logits
        out = self.scheduler.step(
            logits,
            timestep_batch,
            sample,
            previous_timestep=previous_timestep,
            sampling=sampling_config,
            condition=condition,
            generator=generator,
        )
        sample = out.prev_sample
        previous_timestep = int(timestep.item())
        if trajectory is not None:
            trajectory.append(log_onehot_to_index(sample).detach().cpu())
    sequences = log_onehot_to_index(sample).detach().cpu()
    decoded = self.tokenizer.decode_layout(sequences)
    output = LayoutGenerationOutput(
        bbox=decoded["bbox"],
        labels=decoded["labels"],
        mask=decoded["mask"],
        id2label=self.tokenizer.config.id2label,
        sequences=sequences,
        trajectory=trajectory,
        intermediates={"condition_type": str(canonical)}
        if return_intermediates
        else None,
    )
    if output_type == "dict":
        return dict(output)
    if output_type != "dataclass":
        raise ValueError(f"Unsupported output_type: {output_type}")

    return output

save_pretrained

save_pretrained(
    save_directory: str | Path,
    **kwargs: LayoutDMPipelineKwarg,
) -> None

Save the pipeline and tokenizer to a Diffusers directory.

Source code in models/layout-dm/src/layout_dm/pipeline_layout_dm.py
243
244
245
246
247
def save_pretrained(
    self, save_directory: str | Path, **kwargs: LayoutDMPipelineKwarg
) -> None:
    """Save the pipeline and tokenizer to a Diffusers directory."""
    super().save_pretrained(save_directory, **kwargs)

from_pretrained classmethod

from_pretrained(
    pretrained_model_name_or_path: str | Path,
    **kwargs: LayoutDMPipelineKwarg,
) -> "LayoutDMPipeline"

Load a LayoutDM pipeline from a local directory or Hub repo.

Parameters:

Name Type Description Default
pretrained_model_name_or_path str | Path

Diffusers pipeline directory or Hub id.

required
**kwargs LayoutDMPipelineKwarg

Additional arguments forwarded to Diffusers.

{}

Returns:

Type Description
'LayoutDMPipeline'

Loaded pipeline with a matching LayoutDMProcessor.

Source code in models/layout-dm/src/layout_dm/pipeline_layout_dm.py
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
@classmethod
def from_pretrained(
    cls, pretrained_model_name_or_path: str | Path, **kwargs: LayoutDMPipelineKwarg
) -> "LayoutDMPipeline":
    """Load a LayoutDM pipeline from a local directory or Hub repo.

    Args:
        pretrained_model_name_or_path: Diffusers pipeline directory or Hub id.
        **kwargs: Additional arguments forwarded to Diffusers.

    Returns:
        Loaded pipeline with a matching ``LayoutDMProcessor``.
    """
    tokenizer = LayoutDMTokenizer.from_pretrained(pretrained_model_name_or_path)
    kwargs.setdefault("tokenizer", tokenizer)
    pipe = super().from_pretrained(pretrained_model_name_or_path, **kwargs)
    pipe.processor = LayoutDMProcessor(pipe.tokenizer)
    return pipe

LayoutDMProcessor

Bases: ProcessorMixin

Normalize layout arrays and encode them with LayoutDMTokenizer.

Parameters:

Name Type Description Default
tokenizer LayoutDMTokenizer

Tokenizer used to encode processed layouts.

required

Examples:

>>> from layout_dm.configuration_layout_dm import LayoutDMConfig
>>> from layout_dm.tokenization_layout_dm import LayoutDMTokenizer
>>> processor = LayoutDMProcessor(LayoutDMTokenizer(LayoutDMConfig(dataset_name="publaynet")))
>>> sorted(processor(bbox=[[[0.5, 0.5, 0.2, 0.2]]], labels=[[0]]))
['attention_mask', 'input_ids', 'mask']
Source code in models/layout-dm/src/layout_dm/processing_layout_dm.py
 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
class LayoutDMProcessor(ProcessorMixin):
    """Normalize layout arrays and encode them with ``LayoutDMTokenizer``.

    Args:
        tokenizer: Tokenizer used to encode processed layouts.

    Examples:
        >>> from layout_dm.configuration_layout_dm import LayoutDMConfig
        >>> from layout_dm.tokenization_layout_dm import LayoutDMTokenizer
        >>> processor = LayoutDMProcessor(LayoutDMTokenizer(LayoutDMConfig(dataset_name="publaynet")))
        >>> sorted(processor(bbox=[[[0.5, 0.5, 0.2, 0.2]]], labels=[[0]]))
        ['attention_mask', 'input_ids', 'mask']
    """

    config_name = "processor_config.json"
    tokenizer_class = "LayoutDMTokenizer"

    def __init__(self, tokenizer: LayoutDMTokenizer) -> None:
        """Initialize the processor with a tokenizer."""
        super().__init__(tokenizer=tokenizer)

    @classmethod
    def from_pretrained(
        cls,
        pretrained_model_name_or_path: str | PathLike[str],
        cache_dir: str | PathLike[str] | None = None,
        force_download: bool = False,
        local_files_only: bool = False,
        token: str | bool | None = None,
        revision: str = "main",
        **kwargs: str | int | float | bool | None,
    ) -> "LayoutDMProcessor":
        """Load a processor with the LayoutDM tokenizer implementation."""
        tokenizer = LayoutDMTokenizer.from_pretrained(
            pretrained_model_name_or_path,
            cache_dir=cache_dir,
            force_download=force_download,
            local_files_only=local_files_only,
            token=token,
            revision=revision,
            **kwargs,
        )
        return cls(tokenizer=tokenizer)

    def __call__(
        self,
        *,
        bbox: Float[torch.Tensor, "batch elements 4"]
        | Float[np.ndarray, "batch elements 4"]
        | Sequence[ArrayLikeInput],
        labels: Int[torch.Tensor, "batch elements"]
        | Int[np.ndarray, "batch elements"]
        | Sequence[ArrayLikeInput],
        mask: Bool[torch.Tensor, "batch elements"]
        | Bool[np.ndarray, "batch elements"]
        | Sequence[ArrayLikeInput]
        | None = None,
        box_format: BoxFormat | str = BoxFormat.xywh,
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
        return_tensors: Literal["pt"] = "pt",
    ) -> dict[str, Shaped[torch.Tensor, "..."]]:
        """Process a layout batch into model input tensors.

        Args:
            bbox: Layout boxes in ``box_format``.
            labels: Integer labels matching the layout boxes.
            mask: Optional valid-element mask. All elements are valid when omitted.
            box_format: Input box format.
            normalized: Whether boxes are already normalized to ``[0, 1]``.
            canvas_size: Pixel canvas size required when ``normalized=False``.
            return_tensors: Tensor backend. Only ``"pt"`` is supported.

        Returns:
            Tokenizer output containing ``input_ids``, ``attention_mask``, and
            ``mask`` tensors.

        Raises:
            ValueError: If ``return_tensors`` is not ``"pt"`` or if
                ``canvas_size`` is missing for pixel-space boxes.
        """
        if return_tensors != "pt":
            raise ValueError("LayoutDMProcessor only supports return_tensors='pt'")

        bbox_t, labels_t, mask_t = prepare_layout_tensors(
            bbox=bbox,
            labels=labels,
            mask=mask,
            box_format=box_format,
            normalized=normalized,
            canvas_size=canvas_size,
        )
        return self.tokenizer.encode_layout(bbox=bbox_t, labels=labels_t, mask=mask_t)

__init__

__init__(tokenizer: LayoutDMTokenizer) -> None

Initialize the processor with a tokenizer.

Source code in models/layout-dm/src/layout_dm/processing_layout_dm.py
40
41
42
def __init__(self, tokenizer: LayoutDMTokenizer) -> None:
    """Initialize the processor with a tokenizer."""
    super().__init__(tokenizer=tokenizer)

from_pretrained classmethod

from_pretrained(
    pretrained_model_name_or_path: str | PathLike[str],
    cache_dir: str | PathLike[str] | None = None,
    force_download: bool = False,
    local_files_only: bool = False,
    token: str | bool | None = None,
    revision: str = "main",
    **kwargs: str | int | float | bool | None,
) -> "LayoutDMProcessor"

Load a processor with the LayoutDM tokenizer implementation.

Source code in models/layout-dm/src/layout_dm/processing_layout_dm.py
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
@classmethod
def from_pretrained(
    cls,
    pretrained_model_name_or_path: str | PathLike[str],
    cache_dir: str | PathLike[str] | None = None,
    force_download: bool = False,
    local_files_only: bool = False,
    token: str | bool | None = None,
    revision: str = "main",
    **kwargs: str | int | float | bool | None,
) -> "LayoutDMProcessor":
    """Load a processor with the LayoutDM tokenizer implementation."""
    tokenizer = LayoutDMTokenizer.from_pretrained(
        pretrained_model_name_or_path,
        cache_dir=cache_dir,
        force_download=force_download,
        local_files_only=local_files_only,
        token=token,
        revision=revision,
        **kwargs,
    )
    return cls(tokenizer=tokenizer)

__call__

__call__(
    *,
    bbox: Float[Tensor, "batch elements 4"]
    | Float[ndarray, "batch elements 4"]
    | Sequence[ArrayLikeInput],
    labels: Int[Tensor, "batch elements"]
    | Int[ndarray, "batch elements"]
    | Sequence[ArrayLikeInput],
    mask: Bool[Tensor, "batch elements"]
    | Bool[ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    return_tensors: Literal["pt"] = "pt",
) -> dict[str, Shaped[torch.Tensor, "..."]]

Process a layout batch into model input tensors.

Parameters:

Name Type Description Default
bbox Float[Tensor, 'batch elements 4'] | Float[ndarray, 'batch elements 4'] | Sequence[ArrayLikeInput]

Layout boxes in box_format.

required
labels Int[Tensor, 'batch elements'] | Int[ndarray, 'batch elements'] | Sequence[ArrayLikeInput]

Integer labels matching the layout boxes.

required
mask Bool[Tensor, 'batch elements'] | Bool[ndarray, 'batch elements'] | Sequence[ArrayLikeInput] | None

Optional valid-element mask. All elements are valid when omitted.

None
box_format BoxFormat | str

Input box format.

xywh
normalized bool

Whether boxes are already normalized to [0, 1].

True
canvas_size tuple[int, int] | None

Pixel canvas size required when normalized=False.

None
return_tensors Literal['pt']

Tensor backend. Only "pt" is supported.

'pt'

Returns:

Type Description
dict[str, Shaped[Tensor, '...']]

Tokenizer output containing input_ids, attention_mask, and

dict[str, Shaped[Tensor, '...']]

mask tensors.

Raises:

Type Description
ValueError

If return_tensors is not "pt" or if canvas_size is missing for pixel-space boxes.

Source code in models/layout-dm/src/layout_dm/processing_layout_dm.py
 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
def __call__(
    self,
    *,
    bbox: Float[torch.Tensor, "batch elements 4"]
    | Float[np.ndarray, "batch elements 4"]
    | Sequence[ArrayLikeInput],
    labels: Int[torch.Tensor, "batch elements"]
    | Int[np.ndarray, "batch elements"]
    | Sequence[ArrayLikeInput],
    mask: Bool[torch.Tensor, "batch elements"]
    | Bool[np.ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    return_tensors: Literal["pt"] = "pt",
) -> dict[str, Shaped[torch.Tensor, "..."]]:
    """Process a layout batch into model input tensors.

    Args:
        bbox: Layout boxes in ``box_format``.
        labels: Integer labels matching the layout boxes.
        mask: Optional valid-element mask. All elements are valid when omitted.
        box_format: Input box format.
        normalized: Whether boxes are already normalized to ``[0, 1]``.
        canvas_size: Pixel canvas size required when ``normalized=False``.
        return_tensors: Tensor backend. Only ``"pt"`` is supported.

    Returns:
        Tokenizer output containing ``input_ids``, ``attention_mask``, and
        ``mask`` tensors.

    Raises:
        ValueError: If ``return_tensors`` is not ``"pt"`` or if
            ``canvas_size`` is missing for pixel-space boxes.
    """
    if return_tensors != "pt":
        raise ValueError("LayoutDMProcessor only supports return_tensors='pt'")

    bbox_t, labels_t, mask_t = prepare_layout_tensors(
        bbox=bbox,
        labels=labels,
        mask=mask,
        box_format=box_format,
        normalized=normalized,
        canvas_size=canvas_size,
    )
    return self.tokenizer.encode_layout(bbox=bbox_t, labels=labels_t, mask=mask_t)

LayoutDMSamplingConfig dataclass

Sampling parameters passed from the pipeline to the scheduler.

Source code in models/layout-dm/src/layout_dm/sampling.py
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@dataclass
class LayoutDMSamplingConfig:
    """Sampling parameters passed from the pipeline to the scheduler."""

    name: SamplingMode | str = SamplingMode.random
    temperature: float = 1.0
    top_k: int = 5
    top_p: float = 0.9
    num_inference_steps: int | None = None
    time_difference: float = 0.0
    refine_lambda: float = 3.0
    refine_mode: Literal["uniform", "gaussian", "negative"] = "uniform"
    refine_offset_ratio: float = 0.1

    def __post_init__(self) -> None:
        """Normalize public string sampling values to ``SamplingMode``."""
        self.name = normalize_sampling_mode(self.name)

__post_init__

__post_init__() -> None

Normalize public string sampling values to SamplingMode.

Source code in models/layout-dm/src/layout_dm/sampling.py
25
26
27
def __post_init__(self) -> None:
    """Normalize public string sampling values to ``SamplingMode``."""
    self.name = normalize_sampling_mode(self.name)

LayoutDMScheduler

Bases: SchedulerMixin, ConfigMixin

Diffusers-compatible scheduler for LayoutDM categorical diffusion.

Parameters:

Name Type Description Default
num_timesteps int

Number of training diffusion timesteps.

100
q_type Literal['constrained', 'vanilla']

Transition type from the original LayoutDM implementation.

'constrained'
vocab_size int

Full tokenizer vocabulary size.

required
mask_token_id int

Full vocabulary id used for mask tokens.

required
pad_token_id int

Full vocabulary id used for padding tokens.

required
var_order tuple[str, ...]

Per-element token variable order.

('c', 'x', 'y', 'w', 'h')
token_mask list[list[bool]] | None

Optional valid-token mask for each sequence position.

None
per_var_full_ids dict[str, list[int]] | None

Optional constrained vocabulary ids per variable.

None
att_1 float

Initial keep-probability schedule value.

0.99999
att_T float

Final keep-probability schedule value.

9e-06
ctt_1 float

Initial mask-probability schedule value.

9e-06
ctt_T float

Final mask-probability schedule value.

0.99999

Examples:

>>> scheduler = LayoutDMScheduler(vocab_size=8, mask_token_id=7, pad_token_id=6)
>>> scheduler.timesteps.shape[0]
100
Source code in models/layout-dm/src/layout_dm/scheduling_layout_dm.py
 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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
class LayoutDMScheduler(SchedulerMixin, ConfigMixin):
    """Diffusers-compatible scheduler for LayoutDM categorical diffusion.

    Args:
        num_timesteps: Number of training diffusion timesteps.
        q_type: Transition type from the original LayoutDM implementation.
        vocab_size: Full tokenizer vocabulary size.
        mask_token_id: Full vocabulary id used for mask tokens.
        pad_token_id: Full vocabulary id used for padding tokens.
        var_order: Per-element token variable order.
        token_mask: Optional valid-token mask for each sequence position.
        per_var_full_ids: Optional constrained vocabulary ids per variable.
        att_1: Initial keep-probability schedule value.
        att_T: Final keep-probability schedule value.
        ctt_1: Initial mask-probability schedule value.
        ctt_T: Final mask-probability schedule value.

    Examples:
        >>> scheduler = LayoutDMScheduler(vocab_size=8, mask_token_id=7, pad_token_id=6)
        >>> scheduler.timesteps.shape[0]
        100
    """

    config_name = "scheduler_config.json"
    order = 1

    @register_to_config
    def __init__(
        self,
        *,
        num_timesteps: int = 100,
        q_type: Literal["constrained", "vanilla"] = "constrained",
        vocab_size: int,
        mask_token_id: int,
        pad_token_id: int,
        var_order: tuple[str, ...] = ("c", "x", "y", "w", "h"),
        token_mask: list[list[bool]] | None = None,
        per_var_full_ids: dict[str, list[int]] | None = None,
        att_1: float = 0.99999,
        att_T: float = 0.000009,
        ctt_1: float = 0.000009,
        ctt_T: float = 0.99999,
    ) -> None:
        """Initialize LayoutDM transition schedules."""
        self.num_timesteps = num_timesteps
        self.timesteps = torch.arange(num_timesteps - 1, -1, -1)
        self.vocab_size = vocab_size
        self.mask_token_id = mask_token_id
        self.pad_token_id = pad_token_id
        self.var_order = tuple(var_order)
        self.token_mask = (
            None if token_mask is None else torch.tensor(token_mask, dtype=torch.bool)
        )
        self.per_var_full_ids = per_var_full_ids
        self.att_1 = att_1
        self.att_T = att_T
        self.ctt_1 = ctt_1
        self.ctt_T = ctt_T
        if per_var_full_ids is None:
            self.schedules = {
                "full": _alpha_schedule(
                    num_timesteps, vocab_size - 1, att_1, att_T, ctt_1, ctt_T
                )
            }
        else:
            self.schedules = {
                key: _alpha_schedule(
                    num_timesteps, len(ids) - 1, att_1, att_T, ctt_1, ctt_T
                )
                for key, ids in per_var_full_ids.items()
            }

    def set_timesteps(
        self, num_inference_steps: int | None = None, device: torch.device | None = None
    ) -> None:
        """Set reverse-diffusion timesteps for inference."""
        steps = num_inference_steps or self.num_timesteps
        self.timesteps = torch.tensor(
            [int(i * self.num_timesteps / steps) for i in range(steps - 1, -1, -1)],
            dtype=torch.long,
            device=device,
        )

    def initial_sample(
        self,
        batch_size: int,
        token_length: int,
        *,
        device: torch.device,
        condition: LayoutDMCondition | None = None,
    ) -> Float[torch.Tensor, "batch vocab tokens"]:
        """Create the initial log one-hot sample for reverse diffusion."""
        if condition is not None:
            ids = condition.input_ids.to(device)
        else:
            ids = torch.full(
                (batch_size, token_length),
                self.mask_token_id,
                dtype=torch.long,
                device=device,
            )
        return index_to_log_onehot(ids, self.vocab_size)

    def predict_start(
        self, denoiser_output: Float[torch.Tensor, "batch tokens vocab"]
    ) -> Float[torch.Tensor, "batch vocab tokens"]:
        """Convert denoiser logits to start-sequence log probabilities."""
        logits = denoiser_output[:, :, :-1]
        log_pred = torch.log_softmax(logits.double(), dim=-1).float()
        mask_col = torch.full(
            (*log_pred.shape[:2], 1),
            -70.0,
            device=log_pred.device,
            dtype=log_pred.dtype,
        )
        return (
            torch.cat((log_pred, mask_col), dim=-1).permute(0, 2, 1).clamp(-70.0, 0.0)
        )

    def q_posterior(
        self,
        log_x_start: Float[torch.Tensor, "batch vocab tokens"],
        log_x_t: Float[torch.Tensor, "batch vocab tokens"],
        t: Int[torch.Tensor, "batch"],
    ) -> Float[torch.Tensor, "batch vocab tokens"]:
        """Compute the LayoutDM posterior transition distribution."""
        if self.per_var_full_ids is not None:
            return self._constrained_q_posterior(log_x_start, log_x_t, t)
        return self._vanilla_q_posterior(log_x_start, log_x_t, t)

    def _vanilla_q_posterior(
        self,
        log_x_start: Float[torch.Tensor, "batch vocab tokens"],
        log_x_t: Float[torch.Tensor, "batch vocab tokens"],
        t: Int[torch.Tensor, "batch"],
    ) -> Float[torch.Tensor, "batch vocab tokens"]:
        """Compute the vanilla mask-and-replace posterior transition."""
        batch_size = log_x_start.size(0)
        index_x_t = log_onehot_to_index(log_x_t)
        mask = (index_x_t == self.mask_token_id).unsqueeze(1)
        log_one = torch.zeros(
            batch_size, 1, 1, device=log_x_t.device, dtype=log_x_t.dtype
        )
        log_zero = torch.log(log_one + 1.0e-30).expand(-1, -1, log_x_t.shape[-1])

        log_qt = self._vanilla_q_pred(log_x_t, t)[:, :-1, :]
        log_cumprod_ct = _extract(
            self.schedules["full"][5].to(t.device), t, log_x_start.shape
        )
        ct_cumprod = log_cumprod_ct.expand(-1, self.vocab_size - 1, -1)
        log_qt = (~mask) * log_qt + mask * ct_cumprod

        log_qt_one = self._vanilla_q_pred_one_timestep(log_x_t, t)
        log_qt_one = torch.cat((log_qt_one[:, :-1, :], log_zero), dim=1)
        log_ct = _extract(self.schedules["full"][2].to(t.device), t, log_x_start.shape)
        ct_vector = torch.cat(
            (log_ct.expand(-1, self.vocab_size - 1, -1), log_one), dim=1
        )
        log_qt_one = (~mask) * log_qt_one + mask * ct_vector

        q = torch.cat((log_x_start[:, :-1, :] - log_qt, log_zero), dim=1)
        q_log_sum_exp = torch.logsumexp(q, dim=1, keepdim=True)
        q = q - q_log_sum_exp
        return (self._vanilla_q_pred(q, t - 1) + log_qt_one + q_log_sum_exp).clamp(
            -70.0, 0.0
        )

    def _vanilla_q_pred_one_timestep(
        self,
        log_x_t: Float[torch.Tensor, "batch vocab tokens"],
        t: Int[torch.Tensor, "batch"],
    ) -> Float[torch.Tensor, "batch vocab tokens"]:
        """Apply one vanilla forward noising transition."""
        log_at, log_bt, log_ct = (
            self.schedules["full"][i].to(t.device) for i in range(3)
        )
        log_at = _extract(log_at, t, log_x_t.shape)
        log_bt = _extract(log_bt, t, log_x_t.shape)
        log_ct = _extract(log_ct, t, log_x_t.shape)
        log_1_min_ct = _log_1_min_a(log_ct)
        return torch.cat(
            [
                log_add_exp(log_x_t[:, :-1, :] + log_at, log_bt),
                log_add_exp(log_x_t[:, -1:, :] + log_1_min_ct, log_ct),
            ],
            dim=1,
        )

    def _vanilla_q_pred(
        self,
        log_x_start: Float[torch.Tensor, "batch vocab tokens"],
        t: Int[torch.Tensor, "batch"],
    ) -> Float[torch.Tensor, "batch vocab tokens"]:
        """Apply the cumulative vanilla forward noising transition."""
        t = (t + (self.num_timesteps + 1)) % (self.num_timesteps + 1)
        log_cumprod_at, log_cumprod_bt, log_cumprod_ct = (
            self.schedules["full"][i].to(t.device) for i in range(3, 6)
        )
        log_cumprod_at = _extract(log_cumprod_at, t, log_x_start.shape)
        log_cumprod_bt = _extract(log_cumprod_bt, t, log_x_start.shape)
        log_cumprod_ct = _extract(log_cumprod_ct, t, log_x_start.shape)
        log_1_min_cumprod_ct = _log_1_min_a(log_cumprod_ct)
        return torch.cat(
            [
                log_add_exp(log_x_start[:, :-1, :] + log_cumprod_at, log_cumprod_bt),
                log_add_exp(
                    log_x_start[:, -1:, :] + log_1_min_cumprod_ct, log_cumprod_ct
                ),
            ],
            dim=1,
        )

    def _constrained_q_posterior(
        self,
        log_x_start_full: Float[torch.Tensor, "batch vocab tokens"],
        log_x_t_full: Float[torch.Tensor, "batch vocab tokens"],
        t: Int[torch.Tensor, "batch"],
    ) -> Float[torch.Tensor, "batch vocab tokens"]:
        """Compute posterior probabilities with per-variable vocab constraints."""
        batch_size = log_x_start_full.size(0)
        step = len(self.var_order)
        seq_len = log_x_start_full.shape[-1] // step
        index_x_t_full = log_onehot_to_index(log_x_t_full)
        mask_reshaped = (index_x_t_full == self.mask_token_id).reshape(
            batch_size, seq_len, step
        )
        log_one = torch.zeros(
            batch_size, 1, 1, device=log_x_t_full.device, dtype=log_x_t_full.dtype
        )
        log_zero = torch.log(log_one + 1.0e-30).expand(-1, -1, seq_len)
        full_outputs = []
        for i, key in enumerate(self.var_order):
            mask = mask_reshaped[..., i].unsqueeze(1)
            log_x_start = self._full_to_partial_log(log_x_start_full[..., i::step], key)
            log_x_t = self._full_to_partial_log(log_x_t_full[..., i::step], key)
            log_qt = self._q_pred(log_x_t, t, key)[:, :-1, :]
            log_cumprod_ct = _extract(
                self.schedules[key][5].to(t.device), t, log_x_t.shape
            )
            ct_cumprod = log_cumprod_ct.expand(-1, self._mat_size(key) - 1, -1)
            log_qt = (~mask) * log_qt + mask * ct_cumprod
            log_qt_one = self._q_pred_one_timestep(log_x_t, t, key)
            log_qt_one = torch.cat((log_qt_one[:, :-1, :], log_zero), dim=1)
            log_ct = _extract(self.schedules[key][2].to(t.device), t, log_x_t.shape)
            ct_vector = torch.cat(
                (log_ct.expand(-1, self._mat_size(key) - 1, -1), log_one), dim=1
            )
            log_qt_one = (~mask) * log_qt_one + mask * ct_vector
            q = torch.cat((log_x_start[:, :-1, :] - log_qt, log_zero), dim=1)
            q_log_sum_exp = torch.logsumexp(q, dim=1, keepdim=True)
            q = q - q_log_sum_exp
            partial = self._q_pred(q, t - 1, key) + log_qt_one + q_log_sum_exp
            full_outputs.append(
                self._partial_to_full_log(partial.clamp(-70.0, 0.0), key)
            )
        return torch.stack(full_outputs, dim=-1).reshape(
            batch_size, self.vocab_size, -1
        )

    def _q_pred_one_timestep(
        self,
        log_x_t: Float[torch.Tensor, "batch partial_vocab tokens"],
        t: Int[torch.Tensor, "batch"],
        key: str,
    ) -> Float[torch.Tensor, "batch partial_vocab tokens"]:
        """Apply one forward noising transition in partial vocabulary space."""
        log_at, log_bt, log_ct = (self.schedules[key][i].to(t.device) for i in range(3))
        log_at = _extract(log_at, t, log_x_t.shape)
        log_bt = _extract(log_bt, t, log_x_t.shape)
        log_ct = _extract(log_ct, t, log_x_t.shape)
        log_1_min_ct = _log_1_min_a(log_ct)
        return torch.cat(
            [
                log_add_exp(log_x_t[:, :-1, :] + log_at, log_bt),
                log_add_exp(log_x_t[:, -1:, :] + log_1_min_ct, log_ct),
            ],
            dim=1,
        )

    def _q_pred(
        self,
        log_x_start: Float[torch.Tensor, "batch partial_vocab tokens"],
        t: Int[torch.Tensor, "batch"],
        key: str,
    ) -> Float[torch.Tensor, "batch partial_vocab tokens"]:
        """Apply cumulative forward noising in partial vocabulary space."""
        t = (t + (self.num_timesteps + 1)) % (self.num_timesteps + 1)
        log_cumprod_at, log_cumprod_bt, log_cumprod_ct = (
            self.schedules[key][i].to(t.device) for i in range(3, 6)
        )
        log_cumprod_at = _extract(log_cumprod_at, t, log_x_start.shape)
        log_cumprod_bt = _extract(log_cumprod_bt, t, log_x_start.shape)
        log_cumprod_ct = _extract(log_cumprod_ct, t, log_x_start.shape)
        log_1_min_cumprod_ct = _log_1_min_a(log_cumprod_ct)
        return torch.cat(
            [
                log_add_exp(log_x_start[:, :-1, :] + log_cumprod_at, log_cumprod_bt),
                log_add_exp(
                    log_x_start[:, -1:, :] + log_1_min_cumprod_ct, log_cumprod_ct
                ),
            ],
            dim=1,
        )

    def _mat_size(self, key: str) -> int:
        """Return the constrained matrix size for one token variable."""
        assert self.per_var_full_ids is not None
        return len(self.per_var_full_ids[key])

    def _full_ids(
        self, key: str, device: torch.device
    ) -> Int[torch.Tensor, "partial_vocab"]:
        """Return full vocabulary ids for a constrained token variable."""
        assert self.per_var_full_ids is not None
        return torch.tensor(self.per_var_full_ids[key], dtype=torch.long, device=device)

    def _full_to_partial_log(
        self, inputs: Float[torch.Tensor, "batch vocab tokens"], key: str
    ) -> Float[torch.Tensor, "batch partial_vocab tokens"]:
        """Gather full-vocabulary log probabilities into partial space."""
        full_ids = self._full_ids(key, inputs.device)
        index = full_ids.reshape(1, -1, 1).expand(inputs.shape[0], -1, inputs.shape[-1])
        return torch.gather(inputs, dim=1, index=index)

    def _partial_to_full_log(
        self, inputs: Float[torch.Tensor, "batch partial_vocab tokens"], key: str
    ) -> Float[torch.Tensor, "batch vocab tokens"]:
        """Scatter partial-space log probabilities into full vocabulary space."""
        full_ids = self._full_ids(key, inputs.device)
        outputs = torch.full(
            (inputs.shape[0], self.vocab_size, inputs.shape[-1]),
            math.log(1.0e-30),
            device=inputs.device,
            dtype=inputs.dtype,
        )
        index = full_ids.reshape(1, -1, 1).expand(inputs.shape[0], -1, inputs.shape[-1])
        return outputs.scatter(dim=1, index=index, src=inputs)

    def step(
        self,
        denoiser_output: Float[torch.Tensor, "batch tokens vocab"],
        timestep: Int[torch.Tensor, "batch"],
        sample: Float[torch.Tensor, "batch vocab tokens"],
        *,
        previous_timestep: int,
        sampling: LayoutDMSamplingConfig,
        condition: LayoutDMCondition | None = None,
        generator: torch.Generator | None = None,
    ) -> LayoutDMSchedulerOutput:
        """Run one reverse-diffusion scheduler step.

        Args:
            denoiser_output: Raw denoiser logits.
            timestep: Current timestep tensor.
            sample: Current log one-hot sample.
            previous_timestep: Previous timestep value from the sampling loop.
            sampling: Sampling configuration.
            condition: Optional strong condition mask and ids.
            generator: Optional torch generator for stochastic sampling.

        Returns:
            Scheduler output containing the previous sample and log-probability
            intermediates.
        """
        log_x_recon = self.predict_start(denoiser_output)
        model_log_prob = self.q_posterior(log_x_recon, sample, timestep)
        if self.token_mask is not None:
            valid = self.token_mask.to(model_log_prob.device).T.unsqueeze(0)
            model_log_prob = model_log_prob.masked_fill(~valid, -70.0)
        if condition is not None:
            strong_mask = condition.mask.to(model_log_prob.device).unsqueeze(1)
            strong_log_prob = index_to_log_onehot(
                condition.input_ids.to(model_log_prob.device), self.vocab_size
            )
            model_log_prob = torch.where(strong_mask, strong_log_prob, model_log_prob)
        logits = model_log_prob.permute(0, 2, 1)
        ids = sample_categorical(
            logits,
            sampling=sampling.name,
            temperature=sampling.temperature,
            top_k=sampling.top_k,
            top_p=sampling.top_p,
            generator=generator,
        )
        prev_sample = index_to_log_onehot(ids, self.vocab_size)
        return LayoutDMSchedulerOutput(
            prev_sample=prev_sample,
            pred_original_sample=log_x_recon,
            model_log_prob=model_log_prob,
        )

__init__

__init__(
    *,
    num_timesteps: int = 100,
    q_type: Literal[
        "constrained", "vanilla"
    ] = "constrained",
    vocab_size: int,
    mask_token_id: int,
    pad_token_id: int,
    var_order: tuple[str, ...] = ("c", "x", "y", "w", "h"),
    token_mask: list[list[bool]] | None = None,
    per_var_full_ids: dict[str, list[int]] | None = None,
    att_1: float = 0.99999,
    att_T: float = 9e-06,
    ctt_1: float = 9e-06,
    ctt_T: float = 0.99999,
) -> None

Initialize LayoutDM transition schedules.

Source code in models/layout-dm/src/layout_dm/scheduling_layout_dm.py
 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
@register_to_config
def __init__(
    self,
    *,
    num_timesteps: int = 100,
    q_type: Literal["constrained", "vanilla"] = "constrained",
    vocab_size: int,
    mask_token_id: int,
    pad_token_id: int,
    var_order: tuple[str, ...] = ("c", "x", "y", "w", "h"),
    token_mask: list[list[bool]] | None = None,
    per_var_full_ids: dict[str, list[int]] | None = None,
    att_1: float = 0.99999,
    att_T: float = 0.000009,
    ctt_1: float = 0.000009,
    ctt_T: float = 0.99999,
) -> None:
    """Initialize LayoutDM transition schedules."""
    self.num_timesteps = num_timesteps
    self.timesteps = torch.arange(num_timesteps - 1, -1, -1)
    self.vocab_size = vocab_size
    self.mask_token_id = mask_token_id
    self.pad_token_id = pad_token_id
    self.var_order = tuple(var_order)
    self.token_mask = (
        None if token_mask is None else torch.tensor(token_mask, dtype=torch.bool)
    )
    self.per_var_full_ids = per_var_full_ids
    self.att_1 = att_1
    self.att_T = att_T
    self.ctt_1 = ctt_1
    self.ctt_T = ctt_T
    if per_var_full_ids is None:
        self.schedules = {
            "full": _alpha_schedule(
                num_timesteps, vocab_size - 1, att_1, att_T, ctt_1, ctt_T
            )
        }
    else:
        self.schedules = {
            key: _alpha_schedule(
                num_timesteps, len(ids) - 1, att_1, att_T, ctt_1, ctt_T
            )
            for key, ids in per_var_full_ids.items()
        }

set_timesteps

set_timesteps(
    num_inference_steps: int | None = None,
    device: device | None = None,
) -> None

Set reverse-diffusion timesteps for inference.

Source code in models/layout-dm/src/layout_dm/scheduling_layout_dm.py
110
111
112
113
114
115
116
117
118
119
def set_timesteps(
    self, num_inference_steps: int | None = None, device: torch.device | None = None
) -> None:
    """Set reverse-diffusion timesteps for inference."""
    steps = num_inference_steps or self.num_timesteps
    self.timesteps = torch.tensor(
        [int(i * self.num_timesteps / steps) for i in range(steps - 1, -1, -1)],
        dtype=torch.long,
        device=device,
    )

initial_sample

initial_sample(
    batch_size: int,
    token_length: int,
    *,
    device: device,
    condition: LayoutDMCondition | None = None,
) -> Float[torch.Tensor, "batch vocab tokens"]

Create the initial log one-hot sample for reverse diffusion.

Source code in models/layout-dm/src/layout_dm/scheduling_layout_dm.py
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
def initial_sample(
    self,
    batch_size: int,
    token_length: int,
    *,
    device: torch.device,
    condition: LayoutDMCondition | None = None,
) -> Float[torch.Tensor, "batch vocab tokens"]:
    """Create the initial log one-hot sample for reverse diffusion."""
    if condition is not None:
        ids = condition.input_ids.to(device)
    else:
        ids = torch.full(
            (batch_size, token_length),
            self.mask_token_id,
            dtype=torch.long,
            device=device,
        )
    return index_to_log_onehot(ids, self.vocab_size)

predict_start

predict_start(
    denoiser_output: Float[Tensor, "batch tokens vocab"],
) -> Float[torch.Tensor, "batch vocab tokens"]

Convert denoiser logits to start-sequence log probabilities.

Source code in models/layout-dm/src/layout_dm/scheduling_layout_dm.py
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
def predict_start(
    self, denoiser_output: Float[torch.Tensor, "batch tokens vocab"]
) -> Float[torch.Tensor, "batch vocab tokens"]:
    """Convert denoiser logits to start-sequence log probabilities."""
    logits = denoiser_output[:, :, :-1]
    log_pred = torch.log_softmax(logits.double(), dim=-1).float()
    mask_col = torch.full(
        (*log_pred.shape[:2], 1),
        -70.0,
        device=log_pred.device,
        dtype=log_pred.dtype,
    )
    return (
        torch.cat((log_pred, mask_col), dim=-1).permute(0, 2, 1).clamp(-70.0, 0.0)
    )

q_posterior

q_posterior(
    log_x_start: Float[Tensor, "batch vocab tokens"],
    log_x_t: Float[Tensor, "batch vocab tokens"],
    t: Int[Tensor, "batch"],
) -> Float[torch.Tensor, "batch vocab tokens"]

Compute the LayoutDM posterior transition distribution.

Source code in models/layout-dm/src/layout_dm/scheduling_layout_dm.py
157
158
159
160
161
162
163
164
165
166
def q_posterior(
    self,
    log_x_start: Float[torch.Tensor, "batch vocab tokens"],
    log_x_t: Float[torch.Tensor, "batch vocab tokens"],
    t: Int[torch.Tensor, "batch"],
) -> Float[torch.Tensor, "batch vocab tokens"]:
    """Compute the LayoutDM posterior transition distribution."""
    if self.per_var_full_ids is not None:
        return self._constrained_q_posterior(log_x_start, log_x_t, t)
    return self._vanilla_q_posterior(log_x_start, log_x_t, t)

step

step(
    denoiser_output: Float[Tensor, "batch tokens vocab"],
    timestep: Int[Tensor, "batch"],
    sample: Float[Tensor, "batch vocab tokens"],
    *,
    previous_timestep: int,
    sampling: LayoutDMSamplingConfig,
    condition: LayoutDMCondition | None = None,
    generator: Generator | None = None,
) -> LayoutDMSchedulerOutput

Run one reverse-diffusion scheduler step.

Parameters:

Name Type Description Default
denoiser_output Float[Tensor, 'batch tokens vocab']

Raw denoiser logits.

required
timestep Int[Tensor, 'batch']

Current timestep tensor.

required
sample Float[Tensor, 'batch vocab tokens']

Current log one-hot sample.

required
previous_timestep int

Previous timestep value from the sampling loop.

required
sampling LayoutDMSamplingConfig

Sampling configuration.

required
condition LayoutDMCondition | None

Optional strong condition mask and ids.

None
generator Generator | None

Optional torch generator for stochastic sampling.

None

Returns:

Type Description
LayoutDMSchedulerOutput

Scheduler output containing the previous sample and log-probability

LayoutDMSchedulerOutput

intermediates.

Source code in models/layout-dm/src/layout_dm/scheduling_layout_dm.py
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
def step(
    self,
    denoiser_output: Float[torch.Tensor, "batch tokens vocab"],
    timestep: Int[torch.Tensor, "batch"],
    sample: Float[torch.Tensor, "batch vocab tokens"],
    *,
    previous_timestep: int,
    sampling: LayoutDMSamplingConfig,
    condition: LayoutDMCondition | None = None,
    generator: torch.Generator | None = None,
) -> LayoutDMSchedulerOutput:
    """Run one reverse-diffusion scheduler step.

    Args:
        denoiser_output: Raw denoiser logits.
        timestep: Current timestep tensor.
        sample: Current log one-hot sample.
        previous_timestep: Previous timestep value from the sampling loop.
        sampling: Sampling configuration.
        condition: Optional strong condition mask and ids.
        generator: Optional torch generator for stochastic sampling.

    Returns:
        Scheduler output containing the previous sample and log-probability
        intermediates.
    """
    log_x_recon = self.predict_start(denoiser_output)
    model_log_prob = self.q_posterior(log_x_recon, sample, timestep)
    if self.token_mask is not None:
        valid = self.token_mask.to(model_log_prob.device).T.unsqueeze(0)
        model_log_prob = model_log_prob.masked_fill(~valid, -70.0)
    if condition is not None:
        strong_mask = condition.mask.to(model_log_prob.device).unsqueeze(1)
        strong_log_prob = index_to_log_onehot(
            condition.input_ids.to(model_log_prob.device), self.vocab_size
        )
        model_log_prob = torch.where(strong_mask, strong_log_prob, model_log_prob)
    logits = model_log_prob.permute(0, 2, 1)
    ids = sample_categorical(
        logits,
        sampling=sampling.name,
        temperature=sampling.temperature,
        top_k=sampling.top_k,
        top_p=sampling.top_p,
        generator=generator,
    )
    prev_sample = index_to_log_onehot(ids, self.vocab_size)
    return LayoutDMSchedulerOutput(
        prev_sample=prev_sample,
        pred_original_sample=log_x_recon,
        model_log_prob=model_log_prob,
    )

LayoutDMTokenizer

Bases: PreTrainedTokenizer

Structured LayoutDM tokenizer backed by a synthetic vocabulary.

Parameters:

Name Type Description Default
config LayoutDMConfig | Mapping[str, LayoutDMConfigValue] | None

LayoutDM tokenizer/model configuration or serialized config dict.

None
vocab_file str | Path | None

Optional saved vocabulary file.

None
layout_config_file str | Path | None

Optional saved layout config file.

None
cluster_centers_file str | Path | None

Optional saved cluster-center file.

None
**kwargs LayoutDMConfigValue

Extra PreTrainedTokenizer keyword arguments.

{}

Raises:

Type Description
NotImplementedError

If the config uses an unsupported token order.

ValueError

If LayoutDM special-token ordering is invalid.

Examples:

>>> from layout_dm.configuration_layout_dm import LayoutDMConfig
>>> tokenizer = LayoutDMTokenizer(LayoutDMConfig(dataset_name="publaynet"))
>>> tokenizer.mask_token
'mask'
Source code in models/layout-dm/src/layout_dm/tokenization_layout_dm.py
 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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
class LayoutDMTokenizer(PreTrainedTokenizer):
    """Structured LayoutDM tokenizer backed by a synthetic vocabulary.

    Args:
        config: LayoutDM tokenizer/model configuration or serialized config dict.
        vocab_file: Optional saved vocabulary file.
        layout_config_file: Optional saved layout config file.
        cluster_centers_file: Optional saved cluster-center file.
        **kwargs: Extra ``PreTrainedTokenizer`` keyword arguments.

    Raises:
        NotImplementedError: If the config uses an unsupported token order.
        ValueError: If LayoutDM special-token ordering is invalid.

    Examples:
        >>> from layout_dm.configuration_layout_dm import LayoutDMConfig
        >>> tokenizer = LayoutDMTokenizer(LayoutDMConfig(dataset_name="publaynet"))
        >>> tokenizer.mask_token
        'mask'
    """

    vocab_files_names = {
        "vocab_file": "vocab.json",
        "layout_config_file": "layout_config.json",
        "cluster_centers_file": "cluster_centers.json",
    }
    model_input_names = ["input_ids", "attention_mask"]

    def __init__(
        self,
        config: LayoutDMConfig | Mapping[str, LayoutDMConfigValue] | None = None,
        *,
        vocab_file: str | Path | None = None,
        layout_config_file: str | Path | None = None,
        cluster_centers_file: str | Path | None = None,
        **kwargs: LayoutDMConfigValue,
    ) -> None:
        """Initialize a LayoutDM tokenizer from config or saved files."""
        if isinstance(config, LayoutDMConfig):
            pass
        elif config is None:
            config = self._load_config(
                layout_config_file=layout_config_file,
                cluster_centers_file=cluster_centers_file,
                kwargs=kwargs,
            )
        else:
            config = _layout_config_from_mapping(config)
        self.config = config
        if self.config.var_order != "c-x-y-w-h":
            raise NotImplementedError(
                "Only c-x-y-w-h LayoutDM token order is supported"
            )

        if (
            "mask" in self.config.special_tokens
            and self.config.special_tokens[-1] != "mask"
        ):
            raise ValueError("LayoutDM requires mask to be the final special token")

        vocab = self._build_vocab()
        if vocab_file is not None and Path(vocab_file).exists():
            loaded_vocab = json.loads(Path(vocab_file).read_text(encoding="utf-8"))
            vocab = {str(token): int(idx) for token, idx in loaded_vocab.items()}
        self._token_to_id = vocab
        self._id_to_token = {idx: token for token, idx in vocab.items()}
        pad_token = kwargs.pop("pad_token", "pad")
        mask_token = kwargs.pop("mask_token", "mask")
        model_max_length = kwargs.pop("model_max_length", self.config.max_token_length)

        super().__init__(
            pad_token=pad_token,
            mask_token=mask_token,
            model_max_length=model_max_length,
            **kwargs,
        )

    @property
    def vocab_size(self) -> int:
        """Return the synthetic vocabulary size."""
        return self.config.vocab_size

    @property
    def var_names(self) -> tuple[str, ...]:
        """Return the per-element variable names in token order."""
        return tuple(self.config.var_order.split("-"))

    def __call__(
        self,
        *,
        bbox: Float[torch.Tensor, "batch elements 4"] | Sequence[ArrayLikeInput],
        labels: Int[torch.Tensor, "batch elements"] | Sequence[ArrayLikeInput],
        mask: Bool[torch.Tensor, "batch elements"]
        | Sequence[ArrayLikeInput]
        | None = None,
    ) -> dict[str, Shaped[torch.Tensor, "..."]]:
        """Encode structured layout tensors.

        Args:
            bbox: Normalized center ``xywh`` boxes.
            labels: Dataset-local labels.
            mask: Optional valid-element mask.

        Returns:
            Dictionary containing ``input_ids``, ``attention_mask``, and ``mask``.
        """
        return self.encode_layout(
            bbox=torch.as_tensor(bbox),
            labels=torch.as_tensor(labels),
            mask=None if mask is None else torch.as_tensor(mask),
        )

    def get_vocab(self) -> dict[str, int]:
        """Return a copy of the synthetic token-to-id vocabulary."""
        return dict(self._token_to_id)

    def _tokenize(self, text: str, **kwargs: str | float | bool | None) -> list[str]:
        """Reject text tokenization because LayoutDM consumes layouts."""
        _ = text, kwargs
        raise TypeError("LayoutDMTokenizer does not tokenize text")

    def _convert_token_to_id(self, token: str) -> int:
        """Convert a synthetic token string to an integer id."""
        return self._token_to_id.get(token, self._token_to_id[self.pad_token])

    def _convert_id_to_token(self, index: int) -> str:
        """Convert an integer id to a synthetic token string."""
        return self._id_to_token.get(int(index), self.pad_token)

    def convert_tokens_to_string(self, tokens: list[str]) -> str:
        """Join synthetic tokens for human-readable debugging."""
        return " ".join(tokens)

    def save_vocabulary(
        self, save_directory: str | Path, filename_prefix: str | None = None
    ) -> tuple[str, ...]:
        """Save vocabulary, layout config, and cluster centers.

        Args:
            save_directory: Directory where tokenizer files are written.
            filename_prefix: Optional filename prefix used by Transformers.

        Returns:
            Tuple of saved file paths.
        """
        save_path = Path(save_directory)
        save_path.mkdir(parents=True, exist_ok=True)
        prefix = "" if filename_prefix is None else f"{filename_prefix}-"
        vocab_file = save_path / f"{prefix}vocab.json"
        layout_config_file = save_path / f"{prefix}layout_config.json"
        cluster_centers_file = save_path / f"{prefix}cluster_centers.json"
        vocab_file.write_text(
            json.dumps(self._token_to_id, indent=2, sort_keys=True),
            encoding="utf-8",
        )
        config_data = dict(self.config.config)
        config_data["id2label"] = {str(k): v for k, v in self.config.id2label.items()}
        config_data["cluster_centers"] = None
        config_data["cluster_centers_path"] = None
        layout_config_file.write_text(
            json.dumps(config_data, indent=2, sort_keys=True), encoding="utf-8"
        )
        centers = {
            key: [float(v) for v in self._centers(key, torch.device("cpu")).double()]
            for key in ("x", "y", "w", "h")
        }
        cluster_centers_file.write_text(
            json.dumps(centers, indent=2, sort_keys=True), encoding="utf-8"
        )
        return (str(vocab_file), str(layout_config_file), str(cluster_centers_file))

    @classmethod
    def from_pretrained(
        cls,
        path: str | PathLike[str],
        *args: str | PathLike[str] | bool,
        cache_dir: str | PathLike[str] | None = None,
        force_download: bool = False,
        local_files_only: bool = False,
        token: str | bool | None = None,
        revision: str = "main",
        **kwargs: LayoutDMConfigValue,
    ) -> LayoutDMTokenizer:
        """Load a tokenizer from a pipeline or tokenizer directory.

        Args:
            path: Pipeline root or tokenizer subdirectory.
            *args: Additional ``PreTrainedTokenizer`` positional arguments.
            cache_dir: Optional Transformers cache directory.
            force_download: Whether to force file downloads.
            local_files_only: Whether to avoid network access.
            token: Optional Hub authentication token.
            revision: Hub revision to load.
            **kwargs: Additional ``PreTrainedTokenizer`` keyword arguments.

        Returns:
            Loaded tokenizer.
        """
        path = Path(path)
        if (path / "tokenizer").is_dir():
            path = path / "tokenizer"
        return super().from_pretrained(
            path,
            *args,
            cache_dir=cache_dir,
            force_download=force_download,
            local_files_only=local_files_only,
            token=token,
            revision=revision,
            **kwargs,
        )

    @classmethod
    def _load_config(
        cls,
        *,
        layout_config_file: str | Path | None,
        cluster_centers_file: str | Path | None,
        kwargs: dict[str, LayoutDMConfigValue],
    ) -> LayoutDMConfig:
        layout_config = kwargs.pop("layout_config", None)
        if layout_config is None:
            if layout_config_file is None:
                raise ValueError(
                    "LayoutDMTokenizer requires a LayoutDMConfig or layout_config_file"
                )

            layout_config = json.loads(
                Path(layout_config_file).read_text(encoding="utf-8")
            )
        if not isinstance(layout_config, Mapping):
            raise TypeError("layout_config must be a mapping")

        config_data = dict(layout_config)
        if cluster_centers_file is not None and Path(cluster_centers_file).exists():
            centers = json.loads(Path(cluster_centers_file).read_text(encoding="utf-8"))
            if not isinstance(centers, Mapping):
                raise TypeError("cluster centers must be a mapping")

            config_data["cluster_centers"] = _cluster_centers(centers)
        return _layout_config_from_mapping(config_data)

    def encode_layout(
        self,
        *,
        bbox: Float[torch.Tensor, "elements 4"]
        | Float[torch.Tensor, "batch elements 4"],
        labels: Int[torch.Tensor, "elements"] | Int[torch.Tensor, "batch elements"],
        mask: Bool[torch.Tensor, "elements"]
        | Bool[torch.Tensor, "batch elements"]
        | None = None,
    ) -> dict[str, Shaped[torch.Tensor, "..."]]:
        """Encode normalized layout tensors into flattened token sequences.

        Args:
            bbox: Normalized center ``xywh`` boxes with shape ``(seq, 4)`` or
                ``(batch, seq, 4)``.
            labels: Dataset-local labels with shape ``(seq,)`` or
                ``(batch, seq)``.
            mask: Optional valid-element mask. Missing masks mark all elements
                valid.

        Returns:
            Dictionary containing flattened ``input_ids``, ``attention_mask``,
            and ``mask`` tensors.

        Raises:
            ValueError: If the sequence length exceeds the configured maximum.

        Examples:
            >>> import torch
            >>> from layout_dm.configuration_layout_dm import LayoutDMConfig
            >>> tok = LayoutDMTokenizer(LayoutDMConfig(dataset_name="publaynet"))
            >>> encoded = tok.encode_layout(
            ...     bbox=torch.zeros(1, 1, 4),
            ...     labels=torch.zeros(1, 1, dtype=torch.long),
            ... )
            >>> encoded["input_ids"].shape[-1]
            125
        """
        bbox = torch.as_tensor(bbox, dtype=torch.float64)
        labels = torch.as_tensor(labels, dtype=torch.long)
        if labels.ndim == 1:
            labels = labels.unsqueeze(0)
            bbox = bbox.unsqueeze(0)
        if mask is None:
            mask = torch.ones(labels.shape, dtype=torch.bool, device=labels.device)
        else:
            mask = torch.as_tensor(mask, dtype=torch.bool, device=labels.device)
            if mask.ndim == 1:
                mask = mask.unsqueeze(0)
        batch_size, seq_length = labels.shape
        if seq_length > self.config.max_seq_length:
            raise ValueError(
                f"seq_length {seq_length} exceeds max_seq_length {self.config.max_seq_length}"
            )

        bbox_ids = self._encode_bbox(bbox) + self.config.num_categories
        seq = torch.cat((labels.unsqueeze(-1), bbox_ids), dim=-1)
        pad_len = self.config.max_seq_length - seq_length
        if pad_len:
            pad = torch.full(
                (batch_size, pad_len, 5),
                self.pad_token_id,
                dtype=torch.long,
                device=seq.device,
            )
            seq = torch.cat((seq, pad), dim=1)
            mask = torch.cat(
                (
                    mask,
                    torch.zeros(
                        batch_size, pad_len, dtype=torch.bool, device=mask.device
                    ),
                ),
                dim=1,
            )
        seq = seq.masked_fill(~mask.unsqueeze(-1), self.pad_token_id)
        return {
            "input_ids": seq.reshape(batch_size, -1),
            "attention_mask": mask.repeat_interleave(5, dim=1),
            "mask": mask.repeat_interleave(5, dim=1),
        }

    def decode_layout(
        self, input_ids: Int[torch.Tensor, "batch tokens"]
    ) -> dict[str, Shaped[torch.Tensor, "..."]]:
        """Decode flattened token sequences into public layout tensors.

        Args:
            input_ids: Flattened LayoutDM token ids with shape
                ``(batch, max_token_length)``.

        Returns:
            Dictionary with ``bbox``, ``labels``, and ``mask`` tensors.
        """
        ids = torch.as_tensor(input_ids, dtype=torch.long)
        ids = ids.reshape(ids.shape[0], self.config.max_seq_length, 5)
        labels = ids[..., 0].clone()
        bbox_ids = ids[..., 1:].clone() - self.config.num_categories
        label_valid = (labels >= 0) & (labels < self.config.num_categories)
        bbox_valid = (bbox_ids >= 0) & (bbox_ids < self.config.num_bbox_tokens)
        mask = label_valid & bbox_valid.all(dim=-1)
        bbox = self._decode_bbox(bbox_ids)
        labels = labels.masked_fill(~mask, 0)
        bbox = bbox.masked_fill(~mask.unsqueeze(-1), 0.0)
        return {"bbox": bbox.float(), "labels": labels, "mask": mask}

    def token_mask(self) -> Bool[torch.Tensor, "tokens vocab"]:
        """Return the valid vocabulary mask for every flattened token position."""
        mask = torch.zeros(
            self.config.max_token_length, self.config.vocab_size, dtype=torch.bool
        )
        special_start = self.config.num_categories + self.config.num_bbox_tokens
        for pos, key in enumerate(self.var_names * self.config.max_seq_length):
            if key == "c":
                mask[pos, : self.config.num_categories] = True
                mask[pos, special_start:] = True
            else:
                start, end = self.config.bbox_slices[key]
                mask[pos, start:end] = True
                mask[pos, special_start:] = True
        return mask

    def full_to_partial_ids(
        self, ids: Int[torch.Tensor, "batch tokens"], key: str
    ) -> Int[torch.Tensor, "batch tokens"]:
        """Map full vocabulary bbox ids to per-variable partial ids."""
        mapping = self._mapping(key)
        return _bucketize(ids, mapping["full"], mapping["partial"])

    def partial_to_full_ids(
        self, ids: Int[torch.Tensor, "batch tokens"], key: str
    ) -> Int[torch.Tensor, "batch tokens"]:
        """Map per-variable partial ids to full vocabulary bbox ids."""
        mapping = self._mapping(key)
        return _bucketize(ids, mapping["partial"], mapping["full"])

    def full_to_partial_log_probs(
        self, log_probs: Float[torch.Tensor, "batch vocab tokens"], key: str
    ) -> Float[torch.Tensor, "batch vocab tokens"]:
        """Gather full-vocabulary log probabilities into a partial bbox space."""
        mapping = self._mapping(key)["full"].to(log_probs.device)
        index = mapping.reshape(1, -1, 1).expand(
            log_probs.shape[0], -1, log_probs.shape[-1]
        )
        return torch.gather(log_probs, dim=1, index=index)

    def partial_to_full_log_probs(
        self, log_probs: Float[torch.Tensor, "batch vocab tokens"], key: str
    ) -> Float[torch.Tensor, "batch vocab tokens"]:
        """Scatter partial bbox log probabilities into the full vocabulary."""
        mapping = self._mapping(key)["full"].to(log_probs.device)
        out = torch.full(
            (log_probs.shape[0], self.config.vocab_size, log_probs.shape[-1]),
            math.log(1.0e-30),
            device=log_probs.device,
            dtype=log_probs.dtype,
        )
        index = mapping.reshape(1, -1, 1).expand(
            log_probs.shape[0], -1, log_probs.shape[-1]
        )
        return out.scatter(dim=1, index=index, src=log_probs)

    def full_id_maps(self) -> dict[str, list[int]]:
        """Return full vocabulary id lists for every token variable."""
        return {key: self._mapping(key)["full"].tolist() for key in self.var_names}

    def _build_vocab(self) -> dict[str, int]:
        vocab: dict[str, int] = {}
        for idx, label in self.config.id2label.items():
            vocab[f"c:{label}"] = int(idx)
        for key in ("x", "y", "w", "h"):
            start, end = self.config.bbox_slices[key]
            for token_id in range(start, end):
                local_id = token_id - start
                vocab[f"{key}:{local_id}"] = token_id
        vocab["pad"] = self.config.pad_token_id
        vocab["mask"] = self.config.mask_token_id
        return vocab

    def _centers(self, key: str, device: torch.device) -> Float[torch.Tensor, "bins"]:
        centers = self._cluster_centers().get(key)
        if centers is None:
            delta = 1.0 / self.config.num_bin_bboxes
            start, stop = (0.0, 1.0 - delta) if key in {"x", "y"} else (delta, 1.0)
            centers = torch.linspace(
                start, stop, self.config.num_bin_bboxes, dtype=torch.float64
            ).tolist()
        return torch.tensor(centers, device=device, dtype=torch.float64).flatten()

    def _cluster_centers(self) -> dict[str, list[float]]:
        if self.config.cluster_centers is not None:
            return self.config.cluster_centers
        if self.config.cluster_centers_path is None:
            return {}
        centers = _load_cluster_centers_file(
            Path(self.config.cluster_centers_path),
            num_bin_bboxes=self.config.num_bin_bboxes,
        )
        self.config.cluster_centers = centers
        return centers

    def _encode_bbox(
        self, bbox: Float[torch.Tensor, "batch elements 4"]
    ) -> Int[torch.Tensor, "batch elements 4"]:
        bbox = bbox.to(dtype=torch.float64)
        pieces = []
        for i, key in enumerate(("x", "y", "w", "h")):
            values = bbox[..., i]
            if self.config.bbox_quantization == "linear":
                delta = 1.0 / self.config.num_bin_bboxes
                if key in {"x", "y"}:
                    ids = (
                        (values.clamp(0.0, 1.0 - delta) * self.config.num_bin_bboxes)
                        .round()
                        .long()
                    )
                else:
                    ids = (
                        (
                            (values.clamp(delta, 1.0) - delta)
                            * self.config.num_bin_bboxes
                        )
                        .round()
                        .long()
                    )
            elif self.config.bbox_quantization in {"kmeans", "percentile"}:
                centers = self._centers(key, values.device)
                ids = (
                    torch.cdist(values.reshape(-1, 1), centers.reshape(-1, 1))
                    .argmin(dim=-1)
                    .reshape(values.shape)
                )
            else:
                raise ValueError(
                    f"Unsupported bbox_quantization: {self.config.bbox_quantization}"
                )

            offset = (
                KEY_MULT_DICT[self.config.shared_bbox_vocab].get(key, 0)
                * self.config.num_bin_bboxes
            )
            pieces.append(ids + offset)
        return torch.stack(pieces, dim=-1)

    def _decode_bbox(
        self, bbox_ids: Int[torch.Tensor, "batch elements 4"]
    ) -> Float[torch.Tensor, "batch elements 4"]:
        ids = bbox_ids.clone()
        pieces = []
        for i, key in enumerate(("x", "y", "w", "h")):
            offset = (
                KEY_MULT_DICT[self.config.shared_bbox_vocab].get(key, 0)
                * self.config.num_bin_bboxes
            )
            local_ids = (ids[..., i] - offset).clamp(0, self.config.num_bin_bboxes - 1)
            if self.config.bbox_quantization == "linear":
                delta = 1.0 / self.config.num_bin_bboxes
                values = (
                    local_ids.double() * delta
                    if key in {"x", "y"}
                    else (local_ids.double() + 1.0) * delta
                )
            else:
                centers = self._centers(key, ids.device)
                values = centers[local_ids]
            pieces.append(values)
        return torch.stack(pieces, dim=-1).clamp(0.0, 1.0).float()

    def _mapping(self, key: str) -> dict[str, Int[torch.Tensor, "vocab"]]:
        if key == "c":
            full = list(range(self.config.num_categories)) + [
                self.pad_token_id,
                self.mask_token_id,
            ]
        else:
            start, end = self.config.bbox_slices[key]
            full = list(range(start, end)) + [self.pad_token_id, self.mask_token_id]
        return {
            "partial": torch.arange(len(full), dtype=torch.long),
            "full": torch.tensor(full, dtype=torch.long),
        }

vocab_size property

vocab_size: int

Return the synthetic vocabulary size.

var_names property

var_names: tuple[str, ...]

Return the per-element variable names in token order.

__init__

__init__(
    config: LayoutDMConfig
    | Mapping[str, LayoutDMConfigValue]
    | None = None,
    *,
    vocab_file: str | Path | None = None,
    layout_config_file: str | Path | None = None,
    cluster_centers_file: str | Path | None = None,
    **kwargs: LayoutDMConfigValue,
) -> None

Initialize a LayoutDM tokenizer from config or saved files.

Source code in models/layout-dm/src/layout_dm/tokenization_layout_dm.py
 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
def __init__(
    self,
    config: LayoutDMConfig | Mapping[str, LayoutDMConfigValue] | None = None,
    *,
    vocab_file: str | Path | None = None,
    layout_config_file: str | Path | None = None,
    cluster_centers_file: str | Path | None = None,
    **kwargs: LayoutDMConfigValue,
) -> None:
    """Initialize a LayoutDM tokenizer from config or saved files."""
    if isinstance(config, LayoutDMConfig):
        pass
    elif config is None:
        config = self._load_config(
            layout_config_file=layout_config_file,
            cluster_centers_file=cluster_centers_file,
            kwargs=kwargs,
        )
    else:
        config = _layout_config_from_mapping(config)
    self.config = config
    if self.config.var_order != "c-x-y-w-h":
        raise NotImplementedError(
            "Only c-x-y-w-h LayoutDM token order is supported"
        )

    if (
        "mask" in self.config.special_tokens
        and self.config.special_tokens[-1] != "mask"
    ):
        raise ValueError("LayoutDM requires mask to be the final special token")

    vocab = self._build_vocab()
    if vocab_file is not None and Path(vocab_file).exists():
        loaded_vocab = json.loads(Path(vocab_file).read_text(encoding="utf-8"))
        vocab = {str(token): int(idx) for token, idx in loaded_vocab.items()}
    self._token_to_id = vocab
    self._id_to_token = {idx: token for token, idx in vocab.items()}
    pad_token = kwargs.pop("pad_token", "pad")
    mask_token = kwargs.pop("mask_token", "mask")
    model_max_length = kwargs.pop("model_max_length", self.config.max_token_length)

    super().__init__(
        pad_token=pad_token,
        mask_token=mask_token,
        model_max_length=model_max_length,
        **kwargs,
    )

__call__

__call__(
    *,
    bbox: Float[Tensor, "batch elements 4"]
    | Sequence[ArrayLikeInput],
    labels: Int[Tensor, "batch elements"]
    | Sequence[ArrayLikeInput],
    mask: Bool[Tensor, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
) -> dict[str, Shaped[torch.Tensor, "..."]]

Encode structured layout tensors.

Parameters:

Name Type Description Default
bbox Float[Tensor, 'batch elements 4'] | Sequence[ArrayLikeInput]

Normalized center xywh boxes.

required
labels Int[Tensor, 'batch elements'] | Sequence[ArrayLikeInput]

Dataset-local labels.

required
mask Bool[Tensor, 'batch elements'] | Sequence[ArrayLikeInput] | None

Optional valid-element mask.

None

Returns:

Type Description
dict[str, Shaped[Tensor, '...']]

Dictionary containing input_ids, attention_mask, and mask.

Source code in models/layout-dm/src/layout_dm/tokenization_layout_dm.py
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
def __call__(
    self,
    *,
    bbox: Float[torch.Tensor, "batch elements 4"] | Sequence[ArrayLikeInput],
    labels: Int[torch.Tensor, "batch elements"] | Sequence[ArrayLikeInput],
    mask: Bool[torch.Tensor, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
) -> dict[str, Shaped[torch.Tensor, "..."]]:
    """Encode structured layout tensors.

    Args:
        bbox: Normalized center ``xywh`` boxes.
        labels: Dataset-local labels.
        mask: Optional valid-element mask.

    Returns:
        Dictionary containing ``input_ids``, ``attention_mask``, and ``mask``.
    """
    return self.encode_layout(
        bbox=torch.as_tensor(bbox),
        labels=torch.as_tensor(labels),
        mask=None if mask is None else torch.as_tensor(mask),
    )

get_vocab

get_vocab() -> dict[str, int]

Return a copy of the synthetic token-to-id vocabulary.

Source code in models/layout-dm/src/layout_dm/tokenization_layout_dm.py
147
148
149
def get_vocab(self) -> dict[str, int]:
    """Return a copy of the synthetic token-to-id vocabulary."""
    return dict(self._token_to_id)

convert_tokens_to_string

convert_tokens_to_string(tokens: list[str]) -> str

Join synthetic tokens for human-readable debugging.

Source code in models/layout-dm/src/layout_dm/tokenization_layout_dm.py
164
165
166
def convert_tokens_to_string(self, tokens: list[str]) -> str:
    """Join synthetic tokens for human-readable debugging."""
    return " ".join(tokens)

save_vocabulary

save_vocabulary(
    save_directory: str | Path,
    filename_prefix: str | None = None,
) -> tuple[str, ...]

Save vocabulary, layout config, and cluster centers.

Parameters:

Name Type Description Default
save_directory str | Path

Directory where tokenizer files are written.

required
filename_prefix str | None

Optional filename prefix used by Transformers.

None

Returns:

Type Description
tuple[str, ...]

Tuple of saved file paths.

Source code in models/layout-dm/src/layout_dm/tokenization_layout_dm.py
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
def save_vocabulary(
    self, save_directory: str | Path, filename_prefix: str | None = None
) -> tuple[str, ...]:
    """Save vocabulary, layout config, and cluster centers.

    Args:
        save_directory: Directory where tokenizer files are written.
        filename_prefix: Optional filename prefix used by Transformers.

    Returns:
        Tuple of saved file paths.
    """
    save_path = Path(save_directory)
    save_path.mkdir(parents=True, exist_ok=True)
    prefix = "" if filename_prefix is None else f"{filename_prefix}-"
    vocab_file = save_path / f"{prefix}vocab.json"
    layout_config_file = save_path / f"{prefix}layout_config.json"
    cluster_centers_file = save_path / f"{prefix}cluster_centers.json"
    vocab_file.write_text(
        json.dumps(self._token_to_id, indent=2, sort_keys=True),
        encoding="utf-8",
    )
    config_data = dict(self.config.config)
    config_data["id2label"] = {str(k): v for k, v in self.config.id2label.items()}
    config_data["cluster_centers"] = None
    config_data["cluster_centers_path"] = None
    layout_config_file.write_text(
        json.dumps(config_data, indent=2, sort_keys=True), encoding="utf-8"
    )
    centers = {
        key: [float(v) for v in self._centers(key, torch.device("cpu")).double()]
        for key in ("x", "y", "w", "h")
    }
    cluster_centers_file.write_text(
        json.dumps(centers, indent=2, sort_keys=True), encoding="utf-8"
    )
    return (str(vocab_file), str(layout_config_file), str(cluster_centers_file))

from_pretrained classmethod

from_pretrained(
    path: str | PathLike[str],
    *args: str | PathLike[str] | bool,
    cache_dir: str | PathLike[str] | None = None,
    force_download: bool = False,
    local_files_only: bool = False,
    token: str | bool | None = None,
    revision: str = "main",
    **kwargs: LayoutDMConfigValue,
) -> LayoutDMTokenizer

Load a tokenizer from a pipeline or tokenizer directory.

Parameters:

Name Type Description Default
path str | PathLike[str]

Pipeline root or tokenizer subdirectory.

required
*args str | PathLike[str] | bool

Additional PreTrainedTokenizer positional arguments.

()
cache_dir str | PathLike[str] | None

Optional Transformers cache directory.

None
force_download bool

Whether to force file downloads.

False
local_files_only bool

Whether to avoid network access.

False
token str | bool | None

Optional Hub authentication token.

None
revision str

Hub revision to load.

'main'
**kwargs LayoutDMConfigValue

Additional PreTrainedTokenizer keyword arguments.

{}

Returns:

Type Description
LayoutDMTokenizer

Loaded tokenizer.

Source code in models/layout-dm/src/layout_dm/tokenization_layout_dm.py
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
@classmethod
def from_pretrained(
    cls,
    path: str | PathLike[str],
    *args: str | PathLike[str] | bool,
    cache_dir: str | PathLike[str] | None = None,
    force_download: bool = False,
    local_files_only: bool = False,
    token: str | bool | None = None,
    revision: str = "main",
    **kwargs: LayoutDMConfigValue,
) -> LayoutDMTokenizer:
    """Load a tokenizer from a pipeline or tokenizer directory.

    Args:
        path: Pipeline root or tokenizer subdirectory.
        *args: Additional ``PreTrainedTokenizer`` positional arguments.
        cache_dir: Optional Transformers cache directory.
        force_download: Whether to force file downloads.
        local_files_only: Whether to avoid network access.
        token: Optional Hub authentication token.
        revision: Hub revision to load.
        **kwargs: Additional ``PreTrainedTokenizer`` keyword arguments.

    Returns:
        Loaded tokenizer.
    """
    path = Path(path)
    if (path / "tokenizer").is_dir():
        path = path / "tokenizer"
    return super().from_pretrained(
        path,
        *args,
        cache_dir=cache_dir,
        force_download=force_download,
        local_files_only=local_files_only,
        token=token,
        revision=revision,
        **kwargs,
    )

encode_layout

encode_layout(
    *,
    bbox: Float[Tensor, "elements 4"]
    | Float[Tensor, "batch elements 4"],
    labels: Int[Tensor, "elements"]
    | Int[Tensor, "batch elements"],
    mask: Bool[Tensor, "elements"]
    | Bool[Tensor, "batch elements"]
    | None = None,
) -> dict[str, Shaped[torch.Tensor, "..."]]

Encode normalized layout tensors into flattened token sequences.

Parameters:

Name Type Description Default
bbox Float[Tensor, 'elements 4'] | Float[Tensor, 'batch elements 4']

Normalized center xywh boxes with shape (seq, 4) or (batch, seq, 4).

required
labels Int[Tensor, 'elements'] | Int[Tensor, 'batch elements']

Dataset-local labels with shape (seq,) or (batch, seq).

required
mask Bool[Tensor, 'elements'] | Bool[Tensor, 'batch elements'] | None

Optional valid-element mask. Missing masks mark all elements valid.

None

Returns:

Type Description
dict[str, Shaped[Tensor, '...']]

Dictionary containing flattened input_ids, attention_mask,

dict[str, Shaped[Tensor, '...']]

and mask tensors.

Raises:

Type Description
ValueError

If the sequence length exceeds the configured maximum.

Examples:

>>> import torch
>>> from layout_dm.configuration_layout_dm import LayoutDMConfig
>>> tok = LayoutDMTokenizer(LayoutDMConfig(dataset_name="publaynet"))
>>> encoded = tok.encode_layout(
...     bbox=torch.zeros(1, 1, 4),
...     labels=torch.zeros(1, 1, dtype=torch.long),
... )
>>> encoded["input_ids"].shape[-1]
125
Source code in models/layout-dm/src/layout_dm/tokenization_layout_dm.py
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
def encode_layout(
    self,
    *,
    bbox: Float[torch.Tensor, "elements 4"]
    | Float[torch.Tensor, "batch elements 4"],
    labels: Int[torch.Tensor, "elements"] | Int[torch.Tensor, "batch elements"],
    mask: Bool[torch.Tensor, "elements"]
    | Bool[torch.Tensor, "batch elements"]
    | None = None,
) -> dict[str, Shaped[torch.Tensor, "..."]]:
    """Encode normalized layout tensors into flattened token sequences.

    Args:
        bbox: Normalized center ``xywh`` boxes with shape ``(seq, 4)`` or
            ``(batch, seq, 4)``.
        labels: Dataset-local labels with shape ``(seq,)`` or
            ``(batch, seq)``.
        mask: Optional valid-element mask. Missing masks mark all elements
            valid.

    Returns:
        Dictionary containing flattened ``input_ids``, ``attention_mask``,
        and ``mask`` tensors.

    Raises:
        ValueError: If the sequence length exceeds the configured maximum.

    Examples:
        >>> import torch
        >>> from layout_dm.configuration_layout_dm import LayoutDMConfig
        >>> tok = LayoutDMTokenizer(LayoutDMConfig(dataset_name="publaynet"))
        >>> encoded = tok.encode_layout(
        ...     bbox=torch.zeros(1, 1, 4),
        ...     labels=torch.zeros(1, 1, dtype=torch.long),
        ... )
        >>> encoded["input_ids"].shape[-1]
        125
    """
    bbox = torch.as_tensor(bbox, dtype=torch.float64)
    labels = torch.as_tensor(labels, dtype=torch.long)
    if labels.ndim == 1:
        labels = labels.unsqueeze(0)
        bbox = bbox.unsqueeze(0)
    if mask is None:
        mask = torch.ones(labels.shape, dtype=torch.bool, device=labels.device)
    else:
        mask = torch.as_tensor(mask, dtype=torch.bool, device=labels.device)
        if mask.ndim == 1:
            mask = mask.unsqueeze(0)
    batch_size, seq_length = labels.shape
    if seq_length > self.config.max_seq_length:
        raise ValueError(
            f"seq_length {seq_length} exceeds max_seq_length {self.config.max_seq_length}"
        )

    bbox_ids = self._encode_bbox(bbox) + self.config.num_categories
    seq = torch.cat((labels.unsqueeze(-1), bbox_ids), dim=-1)
    pad_len = self.config.max_seq_length - seq_length
    if pad_len:
        pad = torch.full(
            (batch_size, pad_len, 5),
            self.pad_token_id,
            dtype=torch.long,
            device=seq.device,
        )
        seq = torch.cat((seq, pad), dim=1)
        mask = torch.cat(
            (
                mask,
                torch.zeros(
                    batch_size, pad_len, dtype=torch.bool, device=mask.device
                ),
            ),
            dim=1,
        )
    seq = seq.masked_fill(~mask.unsqueeze(-1), self.pad_token_id)
    return {
        "input_ids": seq.reshape(batch_size, -1),
        "attention_mask": mask.repeat_interleave(5, dim=1),
        "mask": mask.repeat_interleave(5, dim=1),
    }

decode_layout

decode_layout(
    input_ids: Int[Tensor, "batch tokens"],
) -> dict[str, Shaped[torch.Tensor, "..."]]

Decode flattened token sequences into public layout tensors.

Parameters:

Name Type Description Default
input_ids Int[Tensor, 'batch tokens']

Flattened LayoutDM token ids with shape (batch, max_token_length).

required

Returns:

Type Description
dict[str, Shaped[Tensor, '...']]

Dictionary with bbox, labels, and mask tensors.

Source code in models/layout-dm/src/layout_dm/tokenization_layout_dm.py
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
def decode_layout(
    self, input_ids: Int[torch.Tensor, "batch tokens"]
) -> dict[str, Shaped[torch.Tensor, "..."]]:
    """Decode flattened token sequences into public layout tensors.

    Args:
        input_ids: Flattened LayoutDM token ids with shape
            ``(batch, max_token_length)``.

    Returns:
        Dictionary with ``bbox``, ``labels``, and ``mask`` tensors.
    """
    ids = torch.as_tensor(input_ids, dtype=torch.long)
    ids = ids.reshape(ids.shape[0], self.config.max_seq_length, 5)
    labels = ids[..., 0].clone()
    bbox_ids = ids[..., 1:].clone() - self.config.num_categories
    label_valid = (labels >= 0) & (labels < self.config.num_categories)
    bbox_valid = (bbox_ids >= 0) & (bbox_ids < self.config.num_bbox_tokens)
    mask = label_valid & bbox_valid.all(dim=-1)
    bbox = self._decode_bbox(bbox_ids)
    labels = labels.masked_fill(~mask, 0)
    bbox = bbox.masked_fill(~mask.unsqueeze(-1), 0.0)
    return {"bbox": bbox.float(), "labels": labels, "mask": mask}

token_mask

token_mask() -> Bool[torch.Tensor, 'tokens vocab']

Return the valid vocabulary mask for every flattened token position.

Source code in models/layout-dm/src/layout_dm/tokenization_layout_dm.py
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
def token_mask(self) -> Bool[torch.Tensor, "tokens vocab"]:
    """Return the valid vocabulary mask for every flattened token position."""
    mask = torch.zeros(
        self.config.max_token_length, self.config.vocab_size, dtype=torch.bool
    )
    special_start = self.config.num_categories + self.config.num_bbox_tokens
    for pos, key in enumerate(self.var_names * self.config.max_seq_length):
        if key == "c":
            mask[pos, : self.config.num_categories] = True
            mask[pos, special_start:] = True
        else:
            start, end = self.config.bbox_slices[key]
            mask[pos, start:end] = True
            mask[pos, special_start:] = True
    return mask

full_to_partial_ids

full_to_partial_ids(
    ids: Int[Tensor, "batch tokens"], key: str
) -> Int[torch.Tensor, "batch tokens"]

Map full vocabulary bbox ids to per-variable partial ids.

Source code in models/layout-dm/src/layout_dm/tokenization_layout_dm.py
399
400
401
402
403
404
def full_to_partial_ids(
    self, ids: Int[torch.Tensor, "batch tokens"], key: str
) -> Int[torch.Tensor, "batch tokens"]:
    """Map full vocabulary bbox ids to per-variable partial ids."""
    mapping = self._mapping(key)
    return _bucketize(ids, mapping["full"], mapping["partial"])

partial_to_full_ids

partial_to_full_ids(
    ids: Int[Tensor, "batch tokens"], key: str
) -> Int[torch.Tensor, "batch tokens"]

Map per-variable partial ids to full vocabulary bbox ids.

Source code in models/layout-dm/src/layout_dm/tokenization_layout_dm.py
406
407
408
409
410
411
def partial_to_full_ids(
    self, ids: Int[torch.Tensor, "batch tokens"], key: str
) -> Int[torch.Tensor, "batch tokens"]:
    """Map per-variable partial ids to full vocabulary bbox ids."""
    mapping = self._mapping(key)
    return _bucketize(ids, mapping["partial"], mapping["full"])

full_to_partial_log_probs

full_to_partial_log_probs(
    log_probs: Float[Tensor, "batch vocab tokens"], key: str
) -> Float[torch.Tensor, "batch vocab tokens"]

Gather full-vocabulary log probabilities into a partial bbox space.

Source code in models/layout-dm/src/layout_dm/tokenization_layout_dm.py
413
414
415
416
417
418
419
420
421
def full_to_partial_log_probs(
    self, log_probs: Float[torch.Tensor, "batch vocab tokens"], key: str
) -> Float[torch.Tensor, "batch vocab tokens"]:
    """Gather full-vocabulary log probabilities into a partial bbox space."""
    mapping = self._mapping(key)["full"].to(log_probs.device)
    index = mapping.reshape(1, -1, 1).expand(
        log_probs.shape[0], -1, log_probs.shape[-1]
    )
    return torch.gather(log_probs, dim=1, index=index)

partial_to_full_log_probs

partial_to_full_log_probs(
    log_probs: Float[Tensor, "batch vocab tokens"], key: str
) -> Float[torch.Tensor, "batch vocab tokens"]

Scatter partial bbox log probabilities into the full vocabulary.

Source code in models/layout-dm/src/layout_dm/tokenization_layout_dm.py
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
def partial_to_full_log_probs(
    self, log_probs: Float[torch.Tensor, "batch vocab tokens"], key: str
) -> Float[torch.Tensor, "batch vocab tokens"]:
    """Scatter partial bbox log probabilities into the full vocabulary."""
    mapping = self._mapping(key)["full"].to(log_probs.device)
    out = torch.full(
        (log_probs.shape[0], self.config.vocab_size, log_probs.shape[-1]),
        math.log(1.0e-30),
        device=log_probs.device,
        dtype=log_probs.dtype,
    )
    index = mapping.reshape(1, -1, 1).expand(
        log_probs.shape[0], -1, log_probs.shape[-1]
    )
    return out.scatter(dim=1, index=index, src=log_probs)

full_id_maps

full_id_maps() -> dict[str, list[int]]

Return full vocabulary id lists for every token variable.

Source code in models/layout-dm/src/layout_dm/tokenization_layout_dm.py
439
440
441
def full_id_maps(self) -> dict[str, list[int]]:
    """Return full vocabulary id lists for every token variable."""
    return {key: self._mapping(key)["full"].tolist() for key in self.var_names}

normalize_condition_type

normalize_condition_type(
    condition_type: ConditionType | str,
) -> ConditionType

Normalize condition aliases to a canonical ConditionType.

Parameters:

Name Type Description Default
condition_type ConditionType | str

Canonical condition enum or a public/release alias.

required

Returns:

Type Description
ConditionType

Canonical condition enum.

Raises:

Type Description
ValueError

If the condition type is unknown.

Examples:

>>> str(normalize_condition_type("gen_t"))
'label'
>>> str(normalize_condition_type("gen_r"))
'relation'
Source code in lib/laygen/src/laygen/common/conditions.py
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
def normalize_condition_type(condition_type: ConditionType | str) -> ConditionType:
    """Normalize condition aliases to a canonical ``ConditionType``.

    Args:
        condition_type: Canonical condition enum or a public/release alias.

    Returns:
        Canonical condition enum.

    Raises:
        ValueError: If the condition type is unknown.

    Examples:
        >>> str(normalize_condition_type("gen_t"))
        'label'
        >>> str(normalize_condition_type("gen_r"))
        'relation'
    """
    if isinstance(condition_type, ConditionType):
        return condition_type
    try:
        return _CONDITION_ALIASES[
            ConditionAlias(condition_type.lower().replace("-", "_"))
        ]
    except ValueError as exc:
        raise ValueError(f"Unknown condition_type: {condition_type}") from exc

conditioning

Condition construction helpers for LayoutDM generation modes.

LayoutDMCondition dataclass

Strong and weak token constraints for conditional LayoutDM sampling.

Source code in models/layout-dm/src/layout_dm/conditioning.py
16
17
18
19
20
21
22
23
24
25
26
@dataclass
class LayoutDMCondition:
    """Strong and weak token constraints for conditional LayoutDM sampling."""

    input_ids: Int[torch.Tensor, "batch tokens"]
    mask: Bool[torch.Tensor, "batch tokens"]
    type: Literal["c", "cwh", "partial", "refinement"]
    num_element: Int[torch.Tensor, "batch"] | None = None
    original_input_ids: Int[torch.Tensor, "batch tokens"] | None = None
    weak_mask: Bool[torch.Tensor, "batch tokens"] | None = None
    weak_logits: Float[torch.Tensor, "batch tokens vocab"] | None = None

build_condition

build_condition(
    tokenizer: LayoutDMTokenizer,
    *,
    cond_type: ConditionType | str,
    bbox: Float[Tensor, "batch elements 4"],
    labels: Int[Tensor, "batch elements"],
    mask: Bool[Tensor, "batch elements"],
    noisy_bbox: Float[Tensor, "batch elements 4"]
    | None = None,
) -> LayoutDMCondition

Build token-level conditioning masks for a LayoutDM layout condition.

Parameters:

Name Type Description Default
tokenizer LayoutDMTokenizer

LayoutDM tokenizer used to encode structured layouts.

required
cond_type ConditionType | str

Canonical condition type or release alias.

required
bbox Float[Tensor, 'batch elements 4']

Normalized center xywh boxes.

required
labels Int[Tensor, 'batch elements']

Dataset-local labels.

required
mask Bool[Tensor, 'batch elements']

Valid-element mask.

required
noisy_bbox Float[Tensor, 'batch elements 4'] | None

Optional noised boxes for refinement mode.

None

Returns:

Type Description
LayoutDMCondition

Token ids and masks consumed by the scheduler.

Raises:

Type Description
NotImplementedError

If the condition type is unsupported.

Source code in models/layout-dm/src/layout_dm/conditioning.py
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
def build_condition(
    tokenizer: LayoutDMTokenizer,
    *,
    cond_type: ConditionType | str,
    bbox: Float[torch.Tensor, "batch elements 4"],
    labels: Int[torch.Tensor, "batch elements"],
    mask: Bool[torch.Tensor, "batch elements"],
    noisy_bbox: Float[torch.Tensor, "batch elements 4"] | None = None,
) -> LayoutDMCondition:
    """Build token-level conditioning masks for a LayoutDM layout condition.

    Args:
        tokenizer: LayoutDM tokenizer used to encode structured layouts.
        cond_type: Canonical condition type or release alias.
        bbox: Normalized center ``xywh`` boxes.
        labels: Dataset-local labels.
        mask: Valid-element mask.
        noisy_bbox: Optional noised boxes for refinement mode.

    Returns:
        Token ids and masks consumed by the scheduler.

    Raises:
        NotImplementedError: If the condition type is unsupported.
    """
    canonical = normalize_condition_type(cond_type)
    encoded = tokenizer.encode_layout(bbox=bbox, labels=labels, mask=mask)
    ids = encoded["input_ids"]
    element_mask = encoded["mask"].reshape(
        ids.shape[0], tokenizer.config.max_seq_length, 5
    )
    if canonical is ConditionType.label:
        strong_mask = torch.zeros_like(ids, dtype=torch.bool)
        strong_mask[:, 0::5] = element_mask[..., 0]
        return LayoutDMCondition(
            input_ids=ids, mask=strong_mask, type="c", num_element=mask.sum(dim=1)
        )
    if canonical is ConditionType.label_size:
        strong_mask = torch.zeros_like(ids, dtype=torch.bool)
        strong_mask[:, 0::5] = element_mask[..., 0]
        strong_mask[:, 3::5] = element_mask[..., 3]
        strong_mask[:, 4::5] = element_mask[..., 4]
        return LayoutDMCondition(
            input_ids=ids, mask=strong_mask, type="cwh", num_element=mask.sum(dim=1)
        )
    if canonical is ConditionType.completion:
        return LayoutDMCondition(
            input_ids=ids,
            mask=encoded["mask"],
            type="partial",
            num_element=mask.sum(dim=1),
        )
    if canonical is ConditionType.refinement:
        original = ids
        if noisy_bbox is not None:
            ids = tokenizer.encode_layout(bbox=noisy_bbox, labels=labels, mask=mask)[
                "input_ids"
            ]
        return LayoutDMCondition(
            input_ids=ids,
            mask=encoded["mask"],
            type="refinement",
            num_element=mask.sum(dim=1),
            original_input_ids=original,
        )
    raise NotImplementedError(f"Unsupported LayoutDM condition_type: {cond_type}")

configuration_layout_dm

Configuration objects for converted LayoutDM checkpoints.

LayoutDMConfig

Bases: ConfigMixin

Serializable LayoutDM architecture and tokenizer configuration.

Parameters:

Name Type Description Default
dataset_name DatasetName | str

Dataset name or alias used to initialize labels.

required
id2label dict[int | str, str] | None

Optional persisted label-id mapping.

None
max_seq_length int

Maximum number of layout elements.

25
num_bin_bboxes int

Number of bins per bounding-box attribute.

32
var_order str

Per-element token order.

'c-x-y-w-h'
shared_bbox_vocab str

Bounding-box vocabulary sharing mode.

'x-y-w-h'
bbox_quantization str

Bounding-box quantization mode.

'kmeans'
special_tokens tuple[str, ...]

Special token names. mask must be last for LayoutDM.

('pad', 'mask')
cluster_centers dict[str, list[float]] | None

Optional bbox cluster centers stored with tokenizer files.

None
cluster_centers_path str | None

Optional local path to released cluster centers.

None
hidden_size int

Transformer hidden size.

464
num_attention_heads int

Number of attention heads.

8
num_hidden_layers int

Number of transformer layers.

4
intermediate_size int

Feed-forward hidden size.

1856
dropout float

Transformer dropout probability.

0.0
timestep_type str | None

Timestep-conditioning type.

'adalayernorm'
num_timesteps int

Number of diffusion timesteps.

100
q_type str

Diffusion transition type.

'constrained'
att_1 float

Initial keep probability schedule value.

0.99999
att_T float

Final keep probability schedule value.

9e-06
ctt_1 float

Initial mask probability schedule value.

9e-06
ctt_T float

Final mask probability schedule value.

0.99999

Examples:

>>> cfg = LayoutDMConfig(dataset_name="publaynet")
>>> cfg.vocab_size > cfg.num_categories
True
Source code in models/layout-dm/src/layout_dm/configuration_layout_dm.py
 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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
class LayoutDMConfig(ConfigMixin):
    """Serializable LayoutDM architecture and tokenizer configuration.

    Args:
        dataset_name: Dataset name or alias used to initialize labels.
        id2label: Optional persisted label-id mapping.
        max_seq_length: Maximum number of layout elements.
        num_bin_bboxes: Number of bins per bounding-box attribute.
        var_order: Per-element token order.
        shared_bbox_vocab: Bounding-box vocabulary sharing mode.
        bbox_quantization: Bounding-box quantization mode.
        special_tokens: Special token names. ``mask`` must be last for LayoutDM.
        cluster_centers: Optional bbox cluster centers stored with tokenizer files.
        cluster_centers_path: Optional local path to released cluster centers.
        hidden_size: Transformer hidden size.
        num_attention_heads: Number of attention heads.
        num_hidden_layers: Number of transformer layers.
        intermediate_size: Feed-forward hidden size.
        dropout: Transformer dropout probability.
        timestep_type: Timestep-conditioning type.
        num_timesteps: Number of diffusion timesteps.
        q_type: Diffusion transition type.
        att_1: Initial keep probability schedule value.
        att_T: Final keep probability schedule value.
        ctt_1: Initial mask probability schedule value.
        ctt_T: Final mask probability schedule value.

    Examples:
        >>> cfg = LayoutDMConfig(dataset_name="publaynet")
        >>> cfg.vocab_size > cfg.num_categories
        True
    """

    config_name = "layout_dm_config.json"

    @register_to_config
    def __init__(
        self,
        *,
        dataset_name: DatasetName | str,
        id2label: dict[int | str, str] | None = None,
        max_seq_length: int = 25,
        num_bin_bboxes: int = 32,
        var_order: str = "c-x-y-w-h",
        shared_bbox_vocab: str = "x-y-w-h",
        bbox_quantization: str = "kmeans",
        special_tokens: tuple[str, ...] = ("pad", "mask"),
        cluster_centers: dict[str, list[float]] | None = None,
        cluster_centers_path: str | None = None,
        hidden_size: int = 464,
        num_attention_heads: int = 8,
        num_hidden_layers: int = 4,
        intermediate_size: int = 1856,
        dropout: float = 0.0,
        timestep_type: str | None = "adalayernorm",
        num_timesteps: int = 100,
        q_type: str = "constrained",
        att_1: float = 0.99999,
        att_T: float = 0.000009,
        ctt_1: float = 0.000009,
        ctt_T: float = 0.99999,
    ) -> None:
        """Initialize a serializable LayoutDM configuration."""
        self.dataset_name = str(normalize_dataset_name(dataset_name))
        raw_id2label = id2label or id2label_for_dataset(self.dataset_name)
        self.id2label = {int(k): v for k, v in raw_id2label.items()}

        self.max_seq_length = max_seq_length
        self.num_bin_bboxes = num_bin_bboxes
        self.var_order = var_order
        self.shared_bbox_vocab = shared_bbox_vocab
        self.bbox_quantization = bbox_quantization
        self.special_tokens = tuple(special_tokens)
        self.cluster_centers = cluster_centers
        self.cluster_centers_path = cluster_centers_path

        self.hidden_size = hidden_size
        self.num_attention_heads = num_attention_heads
        self.num_hidden_layers = num_hidden_layers
        self.intermediate_size = intermediate_size
        self.dropout = dropout
        self.timestep_type = timestep_type
        self.num_timesteps = num_timesteps
        self.q_type = q_type

        self.att_1 = att_1
        self.att_T = att_T
        self.ctt_1 = ctt_1
        self.ctt_T = ctt_T

    @property
    def label2id(self) -> dict[str, int]:
        """Return the inverse label-name to id mapping."""
        return {v: k for k, v in self.id2label.items()}

    @property
    def num_categories(self) -> int:
        """Return the number of dataset categories."""
        return len(self.id2label)

    @property
    def num_bbox_tokens(self) -> int:
        """Return the number of bounding-box vocabulary tokens."""
        return self.num_bin_bboxes * len(self.shared_bbox_vocab.split("-"))

    @property
    def num_special_tokens(self) -> int:
        """Return the number of special tokens."""
        return len(self.special_tokens)

    @property
    def vocab_size(self) -> int:
        """Return the full tokenizer vocabulary size."""
        return self.num_categories + self.num_bbox_tokens + self.num_special_tokens

    @property
    def pad_token_id(self) -> int:
        """Return the full vocabulary id of the padding token."""
        return (
            self.num_categories
            + self.num_bbox_tokens
            + self.special_tokens.index("pad")
        )

    @property
    def mask_token_id(self) -> int:
        """Return the full vocabulary id of the mask token."""
        return (
            self.num_categories
            + self.num_bbox_tokens
            + self.special_tokens.index("mask")
        )

    @property
    def num_attributes_per_element(self) -> int:
        """Return the number of tokens used for each layout element."""
        return len(self.var_order.split("-"))

    @property
    def max_token_length(self) -> int:
        """Return the flattened token sequence length."""
        return self.max_seq_length * self.num_attributes_per_element

    @property
    def bbox_slices(self) -> dict[str, tuple[int, int]]:
        """Return full-vocabulary slices for bbox attributes."""
        slices: dict[str, tuple[int, int]] = {}
        for i, key in enumerate(("x", "y", "w", "h")):
            start = self.num_categories + i * self.num_bin_bboxes
            slices[key] = (start, start + self.num_bin_bboxes)
        return slices

label2id property

label2id: dict[str, int]

Return the inverse label-name to id mapping.

num_categories property

num_categories: int

Return the number of dataset categories.

num_bbox_tokens property

num_bbox_tokens: int

Return the number of bounding-box vocabulary tokens.

num_special_tokens property

num_special_tokens: int

Return the number of special tokens.

vocab_size property

vocab_size: int

Return the full tokenizer vocabulary size.

pad_token_id property

pad_token_id: int

Return the full vocabulary id of the padding token.

mask_token_id property

mask_token_id: int

Return the full vocabulary id of the mask token.

num_attributes_per_element property

num_attributes_per_element: int

Return the number of tokens used for each layout element.

max_token_length property

max_token_length: int

Return the flattened token sequence length.

bbox_slices property

bbox_slices: dict[str, tuple[int, int]]

Return full-vocabulary slices for bbox attributes.

__init__

__init__(
    *,
    dataset_name: DatasetName | str,
    id2label: dict[int | str, str] | None = None,
    max_seq_length: int = 25,
    num_bin_bboxes: int = 32,
    var_order: str = "c-x-y-w-h",
    shared_bbox_vocab: str = "x-y-w-h",
    bbox_quantization: str = "kmeans",
    special_tokens: tuple[str, ...] = ("pad", "mask"),
    cluster_centers: dict[str, list[float]] | None = None,
    cluster_centers_path: str | None = None,
    hidden_size: int = 464,
    num_attention_heads: int = 8,
    num_hidden_layers: int = 4,
    intermediate_size: int = 1856,
    dropout: float = 0.0,
    timestep_type: str | None = "adalayernorm",
    num_timesteps: int = 100,
    q_type: str = "constrained",
    att_1: float = 0.99999,
    att_T: float = 9e-06,
    ctt_1: float = 9e-06,
    ctt_T: float = 0.99999,
) -> None

Initialize a serializable LayoutDM configuration.

Source code in models/layout-dm/src/layout_dm/configuration_layout_dm.py
 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
@register_to_config
def __init__(
    self,
    *,
    dataset_name: DatasetName | str,
    id2label: dict[int | str, str] | None = None,
    max_seq_length: int = 25,
    num_bin_bboxes: int = 32,
    var_order: str = "c-x-y-w-h",
    shared_bbox_vocab: str = "x-y-w-h",
    bbox_quantization: str = "kmeans",
    special_tokens: tuple[str, ...] = ("pad", "mask"),
    cluster_centers: dict[str, list[float]] | None = None,
    cluster_centers_path: str | None = None,
    hidden_size: int = 464,
    num_attention_heads: int = 8,
    num_hidden_layers: int = 4,
    intermediate_size: int = 1856,
    dropout: float = 0.0,
    timestep_type: str | None = "adalayernorm",
    num_timesteps: int = 100,
    q_type: str = "constrained",
    att_1: float = 0.99999,
    att_T: float = 0.000009,
    ctt_1: float = 0.000009,
    ctt_T: float = 0.99999,
) -> None:
    """Initialize a serializable LayoutDM configuration."""
    self.dataset_name = str(normalize_dataset_name(dataset_name))
    raw_id2label = id2label or id2label_for_dataset(self.dataset_name)
    self.id2label = {int(k): v for k, v in raw_id2label.items()}

    self.max_seq_length = max_seq_length
    self.num_bin_bboxes = num_bin_bboxes
    self.var_order = var_order
    self.shared_bbox_vocab = shared_bbox_vocab
    self.bbox_quantization = bbox_quantization
    self.special_tokens = tuple(special_tokens)
    self.cluster_centers = cluster_centers
    self.cluster_centers_path = cluster_centers_path

    self.hidden_size = hidden_size
    self.num_attention_heads = num_attention_heads
    self.num_hidden_layers = num_hidden_layers
    self.intermediate_size = intermediate_size
    self.dropout = dropout
    self.timestep_type = timestep_type
    self.num_timesteps = num_timesteps
    self.q_type = q_type

    self.att_1 = att_1
    self.att_T = att_T
    self.ctt_1 = ctt_1
    self.ctt_T = ctt_T

LayoutDMRuntimeConfig dataclass

Container for runtime defaults used by lightweight integrations.

Source code in models/layout-dm/src/layout_dm/configuration_layout_dm.py
169
170
171
172
173
174
175
@dataclass
class LayoutDMRuntimeConfig:
    """Container for runtime defaults used by lightweight integrations."""

    config: LayoutDMConfig = field(
        default_factory=lambda: LayoutDMConfig(dataset_name="publaynet")
    )

conversion

Checkpoint conversion helpers for original LayoutDM releases.

remap_denoiser_key

remap_denoiser_key(key: str) -> str

Map an original checkpoint key to the converted denoiser key.

Source code in models/layout-dm/src/layout_dm/conversion.py
15
16
17
18
19
20
21
22
23
24
25
26
def remap_denoiser_key(key: str) -> str:
    """Map an original checkpoint key to the converted denoiser key."""
    if key.startswith("model.transformer."):
        return key.removeprefix("model.")
    if key.startswith("model.backbone."):
        return key.removeprefix("model.backbone.")
    if key.startswith("model.model."):
        return key.removeprefix("model.model.")
    if not key.startswith("model.module.transformer."):
        raise KeyError(key)

    return key.removeprefix("model.module.")

split_original_state_dict

split_original_state_dict(
    state_dict: dict[str, Shaped[Tensor, "..."]],
) -> dict[str, Shaped[torch.Tensor, "..."]]

Extract converted denoiser weights from an original state dict.

Source code in models/layout-dm/src/layout_dm/conversion.py
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
def split_original_state_dict(
    state_dict: dict[str, Shaped[torch.Tensor, "..."]],
) -> dict[str, Shaped[torch.Tensor, "..."]]:
    """Extract converted denoiser weights from an original state dict."""
    denoiser: dict[str, Shaped[torch.Tensor, "..."]] = {}
    for key, value in state_dict.items():
        if key.startswith(
            (
                "model.module.transformer.",
                "model.transformer.",
                "model.backbone.",
                "model.model.",
            )
        ):
            denoiser[remap_denoiser_key(key)] = value
        elif (
            "_log_" in key
            or key.startswith("model.module.Lt_")
            or key.startswith("model.diffusion_scheduler.")
            or key.startswith("diffusion_scheduler.")
            or key in {"lt_history", "lt_count"}
            or key == "model.module.zero_vector"
        ):
            continue
        else:
            raise KeyError(key)

    return denoiser

load_cluster_centers

load_cluster_centers(
    starter_dir: Path, dataset: str
) -> dict[str, list[float]]

Load sorted bbox cluster centers from the original starter bundle.

Source code in models/layout-dm/src/layout_dm/conversion.py
59
60
61
62
63
64
65
66
67
68
69
def load_cluster_centers(starter_dir: Path, dataset: str) -> dict[str, list[float]]:
    """Load sorted bbox cluster centers from the original starter bundle."""
    name = "rico25_max25" if dataset == "rico25" else "publaynet_max25"
    path = starter_dir / "clustering_weights" / f"{name}_kmeans_train_clusters.pkl"
    with path.open("rb") as f:
        models = pickle.load(f)
    centers: dict[str, list[float]] = {}
    for key in ("x", "y", "w", "h"):
        arr = models[f"{key}-32"].cluster_centers_
        centers[key] = sorted(float(x) for x in arr.reshape(-1))
    return centers

write_layoutdm_model_card

write_layoutdm_model_card(
    output_dir: Path, dataset: DatasetName | str
) -> Path

Write a LayoutDM model card to a converted pipeline directory.

Source code in models/layout-dm/src/layout_dm/conversion.py
72
73
74
75
76
def write_layoutdm_model_card(output_dir: Path, dataset: DatasetName | str) -> Path:
    """Write a LayoutDM model card to a converted pipeline directory."""
    path = output_dir / "README.md"
    path.write_text(str(layoutdm_model_card(dataset=dataset)), encoding="utf-8")
    return path

modeling_layout_dm

Modeling components for converted LayoutDM checkpoints.

AdaLayerNorm

Bases: _AdaNorm

Adaptive layer normalization conditioned on diffusion timestep.

Origin

This module follows VQ-Diffusion AdaLayerNorm and keeps the submodule names used by LayoutDM, LACE, and Layout-Corrector checkpoints.

Parameters:

Name Type Description Default
n_embd int

Hidden dimension.

required
max_timestep int

Maximum diffusion timestep.

required
emb_type TimestepEmbeddingType | str

Timestep embedding variant.

adalayernorm_abs
Source code in lib/laygen/src/laygen/nn/norms.py
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
class AdaLayerNorm(_AdaNorm):
    """Adaptive layer normalization conditioned on diffusion timestep.

    Origin:
        This module follows VQ-Diffusion ``AdaLayerNorm`` and keeps the
        submodule names used by LayoutDM, LACE, and Layout-Corrector checkpoints.

    Args:
        n_embd: Hidden dimension.
        max_timestep: Maximum diffusion timestep.
        emb_type: Timestep embedding variant.
    """

    def __init__(
        self,
        n_embd: int,
        max_timestep: int,
        emb_type: TimestepEmbeddingType | str = TimestepEmbeddingType.adalayernorm_abs,
    ) -> None:
        """Initialize adaptive layer normalization."""
        super().__init__(n_embd, max_timestep, emb_type)
        self.layernorm = nn.LayerNorm(n_embd, elementwise_affine=False)

    def forward(
        self,
        x: Float[torch.Tensor, "batch tokens channels"],
        timestep: Int[torch.Tensor, "batch"],
    ) -> Float[torch.Tensor, "batch tokens channels"]:
        """Apply timestep-conditioned layer normalization."""
        emb = self.linear(self.silu(self.emb(timestep))).unsqueeze(1)
        scale, shift = torch.chunk(emb, 2, dim=2)
        return self.layernorm(x) * (1 + scale) + shift

__init__

__init__(
    n_embd: int,
    max_timestep: int,
    emb_type: TimestepEmbeddingType
    | str = TimestepEmbeddingType.adalayernorm_abs,
) -> None

Initialize adaptive layer normalization.

Source code in lib/laygen/src/laygen/nn/norms.py
55
56
57
58
59
60
61
62
63
def __init__(
    self,
    n_embd: int,
    max_timestep: int,
    emb_type: TimestepEmbeddingType | str = TimestepEmbeddingType.adalayernorm_abs,
) -> None:
    """Initialize adaptive layer normalization."""
    super().__init__(n_embd, max_timestep, emb_type)
    self.layernorm = nn.LayerNorm(n_embd, elementwise_affine=False)

forward

forward(
    x: Float[Tensor, "batch tokens channels"],
    timestep: Int[Tensor, "batch"],
) -> Float[torch.Tensor, "batch tokens channels"]

Apply timestep-conditioned layer normalization.

Source code in lib/laygen/src/laygen/nn/norms.py
65
66
67
68
69
70
71
72
73
def forward(
    self,
    x: Float[torch.Tensor, "batch tokens channels"],
    timestep: Int[torch.Tensor, "batch"],
) -> Float[torch.Tensor, "batch tokens channels"]:
    """Apply timestep-conditioned layer normalization."""
    emb = self.linear(self.silu(self.emb(timestep))).unsqueeze(1)
    scale, shift = torch.chunk(emb, 2, dim=2)
    return self.layernorm(x) * (1 + scale) + shift

ElementPositionalEmbedding

Bases: Module

Learned element and attribute positional embedding.

Origin

This learned element/attribute positional embedding is specific to CyberAgentAILab LayoutDM and is reused by Layout-Corrector.

Parameters:

Name Type Description Default
dim_model int

Embedding dimension.

required
max_token_length int

Maximum flattened token sequence length.

required
n_attr_per_elem int

Number of attributes per layout element.

5
Source code in lib/laygen/src/laygen/nn/embeddings.py
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
class ElementPositionalEmbedding(nn.Module):
    """Learned element and attribute positional embedding.

    Origin:
        This learned element/attribute positional embedding is specific to
        CyberAgentAILab LayoutDM and is reused by Layout-Corrector.

    Args:
        dim_model: Embedding dimension.
        max_token_length: Maximum flattened token sequence length.
        n_attr_per_elem: Number of attributes per layout element.
    """

    def __init__(
        self, dim_model: int, max_token_length: int, n_attr_per_elem: int = 5
    ) -> None:
        """Initialize element and attribute embedding parameters."""
        super().__init__()
        self.n_elem = max_token_length // n_attr_per_elem
        self.n_attr_per_elem = n_attr_per_elem
        self.elem_emb = nn.Parameter(torch.rand(self.n_elem, dim_model))
        self.attr_emb = nn.Parameter(torch.rand(self.n_attr_per_elem, dim_model))

    def forward(
        self, h: Float[torch.Tensor, "batch tokens channels"]
    ) -> Float[torch.Tensor, "batch tokens channels"]:
        """Return positional embeddings matching hidden-state length.

        Args:
            h: Hidden states shaped ``(batch, sequence, dim)``.

        Returns:
            Positional embedding tensor shaped like ``h``.
        """
        batch, seq_len = h.shape[:2]
        elem_emb = repeat(self.elem_emb, "s d -> (s x) d", x=self.n_attr_per_elem)
        attr_emb = repeat(self.attr_emb, "x d -> (s x) d", s=self.n_elem)
        emb = (elem_emb + attr_emb)[:seq_len]
        return repeat(emb, "s d -> b s d", b=batch)

    @property
    def no_decay_param_names(self) -> list[str]:
        """Return parameter names that should skip weight decay."""
        return ["elem_emb", "attr_emb"]

no_decay_param_names property

no_decay_param_names: list[str]

Return parameter names that should skip weight decay.

__init__

__init__(
    dim_model: int,
    max_token_length: int,
    n_attr_per_elem: int = 5,
) -> None

Initialize element and attribute embedding parameters.

Source code in lib/laygen/src/laygen/nn/embeddings.py
115
116
117
118
119
120
121
122
123
def __init__(
    self, dim_model: int, max_token_length: int, n_attr_per_elem: int = 5
) -> None:
    """Initialize element and attribute embedding parameters."""
    super().__init__()
    self.n_elem = max_token_length // n_attr_per_elem
    self.n_attr_per_elem = n_attr_per_elem
    self.elem_emb = nn.Parameter(torch.rand(self.n_elem, dim_model))
    self.attr_emb = nn.Parameter(torch.rand(self.n_attr_per_elem, dim_model))

forward

forward(
    h: Float[Tensor, "batch tokens channels"],
) -> Float[torch.Tensor, "batch tokens channels"]

Return positional embeddings matching hidden-state length.

Parameters:

Name Type Description Default
h Float[Tensor, 'batch tokens channels']

Hidden states shaped (batch, sequence, dim).

required

Returns:

Type Description
Float[Tensor, 'batch tokens channels']

Positional embedding tensor shaped like h.

Source code in lib/laygen/src/laygen/nn/embeddings.py
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
def forward(
    self, h: Float[torch.Tensor, "batch tokens channels"]
) -> Float[torch.Tensor, "batch tokens channels"]:
    """Return positional embeddings matching hidden-state length.

    Args:
        h: Hidden states shaped ``(batch, sequence, dim)``.

    Returns:
        Positional embedding tensor shaped like ``h``.
    """
    batch, seq_len = h.shape[:2]
    elem_emb = repeat(self.elem_emb, "s d -> (s x) d", x=self.n_attr_per_elem)
    attr_emb = repeat(self.attr_emb, "x d -> (s x) d", s=self.n_elem)
    emb = (elem_emb + attr_emb)[:seq_len]
    return repeat(emb, "s d -> b s d", b=batch)

SinusoidalPosEmb

Bases: Module

Sinusoidal timestep or position embedding.

Origin

This is the VQ-Diffusion-style sinusoidal timestep embedding carried by LayoutDM and LACE. The checkpoint operation order is preserved exactly because LACE denoiser parity is bit-sensitive at rescale_steps=4000.

Parameters:

Name Type Description Default
num_steps int

Maximum number of positions or timesteps.

required
dim int

Embedding dimension. Odd dimensions keep the checkpoint truncation behavior and return 2 * floor(dim / 2) channels.

required
rescale_steps int

Rescaling constant used by the released checkpoints.

4000
Source code in lib/laygen/src/laygen/nn/embeddings.py
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
class SinusoidalPosEmb(nn.Module):
    """Sinusoidal timestep or position embedding.

    Origin:
        This is the VQ-Diffusion-style sinusoidal timestep embedding carried by
        LayoutDM and LACE. The checkpoint operation order is preserved exactly
        because LACE denoiser parity is bit-sensitive at ``rescale_steps=4000``.

    Args:
        num_steps: Maximum number of positions or timesteps.
        dim: Embedding dimension. Odd dimensions keep the checkpoint truncation
            behavior and return ``2 * floor(dim / 2)`` channels.
        rescale_steps: Rescaling constant used by the released checkpoints.
    """

    def __init__(self, num_steps: int, dim: int, rescale_steps: int = 4000) -> None:
        """Initialize the embedding parameters."""
        super().__init__()
        self.dim = dim
        self.num_steps = float(num_steps)
        self.rescale_steps = float(rescale_steps)

    def forward(
        self, x: Int[torch.Tensor, "batch"]
    ) -> Float[torch.Tensor, "batch channels"]:
        """Embed integer positions or timesteps.

        Args:
            x: One-dimensional tensor of positions.

        Returns:
            Sinusoidal embedding tensor.
        """
        x = x / self.num_steps * self.rescale_steps
        half_dim = self.dim // 2
        emb = math.log(10000) / (half_dim - 1)
        emb = torch.exp(torch.arange(half_dim, device=x.device) * -emb)
        emb = x[:, None] * emb[None, :]
        return torch.cat((emb.sin(), emb.cos()), dim=-1)

__init__

__init__(
    num_steps: int, dim: int, rescale_steps: int = 4000
) -> None

Initialize the embedding parameters.

Source code in lib/laygen/src/laygen/nn/embeddings.py
76
77
78
79
80
81
def __init__(self, num_steps: int, dim: int, rescale_steps: int = 4000) -> None:
    """Initialize the embedding parameters."""
    super().__init__()
    self.dim = dim
    self.num_steps = float(num_steps)
    self.rescale_steps = float(rescale_steps)

forward

forward(
    x: Int[Tensor, "batch"],
) -> Float[torch.Tensor, "batch channels"]

Embed integer positions or timesteps.

Parameters:

Name Type Description Default
x Int[Tensor, 'batch']

One-dimensional tensor of positions.

required

Returns:

Type Description
Float[Tensor, 'batch channels']

Sinusoidal embedding tensor.

Source code in lib/laygen/src/laygen/nn/embeddings.py
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
def forward(
    self, x: Int[torch.Tensor, "batch"]
) -> Float[torch.Tensor, "batch channels"]:
    """Embed integer positions or timesteps.

    Args:
        x: One-dimensional tensor of positions.

    Returns:
        Sinusoidal embedding tensor.
    """
    x = x / self.num_steps * self.rescale_steps
    half_dim = self.dim // 2
    emb = math.log(10000) / (half_dim - 1)
    emb = torch.exp(torch.arange(half_dim, device=x.device) * -emb)
    emb = x[:, None] * emb[None, :]
    return torch.cat((emb.sin(), emb.cos()), dim=-1)

CategoricalTransformer

Bases: Module

Token transformer that predicts LayoutDM categorical logits.

Source code in models/layout-dm/src/layout_dm/modeling_layout_dm.py
 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
class CategoricalTransformer(nn.Module):
    """Token transformer that predicts LayoutDM categorical logits."""

    def __init__(
        self,
        *,
        vocab_size: int,
        max_token_length: int,
        hidden_size: int,
        num_attention_heads: int,
        num_hidden_layers: int,
        intermediate_size: int,
        dropout: float = 0.0,
        timestep_type: Literal["adalayernorm", "adalayernorm_abs"]
        | None = "adalayernorm",
    ) -> None:
        """Initialize the categorical transformer denoiser backbone."""
        super().__init__()
        layer = Block(
            d_model=hidden_size,
            nhead=num_attention_heads,
            dim_feedforward=intermediate_size,
            dropout=dropout,
            batch_first=True,
            norm_first=True,
            diffusion_step=100,
            timestep_type=timestep_type,
        )
        self.backbone = TransformerEncoder(layer, num_hidden_layers)
        self.cat_emb = nn.Embedding(vocab_size, hidden_size)
        self.pos_emb = ElementPositionalEmbedding(
            hidden_size, max_token_length, n_attr_per_elem=5
        )
        self.drop = nn.Dropout(0.1)
        self.head = nn.Sequential(
            nn.LayerNorm(hidden_size), nn.Linear(hidden_size, vocab_size, bias=False)
        )

    def forward(
        self,
        input_ids: Int[torch.Tensor, "batch tokens"],
        timestep: Int[torch.Tensor, "batch"] | None = None,
    ) -> dict[str, Shaped[torch.Tensor, "..."]]:
        """Predict logits for flattened LayoutDM token ids."""
        hidden = self.cat_emb(input_ids)
        hidden = self.drop(hidden + self.pos_emb(hidden))
        hidden = self.backbone(hidden, timestep=timestep)
        return {"logits": self.head(hidden)}

__init__

__init__(
    *,
    vocab_size: int,
    max_token_length: int,
    hidden_size: int,
    num_attention_heads: int,
    num_hidden_layers: int,
    intermediate_size: int,
    dropout: float = 0.0,
    timestep_type: Literal[
        "adalayernorm", "adalayernorm_abs"
    ]
    | None = "adalayernorm",
) -> None

Initialize the categorical transformer denoiser backbone.

Source code in models/layout-dm/src/layout_dm/modeling_layout_dm.py
 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
def __init__(
    self,
    *,
    vocab_size: int,
    max_token_length: int,
    hidden_size: int,
    num_attention_heads: int,
    num_hidden_layers: int,
    intermediate_size: int,
    dropout: float = 0.0,
    timestep_type: Literal["adalayernorm", "adalayernorm_abs"]
    | None = "adalayernorm",
) -> None:
    """Initialize the categorical transformer denoiser backbone."""
    super().__init__()
    layer = Block(
        d_model=hidden_size,
        nhead=num_attention_heads,
        dim_feedforward=intermediate_size,
        dropout=dropout,
        batch_first=True,
        norm_first=True,
        diffusion_step=100,
        timestep_type=timestep_type,
    )
    self.backbone = TransformerEncoder(layer, num_hidden_layers)
    self.cat_emb = nn.Embedding(vocab_size, hidden_size)
    self.pos_emb = ElementPositionalEmbedding(
        hidden_size, max_token_length, n_attr_per_elem=5
    )
    self.drop = nn.Dropout(0.1)
    self.head = nn.Sequential(
        nn.LayerNorm(hidden_size), nn.Linear(hidden_size, vocab_size, bias=False)
    )

forward

forward(
    input_ids: Int[Tensor, "batch tokens"],
    timestep: Int[Tensor, "batch"] | None = None,
) -> dict[str, Shaped[torch.Tensor, "..."]]

Predict logits for flattened LayoutDM token ids.

Source code in models/layout-dm/src/layout_dm/modeling_layout_dm.py
108
109
110
111
112
113
114
115
116
117
def forward(
    self,
    input_ids: Int[torch.Tensor, "batch tokens"],
    timestep: Int[torch.Tensor, "batch"] | None = None,
) -> dict[str, Shaped[torch.Tensor, "..."]]:
    """Predict logits for flattened LayoutDM token ids."""
    hidden = self.cat_emb(input_ids)
    hidden = self.drop(hidden + self.pos_emb(hidden))
    hidden = self.backbone(hidden, timestep=timestep)
    return {"logits": self.head(hidden)}

LayoutDMDenoiserOutput dataclass

Bases: BaseOutput

Denoiser output containing token logits.

Source code in models/layout-dm/src/layout_dm/modeling_layout_dm.py
120
121
122
123
124
@dataclass
class LayoutDMDenoiserOutput(BaseOutput):
    """Denoiser output containing token logits."""

    logits: Float[torch.Tensor, "batch tokens vocab"]

LayoutDMDenoiser

Bases: ModelMixin, ConfigMixin

Diffusers-compatible LayoutDM denoiser.

Parameters:

Name Type Description Default
vocab_size int

Size of the LayoutDM tokenizer vocabulary.

required
max_token_length int

Flattened token sequence length.

required
hidden_size int

Transformer hidden size.

464
num_attention_heads int

Number of attention heads.

8
num_hidden_layers int

Number of transformer layers.

4
intermediate_size int

Feed-forward hidden size.

1856
dropout float

Dropout probability.

0.0
timestep_type Literal['adalayernorm', 'adalayernorm_abs'] | None

Timestep-conditioning type.

'adalayernorm'

Examples:

>>> model = LayoutDMDenoiser(vocab_size=10, max_token_length=5, hidden_size=8,
...     num_attention_heads=2, num_hidden_layers=1, intermediate_size=16)
>>> model.config.vocab_size
10
Source code in models/layout-dm/src/layout_dm/modeling_layout_dm.py
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
class LayoutDMDenoiser(ModelMixin, ConfigMixin):
    """Diffusers-compatible LayoutDM denoiser.

    Args:
        vocab_size: Size of the LayoutDM tokenizer vocabulary.
        max_token_length: Flattened token sequence length.
        hidden_size: Transformer hidden size.
        num_attention_heads: Number of attention heads.
        num_hidden_layers: Number of transformer layers.
        intermediate_size: Feed-forward hidden size.
        dropout: Dropout probability.
        timestep_type: Timestep-conditioning type.

    Examples:
        >>> model = LayoutDMDenoiser(vocab_size=10, max_token_length=5, hidden_size=8,
        ...     num_attention_heads=2, num_hidden_layers=1, intermediate_size=16)
        >>> model.config.vocab_size
        10
    """

    config_name = "denoiser_config.json"

    @register_to_config
    def __init__(
        self,
        *,
        vocab_size: int,
        max_token_length: int,
        hidden_size: int = 464,
        num_attention_heads: int = 8,
        num_hidden_layers: int = 4,
        intermediate_size: int = 1856,
        dropout: float = 0.0,
        timestep_type: Literal["adalayernorm", "adalayernorm_abs"]
        | None = "adalayernorm",
    ) -> None:
        """Initialize the categorical transformer denoiser."""
        super().__init__()
        self.transformer = CategoricalTransformer(
            vocab_size=vocab_size,
            max_token_length=max_token_length,
            hidden_size=hidden_size,
            num_attention_heads=num_attention_heads,
            num_hidden_layers=num_hidden_layers,
            intermediate_size=intermediate_size,
            dropout=dropout,
            timestep_type=timestep_type,
        )
        self.apply(_init_layoutdm_weights)

    def forward(
        self,
        input_ids: Int[torch.Tensor, "batch tokens"],
        timesteps: Int[torch.Tensor, "batch"],
    ) -> LayoutDMDenoiserOutput:
        """Predict token logits for noised LayoutDM sequences."""
        return LayoutDMDenoiserOutput(
            logits=self.transformer(input_ids, timestep=timesteps)["logits"]
        )

    def predict_start_log_probs(
        self,
        input_ids: Int[torch.Tensor, "batch tokens"],
        timesteps: Int[torch.Tensor, "batch"],
    ) -> Float[torch.Tensor, "batch tokens vocab"]:
        """Predict log probabilities for the denoised start sequence."""
        logits = self(input_ids=input_ids, timesteps=timesteps).logits[:, :, :-1]
        log_pred = F.log_softmax(logits.double(), dim=-1).float()
        zero_mask = torch.full(
            (*log_pred.shape[:2], 1),
            -70.0,
            device=log_pred.device,
            dtype=log_pred.dtype,
        )
        return torch.cat((log_pred, zero_mask), dim=-1).clamp(-70.0, 0.0)

__init__

__init__(
    *,
    vocab_size: int,
    max_token_length: int,
    hidden_size: int = 464,
    num_attention_heads: int = 8,
    num_hidden_layers: int = 4,
    intermediate_size: int = 1856,
    dropout: float = 0.0,
    timestep_type: Literal[
        "adalayernorm", "adalayernorm_abs"
    ]
    | None = "adalayernorm",
) -> None

Initialize the categorical transformer denoiser.

Source code in models/layout-dm/src/layout_dm/modeling_layout_dm.py
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
@register_to_config
def __init__(
    self,
    *,
    vocab_size: int,
    max_token_length: int,
    hidden_size: int = 464,
    num_attention_heads: int = 8,
    num_hidden_layers: int = 4,
    intermediate_size: int = 1856,
    dropout: float = 0.0,
    timestep_type: Literal["adalayernorm", "adalayernorm_abs"]
    | None = "adalayernorm",
) -> None:
    """Initialize the categorical transformer denoiser."""
    super().__init__()
    self.transformer = CategoricalTransformer(
        vocab_size=vocab_size,
        max_token_length=max_token_length,
        hidden_size=hidden_size,
        num_attention_heads=num_attention_heads,
        num_hidden_layers=num_hidden_layers,
        intermediate_size=intermediate_size,
        dropout=dropout,
        timestep_type=timestep_type,
    )
    self.apply(_init_layoutdm_weights)

forward

forward(
    input_ids: Int[Tensor, "batch tokens"],
    timesteps: Int[Tensor, "batch"],
) -> LayoutDMDenoiserOutput

Predict token logits for noised LayoutDM sequences.

Source code in models/layout-dm/src/layout_dm/modeling_layout_dm.py
177
178
179
180
181
182
183
184
185
def forward(
    self,
    input_ids: Int[torch.Tensor, "batch tokens"],
    timesteps: Int[torch.Tensor, "batch"],
) -> LayoutDMDenoiserOutput:
    """Predict token logits for noised LayoutDM sequences."""
    return LayoutDMDenoiserOutput(
        logits=self.transformer(input_ids, timestep=timesteps)["logits"]
    )

predict_start_log_probs

predict_start_log_probs(
    input_ids: Int[Tensor, "batch tokens"],
    timesteps: Int[Tensor, "batch"],
) -> Float[torch.Tensor, "batch tokens vocab"]

Predict log probabilities for the denoised start sequence.

Source code in models/layout-dm/src/layout_dm/modeling_layout_dm.py
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
def predict_start_log_probs(
    self,
    input_ids: Int[torch.Tensor, "batch tokens"],
    timesteps: Int[torch.Tensor, "batch"],
) -> Float[torch.Tensor, "batch tokens vocab"]:
    """Predict log probabilities for the denoised start sequence."""
    logits = self(input_ids=input_ids, timesteps=timesteps).logits[:, :, :-1]
    log_pred = F.log_softmax(logits.double(), dim=-1).float()
    zero_mask = torch.full(
        (*log_pred.shape[:2], 1),
        -70.0,
        device=log_pred.device,
        dtype=log_pred.dtype,
    )
    return torch.cat((log_pred, zero_mask), dim=-1).clamp(-70.0, 0.0)

pipeline_layout_dm

Diffusers pipeline for converted LayoutDM checkpoints.

LayoutDMPipeline

Bases: DiffusionPipeline

Generate layouts with a converted LayoutDM denoiser and scheduler.

Parameters:

Name Type Description Default
denoiser LayoutDMDenoiser

LayoutDM denoiser model.

required
scheduler LayoutDMScheduler

Discrete diffusion scheduler.

required
tokenizer LayoutDMTokenizer

Structured layout tokenizer.

required
processor LayoutDMProcessor | None

Optional input processor. A default processor is created when omitted.

None

Examples:

>>> from collections.abc import Mapping, Sequence

from pathlib import Path >>> path = Path(".cache/layout-dm/converted/layoutdm-rico25") >>> path.exists() # doctest: +SKIP True >>> pipe = LayoutDMPipeline.from_pretrained(path) # doctest: +SKIP >>> out = pipe(batch_size=1, seed=0, num_inference_steps=1) # doctest: +SKIP >>> out.bbox.shape[-1] # doctest: +SKIP 4

Source code in models/layout-dm/src/layout_dm/pipeline_layout_dm.py
 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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
class LayoutDMPipeline(DiffusionPipeline):
    """Generate layouts with a converted LayoutDM denoiser and scheduler.

    Args:
        denoiser: LayoutDM denoiser model.
        scheduler: Discrete diffusion scheduler.
        tokenizer: Structured layout tokenizer.
        processor: Optional input processor. A default processor is created
            when omitted.

    Examples:
        >>> from collections.abc import Mapping, Sequence
    from pathlib import Path
        >>> path = Path(".cache/layout-dm/converted/layoutdm-rico25")
        >>> path.exists()  # doctest: +SKIP
        True
        >>> pipe = LayoutDMPipeline.from_pretrained(path)  # doctest: +SKIP
        >>> out = pipe(batch_size=1, seed=0, num_inference_steps=1)  # doctest: +SKIP
        >>> out.bbox.shape[-1]  # doctest: +SKIP
        4
    """

    model_cpu_offload_seq = "denoiser"

    def __init__(
        self,
        denoiser: LayoutDMDenoiser,
        scheduler: LayoutDMScheduler,
        tokenizer: LayoutDMTokenizer,
        processor: LayoutDMProcessor | None = None,
    ) -> None:
        """Initialize and register LayoutDM pipeline modules."""
        super().__init__()
        self.register_modules(
            denoiser=denoiser, scheduler=scheduler, tokenizer=tokenizer
        )
        self.tokenizer = tokenizer
        self.processor = processor or LayoutDMProcessor(tokenizer)
        self.denoiser.eval()

    @torch.no_grad()
    def __call__(
        self,
        *,
        batch_size: int = 1,
        seed: int | None = None,
        generator: torch.Generator | None = None,
        condition_type: ConditionType | str = ConditionType.unconditional,
        labels: Int[torch.Tensor, "batch elements"]
        | Int[np.ndarray, "batch elements"]
        | Sequence[ArrayLikeInput]
        | None = None,
        bbox: Float[torch.Tensor, "batch elements 4"]
        | Float[np.ndarray, "batch elements 4"]
        | Sequence[ArrayLikeInput]
        | None = None,
        mask: Bool[torch.Tensor, "batch elements"]
        | Bool[np.ndarray, "batch elements"]
        | Sequence[ArrayLikeInput]
        | None = None,
        num_elements: int | list[int] | Int[torch.Tensor, "batch"] | None = None,
        box_format: BoxFormat | str = BoxFormat.xywh,
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
        num_inference_steps: int | None = None,
        sampling: SamplingMode | str = SamplingMode.random,
        temperature: float = 1.0,
        top_k: int = 5,
        top_p: float = 0.9,
        output_type: Literal["dataclass", "dict"] = "dataclass",
        return_intermediates: bool = False,
        **model_kwargs: str | int | float | bool | None,
    ) -> LayoutGenerationOutput | dict[str, Shaped[torch.Tensor, "..."]]:
        """Run unconditional or conditional layout generation.

        Args:
            batch_size: Number of layouts generated for unconditional sampling.
            seed: Optional seed used only when ``generator`` is omitted.
            generator: Optional torch generator. Takes precedence over ``seed``.
            condition_type: Canonical condition type or supported source alias.
            labels: Optional labels used by conditional modes.
            bbox: Optional boxes used by conditional modes.
            mask: Optional valid-element mask for conditional inputs.
            num_elements: Reserved compatibility argument.
            box_format: Format of conditional input boxes.
            normalized: Whether conditional boxes are already normalized.
            canvas_size: Pixel canvas size used when ``normalized=False``.
            num_inference_steps: Optional shortened diffusion step count.
            sampling: Sampling strategy.
            temperature: Random sampling temperature.
            top_k: Top-k value for top-k modes.
            top_p: Top-p value for top-p modes.
            output_type: ``"dataclass"`` or ``"dict"``.
            return_intermediates: Whether to return sampling trajectory data.
            **model_kwargs: Reserved compatibility keyword arguments.

        Returns:
            ``LayoutGenerationOutput`` by default, or a dictionary when
            ``output_type="dict"``.

        Raises:
            ValueError: If a conditional mode is missing ``bbox`` or ``labels``,
                or if ``output_type`` is unsupported.
        """
        _ = (num_elements, model_kwargs)
        if generator is None and seed is not None:
            generator = torch.Generator(device=self.device).manual_seed(seed)
        canonical = normalize_condition_type(condition_type)
        condition = None
        if canonical is not ConditionType.unconditional:
            missing_inputs = [
                name
                for name, value in (("bbox", bbox), ("labels", labels))
                if value is None
            ]
            if missing_inputs:
                message = (
                    f"bbox and labels are required for condition_type={condition_type}"
                )
                raise ValueError(message)

            processor_inputs = {
                "bbox": bbox,
                "labels": labels,
                "mask": mask,
                "box_format": box_format,
                "normalized": normalized,
                "canvas_size": canvas_size,
            }
            processed = self.processor(**processor_inputs)
            decoded_input = self.tokenizer.decode_layout(processed["input_ids"])
            condition = build_condition(
                self.tokenizer,
                cond_type=canonical,
                bbox=decoded_input["bbox"],
                labels=decoded_input["labels"],
                mask=decoded_input["mask"],
            )
            batch_size = condition.input_ids.shape[0]
        sampling_config = LayoutDMSamplingConfig(
            name=sampling,
            temperature=temperature,
            top_k=top_k,
            top_p=top_p,
            num_inference_steps=num_inference_steps,
        )
        self.scheduler.set_timesteps(num_inference_steps, device=self.device)
        sample = self.scheduler.initial_sample(
            batch_size,
            self.tokenizer.config.max_token_length,
            device=self.device,
            condition=condition,
        )
        trajectory = [] if return_intermediates else None
        previous_timestep = self.scheduler.config.num_timesteps
        for timestep in self.scheduler.timesteps:
            timestep_batch = torch.full(
                (batch_size,),
                int(timestep.item()),
                device=self.device,
                dtype=torch.long,
            )
            input_ids = log_onehot_to_index(sample)
            logits = self.denoiser(input_ids=input_ids, timesteps=timestep_batch).logits
            out = self.scheduler.step(
                logits,
                timestep_batch,
                sample,
                previous_timestep=previous_timestep,
                sampling=sampling_config,
                condition=condition,
                generator=generator,
            )
            sample = out.prev_sample
            previous_timestep = int(timestep.item())
            if trajectory is not None:
                trajectory.append(log_onehot_to_index(sample).detach().cpu())
        sequences = log_onehot_to_index(sample).detach().cpu()
        decoded = self.tokenizer.decode_layout(sequences)
        output = LayoutGenerationOutput(
            bbox=decoded["bbox"],
            labels=decoded["labels"],
            mask=decoded["mask"],
            id2label=self.tokenizer.config.id2label,
            sequences=sequences,
            trajectory=trajectory,
            intermediates={"condition_type": str(canonical)}
            if return_intermediates
            else None,
        )
        if output_type == "dict":
            return dict(output)
        if output_type != "dataclass":
            raise ValueError(f"Unsupported output_type: {output_type}")

        return output

    generate = __call__

    def save_pretrained(
        self, save_directory: str | Path, **kwargs: LayoutDMPipelineKwarg
    ) -> None:
        """Save the pipeline and tokenizer to a Diffusers directory."""
        super().save_pretrained(save_directory, **kwargs)

    @classmethod
    def from_pretrained(
        cls, pretrained_model_name_or_path: str | Path, **kwargs: LayoutDMPipelineKwarg
    ) -> "LayoutDMPipeline":
        """Load a LayoutDM pipeline from a local directory or Hub repo.

        Args:
            pretrained_model_name_or_path: Diffusers pipeline directory or Hub id.
            **kwargs: Additional arguments forwarded to Diffusers.

        Returns:
            Loaded pipeline with a matching ``LayoutDMProcessor``.
        """
        tokenizer = LayoutDMTokenizer.from_pretrained(pretrained_model_name_or_path)
        kwargs.setdefault("tokenizer", tokenizer)
        pipe = super().from_pretrained(pretrained_model_name_or_path, **kwargs)
        pipe.processor = LayoutDMProcessor(pipe.tokenizer)
        return pipe

__init__

__init__(
    denoiser: LayoutDMDenoiser,
    scheduler: LayoutDMScheduler,
    tokenizer: LayoutDMTokenizer,
    processor: LayoutDMProcessor | None = None,
) -> None

Initialize and register LayoutDM pipeline modules.

Source code in models/layout-dm/src/layout_dm/pipeline_layout_dm.py
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
def __init__(
    self,
    denoiser: LayoutDMDenoiser,
    scheduler: LayoutDMScheduler,
    tokenizer: LayoutDMTokenizer,
    processor: LayoutDMProcessor | None = None,
) -> None:
    """Initialize and register LayoutDM pipeline modules."""
    super().__init__()
    self.register_modules(
        denoiser=denoiser, scheduler=scheduler, tokenizer=tokenizer
    )
    self.tokenizer = tokenizer
    self.processor = processor or LayoutDMProcessor(tokenizer)
    self.denoiser.eval()

__call__

__call__(
    *,
    batch_size: int = 1,
    seed: int | None = None,
    generator: Generator | None = None,
    condition_type: ConditionType
    | str = ConditionType.unconditional,
    labels: Int[Tensor, "batch elements"]
    | Int[ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    bbox: Float[Tensor, "batch elements 4"]
    | Float[ndarray, "batch elements 4"]
    | Sequence[ArrayLikeInput]
    | None = None,
    mask: Bool[Tensor, "batch elements"]
    | Bool[ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    num_elements: int
    | list[int]
    | Int[Tensor, "batch"]
    | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    num_inference_steps: int | None = None,
    sampling: SamplingMode | str = SamplingMode.random,
    temperature: float = 1.0,
    top_k: int = 5,
    top_p: float = 0.9,
    output_type: Literal["dataclass", "dict"] = "dataclass",
    return_intermediates: bool = False,
    **model_kwargs: str | int | float | bool | None,
) -> (
    LayoutGenerationOutput
    | dict[str, Shaped[torch.Tensor, "..."]]
)

Run unconditional or conditional layout generation.

Parameters:

Name Type Description Default
batch_size int

Number of layouts generated for unconditional sampling.

1
seed int | None

Optional seed used only when generator is omitted.

None
generator Generator | None

Optional torch generator. Takes precedence over seed.

None
condition_type ConditionType | str

Canonical condition type or supported source alias.

unconditional
labels Int[Tensor, 'batch elements'] | Int[ndarray, 'batch elements'] | Sequence[ArrayLikeInput] | None

Optional labels used by conditional modes.

None
bbox Float[Tensor, 'batch elements 4'] | Float[ndarray, 'batch elements 4'] | Sequence[ArrayLikeInput] | None

Optional boxes used by conditional modes.

None
mask Bool[Tensor, 'batch elements'] | Bool[ndarray, 'batch elements'] | Sequence[ArrayLikeInput] | None

Optional valid-element mask for conditional inputs.

None
num_elements int | list[int] | Int[Tensor, 'batch'] | None

Reserved compatibility argument.

None
box_format BoxFormat | str

Format of conditional input boxes.

xywh
normalized bool

Whether conditional boxes are already normalized.

True
canvas_size tuple[int, int] | None

Pixel canvas size used when normalized=False.

None
num_inference_steps int | None

Optional shortened diffusion step count.

None
sampling SamplingMode | str

Sampling strategy.

random
temperature float

Random sampling temperature.

1.0
top_k int

Top-k value for top-k modes.

5
top_p float

Top-p value for top-p modes.

0.9
output_type Literal['dataclass', 'dict']

"dataclass" or "dict".

'dataclass'
return_intermediates bool

Whether to return sampling trajectory data.

False
**model_kwargs str | int | float | bool | None

Reserved compatibility keyword arguments.

{}

Returns:

Type Description
LayoutGenerationOutput | dict[str, Shaped[Tensor, '...']]

LayoutGenerationOutput by default, or a dictionary when

LayoutGenerationOutput | dict[str, Shaped[Tensor, '...']]

output_type="dict".

Raises:

Type Description
ValueError

If a conditional mode is missing bbox or labels, or if output_type is unsupported.

Source code in models/layout-dm/src/layout_dm/pipeline_layout_dm.py
 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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
@torch.no_grad()
def __call__(
    self,
    *,
    batch_size: int = 1,
    seed: int | None = None,
    generator: torch.Generator | None = None,
    condition_type: ConditionType | str = ConditionType.unconditional,
    labels: Int[torch.Tensor, "batch elements"]
    | Int[np.ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    bbox: Float[torch.Tensor, "batch elements 4"]
    | Float[np.ndarray, "batch elements 4"]
    | Sequence[ArrayLikeInput]
    | None = None,
    mask: Bool[torch.Tensor, "batch elements"]
    | Bool[np.ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    num_elements: int | list[int] | Int[torch.Tensor, "batch"] | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    num_inference_steps: int | None = None,
    sampling: SamplingMode | str = SamplingMode.random,
    temperature: float = 1.0,
    top_k: int = 5,
    top_p: float = 0.9,
    output_type: Literal["dataclass", "dict"] = "dataclass",
    return_intermediates: bool = False,
    **model_kwargs: str | int | float | bool | None,
) -> LayoutGenerationOutput | dict[str, Shaped[torch.Tensor, "..."]]:
    """Run unconditional or conditional layout generation.

    Args:
        batch_size: Number of layouts generated for unconditional sampling.
        seed: Optional seed used only when ``generator`` is omitted.
        generator: Optional torch generator. Takes precedence over ``seed``.
        condition_type: Canonical condition type or supported source alias.
        labels: Optional labels used by conditional modes.
        bbox: Optional boxes used by conditional modes.
        mask: Optional valid-element mask for conditional inputs.
        num_elements: Reserved compatibility argument.
        box_format: Format of conditional input boxes.
        normalized: Whether conditional boxes are already normalized.
        canvas_size: Pixel canvas size used when ``normalized=False``.
        num_inference_steps: Optional shortened diffusion step count.
        sampling: Sampling strategy.
        temperature: Random sampling temperature.
        top_k: Top-k value for top-k modes.
        top_p: Top-p value for top-p modes.
        output_type: ``"dataclass"`` or ``"dict"``.
        return_intermediates: Whether to return sampling trajectory data.
        **model_kwargs: Reserved compatibility keyword arguments.

    Returns:
        ``LayoutGenerationOutput`` by default, or a dictionary when
        ``output_type="dict"``.

    Raises:
        ValueError: If a conditional mode is missing ``bbox`` or ``labels``,
            or if ``output_type`` is unsupported.
    """
    _ = (num_elements, model_kwargs)
    if generator is None and seed is not None:
        generator = torch.Generator(device=self.device).manual_seed(seed)
    canonical = normalize_condition_type(condition_type)
    condition = None
    if canonical is not ConditionType.unconditional:
        missing_inputs = [
            name
            for name, value in (("bbox", bbox), ("labels", labels))
            if value is None
        ]
        if missing_inputs:
            message = (
                f"bbox and labels are required for condition_type={condition_type}"
            )
            raise ValueError(message)

        processor_inputs = {
            "bbox": bbox,
            "labels": labels,
            "mask": mask,
            "box_format": box_format,
            "normalized": normalized,
            "canvas_size": canvas_size,
        }
        processed = self.processor(**processor_inputs)
        decoded_input = self.tokenizer.decode_layout(processed["input_ids"])
        condition = build_condition(
            self.tokenizer,
            cond_type=canonical,
            bbox=decoded_input["bbox"],
            labels=decoded_input["labels"],
            mask=decoded_input["mask"],
        )
        batch_size = condition.input_ids.shape[0]
    sampling_config = LayoutDMSamplingConfig(
        name=sampling,
        temperature=temperature,
        top_k=top_k,
        top_p=top_p,
        num_inference_steps=num_inference_steps,
    )
    self.scheduler.set_timesteps(num_inference_steps, device=self.device)
    sample = self.scheduler.initial_sample(
        batch_size,
        self.tokenizer.config.max_token_length,
        device=self.device,
        condition=condition,
    )
    trajectory = [] if return_intermediates else None
    previous_timestep = self.scheduler.config.num_timesteps
    for timestep in self.scheduler.timesteps:
        timestep_batch = torch.full(
            (batch_size,),
            int(timestep.item()),
            device=self.device,
            dtype=torch.long,
        )
        input_ids = log_onehot_to_index(sample)
        logits = self.denoiser(input_ids=input_ids, timesteps=timestep_batch).logits
        out = self.scheduler.step(
            logits,
            timestep_batch,
            sample,
            previous_timestep=previous_timestep,
            sampling=sampling_config,
            condition=condition,
            generator=generator,
        )
        sample = out.prev_sample
        previous_timestep = int(timestep.item())
        if trajectory is not None:
            trajectory.append(log_onehot_to_index(sample).detach().cpu())
    sequences = log_onehot_to_index(sample).detach().cpu()
    decoded = self.tokenizer.decode_layout(sequences)
    output = LayoutGenerationOutput(
        bbox=decoded["bbox"],
        labels=decoded["labels"],
        mask=decoded["mask"],
        id2label=self.tokenizer.config.id2label,
        sequences=sequences,
        trajectory=trajectory,
        intermediates={"condition_type": str(canonical)}
        if return_intermediates
        else None,
    )
    if output_type == "dict":
        return dict(output)
    if output_type != "dataclass":
        raise ValueError(f"Unsupported output_type: {output_type}")

    return output

save_pretrained

save_pretrained(
    save_directory: str | Path,
    **kwargs: LayoutDMPipelineKwarg,
) -> None

Save the pipeline and tokenizer to a Diffusers directory.

Source code in models/layout-dm/src/layout_dm/pipeline_layout_dm.py
243
244
245
246
247
def save_pretrained(
    self, save_directory: str | Path, **kwargs: LayoutDMPipelineKwarg
) -> None:
    """Save the pipeline and tokenizer to a Diffusers directory."""
    super().save_pretrained(save_directory, **kwargs)

from_pretrained classmethod

from_pretrained(
    pretrained_model_name_or_path: str | Path,
    **kwargs: LayoutDMPipelineKwarg,
) -> "LayoutDMPipeline"

Load a LayoutDM pipeline from a local directory or Hub repo.

Parameters:

Name Type Description Default
pretrained_model_name_or_path str | Path

Diffusers pipeline directory or Hub id.

required
**kwargs LayoutDMPipelineKwarg

Additional arguments forwarded to Diffusers.

{}

Returns:

Type Description
'LayoutDMPipeline'

Loaded pipeline with a matching LayoutDMProcessor.

Source code in models/layout-dm/src/layout_dm/pipeline_layout_dm.py
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
@classmethod
def from_pretrained(
    cls, pretrained_model_name_or_path: str | Path, **kwargs: LayoutDMPipelineKwarg
) -> "LayoutDMPipeline":
    """Load a LayoutDM pipeline from a local directory or Hub repo.

    Args:
        pretrained_model_name_or_path: Diffusers pipeline directory or Hub id.
        **kwargs: Additional arguments forwarded to Diffusers.

    Returns:
        Loaded pipeline with a matching ``LayoutDMProcessor``.
    """
    tokenizer = LayoutDMTokenizer.from_pretrained(pretrained_model_name_or_path)
    kwargs.setdefault("tokenizer", tokenizer)
    pipe = super().from_pretrained(pretrained_model_name_or_path, **kwargs)
    pipe.processor = LayoutDMProcessor(pipe.tokenizer)
    return pipe

processing_layout_dm

Input processor for LayoutDM structured layout tensors.

LayoutDMProcessor

Bases: ProcessorMixin

Normalize layout arrays and encode them with LayoutDMTokenizer.

Parameters:

Name Type Description Default
tokenizer LayoutDMTokenizer

Tokenizer used to encode processed layouts.

required

Examples:

>>> from layout_dm.configuration_layout_dm import LayoutDMConfig
>>> from layout_dm.tokenization_layout_dm import LayoutDMTokenizer
>>> processor = LayoutDMProcessor(LayoutDMTokenizer(LayoutDMConfig(dataset_name="publaynet")))
>>> sorted(processor(bbox=[[[0.5, 0.5, 0.2, 0.2]]], labels=[[0]]))
['attention_mask', 'input_ids', 'mask']
Source code in models/layout-dm/src/layout_dm/processing_layout_dm.py
 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
class LayoutDMProcessor(ProcessorMixin):
    """Normalize layout arrays and encode them with ``LayoutDMTokenizer``.

    Args:
        tokenizer: Tokenizer used to encode processed layouts.

    Examples:
        >>> from layout_dm.configuration_layout_dm import LayoutDMConfig
        >>> from layout_dm.tokenization_layout_dm import LayoutDMTokenizer
        >>> processor = LayoutDMProcessor(LayoutDMTokenizer(LayoutDMConfig(dataset_name="publaynet")))
        >>> sorted(processor(bbox=[[[0.5, 0.5, 0.2, 0.2]]], labels=[[0]]))
        ['attention_mask', 'input_ids', 'mask']
    """

    config_name = "processor_config.json"
    tokenizer_class = "LayoutDMTokenizer"

    def __init__(self, tokenizer: LayoutDMTokenizer) -> None:
        """Initialize the processor with a tokenizer."""
        super().__init__(tokenizer=tokenizer)

    @classmethod
    def from_pretrained(
        cls,
        pretrained_model_name_or_path: str | PathLike[str],
        cache_dir: str | PathLike[str] | None = None,
        force_download: bool = False,
        local_files_only: bool = False,
        token: str | bool | None = None,
        revision: str = "main",
        **kwargs: str | int | float | bool | None,
    ) -> "LayoutDMProcessor":
        """Load a processor with the LayoutDM tokenizer implementation."""
        tokenizer = LayoutDMTokenizer.from_pretrained(
            pretrained_model_name_or_path,
            cache_dir=cache_dir,
            force_download=force_download,
            local_files_only=local_files_only,
            token=token,
            revision=revision,
            **kwargs,
        )
        return cls(tokenizer=tokenizer)

    def __call__(
        self,
        *,
        bbox: Float[torch.Tensor, "batch elements 4"]
        | Float[np.ndarray, "batch elements 4"]
        | Sequence[ArrayLikeInput],
        labels: Int[torch.Tensor, "batch elements"]
        | Int[np.ndarray, "batch elements"]
        | Sequence[ArrayLikeInput],
        mask: Bool[torch.Tensor, "batch elements"]
        | Bool[np.ndarray, "batch elements"]
        | Sequence[ArrayLikeInput]
        | None = None,
        box_format: BoxFormat | str = BoxFormat.xywh,
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
        return_tensors: Literal["pt"] = "pt",
    ) -> dict[str, Shaped[torch.Tensor, "..."]]:
        """Process a layout batch into model input tensors.

        Args:
            bbox: Layout boxes in ``box_format``.
            labels: Integer labels matching the layout boxes.
            mask: Optional valid-element mask. All elements are valid when omitted.
            box_format: Input box format.
            normalized: Whether boxes are already normalized to ``[0, 1]``.
            canvas_size: Pixel canvas size required when ``normalized=False``.
            return_tensors: Tensor backend. Only ``"pt"`` is supported.

        Returns:
            Tokenizer output containing ``input_ids``, ``attention_mask``, and
            ``mask`` tensors.

        Raises:
            ValueError: If ``return_tensors`` is not ``"pt"`` or if
                ``canvas_size`` is missing for pixel-space boxes.
        """
        if return_tensors != "pt":
            raise ValueError("LayoutDMProcessor only supports return_tensors='pt'")

        bbox_t, labels_t, mask_t = prepare_layout_tensors(
            bbox=bbox,
            labels=labels,
            mask=mask,
            box_format=box_format,
            normalized=normalized,
            canvas_size=canvas_size,
        )
        return self.tokenizer.encode_layout(bbox=bbox_t, labels=labels_t, mask=mask_t)

__init__

__init__(tokenizer: LayoutDMTokenizer) -> None

Initialize the processor with a tokenizer.

Source code in models/layout-dm/src/layout_dm/processing_layout_dm.py
40
41
42
def __init__(self, tokenizer: LayoutDMTokenizer) -> None:
    """Initialize the processor with a tokenizer."""
    super().__init__(tokenizer=tokenizer)

from_pretrained classmethod

from_pretrained(
    pretrained_model_name_or_path: str | PathLike[str],
    cache_dir: str | PathLike[str] | None = None,
    force_download: bool = False,
    local_files_only: bool = False,
    token: str | bool | None = None,
    revision: str = "main",
    **kwargs: str | int | float | bool | None,
) -> "LayoutDMProcessor"

Load a processor with the LayoutDM tokenizer implementation.

Source code in models/layout-dm/src/layout_dm/processing_layout_dm.py
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
@classmethod
def from_pretrained(
    cls,
    pretrained_model_name_or_path: str | PathLike[str],
    cache_dir: str | PathLike[str] | None = None,
    force_download: bool = False,
    local_files_only: bool = False,
    token: str | bool | None = None,
    revision: str = "main",
    **kwargs: str | int | float | bool | None,
) -> "LayoutDMProcessor":
    """Load a processor with the LayoutDM tokenizer implementation."""
    tokenizer = LayoutDMTokenizer.from_pretrained(
        pretrained_model_name_or_path,
        cache_dir=cache_dir,
        force_download=force_download,
        local_files_only=local_files_only,
        token=token,
        revision=revision,
        **kwargs,
    )
    return cls(tokenizer=tokenizer)

__call__

__call__(
    *,
    bbox: Float[Tensor, "batch elements 4"]
    | Float[ndarray, "batch elements 4"]
    | Sequence[ArrayLikeInput],
    labels: Int[Tensor, "batch elements"]
    | Int[ndarray, "batch elements"]
    | Sequence[ArrayLikeInput],
    mask: Bool[Tensor, "batch elements"]
    | Bool[ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    return_tensors: Literal["pt"] = "pt",
) -> dict[str, Shaped[torch.Tensor, "..."]]

Process a layout batch into model input tensors.

Parameters:

Name Type Description Default
bbox Float[Tensor, 'batch elements 4'] | Float[ndarray, 'batch elements 4'] | Sequence[ArrayLikeInput]

Layout boxes in box_format.

required
labels Int[Tensor, 'batch elements'] | Int[ndarray, 'batch elements'] | Sequence[ArrayLikeInput]

Integer labels matching the layout boxes.

required
mask Bool[Tensor, 'batch elements'] | Bool[ndarray, 'batch elements'] | Sequence[ArrayLikeInput] | None

Optional valid-element mask. All elements are valid when omitted.

None
box_format BoxFormat | str

Input box format.

xywh
normalized bool

Whether boxes are already normalized to [0, 1].

True
canvas_size tuple[int, int] | None

Pixel canvas size required when normalized=False.

None
return_tensors Literal['pt']

Tensor backend. Only "pt" is supported.

'pt'

Returns:

Type Description
dict[str, Shaped[Tensor, '...']]

Tokenizer output containing input_ids, attention_mask, and

dict[str, Shaped[Tensor, '...']]

mask tensors.

Raises:

Type Description
ValueError

If return_tensors is not "pt" or if canvas_size is missing for pixel-space boxes.

Source code in models/layout-dm/src/layout_dm/processing_layout_dm.py
 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
def __call__(
    self,
    *,
    bbox: Float[torch.Tensor, "batch elements 4"]
    | Float[np.ndarray, "batch elements 4"]
    | Sequence[ArrayLikeInput],
    labels: Int[torch.Tensor, "batch elements"]
    | Int[np.ndarray, "batch elements"]
    | Sequence[ArrayLikeInput],
    mask: Bool[torch.Tensor, "batch elements"]
    | Bool[np.ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    return_tensors: Literal["pt"] = "pt",
) -> dict[str, Shaped[torch.Tensor, "..."]]:
    """Process a layout batch into model input tensors.

    Args:
        bbox: Layout boxes in ``box_format``.
        labels: Integer labels matching the layout boxes.
        mask: Optional valid-element mask. All elements are valid when omitted.
        box_format: Input box format.
        normalized: Whether boxes are already normalized to ``[0, 1]``.
        canvas_size: Pixel canvas size required when ``normalized=False``.
        return_tensors: Tensor backend. Only ``"pt"`` is supported.

    Returns:
        Tokenizer output containing ``input_ids``, ``attention_mask``, and
        ``mask`` tensors.

    Raises:
        ValueError: If ``return_tensors`` is not ``"pt"`` or if
            ``canvas_size`` is missing for pixel-space boxes.
    """
    if return_tensors != "pt":
        raise ValueError("LayoutDMProcessor only supports return_tensors='pt'")

    bbox_t, labels_t, mask_t = prepare_layout_tensors(
        bbox=bbox,
        labels=labels,
        mask=mask,
        box_format=box_format,
        normalized=normalized,
        canvas_size=canvas_size,
    )
    return self.tokenizer.encode_layout(bbox=bbox_t, labels=labels_t, mask=mask_t)

sampling

Sampling configuration for LayoutDM reverse diffusion.

LayoutDMSamplingConfig dataclass

Sampling parameters passed from the pipeline to the scheduler.

Source code in models/layout-dm/src/layout_dm/sampling.py
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@dataclass
class LayoutDMSamplingConfig:
    """Sampling parameters passed from the pipeline to the scheduler."""

    name: SamplingMode | str = SamplingMode.random
    temperature: float = 1.0
    top_k: int = 5
    top_p: float = 0.9
    num_inference_steps: int | None = None
    time_difference: float = 0.0
    refine_lambda: float = 3.0
    refine_mode: Literal["uniform", "gaussian", "negative"] = "uniform"
    refine_offset_ratio: float = 0.1

    def __post_init__(self) -> None:
        """Normalize public string sampling values to ``SamplingMode``."""
        self.name = normalize_sampling_mode(self.name)

__post_init__

__post_init__() -> None

Normalize public string sampling values to SamplingMode.

Source code in models/layout-dm/src/layout_dm/sampling.py
25
26
27
def __post_init__(self) -> None:
    """Normalize public string sampling values to ``SamplingMode``."""
    self.name = normalize_sampling_mode(self.name)

scheduling_layout_dm

Discrete diffusion scheduler for converted LayoutDM pipelines.

LayoutDMSchedulerOutput dataclass

Bases: BaseOutput

Scheduler step output for LayoutDM reverse diffusion.

Source code in models/layout-dm/src/layout_dm/scheduling_layout_dm.py
29
30
31
32
33
34
35
@dataclass
class LayoutDMSchedulerOutput(BaseOutput):
    """Scheduler step output for LayoutDM reverse diffusion."""

    prev_sample: Float[torch.Tensor, "batch vocab tokens"]
    pred_original_sample: Float[torch.Tensor, "batch vocab tokens"] | None = None
    model_log_prob: Float[torch.Tensor, "batch vocab tokens"] | None = None

LayoutDMScheduler

Bases: SchedulerMixin, ConfigMixin

Diffusers-compatible scheduler for LayoutDM categorical diffusion.

Parameters:

Name Type Description Default
num_timesteps int

Number of training diffusion timesteps.

100
q_type Literal['constrained', 'vanilla']

Transition type from the original LayoutDM implementation.

'constrained'
vocab_size int

Full tokenizer vocabulary size.

required
mask_token_id int

Full vocabulary id used for mask tokens.

required
pad_token_id int

Full vocabulary id used for padding tokens.

required
var_order tuple[str, ...]

Per-element token variable order.

('c', 'x', 'y', 'w', 'h')
token_mask list[list[bool]] | None

Optional valid-token mask for each sequence position.

None
per_var_full_ids dict[str, list[int]] | None

Optional constrained vocabulary ids per variable.

None
att_1 float

Initial keep-probability schedule value.

0.99999
att_T float

Final keep-probability schedule value.

9e-06
ctt_1 float

Initial mask-probability schedule value.

9e-06
ctt_T float

Final mask-probability schedule value.

0.99999

Examples:

>>> scheduler = LayoutDMScheduler(vocab_size=8, mask_token_id=7, pad_token_id=6)
>>> scheduler.timesteps.shape[0]
100
Source code in models/layout-dm/src/layout_dm/scheduling_layout_dm.py
 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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
class LayoutDMScheduler(SchedulerMixin, ConfigMixin):
    """Diffusers-compatible scheduler for LayoutDM categorical diffusion.

    Args:
        num_timesteps: Number of training diffusion timesteps.
        q_type: Transition type from the original LayoutDM implementation.
        vocab_size: Full tokenizer vocabulary size.
        mask_token_id: Full vocabulary id used for mask tokens.
        pad_token_id: Full vocabulary id used for padding tokens.
        var_order: Per-element token variable order.
        token_mask: Optional valid-token mask for each sequence position.
        per_var_full_ids: Optional constrained vocabulary ids per variable.
        att_1: Initial keep-probability schedule value.
        att_T: Final keep-probability schedule value.
        ctt_1: Initial mask-probability schedule value.
        ctt_T: Final mask-probability schedule value.

    Examples:
        >>> scheduler = LayoutDMScheduler(vocab_size=8, mask_token_id=7, pad_token_id=6)
        >>> scheduler.timesteps.shape[0]
        100
    """

    config_name = "scheduler_config.json"
    order = 1

    @register_to_config
    def __init__(
        self,
        *,
        num_timesteps: int = 100,
        q_type: Literal["constrained", "vanilla"] = "constrained",
        vocab_size: int,
        mask_token_id: int,
        pad_token_id: int,
        var_order: tuple[str, ...] = ("c", "x", "y", "w", "h"),
        token_mask: list[list[bool]] | None = None,
        per_var_full_ids: dict[str, list[int]] | None = None,
        att_1: float = 0.99999,
        att_T: float = 0.000009,
        ctt_1: float = 0.000009,
        ctt_T: float = 0.99999,
    ) -> None:
        """Initialize LayoutDM transition schedules."""
        self.num_timesteps = num_timesteps
        self.timesteps = torch.arange(num_timesteps - 1, -1, -1)
        self.vocab_size = vocab_size
        self.mask_token_id = mask_token_id
        self.pad_token_id = pad_token_id
        self.var_order = tuple(var_order)
        self.token_mask = (
            None if token_mask is None else torch.tensor(token_mask, dtype=torch.bool)
        )
        self.per_var_full_ids = per_var_full_ids
        self.att_1 = att_1
        self.att_T = att_T
        self.ctt_1 = ctt_1
        self.ctt_T = ctt_T
        if per_var_full_ids is None:
            self.schedules = {
                "full": _alpha_schedule(
                    num_timesteps, vocab_size - 1, att_1, att_T, ctt_1, ctt_T
                )
            }
        else:
            self.schedules = {
                key: _alpha_schedule(
                    num_timesteps, len(ids) - 1, att_1, att_T, ctt_1, ctt_T
                )
                for key, ids in per_var_full_ids.items()
            }

    def set_timesteps(
        self, num_inference_steps: int | None = None, device: torch.device | None = None
    ) -> None:
        """Set reverse-diffusion timesteps for inference."""
        steps = num_inference_steps or self.num_timesteps
        self.timesteps = torch.tensor(
            [int(i * self.num_timesteps / steps) for i in range(steps - 1, -1, -1)],
            dtype=torch.long,
            device=device,
        )

    def initial_sample(
        self,
        batch_size: int,
        token_length: int,
        *,
        device: torch.device,
        condition: LayoutDMCondition | None = None,
    ) -> Float[torch.Tensor, "batch vocab tokens"]:
        """Create the initial log one-hot sample for reverse diffusion."""
        if condition is not None:
            ids = condition.input_ids.to(device)
        else:
            ids = torch.full(
                (batch_size, token_length),
                self.mask_token_id,
                dtype=torch.long,
                device=device,
            )
        return index_to_log_onehot(ids, self.vocab_size)

    def predict_start(
        self, denoiser_output: Float[torch.Tensor, "batch tokens vocab"]
    ) -> Float[torch.Tensor, "batch vocab tokens"]:
        """Convert denoiser logits to start-sequence log probabilities."""
        logits = denoiser_output[:, :, :-1]
        log_pred = torch.log_softmax(logits.double(), dim=-1).float()
        mask_col = torch.full(
            (*log_pred.shape[:2], 1),
            -70.0,
            device=log_pred.device,
            dtype=log_pred.dtype,
        )
        return (
            torch.cat((log_pred, mask_col), dim=-1).permute(0, 2, 1).clamp(-70.0, 0.0)
        )

    def q_posterior(
        self,
        log_x_start: Float[torch.Tensor, "batch vocab tokens"],
        log_x_t: Float[torch.Tensor, "batch vocab tokens"],
        t: Int[torch.Tensor, "batch"],
    ) -> Float[torch.Tensor, "batch vocab tokens"]:
        """Compute the LayoutDM posterior transition distribution."""
        if self.per_var_full_ids is not None:
            return self._constrained_q_posterior(log_x_start, log_x_t, t)
        return self._vanilla_q_posterior(log_x_start, log_x_t, t)

    def _vanilla_q_posterior(
        self,
        log_x_start: Float[torch.Tensor, "batch vocab tokens"],
        log_x_t: Float[torch.Tensor, "batch vocab tokens"],
        t: Int[torch.Tensor, "batch"],
    ) -> Float[torch.Tensor, "batch vocab tokens"]:
        """Compute the vanilla mask-and-replace posterior transition."""
        batch_size = log_x_start.size(0)
        index_x_t = log_onehot_to_index(log_x_t)
        mask = (index_x_t == self.mask_token_id).unsqueeze(1)
        log_one = torch.zeros(
            batch_size, 1, 1, device=log_x_t.device, dtype=log_x_t.dtype
        )
        log_zero = torch.log(log_one + 1.0e-30).expand(-1, -1, log_x_t.shape[-1])

        log_qt = self._vanilla_q_pred(log_x_t, t)[:, :-1, :]
        log_cumprod_ct = _extract(
            self.schedules["full"][5].to(t.device), t, log_x_start.shape
        )
        ct_cumprod = log_cumprod_ct.expand(-1, self.vocab_size - 1, -1)
        log_qt = (~mask) * log_qt + mask * ct_cumprod

        log_qt_one = self._vanilla_q_pred_one_timestep(log_x_t, t)
        log_qt_one = torch.cat((log_qt_one[:, :-1, :], log_zero), dim=1)
        log_ct = _extract(self.schedules["full"][2].to(t.device), t, log_x_start.shape)
        ct_vector = torch.cat(
            (log_ct.expand(-1, self.vocab_size - 1, -1), log_one), dim=1
        )
        log_qt_one = (~mask) * log_qt_one + mask * ct_vector

        q = torch.cat((log_x_start[:, :-1, :] - log_qt, log_zero), dim=1)
        q_log_sum_exp = torch.logsumexp(q, dim=1, keepdim=True)
        q = q - q_log_sum_exp
        return (self._vanilla_q_pred(q, t - 1) + log_qt_one + q_log_sum_exp).clamp(
            -70.0, 0.0
        )

    def _vanilla_q_pred_one_timestep(
        self,
        log_x_t: Float[torch.Tensor, "batch vocab tokens"],
        t: Int[torch.Tensor, "batch"],
    ) -> Float[torch.Tensor, "batch vocab tokens"]:
        """Apply one vanilla forward noising transition."""
        log_at, log_bt, log_ct = (
            self.schedules["full"][i].to(t.device) for i in range(3)
        )
        log_at = _extract(log_at, t, log_x_t.shape)
        log_bt = _extract(log_bt, t, log_x_t.shape)
        log_ct = _extract(log_ct, t, log_x_t.shape)
        log_1_min_ct = _log_1_min_a(log_ct)
        return torch.cat(
            [
                log_add_exp(log_x_t[:, :-1, :] + log_at, log_bt),
                log_add_exp(log_x_t[:, -1:, :] + log_1_min_ct, log_ct),
            ],
            dim=1,
        )

    def _vanilla_q_pred(
        self,
        log_x_start: Float[torch.Tensor, "batch vocab tokens"],
        t: Int[torch.Tensor, "batch"],
    ) -> Float[torch.Tensor, "batch vocab tokens"]:
        """Apply the cumulative vanilla forward noising transition."""
        t = (t + (self.num_timesteps + 1)) % (self.num_timesteps + 1)
        log_cumprod_at, log_cumprod_bt, log_cumprod_ct = (
            self.schedules["full"][i].to(t.device) for i in range(3, 6)
        )
        log_cumprod_at = _extract(log_cumprod_at, t, log_x_start.shape)
        log_cumprod_bt = _extract(log_cumprod_bt, t, log_x_start.shape)
        log_cumprod_ct = _extract(log_cumprod_ct, t, log_x_start.shape)
        log_1_min_cumprod_ct = _log_1_min_a(log_cumprod_ct)
        return torch.cat(
            [
                log_add_exp(log_x_start[:, :-1, :] + log_cumprod_at, log_cumprod_bt),
                log_add_exp(
                    log_x_start[:, -1:, :] + log_1_min_cumprod_ct, log_cumprod_ct
                ),
            ],
            dim=1,
        )

    def _constrained_q_posterior(
        self,
        log_x_start_full: Float[torch.Tensor, "batch vocab tokens"],
        log_x_t_full: Float[torch.Tensor, "batch vocab tokens"],
        t: Int[torch.Tensor, "batch"],
    ) -> Float[torch.Tensor, "batch vocab tokens"]:
        """Compute posterior probabilities with per-variable vocab constraints."""
        batch_size = log_x_start_full.size(0)
        step = len(self.var_order)
        seq_len = log_x_start_full.shape[-1] // step
        index_x_t_full = log_onehot_to_index(log_x_t_full)
        mask_reshaped = (index_x_t_full == self.mask_token_id).reshape(
            batch_size, seq_len, step
        )
        log_one = torch.zeros(
            batch_size, 1, 1, device=log_x_t_full.device, dtype=log_x_t_full.dtype
        )
        log_zero = torch.log(log_one + 1.0e-30).expand(-1, -1, seq_len)
        full_outputs = []
        for i, key in enumerate(self.var_order):
            mask = mask_reshaped[..., i].unsqueeze(1)
            log_x_start = self._full_to_partial_log(log_x_start_full[..., i::step], key)
            log_x_t = self._full_to_partial_log(log_x_t_full[..., i::step], key)
            log_qt = self._q_pred(log_x_t, t, key)[:, :-1, :]
            log_cumprod_ct = _extract(
                self.schedules[key][5].to(t.device), t, log_x_t.shape
            )
            ct_cumprod = log_cumprod_ct.expand(-1, self._mat_size(key) - 1, -1)
            log_qt = (~mask) * log_qt + mask * ct_cumprod
            log_qt_one = self._q_pred_one_timestep(log_x_t, t, key)
            log_qt_one = torch.cat((log_qt_one[:, :-1, :], log_zero), dim=1)
            log_ct = _extract(self.schedules[key][2].to(t.device), t, log_x_t.shape)
            ct_vector = torch.cat(
                (log_ct.expand(-1, self._mat_size(key) - 1, -1), log_one), dim=1
            )
            log_qt_one = (~mask) * log_qt_one + mask * ct_vector
            q = torch.cat((log_x_start[:, :-1, :] - log_qt, log_zero), dim=1)
            q_log_sum_exp = torch.logsumexp(q, dim=1, keepdim=True)
            q = q - q_log_sum_exp
            partial = self._q_pred(q, t - 1, key) + log_qt_one + q_log_sum_exp
            full_outputs.append(
                self._partial_to_full_log(partial.clamp(-70.0, 0.0), key)
            )
        return torch.stack(full_outputs, dim=-1).reshape(
            batch_size, self.vocab_size, -1
        )

    def _q_pred_one_timestep(
        self,
        log_x_t: Float[torch.Tensor, "batch partial_vocab tokens"],
        t: Int[torch.Tensor, "batch"],
        key: str,
    ) -> Float[torch.Tensor, "batch partial_vocab tokens"]:
        """Apply one forward noising transition in partial vocabulary space."""
        log_at, log_bt, log_ct = (self.schedules[key][i].to(t.device) for i in range(3))
        log_at = _extract(log_at, t, log_x_t.shape)
        log_bt = _extract(log_bt, t, log_x_t.shape)
        log_ct = _extract(log_ct, t, log_x_t.shape)
        log_1_min_ct = _log_1_min_a(log_ct)
        return torch.cat(
            [
                log_add_exp(log_x_t[:, :-1, :] + log_at, log_bt),
                log_add_exp(log_x_t[:, -1:, :] + log_1_min_ct, log_ct),
            ],
            dim=1,
        )

    def _q_pred(
        self,
        log_x_start: Float[torch.Tensor, "batch partial_vocab tokens"],
        t: Int[torch.Tensor, "batch"],
        key: str,
    ) -> Float[torch.Tensor, "batch partial_vocab tokens"]:
        """Apply cumulative forward noising in partial vocabulary space."""
        t = (t + (self.num_timesteps + 1)) % (self.num_timesteps + 1)
        log_cumprod_at, log_cumprod_bt, log_cumprod_ct = (
            self.schedules[key][i].to(t.device) for i in range(3, 6)
        )
        log_cumprod_at = _extract(log_cumprod_at, t, log_x_start.shape)
        log_cumprod_bt = _extract(log_cumprod_bt, t, log_x_start.shape)
        log_cumprod_ct = _extract(log_cumprod_ct, t, log_x_start.shape)
        log_1_min_cumprod_ct = _log_1_min_a(log_cumprod_ct)
        return torch.cat(
            [
                log_add_exp(log_x_start[:, :-1, :] + log_cumprod_at, log_cumprod_bt),
                log_add_exp(
                    log_x_start[:, -1:, :] + log_1_min_cumprod_ct, log_cumprod_ct
                ),
            ],
            dim=1,
        )

    def _mat_size(self, key: str) -> int:
        """Return the constrained matrix size for one token variable."""
        assert self.per_var_full_ids is not None
        return len(self.per_var_full_ids[key])

    def _full_ids(
        self, key: str, device: torch.device
    ) -> Int[torch.Tensor, "partial_vocab"]:
        """Return full vocabulary ids for a constrained token variable."""
        assert self.per_var_full_ids is not None
        return torch.tensor(self.per_var_full_ids[key], dtype=torch.long, device=device)

    def _full_to_partial_log(
        self, inputs: Float[torch.Tensor, "batch vocab tokens"], key: str
    ) -> Float[torch.Tensor, "batch partial_vocab tokens"]:
        """Gather full-vocabulary log probabilities into partial space."""
        full_ids = self._full_ids(key, inputs.device)
        index = full_ids.reshape(1, -1, 1).expand(inputs.shape[0], -1, inputs.shape[-1])
        return torch.gather(inputs, dim=1, index=index)

    def _partial_to_full_log(
        self, inputs: Float[torch.Tensor, "batch partial_vocab tokens"], key: str
    ) -> Float[torch.Tensor, "batch vocab tokens"]:
        """Scatter partial-space log probabilities into full vocabulary space."""
        full_ids = self._full_ids(key, inputs.device)
        outputs = torch.full(
            (inputs.shape[0], self.vocab_size, inputs.shape[-1]),
            math.log(1.0e-30),
            device=inputs.device,
            dtype=inputs.dtype,
        )
        index = full_ids.reshape(1, -1, 1).expand(inputs.shape[0], -1, inputs.shape[-1])
        return outputs.scatter(dim=1, index=index, src=inputs)

    def step(
        self,
        denoiser_output: Float[torch.Tensor, "batch tokens vocab"],
        timestep: Int[torch.Tensor, "batch"],
        sample: Float[torch.Tensor, "batch vocab tokens"],
        *,
        previous_timestep: int,
        sampling: LayoutDMSamplingConfig,
        condition: LayoutDMCondition | None = None,
        generator: torch.Generator | None = None,
    ) -> LayoutDMSchedulerOutput:
        """Run one reverse-diffusion scheduler step.

        Args:
            denoiser_output: Raw denoiser logits.
            timestep: Current timestep tensor.
            sample: Current log one-hot sample.
            previous_timestep: Previous timestep value from the sampling loop.
            sampling: Sampling configuration.
            condition: Optional strong condition mask and ids.
            generator: Optional torch generator for stochastic sampling.

        Returns:
            Scheduler output containing the previous sample and log-probability
            intermediates.
        """
        log_x_recon = self.predict_start(denoiser_output)
        model_log_prob = self.q_posterior(log_x_recon, sample, timestep)
        if self.token_mask is not None:
            valid = self.token_mask.to(model_log_prob.device).T.unsqueeze(0)
            model_log_prob = model_log_prob.masked_fill(~valid, -70.0)
        if condition is not None:
            strong_mask = condition.mask.to(model_log_prob.device).unsqueeze(1)
            strong_log_prob = index_to_log_onehot(
                condition.input_ids.to(model_log_prob.device), self.vocab_size
            )
            model_log_prob = torch.where(strong_mask, strong_log_prob, model_log_prob)
        logits = model_log_prob.permute(0, 2, 1)
        ids = sample_categorical(
            logits,
            sampling=sampling.name,
            temperature=sampling.temperature,
            top_k=sampling.top_k,
            top_p=sampling.top_p,
            generator=generator,
        )
        prev_sample = index_to_log_onehot(ids, self.vocab_size)
        return LayoutDMSchedulerOutput(
            prev_sample=prev_sample,
            pred_original_sample=log_x_recon,
            model_log_prob=model_log_prob,
        )

__init__

__init__(
    *,
    num_timesteps: int = 100,
    q_type: Literal[
        "constrained", "vanilla"
    ] = "constrained",
    vocab_size: int,
    mask_token_id: int,
    pad_token_id: int,
    var_order: tuple[str, ...] = ("c", "x", "y", "w", "h"),
    token_mask: list[list[bool]] | None = None,
    per_var_full_ids: dict[str, list[int]] | None = None,
    att_1: float = 0.99999,
    att_T: float = 9e-06,
    ctt_1: float = 9e-06,
    ctt_T: float = 0.99999,
) -> None

Initialize LayoutDM transition schedules.

Source code in models/layout-dm/src/layout_dm/scheduling_layout_dm.py
 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
@register_to_config
def __init__(
    self,
    *,
    num_timesteps: int = 100,
    q_type: Literal["constrained", "vanilla"] = "constrained",
    vocab_size: int,
    mask_token_id: int,
    pad_token_id: int,
    var_order: tuple[str, ...] = ("c", "x", "y", "w", "h"),
    token_mask: list[list[bool]] | None = None,
    per_var_full_ids: dict[str, list[int]] | None = None,
    att_1: float = 0.99999,
    att_T: float = 0.000009,
    ctt_1: float = 0.000009,
    ctt_T: float = 0.99999,
) -> None:
    """Initialize LayoutDM transition schedules."""
    self.num_timesteps = num_timesteps
    self.timesteps = torch.arange(num_timesteps - 1, -1, -1)
    self.vocab_size = vocab_size
    self.mask_token_id = mask_token_id
    self.pad_token_id = pad_token_id
    self.var_order = tuple(var_order)
    self.token_mask = (
        None if token_mask is None else torch.tensor(token_mask, dtype=torch.bool)
    )
    self.per_var_full_ids = per_var_full_ids
    self.att_1 = att_1
    self.att_T = att_T
    self.ctt_1 = ctt_1
    self.ctt_T = ctt_T
    if per_var_full_ids is None:
        self.schedules = {
            "full": _alpha_schedule(
                num_timesteps, vocab_size - 1, att_1, att_T, ctt_1, ctt_T
            )
        }
    else:
        self.schedules = {
            key: _alpha_schedule(
                num_timesteps, len(ids) - 1, att_1, att_T, ctt_1, ctt_T
            )
            for key, ids in per_var_full_ids.items()
        }

set_timesteps

set_timesteps(
    num_inference_steps: int | None = None,
    device: device | None = None,
) -> None

Set reverse-diffusion timesteps for inference.

Source code in models/layout-dm/src/layout_dm/scheduling_layout_dm.py
110
111
112
113
114
115
116
117
118
119
def set_timesteps(
    self, num_inference_steps: int | None = None, device: torch.device | None = None
) -> None:
    """Set reverse-diffusion timesteps for inference."""
    steps = num_inference_steps or self.num_timesteps
    self.timesteps = torch.tensor(
        [int(i * self.num_timesteps / steps) for i in range(steps - 1, -1, -1)],
        dtype=torch.long,
        device=device,
    )

initial_sample

initial_sample(
    batch_size: int,
    token_length: int,
    *,
    device: device,
    condition: LayoutDMCondition | None = None,
) -> Float[torch.Tensor, "batch vocab tokens"]

Create the initial log one-hot sample for reverse diffusion.

Source code in models/layout-dm/src/layout_dm/scheduling_layout_dm.py
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
def initial_sample(
    self,
    batch_size: int,
    token_length: int,
    *,
    device: torch.device,
    condition: LayoutDMCondition | None = None,
) -> Float[torch.Tensor, "batch vocab tokens"]:
    """Create the initial log one-hot sample for reverse diffusion."""
    if condition is not None:
        ids = condition.input_ids.to(device)
    else:
        ids = torch.full(
            (batch_size, token_length),
            self.mask_token_id,
            dtype=torch.long,
            device=device,
        )
    return index_to_log_onehot(ids, self.vocab_size)

predict_start

predict_start(
    denoiser_output: Float[Tensor, "batch tokens vocab"],
) -> Float[torch.Tensor, "batch vocab tokens"]

Convert denoiser logits to start-sequence log probabilities.

Source code in models/layout-dm/src/layout_dm/scheduling_layout_dm.py
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
def predict_start(
    self, denoiser_output: Float[torch.Tensor, "batch tokens vocab"]
) -> Float[torch.Tensor, "batch vocab tokens"]:
    """Convert denoiser logits to start-sequence log probabilities."""
    logits = denoiser_output[:, :, :-1]
    log_pred = torch.log_softmax(logits.double(), dim=-1).float()
    mask_col = torch.full(
        (*log_pred.shape[:2], 1),
        -70.0,
        device=log_pred.device,
        dtype=log_pred.dtype,
    )
    return (
        torch.cat((log_pred, mask_col), dim=-1).permute(0, 2, 1).clamp(-70.0, 0.0)
    )

q_posterior

q_posterior(
    log_x_start: Float[Tensor, "batch vocab tokens"],
    log_x_t: Float[Tensor, "batch vocab tokens"],
    t: Int[Tensor, "batch"],
) -> Float[torch.Tensor, "batch vocab tokens"]

Compute the LayoutDM posterior transition distribution.

Source code in models/layout-dm/src/layout_dm/scheduling_layout_dm.py
157
158
159
160
161
162
163
164
165
166
def q_posterior(
    self,
    log_x_start: Float[torch.Tensor, "batch vocab tokens"],
    log_x_t: Float[torch.Tensor, "batch vocab tokens"],
    t: Int[torch.Tensor, "batch"],
) -> Float[torch.Tensor, "batch vocab tokens"]:
    """Compute the LayoutDM posterior transition distribution."""
    if self.per_var_full_ids is not None:
        return self._constrained_q_posterior(log_x_start, log_x_t, t)
    return self._vanilla_q_posterior(log_x_start, log_x_t, t)

step

step(
    denoiser_output: Float[Tensor, "batch tokens vocab"],
    timestep: Int[Tensor, "batch"],
    sample: Float[Tensor, "batch vocab tokens"],
    *,
    previous_timestep: int,
    sampling: LayoutDMSamplingConfig,
    condition: LayoutDMCondition | None = None,
    generator: Generator | None = None,
) -> LayoutDMSchedulerOutput

Run one reverse-diffusion scheduler step.

Parameters:

Name Type Description Default
denoiser_output Float[Tensor, 'batch tokens vocab']

Raw denoiser logits.

required
timestep Int[Tensor, 'batch']

Current timestep tensor.

required
sample Float[Tensor, 'batch vocab tokens']

Current log one-hot sample.

required
previous_timestep int

Previous timestep value from the sampling loop.

required
sampling LayoutDMSamplingConfig

Sampling configuration.

required
condition LayoutDMCondition | None

Optional strong condition mask and ids.

None
generator Generator | None

Optional torch generator for stochastic sampling.

None

Returns:

Type Description
LayoutDMSchedulerOutput

Scheduler output containing the previous sample and log-probability

LayoutDMSchedulerOutput

intermediates.

Source code in models/layout-dm/src/layout_dm/scheduling_layout_dm.py
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
def step(
    self,
    denoiser_output: Float[torch.Tensor, "batch tokens vocab"],
    timestep: Int[torch.Tensor, "batch"],
    sample: Float[torch.Tensor, "batch vocab tokens"],
    *,
    previous_timestep: int,
    sampling: LayoutDMSamplingConfig,
    condition: LayoutDMCondition | None = None,
    generator: torch.Generator | None = None,
) -> LayoutDMSchedulerOutput:
    """Run one reverse-diffusion scheduler step.

    Args:
        denoiser_output: Raw denoiser logits.
        timestep: Current timestep tensor.
        sample: Current log one-hot sample.
        previous_timestep: Previous timestep value from the sampling loop.
        sampling: Sampling configuration.
        condition: Optional strong condition mask and ids.
        generator: Optional torch generator for stochastic sampling.

    Returns:
        Scheduler output containing the previous sample and log-probability
        intermediates.
    """
    log_x_recon = self.predict_start(denoiser_output)
    model_log_prob = self.q_posterior(log_x_recon, sample, timestep)
    if self.token_mask is not None:
        valid = self.token_mask.to(model_log_prob.device).T.unsqueeze(0)
        model_log_prob = model_log_prob.masked_fill(~valid, -70.0)
    if condition is not None:
        strong_mask = condition.mask.to(model_log_prob.device).unsqueeze(1)
        strong_log_prob = index_to_log_onehot(
            condition.input_ids.to(model_log_prob.device), self.vocab_size
        )
        model_log_prob = torch.where(strong_mask, strong_log_prob, model_log_prob)
    logits = model_log_prob.permute(0, 2, 1)
    ids = sample_categorical(
        logits,
        sampling=sampling.name,
        temperature=sampling.temperature,
        top_k=sampling.top_k,
        top_p=sampling.top_p,
        generator=generator,
    )
    prev_sample = index_to_log_onehot(ids, self.vocab_size)
    return LayoutDMSchedulerOutput(
        prev_sample=prev_sample,
        pred_original_sample=log_x_recon,
        model_log_prob=model_log_prob,
    )

tokenization_layout_dm

Transformers tokenizer for LayoutDM discrete layout sequences.

LayoutDMTokenizer

Bases: PreTrainedTokenizer

Structured LayoutDM tokenizer backed by a synthetic vocabulary.

Parameters:

Name Type Description Default
config LayoutDMConfig | Mapping[str, LayoutDMConfigValue] | None

LayoutDM tokenizer/model configuration or serialized config dict.

None
vocab_file str | Path | None

Optional saved vocabulary file.

None
layout_config_file str | Path | None

Optional saved layout config file.

None
cluster_centers_file str | Path | None

Optional saved cluster-center file.

None
**kwargs LayoutDMConfigValue

Extra PreTrainedTokenizer keyword arguments.

{}

Raises:

Type Description
NotImplementedError

If the config uses an unsupported token order.

ValueError

If LayoutDM special-token ordering is invalid.

Examples:

>>> from layout_dm.configuration_layout_dm import LayoutDMConfig
>>> tokenizer = LayoutDMTokenizer(LayoutDMConfig(dataset_name="publaynet"))
>>> tokenizer.mask_token
'mask'
Source code in models/layout-dm/src/layout_dm/tokenization_layout_dm.py
 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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
class LayoutDMTokenizer(PreTrainedTokenizer):
    """Structured LayoutDM tokenizer backed by a synthetic vocabulary.

    Args:
        config: LayoutDM tokenizer/model configuration or serialized config dict.
        vocab_file: Optional saved vocabulary file.
        layout_config_file: Optional saved layout config file.
        cluster_centers_file: Optional saved cluster-center file.
        **kwargs: Extra ``PreTrainedTokenizer`` keyword arguments.

    Raises:
        NotImplementedError: If the config uses an unsupported token order.
        ValueError: If LayoutDM special-token ordering is invalid.

    Examples:
        >>> from layout_dm.configuration_layout_dm import LayoutDMConfig
        >>> tokenizer = LayoutDMTokenizer(LayoutDMConfig(dataset_name="publaynet"))
        >>> tokenizer.mask_token
        'mask'
    """

    vocab_files_names = {
        "vocab_file": "vocab.json",
        "layout_config_file": "layout_config.json",
        "cluster_centers_file": "cluster_centers.json",
    }
    model_input_names = ["input_ids", "attention_mask"]

    def __init__(
        self,
        config: LayoutDMConfig | Mapping[str, LayoutDMConfigValue] | None = None,
        *,
        vocab_file: str | Path | None = None,
        layout_config_file: str | Path | None = None,
        cluster_centers_file: str | Path | None = None,
        **kwargs: LayoutDMConfigValue,
    ) -> None:
        """Initialize a LayoutDM tokenizer from config or saved files."""
        if isinstance(config, LayoutDMConfig):
            pass
        elif config is None:
            config = self._load_config(
                layout_config_file=layout_config_file,
                cluster_centers_file=cluster_centers_file,
                kwargs=kwargs,
            )
        else:
            config = _layout_config_from_mapping(config)
        self.config = config
        if self.config.var_order != "c-x-y-w-h":
            raise NotImplementedError(
                "Only c-x-y-w-h LayoutDM token order is supported"
            )

        if (
            "mask" in self.config.special_tokens
            and self.config.special_tokens[-1] != "mask"
        ):
            raise ValueError("LayoutDM requires mask to be the final special token")

        vocab = self._build_vocab()
        if vocab_file is not None and Path(vocab_file).exists():
            loaded_vocab = json.loads(Path(vocab_file).read_text(encoding="utf-8"))
            vocab = {str(token): int(idx) for token, idx in loaded_vocab.items()}
        self._token_to_id = vocab
        self._id_to_token = {idx: token for token, idx in vocab.items()}
        pad_token = kwargs.pop("pad_token", "pad")
        mask_token = kwargs.pop("mask_token", "mask")
        model_max_length = kwargs.pop("model_max_length", self.config.max_token_length)

        super().__init__(
            pad_token=pad_token,
            mask_token=mask_token,
            model_max_length=model_max_length,
            **kwargs,
        )

    @property
    def vocab_size(self) -> int:
        """Return the synthetic vocabulary size."""
        return self.config.vocab_size

    @property
    def var_names(self) -> tuple[str, ...]:
        """Return the per-element variable names in token order."""
        return tuple(self.config.var_order.split("-"))

    def __call__(
        self,
        *,
        bbox: Float[torch.Tensor, "batch elements 4"] | Sequence[ArrayLikeInput],
        labels: Int[torch.Tensor, "batch elements"] | Sequence[ArrayLikeInput],
        mask: Bool[torch.Tensor, "batch elements"]
        | Sequence[ArrayLikeInput]
        | None = None,
    ) -> dict[str, Shaped[torch.Tensor, "..."]]:
        """Encode structured layout tensors.

        Args:
            bbox: Normalized center ``xywh`` boxes.
            labels: Dataset-local labels.
            mask: Optional valid-element mask.

        Returns:
            Dictionary containing ``input_ids``, ``attention_mask``, and ``mask``.
        """
        return self.encode_layout(
            bbox=torch.as_tensor(bbox),
            labels=torch.as_tensor(labels),
            mask=None if mask is None else torch.as_tensor(mask),
        )

    def get_vocab(self) -> dict[str, int]:
        """Return a copy of the synthetic token-to-id vocabulary."""
        return dict(self._token_to_id)

    def _tokenize(self, text: str, **kwargs: str | float | bool | None) -> list[str]:
        """Reject text tokenization because LayoutDM consumes layouts."""
        _ = text, kwargs
        raise TypeError("LayoutDMTokenizer does not tokenize text")

    def _convert_token_to_id(self, token: str) -> int:
        """Convert a synthetic token string to an integer id."""
        return self._token_to_id.get(token, self._token_to_id[self.pad_token])

    def _convert_id_to_token(self, index: int) -> str:
        """Convert an integer id to a synthetic token string."""
        return self._id_to_token.get(int(index), self.pad_token)

    def convert_tokens_to_string(self, tokens: list[str]) -> str:
        """Join synthetic tokens for human-readable debugging."""
        return " ".join(tokens)

    def save_vocabulary(
        self, save_directory: str | Path, filename_prefix: str | None = None
    ) -> tuple[str, ...]:
        """Save vocabulary, layout config, and cluster centers.

        Args:
            save_directory: Directory where tokenizer files are written.
            filename_prefix: Optional filename prefix used by Transformers.

        Returns:
            Tuple of saved file paths.
        """
        save_path = Path(save_directory)
        save_path.mkdir(parents=True, exist_ok=True)
        prefix = "" if filename_prefix is None else f"{filename_prefix}-"
        vocab_file = save_path / f"{prefix}vocab.json"
        layout_config_file = save_path / f"{prefix}layout_config.json"
        cluster_centers_file = save_path / f"{prefix}cluster_centers.json"
        vocab_file.write_text(
            json.dumps(self._token_to_id, indent=2, sort_keys=True),
            encoding="utf-8",
        )
        config_data = dict(self.config.config)
        config_data["id2label"] = {str(k): v for k, v in self.config.id2label.items()}
        config_data["cluster_centers"] = None
        config_data["cluster_centers_path"] = None
        layout_config_file.write_text(
            json.dumps(config_data, indent=2, sort_keys=True), encoding="utf-8"
        )
        centers = {
            key: [float(v) for v in self._centers(key, torch.device("cpu")).double()]
            for key in ("x", "y", "w", "h")
        }
        cluster_centers_file.write_text(
            json.dumps(centers, indent=2, sort_keys=True), encoding="utf-8"
        )
        return (str(vocab_file), str(layout_config_file), str(cluster_centers_file))

    @classmethod
    def from_pretrained(
        cls,
        path: str | PathLike[str],
        *args: str | PathLike[str] | bool,
        cache_dir: str | PathLike[str] | None = None,
        force_download: bool = False,
        local_files_only: bool = False,
        token: str | bool | None = None,
        revision: str = "main",
        **kwargs: LayoutDMConfigValue,
    ) -> LayoutDMTokenizer:
        """Load a tokenizer from a pipeline or tokenizer directory.

        Args:
            path: Pipeline root or tokenizer subdirectory.
            *args: Additional ``PreTrainedTokenizer`` positional arguments.
            cache_dir: Optional Transformers cache directory.
            force_download: Whether to force file downloads.
            local_files_only: Whether to avoid network access.
            token: Optional Hub authentication token.
            revision: Hub revision to load.
            **kwargs: Additional ``PreTrainedTokenizer`` keyword arguments.

        Returns:
            Loaded tokenizer.
        """
        path = Path(path)
        if (path / "tokenizer").is_dir():
            path = path / "tokenizer"
        return super().from_pretrained(
            path,
            *args,
            cache_dir=cache_dir,
            force_download=force_download,
            local_files_only=local_files_only,
            token=token,
            revision=revision,
            **kwargs,
        )

    @classmethod
    def _load_config(
        cls,
        *,
        layout_config_file: str | Path | None,
        cluster_centers_file: str | Path | None,
        kwargs: dict[str, LayoutDMConfigValue],
    ) -> LayoutDMConfig:
        layout_config = kwargs.pop("layout_config", None)
        if layout_config is None:
            if layout_config_file is None:
                raise ValueError(
                    "LayoutDMTokenizer requires a LayoutDMConfig or layout_config_file"
                )

            layout_config = json.loads(
                Path(layout_config_file).read_text(encoding="utf-8")
            )
        if not isinstance(layout_config, Mapping):
            raise TypeError("layout_config must be a mapping")

        config_data = dict(layout_config)
        if cluster_centers_file is not None and Path(cluster_centers_file).exists():
            centers = json.loads(Path(cluster_centers_file).read_text(encoding="utf-8"))
            if not isinstance(centers, Mapping):
                raise TypeError("cluster centers must be a mapping")

            config_data["cluster_centers"] = _cluster_centers(centers)
        return _layout_config_from_mapping(config_data)

    def encode_layout(
        self,
        *,
        bbox: Float[torch.Tensor, "elements 4"]
        | Float[torch.Tensor, "batch elements 4"],
        labels: Int[torch.Tensor, "elements"] | Int[torch.Tensor, "batch elements"],
        mask: Bool[torch.Tensor, "elements"]
        | Bool[torch.Tensor, "batch elements"]
        | None = None,
    ) -> dict[str, Shaped[torch.Tensor, "..."]]:
        """Encode normalized layout tensors into flattened token sequences.

        Args:
            bbox: Normalized center ``xywh`` boxes with shape ``(seq, 4)`` or
                ``(batch, seq, 4)``.
            labels: Dataset-local labels with shape ``(seq,)`` or
                ``(batch, seq)``.
            mask: Optional valid-element mask. Missing masks mark all elements
                valid.

        Returns:
            Dictionary containing flattened ``input_ids``, ``attention_mask``,
            and ``mask`` tensors.

        Raises:
            ValueError: If the sequence length exceeds the configured maximum.

        Examples:
            >>> import torch
            >>> from layout_dm.configuration_layout_dm import LayoutDMConfig
            >>> tok = LayoutDMTokenizer(LayoutDMConfig(dataset_name="publaynet"))
            >>> encoded = tok.encode_layout(
            ...     bbox=torch.zeros(1, 1, 4),
            ...     labels=torch.zeros(1, 1, dtype=torch.long),
            ... )
            >>> encoded["input_ids"].shape[-1]
            125
        """
        bbox = torch.as_tensor(bbox, dtype=torch.float64)
        labels = torch.as_tensor(labels, dtype=torch.long)
        if labels.ndim == 1:
            labels = labels.unsqueeze(0)
            bbox = bbox.unsqueeze(0)
        if mask is None:
            mask = torch.ones(labels.shape, dtype=torch.bool, device=labels.device)
        else:
            mask = torch.as_tensor(mask, dtype=torch.bool, device=labels.device)
            if mask.ndim == 1:
                mask = mask.unsqueeze(0)
        batch_size, seq_length = labels.shape
        if seq_length > self.config.max_seq_length:
            raise ValueError(
                f"seq_length {seq_length} exceeds max_seq_length {self.config.max_seq_length}"
            )

        bbox_ids = self._encode_bbox(bbox) + self.config.num_categories
        seq = torch.cat((labels.unsqueeze(-1), bbox_ids), dim=-1)
        pad_len = self.config.max_seq_length - seq_length
        if pad_len:
            pad = torch.full(
                (batch_size, pad_len, 5),
                self.pad_token_id,
                dtype=torch.long,
                device=seq.device,
            )
            seq = torch.cat((seq, pad), dim=1)
            mask = torch.cat(
                (
                    mask,
                    torch.zeros(
                        batch_size, pad_len, dtype=torch.bool, device=mask.device
                    ),
                ),
                dim=1,
            )
        seq = seq.masked_fill(~mask.unsqueeze(-1), self.pad_token_id)
        return {
            "input_ids": seq.reshape(batch_size, -1),
            "attention_mask": mask.repeat_interleave(5, dim=1),
            "mask": mask.repeat_interleave(5, dim=1),
        }

    def decode_layout(
        self, input_ids: Int[torch.Tensor, "batch tokens"]
    ) -> dict[str, Shaped[torch.Tensor, "..."]]:
        """Decode flattened token sequences into public layout tensors.

        Args:
            input_ids: Flattened LayoutDM token ids with shape
                ``(batch, max_token_length)``.

        Returns:
            Dictionary with ``bbox``, ``labels``, and ``mask`` tensors.
        """
        ids = torch.as_tensor(input_ids, dtype=torch.long)
        ids = ids.reshape(ids.shape[0], self.config.max_seq_length, 5)
        labels = ids[..., 0].clone()
        bbox_ids = ids[..., 1:].clone() - self.config.num_categories
        label_valid = (labels >= 0) & (labels < self.config.num_categories)
        bbox_valid = (bbox_ids >= 0) & (bbox_ids < self.config.num_bbox_tokens)
        mask = label_valid & bbox_valid.all(dim=-1)
        bbox = self._decode_bbox(bbox_ids)
        labels = labels.masked_fill(~mask, 0)
        bbox = bbox.masked_fill(~mask.unsqueeze(-1), 0.0)
        return {"bbox": bbox.float(), "labels": labels, "mask": mask}

    def token_mask(self) -> Bool[torch.Tensor, "tokens vocab"]:
        """Return the valid vocabulary mask for every flattened token position."""
        mask = torch.zeros(
            self.config.max_token_length, self.config.vocab_size, dtype=torch.bool
        )
        special_start = self.config.num_categories + self.config.num_bbox_tokens
        for pos, key in enumerate(self.var_names * self.config.max_seq_length):
            if key == "c":
                mask[pos, : self.config.num_categories] = True
                mask[pos, special_start:] = True
            else:
                start, end = self.config.bbox_slices[key]
                mask[pos, start:end] = True
                mask[pos, special_start:] = True
        return mask

    def full_to_partial_ids(
        self, ids: Int[torch.Tensor, "batch tokens"], key: str
    ) -> Int[torch.Tensor, "batch tokens"]:
        """Map full vocabulary bbox ids to per-variable partial ids."""
        mapping = self._mapping(key)
        return _bucketize(ids, mapping["full"], mapping["partial"])

    def partial_to_full_ids(
        self, ids: Int[torch.Tensor, "batch tokens"], key: str
    ) -> Int[torch.Tensor, "batch tokens"]:
        """Map per-variable partial ids to full vocabulary bbox ids."""
        mapping = self._mapping(key)
        return _bucketize(ids, mapping["partial"], mapping["full"])

    def full_to_partial_log_probs(
        self, log_probs: Float[torch.Tensor, "batch vocab tokens"], key: str
    ) -> Float[torch.Tensor, "batch vocab tokens"]:
        """Gather full-vocabulary log probabilities into a partial bbox space."""
        mapping = self._mapping(key)["full"].to(log_probs.device)
        index = mapping.reshape(1, -1, 1).expand(
            log_probs.shape[0], -1, log_probs.shape[-1]
        )
        return torch.gather(log_probs, dim=1, index=index)

    def partial_to_full_log_probs(
        self, log_probs: Float[torch.Tensor, "batch vocab tokens"], key: str
    ) -> Float[torch.Tensor, "batch vocab tokens"]:
        """Scatter partial bbox log probabilities into the full vocabulary."""
        mapping = self._mapping(key)["full"].to(log_probs.device)
        out = torch.full(
            (log_probs.shape[0], self.config.vocab_size, log_probs.shape[-1]),
            math.log(1.0e-30),
            device=log_probs.device,
            dtype=log_probs.dtype,
        )
        index = mapping.reshape(1, -1, 1).expand(
            log_probs.shape[0], -1, log_probs.shape[-1]
        )
        return out.scatter(dim=1, index=index, src=log_probs)

    def full_id_maps(self) -> dict[str, list[int]]:
        """Return full vocabulary id lists for every token variable."""
        return {key: self._mapping(key)["full"].tolist() for key in self.var_names}

    def _build_vocab(self) -> dict[str, int]:
        vocab: dict[str, int] = {}
        for idx, label in self.config.id2label.items():
            vocab[f"c:{label}"] = int(idx)
        for key in ("x", "y", "w", "h"):
            start, end = self.config.bbox_slices[key]
            for token_id in range(start, end):
                local_id = token_id - start
                vocab[f"{key}:{local_id}"] = token_id
        vocab["pad"] = self.config.pad_token_id
        vocab["mask"] = self.config.mask_token_id
        return vocab

    def _centers(self, key: str, device: torch.device) -> Float[torch.Tensor, "bins"]:
        centers = self._cluster_centers().get(key)
        if centers is None:
            delta = 1.0 / self.config.num_bin_bboxes
            start, stop = (0.0, 1.0 - delta) if key in {"x", "y"} else (delta, 1.0)
            centers = torch.linspace(
                start, stop, self.config.num_bin_bboxes, dtype=torch.float64
            ).tolist()
        return torch.tensor(centers, device=device, dtype=torch.float64).flatten()

    def _cluster_centers(self) -> dict[str, list[float]]:
        if self.config.cluster_centers is not None:
            return self.config.cluster_centers
        if self.config.cluster_centers_path is None:
            return {}
        centers = _load_cluster_centers_file(
            Path(self.config.cluster_centers_path),
            num_bin_bboxes=self.config.num_bin_bboxes,
        )
        self.config.cluster_centers = centers
        return centers

    def _encode_bbox(
        self, bbox: Float[torch.Tensor, "batch elements 4"]
    ) -> Int[torch.Tensor, "batch elements 4"]:
        bbox = bbox.to(dtype=torch.float64)
        pieces = []
        for i, key in enumerate(("x", "y", "w", "h")):
            values = bbox[..., i]
            if self.config.bbox_quantization == "linear":
                delta = 1.0 / self.config.num_bin_bboxes
                if key in {"x", "y"}:
                    ids = (
                        (values.clamp(0.0, 1.0 - delta) * self.config.num_bin_bboxes)
                        .round()
                        .long()
                    )
                else:
                    ids = (
                        (
                            (values.clamp(delta, 1.0) - delta)
                            * self.config.num_bin_bboxes
                        )
                        .round()
                        .long()
                    )
            elif self.config.bbox_quantization in {"kmeans", "percentile"}:
                centers = self._centers(key, values.device)
                ids = (
                    torch.cdist(values.reshape(-1, 1), centers.reshape(-1, 1))
                    .argmin(dim=-1)
                    .reshape(values.shape)
                )
            else:
                raise ValueError(
                    f"Unsupported bbox_quantization: {self.config.bbox_quantization}"
                )

            offset = (
                KEY_MULT_DICT[self.config.shared_bbox_vocab].get(key, 0)
                * self.config.num_bin_bboxes
            )
            pieces.append(ids + offset)
        return torch.stack(pieces, dim=-1)

    def _decode_bbox(
        self, bbox_ids: Int[torch.Tensor, "batch elements 4"]
    ) -> Float[torch.Tensor, "batch elements 4"]:
        ids = bbox_ids.clone()
        pieces = []
        for i, key in enumerate(("x", "y", "w", "h")):
            offset = (
                KEY_MULT_DICT[self.config.shared_bbox_vocab].get(key, 0)
                * self.config.num_bin_bboxes
            )
            local_ids = (ids[..., i] - offset).clamp(0, self.config.num_bin_bboxes - 1)
            if self.config.bbox_quantization == "linear":
                delta = 1.0 / self.config.num_bin_bboxes
                values = (
                    local_ids.double() * delta
                    if key in {"x", "y"}
                    else (local_ids.double() + 1.0) * delta
                )
            else:
                centers = self._centers(key, ids.device)
                values = centers[local_ids]
            pieces.append(values)
        return torch.stack(pieces, dim=-1).clamp(0.0, 1.0).float()

    def _mapping(self, key: str) -> dict[str, Int[torch.Tensor, "vocab"]]:
        if key == "c":
            full = list(range(self.config.num_categories)) + [
                self.pad_token_id,
                self.mask_token_id,
            ]
        else:
            start, end = self.config.bbox_slices[key]
            full = list(range(start, end)) + [self.pad_token_id, self.mask_token_id]
        return {
            "partial": torch.arange(len(full), dtype=torch.long),
            "full": torch.tensor(full, dtype=torch.long),
        }

vocab_size property

vocab_size: int

Return the synthetic vocabulary size.

var_names property

var_names: tuple[str, ...]

Return the per-element variable names in token order.

__init__

__init__(
    config: LayoutDMConfig
    | Mapping[str, LayoutDMConfigValue]
    | None = None,
    *,
    vocab_file: str | Path | None = None,
    layout_config_file: str | Path | None = None,
    cluster_centers_file: str | Path | None = None,
    **kwargs: LayoutDMConfigValue,
) -> None

Initialize a LayoutDM tokenizer from config or saved files.

Source code in models/layout-dm/src/layout_dm/tokenization_layout_dm.py
 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
def __init__(
    self,
    config: LayoutDMConfig | Mapping[str, LayoutDMConfigValue] | None = None,
    *,
    vocab_file: str | Path | None = None,
    layout_config_file: str | Path | None = None,
    cluster_centers_file: str | Path | None = None,
    **kwargs: LayoutDMConfigValue,
) -> None:
    """Initialize a LayoutDM tokenizer from config or saved files."""
    if isinstance(config, LayoutDMConfig):
        pass
    elif config is None:
        config = self._load_config(
            layout_config_file=layout_config_file,
            cluster_centers_file=cluster_centers_file,
            kwargs=kwargs,
        )
    else:
        config = _layout_config_from_mapping(config)
    self.config = config
    if self.config.var_order != "c-x-y-w-h":
        raise NotImplementedError(
            "Only c-x-y-w-h LayoutDM token order is supported"
        )

    if (
        "mask" in self.config.special_tokens
        and self.config.special_tokens[-1] != "mask"
    ):
        raise ValueError("LayoutDM requires mask to be the final special token")

    vocab = self._build_vocab()
    if vocab_file is not None and Path(vocab_file).exists():
        loaded_vocab = json.loads(Path(vocab_file).read_text(encoding="utf-8"))
        vocab = {str(token): int(idx) for token, idx in loaded_vocab.items()}
    self._token_to_id = vocab
    self._id_to_token = {idx: token for token, idx in vocab.items()}
    pad_token = kwargs.pop("pad_token", "pad")
    mask_token = kwargs.pop("mask_token", "mask")
    model_max_length = kwargs.pop("model_max_length", self.config.max_token_length)

    super().__init__(
        pad_token=pad_token,
        mask_token=mask_token,
        model_max_length=model_max_length,
        **kwargs,
    )

__call__

__call__(
    *,
    bbox: Float[Tensor, "batch elements 4"]
    | Sequence[ArrayLikeInput],
    labels: Int[Tensor, "batch elements"]
    | Sequence[ArrayLikeInput],
    mask: Bool[Tensor, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
) -> dict[str, Shaped[torch.Tensor, "..."]]

Encode structured layout tensors.

Parameters:

Name Type Description Default
bbox Float[Tensor, 'batch elements 4'] | Sequence[ArrayLikeInput]

Normalized center xywh boxes.

required
labels Int[Tensor, 'batch elements'] | Sequence[ArrayLikeInput]

Dataset-local labels.

required
mask Bool[Tensor, 'batch elements'] | Sequence[ArrayLikeInput] | None

Optional valid-element mask.

None

Returns:

Type Description
dict[str, Shaped[Tensor, '...']]

Dictionary containing input_ids, attention_mask, and mask.

Source code in models/layout-dm/src/layout_dm/tokenization_layout_dm.py
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
def __call__(
    self,
    *,
    bbox: Float[torch.Tensor, "batch elements 4"] | Sequence[ArrayLikeInput],
    labels: Int[torch.Tensor, "batch elements"] | Sequence[ArrayLikeInput],
    mask: Bool[torch.Tensor, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
) -> dict[str, Shaped[torch.Tensor, "..."]]:
    """Encode structured layout tensors.

    Args:
        bbox: Normalized center ``xywh`` boxes.
        labels: Dataset-local labels.
        mask: Optional valid-element mask.

    Returns:
        Dictionary containing ``input_ids``, ``attention_mask``, and ``mask``.
    """
    return self.encode_layout(
        bbox=torch.as_tensor(bbox),
        labels=torch.as_tensor(labels),
        mask=None if mask is None else torch.as_tensor(mask),
    )

get_vocab

get_vocab() -> dict[str, int]

Return a copy of the synthetic token-to-id vocabulary.

Source code in models/layout-dm/src/layout_dm/tokenization_layout_dm.py
147
148
149
def get_vocab(self) -> dict[str, int]:
    """Return a copy of the synthetic token-to-id vocabulary."""
    return dict(self._token_to_id)

convert_tokens_to_string

convert_tokens_to_string(tokens: list[str]) -> str

Join synthetic tokens for human-readable debugging.

Source code in models/layout-dm/src/layout_dm/tokenization_layout_dm.py
164
165
166
def convert_tokens_to_string(self, tokens: list[str]) -> str:
    """Join synthetic tokens for human-readable debugging."""
    return " ".join(tokens)

save_vocabulary

save_vocabulary(
    save_directory: str | Path,
    filename_prefix: str | None = None,
) -> tuple[str, ...]

Save vocabulary, layout config, and cluster centers.

Parameters:

Name Type Description Default
save_directory str | Path

Directory where tokenizer files are written.

required
filename_prefix str | None

Optional filename prefix used by Transformers.

None

Returns:

Type Description
tuple[str, ...]

Tuple of saved file paths.

Source code in models/layout-dm/src/layout_dm/tokenization_layout_dm.py
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
def save_vocabulary(
    self, save_directory: str | Path, filename_prefix: str | None = None
) -> tuple[str, ...]:
    """Save vocabulary, layout config, and cluster centers.

    Args:
        save_directory: Directory where tokenizer files are written.
        filename_prefix: Optional filename prefix used by Transformers.

    Returns:
        Tuple of saved file paths.
    """
    save_path = Path(save_directory)
    save_path.mkdir(parents=True, exist_ok=True)
    prefix = "" if filename_prefix is None else f"{filename_prefix}-"
    vocab_file = save_path / f"{prefix}vocab.json"
    layout_config_file = save_path / f"{prefix}layout_config.json"
    cluster_centers_file = save_path / f"{prefix}cluster_centers.json"
    vocab_file.write_text(
        json.dumps(self._token_to_id, indent=2, sort_keys=True),
        encoding="utf-8",
    )
    config_data = dict(self.config.config)
    config_data["id2label"] = {str(k): v for k, v in self.config.id2label.items()}
    config_data["cluster_centers"] = None
    config_data["cluster_centers_path"] = None
    layout_config_file.write_text(
        json.dumps(config_data, indent=2, sort_keys=True), encoding="utf-8"
    )
    centers = {
        key: [float(v) for v in self._centers(key, torch.device("cpu")).double()]
        for key in ("x", "y", "w", "h")
    }
    cluster_centers_file.write_text(
        json.dumps(centers, indent=2, sort_keys=True), encoding="utf-8"
    )
    return (str(vocab_file), str(layout_config_file), str(cluster_centers_file))

from_pretrained classmethod

from_pretrained(
    path: str | PathLike[str],
    *args: str | PathLike[str] | bool,
    cache_dir: str | PathLike[str] | None = None,
    force_download: bool = False,
    local_files_only: bool = False,
    token: str | bool | None = None,
    revision: str = "main",
    **kwargs: LayoutDMConfigValue,
) -> LayoutDMTokenizer

Load a tokenizer from a pipeline or tokenizer directory.

Parameters:

Name Type Description Default
path str | PathLike[str]

Pipeline root or tokenizer subdirectory.

required
*args str | PathLike[str] | bool

Additional PreTrainedTokenizer positional arguments.

()
cache_dir str | PathLike[str] | None

Optional Transformers cache directory.

None
force_download bool

Whether to force file downloads.

False
local_files_only bool

Whether to avoid network access.

False
token str | bool | None

Optional Hub authentication token.

None
revision str

Hub revision to load.

'main'
**kwargs LayoutDMConfigValue

Additional PreTrainedTokenizer keyword arguments.

{}

Returns:

Type Description
LayoutDMTokenizer

Loaded tokenizer.

Source code in models/layout-dm/src/layout_dm/tokenization_layout_dm.py
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
@classmethod
def from_pretrained(
    cls,
    path: str | PathLike[str],
    *args: str | PathLike[str] | bool,
    cache_dir: str | PathLike[str] | None = None,
    force_download: bool = False,
    local_files_only: bool = False,
    token: str | bool | None = None,
    revision: str = "main",
    **kwargs: LayoutDMConfigValue,
) -> LayoutDMTokenizer:
    """Load a tokenizer from a pipeline or tokenizer directory.

    Args:
        path: Pipeline root or tokenizer subdirectory.
        *args: Additional ``PreTrainedTokenizer`` positional arguments.
        cache_dir: Optional Transformers cache directory.
        force_download: Whether to force file downloads.
        local_files_only: Whether to avoid network access.
        token: Optional Hub authentication token.
        revision: Hub revision to load.
        **kwargs: Additional ``PreTrainedTokenizer`` keyword arguments.

    Returns:
        Loaded tokenizer.
    """
    path = Path(path)
    if (path / "tokenizer").is_dir():
        path = path / "tokenizer"
    return super().from_pretrained(
        path,
        *args,
        cache_dir=cache_dir,
        force_download=force_download,
        local_files_only=local_files_only,
        token=token,
        revision=revision,
        **kwargs,
    )

encode_layout

encode_layout(
    *,
    bbox: Float[Tensor, "elements 4"]
    | Float[Tensor, "batch elements 4"],
    labels: Int[Tensor, "elements"]
    | Int[Tensor, "batch elements"],
    mask: Bool[Tensor, "elements"]
    | Bool[Tensor, "batch elements"]
    | None = None,
) -> dict[str, Shaped[torch.Tensor, "..."]]

Encode normalized layout tensors into flattened token sequences.

Parameters:

Name Type Description Default
bbox Float[Tensor, 'elements 4'] | Float[Tensor, 'batch elements 4']

Normalized center xywh boxes with shape (seq, 4) or (batch, seq, 4).

required
labels Int[Tensor, 'elements'] | Int[Tensor, 'batch elements']

Dataset-local labels with shape (seq,) or (batch, seq).

required
mask Bool[Tensor, 'elements'] | Bool[Tensor, 'batch elements'] | None

Optional valid-element mask. Missing masks mark all elements valid.

None

Returns:

Type Description
dict[str, Shaped[Tensor, '...']]

Dictionary containing flattened input_ids, attention_mask,

dict[str, Shaped[Tensor, '...']]

and mask tensors.

Raises:

Type Description
ValueError

If the sequence length exceeds the configured maximum.

Examples:

>>> import torch
>>> from layout_dm.configuration_layout_dm import LayoutDMConfig
>>> tok = LayoutDMTokenizer(LayoutDMConfig(dataset_name="publaynet"))
>>> encoded = tok.encode_layout(
...     bbox=torch.zeros(1, 1, 4),
...     labels=torch.zeros(1, 1, dtype=torch.long),
... )
>>> encoded["input_ids"].shape[-1]
125
Source code in models/layout-dm/src/layout_dm/tokenization_layout_dm.py
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
def encode_layout(
    self,
    *,
    bbox: Float[torch.Tensor, "elements 4"]
    | Float[torch.Tensor, "batch elements 4"],
    labels: Int[torch.Tensor, "elements"] | Int[torch.Tensor, "batch elements"],
    mask: Bool[torch.Tensor, "elements"]
    | Bool[torch.Tensor, "batch elements"]
    | None = None,
) -> dict[str, Shaped[torch.Tensor, "..."]]:
    """Encode normalized layout tensors into flattened token sequences.

    Args:
        bbox: Normalized center ``xywh`` boxes with shape ``(seq, 4)`` or
            ``(batch, seq, 4)``.
        labels: Dataset-local labels with shape ``(seq,)`` or
            ``(batch, seq)``.
        mask: Optional valid-element mask. Missing masks mark all elements
            valid.

    Returns:
        Dictionary containing flattened ``input_ids``, ``attention_mask``,
        and ``mask`` tensors.

    Raises:
        ValueError: If the sequence length exceeds the configured maximum.

    Examples:
        >>> import torch
        >>> from layout_dm.configuration_layout_dm import LayoutDMConfig
        >>> tok = LayoutDMTokenizer(LayoutDMConfig(dataset_name="publaynet"))
        >>> encoded = tok.encode_layout(
        ...     bbox=torch.zeros(1, 1, 4),
        ...     labels=torch.zeros(1, 1, dtype=torch.long),
        ... )
        >>> encoded["input_ids"].shape[-1]
        125
    """
    bbox = torch.as_tensor(bbox, dtype=torch.float64)
    labels = torch.as_tensor(labels, dtype=torch.long)
    if labels.ndim == 1:
        labels = labels.unsqueeze(0)
        bbox = bbox.unsqueeze(0)
    if mask is None:
        mask = torch.ones(labels.shape, dtype=torch.bool, device=labels.device)
    else:
        mask = torch.as_tensor(mask, dtype=torch.bool, device=labels.device)
        if mask.ndim == 1:
            mask = mask.unsqueeze(0)
    batch_size, seq_length = labels.shape
    if seq_length > self.config.max_seq_length:
        raise ValueError(
            f"seq_length {seq_length} exceeds max_seq_length {self.config.max_seq_length}"
        )

    bbox_ids = self._encode_bbox(bbox) + self.config.num_categories
    seq = torch.cat((labels.unsqueeze(-1), bbox_ids), dim=-1)
    pad_len = self.config.max_seq_length - seq_length
    if pad_len:
        pad = torch.full(
            (batch_size, pad_len, 5),
            self.pad_token_id,
            dtype=torch.long,
            device=seq.device,
        )
        seq = torch.cat((seq, pad), dim=1)
        mask = torch.cat(
            (
                mask,
                torch.zeros(
                    batch_size, pad_len, dtype=torch.bool, device=mask.device
                ),
            ),
            dim=1,
        )
    seq = seq.masked_fill(~mask.unsqueeze(-1), self.pad_token_id)
    return {
        "input_ids": seq.reshape(batch_size, -1),
        "attention_mask": mask.repeat_interleave(5, dim=1),
        "mask": mask.repeat_interleave(5, dim=1),
    }

decode_layout

decode_layout(
    input_ids: Int[Tensor, "batch tokens"],
) -> dict[str, Shaped[torch.Tensor, "..."]]

Decode flattened token sequences into public layout tensors.

Parameters:

Name Type Description Default
input_ids Int[Tensor, 'batch tokens']

Flattened LayoutDM token ids with shape (batch, max_token_length).

required

Returns:

Type Description
dict[str, Shaped[Tensor, '...']]

Dictionary with bbox, labels, and mask tensors.

Source code in models/layout-dm/src/layout_dm/tokenization_layout_dm.py
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
def decode_layout(
    self, input_ids: Int[torch.Tensor, "batch tokens"]
) -> dict[str, Shaped[torch.Tensor, "..."]]:
    """Decode flattened token sequences into public layout tensors.

    Args:
        input_ids: Flattened LayoutDM token ids with shape
            ``(batch, max_token_length)``.

    Returns:
        Dictionary with ``bbox``, ``labels``, and ``mask`` tensors.
    """
    ids = torch.as_tensor(input_ids, dtype=torch.long)
    ids = ids.reshape(ids.shape[0], self.config.max_seq_length, 5)
    labels = ids[..., 0].clone()
    bbox_ids = ids[..., 1:].clone() - self.config.num_categories
    label_valid = (labels >= 0) & (labels < self.config.num_categories)
    bbox_valid = (bbox_ids >= 0) & (bbox_ids < self.config.num_bbox_tokens)
    mask = label_valid & bbox_valid.all(dim=-1)
    bbox = self._decode_bbox(bbox_ids)
    labels = labels.masked_fill(~mask, 0)
    bbox = bbox.masked_fill(~mask.unsqueeze(-1), 0.0)
    return {"bbox": bbox.float(), "labels": labels, "mask": mask}

token_mask

token_mask() -> Bool[torch.Tensor, 'tokens vocab']

Return the valid vocabulary mask for every flattened token position.

Source code in models/layout-dm/src/layout_dm/tokenization_layout_dm.py
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
def token_mask(self) -> Bool[torch.Tensor, "tokens vocab"]:
    """Return the valid vocabulary mask for every flattened token position."""
    mask = torch.zeros(
        self.config.max_token_length, self.config.vocab_size, dtype=torch.bool
    )
    special_start = self.config.num_categories + self.config.num_bbox_tokens
    for pos, key in enumerate(self.var_names * self.config.max_seq_length):
        if key == "c":
            mask[pos, : self.config.num_categories] = True
            mask[pos, special_start:] = True
        else:
            start, end = self.config.bbox_slices[key]
            mask[pos, start:end] = True
            mask[pos, special_start:] = True
    return mask

full_to_partial_ids

full_to_partial_ids(
    ids: Int[Tensor, "batch tokens"], key: str
) -> Int[torch.Tensor, "batch tokens"]

Map full vocabulary bbox ids to per-variable partial ids.

Source code in models/layout-dm/src/layout_dm/tokenization_layout_dm.py
399
400
401
402
403
404
def full_to_partial_ids(
    self, ids: Int[torch.Tensor, "batch tokens"], key: str
) -> Int[torch.Tensor, "batch tokens"]:
    """Map full vocabulary bbox ids to per-variable partial ids."""
    mapping = self._mapping(key)
    return _bucketize(ids, mapping["full"], mapping["partial"])

partial_to_full_ids

partial_to_full_ids(
    ids: Int[Tensor, "batch tokens"], key: str
) -> Int[torch.Tensor, "batch tokens"]

Map per-variable partial ids to full vocabulary bbox ids.

Source code in models/layout-dm/src/layout_dm/tokenization_layout_dm.py
406
407
408
409
410
411
def partial_to_full_ids(
    self, ids: Int[torch.Tensor, "batch tokens"], key: str
) -> Int[torch.Tensor, "batch tokens"]:
    """Map per-variable partial ids to full vocabulary bbox ids."""
    mapping = self._mapping(key)
    return _bucketize(ids, mapping["partial"], mapping["full"])

full_to_partial_log_probs

full_to_partial_log_probs(
    log_probs: Float[Tensor, "batch vocab tokens"], key: str
) -> Float[torch.Tensor, "batch vocab tokens"]

Gather full-vocabulary log probabilities into a partial bbox space.

Source code in models/layout-dm/src/layout_dm/tokenization_layout_dm.py
413
414
415
416
417
418
419
420
421
def full_to_partial_log_probs(
    self, log_probs: Float[torch.Tensor, "batch vocab tokens"], key: str
) -> Float[torch.Tensor, "batch vocab tokens"]:
    """Gather full-vocabulary log probabilities into a partial bbox space."""
    mapping = self._mapping(key)["full"].to(log_probs.device)
    index = mapping.reshape(1, -1, 1).expand(
        log_probs.shape[0], -1, log_probs.shape[-1]
    )
    return torch.gather(log_probs, dim=1, index=index)

partial_to_full_log_probs

partial_to_full_log_probs(
    log_probs: Float[Tensor, "batch vocab tokens"], key: str
) -> Float[torch.Tensor, "batch vocab tokens"]

Scatter partial bbox log probabilities into the full vocabulary.

Source code in models/layout-dm/src/layout_dm/tokenization_layout_dm.py
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
def partial_to_full_log_probs(
    self, log_probs: Float[torch.Tensor, "batch vocab tokens"], key: str
) -> Float[torch.Tensor, "batch vocab tokens"]:
    """Scatter partial bbox log probabilities into the full vocabulary."""
    mapping = self._mapping(key)["full"].to(log_probs.device)
    out = torch.full(
        (log_probs.shape[0], self.config.vocab_size, log_probs.shape[-1]),
        math.log(1.0e-30),
        device=log_probs.device,
        dtype=log_probs.dtype,
    )
    index = mapping.reshape(1, -1, 1).expand(
        log_probs.shape[0], -1, log_probs.shape[-1]
    )
    return out.scatter(dim=1, index=index, src=log_probs)

full_id_maps

full_id_maps() -> dict[str, list[int]]

Return full vocabulary id lists for every token variable.

Source code in models/layout-dm/src/layout_dm/tokenization_layout_dm.py
439
440
441
def full_id_maps(self) -> dict[str, list[int]]:
    """Return full vocabulary id lists for every token variable."""
    return {key: self._mapping(key)["full"].tolist() for key in self.var_names}

training

Training entry points for LayoutDM.

config

Configuration enums for LayoutDM training.

LayoutDMTrainingDatasetName module-attribute

LayoutDMTrainingDatasetName: TypeAlias = Literal[
    "rico25", "publaynet"
]

Dataset names supported by package-local LayoutDM training data.

LayoutDMTrainingDatasetSource module-attribute

LayoutDMTrainingDatasetSource: TypeAlias = Literal[
    "hf", "processed"
]

Dataset source modes supported by package-local LayoutDM training data.

LayoutDMTrainingSplit module-attribute

LayoutDMTrainingSplit: TypeAlias = Literal[
    "train", "validation", "test"
]

Split names supported by package-local LayoutDM training data.

LayoutDMTrainingTransform module-attribute

LayoutDMTrainingTransform: TypeAlias = Literal[
    "RandomOrder"
]

Training-only layout transforms supported by package-local LayoutDM data.

LayoutDMTrainingScheduler module-attribute

LayoutDMTrainingScheduler: TypeAlias = Literal[
    "reduce_on_plateau"
]

Scheduler names supported by package-local LayoutDM training.

LayoutDMTimeSampler module-attribute

LayoutDMTimeSampler: TypeAlias = Literal[
    "importance", "uniform"
]

Timestep-sampling strategies used by the categorical diffusion loss.

LayoutDMSeedMode

Bases: StrEnum

Seed modes for regular and deterministic LayoutDM training.

Source code in models/layout-dm/src/layout_dm/training/config.py
27
28
29
30
31
class LayoutDMSeedMode(StrEnum):
    """Seed modes for regular and deterministic LayoutDM training."""

    default = auto()
    deterministic = auto()

datamodule

LightningDataModule for LayoutDM training.

LayoutDMDataModule

Bases: LightningDataModule

Package-local LightningDataModule for LayoutDM training data.

Source code in models/layout-dm/src/layout_dm/training/datamodule.py
 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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
class LayoutDMDataModule(LightningDataModule):
    """Package-local LightningDataModule for LayoutDM training data."""

    def __init__(
        self,
        *,
        dataset_name: LayoutDMTrainingDatasetName = "publaynet",
        config: LayoutDMConfig,
        batch_size: int = 256,
        max_seq_length: int | None = None,
        num_workers: int = 4,
        box_format: BoxFormat | str = BoxFormat.xywh,
        normalized: bool = True,
        synthetic_size: int | None = None,
        dataset_source: LayoutDMTrainingDatasetSource = "hf",
        processed_data_dir: str | None = None,
        train_transforms: Sequence[LayoutDMTrainingTransform] | None = ("RandomOrder",),
    ) -> None:
        """Initialize datamodule settings."""
        super().__init__()

        self.dataset_name = dataset_name
        self.config = config
        self.batch_size = batch_size
        self.max_seq_length = max_seq_length or self.config.max_seq_length
        self.num_workers = num_workers
        self.box_format = box_format
        self.normalized = normalized
        self.synthetic_size = synthetic_size
        self.dataset_source = dataset_source
        self.processed_data_dir = processed_data_dir
        self.train_transforms = tuple(train_transforms or ())

        unsupported = set(self.train_transforms) - {"RandomOrder"}
        if unsupported:
            raise ValueError(f"Unsupported LayoutDM train transforms: {unsupported}")

        self.tokenizer = LayoutDMTokenizer(self.config)
        self.train_dataset: (
            Dataset[dict[str, Shaped[torch.Tensor, "..."] | str]] | None
        ) = None
        self.val_dataset: (
            Dataset[dict[str, Shaped[torch.Tensor, "..."] | str]] | None
        ) = None
        self.test_dataset: (
            Dataset[dict[str, Shaped[torch.Tensor, "..."] | str]] | None
        ) = None

    def setup(self, stage: str | None = None) -> None:
        """Open datasets for the requested stage."""
        if stage in {None, "fit"}:
            self.train_dataset = self._dataset("train")
            self.val_dataset = self._dataset("validation")
        if stage in {None, "test"}:
            self.test_dataset = self._dataset("test")

    def train_dataloader(
        self,
    ) -> DataLoader[dict[str, Shaped[torch.Tensor, "..."] | str]]:
        """Return the training dataloader."""
        if self.train_dataset is None:
            self.setup("fit")
        return self._loader(self.train_dataset, shuffle=self.synthetic_size is None)

    def val_dataloader(
        self,
    ) -> DataLoader[dict[str, Shaped[torch.Tensor, "..."] | str]]:
        """Return the validation dataloader."""
        if self.val_dataset is None:
            self.setup("fit")
        return self._loader(self.val_dataset, shuffle=False)

    def test_dataloader(
        self,
    ) -> DataLoader[dict[str, Shaped[torch.Tensor, "..."] | str]]:
        """Return the test dataloader."""
        if self.test_dataset is None:
            self.setup("test")
        return self._loader(self.test_dataset, shuffle=False)

    def _dataset(
        self, split: LayoutDMTrainingSplit
    ) -> Dataset[dict[str, Shaped[torch.Tensor, "..."] | str]]:
        if self.synthetic_size is not None:
            return LayoutDMSyntheticDataset(
                config=self.config,
                size=self.synthetic_size,
                elements=min(3, self.max_seq_length),
            )
        if self.dataset_source == "processed":
            if self.processed_data_dir is None:
                raise ValueError(
                    "processed_data_dir is required when dataset_source='processed'"
                )

            return LayoutDMProcessedDataset(
                dataset_name=self.dataset_name,
                split=split,
                config=self.config,
                tokenizer=self.tokenizer,
                max_seq_length=self.max_seq_length,
                processed_data_dir=self.processed_data_dir,
                random_order=self._uses_random_order(split),
            )
        return LayoutDMDataset(
            dataset_name=self.dataset_name,
            split=split,
            config=self.config,
            tokenizer=self.tokenizer,
            max_seq_length=self.max_seq_length,
            box_format=self.box_format,
            normalized=self.normalized,
            random_order=self._uses_random_order(split),
        )

    def _uses_random_order(self, split: LayoutDMTrainingSplit) -> bool:
        del split
        return "RandomOrder" in self.train_transforms

    def _loader(
        self,
        dataset: Dataset[dict[str, Shaped[torch.Tensor, "..."] | str]] | None,
        *,
        shuffle: bool,
    ) -> DataLoader[dict[str, Shaped[torch.Tensor, "..."] | str]]:
        if dataset is None:
            raise RuntimeError("Dataset has not been initialized")

        return DataLoader(
            dataset,
            batch_size=self.batch_size,
            shuffle=shuffle,
            num_workers=self.num_workers,
        )
__init__
__init__(
    *,
    dataset_name: LayoutDMTrainingDatasetName = "publaynet",
    config: LayoutDMConfig,
    batch_size: int = 256,
    max_seq_length: int | None = None,
    num_workers: int = 4,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    synthetic_size: int | None = None,
    dataset_source: LayoutDMTrainingDatasetSource = "hf",
    processed_data_dir: str | None = None,
    train_transforms: Sequence[LayoutDMTrainingTransform]
    | None = ("RandomOrder",),
) -> None

Initialize datamodule settings.

Source code in models/layout-dm/src/layout_dm/training/datamodule.py
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
def __init__(
    self,
    *,
    dataset_name: LayoutDMTrainingDatasetName = "publaynet",
    config: LayoutDMConfig,
    batch_size: int = 256,
    max_seq_length: int | None = None,
    num_workers: int = 4,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    synthetic_size: int | None = None,
    dataset_source: LayoutDMTrainingDatasetSource = "hf",
    processed_data_dir: str | None = None,
    train_transforms: Sequence[LayoutDMTrainingTransform] | None = ("RandomOrder",),
) -> None:
    """Initialize datamodule settings."""
    super().__init__()

    self.dataset_name = dataset_name
    self.config = config
    self.batch_size = batch_size
    self.max_seq_length = max_seq_length or self.config.max_seq_length
    self.num_workers = num_workers
    self.box_format = box_format
    self.normalized = normalized
    self.synthetic_size = synthetic_size
    self.dataset_source = dataset_source
    self.processed_data_dir = processed_data_dir
    self.train_transforms = tuple(train_transforms or ())

    unsupported = set(self.train_transforms) - {"RandomOrder"}
    if unsupported:
        raise ValueError(f"Unsupported LayoutDM train transforms: {unsupported}")

    self.tokenizer = LayoutDMTokenizer(self.config)
    self.train_dataset: (
        Dataset[dict[str, Shaped[torch.Tensor, "..."] | str]] | None
    ) = None
    self.val_dataset: (
        Dataset[dict[str, Shaped[torch.Tensor, "..."] | str]] | None
    ) = None
    self.test_dataset: (
        Dataset[dict[str, Shaped[torch.Tensor, "..."] | str]] | None
    ) = None
setup
setup(stage: str | None = None) -> None

Open datasets for the requested stage.

Source code in models/layout-dm/src/layout_dm/training/datamodule.py
73
74
75
76
77
78
79
def setup(self, stage: str | None = None) -> None:
    """Open datasets for the requested stage."""
    if stage in {None, "fit"}:
        self.train_dataset = self._dataset("train")
        self.val_dataset = self._dataset("validation")
    if stage in {None, "test"}:
        self.test_dataset = self._dataset("test")
train_dataloader
train_dataloader() -> DataLoader[
    dict[str, Shaped[torch.Tensor, "..."] | str]
]

Return the training dataloader.

Source code in models/layout-dm/src/layout_dm/training/datamodule.py
81
82
83
84
85
86
87
def train_dataloader(
    self,
) -> DataLoader[dict[str, Shaped[torch.Tensor, "..."] | str]]:
    """Return the training dataloader."""
    if self.train_dataset is None:
        self.setup("fit")
    return self._loader(self.train_dataset, shuffle=self.synthetic_size is None)
val_dataloader
val_dataloader() -> DataLoader[
    dict[str, Shaped[torch.Tensor, "..."] | str]
]

Return the validation dataloader.

Source code in models/layout-dm/src/layout_dm/training/datamodule.py
89
90
91
92
93
94
95
def val_dataloader(
    self,
) -> DataLoader[dict[str, Shaped[torch.Tensor, "..."] | str]]:
    """Return the validation dataloader."""
    if self.val_dataset is None:
        self.setup("fit")
    return self._loader(self.val_dataset, shuffle=False)
test_dataloader
test_dataloader() -> DataLoader[
    dict[str, Shaped[torch.Tensor, "..."] | str]
]

Return the test dataloader.

Source code in models/layout-dm/src/layout_dm/training/datamodule.py
 97
 98
 99
100
101
102
103
def test_dataloader(
    self,
) -> DataLoader[dict[str, Shaped[torch.Tensor, "..."] | str]]:
    """Return the test dataloader."""
    if self.test_dataset is None:
        self.setup("test")
    return self._loader(self.test_dataset, shuffle=False)

dataset

Dataset and collation helpers for LayoutDM training.

LayoutDMDataset

Bases: Dataset[dict[str, Shaped[Tensor, '...'] | str]]

HF datasets-backed LayoutDM training dataset.

Source code in models/layout-dm/src/layout_dm/training/dataset.py
 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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
class LayoutDMDataset(TorchDataset[dict[str, Shaped[torch.Tensor, "..."] | str]]):
    """HF datasets-backed LayoutDM training dataset."""

    def __init__(
        self,
        *,
        dataset_name: LayoutDMTrainingDatasetName,
        config: LayoutDMConfig,
        split: LayoutDMTrainingSplit = "train",
        tokenizer: LayoutDMTokenizer | None = None,
        max_seq_length: int | None = None,
        box_format: BoxFormat | str = BoxFormat.xywh,
        normalized: bool = True,
        random_order: bool = False,
    ) -> None:
        """Load a LayoutDM training split from the approved HF dataset source.

        Args:
            dataset_name: ``rico25`` or ``publaynet``.
            split: Dataset split to load.
            config: Optional LayoutDM configuration.
            tokenizer: Optional tokenizer. Built from ``config`` otherwise.
            max_seq_length: Optional element cap before tokenization.
            box_format: Source box format.
            normalized: Whether source boxes are already normalized.
            random_order: Whether to apply the RandomOrder transform before
                tokenization.
        """
        super().__init__()
        self.dataset_name = dataset_name
        self.split = split
        self.config = config
        self.tokenizer = tokenizer or LayoutDMTokenizer(self.config)
        self.processor = LayoutDMProcessor(self.tokenizer)
        self.max_seq_length = max_seq_length or self.config.max_seq_length
        self.box_format = box_format
        self.normalized = normalized
        self.random_order = random_order
        self.label2id = _casefold_mapping(label2id_for_dataset(dataset_name))

        import datasets

        path, name = _DATASET_IDS[dataset_name]
        self.dataset: HFDataset = datasets.load_dataset(
            path, name=name, split=split, streaming=False
        )

    def __len__(self) -> int:
        """Return dataset size when the underlying split exposes it."""
        return len(self.dataset)

    def __getitem__(self, index: int) -> dict[str, Shaped[torch.Tensor, "..."] | str]:
        """Return one tokenized training example."""
        sample = cast(Mapping[str, LayoutDMValue], self.dataset[index])
        return self._encode_sample(sample)

    def _encode_sample(
        self, sample: Mapping[str, LayoutDMValue]
    ) -> dict[str, Shaped[torch.Tensor, "..."] | str]:
        bbox, labels, canvas_size = _extract_layout(sample, self.label2id)
        if self.random_order:
            _assert_no_canvas_element(sample)
            bbox, labels = _random_order_layout(bbox, labels)
        bbox = bbox[: self.max_seq_length]
        labels = labels[: self.max_seq_length]
        mask = torch.ones(labels.shape, dtype=torch.bool)
        encoded = self.processor(
            bbox=bbox.unsqueeze(0),
            labels=labels.unsqueeze(0),
            mask=mask.unsqueeze(0),
            box_format=self.box_format,
            normalized=self.normalized,
            canvas_size=canvas_size,
        )
        output: dict[str, Shaped[torch.Tensor, "..."] | str] = {
            key: value.squeeze(0) for key, value in encoded.items()
        }
        sample_id = sample.get("id") or sample.get("image_id") or sample.get("doc_id")
        if sample_id is not None:
            output["id"] = str(sample_id)
        return output
__init__
__init__(
    *,
    dataset_name: LayoutDMTrainingDatasetName,
    config: LayoutDMConfig,
    split: LayoutDMTrainingSplit = "train",
    tokenizer: LayoutDMTokenizer | None = None,
    max_seq_length: int | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    random_order: bool = False,
) -> None

Load a LayoutDM training split from the approved HF dataset source.

Parameters:

Name Type Description Default
dataset_name LayoutDMTrainingDatasetName

rico25 or publaynet.

required
split LayoutDMTrainingSplit

Dataset split to load.

'train'
config LayoutDMConfig

Optional LayoutDM configuration.

required
tokenizer LayoutDMTokenizer | None

Optional tokenizer. Built from config otherwise.

None
max_seq_length int | None

Optional element cap before tokenization.

None
box_format BoxFormat | str

Source box format.

xywh
normalized bool

Whether source boxes are already normalized.

True
random_order bool

Whether to apply the RandomOrder transform before tokenization.

False
Source code in models/layout-dm/src/layout_dm/training/dataset.py
 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
def __init__(
    self,
    *,
    dataset_name: LayoutDMTrainingDatasetName,
    config: LayoutDMConfig,
    split: LayoutDMTrainingSplit = "train",
    tokenizer: LayoutDMTokenizer | None = None,
    max_seq_length: int | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    random_order: bool = False,
) -> None:
    """Load a LayoutDM training split from the approved HF dataset source.

    Args:
        dataset_name: ``rico25`` or ``publaynet``.
        split: Dataset split to load.
        config: Optional LayoutDM configuration.
        tokenizer: Optional tokenizer. Built from ``config`` otherwise.
        max_seq_length: Optional element cap before tokenization.
        box_format: Source box format.
        normalized: Whether source boxes are already normalized.
        random_order: Whether to apply the RandomOrder transform before
            tokenization.
    """
    super().__init__()
    self.dataset_name = dataset_name
    self.split = split
    self.config = config
    self.tokenizer = tokenizer or LayoutDMTokenizer(self.config)
    self.processor = LayoutDMProcessor(self.tokenizer)
    self.max_seq_length = max_seq_length or self.config.max_seq_length
    self.box_format = box_format
    self.normalized = normalized
    self.random_order = random_order
    self.label2id = _casefold_mapping(label2id_for_dataset(dataset_name))

    import datasets

    path, name = _DATASET_IDS[dataset_name]
    self.dataset: HFDataset = datasets.load_dataset(
        path, name=name, split=split, streaming=False
    )
__len__
__len__() -> int

Return dataset size when the underlying split exposes it.

Source code in models/layout-dm/src/layout_dm/training/dataset.py
109
110
111
def __len__(self) -> int:
    """Return dataset size when the underlying split exposes it."""
    return len(self.dataset)
__getitem__
__getitem__(
    index: int,
) -> dict[str, Shaped[torch.Tensor, "..."] | str]

Return one tokenized training example.

Source code in models/layout-dm/src/layout_dm/training/dataset.py
113
114
115
116
def __getitem__(self, index: int) -> dict[str, Shaped[torch.Tensor, "..."] | str]:
    """Return one tokenized training example."""
    sample = cast(Mapping[str, LayoutDMValue], self.dataset[index])
    return self._encode_sample(sample)

LayoutDMProcessedDataset

Bases: Dataset[dict[str, Shaped[Tensor, '...'] | str]]

Preprocessed LayoutDM training data stream.

Source code in models/layout-dm/src/layout_dm/training/dataset.py
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
class LayoutDMProcessedDataset(
    TorchDataset[dict[str, Shaped[torch.Tensor, "..."] | str]]
):
    """Preprocessed LayoutDM training data stream."""

    def __init__(
        self,
        *,
        dataset_name: LayoutDMTrainingDatasetName,
        config: LayoutDMConfig,
        processed_data_dir: str | Path,
        split: LayoutDMTrainingSplit = "train",
        tokenizer: LayoutDMTokenizer | None = None,
        max_seq_length: int | None = None,
        random_order: bool = False,
    ) -> None:
        """Load a preprocessed split from a local data directory.

        Args:
            dataset_name: ``rico25`` or ``publaynet``.
            config: LayoutDM configuration.
            processed_data_dir: Directory containing ``<dataset>-max<S>/processed``.
            split: Package split name. ``validation`` maps to processed ``val``.
            tokenizer: Optional tokenizer. Built from ``config`` otherwise.
            max_seq_length: Optional element cap used in the processed directory name.
            random_order: Whether to apply the RandomOrder transform before
                tokenization.
        """
        super().__init__()
        self.dataset_name = dataset_name
        self.split = split
        self.config = config
        self.tokenizer = tokenizer or LayoutDMTokenizer(self.config)
        self.processor = LayoutDMProcessor(self.tokenizer)
        self.max_seq_length = max_seq_length or self.config.max_seq_length
        self.random_order = random_order
        split_name = _PROCESSED_SPLITS[split]
        path = (
            Path(processed_data_dir)
            / f"{dataset_name}-max{self.max_seq_length}"
            / "processed"
            / f"{split_name}.pt"
        )
        self.data, self.slices = _load_processed_split(path)
        self.length = _processed_length(self.slices)

    def __len__(self) -> int:
        """Return the number of layouts in the processed split."""
        return self.length

    def __getitem__(self, index: int) -> dict[str, Shaped[torch.Tensor, "..."] | str]:
        """Return one tokenized training example."""
        bbox, labels, sample_id = _processed_row(self.data, self.slices, index)
        if self.random_order:
            _assert_processed_no_canvas_element(self.data.attr, index)
            bbox, labels = _random_order_layout(bbox, labels)
        mask = torch.ones(labels.shape, dtype=torch.bool)
        encoded = self.processor(
            bbox=bbox.unsqueeze(0),
            labels=labels.unsqueeze(0),
            mask=mask.unsqueeze(0),
        )
        output: dict[str, Shaped[torch.Tensor, "..."] | str] = {
            key: value.squeeze(0) for key, value in encoded.items()
        }
        if sample_id is not None:
            output["id"] = sample_id
        return output
__init__
__init__(
    *,
    dataset_name: LayoutDMTrainingDatasetName,
    config: LayoutDMConfig,
    processed_data_dir: str | Path,
    split: LayoutDMTrainingSplit = "train",
    tokenizer: LayoutDMTokenizer | None = None,
    max_seq_length: int | None = None,
    random_order: bool = False,
) -> None

Load a preprocessed split from a local data directory.

Parameters:

Name Type Description Default
dataset_name LayoutDMTrainingDatasetName

rico25 or publaynet.

required
config LayoutDMConfig

LayoutDM configuration.

required
processed_data_dir str | Path

Directory containing <dataset>-max<S>/processed.

required
split LayoutDMTrainingSplit

Package split name. validation maps to processed val.

'train'
tokenizer LayoutDMTokenizer | None

Optional tokenizer. Built from config otherwise.

None
max_seq_length int | None

Optional element cap used in the processed directory name.

None
random_order bool

Whether to apply the RandomOrder transform before tokenization.

False
Source code in models/layout-dm/src/layout_dm/training/dataset.py
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
def __init__(
    self,
    *,
    dataset_name: LayoutDMTrainingDatasetName,
    config: LayoutDMConfig,
    processed_data_dir: str | Path,
    split: LayoutDMTrainingSplit = "train",
    tokenizer: LayoutDMTokenizer | None = None,
    max_seq_length: int | None = None,
    random_order: bool = False,
) -> None:
    """Load a preprocessed split from a local data directory.

    Args:
        dataset_name: ``rico25`` or ``publaynet``.
        config: LayoutDM configuration.
        processed_data_dir: Directory containing ``<dataset>-max<S>/processed``.
        split: Package split name. ``validation`` maps to processed ``val``.
        tokenizer: Optional tokenizer. Built from ``config`` otherwise.
        max_seq_length: Optional element cap used in the processed directory name.
        random_order: Whether to apply the RandomOrder transform before
            tokenization.
    """
    super().__init__()
    self.dataset_name = dataset_name
    self.split = split
    self.config = config
    self.tokenizer = tokenizer or LayoutDMTokenizer(self.config)
    self.processor = LayoutDMProcessor(self.tokenizer)
    self.max_seq_length = max_seq_length or self.config.max_seq_length
    self.random_order = random_order
    split_name = _PROCESSED_SPLITS[split]
    path = (
        Path(processed_data_dir)
        / f"{dataset_name}-max{self.max_seq_length}"
        / "processed"
        / f"{split_name}.pt"
    )
    self.data, self.slices = _load_processed_split(path)
    self.length = _processed_length(self.slices)
__len__
__len__() -> int

Return the number of layouts in the processed split.

Source code in models/layout-dm/src/layout_dm/training/dataset.py
191
192
193
def __len__(self) -> int:
    """Return the number of layouts in the processed split."""
    return self.length
__getitem__
__getitem__(
    index: int,
) -> dict[str, Shaped[torch.Tensor, "..."] | str]

Return one tokenized training example.

Source code in models/layout-dm/src/layout_dm/training/dataset.py
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
def __getitem__(self, index: int) -> dict[str, Shaped[torch.Tensor, "..."] | str]:
    """Return one tokenized training example."""
    bbox, labels, sample_id = _processed_row(self.data, self.slices, index)
    if self.random_order:
        _assert_processed_no_canvas_element(self.data.attr, index)
        bbox, labels = _random_order_layout(bbox, labels)
    mask = torch.ones(labels.shape, dtype=torch.bool)
    encoded = self.processor(
        bbox=bbox.unsqueeze(0),
        labels=labels.unsqueeze(0),
        mask=mask.unsqueeze(0),
    )
    output: dict[str, Shaped[torch.Tensor, "..."] | str] = {
        key: value.squeeze(0) for key, value in encoded.items()
    }
    if sample_id is not None:
        output["id"] = sample_id
    return output

LayoutDMSyntheticDataset

Bases: Dataset[dict[str, Shaped[Tensor, '...'] | str]]

Small deterministic dataset for local CLI smoke tests.

Source code in models/layout-dm/src/layout_dm/training/dataset.py
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
class LayoutDMSyntheticDataset(
    TorchDataset[dict[str, Shaped[torch.Tensor, "..."] | str]]
):
    """Small deterministic dataset for local CLI smoke tests."""

    def __init__(
        self,
        *,
        config: LayoutDMConfig,
        size: int = 8,
        elements: int = 3,
    ) -> None:
        """Initialize synthetic examples from a LayoutDM config."""
        super().__init__()
        self.config = config
        self.size = size
        self.elements = min(elements, config.max_seq_length)
        self.tokenizer = LayoutDMTokenizer(config)
        self.processor = LayoutDMProcessor(self.tokenizer)

    def __len__(self) -> int:
        """Return synthetic dataset size."""
        return self.size

    def __getitem__(self, index: int) -> dict[str, Shaped[torch.Tensor, "..."] | str]:
        """Return one deterministic synthetic tokenized layout."""
        generator = torch.Generator().manual_seed(index)
        bbox = torch.rand(self.elements, 4, generator=generator)
        bbox[:, 2:] = bbox[:, 2:].mul(0.35).add(0.05)
        labels = (
            torch.arange(self.elements, dtype=torch.long) % self.config.num_categories
        )
        encoded = self.processor(
            bbox=bbox.unsqueeze(0),
            labels=labels.unsqueeze(0),
            mask=torch.ones(1, self.elements, dtype=torch.bool),
        )
        return {key: value.squeeze(0) for key, value in encoded.items()}
__init__
__init__(
    *,
    config: LayoutDMConfig,
    size: int = 8,
    elements: int = 3,
) -> None

Initialize synthetic examples from a LayoutDM config.

Source code in models/layout-dm/src/layout_dm/training/dataset.py
220
221
222
223
224
225
226
227
228
229
230
231
232
233
def __init__(
    self,
    *,
    config: LayoutDMConfig,
    size: int = 8,
    elements: int = 3,
) -> None:
    """Initialize synthetic examples from a LayoutDM config."""
    super().__init__()
    self.config = config
    self.size = size
    self.elements = min(elements, config.max_seq_length)
    self.tokenizer = LayoutDMTokenizer(config)
    self.processor = LayoutDMProcessor(self.tokenizer)
__len__
__len__() -> int

Return synthetic dataset size.

Source code in models/layout-dm/src/layout_dm/training/dataset.py
235
236
237
def __len__(self) -> int:
    """Return synthetic dataset size."""
    return self.size
__getitem__
__getitem__(
    index: int,
) -> dict[str, Shaped[torch.Tensor, "..."] | str]

Return one deterministic synthetic tokenized layout.

Source code in models/layout-dm/src/layout_dm/training/dataset.py
239
240
241
242
243
244
245
246
247
248
249
250
251
252
def __getitem__(self, index: int) -> dict[str, Shaped[torch.Tensor, "..."] | str]:
    """Return one deterministic synthetic tokenized layout."""
    generator = torch.Generator().manual_seed(index)
    bbox = torch.rand(self.elements, 4, generator=generator)
    bbox[:, 2:] = bbox[:, 2:].mul(0.35).add(0.05)
    labels = (
        torch.arange(self.elements, dtype=torch.long) % self.config.num_categories
    )
    encoded = self.processor(
        bbox=bbox.unsqueeze(0),
        labels=labels.unsqueeze(0),
        mask=torch.ones(1, self.elements, dtype=torch.bool),
    )
    return {key: value.squeeze(0) for key, value in encoded.items()}

lightning_module

PyTorch Lightning module for LayoutDM discrete-diffusion training.

LayoutDMTrainingModule

Bases: LightningModule

Lightning wrapper reproducing LayoutDM categorical-diffusion training.

Source code in models/layout-dm/src/layout_dm/training/lightning_module.py
 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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
class LayoutDMTrainingModule(LightningModule):
    """Lightning wrapper reproducing LayoutDM categorical-diffusion training."""

    lt_history: Float[torch.Tensor, "timesteps"]
    lt_count: Float[torch.Tensor, "timesteps"]

    def __init__(
        self,
        *,
        config: LayoutDMConfig,
        model: LayoutDMDenoiser | None = None,
        tokenizer: LayoutDMTokenizer | None = None,
        learning_rate: float = 5e-4,
        weight_decay: float = 0.1,
        betas: tuple[float, float] = (0.9, 0.98),
        auxiliary_loss_weight: float = 0.1,
        adaptive_auxiliary_loss: bool = True,
        time_sampler: LayoutDMTimeSampler = "importance",
        scheduler: LayoutDMTrainingScheduler | None = "reduce_on_plateau",
        scheduler_factor: float = 0.5,
        scheduler_patience: int = 2,
        scheduler_threshold: float = 1e-2,
        seed_mode: LayoutDMSeedMode | str = LayoutDMSeedMode.default,
    ) -> None:
        """Initialize LayoutDM training state.

        Args:
            config: LayoutDM architecture and tokenizer configuration.
            model: Optional pre-built denoiser. Built from ``config`` otherwise.
            tokenizer: Optional pre-built tokenizer. Built from ``config``
                otherwise.
            learning_rate: AdamW learning rate.
            weight_decay: Weight decay applied to the decay parameter group.
            betas: AdamW beta coefficients.
            auxiliary_loss_weight: Weight of the auxiliary cross-entropy term.
            adaptive_auxiliary_loss: Whether to scale the auxiliary term by the
                per-timestep adaptive weight.
            time_sampler: Timestep-sampling strategy.
            scheduler: Optional learning-rate scheduler name.
            scheduler_factor: ``ReduceLROnPlateau`` multiplicative factor.
            scheduler_patience: ``ReduceLROnPlateau`` patience in epochs.
            scheduler_threshold: ``ReduceLROnPlateau`` improvement threshold.
            seed_mode: Regular or deterministic seed mode.
        """
        super().__init__()
        self.layout_dm_config = config
        self.model = model or LayoutDMDenoiser(
            vocab_size=config.vocab_size,
            max_token_length=config.max_token_length,
            hidden_size=config.hidden_size,
            num_attention_heads=config.num_attention_heads,
            num_hidden_layers=config.num_hidden_layers,
            intermediate_size=config.intermediate_size,
            dropout=config.dropout,
            timestep_type=cast(
                'Literal["adalayernorm", "adalayernorm_abs"] | None',
                config.timestep_type,
            ),
        )
        self.tokenizer = tokenizer or LayoutDMTokenizer(config)
        self.var_order = tuple(config.var_order.split("-"))
        per_var_full_ids = (
            self.tokenizer.full_id_maps() if config.q_type == "constrained" else None
        )
        self.diffusion_scheduler = LayoutDMScheduler(
            num_timesteps=config.num_timesteps,
            q_type=config.q_type,  # type: ignore[arg-type]
            vocab_size=config.vocab_size,
            mask_token_id=config.mask_token_id,
            pad_token_id=config.pad_token_id,
            var_order=self.var_order,
            per_var_full_ids=per_var_full_ids,
            att_1=config.att_1,
            att_T=config.att_T,
            ctt_1=config.ctt_1,
            ctt_T=config.ctt_T,
        )
        self.num_timesteps = config.num_timesteps
        self.num_classes = config.vocab_size

        self.learning_rate = learning_rate
        self.weight_decay = weight_decay
        self.betas = betas
        self.auxiliary_loss_weight = auxiliary_loss_weight
        self.adaptive_auxiliary_loss = adaptive_auxiliary_loss

        self.time_sampler: LayoutDMTimeSampler = time_sampler
        self.scheduler = scheduler
        self.scheduler_factor = scheduler_factor
        self.scheduler_patience = scheduler_patience
        self.scheduler_threshold = scheduler_threshold

        self.seed_mode = LayoutDMSeedMode(seed_mode)
        self.mat_size = {key: len(ids) for key, ids in (per_var_full_ids or {}).items()}
        self.register_buffer("lt_history", torch.zeros(self.num_timesteps))
        self.register_buffer("lt_count", torch.zeros(self.num_timesteps))
        self.latest_step_trace: dict[str, Shaped[torch.Tensor, "..."]] = {}

    # -- optimization ---------------------------------------------------------

    def optim_groups(self) -> list[dict[str, list[nn.Parameter] | float]]:
        """Split parameters into weight-decayed and decay-free groups."""
        decay: set[str] = set()
        no_decay: set[str] = set()
        whitelist = (nn.Linear, nn.MultiheadAttention)
        blacklist = (nn.LayerNorm, nn.Embedding)
        for module_name, module in self.model.named_modules():
            for param_name, _ in module.named_parameters(recurse=False):
                full = f"{module_name}.{param_name}" if module_name else param_name
                if param_name.endswith("bias"):
                    no_decay.add(full)
                elif param_name.endswith("weight") and isinstance(module, whitelist):
                    decay.add(full)
                elif param_name.endswith("weight") and isinstance(module, blacklist):
                    no_decay.add(full)
                else:
                    no_decay.add(full)
        params = dict(self.model.named_parameters())
        inter = decay & no_decay
        assert not inter, f"parameters {inter} in both decay/no_decay groups"
        missing = set(params) - (decay | no_decay)
        assert not missing, f"parameters {missing} were not assigned a group"
        return [
            {
                "params": [params[name] for name in sorted(decay)],
                "weight_decay": self.weight_decay,
            },
            {
                "params": [params[name] for name in sorted(no_decay)],
                "weight_decay": 0.0,
            },
        ]

    def configure_optimizers(self) -> OptimizerLRScheduler:
        """Return AdamW and an optional ``ReduceLROnPlateau`` scheduler."""
        optimizer = torch.optim.AdamW(
            self.optim_groups(), lr=self.learning_rate, betas=self.betas
        )
        if self.scheduler == "reduce_on_plateau":
            plateau = torch.optim.lr_scheduler.ReduceLROnPlateau(
                optimizer,
                mode="min",
                factor=self.scheduler_factor,
                patience=self.scheduler_patience,
                threshold=self.scheduler_threshold,
            )
            return {
                "optimizer": optimizer,
                "lr_scheduler": {
                    "scheduler": plateau,
                    "monitor": "val_loss",
                    "interval": "epoch",
                },
            }
        return optimizer

    # -- diffusion loss -------------------------------------------------------

    def _sample_time(
        self, batch_size: int, device: torch.device
    ) -> tuple[Int[torch.Tensor, "batch"], Float[torch.Tensor, "batch"]]:
        if self.time_sampler == "uniform":
            return sample_time_uniform(
                batch_size, num_timesteps=self.num_timesteps, device=device
            )
        return sample_time_importance(
            batch_size,
            num_timesteps=self.num_timesteps,
            lt_history=self.lt_history,
            lt_count=self.lt_count,
        )

    def _q_sample_full(
        self, x_start: Int[torch.Tensor, "batch tokens"], t: Int[torch.Tensor, "batch"]
    ) -> tuple[
        Float[torch.Tensor, "batch vocab tokens"],
        Int[torch.Tensor, "batch tokens"],
    ]:
        """Draw ``x_t`` from the forward diffusion posterior per variable."""
        if self.layout_dm_config.q_type == "vanilla":
            log_x_start = index_to_log_onehot(x_start, self.num_classes)
            log_qpred = self.diffusion_scheduler._vanilla_q_pred(log_x_start, t)
            xt = log_sample_categorical(log_qpred)
            return index_to_log_onehot(xt, self.num_classes), xt

        batch_size = x_start.shape[0]
        step = len(self.var_order)
        seq_len = x_start.shape[1] // step
        reshaped = x_start.reshape(batch_size, seq_len, step)
        log_xt_full_parts: list[Float[torch.Tensor, "batch vocab tokens"]] = []
        xt_full_parts: list[Int[torch.Tensor, "batch tokens"]] = []

        for i, key in enumerate(self.var_order):
            col_full = reshaped[..., i]
            col_partial = self.tokenizer.full_to_partial_ids(col_full, key)
            log_x_start = index_to_log_onehot(col_partial, self.mat_size[key])
            log_qpred = self.diffusion_scheduler._q_pred(log_x_start, t, key)
            sampled = log_sample_categorical(log_qpred)
            log_xt = index_to_log_onehot(sampled, self.mat_size[key])
            log_xt_full_parts.append(
                self.tokenizer.partial_to_full_log_probs(log_xt, key)
            )
            xt_full_parts.append(self.tokenizer.partial_to_full_ids(sampled, key))

        log_x_t_full = torch.stack(log_xt_full_parts, dim=-1).reshape(
            batch_size, self.num_classes, -1
        )
        xt_full = torch.stack(xt_full_parts, dim=-1).reshape(batch_size, -1)
        return log_x_t_full, xt_full

    def _diffusion_losses(
        self, x_start: Int[torch.Tensor, "batch tokens"], *, is_train: bool
    ) -> tuple[
        dict[str, Float[torch.Tensor, ""]],
        dict[str, Shaped[torch.Tensor, "..."]],
    ]:
        """Compute the LayoutDM variational loss for a token batch."""
        batch_size = x_start.shape[0]
        t, pt = self._sample_time(batch_size, x_start.device)

        log_x_start = index_to_log_onehot(x_start, self.num_classes)
        log_x_t, xt = self._q_sample_full(x_start, t)

        denoiser_logits = self.model(input_ids=xt, timesteps=t).logits
        log_x0_recon = self.diffusion_scheduler.predict_start(denoiser_logits)
        log_model_prob = self.diffusion_scheduler.q_posterior(log_x0_recon, log_x_t, t)
        log_true_prob = self.diffusion_scheduler.q_posterior(log_x_start, log_x_t, t)

        kl = multinomial_kl(log_true_prob, log_model_prob)
        mask_region = (xt == self.num_classes - 1).float()
        mask_weight = mask_region + (1.0 - mask_region)
        kl = mean_except_batch(kl * mask_weight)

        decoder_nll = mean_except_batch(-log_categorical(log_x_start, log_model_prob))
        at_zero = (t == 0).float()
        kl_loss = at_zero * decoder_nll + (1.0 - at_zero) * kl

        update_loss_history(
            kl_loss,
            t,
            self.lt_history,
            self.lt_count,
        )

        losses: dict[str, Float[torch.Tensor, ""]] = {"kl_loss": (kl_loss / pt).mean()}
        if self.auxiliary_loss_weight != 0 and is_train:
            kl_aux = multinomial_kl(log_x_start[:, :-1, :], log_x0_recon[:, :-1, :])
            kl_aux = mean_except_batch(kl_aux * mask_weight)
            kl_aux_loss = at_zero * decoder_nll + (1.0 - at_zero) * kl_aux
            weight = (1 - t / self.num_timesteps) + 1.0
            if not self.adaptive_auxiliary_loss:
                weight = torch.ones_like(weight)
            losses["aux_loss"] = (
                weight * self.auxiliary_loss_weight * kl_aux_loss / pt
            ).mean()

        trace: dict[str, Shaped[torch.Tensor, "..."]] = {
            "t": t.detach(),
            "pt": pt.detach(),
            "xt": xt.detach(),
            "log_model_prob": log_model_prob.detach(),
            "kl": kl.detach(),
            "decoder_nll": decoder_nll.detach(),
            "kl_loss": kl_loss.detach(),
            **{key: value.detach() for key, value in losses.items()},
        }
        return losses, trace

    def training_step(
        self, batch: dict[str, Shaped[torch.Tensor, "..."]], batch_idx: int
    ) -> Float[torch.Tensor, ""]:
        """Run one LayoutDM training step."""
        del batch_idx
        seq = batch["input_ids"].long()
        losses, trace = self._diffusion_losses(seq, is_train=True)
        total, self.latest_step_trace = finish_training_step(self, losses, trace)
        return total

    def validation_step(
        self, batch: dict[str, Shaped[torch.Tensor, "..."]], batch_idx: int
    ) -> Float[torch.Tensor, ""]:
        """Run one LayoutDM validation step."""
        del batch_idx
        seq = batch["input_ids"].long()
        losses, _ = self._diffusion_losses(seq, is_train=True)
        total = sum_loss_values(losses)
        log_validation_loss(self, total)
        return total
__init__
__init__(
    *,
    config: LayoutDMConfig,
    model: LayoutDMDenoiser | None = None,
    tokenizer: LayoutDMTokenizer | None = None,
    learning_rate: float = 0.0005,
    weight_decay: float = 0.1,
    betas: tuple[float, float] = (0.9, 0.98),
    auxiliary_loss_weight: float = 0.1,
    adaptive_auxiliary_loss: bool = True,
    time_sampler: LayoutDMTimeSampler = "importance",
    scheduler: LayoutDMTrainingScheduler
    | None = "reduce_on_plateau",
    scheduler_factor: float = 0.5,
    scheduler_patience: int = 2,
    scheduler_threshold: float = 0.01,
    seed_mode: LayoutDMSeedMode
    | str = LayoutDMSeedMode.default,
) -> None

Initialize LayoutDM training state.

Parameters:

Name Type Description Default
config LayoutDMConfig

LayoutDM architecture and tokenizer configuration.

required
model LayoutDMDenoiser | None

Optional pre-built denoiser. Built from config otherwise.

None
tokenizer LayoutDMTokenizer | None

Optional pre-built tokenizer. Built from config otherwise.

None
learning_rate float

AdamW learning rate.

0.0005
weight_decay float

Weight decay applied to the decay parameter group.

0.1
betas tuple[float, float]

AdamW beta coefficients.

(0.9, 0.98)
auxiliary_loss_weight float

Weight of the auxiliary cross-entropy term.

0.1
adaptive_auxiliary_loss bool

Whether to scale the auxiliary term by the per-timestep adaptive weight.

True
time_sampler LayoutDMTimeSampler

Timestep-sampling strategy.

'importance'
scheduler LayoutDMTrainingScheduler | None

Optional learning-rate scheduler name.

'reduce_on_plateau'
scheduler_factor float

ReduceLROnPlateau multiplicative factor.

0.5
scheduler_patience int

ReduceLROnPlateau patience in epochs.

2
scheduler_threshold float

ReduceLROnPlateau improvement threshold.

0.01
seed_mode LayoutDMSeedMode | str

Regular or deterministic seed mode.

default
Source code in models/layout-dm/src/layout_dm/training/lightning_module.py
 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
127
128
129
130
131
132
133
134
135
136
137
def __init__(
    self,
    *,
    config: LayoutDMConfig,
    model: LayoutDMDenoiser | None = None,
    tokenizer: LayoutDMTokenizer | None = None,
    learning_rate: float = 5e-4,
    weight_decay: float = 0.1,
    betas: tuple[float, float] = (0.9, 0.98),
    auxiliary_loss_weight: float = 0.1,
    adaptive_auxiliary_loss: bool = True,
    time_sampler: LayoutDMTimeSampler = "importance",
    scheduler: LayoutDMTrainingScheduler | None = "reduce_on_plateau",
    scheduler_factor: float = 0.5,
    scheduler_patience: int = 2,
    scheduler_threshold: float = 1e-2,
    seed_mode: LayoutDMSeedMode | str = LayoutDMSeedMode.default,
) -> None:
    """Initialize LayoutDM training state.

    Args:
        config: LayoutDM architecture and tokenizer configuration.
        model: Optional pre-built denoiser. Built from ``config`` otherwise.
        tokenizer: Optional pre-built tokenizer. Built from ``config``
            otherwise.
        learning_rate: AdamW learning rate.
        weight_decay: Weight decay applied to the decay parameter group.
        betas: AdamW beta coefficients.
        auxiliary_loss_weight: Weight of the auxiliary cross-entropy term.
        adaptive_auxiliary_loss: Whether to scale the auxiliary term by the
            per-timestep adaptive weight.
        time_sampler: Timestep-sampling strategy.
        scheduler: Optional learning-rate scheduler name.
        scheduler_factor: ``ReduceLROnPlateau`` multiplicative factor.
        scheduler_patience: ``ReduceLROnPlateau`` patience in epochs.
        scheduler_threshold: ``ReduceLROnPlateau`` improvement threshold.
        seed_mode: Regular or deterministic seed mode.
    """
    super().__init__()
    self.layout_dm_config = config
    self.model = model or LayoutDMDenoiser(
        vocab_size=config.vocab_size,
        max_token_length=config.max_token_length,
        hidden_size=config.hidden_size,
        num_attention_heads=config.num_attention_heads,
        num_hidden_layers=config.num_hidden_layers,
        intermediate_size=config.intermediate_size,
        dropout=config.dropout,
        timestep_type=cast(
            'Literal["adalayernorm", "adalayernorm_abs"] | None',
            config.timestep_type,
        ),
    )
    self.tokenizer = tokenizer or LayoutDMTokenizer(config)
    self.var_order = tuple(config.var_order.split("-"))
    per_var_full_ids = (
        self.tokenizer.full_id_maps() if config.q_type == "constrained" else None
    )
    self.diffusion_scheduler = LayoutDMScheduler(
        num_timesteps=config.num_timesteps,
        q_type=config.q_type,  # type: ignore[arg-type]
        vocab_size=config.vocab_size,
        mask_token_id=config.mask_token_id,
        pad_token_id=config.pad_token_id,
        var_order=self.var_order,
        per_var_full_ids=per_var_full_ids,
        att_1=config.att_1,
        att_T=config.att_T,
        ctt_1=config.ctt_1,
        ctt_T=config.ctt_T,
    )
    self.num_timesteps = config.num_timesteps
    self.num_classes = config.vocab_size

    self.learning_rate = learning_rate
    self.weight_decay = weight_decay
    self.betas = betas
    self.auxiliary_loss_weight = auxiliary_loss_weight
    self.adaptive_auxiliary_loss = adaptive_auxiliary_loss

    self.time_sampler: LayoutDMTimeSampler = time_sampler
    self.scheduler = scheduler
    self.scheduler_factor = scheduler_factor
    self.scheduler_patience = scheduler_patience
    self.scheduler_threshold = scheduler_threshold

    self.seed_mode = LayoutDMSeedMode(seed_mode)
    self.mat_size = {key: len(ids) for key, ids in (per_var_full_ids or {}).items()}
    self.register_buffer("lt_history", torch.zeros(self.num_timesteps))
    self.register_buffer("lt_count", torch.zeros(self.num_timesteps))
    self.latest_step_trace: dict[str, Shaped[torch.Tensor, "..."]] = {}
optim_groups
optim_groups() -> list[
    dict[str, list[nn.Parameter] | float]
]

Split parameters into weight-decayed and decay-free groups.

Source code in models/layout-dm/src/layout_dm/training/lightning_module.py
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
def optim_groups(self) -> list[dict[str, list[nn.Parameter] | float]]:
    """Split parameters into weight-decayed and decay-free groups."""
    decay: set[str] = set()
    no_decay: set[str] = set()
    whitelist = (nn.Linear, nn.MultiheadAttention)
    blacklist = (nn.LayerNorm, nn.Embedding)
    for module_name, module in self.model.named_modules():
        for param_name, _ in module.named_parameters(recurse=False):
            full = f"{module_name}.{param_name}" if module_name else param_name
            if param_name.endswith("bias"):
                no_decay.add(full)
            elif param_name.endswith("weight") and isinstance(module, whitelist):
                decay.add(full)
            elif param_name.endswith("weight") and isinstance(module, blacklist):
                no_decay.add(full)
            else:
                no_decay.add(full)
    params = dict(self.model.named_parameters())
    inter = decay & no_decay
    assert not inter, f"parameters {inter} in both decay/no_decay groups"
    missing = set(params) - (decay | no_decay)
    assert not missing, f"parameters {missing} were not assigned a group"
    return [
        {
            "params": [params[name] for name in sorted(decay)],
            "weight_decay": self.weight_decay,
        },
        {
            "params": [params[name] for name in sorted(no_decay)],
            "weight_decay": 0.0,
        },
    ]
configure_optimizers
configure_optimizers() -> OptimizerLRScheduler

Return AdamW and an optional ReduceLROnPlateau scheduler.

Source code in models/layout-dm/src/layout_dm/training/lightning_module.py
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
def configure_optimizers(self) -> OptimizerLRScheduler:
    """Return AdamW and an optional ``ReduceLROnPlateau`` scheduler."""
    optimizer = torch.optim.AdamW(
        self.optim_groups(), lr=self.learning_rate, betas=self.betas
    )
    if self.scheduler == "reduce_on_plateau":
        plateau = torch.optim.lr_scheduler.ReduceLROnPlateau(
            optimizer,
            mode="min",
            factor=self.scheduler_factor,
            patience=self.scheduler_patience,
            threshold=self.scheduler_threshold,
        )
        return {
            "optimizer": optimizer,
            "lr_scheduler": {
                "scheduler": plateau,
                "monitor": "val_loss",
                "interval": "epoch",
            },
        }
    return optimizer
training_step
training_step(
    batch: dict[str, Shaped[Tensor, "..."]], batch_idx: int
) -> Float[torch.Tensor, ""]

Run one LayoutDM training step.

Source code in models/layout-dm/src/layout_dm/training/lightning_module.py
309
310
311
312
313
314
315
316
317
def training_step(
    self, batch: dict[str, Shaped[torch.Tensor, "..."]], batch_idx: int
) -> Float[torch.Tensor, ""]:
    """Run one LayoutDM training step."""
    del batch_idx
    seq = batch["input_ids"].long()
    losses, trace = self._diffusion_losses(seq, is_train=True)
    total, self.latest_step_trace = finish_training_step(self, losses, trace)
    return total
validation_step
validation_step(
    batch: dict[str, Shaped[Tensor, "..."]], batch_idx: int
) -> Float[torch.Tensor, ""]

Run one LayoutDM validation step.

Source code in models/layout-dm/src/layout_dm/training/lightning_module.py
319
320
321
322
323
324
325
326
327
328
def validation_step(
    self, batch: dict[str, Shaped[torch.Tensor, "..."]], batch_idx: int
) -> Float[torch.Tensor, ""]:
    """Run one LayoutDM validation step."""
    del batch_idx
    seq = batch["input_ids"].long()
    losses, _ = self._diffusion_losses(seq, is_train=True)
    total = sum_loss_values(losses)
    log_validation_loss(self, total)
    return total

losses

Categorical diffusion training-loss helpers for LayoutDM.

log_categorical

log_categorical(
    log_x_start: Float[Tensor, "batch vocab tokens"],
    log_prob: Float[Tensor, "batch vocab tokens"],
) -> Float[torch.Tensor, "batch tokens"]

Categorical log-likelihood of log_x_start under log_prob.

Parameters:

Name Type Description Default
log_x_start Float[Tensor, 'batch vocab tokens']

Log one-hot targets.

required
log_prob Float[Tensor, 'batch vocab tokens']

Predicted log probabilities.

required

Returns:

Type Description
Float[Tensor, 'batch tokens']

Per-token log-likelihood with the vocabulary dimension reduced.

Examples:

>>> import torch
>>> target = torch.log(torch.tensor([[[1.0], [0.0]]]).clamp_min(1e-30))
>>> probs = torch.log(torch.tensor([[[0.25], [0.75]]]))
>>> log_categorical(target, probs).shape
torch.Size([1, 1])
Source code in lib/laygen/src/laygen/common/discrete.py
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
def log_categorical(
    log_x_start: Float[torch.Tensor, "batch vocab tokens"],
    log_prob: Float[torch.Tensor, "batch vocab tokens"],
) -> Float[torch.Tensor, "batch tokens"]:
    """Categorical log-likelihood of ``log_x_start`` under ``log_prob``.

    Args:
        log_x_start: Log one-hot targets.
        log_prob: Predicted log probabilities.

    Returns:
        Per-token log-likelihood with the vocabulary dimension reduced.

    Examples:
        >>> import torch
        >>> target = torch.log(torch.tensor([[[1.0], [0.0]]]).clamp_min(1e-30))
        >>> probs = torch.log(torch.tensor([[[0.25], [0.75]]]))
        >>> log_categorical(target, probs).shape
        torch.Size([1, 1])
    """
    return (log_x_start.exp() * log_prob).sum(dim=1)

multinomial_kl

multinomial_kl(
    log_prob1: Float[Tensor, "batch vocab tokens"],
    log_prob2: Float[Tensor, "batch vocab tokens"],
) -> Float[torch.Tensor, "batch tokens"]

Categorical KL divergence summed over the vocabulary dimension.

Parameters:

Name Type Description Default
log_prob1 Float[Tensor, 'batch vocab tokens']

Log probabilities of the reference distribution.

required
log_prob2 Float[Tensor, 'batch vocab tokens']

Log probabilities of the compared distribution.

required

Returns:

Type Description
Float[Tensor, 'batch tokens']

Per-token KL divergence with the vocabulary dimension reduced.

Examples:

>>> import torch
>>> a = torch.log(torch.tensor([[[1.0], [0.0]]]).clamp_min(1e-30))
>>> float(multinomial_kl(a, a).sum())
0.0
Source code in lib/laygen/src/laygen/common/discrete.py
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
def multinomial_kl(
    log_prob1: Float[torch.Tensor, "batch vocab tokens"],
    log_prob2: Float[torch.Tensor, "batch vocab tokens"],
) -> Float[torch.Tensor, "batch tokens"]:
    """Categorical KL divergence summed over the vocabulary dimension.

    Args:
        log_prob1: Log probabilities of the reference distribution.
        log_prob2: Log probabilities of the compared distribution.

    Returns:
        Per-token KL divergence with the vocabulary dimension reduced.

    Examples:
        >>> import torch
        >>> a = torch.log(torch.tensor([[[1.0], [0.0]]]).clamp_min(1e-30))
        >>> float(multinomial_kl(a, a).sum())
        0.0
    """
    return (log_prob1.exp() * (log_prob1 - log_prob2)).sum(dim=1)

sample_time_importance

sample_time_importance(
    batch_size: int,
    *,
    num_timesteps: int,
    lt_history: Float[Tensor, "timesteps"],
    lt_count: Float[Tensor, "timesteps"],
    generator: Generator | None = None,
) -> tuple[
    Int[torch.Tensor, "batch"], Float[torch.Tensor, "batch"]
]

Sample diffusion timesteps with loss-aware importance sampling.

Until every timestep bucket has more than ten observations the sampler falls back to a uniform draw. Afterwards timesteps are drawn proportionally to the square root of the running squared-loss history.

Parameters:

Name Type Description Default
batch_size int

Number of timesteps to draw.

required
num_timesteps int

Total diffusion timesteps.

required
lt_history Float[Tensor, 'timesteps']

Running squared-loss history buffer.

required
lt_count Float[Tensor, 'timesteps']

Per-timestep observation-count buffer.

required
generator Generator | None

Optional random generator for deterministic draws.

None

Returns:

Type Description
tuple[Int[Tensor, 'batch'], Float[Tensor, 'batch']]

Sampled timesteps and their sampling probabilities.

Examples:

>>> import torch
>>> hist = torch.arange(1, 5, dtype=torch.float32)
>>> count = torch.full((4,), 11.0)
>>> gen = torch.Generator().manual_seed(0)
>>> t, pt = sample_time_importance(
...     2, num_timesteps=4, lt_history=hist, lt_count=count, generator=gen
... )
>>> t.shape, pt.shape
(torch.Size([2]), torch.Size([2]))
Source code in lib/laygen/src/laygen/common/discrete.py
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
def sample_time_importance(
    batch_size: int,
    *,
    num_timesteps: int,
    lt_history: Float[torch.Tensor, "timesteps"],
    lt_count: Float[torch.Tensor, "timesteps"],
    generator: torch.Generator | None = None,
) -> tuple[Int[torch.Tensor, "batch"], Float[torch.Tensor, "batch"]]:
    """Sample diffusion timesteps with loss-aware importance sampling.

    Until every timestep bucket has more than ten observations the sampler falls
    back to a uniform draw. Afterwards timesteps are drawn proportionally to the
    square root of the running squared-loss history.

    Args:
        batch_size: Number of timesteps to draw.
        num_timesteps: Total diffusion timesteps.
        lt_history: Running squared-loss history buffer.
        lt_count: Per-timestep observation-count buffer.
        generator: Optional random generator for deterministic draws.

    Returns:
        Sampled timesteps and their sampling probabilities.

    Examples:
        >>> import torch
        >>> hist = torch.arange(1, 5, dtype=torch.float32)
        >>> count = torch.full((4,), 11.0)
        >>> gen = torch.Generator().manual_seed(0)
        >>> t, pt = sample_time_importance(
        ...     2, num_timesteps=4, lt_history=hist, lt_count=count, generator=gen
        ... )
        >>> t.shape, pt.shape
        (torch.Size([2]), torch.Size([2]))
    """
    import torch

    device = lt_history.device
    if not bool((lt_count > 10).all()):
        return sample_time_uniform(
            batch_size,
            num_timesteps=num_timesteps,
            device=device,
            generator=generator,
        )
    lt_sqrt = torch.sqrt(lt_history + 1e-10) + 0.0001
    lt_sqrt[0] = lt_sqrt[1]
    pt_all = lt_sqrt / lt_sqrt.sum()
    t = torch.multinomial(
        pt_all, num_samples=batch_size, replacement=True, generator=generator
    )
    pt = pt_all.gather(dim=0, index=t)
    return t, pt

sample_time_uniform

sample_time_uniform(
    batch_size: int,
    *,
    num_timesteps: int,
    device: device,
    generator: Generator | None = None,
) -> tuple[
    Int[torch.Tensor, "batch"], Float[torch.Tensor, "batch"]
]

Sample diffusion timesteps uniformly.

Parameters:

Name Type Description Default
batch_size int

Number of timesteps to draw.

required
num_timesteps int

Total diffusion timesteps.

required
device device

Device for the sampled tensors.

required
generator Generator | None

Optional random generator for deterministic draws.

None

Returns:

Type Description
tuple[Int[Tensor, 'batch'], Float[Tensor, 'batch']]

Sampled timesteps and their uniform sampling probabilities.

Examples:

>>> import torch
>>> gen = torch.Generator().manual_seed(0)
>>> t, pt = sample_time_uniform(
...     2, num_timesteps=4, device=torch.device("cpu"), generator=gen
... )
>>> t.shape, pt.tolist()
(torch.Size([2]), [0.25, 0.25])
Source code in lib/laygen/src/laygen/common/discrete.py
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
def sample_time_uniform(
    batch_size: int,
    *,
    num_timesteps: int,
    device: torch.device,
    generator: torch.Generator | None = None,
) -> tuple[Int[torch.Tensor, "batch"], Float[torch.Tensor, "batch"]]:
    """Sample diffusion timesteps uniformly.

    Args:
        batch_size: Number of timesteps to draw.
        num_timesteps: Total diffusion timesteps.
        device: Device for the sampled tensors.
        generator: Optional random generator for deterministic draws.

    Returns:
        Sampled timesteps and their uniform sampling probabilities.

    Examples:
        >>> import torch
        >>> gen = torch.Generator().manual_seed(0)
        >>> t, pt = sample_time_uniform(
        ...     2, num_timesteps=4, device=torch.device("cpu"), generator=gen
        ... )
        >>> t.shape, pt.tolist()
        (torch.Size([2]), [0.25, 0.25])
    """
    import torch

    t = torch.randint(
        0, num_timesteps, (batch_size,), device=device, generator=generator
    ).long()
    pt = torch.ones_like(t).float() / num_timesteps
    return t, pt

mean_except_batch

mean_except_batch(
    x: Float[Tensor, "batch ..."],
) -> Float[torch.Tensor, "batch"]

Average every non-batch dimension.

Parameters:

Name Type Description Default
x Float[Tensor, 'batch ...']

Tensor whose leading dimension is the batch.

required

Returns:

Type Description
Float[Tensor, 'batch']

Per-example mean over all trailing dimensions.

Examples:

>>> mean_except_batch(torch.ones(2, 3)).tolist()
[1.0, 1.0]
Source code in models/layout-dm/src/layout_dm/training/losses.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def mean_except_batch(
    x: Float[torch.Tensor, "batch ..."],
) -> Float[torch.Tensor, "batch"]:
    """Average every non-batch dimension.

    Args:
        x: Tensor whose leading dimension is the batch.

    Returns:
        Per-example mean over all trailing dimensions.

    Examples:
        >>> mean_except_batch(torch.ones(2, 3)).tolist()
        [1.0, 1.0]
    """
    return x.reshape(x.shape[0], -1).mean(dim=-1)

parity

LayoutDM-specific S0-S2 training-parity helpers.

trace_layout_dm_step

trace_layout_dm_step(
    module: TrainingStepModule,
    batch: dict[str, Shaped[Tensor, "..."]],
    rng_state: RNGState | None = None,
) -> StepTrace

Trace one LayoutDM training step with the canonical trace points.

Source code in models/layout-dm/src/layout_dm/training/parity.py
31
32
33
34
35
36
37
def trace_layout_dm_step(
    module: TrainingStepModule,
    batch: dict[str, Shaped[torch.Tensor, "..."]],
    rng_state: RNGState | None = None,
) -> StepTrace:
    """Trace one LayoutDM training step with the canonical trace points."""
    return trace_training_step(module, batch, rng_state, TRACE_POINTS)

compare_layout_dm_step

compare_layout_dm_step(
    reference: StepTrace,
    target: StepTrace,
    *,
    tolerance: TensorTolerance | None = None,
) -> StepReport

Compare S1 LayoutDM pre-optimizer traces.

Source code in models/layout-dm/src/layout_dm/training/parity.py
40
41
42
43
44
45
46
47
48
49
def compare_layout_dm_step(
    reference: StepTrace,
    target: StepTrace,
    *,
    tolerance: TensorTolerance | None = None,
) -> StepReport:
    """Compare S1 LayoutDM pre-optimizer traces."""
    names = set(reference.tensors) & set(target.tensors)
    tolerances = {name: tolerance or TensorTolerance() for name in names}
    return compare_step_trace(reference, target, tolerances)

compare_layout_dm_optimizer_step

compare_layout_dm_optimizer_step(
    reference_state: dict[str, Shaped[Tensor, "..."]],
    target_state: dict[str, Shaped[Tensor, "..."]],
    *,
    tolerance: TensorTolerance | None = None,
) -> OptimizerStepReport

Compare S0/S2 LayoutDM parameter state dictionaries.

Source code in models/layout-dm/src/layout_dm/training/parity.py
52
53
54
55
56
57
58
59
60
61
def compare_layout_dm_optimizer_step(
    reference_state: dict[str, Shaped[torch.Tensor, "..."]],
    target_state: dict[str, Shaped[torch.Tensor, "..."]],
    *,
    tolerance: TensorTolerance | None = None,
) -> OptimizerStepReport:
    """Compare S0/S2 LayoutDM parameter state dictionaries."""
    names = set(reference_state) & set(target_state)
    tolerances = {name: tolerance or TensorTolerance() for name in names}
    return compare_optimizer_step(reference_state, target_state, tolerances)

seed

Seed policy helpers for LayoutDM training.

apply_layout_dm_seed_mode

apply_layout_dm_seed_mode(
    seed_mode: LayoutDMSeedMode | str, *, seed: int = 42975
) -> None

Apply the selected LayoutDM seed mode.

Parameters:

Name Type Description Default
seed_mode LayoutDMSeedMode | str

Regular or deterministic seed mode.

required
seed int

Seed used by both modes.

42975

Returns:

Type Description
None

None.

Raises:

Type Description
ValueError

If the seed mode is unsupported.

Examples:

>>> apply_layout_dm_seed_mode("default", seed=1)
Source code in models/layout-dm/src/layout_dm/training/seed.py
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
def apply_layout_dm_seed_mode(
    seed_mode: LayoutDMSeedMode | str,
    *,
    seed: int = 42975,
) -> None:
    """Apply the selected LayoutDM seed mode.

    Args:
        seed_mode: Regular or deterministic seed mode.
        seed: Seed used by both modes.

    Returns:
        None.

    Raises:
        ValueError: If the seed mode is unsupported.

    Examples:
        >>> apply_layout_dm_seed_mode("default", seed=1)
    """
    mode = LayoutDMSeedMode(seed_mode)
    if mode is LayoutDMSeedMode.default:
        torch.manual_seed(seed)
        if torch.cuda.is_available():
            torch.cuda.manual_seed_all(seed)
        torch.set_float32_matmul_precision("medium")
    elif mode is LayoutDMSeedMode.deterministic:
        apply_determinism(DeterminismConfig(seed=seed))