Skip to content

Model Card for Layout-Corrector

arXiv venue license base dataset dataset dataset vendor-parity hub

This package ports Layout-Corrector, a training-free confidence model that corrects layouts generated by LayoutDM, the companion 🧨diffusers-style package for discrete diffusion layout generation.

Model Details

Model Description

Layout-Corrector refines candidate layouts by running a training-free correction stage on top of LayoutDM, a discrete diffusion generator that samples layout tokens through a denoising loop. It accepts starter layouts or nested LayoutDM samples and returns corrected element boxes, labels, masks, and optional correction traces. Public outputs use normalized center xywh boxes in [0, 1], dataset-local integer labels, a valid-element mask, and id2label.

  • Developed by: Shoma Iwai et al.
  • Shared by: creative-graphic-design.
  • Model type: content-agnostic; task: task-agnostic; conditioning: unconditional, label, label_size, completion, refinement.
  • Language(s) (NLP): not applicable.
  • License: MIT.

Model Sources

Supported Checkpoints

Checkpoint Hub ID Status
RICO25 creative-graphic-design/layout-corrector-rico25 not-published
PubLayNet creative-graphic-design/layout-corrector-publaynet not-published
Crello creative-graphic-design/layout-corrector-crello not-published

Uses

Direct Use

Use this package for research inference, conversion checks, and agreement checks against the original implementation for generated layouts.

The primary use case is to combine a LayoutDM generator with a matching Layout-Corrector confidence model:

from layout_corrector import LayoutCorrectorModel, LayoutCorrectorPipeline
from layout_dm import LayoutDMPipeline

layout_dm = LayoutDMPipeline.from_pretrained(
    ".cache/layout-dm/converted/layoutdm-rico25",
)
corrector = LayoutCorrectorModel.from_pretrained(
    ".cache/layout-corrector/converted/layout-corrector-rico25-smoke",
    subfolder="corrector",
)
# After Hub publication: use "creative-graphic-design/layoutdm-rico25" and
# "creative-graphic-design/layout-corrector-rico25".

pipe = LayoutCorrectorPipeline(layout_dm=layout_dm, corrector=corrector)
out = pipe(
    batch_size=1,
    seed=0,
    sampling="deterministic",
    corrector_t_list=(10, 20, 30),
    return_intermediates=True,
)

print(out.bbox.shape)
print(out.labels.shape)
print(out.id2label[int(out.labels[out.mask][0])])
print(out.scores.shape)

For that return_intermediates=True call, out.scores stores the Layout-Corrector confidence scores collected at the selected correction timesteps.

Without the corrector, LayoutDM samples the next layout tokens directly from its denoiser predictions. With the corrector, low-confidence reconstructed tokens are re-masked at selected timesteps, which gives LayoutDM another chance to regenerate uncertain positions and can reduce layout sticking.

LayoutDM denoiser logits
        |
        v
reconstruct x0 tokens
        |
        v
Layout-Corrector confidence score
        |
        v
re-mask low-confidence tokens
        |
        v
LayoutDM samples the next step

Conditional generation follows the nested LayoutDM processor path:

condition_type Required inputs Effect
unconditional none samples with nested LayoutDM, then applies correction
label labels, bbox, optional mask keeps labels fixed and corrects sampled geometry
label_size labels, bbox, optional mask keeps labels and element sizes fixed
completion labels, bbox, optional mask preserves provided elements and completes the layout
refinement labels, bbox, optional mask refines the provided layout
text, content_image, relation, hierarchical, retrieval not applicable raises NotImplementedError through nested LayoutDM
labels = [[1, 2, 3]]
bbox = [[[0.2, 0.2, 0.1, 0.1], [0.5, 0.5, 0.2, 0.2], [0.7, 0.7, 0.1, 0.2]]]
mask = [[True, True, True]]

out = pipe(
    condition_type="label",
    labels=labels,
    bbox=bbox,
    mask=mask,
    seed=0,
)
print(out.bbox.shape)

Downstream Use

Generated layouts may feed rendering, design tooling, layout evaluation, or downstream content placement systems after task-specific validation.

Out-of-Scope Use

Do not treat generated layouts as production accessibility annotations, OCR output, semantic scene understanding, or license-cleared design assets without separate review.

Bias, Risks, and Limitations

The converted behavior follows the upstream checkpoints, prompt fixtures, and datasets. Dataset coverage, label vocabularies, and layout quality inherit the limits of those sources.

Recommendations

Re-run the agreement-check suite against the original implementation before publishing converted checkpoints or comparing new results.

How to Get Started with the Model

Install the package directly from this repository. The command includes the shared packages required by the pipeline.

pip install \
  "laygen @ git+https://github.com/creative-graphic-design/design-generators.git#subdirectory=lib/laygen" \
  "layout-dm @ git+https://github.com/creative-graphic-design/design-generators.git#subdirectory=models/layout-dm" \
  "layout-corrector @ git+https://github.com/creative-graphic-design/design-generators.git#subdirectory=models/layout-corrector"

Clone this repository, install the workspace member, and run the download and conversion steps in REPRODUCING.md. Those steps create .cache/layout-corrector/converted/layout-corrector-rico25-smoke.

git clone https://github.com/creative-graphic-design/design-generators.git
cd design-generators
uv sync --package layout-corrector
uv run --package layout-corrector python
from layout_corrector import LayoutCorrectorPipeline

path = ".cache/layout-corrector/converted/layout-corrector-rico25-smoke"
# After Hub publication: from_pretrained("creative-graphic-design/layout-corrector-rico25")
pipe = LayoutCorrectorPipeline.from_pretrained(path)
out = pipe(batch_size=1, seed=0, num_inference_steps=1, sampling="deterministic")

print(out.bbox.shape)
print(out.labels.shape)
print(out.id2label[int(out.labels[out.mask][0])])

Training Details

Training Data

Dataset Dataset ID Notes
RICO25 creative-graphic-design/Rico ui-screenshots-and-hierarchies-with-semantic-annotations
PubLayNet creative-graphic-design/PubLayNet default
Crello cyberagent/crello canonical source until an org mirror exists

The converted checkpoints follow the original Layout-Corrector release and use the authors' released preprocessing package for exact agreement fixtures. For Crello, cyberagent/crello is the canonical Hugging Face dataset source for processor/data-path documentation; the authors' processed splits remain the agreement baseline because they are the released preprocessing artifact.

Training Procedure

This package ports released behavior and does not retrain the method in this repository. Trained-checkpoint S5 reproduction is not claimed; the package evidence covers the ordered S0-S4 agreement checks only.

Preprocessing

Inputs and outputs are normalized to the public layout schema at package boundaries. Boxes, tokens, prompts, or analog bits that follow the original implementation stay inside package adapters and fixtures used for agreement checks.

Training Hyperparameters

  • Training regime: original upstream training; not rerun in this repository.

Speeds, Sizes, Times

Training-time and carbon measurements are unknown.

Evaluation

Testing Data, Factors & Metrics

Testing Data

Agreement checks use local-only generated fixtures and converted checkpoint directories. Large generated tensors, images, weights, and downloaded artifacts are not committed.

Factors

Agreement results are reported separately by dataset, checkpoint, condition mode, seed, or prompt fixture where the package has recorded evidence.

Metrics

Metrics are exact tensor equality, exact token or byte equality, or an explicitly stated numeric tolerance against the original implementation.

Parity Results

Dataset Seed Compared path Cases Assertion
RICO25 0 original-implementation corrector logits vs. converted corrector logits 1 synthetic batch torch.allclose(atol=1e-5, rtol=1e-5)
RICO25 1 original-implementation corrector logits vs. converted corrector logits 1 synthetic batch torch.allclose(atol=1e-5, rtol=1e-5)
RICO25 2 original-implementation corrector logits vs. converted corrector logits 1 synthetic batch torch.allclose(atol=1e-5, rtol=1e-5)
PubLayNet 0 original-implementation corrector logits vs. converted corrector logits 1 synthetic batch torch.allclose(atol=1e-5, rtol=1e-5)
PubLayNet 1 original-implementation corrector logits vs. converted corrector logits 1 synthetic batch torch.allclose(atol=1e-5, rtol=1e-5)
PubLayNet 2 original-implementation corrector logits vs. converted corrector logits 1 synthetic batch torch.allclose(atol=1e-5, rtol=1e-5)
Crello 0 original-implementation corrector logits vs. converted corrector logits 1 synthetic batch torch.allclose(atol=1e-5, rtol=1e-5)
Crello 1 original-implementation corrector logits vs. converted corrector logits 1 synthetic batch torch.allclose(atol=1e-5, rtol=1e-5)
Crello 2 original-implementation corrector logits vs. converted corrector logits 1 synthetic batch torch.allclose(atol=1e-5, rtol=1e-5)

Reproducibility

See REPRODUCING.md for the commands that download original-implementation assets, generate reference outputs, run agreement checks, convert checkpoints, and smoke-test local loading.

Environmental Impact

No new model training is performed by these conversion packages. Conversion and parity costs depend on the selected checkpoint and local hardware.

Technical Specifications

Model Architecture and Objective

Layout-Corrector combines a base LayoutDM denoising pipeline with a confidence model that scores reconstructed layout tokens. Low-confidence positions can be masked and regenerated during selected timesteps, producing corrected boxes, labels, masks, score tensors, and optional correction traces.

Compute Infrastructure

Agreement-check commands are intended for one explicitly selected GPU when the original implementation requires CUDA.

Hardware

CPU is sufficient for import and most smoke tests. CUDA is required for heavyweight agreement checks when the original implementation requires it.

Software

Use uv run --package layout-corrector ... from the repository root so workspace dependency sources and extras resolve correctly.

License

Repository wrapper code is Apache-2.0. The original Layout-Corrector implementation is released under the MIT license. The nested LayoutDM implementation is Apache-2.0.

Citation

@article{iwai2024layoutcorrector,
  title = {Layout-Corrector: Alleviating Layout Sticking Phenomenon in Discrete Diffusion Model},
  author = {Iwai, Shoma and Osanai, Atsuki and Kitada, Shunsuke and Omachi, Shinichiro},
  journal = {arXiv preprint arXiv:2409.16689},
  year = {2024}
}

API Reference

Public Layout-Corrector pipeline, model, config, and sampling exports.

CorrectorPositionEmbedding

Bases: StrEnum

Supported original position-embedding modes.

Source code in models/layout-corrector/src/layout_corrector/configuration_layout_corrector.py
35
36
37
38
39
40
41
42
class CorrectorPositionEmbedding(StrEnum):
    """Supported original position-embedding modes."""

    default = auto()
    none = auto()
    pos_enc = auto()
    shuffle_pos_enc = auto()
    shuffle = auto()

CorrectorReconType

Bases: StrEnum

Supported Layout-Corrector reconstruction targets.

Source code in models/layout-corrector/src/layout_corrector/configuration_layout_corrector.py
21
22
23
24
25
class CorrectorReconType(StrEnum):
    """Supported Layout-Corrector reconstruction targets."""

    x_0 = auto()
    x_t_minus_1 = "x_t-1"

CorrectorTarget

Bases: StrEnum

Supported Layout-Corrector confidence targets.

Source code in models/layout-corrector/src/layout_corrector/configuration_layout_corrector.py
28
29
30
31
32
class CorrectorTarget(StrEnum):
    """Supported Layout-Corrector confidence targets."""

    mask = auto()
    recon_acc = auto()

CorrectorTransformerType

Bases: StrEnum

Supported corrector transformer variants.

Source code in models/layout-corrector/src/layout_corrector/configuration_layout_corrector.py
45
46
47
48
class CorrectorTransformerType(StrEnum):
    """Supported corrector transformer variants."""

    aggregated = auto()

LayoutCorrectorConfig

Bases: ConfigMixin

Configuration for the Layout-Corrector transformer.

Parameters:

Name Type Description Default
dataset_name DatasetName | str

Dataset key or alias used for labels.

required
vocab_size int

LayoutDM vocabulary size expected by the corrector.

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

Optional class-id mapping. When omitted, the shared registry is used.

None
max_seq_length int

Maximum number of layout elements.

25
num_attributes_per_element int

Number of token attributes per element.

5
hidden_size int

Transformer hidden dimension.

464
num_attention_heads int

Number of attention heads.

8
num_hidden_layers int

Number of transformer layers.

4
intermediate_size int

Feed-forward hidden dimension.

1856
dropout float

Dropout probability.

0.0
timestep_type str | None

Timestep conditioning type.

'adalayernorm'
num_timesteps int

Number of diffusion training timesteps.

100
recon_type CorrectorReconType | str

Reconstruction target used by the corrector.

x_t_minus_1
target CorrectorTarget | str

Confidence target type.

recon_acc
attr_loss_weights tuple[float, float, float, float, float]

Per-attribute loss weights.

(1.0, 1.0, 1.0, 1.0, 1.0)
use_padding_as_vocab bool

Whether padding is part of the modeled vocabulary.

True
pos_emb CorrectorPositionEmbedding | str

Position embedding mode from the original implementation.

none
transformer_type CorrectorTransformerType | str

Corrector transformer variant.

aggregated
corrector_steps int

Number of correction passes per selected timestep.

1
corrector_t_list tuple[int, ...]

Explicit timesteps where the corrector is applied.

(10, 20, 30)
corrector_mask_mode CorrectorMaskMode | str

Strategy for selecting tokens to remask.

thresh
corrector_mask_threshold float

Confidence threshold for threshold masking.

0.7
corrector_temperature float

Temperature used for corrector resampling.

1.0
use_gumbel_noise bool

Whether to perturb confidence logits.

True
gumbel_temperature float

Temperature for confidence Gumbel noise.

1.0
time_adaptive_temperature bool

Whether to scale noise by timestep ratio.

False

Raises:

Type Description
ValueError

If a supplied dataset, shape, or option is unsupported.

Examples:

>>> cfg = LayoutCorrectorConfig(dataset_name="publaynet", vocab_size=100)
>>> cfg.max_token_length
125
Source code in models/layout-corrector/src/layout_corrector/configuration_layout_corrector.py
 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
class LayoutCorrectorConfig(ConfigMixin):
    """Configuration for the Layout-Corrector transformer.

    Args:
        dataset_name: Dataset key or alias used for labels.
        vocab_size: LayoutDM vocabulary size expected by the corrector.
        id2label: Optional class-id mapping. When omitted, the shared registry is used.
        max_seq_length: Maximum number of layout elements.
        num_attributes_per_element: Number of token attributes per element.
        hidden_size: Transformer hidden dimension.
        num_attention_heads: Number of attention heads.
        num_hidden_layers: Number of transformer layers.
        intermediate_size: Feed-forward hidden dimension.
        dropout: Dropout probability.
        timestep_type: Timestep conditioning type.
        num_timesteps: Number of diffusion training timesteps.
        recon_type: Reconstruction target used by the corrector.
        target: Confidence target type.
        attr_loss_weights: Per-attribute loss weights.
        use_padding_as_vocab: Whether padding is part of the modeled vocabulary.
        pos_emb: Position embedding mode from the original implementation.
        transformer_type: Corrector transformer variant.
        corrector_steps: Number of correction passes per selected timestep.
        corrector_t_list: Explicit timesteps where the corrector is applied.
        corrector_mask_mode: Strategy for selecting tokens to remask.
        corrector_mask_threshold: Confidence threshold for threshold masking.
        corrector_temperature: Temperature used for corrector resampling.
        use_gumbel_noise: Whether to perturb confidence logits.
        gumbel_temperature: Temperature for confidence Gumbel noise.
        time_adaptive_temperature: Whether to scale noise by timestep ratio.

    Raises:
        ValueError: If a supplied dataset, shape, or option is unsupported.

    Examples:
        >>> cfg = LayoutCorrectorConfig(dataset_name="publaynet", vocab_size=100)
        >>> cfg.max_token_length
        125
    """

    config_name = "corrector_config.json"

    @register_to_config
    def __init__(
        self,
        *,
        dataset_name: DatasetName | str,
        vocab_size: int,
        id2label: dict[int | str, str] | None = None,
        max_seq_length: int = 25,
        num_attributes_per_element: int = 5,
        hidden_size: int = 464,
        num_attention_heads: int = 8,
        num_hidden_layers: int = 4,
        intermediate_size: int = 1856,
        dropout: float = 0.0,
        timestep_type: str | None = "adalayernorm",
        num_timesteps: int = 100,
        recon_type: CorrectorReconType | str = CorrectorReconType.x_t_minus_1,
        target: CorrectorTarget | str = CorrectorTarget.recon_acc,
        attr_loss_weights: tuple[float, float, float, float, float] = (
            1.0,
            1.0,
            1.0,
            1.0,
            1.0,
        ),
        use_padding_as_vocab: bool = True,
        pos_emb: CorrectorPositionEmbedding | str = CorrectorPositionEmbedding.none,
        transformer_type: CorrectorTransformerType | str = (
            CorrectorTransformerType.aggregated
        ),
        corrector_steps: int = 1,
        corrector_t_list: tuple[int, ...] = (10, 20, 30),
        corrector_mask_mode: CorrectorMaskMode | str = CorrectorMaskMode.thresh,
        corrector_mask_threshold: float = 0.7,
        corrector_temperature: float = 1.0,
        use_gumbel_noise: bool = True,
        gumbel_temperature: float = 1.0,
        time_adaptive_temperature: bool = False,
    ) -> None:
        """Initialize a Layout-Corrector config.

        Args:
            dataset_name: Dataset key or alias used for labels.
            vocab_size: LayoutDM vocabulary size expected by the corrector.
            id2label: Optional class-id mapping.
            max_seq_length: Maximum number of layout elements.
            num_attributes_per_element: Number of token attributes per element.
            hidden_size: Transformer hidden dimension.
            num_attention_heads: Number of attention heads.
            num_hidden_layers: Number of transformer layers.
            intermediate_size: Feed-forward hidden dimension.
            dropout: Dropout probability.
            timestep_type: Timestep conditioning type.
            num_timesteps: Number of diffusion training timesteps.
            recon_type: Reconstruction target used by the corrector.
            target: Confidence target type.
            attr_loss_weights: Per-attribute loss weights.
            use_padding_as_vocab: Whether padding is part of the modeled vocabulary.
            pos_emb: Position embedding mode.
            transformer_type: Corrector transformer variant.
            corrector_steps: Number of correction passes per selected timestep.
            corrector_t_list: Explicit timesteps where the corrector is applied.
            corrector_mask_mode: Strategy for selecting tokens to remask.
            corrector_mask_threshold: Confidence threshold for threshold masking.
            corrector_temperature: Temperature used for corrector resampling.
            use_gumbel_noise: Whether to perturb confidence logits.
            gumbel_temperature: Temperature for confidence Gumbel noise.
            time_adaptive_temperature: Whether to scale noise by timestep ratio.

        Raises:
            ValueError: If a supplied dataset, shape, or option is unsupported.

        Examples:
            >>> cfg = LayoutCorrectorConfig(dataset_name="publaynet", vocab_size=100)
            >>> cfg.max_token_length
            125
        """
        try:
            dataset_name = str(normalize_dataset_name(dataset_name))
        except ValueError:
            if id2label is None:
                raise

            dataset_name = str(dataset_name)
        self.register_to_config(dataset_name=dataset_name)
        if vocab_size <= 0:
            raise ValueError("vocab_size must be positive")

        if max_seq_length <= 0:
            raise ValueError("max_seq_length must be positive")

        if num_attributes_per_element != 5:
            raise ValueError("Layout-Corrector supports 5 attributes per element")

        if num_timesteps <= 0:
            raise ValueError("num_timesteps must be positive")

        recon_type, target, transformer_type, pos_emb = (
            normalize_corrector_core_options(
                recon_type,
                target,
                transformer_type,
                pos_emb,
            )
        )
        if len(attr_loss_weights) != num_attributes_per_element:
            raise ValueError("attr_loss_weights must match num_attributes_per_element")

        if corrector_steps <= 0:
            raise ValueError("corrector_steps must be positive")

        try:
            corrector_mask_mode = normalize_corrector_mask_mode(corrector_mask_mode)
        except ValueError as exc:
            raise ValueError(
                f"Unsupported corrector_mask_mode: {corrector_mask_mode}"
            ) from exc

        if not 0.0 <= corrector_mask_threshold <= 1.0:
            raise ValueError("corrector_mask_threshold must be in [0, 1]")

        self.register_to_config(
            dataset_name=dataset_name,
            recon_type=str(recon_type),
            target=str(target),
            pos_emb=str(pos_emb),
            transformer_type=str(transformer_type),
            corrector_mask_mode=str(corrector_mask_mode),
        )

        self.dataset_name = dataset_name
        raw_id2label = id2label or id2label_for_dataset(dataset_name)
        self.id2label = {int(k): v for k, v in raw_id2label.items()}
        self.vocab_size = vocab_size
        self.max_seq_length = max_seq_length
        self.num_attributes_per_element = num_attributes_per_element
        self.hidden_size = hidden_size
        self.num_attention_heads = num_attention_heads
        self.num_hidden_layers = num_hidden_layers
        self.intermediate_size = intermediate_size
        self.dropout = dropout
        self.timestep_type = timestep_type
        self.num_timesteps = num_timesteps

        self.recon_type = str(recon_type)
        self.target = str(target)
        self.attr_loss_weights = tuple(float(v) for v in attr_loss_weights)
        self.use_padding_as_vocab = use_padding_as_vocab
        self.pos_emb = str(pos_emb)
        self.transformer_type = str(transformer_type)

        self.corrector_steps = corrector_steps
        self.corrector_t_list = tuple(int(v) for v in corrector_t_list)
        self.corrector_mask_mode = str(corrector_mask_mode)
        self.corrector_mask_threshold = corrector_mask_threshold
        self.corrector_temperature = corrector_temperature
        self.use_gumbel_noise = use_gumbel_noise
        self.gumbel_temperature = gumbel_temperature
        self.time_adaptive_temperature = time_adaptive_temperature

    @property
    def max_token_length(self) -> int:
        """Return the flattened token length for one layout sequence.

        Returns:
            Maximum token count after flattening element attributes.

        Examples:
            >>> LayoutCorrectorConfig(dataset_name="publaynet", vocab_size=100).max_token_length
            125
        """
        return self.max_seq_length * self.num_attributes_per_element

max_token_length property

max_token_length: int

Return the flattened token length for one layout sequence.

Returns:

Type Description
int

Maximum token count after flattening element attributes.

Examples:

>>> LayoutCorrectorConfig(dataset_name="publaynet", vocab_size=100).max_token_length
125

__init__

__init__(
    *,
    dataset_name: DatasetName | str,
    vocab_size: int,
    id2label: dict[int | str, str] | None = None,
    max_seq_length: int = 25,
    num_attributes_per_element: int = 5,
    hidden_size: int = 464,
    num_attention_heads: int = 8,
    num_hidden_layers: int = 4,
    intermediate_size: int = 1856,
    dropout: float = 0.0,
    timestep_type: str | None = "adalayernorm",
    num_timesteps: int = 100,
    recon_type: CorrectorReconType
    | str = CorrectorReconType.x_t_minus_1,
    target: CorrectorTarget
    | str = CorrectorTarget.recon_acc,
    attr_loss_weights: tuple[
        float, float, float, float, float
    ] = (1.0, 1.0, 1.0, 1.0, 1.0),
    use_padding_as_vocab: bool = True,
    pos_emb: CorrectorPositionEmbedding
    | str = CorrectorPositionEmbedding.none,
    transformer_type: CorrectorTransformerType
    | str = CorrectorTransformerType.aggregated,
    corrector_steps: int = 1,
    corrector_t_list: tuple[int, ...] = (10, 20, 30),
    corrector_mask_mode: CorrectorMaskMode
    | str = CorrectorMaskMode.thresh,
    corrector_mask_threshold: float = 0.7,
    corrector_temperature: float = 1.0,
    use_gumbel_noise: bool = True,
    gumbel_temperature: float = 1.0,
    time_adaptive_temperature: bool = False,
) -> None

Initialize a Layout-Corrector config.

Parameters:

Name Type Description Default
dataset_name DatasetName | str

Dataset key or alias used for labels.

required
vocab_size int

LayoutDM vocabulary size expected by the corrector.

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

Optional class-id mapping.

None
max_seq_length int

Maximum number of layout elements.

25
num_attributes_per_element int

Number of token attributes per element.

5
hidden_size int

Transformer hidden dimension.

464
num_attention_heads int

Number of attention heads.

8
num_hidden_layers int

Number of transformer layers.

4
intermediate_size int

Feed-forward hidden dimension.

1856
dropout float

Dropout probability.

0.0
timestep_type str | None

Timestep conditioning type.

'adalayernorm'
num_timesteps int

Number of diffusion training timesteps.

100
recon_type CorrectorReconType | str

Reconstruction target used by the corrector.

x_t_minus_1
target CorrectorTarget | str

Confidence target type.

recon_acc
attr_loss_weights tuple[float, float, float, float, float]

Per-attribute loss weights.

(1.0, 1.0, 1.0, 1.0, 1.0)
use_padding_as_vocab bool

Whether padding is part of the modeled vocabulary.

True
pos_emb CorrectorPositionEmbedding | str

Position embedding mode.

none
transformer_type CorrectorTransformerType | str

Corrector transformer variant.

aggregated
corrector_steps int

Number of correction passes per selected timestep.

1
corrector_t_list tuple[int, ...]

Explicit timesteps where the corrector is applied.

(10, 20, 30)
corrector_mask_mode CorrectorMaskMode | str

Strategy for selecting tokens to remask.

thresh
corrector_mask_threshold float

Confidence threshold for threshold masking.

0.7
corrector_temperature float

Temperature used for corrector resampling.

1.0
use_gumbel_noise bool

Whether to perturb confidence logits.

True
gumbel_temperature float

Temperature for confidence Gumbel noise.

1.0
time_adaptive_temperature bool

Whether to scale noise by timestep ratio.

False

Raises:

Type Description
ValueError

If a supplied dataset, shape, or option is unsupported.

Examples:

>>> cfg = LayoutCorrectorConfig(dataset_name="publaynet", vocab_size=100)
>>> cfg.max_token_length
125
Source code in models/layout-corrector/src/layout_corrector/configuration_layout_corrector.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
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
@register_to_config
def __init__(
    self,
    *,
    dataset_name: DatasetName | str,
    vocab_size: int,
    id2label: dict[int | str, str] | None = None,
    max_seq_length: int = 25,
    num_attributes_per_element: int = 5,
    hidden_size: int = 464,
    num_attention_heads: int = 8,
    num_hidden_layers: int = 4,
    intermediate_size: int = 1856,
    dropout: float = 0.0,
    timestep_type: str | None = "adalayernorm",
    num_timesteps: int = 100,
    recon_type: CorrectorReconType | str = CorrectorReconType.x_t_minus_1,
    target: CorrectorTarget | str = CorrectorTarget.recon_acc,
    attr_loss_weights: tuple[float, float, float, float, float] = (
        1.0,
        1.0,
        1.0,
        1.0,
        1.0,
    ),
    use_padding_as_vocab: bool = True,
    pos_emb: CorrectorPositionEmbedding | str = CorrectorPositionEmbedding.none,
    transformer_type: CorrectorTransformerType | str = (
        CorrectorTransformerType.aggregated
    ),
    corrector_steps: int = 1,
    corrector_t_list: tuple[int, ...] = (10, 20, 30),
    corrector_mask_mode: CorrectorMaskMode | str = CorrectorMaskMode.thresh,
    corrector_mask_threshold: float = 0.7,
    corrector_temperature: float = 1.0,
    use_gumbel_noise: bool = True,
    gumbel_temperature: float = 1.0,
    time_adaptive_temperature: bool = False,
) -> None:
    """Initialize a Layout-Corrector config.

    Args:
        dataset_name: Dataset key or alias used for labels.
        vocab_size: LayoutDM vocabulary size expected by the corrector.
        id2label: Optional class-id mapping.
        max_seq_length: Maximum number of layout elements.
        num_attributes_per_element: Number of token attributes per element.
        hidden_size: Transformer hidden dimension.
        num_attention_heads: Number of attention heads.
        num_hidden_layers: Number of transformer layers.
        intermediate_size: Feed-forward hidden dimension.
        dropout: Dropout probability.
        timestep_type: Timestep conditioning type.
        num_timesteps: Number of diffusion training timesteps.
        recon_type: Reconstruction target used by the corrector.
        target: Confidence target type.
        attr_loss_weights: Per-attribute loss weights.
        use_padding_as_vocab: Whether padding is part of the modeled vocabulary.
        pos_emb: Position embedding mode.
        transformer_type: Corrector transformer variant.
        corrector_steps: Number of correction passes per selected timestep.
        corrector_t_list: Explicit timesteps where the corrector is applied.
        corrector_mask_mode: Strategy for selecting tokens to remask.
        corrector_mask_threshold: Confidence threshold for threshold masking.
        corrector_temperature: Temperature used for corrector resampling.
        use_gumbel_noise: Whether to perturb confidence logits.
        gumbel_temperature: Temperature for confidence Gumbel noise.
        time_adaptive_temperature: Whether to scale noise by timestep ratio.

    Raises:
        ValueError: If a supplied dataset, shape, or option is unsupported.

    Examples:
        >>> cfg = LayoutCorrectorConfig(dataset_name="publaynet", vocab_size=100)
        >>> cfg.max_token_length
        125
    """
    try:
        dataset_name = str(normalize_dataset_name(dataset_name))
    except ValueError:
        if id2label is None:
            raise

        dataset_name = str(dataset_name)
    self.register_to_config(dataset_name=dataset_name)
    if vocab_size <= 0:
        raise ValueError("vocab_size must be positive")

    if max_seq_length <= 0:
        raise ValueError("max_seq_length must be positive")

    if num_attributes_per_element != 5:
        raise ValueError("Layout-Corrector supports 5 attributes per element")

    if num_timesteps <= 0:
        raise ValueError("num_timesteps must be positive")

    recon_type, target, transformer_type, pos_emb = (
        normalize_corrector_core_options(
            recon_type,
            target,
            transformer_type,
            pos_emb,
        )
    )
    if len(attr_loss_weights) != num_attributes_per_element:
        raise ValueError("attr_loss_weights must match num_attributes_per_element")

    if corrector_steps <= 0:
        raise ValueError("corrector_steps must be positive")

    try:
        corrector_mask_mode = normalize_corrector_mask_mode(corrector_mask_mode)
    except ValueError as exc:
        raise ValueError(
            f"Unsupported corrector_mask_mode: {corrector_mask_mode}"
        ) from exc

    if not 0.0 <= corrector_mask_threshold <= 1.0:
        raise ValueError("corrector_mask_threshold must be in [0, 1]")

    self.register_to_config(
        dataset_name=dataset_name,
        recon_type=str(recon_type),
        target=str(target),
        pos_emb=str(pos_emb),
        transformer_type=str(transformer_type),
        corrector_mask_mode=str(corrector_mask_mode),
    )

    self.dataset_name = dataset_name
    raw_id2label = id2label or id2label_for_dataset(dataset_name)
    self.id2label = {int(k): v for k, v in raw_id2label.items()}
    self.vocab_size = vocab_size
    self.max_seq_length = max_seq_length
    self.num_attributes_per_element = num_attributes_per_element
    self.hidden_size = hidden_size
    self.num_attention_heads = num_attention_heads
    self.num_hidden_layers = num_hidden_layers
    self.intermediate_size = intermediate_size
    self.dropout = dropout
    self.timestep_type = timestep_type
    self.num_timesteps = num_timesteps

    self.recon_type = str(recon_type)
    self.target = str(target)
    self.attr_loss_weights = tuple(float(v) for v in attr_loss_weights)
    self.use_padding_as_vocab = use_padding_as_vocab
    self.pos_emb = str(pos_emb)
    self.transformer_type = str(transformer_type)

    self.corrector_steps = corrector_steps
    self.corrector_t_list = tuple(int(v) for v in corrector_t_list)
    self.corrector_mask_mode = str(corrector_mask_mode)
    self.corrector_mask_threshold = corrector_mask_threshold
    self.corrector_temperature = corrector_temperature
    self.use_gumbel_noise = use_gumbel_noise
    self.gumbel_temperature = gumbel_temperature
    self.time_adaptive_temperature = time_adaptive_temperature

LayoutCorrectorModel

Bases: ModelMixin, ConfigMixin

Diffusers-compatible Layout-Corrector confidence model.

Source code in models/layout-corrector/src/layout_corrector/modeling_layout_corrector.py
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
class LayoutCorrectorModel(ModelMixin, ConfigMixin):
    """Diffusers-compatible Layout-Corrector confidence model."""

    config_name = "corrector_config.json"

    @register_to_config
    def __init__(
        self,
        *,
        dataset_name: str,
        vocab_size: int,
        id2label: dict[int | str, str] | None = None,
        max_seq_length: int = 25,
        num_attributes_per_element: int = 5,
        hidden_size: int = 464,
        num_attention_heads: int = 8,
        num_hidden_layers: int = 4,
        intermediate_size: int = 1856,
        dropout: float = 0.0,
        timestep_type: TimestepEmbeddingType | str | None = "adalayernorm",
        num_timesteps: int = 100,
        recon_type: CorrectorReconType | str = CorrectorReconType.x_t_minus_1,
        target: CorrectorTarget | str = CorrectorTarget.recon_acc,
        attr_loss_weights: tuple[float, ...] = (1.0, 1.0, 1.0, 1.0, 1.0),
        use_padding_as_vocab: bool = True,
        pos_emb: CorrectorPositionEmbedding | str = CorrectorPositionEmbedding.none,
        transformer_type: CorrectorTransformerType | str = (
            CorrectorTransformerType.aggregated
        ),
        corrector_steps: int = 1,
        corrector_t_list: tuple[int, ...] = (10, 20, 30),
        corrector_mask_mode: CorrectorMaskMode | str = CorrectorMaskMode.thresh,
        corrector_mask_threshold: float = 0.7,
        corrector_temperature: float = 1.0,
        use_gumbel_noise: bool = True,
        gumbel_temperature: float = 1.0,
        time_adaptive_temperature: bool = False,
    ) -> None:
        """Initialize a Layout-Corrector model.

        Args:
            dataset_name: Dataset key or alias used for labels.
            vocab_size: LayoutDM vocabulary size.
            id2label: Optional class-id mapping.
            max_seq_length: Maximum number of elements.
            num_attributes_per_element: Number of token attributes per element.
            hidden_size: Transformer hidden dimension.
            num_attention_heads: Number of attention heads.
            num_hidden_layers: Number of transformer layers.
            intermediate_size: Feed-forward hidden dimension.
            dropout: Dropout probability.
            timestep_type: Timestep conditioning type.
            num_timesteps: Number of diffusion timesteps.
            recon_type: Reconstruction target.
            target: Confidence target type.
            attr_loss_weights: Per-attribute loss weights.
            use_padding_as_vocab: Whether padding is modeled as a vocabulary token.
            pos_emb: Position embedding mode.
            transformer_type: Corrector transformer type.
            corrector_steps: Number of correction passes.
            corrector_t_list: Explicit correction timesteps.
            corrector_mask_mode: Token remasking mode.
            corrector_mask_threshold: Threshold for confidence remasking.
            corrector_temperature: Corrector sampling temperature.
            use_gumbel_noise: Whether confidence logits receive Gumbel noise.
            gumbel_temperature: Confidence-noise temperature.
            time_adaptive_temperature: Whether to scale noise by timestep ratio.

        Raises:
            ValueError: If reconstruction, target, or transformer options are
                unsupported.
        """
        super().__init__()
        recon_type, target, transformer_type, pos_emb = (
            normalize_corrector_core_options(
                recon_type,
                target,
                transformer_type,
                pos_emb,
            )
        )
        try:
            corrector_mask_mode = normalize_corrector_mask_mode(corrector_mask_mode)
        except ValueError as exc:
            raise ValueError(
                f"Unsupported corrector_mask_mode: {corrector_mask_mode}"
            ) from exc

        try:
            dataset_name = str(normalize_dataset_name(dataset_name))
        except ValueError:
            if id2label is None:
                raise

            dataset_name = str(dataset_name)
        normalized_id2label = {
            int(k): v
            for k, v in (id2label or id2label_for_dataset(dataset_name)).items()
        }
        self.register_to_config(
            dataset_name=dataset_name,
            id2label=normalized_id2label,
            corrector_t_list=tuple(corrector_t_list),
            attr_loss_weights=tuple(attr_loss_weights),
            recon_type=str(recon_type),
            target=str(target),
            pos_emb=str(pos_emb),
            transformer_type=str(transformer_type),
            corrector_mask_mode=str(corrector_mask_mode),
        )
        self.vocab_size = vocab_size
        self.id2label = normalized_id2label
        self.recon_type = recon_type
        self.corrector_steps = corrector_steps
        self.corrector_t_list = tuple(corrector_t_list)
        self.corrector_mask_mode = corrector_mask_mode
        self.corrector_mask_threshold = corrector_mask_threshold
        self.corrector_temperature = corrector_temperature
        self.use_padding_as_vocab = use_padding_as_vocab
        self.use_gumbel_noise = use_gumbel_noise
        self.gumbel_temperature = gumbel_temperature
        self.time_adaptive_temperature = time_adaptive_temperature
        self.model = AggregatedCategoricalTransformer(
            vocab_size=vocab_size,
            max_token_length=max_seq_length * num_attributes_per_element,
            hidden_size=hidden_size,
            num_attention_heads=num_attention_heads,
            num_hidden_layers=num_hidden_layers,
            intermediate_size=intermediate_size,
            dropout=dropout,
            timestep_type=timestep_type,
            pos_emb=pos_emb,
            num_attributes_per_element=num_attributes_per_element,
            num_timesteps=num_timesteps,
        )

    def initialize_weights(self) -> None:
        """Apply the released training initialization on the active device."""
        self.apply(_init_layout_corrector_weights)

    def forward(
        self,
        input_ids: Int[torch.Tensor, "batch tokens"],
        timesteps: Int[torch.Tensor, "batch"],
        padding_mask: Bool[torch.Tensor, "batch tokens"] | None = None,
    ) -> LayoutCorrectorOutput:
        """Run the corrector model.

        Args:
            input_ids: Flattened token ids.
            timesteps: Diffusion timestep tensor.
            padding_mask: Optional padding mask.

        Returns:
            `LayoutCorrectorOutput` containing token confidence logits.
        """
        src_key_padding_mask = None if self.use_padding_as_vocab else padding_mask
        logits = self.model(
            input_ids,
            timestep=timesteps,
            src_key_padding_mask=src_key_padding_mask,
        ).squeeze(-1)
        if not self.use_padding_as_vocab and padding_mask is not None:
            logits = logits.masked_fill(padding_mask, 1000.0)
        return LayoutCorrectorOutput(logits=logits)

    def calc_confidence_score(
        self,
        input_ids: Int[torch.Tensor, "batch tokens"],
        timesteps: Int[torch.Tensor, "batch"],
        padding_mask: Bool[torch.Tensor, "batch tokens"] | None = None,
    ) -> Float[torch.Tensor, "batch tokens"]:
        """Return confidence logits for token remasking.

        Args:
            input_ids: Flattened token ids.
            timesteps: Diffusion timestep tensor.
            padding_mask: Optional padding mask.

        Returns:
            Confidence logits shaped `(batch, tokens)`.
        """
        return self(
            input_ids=input_ids,
            timesteps=timesteps,
            padding_mask=padding_mask,
        ).logits

