Skip to content

Layoutdiffusion

LayoutDiffusion diffusers pipeline package.

LayoutDiffusionConfig

Bases: ConfigMixin

Serializable LayoutDiffusion model, tokenizer, and scheduler settings.

Parameters:

Name Type Description Default
dataset_name DatasetName | str

Dataset name or alias.

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

Optional persisted dataset-local label mapping.

None
vocab dict[str, int] | None

Optional token-to-id vocabulary loaded from vocab.json.

None
seq_length int

Full internal token sequence length.

121
max_num_elements int

Maximum number of layout elements.

20
num_coordinate_bins int

Number of coordinate tokens.

128
diffusion_steps int

Number of training diffusion timesteps.

200
noise_schedule str

Reference diffusion schedule name.

'gaussian_refine_pow2.5'
num_channels int

OpenAI timestep embedding dimension.

128
bert_config_name str

Name of the BERT config used by the checkpoint.

'bert-base-uncased'
max_position_embeddings int

BERT position embedding count.

512
hidden_size int

Transformer hidden size.

768
num_hidden_layers int

BERT encoder layer count.

12
num_attention_heads int

Attention head count.

12
intermediate_size int

Feed-forward hidden size.

3072
dropout float

Dropout probability.

0.1
training_mode str

Reference training mode.

'discrete'
vocab_size int | None

Full vocabulary size including mask.

None
refine_start_step int | None

Dataset-specific refinement start step.

None
type_start_step int

Reference type-conditioned start step.

160
element_count_prior list[float] | None

Optional 20-entry unconditional element count prior.

None
pow_num float

Gaussian transition exponent.

2.5
mul_num float

Gaussian transition multiplier.

12.4

Examples:

>>> cfg = LayoutDiffusionConfig(dataset_name="publaynet")
>>> cfg.mask_token_id == cfg.vocab_size - 1
True
Source code in models/layoutdiffusion/src/layoutdiffusion/configuration_layoutdiffusion.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
 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
class LayoutDiffusionConfig(ConfigMixin):
    """Serializable LayoutDiffusion model, tokenizer, and scheduler settings.

    Args:
        dataset_name: Dataset name or alias.
        id2label: Optional persisted dataset-local label mapping.
        vocab: Optional token-to-id vocabulary loaded from ``vocab.json``.
        seq_length: Full internal token sequence length.
        max_num_elements: Maximum number of layout elements.
        num_coordinate_bins: Number of coordinate tokens.
        diffusion_steps: Number of training diffusion timesteps.
        noise_schedule: Reference diffusion schedule name.
        num_channels: OpenAI timestep embedding dimension.
        bert_config_name: Name of the BERT config used by the checkpoint.
        max_position_embeddings: BERT position embedding count.
        hidden_size: Transformer hidden size.
        num_hidden_layers: BERT encoder layer count.
        num_attention_heads: Attention head count.
        intermediate_size: Feed-forward hidden size.
        dropout: Dropout probability.
        training_mode: Reference training mode.
        vocab_size: Full vocabulary size including mask.
        refine_start_step: Dataset-specific refinement start step.
        type_start_step: Reference type-conditioned start step.
        element_count_prior: Optional 20-entry unconditional element count prior.
        pow_num: Gaussian transition exponent.
        mul_num: Gaussian transition multiplier.

    Examples:
        >>> cfg = LayoutDiffusionConfig(dataset_name="publaynet")
        >>> cfg.mask_token_id == cfg.vocab_size - 1
        True
    """

    config_name = "layoutdiffusion_config.json"

    @register_to_config
    def __init__(
        self,
        *,
        dataset_name: DatasetName | str = DatasetName.rico25,
        id2label: dict[int | str, str] | None = None,
        vocab: dict[str, int] | None = None,
        seq_length: int = 121,
        max_num_elements: int = 20,
        num_coordinate_bins: int = 128,
        diffusion_steps: int = 200,
        noise_schedule: str = "gaussian_refine_pow2.5",
        num_channels: int = 128,
        bert_config_name: str = "bert-base-uncased",
        max_position_embeddings: int = 512,
        hidden_size: int = 768,
        num_hidden_layers: int = 12,
        num_attention_heads: int = 12,
        intermediate_size: int = 3072,
        dropout: float = 0.1,
        training_mode: str = "discrete",
        vocab_size: int | None = None,
        refine_start_step: int | None = None,
        type_start_step: int = 160,
        element_count_prior: list[float] | None = None,
        pow_num: float = 2.5,
        mul_num: float = 12.4,
    ) -> None:
        """Initialize LayoutDiffusion configuration."""
        self.dataset_name = str(normalize_dataset_name(dataset_name))
        raw_id2label = id2label or default_id2label(self.dataset_name)
        self.id2label = {int(k): v for k, v in raw_id2label.items()}

        self.vocab = vocab or self.default_vocab()
        self.seq_length = seq_length
        self.max_num_elements = max_num_elements
        self.num_coordinate_bins = num_coordinate_bins

        self.diffusion_steps = diffusion_steps
        self.noise_schedule = noise_schedule
        self.num_channels = num_channels
        self.bert_config_name = bert_config_name

        self.max_position_embeddings = max_position_embeddings
        self.hidden_size = hidden_size
        self.num_hidden_layers = num_hidden_layers
        self.num_attention_heads = num_attention_heads
        self.intermediate_size = intermediate_size
        self.dropout = dropout

        self.training_mode = training_mode
        self.vocab_size = vocab_size or len(self.vocab)
        self.refine_start_step = refine_start_step or (
            60 if self.dataset_name == str(DatasetName.publaynet) else 50
        )
        self.type_start_step = type_start_step
        self.element_count_prior = element_count_prior or self.default_element_prior()
        self.pow_num = pow_num
        self.mul_num = mul_num

    @property
    def special_token_ids(self) -> dict[str, int]:
        """Return LayoutDiffusion special-token ids."""
        return {
            "START": self.vocab["START"],
            "END": self.vocab["END"],
            "UNK": self.vocab["UNK"],
            "PAD": self.vocab["PAD"],
            "|": self.vocab["|"],
        }

    @property
    def pad_token_id(self) -> int:
        """Return the padding token id."""
        return self.vocab["PAD"]

    @property
    def mask_token_id(self) -> int:
        """Return the mask token id."""
        return self.vocab_size - 1

    @property
    def label_token_offset(self) -> int:
        """Return the first label-token id."""
        return 5

    @property
    def num_labels(self) -> int:
        """Return the dataset label count."""
        return len(self.id2label)

    @property
    def coordinate_token_offset(self) -> int:
        """Return the first coordinate-token id."""
        return self.label_token_offset + self.num_labels

    @property
    def max_token_length(self) -> int:
        """Return the full LayoutDiffusion token sequence length."""
        return self.seq_length

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

    @property
    def type_classes(self) -> int:
        """Return the number of reference type classes."""
        return self.vocab_size - 1 - self.num_coordinate_bins - 5

    def default_vocab(self) -> dict[str, int]:
        """Build the default LayoutDiffusion vocabulary for the dataset."""
        vocab = {"START": 0, "END": 1, "UNK": 2, "PAD": 3, "|": 4}
        for label in default_id2label(self.dataset_name).values():
            vocab[label] = len(vocab)
        for coord in range(self.num_coordinate_bins):
            vocab[str(coord)] = len(vocab)
        vocab["MASK"] = len(vocab)
        return vocab

    def default_element_prior(self) -> list[float]:
        """Return the reference unconditional element-count prior."""
        if self.dataset_name == str(DatasetName.publaynet):
            return [
                0.00321776,
                0.03342678,
                0.04233181,
                0.04218409,
                0.05404355,
                0.07231605,
                0.08247029,
                0.0905211,
                0.0949399,
                0.0959322,
                0.08953522,
                0.07810608,
                0.0619627,
                0.04775897,
                0.03585776,
                0.0261788,
                0.018812,
                0.01404317,
                0.00972071,
                0.00664104,
            ]
        return [
            0.04849498,
            0.03704171,
            0.0534486,
            0.06045308,
            0.06354515,
            0.07585032,
            0.08045687,
            0.0644917,
            0.05676153,
            0.05742412,
            0.05471067,
            0.04944153,
            0.04552912,
            0.04190068,
            0.04426705,
            0.0387455,
            0.03533792,
            0.03167792,
            0.02997413,
            0.0304474,
        ]

special_token_ids property

special_token_ids: dict[str, int]

Return LayoutDiffusion special-token ids.

pad_token_id property

pad_token_id: int

Return the padding token id.

mask_token_id property

mask_token_id: int

Return the mask token id.

label_token_offset property

label_token_offset: int

Return the first label-token id.

num_labels property

num_labels: int

Return the dataset label count.

coordinate_token_offset property

coordinate_token_offset: int

Return the first coordinate-token id.

max_token_length property

max_token_length: int

Return the full LayoutDiffusion token sequence length.

label2id property

label2id: dict[str, int]

Return inverse public label mapping.

type_classes property

type_classes: int

Return the number of reference type classes.

__init__

__init__(
    *,
    dataset_name: DatasetName | str = DatasetName.rico25,
    id2label: dict[int | str, str] | None = None,
    vocab: dict[str, int] | None = None,
    seq_length: int = 121,
    max_num_elements: int = 20,
    num_coordinate_bins: int = 128,
    diffusion_steps: int = 200,
    noise_schedule: str = "gaussian_refine_pow2.5",
    num_channels: int = 128,
    bert_config_name: str = "bert-base-uncased",
    max_position_embeddings: int = 512,
    hidden_size: int = 768,
    num_hidden_layers: int = 12,
    num_attention_heads: int = 12,
    intermediate_size: int = 3072,
    dropout: float = 0.1,
    training_mode: str = "discrete",
    vocab_size: int | None = None,
    refine_start_step: int | None = None,
    type_start_step: int = 160,
    element_count_prior: list[float] | None = None,
    pow_num: float = 2.5,
    mul_num: float = 12.4,
) -> None

Initialize LayoutDiffusion configuration.

Source code in models/layoutdiffusion/src/layoutdiffusion/configuration_layoutdiffusion.py
 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
@register_to_config
def __init__(
    self,
    *,
    dataset_name: DatasetName | str = DatasetName.rico25,
    id2label: dict[int | str, str] | None = None,
    vocab: dict[str, int] | None = None,
    seq_length: int = 121,
    max_num_elements: int = 20,
    num_coordinate_bins: int = 128,
    diffusion_steps: int = 200,
    noise_schedule: str = "gaussian_refine_pow2.5",
    num_channels: int = 128,
    bert_config_name: str = "bert-base-uncased",
    max_position_embeddings: int = 512,
    hidden_size: int = 768,
    num_hidden_layers: int = 12,
    num_attention_heads: int = 12,
    intermediate_size: int = 3072,
    dropout: float = 0.1,
    training_mode: str = "discrete",
    vocab_size: int | None = None,
    refine_start_step: int | None = None,
    type_start_step: int = 160,
    element_count_prior: list[float] | None = None,
    pow_num: float = 2.5,
    mul_num: float = 12.4,
) -> None:
    """Initialize LayoutDiffusion configuration."""
    self.dataset_name = str(normalize_dataset_name(dataset_name))
    raw_id2label = id2label or default_id2label(self.dataset_name)
    self.id2label = {int(k): v for k, v in raw_id2label.items()}

    self.vocab = vocab or self.default_vocab()
    self.seq_length = seq_length
    self.max_num_elements = max_num_elements
    self.num_coordinate_bins = num_coordinate_bins

    self.diffusion_steps = diffusion_steps
    self.noise_schedule = noise_schedule
    self.num_channels = num_channels
    self.bert_config_name = bert_config_name

    self.max_position_embeddings = max_position_embeddings
    self.hidden_size = hidden_size
    self.num_hidden_layers = num_hidden_layers
    self.num_attention_heads = num_attention_heads
    self.intermediate_size = intermediate_size
    self.dropout = dropout

    self.training_mode = training_mode
    self.vocab_size = vocab_size or len(self.vocab)
    self.refine_start_step = refine_start_step or (
        60 if self.dataset_name == str(DatasetName.publaynet) else 50
    )
    self.type_start_step = type_start_step
    self.element_count_prior = element_count_prior or self.default_element_prior()
    self.pow_num = pow_num
    self.mul_num = mul_num

default_vocab

default_vocab() -> dict[str, int]

Build the default LayoutDiffusion vocabulary for the dataset.

Source code in models/layoutdiffusion/src/layoutdiffusion/configuration_layoutdiffusion.py
159
160
161
162
163
164
165
166
167
def default_vocab(self) -> dict[str, int]:
    """Build the default LayoutDiffusion vocabulary for the dataset."""
    vocab = {"START": 0, "END": 1, "UNK": 2, "PAD": 3, "|": 4}
    for label in default_id2label(self.dataset_name).values():
        vocab[label] = len(vocab)
    for coord in range(self.num_coordinate_bins):
        vocab[str(coord)] = len(vocab)
    vocab["MASK"] = len(vocab)
    return vocab

default_element_prior

default_element_prior() -> list[float]

Return the reference unconditional element-count prior.

Source code in models/layoutdiffusion/src/layoutdiffusion/configuration_layoutdiffusion.py
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
def default_element_prior(self) -> list[float]:
    """Return the reference unconditional element-count prior."""
    if self.dataset_name == str(DatasetName.publaynet):
        return [
            0.00321776,
            0.03342678,
            0.04233181,
            0.04218409,
            0.05404355,
            0.07231605,
            0.08247029,
            0.0905211,
            0.0949399,
            0.0959322,
            0.08953522,
            0.07810608,
            0.0619627,
            0.04775897,
            0.03585776,
            0.0261788,
            0.018812,
            0.01404317,
            0.00972071,
            0.00664104,
        ]
    return [
        0.04849498,
        0.03704171,
        0.0534486,
        0.06045308,
        0.06354515,
        0.07585032,
        0.08045687,
        0.0644917,
        0.05676153,
        0.05742412,
        0.05471067,
        0.04944153,
        0.04552912,
        0.04190068,
        0.04426705,
        0.0387455,
        0.03533792,
        0.03167792,
        0.02997413,
        0.0304474,
    ]

LayoutDiffusionTransformer

Bases: ModelMixin, ConfigMixin

BERT-encoder denoiser for LayoutDiffusion token sequences.

Parameters:

Name Type Description Default
vocab_size int

Full tokenizer vocabulary size including mask.

required
num_channels int

OpenAI timestep embedding dimension.

128
hidden_size int

BERT hidden size.

768
num_hidden_layers int

Number of BERT encoder layers.

12
num_attention_heads int

Number of attention heads.

12
intermediate_size int

BERT feed-forward size.

3072
dropout float

Hidden dropout probability.

0.1
max_position_embeddings int

Position embedding count.

512
constrained str | None

Optional reference constraint mode.

None

Examples:

>>> model = LayoutDiffusionTransformer(
...     vocab_size=16, hidden_size=32, num_channels=8,
...     num_hidden_layers=1, num_attention_heads=4, intermediate_size=64,
... )
>>> out = model(torch.zeros(2, 5, dtype=torch.long), torch.zeros(2, dtype=torch.long))
>>> out.logits.shape
torch.Size([2, 15, 5])
Source code in models/layoutdiffusion/src/layoutdiffusion/modeling_layoutdiffusion.py
 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
167
168
169
170
171
172
173
174
175
class LayoutDiffusionTransformer(ModelMixin, ConfigMixin):
    """BERT-encoder denoiser for LayoutDiffusion token sequences.

    Args:
        vocab_size: Full tokenizer vocabulary size including mask.
        num_channels: OpenAI timestep embedding dimension.
        hidden_size: BERT hidden size.
        num_hidden_layers: Number of BERT encoder layers.
        num_attention_heads: Number of attention heads.
        intermediate_size: BERT feed-forward size.
        dropout: Hidden dropout probability.
        max_position_embeddings: Position embedding count.
        constrained: Optional reference constraint mode.

    Examples:
        >>> model = LayoutDiffusionTransformer(
        ...     vocab_size=16, hidden_size=32, num_channels=8,
        ...     num_hidden_layers=1, num_attention_heads=4, intermediate_size=64,
        ... )
        >>> out = model(torch.zeros(2, 5, dtype=torch.long), torch.zeros(2, dtype=torch.long))
        >>> out.logits.shape
        torch.Size([2, 15, 5])
    """

    config_name = "transformer_config.json"

    position_ids: Int[torch.Tensor, "1 max_positions"]

    @register_to_config
    def __init__(
        self,
        *,
        vocab_size: int,
        num_channels: int = 128,
        hidden_size: int = 768,
        num_hidden_layers: int = 12,
        num_attention_heads: int = 12,
        intermediate_size: int = 3072,
        dropout: float = 0.1,
        max_position_embeddings: int = 512,
        constrained: str | None = None,
    ) -> None:
        """Initialize the transformer."""
        super().__init__()
        config = BertConfig(
            hidden_size=hidden_size,
            num_hidden_layers=num_hidden_layers,
            num_attention_heads=num_attention_heads,
            intermediate_size=intermediate_size,
            hidden_dropout_prob=dropout,
            attention_probs_dropout_prob=dropout,
            max_position_embeddings=max_position_embeddings,
        )
        self.constrained = constrained
        self.in_channels = 768
        self.model_channels = num_channels
        self.out_channels = vocab_size - 1
        self.word_embedding = nn.Embedding(vocab_size, self.in_channels)
        time_embed_dim = num_channels * 4
        self.time_embed = nn.Sequential(
            nn.Linear(num_channels, time_embed_dim),
            nn.SiLU(),
            nn.Linear(time_embed_dim, hidden_size),
        )
        self.input_up_proj = nn.Sequential(
            nn.Linear(self.in_channels, hidden_size),
            nn.Tanh(),
            nn.Linear(hidden_size, hidden_size),
        )
        self.input_transformers = BertEncoder(config)
        self.register_buffer(
            "position_ids",
            torch.arange(max_position_embeddings).expand((1, -1)),
            persistent=False,
        )
        self.position_embeddings = nn.Embedding(max_position_embeddings, hidden_size)
        self.LayerNorm = nn.LayerNorm(hidden_size, eps=config.layer_norm_eps)
        self.dropout_layer = nn.Dropout(dropout)
        self.output_down_proj = nn.Sequential(
            nn.Linear(hidden_size, hidden_size),
            nn.Tanh(),
            nn.Linear(hidden_size, self.out_channels),
        )

    def get_embeds(
        self, input_ids: Int[torch.Tensor, "batch tokens"]
    ) -> Float[torch.Tensor, "batch tokens channels"]:
        """Return token embeddings for parity diagnostics."""
        return self.word_embedding(input_ids)

    def forward(
        self,
        input_ids: Int[torch.Tensor, "batch tokens"],
        timesteps: Int[torch.Tensor, "batch"],
        condition_ids: Int[torch.Tensor, "batch tokens"] | None = None,
        condition_type: str | None = None,
        return_dict: bool = True,
    ) -> (
        LayoutDiffusionTransformerOutput
        | tuple[Float[torch.Tensor, "batch vocab tokens"]]
    ):
        """Predict start-token logits for a reverse diffusion step.

        Args:
            input_ids: Current token ids shaped ``(B, L)``.
            timesteps: Diffusion timestep per batch item.
            condition_ids: Optional internal condition token ids.
            condition_type: Optional condition mode.
            return_dict: Whether to return a dataclass output.

        Returns:
            Logits shaped ``(B, vocab_size - 1, L)``.
        """
        x = input_ids
        if condition_ids is not None and condition_type == "label":
            mask = condition_ids.le(self.out_channels - 129).unsqueeze(-1)
            hidden = self.word_embedding(condition_ids) * mask + self.word_embedding(
                x
            ) * (~mask)
        elif condition_ids is not None and condition_type == "completion":
            keep = torch.tensor(
                [1] * 6 + [0] * (condition_ids.shape[1] - 6),
                device=x.device,
                dtype=torch.bool,
            ).expand(condition_ids.shape[0], -1)
            hidden = self.word_embedding(condition_ids) * keep.unsqueeze(
                -1
            ) + self.word_embedding(x) * (~keep).unsqueeze(-1)
        else:
            hidden = self.word_embedding(x)
        emb = self.time_embed(
            get_timestep_embedding(
                timesteps,
                self.model_channels,
                flip_sin_to_cos=True,
                downscale_freq_shift=0,
            ).to(hidden)
        )
        seq_length = hidden.size(1)
        position_ids = self.position_ids[:, :seq_length]
        inputs = self.input_up_proj(hidden)
        inputs = inputs + self.position_embeddings(position_ids) + emb.unsqueeze(1)
        inputs = self.dropout_layer(self.LayerNorm(inputs))
        encoded = self.input_transformers(inputs).last_hidden_state
        logits = rearrange(self.output_down_proj(encoded), "b l c -> b c l").type(
            hidden.dtype
        )
        if not return_dict:
            return (logits,)
        return LayoutDiffusionTransformerOutput(logits=logits)

__init__

__init__(
    *,
    vocab_size: int,
    num_channels: int = 128,
    hidden_size: int = 768,
    num_hidden_layers: int = 12,
    num_attention_heads: int = 12,
    intermediate_size: int = 3072,
    dropout: float = 0.1,
    max_position_embeddings: int = 512,
    constrained: str | None = None,
) -> None

Initialize the transformer.

Source code in models/layoutdiffusion/src/layoutdiffusion/modeling_layoutdiffusion.py
 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
@register_to_config
def __init__(
    self,
    *,
    vocab_size: int,
    num_channels: int = 128,
    hidden_size: int = 768,
    num_hidden_layers: int = 12,
    num_attention_heads: int = 12,
    intermediate_size: int = 3072,
    dropout: float = 0.1,
    max_position_embeddings: int = 512,
    constrained: str | None = None,
) -> None:
    """Initialize the transformer."""
    super().__init__()
    config = BertConfig(
        hidden_size=hidden_size,
        num_hidden_layers=num_hidden_layers,
        num_attention_heads=num_attention_heads,
        intermediate_size=intermediate_size,
        hidden_dropout_prob=dropout,
        attention_probs_dropout_prob=dropout,
        max_position_embeddings=max_position_embeddings,
    )
    self.constrained = constrained
    self.in_channels = 768
    self.model_channels = num_channels
    self.out_channels = vocab_size - 1
    self.word_embedding = nn.Embedding(vocab_size, self.in_channels)
    time_embed_dim = num_channels * 4
    self.time_embed = nn.Sequential(
        nn.Linear(num_channels, time_embed_dim),
        nn.SiLU(),
        nn.Linear(time_embed_dim, hidden_size),
    )
    self.input_up_proj = nn.Sequential(
        nn.Linear(self.in_channels, hidden_size),
        nn.Tanh(),
        nn.Linear(hidden_size, hidden_size),
    )
    self.input_transformers = BertEncoder(config)
    self.register_buffer(
        "position_ids",
        torch.arange(max_position_embeddings).expand((1, -1)),
        persistent=False,
    )
    self.position_embeddings = nn.Embedding(max_position_embeddings, hidden_size)
    self.LayerNorm = nn.LayerNorm(hidden_size, eps=config.layer_norm_eps)
    self.dropout_layer = nn.Dropout(dropout)
    self.output_down_proj = nn.Sequential(
        nn.Linear(hidden_size, hidden_size),
        nn.Tanh(),
        nn.Linear(hidden_size, self.out_channels),
    )

get_embeds

get_embeds(
    input_ids: Int[Tensor, "batch tokens"],
) -> Float[torch.Tensor, "batch tokens channels"]

Return token embeddings for parity diagnostics.

Source code in models/layoutdiffusion/src/layoutdiffusion/modeling_layoutdiffusion.py
110
111
112
113
114
def get_embeds(
    self, input_ids: Int[torch.Tensor, "batch tokens"]
) -> Float[torch.Tensor, "batch tokens channels"]:
    """Return token embeddings for parity diagnostics."""
    return self.word_embedding(input_ids)

forward

forward(
    input_ids: Int[Tensor, "batch tokens"],
    timesteps: Int[Tensor, "batch"],
    condition_ids: Int[Tensor, "batch tokens"]
    | None = None,
    condition_type: str | None = None,
    return_dict: bool = True,
) -> (
    LayoutDiffusionTransformerOutput
    | tuple[Float[torch.Tensor, "batch vocab tokens"]]
)

Predict start-token logits for a reverse diffusion step.

Parameters:

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

Current token ids shaped (B, L).

required
timesteps Int[Tensor, 'batch']

Diffusion timestep per batch item.

required
condition_ids Int[Tensor, 'batch tokens'] | None

Optional internal condition token ids.

None
condition_type str | None

Optional condition mode.

None
return_dict bool

Whether to return a dataclass output.

True

Returns:

Type Description
LayoutDiffusionTransformerOutput | tuple[Float[Tensor, 'batch vocab tokens']]

Logits shaped (B, vocab_size - 1, L).

Source code in models/layoutdiffusion/src/layoutdiffusion/modeling_layoutdiffusion.py
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
def forward(
    self,
    input_ids: Int[torch.Tensor, "batch tokens"],
    timesteps: Int[torch.Tensor, "batch"],
    condition_ids: Int[torch.Tensor, "batch tokens"] | None = None,
    condition_type: str | None = None,
    return_dict: bool = True,
) -> (
    LayoutDiffusionTransformerOutput
    | tuple[Float[torch.Tensor, "batch vocab tokens"]]
):
    """Predict start-token logits for a reverse diffusion step.

    Args:
        input_ids: Current token ids shaped ``(B, L)``.
        timesteps: Diffusion timestep per batch item.
        condition_ids: Optional internal condition token ids.
        condition_type: Optional condition mode.
        return_dict: Whether to return a dataclass output.

    Returns:
        Logits shaped ``(B, vocab_size - 1, L)``.
    """
    x = input_ids
    if condition_ids is not None and condition_type == "label":
        mask = condition_ids.le(self.out_channels - 129).unsqueeze(-1)
        hidden = self.word_embedding(condition_ids) * mask + self.word_embedding(
            x
        ) * (~mask)
    elif condition_ids is not None and condition_type == "completion":
        keep = torch.tensor(
            [1] * 6 + [0] * (condition_ids.shape[1] - 6),
            device=x.device,
            dtype=torch.bool,
        ).expand(condition_ids.shape[0], -1)
        hidden = self.word_embedding(condition_ids) * keep.unsqueeze(
            -1
        ) + self.word_embedding(x) * (~keep).unsqueeze(-1)
    else:
        hidden = self.word_embedding(x)
    emb = self.time_embed(
        get_timestep_embedding(
            timesteps,
            self.model_channels,
            flip_sin_to_cos=True,
            downscale_freq_shift=0,
        ).to(hidden)
    )
    seq_length = hidden.size(1)
    position_ids = self.position_ids[:, :seq_length]
    inputs = self.input_up_proj(hidden)
    inputs = inputs + self.position_embeddings(position_ids) + emb.unsqueeze(1)
    inputs = self.dropout_layer(self.LayerNorm(inputs))
    encoded = self.input_transformers(inputs).last_hidden_state
    logits = rearrange(self.output_down_proj(encoded), "b l c -> b c l").type(
        hidden.dtype
    )
    if not return_dict:
        return (logits,)
    return LayoutDiffusionTransformerOutput(logits=logits)

LayoutDiffusionPipeline

Bases: DiffusionPipeline

Generate layouts with a converted LayoutDiffusion pipeline.

Parameters:

Name Type Description Default
transformer LayoutDiffusionTransformer

LayoutDiffusion transformer denoiser.

required
scheduler LayoutDiffusionScheduler

Categorical diffusion scheduler.

required
tokenizer LayoutDiffusionTokenizer

LayoutDiffusion layout tokenizer.

required
processor LayoutDiffusionProcessor | None

Optional processor.

None

Examples:

>>> from layoutdiffusion import LayoutDiffusionConfig, LayoutDiffusionTokenizer
>>> from layoutdiffusion import LayoutDiffusionScheduler, LayoutDiffusionTransformer
>>> from layoutdiffusion.sampling import LayoutDiffusionSamplingConfig
>>> cfg = LayoutDiffusionConfig(dataset_name="publaynet", hidden_size=32, num_hidden_layers=1, num_attention_heads=4, intermediate_size=64, num_channels=8)
>>> tok = LayoutDiffusionTokenizer(cfg)
>>> pipe = LayoutDiffusionPipeline(
...     LayoutDiffusionTransformer(vocab_size=cfg.vocab_size, hidden_size=32, num_hidden_layers=1, num_attention_heads=4, intermediate_size=64, num_channels=8),
...     LayoutDiffusionScheduler(vocab_size=cfg.vocab_size, mask_token_id=cfg.mask_token_id, type_classes=cfg.type_classes, num_train_timesteps=2),
...     tok,
... )
>>> pipe(batch_size=1, seed=0, sampling=LayoutDiffusionSamplingConfig(num_inference_steps=1)).bbox.shape[-1]
4
Source code in models/layoutdiffusion/src/layoutdiffusion/pipeline_layoutdiffusion.py
 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
class LayoutDiffusionPipeline(DiffusionPipeline):
    """Generate layouts with a converted LayoutDiffusion pipeline.

    Args:
        transformer: LayoutDiffusion transformer denoiser.
        scheduler: Categorical diffusion scheduler.
        tokenizer: LayoutDiffusion layout tokenizer.
        processor: Optional processor.

    Examples:
        >>> from layoutdiffusion import LayoutDiffusionConfig, LayoutDiffusionTokenizer
        >>> from layoutdiffusion import LayoutDiffusionScheduler, LayoutDiffusionTransformer
        >>> from layoutdiffusion.sampling import LayoutDiffusionSamplingConfig
        >>> cfg = LayoutDiffusionConfig(dataset_name="publaynet", hidden_size=32, num_hidden_layers=1, num_attention_heads=4, intermediate_size=64, num_channels=8)
        >>> tok = LayoutDiffusionTokenizer(cfg)
        >>> pipe = LayoutDiffusionPipeline(
        ...     LayoutDiffusionTransformer(vocab_size=cfg.vocab_size, hidden_size=32, num_hidden_layers=1, num_attention_heads=4, intermediate_size=64, num_channels=8),
        ...     LayoutDiffusionScheduler(vocab_size=cfg.vocab_size, mask_token_id=cfg.mask_token_id, type_classes=cfg.type_classes, num_train_timesteps=2),
        ...     tok,
        ... )
        >>> pipe(batch_size=1, seed=0, sampling=LayoutDiffusionSamplingConfig(num_inference_steps=1)).bbox.shape[-1]
        4
    """

    model_cpu_offload_seq = "transformer"

    def __init__(
        self,
        transformer: LayoutDiffusionTransformer,
        scheduler: LayoutDiffusionScheduler,
        tokenizer: LayoutDiffusionTokenizer,
        processor: LayoutDiffusionProcessor | None = None,
    ) -> None:
        """Initialize and register pipeline modules."""
        super().__init__()
        self.register_modules(
            transformer=transformer,
            scheduler=scheduler,
            tokenizer=tokenizer,
        )
        self.tokenizer = tokenizer
        self.processor = processor or LayoutDiffusionProcessor(tokenizer)
        self.transformer.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,
        output_type: Literal["dataclass", "dict"] = "dataclass",
        return_intermediates: bool = False,
        sampling: LayoutDiffusionSamplingConfig,
        **model_kwargs: str | int | float | bool | None,
    ) -> LayoutGenerationOutput | LayoutDiffusionOutputDict:
        """Run LayoutDiffusion generation.

        Args:
            batch_size: Number of layouts for unconditional generation.
            seed: Seed used only when ``generator`` is omitted.
            generator: Optional torch generator. Takes precedence over ``seed``.
            condition_type: Canonical condition type or supported alias.
            labels: Optional conditional labels.
            bbox: Optional conditional boxes.
            mask: Optional conditional valid mask.
            num_elements: Optional element counts.
            box_format: Input box format.
            normalized: Whether conditional boxes are normalized.
            canvas_size: Pixel canvas size for unnormalized inputs.
            num_inference_steps: Optional shortened inference steps.
            output_type: ``"dataclass"`` or ``"dict"``.
            return_intermediates: Whether to include trajectories.
            sampling: Sampling config.
            **model_kwargs: Reserved compatibility kwargs.

        Returns:
            Layout output dataclass or dictionary.

        Raises:
            ValueError: If ``output_type`` is unsupported.
        """
        _ = 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)
        processed = self.processor(
            bbox=bbox,
            labels=labels,
            mask=mask,
            num_elements=num_elements,
            box_format=box_format,
            normalized=normalized,
            canvas_size=canvas_size,
        )
        condition_input = processed.get("input_ids")
        processed_labels = None if labels is None else torch.as_tensor(labels)
        counts = processed.get("num_elements")
        condition = build_condition(
            self.tokenizer,
            condition_type=canonical,
            input_ids=condition_input,
            labels=processed_labels,
            num_elements=counts,
        )
        if condition is not None and condition.input_ids is not None:
            batch_size = condition.input_ids.shape[0]
        if condition is not None and canonical is ConditionType.refinement:
            if condition.input_ids is None:
                raise ValueError("refinement condition is missing input_ids")

            start_ids = condition.input_ids.to(self.device)
        else:
            start_ids = self.tokenizer.build_initial_tokens(
                batch_size=batch_size,
                num_elements=counts,
                labels=processed_labels,
                condition_type=str(canonical),
                generator=generator,
                device=self.device,
            )
        sample = index_to_log_onehot(start_ids, self.scheduler.config.vocab_size)
        start_step = None if condition is None else condition.start_step
        self.scheduler.set_timesteps(
            sampling.num_inference_steps or num_inference_steps,
            start_step=start_step,
            device=self.device,
        )
        trajectory = [] if return_intermediates else None
        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.transformer(
                input_ids=input_ids,
                timesteps=timestep_batch,
                condition_ids=None
                if condition is None or condition.input_ids is None
                else condition.input_ids.to(self.device),
                condition_type=None if condition is None else str(condition.type),
            ).logits
            out = self.scheduler.step(
                logits,
                timestep_batch,
                sample,
                sampling=sampling,
                condition=condition,
                generator=generator,
            )
            sample = out.prev_sample
            if trajectory is not None:
                trajectory.append(log_onehot_to_index(sample).detach().cpu())
        output = self._decode_final_sample(
            sample=sample,
            trajectory=trajectory,
            condition_type=str(canonical),
            return_intermediates=return_intermediates,
        )
        return self._coerce_output(output=output, output_type=output_type)

    def _decode_final_sample(
        self,
        *,
        sample: Float[torch.Tensor, "batch vocab tokens"],
        trajectory: list[Int[torch.Tensor, "batch tokens"]] | None,
        condition_type: str,
        return_intermediates: bool,
    ) -> LayoutGenerationOutput:
        """Decode final token logits into the public layout output schema."""
        sequences = log_onehot_to_index(sample).detach().cpu()
        layout = self.tokenizer.decode_layout(sequences)
        metadata = {"condition_type": condition_type} if return_intermediates else None
        return LayoutGenerationOutput(
            bbox=layout["bbox"],
            labels=layout["labels"],
            mask=layout["mask"],
            id2label=self.tokenizer.config.id2label,
            sequences=sequences if return_intermediates else None,
            trajectory=trajectory,
            intermediates=metadata,
        )

    @staticmethod
    def _coerce_output(
        *,
        output: LayoutGenerationOutput,
        output_type: Literal["dataclass", "dict"],
    ) -> LayoutGenerationOutput | LayoutDiffusionOutputDict:
        """Return the requested output container."""
        if output_type == "dict":
            return cast(LayoutDiffusionOutputDict, 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: LayoutDiffusionPipelineKwarg
    ) -> None:
        """Save a Diffusers pipeline directory."""
        super().save_pretrained(save_directory, **kwargs)

    @classmethod
    def from_pretrained(
        cls,
        pretrained_model_name_or_path: str | Path,
        **kwargs: LayoutDiffusionPipelineKwarg,
    ) -> "LayoutDiffusionPipeline":
        """Load a LayoutDiffusion pipeline and rebuild its processor."""
        tokenizer = kwargs.pop("tokenizer", None)
        if tokenizer is None:
            tokenizer = LayoutDiffusionTokenizer.from_pretrained(
                pretrained_model_name_or_path
            )
        pipe = super().from_pretrained(
            pretrained_model_name_or_path,
            tokenizer=tokenizer,
            **kwargs,
        )
        pipe.processor = LayoutDiffusionProcessor(pipe.tokenizer)
        return pipe