__init__

__init__(
    *,
    dataset_name: str,
    vocab_size: int,
    id2label: dict[int | str, str] | None = None,
    max_seq_length: int = 25,
    num_attributes_per_element: int = 5,
    hidden_size: int = 464,
    num_attention_heads: int = 8,
    num_hidden_layers: int = 4,
    intermediate_size: int = 1856,
    dropout: float = 0.0,
    timestep_type: TimestepEmbeddingType
    | str
    | None = "adalayernorm",
    num_timesteps: int = 100,
    recon_type: CorrectorReconType
    | str = CorrectorReconType.x_t_minus_1,
    target: CorrectorTarget
    | str = CorrectorTarget.recon_acc,
    attr_loss_weights: tuple[float, ...] = (
        1.0,
        1.0,
        1.0,
        1.0,
        1.0,
    ),
    use_padding_as_vocab: bool = True,
    pos_emb: CorrectorPositionEmbedding
    | str = CorrectorPositionEmbedding.none,
    transformer_type: CorrectorTransformerType
    | str = CorrectorTransformerType.aggregated,
    corrector_steps: int = 1,
    corrector_t_list: tuple[int, ...] = (10, 20, 30),
    corrector_mask_mode: CorrectorMaskMode
    | str = CorrectorMaskMode.thresh,
    corrector_mask_threshold: float = 0.7,
    corrector_temperature: float = 1.0,
    use_gumbel_noise: bool = True,
    gumbel_temperature: float = 1.0,
    time_adaptive_temperature: bool = False,
) -> None

Initialize a Layout-Corrector model.

Parameters:

Name Type Description Default
dataset_name str

Dataset key or alias used for labels.

required
vocab_size int

LayoutDM vocabulary size.

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

Optional class-id mapping.

None
max_seq_length int

Maximum number of elements.

25
num_attributes_per_element int

Number of token attributes per element.

5
hidden_size int

Transformer hidden dimension.

464
num_attention_heads int

Number of attention heads.

8
num_hidden_layers int

Number of transformer layers.

4
intermediate_size int

Feed-forward hidden dimension.

1856
dropout float

Dropout probability.

0.0
timestep_type TimestepEmbeddingType | str | None

Timestep conditioning type.

'adalayernorm'
num_timesteps int

Number of diffusion timesteps.

100
recon_type CorrectorReconType | str

Reconstruction target.

x_t_minus_1
target CorrectorTarget | str

Confidence target type.

recon_acc
attr_loss_weights tuple[float, ...]

Per-attribute loss weights.

(1.0, 1.0, 1.0, 1.0, 1.0)
use_padding_as_vocab bool

Whether padding is modeled as a vocabulary token.

True
pos_emb CorrectorPositionEmbedding | str

Position embedding mode.

none
transformer_type CorrectorTransformerType | str

Corrector transformer type.

aggregated
corrector_steps int

Number of correction passes.

1
corrector_t_list tuple[int, ...]

Explicit correction timesteps.

(10, 20, 30)
corrector_mask_mode CorrectorMaskMode | str

Token remasking mode.

thresh
corrector_mask_threshold float

Threshold for confidence remasking.

0.7
corrector_temperature float

Corrector sampling temperature.

1.0
use_gumbel_noise bool

Whether confidence logits receive Gumbel noise.

True
gumbel_temperature float

Confidence-noise temperature.

1.0
time_adaptive_temperature bool

Whether to scale noise by timestep ratio.

False

Raises:

Type Description
ValueError

If reconstruction, target, or transformer options are unsupported.

Source code in models/layout-corrector/src/layout_corrector/modeling_layout_corrector.py
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
@register_to_config
def __init__(
    self,
    *,
    dataset_name: str,
    vocab_size: int,
    id2label: dict[int | str, str] | None = None,
    max_seq_length: int = 25,
    num_attributes_per_element: int = 5,
    hidden_size: int = 464,
    num_attention_heads: int = 8,
    num_hidden_layers: int = 4,
    intermediate_size: int = 1856,
    dropout: float = 0.0,
    timestep_type: TimestepEmbeddingType | str | None = "adalayernorm",
    num_timesteps: int = 100,
    recon_type: CorrectorReconType | str = CorrectorReconType.x_t_minus_1,
    target: CorrectorTarget | str = CorrectorTarget.recon_acc,
    attr_loss_weights: tuple[float, ...] = (1.0, 1.0, 1.0, 1.0, 1.0),
    use_padding_as_vocab: bool = True,
    pos_emb: CorrectorPositionEmbedding | str = CorrectorPositionEmbedding.none,
    transformer_type: CorrectorTransformerType | str = (
        CorrectorTransformerType.aggregated
    ),
    corrector_steps: int = 1,
    corrector_t_list: tuple[int, ...] = (10, 20, 30),
    corrector_mask_mode: CorrectorMaskMode | str = CorrectorMaskMode.thresh,
    corrector_mask_threshold: float = 0.7,
    corrector_temperature: float = 1.0,
    use_gumbel_noise: bool = True,
    gumbel_temperature: float = 1.0,
    time_adaptive_temperature: bool = False,
) -> None:
    """Initialize a Layout-Corrector model.

    Args:
        dataset_name: Dataset key or alias used for labels.
        vocab_size: LayoutDM vocabulary size.
        id2label: Optional class-id mapping.
        max_seq_length: Maximum number of elements.
        num_attributes_per_element: Number of token attributes per element.
        hidden_size: Transformer hidden dimension.
        num_attention_heads: Number of attention heads.
        num_hidden_layers: Number of transformer layers.
        intermediate_size: Feed-forward hidden dimension.
        dropout: Dropout probability.
        timestep_type: Timestep conditioning type.
        num_timesteps: Number of diffusion timesteps.
        recon_type: Reconstruction target.
        target: Confidence target type.
        attr_loss_weights: Per-attribute loss weights.
        use_padding_as_vocab: Whether padding is modeled as a vocabulary token.
        pos_emb: Position embedding mode.
        transformer_type: Corrector transformer type.
        corrector_steps: Number of correction passes.
        corrector_t_list: Explicit correction timesteps.
        corrector_mask_mode: Token remasking mode.
        corrector_mask_threshold: Threshold for confidence remasking.
        corrector_temperature: Corrector sampling temperature.
        use_gumbel_noise: Whether confidence logits receive Gumbel noise.
        gumbel_temperature: Confidence-noise temperature.
        time_adaptive_temperature: Whether to scale noise by timestep ratio.

    Raises:
        ValueError: If reconstruction, target, or transformer options are
            unsupported.
    """
    super().__init__()
    recon_type, target, transformer_type, pos_emb = (
        normalize_corrector_core_options(
            recon_type,
            target,
            transformer_type,
            pos_emb,
        )
    )
    try:
        corrector_mask_mode = normalize_corrector_mask_mode(corrector_mask_mode)
    except ValueError as exc:
        raise ValueError(
            f"Unsupported corrector_mask_mode: {corrector_mask_mode}"
        ) from exc

    try:
        dataset_name = str(normalize_dataset_name(dataset_name))
    except ValueError:
        if id2label is None:
            raise

        dataset_name = str(dataset_name)
    normalized_id2label = {
        int(k): v
        for k, v in (id2label or id2label_for_dataset(dataset_name)).items()
    }
    self.register_to_config(
        dataset_name=dataset_name,
        id2label=normalized_id2label,
        corrector_t_list=tuple(corrector_t_list),
        attr_loss_weights=tuple(attr_loss_weights),
        recon_type=str(recon_type),
        target=str(target),
        pos_emb=str(pos_emb),
        transformer_type=str(transformer_type),
        corrector_mask_mode=str(corrector_mask_mode),
    )
    self.vocab_size = vocab_size
    self.id2label = normalized_id2label
    self.recon_type = recon_type
    self.corrector_steps = corrector_steps
    self.corrector_t_list = tuple(corrector_t_list)
    self.corrector_mask_mode = corrector_mask_mode
    self.corrector_mask_threshold = corrector_mask_threshold
    self.corrector_temperature = corrector_temperature
    self.use_padding_as_vocab = use_padding_as_vocab
    self.use_gumbel_noise = use_gumbel_noise
    self.gumbel_temperature = gumbel_temperature
    self.time_adaptive_temperature = time_adaptive_temperature
    self.model = AggregatedCategoricalTransformer(
        vocab_size=vocab_size,
        max_token_length=max_seq_length * num_attributes_per_element,
        hidden_size=hidden_size,
        num_attention_heads=num_attention_heads,
        num_hidden_layers=num_hidden_layers,
        intermediate_size=intermediate_size,
        dropout=dropout,
        timestep_type=timestep_type,
        pos_emb=pos_emb,
        num_attributes_per_element=num_attributes_per_element,
        num_timesteps=num_timesteps,
    )

initialize_weights

initialize_weights() -> None

Apply the released training initialization on the active device.

Source code in models/layout-corrector/src/layout_corrector/modeling_layout_corrector.py
317
318
319
def initialize_weights(self) -> None:
    """Apply the released training initialization on the active device."""
    self.apply(_init_layout_corrector_weights)

forward

forward(
    input_ids: Int[Tensor, "batch tokens"],
    timesteps: Int[Tensor, "batch"],
    padding_mask: Bool[Tensor, "batch tokens"]
    | None = None,
) -> LayoutCorrectorOutput

Run the corrector model.

Parameters:

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

Flattened token ids.

required
timesteps Int[Tensor, 'batch']

Diffusion timestep tensor.

required
padding_mask Bool[Tensor, 'batch tokens'] | None

Optional padding mask.

None

Returns:

Type Description
LayoutCorrectorOutput

LayoutCorrectorOutput containing token confidence logits.

Source code in models/layout-corrector/src/layout_corrector/modeling_layout_corrector.py
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
def forward(
    self,
    input_ids: Int[torch.Tensor, "batch tokens"],
    timesteps: Int[torch.Tensor, "batch"],
    padding_mask: Bool[torch.Tensor, "batch tokens"] | None = None,
) -> LayoutCorrectorOutput:
    """Run the corrector model.

    Args:
        input_ids: Flattened token ids.
        timesteps: Diffusion timestep tensor.
        padding_mask: Optional padding mask.

    Returns:
        `LayoutCorrectorOutput` containing token confidence logits.
    """
    src_key_padding_mask = None if self.use_padding_as_vocab else padding_mask
    logits = self.model(
        input_ids,
        timestep=timesteps,
        src_key_padding_mask=src_key_padding_mask,
    ).squeeze(-1)
    if not self.use_padding_as_vocab and padding_mask is not None:
        logits = logits.masked_fill(padding_mask, 1000.0)
    return LayoutCorrectorOutput(logits=logits)

calc_confidence_score

calc_confidence_score(
    input_ids: Int[Tensor, "batch tokens"],
    timesteps: Int[Tensor, "batch"],
    padding_mask: Bool[Tensor, "batch tokens"]
    | None = None,
) -> Float[torch.Tensor, "batch tokens"]

Return confidence logits for token remasking.

Parameters:

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

Flattened token ids.

required
timesteps Int[Tensor, 'batch']

Diffusion timestep tensor.

required
padding_mask Bool[Tensor, 'batch tokens'] | None

Optional padding mask.

None

Returns:

Type Description
Float[Tensor, 'batch tokens']

Confidence logits shaped (batch, tokens).

Source code in models/layout-corrector/src/layout_corrector/modeling_layout_corrector.py
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
def calc_confidence_score(
    self,
    input_ids: Int[torch.Tensor, "batch tokens"],
    timesteps: Int[torch.Tensor, "batch"],
    padding_mask: Bool[torch.Tensor, "batch tokens"] | None = None,
) -> Float[torch.Tensor, "batch tokens"]:
    """Return confidence logits for token remasking.

    Args:
        input_ids: Flattened token ids.
        timesteps: Diffusion timestep tensor.
        padding_mask: Optional padding mask.

    Returns:
        Confidence logits shaped `(batch, tokens)`.
    """
    return self(
        input_ids=input_ids,
        timesteps=timesteps,
        padding_mask=padding_mask,
    ).logits

LayoutCorrectorOutput dataclass

Bases: BaseOutput

Output container for Layout-Corrector confidence logits.

Parameters:

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

Token confidence logits shaped (batch, tokens).

required
Source code in models/layout-corrector/src/layout_corrector/modeling_layout_corrector.py
43
44
45
46
47
48
49
50
51
@dataclass
class LayoutCorrectorOutput(BaseOutput):
    """Output container for Layout-Corrector confidence logits.

    Args:
        logits: Token confidence logits shaped `(batch, tokens)`.
    """

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

LayoutCorrectorPipeline

Bases: DiffusionPipeline

Diffusers pipeline that applies Layout-Corrector during LayoutDM sampling.

Parameters:

Name Type Description Default
layout_dm LayoutDMPipeline

Base LayoutDM pipeline.

required
corrector LayoutCorrectorModel

Corrector model used to score and remask tokens.

required
processor LayoutDMProcessor | None

Optional processor for conditional layout inputs.

None

Raises:

Type Description
ValueError

Pipeline construction does not raise directly.

Examples:

>>> LayoutCorrectorPipeline.from_pretrained
<bound method...
Source code in models/layout-corrector/src/layout_corrector/pipeline_layout_corrector.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
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
class LayoutCorrectorPipeline(DiffusionPipeline):
    """Diffusers pipeline that applies Layout-Corrector during LayoutDM sampling.

    Args:
        layout_dm: Base LayoutDM pipeline.
        corrector: Corrector model used to score and remask tokens.
        processor: Optional processor for conditional layout inputs.

    Raises:
        ValueError: Pipeline construction does not raise directly.

    Examples:
        >>> LayoutCorrectorPipeline.from_pretrained  # doctest: +ELLIPSIS
        <bound method...
    """

    model_cpu_offload_seq: ClassVar[str] = "layout_dm.denoiser->corrector"

    def __init__(
        self,
        layout_dm: LayoutDMPipeline,
        corrector: LayoutCorrectorModel,
        processor: LayoutDMProcessor | None = None,
    ) -> None:
        """Initialize the composite pipeline.

        Args:
            layout_dm: Base LayoutDM pipeline.
            corrector: Confidence model used to remask low-confidence tokens.
            processor: Optional processor for conditional inputs.
        """
        super().__init__()
        self.register_modules(layout_dm=layout_dm, corrector=corrector)
        self.layout_dm = layout_dm
        self.corrector = corrector
        self.processor = processor or layout_dm.processor
        self.corrector.eval()

    def to(
        self,
        *args: torch.device | str | torch.dtype | None,
        dtype: torch.dtype | None = None,
        device: torch.device | str | None = None,
        silence_dtype_warnings: bool = False,
    ) -> Self:
        """Move the nested LayoutDM pipeline and the corrector together."""
        self.layout_dm.to(
            *args,
            dtype=dtype,
            device=device,
            silence_dtype_warnings=silence_dtype_warnings,
        )
        return super().to(
            *args,
            dtype=dtype,
            device=device,
            silence_dtype_warnings=silence_dtype_warnings,
        )

    @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"]
        | list[ArrayLikeInput]
        | None = None,
        bbox: Float[torch.Tensor, "batch elements 4"]
        | Float[np.ndarray, "batch elements 4"]
        | list[ArrayLikeInput]
        | None = None,
        mask: Bool[torch.Tensor, "batch elements"]
        | Bool[np.ndarray, "batch elements"]
        | list[ArrayLikeInput]
        | None = None,
        num_elements: int | list[int] | Int[torch.Tensor, "batch"] | None = None,
        box_format: BoxFormat | str = BoxFormat.xywh,
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
        num_inference_steps: int | None = None,
        sampling: SamplingMode | str = SamplingMode.random,
        temperature: float = 1.0,
        top_k: int = 5,
        top_p: float = 0.9,
        corrector_steps: int | None = None,
        corrector_t_list: Sequence[int] | None = None,
        corrector_start: int = -1,
        corrector_end: int = -1,
        corrector_mask_mode: CorrectorMaskMode | str | None = None,
        corrector_mask_threshold: float | None = None,
        corrector_temperature: float | None = None,
        use_gumbel_noise: bool | None = None,
        gumbel_temperature: float | None = None,
        time_adaptive_temperature: bool | None = None,
        output_type: OutputType | str = OutputType.dataclass,
        return_intermediates: bool = False,
    ) -> (
        LayoutGenerationOutput
        | dict[
            str,
            Float[torch.Tensor, "batch elements 4"]
            | Int[torch.Tensor, "batch elements"]
            | Bool[torch.Tensor, "batch elements"]
            | Int[torch.Tensor, "batch tokens"]
            | Float[torch.Tensor, "steps batch tokens"]
            | list[Int[torch.Tensor, "batch tokens"]]
            | dict[int, str]
            | dict[str, str]
            | None,
        ]
    ):
        """Generate layouts with optional Layout-Corrector guidance.

        Args:
            batch_size: Number of layouts to sample for unconditional generation.
            seed: Optional seed used when `generator` is not supplied.
            generator: Optional PyTorch generator.
            condition_type: Condition mode such as `"unconditional"` or `"label"`.
            labels: Optional class ids for conditional generation.
            bbox: Optional boxes for conditional generation.
            mask: Optional element mask for conditional generation.
            num_elements: Reserved for future element-count conditioning.
            box_format: Coordinate format for conditional boxes.
            normalized: Whether conditional boxes are normalized.
            canvas_size: Pixel canvas used when `normalized=False`.
            num_inference_steps: Optional inference timestep count.
            sampling: Base LayoutDM sampling strategy.
            temperature: Base sampling temperature.
            top_k: Top-k cutoff for top-k sampling.
            top_p: Nucleus cutoff for top-p sampling.
            corrector_steps: Optional override for correction passes.
            corrector_t_list: Optional explicit correction timesteps.
            corrector_start: Range start for correction when no list is supplied.
            corrector_end: Range end for correction when no list is supplied.
            corrector_mask_mode: Optional override for remasking mode.
            corrector_mask_threshold: Optional threshold override.
            corrector_temperature: Optional confidence temperature override.
            use_gumbel_noise: Optional confidence-noise override.
            gumbel_temperature: Optional confidence-noise temperature override.
            time_adaptive_temperature: Optional adaptive-noise override.
            output_type: `"dataclass"` or `"dict"`.
            return_intermediates: Whether to include scores and trajectory.

        Returns:
            `LayoutGenerationOutput` by default, or a dictionary when requested.

        Raises:
            ValueError: If conditional generation is missing `bbox` or `labels`, or
                if `output_type` is unsupported.

        Examples:
            >>> LayoutCorrectorPipeline.__call__  # doctest: +ELLIPSIS
            <function...
        """
        _ = num_elements
        generator = resolve_torch_generator(generator=generator, seed=seed)
        canonical = normalize_condition_type(condition_type)
        condition = None
        if canonical != "unconditional":
            missing_inputs = [
                name
                for name, value in (("bbox", bbox), ("labels", labels))
                if value is None
            ]
            if missing_inputs:
                raise ValueError(
                    "bbox and labels are required "
                    f"for condition_type={condition_type}; "
                    f"missing {', '.join(missing_inputs)}"
                )

            processor_inputs = {
                "bbox": bbox,
                "labels": labels,
                "mask": mask,
                "box_format": box_format,
                "normalized": normalized,
                "canvas_size": canvas_size,
            }
            processed = self.processor(**processor_inputs)
            decoded_input = self.layout_dm.tokenizer.decode_layout(
                processed["input_ids"]
            )
            condition = build_condition(
                self.layout_dm.tokenizer,
                cond_type=canonical,
                bbox=decoded_input["bbox"],
                labels=decoded_input["labels"],
                mask=decoded_input["mask"],
            )
            batch_size = condition.input_ids.shape[0]

        corrector_cfg = LayoutCorrectorSamplingConfig(
            sampling=sampling,
            temperature=temperature,
            top_k=top_k,
            top_p=top_p,
            num_inference_steps=num_inference_steps,
            corrector_steps=corrector_steps or self.corrector.corrector_steps,
            corrector_t_list=tuple(
                self.corrector.corrector_t_list
                if corrector_t_list is None
                else corrector_t_list
            ),
            corrector_start=corrector_start,
            corrector_end=corrector_end,
            corrector_mask_mode=corrector_mask_mode
            or self.corrector.corrector_mask_mode,
            corrector_mask_threshold=corrector_mask_threshold
            if corrector_mask_threshold is not None
            else self.corrector.corrector_mask_threshold,
            corrector_temperature=corrector_temperature
            if corrector_temperature is not None
            else self.corrector.corrector_temperature,
            use_gumbel_noise=use_gumbel_noise
            if use_gumbel_noise is not None
            else self.corrector.use_gumbel_noise,
            gumbel_temperature=gumbel_temperature
            if gumbel_temperature is not None
            else self.corrector.gumbel_temperature,
            time_adaptive_temperature=time_adaptive_temperature
            if time_adaptive_temperature is not None
            else self.corrector.time_adaptive_temperature,
        )
        sampling_cfg = LayoutDMSamplingConfig(
            name=sampling,
            temperature=temperature,
            top_k=top_k,
            top_p=top_p,
            num_inference_steps=num_inference_steps,
        )
        self.layout_dm.scheduler.set_timesteps(num_inference_steps, device=self.device)
        sample = self.layout_dm.scheduler.initial_sample(
            batch_size,
            self.layout_dm.tokenizer.config.max_token_length,
            device=self.device,
            condition=condition,
        )
        trajectory = [] if return_intermediates else None
        scores = [] if return_intermediates else None
        previous_timestep = self.layout_dm.scheduler.config.num_timesteps
        for timestep in self.layout_dm.scheduler.timesteps:
            timestep_value = int(timestep.item())
            timestep_batch = torch.full(
                (batch_size,),
                timestep_value,
                device=self.device,
                dtype=torch.long,
            )
            if should_apply_corrector(timestep_value, corrector_cfg):
                sample, confidence = self._step_with_corrector(
                    sample=sample,
                    timestep_batch=timestep_batch,
                    condition=condition,
                    sampling=corrector_cfg,
                    generator=generator,
                )
                if scores is not None and confidence is not None:
                    scores.append(confidence.detach().cpu())
            else:
                input_ids = log_onehot_to_index(sample)
                logits = self.layout_dm.denoiser(
                    input_ids=input_ids, timesteps=timestep_batch
                ).logits
                sample = self.layout_dm.scheduler.step(
                    logits,
                    timestep_batch,
                    sample,
                    previous_timestep=previous_timestep,
                    sampling=sampling_cfg,
                    condition=condition,
                    generator=generator,
                ).prev_sample
            previous_timestep = timestep_value
            if trajectory is not None:
                trajectory.append(log_onehot_to_index(sample).detach().cpu())

        sequences = log_onehot_to_index(sample).detach().cpu()
        decoded = self.layout_dm.tokenizer.decode_layout(sequences)
        output = LayoutGenerationOutput(
            bbox=decoded["bbox"],
            labels=decoded["labels"],
            mask=decoded["mask"],
            id2label=dict(self.corrector.id2label),
            sequences=sequences,
            scores=torch.stack(scores) if scores else None,
            trajectory=trajectory,
            intermediates={"condition_type": canonical}
            if return_intermediates
            else None,
        )
        normalized_output_type = normalize_output_type(output_type)
        if normalized_output_type is OutputType.dict:
            return dict(output)
        if normalized_output_type is OutputType.dataclass:
            return output
        assert_never(normalized_output_type)

    generate = __call__

    def _step_with_corrector(
        self,
        *,
        sample: Float[torch.Tensor, "batch vocab tokens"],
        timestep_batch: Int[torch.Tensor, "batch"],
        condition: LayoutDMCondition | None,
        sampling: LayoutCorrectorSamplingConfig,
        generator: torch.Generator | None,
    ) -> tuple[
        Float[torch.Tensor, "batch vocab tokens"],
        Float[torch.Tensor, "batch tokens"] | None,
    ]:
        confidence = None
        current = sample
        for _ in range(sampling.corrector_steps):
            input_ids = log_onehot_to_index(current)
            denoiser_logits = self.layout_dm.denoiser(
                input_ids=input_ids, timesteps=timestep_batch
            ).logits
            model_log_prob = self.layout_dm.scheduler.predict_start(denoiser_logits)
            if self.corrector.recon_type is CorrectorReconType.x_t_minus_1:
                model_log_prob = self.layout_dm.scheduler.q_posterior(
                    model_log_prob, current, timestep_batch
                )
                model_log_prob[:, self.layout_dm.tokenizer.mask_token_id, :] = -70.0
            model_log_prob = self.layout_dm.scheduler.apply_condition(
                model_log_prob, condition
            )
            x0_recon_ids = multinomial(
                (model_log_prob / sampling.temperature)
                .softmax(dim=1)
                .permute(0, 2, 1)
                .reshape(-1, model_log_prob.size(1)),
                1,
                generator=generator,
                device=model_log_prob.device,
            ).reshape(model_log_prob.shape[0], model_log_prob.shape[-1])
            confidence = self.corrector.calc_confidence_score(
                x0_recon_ids,
                timestep_batch,
                padding_mask=x0_recon_ids == self.layout_dm.tokenizer.pad_token_id,
            )
            adjusted = confidence
            mask_ratio = self._mask_ratio(timestep_batch)
            if sampling.use_gumbel_noise:
                adjusted = add_confidence_gumbel_noise(
                    adjusted,
                    timestep=timestep_batch,
                    mask_ratio=mask_ratio,
                    temperature=sampling.gumbel_temperature,
                    time_adaptive_temperature=sampling.time_adaptive_temperature,
                    generator=generator,
                )
            remask = select_tokens_to_remask(
                adjusted,
                mask_ratio=mask_ratio,
                mode=sampling.corrector_mask_mode,
                threshold=sampling.corrector_mask_threshold,
                temperature=sampling.corrector_temperature,
            )
            x0_recon_ids = x0_recon_ids.masked_fill(
                remask, self.layout_dm.tokenizer.mask_token_id
            )
            if condition is not None:
                x0_recon_ids = torch.where(
                    condition.mask.to(x0_recon_ids.device),
                    condition.input_ids.to(x0_recon_ids.device),
                    x0_recon_ids,
                )
            current = index_to_log_onehot(
                x0_recon_ids, self.layout_dm.scheduler.vocab_size
            )
        return current, confidence

    def _mask_ratio(self, timestep_batch: Int[torch.Tensor, "batch"]) -> float:
        timestep = int(timestep_batch[0].item())
        timestep = max(0, min(timestep, self.layout_dm.scheduler.config.num_timesteps))
        if timestep == 0:
            return 0.0
        return float(timestep / self.layout_dm.scheduler.config.num_timesteps)

    def save_pretrained(
        self, save_directory: str | Path, *, safe_serialization: bool = True
    ) -> None:
        """Save the nested LayoutDM pipeline and corrector model.

        Args:
            save_directory: Destination directory.
            safe_serialization: Whether to save weights as safetensors.

        Returns:
            None.

        Raises:
            OSError: If files cannot be written.

        Examples:
            >>> LayoutCorrectorPipeline.save_pretrained  # doctest: +ELLIPSIS
            <function...
        """
        save_path = Path(save_directory)
        save_path.mkdir(parents=True, exist_ok=True)
        self.layout_dm.save_pretrained(
            save_path / "layout_dm", safe_serialization=safe_serialization
        )
        self.corrector.save_pretrained(
            save_path / "corrector", safe_serialization=safe_serialization
        )

    @classmethod
    def from_pretrained(
        cls,
        pretrained_model_name_or_path: str | Path,
        *,
        processor: LayoutDMProcessor | None = None,
    ) -> "LayoutCorrectorPipeline":
        """Load a Layout-Corrector pipeline from a saved directory.

        Args:
            pretrained_model_name_or_path: Directory containing `layout_dm/` and
                `corrector/` subdirectories.
            processor: Optional processor override.

        Returns:
            Loaded `LayoutCorrectorPipeline`.

        Raises:
            OSError: If nested component files are missing.

        Examples:
            >>> LayoutCorrectorPipeline.from_pretrained  # doctest: +ELLIPSIS
            <bound method...
        """
        path = Path(pretrained_model_name_or_path)
        layout_dm = LayoutDMPipeline.from_pretrained(path / "layout_dm")
        corrector = LayoutCorrectorModel.from_pretrained(path / "corrector")
        return cls(layout_dm=layout_dm, corrector=corrector, processor=processor)

__init__

__init__(
    layout_dm: LayoutDMPipeline,
    corrector: LayoutCorrectorModel,
    processor: LayoutDMProcessor | None = None,
) -> None

Initialize the composite pipeline.

Parameters:

Name Type Description Default
layout_dm LayoutDMPipeline

Base LayoutDM pipeline.

required
corrector LayoutCorrectorModel

Confidence model used to remask low-confidence tokens.

required
processor LayoutDMProcessor | None

Optional processor for conditional inputs.

None
Source code in models/layout-corrector/src/layout_corrector/pipeline_layout_corrector.py
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
def __init__(
    self,
    layout_dm: LayoutDMPipeline,
    corrector: LayoutCorrectorModel,
    processor: LayoutDMProcessor | None = None,
) -> None:
    """Initialize the composite pipeline.

    Args:
        layout_dm: Base LayoutDM pipeline.
        corrector: Confidence model used to remask low-confidence tokens.
        processor: Optional processor for conditional inputs.
    """
    super().__init__()
    self.register_modules(layout_dm=layout_dm, corrector=corrector)
    self.layout_dm = layout_dm
    self.corrector = corrector
    self.processor = processor or layout_dm.processor
    self.corrector.eval()

to

to(
    *args: device | str | dtype | None,
    dtype: dtype | None = None,
    device: device | str | None = None,
    silence_dtype_warnings: bool = False,
) -> Self

Move the nested LayoutDM pipeline and the corrector together.

Source code in models/layout-corrector/src/layout_corrector/pipeline_layout_corrector.py
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
def to(
    self,
    *args: torch.device | str | torch.dtype | None,
    dtype: torch.dtype | None = None,
    device: torch.device | str | None = None,
    silence_dtype_warnings: bool = False,
) -> Self:
    """Move the nested LayoutDM pipeline and the corrector together."""
    self.layout_dm.to(
        *args,
        dtype=dtype,
        device=device,
        silence_dtype_warnings=silence_dtype_warnings,
    )
    return super().to(
        *args,
        dtype=dtype,
        device=device,
        silence_dtype_warnings=silence_dtype_warnings,
    )

__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"]
    | list[ArrayLikeInput]
    | None = None,
    bbox: Float[Tensor, "batch elements 4"]
    | Float[ndarray, "batch elements 4"]
    | list[ArrayLikeInput]
    | None = None,
    mask: Bool[Tensor, "batch elements"]
    | Bool[ndarray, "batch elements"]
    | list[ArrayLikeInput]
    | None = None,
    num_elements: int
    | list[int]
    | Int[Tensor, "batch"]
    | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    num_inference_steps: int | None = None,
    sampling: SamplingMode | str = SamplingMode.random,
    temperature: float = 1.0,
    top_k: int = 5,
    top_p: float = 0.9,
    corrector_steps: int | None = None,
    corrector_t_list: Sequence[int] | None = None,
    corrector_start: int = -1,
    corrector_end: int = -1,
    corrector_mask_mode: CorrectorMaskMode
    | str
    | None = None,
    corrector_mask_threshold: float | None = None,
    corrector_temperature: float | None = None,
    use_gumbel_noise: bool | None = None,
    gumbel_temperature: float | None = None,
    time_adaptive_temperature: bool | None = None,
    output_type: OutputType | str = OutputType.dataclass,
    return_intermediates: bool = False,
) -> (
    LayoutGenerationOutput
    | dict[
        str,
        Float[torch.Tensor, "batch elements 4"]
        | Int[torch.Tensor, "batch elements"]
        | Bool[torch.Tensor, "batch elements"]
        | Int[torch.Tensor, "batch tokens"]
        | Float[torch.Tensor, "steps batch tokens"]
        | list[Int[torch.Tensor, "batch tokens"]]
        | dict[int, str]
        | dict[str, str]
        | None,
    ]
)

Generate layouts with optional Layout-Corrector guidance.

Parameters:

Name Type Description Default
batch_size int

Number of layouts to sample for unconditional generation.

1
seed int | None

Optional seed used when generator is not supplied.

None
generator Generator | None

Optional PyTorch generator.

None
condition_type ConditionType | str

Condition mode such as "unconditional" or "label".

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

Optional class ids for conditional generation.

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

Optional boxes for conditional generation.

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

Optional element mask for conditional generation.

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

Reserved for future element-count conditioning.

None
box_format BoxFormat | str

Coordinate format for conditional boxes.

xywh
normalized bool

Whether conditional boxes are normalized.

True
canvas_size tuple[int, int] | None

Pixel canvas used when normalized=False.

None
num_inference_steps int | None

Optional inference timestep count.

None
sampling SamplingMode | str

Base LayoutDM sampling strategy.

random
temperature float

Base sampling temperature.

1.0
top_k int

Top-k cutoff for top-k sampling.

5
top_p float

Nucleus cutoff for top-p sampling.

0.9
corrector_steps int | None

Optional override for correction passes.

None
corrector_t_list Sequence[int] | None

Optional explicit correction timesteps.

None
corrector_start int

Range start for correction when no list is supplied.

-1
corrector_end int

Range end for correction when no list is supplied.

-1
corrector_mask_mode CorrectorMaskMode | str | None

Optional override for remasking mode.

None
corrector_mask_threshold float | None

Optional threshold override.

None
corrector_temperature float | None

Optional confidence temperature override.

None
use_gumbel_noise bool | None

Optional confidence-noise override.

None
gumbel_temperature float | None

Optional confidence-noise temperature override.

None
time_adaptive_temperature bool | None

Optional adaptive-noise override.

None
output_type OutputType | str

"dataclass" or "dict".

dataclass
return_intermediates bool

Whether to include scores and trajectory.

False

Returns:

Type Description
LayoutGenerationOutput | dict[str, Float[Tensor, 'batch elements 4'] | Int[Tensor, 'batch elements'] | Bool[Tensor, 'batch elements'] | Int[Tensor, 'batch tokens'] | Float[Tensor, 'steps batch tokens'] | list[Int[Tensor, 'batch tokens']] | dict[int, str] | dict[str, str] | None]

LayoutGenerationOutput by default, or a dictionary when requested.

Raises:

Type Description
ValueError

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

Examples:

>>> LayoutCorrectorPipeline.__call__
<function...
Source code in models/layout-corrector/src/layout_corrector/pipeline_layout_corrector.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
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
@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"]
    | list[ArrayLikeInput]
    | None = None,
    bbox: Float[torch.Tensor, "batch elements 4"]
    | Float[np.ndarray, "batch elements 4"]
    | list[ArrayLikeInput]
    | None = None,
    mask: Bool[torch.Tensor, "batch elements"]
    | Bool[np.ndarray, "batch elements"]
    | list[ArrayLikeInput]
    | None = None,
    num_elements: int | list[int] | Int[torch.Tensor, "batch"] | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    num_inference_steps: int | None = None,
    sampling: SamplingMode | str = SamplingMode.random,
    temperature: float = 1.0,
    top_k: int = 5,
    top_p: float = 0.9,
    corrector_steps: int | None = None,
    corrector_t_list: Sequence[int] | None = None,
    corrector_start: int = -1,
    corrector_end: int = -1,
    corrector_mask_mode: CorrectorMaskMode | str | None = None,
    corrector_mask_threshold: float | None = None,
    corrector_temperature: float | None = None,
    use_gumbel_noise: bool | None = None,
    gumbel_temperature: float | None = None,
    time_adaptive_temperature: bool | None = None,
    output_type: OutputType | str = OutputType.dataclass,
    return_intermediates: bool = False,
) -> (
    LayoutGenerationOutput
    | dict[
        str,
        Float[torch.Tensor, "batch elements 4"]
        | Int[torch.Tensor, "batch elements"]
        | Bool[torch.Tensor, "batch elements"]
        | Int[torch.Tensor, "batch tokens"]
        | Float[torch.Tensor, "steps batch tokens"]
        | list[Int[torch.Tensor, "batch tokens"]]
        | dict[int, str]
        | dict[str, str]
        | None,
    ]
):
    """Generate layouts with optional Layout-Corrector guidance.

    Args:
        batch_size: Number of layouts to sample for unconditional generation.
        seed: Optional seed used when `generator` is not supplied.
        generator: Optional PyTorch generator.
        condition_type: Condition mode such as `"unconditional"` or `"label"`.
        labels: Optional class ids for conditional generation.
        bbox: Optional boxes for conditional generation.
        mask: Optional element mask for conditional generation.
        num_elements: Reserved for future element-count conditioning.
        box_format: Coordinate format for conditional boxes.
        normalized: Whether conditional boxes are normalized.
        canvas_size: Pixel canvas used when `normalized=False`.
        num_inference_steps: Optional inference timestep count.
        sampling: Base LayoutDM sampling strategy.
        temperature: Base sampling temperature.
        top_k: Top-k cutoff for top-k sampling.
        top_p: Nucleus cutoff for top-p sampling.
        corrector_steps: Optional override for correction passes.
        corrector_t_list: Optional explicit correction timesteps.
        corrector_start: Range start for correction when no list is supplied.
        corrector_end: Range end for correction when no list is supplied.
        corrector_mask_mode: Optional override for remasking mode.
        corrector_mask_threshold: Optional threshold override.
        corrector_temperature: Optional confidence temperature override.
        use_gumbel_noise: Optional confidence-noise override.
        gumbel_temperature: Optional confidence-noise temperature override.
        time_adaptive_temperature: Optional adaptive-noise override.
        output_type: `"dataclass"` or `"dict"`.
        return_intermediates: Whether to include scores and trajectory.

    Returns:
        `LayoutGenerationOutput` by default, or a dictionary when requested.

    Raises:
        ValueError: If conditional generation is missing `bbox` or `labels`, or
            if `output_type` is unsupported.

    Examples:
        >>> LayoutCorrectorPipeline.__call__  # doctest: +ELLIPSIS
        <function...
    """
    _ = num_elements
    generator = resolve_torch_generator(generator=generator, seed=seed)
    canonical = normalize_condition_type(condition_type)
    condition = None
    if canonical != "unconditional":
        missing_inputs = [
            name
            for name, value in (("bbox", bbox), ("labels", labels))
            if value is None
        ]
        if missing_inputs:
            raise ValueError(
                "bbox and labels are required "
                f"for condition_type={condition_type}; "
                f"missing {', '.join(missing_inputs)}"
            )

        processor_inputs = {
            "bbox": bbox,
            "labels": labels,
            "mask": mask,
            "box_format": box_format,
            "normalized": normalized,
            "canvas_size": canvas_size,
        }
        processed = self.processor(**processor_inputs)
        decoded_input = self.layout_dm.tokenizer.decode_layout(
            processed["input_ids"]
        )
        condition = build_condition(
            self.layout_dm.tokenizer,
            cond_type=canonical,
            bbox=decoded_input["bbox"],
            labels=decoded_input["labels"],
            mask=decoded_input["mask"],
        )
        batch_size = condition.input_ids.shape[0]

    corrector_cfg = LayoutCorrectorSamplingConfig(
        sampling=sampling,
        temperature=temperature,
        top_k=top_k,
        top_p=top_p,
        num_inference_steps=num_inference_steps,
        corrector_steps=corrector_steps or self.corrector.corrector_steps,
        corrector_t_list=tuple(
            self.corrector.corrector_t_list
            if corrector_t_list is None
            else corrector_t_list
        ),
        corrector_start=corrector_start,
        corrector_end=corrector_end,
        corrector_mask_mode=corrector_mask_mode
        or self.corrector.corrector_mask_mode,
        corrector_mask_threshold=corrector_mask_threshold
        if corrector_mask_threshold is not None
        else self.corrector.corrector_mask_threshold,
        corrector_temperature=corrector_temperature
        if corrector_temperature is not None
        else self.corrector.corrector_temperature,
        use_gumbel_noise=use_gumbel_noise
        if use_gumbel_noise is not None
        else self.corrector.use_gumbel_noise,
        gumbel_temperature=gumbel_temperature
        if gumbel_temperature is not None
        else self.corrector.gumbel_temperature,
        time_adaptive_temperature=time_adaptive_temperature
        if time_adaptive_temperature is not None
        else self.corrector.time_adaptive_temperature,
    )
    sampling_cfg = LayoutDMSamplingConfig(
        name=sampling,
        temperature=temperature,
        top_k=top_k,
        top_p=top_p,
        num_inference_steps=num_inference_steps,
    )
    self.layout_dm.scheduler.set_timesteps(num_inference_steps, device=self.device)
    sample = self.layout_dm.scheduler.initial_sample(
        batch_size,
        self.layout_dm.tokenizer.config.max_token_length,
        device=self.device,
        condition=condition,
    )
    trajectory = [] if return_intermediates else None
    scores = [] if return_intermediates else None
    previous_timestep = self.layout_dm.scheduler.config.num_timesteps
    for timestep in self.layout_dm.scheduler.timesteps:
        timestep_value = int(timestep.item())
        timestep_batch = torch.full(
            (batch_size,),
            timestep_value,
            device=self.device,
            dtype=torch.long,
        )
        if should_apply_corrector(timestep_value, corrector_cfg):
            sample, confidence = self._step_with_corrector(
                sample=sample,
                timestep_batch=timestep_batch,
                condition=condition,
                sampling=corrector_cfg,
                generator=generator,
            )
            if scores is not None and confidence is not None:
                scores.append(confidence.detach().cpu())
        else:
            input_ids = log_onehot_to_index(sample)
            logits = self.layout_dm.denoiser(
                input_ids=input_ids, timesteps=timestep_batch
            ).logits
            sample = self.layout_dm.scheduler.step(
                logits,
                timestep_batch,
                sample,
                previous_timestep=previous_timestep,
                sampling=sampling_cfg,
                condition=condition,
                generator=generator,
            ).prev_sample
        previous_timestep = timestep_value
        if trajectory is not None:
            trajectory.append(log_onehot_to_index(sample).detach().cpu())

    sequences = log_onehot_to_index(sample).detach().cpu()
    decoded = self.layout_dm.tokenizer.decode_layout(sequences)
    output = LayoutGenerationOutput(
        bbox=decoded["bbox"],
        labels=decoded["labels"],
        mask=decoded["mask"],
        id2label=dict(self.corrector.id2label),
        sequences=sequences,
        scores=torch.stack(scores) if scores else None,
        trajectory=trajectory,
        intermediates={"condition_type": canonical}
        if return_intermediates
        else None,
    )
    normalized_output_type = normalize_output_type(output_type)
    if normalized_output_type is OutputType.dict:
        return dict(output)
    if normalized_output_type is OutputType.dataclass:
        return output
    assert_never(normalized_output_type)

save_pretrained

save_pretrained(
    save_directory: str | Path,
    *,
    safe_serialization: bool = True,
) -> None

Save the nested LayoutDM pipeline and corrector model.

Parameters:

Name Type Description Default
save_directory str | Path

Destination directory.

required
safe_serialization bool

Whether to save weights as safetensors.

True

Returns:

Type Description
None

None.

Raises:

Type Description
OSError

If files cannot be written.

Examples:

>>> LayoutCorrectorPipeline.save_pretrained
<function...
Source code in models/layout-corrector/src/layout_corrector/pipeline_layout_corrector.py
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
def save_pretrained(
    self, save_directory: str | Path, *, safe_serialization: bool = True
) -> None:
    """Save the nested LayoutDM pipeline and corrector model.

    Args:
        save_directory: Destination directory.
        safe_serialization: Whether to save weights as safetensors.

    Returns:
        None.

    Raises:
        OSError: If files cannot be written.

    Examples:
        >>> LayoutCorrectorPipeline.save_pretrained  # doctest: +ELLIPSIS
        <function...
    """
    save_path = Path(save_directory)
    save_path.mkdir(parents=True, exist_ok=True)
    self.layout_dm.save_pretrained(
        save_path / "layout_dm", safe_serialization=safe_serialization
    )
    self.corrector.save_pretrained(
        save_path / "corrector", safe_serialization=safe_serialization
    )

from_pretrained classmethod

from_pretrained(
    pretrained_model_name_or_path: str | Path,
    *,
    processor: LayoutDMProcessor | None = None,
) -> "LayoutCorrectorPipeline"

Load a Layout-Corrector pipeline from a saved directory.

Parameters:

Name Type Description Default
pretrained_model_name_or_path str | Path

Directory containing layout_dm/ and corrector/ subdirectories.

required
processor LayoutDMProcessor | None

Optional processor override.

None

Returns:

Type Description
'LayoutCorrectorPipeline'

Loaded LayoutCorrectorPipeline.

Raises:

Type Description
OSError

If nested component files are missing.

Examples:

>>> LayoutCorrectorPipeline.from_pretrained
<bound method...
Source code in models/layout-corrector/src/layout_corrector/pipeline_layout_corrector.py
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
@classmethod
def from_pretrained(
    cls,
    pretrained_model_name_or_path: str | Path,
    *,
    processor: LayoutDMProcessor | None = None,
) -> "LayoutCorrectorPipeline":
    """Load a Layout-Corrector pipeline from a saved directory.

    Args:
        pretrained_model_name_or_path: Directory containing `layout_dm/` and
            `corrector/` subdirectories.
        processor: Optional processor override.

    Returns:
        Loaded `LayoutCorrectorPipeline`.

    Raises:
        OSError: If nested component files are missing.

    Examples:
        >>> LayoutCorrectorPipeline.from_pretrained  # doctest: +ELLIPSIS
        <bound method...
    """
    path = Path(pretrained_model_name_or_path)
    layout_dm = LayoutDMPipeline.from_pretrained(path / "layout_dm")
    corrector = LayoutCorrectorModel.from_pretrained(path / "corrector")
    return cls(layout_dm=layout_dm, corrector=corrector, processor=processor)

CorrectorMaskMode

Bases: StrEnum

Supported token remasking modes for Layout-Corrector.

Source code in models/layout-corrector/src/layout_corrector/sampling.py
20
21
22
23
24
class CorrectorMaskMode(StrEnum):
    """Supported token remasking modes for Layout-Corrector."""

    thresh = auto()
    topk = auto()

LayoutCorrectorSamplingConfig dataclass

Sampling options for Layout-Corrector-guided diffusion.

Parameters:

Name Type Description Default
sampling SamplingMode | str

Base LayoutDM sampling strategy.

random
temperature float

Base sampling temperature.

1.0
top_k int

Top-k cutoff for top-k sampling.

5
top_p float

Nucleus cutoff for top-p sampling.

0.9
num_inference_steps int | None

Optional inference timestep count.

None
corrector_steps int

Number of correction passes per selected timestep.

1
corrector_t_list tuple[int, ...]

Explicit timesteps where the corrector is applied.

(10, 20, 30)
corrector_start int

Start timestep for range-based correction.

-1
corrector_end int

End timestep for range-based correction.

-1
corrector_mask_mode CorrectorMaskMode | str

Strategy for selecting tokens to remask.

thresh
corrector_mask_threshold float

Confidence threshold for threshold masking.

0.7
corrector_temperature float

Temperature used for confidence masking.

1.0
use_gumbel_noise bool

Whether to perturb confidence logits.

True
gumbel_temperature float

Temperature for confidence Gumbel noise.

1.0
time_adaptive_temperature bool

Whether to scale noise by timestep ratio.

False

Raises:

Type Description
ValueError

Construction does not raise directly.

Examples:

>>> LayoutCorrectorSamplingConfig(corrector_t_list=(10,)).corrector_t_list
(10,)
Source code in models/layout-corrector/src/layout_corrector/sampling.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
@dataclass
class LayoutCorrectorSamplingConfig:
    """Sampling options for Layout-Corrector-guided diffusion.

    Args:
        sampling: Base LayoutDM sampling strategy.
        temperature: Base sampling temperature.
        top_k: Top-k cutoff for top-k sampling.
        top_p: Nucleus cutoff for top-p sampling.
        num_inference_steps: Optional inference timestep count.
        corrector_steps: Number of correction passes per selected timestep.
        corrector_t_list: Explicit timesteps where the corrector is applied.
        corrector_start: Start timestep for range-based correction.
        corrector_end: End timestep for range-based correction.
        corrector_mask_mode: Strategy for selecting tokens to remask.
        corrector_mask_threshold: Confidence threshold for threshold masking.
        corrector_temperature: Temperature used for confidence masking.
        use_gumbel_noise: Whether to perturb confidence logits.
        gumbel_temperature: Temperature for confidence Gumbel noise.
        time_adaptive_temperature: Whether to scale noise by timestep ratio.

    Raises:
        ValueError: Construction does not raise directly.

    Examples:
        >>> LayoutCorrectorSamplingConfig(corrector_t_list=(10,)).corrector_t_list
        (10,)
    """

    sampling: SamplingMode | str = SamplingMode.random
    temperature: float = 1.0
    top_k: int = 5
    top_p: float = 0.9
    num_inference_steps: int | None = None
    corrector_steps: int = 1
    corrector_t_list: tuple[int, ...] = (10, 20, 30)
    corrector_start: int = -1
    corrector_end: int = -1
    corrector_mask_mode: CorrectorMaskMode | str = CorrectorMaskMode.thresh
    corrector_mask_threshold: float = 0.7
    corrector_temperature: float = 1.0
    use_gumbel_noise: bool = True
    gumbel_temperature: float = 1.0
    time_adaptive_temperature: bool = False

    def __post_init__(self) -> None:
        """Normalize public string modes to enum values."""
        self.sampling = normalize_sampling_mode(self.sampling)
        self.corrector_mask_mode = normalize_corrector_mask_mode(
            self.corrector_mask_mode
        )

__post_init__

__post_init__() -> None

Normalize public string modes to enum values.

Source code in models/layout-corrector/src/layout_corrector/sampling.py
86
87
88
89
90
91
def __post_init__(self) -> None:
    """Normalize public string modes to enum values."""
    self.sampling = normalize_sampling_mode(self.sampling)
    self.corrector_mask_mode = normalize_corrector_mask_mode(
        self.corrector_mask_mode
    )

configuration_layout_corrector

Configuration objects for Layout-Corrector models.

CorrectorReconType

Bases: StrEnum

Supported Layout-Corrector reconstruction targets.

Source code in models/layout-corrector/src/layout_corrector/configuration_layout_corrector.py
21
22
23
24
25
class CorrectorReconType(StrEnum):
    """Supported Layout-Corrector reconstruction targets."""

    x_0 = auto()
    x_t_minus_1 = "x_t-1"

CorrectorTarget

Bases: StrEnum

Supported Layout-Corrector confidence targets.

Source code in models/layout-corrector/src/layout_corrector/configuration_layout_corrector.py
28
29
30
31
32
class CorrectorTarget(StrEnum):
    """Supported Layout-Corrector confidence targets."""

    mask = auto()
    recon_acc = auto()

CorrectorPositionEmbedding

Bases: StrEnum

Supported original position-embedding modes.

Source code in models/layout-corrector/src/layout_corrector/configuration_layout_corrector.py
35
36
37
38
39
40
41
42
class CorrectorPositionEmbedding(StrEnum):
    """Supported original position-embedding modes."""

    default = auto()
    none = auto()
    pos_enc = auto()
    shuffle_pos_enc = auto()
    shuffle = auto()

CorrectorTransformerType

Bases: StrEnum

Supported corrector transformer variants.

Source code in models/layout-corrector/src/layout_corrector/configuration_layout_corrector.py
45
46
47
48
class CorrectorTransformerType(StrEnum):
    """Supported corrector transformer variants."""

    aggregated = auto()

LayoutCorrectorConfig

Bases: ConfigMixin

Configuration for the Layout-Corrector transformer.

Parameters:

Name Type Description Default
dataset_name DatasetName | str

Dataset key or alias used for labels.

required
vocab_size int

LayoutDM vocabulary size expected by the corrector.

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

Optional class-id mapping. When omitted, the shared registry is used.

None
max_seq_length int

Maximum number of layout elements.

25
num_attributes_per_element int

Number of token attributes per element.

5
hidden_size int

Transformer hidden dimension.

464
num_attention_heads int

Number of attention heads.

8
num_hidden_layers int

Number of transformer layers.

4
intermediate_size int

Feed-forward hidden dimension.

1856
dropout float

Dropout probability.

0.0
timestep_type str | None

Timestep conditioning type.

'adalayernorm'
num_timesteps int

Number of diffusion training timesteps.

100
recon_type CorrectorReconType | str

Reconstruction target used by the corrector.

x_t_minus_1
target CorrectorTarget | str

Confidence target type.

recon_acc
attr_loss_weights tuple[float, float, float, float, float]

Per-attribute loss weights.

(1.0, 1.0, 1.0, 1.0, 1.0)
use_padding_as_vocab bool

Whether padding is part of the modeled vocabulary.

True
pos_emb CorrectorPositionEmbedding | str

Position embedding mode from the original implementation.

none
transformer_type CorrectorTransformerType | str

Corrector transformer variant.

aggregated
corrector_steps int

Number of correction passes per selected timestep.

1
corrector_t_list tuple[int, ...]

Explicit timesteps where the corrector is applied.

(10, 20, 30)
corrector_mask_mode CorrectorMaskMode | str

Strategy for selecting tokens to remask.

thresh
corrector_mask_threshold float

Confidence threshold for threshold masking.

0.7
corrector_temperature float

Temperature used for corrector resampling.

1.0
use_gumbel_noise bool

Whether to perturb confidence logits.

True
gumbel_temperature float

Temperature for confidence Gumbel noise.

1.0
time_adaptive_temperature bool

Whether to scale noise by timestep ratio.

False

Raises:

Type Description
ValueError

If a supplied dataset, shape, or option is unsupported.

Examples:

>>> cfg = LayoutCorrectorConfig(dataset_name="publaynet", vocab_size=100)
>>> cfg.max_token_length
125
Source code in models/layout-corrector/src/layout_corrector/configuration_layout_corrector.py
 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
class LayoutCorrectorConfig(ConfigMixin):
    """Configuration for the Layout-Corrector transformer.

    Args:
        dataset_name: Dataset key or alias used for labels.
        vocab_size: LayoutDM vocabulary size expected by the corrector.
        id2label: Optional class-id mapping. When omitted, the shared registry is used.
        max_seq_length: Maximum number of layout elements.
        num_attributes_per_element: Number of token attributes per element.
        hidden_size: Transformer hidden dimension.
        num_attention_heads: Number of attention heads.
        num_hidden_layers: Number of transformer layers.
        intermediate_size: Feed-forward hidden dimension.
        dropout: Dropout probability.
        timestep_type: Timestep conditioning type.
        num_timesteps: Number of diffusion training timesteps.
        recon_type: Reconstruction target used by the corrector.
        target: Confidence target type.
        attr_loss_weights: Per-attribute loss weights.
        use_padding_as_vocab: Whether padding is part of the modeled vocabulary.
        pos_emb: Position embedding mode from the original implementation.
        transformer_type: Corrector transformer variant.
        corrector_steps: Number of correction passes per selected timestep.
        corrector_t_list: Explicit timesteps where the corrector is applied.
        corrector_mask_mode: Strategy for selecting tokens to remask.
        corrector_mask_threshold: Confidence threshold for threshold masking.
        corrector_temperature: Temperature used for corrector resampling.
        use_gumbel_noise: Whether to perturb confidence logits.
        gumbel_temperature: Temperature for confidence Gumbel noise.
        time_adaptive_temperature: Whether to scale noise by timestep ratio.

    Raises:
        ValueError: If a supplied dataset, shape, or option is unsupported.

    Examples:
        >>> cfg = LayoutCorrectorConfig(dataset_name="publaynet", vocab_size=100)
        >>> cfg.max_token_length
        125
    """

    config_name = "corrector_config.json"

    @register_to_config
    def __init__(
        self,
        *,
        dataset_name: DatasetName | str,
        vocab_size: int,
        id2label: dict[int | str, str] | None = None,
        max_seq_length: int = 25,
        num_attributes_per_element: int = 5,
        hidden_size: int = 464,
        num_attention_heads: int = 8,
        num_hidden_layers: int = 4,
        intermediate_size: int = 1856,
        dropout: float = 0.0,
        timestep_type: str | None = "adalayernorm",
        num_timesteps: int = 100,
        recon_type: CorrectorReconType | str = CorrectorReconType.x_t_minus_1,
        target: CorrectorTarget | str = CorrectorTarget.recon_acc,
        attr_loss_weights: tuple[float, float, float, float, float] = (
            1.0,
            1.0,
            1.0,
            1.0,
            1.0,
        ),
        use_padding_as_vocab: bool = True,
        pos_emb: CorrectorPositionEmbedding | str = CorrectorPositionEmbedding.none,
        transformer_type: CorrectorTransformerType | str = (
            CorrectorTransformerType.aggregated
        ),
        corrector_steps: int = 1,
        corrector_t_list: tuple[int, ...] = (10, 20, 30),
        corrector_mask_mode: CorrectorMaskMode | str = CorrectorMaskMode.thresh,
        corrector_mask_threshold: float = 0.7,
        corrector_temperature: float = 1.0,
        use_gumbel_noise: bool = True,
        gumbel_temperature: float = 1.0,
        time_adaptive_temperature: bool = False,
    ) -> None:
        """Initialize a Layout-Corrector config.

        Args:
            dataset_name: Dataset key or alias used for labels.
            vocab_size: LayoutDM vocabulary size expected by the corrector.
            id2label: Optional class-id mapping.
            max_seq_length: Maximum number of layout elements.
            num_attributes_per_element: Number of token attributes per element.
            hidden_size: Transformer hidden dimension.
            num_attention_heads: Number of attention heads.
            num_hidden_layers: Number of transformer layers.
            intermediate_size: Feed-forward hidden dimension.
            dropout: Dropout probability.
            timestep_type: Timestep conditioning type.
            num_timesteps: Number of diffusion training timesteps.
            recon_type: Reconstruction target used by the corrector.
            target: Confidence target type.
            attr_loss_weights: Per-attribute loss weights.
            use_padding_as_vocab: Whether padding is part of the modeled vocabulary.
            pos_emb: Position embedding mode.
            transformer_type: Corrector transformer variant.
            corrector_steps: Number of correction passes per selected timestep.
            corrector_t_list: Explicit timesteps where the corrector is applied.
            corrector_mask_mode: Strategy for selecting tokens to remask.
            corrector_mask_threshold: Confidence threshold for threshold masking.
            corrector_temperature: Temperature used for corrector resampling.
            use_gumbel_noise: Whether to perturb confidence logits.
            gumbel_temperature: Temperature for confidence Gumbel noise.
            time_adaptive_temperature: Whether to scale noise by timestep ratio.

        Raises:
            ValueError: If a supplied dataset, shape, or option is unsupported.

        Examples:
            >>> cfg = LayoutCorrectorConfig(dataset_name="publaynet", vocab_size=100)
            >>> cfg.max_token_length
            125
        """
        try:
            dataset_name = str(normalize_dataset_name(dataset_name))
        except ValueError:
            if id2label is None:
                raise

            dataset_name = str(dataset_name)
        self.register_to_config(dataset_name=dataset_name)
        if vocab_size <= 0:
            raise ValueError("vocab_size must be positive")

        if max_seq_length <= 0:
            raise ValueError("max_seq_length must be positive")

        if num_attributes_per_element != 5:
            raise ValueError("Layout-Corrector supports 5 attributes per element")

        if num_timesteps <= 0:
            raise ValueError("num_timesteps must be positive")

        recon_type, target, transformer_type, pos_emb = (
            normalize_corrector_core_options(
                recon_type,
                target,
                transformer_type,
                pos_emb,
            )
        )
        if len(attr_loss_weights) != num_attributes_per_element:
            raise ValueError("attr_loss_weights must match num_attributes_per_element")

        if corrector_steps <= 0:
            raise ValueError("corrector_steps must be positive")

        try:
            corrector_mask_mode = normalize_corrector_mask_mode(corrector_mask_mode)
        except ValueError as exc:
            raise ValueError(
                f"Unsupported corrector_mask_mode: {corrector_mask_mode}"
            ) from exc

        if not 0.0 <= corrector_mask_threshold <= 1.0:
            raise ValueError("corrector_mask_threshold must be in [0, 1]")

        self.register_to_config(
            dataset_name=dataset_name,
            recon_type=str(recon_type),
            target=str(target),
            pos_emb=str(pos_emb),
            transformer_type=str(transformer_type),
            corrector_mask_mode=str(corrector_mask_mode),
        )

        self.dataset_name = dataset_name
        raw_id2label = id2label or id2label_for_dataset(dataset_name)
        self.id2label = {int(k): v for k, v in raw_id2label.items()}
        self.vocab_size = vocab_size
        self.max_seq_length = max_seq_length
        self.num_attributes_per_element = num_attributes_per_element
        self.hidden_size = hidden_size
        self.num_attention_heads = num_attention_heads
        self.num_hidden_layers = num_hidden_layers
        self.intermediate_size = intermediate_size
        self.dropout = dropout
        self.timestep_type = timestep_type
        self.num_timesteps = num_timesteps

        self.recon_type = str(recon_type)
        self.target = str(target)
        self.attr_loss_weights = tuple(float(v) for v in attr_loss_weights)
        self.use_padding_as_vocab = use_padding_as_vocab
        self.pos_emb = str(pos_emb)
        self.transformer_type = str(transformer_type)

        self.corrector_steps = corrector_steps
        self.corrector_t_list = tuple(int(v) for v in corrector_t_list)
        self.corrector_mask_mode = str(corrector_mask_mode)
        self.corrector_mask_threshold = corrector_mask_threshold
        self.corrector_temperature = corrector_temperature
        self.use_gumbel_noise = use_gumbel_noise
        self.gumbel_temperature = gumbel_temperature
        self.time_adaptive_temperature = time_adaptive_temperature

    @property
    def max_token_length(self) -> int:
        """Return the flattened token length for one layout sequence.

        Returns:
            Maximum token count after flattening element attributes.

        Examples:
            >>> LayoutCorrectorConfig(dataset_name="publaynet", vocab_size=100).max_token_length
            125
        """
        return self.max_seq_length * self.num_attributes_per_element

max_token_length property

max_token_length: int

Return the flattened token length for one layout sequence.

Returns:

Type Description
int

Maximum token count after flattening element attributes.

Examples:

>>> LayoutCorrectorConfig(dataset_name="publaynet", vocab_size=100).max_token_length
125

__init__

__init__(
    *,
    dataset_name: DatasetName | str,
    vocab_size: int,
    id2label: dict[int | str, str] | None = None,
    max_seq_length: int = 25,
    num_attributes_per_element: int = 5,
    hidden_size: int = 464,
    num_attention_heads: int = 8,
    num_hidden_layers: int = 4,
    intermediate_size: int = 1856,
    dropout: float = 0.0,
    timestep_type: str | None = "adalayernorm",
    num_timesteps: int = 100,
    recon_type: CorrectorReconType
    | str = CorrectorReconType.x_t_minus_1,
    target: CorrectorTarget
    | str = CorrectorTarget.recon_acc,
    attr_loss_weights: tuple[
        float, float, float, float, float
    ] = (1.0, 1.0, 1.0, 1.0, 1.0),
    use_padding_as_vocab: bool = True,
    pos_emb: CorrectorPositionEmbedding
    | str = CorrectorPositionEmbedding.none,
    transformer_type: CorrectorTransformerType
    | str = CorrectorTransformerType.aggregated,
    corrector_steps: int = 1,
    corrector_t_list: tuple[int, ...] = (10, 20, 30),
    corrector_mask_mode: CorrectorMaskMode
    | str = CorrectorMaskMode.thresh,
    corrector_mask_threshold: float = 0.7,
    corrector_temperature: float = 1.0,
    use_gumbel_noise: bool = True,
    gumbel_temperature: float = 1.0,
    time_adaptive_temperature: bool = False,
) -> None

Initialize a Layout-Corrector config.

Parameters:

Name Type Description Default
dataset_name DatasetName | str

Dataset key or alias used for labels.

required
vocab_size int

LayoutDM vocabulary size expected by the corrector.

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

Optional class-id mapping.

None
max_seq_length int

Maximum number of layout elements.

25
num_attributes_per_element int

Number of token attributes per element.

5
hidden_size int

Transformer hidden dimension.

464
num_attention_heads int

Number of attention heads.

8
num_hidden_layers int

Number of transformer layers.

4
intermediate_size int

Feed-forward hidden dimension.

1856
dropout float

Dropout probability.

0.0
timestep_type str | None

Timestep conditioning type.

'adalayernorm'
num_timesteps int

Number of diffusion training timesteps.

100
recon_type CorrectorReconType | str

Reconstruction target used by the corrector.

x_t_minus_1
target CorrectorTarget | str

Confidence target type.

recon_acc
attr_loss_weights tuple[float, float, float, float, float]

Per-attribute loss weights.

(1.0, 1.0, 1.0, 1.0, 1.0)
use_padding_as_vocab bool

Whether padding is part of the modeled vocabulary.

True
pos_emb CorrectorPositionEmbedding | str

Position embedding mode.

none
transformer_type CorrectorTransformerType | str

Corrector transformer variant.

aggregated
corrector_steps int

Number of correction passes per selected timestep.

1
corrector_t_list tuple[int, ...]

Explicit timesteps where the corrector is applied.

(10, 20, 30)
corrector_mask_mode CorrectorMaskMode | str

Strategy for selecting tokens to remask.

thresh
corrector_mask_threshold float

Confidence threshold for threshold masking.

0.7
corrector_temperature float

Temperature used for corrector resampling.

1.0
use_gumbel_noise bool

Whether to perturb confidence logits.

True
gumbel_temperature float

Temperature for confidence Gumbel noise.

1.0
time_adaptive_temperature bool

Whether to scale noise by timestep ratio.

False

Raises:

Type Description
ValueError

If a supplied dataset, shape, or option is unsupported.

Examples:

>>> cfg = LayoutCorrectorConfig(dataset_name="publaynet", vocab_size=100)
>>> cfg.max_token_length
125
Source code in models/layout-corrector/src/layout_corrector/configuration_layout_corrector.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
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
@register_to_config
def __init__(
    self,
    *,
    dataset_name: DatasetName | str,
    vocab_size: int,
    id2label: dict[int | str, str] | None = None,
    max_seq_length: int = 25,
    num_attributes_per_element: int = 5,
    hidden_size: int = 464,
    num_attention_heads: int = 8,
    num_hidden_layers: int = 4,
    intermediate_size: int = 1856,
    dropout: float = 0.0,
    timestep_type: str | None = "adalayernorm",
    num_timesteps: int = 100,
    recon_type: CorrectorReconType | str = CorrectorReconType.x_t_minus_1,
    target: CorrectorTarget | str = CorrectorTarget.recon_acc,
    attr_loss_weights: tuple[float, float, float, float, float] = (
        1.0,
        1.0,
        1.0,
        1.0,
        1.0,
    ),
    use_padding_as_vocab: bool = True,
    pos_emb: CorrectorPositionEmbedding | str = CorrectorPositionEmbedding.none,
    transformer_type: CorrectorTransformerType | str = (
        CorrectorTransformerType.aggregated
    ),
    corrector_steps: int = 1,
    corrector_t_list: tuple[int, ...] = (10, 20, 30),
    corrector_mask_mode: CorrectorMaskMode | str = CorrectorMaskMode.thresh,
    corrector_mask_threshold: float = 0.7,
    corrector_temperature: float = 1.0,
    use_gumbel_noise: bool = True,
    gumbel_temperature: float = 1.0,
    time_adaptive_temperature: bool = False,
) -> None:
    """Initialize a Layout-Corrector config.

    Args:
        dataset_name: Dataset key or alias used for labels.
        vocab_size: LayoutDM vocabulary size expected by the corrector.
        id2label: Optional class-id mapping.
        max_seq_length: Maximum number of layout elements.
        num_attributes_per_element: Number of token attributes per element.
        hidden_size: Transformer hidden dimension.
        num_attention_heads: Number of attention heads.
        num_hidden_layers: Number of transformer layers.
        intermediate_size: Feed-forward hidden dimension.
        dropout: Dropout probability.
        timestep_type: Timestep conditioning type.
        num_timesteps: Number of diffusion training timesteps.
        recon_type: Reconstruction target used by the corrector.
        target: Confidence target type.
        attr_loss_weights: Per-attribute loss weights.
        use_padding_as_vocab: Whether padding is part of the modeled vocabulary.
        pos_emb: Position embedding mode.
        transformer_type: Corrector transformer variant.
        corrector_steps: Number of correction passes per selected timestep.
        corrector_t_list: Explicit timesteps where the corrector is applied.
        corrector_mask_mode: Strategy for selecting tokens to remask.
        corrector_mask_threshold: Confidence threshold for threshold masking.
        corrector_temperature: Temperature used for corrector resampling.
        use_gumbel_noise: Whether to perturb confidence logits.
        gumbel_temperature: Temperature for confidence Gumbel noise.
        time_adaptive_temperature: Whether to scale noise by timestep ratio.

    Raises:
        ValueError: If a supplied dataset, shape, or option is unsupported.

    Examples:
        >>> cfg = LayoutCorrectorConfig(dataset_name="publaynet", vocab_size=100)
        >>> cfg.max_token_length
        125
    """
    try:
        dataset_name = str(normalize_dataset_name(dataset_name))
    except ValueError:
        if id2label is None:
            raise

        dataset_name = str(dataset_name)
    self.register_to_config(dataset_name=dataset_name)
    if vocab_size <= 0:
        raise ValueError("vocab_size must be positive")

    if max_seq_length <= 0:
        raise ValueError("max_seq_length must be positive")

    if num_attributes_per_element != 5:
        raise ValueError("Layout-Corrector supports 5 attributes per element")

    if num_timesteps <= 0:
        raise ValueError("num_timesteps must be positive")

    recon_type, target, transformer_type, pos_emb = (
        normalize_corrector_core_options(
            recon_type,
            target,
            transformer_type,
            pos_emb,
        )
    )
    if len(attr_loss_weights) != num_attributes_per_element:
        raise ValueError("attr_loss_weights must match num_attributes_per_element")

    if corrector_steps <= 0:
        raise ValueError("corrector_steps must be positive")

    try:
        corrector_mask_mode = normalize_corrector_mask_mode(corrector_mask_mode)
    except ValueError as exc:
        raise ValueError(
            f"Unsupported corrector_mask_mode: {corrector_mask_mode}"
        ) from exc

    if not 0.0 <= corrector_mask_threshold <= 1.0:
        raise ValueError("corrector_mask_threshold must be in [0, 1]")

    self.register_to_config(
        dataset_name=dataset_name,
        recon_type=str(recon_type),
        target=str(target),
        pos_emb=str(pos_emb),
        transformer_type=str(transformer_type),
        corrector_mask_mode=str(corrector_mask_mode),
    )

    self.dataset_name = dataset_name
    raw_id2label = id2label or id2label_for_dataset(dataset_name)
    self.id2label = {int(k): v for k, v in raw_id2label.items()}
    self.vocab_size = vocab_size
    self.max_seq_length = max_seq_length
    self.num_attributes_per_element = num_attributes_per_element
    self.hidden_size = hidden_size
    self.num_attention_heads = num_attention_heads
    self.num_hidden_layers = num_hidden_layers
    self.intermediate_size = intermediate_size
    self.dropout = dropout
    self.timestep_type = timestep_type
    self.num_timesteps = num_timesteps

    self.recon_type = str(recon_type)
    self.target = str(target)
    self.attr_loss_weights = tuple(float(v) for v in attr_loss_weights)
    self.use_padding_as_vocab = use_padding_as_vocab
    self.pos_emb = str(pos_emb)
    self.transformer_type = str(transformer_type)

    self.corrector_steps = corrector_steps
    self.corrector_t_list = tuple(int(v) for v in corrector_t_list)
    self.corrector_mask_mode = str(corrector_mask_mode)
    self.corrector_mask_threshold = corrector_mask_threshold
    self.corrector_temperature = corrector_temperature
    self.use_gumbel_noise = use_gumbel_noise
    self.gumbel_temperature = gumbel_temperature
    self.time_adaptive_temperature = time_adaptive_temperature

normalize_corrector_core_options

normalize_corrector_core_options(
    recon_type: CorrectorReconType | str,
    target: CorrectorTarget | str,
    transformer_type: CorrectorTransformerType | str,
    pos_emb: CorrectorPositionEmbedding | str,
) -> tuple[
    CorrectorReconType,
    CorrectorTarget,
    CorrectorTransformerType,
    CorrectorPositionEmbedding,
]

Normalize shared Layout-Corrector enum options.

Source code in models/layout-corrector/src/layout_corrector/configuration_layout_corrector.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
def normalize_corrector_core_options(
    recon_type: CorrectorReconType | str,
    target: CorrectorTarget | str,
    transformer_type: CorrectorTransformerType | str,
    pos_emb: CorrectorPositionEmbedding | str,
) -> tuple[
    CorrectorReconType,
    CorrectorTarget,
    CorrectorTransformerType,
    CorrectorPositionEmbedding,
]:
    """Normalize shared Layout-Corrector enum options."""
    try:
        normalized_recon_type = CorrectorReconType(recon_type)
    except ValueError as exc:
        raise ValueError(f"Unsupported recon_type: {recon_type}") from exc

    try:
        normalized_target = CorrectorTarget(target)
    except ValueError as exc:
        raise ValueError(f"Unsupported target: {target}") from exc

    try:
        normalized_transformer_type = CorrectorTransformerType(transformer_type)
    except ValueError as exc:
        raise ValueError("Only transformer_type='aggregated' is supported") from exc

    try:
        normalized_pos_emb = CorrectorPositionEmbedding(pos_emb)
    except ValueError as exc:
        raise ValueError(f"Unsupported pos_emb: {pos_emb}") from exc

    return (
        normalized_recon_type,
        normalized_target,
        normalized_transformer_type,
        normalized_pos_emb,
    )

conversion

Conversion helpers for original Layout-Corrector checkpoints.

LayoutDMPipelineLike

Bases: Protocol

Nested LayoutDM pipeline surface needed during conversion.

Source code in models/layout-corrector/src/layout_corrector/conversion.py
39
40
41
42
43
class LayoutDMPipelineLike(Protocol):
    """Nested LayoutDM pipeline surface needed during conversion."""

    tokenizer: _LayoutDMTokenizerLike
    scheduler: _LayoutDMSchedulerLike

CompatibilityKey

Bases: StrEnum

LayoutDM fields that must match the converted corrector config.

Source code in models/layout-corrector/src/layout_corrector/conversion.py
46
47
48
49
50
51
52
class CompatibilityKey(StrEnum):
    """LayoutDM fields that must match the converted corrector config."""

    vocab_size = auto()
    max_seq_length = auto()
    num_attributes_per_element = auto()
    num_timesteps = auto()

OriginalDatasetConfig

Bases: TypedDict

Dataset section of the original Layout-Corrector YAML.

Source code in models/layout-corrector/src/layout_corrector/conversion.py
55
56
57
58
class OriginalDatasetConfig(TypedDict, total=False):
    """Dataset section of the original Layout-Corrector YAML."""

    max_seq_length: int

OriginalDataConfig

Bases: TypedDict

Data section of the original Layout-Corrector YAML.

Source code in models/layout-corrector/src/layout_corrector/conversion.py
61
62
63
64
class OriginalDataConfig(TypedDict, total=False):
    """Data section of the original Layout-Corrector YAML."""

    var_order: str

OriginalModelConfig

Bases: TypedDict

Model section of the original Layout-Corrector YAML.

Source code in models/layout-corrector/src/layout_corrector/conversion.py
67
68
69
70
71
72
73
74
75
76
class OriginalModelConfig(TypedDict, total=False):
    """Model section of the original Layout-Corrector YAML."""

    num_timesteps: int
    recon_type: str
    target: str
    attr_loss_weights: list[float]
    use_padding_as_vocab: bool
    pos_emb: str
    transformer_type: str

OriginalEncoderLayerConfig

Bases: TypedDict

Backbone encoder-layer section of the original YAML.

Source code in models/layout-corrector/src/layout_corrector/conversion.py
79
80
81
82
83
84
class OriginalEncoderLayerConfig(TypedDict, total=False):
    """Backbone encoder-layer section of the original YAML."""

    nhead: int
    dropout: float
    timestep_type: str

OriginalBackboneConfig

Bases: TypedDict

Backbone section of the original Layout-Corrector YAML.

Source code in models/layout-corrector/src/layout_corrector/conversion.py
87
88
89
90
91
class OriginalBackboneConfig(TypedDict, total=False):
    """Backbone section of the original Layout-Corrector YAML."""

    num_layers: int
    encoder_layer: OriginalEncoderLayerConfig

OriginalConfig

Bases: TypedDict

Typed shape for the original Layout-Corrector YAML.

Source code in models/layout-corrector/src/layout_corrector/conversion.py
 94
 95
 96
 97
 98
 99
100
class OriginalConfig(TypedDict):
    """Typed shape for the original Layout-Corrector YAML."""

    data: OriginalDataConfig
    dataset: OriginalDatasetConfig
    model: OriginalModelConfig
    backbone: OriginalBackboneConfig

remap_corrector_key

remap_corrector_key(key: str) -> str

Map an original checkpoint key to the converted module key.

Parameters:

Name Type Description Default
key str

Original state-dict key.

required

Returns:

Type Description
str

Key accepted by AggregatedCategoricalTransformer.

Raises:

Type Description
ValueError

If the key does not use an expected original prefix.

Source code in models/layout-corrector/src/layout_corrector/conversion.py
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
def remap_corrector_key(key: str) -> str:
    """Map an original checkpoint key to the converted module key.

    Args:
        key: Original state-dict key.

    Returns:
        Key accepted by `AggregatedCategoricalTransformer`.

    Raises:
        ValueError: If the key does not use an expected original prefix.
    """
    for prefix in _ORIGINAL_KEY_PREFIXES:
        if key.startswith(prefix):
            return key.removeprefix(prefix)
    raise ValueError(f"Unexpected corrector checkpoint key: {key}")

split_original_corrector_state_dict

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

Strip original wrapper prefixes from a corrector state dict.

Parameters:

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

Original checkpoint state dictionary.

required

Returns:

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

Converted state dictionary.

Source code in models/layout-corrector/src/layout_corrector/conversion.py
121
122
123
124
125
126
127
128
129
130
131
132
def split_original_corrector_state_dict(
    state_dict: dict[str, Shaped[torch.Tensor, "..."]],
) -> dict[str, Shaped[torch.Tensor, "..."]]:
    """Strip original wrapper prefixes from a corrector state dict.

    Args:
        state_dict: Original checkpoint state dictionary.

    Returns:
        Converted state dictionary.
    """
    return {remap_corrector_key(key): value for key, value in state_dict.items()}

load_original_corrector_state_dict

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

Load and remap an original corrector checkpoint.

Parameters:

Name Type Description Default
path str | Path

Path to best_model.pt.

required

Returns:

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

Converted state dictionary.

Source code in models/layout-corrector/src/layout_corrector/conversion.py
135
136
137
138
139
140
141
142
143
144
145
146
147
148
def load_original_corrector_state_dict(
    path: str | Path,
) -> dict[str, Shaped[torch.Tensor, "..."]]:
    """Load and remap an original corrector checkpoint.

    Args:
        path: Path to `best_model.pt`.

    Returns:
        Converted state dictionary.
    """
    state = torch.load(path, map_location="cpu")
    raw = state.get("state_dict", state)
    return split_original_corrector_state_dict(raw)

corrector_config_from_original

corrector_config_from_original(
    *,
    dataset: str,
    config_path: str | Path,
    state_dict: dict[str, Shaped[Tensor, "..."]],
    layout_dm: LayoutDMPipelineLike,
) -> LayoutCorrectorConfig

Build a Layout-Corrector config from original files.

Parameters:

Name Type Description Default
dataset str

Dataset key or alias.

required
config_path str | Path

Original config.yaml path.

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

Converted corrector state dictionary.

required
layout_dm LayoutDMPipelineLike

Nested LayoutDM pipeline used for compatibility checks.

required

Returns:

Type Description
LayoutCorrectorConfig

Converted Layout-Corrector config.

Source code in models/layout-corrector/src/layout_corrector/conversion.py
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
def corrector_config_from_original(
    *,
    dataset: str,
    config_path: str | Path,
    state_dict: dict[str, Shaped[torch.Tensor, "..."]],
    layout_dm: LayoutDMPipelineLike,
) -> LayoutCorrectorConfig:
    """Build a Layout-Corrector config from original files.

    Args:
        dataset: Dataset key or alias.
        config_path: Original `config.yaml` path.
        state_dict: Converted corrector state dictionary.
        layout_dm: Nested LayoutDM pipeline used for compatibility checks.

    Returns:
        Converted Layout-Corrector config.
    """
    with Path(config_path).open() as f:
        original_config = cast(OriginalConfig, yaml.safe_load(f))
    data_cfg = original_config["data"]
    model_cfg = original_config["model"]
    layer_cfg = original_config["backbone"]["encoder_layer"]
    hidden_size = int(state_dict["cat_emb.weight"].shape[1])
    intermediate_size = int(state_dict["backbone.layers.0.linear1.weight"].shape[0])
    normalized_dataset = (
        str(normalize_dataset_name(dataset))
        if dataset not in _CRELLO_DATASET_ALIASES
        else CRELLO_BBOX_DATASET
    )
    id2label = (
        getattr(getattr(layout_dm, "tokenizer").config, "id2label")
        if normalized_dataset == CRELLO_BBOX_DATASET
        else None
    )
    return LayoutCorrectorConfig(
        dataset_name=normalized_dataset,
        id2label=id2label,
        vocab_size=int(state_dict["cat_emb.weight"].shape[0]),
        max_seq_length=int(original_config["dataset"].get("max_seq_length", 25)),
        num_attributes_per_element=len(
            data_cfg.get("var_order", _DEFAULT_VAR_ORDER).split("-")
        ),
        hidden_size=hidden_size,
        num_attention_heads=int(layer_cfg.get("nhead", 8)),
        num_hidden_layers=int(original_config["backbone"].get("num_layers", 4)),
        intermediate_size=intermediate_size,
        dropout=float(layer_cfg.get("dropout", 0.0)),
        timestep_type=layer_cfg.get("timestep_type", "adalayernorm"),
        num_timesteps=int(model_cfg.get("num_timesteps", 100)),
        recon_type=model_cfg.get("recon_type", "x_t-1"),
        target=model_cfg.get("target", "recon_acc"),
        attr_loss_weights=tuple(
            float(v) for v in model_cfg.get("attr_loss_weights", [1, 1, 1, 1, 1])
        ),
        use_padding_as_vocab=bool(model_cfg.get("use_padding_as_vocab", True)),
        pos_emb=model_cfg.get("pos_emb", "none"),
        transformer_type=model_cfg.get("transformer_type", "aggregated"),
    )

build_corrector_from_original

build_corrector_from_original(
    *,
    dataset: str,
    checkpoint_dir: str | Path,
    layout_dm: LayoutDMPipelineLike,
) -> LayoutCorrectorModel

Build a LayoutCorrectorModel from an original checkpoint directory.

Parameters:

Name Type Description Default
dataset str

Dataset key or alias.

required
checkpoint_dir str | Path

Directory containing best_model.pt and config.yaml.

required
layout_dm LayoutDMPipelineLike

Nested LayoutDM pipeline paired with the corrector.

required

Returns:

Type Description
LayoutCorrectorModel

Loaded and eval-mode corrector model.

Source code in models/layout-corrector/src/layout_corrector/conversion.py
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
def build_corrector_from_original(
    *,
    dataset: str,
    checkpoint_dir: str | Path,
    layout_dm: LayoutDMPipelineLike,
) -> LayoutCorrectorModel:
    """Build a `LayoutCorrectorModel` from an original checkpoint directory.

    Args:
        dataset: Dataset key or alias.
        checkpoint_dir: Directory containing `best_model.pt` and `config.yaml`.
        layout_dm: Nested LayoutDM pipeline paired with the corrector.

    Returns:
        Loaded and eval-mode corrector model.
    """
    checkpoint_dir = Path(checkpoint_dir)
    state_dict = load_original_corrector_state_dict(checkpoint_dir / "best_model.pt")
    config = corrector_config_from_original(
        dataset=dataset,
        config_path=checkpoint_dir / "config.yaml",
        state_dict=state_dict,
        layout_dm=layout_dm,
    )
    validate_layout_dm_compatibility(layout_dm=layout_dm, corrector_config=config)
    corrector = LayoutCorrectorModel(**config.config)
    corrector.model.load_state_dict(state_dict, strict=True)
    corrector.eval()
    return corrector

discover_seed_dirs

discover_seed_dirs(job_dir: str | Path) -> list[Path]

Discover corrector seed directories under an original job directory.

Parameters:

Name Type Description Default
job_dir str | Path

Seed directory or parent directory containing seed subdirectories.

required

Returns:

Type Description
list[Path]

Sorted list of directories containing config.yaml.

Source code in models/layout-corrector/src/layout_corrector/conversion.py
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
def discover_seed_dirs(job_dir: str | Path) -> list[Path]:
    """Discover corrector seed directories under an original job directory.

    Args:
        job_dir: Seed directory or parent directory containing seed subdirectories.

    Returns:
        Sorted list of directories containing `config.yaml`.
    """
    path = Path(job_dir)
    if (path / "config.yaml").is_file():
        return [path]
    return sorted(
        child for child in path.iterdir() if (child / "config.yaml").is_file()
    )

validate_layout_dm_compatibility

validate_layout_dm_compatibility(
    *,
    layout_dm: LayoutDMPipelineLike,
    corrector_config: LayoutCorrectorConfig,
) -> None

Validate that a corrector config matches its nested LayoutDM pipeline.

Parameters:

Name Type Description Default
layout_dm LayoutDMPipelineLike

Nested LayoutDM pipeline.

required
corrector_config LayoutCorrectorConfig

Corrector config to compare.

required

Raises:

Type Description
ValueError

If a shared tokenizer or scheduler field differs.

Source code in models/layout-corrector/src/layout_corrector/conversion.py
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
def validate_layout_dm_compatibility(
    *,
    layout_dm: LayoutDMPipelineLike,
    corrector_config: LayoutCorrectorConfig,
) -> None:
    """Validate that a corrector config matches its nested LayoutDM pipeline.

    Args:
        layout_dm: Nested LayoutDM pipeline.
        corrector_config: Corrector config to compare.

    Raises:
        ValueError: If a shared tokenizer or scheduler field differs.
    """
    tokenizer_config = getattr(layout_dm, "tokenizer").config
    scheduler_config = getattr(layout_dm, "scheduler").config
    checks: dict[CompatibilityKey, int] = {
        CompatibilityKey.vocab_size: tokenizer_config.vocab_size,
        CompatibilityKey.max_seq_length: tokenizer_config.max_seq_length,
        CompatibilityKey.num_attributes_per_element: (
            tokenizer_config.num_attributes_per_element
        ),
        CompatibilityKey.num_timesteps: scheduler_config.num_timesteps,
    }
    for key, value in checks.items():
        field = str(key)
        if getattr(corrector_config, field) != value:
            raise ValueError(
                f"LayoutDM/{field} mismatch: corrector={getattr(corrector_config, field)} "
                f"layout_dm={value}"
            )

model_card

Model-card builders for converted Layout-Corrector checkpoints.

LayoutCorrectorCardDataset

Bases: StrEnum

Canonical datasets used in Layout-Corrector model-card metadata.

Source code in models/layout-corrector/src/layout_corrector/model_card.py
19
20
21
22
23
24
class LayoutCorrectorCardDataset(StrEnum):
    """Canonical datasets used in Layout-Corrector model-card metadata."""

    rico25 = auto()
    publaynet = auto()
    crello = auto()

LayoutCorrectorCardValue

Bases: StrEnum

Closed metadata and tag values emitted by Layout-Corrector cards.

Source code in models/layout-corrector/src/layout_corrector/model_card.py
27
28
29
30
31
32
33
34
class LayoutCorrectorCardValue(StrEnum):
    """Closed metadata and tag values emitted by Layout-Corrector cards."""

    license = "mit"
    library = "diffusers"
    pipeline_tag = "unconditional-layout-generation"
    layout_generation_tag = "layout-generation"
    layout_corrector_tag = "layout-corrector"

layout_corrector_model_card

layout_corrector_model_card(
    *,
    dataset: str,
    parity_metrics: Sequence[ParityMetricInput]
    | None = None,
) -> ModelCard

Build the Layout-Corrector model card for a converted checkpoint.

Source code in models/layout-corrector/src/layout_corrector/model_card.py
 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
def layout_corrector_model_card(
    *,
    dataset: str,
    parity_metrics: Sequence[ParityMetricInput] | None = None,
) -> ModelCard:
    """Build the Layout-Corrector model card for a converted checkpoint."""
    dataset_name = _normalize_model_card_dataset(dataset)
    dataset_id = _DATASET_IDS[dataset_name]
    model_id = f"{_MODEL_ID_PREFIX}-{dataset_name}"
    metrics = parity_metrics or [
        ParityMetric(
            dataset=str(dataset_name),
            tokenizer_exact="checked in reference parity",
            deterministic_exact="not applicable",
            logits_max_abs=0.0,
            logits_max_rel=0.0,
        )
    ]
    how_to_use = f"""
from layout_corrector import LayoutCorrectorModel, LayoutCorrectorPipeline
from layout_dm import LayoutDMPipeline

layout_dm = LayoutDMPipeline.from_pretrained("{model_id}", subfolder="layout_dm")
corrector = LayoutCorrectorModel.from_pretrained("{model_id}", subfolder="corrector")
pipe = LayoutCorrectorPipeline(layout_dm=layout_dm, corrector=corrector)
out = pipe(batch_size=1, seed=0, sampling="deterministic")
print(out.bbox, out.labels, out.mask)
"""
    return build_layout_model_card(
        model_id=model_id,
        model_name=f"Layout-Corrector {dataset_name}",
        dataset_ids=[dataset_id],
        license=str(LayoutCorrectorCardValue.license),
        library_name=str(LayoutCorrectorCardValue.library),
        pipeline_tag=str(LayoutCorrectorCardValue.pipeline_tag),
        tags=[
            str(LayoutCorrectorCardValue.layout_generation_tag),
            str(LayoutCorrectorCardValue.layout_corrector_tag),
            str(LayoutCorrectorCardValue.library),
            str(dataset_name),
        ],
        model_details=(
            "Layout-Corrector is a training-free corrector module for discrete "
            "diffusion layout generators such as LayoutDM. This Diffusers-format "
            "checkpoint pairs a converted LayoutDM generator with the released "
            "Layout-Corrector confidence model, which scores intermediate "
            "reconstructed layout tokens, re-masks low-confidence tokens, and "
            "lets LayoutDM regenerate those positions during sampling."
        ),
        intended_uses=(
            "Use this checkpoint for research and evaluation of controllable "
            "layout generation and Layout-Corrector sampling behavior."
        ),
        limitations=(
            "The model predicts layout structure only. Generated boxes and labels "
            "require downstream validation before use in design or document "
            "processing workflows."
        ),
        how_to_use=how_to_use,
        training_data=(
            f"The original checkpoint was trained on `{dataset_id}` using the "
            "preprocessing released with the original Layout-Corrector starter kit."
        ),
        parity_metrics=metrics,
        citation_bibtex=_LAYOUT_CORRECTOR_BIBTEX,
        original_implementation_url=_ORIGINAL_IMPLEMENTATION_URL,
    )

modeling_layout_corrector

Layout-Corrector confidence model components.

LayoutCorrectorOutput dataclass

Bases: BaseOutput

Output container for Layout-Corrector confidence logits.

Parameters:

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

Token confidence logits shaped (batch, tokens).

required
Source code in models/layout-corrector/src/layout_corrector/modeling_layout_corrector.py
43
44
45
46
47
48
49
50
51
@dataclass
class LayoutCorrectorOutput(BaseOutput):
    """Output container for Layout-Corrector confidence logits.

    Args:
        logits: Token confidence logits shaped `(batch, tokens)`.
    """

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

AggregatedCategoricalTransformer

Bases: Module

Aggregated-token transformer used by the original Layout-Corrector model.

Source code in models/layout-corrector/src/layout_corrector/modeling_layout_corrector.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
class AggregatedCategoricalTransformer(nn.Module):
    """Aggregated-token transformer used by the original Layout-Corrector model."""

    def __init__(
        self,
        *,
        vocab_size: int,
        max_token_length: int,
        hidden_size: int,
        num_attention_heads: int,
        num_hidden_layers: int,
        intermediate_size: int,
        dropout: float,
        timestep_type: TimestepEmbeddingType | str | None,
        pos_emb: CorrectorPositionEmbedding | str,
        num_attributes_per_element: int,
        num_timesteps: int,
    ) -> None:
        """Initialize the aggregated categorical transformer.

        Args:
            vocab_size: Number of token ids.
            max_token_length: Flattened token sequence length.
            hidden_size: Transformer hidden dimension.
            num_attention_heads: Number of attention heads.
            num_hidden_layers: Number of transformer layers.
            intermediate_size: Feed-forward hidden dimension.
            dropout: Dropout probability.
            timestep_type: Timestep conditioning type.
            pos_emb: Position embedding mode.
            num_attributes_per_element: Number of attributes per layout element.
            num_timesteps: Diffusion timestep count.

        Raises:
            ValueError: If `max_token_length` is not divisible by attributes per
                element.
        """
        super().__init__()
        if max_token_length % num_attributes_per_element:
            raise ValueError(
                "max_token_length must divide by num_attributes_per_element"
            )

        self.num_attributes_per_element = num_attributes_per_element
        layer = Block(
            d_model=hidden_size,
            nhead=num_attention_heads,
            dim_feedforward=intermediate_size,
            dropout=dropout,
            batch_first=True,
            norm_first=True,
            diffusion_step=num_timesteps,
            timestep_type=timestep_type,
        )
        backbone = TransformerEncoder(layer, num_hidden_layers)
        cat_emb = nn.Embedding(vocab_size, hidden_size)
        # The token embedding dropout is fixed at 0.1; ``dropout`` controls
        # only the transformer backbone, matching the released recipe.
        drop = nn.Dropout(0.1)
        head = nn.Sequential(
            nn.LayerNorm(hidden_size),
            nn.Linear(hidden_size, 1, bias=False),
        )
        enc = nn.Sequential(
            nn.Linear(num_attributes_per_element * hidden_size, hidden_size),
            nn.ReLU(),
        )
        dec = nn.Sequential(
            nn.Linear(hidden_size, num_attributes_per_element * hidden_size),
            nn.ReLU(),
        )
        self.backbone = backbone
        self.cat_emb = cat_emb
        self.pos_emb = None
        if CorrectorPositionEmbedding(pos_emb) is not CorrectorPositionEmbedding.none:
            self.pos_emb = ElementPositionalEmbedding(
                hidden_size,
                max_token_length // num_attributes_per_element,
                n_attr_per_elem=1,
            )
        self.drop = drop
        self.head = head
        self.enc = enc
        self.dec = dec

    def forward(
        self,
        input_ids: Int[torch.Tensor, "batch tokens"],
        *,
        timestep: Int[torch.Tensor, "batch"] | None = None,
        src_key_padding_mask: Bool[torch.Tensor, "batch tokens"] | None = None,
    ) -> Float[torch.Tensor, "batch tokens 1"]:
        """Predict token confidence logits.

        Args:
            input_ids: Flattened token ids.
            timestep: Optional diffusion timestep tensor.
            src_key_padding_mask: Optional padding mask.

        Returns:
            Confidence logits shaped `(batch, tokens, 1)`.
        """
        batch_size, token_length = input_ids.shape
        step = self.num_attributes_per_element
        hidden = self.drop(self.cat_emb(input_ids))
        hidden = hidden.reshape(
            batch_size, token_length // step, step * hidden.size(-1)
        )
        hidden = self.enc(hidden)
        if self.pos_emb is not None:
            hidden = hidden + self.pos_emb(hidden)
        if src_key_padding_mask is not None:
            element_padding_mask = src_key_padding_mask.reshape(
                batch_size, token_length // step, step
            ).any(dim=-1)
        else:
            element_padding_mask = None
        hidden = self.backbone(
            hidden,
            src_key_padding_mask=element_padding_mask,
            timestep=timestep,
        )
        hidden = self.dec(hidden)
        hidden = hidden.reshape(batch_size, token_length, -1)
        return self.head(hidden)

__init__

__init__(
    *,
    vocab_size: int,
    max_token_length: int,
    hidden_size: int,
    num_attention_heads: int,
    num_hidden_layers: int,
    intermediate_size: int,
    dropout: float,
    timestep_type: TimestepEmbeddingType | str | None,
    pos_emb: CorrectorPositionEmbedding | str,
    num_attributes_per_element: int,
    num_timesteps: int,
) -> None

Initialize the aggregated categorical transformer.

Parameters:

Name Type Description Default
vocab_size int

Number of token ids.

required
max_token_length int

Flattened token sequence length.

required
hidden_size int

Transformer hidden dimension.

required
num_attention_heads int

Number of attention heads.

required
num_hidden_layers int

Number of transformer layers.

required
intermediate_size int

Feed-forward hidden dimension.

required
dropout float

Dropout probability.

required
timestep_type TimestepEmbeddingType | str | None

Timestep conditioning type.

required
pos_emb CorrectorPositionEmbedding | str

Position embedding mode.

required
num_attributes_per_element int

Number of attributes per layout element.

required
num_timesteps int

Diffusion timestep count.

required

Raises:

Type Description
ValueError

If max_token_length is not divisible by attributes per element.

Source code in models/layout-corrector/src/layout_corrector/modeling_layout_corrector.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
127
128
129
130
131
132
133
134
135
136
137
def __init__(
    self,
    *,
    vocab_size: int,
    max_token_length: int,
    hidden_size: int,
    num_attention_heads: int,
    num_hidden_layers: int,
    intermediate_size: int,
    dropout: float,
    timestep_type: TimestepEmbeddingType | str | None,
    pos_emb: CorrectorPositionEmbedding | str,
    num_attributes_per_element: int,
    num_timesteps: int,
) -> None:
    """Initialize the aggregated categorical transformer.

    Args:
        vocab_size: Number of token ids.
        max_token_length: Flattened token sequence length.
        hidden_size: Transformer hidden dimension.
        num_attention_heads: Number of attention heads.
        num_hidden_layers: Number of transformer layers.
        intermediate_size: Feed-forward hidden dimension.
        dropout: Dropout probability.
        timestep_type: Timestep conditioning type.
        pos_emb: Position embedding mode.
        num_attributes_per_element: Number of attributes per layout element.
        num_timesteps: Diffusion timestep count.

    Raises:
        ValueError: If `max_token_length` is not divisible by attributes per
            element.
    """
    super().__init__()
    if max_token_length % num_attributes_per_element:
        raise ValueError(
            "max_token_length must divide by num_attributes_per_element"
        )

    self.num_attributes_per_element = num_attributes_per_element
    layer = Block(
        d_model=hidden_size,
        nhead=num_attention_heads,
        dim_feedforward=intermediate_size,
        dropout=dropout,
        batch_first=True,
        norm_first=True,
        diffusion_step=num_timesteps,
        timestep_type=timestep_type,
    )
    backbone = TransformerEncoder(layer, num_hidden_layers)
    cat_emb = nn.Embedding(vocab_size, hidden_size)
    # The token embedding dropout is fixed at 0.1; ``dropout`` controls
    # only the transformer backbone, matching the released recipe.
    drop = nn.Dropout(0.1)
    head = nn.Sequential(
        nn.LayerNorm(hidden_size),
        nn.Linear(hidden_size, 1, bias=False),
    )
    enc = nn.Sequential(
        nn.Linear(num_attributes_per_element * hidden_size, hidden_size),
        nn.ReLU(),
    )
    dec = nn.Sequential(
        nn.Linear(hidden_size, num_attributes_per_element * hidden_size),
        nn.ReLU(),
    )
    self.backbone = backbone
    self.cat_emb = cat_emb
    self.pos_emb = None
    if CorrectorPositionEmbedding(pos_emb) is not CorrectorPositionEmbedding.none:
        self.pos_emb = ElementPositionalEmbedding(
            hidden_size,
            max_token_length // num_attributes_per_element,
            n_attr_per_elem=1,
        )
    self.drop = drop
    self.head = head
    self.enc = enc
    self.dec = dec

forward

forward(
    input_ids: Int[Tensor, "batch tokens"],
    *,
    timestep: Int[Tensor, "batch"] | None = None,
    src_key_padding_mask: Bool[Tensor, "batch tokens"]
    | None = None,
) -> Float[torch.Tensor, "batch tokens 1"]

Predict token confidence logits.

Parameters:

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

Flattened token ids.

required
timestep Int[Tensor, 'batch'] | None

Optional diffusion timestep tensor.

None
src_key_padding_mask Bool[Tensor, 'batch tokens'] | None

Optional padding mask.

None

Returns:

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

Confidence logits shaped (batch, tokens, 1).

Source code in models/layout-corrector/src/layout_corrector/modeling_layout_corrector.py
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
def forward(
    self,
    input_ids: Int[torch.Tensor, "batch tokens"],
    *,
    timestep: Int[torch.Tensor, "batch"] | None = None,
    src_key_padding_mask: Bool[torch.Tensor, "batch tokens"] | None = None,
) -> Float[torch.Tensor, "batch tokens 1"]:
    """Predict token confidence logits.

    Args:
        input_ids: Flattened token ids.
        timestep: Optional diffusion timestep tensor.
        src_key_padding_mask: Optional padding mask.

    Returns:
        Confidence logits shaped `(batch, tokens, 1)`.
    """
    batch_size, token_length = input_ids.shape
    step = self.num_attributes_per_element
    hidden = self.drop(self.cat_emb(input_ids))
    hidden = hidden.reshape(
        batch_size, token_length // step, step * hidden.size(-1)
    )
    hidden = self.enc(hidden)
    if self.pos_emb is not None:
        hidden = hidden + self.pos_emb(hidden)
    if src_key_padding_mask is not None:
        element_padding_mask = src_key_padding_mask.reshape(
            batch_size, token_length // step, step
        ).any(dim=-1)
    else:
        element_padding_mask = None
    hidden = self.backbone(
        hidden,
        src_key_padding_mask=element_padding_mask,
        timestep=timestep,
    )
    hidden = self.dec(hidden)
    hidden = hidden.reshape(batch_size, token_length, -1)
    return self.head(hidden)

LayoutCorrectorModel

Bases: ModelMixin, ConfigMixin

Diffusers-compatible Layout-Corrector confidence model.

Source code in models/layout-corrector/src/layout_corrector/modeling_layout_corrector.py
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
class LayoutCorrectorModel(ModelMixin, ConfigMixin):
    """Diffusers-compatible Layout-Corrector confidence model."""

    config_name = "corrector_config.json"

    @register_to_config
    def __init__(
        self,
        *,
        dataset_name: str,
        vocab_size: int,
        id2label: dict[int | str, str] | None = None,
        max_seq_length: int = 25,
        num_attributes_per_element: int = 5,
        hidden_size: int = 464,
        num_attention_heads: int = 8,
        num_hidden_layers: int = 4,
        intermediate_size: int = 1856,
        dropout: float = 0.0,
        timestep_type: TimestepEmbeddingType | str | None = "adalayernorm",
        num_timesteps: int = 100,
        recon_type: CorrectorReconType | str = CorrectorReconType.x_t_minus_1,
        target: CorrectorTarget | str = CorrectorTarget.recon_acc,
        attr_loss_weights: tuple[float, ...] = (1.0, 1.0, 1.0, 1.0, 1.0),
        use_padding_as_vocab: bool = True,
        pos_emb: CorrectorPositionEmbedding | str = CorrectorPositionEmbedding.none,
        transformer_type: CorrectorTransformerType | str = (
            CorrectorTransformerType.aggregated
        ),
        corrector_steps: int = 1,
        corrector_t_list: tuple[int, ...] = (10, 20, 30),
        corrector_mask_mode: CorrectorMaskMode | str = CorrectorMaskMode.thresh,
        corrector_mask_threshold: float = 0.7,
        corrector_temperature: float = 1.0,
        use_gumbel_noise: bool = True,
        gumbel_temperature: float = 1.0,
        time_adaptive_temperature: bool = False,
    ) -> None:
        """Initialize a Layout-Corrector model.

        Args:
            dataset_name: Dataset key or alias used for labels.
            vocab_size: LayoutDM vocabulary size.
            id2label: Optional class-id mapping.
            max_seq_length: Maximum number of elements.
            num_attributes_per_element: Number of token attributes per element.
            hidden_size: Transformer hidden dimension.
            num_attention_heads: Number of attention heads.
            num_hidden_layers: Number of transformer layers.
            intermediate_size: Feed-forward hidden dimension.
            dropout: Dropout probability.
            timestep_type: Timestep conditioning type.
            num_timesteps: Number of diffusion timesteps.
            recon_type: Reconstruction target.
            target: Confidence target type.
            attr_loss_weights: Per-attribute loss weights.
            use_padding_as_vocab: Whether padding is modeled as a vocabulary token.
            pos_emb: Position embedding mode.
            transformer_type: Corrector transformer type.
            corrector_steps: Number of correction passes.
            corrector_t_list: Explicit correction timesteps.
            corrector_mask_mode: Token remasking mode.
            corrector_mask_threshold: Threshold for confidence remasking.
            corrector_temperature: Corrector sampling temperature.
            use_gumbel_noise: Whether confidence logits receive Gumbel noise.
            gumbel_temperature: Confidence-noise temperature.
            time_adaptive_temperature: Whether to scale noise by timestep ratio.

        Raises:
            ValueError: If reconstruction, target, or transformer options are
                unsupported.
        """
        super().__init__()
        recon_type, target, transformer_type, pos_emb = (
            normalize_corrector_core_options(
                recon_type,
                target,
                transformer_type,
                pos_emb,
            )
        )
        try:
            corrector_mask_mode = normalize_corrector_mask_mode(corrector_mask_mode)
        except ValueError as exc:
            raise ValueError(
                f"Unsupported corrector_mask_mode: {corrector_mask_mode}"
            ) from exc

        try:
            dataset_name = str(normalize_dataset_name(dataset_name))
        except ValueError:
            if id2label is None:
                raise

            dataset_name = str(dataset_name)
        normalized_id2label = {
            int(k): v
            for k, v in (id2label or id2label_for_dataset(dataset_name)).items()
        }
        self.register_to_config(
            dataset_name=dataset_name,
            id2label=normalized_id2label,
            corrector_t_list=tuple(corrector_t_list),
            attr_loss_weights=tuple(attr_loss_weights),
            recon_type=str(recon_type),
            target=str(target),
            pos_emb=str(pos_emb),
            transformer_type=str(transformer_type),
            corrector_mask_mode=str(corrector_mask_mode),
        )
        self.vocab_size = vocab_size
        self.id2label = normalized_id2label
        self.recon_type = recon_type
        self.corrector_steps = corrector_steps
        self.corrector_t_list = tuple(corrector_t_list)
        self.corrector_mask_mode = corrector_mask_mode
        self.corrector_mask_threshold = corrector_mask_threshold
        self.corrector_temperature = corrector_temperature
        self.use_padding_as_vocab = use_padding_as_vocab
        self.use_gumbel_noise = use_gumbel_noise
        self.gumbel_temperature = gumbel_temperature
        self.time_adaptive_temperature = time_adaptive_temperature
        self.model = AggregatedCategoricalTransformer(
            vocab_size=vocab_size,
            max_token_length=max_seq_length * num_attributes_per_element,
            hidden_size=hidden_size,
            num_attention_heads=num_attention_heads,
            num_hidden_layers=num_hidden_layers,
            intermediate_size=intermediate_size,
            dropout=dropout,
            timestep_type=timestep_type,
            pos_emb=pos_emb,
            num_attributes_per_element=num_attributes_per_element,
            num_timesteps=num_timesteps,
        )

    def initialize_weights(self) -> None:
        """Apply the released training initialization on the active device."""
        self.apply(_init_layout_corrector_weights)

    def forward(
        self,
        input_ids: Int[torch.Tensor, "batch tokens"],
        timesteps: Int[torch.Tensor, "batch"],
        padding_mask: Bool[torch.Tensor, "batch tokens"] | None = None,
    ) -> LayoutCorrectorOutput:
        """Run the corrector model.

        Args:
            input_ids: Flattened token ids.
            timesteps: Diffusion timestep tensor.
            padding_mask: Optional padding mask.

        Returns:
            `LayoutCorrectorOutput` containing token confidence logits.
        """
        src_key_padding_mask = None if self.use_padding_as_vocab else padding_mask
        logits = self.model(
            input_ids,
            timestep=timesteps,
            src_key_padding_mask=src_key_padding_mask,
        ).squeeze(-1)
        if not self.use_padding_as_vocab and padding_mask is not None:
            logits = logits.masked_fill(padding_mask, 1000.0)
        return LayoutCorrectorOutput(logits=logits)

    def calc_confidence_score(
        self,
        input_ids: Int[torch.Tensor, "batch tokens"],
        timesteps: Int[torch.Tensor, "batch"],
        padding_mask: Bool[torch.Tensor, "batch tokens"] | None = None,
    ) -> Float[torch.Tensor, "batch tokens"]:
        """Return confidence logits for token remasking.

        Args:
            input_ids: Flattened token ids.
            timesteps: Diffusion timestep tensor.
            padding_mask: Optional padding mask.

        Returns:
            Confidence logits shaped `(batch, tokens)`.
        """
        return self(
            input_ids=input_ids,
            timesteps=timesteps,
            padding_mask=padding_mask,
        ).logits