__init__

__init__(
    transformer: LayoutDiffusionTransformer,
    scheduler: LayoutDiffusionScheduler,
    tokenizer: LayoutDiffusionTokenizer,
    processor: LayoutDiffusionProcessor | None = None,
) -> None

Initialize and register pipeline modules.

Source code in models/layoutdiffusion/src/layoutdiffusion/pipeline_layoutdiffusion.py
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
def __init__(
    self,
    transformer: LayoutDiffusionTransformer,
    scheduler: LayoutDiffusionScheduler,
    tokenizer: LayoutDiffusionTokenizer,
    processor: LayoutDiffusionProcessor | None = None,
) -> None:
    """Initialize and register pipeline modules."""
    super().__init__()
    self.register_modules(
        transformer=transformer,
        scheduler=scheduler,
        tokenizer=tokenizer,
    )
    self.tokenizer = tokenizer
    self.processor = processor or LayoutDiffusionProcessor(tokenizer)
    self.transformer.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,
    output_type: Literal["dataclass", "dict"] = "dataclass",
    return_intermediates: bool = False,
    sampling: LayoutDiffusionSamplingConfig,
    **model_kwargs: str | int | float | bool | None,
) -> LayoutGenerationOutput | LayoutDiffusionOutputDict

Run LayoutDiffusion generation.

Parameters:

Name Type Description Default
batch_size int

Number of layouts for unconditional generation.

1
seed int | None

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 alias.

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

Optional conditional labels.

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

Optional conditional boxes.

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

Optional conditional valid mask.

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

Optional element counts.

None
box_format BoxFormat | str

Input box format.

xywh
normalized bool

Whether conditional boxes are normalized.

True
canvas_size tuple[int, int] | None

Pixel canvas size for unnormalized inputs.

None
num_inference_steps int | None

Optional shortened inference steps.

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

"dataclass" or "dict".

'dataclass'
return_intermediates bool

Whether to include trajectories.

False
sampling LayoutDiffusionSamplingConfig

Sampling config.

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

Reserved compatibility kwargs.

{}

Returns:

Type Description
LayoutGenerationOutput | LayoutDiffusionOutputDict

Layout output dataclass or dictionary.

Raises:

Type Description
ValueError

If output_type is unsupported.

Source code in models/layoutdiffusion/src/layoutdiffusion/pipeline_layoutdiffusion.py
 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
@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,
    output_type: Literal["dataclass", "dict"] = "dataclass",
    return_intermediates: bool = False,
    sampling: LayoutDiffusionSamplingConfig,
    **model_kwargs: str | int | float | bool | None,
) -> LayoutGenerationOutput | LayoutDiffusionOutputDict:
    """Run LayoutDiffusion generation.

    Args:
        batch_size: Number of layouts for unconditional generation.
        seed: Seed used only when ``generator`` is omitted.
        generator: Optional torch generator. Takes precedence over ``seed``.
        condition_type: Canonical condition type or supported alias.
        labels: Optional conditional labels.
        bbox: Optional conditional boxes.
        mask: Optional conditional valid mask.
        num_elements: Optional element counts.
        box_format: Input box format.
        normalized: Whether conditional boxes are normalized.
        canvas_size: Pixel canvas size for unnormalized inputs.
        num_inference_steps: Optional shortened inference steps.
        output_type: ``"dataclass"`` or ``"dict"``.
        return_intermediates: Whether to include trajectories.
        sampling: Sampling config.
        **model_kwargs: Reserved compatibility kwargs.

    Returns:
        Layout output dataclass or dictionary.

    Raises:
        ValueError: If ``output_type`` is unsupported.
    """
    _ = 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)
    processed = self.processor(
        bbox=bbox,
        labels=labels,
        mask=mask,
        num_elements=num_elements,
        box_format=box_format,
        normalized=normalized,
        canvas_size=canvas_size,
    )
    condition_input = processed.get("input_ids")
    processed_labels = None if labels is None else torch.as_tensor(labels)
    counts = processed.get("num_elements")
    condition = build_condition(
        self.tokenizer,
        condition_type=canonical,
        input_ids=condition_input,
        labels=processed_labels,
        num_elements=counts,
    )
    if condition is not None and condition.input_ids is not None:
        batch_size = condition.input_ids.shape[0]
    if condition is not None and canonical is ConditionType.refinement:
        if condition.input_ids is None:
            raise ValueError("refinement condition is missing input_ids")

        start_ids = condition.input_ids.to(self.device)
    else:
        start_ids = self.tokenizer.build_initial_tokens(
            batch_size=batch_size,
            num_elements=counts,
            labels=processed_labels,
            condition_type=str(canonical),
            generator=generator,
            device=self.device,
        )
    sample = index_to_log_onehot(start_ids, self.scheduler.config.vocab_size)
    start_step = None if condition is None else condition.start_step
    self.scheduler.set_timesteps(
        sampling.num_inference_steps or num_inference_steps,
        start_step=start_step,
        device=self.device,
    )
    trajectory = [] if return_intermediates else None
    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.transformer(
            input_ids=input_ids,
            timesteps=timestep_batch,
            condition_ids=None
            if condition is None or condition.input_ids is None
            else condition.input_ids.to(self.device),
            condition_type=None if condition is None else str(condition.type),
        ).logits
        out = self.scheduler.step(
            logits,
            timestep_batch,
            sample,
            sampling=sampling,
            condition=condition,
            generator=generator,
        )
        sample = out.prev_sample
        if trajectory is not None:
            trajectory.append(log_onehot_to_index(sample).detach().cpu())
    output = self._decode_final_sample(
        sample=sample,
        trajectory=trajectory,
        condition_type=str(canonical),
        return_intermediates=return_intermediates,
    )
    return self._coerce_output(output=output, output_type=output_type)

save_pretrained

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

Save a Diffusers pipeline directory.

Source code in models/layoutdiffusion/src/layoutdiffusion/pipeline_layoutdiffusion.py
275
276
277
278
279
def save_pretrained(
    self, save_directory: str | Path, **kwargs: LayoutDiffusionPipelineKwarg
) -> None:
    """Save a Diffusers pipeline directory."""
    super().save_pretrained(save_directory, **kwargs)

from_pretrained classmethod

from_pretrained(
    pretrained_model_name_or_path: str | Path,
    **kwargs: LayoutDiffusionPipelineKwarg,
) -> "LayoutDiffusionPipeline"

Load a LayoutDiffusion pipeline and rebuild its processor.

Source code in models/layoutdiffusion/src/layoutdiffusion/pipeline_layoutdiffusion.py
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
@classmethod
def from_pretrained(
    cls,
    pretrained_model_name_or_path: str | Path,
    **kwargs: LayoutDiffusionPipelineKwarg,
) -> "LayoutDiffusionPipeline":
    """Load a LayoutDiffusion pipeline and rebuild its processor."""
    tokenizer = kwargs.pop("tokenizer", None)
    if tokenizer is None:
        tokenizer = LayoutDiffusionTokenizer.from_pretrained(
            pretrained_model_name_or_path
        )
    pipe = super().from_pretrained(
        pretrained_model_name_or_path,
        tokenizer=tokenizer,
        **kwargs,
    )
    pipe.processor = LayoutDiffusionProcessor(pipe.tokenizer)
    return pipe

LayoutDiffusionProcessor

Bases: ProcessorMixin

Normalize public layout inputs and delegate tokenization.

Parameters:

Name Type Description Default
tokenizer LayoutDiffusionTokenizer

LayoutDiffusion tokenizer.

required

Examples:

>>> from layoutdiffusion import LayoutDiffusionConfig, LayoutDiffusionTokenizer
>>> cfg = LayoutDiffusionConfig(dataset_name="publaynet")
>>> proc = LayoutDiffusionProcessor(LayoutDiffusionTokenizer(cfg))
>>> proc.num_elements_to_tensor(2, batch_size=1).tolist()
[2]
Source code in models/layoutdiffusion/src/layoutdiffusion/processing_layoutdiffusion.py
 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
class LayoutDiffusionProcessor(ProcessorMixin):
    """Normalize public layout inputs and delegate tokenization.

    Args:
        tokenizer: LayoutDiffusion tokenizer.

    Examples:
        >>> from layoutdiffusion import LayoutDiffusionConfig, LayoutDiffusionTokenizer
        >>> cfg = LayoutDiffusionConfig(dataset_name="publaynet")
        >>> proc = LayoutDiffusionProcessor(LayoutDiffusionTokenizer(cfg))
        >>> proc.num_elements_to_tensor(2, batch_size=1).tolist()
        [2]
    """

    attributes = ["tokenizer"]
    tokenizer_class = "LayoutDiffusionTokenizer"

    def __init__(self, tokenizer: LayoutDiffusionTokenizer) -> None:
        """Initialize the processor."""
        self.tokenizer = tokenizer

    def __call__(
        self,
        *,
        bbox: Float[torch.Tensor, "batch elements 4"]
        | Float[np.ndarray, "batch elements 4"]
        | Sequence[ArrayLikeInput]
        | None = None,
        labels: Int[torch.Tensor, "batch elements"]
        | Int[np.ndarray, "batch elements"]
        | 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,
        return_tensors: Literal["pt"] = "pt",
    ) -> dict[str, Shaped[torch.Tensor, "..."]]:
        """Process layout tensors for conditional generation.

        Args:
            bbox: Optional layout boxes.
            labels: Optional labels.
            mask: Optional valid-element mask.
            num_elements: Optional element counts.
            box_format: Format of ``bbox``.
            normalized: Whether ``bbox`` is normalized.
            canvas_size: Pixel canvas size.
            return_tensors: Only ``"pt"`` is supported.

        Returns:
            Tokenizer output or element-count tensor.

        Raises:
            ValueError: If required conditional tensors are missing.
        """
        if return_tensors != "pt":
            raise ValueError(
                "LayoutDiffusionProcessor only supports return_tensors='pt'"
            )

        if bbox is None or labels is None:
            batch_size = 1
            if isinstance(num_elements, list):
                batch_size = len(num_elements)
            counts = self.num_elements_to_tensor(num_elements, batch_size=batch_size)
            return {} if counts is None else {"num_elements": counts}
        return self.tokenizer(
            bbox=torch.as_tensor(bbox),
            labels=torch.as_tensor(labels),
            mask=None if mask is None else torch.as_tensor(mask),
            box_format=box_format,
            normalized=normalized,
            canvas_size=canvas_size,
        )

    def num_elements_to_tensor(
        self,
        num_elements: int | list[int] | Int[torch.Tensor, "batch"] | None,
        *,
        batch_size: int,
    ) -> Int[torch.Tensor, "batch"] | None:
        """Convert public element counts to a tensor."""
        if num_elements is None:
            return None
        counts = torch.as_tensor(num_elements, dtype=torch.long)
        if counts.ndim == 0:
            counts = counts.expand(batch_size)
        return counts

__init__

__init__(tokenizer: LayoutDiffusionTokenizer) -> None

Initialize the processor.

Source code in models/layoutdiffusion/src/layoutdiffusion/processing_layoutdiffusion.py
35
36
37
def __init__(self, tokenizer: LayoutDiffusionTokenizer) -> None:
    """Initialize the processor."""
    self.tokenizer = tokenizer

__call__

__call__(
    *,
    bbox: Float[Tensor, "batch elements 4"]
    | Float[ndarray, "batch elements 4"]
    | Sequence[ArrayLikeInput]
    | None = None,
    labels: Int[Tensor, "batch elements"]
    | Int[ndarray, "batch elements"]
    | 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,
    return_tensors: Literal["pt"] = "pt",
) -> dict[str, Shaped[torch.Tensor, "..."]]

Process layout tensors for conditional generation.

Parameters:

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

Optional layout boxes.

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

Optional labels.

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

Optional valid-element mask.

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

Optional element counts.

None
box_format BoxFormat | str

Format of bbox.

xywh
normalized bool

Whether bbox is normalized.

True
canvas_size tuple[int, int] | None

Pixel canvas size.

None
return_tensors Literal['pt']

Only "pt" is supported.

'pt'

Returns:

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

Tokenizer output or element-count tensor.

Raises:

Type Description
ValueError

If required conditional tensors are missing.

Source code in models/layoutdiffusion/src/layoutdiffusion/processing_layoutdiffusion.py
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
def __call__(
    self,
    *,
    bbox: Float[torch.Tensor, "batch elements 4"]
    | Float[np.ndarray, "batch elements 4"]
    | Sequence[ArrayLikeInput]
    | None = None,
    labels: Int[torch.Tensor, "batch elements"]
    | Int[np.ndarray, "batch elements"]
    | 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,
    return_tensors: Literal["pt"] = "pt",
) -> dict[str, Shaped[torch.Tensor, "..."]]:
    """Process layout tensors for conditional generation.

    Args:
        bbox: Optional layout boxes.
        labels: Optional labels.
        mask: Optional valid-element mask.
        num_elements: Optional element counts.
        box_format: Format of ``bbox``.
        normalized: Whether ``bbox`` is normalized.
        canvas_size: Pixel canvas size.
        return_tensors: Only ``"pt"`` is supported.

    Returns:
        Tokenizer output or element-count tensor.

    Raises:
        ValueError: If required conditional tensors are missing.
    """
    if return_tensors != "pt":
        raise ValueError(
            "LayoutDiffusionProcessor only supports return_tensors='pt'"
        )

    if bbox is None or labels is None:
        batch_size = 1
        if isinstance(num_elements, list):
            batch_size = len(num_elements)
        counts = self.num_elements_to_tensor(num_elements, batch_size=batch_size)
        return {} if counts is None else {"num_elements": counts}
    return self.tokenizer(
        bbox=torch.as_tensor(bbox),
        labels=torch.as_tensor(labels),
        mask=None if mask is None else torch.as_tensor(mask),
        box_format=box_format,
        normalized=normalized,
        canvas_size=canvas_size,
    )

num_elements_to_tensor

num_elements_to_tensor(
    num_elements: int
    | list[int]
    | Int[Tensor, "batch"]
    | None,
    *,
    batch_size: int,
) -> Int[torch.Tensor, "batch"] | None

Convert public element counts to a tensor.

Source code in models/layoutdiffusion/src/layoutdiffusion/processing_layoutdiffusion.py
 98
 99
100
101
102
103
104
105
106
107
108
109
110
def num_elements_to_tensor(
    self,
    num_elements: int | list[int] | Int[torch.Tensor, "batch"] | None,
    *,
    batch_size: int,
) -> Int[torch.Tensor, "batch"] | None:
    """Convert public element counts to a tensor."""
    if num_elements is None:
        return None
    counts = torch.as_tensor(num_elements, dtype=torch.long)
    if counts.ndim == 0:
        counts = counts.expand(batch_size)
    return counts

LayoutDiffusionScheduler

Bases: SchedulerMixin, ConfigMixin

Diffusers scheduler for LayoutDiffusion categorical transitions.

Parameters:

Name Type Description Default
num_train_timesteps int

Number of training diffusion steps.

200
vocab_size int

Full vocabulary size including mask.

required
mask_token_id int

Mask token id.

required
type_classes int

Number of label/type classes.

required
num_special_tokens int

Number of leading special tokens.

5
num_coordinate_bins int

Coordinate vocabulary size.

128
noise_schedule str

Reference schedule name.

'gaussian_refine_pow2.5'
pow_num float

Gaussian transition exponent.

2.5
mul_num float

Gaussian transition multiplier.

12.4
type_start_step int

Label-conditioned start step.

160
rico_refine_start_step int

RICO refinement start step.

50
publaynet_refine_start_step int

PubLayNet refinement start step.

60

Examples:

>>> scheduler = LayoutDiffusionScheduler(vocab_size=139, mask_token_id=138, type_classes=5)
>>> scheduler.q_mats.shape[-2:]
torch.Size([128, 128])
Source code in models/layoutdiffusion/src/layoutdiffusion/scheduling_layoutdiffusion.py
 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
class LayoutDiffusionScheduler(SchedulerMixin, ConfigMixin):
    """Diffusers scheduler for LayoutDiffusion categorical transitions.

    Args:
        num_train_timesteps: Number of training diffusion steps.
        vocab_size: Full vocabulary size including mask.
        mask_token_id: Mask token id.
        type_classes: Number of label/type classes.
        num_special_tokens: Number of leading special tokens.
        num_coordinate_bins: Coordinate vocabulary size.
        noise_schedule: Reference schedule name.
        pow_num: Gaussian transition exponent.
        mul_num: Gaussian transition multiplier.
        type_start_step: Label-conditioned start step.
        rico_refine_start_step: RICO refinement start step.
        publaynet_refine_start_step: PubLayNet refinement start step.

    Examples:
        >>> scheduler = LayoutDiffusionScheduler(vocab_size=139, mask_token_id=138, type_classes=5)
        >>> scheduler.q_mats.shape[-2:]
        torch.Size([128, 128])
    """

    config_name = "scheduler_config.json"
    order = 1

    @register_to_config
    def __init__(
        self,
        *,
        num_train_timesteps: int = 200,
        vocab_size: int,
        mask_token_id: int,
        type_classes: int,
        num_special_tokens: int = 5,
        num_coordinate_bins: int = 128,
        noise_schedule: str = "gaussian_refine_pow2.5",
        pow_num: float = 2.5,
        mul_num: float = 12.4,
        type_start_step: int = 160,
        rico_refine_start_step: int = 50,
        publaynet_refine_start_step: int = 60,
    ) -> None:
        """Initialize scheduler buffers."""
        self.num_timesteps = num_train_timesteps
        self.vocab_size = vocab_size
        self.mask_token_id = mask_token_id
        self.type_classes = type_classes
        self.num_special_tokens = num_special_tokens
        self.num_coordinate_bins = num_coordinate_bins
        self.noise_schedule = noise_schedule
        self.pow_num = pow_num
        self.mul_num = mul_num
        self.type_start_step = type_start_step
        self.rico_refine_start_step = rico_refine_start_step
        self.publaynet_refine_start_step = publaynet_refine_start_step
        self.timesteps = torch.arange(num_train_timesteps - 1, -1, -1)
        self._init_buffers()

    @classmethod
    def from_layout_config(
        cls, config: LayoutDiffusionConfig
    ) -> LayoutDiffusionScheduler:
        """Build a scheduler from serialized LayoutDiffusion settings.

        Args:
            config: LayoutDiffusion model/tokenizer/scheduler settings.

        Returns:
            A scheduler initialized with the config's diffusion parameters.
        """
        return cls(
            num_train_timesteps=config.diffusion_steps,
            vocab_size=config.vocab_size,
            mask_token_id=config.mask_token_id,
            type_classes=config.type_classes,
            num_coordinate_bins=config.num_coordinate_bins,
            noise_schedule=config.noise_schedule,
            pow_num=config.pow_num,
            mul_num=config.mul_num,
            type_start_step=config.type_start_step,
        )

    def set_timesteps(
        self,
        num_inference_steps: int | None = None,
        *,
        start_step: int | None = None,
        device: torch.device | None = None,
    ) -> None:
        """Set reverse diffusion timesteps."""
        start = self.num_timesteps if start_step is None else start_step
        steps = num_inference_steps or start
        values = [int(i * start / steps) for i in range(steps - 1, -1, -1)]
        if values[-1] != 0:
            values.append(0)
        self.timesteps = torch.tensor(values, dtype=torch.long, device=device)

    def predict_start(
        self,
        logits: Float[torch.Tensor, "batch vocab_without_mask tokens"],
        batch_size: int,
        seq_length: int,
    ) -> Float[torch.Tensor, "batch vocab tokens"]:
        """Append the fixed mask logit and clamp model log probabilities."""
        log_pred = torch.log_softmax(logits.double(), dim=1).float()
        zero = (
            torch.zeros(
                batch_size, 1, seq_length, device=logits.device, dtype=logits.dtype
            )
            - 70
        )
        return torch.cat((log_pred, zero), dim=1).clamp(-70.0, 0.0)

    def 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"]:
        """Compute ``q(x_t | x_{t-1})``."""
        matrix = self._transition_matrix(t, cumulative=False, device=log_x_t.device)
        return matrix.matmul(log_x_t.exp()).clamp(min=1e-30).log()

    def q_pred(
        self,
        log_x_start: Float[torch.Tensor, "batch vocab tokens"],
        t: Int[torch.Tensor, "batch"],
    ) -> Float[torch.Tensor, "batch vocab tokens"]:
        """Compute cumulative ``q(x_t | x_0)``."""
        t = (t + (self.num_timesteps + 1)) % (self.num_timesteps + 1)
        matrix = self._transition_matrix(t, cumulative=True, device=log_x_start.device)
        return matrix.matmul(log_x_start.exp()).clamp(min=1e-30).log()

    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 ``p_theta(x_{t-1} | x_t)`` from predicted start logits."""
        if t.min().item() < 0 or t.max().item() >= self.num_timesteps:
            raise ValueError("timestep outside scheduler range")

        batch_size = log_x_start.shape[0]
        onehot_x_t = log_onehot_to_index(log_x_t)
        mask = onehot_x_t.eq(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_zero_aux = torch.log(log_one + 1.0e-30).expand(-1, -1, -1)

        log_qt = self.q_pred(log_x_t, t)[:, :-1, :]
        log_cumprod_ct = _extract(
            self.log_cumprod_ct.to(t.device), t, log_x_start.shape
        )
        ct_cumprod = torch.cat(
            [
                log_zero_aux.expand(-1, self.num_special_tokens, -1),
                log_cumprod_ct.expand(
                    -1, self.vocab_size - 1 - self.num_special_tokens, -1
                ),
            ],
            dim=1,
        )
        log_qt = (~mask) * log_qt + mask * ct_cumprod

        log_qt_one = self.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.log_ct.to(t.device), t, log_x_start.shape)
        ct_vector = torch.cat(
            [
                log_zero_aux.expand(-1, self.num_special_tokens, -1),
                log_ct.expand(-1, self.vocab_size - 1 - self.num_special_tokens, -1),
            ],
            dim=1,
        )
        ct_vector = torch.cat((ct_vector, 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)
        posterior = self.q_pred(q, t - 1) + log_qt_one
        return posterior.clamp(-70.0, 0.0)

    def step(
        self,
        logits: Float[torch.Tensor, "batch vocab_without_mask tokens"],
        timestep: Int[torch.Tensor, "batch"],
        sample: Float[torch.Tensor, "batch vocab tokens"],
        *,
        sampling: LayoutDiffusionSamplingConfig,
        condition: LayoutDiffusionCondition | None = None,
        generator: torch.Generator | None = None,
    ) -> LayoutDiffusionSchedulerOutput:
        """Run one reverse diffusion step."""
        _ = condition
        log_x_recon = self.predict_start(logits, sample.shape[0], sample.shape[-1])
        model_log_prob = self.q_posterior(log_x_recon, sample, timestep)
        if str(sampling.name) == str(LayoutDiffusionSamplingName.argmax):
            ids = model_log_prob.argmax(dim=1)
            prev = index_to_log_onehot(ids, self.vocab_size)
        else:
            prev = self.log_sample_categorical(model_log_prob, generator=generator)
        return LayoutDiffusionSchedulerOutput(
            prev_sample=prev,
            pred_original_sample=log_x_recon,
            model_log_prob=model_log_prob,
        )

    def log_sample_categorical(
        self,
        logits: Float[torch.Tensor, "batch vocab tokens"],
        *,
        generator: torch.Generator | None = None,
    ) -> Float[torch.Tensor, "batch vocab tokens"]:
        """Sample log one-hot categorical tokens with Gumbel-max."""
        sample = (gumbel_noise_like(logits, generator=generator) + logits).argmax(dim=1)
        return index_to_log_onehot(sample, self.vocab_size)

    def _init_buffers(self) -> None:
        if self.noise_schedule != "gaussian_refine_pow2.5":
            raise NotImplementedError(
                "Only gaussian_refine_pow2.5 LayoutDiffusion schedule is supported"
            )

        at, at1, bt1, bt2, ct, ct1, att, att1, btt1, btt2, ctt, ctt1 = _alpha_schedule(
            self.num_timesteps, type_classes=25
        )
        at1_t = torch.tensor(at1.astype("float64"))
        ct1_t = torch.tensor(ct1.astype("float64"))

        log_at1 = torch.log(at1_t).clamp(-70, 0)
        log_ct1 = torch.log(ct1_t).clamp(-70, 0)

        att1_t = torch.tensor(att1.astype("float64"))
        ctt1_t = torch.tensor(ctt1.astype("float64"))
        log_cumprod_at1 = torch.log(att1_t).clamp(-70, 0)
        log_cumprod_ct1 = torch.log(ctt1_t).clamp(-70, 0)
        log_1_min_ct1 = _log_1_min_a(log_ct1)
        log_1_min_cumprod_ct1 = _log_1_min_a(log_cumprod_ct1)

        self.log_ct1 = log_ct1.float()
        self.log_at1 = log_at1.float()
        self.log_cumprod_at1 = log_cumprod_at1.float()
        self.log_cumprod_ct1 = log_cumprod_ct1.float()
        self.log_1_min_ct1 = log_1_min_ct1.float()
        self.log_1_min_cumprod_ct1 = log_1_min_cumprod_ct1.float()

        at_t = torch.tensor(at.astype("float64"))
        bt1_t = torch.tensor(bt1.astype("float64"))
        bt2_t = torch.tensor(bt2.astype("float64"))
        ct_t = torch.tensor(ct.astype("float64"))

        log_at = torch.log(at_t)
        log_bt1 = torch.log(bt1_t)
        log_bt2 = torch.log(bt2_t)
        log_ct = torch.log(ct_t).clamp(-70, 0)

        att_t = torch.tensor(att.astype("float64"))
        btt1_t = torch.tensor(btt1.astype("float64"))
        btt2_t = torch.tensor(btt2.astype("float64"))
        ctt_t = torch.tensor(ctt.astype("float64"))
        log_cumprod_at = torch.log(att_t)
        log_cumprod_bt1 = torch.log(btt1_t)
        log_cumprod_bt2 = torch.log(btt2_t)
        log_cumprod_ct = torch.log(ctt_t).clamp(-70, 0)
        log_1_min_ct = _log_1_min_a(log_ct)
        log_1_min_cumprod_ct = _log_1_min_a(log_cumprod_ct)

        self.log_at = log_at.float()
        self.log_bt1 = log_bt1.float()
        self.log_bt2 = log_bt2.float()
        self.log_ct = log_ct.float()
        self.log_cumprod_at = log_cumprod_at.float()
        self.log_cumprod_bt1 = log_cumprod_bt1.float()
        self.log_cumprod_bt2 = log_cumprod_bt2.float()
        self.log_cumprod_ct = log_cumprod_ct.float()
        self.log_1_min_ct = log_1_min_ct.float()
        self.log_1_min_cumprod_ct = log_1_min_cumprod_ct.float()

        bt2_t = torch.where(bt2_t == 0.0, bt2_t.max(), bt2_t)
        q_one_step = [
            _gaussian_matrix2(t, bt=bt2_t.pow(2).pow(self.pow_num / 2) * self.mul_num)
            for t in range(self.num_timesteps)
        ]
        q_one_step.append(
            np.ones((self.num_coordinate_bins, self.num_coordinate_bins))
            / (self.num_coordinate_bins**2)
        )
        q_onestep_mats = torch.from_numpy(np.stack(q_one_step, axis=0)).float()
        self.q_onestep_mats = q_onestep_mats
        q_mat = self.q_onestep_mats[0]
        q_mats = [q_mat]
        for t in range(1, self.num_timesteps):
            q_mat = np.tensordot(q_mat, self.q_onestep_mats[t], axes=([1], [0]))
            q_mats.append(q_mat)
        q_mats.append(
            np.ones((self.num_coordinate_bins, self.num_coordinate_bins))
            / (self.num_coordinate_bins**2)
        )
        self.q_mats = torch.from_numpy(np.stack(q_mats, axis=0)).float()

    def _transition_matrix(
        self, t: Int[torch.Tensor, "batch"], *, cumulative: bool, device: torch.device
    ) -> Float[torch.Tensor, "batch vocab vocab"]:
        batch_size = t.shape[0]
        if cumulative:
            log_at = _extract(self.log_cumprod_at.to(device), t, (batch_size, 1, 1))
            log_bt1 = _extract(self.log_cumprod_bt1.to(device), t, (batch_size, 1, 1))
            log_bt2 = _extract(self.log_cumprod_bt2.to(device), t, (batch_size, 1, 1))
            log_ct = _extract(self.log_cumprod_ct.to(device), t, (batch_size, 1, 1))
            log_at1 = _extract(self.log_cumprod_at1.to(device), t, (batch_size, 1, 1))
            log_ct1 = _extract(self.log_cumprod_ct1.to(device), t, (batch_size, 1, 1))
            q_coord = self.q_mats[t.detach().cpu()].to(device)
        else:
            log_at = _extract(self.log_at.to(device), t, (batch_size, 1, 1))
            log_bt1 = _extract(self.log_bt1.to(device), t, (batch_size, 1, 1))
            log_bt2 = _extract(self.log_bt2.to(device), t, (batch_size, 1, 1))
            log_ct = _extract(self.log_ct.to(device), t, (batch_size, 1, 1))
            log_at1 = _extract(self.log_at1.to(device), t, (batch_size, 1, 1))
            log_ct1 = _extract(self.log_ct1.to(device), t, (batch_size, 1, 1))
            q_coord = self.q_onestep_mats[t.detach().cpu()].to(device)
        log_1_min_ct = _log_1_min_a(log_ct)
        log_1_min_ct1 = _log_1_min_a(log_ct1)
        eye_special = torch.eye(self.num_special_tokens, device=device).expand(
            batch_size, -1, -1
        )
        zeros_special_rest = torch.zeros(
            batch_size,
            self.num_special_tokens,
            self.vocab_size - self.num_special_tokens,
            device=device,
        )
        type_eye = (
            torch.eye(self.type_classes, device=device)
            .clamp(min=1e-30)
            .log()
            .expand(batch_size, -1, -1)
        )
        coord_eye = (
            torch.eye(self.num_coordinate_bins, device=device)
            .clamp(min=1e-30)
            .log()
            .expand(batch_size, -1, -1)
        )
        matrix_absorb = torch.cat(
            [
                torch.cat([eye_special, zeros_special_rest], dim=-1),
                torch.cat(
                    [
                        torch.zeros(
                            batch_size,
                            self.type_classes,
                            self.num_special_tokens,
                            device=device,
                        ),
                        log_add_exp(type_eye + log_at1, log_bt1).exp(),
                        torch.zeros(
                            batch_size,
                            self.type_classes,
                            self.vocab_size
                            - self.num_special_tokens
                            - self.type_classes,
                            device=device,
                        ),
                    ],
                    dim=-1,
                ),
                torch.cat(
                    [
                        torch.zeros(
                            batch_size,
                            self.num_coordinate_bins,
                            self.num_special_tokens + self.type_classes,
                            device=device,
                        ),
                        log_add_exp(coord_eye + log_at, log_bt2).exp(),
                        torch.zeros(
                            batch_size, self.num_coordinate_bins, 1, device=device
                        ),
                    ],
                    dim=-1,
                ),
                torch.cat(
                    [
                        torch.zeros(
                            batch_size, 1, self.num_special_tokens, device=device
                        ),
                        log_add_exp(
                            torch.zeros(batch_size, 1, self.type_classes, device=device)
                            .clamp(min=1e-30)
                            .log()
                            + log_1_min_ct1,
                            log_ct1,
                        ).exp(),
                        log_add_exp(
                            torch.zeros(
                                batch_size, 1, self.num_coordinate_bins, device=device
                            )
                            .clamp(min=1e-30)
                            .log()
                            + log_1_min_ct,
                            log_ct,
                        ).exp(),
                        torch.ones(batch_size, 1, 1, device=device),
                    ],
                    dim=-1,
                ),
            ],
            dim=-2,
        )
        matrix_gaussian = matrix_absorb.clone()
        coord_start = self.num_special_tokens + self.type_classes
        matrix_gaussian[
            :,
            coord_start : coord_start + self.num_coordinate_bins,
            coord_start : coord_start + self.num_coordinate_bins,
        ] = q_coord
        early = (t < (self.num_timesteps * 4 // 5)).reshape(batch_size, 1, 1)
        return torch.where(early, matrix_gaussian, matrix_absorb)

__init__

__init__(
    *,
    num_train_timesteps: int = 200,
    vocab_size: int,
    mask_token_id: int,
    type_classes: int,
    num_special_tokens: int = 5,
    num_coordinate_bins: int = 128,
    noise_schedule: str = "gaussian_refine_pow2.5",
    pow_num: float = 2.5,
    mul_num: float = 12.4,
    type_start_step: int = 160,
    rico_refine_start_step: int = 50,
    publaynet_refine_start_step: int = 60,
) -> None

Initialize scheduler buffers.

Source code in models/layoutdiffusion/src/layoutdiffusion/scheduling_layoutdiffusion.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
@register_to_config
def __init__(
    self,
    *,
    num_train_timesteps: int = 200,
    vocab_size: int,
    mask_token_id: int,
    type_classes: int,
    num_special_tokens: int = 5,
    num_coordinate_bins: int = 128,
    noise_schedule: str = "gaussian_refine_pow2.5",
    pow_num: float = 2.5,
    mul_num: float = 12.4,
    type_start_step: int = 160,
    rico_refine_start_step: int = 50,
    publaynet_refine_start_step: int = 60,
) -> None:
    """Initialize scheduler buffers."""
    self.num_timesteps = num_train_timesteps
    self.vocab_size = vocab_size
    self.mask_token_id = mask_token_id
    self.type_classes = type_classes
    self.num_special_tokens = num_special_tokens
    self.num_coordinate_bins = num_coordinate_bins
    self.noise_schedule = noise_schedule
    self.pow_num = pow_num
    self.mul_num = mul_num
    self.type_start_step = type_start_step
    self.rico_refine_start_step = rico_refine_start_step
    self.publaynet_refine_start_step = publaynet_refine_start_step
    self.timesteps = torch.arange(num_train_timesteps - 1, -1, -1)
    self._init_buffers()

from_layout_config classmethod

from_layout_config(
    config: LayoutDiffusionConfig,
) -> LayoutDiffusionScheduler

Build a scheduler from serialized LayoutDiffusion settings.

Parameters:

Name Type Description Default
config LayoutDiffusionConfig

LayoutDiffusion model/tokenizer/scheduler settings.

required

Returns:

Type Description
LayoutDiffusionScheduler

A scheduler initialized with the config's diffusion parameters.

Source code in models/layoutdiffusion/src/layoutdiffusion/scheduling_layoutdiffusion.py
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
@classmethod
def from_layout_config(
    cls, config: LayoutDiffusionConfig
) -> LayoutDiffusionScheduler:
    """Build a scheduler from serialized LayoutDiffusion settings.

    Args:
        config: LayoutDiffusion model/tokenizer/scheduler settings.

    Returns:
        A scheduler initialized with the config's diffusion parameters.
    """
    return cls(
        num_train_timesteps=config.diffusion_steps,
        vocab_size=config.vocab_size,
        mask_token_id=config.mask_token_id,
        type_classes=config.type_classes,
        num_coordinate_bins=config.num_coordinate_bins,
        noise_schedule=config.noise_schedule,
        pow_num=config.pow_num,
        mul_num=config.mul_num,
        type_start_step=config.type_start_step,
    )

set_timesteps

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

Set reverse diffusion timesteps.

Source code in models/layoutdiffusion/src/layoutdiffusion/scheduling_layoutdiffusion.py
120
121
122
123
124
125
126
127
128
129
130
131
132
133
def set_timesteps(
    self,
    num_inference_steps: int | None = None,
    *,
    start_step: int | None = None,
    device: torch.device | None = None,
) -> None:
    """Set reverse diffusion timesteps."""
    start = self.num_timesteps if start_step is None else start_step
    steps = num_inference_steps or start
    values = [int(i * start / steps) for i in range(steps - 1, -1, -1)]
    if values[-1] != 0:
        values.append(0)
    self.timesteps = torch.tensor(values, dtype=torch.long, device=device)

predict_start

predict_start(
    logits: Float[
        Tensor, "batch vocab_without_mask tokens"
    ],
    batch_size: int,
    seq_length: int,
) -> Float[torch.Tensor, "batch vocab tokens"]

Append the fixed mask logit and clamp model log probabilities.

Source code in models/layoutdiffusion/src/layoutdiffusion/scheduling_layoutdiffusion.py
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
def predict_start(
    self,
    logits: Float[torch.Tensor, "batch vocab_without_mask tokens"],
    batch_size: int,
    seq_length: int,
) -> Float[torch.Tensor, "batch vocab tokens"]:
    """Append the fixed mask logit and clamp model log probabilities."""
    log_pred = torch.log_softmax(logits.double(), dim=1).float()
    zero = (
        torch.zeros(
            batch_size, 1, seq_length, device=logits.device, dtype=logits.dtype
        )
        - 70
    )
    return torch.cat((log_pred, zero), dim=1).clamp(-70.0, 0.0)

q_pred_one_timestep

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

Compute q(x_t | x_{t-1}).

Source code in models/layoutdiffusion/src/layoutdiffusion/scheduling_layoutdiffusion.py
151
152
153
154
155
156
157
158
def 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"]:
    """Compute ``q(x_t | x_{t-1})``."""
    matrix = self._transition_matrix(t, cumulative=False, device=log_x_t.device)
    return matrix.matmul(log_x_t.exp()).clamp(min=1e-30).log()

q_pred

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

Compute cumulative q(x_t | x_0).

Source code in models/layoutdiffusion/src/layoutdiffusion/scheduling_layoutdiffusion.py
160
161
162
163
164
165
166
167
168
def q_pred(
    self,
    log_x_start: Float[torch.Tensor, "batch vocab tokens"],
    t: Int[torch.Tensor, "batch"],
) -> Float[torch.Tensor, "batch vocab tokens"]:
    """Compute cumulative ``q(x_t | x_0)``."""
    t = (t + (self.num_timesteps + 1)) % (self.num_timesteps + 1)
    matrix = self._transition_matrix(t, cumulative=True, device=log_x_start.device)
    return matrix.matmul(log_x_start.exp()).clamp(min=1e-30).log()

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 p_theta(x_{t-1} | x_t) from predicted start logits.

Source code in models/layoutdiffusion/src/layoutdiffusion/scheduling_layoutdiffusion.py
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
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 ``p_theta(x_{t-1} | x_t)`` from predicted start logits."""
    if t.min().item() < 0 or t.max().item() >= self.num_timesteps:
        raise ValueError("timestep outside scheduler range")

    batch_size = log_x_start.shape[0]
    onehot_x_t = log_onehot_to_index(log_x_t)
    mask = onehot_x_t.eq(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_zero_aux = torch.log(log_one + 1.0e-30).expand(-1, -1, -1)

    log_qt = self.q_pred(log_x_t, t)[:, :-1, :]
    log_cumprod_ct = _extract(
        self.log_cumprod_ct.to(t.device), t, log_x_start.shape
    )
    ct_cumprod = torch.cat(
        [
            log_zero_aux.expand(-1, self.num_special_tokens, -1),
            log_cumprod_ct.expand(
                -1, self.vocab_size - 1 - self.num_special_tokens, -1
            ),
        ],
        dim=1,
    )
    log_qt = (~mask) * log_qt + mask * ct_cumprod

    log_qt_one = self.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.log_ct.to(t.device), t, log_x_start.shape)
    ct_vector = torch.cat(
        [
            log_zero_aux.expand(-1, self.num_special_tokens, -1),
            log_ct.expand(-1, self.vocab_size - 1 - self.num_special_tokens, -1),
        ],
        dim=1,
    )
    ct_vector = torch.cat((ct_vector, 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)
    posterior = self.q_pred(q, t - 1) + log_qt_one
    return posterior.clamp(-70.0, 0.0)

step

step(
    logits: Float[
        Tensor, "batch vocab_without_mask tokens"
    ],
    timestep: Int[Tensor, "batch"],
    sample: Float[Tensor, "batch vocab tokens"],
    *,
    sampling: LayoutDiffusionSamplingConfig,
    condition: LayoutDiffusionCondition | None = None,
    generator: Generator | None = None,
) -> LayoutDiffusionSchedulerOutput

Run one reverse diffusion step.

Source code in models/layoutdiffusion/src/layoutdiffusion/scheduling_layoutdiffusion.py
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
def step(
    self,
    logits: Float[torch.Tensor, "batch vocab_without_mask tokens"],
    timestep: Int[torch.Tensor, "batch"],
    sample: Float[torch.Tensor, "batch vocab tokens"],
    *,
    sampling: LayoutDiffusionSamplingConfig,
    condition: LayoutDiffusionCondition | None = None,
    generator: torch.Generator | None = None,
) -> LayoutDiffusionSchedulerOutput:
    """Run one reverse diffusion step."""
    _ = condition
    log_x_recon = self.predict_start(logits, sample.shape[0], sample.shape[-1])
    model_log_prob = self.q_posterior(log_x_recon, sample, timestep)
    if str(sampling.name) == str(LayoutDiffusionSamplingName.argmax):
        ids = model_log_prob.argmax(dim=1)
        prev = index_to_log_onehot(ids, self.vocab_size)
    else:
        prev = self.log_sample_categorical(model_log_prob, generator=generator)
    return LayoutDiffusionSchedulerOutput(
        prev_sample=prev,
        pred_original_sample=log_x_recon,
        model_log_prob=model_log_prob,
    )

log_sample_categorical

log_sample_categorical(
    logits: Float[Tensor, "batch vocab tokens"],
    *,
    generator: Generator | None = None,
) -> Float[torch.Tensor, "batch vocab tokens"]

Sample log one-hot categorical tokens with Gumbel-max.

Source code in models/layoutdiffusion/src/layoutdiffusion/scheduling_layoutdiffusion.py
245
246
247
248
249
250
251
252
253
def log_sample_categorical(
    self,
    logits: Float[torch.Tensor, "batch vocab tokens"],
    *,
    generator: torch.Generator | None = None,
) -> Float[torch.Tensor, "batch vocab tokens"]:
    """Sample log one-hot categorical tokens with Gumbel-max."""
    sample = (gumbel_noise_like(logits, generator=generator) + logits).argmax(dim=1)
    return index_to_log_onehot(sample, self.vocab_size)

LayoutDiffusionTokenizer

Bases: PreTrainedTokenizer

Tokenizer backed by the original LayoutDiffusion vocab.json.

Parameters:

Name Type Description Default
config LayoutDiffusionConfig | Mapping[str, LayoutDiffusionConfigValue] | None

LayoutDiffusion config or serialized config mapping.

None
vocab_file str | Path | None

Optional saved vocabulary file.

None
layout_config_file str | Path | None

Optional saved layout config file.

None
**kwargs LayoutDiffusionConfigValue

Extra PreTrainedTokenizer keyword arguments.

{}

Raises:

Type Description
ValueError

If required tokenizer files are absent.

Examples:

>>> tok = LayoutDiffusionTokenizer(
...     LayoutDiffusionConfig(dataset_name="publaynet")
... )
>>> tok.mask_token
'MASK'
Source code in models/layoutdiffusion/src/layoutdiffusion/tokenization_layoutdiffusion.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
 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
class LayoutDiffusionTokenizer(PreTrainedTokenizer):
    """Tokenizer backed by the original LayoutDiffusion ``vocab.json``.

    Args:
        config: LayoutDiffusion config or serialized config mapping.
        vocab_file: Optional saved vocabulary file.
        layout_config_file: Optional saved layout config file.
        **kwargs: Extra ``PreTrainedTokenizer`` keyword arguments.

    Raises:
        ValueError: If required tokenizer files are absent.

    Examples:
        >>> tok = LayoutDiffusionTokenizer(
        ...     LayoutDiffusionConfig(dataset_name="publaynet")
        ... )
        >>> tok.mask_token
        'MASK'
    """

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

    def __init__(
        self,
        config: LayoutDiffusionConfig
        | Mapping[str, LayoutDiffusionConfigValue]
        | None = None,
        *,
        vocab_file: str | Path | None = None,
        layout_config_file: str | Path | None = None,
        **kwargs: LayoutDiffusionConfigValue,
    ) -> None:
        """Initialize the tokenizer."""
        if isinstance(config, LayoutDiffusionConfig):
            pass
        elif config is None:
            config = self._load_config(
                layout_config_file=layout_config_file,
                kwargs=kwargs,
            )
        else:
            config = _config_from_mapping(config)
        if vocab_file is not None and Path(vocab_file).exists():
            raw_vocab = json.loads(Path(vocab_file).read_text(encoding="utf-8"))
            config.vocab = {str(k): int(v) for k, v in raw_vocab.items()}
            if "MASK" not in config.vocab:
                config.vocab["MASK"] = config.vocab_size - 1
            config.vocab_size = max(config.vocab.values()) + 1
        self.config = config
        self._token_to_id = dict(config.vocab)
        self._id_to_token = {idx: token for token, idx in self._token_to_id.items()}
        super().__init__(
            pad_token=kwargs.pop("pad_token", "PAD"),
            mask_token=kwargs.pop("mask_token", "MASK"),
            unk_token=kwargs.pop("unk_token", "UNK"),
            model_max_length=kwargs.pop("model_max_length", config.max_token_length),
            **kwargs,
        )

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

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

    def _tokenize(self, text: str, **kwargs: str | float | bool | None) -> list[str]:
        """Split a LayoutDiffusion token string on whitespace."""
        _ = kwargs
        return text.strip().split()

    def _convert_token_to_id(self, token: str) -> int:
        """Convert one token string to id."""
        return self._token_to_id.get(token, self.config.special_token_ids["UNK"])

    def _convert_id_to_token(self, index: int) -> str:
        """Convert one token id to string."""
        return self._id_to_token.get(int(index), "UNK")

    def convert_tokens_to_string(self, tokens: list[str]) -> str:
        """Join LayoutDiffusion tokens for debugging or parity fixtures."""
        return " ".join(tokens)

    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,
        box_format: BoxFormat | str = BoxFormat.xywh,
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
    ) -> dict[str, Shaped[torch.Tensor, ...]]:
        """Encode layout tensors into token ids.

        Args:
            bbox: Layout boxes.
            labels: Dataset-local labels.
            mask: Optional valid-element mask.
            box_format: Format of ``bbox``.
            normalized: Whether boxes are already normalized.
            canvas_size: Pixel canvas size for unnormalized boxes.

        Returns:
            Dictionary with ``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),
            box_format=box_format,
            normalized=normalized,
            canvas_size=canvas_size,
        )

    def encode_layout(
        self,
        *,
        bbox: Float[torch.Tensor, "batch elements 4"],
        labels: Int[torch.Tensor, "batch elements"],
        mask: Bool[torch.Tensor, "batch elements"] | None = None,
        box_format: BoxFormat | str = BoxFormat.xywh,
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
    ) -> dict[str, Shaped[torch.Tensor, ...]]:
        """Encode public layout tensors into LayoutDiffusion token ids.

        Args:
            bbox: Boxes shaped ``(B, S, 4)``.
            labels: Labels shaped ``(B, S)``.
            mask: Optional valid mask shaped ``(B, S)``.
            box_format: Input box format.
            normalized: Whether input boxes are normalized.
            canvas_size: Pixel canvas size when ``normalized`` is false.

        Returns:
            Encoded token tensors.

        Raises:
            ValueError: If unnormalized boxes omit ``canvas_size``.
        """
        if bbox.ndim == 2:
            bbox = bbox.unsqueeze(0)
        if labels.ndim == 1:
            labels = labels.unsqueeze(0)
        if mask is None:
            mask = torch.ones_like(labels, dtype=torch.bool)
        elif mask.ndim == 1:
            mask = mask.unsqueeze(0)
        bbox = bbox.float()
        if normalized:
            fmt = normalize_box_format(box_format)
            if fmt is BoxFormat.xywh:
                xywh = clamp_boxes(bbox)
            elif fmt is BoxFormat.ltrb:
                xywh = clamp_boxes(ltrb_to_xywh(bbox))
            else:
                xywh = clamp_boxes(ltwh_to_xywh(bbox))
        else:
            if canvas_size is None:
                raise ValueError("canvas_size is required when normalized=False")

            xywh = normalize_boxes(bbox, canvas_size=canvas_size, box_format=box_format)
        ltrb_ids = (xywh_to_ltrb(xywh).clamp(0.0, 1.0) * 127).round().long()
        batch_size = labels.shape[0]
        input_ids = torch.full(
            (batch_size, self.config.max_token_length),
            self.config.pad_token_id,
            dtype=torch.long,
        )
        input_ids[:, 0] = self.config.special_token_ids["START"]
        for batch_idx in range(batch_size):
            valid_positions = torch.nonzero(
                mask[batch_idx].bool(), as_tuple=False
            ).flatten()
            valid_positions = valid_positions[: self.config.max_num_elements]
            cursor = 1
            for elem_idx, source_idx in enumerate(valid_positions.tolist()):
                if elem_idx > 0:
                    input_ids[batch_idx, cursor] = self.config.special_token_ids["|"]
                    cursor += 1
                label = self.config.id2label[int(labels[batch_idx, source_idx].item())]
                token_ids = [self._token_to_id[label]]
                token_ids.extend(
                    self._token_to_id[str(int(v))]
                    for v in ltrb_ids[batch_idx, source_idx].tolist()
                )
                input_ids[batch_idx, cursor : cursor + 5] = torch.tensor(token_ids)
                cursor += 5
            if cursor < self.config.max_token_length:
                input_ids[batch_idx, cursor] = self.config.special_token_ids["END"]
        attention_mask = input_ids.ne(self.config.pad_token_id)
        return {
            "input_ids": input_ids,
            "attention_mask": attention_mask,
            "mask": mask.bool(),
        }

    def decode_layout(
        self,
        input_ids: Int[torch.Tensor, "batch tokens"],
        *,
        output_box_format: Literal["xywh", "ltrb"] = "xywh",
    ) -> dict[str, Shaped[torch.Tensor, ...]]:
        """Decode token ids into public layout tensors.

        Args:
            input_ids: Token ids shaped ``(B, L)``.
            output_box_format: ``"xywh"`` or ``"ltrb"``.

        Returns:
            Dictionary with ``bbox``, ``labels``, and ``mask``.

        Raises:
            ValueError: If ``output_box_format`` is unsupported.
        """
        if input_ids.ndim == 1:
            input_ids = input_ids.unsqueeze(0)

        batch_boxes = []
        batch_labels = []
        batch_masks = []

        for row in input_ids.cpu().long():
            tokens = [self._convert_id_to_token(int(idx)) for idx in row.tolist()]
            elements = self._parse_elements(tokens)
            boxes = torch.zeros(self.config.max_num_elements, 4, dtype=torch.float32)
            labels = torch.zeros(self.config.max_num_elements, dtype=torch.long)
            masks = torch.zeros(self.config.max_num_elements, dtype=torch.bool)

            for i, element in enumerate(elements[: self.config.max_num_elements]):
                label, *coords = element
                labels[i] = self.config.label2id[label]
                ltrb = torch.tensor([int(v) for v in coords], dtype=torch.float32) / 127
                boxes[i] = ltrb_to_xywh(ltrb) if output_box_format == "xywh" else ltrb
                masks[i] = True

            batch_boxes.append(clamp_boxes(boxes))
            batch_labels.append(labels)
            batch_masks.append(masks)

        if output_box_format not in {"xywh", "ltrb"}:
            raise ValueError(f"Unsupported output_box_format: {output_box_format}")

        return {
            "bbox": torch.stack(batch_boxes, dim=0),
            "labels": torch.stack(batch_labels, dim=0),
            "mask": torch.stack(batch_masks, dim=0),
        }

    def build_initial_tokens(
        self,
        *,
        batch_size: int,
        num_elements: Int[torch.Tensor, "batch"] | list[int] | int | None = None,
        labels: Int[torch.Tensor, "batch elements"] | None = None,
        condition_type: str = "unconditional",
        generator: torch.Generator | None = None,
        device: torch.device | None = None,
    ) -> Int[torch.Tensor, "batch tokens"]:
        """Build the LayoutDiffusion sampling start template.

        Args:
            batch_size: Number of samples.
            num_elements: Optional element counts in ``[1, 20]``.
            labels: Optional labels for label-conditioned generation.
            condition_type: Canonical condition name.
            generator: Optional random generator.
            device: Output device.

        Returns:
            Initial token ids shaped ``(B, 121)``.
        """
        device = device or torch.device("cpu")
        if num_elements is None:
            prior = torch.tensor(
                self.config.element_count_prior, dtype=torch.float32, device=device
            )
            counts = (
                torch.multinomial(
                    prior, batch_size, replacement=True, generator=generator
                )
                + 1
            )
        else:
            counts = torch.as_tensor(num_elements, dtype=torch.long, device=device)
            if counts.ndim == 0:
                counts = counts.expand(batch_size)
        counts = counts.clamp(1, self.config.max_num_elements)
        input_ids = torch.full(
            (batch_size, self.config.max_token_length),
            self.config.pad_token_id,
            dtype=torch.long,
            device=device,
        )
        mask_id = self.config.mask_token_id
        start = self.config.special_token_ids["START"]
        sep = self.config.special_token_ids["|"]
        end = self.config.special_token_ids["END"]

        for batch_idx in range(batch_size):
            n = int(counts[batch_idx].item())
            tokens = [start, mask_id, mask_id, mask_id, mask_id, mask_id]
            for _ in range(n - 1):
                tokens.extend([sep, mask_id, mask_id, mask_id, mask_id, mask_id])
            tokens.append(end)
            input_ids[batch_idx, : len(tokens)] = torch.tensor(tokens, device=device)

        if condition_type == "label" and labels is not None:
            label_ids = torch.as_tensor(labels, dtype=torch.long, device=device)
            for batch_idx in range(batch_size):
                for elem_idx in range(min(label_ids.shape[1], int(counts[batch_idx]))):
                    pos = 1 + elem_idx * 6
                    label = self.config.id2label[int(label_ids[batch_idx, elem_idx])]
                    input_ids[batch_idx, pos] = self._token_to_id[label]

                coord_noise = (
                    torch.randint(
                        self.config.num_coordinate_bins,
                        input_ids.shape,
                        generator=generator,
                        device=device,
                    )
                    + self.config.coordinate_token_offset
                )
                coord_positions = torch.zeros_like(input_ids, dtype=torch.bool)
                for elem_idx in range(self.config.max_num_elements):
                    start_pos = 2 + elem_idx * 6
                    coord_positions[:, start_pos : start_pos + 4] = True
                input_ids = torch.where(
                    coord_positions & input_ids.eq(mask_id), coord_noise, input_ids
                )
        return input_ids

    def token_ids_to_text(
        self, input_ids: Int[torch.Tensor, "batch tokens"]
    ) -> list[str]:
        """Convert token ids to LayoutDiffusion text lines."""
        if input_ids.ndim == 1:
            input_ids = input_ids.unsqueeze(0)
        return [
            " ".join(self._convert_id_to_token(int(idx)) for idx in row.tolist())
            for row in input_ids.cpu().long()
        ]

    def text_to_token_ids(self, lines: list[str]) -> Int[torch.Tensor, "batch tokens"]:
        """Convert LayoutDiffusion text lines into padded token ids."""
        rows = []
        for line in lines:
            tokens = line.strip().split()
            if tokens[:1] != ["START"]:
                tokens = ["START", *tokens]
            if tokens[-1:] != ["END"]:
                tokens = [*tokens, "END"]
            ids = [self._convert_token_to_id(token) for token in tokens]
            ids = ids[: self.config.max_token_length]
            ids.extend(
                [self.config.pad_token_id] * (self.config.max_token_length - len(ids))
            )
            rows.append(torch.tensor(ids, dtype=torch.long))
        return torch.stack(rows, dim=0)

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

        Args:
            save_directory: Target directory.
            filename_prefix: Optional Transformers filename prefix.

        Returns:
            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"
        config_file = save_path / f"{prefix}layout_config.json"
        vocab_file.write_text(
            json.dumps(self._token_to_id, indent=2, sort_keys=True),
            encoding="utf-8",
        )
        data = dict(self.config.config)
        data["id2label"] = {str(k): v for k, v in self.config.id2label.items()}
        data["vocab"] = self._token_to_id
        config_file.write_text(
            json.dumps(data, indent=2, sort_keys=True), encoding="utf-8"
        )
        return (str(vocab_file), str(config_file))

    @classmethod
    def from_pretrained(
        cls,
        path: str | PathLike[str],
        *args: 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: LayoutDiffusionConfigValue,
    ) -> LayoutDiffusionTokenizer:
        """Load tokenizer from a pipeline root or tokenizer directory."""
        load_path = Path(path)
        tokenizer_dir = load_path / "tokenizer"
        if tokenizer_dir.is_dir():
            load_path = tokenizer_dir
        load_kwargs = {
            "cache_dir": cache_dir,
            "force_download": force_download,
            "local_files_only": local_files_only,
            "token": token,
            "revision": revision,
        }
        return super().from_pretrained(
            load_path,
            *args,
            **load_kwargs,
            **kwargs,
        )

    @classmethod
    def _load_config(
        cls,
        *,
        layout_config_file: str | Path | None,
        kwargs: dict[str, LayoutDiffusionConfigValue],
    ) -> LayoutDiffusionConfig:
        layout_config = kwargs.pop("layout_config", None)
        if layout_config is None and layout_config_file is None:
            raise ValueError("LayoutDiffusionTokenizer requires layout_config_file")

        if layout_config is None:
            config_file = Path(cast(str | Path, layout_config_file))
            config_text = config_file.read_text(encoding="utf-8")
            layout_config = json.loads(config_text)
        if not isinstance(layout_config, Mapping):
            raise TypeError("layout_config must be a mapping")

        normalized = {str(key): value for key, value in layout_config.items()}
        return _config_from_mapping(
            cast(Mapping[str, LayoutDiffusionConfigValue], normalized)
        )

    def _parse_elements(self, tokens: list[str]) -> list[list[str]]:
        start = tokens.index("START") if "START" in tokens else -1
        end = tokens.index("END") if "END" in tokens else 0
        if end <= start:
            end = max(
                (i for i, token in enumerate(tokens) if token == "|"), default=end
            )
        payload = tokens[start + 1 : end] if end > start else []
        groups: list[list[str]] = []
        current: list[str] = []
        for token in payload:
            if token == "|":
                if current:
                    groups.append(current)
                    current = []
            else:
                current.append(token)
        if current:
            groups.append(current)
        elements = []
        for group in groups:
            if len(group) >= 5 and all(token.isdigit() for token in group[-4:]):
                label = group[-5]
                if label in self.config.label2id:
                    elements.append([label, *group[-4:]])
        return elements

vocab_size property

vocab_size: int

Return full vocabulary size.

__init__

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

Initialize the tokenizer.

Source code in models/layoutdiffusion/src/layoutdiffusion/tokenization_layoutdiffusion.py
 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
def __init__(
    self,
    config: LayoutDiffusionConfig
    | Mapping[str, LayoutDiffusionConfigValue]
    | None = None,
    *,
    vocab_file: str | Path | None = None,
    layout_config_file: str | Path | None = None,
    **kwargs: LayoutDiffusionConfigValue,
) -> None:
    """Initialize the tokenizer."""
    if isinstance(config, LayoutDiffusionConfig):
        pass
    elif config is None:
        config = self._load_config(
            layout_config_file=layout_config_file,
            kwargs=kwargs,
        )
    else:
        config = _config_from_mapping(config)
    if vocab_file is not None and Path(vocab_file).exists():
        raw_vocab = json.loads(Path(vocab_file).read_text(encoding="utf-8"))
        config.vocab = {str(k): int(v) for k, v in raw_vocab.items()}
        if "MASK" not in config.vocab:
            config.vocab["MASK"] = config.vocab_size - 1
        config.vocab_size = max(config.vocab.values()) + 1
    self.config = config
    self._token_to_id = dict(config.vocab)
    self._id_to_token = {idx: token for token, idx in self._token_to_id.items()}
    super().__init__(
        pad_token=kwargs.pop("pad_token", "PAD"),
        mask_token=kwargs.pop("mask_token", "MASK"),
        unk_token=kwargs.pop("unk_token", "UNK"),
        model_max_length=kwargs.pop("model_max_length", config.max_token_length),
        **kwargs,
    )

get_vocab

get_vocab() -> dict[str, int]

Return a copy of token-to-id vocabulary.

Source code in models/layoutdiffusion/src/layoutdiffusion/tokenization_layoutdiffusion.py
110
111
112
def get_vocab(self) -> dict[str, int]:
    """Return a copy of token-to-id vocabulary."""
    return dict(self._token_to_id)

convert_tokens_to_string

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

Join LayoutDiffusion tokens for debugging or parity fixtures.

Source code in models/layoutdiffusion/src/layoutdiffusion/tokenization_layoutdiffusion.py
127
128
129
def convert_tokens_to_string(self, tokens: list[str]) -> str:
    """Join LayoutDiffusion tokens for debugging or parity fixtures."""
    return " ".join(tokens)

__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,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
) -> dict[str, Shaped[torch.Tensor, ...]]

Encode layout tensors into token ids.

Parameters:

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

Layout 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
box_format BoxFormat | str

Format of bbox.

xywh
normalized bool

Whether boxes are already normalized.

True
canvas_size tuple[int, int] | None

Pixel canvas size for unnormalized boxes.

None

Returns:

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

Dictionary with input_ids, attention_mask, and mask.

Source code in models/layoutdiffusion/src/layoutdiffusion/tokenization_layoutdiffusion.py
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
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,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
) -> dict[str, Shaped[torch.Tensor, ...]]:
    """Encode layout tensors into token ids.

    Args:
        bbox: Layout boxes.
        labels: Dataset-local labels.
        mask: Optional valid-element mask.
        box_format: Format of ``bbox``.
        normalized: Whether boxes are already normalized.
        canvas_size: Pixel canvas size for unnormalized boxes.

    Returns:
        Dictionary with ``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),
        box_format=box_format,
        normalized=normalized,
        canvas_size=canvas_size,
    )

encode_layout

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

Encode public layout tensors into LayoutDiffusion token ids.

Parameters:

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

Boxes shaped (B, S, 4).

required
labels Int[Tensor, 'batch elements']

Labels shaped (B, S).

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

Optional valid mask shaped (B, S).

None
box_format BoxFormat | str

Input box format.

xywh
normalized bool

Whether input boxes are normalized.

True
canvas_size tuple[int, int] | None

Pixel canvas size when normalized is false.

None

Returns:

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

Encoded token tensors.

Raises:

Type Description
ValueError

If unnormalized boxes omit canvas_size.

Source code in models/layoutdiffusion/src/layoutdiffusion/tokenization_layoutdiffusion.py
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
def encode_layout(
    self,
    *,
    bbox: Float[torch.Tensor, "batch elements 4"],
    labels: Int[torch.Tensor, "batch elements"],
    mask: Bool[torch.Tensor, "batch elements"] | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
) -> dict[str, Shaped[torch.Tensor, ...]]:
    """Encode public layout tensors into LayoutDiffusion token ids.

    Args:
        bbox: Boxes shaped ``(B, S, 4)``.
        labels: Labels shaped ``(B, S)``.
        mask: Optional valid mask shaped ``(B, S)``.
        box_format: Input box format.
        normalized: Whether input boxes are normalized.
        canvas_size: Pixel canvas size when ``normalized`` is false.

    Returns:
        Encoded token tensors.

    Raises:
        ValueError: If unnormalized boxes omit ``canvas_size``.
    """
    if bbox.ndim == 2:
        bbox = bbox.unsqueeze(0)
    if labels.ndim == 1:
        labels = labels.unsqueeze(0)
    if mask is None:
        mask = torch.ones_like(labels, dtype=torch.bool)
    elif mask.ndim == 1:
        mask = mask.unsqueeze(0)
    bbox = bbox.float()
    if normalized:
        fmt = normalize_box_format(box_format)
        if fmt is BoxFormat.xywh:
            xywh = clamp_boxes(bbox)
        elif fmt is BoxFormat.ltrb:
            xywh = clamp_boxes(ltrb_to_xywh(bbox))
        else:
            xywh = clamp_boxes(ltwh_to_xywh(bbox))
    else:
        if canvas_size is None:
            raise ValueError("canvas_size is required when normalized=False")

        xywh = normalize_boxes(bbox, canvas_size=canvas_size, box_format=box_format)
    ltrb_ids = (xywh_to_ltrb(xywh).clamp(0.0, 1.0) * 127).round().long()
    batch_size = labels.shape[0]
    input_ids = torch.full(
        (batch_size, self.config.max_token_length),
        self.config.pad_token_id,
        dtype=torch.long,
    )
    input_ids[:, 0] = self.config.special_token_ids["START"]
    for batch_idx in range(batch_size):
        valid_positions = torch.nonzero(
            mask[batch_idx].bool(), as_tuple=False
        ).flatten()
        valid_positions = valid_positions[: self.config.max_num_elements]
        cursor = 1
        for elem_idx, source_idx in enumerate(valid_positions.tolist()):
            if elem_idx > 0:
                input_ids[batch_idx, cursor] = self.config.special_token_ids["|"]
                cursor += 1
            label = self.config.id2label[int(labels[batch_idx, source_idx].item())]
            token_ids = [self._token_to_id[label]]
            token_ids.extend(
                self._token_to_id[str(int(v))]
                for v in ltrb_ids[batch_idx, source_idx].tolist()
            )
            input_ids[batch_idx, cursor : cursor + 5] = torch.tensor(token_ids)
            cursor += 5
        if cursor < self.config.max_token_length:
            input_ids[batch_idx, cursor] = self.config.special_token_ids["END"]
    attention_mask = input_ids.ne(self.config.pad_token_id)
    return {
        "input_ids": input_ids,
        "attention_mask": attention_mask,
        "mask": mask.bool(),
    }

decode_layout

decode_layout(
    input_ids: Int[Tensor, "batch tokens"],
    *,
    output_box_format: Literal["xywh", "ltrb"] = "xywh",
) -> dict[str, Shaped[torch.Tensor, ...]]

Decode token ids into public layout tensors.

Parameters:

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

Token ids shaped (B, L).

required
output_box_format Literal['xywh', 'ltrb']

"xywh" or "ltrb".

'xywh'

Returns:

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

Dictionary with bbox, labels, and mask.

Raises:

Type Description
ValueError

If output_box_format is unsupported.

Source code in models/layoutdiffusion/src/layoutdiffusion/tokenization_layoutdiffusion.py
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
def decode_layout(
    self,
    input_ids: Int[torch.Tensor, "batch tokens"],
    *,
    output_box_format: Literal["xywh", "ltrb"] = "xywh",
) -> dict[str, Shaped[torch.Tensor, ...]]:
    """Decode token ids into public layout tensors.

    Args:
        input_ids: Token ids shaped ``(B, L)``.
        output_box_format: ``"xywh"`` or ``"ltrb"``.

    Returns:
        Dictionary with ``bbox``, ``labels``, and ``mask``.

    Raises:
        ValueError: If ``output_box_format`` is unsupported.
    """
    if input_ids.ndim == 1:
        input_ids = input_ids.unsqueeze(0)

    batch_boxes = []
    batch_labels = []
    batch_masks = []

    for row in input_ids.cpu().long():
        tokens = [self._convert_id_to_token(int(idx)) for idx in row.tolist()]
        elements = self._parse_elements(tokens)
        boxes = torch.zeros(self.config.max_num_elements, 4, dtype=torch.float32)
        labels = torch.zeros(self.config.max_num_elements, dtype=torch.long)
        masks = torch.zeros(self.config.max_num_elements, dtype=torch.bool)

        for i, element in enumerate(elements[: self.config.max_num_elements]):
            label, *coords = element
            labels[i] = self.config.label2id[label]
            ltrb = torch.tensor([int(v) for v in coords], dtype=torch.float32) / 127
            boxes[i] = ltrb_to_xywh(ltrb) if output_box_format == "xywh" else ltrb
            masks[i] = True

        batch_boxes.append(clamp_boxes(boxes))
        batch_labels.append(labels)
        batch_masks.append(masks)

    if output_box_format not in {"xywh", "ltrb"}:
        raise ValueError(f"Unsupported output_box_format: {output_box_format}")

    return {
        "bbox": torch.stack(batch_boxes, dim=0),
        "labels": torch.stack(batch_labels, dim=0),
        "mask": torch.stack(batch_masks, dim=0),
    }

build_initial_tokens

build_initial_tokens(
    *,
    batch_size: int,
    num_elements: Int[Tensor, "batch"]
    | list[int]
    | int
    | None = None,
    labels: Int[Tensor, "batch elements"] | None = None,
    condition_type: str = "unconditional",
    generator: Generator | None = None,
    device: device | None = None,
) -> Int[torch.Tensor, "batch tokens"]

Build the LayoutDiffusion sampling start template.

Parameters:

Name Type Description Default
batch_size int

Number of samples.

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

Optional element counts in [1, 20].

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

Optional labels for label-conditioned generation.

None
condition_type str

Canonical condition name.

'unconditional'
generator Generator | None

Optional random generator.

None
device device | None

Output device.

None

Returns:

Type Description
Int[Tensor, 'batch tokens']

Initial token ids shaped (B, 121).

Source code in models/layoutdiffusion/src/layoutdiffusion/tokenization_layoutdiffusion.py
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
def build_initial_tokens(
    self,
    *,
    batch_size: int,
    num_elements: Int[torch.Tensor, "batch"] | list[int] | int | None = None,
    labels: Int[torch.Tensor, "batch elements"] | None = None,
    condition_type: str = "unconditional",
    generator: torch.Generator | None = None,
    device: torch.device | None = None,
) -> Int[torch.Tensor, "batch tokens"]:
    """Build the LayoutDiffusion sampling start template.

    Args:
        batch_size: Number of samples.
        num_elements: Optional element counts in ``[1, 20]``.
        labels: Optional labels for label-conditioned generation.
        condition_type: Canonical condition name.
        generator: Optional random generator.
        device: Output device.

    Returns:
        Initial token ids shaped ``(B, 121)``.
    """
    device = device or torch.device("cpu")
    if num_elements is None:
        prior = torch.tensor(
            self.config.element_count_prior, dtype=torch.float32, device=device
        )
        counts = (
            torch.multinomial(
                prior, batch_size, replacement=True, generator=generator
            )
            + 1
        )
    else:
        counts = torch.as_tensor(num_elements, dtype=torch.long, device=device)
        if counts.ndim == 0:
            counts = counts.expand(batch_size)
    counts = counts.clamp(1, self.config.max_num_elements)
    input_ids = torch.full(
        (batch_size, self.config.max_token_length),
        self.config.pad_token_id,
        dtype=torch.long,
        device=device,
    )
    mask_id = self.config.mask_token_id
    start = self.config.special_token_ids["START"]
    sep = self.config.special_token_ids["|"]
    end = self.config.special_token_ids["END"]

    for batch_idx in range(batch_size):
        n = int(counts[batch_idx].item())
        tokens = [start, mask_id, mask_id, mask_id, mask_id, mask_id]
        for _ in range(n - 1):
            tokens.extend([sep, mask_id, mask_id, mask_id, mask_id, mask_id])
        tokens.append(end)
        input_ids[batch_idx, : len(tokens)] = torch.tensor(tokens, device=device)

    if condition_type == "label" and labels is not None:
        label_ids = torch.as_tensor(labels, dtype=torch.long, device=device)
        for batch_idx in range(batch_size):
            for elem_idx in range(min(label_ids.shape[1], int(counts[batch_idx]))):
                pos = 1 + elem_idx * 6
                label = self.config.id2label[int(label_ids[batch_idx, elem_idx])]
                input_ids[batch_idx, pos] = self._token_to_id[label]

            coord_noise = (
                torch.randint(
                    self.config.num_coordinate_bins,
                    input_ids.shape,
                    generator=generator,
                    device=device,
                )
                + self.config.coordinate_token_offset
            )
            coord_positions = torch.zeros_like(input_ids, dtype=torch.bool)
            for elem_idx in range(self.config.max_num_elements):
                start_pos = 2 + elem_idx * 6
                coord_positions[:, start_pos : start_pos + 4] = True
            input_ids = torch.where(
                coord_positions & input_ids.eq(mask_id), coord_noise, input_ids
            )
    return input_ids