__init__

__init__(
    *,
    dataset_name: str,
    vocab_size: int,
    id2label: dict[int | str, str] | None = None,
    max_seq_length: int = 25,
    num_attributes_per_element: int = 5,
    hidden_size: int = 464,
    num_attention_heads: int = 8,
    num_hidden_layers: int = 4,
    intermediate_size: int = 1856,
    dropout: float = 0.0,
    timestep_type: TimestepEmbeddingType
    | str
    | None = "adalayernorm",
    num_timesteps: int = 100,
    recon_type: CorrectorReconType
    | str = CorrectorReconType.x_t_minus_1,
    target: CorrectorTarget
    | str = CorrectorTarget.recon_acc,
    attr_loss_weights: tuple[float, ...] = (
        1.0,
        1.0,
        1.0,
        1.0,
        1.0,
    ),
    use_padding_as_vocab: bool = True,
    pos_emb: CorrectorPositionEmbedding
    | str = CorrectorPositionEmbedding.none,
    transformer_type: CorrectorTransformerType
    | str = CorrectorTransformerType.aggregated,
    corrector_steps: int = 1,
    corrector_t_list: tuple[int, ...] = (10, 20, 30),
    corrector_mask_mode: CorrectorMaskMode
    | str = CorrectorMaskMode.thresh,
    corrector_mask_threshold: float = 0.7,
    corrector_temperature: float = 1.0,
    use_gumbel_noise: bool = True,
    gumbel_temperature: float = 1.0,
    time_adaptive_temperature: bool = False,
) -> None

Initialize a Layout-Corrector model.

Parameters:

Name Type Description Default
dataset_name str

Dataset key or alias used for labels.

required
vocab_size int

LayoutDM vocabulary size.

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

Optional class-id mapping.

None
max_seq_length int

Maximum number of elements.

25
num_attributes_per_element int

Number of token attributes per element.

5
hidden_size int

Transformer hidden dimension.

464
num_attention_heads int

Number of attention heads.

8
num_hidden_layers int

Number of transformer layers.

4
intermediate_size int

Feed-forward hidden dimension.

1856
dropout float

Dropout probability.

0.0
timestep_type TimestepEmbeddingType | str | None

Timestep conditioning type.

'adalayernorm'
num_timesteps int

Number of diffusion timesteps.

100
recon_type CorrectorReconType | str

Reconstruction target.

x_t_minus_1
target CorrectorTarget | str

Confidence target type.

recon_acc
attr_loss_weights tuple[float, ...]

Per-attribute loss weights.

(1.0, 1.0, 1.0, 1.0, 1.0)
use_padding_as_vocab bool

Whether padding is modeled as a vocabulary token.

True
pos_emb CorrectorPositionEmbedding | str

Position embedding mode.

none
transformer_type CorrectorTransformerType | str

Corrector transformer type.

aggregated
corrector_steps int

Number of correction passes.

1
corrector_t_list tuple[int, ...]

Explicit correction timesteps.

(10, 20, 30)
corrector_mask_mode CorrectorMaskMode | str

Token remasking mode.

thresh
corrector_mask_threshold float

Threshold for confidence remasking.

0.7
corrector_temperature float

Corrector sampling temperature.

1.0
use_gumbel_noise bool

Whether confidence logits receive Gumbel noise.

True
gumbel_temperature float

Confidence-noise temperature.

1.0
time_adaptive_temperature bool

Whether to scale noise by timestep ratio.

False

Raises:

Type Description
ValueError

If reconstruction, target, or transformer options are unsupported.

Source code in models/layout-corrector/src/layout_corrector/modeling_layout_corrector.py
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
@register_to_config
def __init__(
    self,
    *,
    dataset_name: str,
    vocab_size: int,
    id2label: dict[int | str, str] | None = None,
    max_seq_length: int = 25,
    num_attributes_per_element: int = 5,
    hidden_size: int = 464,
    num_attention_heads: int = 8,
    num_hidden_layers: int = 4,
    intermediate_size: int = 1856,
    dropout: float = 0.0,
    timestep_type: TimestepEmbeddingType | str | None = "adalayernorm",
    num_timesteps: int = 100,
    recon_type: CorrectorReconType | str = CorrectorReconType.x_t_minus_1,
    target: CorrectorTarget | str = CorrectorTarget.recon_acc,
    attr_loss_weights: tuple[float, ...] = (1.0, 1.0, 1.0, 1.0, 1.0),
    use_padding_as_vocab: bool = True,
    pos_emb: CorrectorPositionEmbedding | str = CorrectorPositionEmbedding.none,
    transformer_type: CorrectorTransformerType | str = (
        CorrectorTransformerType.aggregated
    ),
    corrector_steps: int = 1,
    corrector_t_list: tuple[int, ...] = (10, 20, 30),
    corrector_mask_mode: CorrectorMaskMode | str = CorrectorMaskMode.thresh,
    corrector_mask_threshold: float = 0.7,
    corrector_temperature: float = 1.0,
    use_gumbel_noise: bool = True,
    gumbel_temperature: float = 1.0,
    time_adaptive_temperature: bool = False,
) -> None:
    """Initialize a Layout-Corrector model.

    Args:
        dataset_name: Dataset key or alias used for labels.
        vocab_size: LayoutDM vocabulary size.
        id2label: Optional class-id mapping.
        max_seq_length: Maximum number of elements.
        num_attributes_per_element: Number of token attributes per element.
        hidden_size: Transformer hidden dimension.
        num_attention_heads: Number of attention heads.
        num_hidden_layers: Number of transformer layers.
        intermediate_size: Feed-forward hidden dimension.
        dropout: Dropout probability.
        timestep_type: Timestep conditioning type.
        num_timesteps: Number of diffusion timesteps.
        recon_type: Reconstruction target.
        target: Confidence target type.
        attr_loss_weights: Per-attribute loss weights.
        use_padding_as_vocab: Whether padding is modeled as a vocabulary token.
        pos_emb: Position embedding mode.
        transformer_type: Corrector transformer type.
        corrector_steps: Number of correction passes.
        corrector_t_list: Explicit correction timesteps.
        corrector_mask_mode: Token remasking mode.
        corrector_mask_threshold: Threshold for confidence remasking.
        corrector_temperature: Corrector sampling temperature.
        use_gumbel_noise: Whether confidence logits receive Gumbel noise.
        gumbel_temperature: Confidence-noise temperature.
        time_adaptive_temperature: Whether to scale noise by timestep ratio.

    Raises:
        ValueError: If reconstruction, target, or transformer options are
            unsupported.
    """
    super().__init__()
    recon_type, target, transformer_type, pos_emb = (
        normalize_corrector_core_options(
            recon_type,
            target,
            transformer_type,
            pos_emb,
        )
    )
    try:
        corrector_mask_mode = normalize_corrector_mask_mode(corrector_mask_mode)
    except ValueError as exc:
        raise ValueError(
            f"Unsupported corrector_mask_mode: {corrector_mask_mode}"
        ) from exc

    try:
        dataset_name = str(normalize_dataset_name(dataset_name))
    except ValueError:
        if id2label is None:
            raise

        dataset_name = str(dataset_name)
    normalized_id2label = {
        int(k): v
        for k, v in (id2label or id2label_for_dataset(dataset_name)).items()
    }
    self.register_to_config(
        dataset_name=dataset_name,
        id2label=normalized_id2label,
        corrector_t_list=tuple(corrector_t_list),
        attr_loss_weights=tuple(attr_loss_weights),
        recon_type=str(recon_type),
        target=str(target),
        pos_emb=str(pos_emb),
        transformer_type=str(transformer_type),
        corrector_mask_mode=str(corrector_mask_mode),
    )
    self.vocab_size = vocab_size
    self.id2label = normalized_id2label
    self.recon_type = recon_type
    self.corrector_steps = corrector_steps
    self.corrector_t_list = tuple(corrector_t_list)
    self.corrector_mask_mode = corrector_mask_mode
    self.corrector_mask_threshold = corrector_mask_threshold
    self.corrector_temperature = corrector_temperature
    self.use_padding_as_vocab = use_padding_as_vocab
    self.use_gumbel_noise = use_gumbel_noise
    self.gumbel_temperature = gumbel_temperature
    self.time_adaptive_temperature = time_adaptive_temperature
    self.model = AggregatedCategoricalTransformer(
        vocab_size=vocab_size,
        max_token_length=max_seq_length * num_attributes_per_element,
        hidden_size=hidden_size,
        num_attention_heads=num_attention_heads,
        num_hidden_layers=num_hidden_layers,
        intermediate_size=intermediate_size,
        dropout=dropout,
        timestep_type=timestep_type,
        pos_emb=pos_emb,
        num_attributes_per_element=num_attributes_per_element,
        num_timesteps=num_timesteps,
    )

initialize_weights

initialize_weights() -> None

Apply the released training initialization on the active device.

Source code in models/layout-corrector/src/layout_corrector/modeling_layout_corrector.py
317
318
319
def initialize_weights(self) -> None:
    """Apply the released training initialization on the active device."""
    self.apply(_init_layout_corrector_weights)

forward

forward(
    input_ids: Int[Tensor, "batch tokens"],
    timesteps: Int[Tensor, "batch"],
    padding_mask: Bool[Tensor, "batch tokens"]
    | None = None,
) -> LayoutCorrectorOutput

Run the corrector model.

Parameters:

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

Flattened token ids.

required
timesteps Int[Tensor, 'batch']

Diffusion timestep tensor.

required
padding_mask Bool[Tensor, 'batch tokens'] | None

Optional padding mask.

None

Returns:

Type Description
LayoutCorrectorOutput

LayoutCorrectorOutput containing token confidence logits.

Source code in models/layout-corrector/src/layout_corrector/modeling_layout_corrector.py
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
def forward(
    self,
    input_ids: Int[torch.Tensor, "batch tokens"],
    timesteps: Int[torch.Tensor, "batch"],
    padding_mask: Bool[torch.Tensor, "batch tokens"] | None = None,
) -> LayoutCorrectorOutput:
    """Run the corrector model.

    Args:
        input_ids: Flattened token ids.
        timesteps: Diffusion timestep tensor.
        padding_mask: Optional padding mask.

    Returns:
        `LayoutCorrectorOutput` containing token confidence logits.
    """
    src_key_padding_mask = None if self.use_padding_as_vocab else padding_mask
    logits = self.model(
        input_ids,
        timestep=timesteps,
        src_key_padding_mask=src_key_padding_mask,
    ).squeeze(-1)
    if not self.use_padding_as_vocab and padding_mask is not None:
        logits = logits.masked_fill(padding_mask, 1000.0)
    return LayoutCorrectorOutput(logits=logits)

calc_confidence_score

calc_confidence_score(
    input_ids: Int[Tensor, "batch tokens"],
    timesteps: Int[Tensor, "batch"],
    padding_mask: Bool[Tensor, "batch tokens"]
    | None = None,
) -> Float[torch.Tensor, "batch tokens"]

Return confidence logits for token remasking.

Parameters:

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

Flattened token ids.

required
timesteps Int[Tensor, 'batch']

Diffusion timestep tensor.

required
padding_mask Bool[Tensor, 'batch tokens'] | None

Optional padding mask.

None

Returns:

Type Description
Float[Tensor, 'batch tokens']

Confidence logits shaped (batch, tokens).

Source code in models/layout-corrector/src/layout_corrector/modeling_layout_corrector.py
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
def calc_confidence_score(
    self,
    input_ids: Int[torch.Tensor, "batch tokens"],
    timesteps: Int[torch.Tensor, "batch"],
    padding_mask: Bool[torch.Tensor, "batch tokens"] | None = None,
) -> Float[torch.Tensor, "batch tokens"]:
    """Return confidence logits for token remasking.

    Args:
        input_ids: Flattened token ids.
        timesteps: Diffusion timestep tensor.
        padding_mask: Optional padding mask.

    Returns:
        Confidence logits shaped `(batch, tokens)`.
    """
    return self(
        input_ids=input_ids,
        timesteps=timesteps,
        padding_mask=padding_mask,
    ).logits

pipeline_layout_corrector

Diffusers pipeline wrapper for Layout-Corrector guided generation.

OutputType

Bases: StrEnum

Supported Layout-Corrector pipeline output formats.

Source code in models/layout-corrector/src/layout_corrector/pipeline_layout_corrector.py
40
41
42
43
44
class OutputType(StrEnum):
    """Supported Layout-Corrector pipeline output formats."""

    dataclass = auto()
    dict = auto()

LayoutCorrectorPipeline

Bases: DiffusionPipeline

Diffusers pipeline that applies Layout-Corrector during LayoutDM sampling.

Parameters:

Name Type Description Default
layout_dm LayoutDMPipeline

Base LayoutDM pipeline.

required
corrector LayoutCorrectorModel

Corrector model used to score and remask tokens.

required
processor LayoutDMProcessor | None

Optional processor for conditional layout inputs.

None

Raises:

Type Description
ValueError

Pipeline construction does not raise directly.

Examples:

>>> LayoutCorrectorPipeline.from_pretrained
<bound method...
Source code in models/layout-corrector/src/layout_corrector/pipeline_layout_corrector.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
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
class LayoutCorrectorPipeline(DiffusionPipeline):
    """Diffusers pipeline that applies Layout-Corrector during LayoutDM sampling.

    Args:
        layout_dm: Base LayoutDM pipeline.
        corrector: Corrector model used to score and remask tokens.
        processor: Optional processor for conditional layout inputs.

    Raises:
        ValueError: Pipeline construction does not raise directly.

    Examples:
        >>> LayoutCorrectorPipeline.from_pretrained  # doctest: +ELLIPSIS
        <bound method...
    """

    model_cpu_offload_seq: ClassVar[str] = "layout_dm.denoiser->corrector"

    def __init__(
        self,
        layout_dm: LayoutDMPipeline,
        corrector: LayoutCorrectorModel,
        processor: LayoutDMProcessor | None = None,
    ) -> None:
        """Initialize the composite pipeline.

        Args:
            layout_dm: Base LayoutDM pipeline.
            corrector: Confidence model used to remask low-confidence tokens.
            processor: Optional processor for conditional inputs.
        """
        super().__init__()
        self.register_modules(layout_dm=layout_dm, corrector=corrector)
        self.layout_dm = layout_dm
        self.corrector = corrector
        self.processor = processor or layout_dm.processor
        self.corrector.eval()

    def to(
        self,
        *args: torch.device | str | torch.dtype | None,
        dtype: torch.dtype | None = None,
        device: torch.device | str | None = None,
        silence_dtype_warnings: bool = False,
    ) -> Self:
        """Move the nested LayoutDM pipeline and the corrector together."""
        self.layout_dm.to(
            *args,
            dtype=dtype,
            device=device,
            silence_dtype_warnings=silence_dtype_warnings,
        )
        return super().to(
            *args,
            dtype=dtype,
            device=device,
            silence_dtype_warnings=silence_dtype_warnings,
        )

    @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"]
        | list[ArrayLikeInput]
        | None = None,
        bbox: Float[torch.Tensor, "batch elements 4"]
        | Float[np.ndarray, "batch elements 4"]
        | list[ArrayLikeInput]
        | None = None,
        mask: Bool[torch.Tensor, "batch elements"]
        | Bool[np.ndarray, "batch elements"]
        | list[ArrayLikeInput]
        | None = None,
        num_elements: int | list[int] | Int[torch.Tensor, "batch"] | None = None,
        box_format: BoxFormat | str = BoxFormat.xywh,
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
        num_inference_steps: int | None = None,
        sampling: SamplingMode | str = SamplingMode.random,
        temperature: float = 1.0,
        top_k: int = 5,
        top_p: float = 0.9,
        corrector_steps: int | None = None,
        corrector_t_list: Sequence[int] | None = None,
        corrector_start: int = -1,
        corrector_end: int = -1,
        corrector_mask_mode: CorrectorMaskMode | str | None = None,
        corrector_mask_threshold: float | None = None,
        corrector_temperature: float | None = None,
        use_gumbel_noise: bool | None = None,
        gumbel_temperature: float | None = None,
        time_adaptive_temperature: bool | None = None,
        output_type: OutputType | str = OutputType.dataclass,
        return_intermediates: bool = False,
    ) -> (
        LayoutGenerationOutput
        | dict[
            str,
            Float[torch.Tensor, "batch elements 4"]
            | Int[torch.Tensor, "batch elements"]
            | Bool[torch.Tensor, "batch elements"]
            | Int[torch.Tensor, "batch tokens"]
            | Float[torch.Tensor, "steps batch tokens"]
            | list[Int[torch.Tensor, "batch tokens"]]
            | dict[int, str]
            | dict[str, str]
            | None,
        ]
    ):
        """Generate layouts with optional Layout-Corrector guidance.

        Args:
            batch_size: Number of layouts to sample for unconditional generation.
            seed: Optional seed used when `generator` is not supplied.
            generator: Optional PyTorch generator.
            condition_type: Condition mode such as `"unconditional"` or `"label"`.
            labels: Optional class ids for conditional generation.
            bbox: Optional boxes for conditional generation.
            mask: Optional element mask for conditional generation.
            num_elements: Reserved for future element-count conditioning.
            box_format: Coordinate format for conditional boxes.
            normalized: Whether conditional boxes are normalized.
            canvas_size: Pixel canvas used when `normalized=False`.
            num_inference_steps: Optional inference timestep count.
            sampling: Base LayoutDM sampling strategy.
            temperature: Base sampling temperature.
            top_k: Top-k cutoff for top-k sampling.
            top_p: Nucleus cutoff for top-p sampling.
            corrector_steps: Optional override for correction passes.
            corrector_t_list: Optional explicit correction timesteps.
            corrector_start: Range start for correction when no list is supplied.
            corrector_end: Range end for correction when no list is supplied.
            corrector_mask_mode: Optional override for remasking mode.
            corrector_mask_threshold: Optional threshold override.
            corrector_temperature: Optional confidence temperature override.
            use_gumbel_noise: Optional confidence-noise override.
            gumbel_temperature: Optional confidence-noise temperature override.
            time_adaptive_temperature: Optional adaptive-noise override.
            output_type: `"dataclass"` or `"dict"`.
            return_intermediates: Whether to include scores and trajectory.

        Returns:
            `LayoutGenerationOutput` by default, or a dictionary when requested.

        Raises:
            ValueError: If conditional generation is missing `bbox` or `labels`, or
                if `output_type` is unsupported.

        Examples:
            >>> LayoutCorrectorPipeline.__call__  # doctest: +ELLIPSIS
            <function...
        """
        _ = num_elements
        generator = resolve_torch_generator(generator=generator, seed=seed)
        canonical = normalize_condition_type(condition_type)
        condition = None
        if canonical != "unconditional":
            missing_inputs = [
                name
                for name, value in (("bbox", bbox), ("labels", labels))
                if value is None
            ]
            if missing_inputs:
                raise ValueError(
                    "bbox and labels are required "
                    f"for condition_type={condition_type}; "
                    f"missing {', '.join(missing_inputs)}"
                )

            processor_inputs = {
                "bbox": bbox,
                "labels": labels,
                "mask": mask,
                "box_format": box_format,
                "normalized": normalized,
                "canvas_size": canvas_size,
            }
            processed = self.processor(**processor_inputs)
            decoded_input = self.layout_dm.tokenizer.decode_layout(
                processed["input_ids"]
            )
            condition = build_condition(
                self.layout_dm.tokenizer,
                cond_type=canonical,
                bbox=decoded_input["bbox"],
                labels=decoded_input["labels"],
                mask=decoded_input["mask"],
            )
            batch_size = condition.input_ids.shape[0]

        corrector_cfg = LayoutCorrectorSamplingConfig(
            sampling=sampling,
            temperature=temperature,
            top_k=top_k,
            top_p=top_p,
            num_inference_steps=num_inference_steps,
            corrector_steps=corrector_steps or self.corrector.corrector_steps,
            corrector_t_list=tuple(
                self.corrector.corrector_t_list
                if corrector_t_list is None
                else corrector_t_list
            ),
            corrector_start=corrector_start,
            corrector_end=corrector_end,
            corrector_mask_mode=corrector_mask_mode
            or self.corrector.corrector_mask_mode,
            corrector_mask_threshold=corrector_mask_threshold
            if corrector_mask_threshold is not None
            else self.corrector.corrector_mask_threshold,
            corrector_temperature=corrector_temperature
            if corrector_temperature is not None
            else self.corrector.corrector_temperature,
            use_gumbel_noise=use_gumbel_noise
            if use_gumbel_noise is not None
            else self.corrector.use_gumbel_noise,
            gumbel_temperature=gumbel_temperature
            if gumbel_temperature is not None
            else self.corrector.gumbel_temperature,
            time_adaptive_temperature=time_adaptive_temperature
            if time_adaptive_temperature is not None
            else self.corrector.time_adaptive_temperature,
        )
        sampling_cfg = LayoutDMSamplingConfig(
            name=sampling,
            temperature=temperature,
            top_k=top_k,
            top_p=top_p,
            num_inference_steps=num_inference_steps,
        )
        self.layout_dm.scheduler.set_timesteps(num_inference_steps, device=self.device)
        sample = self.layout_dm.scheduler.initial_sample(
            batch_size,
            self.layout_dm.tokenizer.config.max_token_length,
            device=self.device,
            condition=condition,
        )
        trajectory = [] if return_intermediates else None
        scores = [] if return_intermediates else None
        previous_timestep = self.layout_dm.scheduler.config.num_timesteps
        for timestep in self.layout_dm.scheduler.timesteps:
            timestep_value = int(timestep.item())
            timestep_batch = torch.full(
                (batch_size,),
                timestep_value,
                device=self.device,
                dtype=torch.long,
            )
            if should_apply_corrector(timestep_value, corrector_cfg):
                sample, confidence = self._step_with_corrector(
                    sample=sample,
                    timestep_batch=timestep_batch,
                    condition=condition,
                    sampling=corrector_cfg,
                    generator=generator,
                )
                if scores is not None and confidence is not None:
                    scores.append(confidence.detach().cpu())
            else:
                input_ids = log_onehot_to_index(sample)
                logits = self.layout_dm.denoiser(
                    input_ids=input_ids, timesteps=timestep_batch
                ).logits
                sample = self.layout_dm.scheduler.step(
                    logits,
                    timestep_batch,
                    sample,
                    previous_timestep=previous_timestep,
                    sampling=sampling_cfg,
                    condition=condition,
                    generator=generator,
                ).prev_sample
            previous_timestep = timestep_value
            if trajectory is not None:
                trajectory.append(log_onehot_to_index(sample).detach().cpu())

        sequences = log_onehot_to_index(sample).detach().cpu()
        decoded = self.layout_dm.tokenizer.decode_layout(sequences)
        output = LayoutGenerationOutput(
            bbox=decoded["bbox"],
            labels=decoded["labels"],
            mask=decoded["mask"],
            id2label=dict(self.corrector.id2label),
            sequences=sequences,
            scores=torch.stack(scores) if scores else None,
            trajectory=trajectory,
            intermediates={"condition_type": canonical}
            if return_intermediates
            else None,
        )
        normalized_output_type = normalize_output_type(output_type)
        if normalized_output_type is OutputType.dict:
            return dict(output)
        if normalized_output_type is OutputType.dataclass:
            return output
        assert_never(normalized_output_type)

    generate = __call__

    def _step_with_corrector(
        self,
        *,
        sample: Float[torch.Tensor, "batch vocab tokens"],
        timestep_batch: Int[torch.Tensor, "batch"],
        condition: LayoutDMCondition | None,
        sampling: LayoutCorrectorSamplingConfig,
        generator: torch.Generator | None,
    ) -> tuple[
        Float[torch.Tensor, "batch vocab tokens"],
        Float[torch.Tensor, "batch tokens"] | None,
    ]:
        confidence = None
        current = sample
        for _ in range(sampling.corrector_steps):
            input_ids = log_onehot_to_index(current)
            denoiser_logits = self.layout_dm.denoiser(
                input_ids=input_ids, timesteps=timestep_batch
            ).logits
            model_log_prob = self.layout_dm.scheduler.predict_start(denoiser_logits)
            if self.corrector.recon_type is CorrectorReconType.x_t_minus_1:
                model_log_prob = self.layout_dm.scheduler.q_posterior(
                    model_log_prob, current, timestep_batch
                )
                model_log_prob[:, self.layout_dm.tokenizer.mask_token_id, :] = -70.0
            model_log_prob = self.layout_dm.scheduler.apply_condition(
                model_log_prob, condition
            )
            x0_recon_ids = multinomial(
                (model_log_prob / sampling.temperature)
                .softmax(dim=1)
                .permute(0, 2, 1)
                .reshape(-1, model_log_prob.size(1)),
                1,
                generator=generator,
                device=model_log_prob.device,
            ).reshape(model_log_prob.shape[0], model_log_prob.shape[-1])
            confidence = self.corrector.calc_confidence_score(
                x0_recon_ids,
                timestep_batch,
                padding_mask=x0_recon_ids == self.layout_dm.tokenizer.pad_token_id,
            )
            adjusted = confidence
            mask_ratio = self._mask_ratio(timestep_batch)
            if sampling.use_gumbel_noise:
                adjusted = add_confidence_gumbel_noise(
                    adjusted,
                    timestep=timestep_batch,
                    mask_ratio=mask_ratio,
                    temperature=sampling.gumbel_temperature,
                    time_adaptive_temperature=sampling.time_adaptive_temperature,
                    generator=generator,
                )
            remask = select_tokens_to_remask(
                adjusted,
                mask_ratio=mask_ratio,
                mode=sampling.corrector_mask_mode,
                threshold=sampling.corrector_mask_threshold,
                temperature=sampling.corrector_temperature,
            )
            x0_recon_ids = x0_recon_ids.masked_fill(
                remask, self.layout_dm.tokenizer.mask_token_id
            )
            if condition is not None:
                x0_recon_ids = torch.where(
                    condition.mask.to(x0_recon_ids.device),
                    condition.input_ids.to(x0_recon_ids.device),
                    x0_recon_ids,
                )
            current = index_to_log_onehot(
                x0_recon_ids, self.layout_dm.scheduler.vocab_size
            )
        return current, confidence

    def _mask_ratio(self, timestep_batch: Int[torch.Tensor, "batch"]) -> float:
        timestep = int(timestep_batch[0].item())
        timestep = max(0, min(timestep, self.layout_dm.scheduler.config.num_timesteps))
        if timestep == 0:
            return 0.0
        return float(timestep / self.layout_dm.scheduler.config.num_timesteps)

    def save_pretrained(
        self, save_directory: str | Path, *, safe_serialization: bool = True
    ) -> None:
        """Save the nested LayoutDM pipeline and corrector model.

        Args:
            save_directory: Destination directory.
            safe_serialization: Whether to save weights as safetensors.

        Returns:
            None.

        Raises:
            OSError: If files cannot be written.

        Examples:
            >>> LayoutCorrectorPipeline.save_pretrained  # doctest: +ELLIPSIS
            <function...
        """
        save_path = Path(save_directory)
        save_path.mkdir(parents=True, exist_ok=True)
        self.layout_dm.save_pretrained(
            save_path / "layout_dm", safe_serialization=safe_serialization
        )
        self.corrector.save_pretrained(
            save_path / "corrector", safe_serialization=safe_serialization
        )

    @classmethod
    def from_pretrained(
        cls,
        pretrained_model_name_or_path: str | Path,
        *,
        processor: LayoutDMProcessor | None = None,
    ) -> "LayoutCorrectorPipeline":
        """Load a Layout-Corrector pipeline from a saved directory.

        Args:
            pretrained_model_name_or_path: Directory containing `layout_dm/` and
                `corrector/` subdirectories.
            processor: Optional processor override.

        Returns:
            Loaded `LayoutCorrectorPipeline`.

        Raises:
            OSError: If nested component files are missing.

        Examples:
            >>> LayoutCorrectorPipeline.from_pretrained  # doctest: +ELLIPSIS
            <bound method...
        """
        path = Path(pretrained_model_name_or_path)
        layout_dm = LayoutDMPipeline.from_pretrained(path / "layout_dm")
        corrector = LayoutCorrectorModel.from_pretrained(path / "corrector")
        return cls(layout_dm=layout_dm, corrector=corrector, processor=processor)

__init__

__init__(
    layout_dm: LayoutDMPipeline,
    corrector: LayoutCorrectorModel,
    processor: LayoutDMProcessor | None = None,
) -> None

Initialize the composite pipeline.

Parameters:

Name Type Description Default
layout_dm LayoutDMPipeline

Base LayoutDM pipeline.

required
corrector LayoutCorrectorModel

Confidence model used to remask low-confidence tokens.

required
processor LayoutDMProcessor | None

Optional processor for conditional inputs.

None
Source code in models/layout-corrector/src/layout_corrector/pipeline_layout_corrector.py
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
def __init__(
    self,
    layout_dm: LayoutDMPipeline,
    corrector: LayoutCorrectorModel,
    processor: LayoutDMProcessor | None = None,
) -> None:
    """Initialize the composite pipeline.

    Args:
        layout_dm: Base LayoutDM pipeline.
        corrector: Confidence model used to remask low-confidence tokens.
        processor: Optional processor for conditional inputs.
    """
    super().__init__()
    self.register_modules(layout_dm=layout_dm, corrector=corrector)
    self.layout_dm = layout_dm
    self.corrector = corrector
    self.processor = processor or layout_dm.processor
    self.corrector.eval()

to

to(
    *args: device | str | dtype | None,
    dtype: dtype | None = None,
    device: device | str | None = None,
    silence_dtype_warnings: bool = False,
) -> Self

Move the nested LayoutDM pipeline and the corrector together.

Source code in models/layout-corrector/src/layout_corrector/pipeline_layout_corrector.py
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
def to(
    self,
    *args: torch.device | str | torch.dtype | None,
    dtype: torch.dtype | None = None,
    device: torch.device | str | None = None,
    silence_dtype_warnings: bool = False,
) -> Self:
    """Move the nested LayoutDM pipeline and the corrector together."""
    self.layout_dm.to(
        *args,
        dtype=dtype,
        device=device,
        silence_dtype_warnings=silence_dtype_warnings,
    )
    return super().to(
        *args,
        dtype=dtype,
        device=device,
        silence_dtype_warnings=silence_dtype_warnings,
    )

__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"]
    | list[ArrayLikeInput]
    | None = None,
    bbox: Float[Tensor, "batch elements 4"]
    | Float[ndarray, "batch elements 4"]
    | list[ArrayLikeInput]
    | None = None,
    mask: Bool[Tensor, "batch elements"]
    | Bool[ndarray, "batch elements"]
    | list[ArrayLikeInput]
    | None = None,
    num_elements: int
    | list[int]
    | Int[Tensor, "batch"]
    | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    num_inference_steps: int | None = None,
    sampling: SamplingMode | str = SamplingMode.random,
    temperature: float = 1.0,
    top_k: int = 5,
    top_p: float = 0.9,
    corrector_steps: int | None = None,
    corrector_t_list: Sequence[int] | None = None,
    corrector_start: int = -1,
    corrector_end: int = -1,
    corrector_mask_mode: CorrectorMaskMode
    | str
    | None = None,
    corrector_mask_threshold: float | None = None,
    corrector_temperature: float | None = None,
    use_gumbel_noise: bool | None = None,
    gumbel_temperature: float | None = None,
    time_adaptive_temperature: bool | None = None,
    output_type: OutputType | str = OutputType.dataclass,
    return_intermediates: bool = False,
) -> (
    LayoutGenerationOutput
    | dict[
        str,
        Float[torch.Tensor, "batch elements 4"]
        | Int[torch.Tensor, "batch elements"]
        | Bool[torch.Tensor, "batch elements"]
        | Int[torch.Tensor, "batch tokens"]
        | Float[torch.Tensor, "steps batch tokens"]
        | list[Int[torch.Tensor, "batch tokens"]]
        | dict[int, str]
        | dict[str, str]
        | None,
    ]
)

Generate layouts with optional Layout-Corrector guidance.

Parameters:

Name Type Description Default
batch_size int

Number of layouts to sample for unconditional generation.

1
seed int | None

Optional seed used when generator is not supplied.

None
generator Generator | None

Optional PyTorch generator.

None
condition_type ConditionType | str

Condition mode such as "unconditional" or "label".

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

Optional class ids for conditional generation.

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

Optional boxes for conditional generation.

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

Optional element mask for conditional generation.

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

Reserved for future element-count conditioning.

None
box_format BoxFormat | str

Coordinate format for conditional boxes.

xywh
normalized bool

Whether conditional boxes are normalized.

True
canvas_size tuple[int, int] | None

Pixel canvas used when normalized=False.

None
num_inference_steps int | None

Optional inference timestep count.

None
sampling SamplingMode | str

Base LayoutDM sampling strategy.

random
temperature float

Base sampling temperature.

1.0
top_k int

Top-k cutoff for top-k sampling.

5
top_p float

Nucleus cutoff for top-p sampling.

0.9
corrector_steps int | None

Optional override for correction passes.

None
corrector_t_list Sequence[int] | None

Optional explicit correction timesteps.

None
corrector_start int

Range start for correction when no list is supplied.

-1
corrector_end int

Range end for correction when no list is supplied.

-1
corrector_mask_mode CorrectorMaskMode | str | None

Optional override for remasking mode.

None
corrector_mask_threshold float | None

Optional threshold override.

None
corrector_temperature float | None

Optional confidence temperature override.

None
use_gumbel_noise bool | None

Optional confidence-noise override.

None
gumbel_temperature float | None

Optional confidence-noise temperature override.

None
time_adaptive_temperature bool | None

Optional adaptive-noise override.

None
output_type OutputType | str

"dataclass" or "dict".

dataclass
return_intermediates bool

Whether to include scores and trajectory.

False

Returns:

Type Description
LayoutGenerationOutput | dict[str, Float[Tensor, 'batch elements 4'] | Int[Tensor, 'batch elements'] | Bool[Tensor, 'batch elements'] | Int[Tensor, 'batch tokens'] | Float[Tensor, 'steps batch tokens'] | list[Int[Tensor, 'batch tokens']] | dict[int, str] | dict[str, str] | None]

LayoutGenerationOutput by default, or a dictionary when requested.

Raises:

Type Description
ValueError

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

Examples:

>>> LayoutCorrectorPipeline.__call__
<function...
Source code in models/layout-corrector/src/layout_corrector/pipeline_layout_corrector.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
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
@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"]
    | list[ArrayLikeInput]
    | None = None,
    bbox: Float[torch.Tensor, "batch elements 4"]
    | Float[np.ndarray, "batch elements 4"]
    | list[ArrayLikeInput]
    | None = None,
    mask: Bool[torch.Tensor, "batch elements"]
    | Bool[np.ndarray, "batch elements"]
    | list[ArrayLikeInput]
    | None = None,
    num_elements: int | list[int] | Int[torch.Tensor, "batch"] | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    num_inference_steps: int | None = None,
    sampling: SamplingMode | str = SamplingMode.random,
    temperature: float = 1.0,
    top_k: int = 5,
    top_p: float = 0.9,
    corrector_steps: int | None = None,
    corrector_t_list: Sequence[int] | None = None,
    corrector_start: int = -1,
    corrector_end: int = -1,
    corrector_mask_mode: CorrectorMaskMode | str | None = None,
    corrector_mask_threshold: float | None = None,
    corrector_temperature: float | None = None,
    use_gumbel_noise: bool | None = None,
    gumbel_temperature: float | None = None,
    time_adaptive_temperature: bool | None = None,
    output_type: OutputType | str = OutputType.dataclass,
    return_intermediates: bool = False,
) -> (
    LayoutGenerationOutput
    | dict[
        str,
        Float[torch.Tensor, "batch elements 4"]
        | Int[torch.Tensor, "batch elements"]
        | Bool[torch.Tensor, "batch elements"]
        | Int[torch.Tensor, "batch tokens"]
        | Float[torch.Tensor, "steps batch tokens"]
        | list[Int[torch.Tensor, "batch tokens"]]
        | dict[int, str]
        | dict[str, str]
        | None,
    ]
):
    """Generate layouts with optional Layout-Corrector guidance.

    Args:
        batch_size: Number of layouts to sample for unconditional generation.
        seed: Optional seed used when `generator` is not supplied.
        generator: Optional PyTorch generator.
        condition_type: Condition mode such as `"unconditional"` or `"label"`.
        labels: Optional class ids for conditional generation.
        bbox: Optional boxes for conditional generation.
        mask: Optional element mask for conditional generation.
        num_elements: Reserved for future element-count conditioning.
        box_format: Coordinate format for conditional boxes.
        normalized: Whether conditional boxes are normalized.
        canvas_size: Pixel canvas used when `normalized=False`.
        num_inference_steps: Optional inference timestep count.
        sampling: Base LayoutDM sampling strategy.
        temperature: Base sampling temperature.
        top_k: Top-k cutoff for top-k sampling.
        top_p: Nucleus cutoff for top-p sampling.
        corrector_steps: Optional override for correction passes.
        corrector_t_list: Optional explicit correction timesteps.
        corrector_start: Range start for correction when no list is supplied.
        corrector_end: Range end for correction when no list is supplied.
        corrector_mask_mode: Optional override for remasking mode.
        corrector_mask_threshold: Optional threshold override.
        corrector_temperature: Optional confidence temperature override.
        use_gumbel_noise: Optional confidence-noise override.
        gumbel_temperature: Optional confidence-noise temperature override.
        time_adaptive_temperature: Optional adaptive-noise override.
        output_type: `"dataclass"` or `"dict"`.
        return_intermediates: Whether to include scores and trajectory.

    Returns:
        `LayoutGenerationOutput` by default, or a dictionary when requested.

    Raises:
        ValueError: If conditional generation is missing `bbox` or `labels`, or
            if `output_type` is unsupported.

    Examples:
        >>> LayoutCorrectorPipeline.__call__  # doctest: +ELLIPSIS
        <function...
    """
    _ = num_elements
    generator = resolve_torch_generator(generator=generator, seed=seed)
    canonical = normalize_condition_type(condition_type)
    condition = None
    if canonical != "unconditional":
        missing_inputs = [
            name
            for name, value in (("bbox", bbox), ("labels", labels))
            if value is None
        ]
        if missing_inputs:
            raise ValueError(
                "bbox and labels are required "
                f"for condition_type={condition_type}; "
                f"missing {', '.join(missing_inputs)}"
            )

        processor_inputs = {
            "bbox": bbox,
            "labels": labels,
            "mask": mask,
            "box_format": box_format,
            "normalized": normalized,
            "canvas_size": canvas_size,
        }
        processed = self.processor(**processor_inputs)
        decoded_input = self.layout_dm.tokenizer.decode_layout(
            processed["input_ids"]
        )
        condition = build_condition(
            self.layout_dm.tokenizer,
            cond_type=canonical,
            bbox=decoded_input["bbox"],
            labels=decoded_input["labels"],
            mask=decoded_input["mask"],
        )
        batch_size = condition.input_ids.shape[0]

    corrector_cfg = LayoutCorrectorSamplingConfig(
        sampling=sampling,
        temperature=temperature,
        top_k=top_k,
        top_p=top_p,
        num_inference_steps=num_inference_steps,
        corrector_steps=corrector_steps or self.corrector.corrector_steps,
        corrector_t_list=tuple(
            self.corrector.corrector_t_list
            if corrector_t_list is None
            else corrector_t_list
        ),
        corrector_start=corrector_start,
        corrector_end=corrector_end,
        corrector_mask_mode=corrector_mask_mode
        or self.corrector.corrector_mask_mode,
        corrector_mask_threshold=corrector_mask_threshold
        if corrector_mask_threshold is not None
        else self.corrector.corrector_mask_threshold,
        corrector_temperature=corrector_temperature
        if corrector_temperature is not None
        else self.corrector.corrector_temperature,
        use_gumbel_noise=use_gumbel_noise
        if use_gumbel_noise is not None
        else self.corrector.use_gumbel_noise,
        gumbel_temperature=gumbel_temperature
        if gumbel_temperature is not None
        else self.corrector.gumbel_temperature,
        time_adaptive_temperature=time_adaptive_temperature
        if time_adaptive_temperature is not None
        else self.corrector.time_adaptive_temperature,
    )
    sampling_cfg = LayoutDMSamplingConfig(
        name=sampling,
        temperature=temperature,
        top_k=top_k,
        top_p=top_p,
        num_inference_steps=num_inference_steps,
    )
    self.layout_dm.scheduler.set_timesteps(num_inference_steps, device=self.device)
    sample = self.layout_dm.scheduler.initial_sample(
        batch_size,
        self.layout_dm.tokenizer.config.max_token_length,
        device=self.device,
        condition=condition,
    )
    trajectory = [] if return_intermediates else None
    scores = [] if return_intermediates else None
    previous_timestep = self.layout_dm.scheduler.config.num_timesteps
    for timestep in self.layout_dm.scheduler.timesteps:
        timestep_value = int(timestep.item())
        timestep_batch = torch.full(
            (batch_size,),
            timestep_value,
            device=self.device,
            dtype=torch.long,
        )
        if should_apply_corrector(timestep_value, corrector_cfg):
            sample, confidence = self._step_with_corrector(
                sample=sample,
                timestep_batch=timestep_batch,
                condition=condition,
                sampling=corrector_cfg,
                generator=generator,
            )
            if scores is not None and confidence is not None:
                scores.append(confidence.detach().cpu())
        else:
            input_ids = log_onehot_to_index(sample)
            logits = self.layout_dm.denoiser(
                input_ids=input_ids, timesteps=timestep_batch
            ).logits
            sample = self.layout_dm.scheduler.step(
                logits,
                timestep_batch,
                sample,
                previous_timestep=previous_timestep,
                sampling=sampling_cfg,
                condition=condition,
                generator=generator,
            ).prev_sample
        previous_timestep = timestep_value
        if trajectory is not None:
            trajectory.append(log_onehot_to_index(sample).detach().cpu())

    sequences = log_onehot_to_index(sample).detach().cpu()
    decoded = self.layout_dm.tokenizer.decode_layout(sequences)
    output = LayoutGenerationOutput(
        bbox=decoded["bbox"],
        labels=decoded["labels"],
        mask=decoded["mask"],
        id2label=dict(self.corrector.id2label),
        sequences=sequences,
        scores=torch.stack(scores) if scores else None,
        trajectory=trajectory,
        intermediates={"condition_type": canonical}
        if return_intermediates
        else None,
    )
    normalized_output_type = normalize_output_type(output_type)
    if normalized_output_type is OutputType.dict:
        return dict(output)
    if normalized_output_type is OutputType.dataclass:
        return output
    assert_never(normalized_output_type)

save_pretrained

save_pretrained(
    save_directory: str | Path,
    *,
    safe_serialization: bool = True,
) -> None

Save the nested LayoutDM pipeline and corrector model.

Parameters:

Name Type Description Default
save_directory str | Path

Destination directory.

required
safe_serialization bool

Whether to save weights as safetensors.

True

Returns:

Type Description
None

None.

Raises:

Type Description
OSError

If files cannot be written.

Examples:

>>> LayoutCorrectorPipeline.save_pretrained
<function...
Source code in models/layout-corrector/src/layout_corrector/pipeline_layout_corrector.py
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
def save_pretrained(
    self, save_directory: str | Path, *, safe_serialization: bool = True
) -> None:
    """Save the nested LayoutDM pipeline and corrector model.

    Args:
        save_directory: Destination directory.
        safe_serialization: Whether to save weights as safetensors.

    Returns:
        None.

    Raises:
        OSError: If files cannot be written.

    Examples:
        >>> LayoutCorrectorPipeline.save_pretrained  # doctest: +ELLIPSIS
        <function...
    """
    save_path = Path(save_directory)
    save_path.mkdir(parents=True, exist_ok=True)
    self.layout_dm.save_pretrained(
        save_path / "layout_dm", safe_serialization=safe_serialization
    )
    self.corrector.save_pretrained(
        save_path / "corrector", safe_serialization=safe_serialization
    )

from_pretrained classmethod

from_pretrained(
    pretrained_model_name_or_path: str | Path,
    *,
    processor: LayoutDMProcessor | None = None,
) -> "LayoutCorrectorPipeline"

Load a Layout-Corrector pipeline from a saved directory.

Parameters:

Name Type Description Default
pretrained_model_name_or_path str | Path

Directory containing layout_dm/ and corrector/ subdirectories.

required
processor LayoutDMProcessor | None

Optional processor override.

None

Returns:

Type Description
'LayoutCorrectorPipeline'

Loaded LayoutCorrectorPipeline.

Raises:

Type Description
OSError

If nested component files are missing.

Examples:

>>> LayoutCorrectorPipeline.from_pretrained
<bound method...
Source code in models/layout-corrector/src/layout_corrector/pipeline_layout_corrector.py
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
@classmethod
def from_pretrained(
    cls,
    pretrained_model_name_or_path: str | Path,
    *,
    processor: LayoutDMProcessor | None = None,
) -> "LayoutCorrectorPipeline":
    """Load a Layout-Corrector pipeline from a saved directory.

    Args:
        pretrained_model_name_or_path: Directory containing `layout_dm/` and
            `corrector/` subdirectories.
        processor: Optional processor override.

    Returns:
        Loaded `LayoutCorrectorPipeline`.

    Raises:
        OSError: If nested component files are missing.

    Examples:
        >>> LayoutCorrectorPipeline.from_pretrained  # doctest: +ELLIPSIS
        <bound method...
    """
    path = Path(pretrained_model_name_or_path)
    layout_dm = LayoutDMPipeline.from_pretrained(path / "layout_dm")
    corrector = LayoutCorrectorModel.from_pretrained(path / "corrector")
    return cls(layout_dm=layout_dm, corrector=corrector, processor=processor)

normalize_output_type

normalize_output_type(
    output_type: OutputType | str,
) -> OutputType

Normalize a public output format string to OutputType.

Source code in models/layout-corrector/src/layout_corrector/pipeline_layout_corrector.py
47
48
49
50
51
52
53
54
def normalize_output_type(output_type: OutputType | str) -> OutputType:
    """Normalize a public output format string to ``OutputType``."""
    if isinstance(output_type, OutputType):
        return output_type
    try:
        return OutputType(output_type)
    except ValueError as exc:
        raise ValueError(f"Unsupported output_type: {output_type}") from exc

sampling

Sampling helpers for Layout-Corrector guided generation.

CorrectorMaskMode

Bases: StrEnum

Supported token remasking modes for Layout-Corrector.

Source code in models/layout-corrector/src/layout_corrector/sampling.py
20
21
22
23
24
class CorrectorMaskMode(StrEnum):
    """Supported token remasking modes for Layout-Corrector."""

    thresh = auto()
    topk = auto()

LayoutCorrectorSamplingConfig dataclass

Sampling options for Layout-Corrector-guided diffusion.

Parameters:

Name Type Description Default
sampling SamplingMode | str

Base LayoutDM sampling strategy.

random
temperature float

Base sampling temperature.

1.0
top_k int

Top-k cutoff for top-k sampling.

5
top_p float

Nucleus cutoff for top-p sampling.

0.9
num_inference_steps int | None

Optional inference timestep count.

None
corrector_steps int

Number of correction passes per selected timestep.

1
corrector_t_list tuple[int, ...]

Explicit timesteps where the corrector is applied.

(10, 20, 30)
corrector_start int

Start timestep for range-based correction.

-1
corrector_end int

End timestep for range-based correction.

-1
corrector_mask_mode CorrectorMaskMode | str

Strategy for selecting tokens to remask.

thresh
corrector_mask_threshold float

Confidence threshold for threshold masking.

0.7
corrector_temperature float

Temperature used for confidence masking.

1.0
use_gumbel_noise bool

Whether to perturb confidence logits.

True
gumbel_temperature float

Temperature for confidence Gumbel noise.

1.0
time_adaptive_temperature bool

Whether to scale noise by timestep ratio.

False

Raises:

Type Description
ValueError

Construction does not raise directly.

Examples:

>>> LayoutCorrectorSamplingConfig(corrector_t_list=(10,)).corrector_t_list
(10,)
Source code in models/layout-corrector/src/layout_corrector/sampling.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
@dataclass
class LayoutCorrectorSamplingConfig:
    """Sampling options for Layout-Corrector-guided diffusion.

    Args:
        sampling: Base LayoutDM sampling strategy.
        temperature: Base sampling temperature.
        top_k: Top-k cutoff for top-k sampling.
        top_p: Nucleus cutoff for top-p sampling.
        num_inference_steps: Optional inference timestep count.
        corrector_steps: Number of correction passes per selected timestep.
        corrector_t_list: Explicit timesteps where the corrector is applied.
        corrector_start: Start timestep for range-based correction.
        corrector_end: End timestep for range-based correction.
        corrector_mask_mode: Strategy for selecting tokens to remask.
        corrector_mask_threshold: Confidence threshold for threshold masking.
        corrector_temperature: Temperature used for confidence masking.
        use_gumbel_noise: Whether to perturb confidence logits.
        gumbel_temperature: Temperature for confidence Gumbel noise.
        time_adaptive_temperature: Whether to scale noise by timestep ratio.

    Raises:
        ValueError: Construction does not raise directly.

    Examples:
        >>> LayoutCorrectorSamplingConfig(corrector_t_list=(10,)).corrector_t_list
        (10,)
    """

    sampling: SamplingMode | str = SamplingMode.random
    temperature: float = 1.0
    top_k: int = 5
    top_p: float = 0.9
    num_inference_steps: int | None = None
    corrector_steps: int = 1
    corrector_t_list: tuple[int, ...] = (10, 20, 30)
    corrector_start: int = -1
    corrector_end: int = -1
    corrector_mask_mode: CorrectorMaskMode | str = CorrectorMaskMode.thresh
    corrector_mask_threshold: float = 0.7
    corrector_temperature: float = 1.0
    use_gumbel_noise: bool = True
    gumbel_temperature: float = 1.0
    time_adaptive_temperature: bool = False

    def __post_init__(self) -> None:
        """Normalize public string modes to enum values."""
        self.sampling = normalize_sampling_mode(self.sampling)
        self.corrector_mask_mode = normalize_corrector_mask_mode(
            self.corrector_mask_mode
        )

__post_init__

__post_init__() -> None

Normalize public string modes to enum values.

Source code in models/layout-corrector/src/layout_corrector/sampling.py
86
87
88
89
90
91
def __post_init__(self) -> None:
    """Normalize public string modes to enum values."""
    self.sampling = normalize_sampling_mode(self.sampling)
    self.corrector_mask_mode = normalize_corrector_mask_mode(
        self.corrector_mask_mode
    )

normalize_corrector_mask_mode

normalize_corrector_mask_mode(
    corrector_mask_mode: CorrectorMaskMode | str,
) -> CorrectorMaskMode

Normalize a public remasking mode to CorrectorMaskMode.

Source code in models/layout-corrector/src/layout_corrector/sampling.py
27
28
29
30
31
32
33
34
35
36
37
38
def normalize_corrector_mask_mode(
    corrector_mask_mode: CorrectorMaskMode | str,
) -> CorrectorMaskMode:
    """Normalize a public remasking mode to ``CorrectorMaskMode``."""
    if isinstance(corrector_mask_mode, CorrectorMaskMode):
        return corrector_mask_mode
    try:
        return CorrectorMaskMode(corrector_mask_mode)
    except ValueError as exc:
        raise ValueError(
            f"Unsupported corrector mask mode: {corrector_mask_mode}"
        ) from exc

should_apply_corrector

should_apply_corrector(
    diffusion_index: int,
    config: LayoutCorrectorSamplingConfig,
) -> bool

Return whether the corrector should run at a diffusion timestep.

Parameters:

Name Type Description Default
diffusion_index int

Current diffusion timestep.

required
config LayoutCorrectorSamplingConfig

Corrector sampling options.

required

Returns:

Type Description
bool

True when the timestep matches the explicit list or configured range.

Raises:

Type Description
ValueError

This function does not raise.

Examples:

>>> should_apply_corrector(10, LayoutCorrectorSamplingConfig(corrector_t_list=(10,)))
True
Source code in models/layout-corrector/src/layout_corrector/sampling.py
 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
def should_apply_corrector(
    diffusion_index: int,
    config: LayoutCorrectorSamplingConfig,
) -> bool:
    """Return whether the corrector should run at a diffusion timestep.

    Args:
        diffusion_index: Current diffusion timestep.
        config: Corrector sampling options.

    Returns:
        `True` when the timestep matches the explicit list or configured range.

    Raises:
        ValueError: This function does not raise.

    Examples:
        >>> should_apply_corrector(10, LayoutCorrectorSamplingConfig(corrector_t_list=(10,)))
        True
    """
    if config.corrector_t_list:
        return diffusion_index in set(config.corrector_t_list)
    if config.corrector_start < 0 or config.corrector_end < 0:
        return False
    start, end = sorted((config.corrector_start, config.corrector_end))
    return start <= diffusion_index <= end

add_confidence_gumbel_noise

add_confidence_gumbel_noise(
    confidence_logits: Float[Tensor, "batch tokens"],
    *,
    timestep: Int[Tensor, "batch"],
    mask_ratio: float,
    temperature: float,
    time_adaptive_temperature: bool,
    generator: Generator | None = None,
) -> Float[torch.Tensor, "batch tokens"]

Add Gumbel noise to confidence logits.

Parameters:

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

Rank-2 confidence logits.

required
timestep Int[Tensor, 'batch']

Current timestep tensor.

required
mask_ratio float

Fraction of tokens still masked.

required
temperature float

Base noise temperature.

required
time_adaptive_temperature bool

Whether to scale temperature by mask_ratio.

required
generator Generator | None

Optional PyTorch generator for reproducible noise.

None

Returns:

Type Description
Float[Tensor, 'batch tokens']

Confidence logits after noise injection.

Raises:

Type Description
ValueError

This function does not raise directly.

Examples:

>>> import torch
>>> logits = torch.zeros(1, 2)
>>> add_confidence_gumbel_noise(
...     logits,
...     timestep=torch.tensor([1]),
...     mask_ratio=0.5,
...     temperature=1.0,
...     time_adaptive_temperature=False,
...     generator=torch.Generator().manual_seed(0),
... ).shape
torch.Size([1, 2])
Source code in models/layout-corrector/src/layout_corrector/sampling.py
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
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
def add_confidence_gumbel_noise(
    confidence_logits: Float[torch.Tensor, "batch tokens"],
    *,
    timestep: Int[torch.Tensor, "batch"],
    mask_ratio: float,
    temperature: float,
    time_adaptive_temperature: bool,
    generator: torch.Generator | None = None,
) -> Float[torch.Tensor, "batch tokens"]:
    """Add Gumbel noise to confidence logits.

    Args:
        confidence_logits: Rank-2 confidence logits.
        timestep: Current timestep tensor.
        mask_ratio: Fraction of tokens still masked.
        temperature: Base noise temperature.
        time_adaptive_temperature: Whether to scale temperature by `mask_ratio`.
        generator: Optional PyTorch generator for reproducible noise.

    Returns:
        Confidence logits after noise injection.

    Raises:
        ValueError: This function does not raise directly.

    Examples:
        >>> import torch
        >>> logits = torch.zeros(1, 2)
        >>> add_confidence_gumbel_noise(
        ...     logits,
        ...     timestep=torch.tensor([1]),
        ...     mask_ratio=0.5,
        ...     temperature=1.0,
        ...     time_adaptive_temperature=False,
        ...     generator=torch.Generator().manual_seed(0),
        ... ).shape
        torch.Size([1, 2])
    """
    scale = torch.full(
        (confidence_logits.shape[0], 1),
        float(temperature),
        device=confidence_logits.device,
        dtype=confidence_logits.dtype,
    )
    if time_adaptive_temperature:
        scale = scale * (1.0 - mask_ratio)
    return confidence_logits + scale * gumbel_noise_like(
        confidence_logits, generator=generator
    )

select_tokens_to_remask

select_tokens_to_remask(
    confidence_logits: Float[Tensor, "batch tokens"],
    *,
    mask_ratio: float,
    mode: CorrectorMaskMode | str,
    threshold: float,
    temperature: float = 1.0,
) -> Bool[torch.Tensor, "batch tokens"]

Select low-confidence tokens that should be masked again.

Parameters:

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

Rank-2 confidence logits shaped (batch, tokens).

required
mask_ratio float

Ratio used to choose top-k remasking count.

required
mode CorrectorMaskMode | str

Selection mode, either "thresh" or "topk".

required
threshold float

Sigmoid confidence threshold for "thresh".

required
temperature float

Temperature applied before thresholding.

1.0

Returns:

Type Description
Bool[Tensor, 'batch tokens']

Boolean mask with True where tokens should be remasked.

Raises:

Type Description
ValueError

If logits are not rank-2 or mode is unsupported.

Examples:

>>> import torch
>>> select_tokens_to_remask(torch.zeros(1, 2), mask_ratio=0.5, mode="topk", threshold=0.7).shape
torch.Size([1, 2])
Source code in models/layout-corrector/src/layout_corrector/sampling.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
210
211
212
213
214
215
216
217
def select_tokens_to_remask(
    confidence_logits: Float[torch.Tensor, "batch tokens"],
    *,
    mask_ratio: float,
    mode: CorrectorMaskMode | str,
    threshold: float,
    temperature: float = 1.0,
) -> Bool[torch.Tensor, "batch tokens"]:
    """Select low-confidence tokens that should be masked again.

    Args:
        confidence_logits: Rank-2 confidence logits shaped `(batch, tokens)`.
        mask_ratio: Ratio used to choose top-k remasking count.
        mode: Selection mode, either `"thresh"` or `"topk"`.
        threshold: Sigmoid confidence threshold for `"thresh"`.
        temperature: Temperature applied before thresholding.

    Returns:
        Boolean mask with `True` where tokens should be remasked.

    Raises:
        ValueError: If logits are not rank-2 or `mode` is unsupported.

    Examples:
        >>> import torch
        >>> select_tokens_to_remask(torch.zeros(1, 2), mask_ratio=0.5, mode="topk", threshold=0.7).shape
        torch.Size([1, 2])
    """
    if confidence_logits.ndim != 2:
        raise ValueError("confidence_logits must be rank-2")

    normalized_mode = normalize_corrector_mask_mode(mode)
    if normalized_mode is CorrectorMaskMode.thresh:
        confidence = torch.sigmoid(confidence_logits / temperature)
        return confidence < threshold
    if normalized_mode is CorrectorMaskMode.topk:
        num_token = confidence_logits.shape[1]
        k = torch.full(
            (confidence_logits.shape[0],),
            int(mask_ratio * num_token),
            device=confidence_logits.device,
            dtype=torch.long,
        )
        return batch_topk_mask(-confidence_logits, k)
    assert_never(normalized_mode)

training

Training entry points for Layout-Corrector.

LayoutCorrectorDataModule

Bases: LightningDataModule

Construct train, validation, and test loaders from processed LayoutDM data.

Source code in models/layout-corrector/src/layout_corrector/training/dataset.py
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
class LayoutCorrectorDataModule(LightningDataModule):
    """Construct train, validation, and test loaders from processed LayoutDM data."""

    def __init__(
        self,
        *,
        dataset_name: LayoutCorrectorTrainingDatasetName,
        config: LayoutDMConfig,
        processed_data_dir: str | Path,
        batch_size: int = 64,
        num_workers: int = 16,
        max_seq_length: int = 25,
        random_order: bool = True,
        pin_memory: bool = True,
    ) -> None:
        """Initialize a processed LayoutDM data-module configuration."""
        super().__init__()
        self.dataset_name = dataset_name
        self.config = config
        self.processed_data_dir = Path(processed_data_dir)
        self.batch_size = batch_size
        self.num_workers = num_workers
        self.max_seq_length = max_seq_length
        self.random_order = random_order
        self.pin_memory = pin_memory
        self._datasets: dict[
            LayoutCorrectorTrainingSplit,
            Dataset[dict[str, Shaped[torch.Tensor, "..."] | str]],
        ] = {}

    def setup(self, stage: str | None = None) -> None:
        """Open the requested processed splits."""
        splits: tuple[LayoutCorrectorTrainingSplit, ...]
        if stage == "fit":
            splits = ("train", "validation")
        elif stage == "test":
            splits = ("test",)
        else:
            splits = ("train", "validation", "test")

        for split in splits:
            self._datasets[split] = LayoutDMProcessedDataset(
                dataset_name=self.dataset_name,
                split=split,
                config=self.config,
                max_seq_length=self.max_seq_length,
                processed_data_dir=self.processed_data_dir,
                random_order=self.random_order,
            )

    def _loader(
        self,
        split: LayoutCorrectorTrainingSplit,
        *,
        shuffle: bool,
    ) -> DataLoader[dict[str, Shaped[torch.Tensor, "..."] | str]]:
        if split not in self._datasets:
            self.setup("fit" if split in {"train", "validation"} else "test")

        return DataLoader(
            self._datasets[split],
            batch_size=self.batch_size,
            shuffle=shuffle,
            num_workers=self.num_workers,
            pin_memory=self.pin_memory,
            worker_init_fn=_preserve_torch_worker_seed,
        )

    def train_dataloader(
        self,
    ) -> DataLoader[dict[str, Shaped[torch.Tensor, "..."] | str]]:
        """Return the LayoutDM-shaped 16-worker training stream."""
        return self._loader("train", shuffle=True)

    def val_dataloader(
        self,
    ) -> DataLoader[dict[str, Shaped[torch.Tensor, "..."] | str]]:
        """Return the validation stream."""
        return self._loader("validation", shuffle=False)

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

__init__

__init__(
    *,
    dataset_name: LayoutCorrectorTrainingDatasetName,
    config: LayoutDMConfig,
    processed_data_dir: str | Path,
    batch_size: int = 64,
    num_workers: int = 16,
    max_seq_length: int = 25,
    random_order: bool = True,
    pin_memory: bool = True,
) -> None

Initialize a processed LayoutDM data-module configuration.

Source code in models/layout-corrector/src/layout_corrector/training/dataset.py
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
def __init__(
    self,
    *,
    dataset_name: LayoutCorrectorTrainingDatasetName,
    config: LayoutDMConfig,
    processed_data_dir: str | Path,
    batch_size: int = 64,
    num_workers: int = 16,
    max_seq_length: int = 25,
    random_order: bool = True,
    pin_memory: bool = True,
) -> None:
    """Initialize a processed LayoutDM data-module configuration."""
    super().__init__()
    self.dataset_name = dataset_name
    self.config = config
    self.processed_data_dir = Path(processed_data_dir)
    self.batch_size = batch_size
    self.num_workers = num_workers
    self.max_seq_length = max_seq_length
    self.random_order = random_order
    self.pin_memory = pin_memory
    self._datasets: dict[
        LayoutCorrectorTrainingSplit,
        Dataset[dict[str, Shaped[torch.Tensor, "..."] | str]],
    ] = {}

setup

setup(stage: str | None = None) -> None

Open the requested processed splits.

Source code in models/layout-corrector/src/layout_corrector/training/dataset.py
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
def setup(self, stage: str | None = None) -> None:
    """Open the requested processed splits."""
    splits: tuple[LayoutCorrectorTrainingSplit, ...]
    if stage == "fit":
        splits = ("train", "validation")
    elif stage == "test":
        splits = ("test",)
    else:
        splits = ("train", "validation", "test")

    for split in splits:
        self._datasets[split] = LayoutDMProcessedDataset(
            dataset_name=self.dataset_name,
            split=split,
            config=self.config,
            max_seq_length=self.max_seq_length,
            processed_data_dir=self.processed_data_dir,
            random_order=self.random_order,
        )

train_dataloader

train_dataloader() -> DataLoader[
    dict[str, Shaped[torch.Tensor, "..."] | str]
]

Return the LayoutDM-shaped 16-worker training stream.

Source code in models/layout-corrector/src/layout_corrector/training/dataset.py
93
94
95
96
97
def train_dataloader(
    self,
) -> DataLoader[dict[str, Shaped[torch.Tensor, "..."] | str]]:
    """Return the LayoutDM-shaped 16-worker training stream."""
    return self._loader("train", shuffle=True)

val_dataloader

val_dataloader() -> DataLoader[
    dict[str, Shaped[torch.Tensor, "..."] | str]
]

Return the validation stream.

Source code in models/layout-corrector/src/layout_corrector/training/dataset.py
 99
100
101
102
103
def val_dataloader(
    self,
) -> DataLoader[dict[str, Shaped[torch.Tensor, "..."] | str]]:
    """Return the validation stream."""
    return self._loader("validation", shuffle=False)

test_dataloader

test_dataloader() -> DataLoader[
    dict[str, Shaped[torch.Tensor, "..."] | str]
]

Return the test stream.

Source code in models/layout-corrector/src/layout_corrector/training/dataset.py
105
106
107
108
109
def test_dataloader(
    self,
) -> DataLoader[dict[str, Shaped[torch.Tensor, "..."] | str]]:
    """Return the test stream."""
    return self._loader("test", shuffle=False)

FrozenLayoutDMReference dataclass

Package-side frozen LayoutDM model, tokenizer, and schedules.

Source code in models/layout-corrector/src/layout_corrector/training/reference.py
 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