token_ids_to_text

token_ids_to_text(
    input_ids: Int[Tensor, "batch tokens"],
) -> list[str]

Convert token ids to LayoutDiffusion text lines.

Source code in models/layoutdiffusion/src/layoutdiffusion/tokenization_layoutdiffusion.py
384
385
386
387
388
389
390
391
392
393
def token_ids_to_text(
    self, input_ids: Int[torch.Tensor, "batch tokens"]
) -> list[str]:
    """Convert token ids to LayoutDiffusion text lines."""
    if input_ids.ndim == 1:
        input_ids = input_ids.unsqueeze(0)
    return [
        " ".join(self._convert_id_to_token(int(idx)) for idx in row.tolist())
        for row in input_ids.cpu().long()
    ]

text_to_token_ids

text_to_token_ids(
    lines: list[str],
) -> Int[torch.Tensor, "batch tokens"]

Convert LayoutDiffusion text lines into padded token ids.

Source code in models/layoutdiffusion/src/layoutdiffusion/tokenization_layoutdiffusion.py
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
def text_to_token_ids(self, lines: list[str]) -> Int[torch.Tensor, "batch tokens"]:
    """Convert LayoutDiffusion text lines into padded token ids."""
    rows = []
    for line in lines:
        tokens = line.strip().split()
        if tokens[:1] != ["START"]:
            tokens = ["START", *tokens]
        if tokens[-1:] != ["END"]:
            tokens = [*tokens, "END"]
        ids = [self._convert_token_to_id(token) for token in tokens]
        ids = ids[: self.config.max_token_length]
        ids.extend(
            [self.config.pad_token_id] * (self.config.max_token_length - len(ids))
        )
        rows.append(torch.tensor(ids, dtype=torch.long))
    return torch.stack(rows, dim=0)

save_vocabulary

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

Save vocabulary and layout config files.

Parameters:

Name Type Description Default
save_directory str | Path

Target directory.

required
filename_prefix str | None

Optional Transformers filename prefix.

None

Returns:

Type Description
tuple[str, ...]

Saved file paths.

Source code in models/layoutdiffusion/src/layoutdiffusion/tokenization_layoutdiffusion.py
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
def save_vocabulary(
    self, save_directory: str | Path, filename_prefix: str | None = None
) -> tuple[str, ...]:
    """Save vocabulary and layout config files.

    Args:
        save_directory: Target directory.
        filename_prefix: Optional Transformers filename prefix.

    Returns:
        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"
    config_file = save_path / f"{prefix}layout_config.json"
    vocab_file.write_text(
        json.dumps(self._token_to_id, indent=2, sort_keys=True),
        encoding="utf-8",
    )
    data = dict(self.config.config)
    data["id2label"] = {str(k): v for k, v in self.config.id2label.items()}
    data["vocab"] = self._token_to_id
    config_file.write_text(
        json.dumps(data, indent=2, sort_keys=True), encoding="utf-8"
    )
    return (str(vocab_file), str(config_file))

from_pretrained classmethod

from_pretrained(
    path: str | PathLike[str],
    *args: 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: LayoutDiffusionConfigValue,
) -> LayoutDiffusionTokenizer

Load tokenizer from a pipeline root or tokenizer directory.

Source code in models/layoutdiffusion/src/layoutdiffusion/tokenization_layoutdiffusion.py
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
@classmethod
def from_pretrained(
    cls,
    path: str | PathLike[str],
    *args: 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: LayoutDiffusionConfigValue,
) -> LayoutDiffusionTokenizer:
    """Load tokenizer from a pipeline root or tokenizer directory."""
    load_path = Path(path)
    tokenizer_dir = load_path / "tokenizer"
    if tokenizer_dir.is_dir():
        load_path = tokenizer_dir
    load_kwargs = {
        "cache_dir": cache_dir,
        "force_download": force_download,
        "local_files_only": local_files_only,
        "token": token,
        "revision": revision,
    }
    return super().from_pretrained(
        load_path,
        *args,
        **load_kwargs,
        **kwargs,
    )

conditioning

Condition normalization for LayoutDiffusion generation modes.

LayoutDiffusionCondition dataclass

Internal condition container used by the scheduler and pipeline.

Source code in models/layoutdiffusion/src/layoutdiffusion/conditioning.py
16
17
18
19
20
21
22
23
24
@dataclass(frozen=True)
class LayoutDiffusionCondition:
    """Internal condition container used by the scheduler and pipeline."""

    type: ConditionType
    input_ids: Int[torch.Tensor, "batch tokens"] | None = None
    mask: Bool[torch.Tensor, "batch elements"] | None = None
    num_elements: Int[torch.Tensor, "batch"] | None = None
    start_step: int | None = None

build_condition

build_condition(
    tokenizer: LayoutDiffusionTokenizer,
    *,
    condition_type: ConditionType | str,
    input_ids: Int[Tensor, "batch tokens"] | None = None,
    labels: Int[Tensor, "batch elements"] | None = None,
    num_elements: Int[Tensor, "batch"] | None = None,
) -> LayoutDiffusionCondition | None

Build a LayoutDiffusion condition from processed inputs.

Parameters:

Name Type Description Default
tokenizer LayoutDiffusionTokenizer

LayoutDiffusion tokenizer.

required
condition_type ConditionType | str

Public condition type or alias.

required
input_ids Int[Tensor, 'batch tokens'] | None

Optional encoded layout tokens.

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

Optional label tensor for label conditioning.

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

Optional element counts.

None

Returns:

Type Description
LayoutDiffusionCondition | None

Internal condition container or None for unconditional generation.

Raises:

Type Description
NotImplementedError

If a canonical mode is unsupported.

ValueError

If required inputs are absent.

Source code in models/layoutdiffusion/src/layoutdiffusion/conditioning.py
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
def build_condition(
    tokenizer: LayoutDiffusionTokenizer,
    *,
    condition_type: ConditionType | str,
    input_ids: Int[torch.Tensor, "batch tokens"] | None = None,
    labels: Int[torch.Tensor, "batch elements"] | None = None,
    num_elements: Int[torch.Tensor, "batch"] | None = None,
) -> LayoutDiffusionCondition | None:
    """Build a LayoutDiffusion condition from processed inputs.

    Args:
        tokenizer: LayoutDiffusion tokenizer.
        condition_type: Public condition type or alias.
        input_ids: Optional encoded layout tokens.
        labels: Optional label tensor for label conditioning.
        num_elements: Optional element counts.

    Returns:
        Internal condition container or ``None`` for unconditional generation.

    Raises:
        NotImplementedError: If a canonical mode is unsupported.
        ValueError: If required inputs are absent.
    """
    canonical = normalize_condition_type(condition_type)
    match canonical:
        case ConditionType.unconditional:
            return LayoutDiffusionCondition(
                type=canonical,
                num_elements=num_elements,
                start_step=tokenizer.config.diffusion_steps,
            )
        case ConditionType.label:
            if labels is None:
                raise ValueError("labels are required for condition_type='label'")

            return LayoutDiffusionCondition(
                type=canonical,
                input_ids=input_ids,
                num_elements=num_elements,
                start_step=tokenizer.config.type_start_step,
            )
        case ConditionType.refinement:
            if input_ids is None:
                raise ValueError("bbox and labels are required for refinement")

            return LayoutDiffusionCondition(
                type=canonical,
                input_ids=input_ids,
                start_step=tokenizer.config.refine_start_step,
            )
        case (
            ConditionType.label_size
            | ConditionType.completion
            | ConditionType.text
            | ConditionType.content_image
            | ConditionType.relation
            | ConditionType.hierarchical
            | ConditionType.retrieval
        ):
            raise NotImplementedError(
                f"LayoutDiffusion does not support condition_type={canonical}"
            )
        case _:
            assert_never(canonical)

configuration_layoutdiffusion

Configuration for converted LayoutDiffusion checkpoints.

LayoutDiffusionConfig

Bases: ConfigMixin

Serializable LayoutDiffusion model, tokenizer, and scheduler settings.

Parameters:

Name Type Description Default
dataset_name DatasetName | str

Dataset name or alias.

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

Optional persisted dataset-local label mapping.

None
vocab dict[str, int] | None

Optional token-to-id vocabulary loaded from vocab.json.

None
seq_length int

Full internal token sequence length.

121
max_num_elements int

Maximum number of layout elements.

20
num_coordinate_bins int

Number of coordinate tokens.

128
diffusion_steps int

Number of training diffusion timesteps.

200
noise_schedule str

Reference diffusion schedule name.

'gaussian_refine_pow2.5'
num_channels int

OpenAI timestep embedding dimension.

128
bert_config_name str

Name of the BERT config used by the checkpoint.

'bert-base-uncased'
max_position_embeddings int

BERT position embedding count.

512
hidden_size int

Transformer hidden size.

768
num_hidden_layers int

BERT encoder layer count.

12
num_attention_heads int

Attention head count.

12
intermediate_size int

Feed-forward hidden size.

3072
dropout float

Dropout probability.

0.1
training_mode str

Reference training mode.

'discrete'
vocab_size int | None

Full vocabulary size including mask.

None
refine_start_step int | None

Dataset-specific refinement start step.

None
type_start_step int

Reference type-conditioned start step.

160
element_count_prior list[float] | None

Optional 20-entry unconditional element count prior.

None
pow_num float

Gaussian transition exponent.

2.5
mul_num float

Gaussian transition multiplier.

12.4

Examples:

>>> cfg = LayoutDiffusionConfig(dataset_name="publaynet")
>>> cfg.mask_token_id == cfg.vocab_size - 1
True
Source code in models/layoutdiffusion/src/layoutdiffusion/configuration_layoutdiffusion.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
 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
class LayoutDiffusionConfig(ConfigMixin):
    """Serializable LayoutDiffusion model, tokenizer, and scheduler settings.

    Args:
        dataset_name: Dataset name or alias.
        id2label: Optional persisted dataset-local label mapping.
        vocab: Optional token-to-id vocabulary loaded from ``vocab.json``.
        seq_length: Full internal token sequence length.
        max_num_elements: Maximum number of layout elements.
        num_coordinate_bins: Number of coordinate tokens.
        diffusion_steps: Number of training diffusion timesteps.
        noise_schedule: Reference diffusion schedule name.
        num_channels: OpenAI timestep embedding dimension.
        bert_config_name: Name of the BERT config used by the checkpoint.
        max_position_embeddings: BERT position embedding count.
        hidden_size: Transformer hidden size.
        num_hidden_layers: BERT encoder layer count.
        num_attention_heads: Attention head count.
        intermediate_size: Feed-forward hidden size.
        dropout: Dropout probability.
        training_mode: Reference training mode.
        vocab_size: Full vocabulary size including mask.
        refine_start_step: Dataset-specific refinement start step.
        type_start_step: Reference type-conditioned start step.
        element_count_prior: Optional 20-entry unconditional element count prior.
        pow_num: Gaussian transition exponent.
        mul_num: Gaussian transition multiplier.

    Examples:
        >>> cfg = LayoutDiffusionConfig(dataset_name="publaynet")
        >>> cfg.mask_token_id == cfg.vocab_size - 1
        True
    """

    config_name = "layoutdiffusion_config.json"

    @register_to_config
    def __init__(
        self,
        *,
        dataset_name: DatasetName | str = DatasetName.rico25,
        id2label: dict[int | str, str] | None = None,
        vocab: dict[str, int] | None = None,
        seq_length: int = 121,
        max_num_elements: int = 20,
        num_coordinate_bins: int = 128,
        diffusion_steps: int = 200,
        noise_schedule: str = "gaussian_refine_pow2.5",
        num_channels: int = 128,
        bert_config_name: str = "bert-base-uncased",
        max_position_embeddings: int = 512,
        hidden_size: int = 768,
        num_hidden_layers: int = 12,
        num_attention_heads: int = 12,
        intermediate_size: int = 3072,
        dropout: float = 0.1,
        training_mode: str = "discrete",
        vocab_size: int | None = None,
        refine_start_step: int | None = None,
        type_start_step: int = 160,
        element_count_prior: list[float] | None = None,
        pow_num: float = 2.5,
        mul_num: float = 12.4,
    ) -> None:
        """Initialize LayoutDiffusion configuration."""
        self.dataset_name = str(normalize_dataset_name(dataset_name))
        raw_id2label = id2label or default_id2label(self.dataset_name)
        self.id2label = {int(k): v for k, v in raw_id2label.items()}

        self.vocab = vocab or self.default_vocab()
        self.seq_length = seq_length
        self.max_num_elements = max_num_elements
        self.num_coordinate_bins = num_coordinate_bins

        self.diffusion_steps = diffusion_steps
        self.noise_schedule = noise_schedule
        self.num_channels = num_channels
        self.bert_config_name = bert_config_name

        self.max_position_embeddings = max_position_embeddings
        self.hidden_size = hidden_size
        self.num_hidden_layers = num_hidden_layers
        self.num_attention_heads = num_attention_heads
        self.intermediate_size = intermediate_size
        self.dropout = dropout

        self.training_mode = training_mode
        self.vocab_size = vocab_size or len(self.vocab)
        self.refine_start_step = refine_start_step or (
            60 if self.dataset_name == str(DatasetName.publaynet) else 50
        )
        self.type_start_step = type_start_step
        self.element_count_prior = element_count_prior or self.default_element_prior()
        self.pow_num = pow_num
        self.mul_num = mul_num

    @property
    def special_token_ids(self) -> dict[str, int]:
        """Return LayoutDiffusion special-token ids."""
        return {
            "START": self.vocab["START"],
            "END": self.vocab["END"],
            "UNK": self.vocab["UNK"],
            "PAD": self.vocab["PAD"],
            "|": self.vocab["|"],
        }

    @property
    def pad_token_id(self) -> int:
        """Return the padding token id."""
        return self.vocab["PAD"]

    @property
    def mask_token_id(self) -> int:
        """Return the mask token id."""
        return self.vocab_size - 1

    @property
    def label_token_offset(self) -> int:
        """Return the first label-token id."""
        return 5

    @property
    def num_labels(self) -> int:
        """Return the dataset label count."""
        return len(self.id2label)

    @property
    def coordinate_token_offset(self) -> int:
        """Return the first coordinate-token id."""
        return self.label_token_offset + self.num_labels

    @property
    def max_token_length(self) -> int:
        """Return the full LayoutDiffusion token sequence length."""
        return self.seq_length

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

    @property
    def type_classes(self) -> int:
        """Return the number of reference type classes."""
        return self.vocab_size - 1 - self.num_coordinate_bins - 5

    def default_vocab(self) -> dict[str, int]:
        """Build the default LayoutDiffusion vocabulary for the dataset."""
        vocab = {"START": 0, "END": 1, "UNK": 2, "PAD": 3, "|": 4}
        for label in default_id2label(self.dataset_name).values():
            vocab[label] = len(vocab)
        for coord in range(self.num_coordinate_bins):
            vocab[str(coord)] = len(vocab)
        vocab["MASK"] = len(vocab)
        return vocab

    def default_element_prior(self) -> list[float]:
        """Return the reference unconditional element-count prior."""
        if self.dataset_name == str(DatasetName.publaynet):
            return [
                0.00321776,
                0.03342678,
                0.04233181,
                0.04218409,
                0.05404355,
                0.07231605,
                0.08247029,
                0.0905211,
                0.0949399,
                0.0959322,
                0.08953522,
                0.07810608,
                0.0619627,
                0.04775897,
                0.03585776,
                0.0261788,
                0.018812,
                0.01404317,
                0.00972071,
                0.00664104,
            ]
        return [
            0.04849498,
            0.03704171,
            0.0534486,
            0.06045308,
            0.06354515,
            0.07585032,
            0.08045687,
            0.0644917,
            0.05676153,
            0.05742412,
            0.05471067,
            0.04944153,
            0.04552912,
            0.04190068,
            0.04426705,
            0.0387455,
            0.03533792,
            0.03167792,
            0.02997413,
            0.0304474,
        ]

special_token_ids property

special_token_ids: dict[str, int]

Return LayoutDiffusion special-token ids.

pad_token_id property

pad_token_id: int

Return the padding token id.

mask_token_id property

mask_token_id: int

Return the mask token id.

label_token_offset property

label_token_offset: int

Return the first label-token id.

num_labels property

num_labels: int

Return the dataset label count.

coordinate_token_offset property

coordinate_token_offset: int

Return the first coordinate-token id.

max_token_length property

max_token_length: int

Return the full LayoutDiffusion token sequence length.

label2id property

label2id: dict[str, int]

Return inverse public label mapping.

type_classes property

type_classes: int

Return the number of reference type classes.

__init__

__init__(
    *,
    dataset_name: DatasetName | str = DatasetName.rico25,
    id2label: dict[int | str, str] | None = None,
    vocab: dict[str, int] | None = None,
    seq_length: int = 121,
    max_num_elements: int = 20,
    num_coordinate_bins: int = 128,
    diffusion_steps: int = 200,
    noise_schedule: str = "gaussian_refine_pow2.5",
    num_channels: int = 128,
    bert_config_name: str = "bert-base-uncased",
    max_position_embeddings: int = 512,
    hidden_size: int = 768,
    num_hidden_layers: int = 12,
    num_attention_heads: int = 12,
    intermediate_size: int = 3072,
    dropout: float = 0.1,
    training_mode: str = "discrete",
    vocab_size: int | None = None,
    refine_start_step: int | None = None,
    type_start_step: int = 160,
    element_count_prior: list[float] | None = None,
    pow_num: float = 2.5,
    mul_num: float = 12.4,
) -> None

Initialize LayoutDiffusion configuration.

Source code in models/layoutdiffusion/src/layoutdiffusion/configuration_layoutdiffusion.py
 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
@register_to_config
def __init__(
    self,
    *,
    dataset_name: DatasetName | str = DatasetName.rico25,
    id2label: dict[int | str, str] | None = None,
    vocab: dict[str, int] | None = None,
    seq_length: int = 121,
    max_num_elements: int = 20,
    num_coordinate_bins: int = 128,
    diffusion_steps: int = 200,
    noise_schedule: str = "gaussian_refine_pow2.5",
    num_channels: int = 128,
    bert_config_name: str = "bert-base-uncased",
    max_position_embeddings: int = 512,
    hidden_size: int = 768,
    num_hidden_layers: int = 12,
    num_attention_heads: int = 12,
    intermediate_size: int = 3072,
    dropout: float = 0.1,
    training_mode: str = "discrete",
    vocab_size: int | None = None,
    refine_start_step: int | None = None,
    type_start_step: int = 160,
    element_count_prior: list[float] | None = None,
    pow_num: float = 2.5,
    mul_num: float = 12.4,
) -> None:
    """Initialize LayoutDiffusion configuration."""
    self.dataset_name = str(normalize_dataset_name(dataset_name))
    raw_id2label = id2label or default_id2label(self.dataset_name)
    self.id2label = {int(k): v for k, v in raw_id2label.items()}

    self.vocab = vocab or self.default_vocab()
    self.seq_length = seq_length
    self.max_num_elements = max_num_elements
    self.num_coordinate_bins = num_coordinate_bins

    self.diffusion_steps = diffusion_steps
    self.noise_schedule = noise_schedule
    self.num_channels = num_channels
    self.bert_config_name = bert_config_name

    self.max_position_embeddings = max_position_embeddings
    self.hidden_size = hidden_size
    self.num_hidden_layers = num_hidden_layers
    self.num_attention_heads = num_attention_heads
    self.intermediate_size = intermediate_size
    self.dropout = dropout

    self.training_mode = training_mode
    self.vocab_size = vocab_size or len(self.vocab)
    self.refine_start_step = refine_start_step or (
        60 if self.dataset_name == str(DatasetName.publaynet) else 50
    )
    self.type_start_step = type_start_step
    self.element_count_prior = element_count_prior or self.default_element_prior()
    self.pow_num = pow_num
    self.mul_num = mul_num

default_vocab

default_vocab() -> dict[str, int]

Build the default LayoutDiffusion vocabulary for the dataset.

Source code in models/layoutdiffusion/src/layoutdiffusion/configuration_layoutdiffusion.py
159
160
161
162
163
164
165
166
167
def default_vocab(self) -> dict[str, int]:
    """Build the default LayoutDiffusion vocabulary for the dataset."""
    vocab = {"START": 0, "END": 1, "UNK": 2, "PAD": 3, "|": 4}
    for label in default_id2label(self.dataset_name).values():
        vocab[label] = len(vocab)
    for coord in range(self.num_coordinate_bins):
        vocab[str(coord)] = len(vocab)
    vocab["MASK"] = len(vocab)
    return vocab

default_element_prior

default_element_prior() -> list[float]

Return the reference unconditional element-count prior.

Source code in models/layoutdiffusion/src/layoutdiffusion/configuration_layoutdiffusion.py
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
def default_element_prior(self) -> list[float]:
    """Return the reference unconditional element-count prior."""
    if self.dataset_name == str(DatasetName.publaynet):
        return [
            0.00321776,
            0.03342678,
            0.04233181,
            0.04218409,
            0.05404355,
            0.07231605,
            0.08247029,
            0.0905211,
            0.0949399,
            0.0959322,
            0.08953522,
            0.07810608,
            0.0619627,
            0.04775897,
            0.03585776,
            0.0261788,
            0.018812,
            0.01404317,
            0.00972071,
            0.00664104,
        ]
    return [
        0.04849498,
        0.03704171,
        0.0534486,
        0.06045308,
        0.06354515,
        0.07585032,
        0.08045687,
        0.0644917,
        0.05676153,
        0.05742412,
        0.05471067,
        0.04944153,
        0.04552912,
        0.04190068,
        0.04426705,
        0.0387455,
        0.03533792,
        0.03167792,
        0.02997413,
        0.0304474,
    ]

conversion

Conversion helpers for original LayoutDiffusion checkpoints.

find_ema_checkpoint

find_ema_checkpoint(
    checkpoint_dir: str | Path,
    checkpoint_name: str | None = None,
) -> Path

Find an EMA checkpoint file in an original checkpoint directory.

Parameters:

Name Type Description Default
checkpoint_dir str | Path

Original checkpoint directory.

required
checkpoint_name str | None

Optional explicit checkpoint filename.

None

Returns:

Type Description
Path

Path to the selected checkpoint.

Raises:

Type Description
FileNotFoundError

If no checkpoint exists.

Source code in models/layoutdiffusion/src/layoutdiffusion/conversion.py
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
def find_ema_checkpoint(
    checkpoint_dir: str | Path, checkpoint_name: str | None = None
) -> Path:
    """Find an EMA checkpoint file in an original checkpoint directory.

    Args:
        checkpoint_dir: Original checkpoint directory.
        checkpoint_name: Optional explicit checkpoint filename.

    Returns:
        Path to the selected checkpoint.

    Raises:
        FileNotFoundError: If no checkpoint exists.
    """
    root = Path(checkpoint_dir)
    if checkpoint_name is not None:
        path = root / checkpoint_name
        if not path.exists():
            raise FileNotFoundError(path)

        return path
    matches = sorted(root.glob("ema_0.9999_*.pt"))
    if not matches:
        raise FileNotFoundError(f"No ema_0.9999_*.pt checkpoint under {root}")

    return matches[-1]

validate_checkpoint_artifacts

validate_checkpoint_artifacts(
    checkpoint_dir: str | Path,
) -> dict[str, Path]

Validate required original checkpoint artifacts.

Parameters:

Name Type Description Default
checkpoint_dir str | Path

Original checkpoint directory.

required

Returns:

Type Description
dict[str, Path]

Mapping from artifact name to path.

Raises:

Type Description
FileNotFoundError

If a required artifact is missing.

Source code in models/layoutdiffusion/src/layoutdiffusion/conversion.py
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
def validate_checkpoint_artifacts(checkpoint_dir: str | Path) -> dict[str, Path]:
    """Validate required original checkpoint artifacts.

    Args:
        checkpoint_dir: Original checkpoint directory.

    Returns:
        Mapping from artifact name to path.

    Raises:
        FileNotFoundError: If a required artifact is missing.
    """
    root = Path(checkpoint_dir)
    artifacts = {name: root / name for name in REQUIRED_ARTIFACTS}
    missing = [str(path) for path in artifacts.values() if not path.exists()]
    if missing:
        raise FileNotFoundError(", ".join(missing))

    artifacts["checkpoint"] = find_ema_checkpoint(root)
    return artifacts

config_from_original

config_from_original(
    checkpoint_dir: str | Path, *, dataset_name: str
) -> LayoutDiffusionConfig

Build LayoutDiffusionConfig from original JSON files.

Parameters:

Name Type Description Default
checkpoint_dir str | Path

Original checkpoint directory.

required
dataset_name str

Canonical dataset name.

required

Returns:

Type Description
LayoutDiffusionConfig

Converted configuration.

Source code in models/layoutdiffusion/src/layoutdiffusion/conversion.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
def config_from_original(
    checkpoint_dir: str | Path,
    *,
    dataset_name: str,
) -> LayoutDiffusionConfig:
    """Build ``LayoutDiffusionConfig`` from original JSON files.

    Args:
        checkpoint_dir: Original checkpoint directory.
        dataset_name: Canonical dataset name.

    Returns:
        Converted configuration.
    """
    artifacts = validate_checkpoint_artifacts(checkpoint_dir)
    args = json.loads(artifacts["training_args.json"].read_text(encoding="utf-8"))
    vocab_size = int(args.get("vocab_size", 0))
    vocab = json.loads(artifacts["vocab.json"].read_text(encoding="utf-8"))
    vocab = {str(k): int(v) for k, v in vocab.items()}
    if "MASK" not in vocab and vocab_size:
        vocab["MASK"] = vocab_size - 1
    return LayoutDiffusionConfig(
        dataset_name=dataset_name,
        vocab=vocab,
        vocab_size=vocab_size or len(vocab),
        seq_length=int(args.get("seq_length") or 121),
        diffusion_steps=int(args.get("diffusion_steps", 200)),
        noise_schedule=str(args.get("noise_schedule", "gaussian_refine_pow2.5")),
        num_channels=int(args.get("num_channels", 128)),
        training_mode=str(args.get("training_mode", "discrete")),
    )

remap_transformer_state_dict

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

Remap original EMA keys to the new transformer module.

Parameters:

Name Type Description Default
state_dict dict[str, Shaped[Tensor, '...']]

Original checkpoint state dict.

required

Returns:

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

Remapped state dict with module. prefixes removed.

Source code in models/layoutdiffusion/src/layoutdiffusion/conversion.py
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
def remap_transformer_state_dict(
    state_dict: dict[str, Shaped[torch.Tensor, "..."]],
) -> dict[str, Shaped[torch.Tensor, "..."]]:
    """Remap original EMA keys to the new transformer module.

    Args:
        state_dict: Original checkpoint state dict.

    Returns:
        Remapped state dict with ``module.`` prefixes removed.
    """
    remapped = {}
    for key, value in state_dict.items():
        new_key = key.removeprefix("module.")
        if new_key in IGNORED_CHECKPOINT_KEYS:
            continue
        remapped[new_key] = value
    return remapped

load_original_state_dict

load_original_state_dict(
    checkpoint_path: str | Path,
) -> dict[str, Shaped[torch.Tensor, "..."]]

Load an original PyTorch checkpoint on CPU.

Source code in models/layoutdiffusion/src/layoutdiffusion/conversion.py
126
127
128
129
130
131
132
133
134
135
def load_original_state_dict(
    checkpoint_path: str | Path,
) -> dict[str, Shaped[torch.Tensor, "..."]]:
    """Load an original PyTorch checkpoint on CPU."""
    raw = torch.load(checkpoint_path, map_location="cpu")
    if isinstance(raw, dict) and all(isinstance(v, torch.Tensor) for v in raw.values()):
        return raw
    if isinstance(raw, dict) and "state_dict" in raw:
        return raw["state_dict"]
    raise TypeError(f"Unsupported checkpoint format: {checkpoint_path}")

labels

LayoutDiffusion label vocabulary compatibility helpers.

layoutdiffusion_labels_for_dataset

layoutdiffusion_labels_for_dataset(
    dataset_name: DatasetName | str,
) -> tuple[str, ...]

Return LayoutDiffusion label strings in checkpoint order.

Parameters:

Name Type Description Default
dataset_name DatasetName | str

Dataset name or alias.

required

Returns:

Type Description
tuple[str, ...]

Ordered checkpoint label names.

Raises:

Type Description
ValueError

If the dataset is unsupported.

Examples:

>>> layoutdiffusion_labels_for_dataset("publaynet")[0]
'text'
Source code in models/layoutdiffusion/src/layoutdiffusion/labels.py
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
def layoutdiffusion_labels_for_dataset(
    dataset_name: DatasetName | str,
) -> tuple[str, ...]:
    """Return LayoutDiffusion label strings in checkpoint order.

    Args:
        dataset_name: Dataset name or alias.

    Returns:
        Ordered checkpoint label names.

    Raises:
        ValueError: If the dataset is unsupported.

    Examples:
        >>> layoutdiffusion_labels_for_dataset("publaynet")[0]
        'text'
    """
    dataset = normalize_dataset_name(dataset_name)
    if dataset is DatasetName.rico25:
        return LAYOUTDIFFUSION_RICO25_LABELS
    if dataset is DatasetName.publaynet:
        return LAYOUTDIFFUSION_PUBLAYNET_LABELS
    raise ValueError(f"Unsupported LayoutDiffusion dataset_name: {dataset_name}")

default_id2label

default_id2label(
    dataset_name: DatasetName | str,
) -> dict[int, str]

Return the public id-to-label mapping for LayoutDiffusion.

Source code in models/layoutdiffusion/src/layoutdiffusion/labels.py
71
72
73
def default_id2label(dataset_name: DatasetName | str) -> dict[int, str]:
    """Return the public id-to-label mapping for LayoutDiffusion."""
    return dict(enumerate(layoutdiffusion_labels_for_dataset(dataset_name)))

normalize_layoutdiffusion_label

normalize_layoutdiffusion_label(label: str) -> str

Normalize public spelling to the internal vocabulary spelling.

Parameters:

Name Type Description Default
label str

Label spelling from a public dataset or checkpoint.

required

Returns:

Type Description
str

LayoutDiffusion checkpoint spelling.

Source code in models/layoutdiffusion/src/layoutdiffusion/labels.py
76
77
78
79
80
81
82
83
84
85
def normalize_layoutdiffusion_label(label: str) -> str:
    """Normalize public spelling to the internal vocabulary spelling.

    Args:
        label: Label spelling from a public dataset or checkpoint.

    Returns:
        LayoutDiffusion checkpoint spelling.
    """
    return label.replace(" ", "_")

label_to_public_id

label_to_public_id(
    dataset_name: DatasetName | str, label: str
) -> int

Map a checkpoint label string to a dataset-local public id.

Source code in models/layoutdiffusion/src/layoutdiffusion/labels.py
88
89
90
91
92
def label_to_public_id(dataset_name: DatasetName | str, label: str) -> int:
    """Map a checkpoint label string to a dataset-local public id."""
    labels = layoutdiffusion_labels_for_dataset(dataset_name)
    normalized = normalize_layoutdiffusion_label(label)
    return labels.index(normalized)

public_id_to_label

public_id_to_label(
    dataset_name: DatasetName | str, label_id: int
) -> str

Map a public dataset-local label id to a checkpoint label string.

Source code in models/layoutdiffusion/src/layoutdiffusion/labels.py
95
96
97
def public_id_to_label(dataset_name: DatasetName | str, label_id: int) -> str:
    """Map a public dataset-local label id to a checkpoint label string."""
    return layoutdiffusion_labels_for_dataset(dataset_name)[int(label_id)]

modeling_layoutdiffusion

Transformer denoiser for converted LayoutDiffusion checkpoints.

LayoutDiffusionTransformerOutput dataclass

Bases: BaseOutput

Output returned by LayoutDiffusionTransformer.

Source code in models/layoutdiffusion/src/layoutdiffusion/modeling_layoutdiffusion.py
19
20
21
22
23
@dataclass
class LayoutDiffusionTransformerOutput(BaseOutput):
    """Output returned by ``LayoutDiffusionTransformer``."""

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

LayoutDiffusionTransformer

Bases: ModelMixin, ConfigMixin

BERT-encoder denoiser for LayoutDiffusion token sequences.

Parameters:

Name Type Description Default
vocab_size int

Full tokenizer vocabulary size including mask.

required
num_channels int

OpenAI timestep embedding dimension.

128
hidden_size int

BERT hidden size.

768
num_hidden_layers int

Number of BERT encoder layers.

12
num_attention_heads int

Number of attention heads.

12
intermediate_size int

BERT feed-forward size.

3072
dropout float

Hidden dropout probability.

0.1
max_position_embeddings int

Position embedding count.

512
constrained str | None

Optional reference constraint mode.

None

Examples:

>>> model = LayoutDiffusionTransformer(
...     vocab_size=16, hidden_size=32, num_channels=8,
...     num_hidden_layers=1, num_attention_heads=4, intermediate_size=64,
... )
>>> out = model(torch.zeros(2, 5, dtype=torch.long), torch.zeros(2, dtype=torch.long))
>>> out.logits.shape
torch.Size([2, 15, 5])
Source code in models/layoutdiffusion/src/layoutdiffusion/modeling_layoutdiffusion.py
 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
167
168
169
170
171
172
173
174
175
class LayoutDiffusionTransformer(ModelMixin, ConfigMixin):
    """BERT-encoder denoiser for LayoutDiffusion token sequences.

    Args:
        vocab_size: Full tokenizer vocabulary size including mask.
        num_channels: OpenAI timestep embedding dimension.
        hidden_size: BERT hidden size.
        num_hidden_layers: Number of BERT encoder layers.
        num_attention_heads: Number of attention heads.
        intermediate_size: BERT feed-forward size.
        dropout: Hidden dropout probability.
        max_position_embeddings: Position embedding count.
        constrained: Optional reference constraint mode.

    Examples:
        >>> model = LayoutDiffusionTransformer(
        ...     vocab_size=16, hidden_size=32, num_channels=8,
        ...     num_hidden_layers=1, num_attention_heads=4, intermediate_size=64,
        ... )
        >>> out = model(torch.zeros(2, 5, dtype=torch.long), torch.zeros(2, dtype=torch.long))
        >>> out.logits.shape
        torch.Size([2, 15, 5])
    """

    config_name = "transformer_config.json"

    position_ids: Int[torch.Tensor, "1 max_positions"]

    @register_to_config
    def __init__(
        self,
        *,
        vocab_size: int,
        num_channels: int = 128,
        hidden_size: int = 768,
        num_hidden_layers: int = 12,
        num_attention_heads: int = 12,
        intermediate_size: int = 3072,
        dropout: float = 0.1,
        max_position_embeddings: int = 512,
        constrained: str | None = None,
    ) -> None:
        """Initialize the transformer."""
        super().__init__()
        config = BertConfig(
            hidden_size=hidden_size,
            num_hidden_layers=num_hidden_layers,
            num_attention_heads=num_attention_heads,
            intermediate_size=intermediate_size,
            hidden_dropout_prob=dropout,
            attention_probs_dropout_prob=dropout,
            max_position_embeddings=max_position_embeddings,
        )
        self.constrained = constrained
        self.in_channels = 768
        self.model_channels = num_channels
        self.out_channels = vocab_size - 1
        self.word_embedding = nn.Embedding(vocab_size, self.in_channels)
        time_embed_dim = num_channels * 4
        self.time_embed = nn.Sequential(
            nn.Linear(num_channels, time_embed_dim),
            nn.SiLU(),
            nn.Linear(time_embed_dim, hidden_size),
        )
        self.input_up_proj = nn.Sequential(
            nn.Linear(self.in_channels, hidden_size),
            nn.Tanh(),
            nn.Linear(hidden_size, hidden_size),
        )
        self.input_transformers = BertEncoder(config)
        self.register_buffer(
            "position_ids",
            torch.arange(max_position_embeddings).expand((1, -1)),
            persistent=False,
        )
        self.position_embeddings = nn.Embedding(max_position_embeddings, hidden_size)
        self.LayerNorm = nn.LayerNorm(hidden_size, eps=config.layer_norm_eps)
        self.dropout_layer = nn.Dropout(dropout)
        self.output_down_proj = nn.Sequential(
            nn.Linear(hidden_size, hidden_size),
            nn.Tanh(),
            nn.Linear(hidden_size, self.out_channels),
        )

    def get_embeds(
        self, input_ids: Int[torch.Tensor, "batch tokens"]
    ) -> Float[torch.Tensor, "batch tokens channels"]:
        """Return token embeddings for parity diagnostics."""
        return self.word_embedding(input_ids)

    def forward(
        self,
        input_ids: Int[torch.Tensor, "batch tokens"],
        timesteps: Int[torch.Tensor, "batch"],
        condition_ids: Int[torch.Tensor, "batch tokens"] | None = None,
        condition_type: str | None = None,
        return_dict: bool = True,
    ) -> (
        LayoutDiffusionTransformerOutput
        | tuple[Float[torch.Tensor, "batch vocab tokens"]]
    ):
        """Predict start-token logits for a reverse diffusion step.

        Args:
            input_ids: Current token ids shaped ``(B, L)``.
            timesteps: Diffusion timestep per batch item.
            condition_ids: Optional internal condition token ids.
            condition_type: Optional condition mode.
            return_dict: Whether to return a dataclass output.

        Returns:
            Logits shaped ``(B, vocab_size - 1, L)``.
        """
        x = input_ids
        if condition_ids is not None and condition_type == "label":
            mask = condition_ids.le(self.out_channels - 129).unsqueeze(-1)
            hidden = self.word_embedding(condition_ids) * mask + self.word_embedding(
                x
            ) * (~mask)
        elif condition_ids is not None and condition_type == "completion":
            keep = torch.tensor(
                [1] * 6 + [0] * (condition_ids.shape[1] - 6),
                device=x.device,
                dtype=torch.bool,
            ).expand(condition_ids.shape[0], -1)
            hidden = self.word_embedding(condition_ids) * keep.unsqueeze(
                -1
            ) + self.word_embedding(x) * (~keep).unsqueeze(-1)
        else:
            hidden = self.word_embedding(x)
        emb = self.time_embed(
            get_timestep_embedding(
                timesteps,
                self.model_channels,
                flip_sin_to_cos=True,
                downscale_freq_shift=0,
            ).to(hidden)
        )
        seq_length = hidden.size(1)
        position_ids = self.position_ids[:, :seq_length]
        inputs = self.input_up_proj(hidden)
        inputs = inputs + self.position_embeddings(position_ids) + emb.unsqueeze(1)
        inputs = self.dropout_layer(self.LayerNorm(inputs))
        encoded = self.input_transformers(inputs).last_hidden_state
        logits = rearrange(self.output_down_proj(encoded), "b l c -> b c l").type(
            hidden.dtype
        )
        if not return_dict:
            return (logits,)
        return LayoutDiffusionTransformerOutput(logits=logits)

__init__

__init__(
    *,
    vocab_size: int,
    num_channels: int = 128,
    hidden_size: int = 768,
    num_hidden_layers: int = 12,
    num_attention_heads: int = 12,
    intermediate_size: int = 3072,
    dropout: float = 0.1,
    max_position_embeddings: int = 512,
    constrained: str | None = None,
) -> None

Initialize the transformer.

Source code in models/layoutdiffusion/src/layoutdiffusion/modeling_layoutdiffusion.py
 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
@register_to_config
def __init__(
    self,
    *,
    vocab_size: int,
    num_channels: int = 128,
    hidden_size: int = 768,
    num_hidden_layers: int = 12,
    num_attention_heads: int = 12,
    intermediate_size: int = 3072,
    dropout: float = 0.1,
    max_position_embeddings: int = 512,
    constrained: str | None = None,
) -> None:
    """Initialize the transformer."""
    super().__init__()
    config = BertConfig(
        hidden_size=hidden_size,
        num_hidden_layers=num_hidden_layers,
        num_attention_heads=num_attention_heads,
        intermediate_size=intermediate_size,
        hidden_dropout_prob=dropout,
        attention_probs_dropout_prob=dropout,
        max_position_embeddings=max_position_embeddings,
    )
    self.constrained = constrained
    self.in_channels = 768
    self.model_channels = num_channels
    self.out_channels = vocab_size - 1
    self.word_embedding = nn.Embedding(vocab_size, self.in_channels)
    time_embed_dim = num_channels * 4
    self.time_embed = nn.Sequential(
        nn.Linear(num_channels, time_embed_dim),
        nn.SiLU(),
        nn.Linear(time_embed_dim, hidden_size),
    )
    self.input_up_proj = nn.Sequential(
        nn.Linear(self.in_channels, hidden_size),
        nn.Tanh(),
        nn.Linear(hidden_size, hidden_size),
    )
    self.input_transformers = BertEncoder(config)
    self.register_buffer(
        "position_ids",
        torch.arange(max_position_embeddings).expand((1, -1)),
        persistent=False,
    )
    self.position_embeddings = nn.Embedding(max_position_embeddings, hidden_size)
    self.LayerNorm = nn.LayerNorm(hidden_size, eps=config.layer_norm_eps)
    self.dropout_layer = nn.Dropout(dropout)
    self.output_down_proj = nn.Sequential(
        nn.Linear(hidden_size, hidden_size),
        nn.Tanh(),
        nn.Linear(hidden_size, self.out_channels),
    )

get_embeds

get_embeds(
    input_ids: Int[Tensor, "batch tokens"],
) -> Float[torch.Tensor, "batch tokens channels"]

Return token embeddings for parity diagnostics.

Source code in models/layoutdiffusion/src/layoutdiffusion/modeling_layoutdiffusion.py
110
111
112
113
114
def get_embeds(
    self, input_ids: Int[torch.Tensor, "batch tokens"]
) -> Float[torch.Tensor, "batch tokens channels"]:
    """Return token embeddings for parity diagnostics."""
    return self.word_embedding(input_ids)

forward

forward(
    input_ids: Int[Tensor, "batch tokens"],
    timesteps: Int[Tensor, "batch"],
    condition_ids: Int[Tensor, "batch tokens"]
    | None = None,
    condition_type: str | None = None,
    return_dict: bool = True,
) -> (
    LayoutDiffusionTransformerOutput
    | tuple[Float[torch.Tensor, "batch vocab tokens"]]
)

Predict start-token logits for a reverse diffusion step.

Parameters:

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

Current token ids shaped (B, L).

required
timesteps Int[Tensor, 'batch']

Diffusion timestep per batch item.

required
condition_ids Int[Tensor, 'batch tokens'] | None

Optional internal condition token ids.

None
condition_type str | None

Optional condition mode.

None
return_dict bool

Whether to return a dataclass output.

True

Returns:

Type Description
LayoutDiffusionTransformerOutput | tuple[Float[Tensor, 'batch vocab tokens']]

Logits shaped (B, vocab_size - 1, L).

Source code in models/layoutdiffusion/src/layoutdiffusion/modeling_layoutdiffusion.py
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
def forward(
    self,
    input_ids: Int[torch.Tensor, "batch tokens"],
    timesteps: Int[torch.Tensor, "batch"],
    condition_ids: Int[torch.Tensor, "batch tokens"] | None = None,
    condition_type: str | None = None,
    return_dict: bool = True,
) -> (
    LayoutDiffusionTransformerOutput
    | tuple[Float[torch.Tensor, "batch vocab tokens"]]
):
    """Predict start-token logits for a reverse diffusion step.

    Args:
        input_ids: Current token ids shaped ``(B, L)``.
        timesteps: Diffusion timestep per batch item.
        condition_ids: Optional internal condition token ids.
        condition_type: Optional condition mode.
        return_dict: Whether to return a dataclass output.

    Returns:
        Logits shaped ``(B, vocab_size - 1, L)``.
    """
    x = input_ids
    if condition_ids is not None and condition_type == "label":
        mask = condition_ids.le(self.out_channels - 129).unsqueeze(-1)
        hidden = self.word_embedding(condition_ids) * mask + self.word_embedding(
            x
        ) * (~mask)
    elif condition_ids is not None and condition_type == "completion":
        keep = torch.tensor(
            [1] * 6 + [0] * (condition_ids.shape[1] - 6),
            device=x.device,
            dtype=torch.bool,
        ).expand(condition_ids.shape[0], -1)
        hidden = self.word_embedding(condition_ids) * keep.unsqueeze(
            -1
        ) + self.word_embedding(x) * (~keep).unsqueeze(-1)
    else:
        hidden = self.word_embedding(x)
    emb = self.time_embed(
        get_timestep_embedding(
            timesteps,
            self.model_channels,
            flip_sin_to_cos=True,
            downscale_freq_shift=0,
        ).to(hidden)
    )
    seq_length = hidden.size(1)
    position_ids = self.position_ids[:, :seq_length]
    inputs = self.input_up_proj(hidden)
    inputs = inputs + self.position_embeddings(position_ids) + emb.unsqueeze(1)
    inputs = self.dropout_layer(self.LayerNorm(inputs))
    encoded = self.input_transformers(inputs).last_hidden_state
    logits = rearrange(self.output_down_proj(encoded), "b l c -> b c l").type(
        hidden.dtype
    )
    if not return_dict:
        return (logits,)
    return LayoutDiffusionTransformerOutput(logits=logits)

pipeline_layoutdiffusion

Diffusers pipeline for converted LayoutDiffusion checkpoints.

LayoutDiffusionOutputDict

Bases: TypedDict

Dictionary form of LayoutDiffusion public output.

Source code in models/layoutdiffusion/src/layoutdiffusion/pipeline_layoutdiffusion.py
43
44
45
46
47
48
49
50
51
52
class LayoutDiffusionOutputDict(TypedDict, total=False):
    """Dictionary form of LayoutDiffusion public output."""

    bbox: Float[torch.Tensor, "batch elements 4"]
    labels: Int[torch.Tensor, "batch elements"]
    mask: Bool[torch.Tensor, "batch elements"]
    id2label: dict[int, str]
    sequences: Int[torch.Tensor, "batch tokens"] | None
    trajectory: list[Int[torch.Tensor, "batch tokens"]] | None
    intermediates: dict[str, str] | None

LayoutDiffusionPipeline

Bases: DiffusionPipeline

Generate layouts with a converted LayoutDiffusion pipeline.

Parameters:

Name Type Description Default
transformer LayoutDiffusionTransformer

LayoutDiffusion transformer denoiser.

required
scheduler LayoutDiffusionScheduler

Categorical diffusion scheduler.

required
tokenizer LayoutDiffusionTokenizer

LayoutDiffusion layout tokenizer.

required
processor LayoutDiffusionProcessor | None

Optional processor.

None

Examples:

>>> from layoutdiffusion import LayoutDiffusionConfig, LayoutDiffusionTokenizer
>>> from layoutdiffusion import LayoutDiffusionScheduler, LayoutDiffusionTransformer
>>> from layoutdiffusion.sampling import LayoutDiffusionSamplingConfig
>>> cfg = LayoutDiffusionConfig(dataset_name="publaynet", hidden_size=32, num_hidden_layers=1, num_attention_heads=4, intermediate_size=64, num_channels=8)
>>> tok = LayoutDiffusionTokenizer(cfg)
>>> pipe = LayoutDiffusionPipeline(
...     LayoutDiffusionTransformer(vocab_size=cfg.vocab_size, hidden_size=32, num_hidden_layers=1, num_attention_heads=4, intermediate_size=64, num_channels=8),
...     LayoutDiffusionScheduler(vocab_size=cfg.vocab_size, mask_token_id=cfg.mask_token_id, type_classes=cfg.type_classes, num_train_timesteps=2),
...     tok,
... )
>>> pipe(batch_size=1, seed=0, sampling=LayoutDiffusionSamplingConfig(num_inference_steps=1)).bbox.shape[-1]
4
Source code in models/layoutdiffusion/src/layoutdiffusion/pipeline_layoutdiffusion.py
 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
class LayoutDiffusionPipeline(DiffusionPipeline):
    """Generate layouts with a converted LayoutDiffusion pipeline.

    Args:
        transformer: LayoutDiffusion transformer denoiser.
        scheduler: Categorical diffusion scheduler.
        tokenizer: LayoutDiffusion layout tokenizer.
        processor: Optional processor.

    Examples:
        >>> from layoutdiffusion import LayoutDiffusionConfig, LayoutDiffusionTokenizer
        >>> from layoutdiffusion import LayoutDiffusionScheduler, LayoutDiffusionTransformer
        >>> from layoutdiffusion.sampling import LayoutDiffusionSamplingConfig
        >>> cfg = LayoutDiffusionConfig(dataset_name="publaynet", hidden_size=32, num_hidden_layers=1, num_attention_heads=4, intermediate_size=64, num_channels=8)
        >>> tok = LayoutDiffusionTokenizer(cfg)
        >>> pipe = LayoutDiffusionPipeline(
        ...     LayoutDiffusionTransformer(vocab_size=cfg.vocab_size, hidden_size=32, num_hidden_layers=1, num_attention_heads=4, intermediate_size=64, num_channels=8),
        ...     LayoutDiffusionScheduler(vocab_size=cfg.vocab_size, mask_token_id=cfg.mask_token_id, type_classes=cfg.type_classes, num_train_timesteps=2),
        ...     tok,
        ... )
        >>> pipe(batch_size=1, seed=0, sampling=LayoutDiffusionSamplingConfig(num_inference_steps=1)).bbox.shape[-1]
        4
    """

    model_cpu_offload_seq = "transformer"

    def __init__(
        self,
        transformer: LayoutDiffusionTransformer,
        scheduler: LayoutDiffusionScheduler,
        tokenizer: LayoutDiffusionTokenizer,
        processor: LayoutDiffusionProcessor | None = None,
    ) -> None:
        """Initialize and register pipeline modules."""
        super().__init__()
        self.register_modules(
            transformer=transformer,
            scheduler=scheduler,
            tokenizer=tokenizer,
        )
        self.tokenizer = tokenizer
        self.processor = processor or LayoutDiffusionProcessor(tokenizer)
        self.transformer.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,
        output_type: Literal["dataclass", "dict"] = "dataclass",
        return_intermediates: bool = False,
        sampling: LayoutDiffusionSamplingConfig,
        **model_kwargs: str | int | float | bool | None,
    ) -> LayoutGenerationOutput | LayoutDiffusionOutputDict:
        """Run LayoutDiffusion generation.

        Args:
            batch_size: Number of layouts for unconditional generation.
            seed: Seed used only when ``generator`` is omitted.
            generator: Optional torch generator. Takes precedence over ``seed``.
            condition_type: Canonical condition type or supported alias.
            labels: Optional conditional labels.
            bbox: Optional conditional boxes.
            mask: Optional conditional valid mask.
            num_elements: Optional element counts.
            box_format: Input box format.
            normalized: Whether conditional boxes are normalized.
            canvas_size: Pixel canvas size for unnormalized inputs.
            num_inference_steps: Optional shortened inference steps.
            output_type: ``"dataclass"`` or ``"dict"``.
            return_intermediates: Whether to include trajectories.
            sampling: Sampling config.
            **model_kwargs: Reserved compatibility kwargs.

        Returns:
            Layout output dataclass or dictionary.

        Raises:
            ValueError: If ``output_type`` is unsupported.
        """
        _ = 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)
        processed = self.processor(
            bbox=bbox,
            labels=labels,
            mask=mask,
            num_elements=num_elements,
            box_format=box_format,
            normalized=normalized,
            canvas_size=canvas_size,
        )
        condition_input = processed.get("input_ids")
        processed_labels = None if labels is None else torch.as_tensor(labels)
        counts = processed.get("num_elements")
        condition = build_condition(
            self.tokenizer,
            condition_type=canonical,
            input_ids=condition_input,
            labels=processed_labels,
            num_elements=counts,
        )
        if condition is not None and condition.input_ids is not None:
            batch_size = condition.input_ids.shape[0]
        if condition is not None and canonical is ConditionType.refinement:
            if condition.input_ids is None:
                raise ValueError("refinement condition is missing input_ids")

            start_ids = condition.input_ids.to(self.device)
        else:
            start_ids = self.tokenizer.build_initial_tokens(
                batch_size=batch_size,
                num_elements=counts,
                labels=processed_labels,
                condition_type=str(canonical),
                generator=generator,
                device=self.device,
            )
        sample = index_to_log_onehot(start_ids, self.scheduler.config.vocab_size)
        start_step = None if condition is None else condition.start_step
        self.scheduler.set_timesteps(
            sampling.num_inference_steps or num_inference_steps,
            start_step=start_step,
            device=self.device,
        )
        trajectory = [] if return_intermediates else None
        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.transformer(
                input_ids=input_ids,
                timesteps=timestep_batch,
                condition_ids=None
                if condition is None or condition.input_ids is None
                else condition.input_ids.to(self.device),
                condition_type=None if condition is None else str(condition.type),
            ).logits
            out = self.scheduler.step(
                logits,
                timestep_batch,
                sample,
                sampling=sampling,
                condition=condition,
                generator=generator,
            )
            sample = out.prev_sample
            if trajectory is not None:
                trajectory.append(log_onehot_to_index(sample).detach().cpu())
        output = self._decode_final_sample(
            sample=sample,
            trajectory=trajectory,
            condition_type=str(canonical),
            return_intermediates=return_intermediates,
        )
        return self._coerce_output(output=output, output_type=output_type)

    def _decode_final_sample(
        self,
        *,
        sample: Float[torch.Tensor, "batch vocab tokens"],
        trajectory: list[Int[torch.Tensor, "batch tokens"]] | None,
        condition_type: str,
        return_intermediates: bool,
    ) -> LayoutGenerationOutput:
        """Decode final token logits into the public layout output schema."""
        sequences = log_onehot_to_index(sample).detach().cpu()
        layout = self.tokenizer.decode_layout(sequences)
        metadata = {"condition_type": condition_type} if return_intermediates else None
        return LayoutGenerationOutput(
            bbox=layout["bbox"],
            labels=layout["labels"],
            mask=layout["mask"],
            id2label=self.tokenizer.config.id2label,
            sequences=sequences if return_intermediates else None,
            trajectory=trajectory,
            intermediates=metadata,
        )

    @staticmethod
    def _coerce_output(
        *,
        output: LayoutGenerationOutput,
        output_type: Literal["dataclass", "dict"],
    ) -> LayoutGenerationOutput | LayoutDiffusionOutputDict:
        """Return the requested output container."""
        if output_type == "dict":
            return cast(LayoutDiffusionOutputDict, 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: LayoutDiffusionPipelineKwarg
    ) -> None:
        """Save a Diffusers pipeline directory."""
        super().save_pretrained(save_directory, **kwargs)

    @classmethod
    def from_pretrained(
        cls,
        pretrained_model_name_or_path: str | Path,
        **kwargs: LayoutDiffusionPipelineKwarg,
    ) -> "LayoutDiffusionPipeline":
        """Load a LayoutDiffusion pipeline and rebuild its processor."""
        tokenizer = kwargs.pop("tokenizer", None)
        if tokenizer is None:
            tokenizer = LayoutDiffusionTokenizer.from_pretrained(
                pretrained_model_name_or_path
            )
        pipe = super().from_pretrained(
            pretrained_model_name_or_path,
            tokenizer=tokenizer,
            **kwargs,
        )
        pipe.processor = LayoutDiffusionProcessor(pipe.tokenizer)
        return pipe

__init__

__init__(
    transformer: LayoutDiffusionTransformer,
    scheduler: LayoutDiffusionScheduler,
    tokenizer: LayoutDiffusionTokenizer,
    processor: LayoutDiffusionProcessor | None = None,
) -> None

Initialize and register pipeline modules.

Source code in models/layoutdiffusion/src/layoutdiffusion/pipeline_layoutdiffusion.py
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
def __init__(
    self,
    transformer: LayoutDiffusionTransformer,
    scheduler: LayoutDiffusionScheduler,
    tokenizer: LayoutDiffusionTokenizer,
    processor: LayoutDiffusionProcessor | None = None,
) -> None:
    """Initialize and register pipeline modules."""
    super().__init__()
    self.register_modules(
        transformer=transformer,
        scheduler=scheduler,
        tokenizer=tokenizer,
    )
    self.tokenizer = tokenizer
    self.processor = processor or LayoutDiffusionProcessor(tokenizer)
    self.transformer.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,
    output_type: Literal["dataclass", "dict"] = "dataclass",
    return_intermediates: bool = False,
    sampling: LayoutDiffusionSamplingConfig,
    **model_kwargs: str | int | float | bool | None,
) -> LayoutGenerationOutput | LayoutDiffusionOutputDict

Run LayoutDiffusion generation.

Parameters:

Name Type Description Default
batch_size int

Number of layouts for unconditional generation.

1
seed int | None

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 alias.

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

Optional conditional labels.

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

Optional conditional boxes.

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

Optional conditional valid mask.

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

Optional element counts.

None
box_format BoxFormat | str

Input box format.

xywh
normalized bool

Whether conditional boxes are normalized.

True
canvas_size tuple[int, int] | None

Pixel canvas size for unnormalized inputs.

None
num_inference_steps int | None

Optional shortened inference steps.

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

"dataclass" or "dict".

'dataclass'
return_intermediates bool

Whether to include trajectories.

False
sampling LayoutDiffusionSamplingConfig

Sampling config.

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

Reserved compatibility kwargs.

{}

Returns:

Type Description
LayoutGenerationOutput | LayoutDiffusionOutputDict

Layout output dataclass or dictionary.

Raises:

Type Description
ValueError

If output_type is unsupported.

Source code in models/layoutdiffusion/src/layoutdiffusion/pipeline_layoutdiffusion.py
 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
@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,
    output_type: Literal["dataclass", "dict"] = "dataclass",
    return_intermediates: bool = False,
    sampling: LayoutDiffusionSamplingConfig,
    **model_kwargs: str | int | float | bool | None,
) -> LayoutGenerationOutput | LayoutDiffusionOutputDict:
    """Run LayoutDiffusion generation.

    Args:
        batch_size: Number of layouts for unconditional generation.
        seed: Seed used only when ``generator`` is omitted.
        generator: Optional torch generator. Takes precedence over ``seed``.
        condition_type: Canonical condition type or supported alias.
        labels: Optional conditional labels.
        bbox: Optional conditional boxes.
        mask: Optional conditional valid mask.
        num_elements: Optional element counts.
        box_format: Input box format.
        normalized: Whether conditional boxes are normalized.
        canvas_size: Pixel canvas size for unnormalized inputs.
        num_inference_steps: Optional shortened inference steps.
        output_type: ``"dataclass"`` or ``"dict"``.
        return_intermediates: Whether to include trajectories.
        sampling: Sampling config.
        **model_kwargs: Reserved compatibility kwargs.

    Returns:
        Layout output dataclass or dictionary.

    Raises:
        ValueError: If ``output_type`` is unsupported.
    """
    _ = 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)
    processed = self.processor(
        bbox=bbox,
        labels=labels,
        mask=mask,
        num_elements=num_elements,
        box_format=box_format,
        normalized=normalized,
        canvas_size=canvas_size,
    )
    condition_input = processed.get("input_ids")
    processed_labels = None if labels is None else torch.as_tensor(labels)
    counts = processed.get("num_elements")
    condition = build_condition(
        self.tokenizer,
        condition_type=canonical,
        input_ids=condition_input,
        labels=processed_labels,
        num_elements=counts,
    )
    if condition is not None and condition.input_ids is not None:
        batch_size = condition.input_ids.shape[0]
    if condition is not None and canonical is ConditionType.refinement:
        if condition.input_ids is None:
            raise ValueError("refinement condition is missing input_ids")

        start_ids = condition.input_ids.to(self.device)
    else:
        start_ids = self.tokenizer.build_initial_tokens(
            batch_size=batch_size,
            num_elements=counts,
            labels=processed_labels,
            condition_type=str(canonical),
            generator=generator,
            device=self.device,
        )
    sample = index_to_log_onehot(start_ids, self.scheduler.config.vocab_size)
    start_step = None if condition is None else condition.start_step
    self.scheduler.set_timesteps(
        sampling.num_inference_steps or num_inference_steps,
        start_step=start_step,
        device=self.device,
    )
    trajectory = [] if return_intermediates else None
    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.transformer(
            input_ids=input_ids,
            timesteps=timestep_batch,
            condition_ids=None
            if condition is None or condition.input_ids is None
            else condition.input_ids.to(self.device),
            condition_type=None if condition is None else str(condition.type),
        ).logits
        out = self.scheduler.step(
            logits,
            timestep_batch,
            sample,
            sampling=sampling,
            condition=condition,
            generator=generator,
        )
        sample = out.prev_sample
        if trajectory is not None:
            trajectory.append(log_onehot_to_index(sample).detach().cpu())
    output = self._decode_final_sample(
        sample=sample,
        trajectory=trajectory,
        condition_type=str(canonical),
        return_intermediates=return_intermediates,
    )
    return self._coerce_output(output=output, output_type=output_type)

save_pretrained

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

Save a Diffusers pipeline directory.

Source code in models/layoutdiffusion/src/layoutdiffusion/pipeline_layoutdiffusion.py
275
276
277
278
279
def save_pretrained(
    self, save_directory: str | Path, **kwargs: LayoutDiffusionPipelineKwarg
) -> None:
    """Save a Diffusers pipeline directory."""
    super().save_pretrained(save_directory, **kwargs)

from_pretrained classmethod

from_pretrained(
    pretrained_model_name_or_path: str | Path,
    **kwargs: LayoutDiffusionPipelineKwarg,
) -> "LayoutDiffusionPipeline"

Load a LayoutDiffusion pipeline and rebuild its processor.

Source code in models/layoutdiffusion/src/layoutdiffusion/pipeline_layoutdiffusion.py
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
@classmethod
def from_pretrained(
    cls,
    pretrained_model_name_or_path: str | Path,
    **kwargs: LayoutDiffusionPipelineKwarg,
) -> "LayoutDiffusionPipeline":
    """Load a LayoutDiffusion pipeline and rebuild its processor."""
    tokenizer = kwargs.pop("tokenizer", None)
    if tokenizer is None:
        tokenizer = LayoutDiffusionTokenizer.from_pretrained(
            pretrained_model_name_or_path
        )
    pipe = super().from_pretrained(
        pretrained_model_name_or_path,
        tokenizer=tokenizer,
        **kwargs,
    )
    pipe.processor = LayoutDiffusionProcessor(pipe.tokenizer)
    return pipe

processing_layoutdiffusion

Input processor for LayoutDiffusion pipelines.

LayoutDiffusionProcessor

Bases: ProcessorMixin

Normalize public layout inputs and delegate tokenization.

Parameters:

Name Type Description Default
tokenizer LayoutDiffusionTokenizer

LayoutDiffusion tokenizer.

required

Examples:

>>> from layoutdiffusion import LayoutDiffusionConfig, LayoutDiffusionTokenizer
>>> cfg = LayoutDiffusionConfig(dataset_name="publaynet")
>>> proc = LayoutDiffusionProcessor(LayoutDiffusionTokenizer(cfg))
>>> proc.num_elements_to_tensor(2, batch_size=1).tolist()
[2]
Source code in models/layoutdiffusion/src/layoutdiffusion/processing_layoutdiffusion.py
 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
class LayoutDiffusionProcessor(ProcessorMixin):
    """Normalize public layout inputs and delegate tokenization.

    Args:
        tokenizer: LayoutDiffusion tokenizer.

    Examples:
        >>> from layoutdiffusion import LayoutDiffusionConfig, LayoutDiffusionTokenizer
        >>> cfg = LayoutDiffusionConfig(dataset_name="publaynet")
        >>> proc = LayoutDiffusionProcessor(LayoutDiffusionTokenizer(cfg))
        >>> proc.num_elements_to_tensor(2, batch_size=1).tolist()
        [2]
    """

    attributes = ["tokenizer"]
    tokenizer_class = "LayoutDiffusionTokenizer"

    def __init__(self, tokenizer: LayoutDiffusionTokenizer) -> None:
        """Initialize the processor."""
        self.tokenizer = tokenizer

    def __call__(
        self,
        *,
        bbox: Float[torch.Tensor, "batch elements 4"]
        | Float[np.ndarray, "batch elements 4"]
        | Sequence[ArrayLikeInput]
        | None = None,
        labels: Int[torch.Tensor, "batch elements"]
        | Int[np.ndarray, "batch elements"]
        | 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,
        return_tensors: Literal["pt"] = "pt",
    ) -> dict[str, Shaped[torch.Tensor, "..."]]:
        """Process layout tensors for conditional generation.

        Args:
            bbox: Optional layout boxes.
            labels: Optional labels.
            mask: Optional valid-element mask.
            num_elements: Optional element counts.
            box_format: Format of ``bbox``.
            normalized: Whether ``bbox`` is normalized.
            canvas_size: Pixel canvas size.
            return_tensors: Only ``"pt"`` is supported.

        Returns:
            Tokenizer output or element-count tensor.

        Raises:
            ValueError: If required conditional tensors are missing.
        """
        if return_tensors != "pt":
            raise ValueError(
                "LayoutDiffusionProcessor only supports return_tensors='pt'"
            )

        if bbox is None or labels is None:
            batch_size = 1
            if isinstance(num_elements, list):
                batch_size = len(num_elements)
            counts = self.num_elements_to_tensor(num_elements, batch_size=batch_size)
            return {} if counts is None else {"num_elements": counts}
        return self.tokenizer(
            bbox=torch.as_tensor(bbox),
            labels=torch.as_tensor(labels),
            mask=None if mask is None else torch.as_tensor(mask),
            box_format=box_format,
            normalized=normalized,
            canvas_size=canvas_size,
        )

    def num_elements_to_tensor(
        self,
        num_elements: int | list[int] | Int[torch.Tensor, "batch"] | None,
        *,
        batch_size: int,
    ) -> Int[torch.Tensor, "batch"] | None:
        """Convert public element counts to a tensor."""
        if num_elements is None:
            return None
        counts = torch.as_tensor(num_elements, dtype=torch.long)
        if counts.ndim == 0:
            counts = counts.expand(batch_size)
        return counts

__init__

__init__(tokenizer: LayoutDiffusionTokenizer) -> None

Initialize the processor.

Source code in models/layoutdiffusion/src/layoutdiffusion/processing_layoutdiffusion.py
35
36
37
def __init__(self, tokenizer: LayoutDiffusionTokenizer) -> None:
    """Initialize the processor."""
    self.tokenizer = tokenizer

__call__

__call__(
    *,
    bbox: Float[Tensor, "batch elements 4"]
    | Float[ndarray, "batch elements 4"]
    | Sequence[ArrayLikeInput]
    | None = None,
    labels: Int[Tensor, "batch elements"]
    | Int[ndarray, "batch elements"]
    | 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,
    return_tensors: Literal["pt"] = "pt",
) -> dict[str, Shaped[torch.Tensor, "..."]]

Process layout tensors for conditional generation.

Parameters:

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

Optional layout boxes.

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

Optional labels.

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

Optional valid-element mask.

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

Optional element counts.

None
box_format BoxFormat | str

Format of bbox.

xywh
normalized bool

Whether bbox is normalized.

True
canvas_size tuple[int, int] | None

Pixel canvas size.

None
return_tensors Literal['pt']

Only "pt" is supported.

'pt'

Returns:

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

Tokenizer output or element-count tensor.

Raises:

Type Description
ValueError

If required conditional tensors are missing.

Source code in models/layoutdiffusion/src/layoutdiffusion/processing_layoutdiffusion.py
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
def __call__(
    self,
    *,
    bbox: Float[torch.Tensor, "batch elements 4"]
    | Float[np.ndarray, "batch elements 4"]
    | Sequence[ArrayLikeInput]
    | None = None,
    labels: Int[torch.Tensor, "batch elements"]
    | Int[np.ndarray, "batch elements"]
    | 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,
    return_tensors: Literal["pt"] = "pt",
) -> dict[str, Shaped[torch.Tensor, "..."]]:
    """Process layout tensors for conditional generation.

    Args:
        bbox: Optional layout boxes.
        labels: Optional labels.
        mask: Optional valid-element mask.
        num_elements: Optional element counts.
        box_format: Format of ``bbox``.
        normalized: Whether ``bbox`` is normalized.
        canvas_size: Pixel canvas size.
        return_tensors: Only ``"pt"`` is supported.

    Returns:
        Tokenizer output or element-count tensor.

    Raises:
        ValueError: If required conditional tensors are missing.
    """
    if return_tensors != "pt":
        raise ValueError(
            "LayoutDiffusionProcessor only supports return_tensors='pt'"
        )

    if bbox is None or labels is None:
        batch_size = 1
        if isinstance(num_elements, list):
            batch_size = len(num_elements)
        counts = self.num_elements_to_tensor(num_elements, batch_size=batch_size)
        return {} if counts is None else {"num_elements": counts}
    return self.tokenizer(
        bbox=torch.as_tensor(bbox),
        labels=torch.as_tensor(labels),
        mask=None if mask is None else torch.as_tensor(mask),
        box_format=box_format,
        normalized=normalized,
        canvas_size=canvas_size,
    )

num_elements_to_tensor

num_elements_to_tensor(
    num_elements: int
    | list[int]
    | Int[Tensor, "batch"]
    | None,
    *,
    batch_size: int,
) -> Int[torch.Tensor, "batch"] | None

Convert public element counts to a tensor.

Source code in models/layoutdiffusion/src/layoutdiffusion/processing_layoutdiffusion.py
 98
 99
100
101
102
103
104
105
106
107
108
109
110
def num_elements_to_tensor(
    self,
    num_elements: int | list[int] | Int[torch.Tensor, "batch"] | None,
    *,
    batch_size: int,
) -> Int[torch.Tensor, "batch"] | None:
    """Convert public element counts to a tensor."""
    if num_elements is None:
        return None
    counts = torch.as_tensor(num_elements, dtype=torch.long)
    if counts.ndim == 0:
        counts = counts.expand(batch_size)
    return counts

sampling

Sampling configuration for LayoutDiffusion.

LayoutDiffusionSamplingName

Bases: StrEnum

Supported LayoutDiffusion sampling modes.

Source code in models/layoutdiffusion/src/layoutdiffusion/sampling.py
 9
10
11
12
13
class LayoutDiffusionSamplingName(StrEnum):
    """Supported LayoutDiffusion sampling modes."""

    gumbel = auto()
    argmax = auto()

LayoutDiffusionSamplingConfig dataclass

Runtime sampling options for the reverse diffusion loop.

Source code in models/layoutdiffusion/src/layoutdiffusion/sampling.py
16
17
18
19
20
21
22
23
@dataclass(frozen=True)
class LayoutDiffusionSamplingConfig:
    """Runtime sampling options for the reverse diffusion loop."""

    name: LayoutDiffusionSamplingName | str = LayoutDiffusionSamplingName.gumbel
    num_inference_steps: int | None = None
    skip_step: int = 0
    multistep: bool = False

scheduling_layoutdiffusion

Categorical Gaussian-refine scheduler for LayoutDiffusion.

LayoutDiffusionSchedulerOutput dataclass

Bases: BaseOutput

Output of one LayoutDiffusion scheduler step.

Source code in models/layoutdiffusion/src/layoutdiffusion/scheduling_layoutdiffusion.py
28
29
30
31
32
33
34
@dataclass
class LayoutDiffusionSchedulerOutput(BaseOutput):
    """Output of one LayoutDiffusion scheduler step."""

    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

LayoutDiffusionScheduler

Bases: SchedulerMixin, ConfigMixin

Diffusers scheduler for LayoutDiffusion categorical transitions.

Parameters:

Name Type Description Default
num_train_timesteps int

Number of training diffusion steps.

200
vocab_size int

Full vocabulary size including mask.

required
mask_token_id int

Mask token id.

required
type_classes int

Number of label/type classes.

required
num_special_tokens int

Number of leading special tokens.

5
num_coordinate_bins int

Coordinate vocabulary size.

128
noise_schedule str

Reference schedule name.

'gaussian_refine_pow2.5'
pow_num float

Gaussian transition exponent.

2.5
mul_num float

Gaussian transition multiplier.

12.4
type_start_step int

Label-conditioned start step.

160
rico_refine_start_step int

RICO refinement start step.

50
publaynet_refine_start_step int

PubLayNet refinement start step.

60

Examples:

>>> scheduler = LayoutDiffusionScheduler(vocab_size=139, mask_token_id=138, type_classes=5)
>>> scheduler.q_mats.shape[-2:]
torch.Size([128, 128])
Source code in models/layoutdiffusion/src/layoutdiffusion/scheduling_layoutdiffusion.py
 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
class LayoutDiffusionScheduler(SchedulerMixin, ConfigMixin):
    """Diffusers scheduler for LayoutDiffusion categorical transitions.

    Args:
        num_train_timesteps: Number of training diffusion steps.
        vocab_size: Full vocabulary size including mask.
        mask_token_id: Mask token id.
        type_classes: Number of label/type classes.
        num_special_tokens: Number of leading special tokens.
        num_coordinate_bins: Coordinate vocabulary size.
        noise_schedule: Reference schedule name.
        pow_num: Gaussian transition exponent.
        mul_num: Gaussian transition multiplier.
        type_start_step: Label-conditioned start step.
        rico_refine_start_step: RICO refinement start step.
        publaynet_refine_start_step: PubLayNet refinement start step.

    Examples:
        >>> scheduler = LayoutDiffusionScheduler(vocab_size=139, mask_token_id=138, type_classes=5)
        >>> scheduler.q_mats.shape[-2:]
        torch.Size([128, 128])
    """

    config_name = "scheduler_config.json"
    order = 1

    @register_to_config
    def __init__(
        self,
        *,
        num_train_timesteps: int = 200,
        vocab_size: int,
        mask_token_id: int,
        type_classes: int,
        num_special_tokens: int = 5,
        num_coordinate_bins: int = 128,
        noise_schedule: str = "gaussian_refine_pow2.5",
        pow_num: float = 2.5,
        mul_num: float = 12.4,
        type_start_step: int = 160,
        rico_refine_start_step: int = 50,
        publaynet_refine_start_step: int = 60,
    ) -> None:
        """Initialize scheduler buffers."""
        self.num_timesteps = num_train_timesteps
        self.vocab_size = vocab_size
        self.mask_token_id = mask_token_id
        self.type_classes = type_classes
        self.num_special_tokens = num_special_tokens
        self.num_coordinate_bins = num_coordinate_bins
        self.noise_schedule = noise_schedule
        self.pow_num = pow_num
        self.mul_num = mul_num
        self.type_start_step = type_start_step
        self.rico_refine_start_step = rico_refine_start_step
        self.publaynet_refine_start_step = publaynet_refine_start_step
        self.timesteps = torch.arange(num_train_timesteps - 1, -1, -1)
        self._init_buffers()

    @classmethod
    def from_layout_config(
        cls, config: LayoutDiffusionConfig
    ) -> LayoutDiffusionScheduler:
        """Build a scheduler from serialized LayoutDiffusion settings.

        Args:
            config: LayoutDiffusion model/tokenizer/scheduler settings.

        Returns:
            A scheduler initialized with the config's diffusion parameters.
        """
        return cls(
            num_train_timesteps=config.diffusion_steps,
            vocab_size=config.vocab_size,
            mask_token_id=config.mask_token_id,
            type_classes=config.type_classes,
            num_coordinate_bins=config.num_coordinate_bins,
            noise_schedule=config.noise_schedule,
            pow_num=config.pow_num,
            mul_num=config.mul_num,
            type_start_step=config.type_start_step,
        )

    def set_timesteps(
        self,
        num_inference_steps: int | None = None,
        *,
        start_step: int | None = None,
        device: torch.device | None = None,
    ) -> None:
        """Set reverse diffusion timesteps."""
        start = self.num_timesteps if start_step is None else start_step
        steps = num_inference_steps or start
        values = [int(i * start / steps) for i in range(steps - 1, -1, -1)]
        if values[-1] != 0:
            values.append(0)
        self.timesteps = torch.tensor(values, dtype=torch.long, device=device)

    def predict_start(
        self,
        logits: Float[torch.Tensor, "batch vocab_without_mask tokens"],
        batch_size: int,
        seq_length: int,
    ) -> Float[torch.Tensor, "batch vocab tokens"]:
        """Append the fixed mask logit and clamp model log probabilities."""
        log_pred = torch.log_softmax(logits.double(), dim=1).float()
        zero = (
            torch.zeros(
                batch_size, 1, seq_length, device=logits.device, dtype=logits.dtype
            )
            - 70
        )
        return torch.cat((log_pred, zero), dim=1).clamp(-70.0, 0.0)

    def 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"]:
        """Compute ``q(x_t | x_{t-1})``."""
        matrix = self._transition_matrix(t, cumulative=False, device=log_x_t.device)
        return matrix.matmul(log_x_t.exp()).clamp(min=1e-30).log()

    def q_pred(
        self,
        log_x_start: Float[torch.Tensor, "batch vocab tokens"],
        t: Int[torch.Tensor, "batch"],
    ) -> Float[torch.Tensor, "batch vocab tokens"]:
        """Compute cumulative ``q(x_t | x_0)``."""
        t = (t + (self.num_timesteps + 1)) % (self.num_timesteps + 1)
        matrix = self._transition_matrix(t, cumulative=True, device=log_x_start.device)
        return matrix.matmul(log_x_start.exp()).clamp(min=1e-30).log()

    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 ``p_theta(x_{t-1} | x_t)`` from predicted start logits."""
        if t.min().item() < 0 or t.max().item() >= self.num_timesteps:
            raise ValueError("timestep outside scheduler range")

        batch_size = log_x_start.shape[0]
        onehot_x_t = log_onehot_to_index(log_x_t)
        mask = onehot_x_t.eq(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_zero_aux = torch.log(log_one + 1.0e-30).expand(-1, -1, -1)

        log_qt = self.q_pred(log_x_t, t)[:, :-1, :]
        log_cumprod_ct = _extract(
            self.log_cumprod_ct.to(t.device), t, log_x_start.shape
        )
        ct_cumprod = torch.cat(
            [
                log_zero_aux.expand(-1, self.num_special_tokens, -1),
                log_cumprod_ct.expand(
                    -1, self.vocab_size - 1 - self.num_special_tokens, -1
                ),
            ],
            dim=1,
        )
        log_qt = (~mask) * log_qt + mask * ct_cumprod

        log_qt_one = self.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.log_ct.to(t.device), t, log_x_start.shape)
        ct_vector = torch.cat(
            [
                log_zero_aux.expand(-1, self.num_special_tokens, -1),
                log_ct.expand(-1, self.vocab_size - 1 - self.num_special_tokens, -1),
            ],
            dim=1,
        )
        ct_vector = torch.cat((ct_vector, 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)
        posterior = self.q_pred(q, t - 1) + log_qt_one
        return posterior.clamp(-70.0, 0.0)

    def step(
        self,
        logits: Float[torch.Tensor, "batch vocab_without_mask tokens"],
        timestep: Int[torch.Tensor, "batch"],
        sample: Float[torch.Tensor, "batch vocab tokens"],
        *,
        sampling: LayoutDiffusionSamplingConfig,
        condition: LayoutDiffusionCondition | None = None,
        generator: torch.Generator | None = None,
    ) -> LayoutDiffusionSchedulerOutput:
        """Run one reverse diffusion step."""
        _ = condition
        log_x_recon = self.predict_start(logits, sample.shape[0], sample.shape[-1])
        model_log_prob = self.q_posterior(log_x_recon, sample, timestep)
        if str(sampling.name) == str(LayoutDiffusionSamplingName.argmax):
            ids = model_log_prob.argmax(dim=1)
            prev = index_to_log_onehot(ids, self.vocab_size)
        else:
            prev = self.log_sample_categorical(model_log_prob, generator=generator)
        return LayoutDiffusionSchedulerOutput(
            prev_sample=prev,
            pred_original_sample=log_x_recon,
            model_log_prob=model_log_prob,
        )

    def log_sample_categorical(
        self,
        logits: Float[torch.Tensor, "batch vocab tokens"],
        *,
        generator: torch.Generator | None = None,
    ) -> Float[torch.Tensor, "batch vocab tokens"]:
        """Sample log one-hot categorical tokens with Gumbel-max."""
        sample = (gumbel_noise_like(logits, generator=generator) + logits).argmax(dim=1)
        return index_to_log_onehot(sample, self.vocab_size)

    def _init_buffers(self) -> None:
        if self.noise_schedule != "gaussian_refine_pow2.5":
            raise NotImplementedError(
                "Only gaussian_refine_pow2.5 LayoutDiffusion schedule is supported"
            )

        at, at1, bt1, bt2, ct, ct1, att, att1, btt1, btt2, ctt, ctt1 = _alpha_schedule(
            self.num_timesteps, type_classes=25
        )
        at1_t = torch.tensor(at1.astype("float64"))
        ct1_t = torch.tensor(ct1.astype("float64"))

        log_at1 = torch.log(at1_t).clamp(-70, 0)
        log_ct1 = torch.log(ct1_t).clamp(-70, 0)

        att1_t = torch.tensor(att1.astype("float64"))
        ctt1_t = torch.tensor(ctt1.astype("float64"))
        log_cumprod_at1 = torch.log(att1_t).clamp(-70, 0)
        log_cumprod_ct1 = torch.log(ctt1_t).clamp(-70, 0)
        log_1_min_ct1 = _log_1_min_a(log_ct1)
        log_1_min_cumprod_ct1 = _log_1_min_a(log_cumprod_ct1)

        self.log_ct1 = log_ct1.float()
        self.log_at1 = log_at1.float()
        self.log_cumprod_at1 = log_cumprod_at1.float()
        self.log_cumprod_ct1 = log_cumprod_ct1.float()
        self.log_1_min_ct1 = log_1_min_ct1.float()
        self.log_1_min_cumprod_ct1 = log_1_min_cumprod_ct1.float()

        at_t = torch.tensor(at.astype("float64"))
        bt1_t = torch.tensor(bt1.astype("float64"))
        bt2_t = torch.tensor(bt2.astype("float64"))
        ct_t = torch.tensor(ct.astype("float64"))

        log_at = torch.log(at_t)
        log_bt1 = torch.log(bt1_t)
        log_bt2 = torch.log(bt2_t)
        log_ct = torch.log(ct_t).clamp(-70, 0)

        att_t = torch.tensor(att.astype("float64"))
        btt1_t = torch.tensor(btt1.astype("float64"))
        btt2_t = torch.tensor(btt2.astype("float64"))
        ctt_t = torch.tensor(ctt.astype("float64"))
        log_cumprod_at = torch.log(att_t)
        log_cumprod_bt1 = torch.log(btt1_t)
        log_cumprod_bt2 = torch.log(btt2_t)
        log_cumprod_ct = torch.log(ctt_t).clamp(-70, 0)
        log_1_min_ct = _log_1_min_a(log_ct)
        log_1_min_cumprod_ct = _log_1_min_a(log_cumprod_ct)

        self.log_at = log_at.float()
        self.log_bt1 = log_bt1.float()
        self.log_bt2 = log_bt2.float()
        self.log_ct = log_ct.float()
        self.log_cumprod_at = log_cumprod_at.float()
        self.log_cumprod_bt1 = log_cumprod_bt1.float()
        self.log_cumprod_bt2 = log_cumprod_bt2.float()
        self.log_cumprod_ct = log_cumprod_ct.float()
        self.log_1_min_ct = log_1_min_ct.float()
        self.log_1_min_cumprod_ct = log_1_min_cumprod_ct.float()

        bt2_t = torch.where(bt2_t == 0.0, bt2_t.max(), bt2_t)
        q_one_step = [
            _gaussian_matrix2(t, bt=bt2_t.pow(2).pow(self.pow_num / 2) * self.mul_num)
            for t in range(self.num_timesteps)
        ]
        q_one_step.append(
            np.ones((self.num_coordinate_bins, self.num_coordinate_bins))
            / (self.num_coordinate_bins**2)
        )
        q_onestep_mats = torch.from_numpy(np.stack(q_one_step, axis=0)).float()
        self.q_onestep_mats = q_onestep_mats
        q_mat = self.q_onestep_mats[0]
        q_mats = [q_mat]
        for t in range(1, self.num_timesteps):
            q_mat = np.tensordot(q_mat, self.q_onestep_mats[t], axes=([1], [0]))
            q_mats.append(q_mat)
        q_mats.append(
            np.ones((self.num_coordinate_bins, self.num_coordinate_bins))
            / (self.num_coordinate_bins**2)
        )
        self.q_mats = torch.from_numpy(np.stack(q_mats, axis=0)).float()

    def _transition_matrix(
        self, t: Int[torch.Tensor, "batch"], *, cumulative: bool, device: torch.device
    ) -> Float[torch.Tensor, "batch vocab vocab"]:
        batch_size = t.shape[0]
        if cumulative:
            log_at = _extract(self.log_cumprod_at.to(device), t, (batch_size, 1, 1))
            log_bt1 = _extract(self.log_cumprod_bt1.to(device), t, (batch_size, 1, 1))
            log_bt2 = _extract(self.log_cumprod_bt2.to(device), t, (batch_size, 1, 1))
            log_ct = _extract(self.log_cumprod_ct.to(device), t, (batch_size, 1, 1))
            log_at1 = _extract(self.log_cumprod_at1.to(device), t, (batch_size, 1, 1))
            log_ct1 = _extract(self.log_cumprod_ct1.to(device), t, (batch_size, 1, 1))
            q_coord = self.q_mats[t.detach().cpu()].to(device)
        else:
            log_at = _extract(self.log_at.to(device), t, (batch_size, 1, 1))
            log_bt1 = _extract(self.log_bt1.to(device), t, (batch_size, 1, 1))
            log_bt2 = _extract(self.log_bt2.to(device), t, (batch_size, 1, 1))
            log_ct = _extract(self.log_ct.to(device), t, (batch_size, 1, 1))
            log_at1 = _extract(self.log_at1.to(device), t, (batch_size, 1, 1))
            log_ct1 = _extract(self.log_ct1.to(device), t, (batch_size, 1, 1))
            q_coord = self.q_onestep_mats[t.detach().cpu()].to(device)
        log_1_min_ct = _log_1_min_a(log_ct)
        log_1_min_ct1 = _log_1_min_a(log_ct1)
        eye_special = torch.eye(self.num_special_tokens, device=device).expand(
            batch_size, -1, -1
        )
        zeros_special_rest = torch.zeros(
            batch_size,
            self.num_special_tokens,
            self.vocab_size - self.num_special_tokens,
            device=device,
        )
        type_eye = (
            torch.eye(self.type_classes, device=device)
            .clamp(min=1e-30)
            .log()
            .expand(batch_size, -1, -1)
        )
        coord_eye = (
            torch.eye(self.num_coordinate_bins, device=device)
            .clamp(min=1e-30)
            .log()
            .expand(batch_size, -1, -1)
        )
        matrix_absorb = torch.cat(
            [
                torch.cat([eye_special, zeros_special_rest], dim=-1),
                torch.cat(
                    [
                        torch.zeros(
                            batch_size,
                            self.type_classes,
                            self.num_special_tokens,
                            device=device,
                        ),
                        log_add_exp(type_eye + log_at1, log_bt1).exp(),
                        torch.zeros(
                            batch_size,
                            self.type_classes,
                            self.vocab_size
                            - self.num_special_tokens
                            - self.type_classes,
                            device=device,
                        ),
                    ],
                    dim=-1,
                ),
                torch.cat(
                    [
                        torch.zeros(
                            batch_size,
                            self.num_coordinate_bins,
                            self.num_special_tokens + self.type_classes,
                            device=device,
                        ),
                        log_add_exp(coord_eye + log_at, log_bt2).exp(),
                        torch.zeros(
                            batch_size, self.num_coordinate_bins, 1, device=device
                        ),
                    ],
                    dim=-1,
                ),
                torch.cat(
                    [
                        torch.zeros(
                            batch_size, 1, self.num_special_tokens, device=device
                        ),
                        log_add_exp(
                            torch.zeros(batch_size, 1, self.type_classes, device=device)
                            .clamp(min=1e-30)
                            .log()
                            + log_1_min_ct1,
                            log_ct1,
                        ).exp(),
                        log_add_exp(
                            torch.zeros(
                                batch_size, 1, self.num_coordinate_bins, device=device
                            )
                            .clamp(min=1e-30)
                            .log()
                            + log_1_min_ct,
                            log_ct,
                        ).exp(),
                        torch.ones(batch_size, 1, 1, device=device),
                    ],
                    dim=-1,
                ),
            ],
            dim=-2,
        )
        matrix_gaussian = matrix_absorb.clone()
        coord_start = self.num_special_tokens + self.type_classes
        matrix_gaussian[
            :,
            coord_start : coord_start + self.num_coordinate_bins,
            coord_start : coord_start + self.num_coordinate_bins,
        ] = q_coord
        early = (t < (self.num_timesteps * 4 // 5)).reshape(batch_size, 1, 1)
        return torch.where(early, matrix_gaussian, matrix_absorb)

__init__

__init__(
    *,
    num_train_timesteps: int = 200,
    vocab_size: int,
    mask_token_id: int,
    type_classes: int,
    num_special_tokens: int = 5,
    num_coordinate_bins: int = 128,
    noise_schedule: str = "gaussian_refine_pow2.5",
    pow_num: float = 2.5,
    mul_num: float = 12.4,
    type_start_step: int = 160,
    rico_refine_start_step: int = 50,
    publaynet_refine_start_step: int = 60,
) -> None

Initialize scheduler buffers.

Source code in models/layoutdiffusion/src/layoutdiffusion/scheduling_layoutdiffusion.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
@register_to_config
def __init__(
    self,
    *,
    num_train_timesteps: int = 200,
    vocab_size: int,
    mask_token_id: int,
    type_classes: int,
    num_special_tokens: int = 5,
    num_coordinate_bins: int = 128,
    noise_schedule: str = "gaussian_refine_pow2.5",
    pow_num: float = 2.5,
    mul_num: float = 12.4,
    type_start_step: int = 160,
    rico_refine_start_step: int = 50,
    publaynet_refine_start_step: int = 60,
) -> None:
    """Initialize scheduler buffers."""
    self.num_timesteps = num_train_timesteps
    self.vocab_size = vocab_size
    self.mask_token_id = mask_token_id
    self.type_classes = type_classes
    self.num_special_tokens = num_special_tokens
    self.num_coordinate_bins = num_coordinate_bins
    self.noise_schedule = noise_schedule
    self.pow_num = pow_num
    self.mul_num = mul_num
    self.type_start_step = type_start_step
    self.rico_refine_start_step = rico_refine_start_step
    self.publaynet_refine_start_step = publaynet_refine_start_step
    self.timesteps = torch.arange(num_train_timesteps - 1, -1, -1)
    self._init_buffers()

from_layout_config classmethod

from_layout_config(
    config: LayoutDiffusionConfig,
) -> LayoutDiffusionScheduler

Build a scheduler from serialized LayoutDiffusion settings.

Parameters:

Name Type Description Default
config LayoutDiffusionConfig

LayoutDiffusion model/tokenizer/scheduler settings.

required

Returns:

Type Description
LayoutDiffusionScheduler

A scheduler initialized with the config's diffusion parameters.

Source code in models/layoutdiffusion/src/layoutdiffusion/scheduling_layoutdiffusion.py
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
@classmethod
def from_layout_config(
    cls, config: LayoutDiffusionConfig
) -> LayoutDiffusionScheduler:
    """Build a scheduler from serialized LayoutDiffusion settings.

    Args:
        config: LayoutDiffusion model/tokenizer/scheduler settings.

    Returns:
        A scheduler initialized with the config's diffusion parameters.
    """
    return cls(
        num_train_timesteps=config.diffusion_steps,
        vocab_size=config.vocab_size,
        mask_token_id=config.mask_token_id,
        type_classes=config.type_classes,
        num_coordinate_bins=config.num_coordinate_bins,
        noise_schedule=config.noise_schedule,
        pow_num=config.pow_num,
        mul_num=config.mul_num,
        type_start_step=config.type_start_step,
    )

set_timesteps

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

Set reverse diffusion timesteps.

Source code in models/layoutdiffusion/src/layoutdiffusion/scheduling_layoutdiffusion.py
120
121
122
123
124
125
126
127
128
129
130
131
132
133
def set_timesteps(
    self,
    num_inference_steps: int | None = None,
    *,
    start_step: int | None = None,
    device: torch.device | None = None,
) -> None:
    """Set reverse diffusion timesteps."""
    start = self.num_timesteps if start_step is None else start_step
    steps = num_inference_steps or start
    values = [int(i * start / steps) for i in range(steps - 1, -1, -1)]
    if values[-1] != 0:
        values.append(0)
    self.timesteps = torch.tensor(values, dtype=torch.long, device=device)

predict_start

predict_start(
    logits: Float[
        Tensor, "batch vocab_without_mask tokens"
    ],
    batch_size: int,
    seq_length: int,
) -> Float[torch.Tensor, "batch vocab tokens"]

Append the fixed mask logit and clamp model log probabilities.

Source code in models/layoutdiffusion/src/layoutdiffusion/scheduling_layoutdiffusion.py
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
def predict_start(
    self,
    logits: Float[torch.Tensor, "batch vocab_without_mask tokens"],
    batch_size: int,
    seq_length: int,
) -> Float[torch.Tensor, "batch vocab tokens"]:
    """Append the fixed mask logit and clamp model log probabilities."""
    log_pred = torch.log_softmax(logits.double(), dim=1).float()
    zero = (
        torch.zeros(
            batch_size, 1, seq_length, device=logits.device, dtype=logits.dtype
        )
        - 70
    )
    return torch.cat((log_pred, zero), dim=1).clamp(-70.0, 0.0)

q_pred_one_timestep

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

Compute q(x_t | x_{t-1}).

Source code in models/layoutdiffusion/src/layoutdiffusion/scheduling_layoutdiffusion.py
151
152
153
154
155
156
157
158
def 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"]:
    """Compute ``q(x_t | x_{t-1})``."""
    matrix = self._transition_matrix(t, cumulative=False, device=log_x_t.device)
    return matrix.matmul(log_x_t.exp()).clamp(min=1e-30).log()

q_pred

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

Compute cumulative q(x_t | x_0).

Source code in models/layoutdiffusion/src/layoutdiffusion/scheduling_layoutdiffusion.py
160
161
162
163
164
165
166
167
168
def q_pred(
    self,
    log_x_start: Float[torch.Tensor, "batch vocab tokens"],
    t: Int[torch.Tensor, "batch"],
) -> Float[torch.Tensor, "batch vocab tokens"]:
    """Compute cumulative ``q(x_t | x_0)``."""
    t = (t + (self.num_timesteps + 1)) % (self.num_timesteps + 1)
    matrix = self._transition_matrix(t, cumulative=True, device=log_x_start.device)
    return matrix.matmul(log_x_start.exp()).clamp(min=1e-30).log()

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 p_theta(x_{t-1} | x_t) from predicted start logits.

Source code in models/layoutdiffusion/src/layoutdiffusion/scheduling_layoutdiffusion.py
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
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 ``p_theta(x_{t-1} | x_t)`` from predicted start logits."""
    if t.min().item() < 0 or t.max().item() >= self.num_timesteps:
        raise ValueError("timestep outside scheduler range")

    batch_size = log_x_start.shape[0]
    onehot_x_t = log_onehot_to_index(log_x_t)
    mask = onehot_x_t.eq(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_zero_aux = torch.log(log_one + 1.0e-30).expand(-1, -1, -1)

    log_qt = self.q_pred(log_x_t, t)[:, :-1, :]
    log_cumprod_ct = _extract(
        self.log_cumprod_ct.to(t.device), t, log_x_start.shape
    )
    ct_cumprod = torch.cat(
        [
            log_zero_aux.expand(-1, self.num_special_tokens, -1),
            log_cumprod_ct.expand(
                -1, self.vocab_size - 1 - self.num_special_tokens, -1
            ),
        ],
        dim=1,
    )
    log_qt = (~mask) * log_qt + mask * ct_cumprod

    log_qt_one = self.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.log_ct.to(t.device), t, log_x_start.shape)
    ct_vector = torch.cat(
        [
            log_zero_aux.expand(-1, self.num_special_tokens, -1),
            log_ct.expand(-1, self.vocab_size - 1 - self.num_special_tokens, -1),
        ],
        dim=1,
    )
    ct_vector = torch.cat((ct_vector, 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)
    posterior = self.q_pred(q, t - 1) + log_qt_one
    return posterior.clamp(-70.0, 0.0)

step

step(
    logits: Float[
        Tensor, "batch vocab_without_mask tokens"
    ],
    timestep: Int[Tensor, "batch"],
    sample: Float[Tensor, "batch vocab tokens"],
    *,
    sampling: LayoutDiffusionSamplingConfig,
    condition: LayoutDiffusionCondition | None = None,
    generator: Generator | None = None,
) -> LayoutDiffusionSchedulerOutput

Run one reverse diffusion step.

Source code in models/layoutdiffusion/src/layoutdiffusion/scheduling_layoutdiffusion.py
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
def step(
    self,
    logits: Float[torch.Tensor, "batch vocab_without_mask tokens"],
    timestep: Int[torch.Tensor, "batch"],
    sample: Float[torch.Tensor, "batch vocab tokens"],
    *,
    sampling: LayoutDiffusionSamplingConfig,
    condition: LayoutDiffusionCondition | None = None,
    generator: torch.Generator | None = None,
) -> LayoutDiffusionSchedulerOutput:
    """Run one reverse diffusion step."""
    _ = condition
    log_x_recon = self.predict_start(logits, sample.shape[0], sample.shape[-1])
    model_log_prob = self.q_posterior(log_x_recon, sample, timestep)
    if str(sampling.name) == str(LayoutDiffusionSamplingName.argmax):
        ids = model_log_prob.argmax(dim=1)
        prev = index_to_log_onehot(ids, self.vocab_size)
    else:
        prev = self.log_sample_categorical(model_log_prob, generator=generator)
    return LayoutDiffusionSchedulerOutput(
        prev_sample=prev,
        pred_original_sample=log_x_recon,
        model_log_prob=model_log_prob,
    )

log_sample_categorical

log_sample_categorical(
    logits: Float[Tensor, "batch vocab tokens"],
    *,
    generator: Generator | None = None,
) -> Float[torch.Tensor, "batch vocab tokens"]

Sample log one-hot categorical tokens with Gumbel-max.

Source code in models/layoutdiffusion/src/layoutdiffusion/scheduling_layoutdiffusion.py
245
246
247
248
249
250
251
252
253
def log_sample_categorical(
    self,
    logits: Float[torch.Tensor, "batch vocab tokens"],
    *,
    generator: torch.Generator | None = None,
) -> Float[torch.Tensor, "batch vocab tokens"]:
    """Sample log one-hot categorical tokens with Gumbel-max."""
    sample = (gumbel_noise_like(logits, generator=generator) + logits).argmax(dim=1)
    return index_to_log_onehot(sample, self.vocab_size)

tokenization_layoutdiffusion

PreTrainedTokenizer for LayoutDiffusion layout token sequences.

LayoutDiffusionTokenizer

Bases: PreTrainedTokenizer

Tokenizer backed by the original LayoutDiffusion vocab.json.

Parameters:

Name Type Description Default
config LayoutDiffusionConfig | Mapping[str, LayoutDiffusionConfigValue] | None

LayoutDiffusion config or serialized config mapping.

None
vocab_file str | Path | None

Optional saved vocabulary file.

None
layout_config_file str | Path | None

Optional saved layout config file.

None
**kwargs LayoutDiffusionConfigValue

Extra PreTrainedTokenizer keyword arguments.

{}

Raises:

Type Description
ValueError

If required tokenizer files are absent.

Examples:

>>> tok = LayoutDiffusionTokenizer(
...     LayoutDiffusionConfig(dataset_name="publaynet")
... )
>>> tok.mask_token
'MASK'
Source code in models/layoutdiffusion/src/layoutdiffusion/tokenization_layoutdiffusion.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
 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
class LayoutDiffusionTokenizer(PreTrainedTokenizer):
    """Tokenizer backed by the original LayoutDiffusion ``vocab.json``.

    Args:
        config: LayoutDiffusion config or serialized config mapping.
        vocab_file: Optional saved vocabulary file.
        layout_config_file: Optional saved layout config file.
        **kwargs: Extra ``PreTrainedTokenizer`` keyword arguments.

    Raises:
        ValueError: If required tokenizer files are absent.

    Examples:
        >>> tok = LayoutDiffusionTokenizer(
        ...     LayoutDiffusionConfig(dataset_name="publaynet")
        ... )
        >>> tok.mask_token
        'MASK'
    """

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

    def __init__(
        self,
        config: LayoutDiffusionConfig
        | Mapping[str, LayoutDiffusionConfigValue]
        | None = None,
        *,
        vocab_file: str | Path | None = None,
        layout_config_file: str | Path | None = None,
        **kwargs: LayoutDiffusionConfigValue,
    ) -> None:
        """Initialize the tokenizer."""
        if isinstance(config, LayoutDiffusionConfig):
            pass
        elif config is None:
            config = self._load_config(
                layout_config_file=layout_config_file,
                kwargs=kwargs,
            )
        else:
            config = _config_from_mapping(config)
        if vocab_file is not None and Path(vocab_file).exists():
            raw_vocab = json.loads(Path(vocab_file).read_text(encoding="utf-8"))
            config.vocab = {str(k): int(v) for k, v in raw_vocab.items()}
            if "MASK" not in config.vocab:
                config.vocab["MASK"] = config.vocab_size - 1
            config.vocab_size = max(config.vocab.values()) + 1
        self.config = config
        self._token_to_id = dict(config.vocab)
        self._id_to_token = {idx: token for token, idx in self._token_to_id.items()}
        super().__init__(
            pad_token=kwargs.pop("pad_token", "PAD"),
            mask_token=kwargs.pop("mask_token", "MASK"),
            unk_token=kwargs.pop("unk_token", "UNK"),
            model_max_length=kwargs.pop("model_max_length", config.max_token_length),
            **kwargs,
        )

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

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

    def _tokenize(self, text: str, **kwargs: str | float | bool | None) -> list[str]:
        """Split a LayoutDiffusion token string on whitespace."""
        _ = kwargs
        return text.strip().split()

    def _convert_token_to_id(self, token: str) -> int:
        """Convert one token string to id."""
        return self._token_to_id.get(token, self.config.special_token_ids["UNK"])

    def _convert_id_to_token(self, index: int) -> str:
        """Convert one token id to string."""
        return self._id_to_token.get(int(index), "UNK")

    def convert_tokens_to_string(self, tokens: list[str]) -> str:
        """Join LayoutDiffusion tokens for debugging or parity fixtures."""
        return " ".join(tokens)

    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,
        box_format: BoxFormat | str = BoxFormat.xywh,
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
    ) -> dict[str, Shaped[torch.Tensor, ...]]:
        """Encode layout tensors into token ids.

        Args:
            bbox: Layout boxes.
            labels: Dataset-local labels.
            mask: Optional valid-element mask.
            box_format: Format of ``bbox``.
            normalized: Whether boxes are already normalized.
            canvas_size: Pixel canvas size for unnormalized boxes.

        Returns:
            Dictionary with ``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),
            box_format=box_format,
            normalized=normalized,
            canvas_size=canvas_size,
        )

    def encode_layout(
        self,
        *,
        bbox: Float[torch.Tensor, "batch elements 4"],
        labels: Int[torch.Tensor, "batch elements"],
        mask: Bool[torch.Tensor, "batch elements"] | None = None,
        box_format: BoxFormat | str = BoxFormat.xywh,
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
    ) -> dict[str, Shaped[torch.Tensor, ...]]:
        """Encode public layout tensors into LayoutDiffusion token ids.

        Args:
            bbox: Boxes shaped ``(B, S, 4)``.
            labels: Labels shaped ``(B, S)``.
            mask: Optional valid mask shaped ``(B, S)``.
            box_format: Input box format.
            normalized: Whether input boxes are normalized.
            canvas_size: Pixel canvas size when ``normalized`` is false.

        Returns:
            Encoded token tensors.

        Raises:
            ValueError: If unnormalized boxes omit ``canvas_size``.
        """
        if bbox.ndim == 2:
            bbox = bbox.unsqueeze(0)
        if labels.ndim == 1:
            labels = labels.unsqueeze(0)
        if mask is None:
            mask = torch.ones_like(labels, dtype=torch.bool)
        elif mask.ndim == 1:
            mask = mask.unsqueeze(0)
        bbox = bbox.float()
        if normalized:
            fmt = normalize_box_format(box_format)
            if fmt is BoxFormat.xywh:
                xywh = clamp_boxes(bbox)
            elif fmt is BoxFormat.ltrb:
                xywh = clamp_boxes(ltrb_to_xywh(bbox))
            else:
                xywh = clamp_boxes(ltwh_to_xywh(bbox))
        else:
            if canvas_size is None:
                raise ValueError("canvas_size is required when normalized=False")

            xywh = normalize_boxes(bbox, canvas_size=canvas_size, box_format=box_format)
        ltrb_ids = (xywh_to_ltrb(xywh).clamp(0.0, 1.0) * 127).round().long()
        batch_size = labels.shape[0]
        input_ids = torch.full(
            (batch_size, self.config.max_token_length),
            self.config.pad_token_id,
            dtype=torch.long,
        )
        input_ids[:, 0] = self.config.special_token_ids["START"]
        for batch_idx in range(batch_size):
            valid_positions = torch.nonzero(
                mask[batch_idx].bool(), as_tuple=False
            ).flatten()
            valid_positions = valid_positions[: self.config.max_num_elements]
            cursor = 1
            for elem_idx, source_idx in enumerate(valid_positions.tolist()):
                if elem_idx > 0:
                    input_ids[batch_idx, cursor] = self.config.special_token_ids["|"]
                    cursor += 1
                label = self.config.id2label[int(labels[batch_idx, source_idx].item())]
                token_ids = [self._token_to_id[label]]
                token_ids.extend(
                    self._token_to_id[str(int(v))]
                    for v in ltrb_ids[batch_idx, source_idx].tolist()
                )
                input_ids[batch_idx, cursor : cursor + 5] = torch.tensor(token_ids)
                cursor += 5
            if cursor < self.config.max_token_length:
                input_ids[batch_idx, cursor] = self.config.special_token_ids["END"]
        attention_mask = input_ids.ne(self.config.pad_token_id)
        return {
            "input_ids": input_ids,
            "attention_mask": attention_mask,
            "mask": mask.bool(),
        }

    def decode_layout(
        self,
        input_ids: Int[torch.Tensor, "batch tokens"],
        *,
        output_box_format: Literal["xywh", "ltrb"] = "xywh",
    ) -> dict[str, Shaped[torch.Tensor, ...]]:
        """Decode token ids into public layout tensors.

        Args:
            input_ids: Token ids shaped ``(B, L)``.
            output_box_format: ``"xywh"`` or ``"ltrb"``.

        Returns:
            Dictionary with ``bbox``, ``labels``, and ``mask``.

        Raises:
            ValueError: If ``output_box_format`` is unsupported.
        """
        if input_ids.ndim == 1:
            input_ids = input_ids.unsqueeze(0)

        batch_boxes = []
        batch_labels = []
        batch_masks = []

        for row in input_ids.cpu().long():
            tokens = [self._convert_id_to_token(int(idx)) for idx in row.tolist()]
            elements = self._parse_elements(tokens)
            boxes = torch.zeros(self.config.max_num_elements, 4, dtype=torch.float32)
            labels = torch.zeros(self.config.max_num_elements, dtype=torch.long)
            masks = torch.zeros(self.config.max_num_elements, dtype=torch.bool)

            for i, element in enumerate(elements[: self.config.max_num_elements]):
                label, *coords = element
                labels[i] = self.config.label2id[label]
                ltrb = torch.tensor([int(v) for v in coords], dtype=torch.float32) / 127
                boxes[i] = ltrb_to_xywh(ltrb) if output_box_format == "xywh" else ltrb
                masks[i] = True

            batch_boxes.append(clamp_boxes(boxes))
            batch_labels.append(labels)
            batch_masks.append(masks)

        if output_box_format not in {"xywh", "ltrb"}:
            raise ValueError(f"Unsupported output_box_format: {output_box_format}")

        return {
            "bbox": torch.stack(batch_boxes, dim=0),
            "labels": torch.stack(batch_labels, dim=0),
            "mask": torch.stack(batch_masks, dim=0),
        }

    def build_initial_tokens(
        self,
        *,
        batch_size: int,
        num_elements: Int[torch.Tensor, "batch"] | list[int] | int | None = None,
        labels: Int[torch.Tensor, "batch elements"] | None = None,
        condition_type: str = "unconditional",
        generator: torch.Generator | None = None,
        device: torch.device | None = None,
    ) -> Int[torch.Tensor, "batch tokens"]:
        """Build the LayoutDiffusion sampling start template.

        Args:
            batch_size: Number of samples.
            num_elements: Optional element counts in ``[1, 20]``.
            labels: Optional labels for label-conditioned generation.
            condition_type: Canonical condition name.
            generator: Optional random generator.
            device: Output device.

        Returns:
            Initial token ids shaped ``(B, 121)``.
        """
        device = device or torch.device("cpu")
        if num_elements is None:
            prior = torch.tensor(
                self.config.element_count_prior, dtype=torch.float32, device=device
            )
            counts = (
                torch.multinomial(
                    prior, batch_size, replacement=True, generator=generator
                )
                + 1
            )
        else:
            counts = torch.as_tensor(num_elements, dtype=torch.long, device=device)
            if counts.ndim == 0:
                counts = counts.expand(batch_size)
        counts = counts.clamp(1, self.config.max_num_elements)
        input_ids = torch.full(
            (batch_size, self.config.max_token_length),
            self.config.pad_token_id,
            dtype=torch.long,
            device=device,
        )
        mask_id = self.config.mask_token_id
        start = self.config.special_token_ids["START"]
        sep = self.config.special_token_ids["|"]
        end = self.config.special_token_ids["END"]

        for batch_idx in range(batch_size):
            n = int(counts[batch_idx].item())
            tokens = [start, mask_id, mask_id, mask_id, mask_id, mask_id]
            for _ in range(n - 1):
                tokens.extend([sep, mask_id, mask_id, mask_id, mask_id, mask_id])
            tokens.append(end)
            input_ids[batch_idx, : len(tokens)] = torch.tensor(tokens, device=device)

        if condition_type == "label" and labels is not None:
            label_ids = torch.as_tensor(labels, dtype=torch.long, device=device)
            for batch_idx in range(batch_size):
                for elem_idx in range(min(label_ids.shape[1], int(counts[batch_idx]))):
                    pos = 1 + elem_idx * 6
                    label = self.config.id2label[int(label_ids[batch_idx, elem_idx])]
                    input_ids[batch_idx, pos] = self._token_to_id[label]

                coord_noise = (
                    torch.randint(
                        self.config.num_coordinate_bins,
                        input_ids.shape,
                        generator=generator,
                        device=device,
                    )
                    + self.config.coordinate_token_offset
                )
                coord_positions = torch.zeros_like(input_ids, dtype=torch.bool)
                for elem_idx in range(self.config.max_num_elements):
                    start_pos = 2 + elem_idx * 6
                    coord_positions[:, start_pos : start_pos + 4] = True
                input_ids = torch.where(
                    coord_positions & input_ids.eq(mask_id), coord_noise, input_ids
                )
        return input_ids

    def token_ids_to_text(
        self, input_ids: Int[torch.Tensor, "batch tokens"]
    ) -> list[str]:
        """Convert token ids to LayoutDiffusion text lines."""
        if input_ids.ndim == 1:
            input_ids = input_ids.unsqueeze(0)
        return [
            " ".join(self._convert_id_to_token(int(idx)) for idx in row.tolist())
            for row in input_ids.cpu().long()
        ]

    def text_to_token_ids(self, lines: list[str]) -> Int[torch.Tensor, "batch tokens"]:
        """Convert LayoutDiffusion text lines into padded token ids."""
        rows = []
        for line in lines:
            tokens = line.strip().split()
            if tokens[:1] != ["START"]:
                tokens = ["START", *tokens]
            if tokens[-1:] != ["END"]:
                tokens = [*tokens, "END"]
            ids = [self._convert_token_to_id(token) for token in tokens]
            ids = ids[: self.config.max_token_length]
            ids.extend(
                [self.config.pad_token_id] * (self.config.max_token_length - len(ids))
            )
            rows.append(torch.tensor(ids, dtype=torch.long))
        return torch.stack(rows, dim=0)

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

        Args:
            save_directory: Target directory.
            filename_prefix: Optional Transformers filename prefix.

        Returns:
            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"
        config_file = save_path / f"{prefix}layout_config.json"
        vocab_file.write_text(
            json.dumps(self._token_to_id, indent=2, sort_keys=True),
            encoding="utf-8",
        )
        data = dict(self.config.config)
        data["id2label"] = {str(k): v for k, v in self.config.id2label.items()}
        data["vocab"] = self._token_to_id
        config_file.write_text(
            json.dumps(data, indent=2, sort_keys=True), encoding="utf-8"
        )
        return (str(vocab_file), str(config_file))

    @classmethod
    def from_pretrained(
        cls,
        path: str | PathLike[str],
        *args: 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: LayoutDiffusionConfigValue,
    ) -> LayoutDiffusionTokenizer:
        """Load tokenizer from a pipeline root or tokenizer directory."""
        load_path = Path(path)
        tokenizer_dir = load_path / "tokenizer"
        if tokenizer_dir.is_dir():
            load_path = tokenizer_dir
        load_kwargs = {
            "cache_dir": cache_dir,
            "force_download": force_download,
            "local_files_only": local_files_only,
            "token": token,
            "revision": revision,
        }
        return super().from_pretrained(
            load_path,
            *args,
            **load_kwargs,
            **kwargs,
        )

    @classmethod
    def _load_config(
        cls,
        *,
        layout_config_file: str | Path | None,
        kwargs: dict[str, LayoutDiffusionConfigValue],
    ) -> LayoutDiffusionConfig:
        layout_config = kwargs.pop("layout_config", None)
        if layout_config is None and layout_config_file is None:
            raise ValueError("LayoutDiffusionTokenizer requires layout_config_file")

        if layout_config is None:
            config_file = Path(cast(str | Path, layout_config_file))
            config_text = config_file.read_text(encoding="utf-8")
            layout_config = json.loads(config_text)
        if not isinstance(layout_config, Mapping):
            raise TypeError("layout_config must be a mapping")

        normalized = {str(key): value for key, value in layout_config.items()}
        return _config_from_mapping(
            cast(Mapping[str, LayoutDiffusionConfigValue], normalized)
        )

    def _parse_elements(self, tokens: list[str]) -> list[list[str]]:
        start = tokens.index("START") if "START" in tokens else -1
        end = tokens.index("END") if "END" in tokens else 0
        if end <= start:
            end = max(
                (i for i, token in enumerate(tokens) if token == "|"), default=end
            )
        payload = tokens[start + 1 : end] if end > start else []
        groups: list[list[str]] = []
        current: list[str] = []
        for token in payload:
            if token == "|":
                if current:
                    groups.append(current)
                    current = []
            else:
                current.append(token)
        if current:
            groups.append(current)
        elements = []
        for group in groups:
            if len(group) >= 5 and all(token.isdigit() for token in group[-4:]):
                label = group[-5]
                if label in self.config.label2id:
                    elements.append([label, *group[-4:]])
        return elements

vocab_size property

vocab_size: int

Return full vocabulary size.

__init__

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

Initialize the tokenizer.

Source code in models/layoutdiffusion/src/layoutdiffusion/tokenization_layoutdiffusion.py
 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
def __init__(
    self,
    config: LayoutDiffusionConfig
    | Mapping[str, LayoutDiffusionConfigValue]
    | None = None,
    *,
    vocab_file: str | Path | None = None,
    layout_config_file: str | Path | None = None,
    **kwargs: LayoutDiffusionConfigValue,
) -> None:
    """Initialize the tokenizer."""
    if isinstance(config, LayoutDiffusionConfig):
        pass
    elif config is None:
        config = self._load_config(
            layout_config_file=layout_config_file,
            kwargs=kwargs,
        )
    else:
        config = _config_from_mapping(config)
    if vocab_file is not None and Path(vocab_file).exists():
        raw_vocab = json.loads(Path(vocab_file).read_text(encoding="utf-8"))
        config.vocab = {str(k): int(v) for k, v in raw_vocab.items()}
        if "MASK" not in config.vocab:
            config.vocab["MASK"] = config.vocab_size - 1
        config.vocab_size = max(config.vocab.values()) + 1
    self.config = config
    self._token_to_id = dict(config.vocab)
    self._id_to_token = {idx: token for token, idx in self._token_to_id.items()}
    super().__init__(
        pad_token=kwargs.pop("pad_token", "PAD"),
        mask_token=kwargs.pop("mask_token", "MASK"),
        unk_token=kwargs.pop("unk_token", "UNK"),
        model_max_length=kwargs.pop("model_max_length", config.max_token_length),
        **kwargs,
    )

get_vocab

get_vocab() -> dict[str, int]

Return a copy of token-to-id vocabulary.

Source code in models/layoutdiffusion/src/layoutdiffusion/tokenization_layoutdiffusion.py
110
111
112
def get_vocab(self) -> dict[str, int]:
    """Return a copy of token-to-id vocabulary."""
    return dict(self._token_to_id)

convert_tokens_to_string

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

Join LayoutDiffusion tokens for debugging or parity fixtures.

Source code in models/layoutdiffusion/src/layoutdiffusion/tokenization_layoutdiffusion.py
127
128
129
def convert_tokens_to_string(self, tokens: list[str]) -> str:
    """Join LayoutDiffusion tokens for debugging or parity fixtures."""
    return " ".join(tokens)

__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,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
) -> dict[str, Shaped[torch.Tensor, ...]]