216
217
218
219
220
221
222
223
224
@dataclass
class FrozenLayoutDMReference:
    """Package-side frozen LayoutDM model, tokenizer, and schedules."""

    dataset_name: str
    checkpoint_path: Path
    config: LayoutDMConfig
    tokenizer: LayoutDMTokenizer
    model: LayoutDMDenoiser
    scheduler: LayoutDMScheduler
    lt_history: Float[torch.Tensor, "timesteps"]
    lt_count: Float[torch.Tensor, "timesteps"]
    checkpoint_sha256: str

    @classmethod
    def from_checkpoint(
        cls,
        *,
        dataset_name: str,
        checkpoint_path: str | Path,
        cluster_centers_path: str | Path,
        initialization_device: torch.device | str = "cpu",
    ) -> "FrozenLayoutDMReference":
        """Load the selected seed-0 checkpoint and preserve its state tensors."""
        path = Path(checkpoint_path)
        raw = torch.load(path, map_location="cpu", weights_only=False)
        if not isinstance(raw, dict):
            raise TypeError("LayoutDM checkpoint must be a state dictionary")

        expected = FROZEN_LAYOUT_DM_SHA256[dataset_name]
        observed = hashlib.sha256(path.read_bytes()).hexdigest()
        if observed != expected:
            raise ValueError(
                f"Unexpected frozen LayoutDM SHA-256 for {dataset_name}: {observed}"
            )

        config = LayoutDMConfig(
            dataset_name=dataset_name,
            max_seq_length=25,
            num_bin_bboxes=32,
            bbox_quantization="kmeans",
            cluster_centers_path=str(cluster_centers_path),
            hidden_size=464,
            num_attention_heads=8,
            num_hidden_layers=4,
            intermediate_size=1856,
            dropout=0.0,
            timestep_type="adalayernorm",
            num_timesteps=100,
            q_type="constrained",
        )
        tokenizer = LayoutDMTokenizer(config)
        with skip_layoutdm_initialization():
            with torch.device("cpu"):
                model = LayoutDMDenoiser(
                    vocab_size=config.vocab_size,
                    max_token_length=config.max_token_length,
                    hidden_size=config.hidden_size,
                    num_attention_heads=config.num_attention_heads,
                    num_hidden_layers=config.num_hidden_layers,
                    intermediate_size=config.intermediate_size,
                    dropout=config.dropout,
                    timestep_type=config.timestep_type,
                )

        target_device = torch.device(initialization_device)
        torch.nn.Module.to(model, target_device)
        model.initialize_weights()
        model.load_state_dict(split_original_state_dict(raw), strict=True)
        torch.nn.Module.to(model, "cpu")
        for parameter in model.parameters():
            parameter.requires_grad_(False)

        model.eval()

        scheduler = LayoutDMScheduler(
            num_timesteps=config.num_timesteps,
            q_type="constrained",
            mask_token_id=config.mask_token_id,
            pad_token_id=config.pad_token_id,
            var_order=tuple(config.var_order.split("-")),
            per_var_full_ids=tokenizer.full_id_maps(),
            att_1=config.att_1,
            att_T=config.att_T,
            ctt_1=config.ctt_1,
            ctt_T=config.ctt_T,
            vocab_size=config.vocab_size,
        )
        for key in tokenizer.var_names:
            scheduler.schedules[key] = tuple(
                raw[f"model.module.{key}_{suffix}"].clone()
                for suffix in (
                    "log_at",
                    "log_bt",
                    "log_ct",
                    "log_cumprod_at",
                    "log_cumprod_bt",
                    "log_cumprod_ct",
                )
            )

        return cls(
            dataset_name=dataset_name,
            checkpoint_path=path,
            config=config,
            tokenizer=tokenizer,
            model=model,
            scheduler=scheduler,
            lt_history=raw["model.module.Lt_history"].clone(),
            lt_count=raw["model.module.Lt_count"].clone(),
            checkpoint_sha256=observed,
        )

    def to(self, device: torch.device | str) -> "FrozenLayoutDMReference":
        """Move the reference model and its comparison buffers."""
        target_device = torch.device(device)
        torch.nn.Module.to(self.model, target_device)
        self.lt_history = self.lt_history.to(target_device)
        self.lt_count = self.lt_count.to(target_device)
        return self

    def sample_time(
        self, batch_size: int, *, device: torch.device
    ) -> tuple[Int[torch.Tensor, "batch"], Float[torch.Tensor, "batch"]]:
        """Use the released importance sampler and its frozen loss history."""
        if not bool((self.lt_count > 10).all()):
            t = randint(0, 100, size=(batch_size,), device=device).long()
            return t, torch.full_like(t, 1.0 / 100, dtype=torch.float32)

        history = torch.sqrt(self.lt_history + 1.0e-10) + 0.0001
        history[0] = history[1]
        probabilities = history / history.sum()
        t = multinomial(probabilities, batch_size, replacement=True, device=device)
        return t, probabilities.gather(dim=0, index=t)

    def corrupt(
        self,
        input_ids: Int[torch.Tensor, "batch tokens"],
        timesteps: Int[torch.Tensor, "batch"],
    ) -> tuple[
        Int[torch.Tensor, "batch tokens"], Float[torch.Tensor, "batch vocab tokens"]
    ]:
        """Apply constrained LayoutDM corruption with reference random sampling."""
        batch_size, token_length = input_ids.shape
        step = len(self.tokenizer.var_names)
        reshaped = input_ids.reshape(batch_size, token_length // step, step)
        full_logs: list[Float[torch.Tensor, "batch vocab tokens"]] = []
        full_ids: list[Int[torch.Tensor, "batch tokens"]] = []
        for index, key in enumerate(self.tokenizer.var_names):
            partial = self.tokenizer.full_to_partial_ids(reshaped[..., index], key)
            log_start = index_to_log_onehot(
                partial, len(self.tokenizer.full_id_maps()[key])
            )
            log_prob = self.scheduler._q_pred(log_start, timesteps, key)
            uniform = rand(*log_prob.shape, device=log_prob.device)
            gumbel = -torch.log(-torch.log(uniform + 1.0e-30) + 1.0e-30)
            sampled = (gumbel + log_prob).argmax(dim=1)
            log_sample = index_to_log_onehot(
                sampled, len(self.tokenizer.full_id_maps()[key])
            )
            full_logs.append(self.tokenizer.partial_to_full_log_probs(log_sample, key))
            full_ids.append(self.tokenizer.partial_to_full_ids(sampled, key))

        log_xt = torch.stack(full_logs, dim=-1).reshape(
            batch_size, self.config.vocab_size, token_length
        )
        xt = torch.stack(full_ids, dim=-1).reshape(batch_size, token_length)
        return xt, log_xt

    def prediction_log_probs(
        self,
        log_xt: Float[torch.Tensor, "batch vocab tokens"],
        timesteps: Int[torch.Tensor, "batch"],
    ) -> Float[torch.Tensor, "batch vocab tokens"]:
        """Predict clean-token probabilities with the frozen denoiser."""
        logits = self.model(input_ids=log_xt.argmax(dim=1), timesteps=timesteps).logits
        return self.scheduler.predict_start(logits)

    def reconstruct_previous(
        self,
        log_x0: Float[torch.Tensor, "batch vocab tokens"],
        log_xt: Float[torch.Tensor, "batch vocab tokens"],
        timesteps: Int[torch.Tensor, "batch"],
    ) -> Int[torch.Tensor, "batch tokens"]:
        """Sample the reference x_t-1 reconstruction, excluding MASK."""
        log_previous = self.scheduler.q_posterior(log_x0, log_xt, timesteps)
        log_previous[:, self.config.mask_token_id, :] = -70.0
        probabilities = log_previous.softmax(dim=1).movedim(1, -1)
        sampled = multinomial(
            probabilities.reshape(-1, probabilities.shape[-1]),
            1,
            replacement=False,
            device=log_previous.device,
        ).reshape(log_previous.shape[0], -1)
        return sampled

from_checkpoint classmethod

from_checkpoint(
    *,
    dataset_name: str,
    checkpoint_path: str | Path,
    cluster_centers_path: str | Path,
    initialization_device: device | str = "cpu",
) -> "FrozenLayoutDMReference"

Load the selected seed-0 checkpoint and preserve its state tensors.

Source code in models/layout-corrector/src/layout_corrector/training/reference.py
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
@classmethod
def from_checkpoint(
    cls,
    *,
    dataset_name: str,
    checkpoint_path: str | Path,
    cluster_centers_path: str | Path,
    initialization_device: torch.device | str = "cpu",
) -> "FrozenLayoutDMReference":
    """Load the selected seed-0 checkpoint and preserve its state tensors."""
    path = Path(checkpoint_path)
    raw = torch.load(path, map_location="cpu", weights_only=False)
    if not isinstance(raw, dict):
        raise TypeError("LayoutDM checkpoint must be a state dictionary")

    expected = FROZEN_LAYOUT_DM_SHA256[dataset_name]
    observed = hashlib.sha256(path.read_bytes()).hexdigest()
    if observed != expected:
        raise ValueError(
            f"Unexpected frozen LayoutDM SHA-256 for {dataset_name}: {observed}"
        )

    config = LayoutDMConfig(
        dataset_name=dataset_name,
        max_seq_length=25,
        num_bin_bboxes=32,
        bbox_quantization="kmeans",
        cluster_centers_path=str(cluster_centers_path),
        hidden_size=464,
        num_attention_heads=8,
        num_hidden_layers=4,
        intermediate_size=1856,
        dropout=0.0,
        timestep_type="adalayernorm",
        num_timesteps=100,
        q_type="constrained",
    )
    tokenizer = LayoutDMTokenizer(config)
    with skip_layoutdm_initialization():
        with torch.device("cpu"):
            model = LayoutDMDenoiser(
                vocab_size=config.vocab_size,
                max_token_length=config.max_token_length,
                hidden_size=config.hidden_size,
                num_attention_heads=config.num_attention_heads,
                num_hidden_layers=config.num_hidden_layers,
                intermediate_size=config.intermediate_size,
                dropout=config.dropout,
                timestep_type=config.timestep_type,
            )

    target_device = torch.device(initialization_device)
    torch.nn.Module.to(model, target_device)
    model.initialize_weights()
    model.load_state_dict(split_original_state_dict(raw), strict=True)
    torch.nn.Module.to(model, "cpu")
    for parameter in model.parameters():
        parameter.requires_grad_(False)

    model.eval()

    scheduler = LayoutDMScheduler(
        num_timesteps=config.num_timesteps,
        q_type="constrained",
        mask_token_id=config.mask_token_id,
        pad_token_id=config.pad_token_id,
        var_order=tuple(config.var_order.split("-")),
        per_var_full_ids=tokenizer.full_id_maps(),
        att_1=config.att_1,
        att_T=config.att_T,
        ctt_1=config.ctt_1,
        ctt_T=config.ctt_T,
        vocab_size=config.vocab_size,
    )
    for key in tokenizer.var_names:
        scheduler.schedules[key] = tuple(
            raw[f"model.module.{key}_{suffix}"].clone()
            for suffix in (
                "log_at",
                "log_bt",
                "log_ct",
                "log_cumprod_at",
                "log_cumprod_bt",
                "log_cumprod_ct",
            )
        )

    return cls(
        dataset_name=dataset_name,
        checkpoint_path=path,
        config=config,
        tokenizer=tokenizer,
        model=model,
        scheduler=scheduler,
        lt_history=raw["model.module.Lt_history"].clone(),
        lt_count=raw["model.module.Lt_count"].clone(),
        checkpoint_sha256=observed,
    )

to

to(device: device | str) -> 'FrozenLayoutDMReference'

Move the reference model and its comparison buffers.

Source code in models/layout-corrector/src/layout_corrector/training/reference.py
143
144
145
146
147
148
149
def to(self, device: torch.device | str) -> "FrozenLayoutDMReference":
    """Move the reference model and its comparison buffers."""
    target_device = torch.device(device)
    torch.nn.Module.to(self.model, target_device)
    self.lt_history = self.lt_history.to(target_device)
    self.lt_count = self.lt_count.to(target_device)
    return self

sample_time

sample_time(
    batch_size: int, *, device: device
) -> tuple[
    Int[torch.Tensor, "batch"], Float[torch.Tensor, "batch"]
]

Use the released importance sampler and its frozen loss history.

Source code in models/layout-corrector/src/layout_corrector/training/reference.py
151
152
153
154
155
156
157
158
159
160
161
162
163
def sample_time(
    self, batch_size: int, *, device: torch.device
) -> tuple[Int[torch.Tensor, "batch"], Float[torch.Tensor, "batch"]]:
    """Use the released importance sampler and its frozen loss history."""
    if not bool((self.lt_count > 10).all()):
        t = randint(0, 100, size=(batch_size,), device=device).long()
        return t, torch.full_like(t, 1.0 / 100, dtype=torch.float32)

    history = torch.sqrt(self.lt_history + 1.0e-10) + 0.0001
    history[0] = history[1]
    probabilities = history / history.sum()
    t = multinomial(probabilities, batch_size, replacement=True, device=device)
    return t, probabilities.gather(dim=0, index=t)

corrupt

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

Apply constrained LayoutDM corruption with reference random sampling.

Source code in models/layout-corrector/src/layout_corrector/training/reference.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
def corrupt(
    self,
    input_ids: Int[torch.Tensor, "batch tokens"],
    timesteps: Int[torch.Tensor, "batch"],
) -> tuple[
    Int[torch.Tensor, "batch tokens"], Float[torch.Tensor, "batch vocab tokens"]
]:
    """Apply constrained LayoutDM corruption with reference random sampling."""
    batch_size, token_length = input_ids.shape
    step = len(self.tokenizer.var_names)
    reshaped = input_ids.reshape(batch_size, token_length // step, step)
    full_logs: list[Float[torch.Tensor, "batch vocab tokens"]] = []
    full_ids: list[Int[torch.Tensor, "batch tokens"]] = []
    for index, key in enumerate(self.tokenizer.var_names):
        partial = self.tokenizer.full_to_partial_ids(reshaped[..., index], key)
        log_start = index_to_log_onehot(
            partial, len(self.tokenizer.full_id_maps()[key])
        )
        log_prob = self.scheduler._q_pred(log_start, timesteps, key)
        uniform = rand(*log_prob.shape, device=log_prob.device)
        gumbel = -torch.log(-torch.log(uniform + 1.0e-30) + 1.0e-30)
        sampled = (gumbel + log_prob).argmax(dim=1)
        log_sample = index_to_log_onehot(
            sampled, len(self.tokenizer.full_id_maps()[key])
        )
        full_logs.append(self.tokenizer.partial_to_full_log_probs(log_sample, key))
        full_ids.append(self.tokenizer.partial_to_full_ids(sampled, key))

    log_xt = torch.stack(full_logs, dim=-1).reshape(
        batch_size, self.config.vocab_size, token_length
    )
    xt = torch.stack(full_ids, dim=-1).reshape(batch_size, token_length)
    return xt, log_xt

prediction_log_probs

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

Predict clean-token probabilities with the frozen denoiser.

Source code in models/layout-corrector/src/layout_corrector/training/reference.py
199
200
201
202
203
204
205
206
def prediction_log_probs(
    self,
    log_xt: Float[torch.Tensor, "batch vocab tokens"],
    timesteps: Int[torch.Tensor, "batch"],
) -> Float[torch.Tensor, "batch vocab tokens"]:
    """Predict clean-token probabilities with the frozen denoiser."""
    logits = self.model(input_ids=log_xt.argmax(dim=1), timesteps=timesteps).logits
    return self.scheduler.predict_start(logits)

reconstruct_previous

reconstruct_previous(
    log_x0: Float[Tensor, "batch vocab tokens"],
    log_xt: Float[Tensor, "batch vocab tokens"],
    timesteps: Int[Tensor, "batch"],
) -> Int[torch.Tensor, "batch tokens"]

Sample the reference x_t-1 reconstruction, excluding MASK.

Source code in models/layout-corrector/src/layout_corrector/training/reference.py
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
def reconstruct_previous(
    self,
    log_x0: Float[torch.Tensor, "batch vocab tokens"],
    log_xt: Float[torch.Tensor, "batch vocab tokens"],
    timesteps: Int[torch.Tensor, "batch"],
) -> Int[torch.Tensor, "batch tokens"]:
    """Sample the reference x_t-1 reconstruction, excluding MASK."""
    log_previous = self.scheduler.q_posterior(log_x0, log_xt, timesteps)
    log_previous[:, self.config.mask_token_id, :] = -70.0
    probabilities = log_previous.softmax(dim=1).movedim(1, -1)
    sampled = multinomial(
        probabilities.reshape(-1, probabilities.shape[-1]),
        1,
        replacement=False,
        device=log_previous.device,
    ).reshape(log_previous.shape[0], -1)
    return sampled

config

Configuration types for Layout-Corrector training.

dataset

Processed LayoutDM streams used by Layout-Corrector training.

LayoutCorrectorDataModule

Bases: LightningDataModule

Construct train, validation, and test loaders from processed LayoutDM data.

Source code in models/layout-corrector/src/layout_corrector/training/dataset.py
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
class LayoutCorrectorDataModule(LightningDataModule):
    """Construct train, validation, and test loaders from processed LayoutDM data."""

    def __init__(
        self,
        *,
        dataset_name: LayoutCorrectorTrainingDatasetName,
        config: LayoutDMConfig,
        processed_data_dir: str | Path,
        batch_size: int = 64,
        num_workers: int = 16,
        max_seq_length: int = 25,
        random_order: bool = True,
        pin_memory: bool = True,
    ) -> None:
        """Initialize a processed LayoutDM data-module configuration."""
        super().__init__()
        self.dataset_name = dataset_name
        self.config = config
        self.processed_data_dir = Path(processed_data_dir)
        self.batch_size = batch_size
        self.num_workers = num_workers
        self.max_seq_length = max_seq_length
        self.random_order = random_order
        self.pin_memory = pin_memory
        self._datasets: dict[
            LayoutCorrectorTrainingSplit,
            Dataset[dict[str, Shaped[torch.Tensor, "..."] | str]],
        ] = {}

    def setup(self, stage: str | None = None) -> None:
        """Open the requested processed splits."""
        splits: tuple[LayoutCorrectorTrainingSplit, ...]
        if stage == "fit":
            splits = ("train", "validation")
        elif stage == "test":
            splits = ("test",)
        else:
            splits = ("train", "validation", "test")

        for split in splits:
            self._datasets[split] = LayoutDMProcessedDataset(
                dataset_name=self.dataset_name,
                split=split,
                config=self.config,
                max_seq_length=self.max_seq_length,
                processed_data_dir=self.processed_data_dir,
                random_order=self.random_order,
            )

    def _loader(
        self,
        split: LayoutCorrectorTrainingSplit,
        *,
        shuffle: bool,
    ) -> DataLoader[dict[str, Shaped[torch.Tensor, "..."] | str]]:
        if split not in self._datasets:
            self.setup("fit" if split in {"train", "validation"} else "test")

        return DataLoader(
            self._datasets[split],
            batch_size=self.batch_size,
            shuffle=shuffle,
            num_workers=self.num_workers,
            pin_memory=self.pin_memory,
            worker_init_fn=_preserve_torch_worker_seed,
        )

    def train_dataloader(
        self,
    ) -> DataLoader[dict[str, Shaped[torch.Tensor, "..."] | str]]:
        """Return the LayoutDM-shaped 16-worker training stream."""
        return self._loader("train", shuffle=True)

    def val_dataloader(
        self,
    ) -> DataLoader[dict[str, Shaped[torch.Tensor, "..."] | str]]:
        """Return the validation stream."""
        return self._loader("validation", shuffle=False)

    def test_dataloader(
        self,
    ) -> DataLoader[dict[str, Shaped[torch.Tensor, "..."] | str]]:
        """Return the test stream."""
        return self._loader("test", shuffle=False)
__init__
__init__(
    *,
    dataset_name: LayoutCorrectorTrainingDatasetName,
    config: LayoutDMConfig,
    processed_data_dir: str | Path,
    batch_size: int = 64,
    num_workers: int = 16,
    max_seq_length: int = 25,
    random_order: bool = True,
    pin_memory: bool = True,
) -> None

Initialize a processed LayoutDM data-module configuration.

Source code in models/layout-corrector/src/layout_corrector/training/dataset.py
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
def __init__(
    self,
    *,
    dataset_name: LayoutCorrectorTrainingDatasetName,
    config: LayoutDMConfig,
    processed_data_dir: str | Path,
    batch_size: int = 64,
    num_workers: int = 16,
    max_seq_length: int = 25,
    random_order: bool = True,
    pin_memory: bool = True,
) -> None:
    """Initialize a processed LayoutDM data-module configuration."""
    super().__init__()
    self.dataset_name = dataset_name
    self.config = config
    self.processed_data_dir = Path(processed_data_dir)
    self.batch_size = batch_size
    self.num_workers = num_workers
    self.max_seq_length = max_seq_length
    self.random_order = random_order
    self.pin_memory = pin_memory
    self._datasets: dict[
        LayoutCorrectorTrainingSplit,
        Dataset[dict[str, Shaped[torch.Tensor, "..."] | str]],
    ] = {}
setup
setup(stage: str | None = None) -> None

Open the requested processed splits.

Source code in models/layout-corrector/src/layout_corrector/training/dataset.py
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
def setup(self, stage: str | None = None) -> None:
    """Open the requested processed splits."""
    splits: tuple[LayoutCorrectorTrainingSplit, ...]
    if stage == "fit":
        splits = ("train", "validation")
    elif stage == "test":
        splits = ("test",)
    else:
        splits = ("train", "validation", "test")

    for split in splits:
        self._datasets[split] = LayoutDMProcessedDataset(
            dataset_name=self.dataset_name,
            split=split,
            config=self.config,
            max_seq_length=self.max_seq_length,
            processed_data_dir=self.processed_data_dir,
            random_order=self.random_order,
        )
train_dataloader
train_dataloader() -> DataLoader[
    dict[str, Shaped[torch.Tensor, "..."] | str]
]

Return the LayoutDM-shaped 16-worker training stream.

Source code in models/layout-corrector/src/layout_corrector/training/dataset.py
93
94
95
96
97
def train_dataloader(
    self,
) -> DataLoader[dict[str, Shaped[torch.Tensor, "..."] | str]]:
    """Return the LayoutDM-shaped 16-worker training stream."""
    return self._loader("train", shuffle=True)
val_dataloader
val_dataloader() -> DataLoader[
    dict[str, Shaped[torch.Tensor, "..."] | str]
]

Return the validation stream.

Source code in models/layout-corrector/src/layout_corrector/training/dataset.py
 99
100
101
102
103
def val_dataloader(
    self,
) -> DataLoader[dict[str, Shaped[torch.Tensor, "..."] | str]]:
    """Return the validation stream."""
    return self._loader("validation", shuffle=False)
test_dataloader
test_dataloader() -> DataLoader[
    dict[str, Shaped[torch.Tensor, "..."] | str]
]

Return the test stream.

Source code in models/layout-corrector/src/layout_corrector/training/dataset.py
105
106
107
108
109
def test_dataloader(
    self,
) -> DataLoader[dict[str, Shaped[torch.Tensor, "..."] | str]]:
    """Return the test stream."""
    return self._loader("test", shuffle=False)

first_sample_ids

first_sample_ids(
    loader: DataLoader[
        dict[str, Shaped[Tensor, "..."] | str]
    ],
    count: int = 8,
) -> list[str]

Return the first sample identifiers without consuming a second stream.

Source code in models/layout-corrector/src/layout_corrector/training/dataset.py
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
def first_sample_ids(
    loader: DataLoader[dict[str, Shaped[torch.Tensor, "..."] | str]], count: int = 8
) -> list[str]:
    """Return the first sample identifiers without consuming a second stream."""
    ids: list[str] = []
    for batch in loader:
        sample_ids = batch.get("id")
        if isinstance(sample_ids, list):
            ids.extend(str(value) for value in sample_ids)
        elif isinstance(sample_ids, tuple):
            ids.extend(str(value) for value in sample_ids)
        elif sample_ids is not None:
            ids.append(str(sample_ids))

        if len(ids) >= count:
            break

    return ids[:count]

lightning_module

Package-local Layout-Corrector training module.

LayoutCorrectorTrainingModule

Bases: LightningModule

Reproduce the original corrector loop with a frozen LayoutDM model.

Source code in models/layout-corrector/src/layout_corrector/training/lightning_module.py
 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
class LayoutCorrectorTrainingModule(LightningModule):
    """Reproduce the original corrector loop with a frozen LayoutDM model."""

    def __init__(
        self,
        *,
        config: LayoutCorrectorConfig,
        layout_dm_checkpoint_path: str | Path,
        cluster_centers_path: str | Path,
        learning_rate: float = 5.0e-4,
        weight_decay: float = 0.1,
        betas: tuple[float, float] = (0.9, 0.98),
        gradient_clip_norm: float = 1.0,
        scheduler: LayoutCorrectorTrainingScheduler | None = "reduce_on_plateau",
        scheduler_factor: float = 0.5,
        scheduler_patience: int = 2,
        scheduler_threshold: float = 1.0e-2,
    ) -> None:
        """Initialize the corrector and its immutable LayoutDM reference."""
        super().__init__()
        self.config = config
        self.pre_model_rng_digest = _rng_digest()
        initialization_device = _initialization_device()
        reference = FrozenLayoutDMReference.from_checkpoint(
            dataset_name=config.dataset_name,
            checkpoint_path=layout_dm_checkpoint_path,
            cluster_centers_path=cluster_centers_path,
            initialization_device=initialization_device,
        )
        self.layout_dm_config: LayoutDMConfig = reference.config
        self.layout_dm_checkpoint_sha256 = reference.checkpoint_sha256
        self.layout_dm_checkpoint_path = str(reference.checkpoint_path)
        self.layout_dm_tokenizer = reference.tokenizer
        object.__setattr__(self, "_reference", reference)
        object.__setattr__(self, "reference_model", reference.model)
        self.register_buffer("layout_dm_lt_history", reference.lt_history)
        self.register_buffer("layout_dm_lt_count", reference.lt_count)

        with torch.device("cpu"):
            self.model = LayoutCorrectorModel(**dict(config.config))

        self.model_construction_device = str(next(self.model.parameters()).device)
        torch.nn.Module.to(self.model, initialization_device)
        self.model.initialize_weights()
        self.initialization_device = str(initialization_device)

        self.learning_rate = learning_rate
        self.weight_decay = weight_decay
        self.betas = betas
        self.gradient_clip_norm = gradient_clip_norm
        self.scheduler = scheduler
        self.scheduler_factor = scheduler_factor
        self.scheduler_patience = scheduler_patience
        self.scheduler_threshold = scheduler_threshold
        self.latest_step_trace: dict[str, Shaped[torch.Tensor, "..."]] = {}
        self.latest_gradient_norm: Shaped[torch.Tensor, ""] | None = None
        self._validation_losses: list[Shaped[torch.Tensor, ""]] = []
        self.latest_validation_loss: Shaped[torch.Tensor, ""] | None = None

    def _ensure_reference_device(self, device: torch.device) -> None:
        reference_model = self._reference_model_value()
        if next(reference_model.parameters()).device != device:
            reference_model.to(device)

        history, count = self._layout_dm_buffers()
        history.data = history.to(device)
        count.data = count.to(device)

    def on_train_epoch_start(self) -> None:
        """Keep the frozen LayoutDM reference in evaluation mode."""
        self._reference_model_value().eval()

    def _reference_value(self) -> FrozenLayoutDMReference:
        return cast(
            FrozenLayoutDMReference,
            object.__getattribute__(self, "_reference"),
        )

    def _reference_model_value(self) -> nn.Module:
        return cast(
            nn.Module,
            object.__getattribute__(self, "reference_model"),
        )

    def _layout_dm_buffers(
        self,
    ) -> tuple[Shaped[torch.Tensor, "..."], Shaped[torch.Tensor, "..."]]:
        history = self._buffers.get("layout_dm_lt_history")
        count = self._buffers.get("layout_dm_lt_count")
        if not isinstance(history, torch.Tensor) or not isinstance(count, torch.Tensor):
            raise RuntimeError("LayoutDM sampler buffers were not registered")

        return history, count

    def optim_groups(self) -> list[dict[str, Iterable[nn.Parameter] | float]]:
        """Match the reference Linear/LayerNorm/Embedding decay partition."""
        decay: set[str] = set()
        no_decay: set[str] = set()
        whitelist = (nn.Linear, nn.MultiheadAttention)
        blacklist = (nn.LayerNorm, nn.Embedding)
        for module_name, module in self.model.model.named_modules():
            for parameter_name, _ in module.named_parameters(recurse=False):
                name = (
                    f"{module_name}.{parameter_name}" if module_name else parameter_name
                )
                if parameter_name.endswith("bias"):
                    no_decay.add(name)
                elif parameter_name.endswith("weight") and isinstance(
                    module, whitelist
                ):
                    decay.add(name)
                elif parameter_name.endswith("weight") and isinstance(
                    module, blacklist
                ):
                    no_decay.add(name)
                else:
                    no_decay.add(name)

        parameters = dict(self.model.model.named_parameters())
        overlap = decay & no_decay
        if overlap:
            raise RuntimeError(
                f"Parameters assigned to both optimizer groups: {overlap}"
            )

        missing = set(parameters) - (decay | no_decay)
        if missing:
            raise RuntimeError(f"Parameters missing from optimizer groups: {missing}")

        return [
            {
                "params": [parameters[name] for name in sorted(decay)],
                "weight_decay": self.weight_decay,
            },
            {
                "params": [parameters[name] for name in sorted(no_decay)],
                "weight_decay": 0.0,
            },
        ]

    def configure_optimizers(self) -> OptimizerLRScheduler:
        """Return AdamW and the reference epoch-level plateau scheduler."""
        optimizer = torch.optim.AdamW(
            self.optim_groups(), lr=self.learning_rate, betas=self.betas
        )
        if self.scheduler != "reduce_on_plateau":
            return optimizer

        plateau = torch.optim.lr_scheduler.ReduceLROnPlateau(
            optimizer,
            mode="min",
            factor=self.scheduler_factor,
            patience=self.scheduler_patience,
            threshold=self.scheduler_threshold,
        )
        scheduler_config: LRSchedulerConfigType = {
            "scheduler": plateau,
            "monitor": "val_loss",
            "interval": "epoch",
        }
        result: OptimizerLRSchedulerConfig = {
            "optimizer": optimizer,
            "lr_scheduler": scheduler_config,
        }
        return result

    def configure_gradient_clipping(
        self,
        optimizer: torch.optim.Optimizer,
        gradient_clip_val: float | None = None,
        gradient_clip_algorithm: str | None = None,
    ) -> None:
        """Clip in model registration order, matching the original loop."""
        del optimizer
        if gradient_clip_algorithm not in {None, "norm"}:
            raise ValueError(
                "Layout-Corrector supports norm gradient clipping only, "
                f"got {gradient_clip_algorithm!r}"
            )

        clip_value = (
            self.gradient_clip_norm if gradient_clip_val is None else gradient_clip_val
        )
        if clip_value <= 0.0:
            self.latest_gradient_norm = None
            return

        self.latest_gradient_norm = torch.nn.utils.clip_grad_norm_(
            self.model.parameters(),
            clip_value,
            norm_type=2.0,
            error_if_nonfinite=False,
        )

    @torch.no_grad()
    def preprocess(
        self, batch: dict[str, Shaped[torch.Tensor, "..."] | str]
    ) -> dict[str, Shaped[torch.Tensor, "..."]]:
        """Build the corrector's corruption and reconstruction targets."""
        input_ids = cast(torch.Tensor, batch["input_ids"]).long()
        self._ensure_reference_device(input_ids.device)
        timesteps, probabilities = self._sample_time(
            input_ids.shape[0], input_ids.device
        )
        xt, log_xt = self._corrupt(input_ids, timesteps)
        log_x0 = self._prediction_log_probs(log_xt, timesteps)
        x0_recon = self._reconstruct_previous(log_x0, log_xt, timesteps)
        padding_mask = input_ids == self.layout_dm_tokenizer.pad_token_id
        recon_acc = (input_ids == x0_recon).long()
        return {
            "t": timesteps,
            "pt": probabilities,
            "x0": input_ids,
            "xt": xt,
            "x0_recon": x0_recon,
            "recon_acc": recon_acc,
            "padding_mask": padding_mask,
        }

    def _sample_time(
        self, batch_size: int, device: torch.device
    ) -> tuple[Int[torch.Tensor, "batch"], Float[torch.Tensor, "batch"]]:
        history, counts = self._layout_dm_buffers()
        if not bool((counts > 10).all()):
            timesteps = randint(
                0,
                self.layout_dm_config.num_timesteps,
                size=(batch_size,),
                device=device,
            ).long()
            return timesteps, torch.full_like(
                timesteps,
                1.0 / self.layout_dm_config.num_timesteps,
                dtype=torch.float32,
            )

        weights = torch.sqrt(history + 1.0e-10) + 0.0001
        weights[0] = weights[1]
        probabilities = weights / weights.sum()
        timesteps = multinomial(
            probabilities.to(device), batch_size, replacement=True, device=device
        )
        return timesteps, probabilities.to(device).gather(0, timesteps)

    def _corrupt(
        self,
        input_ids: Int[torch.Tensor, "batch tokens"],
        timesteps: Int[torch.Tensor, "batch"],
    ) -> tuple[
        Int[torch.Tensor, "batch tokens"], Float[torch.Tensor, "batch vocab tokens"]
    ]:
        return self._reference_value().corrupt(input_ids, timesteps)

    def _prediction_log_probs(
        self,
        log_xt: Float[torch.Tensor, "batch vocab tokens"],
        timesteps: Int[torch.Tensor, "batch"],
    ) -> Float[torch.Tensor, "batch vocab tokens"]:
        return self._reference_value().prediction_log_probs(log_xt, timesteps)

    def _reconstruct_previous(
        self,
        log_x0: Float[torch.Tensor, "batch vocab tokens"],
        log_xt: Float[torch.Tensor, "batch vocab tokens"],
        timesteps: Int[torch.Tensor, "batch"],
    ) -> Int[torch.Tensor, "batch tokens"]:
        return self._reference_value().reconstruct_previous(log_x0, log_xt, timesteps)

    def _corrector_loss(
        self, prepared: dict[str, Shaped[torch.Tensor, "..."]]
    ) -> tuple[Float[torch.Tensor, ""], dict[str, Shaped[torch.Tensor, "..."]]]:
        logits = self.model(
            input_ids=prepared["x0_recon"].long(),
            timesteps=prepared["t"].long(),
            padding_mask=prepared["padding_mask"].bool(),
        ).logits
        target = prepared["recon_acc"].float()
        weights = torch.tensor(
            self.config.attr_loss_weights,
            device=logits.device,
            dtype=logits.dtype,
        ).repeat(self.config.max_seq_length)
        bce = F.binary_cross_entropy_with_logits(logits, target, reduction="none")
        weighted = bce * weights.unsqueeze(0)
        loss = weighted.mean()
        trace: dict[str, Shaped[torch.Tensor, "..."]] = {
            **prepared,
            "logits": logits.detach(),
            "bce_loss": bce.detach(),
            "weighted_bce_loss": weighted.detach(),
            "train_loss": loss.detach(),
        }
        return loss, trace

    def training_step(
        self, batch: dict[str, Shaped[torch.Tensor, "..."] | str], batch_idx: int
    ) -> Float[torch.Tensor, ""]:
        """Run one reference-order corrector update."""
        del batch_idx
        prepared = self.preprocess(batch)
        loss, self.latest_step_trace = self._corrector_loss(prepared)
        self.log("train_loss", loss, prog_bar=True)
        return loss

    def validation_step(
        self, batch: dict[str, Shaped[torch.Tensor, "..."] | str], batch_idx: int
    ) -> Float[torch.Tensor, ""]:
        """Evaluate one batch before the epoch-level scheduler update."""
        del batch_idx
        prepared = self.preprocess(batch)
        loss, _ = self._corrector_loss(prepared)
        self._validation_losses.append(loss.detach())
        return loss

    def on_validation_epoch_start(self) -> None:
        """Reset the per-batch values used by the reference-style reduction."""
        self._validation_losses.clear()

    def _validation_loss_mean(self) -> Shaped[torch.Tensor, ""]:
        if not self._validation_losses:
            raise RuntimeError("validation produced no batch losses")

        first_loss = self._validation_losses[0]
        total = sum(
            float(loss.detach().cpu().item()) for loss in self._validation_losses
        )
        return torch.tensor(
            total / len(self._validation_losses),
            device=first_loss.device,
            dtype=torch.float64,
        )

    def on_validation_epoch_end(self) -> None:
        """Log the unweighted mean of validation-batch losses."""
        validation_loss = self._validation_loss_mean()
        self.latest_validation_loss = validation_loss.detach()
        self.log("val_loss", validation_loss, prog_bar=True, sync_dist=True)
__init__
__init__(
    *,
    config: LayoutCorrectorConfig,
    layout_dm_checkpoint_path: str | Path,
    cluster_centers_path: str | Path,
    learning_rate: float = 0.0005,
    weight_decay: float = 0.1,
    betas: tuple[float, float] = (0.9, 0.98),
    gradient_clip_norm: float = 1.0,
    scheduler: LayoutCorrectorTrainingScheduler
    | None = "reduce_on_plateau",
    scheduler_factor: float = 0.5,
    scheduler_patience: int = 2,
    scheduler_threshold: float = 0.01,
) -> None

Initialize the corrector and its immutable LayoutDM reference.

Source code in models/layout-corrector/src/layout_corrector/training/lightning_module.py
 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: LayoutCorrectorConfig,
    layout_dm_checkpoint_path: str | Path,
    cluster_centers_path: str | Path,
    learning_rate: float = 5.0e-4,
    weight_decay: float = 0.1,
    betas: tuple[float, float] = (0.9, 0.98),
    gradient_clip_norm: float = 1.0,
    scheduler: LayoutCorrectorTrainingScheduler | None = "reduce_on_plateau",
    scheduler_factor: float = 0.5,
    scheduler_patience: int = 2,
    scheduler_threshold: float = 1.0e-2,
) -> None:
    """Initialize the corrector and its immutable LayoutDM reference."""
    super().__init__()
    self.config = config
    self.pre_model_rng_digest = _rng_digest()
    initialization_device = _initialization_device()
    reference = FrozenLayoutDMReference.from_checkpoint(
        dataset_name=config.dataset_name,
        checkpoint_path=layout_dm_checkpoint_path,
        cluster_centers_path=cluster_centers_path,
        initialization_device=initialization_device,
    )
    self.layout_dm_config: LayoutDMConfig = reference.config
    self.layout_dm_checkpoint_sha256 = reference.checkpoint_sha256
    self.layout_dm_checkpoint_path = str(reference.checkpoint_path)
    self.layout_dm_tokenizer = reference.tokenizer
    object.__setattr__(self, "_reference", reference)
    object.__setattr__(self, "reference_model", reference.model)
    self.register_buffer("layout_dm_lt_history", reference.lt_history)
    self.register_buffer("layout_dm_lt_count", reference.lt_count)

    with torch.device("cpu"):
        self.model = LayoutCorrectorModel(**dict(config.config))

    self.model_construction_device = str(next(self.model.parameters()).device)
    torch.nn.Module.to(self.model, initialization_device)
    self.model.initialize_weights()
    self.initialization_device = str(initialization_device)

    self.learning_rate = learning_rate
    self.weight_decay = weight_decay
    self.betas = betas
    self.gradient_clip_norm = gradient_clip_norm
    self.scheduler = scheduler
    self.scheduler_factor = scheduler_factor
    self.scheduler_patience = scheduler_patience
    self.scheduler_threshold = scheduler_threshold
    self.latest_step_trace: dict[str, Shaped[torch.Tensor, "..."]] = {}
    self.latest_gradient_norm: Shaped[torch.Tensor, ""] | None = None
    self._validation_losses: list[Shaped[torch.Tensor, ""]] = []
    self.latest_validation_loss: Shaped[torch.Tensor, ""] | None = None
on_train_epoch_start
on_train_epoch_start() -> None

Keep the frozen LayoutDM reference in evaluation mode.

Source code in models/layout-corrector/src/layout_corrector/training/lightning_module.py
117
118
119
def on_train_epoch_start(self) -> None:
    """Keep the frozen LayoutDM reference in evaluation mode."""
    self._reference_model_value().eval()
optim_groups
optim_groups() -> list[
    dict[str, Iterable[nn.Parameter] | float]
]

Match the reference Linear/LayerNorm/Embedding decay partition.

Source code in models/layout-corrector/src/layout_corrector/training/lightning_module.py
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
def optim_groups(self) -> list[dict[str, Iterable[nn.Parameter] | float]]:
    """Match the reference Linear/LayerNorm/Embedding decay partition."""
    decay: set[str] = set()
    no_decay: set[str] = set()
    whitelist = (nn.Linear, nn.MultiheadAttention)
    blacklist = (nn.LayerNorm, nn.Embedding)
    for module_name, module in self.model.model.named_modules():
        for parameter_name, _ in module.named_parameters(recurse=False):
            name = (
                f"{module_name}.{parameter_name}" if module_name else parameter_name
            )
            if parameter_name.endswith("bias"):
                no_decay.add(name)
            elif parameter_name.endswith("weight") and isinstance(
                module, whitelist
            ):
                decay.add(name)
            elif parameter_name.endswith("weight") and isinstance(
                module, blacklist
            ):
                no_decay.add(name)
            else:
                no_decay.add(name)

    parameters = dict(self.model.model.named_parameters())
    overlap = decay & no_decay
    if overlap:
        raise RuntimeError(
            f"Parameters assigned to both optimizer groups: {overlap}"
        )

    missing = set(parameters) - (decay | no_decay)
    if missing:
        raise RuntimeError(f"Parameters missing from optimizer groups: {missing}")

    return [
        {
            "params": [parameters[name] for name in sorted(decay)],
            "weight_decay": self.weight_decay,
        },
        {
            "params": [parameters[name] for name in sorted(no_decay)],
            "weight_decay": 0.0,
        },
    ]
configure_optimizers
configure_optimizers() -> OptimizerLRScheduler

Return AdamW and the reference epoch-level plateau scheduler.

Source code in models/layout-corrector/src/layout_corrector/training/lightning_module.py
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
def configure_optimizers(self) -> OptimizerLRScheduler:
    """Return AdamW and the reference epoch-level plateau scheduler."""
    optimizer = torch.optim.AdamW(
        self.optim_groups(), lr=self.learning_rate, betas=self.betas
    )
    if self.scheduler != "reduce_on_plateau":
        return optimizer

    plateau = torch.optim.lr_scheduler.ReduceLROnPlateau(
        optimizer,
        mode="min",
        factor=self.scheduler_factor,
        patience=self.scheduler_patience,
        threshold=self.scheduler_threshold,
    )
    scheduler_config: LRSchedulerConfigType = {
        "scheduler": plateau,
        "monitor": "val_loss",
        "interval": "epoch",
    }
    result: OptimizerLRSchedulerConfig = {
        "optimizer": optimizer,
        "lr_scheduler": scheduler_config,
    }
    return result
configure_gradient_clipping
configure_gradient_clipping(
    optimizer: Optimizer,
    gradient_clip_val: float | None = None,
    gradient_clip_algorithm: str | None = None,
) -> None

Clip in model registration order, matching the original loop.

Source code in models/layout-corrector/src/layout_corrector/training/lightning_module.py
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
def configure_gradient_clipping(
    self,
    optimizer: torch.optim.Optimizer,
    gradient_clip_val: float | None = None,
    gradient_clip_algorithm: str | None = None,
) -> None:
    """Clip in model registration order, matching the original loop."""
    del optimizer
    if gradient_clip_algorithm not in {None, "norm"}:
        raise ValueError(
            "Layout-Corrector supports norm gradient clipping only, "
            f"got {gradient_clip_algorithm!r}"
        )

    clip_value = (
        self.gradient_clip_norm if gradient_clip_val is None else gradient_clip_val
    )
    if clip_value <= 0.0:
        self.latest_gradient_norm = None
        return

    self.latest_gradient_norm = torch.nn.utils.clip_grad_norm_(
        self.model.parameters(),
        clip_value,
        norm_type=2.0,
        error_if_nonfinite=False,
    )
preprocess
preprocess(
    batch: dict[str, Shaped[Tensor, "..."] | str],
) -> dict[str, Shaped[torch.Tensor, "..."]]

Build the corrector's corruption and reconstruction targets.

Source code in models/layout-corrector/src/layout_corrector/training/lightning_module.py
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
@torch.no_grad()
def preprocess(
    self, batch: dict[str, Shaped[torch.Tensor, "..."] | str]
) -> dict[str, Shaped[torch.Tensor, "..."]]:
    """Build the corrector's corruption and reconstruction targets."""
    input_ids = cast(torch.Tensor, batch["input_ids"]).long()
    self._ensure_reference_device(input_ids.device)
    timesteps, probabilities = self._sample_time(
        input_ids.shape[0], input_ids.device
    )
    xt, log_xt = self._corrupt(input_ids, timesteps)
    log_x0 = self._prediction_log_probs(log_xt, timesteps)
    x0_recon = self._reconstruct_previous(log_x0, log_xt, timesteps)
    padding_mask = input_ids == self.layout_dm_tokenizer.pad_token_id
    recon_acc = (input_ids == x0_recon).long()
    return {
        "t": timesteps,
        "pt": probabilities,
        "x0": input_ids,
        "xt": xt,
        "x0_recon": x0_recon,
        "recon_acc": recon_acc,
        "padding_mask": padding_mask,
    }
training_step
training_step(
    batch: dict[str, Shaped[Tensor, "..."] | str],
    batch_idx: int,
) -> Float[torch.Tensor, ""]

Run one reference-order corrector update.

Source code in models/layout-corrector/src/layout_corrector/training/lightning_module.py
343
344
345
346
347
348
349
350
351
def training_step(
    self, batch: dict[str, Shaped[torch.Tensor, "..."] | str], batch_idx: int
) -> Float[torch.Tensor, ""]:
    """Run one reference-order corrector update."""
    del batch_idx
    prepared = self.preprocess(batch)
    loss, self.latest_step_trace = self._corrector_loss(prepared)
    self.log("train_loss", loss, prog_bar=True)
    return loss
validation_step
validation_step(
    batch: dict[str, Shaped[Tensor, "..."] | str],
    batch_idx: int,
) -> Float[torch.Tensor, ""]

Evaluate one batch before the epoch-level scheduler update.

Source code in models/layout-corrector/src/layout_corrector/training/lightning_module.py
353
354
355
356
357
358
359
360
361
def validation_step(
    self, batch: dict[str, Shaped[torch.Tensor, "..."] | str], batch_idx: int
) -> Float[torch.Tensor, ""]:
    """Evaluate one batch before the epoch-level scheduler update."""
    del batch_idx
    prepared = self.preprocess(batch)
    loss, _ = self._corrector_loss(prepared)
    self._validation_losses.append(loss.detach())
    return loss
on_validation_epoch_start
on_validation_epoch_start() -> None

Reset the per-batch values used by the reference-style reduction.

Source code in models/layout-corrector/src/layout_corrector/training/lightning_module.py
363
364
365
def on_validation_epoch_start(self) -> None:
    """Reset the per-batch values used by the reference-style reduction."""
    self._validation_losses.clear()
on_validation_epoch_end
on_validation_epoch_end() -> None

Log the unweighted mean of validation-batch losses.

Source code in models/layout-corrector/src/layout_corrector/training/lightning_module.py
381
382
383
384
385
def on_validation_epoch_end(self) -> None:
    """Log the unweighted mean of validation-batch losses."""
    validation_loss = self._validation_loss_mean()
    self.latest_validation_loss = validation_loss.detach()
    self.log("val_loss", validation_loss, prog_bar=True, sync_dist=True)

parity

Shared parity helpers for the Layout-Corrector training adapter.

ProductionTraceCallback

Bases: Callback

Record the configured Lightning training path for parity evidence.

Source code in models/layout-corrector/src/layout_corrector/training/parity.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
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 ProductionTraceCallback(Callback):
    """Record the configured Lightning training path for parity evidence."""

    def __init__(
        self,
        *,
        trace_path: str,
        final_state_path: str,
        trace_seed: int,
    ) -> None:
        """Initialize paths and buffers for one production training run.

        Args:
            trace_path: JSON output path for the per-step trace.
            final_state_path: Output path for the final model state.
            trace_seed: Seed recorded for the production training process.
        """
        self.trace_path = Path(trace_path)
        self.final_state_path = Path(final_state_path)
        self.trace_seed = trace_seed
        self.rows: list[dict[str, object]] = []
        self._batch_rng_digest: str | None = None
        self._batch_digest: dict[str, str] = {}
        self._pre_loader_rng_digest: str | None = None
        self._validation_executed = False
        self._validation_loss: float | None = None

    def on_fit_start(self, trainer: Trainer, pl_module: LightningModule) -> None:
        """Record the production RNG state before the loader is iterated."""
        del trainer
        module = cast(LayoutCorrectorTrainingModule, pl_module)
        module.train()
        self._pre_loader_rng_digest = _rng_digest(capture_rng_state())

    def on_train_batch_start(
        self,
        trainer: Trainer,
        pl_module: LightningModule,
        batch: Mapping[str, Shaped[torch.Tensor, "..."] | str],
        batch_idx: int,
    ) -> None:
        """Capture input and RNG digests at the production batch boundary."""
        del trainer, pl_module
        del batch_idx

        self._batch_rng_digest = _rng_digest(capture_rng_state())
        self._batch_digest = {
            "input_ids": _tensor_digest(
                cast(Shaped[torch.Tensor, "..."], batch["input_ids"])
            ),
            "attention_mask": _tensor_digest(
                cast(Shaped[torch.Tensor, "..."], batch["attention_mask"])
            ),
        }

    def on_train_batch_end(
        self,
        trainer: Trainer,
        pl_module: LightningModule,
        outputs: STEP_OUTPUT,
        batch: Mapping[str, Shaped[torch.Tensor, "..."] | str],
        batch_idx: int,
    ) -> None:
        """Record the completed production update and its state digests."""
        del outputs
        module = cast(LayoutCorrectorTrainingModule, pl_module)
        trace = module.latest_step_trace
        gradient_norm = module.latest_gradient_norm
        if gradient_norm is None or self._batch_rng_digest is None:
            raise RuntimeError("production trace callback observed an incomplete step")

        optimizer = trainer.optimizers[0]
        sample_ids = batch.get("id", [])
        normalized_sample_ids = (
            [str(value) for value in sample_ids]
            if isinstance(sample_ids, (list, tuple))
            else [str(sample_ids)]
        )
        self.rows.append(
            {
                "step": batch_idx,
                "sample_ids": normalized_sample_ids,
                "loss": float(trace["train_loss"].detach().cpu().item()),
                "gradient_norm": float(gradient_norm.detach().cpu().item()),
                "learning_rate": float(optimizer.param_groups[0]["lr"]),
                "optimizer_state_digest": _optimizer_state_digest(optimizer),
                "parameter_state_digest": _parameter_state_digest(
                    module.model.model.state_dict()
                ),
                "rng_digest": self._batch_rng_digest,
                "input_ids_digest": self._batch_digest["input_ids"],
                "attention_mask_digest": self._batch_digest["attention_mask"],
                "timesteps_digest": _tensor_digest(trace["t"]),
                "importance_probability_digest": _tensor_digest(trace["pt"]),
                "corrupted_tokens_digest": _tensor_digest(trace["xt"]),
                "reconstructed_tokens_digest": _tensor_digest(trace["x0_recon"]),
                "bce_loss_digest": _tensor_digest(trace["bce_loss"]),
                "weighted_bce_loss_digest": _tensor_digest(trace["weighted_bce_loss"]),
                "model_training": module.model.training,
            }
        )

    def on_validation_epoch_end(
        self, trainer: Trainer, pl_module: LightningModule
    ) -> None:
        """Capture the validation metric consumed by the plateau scheduler."""
        metric = trainer.callback_metrics.get("val_loss")
        if not isinstance(metric, torch.Tensor):
            module = cast(LayoutCorrectorTrainingModule, pl_module)
            metric = module.latest_validation_loss
            if metric is None and module._validation_losses:
                first_loss = module._validation_losses[0]
                total = sum(
                    float(loss.detach().cpu().item())
                    for loss in module._validation_losses
                )
                metric = torch.tensor(
                    total / len(module._validation_losses),
                    device=first_loss.device,
                    dtype=torch.float64,
                )

        if not isinstance(metric, torch.Tensor):
            raise RuntimeError("production trace did not receive val_loss")

        self._validation_executed = True
        self._validation_loss = float(metric.detach().cpu().item())

    def on_fit_end(self, trainer: Trainer, pl_module: LightningModule) -> None:
        """Persist the final state and production wiring metadata."""
        module = cast(LayoutCorrectorTrainingModule, pl_module)
        torch.save(module.model.model.state_dict(), self.final_state_path)
        scheduler_state: DigestValue = {}
        scheduler_class: str | None = None
        if trainer.lr_scheduler_configs:
            scheduler = trainer.lr_scheduler_configs[0].scheduler
            scheduler_class = type(scheduler).__name__
            scheduler_state = cast("DigestValue", scheduler.state_dict())

        datamodule = cast(_TrainerWithDataModule, trainer).datamodule
        if datamodule is None:
            raise RuntimeError("production trace did not receive a data module")

        validation_batches = trainer.num_val_batches
        if isinstance(validation_batches, list):
            validation_batches = sum(validation_batches)

        self.trace_path.parent.mkdir(parents=True, exist_ok=True)
        self.trace_path.write_text(
            json.dumps(
                {
                    "rows": self.rows,
                    "scheduler_class": scheduler_class,
                    "scheduler_state": scheduler_state,
                    "trace_seed": self.trace_seed,
                    "pre_model_rng_digest": module.pre_model_rng_digest,
                    "pre_loader_rng_digest": self._pre_loader_rng_digest,
                    "scheduler_state_digest": _scheduler_state_digest(scheduler_state),
                    "validation_executed": self._validation_executed,
                    "validation_loss": self._validation_loss,
                    "validation_batches": validation_batches,
                    "train_batches": trainer.num_training_batches,
                    "observed_train_batches": len(self.rows),
                    "max_steps": trainer.max_steps,
                    "num_workers": getattr(datamodule, "num_workers", None),
                    "max_epochs": trainer.max_epochs,
                    "model_training": module.model.training,
                },
                sort_keys=True,
            )
            + "\n"
        )
__init__
__init__(
    *,
    trace_path: str,
    final_state_path: str,
    trace_seed: int,
) -> None

Initialize paths and buffers for one production training run.

Parameters:

Name Type Description Default
trace_path str

JSON output path for the per-step trace.

required
final_state_path str

Output path for the final model state.

required
trace_seed int

Seed recorded for the production training process.

required
Source code in models/layout-corrector/src/layout_corrector/training/parity.py
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
def __init__(
    self,
    *,
    trace_path: str,
    final_state_path: str,
    trace_seed: int,
) -> None:
    """Initialize paths and buffers for one production training run.

    Args:
        trace_path: JSON output path for the per-step trace.
        final_state_path: Output path for the final model state.
        trace_seed: Seed recorded for the production training process.
    """
    self.trace_path = Path(trace_path)
    self.final_state_path = Path(final_state_path)
    self.trace_seed = trace_seed
    self.rows: list[dict[str, object]] = []
    self._batch_rng_digest: str | None = None
    self._batch_digest: dict[str, str] = {}
    self._pre_loader_rng_digest: str | None = None
    self._validation_executed = False
    self._validation_loss: float | None = None
on_fit_start
on_fit_start(
    trainer: Trainer, pl_module: LightningModule
) -> None

Record the production RNG state before the loader is iterated.

Source code in models/layout-corrector/src/layout_corrector/training/parity.py
155
156
157
158
159
160
def on_fit_start(self, trainer: Trainer, pl_module: LightningModule) -> None:
    """Record the production RNG state before the loader is iterated."""
    del trainer
    module = cast(LayoutCorrectorTrainingModule, pl_module)
    module.train()
    self._pre_loader_rng_digest = _rng_digest(capture_rng_state())
on_train_batch_start
on_train_batch_start(
    trainer: Trainer,
    pl_module: LightningModule,
    batch: Mapping[str, Shaped[Tensor, "..."] | str],
    batch_idx: int,
) -> None

Capture input and RNG digests at the production batch boundary.

Source code in models/layout-corrector/src/layout_corrector/training/parity.py
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
def on_train_batch_start(
    self,
    trainer: Trainer,
    pl_module: LightningModule,
    batch: Mapping[str, Shaped[torch.Tensor, "..."] | str],
    batch_idx: int,
) -> None:
    """Capture input and RNG digests at the production batch boundary."""
    del trainer, pl_module
    del batch_idx

    self._batch_rng_digest = _rng_digest(capture_rng_state())
    self._batch_digest = {
        "input_ids": _tensor_digest(
            cast(Shaped[torch.Tensor, "..."], batch["input_ids"])
        ),
        "attention_mask": _tensor_digest(
            cast(Shaped[torch.Tensor, "..."], batch["attention_mask"])
        ),
    }
on_train_batch_end
on_train_batch_end(
    trainer: Trainer,
    pl_module: LightningModule,
    outputs: STEP_OUTPUT,
    batch: Mapping[str, Shaped[Tensor, "..."] | str],
    batch_idx: int,
) -> None

Record the completed production update and its state digests.

Source code in models/layout-corrector/src/layout_corrector/training/parity.py
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
def on_train_batch_end(
    self,
    trainer: Trainer,
    pl_module: LightningModule,
    outputs: STEP_OUTPUT,
    batch: Mapping[str, Shaped[torch.Tensor, "..."] | str],
    batch_idx: int,
) -> None:
    """Record the completed production update and its state digests."""
    del outputs
    module = cast(LayoutCorrectorTrainingModule, pl_module)
    trace = module.latest_step_trace
    gradient_norm = module.latest_gradient_norm
    if gradient_norm is None or self._batch_rng_digest is None:
        raise RuntimeError("production trace callback observed an incomplete step")

    optimizer = trainer.optimizers[0]
    sample_ids = batch.get("id", [])
    normalized_sample_ids = (
        [str(value) for value in sample_ids]
        if isinstance(sample_ids, (list, tuple))
        else [str(sample_ids)]
    )
    self.rows.append(
        {
            "step": batch_idx,
            "sample_ids": normalized_sample_ids,
            "loss": float(trace["train_loss"].detach().cpu().item()),
            "gradient_norm": float(gradient_norm.detach().cpu().item()),
            "learning_rate": float(optimizer.param_groups[0]["lr"]),
            "optimizer_state_digest": _optimizer_state_digest(optimizer),
            "parameter_state_digest": _parameter_state_digest(
                module.model.model.state_dict()
            ),
            "rng_digest": self._batch_rng_digest,
            "input_ids_digest": self._batch_digest["input_ids"],
            "attention_mask_digest": self._batch_digest["attention_mask"],
            "timesteps_digest": _tensor_digest(trace["t"]),
            "importance_probability_digest": _tensor_digest(trace["pt"]),
            "corrupted_tokens_digest": _tensor_digest(trace["xt"]),
            "reconstructed_tokens_digest": _tensor_digest(trace["x0_recon"]),
            "bce_loss_digest": _tensor_digest(trace["bce_loss"]),
            "weighted_bce_loss_digest": _tensor_digest(trace["weighted_bce_loss"]),
            "model_training": module.model.training,
        }
    )
on_validation_epoch_end
on_validation_epoch_end(
    trainer: Trainer, pl_module: LightningModule
) -> None

Capture the validation metric consumed by the plateau scheduler.

Source code in models/layout-corrector/src/layout_corrector/training/parity.py
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
def on_validation_epoch_end(
    self, trainer: Trainer, pl_module: LightningModule
) -> None:
    """Capture the validation metric consumed by the plateau scheduler."""
    metric = trainer.callback_metrics.get("val_loss")
    if not isinstance(metric, torch.Tensor):
        module = cast(LayoutCorrectorTrainingModule, pl_module)
        metric = module.latest_validation_loss
        if metric is None and module._validation_losses:
            first_loss = module._validation_losses[0]
            total = sum(
                float(loss.detach().cpu().item())
                for loss in module._validation_losses
            )
            metric = torch.tensor(
                total / len(module._validation_losses),
                device=first_loss.device,
                dtype=torch.float64,
            )

    if not isinstance(metric, torch.Tensor):
        raise RuntimeError("production trace did not receive val_loss")

    self._validation_executed = True
    self._validation_loss = float(metric.detach().cpu().item())
on_fit_end
on_fit_end(
    trainer: Trainer, pl_module: LightningModule
) -> None

Persist the final state and production wiring metadata.

Source code in models/layout-corrector/src/layout_corrector/training/parity.py
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
def on_fit_end(self, trainer: Trainer, pl_module: LightningModule) -> None:
    """Persist the final state and production wiring metadata."""
    module = cast(LayoutCorrectorTrainingModule, pl_module)
    torch.save(module.model.model.state_dict(), self.final_state_path)
    scheduler_state: DigestValue = {}
    scheduler_class: str | None = None
    if trainer.lr_scheduler_configs:
        scheduler = trainer.lr_scheduler_configs[0].scheduler
        scheduler_class = type(scheduler).__name__
        scheduler_state = cast("DigestValue", scheduler.state_dict())

    datamodule = cast(_TrainerWithDataModule, trainer).datamodule
    if datamodule is None:
        raise RuntimeError("production trace did not receive a data module")

    validation_batches = trainer.num_val_batches
    if isinstance(validation_batches, list):
        validation_batches = sum(validation_batches)

    self.trace_path.parent.mkdir(parents=True, exist_ok=True)
    self.trace_path.write_text(
        json.dumps(
            {
                "rows": self.rows,
                "scheduler_class": scheduler_class,
                "scheduler_state": scheduler_state,
                "trace_seed": self.trace_seed,
                "pre_model_rng_digest": module.pre_model_rng_digest,
                "pre_loader_rng_digest": self._pre_loader_rng_digest,
                "scheduler_state_digest": _scheduler_state_digest(scheduler_state),
                "validation_executed": self._validation_executed,
                "validation_loss": self._validation_loss,
                "validation_batches": validation_batches,
                "train_batches": trainer.num_training_batches,
                "observed_train_batches": len(self.rows),
                "max_steps": trainer.max_steps,
                "num_workers": getattr(datamodule, "num_workers", None),
                "max_epochs": trainer.max_epochs,
                "model_training": module.model.training,
            },
            sort_keys=True,
        )
        + "\n"
    )

build_layout_corrector_step_trace

build_layout_corrector_step_trace(
    name: str, values: Mapping[str, Shaped[Tensor, "..."]]
) -> StepTrace

Build the canonical Layout-Corrector trace from named tensors.

Source code in models/layout-corrector/src/layout_corrector/training/parity.py
316
317
318
319
320
321
322
def build_layout_corrector_step_trace(
    name: str,
    values: Mapping[str, Shaped[torch.Tensor, "..."]],
) -> StepTrace:
    """Build the canonical Layout-Corrector trace from named tensors."""
    tensors = {key: value for key, value in values.items() if key in TRACE_POINTS}
    return build_step_trace(name, tensors, metadata={"trace_points": TRACE_POINTS})

compare_layout_corrector_step

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

Compare the package and original pre-optimizer traces exactly.

Source code in models/layout-corrector/src/layout_corrector/training/parity.py
325
326
327
328
329
330
331
332
333
def compare_layout_corrector_step(
    reference: StepTrace,
    target: StepTrace,
    *,
    tolerance: TensorTolerance | None = None,
) -> StepReport:
    """Compare the package and original pre-optimizer traces exactly."""
    tolerances = {name: tolerance or TensorTolerance() for name in reference.tensors}
    return compare_step_trace(reference, target, tolerances)

compare_layout_corrector_optimizer_step

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

Compare post-step package and original parameter state exactly.

Source code in models/layout-corrector/src/layout_corrector/training/parity.py
336
337
338
339
340
341
342
343
344
def compare_layout_corrector_optimizer_step(
    reference_state: Mapping[str, Shaped[torch.Tensor, "..."]],
    target_state: Mapping[str, Shaped[torch.Tensor, "..."]],
    *,
    tolerance: TensorTolerance | None = None,
) -> OptimizerStepReport:
    """Compare post-step package and original parameter state exactly."""
    tolerances = {name: tolerance or TensorTolerance() for name in reference_state}
    return compare_optimizer_step(reference_state, target_state, tolerances)

reference

Frozen LayoutDM reference used by Layout-Corrector training.

FrozenLayoutDMReference dataclass

Package-side frozen LayoutDM model, tokenizer, and schedules.

Source code in models/layout-corrector/src/layout_corrector/training/reference.py
 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
216
217
218
219
220
221
222
223
224
@dataclass
class FrozenLayoutDMReference:
    """Package-side frozen LayoutDM model, tokenizer, and schedules."""

    dataset_name: str
    checkpoint_path: Path
    config: LayoutDMConfig
    tokenizer: LayoutDMTokenizer
    model: LayoutDMDenoiser
    scheduler: LayoutDMScheduler
    lt_history: Float[torch.Tensor, "timesteps"]
    lt_count: Float[torch.Tensor, "timesteps"]
    checkpoint_sha256: str

    @classmethod
    def from_checkpoint(
        cls,
        *,
        dataset_name: str,
        checkpoint_path: str | Path,
        cluster_centers_path: str | Path,
        initialization_device: torch.device | str = "cpu",
    ) -> "FrozenLayoutDMReference":
        """Load the selected seed-0 checkpoint and preserve its state tensors."""
        path = Path(checkpoint_path)
        raw = torch.load(path, map_location="cpu", weights_only=False)
        if not isinstance(raw, dict):
            raise TypeError("LayoutDM checkpoint must be a state dictionary")

        expected = FROZEN_LAYOUT_DM_SHA256[dataset_name]
        observed = hashlib.sha256(path.read_bytes()).hexdigest()
        if observed != expected:
            raise ValueError(
                f"Unexpected frozen LayoutDM SHA-256 for {dataset_name}: {observed}"
            )

        config = LayoutDMConfig(
            dataset_name=dataset_name,
            max_seq_length=25,
            num_bin_bboxes=32,
            bbox_quantization="kmeans",
            cluster_centers_path=str(cluster_centers_path),
            hidden_size=464,
            num_attention_heads=8,
            num_hidden_layers=4,
            intermediate_size=1856,
            dropout=0.0,
            timestep_type="adalayernorm",
            num_timesteps=100,
            q_type="constrained",
        )
        tokenizer = LayoutDMTokenizer(config)
        with skip_layoutdm_initialization():
            with torch.device("cpu"):
                model = LayoutDMDenoiser(
                    vocab_size=config.vocab_size,
                    max_token_length=config.max_token_length,
                    hidden_size=config.hidden_size,
                    num_attention_heads=config.num_attention_heads,
                    num_hidden_layers=config.num_hidden_layers,
                    intermediate_size=config.intermediate_size,
                    dropout=config.dropout,
                    timestep_type=config.timestep_type,
                )

        target_device = torch.device(initialization_device)
        torch.nn.Module.to(model, target_device)
        model.initialize_weights()
        model.load_state_dict(split_original_state_dict(raw), strict=True)
        torch.nn.Module.to(model, "cpu")
        for parameter in model.parameters():
            parameter.requires_grad_(False)

        model.eval()

        scheduler = LayoutDMScheduler(
            num_timesteps=config.num_timesteps,
            q_type="constrained",
            mask_token_id=config.mask_token_id,
            pad_token_id=config.pad_token_id,
            var_order=tuple(config.var_order.split("-")),
            per_var_full_ids=tokenizer.full_id_maps(),
            att_1=config.att_1,
            att_T=config.att_T,
            ctt_1=config.ctt_1,
            ctt_T=config.ctt_T,
            vocab_size=config.vocab_size,
        )
        for key in tokenizer.var_names:
            scheduler.schedules[key] = tuple(
                raw[f"model.module.{key}_{suffix}"].clone()
                for suffix in (
                    "log_at",
                    "log_bt",
                    "log_ct",
                    "log_cumprod_at",
                    "log_cumprod_bt",
                    "log_cumprod_ct",
                )
            )

        return cls(
            dataset_name=dataset_name,
            checkpoint_path=path,
            config=config,
            tokenizer=tokenizer,
            model=model,
            scheduler=scheduler,
            lt_history=raw["model.module.Lt_history"].clone(),
            lt_count=raw["model.module.Lt_count"].clone(),
            checkpoint_sha256=observed,
        )

    def to(self, device: torch.device | str) -> "FrozenLayoutDMReference":
        """Move the reference model and its comparison buffers."""
        target_device = torch.device(device)
        torch.nn.Module.to(self.model, target_device)
        self.lt_history = self.lt_history.to(target_device)
        self.lt_count = self.lt_count.to(target_device)
        return self

    def sample_time(
        self, batch_size: int, *, device: torch.device
    ) -> tuple[Int[torch.Tensor, "batch"], Float[torch.Tensor, "batch"]]:
        """Use the released importance sampler and its frozen loss history."""
        if not bool((self.lt_count > 10).all()):
            t = randint(0, 100, size=(batch_size,), device=device).long()
            return t, torch.full_like(t, 1.0 / 100, dtype=torch.float32)

        history = torch.sqrt(self.lt_history + 1.0e-10) + 0.0001
        history[0] = history[1]
        probabilities = history / history.sum()
        t = multinomial(probabilities, batch_size, replacement=True, device=device)
        return t, probabilities.gather(dim=0, index=t)

    def corrupt(
        self,
        input_ids: Int[torch.Tensor, "batch tokens"],
        timesteps: Int[torch.Tensor, "batch"],
    ) -> tuple[
        Int[torch.Tensor, "batch tokens"], Float[torch.Tensor, "batch vocab tokens"]
    ]:
        """Apply constrained LayoutDM corruption with reference random sampling."""
        batch_size, token_length = input_ids.shape
        step = len(self.tokenizer.var_names)
        reshaped = input_ids.reshape(batch_size, token_length // step, step)
        full_logs: list[Float[torch.Tensor, "batch vocab tokens"]] = []
        full_ids: list[Int[torch.Tensor, "batch tokens"]] = []
        for index, key in enumerate(self.tokenizer.var_names):
            partial = self.tokenizer.full_to_partial_ids(reshaped[..., index], key)
            log_start = index_to_log_onehot(
                partial, len(self.tokenizer.full_id_maps()[key])
            )
            log_prob = self.scheduler._q_pred(log_start, timesteps, key)
            uniform = rand(*log_prob.shape, device=log_prob.device)
            gumbel = -torch.log(-torch.log(uniform + 1.0e-30) + 1.0e-30)
            sampled = (gumbel + log_prob).argmax(dim=1)
            log_sample = index_to_log_onehot(
                sampled, len(self.tokenizer.full_id_maps()[key])
            )
            full_logs.append(self.tokenizer.partial_to_full_log_probs(log_sample, key))
            full_ids.append(self.tokenizer.partial_to_full_ids(sampled, key))

        log_xt = torch.stack(full_logs, dim=-1).reshape(
            batch_size, self.config.vocab_size, token_length
        )
        xt = torch.stack(full_ids, dim=-1).reshape(batch_size, token_length)
        return xt, log_xt

    def prediction_log_probs(
        self,
        log_xt: Float[torch.Tensor, "batch vocab tokens"],
        timesteps: Int[torch.Tensor, "batch"],
    ) -> Float[torch.Tensor, "batch vocab tokens"]:
        """Predict clean-token probabilities with the frozen denoiser."""
        logits = self.model(input_ids=log_xt.argmax(dim=1), timesteps=timesteps).logits
        return self.scheduler.predict_start(logits)

    def reconstruct_previous(
        self,
        log_x0: Float[torch.Tensor, "batch vocab tokens"],
        log_xt: Float[torch.Tensor, "batch vocab tokens"],
        timesteps: Int[torch.Tensor, "batch"],
    ) -> Int[torch.Tensor, "batch tokens"]:
        """Sample the reference x_t-1 reconstruction, excluding MASK."""
        log_previous = self.scheduler.q_posterior(log_x0, log_xt, timesteps)
        log_previous[:, self.config.mask_token_id, :] = -70.0
        probabilities = log_previous.softmax(dim=1).movedim(1, -1)
        sampled = multinomial(
            probabilities.reshape(-1, probabilities.shape[-1]),
            1,
            replacement=False,
            device=log_previous.device,
        ).reshape(log_previous.shape[0], -1)
        return sampled
from_checkpoint classmethod
from_checkpoint(
    *,
    dataset_name: str,
    checkpoint_path: str | Path,
    cluster_centers_path: str | Path,
    initialization_device: device | str = "cpu",
) -> "FrozenLayoutDMReference"

Load the selected seed-0 checkpoint and preserve its state tensors.

Source code in models/layout-corrector/src/layout_corrector/training/reference.py
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
@classmethod
def from_checkpoint(
    cls,
    *,
    dataset_name: str,
    checkpoint_path: str | Path,
    cluster_centers_path: str | Path,
    initialization_device: torch.device | str = "cpu",
) -> "FrozenLayoutDMReference":
    """Load the selected seed-0 checkpoint and preserve its state tensors."""
    path = Path(checkpoint_path)
    raw = torch.load(path, map_location="cpu", weights_only=False)
    if not isinstance(raw, dict):
        raise TypeError("LayoutDM checkpoint must be a state dictionary")

    expected = FROZEN_LAYOUT_DM_SHA256[dataset_name]
    observed = hashlib.sha256(path.read_bytes()).hexdigest()
    if observed != expected:
        raise ValueError(
            f"Unexpected frozen LayoutDM SHA-256 for {dataset_name}: {observed}"
        )

    config = LayoutDMConfig(
        dataset_name=dataset_name,
        max_seq_length=25,
        num_bin_bboxes=32,
        bbox_quantization="kmeans",
        cluster_centers_path=str(cluster_centers_path),
        hidden_size=464,
        num_attention_heads=8,
        num_hidden_layers=4,
        intermediate_size=1856,
        dropout=0.0,
        timestep_type="adalayernorm",
        num_timesteps=100,
        q_type="constrained",
    )
    tokenizer = LayoutDMTokenizer(config)
    with skip_layoutdm_initialization():
        with torch.device("cpu"):
            model = LayoutDMDenoiser(
                vocab_size=config.vocab_size,
                max_token_length=config.max_token_length,
                hidden_size=config.hidden_size,
                num_attention_heads=config.num_attention_heads,
                num_hidden_layers=config.num_hidden_layers,
                intermediate_size=config.intermediate_size,
                dropout=config.dropout,
                timestep_type=config.timestep_type,
            )

    target_device = torch.device(initialization_device)
    torch.nn.Module.to(model, target_device)
    model.initialize_weights()
    model.load_state_dict(split_original_state_dict(raw), strict=True)
    torch.nn.Module.to(model, "cpu")
    for parameter in model.parameters():
        parameter.requires_grad_(False)

    model.eval()

    scheduler = LayoutDMScheduler(
        num_timesteps=config.num_timesteps,
        q_type="constrained",
        mask_token_id=config.mask_token_id,
        pad_token_id=config.pad_token_id,
        var_order=tuple(config.var_order.split("-")),
        per_var_full_ids=tokenizer.full_id_maps(),
        att_1=config.att_1,
        att_T=config.att_T,
        ctt_1=config.ctt_1,
        ctt_T=config.ctt_T,
        vocab_size=config.vocab_size,
    )
    for key in tokenizer.var_names:
        scheduler.schedules[key] = tuple(
            raw[f"model.module.{key}_{suffix}"].clone()
            for suffix in (
                "log_at",
                "log_bt",
                "log_ct",
                "log_cumprod_at",
                "log_cumprod_bt",
                "log_cumprod_ct",
            )
        )

    return cls(
        dataset_name=dataset_name,
        checkpoint_path=path,
        config=config,
        tokenizer=tokenizer,
        model=model,
        scheduler=scheduler,
        lt_history=raw["model.module.Lt_history"].clone(),
        lt_count=raw["model.module.Lt_count"].clone(),
        checkpoint_sha256=observed,
    )
to
to(device: device | str) -> 'FrozenLayoutDMReference'

Move the reference model and its comparison buffers.

Source code in models/layout-corrector/src/layout_corrector/training/reference.py
143
144
145
146
147
148
149
def to(self, device: torch.device | str) -> "FrozenLayoutDMReference":
    """Move the reference model and its comparison buffers."""
    target_device = torch.device(device)
    torch.nn.Module.to(self.model, target_device)
    self.lt_history = self.lt_history.to(target_device)
    self.lt_count = self.lt_count.to(target_device)
    return self
sample_time
sample_time(
    batch_size: int, *, device: device
) -> tuple[
    Int[torch.Tensor, "batch"], Float[torch.Tensor, "batch"]
]

Use the released importance sampler and its frozen loss history.

Source code in models/layout-corrector/src/layout_corrector/training/reference.py
151
152
153
154
155
156
157
158
159
160
161
162
163
def sample_time(
    self, batch_size: int, *, device: torch.device
) -> tuple[Int[torch.Tensor, "batch"], Float[torch.Tensor, "batch"]]:
    """Use the released importance sampler and its frozen loss history."""
    if not bool((self.lt_count > 10).all()):
        t = randint(0, 100, size=(batch_size,), device=device).long()
        return t, torch.full_like(t, 1.0 / 100, dtype=torch.float32)

    history = torch.sqrt(self.lt_history + 1.0e-10) + 0.0001
    history[0] = history[1]
    probabilities = history / history.sum()
    t = multinomial(probabilities, batch_size, replacement=True, device=device)
    return t, probabilities.gather(dim=0, index=t)
corrupt
corrupt(
    input_ids: Int[Tensor, "batch tokens"],
    timesteps: Int[Tensor, "batch"],
) -> tuple[
    Int[torch.Tensor, "batch tokens"],
    Float[torch.Tensor, "batch vocab tokens"],
]

Apply constrained LayoutDM corruption with reference random sampling.

Source code in models/layout-corrector/src/layout_corrector/training/reference.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
def corrupt(
    self,
    input_ids: Int[torch.Tensor, "batch tokens"],
    timesteps: Int[torch.Tensor, "batch"],
) -> tuple[
    Int[torch.Tensor, "batch tokens"], Float[torch.Tensor, "batch vocab tokens"]
]:
    """Apply constrained LayoutDM corruption with reference random sampling."""
    batch_size, token_length = input_ids.shape
    step = len(self.tokenizer.var_names)
    reshaped = input_ids.reshape(batch_size, token_length // step, step)
    full_logs: list[Float[torch.Tensor, "batch vocab tokens"]] = []
    full_ids: list[Int[torch.Tensor, "batch tokens"]] = []
    for index, key in enumerate(self.tokenizer.var_names):
        partial = self.tokenizer.full_to_partial_ids(reshaped[..., index], key)
        log_start = index_to_log_onehot(
            partial, len(self.tokenizer.full_id_maps()[key])
        )
        log_prob = self.scheduler._q_pred(log_start, timesteps, key)
        uniform = rand(*log_prob.shape, device=log_prob.device)
        gumbel = -torch.log(-torch.log(uniform + 1.0e-30) + 1.0e-30)
        sampled = (gumbel + log_prob).argmax(dim=1)
        log_sample = index_to_log_onehot(
            sampled, len(self.tokenizer.full_id_maps()[key])
        )
        full_logs.append(self.tokenizer.partial_to_full_log_probs(log_sample, key))
        full_ids.append(self.tokenizer.partial_to_full_ids(sampled, key))

    log_xt = torch.stack(full_logs, dim=-1).reshape(
        batch_size, self.config.vocab_size, token_length
    )
    xt = torch.stack(full_ids, dim=-1).reshape(batch_size, token_length)
    return xt, log_xt
prediction_log_probs
prediction_log_probs(
    log_xt: Float[Tensor, "batch vocab tokens"],
    timesteps: Int[Tensor, "batch"],
) -> Float[torch.Tensor, "batch vocab tokens"]

Predict clean-token probabilities with the frozen denoiser.

Source code in models/layout-corrector/src/layout_corrector/training/reference.py
199
200
201
202
203
204
205
206
def prediction_log_probs(
    self,
    log_xt: Float[torch.Tensor, "batch vocab tokens"],
    timesteps: Int[torch.Tensor, "batch"],
) -> Float[torch.Tensor, "batch vocab tokens"]:
    """Predict clean-token probabilities with the frozen denoiser."""
    logits = self.model(input_ids=log_xt.argmax(dim=1), timesteps=timesteps).logits
    return self.scheduler.predict_start(logits)
reconstruct_previous
reconstruct_previous(
    log_x0: Float[Tensor, "batch vocab tokens"],
    log_xt: Float[Tensor, "batch vocab tokens"],
    timesteps: Int[Tensor, "batch"],
) -> Int[torch.Tensor, "batch tokens"]

Sample the reference x_t-1 reconstruction, excluding MASK.

Source code in models/layout-corrector/src/layout_corrector/training/reference.py
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
def reconstruct_previous(
    self,
    log_x0: Float[torch.Tensor, "batch vocab tokens"],
    log_xt: Float[torch.Tensor, "batch vocab tokens"],
    timesteps: Int[torch.Tensor, "batch"],
) -> Int[torch.Tensor, "batch tokens"]:
    """Sample the reference x_t-1 reconstruction, excluding MASK."""
    log_previous = self.scheduler.q_posterior(log_x0, log_xt, timesteps)
    log_previous[:, self.config.mask_token_id, :] = -70.0
    probabilities = log_previous.softmax(dim=1).movedim(1, -1)
    sampled = multinomial(
        probabilities.reshape(-1, probabilities.shape[-1]),
        1,
        replacement=False,
        device=log_previous.device,
    ).reshape(log_previous.shape[0], -1)
    return sampled