Encode layout tensors into token ids.

Parameters:

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

Layout 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
box_format BoxFormat | str

Format of bbox.

xywh
normalized bool

Whether boxes are already normalized.

True
canvas_size tuple[int, int] | None

Pixel canvas size for unnormalized boxes.

None

Returns:

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

Dictionary with input_ids, attention_mask, and mask.

Source code in models/layoutdiffusion/src/layoutdiffusion/tokenization_layoutdiffusion.py
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
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,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
) -> dict[str, Shaped[torch.Tensor, ...]]:
    """Encode layout tensors into token ids.

    Args:
        bbox: Layout boxes.
        labels: Dataset-local labels.
        mask: Optional valid-element mask.
        box_format: Format of ``bbox``.
        normalized: Whether boxes are already normalized.
        canvas_size: Pixel canvas size for unnormalized boxes.

    Returns:
        Dictionary with ``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),
        box_format=box_format,
        normalized=normalized,
        canvas_size=canvas_size,
    )

encode_layout

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

Encode public layout tensors into LayoutDiffusion token ids.

Parameters:

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

Boxes shaped (B, S, 4).

required
labels Int[Tensor, 'batch elements']

Labels shaped (B, S).

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

Optional valid mask shaped (B, S).

None
box_format BoxFormat | str

Input box format.

xywh
normalized bool

Whether input boxes are normalized.

True
canvas_size tuple[int, int] | None

Pixel canvas size when normalized is false.

None

Returns:

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

Encoded token tensors.

Raises:

Type Description
ValueError

If unnormalized boxes omit canvas_size.

Source code in models/layoutdiffusion/src/layoutdiffusion/tokenization_layoutdiffusion.py
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
def encode_layout(
    self,
    *,
    bbox: Float[torch.Tensor, "batch elements 4"],
    labels: Int[torch.Tensor, "batch elements"],
    mask: Bool[torch.Tensor, "batch elements"] | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
) -> dict[str, Shaped[torch.Tensor, ...]]:
    """Encode public layout tensors into LayoutDiffusion token ids.

    Args:
        bbox: Boxes shaped ``(B, S, 4)``.
        labels: Labels shaped ``(B, S)``.
        mask: Optional valid mask shaped ``(B, S)``.
        box_format: Input box format.
        normalized: Whether input boxes are normalized.
        canvas_size: Pixel canvas size when ``normalized`` is false.

    Returns:
        Encoded token tensors.

    Raises:
        ValueError: If unnormalized boxes omit ``canvas_size``.
    """
    if bbox.ndim == 2:
        bbox = bbox.unsqueeze(0)
    if labels.ndim == 1:
        labels = labels.unsqueeze(0)
    if mask is None:
        mask = torch.ones_like(labels, dtype=torch.bool)
    elif mask.ndim == 1:
        mask = mask.unsqueeze(0)
    bbox = bbox.float()
    if normalized:
        fmt = normalize_box_format(box_format)
        if fmt is BoxFormat.xywh:
            xywh = clamp_boxes(bbox)
        elif fmt is BoxFormat.ltrb:
            xywh = clamp_boxes(ltrb_to_xywh(bbox))
        else:
            xywh = clamp_boxes(ltwh_to_xywh(bbox))
    else:
        if canvas_size is None:
            raise ValueError("canvas_size is required when normalized=False")

        xywh = normalize_boxes(bbox, canvas_size=canvas_size, box_format=box_format)
    ltrb_ids = (xywh_to_ltrb(xywh).clamp(0.0, 1.0) * 127).round().long()
    batch_size = labels.shape[0]
    input_ids = torch.full(
        (batch_size, self.config.max_token_length),
        self.config.pad_token_id,
        dtype=torch.long,
    )
    input_ids[:, 0] = self.config.special_token_ids["START"]
    for batch_idx in range(batch_size):
        valid_positions = torch.nonzero(
            mask[batch_idx].bool(), as_tuple=False
        ).flatten()
        valid_positions = valid_positions[: self.config.max_num_elements]
        cursor = 1
        for elem_idx, source_idx in enumerate(valid_positions.tolist()):
            if elem_idx > 0:
                input_ids[batch_idx, cursor] = self.config.special_token_ids["|"]
                cursor += 1
            label = self.config.id2label[int(labels[batch_idx, source_idx].item())]
            token_ids = [self._token_to_id[label]]
            token_ids.extend(
                self._token_to_id[str(int(v))]
                for v in ltrb_ids[batch_idx, source_idx].tolist()
            )
            input_ids[batch_idx, cursor : cursor + 5] = torch.tensor(token_ids)
            cursor += 5
        if cursor < self.config.max_token_length:
            input_ids[batch_idx, cursor] = self.config.special_token_ids["END"]
    attention_mask = input_ids.ne(self.config.pad_token_id)
    return {
        "input_ids": input_ids,
        "attention_mask": attention_mask,
        "mask": mask.bool(),
    }

decode_layout

decode_layout(
    input_ids: Int[Tensor, "batch tokens"],
    *,
    output_box_format: Literal["xywh", "ltrb"] = "xywh",
) -> dict[str, Shaped[torch.Tensor, ...]]

Decode token ids into public layout tensors.

Parameters:

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

Token ids shaped (B, L).

required
output_box_format Literal['xywh', 'ltrb']

"xywh" or "ltrb".

'xywh'

Returns:

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

Dictionary with bbox, labels, and mask.

Raises:

Type Description
ValueError

If output_box_format is unsupported.

Source code in models/layoutdiffusion/src/layoutdiffusion/tokenization_layoutdiffusion.py
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
def decode_layout(
    self,
    input_ids: Int[torch.Tensor, "batch tokens"],
    *,
    output_box_format: Literal["xywh", "ltrb"] = "xywh",
) -> dict[str, Shaped[torch.Tensor, ...]]:
    """Decode token ids into public layout tensors.

    Args:
        input_ids: Token ids shaped ``(B, L)``.
        output_box_format: ``"xywh"`` or ``"ltrb"``.

    Returns:
        Dictionary with ``bbox``, ``labels``, and ``mask``.

    Raises:
        ValueError: If ``output_box_format`` is unsupported.
    """
    if input_ids.ndim == 1:
        input_ids = input_ids.unsqueeze(0)

    batch_boxes = []
    batch_labels = []
    batch_masks = []

    for row in input_ids.cpu().long():
        tokens = [self._convert_id_to_token(int(idx)) for idx in row.tolist()]
        elements = self._parse_elements(tokens)
        boxes = torch.zeros(self.config.max_num_elements, 4, dtype=torch.float32)
        labels = torch.zeros(self.config.max_num_elements, dtype=torch.long)
        masks = torch.zeros(self.config.max_num_elements, dtype=torch.bool)

        for i, element in enumerate(elements[: self.config.max_num_elements]):
            label, *coords = element
            labels[i] = self.config.label2id[label]
            ltrb = torch.tensor([int(v) for v in coords], dtype=torch.float32) / 127
            boxes[i] = ltrb_to_xywh(ltrb) if output_box_format == "xywh" else ltrb
            masks[i] = True

        batch_boxes.append(clamp_boxes(boxes))
        batch_labels.append(labels)
        batch_masks.append(masks)

    if output_box_format not in {"xywh", "ltrb"}:
        raise ValueError(f"Unsupported output_box_format: {output_box_format}")

    return {
        "bbox": torch.stack(batch_boxes, dim=0),
        "labels": torch.stack(batch_labels, dim=0),
        "mask": torch.stack(batch_masks, dim=0),
    }

build_initial_tokens

build_initial_tokens(
    *,
    batch_size: int,
    num_elements: Int[Tensor, "batch"]
    | list[int]
    | int
    | None = None,
    labels: Int[Tensor, "batch elements"] | None = None,
    condition_type: str = "unconditional",
    generator: Generator | None = None,
    device: device | None = None,
) -> Int[torch.Tensor, "batch tokens"]

Build the LayoutDiffusion sampling start template.

Parameters:

Name Type Description Default
batch_size int

Number of samples.

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

Optional element counts in [1, 20].

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

Optional labels for label-conditioned generation.

None
condition_type str

Canonical condition name.

'unconditional'
generator Generator | None

Optional random generator.

None
device device | None

Output device.

None

Returns:

Type Description
Int[Tensor, 'batch tokens']

Initial token ids shaped (B, 121).

Source code in models/layoutdiffusion/src/layoutdiffusion/tokenization_layoutdiffusion.py
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
def build_initial_tokens(
    self,
    *,
    batch_size: int,
    num_elements: Int[torch.Tensor, "batch"] | list[int] | int | None = None,
    labels: Int[torch.Tensor, "batch elements"] | None = None,
    condition_type: str = "unconditional",
    generator: torch.Generator | None = None,
    device: torch.device | None = None,
) -> Int[torch.Tensor, "batch tokens"]:
    """Build the LayoutDiffusion sampling start template.

    Args:
        batch_size: Number of samples.
        num_elements: Optional element counts in ``[1, 20]``.
        labels: Optional labels for label-conditioned generation.
        condition_type: Canonical condition name.
        generator: Optional random generator.
        device: Output device.

    Returns:
        Initial token ids shaped ``(B, 121)``.
    """
    device = device or torch.device("cpu")
    if num_elements is None:
        prior = torch.tensor(
            self.config.element_count_prior, dtype=torch.float32, device=device
        )
        counts = (
            torch.multinomial(
                prior, batch_size, replacement=True, generator=generator
            )
            + 1
        )
    else:
        counts = torch.as_tensor(num_elements, dtype=torch.long, device=device)
        if counts.ndim == 0:
            counts = counts.expand(batch_size)
    counts = counts.clamp(1, self.config.max_num_elements)
    input_ids = torch.full(
        (batch_size, self.config.max_token_length),
        self.config.pad_token_id,
        dtype=torch.long,
        device=device,
    )
    mask_id = self.config.mask_token_id
    start = self.config.special_token_ids["START"]
    sep = self.config.special_token_ids["|"]
    end = self.config.special_token_ids["END"]

    for batch_idx in range(batch_size):
        n = int(counts[batch_idx].item())
        tokens = [start, mask_id, mask_id, mask_id, mask_id, mask_id]
        for _ in range(n - 1):
            tokens.extend([sep, mask_id, mask_id, mask_id, mask_id, mask_id])
        tokens.append(end)
        input_ids[batch_idx, : len(tokens)] = torch.tensor(tokens, device=device)

    if condition_type == "label" and labels is not None:
        label_ids = torch.as_tensor(labels, dtype=torch.long, device=device)
        for batch_idx in range(batch_size):
            for elem_idx in range(min(label_ids.shape[1], int(counts[batch_idx]))):
                pos = 1 + elem_idx * 6
                label = self.config.id2label[int(label_ids[batch_idx, elem_idx])]
                input_ids[batch_idx, pos] = self._token_to_id[label]

            coord_noise = (
                torch.randint(
                    self.config.num_coordinate_bins,
                    input_ids.shape,
                    generator=generator,
                    device=device,
                )
                + self.config.coordinate_token_offset
            )
            coord_positions = torch.zeros_like(input_ids, dtype=torch.bool)
            for elem_idx in range(self.config.max_num_elements):
                start_pos = 2 + elem_idx * 6
                coord_positions[:, start_pos : start_pos + 4] = True
            input_ids = torch.where(
                coord_positions & input_ids.eq(mask_id), coord_noise, input_ids
            )
    return input_ids

token_ids_to_text

token_ids_to_text(
    input_ids: Int[Tensor, "batch tokens"],
) -> list[str]

Convert token ids to LayoutDiffusion text lines.

Source code in models/layoutdiffusion/src/layoutdiffusion/tokenization_layoutdiffusion.py
384
385
386
387
388
389
390
391
392
393
def token_ids_to_text(
    self, input_ids: Int[torch.Tensor, "batch tokens"]
) -> list[str]:
    """Convert token ids to LayoutDiffusion text lines."""
    if input_ids.ndim == 1:
        input_ids = input_ids.unsqueeze(0)
    return [
        " ".join(self._convert_id_to_token(int(idx)) for idx in row.tolist())
        for row in input_ids.cpu().long()
    ]

text_to_token_ids

text_to_token_ids(
    lines: list[str],
) -> Int[torch.Tensor, "batch tokens"]

Convert LayoutDiffusion text lines into padded token ids.

Source code in models/layoutdiffusion/src/layoutdiffusion/tokenization_layoutdiffusion.py
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
def text_to_token_ids(self, lines: list[str]) -> Int[torch.Tensor, "batch tokens"]:
    """Convert LayoutDiffusion text lines into padded token ids."""
    rows = []
    for line in lines:
        tokens = line.strip().split()
        if tokens[:1] != ["START"]:
            tokens = ["START", *tokens]
        if tokens[-1:] != ["END"]:
            tokens = [*tokens, "END"]
        ids = [self._convert_token_to_id(token) for token in tokens]
        ids = ids[: self.config.max_token_length]
        ids.extend(
            [self.config.pad_token_id] * (self.config.max_token_length - len(ids))
        )
        rows.append(torch.tensor(ids, dtype=torch.long))
    return torch.stack(rows, dim=0)

save_vocabulary

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

Save vocabulary and layout config files.

Parameters:

Name Type Description Default
save_directory str | Path

Target directory.

required
filename_prefix str | None

Optional Transformers filename prefix.

None

Returns:

Type Description
tuple[str, ...]

Saved file paths.

Source code in models/layoutdiffusion/src/layoutdiffusion/tokenization_layoutdiffusion.py
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
def save_vocabulary(
    self, save_directory: str | Path, filename_prefix: str | None = None
) -> tuple[str, ...]:
    """Save vocabulary and layout config files.

    Args:
        save_directory: Target directory.
        filename_prefix: Optional Transformers filename prefix.

    Returns:
        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"
    config_file = save_path / f"{prefix}layout_config.json"
    vocab_file.write_text(
        json.dumps(self._token_to_id, indent=2, sort_keys=True),
        encoding="utf-8",
    )
    data = dict(self.config.config)
    data["id2label"] = {str(k): v for k, v in self.config.id2label.items()}
    data["vocab"] = self._token_to_id
    config_file.write_text(
        json.dumps(data, indent=2, sort_keys=True), encoding="utf-8"
    )
    return (str(vocab_file), str(config_file))

from_pretrained classmethod

from_pretrained(
    path: str | PathLike[str],
    *args: 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: LayoutDiffusionConfigValue,
) -> LayoutDiffusionTokenizer

Load tokenizer from a pipeline root or tokenizer directory.

Source code in models/layoutdiffusion/src/layoutdiffusion/tokenization_layoutdiffusion.py
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
@classmethod
def from_pretrained(
    cls,
    path: str | PathLike[str],
    *args: 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: LayoutDiffusionConfigValue,
) -> LayoutDiffusionTokenizer:
    """Load tokenizer from a pipeline root or tokenizer directory."""
    load_path = Path(path)
    tokenizer_dir = load_path / "tokenizer"
    if tokenizer_dir.is_dir():
        load_path = tokenizer_dir
    load_kwargs = {
        "cache_dir": cache_dir,
        "force_download": force_download,
        "local_files_only": local_files_only,
        "token": token,
        "revision": revision,
    }
    return super().from_pretrained(
        load_path,
        *args,
        **load_kwargs,
        **kwargs,
    )

training

Training entry points for LayoutDiffusion.

config

Configuration enums for LayoutDiffusion training.

LayoutDiffusionTrainingDatasetName module-attribute

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

Dataset names supported by package-local LayoutDiffusion training data.

LayoutDiffusionTrainingDatasetSource module-attribute

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

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

LayoutDiffusionTrainingSplit module-attribute

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

Split names supported by package-local LayoutDiffusion training data.

LayoutDiffusionTrainingTransform module-attribute

LayoutDiffusionTrainingTransform: TypeAlias = Literal[
    "LexicographicOrder"
]

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

LayoutDiffusionTrainingScheduler module-attribute

LayoutDiffusionTrainingScheduler: TypeAlias = Literal[
    "linear_anneal"
]

Scheduler names supported by package-local LayoutDiffusion training.

LayoutDiffusionTimeSampler module-attribute

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

Timestep-sampling strategies used by the categorical diffusion loss.

LayoutDiffusionSeedMode

Bases: StrEnum

Seed modes for regular and deterministic LayoutDiffusion training.

Source code in models/layoutdiffusion/src/layoutdiffusion/training/config.py
27
28
29
30
31
class LayoutDiffusionSeedMode(StrEnum):
    """Seed modes for regular and deterministic LayoutDiffusion training."""

    default = auto()
    deterministic = auto()

datamodule

LightningDataModule for LayoutDiffusion training.

LayoutDiffusionDataModule

Bases: LightningDataModule

Package-local LightningDataModule for LayoutDiffusion data.

Source code in models/layoutdiffusion/src/layoutdiffusion/training/datamodule.py
 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
class LayoutDiffusionDataModule(LightningDataModule):
    """Package-local LightningDataModule for LayoutDiffusion data."""

    def __init__(
        self,
        *,
        dataset_name: LayoutDiffusionTrainingDatasetName = "publaynet",
        config: LayoutDiffusionConfig,
        batch_size: int = 64,
        max_num_elements: int | None = None,
        num_workers: int = 4,
        box_format: BoxFormat | str = BoxFormat.xywh,
        normalized: bool = True,
        synthetic_size: int | None = None,
        dataset_source: LayoutDiffusionTrainingDatasetSource = "hf",
        processed_data_dir: str | None = None,
        vocab_file: str | None = None,
        preconsume_train_batches: int = 0,
        processed_stream_rng_warmup: bool = False,
        train_transforms: Sequence[LayoutDiffusionTrainingTransform] | None = (
            "LexicographicOrder",
        ),
    ) -> None:
        """Initialize datamodule settings."""
        super().__init__()
        if preconsume_train_batches < 0:
            raise ValueError("preconsume_train_batches must be non-negative")

        if dataset_source == "hf" and (
            vocab_file is not None or config.id2label != default_id2label(dataset_name)
        ):
            raise ValueError(
                "hf source with non-default id2label is not supported: dataset "
                "numeric labels would be interpreted under a different id2label; "
                "use dataset_source='processed'"
            )

        if vocab_file is not None:
            _validate_vocab_label_count(vocab_file, dataset_name)

        self.dataset_name: LayoutDiffusionTrainingDatasetName = dataset_name
        self.config = config
        self.batch_size = batch_size
        self.max_num_elements = max_num_elements or self.config.max_num_elements

        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.vocab_file = vocab_file
        self.preconsume_train_batches = preconsume_train_batches
        self.processed_stream_rng_warmup = processed_stream_rng_warmup
        self.train_transforms = tuple(train_transforms or ())

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

        self.tokenizer = build_training_tokenizer(self.config, vocab_file=vocab_file)
        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,
            preconsume_batches=self.preconsume_train_batches,
            rng_warmup=self.processed_stream_rng_warmup,
            drop_last=self.dataset_source == "processed",
        )

    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: LayoutDiffusionTrainingSplit
    ) -> Dataset[dict[str, Shaped[torch.Tensor, ...] | str]]:
        if self.synthetic_size is not None:
            return LayoutDiffusionSyntheticDataset(
                config=self.config,
                size=self.synthetic_size,
                elements=min(3, self.max_num_elements),
            )
        if self.dataset_source == "processed":
            if self.processed_data_dir is None:
                raise ValueError(
                    "processed_data_dir is required when dataset_source='processed'"
                )

            return LayoutDiffusionProcessedDataset(
                dataset_name=self.dataset_name,
                split=split,
                config=self.config,
                tokenizer=self.tokenizer,
                processed_data_dir=self.processed_data_dir,
            )
        return LayoutDiffusionDataset(
            dataset_name=self.dataset_name,
            split=split,
            config=self.config,
            tokenizer=self.tokenizer,
            max_num_elements=self.max_num_elements,
            box_format=self.box_format,
            normalized=self.normalized,
            lexicographic_order=self._uses_lexicographic_order(split),
        )

    def _uses_lexicographic_order(self, split: LayoutDiffusionTrainingSplit) -> bool:
        del split
        return "LexicographicOrder" in self.train_transforms

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

        if rng_warmup:
            _warmup_processed_stream_rng(self.config.vocab_size)
        if preconsume_batches == 0:
            return DataLoader(
                dataset,
                batch_size=self.batch_size,
                shuffle=shuffle,
                drop_last=drop_last,
                num_workers=self.num_workers,
            )
        sampler: Sampler[int]
        sized_dataset = cast(Sized, dataset)
        if shuffle:
            sampler = RandomSampler(sized_dataset)
        else:
            sampler = SequentialSampler(sized_dataset)
        batch_sampler = BatchSampler(sampler, self.batch_size, drop_last=drop_last)
        return DataLoader(
            dataset,
            batch_sampler=_PreconsumeBatchSampler(
                batch_sampler, batches=preconsume_batches
            ),
            num_workers=self.num_workers,
        )
__init__
__init__(
    *,
    dataset_name: LayoutDiffusionTrainingDatasetName = "publaynet",
    config: LayoutDiffusionConfig,
    batch_size: int = 64,
    max_num_elements: int | None = None,
    num_workers: int = 4,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    synthetic_size: int | None = None,
    dataset_source: LayoutDiffusionTrainingDatasetSource = "hf",
    processed_data_dir: str | None = None,
    vocab_file: str | None = None,
    preconsume_train_batches: int = 0,
    processed_stream_rng_warmup: bool = False,
    train_transforms: Sequence[
        LayoutDiffusionTrainingTransform
    ]
    | None = ("LexicographicOrder",),
) -> None

Initialize datamodule settings.

Source code in models/layoutdiffusion/src/layoutdiffusion/training/datamodule.py
 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
def __init__(
    self,
    *,
    dataset_name: LayoutDiffusionTrainingDatasetName = "publaynet",
    config: LayoutDiffusionConfig,
    batch_size: int = 64,
    max_num_elements: int | None = None,
    num_workers: int = 4,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    synthetic_size: int | None = None,
    dataset_source: LayoutDiffusionTrainingDatasetSource = "hf",
    processed_data_dir: str | None = None,
    vocab_file: str | None = None,
    preconsume_train_batches: int = 0,
    processed_stream_rng_warmup: bool = False,
    train_transforms: Sequence[LayoutDiffusionTrainingTransform] | None = (
        "LexicographicOrder",
    ),
) -> None:
    """Initialize datamodule settings."""
    super().__init__()
    if preconsume_train_batches < 0:
        raise ValueError("preconsume_train_batches must be non-negative")

    if dataset_source == "hf" and (
        vocab_file is not None or config.id2label != default_id2label(dataset_name)
    ):
        raise ValueError(
            "hf source with non-default id2label is not supported: dataset "
            "numeric labels would be interpreted under a different id2label; "
            "use dataset_source='processed'"
        )

    if vocab_file is not None:
        _validate_vocab_label_count(vocab_file, dataset_name)

    self.dataset_name: LayoutDiffusionTrainingDatasetName = dataset_name
    self.config = config
    self.batch_size = batch_size
    self.max_num_elements = max_num_elements or self.config.max_num_elements

    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.vocab_file = vocab_file
    self.preconsume_train_batches = preconsume_train_batches
    self.processed_stream_rng_warmup = processed_stream_rng_warmup
    self.train_transforms = tuple(train_transforms or ())

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

    self.tokenizer = build_training_tokenizer(self.config, vocab_file=vocab_file)
    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/layoutdiffusion/src/layoutdiffusion/training/datamodule.py
128
129
130
131
132
133
134
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/layoutdiffusion/src/layoutdiffusion/training/datamodule.py
136
137
138
139
140
141
142
143
144
145
146
147
148
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,
        preconsume_batches=self.preconsume_train_batches,
        rng_warmup=self.processed_stream_rng_warmup,
        drop_last=self.dataset_source == "processed",
    )
val_dataloader
val_dataloader() -> DataLoader[
    dict[str, Shaped[torch.Tensor, ...] | str]
]

Return the validation dataloader.

Source code in models/layoutdiffusion/src/layoutdiffusion/training/datamodule.py
150
151
152
153
154
155
156
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/layoutdiffusion/src/layoutdiffusion/training/datamodule.py
158
159
160
161
162
163
164
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 helpers for LayoutDiffusion training.

LayoutDiffusionDataset

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

HF datasets-backed LayoutDiffusion training dataset.

Source code in models/layoutdiffusion/src/layoutdiffusion/training/dataset.py
 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
class LayoutDiffusionDataset(
    TorchDataset[dict[str, Shaped[torch.Tensor, "..."] | str]]
):
    """HF datasets-backed LayoutDiffusion training dataset."""

    def __init__(
        self,
        *,
        dataset_name: LayoutDiffusionTrainingDatasetName,
        config: LayoutDiffusionConfig,
        split: LayoutDiffusionTrainingSplit = "train",
        tokenizer: LayoutDiffusionTokenizer | None = None,
        max_num_elements: int | None = None,
        box_format: BoxFormat | str = BoxFormat.xywh,
        normalized: bool = True,
        lexicographic_order: bool = True,
    ) -> None:
        """Load a LayoutDiffusion training split from approved HF sources."""
        super().__init__()
        self.dataset_name = dataset_name
        self.split = split
        self.config = config
        self.tokenizer = tokenizer or LayoutDiffusionTokenizer(self.config)
        self.max_num_elements = max_num_elements or self.config.max_num_elements
        self.box_format = box_format
        self.normalized = normalized
        self.lexicographic_order = lexicographic_order
        self.label2id = _layoutdiffusion_label2id(self.config)
        self.public_id2label = default_id2label(self.config.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."""
        return len(self.dataset)

    def __getitem__(self, index: int) -> dict[str, Shaped[torch.Tensor, "..."] | str]:
        """Return one tokenized training example."""
        sample = cast(LayoutSample, self.dataset[index])
        encoded = self._encode_sample(sample)
        output: dict[str, Shaped[torch.Tensor, "..."] | str] = dict(encoded)
        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

    def _encode_sample(
        self, sample: LayoutSample
    ) -> dict[str, Shaped[torch.Tensor, "..."]]:
        bbox, labels, canvas_size = _extract_layout(
            sample, self.label2id, self.public_id2label
        )
        bbox = bbox[: self.max_num_elements]
        labels = labels[: self.max_num_elements]
        if self.lexicographic_order:
            bbox, labels = _lexicographic_order(bbox, labels, self.box_format)
        mask = torch.ones(labels.shape, dtype=torch.bool)
        encoded = self.tokenizer(
            bbox=bbox.unsqueeze(0),
            labels=labels.unsqueeze(0),
            mask=mask.unsqueeze(0),
            box_format=self.box_format,
            normalized=self.normalized,
            canvas_size=canvas_size,
        )
        return {key: value.squeeze(0) for key, value in encoded.items()}
__init__
__init__(
    *,
    dataset_name: LayoutDiffusionTrainingDatasetName,
    config: LayoutDiffusionConfig,
    split: LayoutDiffusionTrainingSplit = "train",
    tokenizer: LayoutDiffusionTokenizer | None = None,
    max_num_elements: int | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    lexicographic_order: bool = True,
) -> None

Load a LayoutDiffusion training split from approved HF sources.

Source code in models/layoutdiffusion/src/layoutdiffusion/training/dataset.py
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
def __init__(
    self,
    *,
    dataset_name: LayoutDiffusionTrainingDatasetName,
    config: LayoutDiffusionConfig,
    split: LayoutDiffusionTrainingSplit = "train",
    tokenizer: LayoutDiffusionTokenizer | None = None,
    max_num_elements: int | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    lexicographic_order: bool = True,
) -> None:
    """Load a LayoutDiffusion training split from approved HF sources."""
    super().__init__()
    self.dataset_name = dataset_name
    self.split = split
    self.config = config
    self.tokenizer = tokenizer or LayoutDiffusionTokenizer(self.config)
    self.max_num_elements = max_num_elements or self.config.max_num_elements
    self.box_format = box_format
    self.normalized = normalized
    self.lexicographic_order = lexicographic_order
    self.label2id = _layoutdiffusion_label2id(self.config)
    self.public_id2label = default_id2label(self.config.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.

Source code in models/layoutdiffusion/src/layoutdiffusion/training/dataset.py
92
93
94
def __len__(self) -> int:
    """Return dataset size."""
    return len(self.dataset)
__getitem__
__getitem__(
    index: int,
) -> dict[str, Shaped[torch.Tensor, "..."] | str]

Return one tokenized training example.

Source code in models/layoutdiffusion/src/layoutdiffusion/training/dataset.py
 96
 97
 98
 99
100
101
102
103
104
def __getitem__(self, index: int) -> dict[str, Shaped[torch.Tensor, "..."] | str]:
    """Return one tokenized training example."""
    sample = cast(LayoutSample, self.dataset[index])
    encoded = self._encode_sample(sample)
    output: dict[str, Shaped[torch.Tensor, "..."] | str] = dict(encoded)
    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

LayoutDiffusionProcessedDataset

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

Processed LayoutDiffusion token stream used for parity reruns.

Source code in models/layoutdiffusion/src/layoutdiffusion/training/dataset.py
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
class LayoutDiffusionProcessedDataset(
    TorchDataset[dict[str, Shaped[torch.Tensor, "..."] | str]]
):
    """Processed LayoutDiffusion token stream used for parity reruns."""

    def __init__(
        self,
        *,
        dataset_name: LayoutDiffusionTrainingDatasetName,
        config: LayoutDiffusionConfig,
        processed_data_dir: str | Path,
        split: LayoutDiffusionTrainingSplit = "train",
        tokenizer: LayoutDiffusionTokenizer | None = None,
    ) -> None:
        """Load processed token ids or text lines from a local directory."""
        super().__init__()
        self.dataset_name = dataset_name
        self.config = config
        self.split = split
        self.tokenizer = tokenizer or LayoutDiffusionTokenizer(self.config)
        self.path = _processed_path(Path(processed_data_dir), dataset_name, split)
        if self.path.suffix == ".pt":
            self.rows = _load_processed_tensor_rows(
                self.path, self.config.max_token_length
            )
        else:
            self.rows = self.tokenizer.text_to_token_ids(
                self.path.read_text(encoding="utf-8").splitlines()
            )

    def __len__(self) -> int:
        """Return the number of processed rows."""
        return int(self.rows.shape[0])

    def __getitem__(self, index: int) -> dict[str, Shaped[torch.Tensor, "..."] | str]:
        """Return one processed token row."""
        input_ids = self.rows[index].long()
        return {
            "input_ids": input_ids,
            "attention_mask": input_ids.ne(self.config.pad_token_id),
            "mask": input_ids.ne(self.config.pad_token_id),
            "id": str(index),
        }
__init__
__init__(
    *,
    dataset_name: LayoutDiffusionTrainingDatasetName,
    config: LayoutDiffusionConfig,
    processed_data_dir: str | Path,
    split: LayoutDiffusionTrainingSplit = "train",
    tokenizer: LayoutDiffusionTokenizer | None = None,
) -> None

Load processed token ids or text lines from a local directory.

Source code in models/layoutdiffusion/src/layoutdiffusion/training/dataset.py
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
def __init__(
    self,
    *,
    dataset_name: LayoutDiffusionTrainingDatasetName,
    config: LayoutDiffusionConfig,
    processed_data_dir: str | Path,
    split: LayoutDiffusionTrainingSplit = "train",
    tokenizer: LayoutDiffusionTokenizer | None = None,
) -> None:
    """Load processed token ids or text lines from a local directory."""
    super().__init__()
    self.dataset_name = dataset_name
    self.config = config
    self.split = split
    self.tokenizer = tokenizer or LayoutDiffusionTokenizer(self.config)
    self.path = _processed_path(Path(processed_data_dir), dataset_name, split)
    if self.path.suffix == ".pt":
        self.rows = _load_processed_tensor_rows(
            self.path, self.config.max_token_length
        )
    else:
        self.rows = self.tokenizer.text_to_token_ids(
            self.path.read_text(encoding="utf-8").splitlines()
        )
__len__
__len__() -> int

Return the number of processed rows.

Source code in models/layoutdiffusion/src/layoutdiffusion/training/dataset.py
158
159
160
def __len__(self) -> int:
    """Return the number of processed rows."""
    return int(self.rows.shape[0])
__getitem__
__getitem__(
    index: int,
) -> dict[str, Shaped[torch.Tensor, "..."] | str]

Return one processed token row.

Source code in models/layoutdiffusion/src/layoutdiffusion/training/dataset.py
162
163
164
165
166
167
168
169
170
def __getitem__(self, index: int) -> dict[str, Shaped[torch.Tensor, "..."] | str]:
    """Return one processed token row."""
    input_ids = self.rows[index].long()
    return {
        "input_ids": input_ids,
        "attention_mask": input_ids.ne(self.config.pad_token_id),
        "mask": input_ids.ne(self.config.pad_token_id),
        "id": str(index),
    }

LayoutDiffusionSyntheticDataset

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

Small deterministic dataset for local LightningCLI smoke tests.

Source code in models/layoutdiffusion/src/layoutdiffusion/training/dataset.py
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
class LayoutDiffusionSyntheticDataset(
    TorchDataset[dict[str, Shaped[torch.Tensor, "..."] | str]]
):
    """Small deterministic dataset for local LightningCLI smoke tests."""

    def __init__(
        self,
        *,
        config: LayoutDiffusionConfig,
        size: int = 8,
        elements: int = 3,
    ) -> None:
        """Initialize deterministic synthetic layout examples."""
        super().__init__()
        self.config = config
        self.size = size
        self.elements = min(elements, config.max_num_elements)
        self.tokenizer = LayoutDiffusionTokenizer(config)

    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] + bbox[:, 2:].mul(0.35).add(0.05)
        bbox = bbox.clamp(0.0, 1.0)
        labels = torch.arange(self.elements, dtype=torch.long) % self.config.num_labels
        encoded = self.tokenizer(
            bbox=bbox.unsqueeze(0),
            labels=labels.unsqueeze(0),
            mask=torch.ones(1, self.elements, dtype=torch.bool),
            box_format=BoxFormat.ltrb,
        )
        return {key: value.squeeze(0) for key, value in encoded.items()}
__init__
__init__(
    *,
    config: LayoutDiffusionConfig,
    size: int = 8,
    elements: int = 3,
) -> None

Initialize deterministic synthetic layout examples.

Source code in models/layoutdiffusion/src/layoutdiffusion/training/dataset.py
178
179
180
181
182
183
184
185
186
187
188
189
190
def __init__(
    self,
    *,
    config: LayoutDiffusionConfig,
    size: int = 8,
    elements: int = 3,
) -> None:
    """Initialize deterministic synthetic layout examples."""
    super().__init__()
    self.config = config
    self.size = size
    self.elements = min(elements, config.max_num_elements)
    self.tokenizer = LayoutDiffusionTokenizer(config)
__len__
__len__() -> int

Return synthetic dataset size.

Source code in models/layoutdiffusion/src/layoutdiffusion/training/dataset.py
192
193
194
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/layoutdiffusion/src/layoutdiffusion/training/dataset.py
196
197
198
199
200
201
202
203
204
205
206
207
208
209
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] + bbox[:, 2:].mul(0.35).add(0.05)
    bbox = bbox.clamp(0.0, 1.0)
    labels = torch.arange(self.elements, dtype=torch.long) % self.config.num_labels
    encoded = self.tokenizer(
        bbox=bbox.unsqueeze(0),
        labels=labels.unsqueeze(0),
        mask=torch.ones(1, self.elements, dtype=torch.bool),
        box_format=BoxFormat.ltrb,
    )
    return {key: value.squeeze(0) for key, value in encoded.items()}

lightning_module

PyTorch Lightning module for LayoutDiffusion discrete training.

LayoutDiffusionTrainingModule

Bases: LightningModule

Lightning wrapper reproducing LayoutDiffusion categorical diffusion training.

Source code in models/layoutdiffusion/src/layoutdiffusion/training/lightning_module.py
 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
class LayoutDiffusionTrainingModule(LightningModule):
    """Lightning wrapper reproducing LayoutDiffusion categorical diffusion training."""

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

    def __init__(
        self,
        *,
        config: LayoutDiffusionConfig,
        model: LayoutDiffusionTransformer | None = None,
        tokenizer: LayoutDiffusionTokenizer | None = None,
        vocab_file: str | None = None,
        learning_rate: float = 5e-5,
        weight_decay: float = 0.0,
        betas: tuple[float, float] = (0.9, 0.999),
        auxiliary_loss_weight: float = 1e-3,
        time_sampler: LayoutDiffusionTimeSampler = "importance",
        scheduler: LayoutDiffusionTrainingScheduler | None = "linear_anneal",
        lr_anneal_steps: int = 400_000,
        ema_rate: float = 0.9999,
        seed_mode: LayoutDiffusionSeedMode | str = LayoutDiffusionSeedMode.default,
    ) -> None:
        """Initialize LayoutDiffusion training state."""
        super().__init__()
        self.tokenizer = tokenizer or build_training_tokenizer(
            config, vocab_file=vocab_file
        )
        self.layoutdiffusion_config = config
        self.model = model or LayoutDiffusionTransformer(
            vocab_size=config.vocab_size,
            num_channels=config.num_channels,
            hidden_size=config.hidden_size,
            num_hidden_layers=config.num_hidden_layers,
            num_attention_heads=config.num_attention_heads,
            intermediate_size=config.intermediate_size,
            dropout=config.dropout,
            max_position_embeddings=config.max_position_embeddings,
        )
        self.diffusion_scheduler = LayoutDiffusionScheduler.from_layout_config(config)

        self.num_timesteps = config.diffusion_steps
        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.time_sampler: LayoutDiffusionTimeSampler = time_sampler
        self.scheduler = scheduler
        self.lr_anneal_steps = lr_anneal_steps
        self.ema_rate = ema_rate
        self.seed_mode = LayoutDiffusionSeedMode(seed_mode)

        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, "..."]] = {}
        self._ema_params: dict[str, Shaped[torch.Tensor, "..."]] = {
            name: param.detach().clone()
            for name, param in self.model.named_parameters()
            if param.requires_grad
        }

    def on_fit_start(self) -> None:
        """Validate model/datamodule label order before training starts."""
        datamodule = getattr(self.trainer, "datamodule", None)
        data_config = getattr(datamodule, "config", None)
        data_id2label = getattr(data_config, "id2label", None)
        model_id2label = getattr(self.layoutdiffusion_config, "id2label", None)
        if data_id2label is None or model_id2label is None:
            return
        if dict(data_id2label) == dict(model_id2label):
            return
        data_first = next(iter(dict(data_id2label).items()), None)
        model_first = next(iter(dict(model_id2label).items()), None)
        raise ValueError(
            "LayoutDiffusion model/data id2label mismatch: "
            f"model first entry={model_first}, data first entry={data_first}"
        )

    def configure_optimizers(self) -> OptimizerLRScheduler:
        """Return AdamW and optional linear annealing scheduler."""
        optimizer = torch.optim.AdamW(
            self.model.parameters(),
            lr=self.learning_rate,
            betas=self.betas,
            weight_decay=self.weight_decay,
        )
        if self.scheduler == "linear_anneal":
            lr_scheduler = torch.optim.lr_scheduler.LambdaLR(
                optimizer,
                lr_lambda=lambda step: max(0.0, 1.0 - step / self.lr_anneal_steps),
            )
            return {
                "optimizer": optimizer,
                "lr_scheduler": {
                    "scheduler": lr_scheduler,
                    "interval": "step",
                },
            }
        return optimizer

    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
            )
        if self.time_sampler == "importance":
            return sample_time_importance(
                batch_size,
                num_timesteps=self.num_timesteps,
                lt_history=self.lt_history,
                lt_count=self.lt_count,
            )
        raise ValueError(f"Unsupported time_sampler: {self.time_sampler}")

    def _q_sample(
        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"],
    ]:
        log_x_start = index_to_log_onehot(x_start, self.num_classes)
        log_qpred = self.diffusion_scheduler.q_pred(log_x_start, t)
        log_x_t = self.diffusion_scheduler.log_sample_categorical(log_qpred)
        return log_x_t, log_onehot_to_index(log_x_t)

    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 LayoutDiffusion variational training loss."""
        batch_size, seq_length = x_start.shape
        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(x_start, t)

        logits = self.model(input_ids=xt, timesteps=t).logits
        log_x0_recon = self.diffusion_scheduler.predict_start(
            logits, batch_size, seq_length
        )
        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)

        mask_region = xt.eq(self.diffusion_scheduler.mask_token_id).float()
        mask_weight = mask_region + (1.0 - mask_region)
        kl = sum_except_batch(
            multinomial_kl(log_true_prob, log_model_prob) * mask_weight
        )
        decoder_nll = sum_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,
        )

        loss1 = kl_loss / pt
        losses: dict[str, Float[torch.Tensor, ""]] = {"kl_loss": loss1.mean()}
        aux_loss = torch.zeros_like(losses["kl_loss"])
        if self.auxiliary_loss_weight != 0 and is_train:
            kl_aux = sum_except_batch(
                multinomial_kl(log_x_start[:, :-1, :], log_x0_recon[:, :-1, :])
                * mask_weight
            )
            kl_aux_loss = at_zero * decoder_nll + (1.0 - at_zero) * kl_aux
            adaptive_weight = 2.0 - t.float() / self.num_timesteps
            loss2 = adaptive_weight * self.auxiliary_loss_weight * kl_aux_loss / pt
            aux_loss = loss2.mean()
            losses["aux_loss"] = aux_loss

        trace: dict[str, Shaped[torch.Tensor, "..."]] = {
            "t": t.detach(),
            "pt": pt.detach(),
            "xt": xt.detach(),
            "log_x_t": log_x_t.detach(),
            "log_x0_recon": log_x0_recon.detach(),
            "log_model_prob": log_model_prob.detach(),
            "log_true_prob": log_true_prob.detach(),
            "kl": kl.detach(),
            "decoder_nll": decoder_nll.detach(),
            "kl_loss": kl_loss.detach(),
            "lt_history": self.lt_history.detach().clone(),
            "lt_count": self.lt_count.detach().clone(),
            "aux_loss": aux_loss.detach(),
        }
        return losses, trace

    def training_step(
        self, batch: dict[str, Shaped[torch.Tensor, "..."]], batch_idx: int
    ) -> Float[torch.Tensor, ""]:
        """Run one LayoutDiffusion 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 LayoutDiffusion validation step."""
        del batch_idx
        seq = batch["input_ids"].long()
        losses, _ = self._diffusion_losses(seq, is_train=False)
        total = sum_loss_values(losses)
        log_validation_loss(self, total)
        return total

    def optimizer_step(
        self,
        epoch: int,
        batch_idx: int,
        optimizer: Optimizer | LightningOptimizer,
        optimizer_closure: Callable[[], Float[torch.Tensor, ""]] | None = None,
    ) -> None:
        """Run the optimizer step and update EMA parameters."""
        super().optimizer_step(epoch, batch_idx, optimizer, optimizer_closure)
        self.update_ema()

    def update_ema(self) -> None:
        """Update exponential moving average parameters."""
        with torch.no_grad():
            for name, param in self.model.named_parameters():
                if not param.requires_grad:
                    continue
                ema_param = self._ema_params[name].to(device=param.device)
                self._ema_params[name] = ema_param
                ema_param.mul_(self.ema_rate).add_(
                    param.detach(), alpha=1.0 - self.ema_rate
                )

    def ema_state_dict(self) -> dict[str, Shaped[torch.Tensor, "..."]]:
        """Return a detached copy of EMA parameters."""
        return {
            name: value.detach().clone() for name, value in self._ema_params.items()
        }

    def on_save_checkpoint(
        self,
        checkpoint: dict[
            str,
            dict[str, Shaped[torch.Tensor, "..."]]
            | Shaped[torch.Tensor, "..."]
            | int
            | float
            | str
            | bool
            | None,
        ],
    ) -> None:
        """Persist EMA parameters in Lightning checkpoints."""
        checkpoint[EMA_CHECKPOINT_KEY] = self.ema_state_dict()

    def on_load_checkpoint(
        self,
        checkpoint: dict[
            str,
            dict[str, Shaped[torch.Tensor, "..."]]
            | Shaped[torch.Tensor, "..."]
            | int
            | float
            | str
            | bool
            | None,
        ],
    ) -> None:
        """Restore EMA parameters from Lightning checkpoints when available."""
        ema_state = checkpoint.get(EMA_CHECKPOINT_KEY)
        if ema_state is None:
            return
        if not isinstance(ema_state, dict):
            raise TypeError(f"{EMA_CHECKPOINT_KEY} must be a dict")

        restored: dict[str, Shaped[torch.Tensor, "..."]] = {}
        for name, value in ema_state.items():
            if not isinstance(value, torch.Tensor):
                raise TypeError(f"{EMA_CHECKPOINT_KEY}[{name}] must be a tensor")

            restored[str(name)] = value.detach().clone()
        self._ema_params = restored
__init__
__init__(
    *,
    config: LayoutDiffusionConfig,
    model: LayoutDiffusionTransformer | None = None,
    tokenizer: LayoutDiffusionTokenizer | None = None,
    vocab_file: str | None = None,
    learning_rate: float = 5e-05,
    weight_decay: float = 0.0,
    betas: tuple[float, float] = (0.9, 0.999),
    auxiliary_loss_weight: float = 0.001,
    time_sampler: LayoutDiffusionTimeSampler = "importance",
    scheduler: LayoutDiffusionTrainingScheduler
    | None = "linear_anneal",
    lr_anneal_steps: int = 400000,
    ema_rate: float = 0.9999,
    seed_mode: LayoutDiffusionSeedMode
    | str = LayoutDiffusionSeedMode.default,
) -> None

Initialize LayoutDiffusion training state.

Source code in models/layoutdiffusion/src/layoutdiffusion/training/lightning_module.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
105
106
def __init__(
    self,
    *,
    config: LayoutDiffusionConfig,
    model: LayoutDiffusionTransformer | None = None,
    tokenizer: LayoutDiffusionTokenizer | None = None,
    vocab_file: str | None = None,
    learning_rate: float = 5e-5,
    weight_decay: float = 0.0,
    betas: tuple[float, float] = (0.9, 0.999),
    auxiliary_loss_weight: float = 1e-3,
    time_sampler: LayoutDiffusionTimeSampler = "importance",
    scheduler: LayoutDiffusionTrainingScheduler | None = "linear_anneal",
    lr_anneal_steps: int = 400_000,
    ema_rate: float = 0.9999,
    seed_mode: LayoutDiffusionSeedMode | str = LayoutDiffusionSeedMode.default,
) -> None:
    """Initialize LayoutDiffusion training state."""
    super().__init__()
    self.tokenizer = tokenizer or build_training_tokenizer(
        config, vocab_file=vocab_file
    )
    self.layoutdiffusion_config = config
    self.model = model or LayoutDiffusionTransformer(
        vocab_size=config.vocab_size,
        num_channels=config.num_channels,
        hidden_size=config.hidden_size,
        num_hidden_layers=config.num_hidden_layers,
        num_attention_heads=config.num_attention_heads,
        intermediate_size=config.intermediate_size,
        dropout=config.dropout,
        max_position_embeddings=config.max_position_embeddings,
    )
    self.diffusion_scheduler = LayoutDiffusionScheduler.from_layout_config(config)

    self.num_timesteps = config.diffusion_steps
    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.time_sampler: LayoutDiffusionTimeSampler = time_sampler
    self.scheduler = scheduler
    self.lr_anneal_steps = lr_anneal_steps
    self.ema_rate = ema_rate
    self.seed_mode = LayoutDiffusionSeedMode(seed_mode)

    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, "..."]] = {}
    self._ema_params: dict[str, Shaped[torch.Tensor, "..."]] = {
        name: param.detach().clone()
        for name, param in self.model.named_parameters()
        if param.requires_grad
    }
on_fit_start
on_fit_start() -> None

Validate model/datamodule label order before training starts.

Source code in models/layoutdiffusion/src/layoutdiffusion/training/lightning_module.py
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
def on_fit_start(self) -> None:
    """Validate model/datamodule label order before training starts."""
    datamodule = getattr(self.trainer, "datamodule", None)
    data_config = getattr(datamodule, "config", None)
    data_id2label = getattr(data_config, "id2label", None)
    model_id2label = getattr(self.layoutdiffusion_config, "id2label", None)
    if data_id2label is None or model_id2label is None:
        return
    if dict(data_id2label) == dict(model_id2label):
        return
    data_first = next(iter(dict(data_id2label).items()), None)
    model_first = next(iter(dict(model_id2label).items()), None)
    raise ValueError(
        "LayoutDiffusion model/data id2label mismatch: "
        f"model first entry={model_first}, data first entry={data_first}"
    )
configure_optimizers
configure_optimizers() -> OptimizerLRScheduler

Return AdamW and optional linear annealing scheduler.

Source code in models/layoutdiffusion/src/layoutdiffusion/training/lightning_module.py
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
def configure_optimizers(self) -> OptimizerLRScheduler:
    """Return AdamW and optional linear annealing scheduler."""
    optimizer = torch.optim.AdamW(
        self.model.parameters(),
        lr=self.learning_rate,
        betas=self.betas,
        weight_decay=self.weight_decay,
    )
    if self.scheduler == "linear_anneal":
        lr_scheduler = torch.optim.lr_scheduler.LambdaLR(
            optimizer,
            lr_lambda=lambda step: max(0.0, 1.0 - step / self.lr_anneal_steps),
        )
        return {
            "optimizer": optimizer,
            "lr_scheduler": {
                "scheduler": lr_scheduler,
                "interval": "step",
            },
        }
    return optimizer
training_step
training_step(
    batch: dict[str, Shaped[Tensor, "..."]], batch_idx: int
) -> Float[torch.Tensor, ""]

Run one LayoutDiffusion training step.

Source code in models/layoutdiffusion/src/layoutdiffusion/training/lightning_module.py
239
240
241
242
243
244
245
246
247
def training_step(
    self, batch: dict[str, Shaped[torch.Tensor, "..."]], batch_idx: int
) -> Float[torch.Tensor, ""]:
    """Run one LayoutDiffusion 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 LayoutDiffusion validation step.

Source code in models/layoutdiffusion/src/layoutdiffusion/training/lightning_module.py
249
250
251
252
253
254
255
256
257
258
def validation_step(
    self, batch: dict[str, Shaped[torch.Tensor, "..."]], batch_idx: int
) -> Float[torch.Tensor, ""]:
    """Run one LayoutDiffusion validation step."""
    del batch_idx
    seq = batch["input_ids"].long()
    losses, _ = self._diffusion_losses(seq, is_train=False)
    total = sum_loss_values(losses)
    log_validation_loss(self, total)
    return total
optimizer_step
optimizer_step(
    epoch: int,
    batch_idx: int,
    optimizer: Optimizer | LightningOptimizer,
    optimizer_closure: Callable[[], Float[Tensor, ""]]
    | None = None,
) -> None

Run the optimizer step and update EMA parameters.

Source code in models/layoutdiffusion/src/layoutdiffusion/training/lightning_module.py
260
261
262
263
264
265
266
267
268
269
def optimizer_step(
    self,
    epoch: int,
    batch_idx: int,
    optimizer: Optimizer | LightningOptimizer,
    optimizer_closure: Callable[[], Float[torch.Tensor, ""]] | None = None,
) -> None:
    """Run the optimizer step and update EMA parameters."""
    super().optimizer_step(epoch, batch_idx, optimizer, optimizer_closure)
    self.update_ema()
update_ema
update_ema() -> None

Update exponential moving average parameters.

Source code in models/layoutdiffusion/src/layoutdiffusion/training/lightning_module.py
271
272
273
274
275
276
277
278
279
280
281
def update_ema(self) -> None:
    """Update exponential moving average parameters."""
    with torch.no_grad():
        for name, param in self.model.named_parameters():
            if not param.requires_grad:
                continue
            ema_param = self._ema_params[name].to(device=param.device)
            self._ema_params[name] = ema_param
            ema_param.mul_(self.ema_rate).add_(
                param.detach(), alpha=1.0 - self.ema_rate
            )
ema_state_dict
ema_state_dict() -> dict[str, Shaped[torch.Tensor, '...']]

Return a detached copy of EMA parameters.

Source code in models/layoutdiffusion/src/layoutdiffusion/training/lightning_module.py
283
284
285
286
287
def ema_state_dict(self) -> dict[str, Shaped[torch.Tensor, "..."]]:
    """Return a detached copy of EMA parameters."""
    return {
        name: value.detach().clone() for name, value in self._ema_params.items()
    }
on_save_checkpoint
on_save_checkpoint(
    checkpoint: dict[
        str,
        dict[str, Shaped[Tensor, "..."]]
        | Shaped[Tensor, "..."]
        | int
        | float
        | str
        | bool
        | None,
    ],
) -> None

Persist EMA parameters in Lightning checkpoints.

Source code in models/layoutdiffusion/src/layoutdiffusion/training/lightning_module.py
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
def on_save_checkpoint(
    self,
    checkpoint: dict[
        str,
        dict[str, Shaped[torch.Tensor, "..."]]
        | Shaped[torch.Tensor, "..."]
        | int
        | float
        | str
        | bool
        | None,
    ],
) -> None:
    """Persist EMA parameters in Lightning checkpoints."""
    checkpoint[EMA_CHECKPOINT_KEY] = self.ema_state_dict()
on_load_checkpoint
on_load_checkpoint(
    checkpoint: dict[
        str,
        dict[str, Shaped[Tensor, "..."]]
        | Shaped[Tensor, "..."]
        | int
        | float
        | str
        | bool
        | None,
    ],
) -> None

Restore EMA parameters from Lightning checkpoints when available.

Source code in models/layoutdiffusion/src/layoutdiffusion/training/lightning_module.py
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
def on_load_checkpoint(
    self,
    checkpoint: dict[
        str,
        dict[str, Shaped[torch.Tensor, "..."]]
        | Shaped[torch.Tensor, "..."]
        | int
        | float
        | str
        | bool
        | None,
    ],
) -> None:
    """Restore EMA parameters from Lightning checkpoints when available."""
    ema_state = checkpoint.get(EMA_CHECKPOINT_KEY)
    if ema_state is None:
        return
    if not isinstance(ema_state, dict):
        raise TypeError(f"{EMA_CHECKPOINT_KEY} must be a dict")

    restored: dict[str, Shaped[torch.Tensor, "..."]] = {}
    for name, value in ema_state.items():
        if not isinstance(value, torch.Tensor):
            raise TypeError(f"{EMA_CHECKPOINT_KEY}[{name}] must be a tensor")

        restored[str(name)] = value.detach().clone()
    self._ema_params = restored

losses

Categorical diffusion training-loss helpers for LayoutDiffusion.

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

sum_except_batch

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

Sum every non-batch dimension using the reference reduction.

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 sum over all trailing dimensions.

Examples:

>>> sum_except_batch(torch.ones(2, 3)).tolist()
[3.0, 3.0]
Source code in models/layoutdiffusion/src/layoutdiffusion/training/losses.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def sum_except_batch(
    x: Float[torch.Tensor, "batch ..."],
) -> Float[torch.Tensor, "batch"]:
    """Sum every non-batch dimension using the reference reduction.

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

    Returns:
        Per-example sum over all trailing dimensions.

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

parity

LayoutDiffusion-specific S0-S2 training-parity helpers.

trace_layoutdiffusion_step

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

Trace one LayoutDiffusion training step with canonical trace points.

Source code in models/layoutdiffusion/src/layoutdiffusion/training/parity.py
21
22
23
24
25
26
27
def trace_layoutdiffusion_step(
    module: TrainingStepModule,
    batch: dict[str, Shaped[torch.Tensor, ...]],
    rng_state: RNGState | None = None,
) -> StepTrace:
    """Trace one LayoutDiffusion training step with canonical trace points."""
    return trace_training_step(module, batch, rng_state, TRACE_POINTS)

compare_layoutdiffusion_step

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

Compare S1 LayoutDiffusion pre-optimizer traces.

Source code in models/layoutdiffusion/src/layoutdiffusion/training/parity.py
30
31
32
33
34
35
36
37
38
def compare_layoutdiffusion_step(
    reference: StepTrace,
    target: StepTrace,
    *,
    tolerance: TensorTolerance | None = None,
) -> StepReport:
    """Compare S1 LayoutDiffusion pre-optimizer traces."""
    tolerances = {name: tolerance or TensorTolerance() for name in TRACE_POINTS}
    return compare_step_trace(reference, target, tolerances)

compare_layoutdiffusion_optimizer_step

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

Compare S2 LayoutDiffusion post-optimizer parameters.

Source code in models/layoutdiffusion/src/layoutdiffusion/training/parity.py
41
42
43
44
45
46
47
48
49
def compare_layoutdiffusion_optimizer_step(
    reference_state: dict[str, Shaped[torch.Tensor, ...]],
    target_state: dict[str, Shaped[torch.Tensor, ...]],
    *,
    tolerance: TensorTolerance | None = None,
) -> OptimizerStepReport:
    """Compare S2 LayoutDiffusion post-optimizer parameters."""
    tolerances = {name: tolerance or TensorTolerance() for name in reference_state}
    return compare_optimizer_step(reference_state, target_state, tolerances)

seed

Seed policy helpers for LayoutDiffusion training.

apply_layoutdiffusion_seed_mode

apply_layoutdiffusion_seed_mode(
    seed_mode: LayoutDiffusionSeedMode | str,
    *,
    seed: int = 102,
) -> None

Apply the selected LayoutDiffusion seed mode.

Parameters:

Name Type Description Default
seed_mode LayoutDiffusionSeedMode | str

Regular or deterministic seed mode.

required
seed int

Seed used by both modes.

102

Returns:

Type Description
None

None.

Raises:

Type Description
ValueError

If the seed mode is unsupported.

Examples:

>>> apply_layoutdiffusion_seed_mode("default", seed=1)
Source code in models/layoutdiffusion/src/layoutdiffusion/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_layoutdiffusion_seed_mode(
    seed_mode: LayoutDiffusionSeedMode | str,
    *,
    seed: int = 102,
) -> None:
    """Apply the selected LayoutDiffusion 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_layoutdiffusion_seed_mode("default", seed=1)
    """
    mode = LayoutDiffusionSeedMode(seed_mode)
    if mode is LayoutDiffusionSeedMode.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 LayoutDiffusionSeedMode.deterministic:
        apply_determinism(DeterminismConfig(seed=seed))

vocab

Training vocabulary helpers for LayoutDiffusion.

build_training_tokenizer

build_training_tokenizer(
    config: LayoutDiffusionConfig,
    *,
    vocab_file: str | None = None,
) -> LayoutDiffusionTokenizer

Build a tokenizer while keeping training config vocabulary fields aligned.

Source code in models/layoutdiffusion/src/layoutdiffusion/training/vocab.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
def build_training_tokenizer(
    config: LayoutDiffusionConfig, *, vocab_file: str | None = None
) -> LayoutDiffusionTokenizer:
    """Build a tokenizer while keeping training config vocabulary fields aligned."""
    if vocab_file is None:
        return LayoutDiffusionTokenizer(config)
    vocab_path = Path(vocab_file)
    if not vocab_path.is_file():
        raise FileNotFoundError(vocab_path)

    raw_vocab = json.loads(vocab_path.read_text(encoding="utf-8"))
    vocab = {str(token): int(index) for token, index in raw_vocab.items()}
    if "MASK" not in vocab:
        vocab["MASK"] = max(vocab.values()) + 1
    id2label = _id2label_from_vocab(vocab)
    config.vocab = vocab
    config.id2label = id2label
    config.vocab_size = max(vocab.values()) + 1
    config.register_to_config(
        vocab=vocab,
        id2label={str(index): label for index, label in id2label.items()},
        vocab_size=config.vocab_size,
    )
    return LayoutDiffusionTokenizer(config)